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 and Safari, on macOS and Linux.2

(update: You can now sponsor me to help support further
development of TabFS
🙂

Each of your open tabs is mapped to a folder.


I have 3 tabs open, and
they map to 3 folders in TabFS

The files inside a tab’s folder directly reflect (and can control) the
state of that tab in your browser.

Going through the files inside a tab’s folder. For
example, the url.txt, text.txt, and title.txt files tell me those live
properties of this tab

(Read more up-to-date documentation for all of
TabFS’s files here.)

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, point-and-click explorers, 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.

table of contents

Examples of stuff you can do!3

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

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
...

Cull tabs like any other files

Selecting and deleting a bunch of tabs in my file manager

I’m using Dired in Emacs here, but you could use whatever tools you
already feel comfortable managing your files with.

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

Evaluate JavaScript on a page / watch expressions: demo

(was evals in linked demo, is now renamed to watches)

$ touch mnt/tabs/last-focused/watches/'document.body.style.background = "green"'

$ touch mnt/tabs/last-focused/watches/'alert("hi!")'

$ touch mnt/tabs/last-focused/watches/'2 + 2'
$ cat mnt/tabs/last-focused/watches/'2 + 2'
4
$ touch mnt/tabs/last-focused/watches/window.scrollY

Now you can cat window.scrollY and see where you are scrolled on the
page at any time.

Could make an ad-hoc
dashboard

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.

Get images / scripts / other resource files from page

(TODO: document better, put in screenshots)

The debugger/
subdirectory

in each tab folder has synthetic files that let you access loaded
resources (in debugger/resources/) and scripts (in
debugger/scripts/).

Images will show up as actual PNG or JPEG files, scripts as actual JS
files, and so on. (this is experimental.)

(TODO: edit the images in place? you can already kinda edit the
scripts in place)

Retrieve what’s playing on YouTube Music: youtube-music-tabfs

thanks to Junho Yeo!

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 to refresh the extension.

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

This script turns an extension (this one’s title is “Playgroundize
DevTools Protocol”) off, then turns it back on, then reloads any tabs
that have the relevant pages open (in this case, I decided it’s tabs
whose titles start with “Chrome Dev”):

#!/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 text editor, and now I just hit
that every time I want to reload my extension code.

TODO: Live edit a running Web page

edit page.html in the tab folder. I guess it could just stomp
outerHTML at first, eventually could do something more sophisticated

then you can use your existing text editor! and you’ll always know
that if the file saved, then it’s up to date in the browser. no flaky
watcher that you’re not sure if it’s working

(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)

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, one where graphics come as
naturally
as
console I/O do in most programming languages. 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

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
,
less
disposable,
since now it’s the site I’m dragging stuff to and it might have some
persistent state attached. like, if I’m programming and editing stuff
and saving inside a tab’s folder, that tab suddenly really
matters; I
want it to survive as long as a normal file would, unlike most browser
tabs today)

(the combination of these last 3 TODOs may be a very powerful, open,
dynamic, flexible programming environment where you can bring whatever
external tools you want to bear, everything is live in your browser,
you never need to restart…)

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.

(The installation process is pretty involved right now. I’d like to
simplify it, but I also don’t want a seamless installation process
that does a bad job of managing people’s expectations. And it’s
important to me that users feel
comfortable

looking at how TabFS works — it’s pretty much just two
files! — and that they can mess around with it; it shouldn’t be a
black box.)

Before doing anything, clone this repository:

$ git clone https://github.com/osnr/TabFS.git

First, install the browser extension.

Then, install the C filesystem.

1. Install the browser extension

(including Brave and Vivaldi)

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 Safari (WIP)

See the Safari
instructions
. You
should compile the C filesystem (as below) before trying to run the extension.

in Firefox

You’ll need to install as a “temporary extension”, so it’ll only last
in your current FF session. (If you want to install permanently, see
this
issue
.)

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
macFUSE. (on macOS, also see this
-bug
— TODO work out the
best path to explain here)

Then compile the C filesystem:

$ cd fs
$ mkdir mnt
$ make

(GNU Make is required, so use gmake on FreeBSD)

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

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

$ ./install.sh chrome jimpolemfaeckpjijgapgkmolankohgj

(For Chromium, say chromium instead of chrome. For Vivaldi, say
vivaldi instead. For Brave, say chrome. You can look at the
contents of
install.sh for
the latest on browser and OS support.)

Safari (WIP)

See the Safari
instructions
.

Firefox

$ ./install.sh firefox

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. (If you get a generic I/O
error at the shell when running a command on TabFS, that probably
means that an exception happened which you can check here.)

(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. My sense is that macOS is generally chattier than Linux.)

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.4

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

Thanks to all the project sponsors. Special
thanks to:

things that could/should be done

(maybe you can do these? lots of people are already pitching in on
GitHub
; I wish it was easier for me to
keep up listing them all here!)

  • add more synthetic files!! (it’s just
    JavaScript)

    view DOM nodes, snapshot current HTML of page, spelunk into living
    objects. see what your code is doing. make more files writable also

  • build more (GUI and CLI) tools on top, on both sides

  • 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

  • multithreading. the key constraint is that I pass -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 stdin/stdout 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 (?)

    update: we are
    multithreaded
    now, thanks to
    huglovefan!

    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 problems may be inevitable for any FUSE
    filesystem, even ones you’d assume are reasonably battle-tested and
    well-engineered like sshfs?

  • other performance stuff — remembering when we’re already attached
    to things, reference counting, minimizing browser roundtrips. not
    sure impact of these

  • TypeScript (how to do with the minimum amount of build system and
    package manager nonsense?) (now realizing that if I had gone with
    TypeScript, I would then have to ask people to install npm and
    webpack and the TS compiler and whatever just to get this
    running. really, really glad I didn’t.) maybe we can just do dynamic
    type checking at the fs op call boundaries?

  • 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 already have to get the augmented
    functionality? or just implement it all with JS monkey patching?

  • window management. tab management where you can move tabs. ‘merge
    all windows’. history management

hmm

  • Processes as Files
    (1984)
    ,
    Julia Evans /proc comic 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 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. ‘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 (it has concepts that compose!
    shell, processes, files), even though it’s arguably the less important
    to my daily life. how can the browser take on more of the properties
    of Unix?

  • 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

  • “files are a sort of approachable ‘bridge’ that everyone knows how
    to interact with” / files are like one of the first things you learn
    if you know any programming language / “because of this fs thing any
    beginner coding thing can make use of it now”

  • 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

  • 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

    • 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
    state. so we’d need to sit in a lot of these places from the
    beginning and accumulate the incoming events to know, like, the last
    time a tab was updated, or the list of scripts currently running on
    a tab

  • async/await was absolutely vital to making this readable

  • 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 — screenshots give you
    this open field where you can carry
    through

    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
    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 / is related:

  • rmdir a non-empty
    directory

    — when I was thinking if you should be able to rm by-id/TABID
    even though TABID is a folder. 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

Read More