feat: add functionality to load overlays into browser files
(cherry picked from commit 7561b2434a3258c302ba7a0573bb97831ba5aa64)
This commit is contained in:
@@ -6,6 +6,62 @@
|
||||
|
||||
const EXPORTED_SYMBOLS = ["WaterfoxGlue"];
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
ChromeManifest: "resource:///modules/ChromeManifest.jsm",
|
||||
Overlays: "resource:///modules/Overlays.jsm",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
|
||||
|
||||
const WaterfoxGlue = {
|
||||
init() {},
|
||||
async init() {
|
||||
// Parse chrome.manifest
|
||||
this.startupManifest = await this.getChromeManifest("startup");
|
||||
|
||||
// Observe chrome-document-loaded topic to detect window open
|
||||
Services.obs.addObserver(this, "chrome-document-loaded");
|
||||
},
|
||||
|
||||
async getChromeManifest(manifest) {
|
||||
let uri;
|
||||
switch (manifest) {
|
||||
case "startup":
|
||||
uri = "resource://waterfox/overlays/chrome.manifest";
|
||||
break;
|
||||
}
|
||||
let chromeManifest = new ChromeManifest(async () => {
|
||||
let res = await fetch(uri);
|
||||
return res.text();
|
||||
}, this.options);
|
||||
await chromeManifest.parse();
|
||||
return chromeManifest;
|
||||
},
|
||||
|
||||
options: {
|
||||
application: Services.appinfo.ID,
|
||||
appversion: Services.appinfo.version,
|
||||
platformversion: Services.appinfo.platformVersion,
|
||||
os: Services.appinfo.OS,
|
||||
osversion: Services.sysinfo.getProperty("version"),
|
||||
abi: Services.appinfo.XPCOMABI,
|
||||
},
|
||||
|
||||
async observe(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "chrome-document-loaded":
|
||||
// Only load overlays in new browser windows
|
||||
// baseURI for about:blank is also browser.xhtml, so use URL
|
||||
if (subject.URL.includes("browser.xhtml")) {
|
||||
const window = subject.defaultView;
|
||||
Overlays.load(this.startupManifest, window);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
0
waterfox/browser/components/chrome.manifest
Normal file
0
waterfox/browser/components/chrome.manifest
Normal file
3
waterfox/browser/components/jar.mn
Normal file
3
waterfox/browser/components/jar.mn
Normal file
@@ -0,0 +1,3 @@
|
||||
browser.jar:
|
||||
% resource waterfox %waterfox/ contentaccessible=yes
|
||||
waterfox/overlays/chrome.manifest (chrome.manifest)
|
||||
@@ -7,3 +7,5 @@
|
||||
EXTRA_JS_MODULES += [
|
||||
"WaterfoxGlue.jsm",
|
||||
]
|
||||
|
||||
JAR_MANIFESTS += ["jar.mn"]
|
||||
|
||||
@@ -1,33 +1,23 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Load overlays in a similar way as XUL did for legacy XUL add-ons.
|
||||
*/
|
||||
|
||||
/* exported Overlays */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Overlays"];
|
||||
const EXPORTED_SYMBOLS = ["Overlays"];
|
||||
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const lazy = {};
|
||||
|
||||
const { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm");
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"Services",
|
||||
"resource://gre/modules/Services.jsm"
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
lazy,
|
||||
"setTimeout",
|
||||
"resource://gre/modules/Timer.jsm"
|
||||
);
|
||||
|
||||
let oconsole = new ConsoleAPI({
|
||||
prefix: "Overlays.jsm",
|
||||
consoleID: "overlays-jsm",
|
||||
maxLogLevelPref: "extensions.overlayloader.loglevel",
|
||||
});
|
||||
|
||||
/**
|
||||
* The overlays class, providing support for loading overlays like they used to work. This class
|
||||
* should likely be called through its static method Overlays.load()
|
||||
@@ -42,10 +32,11 @@ class Overlays {
|
||||
* @param {DOMWindow} window The window to load into
|
||||
*/
|
||||
static load(overlayProvider, window) {
|
||||
let instance = new Overlays(overlayProvider, window);
|
||||
const instance = new Overlays(overlayProvider, window);
|
||||
|
||||
let urls = overlayProvider.overlay.get(instance.location, false);
|
||||
instance.load(urls);
|
||||
const urls = overlayProvider.overlay.get(instance.location, false);
|
||||
console.debug(instance.location);
|
||||
return instance.load(urls);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,35 +67,36 @@ class Overlays {
|
||||
* Loads the given urls into the window, recursively loading further overlays as provided by the
|
||||
* overlayProvider.
|
||||
*
|
||||
* @param {string[]} urls The urls to load
|
||||
* @param {String[]} urls The urls to load
|
||||
*/
|
||||
load(urls) {
|
||||
let unloadedOverlays = this._collectOverlays(this.document).concat(urls);
|
||||
async load(urls) {
|
||||
const unloadedOverlays = new Set(
|
||||
this._collectOverlays(this.document).concat(urls)
|
||||
);
|
||||
let forwardReferences = [];
|
||||
let unloadedScripts = [];
|
||||
let unloadedSheets = [];
|
||||
this.unloadedScripts = [];
|
||||
const unloadedSheets = [];
|
||||
this._toolbarsToResolve = [];
|
||||
let xulStore = Services.xulStore;
|
||||
const xulStore = Services.xulStore;
|
||||
this.persistedIDs = new Set();
|
||||
|
||||
// Load css styles from the registry
|
||||
for (let sheet of this.overlayProvider.style.get(this.location, false)) {
|
||||
for (const sheet of this.overlayProvider.style.get(this.location, false)) {
|
||||
unloadedSheets.push(sheet);
|
||||
}
|
||||
|
||||
if (!unloadedOverlays.length && !unloadedSheets.length) {
|
||||
if (!unloadedOverlays.size && !unloadedSheets.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (unloadedOverlays.length) {
|
||||
let url = unloadedOverlays.shift();
|
||||
let xhr = this.fetchOverlay(url);
|
||||
let doc = xhr.responseXML;
|
||||
for (const url of unloadedOverlays) {
|
||||
unloadedOverlays.delete(url);
|
||||
const doc = await this.fetchOverlay(url);
|
||||
|
||||
oconsole.debug(`Applying ${url} to ${this.location}`);
|
||||
console.debug(`Applying ${url} to ${this.location}`);
|
||||
|
||||
// clean the document a bit
|
||||
let emptyNodes = doc.evaluate(
|
||||
const emptyNodes = doc.evaluate(
|
||||
"//text()[normalize-space(.) = '']",
|
||||
doc,
|
||||
null,
|
||||
@@ -112,23 +104,31 @@ class Overlays {
|
||||
null
|
||||
);
|
||||
for (let i = 0, len = emptyNodes.snapshotLength; i < len; ++i) {
|
||||
let node = emptyNodes.snapshotItem(i);
|
||||
const node = emptyNodes.snapshotItem(i);
|
||||
node.remove();
|
||||
}
|
||||
|
||||
let commentNodes = doc.evaluate("//comment()", doc, null, 7, null);
|
||||
const commentNodes = doc.evaluate("//comment()", doc, null, 7, null);
|
||||
for (let i = 0, len = commentNodes.snapshotLength; i < len; ++i) {
|
||||
let node = commentNodes.snapshotItem(i);
|
||||
const node = commentNodes.snapshotItem(i);
|
||||
node.remove();
|
||||
}
|
||||
|
||||
// Force a re-evaluation of inline styles to work around an issue
|
||||
// causing inline styles to be initially ignored.
|
||||
const styledNodes = doc.evaluate("//*[@style]", doc, null, 7, null);
|
||||
for (let i = 0, len = styledNodes.snapshotLength; i < len; ++i) {
|
||||
const node = styledNodes.snapshotItem(i);
|
||||
node.style.display = node.style.display; // eslint-disable-line no-self-assign
|
||||
}
|
||||
|
||||
// Load css styles from the registry
|
||||
for (let sheet of this.overlayProvider.style.get(url, false)) {
|
||||
for (const sheet of this.overlayProvider.style.get(url, false)) {
|
||||
unloadedSheets.push(sheet);
|
||||
}
|
||||
|
||||
// Load css processing instructions from the overlay
|
||||
let stylesheets = doc.evaluate(
|
||||
const stylesheets = doc.evaluate(
|
||||
"/processing-instruction('xml-stylesheet')",
|
||||
doc,
|
||||
null,
|
||||
@@ -136,33 +136,44 @@ class Overlays {
|
||||
null
|
||||
);
|
||||
for (let i = 0, len = stylesheets.snapshotLength; i < len; ++i) {
|
||||
let node = stylesheets.snapshotItem(i);
|
||||
let match = node.nodeValue.match(/href=["']([^"']*)["']/);
|
||||
const node = stylesheets.snapshotItem(i);
|
||||
const match = node.nodeValue.match(/href=["']([^"']*)["']/);
|
||||
if (match) {
|
||||
unloadedSheets.push(new URL(match[1], node.baseURI).href);
|
||||
}
|
||||
}
|
||||
|
||||
const t_unloadedOverlays = [];
|
||||
// Prepare loading further nested xul overlays from the overlay
|
||||
unloadedOverlays.push(...this._collectOverlays(doc));
|
||||
t_unloadedOverlays.push(...this._collectOverlays(doc));
|
||||
|
||||
// Prepare loading further nested xul overlays from the registry
|
||||
for (let overlayUrl of this.overlayProvider.overlay.get(url, false)) {
|
||||
unloadedOverlays.push(overlayUrl);
|
||||
for (const overlayUrl of this.overlayProvider.overlay.get(url, false)) {
|
||||
t_unloadedOverlays.push(overlayUrl);
|
||||
}
|
||||
|
||||
t_unloadedOverlays.forEach(o => unloadedOverlays.add(o));
|
||||
|
||||
// Run through all overlay nodes on the first level (hookup nodes). Scripts will be deferred
|
||||
// until later for simplicity (c++ code seems to process them earlier?).
|
||||
for (let node of doc.documentElement.children) {
|
||||
const t_forwardReferences = [];
|
||||
for (const node of doc.documentElement.children) {
|
||||
if (node.localName == "script") {
|
||||
unloadedScripts.push(node);
|
||||
this.unloadedScripts.push(node);
|
||||
} else {
|
||||
forwardReferences.push(node);
|
||||
t_forwardReferences.push(node);
|
||||
}
|
||||
}
|
||||
forwardReferences.unshift(...t_forwardReferences);
|
||||
}
|
||||
|
||||
let ids = xulStore.getIDsEnumerator(this.location);
|
||||
// We've resolved all the forward references we can, we can now go ahead and load the scripts
|
||||
this.deferredLoad = [];
|
||||
for (const script of this.unloadedScripts) {
|
||||
this.deferredLoad.push(...this.loadScript(script));
|
||||
}
|
||||
|
||||
const ids = xulStore.getIDsEnumerator(this.location);
|
||||
while (ids.hasMore()) {
|
||||
this.persistedIDs.add(ids.getNext());
|
||||
}
|
||||
@@ -172,9 +183,9 @@ class Overlays {
|
||||
let previous = 0;
|
||||
while (forwardReferences.length && forwardReferences.length != previous) {
|
||||
previous = forwardReferences.length;
|
||||
let unresolved = [];
|
||||
const unresolved = [];
|
||||
|
||||
for (let ref of forwardReferences) {
|
||||
for (const ref of forwardReferences) {
|
||||
if (!this._resolveForwardReference(ref)) {
|
||||
unresolved.push(ref);
|
||||
}
|
||||
@@ -184,76 +195,58 @@ class Overlays {
|
||||
}
|
||||
|
||||
if (forwardReferences.length) {
|
||||
oconsole.warn(
|
||||
console.warn(
|
||||
`Could not resolve ${forwardReferences.length} references`,
|
||||
forwardReferences
|
||||
);
|
||||
}
|
||||
|
||||
// Loading the sheets now to avoid race conditions with xbl bindings
|
||||
for (let sheet of unloadedSheets) {
|
||||
for (const sheet of unloadedSheets) {
|
||||
this.loadCSS(sheet);
|
||||
}
|
||||
|
||||
this._decksToResolve = new Map();
|
||||
for (let id of this.persistedIDs.values()) {
|
||||
let element = this.document.getElementById(id);
|
||||
for (const id of this.persistedIDs.values()) {
|
||||
const element = this.document.getElementById(id);
|
||||
if (element) {
|
||||
let attrNames = xulStore.getAttributeEnumerator(this.location, id);
|
||||
const attrNames = xulStore.getAttributeEnumerator(this.location, id);
|
||||
while (attrNames.hasMore()) {
|
||||
let attrName = attrNames.getNext();
|
||||
let attrValue = xulStore.getValue(this.location, id, attrName);
|
||||
const attrName = attrNames.getNext();
|
||||
const attrValue = xulStore.getValue(this.location, id, attrName);
|
||||
if (attrName == "selectedIndex" && element.localName == "deck") {
|
||||
this._decksToResolve.set(element, attrValue);
|
||||
} else {
|
||||
} else if (
|
||||
(element != this.document.documentElement ||
|
||||
!["height", "screenX", "screenY", "sizemode", "width"].includes(
|
||||
attrName
|
||||
)) &&
|
||||
element.getAttribute(attrName) != attrValue.toString()
|
||||
) {
|
||||
element.setAttribute(attrName, attrValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We've resolved all the forward references we can, we can now go ahead and load the scripts
|
||||
let deferredLoad = [];
|
||||
for (let script of unloadedScripts) {
|
||||
deferredLoad.push(...this.loadScript(script));
|
||||
}
|
||||
|
||||
if (this.document.readyState == "complete") {
|
||||
let sheet;
|
||||
let overlayTrigger = this.document.createXULElement("overlayTrigger");
|
||||
overlayTrigger.addEventListener(
|
||||
"bindingattached",
|
||||
() => {
|
||||
oconsole.debug("XBL binding attached, continuing with load");
|
||||
if (sheet) {
|
||||
sheet.remove();
|
||||
lazy.setTimeout(() => {
|
||||
this._finish();
|
||||
|
||||
// Now execute load handlers since we are done loading scripts
|
||||
const bubbles = [];
|
||||
for (const { listener, useCapture } of this.deferredLoad) {
|
||||
if (useCapture) {
|
||||
this._fireEventListener(listener);
|
||||
} else {
|
||||
bubbles.push(listener);
|
||||
}
|
||||
overlayTrigger.remove();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this._finish();
|
||||
|
||||
// Now execute load handlers since we are done loading scripts
|
||||
let bubbles = [];
|
||||
for (let { listener, useCapture } of deferredLoad) {
|
||||
if (useCapture) {
|
||||
this._fireEventListener(listener);
|
||||
} else {
|
||||
bubbles.push(listener);
|
||||
}
|
||||
}
|
||||
|
||||
for (let listener of bubbles) {
|
||||
this._fireEventListener(listener);
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
this.document.documentElement.appendChild(overlayTrigger);
|
||||
if (overlayTrigger.parentNode) {
|
||||
sheet = this.loadCSS("chrome://messenger/content/overlayBindings.css");
|
||||
}
|
||||
for (const listener of bubbles) {
|
||||
this._fireEventListener(listener);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.document.defaultView.addEventListener(
|
||||
"load",
|
||||
@@ -264,18 +257,18 @@ class Overlays {
|
||||
}
|
||||
|
||||
_finish() {
|
||||
for (let [deck, selectedIndex] of this._decksToResolve.entries()) {
|
||||
for (const [deck, selectedIndex] of this._decksToResolve.entries()) {
|
||||
deck.setAttribute("selectedIndex", selectedIndex);
|
||||
}
|
||||
|
||||
for (let bar of this._toolbarsToResolve) {
|
||||
let currentset = Services.xulStore.getValue(
|
||||
for (const bar of this._toolbarsToResolve) {
|
||||
const currentSet = Services.xulStore.getValue(
|
||||
this.location,
|
||||
bar.id,
|
||||
"currentset"
|
||||
);
|
||||
if (currentset) {
|
||||
bar.currentSet = currentset;
|
||||
if (currentSet) {
|
||||
bar.currentSet = currentSet;
|
||||
} else if (bar.getAttribute("defaultset")) {
|
||||
bar.currentSet = bar.getAttribute("defaultset");
|
||||
}
|
||||
@@ -285,12 +278,12 @@ class Overlays {
|
||||
/**
|
||||
* Gets the overlays referenced by processing instruction on a document.
|
||||
*
|
||||
* @param {DOMDocument} document The document to read instuctions from
|
||||
* @returns {string[]} URLs of the overlays from the document
|
||||
* @param {DOMDocument} document The document to read instructions from
|
||||
* @return {String[]} URLs of the overlays from the document
|
||||
*/
|
||||
_collectOverlays(doc) {
|
||||
let urls = [];
|
||||
let instructions = doc.evaluate(
|
||||
const urls = [];
|
||||
const instructions = doc.evaluate(
|
||||
"/processing-instruction('xul-overlay')",
|
||||
doc,
|
||||
null,
|
||||
@@ -298,8 +291,8 @@ class Overlays {
|
||||
null
|
||||
);
|
||||
for (let i = 0, len = instructions.snapshotLength; i < len; ++i) {
|
||||
let node = instructions.snapshotItem(i);
|
||||
let match = node.nodeValue.match(/href=["']([^"']*)["']/);
|
||||
const node = instructions.snapshotItem(i);
|
||||
const match = node.nodeValue.match(/href=["']([^"']*)["']/);
|
||||
if (match) {
|
||||
urls.push(match[1]);
|
||||
}
|
||||
@@ -313,13 +306,13 @@ class Overlays {
|
||||
* @param {EventListener|Function} listener The event listener to call
|
||||
*/
|
||||
_fireEventListener(listener) {
|
||||
let fakeEvent = new this.window.UIEvent("load", { view: this.window });
|
||||
const fakeEvent = new this.window.UIEvent("load", { view: this.window });
|
||||
if (typeof listener == "function") {
|
||||
listener(fakeEvent);
|
||||
} else if (listener && typeof listener == "object") {
|
||||
listener.handleEvent(fakeEvent);
|
||||
} else {
|
||||
oconsole.error("Unknown listener type", listener);
|
||||
console.error("Unknown listener type", listener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,36 +322,27 @@ class Overlays {
|
||||
* level.
|
||||
*
|
||||
* @param {Element} node The DOM Element to resolve in the target document.
|
||||
* @returns {boolean} True, if the node was merged/inserted, false otherwise
|
||||
* @return {Boolean} True, if the node was merged/inserted, false otherwise
|
||||
*/
|
||||
_resolveForwardReference(node) {
|
||||
if (node.id) {
|
||||
let target = this.document.getElementById(node.id);
|
||||
const target = this.document.getElementById(node.id);
|
||||
// if (node.localName == "template") {
|
||||
// this._insertElement(this.document.documentElement, node);
|
||||
// return true;
|
||||
// } else
|
||||
if (node.localName == "toolbarpalette") {
|
||||
let box;
|
||||
if (target) {
|
||||
box = target.closest("toolbox");
|
||||
} else {
|
||||
// These vanish from the document but still exist via the palette property
|
||||
let boxes = [...this.document.getElementsByTagName("toolbox")];
|
||||
box = boxes.find(box => box.palette && box.palette.id == node.id);
|
||||
let palette = box ? box.palette : null;
|
||||
|
||||
if (!palette) {
|
||||
oconsole.debug(
|
||||
`The palette for ${node.id} could not be found, deferring to later`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
target = palette;
|
||||
}
|
||||
|
||||
this._toolbarsToResolve.push(
|
||||
...box.querySelectorAll('toolbar:not([type="menubar"])')
|
||||
);
|
||||
console.error("toolbarpalette is unsupported type", node.id);
|
||||
return false;
|
||||
} else if (!target) {
|
||||
oconsole.debug(
|
||||
if (
|
||||
node.hasAttribute("insertafter") ||
|
||||
node.hasAttribute("insertbefore")
|
||||
) {
|
||||
this._insertElement(this.document.documentElement, node);
|
||||
return true;
|
||||
}
|
||||
console.debug(
|
||||
`The node ${node.id} could not be found, deferring to later`
|
||||
);
|
||||
return false;
|
||||
@@ -380,7 +364,7 @@ class Overlays {
|
||||
_insertElement(parent, node) {
|
||||
// These elements need their values set before they are added to
|
||||
// the document, or bad things happen.
|
||||
for (let element of node.querySelectorAll("menulist, radiogroup")) {
|
||||
for (const element of node.querySelectorAll("menulist")) {
|
||||
if (element.id && this.persistedIDs.has(element.id)) {
|
||||
element.setAttribute(
|
||||
"value",
|
||||
@@ -389,6 +373,17 @@ class Overlays {
|
||||
}
|
||||
}
|
||||
|
||||
if (node.localName == "toolbar") {
|
||||
this._toolbarsToResolve.push(node);
|
||||
} else {
|
||||
this._toolbarsToResolve.push(...node.querySelectorAll("toolbar"));
|
||||
}
|
||||
|
||||
const nodes = node.querySelectorAll("script");
|
||||
for (const script of nodes) {
|
||||
this.deferredLoad.push(...this.loadScript(script));
|
||||
}
|
||||
|
||||
let wasInserted = false;
|
||||
let pos = node.getAttribute("insertafter");
|
||||
let after = true;
|
||||
@@ -399,12 +394,12 @@ class Overlays {
|
||||
}
|
||||
|
||||
if (pos) {
|
||||
for (let id of pos.split(",")) {
|
||||
let targetchild = this.document.getElementById(id);
|
||||
if (targetchild && targetchild.parentNode == parent) {
|
||||
parent.insertBefore(
|
||||
for (const id of pos.split(",")) {
|
||||
const targetChild = this.document.getElementById(id);
|
||||
if (targetChild && parent.contains(targetChild.parentNode)) {
|
||||
targetChild.parentNode.insertBefore(
|
||||
node,
|
||||
after ? targetchild.nextSibling : targetchild
|
||||
after ? targetChild.nextElementSibling : targetChild
|
||||
);
|
||||
wasInserted = true;
|
||||
break;
|
||||
@@ -414,9 +409,9 @@ class Overlays {
|
||||
|
||||
if (!wasInserted) {
|
||||
// position is 1-based
|
||||
let position = parseInt(node.getAttribute("position"), 10);
|
||||
if (position > 0 && position - 1 <= parent.childNodes.length) {
|
||||
parent.insertBefore(node, parent.childNodes[position - 1]);
|
||||
const position = parseInt(node.getAttribute("position"), 10);
|
||||
if (position > 0 && position - 1 <= parent.children.length) {
|
||||
parent.insertBefore(node, parent.children[position - 1]);
|
||||
wasInserted = true;
|
||||
}
|
||||
}
|
||||
@@ -433,36 +428,65 @@ class Overlays {
|
||||
*
|
||||
* @param {Element} target The node to merge into
|
||||
* @param {Element} node The node that is being merged
|
||||
* @param {bool} shadow If the target node is in the shadow DOM
|
||||
*/
|
||||
_mergeElement(target, node) {
|
||||
for (let attribute of node.attributes) {
|
||||
if (attribute.name == "id") {
|
||||
continue;
|
||||
}
|
||||
_mergeElement(target, node, shadow = false) {
|
||||
for (const attribute of node.attributes) {
|
||||
if (attribute.name !== "id") {
|
||||
if (attribute.name == "removeelement" && attribute.value == "true") {
|
||||
target.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
if (attribute.name == "removeelement" && attribute.value == "true") {
|
||||
target.remove();
|
||||
return;
|
||||
target.setAttributeNS(
|
||||
attribute.namespaceURI,
|
||||
attribute.name,
|
||||
attribute.value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
target.setAttributeNS(
|
||||
attribute.namespaceURI,
|
||||
attribute.name,
|
||||
attribute.value
|
||||
);
|
||||
for (const nodes of node.children) {
|
||||
if (nodes.localName == "script") {
|
||||
this.deferredLoad.push(...this.loadScript(nodes));
|
||||
}
|
||||
}
|
||||
|
||||
// Get the DocumentFragment
|
||||
if (node.localName === "template") {
|
||||
node = node.content;
|
||||
}
|
||||
|
||||
for (let i = 0, len = node.childElementCount; i < len; i++) {
|
||||
let child = node.firstElementChild;
|
||||
const child = node.firstElementChild;
|
||||
child.remove();
|
||||
|
||||
let elementInDocument = child.id
|
||||
? this.document.getElementById(child.id)
|
||||
: null;
|
||||
let parentId = elementInDocument ? elementInDocument.parentNode.id : null;
|
||||
const parentId = elementInDocument
|
||||
? elementInDocument.parentNode.id
|
||||
: null;
|
||||
|
||||
// Strictly to working with shadow DOM elements
|
||||
if (target.content) {
|
||||
elementInDocument = target.content.children.namedItem(child.id);
|
||||
} else if (shadow && target.children.length === 1) {
|
||||
// This condition is very specific to adding appMenu items, if we need another
|
||||
// use case we will have to re-visit
|
||||
elementInDocument = target.children[0];
|
||||
}
|
||||
|
||||
// Need an else if nodeName is doc fragment
|
||||
if (parentId && parentId == target.id) {
|
||||
this._mergeElement(elementInDocument, child);
|
||||
} else if (
|
||||
elementInDocument &&
|
||||
(elementInDocument.parentNode.nodeName === "#document-fragment" ||
|
||||
shadow)
|
||||
) {
|
||||
// Merging for shadow DOM elements, as getElementById does not work
|
||||
this._mergeElement(elementInDocument, child, true);
|
||||
} else {
|
||||
this._insertElement(target, child);
|
||||
}
|
||||
@@ -470,11 +494,10 @@ class Overlays {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the overlay from the given chrome:// or resource:// URL. This happen synchronously so
|
||||
* we have a chance to complete before the load event.
|
||||
* Fetches the overlay from the given chrome:// or resource:// URL.
|
||||
*
|
||||
* @param {string} srcUrl The URL to load
|
||||
* @returns {XMLHttpRequest} The completed XHR.
|
||||
* @param {String} srcUrl The URL to load
|
||||
* @return {Promise<XMLDocument>} Returns a promise that is resolved with the XML document.
|
||||
*/
|
||||
fetchOverlay(srcUrl) {
|
||||
if (!srcUrl.startsWith("chrome://") && !srcUrl.startsWith("resource://")) {
|
||||
@@ -483,24 +506,28 @@ class Overlays {
|
||||
);
|
||||
}
|
||||
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.overrideMimeType("application/xml");
|
||||
xhr.open("GET", srcUrl, false);
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new this.window.XMLHttpRequest();
|
||||
xhr.overrideMimeType("application/xml");
|
||||
xhr.open("GET", srcUrl, true);
|
||||
|
||||
// Elevate the request, so DTDs will work. Should not be a security issue since we
|
||||
// only load chrome, resource and file URLs, and that is our privileged chrome package.
|
||||
try {
|
||||
xhr.channel.owner = Services.scriptSecurityManager.getSystemPrincipal();
|
||||
} catch (ex) {
|
||||
oconsole.error(
|
||||
"Failed to set system principal while fetching overlay " + srcUrl
|
||||
);
|
||||
xhr.close();
|
||||
throw new Error("Failed to set system principal");
|
||||
}
|
||||
// Elevate the request, so DTDs will work. Should not be a security issue since we
|
||||
// only load chrome, resource and file URLs, and that is our privileged chrome package.
|
||||
try {
|
||||
xhr.channel.owner = Services.scriptSecurityManager.getSystemPrincipal();
|
||||
} catch (ex) {
|
||||
console.error(
|
||||
`Failed to set system principal while fetching overlay ${srcUrl}`
|
||||
);
|
||||
xhr.close();
|
||||
reject("Failed to set system principal");
|
||||
}
|
||||
|
||||
xhr.send(null);
|
||||
return xhr;
|
||||
xhr.onload = () => resolve(xhr.responseXML);
|
||||
xhr.onerror = () =>
|
||||
reject(`Failed to load ${srcUrl} to ${this.location}`);
|
||||
xhr.send(null);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -508,14 +535,14 @@ class Overlays {
|
||||
* be an inline script with textContent.
|
||||
*
|
||||
* @param {Element} node The <script> element to load the script from
|
||||
* @returns {Object[]} An object with listener and useCapture,
|
||||
* @return {Object[]} An object with listener and useCapture,
|
||||
* describing load handlers the script creates
|
||||
* when first run.
|
||||
*/
|
||||
loadScript(node) {
|
||||
let deferredLoad = [];
|
||||
const deferredLoad = [];
|
||||
|
||||
let oldAddEventListener = this.window.addEventListener;
|
||||
const oldAddEventListener = this.window.addEventListener;
|
||||
if (this.document.readyState == "complete") {
|
||||
this.window.addEventListener = function(
|
||||
type,
|
||||
@@ -545,17 +572,17 @@ class Overlays {
|
||||
}
|
||||
|
||||
if (node.hasAttribute("src")) {
|
||||
let url = new URL(node.getAttribute("src"), node.baseURI).href;
|
||||
oconsole.debug(`Loading script ${url} into ${this.window.location}`);
|
||||
const url = new URL(node.getAttribute("src"), node.baseURI).href;
|
||||
console.debug(`Loading script ${url} into ${this.window.location}`);
|
||||
try {
|
||||
Services.scriptloader.loadSubScript(url, this.window);
|
||||
} catch (ex) {
|
||||
Cu.reportError(ex);
|
||||
}
|
||||
} else if (node.textContent) {
|
||||
oconsole.debug(`Loading eval'd script into ${this.window.location}`);
|
||||
console.debug(`Loading eval'd script into ${this.window.location}`);
|
||||
try {
|
||||
let dataURL =
|
||||
const dataURL =
|
||||
"data:application/javascript," + encodeURIComponent(node.textContent);
|
||||
// It would be great if we could have script errors show the right url, but for now
|
||||
// loadSubScript will have to do.
|
||||
@@ -577,23 +604,13 @@ class Overlays {
|
||||
/**
|
||||
* Load the CSS stylesheet from the given url
|
||||
*
|
||||
* @param {string} url The url to load from
|
||||
* @returns {Element} An HTML link element for this stylesheet
|
||||
* @param {String} url The url to load from
|
||||
* @return {Element} An HTML link element for this stylesheet
|
||||
*/
|
||||
loadCSS(url) {
|
||||
oconsole.debug(`Loading ${url} into ${this.window.location}`);
|
||||
console.debug(`Loading ${url} into ${this.window.location}`);
|
||||
|
||||
// domWindowUtils.loadSheetUsingURIString doesn't record the sheet in document.styleSheets,
|
||||
// adding a html link element seems to do so.
|
||||
let link = this.document.createElementNS(
|
||||
"http://www.w3.org/1999/xhtml",
|
||||
"link"
|
||||
);
|
||||
link.setAttribute("rel", "stylesheet");
|
||||
link.setAttribute("type", "text/css");
|
||||
link.setAttribute("href", url);
|
||||
|
||||
this.document.documentElement.appendChild(link);
|
||||
return link;
|
||||
const winUtils = this.window.windowUtils;
|
||||
winUtils.loadSheetUsingURIString(url, winUtils.AUTHOR_SHEET);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user