feat: add functionality to load overlays into browser files

(cherry picked from commit 7561b2434a3258c302ba7a0573bb97831ba5aa64)
This commit is contained in:
adamp01
2022-07-27 11:12:16 +01:00
committed by Alex Kontos
parent 9b74c03d01
commit 60b5092c93
5 changed files with 281 additions and 203 deletions

View File

@@ -6,6 +6,62 @@
const EXPORTED_SYMBOLS = ["WaterfoxGlue"]; 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 = { 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;
}
},
}; };

View File

@@ -0,0 +1,3 @@
browser.jar:
% resource waterfox %waterfox/ contentaccessible=yes
waterfox/overlays/chrome.manifest (chrome.manifest)

View File

@@ -7,3 +7,5 @@
EXTRA_JS_MODULES += [ EXTRA_JS_MODULES += [
"WaterfoxGlue.jsm", "WaterfoxGlue.jsm",
] ]
JAR_MANIFESTS += ["jar.mn"]

View File

@@ -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. * Load overlays in a similar way as XUL did for legacy XUL add-ons.
*/ */
/* exported Overlays */
"use strict"; "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( ChromeUtils.defineModuleGetter(
this, lazy,
"Services",
"resource://gre/modules/Services.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"setTimeout", "setTimeout",
"resource://gre/modules/Timer.jsm" "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 * 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() * should likely be called through its static method Overlays.load()
@@ -42,10 +32,11 @@ class Overlays {
* @param {DOMWindow} window The window to load into * @param {DOMWindow} window The window to load into
*/ */
static load(overlayProvider, window) { static load(overlayProvider, window) {
let instance = new Overlays(overlayProvider, window); const instance = new Overlays(overlayProvider, window);
let urls = overlayProvider.overlay.get(instance.location, false); const urls = overlayProvider.overlay.get(instance.location, false);
instance.load(urls); 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 * Loads the given urls into the window, recursively loading further overlays as provided by the
* overlayProvider. * overlayProvider.
* *
* @param {string[]} urls The urls to load * @param {String[]} urls The urls to load
*/ */
load(urls) { async load(urls) {
let unloadedOverlays = this._collectOverlays(this.document).concat(urls); const unloadedOverlays = new Set(
this._collectOverlays(this.document).concat(urls)
);
let forwardReferences = []; let forwardReferences = [];
let unloadedScripts = []; this.unloadedScripts = [];
let unloadedSheets = []; const unloadedSheets = [];
this._toolbarsToResolve = []; this._toolbarsToResolve = [];
let xulStore = Services.xulStore; const xulStore = Services.xulStore;
this.persistedIDs = new Set(); this.persistedIDs = new Set();
// Load css styles from the registry // 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); unloadedSheets.push(sheet);
} }
if (!unloadedOverlays.length && !unloadedSheets.length) { if (!unloadedOverlays.size && !unloadedSheets.length) {
return; return;
} }
while (unloadedOverlays.length) { for (const url of unloadedOverlays) {
let url = unloadedOverlays.shift(); unloadedOverlays.delete(url);
let xhr = this.fetchOverlay(url); const doc = await this.fetchOverlay(url);
let doc = xhr.responseXML;
oconsole.debug(`Applying ${url} to ${this.location}`); console.debug(`Applying ${url} to ${this.location}`);
// clean the document a bit // clean the document a bit
let emptyNodes = doc.evaluate( const emptyNodes = doc.evaluate(
"//text()[normalize-space(.) = '']", "//text()[normalize-space(.) = '']",
doc, doc,
null, null,
@@ -112,23 +104,31 @@ class Overlays {
null null
); );
for (let i = 0, len = emptyNodes.snapshotLength; i < len; ++i) { for (let i = 0, len = emptyNodes.snapshotLength; i < len; ++i) {
let node = emptyNodes.snapshotItem(i); const node = emptyNodes.snapshotItem(i);
node.remove(); 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) { for (let i = 0, len = commentNodes.snapshotLength; i < len; ++i) {
let node = commentNodes.snapshotItem(i); const node = commentNodes.snapshotItem(i);
node.remove(); 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 // 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); unloadedSheets.push(sheet);
} }
// Load css processing instructions from the overlay // Load css processing instructions from the overlay
let stylesheets = doc.evaluate( const stylesheets = doc.evaluate(
"/processing-instruction('xml-stylesheet')", "/processing-instruction('xml-stylesheet')",
doc, doc,
null, null,
@@ -136,33 +136,44 @@ class Overlays {
null null
); );
for (let i = 0, len = stylesheets.snapshotLength; i < len; ++i) { for (let i = 0, len = stylesheets.snapshotLength; i < len; ++i) {
let node = stylesheets.snapshotItem(i); const node = stylesheets.snapshotItem(i);
let match = node.nodeValue.match(/href=["']([^"']*)["']/); const match = node.nodeValue.match(/href=["']([^"']*)["']/);
if (match) { if (match) {
unloadedSheets.push(new URL(match[1], node.baseURI).href); unloadedSheets.push(new URL(match[1], node.baseURI).href);
} }
} }
const t_unloadedOverlays = [];
// Prepare loading further nested xul overlays from the overlay // 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 // Prepare loading further nested xul overlays from the registry
for (let overlayUrl of this.overlayProvider.overlay.get(url, false)) { for (const overlayUrl of this.overlayProvider.overlay.get(url, false)) {
unloadedOverlays.push(overlayUrl); 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 // 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?). // 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") { if (node.localName == "script") {
unloadedScripts.push(node); this.unloadedScripts.push(node);
} else { } 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()) { while (ids.hasMore()) {
this.persistedIDs.add(ids.getNext()); this.persistedIDs.add(ids.getNext());
} }
@@ -172,9 +183,9 @@ class Overlays {
let previous = 0; let previous = 0;
while (forwardReferences.length && forwardReferences.length != previous) { while (forwardReferences.length && forwardReferences.length != previous) {
previous = forwardReferences.length; previous = forwardReferences.length;
let unresolved = []; const unresolved = [];
for (let ref of forwardReferences) { for (const ref of forwardReferences) {
if (!this._resolveForwardReference(ref)) { if (!this._resolveForwardReference(ref)) {
unresolved.push(ref); unresolved.push(ref);
} }
@@ -184,58 +195,47 @@ class Overlays {
} }
if (forwardReferences.length) { if (forwardReferences.length) {
oconsole.warn( console.warn(
`Could not resolve ${forwardReferences.length} references`, `Could not resolve ${forwardReferences.length} references`,
forwardReferences forwardReferences
); );
} }
// Loading the sheets now to avoid race conditions with xbl bindings // 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.loadCSS(sheet);
} }
this._decksToResolve = new Map(); this._decksToResolve = new Map();
for (let id of this.persistedIDs.values()) { for (const id of this.persistedIDs.values()) {
let element = this.document.getElementById(id); const element = this.document.getElementById(id);
if (element) { if (element) {
let attrNames = xulStore.getAttributeEnumerator(this.location, id); const attrNames = xulStore.getAttributeEnumerator(this.location, id);
while (attrNames.hasMore()) { while (attrNames.hasMore()) {
let attrName = attrNames.getNext(); const attrName = attrNames.getNext();
let attrValue = xulStore.getValue(this.location, id, attrName); const attrValue = xulStore.getValue(this.location, id, attrName);
if (attrName == "selectedIndex" && element.localName == "deck") { if (attrName == "selectedIndex" && element.localName == "deck") {
this._decksToResolve.set(element, attrValue); 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); 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") { if (this.document.readyState == "complete") {
let sheet; lazy.setTimeout(() => {
let overlayTrigger = this.document.createXULElement("overlayTrigger");
overlayTrigger.addEventListener(
"bindingattached",
() => {
oconsole.debug("XBL binding attached, continuing with load");
if (sheet) {
sheet.remove();
}
overlayTrigger.remove();
setTimeout(() => {
this._finish(); this._finish();
// Now execute load handlers since we are done loading scripts // Now execute load handlers since we are done loading scripts
let bubbles = []; const bubbles = [];
for (let { listener, useCapture } of deferredLoad) { for (const { listener, useCapture } of this.deferredLoad) {
if (useCapture) { if (useCapture) {
this._fireEventListener(listener); this._fireEventListener(listener);
} else { } else {
@@ -243,17 +243,10 @@ class Overlays {
} }
} }
for (let listener of bubbles) { for (const listener of bubbles) {
this._fireEventListener(listener); this._fireEventListener(listener);
} }
}, 0); });
},
{ once: true }
);
this.document.documentElement.appendChild(overlayTrigger);
if (overlayTrigger.parentNode) {
sheet = this.loadCSS("chrome://messenger/content/overlayBindings.css");
}
} else { } else {
this.document.defaultView.addEventListener( this.document.defaultView.addEventListener(
"load", "load",
@@ -264,18 +257,18 @@ class Overlays {
} }
_finish() { _finish() {
for (let [deck, selectedIndex] of this._decksToResolve.entries()) { for (const [deck, selectedIndex] of this._decksToResolve.entries()) {
deck.setAttribute("selectedIndex", selectedIndex); deck.setAttribute("selectedIndex", selectedIndex);
} }
for (let bar of this._toolbarsToResolve) { for (const bar of this._toolbarsToResolve) {
let currentset = Services.xulStore.getValue( const currentSet = Services.xulStore.getValue(
this.location, this.location,
bar.id, bar.id,
"currentset" "currentset"
); );
if (currentset) { if (currentSet) {
bar.currentSet = currentset; bar.currentSet = currentSet;
} else if (bar.getAttribute("defaultset")) { } else if (bar.getAttribute("defaultset")) {
bar.currentSet = bar.getAttribute("defaultset"); bar.currentSet = bar.getAttribute("defaultset");
} }
@@ -285,12 +278,12 @@ class Overlays {
/** /**
* Gets the overlays referenced by processing instruction on a document. * Gets the overlays referenced by processing instruction on a document.
* *
* @param {DOMDocument} document The document to read instuctions from * @param {DOMDocument} document The document to read instructions from
* @returns {string[]} URLs of the overlays from the document * @return {String[]} URLs of the overlays from the document
*/ */
_collectOverlays(doc) { _collectOverlays(doc) {
let urls = []; const urls = [];
let instructions = doc.evaluate( const instructions = doc.evaluate(
"/processing-instruction('xul-overlay')", "/processing-instruction('xul-overlay')",
doc, doc,
null, null,
@@ -298,8 +291,8 @@ class Overlays {
null null
); );
for (let i = 0, len = instructions.snapshotLength; i < len; ++i) { for (let i = 0, len = instructions.snapshotLength; i < len; ++i) {
let node = instructions.snapshotItem(i); const node = instructions.snapshotItem(i);
let match = node.nodeValue.match(/href=["']([^"']*)["']/); const match = node.nodeValue.match(/href=["']([^"']*)["']/);
if (match) { if (match) {
urls.push(match[1]); urls.push(match[1]);
} }
@@ -313,13 +306,13 @@ class Overlays {
* @param {EventListener|Function} listener The event listener to call * @param {EventListener|Function} listener The event listener to call
*/ */
_fireEventListener(listener) { _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") { if (typeof listener == "function") {
listener(fakeEvent); listener(fakeEvent);
} else if (listener && typeof listener == "object") { } else if (listener && typeof listener == "object") {
listener.handleEvent(fakeEvent); listener.handleEvent(fakeEvent);
} else { } else {
oconsole.error("Unknown listener type", listener); console.error("Unknown listener type", listener);
} }
} }
@@ -329,36 +322,27 @@ class Overlays {
* level. * level.
* *
* @param {Element} node The DOM Element to resolve in the target document. * @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) { _resolveForwardReference(node) {
if (node.id) { 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") { if (node.localName == "toolbarpalette") {
let box; console.error("toolbarpalette is unsupported type", node.id);
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; return false;
}
target = palette;
}
this._toolbarsToResolve.push(
...box.querySelectorAll('toolbar:not([type="menubar"])')
);
} else if (!target) { } 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` `The node ${node.id} could not be found, deferring to later`
); );
return false; return false;
@@ -380,7 +364,7 @@ class Overlays {
_insertElement(parent, node) { _insertElement(parent, node) {
// These elements need their values set before they are added to // These elements need their values set before they are added to
// the document, or bad things happen. // 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)) { if (element.id && this.persistedIDs.has(element.id)) {
element.setAttribute( element.setAttribute(
"value", "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 wasInserted = false;
let pos = node.getAttribute("insertafter"); let pos = node.getAttribute("insertafter");
let after = true; let after = true;
@@ -399,12 +394,12 @@ class Overlays {
} }
if (pos) { if (pos) {
for (let id of pos.split(",")) { for (const id of pos.split(",")) {
let targetchild = this.document.getElementById(id); const targetChild = this.document.getElementById(id);
if (targetchild && targetchild.parentNode == parent) { if (targetChild && parent.contains(targetChild.parentNode)) {
parent.insertBefore( targetChild.parentNode.insertBefore(
node, node,
after ? targetchild.nextSibling : targetchild after ? targetChild.nextElementSibling : targetChild
); );
wasInserted = true; wasInserted = true;
break; break;
@@ -414,9 +409,9 @@ class Overlays {
if (!wasInserted) { if (!wasInserted) {
// position is 1-based // position is 1-based
let position = parseInt(node.getAttribute("position"), 10); const position = parseInt(node.getAttribute("position"), 10);
if (position > 0 && position - 1 <= parent.childNodes.length) { if (position > 0 && position - 1 <= parent.children.length) {
parent.insertBefore(node, parent.childNodes[position - 1]); parent.insertBefore(node, parent.children[position - 1]);
wasInserted = true; wasInserted = true;
} }
} }
@@ -433,13 +428,11 @@ class Overlays {
* *
* @param {Element} target The node to merge into * @param {Element} target The node to merge into
* @param {Element} node The node that is being merged * @param {Element} node The node that is being merged
* @param {bool} shadow If the target node is in the shadow DOM
*/ */
_mergeElement(target, node) { _mergeElement(target, node, shadow = false) {
for (let attribute of node.attributes) { for (const attribute of node.attributes) {
if (attribute.name == "id") { if (attribute.name !== "id") {
continue;
}
if (attribute.name == "removeelement" && attribute.value == "true") { if (attribute.name == "removeelement" && attribute.value == "true") {
target.remove(); target.remove();
return; return;
@@ -451,18 +444,49 @@ class Overlays {
attribute.value 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++) { for (let i = 0, len = node.childElementCount; i < len; i++) {
let child = node.firstElementChild; const child = node.firstElementChild;
child.remove(); child.remove();
let elementInDocument = child.id let elementInDocument = child.id
? this.document.getElementById(child.id) ? this.document.getElementById(child.id)
: null; : 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) { if (parentId && parentId == target.id) {
this._mergeElement(elementInDocument, child); 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 { } else {
this._insertElement(target, child); this._insertElement(target, child);
} }
@@ -470,11 +494,10 @@ class Overlays {
} }
/** /**
* Fetches the overlay from the given chrome:// or resource:// URL. This happen synchronously so * Fetches the overlay from the given chrome:// or resource:// URL.
* we have a chance to complete before the load event.
* *
* @param {string} srcUrl The URL to load * @param {String} srcUrl The URL to load
* @returns {XMLHttpRequest} The completed XHR. * @return {Promise<XMLDocument>} Returns a promise that is resolved with the XML document.
*/ */
fetchOverlay(srcUrl) { fetchOverlay(srcUrl) {
if (!srcUrl.startsWith("chrome://") && !srcUrl.startsWith("resource://")) { if (!srcUrl.startsWith("chrome://") && !srcUrl.startsWith("resource://")) {
@@ -483,24 +506,28 @@ class Overlays {
); );
} }
let xhr = new XMLHttpRequest(); return new Promise((resolve, reject) => {
const xhr = new this.window.XMLHttpRequest();
xhr.overrideMimeType("application/xml"); xhr.overrideMimeType("application/xml");
xhr.open("GET", srcUrl, false); xhr.open("GET", srcUrl, true);
// Elevate the request, so DTDs will work. Should not be a security issue since we // 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. // only load chrome, resource and file URLs, and that is our privileged chrome package.
try { try {
xhr.channel.owner = Services.scriptSecurityManager.getSystemPrincipal(); xhr.channel.owner = Services.scriptSecurityManager.getSystemPrincipal();
} catch (ex) { } catch (ex) {
oconsole.error( console.error(
"Failed to set system principal while fetching overlay " + srcUrl `Failed to set system principal while fetching overlay ${srcUrl}`
); );
xhr.close(); xhr.close();
throw new Error("Failed to set system principal"); reject("Failed to set system principal");
} }
xhr.onload = () => resolve(xhr.responseXML);
xhr.onerror = () =>
reject(`Failed to load ${srcUrl} to ${this.location}`);
xhr.send(null); xhr.send(null);
return xhr; });
} }
/** /**
@@ -508,14 +535,14 @@ class Overlays {
* be an inline script with textContent. * be an inline script with textContent.
* *
* @param {Element} node The <script> element to load the script from * @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 * describing load handlers the script creates
* when first run. * when first run.
*/ */
loadScript(node) { loadScript(node) {
let deferredLoad = []; const deferredLoad = [];
let oldAddEventListener = this.window.addEventListener; const oldAddEventListener = this.window.addEventListener;
if (this.document.readyState == "complete") { if (this.document.readyState == "complete") {
this.window.addEventListener = function( this.window.addEventListener = function(
type, type,
@@ -545,17 +572,17 @@ class Overlays {
} }
if (node.hasAttribute("src")) { if (node.hasAttribute("src")) {
let url = new URL(node.getAttribute("src"), node.baseURI).href; const url = new URL(node.getAttribute("src"), node.baseURI).href;
oconsole.debug(`Loading script ${url} into ${this.window.location}`); console.debug(`Loading script ${url} into ${this.window.location}`);
try { try {
Services.scriptloader.loadSubScript(url, this.window); Services.scriptloader.loadSubScript(url, this.window);
} catch (ex) { } catch (ex) {
Cu.reportError(ex); Cu.reportError(ex);
} }
} else if (node.textContent) { } 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 { try {
let dataURL = const dataURL =
"data:application/javascript," + encodeURIComponent(node.textContent); "data:application/javascript," + encodeURIComponent(node.textContent);
// It would be great if we could have script errors show the right url, but for now // It would be great if we could have script errors show the right url, but for now
// loadSubScript will have to do. // loadSubScript will have to do.
@@ -577,23 +604,13 @@ class Overlays {
/** /**
* Load the CSS stylesheet from the given url * Load the CSS stylesheet from the given url
* *
* @param {string} url The url to load from * @param {String} url The url to load from
* @returns {Element} An HTML link element for this stylesheet * @return {Element} An HTML link element for this stylesheet
*/ */
loadCSS(url) { 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, const winUtils = this.window.windowUtils;
// adding a html link element seems to do so. winUtils.loadSheetUsingURIString(url, winUtils.AUTHOR_SHEET);
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;
} }
} }