Merge pull request #4700 from ArekSredzki/async-document-hidden

Cache browser visibility state & emit visibilitychange event on change
This commit is contained in:
Cheng Zhao 2016-03-11 10:19:06 +09:00
commit abd466ee4a
13 changed files with 173 additions and 58 deletions

View file

@ -191,6 +191,14 @@ void Window::OnWindowFocus() {
Emit("focus"); Emit("focus");
} }
void Window::OnWindowShow() {
Emit("show");
}
void Window::OnWindowHide() {
Emit("hide");
}
void Window::OnWindowMaximize() { void Window::OnWindowMaximize() {
Emit("maximize"); Emit("maximize");
} }

View file

@ -58,6 +58,8 @@ class Window : public mate::TrackableObject<Window>,
void OnWindowClosed() override; void OnWindowClosed() override;
void OnWindowBlur() override; void OnWindowBlur() override;
void OnWindowFocus() override; void OnWindowFocus() override;
void OnWindowShow() override;
void OnWindowHide() override;
void OnWindowMaximize() override; void OnWindowMaximize() override;
void OnWindowUnmaximize() override; void OnWindowUnmaximize() override;
void OnWindowMinimize() override; void OnWindowMinimize() override;

View file

@ -428,6 +428,14 @@ void NativeWindow::NotifyWindowFocus() {
FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowFocus()); FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowFocus());
} }
void NativeWindow::NotifyWindowShow() {
FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowShow());
}
void NativeWindow::NotifyWindowHide() {
FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowHide());
}
void NativeWindow::NotifyWindowMaximize() { void NativeWindow::NotifyWindowMaximize() {
FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowMaximize()); FOR_EACH_OBSERVER(NativeWindowObserver, observers_, OnWindowMaximize());
} }

View file

@ -210,6 +210,8 @@ class NativeWindow : public base::SupportsUserData,
void NotifyWindowClosed(); void NotifyWindowClosed();
void NotifyWindowBlur(); void NotifyWindowBlur();
void NotifyWindowFocus(); void NotifyWindowFocus();
void NotifyWindowShow();
void NotifyWindowHide();
void NotifyWindowMaximize(); void NotifyWindowMaximize();
void NotifyWindowUnmaximize(); void NotifyWindowUnmaximize();
void NotifyWindowMinimize(); void NotifyWindowMinimize();

View file

