diff --git a/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_named_window.js b/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_named_window.js index 1d99cf21cf6e..3a0a95031993 100644 --- a/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_named_window.js +++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_named_window.js @@ -94,8 +94,6 @@ add_task(async function test_window_open_in_named_win() { }, }); - assertNoPendingCreatedNavigationTargetData(); - BrowserTestUtils.removeTab(tab1); await extension.unload(); diff --git a/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_subframe_window_open.js b/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_subframe_window_open.js index 281bfa365877..86d52ac548c4 100644 --- a/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_subframe_window_open.js +++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_subframe_window_open.js @@ -94,8 +94,6 @@ add_task(async function test_window_open_from_subframe() { }, }); - assertNoPendingCreatedNavigationTargetData(); - BrowserTestUtils.removeTab(tab1); await extension.unload(); @@ -163,8 +161,6 @@ add_task(async function test_window_open_close_from_browserAction_popup() { }, }); - assertNoPendingCreatedNavigationTargetData(); - BrowserTestUtils.removeTab(tab1); await extension.unload(); diff --git a/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_window_open.js b/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_window_open.js index 79ebb401906b..facb350029ac 100644 --- a/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_window_open.js +++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_onCreatedNavigationTarget_window_open.js @@ -94,8 +94,6 @@ add_task(async function test_window_open() { }, }); - assertNoPendingCreatedNavigationTargetData(); - BrowserTestUtils.removeTab(tab1); await extension.unload(); @@ -163,8 +161,6 @@ add_task(async function test_window_open_close_from_browserAction_popup() { }, }); - assertNoPendingCreatedNavigationTargetData(); - BrowserTestUtils.removeTab(tab1); await extension.unload(); diff --git a/browser/components/extensions/test/browser/head_webNavigation.js b/browser/components/extensions/test/browser/head_webNavigation.js index 3c55af3a68d0..314ddc9326ba 100644 --- a/browser/components/extensions/test/browser/head_webNavigation.js +++ b/browser/components/extensions/test/browser/head_webNavigation.js @@ -3,7 +3,7 @@ "use strict"; /* exported BASE_URL, SOURCE_PAGE, OPENED_PAGE, - runCreatedNavigationTargetTest, assertNoPendingCreatedNavigationTargetData */ + runCreatedNavigationTargetTest */ const BASE_URL = "http://mochi.test:8888/browser/browser/components/extensions/test/browser"; @@ -47,19 +47,3 @@ async function runCreatedNavigationTargetTest({ "Got the expected webNavigation.onCompleted url property" ); } - -// Test that there are no pending createdNavigationTarget messages still tracked -// in WebNavigation.jsm (to be called before the extension is unloaded, because -// once the last extension which have subscribed a webNavigation event is unloaded -// all the pending created navigation target data is completely cleared). -function assertNoPendingCreatedNavigationTargetData() { - const { Manager } = ChromeUtils.import( - "resource://gre/modules/WebNavigation.jsm", - null - ); - Assert.equal( - Manager.createdNavigationTargetByOuterWindowId.size, - 0, - "There should be no pending createdNavigationTarget messages in WebNavigation" - ); -} diff --git a/toolkit/components/extensions/ExtensionsParent.cpp b/toolkit/components/extensions/ExtensionsParent.cpp index 2b3acea03264..b203d39c3975 100644 --- a/toolkit/components/extensions/ExtensionsParent.cpp +++ b/toolkit/components/extensions/ExtensionsParent.cpp @@ -3,17 +3,31 @@ * 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/. */ +#include "extIWebNavigation.h" #include "mozilla/extensions/ExtensionsParent.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/RefPtr.h" #include "jsapi.h" +#include "nsImportModule.h" #include "xpcpublic.h" namespace mozilla { namespace extensions { +ExtensionsParent::ExtensionsParent() {} +ExtensionsParent::~ExtensionsParent() {} + +extIWebNavigation* ExtensionsParent::WebNavigation() { + if (!mWebNavigation) { + mWebNavigation = do_ImportModule("resource://gre/modules/WebNavigation.jsm", + "WebNavigationManager"); + MOZ_RELEASE_ASSERT(mWebNavigation); + } + return mWebNavigation; +} + void ExtensionsParent::ActorDestroy(ActorDestroyReason aWhy) {} static inline JS::Handle ToJSBoolean(bool aValue) { @@ -53,6 +67,8 @@ ipc::IPCResult ExtensionsParent::RecvDocumentChange( JS::Rooted transitionData( dom::RootingCx(), FrameTransitionDataToJSValue(aTransitionData)); + + WebNavigation()->OnDocumentChange(aBC.get(), transitionData, aLocation); return IPC_OK(); } @@ -66,6 +82,10 @@ ipc::IPCResult ExtensionsParent::RecvHistoryChange( JS::Rooted transitionData( dom::RootingCx(), FrameTransitionDataToJSValue(aTransitionData)); + + WebNavigation()->OnHistoryChange(aBC.get(), transitionData, aLocation, + aIsHistoryStateUpdated, + aIsReferenceFragmentUpdated); return IPC_OK(); } @@ -75,15 +95,20 @@ ipc::IPCResult ExtensionsParent::RecvStateChange( if (aBC.IsNullOrDiscarded()) { return IPC_OK(); } + + WebNavigation()->OnStateChange(aBC.get(), aRequestURI, aStatus, aStateFlags); return IPC_OK(); } ipc::IPCResult ExtensionsParent::RecvCreatedNavigationTarget( MaybeDiscardedBrowsingContext&& aBC, - MaybeDiscardedBrowsingContext&& aSourceBC, const nsCString& aURI) { + MaybeDiscardedBrowsingContext&& aSourceBC, const nsCString& aURL) { if (aBC.IsNullOrDiscarded() || aSourceBC.IsNull()) { return IPC_OK(); } + + WebNavigation()->OnCreatedNavigationTarget( + aBC.get(), aSourceBC.GetMaybeDiscarded(), aURL); return IPC_OK(); } @@ -92,6 +117,8 @@ ipc::IPCResult ExtensionsParent::RecvDOMContentLoaded( if (aBC.IsNullOrDiscarded()) { return IPC_OK(); } + + WebNavigation()->OnDOMContentLoaded(aBC.get(), aDocumentURI); return IPC_OK(); } diff --git a/toolkit/components/extensions/ExtensionsParent.h b/toolkit/components/extensions/ExtensionsParent.h index 03c1bb0b0e11..039e6c9093cf 100644 --- a/toolkit/components/extensions/ExtensionsParent.h +++ b/toolkit/components/extensions/ExtensionsParent.h @@ -9,6 +9,8 @@ #include "mozilla/extensions/PExtensionsParent.h" #include "nsISupportsImpl.h" +class extIWebNavigation; + namespace mozilla { namespace extensions { @@ -16,7 +18,7 @@ class ExtensionsParent final : public PExtensionsParent { public: NS_INLINE_DECL_REFCOUNTING(ExtensionsParent, final) - ExtensionsParent() = default; + ExtensionsParent(); ipc::IPCResult RecvDocumentChange(MaybeDiscardedBrowsingContext&& aBC, FrameTransitionData&& aTransitionData, @@ -40,7 +42,11 @@ class ExtensionsParent final : public PExtensionsParent { nsIURI* aDocumentURI); private: - ~ExtensionsParent() = default; + ~ExtensionsParent(); + + extIWebNavigation* WebNavigation(); + + nsCOMPtr mWebNavigation; protected: void ActorDestroy(ActorDestroyReason aWhy) override; diff --git a/toolkit/components/extensions/WebNavigation.jsm b/toolkit/components/extensions/WebNavigation.jsm index 4012a056af8c..3eac83aae65d 100644 --- a/toolkit/components/extensions/WebNavigation.jsm +++ b/toolkit/components/extensions/WebNavigation.jsm @@ -4,7 +4,7 @@ "use strict"; -const EXPORTED_SYMBOLS = ["WebNavigation"]; +const EXPORTED_SYMBOLS = ["WebNavigation", "WebNavigationManager"]; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { AppConstants } = ChromeUtils.import( @@ -26,6 +26,11 @@ ChromeUtils.defineModuleGetter( "UrlbarUtils", "resource:///modules/UrlbarUtils.jsm" ); +ChromeUtils.defineModuleGetter( + this, + "WebNavigationFrames", + "resource://gre/modules/WebNavigationFrames.jsm" +); ChromeUtils.defineModuleGetter( this, "ClickHandlerParent", @@ -37,7 +42,11 @@ ChromeUtils.defineModuleGetter( // e.g. nsNavHistory::CheckIsRecentEvent, but with a lower threshold value). const RECENT_DATA_THRESHOLD = 5 * 1000000; -var Manager = { +function getBrowser(bc) { + return bc.top.embedderElement; +} + +var WebNavigationManager = { // Map[string -> Map[listener -> URLFilter]] listeners: new Map(), @@ -46,11 +55,6 @@ var Manager = { // browser -> tabTransitionData this.recentTabTransitionData = new WeakMap(); - // Collect the pending created navigation target events that still have to - // pair the message received from the source tab to the one received from - // the new tab. - this.createdNavigationTargetByOuterWindowId = new Map(); - Services.obs.addObserver(this, "urlbar-user-start-navigation", true); Services.obs.addObserver(this, "webNavigation-createdNavigationTarget"); @@ -58,17 +62,6 @@ var Manager = { if (AppConstants.MOZ_BUILD_APP == "browser") { ClickHandlerParent.addContentClickListener(this); } - - Services.mm.addMessageListener("Extension:DOMContentLoaded", this); - Services.mm.addMessageListener("Extension:StateChange", this); - Services.mm.addMessageListener("Extension:DocumentChange", this); - Services.mm.addMessageListener("Extension:HistoryChange", this); - Services.mm.addMessageListener("Extension:CreatedNavigationTarget", this); - - Services.mm.loadFrameScript( - "resource://gre/modules/WebNavigationContent.js", - true - ); }, uninit() { @@ -80,22 +73,7 @@ var Manager = { ClickHandlerParent.removeContentClickListener(this); } - Services.mm.removeMessageListener("Extension:StateChange", this); - Services.mm.removeMessageListener("Extension:DocumentChange", this); - Services.mm.removeMessageListener("Extension:HistoryChange", this); - Services.mm.removeMessageListener("Extension:DOMContentLoaded", this); - Services.mm.removeMessageListener( - "Extension:CreatedNavigationTarget", - this - ); - - Services.mm.removeDelayedFrameScript( - "resource://gre/modules/WebNavigationContent.js" - ); - Services.mm.broadcastAsyncMessage("Extension:DisableWebNavigation"); - this.recentTabTransitionData = new WeakMap(); - this.createdNavigationTargetByOuterWindowId.clear(); }, addListener(type, listener, filters, context) { @@ -130,6 +108,7 @@ var Manager = { * to keep track of the urlbar user interaction. */ QueryInterface: ChromeUtils.generateQI([ + "extIWebNavigation", "nsIObserver", "nsISupportsWeakReference", ]), @@ -156,16 +135,11 @@ var Manager = { sourceTabBrowser, } = subject.wrappedJSObject; - this.fire( - "onCreatedNavigationTarget", - createdTabBrowser, - {}, - { - sourceTabBrowser, - sourceFrameId: sourceFrameID, - url, - } - ); + this.fire("onCreatedNavigationTarget", createdTabBrowser, null, { + sourceTabBrowser, + sourceFrameId: sourceFrameID, + url, + }); } }, @@ -295,34 +269,6 @@ var Manager = { return data; }, - /** - * Receive messages from the WebNavigationContent.js framescript - * over message manager events. - */ - receiveMessage({ name, data, target }) { - switch (name) { - case "Extension:StateChange": - this.onStateChange(target, data); - break; - - case "Extension:DocumentChange": - this.onDocumentChange(target, data); - break; - - case "Extension:HistoryChange": - this.onHistoryChange(target, data); - break; - - case "Extension:DOMContentLoaded": - this.onLoad(target, data); - break; - - case "Extension:CreatedNavigationTarget": - this.onCreatedNavigationTarget(target, data); - break; - } - }, - onContentClick(target, data) { // We are interested only on clicks to links which are not "add to bookmark" commands if (data.href && !data.bookmark) { @@ -334,128 +280,101 @@ var Manager = { } }, - onCreatedNavigationTarget(browser, data) { - const { createdOuterWindowId, isSourceTab, sourceFrameId, url } = data; - - // We are going to receive two message manager messages for a single - // onCreatedNavigationTarget event related to a window.open that is happening - // in the child process (one from the source tab and one from the created tab), - // the unique createdWindowId (the outerWindowID of the created docShell) - // to pair them together. - const pairedMessage = this.createdNavigationTargetByOuterWindowId.get( - createdOuterWindowId - ); - - if (!isSourceTab) { - if (pairedMessage) { - // This should not happen, print a warning before overwriting the unexpected pending data. - Services.console.logStringMessage( - `Discarding onCreatedNavigationTarget for ${createdOuterWindowId}: ` + - "unexpected pending data while receiving the created tab data" - ); - } - - // Store a weak reference to the browser XUL element, so that we don't prevent - // it to be garbage collected if it has been destroyed. - const browserWeakRef = Cu.getWeakReference(browser); - - this.createdNavigationTargetByOuterWindowId.set(createdOuterWindowId, { - browserWeakRef, - data, - }); - + onCreatedNavigationTarget(bc, sourceBC, url) { + if (!this.listeners.size) { return; } - if (!pairedMessage) { - // The sourceTab should always be received after the message coming from the created - // top level frame because the "webNavigation-createdNavigationTarget-from-js" observers - // subscribed by WebNavigationContent.js are going to be executed in reverse order - // (See http://searchfox.org/mozilla-central/rev/f54c1723be/xpcom/ds/nsObserverList.cpp#76) - // and the observer subscribed to the created target will be the last one subscribed - // to the ObserverService (and the first one to be triggered). - Services.console.logStringMessage( - `Discarding onCreatedNavigationTarget for ${createdOuterWindowId}: ` + - "received source tab data without any created tab data available" - ); + let browser = getBrowser(bc); - return; - } - - this.createdNavigationTargetByOuterWindowId.delete(createdOuterWindowId); - - let sourceTabBrowser = browser; - let createdTabBrowser = pairedMessage.browserWeakRef.get(); - - if (!createdTabBrowser) { - Services.console.logStringMessage( - `Discarding onCreatedNavigationTarget for ${createdOuterWindowId}: ` + - "the created tab has been already destroyed" - ); - - return; - } - - this.fire( - "onCreatedNavigationTarget", - createdTabBrowser, - {}, - { - sourceTabBrowser, - sourceFrameId, - url, - } - ); + this.fire("onCreatedNavigationTarget", browser, null, { + sourceTabBrowser: getBrowser(sourceBC), + sourceFrameId: WebNavigationFrames.getFrameId(sourceBC), + url, + }); }, - onStateChange(browser, data) { - let stateFlags = data.stateFlags; + onStateChange(bc, requestURI, status, stateFlags) { + if (!this.listeners.size) { + return; + } + + let browser = getBrowser(bc); + if (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) { - let url = data.requestURL; + let url = requestURI.spec; if (stateFlags & Ci.nsIWebProgressListener.STATE_START) { - this.fire("onBeforeNavigate", browser, data, { url }); + this.fire("onBeforeNavigate", browser, bc, { url }); } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) { - if (Components.isSuccessCode(data.status)) { - this.fire("onCompleted", browser, data, { url }); + if (Components.isSuccessCode(status)) { + this.fire("onCompleted", browser, bc, { url }); } else { - let error = `Error code ${data.status}`; - this.fire("onErrorOccurred", browser, data, { error, url }); + let error = `Error code ${status}`; + this.fire("onErrorOccurred", browser, bc, { error, url }); } } } }, - onDocumentChange(browser, data) { + onDocumentChange(bc, frameTransitionData, location) { + if (!this.listeners.size) { + return; + } + + let browser = getBrowser(bc); + let extra = { - url: data.location, + url: location ? location.spec : "", // Transition data which is coming from the content process. - frameTransitionData: data.frameTransitionData, + frameTransitionData, tabTransitionData: this.getAndForgetRecentTabTransitionData(browser), }; - this.fire("onCommitted", browser, data, extra); + this.fire("onCommitted", browser, bc, extra); }, - onHistoryChange(browser, data) { + onHistoryChange( + bc, + frameTransitionData, + location, + isHistoryStateUpdated, + isReferenceFragmentUpdated + ) { + if (!this.listeners.size) { + return; + } + + let browser = getBrowser(bc); + let extra = { - url: data.location, + url: location ? location.spec : "", // Transition data which is coming from the content process. - frameTransitionData: data.frameTransitionData, + frameTransitionData, tabTransitionData: this.getAndForgetRecentTabTransitionData(browser), }; - if (data.isReferenceFragmentUpdated) { - this.fire("onReferenceFragmentUpdated", browser, data, extra); - } else if (data.isHistoryStateUpdated) { - this.fire("onHistoryStateUpdated", browser, data, extra); + if (isReferenceFragmentUpdated) { + this.fire("onReferenceFragmentUpdated", browser, bc, extra); + } else if (isHistoryStateUpdated) { + this.fire("onHistoryStateUpdated", browser, bc, extra); } }, - onLoad(browser, data) { - this.fire("onDOMContentLoaded", browser, data, { url: data.url }); + onDOMContentLoaded(bc, documentURI) { + if (!this.listeners.size) { + return; + } + + let browser = getBrowser(bc); + + this.fire("onDOMContentLoaded", browser, bc, { url: documentURI.spec }); }, - fire(type, browser, data, extra) { + fire(type, browser, bc, extra) { + if (!browser) { + return; + } + let listeners = this.listeners.get(type); if (!listeners) { return; @@ -463,11 +382,11 @@ var Manager = { let details = { browser, - frameId: data.frameId, }; - if (data.parentFrameId !== undefined) { - details.parentFrameId = data.parentFrameId; + if (bc) { + details.frameId = WebNavigationFrames.getFrameId(bc); + details.parentFrameId = WebNavigationFrames.getParentFrameId(bc); } for (let prop in extra) { @@ -505,7 +424,13 @@ var WebNavigation = {}; for (let event of EVENTS) { WebNavigation[event] = { - addListener: Manager.addListener.bind(Manager, event), - removeListener: Manager.removeListener.bind(Manager, event), + addListener: WebNavigationManager.addListener.bind( + WebNavigationManager, + event + ), + removeListener: WebNavigationManager.removeListener.bind( + WebNavigationManager, + event + ), }; } diff --git a/toolkit/components/extensions/WebNavigationContent.js b/toolkit/components/extensions/WebNavigationContent.js deleted file mode 100644 index 8484f104f81b..000000000000 --- a/toolkit/components/extensions/WebNavigationContent.js +++ /dev/null @@ -1,408 +0,0 @@ -/* 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"; - -/* eslint-env mozilla/frame-script */ - -const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); - -ChromeUtils.defineModuleGetter( - this, - "WebNavigationFrames", - "resource://gre/modules/WebNavigationFrames.jsm" -); - -function loadListener(event) { - let document = event.target; - let window = document.defaultView; - let url = document.documentURI; - let frameId = WebNavigationFrames.getFrameId(window); - let parentFrameId = WebNavigationFrames.getParentFrameId(window); - sendAsyncMessage("Extension:DOMContentLoaded", { - frameId, - parentFrameId, - url, - }); -} - -addEventListener("DOMContentLoaded", loadListener); -addMessageListener("Extension:DisableWebNavigation", () => { - removeEventListener("DOMContentLoaded", loadListener); -}); - -var CreatedNavigationTargetListener = { - QueryInterface: ChromeUtils.generateQI([ - "nsIObserver", - "nsISupportsWeakReference", - ]), - - init() { - Services.obs.addObserver( - this, - "webNavigation-createdNavigationTarget-from-js" - ); - }, - uninit() { - Services.obs.removeObserver( - this, - "webNavigation-createdNavigationTarget-from-js" - ); - }, - - observe(subject, topic, data) { - if (!(subject instanceof Ci.nsIPropertyBag2)) { - return; - } - - let props = subject.QueryInterface(Ci.nsIPropertyBag2); - - const createdDocShell = props.getPropertyAsInterface( - "createdTabDocShell", - Ci.nsIDocShell - ); - const sourceDocShell = props.getPropertyAsInterface( - "sourceTabDocShell", - Ci.nsIDocShell - ); - - const isSourceTabDescendant = - sourceDocShell.sameTypeRootTreeItem === docShell; - - if ( - docShell !== createdDocShell && - docShell !== sourceDocShell && - !isSourceTabDescendant - ) { - // if the createdNavigationTarget is not related to this docShell - // (this docShell is not the newly created docShell, it is not the source docShell, - // and the source docShell is not a descendant of it) - // there is nothing to do here and return early. - return; - } - - const isSourceTab = docShell === sourceDocShell || isSourceTabDescendant; - - const sourceFrameId = WebNavigationFrames.getFrameId( - sourceDocShell.browsingContext - ); - const createdOuterWindowId = sourceDocShell?.outerWindowID; - - let url; - if (props.hasKey("url")) { - url = props.getPropertyAsACString("url"); - } - - sendAsyncMessage("Extension:CreatedNavigationTarget", { - url, - sourceFrameId, - createdOuterWindowId, - isSourceTab, - }); - }, -}; - -var FormSubmitListener = { - init() { - this.formSubmitWindows = new WeakSet(); - addEventListener("DOMFormBeforeSubmit", this); - }, - - uninit() { - removeEventListener("DOMFormBeforeSubmit", this); - this.formSubmitWindows = new WeakSet(); - }, - - handleEvent({ target: form }) { - this.formSubmitWindows.add(form.ownerGlobal); - }, - - hasAndForget: function(window) { - let has = this.formSubmitWindows.has(window); - this.formSubmitWindows.delete(window); - return has; - }, -}; - -/** - * A generator function which iterates over a docShell tree, given a root docShell. - * - * @param {nsIDocShell} docShell - the root docShell object - * @returns {Iterator} - */ -function iterateDocShellTree(docShell) { - return docShell.getAllDocShellsInSubtree( - docShell.typeContent, - docShell.ENUMERATE_FORWARDS - ); -} - -var WebProgressListener = { - init: function() { - // This WeakMap (DOMWindow -> nsIURI) keeps track of the pathname and hash - // of the previous location for all the existent docShells. - this.previousURIMap = new WeakMap(); - - // Populate the above previousURIMap by iterating over the docShells tree. - for (let currentDocShell of iterateDocShellTree(docShell)) { - let win = currentDocShell.domWindow; - let { currentURI } = currentDocShell.QueryInterface(Ci.nsIWebNavigation); - - this.previousURIMap.set(win, currentURI); - } - - // This WeakSet of DOMWindows keeps track of the attempted refresh. - this.refreshAttemptedDOMWindows = new WeakSet(); - - let webProgress = docShell - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebProgress); - webProgress.addProgressListener( - this, - Ci.nsIWebProgress.NOTIFY_STATE_WINDOW | - Ci.nsIWebProgress.NOTIFY_REFRESH | - Ci.nsIWebProgress.NOTIFY_LOCATION - ); - }, - - uninit() { - if (!docShell) { - return; - } - let webProgress = docShell - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebProgress); - webProgress.removeProgressListener(this); - }, - - onRefreshAttempted: function onRefreshAttempted( - webProgress, - URI, - delay, - sameURI - ) { - this.refreshAttemptedDOMWindows.add(webProgress.DOMWindow); - - // If this function doesn't return true, the attempted refresh will be blocked. - return true; - }, - - onStateChange: function onStateChange( - webProgress, - request, - stateFlags, - status - ) { - let { originalURI, URI: locationURI } = request.QueryInterface( - Ci.nsIChannel - ); - - // Prevents "about", "chrome", "resource" and "moz-extension" URI schemes to be - // reported with the resolved "file" or "jar" URIs. (see Bug 1246125 for rationale) - if (locationURI.schemeIs("file") || locationURI.schemeIs("jar")) { - let shouldUseOriginalURI = - originalURI.schemeIs("about") || - originalURI.schemeIs("chrome") || - originalURI.schemeIs("resource") || - originalURI.schemeIs("moz-extension"); - - locationURI = shouldUseOriginalURI ? originalURI : locationURI; - } - - this.sendStateChange({ webProgress, locationURI, stateFlags, status }); - - // Based on the docs of the webNavigation.onCommitted event, it should be raised when: - // "The document might still be downloading, but at least part of - // the document has been received" - // and for some reason we don't fire onLocationChange for the - // initial navigation of a sub-frame. - // For the above two reasons, when the navigation event is related to - // a sub-frame we process the document change here and - // then send an "Extension:DocumentChange" message to the main process, - // where it will be turned into a webNavigation.onCommitted event. - // (see Bug 1264936 and Bug 125662 for rationale) - if ( - webProgress.DOMWindow.top != webProgress.DOMWindow && - stateFlags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT - ) { - this.sendDocumentChange({ webProgress, locationURI, request }); - } - }, - - onLocationChange: function onLocationChange( - webProgress, - request, - locationURI, - flags - ) { - let { DOMWindow } = webProgress; - - // Get the previous URI loaded in the DOMWindow. - let previousURI = this.previousURIMap.get(DOMWindow); - - // Update the URI in the map with the new locationURI. - this.previousURIMap.set(DOMWindow, locationURI); - - let isSameDocument = - flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT; - - // When a frame navigation doesn't change the current loaded document - // (which can be due to history.pushState/replaceState or to a changed hash in the url), - // it is reported only to the onLocationChange, for this reason - // we process the history change here and then we are going to send - // an "Extension:HistoryChange" to the main process, where it will be turned - // into a webNavigation.onHistoryStateUpdated/onReferenceFragmentUpdated event. - if (isSameDocument) { - this.sendHistoryChange({ - webProgress, - previousURI, - locationURI, - request, - }); - } else if (webProgress.DOMWindow.top == webProgress.DOMWindow) { - // We have to catch the document changes from top level frames here, - // where we can detect the "server redirect" transition. - // (see Bug 1264936 and Bug 125662 for rationale) - this.sendDocumentChange({ webProgress, locationURI, request }); - } - }, - - sendStateChange({ webProgress, locationURI, stateFlags, status }) { - let data = { - requestURL: locationURI.spec, - frameId: WebNavigationFrames.getFrameId(webProgress.DOMWindow), - parentFrameId: WebNavigationFrames.getParentFrameId( - webProgress.DOMWindow - ), - status, - stateFlags, - }; - - sendAsyncMessage("Extension:StateChange", data); - }, - - sendDocumentChange({ webProgress, locationURI, request }) { - let { loadType, DOMWindow } = webProgress; - let frameTransitionData = this.getFrameTransitionData({ - loadType, - request, - DOMWindow, - }); - - let data = { - frameTransitionData, - location: locationURI ? locationURI.spec : "", - frameId: WebNavigationFrames.getFrameId(webProgress.DOMWindow), - parentFrameId: WebNavigationFrames.getParentFrameId( - webProgress.DOMWindow - ), - }; - - sendAsyncMessage("Extension:DocumentChange", data); - }, - - sendHistoryChange({ webProgress, previousURI, locationURI, request }) { - let { loadType, DOMWindow } = webProgress; - - let isHistoryStateUpdated = false; - let isReferenceFragmentUpdated = false; - - let pathChanged = !( - previousURI && locationURI.equalsExceptRef(previousURI) - ); - let hashChanged = !(previousURI && previousURI.ref == locationURI.ref); - - // When the location changes but the document is the same: - // - path not changed and hash changed -> |onReferenceFragmentUpdated| - // (even if it changed using |history.pushState|) - // - path not changed and hash not changed -> |onHistoryStateUpdated| - // (only if it changes using |history.pushState|) - // - path changed -> |onHistoryStateUpdated| - - if (!pathChanged && hashChanged) { - isReferenceFragmentUpdated = true; - } else if (loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE) { - isHistoryStateUpdated = true; - } else if (loadType & Ci.nsIDocShell.LOAD_CMD_HISTORY) { - isHistoryStateUpdated = true; - } - - if (isHistoryStateUpdated || isReferenceFragmentUpdated) { - let frameTransitionData = this.getFrameTransitionData({ - loadType, - request, - DOMWindow, - }); - - let data = { - frameTransitionData, - isHistoryStateUpdated, - isReferenceFragmentUpdated, - location: locationURI ? locationURI.spec : "", - frameId: WebNavigationFrames.getFrameId(webProgress.DOMWindow), - parentFrameId: WebNavigationFrames.getParentFrameId( - webProgress.DOMWindow - ), - }; - - sendAsyncMessage("Extension:HistoryChange", data); - } - }, - - getFrameTransitionData({ loadType, request, DOMWindow }) { - let frameTransitionData = {}; - - if (loadType & Ci.nsIDocShell.LOAD_CMD_HISTORY) { - frameTransitionData.forward_back = true; - } - - if (loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD) { - frameTransitionData.reload = true; - } - - if (request instanceof Ci.nsIChannel) { - if (request.loadInfo.redirectChain.length) { - frameTransitionData.server_redirect = true; - } - } - - if (FormSubmitListener.hasAndForget(DOMWindow)) { - frameTransitionData.form_submit = true; - } - - if (this.refreshAttemptedDOMWindows.has(DOMWindow)) { - this.refreshAttemptedDOMWindows.delete(DOMWindow); - frameTransitionData.client_redirect = true; - } - - return frameTransitionData; - }, - - QueryInterface: ChromeUtils.generateQI([ - "nsIWebProgressListener", - "nsIWebProgressListener2", - "nsISupportsWeakReference", - ]), -}; - -var disabled = false; -WebProgressListener.init(); -FormSubmitListener.init(); -CreatedNavigationTargetListener.init(); -addEventListener("unload", () => { - if (!disabled) { - disabled = true; - WebProgressListener.uninit(); - FormSubmitListener.uninit(); - CreatedNavigationTargetListener.uninit(); - } -}); -addMessageListener("Extension:DisableWebNavigation", () => { - if (!disabled) { - disabled = true; - WebProgressListener.uninit(); - FormSubmitListener.uninit(); - CreatedNavigationTargetListener.uninit(); - } -}); diff --git a/toolkit/components/extensions/extIWebNavigation.idl b/toolkit/components/extensions/extIWebNavigation.idl new file mode 100644 index 000000000000..24b43ece45f5 --- /dev/null +++ b/toolkit/components/extensions/extIWebNavigation.idl @@ -0,0 +1,35 @@ +/* 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/. */ + +#include "nsISupports.idl" + +webidl BrowsingContext; +interface nsIURI; + +[scriptable, uuid(5cc10dac-cab3-41dd-b4ce-55e27c43cc40)] +interface extIWebNavigation : nsISupports +{ + void onDocumentChange(in BrowsingContext bc, + in jsval transitionData, + in nsIURI location); + + void onHistoryChange(in BrowsingContext bc, + in jsval transitionData, + in nsIURI location, + in bool isHistoryStateUpdated, + in bool isReferenceFragmentUpdated); + + void onStateChange(in BrowsingContext bc, + in nsIURI requestURI, + in nsresult status, + in unsigned long stateFlags); + + void onCreatedNavigationTarget(in BrowsingContext bc, + in BrowsingContext sourceBC, + in ACString url); + + void onDOMContentLoaded(in BrowsingContext bc, + in nsIURI documentURI); +}; + diff --git a/toolkit/components/extensions/moz.build b/toolkit/components/extensions/moz.build index 7037b43537f3..0b535592dab7 100755 --- a/toolkit/components/extensions/moz.build +++ b/toolkit/components/extensions/moz.build @@ -43,7 +43,6 @@ EXTRA_JS_MODULES += [ "ProxyChannelFilter.jsm", "Schemas.jsm", "WebNavigation.jsm", - "WebNavigationContent.js", "WebNavigationFrames.jsm", ] @@ -74,6 +73,7 @@ IPDL_SOURCES += [ ] XPIDL_SOURCES += [ + "extIWebNavigation.idl", "mozIExtensionProcessScript.idl", ]