waiter waiter more webextensions please

This commit is contained in:
wuggy 2026-09-14 14:37:34 -07:00
commit bb0adb61ae
10 changed files with 198 additions and 8 deletions

View file

@ -115,6 +115,20 @@ function WebNavigationEventManager(context, eventName) {
parentFrameId: ExtensionManagement.getParentFrameId(data.parentWindowId, data.windowId),
};
if (eventName == "onCreatedNavigationTarget") {
let source = {};
extensions.emit("fill-browser-data", data.sourceTabBrowser, source);
if (!(source.tabId >= 0)) {
return;
}
delete data2.frameId;
delete data2.parentFrameId;
data2.sourceTabId = source.tabId;
data2.sourceFrameId = ExtensionManagement.getFrameId(data.sourceWindowId);
// Firefox does not expose renderer process IDs through this API.
data2.sourceProcessId = -1;
}
if (eventName == "onErrorOccurred") {
data2.error = data.error;
}
@ -162,7 +176,7 @@ extensions.registerSchemaAPI("webNavigation", "addon_parent", context => {
onErrorOccurred: new WebNavigationEventManager(context, "onErrorOccurred").api(),
onReferenceFragmentUpdated: new WebNavigationEventManager(context, "onReferenceFragmentUpdated").api(),
onHistoryStateUpdated: new WebNavigationEventManager(context, "onHistoryStateUpdated").api(),
onCreatedNavigationTarget: ignoreEvent(context, "webNavigation.onCreatedNavigationTarget"),
onCreatedNavigationTarget: new WebNavigationEventManager(context, "onCreatedNavigationTarget").api(),
getAllFrames(details) {
let tab = TabManager.getTab(details.tabId, context);

View file

@ -284,7 +284,6 @@
},
{
"name": "onCreatedNavigationTarget",
"unsupported": true,
"type": "function",
"description": "Fired when a new window, or a new tab in an existing window, is created to host a navigation.",
"parameters": [

View file

@ -12,6 +12,7 @@ const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
"resource:///modules/RecentWindow.jsm");
@ -21,8 +22,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
// e.g. nsNavHistory::CheckIsRecentEvent, but with a lower threshold value).
const RECENT_DATA_THRESHOLD = 5 * 1000000;
// TODO:
// onCreatedNavigationTarget
var Manager = {
// Map[string -> Map[listener -> URLFilter]]
@ -32,7 +31,10 @@ var Manager = {
// Collect recent tab transition data in a WeakMap:
// browser -> tabTransitionData
this.recentTabTransitionData = new WeakMap();
this.createdNavigationTargetByOuterWindowId = new Map();
Services.obs.addObserver(this, "autocomplete-did-enter-text", true);
Services.obs.addObserver(this, "webNavigation-createdNavigationTarget", false);
Services.mm.addMessageListener("Extension:CreatedNavigationTarget", this);
Services.mm.addMessageListener("Content:Click", this);
Services.mm.addMessageListener("Extension:DOMContentLoaded", this);
@ -45,7 +47,13 @@ var Manager = {
uninit() {
// Stop collecting recent tab transition data and reset the WeakMap.
Services.obs.removeObserver(this, "autocomplete-did-enter-text", true);
Services.obs.removeObserver(this, "autocomplete-did-enter-text");
Services.obs.removeObserver(this, "webNavigation-createdNavigationTarget");
Services.mm.removeMessageListener("Extension:CreatedNavigationTarget", this);
for (let pending of this.createdNavigationTargetByOuterWindowId.values()) {
clearTimeout(pending.timer);
}
this.createdNavigationTargetByOuterWindowId.clear();
this.recentTabTransitionData = new WeakMap();
Services.mm.removeMessageListener("Content:Click", this);
@ -102,6 +110,22 @@ var Manager = {
observe: function(subject, topic, data) {
if (topic == "autocomplete-did-enter-text") {
this.onURLBarAutoCompletion(subject);
} else if (topic == "webNavigation-createdNavigationTarget") {
// The observed notification is coming from privileged JavaScript components running
// in the main process (e.g. when a new tab or window is opened using the context menu
// or Ctrl/Shift + click on a link).
const {
createdTabBrowser,
url,
sourceFrameOuterWindowID,
sourceTabBrowser,
} = subject.wrappedJSObject;
this.fire("onCreatedNavigationTarget", createdTabBrowser, {}, {
sourceTabBrowser,
sourceWindowId: sourceFrameOuterWindowID,
url,
});
}
},
@ -241,6 +265,9 @@ var Manager = {
*/
receiveMessage({name, data, target}) {
switch (name) {
case "Extension:CreatedNavigationTarget":
this.onCreatedNavigationTarget(target, data);
break;
case "Extension:StateChange":
this.onStateChange(target, data);
break;
@ -274,6 +301,44 @@ var Manager = {
}
},
onCreatedNavigationTarget(browser, data) {
const {isSourceTab, createdWindowId, sourceWindowId, url} = data;
// Source and target frame scripts identify their browsers independently.
// Pair their messages by the new window's outer ID, in either arrival order.
const pairedMessage = this.createdNavigationTargetByOuterWindowId.get(createdWindowId);
if (!pairedMessage) {
// A tab can close before its frame script reports. Do not retain it forever.
let timer = setTimeout(() => {
this.createdNavigationTargetByOuterWindowId.delete(createdWindowId);
}, 30000);
this.createdNavigationTargetByOuterWindowId.set(createdWindowId, {browser, data, timer});
return;
}
if (pairedMessage.data.isSourceTab == isSourceTab) {
return;
}
clearTimeout(pairedMessage.timer);
this.createdNavigationTargetByOuterWindowId.delete(createdWindowId);
let sourceTabBrowser;
let createdTabBrowser;
if (isSourceTab) {
sourceTabBrowser = browser;
createdTabBrowser = pairedMessage.browser;
} else {
sourceTabBrowser = pairedMessage.browser;
createdTabBrowser = browser;
}
this.fire("onCreatedNavigationTarget", createdTabBrowser, {}, {
sourceTabBrowser, sourceWindowId, url,
});
},
onStateChange(browser, data) {
let stateFlags = data.stateFlags;
if (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
@ -357,7 +422,7 @@ const EVENTS = [
"onErrorOccurred",
"onReferenceFragmentUpdated",
"onHistoryStateUpdated",
// "onCreatedNavigationTarget",
"onCreatedNavigationTarget",
];
var WebNavigation = {};

View file

@ -23,6 +23,55 @@ addMessageListener("Extension:DisableWebNavigation", () => {
removeEventListener("DOMContentLoaded", loadListener);
});
var CreatedNavigationTargetListener = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
init() {
Services.obs.addObserver(this, "webNavigation-createdNavigationTarget-from-js", false);
},
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 sourceWindowId = WebNavigationFrames.getWindowId(sourceDocShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow));
const createdWindowId = WebNavigationFrames.getWindowId(createdDocShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow));
let url = "about:blank";
if (props.hasKey("url")) {
url = props.getPropertyAsACString("url");
}
sendAsyncMessage("Extension:CreatedNavigationTarget", {
url,
sourceWindowId,
createdWindowId,
isSourceTab,
});
},
};
var FormSubmitListener = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsIFormSubmitObserver,
@ -256,11 +305,13 @@ var WebProgressListener = {
var disabled = false;
WebProgressListener.init();
FormSubmitListener.init();
CreatedNavigationTargetListener.init();
addEventListener("unload", () => {
if (!disabled) {
disabled = true;
WebProgressListener.uninit();
FormSubmitListener.uninit();
CreatedNavigationTargetListener.uninit();
}
});
addMessageListener("Extension:DisableWebNavigation", () => {
@ -268,5 +319,6 @@ addMessageListener("Extension:DisableWebNavigation", () => {
disabled = true;
WebProgressListener.uninit();
FormSubmitListener.uninit();
CreatedNavigationTargetListener.uninit();
}
});