Index: lib/content/snippets.js |
=================================================================== |
--- a/lib/content/snippets.js |
+++ b/lib/content/snippets.js |
@@ -54,21 +54,129 @@ |
} |
function makeInjector(injectable, ...dependencies) |
{ |
return (...args) => injectCode(stringifyFunctionCall(injectable, ...args), |
dependencies); |
} |
+function regexEscape(s) |
+{ |
+ return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"); |
+} |
+ |
+function findProperty(path, root = window) |
+{ |
+ let owner = root; |
+ |
+ let chain = path.split("."); |
+ let property = chain.pop(); |
+ |
+ for (let name of chain) |
+ { |
+ if (!owner) |
+ break; |
+ |
+ owner = owner[name]; |
+ } |
+ |
+ if (!owner) |
+ return null; |
+ |
+ return {owner, property}; |
+} |
+ |
+function injectPropertyAccessValidator(object, property, validate) |
+{ |
+ let value = object[property]; |
+ |
+ Object.defineProperty(object, property, { |
+ get() |
+ { |
+ validate(); |
+ return value; |
+ }, |
+ set(newValue) |
+ { |
+ validate(); |
+ value = newValue; |
+ } |
+ }); |
+} |
+ |
+function getMagic() |
+{ |
+ return String.fromCharCode(Date.now() % 26 + 97) + |
+ Math.floor(Math.random() * 982451653 + 982451653).toString(36); |
+} |
+ |
+function suppressMagicError(magic) |
+{ |
+ let {onerror} = window; |
+ window.onerror = function(message, ...rest) |
+ { |
+ if (typeof message == "string" && message.includes(magic)) |
+ return true; |
+ |
+ if (typeof onerror instanceof Function) |
+ return onerror.call(this, message, ...rest); |
+ }; |
+} |
+ |
function log(...args) |
{ |
console.log(...args); |
} |
exports.log = log; |
function trace(...args) |
{ |
log(...args); |
} |
exports.trace = makeInjector(trace, log); |
+ |
+// This is an implementation of the abort-current-inline-script technique used |
+// by uBlock Origin |
+// https://github.com/uBlockOrigin/uAssets/blob/68cf8a364fb55deb96264c4e546b58f4c851d782/filters/resources.txt#L1943 |
+function abortCurrentInlineScript(target, needle) |
+{ |
+ if (!target) |
+ return; |
+ |
+ let re = null; |
+ |
+ if (needle) |
+ { |
+ re = new RegExp(/^\/.+\/$/.test(needle) ? |
+ needle.slice(1, -1) : |
+ regexEscape(needle)); |
+ } |
+ |
+ let {owner, property} = findProperty(target) || {}; |
+ |
+ if (!owner) |
+ return; |
+ |
+ let descriptor = Object.getOwnPropertyDescriptor(owner, property); |
+ if (descriptor && descriptor.get) |
+ return; |
+ |
+ let magic = getMagic(); |
+ |
+ injectPropertyAccessValidator(owner, property, () => |
+ { |
+ let element = document.currentScript; |
+ if (element instanceof HTMLScriptElement && |
+ element.src == "" && (!re || re.test(element.textContent))) |
+ { |
+ throw new ReferenceError(magic); |
+ } |
+ }); |
+ |
+ suppressMagicError(magic); |
+} |
+ |
+exports["abort-current-inline-script"] = |
+ makeInjector(abortCurrentInlineScript, regexEscape, findProperty, |
+ getMagic, injectPropertyAccessValidator, suppressMagicError); |