aboutsummaryrefslogtreecommitdiffstats
path: root/extension
diff options
context:
space:
mode:
authorOmar Rizwan <omar.rizwan@gmail.com>2020-10-27 20:53:39 -0700
committerOmar Rizwan <omar.rizwan@gmail.com>2020-10-27 20:53:39 -0700
commita9cb8e14af87af949e44f20fc40591eebeadcb66 (patch)
treeb6ebe0e74037555daf7c47dc85ab95304c760d0d /extension
parent2426e9f7a5aa7989573151270fa090b4c93ad53c (diff)
working on refactor of fs spec. also Linux port and README stuff.
Diffstat (limited to 'extension')
-rw-r--r--extension/background.js461
1 files changed, 179 insertions, 282 deletions
diff --git a/extension/background.js b/extension/background.js
index 75520d9..eb120a7 100644
--- a/extension/background.js
+++ b/extension/background.js
@@ -1,3 +1,5 @@
+const TESTING = (typeof chrome === 'undefined');
+
const unix = {
EPERM: 1,
ENOENT: 2,
@@ -24,26 +26,6 @@ function UnixError(error) {
}
UnixError.prototype = Error.prototype;
-async function debugTab(tabId) {
- if (!debugged[tabId]) {
- await new Promise(resolve => chrome.debugger.attach({tabId}, "1.3", resolve));
- debugged[tabId] = 0;
- }
- debugged[tabId] += 1;
-}
-function sendDebuggerCommand(tabId, method, commandParams) {
- return new Promise((resolve, reject) =>
- chrome.debugger.sendCommand({tabId}, method, commandParams, result => {
- console.log(method, result);
- if (result) {
- resolve(result);
- } else {
- reject(chrome.runtime.lastError);
- }
- })
- );
-}
-
// tabs/by-id/ID/title
// tabs/by-id/ID/url
// tabs/by-id/ID/console
@@ -63,10 +45,72 @@ function sanitize(s) {
return s.replace(/[^A-Za-z0-9_\-\.]/gm, '_');
}
-const debugged = {};
+/* if I could specify a custom editor interface for all the routing
+ below ... I would highlight the route names in blocks of some color
+ that sticks out, and let you collapse them. then you could get a
+ view of what the whole filesystem looks like at a glance. */
+const router = {};
+
+async function withTab(handler) {
+ return {
+ async read(path, fh, size, offset) {
+ const tab = await browser.tabs.get(parseInt(pathComponent(path, -2)));
+ return handler(tab);
+ }
+ };
+}
+async function fromScript(code) {
+ return {
+ async read(path, fh, size, offset) {
+ const tabId = parseInt(pathComponent(path, -2));
+ return browser.tabs.executeScript(tabId, {code});
+ }
+ };
+}
+
+router["/tabs/by-id"] = {
+ async entries() {
+ const tabs = await browser.tabs.query({});
+ return tabs.map(tab => String(tab.id));
+ }
+}
+router["/tabs/by-id/*/url"] = withTab(tab => tab.url + "\n");
+router["/tabs/by-id/*/title"] = withTab(tab => tab.title + "\n");
+router["/tabs/by-id/*/text"] = fromScript(`document.body.innerText`);
+router["/tabs/by-id/*/control"] = {
+ async write(path, buf) {
+ const tabId = parseInt(pathComponent(path, -2));
+ if (buf.trim() === 'close') {
+ await new Promise(resolve => chrome.tabs.remove(tabId, resolve));
+ } else {
+ throw new UnixError(unix.EIO);
+ }
+ }
+};
+
+router["/tabs/by-title"] = {
+ async entries() {
+ const tabs = await browser.tabs.query({});
+ return tabs.map(tab => sanitize(String(tab.title).slice(0, 200)) + "_" + String(tab.id));
+ }
+};
+router["/tabs/by-title/*"] = {
+ async getattr(path) {
+ const st_size = (await this.readlink(path)).length + 1;
+ return {
+ st_mode: unix.S_IFLNK | 0444,
+ st_nlink: 1,
+ // You _must_ return correct linkee path length from getattr!
+ st_size
+ };
+ },
+ async readlink(path) {
+ const parts = path.split("_");
+ const id = parts[parts.length - 1];
+ return "../by-id/" + id;
+ }
+};
-const router = {
- "tabs": {
/* "last-focused": {
* // FIXME: symlink to tab by id.
* async readlink() {
@@ -74,283 +118,134 @@ const router = {
* }
* },
*/
- "by-title": {
- async readdir() {
- const tabs = await browser.tabs.query({});
- return tabs.map(tab => sanitize(String(tab.title).slice(0, 200)) + "_" + String(tab.id));
- },
- "*": {
- async getattr(path) {
- const st_size = (await this.readlink(path)).length + 1;
- return {
- st_mode: unix.S_IFLNK | 0444,
- st_nlink: 1,
- // You _must_ return correct linkee path length from getattr!
- st_size
- };
- },
- async readlink(path) {
- const parts = path.split("_");
- const id = parts[parts.length - 1];
- return "../by-id/" + id;
- }
- }
- },
- "by-id": {
- async readdir() {
- const tabs = await browser.tabs.query({});
- return tabs.map(tab => String(tab.id));
- },
-
- "*": {
- "url": {
- async read(path, fh, size, offset) {
- const tab = await browser.tabs.get(parseInt(pathComponent(path, -2)));
- return (tab.url + "\n").substr(offset, size);
- }
- },
- "title": {
- async read(path, fh, size, offset) {
- const tab = await browser.tabs.get(parseInt(pathComponent(path, -2)));
- return (tab.title + "\n").substr(offset, size);
- }
- },
- "text": {
- async read(path, fh, size, offset) {
- const tabId = parseInt(pathComponent(path, -2));
- const [result] = await browser.tabs.executeScript(tabId, {code: "document.body.innerText"});
- return result.substr(offset, size)
- }
- },
- "snapshot.mhtml": {
- async read(path, fh, size, offset) {
- const tabId = parseInt(pathComponent(path, -2));
- await debugTab(tabId);
- await sendDebuggerCommand(tabId, "Page.enable", {});
-
- const {data} = await sendDebuggerCommand(tabId, "Page.captureSnapshot");
- return data.substr(offset, size)
- }
- },
- "screenshot.png": {
- // Broken. Filesystem hangs (? in JS?) and needs to be killed if you read this.
- async read(path, fh, size, offset) {
- const tabId = parseInt(pathComponent(path, -2));
- await debugTab(tabId);
- await sendDebuggerCommand(tabId, "Page.enable", {});
-
- const {data} = await sendDebuggerCommand(tabId, "Page.captureScreenshot");
- const buf = btoa(atob(data).substr(offset, size));
- return { buf, base64Encoded: true };
- }
- },
-
- "resources": {
- async opendir(path) {
- const tabId = parseInt(pathComponent(path, -2));
- await debugTab(tabId);
- return 0;
- },
- async readdir(path) {
- const tabId = parseInt(pathComponent(path, -2));
- if (!debugged[tabId]) throw new UnixError(unix.EIO);
-
- const {frameTree} = await sendDebuggerCommand(tabId, "Page.getResourceTree", {});
- return frameTree.resources.map(r => sanitize(String(r.url).slice(0, 200)));
- },
- async releasedir(path) {
- return 0;
- },
- "*": {
- async read(path, fh, size, offset) {
- const tabId = parseInt(pathComponent(path, -3));
- const suffix = pathComponent(path, -1);
-
- if (!debugged[tabId]) throw new UnixError(unix.EIO);
-
- await sendDebuggerCommand(tabId, "Page.enable", {});
-
- const {frameTree} = await sendDebuggerCommand(tabId, "Page.getResourceTree", {});
- for (let resource of frameTree.resources) {
- const resourceSuffix = sanitize(String(resource.url).slice(0, 200));
- if (resourceSuffix === suffix) {
- let {base64Encoded, content} = await sendDebuggerCommand(tabId, "Page.getResourceContent", {
- frameId: frameTree.frame.id,
- url: resource.url
- });
- if (base64Encoded) {
- const buf = btoa(atob(content).substr(offset, size));
- return { buf, base64Encoded: true };
- }
- return content.substr(offset, size);
- }
- }
- throw new UnixError(unix.ENOENT);
- }
- }
- },
-
- "control": {
- async write(path, buf) {
- const tabId = parseInt(pathComponent(path, -2));
- if (buf.trim() === 'close') {
- await new Promise(resolve => chrome.tabs.remove(tabId, resolve));
- } else {
- throw new UnixError(unix.EIO);
- }
- }
- }
- }
+// ensure that there are entries for all parents
+for (let key in router) {
+ let path = key;
+ while (path !== "/") { // walk upward through the path
+ path = path.substr(0, path.lastIndexOf("/"));
+
+ if (!router[path]) {
+ // find all direct children
+ const children = Object.keys(router)
+ .filter(k => k.startsWith(path) &&
+ (k.match(/\//g) || []).length ===
+ (path.match(/\//g) || []).length + 1)
+ .map(k => k.substr(path.length + 1))
+
+ if (path == '') path = '/';
+ router[path] = {entries() {
+ return children;
+ }}
}
}
-};
+}
+if (TESTING) {
+ const assert = require('assert');
+ (async () => {
+ assert.deepEqual(await router['/tabs/by-id/*'].entries(), ['url', 'title', 'text', 'control']);
+ assert.deepEqual(await router['/'].entries(), ['tabs']);
+ })()
+}
+console.log(router);
function findRoute(path) {
- let route = router;
let pathSegments = path.split("/");
+
if (pathSegments[pathSegments.length - 1].startsWith("._")) {
throw new UnixError(unix.ENOTSUP); // Apple Double file for xattrs
}
- for (let segment of pathSegments) {
- if (segment === "") continue;
- route = route[segment] || route["*"];
- if (!route) throw new UnixError(unix.ENOENT);
- }
- return route;
-}
+ let routingPath = "";
+ for (let segment of pathSegments) {
+ if (router[routingPath + "/" + segment]) {
+ routingPath += "/" + segment;
+ } else {
+ routingPath += "/*";
+ }
-async function getattr(path) {
- let route = findRoute(path);
- if (route.getattr) {
- return route.getattr(path);
- } else if (route.read || route.write) {
- // default file attrs
- return {
- st_mode: unix.S_IFREG | ((route.read && 0444) || (route.write && 0222)),
- st_nlink: 1,
- st_size: 100 // FIXME
- };
- } else {
- // default dir attrs
- return {
- st_mode: unix.S_IFDIR | 0755,
- st_nlink: 3
- };
+ if (!router[routingPath]) throw new UnixError(unix.ENOENT);
}
+ return router[routingPath];
}
-async function open(path) {
- let route = findRoute(path);
- if (route.open) return route.open(path);
- else return 0; // empty fh
-}
-
-async function read(path, fh, size, offset) {
- let route = findRoute(path);
- if (route.read) return route.read(path, fh, size, offset);
-}
-async function write(path, buf, offset) {
- let route = findRoute(path);
- if (route.write) return route.write(path, buf, offset);
-}
-async function release(path, fh) {
- let route = findRoute(path);
- if (route.release) return route.release(path, fh);
-}
-
-async function readlink(path) {
- let route = findRoute(path);
- if (route.readlink) return route.readlink(path);
-}
-
-async function opendir(path) {
- let route = findRoute(path);
- if (route.opendir) return route.opendir(path);
- else return 0; // empty fh
-}
-async function readdir(path) {
- let route = findRoute(path);
- if (route.readdir) return route.readdir(path);
- return Object.keys(route);
-}
-async function releasedir(path) {
- let route = findRoute(path);
- if (route.releasedir) return route.releasedir(path);
-}
-
-function log(...ss) {
- console.log(...ss);
-}
-
-let port;
-async function onMessage(req) {
- log('req', req);
-
- let response = { op: req.op, error: unix.EIO };
- /* console.time(req.op + ':' + req.path);*/
- try {
- if (req.op === 'getattr') {
- response = {
- op: 'getattr',
+const ops = {
+ async getattr({path}) {
+ let route = findRoute(path);
+ if (route.getattr) {
+ return {
st_mode: 0,
st_nlink: 0,
st_size: 0,
- ...(await getattr(req.path))
- };
- } else if (req.op === 'open') {
- response = {
- op: 'open',
- fh: await open(req.path)
+ ...(await route.getattr(path))
};
-
- } else if (req.op === 'read') {
- const ret = await read(req.path, req.fh, req.size, req.offset)
- const buf = typeof ret === 'string' ? ret : ret.buf;
- response = {
- op: 'read',
- buf
+ } else if (route.read || route.write) {
+ // default file attrs
+ return {
+ st_mode: unix.S_IFREG | ((route.read && 0444) || (route.write && 0222)),
+ st_nlink: 1,
+ st_size: 100 // FIXME
};
- if (ret.base64Encoded) response.base64Encoded = ret.base64Encoded;
-
- } else if (req.op === 'write') {
- // FIXME: decide whether base64 should be handled here
- // or in a higher layer?
- const ret = await write(req.path, atob(req.buf), req.offset)
- response = {
- op: 'write'
- };
-
- } else if (req.op === 'release') {
- await release(req.path, req.fh);
- response = {
- op: 'release'
- };
-
- } else if (req.op === 'readlink') {
- const buf = await readlink(req.path)
- response = {
- op: 'readlink',
- buf
+ } else {
+ // default dir attrs
+ return {
+ st_mode: unix.S_IFDIR | 0755,
+ st_nlink: 3,
+ st_size: 0
};
+ }
+ },
+
+ async open({path}) {
+ let route = findRoute(path);
+ if (route.open) return { fh: await route.open(path) };
+ else return { fh: 0 }; // empty fh
+ },
+
+ async read({path, fh, size, offset}) {
+ let route = findRoute(path);
+ if (route.read) return { buf: await route.read(path, fh, size, offset) };
+ },
+ async write({path, buf, offset}) {
+ let route = findRoute(path);
+ if (route.write) return route.write(path, atob(buf), offset);
+ },
+ async release({path, fh}) {
+ let route = findRoute(path);
+ if (route.release) return route.release(path, fh);
+ },
+
+ async readlink({path}) {
+ let route = findRoute(path);
+ if (route.readlink) return { buf: await route.readlink(path) };
+ },
+
+ async opendir({path}) {
+ let route = findRoute(path);
+ if (route.opendir) return { fh: await route.opendir(path) };
+ else return { fh: 0 }; // empty fh
+ },
+ async readdir({path}) {
+ let route = findRoute(path);
+ if (route.readdir) return { entries: await route.readdir(path) };
+ return { entries: [".", "..", ...Object.keys(route)] };
+ },
+ async releasedir({path}) {
+ let route = findRoute(path);
+ if (route.releasedir) return route.releasedir(path);
+ }
+};
- } else if (req.op === 'opendir') {
- response = {
- op: 'opendir',
- fh: await opendir(req.path)
- };
+let port;
+async function onMessage(req) {
+ console.log('req', req);
- } else if (req.op === 'readdir') {
- response = {
- op: 'readdir',
- entries: [".", "..", ...(await readdir(req.path))]
- };
+ let response = { op: req.op, error: unix.EIO };
+ /* console.time(req.op + ':' + req.path);*/
+ try {
+ response = await ops[req.op](req);
+ response.op = req.op;
- } else if (req.op === 'releasedir') {
- await releasedir(req.path, req.fh);
- response = { op: 'releasedir' };
- }
} catch (e) {
console.error(e);
response = {
@@ -360,7 +255,7 @@ async function onMessage(req) {
}
/* console.timeEnd(req.op + ':' + req.path);*/
- log('resp', response);
+ console.log('resp', response);
port.postMessage(response);
};
@@ -369,7 +264,7 @@ function tryConnect() {
/* console.log('hello', port);*/
/* updateToolbarIcon();*/
port.onMessage.addListener(onMessage);
- port.onDisconnect.addListener(p => {log('disconnect', p)});
+ port.onDisconnect.addListener(p => {console.log('disconnect', p)});
/* ws = new WebSocket("ws://localhost:8888");
* updateToolbarIcon();
@@ -387,7 +282,9 @@ function updateToolbarIcon() {
}
}
-tryConnect();
-chrome.browserAction.onClicked.addListener(function() {
+if (!TESTING) {
tryConnect();
-});
+ chrome.browserAction.onClicked.addListener(function() {
+ tryConnect();
+ });
+}