| Index: lib/content/snippets.js |
| =================================================================== |
| --- a/lib/content/snippets.js |
| +++ b/lib/content/snippets.js |
| @@ -15,16 +15,60 @@ |
| * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| /* eslint-env webextensions */ |
| /* eslint no-console: "off" */ |
| "use strict"; |
| +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 injectCode(code) |
| { |
| let script = document.createElement("script"); |
| script.type = "application/javascript"; |
| script.async = false; |
| // Firefox 58 only bypasses site CSPs when assigning to 'src', |
| @@ -50,16 +94,35 @@ |
| return `"use strict";(${func})(${params.map(JSON.stringify).join(",")});`; |
| } |
| function makeInjector(injectable) |
| { |
| return (...args) => injectCode(stringifyFunctionCall(injectable, ...args)); |
| } |
| +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) |
|
kzar
2018/07/16 16:33:15
The content script part of our snippet already run
|
| + { |
| + 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; |
| // This is an implementation of the uabinject-defuser technique used by uBlock |
| @@ -69,8 +132,51 @@ |
| { |
| window.trckd = true; |
| window.uabpdl = true; |
| window.uabInject = true; |
| window.uabDetect = true; |
| } |
| exports["uabinject-defuser"] = makeInjector(uabinjectDefuser); |
| + |
| +// 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) |
|
kzar
2018/07/16 16:33:15
Could you give me an example of `target` and `need
|
| +{ |
| + 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) |
|
kzar
2018/07/16 16:33:15
What's the idea with this check?
|
| + 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); |
|
kzar
2018/07/16 16:33:15
How come you've used `ReferenceError` out of inter
|
| + } |
| + }); |
| + |
| + suppressMagicError(magic); |
| +} |
| + |
| +exports["abort-current-inline-script"] = makeInjector(abortCurrentInlineScript); |