From 71ae6efe5fc0d263719007d520a3970b7a957e4a Mon Sep 17 00:00:00 2001 From: erosman Date: Sat, 3 Jun 2023 17:53:17 +0330 Subject: [PATCH] v2.70 --- content/api-message.js | 38 ++++++++++++- content/api.js | 120 +++++++++++++++++++++-------------------- content/background.js | 6 ++- content/help.html | 3 -- 4 files changed, 103 insertions(+), 64 deletions(-) diff --git a/content/api-message.js b/content/api-message.js index e51a62d..adad654 100644 --- a/content/api-message.js +++ b/content/api-message.js @@ -1,20 +1,25 @@ import {App} from './app.js'; // ---------- API Message Handler (Side Effect) ------------ -class OnMessage { +export class OnMessage { static { // message from api.js browser.runtime.onMessage.addListener((...e) => this.process(...e)); + this.pref = {}; } - static process(message, sender) { + static async process(message, sender) { const {name, api, data: e} = message; if (!api) { return; } + const id = `_${name}`; + const pref = this.pref; + // only set if in container/incognito const storeId = sender.tab.cookieStoreId !== 'firefox-default' && sender.tab.cookieStoreId; const logError = (error) => App.log(name, `${message.api} ➜ ${error.message}`, 'error'); + let needUpdate = false; switch (api) { // ---------- internal use only (not GM API) --------- @@ -22,6 +27,35 @@ class OnMessage { return App.log(name, e.message, e.type); // ---------- GM API --------------------------------- + + // ---------- storage -------------------------------- + case 'setValue': + // e is an object of key/value + Object.entries(e).forEach(([key, value]) => { + if (pref[id].storage[key] !== value) { + pref[id].storage[key] = value; + needUpdate = true; + } + }); + + if (!needUpdate) { return; } // return if storage hasn't changed + + return browser.storage.local.set({[id]: pref[id]}); // Promise with no arguments OR reject with error message + + case 'deleteValue': + // e is an array + e.forEach(item => { + if (pref[id].storage.hasOwnProperty(item)) { + delete pref[id].storage[item]; + needUpdate = true; + } + }); + + if (!needUpdate) { return; } // return if storage hasn't changed + + return browser.storage.local.set({[id]: pref[id]}); // Promise with no arguments OR reject with error message + // ---------- /storage ------------------------------- + case 'download': // Promise with id OR reject with error message return browser.downloads.download({ diff --git a/content/api.js b/content/api.js index ef17345..a8962f1 100644 --- a/content/api.js +++ b/content/api.js @@ -329,7 +329,67 @@ browser.userScripts.onBeforeScript.addListener(script => { // ---------- GM4 Object based functions ----------------- const GM = { - // ---------- background functions --------------------- + // ---------- storage ---------------------------------- + async getValue(key, defaultValue) { + const data = await API.getData(); + return API.getStorageValue(data.storage, key, defaultValue); + }, + + // based on browser.storage.local.set() + // An object containing one or more key/value pairs to be stored in storage. + // If an item already exists, its value will be updated. + async setValue(key, value) { + if (!key) { return; } + + const obj = typeof key === 'string' ? {[key]: value} : key; // change to object + + // update sync storage + Object.entries(obj).forEach(([key, value]) => storage[key] = value); + + // update async storage + return browser.runtime.sendMessage({ + name, + api: 'setValue', + data: obj + }); + }, + + // based on browser.storage.local.remove() + // A string, or array of strings, representing the key(s) of the item(s) to be removed. + async deleteValue(key) { + if (!key) { return; } + + const arr = Array.isArray(key) ? key : [key]; // change to array + + // update sync storage + arr.forEach(item => delete storage[item]); + + // update async storage + return browser.runtime.sendMessage({ + name, + api: 'deleteValue', + data: arr + }); + }, + + async listValues() { + const data = await API.getData(); + const value = Object.keys(data.storage); + return script.export(value); + }, + + addValueChangeListener(key, callback) { + browser.storage.onChanged.addListener(API.onChanged); + valueChange[key] = callback; + return key; + }, + + removeValueChangeListener(key) { + delete valueChange[key]; + }, + // ---------- /storage --------------------------------- + + // ---------- other background functions --------------- download(url, filename) { // --- check url url = API.checkURL(url); @@ -457,63 +517,7 @@ browser.userScripts.onBeforeScript.addListener(script => { API.callUserScriptCallback(init, type, typeof response.response === 'string' ? script.export(response) : cloneInto(response, window)); }, - // ---------- /background functions -------------------- - - // ---------- storage ---------------------------------- - async getValue(key, defaultValue) { - const data = await API.getData(); - return API.getStorageValue(data.storage, key, defaultValue); - }, - - // based on browser.storage.local.set() - // An object containing one or more key/value pairs to be stored in storage. - // If an item already exists, its value will be updated. - async setValue(key, value) { - if (!key) { return; } - - const obj = typeof key === 'string' ? {[key]: value} : key; // change to object - - // update sync storage - Object.entries(obj).forEach(([key, value]) => storage[key] = value); - - // update async storage - const data = await API.getData(); - Object.entries(obj).forEach(([key, value]) => data.storage[key] = value); - return browser.storage.local.set({[id]: data}); - }, - - // based on browser.storage.local.remove() - // A string, or array of strings, representing the key(s) of the item(s) to be removed. - async deleteValue(key) { - if (!key) { return; } - - const arr = Array.isArray(key) ? key : [key]; // change to array - - // update sync storage - arr.forEach(item => delete storage[item]); - - // update async storage - const data = await API.getData(); - arr.forEach(item => delete data.storage[item]); - return browser.storage.local.set({[id]: data}); - }, - - async listValues() { - const data = await API.getData(); - const value = Object.keys(data.storage); - return script.export(value); - }, - - addValueChangeListener(key, callback) { - browser.storage.onChanged.addListener(API.onChanged); - valueChange[key] = callback; - return key; - }, - - removeValueChangeListener(key) { - delete valueChange[key]; - }, - // ---------- /storage --------------------------------- + // ---------- /other background functions -------------- // ---------- DOM functions ---------------------------- addStyle(str) { diff --git a/content/background.js b/content/background.js index 5fcfac4..0dbb0b1 100644 --- a/content/background.js +++ b/content/background.js @@ -5,10 +5,10 @@ import {Match} from './match.js'; import {Script} from './script.js'; import {Counter} from './counter.js'; import {Migrate} from './migrate.js'; +import {OnMessage} from './api-message.js'; import './menus.js' import './installer.js'; import './web-request.js'; -import './api-message.js'; // ---------- User Preference ------------------------------ await App.getPref(); @@ -25,6 +25,9 @@ class ProcessPref { await Migrate.init(pref); // migrate after storage sync check + // --- API Message + OnMessage.pref = pref; + // --- Script Counter Counter.init(pref); @@ -50,6 +53,7 @@ class ProcessPref { delete pref[item]; }); + OnMessage.pref = pref; // update API message pref Counter.pref = pref; // update Counter pref this.processPrefUpdate(changes); // apply changes Sync.set(changes, pref); // set changes to sync diff --git a/content/help.html b/content/help.html index 97103c5..ee0624b 100644 --- a/content/help.html +++ b/content/help.html @@ -2298,9 +2298,6 @@

ℹ️ Asynchronous & Synchronous Storage

-

Setting multiple asynchronous GM_setValue in succession may result in setting mishap.

- -

New API Options: getValue, setValue, deleteValue (v2.69)

Userscripts often need to get/set multiple values. Each storage operation result in costly asynchronous read/write which means resources, time, and hard disk wear (especially for SSDs). These new API options aim to alleviate the aforementioned. The new options are based on StorageArea.get(), StorageArea.set() & StorageArea.remove().