} Promise that resolves with command result
+ */
function sendDebuggerCommand(tabId, method, commandParams) {
return new Promise((resolve, reject) =>
- chrome.debugger.sendCommand({tabId}, method, commandParams, result => {
+ chrome.debugger.sendCommand({ tabId }, method, commandParams, result => {
if (result) { resolve(result); } else { reject(chrome.runtime.lastError); }
})
);
@@ -567,20 +671,20 @@ see https://developer.chrome.com/extensions/tabs.`,
// TODO: scripts/ TODO: allow creation, eval immediately
Routes["/tabs/by-id/#TAB_ID/debugger/resources"] = {
- async readdir({tabId}) {
+ async readdir({ tabId }) {
await TabManager.debugTab(tabId); await TabManager.enableDomainForTab(tabId, "Page");
- const {frameTree} = await sendDebuggerCommand(tabId, "Page.getResourceTree", {});
+ const { frameTree } = await sendDebuggerCommand(tabId, "Page.getResourceTree", {});
return { entries: [".", "..", ...frameTree.resources.map(r => sanitize(String(r.url)))] };
}
};
- Routes["/tabs/by-id/#TAB_ID/debugger/resources/:SUFFIX"] = makeRouteWithContents(async ({path, tabId, suffix}) => {
+ Routes["/tabs/by-id/#TAB_ID/debugger/resources/:SUFFIX"] = makeRouteWithContents(async ({ path: _, tabId, suffix }) => {
await TabManager.debugTab(tabId); await TabManager.enableDomainForTab(tabId, "Page");
- const {frameTree} = await sendDebuggerCommand(tabId, "Page.getResourceTree", {});
+ const { frameTree } = await sendDebuggerCommand(tabId, "Page.getResourceTree", {});
for (let resource of frameTree.resources) {
const resourceSuffix = sanitize(String(resource.url));
if (resourceSuffix === suffix) {
- let {base64Encoded, content} = await sendDebuggerCommand(tabId, "Page.getResourceContent", {
+ let { base64Encoded, content } = await sendDebuggerCommand(tabId, "Page.getResourceContent", {
frameId: frameTree.frame.id,
url: resource.url
});
@@ -591,18 +695,27 @@ see https://developer.chrome.com/extensions/tabs.`,
throw new UnixError(unix.ENOENT);
});
Routes["/tabs/by-id/#TAB_ID/debugger/scripts"] = {
- async opendir({tabId}) {
+ async opendir({ tabId }) {
await TabManager.debugTab(tabId); await TabManager.enableDomainForTab(tabId, "Debugger");
return { fh: 0 };
},
- async readdir({tabId}) {
+ async readdir({ tabId }) {
// it's useful to put the ID first in the script filenames, so
// the .js extension stays on the end
const scriptFileNames = Object.values(TabManager.scriptsForTab[tabId])
- .map(params => params.scriptId + "_" + sanitize(params.url));
+ .map(params => params.scriptId + "_" + sanitize(params.url));
return { entries: [".", "..", ...scriptFileNames] };
}
};
+ /**
+ * @param {number} tabId - Tab ID to get script info for
+ * @param {string} filename - Script filename
+ * @typedef {object} DebuggerScriptInfo
+ * @property {string} scriptId - Script ID
+ * @property {string} url - Script URL
+ * @returns {DebuggerScriptInfo} Script info object
+ * @throws {UnixError} If script not found
+ */
function pathScriptInfo(tabId, filename) {
const [scriptId, ...rest] = filename.split("_");
const scriptInfo = TabManager.scriptsForTab[tabId][scriptId];
@@ -611,42 +724,42 @@ see https://developer.chrome.com/extensions/tabs.`,
}
return scriptInfo;
}
- Routes["/tabs/by-id/#TAB_ID/debugger/scripts/:FILENAME"] = makeRouteWithContents(async ({tabId, filename}) => {
+ Routes["/tabs/by-id/#TAB_ID/debugger/scripts/:FILENAME"] = makeRouteWithContents(async ({ tabId, filename }) => {
await TabManager.debugTab(tabId);
await TabManager.enableDomainForTab(tabId, "Page");
await TabManager.enableDomainForTab(tabId, "Debugger");
- const {scriptId} = pathScriptInfo(tabId, filename);
- const {scriptSource} = await sendDebuggerCommand(tabId, "Debugger.getScriptSource", {scriptId});
+ const { scriptId } = pathScriptInfo(tabId, filename);
+ const { scriptSource } = await sendDebuggerCommand(tabId, "Debugger.getScriptSource", { scriptId });
return scriptSource;
- }, async ({tabId, filename}, buf) => {
+ }, async ({ tabId, filename }, buf) => {
await TabManager.debugTab(tabId); await TabManager.enableDomainForTab(tabId, "Debugger");
- const {scriptId} = pathScriptInfo(tabId, filename);
- await sendDebuggerCommand(tabId, "Debugger.setScriptSource", {scriptId, scriptSource: buf});
+ const { scriptId } = pathScriptInfo(tabId, filename);
+ await sendDebuggerCommand(tabId, "Debugger.setScriptSource", { scriptId, scriptSource: buf });
});
})();
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}) {
+ 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];
+ 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}) => {
+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];
+ 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) => {
+}, async ({ tabId, inputId }, buf) => {
const code = `document.getElementById('${inputId}').value = unescape('${escape(buf)}')`;
- await browser.tabs.executeScript(tabId, {code});
+ await browser.tabs.executeScript(tabId, { code });
});
Routes["/windows"] = {
@@ -657,17 +770,17 @@ Routes["/windows"] = {
};
Routes["/windows/#WINDOW_ID/tabs"] = {
- async readdir({windowId}) {
- const tabs = await browser.tabs.query({windowId});
- return { entries: [".", "..", ...tabs.map(tab => sanitize(String(tab.title) + "." + String(tab.id))) ] }
+ async readdir({ windowId }) {
+ const tabs = await browser.tabs.query({ windowId });
+ return { entries: [".", "..", ...tabs.map(tab => sanitize(String(tab.title) + "." + String(tab.id)))] }
}
}
Routes["/windows/#WINDOW_ID/tabs/:TAB_TITLE.#TAB_ID"] = {
- async readlink({tabId}) {
+ async readlink({ tabId }) {
return { buf: "../../../tabs/by-id/" + tabId };
},
- async unlink({tabId}) {
+ async unlink({ tabId }) {
await browser.tabs.remove(tabId);
return {};
}
@@ -681,56 +794,71 @@ Routes["/windows/last-focused"] = {
}
};
-(function() {
- const withWindow = (readHandler, writeHandler) => makeRouteWithContents(async ({windowId}) => {
+(function () {
+ /**
+ * @typedef {object} WindowInfo
+ * @property {number} id - Window ID
+ * @property {boolean} focused - Whether window is focused
+ */
+
+ /**
+ * @param {(window: WindowInfo) => string} readHandler
+ * @param {(buf: string) => object} [writeHandler]
+ * @returns {RouteHandler}
+ */
+ const withWindow = (readHandler, writeHandler) => makeRouteWithContents(async ({ windowId }) => {
const window = await browser.windows.get(windowId);
return readHandler(window);
- }, writeHandler ? async ({windowId}, buf) => {
+ }, writeHandler ? async ({ windowId }, buf) => {
await browser.windows.update(windowId, writeHandler(buf));
} : undefined);
Routes["/windows/#WINDOW_ID/focused"] =
withWindow(window => JSON.stringify(window.focused) + '\n',
- buf => ({ focused: buf.startsWith('true') }));
+ buf => ({ focused: buf.startsWith('true') }));
})();
-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));
+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
- };
-} };
+ }), async getattr() {
+ return {
+ st_mode: unix.S_IFREG | 0o444,
+ st_nlink: 1,
+ st_size: 10000000 // hard-code to 10MB for now
+ };
+ }
+};
-Routes["/extensions"] = {
+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';
+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");
+ }, 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 {}; } };
+ // 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}) {
+ async write({ buf }) {
await browser.runtime.reload();
- return {size: stringToUtf8Array(buf).length};
+ return { size: stringToUtf8Array(buf).length };
},
truncate() { return {}; }
};
@@ -743,6 +871,10 @@ Routes["/runtime/routes.html"] = makeRouteWithContents(async () => {
// WIP
const jsLines = (window.__backgroundJS).split('\n');
+ /**
+ * @param {string} path - Route path to find line range for
+ * @returns {[number, number]|null} Start and end line numbers, or null if not found
+ */
function findRouteLineRange(path) {
for (let i = 0; i < jsLines.length; i++) {
if (jsLines[i].includes(`Routes["${path}"] = `)) {
@@ -751,12 +883,12 @@ Routes["/runtime/routes.html"] = makeRouteWithContents(async () => {
const startBracket = result[1];
const startBracketIndex = result.index + result[0].length;
- const endBracket = ({'(': ')', '{': '}'})[startBracket];
+ const endBracket = ({ '(': ')', '{': '}' })[startBracket];
let counter = 1;
for (let j = i; j < jsLines.length; j++) {
for (let k = (j === i) ? startBracketIndex + 1 : 0;
- k < jsLines[j].length;
- k++) {
+ k < jsLines[j].length;
+ k++) {
if (jsLines[j][k] === startBracket) { counter++; }
else if (jsLines[j][k] === endBracket) { counter--; }
@@ -785,30 +917,29 @@ Routes["/runtime/routes.html"] = makeRouteWithContents(async () => {
Variables in this document, like :TAB_TITLE and #TAB_ID, are stand-ins for concrete values of what you actually have open in your browser in a running TabFS.
(work in progress)
- ` + Object.entries(Routes).map(([path, {usage, description, __isInfill, readdir}]) => {
- if (__isInfill) { return ''; }
- let usages = usage ? (Array.isArray(usage) ? usage : [usage]) : [];
- usages = usages.map(u => u.replace('\$0', path.substring(1) /* drop leading / */));
- const lineRange = findRouteLineRange(path);
- return `
+ ` + Object.entries(Routes).map(([path, { usage, description, __isInfill, readdir }]) => {
+ if (__isInfill) { return ''; }
+ let usages = usage ? (Array.isArray(usage) ? usage : [usage]) : [];
+ usages = usages.map(u => u.replace('\$0', path.substring(1) /* drop leading / */));
+ const lineRange = findRouteLineRange(path);
+ return `
- ${readdir ? '📁' : '📄'} ${path.substring(1)}
${description ? `- ${description}
` :
- '- No description found!
'}
+ '- No description found!
'}
${usages.length > 0 ? `Usage examples
${usages.map(u => `- ${u}
`).join('\n')}
` : '- No usage examples found!
'}
${lineRange ?
- `
- Source code (on GitHub)
- ${
- jsLines.slice(lineRange[0], lineRange[1] + 1).join('\n')
- // FIXME: escape for HTML
- }
+ `
+ Source code (on GitHub)
+ ${jsLines.slice(lineRange[0], lineRange[1] + 1).join('\n')
+ // FIXME: escape for HTML
+ }
` : '- No source code found!
'}
`;
- }).join('\n') + `
+ }).join('\n') + `