Restructure inclusion of file/ftp protocol-specific content scripts.
This commit is contained in:
parent
fd33d7b3ce
commit
06cd1c1e4e
|
@ -46,8 +46,9 @@ if (MANIFEST_VER.includes(3)) {
|
|||
/^(?:<all_urls>|webRequestBlocking)$/
|
||||
.test(p)
|
||||
);
|
||||
const excludedScriptsRx = /\bcontent\/(?:embeddingDocument|dirindex)\.js$/;
|
||||
for (const cs of json.content_scripts) {
|
||||
cs.js = cs.js.filter(js => !js.includes("content/embeddingDocument.js"))
|
||||
cs.js = cs.js.filter(path => !excludedScriptsRx.test(path));
|
||||
}
|
||||
delete json.browser_action;
|
||||
delete json.commands._execute_browser_action
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
if (UA.isMozilla) (() => {
|
||||
if (FILE_OR_FTP && UA.isMozilla) (() => {
|
||||
// see https://searchfox.org/mozilla-central/rev/76c1ff5f0de23366fe952ab228610ee695a56e68/netwerk/streamconv/converters/nsIndexedToHTML.cpp#334
|
||||
'use strict';
|
||||
var gTable, gOrderBy, gTBody, gRows, gUI_showHidden;
|
|
@ -18,8 +18,9 @@
|
|||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
const FILE_OR_FTP = /^(?:file|ftp):$/.test(location.protocol);
|
||||
{
|
||||
'use strict';
|
||||
let listenersMap = new Map();
|
||||
let backlog = new Set();
|
||||
|
||||
|
@ -155,7 +156,7 @@
|
|||
if (!(UA.isMozilla || perms.capabilities.includes("script")) &&
|
||||
/^file:\/\/\/(?:[^#?]+\/)?$/.test(document.URL)) {
|
||||
// Allow Chromium browser UI scripts for directory navigation
|
||||
// (for Firefox we rely on emulation in content/ftp.js).
|
||||
// (for Firefox we rely on emulation in content/dirindex.js).
|
||||
perms.capabilities.push("script");
|
||||
}
|
||||
this.capabilities = new Set(perms.capabilities);
|
||||
|
@ -177,6 +178,10 @@
|
|||
globalThis.ns_setupCallBack = ns.domPolicy
|
||||
? () => {}
|
||||
: ({domPolicy}) => {
|
||||
|
||||
ns.domPolicy = domPolicy;
|
||||
if (ns.setup) {
|
||||
if (ns.syncSetup) ns.syncSetup(domPolicy);
|
||||
else ns.setup(domPolicy);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -22,205 +22,207 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
(window.ns || (window.ns = {})).syncFetchPolicy = function() {
|
||||
if (FILE_OR_FTP) {
|
||||
(globalThis.ns ||= {}).syncFetchPolicy = function() {
|
||||
|
||||
ns.pendingSyncFetchPolicy = false;
|
||||
ns.syncFetchPolicy = () => {};
|
||||
ns.pendingSyncFetchPolicy = false;
|
||||
ns.syncFetchPolicy = () => {};
|
||||
|
||||
let url = document.URL;
|
||||
let url = document.URL;
|
||||
|
||||
// Here we've got no CSP header yet (file: or ftp: URL), we need one
|
||||
// injected in the DOM as soon as possible.
|
||||
debug("No CSP yet for non-HTTP document load: fetching policy synchronously...", ns);
|
||||
// Here we've got no CSP header yet (file: or ftp: URL), we need one
|
||||
// injected in the DOM as soon as possible.
|
||||
debug("No CSP yet for non-HTTP document load: fetching policy synchronously...", ns);
|
||||
|
||||
let syncSetup = ns.setup.bind(ns);
|
||||
let syncSetup = ns.setup.bind(ns);
|
||||
|
||||
if (window.wrappedJSObject) {
|
||||
if (top === window) {
|
||||
let persistentPolicy = null;
|
||||
syncSetup = policy => {
|
||||
if (persistentPolicy) return;
|
||||
ns.setup(policy);
|
||||
persistentPolicy = JSON.stringify(policy);
|
||||
Object.freeze(persistentPolicy);
|
||||
try {
|
||||
Object.defineProperty(window.wrappedJSObject, "_noScriptPolicy", {value: cloneInto(persistentPolicy, window)});
|
||||
} catch(e) {
|
||||
error(e);
|
||||
}
|
||||
};
|
||||
} else try {
|
||||
if (top.wrappedJSObject._noScriptPolicy) {
|
||||
debug("Policy set in parent frame found!")
|
||||
try {
|
||||
ns.setup(JSON.parse(top.wrappedJSObject._noScriptPolicy));
|
||||
return;
|
||||
} catch(e) {
|
||||
error(e);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// cross-origin access violation, ignore
|
||||
}
|
||||
}
|
||||
if (ns.domPolicy) {
|
||||
syncSetup(ns.domPolicy);
|
||||
return;
|
||||
}
|
||||
|
||||
debug("Initial document state", document.readyState, document.documentElement, document.head, document.body); // DEV_ONLY
|
||||
|
||||
let mustFreeze = document.head && UA.isMozilla
|
||||
&& (!/^(?:image|video|audio)/.test(document.contentType) || document instanceof XMLDocument)
|
||||
&& document.readyState !== "complete";
|
||||
|
||||
if (mustFreeze) {
|
||||
// Mozilla has already parsed the <head> element, we must take extra steps...
|
||||
try {
|
||||
DocumentFreezer.freeze();
|
||||
|
||||
ns.on("capabilities", () => {
|
||||
|
||||
let {readyState} = document;
|
||||
|
||||
debug("Readystate: %s, suppressedScripts = %s, canScript = %s", readyState, DocumentFreezer.suppressedScripts, ns.canScript);
|
||||
|
||||
if (!ns.canScript) {
|
||||
queueMicrotask(() => DocumentFreezer.unfreeze());
|
||||
let normalizeDir = e => {
|
||||
// Chromium does this automatically. We need it to understand we're a directory earlier and allow browser UI scripts.
|
||||
if (document.baseURI === document.URL + "/") {
|
||||
if (e) {
|
||||
document.removeEventListener(e.type, normalizeDir);
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
window.stop();
|
||||
location.replace(document.baseURI);
|
||||
}
|
||||
}
|
||||
if (DocumentFreezer.firedDOMContentLoaded) {
|
||||
normalizeDir();
|
||||
} else {
|
||||
document.addEventListener("readystatechange", normalizeDir);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (DocumentFreezer.suppressedScripts === 0 && readyState === "loading") {
|
||||
// we don't care reloading, if no script has been suppressed
|
||||
// and no readyState change has been fired yet
|
||||
DocumentFreezer.unfreeze();
|
||||
return;
|
||||
}
|
||||
|
||||
let softReload = ev => {
|
||||
removeEventListener("DOMContentLoaded", softReload, true);
|
||||
try {
|
||||
debug("Soft reload", ev); // DEV_ONLY
|
||||
try {
|
||||
let isDir = document.querySelector("link[rel=stylesheet][href^='chrome:']")
|
||||
&& document.querySelector(`base[href^="${url}"]`);
|
||||
if (isDir || document.contentType !== "text/html") {
|
||||
throw new Error(`Can't document.write() on ${isDir ? "directory listings" : document.contentType}`)
|
||||
}
|
||||
|
||||
DocumentFreezer.unfreeze();
|
||||
|
||||
let html = document.documentElement.outerHTML;
|
||||
let sx = window.scrollX, sy = window.scrollY;
|
||||
DocRewriter.rewrite(html);
|
||||
debug("Written", html);
|
||||
// Work-around this rendering bug: https://forums.informaction.com/viewtopic.php?p=103105#p103050
|
||||
debug("Scrolling back to", sx, sy);
|
||||
window.scrollTo(sx, sy);
|
||||
} catch (e) {
|
||||
debug("Can't use document.write(), XML document?", e);
|
||||
try {
|
||||
let eventSuppressor = ev => {
|
||||
if (ev.isTrusted) {
|
||||
debug("Suppressing natural event", ev);
|
||||
ev.preventDefault();
|
||||
ev.stopImmediatePropagation();
|
||||
ev.currentTarget.removeEventListener(ev.type, eventSuppressor, true);
|
||||
}
|
||||
};
|
||||
let svg = document.documentElement instanceof SVGElement;
|
||||
if (svg) {
|
||||
document.addEventListener("SVGLoad", eventSuppressor, true);
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", eventSuppressor, true);
|
||||
if (ev) eventSuppressor(ev);
|
||||
DocumentFreezer.unfreeze();
|
||||
let scripts = [], deferred = [];
|
||||
// push deferred scripts, if any, to the end
|
||||
for (let s of document.getElementsByTagName("script")) {
|
||||
(s.defer && !s.text ? deferred : scripts).push(s);
|
||||
s.addEventListener("beforescriptexecute", e => {
|
||||
console.debug("Suppressing", script);
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
if (deferred.length) scripts.push(...deferred);
|
||||
let doneEvents = ["afterscriptexecute", "load", "error"];
|
||||
(async () => {
|
||||
for (let s of scripts) {
|
||||
let clone = document.createElementNS(s.namespaceURI, "script");
|
||||
for (let a of s.attributes) {
|
||||
clone.setAttributeNS(a.namespaceURI, a.name, a.value);
|
||||
}
|
||||
clone.innerHTML = s.innerHTML;
|
||||
await new Promise(resolve => {
|
||||
let listener = ev => {
|
||||
if (ev.target !== clone) return;
|
||||
debug("Resolving on ", ev.type, ev.target);
|
||||
resolve(ev.target);
|
||||
for (let et of doneEvents) removeEventListener(et, listener, true);
|
||||
};
|
||||
for (let et of doneEvents) {
|
||||
addEventListener(et, listener, true);
|
||||
}
|
||||
s.replaceWith(clone);
|
||||
debug("Replaced", clone);
|
||||
});
|
||||
}
|
||||
debug("All scripts done, firing completion events.");
|
||||
document.dispatchEvent(new Event("readystatechange"));
|
||||
if (svg) {
|
||||
document.documentElement.dispatchEvent(new Event("SVGLoad"));
|
||||
}
|
||||
document.dispatchEvent(new Event("DOMContentLoaded", {
|
||||
bubbles: true,
|
||||
cancelable: false
|
||||
}));
|
||||
if (document.readyState === "complete") {
|
||||
window.dispatchEvent(new Event("load"));
|
||||
}
|
||||
})();
|
||||
} catch (e) {
|
||||
error(e);
|
||||
}
|
||||
}
|
||||
if (window.wrappedJSObject) {
|
||||
if (top === window) {
|
||||
let persistentPolicy = null;
|
||||
syncSetup = policy => {
|
||||
if (persistentPolicy) return;
|
||||
ns.setup(policy);
|
||||
persistentPolicy = JSON.stringify(policy);
|
||||
Object.freeze(persistentPolicy);
|
||||
try {
|
||||
Object.defineProperty(window.wrappedJSObject, "_noScriptPolicy", {value: cloneInto(persistentPolicy, window)});
|
||||
} catch(e) {
|
||||
error(e);
|
||||
}
|
||||
};
|
||||
|
||||
if (DocumentFreezer.firedDOMContentLoaded || document.readyState !== "loading") {
|
||||
softReload();
|
||||
} else {
|
||||
debug("Deferring softReload to DOMContentLoaded...");
|
||||
addEventListener("DOMContentLoaded", softReload, true);
|
||||
} else try {
|
||||
if (top.wrappedJSObject._noScriptPolicy) {
|
||||
debug("Policy set in parent frame found!")
|
||||
try {
|
||||
ns.setup(JSON.parse(top.wrappedJSObject._noScriptPolicy));
|
||||
return;
|
||||
} catch(e) {
|
||||
error(e);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
} catch (e) {
|
||||
error(e);
|
||||
} catch (e) {
|
||||
// cross-origin access violation, ignore
|
||||
}
|
||||
}
|
||||
if (ns.domPolicy) {
|
||||
syncSetup(ns.domPolicy);
|
||||
return;
|
||||
}
|
||||
|
||||
debug("Initial document state", document.readyState, document.documentElement, document.head, document.body); // DEV_ONLY
|
||||
|
||||
let mustFreeze = document.head && UA.isMozilla
|
||||
&& (!/^(?:image|video|audio)/.test(document.contentType) || document instanceof XMLDocument)
|
||||
&& document.readyState !== "complete";
|
||||
|
||||
if (mustFreeze) {
|
||||
// Mozilla has already parsed the <head> element, we must take extra steps...
|
||||
try {
|
||||
DocumentFreezer.freeze();
|
||||
|
||||
ns.on("capabilities", () => {
|
||||
|
||||
let {readyState} = document;
|
||||
|
||||
debug("Readystate: %s, suppressedScripts = %s, canScript = %s", readyState, DocumentFreezer.suppressedScripts, ns.canScript);
|
||||
|
||||
if (!ns.canScript) {
|
||||
queueMicrotask(() => DocumentFreezer.unfreeze());
|
||||
let normalizeDir = e => {
|
||||
// Chromium does this automatically. We need it to understand we're a directory earlier and allow browser UI scripts.
|
||||
if (document.baseURI === document.URL + "/") {
|
||||
if (e) {
|
||||
document.removeEventListener(e.type, normalizeDir);
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
window.stop();
|
||||
location.replace(document.baseURI);
|
||||
}
|
||||
}
|
||||
if (DocumentFreezer.firedDOMContentLoaded) {
|
||||
normalizeDir();
|
||||
} else {
|
||||
document.addEventListener("readystatechange", normalizeDir);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (DocumentFreezer.suppressedScripts === 0 && readyState === "loading") {
|
||||
// we don't care reloading, if no script has been suppressed
|
||||
// and no readyState change has been fired yet
|
||||
DocumentFreezer.unfreeze();
|
||||
return;
|
||||
}
|
||||
|
||||
let softReload = ev => {
|
||||
removeEventListener("DOMContentLoaded", softReload, true);
|
||||
try {
|
||||
debug("Soft reload", ev); // DEV_ONLY
|
||||
try {
|
||||
let isDir = document.querySelector("link[rel=stylesheet][href^='chrome:']")
|
||||
&& document.querySelector(`base[href^="${url}"]`);
|
||||
if (isDir || document.contentType !== "text/html") {
|
||||
throw new Error(`Can't document.write() on ${isDir ? "directory listings" : document.contentType}`)
|
||||
}
|
||||
|
||||
DocumentFreezer.unfreeze();
|
||||
|
||||
let html = document.documentElement.outerHTML;
|
||||
let sx = window.scrollX, sy = window.scrollY;
|
||||
DocRewriter.rewrite(html);
|
||||
debug("Written", html);
|
||||
// Work-around this rendering bug: https://forums.informaction.com/viewtopic.php?p=103105#p103050
|
||||
debug("Scrolling back to", sx, sy);
|
||||
window.scrollTo(sx, sy);
|
||||
} catch (e) {
|
||||
debug("Can't use document.write(), XML document?", e);
|
||||
try {
|
||||
let eventSuppressor = ev => {
|
||||
if (ev.isTrusted) {
|
||||
debug("Suppressing natural event", ev);
|
||||
ev.preventDefault();
|
||||
ev.stopImmediatePropagation();
|
||||
ev.currentTarget.removeEventListener(ev.type, eventSuppressor, true);
|
||||
}
|
||||
};
|
||||
let svg = document.documentElement instanceof SVGElement;
|
||||
if (svg) {
|
||||
document.addEventListener("SVGLoad", eventSuppressor, true);
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", eventSuppressor, true);
|
||||
if (ev) eventSuppressor(ev);
|
||||
DocumentFreezer.unfreeze();
|
||||
let scripts = [], deferred = [];
|
||||
// push deferred scripts, if any, to the end
|
||||
for (let s of document.getElementsByTagName("script")) {
|
||||
(s.defer && !s.text ? deferred : scripts).push(s);
|
||||
s.addEventListener("beforescriptexecute", e => {
|
||||
console.debug("Suppressing", script);
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
if (deferred.length) scripts.push(...deferred);
|
||||
let doneEvents = ["afterscriptexecute", "load", "error"];
|
||||
(async () => {
|
||||
for (let s of scripts) {
|
||||
let clone = document.createElementNS(s.namespaceURI, "script");
|
||||
for (let a of s.attributes) {
|
||||
clone.setAttributeNS(a.namespaceURI, a.name, a.value);
|
||||
}
|
||||
clone.innerHTML = s.innerHTML;
|
||||
await new Promise(resolve => {
|
||||
let listener = ev => {
|
||||
if (ev.target !== clone) return;
|
||||
debug("Resolving on ", ev.type, ev.target);
|
||||
resolve(ev.target);
|
||||
for (let et of doneEvents) removeEventListener(et, listener, true);
|
||||
};
|
||||
for (let et of doneEvents) {
|
||||
addEventListener(et, listener, true);
|
||||
}
|
||||
s.replaceWith(clone);
|
||||
debug("Replaced", clone);
|
||||
});
|
||||
}
|
||||
debug("All scripts done, firing completion events.");
|
||||
document.dispatchEvent(new Event("readystatechange"));
|
||||
if (svg) {
|
||||
document.documentElement.dispatchEvent(new Event("SVGLoad"));
|
||||
}
|
||||
document.dispatchEvent(new Event("DOMContentLoaded", {
|
||||
bubbles: true,
|
||||
cancelable: false
|
||||
}));
|
||||
if (document.readyState === "complete") {
|
||||
window.dispatchEvent(new Event("load"));
|
||||
}
|
||||
})();
|
||||
} catch (e) {
|
||||
error(e);
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
error(e);
|
||||
}
|
||||
};
|
||||
|
||||
if (DocumentFreezer.firedDOMContentLoaded || document.readyState !== "loading") {
|
||||
softReload();
|
||||
} else {
|
||||
debug("Deferring softReload to DOMContentLoaded...");
|
||||
addEventListener("DOMContentLoaded", softReload, true);
|
||||
}
|
||||
|
||||
});
|
||||
} catch (e) {
|
||||
error(e);
|
||||
}
|
||||
}
|
||||
|
||||
ns.fetchLikeNoTomorrow(url, syncSetup);
|
||||
};
|
||||
|
||||
if (ns.pendingSyncFetchPolicy) {
|
||||
ns.syncFetchPolicy();
|
||||
}
|
||||
|
||||
ns.fetchLikeNoTomorrow(url, syncSetup);
|
||||
};
|
||||
|
||||
if (ns.pendingSyncFetchPolicy) {
|
||||
ns.syncFetchPolicy();
|
||||
}
|
|
@ -116,7 +116,10 @@
|
|||
"/nscl/content/WebGLHook.js",
|
||||
"/nscl/content/promptHook.js",
|
||||
"content/embeddingDocument.js",
|
||||
"content/content.js"
|
||||
"content/content.js",
|
||||
"content/dirindex.js",
|
||||
"/nscl/content/DocumentFreezer.js",
|
||||
"content/syncFetchPolicy.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -134,18 +137,6 @@
|
|||
"/nscl/main/WebGLHook.main.js",
|
||||
"/nscl/main/prefetchCSSResources.main.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"run_at": "document_start",
|
||||
"matches": ["file://*/*", "ftp://*/*"],
|
||||
"match_about_blank": true,
|
||||
"match_origin_as_fallback": true,
|
||||
"all_frames": true,
|
||||
"js": [
|
||||
"content/ftp.js",
|
||||
"/nscl/content/DocumentFreezer.js",
|
||||
"content/syncFetchPolicy.js"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
|
|
Loading…
Reference in New Issue