From 1e0c9f72000d0ac2998c570a40a1ad2d390ee75c Mon Sep 17 00:00:00 2001 From: Swapnesh Kumar Sahoo Date: Sat, 13 May 2017 02:31:37 +0530 Subject: [PATCH 01/31] Bug 1302773 - Install tox in docker image in MozReview; r=dustin MozReview-Commit-ID: 3jiVsZOrvW9 --HG-- extra : rebase_source : 516ddf063ed6a15e397cb782262fa15ccb533969 --- taskcluster/ci/source-test/python-tests.yml | 3 +-- taskcluster/docker/lint/Dockerfile | 2 ++ taskcluster/docker/lint/system-setup.sh | 8 ++++++++ tools/lint/tox/tox_requirements.txt | 4 ++++ 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 tools/lint/tox/tox_requirements.txt diff --git a/taskcluster/ci/source-test/python-tests.yml b/taskcluster/ci/source-test/python-tests.yml index ede2ec25c512..b9f5074ca772 100644 --- a/taskcluster/ci/source-test/python-tests.yml +++ b/taskcluster/ci/source-test/python-tests.yml @@ -98,8 +98,7 @@ mozharness: cache-dotcache: true command: > cd /home/worker/checkouts/gecko/testing/mozharness && - /usr/bin/pip2 install tox && - /home/worker/.local/bin/tox -e py27-hg4.1 + /usr/local/bin/tox -e py27-hg4.1 run-on-projects: - integration - release diff --git a/taskcluster/docker/lint/Dockerfile b/taskcluster/docker/lint/Dockerfile index c1a29ef505ca..b75b62359cda 100644 --- a/taskcluster/docker/lint/Dockerfile +++ b/taskcluster/docker/lint/Dockerfile @@ -19,6 +19,8 @@ ADD topsrcdir/taskcluster/docker/recipes/install-mercurial.sh /build/install-mer ADD system-setup.sh /tmp/system-setup.sh # %include tools/lint/flake8/flake8_requirements.txt ADD topsrcdir/tools/lint/flake8/flake8_requirements.txt /tmp/flake8_requirements.txt +# %include tools/lint/tox/tox_requirements.txt +ADD topsrcdir/tools/lint/tox/tox_requirements.txt /tmp/tox_requirements.txt RUN bash /tmp/system-setup.sh # %include taskcluster/docker/recipes/run-task diff --git a/taskcluster/docker/lint/system-setup.sh b/taskcluster/docker/lint/system-setup.sh index cf6ac91fc9f2..3091b164f8ac 100644 --- a/taskcluster/docker/lint/system-setup.sh +++ b/taskcluster/docker/lint/system-setup.sh @@ -51,5 +51,13 @@ cd /setup pip install --require-hashes -r /tmp/flake8_requirements.txt +### +# tox Setup +### + +cd /setup + +pip install --require-hashes -r /tmp/tox_requirements.txt + cd / rm -rf /setup diff --git a/tools/lint/tox/tox_requirements.txt b/tools/lint/tox/tox_requirements.txt new file mode 100644 index 000000000000..fb629566aba2 --- /dev/null +++ b/tools/lint/tox/tox_requirements.txt @@ -0,0 +1,4 @@ +pluggy==0.4.0 --hash=sha256:d2766caddfbbc8ef641d47da556d2ae3056860ce4d553aa04009e42b76a09951 +py==1.4.33 --hash=sha256:81b5e37db3cc1052de438375605fb5d3b3e97f950f415f9143f04697c684d7eb +tox==2.7.0 --hash=sha256:0f37ea637ead4a5bbae91531b0bf8fd327c7152e20255e5960ee180598228d21 +virtualenv==15.1.0 --hash=sha256:39d88b533b422825d644087a21e78c45cf5af0ef7a99a1fc9fbb7b481e5c85b0 From e1cadd87c2bd066fc2705bdcef7960702606db46 Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Wed, 17 May 2017 12:48:47 -0500 Subject: [PATCH 02/31] servo: Merge #16910 - Bindgen cross compilation take 2 (from froydnj:bindgen-cross-compilation-take-2); r=emilio Fix all the cross-compilation issues in build_gecko.rs. - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #16879 Source-Repo: https://github.com/servo/servo Source-Revision: a7fecc4127560ff721611bc055923dd0c6787652 --HG-- extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear extra : subtree_revision : e33681c18c4d435434cf1ca8882215c60d4af8ee --- servo/components/style/build_gecko.rs | 44 ++++++++++++++++----------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/servo/components/style/build_gecko.rs b/servo/components/style/build_gecko.rs index 6da76e25f42b..fa08db836228 100644 --- a/servo/components/style/build_gecko.rs +++ b/servo/components/style/build_gecko.rs @@ -148,38 +148,46 @@ mod bindings { if build_type == BuildType::Debug { builder = builder.clang_arg("-DDEBUG=1").clang_arg("-DJS_DEBUG=1"); } - if cfg!(target_family = "unix") { + // cfg!(...) will check the attributes of the Rust target this file + // is being compiled for. We want the attributes of the target that + // the clang we're going to invoke is being compiled for, which isn't + // necessarily the same thing. Cargo provides the (yet-to-be-documented) + // CARGO_CFG_* environment variables for this purpose. Those variables + // should be used in preference to cfg! checks below. + let target_family = env::var("CARGO_CFG_TARGET_FAMILY").unwrap(); + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let target_pointer_width = env::var("CARGO_CFG_TARGET_POINTER_WIDTH").unwrap(); + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap(); + + if target_family == "unix" { builder = builder.clang_arg("-DOS_POSIX=1"); } - if cfg!(target_os = "linux") { + if target_os == "linux" { builder = builder.clang_arg("-DOS_LINUX=1"); - // We may be cross-compiling with a clang that defaults to - // a different architecture, so we should explicitly specify - // the bitness being used here. Specifying --target instead - // leads to difficulties with LLVM search paths. - if cfg!(target_arch = "x86") { - builder = builder.clang_arg("-m32") - } else if cfg!(target_arch = "x86_64") { - builder = builder.clang_arg("-m64") + if target_arch == "x86" { + builder = builder.clang_arg("-m32"); + } else if target_arch == "x86_64" { + builder = builder.clang_arg("-m64"); } - } else if cfg!(target_os = "solaris") { + } else if target_os == "solaris" { builder = builder.clang_arg("-DOS_SOLARIS=1"); - } else if cfg!(target_os = "dragonfly") { + } else if target_os == "dragonfly" { builder = builder.clang_arg("-DOS_BSD=1").clang_arg("-DOS_DRAGONFLY=1"); - } else if cfg!(target_os = "freebsd") { + } else if target_os == "freebsd" { builder = builder.clang_arg("-DOS_BSD=1").clang_arg("-DOS_FREEBSD=1"); - } else if cfg!(target_os = "netbsd") { + } else if target_os == "netbsd" { builder = builder.clang_arg("-DOS_BSD=1").clang_arg("-DOS_NETBSD=1"); - } else if cfg!(target_os = "openbsd") { + } else if target_os == "openbsd" { builder = builder.clang_arg("-DOS_BSD=1").clang_arg("-DOS_OPENBSD=1"); - } else if cfg!(target_os = "macos") { + } else if target_os == "macos" { builder = builder.clang_arg("-DOS_MACOSX=1") .clang_arg("-stdlib=libc++") // To disable the fixup bindgen applies which adds search // paths from clang command line in order to avoid potential // conflict with -stdlib=libc++. .clang_arg("--target=x86_64-apple-darwin"); - } else if cfg!(target_env = "msvc") { + } else if target_env == "msvc" { builder = builder.clang_arg("-DOS_WIN=1").clang_arg("-DWIN32=1") // For compatibility with MSVC 2015 .clang_arg("-fms-compatibility-version=19") @@ -191,7 +199,7 @@ mod bindings { // thus not enabled by default with a MSVC-compatibile build) // to exclude hidden symbols from the generated file. .clang_arg("-DHAVE_VISIBILITY_HIDDEN_ATTRIBUTE=1"); - if cfg!(target_pointer_width = "32") { + if target_pointer_width == "32" { builder = builder.clang_arg("--target=i686-pc-win32"); } else { builder = builder.clang_arg("--target=x86_64-pc-win32"); From aac6f63057fdd60a83917030577f421af2796587 Mon Sep 17 00:00:00 2001 From: William Lachance Date: Wed, 17 May 2017 14:59:00 -0400 Subject: [PATCH 03/31] Bug 1365708 - Stop running awsy on linux32 r=jmaher MozReview-Commit-ID: 5tIctDBpXLf --HG-- extra : rebase_source : 8616fd68ed4eb1aac90d2dea984fe734a9c2d9c9 --- taskcluster/ci/test/test-platforms.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/taskcluster/ci/test/test-platforms.yml b/taskcluster/ci/test/test-platforms.yml index 1f7078eb730e..a3a33f05628d 100644 --- a/taskcluster/ci/test/test-platforms.yml +++ b/taskcluster/ci/test/test-platforms.yml @@ -24,13 +24,11 @@ linux32/opt: test-sets: - linux32-tests - linux32-opt-tests - - awsy linux32-nightly/opt: build-platform: linux-nightly/opt test-sets: - linux32-tests - linux32-opt-tests - - awsy linux64/debug: build-platform: linux64/debug From 149f1410cefc1504e2dd5082dc6f3a268df85a08 Mon Sep 17 00:00:00 2001 From: Luca Greco Date: Tue, 21 Mar 2017 16:24:13 +0100 Subject: [PATCH 04/31] Bug 1302702 - Provide a DebugUtils object from ExtensionParent.jsm. r=kmag This change prepare the WebExtensions internals to the changes applied to the addon debugging facilities in the other patches from this queue. In ExtensionParent.jsm, a HiddenXULWindow helper class has been refactored out of the HiddenExtensionPage and then reused by both HiddenExtensionPage and the new DebugUtils object. The DebugUtils object provides the utility methods used by the devtools actors related to the addon debugging, which are used to retrieve an "extension process browser XUL element" (a XUL browser element that has been configured by DebugUtils to be used to connect the devtools parent actor to the process where the target extension is running), and then release it when it is not needed anymore (because the developer toolbox has been closed and all the devtools actors destroyed). The DebugUtils object used the HiddenXULWindow class to lazily create an hidden XUL window to contain the "extension process browser XUL elements" described above (and the HiddenXULWindow istance is then destroyed when there is no devtools actor that is using it anymore). MozReview-Commit-ID: 31RYQk1DMvE --HG-- extra : rebase_source : 82a71189b359acecd100e94e3c48ef344dbb48c4 --- toolkit/components/extensions/Extension.jsm | 14 + .../components/extensions/ExtensionParent.jsm | 385 ++++++++++++------ 2 files changed, 284 insertions(+), 115 deletions(-) diff --git a/toolkit/components/extensions/Extension.jsm b/toolkit/components/extensions/Extension.jsm index 622ed19e8b03..17a034c64c65 100644 --- a/toolkit/components/extensions/Extension.jsm +++ b/toolkit/components/extensions/Extension.jsm @@ -14,6 +14,20 @@ this.EXPORTED_SYMBOLS = ["Extension", "ExtensionData"]; * and calls .startup() on it. It calls .shutdown() when the extension * unloads. Extension manages any extension-specific state in * the chrome process. + * + * TODO(rpl): we are current restricting the extensions to a single process + * (set as the current default value of the "dom.ipc.processCount.extension" + * preference), if we switch to use more than one extension process, we have to + * be sure that all the browser's frameLoader are associated to the same process, + * e.g. by using the `sameProcessAsFrameLoader` property. + * (http://searchfox.org/mozilla-central/source/dom/interfaces/base/nsIBrowser.idl) + * + * At that point we are going to keep track of the existing browsers associated to + * a webextension to ensure that they are all running in the same process (and we + * are also going to do the same with the browser element provided to the + * addon debugging Remote Debugging actor, e.g. because the addon has been + * reloaded by the user, we have to ensure that the new extension pages are going + * to run in the same process of the existing addon debugging browser element). */ const Ci = Components.interfaces; diff --git a/toolkit/components/extensions/ExtensionParent.jsm b/toolkit/components/extensions/ExtensionParent.jsm index 31b84db48da2..4e4af22d6312 100644 --- a/toolkit/components/extensions/ExtensionParent.jsm +++ b/toolkit/components/extensions/ExtensionParent.jsm @@ -45,6 +45,7 @@ var { } = ExtensionCommon; var { + DefaultWeakMap, MessageManagerProxy, SpreadArgs, defineLazyGetter, @@ -692,6 +693,126 @@ ParentAPIManager = { ParentAPIManager.init(); +/** + * This utility class is used to create hidden XUL windows, which are used to + * contains the extension pages that are not visible (e.g. the background page and + * the devtools page), and it is also used by the ExtensionDebuggingUtils to + * contains the browser elements that are used by the addon debugger to be able + * to connect to the devtools actors running in the same process of the target + * extension (and be able to stay connected across the addon reloads). + */ +class HiddenXULWindow { + constructor() { + this._windowlessBrowser = null; + this.waitInitialized = this.initWindowlessBrowser(); + } + + shutdown() { + if (this.unloaded) { + throw new Error("Unable to shutdown an unloaded HiddenXULWindow instance"); + } + + this.unloaded = true; + + this.chromeShell = null; + this.waitInitialized = null; + + this._windowlessBrowser.close(); + this._windowlessBrowser = null; + } + + get chromeDocument() { + return this._windowlessBrowser.document; + } + + /** + * Private helper that create a XULDocument in a windowless browser. + * + * @returns {Promise} + * A promise which resolves to the newly created XULDocument. + */ + async initWindowlessBrowser() { + if (this.waitInitialized) { + throw new Error("HiddenXULWindow already initialized"); + } + + // The invisible page is currently wrapped in a XUL window to fix an issue + // with using the canvas API from a background page (See Bug 1274775). + let windowlessBrowser = Services.appShell.createWindowlessBrowser(true); + this._windowlessBrowser = windowlessBrowser; + + // The windowless browser is a thin wrapper around a docShell that keeps + // its related resources alive. It implements nsIWebNavigation and + // forwards its methods to the underlying docShell, but cannot act as a + // docShell itself. Calling `getInterface(nsIDocShell)` gives us the + // underlying docShell, and `QueryInterface(nsIWebNavigation)` gives us + // access to the webNav methods that are already available on the + // windowless browser, but contrary to appearances, they are not the same + // object. + this.chromeShell = this._windowlessBrowser + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIWebNavigation); + + if (PrivateBrowsingUtils.permanentPrivateBrowsing) { + let attrs = this.chromeShell.getOriginAttributes(); + attrs.privateBrowsingId = 1; + this.chromeShell.setOriginAttributes(attrs); + } + + let system = Services.scriptSecurityManager.getSystemPrincipal(); + this.chromeShell.createAboutBlankContentViewer(system); + this.chromeShell.useGlobalHistory = false; + this.chromeShell.loadURI(XUL_URL, 0, null, null, null); + + await promiseObserved("chrome-document-global-created", + win => win.document == this.chromeShell.document); + return promiseDocumentLoaded(windowlessBrowser.document); + } + + /** + * Creates the browser XUL element that will contain the WebExtension Page. + * + * @param {Object} xulAttributes + * An object that contains the xul attributes to set of the newly + * created browser XUL element. + * + * @returns {Promise} + * A Promise which resolves to the newly created browser XUL element. + */ + async createBrowserElement(xulAttributes) { + if (!xulAttributes || Object.keys(xulAttributes).length === 0) { + throw new Error("missing mandatory xulAttributes parameter"); + } + + await this.waitInitialized; + + const chromeDoc = this.chromeDocument; + + const browser = chromeDoc.createElement("browser"); + browser.setAttribute("type", "content"); + browser.setAttribute("disableglobalhistory", "true"); + + for (const [name, value] of Object.entries(xulAttributes)) { + if (value != null) { + browser.setAttribute(name, value); + } + } + + let awaitFrameLoader = Promise.resolve(); + + if (browser.getAttribute("remote") === "true") { + awaitFrameLoader = promiseEvent(browser, "XULFrameLoaderCreated"); + } + + chromeDoc.documentElement.appendChild(browser); + await awaitFrameLoader; + + return browser; + } +} + + /** * This is a base class used by the ext-backgroundPage and ext-devtools API implementations * to inherits the shared boilerplate code needed to create a parent document for the hidden @@ -699,23 +820,22 @@ ParentAPIManager.init(); * DevToolsPage classes. * * @param {Extension} extension - * the Extension which owns the hidden extension page created (used to decide - * if the hidden extension page parent doc is going to be a windowlessBrowser or - * a visible XUL window) + * The Extension which owns the hidden extension page created (used to decide + * if the hidden extension page parent doc is going to be a windowlessBrowser or + * a visible XUL window). * @param {string} viewType - * the viewType of the WebExtension page that is going to be loaded - * in the created browser element (e.g. "background" or "devtools_page"). - * + * The viewType of the WebExtension page that is going to be loaded + * in the created browser element (e.g. "background" or "devtools_page"). */ -class HiddenExtensionPage { +class HiddenExtensionPage extends HiddenXULWindow { constructor(extension, viewType) { if (!extension || !viewType) { throw new Error("extension and viewType parameters are mandatory"); } + + super(); this.extension = extension; this.viewType = viewType; - this.parentWindow = null; - this.windowlessBrowser = null; this.browser = null; } @@ -727,126 +847,160 @@ class HiddenExtensionPage { throw new Error("Unable to shutdown an unloaded HiddenExtensionPage instance"); } - this.unloaded = true; - if (this.browser) { this.browser.remove(); this.browser = null; } - // Navigate away from the background page to invalidate any - // setTimeouts or other callbacks. - if (this.webNav) { - this.webNav.loadURI("about:blank", 0, null, null, null); - this.webNav = null; - } - - if (this.parentWindow) { - this.parentWindow.close(); - this.parentWindow = null; - } - - if (this.windowlessBrowser) { - this.windowlessBrowser.loadURI("about:blank", 0, null, null, null); - this.windowlessBrowser.close(); - this.windowlessBrowser = null; - } + super.shutdown(); } /** * Creates the browser XUL element that will contain the WebExtension Page. * * @returns {Promise} - * a Promise which resolves to the newly created browser XUL element. + * A Promise which resolves to the newly created browser XUL element. */ async createBrowserElement() { if (this.browser) { throw new Error("createBrowserElement called twice"); } - let chromeDoc = await this.createWindowlessBrowser(); - - const browser = this.browser = chromeDoc.createElement("browser"); - browser.setAttribute("type", "content"); - browser.setAttribute("disableglobalhistory", "true"); - browser.setAttribute("webextension-view-type", this.viewType); - - let awaitFrameLoader = Promise.resolve(); - - if (this.extension.remote) { - browser.setAttribute("remote", "true"); - browser.setAttribute("remoteType", E10SUtils.EXTENSION_REMOTE_TYPE); - awaitFrameLoader = promiseEvent(browser, "XULFrameLoaderCreated"); - } - - chromeDoc.documentElement.appendChild(browser); - await awaitFrameLoader; - - return browser; - } - - /** - * Private helper that create a XULDocument in a windowless browser. - * - * An hidden extension page (e.g. a background page or devtools page) is usually - * loaded into a windowless browser, with no on-screen representation or graphical - * display abilities. - * - * This currently does not support remote browsers, and therefore cannot - * be used with out-of-process extensions. - * - * @returns {Promise} - * a promise which resolves to the newly created XULDocument. - */ - createWindowlessBrowser() { - // The invisible page is currently wrapped in a XUL window to fix an issue - // with using the canvas API from a background page (See Bug 1274775). - let windowlessBrowser = Services.appShell.createWindowlessBrowser(true); - this.windowlessBrowser = windowlessBrowser; - - // The windowless browser is a thin wrapper around a docShell that keeps - // its related resources alive. It implements nsIWebNavigation and - // forwards its methods to the underlying docShell, but cannot act as a - // docShell itself. Calling `getInterface(nsIDocShell)` gives us the - // underlying docShell, and `QueryInterface(nsIWebNavigation)` gives us - // access to the webNav methods that are already available on the - // windowless browser, but contrary to appearances, they are not the same - // object. - let chromeShell = windowlessBrowser.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell) - .QueryInterface(Ci.nsIWebNavigation); - - return this.initParentWindow(chromeShell).then(() => { - return promiseDocumentLoaded(windowlessBrowser.document); + this.browser = await super.createBrowserElement({ + "webextension-view-type": this.viewType, + "remote": this.extension.remote ? "true" : null, + "remoteType": this.extension.remote ? + E10SUtils.EXTENSION_REMOTE_TYPE : null, }); - } - /** - * Private helper that initialize the created parent document. - * - * @param {nsIDocShell} chromeShell - * the docShell related to initialize. - * - * @returns {Promise} - * the initialized parent chrome document. - */ - initParentWindow(chromeShell) { - if (PrivateBrowsingUtils.permanentPrivateBrowsing) { - let attrs = chromeShell.getOriginAttributes(); - attrs.privateBrowsingId = 1; - chromeShell.setOriginAttributes(attrs); - } - - let system = Services.scriptSecurityManager.getSystemPrincipal(); - chromeShell.createAboutBlankContentViewer(system); - chromeShell.useGlobalHistory = false; - chromeShell.loadURI(XUL_URL, 0, null, null, null); - - return promiseObserved("chrome-document-global-created", - win => win.document == chromeShell.document); + return this.browser; } } +/** + * This object provides utility functions needed by the devtools actors to + * be able to connect and debug an extension (which can run in the main or in + * a child extension process). + */ +const DebugUtils = { + // A lazily created hidden XUL window, which contains the browser elements + // which are used to connect the webextension patent actor to the extension process. + hiddenXULWindow: null, + + // Map> + debugBrowserPromises: new Map(), + // DefaultWeakMap, Set> + debugActors: new DefaultWeakMap(() => new Set()), + + _extensionUpdatedWatcher: null, + watchExtensionUpdated() { + if (!this._extensionUpdatedWatcher) { + // Watch the updated extension objects. + this._extensionUpdatedWatcher = async (evt, extension) => { + const browserPromise = this.debugBrowserPromises.get(extension.id); + if (browserPromise) { + const browser = await browserPromise; + if (browser.isRemoteBrowser !== extension.remote && + this.debugBrowserPromises.get(extension.id) === browserPromise) { + // If the cached browser element is not anymore of the same + // remote type of the extension, remove it. + this.debugBrowserPromises.delete(extension.id); + browser.remove(); + } + } + }; + + apiManager.on("ready", this._extensionUpdatedWatcher); + } + }, + + unwatchExtensionUpdated() { + if (this._extensionUpdatedWatcher) { + apiManager.off("ready", this._extensionUpdatedWatcher); + delete this._extensionUpdatedWatcher; + } + }, + + + /** + * Retrieve a XUL browser element which has been configured to be able to connect + * the devtools actor with the process where the extension is running. + * + * @param {WebExtensionParentActor} webExtensionParentActor + * The devtools actor that is retrieving the browser element. + * + * @returns {Promise} + * A promise which resolves to the configured browser XUL element. + */ + async getExtensionProcessBrowser(webExtensionParentActor) { + const extensionId = webExtensionParentActor.addonId; + const extension = GlobalManager.getExtension(extensionId); + if (!extension) { + throw new Error(`Extension not found: ${extensionId}`); + } + + const createBrowser = () => { + if (!this.hiddenXULWindow) { + this.hiddenXULWindow = new HiddenXULWindow(); + this.watchExtensionUpdated(); + } + + return this.hiddenXULWindow.createBrowserElement({ + "webextension-addon-debug-target": extensionId, + "remote": extension.remote ? "true" : null, + "remoteType": extension.remote ? + E10SUtils.EXTENSION_REMOTE_TYPE : null, + }); + }; + + let browserPromise = this.debugBrowserPromises.get(extensionId); + + // Create a new promise if there is no cached one in the map. + if (!browserPromise) { + browserPromise = createBrowser(); + this.debugBrowserPromises.set(extensionId, browserPromise); + browserPromise.catch(() => { + this.debugBrowserPromises.delete(extensionId); + }); + } + + this.debugActors.get(browserPromise).add(webExtensionParentActor); + + return browserPromise; + }, + + + /** + * Given the devtools actor that has retrieved an addon debug browser element, + * it destroys the XUL browser element, and it also destroy the hidden XUL window + * if it is not currently needed. + * + * @param {WebExtensionParentActor} webExtensionParentActor + * The devtools actor that has retrieved an addon debug browser element. + */ + async releaseExtensionProcessBrowser(webExtensionParentActor) { + const extensionId = webExtensionParentActor.addonId; + const browserPromise = this.debugBrowserPromises.get(extensionId); + + if (browserPromise) { + const actorsSet = this.debugActors.get(browserPromise); + actorsSet.delete(webExtensionParentActor); + if (actorsSet.size === 0) { + this.debugActors.delete(browserPromise); + this.debugBrowserPromises.delete(extensionId); + await browserPromise.then((browser) => browser.remove()); + } + } + + if (this.debugBrowserPromises.size === 0 && this.hiddenXULWindow) { + this.hiddenXULWindow.shutdown(); + this.hiddenXULWindow = null; + this.unwatchExtensionUpdated(); + } + }, +}; + + function promiseExtensionViewLoaded(browser) { return new Promise(resolve => { browser.messageManager.addMessageListener("Extension:ExtensionViewLoaded", function onLoad({data}) { @@ -863,17 +1017,17 @@ function promiseExtensionViewLoaded(browser) { * created for the extension urls running into its iframe descendants). * * @param {object} params.extension - * the Extension on which we are going to listen for the newly created ExtensionProxyContext. + * The Extension on which we are going to listen for the newly created ExtensionProxyContext. * @param {string} params.viewType - * the viewType of the WebExtension page that we are watching (e.g. "background" or "devtools_page"). + * The viewType of the WebExtension page that we are watching (e.g. "background" or + * "devtools_page"). * @param {XULElement} params.browser - * the browser element of the WebExtension page that we are watching. + * The browser element of the WebExtension page that we are watching. + * @param {function} onExtensionProxyContextLoaded + * The callback that is called when a new context has been loaded (as `callback(context)`); * - * @param {Function} onExtensionProxyContextLoaded - * the callback that is called when a new context has been loaded (as `callback(context)`); - * - * @returns {Function} - * Unsubscribe the listener. + * @returns {function} + * Unsubscribe the listener. */ function watchExtensionProxyContextLoad({extension, viewType, browser}, onExtensionProxyContextLoaded) { if (typeof onExtensionProxyContextLoaded !== "function") { @@ -917,4 +1071,5 @@ const ExtensionParent = { }, promiseExtensionViewLoaded, watchExtensionProxyContextLoad, + DebugUtils, }; From e8cc579d67946042621d2cd0760d88324a4f397a Mon Sep 17 00:00:00 2001 From: Luca Greco Date: Tue, 11 Apr 2017 05:13:13 +0200 Subject: [PATCH 05/31] Bug 1302702 - ExtensionParent DebugUtils xpcshell test unit. r=kmag MozReview-Commit-ID: 2wbZfQiktho --HG-- extra : rebase_source : a8a6524ca74d76ef16eb63b227d1238ead23ae8e --- .../test/xpcshell/test_ext_debugging_utils.js | 231 ++++++++++++++++++ .../extensions/test/xpcshell/xpcshell.ini | 1 + 2 files changed, 232 insertions(+) create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_debugging_utils.js diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_debugging_utils.js b/toolkit/components/extensions/test/xpcshell/test_ext_debugging_utils.js new file mode 100644 index 000000000000..0c5e3e000d40 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_debugging_utils.js @@ -0,0 +1,231 @@ +/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set sts=2 sw=2 et tw=80: */ +"use strict"; + +Cu.import("resource://gre/modules/ExtensionParent.jsm"); +Cu.import("resource://gre/modules/Preferences.jsm"); + +add_task(async function testExtensionDebuggingUtilsCleanup() { + const extension = ExtensionTestUtils.loadExtension({ + background() { + browser.test.sendMessage("background.ready"); + }, + }); + + const expectedEmptyDebugUtils = { + hiddenXULWindow: null, + cacheSize: 0, + }; + + let { + hiddenXULWindow, + debugBrowserPromises, + } = ExtensionParent.DebugUtils; + + deepEqual({hiddenXULWindow, cacheSize: debugBrowserPromises.size}, + expectedEmptyDebugUtils, + "No ExtensionDebugUtils resources has been allocated yet"); + + await extension.startup(); + + await extension.awaitMessage("background.ready"); + + hiddenXULWindow = ExtensionParent.DebugUtils.hiddenXULWindow; + deepEqual({hiddenXULWindow, cacheSize: debugBrowserPromises.size}, + expectedEmptyDebugUtils, + "No debugging resources has been yet allocated once the extension is running"); + + const fakeAddonActor = { + addonId: extension.id, + }; + + const anotherAddonActor = { + addonId: extension.id, + }; + + const waitFirstBrowser = ExtensionParent.DebugUtils.getExtensionProcessBrowser(fakeAddonActor); + const waitSecondBrowser = ExtensionParent.DebugUtils.getExtensionProcessBrowser(anotherAddonActor); + + const addonDebugBrowser = await waitFirstBrowser; + equal(addonDebugBrowser.isRemoteBrowser, extension.extension.remote, + "The addon debugging browser has the expected remote type"); + + equal((await waitSecondBrowser), addonDebugBrowser, + "Two addon debugging actors related to the same addon get the same browser element "); + + equal(debugBrowserPromises.size, 1, "The expected resources has been allocated"); + + const nonExistentAddonActor = { + addonId: "non-existent-addon@test", + }; + + const waitRejection = ExtensionParent.DebugUtils.getExtensionProcessBrowser(nonExistentAddonActor); + + Assert.rejects(waitRejection, /Extension not found/, + "Reject with the expected message for non existent addons"); + + equal(debugBrowserPromises.size, 1, "No additional debugging resources has been allocated"); + + await ExtensionParent.DebugUtils.releaseExtensionProcessBrowser(fakeAddonActor); + + equal(debugBrowserPromises.size, 1, + "The addon debugging browser is cached until all the related actors have released it"); + + await ExtensionParent.DebugUtils.releaseExtensionProcessBrowser(anotherAddonActor); + + hiddenXULWindow = ExtensionParent.DebugUtils.hiddenXULWindow; + + deepEqual({hiddenXULWindow, cacheSize: debugBrowserPromises.size}, + expectedEmptyDebugUtils, + "All the allocated debugging resources has been cleared"); + + await extension.unload(); +}); + +add_task(async function testExtensionDebuggingUtilsAddonReloaded() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + applications: { + gecko: { + id: "test-reloaded@test.mozilla.com", + }, + }, + }, + background() { + browser.test.sendMessage("background.ready"); + }, + }); + + await extension.startup(); + await extension.awaitMessage("background.ready"); + + let fakeAddonActor = { + addonId: extension.id, + }; + + const addonDebugBrowser = await ExtensionParent.DebugUtils + .getExtensionProcessBrowser(fakeAddonActor); + equal(addonDebugBrowser.isRemoteBrowser, extension.extension.remote, + "The addon debugging browser has the expected remote type"); + equal(ExtensionParent.DebugUtils.debugBrowserPromises.size, 1, + "Got the expected number of requested debug browsers"); + + const {chromeDocument} = ExtensionParent.DebugUtils.hiddenXULWindow; + + ok(addonDebugBrowser.parentElement === chromeDocument.documentElement, + "The addon debugging browser is part of the hiddenXULWindow chromeDocument"); + + await extension.unload(); + + // Install an extension with the same id to recreate for the DebugUtils + // conditions similar to an addon reloaded while the Addon Debugger is opened. + extension = ExtensionTestUtils.loadExtension({ + manifest: { + applications: { + gecko: { + id: "test-reloaded@test.mozilla.com", + }, + }, + }, + background() { + browser.test.sendMessage("background.ready"); + }, + }); + + await extension.startup(); + await extension.awaitMessage("background.ready"); + + equal(ExtensionParent.DebugUtils.debugBrowserPromises.size, 1, + "Got the expected number of requested debug browsers"); + + const newAddonDebugBrowser = await ExtensionParent.DebugUtils + .getExtensionProcessBrowser(fakeAddonActor); + + equal(addonDebugBrowser, newAddonDebugBrowser, + "The existent debugging browser has been reused"); + + equal(newAddonDebugBrowser.isRemoteBrowser, extension.extension.remote, + "The addon debugging browser has the expected remote type"); + + await ExtensionParent.DebugUtils.releaseExtensionProcessBrowser(fakeAddonActor); + + equal(ExtensionParent.DebugUtils.debugBrowserPromises.size, 0, + "All the addon debugging browsers has been released"); + + await extension.unload(); +}); + +add_task(async function testExtensionDebuggingUtilsWithMultipleAddons() { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + applications: { + gecko: { + id: "test-addon-1@test.mozilla.com", + }, + }, + }, + background() { + browser.test.sendMessage("background.ready"); + }, + }); + let anotherExtension = ExtensionTestUtils.loadExtension({ + manifest: { + applications: { + gecko: { + id: "test-addon-2@test.mozilla.com", + }, + }, + }, + background() { + browser.test.sendMessage("background.ready"); + }, + }); + + await extension.startup(); + await extension.awaitMessage("background.ready"); + + await anotherExtension.startup(); + await anotherExtension.awaitMessage("background.ready"); + + const fakeAddonActor = { + addonId: extension.id, + }; + + const anotherFakeAddonActor = { + addonId: anotherExtension.id, + }; + + const {DebugUtils} = ExtensionParent; + const debugBrowser = await DebugUtils.getExtensionProcessBrowser(fakeAddonActor); + const anotherDebugBrowser = await DebugUtils.getExtensionProcessBrowser(anotherFakeAddonActor); + + const chromeDocument = DebugUtils.hiddenXULWindow.chromeDocument; + + equal(ExtensionParent.DebugUtils.debugBrowserPromises.size, 2, + "Got the expected number of debug browsers requested"); + ok(debugBrowser.parentElement === chromeDocument.documentElement, + "The first debug browser is part of the hiddenXUL chromeDocument"); + ok(anotherDebugBrowser.parentElement === chromeDocument.documentElement, + "The second debug browser is part of the hiddenXUL chromeDocument"); + + await ExtensionParent.DebugUtils.releaseExtensionProcessBrowser(fakeAddonActor); + + equal(ExtensionParent.DebugUtils.debugBrowserPromises.size, 1, + "Got the expected number of debug browsers requested"); + + ok(anotherDebugBrowser.parentElement === chromeDocument.documentElement, + "The second debug browser is still part of the hiddenXUL chromeDocument"); + + ok(debugBrowser.parentElement == null, + "The first debug browser has been removed from the hiddenXUL chromeDocument"); + + await ExtensionParent.DebugUtils.releaseExtensionProcessBrowser(anotherFakeAddonActor); + + ok(anotherDebugBrowser.parentElement == null, + "The second debug browser has been removed from the hiddenXUL chromeDocument"); + equal(ExtensionParent.DebugUtils.debugBrowserPromises.size, 0, + "All the addon debugging browsers has been released"); + + await extension.unload(); + await anotherExtension.unload(); +}); diff --git a/toolkit/components/extensions/test/xpcshell/xpcshell.ini b/toolkit/components/extensions/test/xpcshell/xpcshell.ini index 576beef24de1..ce2965bbb838 100644 --- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini +++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini @@ -28,6 +28,7 @@ skip-if = os == "android" [test_ext_contexts.js] [test_ext_contextual_identities.js] skip-if = os == "android" # Containers are not exposed to android. +[test_ext_debugging_utils.js] [test_ext_downloads.js] [test_ext_downloads_download.js] skip-if = os == "android" From 0747cbb5517a191c42167d1145ecc5dfa9a49df3 Mon Sep 17 00:00:00 2001 From: Luca Greco Date: Tue, 21 Mar 2017 15:55:35 +0100 Subject: [PATCH 06/31] Bug 1302702 - Make WebExtension Addon Debugging oop-compatible. r=ochameau This patch applies all the changes needed to the devtools actors and the toolbox-process-window, to be able to debug a webextension running in an extension child process (as well as a webextension running in the main process). The devtools actor used to debug a webextension is splitted into 3 actors: - the WebExtensionActor is the actor that is created when the "root.listTabs" RDP request is received, it provides the addon metadata (name, icon and addon id) and two RDP methods: - reload: used to reload the addon (e.g. from the "about:debugging#addons" page) - connectAddonDebuggingActor: which provides the actorID of the actor that is connected to the process where the extension is running (used by toolbox-process-window.js to connect the toolbox to the needed devtools actors, e.g. console, inspector etc.) - the WebExtensionParentActor is the actor that connects to the process where the extension is running and ensures that a WebExtensionChildActor instance is created and connected (this actor is only the entrypoint to reach the WebExtensionChildActor, and so it does not provide any RDP request on its own, it only connect itself to its child counterpart and then it returns the RDP "form" of the child actor, and the client is then connected directly to the child actor) - the WebExtensionChildActor is the actor that is running in the same process of the target extension, and it provides the same requestTypes of a tab actor. By splitting the WebExtensionActor from the WebExtensionParentActor, we are able to prevent the RemoteDebuggingServer to connect (and create instances of the WebExtensionChildActor) for every addon listed by a root.listAddons() request. MozReview-Commit-ID: L1vxhA6xQkD --HG-- extra : rebase_source : 7ed7735084d9351ff32ab1ad822e53dd0828dace --- devtools/client/framework/connect/connect.js | 5 +- devtools/client/framework/target.js | 30 +- .../framework/toolbox-process-window.js | 12 +- devtools/server/actors/moz.build | 1 + devtools/server/actors/root.js | 5 +- devtools/server/actors/tab.js | 131 +++--- devtools/server/actors/webbrowser.js | 6 +- devtools/server/actors/webextension-parent.js | 210 +++++++++ devtools/server/actors/webextension.js | 419 ++++++++++-------- devtools/server/child.js | 13 +- devtools/server/main.js | 9 +- devtools/shared/specs/moz.build | 1 + devtools/shared/specs/webextension-parent.js | 24 + 13 files changed, 586 insertions(+), 280 deletions(-) create mode 100644 devtools/server/actors/webextension-parent.js create mode 100644 devtools/shared/specs/webextension-parent.js diff --git a/devtools/client/framework/connect/connect.js b/devtools/client/framework/connect/connect.js index cdc9cd9776ca..14b2aa8e8cc9 100644 --- a/devtools/client/framework/connect/connect.js +++ b/devtools/client/framework/connect/connect.js @@ -164,8 +164,9 @@ var onConnectionReady = Task.async(function* ([aType, aTraits]) { */ function buildAddonLink(addon, parent) { let a = document.createElement("a"); - a.onclick = function () { - openToolbox(addon, true, "jsdebugger", false); + a.onclick = async function () { + const isTabActor = addon.isWebExtension; + openToolbox(addon, true, "webconsole", isTabActor); }; a.textContent = addon.name; diff --git a/devtools/client/framework/target.js b/devtools/client/framework/target.js index 2054348526f4..99f8fc8c3415 100644 --- a/devtools/client/framework/target.js +++ b/devtools/client/framework/target.js @@ -351,15 +351,15 @@ TabTarget.prototype = { }, get isAddon() { - return !!(this._form && this._form.actor && ( - this._form.actor.match(/conn\d+\.addon\d+/) || - this._form.actor.match(/conn\d+\.webExtension\d+/) - )); + return !!(this._form && this._form.actor && + this._form.actor.match(/conn\d+\.addon\d+/)) || this.isWebExtension; }, get isWebExtension() { - return !!(this._form && this._form.actor && - this._form.actor.match(/conn\d+\.webExtension\d+/)); + return !!(this._form && this._form.actor && ( + this._form.actor.match(/conn\d+\.webExtension\d+/) || + this._form.actor.match(/child\d+\/webExtension\d+/) + )); }, get isLocalTab() { @@ -375,7 +375,7 @@ TabTarget.prototype = { * for tools that support the Remote Debugging Protocol even for local * connections. */ - makeRemote: function () { + makeRemote: async function () { if (this._remote) { return this._remote.promise; } @@ -398,6 +398,22 @@ TabTarget.prototype = { this._client = new DebuggerClient(DebuggerServer.connectPipe()); // A local TabTarget will never perform chrome debugging. this._chrome = false; + } else if (this._form.isWebExtension && + this.client.mainRoot.traits.webExtensionAddonConnect) { + // The addonActor form is related to a WebExtensionParentActor instance, + // which isn't a tab actor on its own, it is an actor living in the parent process + // with access to the addon metadata, it can control the addon (e.g. reloading it) + // and listen to the AddonManager events related to the lifecycle of the addon + // (e.g. when the addon is disabled or uninstalled ). + // To retrieve the TabActor instance, we call its "connect" method, + // (which fetches the TabActor form from a WebExtensionChildActor instance). + let {form} = await this._client.request({ + to: this._form.actor, type: "connect", + }); + + this._form = form; + this._url = form.url; + this._title = form.title; } this._setupRemoteListeners(); diff --git a/devtools/client/framework/toolbox-process-window.js b/devtools/client/framework/toolbox-process-window.js index 0b5ba60b8595..8b2192d6ca99 100644 --- a/devtools/client/framework/toolbox-process-window.js +++ b/devtools/client/framework/toolbox-process-window.js @@ -46,17 +46,11 @@ var connect = Task.async(function*() { if (addonID) { let { addons } = yield gClient.listAddons(); let addonActor = addons.filter(addon => addon.id === addonID).pop(); - openToolbox({ - form: addonActor, - chrome: true, - isTabActor: addonActor.isWebExtension ? true : false - }); + let isTabActor = addonActor.isWebExtension; + openToolbox({form: addonActor, chrome: true, isTabActor}); } else { let response = yield gClient.getProcess(); - openToolbox({ - form: response.form, - chrome: true - }); + openToolbox({form: response.form, chrome: true}); } }); diff --git a/devtools/server/actors/moz.build b/devtools/server/actors/moz.build index f25b6451c19f..783720b7c913 100644 --- a/devtools/server/actors/moz.build +++ b/devtools/server/actors/moz.build @@ -62,6 +62,7 @@ DevToolsModules( 'webbrowser.js', 'webconsole.js', 'webextension-inspected-window.js', + 'webextension-parent.js', 'webextension.js', 'webgl.js', 'window.js', diff --git a/devtools/server/actors/root.js b/devtools/server/actors/root.js index c4afa2703a99..bad819d76a59 100644 --- a/devtools/server/actors/root.js +++ b/devtools/server/actors/root.js @@ -190,7 +190,10 @@ RootActor.prototype = { heapSnapshots: true, // Whether or not the timeline actor can emit DOMContentLoaded and Load // markers, currently in use by the network monitor. Fx45+ - documentLoadingMarkers: true + documentLoadingMarkers: true, + // Whether or not the webextension addon actor have to be connected + // to retrieve the extension child process tab actors. + webExtensionAddonConnect: true, }, /** diff --git a/devtools/server/actors/tab.js b/devtools/server/actors/tab.js index bc823cdfb6f1..03d24ecc0910 100644 --- a/devtools/server/actors/tab.js +++ b/devtools/server/actors/tab.js @@ -597,6 +597,12 @@ TabActor.prototype = { this._updateChildDocShells(); }, + _unwatchDocShell(docShell) { + if (this._progressListener) { + this._progressListener.unwatch(docShell); + } + }, + onSwitchToFrame(request) { let windowId = request.windowId; let win; @@ -700,9 +706,43 @@ TabActor.prototype = { }, _onDocShellDestroy(docShell) { + // Stop watching this docshell (the unwatch() method will check if we + // started watching it before). + this._unwatchDocShell(docShell); + let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebProgress); this._notifyDocShellDestroy(webProgress); + + if (webProgress.DOMWindow == this._originalWindow) { + // If the original top level document we connected to is removed, + // we try to switch to any other top level document + let rootDocShells = this.docShells + .filter(d => { + return d != this.docShell && + this._isRootDocShell(d); + }); + if (rootDocShells.length > 0) { + let newRoot = rootDocShells[0]; + this._originalWindow = newRoot.DOMWindow; + this._changeTopLevelDocument(this._originalWindow); + } else { + // If for some reason (typically during Firefox shutdown), the original + // document is destroyed, and there is no other top level docshell, + // we detach the tab actor to unregister all listeners and prevent any + // exception + this.exit(); + } + return; + } + + // If the currently targeted context is destroyed, + // and we aren't on the top-level document, + // we have to switch to the top-level one. + if (webProgress.DOMWindow == this.window && + this.window != this._originalWindow) { + this._changeTopLevelDocument(this._originalWindow); + } }, _isRootDocShell(docShell) { @@ -715,36 +755,34 @@ TabActor.prototype = { return !docShell.parent; }, + _docShellToWindow(docShell) { + let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebProgress); + let window = webProgress.DOMWindow; + let id = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .outerWindowID; + let parentID = undefined; + // Ignore the parent of the original document on non-e10s firefox, + // as we get the xul window as parent and don't care about it. + if (window.parent && window != this._originalWindow) { + parentID = window.parent + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .outerWindowID; + } + + return { + id, + parentID, + url: window.location.href, + title: window.document.title, + }; + }, + // Convert docShell list to windows objects list being sent to the client _docShellsToWindows(docshells) { - return docshells.map(docShell => { - let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebProgress); - let window = webProgress.DOMWindow; - let id = window.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils) - .outerWindowID; - let parentID = undefined; - // Ignore the parent of the original document on non-e10s firefox, - // as we get the xul window as parent and don't care about it. - if (window.parent && window != this._originalWindow) { - parentID = window.parent - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils) - .outerWindowID; - } - - // Collect the addonID from the document origin attributes. - let addonID = window.document.nodePrincipal.addonId; - - return { - id, - parentID, - addonID, - url: window.location.href, - title: window.document.title, - }; - }); + return docshells.map(docShell => this._docShellToWindow(docShell)); }, _notifyDocShellsUpdate(docshells) { @@ -780,41 +818,6 @@ TabActor.prototype = { destroy: true }] }); - - // Stop watching this docshell (the unwatch() method will check if we - // started watching it before). - webProgress.QueryInterface(Ci.nsIDocShell); - this._progressListener.unwatch(webProgress); - - if (webProgress.DOMWindow == this._originalWindow) { - // If the original top level document we connected to is removed, - // we try to switch to any other top level document - let rootDocShells = this.docShells - .filter(d => { - return d != this.docShell && - this._isRootDocShell(d); - }); - if (rootDocShells.length > 0) { - let newRoot = rootDocShells[0]; - this._originalWindow = newRoot.DOMWindow; - this._changeTopLevelDocument(this._originalWindow); - } else { - // If for some reason (typically during Firefox shutdown), the original - // document is destroyed, and there is no other top level docshell, - // we detach the tab actor to unregister all listeners and prevent any - // exception - this.exit(); - } - return; - } - - // If the currently targeted context is destroyed, - // and we aren't on the top-level document, - // we have to switch to the top-level one. - if (webProgress.DOMWindow == this.window && - this.window != this._originalWindow) { - this._changeTopLevelDocument(this._originalWindow); - } }, _notifyDocShellDestroyAll() { @@ -866,7 +869,7 @@ TabActor.prototype = { // Check for docShell availability, as it can be already gone // during Firefox shutdown. if (this.docShell) { - this._progressListener.unwatch(this.docShell); + this._unwatchDocShell(this.docShell); this._restoreDocumentSettings(); } if (this._progressListener) { diff --git a/devtools/server/actors/webbrowser.js b/devtools/server/actors/webbrowser.js index 6ffbd2e84660..4283f89ae0f2 100644 --- a/devtools/server/actors/webbrowser.js +++ b/devtools/server/actors/webbrowser.js @@ -14,7 +14,7 @@ var DevToolsUtils = require("devtools/shared/DevToolsUtils"); loader.lazyRequireGetter(this, "RootActor", "devtools/server/actors/root", true); loader.lazyRequireGetter(this, "BrowserAddonActor", "devtools/server/actors/addon", true); -loader.lazyRequireGetter(this, "WebExtensionActor", "devtools/server/actors/webextension", true); +loader.lazyRequireGetter(this, "WebExtensionParentActor", "devtools/server/actors/webextension-parent", true); loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker-list", true); loader.lazyRequireGetter(this, "ServiceWorkerRegistrationActorList", "devtools/server/actors/worker-list", true); loader.lazyRequireGetter(this, "ProcessActorList", "devtools/server/actors/process", true); @@ -835,7 +835,7 @@ BrowserAddonList.prototype.getList = function () { let actor = this._actorByAddonId.get(addon.id); if (!actor) { if (addon.isWebExtension) { - actor = new WebExtensionActor(this._connection, addon); + actor = new WebExtensionParentActor(this._connection, addon); } else { actor = new BrowserAddonActor(this._connection, addon); } @@ -843,8 +843,10 @@ BrowserAddonList.prototype.getList = function () { this._actorByAddonId.set(addon.id, actor); } } + deferred.resolve([...this._actorByAddonId].map(([_, actor]) => actor)); }); + return deferred.promise; }; diff --git a/devtools/server/actors/webextension-parent.js b/devtools/server/actors/webextension-parent.js new file mode 100644 index 000000000000..b6c0eec3659a --- /dev/null +++ b/devtools/server/actors/webextension-parent.js @@ -0,0 +1,210 @@ +/* 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 {DebuggerServer} = require("devtools/server/main"); +const protocol = require("devtools/shared/protocol"); +const {webExtensionSpec} = require("devtools/shared/specs/webextension-parent"); + +loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); +loader.lazyImporter(this, "ExtensionParent", "resource://gre/modules/ExtensionParent.jsm"); + +/** + * Creates the actor that represents the addon in the parent process, which connects + * itself to a WebExtensionChildActor counterpart which is created in the + * extension process (or in the main process if the WebExtensions OOP mode is disabled). + * + * The WebExtensionParentActor subscribes itself as an AddonListener on the AddonManager + * and forwards this events to child actor (e.g. on addon reload or when the addon is + * uninstalled completely) and connects to the child extension process using a `browser` + * element provided by the extension internals (it is not related to any single extension, + * but it will be created automatically to the currently selected "WebExtensions OOP mode" + * and it persist across the extension reloads (it is destroyed once the actor exits). + * WebExtensionActor is a child of RootActor, it can be retrieved via + * RootActor.listAddons request. + * + * @param {DebuggerServerConnection} conn + * The connection to the client. + * @param {AddonWrapper} addon + * The target addon. + */ +const WebExtensionParentActor = protocol.ActorClassWithSpec(webExtensionSpec, { + initialize(conn, addon) { + this.conn = conn; + this.addon = addon; + this.id = addon.id; + this._childFormPromise = null; + + AddonManager.addAddonListener(this); + }, + + destroy() { + AddonManager.removeAddonListener(this); + + this.addon = null; + this._childFormPromise = null; + + if (this._destroyProxyChildActor) { + this._destroyProxyChildActor(); + delete this._destroyProxyChildActor; + } + }, + + setOptions() { + // NOTE: not used anymore for webextensions, still used in the legacy addons, + // addon manager is currently going to call it automatically on every addon. + }, + + reload() { + return this.addon.reload().then(() => { + return {}; + }); + }, + + form() { + return { + actor: this.actorID, + id: this.id, + name: this.addon.name, + iconURL: this.addon.iconURL, + debuggable: this.addon.isDebuggable, + temporarilyInstalled: this.addon.temporarilyInstalled, + isWebExtension: true, + }; + }, + + connect() { + if (this._childFormPormise) { + return this._childFormPromise; + } + + let proxy = new ProxyChildActor(this.conn, this); + this._childFormPromise = proxy.connect().then(form => { + // Merge into the child actor form, some addon metadata + // (e.g. the addon name shown in the addon debugger window title). + return Object.assign(form, { + id: this.addon.id, + name: this.addon.name, + iconURL: this.addon.iconURL, + // Set the isOOP attribute on the connected child actor form. + isOOP: proxy.isOOP, + }); + }); + this._destroyProxyChildActor = () => proxy.destroy(); + + return this._childFormPromise; + }, + + // ProxyChildActor callbacks. + + onProxyChildActorDestroy() { + // Invalidate the cached child actor and form Promise + // if the child actor exits. + this._childFormPromise = null; + delete this._destroyProxyChildActor; + }, + + // AddonManagerListener callbacks. + + onInstalled(addon) { + if (addon.id != this.id) { + return; + } + + // Update the AddonManager's addon object on reload/update. + this.addon = addon; + }, + + onUninstalled(addon) { + if (addon != this.addon) { + return; + } + + this.destroy(); + }, +}); + +exports.WebExtensionParentActor = WebExtensionParentActor; + +function ProxyChildActor(connection, parentActor) { + this._conn = connection; + this._parentActor = parentActor; + this.addonId = parentActor.id; + + this._onChildExit = this._onChildExit.bind(this); + + this._form = null; + this._browser = null; + this._childActorID = null; +} + +ProxyChildActor.prototype = { + /** + * Connect the webextension child actor. + */ + async connect() { + if (this._browser) { + throw new Error("This actor is already connected to the extension process"); + } + + // Called when the debug browser element has been destroyed + // (no actor is using it anymore to connect the child extension process). + const onDestroy = this.destroy.bind(this); + + this._browser = await ExtensionParent.DebugUtils.getExtensionProcessBrowser(this); + + this._form = await DebuggerServer.connectToChild(this._conn, this._browser, onDestroy, + {addonId: this.addonId}); + + this._childActorID = this._form.actor; + + // Exit the proxy child actor if the child actor has been destroyed. + this._mm.addMessageListener("debug:webext_child_exit", this._onChildExit); + + return this._form; + }, + + get isOOP() { + return this._browser ? this._browser.isRemoteBrowser : undefined; + }, + + get _mm() { + return this._browser && ( + this._browser.messageManager || + this._browser.frameLoader.messageManager); + }, + + destroy() { + if (this._mm) { + this._mm.removeMessageListener("debug:webext_child_exit", this._onChildExit); + + this._mm.sendAsyncMessage("debug:webext_parent_exit", { + actor: this._childActorID, + }); + + ExtensionParent.DebugUtils.releaseExtensionProcessBrowser(this); + } + + if (this._parentActor) { + this._parentActor.onProxyChildActorDestroy(); + } + + this._parentActor = null; + this._browser = null; + this._childActorID = null; + this._form = null; + }, + + /** + * Handle the child actor exit. + */ + _onChildExit(msg) { + if (msg.json.actor !== this._childActorID) { + return; + } + + this.destroy(); + }, +}; diff --git a/devtools/server/actors/webextension.js b/devtools/server/actors/webextension.js index 712cafd0bee6..2dcb03a1d4c2 100644 --- a/devtools/server/actors/webextension.js +++ b/devtools/server/actors/webextension.js @@ -4,63 +4,77 @@ "use strict"; -const { Ci, Cu } = require("chrome"); +const { Ci, Cu, Cc } = require("chrome"); const Services = require("Services"); + const { ChromeActor } = require("./chrome"); const makeDebugger = require("./utils/make-debugger"); -var DevToolsUtils = require("devtools/shared/DevToolsUtils"); -var { assert } = DevToolsUtils; - loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id"); loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true); -loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); -loader.lazyImporter(this, "XPIProvider", "resource://gre/modules/addons/XPIProvider.jsm"); - const FALLBACK_DOC_MESSAGE = "Your addon does not have any document opened yet."; /** * Creates a TabActor for debugging all the contexts associated to a target WebExtensions - * add-on. + * add-on running in a child extension process. * Most of the implementation is inherited from ChromeActor (which inherits most of its * implementation from TabActor). - * WebExtensionActor is a child of RootActor, it can be retrieved via - * RootActor.listAddons request. - * WebExtensionActor exposes all tab actors via its form() request, like TabActor. + * WebExtensionChildActor is created by a WebExtensionParentActor counterpart, when its + * parent actor's `connect` method has been called (on the listAddons RDP package), + * it runs in the same process that the extension is running into (which can be the main + * process if the extension is running in non-oop mode, or the child extension process + * if the extension is running in oop-mode). + * + * A WebExtensionChildActor contains all tab actors, like a regular ChromeActor + * or TabActor. * * History lecture: - * The add-on actors used to not inherit TabActor because of the different way the + * - The add-on actors used to not inherit TabActor because of the different way the * add-on APIs where exposed to the add-on itself, and for this reason the Addon Debugger * has only a sub-set of the feature available in the Tab or in the Browser Toolbox. - * In a WebExtensions add-on all the provided contexts (background and popup pages etc.), + * - In a WebExtensions add-on all the provided contexts (background, popups etc.), * besides the Content Scripts which run in the content process, hooked to an existent * tab, by creating a new WebExtensionActor which inherits from ChromeActor, we can * provide a full features Addon Toolbox (which is basically like a BrowserToolbox which * filters the visible sources and frames to the one that are related to the target * add-on). + * - When the WebExtensions OOP mode has been introduced, this actor has been refactored + * and moved from the main process to the new child extension process. * - * @param conn DebuggerServerConnection + * @param {DebuggerServerConnection} conn * The connection to the client. - * @param addon AddonWrapper - * The target addon. + * @param {nsIMessageSender} chromeGlobal. + * The chromeGlobal where this actor has been injected by the + * DebuggerServer.connectToChild method. + * @param {string} prefix + * the custom RDP prefix to use. + * @param {string} addonId + * the addonId of the target WebExtension. */ -function WebExtensionActor(conn, addon) { +function WebExtensionChildActor(conn, chromeGlobal, prefix, addonId) { ChromeActor.call(this, conn); - this.id = addon.id; - this.addon = addon; + this._chromeGlobal = chromeGlobal; + this._prefix = prefix; + this.id = addonId; // Bind the _allowSource helper to this, it is used in the // TabActor to lazily create the TabSources instance. this._allowSource = this._allowSource.bind(this); + this._onParentExit = this._onParentExit.bind(this); + + this._chromeGlobal.addMessageListener("debug:webext_parent_exit", this._onParentExit); // Set the consoleAPIListener filtering options // (retrieved and used in the related webconsole child actor). this.consoleAPIListenerOptions = { - addonId: addon.id, + addonId: this.id, }; + this.aps = Cc["@mozilla.org/addons/policy-service;1"] + .getService(Ci.nsIAddonPolicyService); + // This creates a Debugger instance for debugging all the add-on globals. this.makeDebugger = makeDebugger.bind(null, { findDebuggees: dbg => { @@ -69,135 +83,50 @@ function WebExtensionActor(conn, addon) { shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee.bind(this), }); - // Discover the preferred debug global for the target addon - this.preferredTargetWindow = null; - this._findAddonPreferredTargetWindow(); + // Try to discovery an existent extension page to attach (which will provide the initial + // URL shown in the window tittle when the addon debugger is opened). + let extensionWindow = this._searchForExtensionWindow(); - AddonManager.addAddonListener(this); + if (extensionWindow) { + this._setWindow(extensionWindow); + } } -exports.WebExtensionActor = WebExtensionActor; +exports.WebExtensionChildActor = WebExtensionChildActor; -WebExtensionActor.prototype = Object.create(ChromeActor.prototype); +WebExtensionChildActor.prototype = Object.create(ChromeActor.prototype); -WebExtensionActor.prototype.actorPrefix = "webExtension"; -WebExtensionActor.prototype.constructor = WebExtensionActor; +WebExtensionChildActor.prototype.actorPrefix = "webExtension"; +WebExtensionChildActor.prototype.constructor = WebExtensionChildActor; // NOTE: This is needed to catch in the webextension webconsole all the // errors raised by the WebExtension internals that are not currently // associated with any window. -WebExtensionActor.prototype.isRootActor = true; - -WebExtensionActor.prototype.form = function () { - assert(this.actorID, "addon should have an actorID."); - - let baseForm = ChromeActor.prototype.form.call(this); - - return Object.assign(baseForm, { - actor: this.actorID, - id: this.id, - name: this.addon.name, - url: this.addon.sourceURI ? this.addon.sourceURI.spec : undefined, - iconURL: this.addon.iconURL, - debuggable: this.addon.isDebuggable, - temporarilyInstalled: this.addon.temporarilyInstalled, - isWebExtension: this.addon.isWebExtension, - }); -}; - -WebExtensionActor.prototype._attach = function () { - // NOTE: we need to be sure that `this.window` can return a - // window before calling the ChromeActor.onAttach, or the TabActor - // will not be subscribed to the child doc shell updates. - - // If a preferredTargetWindow exists, set it as the target for this actor - // when the client request to attach this actor. - if (this.preferredTargetWindow) { - this._setWindow(this.preferredTargetWindow); - } else { - this._createFallbackWindow(); - } - - // Call ChromeActor's _attach to listen for any new/destroyed chrome docshell - ChromeActor.prototype._attach.apply(this); -}; - -WebExtensionActor.prototype._detach = function () { - this._destroyFallbackWindow(); - - // Call ChromeActor's _detach to unsubscribe new/destroyed chrome docshell listeners. - ChromeActor.prototype._detach.apply(this); -}; +WebExtensionChildActor.prototype.isRootActor = true; /** * Called when the actor is removed from the connection. */ -WebExtensionActor.prototype.exit = function () { - AddonManager.removeAddonListener(this); +WebExtensionChildActor.prototype.exit = function () { + if (this._chromeGlobal) { + let chromeGlobal = this._chromeGlobal; + this._chromeGlobal = null; + + chromeGlobal.removeMessageListener("debug:webext_parent_exit", this._onParentExit); + + chromeGlobal.sendAsyncMessage("debug:webext_child_exit", { + actor: this.actorID + }); + } - this.preferredTargetWindow = null; this.addon = null; this.id = null; return ChromeActor.prototype.exit.apply(this); }; -// Addon Specific Remote Debugging requestTypes and methods. +// Private helpers. -/** - * Reloads the addon. - */ -WebExtensionActor.prototype.onReload = function () { - return this.addon.reload() - .then(() => { - // send an empty response - return {}; - }); -}; - -/** - * Set the preferred global for the add-on (called from the AddonManager). - */ -WebExtensionActor.prototype.setOptions = function (addonOptions) { - if ("global" in addonOptions) { - // Set the proposed debug global as the preferred target window - // (the actor will eventually set it as the target once it is attached) - this.preferredTargetWindow = addonOptions.global; - } -}; - -// AddonManagerListener callbacks. - -WebExtensionActor.prototype.onInstalled = function (addon) { - if (addon.id != this.id) { - return; - } - - // Update the AddonManager's addon object on reload/update. - this.addon = addon; -}; - -WebExtensionActor.prototype.onUninstalled = function (addon) { - if (addon != this.addon) { - return; - } - - this.exit(); -}; - -WebExtensionActor.prototype.onPropertyChanged = function (addon, changedPropNames) { - if (addon != this.addon) { - return; - } - - // Refresh the preferred debug global on disabled/reloaded/upgraded addon. - if (changedPropNames.includes("debugGlobal")) { - this._findAddonPreferredTargetWindow(); - } -}; - -// Private helpers - -WebExtensionActor.prototype._createFallbackWindow = function () { +WebExtensionChildActor.prototype._createFallbackWindow = function () { if (this.fallbackWindow) { // Skip if there is already an existent fallback window. return; @@ -207,26 +136,16 @@ WebExtensionActor.prototype._createFallbackWindow = function () { // not defined for the target add-on or not yet when the actor instance has been // created). this.fallbackWebNav = Services.appShell.createWindowlessBrowser(true); - this.fallbackWebNav.loadURI( - `data:text/html;charset=utf-8,${FALLBACK_DOC_MESSAGE}`, - 0, null, null, null - ); - this.fallbackDocShell = this.fallbackWebNav - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDocShell); + // Save the reference to the fallback DOMWindow. + this.fallbackWindow = this.fallbackWebNav.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); - Object.defineProperty(this, "docShell", { - value: this.fallbackDocShell, - configurable: true - }); - - // Save the reference to the fallback DOMWindow - this.fallbackWindow = this.fallbackDocShell.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); + // Insert the fallback doc message. + this.fallbackWindow.document.body.innerText = FALLBACK_DOC_MESSAGE; }; -WebExtensionActor.prototype._destroyFallbackWindow = function () { +WebExtensionChildActor.prototype._destroyFallbackWindow = function () { if (this.fallbackWebNav) { // Explicitly close the fallback windowless browser to prevent it to leak // (and to prevent it to freeze devtools xpcshell tests). @@ -238,65 +157,173 @@ WebExtensionActor.prototype._destroyFallbackWindow = function () { } }; -/** - * Discover the preferred debug global and switch to it if the addon has been attached. - */ -WebExtensionActor.prototype._findAddonPreferredTargetWindow = function () { - return new Promise(resolve => { - let activeAddon = XPIProvider.activeAddons.get(this.id); +// Discovery an extension page to use as a default target window. +// NOTE: This currently fail to discovery an extension page running in a +// windowless browser when running in non-oop mode, and the background page +// is set later using _onNewExtensionWindow. +WebExtensionChildActor.prototype._searchForExtensionWindow = function () { + let e = Services.ww.getWindowEnumerator(null); + while (e.hasMoreElements()) { + let window = e.getNext(); - if (!activeAddon) { - // The addon is not active, the background page is going to be destroyed, - // navigate to the fallback window (if it already exists). - resolve(null); - } else { - AddonManager.getAddonByInstanceID(activeAddon.instanceID) - .then(privateWrapper => { - let targetWindow = privateWrapper.getDebugGlobal(); - - // Do not use the preferred global if it is not a DOMWindow as expected. - if (!(targetWindow instanceof Ci.nsIDOMWindow)) { - targetWindow = null; - } - - resolve(targetWindow); - }); + if (window.document.nodePrincipal.addonId == this.id) { + return window; } - }).then(preferredTargetWindow => { - this.preferredTargetWindow = preferredTargetWindow; + } - if (!preferredTargetWindow) { - // Create a fallback window if no preferred target window has been found. + return undefined; +}; + +// Customized ChromeActor/TabActor hooks. + +WebExtensionChildActor.prototype._onDocShellDestroy = function (docShell) { + // Stop watching this docshell (the unwatch() method will check if we + // started watching it before). + this._unwatchDocShell(docShell); + + // Let the _onDocShellDestroy notify that the docShell has been destroyed. + let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebProgress); + this._notifyDocShellDestroy(webProgress); + + // If the destroyed docShell was the current docShell and the actor is + // currently attached, switch to the fallback window + if (this.attached && docShell == this.docShell) { + // Creates a fallback window if it doesn't exist yet. + this._createFallbackWindow(); + this._changeTopLevelDocument(this.fallbackWindow); + } +}; + +WebExtensionChildActor.prototype._onNewExtensionWindow = function (window) { + if (!this.window || this.window === this.fallbackWindow) { + this._changeTopLevelDocument(window); + } +}; + +WebExtensionChildActor.prototype._attach = function () { + // NOTE: we need to be sure that `this.window` can return a + // window before calling the ChromeActor.onAttach, or the TabActor + // will not be subscribed to the child doc shell updates. + + if (!this.window || this.window.document.nodePrincipal.addonId !== this.id) { + // Discovery an existent extension page to attach. + let extensionWindow = this._searchForExtensionWindow(); + + if (!extensionWindow) { this._createFallbackWindow(); - } else if (this.attached) { - // Change the top level document if the actor is already attached. - this._changeTopLevelDocument(preferredTargetWindow); + this._setWindow(this.fallbackWindow); + } else { + this._setWindow(extensionWindow); } + } + + // Call ChromeActor's _attach to listen for any new/destroyed chrome docshell + ChromeActor.prototype._attach.apply(this); +}; + +WebExtensionChildActor.prototype._detach = function () { + // Call ChromeActor's _detach to unsubscribe new/destroyed chrome docshell listeners. + ChromeActor.prototype._detach.apply(this); + + // Stop watching for new extension windows. + this._destroyFallbackWindow(); +}; + +/** + * Return the json details related to a docShell. + */ +WebExtensionChildActor.prototype._docShellToWindow = function (docShell) { + const baseWindowDetails = ChromeActor.prototype._docShellToWindow.call(this, docShell); + + let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebProgress); + let window = webProgress.DOMWindow; + + // Collect the addonID from the document origin attributes and its sameType top level + // frame. + let addonID = window.document.nodePrincipal.addonId; + let sameTypeRootAddonID = docShell.QueryInterface(Ci.nsIDocShellTreeItem) + .sameTypeRootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow) + .document.nodePrincipal.addonId; + + return Object.assign(baseWindowDetails, { + addonID, + sameTypeRootAddonID, }); }; /** * Return an array of the json details related to an array/iterator of docShells. */ -WebExtensionActor.prototype._docShellsToWindows = function (docshells) { +WebExtensionChildActor.prototype._docShellsToWindows = function (docshells) { return ChromeActor.prototype._docShellsToWindows.call(this, docshells) .filter(windowDetails => { - // filter the docShells based on the addon id - return windowDetails.addonID == this.id; + // Filter the docShells based on the addon id of the window or + // its sameType top level frame. + return windowDetails.addonID === this.id || + windowDetails.sameTypeRootAddonID === this.id; }); }; +WebExtensionChildActor.prototype.isExtensionWindow = function (window) { + return window.document.nodePrincipal.addonId == this.id; +}; + +WebExtensionChildActor.prototype.isExtensionWindowDescendent = function (window) { + // Check if the source is coming from a descendant docShell of an extension window. + let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell); + let rootWin = docShell.sameTypeRootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + return this.isExtensionWindow(rootWin); +}; + /** * Return true if the given source is associated with this addon and should be * added to the visible sources (retrieved and used by the webbrowser actor module). */ -WebExtensionActor.prototype._allowSource = function (source) { +WebExtensionChildActor.prototype._allowSource = function (source) { + // Use the source.element to detect the allowed source, if any. + if (source.element) { + let domEl = unwrapDebuggerObjectGlobal(source.element); + return (this.isExtensionWindow(domEl.ownerGlobal) || + this.isExtensionWindowDescendent(domEl.ownerGlobal)); + } + + // Fallback to check the uri if there is no source.element associated to the source. + + // Retrieve the first component of source.url in the form "url1 -> url2 -> ...". + let url = source.url.split(" -> ").pop(); + + // Filter out the code introduced by evaluating code in the webconsole. + if (url === "debugger eval code") { + return false; + } + + let uri; + + // Try to decode the url. try { - let uri = Services.io.newURI(source.url); - let addonID = mapURIToAddonID(uri); + uri = Services.io.newURI(url); + } catch (err) { + Cu.reportError(`Unexpected invalid url: ${url}`); + return false; + } + + // Filter out resource and chrome sources (which are related to the loaded internals). + if (["resource", "chrome", "file"].includes(uri.scheme)) { + return false; + } + + try { + let addonID = this.aps.extensionURIToAddonId(uri); return addonID == this.id; - } catch (e) { + } catch (err) { + // extensionURIToAddonId raises an exception on non-extension URLs. return false; } }; @@ -305,11 +332,22 @@ WebExtensionActor.prototype._allowSource = function (source) { * Return true if the given global is associated with this addon and should be * added as a debuggee, false otherwise. */ -WebExtensionActor.prototype._shouldAddNewGlobalAsDebuggee = function (newGlobal) { +WebExtensionChildActor.prototype._shouldAddNewGlobalAsDebuggee = function (newGlobal) { const global = unwrapDebuggerObjectGlobal(newGlobal); if (global instanceof Ci.nsIDOMWindow) { - return global.document.nodePrincipal.addonId == this.id; + // Filter out any global which contains a XUL document. + if (global.document instanceof Ci.nsIDOMXULDocument) { + return false; + } + + // Change top level document as a simulated frame switching. + if (global.document.ownerGlobal && this.isExtensionWindow(global)) { + this._onNewExtensionWindow(global.document.ownerGlobal); + } + + return global.document.ownerGlobal && + this.isExtensionWindowDescendent(global.document.ownerGlobal); } try { @@ -325,9 +363,12 @@ WebExtensionActor.prototype._shouldAddNewGlobalAsDebuggee = function (newGlobal) return false; }; -/** - * Override WebExtensionActor requestTypes: - * - redefined `reload`, which should reload the target addon - * (instead of the entire browser as the regular ChromeActor does). - */ -WebExtensionActor.prototype.requestTypes.reload = WebExtensionActor.prototype.onReload; +// Handlers for the messages received from the parent actor. + +WebExtensionChildActor.prototype._onParentExit = function (msg) { + if (msg.json.actor !== this.actorID) { + return; + } + + this.exit(); +}; diff --git a/devtools/server/child.js b/devtools/server/child.js index 77a9c8bd8598..6f2eca33e885 100644 --- a/devtools/server/child.js +++ b/devtools/server/child.js @@ -17,7 +17,6 @@ try { const DevToolsUtils = require("devtools/shared/DevToolsUtils"); const { dumpn } = DevToolsUtils; const { DebuggerServer, ActorPool } = require("devtools/server/main"); - const { ContentActor } = require("devtools/server/actors/childtab"); if (!DebuggerServer.initialized) { DebuggerServer.init(); @@ -34,12 +33,22 @@ try { let mm = msg.target; let prefix = msg.data.prefix; + let addonId = msg.data.addonId; let conn = DebuggerServer.connectToParent(prefix, mm); conn.parentMessageManager = mm; connections.set(prefix, conn); - let actor = new ContentActor(conn, chromeGlobal, prefix); + let actor; + + if (addonId) { + const { WebExtensionChildActor } = require("devtools/server/actors/webextension"); + actor = new WebExtensionChildActor(conn, chromeGlobal, prefix, addonId); + } else { + const { ContentActor } = require("devtools/server/actors/childtab"); + actor = new ContentActor(conn, chromeGlobal, prefix); + } + let actorPool = new ActorPool(conn); actorPool.addActor(actor); conn.addActorPool(actorPool); diff --git a/devtools/server/main.js b/devtools/server/main.js index f616087168a1..43c559af4001 100644 --- a/devtools/server/main.js +++ b/devtools/server/main.js @@ -1009,7 +1009,7 @@ var DebuggerServer = { * A promise object that is resolved once the connection is * established. */ - connectToChild(connection, frame, onDestroy) { + connectToChild(connection, frame, onDestroy, {addonId} = {}) { let deferred = SyncPromise.defer(); // Get messageManager from XUL browser (which might be a specialized tunnel for RDM) @@ -1122,6 +1122,9 @@ var DebuggerServer = { }; let destroy = DevToolsUtils.makeInfallible(function () { + events.off(connection, "closed", destroy); + Services.obs.removeObserver(onMessageManagerClose, "message-manager-close"); + // provides hook to actor modules that need to exchange messages // between e10s parent and child processes parentModules.forEach(mod => { @@ -1168,8 +1171,6 @@ var DebuggerServer = { // Cleanup all listeners untrackMessageManager(); - Services.obs.removeObserver(onMessageManagerClose, "message-manager-close"); - events.off(connection, "closed", destroy); }); // Listen for various messages and frame events @@ -1188,7 +1189,7 @@ var DebuggerServer = { // when user unplug the device or we lose the connection somehow. events.on(connection, "closed", destroy); - mm.sendAsyncMessage("debug:connect", { prefix }); + mm.sendAsyncMessage("debug:connect", { prefix, addonId }); return deferred.promise; }, diff --git a/devtools/shared/specs/moz.build b/devtools/shared/specs/moz.build index 129af3fb1439..4262f4123953 100644 --- a/devtools/shared/specs/moz.build +++ b/devtools/shared/specs/moz.build @@ -43,6 +43,7 @@ DevToolsModules( 'timeline.js', 'webaudio.js', 'webextension-inspected-window.js', + 'webextension-parent.js', 'webgl.js', 'worker.js' ) diff --git a/devtools/shared/specs/webextension-parent.js b/devtools/shared/specs/webextension-parent.js new file mode 100644 index 000000000000..f5ea2ca0b094 --- /dev/null +++ b/devtools/shared/specs/webextension-parent.js @@ -0,0 +1,24 @@ +/* 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 {RetVal, generateActorSpec} = require("devtools/shared/protocol"); + +const webExtensionSpec = generateActorSpec({ + typeName: "webExtensionAddon", + + methods: { + reload: { + request: { }, + response: { addon: RetVal("json") }, + }, + + connect: { + request: { }, + response: { form: RetVal("json") }, + }, + }, +}); + +exports.webExtensionSpec = webExtensionSpec; From 48dd5d90d6ecd435e95c2aefd2e6ee73b5d2c8c9 Mon Sep 17 00:00:00 2001 From: Luca Greco Date: Tue, 21 Mar 2017 16:28:15 +0100 Subject: [PATCH 07/31] Bug 1302702 - Remove from ext-backgroundPage any code that uses the AddonManager object. r=kmag The background page do not need to use the AddonManager to set its preferred debugging global anymore (and it would not be able to use it from the extension child process). MozReview-Commit-ID: 2IAxvCjDKvl --HG-- extra : rebase_source : eb10f2c71e74f27a0151459a53f5ce6269392155 --- .../extensions/ext-backgroundPage.js | 18 -- .../extensions/test/mochitest/chrome.ini | 2 - ...st_chrome_ext_background_debug_global.html | 166 ------------------ toolkit/mozapps/extensions/AddonManager.jsm | 2 +- .../extensions/internal/XPIProvider.jsm | 77 +------- 5 files changed, 3 insertions(+), 262 deletions(-) delete mode 100644 toolkit/components/extensions/test/mochitest/test_chrome_ext_background_debug_global.html diff --git a/toolkit/components/extensions/ext-backgroundPage.js b/toolkit/components/extensions/ext-backgroundPage.js index 509e65c0be65..4143c902350d 100644 --- a/toolkit/components/extensions/ext-backgroundPage.js +++ b/toolkit/components/extensions/ext-backgroundPage.js @@ -18,7 +18,6 @@ class BackgroundPage extends HiddenExtensionPage { this.page = options.page || null; this.isGenerated = !!options.scripts; - this.webNav = null; if (this.page) { this.url = this.extension.baseURI.resolve(this.page); @@ -41,18 +40,6 @@ class BackgroundPage extends HiddenExtensionPage { let context = await promiseExtensionViewLoaded(this.browser); - if (this.browser.docShell) { - this.webNav = this.browser.docShell.QueryInterface(Ci.nsIWebNavigation); - let window = this.webNav.document.defaultView; - - // Set the add-on's main debugger global, for use in the debugger - // console. - if (this.extension.addonData.instanceID) { - AddonManager.getAddonByInstanceID(this.extension.addonData.instanceID) - .then(addon => addon && addon.setDebugGlobal(window)); - } - } - if (context) { // Wait until all event listeners registered by the script so far // to be handled. @@ -64,11 +51,6 @@ class BackgroundPage extends HiddenExtensionPage { } shutdown() { - if (this.extension.addonData.instanceID) { - AddonManager.getAddonByInstanceID(this.extension.addonData.instanceID) - .then(addon => addon && addon.setDebugGlobal(null)); - } - super.shutdown(); } } diff --git a/toolkit/components/extensions/test/mochitest/chrome.ini b/toolkit/components/extensions/test/mochitest/chrome.ini index 1b78446ec8e4..abcff7fb4c8d 100644 --- a/toolkit/components/extensions/test/mochitest/chrome.ini +++ b/toolkit/components/extensions/test/mochitest/chrome.ini @@ -13,8 +13,6 @@ support-files = redirect_auto.sjs tags = webextensions in-process-webextensions -[test_chrome_ext_background_debug_global.html] -skip-if = (os == 'android') # android doesn't have devtools [test_chrome_ext_background_page.html] skip-if = (toolkit == 'android') # android doesn't have devtools [test_chrome_ext_contentscript_data_uri.html] diff --git a/toolkit/components/extensions/test/mochitest/test_chrome_ext_background_debug_global.html b/toolkit/components/extensions/test/mochitest/test_chrome_ext_background_debug_global.html deleted file mode 100644 index de5b8f678bac..000000000000 --- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_background_debug_global.html +++ /dev/null @@ -1,166 +0,0 @@ - - - - WebExtension test - - - - - - - - - - - - - diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index 5689b0004a1c..a8a7c41d7b98 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -2231,7 +2231,7 @@ var AddonManagerInternal = { if (!wrapper) { throw Error("No addon matching instanceID:", aInstanceID.toString()); } - let addonId = wrapper.addonId(); + let addonId = wrapper.id; logger.debug(`Registering upgrade listener for ${addonId}`); this.upgradeListeners.set(addonId, aCallback); }); diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 54ec7b597567..04b4da3d4489 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -4567,16 +4567,7 @@ this.XPIProvider = { for (let [id, val] of this.activeAddons) { if (aInstanceID == val.instanceID) { - if (val.safeWrapper) { - return Promise.resolve(val.safeWrapper); - } - - return new Promise(resolve => { - this.getAddonByID(id, function(addon) { - val.safeWrapper = new PrivateWrapper(addon); - resolve(val.safeWrapper); - }); - }); + return new Promise(resolve => this.getAddonByID(id, resolve)); } } @@ -4843,7 +4834,7 @@ this.XPIProvider = { for (let [id, val] of this.activeAddons) { aConnection.setAddonOptions( - id, { global: val.debugGlobal || val.bootstrapScope }); + id, { global: val.bootstrapScope }); } }, @@ -5159,8 +5150,6 @@ this.XPIProvider = { aMultiprocessCompatible, aRunInSafeMode, aDependencies, hasEmbeddedWebExtension) { this.activeAddons.set(aId, { - debugGlobal: null, - safeWrapper: null, bootstrapScope: null, // a Symbol passed to this add-on, which it can use to identify itself instanceID: Symbol(aId), @@ -8122,68 +8111,6 @@ AddonWrapper.prototype = { } }; -/** - * The PrivateWrapper is used to expose certain functionality only when being - * called with the add-on instanceID, disallowing other add-ons to access it. - */ -function PrivateWrapper(aAddon) { - AddonWrapper.call(this, aAddon); -} - -PrivateWrapper.prototype = Object.create(AddonWrapper.prototype); -Object.assign(PrivateWrapper.prototype, { - addonId() { - return this.id; - }, - - /** - * Retrieves the preferred global context to be used from the - * add-on debugging window. - * - * @returns global - * The object set as global context. Must be a window object. - */ - getDebugGlobal(global) { - let activeAddon = XPIProvider.activeAddons.get(this.id); - if (activeAddon) { - return activeAddon.debugGlobal; - } - - return null; - }, - - /** - * Defines a global context to be used in the console - * of the add-on debugging window. - * - * @param global - * The object to set as global context. Must be a window object. - */ - setDebugGlobal(global) { - if (!global) { - // If the new global is null, notify the listeners regardless - // from the current state of the addon. - // NOTE: this happen after the addon has been disabled and - // the global will never be set to null otherwise. - AddonManagerPrivate.callAddonListeners("onPropertyChanged", - addonFor(this), - ["debugGlobal"]); - } else { - let activeAddon = XPIProvider.activeAddons.get(this.id); - if (activeAddon) { - let globalChanged = activeAddon.debugGlobal != global; - activeAddon.debugGlobal = global; - - if (globalChanged) { - AddonManagerPrivate.callAddonListeners("onPropertyChanged", - addonFor(this), - ["debugGlobal"]); - } - } - } - } -}); - function chooseValue(aAddon, aObj, aProp) { let repositoryAddon = aAddon._repositoryAddon; let objValue = aObj[aProp]; From 06558b719f4ce7d502b86444d606465ded960c5d Mon Sep 17 00:00:00 2001 From: Luca Greco Date: Wed, 10 May 2017 12:54:18 +0200 Subject: [PATCH 08/31] Bug 1302702 - Add devtools webextension actor mochitest-chrome unit tests. r=ochameau MozReview-Commit-ID: 9pAbT89SlJJ --HG-- extra : rebase_source : c6a5af85c412f5989bdd7664948aaba5a6f9bf66 --- devtools/server/tests/mochitest/chrome.ini | 4 +- ..._webextension-addon-debugging-connect.html | 102 +++++++++ ...t_webextension-addon-debugging-reload.html | 133 +++++++++++ .../tests/mochitest/webextension-helpers.js | 212 ++++++++++++++++++ 4 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 devtools/server/tests/mochitest/test_webextension-addon-debugging-connect.html create mode 100644 devtools/server/tests/mochitest/test_webextension-addon-debugging-reload.html create mode 100644 devtools/server/tests/mochitest/webextension-helpers.js diff --git a/devtools/server/tests/mochitest/chrome.ini b/devtools/server/tests/mochitest/chrome.ini index 4c1ca2a8b93c..3529e4a39073 100644 --- a/devtools/server/tests/mochitest/chrome.ini +++ b/devtools/server/tests/mochitest/chrome.ini @@ -24,7 +24,7 @@ support-files = setup-in-child.js setup-in-parent.js webconsole-helpers.js - + webextension-helpers.js [test_animation_actor-lifetime.html] [test_connection-manager.html] [test_connectToChild.html] @@ -99,5 +99,7 @@ support-files = [test_styles-svg.html] [test_unsafeDereference.html] [test_webconsole-node-grip.html] +[test_webextension-addon-debugging-connect.html] +[test_webextension-addon-debugging-reload.html] [test_websocket-server.html] skip-if = false diff --git a/devtools/server/tests/mochitest/test_webextension-addon-debugging-connect.html b/devtools/server/tests/mochitest/test_webextension-addon-debugging-connect.html new file mode 100644 index 000000000000..bef1d0232eaf --- /dev/null +++ b/devtools/server/tests/mochitest/test_webextension-addon-debugging-connect.html @@ -0,0 +1,102 @@ + + + + + + Mozilla Bug + + + + + + + +
+
+
+ + diff --git a/devtools/server/tests/mochitest/test_webextension-addon-debugging-reload.html b/devtools/server/tests/mochitest/test_webextension-addon-debugging-reload.html new file mode 100644 index 000000000000..5c66729a8cbc --- /dev/null +++ b/devtools/server/tests/mochitest/test_webextension-addon-debugging-reload.html @@ -0,0 +1,133 @@ + + + + + + Mozilla Bug + + + + + + +
+
+
+ + diff --git a/devtools/server/tests/mochitest/webextension-helpers.js b/devtools/server/tests/mochitest/webextension-helpers.js new file mode 100644 index 000000000000..cdf62f133966 --- /dev/null +++ b/devtools/server/tests/mochitest/webextension-helpers.js @@ -0,0 +1,212 @@ +/* exported attachAddon, setWebExtensionOOPMode, waitForFramesUpdated, reloadAddon, + collectFrameUpdates, generateWebExtensionXPI, promiseInstallFile, + promiseAddonByID, promiseWebExtensionStartup, promiseWebExtensionShutdown + */ + +"use strict"; + +const Cu = Components.utils; +const {require, loader} = Cu.import("resource://devtools/shared/Loader.jsm", {}); +const {DebuggerClient} = require("devtools/shared/client/main"); +const {DebuggerServer} = require("devtools/server/main"); +const {TargetFactory} = require("devtools/client/framework/target"); + +const {AddonManager} = require("resource://gre/modules/AddonManager.jsm"); +const {Extension, Management} = require("resource://gre/modules/Extension.jsm"); +const {flushJarCache} = require("resource://gre/modules/ExtensionUtils.jsm"); +const {Services} = require("resource://gre/modules/Services.jsm"); + +loader.lazyImporter(this, "ExtensionParent", "resource://gre/modules/ExtensionParent.jsm"); +loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm"); + +// Initialize a minimal DebuggerServer and connect to the webextension addon actor. +if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + SimpleTest.registerCleanupFunction(function () { + DebuggerServer.destroy(); + }); +} + +SimpleTest.registerCleanupFunction(function () { + const {hiddenXULWindow} = ExtensionParent.DebugUtils; + const debugBrowserMapSize = ExtensionParent.DebugUtils.debugBrowserPromises.size; + + if (debugBrowserMapSize > 0) { + is(debugBrowserMapSize, 0, + "ExtensionParent DebugUtils debug browsers have not been released"); + } + + if (hiddenXULWindow) { + ok(false, "ExtensionParent DebugUtils hiddenXULWindow has not been destroyed"); + } +}); + +// Test helpers related to the webextensions debugging RDP actors. + +function setWebExtensionOOPMode(oopMode) { + return SpecialPowers.pushPrefEnv({ + "set": [ + ["extensions.webextensions.remote", oopMode], + ] + }); +} + +function waitForFramesUpdated({client}, matchFn) { + return new Promise(resolve => { + const listener = (evt, data) => { + if (typeof matchFn === "function" && !matchFn(data)) { + return; + } else if (!data.frames) { + return; + } + + client.removeListener("frameUpdate", listener); + resolve(data.frames); + }; + client.addListener("frameUpdate", listener); + }); +} + +function collectFrameUpdates({client}, matchFn) { + let collected = []; + + const listener = (evt, data) => { + if (matchFn(data)) { + collected.push(data); + } + }; + + client.addListener("frameUpdate", listener); + let unsubscribe = () => { + unsubscribe = null; + client.removeListener("frameUpdate", listener); + return collected; + }; + + SimpleTest.registerCleanupFunction(function () { + if (unsubscribe) { + unsubscribe(); + } + }); + + return unsubscribe; +} + +async function attachAddon(addonId) { + const transport = DebuggerServer.connectPipe(); + const client = new DebuggerClient(transport); + + await client.connect(); + + const {addons} = await client.mainRoot.listAddons(); + const addonActor = addons.filter(actor => actor.id === addonId).pop(); + + if (!addonActor) { + client.close(); + throw new Error(`No WebExtension Actor found for ${addonId}`); + } + + const addonTarget = await TargetFactory.forRemoteTab({ + form: addonActor, + client, + chrome: true, + isTabActor: true, + }); + + return addonTarget; +} + +async function reloadAddon({client}, addonId) { + const {addons} = await client.mainRoot.listAddons(); + const addonActor = addons.filter(actor => actor.id === addonId).pop(); + + if (!addonActor) { + client.close(); + throw new Error(`No WebExtension Actor found for ${addonId}`); + } + + await client.request({ + to: addonActor.actor, + type: "reload", + }); +} + +// Test helpers related to the AddonManager. + +function generateWebExtensionXPI(extDetails) { + const addonFile = Extension.generateXPI(extDetails); + + flushJarCache(addonFile.path); + Services.ppmm.broadcastAsyncMessage("Extension:FlushJarCache", + {path: addonFile.path}); + + // Remove the file on cleanup if needed. + SimpleTest.registerCleanupFunction(() => { + flushJarCache(addonFile.path); + Services.ppmm.broadcastAsyncMessage("Extension:FlushJarCache", + {path: addonFile.path}); + + if (addonFile.exists()) { + OS.File.remove(addonFile.path); + } + }); + + return addonFile; +} + +function promiseCompleteInstall(install) { + let listener; + return new Promise((resolve, reject) => { + listener = { + onDownloadFailed: reject, + onDownloadCancelled: reject, + onInstallFailed: reject, + onInstallCancelled: reject, + onInstallEnded: resolve, + onInstallPostponed: reject, + }; + + install.addListener(listener); + install.install(); + }).then(() => { + install.removeListener(listener); + return install; + }); +} + +function promiseInstallFile(file) { + return AddonManager.getInstallForFile(file).then(install => { + if (!install) { + throw new Error(`No AddonInstall created for ${file.path}`); + } + + if (install.state != AddonManager.STATE_DOWNLOADED) { + throw new Error(`Expected file to be downloaded for install of ${file.path}`); + } + + return promiseCompleteInstall(install); + }); +} + +function promiseWebExtensionStartup() { + return new Promise(resolve => { + let listener = (evt, extension) => { + Management.off("ready", listener); + resolve(extension); + }; + + Management.on("ready", listener); + }); +} + +function promiseWebExtensionShutdown() { + return new Promise(resolve => { + let listener = (event, extension) => { + Management.off("shutdown", listener); + resolve(extension); + }; + + Management.on("shutdown", listener); + }); +} From 4a08259610ade6e25feea5e158f2f5e30ef925d0 Mon Sep 17 00:00:00 2001 From: Luca Greco Date: Mon, 24 Apr 2017 13:47:56 +0200 Subject: [PATCH 09/31] Bug 1302702 - Shorter extension urls in addon debugger window title and frames list selector. r=ochameau MozReview-Commit-ID: zMdiVPyBUR --HG-- extra : rebase_source : aec19e2edba5c44b5bdf4c0bf7dd6924e058cf70 --- devtools/client/framework/target.js | 26 ++++++++++++++++++++++++-- devtools/client/framework/toolbox.js | 13 +++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/devtools/client/framework/target.js b/devtools/client/framework/target.js index 99f8fc8c3415..55331dd23ea7 100644 --- a/devtools/client/framework/target.js +++ b/devtools/client/framework/target.js @@ -370,6 +370,25 @@ TabTarget.prototype = { return !this.window; }, + getExtensionPathName(url) { + // Return the url if the target is not a webextension. + if (!this.isWebExtension) { + throw new Error("Target is not a WebExtension"); + } + + try { + const parsedURL = new URL(url); + // Only moz-extension URL should be shortened into the URL pathname. + if (parsedURL.protocol !== "moz-extension:") { + return url; + } + return parsedURL.pathname; + } catch (e) { + // Return the url if unable to resolve the pathname. + return url; + } + }, + /** * Adds remote protocol capabilities to the target, so that it can be used * for tools that support the Remote Debugging Protocol even for local @@ -514,8 +533,11 @@ TabTarget.prototype = { event.nativeConsoleAPI = packet.nativeConsoleAPI; event.isFrameSwitching = packet.isFrameSwitching; - if (!packet.isFrameSwitching) { - // Update the title and url unless this is a frame switch. + // Keep the title unmodified when a developer toolbox switches frame + // for a tab (Bug 1261687), but always update the title when the target + // is a WebExtension (where the addon name is always included in the title + // and the url is supposed to be updated every time the selected frame changes). + if (!packet.isFrameSwitching || this.isWebExtension) { this._url = packet.url; this._title = packet.title; } diff --git a/devtools/client/framework/toolbox.js b/devtools/client/framework/toolbox.js index 43ab209e5f46..d3694674d078 100644 --- a/devtools/client/framework/toolbox.js +++ b/devtools/client/framework/toolbox.js @@ -1813,8 +1813,10 @@ Toolbox.prototype = { _refreshHostTitle: function () { let title; if (this.target.name && this.target.name != this.target.url) { + const url = this.target.isWebExtension ? + this.target.getExtensionPathName(this.target.url) : this.target.url; title = L10N.getFormatStr("toolbox.titleTemplate2", this.target.name, - this.target.url); + url); } else { title = L10N.getFormatStr("toolbox.titleTemplate1", this.target.url); } @@ -1885,9 +1887,16 @@ Toolbox.prototype = { // A frame is checked if it's the selected one. let checked = frame.id == this.selectedFrameId; + let label = frame.url; + + if (this.target.isWebExtension) { + // Show a shorter url for extensions page. + label = this.target.getExtensionPathName(frame.url); + } + // Create menu item. menu.append(new MenuItem({ - label: frame.url, + label, type: "radio", checked, click: () => { From c422dba97ade43a721a7cce33e3604ab02d3d7a7 Mon Sep 17 00:00:00 2001 From: Luca Greco Date: Mon, 24 Apr 2017 13:49:10 +0200 Subject: [PATCH 10/31] Bug 1302702 - Fix inspector panel deadwrapper exceptions on addon reloads. r=ochameau MozReview-Commit-ID: DPaUiLeTyCC --HG-- extra : rebase_source : 8e72e722ef200f48ce2a2489f4c60de05ab9d4cf --- devtools/server/actors/inspector.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/devtools/server/actors/inspector.js b/devtools/server/actors/inspector.js index ff4dae1fb12e..4c2c8ffdee51 100644 --- a/devtools/server/actors/inspector.js +++ b/devtools/server/actors/inspector.js @@ -2446,6 +2446,8 @@ var WalkerActor = protocol.ActorClassWithSpec(walkerSpec, { this.rootDoc.defaultView) { this.onFrameUnload({ window: this.rootDoc.defaultView }); } + // Update all DOM objects references to target the new document. + this.rootWin = window; this.rootDoc = window.document; this.rootNode = this.document(); this.queueMutation({ @@ -2987,7 +2989,7 @@ function DocumentWalker(node, rootWin, whatToShow = nodeFilterConstants.SHOW_ALL, filter = standardTreeWalkerFilter, skipTo = SKIP_TO_PARENT) { - if (!rootWin.location) { + if (Cu.isDeadWrapper(rootWin) || !rootWin.location) { throw new Error("Got an invalid root window in DocumentWalker"); } From 0a009bbe92617a51ac1a6c36f0d3147f8a9658eb Mon Sep 17 00:00:00 2001 From: Luca Greco Date: Thu, 11 May 2017 18:54:26 +0200 Subject: [PATCH 11/31] Bug 1302702 - Check if the threadActor is attached before using it in tab actor's _windowReady. r=ochameau MozReview-Commit-ID: IexaXRL27Lu --HG-- extra : rebase_source : 8596c602675a33b70b38483fc7b5926cac9df603 --- devtools/server/actors/tab.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/devtools/server/actors/tab.js b/devtools/server/actors/tab.js index 03d24ecc0910..aeb09e48c178 100644 --- a/devtools/server/actors/tab.js +++ b/devtools/server/actors/tab.js @@ -1168,6 +1168,12 @@ TabActor.prototype = { this._setWindow(window); DevToolsUtils.executeSoon(() => { + // No need to do anything more if the actor is not attached anymore + // e.g. the client has been closed and the actors destroyed in the meantime. + if (!this.attached) { + return; + } + // Then fake window-ready and navigate on the given document this._windowReady(window, true); DevToolsUtils.executeSoon(() => { From 85451c5c8ecb6c385efbcbf132f9a9fcbec21117 Mon Sep 17 00:00:00 2001 From: Jan Henning Date: Sun, 26 Mar 2017 18:41:09 +0200 Subject: [PATCH 12/31] Bug 1349155 - Show message when clearing user data on shutdown. r=sebastian Properly clearing data (history etc.) when shutting down via "Quit" can introduce a possibly noticeable delay (up to the order of a few seconds in bad cases) before the UI actually closes. This patch shows a snackbar for this case, so we don't give users the impression of simply randomly hanging during shutdown. MozReview-Commit-ID: AqYw8qK8xol --HG-- extra : rebase_source : 3a1f650dd27ef07ec7eb21dc511decbd94c0a99c --- mobile/android/chrome/content/browser.js | 5 +++++ mobile/android/locales/en-US/chrome/browser.properties | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index c12d5b1062d6..af2706776645 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -1510,6 +1510,11 @@ var BrowserApp = { var promises = []; let refObj = {}; + if (aShutdown && Object.getOwnPropertyNames(aItems).length > 0) { + let msg = Strings.browser.GetStringFromName("alertShutdownSanitize"); + Snackbars.show(msg, Snackbars.LENGTH_INDEFINITE); + } + TelemetryStopwatch.start("FX_SANITIZE_TOTAL", refObj); for (let key in aItems) { diff --git a/mobile/android/locales/en-US/chrome/browser.properties b/mobile/android/locales/en-US/chrome/browser.properties index 8d4312708e82..5007b3bcc309 100644 --- a/mobile/android/locales/en-US/chrome/browser.properties +++ b/mobile/android/locales/en-US/chrome/browser.properties @@ -39,6 +39,10 @@ alertSearchEngineAddedToast='%S' has been added as a search engine alertSearchEngineErrorToast=Couldn't add '%S' as a search engine alertSearchEngineDuplicateToast='%S' is already one of your search engines +# LOCALIZATION NOTE (alertShutdownSanitize): This text is shown as a snackbar during shutdown if the +# user has enabled "Clear private data on exit". +alertShutdownSanitize=Clearing private data… + alertPrintjobToast=Printing… downloadCancelPromptTitle1=Abort Download From 66cb80d20482260953ffe190c22343c0b6808dea Mon Sep 17 00:00:00 2001 From: Botond Ballo Date: Wed, 17 May 2017 12:49:23 -0400 Subject: [PATCH 13/31] Bug 1365088 - Avoid calling HitTestingTreeNode::Untransform() twice for each node during hit testing. r=kats The patch also adds another gtest that exercises hit testing, and fixes a coordinate space bug in the gtest fixture. MozReview-Commit-ID: 3QYTofkKSZj --HG-- extra : rebase_source : 7108c4b57bdd3ada41dc57ab022825a1057b899d --- gfx/layers/apz/src/APZCTreeManager.cpp | 24 ++++++++++--------- gfx/layers/apz/src/HitTestingTreeNode.cpp | 13 ++-------- gfx/layers/apz/src/HitTestingTreeNode.h | 2 +- .../apz/test/gtest/APZCTreeManagerTester.h | 5 ++-- gfx/layers/apz/test/gtest/TestHitTesting.cpp | 24 +++++++++++++++++++ 5 files changed, 43 insertions(+), 25 deletions(-) diff --git a/gfx/layers/apz/src/APZCTreeManager.cpp b/gfx/layers/apz/src/APZCTreeManager.cpp index 1426004381bf..1dc4f36e3934 100644 --- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -1836,12 +1836,15 @@ APZCTreeManager::GetAPZCAtPoint(HitTestingTreeNode* aNode, // APZCs front-to-back on the screen. HitTestingTreeNode* resultNode; HitTestingTreeNode* root = aNode; - std::stack hitTestPoints; - hitTestPoints.push(aHitTestPoint); + std::stack hitTestPoints; + hitTestPoints.push(ViewAs(aHitTestPoint, + PixelCastJustification::MovingDownToChildren)); ForEachNode(root, [&hitTestPoints](HitTestingTreeNode* aNode) { - if (aNode->IsOutsideClip(hitTestPoints.top())) { + ParentLayerPoint hitTestPointForParent = ViewAs(hitTestPoints.top(), + PixelCastJustification::MovingDownToChildren); + if (aNode->IsOutsideClip(hitTestPointForParent)) { // If the point being tested is outside the clip region for this node // then we don't need to test against this node or any of its children. // Just skip it and move on. @@ -1851,21 +1854,20 @@ APZCTreeManager::GetAPZCAtPoint(HitTestingTreeNode* aNode, } // First check the subtree rooted at this node, because deeper nodes // are more "in front". - Maybe hitTestPointForChildLayers = aNode->Untransform(hitTestPoints.top()); + Maybe hitTestPoint = aNode->Untransform(hitTestPointForParent); APZCTM_LOG("Transformed ParentLayer point %s to layer %s\n", - Stringify(hitTestPoints.top()).c_str(), - hitTestPointForChildLayers ? Stringify(hitTestPointForChildLayers.ref()).c_str() : "nil"); - if (!hitTestPointForChildLayers) { + Stringify(hitTestPointForParent).c_str(), + hitTestPoint ? Stringify(hitTestPoint.ref()).c_str() : "nil"); + if (!hitTestPoint) { return TraversalFlag::Skip; } - hitTestPoints.push(ViewAs(hitTestPointForChildLayers.ref(), - PixelCastJustification::MovingDownToChildren)); + hitTestPoints.push(hitTestPoint.ref()); return TraversalFlag::Continue; }, [&resultNode, &hitTestPoints, &aOutHitResult](HitTestingTreeNode* aNode) { - hitTestPoints.pop(); HitTestResult hitResult = aNode->HitTest(hitTestPoints.top()); - APZCTM_LOG("Testing ParentLayer point %s against node %p\n", + hitTestPoints.pop(); + APZCTM_LOG("Testing Layer point %s against node %p\n", Stringify(hitTestPoints.top()).c_str(), aNode); if (hitResult != HitTestResult::HitNothing) { resultNode = aNode; diff --git a/gfx/layers/apz/src/HitTestingTreeNode.cpp b/gfx/layers/apz/src/HitTestingTreeNode.cpp index fe70fbb82cd7..107f51b645e0 100644 --- a/gfx/layers/apz/src/HitTestingTreeNode.cpp +++ b/gfx/layers/apz/src/HitTestingTreeNode.cpp @@ -263,22 +263,13 @@ HitTestingTreeNode::Untransform(const ParentLayerPoint& aPoint) const } HitTestResult -HitTestingTreeNode::HitTest(const ParentLayerPoint& aPoint) const +HitTestingTreeNode::HitTest(const LayerPoint& aPoint) const { - // This should only ever get called if the point is inside the clip region - // for this node. - MOZ_ASSERT(!IsOutsideClip(aPoint)); - if (mOverride & EventRegionsOverride::ForceEmptyHitRegion) { return HitTestResult::HitNothing; } - // convert into Layer coordinate space - Maybe pointInLayerPixels = Untransform(aPoint); - if (!pointInLayerPixels) { - return HitTestResult::HitNothing; - } - auto point = LayerIntPoint::Round(pointInLayerPixels.ref()); + auto point = LayerIntPoint::Round(aPoint); // test against event regions in Layer coordinate space if (!mEventRegions.mHitRegion.Contains(point.x, point.y)) { diff --git a/gfx/layers/apz/src/HitTestingTreeNode.h b/gfx/layers/apz/src/HitTestingTreeNode.h index e462611eed38..f3de36ca8d18 100644 --- a/gfx/layers/apz/src/HitTestingTreeNode.h +++ b/gfx/layers/apz/src/HitTestingTreeNode.h @@ -110,7 +110,7 @@ public: Maybe Untransform(const ParentLayerPoint& aPoint) const; /* Assuming aPoint is inside the clip region for this node, check which of the * event region spaces it falls inside. */ - HitTestResult HitTest(const ParentLayerPoint& aPoint) const; + HitTestResult HitTest(const LayerPoint& aPoint) const; /* Returns the mOverride flag. */ EventRegionsOverride GetEventRegionsOverride() const; diff --git a/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h b/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h index 5b69b71f470d..490c0a3eec5e 100644 --- a/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h +++ b/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h @@ -96,8 +96,9 @@ protected: static void SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId, CSSRect aScrollableRect = CSSRect(-1, -1, -1, -1)) { - ParentLayerIntRect compositionBounds = ViewAs( - aLayer->GetVisibleRegion().ToUnknownRegion().GetBounds()); + ParentLayerIntRect compositionBounds = + RoundedToInt(aLayer->GetLocalTransformTyped(). + TransformBounds(LayerRect(aLayer->GetVisibleRegion().GetBounds()))); ScrollMetadata metadata = BuildScrollMetadata(aScrollId, aScrollableRect, ParentLayerRect(compositionBounds)); aLayer->SetScrollMetadata(metadata); diff --git a/gfx/layers/apz/test/gtest/TestHitTesting.cpp b/gfx/layers/apz/test/gtest/TestHitTesting.cpp index 70df7ee3f454..2f2654d907c8 100644 --- a/gfx/layers/apz/test/gtest/TestHitTesting.cpp +++ b/gfx/layers/apz/test/gtest/TestHitTesting.cpp @@ -278,6 +278,30 @@ TEST_F(APZHitTestingTester, HitTesting2) { EXPECT_EQ(ScreenPoint(25, 25), transformToGecko.TransformPoint(ParentLayerPoint(25, 25))); } +TEST_F(APZHitTestingTester, HitTesting3) { + const char* layerTreeSyntax = "c(t)"; + // LayerID 0 1 + nsIntRegion layerVisibleRegions[] = { + nsIntRegion(IntRect(0,0,200,200)), + nsIntRegion(IntRect(0,0,50,50)) + }; + Matrix4x4 transforms[] = { + Matrix4x4(), + Matrix4x4::Scaling(2, 2, 1) + }; + root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, transforms, lm, layers); + // No actual room to scroll + SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200)); + SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 50, 50)); + + ScopedLayerTreeRegistration registration(manager, 0, root, mcc); + + manager->UpdateHitTestingTree(0, root, false, 0, 0); + + RefPtr hit = GetTargetAPZC(ScreenPoint(75, 75)); + EXPECT_EQ(ApzcOf(layers[1]), hit.get()); +} + TEST_F(APZHitTestingTester, ComplexMultiLayerTree) { CreateComplexMultiLayerTree(); ScopedLayerTreeRegistration registration(manager, 0, root, mcc); From 7353ca75f1a5f5cab06070bff79188c9ca163651 Mon Sep 17 00:00:00 2001 From: Matthew Wein Date: Wed, 10 May 2017 15:14:15 -0400 Subject: [PATCH 14/31] Bug 1266012 - Add identity indication for the moz-extensions scheme r=mixedpuppy MozReview-Commit-ID: BtbRGPJbsHs --HG-- rename : browser/components/extensions/extension.svg => browser/themes/shared/controlcenter/extension.svg extra : rebase_source : a89e321d3f5750307a869fdf82521a61bce62ce1 --- browser/base/content/browser.js | 34 ++++++++++- browser/base/content/browser.xul | 1 + .../controlcenter/content/panel.inc.xul | 1 + .../test/browser/browser-common.ini | 1 + .../browser_ext_identity_indication.js | 61 +++++++++++++++++++ .../locales/en-US/chrome/browser/browser.dtd | 1 + .../en-US/chrome/browser/browser.properties | 2 + .../themes/shared/controlcenter/extension.svg | 9 +++ .../themes/shared/controlcenter/panel.inc.css | 6 ++ .../shared/identity-block/icons.inc.css | 5 ++ .../identity-block/identity-block.inc.css | 5 +- browser/themes/shared/jar.inc.mn | 1 + .../components/extensions/ExtensionParent.jsm | 23 +++++++ 13 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 browser/components/extensions/test/browser/browser_ext_identity_indication.js create mode 100644 browser/themes/shared/controlcenter/extension.svg diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 06959b2a9f50..fb05b42ff935 100755 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -18,6 +18,10 @@ Cu.import("resource://gre/modules/NotificationDB.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Preferences", "resource://gre/modules/Preferences.jsm"); +XPCOMUtils.defineLazyGetter(this, "extensionNameFromURI", () => { + return Cu.import("resource://gre/modules/ExtensionParent.jsm", {}).extensionNameFromURI; +}); + // lazy module getters /* global AboutHome:false, @@ -6879,6 +6883,11 @@ var gIdentityHandler = { */ _uriHasHost: false, + /** + * Whether this is a "moz-extension:" page, loaded from a WebExtension. + */ + _isExtensionPage: false, + /** * Whether this._uri refers to an internally implemented browser page. * @@ -7013,6 +7022,10 @@ var gIdentityHandler = { delete this._connectionIcon; return this._connectionIcon = document.getElementById("connection-icon"); }, + get _extensionIcon() { + delete this._extensionIcon; + return this._extensionIcon = document.getElementById("extension-icon"); + }, get _overrideService() { delete this._overrideService; return this._overrideService = Cc["@mozilla.org/security/certoverride;1"] @@ -7291,7 +7304,11 @@ var gIdentityHandler = { icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ? "rtl" : "ltr"; } - + } else if (this._isExtensionPage) { + this._identityBox.className = "extensionPage"; + let extensionName = extensionNameFromURI(this._uri); + icon_label = gNavigatorBundle.getFormattedString( + "identity.extension.label", [extensionName]); } else if (this._uriHasHost && this._isSecure) { this._identityBox.className = "verifiedDomain"; if (this._isMixedActiveContentBlocked) { @@ -7359,6 +7376,13 @@ var gIdentityHandler = { // Push the appropriate strings out to the UI this._connectionIcon.tooltipText = tooltip; + + if (this._isExtensionPage) { + let extensionName = extensionNameFromURI(this._uri); + this._extensionIcon.tooltipText = gNavigatorBundle.getFormattedString( + "identity.extension.tooltip", [extensionName]); + } + this._identityIconLabels.tooltipText = tooltip; this._identityIcon.tooltipText = gNavigatorBundle.getString("identity.icon.tooltip"); this._identityIconLabel.value = icon_label; @@ -7390,6 +7414,8 @@ var gIdentityHandler = { let connection = "not-secure"; if (this._isSecureInternalUI) { connection = "chrome"; + } else if (this._isExtensionPage) { + connection = "extension"; } else if (this._isURILoadedFromFile) { connection = "file"; } else if (this._isEV) { @@ -7470,6 +7496,10 @@ var gIdentityHandler = { hostless = true; } + if (this._isExtensionPage) { + host = extensionNameFromURI(this._uri); + } + // Fill in the CA name if we have a valid TLS certificate. if (this._isSecure || this._isCertUserOverridden) { verifier = this._identityIconLabels.tooltipText; @@ -7523,6 +7553,8 @@ var gIdentityHandler = { let whitelist = /^(?:accounts|addons|cache|config|crashes|customizing|downloads|healthreport|home|license|newaddon|permissions|preferences|privatebrowsing|rights|searchreset|sessionrestore|support|welcomeback)(?:[?#]|$)/i; this._isSecureInternalUI = uri.schemeIs("about") && whitelist.test(uri.path); + this._isExtensionPage = uri.schemeIs("moz-extension"); + // Create a channel for the sole purpose of getting the resolved URI // of the request to determine if it's loaded from the file system. this._isURILoadedFromFile = false; diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul index ee0e8d07ca2a..22dbcdf979a2 100644 --- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -846,6 +846,7 @@ tooltiptext="&urlbar.persistentStorageNotificationAnchor.tooltip;"/> + diff --git a/browser/components/controlcenter/content/panel.inc.xul b/browser/components/controlcenter/content/panel.inc.xul index 50180d664498..9a3cf8f8a141 100644 --- a/browser/components/controlcenter/content/panel.inc.xul +++ b/browser/components/controlcenter/content/panel.inc.xul @@ -32,6 +32,7 @@ when-connection="secure secure-ev">&identity.connectionSecure; &identity.connectionInternal; &identity.connectionFile; + &identity.extensionPage; + diff --git a/browser/locales/en-US/chrome/browser/browser.properties b/browser/locales/en-US/chrome/browser/browser.properties index 701514816d54..8e0c694da1a5 100644 --- a/browser/locales/en-US/chrome/browser/browser.properties +++ b/browser/locales/en-US/chrome/browser/browser.properties @@ -450,6 +450,8 @@ identity.identified.verified_by_you=You have added a security exception for this identity.identified.state_and_country=%S, %S identity.icon.tooltip=Show site information +identity.extension.label=Extension (%S) +identity.extension.tooltip=Loaded by extension: %S identity.showDetails.tooltip=Show connection details identity.hideDetails.tooltip=Hide connection details diff --git a/browser/themes/shared/controlcenter/extension.svg b/browser/themes/shared/controlcenter/extension.svg new file mode 100644 index 000000000000..72febebd87ed --- /dev/null +++ b/browser/themes/shared/controlcenter/extension.svg @@ -0,0 +1,9 @@ + + + +#include ../icon-colors.inc.svg + + diff --git a/browser/themes/shared/controlcenter/panel.inc.css b/browser/themes/shared/controlcenter/panel.inc.css index 550bf2bd1686..76eb23e07b64 100644 --- a/browser/themes/shared/controlcenter/panel.inc.css +++ b/browser/themes/shared/controlcenter/panel.inc.css @@ -16,6 +16,7 @@ #identity-popup[connection=secure] [when-connection~=secure], #identity-popup[connection=chrome] [when-connection~=chrome], #identity-popup[connection=file] [when-connection~=file], +#identity-popup[connection=extension] [when-connection~=extension], /* Show insecure login forms messages when needed. */ #identity-popup[loginforms=insecure] [when-loginforms=insecure], /* Show weak cipher messages when needed. */ @@ -249,6 +250,11 @@ background-image: url(chrome://browser/skin/controlcenter/mcb-disabled.svg); } +#identity-popup[connection=extension] #identity-popup-securityView, +#identity-popup[connection=extension] #identity-popup-security-content { + background-image: url(chrome://browser/skin/controlcenter/extension.svg); +} + #identity-popup-security-descriptions > description { margin-top: 6px; color: Graytext; diff --git a/browser/themes/shared/identity-block/icons.inc.css b/browser/themes/shared/identity-block/icons.inc.css index 411261cbb35b..b1ff0fdefdeb 100644 --- a/browser/themes/shared/identity-block/icons.inc.css +++ b/browser/themes/shared/identity-block/icons.inc.css @@ -13,6 +13,11 @@ list-style-image: url(chrome://browser/skin/identity-icon.svg#hover@iconVariant@); } +#identity-box.extensionPage > #extension-icon@selectorSuffix@ { + list-style-image: url(chrome://browser/skin/controlcenter/extension.svg); + visibility: visible; +} + #identity-box.grantedPermissions > #identity-icon@selectorSuffix@ { list-style-image: url(chrome://browser/skin/identity-icon.svg#notice@iconVariant@); } diff --git a/browser/themes/shared/identity-block/identity-block.inc.css b/browser/themes/shared/identity-block/identity-block.inc.css index 8b92f5c3562e..2da22e0954b2 100644 --- a/browser/themes/shared/identity-block/identity-block.inc.css +++ b/browser/themes/shared/identity-block/identity-block.inc.css @@ -155,16 +155,15 @@ visibility: collapse; } -/* CONNECTION ICON */ +/* CONNECTION ICON, EXTENSION ICON */ -#connection-icon { +#connection-icon, #extension-icon { width: 16px; height: 16px; margin-inline-start: 2px; visibility: collapse; } - /* REMOTE CONTROL ICON */ #main-window[remotecontrol] #remote-control-icon { diff --git a/browser/themes/shared/jar.inc.mn b/browser/themes/shared/jar.inc.mn index 706f90d1ba99..b82f4bb45451 100644 --- a/browser/themes/shared/jar.inc.mn +++ b/browser/themes/shared/jar.inc.mn @@ -28,6 +28,7 @@ * skin/classic/browser/controlcenter/conn-not-secure.svg (../shared/controlcenter/conn-not-secure.svg) * skin/classic/browser/controlcenter/connection.svg (../shared/controlcenter/connection.svg) * skin/classic/browser/controlcenter/mcb-disabled.svg (../shared/controlcenter/mcb-disabled.svg) +* skin/classic/browser/controlcenter/extension.svg (../shared/controlcenter/extension.svg) * skin/classic/browser/controlcenter/permissions.svg (../shared/controlcenter/permissions.svg) * skin/classic/browser/controlcenter/tracking-protection.svg (../shared/controlcenter/tracking-protection.svg) skin/classic/browser/controlcenter/warning-gray.svg (../shared/controlcenter/warning-gray.svg) diff --git a/toolkit/components/extensions/ExtensionParent.jsm b/toolkit/components/extensions/ExtensionParent.jsm index 4e4af22d6312..fc892cb21eac 100644 --- a/toolkit/components/extensions/ExtensionParent.jsm +++ b/toolkit/components/extensions/ExtensionParent.jsm @@ -35,6 +35,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", XPCOMUtils.defineLazyModuleGetter(this, "Schemas", "resource://gre/modules/Schemas.jsm"); +XPCOMUtils.defineLazyServiceGetter(this, "gAddonPolicyService", + "@mozilla.org/addons/policy-service;1", + "nsIAddonPolicyService"); + Cu.import("resource://gre/modules/ExtensionCommon.jsm"); Cu.import("resource://gre/modules/ExtensionUtils.jsm"); @@ -1050,7 +1054,26 @@ function watchExtensionProxyContextLoad({extension, viewType, browser}, onExtens // Used to cache the list of WebExtensionManifest properties defined in the BASE_SCHEMA. let gBaseManifestProperties = null; +/** + * Function to obtain the extension name from a moz-extension URI without exposing GlobalManager. + * + * @param {Object} uri The URI for the extension to look up. + * @returns {string} the name of the extension. + */ +function extensionNameFromURI(uri) { + let id = null; + try { + id = gAddonPolicyService.extensionURIToAddonId(uri); + } catch (ex) { + if (ex.name != "NS_ERROR_XPC_BAD_CONVERT_JS") { + Cu.reportError("Extension cannot be found in AddonPolicyService."); + } + } + return GlobalManager.getExtension(id).name; +} + const ExtensionParent = { + extensionNameFromURI, GlobalManager, HiddenExtensionPage, ParentAPIManager, From 5b3ddc580cd6c89ada40359edb7ae9c348edecb4 Mon Sep 17 00:00:00 2001 From: "Nils Ohlmeier [:drno]" Date: Mon, 15 May 2017 17:58:41 -0700 Subject: [PATCH 15/31] Bug 1365081: really use prPolicy as the pocily input value. r=jesup MozReview-Commit-ID: Ec6BrdYtT6G --HG-- extra : rebase_source : 7f7a00dd94e3b06a978b0ddd106c37c9adf09550 --- netwerk/sctp/datachannel/DataChannel.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/netwerk/sctp/datachannel/DataChannel.cpp b/netwerk/sctp/datachannel/DataChannel.cpp index c23705822844..536b069b1f67 100644 --- a/netwerk/sctp/datachannel/DataChannel.cpp +++ b/netwerk/sctp/datachannel/DataChannel.cpp @@ -1963,6 +1963,10 @@ DataChannelConnection::Open(const nsACString& label, const nsACString& protocol, case DATA_CHANNEL_PARTIAL_RELIABLE_TIMED: prPolicy = SCTP_PR_SCTP_TTL; break; + default: + LOG(("ERROR: unsupported channel type: %u", type)); + MOZ_ASSERT(false); + return nullptr; } if ((prPolicy == SCTP_PR_SCTP_NONE) && (prValue != 0)) { return nullptr; @@ -1981,7 +1985,7 @@ DataChannelConnection::Open(const nsACString& label, const nsACString& protocol, aStream, DataChannel::CONNECTING, label, protocol, - type, prValue, + prPolicy, prValue, flags, aListener, aContext)); if (aExternalNegotiated) { From 9f2f58316ae98835ed9ad743bfadcb5e539c59ef Mon Sep 17 00:00:00 2001 From: "Nils Ohlmeier [:drno]" Date: Mon, 15 May 2017 18:22:27 -0700 Subject: [PATCH 16/31] Bug 1365081: added data channel test function for maxPacketLifeTime. r=jesup MozReview-Commit-ID: KXSvIxBcLMB --HG-- extra : rebase_source : 85c5b500a2096a0bce1e7a6b241aab171cb3a728 --- dom/media/tests/mochitest/dataChannel.js | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/dom/media/tests/mochitest/dataChannel.js b/dom/media/tests/mochitest/dataChannel.js index fc02514698d3..2b2015ccf112 100644 --- a/dom/media/tests/mochitest/dataChannel.js +++ b/dom/media/tests/mochitest/dataChannel.js @@ -161,6 +161,32 @@ var commandsCheckDataChannel = [ var channels = test.pcRemote.dataChannels; var message = "I am the walrus; Goo goo g'joob"; + return test.send(message).then(result => { + is(channels.indexOf(result.channel), channels.length - 1, "Last channel used"); + is(result.data, message, "Received message has the correct content."); + }); + }, + + function CREATE_NEGOTIATED_MAXPACKET_LIFE_DATA_CHANNEL(test) { + var options = { + ordered: false, + maxPacketLifeTime: 10 + }; + return test.createDataChannel(options).then(result => { + var sourceChannel3 = result.local; + var targetChannel3 = result.remote; + is(sourceChannel3.readyState, "open", sourceChannel3 + " is in state: 'open'"); + is(targetChannel3.readyState, "open", targetChannel3 + " is in state: 'open'"); + + is(targetChannel3.binaryType, "blob", targetChannel3 + " is of binary type 'blob'"); + + }); + }, + + function SEND_MESSAGE_THROUGH_LAST_OPENED_CHANNEL3(test) { + var channels = test.pcRemote.dataChannels; + var message = "Nice to see you working maxPacketLifeTime"; + return test.send(message).then(result => { is(channels.indexOf(result.channel), channels.length - 1, "Last channel used"); is(result.data, message, "Received message has the correct content."); From 4fb45e5b8c7c5b468c80c2fda0730b1baf7b678c Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Tue, 16 May 2017 22:22:42 +0100 Subject: [PATCH 17/31] Bug 1365412 - Clean up various ESLint global definitions in browser/. r=mossop MozReview-Commit-ID: JqAm9x1XGCM --HG-- extra : rebase_source : e9215288195f1fe683832db53d87dbe77c697b4c --- .../general/browser_bug763468_perwindowpb.js | 6 ----- .../general/browser_bug767836_perwindowpb.js | 3 --- .../extensions/ParseSymbols-worker.js | 1 - browser/components/extensions/ext-browser.js | 1 - browser/components/extensions/ext-utils.js | 1 - .../extensions/test/browser/.eslintrc.js | 23 ------------------- .../browser/browser_ext_pageAction_context.js | 2 -- .../browser/browser_ext_pageAction_title.js | 2 -- .../browser_ext_sessions_getRecentlyClosed.js | 2 -- ..._ext_sessions_getRecentlyClosed_private.js | 2 -- .../test/browser/browser_ext_webRequest.js | 3 +-- .../extensions/test/browser/head.js | 1 - .../test/browser/head_pageAction.js | 4 ++-- browser/components/feeds/content/subscribe.js | 2 -- .../migration/360seProfileMigrator.js | 4 ++-- browser/components/migration/AutoMigrate.jsm | 1 - .../migration/ChromeProfileMigrator.js | 4 ++-- .../migration/EdgeProfileMigrator.js | 4 ++-- .../migration/FirefoxProfileMigrator.js | 2 +- .../components/migration/IEProfileMigrator.js | 4 ++-- .../migration/SafariProfileMigrator.js | 4 ++-- .../migration/tests/unit/head_migration.js | 2 +- .../tests/unit/test_Chrome_bookmarks.js | 2 +- .../migration/tests/unit/test_fx_telemetry.js | 2 -- .../components/newtab/NewTabPrefsProvider.jsm | 3 --- .../newtab/NewTabSearchProvider.jsm | 3 --- browser/components/newtab/NewTabURL.jsm | 3 --- .../components/newtab/NewTabWebChannel.jsm | 11 --------- browser/components/newtab/PreviewProvider.jsm | 3 --- .../components/newtab/aboutNewTabService.js | 1 - .../tests/browser/browser_PreviewProvider.js | 1 - .../tests/browser/browser_newtab_overrides.js | 12 ---------- .../browser/browser_remotenewtab_pageloads.js | 1 - .../tests/xpcshell/test_AboutNewTabService.js | 2 -- .../xpcshell/test_NewTabPrefsProvider.js | 3 --- .../xpcshell/test_NewTabSearchProvider.js | 3 --- .../newtab/tests/xpcshell/test_NewTabURL.js | 1 - .../test/browser/file_sharedworker.js | 2 +- .../test/mochitest/.eslintrc.js | 7 ++++++ .../test/mochitest/test_permissions_api.html | 1 - .../components/places/tests/browser/head.js | 5 ---- .../components/preferences/selectBookmark.js | 3 +-- .../content/FormAutofillFrameScript.js | 2 +- .../webcompat-reporter/content/tab-frame.js | 2 +- .../webcompat-reporter/content/wc-frame.js | 2 +- .../webcompat/test/browser_check_installed.js | 2 -- .../webcompat/test/browser_overrider.js | 2 -- browser/modules/ContentSearch.jsm | 1 - browser/modules/test/browser/contentSearch.js | 2 +- .../lib/configs/recommended.js | 1 + .../lib/environments/simpletest.js | 1 + .../eslint/eslint-plugin-mozilla/package.json | 2 +- tools/lint/eslint/modules.json | 2 ++ 53 files changed, 34 insertions(+), 132 deletions(-) create mode 100644 browser/components/originattributes/test/mochitest/.eslintrc.js diff --git a/browser/base/content/test/general/browser_bug763468_perwindowpb.js b/browser/base/content/test/general/browser_bug763468_perwindowpb.js index cbf55299a4f1..7e7897cb7e1f 100644 --- a/browser/base/content/test/general/browser_bug763468_perwindowpb.js +++ b/browser/base/content/test/general/browser_bug763468_perwindowpb.js @@ -3,12 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; -/* globals - waitForExplicitFinish, whenNewWindowLoaded, whenNewTabLoaded, - executeSoon, registerCleanupFunction, finish, is -*/ -/* exported test */ - // This test makes sure that opening a new tab in private browsing mode opens about:privatebrowsing function test() { // initialization diff --git a/browser/base/content/test/general/browser_bug767836_perwindowpb.js b/browser/base/content/test/general/browser_bug767836_perwindowpb.js index 1d32bf019281..d8dd5f158dae 100644 --- a/browser/base/content/test/general/browser_bug767836_perwindowpb.js +++ b/browser/base/content/test/general/browser_bug767836_perwindowpb.js @@ -2,9 +2,6 @@ * 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"; -/* globals waitForExplicitFinish, executeSoon, finish, whenNewWindowLoaded, ok */ -/* globals is */ -/* exported test */ function test() { // initialization diff --git a/browser/components/extensions/ParseSymbols-worker.js b/browser/components/extensions/ParseSymbols-worker.js index 00dbd326e8e3..92b63c5e8b2b 100644 --- a/browser/components/extensions/ParseSymbols-worker.js +++ b/browser/components/extensions/ParseSymbols-worker.js @@ -1,7 +1,6 @@ /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set sts=2 sw=2 et tw=80: */ /* eslint-env worker */ -/* globals OS, ParseSymbols */ "use strict"; diff --git a/browser/components/extensions/ext-browser.js b/browser/components/extensions/ext-browser.js index 82cf1448813e..94cce20d2758 100644 --- a/browser/components/extensions/ext-browser.js +++ b/browser/components/extensions/ext-browser.js @@ -54,7 +54,6 @@ extensions.on("page-shutdown", (type, context) => { } }); /* eslint-enable mozilla/balanced-listeners */ -/* eslint-enable mozilla/balanced-listeners */ global.openOptionsPage = (extension) => { let window = windowTracker.topWindow; diff --git a/browser/components/extensions/ext-utils.js b/browser/components/extensions/ext-utils.js index 18108b1a5cda..19c604abd1fb 100644 --- a/browser/components/extensions/ext-utils.js +++ b/browser/components/extensions/ext-utils.js @@ -5,7 +5,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"); -/* globals TabBase, WindowBase, TabTrackerBase, WindowTrackerBase, TabManagerBase, WindowManagerBase */ Cu.import("resource://gre/modules/ExtensionTabs.jsm"); XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService", diff --git a/browser/components/extensions/test/browser/.eslintrc.js b/browser/components/extensions/test/browser/.eslintrc.js index c2c4cdf37f71..aaa751dd756b 100644 --- a/browser/components/extensions/test/browser/.eslintrc.js +++ b/browser/components/extensions/test/browser/.eslintrc.js @@ -7,29 +7,6 @@ module.exports = { "webextensions": true, }, - "globals": { - "NetUtil": true, - "XPCOMUtils": true, - "Task": true, - - // Browser window globals. - "PanelUI": false, - - // Test harness globals - "ExtensionTestUtils": false, - "TestUtils": false, - - "clickBrowserAction": true, - "clickPageAction": true, - "closeContextMenu": true, - "closeExtensionContextMenu": true, - "focusWindow": true, - "makeWidgetId": true, - "openContextMenu": true, - "openExtensionContextMenu": true, - "CustomizableUI": true, - }, - "rules": { "no-shadow": 0, }, diff --git a/browser/components/extensions/test/browser/browser_ext_pageAction_context.js b/browser/components/extensions/test/browser/browser_ext_pageAction_context.js index 9662dc5764f7..793e14df1ac3 100644 --- a/browser/components/extensions/test/browser/browser_ext_pageAction_context.js +++ b/browser/components/extensions/test/browser/browser_ext_pageAction_context.js @@ -2,8 +2,6 @@ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; -/* global runTests */ - Services.scriptloader.loadSubScript(new URL("head_pageAction.js", gTestPath).href, this); diff --git a/browser/components/extensions/test/browser/browser_ext_pageAction_title.js b/browser/components/extensions/test/browser/browser_ext_pageAction_title.js index 8ae14c04f467..69329897fb03 100644 --- a/browser/components/extensions/test/browser/browser_ext_pageAction_title.js +++ b/browser/components/extensions/test/browser/browser_ext_pageAction_title.js @@ -2,8 +2,6 @@ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; -/* global runTests */ - Services.scriptloader.loadSubScript(new URL("head_pageAction.js", gTestPath).href, this); diff --git a/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed.js b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed.js index 519a3a6a73ca..fcdd3a263490 100644 --- a/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed.js +++ b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed.js @@ -2,8 +2,6 @@ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; -/* globals recordInitialTimestamps, onlyNewItemsFilter, checkRecentlyClosed */ - requestLongerTimeout(2); Services.scriptloader.loadSubScript(new URL("head_sessions.js", gTestPath).href, diff --git a/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_private.js b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_private.js index baafbb6194f0..1d2aab4755e8 100644 --- a/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_private.js +++ b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_private.js @@ -2,8 +2,6 @@ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; -/* globals recordInitialTimestamps, onlyNewItemsFilter, checkRecentlyClosed */ - SimpleTest.requestCompleteLog(); Services.scriptloader.loadSubScript(new URL("head_sessions.js", gTestPath).href, diff --git a/browser/components/extensions/test/browser/browser_ext_webRequest.js b/browser/components/extensions/test/browser/browser_ext_webRequest.js index 6bd8da3824ca..6160f89f61b9 100644 --- a/browser/components/extensions/test/browser/browser_ext_webRequest.js +++ b/browser/components/extensions/test/browser/browser_ext_webRequest.js @@ -1,8 +1,8 @@ /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set sts=2 sw=2 et tw=80: */ -/* globals makeExtension */ "use strict"; +/* import-globals-from ../../../../../toolkit/components/extensions/test/mochitest/head_webrequest.js */ Services.scriptloader.loadSubScript(new URL("head_webrequest.js", gTestPath).href, this); @@ -117,4 +117,3 @@ add_task(async function test_subframe() { add_task(async function teardown() { await extension.unload(); }); - diff --git a/browser/components/extensions/test/browser/head.js b/browser/components/extensions/test/browser/head.js index ec34ebc017b8..deb47543d0ab 100644 --- a/browser/components/extensions/test/browser/head.js +++ b/browser/components/extensions/test/browser/head.js @@ -371,7 +371,6 @@ async function clickPageAction(extension, win = window) { // // Unfortunately, that doesn't happen automatically in browser chrome // tests. - /* globals SetPageProxyState */ SetPageProxyState("valid"); await promiseAnimationFrame(win); diff --git a/browser/components/extensions/test/browser/head_pageAction.js b/browser/components/extensions/test/browser/head_pageAction.js index c6c0c8e683e1..4d5b52f90deb 100644 --- a/browser/components/extensions/test/browser/head_pageAction.js +++ b/browser/components/extensions/test/browser/head_pageAction.js @@ -3,7 +3,8 @@ "use strict"; /* exported runTests */ -/* globals getListStyleImage, promiseAnimationFrame */ +// This file is imported into the same scope as head.js. +/* import-globals-from head.js */ async function runTests(options) { function background(getTests) { @@ -157,4 +158,3 @@ async function runTests(options) { await BrowserTestUtils.closeWindow(win); } } - diff --git a/browser/components/feeds/content/subscribe.js b/browser/components/feeds/content/subscribe.js index 05a564bf1b14..99c6d4e9e89a 100644 --- a/browser/components/feeds/content/subscribe.js +++ b/browser/components/feeds/content/subscribe.js @@ -3,8 +3,6 @@ * 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/. */ -/* global BrowserFeedWriter */ - var SubscribeHandler = { /** * The nsIFeedWriter object that produces the UI diff --git a/browser/components/migration/360seProfileMigrator.js b/browser/components/migration/360seProfileMigrator.js index ff4800b50dcd..ecf7dc5bfd00 100644 --- a/browser/components/migration/360seProfileMigrator.js +++ b/browser/components/migration/360seProfileMigrator.js @@ -10,8 +10,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/NetUtil.jsm"); Cu.import("resource://gre/modules/FileUtils.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */ -Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */ +Cu.import("resource://gre/modules/osfile.jsm"); +Cu.import("resource:///modules/MigrationUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"); diff --git a/browser/components/migration/AutoMigrate.jsm b/browser/components/migration/AutoMigrate.jsm index 9d000e6c219c..75a4c4e03e44 100644 --- a/browser/components/migration/AutoMigrate.jsm +++ b/browser/components/migration/AutoMigrate.jsm @@ -46,7 +46,6 @@ XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() { Cu.importGlobalProperties(["URL"]); -/* globals kUndoStateFullPath */ XPCOMUtils.defineLazyGetter(this, "kUndoStateFullPath", function() { return OS.Path.join(OS.Constants.Path.profileDir, "initialMigrationMetadata.jsonlz4"); }); diff --git a/browser/components/migration/ChromeProfileMigrator.js b/browser/components/migration/ChromeProfileMigrator.js index 5f3b4b2f25d7..1d16a446beb7 100644 --- a/browser/components/migration/ChromeProfileMigrator.js +++ b/browser/components/migration/ChromeProfileMigrator.js @@ -24,8 +24,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/NetUtil.jsm"); Cu.import("resource://gre/modules/FileUtils.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */ -Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */ +Cu.import("resource://gre/modules/osfile.jsm"); +Cu.import("resource:///modules/MigrationUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"); diff --git a/browser/components/migration/EdgeProfileMigrator.js b/browser/components/migration/EdgeProfileMigrator.js index 9d257916296a..3904a274e30d 100644 --- a/browser/components/migration/EdgeProfileMigrator.js +++ b/browser/components/migration/EdgeProfileMigrator.js @@ -7,10 +7,10 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; Cu.import("resource://gre/modules/AppConstants.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */ +Cu.import("resource://gre/modules/osfile.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */ +Cu.import("resource:///modules/MigrationUtils.jsm"); Cu.import("resource:///modules/MSMigrationUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"); diff --git a/browser/components/migration/FirefoxProfileMigrator.js b/browser/components/migration/FirefoxProfileMigrator.js index f886a5a50a5f..b4a290637552 100644 --- a/browser/components/migration/FirefoxProfileMigrator.js +++ b/browser/components/migration/FirefoxProfileMigrator.js @@ -16,7 +16,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */ +Cu.import("resource:///modules/MigrationUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups", diff --git a/browser/components/migration/IEProfileMigrator.js b/browser/components/migration/IEProfileMigrator.js index 7439af27f6e9..90aeb97e8f30 100644 --- a/browser/components/migration/IEProfileMigrator.js +++ b/browser/components/migration/IEProfileMigrator.js @@ -13,10 +13,10 @@ const kLoginsKey = "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storag const kMainKey = "Software\\Microsoft\\Internet Explorer\\Main"; Cu.import("resource://gre/modules/AppConstants.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */ +Cu.import("resource://gre/modules/osfile.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */ +Cu.import("resource:///modules/MigrationUtils.jsm"); Cu.import("resource:///modules/MSMigrationUtils.jsm"); diff --git a/browser/components/migration/SafariProfileMigrator.js b/browser/components/migration/SafariProfileMigrator.js index a4bac3f982ab..02ea4311391b 100644 --- a/browser/components/migration/SafariProfileMigrator.js +++ b/browser/components/migration/SafariProfileMigrator.js @@ -10,10 +10,10 @@ var Cu = Components.utils; Cu.import("resource://gre/modules/AppConstants.jsm"); Cu.import("resource://gre/modules/FileUtils.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */ +Cu.import("resource://gre/modules/osfile.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */ +Cu.import("resource:///modules/MigrationUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Downloads", "resource://gre/modules/Downloads.jsm"); diff --git a/browser/components/migration/tests/unit/head_migration.js b/browser/components/migration/tests/unit/head_migration.js index 57e2287c78c2..3a94860280e9 100644 --- a/browser/components/migration/tests/unit/head_migration.js +++ b/browser/components/migration/tests/unit/head_migration.js @@ -24,7 +24,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "Sqlite", // Initialize profile. var gProfD = do_get_profile(); -Cu.import("resource://testing-common/AppInfo.jsm"); /* globals updateAppInfo */ +Cu.import("resource://testing-common/AppInfo.jsm"); updateAppInfo(); /** diff --git a/browser/components/migration/tests/unit/test_Chrome_bookmarks.js b/browser/components/migration/tests/unit/test_Chrome_bookmarks.js index 57adf6aa98aa..8806fbfa064d 100644 --- a/browser/components/migration/tests/unit/test_Chrome_bookmarks.js +++ b/browser/components/migration/tests/unit/test_Chrome_bookmarks.js @@ -28,7 +28,7 @@ add_task(async function() { // importing osfile will sometimes greedily fetch certain path identifiers // from the dir service, which means they get cached, which means we can't // register a fake path for them anymore. - Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */ + Cu.import("resource://gre/modules/osfile.jsm"); await OS.File.makeDir(target.path, {from: rootDir.parent.path, ignoreExisting: true}); target.append("Bookmarks"); diff --git a/browser/components/migration/tests/unit/test_fx_telemetry.js b/browser/components/migration/tests/unit/test_fx_telemetry.js index acbce1817eae..71ab027da2d6 100644 --- a/browser/components/migration/tests/unit/test_fx_telemetry.js +++ b/browser/components/migration/tests/unit/test_fx_telemetry.js @@ -1,8 +1,6 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ -/* globals do_get_tempdir */ - "use strict"; function run_test() { diff --git a/browser/components/newtab/NewTabPrefsProvider.jsm b/browser/components/newtab/NewTabPrefsProvider.jsm index 38e96262c1b7..8dfbf4b4e582 100644 --- a/browser/components/newtab/NewTabPrefsProvider.jsm +++ b/browser/components/newtab/NewTabPrefsProvider.jsm @@ -1,6 +1,3 @@ -/* global Services, Preferences, EventEmitter, XPCOMUtils */ -/* exported NewTabPrefsProvider */ - "use strict"; this.EXPORTED_SYMBOLS = ["NewTabPrefsProvider"]; diff --git a/browser/components/newtab/NewTabSearchProvider.jsm b/browser/components/newtab/NewTabSearchProvider.jsm index 513092c2a3c6..9d4c83acdff6 100644 --- a/browser/components/newtab/NewTabSearchProvider.jsm +++ b/browser/components/newtab/NewTabSearchProvider.jsm @@ -1,6 +1,3 @@ -/* global XPCOMUtils, ContentSearch, Task, Services, EventEmitter */ -/* exported NewTabSearchProvider */ - "use strict"; this.EXPORTED_SYMBOLS = ["NewTabSearchProvider"]; diff --git a/browser/components/newtab/NewTabURL.jsm b/browser/components/newtab/NewTabURL.jsm index 9c8b78dd571a..83832f09a922 100644 --- a/browser/components/newtab/NewTabURL.jsm +++ b/browser/components/newtab/NewTabURL.jsm @@ -2,9 +2,6 @@ * 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/. */ -/* globals XPCOMUtils, aboutNewTabService*/ -/* exported NewTabURL */ - "use strict"; const {utils: Cu} = Components; diff --git a/browser/components/newtab/NewTabWebChannel.jsm b/browser/components/newtab/NewTabWebChannel.jsm index 2a607a5a40ed..e891a106519f 100644 --- a/browser/components/newtab/NewTabWebChannel.jsm +++ b/browser/components/newtab/NewTabWebChannel.jsm @@ -1,14 +1,3 @@ -/* global - NewTabPrefsProvider, - Services, - EventEmitter, - Preferences, - XPCOMUtils, - WebChannel, - NewTabRemoteResources -*/ -/* exported NewTabWebChannel */ - "use strict"; this.EXPORTED_SYMBOLS = ["NewTabWebChannel"]; diff --git a/browser/components/newtab/PreviewProvider.jsm b/browser/components/newtab/PreviewProvider.jsm index 6f89cd744708..02e86c42656c 100644 --- a/browser/components/newtab/PreviewProvider.jsm +++ b/browser/components/newtab/PreviewProvider.jsm @@ -1,6 +1,3 @@ -/* global XPCOMUtils, BackgroundPageThumbs, FileUtils, PageThumbsStorage, Task, MIMEService */ -/* exported PreviewProvider */ - "use strict"; this.EXPORTED_SYMBOLS = ["PreviewProvider"]; diff --git a/browser/components/newtab/aboutNewTabService.js b/browser/components/newtab/aboutNewTabService.js index 20618153e031..81cc7358eea4 100644 --- a/browser/components/newtab/aboutNewTabService.js +++ b/browser/components/newtab/aboutNewTabService.js @@ -4,7 +4,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -/* globals XPCOMUtils, Preferences, Services */ "use strict"; const {utils: Cu, interfaces: Ci} = Components; diff --git a/browser/components/newtab/tests/browser/browser_PreviewProvider.js b/browser/components/newtab/tests/browser/browser_PreviewProvider.js index ffe8b324bfda..4367450eb38a 100644 --- a/browser/components/newtab/tests/browser/browser_PreviewProvider.js +++ b/browser/components/newtab/tests/browser/browser_PreviewProvider.js @@ -1,4 +1,3 @@ -/* globals XPCOMUtils, Services, PreviewProvider, registerCleanupFunction */ "use strict"; let Cu = Components.utils; diff --git a/browser/components/newtab/tests/browser/browser_newtab_overrides.js b/browser/components/newtab/tests/browser/browser_newtab_overrides.js index 62da01eb5e0d..17785dc63eeb 100644 --- a/browser/components/newtab/tests/browser/browser_newtab_overrides.js +++ b/browser/components/newtab/tests/browser/browser_newtab_overrides.js @@ -1,15 +1,3 @@ -/* globals - XPCOMUtils, - aboutNewTabService, - Services, - ContentTask, - TestUtils, - BrowserOpenTab, - registerCleanupFunction, - is, - content -*/ - "use strict"; let Cu = Components.utils; diff --git a/browser/components/newtab/tests/browser/browser_remotenewtab_pageloads.js b/browser/components/newtab/tests/browser/browser_remotenewtab_pageloads.js index db85d3cc57c6..d7af2abf9088 100644 --- a/browser/components/newtab/tests/browser/browser_remotenewtab_pageloads.js +++ b/browser/components/newtab/tests/browser/browser_remotenewtab_pageloads.js @@ -1,4 +1,3 @@ -/* globals Cu, XPCOMUtils, TestUtils, aboutNewTabService, ContentTask, content, is */ "use strict"; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); diff --git a/browser/components/newtab/tests/xpcshell/test_AboutNewTabService.js b/browser/components/newtab/tests/xpcshell/test_AboutNewTabService.js index 41bffb5f562d..f75068db85ab 100644 --- a/browser/components/newtab/tests/xpcshell/test_AboutNewTabService.js +++ b/browser/components/newtab/tests/xpcshell/test_AboutNewTabService.js @@ -2,8 +2,6 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -/* globals Services, XPCOMUtils, Preferences, aboutNewTabService, do_register_cleanup */ - "use strict"; const {utils: Cu} = Components; diff --git a/browser/components/newtab/tests/xpcshell/test_NewTabPrefsProvider.js b/browser/components/newtab/tests/xpcshell/test_NewTabPrefsProvider.js index 2eca155a5a73..b6c8b2019a9e 100644 --- a/browser/components/newtab/tests/xpcshell/test_NewTabPrefsProvider.js +++ b/browser/components/newtab/tests/xpcshell/test_NewTabPrefsProvider.js @@ -1,8 +1,5 @@ "use strict"; -/* global XPCOMUtils, equal, Preferences, NewTabPrefsProvider, run_next_test */ -/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ - const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Preferences.jsm"); diff --git a/browser/components/newtab/tests/xpcshell/test_NewTabSearchProvider.js b/browser/components/newtab/tests/xpcshell/test_NewTabSearchProvider.js index c6c39dc0104c..e02418f7d9db 100644 --- a/browser/components/newtab/tests/xpcshell/test_NewTabSearchProvider.js +++ b/browser/components/newtab/tests/xpcshell/test_NewTabSearchProvider.js @@ -1,8 +1,5 @@ "use strict"; -/* global XPCOMUtils, NewTabSearchProvider, run_next_test, ok, equal, do_check_true, do_get_profile, Services */ -/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ - const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); diff --git a/browser/components/newtab/tests/xpcshell/test_NewTabURL.js b/browser/components/newtab/tests/xpcshell/test_NewTabURL.js index 46bf59a49aa7..c16822fa2c81 100644 --- a/browser/components/newtab/tests/xpcshell/test_NewTabURL.js +++ b/browser/components/newtab/tests/xpcshell/test_NewTabURL.js @@ -2,7 +2,6 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ -/* globals Services, NewTabURL, XPCOMUtils, aboutNewTabService */ "use strict"; const {utils: Cu} = Components; diff --git a/browser/components/originattributes/test/browser/file_sharedworker.js b/browser/components/originattributes/test/browser/file_sharedworker.js index f0fd2dac58a8..1fb7b0b0054f 100644 --- a/browser/components/originattributes/test/browser/file_sharedworker.js +++ b/browser/components/originattributes/test/browser/file_sharedworker.js @@ -1,6 +1,6 @@ self.randomValue = Math.random(); -/* global onconnect:true */ +/* eslint-env worker */ onconnect = function(e) { let port = e.ports[0]; diff --git a/browser/components/originattributes/test/mochitest/.eslintrc.js b/browser/components/originattributes/test/mochitest/.eslintrc.js new file mode 100644 index 000000000000..8c8be53cd53d --- /dev/null +++ b/browser/components/originattributes/test/mochitest/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "plugin:mozilla/mochitest-test" + ] +}; diff --git a/browser/components/originattributes/test/mochitest/test_permissions_api.html b/browser/components/originattributes/test/mochitest/test_permissions_api.html index 79c5bd8cf700..f33c8c2b5793 100644 --- a/browser/components/originattributes/test/mochitest/test_permissions_api.html +++ b/browser/components/originattributes/test/mochitest/test_permissions_api.html @@ -15,7 +15,6 @@