Safari fixes and code reformatting

This commit is contained in:
Chris 2015-01-27 22:39:55 -07:00
parent a51a2666a9
commit 5a4f1b57cc
2 changed files with 323 additions and 372 deletions

View File

@ -1,104 +1,109 @@
/******************************************************************************* /*******************************************************************************
µBlock - a browser extension to block requests. µBlock - a browser extension to block requests.
Copyright (C) 2014 The µBlock authors Copyright (C) 2014 The µBlock authors
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}. along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/******************************************************************************/
// 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.
var vAPI = self.vAPI = self.vAPI || {}; var vAPI = self.vAPI = self.vAPI || {};
vAPI.sitePatch = function() { vAPI.sitePatch = function() {
// disable spf // disable spf
window.ytspf = {}; window.ytspf = {};
Object.defineProperty(ytspf, 'enabled', {'value': false}); Object.defineProperty(ytspf, "enabled", {
"value": false
});
// Based on ExtendTube's ad removing solution // Based on ExtendTube's ad removing solution
var p; var p;
var yt = {}; var yt = {};
var config_ = {}; var config_ = {};
var ytplayer = {}; var ytplayer = {};
var playerConfig = { args: {} }; var playerConfig = {
args: {}
};
Object.defineProperties(yt, { Object.defineProperties(yt, {
'playerConfig': { "playerConfig": {
get: function() { return playerConfig; }, get: function() {
return playerConfig;
},
set: function(data) { set: function(data) {
if ( data && typeof data === 'object' if(data && typeof data === "object" && data.args && typeof data.args === "object") {
&& data.args && typeof data.args === 'object' ) {
var nope = /ad\d?_|afv|watermark|adsense|xfp/; var nope = /ad\d?_|afv|watermark|adsense|xfp/;
for(var prop in data.args) {
for ( var prop in data.args ) { if(nope.test(prop) && !/policy/.test(prop)) {
if ( nope.test(prop) && !/policy/.test(prop) ) {
delete data.args[prop]; delete data.args[prop];
} }
} }
} }
playerConfig = data; playerConfig = data;
var playerRoot = document.querySelector("[data-swf-config]");
var playerRoot = document.querySelector('[data-swf-config]'); if(playerRoot) {
if ( playerRoot ) {
playerRoot.dataset.swfConfig = JSON.stringify(yt.playerConfig); playerRoot.dataset.swfConfig = JSON.stringify(yt.playerConfig);
} }
} }
}, },
'config_': { "config_": {
get: function() { return config_; }, get: function() {
set: function(value) { config_ = value; } return config_;
},
set: function(value) {
config_ = value;
}
} }
}); });
Object.defineProperty(config_, "PLAYER_CONFIG", {
Object.defineProperty(config_, 'PLAYER_CONFIG', { get: function() {
get: function() { return yt.playerConfig; }, return yt.playerConfig;
set: function(value) { yt.playerConfig = value; } },
set: function(value) {
yt.playerConfig = value;
}
}); });
Object.defineProperty(ytplayer, "config", {
Object.defineProperty(ytplayer, 'config', { get: function() {
get: function() { return playerConfig; }, return playerConfig;
set: function(value) { yt.playerConfig = value; } },
set: function(value) {
yt.playerConfig = value;
}
}); });
if(window.yt) {
if ( window.yt ) {
var oldyt = window.yt; var oldyt = window.yt;
delete window.yt; delete window.yt;
for ( p in oldyt ) { yt[p] = oldyt[p]; } for(p in oldyt) {
yt[p] = oldyt[p];
} }
Object.defineProperty(window, 'yt', { }
get: function() { return yt; }, if(window.ytplayer) {
var oldytplayer = window.ytplayer;
delete window.ytplayer;
for(p in oldytplayer) {
ytplayer[p] = oldytplayer[p];
}
}
Object.defineProperty(window, "yt", {
get: function() {
return yt;
},
set: function() {} set: function() {}
}); });
Object.defineProperty(window, "ytplayer", {
if ( window.ytplayer ) { get: function() {
for ( p in window.ytplayer ) { ytplayer[p] = window.ytplayer[p]; } return ytplayer;
window.ytplayer = ytplayer; },
}
else {
Object.defineProperty(window, 'ytplayer', {
get: function() { return ytplayer; },
set: function() {} set: function() {}
}); });
}
}; };

