auto-promisify browser.* methods on call
This commit is contained in:
parent
3db6662d2f
commit
0b3e027bfd
|
@ -38,14 +38,11 @@ const sync = (() => {
|
|||
},
|
||||
getState(drive) {
|
||||
const key = `sync/state/${drive.name}`;
|
||||
return chromeLocal.get(key)
|
||||
.then(obj => obj[key]);
|
||||
return chromeLocal.getValue(key);
|
||||
},
|
||||
setState(drive, state) {
|
||||
const key = `sync/state/${drive.name}`;
|
||||
return chromeLocal.set({
|
||||
[key]: state
|
||||
});
|
||||
return chromeLocal.setValue(key, state);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
/* global chromeLocal promisifyChrome webextLaunchWebAuthFlow FIREFOX */
|
||||
/* global chromeLocal webextLaunchWebAuthFlow FIREFOX */
|
||||
/* exported tokenManager */
|
||||
'use strict';
|
||||
|
||||
const tokenManager = (() => {
|
||||
promisifyChrome({
|
||||
'windows': ['create', 'update', 'remove'],
|
||||
'tabs': ['create', 'update', 'remove']
|
||||
});
|
||||
const AUTH = {
|
||||
dropbox: {
|
||||
flow: 'token',
|
||||
|
@ -93,24 +89,20 @@ const tokenManager = (() => {
|
|||
});
|
||||
}
|
||||
|
||||
function revokeToken(name) {
|
||||
async function revokeToken(name) {
|
||||
const provider = AUTH[name];
|
||||
const k = buildKeys(name);
|
||||
return revoke()
|
||||
.then(() => chromeLocal.remove(k.LIST));
|
||||
|
||||
function revoke() {
|
||||
if (!provider.revoke) {
|
||||
return Promise.resolve();
|
||||
if (provider.revoke) {
|
||||
try {
|
||||
const token = await chromeLocal.getValue(k.TOKEN);
|
||||
if (token) {
|
||||
await provider.revoke(token);
|
||||
}
|
||||
return chromeLocal.get(k.TOKEN)
|
||||
.then(obj => {
|
||||
if (obj[k.TOKEN]) {
|
||||
return provider.revoke(obj[k.TOKEN]);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
await chromeLocal.remove(k.LIST);
|
||||
}
|
||||
|
||||
function refreshToken(name, k, obj) {
|
||||
|
|
|
@ -264,9 +264,9 @@
|
|||
debounce(flushQueue, text && checkingAll ? 1000 : 0);
|
||||
}
|
||||
|
||||
function flushQueue(lines) {
|
||||
async function flushQueue(lines) {
|
||||
if (!lines) {
|
||||
chromeLocal.getValue('updateLog', []).then(flushQueue);
|
||||
flushQueue(await chromeLocal.getValue('updateLog') || []);
|
||||
return;
|
||||
}
|
||||
const time = Date.now() - logLastWriteTime > 11e3 ?
|
||||
|
|
48
edit/edit.js
48
edit/edit.js
|
@ -151,11 +151,10 @@ lazyInit();
|
|||
if (onBoundsChanged) {
|
||||
// * movement is reported even if the window wasn't resized
|
||||
// * 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
|
||||
chrome.windows.getCurrent(ownWnd => {
|
||||
if (wnd.id === ownWnd.id) saveWindowPos();
|
||||
});
|
||||
const {id} = await browser.windows.getCurrent();
|
||||
if (id === wnd.id) saveWindowPos();
|
||||
});
|
||||
}
|
||||
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 */
|
||||
function lazyInit() {
|
||||
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;
|
||||
// use browser history back when 'back to manage' is clicked
|
||||
if (sessionStorageHash('manageStylesHistory').value[ownTabId] === location.href) {
|
||||
|
@ -336,29 +343,23 @@ function lazyInit() {
|
|||
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);
|
||||
delete sessionStorage.windowPos;
|
||||
if (pos && pos.left != null && chrome.windows) {
|
||||
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
|
||||
chrome.tabs.onAttached.addListener((tabId, info) => {
|
||||
async function onAttached(tabId, info) {
|
||||
if (tabId !== ownTabId) {
|
||||
return;
|
||||
}
|
||||
|
@ -366,16 +367,15 @@ function lazyInit() {
|
|||
prefs.set('openEditInWindow', false);
|
||||
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
|
||||
const openEditInWindow = win.tabs.length === 1;
|
||||
if (openEditInWindow && FIREFOX) {
|
||||
// FF-only because Chrome retardedly resets the size during dragging
|
||||
if (openEditInWindow && FIREFOX) {
|
||||
chrome.windows.update(info.newWindowId, prefs.get('windowPosition'));
|
||||
}
|
||||
prefs.set('openEditInWindow', openEditInWindow);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onRuntimeMessage(request) {
|
||||
|
|
|
@ -398,11 +398,13 @@
|
|||
r.resolve(code);
|
||||
}
|
||||
});
|
||||
port.onDisconnect.addListener(() => {
|
||||
chrome.tabs.get(tabId, tab =>
|
||||
!chrome.runtime.lastError && tab.url === initialUrl
|
||||
? location.reload()
|
||||
: closeCurrentTab());
|
||||
port.onDisconnect.addListener(async () => {
|
||||
const tab = await browser.tabs.get(tabId);
|
||||
if (!chrome.runtime.lastError && tab.url === initialUrl) {
|
||||
location.reload();
|
||||
} else {
|
||||
closeCurrentTab();
|
||||
}
|
||||
});
|
||||
return (opts = {}) => new Promise((resolve, reject) => {
|
||||
const id = performance.now();
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/* exported getTab getActiveTab onTabReady stringAsRegExp openURL ignoreChromeError
|
||||
getStyleWithNoCode tryRegExp sessionStorageHash download deepEqual
|
||||
closeCurrentTab capitalize CHROME_HAS_BORDER_BUG */
|
||||
/* global promisifyChrome */
|
||||
'use strict';
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
promisifyChrome({
|
||||
tabs: ['create', 'get', 'getCurrent', 'move', 'query', 'update'],
|
||||
windows: ['create', 'update'], // Android doesn't have chrome.windows
|
||||
});
|
||||
// FF57+ supports openerTabId, but not in Android
|
||||
// (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;
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
/* global promisifyChrome */
|
||||
/* global deepCopy getOwnTab URLS */ // not used in content scripts
|
||||
'use strict';
|
||||
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
window.INJECTED !== 1 && (() => {
|
||||
promisifyChrome({
|
||||
runtime: ['sendMessage', 'getBackgroundPage'],
|
||||
tabs: ['sendMessage', 'query'],
|
||||
});
|
||||
const TARGETS = Object.assign(Object.create(null), {
|
||||
all: ['both', 'tab', 'extension'],
|
||||
extension: ['both', 'extension'],
|
||||
|
|
|
@ -5,52 +5,65 @@ self.INJECTED !== 1 && (() => {
|
|||
|
||||
//#region for content scripts and our extension pages
|
||||
|
||||
if (!window.browser || !browser.runtime) {
|
||||
const createTrap = (base, parent) => {
|
||||
const target = typeof base === 'function' ? () => {} : {};
|
||||
target.isTrap = true;
|
||||
return new Proxy(target, {
|
||||
get: (target, prop) => {
|
||||
if (target[prop]) return target[prop];
|
||||
if (base[prop] && (typeof base[prop] === 'object' || typeof base[prop] === 'function')) {
|
||||
target[prop] = createTrap(base[prop], base);
|
||||
return target[prop];
|
||||
}
|
||||
return base[prop];
|
||||
},
|
||||
apply: (target, thisArg, args) => base.apply(parent, args)
|
||||
});
|
||||
if (!((window.browser || {}).runtime || {}).sendMessage) {
|
||||
/* Auto-promisifier with a fallback to direct call on signature error.
|
||||
The fallback isn't used now since we call all synchronous methods via `chrome` */
|
||||
const directEvents = ['addListener', 'removeListener', 'hasListener', 'hasListeners'];
|
||||
// generated by tools/chrome-api-no-cb.js
|
||||
const directMethods = {
|
||||
alarms: ['create'],
|
||||
extension: ['getBackgroundPage', 'getExtensionTabs', 'getURL', 'getViews', 'setUpdateUrlData'],
|
||||
i18n: ['getMessage', 'getUILanguage'],
|
||||
identity: ['getRedirectURL'],
|
||||
runtime: ['connect', 'connectNative', 'getManifest', 'getURL', 'reload', 'restart'],
|
||||
tabs: ['connect'],
|
||||
};
|
||||
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`.
|
||||
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)
|
||||
}
|
||||
}
|
||||
};
|
||||
//#endregion
|
||||
|
||||
if (!chrome.tabs) return;
|
||||
|
||||
//#endregion
|
||||
//#region for our extension pages
|
||||
|
||||
for (const storage of ['localStorage', 'sessionStorage']) {
|
||||
|
@ -77,5 +90,6 @@ self.INJECTED !== 1 && (() => {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
//#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
|
||||
'use strict';
|
||||
|
||||
|
@ -114,11 +114,6 @@ window.INJECTED !== 1 && (() => {
|
|||
any: new Set(),
|
||||
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
|
||||
const initializing = (msg.isBg ? readStorage() : API.getPrefs().catch(readStorage))
|
||||
.then(setAll);
|
||||
|
@ -236,11 +231,8 @@ window.INJECTED !== 1 && (() => {
|
|||
}
|
||||
|
||||
function readStorage() {
|
||||
/* Using a non-promisified call since this code may also run in a content script
|
||||
when API.getPrefs occasionally fails during browser startup in the active tab */
|
||||
return new Promise(resolve =>
|
||||
chrome.storage.sync.get(STORAGE_KEY, data =>
|
||||
resolve(data[STORAGE_KEY])));
|
||||
return browser.storage.sync.get(STORAGE_KEY)
|
||||
.then(data => data[STORAGE_KEY]);
|
||||
}
|
||||
|
||||
function updateStorage() {
|
||||
|
|
|
@ -1,72 +1,51 @@
|
|||
/* global loadScript tryJSONparse promisifyChrome */
|
||||
/* exported chromeLocal chromeSync */
|
||||
/* global loadScript tryJSONparse */
|
||||
'use strict';
|
||||
|
||||
promisifyChrome({
|
||||
'storage.local': ['get', 'remove', 'set'],
|
||||
'storage.sync': ['get', 'remove', 'set'],
|
||||
});
|
||||
|
||||
const [chromeLocal, chromeSync] = (() => {
|
||||
return [
|
||||
createWrapper('local'),
|
||||
createWrapper('sync'),
|
||||
];
|
||||
|
||||
function createWrapper(name) {
|
||||
const storage = browser.storage[name];
|
||||
const wrapper = {
|
||||
get: storage.get.bind(storage),
|
||||
set: data => storage.set(data).then(() => data),
|
||||
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]) => {
|
||||
(() => {
|
||||
/** @namespace StorageExtras */
|
||||
const StorageExtras = {
|
||||
async getValue(key) {
|
||||
return (await this.get(key))[key];
|
||||
},
|
||||
async setValue(key, value) {
|
||||
await this.set({[key]: value});
|
||||
},
|
||||
async getLZValue(key) {
|
||||
return (await this.getLZValues([key]))[key];
|
||||
},
|
||||
async getLZValues(keys = Object.values(this.LZ_KEY)) {
|
||||
const [data, LZString] = await Promise.all([
|
||||
this.get(keys),
|
||||
this.getLZString(),
|
||||
]);
|
||||
for (const key of keys) {
|
||||
const value = data[key];
|
||||
data[key] = value && tryJSONparse(LZString.decompressFromUTF16(value));
|
||||
}
|
||||
return data;
|
||||
}),
|
||||
setLZValue: (key, value) =>
|
||||
loadLZStringScript().then(LZString =>
|
||||
wrapper.set({
|
||||
[key]: LZString.compressToUTF16(JSON.stringify(value)),
|
||||
})),
|
||||
|
||||
loadLZStringScript,
|
||||
},
|
||||
async setLZValue(key, value) {
|
||||
const LZString = await this.getLZString();
|
||||
return this.setValue(key, LZString.compressToUTF16(JSON.stringify(value)));
|
||||
},
|
||||
async getLZString() {
|
||||
if (!window.LZString) {
|
||||
await loadScript('/vendor/lz-string-unsafe/lz-string-unsafe.min.js');
|
||||
window.LZString = window.LZString || window.LZStringUnsafe;
|
||||
}
|
||||
return window.LZString;
|
||||
},
|
||||
};
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
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 = {
|
||||
/** @namespace StorageExtrasSync */
|
||||
const StorageExtrasSync = {
|
||||
LZ_KEY: {
|
||||
csslint: 'editorCSSLintConfig',
|
||||
stylelint: 'editorStylelintConfig',
|
||||
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
|
||||
if (CHROME && !chrome.declarativeContent &&
|
||||
stats.options.names.find(_ => _.name === 'styleViaXhr' && _.isValid && _.val)) {
|
||||
await new Promise(resolve =>
|
||||
chrome.permissions.request({permissions: ['declarativeContent']}, resolve));
|
||||
await browser.permissions.request({permissions: ['declarativeContent']});
|
||||
}
|
||||
const oldStorage = await chromeSync.get();
|
||||
for (const {name, val, isValid, isPref} of stats.options.names) {
|
||||
|
|
|
@ -88,16 +88,12 @@ function toggleSideBorders(state = prefs.get('popup.borders')) {
|
|||
}
|
||||
}
|
||||
|
||||
function initTabUrls() {
|
||||
return getActiveTab()
|
||||
.then((tab = {}) =>
|
||||
FIREFOX && tab.status === 'loading' && tab.url === ABOUT_BLANK
|
||||
? waitForTabUrlFF(tab)
|
||||
: tab)
|
||||
.then(tab => new Promise(resolve =>
|
||||
chrome.webNavigation.getAllFrames({tabId: tab.id}, frames =>
|
||||
resolve({frames, tab}))))
|
||||
.then(({frames, tab}) => {
|
||||
async function initTabUrls() {
|
||||
let tab = await getActiveTab();
|
||||
if (FIREFOX && tab.status === 'loading' && tab.url === ABOUT_BLANK) {
|
||||
tab = await waitForTabUrlFF(tab);
|
||||
}
|
||||
let frames = await browser.webNavigation.getAllFrames({tabId: tab.id});
|
||||
let url = tab.pendingUrl || tab.url || ''; // new Chrome uses pendingUrl while connecting
|
||||
frames = sortTabFrames(frames);
|
||||
if (url === 'chrome://newtab/' && !URLS.chromeProtectsNTP) {
|
||||
|
@ -109,7 +105,6 @@ function initTabUrls() {
|
|||
}
|
||||
tabURL = frames[0].url = url;
|
||||
return 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