diff --git a/.gitignore b/.gitignore
index a9b31f80a..b5ba1f15a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,4 @@
*.bak
*.pem
-*.safariextension/
/meta/safariextz/certs/
-/dist/build/
-/tmp/
\ No newline at end of file
+/dist/build/
\ No newline at end of file
diff --git a/meta/config.json b/meta/config.json
deleted file mode 100644
index 3741eb7d2..000000000
--- a/meta/config.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "name": "µBlock",
- "clean_name": "uBlock",
- "url": "https://github.com/gorhill/uBlock",
- "author": "Raymond Hill",
- "author_email": "rhill@raymondhill.net",
- "version": "0.7.0.10",
- "def_lang": "en",
- "vendors": {
- "crx": {
- "app_id": "cjpalhdlnbpafiamejdnhcphjbkeiagm",
- "manifest": "manifest.json",
- "locales": "_locales",
- "file_ext": ".crx",
- "private_key": "./meta/crx/key.pem"
- },
- "safariextz": {
- "app_id": "net.gorhill.uBlock",
- "manifest": {
- "Info": "Info.plist",
- "Settings": "Settings.plist"
- },
- "file_ext": ".safariextz",
- "developer_identifier": "",
- "cert_dir": "./meta/safariextz/certs/",
- "private_key": "./meta/safariextz/key.pem"
- }
- }
-}
\ No newline at end of file
diff --git a/meta/crx/update_crx.xml b/meta/crx/update_crx.xml
deleted file mode 100644
index 93cdcde00..000000000
--- a/meta/crx/update_crx.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/meta/crx/vapi-background.js b/meta/crx/vapi-background.js
index ccbe85d4a..84587d6f7 100644
--- a/meta/crx/vapi-background.js
+++ b/meta/crx/vapi-background.js
@@ -158,9 +158,9 @@ vAPI.tabs.open = function(details) {
if ( details.select ) {
chrome.tabs.query({ currentWindow: true }, function(tabs) {
- var url = targetURL.replace(rgxHash, '');
- // this is questionable
var rgxHash = /#.*/;
+ // this is questionable
+ var url = targetURL.replace(rgxHash, '');
var selected = tabs.some(function(tab) {
if ( tab.url.replace(rgxHash, '') === url ) {
chrome.tabs.update(tab.id, { active: true });
diff --git a/meta/safariextz/Info.plist b/meta/safariextz/Info.plist
index 8f32a1721..50134bc3a 100644
--- a/meta/safariextz/Info.plist
+++ b/meta/safariextz/Info.plist
@@ -3,19 +3,19 @@
Author
- {author}
+ Raymond Hill
Builder Version
534.57.2
CFBundleDisplayName
- {name}
+ µBlock
CFBundleIdentifier
- {app_id}
+ net.gorhill.uBlock
CFBundleInfoDictionaryVersion
6.0
CFBundleShortVersionString
- {version}
+ 0.7.0.10
CFBundleVersion
- {build_number}
+ 1456132
Chrome
Database Quota
@@ -43,7 +43,7 @@
Image
img/icon16.png
Label
- {name}
+ µBlock
Popover
popover
@@ -70,7 +70,7 @@
Description
- {description}
+ Finally, an efficient blocker. Easy on CPU and memory.
ExtensionInfoDictionaryVersion
1.0
Permissions
@@ -84,8 +84,8 @@
+ https://github.com/gorhill/uBlockupdate_safariextz.plist -->
Website
- {url}
+ https://github.com/gorhill/uBlock
diff --git a/src/js/sitepatch-safari.js b/meta/safariextz/sitepatch-safari.js
similarity index 99%
rename from src/js/sitepatch-safari.js
rename to meta/safariextz/sitepatch-safari.js
index f859d5aa6..b99a28ea7 100644
--- a/src/js/sitepatch-safari.js
+++ b/meta/safariextz/sitepatch-safari.js
@@ -1,4 +1,3 @@
-// Only for Safari
// Adding new URL requires to whitelist it in the background script too (addContentScriptFromURL)
// Note that the sitePach function will be converted to a string, and injected
// into the web-page in order to run in that scope. Because of this, variables
diff --git a/meta/safariextz/update_safariextz.plist b/meta/safariextz/update_safariextz.plist
index b0311b483..0681b05e6 100644
--- a/meta/safariextz/update_safariextz.plist
+++ b/meta/safariextz/update_safariextz.plist
@@ -6,15 +6,15 @@
CFBundleIdentifier
- {app_id}
+ net.gorhill.uBlock
Developer Identifier
- {developer_identifier}
+
CFBundleShortVersionString
- {version}
+ 0.7.0.10
CFBundleVersion
- {build_number}
+ 1456132
URL
- {url}/{name}.safariextz
+ https://.../uBlock.safariextz
diff --git a/meta/safariextz/vapi-background.js b/meta/safariextz/vapi-background.js
index f976b1ef3..d4a78d6bc 100644
--- a/meta/safariextz/vapi-background.js
+++ b/meta/safariextz/vapi-background.js
@@ -19,10 +19,9 @@
Home: https://github.com/gorhill/uBlock
*/
+/* global µBlock, SafariBrowserTab */
// For background page
-/* global SafariBrowserTab, Services, XPCOMUtils */
-
/******************************************************************************/
(function() {
@@ -158,159 +157,183 @@ vAPI.storage = {
vAPI.tabs = {
stack: {},
- stackID: 1,
- registerListeners: function() {
- var onNavigation = this.onNavigation;
+ stackID: 1
+};
- 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
- });
- };
+vAPI.tabs.registerListeners = function() {
+ var onNavigation = this.onNavigation;
- 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) {
+ 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') { } */
+};
+
+/******************************************************************************/
+
+vAPI.tabs.getTabId = function(tab) {
+ for (var i in vAPI.tabs.stack) {
+ if (vAPI.tabs.stack[i] === tab) {
+ return +i;
}
+ }
- if (details.active === undefined) {
- details.active = true;
- }
+ return -1;
+};
- 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;
- }
+vAPI.tabs.get = function(tabId, callback) {
+ var tab;
- tab = details.tabId && this.stack[details.tabId]
- || curWin.openTab(details.active ? 'foreground' : 'background');
+ if (tabId === null) {
+ tab = safari.application.activeBrowserWindow.activeTab;
+ tabId = this.getTabId(tab);
+ }
+ else {
+ tab = this.stack[tabId];
+ }
- if (details.index !== undefined) {
- curWin.insertTab(tab, details.index);
- }
+ if (!tab) {
+ callback();
+ return;
+ }
- tab.url = details.url;
- },
- close: function(tab) {
- if (!(tab instanceof SafariBrowserTab)) {
- tab = this.stack[tab];
- }
+ 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
+ });
+};
+// 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
- 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;
- }
+vAPI.tabs.open = function(details) {
+ if (!details.url) {
+ return null;
+ }
+ // extension pages
+ if (!/^[\w-]{2,}:/.test(details.url)) {
+ details.url = vAPI.getURL(details.url);
+ }
- tab.page.dispatchMessage('broadcast', {
- portName: 'vAPI',
- msg: {
- cmd: 'runScript',
- details: details
+ 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 (typeof callback === 'function') {
- setTimeout(callback, 13);
+ 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;
+};
+
+/******************************************************************************/
+
+vAPI.tabs.close = function(tab) {
+ if (!(tab instanceof SafariBrowserTab)) {
+ tab = this.stack[tab];
+ }
+
+ if (tab) {
+ tab.close();
+ }
+};
+
+/******************************************************************************/
+
+vAPI.tabs.injectScript = function(tabId, details, callback) {
+ var tab;
+
+ if (tabId) {
+ tab = this.stack[tabId];
+ }
+ else {
+ tab = 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);
+ }
};
/******************************************************************************/
@@ -403,7 +426,9 @@ safari.application.addEventListener('popover', function(e) {
vAPI.tabIcons = { /*tabId: {badge: 0, img: dict}*/ };
vAPI.setIcon = function(tabId, img, badge) {
- var curTabId = vAPI.tabs.getTabId(safari.application.activeBrowserWindow.activeTab);
+ var curTabId = vAPI.tabs.getTabId(
+ safari.application.activeBrowserWindow.activeTab
+ );
// from 'activate' event
if (tabId === undefined) {
@@ -416,23 +441,25 @@ vAPI.setIcon = function(tabId, img, badge) {
};
}
+ if (tabId !== curTabId) {
+ return;
+ }
+
// 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;
+ 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;
+ 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;
}
}
};
@@ -441,242 +468,283 @@ vAPI.setIcon = function(tabId, img, badge) {
vAPI.messaging = {
listeners: {},
- listen: function(listenerName, callback) {
- this.listeners[listenerName] = callback;
- },
- setup: function(connector) {
- if (this.connector) {
- return;
- }
+ defaultHandler: null,
+ NOOPFUNC: function(){},
+ UNHANDLED: 'vAPI.messaging.notHandled'
+};
- 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
- }
- );
+/******************************************************************************/
+
+vAPI.messaging.listen = function(listenerName, callback) {
+ this.listeners[listenerName] = callback;
+};
+
+/******************************************************************************/
+
+vAPI.messaging.onMessage = function(request) {
+ var callback = vAPI.messaging.NOOPFUNC;
+ if ( request.message.requestId !== undefined ) {
+ callback = function(response) {
+ request.target.page.dispatchMessage(
+ request.name,
+ {
+ requestId: request.message.requestId,
+ portName: request.message.portName,
+ msg: response !== undefined ? response : null
}
- };
-
- var sender = {
- tab: {
- id: vAPI.tabs.getTabId(request.target)
- }
- };
-
- var listener = connector(request.message.msg, sender, callback);
-
- if (listener === vAPI.messaging.UNHANDLED) {
- 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);
+ var sender = {
+ tab: {
+ id: vAPI.tabs.getTabId(request.target)
}
+ };
+
+ // Specific handler
+ var r = vAPI.messaging.UNHANDLED;
+ var listener = vAPI.messaging.listeners[request.message.portName];
+ if ( typeof listener === 'function' ) {
+ r = listener(request.message.msg, sender, callback);
+ }
+ if ( r !== vAPI.messaging.UNHANDLED ) {
+ return;
+ }
+
+ // Default handler
+ r = vAPI.messaging.defaultHandler(request.message.msg, sender, callback);
+ if ( r !== vAPI.messaging.UNHANDLED ) {
+ return;
+ }
+
+ console.error('µBlock> messaging > unknown request: %o', request.message);
+
+ // Unhandled:
+ // Need to callback anyways in case caller expected an answer, or
+ // else there is a memory leak on caller's side
+ callback();
+};
+
+/******************************************************************************/
+
+vAPI.messaging.setup = function(defaultHandler) {
+ // Already setup?
+ if ( this.defaultHandler !== null ) {
+ return;
+ }
+
+ if ( typeof defaultHandler !== 'function' ) {
+ defaultHandler = function(){ return vAPI.messaging.UNHANDLED; };
+ }
+ this.defaultHandler = defaultHandler;
+
+ // the third parameter must stay false (bubbling), so later
+ // onBeforeRequest will use true (capturing), where we can invoke
+ // stopPropagation() (this way this.onMessage won't be fired)
+ safari.application.addEventListener('message', this.onMessage, false);
+};
+
+/******************************************************************************/
+
+vAPI.messaging.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;
+vAPI.net = {}
- if (typeof onBeforeRequest.callback === 'function') {
- if (!Array.isArray(onBeforeRequest.types)) {
- onBeforeRequest.types = [];
+/******************************************************************************/
+
+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;
}
- 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;
- }
+ // 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;
- };
+ }
- safari.application.addEventListener('message', this.onBeforeRequest.callback, true);
- }
- }
-};
+ // 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);
-/******************************************************************************/
-
-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) {
+ if (vAPI.tabs.onPopup(e.message)) {
+ e.message = false;
return;
}
}
- e.contextMenu.appendContextMenuItem(menuItemId, menuTitle);
+ 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;
};
- 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;
+ safari.application.addEventListener('message', this.onBeforeRequest.callback, true);
}
};
/******************************************************************************/
-vAPI.lastError = {
+vAPI.contextMenu = {};
+
+/******************************************************************************/
+
+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.onContextMenuCmd = 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.onContextMenuCmd);
+};
+
+/******************************************************************************/
+
+vAPI.contextMenu.remove = function() {
+ safari.application.removeEventListener('contextmenu', this.onContextMenu);
+ safari.application.removeEventListener("command", this.onContextMenuCmd);
+ this.onContextMenu = null;
+ this.onContextMenuCmd = null;
+};
+
+/******************************************************************************/
+
+vAPI.lastError = function() {
return null;
};
diff --git a/meta/safariextz/vapi-client.js b/meta/safariextz/vapi-client.js
index b9ddfd3a9..65e40029e 100644
--- a/meta/safariextz/vapi-client.js
+++ b/meta/safariextz/vapi-client.js
@@ -21,8 +21,6 @@
// For non background pages
-/* global addMessageListener, removeMessageListener, sendAsyncMessage */
-
/******************************************************************************/
(function() {
@@ -36,42 +34,41 @@ self.vAPI.safari = true;
/******************************************************************************/
-// since this is common across vendors
var messagingConnector = function(response) {
- var channel, listener;
-
- if (!response) {
+ if ( !response ) {
return;
}
- if (response.broadcast === true) {
- for (channel in vAPI.messaging.channels) {
- listener = vAPI.messaging.channels[channel].listener;
+ var channel, listener;
- if (typeof listener === 'function') {
+ 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) {
+ if ( response.requestId ) {
listener = vAPI.messaging.listeners[response.requestId];
delete vAPI.messaging.listeners[response.requestId];
delete response.requestId;
}
- if (!listener) {
+ if ( !listener ) {
channel = vAPI.messaging.channels[response.portName];
listener = channel && channel.listener;
}
- if (typeof listener === 'function') {
+ if ( typeof listener === 'function' ) {
listener(response.msg);
}
};
+/******************************************************************************/
+
var uniqueId = function() {
return parseInt(Math.random() * 1e10, 10).toString(36);
};
@@ -83,8 +80,9 @@ var uniqueId = function() {
vAPI.messaging = {
channels: {},
listeners: {},
- requestId: 0,
+ requestId: 1,
connectorId: uniqueId(),
+
setup: function() {
this.connector = function(msg) {
// messages from the background script are sent to every frame,
@@ -108,11 +106,13 @@ vAPI.messaging = {
close: function() {
if (this.connector) {
safari.self.removeEventListener('message', this.connector, false);
- this.connector = this.channels = this.listeners = null;
+ this.connector = null;
+ this.channels = {};
+ this.listeners = {};
}
},
channel: function(channelName, callback) {
- if (!channelName) {
+ if ( !channelName ) {
return;
}
@@ -129,15 +129,19 @@ vAPI.messaging = {
msg: message
};
- if (callback) {
- message.requestId = ++vAPI.messaging.requestId;
+ if ( callback ) {
+ message.requestId = vAPI.messaging.requestId++;
vAPI.messaging.listeners[message.requestId] = callback;
}
+ // popover content doesn't know messaging...
if (safari.extension.globalPage) {
- // popover content doesn't know messaging...
+ if (!safari.self.visible) {
+ return;
+ }
+
safari.extension.globalPage.contentWindow
- .vAPI.messaging.connector({
+ .vAPI.messaging.onMessage({
name: vAPI.messaging.connectorId,
message: message,
target: {
@@ -167,6 +171,8 @@ vAPI.messaging = {
/******************************************************************************/
+// content scripts are loaded into extension pages by default, but they shouldn't
+
if (location.protocol === "safari-extension:") {
return;
}
@@ -184,7 +190,7 @@ if (!window.MutationObserver) {
handler([{addedNodes: [e.target]}]);
}, true);
};
- }
+ };
}
/******************************************************************************/
diff --git a/meta/safariextz/vapi-common.js b/meta/safariextz/vapi-common.js
index 1af6ad998..2021d7765 100644
--- a/meta/safariextz/vapi-common.js
+++ b/meta/safariextz/vapi-common.js
@@ -27,45 +27,44 @@
'use strict';
-/******************************************************************************/
-
self.vAPI = self.vAPI || {};
/******************************************************************************/
// http://www.w3.org/International/questions/qa-scripts#directions
-var setScriptDirection = function(langugae) {
+
+var setScriptDirection = function(language) {
document.body.setAttribute(
'dir',
- ~['ar', 'he', 'fa', 'ps', 'ur'].indexOf(langugae) ? 'rtl' : 'ltr'
+ ~['ar', 'he', 'fa', 'ps', 'ur'].indexOf(language) ? 'rtl' : 'ltr'
);
};
/******************************************************************************/
vAPI.download = function(details) {
- if (!details.url) {
+ if ( !details.url ) {
return;
}
var a = document.createElement('a');
- if ('download' in a) {
+ if ( 'download' in a ) {
a.href = details.url;
a.setAttribute('download', details.filename || '');
a.dispatchEvent(new MouseEvent('click'));
+ return;
}
- else {
- var messager = vAPI.messaging.channel('_download');
- messager.send({
- what: 'gotoURL',
- details: {
- url: details.url,
- index: -1
- }
- });
- messager.close();
- }
+
+ var messager = vAPI.messaging.channel('_download');
+ messager.send({
+ what: 'gotoURL',
+ details: {
+ url: details.url,
+ index: -1
+ }
+ });
+ messager.close();
};
/******************************************************************************/
@@ -76,22 +75,29 @@ vAPI.getURL = function(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);
+// supported languages
+// first language is the default
+vAPI.i18nData = [
+ "en", "ar", "cs", "da", "de", "el", "es", "et", "fi", "fr", "he", "hi",
+ "hr", "hu", "id", "it", "ja", "mr", "nb", "nl", "pl", "pt_BR", "pt_PT",
+ "ro", "ru", "sv", "tr", "uk", "vi", "zh_CN"
+];
-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._;
+vAPI.i18n = navigator.language.replace('-', '_');
+
+if (vAPI.i18nData.indexOf(vAPI.i18n) === -1) {
+ vAPI.i18n = vAPI.i18n.slice(0, 2);
+
+ if (vAPI.i18nData.indexOf(vAPI.i18n) === -1) {
+ vAPI.i18n = vAPI.i18nData[0];
+ }
}
-xhr = new XMLHttpRequest;
+setScriptDirection(vAPI.i18n);
+
+var xhr = new XMLHttpRequest;
xhr.overrideMimeType('application/json;charset=utf-8');
-xhr.open('GET', './_locales/' + vAPI.i18nLocale + '/messages.json', false);
+xhr.open('GET', './_locales/' + vAPI.i18n + '/messages.json', false);
xhr.send();
vAPI.i18nData = JSON.parse(xhr.responseText);
@@ -103,12 +109,10 @@ 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) {
+if (safari.self.identifier === 'popover') {
var onLoaded = function() {
// Initial dimensions are set in Info.plist
var pWidth = safari.self.width;
@@ -142,7 +146,7 @@ if (safari.self.identifier === 'popover' && safari.self) {
}
};
- window.addEventListener('load', );
+ window.addEventListener('load', onLoaded);
}
/******************************************************************************/
diff --git a/meta/vapi-appinfo.js b/meta/vapi-appinfo.js
new file mode 100644
index 000000000..f5f7131ab
--- /dev/null
+++ b/meta/vapi-appinfo.js
@@ -0,0 +1,9 @@
+// can be included anywhere if it's needed
+'use strict';
+
+self.vAPI = self.vAPI || {};
+
+vAPI.app = {
+ name: 'µBlock',
+ version: '0.7.0.10'
+};
\ No newline at end of file
diff --git a/src/Info.plist b/src/Info.plist
deleted file mode 100644
index cf52f1edd..000000000
--- a/src/Info.plist
+++ /dev/null
@@ -1,91 +0,0 @@
-
-
-
-
- Author
- Raymond Hill
- Builder Version
- 534.57.2
- CFBundleDisplayName
- µBlock
- CFBundleIdentifier
- net.gorhill.uBlock
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleShortVersionString
- 0.7.0.10
- CFBundleVersion
- 1456132
- Chrome
-
- Database Quota
- 52428800
- Global Page
- background.html
- Popovers
-
-
- Filename
- popup.html
- Height
- 310
- Identifier
- popover
- Width
- 180
-
-
- Toolbar Items
-
-
- Identifier
- toolbarItem
- Image
- img/icon16.png
- Label
- µBlock
- Popover
- popover
-
-
-
- Content
-
- Scripts
-
- End
-
- js/contentscript-end.js
-
- Start
-
- js/vapi-client.js
- js/contentscript-start.js
-
-
- Whitelist
-
- http://*/*
- https://*/*
-
-
- Description
- Finally, an efficient blocker for Chromium-based browsers. Easy on CPU and memory.
- ExtensionInfoDictionaryVersion
- 1.0
- Permissions
-
- Website Access
-
- Include Secure Pages
-
- Level
- All
-
-
-
- Website
- https://github.com/gorhill/uBlock
-
-
diff --git a/src/Settings.plist b/src/Settings.plist
deleted file mode 100644
index 40cc1ff16..000000000
--- a/src/Settings.plist
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
- DefaultValue
-
- FalseValue
-
- Key
- open_prefs
- Secure
-
- Title
- Click to see the Preferences
- TrueValue
-
- Type
- CheckBox
-
-
-
diff --git a/src/icon.png b/src/icon.png
deleted file mode 100644
index 165e37d81..000000000
Binary files a/src/icon.png and /dev/null differ
diff --git a/src/js/vapi-appinfo.js b/src/js/vapi-appinfo.js
deleted file mode 100644
index 2deaeb5fc..000000000
--- a/src/js/vapi-appinfo.js
+++ /dev/null
@@ -1,10 +0,0 @@
-// can be included anywhere if it's needed
-'use strict';
-
-self.vAPI = self.vAPI || {};
-
-vAPI.app = {
- /**/name: 'µBlock',
- /**/version: '0.7.0.10',
- /**/url: 'https://github.com/gorhill/uBlock',
-};
\ No newline at end of file
diff --git a/src/js/vapi-background.js b/src/js/vapi-background.js
deleted file mode 100644
index bccf934da..000000000
--- a/src/js/vapi-background.js
+++ /dev/null
@@ -1,905 +0,0 @@
-// » header
-/* global SafariBrowserTab, Services, XPCOMUtils */
-// for background page only
-
-(function() {
-'use strict';
-
-self.vAPI = self.vAPI || {};
-// «
-
-if (self.chrome) {
- // » crx
- 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);
- }
- };
- // «
-} else if (self.safari) {
- // » safariextz
- 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 === vAPI.messaging.UNHANDLED) {
- 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;
- }
- };
- // «
-}
-
-// » footer
-
-if (!self.chrome) {
- self.chrome = { runtime: { lastError: null } };
-}
-})();
-// «
diff --git a/src/js/vapi-client.js b/src/js/vapi-client.js
deleted file mode 100644
index 69bd09c11..000000000
--- a/src/js/vapi-client.js
+++ /dev/null
@@ -1,406 +0,0 @@
-// » header
-/* global addMessageListener, removeMessageListener, sendAsyncMessage */
-// for non background pages
-
-(function() {
-'use strict';
-
-self.vAPI = self.vAPI || {};
-
-// 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);
-};
-// «
-
-if (self.chrome) {
- // » crx
- vAPI.chrome = true;
- vAPI.messaging = {
- port: null,
- channels: {},
- listeners: {},
- requestId: 0,
- connectorId: uniqueId(),
- setup: function() {
- this.port = chrome.runtime.connect({name: this.connectorId});
- this.port.onMessage.addListener(messagingConnector);
- },
- close: function() {
- if (this.port) {
- this.port.disconnect();
- this.port.onMessage.removeListener(messagingConnector);
- this.port = 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.port) {
- 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];
- }
- };
- // «
-} else if (self.safari) {
- // » safariextz
- vAPI.safari = true;
-
- // 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'
- });
- }
- // «
-}
-// » footer
-})();
-// «
\ No newline at end of file
diff --git a/src/js/vapi-common.js b/src/js/vapi-common.js
deleted file mode 100644
index d8c0b4a66..000000000
--- a/src/js/vapi-common.js
+++ /dev/null
@@ -1,131 +0,0 @@
-// » header
-// could be used for background and other extension 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();
- }
-};
-// «
-
-if (self.chrome) {
- // » crx
- 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'));
- // «
-} else if (self.safari) {
- // » safariextz
- 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) {
- window.addEventListener('load', 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);
- }
- });
- }
- // «
-}
-// » footer
-})();
-// «
\ No newline at end of file
diff --git a/src/locales.json b/src/locales.json
deleted file mode 100644
index 7561ae29a..000000000
--- a/src/locales.json
+++ /dev/null
@@ -1 +0,0 @@
-{"_": "en", "ar": 1, "cs": 1, "da": 1, "de": 1, "el": 1, "en": 1, "es": 1, "et": 1, "fi": 1, "fr": 1, "he": 1, "hi": 1, "hr": 1, "hu": 1, "id": 1, "it": 1, "ja": 1, "mr": 1, "nb": 1, "nl": 1, "pl": 1, "pt-BR": 1, "pt-PT": 1, "ro": 1, "ru": 1, "sv": 1, "tr": 1, "uk": 1, "vi": 1, "zh-CN": 1}
\ No newline at end of file
diff --git a/src/manifest.json b/src/manifest.json
deleted file mode 100644
index 1d983d822..000000000
--- a/src/manifest.json
+++ /dev/null
@@ -1,61 +0,0 @@
-{
- "manifest_version": 2,
- "minimum_chrome_version": "22.0",
- "default_locale": "en",
- "update_url": "https://clients2.google.com/service/update2/crx",
-
- "version": "0.7.0.10",
- "name": "µBlock",
- "description": "__MSG_extShortDesc__",
- "homepage_url": "https://github.com/gorhill/uBlock",
- "author": "Raymond Hill",
- "developer": {
- "name": "Raymond Hill",
- "email": "rhill@raymondhill.net"
- },
-
- "icons": {
- "16": "img/icon_16.png",
- "128": "img/icon_128.png"
- },
-
- "permissions": [
- "contextMenus",
- "storage",
- "tabs",
- "unlimitedStorage",
- "webNavigation",
- "webRequest",
- "webRequestBlocking",
- "http://*/*",
- "https://*/*"
- ],
-
- "background": {
- "page": "background.html"
- },
- "options_page": "dashboard.html",
- "content_scripts": [
- {
- "matches": ["http://*/*", "https://*/*"],
- "js": ["js/vapi-client.js", "js/contentscript-start.js"],
- "run_at": "document_start",
- "all_frames": true
- },
- {
- "matches": ["http://*/*", "https://*/*"],
- "js": ["js/contentscript-end.js"],
- "run_at": "document_end",
- "all_frames": true
- }
- ],
-
- "browser_action": {
- "default_icon": {
- "19": "img/browsericons/icon19-off.png",
- "38": "img/browsericons/icon38-off.png"
- },
- "default_title": "µBlock",
- "default_popup": "popup.html"
- }
-}
\ No newline at end of file
diff --git a/tools/build.py b/tools/build.py
deleted file mode 100644
index 41cbe7983..000000000
--- a/tools/build.py
+++ /dev/null
@@ -1,269 +0,0 @@
-#!/usr/bin/env python3
-
-import os
-import re
-import json
-import glob
-import sys
-import subprocess
-from time import strftime
-from datetime import datetime
-from shutil import which as iscmd, rmtree as rmt, copytree, copy, move
-from collections import OrderedDict
-from xml.sax.saxutils import escape
-
-osp = os.path
-pj = osp.join
-
-os.chdir(pj(osp.split(osp.abspath(__file__))[0], '..'))
-
-
-def rmtree(path):
- if osp.exists(path):
- rmt(path)
-
-
-def mkdirs(path):
- try:
- os.makedirs(path)
- finally:
- return osp.exists(path)
-
-
-def readfile(path, mode='rt'):
- with open(path, mode) as f:
- return f.read()
-
-
-src_dir = osp.abspath(pj('src'))
-meta_dir = osp.abspath(pj('meta'))
-tmp_dir = osp.abspath(pj('tmp'))
-
-with open(pj(meta_dir, 'config.json'), encoding='utf-8') as f:
- config = json.load(f)
-
-vendors = config['vendors']
-del config['vendors']
-
-tmp = datetime.now() - datetime(year=datetime.today().year, month=1, day=1)
-config['build_number'] = strftime('%y' + str(int(tmp.total_seconds() * 65535 / 31536000)).zfill(5))
-
-descriptions = OrderedDict({})
-source_locale_dir = pj('src', '_locales')
-
-build_tmp = pj(tmp_dir, config['clean_name'])
-build_dir = osp.abspath(pj('dist', 'build', config['version']))
-
-
-# fill 'descriptions'
-for alpha2 in os.listdir(source_locale_dir):
- with open(pj(source_locale_dir, alpha2, 'messages.json'), encoding='utf-8') as f:
- string_data = json.load(f, object_pairs_hook=OrderedDict)
-
- descriptions[alpha2] = string_data['extShortDesc']['message']
-
-
-# only needed for Safari
-with open(pj(src_dir, 'locales.json'), 'wt', encoding='utf-8', newline='\n') as f:
- tmp = {
- '_': config['def_lang']
- }
-
- for alpha2 in descriptions:
- tmp[alpha2] = 1
-
- json.dump(tmp, f, sort_keys=True, ensure_ascii=False)
-
-
-with open(pj(src_dir, 'js', 'vapi-appinfo.js'), 'r+t', encoding='utf-8', newline='\n') as f:
- tmp = f.read()
- f.seek(0)
-
- f.write(re.sub(
- r'/\*\*/([^:]+:).+',
- lambda m: '/**/' + m.group(1) + " '" + config[m.group(1)[:-1]] + "',",
- tmp
- ))
-
-
-with open(pj(src_dir, vendors['crx']['manifest']), 'wt', encoding='utf-8', newline='\n') as f:
- cf_content = readfile(pj(meta_dir, 'crx', vendors['crx']['manifest']))
-
- f.write(
- re.sub(r"\{(?=\W)|(?<=\W)\}", r'\g<0>\g<0>', cf_content).format(**config)
- )
-
-
-with open(pj(src_dir, vendors['safariextz']['manifest']['Info']), 'wt', encoding='utf-8', newline='\n') as f:
- config['app_id'] = vendors['safariextz']['app_id']
- config['description'] = descriptions[config['def_lang']]
- cf_content = readfile(pj(meta_dir, 'safariextz', vendors['safariextz']['manifest']['Info']))
- f.write(cf_content.format(**config))
-
-copy(pj(meta_dir, 'safariextz', vendors['safariextz']['manifest']['Settings']), pj(src_dir, vendors['safariextz']['manifest']['Settings']))
-
-
-if 'meta' in sys.argv:
- raise SystemExit('Metadata generated.')
-
-
-rmtree(tmp_dir)
-mkdirs(tmp_dir)
-
-rmtree(build_dir)
-mkdirs(build_dir)
-
-# create update meta
-for vendor, ext in {'crx': 'xml', 'safariextz': 'plist'}.items():
- with open(pj(build_dir, 'update_' + vendor + '.' + ext), 'wt', encoding='utf-8', newline='\n') as f:
- if vendor == 'safariextz':
- config['developer_identifier'] = vendors[vendor]['developer_identifier']
-
- config['app_id'] = vendors[vendor]['app_id']
- cf_content = readfile(pj(meta_dir, vendor, 'update_' + vendor + '.' + ext))
- f.write(cf_content.format(**config))
- f.close()
-
-
-# separate vendor specific code
-for vapijsfile in [pj(src_dir, 'js', 'vapi-' + jsfile + '.js') for jsfile in ['background', 'common', 'client']]:
- vapijs = readfile(vapijsfile)
-
- # "» name" is the start marker, "«" is the end marker
- js_parts = re.findall(r'»\s*(\w+)\n([^«]+)//', vapijs)
-
- if not js_parts:
- continue
-
- js_header = js_parts.pop(0)[1]
- js_footer = js_parts.pop()[1]
-
- for js in js_parts:
- with open(pj(tmp_dir, js[0] + '_' + osp.basename(vapijsfile)), 'wt', encoding='utf-8', newline='\n') as f:
- f.write(js_header)
- f.write(re.sub(r'^ ', '', js[1], flags=re.M))
- f.write(js_footer)
-
-
-def move_vendor_specific_js(vendor):
- for file in ['background', 'common', 'client']:
- move(pj(tmp_dir, vendor + '_vapi-' + file + '.js'), pj(build_tmp, 'js', 'vapi-' + file + '.js'))
-
-
-def copy_vendor_files(files):
- for file in files:
- path = pj(src_dir, file)
-
- if osp.isdir(path):
- copytree(path, pj(build_tmp, file), copy_function=copy)
- else:
- copy(path, pj(build_tmp, file))
-
-
-def remove_vendor_files(files):
- for file in files:
- path = pj(build_tmp, file)
-
- if osp.isdir(path):
- rmtree(path)
- else:
- os.remove(path)
-
-
-def norm_cygdrive(path):
- return '/cygdrive/' + path[0] + path[2:].replace('\\', '/') if path[1] == ':' else path
-
-
-mkdirs(build_tmp)
-
-for file in glob.iglob(pj(src_dir, '*')):
- basename = osp.basename(file)
-
- if osp.isfile(file) and (file.endswith('.html') or basename == 'icon.png'):
- copy(file, pj(build_tmp, basename))
- elif osp.isdir(file) and basename not in ['_locales', 'locale']:
- copytree(file, pj(build_tmp, basename), copy_function=copy)
-
-os.remove(pj(build_tmp, 'js', 'sitepatch-safari.js'))
-
-
-package_name = config['clean_name'] + '-' + config['version']
-
-
-# Chrome
-if not iscmd('7z'):
- print('Cannot build for Chrome: `7z` command not found.')
-else:
- vendor_files = ['_locales', 'manifest.json']
-
- move_vendor_specific_js('crx')
- copy_vendor_files(vendor_files)
-
- package = pj(build_dir, package_name + '.zip')
- subprocess.call('7z a -r -tzip -mx=8 "' + norm_cygdrive(package) + '" "' + norm_cygdrive(pj(build_tmp, '*')) + '"', stdout=subprocess.DEVNULL)
-
- if osp.exists(vendors['crx']['private_key']):
- if not iscmd('openssl'):
- print('Cannot build for Chrome: `openssl` command not found.')
- else:
- # Convert the PEM key to DER (and extract the public form) for inclusion in the CRX header
- derkey = subprocess.Popen([
- 'openssl', 'rsa', '-pubout',
- '-inform', 'PEM',
- '-outform', 'DER',
- '-in', norm_cygdrive(vendors['crx']['private_key'])
- ], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.read()
- # Sign the zip file with the private key in PEM format
- signature = subprocess.Popen([
- 'openssl', 'sha1',
- '-sign', norm_cygdrive(vendors['crx']['private_key']),
- norm_cygdrive(package)
- ], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.read()
- out = open(package.replace('.zip', vendors['crx']['file_ext']), "wb")
- # Extension file magic number
- out.write(bytes("Cr24\x02\x00\x00\x00", 'UTF-8') + len(derkey).to_bytes(4, 'little') + len(signature).to_bytes(4, 'little'))
- out.write(derkey)
- out.write(signature)
- out.write(readfile(package, 'rb'))
- out.close()
-
- subprocess.call('7z a ' + norm_cygdrive(package) + ' ' + norm_cygdrive(osp.abspath(vendors['crx']['private_key'])), stdout=subprocess.DEVNULL)
-
- remove_vendor_files(vendor_files)
-
-
-# Safari
-if not iscmd('xar'):
- print('Cannot build for Safari: `xar` command not found.')
-elif osp.exists(vendors['safariextz']['cert_dir']):
- vendor_files = [
- '_locales',
- 'locales.json',
- 'Info.plist',
- 'Settings.plist',
- pj('js', 'sitepatch-safari.js')
- ]
-
- move_vendor_specific_js('safariextz')
- copy_vendor_files(vendor_files)
-
- build_tmp = move(build_tmp, pj(tmp_dir, config['clean_name'] + '.safariextension'))
-
- # xar accepts only unix style directory separators
- package = pj(build_dir, package_name + vendors['safariextz']['file_ext']).replace('\\', '/');
- subprocess.call('xar -czf "' + package + '" --compression-args=9 --distribution --directory="' + osp.basename(tmp_dir) + '" ' + config['clean_name'] + '.safariextension', stderr=subprocess.DEVNULL)
- subprocess.call('xar --sign -f "' + package + '" --digestinfo-to-sign sfr_digest.dat --sig-size 256 ' + ' '.join('--cert-loc="' + vendors['safariextz']['cert_dir'] + 'cert0{0}"'.format(i) for i in range(3)), stderr=subprocess.DEVNULL)
- subprocess.call('openssl rsautl -sign -inkey ' + vendors['safariextz']['private_key'] + ' -in sfr_digest.dat -out sfr_sig.dat', stderr=subprocess.DEVNULL)
- subprocess.call('xar --inject-sig sfr_sig.dat -f "' + package + '"', stderr=subprocess.DEVNULL)
-
- os.remove('sfr_sig.dat')
- os.remove('sfr_digest.dat')
-
- build_tmp = move(build_tmp, pj(tmp_dir, config['clean_name']))
-
- remove_vendor_files(vendor_files)
-
-
-rmtree(tmp_dir)
-
-print("Files ready @ " + build_dir)
diff --git a/tools/make-chrome.sh b/tools/make-chrome.sh
index 9a4f075db..e5a3d08b7 100755
--- a/tools/make-chrome.sh
+++ b/tools/make-chrome.sh
@@ -12,12 +12,10 @@ rm $DES/assets/*.sh
cp -R src/css $DES/
cp -R src/img $DES/
cp -R src/js $DES/
-rm $DES/js/vapi-background.js
-rm $DES/js/vapi-client.js
-rm $DES/js/vapi-common.js
cp -R src/lib $DES/
cp -R src/_locales $DES/
cp src/*.html $DES/
+cp meta/vapi-appinfo.js $DES/js/
cp meta/crx/*.js $DES/js/
cp meta/crx/manifest.json $DES/
echo "*** uBlock.chromium: Package done."
diff --git a/tools/make-safari.sh b/tools/make-safari.sh
new file mode 100644
index 000000000..a681b0ebf
--- /dev/null
+++ b/tools/make-safari.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+#
+# This script assumes a linux environment
+
+echo "*** uBlock.safariextension: Copying files"
+DES=dist/build/uBlock.safariextension
+rm -r $DES
+mkdir -p $DES
+cp -R assets $DES/
+rm $DES/assets/*.sh
+cp -R src/css $DES/
+cp -R src/img $DES/
+cp -R src/js $DES/
+cp -R src/lib $DES/
+cp -R src/_locales $DES/
+cp src/*.html $DES/
+cp src/img/icon_128.png $DES/Icon.png
+cp meta/vapi-appinfo.js $DES/js/
+cp meta/safariextz/*.js $DES/js/
+cp meta/safariextz/Info.plist $DES/
+cp meta/safariextz/Settings.plist $DES/
+echo "*** uBlock.safariextension: Package done."