Site-patching possibility for Safari

Safari's extension API doesn't provide a way to intercept requests
initiated by plugins, so those cases need special care (or at least the
popular sites).

This commit adds a new JS file (sitepatch-safari.js), which will store the
patches (if it's possible to create one) for specific sites.

As an example, this commit includes a technique for removing in-video ads
from YouTube videos.
This commit is contained in:
Deathamns 2014-11-02 17:20:06 +01:00
parent e6e3f676e2
commit d114bf21e7
7 changed files with 148 additions and 28 deletions

View file

@ -13,6 +13,7 @@
"newcap": false,
"-W058": true, // suppress "Missing '()' invoking a constructor" message
"globals": {
"self": false,
"vAPI": false,
"chrome": false,
"safari": false,

View file

@ -108,7 +108,7 @@
};
}
}(this));
}(self));
/******************************************************************************/
/******************************************************************************/

View file

@ -0,0 +1,91 @@
// Only for Safari
// Adding new URL requires to whitelist it in the background script too (addContentScriptFromURL)
// Note that the sitePach function will be converted to a string, and injected
// into the web-page in order to run in that scope. Because of this, variables
// from the extension scope won't be accessible in the sitePatch function.
'use strict';
self.vAPI = self.vAPI || {};
if (/^www\.youtube(-nocookie)?\.com/.test(location.host)) {
vAPI.sitePatch = function() {
window.addEventListener('load', function onWindowLoad() {
this.removeEventListener('load', onWindowLoad, true);
var spf = this._spf_state;
if (spf && (spf = spf.config)) {
spf['navigate-limit'] = 0;
spf['navigate-part-received-callback'] = function(url) {
window.location.href = url;
};
}
}, true);
// based on ExtendTube's ad removing solution
var p, yt = {}, config_ = {}, ytplayer = {}, playerConfig = { args: {} };
Object.defineProperties(yt, {
'playerConfig': {
get: function() { return playerConfig; },
set: function(data) {
if (data && typeof data === 'object'
&& data.args && typeof data.args === 'object') {
var nope = /ad\d?_|afv|watermark|adsense|xfp/;
for (var prop in data.args) {
if (nope.test(prop) && !/policy/.test(prop)) {
delete data.args[prop];
}
}
}
playerConfig = data;
var playerRoot = document.querySelector('[data-swf-config]');
if (playerRoot)
playerRoot.dataset.swfConfig = JSON.stringify(yt.playerConfig);
}
},
'config_': {
get: function() { return config_; },
set: function(value) { config_ = value; }
}
});
Object.defineProperty(config_, 'PLAYER_CONFIG', {
get: function() { return yt.playerConfig; },
set: function(value) { yt.playerConfig = value; }
});
Object.defineProperty(ytplayer, 'config', {
get: function() { return playerConfig; },
set: function(value) { yt.playerConfig = value; }
});
if (window.yt) {
for (p in window.yt) { yt[p] = window.yt[p]; }
window.yt = yt;
}
else {
Object.defineProperty(window, 'yt', {
get: function() { return yt; },
set: function() {}
});
}
if (window.ytplayer) {
for (p in window.ytplayer) { ytplayer[p] = window.ytplayer[p]; }
window.ytplayer = ytplayer;
}
else {
Object.defineProperty(window, 'ytplayer', {
get: function() { return ytplayer; },
set: function() {}
});
}
};
}
/*else if (check url) {
vAPI.sitePatch do something
}*/

View file

