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,
"self": false,
"vAPI": false,
"webext": false,
"µBlock": false
},
"laxbreak": true,

View File

@ -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;
}
};
})();
/******************************************************************************/
/******************************************************************************/

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/punycode.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-common.js"></script>
<script src="js/vapi-background.js"></script>

View File

@ -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() {

View File

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

View File

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

View File

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

View File

@ -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: '<all_urls>' }, bindToTabs);
const tabs = await vAPI.tabs.query({ url: '<all_urls>' });
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)
);
}
};
/******************************************************************************/

View File

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

View File

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