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) {
for ( p in oldyt ) { yt[p] = oldyt[p]; } yt[p] = oldyt[p];
}
} }
Object.defineProperty(window, 'yt', { if(window.ytplayer) {
get: function() { return yt; }, 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() {}
});
Object.defineProperty(window, "ytplayer", {
get: function() {
return ytplayer;
},
set: function() {} 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() {}
});
}
}; };

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;
}
var channels = vAPI.messaging.channels;
var channel, listener;
if ( response.broadcast === true && !response.channelName ) {
for ( channel in channels ) {
if ( channels.hasOwnProperty(channel) === false ) {
continue;
}
listener = channels[channel].listener;
if ( typeof listener === 'function' ) {
listener(response.msg);
}
}
return;
}
if ( response.requestId ) {
listener = vAPI.messaging.listeners[response.requestId];
delete vAPI.messaging.listeners[response.requestId];
delete response.requestId;
}
if ( !listener ) {
channel = channels[response.channelName];
listener = channel && channel.listener;
}
if ( typeof listener === 'function' ) {
listener(response.msg);
}
};
/******************************************************************************/
var uniqueId = function() {
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
vAPI.messaging = {
channels: {},
listeners: {},
requestId: 1,
connectorId: uniqueId(),
setup: function() {
this.connector = function(msg) {
// messages from the background script are sent to every frame,
// so we need to check the connectorId to accept only
// what is meant for the current context
if ( msg.name === vAPI.messaging.connectorId
|| msg.name === 'broadcast' ) {
messagingConnector(msg.message);
}
};
safari.self.addEventListener('message', this.connector, false);
this.channels['vAPI'] = {
listener: function(msg) {
if ( msg.cmd === 'injectScript' && msg.details.code ) {
Function(msg.details.code).call(self);
}
}
};
},
close: function() {
if ( this.connector ) {
safari.self.removeEventListener('message', this.connector, false);
this.connector = null;
this.channels = {};
this.listeners = {};
}
},
channel: function(channelName, callback) {
if ( !channelName ) {
return; return;
} }
var channels = vAPI.messaging.channels;
this.channels[channelName] = { var channel, listener;
channelName: channelName, if(response.broadcast === true && !response.channelName) {
listener: typeof callback === 'function' ? callback : null, for(channel in channels) {
send: function(message, callback) { if(channels.hasOwnProperty(channel) === false) {
if ( !vAPI.messaging.connector ) { continue;
vAPI.messaging.setup();
} }
listener = channels[channel].listener;
message = { if(typeof listener === 'function') {
channelName: this.channelName, listener(response.msg);
msg: message
};
if ( callback ) {
message.requestId = vAPI.messaging.requestId++;
vAPI.messaging.listeners[message.requestId] = callback;
} }
}
// popover content doesn't know messaging... return;
if ( safari.extension.globalPage ) { }
if ( !safari.self.visible ) { if(response.requestId) {
return; listener = vAPI.messaging.listeners[response.requestId];
delete vAPI.messaging.listeners[response.requestId];
delete response.requestId;
}
if(!listener) {
channel = channels[response.channelName];
listener = channel && channel.listener;
}
if(typeof listener === 'function') {
listener(response.msg);
}
};
/******************************************************************************/
var uniqueId = function() {
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
vAPI.messaging = {
channels: {},
listeners: {},
requestId: 1,
connectorId: uniqueId(),
setup: function() {
this.connector = function(msg) {
// messages from the background script are sent to every frame,
// so we need to check the connectorId to accept only
// what is meant for the current context
if(msg.name === vAPI.messaging.connectorId || msg.name === 'broadcast') {
messagingConnector(msg.message);
}
};
safari.self.addEventListener('message', this.connector, false);
this.channels['vAPI'] = {
listener: function(msg) {
if(msg.cmd === 'injectScript' && msg.details.code) {
Function(msg.details.code).call(self);
} }
}
safari.extension.globalPage.contentWindow };
.vAPI.messaging.onMessage({ },
close: function() {
if(this.connector) {
safari.self.removeEventListener('message', this.connector, false);
this.connector = null;
this.channels = {};
this.listeners = {};
}
},
channel: function(channelName, callback) {
if(!channelName) {
return;
}
this.channels[channelName] = {
channelName: channelName,
listener: typeof callback === 'function' ? callback : null,
send: function(message, callback) {
if(!vAPI.messaging.connector) {
vAPI.messaging.setup();
}
message = {
channelName: this.channelName,
msg: message
};
if(callback) {
message.requestId = vAPI.messaging.requestId++;
vAPI.messaging.listeners[message.requestId] = callback;
}
// popover content doesn't know messaging...
if(safari.extension.globalPage) {
if(!safari.self.visible) {
return;
}
safari.extension.globalPage.contentWindow.vAPI.messaging.onMessage({
name: vAPI.messaging.connectorId, name: vAPI.messaging.connectorId,
message: message, message: message,
target: { target: {
@ -156,200 +130,172 @@ vAPI.messaging = {
} }
} }
}); });
} else { } else {
safari.self.tab.dispatchMessage( safari.self.tab.dispatchMessage(vAPI.messaging.connectorId, message);
vAPI.messaging.connectorId, }
message },
); close: function() {
delete vAPI.messaging.channels[this.channelName];
} }
}, };
close: function() { return this.channels[channelName];
delete vAPI.messaging.channels[this.channelName]; }
} };
};
return this.channels[channelName]; vAPI.canExecuteContentScript = function() {
} return(/^https?:/.test(location.protocol) && typeof safari === "object");
}; };
/******************************************************************************/ // The following code should run only in content pages
if(location.protocol === "safari-extension:" || typeof safari !== "object") {
vAPI.canExecuteContentScript = function() {
return (/^https?:/.test(location.protocol) && 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;
}
/******************************************************************************/
var frameId = window === window.top ? 0 : Date.now() % 1E5;
var parentFrameId = (frameId ? 0 : -1);
var beforeLoadEvent = new Event("beforeload"); // Helper event to message background
// Inform that we've navigated
if(frameId === 0) {
safari.self.tab.canLoad(beforeLoadEvent, {
url: location.href,
type: "navigatedToNew"
});
}
var linkHelper = document.createElement("a");
var nodeTypes = {
"frame": "sub_frame",
"iframe": "sub_frame",
"script": "script",
"img": "image",
"input": "image",
"object": "object",
"embed": "object",
"link": "stylesheet"
};
var shouldBlockDetailedRequest = function(details) {
linkHelper.href = details.url;
details.url = linkHelper.href;
details.frameId = frameId;
details.parentFrameId = parentFrameId;
details.timeStamp = Date.now();
return !(safari.self.tab.canLoad(beforeLoadEvent, details));
}
var onBeforeLoad = function(e) {
if(e.url.lastIndexOf("data:", 0) === 0) {
return; return;
} }
linkHelper.href = e.url;
var url = linkHelper.href;
var details = {
url: url,
type: nodeTypes[e.target.nodeName.toLowerCase()] || 'other',
// tabId is determined in the background script
frameId: frameId,
parentFrameId: parentFrameId,
timeStamp: Date.now()
};
var response = safari.self.tab.canLoad(e, details);
if(!response) {
e.preventDefault();
}
};
document.addEventListener('beforeload', onBeforeLoad, true); var frameId = window === window.top ? 0 : Date.now() % 1E5;
var parentFrameId = (frameId ? 0 : -1);
/******************************************************************************/ // Helper event to message background,
// block pop-ups, intercept xhr requests, and apply site patches // and helper anchor element
var firstMutation = function() { var beforeLoadEvent = new Event("beforeload"),
document.removeEventListener('DOMContentLoaded', firstMutation, true); linkHelper = document.createElement("a");
firstMutation = false;
var randEventName = uniqueId();
window.addEventListener(randEventName, function(e) {
if(shouldBlockDetailedRequest(e.detail)) {
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',
');',
'};'
];
// Inform that we've navigated
if(frameId === 0) { if(frameId === 0) {
tmpScript.push( safari.self.tab.canLoad(beforeLoadEvent, {
'var pS = history.pushState, rS = history.replaceState,', url: location.href,
'onpopstate = function(e) {', type: "navigatedToNew"
'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 nodeTypes = {
var whiteListed = safari.self.tab.canLoad(beforeLoadEvent, { "frame": "sub_frame",
type: "isWhiteListed", "iframe": "sub_frame",
url: location.href "script": "script",
}); "img": "image",
if(vAPI.sitePatch && !whiteListed) { "input": "image",
tmpScript.push('(' + vAPI.sitePatch + ')();'); "object": "object",
} "embed": "object",
"link": "stylesheet"
tmpScript.push('})();');
tmpJS.textContent = tmpScript.join('');
document.documentElement.removeChild(document.documentElement.appendChild(tmpJS));
};
document.addEventListener('DOMContentLoaded', firstMutation, true);
/******************************************************************************/
var onContextMenu = function(e) {
var target = e.target;
var tagName = target.tagName.toLowerCase();
var details = {
tagName: tagName,
pageUrl: location.href,
insideFrame: window !== window.top
}; };
var shouldBlockDetailedRequest = function(details) {
details.editable = tagName === 'textarea' || tagName === 'input'; linkHelper.href = details.url;
details.url = linkHelper.href;
if ( target.hasOwnProperty('checked') ) { details.frameId = frameId;
details.checked = target.checked; details.parentFrameId = parentFrameId;
details.timeStamp = Date.now();
return !(safari.self.tab.canLoad(beforeLoadEvent, details));
} }
var onBeforeLoad = function(e) {
if ( tagName === 'a' ) { if(e.url.lastIndexOf("data:", 0) === 0) {
details.linkUrl = target.href; return;
}
if ( target.hasOwnProperty('src') ) {
details.srcUrl = target.src;
if ( tagName === 'img' ) {
details.mediaType = 'image';
} else if ( tagName === 'video' || tagName === 'audio' ) {
details.mediaType = tagName;
} }
} linkHelper.href = e.url;
var url = linkHelper.href;
var details = {
url: url,
type: nodeTypes[e.target.nodeName.toLowerCase()] || "other",
// tabId is determined in the background script
frameId: frameId,
parentFrameId: parentFrameId,
timeStamp: Date.now()
};
var response = safari.self.tab.canLoad(e, details);
if(!response) {
e.preventDefault();
}
};
document.addEventListener("beforeload", onBeforeLoad, true);
safari.self.tab.setContextMenuEventUserInfo(e, details); // Block popups, intercept XHRs, and add site patches
}; var firstMutation = function() {
document.removeEventListener("DOMContentLoaded", firstMutation, true);
firstMutation = false;
var randEventName = uniqueId();
window.addEventListener(randEventName, function(e) {
if(shouldBlockDetailedRequest(e.detail)) {
e.detail.url = false;
}
}, 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, {
type: "isWhiteListed",
url: location.href
});
if(vAPI.sitePatch && !whiteListed) {
tmpScript += "(" + vAPI.sitePatch + ")();";
}
tmpScript += "})();";
tmpJS.textContent = tmpScript;
document.documentElement.removeChild(document.documentElement.appendChild(tmpJS));
};
document.addEventListener("DOMContentLoaded", firstMutation, true);
self.addEventListener('contextmenu', onContextMenu, true); var onContextMenu = function(e) {
var target = e.target;
var tagName = target.tagName.toLowerCase();
/******************************************************************************/ var details = {
tagName: tagName,
pageUrl: location.href,
insideFrame: window !== window.top
};
details.editable = (tagName === "textarea" || tagName === "input");
if(target.hasOwnProperty("checked")) {
details.checked = target.checked;
}
if(tagName === "a") {
details.linkUrl = target.href;
}
if(target.hasOwnProperty("src")) {
details.srcUrl = target.src;
if(tagName === "img") {
details.mediaType = "image";
} else if(tagName === "video" || tagName === "audio") {
details.mediaType = tagName;
}
}
safari.self.tab.setContextMenuEventUserInfo(e, details);
};
self.addEventListener("contextmenu", onContextMenu, true);
})(); })();
/******************************************************************************/ /******************************************************************************/