From 7f368d46ea06164da025c1ac4ed9a65ad23b25ef Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Wed, 12 May 2021 16:00:09 +0200 Subject: stop using js modules --- common/browser.js | 27 +++++++ common/browser.mjs | 18 ----- common/connection_types.js | 25 ++++++ common/connection_types.mjs | 21 ----- common/is_background.mjs | 17 ---- common/lock.js | 61 ++++++++++++++ common/lock.mjs | 55 ------------- common/once.js | 44 +++++++++++ common/once.mjs | 42 ---------- common/storage_client.js | 189 ++++++++++++++++++++++++++++++++++++++++++++ common/storage_client.mjs | 186 ------------------------------------------- common/stored_types.js | 44 +++++++++++ common/stored_types.mjs | 38 --------- 13 files changed, 390 insertions(+), 377 deletions(-) create mode 100644 common/browser.js delete mode 100644 common/browser.mjs create mode 100644 common/connection_types.js delete mode 100644 common/connection_types.mjs delete mode 100644 common/is_background.mjs create mode 100644 common/lock.js delete mode 100644 common/lock.mjs create mode 100644 common/once.js delete mode 100644 common/once.mjs create mode 100644 common/storage_client.js delete mode 100644 common/storage_client.mjs create mode 100644 common/stored_types.js delete mode 100644 common/stored_types.mjs (limited to 'common') diff --git a/common/browser.js b/common/browser.js new file mode 100644 index 0000000..1d9edda --- /dev/null +++ b/common/browser.js @@ -0,0 +1,27 @@ +/** +* Myext WebExtension API access normalization +* +* Copyright (C) 2021 Wojtek Kosior +* +* Dual-licensed under: +* - 0BSD license +* - GPLv3 or (at your option) any later version +*/ + +"use strict"; + +/* + * This module normalizes access to WebExtension apis between + * chrome-based and firefox-based browsers. + */ + +(() => { + if (window.browser === undefined) { + window.browser = window.chrome; + window.is_chrome = true; + window.is_mozilla = false; + } else { + window.is_chrome = false; + window.is_mozilla = true; + } +})(); diff --git a/common/browser.mjs b/common/browser.mjs deleted file mode 100644 index 0d1b233..0000000 --- a/common/browser.mjs +++ /dev/null @@ -1,18 +0,0 @@ -/** -* Myext WebExtension API access normalization -* -* Copyright (C) 2021 Wojtek Kosior -* -* Dual-licensed under: -* - 0BSD license -* - GPLv3 or (at your option) any later version -*/ - -"use strict"; - -/* - * This module normalizes access to WebExtension apis between - * chrome-based and firefox-based browsers. - */ - -export default (window.browser === undefined) ? chrome : browser; diff --git a/common/connection_types.js b/common/connection_types.js new file mode 100644 index 0000000..a01a777 --- /dev/null +++ b/common/connection_types.js @@ -0,0 +1,25 @@ +/** +* Myext background scripts message connection types "enum" +* +* Copyright (C) 2021 Wojtek Kosior +* +* Dual-licensed under: +* - 0BSD license +* - GPLv3 or (at your option) any later version +*/ + +"use strict"; + +/* + * Those need to be strings so they can be used as 'name' parameter + * to browser.runtime.connect() + */ + +(() => { + const CONNECTION_TYPE = { + REMOTE_STORAGE : "0", + PAGE_ACTIONS : "1" + }; + + window.CONNECTION_TYPE = CONNECTION_TYPE; +})(); diff --git a/common/connection_types.mjs b/common/connection_types.mjs deleted file mode 100644 index 12d6de3..0000000 --- a/common/connection_types.mjs +++ /dev/null @@ -1,21 +0,0 @@ -/** -* Myext background scripts message connection types "enum" -* -* Copyright (C) 2021 Wojtek Kosior -* -* Dual-licensed under: -* - 0BSD license -* - GPLv3 or (at your option) any later version -*/ - -/* - * Those need to be strings so they can be used as 'name' parameter - * to browser.runtime.connect() - */ - -const CONNECTION_TYPE = { - REMOTE_STORAGE : "0", - PAGE_ACTIONS : "1" -}; - -export default CONNECTION_TYPE; diff --git a/common/is_background.mjs b/common/is_background.mjs deleted file mode 100644 index ef728a7..0000000 --- a/common/is_background.mjs +++ /dev/null @@ -1,17 +0,0 @@ -/** -* Myext programmatic check of where the script is being run -* -* Copyright (C) 2021 Wojtek Kosior -* -* Dual-licensed under: -* - 0BSD license -* - GPLv3 or (at your option) any later version -*/ - -/* This needs to be changed if we ever modify the html file path. */ - -export default function is_background() -{ - return window.location.protocol === "moz-extension:" && - window.location.pathname === "/background/background.html"; -} diff --git a/common/lock.js b/common/lock.js new file mode 100644 index 0000000..107287f --- /dev/null +++ b/common/lock.js @@ -0,0 +1,61 @@ +/** +* Myext lock (aka binary semaphore aka mutex) +* +* Copyright (C) 2021 Wojtek Kosior +* +* Dual-licensed under: +* - 0BSD license +* - GPLv3 or (at your option) any later version +*/ + +/* + * Javascript runs single-threaded, with an event loop. Because of that, + * explicit synchronization is often not needed. An exception is when we use + * an API function that must wait. Ajax is an example. Callback passed to ajax + * call doesn't get called immediately, but after some time. In the meantime + * some other piece of code might get to execute and modify some variables. + * Access to WebExtension local storage is another situation where this problem + * can occur. + * + * This is a solution. A lock object, that can be used to delay execution of + * some code until other code finishes its critical work. Locking is wrapped + * in a promise. + */ + +"use strict"; + +(() => { + function make_lock() { + return {free: true, queue: []}; + } + + function _lock(lock, cb) { + if (lock.free) { + lock.free = false; + setTimeout(cb); + } else { + lock.queue.push(cb); + } + } + + function lock(lock) { + return new Promise((resolve, reject) => _lock(lock, resolve)); + } + + function unlock(lock) { + if (lock.free) + throw new Exception("Attempting to release a free lock"); + + if (lock.queue.length === 0) { + lock.free = true; + } else { + let cb = lock.queue[0]; + lock.queue.splice(0, 1); + setTimeout(cb); + } + } + + window.make_lock = make_lock; + window.lock = lock; + window.unlock = unlock; +})(); diff --git a/common/lock.mjs b/common/lock.mjs deleted file mode 100644 index 596dd9c..0000000 --- a/common/lock.mjs +++ /dev/null @@ -1,55 +0,0 @@ -/** -* Myext lock (aka binary semaphore aka mutex) -* -* Copyright (C) 2021 Wojtek Kosior -* -* Dual-licensed under: -* - 0BSD license -* - GPLv3 or (at your option) any later version -*/ - -/* - * Javascript runs single-threaded, with an event loop. Because of that, - * explicit synchronization is often not needed. An exception is when we use - * an API function that must wait. Ajax is an example. Callback passed to ajax - * call doesn't get called immediately, but after some time. In the meantime - * some other piece of code might get to execute and modify some variables. - * Access to WebExtension local storage is another situation where this problem - * can occur. - * - * This is a solution. A lock object, that can be used to delay execution of - * some code until other code finishes its critical work. Locking is wrapped - * in a promise. - */ - -"use strict"; - -export function make_lock() { - return {free: true, queue: []}; -} - -function _lock(lock, cb) { - if (lock.free) { - lock.free = false; - setTimeout(cb); - } else { - lock.queue.push(cb); - } -} - -export function lock(lock) { - return new Promise((resolve, reject) => _lock(lock, resolve)); -} - -export function unlock(lock) { - if (lock.free) - throw new Exception("Attempting to release a free lock"); - - if (lock.queue.length === 0) { - lock.free = true; - } else { - let cb = lock.queue[0]; - lock.queue.splice(0, 1); - setTimeout(cb); - } -} diff --git a/common/once.js b/common/once.js new file mode 100644 index 0000000..1842a47 --- /dev/null +++ b/common/once.js @@ -0,0 +1,44 @@ +/** +* Myext feature initialization promise +* +* Copyright (C) 2021 Wojtek Kosior +* +* Dual-licensed under: +* - 0BSD license +* - GPLv3 or (at your option) any later version +*/ + +"use strict"; + +/* + * This module provides an easy way to wrap an async function into a promise + * so that it only gets executed once. + */ + +(() => { + async function assign_result(state, result_producer) + { + state.result = await result_producer(); + state.ready = true; + for (let cb of state.waiting) + setTimeout(cb, 0, state.result); + state.waiting = undefined; + } + + async function get_result(state) + { + if (state.ready) + return state.result; + + return new Promise((resolve, reject) => state.waiting.push(resolve)); + } + + function make_once(result_producer) + { + let state = {waiting : [], ready : false, result : undefined}; + assign_result(state, result_producer); + return () => get_result(state); + } + + window.make_once = make_once; +})(); diff --git a/common/once.mjs b/common/once.mjs deleted file mode 100644 index 0f76366..0000000 --- a/common/once.mjs +++ /dev/null @@ -1,42 +0,0 @@ -/** -* Myext feature initialization promise -* -* Copyright (C) 2021 Wojtek Kosior -* -* Dual-licensed under: -* - 0BSD license -* - GPLv3 or (at your option) any later version -*/ - -"use strict"; - -/* - * This module provides an easy way to wrap an async function into a promise - * so that it only gets executed once. - */ - -async function assign_result(state, result_producer) -{ - state.result = await result_producer(); - state.ready = true; - for (let cb of state.waiting) - setTimeout(cb, 0, state.result); - state.waiting = undefined; -} - -async function get_result(state) -{ - if (state.ready) - return state.result; - - return new Promise((resolve, reject) => state.waiting.push(resolve)); -} - -export function make_once(result_producer) -{ - let state = {waiting : [], ready : false, result : undefined}; - assign_result(state, result_producer); - return () => get_result(state); -} - -export default make_once; diff --git a/common/storage_client.js b/common/storage_client.js new file mode 100644 index 0000000..39ece44 --- /dev/null +++ b/common/storage_client.js @@ -0,0 +1,189 @@ +/** +* Myext storage through connection (client side) +* +* Copyright (C) 2021 Wojtek Kosior +* +* Dual-licensed under: +* - 0BSD license +* - GPLv3 or (at your option) any later version +*/ + +"use strict"; + +(() => { + const CONNECTION_TYPE = window.CONNECTION_TYPE; + const TYPE_PREFIX = window.TYPE_PREFIX; + const list_prefixes = window.list_prefixes; + const make_once = window.make_once; + const browser = window.browser; + + var call_id = 0; + var port; + var calls_waiting = new Map(); + + function set_call_callback(resolve, reject, func, args) + { + port.postMessage([call_id, func, args]); + calls_waiting.set(call_id++, [resolve, reject]); + } + + async function remote_call(func, args) + { + return new Promise((resolve, reject) => + set_call_callback(resolve, reject, func, args)); + } + + function handle_message(message) + { + let callbacks = calls_waiting.get(message.call_id); + if (callbacks === undefined) { + handle_change(message); + return; + } + + let [resolve, reject] = callbacks; + calls_waiting.delete(message.call_id); + if (message.error !== undefined) + setTimeout(reject, 0, message.error); + else + setTimeout(resolve, 0, message.result); + } + + function list(name, prefix) + { + return {prefix, name, listeners : new Set()}; + } + + var scripts = list("scripts", TYPE_PREFIX.SCRIPT); + var bundles = list("bundles", TYPE_PREFIX.BUNDLE); + var pages = list("pages", TYPE_PREFIX.PAGE); + + const list_by_prefix = { + [TYPE_PREFIX.SCRIPT] : scripts, + [TYPE_PREFIX.BUNDLE] : bundles, + [TYPE_PREFIX.PAGE] : pages + }; + + var resolve_init; + + function handle_first_message(message) + { + for (let prefix of Object.keys(message)) + list_by_prefix[prefix].map = new Map(message[prefix]); + + port.onMessage.removeListener(handle_first_message); + port.onMessage.addListener(handle_message); + + resolve_init(); + } + + function handle_change(change) + { + let list = list_by_prefix[change.prefix]; + + if (change.new_val === undefined) + list.map.delete(change.item); + else + list.map.set(change.item, change.new_val); + + for (let listener_callback of list.listeners) + listener_callback(change); + } + + var exports = {}; + + function start_connection(resolve) + { + resolve_init = resolve; + port = browser.runtime.connect({name : CONNECTION_TYPE.REMOTE_STORAGE}); + port.onMessage.addListener(handle_first_message); + } + + async function init() { + await new Promise((resolve, reject) => start_connection(resolve)); + return exports; + } + + for (let call_name of ["set", "remove", "replace", "clear"]) + exports [call_name] = (...args) => remote_call(call_name, args); + + // TODO: Much of the code below is copy-pasted from /background/storage.mjs. + // This should later be refactored into a separate module + // to avoid duplication. + + /* + * Facilitate listening to changes + */ + + exports.add_change_listener = function (cb, prefixes=list_prefixes) + { + if (typeof(prefixes) === "string") + prefixes = [prefixes]; + + for (let prefix of prefixes) + list_by_prefix[prefix].listeners.add(cb); + } + + exports.remove_change_listener = function (cb, prefixes=list_prefixes) + { + if (typeof(prefixes) === "string") + prefixes = [prefixes]; + + for (let prefix of prefixes) + list_by_prefix[prefix].listeners.delete(cb); + } + + /* Prepare some hepler functions to get elements of a list */ + + function list_items_it(list, with_values=false) + { + return with_values ? list.map.entries() : list.map.keys(); + } + + function list_entries_it(list) + { + return list_items_it(list, true); + } + + function list_items(list, with_values=false) + { + let array = []; + + for (let item of list_items_it(list, with_values)) + array.push(item); + + return array; + } + + function list_entries(list) + { + return list_items(list, true); + } + + exports.get = function (prefix, item) + { + return list_by_prefix[prefix].map.get(item); + } + + exports.get_all_names = function (prefix) + { + return list_items(list_by_prefix[prefix]); + } + + exports.get_all_names_it = function (prefix) + { + return list_items_it(list_by_prefix[prefix]); + } + + exports.get_all = function (prefix) + { + return list_entries(list_by_prefix[prefix]); + } + + exports.get_all_it = function (prefix) + { + return list_entries_it(list_by_prefix[prefix]); + } + + window.get_storage = make_once(init); +})(); diff --git a/common/storage_client.mjs b/common/storage_client.mjs deleted file mode 100644 index 8260ad7..0000000 --- a/common/storage_client.mjs +++ /dev/null @@ -1,186 +0,0 @@ -/** -* Myext storage through connection (client side) -* -* Copyright (C) 2021 Wojtek Kosior -* -* Dual-licensed under: -* - 0BSD license -* - GPLv3 or (at your option) any later version -*/ - -"use strict"; - -import CONNECTION_TYPE from './connection_types.mjs'; -import {TYPE_PREFIX, list_prefixes} from '/common/stored_types.mjs'; -import make_once from './once.mjs'; -import browser from '/common/browser.mjs'; - -var call_id = 0; -var port; -var calls_waiting = new Map(); - -function set_call_callback(resolve, reject, func, args) -{ - port.postMessage([call_id, func, args]); - calls_waiting.set(call_id++, [resolve, reject]); -} - -async function remote_call(func, args) -{ - return new Promise((resolve, reject) => - set_call_callback(resolve, reject, func, args)); -} - -function handle_message(message) -{ - let callbacks = calls_waiting.get(message.call_id); - if (callbacks === undefined) { - handle_change(message); - return; - } - - let [resolve, reject] = callbacks; - calls_waiting.delete(message.call_id); - if (message.error !== undefined) - setTimeout(reject, 0, message.error); - else - setTimeout(resolve, 0, message.result); -} - -function list(name, prefix) -{ - return {prefix, name, listeners : new Set()}; -} - -var scripts = list("scripts", TYPE_PREFIX.SCRIPT); -var bundles = list("bundles", TYPE_PREFIX.BUNDLE); -var pages = list("pages", TYPE_PREFIX.PAGE); - -const list_by_prefix = { - [TYPE_PREFIX.SCRIPT] : scripts, - [TYPE_PREFIX.BUNDLE] : bundles, - [TYPE_PREFIX.PAGE] : pages -}; - -var resolve_init; - -function handle_first_message(message) -{ - for (let prefix of Object.keys(message)) - list_by_prefix[prefix].map = new Map(message[prefix]); - - port.onMessage.removeListener(handle_first_message); - port.onMessage.addListener(handle_message); - - resolve_init(); -} - -function handle_change(change) -{ - let list = list_by_prefix[change.prefix]; - - if (change.new_val === undefined) - list.map.delete(change.item); - else - list.map.set(change.item, change.new_val); - - for (let listener_callback of list.listeners) - listener_callback(change); -} - -var exports = {}; - -function start_connection(resolve) -{ - resolve_init = resolve; - port = browser.runtime.connect({name : CONNECTION_TYPE.REMOTE_STORAGE}); - port.onMessage.addListener(handle_first_message); -} - -async function init() { - await new Promise((resolve, reject) => start_connection(resolve)); - return exports; -} - -for (let call_name of ["set", "remove", "replace", "clear"]) - exports [call_name] = (...args) => remote_call(call_name, args); - -// TODO: Much of the code below is copy-pasted from /background/storage.mjs. -// This should later be refactored into a separate module -// to avoid duplication. - -/* - * Facilitate listening to changes - */ - -exports.add_change_listener = function (cb, prefixes=list_prefixes) -{ - if (typeof(prefixes) === "string") - prefixes = [prefixes]; - - for (let prefix of prefixes) - list_by_prefix[prefix].listeners.add(cb); -} - -exports.remove_change_listener = function (cb, prefixes=list_prefixes) -{ - if (typeof(prefixes) === "string") - prefixes = [prefixes]; - - for (let prefix of prefixes) - list_by_prefix[prefix].listeners.delete(cb); -} - -/* Prepare some hepler functions to get elements of a list */ - -function list_items_it(list, with_values=false) -{ - return with_values ? list.map.entries() : list.map.keys(); -} - -function list_entries_it(list) -{ - return list_items_it(list, true); -} - -function list_items(list, with_values=false) -{ - let array = []; - - for (let item of list_items_it(list, with_values)) - array.push(item); - - return array; -} - -function list_entries(list) -{ - return list_items(list, true); -} - -exports.get = function (prefix, item) -{ - return list_by_prefix[prefix].map.get(item); -} - -exports.get_all_names = function (prefix) -{ - return list_items(list_by_prefix[prefix]); -} - -exports.get_all_names_it = function (prefix) -{ - return list_items_it(list_by_prefix[prefix]); -} - -exports.get_all = function (prefix) -{ - return list_entries(list_by_prefix[prefix]); -} - -exports.get_all_it = function (prefix) -{ - return list_entries_it(list_by_prefix[prefix]); -} - -export default make_once(init); diff --git a/common/stored_types.js b/common/stored_types.js new file mode 100644 index 0000000..de0ec71 --- /dev/null +++ b/common/stored_types.js @@ -0,0 +1,44 @@ +/** + * Myext stored item types "enum" + * + * Copyright (C) 2021 Wojtek Kosior + * + * Dual-licensed under: + * - 0BSD license + * - GPLv3 or (at your option) any later version + */ + +/* + * Key for item that is stored in quantity (script, page) is constructed by + * prepending its name with first letter of its list name. However, we also + * need to store some items that don't belong to any list. Let's call them + * persisted variables. In such case item's key is its "name" prepended with + * an underscore. + */ + +"use strict"; + +(() => { + const TYPE_PREFIX = { + PAGE : "p", + BUNDLE : "b", + SCRIPT : "s", + VAR : "_" + }; + + const TYPE_NAME = { + [TYPE_PREFIX.PAGE] : "page", + [TYPE_PREFIX.BUNDLE] : "bundle", + [TYPE_PREFIX.SCRIPT] : "script" + } + + const list_prefixes = [ + TYPE_PREFIX.PAGE, + TYPE_PREFIX.BUNDLE, + TYPE_PREFIX.SCRIPT + ]; + + window.TYPE_PREFIX = TYPE_PREFIX; + window.TYPE_NAME = TYPE_NAME; + window.list_prefixes = list_prefixes; +})(); diff --git a/common/stored_types.mjs b/common/stored_types.mjs deleted file mode 100644 index 8545d44..0000000 --- a/common/stored_types.mjs +++ /dev/null @@ -1,38 +0,0 @@ -/** -* Myext stored item types "enum" -* -* Copyright (C) 2021 Wojtek Kosior -* -* Dual-licensed under: -* - 0BSD license -* - GPLv3 or (at your option) any later version -*/ - -/* - * Key for item that is stored in quantity (script, page) is constructed by - * prepending its name with first letter of its list name. However, we also - * need to store some items that don't belong to any list. Let's call them - * persisted variables. In such case item's key is its "name" prepended with - * an underscore. - */ - -const TYPE_PREFIX = { - PAGE : "p", - BUNDLE : "b", - SCRIPT : "s", - VAR : "_" -}; - -const TYPE_NAME = { - [TYPE_PREFIX.PAGE] : "page", - [TYPE_PREFIX.BUNDLE] : "bundle", - [TYPE_PREFIX.SCRIPT] : "script" -} - -const list_prefixes = [ - TYPE_PREFIX.PAGE, - TYPE_PREFIX.BUNDLE, - TYPE_PREFIX.SCRIPT -]; - -export {TYPE_PREFIX, TYPE_NAME, list_prefixes}; -- cgit v1.2.3