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/vapi-background.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/vapi-background.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/vapi-background.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'js/vapi-background.js') 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 +------------------------------------------------- 1 file changed, 8 insertions(+), 704 deletions(-) (limited to 'js/vapi-background.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 -- 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 ++++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 24 deletions(-) (limited to 'js/vapi-background.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 { -- 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 +++++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 19 deletions(-) (limited to 'js/vapi-background.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'); } -- 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/vapi-background.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 - 1 file changed, 1 deletion(-) (limited to 'js/vapi-background.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; -- 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/vapi-background.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/vapi-background.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 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 ++------------------------------------------------ 1 file changed, 7 insertions(+), 238 deletions(-) (limited to 'js/vapi-background.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); } -- 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 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/vapi-background.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}' -- 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 ------------------------------- 1 file changed, 31 deletions(-) (limited to 'js/vapi-background.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 -- 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/vapi-background.js | 212 -------------------------------------------------- 1 file changed, 212 deletions(-) (limited to 'js/vapi-background.js') 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 -- 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 -------------------------------------------------- 1 file changed, 302 deletions(-) (limited to 'js/vapi-background.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 -- 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 -------------------------------------------------- 1 file changed, 108 deletions(-) (limited to 'js/vapi-background.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 -- 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 --------------------------------------------------- 1 file changed, 66 deletions(-) (limited to 'js/vapi-background.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) { -- 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/vapi-background.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 ----------------------------------------- 1 file changed, 41 deletions(-) (limited to 'js/vapi-background.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', -- 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 -------------------------------------------------- 1 file changed, 101 deletions(-) (limited to 'js/vapi-background.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 - }); - } - }; })(); -- 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 -------------------------------------------------- 1 file changed, 184 deletions(-) (limited to 'js/vapi-background.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'; -- 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 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'js/vapi-background.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', -- 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 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'js/vapi-background.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 -- 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 -------------------------------------------------- 1 file changed, 128 deletions(-) (limited to 'js/vapi-background.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 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 -- 1 file changed, 2 deletions(-) (limited to 'js/vapi-background.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 -- 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 ++ 1 file changed, 2 insertions(+) (limited to 'js/vapi-background.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 -- 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 -- 1 file changed, 2 deletions(-) (limited to 'js/vapi-background.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 -- 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 -- 1 file changed, 2 deletions(-) (limited to 'js/vapi-background.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 -- cgit v1.2.3