TabFS/tabfs.md
2020-12-27 18:20:34 -08:00

15 KiB

title
TabFS

TabFS is a browser extension that mounts your browser tabs as a filesystem on your computer.

Out of the box, it supports Chrome and (to a lesser extent1) Firefox, on macOS and Linux.2

Each of your open tabs is mapped to a folder.

The files inside a tab's folder directly reflect (and can control) the state of that tab in your browser. (TODO: update as I add more)

This gives you a ton of power, because now you can apply all the existing tools on your computer that already know how to deal with files -- terminal commands, scripting languages, etc -- and use them to control and communicate with your browser.

Now you don't need to code up a browser extension from scratch every time you want to do anything. You can write a script that talks to your browser in, like, a melange of Python and bash, and you can save it as a single ordinary file that you can run whenever, and it's no different from scripting any other part of your computer.

Examples of stuff you can do!

(assuming your current directory is the fs subdirectory of the git repo and you have the extension running)

(TODO: more of these)

List the titles of all the tabs you have open

$ cat mnt/tabs/by-id/*/title.txt
GitHub
Extensions
TabFS/install.sh at master · osnr/TabFS
Alternative Extension Distribution Options - Google Chrome
Web Store Hosting and Updating - Google Chrome
Home / Twitter
...

Close all Stack Overflow tabs

$ rm mnt/tabs/by-title/*Stack_Overflow*

or (older / more explicit)

$ echo remove | tee -a mnt/tabs/by-title/*Stack_Overflow*/control

btw

