From 31f9683d9fa412e028eb0c71d7c28be9fa2c36bb Mon Sep 17 00:00:00 2001 From: Deathamns Date: Fri, 24 Oct 2014 16:14:25 +0200 Subject: [PATCH] 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). --- src/js/vapi-background.js | 96 +++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 44 deletions(-) diff --git a/src/js/vapi-background.js b/src/js/vapi-background.js index 837c845..a491885 100644 --- a/src/js/vapi-background.js +++ b/src/js/vapi-background.js @@ -1,3 +1,4 @@ +/* global SafariBrowserTab, Services, XPCOMUtils */ // for background page only (function() { @@ -15,7 +16,7 @@ if (window.chrome) { vAPI.tabs = { registerListeners: function() { if (typeof this.onNavigation === 'function') { - chrome.webNavigation.onCommitted.addListener(this.onNavigation); + chrome.webNavigation.onCommitted.addListener(this.onNavigation); } if (typeof this.onUpdated === 'function') { @@ -392,16 +393,11 @@ if (window.chrome) { } // ?? - /*if (typeof onUpdated === 'function') { - chrome.tabs.onUpdated.addListener(this.onUpdated); - }*/ + /* if (typeof this.onUpdated === 'function') { } */ // onClosed handled in the main tab-close event - - // maybe intercept window.open on web-pages? - /*if (typeof onPopup === 'function') { - chrome.webNavigation.onCreatedNavigationTarget.addListener(this.onPopup); - }*/ + // onPopup is handled in window.open on web-pages? + /* if (typeof onPopup === 'function') { } */ }, getTabId: function(tab) { for (var i in vAPI.tabs.stack) { @@ -653,10 +649,6 @@ if (window.chrome) { } this.connector = function(request) { - if (request.name === 'canLoad') { - return; - } - var callback = function(response) { if (request.message.requestId && response !== undefined) { request.target.page.dispatchMessage( @@ -689,6 +681,9 @@ if (window.chrome) { } }; + // the third parameter must stay false (bubbling), so later + // onBeforeRequest will use true (capturing), where we can invoke + // stopPropagation() (this way this.connector won't be fired) safari.application.addEventListener('message', this.connector, false); }, broadcast: function(message) { @@ -705,8 +700,6 @@ if (window.chrome) { vAPI.net = { registerListeners: function() { - // onBeforeRequest is used in the messaging above, in the connector method - // in order to use only one listener var onBeforeRequest = this.onBeforeRequest; if (typeof onBeforeRequest.callback === 'function') { @@ -715,64 +708,79 @@ if (window.chrome) { } onBeforeRequest = onBeforeRequest.callback; - this.onBeforeRequest.callback = function(request) { - if (request.name !== 'canLoad') { + this.onBeforeRequest.callback = function(e) { + if (e.name !== 'canLoad') { return; } // no stopPropagation if it was called from beforeNavigate event - if (request.stopPropagation) { - request.stopPropagation(); + if (e.stopPropagation) { + e.stopPropagation(); } - var block = vAPI.net.onBeforeRequest; + // blocking unwanted pop-ups + if (e.message.type === 'popup') { + if (typeof vAPI.tabs.onPopup === 'function') { + e.message.type = 'main_frame'; + e.message.sourceTabId = vAPI.tabs.getTabId(e.target); + + if (vAPI.tabs.onPopup(e.message)) { + e.message = false; + return; + } + } - if (block.types.indexOf(request.message.type) < 0) { + e.message = true; return; } - request.message.tabId = vAPI.tabs.getTabId(request.target); - block = onBeforeRequest(request.message); + var block = vAPI.net.onBeforeRequest; + + if (block.types.indexOf(e.message.type) < 0) { + return true; + } + + e.message.tabId = vAPI.tabs.getTabId(e.target); + block = onBeforeRequest(e.message); // truthy return value will allow the request, // except when redirectUrl is present if (block && typeof block === 'object') { if (block.cancel) { - request.message = false; + e.message = false; } - else if (typeof block.redirectUrl === "string") { - request.message = block.redirectUrl; + else if (e.message.type === 'script' + && typeof block.redirectUrl === "string") { + e.message = block.redirectUrl; } else { - request.message = true; + e.message = true; } } else { - request.message = true; + e.message = true; } - return request.message; + return e.message; }; safari.application.addEventListener('message', this.onBeforeRequest.callback, true); // 'main_frame' simulation, since this isn't available in beforeload safari.application.addEventListener('beforeNavigate', function(e) { // e.url is not present for local files or data URIs - if (!e.url) { - return; + if (e.url) { + vAPI.net.onBeforeRequest.callback({ + name: 'canLoad', + target: e.target, + message: { + url: e.url, + type: 'main_frame', + frameId: 0, + parentFrameId: -1, + timeStamp: e.timeStamp + } + }) || e.preventDefault(); } - - vAPI.net.onBeforeRequest.callback({ - name: 'canLoad', - target: e.target, - message: { - url: e.url, - type: 'main_frame', - frameId: 0, - parentFrameId: -1, - timeStamp: e.timeStamp - } - }) || e.preventDefault(); }, true); } } @@ -861,7 +869,7 @@ if (window.chrome) { safari.application.addEventListener('contextmenu', this.onContextMenu); safari.application.addEventListener("command", this.onContextMenuCommand); }, - remove: function(argument) { + remove: function() { safari.application.removeEventListener('contextmenu', this.onContextMenu); safari.application.removeEventListener("command", this.onContextMenuCommand); this.onContextMenu = null;