/** * Copyright (c) Microsoft Corporation. * * 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. */ // @ts-check /** * Popup script for Playwright MCP Bridge extension */ class PopupController { constructor() { this.currentTab = null; this.bridgeUrlInput = /** @type {HTMLInputElement} */ (document.getElementById('bridge-url')); this.connectBtn = /** @type {HTMLButtonElement} */ (document.getElementById('connect-btn')); this.statusContainer = /** @type {HTMLElement} */ (document.getElementById('status-container')); this.actionContainer = /** @type {HTMLElement} */ (document.getElementById('action-container')); this.init(); } async init() { // Get current tab const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); this.currentTab = tab; // Load saved bridge URL const result = await chrome.storage.sync.get(['bridgeUrl']); const savedUrl = result.bridgeUrl || 'ws://localhost:9223/extension'; this.bridgeUrlInput.value = savedUrl; this.bridgeUrlInput.disabled = false; // Set up event listeners this.bridgeUrlInput.addEventListener('input', this.onUrlChange.bind(this)); this.connectBtn.addEventListener('click', this.onConnectClick.bind(this)); // Update UI based on current state await this.updateUI(); } async updateUI() { if (!this.currentTab?.id) return; // Get connection status from background script const response = await chrome.runtime.sendMessage({ type: 'getStatus', tabId: this.currentTab.id }); const { isConnected, activeTabId, activeTabInfo, error } = response; if (!this.statusContainer || !this.actionContainer) return; this.statusContainer.innerHTML = ''; this.actionContainer.innerHTML = ''; if (error) { this.showStatus('error', `Error: ${error}`); this.showConnectButton(); } else if (isConnected && activeTabId === this.currentTab.id) { // Current tab is connected this.showStatus('connected', 'This tab is currently shared with MCP server'); this.showDisconnectButton(); } else if (isConnected && activeTabId !== this.currentTab.id) { // Another tab is connected this.showStatus('warning', 'Another tab is already sharing the CDP session'); this.showActiveTabInfo(activeTabInfo); this.showFocusButton(activeTabId); } else { // No connection this.showConnectButton(); } } showStatus(type, message) { const statusDiv = document.createElement('div'); statusDiv.className = `status ${type}`; statusDiv.textContent = message; this.statusContainer.appendChild(statusDiv); } showConnectButton() { if (!this.actionContainer) return; this.actionContainer.innerHTML = ` `; const connectBtn = /** @type {HTMLButtonElement} */ (document.getElementById('connect-btn')); if (connectBtn) { connectBtn.addEventListener('click', this.onConnectClick.bind(this)); // Disable if URL is invalid const isValidUrl = this.bridgeUrlInput ? this.isValidWebSocketUrl(this.bridgeUrlInput.value) : false; connectBtn.disabled = !isValidUrl; } } showDisconnectButton() { if (!this.actionContainer) return; this.actionContainer.innerHTML = ` `; const disconnectBtn = /** @type {HTMLButtonElement} */ (document.getElementById('disconnect-btn')); if (disconnectBtn) { disconnectBtn.addEventListener('click', this.onDisconnectClick.bind(this)); } } showActiveTabInfo(tabInfo) { if (!tabInfo) return; const tabDiv = document.createElement('div'); tabDiv.className = 'tab-info'; tabDiv.innerHTML = `