diff --git a/tensorboard/components/plugin_lib/BUILD b/tensorboard/components/plugin_lib/BUILD new file mode 100644 index 0000000000..f609151c57 --- /dev/null +++ b/tensorboard/components/plugin_lib/BUILD @@ -0,0 +1,32 @@ +package(default_visibility = ["//tensorboard:internal"]) + +load("//tensorboard/defs:web.bzl", "tf_web_library") + +licenses(["notice"]) # Apache 2.0 + +# TODO(stephanwlee): figure out how this tf_web_library can be used to create +# maybe a NPM package. +tf_web_library( + name = "plugin_lib", + srcs = [ + "runs.ts", + "tf-plugin-lib.html", + ], + path = "/", + deps = [ + "//tensorboard/components/plugin_util:plugin_guest", + ], +) + +tf_web_library( + name = "host_impls", + srcs = [ + "runs-host-impl.ts", + "tf-plugin-host-impls.html", + ], + path = "/tf-plugin-lib", + deps = [ + "//tensorboard/components/plugin_util:plugin_host", + "//tensorboard/components/tf_backend", + ], +) diff --git a/tensorboard/components/plugin_lib/runs-host-impl.ts b/tensorboard/components/plugin_lib/runs-host-impl.ts new file mode 100644 index 0000000000..d5faf6f6ed --- /dev/null +++ b/tensorboard/components/plugin_lib/runs-host-impl.ts @@ -0,0 +1,27 @@ +/* Copyright 2017 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ +/** + * Implements run related plugin APIs. + */ +tb.plugin.lib.host.listen('experimental.GetRuns', () => { + return tf_backend.runsStore.getRuns(); +}); + +tf_backend.runsStore.addListener(() => { + return tb.plugin.lib.host.broadcast( + 'experimental.RunsChange', + tf_backend.runsStore.getRuns() + ); +}); diff --git a/tensorboard/components/plugin_util/test/iframe.ts b/tensorboard/components/plugin_lib/runs.ts similarity index 68% rename from tensorboard/components/plugin_util/test/iframe.ts rename to tensorboard/components/plugin_lib/runs.ts index c1c979687c..1a7ae23955 100644 --- a/tensorboard/components/plugin_util/test/iframe.ts +++ b/tensorboard/components/plugin_lib/runs.ts @@ -12,8 +12,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +namespace tb.plugin.lib.run { + export async function getRuns() { + return tb.plugin.lib.internal.sendMessage('experimental.GetRuns'); + } -import {sendMessage, listen, unlisten, _guestIPC} from '../plugin-guest.js'; - -const win = window as any; -win.test = {sendMessage, listen, unlisten, _guestIPC}; + export function addRunsChangeListener(callback: (runs: string[]) => void) { + return tb.plugin.lib.internal.listen('experimental.RunsChange', callback); + } +} diff --git a/tensorboard/components/plugin_lib/test/BUILD b/tensorboard/components/plugin_lib/test/BUILD new file mode 100644 index 0000000000..85d3d5461b --- /dev/null +++ b/tensorboard/components/plugin_lib/test/BUILD @@ -0,0 +1,22 @@ +package(default_visibility = ["//tensorboard:internal"]) + +load("//tensorboard/defs:web.bzl", "tf_web_library") + +licenses(["notice"]) # Apache 2.0 + +tf_web_library( + name = "test", + testonly = True, + srcs = [ + "test.html", + "test.ts", + "testable-iframe.html", + ], + path = "/tf-plugin-lib/test", + deps = [ + "//tensorboard/components/plugin_lib", + "//tensorboard/components/plugin_lib:host_impls", + "//tensorboard/components/tf_backend", + "//tensorboard/components/tf_imports:web_component_tester", + ], +) diff --git a/tensorboard/components/plugin_lib/test/test.html b/tensorboard/components/plugin_lib/test/test.html new file mode 100644 index 0000000000..4c443aa806 --- /dev/null +++ b/tensorboard/components/plugin_lib/test/test.html @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/tensorboard/components/plugin_lib/test/test.ts b/tensorboard/components/plugin_lib/test/test.ts new file mode 100644 index 0000000000..bded353153 --- /dev/null +++ b/tensorboard/components/plugin_lib/test/test.ts @@ -0,0 +1,72 @@ +/* Copyright 2019 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +async function createIframe(): Promise { + return new Promise((resolve) => { + const iframe = document.createElement('iframe') as HTMLIFrameElement; + document.body.appendChild(iframe); + iframe.src = './testable-iframe.html'; + iframe.onload = () => resolve(iframe); + }); +} + +describe('plugin lib integration', () => { + const {expect} = chai; + + beforeEach(async function() { + this.sandbox = sinon.sandbox.create({useFakeServer: true}); + this.sandbox.server.respondImmediately = true; + this.iframe = await createIframe(); + }); + + afterEach(function() { + document.body.removeChild(this.iframe); + this.sandbox.restore(); + }); + + describe('tb.plugin.lib.run', () => { + describe('#getRuns', () => { + it('returns list of runs', async function() { + this.sandbox + .stub(tf_backend.runsStore, 'getRuns') + .returns(['foo', 'bar', 'baz']); + + const runs = await (this.iframe.contentWindow as any).getRuns(); + + expect(runs).to.deep.equal(['foo', 'bar', 'baz']); + }); + }); + describe('#addRunsChangeListener', () => { + it('lets plugins to subscribe to runs change', async function() { + const runsChanged = this.sandbox.stub(); + const promise = new Promise((resolve) => { + (this.iframe.contentWindow as any).addRunsChangeListener(resolve); + }).then(runsChanged); + this.sandbox.server.respondWith([ + 200, + {'Content-Type': 'application/json'}, + '["foo", "bar"]', + ]); + + await tf_backend.runsStore.refresh(); + await promise; + + expect(runsChanged).to.have.been.calledOnce; + expect(runsChanged).to.have.been.calledWith(['foo', 'bar']); + }); + }); + }); +}); +1; diff --git a/tensorboard/components/plugin_lib/test/testable-iframe.html b/tensorboard/components/plugin_lib/test/testable-iframe.html new file mode 100644 index 0000000000..ab1d38f27c --- /dev/null +++ b/tensorboard/components/plugin_lib/test/testable-iframe.html @@ -0,0 +1,21 @@ + + + diff --git a/tensorboard/components/plugin_lib/tf-plugin-host-impls.html b/tensorboard/components/plugin_lib/tf-plugin-host-impls.html new file mode 100644 index 0000000000..9786493117 --- /dev/null +++ b/tensorboard/components/plugin_lib/tf-plugin-host-impls.html @@ -0,0 +1,20 @@ + + + + + diff --git a/tensorboard/components/plugin_lib/tf-plugin-lib.html b/tensorboard/components/plugin_lib/tf-plugin-lib.html new file mode 100644 index 0000000000..8576315366 --- /dev/null +++ b/tensorboard/components/plugin_lib/tf-plugin-lib.html @@ -0,0 +1,19 @@ + + + + diff --git a/tensorboard/components/plugin_util/BUILD b/tensorboard/components/plugin_util/BUILD index 622dfeef6e..df5e9e0fb1 100644 --- a/tensorboard/components/plugin_util/BUILD +++ b/tensorboard/components/plugin_util/BUILD @@ -5,24 +5,12 @@ load("//tensorboard/defs:web.bzl", "tf_web_library") licenses(["notice"]) # Apache 2.0 tf_web_library( - name = "plugin_host", - srcs = [ - "plugin-host.html", - "plugin-host.ts", - ], - path = "/tf-plugin", - deps = [ - ":plugin_lib", - "//tensorboard/components/tf_backend", - ], -) - -tf_web_library( - name = "plugin_lib", + name = "message", srcs = [ + "message.html", "message.ts", ], - path = "/tf-plugin", + path = "/tf-plugin-lib", ) tf_web_library( @@ -31,9 +19,20 @@ tf_web_library( "plugin-guest.html", "plugin-guest.ts", ], - path = "/tf-plugin", - visibility = ["//visibility:public"], - deps = [ - ":plugin_lib", + path = "/tf-plugin-lib", + visibility = [ + "//tensorboard/components/plugin_lib:__subpackages__", + "//tensorboard/components/plugin_util/test:__subpackages__", + ], + deps = [":message"], +) + +tf_web_library( + name = "plugin_host", + srcs = [ + "plugin-host.html", + "plugin-host.ts", ], + path = "/tf-plugin-lib", + deps = [":message"], ) diff --git a/tensorboard/components/plugin_util/message.html b/tensorboard/components/plugin_util/message.html new file mode 100644 index 0000000000..bd98b715f5 --- /dev/null +++ b/tensorboard/components/plugin_util/message.html @@ -0,0 +1,17 @@ + + diff --git a/tensorboard/components/plugin_util/message.ts b/tensorboard/components/plugin_util/message.ts index d9efb9612c..b9dac401da 100644 --- a/tensorboard/components/plugin_util/message.ts +++ b/tensorboard/components/plugin_util/message.ts @@ -12,112 +12,108 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ - -export type PayloadType = - | null - | undefined - | string - | string[] - | boolean - | boolean[] - | number - | number[] - | object - | object[]; - -export interface Message { - type: string; - id: string; - payload: PayloadType; - error: string | null; -} - -export type MessageType = string; -export type MessageCallback = (payload: any) => any; - -interface PromiseResolver { - resolve: (data: any) => void; - reject: (error: Error) => void; -} - -export abstract class IPC { - private idPrefix: string; - private id = 0; - private readonly responseWaits = new Map(); - private readonly listeners = new Map(); - - constructor() { - window.addEventListener('message', this.onMessage.bind(this)); - - // TODO(tensorboard-team): remove this by using MessageChannel. - const randomArray = new Uint8Array(16); - window.crypto.getRandomValues(randomArray); - this.idPrefix = Array.from(randomArray) - .map((int: number) => int.toString(16)) - .join(''); +namespace tb.plugin.lib.internal { + /** + * This file defines utilities shared by TensorBoard (plugin host) and the + * dynamic plugin library, used by plugin authors. + */ + + export type PayloadType = + | null + | undefined + | string + | string[] + | boolean + | boolean[] + | number + | number[] + | object + | object[]; + + export interface Message { + type: string; + id: string; + payload: PayloadType; + error: string | null; + isReply: boolean; } - listen(type: MessageType, callback: MessageCallback) { - this.listeners.set(type, callback); - } + export type MessageType = string; + export type MessageCallback = (payload: any) => any; - unlisten(type: MessageType) { - this.listeners.delete(type); + interface PromiseResolver { + resolve: (data: any) => void; + reject: (error: Error) => void; } - private async onMessage(event: MessageEvent) { - // There are instances where random browser extensions send messages. - if (typeof event.data !== 'string') return; - - const message = JSON.parse(event.data) as Message; - const callback = this.listeners.get(message.type); - - if (this.responseWaits.has(message.id)) { - const {id, payload, error} = message; - const {resolve, reject} = this.responseWaits.get(id); - this.responseWaits.delete(id); - if (error) { - reject(new Error(error)); - } else { - resolve(payload); - } - return; + export class IPC { + private id = 0; + private readonly responseWaits = new Map(); + private readonly listeners = new Map(); + private readonly port: MessagePort; + + constructor(port) { + this.port = port; + port.addEventListener('message', this.onMessage.bind(this)); + } + + listen(type: MessageType, callback: MessageCallback) { + this.listeners.set(type, callback); + } + + unlisten(type: MessageType) { + this.listeners.delete(type); } - let payload = null; - let error = null; - if (this.listeners.has(message.type)) { + private async onMessage(event: MessageEvent) { + const message = JSON.parse(event.data) as Message; const callback = this.listeners.get(message.type); - try { - const result = await callback(message.payload); - payload = result; - } catch (e) { - error = e; + + if (message.isReply) { + if (!this.responseWaits.has(message.id)) return; + const {id, payload, error} = message; + const {resolve, reject} = this.responseWaits.get(id); + this.responseWaits.delete(id); + if (error) { + reject(new Error(error)); + } else { + resolve(payload); + } + return; + } + + let payload = null; + let error = null; + if (this.listeners.has(message.type)) { + const callback = this.listeners.get(message.type); + try { + const result = await callback(message.payload); + payload = result; + } catch (e) { + error = e; + } } + const replyMessage: Message = { + type: message.type, + id: message.id, + payload, + error, + isReply: true, + }; + this.postMessage(replyMessage); } - const replyMessage: Message = { - type: message.type, - id: message.id, - payload, - error, - }; - this.postMessage(event.source, JSON.stringify(replyMessage)); - } - private postMessage(targetWindow: Window, message: string) { - targetWindow.postMessage(message, '*'); - } + private postMessage(message: Message) { + this.port.postMessage(JSON.stringify(message)); + } - protected sendMessageToWindow( - targetWindow: Window, - type: MessageType, - payload: PayloadType - ): Promise { - const id = `${this.idPrefix}_${this.id++}`; - const message: Message = {type, id, payload, error: null}; - this.postMessage(targetWindow, JSON.stringify(message)); - return new Promise((resolve, reject) => { - this.responseWaits.set(id, {resolve, reject}); - }); + sendMessage(type: MessageType, payload: PayloadType): Promise { + const id = `${this.id++}`; + const message: Message = {type, id, payload, error: null, isReply: false}; + this.postMessage(message); + return new Promise((resolve, reject) => { + this.responseWaits.set(id, {resolve, reject}); + }); + } } -} +} // namespace tb.plugin.lib.internal diff --git a/tensorboard/components/plugin_util/plugin-guest.html b/tensorboard/components/plugin_util/plugin-guest.html index 807d96f6e7..9c0ccdfb86 100644 --- a/tensorboard/components/plugin_util/plugin-guest.html +++ b/tensorboard/components/plugin_util/plugin-guest.html @@ -14,5 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. --> + + - diff --git a/tensorboard/components/plugin_util/plugin-guest.ts b/tensorboard/components/plugin_util/plugin-guest.ts index 9557c92a10..0914af5915 100644 --- a/tensorboard/components/plugin_util/plugin-guest.ts +++ b/tensorboard/components/plugin_util/plugin-guest.ts @@ -12,36 +12,44 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -import {IPC, Message, MessageType, PayloadType} from './message.js'; - -class GuestIPC extends IPC { +namespace tb.plugin.lib.internal { /** - * payload must be JSON serializable. + * This code is part of a public bundle provided to plugin authors, + * and runs within an IFrame to setup communication with TensorBoard's frame. */ - sendMessage(type: MessageType, payload: PayloadType): Promise { - return this.sendMessageToWindow(window.parent, type, payload); + if (!window.parent) { + throw Error( + 'This library must be run from within a loaded TensorBoard dynamic plugin.' + ); } -} - -// Only export for testability. -export const _guestIPC = new GuestIPC(); - -/** - * Sends a message to the parent frame. - * @return Promise that resolves with a payload from parent in response to this message. - * - * @example - * const someList = await sendMessage('v1.some.type.parent.understands'); - * // do fun things with someList. - */ -export const sendMessage = _guestIPC.sendMessage.bind(_guestIPC); - -/** - * Subscribes a callback to a message with particular type. - */ -export const listen = _guestIPC.listen.bind(_guestIPC); - -/** - * Unsubscribes a callback to a message. - */ -export const unlisten = _guestIPC.unlisten.bind(_guestIPC); + + const channel = new MessageChannel(); + const ipc = new IPC(channel.port1); + channel.port1.start(); + + const VERSION = 'experimental'; + window.parent.postMessage(`${VERSION}.bootstrap`, '*', [channel.port2]); + + // Only export for testability. + export const _guestIPC = ipc; + + /** + * Sends a message to the parent frame. + * @return Promise that resolves with a payload from parent in response to this message. + * + * @example + * const someList = await sendMessage('v1.some.type.parent.understands'); + * // do fun things with someList. + */ + export const sendMessage = _guestIPC.sendMessage.bind(_guestIPC); + + /** + * Subscribes a callback to a message with particular type. + */ + export const listen = _guestIPC.listen.bind(_guestIPC); + + /** + * Unsubscribes a callback to a message. + */ + export const unlisten = _guestIPC.unlisten.bind(_guestIPC); +} // namespace tb.plugin.lib.internal diff --git a/tensorboard/components/plugin_util/plugin-host.html b/tensorboard/components/plugin_util/plugin-host.html index d1b6f3ae89..4dae3ba4bb 100644 --- a/tensorboard/components/plugin_util/plugin-host.html +++ b/tensorboard/components/plugin_util/plugin-host.html @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. --> - + - diff --git a/tensorboard/components/plugin_util/plugin-host.ts b/tensorboard/components/plugin_util/plugin-host.ts index 48da663013..11c935388a 100644 --- a/tensorboard/components/plugin_util/plugin-host.ts +++ b/tensorboard/components/plugin_util/plugin-host.ts @@ -12,46 +12,60 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -import {IPC, Message, MessageType, PayloadType} from './message.js'; - -class HostIPC extends IPC { - sendMessage( - iframe: HTMLIFrameElement, - type: MessageType, - payload: PayloadType - ): Promise { - return this.sendMessageToWindow(iframe.contentWindow, type, payload); +namespace tb.plugin.lib.host { + const portIPCs = new Set(); + const VERSION = 'experimental'; + const ipcToFrame = new WeakMap(); + const callbacks = new Map< + lib.internal.MessageType, + lib.internal.MessageCallback + >(); + + // The initial Window-level listener is needed to bootstrap only. + // All further communication is done over MessagePorts. + window.addEventListener('message', (event) => { + if (event.data !== `${VERSION}.bootstrap`) return; + const port = event.ports[0]; + if (!port) return; + const frame = event.source ? event.source.frameElement : null; + if (!frame) return; + + const portIPC = new lib.internal.IPC(port); + portIPCs.add(portIPC); + ipcToFrame.set(portIPC, frame as HTMLIFrameElement); + port.start(); + + [...callbacks].forEach(([type, callback]) => { + portIPC.listen(type, callback); + }); + // TODO: install API. + }); + + function _broadcast( + type: lib.internal.MessageType, + payload: lib.internal.PayloadType + ): Promise { + // Clean up disconnected iframes, since they won't respond. + for (const ipc of portIPCs) { + if (!ipcToFrame.get(ipc).isConnected) { + portIPCs.delete(ipc); + ipcToFrame.delete(ipc); + } + } + + const ipcs = [...portIPCs]; + const promises = ipcs.map((ipc) => ipc.sendMessage(type, payload)); + return Promise.all(promises); + } + + export const broadcast = _broadcast; + export function listen( + type: lib.internal.MessageType, + callback: lib.internal.MessageCallback + ) { + callbacks.set(type, callback); + [...portIPCs].forEach((ipc) => { + ipc.listen(type, callback); + }); } -} - -const hostIPC = new HostIPC(); -const _listen = hostIPC.listen.bind(hostIPC); -const _unlisten = hostIPC.unlisten.bind(hostIPC); -const _sendMessage = hostIPC.sendMessage.bind(hostIPC); - -export const sendMessage = _sendMessage; -export const listen = _listen; -export const unlisten = _unlisten; - -// Export for testability. -export const _hostIPC = hostIPC; - -namespace tf_plugin { - /** - * Sends a message to the frame specified. - * @return Promise that resolves with a payload from frame in response to the message. - * - * @example - * const someList = await sendMessage('v1.some.type.guest.understands'); - * // do fun things with someList. - */ - export const sendMessage = _sendMessage; - /** - * Subscribes to messages from specified frame of a type specified. - */ - export const listen = _listen; - /** - * Unsubscribes to messages from specified frame of a type specified. - */ - export const unlisten = _unlisten; -} // namespace tf_plugin +} // namespace tb.plugin.lib.host diff --git a/tensorboard/components/plugin_util/test/BUILD b/tensorboard/components/plugin_util/test/BUILD index 0de10f7665..0a7c420cd7 100644 --- a/tensorboard/components/plugin_util/test/BUILD +++ b/tensorboard/components/plugin_util/test/BUILD @@ -10,51 +10,50 @@ licenses(["notice"]) # Apache 2.0 tf_web_test( name = "test", + src = "/tf-plugin/test/test_binary.html", web_library = ":test_web_library", - src = "/tf-plugin/test/test_binary.html" ) -# HACK: specifying tensorboard_html_binary on tf_web_test causes certain -# environment to throw exception but wrapping tensorbard_html_binary with -# tf_web_library seems to be okay. -tf_web_library( - name = "test_web_library", - srcs = [ - ":test_binary.html", - ], - path = "/tf-plugin/test", - deps = [ - ":test_lib", - "//tensorboard/components/tf_imports:web_component_tester", - ], -) +# # HACK: specifying tensorboard_html_binary on tf_web_test causes certain +# # environment to throw exception but wrapping tensorbard_html_binary with +# # tf_web_library seems to be okay. +# tf_web_library( +# name = "test_web_library", +# srcs = [ +# ":test_binary.html", +# ], +# path = "/tf-plugin/test", +# deps = [ +# ":test_lib", +# "//tensorboard/components/tf_imports:web_component_tester", +# ], +# ) -tensorboard_html_binary( - name = "test_binary", - # Disable advanced optimization to prevent check for WebComponentTester - # gobals. - compilation_level = "SIMPLE", - # Requires for compiling `import`s away. - compile = True, - input_path = "/tf-plugin/test/tests.html", - output_path = "/tf-plugin/test/test_binary.html", - deps = [ - ":test_lib", - ], -) +# tensorboard_html_binary( +# name = "test_binary", +# # Disable advanced optimization to prevent check for WebComponentTester +# # gobals. +# compilation_level = "SIMPLE", +# # Requires for compiling `import`s away. +# compile = True, +# input_path = "/tf-plugin/test/tests.html", +# output_path = "/tf-plugin/test/test_binary.html", +# deps = [ +# ":test_lib", +# ], +# ) tf_web_library( - name = "test_lib", + name = "test_web_library", srcs = [ "iframe.html", - "iframe.ts", "plugin-test.ts", "tests.html", ], - path = "/tf-plugin/test", + path = "/tf-plugin-lib/test", deps = [ - "//tensorboard/components/plugin_util:plugin_host", "//tensorboard/components/plugin_util:plugin_guest", + "//tensorboard/components/plugin_util:plugin_host", "//tensorboard/components/tf_imports:web_component_tester", ], ) diff --git a/tensorboard/components/plugin_util/test/iframe.html b/tensorboard/components/plugin_util/test/iframe.html index 9aff72251d..a1f16dd479 100644 --- a/tensorboard/components/plugin_util/test/iframe.html +++ b/tensorboard/components/plugin_util/test/iframe.html @@ -15,6 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - - + + diff --git a/tensorboard/components/plugin_util/test/plugin-test.ts b/tensorboard/components/plugin_util/test/plugin-test.ts index 4c1500ee2f..488e586205 100644 --- a/tensorboard/components/plugin_util/test/plugin-test.ts +++ b/tensorboard/components/plugin_util/test/plugin-test.ts @@ -12,9 +12,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -import * as pluginHost from '../plugin-host.js'; -namespace tf_plugin.test { +namespace tb.plugin.lib.host.test { const {expect} = chai; const template = document.getElementById( 'iframe-template' @@ -57,24 +56,15 @@ namespace tf_plugin.test { this.destListen = this.guestWindow.test.listen; this.destUnlisten = this.guestWindow.test.unlisten; this.srcSendMessage = (type, payload) => { - return pluginHost.sendMessage(this.guestFrame, type, payload); + return new Promise(async (resolve) => { + const results = await broadcast(type, payload); + resolve(results[0]); + }); }; this.destPostMessageSpy = () => this.sandbox.spy(this.guestWindow.test._guestIPC, 'postMessage'); }, }, - { - spec: 'guest (src) to host (dest)', - beforeEachFunc: function() { - this.destListen = pluginHost.listen; - this.destUnlisten = pluginHost.unlisten; - this.srcSendMessage = (type, payload) => { - return this.guestWindow.test.sendMessage(type, payload); - }; - this.destPostMessageSpy = () => - this.sandbox.spy(pluginHost._hostIPC, 'postMessage'); - }, - }, ].forEach(({spec, beforeEachFunc}) => { describe(spec, () => { beforeEach(beforeEachFunc); diff --git a/tensorboard/components/plugin_util/test/tests.html b/tensorboard/components/plugin_util/test/tests.html index 76946a9b0e..20a4d13677 100644 --- a/tensorboard/components/plugin_util/test/tests.html +++ b/tensorboard/components/plugin_util/test/tests.html @@ -20,4 +20,4 @@ - +