Implement pop-up blocking for Safari

It works similarly to the xhr intercepting, except here the window.open
global function is being overridden.
Note that it could only work if the site's Content Security Policy allows
inline scripts, and the script on the webpage doesn't have a copy of the
original window.open function (it can happen only if the page has an
inline script in its head element, where the reference to the original
function can be obtained - likely this cannot be prevented in Safari).
pull/2/head
Deathamns 10 years ago committed by gorhill
parent 9313a7f16b
commit bc980a3522

@ -1,3 +1,4 @@
/* global addMessageListener, removeMessageListener, sendAsyncMessage */
// for non background pages // for non background pages
(function() { (function() {
@ -200,7 +201,6 @@ if (window.chrome) {
if (details) { if (details) {
details.url = linkHelper.href; details.url = linkHelper.href;
details.type = 'xmlhttprequest';
} }
else { else {
details = { details = {
@ -237,6 +237,11 @@ if (window.chrome) {
default: default:
details.type = 'other'; details.type = 'other';
} }
// This can run even before the first DOMSubtreeModified event fired
if (firstMutation) {
firstMutation();
}
} }
// tabId is determined in the background script // tabId is determined in the background script
@ -252,25 +257,22 @@ if (window.chrome) {
return false; return false;
} }
// local mirroring, response is a data: URL here // local mirroring, response is a data: URL here
else if (typeof response === 'string') { else if (typeof response === 'string' && details.type === 'script') {
if (details.type === 'script') { // Content Security Policy with disallowed inline scripts may break things
e.preventDefault(); e.preventDefault();
return response; details = document.createElement('script');
} details.textContent = atob(response.slice(response.indexOf(',', 20) + 1));
else if (details.type === 'script') { e.target.parentNode.insertBefore(details, e.target);
e.preventDefault(); details.parentNode.removeChild(details);
details = document.createElement('script');
details.textContent = atob(response.slice(35));
e.target.parentNode.insertBefore(details, e.target);
details.parentNode.removeChild(details);
}
} }
}; };
document.addEventListener('beforeload', onBeforeLoad, true); document.addEventListener('beforeload', onBeforeLoad, true);
// intercepting xhr requests // blocking pop-ups and intercepting xhr requests
setTimeout(function() { var firstMutation = function() {
document.removeEventListener('DOMSubtreeModified', firstMutation, true);
firstMutation = null;
var randomEventName = parseInt(Math.random() * 1e15, 10).toString(36); var randomEventName = parseInt(Math.random() * 1e15, 10).toString(36);
var beforeLoadEvent = document.createEvent('Event'); var beforeLoadEvent = document.createEvent('Event');
beforeLoadEvent.initEvent('beforeload'); beforeLoadEvent.initEvent('beforeload');
@ -278,41 +280,35 @@ if (window.chrome) {
window.addEventListener(randomEventName, function(e) { window.addEventListener(randomEventName, function(e) {
var result = onBeforeLoad(beforeLoadEvent, e.detail); var result = onBeforeLoad(beforeLoadEvent, e.detail);
if (onBeforeLoad(beforeLoadEvent, e.detail) === false) { if (result === false) {
e.detail.url = false; e.detail.url = false;
} }
else if (typeof result === 'string') {
e.detail.url = result;
}
}, true); }, true);
// since the extension context is unable to reach the page context // the extension context is unable to reach the page context,
var tempScript = document.createElement('script'); // also this only works when Content Security Policy allows inline scripts
tempScript.onload = function() { var tmpJS = document.createElement('script');
this.parentNode.removeChild(this); tmpJS.textContent = ["(function() {",
}; "var block = function(u, t) {",
document.head.appendChild(tempScript).src = "data:application/x-javascript;base64," + btoa(["(function() {", "var e = document.createEvent('CustomEvent'),",
"var xhr_open = XMLHttpRequest.prototype.open;", "d = {url: u, type: t};",
"e.initCustomEvent(",
"XMLHttpRequest.prototype.open = function(method, url, async, u, p) {", "'" + randomEventName + "', !1, !1, d",
"var ev = document.createEvent('CustomEvent');",
"var detail = {url: url};",
"ev.initCustomEvent(",
"'" + randomEventName + "',",
"false, false,",
"detail",
");", ");",
"window.dispatchEvent(ev);", "dispatchEvent(e);",
"if (detail.url === false) {", "return d.url === !1;",
"throw Error;", "}, wo = open, xo = XMLHttpRequest.prototype.open;",
"}", "open = function(u) {",
"else if (typeof detail.url === 'string') {", "return block(u, 'popup') ? null : wo.apply(this, [].slice.call(arguments));",
"url = detail.url;", "};",
"}", "XMLHttpRequest.prototype.open = function(m, u) {",
"return xhr_open.call(this, method, url, async, u, p);", "return block(u, 'xmlhttprequest') ? null : xo.apply(this, [].slice.call(arguments));",
"};", "};",
"})();"].join('')); "})();"].join('');
}, 0); document.head.removeChild(document.head.appendChild(tmpJS));
};
document.addEventListener('DOMSubtreeModified', firstMutation, true);
var onContextMenu = function(e) { var onContextMenu = function(e) {
var details = { var details = {

Loading…
Cancel
Save