(this task, removing all tabs whose titles contain some string, is a little contrived, but it's not that unrealistic, right?)

(now... how would you do this without TabFS? I honestly have no idea, off the top of my head. like, how do you even get the titles of tabs? how do you tell the browser to close them?)

(I looked up the APIs, and, OK, if you're already in a browser extension, in a 'background script' inside the extension, and your extension has the tabs permission -- this already requires you to make 2 separate files and hop between your browser and your text editor to set it all up! -- you can do this: chrome.tabs.query({}, tabs => chrome.tabs.remove(tabs.filter(tab => tab.title.includes('Stack Overflow')).map(tab => tab.id))))

(not terrible, but look at all that upfront overhead to get it set up. and it's not all that discoverable. and what if you want to reuse this later, or plug it into some larger pipeline of tools on your computer, or give it a visual interface? the jump in complexity once you need to communicate with anything -- possibly setting up a WebSocket, setting up handlers and a state machine -- is pretty horrifying)

(but to be honest, I wouldn't even have conceived of this as a thing I could do in the first place)

Save text of all tabs to a file

$ cat mnt/tabs/by-id/*/text.txt > text-of-all-tabs.txt

Reload an extension when you edit its source code

Suppose you're working on a Chrome extension (apart from this one). It's a pain to reload the extension (and possibly affected Web pages) every time you change its code. There's a Stack Overflow post with ways to automate this, but they're all sort of hacky. You need yet another extension, or you need to tack weird permissions onto your work-in-progress extension, and you don't just get a command you can trigger from your editor or shell.

TabFS lets you do all this in an ordinary shell script. You don't have to write any browser-side code at all.

The script linked above turns the extension (this one's title is "Playgroundize DevTools Protocol") off, then turns it back on, then reloads any Chrome DevTools pages that are open:

#!/bin/bash -eux
echo false > mnt/extensions/Playg*/enabled
echo true > mnt/extensions/Playg*/enabled
echo reload | tee mnt/tabs/by-title/Chrome_Dev*/control

I mapped this script to Ctrl-. in my Emacs, so now I can just hit that every time I want to reload my extension code!

TODO: Cull tabs like any other files

I do this in Emacs dired.

TODO: Live edit a running Web page

(TODO: it would be cool to have a persistent storage story here)

TODO: Something with live view of variables

two floating windows. watch mouse position?

TODO: Import data (JSON or XLS). Persistent storage?

hehehehehehehehehe

import a plotting library too?

Setup

disclaimer: this extension is an experiment. I think it's cool and useful and provocative, and I usually leave it on, but I make no promises about functionality or, especially, security. applications may freeze, your browser may freeze, there may be ways for Web pages to use the extension to escape and hurt your computer ... In some sense, the whole point of this extension is to create a gigantic new surface area of communication between stuff inside your browser and software on the rest of your computer.

First, install the browser extension.

Then, install the C filesystem.

1. Install the browser extension

(I think for Opera or whatever other Chromium-based browser, you could get it to work, but you'd need to change the native messaging path in install.sh. Not sure about Safari. maybe Edge too? if you also got everything to compile for Windows)

in Chrome

Go to the Chrome extensions page. Enable Developer mode (top-right corner).

Load-unpacked the extension/ folder in this repo.

Make a note of the extension ID Chrome assigns. Mine is jimpolemfaeckpjijgapgkmolankohgj. We'll use this later.

in Firefox

You'll need to install as a "temporary extension", so it'll only last in your current FF session. (TODO: is this fixable? signature stuff?)

Go to about:debugging#/runtime/this-firefox.

Load Temporary Add-on...

Choose manifest.json in the extension subfolder of this repo.

2. Install the C filesystem

First, make sure you have FUSE and FUSE headers. On Linux, for example, sudo apt install libfuse-dev or equivalent. On macOS, get FUSE for macOS.

Then compile the C filesystem:

$ cd fs
$ mkdir mnt
$ make

Now install the native messaging host into your browser, so the extension can launch and talk to the filesystem:

Chrome and Chromium

Substitute the extension ID you copied earlier for jimpolemfaeckpjijgapgkmolankohgj in the command below.

$ ./install.sh chrome jimpolemfaeckpjijgapgkmolankohgj

or

$ ./install.sh chromium jimpolemfaeckpjijgapgkmolankohgj

3. Ready!

Go back to chrome://extensions or about:debugging#/runtime/this-firefox and reload the extension.

Now your browser tabs should be mounted in fs/mnt!

Open the background page inspector to see the filesystem operations stream in. (in Chrome, click "background page" next to "Inspect views" in the extension's entry in the Chrome extensions page; in Firefox, click "Inspect")

This console is also incredibly helpful for debugging anything that goes wrong, which probably will happen.

(My OS and applications are pretty chatty! They do a lot of operations, even when I don't feel like I'm actually doing anything.)

Design

  • fs/: Native FUSE filesystem, written in C
    • tabfs.c: Talks to FUSE, implements fs operations, talks to extension. I rarely have to change this file; it essentially is just a stub that forwards everything to the browser extension.
  • extension/: Browser extension, written in JS
    • background.js: The most interesting file. Defines all the synthetic files and what browser operations they invoke behind the scenes.3

My understanding is that when you, for example, cat mnt/tabs/by-id/6377/title.txt in the tab filesystem:

  1. cat on your computer does a system call open() down into macOS or Linux,

  2. macOS/Linux sees that this path is part of a FUSE filesystem, so it forwards the open() to the FUSE kernel module,

  3. FUSE forwards it to the tabfs_open implementation in our userspace filesystem in fs/tabfs.c,

  4. then tabfs_open rephrases the request as a JSON string and forwards it to our browser extension over stdout ('native messaging'),

  5. our browser extension in extension/background.js gets the incoming message; it triggers the route for /tabs/by-id/*/title.txt, which calls the browser extension API browser.tabs.get to get the data about tab ID 6377, including its title,

  6. so when cat does read() later, the title can get sent back in a JSON native message to tabfs.c and finally back to FUSE and the kernel and cat.

(very little actual work happened here, tbh. it's all just marshalling)

TODO: make diagrams?

License

GPLv3

things that would be good to do

  • multithreading. the key constraint is that I give -s to fuse_main in tabfs.c, which makes everything single-threaded. but I'm not clear on how much it would improve performance? maybe a lot, but not sure. maybe workload-dependent?

    the extension itself (and the standard I/O comm between the fs and the extension) would still be single-threaded, but you could interleave requests since most of that stuff is async. like the screenshot request that takes like half a second, you could do other stuff while waiting for the browser to get back to you on that (?)

    another issue is that applications tend to hang if any individual request hangs anyway; they're not expecting the filesystem to be so slow (and to be fair to them, they really have no way to). some of these are problems that may be inevitable for any FUSE filesystem, even ones you'd assume are reasonably battle-tested and well-engineered like sshfs?

  • look into support for Firefox / Windows / Safari / etc. best FUSE equiv for Windows? can you bridge to the remote debugging APIs that all of them have to get the augmented functionality? or just implement it all with JS monkey patching?

  • fix leaks

  • elim unnecessary round-trips / browser API calls

hmm

  • there's a famous (?) paper, Processes as Files (1984), 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 programs that I care about running on my computer these days are Web pages, not Unix processes. so I want to take the approach of /proc -- 'expose the stuff you care about as a filesystem' -- and apply it to something modern: the inside of the browser. can we have '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 cohesive as a computing environment (shells, processes), even though it's arguably the less important to my daily life. how can the browser take on these properties?

  • it's way too hard to make a browser extension. even 'make an extension' is a bad framing; it 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

  • open input space -- filesystem. (it reminds me of Screenotate.)

  • now you have this whole 'language', this whole toolset, to control and automate your browser. there's this built-up existing capital where lots of people and lots of application software and lots of programming languages ... already know the operations to work with files

  • this project is cool bc i immediately get a dataset i care about. I found myself using it 'authentically' pretty quickly -- to clear out 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:

  • fake filesystems talk

  • rmdir a non-empty directory. I feel like a new OS, something like Plan 9, should generalize its file I/O APIs just enough to avoid problems like this. like design them with the disk in mind but also a few concrete cases of synthetic filesystems, very slow remote filesystems, etc

do you like setting up sockets? I don't


  1. because of the absence of the chrome.debugger API for extensions. With a bit more plumbing, you could maybe find a way to connect it to the remote debugging protocol in Firefox and other browsers and get that second level of functionality that is currently Chrome-only. ↩︎

  2. It could probably be made to work on other browsers like Safari and Opera that support the WebExtensions API, and on Windows using Dokan or WinFUSE/WSL stuff (?), but I haven't looked into that. ↩︎

  3. it frustrates me that I can't have something like a table of contents for this source file. because it does have a structure to it! so I feel like the UI for looking at the file should highlight and exploit that structure.

    I want to link you to a particular route and talk about it here and also have some kind of transclusion (without the horrifying mess of making a lot of tiny separate files). I want to use typesetting and whitespace to set each route in that file apart, and set them as a whole apart from the utility functions & default implementations & networking. ↩︎