mirror of
https://github.com/finos/SymphonyElectron.git
synced 2024-11-21 16:38:41 -06:00
Initial implementation (#1362)
This commit is contained in:
parent
95b03ff5a1
commit
41f0ca4e7e
159
spec/hwndHandler.spec.ts
Normal file
159
spec/hwndHandler.spec.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import { getContentWindowHandle } from '../src/app/hwnd-handler';
|
||||
|
||||
jest.mock('../src/common/env', () => {
|
||||
return {
|
||||
isWindowsOS: true,
|
||||
isLinux: false,
|
||||
isMac: false,
|
||||
};
|
||||
});
|
||||
|
||||
const mockFindWindowExA = jest.fn();
|
||||
const mockGetWindowRect = jest.fn();
|
||||
|
||||
jest.mock('ffi-napi', () => {
|
||||
return {
|
||||
Library: jest.fn(() => {
|
||||
return {
|
||||
FindWindowExA: mockFindWindowExA,
|
||||
GetWindowRect: mockGetWindowRect,
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
function writeRect(
|
||||
buffer: Buffer,
|
||||
left: number,
|
||||
top: number,
|
||||
right: number,
|
||||
bottom: number,
|
||||
) {
|
||||
buffer.writeInt32LE(left, 0);
|
||||
buffer.writeInt32LE(top, 4);
|
||||
buffer.writeInt32LE(right, 8);
|
||||
buffer.writeInt32LE(bottom, 12);
|
||||
}
|
||||
|
||||
describe('hwnd handler', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks().resetModules();
|
||||
|
||||
mockGetWindowRect.mockImplementation((_hwnd: bigint, _rect: Buffer) => {
|
||||
return 0;
|
||||
});
|
||||
mockFindWindowExA.mockImplementation(() => {
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
|
||||
it('not using windows', () => {
|
||||
jest.mock('../src/common/env', () => {
|
||||
return {
|
||||
isWindowsOS: false,
|
||||
isLinux: true,
|
||||
isMac: false,
|
||||
};
|
||||
});
|
||||
const { getContentWindowHandle } = require('../src/app/hwnd-handler');
|
||||
const parent = Buffer.from('hwnd', 'utf8');
|
||||
const hwnd = getContentWindowHandle(parent);
|
||||
expect(hwnd).toBe(parent);
|
||||
});
|
||||
|
||||
it('unexpected buffer size', () => {
|
||||
const parent = Buffer.from('hwnd', 'utf8');
|
||||
const hwnd = getContentWindowHandle(parent);
|
||||
expect(hwnd).toBe(parent);
|
||||
});
|
||||
|
||||
it('no rect found for parent window', () => {
|
||||
const parent = Buffer.from('validhwnd', 'utf8');
|
||||
const hwnd = getContentWindowHandle(parent);
|
||||
expect(hwnd).toBe(parent);
|
||||
});
|
||||
|
||||
it('no child window found', () => {
|
||||
mockGetWindowRect.mockImplementationOnce((_hwnd, rect) => {
|
||||
writeRect(rect, 10, 10, 100, 100);
|
||||
return 1;
|
||||
});
|
||||
|
||||
const parent = Buffer.from('validhwnd', 'utf8');
|
||||
const hwnd = getContentWindowHandle(parent);
|
||||
|
||||
expect(mockGetWindowRect).toBeCalledTimes(1);
|
||||
expect(mockFindWindowExA).toBeCalledTimes(1);
|
||||
expect(hwnd).toBe(parent);
|
||||
});
|
||||
|
||||
it('matching child window found', () => {
|
||||
mockGetWindowRect.mockImplementationOnce((_hwnd, rect) => {
|
||||
writeRect(rect, 10, 10, 100, 100);
|
||||
return 1;
|
||||
});
|
||||
mockGetWindowRect.mockImplementationOnce((_hwnd, rect) => {
|
||||
writeRect(rect, 10, 20, 100, 100);
|
||||
return 1;
|
||||
});
|
||||
mockFindWindowExA.mockImplementationOnce(() => {
|
||||
return 4711;
|
||||
});
|
||||
|
||||
const parent = Buffer.from('validhwnd', 'utf8');
|
||||
const hwnd = getContentWindowHandle(parent);
|
||||
|
||||
expect(mockGetWindowRect).toBeCalledTimes(2);
|
||||
expect(mockFindWindowExA).toBeCalledTimes(1);
|
||||
expect(hwnd.readInt32LE(0)).toBe(4711);
|
||||
});
|
||||
|
||||
it('matching child window found second', () => {
|
||||
mockGetWindowRect.mockImplementationOnce((_hwnd, rect) => {
|
||||
writeRect(rect, 10, 10, 100, 100);
|
||||
return 1;
|
||||
});
|
||||
mockGetWindowRect.mockImplementationOnce((_hwnd, rect) => {
|
||||
writeRect(rect, 100, 10, 100, 100);
|
||||
return 1;
|
||||
});
|
||||
mockGetWindowRect.mockImplementationOnce((_hwnd, rect) => {
|
||||
writeRect(rect, 10, 20, 100, 100);
|
||||
return 1;
|
||||
});
|
||||
mockFindWindowExA.mockImplementationOnce(() => {
|
||||
return 4711;
|
||||
});
|
||||
mockFindWindowExA.mockImplementationOnce(() => {
|
||||
return 42;
|
||||
});
|
||||
|
||||
const parent = Buffer.from('validhwnd', 'utf8');
|
||||
const hwnd = getContentWindowHandle(parent);
|
||||
|
||||
expect(mockGetWindowRect).toBeCalledTimes(3);
|
||||
expect(mockFindWindowExA).toBeCalledTimes(2);
|
||||
expect(hwnd.readInt32LE(0)).toBe(42);
|
||||
});
|
||||
|
||||
it('no matching child window found', () => {
|
||||
mockGetWindowRect.mockImplementationOnce((_hwnd, rect) => {
|
||||
writeRect(rect, 10, 10, 100, 100);
|
||||
return 1;
|
||||
});
|
||||
mockGetWindowRect.mockImplementationOnce((_hwnd, rect) => {
|
||||
writeRect(rect, 10, 10, 100, 100);
|
||||
return 1;
|
||||
});
|
||||
mockFindWindowExA.mockImplementationOnce(() => {
|
||||
return 4711;
|
||||
});
|
||||
|
||||
const parent = Buffer.from('validhwnd', 'utf8');
|
||||
const hwnd = getContentWindowHandle(parent);
|
||||
|
||||
expect(mockGetWindowRect).toBeCalledTimes(2);
|
||||
expect(mockFindWindowExA).toBeCalledTimes(2);
|
||||
expect(hwnd).toBe(parent);
|
||||
});
|
||||
});
|
70
src/app/hwnd-handler.ts
Normal file
70
src/app/hwnd-handler.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { Library } from 'ffi-napi';
|
||||
import { isWindowsOS } from '../common/env';
|
||||
|
||||
/**
|
||||
* Translate the nativeWindowHandle of an Electron BrowserWindow to the handle
|
||||
* of the window where the main content is hosted. On Windows, Chrome uses a separate
|
||||
* window handle for the title bar and the main content has a different window handle
|
||||
* that is positioned below the title bar.
|
||||
* @returns translated window handle, or original handle if no applicable translation found
|
||||
*/
|
||||
export const getContentWindowHandle = (nativeWindowHandle: Buffer): Buffer => {
|
||||
if (!isWindowsOS) {
|
||||
return nativeWindowHandle;
|
||||
}
|
||||
if (nativeWindowHandle.byteLength < 8) {
|
||||
return nativeWindowHandle;
|
||||
}
|
||||
|
||||
const user32 = Library('user32.dll', {
|
||||
FindWindowExA: ['int', ['int', 'int', 'string', 'string']],
|
||||
GetWindowRect: ['int', ['int', 'pointer']],
|
||||
});
|
||||
|
||||
const getWindowRect = (hwnd: bigint) => {
|
||||
const rect = Buffer.alloc(16);
|
||||
|
||||
const ret = user32.GetWindowRect(hwnd.toString(), rect);
|
||||
if (ret) {
|
||||
return {
|
||||
left: rect.readInt32LE(0),
|
||||
top: rect.readInt32LE(4),
|
||||
right: rect.readInt32LE(8),
|
||||
bottom: rect.readInt32LE(12),
|
||||
};
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const parentHwnd = nativeWindowHandle.readBigUInt64LE();
|
||||
const parentRect = getWindowRect(parentHwnd);
|
||||
if (!parentRect) {
|
||||
return nativeWindowHandle;
|
||||
}
|
||||
|
||||
let child = user32.FindWindowExA(
|
||||
parentHwnd.toString(),
|
||||
0,
|
||||
'Chrome_RenderWidgetHostHWND',
|
||||
null,
|
||||
);
|
||||
while (child !== 0) {
|
||||
const rect = getWindowRect(child);
|
||||
|
||||
// The candidate child window is located at the same x position as the parent window, but
|
||||
// has a higher y position (due to the window title frame at the top).
|
||||
if (rect && parentRect.left === rect.left && parentRect.top < rect.top) {
|
||||
const ret = Buffer.alloc(8);
|
||||
ret.writeBigUInt64LE(BigInt(child));
|
||||
return ret;
|
||||
}
|
||||
child = user32.FindWindowExA(
|
||||
parentHwnd.toString(),
|
||||
child,
|
||||
'Chrome_RenderWidgetHostHWND',
|
||||
null,
|
||||
);
|
||||
}
|
||||
return nativeWindowHandle;
|
||||
};
|
@ -20,6 +20,7 @@ import appStateHandler from './app-state-handler';
|
||||
import { getCitrixMediaRedirectionStatus } from './citrix-handler';
|
||||
import { CloudConfigDataTypes, config, ICloudConfig } from './config-handler';
|
||||
import { downloadHandler } from './download-handler';
|
||||
import { getContentWindowHandle } from './hwnd-handler';
|
||||
import { mainEvents } from './main-event-handler';
|
||||
import { memoryMonitor } from './memory-monitor';
|
||||
import notificationHelper from './notifications/notification-helper';
|
||||
@ -422,7 +423,8 @@ ipcMain.handle(
|
||||
event.sender,
|
||||
) as ICustomBrowserWindow;
|
||||
if (browserWin && windowExists(browserWin)) {
|
||||
return browserWin.getNativeWindowHandle();
|
||||
const windowHandle = browserWin.getNativeWindowHandle();
|
||||
return getContentWindowHandle(windowHandle);
|
||||
}
|
||||
break;
|
||||
case apiCmds.getCitrixMediaRedirectionStatus:
|
||||
|
Loading…
Reference in New Issue
Block a user