Bug 1200027: Fix installing add-ons from the AMO discovery pane in the add-ons manager. r=dveditz

--HG--
extra : commitid : 94OiH0KUpNn
extra : rebase_source : 5d51f94c1627b19bdc438fe05e5475bd3525070d
extra : amend_source : 203756cbbab7f0f7dbda20ec003292081d96bb51
This commit is contained in:
Dave Townsend 2015-08-31 11:23:39 -07:00
parent a530c7b60d
commit d78b5ae798
8 changed files with 236 additions and 17 deletions

View File

@ -2184,6 +2184,19 @@ var AddonManagerInternal = {
return;
}
// When a chrome in-content UI has loaded a <browser> inside to host a
// website we want to do our security checks on the inner-browser but
// notify front-end that install events came from the outer-browser (the
// main tab's browser). Check this by seeing if the browser we've been
// passed is in a content type docshell and if so get the outer-browser.
let topBrowser = aBrowser;
let docShell = aBrowser.ownerDocument.defaultView
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIDocShellTreeItem);
if (docShell.itemType == Ci.nsIDocShellTreeItem.typeContent)
topBrowser = docShell.chromeEventHandler;
try {
let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"].
getService(Ci.amIWebInstallListener);
@ -2192,7 +2205,7 @@ var AddonManagerInternal = {
for (let install of aInstalls)
install.cancel();
weblistener.onWebInstallDisabled(aBrowser, aInstallingPrincipal.URI,
weblistener.onWebInstallDisabled(topBrowser, aInstallingPrincipal.URI,
aInstalls, aInstalls.length);
return;
}
@ -2201,7 +2214,7 @@ var AddonManagerInternal = {
install.cancel();
if (weblistener instanceof Ci.amIWebInstallListener2) {
weblistener.onWebInstallOriginBlocked(aBrowser, aInstallingPrincipal.URI,
weblistener.onWebInstallOriginBlocked(topBrowser, aInstallingPrincipal.URI,
aInstalls, aInstalls.length);
}
return;
@ -2213,14 +2226,14 @@ var AddonManagerInternal = {
new BrowserListener(aBrowser, aInstallingPrincipal, aInstalls);
if (!this.isInstallAllowed(aMimetype, aInstallingPrincipal)) {
if (weblistener.onWebInstallBlocked(aBrowser, aInstallingPrincipal.URI,
if (weblistener.onWebInstallBlocked(topBrowser, aInstallingPrincipal.URI,
aInstalls, aInstalls.length)) {
aInstalls.forEach(function(aInstall) {
aInstall.install();
});
}
}
else if (weblistener.onWebInstallRequested(aBrowser, aInstallingPrincipal.URI,
else if (weblistener.onWebInstallRequested(topBrowser, aInstallingPrincipal.URI,
aInstalls, aInstalls.length)) {
aInstalls.forEach(function(aInstall) {
aInstall.install();

View File

@ -47,6 +47,9 @@ function amManager() {
gParentMM = Cc["@mozilla.org/parentprocessmessagemanager;1"]
.getService(Ci.nsIMessageListenerManager);
gParentMM.addMessageListener(MSG_INSTALL_ENABLED, this);
// Needed so receiveMessage can be called directly by JS callers
this.wrappedJSObject = this;
}
amManager.prototype = {

View File

@ -12,6 +12,7 @@ const XPI_CONTENT_TYPE = "application/x-xpinstall";
const MSG_INSTALL_ADDONS = "WebInstallerInstallAddonsFromWebpage";
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
function amContentHandler() {
}
@ -45,12 +46,7 @@ amContentHandler.prototype = {
aRequest.cancel(Cr.NS_BINDING_ABORTED);
let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIContentFrameMessageManager);
messageManager.sendAsyncMessage(MSG_INSTALL_ADDONS, {
let installs = {
uris: [uri.spec],
hashes: [null],
names: [null],
@ -58,7 +54,36 @@ amContentHandler.prototype = {
mimetype: XPI_CONTENT_TYPE,
triggeringPrincipal: aRequest.loadInfo.triggeringPrincipal,
callbackID: -1
});
};
if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
// When running in the main process this might be a frame inside an
// in-content UI page, walk up to find the first frame element in a chrome
// privileged document
let element = window.frameElement;
let ssm = Services.scriptSecurityManager;
while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal))
element = element.ownerDocument.defaultView.frameElement;
if (element) {
let listener = Cc["@mozilla.org/addons/integration;1"].
getService(Ci.nsIMessageListener);
listener.wrappedJSObject.receiveMessage({
name: MSG_INSTALL_ADDONS,
target: element,
data: installs,
});
return;
}
}
// Fall back to sending through the message manager
let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIContentFrameMessageManager);
messageManager.sendAsyncMessage(MSG_INSTALL_ADDONS, installs);
},
classID: Components.ID("{7beb3ba8-6ec3-41b4-b67c-da89b8518922}"),

View File

@ -67,18 +67,39 @@ RemoteMediator.prototype = {
},
install: function(installs, principal, callback, window) {
let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIContentFrameMessageManager);
let callbackID = this._addCallback(callback, installs.uris);
installs.mimetype = XPINSTALL_MIMETYPE;
installs.triggeringPrincipal = principal;
installs.callbackID = callbackID;
if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
// When running in the main process this might be a frame inside an
// in-content UI page, walk up to find the first frame element in a chrome
// privileged document
let element = window.frameElement;
let ssm = Services.scriptSecurityManager;
while (element && !ssm.isSystemPrincipal(element.ownerDocument.nodePrincipal))
element = element.ownerDocument.defaultView.frameElement;
if (element) {
let listener = Cc["@mozilla.org/addons/integration;1"].
getService(Ci.nsIMessageListener);
return listener.wrappedJSObject.receiveMessage({
name: MSG_INSTALL_ADDONS,
target: element,
data: installs,
});
}
}
// Fall back to sending through the message manager
let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIContentFrameMessageManager);
return messageManager.sendSyncMessage(MSG_INSTALL_ADDONS, installs)[0];
},

View File

@ -6,6 +6,8 @@ support-files =
addon_prefs.xul
cancelCompatCheck.sjs
discovery.html
discovery_frame.html
discovery_install.html
signed_hotfix.rdf
signed_hotfix.xpi
unsigned_hotfix.rdf
@ -52,5 +54,6 @@ skip-if = e10s
[browser_select_update.js]
[browser_updatessl.js]
[browser_task_next_test.js]
[browser_discovery_install.js]
[include:browser-common.ini]

View File

@ -0,0 +1,130 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Tests that the discovery view can install add-ons correctly
const MAIN_URL = "https://example.com/" + RELATIVE_DIR + "discovery_install.html";
const GOOD_FRAMED_URL = "https://example.com/" + RELATIVE_DIR + "discovery_frame.html";
const BAD_FRAMED_URL = "https://example.org/" + RELATIVE_DIR + "discovery_frame.html";
// Temporarily enable caching
Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true);
// Allow SSL from non-built-in certs
Services.prefs.setBoolPref("extensions.install.requireBuiltInCerts", false);
// Allow installs from the test site
Services.perms.add(NetUtil.newURI("https://example.com/"), "install",
Ci.nsIPermissionManager.ALLOW_ACTION);
Services.perms.add(NetUtil.newURI("https://example.org/"), "install",
Ci.nsIPermissionManager.ALLOW_ACTION);
registerCleanupFunction(() => {
Services.perms.remove(NetUtil.newURI("https://example.com/"), "install");
Services.perms.remove(NetUtil.newURI("https://example.org/"), "install");
});
function clickLink(frameLoader, id) {
let link = frameLoader.contentDocument.getElementById(id);
EventUtils.sendMouseEvent({type: "click"}, link);
}
function waitForInstall() {
return new Promise(resolve => {
wait_for_window_open((window) => {
is(window.location, "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul",
"Should have seen the install window");
window.document.documentElement.cancelDialog();
resolve();
});
});
}
function waitForFail() {
return new Promise(resolve => {
let listener = (subject, topic, data) => {
Services.obs.removeObserver(listener, topic);
resolve();
}
Services.obs.addObserver(listener, "addon-install-origin-blocked", false);
});
}
// Tests that navigating to an XPI attempts to install correctly
add_task(function* test_install_direct() {
Services.prefs.setCharPref(PREF_DISCOVERURL, MAIN_URL);
let managerWindow = yield open_manager("addons://discover/");
let browser = managerWindow.document.getElementById("discover-browser");
clickLink(browser, "install-direct");
yield waitForInstall();
yield close_manager(managerWindow);
});
// Tests that installing via JS works correctly
add_task(function* test_install_js() {
Services.prefs.setCharPref(PREF_DISCOVERURL, MAIN_URL);
let managerWindow = yield open_manager("addons://discover/");
let browser = managerWindow.document.getElementById("discover-browser");
clickLink(browser, "install-js");
yield waitForInstall();
yield close_manager(managerWindow);
});
// Installing from an inner-frame of the same origin should work
add_task(function* test_install_inner_direct() {
Services.prefs.setCharPref(PREF_DISCOVERURL, GOOD_FRAMED_URL);
let managerWindow = yield open_manager("addons://discover/");
let browser = managerWindow.document.getElementById("discover-browser");
let frame = browser.contentDocument.getElementById("frame");
clickLink(frame, "install-direct");
yield waitForInstall();
yield close_manager(managerWindow);
});
add_task(function* test_install_inner_js() {
Services.prefs.setCharPref(PREF_DISCOVERURL, GOOD_FRAMED_URL);
let managerWindow = yield open_manager("addons://discover/");
let browser = managerWindow.document.getElementById("discover-browser");
let frame = browser.contentDocument.getElementById("frame");
clickLink(frame, "install-js");
yield waitForInstall();
yield close_manager(managerWindow);
});
// Installing from an inner-frame of a different origin should fail
add_task(function* test_install_xorigin_direct() {
Services.prefs.setCharPref(PREF_DISCOVERURL, BAD_FRAMED_URL);
let managerWindow = yield open_manager("addons://discover/");
let browser = managerWindow.document.getElementById("discover-browser");
let frame = browser.contentDocument.getElementById("frame");
clickLink(frame, "install-direct");
yield waitForFail();
yield close_manager(managerWindow);
});
add_task(function* test_install_xorigin_js() {
Services.prefs.setCharPref(PREF_DISCOVERURL, BAD_FRAMED_URL);
let managerWindow = yield open_manager("addons://discover/");
let browser = managerWindow.document.getElementById("discover-browser");
let frame = browser.contentDocument.getElementById("frame");
clickLink(frame, "install-js");
yield waitForFail();
yield close_manager(managerWindow);
});

View File

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<iframe id="frame" width="100%" height="100%" src="https://example.com/browser/toolkit/mozapps/extensions/test/browser/discovery_install.html"></iframe>
</body>

View File

@ -0,0 +1,18 @@
<html>
<head>
<script type="text/javascript">
function install() {
InstallTrigger.install({
"Test Add-on": {
URL: "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/unsigned.xpi"
}
});
}
</script>
</head>
<body>
<h1>Test page for the discovery pane</h1>
<p><a id="install-direct" href="https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/unsigned.xpi">Direct install</a></p>
<p><a id="install-js" href="javascript:install()">JS install</a></p>
</body>
</html>