Files
electron/lib/browser/api/shared-texture.ts
Samuel Attard 2c94aac330 build: add oxfmt for JS/TS formatting and import sorting (#50692)
* build: add oxfmt for code formatting and import sorting

Adds oxfmt as a devDependency alongside oxlint and wires it into the
lint pipeline. The .oxfmtrc.json config matches Electron's current JS
style (single quotes, semicolons, 2-space indent, trailing commas off,
printWidth 100) and configures sortImports with custom groups that
mirror the import/order pathGroups previously enforced by ESLint:
@electron/internal, @electron/*, and {electron,electron/**} each get
their own ordered group ahead of external modules.

- `yarn lint:fmt` runs `oxfmt --check` over JS/TS sources and is
  chained into `yarn lint` so CI enforces it automatically.
- `yarn format` runs `oxfmt --write` for local fix-up.
- lint-staged invokes `oxfmt --write` on staged .js/.ts/.mjs/.cjs
  files before oxlint, so formatting is applied at commit time.

The next commit applies the formatter to the existing codebase so the
check actually passes.

* chore: apply oxfmt formatting to JS and TS sources

Runs `yarn format` across lib/, spec/, script/, build/, default_app/,
and npm/ to bring the codebase in line with the .oxfmtrc.json settings
added in the previous commit. This is a pure formatting pass: import
statements are sorted into the groups defined by the config, method
chains longer than printWidth are broken, single-quoted strings
containing apostrophes are switched to double quotes, and a handful of
single-statement `if` bodies are re-wrapped and get braces added by
`oxlint --fix` to satisfy the `curly: multi-line` rule.

No behavior changes.
2026-04-12 02:03:04 -07:00

202 lines
6.6 KiB
TypeScript

import ipcMain from '@electron/internal/browser/api/ipc-main';
import * as ipcMainInternalUtils from '@electron/internal/browser/ipc-main-internal-utils';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
import { randomUUID } from 'crypto';
const transferTimeout = 1000;
const sharedTextureNative = process._linkedBinding('electron_common_shared_texture');
const managedSharedTextures = new Map<string, SharedTextureImportedWrapper>();
type AllReleasedCallback = (imported: Electron.SharedTextureImported) => void;
type SharedTextureImportedWrapper = {
texture: Electron.SharedTextureImported;
allReferencesReleased: AllReleasedCallback | undefined;
mainReference: boolean;
rendererFrameReferences: Map<number, { count: number; reference: Electron.WebFrameMain }>;
};
ipcMain.handle(
IPC_MESSAGES.IMPORT_SHARED_TEXTURE_RELEASE_RENDERER_TO_MAIN,
(event: Electron.IpcMainInvokeEvent, textureId: string) => {
const frameTreeNodeId = event.frameTreeNodeId ?? event.sender.mainFrame.frameTreeNodeId;
wrapperReleaseFromRenderer(textureId, frameTreeNodeId);
}
);
let checkManagedSharedTexturesInterval: NodeJS.Timeout | null = null;
function scheduleCheckManagedSharedTextures() {
if (checkManagedSharedTexturesInterval === null) {
checkManagedSharedTexturesInterval = setInterval(checkManagedSharedTextures, 1000);
}
}
function unscheduleCheckManagedSharedTextures() {
if (checkManagedSharedTexturesInterval !== null) {
clearInterval(checkManagedSharedTexturesInterval);
checkManagedSharedTexturesInterval = null;
}
}
function checkManagedSharedTextures() {
const texturesToRemoveTracking = new Set<string>();
for (const [, wrapper] of managedSharedTextures) {
for (const [frameTreeNodeId, entry] of wrapper.rendererFrameReferences) {
const frame = entry.reference;
if (!frame || frame.isDestroyed()) {
console.error(
`The imported shared texture ${wrapper.texture.textureId} is referenced by a destroyed webContent/webFrameMain, this means a imported shared texture in renderer process is not released before the process is exited. Releasing that dangling reference now.`
);
wrapper.rendererFrameReferences.delete(frameTreeNodeId);
}
}
if (wrapper.rendererFrameReferences.size === 0 && !wrapper.mainReference) {
texturesToRemoveTracking.add(wrapper.texture.textureId);
wrapper.texture.subtle.release(() => {
wrapper.allReferencesReleased?.(wrapper.texture);
});
}
}
for (const id of texturesToRemoveTracking) {
managedSharedTextures.delete(id);
}
if (managedSharedTextures.size === 0) {
unscheduleCheckManagedSharedTextures();
}
}
function wrapperReleaseFromRenderer(id: string, frameTreeNodeId: number) {
const wrapper = managedSharedTextures.get(id);
if (!wrapper) {
throw new Error(`Shared texture with id ${id} not found`);
}
const entry = wrapper.rendererFrameReferences.get(frameTreeNodeId);
if (!entry) {
throw new Error(`Shared texture ${id} is not referenced by renderer frame ${frameTreeNodeId}`);
}
entry.count -= 1;
if (entry.count === 0) {
wrapper.rendererFrameReferences.delete(frameTreeNodeId);
} else {
wrapper.rendererFrameReferences.set(frameTreeNodeId, entry);
}
// Actually release the texture if no one is referencing it
if (wrapper.rendererFrameReferences.size === 0 && !wrapper.mainReference) {
managedSharedTextures.delete(id);
wrapper.texture.subtle.release(() => {
wrapper.allReferencesReleased?.(wrapper.texture);
});
}
}
function wrapperReleaseFromMain(id: string) {
const wrapper = managedSharedTextures.get(id);
if (!wrapper) {
throw new Error(`Shared texture with id ${id} not found`);
}
// Actually release the texture if no one is referencing it
wrapper.mainReference = false;
if (wrapper.rendererFrameReferences.size === 0) {
managedSharedTextures.delete(id);
wrapper.texture.subtle.release(() => {
wrapper.allReferencesReleased?.(wrapper.texture);
});
}
}
async function sendSharedTexture(options: Electron.SendSharedTextureOptions, ...args: any[]) {
const imported = options.importedSharedTexture;
const transfer = imported.subtle.startTransferSharedTexture();
let timeoutHandle: NodeJS.Timeout | null = null;
const timeoutPromise = new Promise<never>((resolve, reject) => {
timeoutHandle = setTimeout(() => {
reject(
new Error(
`transfer shared texture timed out after ${transferTimeout}ms, ensure you have registered receiver at renderer process.`
)
);
}, transferTimeout);
});
const targetFrame: Electron.WebFrameMain | undefined = options.frame;
if (!targetFrame) {
throw new Error('`frame` should be provided');
}
const invokePromise: Promise<Electron.SharedTextureSyncToken> =
ipcMainInternalUtils.invokeInWebFrameMain<Electron.SharedTextureSyncToken>(
targetFrame,
IPC_MESSAGES.IMPORT_SHARED_TEXTURE_TRANSFER_MAIN_TO_RENDERER,
transfer,
imported.textureId,
...args
);
try {
const syncToken = await Promise.race([invokePromise, timeoutPromise]);
imported.subtle.setReleaseSyncToken(syncToken);
const wrapper = managedSharedTextures.get(imported.textureId);
if (!wrapper) {
throw new Error(`Shared texture with id ${imported.textureId} not found`);
}
const key = targetFrame.frameTreeNodeId;
const existing = wrapper.rendererFrameReferences.get(key);
if (existing) {
existing.count += 1;
wrapper.rendererFrameReferences.set(key, existing);
} else {
wrapper.rendererFrameReferences.set(key, { count: 1, reference: targetFrame });
}
} finally {
if (timeoutHandle) {
clearTimeout(timeoutHandle);
}
}
// Schedule a check to see if any texture is referenced by any dangling renderer
scheduleCheckManagedSharedTextures();
}
function importSharedTexture(options: Electron.ImportSharedTextureOptions) {
const id = randomUUID();
const imported = sharedTextureNative.importSharedTexture(Object.assign(options.textureInfo, { id }));
const ret: Electron.SharedTextureImported = {
textureId: id,
subtle: imported,
getVideoFrame: imported.getVideoFrame,
release: () => {
wrapperReleaseFromMain(id);
}
};
const wrapper: SharedTextureImportedWrapper = {
texture: ret,
allReferencesReleased: options.allReferencesReleased,
mainReference: true,
rendererFrameReferences: new Map()
};
managedSharedTextures.set(id, wrapper);
return ret;
}
const sharedTexture = {
subtle: sharedTextureNative,
importSharedTexture,
sendSharedTexture
};
export default sharedTexture;