@mehmoodqureshi/chrome-mcp 0.9.4 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/shared/page-fns.js
CHANGED
|
@@ -96,6 +96,40 @@ function pageOp(a) {
|
|
|
96
96
|
node.value = next;
|
|
97
97
|
node.dispatchEvent(new Event('input', { bubbles: true }));
|
|
98
98
|
};
|
|
99
|
+
/**
|
|
100
|
+
* Clear a contenteditable host. `setValue` cannot: a div has no `value`
|
|
101
|
+
* setter, so it assigns a meaningless JS property and the visible text
|
|
102
|
+
* survives - which is why `clear` silently did nothing on every rich editor
|
|
103
|
+
* (X, Reddit, LinkedIn) and new text landed beside the old.
|
|
104
|
+
*
|
|
105
|
+
* execCommand is deprecated but remains the only call that emits the
|
|
106
|
+
* beforeinput/input pair React, Quill and Lexical listen for. Assigning
|
|
107
|
+
* textContent leaves their internal model stale and the old text returns on
|
|
108
|
+
* the next keystroke.
|
|
109
|
+
*/
|
|
110
|
+
const clearEditable = (host) => {
|
|
111
|
+
host.focus();
|
|
112
|
+
const sel = window.getSelection();
|
|
113
|
+
if (!sel)
|
|
114
|
+
return;
|
|
115
|
+
const range = document.createRange();
|
|
116
|
+
range.selectNodeContents(host);
|
|
117
|
+
sel.removeAllRanges();
|
|
118
|
+
sel.addRange(range);
|
|
119
|
+
document.execCommand('delete');
|
|
120
|
+
};
|
|
121
|
+
/** Put the caret at the end of a contenteditable so text appends, not prepends. */
|
|
122
|
+
const caretToEnd = (host) => {
|
|
123
|
+
host.focus();
|
|
124
|
+
const sel = window.getSelection();
|
|
125
|
+
if (!sel)
|
|
126
|
+
return;
|
|
127
|
+
const range = document.createRange();
|
|
128
|
+
range.selectNodeContents(host);
|
|
129
|
+
range.collapse(false);
|
|
130
|
+
sel.removeAllRanges();
|
|
131
|
+
sel.addRange(range);
|
|
132
|
+
};
|
|
99
133
|
/** The op proper, against an element already resolved (or null). */
|
|
100
134
|
const perform = (el) => {
|
|
101
135
|
const missing = sel !== null && el === null;
|
|
@@ -125,6 +159,19 @@ function pageOp(a) {
|
|
|
125
159
|
return { found: true };
|
|
126
160
|
}
|
|
127
161
|
case 'type': {
|
|
162
|
+
if (!el)
|
|
163
|
+
return { found: false };
|
|
164
|
+
// Rich editors (every social composer) are contenteditable, not inputs.
|
|
165
|
+
if (el.isContentEditable) {
|
|
166
|
+
if (a.clear)
|
|
167
|
+
clearEditable(el);
|
|
168
|
+
else
|
|
169
|
+
caretToEnd(el);
|
|
170
|
+
if (a.text)
|
|
171
|
+
document.execCommand('insertText', false, a.text);
|
|
172
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
173
|
+
return { found: true };
|
|
174
|
+
}
|
|
128
175
|
const node = el;
|
|
129
176
|
if (!node)
|
|
130
177
|
return { found: false };
|
|
@@ -135,6 +182,23 @@ function pageOp(a) {
|
|
|
135
182
|
return { found: true };
|
|
136
183
|
}
|
|
137
184
|
case 'focus': {
|
|
185
|
+
if (!el)
|
|
186
|
+
return { found: false };
|
|
187
|
+
// trustedType() clears through this op before sending real keystrokes,
|
|
188
|
+
// so contenteditable has to be handled here too or `trusted: true`
|
|
189
|
+
// appends to whatever was already in the box.
|
|
190
|
+
if (el.isContentEditable) {
|
|
191
|
+
el.focus();
|
|
192
|
+
if (a.clear) {
|
|
193
|
+
clearEditable(el);
|
|
194
|
+
// The editor re-renders after the delete and drops the selection.
|
|
195
|
+
// trustedType() sends CDP Input.insertText straight after this, and
|
|
196
|
+
// without a caret to land on the keystrokes go nowhere — the box
|
|
197
|
+
// ends up cleared but empty.
|
|
198
|
+
caretToEnd(el);
|
|
199
|
+
}
|
|
200
|
+
return { found: true };
|
|
201
|
+
}
|
|
138
202
|
const node = el;
|
|
139
203
|
if (!node)
|
|
140
204
|
return { found: false };
|
|
@@ -500,6 +500,26 @@
|
|
|
500
500
|
else node.value = next;
|
|
501
501
|
node.dispatchEvent(new Event("input", { bubbles: true }));
|
|
502
502
|
};
|
|
503
|
+
const clearEditable = (host) => {
|
|
504
|
+
host.focus();
|
|
505
|
+
const sel2 = window.getSelection();
|
|
506
|
+
if (!sel2) return;
|
|
507
|
+
const range = document.createRange();
|
|
508
|
+
range.selectNodeContents(host);
|
|
509
|
+
sel2.removeAllRanges();
|
|
510
|
+
sel2.addRange(range);
|
|
511
|
+
document.execCommand("delete");
|
|
512
|
+
};
|
|
513
|
+
const caretToEnd = (host) => {
|
|
514
|
+
host.focus();
|
|
515
|
+
const sel2 = window.getSelection();
|
|
516
|
+
if (!sel2) return;
|
|
517
|
+
const range = document.createRange();
|
|
518
|
+
range.selectNodeContents(host);
|
|
519
|
+
range.collapse(false);
|
|
520
|
+
sel2.removeAllRanges();
|
|
521
|
+
sel2.addRange(range);
|
|
522
|
+
};
|
|
503
523
|
const perform = (el2) => {
|
|
504
524
|
const missing = sel !== null && el2 === null;
|
|
505
525
|
switch (a.op) {
|
|
@@ -524,6 +544,14 @@
|
|
|
524
544
|
return { found: true };
|
|
525
545
|
}
|
|
526
546
|
case "type": {
|
|
547
|
+
if (!el2) return { found: false };
|
|
548
|
+
if (el2.isContentEditable) {
|
|
549
|
+
if (a.clear) clearEditable(el2);
|
|
550
|
+
else caretToEnd(el2);
|
|
551
|
+
if (a.text) document.execCommand("insertText", false, a.text);
|
|
552
|
+
el2.dispatchEvent(new Event("change", { bubbles: true }));
|
|
553
|
+
return { found: true };
|
|
554
|
+
}
|
|
527
555
|
const node = el2;
|
|
528
556
|
if (!node) return { found: false };
|
|
529
557
|
node.focus();
|
|
@@ -533,6 +561,15 @@
|
|
|
533
561
|
return { found: true };
|
|
534
562
|
}
|
|
535
563
|
case "focus": {
|
|
564
|
+
if (!el2) return { found: false };
|
|
565
|
+
if (el2.isContentEditable) {
|
|
566
|
+
el2.focus();
|
|
567
|
+
if (a.clear) {
|
|
568
|
+
clearEditable(el2);
|
|
569
|
+
caretToEnd(el2);
|
|
570
|
+
}
|
|
571
|
+
return { found: true };
|
|
572
|
+
}
|
|
536
573
|
const node = el2;
|
|
537
574
|
if (!node) return { found: false };
|
|
538
575
|
node.focus();
|
|
@@ -1041,9 +1078,22 @@
|
|
|
1041
1078
|
});
|
|
1042
1079
|
}
|
|
1043
1080
|
async function trustedType(tabId, selector, text, clear, frameIds, pressEnter = false) {
|
|
1044
|
-
const focused = await execOp(tabId, withWait({ op: "focus", selector, clear }), frameIds);
|
|
1081
|
+
const focused = await execOp(tabId, withWait({ op: "focus", selector, clear: false }), frameIds);
|
|
1045
1082
|
if (!focused.found) return false;
|
|
1046
1083
|
await withDebugger(tabId, async (t) => {
|
|
1084
|
+
if (clear) {
|
|
1085
|
+
for (const type of ["keyDown", "keyUp"]) {
|
|
1086
|
+
await chrome.debugger.sendCommand(t, "Input.dispatchKeyEvent", {
|
|
1087
|
+
type,
|
|
1088
|
+
key: "a",
|
|
1089
|
+
code: "KeyA",
|
|
1090
|
+
windowsVirtualKeyCode: 65,
|
|
1091
|
+
modifiers: 8,
|
|
1092
|
+
// Meta
|
|
1093
|
+
...type === "keyDown" ? { commands: ["selectAll"] } : {}
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1047
1097
|
await chrome.debugger.sendCommand(t, "Input.insertText", { text });
|
|
1048
1098
|
if (!pressEnter) return;
|
|
1049
1099
|
for (const type of ["keyDown", "keyUp"]) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "MCP Extension for Chrome",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.5",
|
|
5
5
|
"description": "Lets a local chrome-mcp server drive this browser. Pair it with the server's handshake token.",
|
|
6
6
|
"homepage_url": "https://chrome-mcp-omega.vercel.app",
|
|
7
7
|
"minimum_chrome_version": "116",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mehmoodqureshi/chrome-mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
4
4
|
"description": "Drive your real Chrome browser over MCP — real logins, real cookies. A stdio MCP server (CLI) plus an MV3 extension, driving Chrome via chrome.scripting/chrome.tabs. Multi-tab batch automation, accessibility snapshots, deny-all security by default.",
|
|
5
5
|
"author": "Mehmood Ur Rehman Qureshi",
|
|
6
6
|
"license": "MIT",
|