Work toward modernizing code base: promisification

Swathes of code have been converted to use
Promises/async/await. More left to do.

Related commits:
- 915687fddb
- 55cc0c6997
- e27328f931
This commit is contained in:
Raymond Hill 2019-09-16 09:45:17 -04:00
parent c4ee846cd4
commit eec53c0154
No known key found for this signature in database
GPG Key ID: 25E1490B761470C2
12 changed files with 562 additions and 519 deletions

View File

@ -11,6 +11,7 @@
"safari": false, "safari": false,
"self": false, "self": false,
"vAPI": false, "vAPI": false,
"webext": false,
"µBlock": false "µBlock": false
}, },
"laxbreak": true, "laxbreak": true,

View File

@ -43,15 +43,6 @@ vAPI.lastError = function() {
return chrome.runtime.lastError; 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://github.com/gorhill/uBlock/issues/875
// https://code.google.com/p/chromium/issues/detail?id=410868#c8 // https://code.google.com/p/chromium/issues/detail?id=410868#c8
// Must not leave `lastError` unchecked. // Must not leave `lastError` unchecked.
@ -116,83 +107,7 @@ vAPI.app = {
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/
vAPI.storage = (( ) => { vAPI.storage = webext.storage.local;
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();
});
});
},
};
})();
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/
@ -211,7 +126,7 @@ vAPI.storage = (( ) => {
// Do not mess up with existing settings if not assigning them stricter // Do not mess up with existing settings if not assigning them stricter
// values. // values.
vAPI.browserSettings = (function() { vAPI.browserSettings = (( ) => {
// Not all platforms support `chrome.privacy`. // Not all platforms support `chrome.privacy`.
if ( chrome.privacy instanceof Object === false ) { return; } if ( chrome.privacy instanceof Object === false ) { return; }
@ -384,9 +299,8 @@ vAPI.isBehindTheSceneTabId = function(tabId) {
vAPI.unsetTabId = 0; vAPI.unsetTabId = 0;
vAPI.noTabId = -1; // definitely not any existing tab vAPI.noTabId = -1; // definitely not any existing tab
// To remove when tabId-as-integer has been tested enough. // To ensure we always use a good tab id
const toTabId = function(tabId) {
const toChromiumTabId = function(tabId) {
return typeof tabId === 'number' && isNaN(tabId) === false return typeof tabId === 'number' && isNaN(tabId) === false
? tabId ? tabId
: 0; : 0;
@ -436,17 +350,12 @@ vAPI.Tabs = class {
// https://github.com/uBlockOrigin/uBlock-issues/issues/151 // https://github.com/uBlockOrigin/uBlock-issues/issues/151
// https://github.com/uBlockOrigin/uBlock-issues/issues/680#issuecomment-515215220 // https://github.com/uBlockOrigin/uBlock-issues/issues/680#issuecomment-515215220
if ( browser.windows instanceof Object ) { if ( browser.windows instanceof Object ) {
browser.windows.onFocusChanged.addListener(windowId => { browser.windows.onFocusChanged.addListener(async windowId => {
if ( windowId === browser.windows.WINDOW_ID_NONE ) { return; } if ( windowId === browser.windows.WINDOW_ID_NONE ) { return; }
browser.tabs.query({ active: true, windowId }, tabs => { const tabs = await vAPI.tabs.query({ active: true, windowId });
if ( Array.isArray(tabs) === false ) { return; } if ( tabs.length === 0 ) { return; }
if ( tabs.length === 0 ) { return; } const tab = tabs[0];
const tab = tabs[0]; this.onActivated({ tabId: tab.id, windowId: tab.windowId });
this.onActivated({
tabId: tab.id,
windowId: tab.windowId,
});
});
}); });
} }
@ -455,32 +364,33 @@ vAPI.Tabs = class {
}); });
} }
get(tabId, callback) { async get(tabId) {
if ( tabId === null ) { if ( tabId === null ) {
browser.tabs.query( return this.getCurrent();
{ active: true, currentWindow: true },
tabs => {
void browser.runtime.lastError;
callback(
Array.isArray(tabs) && tabs.length !== 0
? tabs[0]
: null
);
}
);
return;
} }
if ( tabId <= 0 ) { return null; }
tabId = toChromiumTabId(tabId); let tab;
if ( tabId === 0 ) { try {
callback(null); tab = await webext.tabs.get(tabId);
return;
} }
catch(reason) {
}
return tab instanceof Object ? tab : null;
}
browser.tabs.get(tabId, function(tab) { async getCurrent() {
void browser.runtime.lastError; const tabs = await this.query({ active: true, currentWindow: true });
callback(tab); 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: // Properties of the details object:
@ -491,12 +401,12 @@ vAPI.Tabs = class {
// foreground: undefined // foreground: undefined
// - popup: 'popup' => open in a new window // - popup: 'popup' => open in a new window
create(url, details) { async create(url, details) {
if ( details.active === undefined ) { if ( details.active === undefined ) {
details.active = true; details.active = true;
} }
const subWrapper = ( ) => { const subWrapper = async ( ) => {
const updateDetails = { const updateDetails = {
url: url, url: url,
active: !!details.active active: !!details.active
@ -505,8 +415,8 @@ vAPI.Tabs = class {
// Opening a tab from incognito window won't focus the window // Opening a tab from incognito window won't focus the window
// in which the tab was opened // in which the tab was opened
const focusWindow = tab => { const focusWindow = tab => {
if ( tab.active && browser.windows instanceof Object ) { if ( tab.active && vAPI.windows instanceof Object ) {
browser.windows.update(tab.windowId, { focused: true }); vAPI.windows.update(tab.windowId, { focused: true });
} }
}; };
@ -519,29 +429,27 @@ vAPI.Tabs = class {
} }
// update doesn't accept index, must use move // update doesn't accept index, must use move
browser.tabs.update( const tab = await vAPI.tabs.update(
toChromiumTabId(details.tabId), toTabId(details.tabId),
updateDetails, 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 });
}
}
); );
// 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 // Open in a standalone window
// //
// https://github.com/uBlockOrigin/uBlock-issues/issues/168#issuecomment-413038191 // 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 // For some reasons, some platforms do not honor the left,top
// position when specified. I found that further calling // position when specified. I found that further calling
// windows.update again with the same position _may_ help. // 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 = { const createDetails = {
url: details.url, url: details.url,
type: details.popup, type: details.popup,
@ -549,19 +457,18 @@ vAPI.Tabs = class {
if ( details.box instanceof Object ) { if ( details.box instanceof Object ) {
Object.assign(createDetails, details.box); Object.assign(createDetails, details.box);
} }
browser.windows.create(createDetails, win => { const win = await vAPI.windows.create(createDetails);
if ( win instanceof Object === false ) { return; } if ( win === null ) { return; }
if ( details.box instanceof Object === false ) { return; } if ( details.box instanceof Object === false ) { return; }
if ( if (
win.left === details.box.left && win.left === details.box.left &&
win.top === details.box.top win.top === details.box.top
) { ) {
return; return;
} }
browser.windows.update(win.id, { vAPI.windows.update(win.id, {
left: details.box.left, left: details.box.left,
top: details.box.top top: details.box.top
});
}); });
return; return;
} }
@ -571,15 +478,13 @@ vAPI.Tabs = class {
return; return;
} }
vAPI.tabs.get(null, tab => { const tab = await vAPI.tabs.getCurrent();
if ( tab ) { if ( tab !== null ) {
details.index = tab.index + 1; details.index = tab.index + 1;
} else { } else {
delete details.index; details.index = undefined;
} }
subWrapper();
subWrapper();
});
} }
// Properties of the details object: // Properties of the details object:
@ -593,7 +498,7 @@ vAPI.Tabs = class {
// it instead of opening a new one // it instead of opening a new one
// - popup: true => open in a new window // - popup: true => open in a new window
open(details) { async open(details) {
let targetURL = details.url; let targetURL = details.url;
if ( typeof targetURL !== 'string' || targetURL === '' ) { if ( typeof targetURL !== 'string' || targetURL === '' ) {
return null; return null;
@ -628,29 +533,35 @@ vAPI.Tabs = class {
? targetURL ? targetURL
: targetURL.slice(0, pos); : targetURL.slice(0, pos);
browser.tabs.query({ url: targetURLWithoutHash }, tabs => { const tabs = await vAPI.tabs.query({ url: targetURLWithoutHash });
void browser.runtime.lastError; if ( tabs.length === 0 ) {
const tab = Array.isArray(tabs) && tabs[0]; this.create(targetURL, details);
if ( !tab ) { return;
this.create(targetURL, details); }
return; let tab = tabs[0];
} const updateDetails = { active: true };
const updateDetails = { active: true }; // https://github.com/uBlockOrigin/uBlock-issues/issues/592
// https://github.com/uBlockOrigin/uBlock-issues/issues/592 if ( tab.url.startsWith(targetURL) === false ) {
if ( tab.url.startsWith(targetURL) === false ) { updateDetails.url = targetURL;
updateDetails.url = targetURL; }
} tab = await vAPI.tabs.update(tab.id, updateDetails);
browser.tabs.update(tab.id, updateDetails, tab => { if ( vAPI.windows instanceof Object === false ) { return; }
if ( browser.windows instanceof Object === false ) { return; } vAPI.windows.update(tab.windowId, { focused: true });
browser.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 the URL of a tab. Noop if the tab does not exist.
replace(tabId, url) { replace(tabId, url) {
tabId = toChromiumTabId(tabId); tabId = toTabId(tabId);
if ( tabId === 0 ) { return; } if ( tabId === 0 ) { return; }
let targetURL = url; let targetURL = url;
@ -660,18 +571,17 @@ vAPI.Tabs = class {
targetURL = vAPI.getURL(targetURL); targetURL = vAPI.getURL(targetURL);
} }
browser.tabs.update(tabId, { url: targetURL }, vAPI.resetLastError); vAPI.tabs.update(tabId, { url: targetURL });
} }
remove(tabId) { remove(tabId) {
tabId = toChromiumTabId(tabId); tabId = toTabId(tabId);
if ( tabId === 0 ) { return; } if ( tabId === 0 ) { return; }
browser.tabs.remove(tabId, vAPI.resetLastError); browser.tabs.remove(tabId, vAPI.resetLastError);
} }
reload(tabId, bypassCache = false) { reload(tabId, bypassCache = false) {
tabId = toChromiumTabId(tabId); tabId = toTabId(tabId);
if ( tabId === 0 ) { return; } if ( tabId === 0 ) { return; }
browser.tabs.reload( browser.tabs.reload(
@ -681,16 +591,13 @@ vAPI.Tabs = class {
); );
} }
select(tabId) { async select(tabId) {
tabId = toChromiumTabId(tabId); tabId = toTabId(tabId);
if ( tabId === 0 ) { return; } if ( tabId === 0 ) { return; }
const tab = await vAPI.tabs.update(tabId, { active: true });
browser.tabs.update(tabId, { active: true }, function(tab) { if ( tab === null ) { return; }
void browser.runtime.lastError; if ( vAPI.windows instanceof Object === false ) { return; }
if ( !tab ) { return; } vAPI.windows.update(tab.windowId, { focused: true });
if ( browser.windows instanceof Object === false ) { return; }
browser.windows.update(tab.windowId, { focused: true });
});
} }
injectScript(tabId, details, callback) { injectScript(tabId, details, callback) {
@ -703,7 +610,7 @@ vAPI.Tabs = class {
}; };
if ( tabId ) { if ( tabId ) {
browser.tabs.executeScript( browser.tabs.executeScript(
toChromiumTabId(tabId), toTabId(tabId),
details, details,
onScriptExecuted 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 // Must read: https://code.google.com/p/chromium/issues/detail?id=410868#c8
// https://github.com/chrisaljoudi/uBlock/issues/19 // https://github.com/chrisaljoudi/uBlock/issues/19
@ -852,8 +794,17 @@ vAPI.setIcon = (( ) => {
} }
})(); })();
const onTabReady = function(tab, details) { // parts: bit 0 = icon
if ( vAPI.lastError() || !tab ) { return; } // 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; 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 ) { if ( vAPI.contextMenu instanceof Object ) {
vAPI.contextMenu.onMustUpdate(tabId); vAPI.contextMenu.onMustUpdate(tabId);
@ -1381,48 +1320,27 @@ vAPI.commands = chrome.commands;
// Also, UC Browser: http://www.upsieutoc.com/image/WXuH // Also, UC Browser: http://www.upsieutoc.com/image/WXuH
vAPI.adminStorage = (( ) => { vAPI.adminStorage = (( ) => {
if ( browser.storage.managed instanceof Object === false ) { if ( webext.storage.managed instanceof Object === false ) {
return { return {
getItem: function() { getItem: function() {
return Promise.resolve(); 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 { return {
getItem: async function(key) { getItem: async function(key) {
let bin; let bin;
try { try {
bin = await managedStorage.get(key); bin = await webext.storage.managed.get(key);
} catch(ex) { } catch(ex) {
} }
let data; if ( bin instanceof Object ) {
if ( return bin[key];
chrome.runtime.lastError instanceof Object === false &&
bin instanceof Object
) {
data = bin[key];
} }
return data;
} }
}; };
})(); })();
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/

157
platform/chromium/webext.js Normal file
View File

@ -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);
});
});
},
};
}

View File

@ -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

View File

@ -9,6 +9,7 @@
<script src="lib/lz4/lz4-block-codec-any.js"></script> <script src="lib/lz4/lz4-block-codec-any.js"></script>
<script src="lib/punycode.js"></script> <script src="lib/punycode.js"></script>
<script src="lib/publicsuffixlist/publicsuffixlist.js"></script> <script src="lib/publicsuffixlist/publicsuffixlist.js"></script>
<script src="js/webext.js"></script>
<script src="js/vapi.js"></script> <script src="js/vapi.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-background.js"></script> <script src="js/vapi-background.js"></script>

View File

@ -54,64 +54,15 @@
const STORAGE_NAME = 'uBlock0CacheStorage'; const STORAGE_NAME = 'uBlock0CacheStorage';
// Default to webext storage. Wrapped into promises if the API does not // Default to webext storage.
// support returning promises. const localStorage = webext.storage.local;
const promisified = (( ) => {
try {
return browser.storage.local.get('_') instanceof Promise;
}
catch(ex) {
}
return false;
})();
const api = { const api = {
name: 'browser.storage.local', name: 'browser.storage.local',
get: promisified ? get: localStorage.get,
browser.storage.local.get : set: localStorage.set,
function(keys) { remove: localStorage.remove,
return new Promise(resolve => { clear: localStorage.clear,
browser.storage.local.get(keys, bin => { getBytesInUse: localStorage.getBytesInUse,
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);
});
});
},
select: function(selectedBackend) { select: function(selectedBackend) {
let actualBackend = selectedBackend; let actualBackend = selectedBackend;
if ( actualBackend === undefined || actualBackend === 'unset' ) { if ( actualBackend === undefined || actualBackend === 'unset' ) {
@ -139,7 +90,7 @@
}; };
// Reassign API entries to that of indexedDB-based ones // Reassign API entries to that of indexedDB-based ones
const selectIDB = function() { const selectIDB = async function() {
let db; let db;
let dbPromise; let dbPromise;
let dbTimer; let dbTimer;
@ -244,7 +195,7 @@
return dbPromise; return dbPromise;
}; };
const getFromDb = function(keys, keyvalStore, callback) { const getFromDb = async function(keys, keyvalStore, callback) {
if ( typeof callback !== 'function' ) { return; } if ( typeof callback !== 'function' ) { return; }
if ( keys.length === 0 ) { return callback(keyvalStore); } if ( keys.length === 0 ) { return callback(keyvalStore); }
const promises = []; const promises = [];
@ -261,7 +212,8 @@
}) })
); );
}; };
getDb().then(db => { try {
const db = await getDb();
if ( !db ) { return callback(); } if ( !db ) { return callback(); }
const transaction = db.transaction(STORAGE_NAME, 'readonly'); const transaction = db.transaction(STORAGE_NAME, 'readonly');
transaction.oncomplete = transaction.oncomplete =
@ -277,29 +229,29 @@
req.onsuccess = gotOne; req.onsuccess = gotOne;
req.onerror = noopfn; req.onerror = noopfn;
} }
}).catch(reason => { }
catch(reason) {
console.info(`cacheStorage.getFromDb() failed: ${reason}`); console.info(`cacheStorage.getFromDb() failed: ${reason}`);
callback(); callback();
}); }
}; };
const visitAllFromDb = function(visitFn) { const visitAllFromDb = async function(visitFn) {
getDb().then(db => { const db = await getDb();
if ( !db ) { return visitFn(); } if ( !db ) { return visitFn(); }
const transaction = db.transaction(STORAGE_NAME, 'readonly'); const transaction = db.transaction(STORAGE_NAME, 'readonly');
transaction.oncomplete = transaction.oncomplete =
transaction.onerror = transaction.onerror =
transaction.onabort = ( ) => visitFn(); transaction.onabort = ( ) => visitFn();
const table = transaction.objectStore(STORAGE_NAME); const table = transaction.objectStore(STORAGE_NAME);
const req = table.openCursor(); const req = table.openCursor();
req.onsuccess = function(ev) { req.onsuccess = function(ev) {
let cursor = ev.target && ev.target.result; let cursor = ev.target && ev.target.result;
if ( !cursor ) { return; } if ( !cursor ) { return; }
let entry = cursor.value; let entry = cursor.value;
visitFn(entry); visitFn(entry);
cursor.continue(); cursor.continue();
}; };
});
}; };
const getAllFromDb = function(callback) { 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/IDBDatabase/transaction
// https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/put // 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' ) { if ( typeof callback !== 'function' ) {
callback = noopfn; callback = noopfn;
} }
@ -359,67 +311,67 @@
µBlock.lz4Codec.encode(key, data).then(handleEncodingResult) µ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]; const db = results[0];
if ( !db ) { return callback(); } if ( !db ) { return callback(); }
const finish = ( ) => { const transaction = db.transaction(
if ( callback === undefined ) { return; } STORAGE_NAME,
let cb = callback; 'readwrite'
callback = undefined; );
cb(); transaction.oncomplete =
}; transaction.onerror =
try { transaction.onabort = finish;
const transaction = db.transaction( const table = transaction.objectStore(STORAGE_NAME);
STORAGE_NAME, for ( const entry of entries ) {
'readwrite' table.put(entry);
);
transaction.oncomplete =
transaction.onerror =
transaction.onabort = finish;
const table = transaction.objectStore(STORAGE_NAME);
for ( const entry of entries ) {
table.put(entry);
}
} catch (ex) {
finish();
} }
}); } catch (ex) {
finish();
}
}; };
const deleteFromDb = function(input, callback) { const deleteFromDb = async function(input, callback) {
if ( typeof callback !== 'function' ) { if ( typeof callback !== 'function' ) {
callback = noopfn; callback = noopfn;
} }
const keys = Array.isArray(input) ? input.slice() : [ input ]; const keys = Array.isArray(input) ? input.slice() : [ input ];
if ( keys.length === 0 ) { return callback(); } 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(); } if ( !db ) { return callback(); }
const finish = ( ) => { const transaction = db.transaction(STORAGE_NAME, 'readwrite');
if ( callback === undefined ) { return; } transaction.oncomplete =
let cb = callback; transaction.onerror =
callback = undefined; transaction.onabort = finish;
cb(); const table = transaction.objectStore(STORAGE_NAME);
}; for ( const key of keys ) {
try { table.delete(key);
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();
} }
}); } catch (ex) {
finish();
}
}; };
const clearDb = function(callback) { const clearDb = async function(callback) {
if ( typeof callback !== 'function' ) { if ( typeof callback !== 'function' ) {
callback = noopfn; callback = noopfn;
} }
getDb().then(db => { try {
const db = await getDb();
if ( !db ) { return callback(); }
const transaction = db.transaction(STORAGE_NAME, 'readwrite'); const transaction = db.transaction(STORAGE_NAME, 'readwrite');
transaction.oncomplete = transaction.oncomplete =
transaction.onerror = transaction.onerror =
@ -427,77 +379,77 @@
callback(); callback();
}; };
transaction.objectStore(STORAGE_NAME).clear(); transaction.objectStore(STORAGE_NAME).clear();
}).catch(reason => { }
catch(reason) {
console.info(`cacheStorage.clearDb() failed: ${reason}`); console.info(`cacheStorage.clearDb() failed: ${reason}`);
callback(); callback();
}); }
}; };
return getDb().then(db => { await getDb();
if ( !db ) { return false; } if ( !db ) { return false; }
api.name = 'indexedDB';
api.get = function get(keys) { api.name = 'indexedDB';
return new Promise(resolve => { api.get = function get(keys) {
if ( keys === null ) { return new Promise(resolve => {
return getAllFromDb(bin => resolve(bin)); if ( keys === null ) {
} return getAllFromDb(bin => resolve(bin));
let toRead, output = {}; }
if ( typeof keys === 'string' ) { let toRead, output = {};
toRead = [ keys ]; if ( typeof keys === 'string' ) {
} else if ( Array.isArray(keys) ) { toRead = [ keys ];
toRead = keys; } else if ( Array.isArray(keys) ) {
} else /* if ( typeof keys === 'object' ) */ { toRead = keys;
toRead = Object.keys(keys); } else /* if ( typeof keys === 'object' ) */ {
output = keys; toRead = Object.keys(keys);
} output = keys;
getFromDb(toRead, output, bin => resolve(bin)); }
}); getFromDb(toRead, output, bin => resolve(bin));
}; });
api.set = function set(keys) { };
return new Promise(resolve => { api.set = function set(keys) {
putToDb(keys, details => resolve(details)); return new Promise(resolve => {
}); putToDb(keys, details => resolve(details));
}; });
api.remove = function remove(keys) { };
return new Promise(resolve => { api.remove = function remove(keys) {
deleteFromDb(keys, ( ) => resolve()); return new Promise(resolve => {
}); deleteFromDb(keys, ( ) => resolve());
}; });
api.clear = function clear() { };
return new Promise(resolve => { api.clear = function clear() {
clearDb(( ) => resolve()); return new Promise(resolve => {
}); clearDb(( ) => resolve());
}; });
api.getBytesInUse = function getBytesInUse() { };
return Promise.resolve(0); api.getBytesInUse = function getBytesInUse() {
}; return Promise.resolve(0);
return true; };
}); return true;
}; };
// https://github.com/uBlockOrigin/uBlock-issues/issues/328 // https://github.com/uBlockOrigin/uBlock-issues/issues/328
// Delete cache-related entries from webext storage. // Delete cache-related entries from webext storage.
const clearWebext = function() { const clearWebext = async function() {
browser.storage.local.get('assetCacheRegistry', bin => { const bin = await webext.storage.local.get('assetCacheRegistry');
if ( if (
bin instanceof Object === false || bin instanceof Object === false ||
bin.assetCacheRegistry instanceof Object === false bin.assetCacheRegistry instanceof Object === false
) { ) {
return; 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', webext.storage.local.remove(toRemove);
'assetSourceRegistry',
'resourcesSelfie',
'selfie'
];
for ( const key in bin.assetCacheRegistry ) {
if ( bin.assetCacheRegistry.hasOwnProperty(key) ) {
toRemove.push('cache/' + key);
}
}
browser.storage.local.remove(toRemove);
});
}; };
const clearIDB = function() { const clearIDB = function() {

View File

@ -128,36 +128,37 @@ const relaxBlockingMode = (( ) => {
}; };
})(); })();
vAPI.commands.onCommand.addListener(command => { vAPI.commands.onCommand.addListener(async command => {
const µb = µBlock; const µb = µBlock;
switch ( command ) { switch ( command ) {
case 'launch-element-picker': case 'launch-element-picker':
case 'launch-element-zapper': case 'launch-element-zapper': {
vAPI.tabs.get(null, tab => { const tab = await vAPI.tabs.getCurrent();
if ( tab instanceof Object === false ) { return; } if ( tab instanceof Object === false ) { return; }
µb.mouseEventRegister.x = µb.mouseEventRegister.y = -1; µb.mouseEventRegister.x = µb.mouseEventRegister.y = -1;
µb.elementPickerExec( µb.elementPickerExec(
tab.id, tab.id,
undefined, undefined,
command === 'launch-element-zapper' command === 'launch-element-zapper'
); );
}); break;
break; }
case 'launch-logger': case 'launch-logger': {
vAPI.tabs.get(null, tab => { const tab = await vAPI.tabs.getCurrent();
const hash = tab.url.startsWith(vAPI.getURL('')) if ( tab instanceof Object === false ) { return; }
? '' const hash = tab.url.startsWith(vAPI.getURL(''))
: `#_+${tab.id}`; ? ''
µb.openNewTab({ : `#_+${tab.id}`;
url: `logger-ui.html${hash}`, µb.openNewTab({
select: true, url: `logger-ui.html${hash}`,
index: -1 select: true,
}); index: -1
}); });
break; break;
}
case 'relax-blocking-mode': case 'relax-blocking-mode':
vAPI.tabs.get(null, relaxBlockingMode); relaxBlockingMode(await vAPI.tabs.getCurrent());
break; break;
default: default:
break; break;

View File

@ -128,17 +128,16 @@ const update = function(tabId = undefined) {
// For unknown reasons, the currently active tab will not be successfully // For unknown reasons, the currently active tab will not be successfully
// looked up after closing a window. // looked up after closing a window.
vAPI.contextMenu.onMustUpdate = function(tabId = undefined) { vAPI.contextMenu.onMustUpdate = async function(tabId = undefined) {
if ( µBlock.userSettings.contextMenuEnabled === false ) { if ( µBlock.userSettings.contextMenuEnabled === false ) {
return update(); return update();
} }
if ( tabId !== undefined ) { if ( tabId !== undefined ) {
return update(tabId); return update(tabId);
} }
vAPI.tabs.get(null, tab => { const tab = await vAPI.tabs.getCurrent();
if ( tab instanceof Object === false ) { return; } if ( tab instanceof Object === false ) { return; }
update(tab.id); update(tab.id);
});
}; };
return { update: vAPI.contextMenu.onMustUpdate }; return { update: vAPI.contextMenu.onMustUpdate };

View File

@ -346,22 +346,20 @@ const popupDataFromTabId = function(tabId, tabTitle) {
return r; return r;
}; };
const popupDataFromRequest = function(request, callback) { const popupDataFromRequest = async function(request) {
if ( request.tabId ) { if ( request.tabId ) {
callback(popupDataFromTabId(request.tabId, '')); return popupDataFromTabId(request.tabId, '');
return;
} }
// Still no target tab id? Use currently selected tab. // Still no target tab id? Use currently selected tab.
vAPI.tabs.get(null, function(tab) { const tab = await vAPI.tabs.getCurrent();
var tabId = ''; let tabId = '';
var tabTitle = ''; let tabTitle = '';
if ( tab ) { if ( tab instanceof Object ) {
tabId = tab.id; tabId = tab.id;
tabTitle = tab.title || ''; tabTitle = tab.title || '';
} }
callback(popupDataFromTabId(tabId, tabTitle)); return popupDataFromTabId(tabId, tabTitle);
});
}; };
const onMessage = function(request, sender, callback) { const onMessage = function(request, sender, callback) {
@ -370,7 +368,9 @@ const onMessage = function(request, sender, callback) {
// Async // Async
switch ( request.what ) { switch ( request.what ) {
case 'getPopupData': case 'getPopupData':
popupDataFromRequest(request, callback); popupDataFromRequest(request).then(popupData => {
callback(popupData);
});
return; return;
default: default:
@ -1151,7 +1151,7 @@ vAPI.messaging.listen({
const µb = µBlock; const µb = µBlock;
const extensionOriginURL = vAPI.getURL(''); const extensionOriginURL = vAPI.getURL('');
const getLoggerData = function(details, activeTabId, callback) { const getLoggerData = async function(details, activeTabId, callback) {
const response = { const response = {
colorBlind: µb.userSettings.colorBlindFriendly, colorBlind: µb.userSettings.colorBlindFriendly,
entries: µb.logger.readAll(details.ownerId), entries: µb.logger.readAll(details.ownerId),
@ -1178,26 +1178,20 @@ const getLoggerData = function(details, activeTabId, callback) {
response.activeTabId = undefined; response.activeTabId = undefined;
} }
} }
if ( details.popupLoggerBoxChanged && browser.windows instanceof Object ) { if ( details.popupLoggerBoxChanged && vAPI.windows instanceof Object ) {
browser.tabs.query( const tabs = await vAPI.tabs.query({
{ url: vAPI.getURL('/logger-ui.html?popup=1') }, url: vAPI.getURL('/logger-ui.html?popup=1')
tabs => { });
if ( Array.isArray(tabs) === false ) { return; } if ( tabs.length !== 0 ) {
if ( tabs.length === 0 ) { return; } const win = await vAPI.windows.get(tabs[0].windowId);
browser.windows.get(tabs[0].windowId, win => { if ( win === null ) { return; }
if ( win instanceof Object === false ) { return; } vAPI.localStorage.setItem('popupLoggerBox', JSON.stringify({
vAPI.localStorage.setItem( left: win.left,
'popupLoggerBox', top: win.top,
JSON.stringify({ width: win.width,
left: win.left, height: win.height,
top: win.top, }));
width: win.width, }
height: win.height,
})
);
});
}
);
} }
callback(response); callback(response);
}; };
@ -1242,10 +1236,9 @@ const onMessage = function(request, sender, callback) {
µb.logger.ownerId !== undefined && µb.logger.ownerId !== undefined &&
µb.logger.ownerId !== request.ownerId µb.logger.ownerId !== request.ownerId
) { ) {
callback({ unavailable: true }); return callback({ unavailable: true });
return;
} }
vAPI.tabs.get(null, function(tab) { vAPI.tabs.getCurrent().then(tab => {
getLoggerData(request, tab && tab.id, callback); getLoggerData(request, tab && tab.id, callback);
}); });
return; return;

View File

@ -106,7 +106,7 @@ const onAllReady = function() {
// in already opened web pages, to remove whatever nuisance could make it to // in already opened web pages, to remove whatever nuisance could make it to
// the web pages before uBlock was ready. // the web pages before uBlock was ready.
const initializeTabs = function() { const initializeTabs = async function() {
const handleScriptResponse = function(tabId, results) { const handleScriptResponse = function(tabId, results) {
if ( if (
Array.isArray(results) === false || Array.isArray(results) === false ||
@ -128,24 +128,21 @@ const initializeTabs = function() {
} }
} }
}; };
const bindToTabs = function(tabs) { const tabs = await vAPI.tabs.query({ url: '<all_urls>' });
for ( const tab of tabs ) { for ( const tab of tabs ) {
µb.tabContextManager.commit(tab.id, tab.url); µb.tabContextManager.commit(tab.id, tab.url);
µb.bindTabToPageStats(tab.id); µb.bindTabToPageStats(tab.id);
// https://github.com/chrisaljoudi/uBlock/issues/129 // https://github.com/chrisaljoudi/uBlock/issues/129
// Find out whether content scripts need to be injected // Find out whether content scripts need to be injected
// programmatically. This may be necessary for web pages which // programmatically. This may be necessary for web pages which
// were loaded before uBO launched. // were loaded before uBO launched.
if ( /^https?:\/\//.test(tab.url) === false ) { continue; } if ( /^https?:\/\//.test(tab.url) === false ) { continue; }
vAPI.tabs.injectScript( vAPI.tabs.injectScript(
tab.id, tab.id,
{ file: 'js/scriptlets/should-inject-contentscript.js' }, { file: 'js/scriptlets/should-inject-contentscript.js' },
handleScriptResponse.bind(null, tab.id) handleScriptResponse.bind(null, tab.id)
); );
} }
};
browser.tabs.query({ url: '<all_urls>' }, bindToTabs);
}; };
/******************************************************************************/ /******************************************************************************/

View File

@ -542,19 +542,18 @@ housekeep itself.
} }
}; };
TabContext.prototype.onGC = function() { TabContext.prototype.onGC = async function() {
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; } if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; }
// https://github.com/gorhill/uBlock/issues/1713 // https://github.com/gorhill/uBlock/issues/1713
// For unknown reasons, Firefox's setTimeout() will sometimes // For unknown reasons, Firefox's setTimeout() will sometimes
// causes the callback function to be called immediately, bypassing // causes the callback function to be called immediately, bypassing
// the main event loop. For now this should prevent uBO from crashing // the main event loop. For now this should prevent uBO from crashing
// as a result of the bad setTimeout() behavior. // as a result of the bad setTimeout() behavior.
if ( this.onGCBarrier ) { if ( this.onGCBarrier ) { return; }
return;
}
this.onGCBarrier = true; this.onGCBarrier = true;
this.gcTimer = null; 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; 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); 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) { return function(tabId) {
@ -1098,11 +1098,10 @@ vAPI.tabs = new vAPI.Tabs();
const pageStoreJanitor = function() { const pageStoreJanitor = function() {
const tabIds = Array.from(µBlock.pageStores.keys()).sort(); const tabIds = Array.from(µBlock.pageStores.keys()).sort();
const checkTab = tabId => { const checkTab = async tabId => {
vAPI.tabs.get(tabId, tab => { const tab = await vAPI.tabs.get(tabId);
if ( tab ) { return; } if ( tab ) { return; }
µBlock.unbindTabFromPageStats(tabId); µBlock.unbindTabFromPageStats(tabId);
});
}; };
if ( pageStoreJanitorSampleAt >= tabIds.length ) { if ( pageStoreJanitorSampleAt >= tabIds.length ) {
pageStoreJanitorSampleAt = 0; pageStoreJanitorSampleAt = 0;

View File

@ -12,11 +12,12 @@ mkdir -p $DES
echo "*** uBlock0.firefox: copying common files" echo "*** uBlock0.firefox: copying common files"
bash ./tools/copy-common-files.sh $DES 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/manifest.json $DES/
cp platform/firefox/vapi-usercss.js $DES/js/ cp platform/firefox/webext.js $DES/js/
cp platform/firefox/vapi-webrequest.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" echo "*** uBlock0.firefox: concatenating content scripts"
cat $DES/js/vapi-usercss.js > /tmp/contentscript.js cat $DES/js/vapi-usercss.js > /tmp/contentscript.js