From 58620fb05150cd8cc06a2cfad64011ca34f93468 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 19 Sep 2019 16:41:44 -0400 Subject: [PATCH] Work toward modernizing code base: promisification Swathes of code have been converted to use Promises/async/await. Related commits: - https://github.com/gorhill/uBlock/commit/022951547c1f6d381bf7e9e16bd28b6feb32c9de - https://github.com/gorhill/uBlock/commit/3224d9b5cc72a3db007f4c681bcd622ecdbd923f - https://github.com/gorhill/uBlock/commit/26235d80d0d28a21063891c6095b2e03ca105cf7 - https://github.com/gorhill/uBlock/commit/0051f3b5c73eef3c18b61f620a90e0416c0fd766 - https://github.com/gorhill/uBlock/commit/eec53c01540cf842c35cbfdd1a5b029e0dea6654 - https://github.com/gorhill/uBlock/commit/915687fddb23087b64e22e63d88f4d9e8764c962 - https://github.com/gorhill/uBlock/commit/55cc0c69975482efa1bceb2d938eba27b108e386 - https://github.com/gorhill/uBlock/commit/e27328f9319132ba7d7a27de159aa077cf80ff37 --- platform/chromium/vapi-background.js | 169 ++++++--------- platform/chromium/webext.js | 297 +++++++++------------------ 2 files changed, 156 insertions(+), 310 deletions(-) diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 08c350ab8..57ab298ce 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -39,17 +39,6 @@ vAPI.cantWebsocket = browser.webRequest.ResourceType instanceof Object === false || browser.webRequest.ResourceType.WEBSOCKET !== 'websocket'; -vAPI.lastError = function() { - return browser.runtime.lastError; -}; - -// https://github.com/gorhill/uBlock/issues/875 -// https://code.google.com/p/chromium/issues/detail?id=410868#c8 -// Must not leave `lastError` unchecked. -vAPI.resetLastError = function() { - void browser.runtime.lastError; -}; - vAPI.supportsUserStylesheets = vAPI.webextFlavor.soup.has('user_stylesheet'); // The real actual webextFlavor value may not be set in stone, so listen // for possible future changes. @@ -58,8 +47,6 @@ window.addEventListener('webextFlavor', function() { vAPI.webextFlavor.soup.has('user_stylesheet'); }, { once: true }); -const noopFunc = function(){}; - /******************************************************************************/ vAPI.app = { @@ -111,20 +98,14 @@ vAPI.storage = webext.storage.local; // https://github.com/gorhill/uMatrix/issues/234 // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/privacy/network -// 2015-08-12: Wrapped Chrome API in try-catch statements. I had a fluke -// event in which it appeared Chrome 46 decided to restart uBlock (for -// unknown reasons) and again for unknown reasons the browser acted as if -// uBlock did not declare the `privacy` permission in its manifest, putting -// uBlock in a bad, non-functional state -- because call to `chrome.privacy` -// API threw an exception. - // https://github.com/gorhill/uBlock/issues/2048 // Do not mess up with existing settings if not assigning them stricter // values. vAPI.browserSettings = (( ) => { // Not all platforms support `browser.privacy`. - if ( browser.privacy instanceof Object === false ) { return; } + const bp = webext.privacy; + if ( bp instanceof Object === false ) { return; } return { // Whether the WebRTC-related privacy API is crashy is an open question @@ -181,75 +162,42 @@ vAPI.browserSettings = (( ) => { // crash. if ( this.webRTCSupported !== true ) { return; } - const bp = browser.privacy; const bpn = bp.network; - // Older version of Chromium do not support this setting, and is - // marked as "deprecated" since Chromium 48. - if ( typeof bpn.webRTCMultipleRoutesEnabled === 'object' ) { - try { - if ( setting ) { - bpn.webRTCMultipleRoutesEnabled.clear({ - scope: 'regular' - }, vAPI.resetLastError); - } else { - bpn.webRTCMultipleRoutesEnabled.set({ - value: false, - scope: 'regular' - }, vAPI.resetLastError); - } - } catch(ex) { - console.error(ex); - } - } - - // This setting became available in Chromium 48. - if ( typeof bpn.webRTCIPHandlingPolicy === 'object' ) { - try { - if ( setting ) { - bpn.webRTCIPHandlingPolicy.clear({ - scope: 'regular' - }, vAPI.resetLastError); - } else { - // https://github.com/uBlockOrigin/uAssets/issues/333#issuecomment-289426678 - // Leverage virtuous side-effect of strictest setting. - // https://github.com/gorhill/uBlock/issues/3009 - // Firefox currently works differently, use - // `default_public_interface_only` for now. - bpn.webRTCIPHandlingPolicy.set({ - value: vAPI.webextFlavor.soup.has('chromium') - ? 'disable_non_proxied_udp' - : 'default_public_interface_only', - scope: 'regular' - }, vAPI.resetLastError); - } - } catch(ex) { - console.error(ex); - } + if ( setting ) { + bpn.webRTCIPHandlingPolicy.clear({ + scope: 'regular', + }); + } else { + // https://github.com/uBlockOrigin/uAssets/issues/333#issuecomment-289426678 + // Leverage virtuous side-effect of strictest setting. + // https://github.com/gorhill/uBlock/issues/3009 + // Firefox currently works differently, use + // `default_public_interface_only` for now. + bpn.webRTCIPHandlingPolicy.set({ + value: vAPI.webextFlavor.soup.has('chromium') + ? 'disable_non_proxied_udp' + : 'default_public_interface_only', + scope: 'regular', + }); } }, set: function(details) { for ( const setting in details ) { - if ( details.hasOwnProperty(setting) === false ) { - continue; - } + if ( details.hasOwnProperty(setting) === false ) { continue; } switch ( setting ) { case 'prefetching': const enabled = !!details[setting]; - try { - if ( enabled ) { - browser.privacy.network.networkPredictionEnabled.clear({ - scope: 'regular' - }, vAPI.resetLastError); - } else { - browser.privacy.network.networkPredictionEnabled.set({ - value: false, - scope: 'regular' - }, vAPI.resetLastError); - } - } catch(ex) { - console.error(ex); + if ( enabled ) { + bp.network.networkPredictionEnabled.clear({ + scope: 'regular', + }); + } else { + bp.network.networkPredictionEnabled.set({ + value: false, + scope: 'regular', + }); } if ( vAPI.prefetching instanceof Function ) { vAPI.prefetching(enabled); @@ -257,19 +205,15 @@ vAPI.browserSettings = (( ) => { break; case 'hyperlinkAuditing': - try { - if ( !!details[setting] ) { - browser.privacy.websites.hyperlinkAuditingEnabled.clear({ - scope: 'regular' - }, vAPI.resetLastError); - } else { - browser.privacy.websites.hyperlinkAuditingEnabled.set({ - value: false, - scope: 'regular' - }, vAPI.resetLastError); - } - } catch(ex) { - console.error(ex); + if ( !!details[setting] ) { + bp.websites.hyperlinkAuditingEnabled.clear({ + scope: 'regular', + }); + } else { + bp.websites.hyperlinkAuditingEnabled.set({ + value: false, + scope: 'regular', + }); } break; @@ -595,21 +539,27 @@ vAPI.Tabs = class { vAPI.tabs.update(tabId, { url: targetURL }); } - remove(tabId) { + async remove(tabId) { tabId = toTabId(tabId); if ( tabId === 0 ) { return; } - browser.tabs.remove(tabId, vAPI.resetLastError); + try { + await webext.tabs.remove(tabId); + } + catch (reason) { + } } - reload(tabId, bypassCache = false) { + async reload(tabId, bypassCache = false) { tabId = toTabId(tabId); if ( tabId === 0 ) { return; } - - browser.tabs.reload( - tabId, - { bypassCache: bypassCache === true }, - vAPI.resetLastError - ); + try { + await webext.tabs.reload( + tabId, + { bypassCache: bypassCache === true } + ); + } + catch (reason) { + } } async select(tabId) { @@ -861,7 +811,7 @@ vAPI.messaging = { listeners: new Map(), defaultHandler: null, PRIVILEGED_URL: vAPI.getURL(''), - NOOPFUNC: noopFunc, + NOOPFUNC: function(){}, UNHANDLED: 'vAPI.messaging.notHandled', listen: function(details) { @@ -1256,10 +1206,7 @@ vAPI.contextMenu = browser.contextMenus && { _callback: null, _entries: [], _createEntry: function(entry) { - browser.contextMenus.create( - JSON.parse(JSON.stringify(entry)), - vAPI.resetLastError - ); + webext.menus.create(JSON.parse(JSON.stringify(entry))); }, onMustUpdate: function() {}, setEntries: function(entries, callback) { @@ -1270,12 +1217,12 @@ vAPI.contextMenu = browser.contextMenus && { const newEntry = entries[i]; if ( oldEntryId && newEntry ) { if ( newEntry.id !== oldEntryId ) { - browser.contextMenus.remove(oldEntryId); + webext.menus.remove(oldEntryId); this._createEntry(newEntry); this._entries[i] = newEntry.id; } } else if ( oldEntryId && !newEntry ) { - browser.contextMenus.remove(oldEntryId); + webext.menus.remove(oldEntryId); } else if ( !oldEntryId && newEntry ) { this._createEntry(newEntry); this._entries[i] = newEntry.id; @@ -1287,10 +1234,10 @@ vAPI.contextMenu = browser.contextMenus && { return; } if ( n !== 0 && callback !== null ) { - browser.contextMenus.onClicked.addListener(callback); + webext.menus.onClicked.addListener(callback); this._callback = callback; } else if ( n === 0 && this._callback !== null ) { - browser.contextMenus.onClicked.removeListener(this._callback); + webext.menus.onClicked.removeListener(this._callback); this._callback = null; } } diff --git a/platform/chromium/webext.js b/platform/chromium/webext.js index b5130e421..5f61dcf1d 100644 --- a/platform/chromium/webext.js +++ b/platform/chromium/webext.js @@ -24,140 +24,108 @@ // `webext` is a promisified api of `chrome`. Entries are added as // the promisification of uBO progress. -const webext = { // jshint ignore:line +const webext = (( ) => { // jshint ignore:line +// >>>>> start of private scope + +const promisifyNoFail = function(thisArg, fnName, outFn = r => r) { + const fn = thisArg[fnName]; + return function() { + return new Promise(resolve => { + fn.call(thisArg, ...arguments, function() { + void chrome.runtime.lastError; + resolve(outFn(...arguments)); + }); + }); + }; +}; + +const promisify = function(thisArg, fnName) { + const fn = thisArg[fnName]; + return function() { + return new Promise((resolve, reject) => { + fn.call(thisArg, ...arguments, function() { + const lastError = chrome.runtime.lastError; + if ( lastError instanceof Object ) { + return reject(lastError.message); + } + resolve(...arguments); + }); + }); + }; +}; + +const webext = { + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/menus + menus: { + create: function() { + return chrome.contextMenus.create(...arguments, ( ) => { + void chrome.runtime.lastError; + }); + }, + onClicked: chrome.contextMenus.onClicked, + remove: promisifyNoFail(chrome.contextMenus, 'remove'), + }, + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/privacy + privacy: { + }, // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage storage: { // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/local local: { - clear: function() { - return new Promise((resolve, reject) => { - chrome.storage.local.clear(( ) => { - const lastError = chrome.runtime.lastError; - if ( lastError instanceof Object ) { - return reject(lastError.message); - } - 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.message); - } - 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.message); - } - 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.message); - } - 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.message); - } - resolve(); - }); - }); - }, + clear: promisify(chrome.storage.local, 'clear'), + get: promisify(chrome.storage.local, 'get'), + getBytesInUse: promisify(chrome.storage.local, 'getBytesInUse'), + remove: promisify(chrome.storage.local, 'remove'), + set: promisify(chrome.storage.local, 'set'), }, }, // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs tabs: { - get: function() { - return new Promise(resolve => { - chrome.tabs.get(...arguments, tab => { - void chrome.runtime.lastError; - resolve(tab instanceof Object ? tab : null); - }); - }); - }, - executeScript: function() { - return new Promise(resolve => { - chrome.tabs.executeScript(...arguments, result => { - void chrome.runtime.lastError; - resolve(result); - }); - }); - }, - insertCSS: function() { - return new Promise(resolve => { - chrome.tabs.insertCSS(...arguments, ( ) => { - void chrome.runtime.lastError; - resolve(); - }); - }); - }, - 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); - }); - }); - }, + get: promisifyNoFail(chrome.tabs, 'get', tab => tab instanceof Object ? tab : null), + executeScript: promisifyNoFail(chrome.tabs, 'executeScript'), + insertCSS: promisifyNoFail(chrome.tabs, 'insertCSS'), + query: promisifyNoFail(chrome.tabs, 'query', tabs => Array.isArray(tabs) ? tabs : []), + reload: promisifyNoFail(chrome.tabs, 'reload'), + remove: promisifyNoFail(chrome.tabs, 'remove'), + update: promisifyNoFail(chrome.tabs, 'update', tab => tab instanceof Object ? tab : null), }, // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/windows windows: { - get: function() { - return new Promise(resolve => { - chrome.windows.get(...arguments, win => { - void chrome.runtime.lastError; - resolve(win instanceof Object ? win : null); - }); - }); - }, - create: function() { - return new Promise(resolve => { - chrome.windows.create(...arguments, win => { - void chrome.runtime.lastError; - resolve(win instanceof Object ? win : null); - }); - }); - }, - update: function() { - return new Promise(resolve => { - chrome.windows.update(...arguments, win => { - void chrome.runtime.lastError; - resolve(win instanceof Object ? win : null); - }); - }); - }, + get: promisifyNoFail(chrome.windows, 'get', win => win instanceof Object ? win : null), + create: promisifyNoFail(chrome.windows, 'create', win => win instanceof Object ? win : null), + update: promisifyNoFail(chrome.windows, 'update', win => win instanceof Object ? win : null), }, }; +// browser.privacy entries +{ + const settings = [ + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/privacy/network + [ 'network', 'networkPredictionEnabled' ], + [ 'network', 'webRTCIPHandlingPolicy' ], + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/privacy/websites + [ 'websites', 'hyperlinkAuditingEnabled' ], + ]; + for ( const [ category, setting ] of settings ) { + let categoryEntry = webext.privacy[category]; + if ( categoryEntry instanceof Object === false ) { + categoryEntry = webext.privacy[category] = {}; + } + const settingEntry = categoryEntry[setting] = {}; + const thisArg = chrome.privacy[category][setting]; + settingEntry.clear = promisifyNoFail(thisArg, 'clear'); + settingEntry.get = promisifyNoFail(thisArg, 'get'); + settingEntry.set = promisifyNoFail(thisArg, 'set'); + } +} + +// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/managed +if ( chrome.storage.managed instanceof Object ) { + webext.storage.managed = { + get: promisify(chrome.storage.managed, 'get'), + }; +} + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/sync if ( chrome.storage.sync instanceof Object ) { webext.storage.sync = { @@ -167,89 +135,20 @@ if ( chrome.storage.sync instanceof Object ) { MAX_WRITE_OPERATIONS_PER_HOUR: chrome.storage.sync.MAX_WRITE_OPERATIONS_PER_HOUR, MAX_WRITE_OPERATIONS_PER_MINUTE: chrome.storage.sync.MAX_WRITE_OPERATIONS_PER_MINUTE, - clear: function() { - return new Promise((resolve, reject) => { - chrome.storage.sync.clear(( ) => { - const lastError = chrome.runtime.lastError; - if ( lastError instanceof Object ) { - return reject(lastError.message); - } - resolve(); - }); - }); - }, - get: function() { - return new Promise((resolve, reject) => { - chrome.storage.sync.get(...arguments, result => { - const lastError = chrome.runtime.lastError; - if ( lastError instanceof Object ) { - return reject(lastError.message); - } - resolve(result); - }); - }); - }, - getBytesInUse: function() { - return new Promise((resolve, reject) => { - chrome.storage.sync.getBytesInUse(...arguments, result => { - const lastError = chrome.runtime.lastError; - if ( lastError instanceof Object ) { - return reject(lastError.message); - } - resolve(result); - }); - }); - }, - remove: function() { - return new Promise((resolve, reject) => { - chrome.storage.sync.remove(...arguments, ( ) => { - const lastError = chrome.runtime.lastError; - if ( lastError instanceof Object ) { - return reject(lastError.message); - } - resolve(); - }); - }); - }, - set: function() { - return new Promise((resolve, reject) => { - chrome.storage.sync.set(...arguments, ( ) => { - const lastError = chrome.runtime.lastError; - if ( lastError instanceof Object ) { - return reject(lastError.message); - } - resolve(); - }); - }); - }, + clear: promisify(chrome.storage.sync, 'clear'), + get: promisify(chrome.storage.sync, 'get'), + getBytesInUse: promisify(chrome.storage.sync, 'getBytesInUse'), + remove: promisify(chrome.storage.sync, 'remove'), + set: promisify(chrome.storage.sync, 'set'), }; } // https://bugs.chromium.org/p/chromium/issues/detail?id=608854 if ( chrome.tabs.removeCSS instanceof Function ) { - webext.tabs.removeCSS = function() { - return new Promise(resolve => { - chrome.tabs.removeCSS(...arguments, ( ) => { - void chrome.runtime.lastError; - resolve(); - }); - }); - }; + webext.tabs.removeCSS = promisifyNoFail(chrome.tabs, 'removeCSS'); } -// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/managed -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.message); - } - resolve(result); - }); - }); - }, - }; -} +return webext; + +// <<<<< end of private scope +})();