@ -1,7 +1,7 @@
// can be included anywhere if it's needed
'use strict';
window.vAPI = window.vAPI || {};
self.vAPI = self.vAPI || {};
vAPI.app = {
/**/name: 'µBlock',

View file

@ -4,10 +4,10 @@
(function() {
'use strict';
window.vAPI = window.vAPI || {};
self.vAPI = self.vAPI || {};
if (window.chrome) {
var chrome = window.chrome;
if (self.chrome) {
var chrome = self.chrome;
vAPI.chrome = true;
@ -272,9 +272,22 @@ if (window.chrome) {
chrome.contextMenus.remove(this.menuId);
}
};
} else if (window.safari) {
} else if (self.safari) {
vAPI.safari = true;
// addContentScriptFromURL allows whitelisting,
// so load sitepaching this way, instead of adding it to the Info.plist
safari.extension.addContentScriptFromURL(
safari.extension.baseURI + 'js/sitepatch-safari.js',
[
'http://www.youtube.com/*',
'https://www.youtube.com/*',
'http://www.youtube-nocookie.com/*',
'https://www.youtube-nocookie.com/*'
]
);
vAPI.storage = {
_storage: safari.extension.settings,
QUOTA_BYTES: 52428800, // copied from Info.plist
@ -709,6 +722,8 @@ if (window.chrome) {
onBeforeRequest = onBeforeRequest.callback;
this.onBeforeRequest.callback = function(e) {
var block;
if (e.name !== 'canLoad') {
return;
}
@ -718,6 +733,13 @@ if (window.chrome) {
e.stopPropagation();
}
if (e.message.isWhiteListed) {
block = µBlock.URI.hostnameFromURI(e.message.isWhiteListed);
block = µBlock.URI.domainFromHostname(block) || block;
e.message = !!µBlock.netWhitelist[block];
return e.message;
}
if (e.message.middleClickURL) {
vAPI.lastMiddleClick = e.message;
return;
@ -739,7 +761,7 @@ if (window.chrome) {
return;
}
var block = vAPI.net.onBeforeRequest;
block = vAPI.net.onBeforeRequest;
if (block.types.indexOf(e.message.type) < 0) {
return true;
@ -897,7 +919,7 @@ if (window.chrome) {
};
}
if (!window.chrome) {
window.chrome = { runtime: { lastError: null } };
if (!self.chrome) {
self.chrome = { runtime: { lastError: null } };
}
})();

View file

@ -4,7 +4,7 @@
(function() {
'use strict';
window.vAPI = window.vAPI || {};
self.vAPI = self.vAPI || {};
// since this is common across vendors
var messagingConnector = function(response) {
@ -48,7 +48,7 @@ var messagingConnector = function(response) {
}
};
if (window.chrome) {
if (self.chrome) {
vAPI.chrome = true;
vAPI.messaging = {
port: null,
@ -102,7 +102,7 @@ if (window.chrome) {
return this.channels[name];
}
};
} else if (window.safari) {
} else if (self.safari) {
vAPI.safari = true;
// relevant?
@ -121,7 +121,7 @@ if (window.chrome) {
this.channels['vAPI'] = {
listener: function(msg) {
if (msg.cmd === 'runScript' && msg.details.code) {
Function(msg.details.code).call(window);
Function(msg.details.code).call(self);
}
}
};
@ -272,13 +272,13 @@ if (window.chrome) {
document.addEventListener('beforeload', onBeforeLoad, true);
// blocking pop-ups and intercepting xhr requests
// block pop-ups, intercept xhr requests, and apply site patches
var firstMutation = function() {
document.removeEventListener('DOMSubtreeModified', firstMutation, true);
firstMutation = null;
var randomEventName = parseInt(Math.random() * 1e15, 10).toString(36);
var randEventName = parseInt(Math.random() * 1e15, 10).toString(36);
window.addEventListener(randomEventName, function(e) {
window.addEventListener(randEventName, function(e) {
var result = onBeforeLoad(beforeLoadEvent, e.detail);
if (result === false) {
@ -289,13 +289,11 @@ if (window.chrome) {
// 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 tmpScript = ["(function() {",
"var block = function(u, t) {",
"var e = document.createEvent('CustomEvent'),",
"d = {url: u, type: t};",
"e.initCustomEvent(",
"'" + randomEventName + "', !1, !1, d",
");",
"e.initCustomEvent('" + randEventName + "', !1, !1, d);",
"dispatchEvent(e);",
"return d.url === !1;",
"}, wo = open, xo = XMLHttpRequest.prototype.open;",
@ -304,8 +302,16 @@ if (window.chrome) {
"};",
"XMLHttpRequest.prototype.open = function(m, u) {",
"return block(u, 'xmlhttprequest') ? null : xo.apply(this, [].slice.call(arguments));",
"};",
"})();"].join('');
"};"
];
if (vAPI.sitePatch
&& !safari.self.tab.canLoad(beforeLoadEvent, {isWhiteListed: location.href})) {
tmpScript.push('(' + vAPI.sitePatch + ')();');
}
tmpScript.push("})();");
tmpJS.textContent = tmpScript.join('');
document.documentElement.removeChild(document.documentElement.appendChild(tmpJS));
};
@ -342,9 +348,9 @@ if (window.chrome) {
safari.self.tab.setContextMenuEventUserInfo(e, details);
};
window.addEventListener('contextmenu', onContextMenu, true);
self.addEventListener('contextmenu', onContextMenu, true);
window.addEventListener('mouseup', function(e) {
self.addEventListener('mouseup', function(e) {
if (e.button !== 1) {
return;
}

View file

@ -3,7 +3,7 @@
(function() {
'use strict';
window.vAPI = window.vAPI || {};
self.vAPI = self.vAPI || {};
// http://www.w3.org/International/questions/qa-scripts#directions
var setScriptDirection = function(langugae) {
@ -38,8 +38,8 @@ vAPI.download = function(details) {
}
};
if (window.chrome) {
var chrome = window.chrome;
if (self.chrome) {
var chrome = self.chrome;
vAPI.getURL = function(path) {
return chrome.runtime.getURL(path);
@ -50,7 +50,7 @@ if (window.chrome) {
};
setScriptDirection(vAPI.i18n('@@ui_locale'));
} else if (window.safari) {
} else if (self.safari) {
vAPI.getURL = function(path) {
return safari.extension.baseURI + path;
};