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",