From eb66820728c2275470776550112c60f1741e553f Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 4 Dec 2023 15:15:08 -0500 Subject: [PATCH] ]firefox] Improve load time & behavior from suspended state Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/2969 Changes: Use browser.alarms to trigger selfie creation. Presence of a selfie improve markedly time to readiness when uBO is unsuspended. Mirror content of storage.local to (in-memory) storage.session for faster load to readiness when uBO is ususpended. --- platform/common/vapi-background.js | 119 ++++++++++++++++++++++++++--- platform/firefox/manifest.json | 1 + src/js/background.js | 2 + src/js/storage.js | 16 +++- 4 files changed, 126 insertions(+), 12 deletions(-) diff --git a/platform/common/vapi-background.js b/platform/common/vapi-background.js index b65577b4d3c99..ae581320d70a3 100644 --- a/platform/common/vapi-background.js +++ b/platform/common/vapi-background.js @@ -87,26 +87,111 @@ vAPI.app = { }, }; -/******************************************************************************/ -/******************************************************************************/ +/******************************************************************************* + * + * https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/storage/session + * + * Session (in-memory) storage is promise-based in all browsers, no need for + * a webext polyfill. However, not all browsers supports it in MV2. + * + * */ + +vAPI.sessionStorage = (( ) => { + if ( browser.storage.session instanceof Object === false ) { + return { + get() { + return Promise.resolve({}); + }, + set() { + return Promise.resolve(); + }, + remove() { + return Promise.resolve(); + }, + clear() { + return Promise.resolve(); + }, + implemented: false, + }; + } + return { + get(...args) { + return browser.storage.session.get(...args).catch(reason => { + console.log(reason); + return {}; + }); + }, + set(...args) { + return browser.storage.session.set(...args).catch(reason => { + console.log(reason); + }); + }, + remove(...args) { + return browser.storage.session.remove(...args).catch(reason => { + console.log(reason); + }); + }, + clear(...args) { + return browser.storage.session.clear(...args).catch(reason => { + console.log(reason); + }); + }, + implemented: true, + }; +})(); + +/******************************************************************************* + * + * Data written to and read from storage.local will be mirrored to in-memory + * storage.session. + * + * Data read from storage.local will be first fetched from storage.session, + * then if not available, read from storage.local. + * + * */ vAPI.storage = { - get(...args) { - return webext.storage.local.get(...args).catch(reason => { - console.log(reason); + get(key, ...args) { + if ( vAPI.sessionStorage.implemented !== true ) { + return webext.storage.local.get(key, ...args).catch(reason => { + console.log(reason); + }); + } + return vAPI.sessionStorage.get(key, ...args).then(bin => { + const size = Object.keys(bin).length; + if ( size === 1 && typeof key === 'string' && bin[key] === null ) { + return {}; + } + if ( size !== 0 ) { return bin; } + return webext.storage.local.get(key, ...args).then(bin => { + if ( bin instanceof Object === false ) { return bin; } + // Mirror empty result as null value in order to prevent + // from falling back to storage.local when there is no need. + const tomirror = Object.assign({}, bin); + if ( typeof key === 'string' && Object.keys(bin).length === 0 ) { + Object.assign(tomirror, { [key]: null }); + } + vAPI.sessionStorage.set(tomirror); + return bin; + }).catch(reason => { + console.log(reason); + }); }); }, set(...args) { + vAPI.sessionStorage.set(...args); return webext.storage.local.set(...args).catch(reason => { console.log(reason); }); }, remove(...args) { + vAPI.sessionStorage.remove(...args); return webext.storage.local.remove(...args).catch(reason => { console.log(reason); }); }, clear(...args) { + vAPI.sessionStorage.clear(...args); return webext.storage.local.clear(...args).catch(reason => { console.log(reason); }); @@ -1422,14 +1507,14 @@ vAPI.adminStorage = (( ) => { store = await webext.storage.managed.get(); } catch(ex) { } - webext.storage.local.set({ cachedManagedStorage: store || {} }); + vAPI.storage.set({ cachedManagedStorage: store || {} }); }; return { get: async function(key) { let bin; try { - bin = await webext.storage.local.get('cachedManagedStorage') || {}; + bin = await vAPI.storage.get('cachedManagedStorage') || {}; if ( Object.keys(bin).length === 0 ) { bin = await webext.storage.managed.get() || {}; } else { @@ -1477,7 +1562,7 @@ vAPI.localStorage = { start: async function() { if ( this.cache instanceof Promise ) { return this.cache; } if ( this.cache instanceof Object ) { return this.cache; } - this.cache = webext.storage.local.get('localStorage').then(bin => { + this.cache = vAPI.storage.get('localStorage').then(bin => { this.cache = bin instanceof Object && bin.localStorage instanceof Object ? bin.localStorage @@ -1487,7 +1572,7 @@ vAPI.localStorage = { }, clear: function() { this.cache = {}; - return webext.storage.local.set({ localStorage: this.cache }); + return vAPI.storage.set({ localStorage: this.cache }); }, getItem: function(key) { if ( this.cache instanceof Object === false ) { @@ -1509,7 +1594,7 @@ vAPI.localStorage = { await this.start(); if ( value === this.cache[key] ) { return; } this.cache[key] = value; - return webext.storage.local.set({ localStorage: this.cache }); + return vAPI.storage.set({ localStorage: this.cache }); }, cache: undefined, }; @@ -1740,3 +1825,17 @@ vAPI.cloud = (( ) => { })(); /******************************************************************************/ +/******************************************************************************/ + +vAPI.alarms = browser.alarms || { + create() { + }, + clear() { + }, + onAlarm: { + addListener() { + } + } +}; + +/******************************************************************************/ diff --git a/platform/firefox/manifest.json b/platform/firefox/manifest.json index ccafed27e3bc6..4ee0b566138e8 100644 --- a/platform/firefox/manifest.json +++ b/platform/firefox/manifest.json @@ -106,6 +106,7 @@ "open_in_tab": true }, "permissions": [ + "alarms", "dns", "menus", "privacy", diff --git a/src/js/background.js b/src/js/background.js index 94f61a5a8dd2c..31fc9ddf6b75b 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -144,6 +144,8 @@ if ( vAPI.webextFlavor.soup.has('firefox') ) { } const µBlock = { // jshint ignore:line + wakeupReason: '', + userSettingsDefault, userSettings: Object.assign({}, userSettingsDefault), diff --git a/src/js/storage.js b/src/js/storage.js index ffbdbe2d61fd2..ab5cb01fd21e7 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -19,8 +19,6 @@ Home: https://github.com/gorhill/uBlock */ -/* globals WebAssembly */ - 'use strict'; /******************************************************************************/ @@ -1243,6 +1241,8 @@ onBroadcast(msg => { // memory usage at selfie-load time. For some reasons. const create = async function() { + vAPI.alarms.clear('createSelfie'); + createTimer.off(); if ( µb.inMemoryFilters.length !== 0 ) { return; } if ( Object.keys(µb.availableFilterLists).length === 0 ) { return; } await Promise.all([ @@ -1316,11 +1316,23 @@ onBroadcast(msg => { io.remove(/^selfie\//); µb.selfieIsInvalid = true; } + if ( µb.wakeupReason === 'createSelfie' ) { + µb.wakeupReason = ''; + return createTimer.offon({ sec: 27 }); + } + vAPI.alarms.create('createSelfie', { + delayInMinutes: µb.hiddenSettings.selfieAfter + }); createTimer.offon({ min: µb.hiddenSettings.selfieAfter }); }; const createTimer = vAPI.defer.create(create); + vAPI.alarms.onAlarm.addListener(alarm => { + if ( alarm.name !== 'createSelfie') { return; } + µb.wakeupReason = 'createSelfie'; + }); + µb.selfieManager = { load, destroy }; }