summaryrefslogtreecommitdiffstats
path: root/remote/test/puppeteer/packages/puppeteer-core/src/cdp/DeviceRequestPrompt.ts
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /remote/test/puppeteer/packages/puppeteer-core/src/cdp/DeviceRequestPrompt.ts
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'remote/test/puppeteer/packages/puppeteer-core/src/cdp/DeviceRequestPrompt.ts')
-rw-r--r--remote/test/puppeteer/packages/puppeteer-core/src/cdp/DeviceRequestPrompt.ts280
1 files changed, 280 insertions, 0 deletions
diff --git a/remote/test/puppeteer/packages/puppeteer-core/src/cdp/DeviceRequestPrompt.ts b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/DeviceRequestPrompt.ts
new file mode 100644
index 0000000000..f5bd73bf72
--- /dev/null
+++ b/remote/test/puppeteer/packages/puppeteer-core/src/cdp/DeviceRequestPrompt.ts
@@ -0,0 +1,280 @@
+/**
+ * @license
+ * Copyright 2022 Google Inc.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import type Protocol from 'devtools-protocol';
+
+import type {CDPSession} from '../api/CDPSession.js';
+import type {WaitTimeoutOptions} from '../api/Page.js';
+import type {TimeoutSettings} from '../common/TimeoutSettings.js';
+import {assert} from '../util/assert.js';
+import {Deferred} from '../util/Deferred.js';
+
+/**
+ * Device in a request prompt.
+ *
+ * @public
+ */
+export class DeviceRequestPromptDevice {
+ /**
+ * Device id during a prompt.
+ */
+ id: string;
+
+ /**
+ * Device name as it appears in a prompt.
+ */
+ name: string;
+
+ /**
+ * @internal
+ */
+ constructor(id: string, name: string) {
+ this.id = id;
+ this.name = name;
+ }
+}
+
+/**
+ * Device request prompts let you respond to the page requesting for a device
+ * through an API like WebBluetooth.
+ *
+ * @remarks
+ * `DeviceRequestPrompt` instances are returned via the
+ * {@link Page.waitForDevicePrompt} method.
+ *
+ * @example
+ *
+ * ```ts
+ * const [deviceRequest] = Promise.all([
+ * page.waitForDevicePrompt(),
+ * page.click('#connect-bluetooth'),
+ * ]);
+ * await devicePrompt.select(
+ * await devicePrompt.waitForDevice(({name}) => name.includes('My Device'))
+ * );
+ * ```
+ *
+ * @public
+ */
+export class DeviceRequestPrompt {
+ #client: CDPSession | null;
+ #timeoutSettings: TimeoutSettings;
+ #id: string;
+ #handled = false;
+ #updateDevicesHandle = this.#updateDevices.bind(this);
+ #waitForDevicePromises = new Set<{
+ filter: (device: DeviceRequestPromptDevice) => boolean;
+ promise: Deferred<DeviceRequestPromptDevice>;
+ }>();
+
+ /**
+ * Current list of selectable devices.
+ */
+ devices: DeviceRequestPromptDevice[] = [];
+
+ /**
+ * @internal
+ */
+ constructor(
+ client: CDPSession,
+ timeoutSettings: TimeoutSettings,
+ firstEvent: Protocol.DeviceAccess.DeviceRequestPromptedEvent
+ ) {
+ this.#client = client;
+ this.#timeoutSettings = timeoutSettings;
+ this.#id = firstEvent.id;
+
+ this.#client.on(
+ 'DeviceAccess.deviceRequestPrompted',
+ this.#updateDevicesHandle
+ );
+ this.#client.on('Target.detachedFromTarget', () => {
+ this.#client = null;
+ });
+
+ this.#updateDevices(firstEvent);
+ }
+
+ #updateDevices(event: Protocol.DeviceAccess.DeviceRequestPromptedEvent) {
+ if (event.id !== this.#id) {
+ return;
+ }
+
+ for (const rawDevice of event.devices) {
+ if (
+ this.devices.some(device => {
+ return device.id === rawDevice.id;
+ })
+ ) {
+ continue;
+ }
+
+ const newDevice = new DeviceRequestPromptDevice(
+ rawDevice.id,
+ rawDevice.name
+ );
+ this.devices.push(newDevice);
+
+ for (const waitForDevicePromise of this.#waitForDevicePromises) {
+ if (waitForDevicePromise.filter(newDevice)) {
+ waitForDevicePromise.promise.resolve(newDevice);
+ }
+ }
+ }
+ }
+
+ /**
+ * Resolve to the first device in the prompt matching a filter.
+ */
+ async waitForDevice(
+ filter: (device: DeviceRequestPromptDevice) => boolean,
+ options: WaitTimeoutOptions = {}
+ ): Promise<DeviceRequestPromptDevice> {
+ for (const device of this.devices) {
+ if (filter(device)) {
+ return device;
+ }
+ }
+
+ const {timeout = this.#timeoutSettings.timeout()} = options;
+ const deferred = Deferred.create<DeviceRequestPromptDevice>({
+ message: `Waiting for \`DeviceRequestPromptDevice\` failed: ${timeout}ms exceeded`,
+ timeout,
+ });
+ const handle = {filter, promise: deferred};
+ this.#waitForDevicePromises.add(handle);
+ try {
+ return await deferred.valueOrThrow();
+ } finally {
+ this.#waitForDevicePromises.delete(handle);
+ }
+ }
+
+ /**
+ * Select a device in the prompt's list.
+ */
+ async select(device: DeviceRequestPromptDevice): Promise<void> {
+ assert(
+ this.#client !== null,
+ 'Cannot select device through detached session!'
+ );
+ assert(this.devices.includes(device), 'Cannot select unknown device!');
+ assert(
+ !this.#handled,
+ 'Cannot select DeviceRequestPrompt which is already handled!'
+ );
+ this.#client.off(
+ 'DeviceAccess.deviceRequestPrompted',
+ this.#updateDevicesHandle
+ );
+ this.#handled = true;
+ return await this.#client.send('DeviceAccess.selectPrompt', {
+ id: this.#id,
+ deviceId: device.id,
+ });
+ }
+
+ /**
+ * Cancel the prompt.
+ */
+ async cancel(): Promise<void> {
+ assert(
+ this.#client !== null,
+ 'Cannot cancel prompt through detached session!'
+ );
+ assert(
+ !this.#handled,
+ 'Cannot cancel DeviceRequestPrompt which is already handled!'
+ );
+ this.#client.off(
+ 'DeviceAccess.deviceRequestPrompted',
+ this.#updateDevicesHandle
+ );
+ this.#handled = true;
+ return await this.#client.send('DeviceAccess.cancelPrompt', {id: this.#id});
+ }
+}
+
+/**
+ * @internal
+ */
+export class DeviceRequestPromptManager {
+ #client: CDPSession | null;
+ #timeoutSettings: TimeoutSettings;
+ #deviceRequestPrompDeferreds = new Set<Deferred<DeviceRequestPrompt>>();
+
+ /**
+ * @internal
+ */
+ constructor(client: CDPSession, timeoutSettings: TimeoutSettings) {
+ this.#client = client;
+ this.#timeoutSettings = timeoutSettings;
+
+ this.#client.on('DeviceAccess.deviceRequestPrompted', event => {
+ this.#onDeviceRequestPrompted(event);
+ });
+ this.#client.on('Target.detachedFromTarget', () => {
+ this.#client = null;
+ });
+ }
+
+ /**
+ * Wait for device prompt created by an action like calling WebBluetooth's
+ * requestDevice.
+ */
+ async waitForDevicePrompt(
+ options: WaitTimeoutOptions = {}
+ ): Promise<DeviceRequestPrompt> {
+ assert(
+ this.#client !== null,
+ 'Cannot wait for device prompt through detached session!'
+ );
+ const needsEnable = this.#deviceRequestPrompDeferreds.size === 0;
+ let enablePromise: Promise<void> | undefined;
+ if (needsEnable) {
+ enablePromise = this.#client.send('DeviceAccess.enable');
+ }
+
+ const {timeout = this.#timeoutSettings.timeout()} = options;
+ const deferred = Deferred.create<DeviceRequestPrompt>({
+ message: `Waiting for \`DeviceRequestPrompt\` failed: ${timeout}ms exceeded`,
+ timeout,
+ });
+ this.#deviceRequestPrompDeferreds.add(deferred);
+
+ try {
+ const [result] = await Promise.all([
+ deferred.valueOrThrow(),
+ enablePromise,
+ ]);
+ return result;
+ } finally {
+ this.#deviceRequestPrompDeferreds.delete(deferred);
+ }
+ }
+
+ /**
+ * @internal
+ */
+ #onDeviceRequestPrompted(
+ event: Protocol.DeviceAccess.DeviceRequestPromptedEvent
+ ) {
+ if (!this.#deviceRequestPrompDeferreds.size) {
+ return;
+ }
+
+ assert(this.#client !== null);
+ const devicePrompt = new DeviceRequestPrompt(
+ this.#client,
+ this.#timeoutSettings,
+ event
+ );
+ for (const promise of this.#deviceRequestPrompDeferreds) {
+ promise.resolve(devicePrompt);
+ }
+ this.#deviceRequestPrompDeferreds.clear();
+ }
+}