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."