From 0bcf04d3dda2065a6a16c86b05fa3c95c8918ae9 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 15 Apr 2020 15:55:29 -0400 Subject: [PATCH] Centralize access to browser.storage.local('localStorage') Related commit: - https://github.com/gorhill/uBlock/commit/2ac288397ca599692a4450bfdf511453bfa03bb7 Instead of having the `localStorage` data being accessed from different locations, all accesses are now funnelled to the main process. Doing so simplifies the code in auxiliary processes and also remove the need for browser.storage.local.onChanged() listeners. No longer using an onChanged() listener also happens to remove spurious warnings from the Firefox console. --- platform/chromium/vapi-background.js | 82 +++++++++++++++++++++++ platform/chromium/vapi-common.js | 97 ++++++---------------------- src/js/1p-filters.js | 34 ++++++---- 3 files changed, 123 insertions(+), 90 deletions(-) diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 461a8e797..5cc26b5cd 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -943,6 +943,7 @@ vAPI.messaging = { onFrameworkMessage: function(request, port, callback) { const sender = port && port.sender; if ( !sender ) { return; } + const fromDetails = this.ports.get(port.name) || {}; const tabId = sender.tab && sender.tab.id || undefined; const msg = request.msg; switch ( msg.what ) { @@ -981,12 +982,21 @@ vAPI.messaging = { break; } case 'extendClient': + if ( fromDetails.privileged !== true ) { break; } vAPI.tabs.executeScript(tabId, { file: '/js/vapi-client-extra.js', }).then(( ) => { callback(); }); break; + case 'localStorage': { + if ( fromDetails.privileged !== true ) { break; } + const args = msg.args || []; + vAPI.localStorage[msg.fn](...args).then(result => { + callback(result); + }); + break; + } case 'userCSS': if ( tabId === undefined ) { break; } const details = { @@ -1401,6 +1411,78 @@ vAPI.adminStorage = (( ) => { /******************************************************************************/ /******************************************************************************/ +// A localStorage-like object which should be accessible from the +// background page or auxiliary pages. +// +// https://github.com/uBlockOrigin/uBlock-issues/issues/899 +// Convert into asynchronous access API. +// +// Note: vAPI.localStorage should already be defined with the client-side +// implementation at this point, but we override with the +// background-side implementation. +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 = undefined; + try { + if ( + bin instanceof Object === false || + bin.localStorage instanceof Object === false + ) { + this.cache = {}; + const ls = self.localStorage; + for ( let i = 0; i < ls.length; i++ ) { + const key = ls.key(i); + this.cache[key] = ls.getItem(key); + } + webext.storage.local.set({ localStorage: this.cache }); + } else { + this.cache = bin.localStorage; + } + } catch(ex) { + } + if ( this.cache instanceof Object === false ) { + this.cache = {}; + } + }); + return this.cache; + }, + clear: function() { + this.cache = {}; + return webext.storage.local.set({ localStorage: this.cache }); + }, + getItem: function(key) { + if ( this.cache instanceof Object === false ) { + console.info(`localStorage.getItem('${key}') not ready`); + return null; + } + const value = this.cache[key]; + return value !== undefined ? value : null; + }, + getItemAsync: async function(key) { + await this.start(); + const value = this.cache[key]; + return value !== undefined ? value : null; + }, + removeItem: async function(key) { + this.setItem(key); + }, + setItem: async function(key, value = undefined) { + await this.start(); + if ( value === this.cache[key] ) { return; } + this.cache[key] = value; + return webext.storage.local.set({ localStorage: this.cache }); + }, + cache: undefined, +}; + +vAPI.localStorage.start(); + +/******************************************************************************/ +/******************************************************************************/ + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/sync vAPI.cloud = (( ) => { diff --git a/platform/chromium/vapi-common.js b/platform/chromium/vapi-common.js index 34b1d6d64..7fc1c96ad 100644 --- a/platform/chromium/vapi-common.js +++ b/platform/chromium/vapi-common.js @@ -216,98 +216,41 @@ vAPI.closePopup = function() { // A localStorage-like object which should be accessible from the // background page or auxiliary pages. -// This storage is optional, but it is nice to have, for a more polished user -// experience. -// -// https://github.com/gorhill/uBlock/issues/2824 -// Use a dummy localStorage if for some reasons it's not available. -// -// https://github.com/gorhill/uMatrix/issues/840 -// Always use a wrapper to seamlessly handle exceptions // // https://github.com/uBlockOrigin/uBlock-issues/issues/899 // Convert into asynchronous access API. vAPI.localStorage = { - start: function() { - if ( this.cache instanceof Promise ) { return this.cache; } - if ( this.cache instanceof Object ) { return Promise.resolve(); } - const onChanged = (changes, area) => { - if ( - area !== 'local' || - changes instanceof Object === false || - changes.localStorage instanceof Object === false - ) { - return; - } - const newValue = changes.localStorage.newValue; - this.cache = newValue instanceof Object ? newValue : {}; - }; - this.cache = new Promise(resolve => { - browser.storage.local.get('localStorage', bin => { - this.cache = undefined; - try { - if ( - bin instanceof Object === false || - bin.localStorage instanceof Object === false - ) { - this.cache = {}; - const ls = self.localStorage; - for ( let i = 0; i < ls.length; i++ ) { - const key = ls.key(i); - this.cache[key] = ls.getItem(key); - } - browser.storage.local.set({ localStorage: this.cache }); - } else { - this.cache = bin.localStorage; - } - } catch(ex) { - } - if ( this.cache instanceof Object === false ) { - this.cache = {}; - } - resolve(); - browser.storage.onChanged.addListener(onChanged); - self.addEventListener('beforeunload', ( ) => { - this.cache = undefined; - browser.storage.onChanged.removeListener(onChanged); - }); - }); - }); - return this.cache; - }, clear: function() { - this.cache = {}; - return browser.storage.local.set({ localStorage: this.cache }); - }, - getItem: function(key) { - if ( this.cache instanceof Object === false ) { - console.info(`localStorage.getItem('${key}') not ready`); - return null; - } - const value = this.cache[key]; - return value !== undefined ? value : null; + vAPI.messaging.send('vapi', { + what: 'localStorage', + fn: 'clear', + }); }, getItemAsync: function(key) { - return this.start().then(( ) => { - const value = this.cache[key]; - return value !== undefined ? value : null; + return vAPI.messaging.send('vapi', { + what: 'localStorage', + fn: 'getItemAsync', + args: [ key ], }); }, removeItem: function(key) { - this.setItem(key); - }, - setItem: function(key, value = undefined) { - return this.start().then(( ) => { - if ( value === this.cache[key] ) { return; } - this.cache[key] = value; - return browser.storage.local.set({ localStorage: this.cache }); + return vAPI.messaging.send('vapi', { + what: 'localStorage', + fn: 'removeItem', + args: [ key ], + }); + }, + setItem: function(key, value = undefined) { + return vAPI.messaging.send('vapi', { + what: 'localStorage', + fn: 'setItem', + args: [ key, value ] }); }, - cache: undefined, }; -vAPI.localStorage.start(); + diff --git a/src/js/1p-filters.js b/src/js/1p-filters.js index 1cc6cc3c6..28217dd5a 100644 --- a/src/js/1p-filters.js +++ b/src/js/1p-filters.js @@ -202,21 +202,29 @@ uDom('#userFiltersRevert').on('click', revertChanges); // https://github.com/gorhill/uBlock/issues/3706 // Save/restore cursor position // -// CoreMirror reference: https://codemirror.net/doc/manual.html#api_selection -renderUserFilters().then(( ) => { - cmEditor.clearHistory(); - return vAPI.localStorage.getItemAsync('myFiltersCursorPosition'); -}).then(line => { - if ( typeof line === 'number' ) { - cmEditor.setCursor(line, 0); - } - cmEditor.on('cursorActivity', ( ) => { - const line = cmEditor.getCursor().line; - if ( vAPI.localStorage.getItem('myFiltersCursorPosition') !== line ) { - vAPI.localStorage.setItem('myFiltersCursorPosition', line); +// CodeMirror reference: https://codemirror.net/doc/manual.html#api_selection +{ + let curline = 0; + let timer; + + renderUserFilters().then(( ) => { + cmEditor.clearHistory(); + return vAPI.localStorage.getItemAsync('myFiltersCursorPosition'); + }).then(line => { + if ( typeof line === 'number' ) { + cmEditor.setCursor(line, 0); } + cmEditor.on('cursorActivity', ( ) => { + if ( timer !== undefined ) { return; } + if ( cmEditor.getCursor().line === curline ) { return; } + timer = vAPI.setTimeout(( ) => { + timer = undefined; + curline = cmEditor.getCursor().line; + vAPI.localStorage.setItem('myFiltersCursorPosition', curline); + }, 701); + }); }); -}); +} cmEditor.on('changes', userFiltersChanged); CodeMirror.commands.save = applyChanges;