extension,test: Move Router down, Router -> Routes.

This commit is contained in:
Omar Rizwan 2021-03-22 23:25:38 -07:00
parent 826246bf29
commit 383096da00
2 changed files with 57 additions and 57 deletions

View file

@ -61,8 +61,6 @@ const utf8ArrayToString = (function() {
return utf8 => decoder.decode(utf8); return utf8 => decoder.decode(utf8);
})(); })();
const Router = {};
const Cache = { const Cache = {
// used when you open a file to cache the content we got from the // used when you open a file to cache the content we got from the
// browser until you close that file. (so we can respond to // browser until you close that file. (so we can respond to
@ -147,7 +145,9 @@ const defineFile = (getData, setData) => ({
} }
}); });
Router["/tabs/create"] = { const Routes = {};
Routes["/tabs/create"] = {
async write({buf}) { async write({buf}) {
const url = buf.trim(); const url = buf.trim();
await browser.tabs.create({url}); await browser.tabs.create({url});
@ -156,7 +156,7 @@ Router["/tabs/create"] = {
async truncate() { return {}; } async truncate() { return {}; }
}; };
Router["/tabs/by-id"] = { Routes["/tabs/by-id"] = {
async readdir() { async readdir() {
const tabs = await browser.tabs.query({}); const tabs = await browser.tabs.query({});
return { entries: [".", "..", ...tabs.map(tab => String(tab.id))] }; return { entries: [".", "..", ...tabs.map(tab => String(tab.id))] };
@ -176,21 +176,21 @@ Router["/tabs/by-id"] = {
return (await browser.tabs.executeScript(tabId, {code}))[0]; return (await browser.tabs.executeScript(tabId, {code}))[0];
}); });
Router["/tabs/by-id/#TAB_ID/url.txt"] = withTab(tab => tab.url + "\n", buf => ({ url: buf })); Routes["/tabs/by-id/#TAB_ID/url.txt"] = withTab(tab => tab.url + "\n", buf => ({ url: buf }));
Router["/tabs/by-id/#TAB_ID/title.txt"] = withTab(tab => tab.title + "\n"); Routes["/tabs/by-id/#TAB_ID/title.txt"] = withTab(tab => tab.title + "\n");
Router["/tabs/by-id/#TAB_ID/text.txt"] = fromScript(`document.body.innerText`); Routes["/tabs/by-id/#TAB_ID/text.txt"] = fromScript(`document.body.innerText`);
Router["/tabs/by-id/#TAB_ID/body.html"] = fromScript(`document.body.innerHTML`); Routes["/tabs/by-id/#TAB_ID/body.html"] = fromScript(`document.body.innerHTML`);
// echo true > mnt/tabs/by-id/1644/active // echo true > mnt/tabs/by-id/1644/active
// cat mnt/tabs/by-id/1644/active // cat mnt/tabs/by-id/1644/active
Router["/tabs/by-id/#TAB_ID/active"] = withTab(tab => JSON.stringify(tab.active) + '\n', Routes["/tabs/by-id/#TAB_ID/active"] = withTab(tab => JSON.stringify(tab.active) + '\n',
// WEIRD: we do startsWith because you might end up with buf // WEIRD: we do startsWith because you might end up with buf
// being "truee" (if it was "false", then someone wrote "true") // being "truee" (if it was "false", then someone wrote "true")
buf => ({ active: buf.startsWith("true") })); buf => ({ active: buf.startsWith("true") }));
})(); })();
(function() { (function() {
const evals = {}; const evals = {};
Router["/tabs/by-id/#TAB_ID/evals"] = { Routes["/tabs/by-id/#TAB_ID/evals"] = {
async readdir({path, tabId}) { async readdir({path, tabId}) {
return { entries: [".", "..", return { entries: [".", "..",
...Object.keys(evals[tabId] || {}), ...Object.keys(evals[tabId] || {}),
@ -204,7 +204,7 @@ Router["/tabs/by-id"] = {
}; };
}, },
}; };
Router["/tabs/by-id/#TAB_ID/evals/:FILENAME"] = { Routes["/tabs/by-id/#TAB_ID/evals/:FILENAME"] = {
// NOTE: eval runs in extension's content script, not in original page JS context // NOTE: eval runs in extension's content script, not in original page JS context
async mknod({tabId, filename, mode}) { async mknod({tabId, filename, mode}) {
evals[tabId] = evals[tabId] || {}; evals[tabId] = evals[tabId] || {};
@ -239,7 +239,7 @@ Router["/tabs/by-id"] = {
})(); })();
(function() { (function() {
const watches = {}; const watches = {};
Router["/tabs/by-id/#TAB_ID/watches"] = { Routes["/tabs/by-id/#TAB_ID/watches"] = {
async readdir({tabId}) { async readdir({tabId}) {
return { entries: [".", "..", ...Object.keys(watches[tabId] || [])] }; return { entries: [".", "..", ...Object.keys(watches[tabId] || [])] };
}, },
@ -251,7 +251,7 @@ Router["/tabs/by-id"] = {
}; };
}, },
}; };
Router["/tabs/by-id/#TAB_ID/watches/:EXPR"] = { Routes["/tabs/by-id/#TAB_ID/watches/:EXPR"] = {
// NOTE: eval runs in extension's content script, not in original page JS context // NOTE: eval runs in extension's content script, not in original page JS context
async mknod({tabId, expr, mode}) { async mknod({tabId, expr, mode}) {
watches[tabId] = watches[tabId] || {}; watches[tabId] = watches[tabId] || {};
@ -277,14 +277,14 @@ Router["/tabs/by-id"] = {
}; };
})(); })();
Router["/tabs/by-id/#TAB_ID/window"] = { Routes["/tabs/by-id/#TAB_ID/window"] = {
// a symbolic link to /windows/[id for this window] // a symbolic link to /windows/[id for this window]
async readlink({tabId}) { async readlink({tabId}) {
const tab = await browser.tabs.get(tabId); const tab = await browser.tabs.get(tabId);
return { buf: "../../../windows/" + tab.windowId }; return { buf: "../../../windows/" + tab.windowId };
} }
}; };
Router["/tabs/by-id/#TAB_ID/control"] = { Routes["/tabs/by-id/#TAB_ID/control"] = {
// echo remove > mnt/tabs/by-id/1644/control // echo remove > mnt/tabs/by-id/1644/control
async write({tabId, buf}) { async write({tabId, buf}) {
const command = buf.trim(); const command = buf.trim();
@ -357,14 +357,14 @@ Router["/tabs/by-id/#TAB_ID/control"] = {
// resources/ // resources/
// TODO: scripts/ TODO: allow creation, eval immediately // TODO: scripts/ TODO: allow creation, eval immediately
Router["/tabs/by-id/#TAB_ID/debugger/resources"] = { Routes["/tabs/by-id/#TAB_ID/debugger/resources"] = {
async readdir({tabId}) { async readdir({tabId}) {
await TabManager.debugTab(tabId); await TabManager.enableDomainForTab(tabId, "Page"); await TabManager.debugTab(tabId); await TabManager.enableDomainForTab(tabId, "Page");
const {frameTree} = await sendDebuggerCommand(tabId, "Page.getResourceTree", {}); const {frameTree} = await sendDebuggerCommand(tabId, "Page.getResourceTree", {});
return { entries: [".", "..", ...frameTree.resources.map(r => sanitize(String(r.url)))] }; return { entries: [".", "..", ...frameTree.resources.map(r => sanitize(String(r.url)))] };
} }
}; };
Router["/tabs/by-id/#TAB_ID/debugger/resources/:SUFFIX"] = defineFile(async ({path, tabId, suffix}) => { Routes["/tabs/by-id/#TAB_ID/debugger/resources/:SUFFIX"] = defineFile(async ({path, tabId, suffix}) => {
await TabManager.debugTab(tabId); await TabManager.enableDomainForTab(tabId, "Page"); await TabManager.debugTab(tabId); await TabManager.enableDomainForTab(tabId, "Page");
const {frameTree} = await sendDebuggerCommand(tabId, "Page.getResourceTree", {}); const {frameTree} = await sendDebuggerCommand(tabId, "Page.getResourceTree", {});
@ -381,7 +381,7 @@ Router["/tabs/by-id/#TAB_ID/control"] = {
} }
throw new UnixError(unix.ENOENT); throw new UnixError(unix.ENOENT);
}); });
Router["/tabs/by-id/#TAB_ID/debugger/scripts"] = { Routes["/tabs/by-id/#TAB_ID/debugger/scripts"] = {
async opendir({tabId}) { async opendir({tabId}) {
await TabManager.debugTab(tabId); await TabManager.enableDomainForTab(tabId, "Debugger"); await TabManager.debugTab(tabId); await TabManager.enableDomainForTab(tabId, "Debugger");
return { fh: 0 }; return { fh: 0 };
@ -402,7 +402,7 @@ Router["/tabs/by-id/#TAB_ID/control"] = {
} }
return scriptInfo; return scriptInfo;
} }
Router["/tabs/by-id/#TAB_ID/debugger/scripts/:FILENAME"] = defineFile(async ({tabId, filename}) => { Routes["/tabs/by-id/#TAB_ID/debugger/scripts/:FILENAME"] = defineFile(async ({tabId, filename}) => {
await TabManager.debugTab(tabId); await TabManager.debugTab(tabId);
await TabManager.enableDomainForTab(tabId, "Page"); await TabManager.enableDomainForTab(tabId, "Page");
await TabManager.enableDomainForTab(tabId, "Debugger"); await TabManager.enableDomainForTab(tabId, "Debugger");
@ -419,7 +419,7 @@ Router["/tabs/by-id/#TAB_ID/control"] = {
}); });
})(); })();
Router["/tabs/by-id/#TAB_ID/inputs"] = { Routes["/tabs/by-id/#TAB_ID/inputs"] = {
async readdir({tabId}) { async readdir({tabId}) {
// TODO: assign new IDs to inputs without them? // TODO: assign new IDs to inputs without them?
const code = `Array.from(document.querySelectorAll('textarea, input[type=text]')) const code = `Array.from(document.querySelectorAll('textarea, input[type=text]'))
@ -428,7 +428,7 @@ Router["/tabs/by-id/#TAB_ID/inputs"] = {
return { entries: [".", "..", ...ids.map(id => `${id}.txt`)] }; return { entries: [".", "..", ...ids.map(id => `${id}.txt`)] };
} }
}; };
Router["/tabs/by-id/#TAB_ID/inputs/:INPUT_ID.txt"] = defineFile(async ({tabId, inputId}) => { Routes["/tabs/by-id/#TAB_ID/inputs/:INPUT_ID.txt"] = defineFile(async ({tabId, inputId}) => {
const code = `document.getElementById('${inputId}').value`; const code = `document.getElementById('${inputId}').value`;
const inputValue = (await browser.tabs.executeScript(tabId, {code}))[0]; const inputValue = (await browser.tabs.executeScript(tabId, {code}))[0];
if (inputValue === null) { throw new UnixError(unix.ENOENT); } /* FIXME: hack to deal with if inputId isn't valid */ if (inputValue === null) { throw new UnixError(unix.ENOENT); } /* FIXME: hack to deal with if inputId isn't valid */
@ -439,7 +439,7 @@ Router["/tabs/by-id/#TAB_ID/inputs/:INPUT_ID.txt"] = defineFile(async ({tabId, i
await browser.tabs.executeScript(tabId, {code}); await browser.tabs.executeScript(tabId, {code});
}); });
Router["/tabs/by-title"] = { Routes["/tabs/by-title"] = {
getattr() { getattr() {
return { return {
st_mode: unix.S_IFDIR | 0777, // writable so you can delete tabs st_mode: unix.S_IFDIR | 0777, // writable so you can delete tabs
@ -452,7 +452,7 @@ Router["/tabs/by-title"] = {
return { entries: [".", "..", ...tabs.map(tab => sanitize(String(tab.title)) + "." + String(tab.id))] }; return { entries: [".", "..", ...tabs.map(tab => sanitize(String(tab.title)) + "." + String(tab.id))] };
} }
}; };
Router["/tabs/by-title/:TAB_TITLE.#TAB_ID"] = { Routes["/tabs/by-title/:TAB_TITLE.#TAB_ID"] = {
// TODO: date // TODO: date
async readlink({tabId}) { // a symbolic link to /tabs/by-id/[id for this tab] async readlink({tabId}) { // a symbolic link to /tabs/by-id/[id for this tab]
return { buf: "../by-id/" + tabId }; return { buf: "../by-id/" + tabId };
@ -462,7 +462,7 @@ Router["/tabs/by-title/:TAB_TITLE.#TAB_ID"] = {
return {}; return {};
} }
}; };
Router["/tabs/last-focused"] = { Routes["/tabs/last-focused"] = {
// a symbolic link to /tabs/by-id/[id for this tab] // a symbolic link to /tabs/by-id/[id for this tab]
async readlink() { async readlink() {
const id = (await browser.tabs.query({ active: true, lastFocusedWindow: true }))[0].id; const id = (await browser.tabs.query({ active: true, lastFocusedWindow: true }))[0].id;
@ -470,13 +470,13 @@ Router["/tabs/last-focused"] = {
} }
}; };
Router["/windows"] = { Routes["/windows"] = {
async readdir() { async readdir() {
const windows = await browser.windows.getAll(); const windows = await browser.windows.getAll();
return { entries: [".", "..", ...windows.map(window => String(window.id))] }; return { entries: [".", "..", ...windows.map(window => String(window.id))] };
} }
}; };
Router["/windows/last-focused"] = { Routes["/windows/last-focused"] = {
// a symbolic link to /windows/[id for this window] // a symbolic link to /windows/[id for this window]
async readlink() { async readlink() {
const windowId = (await browser.windows.getLastFocused()).id; const windowId = (await browser.windows.getLastFocused()).id;
@ -492,11 +492,11 @@ Router["/windows/last-focused"] = {
await browser.windows.update(windowId, writeHandler(buf)); await browser.windows.update(windowId, writeHandler(buf));
} : undefined); } : undefined);
Router["/windows/#WINDOW_ID/focused"] = Routes["/windows/#WINDOW_ID/focused"] =
withWindow(window => JSON.stringify(window.focused) + '\n', withWindow(window => JSON.stringify(window.focused) + '\n',
buf => ({ focused: buf.startsWith('true') })); buf => ({ focused: buf.startsWith('true') }));
})(); })();
Router["/windows/#WINDOW_ID/visible-tab.png"] = { ...defineFile(async ({windowId}) => { Routes["/windows/#WINDOW_ID/visible-tab.png"] = { ...defineFile(async ({windowId}) => {
// screen capture is a window thing and not a tab thing because you // screen capture is a window thing and not a tab thing because you
// can only capture the visible tab for each window anyway; you // can only capture the visible tab for each window anyway; you
// can't take a screenshot of just any arbitrary tab // can't take a screenshot of just any arbitrary tab
@ -513,13 +513,13 @@ Router["/windows/#WINDOW_ID/visible-tab.png"] = { ...defineFile(async ({windowId
} }; } };
Router["/extensions"] = { Routes["/extensions"] = {
async readdir() { async readdir() {
const infos = await browser.management.getAll(); const infos = await browser.management.getAll();
return { entries: [".", "..", ...infos.map(info => `${sanitize(info.name)}.${info.id}`)] }; return { entries: [".", "..", ...infos.map(info => `${sanitize(info.name)}.${info.id}`)] };
} }
}; };
Router["/extensions/:EXTENSION_TITLE.:EXTENSION_ID/enabled"] = { ...defineFile(async ({extensionId}) => { Routes["/extensions/:EXTENSION_TITLE.:EXTENSION_ID/enabled"] = { ...defineFile(async ({extensionId}) => {
const info = await browser.management.get(extensionId); const info = await browser.management.get(extensionId);
return String(info.enabled) + '\n'; return String(info.enabled) + '\n';
@ -529,7 +529,7 @@ Router["/extensions/:EXTENSION_TITLE.:EXTENSION_ID/enabled"] = { ...defineFile(a
// suppress truncate so it doesn't accidentally flip the state when you do, e.g., `echo true >` // suppress truncate so it doesn't accidentally flip the state when you do, e.g., `echo true >`
}), truncate() { return {}; } }; }), truncate() { return {}; } };
Router["/runtime/reload"] = { Routes["/runtime/reload"] = {
async write({buf}) { async write({buf}) {
await browser.runtime.reload(); await browser.runtime.reload();
return {size: stringToUtf8Array(buf).length}; return {size: stringToUtf8Array(buf).length};
@ -544,20 +544,20 @@ Router["/runtime/reload"] = {
// one level at a time so you know (for each parent) what all the // one level at a time so you know (for each parent) what all the
// children will be. // children will be.
for (let i = 10; i >= 0; i--) { for (let i = 10; i >= 0; i--) {
for (let path of Object.keys(Router).filter(key => key.split("/").length === i)) { for (let path of Object.keys(Routes).filter(key => key.split("/").length === i)) {
path = path.substr(0, path.lastIndexOf("/")); path = path.substr(0, path.lastIndexOf("/"));
if (path == '') path = '/'; if (path == '') path = '/';
if (!Router[path]) { if (!Routes[path]) {
function depth(p) { return p === '/' ? 0 : (p.match(/\//g) || []).length; } function depth(p) { return p === '/' ? 0 : (p.match(/\//g) || []).length; }
// find all direct children // find all direct children
let entries = Object.keys(Router) let entries = Object.keys(Routes)
.filter(k => k.startsWith(path) && depth(k) === depth(path) + 1) .filter(k => k.startsWith(path) && depth(k) === depth(path) + 1)
.map(k => k.substr((path === '/' ? 0 : path.length) + 1).split('/')[0]); .map(k => k.substr((path === '/' ? 0 : path.length) + 1).split('/')[0]);
entries = [".", "..", ...new Set(entries)]; entries = [".", "..", ...new Set(entries)];
Router[path] = { readdir() { return { entries }; } }; Routes[path] = { readdir() { return { entries }; } };
} }
} }
// I also think it would be better to compute this stuff on the fly, // I also think it would be better to compute this stuff on the fly,
@ -566,9 +566,9 @@ for (let i = 10; i >= 0; i--) {
} }
for (let key in Router) { for (let key in Routes) {
// /tabs/by-id/#TAB_ID/url.txt -> RegExp \/tabs\/by-id\/(?<int$TAB_ID>[0-9]+)\/url.txt // /tabs/by-id/#TAB_ID/url.txt -> RegExp \/tabs\/by-id\/(?<int$TAB_ID>[0-9]+)\/url.txt
Router[key].__regex = new RegExp( Routes[key].__regex = new RegExp(
'^' + key '^' + key
.split('/') .split('/')
.map(keySegment => keySegment .map(keySegment => keySegment
@ -579,8 +579,8 @@ for (let key in Router) {
})) }))
.join('/') + '$'); .join('/') + '$');
Router[key].__match = function(path) { Routes[key].__match = function(path) {
const result = Router[key].__regex.exec(path); const result = Routes[key].__regex.exec(path);
if (!result) { return; } if (!result) { return; }
const vars = {}; const vars = {};
@ -597,8 +597,8 @@ for (let key in Router) {
// Fill in default implementations of fs ops: // Fill in default implementations of fs ops:
// if readdir -> directory -> add getattr, opendir, releasedir // if readdir -> directory -> add getattr, opendir, releasedir
if (Router[key].readdir) { if (Routes[key].readdir) {
Router[key] = { Routes[key] = {
getattr() { getattr() {
return { return {
st_mode: unix.S_IFDIR | 0755, st_mode: unix.S_IFDIR | 0755,
@ -608,11 +608,11 @@ for (let key in Router) {
}, },
opendir({path}) { return { fh: 0 }; }, opendir({path}) { return { fh: 0 }; },
releasedir({path}) { return {}; }, releasedir({path}) { return {}; },
...Router[key] ...Routes[key]
}; };
} else if (Router[key].readlink) { } else if (Routes[key].readlink) {
Router[key] = { Routes[key] = {
async getattr(req) { async getattr(req) {
const st_size = (await this.readlink(req)).buf.length + 1; const st_size = (await this.readlink(req)).buf.length + 1;
return { return {
@ -622,21 +622,21 @@ for (let key in Router) {
st_size st_size
}; };
}, },
...Router[key] ...Routes[key]
}; };
} else if (Router[key].read || Router[key].write) { } else if (Routes[key].read || Routes[key].write) {
Router[key] = { Routes[key] = {
async getattr() { async getattr() {
return { return {
st_mode: unix.S_IFREG | ((Router[key].read && 0444) | (Router[key].write && 0222)), st_mode: unix.S_IFREG | ((Routes[key].read && 0444) | (Routes[key].write && 0222)),
st_nlink: 1, st_nlink: 1,
st_size: 100 // FIXME st_size: 100 // FIXME
}; };
}, },
open() { return { fh: 0 }; }, open() { return { fh: 0 }; },
release() { return {}; }, release() { return {}; },
...Router[key] ...Routes[key]
}; };
} }
} }
@ -647,7 +647,7 @@ function tryMatchRoute(path) {
throw new UnixError(unix.ENOTSUP); throw new UnixError(unix.ENOTSUP);
} }
for (let route of Object.values(Router)) { for (let route of Object.values(Routes)) {
const vars = route.__match(path); const vars = route.__match(path);
if (vars) { return [route, vars]; } if (vars) { return [route, vars]; }
} }
@ -733,7 +733,7 @@ function tryConnect() {
if (typeof process === 'object') { if (typeof process === 'object') {
// we're running in node (as part of a test) // we're running in node (as part of a test)
// return everything they might want to test // return everything they might want to test
module.exports = {Router, tryMatchRoute}; module.exports = {Routes, tryMatchRoute};
} else { } else {
tryConnect(); tryConnect();

View file

@ -3,21 +3,21 @@ const assert = require('assert');
// mock chrome namespace // mock chrome namespace
global.chrome = {}; global.chrome = {};
// run background.js // run background.js
const {Router, tryMatchRoute} = require('../extension/background'); const {Routes, tryMatchRoute} = require('../extension/background');
(async () => { (async () => {
const tabRoute = await Router['/tabs/by-id/#TAB_ID'].readdir(); const tabRoute = await Routes['/tabs/by-id/#TAB_ID'].readdir();
assert(['.', '..', 'url.txt', 'title.txt', 'text.txt'] assert(['.', '..', 'url.txt', 'title.txt', 'text.txt']
.every(file => tabRoute.entries.includes(file))); .every(file => tabRoute.entries.includes(file)));
assert.deepEqual(await Router['/'].readdir(), assert.deepEqual(await Routes['/'].readdir(),
{ entries: ['.', '..', 'windows', 'extensions', 'tabs', 'runtime'] }); { entries: ['.', '..', 'windows', 'extensions', 'tabs', 'runtime'] });
assert.deepEqual(await Router['/tabs'].readdir(), assert.deepEqual(await Routes['/tabs'].readdir(),
{ entries: ['.', '..', 'create', { entries: ['.', '..', 'create',
'by-id', 'by-title', 'last-focused'] }); 'by-id', 'by-title', 'last-focused'] });
assert.deepEqual(tryMatchRoute('/'), [Router['/'], {}]); assert.deepEqual(tryMatchRoute('/'), [Routes['/'], {}]);
assert.deepEqual(tryMatchRoute('/tabs/by-id/10/url.txt'), assert.deepEqual(tryMatchRoute('/tabs/by-id/10/url.txt'),
[Router['/tabs/by-id/#TAB_ID/url.txt'], {tabId: 10}]); [Routes['/tabs/by-id/#TAB_ID/url.txt'], {tabId: 10}]);
})(); })();