View File

@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
µBlock - a browser extension to block requests. µBlock - a browser extension to block requests.
Copyright (C) 2014 The µBlock authors Copyright (C) 2015 The µBlock authors
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -18,134 +18,108 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/******************************************************************************/
// For non background pages // For non background pages
/******************************************************************************/
(function() { (function() {
'use strict';
'use strict'; var vAPI = self.vAPI = self.vAPI || {};
vAPI.safari = true;
var vAPI = self.vAPI = self.vAPI || {}; /******************************************************************************/
vAPI.safari = true; var messagingConnector = function(response) {
if(!response) {
/******************************************************************************/
var messagingConnector = function(response) {
if ( !response ) {
return; return;
} }
var channels = vAPI.messaging.channels; var channels = vAPI.messaging.channels;
var channel, listener; var channel, listener;
if(response.broadcast === true && !response.channelName) {
if ( response.broadcast === true && !response.channelName ) { for(channel in channels) {
for ( channel in channels ) { if(channels.hasOwnProperty(channel) === false) {
if ( channels.hasOwnProperty(channel) === false ) {
continue; continue;
} }
listener = channels[channel].listener; listener = channels[channel].listener;
if ( typeof listener === 'function' ) { if(typeof listener === 'function') {
listener(response.msg); listener(response.msg);
} }
} }
return; return;
} }
if(response.requestId) {
if ( response.requestId ) {
listener = vAPI.messaging.listeners[response.requestId]; listener = vAPI.messaging.listeners[response.requestId];
delete vAPI.messaging.listeners[response.requestId]; delete vAPI.messaging.listeners[response.requestId];
delete response.requestId; delete response.requestId;
} }
if(!listener) {
if ( !listener ) {
channel = channels[response.channelName]; channel = channels[response.channelName];
listener = channel && channel.listener; listener = channel && channel.listener;
} }
if(typeof listener === 'function') {
if ( typeof listener === 'function' ) {
listener(response.msg); listener(response.msg);
} }
}; };
/******************************************************************************/
/******************************************************************************/ var uniqueId = function() {
var uniqueId = function() {
return Math.random().toString(36).slice(2); return Math.random().toString(36).slice(2);
}; };
/******************************************************************************/
/******************************************************************************/ // Relevant?
// https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/MessagesandProxies/MessagesandProxies.html#//apple_ref/doc/uid/TP40009977-CH14-SW12
// Relevant? vAPI.messaging = {
// https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/MessagesandProxies/MessagesandProxies.html#//apple_ref/doc/uid/TP40009977-CH14-SW12
vAPI.messaging = {
channels: {}, channels: {},
listeners: {}, listeners: {},
requestId: 1, requestId: 1,
connectorId: uniqueId(), connectorId: uniqueId(),
setup: function() { setup: function() {
this.connector = function(msg) { this.connector = function(msg) {
// messages from the background script are sent to every frame, // messages from the background script are sent to every frame,
// so we need to check the connectorId to accept only // so we need to check the connectorId to accept only
// what is meant for the current context // what is meant for the current context
if ( msg.name === vAPI.messaging.connectorId if(msg.name === vAPI.messaging.connectorId || msg.name === 'broadcast') {
|| msg.name === 'broadcast' ) {
messagingConnector(msg.message); messagingConnector(msg.message);
} }
}; };
safari.self.addEventListener('message', this.connector, false); safari.self.addEventListener('message', this.connector, false);
this.channels['vAPI'] = { this.channels['vAPI'] = {
listener: function(msg) { listener: function(msg) {
if ( msg.cmd === 'injectScript' && msg.details.code ) { if(msg.cmd === 'injectScript' && msg.details.code) {
Function(msg.details.code).call(self); Function(msg.details.code).call(self);
} }
} }
}; };
}, },
close: function() { close: function() {
if ( this.connector ) { if(this.connector) {
safari.self.removeEventListener('message', this.connector, false); safari.self.removeEventListener('message', this.connector, false);
this.connector = null; this.connector = null;
this.channels = {}; this.channels = {};
this.listeners = {}; this.listeners = {};
} }
}, },
channel: function(channelName, callback) { channel: function(channelName, callback) {
if ( !channelName ) { if(!channelName) {
return; return;
} }
this.channels[channelName] = { this.channels[channelName] = {
channelName: channelName, channelName: channelName,
listener: typeof callback === 'function' ? callback : null, listener: typeof callback === 'function' ? callback : null,
send: function(message, callback) { send: function(message, callback) {
if ( !vAPI.messaging.connector ) { if(!vAPI.messaging.connector) {
vAPI.messaging.setup(); vAPI.messaging.setup();
} }
message = { message = {
channelName: this.channelName, channelName: this.channelName,
msg: message msg: message
}; };
if(callback) {
if ( callback ) {
message.requestId = vAPI.messaging.requestId++; message.requestId = vAPI.messaging.requestId++;
vAPI.messaging.listeners[message.requestId] = callback; vAPI.messaging.listeners[message.requestId] = callback;
} }
// popover content doesn't know messaging... // popover content doesn't know messaging...
if ( safari.extension.globalPage ) { if(safari.extension.globalPage) {
if ( !safari.self.visible ) { if(!safari.self.visible) {
return; return;
} }
safari.extension.globalPage.contentWindow.vAPI.messaging.onMessage({
safari.extension.globalPage.contentWindow
.vAPI.messaging.onMessage({
name: vAPI.messaging.connectorId, name: vAPI.messaging.connectorId,
message: message, message: message,
target: { target: {
@ -157,52 +131,42 @@ vAPI.messaging = {
} }
}); });
} else { } else {
safari.self.tab.dispatchMessage( safari.self.tab.dispatchMessage(vAPI.messaging.connectorId, message);
vAPI.messaging.connectorId,
message
);
} }
}, },
close: function() { close: function() {
delete vAPI.messaging.channels[this.channelName]; delete vAPI.messaging.channels[this.channelName];
} }
}; };
return this.channels[channelName]; return this.channels[channelName];
} }
}; };
/******************************************************************************/ vAPI.canExecuteContentScript = function() {
return(/^https?:/.test(location.protocol) && typeof safari === "object");
};
vAPI.canExecuteContentScript = function() { // The following code should run only in content pages
return (/^https?:/.test(location.protocol) && typeof safari === 'object'); if(location.protocol === "safari-extension:" || typeof safari !== "object") {
};
/******************************************************************************/
// This file can be included into extensin pages,
// but the following code should run only in content pages.
if ( location.protocol === 'safari-extension:' || typeof safari !== 'object' ) {
return; return;
} }
/******************************************************************************/ var frameId = window === window.top ? 0 : Date.now() % 1E5;
var parentFrameId = (frameId ? 0 : -1);
var frameId = window === window.top ? 0 : Date.now() % 1E5; // Helper event to message background,
var parentFrameId = (frameId ? 0 : -1); // and helper anchor element
var beforeLoadEvent = new Event("beforeload"); // Helper event to message background var beforeLoadEvent = new Event("beforeload"),
linkHelper = document.createElement("a");
// Inform that we've navigated // Inform that we've navigated
if(frameId === 0) { if(frameId === 0) {
safari.self.tab.canLoad(beforeLoadEvent, { safari.self.tab.canLoad(beforeLoadEvent, {
url: location.href, url: location.href,
type: "navigatedToNew" type: "navigatedToNew"
}); });
} }
var nodeTypes = {
var linkHelper = document.createElement("a");
var nodeTypes = {
"frame": "sub_frame", "frame": "sub_frame",
"iframe": "sub_frame", "iframe": "sub_frame",
"script": "script", "script": "script",
@ -211,16 +175,16 @@ var nodeTypes = {
"object": "object", "object": "object",
"embed": "object", "embed": "object",
"link": "stylesheet" "link": "stylesheet"
}; };
var shouldBlockDetailedRequest = function(details) { var shouldBlockDetailedRequest = function(details) {
linkHelper.href = details.url; linkHelper.href = details.url;
details.url = linkHelper.href; details.url = linkHelper.href;
details.frameId = frameId; details.frameId = frameId;
details.parentFrameId = parentFrameId; details.parentFrameId = parentFrameId;
details.timeStamp = Date.now(); details.timeStamp = Date.now();
return !(safari.self.tab.canLoad(beforeLoadEvent, details)); return !(safari.self.tab.canLoad(beforeLoadEvent, details));
} }
var onBeforeLoad = function(e) { var onBeforeLoad = function(e) {
if(e.url.lastIndexOf("data:", 0) === 0) { if(e.url.lastIndexOf("data:", 0) === 0) {
return; return;
} }
@ -228,7 +192,7 @@ var onBeforeLoad = function(e) {
var url = linkHelper.href; var url = linkHelper.href;
var details = { var details = {
url: url, url: url,
type: nodeTypes[e.target.nodeName.toLowerCase()] || 'other', type: nodeTypes[e.target.nodeName.toLowerCase()] || "other",
// tabId is determined in the background script // tabId is determined in the background script
frameId: frameId, frameId: frameId,
parentFrameId: parentFrameId, parentFrameId: parentFrameId,
@ -238,83 +202,76 @@ var onBeforeLoad = function(e) {
if(!response) { if(!response) {
e.preventDefault(); e.preventDefault();
} }
}; };
document.addEventListener("beforeload", onBeforeLoad, true);
document.addEventListener('beforeload', onBeforeLoad, true); // Block popups, intercept XHRs, and add site patches
var firstMutation = function() {
/******************************************************************************/ document.removeEventListener("DOMContentLoaded", firstMutation, true);
// block pop-ups, intercept xhr requests, and apply site patches
var firstMutation = function() {
document.removeEventListener('DOMContentLoaded', firstMutation, true);
firstMutation = false; firstMutation = false;
var randEventName = uniqueId(); var randEventName = uniqueId();
window.addEventListener(randEventName, function(e) { window.addEventListener(randEventName, function(e) {
if(shouldBlockDetailedRequest(e.detail)) { if(shouldBlockDetailedRequest(e.detail)) {
e.detail.url = false; e.detail.url = false;
};
}, true);
// 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');
var tmpScript = ['(function() {',
'var block = function(u, t) {',
'var e = new CustomEvent("' + randEventName +'",',
'{detail: {url: u, type: t}, bubbles: false});',
'dispatchEvent(e);',
'return e.detail.url === false;',
'}, wo = open, xo = XMLHttpRequest.prototype.open;',
'open = function(u) {',
'return block(u, "popup") ? null : wo.apply(this, arguments);',
'};',
'XMLHttpRequest.prototype.open = function(m, u, s) {',
'return xo.apply(',
'this,',
'block(u, "xmlhttprequest") ? ["HEAD", u, s] : arguments',
');',
'};'
];
if(frameId === 0) {
tmpScript.push(
'var pS = history.pushState, rS = history.replaceState,',
'onpopstate = function(e) {',
'if (!e || e.state !== null) block(location.href, "popstate");',
'};',
'window.addEventListener("popstate", onpopstate, true);',
'history.pushState = function() {',
'var r = pS.apply(this, arguments);',
'onpopstate();',
'return r;',
'};',
'history.replaceState = function() {',
'var r = rS.apply(this, arguments);',
'onpopstate();',
'return r;',
'};'
);
} }
}, true);
var tmpJS = document.createElement("script");
var tmpScript = "\
(function() {\
var block = function(u, t) {\
var e = new CustomEvent('" + randEventName + "', {\
detail: {\
url: u,\
type: t\
},\
bubbles: false\
});\
dispatchEvent(e);\
return e.detail.url === false;\
},\
wo = open,\
xo = XMLHttpRequest.prototype.open;\
open = function(u) {\
return block(u, 'popup') ? null : wo.apply(this, arguments);\
};\
XMLHttpRequest.prototype.open = function(m, u, s) {\
return xo.apply(this, block(u, 'xmlhttprequest') ? ['HEAD', u, s] : arguments);\
};";
if(frameId === 0) {
tmpScript += "\
var pS = history.pushState,\
rS = history.replaceState,\
onpopstate = function(e) {\
if(!e || e.state !== null) {\
block(location.href, 'popstate');\
}\
};\
window.addEventListener('popstate', onpopstate, true);\
history.pushState = function() {\
var r = pS.apply(this, arguments);\
onpopstate();\
return r;\
};\
history.replaceState = function() {\
var r = rS.apply(this, arguments);\
onpopstate();\
return r;\
};";
}
var whiteListed = safari.self.tab.canLoad(beforeLoadEvent, { var whiteListed = safari.self.tab.canLoad(beforeLoadEvent, {
type: "isWhiteListed", type: "isWhiteListed",
url: location.href url: location.href
}); });
if(vAPI.sitePatch && !whiteListed) { if(vAPI.sitePatch && !whiteListed) {
tmpScript.push('(' + vAPI.sitePatch + ')();'); tmpScript += "(" + vAPI.sitePatch + ")();";
} }
tmpScript += "})();";
tmpScript.push('})();'); tmpJS.textContent = tmpScript;
tmpJS.textContent = tmpScript.join('');
document.documentElement.removeChild(document.documentElement.appendChild(tmpJS)); document.documentElement.removeChild(document.documentElement.appendChild(tmpJS));
}; };
document.addEventListener("DOMContentLoaded", firstMutation, true);
document.addEventListener('DOMContentLoaded', firstMutation, true); var onContextMenu = function(e) {
/******************************************************************************/
var onContextMenu = function(e) {
var target = e.target; var target = e.target;
var tagName = target.tagName.toLowerCase(); var tagName = target.tagName.toLowerCase();
var details = { var details = {
@ -322,34 +279,23 @@ var onContextMenu = function(e) {
pageUrl: location.href, pageUrl: location.href,
insideFrame: window !== window.top insideFrame: window !== window.top
}; };
details.editable = (tagName === "textarea" || tagName === "input");
details.editable = tagName === 'textarea' || tagName === 'input'; if(target.hasOwnProperty("checked")) {
if ( target.hasOwnProperty('checked') ) {
details.checked = target.checked; details.checked = target.checked;
} }
if(tagName === "a") {
if ( tagName === 'a' ) {
details.linkUrl = target.href; details.linkUrl = target.href;
} }
if(target.hasOwnProperty("src")) {
if ( target.hasOwnProperty('src') ) {
details.srcUrl = target.src; details.srcUrl = target.src;
if(tagName === "img") {
if ( tagName === 'img' ) { details.mediaType = "image";
details.mediaType = 'image'; } else if(tagName === "video" || tagName === "audio") {
} else if ( tagName === 'video' || tagName === 'audio' ) {
details.mediaType = tagName; details.mediaType = tagName;
} }
} }
safari.self.tab.setContextMenuEventUserInfo(e, details); safari.self.tab.setContextMenuEventUserInfo(e, details);
}; };
self.addEventListener("contextmenu", onContextMenu, true);
self.addEventListener('contextmenu', onContextMenu, true);
/******************************************************************************/
})(); })();
/******************************************************************************/ /******************************************************************************/