diff --git a/src/browser/api/external_window.ts b/src/browser/api/external_window.ts index e99012379..bf3dfd686 100644 --- a/src/browser/api/external_window.ts +++ b/src/browser/api/external_window.ts @@ -13,6 +13,7 @@ import route from '../../common/route'; import WindowGroups, { GroupChangedEvent, GroupEvent } from '../window_groups'; import ProcessTracker from '../process_tracker'; import SubscriptionManager from '../subscription_manager'; +import { releaseUuid, lockUuid } from '../uuid_availability'; electronApp.on('ready', () => { subToGlobalWinEventHooks(); @@ -22,6 +23,7 @@ const subscriptionManager = new SubscriptionManager(); // Maps export const externalWindows = new Map(); +export const nativeIdToUuid = new Map(); const disabledUserMovementRequestorCount = new Map(); const externalWindowEventAdapters = new Map(); const injectionBuses = new Map(); @@ -106,9 +108,9 @@ export function getExternalWindowGroup(identity: Identity): Shapes.GroupWindowId return windowGroup.map(({ name, uuid, isExternalWindow }) => ({ name, uuid, windowName: name, isExternalWindow })); } -export function getExternalWindowInfo(identity: Identity): Shapes.NativeWindowInfo { - const { uuid } = identity; - const rawNativeWindowInfo = electronApp.getNativeWindowInfoForNativeId(uuid); +export function getExternalWindowInfo(identity: Shapes.NativeWindowIdentity): Shapes.NativeWindowInfo { + const { nativeId } = identity; + const rawNativeWindowInfo = electronApp.getNativeWindowInfoForNativeId(nativeId); return getNativeWindowInfo(rawNativeWindowInfo); } @@ -257,29 +259,28 @@ function findExistingNativeWindow(identity: Shapes.NativeWindowIdentity): Shapes ? allNativeWindows.find(win => win.process.pid === prelaunchedProcess.process.id) : allNativeWindows.find(win => { const liteInfo = getNativeWindowInfoLite(win); - return liteInfo.uuid === uuid || liteInfo.nativeId === nativeId; + return liteInfo.nativeId === nativeId; }); - if (!win) { - return; - } - - const liteInfo = getNativeWindowInfoLite(win); - if (prelaunchedProcess) { - // Respect original uuid if present - liteInfo.uuid = prelaunchedProcess.uuid; - } - return liteInfo; + return win && getNativeWindowInfoLite(win); } /* Returns a registered native window or creates a new one if not found. */ export function getExternalWindow(identity: Shapes.NativeWindowIdentity): Shapes.ExternalWindow { - const { uuid } = identity; + const nativeId = identity.nativeId; + const uuid = identity.uuid || electronApp.generateGUID(); let externalWindow = externalWindows.get(uuid); + if (externalWindow && nativeId && externalWindow.nativeId !== nativeId) { + throw new Error(`Requested uuid "${uuid}" is already in use for a different window.`); + } + if (!externalWindow) { + if (!lockUuid(uuid)) { + throw new Error(`Failed to wrap, uuid "${uuid}" is already in use.`); + } const nativeWinObj = findExistingNativeWindow(identity); if (!nativeWinObj) { @@ -287,12 +288,13 @@ export function getExternalWindow(identity: Shapes.NativeWindowIdentity): Shapes } externalWindow = (new ExternalWindow({ hwnd: nativeWinObj.nativeId })); - setAdditionalProperties(externalWindow, identity); + setAdditionalProperties(externalWindow, { uuid, name: identity.name || nativeWinObj.name }); subscribeToInjectionEvents(externalWindow); subscribeToWinEventHooks(externalWindow); subscribeToWindowGroupEvents(externalWindow); - externalWindows.set(uuid, externalWindow); + externalWindows.set(externalWindow.uuid, externalWindow); + nativeIdToUuid.set(externalWindow.nativeId, externalWindow.uuid); } return externalWindow; @@ -450,7 +452,7 @@ function subscribeToWinEventHooks(externalWindow: Shapes.ExternalWindow): void { // We are subscribing to a process, so we only care about a specific window. // idChild === '0' indicates that event is from main window, not a subcomponent. - if (nativeWindowInfo.uuid !== nativeId || idChild !== '0') { + if (nativeWindowInfo.nativeId !== nativeId || idChild !== '0') { return; } parser(nativeWindowInfo); @@ -532,10 +534,13 @@ function subscribeToWinEventHooks(externalWindow: Shapes.ExternalWindow): void { // Window grouping stub (makes external windows work with our original disabled frame group tracker) // Also some of the _options' values are needed in OpenFin Layouts -function setAdditionalProperties(externalWindow: Shapes.ExternalWindow, properIdentity: Shapes.NativeWindowIdentity): Shapes.GroupWindow { +function setAdditionalProperties( + externalWindow: Shapes.ExternalWindow, + requestedIdentity: Shapes.NativeWindowIdentity +): Shapes.GroupWindow { const { nativeId } = externalWindow; - const uuid = properIdentity.uuid || nativeId; - const name = properIdentity.name || nativeId; + const uuid = requestedIdentity.uuid; + const name = requestedIdentity.name || nativeId; const identity = { uuid, name, nativeId }; externalWindow._userMovement = true; @@ -670,7 +675,7 @@ export function isValidExternalWindow(rawNativeWindowInfo: NativeWindowInfo, ign */ function externalWindowCloseCleanup(externalWindow: Shapes.ExternalWindow): void { const key = getKey(externalWindow); - const { nativeId } = externalWindow; + const { uuid, nativeId } = externalWindow; const winEventHooks = winEventHooksEmitters.get(key); const injectionBus = injectionBuses.get(key); const externalWindowEventAdapter = externalWindowEventAdapters.get(key); @@ -679,21 +684,28 @@ function externalWindowCloseCleanup(externalWindow: Shapes.ExternalWindow): void externalWindow.emit('closing'); disabledUserMovementRequestorCount.delete(key); - winEventHooks.removeAllListeners(); - winEventHooksEmitters.delete(key); + try { + winEventHooks.removeAllListeners(); + winEventHooksEmitters.delete(key); + + injectionBus.removeAllListeners(); + injectionBuses.delete(key); - injectionBus.removeAllListeners(); - injectionBuses.delete(key); + windowGroupUnSubscription(); + windowGroupUnSubscriptions.delete(key); - windowGroupUnSubscription(); - windowGroupUnSubscriptions.delete(key); + externalWindowEventAdapter.removeAllListeners(); + externalWindowEventAdapters.delete(key); - externalWindowEventAdapter.removeAllListeners(); - externalWindowEventAdapters.delete(key); + externalWindow.emit('closed'); + externalWindow.removeAllListeners(); + } catch (err) { + electronApp.vlog(2, `Error cleaning up ExternalWindow: ${err}`); + } - externalWindow.emit('closed'); - externalWindow.removeAllListeners(); - externalWindows.delete(nativeId); + externalWindows.delete(uuid); + nativeIdToUuid.delete(nativeId); + releaseUuid(uuid); } /* diff --git a/src/browser/api/system.js b/src/browser/api/system.js index 28bb51aec..020d1ef2a 100644 --- a/src/browser/api/system.js +++ b/src/browser/api/system.js @@ -29,7 +29,7 @@ import { fetchReadFile } from '../cached_resource_fetcher'; import { createChromiumSocket, authenticateChromiumSocket } from '../transports/chromium_socket'; import { authenticateFetch, grantAccess } from '../cached_resource_fetcher'; import { getNativeWindowInfoLite } from '../utils'; -import { isValidExternalWindow } from './external_window'; +import { isValidExternalWindow, nativeIdToUuid } from './external_window'; import { getWindowByUuidName, getBrowserViewByIdentity, getInfoByUuidFrame } from '../core_state'; const defaultProc = { @@ -731,6 +731,10 @@ export const System = { allNativeWindows.forEach(e => { const externalWindow = getNativeWindowInfoLite(e); + const uuid = nativeIdToUuid.get(externalWindow.nativeId); + if (uuid) { + externalWindow.uuid = uuid; + } const isValid = isValidExternalWindow(e); if (isValid) { diff --git a/src/browser/utils.ts b/src/browser/utils.ts index abd1777ba..6289dd77f 100644 --- a/src/browser/utils.ts +++ b/src/browser/utils.ts @@ -18,6 +18,8 @@ import { basename } from 'path'; import { BrowserWindow as OFBrowserWindow } from '../shapes'; import { BrowserWindow, Rectangle, screen, NativeWindowInfo } from 'electron'; import * as Shapes from '../shapes'; +import { nativeIdToUuid } from './api/external_window'; +import ProcessTracker from './process_tracker'; /* This function sets window's bounds to be in a visible area, in case @@ -83,7 +85,7 @@ export function getNativeWindowInfoLite(rawNativeWindowInfo: NativeWindowInfo): name = rawNativeWindowInfo.title; } - return { + const liteInfo: Shapes.NativeWindowInfoLite = { name, nativeId: rawNativeWindowInfo.id, process: { @@ -91,15 +93,38 @@ export function getNativeWindowInfoLite(rawNativeWindowInfo: NativeWindowInfo): pid: rawNativeWindowInfo.process.pid }, title: rawNativeWindowInfo.title, - uuid: rawNativeWindowInfo.id, visible: rawNativeWindowInfo.visible }; + + const uuid = getNativeWindowUuid(liteInfo); + + if (uuid) { + liteInfo.uuid = uuid; + } + + return liteInfo; +} + +/* + Finds an appropriate uuid for a native window by first checking for its nativeId among + registered ExternalWindows, then checking for its pid among registered external processes +*/ +function getNativeWindowUuid(nativeWindowInfo: Shapes.NativeWindowInfoLite): string | void { + let uuid = nativeIdToUuid.get(nativeWindowInfo.nativeId); + if (!uuid) { + const { process: { pid } } = nativeWindowInfo; + const launchedProcess = ProcessTracker.getProcessByPid(pid); + if (launchedProcess) { + uuid = launchedProcess.uuid; + } + } + return uuid; } /* Returns full version of external window info object */ -export function getNativeWindowInfo(rawNativeWindowInfo: NativeWindowInfo): Shapes.NativeWindowInfo { +export function getNativeWindowInfo(rawNativeWindowInfo: NativeWindowInfo): Shapes.NativeWindowInfo & Shapes.NativeWindowInfoLite { const liteInfoObject = getNativeWindowInfoLite(rawNativeWindowInfo); return { diff --git a/src/shapes.ts b/src/shapes.ts index b8d1b2525..3df5a36c0 100644 --- a/src/shapes.ts +++ b/src/shapes.ts @@ -477,7 +477,7 @@ export interface Process extends Omit { export interface NativeWindowInfo extends Omit { process: Process; name: string; - uuid: string; + uuid?: string; } export type NativeWindowInfoLite = (Pick) & { nativeId: string };