blob: 66e9beb79ba2e23e0a92b0268b82898072c6a705 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {assertNotReachedCase} from '//resources/js/assert.js';
import type {TabsEvent, TabsObserverInterface, TabsObserverPendingReceiverEndpoint} from './tab_strip_api.mojom-webui.js';
import {TabsEventFieldTags, TabsObserverReceiver, whichTabsEvent} from './tab_strip_api.mojom-webui.js';
import type {OnCollectionCreatedEvent, OnDataChangedEvent, OnNodeMovedEvent, OnTabsClosedEvent, OnTabsCreatedEvent} from './tab_strip_api_events.mojom-webui.js';
type CallbackType<EventType> = (event: EventType) => void;
class Channel<EventType> {
private listeners_: Array<CallbackType<EventType>> = [];
// TODO(crbug.com/439639253): add removeListener
addListener(listener: CallbackType<EventType>) {
this.listeners_.push(listener);
}
notify(event: EventType): void {
for (const listener of this.listeners_) {
listener(event);
}
}
}
/**
* @fileoverview
* This file defines the TabStripObservation, a TypeScript client for the
* TabsObserver mojom interface.
*
* ...
*
* @example
* // Get the TabStripService remote and create a new router.
* const service = TabStripService.getRemote();
* const observation = new TabStripObservation();
*
* // Fetch the initial tab state and the observer stream handle.
* const snapshot = await service.getTabs();
* observationRouter.bind((snapshot.stream as any).handle);
*
* // Add listeners for the events you want to handle.
* observationRouter.onTabsCreated.addListener((event) => {
* // Logic to add new tabs to the UI.
* for (const tabContainer of event.tabs) {
* myUi.addTab(tabContainer.tab);
* }
* });
*
*/
export class TabStripObservation implements TabsObserverInterface {
readonly onDataChanged = new Channel<OnDataChangedEvent>();
readonly onCollectionCreated = new Channel<OnCollectionCreatedEvent>();
readonly onNodeMoved = new Channel<OnNodeMovedEvent>();
readonly onTabsClosed = new Channel<OnTabsClosedEvent>();
readonly onTabsCreated = new Channel<OnTabsCreatedEvent>();
private readonly receiver_: TabsObserverReceiver;
constructor() {
this.receiver_ = new TabsObserverReceiver(this);
}
bind(handle: TabsObserverPendingReceiverEndpoint) {
// TODO(crbug.com/439639253): throw error if already bound. This will
// already throw, but the msg is probably not very helpful.
this.receiver_.$.bindHandle(handle);
}
onTabEvents(events: TabsEvent[]) {
for (const event of events) {
this.notify_(event);
}
}
private notify_(event: TabsEvent) {
const which = whichTabsEvent(event);
switch (which) {
case TabsEventFieldTags.DATA_CHANGED_EVENT:
this.onDataChanged.notify(event.dataChangedEvent!);
break;
case TabsEventFieldTags.COLLECTION_CREATED_EVENT:
this.onCollectionCreated.notify(event.collectionCreatedEvent!);
break;
case TabsEventFieldTags.NODE_MOVED_EVENT:
this.onNodeMoved.notify(event.nodeMovedEvent!);
break;
case TabsEventFieldTags.TABS_CLOSED_EVENT:
this.onTabsClosed.notify(event.tabsClosedEvent!);
break;
case TabsEventFieldTags.TABS_CREATED_EVENT:
this.onTabsCreated.notify(event.tabsCreatedEvent!);
break;
default:
assertNotReachedCase(which);
}
}
}