зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1581859: Part 4f - Migrate to the native WebNavigation implementation. r=zombie
This migrates WebNavigation.jsm to use messages from PExtensionsParent generated by the native WebNavigationContent class, and removes the now-unused WebNavigationContent.js frame script. Differential Revision: https://phabricator.services.mozilla.com/D103217
This commit is contained in:
Родитель
dad37640d8
Коммит
28f9b0d638
|
@ -94,8 +94,6 @@ add_task(async function test_window_open_in_named_win() {
|
|||
},
|
||||
});
|
||||
|
||||
assertNoPendingCreatedNavigationTargetData();
|
||||
|
||||
BrowserTestUtils.removeTab(tab1);
|
||||
|
||||
await extension.unload();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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<JS::Value> ToJSBoolean(bool aValue) {
|
||||
|
@ -53,6 +67,8 @@ ipc::IPCResult ExtensionsParent::RecvDocumentChange(
|
|||
|
||||
JS::Rooted<JS::Value> transitionData(
|
||||
dom::RootingCx(), FrameTransitionDataToJSValue(aTransitionData));
|
||||
|
||||
WebNavigation()->OnDocumentChange(aBC.get(), transitionData, aLocation);
|
||||
return IPC_OK();
|
||||
}
|
||||
|
||||
|
@ -66,6 +82,10 @@ ipc::IPCResult ExtensionsParent::RecvHistoryChange(
|
|||
|
||||
JS::Rooted<JS::Value> 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<extIWebNavigation> mWebNavigation;
|
||||
|
||||
protected:
|
||||
void ActorDestroy(ActorDestroyReason aWhy) override;
|
||||
|
|
|
@ -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
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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<nsIDocShell>}
|
||||
*/
|
||||
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();
|
||||
}
|
||||
});
|
|
@ -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);
|
||||
};
|
||||
|
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче