| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import {assert} from './assert.js'; |
| import {PromiseResolver} from './promise_resolver.js'; |
| |
| export interface WebUiListener { |
| eventName: string; |
| uid: number; |
| } |
| |
| /** Counter for use with createUid */ |
| let uidCounter: number = 1; |
| |
| /** @return A new unique ID. */ |
| function createUid(): number { |
| return uidCounter++; |
| } |
| |
| /** |
| * The mapping used by the sendWithPromise mechanism to tie the Promise |
| * returned to callers with the corresponding WebUI response. The mapping is |
| * from ID to the PromiseResolver helper; the ID is generated by |
| * sendWithPromise and is unique across all invocations of said method. |
| */ |
| const chromeSendResolverMap: {[id: string]: PromiseResolver<any>} = {}; |
| |
| /** |
| * The named method the WebUI handler calls directly in response to a |
| * chrome.send call that expects a response. The handler requires no knowledge |
| * of the specific name of this method, as the name is passed to the handler |
| * as the first argument in the arguments list of chrome.send. The handler |
| * must pass the ID, also sent via the chrome.send arguments list, as the |
| * first argument of the JS invocation; additionally, the handler may |
| * supply any number of other arguments that will be included in the response. |
| * @param id The unique ID identifying the Promise this response is |
| * tied to. |
| * @param isSuccess Whether the request was successful. |
| * @param response The response as sent from C++. |
| */ |
| export function webUIResponse(id: string, isSuccess: boolean, response: any) { |
| const resolver = chromeSendResolverMap[id]; |
| assert(resolver); |
| delete chromeSendResolverMap[id]; |
| |
| if (isSuccess) { |
| resolver.resolve(response); |
| } else { |
| resolver.reject(response); |
| } |
| } |
| |
| /** |
| * A variation of chrome.send, suitable for messages that expect a single |
| * response from C++. |
| * @param methodName The name of the WebUI handler API. |
| * @param args Variable number of arguments to be forwarded to the |
| * C++ call. |
| */ |
| export function sendWithPromise( |
| methodName: string, ...args: any[]): Promise<any> { |
| const promiseResolver = new PromiseResolver(); |
| const id = methodName + '_' + createUid(); |
| chromeSendResolverMap[id] = promiseResolver; |
| chrome.send(methodName, [id].concat(args)); |
| return promiseResolver.promise; |
| } |
| |
| /** |
| * A map of maps associating event names with listeners. The 2nd level map |
| * associates a listener ID with the callback function, such that individual |
| * listeners can be removed from an event without affecting other listeners of |
| * the same event. |
| */ |
| const webUiListenerMap: |
| {[event: string]: {[listenerId: number]: Function}} = {}; |
| |
| /** |
| * The named method the WebUI handler calls directly when an event occurs. |
| * The WebUI handler must supply the name of the event as the first argument |
| * of the JS invocation; additionally, the handler may supply any number of |
| * other arguments that will be forwarded to the listener callbacks. |
| * @param event The name of the event that has occurred. |
| * @param args Additional arguments passed from C++. |
| */ |
| export function webUIListenerCallback(event: string, ...args: any[]) { |
| const eventListenersMap = webUiListenerMap[event]; |
| if (!eventListenersMap) { |
| // C++ event sent for an event that has no listeners. |
| // TODO(dpapad): Should a warning be displayed here? |
| return; |
| } |
| |
| for (const listenerId in eventListenersMap) { |
| eventListenersMap[listenerId]!.apply(null, args); |
| } |
| } |
| |
| /** |
| * Registers a listener for an event fired from WebUI handlers. Any number of |
| * listeners may register for a single event. |
| * @param eventName The event to listen to. |
| * @param callback The callback run when the event is fired. |
| * @return An object to be used for removing a listener via |
| * removeWebUiListener. Should be treated as read-only. |
| */ |
| export function addWebUiListener( |
| eventName: string, callback: Function): WebUiListener { |
| webUiListenerMap[eventName] = webUiListenerMap[eventName] || {}; |
| const uid = createUid(); |
| webUiListenerMap[eventName][uid] = callback; |
| return {eventName: eventName, uid: uid}; |
| } |
| |
| /** |
| * Removes a listener. Does nothing if the specified listener is not found. |
| * @param listener The listener to be removed (as returned by addWebUiListener). |
| * @return Whether the given listener was found and actually removed. |
| */ |
| export function removeWebUiListener(listener: WebUiListener): boolean { |
| const listenerExists = webUiListenerMap[listener.eventName] && |
| webUiListenerMap[listener.eventName]![listener.uid]; |
| if (listenerExists) { |
| const map = webUiListenerMap[listener.eventName]!; |
| delete map[listener.uid]; |
| return true; |
| } |
| return false; |
| } |
| |
| // Globally expose functions that must be called from C++. |
| type WindowAndCr = Window&{cr?: object}; |
| assert(!(window as WindowAndCr).cr); |
| Object.assign(window, {cr: {webUIResponse, webUIListenerCallback}}); |