mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
chore: use internal event emitter for trusted events (#11898)
This commit is contained in:
parent
5b6456a7ca
commit
928d14ac84
@ -8,6 +8,7 @@ import type {ChildProcess} from 'child_process';
|
|||||||
|
|
||||||
import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
|
||||||
|
import type {BrowserEvents} from '../api/Browser.js';
|
||||||
import {
|
import {
|
||||||
Browser,
|
Browser,
|
||||||
BrowserEvent,
|
BrowserEvent,
|
||||||
@ -19,8 +20,10 @@ import {BrowserContextEvent} from '../api/BrowserContext.js';
|
|||||||
import type {Page} from '../api/Page.js';
|
import type {Page} from '../api/Page.js';
|
||||||
import type {Target} from '../api/Target.js';
|
import type {Target} from '../api/Target.js';
|
||||||
import {UnsupportedOperation} from '../common/Errors.js';
|
import {UnsupportedOperation} from '../common/Errors.js';
|
||||||
|
import {EventEmitter} from '../common/EventEmitter.js';
|
||||||
import {debugError} from '../common/util.js';
|
import {debugError} from '../common/util.js';
|
||||||
import type {Viewport} from '../common/Viewport.js';
|
import type {Viewport} from '../common/Viewport.js';
|
||||||
|
import {bubble} from '../util/decorators.js';
|
||||||
|
|
||||||
import {BidiBrowserContext} from './BrowserContext.js';
|
import {BidiBrowserContext} from './BrowserContext.js';
|
||||||
import type {BidiConnection} from './Connection.js';
|
import type {BidiConnection} from './Connection.js';
|
||||||
@ -85,6 +88,9 @@ export class BidiBrowser extends Browser {
|
|||||||
return browser;
|
return browser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bubble()
|
||||||
|
accessor #trustedEmitter = new EventEmitter<BrowserEvents>();
|
||||||
|
|
||||||
#process?: ChildProcess;
|
#process?: ChildProcess;
|
||||||
#closeCallback?: BrowserCloseCallback;
|
#closeCallback?: BrowserCloseCallback;
|
||||||
#browserCore: BrowserCore;
|
#browserCore: BrowserCore;
|
||||||
@ -107,7 +113,8 @@ export class BidiBrowser extends Browser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.#browserCore.once('disconnected', () => {
|
this.#browserCore.once('disconnected', () => {
|
||||||
this.emit(BrowserEvent.Disconnected, undefined);
|
this.#trustedEmitter.emit(BrowserEvent.Disconnected, undefined);
|
||||||
|
this.#trustedEmitter.removeAllListeners();
|
||||||
});
|
});
|
||||||
this.#process?.once('close', () => {
|
this.#process?.once('close', () => {
|
||||||
this.#browserCore.dispose('Browser process exited.', true);
|
this.#browserCore.dispose('Browser process exited.', true);
|
||||||
@ -136,15 +143,24 @@ export class BidiBrowser extends Browser {
|
|||||||
});
|
});
|
||||||
this.#browserContexts.set(userContext, browserContext);
|
this.#browserContexts.set(userContext, browserContext);
|
||||||
|
|
||||||
browserContext.on(BrowserContextEvent.TargetCreated, target => {
|
browserContext.trustedEmitter.on(
|
||||||
this.emit(BrowserEvent.TargetCreated, target);
|
BrowserContextEvent.TargetCreated,
|
||||||
});
|
target => {
|
||||||
browserContext.on(BrowserContextEvent.TargetChanged, target => {
|
this.#trustedEmitter.emit(BrowserEvent.TargetCreated, target);
|
||||||
this.emit(BrowserEvent.TargetChanged, target);
|
}
|
||||||
});
|
);
|
||||||
browserContext.on(BrowserContextEvent.TargetDestroyed, target => {
|
browserContext.trustedEmitter.on(
|
||||||
this.emit(BrowserEvent.TargetDestroyed, target);
|
BrowserContextEvent.TargetChanged,
|
||||||
});
|
target => {
|
||||||
|
this.#trustedEmitter.emit(BrowserEvent.TargetChanged, target);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
browserContext.trustedEmitter.on(
|
||||||
|
BrowserContextEvent.TargetDestroyed,
|
||||||
|
target => {
|
||||||
|
this.#trustedEmitter.emit(BrowserEvent.TargetDestroyed, target);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return browserContext;
|
return browserContext;
|
||||||
}
|
}
|
||||||
|
@ -6,20 +6,22 @@
|
|||||||
|
|
||||||
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
||||||
|
|
||||||
|
import type {BrowserContextEvents} from '../api/BrowserContext.js';
|
||||||
import {BrowserContext, BrowserContextEvent} from '../api/BrowserContext.js';
|
import {BrowserContext, BrowserContextEvent} from '../api/BrowserContext.js';
|
||||||
import {PageEvent, type Page} from '../api/Page.js';
|
import {PageEvent, type Page} from '../api/Page.js';
|
||||||
import type {Target} from '../api/Target.js';
|
import type {Target} from '../api/Target.js';
|
||||||
import {UnsupportedOperation} from '../common/Errors.js';
|
import {UnsupportedOperation} from '../common/Errors.js';
|
||||||
|
import {EventEmitter} from '../common/EventEmitter.js';
|
||||||
import {debugError} from '../common/util.js';
|
import {debugError} from '../common/util.js';
|
||||||
import type {Viewport} from '../common/Viewport.js';
|
import type {Viewport} from '../common/Viewport.js';
|
||||||
|
import {bubble} from '../util/decorators.js';
|
||||||
|
|
||||||
import type {BidiBrowser} from './Browser.js';
|
import type {BidiBrowser} from './Browser.js';
|
||||||
import type {BrowsingContext} from './core/BrowsingContext.js';
|
import type {BrowsingContext} from './core/BrowsingContext.js';
|
||||||
import {UserContext} from './core/UserContext.js';
|
import {UserContext} from './core/UserContext.js';
|
||||||
import type {BidiFrame} from './Frame.js';
|
import type {BidiFrame} from './Frame.js';
|
||||||
import {BidiPage} from './Page.js';
|
import {BidiPage} from './Page.js';
|
||||||
import {BidiPageTarget} from './Target.js';
|
import {BidiFrameTarget, BidiPageTarget} from './Target.js';
|
||||||
import {BidiFrameTarget} from './Target.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
@ -42,6 +44,9 @@ export class BidiBrowserContext extends BrowserContext {
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bubble()
|
||||||
|
accessor trustedEmitter = new EventEmitter<BrowserContextEvents>();
|
||||||
|
|
||||||
readonly #browser: BidiBrowser;
|
readonly #browser: BidiBrowser;
|
||||||
readonly #defaultViewport: Viewport | null;
|
readonly #defaultViewport: Viewport | null;
|
||||||
// This is public because of cookies.
|
// This is public because of cookies.
|
||||||
@ -52,7 +57,7 @@ export class BidiBrowserContext extends BrowserContext {
|
|||||||
[BidiPageTarget, Map<BidiFrame, BidiFrameTarget>]
|
[BidiPageTarget, Map<BidiFrame, BidiFrameTarget>]
|
||||||
>();
|
>();
|
||||||
|
|
||||||
constructor(
|
private constructor(
|
||||||
browser: BidiBrowser,
|
browser: BidiBrowser,
|
||||||
userContext: UserContext,
|
userContext: UserContext,
|
||||||
options: BidiBrowserContextOptions
|
options: BidiBrowserContextOptions
|
||||||
@ -72,12 +77,15 @@ export class BidiBrowserContext extends BrowserContext {
|
|||||||
this.userContext.on('browsingcontext', ({browsingContext}) => {
|
this.userContext.on('browsingcontext', ({browsingContext}) => {
|
||||||
this.#createPage(browsingContext);
|
this.#createPage(browsingContext);
|
||||||
});
|
});
|
||||||
|
this.userContext.on('closed', () => {
|
||||||
|
this.trustedEmitter.removeAllListeners();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#createPage(browsingContext: BrowsingContext): BidiPage {
|
#createPage(browsingContext: BrowsingContext): BidiPage {
|
||||||
const page = BidiPage.from(this, browsingContext);
|
const page = BidiPage.from(this, browsingContext);
|
||||||
this.#pages.set(browsingContext, page);
|
this.#pages.set(browsingContext, page);
|
||||||
page.on(PageEvent.Close, () => {
|
page.trustedEmitter.on(PageEvent.Close, () => {
|
||||||
this.#pages.delete(browsingContext);
|
this.#pages.delete(browsingContext);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -85,36 +93,36 @@ export class BidiBrowserContext extends BrowserContext {
|
|||||||
const pageTarget = new BidiPageTarget(page);
|
const pageTarget = new BidiPageTarget(page);
|
||||||
const frameTargets = new Map();
|
const frameTargets = new Map();
|
||||||
this.#targets.set(page, [pageTarget, frameTargets]);
|
this.#targets.set(page, [pageTarget, frameTargets]);
|
||||||
page.on(PageEvent.FrameAttached, frame => {
|
page.trustedEmitter.on(PageEvent.FrameAttached, frame => {
|
||||||
const bidiFrame = frame as BidiFrame;
|
const bidiFrame = frame as BidiFrame;
|
||||||
const target = new BidiFrameTarget(bidiFrame);
|
const target = new BidiFrameTarget(bidiFrame);
|
||||||
frameTargets.set(bidiFrame, target);
|
frameTargets.set(bidiFrame, target);
|
||||||
this.emit(BrowserContextEvent.TargetCreated, target);
|
this.trustedEmitter.emit(BrowserContextEvent.TargetCreated, target);
|
||||||
});
|
});
|
||||||
page.on(PageEvent.FrameNavigated, frame => {
|
page.trustedEmitter.on(PageEvent.FrameNavigated, frame => {
|
||||||
const bidiFrame = frame as BidiFrame;
|
const bidiFrame = frame as BidiFrame;
|
||||||
const target = frameTargets.get(bidiFrame);
|
const target = frameTargets.get(bidiFrame);
|
||||||
// If there is no target, then this is the page's frame.
|
// If there is no target, then this is the page's frame.
|
||||||
if (target === undefined) {
|
if (target === undefined) {
|
||||||
this.emit(BrowserContextEvent.TargetChanged, pageTarget);
|
this.trustedEmitter.emit(BrowserContextEvent.TargetChanged, pageTarget);
|
||||||
} else {
|
} else {
|
||||||
this.emit(BrowserContextEvent.TargetChanged, target);
|
this.trustedEmitter.emit(BrowserContextEvent.TargetChanged, target);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
page.on(PageEvent.FrameDetached, frame => {
|
page.trustedEmitter.on(PageEvent.FrameDetached, frame => {
|
||||||
const bidiFrame = frame as BidiFrame;
|
const bidiFrame = frame as BidiFrame;
|
||||||
const target = frameTargets.get(bidiFrame);
|
const target = frameTargets.get(bidiFrame);
|
||||||
if (target === undefined) {
|
if (target === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
frameTargets.delete(bidiFrame);
|
frameTargets.delete(bidiFrame);
|
||||||
this.emit(BrowserContextEvent.TargetDestroyed, target);
|
this.trustedEmitter.emit(BrowserContextEvent.TargetDestroyed, target);
|
||||||
});
|
});
|
||||||
page.on(PageEvent.Close, () => {
|
page.trustedEmitter.on(PageEvent.Close, () => {
|
||||||
this.#targets.delete(page);
|
this.#targets.delete(page);
|
||||||
this.emit(BrowserContextEvent.TargetDestroyed, pageTarget);
|
this.trustedEmitter.emit(BrowserContextEvent.TargetDestroyed, pageTarget);
|
||||||
});
|
});
|
||||||
this.emit(BrowserContextEvent.TargetCreated, pageTarget);
|
this.trustedEmitter.emit(BrowserContextEvent.TargetCreated, pageTarget);
|
||||||
// -- Target stuff ends here --
|
// -- Target stuff ends here --
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
|
@ -101,38 +101,40 @@ export class BidiFrame extends Frame {
|
|||||||
void session.detach().catch(debugError);
|
void session.detach().catch(debugError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.page().emit(PageEvent.FrameDetached, this);
|
this.page().trustedEmitter.emit(PageEvent.FrameDetached, this);
|
||||||
this.removeAllListeners();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.browsingContext.on('request', ({request}) => {
|
this.browsingContext.on('request', ({request}) => {
|
||||||
const httpRequest = BidiHTTPRequest.from(request, this);
|
const httpRequest = BidiHTTPRequest.from(request, this);
|
||||||
request.once('success', () => {
|
request.once('success', () => {
|
||||||
// SAFETY: BidiHTTPRequest will create this before here.
|
// SAFETY: BidiHTTPRequest will create this before here.
|
||||||
this.page().emit(PageEvent.RequestFinished, httpRequest);
|
this.page().trustedEmitter.emit(PageEvent.RequestFinished, httpRequest);
|
||||||
});
|
});
|
||||||
|
|
||||||
request.once('error', () => {
|
request.once('error', () => {
|
||||||
this.page().emit(PageEvent.RequestFailed, httpRequest);
|
this.page().trustedEmitter.emit(PageEvent.RequestFailed, httpRequest);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.browsingContext.on('navigation', ({navigation}) => {
|
this.browsingContext.on('navigation', ({navigation}) => {
|
||||||
navigation.once('fragment', () => {
|
navigation.once('fragment', () => {
|
||||||
this.page().emit(PageEvent.FrameNavigated, this);
|
this.page().trustedEmitter.emit(PageEvent.FrameNavigated, this);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.browsingContext.on('load', () => {
|
this.browsingContext.on('load', () => {
|
||||||
this.page().emit(PageEvent.Load, undefined);
|
this.page().trustedEmitter.emit(PageEvent.Load, undefined);
|
||||||
});
|
});
|
||||||
this.browsingContext.on('DOMContentLoaded', () => {
|
this.browsingContext.on('DOMContentLoaded', () => {
|
||||||
this._hasStartedLoading = true;
|
this._hasStartedLoading = true;
|
||||||
this.page().emit(PageEvent.DOMContentLoaded, undefined);
|
this.page().trustedEmitter.emit(PageEvent.DOMContentLoaded, undefined);
|
||||||
this.page().emit(PageEvent.FrameNavigated, this);
|
this.page().trustedEmitter.emit(PageEvent.FrameNavigated, this);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.browsingContext.on('userprompt', ({userPrompt}) => {
|
this.browsingContext.on('userprompt', ({userPrompt}) => {
|
||||||
this.page().emit(PageEvent.Dialog, BidiDialog.from(userPrompt));
|
this.page().trustedEmitter.emit(
|
||||||
|
PageEvent.Dialog,
|
||||||
|
BidiDialog.from(userPrompt)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.browsingContext.on('log', ({entry}) => {
|
this.browsingContext.on('log', ({entry}) => {
|
||||||
@ -154,7 +156,7 @@ export class BidiFrame extends Frame {
|
|||||||
}, '')
|
}, '')
|
||||||
.slice(1);
|
.slice(1);
|
||||||
|
|
||||||
this.page().emit(
|
this.page().trustedEmitter.emit(
|
||||||
PageEvent.Console,
|
PageEvent.Console,
|
||||||
new ConsoleMessage(
|
new ConsoleMessage(
|
||||||
entry.method as any,
|
entry.method as any,
|
||||||
@ -185,7 +187,7 @@ export class BidiFrame extends Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
error.stack = [...messageLines, ...stackLines].join('\n');
|
error.stack = [...messageLines, ...stackLines].join('\n');
|
||||||
this.page().emit(PageEvent.PageError, error);
|
this.page().trustedEmitter.emit(PageEvent.PageError, error);
|
||||||
} else {
|
} else {
|
||||||
debugError(
|
debugError(
|
||||||
`Unhandled LogEntry with type "${entry.type}", text "${entry.text}" and level "${entry.level}"`
|
`Unhandled LogEntry with type "${entry.type}", text "${entry.text}" and level "${entry.level}"`
|
||||||
@ -197,7 +199,7 @@ export class BidiFrame extends Frame {
|
|||||||
#createFrameTarget(browsingContext: BrowsingContext) {
|
#createFrameTarget(browsingContext: BrowsingContext) {
|
||||||
const frame = BidiFrame.from(this, browsingContext);
|
const frame = BidiFrame.from(this, browsingContext);
|
||||||
this.#frames.set(browsingContext, frame);
|
this.#frames.set(browsingContext, frame);
|
||||||
this.page().emit(PageEvent.FrameAttached, frame);
|
this.page().trustedEmitter.emit(PageEvent.FrameAttached, frame);
|
||||||
|
|
||||||
browsingContext.on('closed', () => {
|
browsingContext.on('closed', () => {
|
||||||
this.#frames.delete(browsingContext);
|
this.#frames.delete(browsingContext);
|
||||||
|
@ -60,7 +60,7 @@ export class BidiHTTPRequest extends HTTPRequest {
|
|||||||
this.#response = BidiHTTPResponse.from(data, this);
|
this.#response = BidiHTTPResponse.from(data, this);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#frame?.page().emit(PageEvent.Request, this);
|
this.#frame?.page().trustedEmitter.emit(PageEvent.Request, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
override url(): string {
|
override url(): string {
|
||||||
|
@ -40,7 +40,7 @@ export class BidiHTTPResponse extends HTTPResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#initialize() {
|
#initialize() {
|
||||||
this.#request.frame()?.page().emit(PageEvent.Response, this);
|
this.#request.frame()?.page().trustedEmitter.emit(PageEvent.Response, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@invokeAtMostOnceForArguments
|
@invokeAtMostOnceForArguments
|
||||||
|
@ -12,7 +12,11 @@ import type {CDPSession} from '../api/CDPSession.js';
|
|||||||
import type {BoundingBox} from '../api/ElementHandle.js';
|
import type {BoundingBox} from '../api/ElementHandle.js';
|
||||||
import type {WaitForOptions} from '../api/Frame.js';
|
import type {WaitForOptions} from '../api/Frame.js';
|
||||||
import type {HTTPResponse} from '../api/HTTPResponse.js';
|
import type {HTTPResponse} from '../api/HTTPResponse.js';
|
||||||
import type {MediaFeature, GeolocationOptions} from '../api/Page.js';
|
import type {
|
||||||
|
MediaFeature,
|
||||||
|
GeolocationOptions,
|
||||||
|
PageEvents,
|
||||||
|
} from '../api/Page.js';
|
||||||
import {
|
import {
|
||||||
Page,
|
Page,
|
||||||
PageEvent,
|
PageEvent,
|
||||||
@ -25,11 +29,13 @@ import {EmulationManager} from '../cdp/EmulationManager.js';
|
|||||||
import {Tracing} from '../cdp/Tracing.js';
|
import {Tracing} from '../cdp/Tracing.js';
|
||||||
import type {Cookie, CookieParam, CookieSameSite} from '../common/Cookie.js';
|
import type {Cookie, CookieParam, CookieSameSite} from '../common/Cookie.js';
|
||||||
import {UnsupportedOperation} from '../common/Errors.js';
|
import {UnsupportedOperation} from '../common/Errors.js';
|
||||||
|
import {EventEmitter} from '../common/EventEmitter.js';
|
||||||
import type {PDFOptions} from '../common/PDFOptions.js';
|
import type {PDFOptions} from '../common/PDFOptions.js';
|
||||||
import type {Awaitable} from '../common/types.js';
|
import type {Awaitable} from '../common/types.js';
|
||||||
import {evaluationString, parsePDFOptions, timeout} from '../common/util.js';
|
import {evaluationString, parsePDFOptions, timeout} from '../common/util.js';
|
||||||
import type {Viewport} from '../common/Viewport.js';
|
import type {Viewport} from '../common/Viewport.js';
|
||||||
import {assert} from '../util/assert.js';
|
import {assert} from '../util/assert.js';
|
||||||
|
import {bubble} from '../util/decorators.js';
|
||||||
import {isErrorLike} from '../util/ErrorLike.js';
|
import {isErrorLike} from '../util/ErrorLike.js';
|
||||||
|
|
||||||
import type {BidiBrowser} from './Browser.js';
|
import type {BidiBrowser} from './Browser.js';
|
||||||
@ -56,6 +62,9 @@ export class BidiPage extends Page {
|
|||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bubble()
|
||||||
|
accessor trustedEmitter = new EventEmitter<PageEvents>();
|
||||||
|
|
||||||
readonly #browserContext: BidiBrowserContext;
|
readonly #browserContext: BidiBrowserContext;
|
||||||
readonly #frame: BidiFrame;
|
readonly #frame: BidiFrame;
|
||||||
#viewport: Viewport | null = null;
|
#viewport: Viewport | null = null;
|
||||||
@ -91,8 +100,8 @@ export class BidiPage extends Page {
|
|||||||
|
|
||||||
#initialize() {
|
#initialize() {
|
||||||
this.#frame.browsingContext.on('closed', () => {
|
this.#frame.browsingContext.on('closed', () => {
|
||||||
this.emit(PageEvent.Close, undefined);
|
this.trustedEmitter.emit(PageEvent.Close, undefined);
|
||||||
this.removeAllListeners();
|
this.trustedEmitter.removeAllListeners();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +157,4 @@ export interface Connection<Events extends BidiEvents = BidiEvents>
|
|||||||
method: T,
|
method: T,
|
||||||
params: Commands[T]['params']
|
params: Commands[T]['params']
|
||||||
): Promise<{result: Commands[T]['returnType']}>;
|
): Promise<{result: Commands[T]['returnType']}>;
|
||||||
|
|
||||||
// This will pipe events into the provided emitter.
|
|
||||||
pipeTo<Events extends BidiEvents>(emitter: EventEmitter<Events>): void;
|
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,11 @@ import type * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
|
|||||||
|
|
||||||
import {EventEmitter} from '../../common/EventEmitter.js';
|
import {EventEmitter} from '../../common/EventEmitter.js';
|
||||||
import {debugError} from '../../common/util.js';
|
import {debugError} from '../../common/util.js';
|
||||||
import {inertIfDisposed, throwIfDisposed} from '../../util/decorators.js';
|
import {
|
||||||
|
bubble,
|
||||||
|
inertIfDisposed,
|
||||||
|
throwIfDisposed,
|
||||||
|
} from '../../util/decorators.js';
|
||||||
import {DisposableStack, disposeSymbol} from '../../util/disposable.js';
|
import {DisposableStack, disposeSymbol} from '../../util/disposable.js';
|
||||||
|
|
||||||
import {Browser} from './Browser.js';
|
import {Browser} from './Browser.js';
|
||||||
@ -81,7 +85,8 @@ export class Session
|
|||||||
readonly #disposables = new DisposableStack();
|
readonly #disposables = new DisposableStack();
|
||||||
readonly #info: Bidi.Session.NewResult;
|
readonly #info: Bidi.Session.NewResult;
|
||||||
readonly browser!: Browser;
|
readonly browser!: Browser;
|
||||||
readonly connection: Connection;
|
@bubble()
|
||||||
|
accessor connection: Connection;
|
||||||
// keep-sorted end
|
// keep-sorted end
|
||||||
|
|
||||||
private constructor(connection: Connection, info: Bidi.Session.NewResult) {
|
private constructor(connection: Connection, info: Bidi.Session.NewResult) {
|
||||||
@ -93,8 +98,6 @@ export class Session
|
|||||||
}
|
}
|
||||||
|
|
||||||
async #initialize(): Promise<void> {
|
async #initialize(): Promise<void> {
|
||||||
this.connection.pipeTo(this);
|
|
||||||
|
|
||||||
// SAFETY: We use `any` to allow assignment of the readonly property.
|
// SAFETY: We use `any` to allow assignment of the readonly property.
|
||||||
(this as any).browser = await Browser.from(this);
|
(this as any).browser = await Browser.from(this);
|
||||||
|
|
||||||
@ -125,10 +128,6 @@ export class Session
|
|||||||
this[disposeSymbol]();
|
this[disposeSymbol]();
|
||||||
}
|
}
|
||||||
|
|
||||||
pipeTo<Events extends BidiEvents>(emitter: EventEmitter<Events>): void {
|
|
||||||
this.connection.pipeTo(emitter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Currently, there is a 1:1 relationship between the session and the
|
* Currently, there is a 1:1 relationship between the session and the
|
||||||
* session. In the future, we might support multiple sessions and in that
|
* session. In the future, we might support multiple sessions and in that
|
||||||
|
@ -9,7 +9,9 @@ import {describe, it} from 'node:test';
|
|||||||
import expect from 'expect';
|
import expect from 'expect';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
|
||||||
import {invokeAtMostOnceForArguments} from './decorators.js';
|
import {EventEmitter} from '../common/EventEmitter.js';
|
||||||
|
|
||||||
|
import {bubble, invokeAtMostOnceForArguments} from './decorators.js';
|
||||||
|
|
||||||
describe('decorators', function () {
|
describe('decorators', function () {
|
||||||
describe('invokeAtMostOnceForArguments', () => {
|
describe('invokeAtMostOnceForArguments', () => {
|
||||||
@ -76,4 +78,48 @@ describe('decorators', function () {
|
|||||||
}).toThrow();
|
}).toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('bubble', () => {
|
||||||
|
it('should work', () => {
|
||||||
|
class Test extends EventEmitter<any> {
|
||||||
|
@bubble()
|
||||||
|
accessor field = new EventEmitter();
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = new Test();
|
||||||
|
let a = false;
|
||||||
|
t.on('a', (value: boolean) => {
|
||||||
|
a = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
t.field.emit('a', true);
|
||||||
|
expect(a).toBeTruthy();
|
||||||
|
|
||||||
|
// Set a new emitter.
|
||||||
|
t.field = new EventEmitter();
|
||||||
|
a = false;
|
||||||
|
|
||||||
|
t.field.emit('a', true);
|
||||||
|
expect(a).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not bubble down', () => {
|
||||||
|
class Test extends EventEmitter<any> {
|
||||||
|
@bubble()
|
||||||
|
accessor field = new EventEmitter<any>();
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = new Test();
|
||||||
|
let a = false;
|
||||||
|
t.field.on('a', (value: boolean) => {
|
||||||
|
a = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
t.emit('a', true);
|
||||||
|
expect(a).toBeFalsy();
|
||||||
|
|
||||||
|
t.field.emit('a', true);
|
||||||
|
expect(a).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type {EventType} from '../common/EventEmitter.js';
|
||||||
|
import type {EventEmitter} from '../common/EventEmitter.js';
|
||||||
import type {Disposed, Moveable} from '../common/types.js';
|
import type {Disposed, Moveable} from '../common/types.js';
|
||||||
|
|
||||||
import {asyncDisposeSymbol, disposeSymbol} from './disposable.js';
|
import {asyncDisposeSymbol, disposeSymbol} from './disposable.js';
|
||||||
@ -138,3 +140,67 @@ export function guarded<T extends object>(
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bubbleHandlers = new WeakMap<object, Map<any, any>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event emitter fields marked with `bubble` will have their events bubble up
|
||||||
|
* the field owner.
|
||||||
|
*/
|
||||||
|
// The type is too complicated to type.
|
||||||
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
|
export function bubble<T extends EventType[]>(events?: T) {
|
||||||
|
return <This extends EventEmitter<any>, Value extends EventEmitter<any>>(
|
||||||
|
{set, get}: ClassAccessorDecoratorTarget<This, Value>,
|
||||||
|
context: ClassAccessorDecoratorContext<This, Value>
|
||||||
|
): ClassAccessorDecoratorResult<This, Value> => {
|
||||||
|
context.addInitializer(function () {
|
||||||
|
const handlers = bubbleHandlers.get(this) ?? new Map();
|
||||||
|
if (handlers.has(events)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handler =
|
||||||
|
events !== undefined
|
||||||
|
? (type: EventType, event: unknown) => {
|
||||||
|
if (events.includes(type)) {
|
||||||
|
this.emit(type, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: (type: EventType, event: unknown) => {
|
||||||
|
this.emit(type, event);
|
||||||
|
};
|
||||||
|
|
||||||
|
handlers.set(events, handler);
|
||||||
|
bubbleHandlers.set(this, handlers);
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
set(emitter) {
|
||||||
|
const handler = bubbleHandlers.get(this)!.get(events)!;
|
||||||
|
|
||||||
|
// In case we are re-setting.
|
||||||
|
const oldEmitter = get.call(this);
|
||||||
|
if (oldEmitter !== undefined) {
|
||||||
|
oldEmitter.off('*', handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emitter === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emitter.on('*', handler);
|
||||||
|
set.call(this, emitter);
|
||||||
|
},
|
||||||
|
// @ts-expect-error -- TypeScript incorrectly types init to require a
|
||||||
|
// return.
|
||||||
|
init(emitter) {
|
||||||
|
if (emitter === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const handler = bubbleHandlers.get(this)!.get(events)!;
|
||||||
|
|
||||||
|
emitter.on('*', handler);
|
||||||
|
return emitter;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user