1
0
mirror of https://github.com/kkapsner/CanvasBlocker synced 2024-12-22 21:00:23 +01:00
CanvasBlocker/lib/iframeProtection.js

308 lines
9.1 KiB
JavaScript
Raw Normal View History

2019-05-22 23:37:23 +02:00
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
(function(){
"use strict";
let scope;
if ((typeof exports) !== "undefined"){
scope = exports;
}
else {
scope = require.register("./iframeProtection", {});
}
const settings = require("./settings");
2019-12-12 00:09:53 +01:00
const extension = require("./extension");
const lists = require("./lists");
function isWhitelisted(url){
return lists.get("white").match(url) || settings.get("blockMode", url).startsWith("allow");
}
2019-11-28 01:26:35 +01:00
function createChangeProperty(window){
function changeProperty(object, name, type, changed){
const descriptor = Object.getOwnPropertyDescriptor(object, name);
const original = descriptor[type];
2019-12-13 17:34:14 +01:00
if ((typeof changed) === "function"){
changed = extension.exportFunctionWithName(changed, window, original.name);
}
descriptor[type] = changed;
Object.defineProperty(object, name, descriptor);
registerChangedProperty(object, name, descriptor, type, original);
}
const changedProperties = [];
// eslint-disable-next-line max-params
function registerChangedProperty(object, name, descriptor, type, original){
changedProperties.push({object, name, descriptor, type, original});
}
if (settings.isStillDefault){
settings.onloaded(function(){
if (isWhitelisted(window.location)){
changedProperties.forEach(function({object, name, descriptor, type, original}){
descriptor[type] = original;
Object.defineProperty(object, name, descriptor);
});
changedProperties.length = 0;
}
});
}
else {
if (isWhitelisted(window.location)){
2019-12-01 01:25:39 +01:00
return false;
}
}
2019-11-28 01:26:35 +01:00
return changeProperty;
}
function protectFrameProperties({window, wrappedWindow, changeProperty, singleCallback}){
["HTMLIFrameElement", "HTMLFrameElement"].forEach(function(constructorName){
const constructor = window[constructorName];
const wrappedConstructor = wrappedWindow[constructorName];
const contentWindowDescriptor = Object.getOwnPropertyDescriptor(
constructor.prototype,
"contentWindow"
);
const originalContentWindowGetter = contentWindowDescriptor.get;
const contentWindowTemp = {
get contentWindow(){
2019-11-28 01:26:35 +01:00
const window = originalContentWindowGetter.call(this);
if (window){
singleCallback(window);
}
return window;
2019-05-22 23:37:23 +02:00
}
};
2019-12-13 17:34:14 +01:00
changeProperty(wrappedConstructor.prototype, "contentWindow", "get",
Object.getOwnPropertyDescriptor(contentWindowTemp, "contentWindow").get
);
const contentDocumentDescriptor = Object.getOwnPropertyDescriptor(
constructor.prototype,
"contentDocument"
2019-05-22 23:37:23 +02:00
);
const originalContentDocumentGetter = contentDocumentDescriptor.get;
const contentDocumentTemp = {
get contentDocument(){
2019-11-28 01:26:35 +01:00
const document = originalContentDocumentGetter.call(this);
if (document){
singleCallback(document.defaultView);
}
return document;
2019-05-22 23:37:23 +02:00
}
};
2019-12-13 17:34:14 +01:00
changeProperty(wrappedConstructor.prototype, "contentDocument", "get",
Object.getOwnPropertyDescriptor(contentDocumentTemp, "contentDocument").get
);
2019-05-22 23:37:23 +02:00
});
2019-11-28 01:26:35 +01:00
}
function protectDOMModifications({window, wrappedWindow, changeProperty, allCallback}){
2019-05-22 23:37:23 +02:00
[
// useless as length could be obtained before the iframe is created and window.frames === window
// {
// object: wrappedWindow,
// methods: [],
// getters: ["length", "frames"],
// setters: []
// },
{
object: wrappedWindow.Node.prototype,
methods: ["appendChild", "insertBefore", "replaceChild"],
getters: [],
setters: []
},
{
object: wrappedWindow.Element.prototype,
methods: [
"append", "prepend",
"insertAdjacentElement", "insertAdjacentHTML", "insertAdjacentText",
"replaceWith"
],
getters: [],
setters: [
"innerHTML",
"outerHTML"
]
2019-05-22 23:37:23 +02:00
}
].forEach(function(protectionDefinition){
const object = protectionDefinition.object;
protectionDefinition.methods.forEach(function(method){
const descriptor = Object.getOwnPropertyDescriptor(object, method);
const original = descriptor.value;
2019-12-13 17:34:14 +01:00
changeProperty(object, method, "value", function method(){
2019-05-22 23:37:23 +02:00
const value = arguments.length?
original.call(this, ...arguments):
2019-05-22 23:37:23 +02:00
original.call(this);
allCallback();
return value;
2019-12-13 17:34:14 +01:00
});
2019-05-22 23:37:23 +02:00
});
protectionDefinition.getters.forEach(function(property){
2019-12-12 00:09:53 +01:00
const temp = {
get [property](){
const ret = this[property];
2019-05-22 23:37:23 +02:00
allCallback();
return ret;
}
2019-12-12 00:09:53 +01:00
};
2019-12-13 17:34:14 +01:00
changeProperty(object, property, "get",
Object.getOwnPropertyDescriptor(temp, property).get
);
2019-05-22 23:37:23 +02:00
});
protectionDefinition.setters.forEach(function(property){
const descriptor = Object.getOwnPropertyDescriptor(object, property);
const setter = descriptor.set;
2019-12-12 00:09:53 +01:00
const temp = {
set [property](value){
const ret = setter.call(this, value);
// const ret = this.${property} = value;
2019-05-22 23:37:23 +02:00
allCallback();
return ret;
}
2019-12-12 00:09:53 +01:00
};
2019-12-13 17:34:14 +01:00
changeProperty(object, property, "set",
Object.getOwnPropertyDescriptor(temp, property).set
);
2019-05-22 23:37:23 +02:00
});
});
2019-11-28 01:26:35 +01:00
}
function enableMutationObserver({window, allCallback}){
const observer = new MutationObserver(allCallback);
let observing = false;
function observe(){
if (
!observing &&
window.document
){
observer.observe(window.document, {subtree: true, childList: true});
observing = true;
2019-05-24 18:30:57 +02:00
}
2019-11-28 01:26:35 +01:00
}
observe();
window.document.addEventListener("DOMContentLoaded", function(){
if (observing){
observer.disconnect();
observing = false;
}
});
return observe;
}
function protectDocumentWrite({window, wrappedWindow, changeProperty, observe, allCallback}){
const documentWriteDescriptorOnHTMLDocument = Object.getOwnPropertyDescriptor(
2019-05-24 18:30:57 +02:00
wrappedWindow.HTMLDocument.prototype,
"write"
);
const documentWriteDescriptor = documentWriteDescriptorOnHTMLDocument || Object.getOwnPropertyDescriptor(
wrappedWindow.Document.prototype,
"write"
2019-05-24 18:30:57 +02:00
);
2019-05-22 23:37:23 +02:00
const documentWrite = documentWriteDescriptor.value;
changeProperty(
documentWriteDescriptorOnHTMLDocument?
wrappedWindow.HTMLDocument.prototype:
wrappedWindow.Document.prototype,
2019-12-13 17:34:14 +01:00
"write", "value", function write(markup){
for (let i = 0, l = arguments.length; i < l; i += 1){
const str = "" + arguments[i];
// weird problem with waterfox and google docs
const parts = (
str.match(/^\s*<!doctype/i) &&
!str.match(/frame/i)
)? [str]: str.split(/(?=<)/);
const length = parts.length;
const scripts = window.document.getElementsByTagName("script");
for (let i = 0; i < length; i += 1){
documentWrite.call(this, parts[i]);
allCallback();
if (scripts.length && scripts[scripts.length - 1].src){
observe();
}
}
2019-05-24 18:30:57 +02:00
}
2019-12-13 17:34:14 +01:00
}
);
2019-05-22 23:37:23 +02:00
const documentWritelnDescriptorOnHTMLDocument = Object.getOwnPropertyDescriptor(
2019-05-24 18:30:57 +02:00
wrappedWindow.HTMLDocument.prototype,
"writeln"
);
const documentWritelnDescriptor = documentWritelnDescriptorOnHTMLDocument || Object.getOwnPropertyDescriptor(
wrappedWindow.Document.prototype,
"writeln"
2019-05-24 18:30:57 +02:00
);
2019-05-22 23:37:23 +02:00
const documentWriteln = documentWritelnDescriptor.value;
changeProperty(
documentWritelnDescriptorOnHTMLDocument?
wrappedWindow.HTMLDocument.prototype:
wrappedWindow.Document.prototype,
2019-12-13 17:34:14 +01:00
"writeln", "value", function writeln(markup){
for (let i = 0, l = arguments.length; i < l; i += 1){
const str = "" + arguments[i];
const parts = str.split(/(?=<)/);
const length = parts.length;
const scripts = window.document.getElementsByTagName("script");
for (let i = 0; i < length; i += 1){
documentWrite.call(this, parts[i]);
allCallback();
if (scripts.length && scripts[scripts.length - 1].src){
observe();
}
}
2019-12-13 17:34:14 +01:00
}
documentWriteln.call(this, "");
}
);
2019-11-28 01:26:35 +01:00
}
2019-12-02 22:57:11 +01:00
function protectWindowOpen({window, wrappedWindow, changeProperty, singleCallback}){
const windowOpenDescriptor = Object.getOwnPropertyDescriptor(
wrappedWindow,
"open"
);
const windowOpen = windowOpenDescriptor.value;
const getDocument = Object.getOwnPropertyDescriptor(
window,
"document"
).get;
changeProperty(
wrappedWindow,
2019-12-13 17:34:14 +01:00
"open", "value", function open(){
2019-12-02 22:57:11 +01:00
const newWindow = arguments.length?
windowOpen.call(this, ...arguments):
2019-12-02 22:57:11 +01:00
windowOpen.call(this);
if (newWindow){
// if we use windowOpen from the normal window we see some SOP errors
// BUT we need the unwrapped window...
singleCallback(getDocument.call(newWindow).defaultView);
}
return newWindow;
2019-12-13 17:34:14 +01:00
}
2019-12-02 22:57:11 +01:00
);
}
2019-11-28 01:26:35 +01:00
scope.protect = function protect(window, wrappedWindow, singleCallback, allCallback){
const changeProperty = createChangeProperty(window);
2019-12-01 01:25:39 +01:00
if (!changeProperty){
return;
}
2019-11-28 01:26:35 +01:00
const api = {window, wrappedWindow, changeProperty, singleCallback, allCallback};
protectFrameProperties(api);
protectDOMModifications(api);
// MutationObserver to intercept iFrames while generating the DOM.
api.observe = enableMutationObserver(api);
// MutationObserver does not trigger fast enough when document.write is used
protectDocumentWrite(api);
2019-12-02 22:57:11 +01:00
protectWindowOpen(api);
2019-05-22 23:37:23 +02:00
};
}());