This page is automatically generated from extension/background.js in the TabFS source code.
It documents each of the folders and files that TabFS serves up from your browser.
Variables here, like :TAB_TITLE and #TAB_ID, are stand-ins for concrete values of what you actually have open in your browser.
(work in progress)
Routes["/tabs/create"] = {
description: 'Create a new tab.',
usage: 'echo "https://www.google.com" > $0',
async write({buf}) {
const url = buf.trim();
await browser.tabs.create({url});
return {size: stringToUtf8Array(buf).length};
},
async truncate() { return {}; }
};
Routes["/tabs/by-title"] = {
description: 'Open tabs, organized by title; each subfolder represents an open tab.',
usage: 'ls $0',
getattr() {
return {
st_mode: unix.S_IFDIR | 0777, // writable so you can delete tabs
st_nlink: 3,
st_size: 0,
};
},
async readdir() {
const tabs = await browser.tabs.query({});
return { entries: [".", "..", ...tabs.map(tab => sanitize(String(tab.title)) + "." + String(tab.id))] };
}
};
Routes["/tabs/by-title/:TAB_TITLE.#TAB_ID"] = {
description: `Represents one open tab.
It's a symbolic link to the folder /tabs/by-id/#TAB_ID.`,
// TODO: date
usage: ['rm $0'],
async readlink({tabId}) {
return { buf: "../by-id/" + tabId };
},
async unlink({tabId}) {
await browser.tabs.remove(tabId);
return {};
}
};
Routes["/tabs/last-focused"] = {
description: `Represents the most recently focused tab.
It's a symbolic link to the folder /tabs/by-id/[ID of most recently focused tab].`,
async readlink() {
const id = (await browser.tabs.query({ active: true, lastFocusedWindow: true }))[0].id;
return { buf: "by-id/" + id };
}
};
Routes["/tabs/by-id"] = {
description: `Open tabs, organized by ID; each subfolder represents an open tab.`,
usage: 'ls $0',
async readdir() {
const tabs = await browser.tabs.query({});
return { entries: [".", "..", ...tabs.map(tab => String(tab.id))] };
}
};
Routes["/tabs/by-id/#TAB_ID/url.txt"] = {
description: `Text file containing the current URL of this tab.`,
usage: ['cat $0',
'echo "https://www.google.com" > $0'],
...routeForTab(tab => tab.url + "\n",
buf => ({ url: buf }))
};
Routes["/tabs/by-id/#TAB_ID/title.txt"] = {
description: `Text file containing the current title of this tab.`,
usage: 'cat $0',
...routeForTab(tab => tab.title + "\n")
};
Routes["/tabs/by-id/#TAB_ID/text.txt"] = {
description: `Text file containing the current body text of this tab.`,
usage: 'cat $0',
...routeFromScript(`document.body.innerText`)
};
Routes["/tabs/by-id/#TAB_ID/body.html"] = {
description: `Text file containing the current body HTML of this tab.`,
usage: 'cat $0',
...routeFromScript(`document.body.innerHTML`)
};
Routes["/tabs/by-id/#TAB_ID/active"] = {
description: 'Text file containing `true` or `false` depending on whether this tab is active in its window.',
usage: ['cat $0',
'echo true > $0'],
...routeForTab(
tab => JSON.stringify(tab.active) + '\n',
// WEIRD: we do startsWith because you might end up with buf
// being "truee" (if it was "false", then someone wrote "true")
buf => ({ active: buf.startsWith("true") })
)
};
Routes["/tabs/by-id/#TAB_ID/evals"] = {
...evals.routeForRoot,
description: `Add JavaScript files to this folder to evaluate them in the tab.`,
usage: 'ls $0'
};
Routes["/tabs/by-id/#TAB_ID/evals/:FILENAME"] = {
...evals.routeForFilename,
// FIXME: use $0 here
usage: ['echo "2 + 2" > tabs/by-id/#TAB_ID/evals/twoplustwo.js',
'cat tabs/by-id/#TAB_ID/evals/twoplustwo.js.result'],
async write(req) {
const ret = await evals.routeForFilename.write(req);
const code = evals.directory[req.path];
evals.directory[req.path + '.result'] = JSON.stringify((await browser.tabs.executeScript(req.tabId, {code}))[0]) + '\n';
return ret;
}
};
Routes["/tabs/by-id/#TAB_ID/watches"] = {
description: `Put a file in this folder with a JS expression as its filename.
Read that file to evaluate and return the current value of that JS expression.`,
usage: 'ls $0',
async readdir({tabId}) {
return { entries: [".", "..", ...Object.keys(watches[tabId] || [])] };
},
getattr() {
return {
st_mode: unix.S_IFDIR | 0777, // writable so you can create/rm watches
st_nlink: 3,
st_size: 0,
};
},
};
Routes["/tabs/by-id/#TAB_ID/watches/:EXPR"] = {
description: `A file with a JS expression :EXPR as its filename.`,
usage: `touch '/tabs/by-id/#TAB_ID/watches/2+2' && cat '/tabs/by-id/#TAB_ID/watches/2+2'`,
// NOTE: eval runs in extension's content script, not in original page JS context
async mknod({tabId, expr, mode}) {
watches[tabId] = watches[tabId] || {};
watches[tabId][expr] = async function() {
return (await browser.tabs.executeScript(tabId, {code: expr}))[0];
};
return {};
},
async unlink({tabId, expr}) {
delete watches[tabId][expr]; // TODO: also delete watches[tabId] if empty
return {};
},
...makeRouteWithContents(async ({tabId, expr}) => {
if (!watches[tabId] || !(expr in watches[tabId])) { throw new UnixError(unix.ENOENT); }
return JSON.stringify(await watches[tabId][expr]()) + '\n';
}, () => {
// setData handler -- only providing this so that getattr reports
// that the file is writable, so it can be deleted without annoying prompt.
throw new UnixError(unix.EPERM);
})
};
Routes["/tabs/by-id/#TAB_ID/window"] = {
description: `The window that this tab lives in;
a symbolic link to the folder /windows/[id for this window].`,
async readlink({tabId}) {
const tab = await browser.tabs.get(tabId);
return { buf: "../../../windows/" + tab.windowId };
}
};
Routes["/tabs/by-id/#TAB_ID/control"] = {
description: `Write control commands to this file to control this tab;
see https://developer.chrome.com/extensions/tabs.`,
usage: ['echo remove > $0',
'echo reload > $0',
'echo goForward > $0',
'echo goBack > $0',
'echo discard > $0'],
async write({tabId, buf}) {
const command = buf.trim();
await browser.tabs[command](tabId);
return {size: stringToUtf8Array(buf).length};
},
async truncate({size}) { return {}; }
};
Routes["/tabs/by-id/#TAB_ID/inputs"] = {
description: `Contains a file for each text input and textarea on this page (as long as it has an ID, currently).`,
async readdir({tabId}) {
// TODO: assign new IDs to inputs without them?
const code = `Array.from(document.querySelectorAll('textarea, input[type=text]'))
.map(e => e.id).filter(id => id)`;
const ids = (await browser.tabs.executeScript(tabId, {code}))[0];
return { entries: [".", "..", ...ids.map(id => `${id}.txt`)] };
}
};
Routes["/tabs/by-id/#TAB_ID/inputs/:INPUT_ID.txt"] = makeRouteWithContents(async ({tabId, inputId}) => {
const code = `document.getElementById('${inputId}').value`;
const inputValue = (await browser.tabs.executeScript(tabId, {code}))[0];
if (inputValue === null) { throw new UnixError(unix.ENOENT); } /* FIXME: hack to deal with if inputId isn't valid */
return inputValue;
}, async ({tabId, inputId}, buf) => {
const code = `document.getElementById('${inputId}').value = unescape('${escape(buf)}')`;
await browser.tabs.executeScript(tabId, {code});
});
Routes["/windows"] = {
async readdir() {
const windows = await browser.windows.getAll();
return { entries: [".", "..", ...windows.map(window => String(window.id))] };
}
};
Routes["/windows/last-focused"] = {
description: `A symbolic link to /windows/[id for the last focused window].`,
async readlink() {
const windowId = (await browser.windows.getLastFocused()).id;
return { buf: windowId };
}
};
Routes["/windows/#WINDOW_ID/visible-tab.png"] = { ...makeRouteWithContents(async ({windowId}) => {
// screen capture is a window thing and not a tab thing because you
// can only capture the visible tab for each window anyway; you
// can't take a screenshot of just any arbitrary tab
const dataUrl = await browser.tabs.captureVisibleTab(windowId, {format: 'png'});
return Uint8Array.from(atob(dataUrl.substr(("data:image/png;base64,").length)),
c => c.charCodeAt(0));
}), async getattr() {
return {
st_mode: unix.S_IFREG | 0444,
st_nlink: 1,
st_size: 10000000 // hard-code to 10MB for now
};
} };
Routes["/extensions"] = {
async readdir() {
const infos = await browser.management.getAll();
return { entries: [".", "..", ...infos.map(info => `${sanitize(info.name)}.${info.id}`)] };
}
};
Routes["/extensions/:EXTENSION_TITLE.:EXTENSION_ID/enabled"] = { ...makeRouteWithContents(async ({extensionId}) => {
const info = await browser.management.get(extensionId);
return String(info.enabled) + '\n';
}, async ({extensionId}, buf) => {
await browser.management.setEnabled(extensionId, buf.trim() === "true");
// suppress truncate so it doesn't accidentally flip the state when you do, e.g., `echo true >`
}), truncate() { return {}; } };
Routes["/runtime/reload"] = {
async write({buf}) {
await browser.runtime.reload();
return {size: stringToUtf8Array(buf).length};
},
truncate() { return {}; }
};