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