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)$/
|
/^(?:<all_urls>|webRequestBlocking)$/
|
||||||
.test(p)
|
.test(p)
|
||||||
);
|
);
|
||||||
|
const excludedScriptsRx = /\bcontent\/(?:embeddingDocument|dirindex)\.js$/;
|
||||||
for (const cs of json.content_scripts) {
|
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.browser_action;
|
||||||
delete json.commands._execute_browser_action
|
delete json.commands._execute_browser_action
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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
|
// see https://searchfox.org/mozilla-central/rev/76c1ff5f0de23366fe952ab228610ee695a56e68/netwerk/streamconv/converters/nsIndexedToHTML.cpp#334
|
||||||
'use strict';
|
'use strict';
|
||||||
var gTable, gOrderBy, gTBody, gRows, gUI_showHidden;
|
var gTable, gOrderBy, gTBody, gRows, gUI_showHidden;
|
|
@ -18,8 +18,9 @@
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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 listenersMap = new Map();
|
||||||
let backlog = new Set();
|
let backlog = new Set();
|
||||||
|
|
||||||
|
@ -155,7 +156,7 @@
|
||||||
if (!(UA.isMozilla || perms.capabilities.includes("script")) &&
|
if (!(UA.isMozilla || perms.capabilities.includes("script")) &&
|
||||||
/^file:\/\/\/(?:[^#?]+\/)?$/.test(document.URL)) {
|
/^file:\/\/\/(?:[^#?]+\/)?$/.test(document.URL)) {
|
||||||
// Allow Chromium browser UI scripts for directory navigation
|
// 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");
|
perms.capabilities.push("script");
|
||||||
}
|
}
|
||||||
this.capabilities = new Set(perms.capabilities);
|
this.capabilities = new Set(perms.capabilities);
|
||||||
|
@ -177,6 +178,10 @@
|
||||||
globalThis.ns_setupCallBack = ns.domPolicy
|
globalThis.ns_setupCallBack = ns.domPolicy
|
||||||
? () => {}
|
? () => {}
|
||||||
: ({domPolicy}) => {
|
: ({domPolicy}) => {
|
||||||
|
ns.domPolicy = domPolicy;
|
||||||
|
if (ns.setup) {
|
||||||
|
if (ns.syncSetup) ns.syncSetup(domPolicy);
|
||||||
|
else ns.setup(domPolicy);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,205 +22,207 @@
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
(window.ns || (window.ns = {})).syncFetchPolicy = function() {
|
if (FILE_OR_FTP) {
|
||||||
|
(globalThis.ns ||= {}).syncFetchPolicy = function() {
|
||||||
|
|
||||||
ns.pendingSyncFetchPolicy = false;
|
ns.pendingSyncFetchPolicy = false;
|
||||||
ns.syncFetchPolicy = () => {};
|
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
|
// Here we've got no CSP header yet (file: or ftp: URL), we need one
|
||||||
// injected in the DOM as soon as possible.
|
// injected in the DOM as soon as possible.
|
||||||
debug("No CSP yet for non-HTTP document load: fetching policy synchronously...", ns);
|
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 (window.wrappedJSObject) {
|
||||||
if (top === window) {
|
if (top === window) {
|
||||||
let persistentPolicy = null;
|
let persistentPolicy = null;
|
||||||
syncSetup = policy => {
|
syncSetup = policy => {
|
||||||
if (persistentPolicy) return;
|
if (persistentPolicy) return;
|
||||||
ns.setup(policy);
|
ns.setup(policy);
|
||||||
persistentPolicy = JSON.stringify(policy);
|
persistentPolicy = JSON.stringify(policy);
|
||||||
Object.freeze(persistentPolicy);
|
Object.freeze(persistentPolicy);
|
||||||
try {
|
try {
|
||||||
Object.defineProperty(window.wrappedJSObject, "_noScriptPolicy", {value: cloneInto(persistentPolicy, window)});
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
error(e);
|
error(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
} else try {
|
||||||
if (DocumentFreezer.firedDOMContentLoaded || document.readyState !== "loading") {
|
if (top.wrappedJSObject._noScriptPolicy) {
|
||||||
softReload();
|
debug("Policy set in parent frame found!")
|
||||||
} else {
|
try {
|
||||||
debug("Deferring softReload to DOMContentLoaded...");
|
ns.setup(JSON.parse(top.wrappedJSObject._noScriptPolicy));
|
||||||
addEventListener("DOMContentLoaded", softReload, true);
|
return;
|
||||||
|
} catch(e) {
|
||||||
|
error(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
});
|
// cross-origin access violation, ignore
|
||||||
} catch (e) {
|
}
|
||||||
error(e);
|
|
||||||
}
|
}
|
||||||
|
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/WebGLHook.js",
|
||||||
"/nscl/content/promptHook.js",
|
"/nscl/content/promptHook.js",
|
||||||
"content/embeddingDocument.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/WebGLHook.main.js",
|
||||||
"/nscl/main/prefetchCSSResources.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