Skip to content

Commit

Permalink
#2902: Throw BusinessError on runBrick (closed tab/no access) (#2917)
Browse files Browse the repository at this point in the history
  • Loading branch information
fregante committed Mar 12, 2022
1 parent 97177a3 commit 0fbfe24
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 4 deletions.
29 changes: 26 additions & 3 deletions src/background/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,36 @@ import { runBrick } from "@/contentScript/messenger/api";
import { Target } from "@/types";
import { RemoteExecutionError } from "@/blocks/errors";
import pDefer from "p-defer";
import { canAccessTab } from "webext-tools";
import { onTabClose } from "@/chrome";

type TabId = number;

// Used to determine which promise was resolved in a race
const TYPE_WAS_CLOSED = Symbol("Tab was closed");

const tabToOpener = new Map<TabId, TabId>();
const tabToTarget = new Map<TabId, TabId>();
// TODO: One tab could have multiple targets, but `tabToTarget` currenly only supports one at a time

async function safelyRunBrick({ tabId }: { tabId: number }, request: RunBlock) {
if (!(await canAccessTab(tabId))) {
throw new BusinessError("PixieBrix doesn't have access to the tab");
}

const result = await Promise.race([
// If https://github.com/pixiebrix/webext-messenger/issues/67 is resolved, we don't need the listener
onTabClose(tabId).then(() => TYPE_WAS_CLOSED),
runBrick({ tabId }, request),
]);

if (result === TYPE_WAS_CLOSED) {
throw new BusinessError("The tab was closed");
}

return result;
}

export async function waitForTargetByUrl(url: string): Promise<Target> {
const { promise, resolve } = pDefer<Target>();

Expand Down Expand Up @@ -65,7 +88,7 @@ export async function requestRunInOpener(
tabId: tabToOpener.get(sourceTabId),
};
const subRequest = { ...request, sourceTabId };
return runBrick(opener, subRequest);
return safelyRunBrick(opener, subRequest);
}

export async function requestRunInBroadcast(
Expand All @@ -87,7 +110,7 @@ export async function requestRunInBroadcast(
}

try {
const response = runBrick({ tabId: tab.id }, subRequest);
const response = safelyRunBrick({ tabId: tab.id }, subRequest);
fulfilled.set(tab.id, await response);
} catch (error) {
rejected.set(tab.id, error);
Expand All @@ -113,7 +136,7 @@ export async function requestRunInTarget(
}

const subRequest = { ...request, sourceTabId };
return runBrick({ tabId: target }, subRequest);
return safelyRunBrick({ tabId: target }, subRequest);
}

export async function openTab(
Expand Down
13 changes: 13 additions & 0 deletions src/chrome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,16 @@ export async function setReduxStorage<T extends JsonValue = JsonValue>(
): Promise<void> {
await browser.storage.local.set({ [storageKey]: JSON.stringify(value) });
}

export async function onTabClose(watchedTabId: number): Promise<void> {
await new Promise<void>((resolve) => {
const listener = (closedTabId: number) => {
if (closedTabId === watchedTabId) {
resolve();
browser.tabs.onRemoved.removeListener(listener);
}
};

browser.tabs.onRemoved.addListener(listener);
});
}
4 changes: 3 additions & 1 deletion src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,11 @@ export class ContextError extends Error {
}
}

export const NO_TARGET_FOUND_CONNECTION_ERROR =
"Could not establish connection. Receiving end does not exist.";
/** Browser Messenger API error message patterns */
export const CONNECTION_ERROR_MESSAGES = [
"Could not establish connection. Receiving end does not exist.",
NO_TARGET_FOUND_CONNECTION_ERROR,
"Extension context invalidated.",
];

Expand Down

0 comments on commit 0fbfe24

Please sign in to comment.