From 86cf0a0ae87e6d1981bded138e8712badf28c17b Mon Sep 17 00:00:00 2001 From: Omar Rizwan Date: Thu, 31 Dec 2020 17:33:11 -0800 Subject: [PATCH] update md, add some stubs, move console inside scope --- extension/background.js | 105 +++++++++++++++---------- fs/tabfs.c | 27 +++++-- tabfs.md | 165 ++++++++++++++++++++++++++++++++-------- 3 files changed, 219 insertions(+), 78 deletions(-) diff --git a/extension/background.js b/extension/background.js index 10be833..a78a7fd 100644 --- a/extension/background.js +++ b/extension/background.js @@ -228,25 +228,26 @@ router["/tabs/by-id"] = { router["/tabs/by-id/*/title.txt"] = withTab(tab => tab.title + "\n"); router["/tabs/by-id/*/text.txt"] = fromScript(`document.body.innerText`); })(); -let nextConsoleFh = 0; let consoleForFh = {}; -chrome.runtime.onMessage.addListener(data => { - if (!consoleForFh[data.fh]) return; - consoleForFh[data.fh].push(data.xs); -}); -router["/tabs/by-id/*/console"] = { - // this one is a bit weird. it doesn't start tracking until it's opened. - // tail -f console - async getattr() { - return { - st_mode: unix.S_IFREG | 0444, - st_nlink: 1, - st_size: 0 // FIXME - }; - }, - async open({path}) { - const tabId = parseInt(pathComponent(path, -2)); - const fh = nextConsoleFh++; - const code = ` +(function() { + let nextConsoleFh = 0; let consoleForFh = {}; + chrome.runtime.onMessage.addListener(data => { + if (!consoleForFh[data.fh]) return; + consoleForFh[data.fh].push(data.xs); + }); + router["/tabs/by-id/*/console"] = { + // this one is a bit weird. it doesn't start tracking until it's opened. + // tail -f console + async getattr() { + return { + st_mode: unix.S_IFREG | 0444, + st_nlink: 1, + st_size: 0 // FIXME + }; + }, + async open({path}) { + const tabId = parseInt(pathComponent(path, -2)); + const fh = nextConsoleFh++; + const code = ` // runs in 'content script' context var script = document.createElement('script'); var code = \` @@ -258,10 +259,13 @@ var code = \` console.__logFhs.add(${fh}); console.log = (...xs) => { console.__logOld(...xs); - // TODO: use random event for security instead of this broadcast - for (let fh of console.__logFhs) { - window.postMessage({fh: ${fh}, xs: xs}, '*'); - } + try { + // TODO: use random event for security instead of this broadcast + for (let fh of console.__logFhs) { + window.postMessage({fh: ${fh}, xs: xs}, '*'); + } + // error usually if one of xs is not serializable + } catch (e) { console.error(e); } }; })() \`; @@ -275,24 +279,25 @@ window.addEventListener('message', function({data}) { chrome.runtime.sendMessage(null, data); }); `; - consoleForFh[fh] = []; - await browser.tabs.executeScript(tabId, {code}); - return {fh}; - }, - async read({path, fh, offset, size}) { - const all = consoleForFh[fh].join('\n'); - // TODO: do this more incrementally ? - // will probably break down if log is huge - const buf = String.fromCharCode(...toUtf8Array(all).slice(offset, offset + size)); - return { buf }; - }, - async release({path, fh}) { - const tabId = parseInt(pathComponent(path, -2)); - // TODO: clean up the hooks inside the contexts - delete consoleForFh[fh]; - return {}; - } -}; + consoleForFh[fh] = []; + await browser.tabs.executeScript(tabId, {code}); + return {fh}; + }, + async read({path, fh, offset, size}) { + const all = consoleForFh[fh].join('\n'); + // TODO: do this more incrementally ? + // will probably break down if log is huge + const buf = String.fromCharCode(...toUtf8Array(all).slice(offset, offset + size)); + return { buf }; + }, + async release({path, fh}) { + const tabId = parseInt(pathComponent(path, -2)); + // TODO: clean up the hooks inside the contexts + delete consoleForFh[fh]; + return {}; + } + }; +})(); router["/tabs/by-id/*/execute-script"] = { // note: runs in a content script, _not_ in the Web page context async write({path, buf}) { @@ -304,6 +309,22 @@ router["/tabs/by-id/*/execute-script"] = { }, async truncate({path, size}) { return {}; } }; +// TODO: imports +// (function() { +// const imports = {}; +// // .json - autoparse, spit back out changes in data +// // .js +// // .png +// // write back modify +// router["/tabs/by-id/*/imports"] = { +// readdir({path}) { + +// } +// }; +// })(); +// TODO: watches +// router["/tabs/by-id/*/watches"] = { +// }; router["/tabs/by-id/*/window"] = { // a symbolic link to /windows/[id for this window] async readlink({path}) { @@ -326,7 +347,7 @@ router["/tabs/by-id/*/control"] = { // debugger/ : debugger-API-dependent (Chrome-only) (function() { - // console + // possible idea: console (using Log API instead of monkey-patching) // resources/ // TODO: scripts/ TODO: allow creation, eval immediately diff --git a/fs/tabfs.c b/fs/tabfs.c index c2c2715..c5ed91e 100644 --- a/fs/tabfs.c +++ b/fs/tabfs.c @@ -176,21 +176,38 @@ static int tabfs_unlink(const char *path) { return 0; } +static int tabfs_mkdir(const char *path, mode_t mode) { + send_request("{op: %Q, path: %Q, mode: %d}", "mkdir", path, mode); + + receive_response("{}", NULL); + return 0; +} + +static int tabfs_create(const char *path, mode_t mode, struct fuse_file_info *fi) { + send_request("{op: %Q, path: %Q, mode: %d}", "mkdir", path, mode); + + receive_response("{}", NULL); + return 0; +} + static struct fuse_operations tabfs_filesystem_operations = { - .getattr = tabfs_getattr, /* To provide size, permissions, etc. */ + .getattr = tabfs_getattr, .readlink = tabfs_readlink, - .open = tabfs_open, /* To enforce read-only access. */ - .read = tabfs_read, /* To provide file content. */ + .open = tabfs_open, + .read = tabfs_read, .write = tabfs_write, .release = tabfs_release, .opendir = tabfs_opendir, - .readdir = tabfs_readdir, /* To provide directory listing. */ + .readdir = tabfs_readdir, .releasedir = tabfs_releasedir, .truncate = tabfs_truncate, - .unlink = tabfs_unlink + .unlink = tabfs_unlink, + + .mkdir = tabfs_mkdir, + .create = tabfs_create }; int main(int argc, char **argv) { diff --git a/tabfs.md b/tabfs.md index 443395b..ffb6424 100644 --- a/tabfs.md +++ b/tabfs.md @@ -6,6 +6,8 @@ title: TabFS [TabFS](https://github.com/osnr/TabFS) is a browser extension that @@ -62,6 +64,10 @@ file](https://twitter.com/rsnous/status/1308588645872435202) that you can run whenever, and it's no different from scripting any other part of your computer. +### table of contents + +{{< table_of_contents >}} + ## Examples of stuff you can do![^examples] [^examples]: maybe some of these feel a little more vital and @@ -145,6 +151,13 @@ could do in the first place) $ cat mnt/tabs/by-id/*/text.txt > text-of-all-tabs.txt ``` +### Run script + +``` +$ echo 'document.body.style.background = "green"' > mnt/tabs/last-focused/execute-script +$ echo 'alert("hi!")' > mnt/tabs/last-focused/execute-script +``` + ### Reload an extension when you edit its source code Suppose you're working on a Chrome extension (apart from this @@ -177,18 +190,60 @@ that every time I want to reload my extension code. ### TODO: Live edit a running Web page -(TODO: it would be cool to have a persistent storage story here) +edit `page.html` in the tab folder. I guess it could just stomp +outerHTML at first, eventually could do something more sophisticated -### TODO: Something with live view of variables +(it would be cool to have a persistent storage story here +also. I like the idea of being able to put arbitrary files anywhere in +the subtree, actually, because then you could use git and emacs +autosave and stuff for free... hmm) -two floating windows. watch -mouse position? +### TODO: Watch expressions -### TODO: Import data (JSON or XLS). Persistent storage? +``` +$ touch mnt/tabs/last-focused/watches/window.scrollY +``` -hehehehehehehehehe +Now you can `cat window.scrollY` and see where you are scrolled on the +page at any time. -import a plotting library too? +Could make an [ad-hoc +dashboard](https://twitter.com/rsnous/status/1080878682275803137) +around a Web page: a bunch of terminal windows floating around your +screen, each sitting in a loop and using `cat` to monitor a different +variable. + +### TODO: Import data (JSON? XLS? JS?) + +drag a JSON file `foo.json` into the `imports` subfolder of the tab +and it shows up as the object `imports.foo` in JS. (modify +`imports.foo` in JS and then read `imports/foo.json` and you read the +changes back?) + +import a plotting library or whatever the same way? dragging +`plotlib.js` into `imports/plotlib.js` and then calling +`imports.plotlib()` to invoke that JS file + +the browser has a lot of potential power as an interactive programming +environment with built-in stuff. i think something that holds it back +that is underexplored is lack of ability to just... drag files in and +manage them with decent tools. many Web-based 'IDEs' have to reinvent +file management, etc from scratch, and it's like a separate universe +from the rest of your computer, and migrating between one and the +other is a real pain (if you want to use some Python library to munge +some data and then have a Web-based visualization of it, for instance, +or if you want to version files inside it, or make snapshots so you +[feel +comfortable](https://twitter.com/rsnous/status/1288725175895068673) +trying stuff, etc). + +(what would the persistent storage story here be? localStorage? it's +interesting because I almost want each tab to be [less of a +commodity](https://twitter.com/rsnous/status/1344753559007420416), +less +[disposable](https://twitter.com/rsnous/status/1270192308772691968), +since it's the site I'm dragging stuff to and it might have some +persistent state attached) ## Setup @@ -267,6 +322,12 @@ or $ ./install.sh chromium jimpolemfaeckpjijgapgkmolankohgj ``` +#### Firefox + +``` +$ ./install.sh firefox +``` + ### 3. Ready! Go back to `chrome://extensions` or @@ -366,7 +427,12 @@ GPLv3 - build more (GUI and CLI) tools on top, on both sides -- more persistence stuff +- more persistence stuff. as I said earlier, it would also be cool if + you could put arbitrary files in the subtrees, so .git, Mac extended + attrs, editor temp files, etc all work. make it able to behave like + a 'real' filesystem. also as I said earlier, some weirdness in the + fact that tabs are so disposable; they have a very different + lifecycle from most parts of my real filesystem. how to nudge that? - why can't Preview open images? GUI programs often struggle with the filesystem for some reason. CLI more reliable @@ -407,12 +473,12 @@ GPLv3 ## hmm -- there's a famous (?) paper, [Processes as Files +- [Processes as Files (1984)](https://lucasvr.gobolinux.org/etc/Killian84-Procfs-USENIX.pdf), -which lays out the case for the `/proc` filesystem. it's very cool! -very elegant in how it reapplies the existing interface of files to -the new domain of Unix processes. but how much do I care about Unix -processes now? most +[Julia Evans /proc comic](https://drawings.jvns.ca/proc/) lay out the +original `/proc` filesystem. it's very cool! very elegant in how it +reapplies the existing interface of files to the new domain of Unix +processes. but how much do I care about Unix processes now? most [programs](https://twitter.com/rsnous/status/1176587656915849218) that I care about running on my computer these days are Web pages, [not Unix @@ -420,7 +486,7 @@ processes](https://twitter.com/rsnous/status/1076229968017772544). so I want to take the approach of `/proc` -- 'expose the stuff you care about as a filesystem' -- and apply it to something [modern](https://twitter.com/rsnous/status/1251342095698112512): the -inside of the browser. can we have 'browser tabs as files'? +inside of the browser. 'browser tabs as files' - there are two 'operating systems' on my computer, the browser and Unix, and Unix is by far the more accessible and programmable and @@ -436,20 +502,28 @@ suggests making an extension is a whole Thing, a whole Project. like, why can't I just take a minute to ask my browser a question or tell it to automate something? lightness -- a lot of existing uses of these APIs are in an automation context: - testing your code on a robotic browser as part of some pipeline. I'm - much more interested in an interactive, end-user context. augmenting - the way I use my everyday browser. that's why this is an - extension. it doesn't require your browser to run in some weird - remote debugging mode that you'd always forget to turn on. it just - [stays +- a lot of existing uses of these browser control APIs are in an + automation context: testing your code on a robotic browser as part + of some pipeline. I'm much more interested in an interactive, + end-user context. augmenting the way I use my everyday + browser. that's why this is an extension. it doesn't require your + browser to run in some weird remote debugging mode that you'd always + forget to turn on. it just [stays running](https://twitter.com/rsnous/status/1340150818553561094) -- system call tracing (dtruss or strace) super useful when anything is - going wrong. (need to disable SIP on macOS, though.) the - combination of dtruss (application side) & console logging fs - request/response (filesystem side) gives a huge amount of insight - into basically any problem, end to end +- [system call tracing](https://jvns.ca/strace-zine-v2.pdf) (dtruss or + strace) super useful when anything is going wrong. (need to disable + SIP on macOS, though.) the combination of dtruss (application side) + & console logging fs request/response (filesystem side) gives a huge + amount of insight into basically any problem, end to end + + - there is sort of this sequence that I learned to try with + anything. first, either simple shell commands or pure C calls -- + shell commands are more ergonomic, C calls have the clearest + mental model of what syscalls they actually invoke. only then do + you move to the text editor or the Mac Finder, which are a lot + fancier and throw a lot more stuff at the filesystem at once (so + more can go wrong) - for a lot of things in the extension API, the browser can notify you of updates but there's no apparent way to query the full current @@ -460,7 +534,14 @@ to automate something? lightness - async/await was absolutely vital to making this readable -- open input space -- filesystem. (it reminds me of Screenotate.) +- filesystem as 'open input space' where there are things you can say + beyond what this particular filesystem cares about. (it reminds me + of my [Screenotate](https://screenotate.com) -- screenshots give you + this open field where you can [carry + through](https://twitter.com/rsnous/status/1221687986510680064) + stuff that the OCR doesn't necessarily recognize or care about. same + for the real world in Dynamicland; you can scribble notes or + whatever even if the computer doesn't see them) - now you have this whole 'language', this whole toolset, to control and automate your browser. there's this built-up existing capital @@ -474,13 +555,35 @@ files my tabs, to help me develop other things in the browser so I'd have actions I could trigger from my editor, ... -- stuff that looks cool: +- stuff that looks cool / is related: - - SQLite. OSQuery + - [SQLite virtual tables](https://www.sqlite.org/vtablist.html) + have some of the same energy as FUSE synthetic filesystems to + me, except instead of 'file operations', 'SQL' is the well-known + interface / knowledge base / ecosystem that they + [piggyback](https://twitter.com/rsnous/status/1237986368812224513) + on. [osquery](https://osquery.readthedocs.io/en/stable/) seems + particularly cool - - it has the right idea + - Plan 9. I think a lot about [extensibility in the Acme text + editor](https://mostlymaths.net/2013/03/extensibility-programming-acme-text-editor.html/), + where + [instead](https://twitter.com/geoffreylitt/status/1265384495542415360) + of a 'plugin API', the editor just provides a synthetic + filesystem -- fake filesystems talk + - my [fake filesystems talk](https://www.youtube.com/watch?v=pfHpDDXJQVg) + + - it has the right idea for + how to set up userscripts. just make files -- don't make [your + own weird UI to add and remove + them](https://twitter.com/rsnous/status/1196536798312140800). (I + guess there is a political or audience + [tradeoff](https://twitter.com/rsnous/status/1290031845363466242) + here, where [some + kinds](https://twitter.com/rsnous/status/1039036578427891713) of + users might be comfortable with managing files, but you might + alienate others. hmm) - [rmdir a non-empty directory](https://twitter.com/rsnous/status/1107427906832089088)