Bug 963239 - Implements "SystemAppProxy.jsm" to abtract and ease interacting with the system app from platform code. r=vingtetun

This commit is contained in:
Alexandre Poirot 2014-04-07 09:59:48 -04:00
parent c049e8b0be
commit 6b82f43a5f
19 changed files with 412 additions and 209 deletions

View File

@ -46,6 +46,9 @@ XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
#ifdef MOZ_B2G_RIL
XPCOMUtils.defineLazyServiceGetter(this, "gRil",
"@mozilla.org/ril;1",
@ -197,8 +200,6 @@ const kClosePaymentFlowEvent = "close-payment-flow-dialog";
let gRequestId;
let gBrowser = Services.wm.getMostRecentWindow("navigator:browser");
let PaymentProvider = {
#ifdef MOZ_B2G_RIL
__exposedProps__: {
@ -229,11 +230,6 @@ let PaymentProvider = {
// payment flow dialog and return to the caller application.
let id = kClosePaymentFlowEvent + "-" + uuidgen.generateUUID().toString();
let content = gBrowser.getContentWindow();
if (!content) {
return;
}
let detail = {
type: kClosePaymentFlowEvent,
id: id,
@ -244,13 +240,13 @@ let PaymentProvider = {
// it has successfully closed the payment flow and has recovered the
// caller app, before notifying the parent process to fire the success
// or error event over the DOMRequest.
content.addEventListener("mozContentEvent",
function closePaymentFlowReturn(evt) {
SystemAppProxy.addEventListener("mozContentEvent",
function closePaymentFlowReturn(evt) {
if (evt.detail.id == id && aCallback) {
aCallback();
}
content.removeEventListener("mozContentEvent",
SystemAppProxy.removeEventListener("mozContentEvent",
closePaymentFlowReturn);
let glue = Cc["@mozilla.org/payment/ui-glue;1"]
@ -258,7 +254,7 @@ let PaymentProvider = {
glue.cleanup();
});
gBrowser.shell.sendChromeEvent(detail);
SystemAppProxy.dispatchEvent(detail);
#ifdef MOZ_B2G_RIL
this._cleanUp();
@ -478,7 +474,7 @@ addEventListener("DOMWindowCreated", function(e) {
// If the trusted dialog is not closed via paymentSuccess or paymentFailed
// a mozContentEvent with type 'cancel' is sent from the UI. We need to listen
// for this event to clean up the silent sms observers if any exists.
gBrowser.getContentWindow().addEventListener("mozContentEvent", function(e) {
SystemAppProxy.addEventListener("mozContentEvent", function(e) {
if (e.detail.type === "cancel") {
PaymentProvider._cleanUp();
}

View File

@ -30,6 +30,9 @@ Cu.import('resource://gre/modules/FxAccountsMgmtService.jsm');
Cu.import('resource://gre/modules/DownloadsAPI.jsm');
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
Cu.import('resource://gre/modules/Webapps.jsm');
DOMApplicationRegistry.allAppsLaunchable = true;
@ -340,6 +343,8 @@ var shell = {
window.addEventListener('unload', this);
this.contentBrowser.addEventListener('mozbrowserloadstart', this, true);
SystemAppProxy.registerFrame(this.contentBrowser);
CustomEventManager.init();
WebappsHelper.init();
UserAgentOverrides.init();
@ -666,6 +671,7 @@ var shell = {
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
SystemAppProxy.setIsReady();
if ('pendingChromeEvents' in shell) {
shell.pendingChromeEvents.forEach((shell.sendChromeEvent).bind(shell));
}

View File

@ -3,6 +3,7 @@
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
const { Services } = Cu.import('resource://gre/modules/Services.jsm');
const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm');
var processId;
@ -17,9 +18,8 @@ function peekChildId(aSubject, aTopic, aData) {
addMessageListener('init-chrome-event', function(message) {
// listen mozChromeEvent and forward to content process.
let browser = Services.wm.getMostRecentWindow('navigator:browser');
let type = message.type;
browser.addEventListener('mozChromeEvent', function(event) {
SystemAppProxy.addEventListener('mozChromeEvent', function(event) {
let details = event.detail;
if (details.type === type) {
sendAsyncMessage('chrome-event', details);

View File

@ -11,6 +11,9 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
function ActivitiesDialog() {
this._id = 0;
@ -31,8 +34,6 @@ ActivitiesDialog.prototype = {
// Keep up the frond-end of an activity choice. The messages contains
// a list of {names, icons} for applications able to handle this particular
// activity. The front-end should display a UI to pick one.
let browser = Services.wm.getMostRecentWindow("navigator:browser");
let content = browser.getContentWindow();
let detail = {
type: "activity-choice",
id: id,
@ -42,17 +43,17 @@ ActivitiesDialog.prototype = {
// Listen the resulting choice from the front-end. If there is no choice,
// let's return -1, which means the user has cancelled the dialog.
content.addEventListener("mozContentEvent", function act_getChoice(evt) {
SystemAppProxy.addEventListener("mozContentEvent", function act_getChoice(evt) {
if (evt.detail.id != id)
return;
content.removeEventListener("mozContentEvent", act_getChoice);
SystemAppProxy.removeEventListener("mozContentEvent", act_getChoice);
activity.callback.handleEvent(evt.detail.value !== undefined
? evt.detail.value
: -1);
});
browser.shell.sendChromeEvent(detail);
SystemAppProxy.dispatchEvent(detail);
},
chooseActivity: function ap_chooseActivity(aName, aActivities, aCallback) {

View File

@ -39,6 +39,9 @@ XPCOMUtils.defineLazyServiceGetter(this,
"@mozilla.org/telephony/audiomanager;1",
"nsIAudioManager");
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
/**
* aTypesInfo is an array of {permission, access, action, deny} which keeps
* the information of each permission. This arrary is initialized in
@ -346,17 +349,12 @@ ContentPermissionPrompt.prototype = {
},
sendToBrowserWindow: function(type, request, requestId, typesInfo, callback) {
let browser = Services.wm.getMostRecentWindow("navigator:browser");
let content = browser.getContentWindow();
if (!content)
return;
if (callback) {
content.addEventListener("mozContentEvent", function contentEvent(evt) {
SystemAppProxy.addEventListener("mozContentEvent", function contentEvent(evt) {
let detail = evt.detail;
if (detail.id != requestId)
return;
evt.target.removeEventListener(evt.type, contentEvent);
SystemAppProxy.removeEventListener("mozContentEvent", contentEvent);
callback(detail.type, detail.remember, detail.choices);
})
@ -383,13 +381,10 @@ ContentPermissionPrompt.prototype = {
remember: remember
};
if (!isApp) {
browser.shell.sendChromeEvent(details);
return;
if (isApp) {
details.manifestURL = DOMApplicationRegistry.getManifestURLByLocalId(principal.appId);
}
details.manifestURL = DOMApplicationRegistry.getManifestURLByLocalId(principal.appId);
browser.shell.sendChromeEvent(details);
SystemAppProxy.dispatchEvent(details);
},
classID: Components.ID("{8c719f03-afe0-4aac-91ff-6c215895d467}"),

View File

@ -29,25 +29,19 @@ Cu.import("resource://gre/modules/FxAccountsCommon.js");
XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsManager",
"resource://gre/modules/FxAccountsManager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
this.FxAccountsMgmtService = {
_sendChromeEvent: function(aEventName, aMsg) {
if (!this._shell) {
return;
}
log.debug("Chrome event " + JSON.stringify(aMsg));
this._shell.sendCustomEvent(aEventName, aMsg);
},
_onFulfill: function(aMsgId, aData) {
this._sendChromeEvent("mozFxAccountsChromeEvent", {
SystemAppProxy._sendCustomEvent("mozFxAccountsChromeEvent", {
id: aMsgId,
data: aData ? aData : null
});
},
_onReject: function(aMsgId, aReason) {
this._sendChromeEvent("mozFxAccountsChromeEvent", {
SystemAppProxy._sendCustomEvent("mozFxAccountsChromeEvent", {
id: aMsgId,
error: aReason ? aReason : null
});
@ -64,17 +58,15 @@ this.FxAccountsMgmtService = {
log.debug("Observed " + aTopic);
switch (aTopic) {
case "content-start":
this._shell = Services.wm.getMostRecentWindow("navigator:browser").shell;
let content = this._shell.contentBrowser.contentWindow;
content.addEventListener("mozFxAccountsContentEvent",
FxAccountsMgmtService);
SystemAppProxy.addEventListener("mozFxAccountsContentEvent",
FxAccountsMgmtService);
Services.obs.removeObserver(this, "content-start");
break;
case ONLOGIN_NOTIFICATION:
case ONVERIFIED_NOTIFICATION:
case ONLOGOUT_NOTIFICATION:
// FxAccounts notifications have the form of fxaccounts:*
this._sendChromeEvent("mozFxAccountsUnsolChromeEvent", {
SystemAppProxy._sendCustomEvent("mozFxAccountsUnsolChromeEvent", {
eventName: aTopic.substring(aTopic.indexOf(":") + 1)
});
break;

View File

@ -15,31 +15,26 @@ XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
function FxAccountsUIGlue() {
}
FxAccountsUIGlue.prototype = {
_browser: Services.wm.getMostRecentWindow("navigator:browser"),
_contentRequest: function(aEventName, aData) {
let deferred = Promise.defer();
let content = this._browser.getContentWindow();
if (!content) {
deferred.reject("InternalErrorNoContent");
return;
}
let id = uuidgen.generateUUID().toString();
content.addEventListener("mozFxAccountsRPContentEvent",
function onContentEvent(result) {
SystemAppProxy.addEventListener("mozFxAccountsRPContentEvent",
function onContentEvent(result) {
let msg = result.detail;
if (!msg || !msg.id || msg.id != id) {
deferred.reject("InternalErrorWrongContentEvent");
content.removeEventListener("mozFxAccountsRPContentEvent",
onContentEvent);
SystemAppProxy.removeEventListener("mozFxAccountsRPContentEvent",
onContentEvent);
return;
}
@ -50,8 +45,8 @@ FxAccountsUIGlue.prototype = {
} else {
deferred.resolve(msg.result);
}
content.removeEventListener("mozFxAccountsRPContentEvent",
onContentEvent);
SystemAppProxy.removeEventListener("mozFxAccountsRPContentEvent",
onContentEvent);
});
let detail = {
@ -60,7 +55,7 @@ FxAccountsUIGlue.prototype = {
data: aData
};
log.debug("Send chrome event " + JSON.stringify(detail));
this._browser.shell.sendCustomEvent("mozFxAccountsUnsolChromeEvent", detail);
SystemAppProxy._sendCustomEvent("mozFxAccountsUnsolChromeEvent", detail);
return deferred.promise;
},

View File

@ -23,6 +23,9 @@ XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
function PaymentUI() {
try {
this._debug =
@ -45,13 +48,6 @@ PaymentUI.prototype = {
}
};
let browser = Services.wm.getMostRecentWindow("navigator:browser");
let content = browser.getContentWindow();
if (!content) {
_error("NO_CONTENT_WINDOW");
return;
}
// The UI should listen for mozChromeEvent 'open-payment-confirmation-dialog'
// type in order to create and show the payment request confirmation frame
// embeded within a trusted dialog.
@ -78,12 +74,12 @@ PaymentUI.prototype = {
_error(msg.errorMsg);
}
content.removeEventListener("mozContentEvent", this._handleSelection);
SystemAppProxy.removeEventListener("mozContentEvent", this._handleSelection);
this._handleSelection = null;
}).bind(this);
content.addEventListener("mozContentEvent", this._handleSelection);
SystemAppProxy.addEventListener("mozContentEvent", this._handleSelection);
browser.shell.sendChromeEvent(detail);
SystemAppProxy.dispatchEvent(detail);
},
showPaymentFlow: function showPaymentFlow(aRequestId,
@ -96,13 +92,6 @@ PaymentUI.prototype = {
};
// We ask the UI to browse to the selected payment flow.
let browser = Services.wm.getMostRecentWindow("navigator:browser");
let content = browser.getContentWindow();
if (!content) {
_error("NO_CONTENT_WINDOW");
return;
}
let id = kOpenPaymentFlowEvent + "-" + this.getRandomId();
let detail = {
type: kOpenPaymentFlowEvent,
@ -123,14 +112,14 @@ PaymentUI.prototype = {
}
if (msg.errorMsg) {
content.removeEventListener("mozContentEvent", this._loadPaymentShim);
SystemAppProxy.removeEventListener("mozContentEvent", this._loadPaymentShim);
this._loadPaymentShim = null;
_error("ERROR_LOADING_PAYMENT_SHIM: " + msg.errorMsg);
return;
}
if (!msg.frame) {
content.removeEventListener("mozContentEvent", this._loadPaymentShim);
SystemAppProxy.removeEventListener("mozContentEvent", this._loadPaymentShim);
this._loadPaymentShim = null;
_error("ERROR_LOADING_PAYMENT_SHIM");
return;
@ -152,11 +141,11 @@ PaymentUI.prototype = {
}
_error("ERROR_LOADING_PAYMENT_SHIM");
} finally {
content.removeEventListener("mozContentEvent", this._loadPaymentShim);
SystemAppProxy.removeEventListener("mozContentEvent", this._loadPaymentShim);
this._loadPaymentShim = null;
}
}).bind(this);
content.addEventListener("mozContentEvent", this._loadPaymentShim);
SystemAppProxy.addEventListener("mozContentEvent", this._loadPaymentShim);
// We also listen for UI notifications about a closed payment flow. The UI
// should provide the reason of the closure within the 'errorMsg' parameter
@ -173,35 +162,29 @@ PaymentUI.prototype = {
if (msg.errorMsg) {
_error(msg.errorMsg);
}
content.removeEventListener("mozContentEvent",
this._notifyPayFlowClosed);
SystemAppProxy.removeEventListener("mozContentEvent",
this._notifyPayFlowClosed);
this._notifyPayFlowClosed = null;
}).bind(this);
content.addEventListener("mozContentEvent",
this._notifyPayFlowClosed);
SystemAppProxy.addEventListener("mozContentEvent",
this._notifyPayFlowClosed);
browser.shell.sendChromeEvent(detail);
SystemAppProxy.dispatchEvent(detail);
},
cleanup: function cleanup() {
let browser = Services.wm.getMostRecentWindow("navigator:browser");
let content = browser.getContentWindow();
if (!content) {
return;
}
if (this._handleSelection) {
content.removeEventListener("mozContentEvent", this._handleSelection);
SystemAppProxy.removeEventListener("mozContentEvent", this._handleSelection);
this._handleSelection = null;
}
if (this._notifyPayFlowClosed) {
content.removeEventListener("mozContentEvent", this._notifyPayFlowClosed);
SystemAppProxy.removeEventListener("mozContentEvent", this._notifyPayFlowClosed);
this._notifyPayFlowClosed = null;
}
if (this._loadPaymentShim) {
content.removeEventListener("mozContentEvent", this._loadPaymentShim);
SystemAppProxy.removeEventListener("mozContentEvent", this._loadPaymentShim);
this._loadPaymentShim = null;
}
},

View File

@ -86,6 +86,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "IdentityService",
XPCOMUtils.defineLazyModuleGetter(this, "Logger",
"resource://gre/modules/identity/LogUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
// The default persona uri; can be overwritten with toolkit.identity.uri pref.
// Do this if you want to repoint to a different service for testing.
// There's no point in setting up an observer to monitor the pref, as b2g prefs
@ -122,26 +125,10 @@ function log(...aMessageArgs) {
log("persona uri =", kPersonaUri);
/*
* ContentInterface encapsulates the our content functions. There are only two:
*
* getContent - return the current content window
* sendChromeEvent - send a chromeEvent from the browser shell
*/
let ContentInterface = {
_getBrowser: function SignInToWebsiteController__getBrowser() {
return Services.wm.getMostRecentWindow("navigator:browser");
},
getContent: function SignInToWebsiteController_getContent() {
return this._getBrowser().getContentWindow();
},
sendChromeEvent: function SignInToWebsiteController_sendChromeEvent(detail) {
detail.uri = kPersonaUri;
this._getBrowser().shell.sendChromeEvent(detail);
}
};
function sendChromeEvent(details) {
details.uri = kPersonaUri;
SystemAppProxy.dispatchEvent(details);
}
function Pipe() {
this._watchers = [];
@ -217,7 +204,7 @@ Pipe.prototype = {
};
log('telling content to close the dialog');
// tell content to close the dialog
ContentInterface.sendChromeEvent(detail);
sendChromeEvent(detail);
}
},
@ -233,17 +220,10 @@ Pipe.prototype = {
// This content variable is injected into the scope of
// kIdentityShimFile, where it is used to access the BrowserID object
// and its internal API.
let content = ContentInterface.getContent();
let mm = null;
let uuid = getRandomId();
let self = this;
if (!content) {
log("ERROR: what the what? no content window?");
// aErrorCb.onresult("NO_CONTENT_WINDOW");
return;
}
function removeMessageListeners() {
if (mm) {
mm.removeMessageListener(kIdentityDelegateFinished, identityDelegateFinished);
@ -261,11 +241,11 @@ Pipe.prototype = {
requestId: aRpOptions.id
};
log('received delegate finished; telling content to close the dialog');
ContentInterface.sendChromeEvent(detail);
sendChromeEvent(detail);
self._removeWatchers(rpID, rpMM);
}
content.addEventListener("mozContentEvent", function getAssertion(evt) {
SystemAppProxy.addEventListener("mozContentEvent", function getAssertion(evt) {
let msg = evt.detail;
if (!msg.id.match(uuid)) {
return;
@ -275,7 +255,7 @@ Pipe.prototype = {
case kOpenIdentityDialog + '-' + uuid:
if (msg.type === 'cancel') {
// The user closed the dialog. Clean up and call cancel.
content.removeEventListener("mozContentEvent", getAssertion);
SystemAppProxy.removeEventListener("mozContentEvent", getAssertion);
removeMessageListeners();
aMessageCallback({json: {method: "cancel"}});
} else {
@ -309,7 +289,7 @@ Pipe.prototype = {
// Received our assertion. The message manager callbacks will handle
// communicating back to the IDService. All we have to do is remove
// this listener.
content.removeEventListener("mozContentEvent", getAssertion);
SystemAppProxy.removeEventListener("mozContentEvent", getAssertion);
break;
default:
@ -330,7 +310,7 @@ Pipe.prototype = {
requestId: aRpOptions.id
};
ContentInterface.sendChromeEvent(detail);
sendChromeEvent(detail);
}
};

View File

@ -0,0 +1,114 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict';
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
this.EXPORTED_SYMBOLS = ['SystemAppProxy'];
let SystemAppProxy = {
_frame: null,
_isReady: false,
_pendingEvents: [],
_pendingListeners: [],
// To call when a new system app iframe is created
registerFrame: function (frame) {
this._isReady = false;
this._frame = frame;
// Register all DOM event listeners added before we got a ref to the app iframe
this._pendingListeners
.forEach((args) =>
this.addEventListener.apply(this, args));
this._pendingListeners = [];
},
// To call when it is ready to receive events
setIsReady: function () {
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
.forEach(([type, details]) =>
this._sendCustomEvent(type, details));
this._pendingEvents = [];
},
/*
* Common way to send an event to the system app.
*
* // In gecko code:
* SystemAppProxy.sendCustomEvent('foo', { data: 'bar' });
* // In system app:
* window.addEventListener('foo', function (event) {
* event.details == 'bar'
* });
*/
_sendCustomEvent: function systemApp_sendCustomEvent(type, details) {
let content = this._frame ? this._frame.contentWindow : null;
// If the system app isn't ready yet,
// queue events until someone calls setIsLoaded
if (!this._isReady || !content) {
this._pendingEvents.push([type, details]);
return null;
}
let event = content.document.createEvent('CustomEvent');
let payload;
// If the root object already has __exposedProps__,
// we consider the caller already wrapped (correctly) the object.
if ('__exposedProps__' in details) {
payload = details;
} else {
payload = details ? Cu.cloneInto(details, content) : {};
}
event.initCustomEvent(type, true, false, payload);
content.dispatchEvent(event);
return event;
},
// Now deprecated, use sendCustomEvent with a custom event name
dispatchEvent: function systemApp_sendChromeEvent(details) {
return this._sendCustomEvent('mozChromeEvent', details);
},
// Listen for dom events on the system app
addEventListener: function systemApp_addEventListener() {
let content = this._frame ? this._frame.contentWindow : null;
if (!content) {
this._pendingListeners.push(arguments);
return false;
}
content.addEventListener.apply(content, arguments);
return true;
},
removeEventListener: function systemApp_removeEventListener(name, listener) {
let content = this._frame ? this._frame.contentWindow : null;
if (content) {
content.removeEventListener.apply(content, arguments);
} else {
let idx = this._pendingListeners.indexOf(listener);
if (idx != -1) {
this._pendingListeners.splice(idx, 1);
}
}
}
};
this.SystemAppProxy = SystemAppProxy;

View File

@ -61,6 +61,9 @@ function useSettings() {
return useSettings.result;
}
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
function UpdateCheckListener(updatePrompt) {
this._updatePrompt = updatePrompt;
}
@ -133,7 +136,6 @@ UpdatePrompt.prototype = {
_applyPromptTimer: null,
_waitingForIdle: false,
_updateCheckListner: null,
_pendingEvents: [],
get applyPromptTimeout() {
return Services.prefs.getIntPref(PREF_APPLY_PROMPT_TIMEOUT);
@ -143,14 +145,8 @@ UpdatePrompt.prototype = {
return Services.prefs.getIntPref(PREF_APPLY_IDLE_TIMEOUT);
},
handleContentStart: function UP_handleContentStart(shell) {
let content = shell.contentBrowser.contentWindow;
content.addEventListener("mozContentEvent", this);
for (let i = 0; i < this._pendingEvents.length; i++) {
shell.sendChromeEvent(this._pendingEvents[i]);
}
this._pendingEvents.length = 0;
handleContentStart: function UP_handleContentStart() {
SystemAppProxy.addEventListener("mozContentEvent", this);
},
// nsIUpdatePrompt
@ -290,15 +286,12 @@ UpdatePrompt.prototype = {
let detail = aDetail || {};
detail.type = aType;
let browser = Services.wm.getMostRecentWindow("navigator:browser");
if (!browser) {
this._pendingEvents.push(detail);
let sent = SystemAppProxy.dispatchEvent(detail);
if (!sent) {
log("Warning: Couldn't send update event " + aType +
": no content browser. Will send again when content becomes available.");
return false;
}
browser.shell.sendChromeEvent(detail);
return true;
},

View File

@ -16,34 +16,30 @@ XPCOMUtils.defineLazyServiceGetter(this, "settings",
"@mozilla.org/settingsService;1",
"nsISettingsService");
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
function debug(aStr) {
//dump("--*-- WebappsUpdater: " + aStr);
}
this.WebappsUpdater = {
_checkingApps: false,
_pendingEvents: [],
handleContentStart: function(aShell) {
let content = aShell.contentBrowser.contentWindow;
this._pendingEvents.forEach(aShell.sendChromeEvent);
this._pendingEvents.length = 0;
handleContentStart: function() {
},
sendChromeEvent: function(aType, aDetail) {
let detail = aDetail || {};
detail.type = aType;
let browser = Services.wm.getMostRecentWindow("navigator:browser");
if (!browser) {
this._pendingEvents.push(detail);
let sent = SystemAppProxy.dispatchEvent(detail);
if (!sent) {
debug("Warning: Couldn't send update event " + aType +
": no content browser. Will send again when content becomes available.");
return false;
}
browser.shell.sendChromeEvent(detail);
return true;
},

View File

@ -4,7 +4,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
TEST_DIRS += ['test']
DIRS += ['test']
EXTRA_COMPONENTS += [
'ActivitiesGlue.js',
@ -42,6 +42,7 @@ if CONFIG['MOZ_UPDATER']:
EXTRA_JS_MODULES += [
'ErrorPage.jsm',
'SignInToWebsite.jsm',
'SystemAppProxy.jsm',
'TelURIParser.jsm',
'WebappsUpdater.jsm',
]

View File

@ -1,10 +1,14 @@
[DEFAULT]
run-if = toolkit == "gonk"
support-files =
permission_handler_chrome.js
SandboxPromptTest.html
filepicker_path_handler_chrome.js
systemapp_helper.js
[test_sandbox_permission.html]
run-if = toolkit == "gonk"
[test_filepicker_path.html]
run-if = toolkit == "gonk"
[test_permission_deny.html]
run-if = toolkit == "gonk"
[test_systemapp.html]

View File

@ -13,44 +13,24 @@ const Ci = Components.interfaces;
const Cu = Components.utils;
const { Services } = Cu.import("resource://gre/modules/Services.jsm");
const { SystemAppProxy } = Cu.import("resource://gre/modules/SystemAppProxy.jsm");
let browser = Services.wm.getMostRecentWindow("navigator:browser");
let shell;
function loadShell() {
if (!browser) {
debug("no browser");
return false;
let eventHandler = function(evt) {
if (!evt.detail || evt.detail.type !== "permission-prompt") {
return;
}
shell = browser.shell;
return true;
}
function getContentWindow() {
return shell.contentBrowser.contentWindow;
}
sendAsyncMessage("permission-request", evt.detail);
};
if (loadShell()) {
let content = getContentWindow();
let eventHandler = function(evt) {
if (!evt.detail || evt.detail.type !== "permission-prompt") {
return;
}
SystemAppProxy.addEventListener("mozChromeEvent", eventHandler);
sendAsyncMessage("permission-request", evt.detail);
};
// need to remove ChromeEvent listener after test finished.
addMessageListener("teardown", function() {
SystemAppProxy.removeEventListener("mozChromeEvent", eventHandler);
});
content.addEventListener("mozChromeEvent", eventHandler);
// need to remove ChromeEvent listener after test finished.
addMessageListener("teardown", function() {
content.removeEventListener("mozChromeEvent", eventHandler);
});
addMessageListener("permission-response", function(detail) {
let event = content.document.createEvent('CustomEvent');
event.initCustomEvent('mozContentEvent', true, true, detail);
content.dispatchEvent(event);
});
}
addMessageListener("permission-response", function(detail) {
SystemAppProxy._sendCustomEvent('mozContentEvent', detail);
});

View File

@ -0,0 +1,141 @@
const Cu = Components.utils;
const { Services } = Cu.import("resource://gre/modules/Services.jsm");
// Load a duplicated copy of the jsm to prevent messing with the currently running one
let scope = {};
Services.scriptloader.loadSubScript("resource://gre/modules/SystemAppProxy.jsm", scope);
const { SystemAppProxy } = scope;
let frame;
let index = -1;
function next() {
index++;
if (index >= steps.length) {
assert.ok(false, "Shouldn't get here!");
return;
}
try {
steps[index]();
} catch(ex) {
assert.ok(false, "Caught exception: " + ex);
}
}
// Listen for events received by the system app document
// to ensure that we receive all of them, in an expected order and time
let isLoaded = false;
let n = 0;
function listener(event) {
if (!isLoaded) {
assert.ok(false, "Received event before the iframe is ready");
return;
}
n++;
if (n == 1) {
assert.equal(event.type, "mozChromeEvent");
assert.equal(event.detail.name, "first");
} else if (n == 2) {
assert.equal(event.type, "custom");
assert.equal(event.detail.name, "second");
next(); // call checkEventDispatching
} else if (n == 3) {
assert.equal(event.type, "custom");
assert.equal(event.detail.name, "third");
} else if (n == 4) {
assert.equal(event.type, "mozChromeEvent");
assert.equal(event.detail.name, "fourth");
next(); // call checkEventListening();
} else {
assert.ok(false, "Unexpected event of type " + event.type);
}
}
let steps = [
function waitForWebapps() {
// We are using webapps API later in this test and we need to ensure
// it is fully initialized before trying to use it
let { DOMApplicationRegistry } = Cu.import('resource://gre/modules/Webapps.jsm', {});
DOMApplicationRegistry.registryReady.then(function () {
next();
});
},
function earlyEvents() {
// Immediately try to send events
SystemAppProxy.dispatchEvent({ name: "first" });
SystemAppProxy._sendCustomEvent("custom", { name: "second" });
next();
},
function createFrame() {
// Create a fake system app frame
let win = Services.wm.getMostRecentWindow("navigator:browser");
let doc = win.document;
frame = doc.createElement("iframe");
doc.documentElement.appendChild(frame);
// Ensure that events are correctly sent to the frame.
// `listener` is going to call next()
frame.contentWindow.addEventListener("mozChromeEvent", listener);
frame.contentWindow.addEventListener("custom", listener);
// Ensure that listener being registered before the system app is ready
// are correctly removed from the pending list
function removedListener() {
assert(false, "Listener isn't correctly removed from the pending list");
}
SystemAppProxy.addEventListener("mozChromeEvent", removedListener);
SystemAppProxy.removeEventListener("mozChromeEvent", removedListener);
// Register it to the JSM
SystemAppProxy.registerFrame(frame);
assert.ok(true, "Frame created and registered");
frame.contentWindow.addEventListener("load", function onload() {
frame.contentWindow.removeEventListener("load", onload);
assert.ok(true, "Frame document loaded");
// Declare that the iframe is now loaded.
// That should dispatch early events
isLoaded = true;
SystemAppProxy.setIsReady();
assert.ok(true, "Frame declared as loaded");
// Once pending events are received,
// we will run checkEventDispatching from `listener` function
});
frame.setAttribute("src", "data:text/html,system app");
},
function checkEventDispatching() {
// Send events after the iframe is ready,
// they should be dispatched right away
SystemAppProxy._sendCustomEvent("custom", { name: "third" });
SystemAppProxy.dispatchEvent({ name: "fourth" });
// Once this 4th event is received, we will run checkEventListening
},
function checkEventListening() {
SystemAppProxy.addEventListener("mozContentEvent", function onContentEvent(event) {
assert.equal(event.detail.name, "first-content", "received a system app event");
SystemAppProxy.removeEventListener("mozContentEvent", onContentEvent);
next();
});
let win = frame.contentWindow;
win.dispatchEvent(new win.CustomEvent("mozContentEvent", { detail: {name: "first-content"} }));
},
function endOfTest() {
frame.remove();
sendAsyncMessage("finish");
}
];
next();

View File

@ -0,0 +1,31 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=963239
-->
<head>
<meta charset="utf-8">
<title>SystemAppProxy Test</title>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=963239">SystemAppProxy.jsm</a>
<script type="application/javascript">
"use strict";
var gUrl = SimpleTest.getTestFileURL("systemapp_helper.js");
var gScript = SpecialPowers.loadChromeScript(gUrl);
SimpleTest.waitForExplicitFinish();
gScript.addMessageListener("finish", function () {
SimpleTest.ok(true, "chrome test script finished");
gScript.destroy();
SimpleTest.finish();
});
</script>
</pre>
</body>
</html>

View File

@ -16,20 +16,19 @@ XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsMgmtService",
// At end of test, restore original state
const ORIGINAL_AUTH_URI = Services.prefs.getCharPref("identity.fxaccounts.auth.uri");
const ORIGINAL_SHELL = FxAccountsMgmtService._shell;
let { SystemAppProxy } = Cu.import("resource://gre/modules/FxAccountsMgmtService.jsm");
const ORIGINAL_SENDCUSTOM = SystemAppProxy._sendCustomEvent;
do_register_cleanup(function() {
Services.prefs.setCharPref("identity.fxaccounts.auth.uri", ORIGINAL_AUTH_URI);
FxAccountsMgmtService._shell = ORIGINAL_SHELL;
SystemAppProxy._sendCustomEvent = ORIGINAL_SENDCUSTOM;
});
// Make profile available so that fxaccounts can store user data
do_get_profile();
// Mock the b2g shell; make message passing possible
let mockShell = {
sendCustomEvent: function(aEventName, aMsg) {
Services.obs.notifyObservers({wrappedJSObject: aMsg}, aEventName, null);
},
// Mock the system app proxy; make message passing possible
let mockSendCustomEvent = function(aEventName, aMsg) {
Services.obs.notifyObservers({wrappedJSObject: aMsg}, aEventName, null);
};
function run_test() {
@ -144,7 +143,7 @@ add_test(function test_invalidEmailCase_signIn() {
Services.obs.addObserver(onMessage, "mozFxAccountsChromeEvent", false);
FxAccountsMgmtService._shell = mockShell;
SystemAppProxy._sendCustomEvent = mockSendCustomEvent;
// Trigger signIn using an email with incorrect capitalization
FxAccountsMgmtService.handleEvent({

View File

@ -16,6 +16,9 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1", "nsIMessageBroadcaster");
XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
"resource://gre/modules/SystemAppProxy.jsm");
this.Keyboard = {
_formMM: null, // The current web page message manager.
_keyboardMM: null, // The keyboard app message manager.
@ -231,7 +234,7 @@ this.Keyboard = {
// Chrome event, used also to render value selectors; that's why we need
// the info about choices / min / max here as well...
this.sendChromeEvent({
SystemAppProxy.dispatchEvent({
type: 'inputmethod-contextchange',
inputType: msg.data.type,
value: msg.data.value,
@ -266,13 +269,13 @@ this.Keyboard = {
},
showInputMethodPicker: function keyboardShowInputMethodPicker() {
this.sendChromeEvent({
SystemAppProxy.dispatchEvent({
type: "inputmethod-showall"
});
},
switchToNextInputMethod: function keyboardSwitchToNextInputMethod() {
this.sendChromeEvent({
SystemAppProxy.dispatchEvent({
type: "inputmethod-next"
});
},
@ -312,13 +315,6 @@ this.Keyboard = {
this._layouts = layouts;
this.sendToKeyboard('Keyboard:LayoutsChange', layouts);
},
sendChromeEvent: function(event) {
let browser = Services.wm.getMostRecentWindow("navigator:browser");
if (browser && browser.shell) {
browser.shell.sendChromeEvent(event);;
}
}
};