Skip to content

Commit 1876084

Browse files
committed
revision
1 parent df8cbb3 commit 1876084

File tree

10 files changed

+475
-48
lines changed

10 files changed

+475
-48
lines changed

extension/background.js

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,57 +14,111 @@ function debugLog(...args) {
1414
class TabShareExtension {
1515
constructor() {
1616
this.activeConnections = new Map(); // tabId -> connection info
17-
this.bridgeURL = 'ws://localhost:9223/extension'; // Default bridge URL
1817

19-
// Set up page action
20-
chrome.action.onClicked.addListener(this.onPageActionClicked.bind(this));
18+
// Remove page action click handler since we now use popup
2119
chrome.tabs.onRemoved.addListener(this.onTabRemoved.bind(this));
20+
21+
// Handle messages from popup
22+
chrome.runtime.onMessage.addListener(this.onMessage.bind(this));
23+
}
24+
25+
/**
26+
* Handle messages from popup
27+
* @param {any} message
28+
* @param {chrome.runtime.MessageSender} sender
29+
* @param {Function} sendResponse
30+
*/
31+
onMessage(message, sender, sendResponse) {
32+
switch (message.type) {
33+
case 'getStatus':
34+
this.getStatus(message.tabId, sendResponse);
35+
return true; // Will respond asynchronously
36+
37+
case 'connect':
38+
this.connectTab(message.tabId, message.bridgeUrl).then(
39+
() => sendResponse({ success: true }),
40+
(error) => sendResponse({ success: false, error: error.message })
41+
);
42+
return true; // Will respond asynchronously
43+
44+
case 'disconnect':
45+
this.disconnectTab(message.tabId).then(
46+
() => sendResponse({ success: true }),
47+
(error) => sendResponse({ success: false, error: error.message })
48+
);
49+
return true; // Will respond asynchronously
50+
}
51+
return false;
2252
}
2353

2454
/**
25-
* Handle page action click - "share" the tab with MCP server
26-
* @param {chrome.tabs.Tab} tab
55+
* Get connection status for popup
56+
* @param {number} requestedTabId
57+
* @param {Function} sendResponse
2758
*/
28-
async onPageActionClicked(tab) {
29-
if (!tab.id) return;
30-
31-
if (this.activeConnections.has(tab.id)) {
32-
// Already connected - disconnect
33-
await this.disconnectTab(tab.id);
34-
chrome.action.setBadgeText({ tabId: tab.id, text: '' });
35-
chrome.action.setTitle({ tabId: tab.id, title: 'Share tab with Playwright MCP' });
59+
getStatus(requestedTabId, sendResponse) {
60+
const isConnected = this.activeConnections.size > 0;
61+
let activeTabId = null;
62+
let activeTabInfo = null;
63+
64+
if (isConnected) {
65+
const [tabId, connection] = this.activeConnections.entries().next().value;
66+
activeTabId = tabId;
67+
68+
// Get tab info
69+
chrome.tabs.get(tabId, (tab) => {
70+
if (chrome.runtime.lastError) {
71+
sendResponse({
72+
isConnected: false,
73+
error: 'Active tab not found'
74+
});
75+
} else {
76+
sendResponse({
77+
isConnected: true,
78+
activeTabId,
79+
activeTabInfo: {
80+
title: tab.title,
81+
url: tab.url
82+
}
83+
});
84+
}
85+
});
3686
} else {
37-
// Connect tab
38-
await this.connectTab(tab.id);
87+
sendResponse({
88+
isConnected: false,
89+
activeTabId: null,
90+
activeTabInfo: null
91+
});
3992
}
4093
}
4194

4295
/**
4396
* Connect a tab to the bridge server
4497
* @param {number} tabId
98+
* @param {string} bridgeUrl
4599
*/
46-
async connectTab(tabId) {
100+
async connectTab(tabId, bridgeUrl) {
47101
try {
48-
debugLog(`Connecting tab ${tabId} to bridge`);
102+
debugLog(`Connecting tab ${tabId} to bridge at ${bridgeUrl}`);
49103

50104
// Attach chrome debugger
51105
const debuggee = { tabId };
52106
await chrome.debugger.attach(debuggee, '1.3');
53107

54108
if (chrome.runtime.lastError)
55109
throw new Error(chrome.runtime.lastError.message);
56-
const targetInfo = await chrome.debugger.sendCommand(debuggee, 'Target.getTargetInfo');
110+
const targetInfo = /** @type {any} */ (await chrome.debugger.sendCommand(debuggee, 'Target.getTargetInfo'));
57111
debugLog('Target info:', targetInfo);
58112

59113
// Connect to bridge server
60-
const socket = new WebSocket(this.bridgeURL);
114+
const socket = new WebSocket(bridgeUrl);
61115

62116
const connection = {
63117
debuggee,
64118
socket,
65119
tabId,
66-
targetId: targetInfo.targetInfo.targetId,
67-
browserContextId: targetInfo.targetInfo.browserContextId
120+
targetId: targetInfo?.targetInfo?.targetId,
121+
browserContextId: targetInfo?.targetInfo?.browserContextId
68122
};
69123

70124
await new Promise((resolve, reject) => {
@@ -76,7 +130,7 @@ class TabShareExtension {
76130
tabId,
77131
targetId: connection.targetId,
78132
browserContextId: connection.browserContextId,
79-
targetInfo: targetInfo.targetInfo
133+
targetInfo: targetInfo?.targetInfo
80134
}));
81135
resolve(undefined);
82136
};
@@ -105,6 +159,8 @@ class TabShareExtension {
105159
chrome.action.setBadgeText({ tabId, text: '!' });
106160
chrome.action.setBadgeBackgroundColor({ tabId, color: '#F44336' });
107161
chrome.action.setTitle({ tabId, title: `Connection failed: ${error.message}` });
162+
163+
throw error; // Re-throw for popup to handle
108164
}
109165
}
110166

extension/manifest.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"permissions": [
88
"debugger",
99
"activeTab",
10-
"tabs"
10+
"tabs",
11+
"storage"
1112
],
1213

1314
"host_permissions": [
@@ -21,6 +22,7 @@
2122

2223
"action": {
2324
"default_title": "Share tab with Playwright MCP",
25+
"default_popup": "popup.html",
2426
"default_icon": {
2527
"16": "icons/icon-16.png",
2628
"32": "icons/icon-32.png",

extension/popup.html

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<style>
6+
body {
7+
width: 320px;
8+
padding: 16px;
9+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10+
font-size: 14px;
11+
margin: 0;
12+
}
13+
14+
.header {
15+
margin-bottom: 16px;
16+
text-align: center;
17+
}
18+
19+
.header h3 {
20+
margin: 0 0 8px 0;
21+
color: #333;
22+
}
23+
24+
.section {
25+
margin-bottom: 16px;
26+
}
27+
28+
label {
29+
display: block;
30+
margin-bottom: 4px;
31+
font-weight: 500;
32+
color: #555;
33+
}
34+
35+
input[type="url"] {
36+
width: 100%;
37+
padding: 8px;
38+
border: 1px solid #ddd;
39+
border-radius: 4px;
40+
font-size: 14px;
41+
box-sizing: border-box;
42+
}
43+
44+
input[type="url"]:focus {
45+
outline: none;
46+
border-color: #4CAF50;
47+
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);
48+
}
49+
50+
.button {
51+
background: #4CAF50;
52+
color: white;
53+
border: none;
54+
padding: 10px 16px;
55+
border-radius: 4px;
56+
cursor: pointer;
57+
font-size: 14px;
58+
width: 100%;
59+
margin-top: 8px;
60+
}
61+
62+
.button:hover {
63+
background: #45a049;
64+
}
65+
66+
.button:disabled {
67+
background: #cccccc;
68+
cursor: not-allowed;
69+
}
70+
71+
.button.disconnect {
72+
background: #f44336;
73+
}
74+
75+
.button.disconnect:hover {
76+
background: #da190b;
77+
}
78+
79+
.status {
80+
padding: 12px;
81+
border-radius: 4px;
82+
margin-bottom: 16px;
83+
text-align: center;
84+
}
85+
86+
.status.connected {
87+
background: #e8f5e8;
88+
color: #2e7d32;
89+
border: 1px solid #4caf50;
90+
}
91+
92+
.status.error {
93+
background: #ffebee;
94+
color: #c62828;
95+
border: 1px solid #f44336;
96+
}
97+
98+
.status.warning {
99+
background: #fff3e0;
100+
color: #ef6c00;
101+
border: 1px solid #ff9800;
102+
}
103+
104+
.tab-info {
105+
background: #f5f5f5;
106+
padding: 12px;
107+
border-radius: 4px;
108+
margin-bottom: 16px;
109+
}
110+
111+
.tab-title {
112+
font-weight: 500;
113+
margin-bottom: 4px;
114+
color: #333;
115+
}
116+
117+
.tab-url {
118+
font-size: 12px;
119+
color: #666;
120+
word-break: break-all;
121+
}
122+
123+
.focus-button {
124+
background: #2196F3;
125+
margin-top: 8px;
126+
}
127+
128+
.focus-button:hover {
129+
background: #1976D2;
130+
}
131+
132+
.small-text {
133+
font-size: 12px;
134+
color: #666;
135+
margin-top: 8px;
136+
}
137+
</style>
138+
</head>
139+
<body>
140+
<div class="header">
141+
<h3>Playwright MCP Bridge</h3>
142+
</div>
143+
144+
<div id="status-container"></div>
145+
146+
<div class="section">
147+
<label for="bridge-url">Bridge Server URL:</label>
148+
<input type="url" id="bridge-url" placeholder="ws://localhost:9223/extension" />
149+
<div class="small-text">Enter the WebSocket URL of your MCP bridge server</div>
150+
</div>
151+
152+
<div id="action-container">
153+
<button id="connect-btn" class="button">Share This Tab</button>
154+
</div>
155+
156+
<script src="popup.js"></script>
157+
</body>
158+
</html>

0 commit comments

Comments
 (0)