diff --git a/src/Default/default.ts b/src/Default/default.ts index 855725f1..d6d726db 100644 --- a/src/Default/default.ts +++ b/src/Default/default.ts @@ -94,6 +94,12 @@ export class DefaultContainerWindow extends ContainerWindow { }); } + public getOptions(): Promise { + return new Promise((resolve, reject) => { + resolve(this.innerWindow[Container.windowOptionsPropertyKey]); + }); + } + protected attachListener(eventName: string, listener: (...args: any[]) => void): void { this.innerWindow.addEventListener(windowEventMap[eventName] || eventName, listener); } diff --git a/src/Electron/electron.ts b/src/Electron/electron.ts index 547a91f6..473d8af4 100644 --- a/src/Electron/electron.ts +++ b/src/Electron/electron.ts @@ -25,6 +25,7 @@ class InternalMessageType { public static readonly getGroup: string = "desktopJS.window-getGroup"; public static readonly joinGroup: string = "desktopJS.window-joinGroup"; public static readonly leaveGroup: string = "desktopJS.window-leaveGroup"; + public static readonly getOptions: string = "desktopJS.window-getOptions"; } const windowEventMap = {}; @@ -164,6 +165,16 @@ export class ElectronContainerWindow extends ContainerWindow { return Promise.resolve(); } + public getOptions(): Promise { + return new Promise((resolve, reject) => { + const options = (this.isRemote) + ? (this.container.internalIpc).sendSync(InternalMessageType.getOptions, { source: this.id }) + : this.innerWindow[Container.windowOptionsPropertyKey]; + + resolve(options); + }); + } + protected attachListener(eventName: string, listener: (...args: any[]) => void): void { this.innerWindow.addListener(windowEventMap[eventName] || eventName, listener); } @@ -506,6 +517,11 @@ export class ElectronWindowManager { const { "source": sourceId } = message; event.returnValue = this.getGroup(this.browserWindow.fromId(sourceId)); }); + + this.ipc.on(InternalMessageType.getOptions, (event: any, message: any) => { + const { "source": sourceId } = message; + event.returnValue = this.browserWindow.fromId(sourceId)[Container.windowOptionsPropertyKey]; + }); } public initializeWindow(win: any, name: string, options: any) { diff --git a/src/OpenFin/openfin.ts b/src/OpenFin/openfin.ts index e0ff22d3..c21c38b1 100644 --- a/src/OpenFin/openfin.ts +++ b/src/OpenFin/openfin.ts @@ -151,6 +151,12 @@ export class OpenFinContainerWindow extends ContainerWindow { }); } + public getOptions(): Promise { + return new Promise((resolve, reject) => { + this.innerWindow.getOptions(options => resolve(options.customData ? JSON.parse(options.customData) : undefined), reject); + }); + } + protected attachListener(eventName: string, listener: (...args: any[]) => void): void { this.innerWindow.addEventListener(windowEventMap[eventName] || eventName, listener); } @@ -411,6 +417,7 @@ export class OpenFinContainer extends WebContainerBase { protected getWindowOptions(options?: any): any { const newOptions = ObjectTransform.transformProperties(options, this.windowOptionsMap); + newOptions.customData = options ? JSON.stringify(options) : undefined; // Default behavior is to show window so if there is no override in options, show the window if (!("autoShow" in newOptions)) { diff --git a/src/window.ts b/src/window.ts index d5511189..2586134c 100644 --- a/src/window.ts +++ b/src/window.ts @@ -61,7 +61,7 @@ export type WindowEventType = "maximize" | "minimize" | "restore" -; + ; export class WindowEventArgs extends EventArgs { public readonly window?: ContainerWindow; @@ -83,7 +83,7 @@ export abstract class ContainerWindow extends EventEmitter { public constructor(wrap: any) { super(); this.innerWindow = wrap; - } + } /** Gives focus to the window. */ public abstract focus(): Promise; @@ -138,6 +138,8 @@ export abstract class ContainerWindow extends EventEmitter { return Promise.resolve(); } + public abstract getOptions(): Promise; + /** * Override to provide custom container logic for adding an event handler. */ @@ -267,7 +269,7 @@ export class PersistedWindowLayout { } } -type WindowGroupStatus = {window: ContainerWindow, isGrouped: boolean}; +type WindowGroupStatus = { window: ContainerWindow, isGrouped: boolean }; /** Specifies the tracking behavior for window state linking */ export enum WindowStateTracking { @@ -285,7 +287,7 @@ export class GroupWindowManager { protected readonly container: Container; public windowStateTracking: WindowStateTracking = WindowStateTracking.None; - public constructor(container: Container, options? : any) { + public constructor(container: Container, options?: any) { this.container = container; if (options) { @@ -299,7 +301,7 @@ export class GroupWindowManager { public attach(win?: ContainerWindow) { if (win) { - win.addListener((typeof fin !== "undefined") ? "minimized" : "minimize", (e) => { + win.addListener((typeof fin !== "undefined") ? "minimized" : "minimize", (e) => { if ((this.windowStateTracking & WindowStateTracking.Main) && this.container.getMainWindow().id === e.sender.id) { this.container.getAllWindows().then(windows => { windows.forEach(window => window.minimize()); @@ -313,7 +315,7 @@ export class GroupWindowManager { } }); - win.addListener((typeof fin !== "undefined") ? "restored" : "restore", (e) => { + win.addListener((typeof fin !== "undefined") ? "restored" : "restore", (e) => { if ((this.windowStateTracking & WindowStateTracking.Main) && this.container.getMainWindow().id === e.sender.id) { this.container.getAllWindows().then(windows => { windows.forEach(window => window.restore()); @@ -339,8 +341,8 @@ export class GroupWindowManager { // Attach handlers to any windows already open if (this.container) { this.container.getAllWindows().then(windows => { - windows.forEach(window => this.attach(window)); - }); + windows.forEach(window => this.attach(window)); + }); } } } @@ -384,7 +386,7 @@ export class SnapAssistWindowManager extends GroupWindowManager { // Attach listeners for handling when the move/resize of a window is done if (typeof fin !== "undefined") { // OpenFin moved handler - win.addListener( "disabled-frame-bounds-changed", () => this.onMoved(win)); + win.addListener("disabled-frame-bounds-changed", () => this.onMoved(win)); } else { // Electron windows specific moved handler if (win.innerWindow && win.innerWindow.hookWindowMessage) { @@ -397,63 +399,78 @@ export class SnapAssistWindowManager extends GroupWindowManager { super.attach(win); if (win) { - this.onAttached(win); - - win.addListener((typeof fin !== "undefined") ? "disabled-frame-bounds-changing" : "move", (e) => { - const id = e.sender.id; - - if (this.snappingWindow === id) { + win.getOptions().then(options => { + if (options && typeof (options.snap) !== "undefined" && options.snap === false) { return; } - e.sender.getGroup().then(groupedWindows => { - const getBounds: Promise = (typeof fin !== "undefined") - ? Promise.resolve(new Rectangle(e.innerEvent.left, e.innerEvent.top, e.innerEvent.width, e.innerEvent.height)) - : e.sender.getBounds(); + this.onAttached(win); + win.addListener((typeof fin !== "undefined") ? "disabled-frame-bounds-changing" : "move", (e) => this.onMoving(e)); + }); + } + } - getBounds.then(bounds => { - // If we are already in a group, don't snap or group with other windows, ungrouped windows need to group to us - if (groupedWindows.length > 0) { - if (typeof fin !== "undefined") { - this.moveWindow(e.sender, bounds); - } + protected onMoving(e: any) { + const id = e.sender.id; + + if (this.snappingWindow === id) { + return; + } - return; + e.sender.getOptions().then(senderOptions => { + if (senderOptions && typeof (senderOptions.snap) !== "undefined" && senderOptions.snap === false) { + return; + } + + e.sender.getGroup().then(groupedWindows => { + const getBounds: Promise = (typeof fin !== "undefined") + ? Promise.resolve(new Rectangle(e.innerEvent.left, e.innerEvent.top, e.innerEvent.width, e.innerEvent.height)) + : e.sender.getBounds(); + + getBounds.then(bounds => { + // If we are already in a group, don't snap or group with other windows, ungrouped windows need to group to us + if (groupedWindows.length > 0) { + if (typeof fin !== "undefined") { + this.moveWindow(e.sender, bounds); } - const promises: Promise<{window: ContainerWindow, bounds: Rectangle}>[] = []; - this.container.getAllWindows().then(windows => { - windows.filter(window => id !== window.id).forEach(window => { - promises.push(new Promise(resolve => { - window.getBounds().then(targetBounds => resolve({ window: window, bounds: targetBounds})); - })); - }); - - Promise.all(promises).then(responses => { - let isSnapped = false; - let snapHint; - - for (const target of responses) { - snapHint = this.getSnapBounds(snapHint || bounds, target.bounds); - if (snapHint) { - isSnapped = true; - this.showGroupingHint(target.window); - this.moveWindow(e.sender, snapHint); - } else { - this.hideGroupingHint(target.window); - } - } + return; + } - // If the window wasn't moved as part of snapping, we need to manually move for OpenFin since dragging was disabled - if (!isSnapped && typeof fin !== "undefined") { - this.moveWindow(e.sender, bounds); + const promises: Promise<{ window: ContainerWindow, bounds: Rectangle, options: any }>[] = []; + this.container.getAllWindows().then(windows => { + windows.filter(window => id !== window.id).forEach(window => { + promises.push(new Promise(resolve => { + window.getOptions().then(targetOptions => { + window.getBounds().then(targetBounds => resolve({ window: window, bounds: targetBounds, options: targetOptions })); + }); + })); + }); + + Promise.all(promises).then(responses => { + let isSnapped = false; + let snapHint; + + for (const target of responses.filter(response => !(response.options && typeof (response.options.snap) !== "undefined" && response.options.snap === false))) { + snapHint = this.getSnapBounds(snapHint || bounds, target.bounds); + if (snapHint) { + isSnapped = true; + this.showGroupingHint(target.window); + this.moveWindow(e.sender, snapHint); + } else { + this.hideGroupingHint(target.window); } - }); + } + + // If the window wasn't moved as part of snapping, we need to manually move for OpenFin since dragging was disabled + if (!isSnapped && typeof fin !== "undefined") { + this.moveWindow(e.sender, bounds); + } }); }); }); }); - } + }); } protected moveWindow(win: ContainerWindow, bounds: Rectangle) { @@ -491,7 +508,7 @@ export class SnapAssistWindowManager extends GroupWindowManager { protected showGroupingHint(win: ContainerWindow) { if (win.innerWindow && win.innerWindow.updateOptions) { - win.innerWindow.updateOptions({opacity: 0.75}); + win.innerWindow.updateOptions({ opacity: 0.75 }); } this.targetGroup.set(win.id, win); @@ -499,7 +516,7 @@ export class SnapAssistWindowManager extends GroupWindowManager { protected hideGroupingHint(win: ContainerWindow) { if (win.innerWindow && win.innerWindow.updateOptions) { - win.innerWindow.updateOptions({opacity: 1.0}); + win.innerWindow.updateOptions({ opacity: 1.0 }); } this.targetGroup.delete(win.id); diff --git a/tests/unit/Default/default.spec.ts b/tests/unit/Default/default.spec.ts index 0453790c..15e662fb 100644 --- a/tests/unit/Default/default.spec.ts +++ b/tests/unit/Default/default.spec.ts @@ -128,6 +128,14 @@ describe("DefaultContainerWindow", () => { }); }); + it("getOptions", async (done) => { + const win = await new DefaultContainer(new MockWindow()).createWindow("url", { a: "foo" }); + win.getOptions().then(options => { + expect(options).toBeDefined(); + expect(options).toEqual({ a: "foo"}); + }).then(done); + }); + describe("addListener", () => { it("addListener calls underlying window addEventListener with mapped event name", () => { spyOn(win.innerWindow, "addEventListener").and.callThrough() diff --git a/tests/unit/Electron/electron.spec.ts b/tests/unit/Electron/electron.spec.ts index d6b704ed..88c074b3 100644 --- a/tests/unit/Electron/electron.spec.ts +++ b/tests/unit/Electron/electron.spec.ts @@ -224,6 +224,18 @@ describe("ElectronContainerWindow", () => { }); }); + it ("getOptions sends synchronous ipc message", (done) => { + spyOn(container.internalIpc, "sendSync").and.returnValue({ foo: "bar"}); + spyOnProperty(win, "id", "get").and.returnValue(5); + spyOn(container, "wrapWindow").and.returnValue(new MockWindow()); + spyOn(container.browserWindow, "fromId").and.returnValue(innerWin); + + win.getOptions().then(options => { + expect(container.internalIpc.sendSync).toHaveBeenCalledWith("desktopJS.window-getOptions", { source: 5}); + expect(options).toEqual({ foo: "bar" }); + }).then(done); + }); + it("addListener calls underlying Electron window addListener", () => { spyOn(win.innerWindow, "addListener").and.callThrough() win.addListener("move", () => {}); @@ -633,11 +645,12 @@ describe("ElectronWindowManager", () => { const ipc = new MockMainIpc(); spyOn(ipc, "on").and.callThrough(); new ElectronWindowManager({}, ipc, { }); - expect(ipc.on).toHaveBeenCalledTimes(4); + expect(ipc.on).toHaveBeenCalledTimes(5); expect(ipc.on).toHaveBeenCalledWith("desktopJS.window-initialize", jasmine.any(Function)); expect(ipc.on).toHaveBeenCalledWith("desktopJS.window-joinGroup", jasmine.any(Function)); expect(ipc.on).toHaveBeenCalledWith("desktopJS.window-leaveGroup", jasmine.any(Function)); expect(ipc.on).toHaveBeenCalledWith("desktopJS.window-getGroup", jasmine.any(Function)); + expect(ipc.on).toHaveBeenCalledWith("desktopJS.window-getOptions", jasmine.any(Function)); }); it ("initializeWindow on non-main does not attach to close", () => { diff --git a/tests/unit/OpenFin/openfin.spec.ts b/tests/unit/OpenFin/openfin.spec.ts index 96b07899..c135a235 100644 --- a/tests/unit/OpenFin/openfin.spec.ts +++ b/tests/unit/OpenFin/openfin.spec.ts @@ -298,6 +298,23 @@ describe("OpenFinContainerWindow", () => { }); }); + describe("getOptions", () => { + it("getOptions invokes underlying getOptions and returns undefined customData", (done) => { + spyOn(win.innerWindow, "getOptions").and.callFake(callback => callback({ })); + win.getOptions().then(options => { + expect(options).toBeUndefined(); + }).then(done); + }); + + it("getOptions invokes underlying getOptions and parses non-null customData", (done) => { + spyOn(win.innerWindow, "getOptions").and.callFake(callback => callback({ customData: '{ "a": "foo"}' })); + win.getOptions().then(options => { + expect(options).toBeDefined(); + expect(options.a).toEqual("foo"); + }).then(done); + }); + }); + describe("addListener", () => { it("addListener calls underlying OpenFin window addEventListener with mapped event name", () => { spyOn(win.innerWindow, "addEventListener").and.callThrough() @@ -485,25 +502,25 @@ describe("OpenFinContainer", () => { it("defaults", (done) => { container.createWindow("url").then(win => { expect(win).toBeDefined(); - expect(desktop.Window).toHaveBeenCalledWith({ autoShow: true, url: "url", name: jasmine.stringMatching(/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/) }, jasmine.any(Function), jasmine.any(Function)); + expect(desktop.Window).toHaveBeenCalledWith({ autoShow: true, url: "url", name: jasmine.stringMatching(/\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/), customData: undefined }, jasmine.any(Function), jasmine.any(Function)); done(); }); }); it("createWindow defaults", (done) => { spyOn(container, "ensureAbsoluteUrl").and.returnValue("absoluteIcon"); + const options = { + x: "x", + y: "y", + height: "height", + width: "width", + taskbar: "taskbar", + center: "center", + icon: "icon", + name: "name" + } - container.createWindow("url", - { - x: "x", - y: "y", - height: "height", - width: "width", - taskbar: "taskbar", - center: "center", - icon: "icon", - name: "name" - }).then(win => { + container.createWindow("url", options).then(win => { expect(win).toBeDefined(); expect(desktop.Window).toHaveBeenCalledWith( { @@ -517,7 +534,8 @@ describe("OpenFinContainer", () => { autoShow: true, saveWindowState: false, url: "url", - name: "name" + name: "name", + customData: JSON.stringify(options) }, jasmine.any(Function), jasmine.any(Function) diff --git a/tests/unit/window.spec.ts b/tests/unit/window.spec.ts index 16f3441d..f98085b0 100644 --- a/tests/unit/window.spec.ts +++ b/tests/unit/window.spec.ts @@ -186,7 +186,8 @@ describe("SnapAssistWindowManager", () => { it ("attach enumerates all open windows and hooks handlers", () => { const container = jasmine.createSpyObj("container", [ "getAllWindows" ]); - const win = jasmine.createSpyObj("window", [ "addListener"] ); + const win = jasmine.createSpyObj("window", [ "addListener", "getOptions"] ); + win.getOptions.and.returnValue(Promise.resolve(undefined)); container.getAllWindows.and.returnValue(Promise.resolve([win])); const mgr = new SnapAssistWindowManager(container); //expect(win.addListener).toHaveBeenCalled(); @@ -194,8 +195,8 @@ describe("SnapAssistWindowManager", () => { it ("hook handlers on window attach", () => { const mgr = new SnapAssistWindowManager(null); - const win = new MockWindow(null); - spyOn(win, "addListener").and.stub(); + const win = jasmine.createSpyObj("win", ["addListener", "getOptions"]); //new MockWindow(null); + win.getOptions.and.returnValue(Promise.resolve(undefined)); mgr.attach(win); expect(win.addListener).toHaveBeenCalled(); }); @@ -299,10 +300,9 @@ describe("SnapAssistWindowManager", () => { }); it ("move handler", (done) => { - const win = jasmine.createSpyObj("window", ["addListener", "getGroup", "getBounds", "setBounds"]); + const win = jasmine.createSpyObj("window", ["addListener", "getGroup", "getBounds", "setBounds", "getOptions"]); Object.defineProperty(win, "id", { value: "1" }); - let callback; - win.addListener.and.callFake((event, fn) => callback = fn); + win.getOptions.and.returnValue(Promise.resolve(undefined)); win.getGroup.and.returnValue(Promise.resolve([])); win.getBounds.and.returnValue(Promise.resolve(new Rectangle(0, 0, 50, 50))); win.setBounds.and.callFake(() => { @@ -310,16 +310,17 @@ describe("SnapAssistWindowManager", () => { return Promise.resolve(); }); - const win2 = jasmine.createSpyObj("targetWindow", ["addListener", "getBounds"]); + const win2 = jasmine.createSpyObj("targetWindow", ["addListener", "getBounds", "getOptions"]); Object.defineProperty(win2, "id", { value: "2" }); win2.getBounds.and.returnValue(Promise.resolve(new Rectangle(52, 0, 50, 50))); + win2.getOptions.and.returnValue(Promise.resolve({})); const container = jasmine.createSpyObj("container", ["getAllWindows"]); container.getAllWindows.and.returnValue(Promise.resolve([ win, win2 ])); const mgr = new SnapAssistWindowManager(container, { snapThreshold: 20 }); mgr.attach(win); - callback(new WindowEventArgs(win, "move", undefined)); + mgr.onMoving(new WindowEventArgs(win, "move", undefined)); }); describe("getSnapBounds", () => {