blob: 8feabb91daf19f205eb9a7d3bc3fd4a00ce01f53 [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 {TabStripService} from '/tab_strip_api/tab_strip_api.mojom-webui.js';
import type {TabStripServiceRemote} from '/tab_strip_api/tab_strip_api.mojom-webui.js';
import type {Container, Tab, TabCreatedContainer} from '/tab_strip_api/tab_strip_api_data_model.mojom-webui.js';
import type {OnDataChangedEvent, OnTabsClosedEvent, OnTabsCreatedEvent} from '/tab_strip_api/tab_strip_api_events.mojom-webui.js';
import type {NodeId} from '/tab_strip_api/tab_strip_api_types.mojom-webui.js';
import {TabStripObservation} from '/tab_strip_api/tab_strip_observation.js';
import type {ContentRegion} from './content_region.js';
import type {TabStrip} from './tab_strip.js';
export interface TabStripControllerDelegate {
// Notifies the layout manager to recompute its layout, because the tab strip
// might have changed.
refreshLayout: () => void;
// Notifies that the active tab has updated.
activeTabUpdated: (tabData: Tab) => void;
}
export class TabStripController {
private readonly tabStripControllerDelegate_: TabStripControllerDelegate;
private readonly tabStripService_: TabStripServiceRemote;
private readonly tabStripObservation_: TabStripObservation;
private tabStrip_: TabStrip;
private contentRegion_: ContentRegion;
constructor(
tabStripControllerDelegate: TabStripControllerDelegate,
tabStrip: TabStrip, contentRegion: ContentRegion) {
this.tabStripControllerDelegate_ = tabStripControllerDelegate;
this.tabStripService_ = TabStripService.getRemote();
this.tabStripObservation_ = new TabStripObservation();
this.tabStrip_ = tabStrip;
this.contentRegion_ = contentRegion;
this.registerTabChangeCallbacks_();
this.loadTabStripModel_();
}
addNewTab() {
// Asynchronously the browser will call onTabCreated_() and
// onTabActivated_().
this.tabStripService_.createTabAt(null, null);
}
removeTab(tabId: NodeId) {
// Asynchronously the browser will call onTabRemoved_().
this.tabStripService_.closeTabs([tabId]);
}
/* TODO(webium): Do we need this? if so, rewrite to use getTabs().
public async getGroupVisualData_(groupId: NodeId):
Promise<TabGroupVisualData|undefined> { const allVisualData = await
this.tabStripService_.getGroupVisualData(); return
allVisualData.data[groupId];
}
*/
onTabClick(e: CustomEvent) {
this.tabStripService_.activateTab(e.detail.tabId);
}
onTabDragOutOfBounds(_: CustomEvent) {
/* TODO(webium): Implement this.
const tabId = e.detail.tabId;
const dragOffsetX = e.detail.drag_offset_x;
const dragOffsetY = e.detail.drag_offset_y;
this.tabStripService_.detachTab(tabId, dragOffsetX, dragOffsetY);
*/
}
// Private methods:
private registerTabChangeCallbacks_() {
// TODO(webium): implement these callbacks.
// this.tabStripObservation_.showContextMenu.addListener(
// () => this.onShowContextMenu_());
this.tabStripObservation_.onTabsCreated.addListener(
this.onTabsCreated_.bind(this));
// this.tabStripObservation_.tabMoved.addListener(
// this.onTabMoved_.bind(this));
this.tabStripObservation_.onTabsClosed.addListener(
this.onTabsClosed_.bind(this));
this.tabStripObservation_.onDataChanged.addListener(
this.onDataChanged_.bind(this));
// this.tabStripObservation_.tabReplaced.addListener(
// this.onTabReplaced_.bind(this));
// this.tabStripObservation_.tabCloseCancelled.addListener(
// this.onTabCloseCancelled_.bind(this));
// this.tabStripObservation_.tabGroupStateChanged.addListener(
// this.onTabGroupStateChanged_.bind(this));
// this.tabStripObservation_.tabGroupClosed.addListener(
// this.onTabGroupClosed_.bind(this));
// this.tabStripObservation_.tabGroupMoved.addListener(
// this.onTabGroupMoved_.bind(this));
}
private async loadTabStripModel_() {
const tabSnapshot = await this.tabStripService_.getTabs();
// TODO(crbug.com/439844342): add type signature.
this.tabStripObservation_.bind((tabSnapshot.stream as any).handle);
const tabStrip = tabSnapshot.tabStrip;
const processContainer = (container: Container) => {
if (!container || !container.children) {
return;
}
container.children.forEach((containerElement: Container, _: number) => {
if (containerElement.data.tab) {
this.addTab_(containerElement.data.tab);
} else {
processContainer(containerElement);
}
});
};
processContainer(tabStrip);
}
private addTab_(tab: Tab) {
this.tabStrip_.addTab(tab);
if (tab.isActive) {
this.tabStrip_.activateTab(tab.id);
}
this.contentRegion_.createWebView(tab.id, tab.isActive);
this.tabStripControllerDelegate_.refreshLayout();
}
// tab_strip::mojom::Page implementation:
private onTabsCreated_(tabsCreatedEvent: OnTabsCreatedEvent) {
const tabsCreated: TabCreatedContainer[] = tabsCreatedEvent.tabs;
tabsCreated.forEach((container) => {
this.addTab_(container.tab);
});
}
/* TODO(webium): get this working.
private onTabGroupStateChanged_(tabId: NodeId, _: number, groupId?: NodeId) {
this.tabStrip_.setTabGroupForTab_(tabId, groupId);
}
*/
private onDataChanged_(onDataChangedEvent: OnDataChangedEvent) {
const data = onDataChangedEvent.data;
if (data.tab) {
const tab = data.tab;
this.tabStrip_.updateTab(tab);
if (tab.isActive) {
this.tabStrip_.activateTab(tab.id);
this.contentRegion_.activateTab(tab.id);
this.tabStripControllerDelegate_.activeTabUpdated(tab);
}
this.tabStripControllerDelegate_.refreshLayout();
} else if (data.tabGroup) {
const tabGroup = data.tabGroup;
if (tabGroup) {
this.tabStrip_.setTabGroupVisualData(tabGroup.id, tabGroup.data);
}
}
}
private onTabsClosed_(tabsClosedEvent: OnTabsClosedEvent) {
const tabsClosed = tabsClosedEvent.tabs;
tabsClosed.forEach((tabId: NodeId) => {
this.tabStrip_.removeTab(tabId);
this.contentRegion_.removeTab(tabId);
});
}
}