From 8c0a0024ebe2a355aa411ab557663f5c0031dcf6 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Wed, 19 Jun 2019 18:04:54 +0200 Subject: Reformat style (huge and mostly boring commit) --- js/vapi-background.js | 6321 +++++++++++++++++++++++++------------------------ 1 file changed, 3176 insertions(+), 3145 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 957a3b8..6888317 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -30,3502 +30,3533 @@ /******************************************************************************/ -(function() { +(function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); -/******************************************************************************/ - -// Useful links -// -// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface -// https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Services.jsm - -/******************************************************************************/ - -const {classes: Cc, interfaces: Ci, utils: Cu} = Components; -const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); - -/******************************************************************************/ - -var vAPI = self.vAPI = self.vAPI || {}; -vAPI.firefox = true; + let vAPI = self.vAPI = self.vAPI || {}; + + vAPI.firefox = true; vAPI.modernFirefox = Services.appinfo.ID === '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}' && Services.vc.compare(Services.appinfo.version, '44') > 0; -/******************************************************************************/ - -var deferUntil = function(testFn, mainFn, details) { - if ( typeof details !== 'object' ) { - details = {}; - } + let deferUntil = function (testFn, mainFn, details) { + let dtls = (typeof details !== 'object') ? {} : details; + let now = 0; + let next = dtls.next || 200; + let until = dtsl.until || 2000; + + let check = function () { + if (testFn() === true || now >= until) { + mainFn(); + return; + } + now += next; + vAPI.setTimeout(check, next); + }; + + if ('sync' in dtls && dtls.sync === true) { + check(); + } else { + vAPI.setTimeout(check, 1); + } + }; - var now = 0; - var next = details.next || 200; - var until = details.until || 2000; + vAPI.app = { + name: 'eMatrix', + version: location.hash.slice(1), + + start: function () { + return; + }, + stop: function () { + return; + }, + restart: function () { + Cc['@mozilla.org/childprocessmessagemanager;1'] + .getService(Ci.nsIMessageSender) + .sendAsyncMessage(location.host + '-restart'); + }, + }; + + + // List of things that needs to be destroyed when disabling the extension + // Only functions should be added to it + + let cleanupTasks = []; + + // This must be updated manually, every time a new task is added/removed + // eMatrix: do we? + let expectedNumberOfCleanups = 7; + + window.addEventListener('unload', function () { + // if (typeof vAPI.app.onShutdown === 'function') { + // vAPI.app.onShutdown(); + // } + + // IMPORTANT: cleanup tasks must be executed using LIFO order. + for (let i=cleanupTasks.length-1; i>=0; --i) { + try { + cleanupTasks[i](); + } catch (e) { + // Just in case a clean up task ends up throwing for + // no reason + console.error(e); + } + } - var check = function() { - if ( testFn() === true || now >= until ) { - mainFn(); - return; - } - now += next; - vAPI.setTimeout(check, next); - }; + // eMatrix: temporarily disabled + // if (cleanupTasks.length < expectedNumberOfCleanups) { + // console.error + // ('eMatrix> Cleanup tasks performed: %s (out of %s)', + // cleanupTasks.length, + // expectedNumberOfCleanups); + // } + + // frameModule needs to be cleared too + let frameModuleURL = vAPI.getURL('frameModule.js'); + let frameModule = {}; + + Cu.import(frameModuleURL, frameModule); + frameModule.contentObserver.unregister(); + Cu.unload(frameModuleURL); + }); - if ( 'sync' in details && details.sync === true ) { - check(); - } else { - vAPI.setTimeout(check, 1); - } -}; + vAPI.browserSettings = { + // For now, only booleans. + originalValues: {}, -/******************************************************************************/ + rememberOriginalValue: function (path, setting) { + let key = path + '.' + setting; + if (this.originalValues.hasOwnProperty(key)) { + return; + } + + let hasUserValue; + let branch = Services.prefs.getBranch(path + '.'); + + try { + hasUserValue = branch.prefHasUserValue(setting); + } catch (ex) { + // Ignore + } + + if (hasUserValue !== undefined) { + this.originalValues[key] = hasUserValue + ? this.getValue(path, setting) + : undefined; + } + }, + clear: function (path, setting) { + let key = path + '.' + setting; + + // Value was not overriden -- nothing to restore + if (this.originalValues.hasOwnProperty(key) === false) { + return; + } + + let value = this.originalValues[key]; + + // https://github.com/gorhill/uBlock/issues/292#issuecomment-109621979 + // Forget the value immediately, it may change outside of + // uBlock control. + delete this.originalValues[key]; + + // Original value was a default one + if (value === undefined) { + try { + Services.prefs.getBranch(path + '.').clearUserPref(setting); + } catch (ex) { + // Ignore + } + return; + } + + // Reset to original value + this.setValue(path, setting, value); + }, + getValue: function (path, setting) { + let branch = Services.prefs.getBranch(path + '.'); + + try { + switch (branch.getPrefType(setting)) { + case branch.PREF_INT: + return branch.getIntPref(setting); + case branch.PREF_BOOL: + return branch.getBoolPref(setting); + default: + // not supported + return; + } + } catch (e) { + // Ignore + } + }, + setValue: function (path, setting, value) { + let branch = Services.prefs.getBranch(path + '.'); + + try { + switch (typeof value) { + case 'number': + return branch.setIntPref(setting, value); + case 'boolean': + return branch.setBoolPref(setting, value); + default: + // not supported + return; + } + } catch (e) { + // Ignore + } + }, + setSetting: function (setting, value) { + switch (setting) { + case 'prefetching': + this.rememberOriginalValue('network', 'prefetch-next'); + // https://bugzilla.mozilla.org/show_bug.cgi?id=814169 + // Sigh. + // eMatrix: doesn't seem the case for Pale + // Moon/Basilisk, but let's keep this anyway + this.rememberOriginalValue('network.http', 'speculative-parallel-limit'); + + // https://github.com/gorhill/uBlock/issues/292 + // "true" means "do not disable", i.e. leave entry alone + if (value) { + this.clear('network', 'prefetch-next'); + this.clear('network.http', 'speculative-parallel-limit'); + } else { + this.setValue('network', 'prefetch-next', false); + this.setValue('network.http', + 'speculative-parallel-limit', 0); + } + break; + case 'hyperlinkAuditing': + this.rememberOriginalValue('browser', 'send_pings'); + this.rememberOriginalValue('beacon', 'enabled'); + + // https://github.com/gorhill/uBlock/issues/292 + // "true" means "do not disable", i.e. leave entry alone + if (value) { + this.clear('browser', 'send_pings'); + this.clear('beacon', 'enabled'); + } else { + this.setValue('browser', 'send_pings', false); + this.setValue('beacon', 'enabled', false); + } + break; + case 'webrtcIPAddress': + let prefName; + let prefVal; + + // https://github.com/gorhill/uBlock/issues/894 + // Do not disable completely WebRTC if it can be avoided. FF42+ + // has a `media.peerconnection.ice.default_address_only` pref which + // purpose is to prevent local IP address leakage. + if (this.getValue('media.peerconnection', + 'ice.default_address_only') !== undefined) { + prefName = 'ice.default_address_only'; + prefVal = true; + } else { + prefName = 'enabled'; + prefVal = false; + } -vAPI.app = { - name: 'eMatrix', - version: location.hash.slice(1) -}; + this.rememberOriginalValue('media.peerconnection', prefName); + if (value) { + this.clear('media.peerconnection', prefName); + } else { + this.setValue('media.peerconnection', prefName, prefVal); + } + break; + default: + break; + } + }, + set: function (details) { + for (let setting in details) { + if (details.hasOwnProperty(setting) === false) { + continue; + } + this.setSetting(setting, !!details[setting]); + } + }, + restoreAll: function () { + let pos; + for (let key in this.originalValues) { + if (this.originalValues.hasOwnProperty(key) === false) { + continue; + } + + pos = key.lastIndexOf('.'); + this.clear(key.slice(0, pos), key.slice(pos + 1)); + } + }, + }; -/******************************************************************************/ + cleanupTasks.push(vAPI.browserSettings.restoreAll.bind(vAPI.browserSettings)); -vAPI.app.start = function() { -}; + // API matches that of chrome.storage.local: + // https://developer.chrome.com/extensions/storage + vAPI.storage = (function () { + let db = null; + let vacuumTimer = null; -/******************************************************************************/ + let close = function () { + if (vacuumTimer !== null) { + clearTimeout(vacuumTimer); + vacuumTimer = null; + } + + if (db === null) { + return; + } + + db.asyncClose(); + db = null; + }; -vAPI.app.stop = function() { -}; + let open = function () { + if (db !== null) { + return db; + } + + // Create path + let path = Services.dirsvc.get('ProfD', Ci.nsIFile); + path.append('ematrix-data'); + if (!path.exists()) { + path.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0774', 8)); + } + if (!path.isDirectory()) { + throw Error('Should be a directory...'); + } -/******************************************************************************/ + let path2 = Services.dirsvc.get('ProfD', Ci.nsIFile); + path2.append('extension-data'); + path2.append(location.host + '.sqlite'); + if (path2.exists()) { + path2.moveTo(path, location.host+'.sqlite'); + } -vAPI.app.restart = function() { - // Listening in bootstrap.js - Cc['@mozilla.org/childprocessmessagemanager;1'] - .getService(Ci.nsIMessageSender) - .sendAsyncMessage(location.host + '-restart'); -}; + path.append(location.host + '.sqlite'); -/******************************************************************************/ + // Open database + try { + db = Services.storage.openDatabase(path); + if (db.connectionReady === false) { + db.asyncClose(); + db = null; + } + } catch (ex) { + // Ignore + } -// List of things that needs to be destroyed when disabling the extension -// Only functions should be added to it + if (db === null) { + return null; + } -var cleanupTasks = []; + // Database was opened, register cleanup task + cleanupTasks.push(close); -// This must be updated manually, every time a new task is added/removed + // Setup database + db.createAsyncStatement('CREATE TABLE IF NOT EXISTS ' + +'"settings" ("name" ' + +'TEXT PRIMARY KEY NOT NULL, ' + +'"value" TEXT);') + .executeAsync(); -// Fixed by github.com/AlexVallat: -// https://github.com/AlexVallat/uBlock/commit/7b781248f00cbe3d61b1cc367c440db80fa06049 -// 7 instances of cleanupTasks.push, but one is unique to fennec, and one to desktop. -var expectedNumberOfCleanups = 7; + if (vacuum !== null) { + vacuumTimer = vAPI.setTimeout(vacuum, 60000); + } -window.addEventListener('unload', function() { - if ( typeof vAPI.app.onShutdown === 'function' ) { - vAPI.app.onShutdown(); - } + return db; + }; - // IMPORTANT: cleanup tasks must be executed using LIFO order. - var i = cleanupTasks.length; - while ( i-- ) { - cleanupTasks[i](); - } + // Vacuum only once, and only while idle + let vacuum = function () { + vacuumTimer = null; + if (db === null) { + return; + } + let idleSvc = + Cc['@mozilla.org/widget/idleservice;1'] + .getService(Ci.nsIIdleService); + + if (idleSvc.idleTime < 60000) { + vacuumTimer = vAPI.setTimeout(vacuum, 60000); + return; + } + + db.createAsyncStatement('VACUUM').executeAsync(); + vacuum = null; + }; + + // Execute a query + let runStatement = function (stmt, callback) { + let result = {}; + + stmt.executeAsync({ + handleResult: function (rows) { + if (!rows || typeof callback !== 'function') { + return; + } - if ( cleanupTasks.length < expectedNumberOfCleanups ) { - console.error( - 'eMatrix> Cleanup tasks performed: %s (out of %s)', - cleanupTasks.length, - expectedNumberOfCleanups - ); - } + let row; + while ((row = rows.getNextRow())) { + // we assume that there will be two columns, since we're + // using it only for preferences + // eMatrix: the above comment is obsolete + // (it's not used just for preferences + // anymore), but we still expect two columns. + let res = row.getResultByIndex(0); + result[res] = row.getResultByIndex(1); + } + }, + handleCompletion: function (reason) { + if (typeof callback === 'function' && reason === 0) { + callback(result); + } + }, + handleError: function (error) { + console.error('SQLite error ', error.result, error.message); + + // Caller expects an answer regardless of failure. + if (typeof callback === 'function' ) { + callback(null); + } + }, + }); + }; + + let bindNames = function (stmt, names) { + if (Array.isArray(names) === false || names.length === 0) { + return; + } + + let params = stmt.newBindingParamsArray(); + + for (let i=names.length-1; i>=0; --i) { + let bp = params.newBindingParams(); + bp.bindByName('name', names[i]); + params.addParams(bp); + } + + stmt.bindParameters(params); + }; + + let clear = function (callback) { + if (open() === null) { + if (typeof callback === 'function') { + callback(); + } + return; + } + + runStatement(db.createAsyncStatement('DELETE FROM "settings";'), + callback); + }; - // frameModule needs to be cleared too - var frameModuleURL = vAPI.getURL('frameModule.js'); - var frameModule = {}; - Cu.import(frameModuleURL, frameModule); - frameModule.contentObserver.unregister(); - Cu.unload(frameModuleURL); -}); + let getBytesInUse = function (keys, callback) { + if (typeof callback !== 'function') { + return; + } -/******************************************************************************/ + if (open() === null) { + callback(0); + return; + } -// For now, only booleans. + let stmt; + if (Array.isArray(keys)) { + stmt = db.createAsyncStatement('SELECT "size" AS "size", ' + +'SUM(LENGTH("value")) ' + +'FROM "settings" WHERE ' + +'"name" = :name'); + bindNames(keys); + } else { + stmt = db.createAsyncStatement('SELECT "size" AS "size", ' + +'SUM(LENGTH("value")) ' + +'FROM "settings"'); + } -vAPI.browserSettings = { - originalValues: {}, + runStatement(stmt, function (result) { + callback(result.size); + }); + }; - rememberOriginalValue: function(path, setting) { - var key = path + '.' + setting; - if ( this.originalValues.hasOwnProperty(key) ) { - return; - } - var hasUserValue; - var branch = Services.prefs.getBranch(path + '.'); - try { - hasUserValue = branch.prefHasUserValue(setting); - } catch (ex) { - } - if ( hasUserValue !== undefined ) { - this.originalValues[key] = hasUserValue ? this.getValue(path, setting) : undefined; - } - }, - - clear: function(path, setting) { - var key = path + '.' + setting; - - // Value was not overriden -- nothing to restore - if ( this.originalValues.hasOwnProperty(key) === false ) { - return; - } + let read = function (details, callback) { + if (typeof callback !== 'function') { + return; + } - var value = this.originalValues[key]; - // https://github.com/gorhill/uBlock/issues/292#issuecomment-109621979 - // Forget the value immediately, it may change outside of - // uBlock control. - delete this.originalValues[key]; + let prepareResult = function (result) { + for (let key in result) { + if (result.hasOwnProperty(key) === false) { + continue; + } + + result[key] = JSON.parse(result[key]); + } + + if (typeof details === 'object' && details !== null) { + for (let key in details) { + if (result.hasOwnProperty(key) === false) { + result[key] = details[key]; + } + } + } + + callback(result); + }; - // Original value was a default one - if ( value === undefined ) { - try { - Services.prefs.getBranch(path + '.').clearUserPref(setting); - } catch (ex) { + if (open() === null) { + prepareResult({}); + return; } - return; - } - - // Reset to original value - this.setValue(path, setting, value); - }, - - getValue: function(path, setting) { - var branch = Services.prefs.getBranch(path + '.'); - var getMethod; - - // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPrefBranch#getPrefType%28%29 - switch ( branch.getPrefType(setting) ) { - case 64: // PREF_INT - getMethod = 'getIntPref'; - break; - case 128: // PREF_BOOL - getMethod = 'getBoolPref'; - break; - default: // not supported - return; - } - - try { - return branch[getMethod](setting); - } catch (ex) { - } - }, - - setValue: function(path, setting, value) { - var setMethod; - switch ( typeof value ) { - case 'number': - setMethod = 'setIntPref'; - break; - case 'boolean': - setMethod = 'setBoolPref'; - break; - default: // not supported - return; - } - - try { - Services.prefs.getBranch(path + '.')[setMethod](setting, value); - } catch (ex) { - } - }, - - setSetting: function(setting, value) { - var prefName, prefVal; - switch ( setting ) { - case 'prefetching': - this.rememberOriginalValue('network', 'prefetch-next'); - // http://betanews.com/2015/08/15/firefox-stealthily-loads-webpages-when-you-hover-over-links-heres-how-to-stop-it/ - // https://bugzilla.mozilla.org/show_bug.cgi?id=814169 - // Sigh. - this.rememberOriginalValue('network.http', 'speculative-parallel-limit'); - // https://github.com/gorhill/uBlock/issues/292 - // "true" means "do not disable", i.e. leave entry alone - if ( value ) { - this.clear('network', 'prefetch-next'); - this.clear('network.http', 'speculative-parallel-limit'); - } else { - this.setValue('network', 'prefetch-next', false); - this.setValue('network.http', 'speculative-parallel-limit', 0); - } - break; - - case 'hyperlinkAuditing': - this.rememberOriginalValue('browser', 'send_pings'); - this.rememberOriginalValue('beacon', 'enabled'); - // https://github.com/gorhill/uBlock/issues/292 - // "true" means "do not disable", i.e. leave entry alone - if ( value ) { - this.clear('browser', 'send_pings'); - this.clear('beacon', 'enabled'); - } else { - this.setValue('browser', 'send_pings', false); - this.setValue('beacon', 'enabled', false); - } - break; - - // https://github.com/gorhill/uBlock/issues/894 - // Do not disable completely WebRTC if it can be avoided. FF42+ - // has a `media.peerconnection.ice.default_address_only` pref which - // purpose is to prevent local IP address leakage. - case 'webrtcIPAddress': - if ( this.getValue('media.peerconnection', 'ice.default_address_only') !== undefined ) { - prefName = 'ice.default_address_only'; - prefVal = true; - } else { - prefName = 'enabled'; - prefVal = false; + + let names = []; + if (details !== null) { + if (Array.isArray(details)) { + names = details; + } else if (typeof details === 'object') { + names = Object.keys(details); + } else { + names = [details.toString()]; + } } - this.rememberOriginalValue('media.peerconnection', prefName); - if ( value ) { - this.clear('media.peerconnection', prefName); + let stmt; + if (names.length === 0) { + stmt = db.createAsyncStatement('SELECT * FROM "settings"'); } else { - this.setValue('media.peerconnection', prefName, prefVal); - } - break; + stmt = db.createAsyncStatement('SELECT * FROM "settings" ' + +'WHERE "name" = :name'); + bindNames(stmt, names); + } + + runStatement(stmt, prepareResult); + }; + + let remove = function (keys, callback) { + if (open() === null) { + if (typeof callback === 'function') { + callback(); + } + return; + } + + var stmt = db.createAsyncStatement('DELETE FROM "settings" ' + +'WHERE "name" = :name'); + bindNames(stmt, typeof keys === 'string' ? [keys] : keys); + runStatement(stmt, callback); + }; + + let write = function (details, callback) { + if (open() === null) { + if (typeof callback === 'function') { + callback(); + } + return; + } + + let stmt = db.createAsyncStatement('INSERT OR REPLACE INTO ' + +'"settings" ("name", "value") ' + +'VALUES(:name, :value)'); + let params = stmt.newBindingParamsArray(); + + for (let key in details) { + if (details.hasOwnProperty(key) === false) { + continue; + } + + let bp = params.newBindingParams(); + bp.bindByName('name', key); + bp.bindByName('value', JSON.stringify(details[key])); + params.addParams(bp); + } + + if (params.length === 0) { + return; + } + + stmt.bindParameters(params); + runStatement(stmt, callback); + }; + + // Export API + var api = { + QUOTA_BYTES: 100 * 1024 * 1024, + clear: clear, + get: read, + getBytesInUse: getBytesInUse, + remove: remove, + set: write + }; + + return api; + })(); - default: - break; - } - }, + vAPI.cacheStorage = vAPI.storage; + + // This must be executed/setup early. + let winWatcher = (function () { + let windowToIdMap = new Map(); + let windowIdGenerator = 1; + let api = { + onOpenWindow: null, + onCloseWindow: null + }; + + // https://github.com/gorhill/uMatrix/issues/586 This is + // necessary hack because on SeaMonkey 2.40, for unknown + // reasons private windows do not have the attribute + // `windowtype` set to `navigator:browser`. As a fallback, the + // code here will also test whether the id attribute is + // `main-window`. + api.toBrowserWindow = function (win) { + let docElement = win && win.document + && win.document.documentElement; + + if (!docElement) { + return null; + } + if (vAPI.thunderbird) { + return docElement.getAttribute('windowtype') === 'mail:3pane' + ? win + : null; + } + + return docElement.getAttribute('windowtype') === 'navigator:browser' + || docElement.getAttribute('id') === 'main-window' + ? win + : null; + }; + + api.getWindows = function () { + return windowToIdMap.keys(); + }; + + api.idFromWindow = function (win) { + return windowToIdMap.get(win) || 0; + }; + + api.getCurrentWindow = function () { + return this.toBrowserWindow(Services.wm.getMostRecentWindow(null)); + }; + + let addWindow = function (win) { + if (!win || windowToIdMap.has(win)) { + return; + } + + windowToIdMap.set(win, windowIdGenerator++); + + if (typeof api.onOpenWindow === 'function') { + api.onOpenWindow(win); + } + }; + + let removeWindow = function (win) { + if (!win || windowToIdMap.delete(win) !== true) { + return; + } + + if (typeof api.onCloseWindow === 'function') { + api.onCloseWindow(win); + } + }; + + // https://github.com/gorhill/uMatrix/issues/357 + // Use nsIWindowMediator for being notified of opened/closed windows. + let listeners = { + onOpenWindow: function (aWindow) { + let win; + try { + win = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + } catch (e) { + // Ignore + } + + addWindow(win); + }, + onCloseWindow: function (aWindow) { + let win; + try { + win = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + } catch (e) { + // Ignore + } + + removeWindow(win); + }, + observe: function (aSubject, topic) { + let win; + try { + win = aSubject.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + } catch (e) { + // Ignore + } + + if (!win) { + return; + } - set: function(details) { - for ( var setting in details ) { - if ( details.hasOwnProperty(setting) === false ) { - continue; + switch (topic) { + case 'domwindowopened': + addWindow(win); + break; + case 'domwindowclosed': + removeWindow(win); + break; + default: + console.error('unknown observer topic'); + break; + } } - this.setSetting(setting, !!details[setting]); - } - }, + }; + + (function() { + let winumerator; - restoreAll: function() { - var pos; - for ( var key in this.originalValues ) { - if ( this.originalValues.hasOwnProperty(key) === false ) { - continue; + winumerator = Services.wm.getEnumerator(null); + while (winumerator.hasMoreElements()) { + let win = winumerator.getNext(); + + if (!win.closed) { + windowToIdMap.set(win, windowIdGenerator++); + } } - pos = key.lastIndexOf('.'); - this.clear(key.slice(0, pos), key.slice(pos + 1)); - } - } -}; -cleanupTasks.push(vAPI.browserSettings.restoreAll.bind(vAPI.browserSettings)); + winumerator = Services.ww.getWindowEnumerator(); + while (winumerator.hasMoreElements()) { + let win = winumerator.getNext() + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + + if (!win.closed) { + windowToIdMap.set(win, windowIdGenerator++); + } + } -/******************************************************************************/ + Services.wm.addListener(listeners); + Services.ww.registerNotification(listeners); + })(); -// API matches that of chrome.storage.local: -// https://developer.chrome.com/extensions/storage + cleanupTasks.push(function() { + Services.wm.removeListener(listeners); + Services.ww.unregisterNotification(listeners); + windowToIdMap.clear(); + }); -vAPI.storage = (function() { - var db = null; - var vacuumTimer = null; + return api; + })(); - var close = function() { - if ( vacuumTimer !== null ) { - clearTimeout(vacuumTimer); - vacuumTimer = null; - } - if ( db === null ) { - return; - } - db.asyncClose(); - db = null; + let getTabBrowser = function (win) { + return win && win.gBrowser || null; }; - var open = function() { - if ( db !== null ) { - return db; - } - - // Create path - let path = Services.dirsvc.get('ProfD', Ci.nsIFile); - path.append('ematrix-data'); - if ( !path.exists() ) { - path.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0774', 8)); - } - if ( !path.isDirectory() ) { - throw Error('Should be a directory...'); - } + let getOwnerWindow = function (target) { + if (target.ownerDocument) { + return target.ownerDocument.defaultView; + } - let path2 = Services.dirsvc.get('ProfD', Ci.nsIFile); - path2.append('extension-data'); - path2.append(location.host + '.sqlite'); - if (path2.exists()) { - path2.moveTo(path, location.host+'.sqlite'); + return null; + }; + + vAPI.isBehindTheSceneTabId = function (tabId) { + return tabId.toString() === '-1'; + }; + + vAPI.noTabId = '-1'; + + // Tabs and related functions + vAPI.tabs = {}; + + vAPI.tabs.registerListeners = function() { + tabWatcher.start(); + }; + + vAPI.tabs.get = function (tabId, callback) { + // eMatrix: the following might be obsoleted (though probably + // still relevant at least for Pale Moon.) + // + // Firefox: + // + // browser -> ownerDocument -> defaultView -> gBrowser -> browsers --+ + // ^ | + // | | + // +--------------------------------------------------------------+ + // + // browser (browser) + // contentTitle + // currentURI + // ownerDocument (XULDocument) + // defaultView (ChromeWindow) + // gBrowser (tabbrowser OR browser) + // browsers (browser) + // selectedBrowser + // selectedTab + // tabs (tab.tabbrowser-tab) + // + // Fennec: (what I figured so far) + // + // tab -> browser windows -> window -> BrowserApp -> tabs --+ + // ^ window | + // | | + // +-----------------------------------------------------------+ + // + // tab + // browser + // [manual search to go back to tab from list of windows] + let browser; + + if (tabId === null) { + browser = tabWatcher.currentBrowser(); + tabId = tabWatcher.tabIdFromTarget(browser); + } else { + browser = tabWatcher.browserFromTabId(tabId); } - path.append(location.host + '.sqlite'); - - // Open database - try { - db = Services.storage.openDatabase(path); - if ( db.connectionReady === false ) { - db.asyncClose(); - db = null; - } - } catch (ex) { - } - - if ( db === null ) { - return null; - } - - // Database was opened, register cleanup task - cleanupTasks.push(close); + // For internal use + if (typeof callback !== 'function') { + return browser; + } - // Setup database - db.createAsyncStatement('CREATE TABLE IF NOT EXISTS "settings" ("name" TEXT PRIMARY KEY NOT NULL, "value" TEXT);') - .executeAsync(); + if (!browser || !browser.currentURI) { + callback(); + return; + } - if ( vacuum !== null ) { - vacuumTimer = vAPI.setTimeout(vacuum, 60000); - } + let win = getOwnerWindow(browser); + let tabBrowser = getTabBrowser(win); - return db; + callback({ + id: tabId, + windowId: winWatcher.idFromWindow(win), + active: tabBrowser !== null + && browser === tabBrowser.selectedBrowser, + url: browser.currentURI.asciiSpec, + title: browser.contentTitle + }); }; - // https://developer.mozilla.org/en-US/docs/Storage/Performance#Vacuuming_and_zero-fill - // Vacuum only once, and only while idle - var vacuum = function() { - vacuumTimer = null; - if ( db === null ) { - return; - } - var idleSvc = Cc['@mozilla.org/widget/idleservice;1'] - .getService(Ci.nsIIdleService); - if ( idleSvc.idleTime < 60000 ) { - vacuumTimer = vAPI.setTimeout(vacuum, 60000); - return; - } - db.createAsyncStatement('VACUUM').executeAsync(); - vacuum = null; - }; + vAPI.tabs.getAllSync = function (window) { + let win; + let tabs = []; - // Execute a query - var runStatement = function(stmt, callback) { - var result = {}; + for (let win of winWatcher.getWindows()) { + if (window && window !== win) { + continue; + } - stmt.executeAsync({ - handleResult: function(rows) { - if ( !rows || typeof callback !== 'function' ) { - return; - } + let tabBrowser = getTabBrowser(win); + if (tabBrowser === null) { + continue; + } - var row; + // This can happens if a tab-less window is currently opened. + // Example of a tab-less window: one opened from clicking + // "View Page Source". + if (!tabBrowser.tabs) { + continue; + } - while ( (row = rows.getNextRow()) ) { - // we assume that there will be two columns, since we're - // using it only for preferences - result[row.getResultByIndex(0)] = row.getResultByIndex(1); - } - }, - handleCompletion: function(reason) { - if ( typeof callback === 'function' && reason === 0 ) { - callback(result); - } - }, - handleError: function(error) { - console.error('SQLite error ', error.result, error.message); - // Caller expects an answer regardless of failure. - if ( typeof callback === 'function' ) { - callback(null); - } - } - }); - }; + for (let tab of tabBrowser.tabs) { + tabs.push(tab); + } + } - var bindNames = function(stmt, names) { - if ( Array.isArray(names) === false || names.length === 0 ) { - return; - } - var params = stmt.newBindingParamsArray(); - var i = names.length, bp; - while ( i-- ) { - bp = params.newBindingParams(); - bp.bindByName('name', names[i]); - params.addParams(bp); - } - stmt.bindParameters(params); + return tabs; }; - var clear = function(callback) { - if ( open() === null ) { - if ( typeof callback === 'function' ) { - callback(); + vAPI.tabs.getAll = function (callback) { + let tabs = []; + + for (let browser of tabWatcher.browsers()) { + let tab = tabWatcher.tabFromBrowser(browser); + + if (tab === null) { + continue; } - return; - } - runStatement(db.createAsyncStatement('DELETE FROM "settings";'), callback); + + if (tab.hasAttribute('pending')) { + continue; + } + + tabs.push({ + id: tabWatcher.tabIdFromTarget(browser), + url: browser.currentURI.asciiSpec + }); + } + + callback(tabs); }; - var getBytesInUse = function(keys, callback) { - if ( typeof callback !== 'function' ) { - return; - } + vAPI.tabs.open = function (details) { + // properties of the details object: + // + url - the address that will be opened + // + tabId:- the tab is used if set, instead of creating a new one + // + index: - undefined: end of the list, -1: following tab, or + // after index + // + active: - opens the tab in background - true and undefined: + // foreground + // + select: - if a tab is already opened with that url, then + // select it instead of opening a new one + if (!details.url) { + return null; + } + + // extension pages + if (/^[\w-]{2,}:/.test(details.url) === false) { + details.url = vAPI.getURL(details.url); + } - if ( open() === null ) { - callback(0); - return; - } - - var stmt; - if ( Array.isArray(keys) ) { - stmt = db.createAsyncStatement('SELECT "size" AS "size", SUM(LENGTH("value")) FROM "settings" WHERE "name" = :name'); - bindNames(keys); - } else { - stmt = db.createAsyncStatement('SELECT "size" AS "size", SUM(LENGTH("value")) FROM "settings"'); - } - - runStatement(stmt, function(result) { - callback(result.size); - }); - }; + if (details.select) { + let URI = Services.io.newURI(details.url, null, null); - var read = function(details, callback) { - if ( typeof callback !== 'function' ) { - return; - } + for (let tab of this.getAllSync()) { + let browser = tabWatcher.browserFromTarget(tab); + + // https://github.com/gorhill/uBlock/issues/2558 + if (browser === null) { + continue; + } - var prepareResult = function(result) { - var key; - for ( key in result ) { - if ( result.hasOwnProperty(key) === false ) { + // Or simply .equals if we care about the fragment + if (URI.equalsExceptRef(browser.currentURI) === false) { continue; - } - result[key] = JSON.parse(result[key]); - } - if ( typeof details === 'object' && details !== null ) { - for ( key in details ) { - if ( result.hasOwnProperty(key) === false ) { - result[key] = details[key]; - } - } - } - callback(result); - }; + } - if ( open() === null ) { - prepareResult({}); - return; - } - - var names = []; - if ( details !== null ) { - if ( Array.isArray(details) ) { - names = details; - } else if ( typeof details === 'object' ) { - names = Object.keys(details); - } else { - names = [details.toString()]; - } - } + this.select(tab); - var stmt; - if ( names.length === 0 ) { - stmt = db.createAsyncStatement('SELECT * FROM "settings"'); - } else { - stmt = db.createAsyncStatement('SELECT * FROM "settings" WHERE "name" = :name'); - bindNames(stmt, names); - } + // Update URL if fragment is different + if (URI.equals(browser.currentURI) === false) { + browser.loadURI(URI.asciiSpec); + } + + return; + } + } - runStatement(stmt, prepareResult); - }; + if (details.active === undefined) { + details.active = true; + } - var remove = function(keys, callback) { - if ( open() === null ) { - if ( typeof callback === 'function' ) { - callback(); + if (details.tabId) { + let tab = tabWatcher.browserFromTabId(details.tabId); + + if (tab) { + tabWatcher.browserFromTarget(tab).loadURI(details.url); + return; } - return; - } - var stmt = db.createAsyncStatement('DELETE FROM "settings" WHERE "name" = :name'); - bindNames(stmt, typeof keys === 'string' ? [keys] : keys); - runStatement(stmt, callback); - }; + } - var write = function(details, callback) { - if ( open() === null ) { - if ( typeof callback === 'function' ) { - callback(); - } + // Open in a standalone window + if (details.popup === true) { + Services.ww.openWindow(self, + details.url, + null, + 'location=1,menubar=1,personalbar=1,' + +'resizable=1,toolbar=1', + null); return; - } - - var stmt = db.createAsyncStatement('INSERT OR REPLACE INTO "settings" ("name", "value") VALUES(:name, :value)'); - var params = stmt.newBindingParamsArray(), bp; - for ( var key in details ) { - if ( details.hasOwnProperty(key) === false ) { - continue; - } - bp = params.newBindingParams(); - bp.bindByName('name', key); - bp.bindByName('value', JSON.stringify(details[key])); - params.addParams(bp); - } - if ( params.length === 0 ) { + } + + let win = winWatcher.getCurrentWindow(); + let tabBrowser = getTabBrowser(win); + + if (tabBrowser === null) { return; - } + } - stmt.bindParameters(params); - runStatement(stmt, callback); - }; + if (details.index === -1) { + details.index = + tabBrowser.browsers.indexOf(tabBrowser.selectedBrowser) + 1; + } - // Export API - var api = { - QUOTA_BYTES: 100 * 1024 * 1024, - clear: clear, - get: read, - getBytesInUse: getBytesInUse, - remove: remove, - set: write - }; - return api; -})(); + let tab = tabBrowser.loadOneTab(details.url, { + inBackground: !details.active + }); -vAPI.cacheStorage = vAPI.storage; + if (details.index !== undefined) { + tabBrowser.moveTabTo(tab, details.index); + } + }; -/******************************************************************************/ + vAPI.tabs.replace = function (tabId, url) { + // Replace the URL of a tab. Noop if the tab does not exist. + let targetURL = url; -// This must be executed/setup early. + // extension pages + if (/^[\w-]{2,}:/.test(targetURL) !== true) { + targetURL = vAPI.getURL(targetURL); + } -var winWatcher = (function() { - var windowToIdMap = new Map(); - var windowIdGenerator = 1; - var api = { - onOpenWindow: null, - onCloseWindow: null + let browser = tabWatcher.browserFromTabId(tabId); + if (browser) { + browser.loadURI(targetURL); + } }; - // https://github.com/gorhill/uMatrix/issues/586 - // This is necessary hack because on SeaMonkey 2.40, for unknown reasons - // private windows do not have the attribute `windowtype` set to - // `navigator:browser`. As a fallback, the code here will also test whether - // the id attribute is `main-window`. - api.toBrowserWindow = function(win) { - var docElement = win && win.document && win.document.documentElement; - if ( !docElement ) { - return null; - } - if ( vAPI.thunderbird ) { - return docElement.getAttribute('windowtype') === 'mail:3pane' ? win : null; - } - return docElement.getAttribute('windowtype') === 'navigator:browser' || - docElement.getAttribute('id') === 'main-window' ? - win : null; + vAPI.tabs._remove = function (tab, tabBrowser) { + if (tabBrowser) { + tabBrowser.removeTab(tab); + } }; + function removeInternal(tab, tabBrowser) { + if (tabBrowser) { + tabBrowser.removeTab(tab); + } + } - api.getWindows = function() { - return windowToIdMap.keys(); + vAPI.tabs.remove = function (tabId) { + let browser = tabWatcher.browserFromTabId(tabId); + if (!browser) { + return; + } + + let tab = tabWatcher.tabFromBrowser(browser); + if (!tab) { + return; + } + + removeInternal(tab, getTabBrowser(getOwnerWindow(browser))); }; - api.idFromWindow = function(win) { - return windowToIdMap.get(win) || 0; - }; + vAPI.tabs.reload = function (tabId) { + let browser = tabWatcher.browserFromTabId(tabId); + if (!browser) { + return; + } - api.getCurrentWindow = function() { - return this.toBrowserWindow(Services.wm.getMostRecentWindow(null)); + browser.webNavigation.reload(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE); }; - var addWindow = function(win) { - if ( !win || windowToIdMap.has(win) ) { + vAPI.tabs.select = function (tab) { + if (typeof tab !== 'object') { + tab = tabWatcher.tabFromBrowser(tabWatcher.browserFromTabId(tab)); + } + if (!tab) { return; - } - windowToIdMap.set(win, windowIdGenerator++); - if ( typeof api.onOpenWindow === 'function' ) { - api.onOpenWindow(win); - } + } + + // https://github.com/gorhill/uBlock/issues/470 + let win = getOwnerWindow(tab); + win.focus(); + + let tabBrowser = getTabBrowser(win); + if (tabBrowser) { + tabBrowser.selectedTab = tab; + } }; - var removeWindow = function(win) { - if ( !win || windowToIdMap.delete(win) !== true ) { + vAPI.tabs.injectScript = function (tabId, details, callback) { + let browser = tabWatcher.browserFromTabId(tabId); + if (!browser) { + return; + } + + if (typeof details.file !== 'string') { return; - } - if ( typeof api.onCloseWindow === 'function' ) { - api.onCloseWindow(win); - } + } + + details.file = vAPI.getURL(details.file); + browser.messageManager.sendAsyncMessage(location.host + ':broadcast', + JSON.stringify({ + broadcast: true, + channelName: 'vAPI', + msg: { + cmd: 'injectScript', + details: details + } + })); + + if (typeof callback === 'function') { + vAPI.setTimeout(callback, 13); + } }; - // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIWindowMediator - // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIWindowWatcher - // https://github.com/gorhill/uMatrix/issues/357 - // Use nsIWindowMediator for being notified of opened/closed windows. - var listeners = { - onOpenWindow: function(aWindow) { - var win; - try { - win = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); - } catch (ex) { + let tabWatcher = (function () { + // TODO: find out whether we need a janitor to take care of stale entries. + + // https://github.com/gorhill/uMatrix/issues/540 + // Use only weak references to hold onto browser references. + let browserToTabIdMap = new WeakMap(); + let tabIdToBrowserMap = new Map(); + let tabIdGenerator = 1; + + let indexFromBrowser = function (browser) { + if (!browser) { + return -1; + } + + let win = getOwnerWindow(browser); + if (!win) { + return -1; + } + + let tabBrowser = getTabBrowser(win); + if (tabBrowser === null) { + return -1; + } + + // This can happen, for example, the `view-source:` + // window, there is no tabbrowser object, the browser + // object sits directly in the window. + if (tabBrowser === browser) { + return 0; + } + + return tabBrowser.browsers.indexOf(browser); + }; + + let indexFromTarget = function (target) { + return indexFromBrowser(browserFromTarget(target)); + }; + + let tabFromBrowser = function (browser) { + let i = indexFromBrowser(browser); + if (i === -1) { + return null; + } + + let win = getOwnerWindow(browser); + if (!win) { + return null; + } + + let tabBrowser = getTabBrowser(win); + if (tabBrowser === null) { + return null; + } + + if (!tabBrowser.tabs || i >= tabBrowser.tabs.length) { + return null; + } + + return tabBrowser.tabs[i]; + }; + + let browserFromTarget = function (target) { + if (!target) { + return null; + } + + if (target.linkedPanel) { + // target is a tab + target = target.linkedBrowser; + } + + if (target.localName !== 'browser') { + return null; + } + + return target; + }; + + let tabIdFromTarget = function (target) { + let browser = browserFromTarget(target); + if (browser === null) { + return vAPI.noTabId; + } + + let tabId = browserToTabIdMap.get(browser); + if (tabId === undefined) { + tabId = '' + tabIdGenerator++; + browserToTabIdMap.set(browser, tabId); + tabIdToBrowserMap.set(tabId, Cu.getWeakReference(browser)); + } + + return tabId; + }; + + let browserFromTabId = function (tabId) { + let weakref = tabIdToBrowserMap.get(tabId); + let browser = weakref && weakref.get(); + + return browser || null; + }; + + let currentBrowser = function () { + let win = winWatcher.getCurrentWindow(); + + // https://github.com/gorhill/uBlock/issues/399 + // getTabBrowser() can return null at browser launch time. + let tabBrowser = getTabBrowser(win); + if (tabBrowser === null) { + return null; + } + + return browserFromTarget(tabBrowser.selectedTab); + }; + + let removeBrowserEntry = function (tabId, browser) { + if (tabId && tabId !== vAPI.noTabId) { + vAPI.tabs.onClosed(tabId); + delete vAPI.toolbarButton.tabs[tabId]; + tabIdToBrowserMap.delete(tabId); + } + + if (browser) { + browserToTabIdMap.delete(browser); + } + }; + + let removeTarget = function (target) { + onClose({ + target: target + }); + }; + + let getAllBrowsers = function () { + let browsers = []; + + for (let [tabId, weakref] of tabIdToBrowserMap) { + let browser = weakref.get(); + + // TODO: Maybe call removeBrowserEntry() if the + // browser no longer exists? + if (browser) { + browsers.push(browser); + } } - addWindow(win); - }, + + return browsers; + }; + + // var onOpen = function (target) { + // var tabId = tabIdFromTarget(target); + // var browser = browserFromTabId(tabId); + // vAPI.tabs.onNavigation({ + // frameId: 0, + // tabId: tabId, + // url: browser.currentURI.asciiSpec, + // }); + // }; + + var onShow = function (target) { + tabIdFromTarget(target); + }; + + var onClose = function (target) { + // target is tab in Firefox, browser in Fennec + let browser = browserFromTarget(target); + let tabId = browserToTabIdMap.get(browser); + removeBrowserEntry(tabId, browser); + }; + + var onSelect = function (target) { + // This is an entry point: when creating a new tab, it is + // not always reported through onLocationChanged... + // Sigh. It is "reported" here however. + let browser = browserFromTarget(target); + let tabId = browserToTabIdMap.get(browser); + + if (tabId === undefined) { + tabId = tabIdFromTarget(target); + vAPI.tabs.onNavigation({ + frameId: 0, + tabId: tabId, + url: browser.currentURI.asciiSpec + }); + } + + vAPI.setIcon(tabId, getOwnerWindow(target)); + }; + + let locationChangedMessageName = location.host + ':locationChanged'; + + let onLocationChanged = function (e) { + let details = e.data; + + // Ignore notifications related to our popup + if (details.url.lastIndexOf(vAPI.getURL('popup.html'), 0) === 0) { + return; + } + + let browser = e.target; + let tabId = tabIdFromTarget(browser); + if (tabId === vAPI.noTabId) { + return; + } + + // LOCATION_CHANGE_SAME_DOCUMENT = "did not load a new document" + if (details.flags + & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) { + vAPI.tabs.onUpdated(tabId, {url: details.url}, { + frameId: 0, + tabId: tabId, + url: browser.currentURI.asciiSpec + }); + return; + } + + // https://github.com/chrisaljoudi/uBlock/issues/105 + // Allow any kind of pages + vAPI.tabs.onNavigation({ + frameId: 0, + tabId: tabId, + url: details.url + }); + }; - onCloseWindow: function(aWindow) { - var win; - try { - win = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); - } catch (ex) { + let attachToTabBrowser = function (window) { + if (typeof vAPI.toolbarButton.attachToNewWindow === 'function') { + vAPI.toolbarButton.attachToNewWindow(window); } - removeWindow(win); - }, - observe: function(aSubject, topic) { - // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIWindowWatcher#registerNotification%28%29 - // "aSubject - the window being opened or closed, sent as an - // "nsISupports which can be ... QueryInterfaced to an - // "nsIDOMWindow." - var win; - try { - win = aSubject.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); - } catch (ex) { + let tabBrowser = getTabBrowser(window); + if (tabBrowser === null) { + return; + } + + let tabContainer; + if (tabBrowser.deck) { + // Fennec + tabContainer = tabBrowser.deck; + } else if (tabBrowser.tabContainer) { + // Firefox + tabContainer = tabBrowser.tabContainer; + vAPI.contextMenu.register(document); } - if ( !win ) { return; } - if ( topic === 'domwindowopened' ) { - addWindow(win); - return; + + // https://github.com/gorhill/uBlock/issues/697 + // Ignore `TabShow` events: unfortunately the `pending` + // attribute is not set when a tab is opened as a result + // of session restore -- it is set *after* the event is + // fired in such case. + if (tabContainer) { + tabContainer.addEventListener('TabShow', onShow); + tabContainer.addEventListener('TabClose', onClose); + // when new window is opened TabSelect doesn't run on + // the selected tab? + tabContainer.addEventListener('TabSelect', onSelect); } - if ( topic === 'domwindowclosed' ) { - removeWindow(win); - return; + }; + + var canAttachToTabBrowser = function (window) { + // https://github.com/gorhill/uBlock/issues/906 + // Ensure the environment is ready before trying to attaching. + let document = window && window.document; + if (!document || document.readyState !== 'complete') { + return false; + } + + // On some platforms, the tab browser isn't immediately + // available, try waiting a bit if this + // https://github.com/gorhill/uBlock/issues/763 + // Not getting a tab browser should not prevent from + // attaching ourself to the window. + let tabBrowser = getTabBrowser(window); + if (tabBrowser === null) { + return false; + } + + return winWatcher.toBrowserWindow(window) !== null; + }; + + let onWindowLoad = function (win) { + deferUntil(canAttachToTabBrowser.bind(null, win), + attachToTabBrowser.bind(null, win)); + }; + + let onWindowUnload = function (win) { + vAPI.contextMenu.unregister(win.document); + + let tabBrowser = getTabBrowser(win); + if (tabBrowser === null) { + return; + } + + let tabContainer = tabBrowser.tabContainer; + if (tabContainer) { + tabContainer.removeEventListener('TabShow', onShow); + tabContainer.removeEventListener('TabClose', onClose); + tabContainer.removeEventListener('TabSelect', onSelect); + } + + // https://github.com/gorhill/uBlock/issues/574 + // To keep in mind: not all windows are tab containers, + // sometimes the window IS the tab. + let tabs; + if (tabBrowser.tabs) { + tabs = tabBrowser.tabs; + } else if (tabBrowser.localName === 'browser') { + tabs = [tabBrowser]; + } else { + tabs = []; } - } - }; - (function() { - var winumerator, win; - - // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIWindowMediator#getEnumerator%28%29 - winumerator = Services.wm.getEnumerator(null); - while ( winumerator.hasMoreElements() ) { - win = winumerator.getNext(); - if ( !win.closed ) { - windowToIdMap.set(win, windowIdGenerator++); - } - } - - // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIWindowWatcher#getWindowEnumerator%28%29 - winumerator = Services.ww.getWindowEnumerator(); - while ( winumerator.hasMoreElements() ) { - win = winumerator.getNext() - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); - if ( !win.closed ) { - windowToIdMap.set(win, windowIdGenerator++); - } - } - - Services.wm.addListener(listeners); - Services.ww.registerNotification(listeners); - })(); + let browser; + let URI; + let tabId; + for (let i=tabs.length-1; i>=0; --i) { + let tab = tabs[i]; + browser = browserFromTarget(tab); + if (browser === null) { + continue; + } + + URI = browser.currentURI; + // Close extension tabs + if (URI.schemeIs('chrome') && URI.host === location.host) { + removeInternal(tab, getTabBrowser(win)); + } + + tabId = browserToTabIdMap.get(browser); + if (tabId !== undefined) { + removeBrowserEntry(tabId, browser); + tabIdToBrowserMap.delete(tabId); + } + browserToTabIdMap.delete(browser); + } + }; - cleanupTasks.push(function() { - Services.wm.removeListener(listeners); - Services.ww.unregisterNotification(listeners); - windowToIdMap.clear(); - }); + var start = function () { + // Initialize map with existing active tabs + let tabBrowser; + let tabs; - return api; -})(); + for (let win of winWatcher.getWindows()) { + onWindowLoad(win); + + tabBrowser = getTabBrowser(win); + if (tabBrowser === null) { + continue; + } + + for (let tab of tabBrowser.tabs) { + if (!tab.hasAttribute('pending')) { + tabIdFromTarget(tab); + } + } + } -/******************************************************************************/ + winWatcher.onOpenWindow = onWindowLoad; + winWatcher.onCloseWindow = onWindowUnload; -var getTabBrowser = function(win) { - return win && win.gBrowser || null; -}; + vAPI.messaging.globalMessageManager + .addMessageListener(locationChangedMessageName, + onLocationChanged); + }; -/******************************************************************************/ + let stop = function () { + winWatcher.onOpenWindow = null; + winWatcher.onCloseWindow = null; -var getOwnerWindow = function(target) { - if ( target.ownerDocument ) { - return target.ownerDocument.defaultView; - } - return null; -}; + vAPI.messaging.globalMessageManager + .removeMessageListener(locationChangedMessageName, + onLocationChanged); -/******************************************************************************/ + for (let win of winWatcher.getWindows()) { + onWindowUnload(win); + } -vAPI.isBehindTheSceneTabId = function(tabId) { - return tabId.toString() === '-1'; -}; + browserToTabIdMap = new WeakMap(); + tabIdToBrowserMap.clear(); + }; -vAPI.noTabId = '-1'; + cleanupTasks.push(stop); -/******************************************************************************/ + return { + browsers: getAllBrowsers, + browserFromTabId: browserFromTabId, + browserFromTarget: browserFromTarget, + currentBrowser: currentBrowser, + indexFromTarget: indexFromTarget, + removeTarget: removeTarget, + start: start, + tabFromBrowser: tabFromBrowser, + tabIdFromTarget: tabIdFromTarget + }; + })(); -vAPI.tabs = {}; + // Icon-related stuff + vAPI.setIcon = function (tabId, iconId, badge) { + // If badge is undefined, then setIcon was called from the + // TabSelect event + let win; + if (badge === undefined) { + win = iconId; + } else { + win = winWatcher.getCurrentWindow(); + } + + let tabBrowser = getTabBrowser(win); + if (tabBrowser === null) { + return; + } + + let curTabId = tabWatcher.tabIdFromTarget(tabBrowser.selectedTab); + let tb = vAPI.toolbarButton; + + // from 'TabSelect' event + if (tabId === undefined) { + tabId = curTabId; + } else if (badge !== undefined) { + tb.tabs[tabId] = { + badge: badge, img: iconId + }; + } -/******************************************************************************/ - -vAPI.tabs.registerListeners = function() { - tabWatcher.start(); -}; - -/******************************************************************************/ - -// Firefox: -// https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Tabbed_browser -// -// browser --> ownerDocument --> defaultView --> gBrowser --> browsers --+ -// ^ | -// | | -// +------------------------------------------------------------------- -// -// browser (browser) -// contentTitle -// currentURI -// ownerDocument (XULDocument) -// defaultView (ChromeWindow) -// gBrowser (tabbrowser OR browser) -// browsers (browser) -// selectedBrowser -// selectedTab -// tabs (tab.tabbrowser-tab) -// -// Fennec: (what I figured so far) -// -// tab --> browser windows --> window --> BrowserApp --> tabs --+ -// ^ window | -// | | -// +---------------------------------------------------------------+ -// -// tab -// browser -// [manual search to go back to tab from list of windows] - -vAPI.tabs.get = function(tabId, callback) { - var browser; - - if ( tabId === null ) { - browser = tabWatcher.currentBrowser(); - tabId = tabWatcher.tabIdFromTarget(browser); - } else { - browser = tabWatcher.browserFromTabId(tabId); - } - - // For internal use - if ( typeof callback !== 'function' ) { - return browser; - } - - if ( !browser || !browser.currentURI ) { - callback(); - return; - } - - var win = getOwnerWindow(browser); - var tabBrowser = getTabBrowser(win); - - // https://github.com/gorhill/uMatrix/issues/540 - // The `index` property is nowhere used by eMatrix at this point, so we - // will refrain from returning this information for the time being. - - callback({ - id: tabId, - index: undefined, - windowId: winWatcher.idFromWindow(win), - active: tabBrowser !== null && browser === tabBrowser.selectedBrowser, - url: browser.currentURI.asciiSpec, - title: browser.contentTitle - }); -}; - -/******************************************************************************/ - -vAPI.tabs.getAllSync = function(window) { - var win, tab; - var tabs = []; - - for ( win of winWatcher.getWindows() ) { - if ( window && window !== win ) { - continue; - } - - var tabBrowser = getTabBrowser(win); - if ( tabBrowser === null ) { - continue; - } - - // This can happens if a tab-less window is currently opened. - // Example of a tab-less window: one opened from clicking - // "View Page Source". - if ( !tabBrowser.tabs ) { - continue; - } - - for ( tab of tabBrowser.tabs ) { - tabs.push(tab); - } - } - - return tabs; -}; - -/******************************************************************************/ - -vAPI.tabs.getAll = function(callback) { - var tabs = [], tab; - - for ( var browser of tabWatcher.browsers() ) { - tab = tabWatcher.tabFromBrowser(browser); - if ( tab === null ) { - continue; - } - if ( tab.hasAttribute('pending') ) { - continue; - } - tabs.push({ - id: tabWatcher.tabIdFromTarget(browser), - url: browser.currentURI.asciiSpec - }); - } - - callback(tabs); -}; + if (tabId === curTabId) { + tb.updateState(win, tabId); + } + }; -/******************************************************************************/ + // Internal message passing mechanism + vAPI.messaging = { + get globalMessageManager() { + return Cc['@mozilla.org/globalmessagemanager;1'] + .getService(Ci.nsIMessageListenerManager); + }, + frameScript: vAPI.getURL('frameScript.js'), + listeners: {}, + defaultHandler: null, + NOOPFUNC: function(){}, + UNHANDLED: 'vAPI.messaging.notHandled' + }; -// 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.messaging.listen = function (listenerName, callback) { + this.listeners[listenerName] = callback; + }; -vAPI.tabs.open = function(details) { - if ( !details.url ) { - return null; - } - // extension pages - if ( /^[\w-]{2,}:/.test(details.url) === false ) { - details.url = vAPI.getURL(details.url); - } + vAPI.messaging.onMessage = function (target, data) { + let messageManager = target.messageManager; - var tab; + if (!messageManager) { + // Message came from a popup, and its message manager is + // not usable. So instead we broadcast to the parent + // window. + messageManager = + getOwnerWindow(target.webNavigation + .QueryInterface(Ci.nsIDocShell) + .chromeEventHandler).messageManager; + } - if ( details.select ) { - var URI = Services.io.newURI(details.url, null, null); + let channelNameRaw = data.channelName; + let pos = channelNameRaw.indexOf('|'); + let channelName = channelNameRaw.slice(pos + 1); - for ( tab of this.getAllSync() ) { - var browser = tabWatcher.browserFromTarget(tab); - // https://github.com/gorhill/uBlock/issues/2558 - if ( browser === null ) { continue; } + let callback = vAPI.messaging.NOOPFUNC; + if (data.requestId !== undefined) { + callback = CallbackWrapper.factory(messageManager, + channelName, + channelNameRaw.slice(0, pos), + data.requestId).callback; + } - // Or simply .equals if we care about the fragment - if ( URI.equalsExceptRef(browser.currentURI) === false ) { - continue; + let sender = { + tab: { + id: tabWatcher.tabIdFromTarget(target) } + }; - this.select(tab); - - // Update URL if fragment is different - if ( URI.equals(browser.currentURI) === false ) { - browser.loadURI(URI.asciiSpec); - } + // Specific handler + let r = vAPI.messaging.UNHANDLED; + let listener = vAPI.messaging.listeners[channelName]; + + if (typeof listener === 'function') { + r = listener(data.msg, sender, callback); + } + if (r !== vAPI.messaging.UNHANDLED) { return; - } - } - - if ( details.active === undefined ) { - details.active = true; - } + } - if ( details.tabId ) { - tab = tabWatcher.browserFromTabId(details.tabId); - if ( tab ) { - tabWatcher.browserFromTarget(tab).loadURI(details.url); + // Default handler + r = vAPI.messaging.defaultHandler(data.msg, sender, callback); + if (r !== vAPI.messaging.UNHANDLED) { return; - } - } - - // Open in a standalone window - if ( details.popup === true ) { - Services.ww.openWindow( - self, - details.url, - null, - 'location=1,menubar=1,personalbar=1,resizable=1,toolbar=1', - null - ); - return; - } - - var win = winWatcher.getCurrentWindow(); - var tabBrowser = getTabBrowser(win); - if ( tabBrowser === null ) { - return; - } - - if ( details.index === -1 ) { - details.index = tabBrowser.browsers.indexOf(tabBrowser.selectedBrowser) + 1; - } - - tab = tabBrowser.loadOneTab(details.url, { inBackground: !details.active }); - - if ( details.index !== undefined ) { - tabBrowser.moveTabTo(tab, details.index); - } -}; - -/******************************************************************************/ - -// Replace the URL of a tab. Noop if the tab does not exist. - -vAPI.tabs.replace = function(tabId, url) { - var targetURL = url; - - // extension pages - if ( /^[\w-]{2,}:/.test(targetURL) !== true ) { - targetURL = vAPI.getURL(targetURL); - } - - var browser = tabWatcher.browserFromTabId(tabId); - if ( browser ) { - browser.loadURI(targetURL); - } -}; - -/******************************************************************************/ - -vAPI.tabs._remove = function(tab, tabBrowser) { - if ( tabBrowser ) { - tabBrowser.removeTab(tab); - } -}; - -/******************************************************************************/ - -vAPI.tabs.remove = function(tabId) { - var browser = tabWatcher.browserFromTabId(tabId); - if ( !browser ) { - return; - } - var tab = tabWatcher.tabFromBrowser(browser); - if ( !tab ) { - return; - } - this._remove(tab, getTabBrowser(getOwnerWindow(browser))); -}; - -/******************************************************************************/ - -vAPI.tabs.reload = function(tabId) { - var browser = tabWatcher.browserFromTabId(tabId); - if ( !browser ) { - return; - } - - browser.webNavigation.reload(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE); -}; - -/******************************************************************************/ - -vAPI.tabs.select = function(tab) { - if ( typeof tab !== 'object' ) { - tab = tabWatcher.tabFromBrowser(tabWatcher.browserFromTabId(tab)); - } - if ( !tab ) { - return; - } - - // https://github.com/gorhill/uBlock/issues/470 - var win = getOwnerWindow(tab); - win.focus(); - - var tabBrowser = getTabBrowser(win); - if ( tabBrowser ) { - tabBrowser.selectedTab = tab; - } -}; - -/******************************************************************************/ - -vAPI.tabs.injectScript = function(tabId, details, callback) { - var browser = tabWatcher.browserFromTabId(tabId); - if ( !browser ) { - return; - } - - if ( typeof details.file !== 'string' ) { - return; - } - - details.file = vAPI.getURL(details.file); - browser.messageManager.sendAsyncMessage( - location.host + ':broadcast', - JSON.stringify({ - broadcast: true, - channelName: 'vAPI', - msg: { - cmd: 'injectScript', - details: details - } - }) - ); - - if ( typeof callback === 'function' ) { - vAPI.setTimeout(callback, 13); - } -}; - -/******************************************************************************/ + } -var tabWatcher = (function() { - // TODO: find out whether we need a janitor to take care of stale entries. - - // https://github.com/gorhill/uMatrix/issues/540 - // Use only weak references to hold onto browser references. - var browserToTabIdMap = new WeakMap(); - var tabIdToBrowserMap = new Map(); - var tabIdGenerator = 1; - - var indexFromBrowser = function(browser) { - if ( !browser ) { - return -1; - } - var win = getOwnerWindow(browser); - if ( !win ) { - return -1; - } - var tabBrowser = getTabBrowser(win); - if ( tabBrowser === null ) { - return -1; - } - // This can happen, for example, the `view-source:` window, there is - // no tabbrowser object, the browser object sits directly in the - // window. - if ( tabBrowser === browser ) { - return 0; - } - return tabBrowser.browsers.indexOf(browser); - }; + console.error('eMatrix> messaging > unknown request: %o', data); - var indexFromTarget = function(target) { - return indexFromBrowser(browserFromTarget(target)); + // Unhandled: Need to callback anyways in case caller expected + // an answer, or else there is a memory leak on caller's side + callback(); }; - var tabFromBrowser = function(browser) { - var i = indexFromBrowser(browser); - if ( i === -1 ) { - return null; - } - var win = getOwnerWindow(browser); - if ( !win ) { - return null; - } - var tabBrowser = getTabBrowser(win); - if ( tabBrowser === null ) { - return null; - } - if ( !tabBrowser.tabs || i >= tabBrowser.tabs.length ) { - return null; - } - return tabBrowser.tabs[i]; - }; + vAPI.messaging.setup = function (defaultHandler) { + // Already setup? + if (this.defaultHandler !== null) { + return; + } - var browserFromTarget = function(target) { - if ( !target ) { - return null; - } - if ( target.linkedPanel ) { // target is a tab - target = target.linkedBrowser; - } - if ( target.localName !== 'browser' ) { - return null; - } - return target; + if (typeof defaultHandler !== 'function') { + defaultHandler = function () { + return vAPI.messaging.UNHANDLED; + }; + } + + this.defaultHandler = defaultHandler; + this.globalMessageManager.addMessageListener(location.host + + ':background', + this.onMessage); + this.globalMessageManager.loadFrameScript(this.frameScript, true); + + cleanupTasks.push(function () { + let gmm = vAPI.messaging.globalMessageManager; + + gmm.removeDelayedFrameScript(vAPI.messaging.frameScript); + gmm.removeMessageListener(location.host + ':background', + vAPI.messaging.onMessage); + }); + }; + + vAPI.messaging.broadcast = function (message) { + this.globalMessageManager + .broadcastAsyncMessage(location.host + ':broadcast', + JSON.stringify({ + broadcast: true, + msg: message})); + }; + + let CallbackWrapper = function (messageManager, channelName, + listenerId, requestId) { + // This allows to avoid creating a closure for every single + // message which expects an answer. Having a closure created + // each time a message is processed has been always bothering + // me. Another benefit of the implementation here is to reuse + // the callback proxy object, so less memory churning. + // + // https://developers.google.com/speed/articles/optimizing-javascript + // "Creating a closure is significantly slower then creating + // an inner function without a closure, and much slower than + // reusing a static function" + // + // http://hacksoflife.blogspot.ca/2015/01/the-four-horsemen-of-performance.html + // "the dreaded 'uniformly slow code' case where every + // function takes 1% of CPU and you have to make one hundred + // separate performance optimizations to improve performance + // at all" + // + // http://jsperf.com/closure-no-closure/2 + this.callback = this.proxy.bind(this); // bind once + this.init(messageManager, channelName, listenerId, requestId); + }; + + CallbackWrapper.junkyard = []; + + CallbackWrapper.factory = function (messageManager, channelName, + listenerId, requestId) { + let wrapper = CallbackWrapper.junkyard.pop(); + if (wrapper) { + wrapper.init(messageManager, channelName, listenerId, requestId); + return wrapper; + } + + return new CallbackWrapper(messageManager, channelName, + listenerId, requestId); }; - var tabIdFromTarget = function(target) { - var browser = browserFromTarget(target); - if ( browser === null ) { - return vAPI.noTabId; - } - var tabId = browserToTabIdMap.get(browser); - if ( tabId === undefined ) { - tabId = '' + tabIdGenerator++; - browserToTabIdMap.set(browser, tabId); - tabIdToBrowserMap.set(tabId, Cu.getWeakReference(browser)); - } - return tabId; + CallbackWrapper.prototype.init = function (messageManager, channelName, + listenerId, requestId) { + this.messageManager = messageManager; + this.channelName = channelName; + this.listenerId = listenerId; + this.requestId = requestId; }; - var browserFromTabId = function(tabId) { - var weakref = tabIdToBrowserMap.get(tabId); - var browser = weakref && weakref.get(); - return browser || null; - }; + CallbackWrapper.prototype.proxy = function (response) { + let message = JSON.stringify({ + requestId: this.requestId, + channelName: this.channelName, + msg: response !== undefined ? response : null + }); - var currentBrowser = function() { - var win = winWatcher.getCurrentWindow(); - // https://github.com/gorhill/uBlock/issues/399 - // getTabBrowser() can return null at browser launch time. - var tabBrowser = getTabBrowser(win); - if ( tabBrowser === null ) { - return null; - } - return browserFromTarget(tabBrowser.selectedTab); - }; + if (this.messageManager.sendAsyncMessage) { + this.messageManager.sendAsyncMessage(this.listenerId, message); + } else { + this.messageManager.broadcastAsyncMessage(this.listenerId, message); + } - var removeBrowserEntry = function(tabId, browser) { - if ( tabId && tabId !== vAPI.noTabId ) { - vAPI.tabs.onClosed(tabId); - delete vAPI.toolbarButton.tabs[tabId]; - tabIdToBrowserMap.delete(tabId); - } - if ( browser ) { - browserToTabIdMap.delete(browser); - } + // Mark for reuse + this.messageManager = this.channelName = + this.requestId = this.listenerId = null; + + CallbackWrapper.junkyard.push(this); }; - var removeTarget = function(target) { - onClose({ target: target }); + let httpRequestHeadersFactory = function (channel) { + let entry = httpRequestHeadersFactory.junkyard.pop(); + if (entry) { + return entry.init(channel); + } + + return new HTTPRequestHeaders(channel); }; - var getAllBrowsers = function() { - var browsers = [], browser; - for ( var [tabId, weakref] of tabIdToBrowserMap ) { - browser = weakref.get(); - // TODO: - // Maybe call removeBrowserEntry() if the browser no longer exists? - if ( browser ) { - browsers.push(browser); - } - } - return browsers; - }; + httpRequestHeadersFactory.junkyard = []; - // https://developer.mozilla.org/en-US/docs/Web/Events/TabOpen - //var onOpen = function({target}) { - // var tabId = tabIdFromTarget(target); - // var browser = browserFromTabId(tabId); - // vAPI.tabs.onNavigation({ - // frameId: 0, - // tabId: tabId, - // url: browser.currentURI.asciiSpec, - // }); - //}; - - // https://developer.mozilla.org/en-US/docs/Web/Events/TabShow - var onShow = function({target}) { - tabIdFromTarget(target); + let HTTPRequestHeaders = function (channel) { + this.init(channel); }; - // https://developer.mozilla.org/en-US/docs/Web/Events/TabClose - var onClose = function({target}) { - // target is tab in Firefox, browser in Fennec - var browser = browserFromTarget(target); - var tabId = browserToTabIdMap.get(browser); - removeBrowserEntry(tabId, browser); + HTTPRequestHeaders.prototype.init = function (channel) { + this.channel = channel; + this.headers = new Array(); + this.originalHeaderNames = new Array(); + + channel.visitRequestHeaders({ + visitHeader: function (name, value) { + this.headers.push({name: name, value: value}); + this.originalHeaderNames.push(name); + }.bind(this) + }); + + return this; }; - // https://developer.mozilla.org/en-US/docs/Web/Events/TabSelect - // This is an entry point: when creating a new tab, it is not always - // reported through onLocationChanged... Sigh. It is "reported" here - // however. - var onSelect = function({target}) { - var browser = browserFromTarget(target); - var tabId = browserToTabIdMap.get(browser); - if ( tabId === undefined ) { - tabId = tabIdFromTarget(target); - vAPI.tabs.onNavigation({ - frameId: 0, - tabId: tabId, - url: browser.currentURI.asciiSpec - }); - } - vAPI.setIcon(tabId, getOwnerWindow(target)); + HTTPRequestHeaders.prototype.dispose = function () { + this.channel = null; + this.headers = null; + this.originalHeaderNames = null; + httpRequestHeadersFactory.junkyard.push(this); }; - var locationChangedMessageName = location.host + ':locationChanged'; - - var onLocationChanged = function(e) { - var vapi = vAPI; - var details = e.data; - - // Ignore notifications related to our popup - if ( details.url.lastIndexOf(vapi.getURL('popup.html'), 0) === 0 ) { - return; - } - - var browser = e.target; - var tabId = tabIdFromTarget(browser); - if ( tabId === vapi.noTabId ) { - return; - } - - // LOCATION_CHANGE_SAME_DOCUMENT = "did not load a new document" - if ( details.flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT ) { - vapi.tabs.onUpdated(tabId, {url: details.url}, { - frameId: 0, - tabId: tabId, - url: browser.currentURI.asciiSpec - }); - return; - } - - // https://github.com/chrisaljoudi/uBlock/issues/105 - // Allow any kind of pages - vapi.tabs.onNavigation({ - frameId: 0, - tabId: tabId, - url: details.url - }); + HTTPRequestHeaders.prototype.update = function () { + let newHeaderNames = new Set(); + for (let header of this.headers) { + this.setHeader(header.name, header.value, true); + newHeaderNames.add(header.name); + } + + //Clear any headers that were removed + for (let name of this.originalHeaderNames) { + if (!newHeaderNames.has(name)) { + this.channel.setRequestHeader(name, '', false); + } + } }; - var attachToTabBrowser = function(window) { - if ( typeof vAPI.toolbarButton.attachToNewWindow === 'function' ) { - vAPI.toolbarButton.attachToNewWindow(window); - } - - var tabBrowser = getTabBrowser(window); - if ( tabBrowser === null ) { - return; - } - - var tabContainer; - if ( tabBrowser.deck ) { // Fennec - tabContainer = tabBrowser.deck; - } else if ( tabBrowser.tabContainer ) { // Firefox - tabContainer = tabBrowser.tabContainer; - vAPI.contextMenu.register(document); - } - - // https://github.com/gorhill/uBlock/issues/697 - // Ignore `TabShow` events: unfortunately the `pending` attribute is - // not set when a tab is opened as a result of session restore -- it is - // set *after* the event is fired in such case. - if ( tabContainer ) { - tabContainer.addEventListener('TabShow', onShow); - tabContainer.addEventListener('TabClose', onClose); - // when new window is opened TabSelect doesn't run on the selected tab? - tabContainer.addEventListener('TabSelect', onSelect); - } + HTTPRequestHeaders.prototype.getHeader = function (name) { + try { + return this.channel.getRequestHeader(name); + } catch (e) { + // Ignore + } + + return ''; }; - // https://github.com/gorhill/uBlock/issues/906 - // Ensure the environment is ready before trying to attaching. - var canAttachToTabBrowser = function(window) { - var document = window && window.document; - if ( !document || document.readyState !== 'complete' ) { + HTTPRequestHeaders.prototype.setHeader = function (name, newValue, create) { + let oldValue = this.getHeader(name); + if (newValue === oldValue) { return false; - } - - // On some platforms, the tab browser isn't immediately available, - // try waiting a bit if this happens. - // https://github.com/gorhill/uBlock/issues/763 - // Not getting a tab browser should not prevent from attaching ourself - // to the window. - var tabBrowser = getTabBrowser(window); - if ( tabBrowser === null ) { + } + + if (oldValue === '' && create !== true) { return false; - } - - return winWatcher.toBrowserWindow(window) !== null; - }; - - var onWindowLoad = function(win) { - deferUntil( - canAttachToTabBrowser.bind(null, win), - attachToTabBrowser.bind(null, win) - ); - }; - - var onWindowUnload = function(win) { - vAPI.contextMenu.unregister(win.document); - - var tabBrowser = getTabBrowser(win); - if ( tabBrowser === null ) { - return; - } - - var tabContainer = tabBrowser.tabContainer; - if ( tabContainer ) { - tabContainer.removeEventListener('TabShow', onShow); - tabContainer.removeEventListener('TabClose', onClose); - tabContainer.removeEventListener('TabSelect', onSelect); - } - - // https://github.com/gorhill/uBlock/issues/574 - // To keep in mind: not all windows are tab containers, - // sometimes the window IS the tab. - var tabs; - if ( tabBrowser.tabs ) { - tabs = tabBrowser.tabs; - } else if ( tabBrowser.localName === 'browser' ) { - tabs = [tabBrowser]; - } else { - tabs = []; - } - - var browser, URI, tabId; - var tabindex = tabs.length, tab; - while ( tabindex-- ) { - tab = tabs[tabindex]; - browser = browserFromTarget(tab); - if ( browser === null ) { - continue; - } - URI = browser.currentURI; - // Close extension tabs - if ( URI.schemeIs('chrome') && URI.host === location.host ) { - vAPI.tabs._remove(tab, getTabBrowser(win)); - } - tabId = browserToTabIdMap.get(browser); - if ( tabId !== undefined ) { - removeBrowserEntry(tabId, browser); - tabIdToBrowserMap.delete(tabId); - } - browserToTabIdMap.delete(browser); - } - }; - - // Initialize map with existing active tabs - var start = function() { - var tabBrowser, tabs, tab; - for ( var win of winWatcher.getWindows() ) { - onWindowLoad(win); - tabBrowser = getTabBrowser(win); - if ( tabBrowser === null ) { - continue; - } - for ( tab of tabBrowser.tabs ) { - if ( !tab.hasAttribute('pending') ) { - tabIdFromTarget(tab); - } - } - } - - winWatcher.onOpenWindow = onWindowLoad; - winWatcher.onCloseWindow = onWindowUnload; - - vAPI.messaging.globalMessageManager.addMessageListener( - locationChangedMessageName, - onLocationChanged - ); - }; - - var stop = function() { - winWatcher.onOpenWindow = null; - winWatcher.onCloseWindow = null; - - vAPI.messaging.globalMessageManager.removeMessageListener( - locationChangedMessageName, - onLocationChanged - ); - - for ( var win of winWatcher.getWindows() ) { - onWindowUnload(win); - } - - browserToTabIdMap = new WeakMap(); - tabIdToBrowserMap.clear(); - }; - - cleanupTasks.push(stop); - - return { - browsers: getAllBrowsers, - browserFromTabId: browserFromTabId, - browserFromTarget: browserFromTarget, - currentBrowser: currentBrowser, - indexFromTarget: indexFromTarget, - removeTarget: removeTarget, - start: start, - tabFromBrowser: tabFromBrowser, - tabIdFromTarget: tabIdFromTarget - }; -})(); - -/******************************************************************************/ - -vAPI.setIcon = function(tabId, iconId, badge) { - // If badge is undefined, then setIcon was called from the TabSelect event - var win; - if ( badge === undefined ) { - win = iconId; - } else { - win = winWatcher.getCurrentWindow(); - } - var tabBrowser = getTabBrowser(win); - if ( tabBrowser === null ) { - return; - } - var curTabId = tabWatcher.tabIdFromTarget(tabBrowser.selectedTab); - var tb = vAPI.toolbarButton; - - // from 'TabSelect' event - if ( tabId === undefined ) { - tabId = curTabId; - } else if ( badge !== undefined ) { - tb.tabs[tabId] = { badge: badge, img: iconId }; - } - - if ( tabId === curTabId ) { - tb.updateState(win, tabId); - } -}; - -/******************************************************************************/ - -vAPI.messaging = { - get globalMessageManager() { - return Cc['@mozilla.org/globalmessagemanager;1'] - .getService(Ci.nsIMessageListenerManager); - }, - frameScript: vAPI.getURL('frameScript.js'), - listeners: {}, - defaultHandler: null, - NOOPFUNC: function(){}, - UNHANDLED: 'vAPI.messaging.notHandled' -}; - -/******************************************************************************/ - -vAPI.messaging.listen = function(listenerName, callback) { - this.listeners[listenerName] = callback; -}; - -/******************************************************************************/ - -vAPI.messaging.onMessage = function({target, data}) { - var messageManager = target.messageManager; - - if ( !messageManager ) { - // Message came from a popup, and its message manager is not usable. - // So instead we broadcast to the parent window. - messageManager = getOwnerWindow( - target.webNavigation.QueryInterface(Ci.nsIDocShell).chromeEventHandler - ).messageManager; - } - - var channelNameRaw = data.channelName; - var pos = channelNameRaw.indexOf('|'); - var channelName = channelNameRaw.slice(pos + 1); - - var callback = vAPI.messaging.NOOPFUNC; - if ( data.requestId !== undefined ) { - callback = CallbackWrapper.factory( - messageManager, - channelName, - channelNameRaw.slice(0, pos), - data.requestId - ).callback; - } - - var sender = { - tab: { - id: tabWatcher.tabIdFromTarget(target) - } - }; - - // Specific handler - var r = vAPI.messaging.UNHANDLED; - var listener = vAPI.messaging.listeners[channelName]; - if ( typeof listener === 'function' ) { - r = listener(data.msg, sender, callback); - } - if ( r !== vAPI.messaging.UNHANDLED ) { - return; - } - - // Default handler - r = vAPI.messaging.defaultHandler(data.msg, sender, callback); - if ( r !== vAPI.messaging.UNHANDLED ) { - return; - } - - console.error('eMatrix> messaging > unknown request: %o', data); - - // 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; - - this.globalMessageManager.addMessageListener( - location.host + ':background', - this.onMessage - ); - - this.globalMessageManager.loadFrameScript(this.frameScript, true); - - cleanupTasks.push(function() { - var gmm = vAPI.messaging.globalMessageManager; - - gmm.removeDelayedFrameScript(vAPI.messaging.frameScript); - gmm.removeMessageListener( - location.host + ':background', - vAPI.messaging.onMessage - ); - }); -}; - -/******************************************************************************/ - -vAPI.messaging.broadcast = function(message) { - this.globalMessageManager.broadcastAsyncMessage( - location.host + ':broadcast', - JSON.stringify({broadcast: true, msg: message}) - ); -}; - -/******************************************************************************/ - -// This allows to avoid creating a closure for every single message which -// expects an answer. Having a closure created each time a message is processed -// has been always bothering me. Another benefit of the implementation here -// is to reuse the callback proxy object, so less memory churning. -// -// https://developers.google.com/speed/articles/optimizing-javascript -// "Creating a closure is significantly slower then creating an inner -// function without a closure, and much slower than reusing a static -// function" -// -// http://hacksoflife.blogspot.ca/2015/01/the-four-horsemen-of-performance.html -// "the dreaded 'uniformly slow code' case where every function takes 1% -// of CPU and you have to make one hundred separate performance optimizations -// to improve performance at all" -// -// http://jsperf.com/closure-no-closure/2 - -var CallbackWrapper = function(messageManager, channelName, listenerId, requestId) { - this.callback = this.proxy.bind(this); // bind once - this.init(messageManager, channelName, listenerId, requestId); -}; - -CallbackWrapper.junkyard = []; - -CallbackWrapper.factory = function(messageManager, channelName, listenerId, requestId) { - var wrapper = CallbackWrapper.junkyard.pop(); - if ( wrapper ) { - wrapper.init(messageManager, channelName, listenerId, requestId); - return wrapper; - } - return new CallbackWrapper(messageManager, channelName, listenerId, requestId); -}; - -CallbackWrapper.prototype.init = function(messageManager, channelName, listenerId, requestId) { - this.messageManager = messageManager; - this.channelName = channelName; - this.listenerId = listenerId; - this.requestId = requestId; -}; - -CallbackWrapper.prototype.proxy = function(response) { - var message = JSON.stringify({ - requestId: this.requestId, - channelName: this.channelName, - msg: response !== undefined ? response : null - }); - - if ( this.messageManager.sendAsyncMessage ) { - this.messageManager.sendAsyncMessage(this.listenerId, message); - } else { - this.messageManager.broadcastAsyncMessage(this.listenerId, message); - } - - // Mark for reuse - this.messageManager = - this.channelName = - this.requestId = - this.listenerId = null; - CallbackWrapper.junkyard.push(this); -}; - -/******************************************************************************/ - -var httpRequestHeadersFactory = function(channel) { - var entry = httpRequestHeadersFactory.junkyard.pop(); - if ( entry ) { - return entry.init(channel); - } - return new HTTPRequestHeaders(channel); -}; - -httpRequestHeadersFactory.junkyard = []; - -var HTTPRequestHeaders = function(channel) { - this.init(channel); -}; - -HTTPRequestHeaders.prototype.init = function(channel) { - this.channel = channel; - this.headers = new Array(); - this.originalHeaderNames = new Array(); - channel.visitRequestHeaders({visitHeader: function(name, value) { - this.headers.push({name: name, value: value}); - this.originalHeaderNames.push(name); - }.bind(this)}); - return this; -}; - -HTTPRequestHeaders.prototype.dispose = function() { - this.channel = null; - this.headers = null; - this.originalHeaderNames = null; - httpRequestHeadersFactory.junkyard.push(this); -}; - -HTTPRequestHeaders.prototype.update = function() { - var newHeaderNames = new Set(); - for ( var header of this.headers ) { - this.setHeader(header.name, header.value, true); - newHeaderNames.add(header.name); - } - //Clear any headers that were removed - for ( var name of this.originalHeaderNames ) { - if ( !newHeaderNames.has(name) ) { - this.channel.setRequestHeader(name, '', false); - } - } -} - -HTTPRequestHeaders.prototype.getHeader = function(name) { - try { - return this.channel.getRequestHeader(name); - } catch (e) { - } - return ''; -}; - -HTTPRequestHeaders.prototype.setHeader = function(name, newValue, create) { - var oldValue = this.getHeader(name); - if ( newValue === oldValue ) { - return false; - } - if ( oldValue === '' && create !== true ) { - return false; - } - this.channel.setRequestHeader(name, newValue, false); - return true; -}; - -/******************************************************************************/ - -var httpObserver = { - classDescription: 'net-channel-event-sinks for ' + location.host, - classID: Components.ID('{5d2e2797-6d68-42e2-8aeb-81ce6ba16b95}'), - contractID: '@' + location.host + '/net-channel-event-sinks;1', - REQDATAKEY: location.host + 'reqdata', - ABORT: Components.results.NS_BINDING_ABORTED, - ACCEPT: Components.results.NS_SUCCEEDED, - // Request types: - // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIContentPolicy#Constants - frameTypeMap: { - 6: 'main_frame', - 7: 'sub_frame' - }, - typeMap: { - 1: 'other', - 2: 'script', - 3: 'image', - 4: 'stylesheet', - 5: 'object', - 6: 'main_frame', - 7: 'sub_frame', - 9: 'xbl', - 10: 'ping', - 11: 'xmlhttprequest', - 12: 'object', - 13: 'xml_dtd', - 14: 'font', - 15: 'media', - 16: 'websocket', - 17: 'csp_report', - 18: 'xslt', - 19: 'beacon', - 20: 'xmlhttprequest', - 21: 'imageset', - 22: 'web_manifest' - }, - mimeTypeMap: { - 'audio': 15, - 'video': 15 - }, - - get componentRegistrar() { - return Components.manager.QueryInterface(Ci.nsIComponentRegistrar); - }, - - get categoryManager() { - return Cc['@mozilla.org/categorymanager;1'] + } + + this.channel.setRequestHeader(name, newValue, false); + return true; + }; + + let httpObserver = { + classDescription: 'net-channel-event-sinks for ' + location.host, + classID: Components.ID('{5d2e2797-6d68-42e2-8aeb-81ce6ba16b95}'), + contractID: '@' + location.host + '/net-channel-event-sinks;1', + REQDATAKEY: location.host + 'reqdata', + ABORT: Components.results.NS_BINDING_ABORTED, + ACCEPT: Components.results.NS_SUCCEEDED, + // Request types: + // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIContentPolicy#Constants + // eMatrix: we can just use nsIContentPolicy's built-in + // constants, can we? + frameTypeMap: { + 6: 'main_frame', + 7: 'sub_frame' + }, + typeMap: { + 1: 'other', + 2: 'script', + 3: 'image', + 4: 'stylesheet', + 5: 'object', + 6: 'main_frame', + 7: 'sub_frame', + 9: 'xbl', + 10: 'ping', + 11: 'xmlhttprequest', + 12: 'object', + 13: 'xml_dtd', + 14: 'font', + 15: 'media', + 16: 'websocket', + 17: 'csp_report', + 18: 'xslt', + 19: 'beacon', + 20: 'xmlhttprequest', + 21: 'imageset', + 22: 'web_manifest' + }, + mimeTypeMap: { + 'audio': 15, + 'video': 15 + }, + get componentRegistrar () { + return Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + }, + get categoryManager () { + return Cc['@mozilla.org/categorymanager;1'] .getService(Ci.nsICategoryManager); - }, - - QueryInterface: (function() { - var {XPCOMUtils} = Cu.import('resource://gre/modules/XPCOMUtils.jsm', null); - - return XPCOMUtils.generateQI([ - Ci.nsIFactory, - Ci.nsIObserver, - Ci.nsIChannelEventSink, - Ci.nsISupportsWeakReference - ]); - })(), - - createInstance: function(outer, iid) { - if ( outer ) { - throw Components.results.NS_ERROR_NO_AGGREGATION; - } - - return this.QueryInterface(iid); - }, - - register: function() { - this.pendingRingBufferInit(); - - // https://developer.mozilla.org/en/docs/Observer_Notifications#HTTP_requests - Services.obs.addObserver(this, 'http-on-modify-request', true); - Services.obs.addObserver(this, 'http-on-examine-response', true); - Services.obs.addObserver(this, 'http-on-examine-cached-response', true); + }, + QueryInterface: (function () { + var {XPCOMUtils} = + Cu.import('resource://gre/modules/XPCOMUtils.jsm', null); + + return XPCOMUtils.generateQI([ + Ci.nsIFactory, + Ci.nsIObserver, + Ci.nsIChannelEventSink, + Ci.nsISupportsWeakReference + ]); + })(), + createInstance: function (outer, iid) { + if (outer) { + throw Components.results.NS_ERROR_NO_AGGREGATION; + } + + return this.QueryInterface(iid); + }, + register: function () { + this.pendingRingBufferInit(); + + Services.obs.addObserver(this, 'http-on-modify-request', true); + Services.obs.addObserver(this, 'http-on-examine-response', true); + Services.obs.addObserver(this, 'http-on-examine-cached-response', true); + + // Guard against stale instances not having been unregistered + if (this.componentRegistrar.isCIDRegistered(this.classID)) { + try { + this.componentRegistrar + .unregisterFactory(this.classID, + Components.manager + .getClassObject(this.classID, + Ci.nsIFactory)); + } catch (ex) { + console.error('eMatrix> httpObserver > ' + +'unable to unregister stale instance: ', ex); + } + } - // Guard against stale instances not having been unregistered - if ( this.componentRegistrar.isCIDRegistered(this.classID) ) { - try { - this.componentRegistrar.unregisterFactory(this.classID, Components.manager.getClassObject(this.classID, Ci.nsIFactory)); - } catch (ex) { - console.error('eMatrix> httpObserver > unable to unregister stale instance: ', ex); - } - } - - this.componentRegistrar.registerFactory( - this.classID, - this.classDescription, - this.contractID, - this - ); - this.categoryManager.addCategoryEntry( - 'net-channel-event-sinks', - this.contractID, - this.contractID, - false, - true - ); - }, - - unregister: function() { - Services.obs.removeObserver(this, 'http-on-modify-request'); - Services.obs.removeObserver(this, 'http-on-examine-response'); - Services.obs.removeObserver(this, 'http-on-examine-cached-response'); - - this.componentRegistrar.unregisterFactory(this.classID, this); - this.categoryManager.deleteCategoryEntry( - 'net-channel-event-sinks', - this.contractID, - false - ); - }, - - PendingRequest: function() { - this.rawType = 0; - this.tabId = 0; - this._key = ''; // key is url, from URI.spec - }, - - // If all work fine, this map should not grow indefinitely. It can have - // stale items in it, but these will be taken care of when entries in - // the ring buffer are overwritten. - pendingURLToIndex: new Map(), - pendingWritePointer: 0, - pendingRingBuffer: new Array(256), - pendingRingBufferInit: function() { - // Use and reuse pre-allocated PendingRequest objects = less memory - // churning. - var i = this.pendingRingBuffer.length; - while ( i-- ) { - this.pendingRingBuffer[i] = new this.PendingRequest(); - } - }, - - // Pending request ring buffer: - // +-------+-------+-------+-------+-------+-------+------- - // |0 |1 |2 |3 |4 |5 |... - // +-------+-------+-------+-------+-------+-------+------- - // - // URL to ring buffer index map: - // { k = URL, s = ring buffer indices } - // - // s is a string which character codes map to ring buffer indices -- for - // when the same URL is received multiple times by shouldLoadListener() - // before the existing one is serviced by the network request observer. - // I believe the use of a string in lieu of an array reduces memory - // churning. - - createPendingRequest: function(url) { - var bucket; - var i = this.pendingWritePointer; - this.pendingWritePointer = i + 1 & 255; - var preq = this.pendingRingBuffer[i]; - var si = String.fromCharCode(i); - // Cleanup unserviced pending request - if ( preq._key !== '' ) { - bucket = this.pendingURLToIndex.get(preq._key); - if ( bucket.length === 1 ) { - this.pendingURLToIndex.delete(preq._key); + this.componentRegistrar.registerFactory(this.classID, + this.classDescription, + this.contractID, + this); + this.categoryManager.addCategoryEntry('net-channel-event-sinks', + this.contractID, + this.contractID, + false, + true); + }, + unregister: function () { + Services.obs.removeObserver(this, 'http-on-modify-request'); + Services.obs.removeObserver(this, 'http-on-examine-response'); + Services.obs.removeObserver(this, 'http-on-examine-cached-response'); + + this.componentRegistrar.unregisterFactory(this.classID, this); + this.categoryManager.deleteCategoryEntry('net-channel-event-sinks', + this.contractID, + false); + }, + PendingRequest: function () { + this.rawType = 0; + this.tabId = 0; + this._key = ''; // key is url, from URI.spec + }, + // If all work fine, this map should not grow indefinitely. It + // can have stale items in it, but these will be taken care of + // when entries in the ring buffer are overwritten. + pendingURLToIndex: new Map(), + pendingWritePointer: 0, + pendingRingBuffer: new Array(256), + pendingRingBufferInit: function () { + // Use and reuse pre-allocated PendingRequest objects = + // less memory churning. + for (let i=this.pendingRingBuffer.length-1; i>=0; --i) { + this.pendingRingBuffer[i] = new this.PendingRequest(); + } + }, + createPendingRequest: function (url) { + // Pending request ring buffer: + // +-------+-------+-------+-------+-------+-------+------- + // |0 |1 |2 |3 |4 |5 |... + // +-------+-------+-------+-------+-------+-------+------- + // + // URL to ring buffer index map: + // { k = URL, s = ring buffer indices } + // + // s is a string which character codes map to ring buffer + // indices -- for when the same URL is received multiple times + // by shouldLoadListener() before the existing one is serviced + // by the network request observer. I believe the use of a + // string in lieu of an array reduces memory churning. + let bucket; + let i = this.pendingWritePointer; + this.pendingWritePointer = i + 1 & 255; + + let preq = this.pendingRingBuffer[i]; + let si = String.fromCharCode(i); + + // Cleanup unserviced pending request + if (preq._key !== '') { + bucket = this.pendingURLToIndex.get(preq._key); + if (bucket.length === 1) { + this.pendingURLToIndex.delete(preq._key); + } else { + let pos = bucket.indexOf(si); + this.pendingURLToIndex.set(preq._key, + bucket.slice(0, pos) + + bucket.slice(pos + 1)); + } + } + + bucket = this.pendingURLToIndex.get(url); + this.pendingURLToIndex.set(url, bucket === undefined + ? si + : bucket + si); + preq._key = url; + return preq; + }, + lookupPendingRequest: function (url) { + let bucket = this.pendingURLToIndex.get(url); + if (bucket === undefined) { + return null; + } + + let i = bucket.charCodeAt(0); + if (bucket.length === 1) { + this.pendingURLToIndex.delete(url); } else { - var pos = bucket.indexOf(si); - this.pendingURLToIndex.set(preq._key, bucket.slice(0, pos) + bucket.slice(pos + 1)); - } - } - bucket = this.pendingURLToIndex.get(url); - this.pendingURLToIndex.set(url, bucket === undefined ? si : bucket + si); - preq._key = url; - return preq; - }, - - lookupPendingRequest: function(url) { - var bucket = this.pendingURLToIndex.get(url); - if ( bucket === undefined ) { - return null; - } - var i = bucket.charCodeAt(0); - if ( bucket.length === 1 ) { - this.pendingURLToIndex.delete(url); - } else { - this.pendingURLToIndex.set(url, bucket.slice(1)); - } - var preq = this.pendingRingBuffer[i]; - preq._key = ''; // mark as "serviced" - return preq; - }, - - handleRequest: function(channel, URI, tabId, rawType) { - var type = this.typeMap[rawType] || 'other'; - - var onBeforeRequest = vAPI.net.onBeforeRequest; - if ( onBeforeRequest.types === null || onBeforeRequest.types.has(type) ) { - var result = onBeforeRequest.callback({ - parentFrameId: type === 'main_frame' ? -1 : 0, - tabId: tabId, - type: type, - url: URI.asciiSpec - }); - if ( typeof result === 'object' ) { - channel.cancel(this.ABORT); - return true; - } - } - - var onBeforeSendHeaders = vAPI.net.onBeforeSendHeaders; - if ( onBeforeSendHeaders.types === null || onBeforeSendHeaders.types.has(type) ) { - var requestHeaders = httpRequestHeadersFactory(channel); - var newHeaders = onBeforeSendHeaders.callback({ - parentFrameId: type === 'main_frame' ? -1 : 0, - requestHeaders: requestHeaders.headers, - tabId: tabId, - type: type, - url: URI.asciiSpec, - method: channel.requestMethod - }); - if ( newHeaders ) { - requestHeaders.update(); + this.pendingURLToIndex.set(url, bucket.slice(1)); + } + + let preq = this.pendingRingBuffer[i]; + preq._key = ''; // mark as "serviced" + return preq; + }, + handleRequest: function (channel, URI, tabId, rawType) { + let type = this.typeMap[rawType] || 'other'; + + let onBeforeRequest = vAPI.net.onBeforeRequest; + if (onBeforeRequest.types === null + || onBeforeRequest.types.has(type)) { + letresult = onBeforeRequest.callback({ + parentFrameId: type === 'main_frame' ? -1 : 0, + tabId: tabId, + type: type, + url: URI.asciiSpec + }); + + if (typeof result === 'object') { + channel.cancel(this.ABORT); + return true; + } } - requestHeaders.dispose(); - } - return false; - }, + let onBeforeSendHeaders = vAPI.net.onBeforeSendHeaders; + if (onBeforeSendHeaders.types === null + || onBeforeSendHeaders.types.has(type)) { + let requestHeaders = httpRequestHeadersFactory(channel); + let newHeaders = onBeforeSendHeaders.callback({ + parentFrameId: type === 'main_frame' ? -1 : 0, + requestHeaders: requestHeaders.headers, + tabId: tabId, + type: type, + url: URI.asciiSpec, + method: channel.requestMethod + }); + + if (newHeaders) { + requestHeaders.update(); + } + requestHeaders.dispose(); + } - channelDataFromChannel: function(channel) { - if ( channel instanceof Ci.nsIWritablePropertyBag ) { - try { - return channel.getProperty(this.REQDATAKEY) || null; - } catch (ex) { + return false; + }, + channelDataFromChannel: function (channel) { + if (channel instanceof Ci.nsIWritablePropertyBag) { + try { + return channel.getProperty(this.REQDATAKEY) || null; + } catch (ex) { + // Ignore + } } - } - return null; - }, - - // https://github.com/gorhill/uMatrix/issues/165 - // https://developer.mozilla.org/en-US/Firefox/Releases/3.5/Updating_extensions#Getting_a_load_context_from_a_request - // Not sure `ematrix:shouldLoad` is still needed, eMatrix does not - // care about embedded frames topography. - // Also: - // https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Limitations_of_chrome_scripts - tabIdFromChannel: function(channel) { - var lc; - try { - lc = channel.notificationCallbacks.getInterface(Ci.nsILoadContext); - } catch(ex) { - } - if ( !lc ) { + + return null; + }, + // https://github.com/gorhill/uMatrix/issues/165 + // https://developer.mozilla.org/en-US/Firefox/Releases/3.5/Updating_extensions#Getting_a_load_context_from_a_request + // Not sure `ematrix:shouldLoad` is still needed, eMatrix does + // not care about embedded frames topography. + // Also: + // https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Limitations_of_chrome_scripts + tabIdFromChannel: function (channel) { + let lc; try { - lc = channel.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext); + lc = channel.notificationCallbacks.getInterface(Ci.nsILoadContext); } catch(ex) { + // Ignore + } + + if (!lc) { + try { + lc = channel.loadGroup.notificationCallbacks + .getInterface(Ci.nsILoadContext); + } catch(ex) { + // Ignore + } + + if (!lc) { + return vAPI.noTabId; + } } - if ( !lc ) { - return vAPI.noTabId; - } - } - if ( lc.topFrameElement ) { - return tabWatcher.tabIdFromTarget(lc.topFrameElement); - } - var win; - try { - win = lc.associatedWindow; - } catch (ex) { } - if ( !win ) { - return vAPI.noTabId; - } - if ( win.top ) { - win = win.top; - } - var tabBrowser; - try { - tabBrowser = getTabBrowser( - win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShell).rootTreeItem - .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow) - ); - } catch (ex) { } - if ( !tabBrowser ) { - return vAPI.noTabId; - } - if ( tabBrowser.getBrowserForContentWindow ) { - return tabWatcher.tabIdFromTarget(tabBrowser.getBrowserForContentWindow(win)); - } - // Falling back onto _getTabForContentWindow to ensure older versions - // of Firefox work well. - return tabBrowser._getTabForContentWindow ? - tabWatcher.tabIdFromTarget(tabBrowser._getTabForContentWindow(win)) : - vAPI.noTabId; - }, - - rawtypeFromContentType: function(channel) { - var mime = channel.contentType; - if ( !mime ) { - return 0; - } - var pos = mime.indexOf('/'); - if ( pos === -1 ) { - pos = mime.length; - } - return this.mimeTypeMap[mime.slice(0, pos)] || 0; - }, - - observe: function(channel, topic) { - if ( channel instanceof Ci.nsIHttpChannel === false ) { - return; - } - - var URI = channel.URI; - var channelData = this.channelDataFromChannel(channel); - - if ( topic.lastIndexOf('http-on-examine-', 0) === 0 ) { - if ( channelData === null ) { - return; - } - - var type = this.frameTypeMap[channelData[1]]; - if ( !type ) { - return; + + if (lc.topFrameElement) { + return tabWatcher.tabIdFromTarget(lc.topFrameElement); } + + let win; + try { + win = lc.associatedWindow; + } catch (ex) { + // Ignore + } + + if (!win) { + return vAPI.noTabId; + } + + if (win.top) { + win = win.top; + } + + let tabBrowser; + try { + tabBrowser = + getTabBrowser(win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell).rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow)); + } catch (ex) { + // Ignore + } + + if (!tabBrowser) { + return vAPI.noTabId; + } + + if (tabBrowser.getBrowserForContentWindow) { + return tabWatcher.tabIdFromTarget(tabBrowser.getBrowserForContentWindow(win)); + } + + // Falling back onto _getTabForContentWindow to ensure older + // versions of Firefox work well. + return tabBrowser._getTabForContentWindow + ? tabWatcher.tabIdFromTarget(tabBrowser + ._getTabForContentWindow(win)) + : vAPI.noTabId; + }, + rawtypeFromContentType: function (channel) { + let mime = channel.contentType; + if (!mime) { + return 0; + } + + let pos = mime.indexOf('/'); + if (pos === -1) { + pos = mime.length; + } + + return this.mimeTypeMap[mime.slice(0, pos)] || 0; + }, + observe: function (channel, topic) { + if (channel instanceof Ci.nsIHttpChannel === false) { + return; + } + + let URI = channel.URI; + let channelData = this.channelDataFromChannel(channel); + + if (topic.lastIndexOf('http-on-examine-', 0) === 0) { + if (channelData === null) { + return; + } - //topic = ['Content-Security-Policy', 'Content-Security-Policy-Report-Only']; - // Can send empty responseHeaders as these headers are only added to and then merged. - - // TODO: Find better place for this, needs to be set before onHeadersReceived.callback. - // Web workers not blocked in Pale Moon as child-src currently unavailable, see: - // https://github.com/MoonchildProductions/Pale-Moon/issues/949 - if ( ηMatrix.cspNoWorker === undefined ) { - ηMatrix.cspNoWorker = "child-src 'none'; frame-src data: blob: *; report-uri about:blank"; - } + let type = this.frameTypeMap[channelData[1]]; + if (!type) { + return; + } - var result = vAPI.net.onHeadersReceived.callback({ - parentFrameId: type === 'main_frame' ? -1 : 0, - responseHeaders: [], - tabId: channelData[0], - type: type, - url: URI.asciiSpec - }); + // topic = ['Content-Security-Policy', + // 'Content-Security-Policy-Report-Only']; + // + // Can send empty responseHeaders as these headers are + // only added to and then merged. + // + // TODO: Find better place for this, needs to be set + // before onHeadersReceived.callback. Web workers not + // blocked in Pale Moon as child-src currently + // unavailable, see: + // + // https://github.com/MoonchildProductions/Pale-Moon/issues/949 + // + // eMatrix: as of Pale Moon 28 it seems child-src is + // available and depracated(?) + if (ηMatrix.cspNoWorker === undefined) { + ηMatrix.cspNoWorker = "child-src 'none'; " + +"frame-src data: blob: *; " + +"report-uri about:blank"; + } - if ( result ) { - for ( let header of result.responseHeaders ) { - channel.setResponseHeader( - header.name, - header.value, - true - ); - } + let result = vAPI.net.onHeadersReceived.callback({ + parentFrameId: type === 'main_frame' ? -1 : 0, + responseHeaders: [], + tabId: channelData[0], + type: type, + url: URI.asciiSpec + }); + + if (result) { + for (let header of result.responseHeaders) { + channel.setResponseHeader(header.name, + header.value, + true); + } + } + + return; + } + + // http-on-modify-request + // The channel was previously serviced. + if (channelData !== null) { + this.handleRequest(channel, URI, + channelData[0], channelData[1]); + return; + } + + // The channel was never serviced. + let tabId; + let pendingRequest = this.lookupPendingRequest(URI.asciiSpec); + let rawType = 1; + let loadInfo = channel.loadInfo; + + // https://github.com/gorhill/uMatrix/issues/390#issuecomment-155717004 + if (loadInfo) { + rawType = (loadInfo.externalContentPolicyType !== undefined) + ? loadInfo.externalContentPolicyType + : loadInfo.contentPolicyType; + if (!rawType) { + rawType = 1; + } } - return; - } - - // http-on-modify-request - - // The channel was previously serviced. - if ( channelData !== null ) { - this.handleRequest(channel, URI, channelData[0], channelData[1]); - return; - } - - // The channel was never serviced. - var tabId; - var pendingRequest = this.lookupPendingRequest(URI.asciiSpec); - var rawType = 1; - var loadInfo = channel.loadInfo; - - // https://github.com/gorhill/uMatrix/issues/390#issuecomment-155717004 - if ( loadInfo ) { - rawType = loadInfo.externalContentPolicyType !== undefined ? - loadInfo.externalContentPolicyType : - loadInfo.contentPolicyType; - if ( !rawType ) { - rawType = 1; - } - } - - if ( pendingRequest !== null ) { - tabId = pendingRequest.tabId; - // https://github.com/gorhill/uBlock/issues/654 - // Use the request type from the HTTP observer point of view. - if ( rawType !== 1 ) { - pendingRequest.rawType = rawType; + if (pendingRequest !== null) { + tabId = pendingRequest.tabId; + // https://github.com/gorhill/uBlock/issues/654 + // Use the request type from the HTTP observer point of view. + if (rawType !== 1) { + pendingRequest.rawType = rawType; + } else { + rawType = pendingRequest.rawType; + } } else { - rawType = pendingRequest.rawType; - } - } else { - tabId = this.tabIdFromChannel(channel); - } - - if ( this.handleRequest(channel, URI, tabId, rawType) ) { - return; - } - - if ( channel instanceof Ci.nsIWritablePropertyBag === false ) { - return; - } - - // Carry data for behind-the-scene redirects - channel.setProperty(this.REQDATAKEY, [tabId, rawType]); - }, - - // contentPolicy.shouldLoad doesn't detect redirects, this needs to be used - asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) { - // If error thrown, the redirect will fail - try { - var URI = newChannel.URI; - if ( !URI.schemeIs('http') && !URI.schemeIs('https') ) { - return; + tabId = this.tabIdFromChannel(channel); } - if ( newChannel instanceof Ci.nsIWritablePropertyBag === false ) { - return; + if (this.handleRequest(channel, URI, tabId, rawType)) { + return; } - var channelData = this.channelDataFromChannel(oldChannel); - if ( channelData === null ) { - return; + if (channel instanceof Ci.nsIWritablePropertyBag === false) { + return; } - // Carry the data on in case of multiple redirects - newChannel.setProperty(this.REQDATAKEY, channelData); - } catch (ex) { - // console.error(ex); - } finally { - callback.onRedirectVerifyCallback(this.ACCEPT); - } - } -}; - -/******************************************************************************/ + // Carry data for behind-the-scene redirects + channel.setProperty(this.REQDATAKEY, [tabId, rawType]); + }, + asyncOnChannelRedirect: function (oldChannel, newChannel, + flags, callback) { + // contentPolicy.shouldLoad doesn't detect redirects, this + // needs to be used + // If error thrown, the redirect will fail + try { + let URI = newChannel.URI; + if (!URI.schemeIs('http') && !URI.schemeIs('https')) { + return; + } -vAPI.net = {}; + if (newChannel instanceof Ci.nsIWritablePropertyBag === false) { + return; + } -/******************************************************************************/ + let channelData = this.channelDataFromChannel(oldChannel); + if (channelData === null) { + return; + } -vAPI.net.registerListeners = function() { - this.onBeforeRequest.types = this.onBeforeRequest.types ? - new Set(this.onBeforeRequest.types) : - null; - this.onBeforeSendHeaders.types = this.onBeforeSendHeaders.types ? - new Set(this.onBeforeSendHeaders.types) : - null; - - var shouldLoadListenerMessageName = location.host + ':shouldLoad'; - var shouldLoadListener = function(e) { - var details = e.data; - var pendingReq = httpObserver.createPendingRequest(details.url); - pendingReq.rawType = details.rawType; - pendingReq.tabId = tabWatcher.tabIdFromTarget(e.target); + // Carry the data on in case of multiple redirects + newChannel.setProperty(this.REQDATAKEY, channelData); + } catch (ex) { + // console.error(ex); + // Ignore + } finally { + callback.onRedirectVerifyCallback(this.ACCEPT); + } + } }; - // https://github.com/gorhill/uMatrix/issues/200 - // We need this only for Firefox 34 and less: the tab id is derived from - // the origin of the message. - if ( !vAPI.modernFirefox ) { - vAPI.messaging.globalMessageManager.addMessageListener( - shouldLoadListenerMessageName, - shouldLoadListener - ); - } - - httpObserver.register(); + vAPI.net = {}; - cleanupTasks.push(function() { - if ( !vAPI.modernFirefox ) { - vAPI.messaging.globalMessageManager.removeMessageListener( - shouldLoadListenerMessageName, - shouldLoadListener - ); - } - - httpObserver.unregister(); - }); -}; - -/******************************************************************************/ -/******************************************************************************/ - -vAPI.toolbarButton = { - id: location.host + '-button', - type: 'view', - viewId: location.host + '-panel', - label: vAPI.app.name, - tooltiptext: vAPI.app.name, - tabs: {/*tabId: {badge: 0, img: boolean}*/}, - init: null, - codePath: '' -}; - -/******************************************************************************/ - -// Non-Fennec: common code paths. + vAPI.net.registerListeners = function () { + this.onBeforeRequest.types = this.onBeforeRequest.types + ? new Set(this.onBeforeRequest.types) + : null; + + this.onBeforeSendHeaders.types = this.onBeforeSendHeaders.types + ? new Set(this.onBeforeSendHeaders.types) + : null; + + let shouldLoadListenerMessageName = location.host + ':shouldLoad'; + let shouldLoadListener = function (e) { + let details = e.data; + let pendingReq = httpObserver.createPendingRequest(details.url); + pendingReq.rawType = details.rawType; + pendingReq.tabId = tabWatcher.tabIdFromTarget(e.target); + }; + + // https://github.com/gorhill/uMatrix/issues/200 + // We need this only for Firefox 34 and less: the tab id is derived from + // the origin of the message. + if (!vAPI.modernFirefox) { + vAPI.messaging.globalMessageManager + .addMessageListener(shouldLoadListenerMessageName, + shouldLoadListener); + } -(function() { - if ( vAPI.fennec ) { - return; - } + httpObserver.register(); - var tbb = vAPI.toolbarButton; - var popupCommittedWidth = 0; - var popupCommittedHeight = 0; + cleanupTasks.push(function () { + if (!vAPI.modernFirefox) { + vAPI.messaging.globalMessageManager + .removeMessageListener(shouldLoadListenerMessageName, + shouldLoadListener); + } - tbb.onViewShowing = function({target}) { - popupCommittedWidth = popupCommittedHeight = 0; - target.firstChild.setAttribute('src', vAPI.getURL('popup.html')); + httpObserver.unregister(); + }); }; - tbb.onViewHiding = function({target}) { - target.parentNode.style.maxWidth = ''; - target.firstChild.setAttribute('src', 'about:blank'); + vAPI.toolbarButton = { + id: location.host + '-button', + type: 'view', + viewId: location.host + '-panel', + label: vAPI.app.name, + tooltiptext: vAPI.app.name, + tabs: {/*tabId: {badge: 0, img: boolean}*/}, + init: null, + codePath: '' }; - tbb.updateState = function(win, tabId) { - var button = win.document.getElementById(this.id); - - if ( !button ) { + // Non-Fennec: common code paths. + (function () { + if (vAPI.fennec) { return; - } - - var icon = this.tabs[tabId]; - button.setAttribute('badge', icon && icon.badge || ''); - button.classList.toggle('off', !icon || !icon.img); - - var iconId = icon && icon.img ? icon.img : 'off'; - icon = 'url(' + vAPI.getURL('img/browsericons/icon19-' + iconId + '.png') + ')'; - button.style.listStyleImage = icon; - }; - - tbb.populatePanel = function(doc, panel) { - panel.setAttribute('id', this.viewId); - - var iframe = doc.createElement('iframe'); - iframe.setAttribute('type', 'content'); + } - panel.appendChild(iframe); + let tbb = vAPI.toolbarButton; + let popupCommittedWidth = 0; + let popupCommittedHeight = 0; - var toPx = function(pixels) { - return pixels.toString() + 'px'; - }; + tbb.onViewShowing = function (target) { + popupCommittedWidth = popupCommittedHeight = 0; + target.firstChild.setAttribute('src', vAPI.getURL('popup.html')); + }; - var scrollBarWidth = 0; - var resizeTimer = null; + tbb.onViewHiding = function (target) { + target.parentNode.style.maxWidth = ''; + target.firstChild.setAttribute('src', 'about:blank'); + }; - var resizePopupDelayed = function(attempts) { - if ( resizeTimer !== null ) { - return false; - } + tbb.updateState = function (win, tabId) { + let button = win.document.getElementById(this.id); - // Sanity check - attempts = (attempts || 0) + 1; - if ( attempts > 1/*000*/ ) { - //console.error('eMatrix> resizePopupDelayed: giving up after too many attempts'); - return false; + if (!button) { + return; } - resizeTimer = vAPI.setTimeout(resizePopup, 10, attempts); - return true; - }; - - var resizePopup = function(attempts) { - resizeTimer = null; + let icon = this.tabs[tabId]; + button.setAttribute('badge', icon && icon.badge || ''); + button.classList.toggle('off', !icon || !icon.img); - panel.parentNode.style.maxWidth = 'none'; - var body = iframe.contentDocument.body; + let iconId = icon && icon.img ? icon.img : 'off'; + icon = 'url(' + vAPI.getURL('img/browsericons/icon19-' + iconId + '.png') + ')'; + button.style.listStyleImage = icon; + }; - // https://github.com/gorhill/uMatrix/issues/301 - // Don't resize if committed size did not change. - if ( - popupCommittedWidth === body.clientWidth && - popupCommittedHeight === body.clientHeight - ) { - return; - } + tbb.populatePanel = function (doc, panel) { + panel.setAttribute('id', this.viewId); - // We set a limit for height - var height = Math.min(body.clientHeight, 600); + let iframe = doc.createElement('iframe'); + iframe.setAttribute('type', 'content'); - // https://github.com/chrisaljoudi/uBlock/issues/730 - // Voodoo programming: this recipe works - panel.style.setProperty('height', toPx(height)); - iframe.style.setProperty('height', toPx(height)); + panel.appendChild(iframe); - // Adjust width for presence/absence of vertical scroll bar which may - // have appeared as a result of last operation. - var contentWindow = iframe.contentWindow; - var width = body.clientWidth; - if ( contentWindow.scrollMaxY !== 0 ) { - width += scrollBarWidth; - } - panel.style.setProperty('width', toPx(width)); + let toPx = function (pixels) { + return pixels.toString() + 'px'; + }; - // scrollMaxX should always be zero once we know the scrollbar width - if ( contentWindow.scrollMaxX !== 0 ) { - scrollBarWidth = contentWindow.scrollMaxX; - width += scrollBarWidth; - panel.style.setProperty('width', toPx(width)); - } + let scrollBarWidth = 0; + let resizeTimer = null; - if ( iframe.clientHeight !== height || panel.clientWidth !== width ) { - if ( resizePopupDelayed(attempts) ) { - return; - } - // resizePopupDelayed won't be called again, so commit - // dimentsions. - } - - popupCommittedWidth = body.clientWidth; - popupCommittedHeight = body.clientHeight; - }; - - var onResizeRequested = function() { - var body = iframe.contentDocument.body; - if ( body.getAttribute('data-resize-popup') !== 'true' ) { - return; - } - body.removeAttribute('data-resize-popup'); - resizePopupDelayed(); - }; - - var onPopupReady = function() { - var win = this.contentWindow; + let resizePopupDelayed = function (attempts) { + if (resizeTimer !== null) { + return false; + } - if ( !win || win.location.host !== location.host ) { - return; - } + // Sanity check + attempts = (attempts || 0) + 1; + if ( attempts > 1/*000*/ ) { + //console.error('eMatrix> resizePopupDelayed: giving up after too many attempts'); + return false; + } - if ( typeof tbb.onBeforePopupReady === 'function' ) { - tbb.onBeforePopupReady.call(this); - } + resizeTimer = vAPI.setTimeout(resizePopup, 10, attempts); + return true; + }; - resizePopupDelayed(); + let resizePopup = function (attempts) { + resizeTimer = null; - var body = win.document.body; - body.removeAttribute('data-resize-popup'); - var mutationObserver = new win.MutationObserver(onResizeRequested); - mutationObserver.observe(body, { - attributes: true, - attributeFilter: [ 'data-resize-popup' ] - }); - }; + panel.parentNode.style.maxWidth = 'none'; + let body = iframe.contentDocument.body; - iframe.addEventListener('load', onPopupReady, true); - }; -})(); + // https://github.com/gorhill/uMatrix/issues/301 + // Don't resize if committed size did not change. + if (popupCommittedWidth === body.clientWidth + && popupCommittedHeight === body.clientHeight) { + return; + } -/******************************************************************************/ + // We set a limit for height + let height = Math.min(body.clientHeight, 600); -(function() { - // Add toolbar button for not-Basilisk - if (Services.appinfo.ID === "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") { - return; - } - - var tbb = vAPI.toolbarButton; - if ( tbb.init !== null ) { - return; - } + // https://github.com/chrisaljoudi/uBlock/issues/730 + // Voodoo programming: this recipe works + panel.style.setProperty('height', toPx(height)); + iframe.style.setProperty('height', toPx(height)); - tbb.codePath = 'legacy'; - tbb.viewId = tbb.id + '-panel'; + // Adjust width for presence/absence of vertical scroll bar which may + // have appeared as a result of last operation. + let contentWindow = iframe.contentWindow; + let width = body.clientWidth; + if (contentWindow.scrollMaxY !== 0) { + width += scrollBarWidth; + } + panel.style.setProperty('width', toPx(width)); - var styleSheetUri = null; + // scrollMaxX should always be zero once we know the scrollbar width + if (contentWindow.scrollMaxX !== 0) { + scrollBarWidth = contentWindow.scrollMaxX; + width += scrollBarWidth; + panel.style.setProperty('width', toPx(width)); + } - var createToolbarButton = function(window) { - var document = window.document; + if (iframe.clientHeight !== height + || panel.clientWidth !== width) { + if (resizePopupDelayed(attempts)) { + return; + } + // resizePopupDelayed won't be called again, so commit + // dimentsions. + } - var toolbarButton = document.createElement('toolbarbutton'); - toolbarButton.setAttribute('id', tbb.id); - // type = panel would be more accurate, but doesn't look as good - toolbarButton.setAttribute('type', 'menu'); - toolbarButton.setAttribute('removable', 'true'); - toolbarButton.setAttribute('class', 'toolbarbutton-1 chromeclass-toolbar-additional'); - toolbarButton.setAttribute('label', tbb.label); - toolbarButton.setAttribute('tooltiptext', tbb.tooltiptext); + popupCommittedWidth = body.clientWidth; + popupCommittedHeight = body.clientHeight; + }; - var toolbarButtonPanel = document.createElement('panel'); - // NOTE: Setting level to parent breaks the popup for PaleMoon under - // linux (mouse pointer misaligned with content). For some reason. - // toolbarButtonPanel.setAttribute('level', 'parent'); - tbb.populatePanel(document, toolbarButtonPanel); - toolbarButtonPanel.addEventListener('popupshowing', tbb.onViewShowing); - toolbarButtonPanel.addEventListener('popuphiding', tbb.onViewHiding); - toolbarButton.appendChild(toolbarButtonPanel); + let onResizeRequested = function () { + let body = iframe.contentDocument.body; + if (body.getAttribute('data-resize-popup') !== 'true') { + return; + } + body.removeAttribute('data-resize-popup'); + resizePopupDelayed(); + }; - toolbarButtonPanel.setAttribute('tooltiptext', ''); + let onPopupReady = function () { + let win = this.contentWindow; - return toolbarButton; - }; + if (!win || win.location.host !== location.host) { + return; + } - var addLegacyToolbarButton = function(window) { - // eMatrix's stylesheet lazily added. - if ( styleSheetUri === null ) { - var sss = Cc["@mozilla.org/content/style-sheet-service;1"] - .getService(Ci.nsIStyleSheetService); - styleSheetUri = Services.io.newURI(vAPI.getURL("css/legacy-toolbar-button.css"), null, null); + if (typeof tbb.onBeforePopupReady === 'function') { + tbb.onBeforePopupReady.call(this); + } - // Register global so it works in all windows, including palette - if ( !sss.sheetRegistered(styleSheetUri, sss.AUTHOR_SHEET) ) { - sss.loadAndRegisterSheet(styleSheetUri, sss.AUTHOR_SHEET); - } - } + resizePopupDelayed(); + + let body = win.document.body; + body.removeAttribute('data-resize-popup'); + + let mutationObserver = + new win.MutationObserver(onResizeRequested); + + mutationObserver.observe(body, { + attributes: true, + attributeFilter: [ 'data-resize-popup' ] + }); + }; + + iframe.addEventListener('load', onPopupReady, true); + }; + })(); - var document = window.document; + /******************************************************************************/ - // https://github.com/gorhill/uMatrix/issues/357 - // Already installed? - if ( document.getElementById(tbb.id) !== null ) { + (function () { + // Add toolbar button for not-Basilisk + if (Services.appinfo.ID === "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") { + return; + } + + let tbb = vAPI.toolbarButton; + if (tbb.init !== null) { return; - } + } - var toolbox = document.getElementById('navigator-toolbox') || - document.getElementById('mail-toolbox'); - if ( toolbox === null ) { - return; - } - - var toolbarButton = createToolbarButton(window); - - // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/toolbarpalette - var palette = toolbox.palette; - if ( palette && palette.querySelector('#' + tbb.id) === null ) { - palette.appendChild(toolbarButton); - } - - // Find the place to put the button. - // Pale Moon: `toolbox.externalToolbars` can be undefined. Seen while - // testing popup test number 3: - // http://raymondhill.net/ublock/popup.html - var toolbars = toolbox.externalToolbars ? toolbox.externalToolbars.slice() : []; - for ( var child of toolbox.children ) { - if ( child.localName === 'toolbar' ) { - toolbars.push(child); - } - } - - for ( var toolbar of toolbars ) { - var currentsetString = toolbar.getAttribute('currentset'); - if ( !currentsetString ) { - continue; - } - var currentset = currentsetString.split(/\s*,\s*/); - var index = currentset.indexOf(tbb.id); - if ( index === -1 ) { - continue; - } - // This can occur with Pale Moon: - // "TypeError: toolbar.insertItem is not a function" - if ( typeof toolbar.insertItem !== 'function' ) { - continue; - } - // Found our button on this toolbar - but where on it? - var before = null; - for ( var i = index + 1; i < currentset.length; i++ ) { - // The [id=...] notation doesn't work on - // space elements as they get a random ID each session - // (or something like that) - // https://gitlab.com/vannilla/ematrix/issues/5 - // https://gitlab.com/vannilla/ematrix/issues/6 - - // Based on JustOff's snippet from the Pale Moon forum. - // It was reorganized because I find it more readable like this, - // but he did most of the work. - let space = /^(spring|spacer|separator)$/.exec(currentset[i]); - if (space !== null) { - let elems = toolbar.querySelectorAll('toolbar'+space[1]); - - let count = currentset.slice(i-currentset.length) - .filter(function (x) {return x == space[1];}) - .length; - - before = - toolbar.querySelector('[id="' - + elems[elems.length-count].id - + '"]'); - } else { - before = toolbar.querySelector('[id="'+currentset[i]+'"]'); + tbb.codePath = 'legacy'; + tbb.viewId = tbb.id + '-panel'; + + let styleSheetUri = null; + + let createToolbarButton = function (window) { + let document = window.document; + + let toolbarButton = document.createElement('toolbarbutton'); + toolbarButton.setAttribute('id', tbb.id); + // type = panel would be more accurate, but doesn't look as good + toolbarButton.setAttribute('type', 'menu'); + toolbarButton.setAttribute('removable', 'true'); + toolbarButton.setAttribute('class', 'toolbarbutton-1 ' + +'chromeclass-toolbar-additional'); + toolbarButton.setAttribute('label', tbb.label); + toolbarButton.setAttribute('tooltiptext', tbb.tooltiptext); + + let toolbarButtonPanel = document.createElement('panel'); + // NOTE: Setting level to parent breaks the popup for PaleMoon under + // linux (mouse pointer misaligned with content). For some reason. + // eMatrix: TODO check if it's still true + // toolbarButtonPanel.setAttribute('level', 'parent'); + tbb.populatePanel(document, toolbarButtonPanel); + toolbarButtonPanel.addEventListener('popupshowing', + tbb.onViewShowing); + toolbarButtonPanel.addEventListener('popuphiding', + tbb.onViewHiding); + toolbarButton.appendChild(toolbarButtonPanel); + + toolbarButtonPanel.setAttribute('tooltiptext', ''); + + return toolbarButton; + }; + + let addLegacyToolbarButton = function (window) { + // eMatrix's stylesheet lazily added. + if (styleSheetUri === null) { + var sss = Cc["@mozilla.org/content/style-sheet-service;1"] + .getService(Ci.nsIStyleSheetService); + styleSheetUri = Services.io + .newURI(vAPI.getURL("css/legacy-toolbar-button.css"), + null, null); + + // Register global so it works in all windows, including palette + if (!sss.sheetRegistered(styleSheetUri, sss.AUTHOR_SHEET)) { + sss.loadAndRegisterSheet(styleSheetUri, sss.AUTHOR_SHEET); } - if ( before !== null ) { - break; - } } - // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Method/insertItem - toolbar.insertItem(tbb.id, before); - break; - } - // https://github.com/gorhill/uBlock/issues/763 - // We are done if our toolbar button is already installed in one of the - // toolbar. - if ( palette !== null && toolbarButton.parentElement !== palette ) { - return; - } - - // No button yet so give it a default location. If forcing the button, - // just put in in the palette rather than on any specific toolbar (who - // knows what toolbars will be available or visible!) - var navbar = document.getElementById('nav-bar'); - if ( navbar !== null && !vAPI.localStorage.getBool('legacyToolbarButtonAdded') ) { - // https://github.com/gorhill/uBlock/issues/264 - // Find a child customizable palette, if any. - navbar = navbar.querySelector('.customization-target') || navbar; - navbar.appendChild(toolbarButton); - navbar.setAttribute('currentset', navbar.currentSet); - document.persist(navbar.id, 'currentset'); - vAPI.localStorage.setBool('legacyToolbarButtonAdded', 'true'); - } - }; - - var canAddLegacyToolbarButton = function(window) { - var document = window.document; - if ( - !document || - document.readyState !== 'complete' || - document.getElementById('nav-bar') === null - ) { - return false; - } - var toolbox = document.getElementById('navigator-toolbox') || - document.getElementById('mail-toolbox'); - return toolbox !== null && !!toolbox.palette; - }; - - var onPopupCloseRequested = function({target}) { - var document = target.ownerDocument; - if ( !document ) { - return; - } - var toolbarButtonPanel = document.getElementById(tbb.viewId); - if ( toolbarButtonPanel === null ) { - return; - } - // `hidePopup` reported as not existing while testing legacy button - // on FF 41.0.2. - // https://bugzilla.mozilla.org/show_bug.cgi?id=1151796 - if ( typeof toolbarButtonPanel.hidePopup === 'function' ) { - toolbarButtonPanel.hidePopup(); - } - }; + let document = window.document; - var shutdown = function() { - for ( var win of winWatcher.getWindows() ) { - var toolbarButton = win.document.getElementById(tbb.id); - if ( toolbarButton ) { - toolbarButton.parentNode.removeChild(toolbarButton); + // https://github.com/gorhill/uMatrix/issues/357 + // Already installed? + if (document.getElementById(tbb.id) !== null) { + return; } - } - if ( styleSheetUri !== null ) { - var sss = Cc["@mozilla.org/content/style-sheet-service;1"] - .getService(Ci.nsIStyleSheetService); - if ( sss.sheetRegistered(styleSheetUri, sss.AUTHOR_SHEET) ) { - sss.unregisterSheet(styleSheetUri, sss.AUTHOR_SHEET); + let toolbox = document.getElementById('navigator-toolbox') + || document.getElementById('mail-toolbox'); + + if (toolbox === null) { + return; } - styleSheetUri = null; - } - vAPI.messaging.globalMessageManager.removeMessageListener( - location.host + ':closePopup', - onPopupCloseRequested - ); - }; - - tbb.attachToNewWindow = function(win) { - deferUntil( - canAddLegacyToolbarButton.bind(null, win), - addLegacyToolbarButton.bind(null, win) - ); - }; + let toolbarButton = createToolbarButton(window); - tbb.init = function() { - vAPI.messaging.globalMessageManager.addMessageListener( - location.host + ':closePopup', - onPopupCloseRequested - ); - - cleanupTasks.push(shutdown); - }; -})(); - -/******************************************************************************/ - -/* -(function() { - // Add toolbar button for Basilisk - if (Services.appinfo.ID !== "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") { - return; - } - - var tbb = vAPI.toolbarButton; - if ( tbb.init !== null ) { - return; - } - - if ( Services.vc.compare(Services.appinfo.version, '36.0') >= 0 ) { - return null; - } - var CustomizableUI = null; - try { - CustomizableUI = Cu.import('resource:///modules/CustomizableUI.jsm', null).CustomizableUI; - } catch (ex) { - } - if ( CustomizableUI === null ) { - return; - } - tbb.codePath = 'australis'; - tbb.CustomizableUI = CustomizableUI; - tbb.defaultArea = CustomizableUI.AREA_NAVBAR; - - var styleURI = null; + let palette = toolbox.palette; + if (palette && palette.querySelector('#' + tbb.id) === null) { + palette.appendChild(toolbarButton); + } - var onPopupCloseRequested = function({target}) { - if ( typeof tbb.closePopup === 'function' ) { - tbb.closePopup(target); - } - }; + // Find the place to put the button. + // Pale Moon: `toolbox.externalToolbars` can be + // undefined. Seen while testing popup test number 3: + // http://raymondhill.net/ublock/popup.html + let toolbars = toolbox.externalToolbars + ? toolbox.externalToolbars.slice() + : []; + + for (let child of toolbox.children) { + if (child.localName === 'toolbar') { + toolbars.push(child); + } + } - var shutdown = function() { - CustomizableUI.destroyWidget(tbb.id); - - for ( var win of winWatcher.getWindows() ) { - var panel = win.document.getElementById(tbb.viewId); - panel.parentNode.removeChild(panel); - win.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils) - .removeSheet(styleURI, 1); - } - - vAPI.messaging.globalMessageManager.removeMessageListener( - location.host + ':closePopup', - onPopupCloseRequested - ); - }; + for (let toolbar of toolbars) { + let currentsetString = toolbar.getAttribute('currentset'); + if (!currentsetString) { + continue; + } + + let currentset = currentsetString.split(/\s*,\s*/); + let index = currentset.indexOf(tbb.id); + if (index === -1) { + continue; + } + + // This can occur with Pale Moon: + // "TypeError: toolbar.insertItem is not a function" + if (typeof toolbar.insertItem !== 'function') { + continue; + } + + // Found our button on this toolbar - but where on it? + let before = null; + for (let i = index+1; i= 0 ) { + return null; + } + var CustomizableUI = null; + try { + CustomizableUI = Cu.import('resource:///modules/CustomizableUI.jsm', null).CustomizableUI; + } catch (ex) { + } + if ( CustomizableUI === null ) { + return; + } + tbb.codePath = 'australis'; + tbb.CustomizableUI = CustomizableUI; + tbb.defaultArea = CustomizableUI.AREA_NAVBAR; + + var styleURI = null; + + var onPopupCloseRequested = function({target}) { + if ( typeof tbb.closePopup === 'function' ) { + tbb.closePopup(target); + } + }; + + var shutdown = function() { + CustomizableUI.destroyWidget(tbb.id); + + for ( var win of winWatcher.getWindows() ) { + var panel = win.document.getElementById(tbb.viewId); + panel.parentNode.removeChild(panel); + win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .removeSheet(styleURI, 1); + } + + vAPI.messaging.globalMessageManager.removeMessageListener( + location.host + ':closePopup', + onPopupCloseRequested + ); + }; + + tbb.onBeforeCreated = function(doc) { + var panel = doc.createElement('panelview'); + + this.populatePanel(doc, panel); + + doc.getElementById('PanelUI-multiView').appendChild(panel); + + doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .loadSheet(styleURI, 1); + }; + + tbb.onBeforePopupReady = function() { + // https://github.com/gorhill/uBlock/issues/83 + // Add `portrait` class if width is constrained. + try { + this.contentDocument.body.classList.toggle( + 'portrait', + CustomizableUI.getWidget(tbb.id).areaType === CustomizableUI.TYPE_MENU_PANEL + ); + } catch (ex) { + // noop + } + }; + + tbb.init = function() { + vAPI.messaging.globalMessageManager.addMessageListener( + location.host + ':closePopup', + onPopupCloseRequested + ); + + var style = [ + '#' + this.id + '.off {', + 'list-style-image: url(', + vAPI.getURL('img/browsericons/icon19-off.png'), + ');', + '}', + '#' + this.id + ' {', + 'list-style-image: url(', + vAPI.getURL('img/browsericons/icon19.png'), + ');', + '}', + '#' + this.viewId + ', #' + this.viewId + ' > iframe {', + 'width: 160px;', + 'height: 290px;', + 'overflow: hidden !important;', + '}', + '#' + this.id + '[badge]:not([badge=""])::after {', + 'position: absolute;', + 'margin-left: -16px;', + 'margin-top: 3px;', + 'padding: 1px 2px;', + 'font-size: 9px;', + 'font-weight: bold;', + 'color: #fff;', + 'background: #000;', + 'content: attr(badge);', + '}' + ]; + + styleURI = Services.io.newURI( + 'data:text/css,' + encodeURIComponent(style.join('')), + null, + null + ); + + this.closePopup = function(tabBrowser) { + CustomizableUI.hidePanelForNode( + tabBrowser.ownerDocument.getElementById(this.viewId) + ); + }; + + CustomizableUI.createWidget(this); + + cleanupTasks.push(shutdown); + }; + })(); + */ - tbb.onBeforePopupReady = function() { - // https://github.com/gorhill/uBlock/issues/83 - // Add `portrait` class if width is constrained. - try { - this.contentDocument.body.classList.toggle( - 'portrait', - CustomizableUI.getWidget(tbb.id).areaType === CustomizableUI.TYPE_MENU_PANEL - ); - } catch (ex) { - // noop - } - }; + (function() { + // It appears that this branch actually works on the latest + // Basilisk. Maybe we can simply use this one directly instead of + // making checks like it's done now. - tbb.init = function() { - vAPI.messaging.globalMessageManager.addMessageListener( - location.host + ':closePopup', - onPopupCloseRequested - ); + // It was decided to use this branch unconditionally. It's still + // experimental though. - var style = [ - '#' + this.id + '.off {', - 'list-style-image: url(', - vAPI.getURL('img/browsericons/icon19-off.png'), - ');', - '}', - '#' + this.id + ' {', - 'list-style-image: url(', - vAPI.getURL('img/browsericons/icon19.png'), - ');', - '}', - '#' + this.viewId + ', #' + this.viewId + ' > iframe {', - 'width: 160px;', - 'height: 290px;', - 'overflow: hidden !important;', - '}', - '#' + this.id + '[badge]:not([badge=""])::after {', - 'position: absolute;', - 'margin-left: -16px;', - 'margin-top: 3px;', - 'padding: 1px 2px;', - 'font-size: 9px;', - 'font-weight: bold;', - 'color: #fff;', - 'background: #000;', - 'content: attr(badge);', - '}' - ]; - - styleURI = Services.io.newURI( - 'data:text/css,' + encodeURIComponent(style.join('')), - null, - null - ); - - this.closePopup = function(tabBrowser) { - CustomizableUI.hidePanelForNode( - tabBrowser.ownerDocument.getElementById(this.viewId) - ); - }; - - CustomizableUI.createWidget(this); - - cleanupTasks.push(shutdown); - }; -})(); -*/ + // Add toolbar button for Basilisk + if (Services.appinfo.ID !== "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") { + return; + } + + let tbb = vAPI.toolbarButton; + if (tbb.init !== null) { + return; + } + // if ( Services.vc.compare(Services.appinfo.version, '36.0') < 0 ) { + // return null; + // } + let CustomizableUI = null; + try { + CustomizableUI = + Cu.import('resource:///modules/CustomizableUI.jsm', null) + .CustomizableUI; + } catch (ex) { + // Ignore + } + if (CustomizableUI === null) { + return null; + } + tbb.codePath = 'australis'; + tbb.CustomizableUI = CustomizableUI; + tbb.defaultArea = CustomizableUI.AREA_NAVBAR; -/******************************************************************************/ + let CUIEvents = {}; -(function() { - // It appears that this branch actually works on the latest - // Basilisk. Maybe we can simply use this one directly instead of - // making checks like it's done now. + let badgeCSSRules = 'background: #000;color: #fff'; - // It was decided to use this branch unconditionally. It's still - // experimental though. + let updateBadgeStyle = function () { + for (let win of winWatcher.getWindows()) { + let button = win.document.getElementById(tbb.id); + if (button === null) { + continue; + } + let badge = button.ownerDocument + .getAnonymousElementByAttribute(button, + 'class', + 'toolbarbutton-badge'); + if (!badge) { + continue; + } - // Add toolbar button for Basilisk - if (Services.appinfo.ID !== "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") { - return; - } - - var tbb = vAPI.toolbarButton; - if ( tbb.init !== null ) { - return; - } - // if ( Services.vc.compare(Services.appinfo.version, '36.0') < 0 ) { - // return null; - // } - var CustomizableUI = null; - try { - CustomizableUI = Cu.import('resource:///modules/CustomizableUI.jsm', null).CustomizableUI; - } catch (ex) { - } - if ( CustomizableUI === null ) { - return null; - } - tbb.codePath = 'australis'; - tbb.CustomizableUI = CustomizableUI; - tbb.defaultArea = CustomizableUI.AREA_NAVBAR; - - var CUIEvents = {}; - - var badgeCSSRules = [ - 'background: #000', - 'color: #fff' - ].join(';'); - - var updateBadgeStyle = function() { - for ( var win of winWatcher.getWindows() ) { - var button = win.document.getElementById(tbb.id); - if ( button === null ) { - continue; - } - var badge = button.ownerDocument.getAnonymousElementByAttribute( - button, - 'class', - 'toolbarbutton-badge' - ); - if ( !badge ) { - continue; - } - - badge.style.cssText = badgeCSSRules; - } - }; + badge.style.cssText = badgeCSSRules; + } + }; - var updateBadge = function() { - var wId = tbb.id; - var buttonInPanel = CustomizableUI.getWidget(wId).areaType === CustomizableUI.TYPE_MENU_PANEL; + let updateBadge = function () { + let wId = tbb.id; + let buttonInPanel = + CustomizableUI.getWidget(wId).areaType + === CustomizableUI.TYPE_MENU_PANEL; - for ( var win of winWatcher.getWindows() ) { - var button = win.document.getElementById(wId); - if ( button === null ) { - continue; - } - if ( buttonInPanel ) { - button.classList.remove('badged-button'); - continue; + for (let win of winWatcher.getWindows()) { + let button = win.document.getElementById(wId); + if (button === null) { + continue; + } + + if (buttonInPanel) { + button.classList.remove('badged-button'); + continue; + } + + button.classList.add('badged-button'); } - button.classList.add('badged-button'); - } - if ( buttonInPanel ) { - return; - } + if (buttonInPanel) { + return; + } - // Anonymous elements need some time to be reachable - vAPI.setTimeout(updateBadgeStyle, 250); - }.bind(CUIEvents); + // Anonymous elements need some time to be reachable + vAPI.setTimeout(updateBadgeStyle, 250); + }.bind(CUIEvents); - // https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/CustomizableUI.jsm#Listeners - CUIEvents.onCustomizeEnd = updateBadge; - CUIEvents.onWidgetAdded = updateBadge; - CUIEvents.onWidgetUnderflow = updateBadge; + CUIEvents.onCustomizeEnd = updateBadge; + CUIEvents.onWidgetAdded = updateBadge; + CUIEvents.onWidgetUnderflow = updateBadge; - var onPopupCloseRequested = function({target}) { - if ( typeof tbb.closePopup === 'function' ) { - tbb.closePopup(target); - } - }; + let onPopupCloseRequested = function (target) { + if (typeof tbb.closePopup === 'function') { + tbb.closePopup(target); + } + }; - var shutdown = function() { - for ( var win of winWatcher.getWindows() ) { - var panel = win.document.getElementById(tbb.viewId); - if ( panel !== null && panel.parentNode !== null ) { - panel.parentNode.removeChild(panel); + let shutdown = function () { + for (let win of winWatcher.getWindows()) { + let panel = win.document.getElementById(tbb.viewId); + if (panel !== null && panel.parentNode !== null) { + panel.parentNode.removeChild(panel); + } + + win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .removeSheet(styleURI, 1); } - win.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils) - .removeSheet(styleURI, 1); - } - CustomizableUI.removeListener(CUIEvents); - CustomizableUI.destroyWidget(tbb.id); + CustomizableUI.removeListener(CUIEvents); + CustomizableUI.destroyWidget(tbb.id); - vAPI.messaging.globalMessageManager.removeMessageListener( - location.host + ':closePopup', - onPopupCloseRequested - ); - }; + vAPI.messaging.globalMessageManager + .removeMessageListener(location.host + ':closePopup', + onPopupCloseRequested); + }; - var styleURI = null; + let styleURI = null; - tbb.onBeforeCreated = function(doc) { - var panel = doc.createElement('panelview'); + tbb.onBeforeCreated = function (doc) { + let panel = doc.createElement('panelview'); - this.populatePanel(doc, panel); + this.populatePanel(doc, panel); - doc.getElementById('PanelUI-multiView').appendChild(panel); + doc.getElementById('PanelUI-multiView').appendChild(panel); - doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils) - .loadSheet(styleURI, 1); - }; + doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils) + .loadSheet(styleURI, 1); + }; - tbb.onCreated = function(button) { - button.setAttribute('badge', ''); - vAPI.setTimeout(updateBadge, 250); - }; + tbb.onCreated = function (button) { + button.setAttribute('badge', ''); + vAPI.setTimeout(updateBadge, 250); + }; - tbb.onBeforePopupReady = function() { - // https://github.com/gorhill/uBlock/issues/83 - // Add `portrait` class if width is constrained. - try { - this.contentDocument.body.classList.toggle( - 'portrait', - CustomizableUI.getWidget(tbb.id).areaType === CustomizableUI.TYPE_MENU_PANEL - ); - } catch (ex) { - /* noop */ - } - }; + tbb.onBeforePopupReady = function () { + // https://github.com/gorhill/uBlock/issues/83 + // Add `portrait` class if width is constrained. + try { + this.contentDocument.body + .classList.toggle('portrait', + CustomizableUI.getWidget(tbb.id).areaType + === CustomizableUI.TYPE_MENU_PANEL); + } catch (ex) { + // Ignore + } + }; - tbb.closePopup = function(tabBrowser) { - CustomizableUI.hidePanelForNode( - tabBrowser.ownerDocument.getElementById(tbb.viewId) - ); - }; + tbb.closePopup = function (tabBrowser) { + CustomizableUI.hidePanelForNode(tabBrowser + .ownerDocument + .getElementById(tbb.viewId)); + }; - tbb.init = function() { - vAPI.messaging.globalMessageManager.addMessageListener( - location.host + ':closePopup', - onPopupCloseRequested - ); + tbb.init = function () { + vAPI.messaging.globalMessageManager + .addMessageListener(location.host + ':closePopup', + onPopupCloseRequested); - CustomizableUI.addListener(CUIEvents); + CustomizableUI.addListener(CUIEvents); - var style = [ - '#' + this.id + '.off {', + var style = [ + '#' + this.id + '.off {', 'list-style-image: url(', - vAPI.getURL('img/browsericons/icon19-off.png'), + vAPI.getURL('img/browsericons/icon19-off.png'), ');', - '}', - '#' + this.id + ' {', + '}', + '#' + this.id + ' {', 'list-style-image: url(', - vAPI.getURL('img/browsericons/icon19-19.png'), + vAPI.getURL('img/browsericons/icon19-19.png'), ');', - '}', - '#' + this.viewId + ', #' + this.viewId + ' > iframe {', + '}', + '#' + this.viewId + ', #' + this.viewId + ' > iframe {', 'height: 290px;', 'max-width: none !important;', 'min-width: 0 !important;', 'overflow: hidden !important;', 'padding: 0 !important;', 'width: 160px;', - '}' - ]; - - styleURI = Services.io.newURI( - 'data:text/css,' + encodeURIComponent(style.join('')), - null, - null - ); - - CustomizableUI.createWidget(this); - - cleanupTasks.push(shutdown); - }; -})(); - -/******************************************************************************/ + '}' + ]; -// No toolbar button. + styleURI = + Services.io.newURI('data:text/css,' + +encodeURIComponent(style.join('')), + null, + null); -(function() { - // Just to ensure the number of cleanup tasks is as expected: toolbar - // button code is one single cleanup task regardless of platform. - if ( vAPI.toolbarButton.init === null ) { - cleanupTasks.push(function(){}); - } -})(); - -/******************************************************************************/ - -if ( vAPI.toolbarButton.init !== null ) { - vAPI.toolbarButton.init(); -} - -/******************************************************************************/ -/******************************************************************************/ + CustomizableUI.createWidget(this); -vAPI.contextMenu = { - contextMap: { - frame: 'inFrame', - link: 'onLink', - image: 'onImage', - audio: 'onAudio', - video: 'onVideo', - editable: 'onEditableArea' - } -}; - -/******************************************************************************/ - -vAPI.contextMenu.displayMenuItem = function({target}) { - var doc = target.ownerDocument; - var gContextMenu = doc.defaultView.gContextMenu; - if ( !gContextMenu.browser ) { - return; - } - - var menuitem = doc.getElementById(vAPI.contextMenu.menuItemId); - var currentURI = gContextMenu.browser.currentURI; + cleanupTasks.push(shutdown); + }; + })(); - // https://github.com/chrisaljoudi/uBlock/issues/105 - // TODO: Should the element picker works on any kind of pages? - if ( !currentURI.schemeIs('http') && !currentURI.schemeIs('https') ) { - menuitem.setAttribute('hidden', true); - return; - } + // No toolbar button. - var ctx = vAPI.contextMenu.contexts; + (function () { + // Just to ensure the number of cleanup tasks is as expected: toolbar + // button code is one single cleanup task regardless of platform. + // eMatrix: might not be needed anymore + if (vAPI.toolbarButton.init === null) { + cleanupTasks.push(function(){}); + } + })(); - if ( !ctx ) { - menuitem.setAttribute('hidden', false); - return; + if (vAPI.toolbarButton.init !== null) { + vAPI.toolbarButton.init(); } - var ctxMap = vAPI.contextMenu.contextMap; - - for ( var context of ctx ) { - if ( - context === 'page' && - !gContextMenu.onLink && - !gContextMenu.onImage && - !gContextMenu.onEditableArea && - !gContextMenu.inFrame && - !gContextMenu.onVideo && - !gContextMenu.onAudio - ) { - menuitem.setAttribute('hidden', false); - return; - } + vAPI.contextMenu = { + contextMap: { + frame: 'inFrame', + link: 'onLink', + image: 'onImage', + audio: 'onAudio', + video: 'onVideo', + editable: 'onEditableArea' + } + }; - if ( - ctxMap.hasOwnProperty(context) && - gContextMenu[ctxMap[context]] - ) { - menuitem.setAttribute('hidden', false); + vAPI.contextMenu.displayMenuItem = function (target) { + let doc = target.ownerDocument; + let gContextMenu = doc.defaultView.gContextMenu; + if (!gContextMenu.browser) { return; - } - } - - menuitem.setAttribute('hidden', true); -}; + } -/******************************************************************************/ + let menuitem = doc.getElementById(vAPI.contextMenu.menuItemId); + let currentURI = gContextMenu.browser.currentURI; -vAPI.contextMenu.register = (function() { - var register = function(doc) { - if ( !this.menuItemId ) { + // https://github.com/chrisaljoudi/uBlock/issues/105 + // TODO: Should the element picker works on any kind of pages? + if (!currentURI.schemeIs('http') && !currentURI.schemeIs('https')) { + menuitem.setAttribute('hidden', true); return; - } + } - // Already installed? - if ( doc.getElementById(this.menuItemId) !== null ) { - return; - } - - var contextMenu = doc.getElementById('contentAreaContextMenu'); - var menuitem = doc.createElement('menuitem'); - menuitem.setAttribute('id', this.menuItemId); - menuitem.setAttribute('label', this.menuLabel); - menuitem.setAttribute('image', vAPI.getURL('img/browsericons/icon19-19.png')); - menuitem.setAttribute('class', 'menuitem-iconic'); - menuitem.addEventListener('command', this.onCommand); - contextMenu.addEventListener('popupshowing', this.displayMenuItem); - contextMenu.insertBefore(menuitem, doc.getElementById('inspect-separator')); - }; + let ctx = vAPI.contextMenu.contexts; - // https://github.com/gorhill/uBlock/issues/906 - // Be sure document.readyState is 'complete': it could happen at launch - // time that we are called by vAPI.contextMenu.create() directly before - // the environment is properly initialized. - var registerSafely = function(doc, tryCount) { - if ( doc.readyState === 'complete' ) { - register.call(this, doc); + if (!ctx) { + menuitem.setAttribute('hidden', false); return; - } - if ( typeof tryCount !== 'number' ) { - tryCount = 0; - } - tryCount += 1; - if ( tryCount < 8 ) { - vAPI.setTimeout(registerSafely.bind(this, doc, tryCount), 200); - } - }; - - return registerSafely; -})(); + } -/******************************************************************************/ + let ctxMap = vAPI.contextMenu.contextMap; -vAPI.contextMenu.unregister = function(doc) { - if ( !this.menuItemId ) { - return; - } + for (let context of ctx) { + if (context === 'page' + && !gContextMenu.onLink + && !gContextMenu.onImage + && !gContextMenu.onEditableArea + && !gContextMenu.inFrame + && !gContextMenu.onVideo + && !gContextMenu.onAudio) { + menuitem.setAttribute('hidden', false); + return; + } - var menuitem = doc.getElementById(this.menuItemId); - if ( menuitem === null ) { - return; - } - var contextMenu = menuitem.parentNode; - menuitem.removeEventListener('command', this.onCommand); - contextMenu.removeEventListener('popupshowing', this.displayMenuItem); - contextMenu.removeChild(menuitem); -}; + if (ctxMap.hasOwnProperty(context) + && gContextMenu[ctxMap[context]]) { + menuitem.setAttribute('hidden', false); + return; + } + } -/******************************************************************************/ + menuitem.setAttribute('hidden', true); + }; -vAPI.contextMenu.create = function(details, callback) { - this.menuItemId = details.id; - this.menuLabel = details.title; - this.contexts = details.contexts; + vAPI.contextMenu.register = (function () { + let register = function (doc) { + if (!this.menuItemId) { + return; + } - if ( Array.isArray(this.contexts) && this.contexts.length ) { - this.contexts = this.contexts.indexOf('all') === -1 ? this.contexts : null; - } else { - // default in Chrome - this.contexts = ['page']; - } + // Already installed? + if (doc.getElementById(this.menuItemId) !== null) { + return; + } - this.onCommand = function() { - var gContextMenu = getOwnerWindow(this).gContextMenu; - var details = { - menuItemId: this.id - }; - - if ( gContextMenu.inFrame ) { - details.tagName = 'iframe'; - // Probably won't work with e10s - details.frameUrl = gContextMenu.focusedWindow.location.href; - } else if ( gContextMenu.onImage ) { - details.tagName = 'img'; - details.srcUrl = gContextMenu.mediaURL; - } else if ( gContextMenu.onAudio ) { - details.tagName = 'audio'; - details.srcUrl = gContextMenu.mediaURL; - } else if ( gContextMenu.onVideo ) { - details.tagName = 'video'; - details.srcUrl = gContextMenu.mediaURL; - } else if ( gContextMenu.onLink ) { - details.tagName = 'a'; - details.linkUrl = gContextMenu.linkURL; - } - - callback(details, { - id: tabWatcher.tabIdFromTarget(gContextMenu.browser), - url: gContextMenu.browser.currentURI.asciiSpec - }); - }; + let contextMenu = doc.getElementById('contentAreaContextMenu'); + + let menuitem = doc.createElement('menuitem'); + menuitem.setAttribute('id', this.menuItemId); + menuitem.setAttribute('label', this.menuLabel); + menuitem.setAttribute('image', vAPI.getURL('img/browsericons/icon19-19.png')); + menuitem.setAttribute('class', 'menuitem-iconic'); + menuitem.addEventListener('command', this.onCommand); + + contextMenu.addEventListener('popupshowing', this.displayMenuItem); + contextMenu.insertBefore(menuitem, doc.getElementById('inspect-separator')); + }; - for ( var win of winWatcher.getWindows() ) { - this.register(win.document); - } -}; + var registerSafely = function (doc, tryCount) { + // https://github.com/gorhill/uBlock/issues/906 + // Be sure document.readyState is 'complete': it could happen + // at launch time that we are called by + // vAPI.contextMenu.create() directly before the environment + // is properly initialized. + if (doc.readyState === 'complete') { + register.call(this, doc); + return; + } + + if (typeof tryCount !== 'number') { + tryCount = 0; + } + + tryCount += 1; + if ( tryCount < 8) { + vAPI.setTimeout(registerSafely.bind(this, doc, tryCount), 200); + } + }; -/******************************************************************************/ + return registerSafely; + })(); -vAPI.contextMenu.remove = function() { - for ( var win of winWatcher.getWindows() ) { - this.unregister(win.document); - } + vAPI.contextMenu.unregister = function (doc) { + if (!this.menuItemId) { + return; + } - this.menuItemId = null; - this.menuLabel = null; - this.contexts = null; - this.onCommand = null; -}; + let menuitem = doc.getElementById(this.menuItemId); + if (menuitem === null) { + return; + } + + let contextMenu = menuitem.parentNode; + menuitem.removeEventListener('command', this.onCommand); + contextMenu.removeEventListener('popupshowing', this.displayMenuItem); + contextMenu.removeChild(menuitem); + }; + + vAPI.contextMenu.create = function (details, callback) { + this.menuItemId = details.id; + this.menuLabel = details.title; + this.contexts = details.contexts; + + if (Array.isArray(this.contexts) && this.contexts.length) { + this.contexts = this.contexts.indexOf('all') === -1 + ? this.contexts + : null; + } else { + // default in Chrome + this.contexts = ['page']; + } -/******************************************************************************/ -/******************************************************************************/ + this.onCommand = function () { + let gContextMenu = getOwnerWindow(this).gContextMenu; + let details = { + menuItemId: this.id + }; + + if (gContextMenu.inFrame) { + details.tagName = 'iframe'; + // Probably won't work with e10s + // eMatrix: doesn't matter ;) + details.frameUrl = gContextMenu.focusedWindow.location.href; + } else if (gContextMenu.onImage) { + details.tagName = 'img'; + details.srcUrl = gContextMenu.mediaURL; + } else if (gContextMenu.onAudio) { + details.tagName = 'audio'; + details.srcUrl = gContextMenu.mediaURL; + } else if (gContextMenu.onVideo) { + details.tagName = 'video'; + details.srcUrl = gContextMenu.mediaURL; + } else if (gContextMenu.onLink) { + details.tagName = 'a'; + details.linkUrl = gContextMenu.linkURL; + } + + callback(details, { + id: tabWatcher.tabIdFromTarget(gContextMenu.browser), + url: gContextMenu.browser.currentURI.asciiSpec + }); + }; -var optionsObserver = (function() { - var addonId = 'eMatrix@vannilla.org'; - - var commandHandler = function() { - switch ( this.id ) { - case 'showDashboardButton': - vAPI.tabs.open({ url: 'dashboard.html', index: -1 }); - break; - case 'showLoggerButton': - vAPI.tabs.open({ url: 'logger-ui.html', index: -1 }); - break; - default: - break; - } + for (let win of winWatcher.getWindows()) { + this.register(win.document); + } }; - var setupOptionsButton = function(doc, id) { - var button = doc.getElementById(id); - if ( button === null ) { - return; - } - button.addEventListener('command', commandHandler); - button.label = vAPI.i18n(id); - }; + vAPI.contextMenu.remove = function () { + for (let win of winWatcher.getWindows()) { + this.unregister(win.document); + } - var setupOptionsButtons = function(doc) { - setupOptionsButton(doc, 'showDashboardButton'); - setupOptionsButton(doc, 'showLoggerButton'); - }; + this.menuItemId = null; + this.menuLabel = null; + this.contexts = null; + this.onCommand = null; + }; + + let optionsObserver = (function () { + let addonId = 'eMatrix@vannilla.org'; + + let commandHandler = function () { + switch (this.id) { + case 'showDashboardButton': + vAPI.tabs.open({ + url: 'dashboard.html', + index: -1, + }); + break; + case 'showLoggerButton': + vAPI.tabs.open({ + url: 'logger-ui.html', + index: -1, + }); + break; + default: + break; + } + }; + + let setupOptionsButton = function (doc, id) { + let button = doc.getElementById(id); + if (button === null) { + return; + } + button.addEventListener('command', commandHandler); + button.label = vAPI.i18n(id); + }; + + let setupOptionsButtons = function (doc) { + setupOptionsButton(doc, 'showDashboardButton'); + setupOptionsButton(doc, 'showLoggerButton'); + }; + + let observer = { + observe: function (doc, topic, id) { + if (id !== addonId) { + return; + } - var observer = { - observe: function(doc, topic, id) { - if ( id !== addonId ) { - return; + setupOptionsButtons(doc); } + }; - setupOptionsButtons(doc); - } - }; - - // https://github.com/gorhill/uBlock/issues/948 - // Older versions of Firefox can throw here when looking up `currentURI`. - - var canInit = function() { - try { - var tabBrowser = tabWatcher.currentBrowser(); - return tabBrowser && - tabBrowser.currentURI && - tabBrowser.currentURI.spec === 'about:addons' && - tabBrowser.contentDocument && - tabBrowser.contentDocument.readyState === 'complete'; - } catch (ex) { - } - }; + var canInit = function() { + // https://github.com/gorhill/uBlock/issues/948 + // Older versions of Firefox can throw here when looking + // up `currentURI`. + try { + let tabBrowser = tabWatcher.currentBrowser(); + return tabBrowser + && tabBrowser.currentURI + && tabBrowser.currentURI.spec === 'about:addons' + && tabBrowser.contentDocument + && tabBrowser.contentDocument.readyState === 'complete'; + } catch (ex) { + // Ignore + } + }; + + // Manually add the buttons if the `about:addons` page is + // already opened. + let init = function () { + if (canInit()) { + setupOptionsButtons(tabWatcher.currentBrowser().contentDocument); + } + }; + + let unregister = function () { + Services.obs.removeObserver(observer, 'addon-options-displayed'); + }; + + let register = function () { + Services.obs.addObserver(observer, + 'addon-options-displayed', + false); + cleanupTasks.push(unregister); + deferUntil(canInit, init, { next: 463 }); + }; + + return { + register: register, + unregister: unregister + }; + })(); - // Manually add the buttons if the `about:addons` page is already opened. + optionsObserver.register(); - var init = function() { - if ( canInit() ) { - setupOptionsButtons(tabWatcher.currentBrowser().contentDocument); - } + vAPI.lastError = function () { + return null; }; - var unregister = function() { - Services.obs.removeObserver(observer, 'addon-options-displayed'); + vAPI.onLoadAllCompleted = function() { + // This is called only once, when everything has been loaded + // in memory after the extension was launched. It can be used + // to inject content scripts in already opened web pages, to + // remove whatever nuisance could make it to the web pages + // before uBlock was ready. + for (let browser of tabWatcher.browsers()) { + browser.messageManager + .sendAsyncMessage(location.host + '-load-completed'); + } }; - var register = function() { - Services.obs.addObserver(observer, 'addon-options-displayed', false); - cleanupTasks.push(unregister); - deferUntil(canInit, init, { next: 463 }); - }; + // Likelihood is that we do not have to punycode: given punycode overhead, + // it's faster to check and skip than do it unconditionally all the time. + var punycodeHostname = punycode.toASCII; + var isNotASCII = /[^\x21-\x7F]/; - return { - register: register, - unregister: unregister + vAPI.punycodeHostname = function (hostname) { + return isNotASCII.test(hostname) + ? punycodeHostname(hostname) + : hostname; }; -})(); - -optionsObserver.register(); -/******************************************************************************/ -/******************************************************************************/ - -vAPI.lastError = function() { - return null; -}; - -/******************************************************************************/ -/******************************************************************************/ - -// This is called only once, when everything has been loaded in memory after -// the extension was launched. It can be used to inject content scripts -// in already opened web pages, to remove whatever nuisance could make it to -// the web pages before uBlock was ready. - -vAPI.onLoadAllCompleted = function() { - for ( var browser of tabWatcher.browsers() ) { - browser.messageManager.sendAsyncMessage( - location.host + '-load-completed' - ); - } -}; - -/******************************************************************************/ -/******************************************************************************/ + vAPI.punycodeURL = function (url) { + if (isNotASCII.test(url)) { + return Services.io.newURI(url, null, null).asciiSpec; + } + + return url; + }; + + vAPI.cloud = (function () { + let extensionBranchPath = 'extensions.' + location.host; + let cloudBranchPath = extensionBranchPath + '.cloudStorage'; + + // https://github.com/gorhill/uBlock/issues/80#issuecomment-132081658 + // We must use get/setComplexValue in order to properly handle strings + // with unicode characters. + let iss = Ci.nsISupportsString; + let argstr = Components.classes['@mozilla.org/supports-string;1'] + .createInstance(iss); + + let options = { + defaultDeviceName: '', + deviceName: '' + }; + + // User-supplied device name. + try { + options.deviceName = Services.prefs + .getBranch(extensionBranchPath + '.') + .getComplexValue('deviceName', iss) + .data; + } catch(ex) { + // Ignore + } -// Likelihood is that we do not have to punycode: given punycode overhead, -// it's faster to check and skip than do it unconditionally all the time. + var getDefaultDeviceName = function() { + var name = ''; + try { + name = Services.prefs + .getBranch('services.sync.client.') + .getComplexValue('name', iss) + .data; + } catch(ex) { + // Ignore + } + + return name || window.navigator.platform || window.navigator.oscpu; + }; + + let start = function (dataKeys) { + let extensionBranch = + Services.prefs.getBranch(extensionBranchPath + '.'); + let syncBranch = + Services.prefs.getBranch('services.sync.prefs.sync.'); + + // Mark config entries as syncable + argstr.data = ''; + let dataKey; + for (let i=0; i Date: Wed, 19 Jun 2019 18:59:25 +0200 Subject: Fix typos and reinstate some old declarations --- js/vapi-background.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 6888317..3753b0b 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -45,7 +45,7 @@ let dtls = (typeof details !== 'object') ? {} : details; let now = 0; let next = dtls.next || 200; - let until = dtsl.until || 2000; + let until = dtls.until || 2000; let check = function () { if (testFn() === true || now >= until) { @@ -1237,18 +1237,18 @@ // }); // }; - var onShow = function (target) { + var onShow = function ({target}) { tabIdFromTarget(target); }; - var onClose = function (target) { + var onClose = function ({target}) { // target is tab in Firefox, browser in Fennec let browser = browserFromTarget(target); let tabId = browserToTabIdMap.get(browser); removeBrowserEntry(tabId, browser); }; - var onSelect = function (target) { + var onSelect = function ({target}) { // This is an entry point: when creating a new tab, it is // not always reported through onLocationChanged... // Sigh. It is "reported" here however. @@ -1524,7 +1524,7 @@ this.listeners[listenerName] = callback; }; - vAPI.messaging.onMessage = function (target, data) { + vAPI.messaging.onMessage = function ({target, data}) { let messageManager = target.messageManager; if (!messageManager) { @@ -1946,7 +1946,7 @@ let onBeforeRequest = vAPI.net.onBeforeRequest; if (onBeforeRequest.types === null || onBeforeRequest.types.has(type)) { - letresult = onBeforeRequest.callback({ + let result = onBeforeRequest.callback({ parentFrameId: type === 'main_frame' ? -1 : 0, tabId: tabId, type: type, @@ -2276,12 +2276,12 @@ let popupCommittedWidth = 0; let popupCommittedHeight = 0; - tbb.onViewShowing = function (target) { + tbb.onViewShowing = function ({target}) { popupCommittedWidth = popupCommittedHeight = 0; target.firstChild.setAttribute('src', vAPI.getURL('popup.html')); }; - tbb.onViewHiding = function (target) { + tbb.onViewHiding = function ({target}) { target.parentNode.style.maxWidth = ''; target.firstChild.setAttribute('src', 'about:blank'); }; @@ -2612,7 +2612,7 @@ return toolbox !== null && !!toolbox.palette; }; - let onPopupCloseRequested = function (target) { + let onPopupCloseRequested = function ({target}) { let document = target.ownerDocument; if (!document) { return; @@ -2888,7 +2888,7 @@ CUIEvents.onWidgetAdded = updateBadge; CUIEvents.onWidgetUnderflow = updateBadge; - let onPopupCloseRequested = function (target) { + let onPopupCloseRequested = function ({target}) { if (typeof tbb.closePopup === 'function') { tbb.closePopup(target); } @@ -3018,7 +3018,7 @@ } }; - vAPI.contextMenu.displayMenuItem = function (target) { + vAPI.contextMenu.displayMenuItem = function ({target}) { let doc = target.ownerDocument; let gContextMenu = doc.defaultView.gContextMenu; if (!gContextMenu.browser) { -- cgit v1.2.3 From 23f2978b8a41649c93f8b46c0f967f8b226928a4 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Wed, 19 Jun 2019 19:11:36 +0200 Subject: Don't use child-src It generates an error in the browser console and it's rather annoying. There is still one error being generated, but it's unclear from where it comes from. Not yet tested in Basilisk. --- js/traffic.js | 10 +++++----- js/vapi-background.js | 5 ++++- 2 files changed, 9 insertions(+), 6 deletions(-) (limited to 'js') diff --git a/js/traffic.js b/js/traffic.js index b9efa4b..80af782 100644 --- a/js/traffic.js +++ b/js/traffic.js @@ -366,12 +366,12 @@ var onHeadersReceived = function(details) { /******************************************************************************/ var cspNoWorkerInit = function() { - if ( vAPI.webextFlavor === undefined ) { - return "child-src 'none'; frame-src data: blob: *; report-uri about:blank"; + if (ηMatrix.cspNoWorker === undefined) { + ηMatrix.cspNoWorker = "worker-src 'none'; " + +"frame-src data: blob: *; " + +"report-uri about:blank"; } - ηMatrix.cspNoWorker = /^Mozilla-Firefox-5[67]/.test(vAPI.webextFlavor) ? - "child-src 'none'; frame-src data: blob: *; report-uri about:blank" : - "worker-src 'none'; report-uri about:blank" ; + return ηMatrix.cspNoWorker; }; diff --git a/js/vapi-background.js b/js/vapi-background.js index 3753b0b..f861338 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -2111,7 +2111,10 @@ // eMatrix: as of Pale Moon 28 it seems child-src is // available and depracated(?) if (ηMatrix.cspNoWorker === undefined) { - ηMatrix.cspNoWorker = "child-src 'none'; " + // ηMatrix.cspNoWorker = "child-src 'none'; " + // +"frame-src data: blob: *; " + // +"report-uri about:blank"; + ηMatrix.cspNoWorker = "worker-src 'none'; " +"frame-src data: blob: *; " +"report-uri about:blank"; } -- cgit v1.2.3 From bdc82e65c4d30f541b5e0efcd3c0723103435c4e Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Fri, 21 Jun 2019 22:03:03 +0200 Subject: Split tab handling from vapi-background That file is too large, let's split it up. --- js/vapi-background.js | 712 +----------------------------------------------- js/vapi-tabs.js | 735 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 743 insertions(+), 704 deletions(-) create mode 100644 js/vapi-tabs.js (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index f861338..d939186 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -90,6 +90,14 @@ // eMatrix: do we? let expectedNumberOfCleanups = 7; + vAPI.addCleanUpTask = function (task) { + if (typeof task !== 'function') { + return; + } + + cleanupTasks.push(task); + } + window.addEventListener('unload', function () { // if (typeof vAPI.app.onShutdown === 'function') { // vAPI.app.onShutdown(); @@ -770,710 +778,6 @@ return tabId.toString() === '-1'; }; - vAPI.noTabId = '-1'; - - // Tabs and related functions - vAPI.tabs = {}; - - vAPI.tabs.registerListeners = function() { - tabWatcher.start(); - }; - - vAPI.tabs.get = function (tabId, callback) { - // eMatrix: the following might be obsoleted (though probably - // still relevant at least for Pale Moon.) - // - // Firefox: - // - // browser -> ownerDocument -> defaultView -> gBrowser -> browsers --+ - // ^ | - // | | - // +--------------------------------------------------------------+ - // - // browser (browser) - // contentTitle - // currentURI - // ownerDocument (XULDocument) - // defaultView (ChromeWindow) - // gBrowser (tabbrowser OR browser) - // browsers (browser) - // selectedBrowser - // selectedTab - // tabs (tab.tabbrowser-tab) - // - // Fennec: (what I figured so far) - // - // tab -> browser windows -> window -> BrowserApp -> tabs --+ - // ^ window | - // | | - // +-----------------------------------------------------------+ - // - // tab - // browser - // [manual search to go back to tab from list of windows] - let browser; - - if (tabId === null) { - browser = tabWatcher.currentBrowser(); - tabId = tabWatcher.tabIdFromTarget(browser); - } else { - browser = tabWatcher.browserFromTabId(tabId); - } - - // For internal use - if (typeof callback !== 'function') { - return browser; - } - - if (!browser || !browser.currentURI) { - callback(); - return; - } - - let win = getOwnerWindow(browser); - let tabBrowser = getTabBrowser(win); - - callback({ - id: tabId, - windowId: winWatcher.idFromWindow(win), - active: tabBrowser !== null - && browser === tabBrowser.selectedBrowser, - url: browser.currentURI.asciiSpec, - title: browser.contentTitle - }); - }; - - vAPI.tabs.getAllSync = function (window) { - let win; - let tabs = []; - - for (let win of winWatcher.getWindows()) { - if (window && window !== win) { - continue; - } - - let tabBrowser = getTabBrowser(win); - if (tabBrowser === null) { - continue; - } - - // This can happens if a tab-less window is currently opened. - // Example of a tab-less window: one opened from clicking - // "View Page Source". - if (!tabBrowser.tabs) { - continue; - } - - for (let tab of tabBrowser.tabs) { - tabs.push(tab); - } - } - - return tabs; - }; - - vAPI.tabs.getAll = function (callback) { - let tabs = []; - - for (let browser of tabWatcher.browsers()) { - let tab = tabWatcher.tabFromBrowser(browser); - - if (tab === null) { - continue; - } - - if (tab.hasAttribute('pending')) { - continue; - } - - tabs.push({ - id: tabWatcher.tabIdFromTarget(browser), - url: browser.currentURI.asciiSpec - }); - } - - callback(tabs); - }; - - vAPI.tabs.open = function (details) { - // properties of the details object: - // + url - the address that will be opened - // + tabId:- the tab is used if set, instead of creating a new one - // + index: - undefined: end of the list, -1: following tab, or - // after index - // + active: - opens the tab in background - true and undefined: - // foreground - // + select: - if a tab is already opened with that url, then - // select it instead of opening a new one - if (!details.url) { - return null; - } - - // extension pages - if (/^[\w-]{2,}:/.test(details.url) === false) { - details.url = vAPI.getURL(details.url); - } - - if (details.select) { - let URI = Services.io.newURI(details.url, null, null); - - for (let tab of this.getAllSync()) { - let browser = tabWatcher.browserFromTarget(tab); - - // https://github.com/gorhill/uBlock/issues/2558 - if (browser === null) { - continue; - } - - // Or simply .equals if we care about the fragment - if (URI.equalsExceptRef(browser.currentURI) === false) { - continue; - } - - this.select(tab); - - // Update URL if fragment is different - if (URI.equals(browser.currentURI) === false) { - browser.loadURI(URI.asciiSpec); - } - - return; - } - } - - if (details.active === undefined) { - details.active = true; - } - - if (details.tabId) { - let tab = tabWatcher.browserFromTabId(details.tabId); - - if (tab) { - tabWatcher.browserFromTarget(tab).loadURI(details.url); - return; - } - } - - // Open in a standalone window - if (details.popup === true) { - Services.ww.openWindow(self, - details.url, - null, - 'location=1,menubar=1,personalbar=1,' - +'resizable=1,toolbar=1', - null); - return; - } - - let win = winWatcher.getCurrentWindow(); - let tabBrowser = getTabBrowser(win); - - if (tabBrowser === null) { - return; - } - - if (details.index === -1) { - details.index = - tabBrowser.browsers.indexOf(tabBrowser.selectedBrowser) + 1; - } - - let tab = tabBrowser.loadOneTab(details.url, { - inBackground: !details.active - }); - - if (details.index !== undefined) { - tabBrowser.moveTabTo(tab, details.index); - } - }; - - vAPI.tabs.replace = function (tabId, url) { - // Replace the URL of a tab. Noop if the tab does not exist. - let targetURL = url; - - // extension pages - if (/^[\w-]{2,}:/.test(targetURL) !== true) { - targetURL = vAPI.getURL(targetURL); - } - - let browser = tabWatcher.browserFromTabId(tabId); - if (browser) { - browser.loadURI(targetURL); - } - }; - - vAPI.tabs._remove = function (tab, tabBrowser) { - if (tabBrowser) { - tabBrowser.removeTab(tab); - } - }; - function removeInternal(tab, tabBrowser) { - if (tabBrowser) { - tabBrowser.removeTab(tab); - } - } - - vAPI.tabs.remove = function (tabId) { - let browser = tabWatcher.browserFromTabId(tabId); - if (!browser) { - return; - } - - let tab = tabWatcher.tabFromBrowser(browser); - if (!tab) { - return; - } - - removeInternal(tab, getTabBrowser(getOwnerWindow(browser))); - }; - - vAPI.tabs.reload = function (tabId) { - let browser = tabWatcher.browserFromTabId(tabId); - if (!browser) { - return; - } - - browser.webNavigation.reload(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE); - }; - - vAPI.tabs.select = function (tab) { - if (typeof tab !== 'object') { - tab = tabWatcher.tabFromBrowser(tabWatcher.browserFromTabId(tab)); - } - if (!tab) { - return; - } - - // https://github.com/gorhill/uBlock/issues/470 - let win = getOwnerWindow(tab); - win.focus(); - - let tabBrowser = getTabBrowser(win); - if (tabBrowser) { - tabBrowser.selectedTab = tab; - } - }; - - vAPI.tabs.injectScript = function (tabId, details, callback) { - let browser = tabWatcher.browserFromTabId(tabId); - if (!browser) { - return; - } - - if (typeof details.file !== 'string') { - return; - } - - details.file = vAPI.getURL(details.file); - browser.messageManager.sendAsyncMessage(location.host + ':broadcast', - JSON.stringify({ - broadcast: true, - channelName: 'vAPI', - msg: { - cmd: 'injectScript', - details: details - } - })); - - if (typeof callback === 'function') { - vAPI.setTimeout(callback, 13); - } - }; - - let tabWatcher = (function () { - // TODO: find out whether we need a janitor to take care of stale entries. - - // https://github.com/gorhill/uMatrix/issues/540 - // Use only weak references to hold onto browser references. - let browserToTabIdMap = new WeakMap(); - let tabIdToBrowserMap = new Map(); - let tabIdGenerator = 1; - - let indexFromBrowser = function (browser) { - if (!browser) { - return -1; - } - - let win = getOwnerWindow(browser); - if (!win) { - return -1; - } - - let tabBrowser = getTabBrowser(win); - if (tabBrowser === null) { - return -1; - } - - // This can happen, for example, the `view-source:` - // window, there is no tabbrowser object, the browser - // object sits directly in the window. - if (tabBrowser === browser) { - return 0; - } - - return tabBrowser.browsers.indexOf(browser); - }; - - let indexFromTarget = function (target) { - return indexFromBrowser(browserFromTarget(target)); - }; - - let tabFromBrowser = function (browser) { - let i = indexFromBrowser(browser); - if (i === -1) { - return null; - } - - let win = getOwnerWindow(browser); - if (!win) { - return null; - } - - let tabBrowser = getTabBrowser(win); - if (tabBrowser === null) { - return null; - } - - if (!tabBrowser.tabs || i >= tabBrowser.tabs.length) { - return null; - } - - return tabBrowser.tabs[i]; - }; - - let browserFromTarget = function (target) { - if (!target) { - return null; - } - - if (target.linkedPanel) { - // target is a tab - target = target.linkedBrowser; - } - - if (target.localName !== 'browser') { - return null; - } - - return target; - }; - - let tabIdFromTarget = function (target) { - let browser = browserFromTarget(target); - if (browser === null) { - return vAPI.noTabId; - } - - let tabId = browserToTabIdMap.get(browser); - if (tabId === undefined) { - tabId = '' + tabIdGenerator++; - browserToTabIdMap.set(browser, tabId); - tabIdToBrowserMap.set(tabId, Cu.getWeakReference(browser)); - } - - return tabId; - }; - - let browserFromTabId = function (tabId) { - let weakref = tabIdToBrowserMap.get(tabId); - let browser = weakref && weakref.get(); - - return browser || null; - }; - - let currentBrowser = function () { - let win = winWatcher.getCurrentWindow(); - - // https://github.com/gorhill/uBlock/issues/399 - // getTabBrowser() can return null at browser launch time. - let tabBrowser = getTabBrowser(win); - if (tabBrowser === null) { - return null; - } - - return browserFromTarget(tabBrowser.selectedTab); - }; - - let removeBrowserEntry = function (tabId, browser) { - if (tabId && tabId !== vAPI.noTabId) { - vAPI.tabs.onClosed(tabId); - delete vAPI.toolbarButton.tabs[tabId]; - tabIdToBrowserMap.delete(tabId); - } - - if (browser) { - browserToTabIdMap.delete(browser); - } - }; - - let removeTarget = function (target) { - onClose({ - target: target - }); - }; - - let getAllBrowsers = function () { - let browsers = []; - - for (let [tabId, weakref] of tabIdToBrowserMap) { - let browser = weakref.get(); - - // TODO: Maybe call removeBrowserEntry() if the - // browser no longer exists? - if (browser) { - browsers.push(browser); - } - } - - return browsers; - }; - - // var onOpen = function (target) { - // var tabId = tabIdFromTarget(target); - // var browser = browserFromTabId(tabId); - // vAPI.tabs.onNavigation({ - // frameId: 0, - // tabId: tabId, - // url: browser.currentURI.asciiSpec, - // }); - // }; - - var onShow = function ({target}) { - tabIdFromTarget(target); - }; - - var onClose = function ({target}) { - // target is tab in Firefox, browser in Fennec - let browser = browserFromTarget(target); - let tabId = browserToTabIdMap.get(browser); - removeBrowserEntry(tabId, browser); - }; - - var onSelect = function ({target}) { - // This is an entry point: when creating a new tab, it is - // not always reported through onLocationChanged... - // Sigh. It is "reported" here however. - let browser = browserFromTarget(target); - let tabId = browserToTabIdMap.get(browser); - - if (tabId === undefined) { - tabId = tabIdFromTarget(target); - vAPI.tabs.onNavigation({ - frameId: 0, - tabId: tabId, - url: browser.currentURI.asciiSpec - }); - } - - vAPI.setIcon(tabId, getOwnerWindow(target)); - }; - - let locationChangedMessageName = location.host + ':locationChanged'; - - let onLocationChanged = function (e) { - let details = e.data; - - // Ignore notifications related to our popup - if (details.url.lastIndexOf(vAPI.getURL('popup.html'), 0) === 0) { - return; - } - - let browser = e.target; - let tabId = tabIdFromTarget(browser); - if (tabId === vAPI.noTabId) { - return; - } - - // LOCATION_CHANGE_SAME_DOCUMENT = "did not load a new document" - if (details.flags - & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) { - vAPI.tabs.onUpdated(tabId, {url: details.url}, { - frameId: 0, - tabId: tabId, - url: browser.currentURI.asciiSpec - }); - return; - } - - // https://github.com/chrisaljoudi/uBlock/issues/105 - // Allow any kind of pages - vAPI.tabs.onNavigation({ - frameId: 0, - tabId: tabId, - url: details.url - }); - }; - - let attachToTabBrowser = function (window) { - if (typeof vAPI.toolbarButton.attachToNewWindow === 'function') { - vAPI.toolbarButton.attachToNewWindow(window); - } - - let tabBrowser = getTabBrowser(window); - if (tabBrowser === null) { - return; - } - - let tabContainer; - if (tabBrowser.deck) { - // Fennec - tabContainer = tabBrowser.deck; - } else if (tabBrowser.tabContainer) { - // Firefox - tabContainer = tabBrowser.tabContainer; - vAPI.contextMenu.register(document); - } - - // https://github.com/gorhill/uBlock/issues/697 - // Ignore `TabShow` events: unfortunately the `pending` - // attribute is not set when a tab is opened as a result - // of session restore -- it is set *after* the event is - // fired in such case. - if (tabContainer) { - tabContainer.addEventListener('TabShow', onShow); - tabContainer.addEventListener('TabClose', onClose); - // when new window is opened TabSelect doesn't run on - // the selected tab? - tabContainer.addEventListener('TabSelect', onSelect); - } - }; - - var canAttachToTabBrowser = function (window) { - // https://github.com/gorhill/uBlock/issues/906 - // Ensure the environment is ready before trying to attaching. - let document = window && window.document; - if (!document || document.readyState !== 'complete') { - return false; - } - - // On some platforms, the tab browser isn't immediately - // available, try waiting a bit if this - // https://github.com/gorhill/uBlock/issues/763 - // Not getting a tab browser should not prevent from - // attaching ourself to the window. - let tabBrowser = getTabBrowser(window); - if (tabBrowser === null) { - return false; - } - - return winWatcher.toBrowserWindow(window) !== null; - }; - - let onWindowLoad = function (win) { - deferUntil(canAttachToTabBrowser.bind(null, win), - attachToTabBrowser.bind(null, win)); - }; - - let onWindowUnload = function (win) { - vAPI.contextMenu.unregister(win.document); - - let tabBrowser = getTabBrowser(win); - if (tabBrowser === null) { - return; - } - - let tabContainer = tabBrowser.tabContainer; - if (tabContainer) { - tabContainer.removeEventListener('TabShow', onShow); - tabContainer.removeEventListener('TabClose', onClose); - tabContainer.removeEventListener('TabSelect', onSelect); - } - - // https://github.com/gorhill/uBlock/issues/574 - // To keep in mind: not all windows are tab containers, - // sometimes the window IS the tab. - let tabs; - if (tabBrowser.tabs) { - tabs = tabBrowser.tabs; - } else if (tabBrowser.localName === 'browser') { - tabs = [tabBrowser]; - } else { - tabs = []; - } - - let browser; - let URI; - let tabId; - for (let i=tabs.length-1; i>=0; --i) { - let tab = tabs[i]; - browser = browserFromTarget(tab); - if (browser === null) { - continue; - } - - URI = browser.currentURI; - // Close extension tabs - if (URI.schemeIs('chrome') && URI.host === location.host) { - removeInternal(tab, getTabBrowser(win)); - } - - tabId = browserToTabIdMap.get(browser); - if (tabId !== undefined) { - removeBrowserEntry(tabId, browser); - tabIdToBrowserMap.delete(tabId); - } - browserToTabIdMap.delete(browser); - } - }; - - var start = function () { - // Initialize map with existing active tabs - let tabBrowser; - let tabs; - - for (let win of winWatcher.getWindows()) { - onWindowLoad(win); - - tabBrowser = getTabBrowser(win); - if (tabBrowser === null) { - continue; - } - - for (let tab of tabBrowser.tabs) { - if (!tab.hasAttribute('pending')) { - tabIdFromTarget(tab); - } - } - } - - winWatcher.onOpenWindow = onWindowLoad; - winWatcher.onCloseWindow = onWindowUnload; - - vAPI.messaging.globalMessageManager - .addMessageListener(locationChangedMessageName, - onLocationChanged); - }; - - let stop = function () { - winWatcher.onOpenWindow = null; - winWatcher.onCloseWindow = null; - - vAPI.messaging.globalMessageManager - .removeMessageListener(locationChangedMessageName, - onLocationChanged); - - for (let win of winWatcher.getWindows()) { - onWindowUnload(win); - } - - browserToTabIdMap = new WeakMap(); - tabIdToBrowserMap.clear(); - }; - - cleanupTasks.push(stop); - - return { - browsers: getAllBrowsers, - browserFromTabId: browserFromTabId, - browserFromTarget: browserFromTarget, - currentBrowser: currentBrowser, - indexFromTarget: indexFromTarget, - removeTarget: removeTarget, - start: start, - tabFromBrowser: tabFromBrowser, - tabIdFromTarget: tabIdFromTarget - }; - })(); - // Icon-related stuff vAPI.setIcon = function (tabId, iconId, badge) { // If badge is undefined, then setIcon was called from the diff --git a/js/vapi-tabs.js b/js/vapi-tabs.js new file mode 100644 index 0000000..4c9caf5 --- /dev/null +++ b/js/vapi-tabs.js @@ -0,0 +1,735 @@ +/******************************************************************************* + + ηMatrix - a browser extension to black/white list requests. + Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors + Copyright (C) 2019 Alessio Vanni + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://gitlab.com/vannilla/ematrix + uMatrix Home: https://github.com/gorhill/uMatrix +*/ + +/* global self, Components */ + +// For background page (tabs management) + +'use strict'; + +/******************************************************************************/ + +(function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js + + vAPI.noTabId = '-1'; + + vAPI.tabs = {}; + + vAPI.tabs.registerListeners = function() { + tabWatcher.start(); + }; + + vAPI.tabs.get = function (tabId, callback) { + // eMatrix: the following might be obsoleted (though probably + // still relevant at least for Pale Moon.) + // + // Firefox: + // + // browser -> ownerDocument -> defaultView -> gBrowser -> browsers --+ + // ^ | + // | | + // +--------------------------------------------------------------+ + // + // browser (browser) + // contentTitle + // currentURI + // ownerDocument (XULDocument) + // defaultView (ChromeWindow) + // gBrowser (tabbrowser OR browser) + // browsers (browser) + // selectedBrowser + // selectedTab + // tabs (tab.tabbrowser-tab) + // + // Fennec: (what I figured so far) + // + // tab -> browser windows -> window -> BrowserApp -> tabs --+ + // ^ window | + // | | + // +-----------------------------------------------------------+ + // + // tab + // browser + // [manual search to go back to tab from list of windows] + let browser; + + if (tabId === null) { + browser = tabWatcher.currentBrowser(); + tabId = tabWatcher.tabIdFromTarget(browser); + } else { + browser = tabWatcher.browserFromTabId(tabId); + } + + // For internal use + if (typeof callback !== 'function') { + return browser; + } + + if (!browser || !browser.currentURI) { + callback(); + return; + } + + let win = getOwnerWindow(browser); + let tabBrowser = getTabBrowser(win); + + callback({ + id: tabId, + windowId: winWatcher.idFromWindow(win), + active: tabBrowser !== null + && browser === tabBrowser.selectedBrowser, + url: browser.currentURI.asciiSpec, + title: browser.contentTitle + }); + }; + + vAPI.tabs.getAllSync = function (window) { + let win; + let tabs = []; + + for (let win of winWatcher.getWindows()) { + if (window && window !== win) { + continue; + } + + let tabBrowser = getTabBrowser(win); + if (tabBrowser === null) { + continue; + } + + // This can happens if a tab-less window is currently opened. + // Example of a tab-less window: one opened from clicking + // "View Page Source". + if (!tabBrowser.tabs) { + continue; + } + + for (let tab of tabBrowser.tabs) { + tabs.push(tab); + } + } + + return tabs; + }; + + vAPI.tabs.getAll = function (callback) { + let tabs = []; + + for (let browser of tabWatcher.browsers()) { + let tab = tabWatcher.tabFromBrowser(browser); + + if (tab === null) { + continue; + } + + if (tab.hasAttribute('pending')) { + continue; + } + + tabs.push({ + id: tabWatcher.tabIdFromTarget(browser), + url: browser.currentURI.asciiSpec + }); + } + + callback(tabs); + }; + + vAPI.tabs.open = function (details) { + // properties of the details object: + // + url - the address that will be opened + // + tabId:- the tab is used if set, instead of creating a new one + // + index: - undefined: end of the list, -1: following tab, or + // after index + // + active: - opens the tab in background - true and undefined: + // foreground + // + select: - if a tab is already opened with that url, then + // select it instead of opening a new one + if (!details.url) { + return null; + } + + // extension pages + if (/^[\w-]{2,}:/.test(details.url) === false) { + details.url = vAPI.getURL(details.url); + } + + if (details.select) { + let URI = Services.io.newURI(details.url, null, null); + + for (let tab of this.getAllSync()) { + let browser = tabWatcher.browserFromTarget(tab); + + // https://github.com/gorhill/uBlock/issues/2558 + if (browser === null) { + continue; + } + + // Or simply .equals if we care about the fragment + if (URI.equalsExceptRef(browser.currentURI) === false) { + continue; + } + + this.select(tab); + + // Update URL if fragment is different + if (URI.equals(browser.currentURI) === false) { + browser.loadURI(URI.asciiSpec); + } + + return; + } + } + + if (details.active === undefined) { + details.active = true; + } + + if (details.tabId) { + let tab = tabWatcher.browserFromTabId(details.tabId); + + if (tab) { + tabWatcher.browserFromTarget(tab).loadURI(details.url); + return; + } + } + + // Open in a standalone window + if (details.popup === true) { + Services.ww.openWindow(self, + details.url, + null, + 'location=1,menubar=1,personalbar=1,' + +'resizable=1,toolbar=1', + null); + return; + } + + let win = winWatcher.getCurrentWindow(); + let tabBrowser = getTabBrowser(win); + + if (tabBrowser === null) { + return; + } + + if (details.index === -1) { + details.index = + tabBrowser.browsers.indexOf(tabBrowser.selectedBrowser) + 1; + } + + let tab = tabBrowser.loadOneTab(details.url, { + inBackground: !details.active + }); + + if (details.index !== undefined) { + tabBrowser.moveTabTo(tab, details.index); + } + }; + + vAPI.tabs.replace = function (tabId, url) { + // Replace the URL of a tab. Noop if the tab does not exist. + let targetURL = url; + + // extension pages + if (/^[\w-]{2,}:/.test(targetURL) !== true) { + targetURL = vAPI.getURL(targetURL); + } + + let browser = tabWatcher.browserFromTabId(tabId); + if (browser) { + browser.loadURI(targetURL); + } + }; + + function removeInternal(tab, tabBrowser) { + if (tabBrowser) { + tabBrowser.removeTab(tab); + } + } + + vAPI.tabs.remove = function (tabId) { + let browser = tabWatcher.browserFromTabId(tabId); + if (!browser) { + return; + } + + let tab = tabWatcher.tabFromBrowser(browser); + if (!tab) { + return; + } + + removeInternal(tab, getTabBrowser(getOwnerWindow(browser))); + }; + + vAPI.tabs.reload = function (tabId) { + let browser = tabWatcher.browserFromTabId(tabId); + if (!browser) { + return; + } + + browser.webNavigation.reload(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE); + }; + + vAPI.tabs.select = function (tab) { + if (typeof tab !== 'object') { + tab = tabWatcher.tabFromBrowser(tabWatcher.browserFromTabId(tab)); + } + if (!tab) { + return; + } + + // https://github.com/gorhill/uBlock/issues/470 + let win = getOwnerWindow(tab); + win.focus(); + + let tabBrowser = getTabBrowser(win); + if (tabBrowser) { + tabBrowser.selectedTab = tab; + } + }; + + vAPI.tabs.injectScript = function (tabId, details, callback) { + let browser = tabWatcher.browserFromTabId(tabId); + if (!browser) { + return; + } + + if (typeof details.file !== 'string') { + return; + } + + details.file = vAPI.getURL(details.file); + browser.messageManager.sendAsyncMessage(location.host + ':broadcast', + JSON.stringify({ + broadcast: true, + channelName: 'vAPI', + msg: { + cmd: 'injectScript', + details: details + } + })); + + if (typeof callback === 'function') { + vAPI.setTimeout(callback, 13); + } + }; + + let tabWatcher = (function () { + // TODO: find out whether we need a janitor to take care of stale entries. + + // https://github.com/gorhill/uMatrix/issues/540 + // Use only weak references to hold onto browser references. + let browserToTabIdMap = new WeakMap(); + let tabIdToBrowserMap = new Map(); + let tabIdGenerator = 1; + + let indexFromBrowser = function (browser) { + if (!browser) { + return -1; + } + + let win = getOwnerWindow(browser); + if (!win) { + return -1; + } + + let tabBrowser = getTabBrowser(win); + if (tabBrowser === null) { + return -1; + } + + // This can happen, for example, the `view-source:` + // window, there is no tabbrowser object, the browser + // object sits directly in the window. + if (tabBrowser === browser) { + return 0; + } + + return tabBrowser.browsers.indexOf(browser); + }; + + let indexFromTarget = function (target) { + return indexFromBrowser(browserFromTarget(target)); + }; + + let tabFromBrowser = function (browser) { + let i = indexFromBrowser(browser); + if (i === -1) { + return null; + } + + let win = getOwnerWindow(browser); + if (!win) { + return null; + } + + let tabBrowser = getTabBrowser(win); + if (tabBrowser === null) { + return null; + } + + if (!tabBrowser.tabs || i >= tabBrowser.tabs.length) { + return null; + } + + return tabBrowser.tabs[i]; + }; + + let browserFromTarget = function (target) { + if (!target) { + return null; + } + + if (target.linkedPanel) { + // target is a tab + target = target.linkedBrowser; + } + + if (target.localName !== 'browser') { + return null; + } + + return target; + }; + + let tabIdFromTarget = function (target) { + let browser = browserFromTarget(target); + if (browser === null) { + return vAPI.noTabId; + } + + let tabId = browserToTabIdMap.get(browser); + if (tabId === undefined) { + tabId = '' + tabIdGenerator++; + browserToTabIdMap.set(browser, tabId); + tabIdToBrowserMap.set(tabId, Cu.getWeakReference(browser)); + } + + return tabId; + }; + + let browserFromTabId = function (tabId) { + let weakref = tabIdToBrowserMap.get(tabId); + let browser = weakref && weakref.get(); + + return browser || null; + }; + + let currentBrowser = function () { + let win = winWatcher.getCurrentWindow(); + + // https://github.com/gorhill/uBlock/issues/399 + // getTabBrowser() can return null at browser launch time. + let tabBrowser = getTabBrowser(win); + if (tabBrowser === null) { + return null; + } + + return browserFromTarget(tabBrowser.selectedTab); + }; + + let removeBrowserEntry = function (tabId, browser) { + if (tabId && tabId !== vAPI.noTabId) { + vAPI.tabs.onClosed(tabId); + delete vAPI.toolbarButton.tabs[tabId]; + tabIdToBrowserMap.delete(tabId); + } + + if (browser) { + browserToTabIdMap.delete(browser); + } + }; + + let removeTarget = function (target) { + onClose({ + target: target + }); + }; + + let getAllBrowsers = function () { + let browsers = []; + + for (let [tabId, weakref] of tabIdToBrowserMap) { + let browser = weakref.get(); + + // TODO: Maybe call removeBrowserEntry() if the + // browser no longer exists? + if (browser) { + browsers.push(browser); + } + } + + return browsers; + }; + + // var onOpen = function (target) { + // var tabId = tabIdFromTarget(target); + // var browser = browserFromTabId(tabId); + // vAPI.tabs.onNavigation({ + // frameId: 0, + // tabId: tabId, + // url: browser.currentURI.asciiSpec, + // }); + // }; + + var onShow = function ({target}) { + tabIdFromTarget(target); + }; + + var onClose = function ({target}) { + // target is tab in Firefox, browser in Fennec + let browser = browserFromTarget(target); + let tabId = browserToTabIdMap.get(browser); + removeBrowserEntry(tabId, browser); + }; + + var onSelect = function ({target}) { + // This is an entry point: when creating a new tab, it is + // not always reported through onLocationChanged... + // Sigh. It is "reported" here however. + let browser = browserFromTarget(target); + let tabId = browserToTabIdMap.get(browser); + + if (tabId === undefined) { + tabId = tabIdFromTarget(target); + vAPI.tabs.onNavigation({ + frameId: 0, + tabId: tabId, + url: browser.currentURI.asciiSpec + }); + } + + vAPI.setIcon(tabId, getOwnerWindow(target)); + }; + + let locationChangedMessageName = location.host + ':locationChanged'; + + let onLocationChanged = function (e) { + let details = e.data; + + // Ignore notifications related to our popup + if (details.url.lastIndexOf(vAPI.getURL('popup.html'), 0) === 0) { + return; + } + + let browser = e.target; + let tabId = tabIdFromTarget(browser); + if (tabId === vAPI.noTabId) { + return; + } + + // LOCATION_CHANGE_SAME_DOCUMENT = "did not load a new document" + if (details.flags + & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) { + vAPI.tabs.onUpdated(tabId, {url: details.url}, { + frameId: 0, + tabId: tabId, + url: browser.currentURI.asciiSpec + }); + return; + } + + // https://github.com/chrisaljoudi/uBlock/issues/105 + // Allow any kind of pages + vAPI.tabs.onNavigation({ + frameId: 0, + tabId: tabId, + url: details.url + }); + }; + + let attachToTabBrowser = function (window) { + if (typeof vAPI.toolbarButton.attachToNewWindow === 'function') { + vAPI.toolbarButton.attachToNewWindow(window); + } + + let tabBrowser = getTabBrowser(window); + if (tabBrowser === null) { + return; + } + + let tabContainer; + if (tabBrowser.deck) { + // Fennec + tabContainer = tabBrowser.deck; + } else if (tabBrowser.tabContainer) { + // Firefox + tabContainer = tabBrowser.tabContainer; + vAPI.contextMenu.register(document); + } + + // https://github.com/gorhill/uBlock/issues/697 + // Ignore `TabShow` events: unfortunately the `pending` + // attribute is not set when a tab is opened as a result + // of session restore -- it is set *after* the event is + // fired in such case. + if (tabContainer) { + tabContainer.addEventListener('TabShow', onShow); + tabContainer.addEventListener('TabClose', onClose); + // when new window is opened TabSelect doesn't run on + // the selected tab? + tabContainer.addEventListener('TabSelect', onSelect); + } + }; + + var canAttachToTabBrowser = function (window) { + // https://github.com/gorhill/uBlock/issues/906 + // Ensure the environment is ready before trying to attaching. + let document = window && window.document; + if (!document || document.readyState !== 'complete') { + return false; + } + + // On some platforms, the tab browser isn't immediately + // available, try waiting a bit if this + // https://github.com/gorhill/uBlock/issues/763 + // Not getting a tab browser should not prevent from + // attaching ourself to the window. + let tabBrowser = getTabBrowser(window); + if (tabBrowser === null) { + return false; + } + + return winWatcher.toBrowserWindow(window) !== null; + }; + + let onWindowLoad = function (win) { + deferUntil(canAttachToTabBrowser.bind(null, win), + attachToTabBrowser.bind(null, win)); + }; + + let onWindowUnload = function (win) { + vAPI.contextMenu.unregister(win.document); + + let tabBrowser = getTabBrowser(win); + if (tabBrowser === null) { + return; + } + + let tabContainer = tabBrowser.tabContainer; + if (tabContainer) { + tabContainer.removeEventListener('TabShow', onShow); + tabContainer.removeEventListener('TabClose', onClose); + tabContainer.removeEventListener('TabSelect', onSelect); + } + + // https://github.com/gorhill/uBlock/issues/574 + // To keep in mind: not all windows are tab containers, + // sometimes the window IS the tab. + let tabs; + if (tabBrowser.tabs) { + tabs = tabBrowser.tabs; + } else if (tabBrowser.localName === 'browser') { + tabs = [tabBrowser]; + } else { + tabs = []; + } + + let browser; + let URI; + let tabId; + for (let i=tabs.length-1; i>=0; --i) { + let tab = tabs[i]; + browser = browserFromTarget(tab); + if (browser === null) { + continue; + } + + URI = browser.currentURI; + // Close extension tabs + if (URI.schemeIs('chrome') && URI.host === location.host) { + removeInternal(tab, getTabBrowser(win)); + } + + tabId = browserToTabIdMap.get(browser); + if (tabId !== undefined) { + removeBrowserEntry(tabId, browser); + tabIdToBrowserMap.delete(tabId); + } + browserToTabIdMap.delete(browser); + } + }; + + var start = function () { + // Initialize map with existing active tabs + let tabBrowser; + let tabs; + + for (let win of winWatcher.getWindows()) { + onWindowLoad(win); + + tabBrowser = getTabBrowser(win); + if (tabBrowser === null) { + continue; + } + + for (let tab of tabBrowser.tabs) { + if (!tab.hasAttribute('pending')) { + tabIdFromTarget(tab); + } + } + } + + winWatcher.onOpenWindow = onWindowLoad; + winWatcher.onCloseWindow = onWindowUnload; + + vAPI.messaging.globalMessageManager + .addMessageListener(locationChangedMessageName, + onLocationChanged); + }; + + let stop = function () { + winWatcher.onOpenWindow = null; + winWatcher.onCloseWindow = null; + + vAPI.messaging.globalMessageManager + .removeMessageListener(locationChangedMessageName, + onLocationChanged); + + for (let win of winWatcher.getWindows()) { + onWindowUnload(win); + } + + browserToTabIdMap = new WeakMap(); + tabIdToBrowserMap.clear(); + }; + + vAPI.addCleanUpTask(stop); + + return { + browsers: getAllBrowsers, + browserFromTabId: browserFromTabId, + browserFromTarget: browserFromTarget, + currentBrowser: currentBrowser, + indexFromTarget: indexFromTarget, + removeTarget: removeTarget, + start: start, + tabFromBrowser: tabFromBrowser, + tabIdFromTarget: tabIdFromTarget + }; + })(); +})(); -- cgit v1.2.3 From fa59204140f82c9516e9c4cb7afc2718abf5baaa Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Fri, 21 Jun 2019 22:09:37 +0200 Subject: Make some private entities public Since things have been split, some of these have to be exposed to the rest of the world. --- js/vapi-background.js | 50 ++++++++++++++++++++++++++------------------------ js/vapi-tabs.js | 26 +++++++++++++------------- 2 files changed, 39 insertions(+), 37 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index d939186..10f7b2b 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -41,28 +41,6 @@ Services.appinfo.ID === '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}' && Services.vc.compare(Services.appinfo.version, '44') > 0; - let deferUntil = function (testFn, mainFn, details) { - let dtls = (typeof details !== 'object') ? {} : details; - let now = 0; - let next = dtls.next || 200; - let until = dtls.until || 2000; - - let check = function () { - if (testFn() === true || now >= until) { - mainFn(); - return; - } - now += next; - vAPI.setTimeout(check, next); - }; - - if ('sync' in dtls && dtls.sync === true) { - check(); - } else { - vAPI.setTimeout(check, 1); - } - }; - vAPI.app = { name: 'eMatrix', version: location.hash.slice(1), @@ -98,6 +76,28 @@ cleanupTasks.push(task); } + vAPI.deferUntil = function (testFn, mainFn, details) { + let dtls = (typeof details !== 'object') ? {} : details; + let now = 0; + let next = dtls.next || 200; + let until = dtls.until || 2000; + + let check = function () { + if (testFn() === true || now >= until) { + mainFn(); + return; + } + now += next; + vAPI.setTimeout(check, next); + }; + + if ('sync' in dtls && dtls.sync === true) { + check(); + } else { + vAPI.setTimeout(check, 1); + } + }; + window.addEventListener('unload', function () { // if (typeof vAPI.app.onShutdown === 'function') { // vAPI.app.onShutdown(); @@ -762,6 +762,8 @@ return api; })(); + vAPI.window = winWatcher; + let getTabBrowser = function (win) { return win && win.gBrowser || null; }; @@ -1961,7 +1963,7 @@ }; tbb.attachToNewWindow = function (win) { - deferUntil(canAddLegacyToolbarButton.bind(null, win), + vAPI.deferUntil(canAddLegacyToolbarButton.bind(null, win), addLegacyToolbarButton.bind(null, win)); }; @@ -2577,7 +2579,7 @@ 'addon-options-displayed', false); cleanupTasks.push(unregister); - deferUntil(canInit, init, { next: 463 }); + vAPI.deferUntil(canInit, init, { next: 463 }); }; return { diff --git a/js/vapi-tabs.js b/js/vapi-tabs.js index 4c9caf5..97d0f69 100644 --- a/js/vapi-tabs.js +++ b/js/vapi-tabs.js @@ -99,7 +99,7 @@ callback({ id: tabId, - windowId: winWatcher.idFromWindow(win), + windowId: vAPI.window.idFromWindow(win), active: tabBrowser !== null && browser === tabBrowser.selectedBrowser, url: browser.currentURI.asciiSpec, @@ -111,7 +111,7 @@ let win; let tabs = []; - for (let win of winWatcher.getWindows()) { + for (let win of vAPI.window.getWindows()) { if (window && window !== win) { continue; } @@ -229,7 +229,7 @@ return; } - let win = winWatcher.getCurrentWindow(); + let win = vAPI.window.getCurrentWindow(); let tabBrowser = getTabBrowser(win); if (tabBrowser === null) { @@ -440,7 +440,7 @@ }; let currentBrowser = function () { - let win = winWatcher.getCurrentWindow(); + let win = vAPI.window.getCurrentWindow(); // https://github.com/gorhill/uBlock/issues/399 // getTabBrowser() can return null at browser launch time. @@ -614,12 +614,12 @@ return false; } - return winWatcher.toBrowserWindow(window) !== null; + return vAPI.window.toBrowserWindow(window) !== null; }; let onWindowLoad = function (win) { - deferUntil(canAttachToTabBrowser.bind(null, win), - attachToTabBrowser.bind(null, win)); + vAPI.deferUntil(canAttachToTabBrowser.bind(null, win), + attachToTabBrowser.bind(null, win)); }; let onWindowUnload = function (win) { @@ -679,7 +679,7 @@ let tabBrowser; let tabs; - for (let win of winWatcher.getWindows()) { + for (let win of vAPI.window.getWindows()) { onWindowLoad(win); tabBrowser = getTabBrowser(win); @@ -694,8 +694,8 @@ } } - winWatcher.onOpenWindow = onWindowLoad; - winWatcher.onCloseWindow = onWindowUnload; + vAPI.window.onOpenWindow = onWindowLoad; + vAPI.window.onCloseWindow = onWindowUnload; vAPI.messaging.globalMessageManager .addMessageListener(locationChangedMessageName, @@ -703,14 +703,14 @@ }; let stop = function () { - winWatcher.onOpenWindow = null; - winWatcher.onCloseWindow = null; + vAPI.window.onOpenWindow = null; + vAPI.window.onCloseWindow = null; vAPI.messaging.globalMessageManager .removeMessageListener(locationChangedMessageName, onLocationChanged); - for (let win of winWatcher.getWindows()) { + for (let win of vAPI.window.getWindows()) { onWindowUnload(win); } -- cgit v1.2.3 From 6ce63ba79e0d2d9f9d0ca4821c259d4b61dee769 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Fri, 21 Jun 2019 22:19:10 +0200 Subject: Keep refactoring Exported stuff to handle the splitting. --- js/vapi-background.js | 46 +++++++++++++++++++++++++++------------------- js/vapi-tabs.js | 42 ++++++++++++++++++++++-------------------- 2 files changed, 49 insertions(+), 39 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 10f7b2b..109a06b 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -764,11 +764,14 @@ vAPI.window = winWatcher; - let getTabBrowser = function (win) { + // browser-handling utilities + vAPI.browser = {}; + + vAPI.browser.getTabBrowser = function (win) { return win && win.gBrowser || null; }; - let getOwnerWindow = function (target) { + vAPI.browser.getOwnerWindow = function (target) { if (target.ownerDocument) { return target.ownerDocument.defaultView; } @@ -776,6 +779,8 @@ return null; }; + vAPI.noTabId = '-1'; + vAPI.isBehindTheSceneTabId = function (tabId) { return tabId.toString() === '-1'; }; @@ -791,12 +796,12 @@ win = winWatcher.getCurrentWindow(); } - let tabBrowser = getTabBrowser(win); + let tabBrowser = vAPI.browser.getTabBrowser(win); if (tabBrowser === null) { return; } - let curTabId = tabWatcher.tabIdFromTarget(tabBrowser.selectedTab); + let curTabId = vAPI.tabs.manager.tabIdFromTarget(tabBrowser.selectedTab); let tb = vAPI.toolbarButton; // from 'TabSelect' event @@ -857,7 +862,7 @@ let sender = { tab: { - id: tabWatcher.tabIdFromTarget(target) + id: vAPI.tabs.manager.tabIdFromTarget(target) } }; @@ -1325,7 +1330,7 @@ } if (lc.topFrameElement) { - return tabWatcher.tabIdFromTarget(lc.topFrameElement); + return vAPI.tabs.manager.tabIdFromTarget(lc.topFrameElement); } let win; @@ -1346,11 +1351,12 @@ let tabBrowser; try { tabBrowser = - getTabBrowser(win.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShell).rootTreeItem - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow)); + vAPI.browser.getTabBrowser + (win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell).rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow)); } catch (ex) { // Ignore } @@ -1360,14 +1366,15 @@ } if (tabBrowser.getBrowserForContentWindow) { - return tabWatcher.tabIdFromTarget(tabBrowser.getBrowserForContentWindow(win)); + return vAPI.tabs.manager + .tabIdFromTarget(tabBrowser.getBrowserForContentWindow(win)); } // Falling back onto _getTabForContentWindow to ensure older // versions of Firefox work well. return tabBrowser._getTabForContentWindow - ? tabWatcher.tabIdFromTarget(tabBrowser - ._getTabForContentWindow(win)) + ? vAPI.tabs.manager + .tabIdFromTarget(tabBrowser._getTabForContentWindow(win)) : vAPI.noTabId; }, rawtypeFromContentType: function (channel) { @@ -1539,7 +1546,7 @@ let details = e.data; let pendingReq = httpObserver.createPendingRequest(details.url); pendingReq.rawType = details.rawType; - pendingReq.tabId = tabWatcher.tabIdFromTarget(e.target); + pendingReq.tabId = vAPI.tabs.manager.tabIdFromTarget(e.target); }; // https://github.com/gorhill/uMatrix/issues/200 @@ -2479,7 +2486,7 @@ } callback(details, { - id: tabWatcher.tabIdFromTarget(gContextMenu.browser), + id: vAPI.tabs.manager.tabIdFromTarget(gContextMenu.browser), url: gContextMenu.browser.currentURI.asciiSpec }); }; @@ -2551,7 +2558,7 @@ // Older versions of Firefox can throw here when looking // up `currentURI`. try { - let tabBrowser = tabWatcher.currentBrowser(); + let tabBrowser = vAPI.tabs.manager.currentBrowser(); return tabBrowser && tabBrowser.currentURI && tabBrowser.currentURI.spec === 'about:addons' @@ -2566,7 +2573,8 @@ // already opened. let init = function () { if (canInit()) { - setupOptionsButtons(tabWatcher.currentBrowser().contentDocument); + setupOptionsButtons(vAPI.tabs.manager + .currentBrowser().contentDocument); } }; @@ -2600,7 +2608,7 @@ // to inject content scripts in already opened web pages, to // remove whatever nuisance could make it to the web pages // before uBlock was ready. - for (let browser of tabWatcher.browsers()) { + for (let browser of vAPI.tabs.manager.browsers()) { browser.messageManager .sendAsyncMessage(location.host + '-load-completed'); } diff --git a/js/vapi-tabs.js b/js/vapi-tabs.js index 97d0f69..0e71358 100644 --- a/js/vapi-tabs.js +++ b/js/vapi-tabs.js @@ -35,8 +35,6 @@ let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js - vAPI.noTabId = '-1'; - vAPI.tabs = {}; vAPI.tabs.registerListeners = function() { @@ -94,8 +92,8 @@ return; } - let win = getOwnerWindow(browser); - let tabBrowser = getTabBrowser(win); + let win = vAPI.browser.getOwnerWindow(browser); + let tabBrowser = vAPI.browser.getTabBrowser(win); callback({ id: tabId, @@ -116,7 +114,7 @@ continue; } - let tabBrowser = getTabBrowser(win); + let tabBrowser = vAPI.browser.getTabBrowser(win); if (tabBrowser === null) { continue; } @@ -230,7 +228,7 @@ } let win = vAPI.window.getCurrentWindow(); - let tabBrowser = getTabBrowser(win); + let tabBrowser = vAPI.browser.getTabBrowser(win); if (tabBrowser === null) { return; @@ -282,7 +280,9 @@ return; } - removeInternal(tab, getTabBrowser(getOwnerWindow(browser))); + removeInternal(tab, + vAPI.browser.getTabBrowser + (vAPI.browser.getOwnerWindow(browser))); }; vAPI.tabs.reload = function (tabId) { @@ -303,10 +303,10 @@ } // https://github.com/gorhill/uBlock/issues/470 - let win = getOwnerWindow(tab); + let win = vAPI.browser.getOwnerWindow(tab); win.focus(); - let tabBrowser = getTabBrowser(win); + let tabBrowser = vAPI.browser.getTabBrowser(win); if (tabBrowser) { tabBrowser.selectedTab = tab; } @@ -352,12 +352,12 @@ return -1; } - let win = getOwnerWindow(browser); + let win = vAPI.browser.getOwnerWindow(browser); if (!win) { return -1; } - let tabBrowser = getTabBrowser(win); + let tabBrowser = vAPI.browser.getTabBrowser(win); if (tabBrowser === null) { return -1; } @@ -382,12 +382,12 @@ return null; } - let win = getOwnerWindow(browser); + let win = vAPI.browser.getOwnerWindow(browser); if (!win) { return null; } - let tabBrowser = getTabBrowser(win); + let tabBrowser = vAPI.browser.getTabBrowser(win); if (tabBrowser === null) { return null; } @@ -444,7 +444,7 @@ // https://github.com/gorhill/uBlock/issues/399 // getTabBrowser() can return null at browser launch time. - let tabBrowser = getTabBrowser(win); + let tabBrowser = vAPI.browser.getTabBrowser(win); if (tabBrowser === null) { return null; } @@ -523,7 +523,7 @@ }); } - vAPI.setIcon(tabId, getOwnerWindow(target)); + vAPI.setIcon(tabId, vAPI.browser.getOwnerWindow(target)); }; let locationChangedMessageName = location.host + ':locationChanged'; @@ -567,7 +567,7 @@ vAPI.toolbarButton.attachToNewWindow(window); } - let tabBrowser = getTabBrowser(window); + let tabBrowser = vAPI.browser.getTabBrowser(window); if (tabBrowser === null) { return; } @@ -609,7 +609,7 @@ // https://github.com/gorhill/uBlock/issues/763 // Not getting a tab browser should not prevent from // attaching ourself to the window. - let tabBrowser = getTabBrowser(window); + let tabBrowser = vAPI.browser.getTabBrowser(window); if (tabBrowser === null) { return false; } @@ -625,7 +625,7 @@ let onWindowUnload = function (win) { vAPI.contextMenu.unregister(win.document); - let tabBrowser = getTabBrowser(win); + let tabBrowser = vAPI.browser.getTabBrowser(win); if (tabBrowser === null) { return; } @@ -662,7 +662,7 @@ URI = browser.currentURI; // Close extension tabs if (URI.schemeIs('chrome') && URI.host === location.host) { - removeInternal(tab, getTabBrowser(win)); + removeInternal(tab, vAPI.browser.getTabBrowser(win)); } tabId = browserToTabIdMap.get(browser); @@ -682,7 +682,7 @@ for (let win of vAPI.window.getWindows()) { onWindowLoad(win); - tabBrowser = getTabBrowser(win); + tabBrowser = vAPI.browser.getTabBrowser(win); if (tabBrowser === null) { continue; } @@ -732,4 +732,6 @@ tabIdFromTarget: tabIdFromTarget }; })(); + + vAPI.tabs.manager = tabWatcher; })(); -- cgit v1.2.3 From 64316b310f2908dd5dd8ffbfd2d36e9d14ac6a40 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Fri, 21 Jun 2019 23:58:56 +0200 Subject: Fix missing namespace --- js/vapi-background.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 109a06b..a1b70e4 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -843,6 +843,7 @@ // not usable. So instead we broadcast to the parent // window. messageManager = + vAPI.browser. getOwnerWindow(target.webNavigation .QueryInterface(Ci.nsIDocShell) .chromeEventHandler).messageManager; @@ -2461,7 +2462,7 @@ } this.onCommand = function () { - let gContextMenu = getOwnerWindow(this).gContextMenu; + let gContextMenu = vAPI.browser.getOwnerWindow(this).gContextMenu; let details = { menuItemId: this.id }; -- cgit v1.2.3 From fc2df27d5768294f22a4d9ad049d45f2d0d52781 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Sat, 22 Jun 2019 00:12:53 +0200 Subject: Remove some cruft --- js/vapi-background.js | 1 - js/vapi-client.js | 8 +++++--- js/vapi-common.js | 6 ++++-- 3 files changed, 9 insertions(+), 6 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index a1b70e4..fbb7353 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -36,7 +36,6 @@ let vAPI = self.vAPI = self.vAPI || {}; - vAPI.firefox = true; vAPI.modernFirefox = Services.appinfo.ID === '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}' && Services.vc.compare(Services.appinfo.version, '44') > 0; diff --git a/js/vapi-client.js b/js/vapi-client.js index 20e16f9..6f46e96 100644 --- a/js/vapi-client.js +++ b/js/vapi-client.js @@ -35,12 +35,14 @@ /******************************************************************************/ // https://bugs.chromium.org/p/project-zero/issues/detail?id=1225&desc=6#c10 -if ( self.vAPI === undefined || self.vAPI.eMatrix !== true ) { - self.vAPI = { eMatrix: true }; +// eMatrix: does this apply to us? +// In the meantime, let's remove the useless `eMatrix' property from vAPI +if (self.vAPI === undefined) { + self.vAPI = {}; } var vAPI = self.vAPI; -vAPI.firefox = true; + vAPI.sessionId = String.fromCharCode(Date.now() % 25 + 97) + Math.random().toString(36).slice(2); diff --git a/js/vapi-common.js b/js/vapi-common.js index 6946a9c..f1bc2ca 100644 --- a/js/vapi-common.js +++ b/js/vapi-common.js @@ -39,8 +39,10 @@ const {Services} = Components.utils.import( ); // https://bugs.chromium.org/p/project-zero/issues/detail?id=1225&desc=6#c10 -if ( self.vAPI === undefined || self.vAPI.eMatrix !== true ) { - self.vAPI = { eMatrix: true }; +// eMatrix: does this apply to us? +// In the meantime, let's remove the useless `eMatrix' property from vAPI +if (self.vAPI === undefined) { + self.vAPI = {}; } var vAPI = self.vAPI; -- cgit v1.2.3 From d5f07761dcaad4128a222537fc36d49e1debb55b Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Sat, 22 Jun 2019 00:17:30 +0200 Subject: More refactoring --- js/vapi-background.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index fbb7353..a482af6 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -60,7 +60,7 @@ // List of things that needs to be destroyed when disabling the extension // Only functions should be added to it - + // eMatrix: taken care by vAPI.addCleanUpTask --- use that function let cleanupTasks = []; // This must be updated manually, every time a new task is added/removed @@ -301,7 +301,8 @@ }, }; - cleanupTasks.push(vAPI.browserSettings.restoreAll.bind(vAPI.browserSettings)); + vAPI.addCleanUpTask(vAPI.browserSettings + .restoreAll.bind(vAPI.browserSettings)); // API matches that of chrome.storage.local: // https://developer.chrome.com/extensions/storage @@ -363,7 +364,7 @@ } // Database was opened, register cleanup task - cleanupTasks.push(close); + vAPI.addCleanUpTask(close); // Setup database db.createAsyncStatement('CREATE TABLE IF NOT EXISTS ' @@ -752,7 +753,7 @@ Services.ww.registerNotification(listeners); })(); - cleanupTasks.push(function() { + vAPI.addCleanUpTask(function() { Services.wm.removeListener(listeners); Services.ww.unregisterNotification(listeners); windowToIdMap.clear(); @@ -908,7 +909,7 @@ this.onMessage); this.globalMessageManager.loadFrameScript(this.frameScript, true); - cleanupTasks.push(function () { + vAPI.addCleanUpTask(function () { let gmm = vAPI.messaging.globalMessageManager; gmm.removeDelayedFrameScript(vAPI.messaging.frameScript); @@ -1560,7 +1561,7 @@ httpObserver.register(); - cleanupTasks.push(function () { + vAPI.addCleanUpTask(function () { if (!vAPI.modernFirefox) { vAPI.messaging.globalMessageManager .removeMessageListener(shouldLoadListenerMessageName, @@ -1979,7 +1980,7 @@ .addMessageListener(location.host + ':closePopup', onPopupCloseRequested); - cleanupTasks.push(shutdown); + vAPI.addCleanUpTask(shutdown); }; })(); @@ -2304,7 +2305,7 @@ CustomizableUI.createWidget(this); - cleanupTasks.push(shutdown); + vAPI.addCleanUpTask(shutdown); }; })(); @@ -2315,7 +2316,7 @@ // button code is one single cleanup task regardless of platform. // eMatrix: might not be needed anymore if (vAPI.toolbarButton.init === null) { - cleanupTasks.push(function(){}); + vAPI.addCleanUpTask(function(){}); } })(); @@ -2586,7 +2587,7 @@ Services.obs.addObserver(observer, 'addon-options-displayed', false); - cleanupTasks.push(unregister); + vAPI.addCleanUpTask(unregister); vAPI.deferUntil(canInit, init, { next: 463 }); }; @@ -2801,7 +2802,7 @@ vAPI.cookies.start = function () { Services.obs.addObserver(this, 'cookie-changed', false); Services.obs.addObserver(this, 'private-cookie-changed', false); - cleanupTasks.push(this.stop.bind(this)); + vAPI.addCleanUpTask(this.stop.bind(this)); }; vAPI.cookies.stop = function () { -- cgit v1.2.3 From 3cc75d0a0da35336b9c12377ea1c63990f55be31 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Sat, 22 Jun 2019 00:18:14 +0200 Subject: Remove commented-out region --- js/vapi-background.js | 132 -------------------------------------------------- 1 file changed, 132 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index a482af6..a809b48 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -1984,138 +1984,6 @@ }; })(); - - /* - (function() { - // Add toolbar button for Basilisk - if (Services.appinfo.ID !== "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") { - return; - } - - var tbb = vAPI.toolbarButton; - if ( tbb.init !== null ) { - return; - } - - if ( Services.vc.compare(Services.appinfo.version, '36.0') >= 0 ) { - return null; - } - var CustomizableUI = null; - try { - CustomizableUI = Cu.import('resource:///modules/CustomizableUI.jsm', null).CustomizableUI; - } catch (ex) { - } - if ( CustomizableUI === null ) { - return; - } - tbb.codePath = 'australis'; - tbb.CustomizableUI = CustomizableUI; - tbb.defaultArea = CustomizableUI.AREA_NAVBAR; - - var styleURI = null; - - var onPopupCloseRequested = function({target}) { - if ( typeof tbb.closePopup === 'function' ) { - tbb.closePopup(target); - } - }; - - var shutdown = function() { - CustomizableUI.destroyWidget(tbb.id); - - for ( var win of winWatcher.getWindows() ) { - var panel = win.document.getElementById(tbb.viewId); - panel.parentNode.removeChild(panel); - win.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils) - .removeSheet(styleURI, 1); - } - - vAPI.messaging.globalMessageManager.removeMessageListener( - location.host + ':closePopup', - onPopupCloseRequested - ); - }; - - tbb.onBeforeCreated = function(doc) { - var panel = doc.createElement('panelview'); - - this.populatePanel(doc, panel); - - doc.getElementById('PanelUI-multiView').appendChild(panel); - - doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils) - .loadSheet(styleURI, 1); - }; - - tbb.onBeforePopupReady = function() { - // https://github.com/gorhill/uBlock/issues/83 - // Add `portrait` class if width is constrained. - try { - this.contentDocument.body.classList.toggle( - 'portrait', - CustomizableUI.getWidget(tbb.id).areaType === CustomizableUI.TYPE_MENU_PANEL - ); - } catch (ex) { - // noop - } - }; - - tbb.init = function() { - vAPI.messaging.globalMessageManager.addMessageListener( - location.host + ':closePopup', - onPopupCloseRequested - ); - - var style = [ - '#' + this.id + '.off {', - 'list-style-image: url(', - vAPI.getURL('img/browsericons/icon19-off.png'), - ');', - '}', - '#' + this.id + ' {', - 'list-style-image: url(', - vAPI.getURL('img/browsericons/icon19.png'), - ');', - '}', - '#' + this.viewId + ', #' + this.viewId + ' > iframe {', - 'width: 160px;', - 'height: 290px;', - 'overflow: hidden !important;', - '}', - '#' + this.id + '[badge]:not([badge=""])::after {', - 'position: absolute;', - 'margin-left: -16px;', - 'margin-top: 3px;', - 'padding: 1px 2px;', - 'font-size: 9px;', - 'font-weight: bold;', - 'color: #fff;', - 'background: #000;', - 'content: attr(badge);', - '}' - ]; - - styleURI = Services.io.newURI( - 'data:text/css,' + encodeURIComponent(style.join('')), - null, - null - ); - - this.closePopup = function(tabBrowser) { - CustomizableUI.hidePanelForNode( - tabBrowser.ownerDocument.getElementById(this.viewId) - ); - }; - - CustomizableUI.createWidget(this); - - cleanupTasks.push(shutdown); - }; - })(); - */ - (function() { // It appears that this branch actually works on the latest // Basilisk. Maybe we can simply use this one directly instead of -- cgit v1.2.3 From 50aff81e575d92fc3ac4a6194fffa7ea89344e2c Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Sat, 22 Jun 2019 00:34:29 +0200 Subject: Move some commonly used functions out of vapi-background --- js/vapi-core.js | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 js/vapi-core.js (limited to 'js') diff --git a/js/vapi-core.js b/js/vapi-core.js new file mode 100644 index 0000000..bc7bcb9 --- /dev/null +++ b/js/vapi-core.js @@ -0,0 +1,109 @@ +/******************************************************************************* + + ηMatrix - a browser extension to black/white list requests. + Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors + Copyright (C) 2019 Alessio Vanni + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://gitlab.com/vannilla/ematrix + uMatrix Home: https://github.com/gorhill/uMatrix +*/ + +/* global self, Components */ + +// For background page (important functions) + +'use strict'; + +/******************************************************************************/ + +(function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + + let vAPI = self.vAPI = self.vAPI || {}; + + // List of things that needs to be destroyed when disabling the extension + // Only functions should be added to it + // eMatrix: taken care by vAPI.addCleanUpTask --- use that function + let cleanupTasks = []; + + // This must be updated manually, every time a new task is added/removed + // eMatrix: do we? + let expectedNumberOfCleanups = 7; + + vAPI.addCleanUpTask = function (task) { + if (typeof task !== 'function') { + return; + } + + cleanupTasks.push(task); + }; + + vAPI.deferUntil = function (testFn, mainFn, details) { + let dtls = (typeof details !== 'object') ? {} : details; + let now = 0; + let next = dtls.next || 200; + let until = dtls.until || 2000; + + let check = function () { + if (testFn() === true || now >= until) { + mainFn(); + return; + } + now += next; + vAPI.setTimeout(check, next); + }; + + if ('sync' in dtls && dtls.sync === true) { + check(); + } else { + vAPI.setTimeout(check, 1); + } + }; + + window.addEventListener('unload', function () { + // if (typeof vAPI.app.onShutdown === 'function') { + // vAPI.app.onShutdown(); + // } + + // IMPORTANT: cleanup tasks must be executed using LIFO order. + for (let i=cleanupTasks.length-1; i>=0; --i) { + try { + cleanupTasks[i](); + } catch (e) { + // Just in case a clean up task ends up throwing for + // no reason + console.error(e); + } + } + + // eMatrix: temporarily disabled + // if (cleanupTasks.length < expectedNumberOfCleanups) { + // console.error + // ('eMatrix> Cleanup tasks performed: %s (out of %s)', + // cleanupTasks.length, + // expectedNumberOfCleanups); + // } + + // frameModule needs to be cleared too + let frameModuleURL = vAPI.getURL('frameModule.js'); + let frameModule = {}; + + Cu.import(frameModuleURL, frameModule); + frameModule.contentObserver.unregister(); + Cu.unload(frameModuleURL); + }); +})(); -- cgit v1.2.3 From 89a737419d54d31fb6d312f1ac87b4ff4f2de75a Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Sat, 22 Jun 2019 00:35:25 +0200 Subject: Put window management into its own file --- js/vapi-background.js | 245 ++------------------------------------------------ js/vapi-window.js | 191 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+), 238 deletions(-) create mode 100644 js/vapi-window.js (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index a809b48..d03a205 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -57,79 +57,6 @@ }, }; - - // List of things that needs to be destroyed when disabling the extension - // Only functions should be added to it - // eMatrix: taken care by vAPI.addCleanUpTask --- use that function - let cleanupTasks = []; - - // This must be updated manually, every time a new task is added/removed - // eMatrix: do we? - let expectedNumberOfCleanups = 7; - - vAPI.addCleanUpTask = function (task) { - if (typeof task !== 'function') { - return; - } - - cleanupTasks.push(task); - } - - vAPI.deferUntil = function (testFn, mainFn, details) { - let dtls = (typeof details !== 'object') ? {} : details; - let now = 0; - let next = dtls.next || 200; - let until = dtls.until || 2000; - - let check = function () { - if (testFn() === true || now >= until) { - mainFn(); - return; - } - now += next; - vAPI.setTimeout(check, next); - }; - - if ('sync' in dtls && dtls.sync === true) { - check(); - } else { - vAPI.setTimeout(check, 1); - } - }; - - window.addEventListener('unload', function () { - // if (typeof vAPI.app.onShutdown === 'function') { - // vAPI.app.onShutdown(); - // } - - // IMPORTANT: cleanup tasks must be executed using LIFO order. - for (let i=cleanupTasks.length-1; i>=0; --i) { - try { - cleanupTasks[i](); - } catch (e) { - // Just in case a clean up task ends up throwing for - // no reason - console.error(e); - } - } - - // eMatrix: temporarily disabled - // if (cleanupTasks.length < expectedNumberOfCleanups) { - // console.error - // ('eMatrix> Cleanup tasks performed: %s (out of %s)', - // cleanupTasks.length, - // expectedNumberOfCleanups); - // } - - // frameModule needs to be cleared too - let frameModuleURL = vAPI.getURL('frameModule.js'); - let frameModule = {}; - - Cu.import(frameModuleURL, frameModule); - frameModule.contentObserver.unregister(); - Cu.unload(frameModuleURL); - }); - vAPI.browserSettings = { // For now, only booleans. originalValues: {}, @@ -606,164 +533,6 @@ vAPI.cacheStorage = vAPI.storage; - // This must be executed/setup early. - let winWatcher = (function () { - let windowToIdMap = new Map(); - let windowIdGenerator = 1; - let api = { - onOpenWindow: null, - onCloseWindow: null - }; - - // https://github.com/gorhill/uMatrix/issues/586 This is - // necessary hack because on SeaMonkey 2.40, for unknown - // reasons private windows do not have the attribute - // `windowtype` set to `navigator:browser`. As a fallback, the - // code here will also test whether the id attribute is - // `main-window`. - api.toBrowserWindow = function (win) { - let docElement = win && win.document - && win.document.documentElement; - - if (!docElement) { - return null; - } - if (vAPI.thunderbird) { - return docElement.getAttribute('windowtype') === 'mail:3pane' - ? win - : null; - } - - return docElement.getAttribute('windowtype') === 'navigator:browser' - || docElement.getAttribute('id') === 'main-window' - ? win - : null; - }; - - api.getWindows = function () { - return windowToIdMap.keys(); - }; - - api.idFromWindow = function (win) { - return windowToIdMap.get(win) || 0; - }; - - api.getCurrentWindow = function () { - return this.toBrowserWindow(Services.wm.getMostRecentWindow(null)); - }; - - let addWindow = function (win) { - if (!win || windowToIdMap.has(win)) { - return; - } - - windowToIdMap.set(win, windowIdGenerator++); - - if (typeof api.onOpenWindow === 'function') { - api.onOpenWindow(win); - } - }; - - let removeWindow = function (win) { - if (!win || windowToIdMap.delete(win) !== true) { - return; - } - - if (typeof api.onCloseWindow === 'function') { - api.onCloseWindow(win); - } - }; - - // https://github.com/gorhill/uMatrix/issues/357 - // Use nsIWindowMediator for being notified of opened/closed windows. - let listeners = { - onOpenWindow: function (aWindow) { - let win; - try { - win = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); - } catch (e) { - // Ignore - } - - addWindow(win); - }, - onCloseWindow: function (aWindow) { - let win; - try { - win = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); - } catch (e) { - // Ignore - } - - removeWindow(win); - }, - observe: function (aSubject, topic) { - let win; - try { - win = aSubject.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); - } catch (e) { - // Ignore - } - - if (!win) { - return; - } - - switch (topic) { - case 'domwindowopened': - addWindow(win); - break; - case 'domwindowclosed': - removeWindow(win); - break; - default: - console.error('unknown observer topic'); - break; - } - } - }; - - (function() { - let winumerator; - - winumerator = Services.wm.getEnumerator(null); - while (winumerator.hasMoreElements()) { - let win = winumerator.getNext(); - - if (!win.closed) { - windowToIdMap.set(win, windowIdGenerator++); - } - } - - winumerator = Services.ww.getWindowEnumerator(); - while (winumerator.hasMoreElements()) { - let win = winumerator.getNext() - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow); - - if (!win.closed) { - windowToIdMap.set(win, windowIdGenerator++); - } - } - - Services.wm.addListener(listeners); - Services.ww.registerNotification(listeners); - })(); - - vAPI.addCleanUpTask(function() { - Services.wm.removeListener(listeners); - Services.ww.unregisterNotification(listeners); - windowToIdMap.clear(); - }); - - return api; - })(); - - vAPI.window = winWatcher; - // browser-handling utilities vAPI.browser = {}; @@ -793,7 +562,7 @@ if (badge === undefined) { win = iconId; } else { - win = winWatcher.getCurrentWindow(); + win = vAPI.window.getCurrentWindow(); } let tabBrowser = vAPI.browser.getTabBrowser(win); @@ -1949,7 +1718,7 @@ }; let shutdown = function () { - for (let win of winWatcher.getWindows()) { + for (let win of vAPI.window.getWindows()) { let toolbarButton = win.document.getElementById(tbb.id); if (toolbarButton) { toolbarButton.parentNode.removeChild(toolbarButton); @@ -2024,7 +1793,7 @@ let badgeCSSRules = 'background: #000;color: #fff'; let updateBadgeStyle = function () { - for (let win of winWatcher.getWindows()) { + for (let win of vAPI.window.getWindows()) { let button = win.document.getElementById(tbb.id); if (button === null) { continue; @@ -2047,7 +1816,7 @@ CustomizableUI.getWidget(wId).areaType === CustomizableUI.TYPE_MENU_PANEL; - for (let win of winWatcher.getWindows()) { + for (let win of vAPI.window.getWindows()) { let button = win.document.getElementById(wId); if (button === null) { continue; @@ -2080,7 +1849,7 @@ }; let shutdown = function () { - for (let win of winWatcher.getWindows()) { + for (let win of vAPI.window.getWindows()) { let panel = win.document.getElementById(tbb.viewId); if (panel !== null && panel.parentNode !== null) { panel.parentNode.removeChild(panel); @@ -2360,13 +2129,13 @@ }); }; - for (let win of winWatcher.getWindows()) { + for (let win of vAPI.window.getWindows()) { this.register(win.document); } }; vAPI.contextMenu.remove = function () { - for (let win of winWatcher.getWindows()) { + for (let win of vAPI.window.getWindows()) { this.unregister(win.document); } diff --git a/js/vapi-window.js b/js/vapi-window.js new file mode 100644 index 0000000..ff0e9fe --- /dev/null +++ b/js/vapi-window.js @@ -0,0 +1,191 @@ +/******************************************************************************* + + ηMatrix - a browser extension to black/white list requests. + Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors + Copyright (C) 2019 Alessio Vanni + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://gitlab.com/vannilla/ematrix + uMatrix Home: https://github.com/gorhill/uMatrix +*/ + +/* global self, Components */ + +// For background page (windows management) +'use strict'; + +/******************************************************************************/ + +(function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js + + vAPI.window = (function () { + let windowToIdMap = new Map(); + let windowIdGenerator = 1; + let api = { + onOpenWindow: null, + onCloseWindow: null + }; + + // https://github.com/gorhill/uMatrix/issues/586 This is + // necessary hack because on SeaMonkey 2.40, for unknown + // reasons private windows do not have the attribute + // `windowtype` set to `navigator:browser`. As a fallback, the + // code here will also test whether the id attribute is + // `main-window`. + api.toBrowserWindow = function (win) { + let docElement = win && win.document + && win.document.documentElement; + + if (!docElement) { + return null; + } + if (vAPI.thunderbird) { + return docElement.getAttribute('windowtype') === 'mail:3pane' + ? win + : null; + } + + return docElement.getAttribute('windowtype') === 'navigator:browser' + || docElement.getAttribute('id') === 'main-window' + ? win + : null; + }; + + api.getWindows = function () { + return windowToIdMap.keys(); + }; + + api.idFromWindow = function (win) { + return windowToIdMap.get(win) || 0; + }; + + api.getCurrentWindow = function () { + return this.toBrowserWindow(Services.wm.getMostRecentWindow(null)); + }; + + let addWindow = function (win) { + if (!win || windowToIdMap.has(win)) { + return; + } + + windowToIdMap.set(win, windowIdGenerator++); + + if (typeof api.onOpenWindow === 'function') { + api.onOpenWindow(win); + } + }; + + let removeWindow = function (win) { + if (!win || windowToIdMap.delete(win) !== true) { + return; + } + + if (typeof api.onCloseWindow === 'function') { + api.onCloseWindow(win); + } + }; + + // https://github.com/gorhill/uMatrix/issues/357 + // Use nsIWindowMediator for being notified of opened/closed windows. + let listeners = { + onOpenWindow: function (aWindow) { + let win; + try { + win = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + } catch (e) { + // Ignore + } + + addWindow(win); + }, + onCloseWindow: function (aWindow) { + let win; + try { + win = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + } catch (e) { + // Ignore + } + + removeWindow(win); + }, + observe: function (aSubject, topic) { + let win; + try { + win = aSubject.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + } catch (e) { + // Ignore + } + + if (!win) { + return; + } + + switch (topic) { + case 'domwindowopened': + addWindow(win); + break; + case 'domwindowclosed': + removeWindow(win); + break; + default: + console.error('unknown observer topic'); + break; + } + } + }; + + (function() { + let winumerator; + + winumerator = Services.wm.getEnumerator(null); + while (winumerator.hasMoreElements()) { + let win = winumerator.getNext(); + + if (!win.closed) { + windowToIdMap.set(win, windowIdGenerator++); + } + } + + winumerator = Services.ww.getWindowEnumerator(); + while (winumerator.hasMoreElements()) { + let win = winumerator.getNext() + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + + if (!win.closed) { + windowToIdMap.set(win, windowIdGenerator++); + } + } + + Services.wm.addListener(listeners); + Services.ww.registerNotification(listeners); + })(); + + vAPI.addCleanUpTask(function() { + Services.wm.removeListener(listeners); + Services.ww.unregisterNotification(listeners); + windowToIdMap.clear(); + }); + + return api; + })(); +})(); -- cgit v1.2.3 From 149164fed6a978ddc515107641cc29f5527340ae Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Sat, 22 Jun 2019 00:39:31 +0200 Subject: Minor refactoring --- js/vapi-background.js | 2 +- js/vapi-tabs.js | 37 ++++++++++++++++++------------------- js/vapi-window.js | 2 +- 3 files changed, 20 insertions(+), 21 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index d03a205..4cfebb8 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -34,7 +34,7 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); - let vAPI = self.vAPI = self.vAPI || {}; + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-core.js vAPI.modernFirefox = Services.appinfo.ID === '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}' diff --git a/js/vapi-tabs.js b/js/vapi-tabs.js index 0e71358..1c56cfb 100644 --- a/js/vapi-tabs.js +++ b/js/vapi-tabs.js @@ -38,7 +38,7 @@ vAPI.tabs = {}; vAPI.tabs.registerListeners = function() { - tabWatcher.start(); + vAPI.tabs.manager.start(); }; vAPI.tabs.get = function (tabId, callback) { @@ -76,10 +76,10 @@ let browser; if (tabId === null) { - browser = tabWatcher.currentBrowser(); - tabId = tabWatcher.tabIdFromTarget(browser); + browser = vAPI.tabs.manager.currentBrowser(); + tabId = vAPI.tabs.manager.tabIdFromTarget(browser); } else { - browser = tabWatcher.browserFromTabId(tabId); + browser = vAPI.tabs.manager.browserFromTabId(tabId); } // For internal use @@ -137,8 +137,8 @@ vAPI.tabs.getAll = function (callback) { let tabs = []; - for (let browser of tabWatcher.browsers()) { - let tab = tabWatcher.tabFromBrowser(browser); + for (let browser of vAPI.tabs.manager.browsers()) { + let tab = vAPI.tabs.manager.tabFromBrowser(browser); if (tab === null) { continue; @@ -149,7 +149,7 @@ } tabs.push({ - id: tabWatcher.tabIdFromTarget(browser), + id: vAPI.tabs.manager.tabIdFromTarget(browser), url: browser.currentURI.asciiSpec }); } @@ -180,7 +180,7 @@ let URI = Services.io.newURI(details.url, null, null); for (let tab of this.getAllSync()) { - let browser = tabWatcher.browserFromTarget(tab); + let browser = vAPI.tabs.manager.browserFromTarget(tab); // https://github.com/gorhill/uBlock/issues/2558 if (browser === null) { @@ -208,10 +208,10 @@ } if (details.tabId) { - let tab = tabWatcher.browserFromTabId(details.tabId); + let tab = vAPI.tabs.manager.browserFromTabId(details.tabId); if (tab) { - tabWatcher.browserFromTarget(tab).loadURI(details.url); + vAPI.tabs.manager.browserFromTarget(tab).loadURI(details.url); return; } } @@ -257,7 +257,7 @@ targetURL = vAPI.getURL(targetURL); } - let browser = tabWatcher.browserFromTabId(tabId); + let browser = vAPI.tabs.manager.browserFromTabId(tabId); if (browser) { browser.loadURI(targetURL); } @@ -270,12 +270,12 @@ } vAPI.tabs.remove = function (tabId) { - let browser = tabWatcher.browserFromTabId(tabId); + let browser = vAPI.tabs.manager.browserFromTabId(tabId); if (!browser) { return; } - let tab = tabWatcher.tabFromBrowser(browser); + let tab = vAPI.tabs.manager.tabFromBrowser(browser); if (!tab) { return; } @@ -286,7 +286,7 @@ }; vAPI.tabs.reload = function (tabId) { - let browser = tabWatcher.browserFromTabId(tabId); + let browser = vAPI.tabs.manager.browserFromTabId(tabId); if (!browser) { return; } @@ -296,7 +296,8 @@ vAPI.tabs.select = function (tab) { if (typeof tab !== 'object') { - tab = tabWatcher.tabFromBrowser(tabWatcher.browserFromTabId(tab)); + tab = vAPI.tabs.manager + .tabFromBrowser(vAPI.tabs.manager.browserFromTabId(tab)); } if (!tab) { return; @@ -313,7 +314,7 @@ }; vAPI.tabs.injectScript = function (tabId, details, callback) { - let browser = tabWatcher.browserFromTabId(tabId); + let browser = vAPI.tabs.manager.browserFromTabId(tabId); if (!browser) { return; } @@ -338,7 +339,7 @@ } }; - let tabWatcher = (function () { + vAPI.tabs.manager = (function () { // TODO: find out whether we need a janitor to take care of stale entries. // https://github.com/gorhill/uMatrix/issues/540 @@ -732,6 +733,4 @@ tabIdFromTarget: tabIdFromTarget }; })(); - - vAPI.tabs.manager = tabWatcher; })(); diff --git a/js/vapi-window.js b/js/vapi-window.js index ff0e9fe..ab57e35 100644 --- a/js/vapi-window.js +++ b/js/vapi-window.js @@ -32,7 +32,7 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-core.js vAPI.window = (function () { let windowToIdMap = new Map(); -- cgit v1.2.3 From 5befab2b81270d55e5cb45ed39756dd9c069cf1f Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Sat, 22 Jun 2019 00:43:52 +0200 Subject: Move more entities to core --- js/vapi-background.js | 31 ------------------------------- js/vapi-core.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 31 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 4cfebb8..b7d0d65 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -35,27 +35,6 @@ const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-core.js - - vAPI.modernFirefox = - Services.appinfo.ID === '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}' - && Services.vc.compare(Services.appinfo.version, '44') > 0; - - vAPI.app = { - name: 'eMatrix', - version: location.hash.slice(1), - - start: function () { - return; - }, - stop: function () { - return; - }, - restart: function () { - Cc['@mozilla.org/childprocessmessagemanager;1'] - .getService(Ci.nsIMessageSender) - .sendAsyncMessage(location.host + '-restart'); - }, - }; vAPI.browserSettings = { // For now, only booleans. @@ -548,12 +527,6 @@ return null; }; - vAPI.noTabId = '-1'; - - vAPI.isBehindTheSceneTabId = function (tabId) { - return tabId.toString() === '-1'; - }; - // Icon-related stuff vAPI.setIcon = function (tabId, iconId, badge) { // If badge is undefined, then setIcon was called from the @@ -2236,10 +2209,6 @@ optionsObserver.register(); - vAPI.lastError = function () { - return null; - }; - vAPI.onLoadAllCompleted = function() { // This is called only once, when everything has been loaded // in memory after the extension was launched. It can be used diff --git a/js/vapi-core.js b/js/vapi-core.js index bc7bcb9..4ab5216 100644 --- a/js/vapi-core.js +++ b/js/vapi-core.js @@ -34,6 +34,27 @@ const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); let vAPI = self.vAPI = self.vAPI || {}; + + vAPI.modernFirefox = + Services.appinfo.ID === '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}' + && Services.vc.compare(Services.appinfo.version, '44') > 0; + + vAPI.app = { + name: 'eMatrix', + version: location.hash.slice(1), + + start: function () { + return; + }, + stop: function () { + return; + }, + restart: function () { + Cc['@mozilla.org/childprocessmessagemanager;1'] + .getService(Ci.nsIMessageSender) + .sendAsyncMessage(location.host + '-restart'); + }, + }; // List of things that needs to be destroyed when disabling the extension // Only functions should be added to it @@ -106,4 +127,14 @@ frameModule.contentObserver.unregister(); Cu.unload(frameModuleURL); }); + + vAPI.noTabId = '-1'; + + vAPI.isBehindTheSceneTabId = function (tabId) { + return tabId.toString() === '-1'; + }; + + vAPI.lastError = function () { + return null; + }; })(); -- cgit v1.2.3 From 3e1d8467b38a31b8f3a9ef562fe9a890a5838276 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Sat, 22 Jun 2019 14:11:45 +0200 Subject: Move browser-related entities away from vapi-background --- js/browsercache.js | 4 +- js/vapi-background.js | 212 ------------------------------------------ js/vapi-browser.js | 250 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 252 insertions(+), 214 deletions(-) create mode 100644 js/vapi-browser.js (limited to 'js') diff --git a/js/browsercache.js b/js/browsercache.js index 20a53f2..e1986db 100644 --- a/js/browsercache.js +++ b/js/browsercache.js @@ -46,7 +46,7 @@ var clearCache = function() { return; } - vAPI.browserData.clearCache(); + vAPI.browser.data.clearCache(); ηm.clearBrowserCacheCycle = ηm.userSettings.clearBrowserCacheAfter; ηm.browserCacheClearedCounter++; @@ -54,7 +54,7 @@ var clearCache = function() { // TODO: i18n ηm.logger.writeOne('', 'info', vAPI.i18n('loggerEntryBrowserCacheCleared')); - //console.debug('clearBrowserCacheCallback()> vAPI.browserData.clearCache() called'); + //console.debug('clearBrowserCacheCallback()> vAPI.browser.data.clearCache() called'); }; vAPI.setTimeout(clearCache, 15 * 60 * 1000); diff --git a/js/vapi-background.js b/js/vapi-background.js index b7d0d65..7f5f223 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -36,180 +36,6 @@ let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-core.js - vAPI.browserSettings = { - // For now, only booleans. - originalValues: {}, - - rememberOriginalValue: function (path, setting) { - let key = path + '.' + setting; - if (this.originalValues.hasOwnProperty(key)) { - return; - } - - let hasUserValue; - let branch = Services.prefs.getBranch(path + '.'); - - try { - hasUserValue = branch.prefHasUserValue(setting); - } catch (ex) { - // Ignore - } - - if (hasUserValue !== undefined) { - this.originalValues[key] = hasUserValue - ? this.getValue(path, setting) - : undefined; - } - }, - clear: function (path, setting) { - let key = path + '.' + setting; - - // Value was not overriden -- nothing to restore - if (this.originalValues.hasOwnProperty(key) === false) { - return; - } - - let value = this.originalValues[key]; - - // https://github.com/gorhill/uBlock/issues/292#issuecomment-109621979 - // Forget the value immediately, it may change outside of - // uBlock control. - delete this.originalValues[key]; - - // Original value was a default one - if (value === undefined) { - try { - Services.prefs.getBranch(path + '.').clearUserPref(setting); - } catch (ex) { - // Ignore - } - return; - } - - // Reset to original value - this.setValue(path, setting, value); - }, - getValue: function (path, setting) { - let branch = Services.prefs.getBranch(path + '.'); - - try { - switch (branch.getPrefType(setting)) { - case branch.PREF_INT: - return branch.getIntPref(setting); - case branch.PREF_BOOL: - return branch.getBoolPref(setting); - default: - // not supported - return; - } - } catch (e) { - // Ignore - } - }, - setValue: function (path, setting, value) { - let branch = Services.prefs.getBranch(path + '.'); - - try { - switch (typeof value) { - case 'number': - return branch.setIntPref(setting, value); - case 'boolean': - return branch.setBoolPref(setting, value); - default: - // not supported - return; - } - } catch (e) { - // Ignore - } - }, - setSetting: function (setting, value) { - switch (setting) { - case 'prefetching': - this.rememberOriginalValue('network', 'prefetch-next'); - // https://bugzilla.mozilla.org/show_bug.cgi?id=814169 - // Sigh. - // eMatrix: doesn't seem the case for Pale - // Moon/Basilisk, but let's keep this anyway - this.rememberOriginalValue('network.http', 'speculative-parallel-limit'); - - // https://github.com/gorhill/uBlock/issues/292 - // "true" means "do not disable", i.e. leave entry alone - if (value) { - this.clear('network', 'prefetch-next'); - this.clear('network.http', 'speculative-parallel-limit'); - } else { - this.setValue('network', 'prefetch-next', false); - this.setValue('network.http', - 'speculative-parallel-limit', 0); - } - break; - case 'hyperlinkAuditing': - this.rememberOriginalValue('browser', 'send_pings'); - this.rememberOriginalValue('beacon', 'enabled'); - - // https://github.com/gorhill/uBlock/issues/292 - // "true" means "do not disable", i.e. leave entry alone - if (value) { - this.clear('browser', 'send_pings'); - this.clear('beacon', 'enabled'); - } else { - this.setValue('browser', 'send_pings', false); - this.setValue('beacon', 'enabled', false); - } - break; - case 'webrtcIPAddress': - let prefName; - let prefVal; - - // https://github.com/gorhill/uBlock/issues/894 - // Do not disable completely WebRTC if it can be avoided. FF42+ - // has a `media.peerconnection.ice.default_address_only` pref which - // purpose is to prevent local IP address leakage. - if (this.getValue('media.peerconnection', - 'ice.default_address_only') !== undefined) { - prefName = 'ice.default_address_only'; - prefVal = true; - } else { - prefName = 'enabled'; - prefVal = false; - } - - this.rememberOriginalValue('media.peerconnection', prefName); - if (value) { - this.clear('media.peerconnection', prefName); - } else { - this.setValue('media.peerconnection', prefName, prefVal); - } - break; - default: - break; - } - }, - set: function (details) { - for (let setting in details) { - if (details.hasOwnProperty(setting) === false) { - continue; - } - this.setSetting(setting, !!details[setting]); - } - }, - restoreAll: function () { - let pos; - for (let key in this.originalValues) { - if (this.originalValues.hasOwnProperty(key) === false) { - continue; - } - - pos = key.lastIndexOf('.'); - this.clear(key.slice(0, pos), key.slice(pos + 1)); - } - }, - }; - - vAPI.addCleanUpTask(vAPI.browserSettings - .restoreAll.bind(vAPI.browserSettings)); - // API matches that of chrome.storage.local: // https://developer.chrome.com/extensions/storage vAPI.storage = (function () { @@ -512,21 +338,6 @@ vAPI.cacheStorage = vAPI.storage; - // browser-handling utilities - vAPI.browser = {}; - - vAPI.browser.getTabBrowser = function (win) { - return win && win.gBrowser || null; - }; - - vAPI.browser.getOwnerWindow = function (target) { - if (target.ownerDocument) { - return target.ownerDocument.defaultView; - } - - return null; - }; - // Icon-related stuff vAPI.setIcon = function (tabId, iconId, badge) { // If badge is undefined, then setIcon was called from the @@ -2368,29 +2179,6 @@ }; })(); - vAPI.browserData = {}; - - vAPI.browserData.clearCache = function (callback) { - // PURGE_DISK_DATA_ONLY:1 - // PURGE_DISK_ALL:2 - // PURGE_EVERYTHING:3 - // However I verified that no argument does clear the cache data. - // There is no cache2 for older versions of Firefox. - if (Services.cache2) { - Services.cache2.clear(); - } else if (Services.cache) { - Services.cache.evictEntries(Services.cache.STORE_ON_DISK); - } - - if (typeof callback === 'function') { - callback(); - } - }; - - vAPI.browserData.clearOrigin = function(/* domain */) { - // TODO - }; - vAPI.cookies = {}; // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsICookieManager2 // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsICookie2 diff --git a/js/vapi-browser.js b/js/vapi-browser.js new file mode 100644 index 0000000..be4a8bd --- /dev/null +++ b/js/vapi-browser.js @@ -0,0 +1,250 @@ +/******************************************************************************* + + ηMatrix - a browser extension to black/white list requests. + Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors + Copyright (C) 2019 Alessio Vanni + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://gitlab.com/vannilla/ematrix + uMatrix Home: https://github.com/gorhill/uMatrix +*/ + +/* global self, Components */ + +// For background page (tabs management) + +'use strict'; + +/******************************************************************************/ + +(function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js + + vAPI.browser = {}; + + vAPI.browser.getTabBrowser = function (win) { + return win && win.gBrowser || null; + }; + + vAPI.browser.getOwnerWindow = function (target) { + if (target.ownerDocument) { + return target.ownerDocument.defaultView; + } + + return null; + }; + + vAPI.browser.settings = { + // For now, only booleans. + originalValues: {}, + + rememberOriginalValue: function (path, setting) { + let key = path + '.' + setting; + if (this.originalValues.hasOwnProperty(key)) { + return; + } + + let hasUserValue; + let branch = Services.prefs.getBranch(path + '.'); + + try { + hasUserValue = branch.prefHasUserValue(setting); + } catch (ex) { + // Ignore + } + + if (hasUserValue !== undefined) { + this.originalValues[key] = hasUserValue + ? this.getValue(path, setting) + : undefined; + } + }, + clear: function (path, setting) { + let key = path + '.' + setting; + + // Value was not overriden -- nothing to restore + if (this.originalValues.hasOwnProperty(key) === false) { + return; + } + + let value = this.originalValues[key]; + + // https://github.com/gorhill/uBlock/issues/292#issuecomment-109621979 + // Forget the value immediately, it may change outside of + // uBlock control. + delete this.originalValues[key]; + + // Original value was a default one + if (value === undefined) { + try { + Services.prefs.getBranch(path + '.').clearUserPref(setting); + } catch (ex) { + // Ignore + } + return; + } + + // Reset to original value + this.setValue(path, setting, value); + }, + getValue: function (path, setting) { + let branch = Services.prefs.getBranch(path + '.'); + + try { + switch (branch.getPrefType(setting)) { + case branch.PREF_INT: + return branch.getIntPref(setting); + case branch.PREF_BOOL: + return branch.getBoolPref(setting); + default: + // not supported + return; + } + } catch (e) { + // Ignore + } + }, + setValue: function (path, setting, value) { + let branch = Services.prefs.getBranch(path + '.'); + + try { + switch (typeof value) { + case 'number': + return branch.setIntPref(setting, value); + case 'boolean': + return branch.setBoolPref(setting, value); + default: + // not supported + return; + } + } catch (e) { + // Ignore + } + }, + setSetting: function (setting, value) { + switch (setting) { + case 'prefetching': + this.rememberOriginalValue('network', 'prefetch-next'); + // https://bugzilla.mozilla.org/show_bug.cgi?id=814169 + // Sigh. + // eMatrix: doesn't seem the case for Pale + // Moon/Basilisk, but let's keep this anyway + this.rememberOriginalValue('network.http', 'speculative-parallel-limit'); + + // https://github.com/gorhill/uBlock/issues/292 + // "true" means "do not disable", i.e. leave entry alone + if (value) { + this.clear('network', 'prefetch-next'); + this.clear('network.http', 'speculative-parallel-limit'); + } else { + this.setValue('network', 'prefetch-next', false); + this.setValue('network.http', + 'speculative-parallel-limit', 0); + } + break; + case 'hyperlinkAuditing': + this.rememberOriginalValue('browser', 'send_pings'); + this.rememberOriginalValue('beacon', 'enabled'); + + // https://github.com/gorhill/uBlock/issues/292 + // "true" means "do not disable", i.e. leave entry alone + if (value) { + this.clear('browser', 'send_pings'); + this.clear('beacon', 'enabled'); + } else { + this.setValue('browser', 'send_pings', false); + this.setValue('beacon', 'enabled', false); + } + break; + case 'webrtcIPAddress': + let prefName; + let prefVal; + + // https://github.com/gorhill/uBlock/issues/894 + // Do not disable completely WebRTC if it can be avoided. FF42+ + // has a `media.peerconnection.ice.default_address_only` pref which + // purpose is to prevent local IP address leakage. + if (this.getValue('media.peerconnection', + 'ice.default_address_only') !== undefined) { + prefName = 'ice.default_address_only'; + prefVal = true; + } else { + prefName = 'enabled'; + prefVal = false; + } + + this.rememberOriginalValue('media.peerconnection', prefName); + if (value) { + this.clear('media.peerconnection', prefName); + } else { + this.setValue('media.peerconnection', prefName, prefVal); + } + break; + default: + break; + } + }, + set: function (details) { + for (let setting in details) { + if (details.hasOwnProperty(setting) === false) { + continue; + } + this.setSetting(setting, !!details[setting]); + } + }, + restoreAll: function () { + let pos; + for (let key in this.originalValues) { + if (this.originalValues.hasOwnProperty(key) === false) { + continue; + } + + pos = key.lastIndexOf('.'); + this.clear(key.slice(0, pos), key.slice(pos + 1)); + } + }, + }; + + vAPI.addCleanUpTask(vAPI.browser.settings + .restoreAll.bind(vAPI.browser.settings)); + + vAPI.browser.data = {}; + + vAPI.browser.data.clearCache = function (callback) { + // PURGE_DISK_DATA_ONLY:1 + // PURGE_DISK_ALL:2 + // PURGE_EVERYTHING:3 + // However I verified that no argument does clear the cache data. + // There is no cache2 for older versions of Firefox. + if (Services.cache2) { + Services.cache2.clear(); + } else if (Services.cache) { + Services.cache.evictEntries(Services.cache.STORE_ON_DISK); + } + + if (typeof callback === 'function') { + callback(); + } + }; + + vAPI.browser.data.clearOrigin = function(/* domain */) { + // TODO + // eMatrix: is this actually needed? I don't really know what + // it's supposed to do anyway. + }; +})(); -- cgit v1.2.3 From e29205dc4d013704cd6b1ee2fd7b7dc0ef226c01 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Sun, 23 Jun 2019 02:27:59 +0200 Subject: Move storage API Also remove optional cachestorage script. It doesn't exists and the cachedstorage, whatever it is, is defined as an alias for the normal storage system. --- js/vapi-background.js | 302 -------------------------------------------- js/vapi-storage.js | 339 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 339 insertions(+), 302 deletions(-) create mode 100644 js/vapi-storage.js (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 7f5f223..14544cb 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -36,308 +36,6 @@ let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-core.js - // API matches that of chrome.storage.local: - // https://developer.chrome.com/extensions/storage - vAPI.storage = (function () { - let db = null; - let vacuumTimer = null; - - let close = function () { - if (vacuumTimer !== null) { - clearTimeout(vacuumTimer); - vacuumTimer = null; - } - - if (db === null) { - return; - } - - db.asyncClose(); - db = null; - }; - - let open = function () { - if (db !== null) { - return db; - } - - // Create path - let path = Services.dirsvc.get('ProfD', Ci.nsIFile); - path.append('ematrix-data'); - if (!path.exists()) { - path.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0774', 8)); - } - if (!path.isDirectory()) { - throw Error('Should be a directory...'); - } - - let path2 = Services.dirsvc.get('ProfD', Ci.nsIFile); - path2.append('extension-data'); - path2.append(location.host + '.sqlite'); - if (path2.exists()) { - path2.moveTo(path, location.host+'.sqlite'); - } - - path.append(location.host + '.sqlite'); - - // Open database - try { - db = Services.storage.openDatabase(path); - if (db.connectionReady === false) { - db.asyncClose(); - db = null; - } - } catch (ex) { - // Ignore - } - - if (db === null) { - return null; - } - - // Database was opened, register cleanup task - vAPI.addCleanUpTask(close); - - // Setup database - db.createAsyncStatement('CREATE TABLE IF NOT EXISTS ' - +'"settings" ("name" ' - +'TEXT PRIMARY KEY NOT NULL, ' - +'"value" TEXT);') - .executeAsync(); - - if (vacuum !== null) { - vacuumTimer = vAPI.setTimeout(vacuum, 60000); - } - - return db; - }; - - // Vacuum only once, and only while idle - let vacuum = function () { - vacuumTimer = null; - if (db === null) { - return; - } - let idleSvc = - Cc['@mozilla.org/widget/idleservice;1'] - .getService(Ci.nsIIdleService); - - if (idleSvc.idleTime < 60000) { - vacuumTimer = vAPI.setTimeout(vacuum, 60000); - return; - } - - db.createAsyncStatement('VACUUM').executeAsync(); - vacuum = null; - }; - - // Execute a query - let runStatement = function (stmt, callback) { - let result = {}; - - stmt.executeAsync({ - handleResult: function (rows) { - if (!rows || typeof callback !== 'function') { - return; - } - - let row; - while ((row = rows.getNextRow())) { - // we assume that there will be two columns, since we're - // using it only for preferences - // eMatrix: the above comment is obsolete - // (it's not used just for preferences - // anymore), but we still expect two columns. - let res = row.getResultByIndex(0); - result[res] = row.getResultByIndex(1); - } - }, - handleCompletion: function (reason) { - if (typeof callback === 'function' && reason === 0) { - callback(result); - } - }, - handleError: function (error) { - console.error('SQLite error ', error.result, error.message); - - // Caller expects an answer regardless of failure. - if (typeof callback === 'function' ) { - callback(null); - } - }, - }); - }; - - let bindNames = function (stmt, names) { - if (Array.isArray(names) === false || names.length === 0) { - return; - } - - let params = stmt.newBindingParamsArray(); - - for (let i=names.length-1; i>=0; --i) { - let bp = params.newBindingParams(); - bp.bindByName('name', names[i]); - params.addParams(bp); - } - - stmt.bindParameters(params); - }; - - let clear = function (callback) { - if (open() === null) { - if (typeof callback === 'function') { - callback(); - } - return; - } - - runStatement(db.createAsyncStatement('DELETE FROM "settings";'), - callback); - }; - - let getBytesInUse = function (keys, callback) { - if (typeof callback !== 'function') { - return; - } - - if (open() === null) { - callback(0); - return; - } - - let stmt; - if (Array.isArray(keys)) { - stmt = db.createAsyncStatement('SELECT "size" AS "size", ' - +'SUM(LENGTH("value")) ' - +'FROM "settings" WHERE ' - +'"name" = :name'); - bindNames(keys); - } else { - stmt = db.createAsyncStatement('SELECT "size" AS "size", ' - +'SUM(LENGTH("value")) ' - +'FROM "settings"'); - } - - runStatement(stmt, function (result) { - callback(result.size); - }); - }; - - let read = function (details, callback) { - if (typeof callback !== 'function') { - return; - } - - let prepareResult = function (result) { - for (let key in result) { - if (result.hasOwnProperty(key) === false) { - continue; - } - - result[key] = JSON.parse(result[key]); - } - - if (typeof details === 'object' && details !== null) { - for (let key in details) { - if (result.hasOwnProperty(key) === false) { - result[key] = details[key]; - } - } - } - - callback(result); - }; - - if (open() === null) { - prepareResult({}); - return; - } - - let names = []; - if (details !== null) { - if (Array.isArray(details)) { - names = details; - } else if (typeof details === 'object') { - names = Object.keys(details); - } else { - names = [details.toString()]; - } - } - - let stmt; - if (names.length === 0) { - stmt = db.createAsyncStatement('SELECT * FROM "settings"'); - } else { - stmt = db.createAsyncStatement('SELECT * FROM "settings" ' - +'WHERE "name" = :name'); - bindNames(stmt, names); - } - - runStatement(stmt, prepareResult); - }; - - let remove = function (keys, callback) { - if (open() === null) { - if (typeof callback === 'function') { - callback(); - } - return; - } - - var stmt = db.createAsyncStatement('DELETE FROM "settings" ' - +'WHERE "name" = :name'); - bindNames(stmt, typeof keys === 'string' ? [keys] : keys); - runStatement(stmt, callback); - }; - - let write = function (details, callback) { - if (open() === null) { - if (typeof callback === 'function') { - callback(); - } - return; - } - - let stmt = db.createAsyncStatement('INSERT OR REPLACE INTO ' - +'"settings" ("name", "value") ' - +'VALUES(:name, :value)'); - let params = stmt.newBindingParamsArray(); - - for (let key in details) { - if (details.hasOwnProperty(key) === false) { - continue; - } - - let bp = params.newBindingParams(); - bp.bindByName('name', key); - bp.bindByName('value', JSON.stringify(details[key])); - params.addParams(bp); - } - - if (params.length === 0) { - return; - } - - stmt.bindParameters(params); - runStatement(stmt, callback); - }; - - // Export API - var api = { - QUOTA_BYTES: 100 * 1024 * 1024, - clear: clear, - get: read, - getBytesInUse: getBytesInUse, - remove: remove, - set: write - }; - - return api; - })(); - - vAPI.cacheStorage = vAPI.storage; - // Icon-related stuff vAPI.setIcon = function (tabId, iconId, badge) { // If badge is undefined, then setIcon was called from the diff --git a/js/vapi-storage.js b/js/vapi-storage.js new file mode 100644 index 0000000..768958c --- /dev/null +++ b/js/vapi-storage.js @@ -0,0 +1,339 @@ +/******************************************************************************* + + ηMatrix - a browser extension to black/white list requests. + Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors + Copyright (C) 2019 Alessio Vanni + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://gitlab.com/vannilla/ematrix + uMatrix Home: https://github.com/gorhill/uMatrix +*/ + +/* global self, Components */ + +// For background page (tabs management) + +'use strict'; + +/******************************************************************************/ + +(function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js + + // API matches that of chrome.storage.local: + // https://developer.chrome.com/extensions/storage + vAPI.storage = (function () { + let db = null; + let vacuumTimer = null; + + let close = function () { + if (vacuumTimer !== null) { + clearTimeout(vacuumTimer); + vacuumTimer = null; + } + + if (db === null) { + return; + } + + db.asyncClose(); + db = null; + }; + + let open = function () { + if (db !== null) { + return db; + } + + // Create path + let path = Services.dirsvc.get('ProfD', Ci.nsIFile); + path.append('ematrix-data'); + if (!path.exists()) { + path.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0774', 8)); + } + if (!path.isDirectory()) { + throw Error('Should be a directory...'); + } + + let path2 = Services.dirsvc.get('ProfD', Ci.nsIFile); + path2.append('extension-data'); + path2.append(location.host + '.sqlite'); + if (path2.exists()) { + path2.moveTo(path, location.host+'.sqlite'); + } + + path.append(location.host + '.sqlite'); + + // Open database + try { + db = Services.storage.openDatabase(path); + if (db.connectionReady === false) { + db.asyncClose(); + db = null; + } + } catch (ex) { + // Ignore + } + + if (db === null) { + return null; + } + + // Database was opened, register cleanup task + vAPI.addCleanUpTask(close); + + // Setup database + db.createAsyncStatement('CREATE TABLE IF NOT EXISTS ' + +'"settings" ("name" ' + +'TEXT PRIMARY KEY NOT NULL, ' + +'"value" TEXT);') + .executeAsync(); + + if (vacuum !== null) { + vacuumTimer = vAPI.setTimeout(vacuum, 60000); + } + + return db; + }; + + // Vacuum only once, and only while idle + let vacuum = function () { + vacuumTimer = null; + if (db === null) { + return; + } + let idleSvc = + Cc['@mozilla.org/widget/idleservice;1'] + .getService(Ci.nsIIdleService); + + if (idleSvc.idleTime < 60000) { + vacuumTimer = vAPI.setTimeout(vacuum, 60000); + return; + } + + db.createAsyncStatement('VACUUM').executeAsync(); + vacuum = null; + }; + + // Execute a query + let runStatement = function (stmt, callback) { + let result = {}; + + stmt.executeAsync({ + handleResult: function (rows) { + if (!rows || typeof callback !== 'function') { + return; + } + + let row; + while ((row = rows.getNextRow())) { + // we assume that there will be two columns, since we're + // using it only for preferences + // eMatrix: the above comment is obsolete + // (it's not used just for preferences + // anymore), but we still expect two columns. + let res = row.getResultByIndex(0); + result[res] = row.getResultByIndex(1); + } + }, + handleCompletion: function (reason) { + if (typeof callback === 'function' && reason === 0) { + callback(result); + } + }, + handleError: function (error) { + console.error('SQLite error ', error.result, error.message); + + // Caller expects an answer regardless of failure. + if (typeof callback === 'function' ) { + callback(null); + } + }, + }); + }; + + let bindNames = function (stmt, names) { + if (Array.isArray(names) === false || names.length === 0) { + return; + } + + let params = stmt.newBindingParamsArray(); + + for (let i=names.length-1; i>=0; --i) { + let bp = params.newBindingParams(); + bp.bindByName('name', names[i]); + params.addParams(bp); + } + + stmt.bindParameters(params); + }; + + let clear = function (callback) { + if (open() === null) { + if (typeof callback === 'function') { + callback(); + } + return; + } + + runStatement(db.createAsyncStatement('DELETE FROM "settings";'), + callback); + }; + + let getBytesInUse = function (keys, callback) { + if (typeof callback !== 'function') { + return; + } + + if (open() === null) { + callback(0); + return; + } + + let stmt; + if (Array.isArray(keys)) { + stmt = db.createAsyncStatement('SELECT "size" AS "size", ' + +'SUM(LENGTH("value")) ' + +'FROM "settings" WHERE ' + +'"name" = :name'); + bindNames(keys); + } else { + stmt = db.createAsyncStatement('SELECT "size" AS "size", ' + +'SUM(LENGTH("value")) ' + +'FROM "settings"'); + } + + runStatement(stmt, function (result) { + callback(result.size); + }); + }; + + let read = function (details, callback) { + if (typeof callback !== 'function') { + return; + } + + let prepareResult = function (result) { + for (let key in result) { + if (result.hasOwnProperty(key) === false) { + continue; + } + + result[key] = JSON.parse(result[key]); + } + + if (typeof details === 'object' && details !== null) { + for (let key in details) { + if (result.hasOwnProperty(key) === false) { + result[key] = details[key]; + } + } + } + + callback(result); + }; + + if (open() === null) { + prepareResult({}); + return; + } + + let names = []; + if (details !== null) { + if (Array.isArray(details)) { + names = details; + } else if (typeof details === 'object') { + names = Object.keys(details); + } else { + names = [details.toString()]; + } + } + + let stmt; + if (names.length === 0) { + stmt = db.createAsyncStatement('SELECT * FROM "settings"'); + } else { + stmt = db.createAsyncStatement('SELECT * FROM "settings" ' + +'WHERE "name" = :name'); + bindNames(stmt, names); + } + + runStatement(stmt, prepareResult); + }; + + let remove = function (keys, callback) { + if (open() === null) { + if (typeof callback === 'function') { + callback(); + } + return; + } + + var stmt = db.createAsyncStatement('DELETE FROM "settings" ' + +'WHERE "name" = :name'); + bindNames(stmt, typeof keys === 'string' ? [keys] : keys); + runStatement(stmt, callback); + }; + + let write = function (details, callback) { + if (open() === null) { + if (typeof callback === 'function') { + callback(); + } + return; + } + + let stmt = db.createAsyncStatement('INSERT OR REPLACE INTO ' + +'"settings" ("name", "value") ' + +'VALUES(:name, :value)'); + let params = stmt.newBindingParamsArray(); + + for (let key in details) { + if (details.hasOwnProperty(key) === false) { + continue; + } + + let bp = params.newBindingParams(); + bp.bindByName('name', key); + bp.bindByName('value', JSON.stringify(details[key])); + params.addParams(bp); + } + + if (params.length === 0) { + return; + } + + stmt.bindParameters(params); + runStatement(stmt, callback); + }; + + // Export API + var api = { + QUOTA_BYTES: 100 * 1024 * 1024, + clear: clear, + get: read, + getBytesInUse: getBytesInUse, + remove: remove, + set: write + }; + + return api; + })(); + + vAPI.cacheStorage = vAPI.storage; +})(); -- cgit v1.2.3 From d9858f22244f58eb2a305aa2e42439c77acdb861 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Sun, 23 Jun 2019 02:34:09 +0200 Subject: Make messaging its own file Also reorganize the Makefile a bit. --- js/vapi-background.js | 108 ------------------------------------- js/vapi-messaging.js | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 108 deletions(-) create mode 100644 js/vapi-messaging.js (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 14544cb..6c874f3 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -69,114 +69,6 @@ } }; - // Internal message passing mechanism - vAPI.messaging = { - get globalMessageManager() { - return Cc['@mozilla.org/globalmessagemanager;1'] - .getService(Ci.nsIMessageListenerManager); - }, - frameScript: vAPI.getURL('frameScript.js'), - listeners: {}, - defaultHandler: null, - NOOPFUNC: function(){}, - UNHANDLED: 'vAPI.messaging.notHandled' - }; - - vAPI.messaging.listen = function (listenerName, callback) { - this.listeners[listenerName] = callback; - }; - - vAPI.messaging.onMessage = function ({target, data}) { - let messageManager = target.messageManager; - - if (!messageManager) { - // Message came from a popup, and its message manager is - // not usable. So instead we broadcast to the parent - // window. - messageManager = - vAPI.browser. - getOwnerWindow(target.webNavigation - .QueryInterface(Ci.nsIDocShell) - .chromeEventHandler).messageManager; - } - - let channelNameRaw = data.channelName; - let pos = channelNameRaw.indexOf('|'); - let channelName = channelNameRaw.slice(pos + 1); - - let callback = vAPI.messaging.NOOPFUNC; - if (data.requestId !== undefined) { - callback = CallbackWrapper.factory(messageManager, - channelName, - channelNameRaw.slice(0, pos), - data.requestId).callback; - } - - let sender = { - tab: { - id: vAPI.tabs.manager.tabIdFromTarget(target) - } - }; - - // Specific handler - let r = vAPI.messaging.UNHANDLED; - let listener = vAPI.messaging.listeners[channelName]; - - if (typeof listener === 'function') { - r = listener(data.msg, sender, callback); - } - if (r !== vAPI.messaging.UNHANDLED) { - return; - } - - // Default handler - r = vAPI.messaging.defaultHandler(data.msg, sender, callback); - if (r !== vAPI.messaging.UNHANDLED) { - return; - } - - console.error('eMatrix> messaging > unknown request: %o', data); - - // 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; - this.globalMessageManager.addMessageListener(location.host - + ':background', - this.onMessage); - this.globalMessageManager.loadFrameScript(this.frameScript, true); - - vAPI.addCleanUpTask(function () { - let gmm = vAPI.messaging.globalMessageManager; - - gmm.removeDelayedFrameScript(vAPI.messaging.frameScript); - gmm.removeMessageListener(location.host + ':background', - vAPI.messaging.onMessage); - }); - }; - - vAPI.messaging.broadcast = function (message) { - this.globalMessageManager - .broadcastAsyncMessage(location.host + ':broadcast', - JSON.stringify({ - broadcast: true, - msg: message})); - }; - let CallbackWrapper = function (messageManager, channelName, listenerId, requestId) { // This allows to avoid creating a closure for every single diff --git a/js/vapi-messaging.js b/js/vapi-messaging.js new file mode 100644 index 0000000..abed913 --- /dev/null +++ b/js/vapi-messaging.js @@ -0,0 +1,144 @@ +/******************************************************************************* + + ηMatrix - a browser extension to black/white list requests. + Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors + Copyright (C) 2019 Alessio Vanni + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://gitlab.com/vannilla/ematrix + uMatrix Home: https://github.com/gorhill/uMatrix +*/ + +/* global self, Components */ + +// For background page (tabs management) + +'use strict'; + +/******************************************************************************/ + +(function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js + + vAPI.messaging = { + get globalMessageManager() { + return Cc['@mozilla.org/globalmessagemanager;1'] + .getService(Ci.nsIMessageListenerManager); + }, + frameScript: vAPI.getURL('frameScript.js'), + listeners: {}, + defaultHandler: null, + NOOPFUNC: function(){}, + UNHANDLED: 'vAPI.messaging.notHandled' + }; + + vAPI.messaging.listen = function (listenerName, callback) { + this.listeners[listenerName] = callback; + }; + + vAPI.messaging.onMessage = function ({target, data}) { + let messageManager = target.messageManager; + + if (!messageManager) { + // Message came from a popup, and its message manager is + // not usable. So instead we broadcast to the parent + // window. + messageManager = + vAPI.browser. + getOwnerWindow(target.webNavigation + .QueryInterface(Ci.nsIDocShell) + .chromeEventHandler).messageManager; + } + + let channelNameRaw = data.channelName; + let pos = channelNameRaw.indexOf('|'); + let channelName = channelNameRaw.slice(pos + 1); + + let callback = vAPI.messaging.NOOPFUNC; + if (data.requestId !== undefined) { + callback = CallbackWrapper.factory(messageManager, + channelName, + channelNameRaw.slice(0, pos), + data.requestId).callback; + } + + let sender = { + tab: { + id: vAPI.tabs.manager.tabIdFromTarget(target) + } + }; + + // Specific handler + let r = vAPI.messaging.UNHANDLED; + let listener = vAPI.messaging.listeners[channelName]; + + if (typeof listener === 'function') { + r = listener(data.msg, sender, callback); + } + if (r !== vAPI.messaging.UNHANDLED) { + return; + } + + // Default handler + r = vAPI.messaging.defaultHandler(data.msg, sender, callback); + if (r !== vAPI.messaging.UNHANDLED) { + return; + } + + console.error('eMatrix> messaging > unknown request: %o', data); + + // 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; + this.globalMessageManager.addMessageListener(location.host + + ':background', + this.onMessage); + this.globalMessageManager.loadFrameScript(this.frameScript, true); + + vAPI.addCleanUpTask(function () { + let gmm = vAPI.messaging.globalMessageManager; + + gmm.removeDelayedFrameScript(vAPI.messaging.frameScript); + gmm.removeMessageListener(location.host + ':background', + vAPI.messaging.onMessage); + }); + }; + + vAPI.messaging.broadcast = function (message) { + this.globalMessageManager + .broadcastAsyncMessage(location.host + ':broadcast', + JSON.stringify({ + broadcast: true, + msg: message})); + }; +})(); -- cgit v1.2.3 From 6458ec333511fe07101dd3afffbefed7b4fead07 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Sun, 23 Jun 2019 02:43:40 +0200 Subject: Make CallbackWrapper a module In theory this way it can be used anywhere else if needed, but right now it's simply because it has to be placed somewhere and a module seems the best place for an object definition (constructor, etc.) --- js/vapi-background.js | 66 --------------------------------------------------- js/vapi-messaging.js | 2 ++ 2 files changed, 2 insertions(+), 66 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 6c874f3..0da0638 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -69,72 +69,6 @@ } }; - let CallbackWrapper = function (messageManager, channelName, - listenerId, requestId) { - // This allows to avoid creating a closure for every single - // message which expects an answer. Having a closure created - // each time a message is processed has been always bothering - // me. Another benefit of the implementation here is to reuse - // the callback proxy object, so less memory churning. - // - // https://developers.google.com/speed/articles/optimizing-javascript - // "Creating a closure is significantly slower then creating - // an inner function without a closure, and much slower than - // reusing a static function" - // - // http://hacksoflife.blogspot.ca/2015/01/the-four-horsemen-of-performance.html - // "the dreaded 'uniformly slow code' case where every - // function takes 1% of CPU and you have to make one hundred - // separate performance optimizations to improve performance - // at all" - // - // http://jsperf.com/closure-no-closure/2 - this.callback = this.proxy.bind(this); // bind once - this.init(messageManager, channelName, listenerId, requestId); - }; - - CallbackWrapper.junkyard = []; - - CallbackWrapper.factory = function (messageManager, channelName, - listenerId, requestId) { - let wrapper = CallbackWrapper.junkyard.pop(); - if (wrapper) { - wrapper.init(messageManager, channelName, listenerId, requestId); - return wrapper; - } - - return new CallbackWrapper(messageManager, channelName, - listenerId, requestId); - }; - - CallbackWrapper.prototype.init = function (messageManager, channelName, - listenerId, requestId) { - this.messageManager = messageManager; - this.channelName = channelName; - this.listenerId = listenerId; - this.requestId = requestId; - }; - - CallbackWrapper.prototype.proxy = function (response) { - let message = JSON.stringify({ - requestId: this.requestId, - channelName: this.channelName, - msg: response !== undefined ? response : null - }); - - if (this.messageManager.sendAsyncMessage) { - this.messageManager.sendAsyncMessage(this.listenerId, message); - } else { - this.messageManager.broadcastAsyncMessage(this.listenerId, message); - } - - // Mark for reuse - this.messageManager = this.channelName = - this.requestId = this.listenerId = null; - - CallbackWrapper.junkyard.push(this); - }; - let httpRequestHeadersFactory = function (channel) { let entry = httpRequestHeadersFactory.junkyard.pop(); if (entry) { diff --git a/js/vapi-messaging.js b/js/vapi-messaging.js index abed913..b00c054 100644 --- a/js/vapi-messaging.js +++ b/js/vapi-messaging.js @@ -32,6 +32,8 @@ (function () { const {classes: Cc, interfaces: Ci, utils: Cu} = Components; const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + const {CallbackWrapper} = + Cu.import('chrome://ematrix/content/CallbackWrapper.jsm', null); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js -- cgit v1.2.3 From a7ad9ff0d7e80001db78e65fb5e756966141230f Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Sun, 23 Jun 2019 03:11:39 +0200 Subject: Make HTTPRequestHeaders a module The same as CallbackWrapper. --- js/vapi-background.js | 80 ++------------------------------------------------- 1 file changed, 3 insertions(+), 77 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 0da0638..a7cc5f3 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -33,6 +33,8 @@ (function () { const {classes: Cc, interfaces: Ci, utils: Cu} = Components; const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + const {HTTPRequestHeaders} = + Cu.import('chrome://ematrix/content/HTTPRequestHeaders.jsm', null); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-core.js @@ -69,82 +71,6 @@ } }; - let httpRequestHeadersFactory = function (channel) { - let entry = httpRequestHeadersFactory.junkyard.pop(); - if (entry) { - return entry.init(channel); - } - - return new HTTPRequestHeaders(channel); - }; - - httpRequestHeadersFactory.junkyard = []; - - let HTTPRequestHeaders = function (channel) { - this.init(channel); - }; - - HTTPRequestHeaders.prototype.init = function (channel) { - this.channel = channel; - this.headers = new Array(); - this.originalHeaderNames = new Array(); - - channel.visitRequestHeaders({ - visitHeader: function (name, value) { - this.headers.push({name: name, value: value}); - this.originalHeaderNames.push(name); - }.bind(this) - }); - - return this; - }; - - HTTPRequestHeaders.prototype.dispose = function () { - this.channel = null; - this.headers = null; - this.originalHeaderNames = null; - httpRequestHeadersFactory.junkyard.push(this); - }; - - HTTPRequestHeaders.prototype.update = function () { - let newHeaderNames = new Set(); - for (let header of this.headers) { - this.setHeader(header.name, header.value, true); - newHeaderNames.add(header.name); - } - - //Clear any headers that were removed - for (let name of this.originalHeaderNames) { - if (!newHeaderNames.has(name)) { - this.channel.setRequestHeader(name, '', false); - } - } - }; - - HTTPRequestHeaders.prototype.getHeader = function (name) { - try { - return this.channel.getRequestHeader(name); - } catch (e) { - // Ignore - } - - return ''; - }; - - HTTPRequestHeaders.prototype.setHeader = function (name, newValue, create) { - let oldValue = this.getHeader(name); - if (newValue === oldValue) { - return false; - } - - if (oldValue === '' && create !== true) { - return false; - } - - this.channel.setRequestHeader(name, newValue, false); - return true; - }; - let httpObserver = { classDescription: 'net-channel-event-sinks for ' + location.host, classID: Components.ID('{5d2e2797-6d68-42e2-8aeb-81ce6ba16b95}'), @@ -351,7 +277,7 @@ let onBeforeSendHeaders = vAPI.net.onBeforeSendHeaders; if (onBeforeSendHeaders.types === null || onBeforeSendHeaders.types.has(type)) { - let requestHeaders = httpRequestHeadersFactory(channel); + let requestHeaders = HTTPRequestHeaders.factory(channel); let newHeaders = onBeforeSendHeaders.callback({ parentFrameId: type === 'main_frame' ? -1 : 0, requestHeaders: requestHeaders.headers, -- cgit v1.2.3 From 9bde0c09c312feebe5abc96240214f91aeca198a Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Sun, 23 Jun 2019 13:30:04 +0200 Subject: Split vAPI.net --- js/vapi-background.js | 41 --------------------------- js/vapi-net.js | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 41 deletions(-) create mode 100644 js/vapi-net.js (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index a7cc5f3..3035556 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -534,47 +534,6 @@ } }; - vAPI.net = {}; - - vAPI.net.registerListeners = function () { - this.onBeforeRequest.types = this.onBeforeRequest.types - ? new Set(this.onBeforeRequest.types) - : null; - - this.onBeforeSendHeaders.types = this.onBeforeSendHeaders.types - ? new Set(this.onBeforeSendHeaders.types) - : null; - - let shouldLoadListenerMessageName = location.host + ':shouldLoad'; - let shouldLoadListener = function (e) { - let details = e.data; - let pendingReq = httpObserver.createPendingRequest(details.url); - pendingReq.rawType = details.rawType; - pendingReq.tabId = vAPI.tabs.manager.tabIdFromTarget(e.target); - }; - - // https://github.com/gorhill/uMatrix/issues/200 - // We need this only for Firefox 34 and less: the tab id is derived from - // the origin of the message. - if (!vAPI.modernFirefox) { - vAPI.messaging.globalMessageManager - .addMessageListener(shouldLoadListenerMessageName, - shouldLoadListener); - } - - httpObserver.register(); - - vAPI.addCleanUpTask(function () { - if (!vAPI.modernFirefox) { - vAPI.messaging.globalMessageManager - .removeMessageListener(shouldLoadListenerMessageName, - shouldLoadListener); - } - - httpObserver.unregister(); - }); - }; - vAPI.toolbarButton = { id: location.host + '-button', type: 'view', diff --git a/js/vapi-net.js b/js/vapi-net.js new file mode 100644 index 0000000..5c6ccaf --- /dev/null +++ b/js/vapi-net.js @@ -0,0 +1,78 @@ +/******************************************************************************* + + ηMatrix - a browser extension to black/white list requests. + Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors + Copyright (C) 2019 Alessio Vanni + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://gitlab.com/vannilla/ematrix + uMatrix Home: https://github.com/gorhill/uMatrix +*/ + +/* global self, Components */ + +// For background page (tabs management) + +'use strict'; + +/******************************************************************************/ + +(function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js + + vAPI.net = {}; + + vAPI.net.registerListeners = function () { + this.onBeforeRequest.types = this.onBeforeRequest.types + ? new Set(this.onBeforeRequest.types) + : null; + + this.onBeforeSendHeaders.types = this.onBeforeSendHeaders.types + ? new Set(this.onBeforeSendHeaders.types) + : null; + + let shouldLoadListenerMessageName = location.host + ':shouldLoad'; + let shouldLoadListener = function (e) { + let details = e.data; + let pendingReq = httpObserver.createPendingRequest(details.url); + pendingReq.rawType = details.rawType; + pendingReq.tabId = vAPI.tabs.manager.tabIdFromTarget(e.target); + }; + + // https://github.com/gorhill/uMatrix/issues/200 + // We need this only for Firefox 34 and less: the tab id is derived from + // the origin of the message. + if (!vAPI.modernFirefox) { + vAPI.messaging.globalMessageManager + .addMessageListener(shouldLoadListenerMessageName, + shouldLoadListener); + } + + httpObserver.register(); + + vAPI.addCleanUpTask(function () { + if (!vAPI.modernFirefox) { + vAPI.messaging.globalMessageManager + .removeMessageListener(shouldLoadListenerMessageName, + shouldLoadListener); + } + + httpObserver.unregister(); + }); + }; +})(); -- cgit v1.2.3 From 9a5c1c75f96b26041443309a8bfa977da8950c38 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Sun, 23 Jun 2019 13:34:42 +0200 Subject: Get cookie management out of vapi-background --- js/vapi-background.js | 101 --------------------------------------- js/vapi-cookies.js | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 101 deletions(-) create mode 100644 js/vapi-cookies.js (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 3035556..1690c06 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -947,13 +947,6 @@ })(); (function() { - // It appears that this branch actually works on the latest - // Basilisk. Maybe we can simply use this one directly instead of - // making checks like it's done now. - - // It was decided to use this branch unconditionally. It's still - // experimental though. - // Add toolbar button for Basilisk if (Services.appinfo.ID !== "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") { return; @@ -1140,7 +1133,6 @@ })(); // No toolbar button. - (function () { // Just to ensure the number of cleanup tasks is as expected: toolbar // button code is one single cleanup task regardless of platform. @@ -1587,97 +1579,4 @@ setOptions: setOptions }; })(); - - vAPI.cookies = {}; - // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsICookieManager2 - // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsICookie2 - // https://developer.mozilla.org/en-US/docs/Observer_Notifications#Cookies - - vAPI.cookies.CookieEntry = function (ffCookie) { - this.domain = ffCookie.host; - this.name = ffCookie.name; - this.path = ffCookie.path; - this.secure = ffCookie.isSecure === true; - this.session = ffCookie.expires === 0; - this.value = ffCookie.value; - }; - - vAPI.cookies.start = function () { - Services.obs.addObserver(this, 'cookie-changed', false); - Services.obs.addObserver(this, 'private-cookie-changed', false); - vAPI.addCleanUpTask(this.stop.bind(this)); - }; - - vAPI.cookies.stop = function () { - Services.obs.removeObserver(this, 'cookie-changed'); - Services.obs.removeObserver(this, 'private-cookie-changed'); - }; - - vAPI.cookies.observe = function (subject, topic, reason) { - //if ( topic !== 'cookie-changed' && topic !== 'private-cookie-changed' ) { - // return; - //} - // - if (reason === 'cleared' && typeof this.onAllRemoved === 'function') { - this.onAllRemoved(); - return; - } - if (subject === null) { - return; - } - if (subject instanceof Ci.nsICookie2 === false) { - try { - subject = subject.QueryInterface(Ci.nsICookie2); - } catch (ex) { - return; - } - } - if (reason === 'deleted') { - if (typeof this.onRemoved === 'function') { - this.onRemoved(new this.CookieEntry(subject)); - } - return; - } - if (typeof this.onChanged === 'function') { - this.onChanged(new this.CookieEntry(subject)); - } - }; - - vAPI.cookies.getAll = function(callback) { - // Meant and expected to be asynchronous. - if (typeof callback !== 'function') { - return; - } - - let onAsync = function () { - let out = []; - let enumerator = Services.cookies.enumerator; - let ffcookie; - while (enumerator.hasMoreElements()) { - ffcookie = enumerator.getNext(); - if (ffcookie instanceof Ci.nsICookie) { - out.push(new this.CookieEntry(ffcookie)); - } - } - - callback(out); - }; - - vAPI.setTimeout(onAsync.bind(this), 0); - }; - - vAPI.cookies.remove = function (details, callback) { - let uri = Services.io.newURI(details.url, null, null); - let cookies = Services.cookies; - cookies.remove(uri.asciiHost, details.name, uri.path, false, {}); - cookies.remove( '.' + uri.asciiHost, details.name, uri.path, false, {}); - - if (typeof callback === 'function') { - callback({ - domain: uri.asciiHost, - name: details.name, - path: uri.path - }); - } - }; })(); diff --git a/js/vapi-cookies.js b/js/vapi-cookies.js new file mode 100644 index 0000000..be989b2 --- /dev/null +++ b/js/vapi-cookies.js @@ -0,0 +1,127 @@ +/******************************************************************************* + + ηMatrix - a browser extension to black/white list requests. + Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors + Copyright (C) 2019 Alessio Vanni + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://gitlab.com/vannilla/ematrix + uMatrix Home: https://github.com/gorhill/uMatrix +*/ + +/* global self, Components */ + +// For background page (tabs management) + +'use strict'; + +/******************************************************************************/ + +(function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js + + vAPI.cookies = {}; + + vAPI.cookies.CookieEntry = function (ffCookie) { + this.domain = ffCookie.host; + this.name = ffCookie.name; + this.path = ffCookie.path; + this.secure = ffCookie.isSecure === true; + this.session = ffCookie.expires === 0; + this.value = ffCookie.value; + }; + + vAPI.cookies.start = function () { + Services.obs.addObserver(this, 'cookie-changed', false); + Services.obs.addObserver(this, 'private-cookie-changed', false); + vAPI.addCleanUpTask(this.stop.bind(this)); + }; + + vAPI.cookies.stop = function () { + Services.obs.removeObserver(this, 'cookie-changed'); + Services.obs.removeObserver(this, 'private-cookie-changed'); + }; + + vAPI.cookies.observe = function (subject, topic, reason) { + //if ( topic !== 'cookie-changed' && topic !== 'private-cookie-changed' ) { + // return; + //} + // + if (reason === 'cleared' && typeof this.onAllRemoved === 'function') { + this.onAllRemoved(); + return; + } + if (subject === null) { + return; + } + if (subject instanceof Ci.nsICookie2 === false) { + try { + subject = subject.QueryInterface(Ci.nsICookie2); + } catch (ex) { + return; + } + } + if (reason === 'deleted') { + if (typeof this.onRemoved === 'function') { + this.onRemoved(new this.CookieEntry(subject)); + } + return; + } + if (typeof this.onChanged === 'function') { + this.onChanged(new this.CookieEntry(subject)); + } + }; + + vAPI.cookies.getAll = function(callback) { + // Meant and expected to be asynchronous. + if (typeof callback !== 'function') { + return; + } + + let onAsync = function () { + let out = []; + let enumerator = Services.cookies.enumerator; + let ffcookie; + while (enumerator.hasMoreElements()) { + ffcookie = enumerator.getNext(); + if (ffcookie instanceof Ci.nsICookie) { + out.push(new this.CookieEntry(ffcookie)); + } + } + + callback(out); + }; + + vAPI.setTimeout(onAsync.bind(this), 0); + }; + + vAPI.cookies.remove = function (details, callback) { + let uri = Services.io.newURI(details.url, null, null); + let cookies = Services.cookies; + cookies.remove(uri.asciiHost, details.name, uri.path, false, {}); + cookies.remove( '.' + uri.asciiHost, details.name, uri.path, false, {}); + + if (typeof callback === 'function') { + callback({ + domain: uri.asciiHost, + name: details.name, + path: uri.path + }); + } + }; +})(); -- cgit v1.2.3 From 194b9f768b7e8ea57217fa6cf7b501727e65b662 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Sun, 23 Jun 2019 13:36:44 +0200 Subject: Remove some comments While they are technically informative, the splitting makes things easier to follow already (somewhat) and there's not really a need to list each global variable (there aren't many anyway.) --- js/vapi-browser.js | 4 ---- js/vapi-cookies.js | 4 ---- js/vapi-core.js | 4 ---- js/vapi-messaging.js | 4 ---- js/vapi-net.js | 4 ---- js/vapi-storage.js | 4 ---- js/vapi-tabs.js | 4 ---- js/vapi-window.js | 3 --- 8 files changed, 31 deletions(-) (limited to 'js') diff --git a/js/vapi-browser.js b/js/vapi-browser.js index be4a8bd..27e5e9c 100644 --- a/js/vapi-browser.js +++ b/js/vapi-browser.js @@ -21,10 +21,6 @@ uMatrix Home: https://github.com/gorhill/uMatrix */ -/* global self, Components */ - -// For background page (tabs management) - 'use strict'; /******************************************************************************/ diff --git a/js/vapi-cookies.js b/js/vapi-cookies.js index be989b2..500d27b 100644 --- a/js/vapi-cookies.js +++ b/js/vapi-cookies.js @@ -21,10 +21,6 @@ uMatrix Home: https://github.com/gorhill/uMatrix */ -/* global self, Components */ - -// For background page (tabs management) - 'use strict'; /******************************************************************************/ diff --git a/js/vapi-core.js b/js/vapi-core.js index 4ab5216..419955b 100644 --- a/js/vapi-core.js +++ b/js/vapi-core.js @@ -21,10 +21,6 @@ uMatrix Home: https://github.com/gorhill/uMatrix */ -/* global self, Components */ - -// For background page (important functions) - 'use strict'; /******************************************************************************/ diff --git a/js/vapi-messaging.js b/js/vapi-messaging.js index b00c054..e7d1985 100644 --- a/js/vapi-messaging.js +++ b/js/vapi-messaging.js @@ -21,10 +21,6 @@ uMatrix Home: https://github.com/gorhill/uMatrix */ -/* global self, Components */ - -// For background page (tabs management) - 'use strict'; /******************************************************************************/ diff --git a/js/vapi-net.js b/js/vapi-net.js index 5c6ccaf..c1a605d 100644 --- a/js/vapi-net.js +++ b/js/vapi-net.js @@ -21,10 +21,6 @@ uMatrix Home: https://github.com/gorhill/uMatrix */ -/* global self, Components */ - -// For background page (tabs management) - 'use strict'; /******************************************************************************/ diff --git a/js/vapi-storage.js b/js/vapi-storage.js index 768958c..75ed6ff 100644 --- a/js/vapi-storage.js +++ b/js/vapi-storage.js @@ -21,10 +21,6 @@ uMatrix Home: https://github.com/gorhill/uMatrix */ -/* global self, Components */ - -// For background page (tabs management) - 'use strict'; /******************************************************************************/ diff --git a/js/vapi-tabs.js b/js/vapi-tabs.js index 1c56cfb..fa0fe3b 100644 --- a/js/vapi-tabs.js +++ b/js/vapi-tabs.js @@ -21,10 +21,6 @@ uMatrix Home: https://github.com/gorhill/uMatrix */ -/* global self, Components */ - -// For background page (tabs management) - 'use strict'; /******************************************************************************/ diff --git a/js/vapi-window.js b/js/vapi-window.js index ab57e35..3a59cb9 100644 --- a/js/vapi-window.js +++ b/js/vapi-window.js @@ -21,9 +21,6 @@ uMatrix Home: https://github.com/gorhill/uMatrix */ -/* global self, Components */ - -// For background page (windows management) 'use strict'; /******************************************************************************/ -- cgit v1.2.3 From a15d6164fa76b14a06de23eecaa850704a77447f Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Thu, 4 Jul 2019 17:12:55 +0200 Subject: Split context menu from vapi-background Also fix a typo in Makefile. --- js/vapi-background.js | 184 ----------------------------------------- js/vapi-contextmenu.js | 219 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 184 deletions(-) create mode 100644 js/vapi-contextmenu.js (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 1690c06..8f095ae 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -1146,190 +1146,6 @@ vAPI.toolbarButton.init(); } - vAPI.contextMenu = { - contextMap: { - frame: 'inFrame', - link: 'onLink', - image: 'onImage', - audio: 'onAudio', - video: 'onVideo', - editable: 'onEditableArea' - } - }; - - vAPI.contextMenu.displayMenuItem = function ({target}) { - let doc = target.ownerDocument; - let gContextMenu = doc.defaultView.gContextMenu; - if (!gContextMenu.browser) { - return; - } - - let menuitem = doc.getElementById(vAPI.contextMenu.menuItemId); - let currentURI = gContextMenu.browser.currentURI; - - // https://github.com/chrisaljoudi/uBlock/issues/105 - // TODO: Should the element picker works on any kind of pages? - if (!currentURI.schemeIs('http') && !currentURI.schemeIs('https')) { - menuitem.setAttribute('hidden', true); - return; - } - - let ctx = vAPI.contextMenu.contexts; - - if (!ctx) { - menuitem.setAttribute('hidden', false); - return; - } - - let ctxMap = vAPI.contextMenu.contextMap; - - for (let context of ctx) { - if (context === 'page' - && !gContextMenu.onLink - && !gContextMenu.onImage - && !gContextMenu.onEditableArea - && !gContextMenu.inFrame - && !gContextMenu.onVideo - && !gContextMenu.onAudio) { - menuitem.setAttribute('hidden', false); - return; - } - - if (ctxMap.hasOwnProperty(context) - && gContextMenu[ctxMap[context]]) { - menuitem.setAttribute('hidden', false); - return; - } - } - - menuitem.setAttribute('hidden', true); - }; - - vAPI.contextMenu.register = (function () { - let register = function (doc) { - if (!this.menuItemId) { - return; - } - - // Already installed? - if (doc.getElementById(this.menuItemId) !== null) { - return; - } - - let contextMenu = doc.getElementById('contentAreaContextMenu'); - - let menuitem = doc.createElement('menuitem'); - menuitem.setAttribute('id', this.menuItemId); - menuitem.setAttribute('label', this.menuLabel); - menuitem.setAttribute('image', vAPI.getURL('img/browsericons/icon19-19.png')); - menuitem.setAttribute('class', 'menuitem-iconic'); - menuitem.addEventListener('command', this.onCommand); - - contextMenu.addEventListener('popupshowing', this.displayMenuItem); - contextMenu.insertBefore(menuitem, doc.getElementById('inspect-separator')); - }; - - var registerSafely = function (doc, tryCount) { - // https://github.com/gorhill/uBlock/issues/906 - // Be sure document.readyState is 'complete': it could happen - // at launch time that we are called by - // vAPI.contextMenu.create() directly before the environment - // is properly initialized. - if (doc.readyState === 'complete') { - register.call(this, doc); - return; - } - - if (typeof tryCount !== 'number') { - tryCount = 0; - } - - tryCount += 1; - if ( tryCount < 8) { - vAPI.setTimeout(registerSafely.bind(this, doc, tryCount), 200); - } - }; - - return registerSafely; - })(); - - vAPI.contextMenu.unregister = function (doc) { - if (!this.menuItemId) { - return; - } - - let menuitem = doc.getElementById(this.menuItemId); - if (menuitem === null) { - return; - } - - let contextMenu = menuitem.parentNode; - menuitem.removeEventListener('command', this.onCommand); - contextMenu.removeEventListener('popupshowing', this.displayMenuItem); - contextMenu.removeChild(menuitem); - }; - - vAPI.contextMenu.create = function (details, callback) { - this.menuItemId = details.id; - this.menuLabel = details.title; - this.contexts = details.contexts; - - if (Array.isArray(this.contexts) && this.contexts.length) { - this.contexts = this.contexts.indexOf('all') === -1 - ? this.contexts - : null; - } else { - // default in Chrome - this.contexts = ['page']; - } - - this.onCommand = function () { - let gContextMenu = vAPI.browser.getOwnerWindow(this).gContextMenu; - let details = { - menuItemId: this.id - }; - - if (gContextMenu.inFrame) { - details.tagName = 'iframe'; - // Probably won't work with e10s - // eMatrix: doesn't matter ;) - details.frameUrl = gContextMenu.focusedWindow.location.href; - } else if (gContextMenu.onImage) { - details.tagName = 'img'; - details.srcUrl = gContextMenu.mediaURL; - } else if (gContextMenu.onAudio) { - details.tagName = 'audio'; - details.srcUrl = gContextMenu.mediaURL; - } else if (gContextMenu.onVideo) { - details.tagName = 'video'; - details.srcUrl = gContextMenu.mediaURL; - } else if (gContextMenu.onLink) { - details.tagName = 'a'; - details.linkUrl = gContextMenu.linkURL; - } - - callback(details, { - id: vAPI.tabs.manager.tabIdFromTarget(gContextMenu.browser), - url: gContextMenu.browser.currentURI.asciiSpec - }); - }; - - for (let win of vAPI.window.getWindows()) { - this.register(win.document); - } - }; - - vAPI.contextMenu.remove = function () { - for (let win of vAPI.window.getWindows()) { - this.unregister(win.document); - } - - this.menuItemId = null; - this.menuLabel = null; - this.contexts = null; - this.onCommand = null; - }; - let optionsObserver = (function () { let addonId = 'eMatrix@vannilla.org'; diff --git a/js/vapi-contextmenu.js b/js/vapi-contextmenu.js new file mode 100644 index 0000000..28715a8 --- /dev/null +++ b/js/vapi-contextmenu.js @@ -0,0 +1,219 @@ +/******************************************************************************* + + ηMatrix - a browser extension to black/white list requests. + Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors + Copyright (C) 2019 Alessio Vanni + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://gitlab.com/vannilla/ematrix + uMatrix Home: https://github.com/gorhill/uMatrix +*/ + +'use strict'; + +/******************************************************************************/ + +(function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js + + vAPI.contextMenu = { + contextMap: { + frame: 'inFrame', + link: 'onLink', + image: 'onImage', + audio: 'onAudio', + video: 'onVideo', + editable: 'onEditableArea' + } + }; + + vAPI.contextMenu.displayMenuItem = function ({target}) { + let doc = target.ownerDocument; + let gContextMenu = doc.defaultView.gContextMenu; + if (!gContextMenu.browser) { + return; + } + + let menuitem = doc.getElementById(vAPI.contextMenu.menuItemId); + let currentURI = gContextMenu.browser.currentURI; + + // https://github.com/chrisaljoudi/uBlock/issues/105 + // TODO: Should the element picker works on any kind of pages? + if (!currentURI.schemeIs('http') && !currentURI.schemeIs('https')) { + menuitem.setAttribute('hidden', true); + return; + } + + let ctx = vAPI.contextMenu.contexts; + + if (!ctx) { + menuitem.setAttribute('hidden', false); + return; + } + + let ctxMap = vAPI.contextMenu.contextMap; + + for (let context of ctx) { + if (context === 'page' + && !gContextMenu.onLink + && !gContextMenu.onImage + && !gContextMenu.onEditableArea + && !gContextMenu.inFrame + && !gContextMenu.onVideo + && !gContextMenu.onAudio) { + menuitem.setAttribute('hidden', false); + return; + } + + if (ctxMap.hasOwnProperty(context) + && gContextMenu[ctxMap[context]]) { + menuitem.setAttribute('hidden', false); + return; + } + } + + menuitem.setAttribute('hidden', true); + }; + + vAPI.contextMenu.register = (function () { + let register = function (doc) { + if (!this.menuItemId) { + return; + } + + // Already installed? + if (doc.getElementById(this.menuItemId) !== null) { + return; + } + + let contextMenu = doc.getElementById('contentAreaContextMenu'); + + let menuitem = doc.createElement('menuitem'); + menuitem.setAttribute('id', this.menuItemId); + menuitem.setAttribute('label', this.menuLabel); + menuitem.setAttribute('image', vAPI.getURL('img/browsericons/icon19-19.png')); + menuitem.setAttribute('class', 'menuitem-iconic'); + menuitem.addEventListener('command', this.onCommand); + + contextMenu.addEventListener('popupshowing', this.displayMenuItem); + contextMenu.insertBefore(menuitem, doc.getElementById('inspect-separator')); + }; + + var registerSafely = function (doc, tryCount) { + // https://github.com/gorhill/uBlock/issues/906 + // Be sure document.readyState is 'complete': it could happen + // at launch time that we are called by + // vAPI.contextMenu.create() directly before the environment + // is properly initialized. + if (doc.readyState === 'complete') { + register.call(this, doc); + return; + } + + if (typeof tryCount !== 'number') { + tryCount = 0; + } + + tryCount += 1; + if ( tryCount < 8) { + vAPI.setTimeout(registerSafely.bind(this, doc, tryCount), 200); + } + }; + + return registerSafely; + })(); + + vAPI.contextMenu.unregister = function (doc) { + if (!this.menuItemId) { + return; + } + + let menuitem = doc.getElementById(this.menuItemId); + if (menuitem === null) { + return; + } + + let contextMenu = menuitem.parentNode; + menuitem.removeEventListener('command', this.onCommand); + contextMenu.removeEventListener('popupshowing', this.displayMenuItem); + contextMenu.removeChild(menuitem); + }; + + vAPI.contextMenu.create = function (details, callback) { + this.menuItemId = details.id; + this.menuLabel = details.title; + this.contexts = details.contexts; + + console.debug(this.menuItemId); + + if (Array.isArray(this.contexts) && this.contexts.length) { + this.contexts = this.contexts.indexOf('all') === -1 + ? this.contexts + : null; + } else { + // default in Chrome + this.contexts = ['page']; + } + + this.onCommand = function () { + let gContextMenu = vAPI.browser.getOwnerWindow(this).gContextMenu; + let details = { + menuItemId: this.id + }; + + if (gContextMenu.inFrame) { + details.tagName = 'iframe'; + // Probably won't work with e10s + // eMatrix: doesn't matter ;) + details.frameUrl = gContextMenu.focusedWindow.location.href; + } else if (gContextMenu.onImage) { + details.tagName = 'img'; + details.srcUrl = gContextMenu.mediaURL; + } else if (gContextMenu.onAudio) { + details.tagName = 'audio'; + details.srcUrl = gContextMenu.mediaURL; + } else if (gContextMenu.onVideo) { + details.tagName = 'video'; + details.srcUrl = gContextMenu.mediaURL; + } else if (gContextMenu.onLink) { + details.tagName = 'a'; + details.linkUrl = gContextMenu.linkURL; + } + + callback(details, { + id: vAPI.tabs.manager.tabIdFromTarget(gContextMenu.browser), + url: gContextMenu.browser.currentURI.asciiSpec + }); + }; + + for (let win of vAPI.window.getWindows()) { + this.register(win.document); + } + }; + + vAPI.contextMenu.remove = function () { + for (let win of vAPI.window.getWindows()) { + this.unregister(win.document); + } + + this.menuItemId = null; + this.menuLabel = null; + this.contexts = null; + this.onCommand = null; + }; +})(); -- cgit v1.2.3 From fbb154ffb1bc1be56004820bf62015c92d13c3c5 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Thu, 4 Jul 2019 17:17:32 +0200 Subject: Temporarily fix undefined reference Ideally HttpObserver should be its own module, but right now it depends on too much "context" to be independent from vapi-background. --- js/vapi-background.js | 4 ++-- js/vapi-net.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 8f095ae..0c14992 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -34,7 +34,7 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); const {HTTPRequestHeaders} = - Cu.import('chrome://ematrix/content/HTTPRequestHeaders.jsm', null); + Cu.import('chrome://ematrix/content/HttpRequestHeaders.jsm', null); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-core.js @@ -71,7 +71,7 @@ } }; - let httpObserver = { + vAPI.httpObserver = { classDescription: 'net-channel-event-sinks for ' + location.host, classID: Components.ID('{5d2e2797-6d68-42e2-8aeb-81ce6ba16b95}'), contractID: '@' + location.host + '/net-channel-event-sinks;1', diff --git a/js/vapi-net.js b/js/vapi-net.js index c1a605d..3d5b27b 100644 --- a/js/vapi-net.js +++ b/js/vapi-net.js @@ -45,7 +45,7 @@ let shouldLoadListenerMessageName = location.host + ':shouldLoad'; let shouldLoadListener = function (e) { let details = e.data; - let pendingReq = httpObserver.createPendingRequest(details.url); + let pendingReq = vAPI.httpObserver.createPendingRequest(details.url); pendingReq.rawType = details.rawType; pendingReq.tabId = vAPI.tabs.manager.tabIdFromTarget(e.target); }; @@ -59,7 +59,7 @@ shouldLoadListener); } - httpObserver.register(); + vAPI.httpObserver.register(); vAPI.addCleanUpTask(function () { if (!vAPI.modernFirefox) { @@ -68,7 +68,7 @@ shouldLoadListener); } - httpObserver.unregister(); + vAPI.httpObserver.unregister(); }); }; })(); -- cgit v1.2.3 From 9d87f8f864b28d182af9659a3095433adb4b0126 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Thu, 4 Jul 2019 17:33:06 +0200 Subject: Change how modules are imported I can't really find a reason why the returned value is preferred over the normal importing process. Additionally, there's a good chance importing Services.jsm can be done only once at the start of everything, instead of binding each object to a separate closure. --- js/vapi-background.js | 5 ++--- js/vapi-browser.js | 2 +- js/vapi-common.js | 5 +---- js/vapi-contextmenu.js | 2 +- js/vapi-cookies.js | 2 +- js/vapi-core.js | 2 +- js/vapi-messaging.js | 5 ++--- js/vapi-net.js | 2 +- js/vapi-storage.js | 2 +- js/vapi-tabs.js | 2 +- js/vapi-window.js | 2 +- 11 files changed, 13 insertions(+), 18 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 0c14992..3f73af7 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -32,9 +32,8 @@ (function () { const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); - const {HTTPRequestHeaders} = - Cu.import('chrome://ematrix/content/HttpRequestHeaders.jsm', null); + Cu.import('resource://gre/modules/Services.jsm'); + Cu.import('chrome://ematrix/content/HttpRequestHeaders.jsm'); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-core.js diff --git a/js/vapi-browser.js b/js/vapi-browser.js index 27e5e9c..07d954b 100644 --- a/js/vapi-browser.js +++ b/js/vapi-browser.js @@ -27,7 +27,7 @@ (function () { const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + Cu.import('resource://gre/modules/Services.jsm'); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js diff --git a/js/vapi-common.js b/js/vapi-common.js index f1bc2ca..1bc20bc 100644 --- a/js/vapi-common.js +++ b/js/vapi-common.js @@ -33,10 +33,7 @@ /******************************************************************************/ -const {Services} = Components.utils.import( - 'resource://gre/modules/Services.jsm', - null -); +Components.utils.import('resource://gre/modules/Services.jsm'); // https://bugs.chromium.org/p/project-zero/issues/detail?id=1225&desc=6#c10 // eMatrix: does this apply to us? diff --git a/js/vapi-contextmenu.js b/js/vapi-contextmenu.js index 28715a8..70229da 100644 --- a/js/vapi-contextmenu.js +++ b/js/vapi-contextmenu.js @@ -27,7 +27,7 @@ (function () { const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + Cu.import('resource://gre/modules/Services.jsm'); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js diff --git a/js/vapi-cookies.js b/js/vapi-cookies.js index 500d27b..1f19c49 100644 --- a/js/vapi-cookies.js +++ b/js/vapi-cookies.js @@ -27,7 +27,7 @@ (function () { const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + Cu.import('resource://gre/modules/Services.jsm'); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js diff --git a/js/vapi-core.js b/js/vapi-core.js index 419955b..c904ff2 100644 --- a/js/vapi-core.js +++ b/js/vapi-core.js @@ -27,7 +27,7 @@ (function () { const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + Cu.import('resource://gre/modules/Services.jsm'); let vAPI = self.vAPI = self.vAPI || {}; diff --git a/js/vapi-messaging.js b/js/vapi-messaging.js index e7d1985..170e85d 100644 --- a/js/vapi-messaging.js +++ b/js/vapi-messaging.js @@ -27,9 +27,8 @@ (function () { const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); - const {CallbackWrapper} = - Cu.import('chrome://ematrix/content/CallbackWrapper.jsm', null); + Cu.import('resource://gre/modules/Services.jsm'); + Cu.import('chrome://ematrix/content/CallbackWrapper.jsm'); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js diff --git a/js/vapi-net.js b/js/vapi-net.js index 3d5b27b..25de1cb 100644 --- a/js/vapi-net.js +++ b/js/vapi-net.js @@ -27,7 +27,7 @@ (function () { const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + Cu.import('resource://gre/modules/Services.jsm'); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js diff --git a/js/vapi-storage.js b/js/vapi-storage.js index 75ed6ff..6a6cb3b 100644 --- a/js/vapi-storage.js +++ b/js/vapi-storage.js @@ -27,7 +27,7 @@ (function () { const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + Cu.import('resource://gre/modules/Services.jsm'); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js diff --git a/js/vapi-tabs.js b/js/vapi-tabs.js index fa0fe3b..390f449 100644 --- a/js/vapi-tabs.js +++ b/js/vapi-tabs.js @@ -27,7 +27,7 @@ (function () { const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + Cu.import('resource://gre/modules/Services.jsm'); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js diff --git a/js/vapi-window.js b/js/vapi-window.js index 3a59cb9..07fb80c 100644 --- a/js/vapi-window.js +++ b/js/vapi-window.js @@ -27,7 +27,7 @@ (function () { const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + Cu.import('resource://gre/modules/Services.jsm'); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-core.js -- cgit v1.2.3 From 669290775fd9b85fdb8c3fdbaa597c30841803aa Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Thu, 4 Jul 2019 17:41:59 +0200 Subject: Fix a couple of stylistic choices --- js/vapi-contextmenu.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'js') diff --git a/js/vapi-contextmenu.js b/js/vapi-contextmenu.js index 70229da..34c4263 100644 --- a/js/vapi-contextmenu.js +++ b/js/vapi-contextmenu.js @@ -114,7 +114,7 @@ contextMenu.insertBefore(menuitem, doc.getElementById('inspect-separator')); }; - var registerSafely = function (doc, tryCount) { + let registerSafely = function (doc, tryCount) { // https://github.com/gorhill/uBlock/issues/906 // Be sure document.readyState is 'complete': it could happen // at launch time that we are called by @@ -130,7 +130,7 @@ } tryCount += 1; - if ( tryCount < 8) { + if (tryCount < 8) { vAPI.setTimeout(registerSafely.bind(this, doc, tryCount), 200); } }; @@ -159,8 +159,6 @@ this.menuLabel = details.title; this.contexts = details.contexts; - console.debug(this.menuItemId); - if (Array.isArray(this.contexts) && this.contexts.length) { this.contexts = this.contexts.indexOf('all') === -1 ? this.contexts -- cgit v1.2.3 From 8518e00ce1013978bebde2c8ddf36766c982ab87 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Thu, 4 Jul 2019 17:54:50 +0200 Subject: Make vapi-cloud its own file --- js/vapi-background.js | 128 --------------------------------------- js/vapi-cloud.js | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 128 deletions(-) create mode 100644 js/vapi-cloud.js (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 3f73af7..067707c 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -1266,132 +1266,4 @@ return url; }; - - vAPI.cloud = (function () { - let extensionBranchPath = 'extensions.' + location.host; - let cloudBranchPath = extensionBranchPath + '.cloudStorage'; - - // https://github.com/gorhill/uBlock/issues/80#issuecomment-132081658 - // We must use get/setComplexValue in order to properly handle strings - // with unicode characters. - let iss = Ci.nsISupportsString; - let argstr = Components.classes['@mozilla.org/supports-string;1'] - .createInstance(iss); - - let options = { - defaultDeviceName: '', - deviceName: '' - }; - - // User-supplied device name. - try { - options.deviceName = Services.prefs - .getBranch(extensionBranchPath + '.') - .getComplexValue('deviceName', iss) - .data; - } catch(ex) { - // Ignore - } - - var getDefaultDeviceName = function() { - var name = ''; - try { - name = Services.prefs - .getBranch('services.sync.client.') - .getComplexValue('name', iss) - .data; - } catch(ex) { - // Ignore - } - - return name || window.navigator.platform || window.navigator.oscpu; - }; - - let start = function (dataKeys) { - let extensionBranch = - Services.prefs.getBranch(extensionBranchPath + '.'); - let syncBranch = - Services.prefs.getBranch('services.sync.prefs.sync.'); - - // Mark config entries as syncable - argstr.data = ''; - let dataKey; - for (let i=0; i Date: Thu, 4 Jul 2019 17:57:11 +0200 Subject: Remove unused files vapi-popup wasn't even included in background.html. Also, it seems vapi-contextmenu is useless, because vapi-tabs attempts to register it without creating it first (making registration a failure, naturally.) Since it never worked since eMatrix 1.0.0 and it doesn't seem to add anything useful, let's remove it from the XPI. --- js/vapi-tabs.js | 3 --- 1 file changed, 3 deletions(-) (limited to 'js') diff --git a/js/vapi-tabs.js b/js/vapi-tabs.js index 390f449..54b09d0 100644 --- a/js/vapi-tabs.js +++ b/js/vapi-tabs.js @@ -576,7 +576,6 @@ } else if (tabBrowser.tabContainer) { // Firefox tabContainer = tabBrowser.tabContainer; - vAPI.contextMenu.register(document); } // https://github.com/gorhill/uBlock/issues/697 @@ -620,8 +619,6 @@ }; let onWindowUnload = function (win) { - vAPI.contextMenu.unregister(win.document); - let tabBrowser = vAPI.browser.getTabBrowser(win); if (tabBrowser === null) { return; -- cgit v1.2.3 From 51f5e899fff9e804d9c91e4fefdd57ea5a85e99c Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Thu, 4 Jul 2019 18:06:54 +0200 Subject: Make components and services global Given that they are used a lot, at least in vAPI, let's just define/import them only once. --- js/vapi-background.js | 2 -- js/vapi-browser.js | 3 --- js/vapi-cloud.js | 3 --- js/vapi-contextmenu.js | 3 --- js/vapi-cookies.js | 3 --- js/vapi-core.js | 6 +++--- js/vapi-messaging.js | 2 -- js/vapi-net.js | 3 --- js/vapi-storage.js | 3 --- js/vapi-tabs.js | 3 --- js/vapi-window.js | 3 --- 11 files changed, 3 insertions(+), 31 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 067707c..9df1dbb 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -31,8 +31,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); Cu.import('chrome://ematrix/content/HttpRequestHeaders.jsm'); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-core.js diff --git a/js/vapi-browser.js b/js/vapi-browser.js index 07d954b..4828b3d 100644 --- a/js/vapi-browser.js +++ b/js/vapi-browser.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js vAPI.browser = {}; diff --git a/js/vapi-cloud.js b/js/vapi-cloud.js index b3d1b80..e4e3092 100644 --- a/js/vapi-cloud.js +++ b/js/vapi-cloud.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js vAPI.cloud = (function () { diff --git a/js/vapi-contextmenu.js b/js/vapi-contextmenu.js index 34c4263..3cdfe20 100644 --- a/js/vapi-contextmenu.js +++ b/js/vapi-contextmenu.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js vAPI.contextMenu = { diff --git a/js/vapi-cookies.js b/js/vapi-cookies.js index 1f19c49..9211c48 100644 --- a/js/vapi-cookies.js +++ b/js/vapi-cookies.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js vAPI.cookies = {}; diff --git a/js/vapi-core.js b/js/vapi-core.js index c904ff2..3901c1c 100644 --- a/js/vapi-core.js +++ b/js/vapi-core.js @@ -23,12 +23,12 @@ 'use strict'; +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; +Cu.import('resource://gre/modules/Services.jsm'); + /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI = self.vAPI || {}; vAPI.modernFirefox = diff --git a/js/vapi-messaging.js b/js/vapi-messaging.js index 170e85d..0df2362 100644 --- a/js/vapi-messaging.js +++ b/js/vapi-messaging.js @@ -26,8 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); Cu.import('chrome://ematrix/content/CallbackWrapper.jsm'); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js diff --git a/js/vapi-net.js b/js/vapi-net.js index 25de1cb..6f7e885 100644 --- a/js/vapi-net.js +++ b/js/vapi-net.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js vAPI.net = {}; diff --git a/js/vapi-storage.js b/js/vapi-storage.js index 6a6cb3b..6dbb955 100644 --- a/js/vapi-storage.js +++ b/js/vapi-storage.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js // API matches that of chrome.storage.local: diff --git a/js/vapi-tabs.js b/js/vapi-tabs.js index 54b09d0..6e953f2 100644 --- a/js/vapi-tabs.js +++ b/js/vapi-tabs.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js vAPI.tabs = {}; diff --git a/js/vapi-window.js b/js/vapi-window.js index 07fb80c..9cb245d 100644 --- a/js/vapi-window.js +++ b/js/vapi-window.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-core.js vAPI.window = (function () { -- cgit v1.2.3 From d8950421332a0d5b28cba6df38fc4fe85ff26d9e Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Thu, 4 Jul 2019 18:08:31 +0200 Subject: Remove one more importing --- js/vapi-common.js | 2 -- 1 file changed, 2 deletions(-) (limited to 'js') diff --git a/js/vapi-common.js b/js/vapi-common.js index 1bc20bc..2b4158a 100644 --- a/js/vapi-common.js +++ b/js/vapi-common.js @@ -33,8 +33,6 @@ /******************************************************************************/ -Components.utils.import('resource://gre/modules/Services.jsm'); - // https://bugs.chromium.org/p/project-zero/issues/detail?id=1225&desc=6#c10 // eMatrix: does this apply to us? // In the meantime, let's remove the useless `eMatrix' property from vAPI -- cgit v1.2.3 From f1f66637b814155c95a2bfddfdd9cd2973b86659 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Thu, 4 Jul 2019 18:20:08 +0200 Subject: Revert "Make components and services global" This reverts commit 51f5e899fff9e804d9c91e4fefdd57ea5a85e99c. It seems to cause issues with the popup menu. --- js/vapi-background.js | 2 ++ js/vapi-browser.js | 3 +++ js/vapi-cloud.js | 3 +++ js/vapi-contextmenu.js | 3 +++ js/vapi-cookies.js | 3 +++ js/vapi-core.js | 6 +++--- js/vapi-messaging.js | 2 ++ js/vapi-net.js | 3 +++ js/vapi-storage.js | 3 +++ js/vapi-tabs.js | 3 +++ js/vapi-window.js | 3 +++ 11 files changed, 31 insertions(+), 3 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 9df1dbb..067707c 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -31,6 +31,8 @@ /******************************************************************************/ (function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + Cu.import('resource://gre/modules/Services.jsm'); Cu.import('chrome://ematrix/content/HttpRequestHeaders.jsm'); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-core.js diff --git a/js/vapi-browser.js b/js/vapi-browser.js index 4828b3d..07d954b 100644 --- a/js/vapi-browser.js +++ b/js/vapi-browser.js @@ -26,6 +26,9 @@ /******************************************************************************/ (function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + Cu.import('resource://gre/modules/Services.jsm'); + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js vAPI.browser = {}; diff --git a/js/vapi-cloud.js b/js/vapi-cloud.js index e4e3092..b3d1b80 100644 --- a/js/vapi-cloud.js +++ b/js/vapi-cloud.js @@ -26,6 +26,9 @@ /******************************************************************************/ (function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + Cu.import('resource://gre/modules/Services.jsm'); + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js vAPI.cloud = (function () { diff --git a/js/vapi-contextmenu.js b/js/vapi-contextmenu.js index 3cdfe20..34c4263 100644 --- a/js/vapi-contextmenu.js +++ b/js/vapi-contextmenu.js @@ -26,6 +26,9 @@ /******************************************************************************/ (function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + Cu.import('resource://gre/modules/Services.jsm'); + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js vAPI.contextMenu = { diff --git a/js/vapi-cookies.js b/js/vapi-cookies.js index 9211c48..1f19c49 100644 --- a/js/vapi-cookies.js +++ b/js/vapi-cookies.js @@ -26,6 +26,9 @@ /******************************************************************************/ (function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + Cu.import('resource://gre/modules/Services.jsm'); + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js vAPI.cookies = {}; diff --git a/js/vapi-core.js b/js/vapi-core.js index 3901c1c..c904ff2 100644 --- a/js/vapi-core.js +++ b/js/vapi-core.js @@ -23,12 +23,12 @@ 'use strict'; -const {classes: Cc, interfaces: Ci, utils: Cu} = Components; -Cu.import('resource://gre/modules/Services.jsm'); - /******************************************************************************/ (function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + Cu.import('resource://gre/modules/Services.jsm'); + let vAPI = self.vAPI = self.vAPI || {}; vAPI.modernFirefox = diff --git a/js/vapi-messaging.js b/js/vapi-messaging.js index 0df2362..170e85d 100644 --- a/js/vapi-messaging.js +++ b/js/vapi-messaging.js @@ -26,6 +26,8 @@ /******************************************************************************/ (function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + Cu.import('resource://gre/modules/Services.jsm'); Cu.import('chrome://ematrix/content/CallbackWrapper.jsm'); let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js diff --git a/js/vapi-net.js b/js/vapi-net.js index 6f7e885..25de1cb 100644 --- a/js/vapi-net.js +++ b/js/vapi-net.js @@ -26,6 +26,9 @@ /******************************************************************************/ (function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + Cu.import('resource://gre/modules/Services.jsm'); + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js vAPI.net = {}; diff --git a/js/vapi-storage.js b/js/vapi-storage.js index 6dbb955..6a6cb3b 100644 --- a/js/vapi-storage.js +++ b/js/vapi-storage.js @@ -26,6 +26,9 @@ /******************************************************************************/ (function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + Cu.import('resource://gre/modules/Services.jsm'); + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js // API matches that of chrome.storage.local: diff --git a/js/vapi-tabs.js b/js/vapi-tabs.js index 6e953f2..54b09d0 100644 --- a/js/vapi-tabs.js +++ b/js/vapi-tabs.js @@ -26,6 +26,9 @@ /******************************************************************************/ (function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + Cu.import('resource://gre/modules/Services.jsm'); + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js vAPI.tabs = {}; diff --git a/js/vapi-window.js b/js/vapi-window.js index 9cb245d..07fb80c 100644 --- a/js/vapi-window.js +++ b/js/vapi-window.js @@ -26,6 +26,9 @@ /******************************************************************************/ (function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + Cu.import('resource://gre/modules/Services.jsm'); + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-core.js vAPI.window = (function () { -- cgit v1.2.3 From 29943a00d915abd025dc93f7f52b3b03aabf149f Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Thu, 4 Jul 2019 18:20:35 +0200 Subject: Revert "Remove one more importing" This reverts commit d8950421332a0d5b28cba6df38fc4fe85ff26d9e. Followup to the previous reverting. --- js/vapi-common.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'js') diff --git a/js/vapi-common.js b/js/vapi-common.js index 2b4158a..1bc20bc 100644 --- a/js/vapi-common.js +++ b/js/vapi-common.js @@ -33,6 +33,8 @@ /******************************************************************************/ +Components.utils.import('resource://gre/modules/Services.jsm'); + // https://bugs.chromium.org/p/project-zero/issues/detail?id=1225&desc=6#c10 // eMatrix: does this apply to us? // In the meantime, let's remove the useless `eMatrix' property from vAPI -- cgit v1.2.3 From 6908a6c89f9f44380faa1831ec13cff4042b671a Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Thu, 4 Jul 2019 18:41:18 +0200 Subject: Make vAPI definitely global At least for background.html, it can be defined once at the start of vapi-core and then populated. --- js/vapi-background.js | 2 -- js/vapi-browser.js | 2 -- js/vapi-client.js | 2 +- js/vapi-cloud.js | 2 -- js/vapi-common.js | 2 +- js/vapi-contextmenu.js | 2 -- js/vapi-cookies.js | 2 -- js/vapi-core.js | 8 ++++---- js/vapi-messaging.js | 2 -- js/vapi-net.js | 2 -- js/vapi-storage.js | 2 -- js/vapi-tabs.js | 2 -- js/vapi-window.js | 2 -- 13 files changed, 6 insertions(+), 26 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index 067707c..ed70fe2 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -35,8 +35,6 @@ Cu.import('resource://gre/modules/Services.jsm'); Cu.import('chrome://ematrix/content/HttpRequestHeaders.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-core.js - // Icon-related stuff vAPI.setIcon = function (tabId, iconId, badge) { // If badge is undefined, then setIcon was called from the diff --git a/js/vapi-browser.js b/js/vapi-browser.js index 07d954b..6b8a846 100644 --- a/js/vapi-browser.js +++ b/js/vapi-browser.js @@ -29,8 +29,6 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js - vAPI.browser = {}; vAPI.browser.getTabBrowser = function (win) { diff --git a/js/vapi-client.js b/js/vapi-client.js index 6f46e96..337762d 100644 --- a/js/vapi-client.js +++ b/js/vapi-client.js @@ -41,7 +41,7 @@ if (self.vAPI === undefined) { self.vAPI = {}; } -var vAPI = self.vAPI; +var vAPI = self.vAPI; // This is also used when vapi-core is not available vAPI.sessionId = String.fromCharCode(Date.now() % 25 + 97) + Math.random().toString(36).slice(2); diff --git a/js/vapi-cloud.js b/js/vapi-cloud.js index b3d1b80..f7c47a2 100644 --- a/js/vapi-cloud.js +++ b/js/vapi-cloud.js @@ -29,8 +29,6 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js - vAPI.cloud = (function () { let extensionBranchPath = 'extensions.' + location.host; let cloudBranchPath = extensionBranchPath + '.cloudStorage'; diff --git a/js/vapi-common.js b/js/vapi-common.js index 1bc20bc..9a322ec 100644 --- a/js/vapi-common.js +++ b/js/vapi-common.js @@ -42,7 +42,7 @@ if (self.vAPI === undefined) { self.vAPI = {}; } -var vAPI = self.vAPI; +var vAPI = self.vAPI; // This is used also when vapi-core is not available /******************************************************************************/ diff --git a/js/vapi-contextmenu.js b/js/vapi-contextmenu.js index 34c4263..5815d78 100644 --- a/js/vapi-contextmenu.js +++ b/js/vapi-contextmenu.js @@ -29,8 +29,6 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js - vAPI.contextMenu = { contextMap: { frame: 'inFrame', diff --git a/js/vapi-cookies.js b/js/vapi-cookies.js index 1f19c49..21f5d2e 100644 --- a/js/vapi-cookies.js +++ b/js/vapi-cookies.js @@ -29,8 +29,6 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js - vAPI.cookies = {}; vAPI.cookies.CookieEntry = function (ffCookie) { diff --git a/js/vapi-core.js b/js/vapi-core.js index c904ff2..e2afa4f 100644 --- a/js/vapi-core.js +++ b/js/vapi-core.js @@ -23,14 +23,14 @@ 'use strict'; +var vAPI = {}; + /******************************************************************************/ -(function () { +(function (self) { const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI = self.vAPI || {}; - vAPI.modernFirefox = Services.appinfo.ID === '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}' && Services.vc.compare(Services.appinfo.version, '44') > 0; @@ -133,4 +133,4 @@ vAPI.lastError = function () { return null; }; -})(); +})(this); diff --git a/js/vapi-messaging.js b/js/vapi-messaging.js index 170e85d..b320a27 100644 --- a/js/vapi-messaging.js +++ b/js/vapi-messaging.js @@ -29,8 +29,6 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import('resource://gre/modules/Services.jsm'); Cu.import('chrome://ematrix/content/CallbackWrapper.jsm'); - - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js vAPI.messaging = { get globalMessageManager() { diff --git a/js/vapi-net.js b/js/vapi-net.js index 25de1cb..ee52945 100644 --- a/js/vapi-net.js +++ b/js/vapi-net.js @@ -29,8 +29,6 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js - vAPI.net = {}; vAPI.net.registerListeners = function () { diff --git a/js/vapi-storage.js b/js/vapi-storage.js index 6a6cb3b..ce4ab72 100644 --- a/js/vapi-storage.js +++ b/js/vapi-storage.js @@ -29,8 +29,6 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js - // API matches that of chrome.storage.local: // https://developer.chrome.com/extensions/storage vAPI.storage = (function () { diff --git a/js/vapi-tabs.js b/js/vapi-tabs.js index 54b09d0..d36800a 100644 --- a/js/vapi-tabs.js +++ b/js/vapi-tabs.js @@ -29,8 +29,6 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js - vAPI.tabs = {}; vAPI.tabs.registerListeners = function() { diff --git a/js/vapi-window.js b/js/vapi-window.js index 07fb80c..e7e5e50 100644 --- a/js/vapi-window.js +++ b/js/vapi-window.js @@ -29,8 +29,6 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import('resource://gre/modules/Services.jsm'); - let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-core.js - vAPI.window = (function () { let windowToIdMap = new Map(); let windowIdGenerator = 1; -- cgit v1.2.3 From 4c1c37a54318d91d572180b45bda9db6d9de0389 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Fri, 5 Jul 2019 13:34:01 +0200 Subject: Style changes --- js/vapi-common.js | 286 +++++++++++++++++++++++++----------------------------- 1 file changed, 133 insertions(+), 153 deletions(-) (limited to 'js') diff --git a/js/vapi-common.js b/js/vapi-common.js index 9a322ec..14b1086 100644 --- a/js/vapi-common.js +++ b/js/vapi-common.js @@ -21,172 +21,152 @@ uMatrix Home: https://github.com/gorhill/uMatrix */ -/* global sendAsyncMessage */ - // For background page or non-background pages 'use strict'; -/******************************************************************************/ - -(function(self) { - -/******************************************************************************/ - -Components.utils.import('resource://gre/modules/Services.jsm'); - -// https://bugs.chromium.org/p/project-zero/issues/detail?id=1225&desc=6#c10 -// eMatrix: does this apply to us? -// In the meantime, let's remove the useless `eMatrix' property from vAPI -if (self.vAPI === undefined) { - self.vAPI = {}; -} - -var vAPI = self.vAPI; // This is used also when vapi-core is not available - -/******************************************************************************/ - -vAPI.setTimeout = vAPI.setTimeout || function(callback, delay, extra) { - return setTimeout(function(a) { callback(a); }, delay, extra); -}; - -/******************************************************************************/ - -// http://www.w3.org/International/questions/qa-scripts#directions - -var setScriptDirection = function(language) { - document.body.setAttribute( - 'dir', - ['ar', 'he', 'fa', 'ps', 'ur'].indexOf(language) !== -1 ? 'rtl' : 'ltr' - ); -}; +(function (self) { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + Cu.import('resource://gre/modules/Services.jsm'); -/******************************************************************************/ - -vAPI.download = function(details) { - if ( !details.url ) { - return; + // https://bugs.chromium.org/p/project-zero/issues/detail?id=1225&desc=6#c10 + // eMatrix: does this apply to us? + // In the meantime, let's remove the useless `eMatrix' property from vAPI + if (self.vAPI === undefined) { + self.vAPI = {}; } - var a = document.createElement('a'); - a.href = details.url; - a.setAttribute('download', details.filename || ''); - a.dispatchEvent(new MouseEvent('click')); -}; - -/******************************************************************************/ - -vAPI.insertHTML = (function() { - const parser = Components.classes['@mozilla.org/parserutils;1'] - .getService(Components.interfaces.nsIParserUtils); - - // https://github.com/gorhill/uBlock/issues/845 - // Apparently dashboard pages execute with `about:blank` principal. - - return function(node, html) { - while ( node.firstChild ) { - node.removeChild(node.firstChild); - } - - node.appendChild(parser.parseFragment( - html, - parser.SanitizerAllowStyle, - false, - Services.io.newURI('about:blank', null, null), - document.documentElement - )); - }; -})(); - -/******************************************************************************/ + var vAPI = self.vAPI; // This is used also when vapi-core is not available -vAPI.getURL = function(path) { - return 'chrome://' + location.host + '/content/' + path.replace(/^\/+/, ''); -}; + vAPI.setTimeout = vAPI.setTimeout || function (callback, delay, extra) { + return setTimeout(function (a) { + callback(a); + }, delay, extra); + }; -/******************************************************************************/ + // http://www.w3.org/International/questions/qa-scripts#directions + var setScriptDirection = function(language) { + let dir = + ['ar', 'he', 'fa', 'ps', 'ur'].indexOf(language) !== -1 + ? 'rtl' + : 'ltr'; + + document.body.setAttribute('dir', dir); + }; -vAPI.i18n = (function() { - var stringBundle = Services.strings.createBundle( - 'chrome://' + location.host + '/locale/messages.properties' - ); + vAPI.download = function (details) { + if (!details.url) { + return; + } - return function(s) { - try { - return stringBundle.GetStringFromName(s); - } catch (ex) { - return ''; - } + var a = document.createElement('a'); + a.href = details.url; + a.setAttribute('download', details.filename || ''); + a.dispatchEvent(new MouseEvent('click')); }; -})(); - -setScriptDirection(navigator.language); - -/******************************************************************************/ - -vAPI.closePopup = function() { - sendAsyncMessage(location.host + ':closePopup'); -}; - -/******************************************************************************/ - -// A localStorage-like object which should be accessible from the -// background page or auxiliary pages. -// This storage is optional, but it is nice to have, for a more polished user -// experience. - -vAPI.localStorage = { - pbName: '', - pb: null, - str: Components.classes['@mozilla.org/supports-string;1'] - .createInstance(Components.interfaces.nsISupportsString), - init: function(pbName) { - this.pbName = pbName; - this.pb = Services.prefs.getBranch(pbName); - }, - getItem: function(key) { - try { - return this.pb.getComplexValue( - key, - Components.interfaces.nsISupportsString - ).data; - } catch (ex) { - return null; - } - }, - setItem: function(key, value) { - this.str.data = value; - this.pb.setComplexValue( - key, - Components.interfaces.nsISupportsString, - this.str - ); - }, - getBool: function(key) { - try { - return this.pb.getBoolPref(key); - } catch (ex) { - return null; - } - }, - setBool: function(key, value) { - this.pb.setBoolPref(key, value); - }, - setDefaultBool: function(key, defaultValue) { - Services.prefs.getDefaultBranch(this.pbName).setBoolPref(key, defaultValue); - }, - removeItem: function(key) { - this.pb.clearUserPref(key); - }, - clear: function() { - this.pb.deleteBranch(''); - } -}; -vAPI.localStorage.init('extensions.' + location.host + '.'); + vAPI.insertHTML = (function () { + const parser = Cc['@mozilla.org/parserutils;1'] + .getService(Ci.nsIParserUtils); + + // https://github.com/gorhill/uBlock/issues/845 + // Apparently dashboard pages execute with `about:blank` principal. + + return function (node, html) { + while (node.firstChild) { + node.removeChild(node.firstChild); + } + + let parsed = + parser.parseFragment(html, + parser.SanitizerAllowStyle, + false, + Services.io.newURI('about:blank', + null, null), + document.documentElement); + + node.appendChild(parsed); + }; + })(); + + vAPI.getURL = function (path) { + return 'chrome://' + + location.host + + '/content/' + + path.replace(/^\/+/, ''); + }; + + vAPI.i18n = (function () { + var stringBundle = + Services.strings.createBundle('chrome://' + + location.host + + '/locale/messages.properties'); + + return function (s) { + try { + return stringBundle.GetStringFromName(s); + } catch (ex) { + return ''; + } + }; + })(); + + setScriptDirection(navigator.language); + + vAPI.closePopup = function() { + sendAsyncMessage(location.host + ':closePopup'); + }; -/******************************************************************************/ + // A localStorage-like object which should be accessible from the + // background page or auxiliary pages. + // This storage is optional, but it is nice to have, for a more polished user + // experience. + vAPI.localStorage = { + pbName: '', + pb: null, + str: Cc['@mozilla.org/supports-string;1'] + .createInstance(Ci.nsISupportsString), + + init: function (pbName) { + this.pbName = pbName; + this.pb = Services.prefs.getBranch(pbName); + }, + getItem: function (key) { + try { + return this.pb + .getComplexValue(key, + Ci.nsISupportsString).data; + } catch (ex) { + return null; + } + }, + setItem: function (key, value) { + this.str.data = value; + this.pb.setComplexValue(key, + Ci.nsISupportsString, + this.str); + }, + getBool: function (key) { + try { + return this.pb.getBoolPref(key); + } catch (ex) { + return null; + } + }, + setBool: function (key, value) { + this.pb.setBoolPref(key, value); + }, + setDefaultBool: function (key, defaultValue) { + Services.prefs.getDefaultBranch(this.pbName) + .setBoolPref(key, defaultValue); + }, + removeItem: function (key) { + this.pb.clearUserPref(key); + }, + clear: function () { + this.pb.deleteBranch(''); + } + }; + vAPI.localStorage.init('extensions.' + location.host + '.'); })(this); - -/******************************************************************************/ -- cgit v1.2.3 From e1c3d731eb012a802ac94b849f7764aba4108366 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Fri, 5 Jul 2019 13:42:18 +0200 Subject: Style changes part 2 --- js/vapi-client.js | 351 +++++++++++++++++++++++++----------------------------- 1 file changed, 164 insertions(+), 187 deletions(-) (limited to 'js') diff --git a/js/vapi-client.js b/js/vapi-client.js index 337762d..8ef7805 100644 --- a/js/vapi-client.js +++ b/js/vapi-client.js @@ -21,210 +21,187 @@ uMatrix Home: https://github.com/gorhill/uMatrix */ -/* jshint esnext: true */ -/* global addMessageListener, removeMessageListener, sendAsyncMessage */ - // For non background pages 'use strict'; -/******************************************************************************/ - -(function(self) { - -/******************************************************************************/ - -// https://bugs.chromium.org/p/project-zero/issues/detail?id=1225&desc=6#c10 -// eMatrix: does this apply to us? -// In the meantime, let's remove the useless `eMatrix' property from vAPI -if (self.vAPI === undefined) { - self.vAPI = {}; -} - -var vAPI = self.vAPI; // This is also used when vapi-core is not available - -vAPI.sessionId = String.fromCharCode(Date.now() % 25 + 97) + - Math.random().toString(36).slice(2); - -/******************************************************************************/ - -vAPI.setTimeout = vAPI.setTimeout || function(callback, delay) { - return setTimeout(function() { callback(); }, delay); -}; +(function (self) { + // https://bugs.chromium.org/p/project-zero/issues/detail?id=1225&desc=6#c10 + // eMatrix: does this apply to us? + // In the meantime, let's remove the useless `eMatrix' property from vAPI + if (self.vAPI === undefined) { + self.vAPI = {}; + } -/******************************************************************************/ + let vAPI = self.vAPI; // This is also used when vapi-core is not available -vAPI.shutdown = (function() { - var jobs = []; + vAPI.sessionId = String.fromCharCode(Date.now() % 25 + 97) + + Math.random().toString(36).slice(2); - var add = function(job) { - jobs.push(job); + vAPI.setTimeout = vAPI.setTimeout || function (callback, delay) { + return setTimeout(function () { + callback(); + }, delay); }; - var exec = function() { - //console.debug('Shutting down...'); - var job; - while ( (job = jobs.pop()) ) { - job(); - } - }; + vAPI.shutdown = (function () { + var jobs = []; - return { - add: add, - exec: exec - }; -})(); - -/******************************************************************************/ - -vAPI.messaging = { - listeners: new Set(), - pending: new Map(), - requestId: 1, - connected: false, - - start: function() { - this.addListener(this.builtinListener); - if ( this.toggleListenerCallback === null ) { - this.toggleListenerCallback = this.toggleListener.bind(this); - } - window.addEventListener('pagehide', this.toggleListenerCallback, true); - window.addEventListener('pageshow', this.toggleListenerCallback, true); - }, - - shutdown: function() { - if ( this.toggleListenerCallback !== null ) { - window.removeEventListener('pagehide', this.toggleListenerCallback, true); - window.removeEventListener('pageshow', this.toggleListenerCallback, true); - } - this.removeAllListeners(); - //service pending callbacks - var pending = this.pending; - this.pending.clear(); - for ( var callback of pending.values() ) { - if ( typeof callback === 'function' ) { - callback(null); - } - } - }, + var add = function (job) { + jobs.push(job); + }; - connect: function() { - if ( !this.connected ) { - if ( this.messageListenerCallback === null ) { - this.messageListenerCallback = this.messageListener.bind(this); + var exec = function () { + //console.debug('Shutting down...'); + var job; + while ((job = jobs.pop())) { + job(); } - addMessageListener(this.messageListenerCallback); - this.connected = true; - } - }, - - disconnect: function() { - if ( this.connected ) { - removeMessageListener(); - this.connected = false; - } - }, - - messageListener: function(msg) { - var details = JSON.parse(msg); - if ( !details ) { - return; - } - - if ( details.broadcast ) { - this.sendToListeners(details.msg); - return; - } - - if ( details.requestId ) { - var listener = this.pending.get(details.requestId); - if ( listener !== undefined ) { - this.pending.delete(details.requestId); - listener(details.msg); - return; + }; + + return { + add: add, + exec: exec + }; + })(); + + vAPI.messaging = { + listeners: new Set(), + pending: new Map(), + requestId: 1, + connected: false, + messageListenerCallback: null, + toggleListenerCallback: null, + + start: function () { + this.addListener(this.builtinListener); + if (this.toggleListenerCallback === null) { + this.toggleListenerCallback = this.toggleListener.bind(this); } - } - }, - messageListenerCallback: null, - - builtinListener: function(msg) { - if ( typeof msg.cmd === 'string' && msg.cmd === 'injectScript' ) { - var details = msg.details; - if ( !details.allFrames && window !== window.top ) { - return; + + window.addEventListener('pagehide', + this.toggleListenerCallback, true); + window.addEventListener('pageshow', + this.toggleListenerCallback, true); + }, + shutdown: function () { + if (this.toggleListenerCallback !== null) { + window.removeEventListener('pagehide', + this.toggleListenerCallback, true); + window.removeEventListener('pageshow', + this.toggleListenerCallback, true); + } + this.removeAllListeners(); + + //service pending callbacks + var pending = this.pending; + this.pending.clear(); + for (let callback of pending.values()) { + if (typeof callback === 'function') { + callback(null); + } + } + }, + connect: function () { + if (!this.connected) { + if (this.messageListenerCallback === null) { + this.messageListenerCallback = + this.messageListener.bind(this); + } + addMessageListener(this.messageListenerCallback); + this.connected = true; + } + }, + disconnect: function () { + if (this.connected) { + removeMessageListener(); + this.connected = false; + } + }, + messageListener: function (msg) { + let details = JSON.parse(msg); + if (!details) { + return; } - self.injectScript(details.file); - } - }, - - send: function(channelName, message, callback) { - this.connect() - - message = { - channelName: self._sandboxId_ + '|' + channelName, - msg: message - }; - - if ( callback ) { - message.requestId = this.requestId++; - this.pending.set(message.requestId, callback); - } - - sendAsyncMessage('ematrix:background', message); - }, - - toggleListener: function({type, persisted}) { - if ( type === 'pagehide' && !persisted ) { - vAPI.shutdown.exec(); - this.shutdown(); - return; - } - - if ( type === 'pagehide' ) { - this.disconnect(); - } else /* if ( type === 'pageshow' ) */ { - this.connect(); - } - }, - toggleListenerCallback: null, - - sendToListeners: function(msg) { - for ( var listener of this.listeners ) { - listener(msg); - } - }, - - addListener: function(listener) { - this.listeners.add(listener); - this.connect() - }, - - removeListener: function(listener) { - this.listeners.delete(listener); - }, - - removeAllListeners: function() { - this.disconnect(); - this.listeners.clear();; - } -}; -vAPI.messaging.start() + if (details.broadcast) { + this.sendToListeners(details.msg); + return; + } -/******************************************************************************/ + if (details.requestId) { + let listener = this.pending.get(details.requestId); + if (listener !== undefined) { + this.pending.delete(details.requestId); + listener(details.msg); + return; + } + } + }, + builtinListener: function (msg) { + if (typeof msg.cmd === 'string' && msg.cmd === 'injectScript') { + let details = msg.details; + if (!details.allFrames && window !== window.top) { + return; + } + self.injectScript(details.file); + } + }, + send: function (channelName, message, callback) { + this.connect() + + message = { + channelName: self._sandboxId_ + '|' + channelName, + msg: message + }; + + if (callback) { + message.requestId = this.requestId++; + this.pending.set(message.requestId, callback); + } -// No need to have vAPI client linger around after shutdown if -// we are not a top window (because element picker can still -// be injected in top window). -// Needs more investigating -/*if ( window !== window.top ) { - vAPI.shutdown.add(function() { - vAPI = null; - }); -}*/ + sendAsyncMessage('ematrix:background', message); + }, + toggleListener: function ({type, persisted}) { + if (type === 'pagehide' && !persisted) { + vAPI.shutdown.exec(); + this.shutdown(); + return; + } -/******************************************************************************/ + if (type === 'pagehide') { + this.disconnect(); + } else { + this.connect(); + } + }, + sendToListeners: function (msg) { + for (let listener of this.listeners) { + listener(msg); + } + }, + addListener: function (listener) { + this.listeners.add(listener); + this.connect() + }, + removeListener: function (listener) { + this.listeners.delete(listener); + }, + removeAllListeners: function () { + this.disconnect(); + this.listeners.clear(); + } + }; + vAPI.messaging.start() + + // No need to have vAPI client linger around after shutdown if + // we are not a top window (because element picker can still + // be injected in top window). + // Needs more investigating + // if ( window !== window.top ) { + // vAPI.shutdown.add(function() { + // vAPI = null; + // }); + // } })(this); - -/******************************************************************************/ -- cgit v1.2.3 From 1418306606fbf0c06cf1c12e027cf159ce478e02 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Fri, 5 Jul 2019 13:42:53 +0200 Subject: Minor style fixes --- js/vapi-common.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'js') diff --git a/js/vapi-common.js b/js/vapi-common.js index 14b1086..46225b1 100644 --- a/js/vapi-common.js +++ b/js/vapi-common.js @@ -36,7 +36,7 @@ self.vAPI = {}; } - var vAPI = self.vAPI; // This is used also when vapi-core is not available + let vAPI = self.vAPI; // This is used also when vapi-core is not available vAPI.setTimeout = vAPI.setTimeout || function (callback, delay, extra) { return setTimeout(function (a) { @@ -45,7 +45,7 @@ }; // http://www.w3.org/International/questions/qa-scripts#directions - var setScriptDirection = function(language) { + let setScriptDirection = function(language) { let dir = ['ar', 'he', 'fa', 'ps', 'ur'].indexOf(language) !== -1 ? 'rtl' @@ -59,7 +59,7 @@ return; } - var a = document.createElement('a'); + let a = document.createElement('a'); a.href = details.url; a.setAttribute('download', details.filename || ''); a.dispatchEvent(new MouseEvent('click')); @@ -97,7 +97,7 @@ }; vAPI.i18n = (function () { - var stringBundle = + let stringBundle = Services.strings.createBundle('chrome://' + location.host + '/locale/messages.properties'); -- cgit v1.2.3 From 34a61bb50143a88c885542274d25f2a7e35218c4 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Thu, 11 Jul 2019 16:38:23 +0200 Subject: Change child-src to frame-src --- js/contentscript-start.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'js') diff --git a/js/contentscript-start.js b/js/contentscript-start.js index 710504e..dc93c1d 100644 --- a/js/contentscript-start.js +++ b/js/contentscript-start.js @@ -34,7 +34,7 @@ vAPI.selfWorkerSrcReported = vAPI.selfWorkerSrcReported || false; - var reGoodWorkerSrc = /(?:child|worker)-src[^;,]+?'none'/; + var reGoodWorkerSrc = /(?:frame|worker)-src[^;,]+?'none'/; var handler = function(ev) { if ( @@ -48,7 +48,7 @@ // 'effectiveDirective' property. if ( ev.effectiveDirective.startsWith('worker-src') === false && - ev.effectiveDirective.startsWith('child-src') === false + ev.effectiveDirective.startsWith('frame-src') === false ) { return false; } -- cgit v1.2.3 From 8c05a1711951f3cc9179eec2c086b0e88d42e5f0 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Fri, 19 Jul 2019 15:54:34 +0200 Subject: Make vAPI definitely global for non-background pages too --- js/vapi-client.js | 23 ++++------------------- js/vapi-common.js | 11 ++--------- js/vapi-core.js | 2 -- 3 files changed, 6 insertions(+), 30 deletions(-) (limited to 'js') diff --git a/js/vapi-client.js b/js/vapi-client.js index 8ef7805..e45cc4d 100644 --- a/js/vapi-client.js +++ b/js/vapi-client.js @@ -26,34 +26,19 @@ 'use strict'; (function (self) { - // https://bugs.chromium.org/p/project-zero/issues/detail?id=1225&desc=6#c10 - // eMatrix: does this apply to us? - // In the meantime, let's remove the useless `eMatrix' property from vAPI - if (self.vAPI === undefined) { - self.vAPI = {}; - } - - let vAPI = self.vAPI; // This is also used when vapi-core is not available - vAPI.sessionId = String.fromCharCode(Date.now() % 25 + 97) + Math.random().toString(36).slice(2); - vAPI.setTimeout = vAPI.setTimeout || function (callback, delay) { - return setTimeout(function () { - callback(); - }, delay); - }; - vAPI.shutdown = (function () { - var jobs = []; + let jobs = []; - var add = function (job) { + let add = function (job) { jobs.push(job); }; - var exec = function () { + let exec = function () { //console.debug('Shutting down...'); - var job; + let job; while ((job = jobs.pop())) { job(); } diff --git a/js/vapi-common.js b/js/vapi-common.js index 46225b1..473ad89 100644 --- a/js/vapi-common.js +++ b/js/vapi-common.js @@ -25,19 +25,12 @@ 'use strict'; +let vAPI = {}; + (function (self) { const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import('resource://gre/modules/Services.jsm'); - // https://bugs.chromium.org/p/project-zero/issues/detail?id=1225&desc=6#c10 - // eMatrix: does this apply to us? - // In the meantime, let's remove the useless `eMatrix' property from vAPI - if (self.vAPI === undefined) { - self.vAPI = {}; - } - - let vAPI = self.vAPI; // This is used also when vapi-core is not available - vAPI.setTimeout = vAPI.setTimeout || function (callback, delay, extra) { return setTimeout(function (a) { callback(a); diff --git a/js/vapi-core.js b/js/vapi-core.js index e2afa4f..620faf2 100644 --- a/js/vapi-core.js +++ b/js/vapi-core.js @@ -23,8 +23,6 @@ 'use strict'; -var vAPI = {}; - /******************************************************************************/ (function (self) { -- cgit v1.2.3 From acd097e4733c106a15815c90e21c00d0c545e042 Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Fri, 19 Jul 2019 16:00:08 +0200 Subject: Make components and Services.jsm global Once again, but this time it works. --- js/vapi-background.js | 2 -- js/vapi-browser.js | 3 --- js/vapi-cloud.js | 3 --- js/vapi-common.js | 6 +++--- js/vapi-contextmenu.js | 3 --- js/vapi-cookies.js | 3 --- js/vapi-core.js | 3 --- js/vapi-messaging.js | 2 -- js/vapi-net.js | 3 --- js/vapi-storage.js | 3 --- js/vapi-tabs.js | 3 --- js/vapi-window.js | 3 --- 12 files changed, 3 insertions(+), 34 deletions(-) (limited to 'js') diff --git a/js/vapi-background.js b/js/vapi-background.js index ed70fe2..6100c18 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -31,8 +31,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); Cu.import('chrome://ematrix/content/HttpRequestHeaders.jsm'); // Icon-related stuff diff --git a/js/vapi-browser.js b/js/vapi-browser.js index 6b8a846..b3c9a5f 100644 --- a/js/vapi-browser.js +++ b/js/vapi-browser.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - vAPI.browser = {}; vAPI.browser.getTabBrowser = function (win) { diff --git a/js/vapi-cloud.js b/js/vapi-cloud.js index f7c47a2..90b077e 100644 --- a/js/vapi-cloud.js +++ b/js/vapi-cloud.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - vAPI.cloud = (function () { let extensionBranchPath = 'extensions.' + location.host; let cloudBranchPath = extensionBranchPath + '.cloudStorage'; diff --git a/js/vapi-common.js b/js/vapi-common.js index 473ad89..b7c5635 100644 --- a/js/vapi-common.js +++ b/js/vapi-common.js @@ -27,10 +27,10 @@ let vAPI = {}; -(function (self) { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; +Cu.import('resource://gre/modules/Services.jsm'); +(function (self) { vAPI.setTimeout = vAPI.setTimeout || function (callback, delay, extra) { return setTimeout(function (a) { callback(a); diff --git a/js/vapi-contextmenu.js b/js/vapi-contextmenu.js index 5815d78..b0f8694 100644 --- a/js/vapi-contextmenu.js +++ b/js/vapi-contextmenu.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - vAPI.contextMenu = { contextMap: { frame: 'inFrame', diff --git a/js/vapi-cookies.js b/js/vapi-cookies.js index 21f5d2e..ff36d97 100644 --- a/js/vapi-cookies.js +++ b/js/vapi-cookies.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - vAPI.cookies = {}; vAPI.cookies.CookieEntry = function (ffCookie) { diff --git a/js/vapi-core.js b/js/vapi-core.js index 620faf2..0c907f7 100644 --- a/js/vapi-core.js +++ b/js/vapi-core.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function (self) { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - vAPI.modernFirefox = Services.appinfo.ID === '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}' && Services.vc.compare(Services.appinfo.version, '44') > 0; diff --git a/js/vapi-messaging.js b/js/vapi-messaging.js index b320a27..d0a3333 100644 --- a/js/vapi-messaging.js +++ b/js/vapi-messaging.js @@ -26,8 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); Cu.import('chrome://ematrix/content/CallbackWrapper.jsm'); vAPI.messaging = { diff --git a/js/vapi-net.js b/js/vapi-net.js index ee52945..7accbc0 100644 --- a/js/vapi-net.js +++ b/js/vapi-net.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - vAPI.net = {}; vAPI.net.registerListeners = function () { diff --git a/js/vapi-storage.js b/js/vapi-storage.js index ce4ab72..7aa873c 100644 --- a/js/vapi-storage.js +++ b/js/vapi-storage.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - // API matches that of chrome.storage.local: // https://developer.chrome.com/extensions/storage vAPI.storage = (function () { diff --git a/js/vapi-tabs.js b/js/vapi-tabs.js index d36800a..d88d184 100644 --- a/js/vapi-tabs.js +++ b/js/vapi-tabs.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - vAPI.tabs = {}; vAPI.tabs.registerListeners = function() { diff --git a/js/vapi-window.js b/js/vapi-window.js index e7e5e50..e01dc7a 100644 --- a/js/vapi-window.js +++ b/js/vapi-window.js @@ -26,9 +26,6 @@ /******************************************************************************/ (function () { - const {classes: Cc, interfaces: Ci, utils: Cu} = Components; - Cu.import('resource://gre/modules/Services.jsm'); - vAPI.window = (function () { let windowToIdMap = new Map(); let windowIdGenerator = 1; -- cgit v1.2.3