From 3c76c61f61103c147b7e066583f773b4cd235c5a Mon Sep 17 00:00:00 2001 From: Deathamns Date: Tue, 2 Dec 2014 08:35:25 +0100 Subject: [PATCH] Firefox: partial vAPI.tabs implementation --- platform/firefox/bootstrap.js | 55 +-- platform/firefox/frameScript.js | 145 ++++--- platform/firefox/install.rdf | 2 +- platform/firefox/vapi-background.js | 353 ++++++++++++++++-- platform/firefox/vapi-client.js | 3 +- src/js/asset-viewer.js | 9 +- tools/make-firefox.sh | 4 +- ...nvert-locale.py => make-locale-firefox.py} | 0 8 files changed, 438 insertions(+), 133 deletions(-) rename tools/{xpi-convert-locale.py => make-locale-firefox.py} (100%) diff --git a/platform/firefox/bootstrap.js b/platform/firefox/bootstrap.js index f9fae1756..a3096afa8 100644 --- a/platform/firefox/bootstrap.js +++ b/platform/firefox/bootstrap.js @@ -3,44 +3,45 @@ 'use strict'; -var bgProcess; Components.utils['import']('resource://gre/modules/Services.jsm'); +var bgProcess; + function startup(data, reason) { - bgProcess = function(ev) { - if (ev) { - this.removeEventListener('load', bgProcess); - } + bgProcess = function(ev) { + if (ev) { + this.removeEventListener('load', bgProcess); + } - bgProcess = Services.appShell.hiddenDOMWindow.document; - bgProcess = bgProcess.documentElement.appendChild( - bgProcess.createElementNS('http://www.w3.org/1999/xhtml', 'iframe') - ); - bgProcess.setAttribute('src', 'chrome://ublock/content/background.html'); - }; + bgProcess = Services.appShell.hiddenDOMWindow.document; + bgProcess = bgProcess.documentElement.appendChild( + bgProcess.createElementNS('http://www.w3.org/1999/xhtml', 'iframe') + ); + bgProcess.setAttribute('src', 'chrome://ublock/content/background.html'); + }; - if (reason === APP_STARTUP) { - Services.ww.registerNotification({ - observe: function(subject) { - Services.ww.unregisterNotification(this); - subject.addEventListener('load', bgProcess); - } - }); - } - else { - bgProcess(); - } + if (reason === APP_STARTUP) { + Services.ww.registerNotification({ + observe: function(subject) { + Services.ww.unregisterNotification(this); + subject.addEventListener('load', bgProcess); + } + }); + } + else { + bgProcess(); + } } function shutdown(data, reason) { - if (reason !== APP_SHUTDOWN) { - bgProcess.parentNode.removeChild(bgProcess); - } + if (reason !== APP_SHUTDOWN) { + bgProcess.parentNode.removeChild(bgProcess); + } } function install() { - // https://bugzil.la/719376 - Services.strings.flushBundles(); + // https://bugzil.la/719376 + Services.strings.flushBundles(); } function uninstall() {} \ No newline at end of file diff --git a/platform/firefox/frameScript.js b/platform/firefox/frameScript.js index c94f260de..a27022f18 100644 --- a/platform/firefox/frameScript.js +++ b/platform/firefox/frameScript.js @@ -4,96 +4,95 @@ 'use strict'; -let - appName = 'ublock', - contentBaseURI = 'chrome://' + appName + '/content/js/', - listeners = {}, - _addMessageListener = function(id, fn) { - _removeMessageListener(id); - listeners[id] = function(msg) { - fn(msg.data); - }; - addMessageListener(id, listeners[id]); - }, - _removeMessageListener = function(id) { - if (listeners[id]) { - removeMessageListener(id, listeners[id]); - } +let appName = 'ublock'; +let contentBaseURI = 'chrome://' + appName + '/content/js/'; +let listeners = {}; - delete listeners[id]; - }; +let _addMessageListener = function(id, fn) { + _removeMessageListener(id); + listeners[id] = function(msg) { + fn(msg.data); + }; + addMessageListener(id, listeners[id]); +}; + +let _removeMessageListener = function(id) { + if (listeners[id]) { + removeMessageListener(id, listeners[id]); + } + + delete listeners[id]; +}; addMessageListener('µBlock:broadcast', function(msg) { - for (var id in listeners) { - listeners[id](msg); - } + for (let id in listeners) { + listeners[id](msg); + } }); -var observer = { - unload: function(e) { - Services.obs.removeObserver(observer, 'content-document-global-created'); - observer = listeners = null; - }, - onDOMReady: function(e) { - var win = e.target.defaultView; +let initContext = function(win, sandbox) { + if (sandbox) { + win = Components.utils.Sandbox([win], { + sandboxPrototype: win, + wantComponents: false, + wantXHRConstructor: false + }); + } - if (win.location.protocol === 'chrome:' && win.location.host === appName) { - win.sendAsyncMessage = sendAsyncMessage; - win.addMessageListener = _addMessageListener; - win.removeMessageListener = _removeMessageListener; - } - }, - observe: function(win) { - if (!win || win.top !== content) { - return; - } + win.sendAsyncMessage = sendAsyncMessage; + win.addMessageListener = _addMessageListener; + win.removeMessageListener = _removeMessageListener; - // baseURI is more reliable - var location = Services.io.newURI( - win.location.protocol === 'data:' ? 'data:text/plain,' : win.document.baseURI, - null, - null - ); + return win; +}; - if (!(win.document instanceof win.HTMLDocument - && (/^https?$/.test(location.scheme)))) { - return; - } +let observer = { + observe: function(win) { + if (!win || win.top !== content) { + return; + } - win = Components.utils.Sandbox([win], { - sandboxPrototype: win, - wantComponents: false, - wantXHRConstructor: false - }); + if (!(win.document instanceof win.HTMLDocument + && (/^https?:$/.test(win.location.protocol)))) { + return; + } - win.sendAsyncMessage = sendAsyncMessage; - win.addMessageListener = _addMessageListener; - win.removeMessageListener = _removeMessageListener; + let lss = Services.scriptloader.loadSubScript; + win = initContext(win, true); - var lss = Services.scriptloader.loadSubScript; + lss(contentBaseURI + 'vapi-client.js', win); + lss(contentBaseURI + 'contentscript-start.js', win); - lss(contentBaseURI + 'vapi-client.js', win); - lss(contentBaseURI + 'contentscript-start.js', win); + if (win.document.readyState === 'loading') { + let docReady = function(e) { + this.removeEventListener(e.type, docReady, true); + lss(contentBaseURI + 'contentscript-end.js', win); + }; - if (win.document.readyState === 'loading') { - let docReady = function(e) { - this.removeEventListener(e.type, docReady, true); - lss(contentBaseURI + 'contentscript-end.js', win); - }; - - win.document.addEventListener('DOMContentLoaded', docReady, true); - } - else { - lss(contentBaseURI + 'contentscript-end.js', win); - } - } + win.document.addEventListener('DOMContentLoaded', docReady, true); + } + else { + lss(contentBaseURI + 'contentscript-end.js', win); + } + } }; Services.obs.addObserver(observer, 'content-document-global-created', false); -addEventListener('unload', observer.unload, false); +let DOMReady = function(e) { + let win = e.target.defaultView; -// for the Options page -addEventListener('DOMContentLoaded', observer.onDOMReady, true); + // inject the message handlers for the options page + if (win.location.protocol === 'chrome:' && win.location.host === appName) { + initContext(win); + } +}; + +addEventListener('DOMContentLoaded', DOMReady, true); + +addEventListener('unload', function() { + Services.obs.removeObserver(observer, 'content-document-global-created'); + observer = listeners = null; +}, false); })(); \ No newline at end of file diff --git a/platform/firefox/install.rdf b/platform/firefox/install.rdf index 17fd41513..9be5d79e1 100644 --- a/platform/firefox/install.rdf +++ b/platform/firefox/install.rdf @@ -2,7 +2,7 @@ {2b10c1c8-a11f-4bad-fe9c-1c11e82cac42} - 0.7.0.11 + 0.7.2.0 µBlock Finally, an efficient blocker. Easy on CPU and memory. https://github.com/gorhill/uBlock diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js index 733c694fe..b669d69d9 100644 --- a/platform/firefox/vapi-background.js +++ b/platform/firefox/vapi-background.js @@ -43,6 +43,13 @@ vAPI.firefox = true; /******************************************************************************/ +vAPI.app = { + name: 'µBlock', + version: '0.7.2.0' +}; + +/******************************************************************************/ + var SQLite = { open: function() { var path = Services.dirsvc.get('ProfD', Ci.nsIFile); @@ -114,10 +121,10 @@ var SQLite = { vAPI.storage = { QUOTA_BYTES: 100 * 1024 * 1024, - sqlWhere: function(col, valNum) { - if (valNum > 0) { - valNum = Array(valNum + 1).join('?, ').slice(0, -2); - return ' WHERE ' + col + ' IN (' + valNum + ')'; + sqlWhere: function(col, params) { + if (params > 0) { + params = Array(params + 1).join('?, ').slice(0, -2); + return ' WHERE ' + col + ' IN (' + params + ')'; } return ''; @@ -165,12 +172,12 @@ vAPI.storage = { ); }, set: function(details, callback) { - var key, values = [], questionmarks = []; + var key, values = [], placeholders = []; for (key in details) { values.push(key); values.push(JSON.stringify(details[key])); - questionmarks.push('?, ?'); + placeholders.push('?, ?'); } if (!values.length) { @@ -179,7 +186,7 @@ vAPI.storage = { SQLite.run( 'INSERT OR REPLACE INTO settings (name, value) SELECT ' + - questionmarks.join(' UNION SELECT '), + placeholders.join(' UNION SELECT '), values, callback ); @@ -205,7 +212,7 @@ vAPI.storage = { } SQLite.run( - "SELECT 'size' AS size, SUM(LENGTH(value)) FROM settings" + + 'SELECT "size" AS size, SUM(LENGTH(value)) FROM settings' + this.sqlWhere('name', Array.isArray(keys) ? keys.length : 0), keys, function(result) { @@ -217,6 +224,295 @@ vAPI.storage = { /******************************************************************************/ +var windowWatcher = { + onTabClose: function(e) { + vAPI.tabs.onClosed(vAPI.tabs.getTabId(e.target)); + }, + onTabSelect: function(e) { + console.log(vAPI.tabs.getTabId(e.target)); + // vAPI.setIcon(); + }, + onLoad: function(e) { + if (e) { + this.removeEventListener('load', windowWatcher.onLoad); + } + + var docElement = this.document.documentElement; + + if (docElement.getAttribute('windowtype') !== 'navigator:browser') { + return; + } + + if (!this.gBrowser || this.gBrowser.tabContainer) { + return; + } + + var tC = this.gBrowser.tabContainer; + + this.gBrowser.addTabsProgressListener(tabsProgressListener); + tC.addEventListener('TabClose', windowWatcher.onTabClose); + tC.addEventListener('TabSelect', windowWatcher.onTabSelect); + + // when new window is opened TabSelect doesn't run on the selected tab? + }, + unregister: function() { + Services.ww.unregisterNotification(this); + + for (var win of vAPI.tabs.getWindows()) { + win.removeEventListener('load', this.onLoad); + win.gBrowser.removeTabsProgressListener(tabsProgressListener); + + var tC = win.gBrowser.tabContainer; + tC.removeEventListener('TabClose', this.onTabClose); + tC.removeEventListener('TabSelect', this.onTabSelect); + } + }, + observe: function(win, topic) { + if (topic === 'domwindowopened') { + win.addEventListener('load', this.onLoad); + } + } +}; + +/******************************************************************************/ + +var tabsProgressListener = { + onLocationChange: function(browser, webProgress, request, location, flags) { + if (!webProgress.isTopLevel) { + return; + } + + var tabId = vAPI.tabs.getTabId(browser); + + if (flags & 1) { + vAPI.tabs.onUpdated(tabId, {url: location.spec}, { + frameId: 0, + tabId: tabId, + url: browser.currentURI.spec + }); + } + else { + vAPI.tabs.onNavigation({ + frameId: 0, + tabId: tabId, + url: location.spec + }); + } + } +}; + +/******************************************************************************/ + +vAPI.tabs = {}; + +/******************************************************************************/ + +vAPI.tabs.registerListeners = function() { + // onNavigation and onUpdated handled with tabsProgressListener + // onClosed - handled in windowWatcher.onTabClose + // onPopup ? + + Services.ww.registerNotification(windowWatcher); + + // already opened windows + for (var win of this.getWindows()) { + windowWatcher.onLoad.call(win); + } +}; + +/******************************************************************************/ + +vAPI.tabs.getTabId = function(target) { + if (target.linkedPanel) { + return target.linkedPanel.slice(6); + } + + var gBrowser = target.ownerDocument.defaultView.gBrowser; + var i = gBrowser.browsers.indexOf(target); + + if (i !== -1) { + i = this.getTabId(gBrowser.tabs[i]); + } + + return i; +}; + +/******************************************************************************/ + +vAPI.tabs.get = function(tabId, callback) { + var tab, windows; + + if (tabId === null) { + tab = Services.wm.getMostRecentWindow('navigator:browser').gBrowser.selectedTab; + tabId = vAPI.tabs.getTabId(tab); + } + else { + windows = this.getWindows(); + + for (var win of windows) { + tab = win.gBrowser.tabContainer.querySelector( + 'tab[linkedpanel="panel-' + tabId + '"]' + ); + + if (tab) { + break; + } + } + } + + if (!tab) { + callback(); + return; + } + + var browser = tab.linkedBrowser; + var gBrowser = browser.ownerDocument.defaultView.gBrowser; + + if (!windows) { + windows = this.getWindows(); + } + + callback({ + id: tabId, + index: gBrowser.browsers.indexOf(browser), + windowId: windows.indexOf(browser.ownerDocument.defaultView), + active: tab === gBrowser.selectedTab, + url: browser.currentURI.spec, + title: tab.label + }); +}; + +/******************************************************************************/ + +vAPI.tabs.getAll = function(window) { + var tabs = []; + + for (var win of this.getWindows()) { + if (window && window !== win) { + continue; + } + + for (var tab of win.gBrowser.tabs) { + tabs.push(tab); + } + } + + return tabs; +}; + +/******************************************************************************/ + +vAPI.tabs.getWindows = function() { + var winumerator = Services.wm.getEnumerator('navigator:browser'); + var windows = []; + + while (winumerator.hasMoreElements()) { + var win = winumerator.getNext(); + + if (!win.closed) { + windows.push(win); + } + } + + return windows; +}; + +/******************************************************************************/ + +// 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 + +vAPI.tabs.open = function(details) { + if (!details.url) { + return null; + } + // extension pages + if (!/^[\w-]{2,}:/.test(details.url)) { + details.url = vAPI.getURL(details.url); + } + + var tab, tabs; + + if (details.select) { + var rgxHash = /#.*/; + // this is questionable + var url = details.url.replace(rgxHash, ''); + tabs = this.getAll(); + + for (tab of tabs) { + var browser = tab.linkedBrowser; + + if (browser.currentURI.spec.replace(rgxHash, '') === url) { + browser.ownerDocument.defaultView.gBrowser.selectedTab = tab; + return; + } + } + } + + if (details.active === undefined) { + details.active = true; + } + + var gBrowser = Services.wm.getMostRecentWindow('navigator:browser').gBrowser; + + if (details.index === -1) { + details.index = gBrowser.browsers.indexOf(gBrowser.selectedBrowser) + 1; + } + + if (details.tabId) { + tabs = tabs || this.getAll(); + + for (tab of tabs) { + if (vAPI.tabs.getTabId(tab) === details.tabId) { + tab.linkedBrowser.loadURI(details.url); + return; + } + } + } + + tab = gBrowser.loadOneTab(details.url, {inBackground: !details.active}); + + if (details.index !== undefined) { + gBrowser.moveTabTo(tab, details.index); + } +}; + +/******************************************************************************/ + +vAPI.tabs.close = function(tabIds) { + if (!Array.isArray(tabIds)) { + tabIds = [tabIds]; + } + + tabIds = tabIds.map(function(tabId) { + return 'tab[linkedpanel="panel-' + tabId + '"]'; + }).join(','); + + for (var win of this.getWindows()) { + var tabs = win.gBrowser.tabContainer.querySelectorAll(tabIds); + + if (!tabs) { + continue; + } + + for (var tab of tabs) { + win.gBrowser.removeTab(tab); + } + } +}; + +/******************************************************************************/ + +/*vAPI.tabs.injectScript = function(tabId, details, callback) { + +};*/ + +/******************************************************************************/ + vAPI.messaging = { gmm: Cc['@mozilla.org/globalmessagemanager;1'].getService(Ci.nsIMessageListenerManager), frameScript: 'chrome://ublock/content/frameScript.js', @@ -241,16 +537,17 @@ vAPI.messaging.listen = function(listenerName, callback) { vAPI.messaging.onMessage = function(request) { var messageManager = request.target.messageManager; var listenerId = request.data.portName.split('|'); + var requestId = request.data.requestId; var portName = listenerId[1]; listenerId = listenerId[0]; var callback = vAPI.messaging.NOOPFUNC; - if ( request.data.requestId !== undefined ) { + if ( requestId !== undefined ) { callback = function(response) { messageManager.sendAsyncMessage( listenerId, JSON.stringify({ - requestId: request.data.requestId, + requestId: requestId, portName: portName, msg: response !== undefined ? response : null }) @@ -258,10 +555,9 @@ vAPI.messaging.onMessage = function(request) { }; } - // TODO: var sender = { tab: { - id: 0 + id: vAPI.tabs.getTabId(request.target) } }; @@ -316,6 +612,16 @@ vAPI.messaging.broadcast = function(message) { /******************************************************************************/ +vAPI.messaging.unload = function() { + this.gmm.removeMessageListener( + vAPI.app.name + ':background', + this.onMessage + ); + this.gmm.removeDelayedFrameScript(this.frameScript); +}; + +/******************************************************************************/ + vAPI.lastError = function() { return null; }; @@ -326,27 +632,18 @@ vAPI.lastError = function() { window.addEventListener('unload', function() { SQLite.close(); - vAPI.messaging.gmm.removeMessageListener( - vAPI.app.name + ':background', - vAPI.messaging.postMessage - ); - vAPI.messaging.gmm.removeDelayedFrameScript(vAPI.messaging.frameScript); + windowWatcher.unregister(); + vAPI.messaging.unload(); // close extension tabs - var enumerator = Services.wm.getEnumerator('navigator:browser'); - var host = 'ublock'; - var gBrowser, tabs, i, extURI; + var extURI, win, tab, host = 'ublock'; - while (enumerator.hasMoreElements()) { - gBrowser = enumerator.getNext().gBrowser; - tabs = gBrowser.tabs; - i = tabs.length; - - while (i--) { - extURI = tabs[i].linkedBrowser.currentURI; + for (win of vAPI.tabs.getWindows()) { + for (tab of win.gBrowser.tabs) { + extURI = tab.linkedBrowser.currentURI; if (extURI.scheme === 'chrome' && extURI.host === host) { - gBrowser.removeTab(tabs[i]); + win.gBrowser.removeTab(tab); } } } diff --git a/platform/firefox/vapi-client.js b/platform/firefox/vapi-client.js index 871996dea..b957be407 100644 --- a/platform/firefox/vapi-client.js +++ b/platform/firefox/vapi-client.js @@ -84,6 +84,7 @@ vAPI.messaging = { listeners: {}, requestId: 1, connectorId: uniqueId(), + bgMessageName: 'µBlock:background', setup: function() { this.connector = function(msg) { @@ -124,7 +125,7 @@ vAPI.messaging = { vAPI.messaging.listeners[message.requestId] = callback; } - sendAsyncMessage('µBlock:background', message); + sendAsyncMessage(vAPI.messaging.bgMessageName, message); }, close: function() { delete vAPI.messaging.channels[this.portName]; diff --git a/src/js/asset-viewer.js b/src/js/asset-viewer.js index fb6c80633..ba55083a1 100644 --- a/src/js/asset-viewer.js +++ b/src/js/asset-viewer.js @@ -44,7 +44,14 @@ if ( !matches || matches.length !== 2 ) { return; } -messager.send({ what : 'getAssetContent', url: matches[1] }, onAssetContentReceived); +uDom.onLoad(function() { + messager.send({ + what: 'getAssetContent', + url: matches[1] + }, + onAssetContentReceived + ); +}); /******************************************************************************/ diff --git a/tools/make-firefox.sh b/tools/make-firefox.sh index aeb6c31b7..08f4ad22d 100644 --- a/tools/make-firefox.sh +++ b/tools/make-firefox.sh @@ -16,13 +16,13 @@ cp -R src/lib $DES/ cp -R src/_locales $DES/ cp src/*.html $DES/ cp src/img/icon_128.png $DES/icon.png -cp platform/vapi-appinfo.js $DES/js/ cp platform/firefox/vapi-*.js $DES/js/ cp platform/firefox/bootstrap.js $DES/ cp platform/firefox/frameScript.js $DES/ cp platform/firefox/chrome.manifest $DES/ cp platform/firefox/install.rdf $DES/ -python tools/xpi-convert-locale.py $DES/ +echo "*** uBlock_xpi: Generating locales" +python tools/make-locale-firefox.py $DES/ echo "*** uBlock_xpi: Package done." diff --git a/tools/xpi-convert-locale.py b/tools/make-locale-firefox.py similarity index 100% rename from tools/xpi-convert-locale.py rename to tools/make-locale-firefox.py