From eafc96859ca2a5567948f69afccb49e556a86cdd Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 15 Nov 2014 16:15:11 -0200 Subject: [PATCH] further separate platform-specific code --- meta/crx/vapi-background.js | 329 ++++++++++++++ meta/crx/vapi-client.js | 145 ++++++ meta/crx/vapi-common.js | 87 ++++ meta/safariextz/vapi-background.js | 685 +++++++++++++++++++++++++++++ meta/safariextz/vapi-client.js | 399 +++++++++++++++++ meta/safariextz/vapi-common.js | 152 +++++++ src/assets/ublock/filters.txt | 4 +- src/js/assets.js | 25 +- src/js/background.js | 2 +- src/js/contentscript-end.js | 6 +- src/js/contentscript-start.js | 12 +- src/js/mirrors.js | 2 +- src/js/storage.js | 6 +- 13 files changed, 1827 insertions(+), 27 deletions(-) create mode 100644 meta/crx/vapi-background.js create mode 100644 meta/crx/vapi-client.js create mode 100644 meta/crx/vapi-common.js create mode 100644 meta/safariextz/vapi-background.js create mode 100644 meta/safariextz/vapi-client.js create mode 100644 meta/safariextz/vapi-common.js diff --git a/meta/crx/vapi-background.js b/meta/crx/vapi-background.js new file mode 100644 index 000000000..6af3cb7db --- /dev/null +++ b/meta/crx/vapi-background.js @@ -0,0 +1,329 @@ +/******************************************************************************* + + µBlock - a Chromium browser extension to block requests. + Copyright (C) 2014 The µBlock authors + + 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +// For background page + +/* global SafariBrowserTab, Services, XPCOMUtils */ + +/******************************************************************************/ + +(function() { + +'use strict'; + +/******************************************************************************/ + +self.vAPI = self.vAPI || {}; + +var vAPI = self.vAPI; +var chrome = self.chrome; + +vAPI.chrome = true; + +/******************************************************************************/ + +vAPI.storage = chrome.storage.local; + +/******************************************************************************/ + +vAPI.tabs = { + registerListeners: function() { + if (typeof this.onNavigation === 'function') { + chrome.webNavigation.onCommitted.addListener(this.onNavigation); + } + + if (typeof this.onUpdated === 'function') { + chrome.tabs.onUpdated.addListener(this.onUpdated); + } + + if (typeof this.onClosed === 'function') { + chrome.tabs.onRemoved.addListener(this.onClosed); + } + + if (typeof this.onPopup === 'function') { + chrome.webNavigation.onCreatedNavigationTarget.addListener(this.onPopup); + } + }, + + get: function(tabId, callback) { + if (tabId === null) { + chrome.tabs.query( + { + active: true, + currentWindow: true + }, + function(tabs) { + callback(tabs[0]); + } + ); + } + else { + chrome.tabs.get(tabId, callback); + } + }, + /*open: function(details) { + // to keep incognito context? + chrome.windows.getCurrent(function(win) { + details.windowId = win.windowId; + chrome.tabs.create(details); + }); + },*/ + open: function(details) { + if (!details.url) { + return null; + } + // extension pages + else if (!details.url.match(/^\w{2,20}:/)) { + details.url = vAPI.getURL(details.url); + } + + // dealing with Chrome's asynhronous API + var wrapper = function() { + if (details.active === undefined) { + details.active = true; + } + + var subWrapper = function() { + var _details = { + url: details.url, + active: !!details.active + }; + + if (details.tabId) { + // update doesn't accept index, must use move + chrome.tabs.update(details.tabId, _details, function(tab) { + // if the tab doesn't exist + if (chrome.runtime.lastError) { + chrome.tabs.create(_details); + } + else if (details.index !== undefined) { + chrome.tabs.move(tab.id, {index: details.index}); + } + }); + } + else { + if (details.index !== undefined) { + _details.index = details.index; + } + + chrome.tabs.create(_details); + } + }; + + if (details.index === -1) { + vAPI.tabs.get(null, function(tab) { + if (tab) { + details.index = tab.index + 1; + } + else { + delete details.index; + } + + subWrapper(); + }); + } + else { + subWrapper(); + } + }; + + if (details.select) { + // note that currentWindow may be even the window of Developer Tools + // so, test with setTimeout... + chrome.tabs.query({currentWindow: true}, function(tabs) { + var url = details.url.replace(rgxHash, ''); + // this is questionable + var rgxHash = /#.*/; + + tabs = tabs.some(function(tab) { + if (tab.url.replace(rgxHash, '') === url) { + chrome.tabs.update(tab.id, {active: true}); + return true; + } + }); + + if (!tabs) { + wrapper(); + } + }); + } + else { + wrapper(); + } + }, + close: chrome.tabs.remove.bind(chrome.tabs), + injectScript: function(tabId, details, callback) { + if (!callback) { + callback = function(){}; + } + + if (tabId) { + chrome.tabs.executeScript(tabId, details, callback); + } + else { + chrome.tabs.executeScript(details, callback); + } + } +}; + +/******************************************************************************/ + +// Must read: https://code.google.com/p/chromium/issues/detail?id=410868#c8 + +// https://github.com/gorhill/uBlock/issues/19 +// https://github.com/gorhill/uBlock/issues/207 +// Since we may be called asynchronously, the tab id may not exist +// anymore, so this ensures it does still exist. + +vAPI.setIcon = function(tabId, img, badge) { + var onIconReady = function() { + if ( chrome.runtime.lastError ) { + return; + } + + chrome.browserAction.setBadgeText({ tabId: tabId, text: badge }); + + if ( badge !== '' ) { + chrome.browserAction.setBadgeBackgroundColor({ tabId: tabId, color: '#666' }); + } + }; + chrome.browserAction.setIcon({ tabId: tabId, path: img }, onIconReady); +}; + +/******************************************************************************/ + +vAPI.messaging = { + ports: {}, + listeners: {}, + + listen: function(listenerName, callback) { + this.listeners[listenerName] = callback; + }, + + setup: function(connector) { + if ( this.connector ) { + return; + } + + this.connector = function(port) { + var onMessage = function(request) { + var callback = function(response) { + if (chrome.runtime.lastError || response === undefined) { + return; + } + + if (request.requestId) { + port.postMessage({ + requestId: request.requestId, + portName: request.portName, + msg: response + }); + } + }; + + var listener = connector(request.msg, port.sender, callback); + + if ( listener === null ) { + listener = vAPI.messaging.listeners[request.portName]; + + if (typeof listener === 'function') { + listener(request.msg, port.sender, callback); + } else { + console.error('µBlock> messaging > unknown request: %o', request); + } + } + }; + + var onDisconnect = function(port) { + port.onDisconnect.removeListener(onDisconnect); + port.onMessage.removeListener(onMessage); + delete vAPI.messaging.ports[port.name]; + }; + + port.onDisconnect.addListener(onDisconnect); + port.onMessage.addListener(onMessage); + vAPI.messaging.ports[port.name] = port; + }; + + chrome.runtime.onConnect.addListener(this.connector); + }, + + broadcast: function(message) { + message = { + broadcast: true, + msg: message + }; + + for ( var portName in this.ports ) { + this.ports[portName].postMessage(message); + } + } +}; + +/******************************************************************************/ + +vAPI.net = { + registerListeners: function() { + var listeners = [ + 'onBeforeRequest', + 'onBeforeSendHeaders', + 'onHeadersReceived' + ]; + + for (var i = 0; i < listeners.length; ++i) { + chrome.webRequest[listeners[i]].addListener( + this[listeners[i]].callback, + { + 'urls': this[listeners[i]].urls || [''], + 'types': this[listeners[i]].types || [] + }, + this[listeners[i]].extra + ); + } + } +}; + +/******************************************************************************/ + +vAPI.contextMenu = { + create: function(details, callback) { + this.menuId = details.id; + this.callback = callback; + chrome.contextMenus.create(details); + chrome.contextMenus.onClicked.addListener(this.callback); + }, + remove: function() { + chrome.contextMenus.onClicked.removeListener(this.callback); + chrome.contextMenus.remove(this.menuId); + } +}; + +/******************************************************************************/ + +vAPI.lastError = function() { + return chrome.runtime.lastError; +}; + +/******************************************************************************/ + +})(); + +/******************************************************************************/ diff --git a/meta/crx/vapi-client.js b/meta/crx/vapi-client.js new file mode 100644 index 000000000..69be8f952 --- /dev/null +++ b/meta/crx/vapi-client.js @@ -0,0 +1,145 @@ +/******************************************************************************* + + µBlock - a Chromium browser extension to block requests. + Copyright (C) 2014 The µBlock authors + + 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +// For non background pages + +/******************************************************************************/ + +(function() { + +'use strict'; + +/******************************************************************************/ + +self.vAPI = self.vAPI || {}; +self.vAPI.chrome = true; + +/******************************************************************************/ + +var messagingConnector = function(response) { + if ( !response ) { + return; + } + + var channel, listener; + + if ( response.broadcast === true ) { + for ( channel in vAPI.messaging.channels ) { + listener = vAPI.messaging.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 = vAPI.messaging.channels[response.portName]; + listener = channel && channel.listener; + } + + if ( typeof listener === 'function' ) { + listener(response.msg); + } +}; + +/******************************************************************************/ + +var uniqueId = function() { + return parseInt(Math.random() * 1e10, 10).toString(36); +}; + +/******************************************************************************/ + +self.vAPI.messaging = { + port: null, + channels: {}, + listeners: {}, + requestId: 1, + connectorId: uniqueId(), + + setup: function() { + this.port = chrome.runtime.connect({name: this.connectorId}); + this.port.onMessage.addListener(messagingConnector); + }, + + close: function() { + if ( this.port === null ) { + return; + } + this.port.disconnect(); + this.port.onMessage.removeListener(messagingConnector); + this.port = null; + this.channels = {}; + this.listeners = {}; + }, + + channel: function(channelName, callback) { + if ( !channelName ) { + return; + } + + this.channels[channelName] = { + portName: channelName, + listener: typeof callback === 'function' ? callback : null, + send: function(message, callback) { + if ( vAPI.messaging.port === null ) { + vAPI.messaging.setup(); + } + + message = { + portName: this.portName, + msg: message + }; + + if ( callback ) { + message.requestId = vAPI.messaging.requestId++; + vAPI.messaging.listeners[message.requestId] = callback; + } + + vAPI.messaging.port.postMessage(message); + }, + close: function() { + delete vAPI.messaging.channels[this.portName]; + } + }; + + return this.channels[channelName]; + } +}; + +/******************************************************************************/ + +self.vAPI.canExecuteContentScript = function() { + return true; +}; + +/******************************************************************************/ + +})(); + +/******************************************************************************/ \ No newline at end of file diff --git a/meta/crx/vapi-common.js b/meta/crx/vapi-common.js new file mode 100644 index 000000000..465600136 --- /dev/null +++ b/meta/crx/vapi-common.js @@ -0,0 +1,87 @@ +/******************************************************************************* + + µBlock - a Chromium browser extension to block requests. + Copyright (C) 2014 The µBlock authors + + 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +// For background page or non-background pages + +/******************************************************************************/ + +(function() { + +'use strict'; + +self.vAPI = self.vAPI || {}; + +/******************************************************************************/ + +// http://www.w3.org/International/questions/qa-scripts#directions + +var setScriptDirection = function(language) { + document.body.setAttribute( + 'dir', + ~['ar', 'he', 'fa', 'ps', 'ur'].indexOf(language) ? 'rtl' : 'ltr' + ); +}; + +/******************************************************************************/ + +vAPI.download = function(details) { + if ( !details.url ) { + return; + } + + var a = document.createElement('a'); + + if ( 'download' in a ) { + a.href = details.url; + a.setAttribute('download', details.filename || ''); + a.dispatchEvent(new MouseEvent('click')); + return; + } + var messager = vAPI.messaging.channel('_download'); + messager.send({ + what: 'gotoURL', + details: { + url: details.url, + index: -1 + } + }); + messager.close(); +}; + +/******************************************************************************/ + +var chrome = self.chrome; + +vAPI.getURL = function(path) { + return chrome.runtime.getURL(path); +}; + +vAPI.i18n = function(s) { + return chrome.i18n.getMessage(s) || s; +}; + +setScriptDirection(vAPI.i18n('@@ui_locale')); + +/******************************************************************************/ + +})(); + +/******************************************************************************/ diff --git a/meta/safariextz/vapi-background.js b/meta/safariextz/vapi-background.js new file mode 100644 index 000000000..905b4e955 --- /dev/null +++ b/meta/safariextz/vapi-background.js @@ -0,0 +1,685 @@ +/******************************************************************************* + + µBlock - a Chromium browser extension to block requests. + Copyright (C) 2014 The µBlock authors + + 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +// For background page + +/* global SafariBrowserTab, Services, XPCOMUtils */ + +/******************************************************************************/ + +(function() { + +'use strict'; + +/******************************************************************************/ + +self.vAPI = self.vAPI || {}; + +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/*' + ] +); + +/******************************************************************************/ + +safari.extension.settings.addEventListener('change', function(e) { + if (e.key === 'open_prefs') { + vAPI.tabs.open({url: 'dashboard.html', active: true}); + } +}, false); + +/******************************************************************************/ + +vAPI.storage = { + _storage: safari.extension.settings, + QUOTA_BYTES: 52428800, // copied from Info.plist + get: function(keys, callback) { + if (typeof callback !== 'function') { + return; + } + + var i, value, result = {}; + + if (keys === null) { + for (i in this._storage) { + value = this._storage[i]; + + if (typeof value === 'string') { + result[i] = JSON.parse(value); + } + } + } + else if (typeof keys === 'string') { + value = this._storage[keys]; + + if (typeof value === 'string') { + result[keys] = JSON.parse(value); + } + } + else if (Array.isArray(keys)) { + for ( i = 0; i < keys.length; ++i) { + value = this._storage[i]; + + if (typeof value === 'string') { + result[keys[i]] = JSON.parse(value); + } + } + } + else if (typeof keys === 'object') { + for (i in keys) { + value = this._storage[i]; + + if (typeof value === 'string') { + result[i] = JSON.parse(value); + } + else { + result[i] = keys[i]; + } + } + } + + callback(result); + }, + set: function(details, callback) { + for (var key in details) { + this._storage.setItem(key, JSON.stringify(details[key])); + } + + if (typeof callback === 'function') { + callback(); + } + }, + remove: function(keys) { + if (typeof keys === 'string') { + keys = [keys]; + } + + for (var i = 0; i < keys.length; ++i) { + this._storage.removeItem(keys[i]); + } + }, + clear: function(callback) { + this._storage.clear(); + callback(); + }, + getBytesInUse: function(keys, callback) { + var key, size = 0; + + if (keys === null) { + for (key in this._storage) { + size += (this._storage[key] || '').length; + } + } + else { + if (typeof keys === 'string') { + keys = [keys]; + } + + for (key = 0; key < keys.length; ++key) { + size += (this._storage[keys[key]] || '').length; + } + } + + callback(size); + } +}; + +/******************************************************************************/ + +vAPI.tabs = { + stack: {}, + stackID: 1, + registerListeners: function() { + var onNavigation = this.onNavigation; + + if (typeof onNavigation === 'function') { + this.onNavigation = function(e) { + // e.url is not present for local files or data URIs, + // or probably for those URLs which we don't have access to + if (!e.target || !e.target.url) { + return; + } + + onNavigation({ + frameId: 0, + tabId: vAPI.tabs.getTabId(e.target), + url: e.target.url + }); + }; + + safari.application.addEventListener('navigate', this.onNavigation, true); + } + + // ?? + /* if (typeof this.onUpdated === 'function') { } */ + + // onClosed handled in the main tab-close event + // onPopup is handled in window.open on web-pages? + /* if (typeof onPopup === 'function') { } */ + }, + getTabId: function(tab) { + for (var i in vAPI.tabs.stack) { + if (vAPI.tabs.stack[i] === tab) { + return +i; + } + } + + return -1; + }, + get: function(tabId, callback) { + var tab; + + if (tabId === null) { + tab = safari.application.activeBrowserWindow.activeTab; + tabId = this.getTabId(tab); + } + else { + tab = this.stack[tabId]; + } + + if (!tab) { + callback(); + return; + } + + callback({ + id: tabId, + index: tab.browserWindow.tabs.indexOf(tab), + windowId: safari.application.browserWindows.indexOf(tab.browserWindow), + active: tab === tab.browserWindow.activeTab, + url: tab.url, + title: tab.title + }); + }, + open: function(details) { + if (!details.url) { + return null; + } + // extension pages + else if (!details.url.match(/^\w{2,20}:/)) { + details.url = vAPI.getURL(details.url); + } + + // properties of the details object: + // url: 'URL', // the address that will be opened + // tabId: 1, // the tab is used if set, instead of creating a new one + // index: -1, // undefined: end of the list, -1: following tab, or after index + // active: false, // opens the tab in background - true and undefined: foreground + // select: true // if a tab is already opened with that url, then select it instead of opening a new one + + var curWin, tab; + + if (details.select) { + tab = safari.application.browserWindows.some(function(win) { + var rgxHash = /#.*/; + // this is questionable + var url = details.url.replace(rgxHash, ''); + + for (var i = 0; i < win.tabs.length; ++i) { + if (win.tabs[i].url.replace(rgxHash, '') === url) { + win.tabs[i].activate(); + return true; + } + } + }); + + if (tab) { + return; + } + } + + if (details.active === undefined) { + details.active = true; + } + + curWin = safari.application.activeBrowserWindow; + + // it must be calculated before opening a new tab, + // otherwise the new tab will be the active tab here + if (details.index === -1) { + details.index = curWin.tabs.indexOf(curWin.activeTab) + 1; + } + + tab = details.tabId && this.stack[details.tabId] + || curWin.openTab(details.active ? 'foreground' : 'background'); + + if (details.index !== undefined) { + curWin.insertTab(tab, details.index); + } + + tab.url = details.url; + }, + close: function(tab) { + if (!(tab instanceof SafariBrowserTab)) { + tab = this.stack[tab]; + } + + if (tab) { + tab.close(); + } + }, + injectScript: function(tabId, details, callback) { + var tab = tabId ? this.stack[tabId] : safari.application.activeBrowserWindow.activeTab; + + if (details.file) { + var xhr = new XMLHttpRequest; + xhr.overrideMimeType('application/x-javascript;charset=utf-8'); + xhr.open('GET', details.file, false); + xhr.send(); + details.code = xhr.responseText; + } + + tab.page.dispatchMessage('broadcast', { + portName: 'vAPI', + msg: { + cmd: 'runScript', + details: details + } + }); + + if (typeof callback === 'function') { + setTimeout(callback, 13); + } + } +}; + +/******************************************************************************/ + +// bind tabs to unique IDs + +(function() { + var wins = safari.application.browserWindows, i = wins.length, j; + var tabs = []; + + while (i--) { + j = wins[i].tabs.length; + + while (j--) { + tabs.push(wins[i].tabs[j]); + } + } + + return tabs; +})().forEach(function(tab) { + vAPI.tabs.stack[vAPI.tabs.stackID++] = tab; +}); + +/******************************************************************************/ + +safari.application.addEventListener('open', function(e) { + // ignore windows + if (e.target instanceof SafariBrowserTab) { + vAPI.tabs.stack[vAPI.tabs.stackID++] = e.target; + } +}, true); + +/******************************************************************************/ + +safari.application.addEventListener('close', function(e) { + // ignore windows + if (!(e.target instanceof SafariBrowserTab)) { + return; + } + + var tabId = vAPI.tabs.getTabId(e.target); + + if (tabId > -1) { + // to not add another listener, put this here + // instead of vAPI.tabs.registerListeners + if (typeof vAPI.tabs.onClosed === 'function') { + vAPI.tabs.onClosed(tabId); + } + + delete vAPI.tabIcons[tabId]; + delete vAPI.tabs.stack[tabId]; + } +}, true); + +/******************************************************************************/ + +// update badge when tab is activated +safari.application.addEventListener('activate', function(e) { + // hide popover, since in some cases won't close by itself + var items = safari.extension.toolbarItems; + + for (var i = 0; i < items.length; ++i) { + if (items[i].browserWindow === safari.application.activeBrowserWindow) { + if (items[i].popover) { + items[i].popover.hide(); + } + + break; + } + } + + // ignore windows + if (!(e.target instanceof SafariBrowserTab)) { + return; + } + + // update the badge, when tab is selected + vAPI.setIcon(); +}, true); + +/******************************************************************************/ + +// reload the popup when that is opened +safari.application.addEventListener('popover', function(e) { + e.target.contentWindow.document.body.textContent = ''; + e.target.contentWindow.location.reload(); +}, true); + +/******************************************************************************/ + +vAPI.tabIcons = { /*tabId: {badge: 0, img: dict}*/ }; +vAPI.setIcon = function(tabId, img, badge) { + var curTabId = vAPI.tabs.getTabId(safari.application.activeBrowserWindow.activeTab); + + // from 'activate' event + if (tabId === undefined) { + tabId = curTabId; + } + else { + vAPI.tabIcons[tabId] = { + badge: badge || 0/*, + img: img*/ + }; + } + + // if the selected tab has the same ID, then update the badge too, + // or always update it when changing tabs ('activate' event) + if (tabId === curTabId) { + var items = safari.extension.toolbarItems, i = items.length; + + while (i--) { + if (items[i].browserWindow === safari.application.activeBrowserWindow) { + if (vAPI.tabIcons[tabId]) { + items[i].badge = vAPI.tabIcons[tabId].badge; + // items[i].img = vAPI.tabIcons[tabId].img; + } + else { + items[i].badge = 0; + } + + return; + } + } + } +}; + +/******************************************************************************/ + +vAPI.messaging = { + listeners: {}, + listen: function(listenerName, callback) { + this.listeners[listenerName] = callback; + }, + setup: function(connector) { + if (this.connector) { + return; + } + + this.connector = function(request) { + var callback = function(response) { + if (response !== undefined) { + request.target.page.dispatchMessage( + request.name, + { + requestId: request.message.requestId, + portName: request.message.portName, + msg: response + } + ); + } + }; + + var sender = { + tab: { + id: vAPI.tabs.getTabId(request.target) + } + }; + + var listener = connector(request.message.msg, sender, callback); + + if (listener === null) { + listener = vAPI.messaging.listeners[request.message.portName]; + + if (typeof listener === 'function') { + listener(request.message.msg, sender, callback); + } else { + console.error('µBlock> messaging > unknown request: %o', request.message); + } + } + }; + + // 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) { + message = { + broadcast: true, + msg: message + }; + + for (var tabId in vAPI.tabs.stack) { + vAPI.tabs.stack[tabId].page.dispatchMessage('broadcast', message); + } + } +}; + +/******************************************************************************/ + +vAPI.net = { + registerListeners: function() { + var onBeforeRequest = this.onBeforeRequest; + + if (typeof onBeforeRequest.callback === 'function') { + if (!Array.isArray(onBeforeRequest.types)) { + onBeforeRequest.types = []; + } + + onBeforeRequest = onBeforeRequest.callback; + this.onBeforeRequest.callback = function(e) { + var block; + + if (e.name !== 'canLoad') { + return; + } + + // no stopPropagation if it was called from beforeNavigate event + if (e.stopPropagation) { + 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; + } + + // 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; + } + + 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) { + e.message = false; + } + else if (e.message.type === 'script' + && typeof block.redirectUrl === "string") { + e.message = block.redirectUrl; + } + else { + e.message = true; + } + } + else { + e.message = true; + } + + return e.message; + }; + + safari.application.addEventListener('message', this.onBeforeRequest.callback, true); + } + } +}; + +/******************************************************************************/ + +vAPI.contextMenu = { + create: function(details, callback) { + var contexts = details.contexts; + var menuItemId = details.id; + var menuTitle = details.title; + + if (Array.isArray(contexts) && contexts.length) { + contexts = contexts.indexOf('all') === -1 ? contexts : null; + } + else { + // default in Chrome + contexts = ['page']; + } + + this.onContextMenu = function(e) { + var uI = e.userInfo; + + if (uI && /^https?:\/\//i.test(uI.pageUrl)) { + if (contexts) { + var invalidContext = true; + + for (var i = 0; i < contexts.length; ++i) { + if (contexts[i] === 'frame') { + if (uI.insideFrame) { + invalidContext = false; + break; + } + } + else if (contexts[i] === 'link') { + if (uI.linkHref) { + invalidContext = false; + break; + } + } + else if (contexts[i] === 'image') { + if (uI.srcUrl) { + invalidContext = false; + break; + } + } + else if (contexts[i] === 'audio' || contexts[i] === 'video') { + if (uI.srcUrl && uI.tagName === contexts[i]) { + invalidContext = false; + break; + } + } + else if (contexts[i] === 'editable') { + if (uI.editable) { + invalidContext = false; + break; + } + } + else if (contexts[i] === 'page') { + if (!(uI.insideFrame || uI.linkHref || uI.mediaType || uI.editable)) { + invalidContext = false; + break; + } + } + } + + if (invalidContext) { + return; + } + } + + e.contextMenu.appendContextMenuItem(menuItemId, menuTitle); + } + }; + + this.onContextMenuCommand = function(e) { + if (e.command === menuItemId) { + var tab = e.currentTarget.activeBrowserWindow.activeTab; + e.userInfo.menuItemId = menuItemId; + callback(e.userInfo, tab ? { + id: vAPI.tabs.getTabId(tab), + url: tab.url + } : undefined); + } + }; + + safari.application.addEventListener('contextmenu', this.onContextMenu); + safari.application.addEventListener("command", this.onContextMenuCommand); + }, + remove: function() { + safari.application.removeEventListener('contextmenu', this.onContextMenu); + safari.application.removeEventListener("command", this.onContextMenuCommand); + this.onContextMenu = null; + this.onContextMenuCommand = null; + } +}; + +/******************************************************************************/ + +vAPI.lastError = { + return null; +}; + +/******************************************************************************/ + +})(); diff --git a/meta/safariextz/vapi-client.js b/meta/safariextz/vapi-client.js new file mode 100644 index 000000000..b9ddfd3a9 --- /dev/null +++ b/meta/safariextz/vapi-client.js @@ -0,0 +1,399 @@ +/******************************************************************************* + + µBlock - a Chromium browser extension to block requests. + Copyright (C) 2014 The µBlock authors + + 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +// For non background pages + +/* global addMessageListener, removeMessageListener, sendAsyncMessage */ + +/******************************************************************************/ + +(function() { + +'use strict'; + +/******************************************************************************/ + +self.vAPI = self.vAPI || {}; +self.vAPI.safari = true; + +/******************************************************************************/ + +// since this is common across vendors +var messagingConnector = function(response) { + var channel, listener; + + if (!response) { + return; + } + + if (response.broadcast === true) { + for (channel in vAPI.messaging.channels) { + listener = vAPI.messaging.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 = vAPI.messaging.channels[response.portName]; + listener = channel && channel.listener; + } + + if (typeof listener === 'function') { + listener(response.msg); + } +}; + +var uniqueId = function() { + return parseInt(Math.random() * 1e10, 10).toString(36); +}; + +/******************************************************************************/ + +// 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: 0, + 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 === 'runScript' && msg.details.code) { + Function(msg.details.code).call(self); + } + } + }; + }, + close: function() { + if (this.connector) { + safari.self.removeEventListener('message', this.connector, false); + this.connector = this.channels = this.listeners = null; + } + }, + channel: function(channelName, callback) { + if (!channelName) { + return; + } + + this.channels[channelName] = { + portName: channelName, + listener: typeof callback === 'function' ? callback : null, + send: function(message, callback) { + if (!vAPI.messaging.connector) { + vAPI.messaging.setup(); + } + + message = { + portName: this.portName, + msg: message + }; + + if (callback) { + message.requestId = ++vAPI.messaging.requestId; + vAPI.messaging.listeners[message.requestId] = callback; + } + + if (safari.extension.globalPage) { + // popover content doesn't know messaging... + safari.extension.globalPage.contentWindow + .vAPI.messaging.connector({ + name: vAPI.messaging.connectorId, + message: message, + target: { + page: { + dispatchMessage: function(name, msg) { + messagingConnector(msg); + } + } + } + }); + } + else { + safari.self.tab.dispatchMessage( + vAPI.messaging.connectorId, + message + ); + } + }, + close: function() { + delete vAPI.messaging.channels[this.portName]; + } + }; + + return this.channels[channelName]; + } +}; + +/******************************************************************************/ + +if (location.protocol === "safari-extension:") { + return; +} + +/******************************************************************************/ + +window.MutationObserver = window.MutationObserver || window.WebKitMutationObserver; + +if (!window.MutationObserver) { + // dummy, minimalistic shim for older versions (<6) + // only supports node insertions, but currently we don't use it for anything else + window.MutationObserver = function(handler) { + this.observe = function(target) { + target.addEventListener('DOMNodeInserted', function(e) { + handler([{addedNodes: [e.target]}]); + }, true); + }; + } +} + +/******************************************************************************/ + +var beforeLoadEvent = document.createEvent('Event'); +beforeLoadEvent.initEvent('beforeload'); + +/******************************************************************************/ + +var frameId = window === window.top ? 0 : Date.now() % 1E5; +var linkHelper = document.createElement('a'); +var onBeforeLoad = function(e, details) { + if (e.url && e.url.slice(0, 5) === 'data:') { + return; + } + + linkHelper.href = details ? details.url : e.url; + + if (!/^https?:/.test(linkHelper.protocol)) { + return; + } + + if (details) { + details.url = linkHelper.href; + } + else { + details = { + url: linkHelper.href + }; + + switch (e.target.nodeName.toLowerCase()) { + case 'frame': + case 'iframe': + details.type = 'sub_frame'; + break; + case 'script': + details.type = 'script'; + break; + case 'img': + case 'input': // type=image + details.type = 'image'; + break; + case 'object': + case 'embed': + details.type = 'object'; + break; + case 'link': + var rel = e.target.rel.trim().toLowerCase(); + + if (rel.indexOf('icon') > -1) { + details.type = 'image'; + break; + } + else if (rel === 'stylesheet') { + details.type = 'stylesheet'; + break; + } + default: + details.type = 'other'; + } + + // This can run even before the first DOMSubtreeModified event fired + if (firstMutation) { + firstMutation(); + } + } + + // tabId is determined in the background script + // details.tabId = null; + details.frameId = frameId; + details.parentFrameId = frameId ? 0 : -1; + details.timeStamp = Date.now(); + + var response = safari.self.tab.canLoad(e, details); + + if (!response) { + if (details.type === 'main_frame') { + window.stop(); + } + else { + e.preventDefault(); + } + + return false; + } + // local mirroring, response is a data: URL here + 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)); + + if (e.target.hasAttribute('defer') && document.readyState === 'loading') { + var jsOnLoad = function(ev) { + this.removeEventListener(ev.type, jsOnLoad, true); + this.body.removeChild(this.body.appendChild(details)); + }; + + document.addEventListener('DOMContentLoaded', jsOnLoad, true); + } + else { + e.target.parentNode.insertBefore(details, e.target); + details.parentNode.removeChild(details); + } + } +}; + +document.addEventListener('beforeload', onBeforeLoad, true); + +/******************************************************************************/ + +// block pop-ups, intercept xhr requests, and apply site patches +var firstMutation = function() { + document.removeEventListener('DOMSubtreeModified', firstMutation, true); + firstMutation = null; + var randEventName = parseInt(Math.random() * 1e15, 10).toString(36); + + window.addEventListener(randEventName, function(e) { + var result = onBeforeLoad(beforeLoadEvent, e.detail); + + if (result === 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 = document.createEvent('CustomEvent'),", + "d = {url: u, type: t};", + "e.initCustomEvent('" + randEventName + "', !1, !1, d);", + "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, s) {", + "return xo.apply(this, block(u, 'xmlhttprequest') ? ['HEAD', u, s] : [].slice.call(arguments));", + "};" + ]; + + 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)); +}; + +document.addEventListener('DOMSubtreeModified', firstMutation, true); + +/******************************************************************************/ + +var onContextMenu = function(e) { + var details = { + tagName: e.target.tagName.toLowerCase(), + pageUrl: location.href, + insideFrame: window !== window.top + }; + + details.editable = details.tagName === 'textarea' || details.tagName === 'input'; + + if ('checked' in e.target) { + details.checked = e.target.checked; + } + + if (details.tagName === 'a') { + details.linkUrl = e.target.href; + } + + if ('src' in e.target) { + details.srcUrl = e.target.src; + + if (details.tagName === 'img') { + details.mediaType = 'image'; + } + else if (details.tagName === 'video' || details.tagName === 'audio') { + details.mediaType = details.tagName; + } + } + + safari.self.tab.setContextMenuEventUserInfo(e, details); +}; + +self.addEventListener('contextmenu', onContextMenu, true); + +/******************************************************************************/ + +// 'main_frame' simulation +if (frameId === 0) { + onBeforeLoad(beforeLoadEvent, { + url: location.href, + type: 'main_frame' + }); +} + +/******************************************************************************/ + +self.vAPI.canExecuteContentScript = function() { + return /^https?:/.test(location.protocol); +}; + +/******************************************************************************/ + +})(); + +/******************************************************************************/ diff --git a/meta/safariextz/vapi-common.js b/meta/safariextz/vapi-common.js new file mode 100644 index 000000000..1af6ad998 --- /dev/null +++ b/meta/safariextz/vapi-common.js @@ -0,0 +1,152 @@ +/******************************************************************************* + + µBlock - a Chromium browser extension to block requests. + Copyright (C) 2014 The µBlock authors + + 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 + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +// For background page or non-background pages + +/******************************************************************************/ + +(function() { + +'use strict'; + +/******************************************************************************/ + +self.vAPI = self.vAPI || {}; + +/******************************************************************************/ + +// http://www.w3.org/International/questions/qa-scripts#directions +var setScriptDirection = function(langugae) { + document.body.setAttribute( + 'dir', + ~['ar', 'he', 'fa', 'ps', 'ur'].indexOf(langugae) ? 'rtl' : 'ltr' + ); +}; + +/******************************************************************************/ + +vAPI.download = function(details) { + if (!details.url) { + return; + } + + var a = document.createElement('a'); + + if ('download' in a) { + a.href = details.url; + a.setAttribute('download', details.filename || ''); + a.dispatchEvent(new MouseEvent('click')); + } + else { + var messager = vAPI.messaging.channel('_download'); + messager.send({ + what: 'gotoURL', + details: { + url: details.url, + index: -1 + } + }); + messager.close(); + } +}; + +/******************************************************************************/ + +vAPI.getURL = function(path) { + return safari.extension.baseURI + path; +}; + +/******************************************************************************/ + +var xhr = new XMLHttpRequest; +xhr.overrideMimeType('application/json;charset=utf-8'); +xhr.open('GET', './locales.json', false); +xhr.send(); +vAPI.i18nData = JSON.parse(xhr.responseText); + +if (vAPI.i18nData[vAPI.i18n = navigator.language.replace('-', '_')] + || vAPI.i18nData[vAPI.i18n = vAPI.i18n.slice(0, 2)]) { + vAPI.i18nLocale = vAPI.i18n; +} else { + vAPI.i18nLocale = vAPI.i18nData._; +} + +xhr = new XMLHttpRequest; +xhr.overrideMimeType('application/json;charset=utf-8'); +xhr.open('GET', './_locales/' + vAPI.i18nLocale + '/messages.json', false); +xhr.send(); +vAPI.i18nData = JSON.parse(xhr.responseText); + +for (var i18nKey in vAPI.i18nData) { + vAPI.i18nData[i18nKey] = vAPI.i18nData[i18nKey].message; +} + +vAPI.i18n = function(s) { + return this.i18nData[s] || s; +}; + +setScriptDirection(vAPI.i18nLocale); + +/******************************************************************************/ + +// update popover size to its content +if (safari.self.identifier === 'popover' && safari.self) { + var onLoaded = function() { + // Initial dimensions are set in Info.plist + var pWidth = safari.self.width; + var pHeight = safari.self.height; + var upadteTimer = null; + var resizePopover = function() { + if (upadteTimer) { + return; + } + + upadteTimer = setTimeout(function() { + safari.self.width = Math.max(pWidth, document.body.clientWidth); + safari.self.height = Math.max(pHeight, document.body.clientHeight); + upadteTimer = null; + }, 20); + }; + + var mutObs = window.MutationObserver || window.WebkitMutationObserver; + + if (mutObs) { + (new mutObs(resizePopover)).observe(document, { + childList: true, + attributes: true, + characterData: true, + subtree: true + }); + } + else { + // Safari doesn't support DOMAttrModified + document.addEventListener('DOMSubtreeModified', resizePopover); + } + }; + + window.addEventListener('load', ); +} + +/******************************************************************************/ + +})(); + +/******************************************************************************/ diff --git a/src/assets/ublock/filters.txt b/src/assets/ublock/filters.txt index 8975e6418..7c534f766 100644 --- a/src/assets/ublock/filters.txt +++ b/src/assets/ublock/filters.txt @@ -29,7 +29,7 @@ www.zerohedge.com##.similar-box # entity = domain minus public suffix # The following filters were taken out of EasyList and given an entity name, # which makes them candidates to be injected early in the page. -# Last updated: 2014-09-24 +# Last updated: 2014-11-14 google.*###center_col > #resultStats + #tads google.*###center_col > #resultStats + #tads + #res + #tads google.*###center_col > #resultStats + div + #res + #tads @@ -54,8 +54,10 @@ google.*###tadsto.c google.*###topstuff > #tads google.*##.GB3L-QEDGY .GB3L-QEDF- > .GB3L-QEDE- google.*##.GFYY1SVD2 > .GFYY1SVC2 > .GFYY1SVF5 +google.*##.GFYY1SVE2 > .GFYY1SVD2 > .GFYY1SVG5 google.*##.GHOFUQ5BG2 > .GHOFUQ5BF2 > .GHOFUQ5BG5 google.*##.GJJKPX2N1 > .GJJKPX2M1 > .GJJKPX2P4 +google.*##.GKJYXHBF2 > .GKJYXHBE2 > .GKJYXHBH5 google.*##.GPMV2XEDA2 > .GPMV2XEDP1 > .GPMV2XEDJBB google.*##.ch[onclick="ga(this,event)"] google.*##.commercial-unit-desktop-rhs diff --git a/src/js/assets.js b/src/js/assets.js index 737dea631..8f0826803 100644 --- a/src/js/assets.js +++ b/src/js/assets.js @@ -20,6 +20,7 @@ */ /* global µBlock, YaMD5 */ + 'use strict'; /******************************************************************************* @@ -51,7 +52,8 @@ var oneDay = 24 * oneHour; /******************************************************************************/ -var repositoryRoot = µBlock.projectServerRoot; +var projectRepositoryRoot = µBlock.projectServerRoot; +var thirdpartiesRepositoryRoot = 'https://raw.githubusercontent.com/gorhill/uAssets/master/src'; var nullFunc = function() {}; var reIsExternalPath = /^https?:\/\/[a-z0-9]/; var reIsUserPath = /^assets\/user\//; @@ -111,10 +113,11 @@ var cachedAssetsManager = (function() { // Maybe the index was requested multiple times and already // fetched by one of the occurrences. if ( entries === null ) { - if ( chrome.runtime.lastError ) { + var lastError = vAPI.lastError; + if ( lastError ) { console.error( 'µBlock> cachedAssetsManager> getEntries():', - chrome.runtime.lastError.message + lastError.message ); } entries = bin.cached_asset_entries || {}; @@ -134,8 +137,9 @@ var cachedAssetsManager = (function() { }; var cachedContentPath = cachedAssetPathPrefix + path; var onLoaded = function(bin) { - if ( chrome.runtime.lastError ) { - details.error = 'Error: ' + chrome.runtime.lastError.message; + var lastError = vAPI.lastError; + if ( lastError ) { + details.error = 'Error: ' + lastError.message; console.error('µBlock> cachedAssetsManager.load():', details.error); cbError(details); } else { @@ -165,8 +169,9 @@ var cachedAssetsManager = (function() { var bin = {}; bin[cachedContentPath] = content; var onSaved = function() { - if ( chrome.runtime.lastError ) { - details.error = 'Error: ' + chrome.runtime.lastError.message; + var lastError = vAPI.lastError; + if ( lastError ) { + details.error = 'Error: ' + lastError.message; console.error('µBlock> cachedAssetsManager.save():', details.error); cbError(details); } else { @@ -452,7 +457,7 @@ var readRepoFile = function(path, callback) { callback(details); }; - var repositoryURL = repositoryRoot + path; + var repositoryURL = projectRepositoryRoot + path; var onRepoFileLoaded = function() { this.onload = this.onerror = null; @@ -531,7 +536,7 @@ var readRepoCopyAsset = function(path, callback) { getTextFileFromURL(vAPI.getURL(details.path), onInstallFileLoaded, onInstallFileError); }; - var repositoryURL = repositoryRoot + path; + var repositoryURL = projectRepositoryRoot + path; var repositoryURLSkipCache = repositoryURL + '?ublock=' + Date.now(); var onRepoFileLoaded = function() { @@ -680,7 +685,7 @@ var readRepoOnlyAsset = function(path, callback) { getTextFileFromURL(vAPI.getURL(path), onInstallFileLoaded, onInstallFileError); }; - var repositoryURL = repositoryRoot + path + '?ublock=' + Date.now(); + var repositoryURL = projectRepositoryRoot + path + '?ublock=' + Date.now(); var onRepoFileLoaded = function() { this.onload = this.onerror = null; diff --git a/src/js/background.js b/src/js/background.js index 0f3340567..d0ffe6fa2 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -78,7 +78,7 @@ return { // EasyList, EasyPrivacy and many others have an 4-day update period, // as per list headers. updateAssetsEvery: 75 * oneHour + 23 * oneMinute + 53 * oneSecond + 605, - projectServerRoot: 'https://raw.githubusercontent.com/gorhill/uBlock/master/', + projectServerRoot: 'https://raw.githubusercontent.com/gorhill/uBlock/master/src', userFiltersPath: 'assets/user/filters.txt', pslPath: 'assets/thirdparties/publicsuffix.org/list/effective_tld_names.dat', diff --git a/src/js/contentscript-end.js b/src/js/contentscript-end.js index 03e2b2a21..9aa3644d3 100644 --- a/src/js/contentscript-end.js +++ b/src/js/contentscript-end.js @@ -28,10 +28,8 @@ /******************************************************************************/ -if (vAPI.safari) { - if (!/^https?:/.test(location.protocol)) { - throw "uBlock> contentscript-end.js > Skipping " + location.protocol; - } +if ( vAPI.canExecuteContentScript() !== true ) { + throw "uBlock> contentscript-end.js > Skipping " + location.protocol; } /******************************************************************************/ diff --git a/src/js/contentscript-start.js b/src/js/contentscript-start.js index 19e54ca7b..3fbf3d8ab 100644 --- a/src/js/contentscript-start.js +++ b/src/js/contentscript-start.js @@ -29,20 +29,18 @@ /******************************************************************************/ -// OK, I keep changing my mind whether a closure should be used or not. This -// will be the rule: if there are any variables directly accessed on a regular -// basis, use a closure so that they are cached. Otherwise I don't think the -// overhead of a closure is worth it. That's my understanding. - (function() { +/******************************************************************************/ + // because Safari -if (location.protocol !== "http:" && location.protocol !== "https:") { +if ( vAPI.canExecuteContentScript() !== true ) { return; } -var localMessager = vAPI.messaging.channel('contentscript-start.js'); +/******************************************************************************/ +var localMessager = vAPI.messaging.channel('contentscript-start.js'); /******************************************************************************/ /******************************************************************************/ diff --git a/src/js/mirrors.js b/src/js/mirrors.js index b8a76afc4..d68b91c3e 100644 --- a/src/js/mirrors.js +++ b/src/js/mirrors.js @@ -475,7 +475,7 @@ var load = function() { var loadContent = function(urlKey, hash) { var binKey = storageKeyFromHash(hash); var onContentReady = function(bin) { - if ( chrome.runtime.lastError || bin.hasOwnProperty(binKey) === false ) { + if ( vAPI.lastError || bin.hasOwnProperty(binKey) === false ) { //console.debug('mirrors.load(): failed to load content "%s"', binKey); removeMetadata(urlKey); removeContent(binKey); diff --git a/src/js/storage.js b/src/js/storage.js index 255e3ea1b..698a7e1d5 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -594,13 +594,13 @@ var onAllDone = function() { if (vAPI.chrome) { // http://code.google.com/p/chromium/issues/detail?id=410868#c11 - // Need to be sure to access `chrome.runtime.lastError` to prevent + // Need to be sure to access `vAPI.lastError` to prevent // spurious warnings in the console. var scriptDone = function() { - chrome.runtime.lastError; + vAPI.lastError; }; var scriptEnd = function(tabId) { - if ( chrome.runtime.lastError ) { + if ( vAPI.lastError ) { return; } vAPI.tabs.injectScript(tabId, {