Bug 1160923 - [B2G] Waiting for explicit mozContentEvent before sending out mozChromeEvents, r=vingtetun, f=ochaumeau

This commit is contained in:
Tim Chien 2015-09-28 09:27:49 -07:00
parent 2c103b0ace
commit c251097169
6 changed files with 157 additions and 72 deletions

View File

@ -190,7 +190,7 @@ var developerHUD = {
* metrics, and how to notify the front-end when metrics have changed.
*/
function Target(frame, actor) {
this._frame = frame;
this.frame = frame;
this.actor = actor;
this.metrics = new Map();
this._appName = null;
@ -198,15 +198,8 @@ function Target(frame, actor) {
Target.prototype = {
get frame() {
let frame = this._frame;
let systemapp = document.querySelector('#systemapp');
return (frame === systemapp ? getContentWindow() : frame);
},
get manifest() {
return this._frame.appManifestURL;
return this.frame.appManifestURL;
},
get appName() {

View File

@ -82,10 +82,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "SafeMode",
window.performance.measure('gecko-shell-jsm-loaded', 'gecko-shell-loadstart');
function getContentWindow() {
return shell.contentBrowser.contentWindow;
}
function debug(str) {
dump(' -*- Shell.js: ' + str + '\n');
}
@ -388,7 +384,7 @@ var shell = {
CaptivePortalLoginHelper.init();
this.contentBrowser.src = homeURL;
this.isHomeLoaded = false;
this._isEventListenerReady = false;
window.performance.mark('gecko-shell-system-frame-set');
@ -480,6 +476,13 @@ var shell = {
this.contentBrowser.setVisible(true);
}
break;
case 'load':
if (content.document.location == 'about:blank') {
return;
}
content.removeEventListener('load', this, true);
this.notifyContentWindowLoaded();
break;
case 'mozbrowserloadstart':
if (content.document.location == 'about:blank') {
this.contentBrowser.addEventListener('mozbrowserlocationchange', this, true);
@ -586,9 +589,12 @@ var shell = {
break;
case 'MozAfterPaint':
window.removeEventListener('MozAfterPaint', this);
this.sendChromeEvent({
// This event should be sent before System app returns with
// system-message-listener-ready mozContentEvent, because it's on
// the critical launch path of the app.
SystemAppProxy._sendCustomEvent('mozChromeEvent', {
type: 'system-first-paint'
});
}, /* noPending */ true);
break;
case 'unload':
this.stop();
@ -610,6 +616,14 @@ var shell = {
// Send an event to a specific window, document or element.
sendEvent: function shell_sendEvent(target, type, details) {
if (target === this.contentBrowser) {
// We must ask SystemAppProxy to send the event in this case so
// that event would be dispatched from frame.contentWindow instead of
// on the System app frame.
SystemAppProxy._sendCustomEvent(type, details);
return;
}
let doc = target.document || target.ownerDocument || target;
let event = doc.createEvent('CustomEvent');
event.initCustomEvent(type, true, true, details ? details : {});
@ -617,21 +631,10 @@ var shell = {
},
sendCustomEvent: function shell_sendCustomEvent(type, details) {
let target = getContentWindow();
let payload = details ? Cu.cloneInto(details, target) : {};
this.sendEvent(target, type, payload);
SystemAppProxy._sendCustomEvent(type, details);
},
sendChromeEvent: function shell_sendChromeEvent(details) {
if (!this.isHomeLoaded) {
if (!('pendingChromeEvents' in this)) {
this.pendingChromeEvents = [];
}
this.pendingChromeEvents.push(details);
return;
}
this.sendCustomEvent("mozChromeEvent", details);
},
@ -672,6 +675,7 @@ var shell = {
this.contentBrowser.removeEventListener('mozbrowserlocationchange', this, true);
let content = this.contentBrowser.contentWindow;
content.addEventListener('load', this, true);
this.reportCrash(true);
@ -685,28 +689,7 @@ var shell = {
Cu.import('resource://gre/modules/OperatorApps.jsm');
#endif
content.addEventListener('load', function shell_homeLoaded() {
content.removeEventListener('load', shell_homeLoaded);
shell.isHomeLoaded = true;
if (Services.prefs.getBoolPref('b2g.orientation.animate')) {
Cu.import('resource://gre/modules/OrientationChangeHandler.jsm');
}
#ifdef MOZ_WIDGET_GONK
libcutils.property_set('sys.boot_completed', '1');
#endif
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
SystemAppProxy.setIsReady();
if ('pendingChromeEvents' in shell) {
shell.pendingChromeEvents.forEach((shell.sendChromeEvent).bind(shell));
}
delete shell.pendingChromeEvents;
});
shell.handleCmdLine();
this.handleCmdLine();
},
handleCmdLine: function shell_handleCmdLine() {
@ -727,6 +710,38 @@ var shell = {
}
#endif
},
// This gets called when window.onload fires on the System app content window,
// which means things in <html> are parsed and statically referenced <script>s
// and <script defer>s are loaded and run.
notifyContentWindowLoaded: function shell_notifyContentWindowLoaded() {
#ifdef MOZ_WIDGET_GONK
libcutils.property_set('sys.boot_completed', '1');
#endif
// This will cause Gonk Widget to remove boot animation from the screen
// and reveals the page.
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
SystemAppProxy.setIsLoaded();
},
// This gets called when the content sends us system-message-listener-ready
// mozContentEvent, OR when an observer message tell us we should consider
// the content as ready.
notifyEventListenerReady: function shell_notifyEventListenerReady() {
if (this._isEventListenerReady) {
Cu.reportError('shell.js: SystemApp has already been declared as being ready.');
return;
}
this._isEventListenerReady = true;
if (Services.prefs.getBoolPref('b2g.orientation.animate')) {
Cu.import('resource://gre/modules/OrientationChangeHandler.jsm');
}
SystemAppProxy.setIsReady();
}
};
Services.obs.addObserver(function onFullscreenOriginChange(subject, topic, data) {
@ -734,11 +749,13 @@ Services.obs.addObserver(function onFullscreenOriginChange(subject, topic, data)
fullscreenorigin: data });
}, "fullscreen-origin-change", false);
DOMApplicationRegistry.registryStarted.then(function () {
shell.sendChromeEvent({ type: 'webapps-registry-start' });
});
DOMApplicationRegistry.registryReady.then(function () {
shell.sendChromeEvent({ type: 'webapps-registry-ready' });
// This event should be sent before System app returns with
// system-message-listener-ready mozContentEvent, because it's on
// the critical launch path of the app.
SystemAppProxy._sendCustomEvent('mozChromeEvent', {
type: 'webapps-registry-ready'
}, /* noPending */ true);
});
Services.obs.addObserver(function onBluetoothVolumeChange(subject, topic, data) {
@ -752,6 +769,10 @@ Services.obs.addObserver(function(subject, topic, data) {
shell.sendCustomEvent('mozmemorypressure');
}, 'memory-pressure', false);
Services.obs.addObserver(function(subject, topic, data) {
shell.notifyEventListenerReady();
}, 'system-message-listener-ready', false);
var permissionMap = new Map([
['unknown', Services.perms.UNKNOWN_ACTION],
['allow', Services.perms.ALLOW_ACTION],

View File

@ -13,8 +13,10 @@ this.EXPORTED_SYMBOLS = ['SystemAppProxy'];
var SystemAppProxy = {
_frame: null,
_isLoaded: false,
_isReady: false,
_pendingEvents: [],
_pendingLoadedEvents: [],
_pendingReadyEvents: [],
_pendingListeners: [],
// To call when a new system app iframe is created
@ -34,18 +36,38 @@ var SystemAppProxy = {
return this._frame;
},
// To call when the load event of the System app document is triggered.
// i.e. everything that is not lazily loaded are run and done.
setIsLoaded: function () {
if (this._isLoaded) {
Cu.reportError('SystemApp has already been declared as being loaded.');
}
this._isLoaded = true;
// Dispatch all events being queued while the system app was still loading
this._pendingLoadedEvents
.forEach(([type, details]) =>
this._sendCustomEvent(type, details, true));
this._pendingLoadedEvents = [];
},
// To call when it is ready to receive events
// i.e. when system-message-listener-ready mozContentEvent is sent.
setIsReady: function () {
if (!this._isLoaded) {
Cu.reportError('SystemApp.setIsLoaded() should be called before setIsReady().');
}
if (this._isReady) {
Cu.reportError('SystemApp has already been declared as being ready.');
}
this._isReady = true;
// Dispatch all events being queued while the system app was still loading
this._pendingEvents
// Dispatch all events being queued while the system app was still not ready
this._pendingReadyEvents
.forEach(([type, details]) =>
this._sendCustomEvent(type, details));
this._pendingEvents = [];
this._pendingReadyEvents = [];
},
/*
@ -62,6 +84,9 @@ var SystemAppProxy = {
* @param details The event details.
* @param noPending Set to true to emit this event even before the system
* app is ready.
* Event is always pending if the app is not loaded yet.
*
* @returns event? Dispatched event, or null if the event is pending.
*/
_sendCustomEvent: function systemApp_sendCustomEvent(type,
details,
@ -69,10 +94,22 @@ var SystemAppProxy = {
target) {
let content = this._frame ? this._frame.contentWindow : null;
// If the system app isn't loaded yet,
// queue events until someone calls setIsLoaded
if (!content || !this._isLoaded) {
if (noPending) {
this._pendingLoadedEvents.push([type, details]);
} else {
this._pendingReadyEvents.push([type, details]);
}
return null;
}
// If the system app isn't ready yet,
// queue events until someone calls setIsReady
if (!content || (!this._isReady && !noPending)) {
this._pendingEvents.push([type, details]);
if (!this._isReady && !noPending) {
this._pendingReadyEvents.push([type, details]);
return null;
}
@ -87,6 +124,10 @@ var SystemAppProxy = {
payload = details ? Cu.cloneInto(details, content) : {};
}
if ((target || content) === this._frame.contentWindow) {
dump('XXX FIXME : Dispatch a ' + type + ': ' + details.type + "\n");
}
event.initCustomEvent(type, true, false, payload);
(target || content).dispatchEvent(event);

View File

@ -27,10 +27,11 @@ function next() {
// Listen for events received by the system app document
// to ensure that we receive all of them, in an expected order and time
var isLoaded = false;
var isReady = false;
var n = 0;
function listener(event) {
if (!isLoaded) {
assert.ok(false, "Received event before the iframe is ready");
assert.ok(false, "Received event before the iframe is loaded");
return;
}
n++;
@ -41,16 +42,34 @@ function listener(event) {
assert.equal(event.type, "custom");
assert.equal(event.detail.name, "second");
next(); // call checkEventDispatching
next(); // call checkEventPendingBeforeLoad
} else if (n == 3) {
if (!isReady) {
assert.ok(false, "Received event before the iframe is loaded");
return;
}
assert.equal(event.type, "custom");
assert.equal(event.detail.name, "third");
} else if (n == 4) {
if (!isReady) {
assert.ok(false, "Received event before the iframe is loaded");
return;
}
assert.equal(event.type, "mozChromeEvent");
assert.equal(event.detail.name, "fourth");
next(); // call checkEventDispatching
} else if (n == 5) {
assert.equal(event.type, "custom");
assert.equal(event.detail.name, "fifth");
} else if (n === 6) {
assert.equal(event.type, "mozChromeEvent");
assert.equal(event.detail.name, "sixth");
} else if (n === 7) {
assert.equal(event.type, "custom");
assert.equal(event.detail.name, "seventh");
assert.equal(event.target, customEventTarget);
next(); // call checkEventListening();
@ -72,8 +91,8 @@ var steps = [
function earlyEvents() {
// Immediately try to send events
SystemAppProxy.dispatchEvent({ name: "first" });
SystemAppProxy._sendCustomEvent("custom", { name: "second" });
SystemAppProxy._sendCustomEvent("mozChromeEvent", { name: "first" }, true);
SystemAppProxy._sendCustomEvent("custom", { name: "second" }, true);
next();
},
@ -110,7 +129,7 @@ var steps = [
// Declare that the iframe is now loaded.
// That should dispatch early events
isLoaded = true;
SystemAppProxy.setIsReady();
SystemAppProxy.setIsLoaded();
assert.ok(true, "Frame declared as loaded");
let gotFrame = SystemAppProxy.getFrame();
@ -123,13 +142,24 @@ var steps = [
frame.setAttribute("src", "data:text/html,system app");
},
function checkEventPendingBeforeLoad() {
// Frame is loaded but not ready,
// these events should queue before the System app is ready.
SystemAppProxy._sendCustomEvent("custom", { name: "third" });
SystemAppProxy.dispatchEvent({ name: "fourth" });
isReady = true;
SystemAppProxy.setIsReady();
// Once this 4th event is received, we will run checkEventDispatching
},
function checkEventDispatching() {
// Send events after the iframe is ready,
// they should be dispatched right away
SystemAppProxy._sendCustomEvent("custom", { name: "third" });
SystemAppProxy.dispatchEvent({ name: "fourth" });
SystemAppProxy._sendCustomEvent("custom", { name: "fifth" }, false, customEventTarget);
// Once this 5th event is received, we will run checkEventListening
SystemAppProxy._sendCustomEvent("custom", { name: "fifth" });
SystemAppProxy.dispatchEvent({ name: "sixth" });
SystemAppProxy._sendCustomEvent("custom", { name: "seventh" }, false, customEventTarget);
// Once this 7th event is received, we will run checkEventListening
},
function checkEventListening() {

View File

@ -38,9 +38,6 @@ function disableRadio() {
}
addMessageListener('prepare-network', function(message) {
//RIL DOM events will be pending until RIL receiveing system-message-listener-ready event.
Services.obs.notifyObservers(null, 'system-message-listener-ready', null);
connection.addEventListener('datachange', function onDataChange() {
if (connection.data.connected) {
connection.removeEventListener('datachange', onDataChange);

View File

@ -32,6 +32,9 @@ if (cm) {
var SECURITY_PREF = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer";
Components.utils.import("resource://gre/modules/Services.jsm");
Services.prefs.setBoolPref(SECURITY_PREF, true);
// RIL DOM events and mozChromeEvents will be pending until
// this observer message is sent.
Services.obs.notifyObservers(null, 'system-message-listener-ready', null);
function openWindow(aEvent) {
var popupIframe = aEvent.detail.frameElement;