@ -79,6 +79,21 @@ bool ScopedDisableResize::disable_resize_ = false;
return self; return self;
} }
- (void)windowDidChangeOcclusionState:(NSNotification *)notification {
// notification.object is the window that changed its state.
// It's safe to use self.window instead if you don't assign one delegate to many windows
NSWindow *window = notification.object;
// check occlusion binary flag
if (window.occlusionState & NSWindowOcclusionStateVisible) {
// The app is visible
shell_->NotifyWindowShow();
} else {
// The app is not visible
shell_->NotifyWindowHide();
}
}
- (void)windowDidBecomeMain:(NSNotification*)notification { - (void)windowDidBecomeMain:(NSNotification*)notification {
content::WebContents* web_contents = shell_->web_contents(); content::WebContents* web_contents = shell_->web_contents();
if (!web_contents) if (!web_contents)

View file

@ -42,6 +42,12 @@ class NativeWindowObserver {
// Called when window gains focus. // Called when window gains focus.
virtual void OnWindowFocus() {} virtual void OnWindowFocus() {}
// Called when window is shown.
virtual void OnWindowShow() {}
// Called when window is hidden.
virtual void OnWindowHide() {}
// Called when window state changed. // Called when window state changed.
virtual void OnWindowMaximize() {} virtual void OnWindowMaximize() {}
virtual void OnWindowUnmaximize() {} virtual void OnWindowUnmaximize() {}

View file

@ -302,6 +302,8 @@ bool NativeWindowViews::IsFocused() {
void NativeWindowViews::Show() { void NativeWindowViews::Show() {
window_->native_widget_private()->ShowWithWindowState(GetRestoredState()); window_->native_widget_private()->ShowWithWindowState(GetRestoredState());
NotifyWindowShow();
#if defined(USE_X11) #if defined(USE_X11)
if (global_menu_bar_) if (global_menu_bar_)
global_menu_bar_->OnWindowMapped(); global_menu_bar_->OnWindowMapped();
@ -311,6 +313,8 @@ void NativeWindowViews::Show() {
void NativeWindowViews::ShowInactive() { void NativeWindowViews::ShowInactive() {
window_->ShowInactive(); window_->ShowInactive();
NotifyWindowShow();
#if defined(USE_X11) #if defined(USE_X11)
if (global_menu_bar_) if (global_menu_bar_)
global_menu_bar_->OnWindowMapped(); global_menu_bar_->OnWindowMapped();
@ -320,6 +324,8 @@ void NativeWindowViews::ShowInactive() {
void NativeWindowViews::Hide() { void NativeWindowViews::Hide() {
window_->Hide(); window_->Hide();
NotifyWindowHide();
#if defined(USE_X11) #if defined(USE_X11)
if (global_menu_bar_) if (global_menu_bar_)
global_menu_bar_->OnWindowUnmapped(); global_menu_bar_->OnWindowUnmapped();

View file

@ -245,6 +245,14 @@ Emitted when the window loses focus.
Emitted when the window gains focus. Emitted when the window gains focus.
### Event: 'show'
Emitted when the window is shown.
### Event: 'hide'
Emitted when the window is hidden.
### Event: 'maximize' ### Event: 'maximize'
Emitted when window is maximized. Emitted when window is maximized.

View file

@ -20,7 +20,7 @@ BrowserWindow.prototype._init = function() {
} }
// Make new windows requested by links behave like "window.open" // Make new windows requested by links behave like "window.open"
this.webContents.on('-new-window', function(event, url, frameName) { this.webContents.on('-new-window', (event, url, frameName) => {
var options; var options;
options = { options = {
show: true, show: true,
@ -32,27 +32,21 @@ BrowserWindow.prototype._init = function() {
// window.resizeTo(...) // window.resizeTo(...)
// window.moveTo(...) // window.moveTo(...)
this.webContents.on('move', (function(_this) { this.webContents.on('move', (event, size) => {
return function(event, size) { return this.setBounds(size);
return _this.setBounds(size); });
};
})(this));
// Hide the auto-hide menu when webContents is focused. // Hide the auto-hide menu when webContents is focused.
this.webContents.on('activate', (function(_this) { this.webContents.on('activate', () => {
return function() { if (process.platform !== 'darwin' && this.isMenuBarAutoHide() && this.isMenuBarVisible()) {
if (process.platform !== 'darwin' && _this.isMenuBarAutoHide() && _this.isMenuBarVisible()) { return this.setMenuBarVisibility(false);
return _this.setMenuBarVisibility(false); }
} });
};
})(this));
// Forward the crashed event. // Forward the crashed event.
this.webContents.on('crashed', (function(_this) { this.webContents.on('crashed', () => {
return function() { return this.emit('crashed');
return _this.emit('crashed'); });
};
})(this));
// Change window title to page title. // Change window title to page title.
this.webContents.on('page-title-updated', (event, title) => { this.webContents.on('page-title-updated', (event, title) => {
@ -77,36 +71,40 @@ BrowserWindow.prototype._init = function() {
}); });
// Redirect focus/blur event to app instance too. // Redirect focus/blur event to app instance too.
this.on('blur', (function(_this) { this.on('blur', (event) => {
return function(event) { return app.emit('browser-window-blur', event, this);
return app.emit('browser-window-blur', event, _this); });
}; this.on('focus', (event) => {
})(this)); return app.emit('browser-window-focus', event, this);
this.on('focus', (function(_this) { });
return function(event) {
return app.emit('browser-window-focus', event, _this); // Evented visibilityState changes
}; this.on('show', () => {
})(this)); return this.webContents.send('ATOM_RENDERER_WINDOW_VISIBILITY_CHANGE', this.isVisible(), this.isMinimized());
});
this.on('hide', () => {
return this.webContents.send('ATOM_RENDERER_WINDOW_VISIBILITY_CHANGE', this.isVisible(), this.isMinimized());
});
this.on('minimize', () => {
return this.webContents.send('ATOM_RENDERER_WINDOW_VISIBILITY_CHANGE', this.isVisible(), this.isMinimized());
});
this.on('restore', () => {
return this.webContents.send('ATOM_RENDERER_WINDOW_VISIBILITY_CHANGE', this.isVisible(), this.isMinimized());
});
// Notify the creation of the window. // Notify the creation of the window.
app.emit('browser-window-created', {}, this); app.emit('browser-window-created', {}, this);
// Be compatible with old APIs. // Be compatible with old APIs.
this.webContents.on('devtools-focused', (function(_this) { this.webContents.on('devtools-focused', () => {
return function() { return this.emit('devtools-focused');
return _this.emit('devtools-focused'); });
}; this.webContents.on('devtools-opened', () => {
})(this)); return this.emit('devtools-opened');
this.webContents.on('devtools-opened', (function(_this) { });
return function() { this.webContents.on('devtools-closed', () => {
return _this.emit('devtools-opened'); return this.emit('devtools-closed');
}; });
})(this));
this.webContents.on('devtools-closed', (function(_this) {
return function() {
return _this.emit('devtools-closed');
};
})(this));
return Object.defineProperty(this, 'devToolsWebContents', { return Object.defineProperty(this, 'devToolsWebContents', {
enumerable: true, enumerable: true,
configurable: false, configurable: false,

View file

@ -3,7 +3,7 @@ const v8Util = process.atomBinding('v8_util');
var slice = [].slice; var slice = [].slice;
// Created by init.coffee. // Created by init.js.
const ipcRenderer = v8Util.getHiddenValue(global, 'ipc'); const ipcRenderer = v8Util.getHiddenValue(global, 'ipc');
ipcRenderer.send = function() { ipcRenderer.send = function() {

View file

@ -3,7 +3,7 @@ window.onload = function() {
InspectorFrontendHost.showContextMenuAtPoint = createMenu; InspectorFrontendHost.showContextMenuAtPoint = createMenu;
// Use dialog API to override file chooser dialog. // Use dialog API to override file chooser dialog.
return WebInspector.createFileSelectorElement = createFileSelectorElement; return (WebInspector.createFileSelectorElement = createFileSelectorElement);
}; };
var convertToMenuTemplate = function(items) { var convertToMenuTemplate = function(items) {

View file

@ -3,6 +3,16 @@ const remote = require('electron').remote;
var slice = [].slice; var slice = [].slice;
// Cache browser window visibility
var _isVisible = true;
var _isMinimized = false;
(function() {
var currentWindow;
currentWindow = remote.getCurrentWindow();
_isVisible = currentWindow.isVisible();
_isMinimized = currentWindow.isMinimized();
})();
// Helper function to resolve relative url. // Helper function to resolve relative url.
var a = window.top.document.createElement('a'); var a = window.top.document.createElement('a');
@ -30,7 +40,7 @@ var BrowserWindowProxy = (function() {
ipcRenderer.once("ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_" + this.guestId, (function(_this) { ipcRenderer.once("ATOM_SHELL_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_" + this.guestId, (function(_this) {
return function() { return function() {
BrowserWindowProxy.remove(_this.guestId); BrowserWindowProxy.remove(_this.guestId);
return _this.closed = true; return (_this.closed = true);
}; };
})(this)); })(this));
} }
@ -87,7 +97,9 @@ window.open = function(url, frameName, features) {
ref1 = features.split(/,\s*/); ref1 = features.split(/,\s*/);
for (i = 0, len = ref1.length; i < len; i++) { for (i = 0, len = ref1.length; i < len; i++) {
feature = ref1[i]; feature = ref1[i];
ref2 = feature.split(/\s*=/), name = ref2[0], value = ref2[1]; ref2 = feature.split(/\s*=/);
name = ref2[0];
value = ref2[1];
options[name] = value === 'yes' || value === '1' ? true : value === 'no' || value === '0' ? false : value; options[name] = value === 'yes' || value === '1' ? true : value === 'no' || value === '0' ? false : value;
} }
if (options.left) { if (options.left) {
@ -168,6 +180,17 @@ if (process.openerId != null) {
window.opener = BrowserWindowProxy.getOrCreate(process.openerId); window.opener = BrowserWindowProxy.getOrCreate(process.openerId);
} }
ipcRenderer.on('ATOM_RENDERER_WINDOW_VISIBILITY_CHANGE', function (event, isVisible, isMinimized) {
var hasChanged = _isVisible != isVisible || _isMinimized != isMinimized;
if (hasChanged) {
_isVisible = isVisible;
_isMinimized = isMinimized;
document.dispatchEvent(new Event('visibilitychange'));
}
});
ipcRenderer.on('ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', function(event, sourceId, message, sourceOrigin) { ipcRenderer.on('ATOM_SHELL_GUEST_WINDOW_POSTMESSAGE', function(event, sourceId, message, sourceOrigin) {
// Manually dispatch event instead of using postMessage because we also need to // Manually dispatch event instead of using postMessage because we also need to
// set event.source. // set event.source.
@ -212,19 +235,17 @@ Object.defineProperty(window.history, 'length', {
// Make document.hidden and document.visibilityState return the correct value. // Make document.hidden and document.visibilityState return the correct value.
Object.defineProperty(document, 'hidden', { Object.defineProperty(document, 'hidden', {
get: function() { get: function () {
var currentWindow; return _isMinimized || !_isVisible;
currentWindow = remote.getCurrentWindow();
return currentWindow.isMinimized() || !currentWindow.isVisible();
} }
}); });
Object.defineProperty(document, 'visibilityState', { Object.defineProperty(document, 'visibilityState', {
get: function() { get: function() {
if (document.hidden) { if (_isVisible && !_isMinimized) {
return "hidden";
} else {
return "visible"; return "visible";
} else {
return "hidden";
} }
} }
}); });

View file

@ -120,14 +120,55 @@ describe('browser-window module', function() {
}); });
describe('BrowserWindow.show()', function() { describe('BrowserWindow.show()', function() {
it('should focus on window', function() { if (isCI) {
if (isCI) { return;
return; }
}
it('should focus on window', function() {
w.show(); w.show();
assert(w.isFocused()); assert(w.isFocused());
}); });
it('should make the window visible', function() {
w.show();
assert(w.isVisible());
});
it('emits when window is shown', function(done) {
this.timeout(10000);
w.once('show', function() {
assert.equal(w.isVisible(), true);
done();
});
w.show();
});
});
describe('BrowserWindow.hide()', function() {
if (isCI) {
return;
}
it('should defocus on window', function() {
w.hide();
assert(!w.isFocused());
});
it('should make the window not visible', function() {
w.show();
w.hide();
assert(!w.isVisible());
});
it('emits when window is hidden', function(done) {
this.timeout(10000);
w.show();
w.once('hide', function() {
assert.equal(w.isVisible(), false);
done();
});
w.hide();
});
}); });
describe('BrowserWindow.showInactive()', function() { describe('BrowserWindow.showInactive()', function() {