From 3542593d7f96c431777bb426112bb1804704720b Mon Sep 17 00:00:00 2001 From: Erwan Jugand <47392755+erwanjugand@users.noreply.github.com> Date: Fri, 3 Oct 2025 07:58:49 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=A4=96=20Merge=20PR=20#73802=20[chrom?= =?UTF-8?q?e]=20update=20tabCapture=20namespace=20by=20@erwanjugand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/chrome/index.d.ts | 44 +++++++++++++----------- types/chrome/test/index.ts | 70 ++++++++++++++++++++++++-------------- 2 files changed, 70 insertions(+), 44 deletions(-) diff --git a/types/chrome/index.d.ts b/types/chrome/index.d.ts index 0f062fd37cefe8..1b3d1b9f77d253 100644 --- a/types/chrome/index.d.ts +++ b/types/chrome/index.d.ts @@ -10720,11 +10720,8 @@ declare namespace chrome { export interface CaptureInfo { /** The id of the tab whose status changed. */ tabId: number; - /** - * The new capture status of the tab. - * One of: "pending", "active", "stopped", or "error" - */ - status: string; + /** The new capture status of the tab. */ + status: `${TabCaptureState}`; /** Whether an element in the tab being captured is in fullscreen mode. */ fullscreen: boolean; } @@ -10735,46 +10732,55 @@ declare namespace chrome { } export interface CaptureOptions { - /** Optional. */ audio?: boolean | undefined; - /** Optional. */ video?: boolean | undefined; - /** Optional. */ audioConstraints?: MediaStreamConstraint | undefined; - /** Optional. */ videoConstraints?: MediaStreamConstraint | undefined; } + /** @since Chrome 71 */ export interface GetMediaStreamOptions { - /** Optional tab id of the tab which will later invoke getUserMedia() to consume the stream. If not specified then the resulting stream can be used only by the calling extension. The stream can only be used by frames in the given tab whose security origin matches the consumber tab's origin. The tab's origin must be a secure origin, e.g. HTTPS. */ + /** Optional tab id of the tab which will later invoke `getUserMedia()` to consume the stream. If not specified then the resulting stream can be used only by the calling extension. The stream can only be used by frames in the given tab whose security origin matches the consumber tab's origin. The tab's origin must be a secure origin, e.g. HTTPS. */ consumerTabId?: number | undefined; - /** Optional tab id of the tab which will be captured. If not specified then the current active tab will be selected. Only tabs for which the extension has been granted the activeTab permission can be used as the target tab. */ + /** Optional tab id of the tab which will be captured. If not specified then the current active tab will be selected. Only tabs for which the extension has been granted the `activeTab` permission can be used as the target tab. */ targetTabId?: number | undefined; } - export interface CaptureStatusChangedEvent extends chrome.events.Event<(info: CaptureInfo) => void> {} + export enum TabCaptureState { + PENDING = "pending", + ACTIVE = "active", + STOPPED = "stopped", + ERROR = "error", + } /** - * Captures the visible area of the currently active tab. Capture can only be started on the currently active tab after the extension has been invoked. Capture is maintained across page navigations within the tab, and stops when the tab is closed, or the media stream is closed by the extension. + * Captures the visible area of the currently active tab. Capture can only be started on the currently active tab after the extension has been invoked, similar to the way that activeTab works. Capture is maintained across page navigations within the tab, and stops when the tab is closed, or the media stream is closed by the extension. * @param options Configures the returned media stream. - * @param callback Callback with either the tab capture stream or null. */ export function capture(options: CaptureOptions, callback: (stream: MediaStream | null) => void): void; + /** * Returns a list of tabs that have requested capture or are being captured, i.e. status != stopped and status != error. This allows extensions to inform the user that there is an existing tab capture that would prevent a new tab capture from succeeding (or to prevent redundant requests for the same tab). - * @param callback Callback invoked with CaptureInfo[] for captured tabs. + * + * Can return its result via Promise in Manifest V3 or later since Chrome 116. */ + export function getCapturedTabs(): Promise; export function getCapturedTabs(callback: (result: CaptureInfo[]) => void): void; /** * Creates a stream ID to capture the target tab. Similar to chrome.tabCapture.capture() method, but returns a media stream ID, instead of a media stream, to the consumer tab. - * @param options Options for the media stream id to retrieve. - * @param callback Callback to invoke with the result. If successful, the result is an opaque string that can be passed to the getUserMedia() API to generate a media stream that corresponds to the target tab. The created streamId can only be used once and expires after a few seconds if it is not used. + * + * Can return its result via Promise in Manifest V3 or later since Chrome 116. */ - export function getMediaStreamId(options: GetMediaStreamOptions, callback: (streamId: string) => void): void; + export function getMediaStreamId(options?: GetMediaStreamOptions): Promise; + export function getMediaStreamId(callback: (streamId: string) => void): void; + export function getMediaStreamId( + options: GetMediaStreamOptions | undefined, + callback: (streamId: string) => void, + ): void; /** Event fired when the capture status of a tab changes. This allows extension authors to keep track of the capture status of tabs to keep UI elements like page actions in sync. */ - export var onStatusChanged: CaptureStatusChangedEvent; + export const onStatusChanged: events.Event<(info: CaptureInfo) => void>; } //////////////////// diff --git a/types/chrome/test/index.ts b/types/chrome/test/index.ts index 2cc0fd2c993df4..637fe1e3f88e56 100644 --- a/types/chrome/test/index.ts +++ b/types/chrome/test/index.ts @@ -996,38 +996,58 @@ function testGetManifest() { }; } -// https://developer.chrome.com/extensions/tabCapture#type-CaptureOptions -function testTabCaptureOptions() { - // Constraints based on: - // https://github.com/muaz-khan/WebRTC-Experiment/blob/master/Chrome-Extensions/tabCapture/tab-capturing.js - - const resolutions = { - maxWidth: 1920, - maxHeight: 1080, - }; - - const constraints: chrome.tabCapture.CaptureOptions = { +// https://developer.chrome.com/docs/extensions/reference/api/tabCapture +function testTabCapture() { + chrome.tabCapture.TabCaptureState.ACTIVE === "active"; + chrome.tabCapture.TabCaptureState.ERROR === "error"; + chrome.tabCapture.TabCaptureState.PENDING === "pending"; + chrome.tabCapture.TabCaptureState.STOPPED === "stopped"; + + const captureOptions: chrome.tabCapture.CaptureOptions = { audio: true, - video: true, audioConstraints: { - mandatory: { - chromeMediaSource: "tab", - echoCancellation: true, - }, + mandatory: {}, + optional: {}, }, + video: true, videoConstraints: { - mandatory: { - chromeMediaSource: "tab", - maxWidth: resolutions.maxWidth, - maxHeight: resolutions.maxHeight, - minFrameRate: 30, - minAspectRatio: 1.77, - }, + mandatory: {}, + optional: {}, }, }; - let constraints2: chrome.tabCapture.CaptureOptions; - constraints2 = constraints; + chrome.tabCapture.capture(captureOptions, (stream) => { // $ExpectType void + stream; // $ExpectType MediaStream | null + }); + + chrome.tabCapture.getCapturedTabs(); // $ExpectType Promise + chrome.tabCapture.getCapturedTabs((result) => { // $ExpectType void + result; // $ExpectType CaptureInfo[] + }); + // @ts-expect-error + chrome.tabCapture.getCapturedTabs(() => {}).then(() => {}); + + const mediaStreamOptions: chrome.tabCapture.GetMediaStreamOptions = { + consumerTabId: 123, + targetTabId: 456, + }; + + chrome.tabCapture.getMediaStreamId(); // $ExpectType Promise + chrome.tabCapture.getMediaStreamId(mediaStreamOptions); // $ExpectType Promise + chrome.tabCapture.getMediaStreamId((streamId) => { // $ExpectType void + streamId; // $ExpectType string + }); + chrome.tabCapture.getMediaStreamId(mediaStreamOptions, (streamId) => { // $ExpectType void + streamId; // $ExpectType string + }); + // @ts-expect-error + chrome.tabCapture.getMediaStreamId(() => {}).then(() => {}); + + checkChromeEvent(chrome.tabCapture.onStatusChanged, (info) => { + info.fullscreen; // $ExpectType boolean + info.status; // $ExpectType "active" | "error" | "pending" | "stopped" + info.tabId; // $ExpectType number + }); } // https://developer.chrome.com/docs/extensions/reference/api/debugger From 8475918936a023708dc8cc62a2c217e9ee9029ba Mon Sep 17 00:00:00 2001 From: Nicolas Le Cam Date: Fri, 3 Oct 2025 08:05:19 +0200 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=A4=96=20Merge=20PR=20#73805=20[recor?= =?UTF-8?q?drtc]=20accept=20array=20of=20MediaStream=20in=20RecordRTC=20co?= =?UTF-8?q?nstructor=20by=20@KuSh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/recordrtc/index.d.ts | 5 +++- types/recordrtc/recordrtc-tests.ts | 42 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/types/recordrtc/index.d.ts b/types/recordrtc/index.d.ts index 712ae91e69c575..e36a8fe1bffd13 100644 --- a/types/recordrtc/index.d.ts +++ b/types/recordrtc/index.d.ts @@ -302,7 +302,10 @@ declare namespace RecordRTC { } declare class RecordRTC { - constructor(stream: MediaStream | HTMLCanvasElement | HTMLVideoElement | HTMLElement, options?: RecordRTC.Options); + constructor( + stream: MediaStream | MediaStream[] | HTMLCanvasElement | HTMLVideoElement | HTMLElement, + options?: RecordRTC.Options, + ); /** start the recording */ startRecording(): void; diff --git a/types/recordrtc/recordrtc-tests.ts b/types/recordrtc/recordrtc-tests.ts index b2d09840b7f049..6260257a74915e 100644 --- a/types/recordrtc/recordrtc-tests.ts +++ b/types/recordrtc/recordrtc-tests.ts @@ -50,6 +50,48 @@ navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then(stream => StereoAudioRecorder.onAudioProcessStarted(); }); +Promise.all([ + navigator.mediaDevices.getUserMedia({ audio: true, video: true }), + navigator.mediaDevices.getDisplayMedia({ audio: true, video: true }), +]).then(streams => { + const instance = new RecordRTC(streams, { + type: "video", + disableLogs: true, + bufferSize: 2048, + ondataavailable: (blob: Blob) => { + console.log(blob); + }, + onTimeStamp: (timestamp: number, timestamps: number[]) => { + console.log(timestamp, timestamps); + }, + previewStream: (stream: MediaStream) => { + console.log(stream); + }, + }); + + instance.stopRecording(() => { + const blob = instance.getBlob(); + }); + + // $ExpectType State + instance.getState(); + + // $ExpectType { onRecordingStopped: (callback: () => void) => void; } + instance.setRecordingDuration(1); + + const fiveMinutes = 5 * 1000 * 60; + // $ExpectType void + instance.setRecordingDuration(fiveMinutes, () => {}); + + // $ExpectType void + instance.getDataURL(dataURL => { + console.log({ dataURL }); + }); + + // $ExpectType void + instance.setRecordingDuration(fiveMinutes).onRecordingStopped(() => {}); +}); + const canvas = document.querySelector("canvas")!; const instance2 = new RecordRTC(canvas, { From 97c7c208fbe65f9c3cb3b50519a492425729ef45 Mon Sep 17 00:00:00 2001 From: Erwan Jugand <47392755+erwanjugand@users.noreply.github.com> Date: Fri, 3 Oct 2025 08:11:07 +0200 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=A4=96=20Merge=20PR=20#73667=20[chrom?= =?UTF-8?q?e]=20update=20loginState=20namespace=20by=20@erwanjugand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- types/chrome/index.d.ts | 40 ++++++++++++++++++++++++++------------ types/chrome/test/index.ts | 31 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/types/chrome/index.d.ts b/types/chrome/index.d.ts index 1b3d1b9f77d253..e8ec196ac916c0 100644 --- a/types/chrome/index.d.ts +++ b/types/chrome/index.d.ts @@ -6900,22 +6900,38 @@ declare namespace chrome { * @since Chrome 78 */ export namespace loginState { - export interface SessionStateChangedEvent extends chrome.events.Event<(sessionState: SessionState) => void> {} - - /** Possible profile types. */ - export type ProfileType = "SIGNIN_PROFILE" | "USER_PROFILE"; + export enum ProfileType { + SIGNIN_PROFILE = "SIGNIN_PROFILE", + USER_PROFILE = "USER_PROFILE", + } - /** Possible session states. */ - export type SessionState = "UNKNOWN" | "IN_OOBE_SCREEN" | "IN_LOGIN_SCREEN" | "IN_SESSION" | "IN_LOCK_SCREEN"; + export enum SessionState { + UNKNOWN = "UNKNOWN", + IN_OOBE_SCREEN = "IN_OOBE_SCREEN", + IN_LOGIN_SCREEN = "IN_LOGIN_SCREEN", + IN_SESSION = "IN_SESSION", + IN_LOCK_SCREEN = "IN_LOCK_SCREEN", + IN_RMA_SCREEN = "IN_RMA_SCREEN", + } - /** Gets the type of the profile the extension is in. */ - export function getProfileType(callback: (profileType: ProfileType) => void): void; + /** + * Gets the type of the profile the extension is in. + * + * Can return its result via Promise in Manifest V3 or later since Chrome 96. + */ + export function getProfileType(): Promise<`${ProfileType}`>; + export function getProfileType(callback: (result: `${ProfileType}`) => void): void; - /** Gets the current session state. */ - export function getSessionState(callback: (sessionState: SessionState) => void): void; + /** + * Gets the current session state. + * + * Can return its result via Promise in Manifest V3 or later since Chrome 96. + */ + export function getSessionState(): Promise<`${SessionState}`>; + export function getSessionState(callback: (sessionState: `${SessionState}`) => void): void; - /** Dispatched when the session state changes. sessionState is the new session state.*/ - export const onSessionStateChanged: SessionStateChangedEvent; + /** Dispatched when the session state changes. `sessionState` is the new session state.*/ + export const onSessionStateChanged: events.Event<(sessionState: `${SessionState}`) => void>; } //////////////////// diff --git a/types/chrome/test/index.ts b/types/chrome/test/index.ts index 637fe1e3f88e56..363306740f25f6 100644 --- a/types/chrome/test/index.ts +++ b/types/chrome/test/index.ts @@ -6048,6 +6048,37 @@ function testInstanceID() { checkChromeEvent(chrome.instanceID.onTokenRefresh, () => void 0); } +// https://developer.chrome.com/docs/extensions/reference/api/loginState +function testLoginState() { + chrome.loginState.ProfileType.SIGNIN_PROFILE === "SIGNIN_PROFILE"; + chrome.loginState.ProfileType.USER_PROFILE === "USER_PROFILE"; + + chrome.loginState.SessionState.IN_LOCK_SCREEN === "IN_LOCK_SCREEN"; + chrome.loginState.SessionState.IN_LOGIN_SCREEN === "IN_LOGIN_SCREEN"; + chrome.loginState.SessionState.IN_OOBE_SCREEN === "IN_OOBE_SCREEN"; + chrome.loginState.SessionState.IN_RMA_SCREEN === "IN_RMA_SCREEN"; + chrome.loginState.SessionState.IN_SESSION === "IN_SESSION"; + chrome.loginState.SessionState.UNKNOWN === "UNKNOWN"; + + chrome.loginState.getProfileType(); // $ExpectType Promise<"SIGNIN_PROFILE" | "USER_PROFILE"> + chrome.loginState.getProfileType((result) => { // $ExpectType void + result; // $ExpectType "SIGNIN_PROFILE" | "USER_PROFILE" + }); + // @ts-expect-error + chrome.loginState.getProfileType(() => {}).then(() => {}); + + chrome.loginState.getSessionState(); // $ExpectType Promise<"IN_LOCK_SCREEN" | "IN_LOGIN_SCREEN" | "IN_OOBE_SCREEN" | "IN_RMA_SCREEN" | "IN_SESSION" | "UNKNOWN"> + chrome.loginState.getSessionState((result) => { // $ExpectType void + result; // $ExpectType "IN_LOCK_SCREEN" | "IN_LOGIN_SCREEN" | "IN_OOBE_SCREEN" | "IN_RMA_SCREEN" | "IN_SESSION" | "UNKNOWN" + }); + // @ts-expect-error + chrome.loginState.getSessionState(() => {}).then(() => {}); + + checkChromeEvent(chrome.loginState.onSessionStateChanged, (sessionState) => { + sessionState; // $ExpectType "IN_LOCK_SCREEN" | "IN_LOGIN_SCREEN" | "IN_OOBE_SCREEN" | "IN_RMA_SCREEN" | "IN_SESSION" | "UNKNOWN" + }); +} + function testUserScripts() { const worldProperties: chrome.userScripts.WorldProperties = { csp: "script-src 'self'",