update md, add some stubs, move console inside scope

This commit is contained in:
Omar Rizwan 2020-12-31 17:33:11 -08:00
parent 957ea5a3d4
commit 86cf0a0ae8
3 changed files with 219 additions and 78 deletions

View file

@ -228,25 +228,26 @@ router["/tabs/by-id"] = {
router["/tabs/by-id/*/title.txt"] = withTab(tab => tab.title + "\n"); 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/*/text.txt"] = fromScript(`document.body.innerText`);
})(); })();
let nextConsoleFh = 0; let consoleForFh = {}; (function() {
chrome.runtime.onMessage.addListener(data => { let nextConsoleFh = 0; let consoleForFh = {};
if (!consoleForFh[data.fh]) return; chrome.runtime.onMessage.addListener(data => {
consoleForFh[data.fh].push(data.xs); 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. router["/tabs/by-id/*/console"] = {
// tail -f console // this one is a bit weird. it doesn't start tracking until it's opened.
async getattr() { // tail -f console
return { async getattr() {
st_mode: unix.S_IFREG | 0444, return {
st_nlink: 1, st_mode: unix.S_IFREG | 0444,
st_size: 0 // FIXME st_nlink: 1,
}; st_size: 0 // FIXME
}, };
async open({path}) { },
const tabId = parseInt(pathComponent(path, -2)); async open({path}) {
const fh = nextConsoleFh++; const tabId = parseInt(pathComponent(path, -2));
const code = ` const fh = nextConsoleFh++;
const code = `
// runs in 'content script' context // runs in 'content script' context
var script = document.createElement('script'); var script = document.createElement('script');
var code = \` var code = \`
@ -258,10 +259,13 @@ var code = \`
console.__logFhs.add(${fh}); console.__logFhs.add(${fh});
console.log = (...xs) => { console.log = (...xs) => {
console.__logOld(...xs); console.__logOld(...xs);
// TODO: use random event for security instead of this broadcast try {
for (let fh of console.__logFhs) { // TODO: use random event for security instead of this broadcast
window.postMessage({fh: ${fh}, xs: xs}, '*'); 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); chrome.runtime.sendMessage(null, data);
}); });
`; `;
consoleForFh[fh] = []; consoleForFh[fh] = [];
await browser.tabs.executeScript(tabId, {code}); await browser.tabs.executeScript(tabId, {code});
return {fh}; return {fh};
}, },
async read({path, fh, offset, size}) { async read({path, fh, offset, size}) {
const all = consoleForFh[fh].join('\n'); const all = consoleForFh[fh].join('\n');
// TODO: do this more incrementally ? // TODO: do this more incrementally ?
// will probably break down if log is huge // will probably break down if log is huge
const buf = String.fromCharCode(...toUtf8Array(all).slice(offset, offset + size)); const buf = String.fromCharCode(...toUtf8Array(all).slice(offset, offset + size));
return { buf }; return { buf };
}, },
async release({path, fh}) { async release({path, fh}) {
const tabId = parseInt(pathComponent(path, -2)); const tabId = parseInt(pathComponent(path, -2));
// TODO: clean up the hooks inside the contexts // TODO: clean up the hooks inside the contexts
delete consoleForFh[fh]; delete consoleForFh[fh];
return {}; return {};
} }
}; };
})();
router["/tabs/by-id/*/execute-script"] = { router["/tabs/by-id/*/execute-script"] = {
// note: runs in a content script, _not_ in the Web page context // note: runs in a content script, _not_ in the Web page context
async write({path, buf}) { async write({path, buf}) {
@ -304,6 +309,22 @@ router["/tabs/by-id/*/execute-script"] = {
}, },
async truncate({path, size}) { return {}; } 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"] = { router["/tabs/by-id/*/window"] = {
// a symbolic link to /windows/[id for this window] // a symbolic link to /windows/[id for this window]
async readlink({path}) { async readlink({path}) {
@ -326,7 +347,7 @@ router["/tabs/by-id/*/control"] = {
// debugger/ : debugger-API-dependent (Chrome-only) // debugger/ : debugger-API-dependent (Chrome-only)
(function() { (function() {
// console // possible idea: console (using Log API instead of monkey-patching)
// resources/ // resources/
// TODO: scripts/ TODO: allow creation, eval immediately // TODO: scripts/ TODO: allow creation, eval immediately

View file

@ -176,21 +176,38 @@ static int tabfs_unlink(const char *path) {
return 0; 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 = { static struct fuse_operations tabfs_filesystem_operations = {
.getattr = tabfs_getattr, /* To provide size, permissions, etc. */ .getattr = tabfs_getattr,
.readlink = tabfs_readlink, .readlink = tabfs_readlink,
.open = tabfs_open, /* To enforce read-only access. */ .open = tabfs_open,
.read = tabfs_read, /* To provide file content. */ .read = tabfs_read,
.write = tabfs_write, .write = tabfs_write,
.release = tabfs_release, .release = tabfs_release,
.opendir = tabfs_opendir, .opendir = tabfs_opendir,
.readdir = tabfs_readdir, /* To provide directory listing. */ .readdir = tabfs_readdir,
.releasedir = tabfs_releasedir, .releasedir = tabfs_releasedir,
.truncate = tabfs_truncate, .truncate = tabfs_truncate,
.unlink = tabfs_unlink .unlink = tabfs_unlink,
.mkdir = tabfs_mkdir,
.create = tabfs_create
}; };
int main(int argc, char **argv) { int main(int argc, char **argv) {

165
tabfs.md
View file

@ -6,6 +6,8 @@ title: TabFS
<style> <style>
body { font-family: Verdana, sans-serif; background: #eee; } body { font-family: Verdana, sans-serif; background: #eee; }
h1 { font-family: Helvetica; } h1 { font-family: Helvetica; }
#TableOfContents > ul > li:first-child { display: none; }
#TableOfContents a[rel=footnote] { display: none; }
</style> </style>
[TabFS](https://github.com/osnr/TabFS) is a browser extension that [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 can run whenever, and it's no different from scripting any other part
of your computer. of your computer.
### table of contents
{{< table_of_contents >}}
## Examples of stuff you can do![^examples] ## Examples of stuff you can do![^examples]
[^examples]: maybe some of these feel a little more vital and [^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 $ 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 ### Reload an extension when you edit its source code
Suppose you're working on a Chrome extension (apart from this 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: 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 ### TODO: Watch expressions
mouse position?
### 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 ## Setup
@ -267,6 +322,12 @@ or
$ ./install.sh chromium jimpolemfaeckpjijgapgkmolankohgj $ ./install.sh chromium jimpolemfaeckpjijgapgkmolankohgj
``` ```
#### Firefox
```
$ ./install.sh firefox
```
### 3. Ready! ### 3. Ready!
Go back to `chrome://extensions` or Go back to `chrome://extensions` or
@ -366,7 +427,12 @@ GPLv3
- build more (GUI and CLI) tools on top, on both sides - 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 - why can't Preview open images? GUI programs often struggle with the
filesystem for some reason. CLI more reliable filesystem for some reason. CLI more reliable
@ -407,12 +473,12 @@ GPLv3
## hmm ## hmm
- there's a famous (?) paper, [Processes as Files - [Processes as Files
(1984)](https://lucasvr.gobolinux.org/etc/Killian84-Procfs-USENIX.pdf), (1984)](https://lucasvr.gobolinux.org/etc/Killian84-Procfs-USENIX.pdf),
which lays out the case for the `/proc` filesystem. it's very cool! [Julia Evans /proc comic](https://drawings.jvns.ca/proc/) lay out the
very elegant in how it reapplies the existing interface of files to original `/proc` filesystem. it's very cool! very elegant in how it
the new domain of Unix processes. but how much do I care about Unix reapplies the existing interface of files to the new domain of Unix
processes now? most processes. but how much do I care about Unix processes now? most
[programs](https://twitter.com/rsnous/status/1176587656915849218) that [programs](https://twitter.com/rsnous/status/1176587656915849218) that
I care about running on my computer these days are Web pages, [not I care about running on my computer these days are Web pages, [not
Unix 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 I want to take the approach of `/proc` -- 'expose the stuff you care
about as a filesystem' -- and apply it to something about as a filesystem' -- and apply it to something
[modern](https://twitter.com/rsnous/status/1251342095698112512): the [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 - there are two 'operating systems' on my computer, the browser and
Unix, and Unix is by far the more accessible and programmable 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 why can't I just take a minute to ask my browser a question or tell it
to automate something? lightness to automate something? lightness
- a lot of existing uses of these APIs are in an automation context: - a lot of existing uses of these browser control APIs are in an
testing your code on a robotic browser as part of some pipeline. I'm automation context: testing your code on a robotic browser as part
much more interested in an interactive, end-user context. augmenting of some pipeline. I'm much more interested in an interactive,
the way I use my everyday browser. that's why this is an end-user context. augmenting the way I use my everyday
extension. it doesn't require your browser to run in some weird browser. that's why this is an extension. it doesn't require your
remote debugging mode that you'd always forget to turn on. it just browser to run in some weird remote debugging mode that you'd always
[stays forget to turn on. it just [stays
running](https://twitter.com/rsnous/status/1340150818553561094) running](https://twitter.com/rsnous/status/1340150818553561094)
- system call tracing (dtruss or strace) super useful when anything is - [system call tracing](https://jvns.ca/strace-zine-v2.pdf) (dtruss or
going wrong. (need to disable SIP on macOS, though.) the strace) super useful when anything is going wrong. (need to disable
combination of dtruss (application side) & console logging fs SIP on macOS, though.) the combination of dtruss (application side)
request/response (filesystem side) gives a huge amount of insight & console logging fs request/response (filesystem side) gives a huge
into basically any problem, end to end 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 - 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 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 - 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 - now you have this whole 'language', this whole toolset, to control
and automate your browser. there's this built-up existing capital 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 my tabs, to help me develop other things in the browser so I'd have
actions I could trigger from my editor, ... 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
- <https://luciopaiva.com/witchcraft/> 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)
- <https://luciopaiva.com/witchcraft/> 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 - [rmdir a non-empty
directory](https://twitter.com/rsnous/status/1107427906832089088) directory](https://twitter.com/rsnous/status/1107427906832089088)