Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 12 additions & 54 deletions packages/wxt/src/utils/content-script-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,14 @@ import { createLocationWatcher } from './internal/location-watcher';
* ```
*/
export class ContentScriptContext implements AbortController {
private static SCRIPT_STARTED_MESSAGE_TYPE = getUniqueEventName(
'wxt:content-script-started',
);

private isTopFrame = window.self === window.top;
private abortController: AbortController;
private locationWatcher = createLocationWatcher(this);
private receivedMessageIds = new Set<string>();

constructor(
private readonly contentScriptName: string,
public readonly options?: Omit<ContentScriptDefinition, 'main'>,
) {
this.abortController = new AbortController();

if (this.isTopFrame) {
this.listenForNewerScripts({ ignoreFirstEvent: true });
this.stopOldScripts();
} else {
this.listenForNewerScripts();
}
}

get signal() {
Expand Down Expand Up @@ -221,8 +208,19 @@ export class ContentScriptContext implements AbortController {
if (this.isValid) this.locationWatcher.run();
}

const eventType = type.startsWith('wxt:') ? getUniqueEventName(type) : type

target.addEventListener?.(
eventType,
() => this.isInvalid, // signals the abort controller when invalid
{
capture: true,
signal: this.signal,
},
);

target.addEventListener?.(
type.startsWith('wxt:') ? getUniqueEventName(type) : type,
eventType,
handler,
{
...options,
Expand All @@ -241,46 +239,6 @@ export class ContentScriptContext implements AbortController {
`Content script "${this.contentScriptName}" context invalidated`,
);
}

stopOldScripts() {
// Use postMessage so it get's sent to all the frames of the page.
window.postMessage(
{
type: ContentScriptContext.SCRIPT_STARTED_MESSAGE_TYPE,
contentScriptName: this.contentScriptName,
messageId: Math.random().toString(36).slice(2),
},
'*',
);
}

verifyScriptStartedEvent(event: MessageEvent) {
const isScriptStartedEvent =
event.data?.type === ContentScriptContext.SCRIPT_STARTED_MESSAGE_TYPE;
const isSameContentScript =
event.data?.contentScriptName === this.contentScriptName;
const isNotDuplicate = !this.receivedMessageIds.has(event.data?.messageId);
return isScriptStartedEvent && isSameContentScript && isNotDuplicate;
}

listenForNewerScripts(options?: { ignoreFirstEvent?: boolean }) {
let isFirst = true;

const cb = (event: MessageEvent) => {
if (this.verifyScriptStartedEvent(event)) {
this.receivedMessageIds.add(event.data.messageId);

const wasFirst = isFirst;
isFirst = false;
if (wasFirst && options?.ignoreFirstEvent) return;

this.notifyInvalidated();
}
};

addEventListener('message', cb);
this.onInvalidated(() => removeEventListener('message', cb));
}
}

export interface WxtWindowEventMap extends WindowEventMap {
Expand Down