From be519afe5fdd956ac092d7ac01f161541e3db3f8 Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Sun, 21 Mar 2021 15:35:01 -0700 Subject: [PATCH] extension,test: Start on simplifying routing. Introduces a 'named variable' syntax so we can remove all the manual indexing into path components / converting to int in individual fs ops. Also rewrites the route finding to use regex: all routes are compiled to regexes, then the route finder just walks through them all until it hits a match. Doesn't fully work yet; JS test passes. --- extension/background.js | 127 +++++++++++++++++++--------------------- test/test.js | 6 +- 2 files changed, 63 insertions(+), 70 deletions(-) diff --git a/extension/background.js b/extension/background.js index 107d847..b727945 100644 --- a/extension/background.js +++ b/extension/background.js @@ -223,26 +223,6 @@ router["/tabs/by-id"] = { return { entries: [".", "..", ...tabs.map(tab => String(tab.id))] }; } }; -// title.txt -// url.txt -// text.txt -// TODO: document.html - -// eval-in -// eval-out - -// TODO: mem (?) -// TODO: cpu (?) - -// TODO: dom/ ? -// TODO: globals/ ? - -// TODO: archive.mhtml ? -// TODO: printed.pdf -// control - -// there's a question about whether to do stuff through injected -// JavaScript or through the devtools API. (function() { const withTab = (readHandler, writeHandler) => defineFile(async path => { @@ -259,23 +239,22 @@ router["/tabs/by-id"] = { return (await browser.tabs.executeScript(tabId, {code}))[0]; }); - router["/tabs/by-id/*/url.txt"] = withTab(tab => tab.url + "\n", buf => ({ url: buf })); - router["/tabs/by-id/*/title.txt"] = withTab(tab => tab.title + "\n"); - router["/tabs/by-id/*/text.txt"] = fromScript(`document.body.innerText`); - router["/tabs/by-id/*/body.html"] = fromScript(`document.body.innerHTML`); + router["/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"); + router["/tabs/by-id/#TAB_ID/text.txt"] = fromScript(`document.body.innerText`); + router["/tabs/by-id/#TAB_ID/body.html"] = fromScript(`document.body.innerHTML`); // echo true > mnt/tabs/by-id/1644/active // cat mnt/tabs/by-id/1644/active - router["/tabs/by-id/*/active"] = withTab(tab => JSON.stringify(tab.active) + '\n', - // WEIRD: we do startsWith because you might end up with buf - // being "truee" (if it was "false", then someone wrote "true") - buf => ({ active: buf.startsWith("true") })); + router["/tabs/by-id/#TAB_ID/active"] = withTab(tab => JSON.stringify(tab.active) + '\n', + // WEIRD: we do startsWith because you might end up with buf + // being "truee" (if it was "false", then someone wrote "true") + buf => ({ active: buf.startsWith("true") })); })(); (function() { const evals = {}; - router["/tabs/by-id/*/evals"] = { - async readdir({path}) { - const tabId = parseInt(pathComponent(path, -2)); + router["/tabs/by-id/#TAB_ID/evals"] = { + async readdir({path, tabId}) { return { entries: [".", "..", ...Object.keys(evals[tabId] || {}), ...Object.keys(evals[tabId] || {}).map(f => f + '.result')] }; @@ -288,7 +267,7 @@ router["/tabs/by-id"] = { }; }, }; - router["/tabs/by-id/*/evals/*"] = { + router["/tabs/by-id/#TAB_ID/evals/*"] = { // NOTE: eval runs in extension's content script, not in original page JS context async mknod({path, mode}) { const [tabId, name] = [parseInt(pathComponent(path, -3)), pathComponent(path, -1)]; @@ -326,7 +305,7 @@ router["/tabs/by-id"] = { })(); (function() { const watches = {}; - router["/tabs/by-id/*/watches"] = { + router["/tabs/by-id/#TAB_ID/watches"] = { async readdir({path}) { const tabId = parseInt(pathComponent(path, -2)); return { entries: [".", "..", ...Object.keys(watches[tabId] || [])] }; @@ -339,7 +318,7 @@ router["/tabs/by-id"] = { }; }, }; - router["/tabs/by-id/*/watches/*"] = { + router["/tabs/by-id/#TAB_ID/watches/*"] = { // NOTE: eval runs in extension's content script, not in original page JS context async mknod({path, mode}) { const [tabId, expr] = [parseInt(pathComponent(path, -3)), pathComponent(path, -1)]; @@ -367,14 +346,14 @@ router["/tabs/by-id"] = { }; })(); -router["/tabs/by-id/*/window"] = { +router["/tabs/by-id/#TAB_ID/window"] = { // a symbolic link to /windows/[id for this window] async readlink({path}) { const tabId = parseInt(pathComponent(path, -2)); const tab = await browser.tabs.get(tabId); return { buf: "../../../windows/" + tab.windowId }; } }; -router["/tabs/by-id/*/control"] = { +router["/tabs/by-id/#TAB_ID/control"] = { // echo remove > mnt/tabs/by-id/1644/control async write({path, buf}) { const tabId = parseInt(pathComponent(path, -2)); @@ -393,7 +372,7 @@ router["/tabs/by-id/*/control"] = { // resources/ // TODO: scripts/ TODO: allow creation, eval immediately - router["/tabs/by-id/*/debugger/resources"] = { + router["/tabs/by-id/#TAB_ID/debugger/resources"] = { async readdir({path}) { const tabId = parseInt(pathComponent(path, -3)); await TabManager.debugTab(tabId); await TabManager.enableDomainForTab(tabId, "Page"); @@ -401,7 +380,7 @@ router["/tabs/by-id/*/control"] = { return { entries: [".", "..", ...frameTree.resources.map(r => sanitize(String(r.url)))] }; } }; - router["/tabs/by-id/*/debugger/resources/*"] = defineFile(async path => { + router["/tabs/by-id/#TAB_ID/debugger/resources/*"] = defineFile(async path => { const [tabId, suffix] = [parseInt(pathComponent(path, -4)), pathComponent(path, -1)]; await TabManager.debugTab(tabId); await TabManager.enableDomainForTab(tabId, "Page"); @@ -419,7 +398,7 @@ router["/tabs/by-id/*/control"] = { } throw new UnixError(unix.ENOENT); }); - router["/tabs/by-id/*/debugger/scripts"] = { + router["/tabs/by-id/#TAB_ID/debugger/scripts"] = { async opendir({path}) { const tabId = parseInt(pathComponent(path, -3)); await TabManager.debugTab(tabId); await TabManager.enableDomainForTab(tabId, "Debugger"); @@ -442,7 +421,7 @@ router["/tabs/by-id/*/control"] = { } return scriptInfo; } - router["/tabs/by-id/*/debugger/scripts/*"] = defineFile(async path => { + router["/tabs/by-id/#TAB_ID/debugger/scripts/*"] = defineFile(async path => { const [tabId, suffix] = [parseInt(pathComponent(path, -4)), pathComponent(path, -1)]; await TabManager.debugTab(tabId); await TabManager.enableDomainForTab(tabId, "Page"); @@ -461,16 +440,17 @@ router["/tabs/by-id/*/control"] = { }); })(); -router["/tabs/by-id/*/inputs"] = { +router["/tabs/by-id/#TAB_ID/inputs"] = { async readdir({path}) { const tabId = parseInt(pathComponent(path, -2)); // TODO: assign new IDs to inputs without them? - const code = `Array.from(document.querySelectorAll('textarea, input[type=text]')).map(e => e.id).filter(id => id)` + const code = `Array.from(document.querySelectorAll('textarea, input[type=text]')) + .map(e => e.id).filter(id => id)`; const ids = (await browser.tabs.executeScript(tabId, {code}))[0]; return { entries: [".", "..", ...ids.map(id => `${id}.txt`)] }; } }; -router["/tabs/by-id/*/inputs/*"] = defineFile(async path => { +router["/tabs/by-id/#TAB_ID/inputs/*"] = defineFile(async path => { const [tabId, inputId] = [parseInt(pathComponent(path, -3)), pathComponent(path, -1).slice(0, -4)]; const code = `document.getElementById('${inputId}').value`; const inputValue = (await browser.tabs.executeScript(tabId, {code}))[0]; @@ -631,8 +611,36 @@ for (let i = 10; i >= 0; i--) { // a bit about how to make that work with wildcards. } -// fill in default implementations of fs ops + for (let key in router) { + // /tabs/by-id/#TAB_ID/url.txt -> RegExp \/tabs\/by-id\/(?[^/]+)\/url.txt + router[key].__regex = new RegExp( + '^' + key + .split('/') + .map(keySegment => keySegment + .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + .replace(/([#:])([A-Z_]+)/g, (_, sigil, varName) => { + return `(?<${sigil === '#' ? 'int$' : 'string$'}${varName}>[^/]+)`; + })) + .join('/') + '$'); + + router[key].__match = function(path) { + const result = router[key].__regex.exec(path); + if (!result) { return; } + + const vars = {}; + for (let [typeAndVarName, value] of Object.entries(result.groups)) { + let [type_, varName] = typeAndVarName.split('$'); + // TAB_ID -> tabId + varName = varName.toLowerCase(); + varName = varName.replace(/_([a-z])/g, c => c[1].toUpperCase()); + vars[varName] = type_ === 'int' ? parseInt(value) : value; + } + return vars; + }; + + // Fill in default implementations of fs ops: + // if readdir -> directory -> add getattr, opendir, releasedir if (router[key].readdir) { router[key] = { @@ -671,37 +679,22 @@ for (let key in router) { st_size: 100 // FIXME }; }, - open() { - return { fh: 0 }; - }, - release() { - return {}; - }, + open() { return { fh: 0 }; }, + release() { return {}; }, ...router[key] }; } } function findRoute(path) { - let pathSegments = path.split("/"); - - if (pathSegments[pathSegments.length - 1].startsWith("._")) { + if (path.match(/\/\._[^\/]+$/)) { throw new UnixError(unix.ENOTSUP); // Apple Double file for xattrs } - let routingPath = ""; - for (let segment of pathSegments) { - if (routingPath === "/") { routingPath = ""; } - - if (router[routingPath + "/*"]) { - routingPath += "/*"; - } else if (router[routingPath + "/" + segment]) { - routingPath += "/" + segment; - } else { - throw new UnixError(unix.ENOENT); - } + for (let route of Object.values(router)) { + if (route.__match(path)) { return route; } } - return router[routingPath]; + throw new UnixError(unix.ENOENT); } let port; @@ -728,7 +721,7 @@ async function onMessage(req) { response = { op: req.op, error: e instanceof UnixError ? e.error : unix.EIO - } + }; } /* console.timeEnd(req.op + ':' + req.path);*/ @@ -775,7 +768,7 @@ function tryConnect() { port = chrome.runtime.connectNative('com.rsnous.tabfs'); port.onMessage.addListener(onMessage); - port.onDisconnect.addListener(p => {console.log('disconnect', p)}); + port.onDisconnect.addListener(p => { console.log('disconnect', p); }); } diff --git a/test/test.js b/test/test.js index 1288bf4..6376885 100644 --- a/test/test.js +++ b/test/test.js @@ -6,7 +6,7 @@ global.chrome = {}; const {router, findRoute} = require('../extension/background'); (async () => { - const tabRoute = await router['/tabs/by-id/*'].readdir(); + const tabRoute = await router['/tabs/by-id/#TAB_ID'].readdir(); assert(['.', '..', 'url.txt', 'title.txt', 'text.txt'] .every(file => tabRoute.entries.includes(file))); @@ -16,6 +16,6 @@ const {router, findRoute} = require('../extension/background'); { entries: ['.', '..', 'create', 'by-id', 'by-title', 'last-focused'] }); - assert.deepEqual(findRoute('/tabs/by-id/TABID/url.txt'), - router['/tabs/by-id/*/url.txt']); + assert.deepEqual(findRoute('/tabs/by-id/10/url.txt'), + router['/tabs/by-id/#TAB_ID/url.txt']); })();