auto-promisify browser.* methods on call
This commit is contained in:
parent
3db6662d2f
commit
0b3e027bfd
|
@ -38,14 +38,11 @@ const sync = (() => {
|
||||||
},
|
},
|
||||||
getState(drive) {
|
getState(drive) {
|
||||||
const key = `sync/state/${drive.name}`;
|
const key = `sync/state/${drive.name}`;
|
||||||
return chromeLocal.get(key)
|
return chromeLocal.getValue(key);
|
||||||
.then(obj => obj[key]);
|
|
||||||
},
|
},
|
||||||
setState(drive, state) {
|
setState(drive, state) {
|
||||||
const key = `sync/state/${drive.name}`;
|
const key = `sync/state/${drive.name}`;
|
||||||
return chromeLocal.set({
|
return chromeLocal.setValue(key, state);
|
||||||
[key]: state
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
/* global chromeLocal promisifyChrome webextLaunchWebAuthFlow FIREFOX */
|
/* global chromeLocal webextLaunchWebAuthFlow FIREFOX */
|
||||||
/* exported tokenManager */
|
/* exported tokenManager */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const tokenManager = (() => {
|
const tokenManager = (() => {
|
||||||
promisifyChrome({
|
|
||||||
'windows': ['create', 'update', 'remove'],
|
|
||||||
'tabs': ['create', 'update', 'remove']
|
|
||||||
});
|
|
||||||
const AUTH = {
|
const AUTH = {
|
||||||
dropbox: {
|
dropbox: {
|
||||||
flow: 'token',
|
flow: 'token',
|
||||||
|
@ -93,24 +89,20 @@ const tokenManager = (() => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function revokeToken(name) {
|
async function revokeToken(name) {
|
||||||
const provider = AUTH[name];
|
const provider = AUTH[name];
|
||||||
const k = buildKeys(name);
|
const k = buildKeys(name);
|
||||||
return revoke()
|
if (provider.revoke) {
|
||||||
.then(() => chromeLocal.remove(k.LIST));
|
try {
|
||||||
|
const token = await chromeLocal.getValue(k.TOKEN);
|
||||||
function revoke() {
|
if (token) {
|
||||||
if (!provider.revoke) {
|
await provider.revoke(token);
|
||||||
return Promise.resolve();
|
|
||||||
}
|
}
|
||||||
return chromeLocal.get(k.TOKEN)
|
} catch (e) {
|
||||||
.then(obj => {
|
console.error(e);
|
||||||
if (obj[k.TOKEN]) {
|
|
||||||
return provider.revoke(obj[k.TOKEN]);
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
}
|
||||||
|
await chromeLocal.remove(k.LIST);
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshToken(name, k, obj) {
|
function refreshToken(name, k, obj) {
|
||||||
|
|
|
@ -264,9 +264,9 @@
|
||||||
debounce(flushQueue, text && checkingAll ? 1000 : 0);
|
debounce(flushQueue, text && checkingAll ? 1000 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function flushQueue(lines) {
|
async function flushQueue(lines) {
|
||||||
if (!lines) {
|
if (!lines) {
|
||||||
chromeLocal.getValue('updateLog', []).then(flushQueue);
|
flushQueue(await chromeLocal.getValue('updateLog') || []);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const time = Date.now() - logLastWriteTime > 11e3 ?
|
const time = Date.now() - logLastWriteTime > 11e3 ?
|
||||||
|
|
48
edit/edit.js
48
edit/edit.js
|
@ -151,11 +151,10 @@ lazyInit();
|
||||||
if (onBoundsChanged) {
|
if (onBoundsChanged) {
|
||||||
// * movement is reported even if the window wasn't resized
|
// * movement is reported even if the window wasn't resized
|
||||||
// * fired just once when done so debounce is not needed
|
// * fired just once when done so debounce is not needed
|
||||||
onBoundsChanged.addListener(wnd => {
|
onBoundsChanged.addListener(async wnd => {
|
||||||
// getting the current window id as it may change if the user attached/detached the tab
|
// getting the current window id as it may change if the user attached/detached the tab
|
||||||
chrome.windows.getCurrent(ownWnd => {
|
const {id} = await browser.windows.getCurrent();
|
||||||
if (wnd.id === ownWnd.id) saveWindowPos();
|
if (id === wnd.id) saveWindowPos();
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
window.on('resize', () => {
|
window.on('resize', () => {
|
||||||
|
@ -325,7 +324,15 @@ lazyInit();
|
||||||
/* Stuff not needed for the main init so we can let it run at its own tempo */
|
/* Stuff not needed for the main init so we can let it run at its own tempo */
|
||||||
function lazyInit() {
|
function lazyInit() {
|
||||||
let ownTabId;
|
let ownTabId;
|
||||||
getOwnTab().then(async tab => {
|
// not using `await` so we don't block the subsequent code
|
||||||
|
getOwnTab().then(patchHistoryBack);
|
||||||
|
// no windows on android
|
||||||
|
if (chrome.windows) {
|
||||||
|
restoreWindowSize();
|
||||||
|
detectWindowedState();
|
||||||
|
chrome.tabs.onAttached.addListener(onAttached);
|
||||||
|
}
|
||||||
|
async function patchHistoryBack(tab) {
|
||||||
ownTabId = tab.id;
|
ownTabId = tab.id;
|
||||||
// use browser history back when 'back to manage' is clicked
|
// use browser history back when 'back to manage' is clicked
|
||||||
if (sessionStorageHash('manageStylesHistory').value[ownTabId] === location.href) {
|
if (sessionStorageHash('manageStylesHistory').value[ownTabId] === location.href) {
|
||||||
|
@ -336,29 +343,23 @@ function lazyInit() {
|
||||||
history.back();
|
history.back();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
|
||||||
// no windows on android
|
|
||||||
if (!chrome.windows) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// resize on 'undo close'
|
/** resize on 'undo close' */
|
||||||
|
function restoreWindowSize() {
|
||||||
const pos = tryJSONparse(sessionStorage.windowPos);
|
const pos = tryJSONparse(sessionStorage.windowPos);
|
||||||
delete sessionStorage.windowPos;
|
delete sessionStorage.windowPos;
|
||||||
if (pos && pos.left != null && chrome.windows) {
|
if (pos && pos.left != null && chrome.windows) {
|
||||||
chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, pos);
|
chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, pos);
|
||||||
}
|
}
|
||||||
// detect isWindowed
|
|
||||||
if (prefs.get('openEditInWindow') && history.length === 1) {
|
|
||||||
chrome.tabs.query({currentWindow: true}, tabs => {
|
|
||||||
if (tabs.length === 1) {
|
|
||||||
chrome.windows.getAll(windows => {
|
|
||||||
isWindowed = windows.length > 1; // not modifying the main browser window
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
async function detectWindowedState() {
|
||||||
|
isWindowed =
|
||||||
|
prefs.get('openEditInWindow') &&
|
||||||
|
history.length === 1 &&
|
||||||
|
browser.windows.getAll().length > 1 &&
|
||||||
|
(await browser.tabs.query({currentWindow: true})).length === 1;
|
||||||
}
|
}
|
||||||
// toggle openEditInWindow
|
async function onAttached(tabId, info) {
|
||||||
chrome.tabs.onAttached.addListener((tabId, info) => {
|
|
||||||
if (tabId !== ownTabId) {
|
if (tabId !== ownTabId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -366,16 +367,15 @@ function lazyInit() {
|
||||||
prefs.set('openEditInWindow', false);
|
prefs.set('openEditInWindow', false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
chrome.windows.get(info.newWindowId, {populate: true}, win => {
|
const win = await browser.windows.get(info.newWindowId, {populate: true});
|
||||||
// If there's only one tab in this window, it's been dragged to new window
|
// If there's only one tab in this window, it's been dragged to new window
|
||||||
const openEditInWindow = win.tabs.length === 1;
|
const openEditInWindow = win.tabs.length === 1;
|
||||||
if (openEditInWindow && FIREFOX) {
|
|
||||||
// FF-only because Chrome retardedly resets the size during dragging
|
// FF-only because Chrome retardedly resets the size during dragging
|
||||||
|
if (openEditInWindow && FIREFOX) {
|
||||||
chrome.windows.update(info.newWindowId, prefs.get('windowPosition'));
|
chrome.windows.update(info.newWindowId, prefs.get('windowPosition'));
|
||||||
}
|
}
|
||||||
prefs.set('openEditInWindow', openEditInWindow);
|
prefs.set('openEditInWindow', openEditInWindow);
|
||||||
});
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onRuntimeMessage(request) {
|
function onRuntimeMessage(request) {
|
||||||
|
|
|
@ -398,11 +398,13 @@
|
||||||
r.resolve(code);
|
r.resolve(code);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
port.onDisconnect.addListener(() => {
|
port.onDisconnect.addListener(async () => {
|
||||||
chrome.tabs.get(tabId, tab =>
|
const tab = await browser.tabs.get(tabId);
|
||||||
!chrome.runtime.lastError && tab.url === initialUrl
|
if (!chrome.runtime.lastError && tab.url === initialUrl) {
|
||||||
? location.reload()
|
location.reload();
|
||||||
: closeCurrentTab());
|
} else {
|
||||||
|
closeCurrentTab();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return (opts = {}) => new Promise((resolve, reject) => {
|
return (opts = {}) => new Promise((resolve, reject) => {
|
||||||
const id = performance.now();
|
const id = performance.now();
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/* exported getTab getActiveTab onTabReady stringAsRegExp openURL ignoreChromeError
|
/* exported getTab getActiveTab onTabReady stringAsRegExp openURL ignoreChromeError
|
||||||
getStyleWithNoCode tryRegExp sessionStorageHash download deepEqual
|
getStyleWithNoCode tryRegExp sessionStorageHash download deepEqual
|
||||||
closeCurrentTab capitalize CHROME_HAS_BORDER_BUG */
|
closeCurrentTab capitalize CHROME_HAS_BORDER_BUG */
|
||||||
/* global promisifyChrome */
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const CHROME = Boolean(chrome.app) && parseInt(navigator.userAgent.match(/Chrom\w+\/(\d+)|$/)[1]);
|
const CHROME = Boolean(chrome.app) && parseInt(navigator.userAgent.match(/Chrom\w+\/(\d+)|$/)[1]);
|
||||||
|
@ -92,10 +91,6 @@ if (chrome.extension.getBackgroundPage && chrome.extension.getBackgroundPage() =
|
||||||
if (cls) document.documentElement.classList.add(cls);
|
if (cls) document.documentElement.classList.add(cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
promisifyChrome({
|
|
||||||
tabs: ['create', 'get', 'getCurrent', 'move', 'query', 'update'],
|
|
||||||
windows: ['create', 'update'], // Android doesn't have chrome.windows
|
|
||||||
});
|
|
||||||
// FF57+ supports openerTabId, but not in Android
|
// FF57+ supports openerTabId, but not in Android
|
||||||
// (detecting FF57 by the feature it added, not navigator.ua which may be spoofed in about:config)
|
// (detecting FF57 by the feature it added, not navigator.ua which may be spoofed in about:config)
|
||||||
const openerTabIdSupported = (!FIREFOX || window.AbortController) && chrome.windows != null;
|
const openerTabIdSupported = (!FIREFOX || window.AbortController) && chrome.windows != null;
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
/* global promisifyChrome */
|
|
||||||
/* global deepCopy getOwnTab URLS */ // not used in content scripts
|
/* global deepCopy getOwnTab URLS */ // not used in content scripts
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-expressions
|
// eslint-disable-next-line no-unused-expressions
|
||||||
window.INJECTED !== 1 && (() => {
|
window.INJECTED !== 1 && (() => {
|
||||||
promisifyChrome({
|
|
||||||
runtime: ['sendMessage', 'getBackgroundPage'],
|
|
||||||
tabs: ['sendMessage', 'query'],
|
|
||||||
});
|
|
||||||
const TARGETS = Object.assign(Object.create(null), {
|
const TARGETS = Object.assign(Object.create(null), {
|
||||||
all: ['both', 'tab', 'extension'],
|
all: ['both', 'tab', 'extension'],
|
||||||
extension: ['both', 'extension'],
|
extension: ['both', 'extension'],
|
||||||
|
|
|
@ -5,52 +5,65 @@ self.INJECTED !== 1 && (() => {
|
||||||
|
|
||||||
//#region for content scripts and our extension pages
|
//#region for content scripts and our extension pages
|
||||||
|
|
||||||
if (!window.browser || !browser.runtime) {
|
if (!((window.browser || {}).runtime || {}).sendMessage) {
|
||||||
const createTrap = (base, parent) => {
|
/* Auto-promisifier with a fallback to direct call on signature error.
|
||||||
const target = typeof base === 'function' ? () => {} : {};
|
The fallback isn't used now since we call all synchronous methods via `chrome` */
|
||||||
target.isTrap = true;
|
const directEvents = ['addListener', 'removeListener', 'hasListener', 'hasListeners'];
|
||||||
return new Proxy(target, {
|
// generated by tools/chrome-api-no-cb.js
|
||||||
get: (target, prop) => {
|
const directMethods = {
|
||||||
if (target[prop]) return target[prop];
|
alarms: ['create'],
|
||||||
if (base[prop] && (typeof base[prop] === 'object' || typeof base[prop] === 'function')) {
|
extension: ['getBackgroundPage', 'getExtensionTabs', 'getURL', 'getViews', 'setUpdateUrlData'],
|
||||||
target[prop] = createTrap(base[prop], base);
|
i18n: ['getMessage', 'getUILanguage'],
|
||||||
return target[prop];
|
identity: ['getRedirectURL'],
|
||||||
}
|
runtime: ['connect', 'connectNative', 'getManifest', 'getURL', 'reload', 'restart'],
|
||||||
return base[prop];
|
tabs: ['connect'],
|
||||||
},
|
|
||||||
apply: (target, thisArg, args) => base.apply(parent, args)
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
window.browser = createTrap(chrome, null);
|
const promisify = function (fn, ...args) {
|
||||||
|
let res;
|
||||||
|
try {
|
||||||
|
let resolve, reject;
|
||||||
|
/* Some callbacks have 2 parameters so we're resolving as an array in that case.
|
||||||
|
For example, chrome.runtime.requestUpdateCheck and chrome.webRequest.onAuthRequired */
|
||||||
|
args.push((...results) =>
|
||||||
|
chrome.runtime.lastError ?
|
||||||
|
reject(new Error(chrome.runtime.lastError.message)) :
|
||||||
|
resolve(results.length <= 1 ? results[0] : results));
|
||||||
|
fn.apply(this, args);
|
||||||
|
res = new Promise((...rr) => ([resolve, reject] = rr));
|
||||||
|
} catch (err) {
|
||||||
|
if (!err.message.includes('No matching signature')) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
args.pop();
|
||||||
|
res = fn.apply(this, args);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
const proxify = (src, srcName, target, key) => {
|
||||||
|
let res = src[key];
|
||||||
|
if (res && typeof res === 'object') {
|
||||||
|
res = createProxy(res, key); // eslint-disable-line no-use-before-define
|
||||||
|
} else if (typeof res === 'function') {
|
||||||
|
res = (directMethods[srcName] || directEvents).includes(key)
|
||||||
|
? res.bind(src)
|
||||||
|
: promisify.bind(src, res);
|
||||||
|
}
|
||||||
|
target[key] = res;
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
const createProxy = (src, srcName) =>
|
||||||
|
new Proxy({}, {
|
||||||
|
get(target, key) {
|
||||||
|
return target[key] || proxify(src, srcName, target, key);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
window.browser = createProxy(chrome);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Promisifies the specified `chrome` methods into `browser`.
|
//#endregion
|
||||||
The definitions is an object like this: {
|
|
||||||
'storage.sync': ['get', 'set'], // if deeper than one level, combine the path via `.`
|
|
||||||
windows: ['create', 'update'], // items and sub-objects will only be created if present in `chrome`
|
|
||||||
} */
|
|
||||||
window.promisifyChrome = definitions => {
|
|
||||||
for (const [scopeName, methods] of Object.entries(definitions)) {
|
|
||||||
const path = scopeName.split('.');
|
|
||||||
const src = path.reduce((obj, p) => obj && obj[p], chrome);
|
|
||||||
if (!src) continue;
|
|
||||||
const dst = path.reduce((obj, p) => obj[p] || (obj[p] = {}), browser);
|
|
||||||
for (const name of methods) {
|
|
||||||
const fn = src[name];
|
|
||||||
if (!fn || dst[name] && !dst[name].isTrap) continue;
|
|
||||||
dst[name] = (...args) => new Promise((resolve, reject) =>
|
|
||||||
fn.call(src, ...args, (...results) =>
|
|
||||||
chrome.runtime.lastError ?
|
|
||||||
reject(chrome.runtime.lastError) :
|
|
||||||
resolve(results.length <= 1 ? results[0] : results)));
|
|
||||||
// a couple of callbacks have 2 parameters (we don't use those methods, but just in case)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!chrome.tabs) return;
|
if (!chrome.tabs) return;
|
||||||
|
|
||||||
//#endregion
|
|
||||||
//#region for our extension pages
|
//#region for our extension pages
|
||||||
|
|
||||||
for (const storage of ['localStorage', 'sessionStorage']) {
|
for (const storage of ['localStorage', 'sessionStorage']) {
|
||||||
|
@ -77,5 +90,6 @@ self.INJECTED !== 1 && (() => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
})();
|
})();
|
||||||
|
|
14
js/prefs.js
14
js/prefs.js
|
@ -1,4 +1,4 @@
|
||||||
/* global promisifyChrome msg API */
|
/* global msg API */
|
||||||
/* global deepCopy debounce */ // not used in content scripts
|
/* global deepCopy debounce */ // not used in content scripts
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -114,11 +114,6 @@ window.INJECTED !== 1 && (() => {
|
||||||
any: new Set(),
|
any: new Set(),
|
||||||
specific: {},
|
specific: {},
|
||||||
};
|
};
|
||||||
if (msg.isBg) {
|
|
||||||
promisifyChrome({
|
|
||||||
'storage.sync': ['get', 'set'],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// getPrefs may fail on browser startup in the active tab as it loads before the background script
|
// getPrefs may fail on browser startup in the active tab as it loads before the background script
|
||||||
const initializing = (msg.isBg ? readStorage() : API.getPrefs().catch(readStorage))
|
const initializing = (msg.isBg ? readStorage() : API.getPrefs().catch(readStorage))
|
||||||
.then(setAll);
|
.then(setAll);
|
||||||
|
@ -236,11 +231,8 @@ window.INJECTED !== 1 && (() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function readStorage() {
|
function readStorage() {
|
||||||
/* Using a non-promisified call since this code may also run in a content script
|
return browser.storage.sync.get(STORAGE_KEY)
|
||||||
when API.getPrefs occasionally fails during browser startup in the active tab */
|
.then(data => data[STORAGE_KEY]);
|
||||||
return new Promise(resolve =>
|
|
||||||
chrome.storage.sync.get(STORAGE_KEY, data =>
|
|
||||||
resolve(data[STORAGE_KEY])));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStorage() {
|
function updateStorage() {
|
||||||
|
|
|
@ -1,72 +1,51 @@
|
||||||
/* global loadScript tryJSONparse promisifyChrome */
|
/* global loadScript tryJSONparse */
|
||||||
/* exported chromeLocal chromeSync */
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
promisifyChrome({
|
(() => {
|
||||||
'storage.local': ['get', 'remove', 'set'],
|
/** @namespace StorageExtras */
|
||||||
'storage.sync': ['get', 'remove', 'set'],
|
const StorageExtras = {
|
||||||
});
|
async getValue(key) {
|
||||||
|
return (await this.get(key))[key];
|
||||||
const [chromeLocal, chromeSync] = (() => {
|
},
|
||||||
return [
|
async setValue(key, value) {
|
||||||
createWrapper('local'),
|
await this.set({[key]: value});
|
||||||
createWrapper('sync'),
|
},
|
||||||
];
|
async getLZValue(key) {
|
||||||
|
return (await this.getLZValues([key]))[key];
|
||||||
function createWrapper(name) {
|
},
|
||||||
const storage = browser.storage[name];
|
async getLZValues(keys = Object.values(this.LZ_KEY)) {
|
||||||
const wrapper = {
|
const [data, LZString] = await Promise.all([
|
||||||
get: storage.get.bind(storage),
|
this.get(keys),
|
||||||
set: data => storage.set(data).then(() => data),
|
this.getLZString(),
|
||||||
remove: storage.remove.bind(storage),
|
]);
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {String} key
|
|
||||||
* @param {Any} [defaultValue]
|
|
||||||
* @returns {Promise<any>}
|
|
||||||
*/
|
|
||||||
getValue: (key, defaultValue) =>
|
|
||||||
wrapper.get(
|
|
||||||
defaultValue !== undefined ?
|
|
||||||
{[key]: defaultValue} :
|
|
||||||
key
|
|
||||||
).then(data => data[key]),
|
|
||||||
|
|
||||||
setValue: (key, value) => wrapper.set({[key]: value}),
|
|
||||||
|
|
||||||
getLZValue: key => wrapper.getLZValues([key]).then(data => data[key]),
|
|
||||||
getLZValues: (keys = Object.values(wrapper.LZ_KEY)) =>
|
|
||||||
Promise.all([
|
|
||||||
wrapper.get(keys),
|
|
||||||
loadLZStringScript(),
|
|
||||||
]).then(([data = {}, LZString]) => {
|
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const value = data[key];
|
const value = data[key];
|
||||||
data[key] = value && tryJSONparse(LZString.decompressFromUTF16(value));
|
data[key] = value && tryJSONparse(LZString.decompressFromUTF16(value));
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}),
|
},
|
||||||
setLZValue: (key, value) =>
|
async setLZValue(key, value) {
|
||||||
loadLZStringScript().then(LZString =>
|
const LZString = await this.getLZString();
|
||||||
wrapper.set({
|
return this.setValue(key, LZString.compressToUTF16(JSON.stringify(value)));
|
||||||
[key]: LZString.compressToUTF16(JSON.stringify(value)),
|
},
|
||||||
})),
|
async getLZString() {
|
||||||
|
if (!window.LZString) {
|
||||||
loadLZStringScript,
|
await loadScript('/vendor/lz-string-unsafe/lz-string-unsafe.min.js');
|
||||||
|
window.LZString = window.LZString || window.LZStringUnsafe;
|
||||||
|
}
|
||||||
|
return window.LZString;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
return wrapper;
|
/** @namespace StorageExtrasSync */
|
||||||
}
|
const StorageExtrasSync = {
|
||||||
|
LZ_KEY: {
|
||||||
function loadLZStringScript() {
|
|
||||||
return window.LZString ?
|
|
||||||
Promise.resolve(window.LZString) :
|
|
||||||
loadScript('/vendor/lz-string-unsafe/lz-string-unsafe.min.js').then(() =>
|
|
||||||
(window.LZString = window.LZString || window.LZStringUnsafe));
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
chromeSync.LZ_KEY = {
|
|
||||||
csslint: 'editorCSSLintConfig',
|
csslint: 'editorCSSLintConfig',
|
||||||
stylelint: 'editorStylelintConfig',
|
stylelint: 'editorStylelintConfig',
|
||||||
usercssTemplate: 'usercssTemplate',
|
usercssTemplate: 'usercssTemplate',
|
||||||
};
|
},
|
||||||
|
};
|
||||||
|
/** @type {chrome.storage.StorageArea|StorageExtras} */
|
||||||
|
window.chromeLocal = Object.assign(browser.storage.local, StorageExtras);
|
||||||
|
/** @type {chrome.storage.StorageArea|StorageExtras|StorageExtrasSync} */
|
||||||
|
window.chromeSync = Object.assign(browser.storage.sync, StorageExtras, StorageExtrasSync);
|
||||||
|
})();
|
||||||
|
|
|
@ -257,8 +257,7 @@ async function importFromString(jsonString) {
|
||||||
// Must acquire the permission before setting the pref
|
// Must acquire the permission before setting the pref
|
||||||
if (CHROME && !chrome.declarativeContent &&
|
if (CHROME && !chrome.declarativeContent &&
|
||||||
stats.options.names.find(_ => _.name === 'styleViaXhr' && _.isValid && _.val)) {
|
stats.options.names.find(_ => _.name === 'styleViaXhr' && _.isValid && _.val)) {
|
||||||
await new Promise(resolve =>
|
await browser.permissions.request({permissions: ['declarativeContent']});
|
||||||
chrome.permissions.request({permissions: ['declarativeContent']}, resolve));
|
|
||||||
}
|
}
|
||||||
const oldStorage = await chromeSync.get();
|
const oldStorage = await chromeSync.get();
|
||||||
for (const {name, val, isValid, isPref} of stats.options.names) {
|
for (const {name, val, isValid, isPref} of stats.options.names) {
|
||||||
|
|
|
@ -88,16 +88,12 @@ function toggleSideBorders(state = prefs.get('popup.borders')) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initTabUrls() {
|
async function initTabUrls() {
|
||||||
return getActiveTab()
|
let tab = await getActiveTab();
|
||||||
.then((tab = {}) =>
|
if (FIREFOX && tab.status === 'loading' && tab.url === ABOUT_BLANK) {
|
||||||
FIREFOX && tab.status === 'loading' && tab.url === ABOUT_BLANK
|
tab = await waitForTabUrlFF(tab);
|
||||||
? waitForTabUrlFF(tab)
|
}
|
||||||
: tab)
|
let frames = await browser.webNavigation.getAllFrames({tabId: tab.id});
|
||||||
.then(tab => new Promise(resolve =>
|
|
||||||
chrome.webNavigation.getAllFrames({tabId: tab.id}, frames =>
|
|
||||||
resolve({frames, tab}))))
|
|
||||||
.then(({frames, tab}) => {
|
|
||||||
let url = tab.pendingUrl || tab.url || ''; // new Chrome uses pendingUrl while connecting
|
let url = tab.pendingUrl || tab.url || ''; // new Chrome uses pendingUrl while connecting
|
||||||
frames = sortTabFrames(frames);
|
frames = sortTabFrames(frames);
|
||||||
if (url === 'chrome://newtab/' && !URLS.chromeProtectsNTP) {
|
if (url === 'chrome://newtab/' && !URLS.chromeProtectsNTP) {
|
||||||
|
@ -109,7 +105,6 @@ function initTabUrls() {
|
||||||
}
|
}
|
||||||
tabURL = frames[0].url = url;
|
tabURL = frames[0].url = url;
|
||||||
return frames;
|
return frames;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {chrome.webNavigation.GetAllFrameResultDetails[]} frames */
|
/** @param {chrome.webNavigation.GetAllFrameResultDetails[]} frames */
|
||||||
|
|
73
tools/chrome-api-no-cb.js
Normal file
73
tools/chrome-api-no-cb.js
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
/*
|
||||||
|
Generates a list of callbackless chrome.* API methods from chromium source
|
||||||
|
to be used in polyfill.js
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const manifest = require('../manifest.json');
|
||||||
|
const fetch = require('make-fetch-happen');
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
manifest.permissions.push('extension', 'i18n', 'runtime');
|
||||||
|
const FN_NO_CB = /\bstatic (\w+) (\w+)(?![^)]*callback)\(\s*([^)]*)\)/g;
|
||||||
|
const BASE = 'https://github.com/chromium/chromium/raw/master/';
|
||||||
|
const PATHS = [
|
||||||
|
[BASE + 'extensions/common/api/', 'schema.gni'],
|
||||||
|
[BASE + 'chrome/common/extensions/api/', 'api_sources.gni'],
|
||||||
|
];
|
||||||
|
console.debug('Downloading...');
|
||||||
|
const schemas = await Promise.all(PATHS.map(([path, name]) => fetchText(path + name)));
|
||||||
|
const files = {};
|
||||||
|
schemas.forEach((text, i) => {
|
||||||
|
const path = PATHS[i][0];
|
||||||
|
text.match(/\w+\.(idl|json)/g).forEach(name => {
|
||||||
|
files[name] = path;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const resList = [];
|
||||||
|
const resObj = {};
|
||||||
|
await Promise.all(Object.entries(files).map(processApi));
|
||||||
|
Object.entries(resObj)
|
||||||
|
.sort(([a], [b]) => a < b ? -1 : a > b)
|
||||||
|
.forEach(([key, val]) => {
|
||||||
|
delete resObj[key];
|
||||||
|
resObj[key] = val;
|
||||||
|
val.sort();
|
||||||
|
});
|
||||||
|
console.log(resList.sort().join('\n'));
|
||||||
|
console.log(JSON.stringify(resObj));
|
||||||
|
|
||||||
|
async function fetchText(file) {
|
||||||
|
return (await fetch(file)).text();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processApi([file, path]) {
|
||||||
|
const [name, ext] = file.split('.');
|
||||||
|
const api = manifest.permissions.find(p =>
|
||||||
|
name === p.replace(/([A-Z])/g, s => '_' + s.toLowerCase()) ||
|
||||||
|
name === p.replace(/\./g, '_'));
|
||||||
|
if (!api) return;
|
||||||
|
const text = await fetchText(path + file);
|
||||||
|
const noCmt = text.replace(/^\s*\/\/.*$/gm, '');
|
||||||
|
if (ext === 'idl') {
|
||||||
|
const fnBlock = (noCmt.split(/\n\s*interface Functions {\s*/)[1] || '')
|
||||||
|
.split(/\n\s*interface \w+ {/)[0];
|
||||||
|
for (let m; (m = FN_NO_CB.exec(fnBlock));) {
|
||||||
|
const [, type, name, params] = m;
|
||||||
|
resList.push(`chrome.${api}.${name}(${params.replace(/\n\s*/g, ' ')}): ${type}`);
|
||||||
|
(resObj[api] || (resObj[api] = [])).push(name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const fn of JSON.parse(noCmt)[0].functions || []) {
|
||||||
|
const last = fn.parameters[fn.parameters.length - 1];
|
||||||
|
if (!fn.returns_async && (!last || last.type !== 'function')) {
|
||||||
|
resList.push(`chrome.${api}.${fn.name}(${
|
||||||
|
fn.parameters.map(p => `${p.optional ? '?' : ''}${p.name}: ${p.type}`).join(', ')
|
||||||
|
})`);
|
||||||
|
(resObj[api] || (resObj[api] = [])).push(fn.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
Loading…
Reference in New Issue
Block a user