| 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); |