diff --git a/meta/crx/manifest.json b/meta/crx/manifest.json index be0a7081b..0f5d2262e 100644 --- a/meta/crx/manifest.json +++ b/meta/crx/manifest.json @@ -5,7 +5,7 @@ "update_url": "https://clients2.google.com/service/update2/crx", "version": "{version}", - "name": "__MSG_extName__", + "name": "{name}", "description": "__MSG_extShortDesc__", "homepage_url": "{url}", "author": "{author}", diff --git a/src/Info.plist b/src/Info.plist index 2f5bbe56c..edf9a8349 100644 --- a/src/Info.plist +++ b/src/Info.plist @@ -15,7 +15,7 @@ CFBundleShortVersionString 0.7.0.7 CFBundleVersion - 1452580 + 1453267 Chrome Database Quota diff --git a/src/js/storage.js b/src/js/storage.js index fb7b6103a..255e3ea1b 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -610,6 +610,11 @@ }, scriptDone); }; var scriptStart = function(tabId) { + vAPI.tabs.injectScript(tabId, { + file: 'js/vapi-client.js', + allFrames: true, + runAt: 'document_start' + }, function(){ }); vAPI.tabs.injectScript(tabId, { file: 'js/contentscript-start.js', allFrames: true, diff --git a/src/js/tab.js b/src/js/tab.js index 71a699337..0128deb73 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -69,9 +69,9 @@ vAPI.tabs.onPopup = function(details) { } // https://github.com/gorhill/uBlock/issues/91 - if ( result !== '' ) { - pageStore.recordResult('popup', requestURL, result); - } + if ( result !== '' ) { + pageStore.recordResult('popup', requestURL, result); + } // Not blocked if ( pageStore.boolFromResult(result) === false ) { @@ -79,6 +79,12 @@ vAPI.tabs.onPopup = function(details) { } // Blocked + + // Safari blocks before the pop-up opens, so there is no window to remove. + if (vAPI.safari) { + return true; + } + // It is a popup, block and remove the tab. µBlock.unbindTabFromPageStats(details.tabId); µBlock.XAL.destroyTab(details.tabId); diff --git a/src/js/vapi-background.js b/src/js/vapi-background.js index 837c845a0..a4918859a 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(); + } + + // 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; + } + } + + e.message = true; + return; } var block = vAPI.net.onBeforeRequest; - if (block.types.indexOf(request.message.type) < 0) { - return; + if (block.types.indexOf(e.message.type) < 0) { + return true; } - request.message.tabId = vAPI.tabs.getTabId(request.target); - block = onBeforeRequest(request.message); + 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; diff --git a/src/js/vapi-client.js b/src/js/vapi-client.js index d350fcbce..e9bfd63d1 100644 --- a/src/js/vapi-client.js +++ b/src/js/vapi-client.js @@ -1,3 +1,4 @@ +/* global addMessageListener, removeMessageListener, sendAsyncMessage */ // for non background pages (function() { @@ -200,7 +201,6 @@ if (window.chrome) { if (details) { details.url = linkHelper.href; - details.type = 'xmlhttprequest'; } else { details = { @@ -237,6 +237,11 @@ if (window.chrome) { default: details.type = 'other'; } + + // This can run even before the first DOMSubtreeModified event fired + if (firstMutation) { + firstMutation(); + } } // tabId is determined in the background script @@ -252,25 +257,22 @@ if (window.chrome) { return false; } // local mirroring, response is a data: URL here - else if (typeof response === 'string') { - if (details.type === 'script') { - e.preventDefault(); - return response; - } - else if (details.type === 'script') { - e.preventDefault(); - details = document.createElement('script'); - details.textContent = atob(response.slice(35)); - e.target.parentNode.insertBefore(details, e.target); - details.parentNode.removeChild(details); - } + else if (typeof response === 'string' && details.type === 'script') { + // Content Security Policy with disallowed inline scripts may break things + e.preventDefault(); + details = document.createElement('script'); + details.textContent = atob(response.slice(response.indexOf(',', 20) + 1)); + e.target.parentNode.insertBefore(details, e.target); + details.parentNode.removeChild(details); } }; document.addEventListener('beforeload', onBeforeLoad, true); - // intercepting xhr requests - setTimeout(function() { + // blocking pop-ups and intercepting xhr requests + var firstMutation = function() { + document.removeEventListener('DOMSubtreeModified', firstMutation, true); + firstMutation = null; var randomEventName = parseInt(Math.random() * 1e15, 10).toString(36); var beforeLoadEvent = document.createEvent('Event'); beforeLoadEvent.initEvent('beforeload'); @@ -278,41 +280,35 @@ if (window.chrome) { window.addEventListener(randomEventName, function(e) { var result = onBeforeLoad(beforeLoadEvent, e.detail); - if (onBeforeLoad(beforeLoadEvent, e.detail) === false) { + if (result === false) { e.detail.url = false; } - else if (typeof result === 'string') { - e.detail.url = result; - } }, true); - // since the extension context is unable to reach the page context - var tempScript = document.createElement('script'); - tempScript.onload = function() { - this.parentNode.removeChild(this); - }; - document.head.appendChild(tempScript).src = "data:application/x-javascript;base64," + btoa(["(function() {", - "var xhr_open = XMLHttpRequest.prototype.open;", - - "XMLHttpRequest.prototype.open = function(method, url, async, u, p) {", - "var ev = document.createEvent('CustomEvent');", - "var detail = {url: url};", - "ev.initCustomEvent(", - "'" + randomEventName + "',", - "false, false,", - "detail", + // the extension context is unable to reach the page context, + // also this only works when Content Security Policy allows inline scripts + var tmpJS = document.createElement('script'); + tmpJS.textContent = ["(function() {", + "var block = function(u, t) {", + "var e = document.createEvent('CustomEvent'),", + "d = {url: u, type: t};", + "e.initCustomEvent(", + "'" + randomEventName + "', !1, !1, d", ");", - "window.dispatchEvent(ev);", - "if (detail.url === false) {", - "throw Error;", - "}", - "else if (typeof detail.url === 'string') {", - "url = detail.url;", - "}", - "return xhr_open.call(this, method, url, async, u, p);", + "dispatchEvent(e);", + "return d.url === !1;", + "}, wo = open, xo = XMLHttpRequest.prototype.open;", + "open = function(u) {", + "return block(u, 'popup') ? null : wo.apply(this, [].slice.call(arguments));", "};", - "})();"].join('')); - }, 0); + "XMLHttpRequest.prototype.open = function(m, u) {", + "return block(u, 'xmlhttprequest') ? null : xo.apply(this, [].slice.call(arguments));", + "};", + "})();"].join(''); + document.head.removeChild(document.head.appendChild(tmpJS)); + }; + + document.addEventListener('DOMSubtreeModified', firstMutation, true); var onContextMenu = function(e) { var details = { diff --git a/src/manifest.json b/src/manifest.json index 4fc5f9144..f42047fa6 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -5,7 +5,7 @@ "update_url": "https://clients2.google.com/service/update2/crx", "version": "0.7.0.10", - "name": "__MSG_extName__", + "name": "µBlock", "description": "__MSG_extShortDesc__", "homepage_url": "https://github.com/gorhill/uBlock", "author": "Raymond Hill",