aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDeathamns <deathamns@gmail.com>2014-10-24 16:14:25 +0200
committerDeathamns <deathamns@gmail.com>2014-11-09 17:40:44 +0100
commitf6f85ec793978d62d5268ce7a1402621f153a0ec (patch)
tree4c9fc4d317771053a6bb54f4888f5a3568394092
parentac272afb4bcbc1a2296c091d91347b4cd2d2d9b5 (diff)
downloaduBlock-f6f85ec793978d62d5268ce7a1402621f153a0ec.zip
uBlock-f6f85ec793978d62d5268ce7a1402621f153a0ec.tar.gz
uBlock-f6f85ec793978d62d5268ce7a1402621f153a0ec.tar.bz2
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).
-rw-r--r--meta/crx/manifest.json2
-rw-r--r--src/Info.plist2
-rw-r--r--src/js/storage.js5
-rw-r--r--src/js/tab.js12
-rw-r--r--src/js/vapi-background.js96
-rw-r--r--src/js/vapi-client.js84
-rw-r--r--src/manifest.json2
7 files changed, 109 insertions, 94 deletions
diff --git a/meta/crx/manifest.json b/meta/crx/manifest.json
index be0a708..0f5d226 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 2f5bbe5..edf9a83 100644
--- a/src/Info.plist
+++ b/src/Info.plist
@@ -15,7 +15,7 @@
<key>CFBundleShortVersionString</key>
<string>0.7.0.7</string>
<key>CFBundleVersion</key>
- <string>1452580</string>
+ <string>1453267</string>
<key>Chrome</key>
<dict>
<key>Database Quota</key>
diff --git a/src/js/storage.js b/src/js/storage.js
index fb7b610..255e3ea 100644
--- a/src/js/storage.js
+++ b/src/js/storage.js
@@ -611,6 +611,11 @@
};
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,
runAt: 'document_idle'
diff --git a/src/js/tab.js b/src/js/tab.js
index 71a6993..0128deb 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 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;
diff --git a/src/js/vapi-client.js b/src/js/vapi-client.js
index d350fcb..e9bfd63 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));",
+ "};",
+ "XMLHttpRequest.prototype.open = function(m, u) {",
+ "return block(u, 'xmlhttprequest') ? null : xo.apply(this, [].slice.call(arguments));",
"};",
- "})();"].join(''));
- }, 0);
+ "})();"].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 4fc5f91..f42047f 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",