diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 76b25bf6c..9609980f6 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -140,6 +140,8 @@ vAPI.tabs.open = function(details) { }; if ( details.tabId ) { + details.tabId = parseInt(tabId, 10); + // update doesn't accept index, must use move chrome.tabs.update(details.tabId, _details, function(tab) { // if the tab doesn't exist @@ -203,7 +205,7 @@ vAPI.tabs.remove = function(tabId) { if ( vAPI.lastError() ) { } }; - chrome.tabs.remove(tabId, onTabRemoved); + chrome.tabs.remove(parseInt(tabId, 10), onTabRemoved); }; /******************************************************************************/ @@ -227,6 +229,7 @@ vAPI.tabs.injectScript = function(tabId, details, callback) { } }; if ( tabId ) { + tabid = parseInt(tabId, 10); chrome.tabs.executeScript(tabId, details, onScriptExecuted); } else { chrome.tabs.executeScript(details, onScriptExecuted); @@ -243,6 +246,7 @@ vAPI.tabs.injectScript = function(tabId, details, callback) { // anymore, so this ensures it does still exist. vAPI.setIcon = function(tabId, img, badge) { + tabId = parseInt(tabId, 10); var onIconReady = function() { if ( vAPI.lastError() ) { return; diff --git a/platform/firefox/frameModule.js b/platform/firefox/frameModule.js new file mode 100644 index 000000000..7465c2bd5 --- /dev/null +++ b/platform/firefox/frameModule.js @@ -0,0 +1,169 @@ +/* global Services, Components, XPCOMUtils */ +/* exported EXPORTED_SYMBOLS, isTabbed */ + +'use strict'; + +var EXPORTED_SYMBOLS = ['contentPolicy', 'docObserver']; + +Components.utils['import']('resource://gre/modules/Services.jsm'); +Components.utils['import']('resource://gre/modules/XPCOMUtils.jsm'); + +const Ci = Components.interfaces, appName = 'ublock'; + +let getMessager = function(win) { + try { + // e10s + return win + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIContentFrameMessageManager); + } catch (ex) { + return win + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIContentFrameMessageManager); + } +}; + +let contentPolicy = { + classDescription: 'ContentPolicy implementation', + classID: Components.ID('{e6d173c8-8dbf-4189-a6fd-189e8acffd27}'), + contractID: '@ublock/content-policy;1', + ACCEPT: Ci.nsIContentPolicy.ACCEPT, + REJECT: Ci.nsIContentPolicy.REJECT_REQUEST, + get componentRegistrar() { + return Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + }, + get categoryManager() { + return Components.classes['@mozilla.org/categorymanager;1'] + .getService(Ci.nsICategoryManager); + }, + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsIFactory, + Ci.nsIContentPolicy, + Ci.nsISupportsWeakReference + ]), + createInstance: function(outer, iid) { + if (outer) { + throw Components.results.NS_ERROR_NO_AGGREGATION; + } + + return this.QueryInterface(iid); + }, + register: function() { + this.componentRegistrar.registerFactory( + this.classID, + this.classDescription, + this.contractID, + this + ); + this.categoryManager.addCategoryEntry( + 'content-policy', + this.contractID, + this.contractID, + false, + true + ); + }, + unregister: function() { + this.componentRegistrar.unregisterFactory(this.classID, this); + this.categoryManager.deleteCategoryEntry( + 'content-policy', + this.contractID, + false + ); + }, + shouldLoad: function(type, location, origin, context) { + if (type === 6 || !context || !/^https?$/.test(location.scheme)) { + return this.ACCEPT; + } + + let win = (context.ownerDocument || context).defaultView; + + if (!win) { + return this.ACCEPT; + } + + let result = getMessager(win).sendSyncMessage('ublock:onBeforeRequest', { + url: location.spec, + type: type, + tabId: -1, + frameId: win === win.top ? 0 : 1, + parentFrameId: win === win.top ? -1 : 0 + })[0]; + + return result === true ? this.REJECT : this.ACCEPT; + }/*, + shouldProcess: function() { + return this.ACCEPT; + }*/ +}; + +let docObserver = { + contentBaseURI: 'chrome://ublock/content/js/', + initContext: function(win, sandbox) { + let messager = getMessager(win); + + if (sandbox) { + win = Components.utils.Sandbox([win], { + sandboxPrototype: win, + wantComponents: false, + wantXHRConstructor: false + }); + } + + win.sendAsyncMessage = messager.sendAsyncMessage; + win.addMessageListener = messager.ublock_addMessageListener; + win.removeMessageListener = messager.ublock_removeMessageListener; + + return win; + }, + register: function() { + Services.obs.addObserver(this, 'document-element-inserted', false); + }, + unregister: function() { + Services.obs.removeObserver(this, 'document-element-inserted'); + }, + observe: function(doc) { + let win = doc.defaultView; + + if (!win) { + return; + } + + if (!/^https?:$/.test(win.location.protocol)) { + if (win.location.protocol === 'chrome:' + && win.location.host === appName) { + this.initContext(win); + } + + return; + } + + let lss = Services.scriptloader.loadSubScript; + win = this.initContext(win, true); + + lss(this.contentBaseURI + 'vapi-client.js', win); + lss(this.contentBaseURI + 'contentscript-start.js', win); + + if (doc.readyState === 'interactive' || doc.readyState === 'complete') { + lss(this.contentBaseURI + 'contentscript-end.js', win); + } + else { + let docReady = function(e) { + this.removeEventListener(e.type, docReady, true); + lss(docObserver.contentBaseURI + 'contentscript-end.js', win); + }; + + doc.addEventListener('DOMContentLoaded', docReady, true); + } + } +}; + +contentPolicy.register(); +docObserver.register(); diff --git a/platform/firefox/frameScript.js b/platform/firefox/frameScript.js index 4d4225b34..21586a991 100644 --- a/platform/firefox/frameScript.js +++ b/platform/firefox/frameScript.js @@ -1,22 +1,20 @@ -/* globals Services, sendAsyncMessage, addMessageListener, removeMessageListener, content */ - -(function() { +/* globals Services, sendAsyncMessage, addMessageListener, removeMessageListener */ 'use strict'; let appName = 'ublock'; -let contentBaseURI = 'chrome://' + appName + '/content/js/'; let listeners = {}; +let frameModule = Components.utils['import']('chrome://' + appName + '/content/frameModule.js', {}); -let _addMessageListener = function(id, fn) { - _removeMessageListener(id); +this.ublock_addMessageListener = function(id, fn) { + ublock_removeMessageListener(id); listeners[id] = function(msg) { fn(msg.data); }; addMessageListener(id, listeners[id]); }; -let _removeMessageListener = function(id) { +this.ublock_removeMessageListener = function(id) { if (listeners[id]) { removeMessageListener(id, listeners[id]); } @@ -29,72 +27,3 @@ addMessageListener(appName + ':broadcast', function(msg) { listeners[id](msg); } }); - -let initContext = function(win, sandbox) { - if (sandbox) { - win = Components.utils.Sandbox([win], { - sandboxPrototype: win, - wantComponents: false, - wantXHRConstructor: false - }); - } - - win.sendAsyncMessage = sendAsyncMessage; - win.addMessageListener = _addMessageListener; - win.removeMessageListener = _removeMessageListener; - - return win; -}; - -let observer = { - observe: function(win) { - if (!win || win.top !== content) { - return; - } - - if (!(win.document instanceof win.HTMLDocument - && (/^https?:$/.test(win.location.protocol)))) { - return; - } - - let lss = Services.scriptloader.loadSubScript; - win = initContext(win, true); - - lss(contentBaseURI + 'vapi-client.js', win); - lss(contentBaseURI + 'contentscript-start.js', win); - - let readyState = win.document.readyState; - - if (readyState === "interactive" || readyState === "complete") { - lss(contentBaseURI + 'contentscript-end.js', win); - } - else { - let docReady = function(e) { - this.removeEventListener(e.type, docReady, true); - lss(contentBaseURI + 'contentscript-end.js', win); - }; - - win.document.addEventListener('DOMContentLoaded', docReady, true); - } - } -}; - -Services.obs.addObserver(observer, 'content-document-global-created', false); - -let DOMReady = function(e) { - let win = e.target.defaultView; - - // 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/vapi-background.js b/platform/firefox/vapi-background.js index c6eca9a5a..753124f7c 100644 --- a/platform/firefox/vapi-background.js +++ b/platform/firefox/vapi-background.js @@ -19,7 +19,7 @@ Home: https://github.com/gorhill/uBlock */ -/* global Services, XPCOMUtils */ +/* global Services */ // For background page @@ -34,7 +34,6 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu['import']('resource://gre/modules/Services.jsm'); -Cu['import']('resource://gre/modules/XPCOMUtils.jsm'); /******************************************************************************/ @@ -236,7 +235,7 @@ var windowWatcher = { onTabClose: function(e) { vAPI.tabs.onClosed(vAPI.tabs.getTabId(e.target)); }, - onTabSelect: function(e) { + onTabSelect: function() { // vAPI.setIcon(); }, onLoad: function(e) { @@ -338,7 +337,7 @@ vAPI.tabs.getTabId = function(target) { var i = gBrowser.browsers.indexOf(target); if (i !== -1) { - i = this.getTabId(gBrowser.tabs[i]); + i = gBrowser.tabs[i].linkedPanel.slice(6); } return i; @@ -638,133 +637,69 @@ vAPI.messaging.unload = function() { /******************************************************************************/ -vAPI.lastError = function() { - return null; -}; - -/******************************************************************************/ - -var conentPolicy = { - classDescription: vAPI.app.name + ' ContentPolicy', - classID: Components.ID('{e6d173c8-8dbf-4189-a6fd-189e8acffd27}'), - contractID: '@' + vAPI.app.cleanName + '/content-policy;1', - ACCEPT: Ci.nsIContentPolicy.ACCEPT, - REJECT: Ci.nsIContentPolicy.REJECT_REQUEST, - types: { - 7: 'sub_frame', - 4: 'stylesheet', - 2: 'script', - 3: 'image', - 5: 'object', - 11: 'xmlhttprequest' - }, - get registrar() { - return Components.manager.QueryInterface(Ci.nsIComponentRegistrar); - }, - get catManager() { - return Cc['@mozilla.org/categorymanager;1'] - .getService(Ci.nsICategoryManager); - }, - QueryInterface: XPCOMUtils.generateQI([ - Ci.nsIFactory, - Ci.nsIContentPolicy, - Ci.nsISupportsWeakReference - ]), - createInstance: function(outer, iid) { - if (outer) { - throw Components.results.NS_ERROR_NO_AGGREGATION; - } - - return this.QueryInterface(iid); - }, - shouldLoad: function(type, location, origin, context) { - if (type === 6 || !context || !/^https?$/.test(location.scheme)) { - return this.ACCEPT; - } - - var win = (context.ownerDocument || context).defaultView; - - if (!win) { - return this.ACCEPT; - } - - var block = vAPI.net.onBeforeRequest; - - type = this.types[type] || 'other'; - - if (block.types.indexOf(type) === -1) { - return this.ACCEPT; - } - - var browser = win.top.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShell) - .chromeEventHandler; - - if (!browser) { - return this.ACCEPT; - } - - block = block.callback({ - url: location.spec, - type: type, - tabId: vAPI.tabs.getTabId(browser), - frameId: win === win.top ? 0 : 1, - parentFrameId: win === win.top ? -1 : 0 - }); - - if (block && typeof block === 'object') { - if (block.cancel === true) { - return this.REJECT; - } - else if (block.redirectURL) { - location.spec = block.redirectURL; - return this.REJECT; - } - } - - return this.ACCEPT; - },/* - shouldProcess: function() { - return this.ACCEPT; - }*/ -}; - -/******************************************************************************/ - vAPI.net = {}; /******************************************************************************/ vAPI.net.registerListeners = function() { - conentPolicy.registrar.registerFactory( - conentPolicy.classID, - conentPolicy.classDescription, - conentPolicy.contractID, - conentPolicy - ); - conentPolicy.catManager.addCategoryEntry( - 'content-policy', - conentPolicy.contractID, - conentPolicy.contractID, - false, - true + var types = { + 2: 'script', + 3: 'image', + 4: 'stylesheet', + 5: 'object', + 7: 'sub_frame', + 11: 'xmlhttprequest' + }; + + var onBeforeRequest = this.onBeforeRequest; + + this.onBeforeRequest = function(e) { + var details = e.data; + + details.type = types[details.type] || 'other'; + details.tabId = vAPI.tabs.getTabId(e.target); + + if (onBeforeRequest.types.indexOf(details.type) === -1) { + return false; + } + + var block = onBeforeRequest.callback(details); + + if (block && typeof block === 'object') { + if (block.cancel === true) { + return true; + } + else if (block.redirectURL) { + return block.redirectURL; + } + } + + return false; + }; + + vAPI.messaging.gmm.addMessageListener( + vAPI.app.cleanName + ':onBeforeRequest', + this.onBeforeRequest ); }; /******************************************************************************/ vAPI.net.unregisterListeners = function() { - conentPolicy.registrar.unregisterFactory(conentPolicy.classID, conentPolicy); - conentPolicy.catManager.deleteCategoryEntry( - 'content-policy', - conentPolicy.contractID, - false + vAPI.messaging.gmm.removeMessageListener( + vAPI.app.cleanName + ':onBeforeRequest', + this.onBeforeRequest ); }; /******************************************************************************/ +vAPI.lastError = function() { + return null; +}; + +/******************************************************************************/ + // clean up when the extension is disabled window.addEventListener('unload', function() { @@ -773,14 +708,21 @@ window.addEventListener('unload', function() { vAPI.messaging.unload(); vAPI.net.unregisterListeners(); + var URI = vAPI.messaging.frameScript.replace('Script.', 'Module.'); + var frameModule = {}; + Cu['import'](URI, frameModule); + frameModule.contentPolicy.unregister(); + frameModule.docObserver.unregister(); + Cu.unload(URI); + // close extension tabs - var extURI, win, tab, host = vAPI.app.cleanName; + var win, tab, host = vAPI.app.cleanName; for (win of vAPI.tabs.getWindows()) { for (tab of win.gBrowser.tabs) { - extURI = tab.linkedBrowser.currentURI; + URI = tab.linkedBrowser.currentURI; - if (extURI.scheme === 'chrome' && extURI.host === host) { + if (URI.scheme === 'chrome' && URI.host === host) { win.gBrowser.removeTab(tab); } } diff --git a/platform/safari/vapi-background.js b/platform/safari/vapi-background.js index dcdc0e4b8..8c4e61f62 100644 --- a/platform/safari/vapi-background.js +++ b/platform/safari/vapi-background.js @@ -179,7 +179,7 @@ vAPI.storage = { vAPI.tabs = { stack: {}, - stackID: 1 + stackId: 1 }; /******************************************************************************/ @@ -374,21 +374,17 @@ vAPI.tabs.injectScript = function(tabId, details, callback) { j = wins[i].tabs.length; while (j--) { - tabs.push(wins[i].tabs[j]); + vAPI.tabs.stack[vAPI.tabs.stackId++] = 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; + vAPI.tabs.stack[vAPI.tabs.stackId++] = e.target; } }, true); @@ -402,7 +398,7 @@ safari.application.addEventListener('close', function(e) { var tabId = vAPI.tabs.getTabId(e.target); - if (tabId > -1) { + if (tabId !== -1) { // to not add another listener, put this here // instead of vAPI.tabs.registerListeners if (typeof vAPI.tabs.onClosed === 'function') { diff --git a/tools/make-firefox.sh b/tools/make-firefox.sh index 08f4ad22d..bdc5a4899 100644 --- a/tools/make-firefox.sh +++ b/tools/make-firefox.sh @@ -19,6 +19,7 @@ cp src/img/icon_128.png $DES/icon.png cp platform/firefox/vapi-*.js $DES/js/ cp platform/firefox/bootstrap.js $DES/ cp platform/firefox/frameScript.js $DES/ +cp platform/firefox/frameModule.js $DES/ cp platform/firefox/chrome.manifest $DES/ cp platform/firefox/install.rdf $DES/