From d78b5ae798fdc9eeb7c32c3ef4504af10c16aab1 Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Mon, 31 Aug 2015 11:23:39 -0700 Subject: [PATCH] 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 --- toolkit/mozapps/extensions/AddonManager.jsm | 21 ++- toolkit/mozapps/extensions/addonManager.js | 3 + .../mozapps/extensions/amContentHandler.js | 39 +++++- .../mozapps/extensions/amInstallTrigger.js | 33 ++++- .../extensions/test/browser/browser.ini | 3 + .../test/browser/browser_discovery_install.js | 130 ++++++++++++++++++ .../test/browser/discovery_frame.html | 6 + .../test/browser/discovery_install.html | 18 +++ 8 files changed, 236 insertions(+), 17 deletions(-) create mode 100644 toolkit/mozapps/extensions/test/browser/browser_discovery_install.js create mode 100644 toolkit/mozapps/extensions/test/browser/discovery_frame.html create mode 100644 toolkit/mozapps/extensions/test/browser/discovery_install.html diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index 46a118ef5d34..77573e405d4f 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -2184,6 +2184,19 @@ var AddonManagerInternal = { return; } + // When a chrome in-content UI has loaded a 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(); diff --git a/toolkit/mozapps/extensions/addonManager.js b/toolkit/mozapps/extensions/addonManager.js index dcaa43d6be13..862b1ea69e45 100644 --- a/toolkit/mozapps/extensions/addonManager.js +++ b/toolkit/mozapps/extensions/addonManager.js @@ -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 = { diff --git a/toolkit/mozapps/extensions/amContentHandler.js b/toolkit/mozapps/extensions/amContentHandler.js index ebe552110fe0..708f670b1e87 100644 --- a/toolkit/mozapps/extensions/amContentHandler.js +++ b/toolkit/mozapps/extensions/amContentHandler.js @@ -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}"), diff --git a/toolkit/mozapps/extensions/amInstallTrigger.js b/toolkit/mozapps/extensions/amInstallTrigger.js index 481e8a254f84..f310306c38b1 100644 --- a/toolkit/mozapps/extensions/amInstallTrigger.js +++ b/toolkit/mozapps/extensions/amInstallTrigger.js @@ -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]; }, diff --git a/toolkit/mozapps/extensions/test/browser/browser.ini b/toolkit/mozapps/extensions/test/browser/browser.ini index 3f5abe470d42..b28ed45b0350 100644 --- a/toolkit/mozapps/extensions/test/browser/browser.ini +++ b/toolkit/mozapps/extensions/test/browser/browser.ini @@ -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] diff --git a/toolkit/mozapps/extensions/test/browser/browser_discovery_install.js b/toolkit/mozapps/extensions/test/browser/browser_discovery_install.js new file mode 100644 index 000000000000..af10a9bc7d73 --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/browser_discovery_install.js @@ -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); +}); diff --git a/toolkit/mozapps/extensions/test/browser/discovery_frame.html b/toolkit/mozapps/extensions/test/browser/discovery_frame.html new file mode 100644 index 000000000000..8ec722812eed --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/discovery_frame.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/toolkit/mozapps/extensions/test/browser/discovery_install.html b/toolkit/mozapps/extensions/test/browser/discovery_install.html new file mode 100644 index 000000000000..3832adff640c --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/discovery_install.html @@ -0,0 +1,18 @@ + + + + + +

Test page for the discovery pane

+

Direct install

+

JS install

+ +