diff --git a/.jshintrc b/.jshintrc index 940e5edf9..ba4d941e0 100644 --- a/.jshintrc +++ b/.jshintrc @@ -11,6 +11,7 @@ "safari": false, "self": false, "vAPI": false, + "webext": false, "µBlock": false }, "laxbreak": true, diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index a9234ee70..c3a4d7aec 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -43,15 +43,6 @@ vAPI.lastError = function() { return chrome.runtime.lastError; }; -vAPI.apiIsPromisified = (( ) => { - try { - return browser.storage.local.get('_') instanceof Promise; - } - catch(ex) { - } - return false; -})(); - // https://github.com/gorhill/uBlock/issues/875 // https://code.google.com/p/chromium/issues/detail?id=410868#c8 // Must not leave `lastError` unchecked. @@ -116,83 +107,7 @@ vAPI.app = { /******************************************************************************/ /******************************************************************************/ -vAPI.storage = (( ) => { - if ( vAPI.apiIsPromisified ) { - return browser.storage.local; - } - return { - clear: function(callback) { - if ( callback !== undefined ) { - return browser.storage.local.clear(...arguments); - } - return new Promise((resolve, reject) => { - browser.storage.local.clear(( ) => { - const lastError = browser.runtime.lastError; - if ( lastError instanceof Object ) { - return reject(lastError); - } - resolve(); - }); - }); - }, - get: function(keys, callback) { - if ( callback !== undefined ) { - return browser.storage.local.get(...arguments); - } - return new Promise((resolve, reject) => { - browser.storage.local.get(keys, result => { - const lastError = browser.runtime.lastError; - if ( lastError instanceof Object ) { - return reject(lastError); - } - resolve(result); - }); - }); - }, - getBytesInUse: function(keys, callback) { - if ( callback !== undefined ) { - return browser.storage.local.getBytesInUse(...arguments); - } - return new Promise((resolve, reject) => { - browser.storage.local.getBytesInUse(keys, result => { - const lastError = browser.runtime.lastError; - if ( lastError instanceof Object ) { - return reject(lastError); - } - resolve(result); - }); - }); - }, - remove: function(keys, callback) { - if ( callback !== undefined ) { - return browser.storage.local.remove(...arguments); - } - return new Promise((resolve, reject) => { - browser.storage.local.remove(keys, ( ) => { - const lastError = browser.runtime.lastError; - if ( lastError instanceof Object ) { - return reject(lastError); - } - resolve(); - }); - }); - }, - set: function(items, callback) { - if ( callback !== undefined ) { - return browser.storage.local.set(...arguments); - } - return new Promise((resolve, reject) => { - browser.storage.local.set(items, ( ) => { - const lastError = browser.runtime.lastError; - if ( lastError instanceof Object ) { - return reject(lastError); - } - resolve(); - }); - }); - }, - }; -})(); +vAPI.storage = webext.storage.local; /******************************************************************************/ /******************************************************************************/ @@ -211,7 +126,7 @@ vAPI.storage = (( ) => { // Do not mess up with existing settings if not assigning them stricter // values. -vAPI.browserSettings = (function() { +vAPI.browserSettings = (( ) => { // Not all platforms support `chrome.privacy`. if ( chrome.privacy instanceof Object === false ) { return; } @@ -384,9 +299,8 @@ vAPI.isBehindTheSceneTabId = function(tabId) { vAPI.unsetTabId = 0; vAPI.noTabId = -1; // definitely not any existing tab -// To remove when tabId-as-integer has been tested enough. - -const toChromiumTabId = function(tabId) { +// To ensure we always use a good tab id +const toTabId = function(tabId) { return typeof tabId === 'number' && isNaN(tabId) === false ? tabId : 0; @@ -436,17 +350,12 @@ vAPI.Tabs = class { // https://github.com/uBlockOrigin/uBlock-issues/issues/151 // https://github.com/uBlockOrigin/uBlock-issues/issues/680#issuecomment-515215220 if ( browser.windows instanceof Object ) { - browser.windows.onFocusChanged.addListener(windowId => { + browser.windows.onFocusChanged.addListener(async windowId => { if ( windowId === browser.windows.WINDOW_ID_NONE ) { return; } - browser.tabs.query({ active: true, windowId }, tabs => { - if ( Array.isArray(tabs) === false ) { return; } - if ( tabs.length === 0 ) { return; } - const tab = tabs[0]; - this.onActivated({ - tabId: tab.id, - windowId: tab.windowId, - }); - }); + const tabs = await vAPI.tabs.query({ active: true, windowId }); + if ( tabs.length === 0 ) { return; } + const tab = tabs[0]; + this.onActivated({ tabId: tab.id, windowId: tab.windowId }); }); } @@ -455,32 +364,33 @@ vAPI.Tabs = class { }); } - get(tabId, callback) { + async get(tabId) { if ( tabId === null ) { - browser.tabs.query( - { active: true, currentWindow: true }, - tabs => { - void browser.runtime.lastError; - callback( - Array.isArray(tabs) && tabs.length !== 0 - ? tabs[0] - : null - ); - } - ); - return; + return this.getCurrent(); } - - tabId = toChromiumTabId(tabId); - if ( tabId === 0 ) { - callback(null); - return; + if ( tabId <= 0 ) { return null; } + let tab; + try { + tab = await webext.tabs.get(tabId); } + catch(reason) { + } + return tab instanceof Object ? tab : null; + } - browser.tabs.get(tabId, function(tab) { - void browser.runtime.lastError; - callback(tab); - }); + async getCurrent() { + const tabs = await this.query({ active: true, currentWindow: true }); + return tabs.length !== 0 ? tabs[0] : null; + } + + async query(queryInfo) { + let tabs; + try { + tabs = await webext.tabs.query(queryInfo); + } + catch(reason) { + } + return Array.isArray(tabs) ? tabs : []; } // Properties of the details object: @@ -491,12 +401,12 @@ vAPI.Tabs = class { // foreground: undefined // - popup: 'popup' => open in a new window - create(url, details) { + async create(url, details) { if ( details.active === undefined ) { details.active = true; } - const subWrapper = ( ) => { + const subWrapper = async ( ) => { const updateDetails = { url: url, active: !!details.active @@ -505,8 +415,8 @@ vAPI.Tabs = class { // Opening a tab from incognito window won't focus the window // in which the tab was opened const focusWindow = tab => { - if ( tab.active && browser.windows instanceof Object ) { - browser.windows.update(tab.windowId, { focused: true }); + if ( tab.active && vAPI.windows instanceof Object ) { + vAPI.windows.update(tab.windowId, { focused: true }); } }; @@ -519,29 +429,27 @@ vAPI.Tabs = class { } // update doesn't accept index, must use move - browser.tabs.update( - toChromiumTabId(details.tabId), - updateDetails, - tab => { - // if the tab doesn't exist - if ( vAPI.lastError() ) { - browser.tabs.create(updateDetails, focusWindow); - } else if ( details.index !== undefined ) { - browser.tabs.move(tab.id, { index: details.index }); - } - } + const tab = await vAPI.tabs.update( + toTabId(details.tabId), + updateDetails ); + // if the tab doesn't exist + if ( tab === null ) { + browser.tabs.create(updateDetails, focusWindow); + } else if ( details.index !== undefined ) { + browser.tabs.move(tab.id, { index: details.index }); + } }; // Open in a standalone window // // https://github.com/uBlockOrigin/uBlock-issues/issues/168#issuecomment-413038191 - // Not all platforms support browser.windows API. + // Not all platforms support vAPI.windows. // // For some reasons, some platforms do not honor the left,top // position when specified. I found that further calling // windows.update again with the same position _may_ help. - if ( details.popup !== undefined && browser.windows instanceof Object ) { + if ( details.popup !== undefined && vAPI.windows instanceof Object ) { const createDetails = { url: details.url, type: details.popup, @@ -549,19 +457,18 @@ vAPI.Tabs = class { if ( details.box instanceof Object ) { Object.assign(createDetails, details.box); } - browser.windows.create(createDetails, win => { - if ( win instanceof Object === false ) { return; } - if ( details.box instanceof Object === false ) { return; } - if ( - win.left === details.box.left && - win.top === details.box.top - ) { - return; - } - browser.windows.update(win.id, { - left: details.box.left, - top: details.box.top - }); + const win = await vAPI.windows.create(createDetails); + if ( win === null ) { return; } + if ( details.box instanceof Object === false ) { return; } + if ( + win.left === details.box.left && + win.top === details.box.top + ) { + return; + } + vAPI.windows.update(win.id, { + left: details.box.left, + top: details.box.top }); return; } @@ -571,15 +478,13 @@ vAPI.Tabs = class { return; } - vAPI.tabs.get(null, tab => { - if ( tab ) { - details.index = tab.index + 1; - } else { - delete details.index; - } - - subWrapper(); - }); + const tab = await vAPI.tabs.getCurrent(); + if ( tab !== null ) { + details.index = tab.index + 1; + } else { + details.index = undefined; + } + subWrapper(); } // Properties of the details object: @@ -593,7 +498,7 @@ vAPI.Tabs = class { // it instead of opening a new one // - popup: true => open in a new window - open(details) { + async open(details) { let targetURL = details.url; if ( typeof targetURL !== 'string' || targetURL === '' ) { return null; @@ -628,29 +533,35 @@ vAPI.Tabs = class { ? targetURL : targetURL.slice(0, pos); - browser.tabs.query({ url: targetURLWithoutHash }, tabs => { - void browser.runtime.lastError; - const tab = Array.isArray(tabs) && tabs[0]; - if ( !tab ) { - this.create(targetURL, details); - return; - } - const updateDetails = { active: true }; - // https://github.com/uBlockOrigin/uBlock-issues/issues/592 - if ( tab.url.startsWith(targetURL) === false ) { - updateDetails.url = targetURL; - } - browser.tabs.update(tab.id, updateDetails, tab => { - if ( browser.windows instanceof Object === false ) { return; } - browser.windows.update(tab.windowId, { focused: true }); - }); - }); + const tabs = await vAPI.tabs.query({ url: targetURLWithoutHash }); + if ( tabs.length === 0 ) { + this.create(targetURL, details); + return; + } + let tab = tabs[0]; + const updateDetails = { active: true }; + // https://github.com/uBlockOrigin/uBlock-issues/issues/592 + if ( tab.url.startsWith(targetURL) === false ) { + updateDetails.url = targetURL; + } + tab = await vAPI.tabs.update(tab.id, updateDetails); + if ( vAPI.windows instanceof Object === false ) { return; } + vAPI.windows.update(tab.windowId, { focused: true }); + } + + async update() { + let tab; + try { + tab = await webext.tabs.update(...arguments); + } + catch (reason) { + } + return tab instanceof Object ? tab : null; } // Replace the URL of a tab. Noop if the tab does not exist. - replace(tabId, url) { - tabId = toChromiumTabId(tabId); + tabId = toTabId(tabId); if ( tabId === 0 ) { return; } let targetURL = url; @@ -660,18 +571,17 @@ vAPI.Tabs = class { targetURL = vAPI.getURL(targetURL); } - browser.tabs.update(tabId, { url: targetURL }, vAPI.resetLastError); + vAPI.tabs.update(tabId, { url: targetURL }); } remove(tabId) { - tabId = toChromiumTabId(tabId); + tabId = toTabId(tabId); if ( tabId === 0 ) { return; } - browser.tabs.remove(tabId, vAPI.resetLastError); } reload(tabId, bypassCache = false) { - tabId = toChromiumTabId(tabId); + tabId = toTabId(tabId); if ( tabId === 0 ) { return; } browser.tabs.reload( @@ -681,16 +591,13 @@ vAPI.Tabs = class { ); } - select(tabId) { - tabId = toChromiumTabId(tabId); + async select(tabId) { + tabId = toTabId(tabId); if ( tabId === 0 ) { return; } - - browser.tabs.update(tabId, { active: true }, function(tab) { - void browser.runtime.lastError; - if ( !tab ) { return; } - if ( browser.windows instanceof Object === false ) { return; } - browser.windows.update(tab.windowId, { focused: true }); - }); + const tab = await vAPI.tabs.update(tabId, { active: true }); + if ( tab === null ) { return; } + if ( vAPI.windows instanceof Object === false ) { return; } + vAPI.windows.update(tab.windowId, { focused: true }); } injectScript(tabId, details, callback) { @@ -703,7 +610,7 @@ vAPI.Tabs = class { }; if ( tabId ) { browser.tabs.executeScript( - toChromiumTabId(tabId), + toTabId(tabId), details, onScriptExecuted ); @@ -749,6 +656,41 @@ vAPI.Tabs = class { /******************************************************************************/ /******************************************************************************/ +if ( browser.windows instanceof Object ) { + vAPI.windows = { + get: async function() { + let win; + try { + win = await webext.windows.get(...arguments); + } + catch (reason) { + } + return win instanceof Object ? win : null; + }, + create: async function() { + let win; + try { + win = await webext.windows.create(...arguments); + } + catch (reason) { + } + return win instanceof Object ? win : null; + }, + update: async function() { + let win; + try { + win = await webext.windows.update(...arguments); + } + catch (reason) { + } + return win instanceof Object ? win : null; + }, + }; +} + +/******************************************************************************/ +/******************************************************************************/ + // Must read: https://code.google.com/p/chromium/issues/detail?id=410868#c8 // https://github.com/chrisaljoudi/uBlock/issues/19 @@ -852,8 +794,17 @@ vAPI.setIcon = (( ) => { } })(); - const onTabReady = function(tab, details) { - if ( vAPI.lastError() || !tab ) { return; } + // parts: bit 0 = icon + // bit 1 = badge text + // bit 2 = badge color + // bit 3 = hide badge + + return async function(tabId, details) { + tabId = toTabId(tabId); + if ( tabId === 0 ) { return; } + + const tab = await vAPI.tabs.get(tabId); + if ( tab === null ) { return; } const { parts, state, badge, color } = details; @@ -883,18 +834,6 @@ vAPI.setIcon = (( ) => { ) }); } - }; - - // parts: bit 0 = icon - // bit 1 = badge text - // bit 2 = badge color - // bit 3 = hide badge - - return function(tabId, details) { - tabId = toChromiumTabId(tabId); - if ( tabId === 0 ) { return; } - - browser.tabs.get(tabId, tab => onTabReady(tab, details)); if ( vAPI.contextMenu instanceof Object ) { vAPI.contextMenu.onMustUpdate(tabId); @@ -1381,48 +1320,27 @@ vAPI.commands = chrome.commands; // Also, UC Browser: http://www.upsieutoc.com/image/WXuH vAPI.adminStorage = (( ) => { - if ( browser.storage.managed instanceof Object === false ) { + if ( webext.storage.managed instanceof Object === false ) { return { getItem: function() { return Promise.resolve(); }, }; } - const managedStorage = vAPI.apiIsPromisified - ? browser.storage.managed - : { - get: function(keys) { - return new Promise((resolve, reject) => { - browser.storage.managed.get(keys, result => { - const lastError = browser.runtime.lastError; - if ( lastError instanceof Object ) { - return reject(lastError); - } - resolve(result); - }); - }); - }, - }; return { getItem: async function(key) { let bin; try { - bin = await managedStorage.get(key); + bin = await webext.storage.managed.get(key); } catch(ex) { } - let data; - if ( - chrome.runtime.lastError instanceof Object === false && - bin instanceof Object - ) { - data = bin[key]; + if ( bin instanceof Object ) { + return bin[key]; } - return data; } }; })(); - /******************************************************************************/ /******************************************************************************/ diff --git a/platform/chromium/webext.js b/platform/chromium/webext.js new file mode 100644 index 000000000..00b7da1fd --- /dev/null +++ b/platform/chromium/webext.js @@ -0,0 +1,157 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +'use strict'; + +// `webext` is a promisified api of `chrome`. Entries are added as +// the promisification of uBO progress. + +const webext = { // jshint ignore:line + storage: { + local: { + clear: function() { + return new Promise((resolve, reject) => { + chrome.storage.local.clear(( ) => { + const lastError = chrome.runtime.lastError; + if ( lastError instanceof Object ) { + return reject(lastError); + } + resolve(); + }); + }); + }, + get: function() { + return new Promise((resolve, reject) => { + chrome.storage.local.get(...arguments, result => { + const lastError = chrome.runtime.lastError; + if ( lastError instanceof Object ) { + return reject(lastError); + } + resolve(result); + }); + }); + }, + getBytesInUse: function() { + return new Promise((resolve, reject) => { + chrome.storage.local.getBytesInUse(...arguments, result => { + const lastError = chrome.runtime.lastError; + if ( lastError instanceof Object ) { + return reject(lastError); + } + resolve(result); + }); + }); + }, + remove: function() { + return new Promise((resolve, reject) => { + chrome.storage.local.remove(...arguments, ( ) => { + const lastError = chrome.runtime.lastError; + if ( lastError instanceof Object ) { + return reject(lastError); + } + resolve(); + }); + }); + }, + set: function() { + return new Promise((resolve, reject) => { + chrome.storage.local.set(...arguments, ( ) => { + const lastError = chrome.runtime.lastError; + if ( lastError instanceof Object ) { + return reject(lastError); + } + resolve(); + }); + }); + }, + }, + }, + + tabs: { + get: function() { + return new Promise(resolve => { + chrome.tabs.get(...arguments, tab => { + void chrome.runtime.lastError; + resolve(tab instanceof Object ? tab : null); + }); + }); + }, + query: function() { + return new Promise(resolve => { + chrome.tabs.query(...arguments, tabs => { + void chrome.runtime.lastError; + resolve(Array.isArray(tabs) ? tabs : []); + }); + }); + }, + update: function() { + return new Promise(resolve => { + chrome.tabs.update(...arguments, tab => { + void chrome.runtime.lastError; + resolve(tab instanceof Object ? tab : null); + }); + }); + }, + }, + + windows: { + get: async function() { + return new Promise(resolve => { + chrome.windows.get(...arguments, win => { + void chrome.runtime.lastError; + resolve(win instanceof Object ? win : null); + }); + }); + }, + create: async function() { + return new Promise(resolve => { + chrome.windows.create(...arguments, win => { + void chrome.runtime.lastError; + resolve(win instanceof Object ? win : null); + }); + }); + }, + update: async function() { + return new Promise(resolve => { + chrome.windows.update(...arguments, win => { + void chrome.runtime.lastError; + resolve(win instanceof Object ? win : null); + }); + }); + }, + }, +}; + +if ( chrome.storage.managed instanceof Object ) { + webext.storage.managed = { + get: function() { + return new Promise((resolve, reject) => { + chrome.storage.local.get(...arguments, result => { + const lastError = chrome.runtime.lastError; + if ( lastError instanceof Object ) { + return reject(lastError); + } + resolve(result); + }); + }); + }, + }; +} diff --git a/platform/firefox/webext.js b/platform/firefox/webext.js new file mode 100644 index 000000000..4729e7c17 --- /dev/null +++ b/platform/firefox/webext.js @@ -0,0 +1,24 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +'use strict'; + +const webext = browser; // jshint ignore:line diff --git a/src/background.html b/src/background.html index 0f9b37070..8db736597 100644 --- a/src/background.html +++ b/src/background.html @@ -9,6 +9,7 @@ + diff --git a/src/js/cachestorage.js b/src/js/cachestorage.js index cb0f3690d..1657ee3ec 100644 --- a/src/js/cachestorage.js +++ b/src/js/cachestorage.js @@ -54,64 +54,15 @@ const STORAGE_NAME = 'uBlock0CacheStorage'; - // Default to webext storage. Wrapped into promises if the API does not - // support returning promises. - const promisified = (( ) => { - try { - return browser.storage.local.get('_') instanceof Promise; - } - catch(ex) { - } - return false; - })(); - + // Default to webext storage. + const localStorage = webext.storage.local; const api = { name: 'browser.storage.local', - get: promisified ? - browser.storage.local.get : - function(keys) { - return new Promise(resolve => { - browser.storage.local.get(keys, bin => { - resolve(bin); - }); - }); - }, - set: promisified ? - browser.storage.local.set : - function(keys) { - return new Promise(resolve => { - browser.storage.local.set(keys, ( ) => { - resolve(); - }); - }); - }, - remove: promisified ? - browser.storage.local.remove : - function(keys) { - return new Promise(resolve => { - browser.storage.local.remove(keys, ( ) => { - resolve(); - }); - }); - }, - clear: promisified ? - browser.storage.local.clear : - function() { - return new Promise(resolve => { - browser.storage.local.clear(( ) => { - resolve(); - }); - }); - }, - getBytesInUse: promisified ? - browser.storage.local.getBytesInUse : - function(keys) { - return new Promise(resolve => { - browser.storage.local.getBytesInUse(keys, count => { - resolve(count); - }); - }); - }, + get: localStorage.get, + set: localStorage.set, + remove: localStorage.remove, + clear: localStorage.clear, + getBytesInUse: localStorage.getBytesInUse, select: function(selectedBackend) { let actualBackend = selectedBackend; if ( actualBackend === undefined || actualBackend === 'unset' ) { @@ -139,7 +90,7 @@ }; // Reassign API entries to that of indexedDB-based ones - const selectIDB = function() { + const selectIDB = async function() { let db; let dbPromise; let dbTimer; @@ -244,7 +195,7 @@ return dbPromise; }; - const getFromDb = function(keys, keyvalStore, callback) { + const getFromDb = async function(keys, keyvalStore, callback) { if ( typeof callback !== 'function' ) { return; } if ( keys.length === 0 ) { return callback(keyvalStore); } const promises = []; @@ -261,7 +212,8 @@ }) ); }; - getDb().then(db => { + try { + const db = await getDb(); if ( !db ) { return callback(); } const transaction = db.transaction(STORAGE_NAME, 'readonly'); transaction.oncomplete = @@ -277,29 +229,29 @@ req.onsuccess = gotOne; req.onerror = noopfn; } - }).catch(reason => { + } + catch(reason) { console.info(`cacheStorage.getFromDb() failed: ${reason}`); callback(); - }); + } }; - const visitAllFromDb = function(visitFn) { - getDb().then(db => { - if ( !db ) { return visitFn(); } - const transaction = db.transaction(STORAGE_NAME, 'readonly'); - transaction.oncomplete = - transaction.onerror = - transaction.onabort = ( ) => visitFn(); - const table = transaction.objectStore(STORAGE_NAME); - const req = table.openCursor(); - req.onsuccess = function(ev) { - let cursor = ev.target && ev.target.result; - if ( !cursor ) { return; } - let entry = cursor.value; - visitFn(entry); - cursor.continue(); - }; - }); + const visitAllFromDb = async function(visitFn) { + const db = await getDb(); + if ( !db ) { return visitFn(); } + const transaction = db.transaction(STORAGE_NAME, 'readonly'); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = ( ) => visitFn(); + const table = transaction.objectStore(STORAGE_NAME); + const req = table.openCursor(); + req.onsuccess = function(ev) { + let cursor = ev.target && ev.target.result; + if ( !cursor ) { return; } + let entry = cursor.value; + visitFn(entry); + cursor.continue(); + }; }; const getAllFromDb = function(callback) { @@ -335,7 +287,7 @@ // https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/transaction // https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/put - const putToDb = function(keyvalStore, callback) { + const putToDb = async function(keyvalStore, callback) { if ( typeof callback !== 'function' ) { callback = noopfn; } @@ -359,67 +311,67 @@ µBlock.lz4Codec.encode(key, data).then(handleEncodingResult) ); } - Promise.all(promises).then(results => { + const finish = ( ) => { + if ( callback === undefined ) { return; } + let cb = callback; + callback = undefined; + cb(); + }; + try { + const results = await Promise.all(promises); const db = results[0]; if ( !db ) { return callback(); } - const finish = ( ) => { - if ( callback === undefined ) { return; } - let cb = callback; - callback = undefined; - cb(); - }; - try { - const transaction = db.transaction( - STORAGE_NAME, - 'readwrite' - ); - transaction.oncomplete = - transaction.onerror = - transaction.onabort = finish; - const table = transaction.objectStore(STORAGE_NAME); - for ( const entry of entries ) { - table.put(entry); - } - } catch (ex) { - finish(); + const transaction = db.transaction( + STORAGE_NAME, + 'readwrite' + ); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = finish; + const table = transaction.objectStore(STORAGE_NAME); + for ( const entry of entries ) { + table.put(entry); } - }); + } catch (ex) { + finish(); + } }; - const deleteFromDb = function(input, callback) { + const deleteFromDb = async function(input, callback) { if ( typeof callback !== 'function' ) { callback = noopfn; } const keys = Array.isArray(input) ? input.slice() : [ input ]; if ( keys.length === 0 ) { return callback(); } - getDb().then(db => { + const finish = ( ) => { + if ( callback === undefined ) { return; } + let cb = callback; + callback = undefined; + cb(); + }; + try { + const db = await getDb(); if ( !db ) { return callback(); } - const finish = ( ) => { - if ( callback === undefined ) { return; } - let cb = callback; - callback = undefined; - cb(); - }; - try { - const transaction = db.transaction(STORAGE_NAME, 'readwrite'); - transaction.oncomplete = - transaction.onerror = - transaction.onabort = finish; - const table = transaction.objectStore(STORAGE_NAME); - for ( const key of keys ) { - table.delete(key); - } - } catch (ex) { - finish(); + const transaction = db.transaction(STORAGE_NAME, 'readwrite'); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = finish; + const table = transaction.objectStore(STORAGE_NAME); + for ( const key of keys ) { + table.delete(key); } - }); + } catch (ex) { + finish(); + } }; - const clearDb = function(callback) { + const clearDb = async function(callback) { if ( typeof callback !== 'function' ) { callback = noopfn; } - getDb().then(db => { + try { + const db = await getDb(); + if ( !db ) { return callback(); } const transaction = db.transaction(STORAGE_NAME, 'readwrite'); transaction.oncomplete = transaction.onerror = @@ -427,77 +379,77 @@ callback(); }; transaction.objectStore(STORAGE_NAME).clear(); - }).catch(reason => { + } + catch(reason) { console.info(`cacheStorage.clearDb() failed: ${reason}`); callback(); - }); + } }; - return getDb().then(db => { - if ( !db ) { return false; } - api.name = 'indexedDB'; - api.get = function get(keys) { - return new Promise(resolve => { - if ( keys === null ) { - return getAllFromDb(bin => resolve(bin)); - } - let toRead, output = {}; - if ( typeof keys === 'string' ) { - toRead = [ keys ]; - } else if ( Array.isArray(keys) ) { - toRead = keys; - } else /* if ( typeof keys === 'object' ) */ { - toRead = Object.keys(keys); - output = keys; - } - getFromDb(toRead, output, bin => resolve(bin)); - }); - }; - api.set = function set(keys) { - return new Promise(resolve => { - putToDb(keys, details => resolve(details)); - }); - }; - api.remove = function remove(keys) { - return new Promise(resolve => { - deleteFromDb(keys, ( ) => resolve()); - }); - }; - api.clear = function clear() { - return new Promise(resolve => { - clearDb(( ) => resolve()); - }); - }; - api.getBytesInUse = function getBytesInUse() { - return Promise.resolve(0); - }; - return true; - }); + await getDb(); + if ( !db ) { return false; } + + api.name = 'indexedDB'; + api.get = function get(keys) { + return new Promise(resolve => { + if ( keys === null ) { + return getAllFromDb(bin => resolve(bin)); + } + let toRead, output = {}; + if ( typeof keys === 'string' ) { + toRead = [ keys ]; + } else if ( Array.isArray(keys) ) { + toRead = keys; + } else /* if ( typeof keys === 'object' ) */ { + toRead = Object.keys(keys); + output = keys; + } + getFromDb(toRead, output, bin => resolve(bin)); + }); + }; + api.set = function set(keys) { + return new Promise(resolve => { + putToDb(keys, details => resolve(details)); + }); + }; + api.remove = function remove(keys) { + return new Promise(resolve => { + deleteFromDb(keys, ( ) => resolve()); + }); + }; + api.clear = function clear() { + return new Promise(resolve => { + clearDb(( ) => resolve()); + }); + }; + api.getBytesInUse = function getBytesInUse() { + return Promise.resolve(0); + }; + return true; }; // https://github.com/uBlockOrigin/uBlock-issues/issues/328 // Delete cache-related entries from webext storage. - const clearWebext = function() { - browser.storage.local.get('assetCacheRegistry', bin => { - if ( - bin instanceof Object === false || - bin.assetCacheRegistry instanceof Object === false - ) { - return; + const clearWebext = async function() { + const bin = await webext.storage.local.get('assetCacheRegistry'); + if ( + bin instanceof Object === false || + bin.assetCacheRegistry instanceof Object === false + ) { + return; + } + const toRemove = [ + 'assetCacheRegistry', + 'assetSourceRegistry', + 'resourcesSelfie', + 'selfie' + ]; + for ( const key in bin.assetCacheRegistry ) { + if ( bin.assetCacheRegistry.hasOwnProperty(key) ) { + toRemove.push('cache/' + key); } - const toRemove = [ - 'assetCacheRegistry', - 'assetSourceRegistry', - 'resourcesSelfie', - 'selfie' - ]; - for ( const key in bin.assetCacheRegistry ) { - if ( bin.assetCacheRegistry.hasOwnProperty(key) ) { - toRemove.push('cache/' + key); - } - } - browser.storage.local.remove(toRemove); - }); + } + webext.storage.local.remove(toRemove); }; const clearIDB = function() { diff --git a/src/js/commands.js b/src/js/commands.js index dff8a7772..4a5c61521 100644 --- a/src/js/commands.js +++ b/src/js/commands.js @@ -128,36 +128,37 @@ const relaxBlockingMode = (( ) => { }; })(); -vAPI.commands.onCommand.addListener(command => { +vAPI.commands.onCommand.addListener(async command => { const µb = µBlock; switch ( command ) { case 'launch-element-picker': - case 'launch-element-zapper': - vAPI.tabs.get(null, tab => { - if ( tab instanceof Object === false ) { return; } - µb.mouseEventRegister.x = µb.mouseEventRegister.y = -1; - µb.elementPickerExec( - tab.id, - undefined, - command === 'launch-element-zapper' - ); - }); - break; - case 'launch-logger': - vAPI.tabs.get(null, tab => { - const hash = tab.url.startsWith(vAPI.getURL('')) - ? '' - : `#_+${tab.id}`; - µb.openNewTab({ - url: `logger-ui.html${hash}`, - select: true, - index: -1 - }); + case 'launch-element-zapper': { + const tab = await vAPI.tabs.getCurrent(); + if ( tab instanceof Object === false ) { return; } + µb.mouseEventRegister.x = µb.mouseEventRegister.y = -1; + µb.elementPickerExec( + tab.id, + undefined, + command === 'launch-element-zapper' + ); + break; + } + case 'launch-logger': { + const tab = await vAPI.tabs.getCurrent(); + if ( tab instanceof Object === false ) { return; } + const hash = tab.url.startsWith(vAPI.getURL('')) + ? '' + : `#_+${tab.id}`; + µb.openNewTab({ + url: `logger-ui.html${hash}`, + select: true, + index: -1 }); break; + } case 'relax-blocking-mode': - vAPI.tabs.get(null, relaxBlockingMode); + relaxBlockingMode(await vAPI.tabs.getCurrent()); break; default: break; diff --git a/src/js/contextmenu.js b/src/js/contextmenu.js index 4d70ecb24..4c5ff4c9e 100644 --- a/src/js/contextmenu.js +++ b/src/js/contextmenu.js @@ -128,17 +128,16 @@ const update = function(tabId = undefined) { // For unknown reasons, the currently active tab will not be successfully // looked up after closing a window. -vAPI.contextMenu.onMustUpdate = function(tabId = undefined) { +vAPI.contextMenu.onMustUpdate = async function(tabId = undefined) { if ( µBlock.userSettings.contextMenuEnabled === false ) { return update(); } if ( tabId !== undefined ) { return update(tabId); } - vAPI.tabs.get(null, tab => { - if ( tab instanceof Object === false ) { return; } - update(tab.id); - }); + const tab = await vAPI.tabs.getCurrent(); + if ( tab instanceof Object === false ) { return; } + update(tab.id); }; return { update: vAPI.contextMenu.onMustUpdate }; diff --git a/src/js/messaging.js b/src/js/messaging.js index 3ea332a4c..0ccb7d123 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -346,22 +346,20 @@ const popupDataFromTabId = function(tabId, tabTitle) { return r; }; -const popupDataFromRequest = function(request, callback) { +const popupDataFromRequest = async function(request) { if ( request.tabId ) { - callback(popupDataFromTabId(request.tabId, '')); - return; + return popupDataFromTabId(request.tabId, ''); } // Still no target tab id? Use currently selected tab. - vAPI.tabs.get(null, function(tab) { - var tabId = ''; - var tabTitle = ''; - if ( tab ) { - tabId = tab.id; - tabTitle = tab.title || ''; - } - callback(popupDataFromTabId(tabId, tabTitle)); - }); + const tab = await vAPI.tabs.getCurrent(); + let tabId = ''; + let tabTitle = ''; + if ( tab instanceof Object ) { + tabId = tab.id; + tabTitle = tab.title || ''; + } + return popupDataFromTabId(tabId, tabTitle); }; const onMessage = function(request, sender, callback) { @@ -370,7 +368,9 @@ const onMessage = function(request, sender, callback) { // Async switch ( request.what ) { case 'getPopupData': - popupDataFromRequest(request, callback); + popupDataFromRequest(request).then(popupData => { + callback(popupData); + }); return; default: @@ -1151,7 +1151,7 @@ vAPI.messaging.listen({ const µb = µBlock; const extensionOriginURL = vAPI.getURL(''); -const getLoggerData = function(details, activeTabId, callback) { +const getLoggerData = async function(details, activeTabId, callback) { const response = { colorBlind: µb.userSettings.colorBlindFriendly, entries: µb.logger.readAll(details.ownerId), @@ -1178,26 +1178,20 @@ const getLoggerData = function(details, activeTabId, callback) { response.activeTabId = undefined; } } - if ( details.popupLoggerBoxChanged && browser.windows instanceof Object ) { - browser.tabs.query( - { url: vAPI.getURL('/logger-ui.html?popup=1') }, - tabs => { - if ( Array.isArray(tabs) === false ) { return; } - if ( tabs.length === 0 ) { return; } - browser.windows.get(tabs[0].windowId, win => { - if ( win instanceof Object === false ) { return; } - vAPI.localStorage.setItem( - 'popupLoggerBox', - JSON.stringify({ - left: win.left, - top: win.top, - width: win.width, - height: win.height, - }) - ); - }); - } - ); + if ( details.popupLoggerBoxChanged && vAPI.windows instanceof Object ) { + const tabs = await vAPI.tabs.query({ + url: vAPI.getURL('/logger-ui.html?popup=1') + }); + if ( tabs.length !== 0 ) { + const win = await vAPI.windows.get(tabs[0].windowId); + if ( win === null ) { return; } + vAPI.localStorage.setItem('popupLoggerBox', JSON.stringify({ + left: win.left, + top: win.top, + width: win.width, + height: win.height, + })); + } } callback(response); }; @@ -1242,10 +1236,9 @@ const onMessage = function(request, sender, callback) { µb.logger.ownerId !== undefined && µb.logger.ownerId !== request.ownerId ) { - callback({ unavailable: true }); - return; + return callback({ unavailable: true }); } - vAPI.tabs.get(null, function(tab) { + vAPI.tabs.getCurrent().then(tab => { getLoggerData(request, tab && tab.id, callback); }); return; diff --git a/src/js/start.js b/src/js/start.js index 45d42b5d4..05384814a 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -106,7 +106,7 @@ const onAllReady = function() { // in already opened web pages, to remove whatever nuisance could make it to // the web pages before uBlock was ready. -const initializeTabs = function() { +const initializeTabs = async function() { const handleScriptResponse = function(tabId, results) { if ( Array.isArray(results) === false || @@ -128,24 +128,21 @@ const initializeTabs = function() { } } }; - const bindToTabs = function(tabs) { - for ( const tab of tabs ) { - µb.tabContextManager.commit(tab.id, tab.url); - µb.bindTabToPageStats(tab.id); - // https://github.com/chrisaljoudi/uBlock/issues/129 - // Find out whether content scripts need to be injected - // programmatically. This may be necessary for web pages which - // were loaded before uBO launched. - if ( /^https?:\/\//.test(tab.url) === false ) { continue; } - vAPI.tabs.injectScript( - tab.id, - { file: 'js/scriptlets/should-inject-contentscript.js' }, - handleScriptResponse.bind(null, tab.id) - ); - } - }; - - browser.tabs.query({ url: '' }, bindToTabs); + const tabs = await vAPI.tabs.query({ url: '' }); + for ( const tab of tabs ) { + µb.tabContextManager.commit(tab.id, tab.url); + µb.bindTabToPageStats(tab.id); + // https://github.com/chrisaljoudi/uBlock/issues/129 + // Find out whether content scripts need to be injected + // programmatically. This may be necessary for web pages which + // were loaded before uBO launched. + if ( /^https?:\/\//.test(tab.url) === false ) { continue; } + vAPI.tabs.injectScript( + tab.id, + { file: 'js/scriptlets/should-inject-contentscript.js' }, + handleScriptResponse.bind(null, tab.id) + ); + } }; /******************************************************************************/ diff --git a/src/js/tab.js b/src/js/tab.js index 1dbecb1b2..62c5ad9f8 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -542,19 +542,18 @@ housekeep itself. } }; - TabContext.prototype.onGC = function() { + TabContext.prototype.onGC = async function() { if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; } // https://github.com/gorhill/uBlock/issues/1713 // For unknown reasons, Firefox's setTimeout() will sometimes // causes the callback function to be called immediately, bypassing // the main event loop. For now this should prevent uBO from crashing // as a result of the bad setTimeout() behavior. - if ( this.onGCBarrier ) { - return; - } + if ( this.onGCBarrier ) { return; } this.onGCBarrier = true; this.gcTimer = null; - vAPI.tabs.get(this.tabId, tab => { this.onTab(tab); }); + const tab = await vAPI.tabs.get(this.tabId); + this.onTab(tab); this.onGCBarrier = false; }; @@ -1065,9 +1064,10 @@ vAPI.tabs = new vAPI.Tabs(); } }; - const updateTitle = function(entry) { + const updateTitle = async function(entry) { tabIdToTimer.delete(entry.tabId); - vAPI.tabs.get(entry.tabId, onTabReady.bind(null, entry)); + const tab = await vAPI.tabs.get(entry.tabId); + onTabReady(entry, tab); }; return function(tabId) { @@ -1098,11 +1098,10 @@ vAPI.tabs = new vAPI.Tabs(); const pageStoreJanitor = function() { const tabIds = Array.from(µBlock.pageStores.keys()).sort(); - const checkTab = tabId => { - vAPI.tabs.get(tabId, tab => { - if ( tab ) { return; } - µBlock.unbindTabFromPageStats(tabId); - }); + const checkTab = async tabId => { + const tab = await vAPI.tabs.get(tabId); + if ( tab ) { return; } + µBlock.unbindTabFromPageStats(tabId); }; if ( pageStoreJanitorSampleAt >= tabIds.length ) { pageStoreJanitorSampleAt = 0; diff --git a/tools/make-firefox.sh b/tools/make-firefox.sh index f46ed0ce8..6bca88b7c 100755 --- a/tools/make-firefox.sh +++ b/tools/make-firefox.sh @@ -12,11 +12,12 @@ mkdir -p $DES echo "*** uBlock0.firefox: copying common files" bash ./tools/copy-common-files.sh $DES -cp -R $DES/_locales/nb $DES/_locales/no +cp -R $DES/_locales/nb $DES/_locales/no -cp platform/firefox/manifest.json $DES/ -cp platform/firefox/vapi-usercss.js $DES/js/ -cp platform/firefox/vapi-webrequest.js $DES/js/ +cp platform/firefox/manifest.json $DES/ +cp platform/firefox/webext.js $DES/js/ +cp platform/firefox/vapi-usercss.js $DES/js/ +cp platform/firefox/vapi-webrequest.js $DES/js/ echo "*** uBlock0.firefox: concatenating content scripts" cat $DES/js/vapi-usercss.js > /tmp/contentscript.js