@mcp-b/chrome-devtools-mcp 1.7.0 → 1.7.1
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/README.md +120 -10
- package/build/src/McpContext.js +50 -3
- package/build/src/browser.js +62 -6
- package/build/src/cli.js +6 -1
- package/build/src/formatters/IssueFormatter.js +190 -0
- package/build/src/main.js +83 -2
- package/build/src/telemetry/clearcut-logger.js +102 -0
- package/build/src/telemetry/flag-utils.js +45 -0
- package/build/src/telemetry/metric-utils.js +14 -0
- package/build/src/telemetry/persistence.js +53 -0
- package/build/src/telemetry/types.js +33 -0
- package/build/src/telemetry/watchdog/clearcut-sender.js +201 -0
- package/build/src/telemetry/watchdog/main.js +127 -0
- package/build/src/telemetry/watchdog-client.js +60 -0
- package/build/src/third_party/devtools-formatter-worker.js +7 -0
- package/build/src/tools/browser.js +92 -0
- package/build/src/tools/extension.js +31 -0
- package/build/src/tools/extensions.js +79 -0
- package/build/src/tools/input.js +6 -1
- package/build/src/tools/pages.js +0 -1
- package/build/src/tools/tools.js +4 -0
- package/build/src/transports/WebMCPBridgeScript.js +11 -2
- package/build/src/utils/ExtensionRegistry.js +35 -0
- package/build/src/utils/string.js +36 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/common/Base64.js +20 -2
- package/build/vendor/chrome-devtools-frontend/front_end/core/common/Debouncer.js +8 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/common/Gzip.js +11 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/common/Object.js +6 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/common/ParsedURL.js +3 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/common/ResourceType.js +6 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/common/Revealer.js +0 -5
- package/build/vendor/chrome-devtools-frontend/front_end/core/common/Settings.js +18 -8
- package/build/vendor/chrome-devtools-frontend/front_end/core/host/AidaClient.js +24 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/host/InspectorFrontendHostStub.js +11 -3
- package/build/vendor/chrome-devtools-frontend/front_end/core/host/ResourceLoader.js +1 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/host/UserMetrics.js +27 -20
- package/build/vendor/chrome-devtools-frontend/front_end/core/i18n/collect-ui-strings.js +7 -8
- package/build/vendor/chrome-devtools-frontend/front_end/core/i18n/generate-locales-js.js +4 -5
- package/build/vendor/chrome-devtools-frontend/front_end/core/platform/ArrayUtilities.js +10 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/platform/StringUtilities.js +63 -12
- package/build/vendor/chrome-devtools-frontend/front_end/core/protocol_client/CDPConnection.js +1 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/protocol_client/InspectorBackend.js +4 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/root/ExperimentNames.js +30 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/root/root.js +2 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/AnimationModel.js +0 -4
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSMatchedStyles.js +69 -9
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSMetadata.js +6 -6
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSModel.js +28 -13
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSProperty.js +1 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CSSPropertyParserMatchers.js +6 -0
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/ConsoleModel.js +0 -2
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/CookieModel.js +1 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/DOMModel.js +170 -13
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/DebuggerModel.js +5 -39
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/HeapProfilerModel.js +8 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/NetworkManager.js +20 -5
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/NetworkRequest.js +12 -21
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/OverlayModel.js +19 -6
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/RehydratingConnection.js +5 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/ResourceTreeModel.js +8 -5
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/SourceMap.js +15 -10
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/SourceMapManager.js +1 -1
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/SourceMapScopesInfo.js +13 -27
- package/build/vendor/chrome-devtools-frontend/front_end/core/sdk/Target.js +3 -1
- package/build/vendor/chrome-devtools-frontend/front_end/generated/ARIAProperties.js +1 -7
- package/build/vendor/chrome-devtools-frontend/front_end/generated/Deprecation.js +1 -16
- package/build/vendor/chrome-devtools-frontend/front_end/generated/InspectorBackendCommands.js +82 -22
- package/build/vendor/chrome-devtools-frontend/front_end/generated/SupportedCSSProperties.js +265 -123
- package/build/vendor/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.js +2 -1
- package/build/vendor/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.js +10 -16
- package/build/vendor/chrome-devtools-frontend/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.js +97 -26
- package/build/vendor/chrome-devtools-frontend/front_end/models/ai_assistance/performance/AICallTree.js +35 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/annotations/AnnotationRepository.js +163 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/annotations/AnnotationType.js +10 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/annotations/annotations.js +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/bindings/CompilerScriptMapping.js +5 -3
- package/build/vendor/chrome-devtools-frontend/front_end/models/bindings/DebuggerLanguagePlugins.js +29 -58
- package/build/vendor/chrome-devtools-frontend/front_end/models/bindings/DebuggerWorkspaceBinding.js +7 -45
- package/build/vendor/chrome-devtools-frontend/front_end/models/emulation/DeviceModeModel.js +1 -1
- package/build/vendor/chrome-devtools-frontend/front_end/models/emulation/EmulatedDevices.js +14 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/formatter/FormatterWorkerPool.js +8 -5
- package/build/vendor/chrome-devtools-frontend/front_end/models/greendev/Prototypes.js +33 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/greendev/greendev.js +4 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/ContrastCheckTrigger.js +2 -2
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/CookieIssue.js +0 -21
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/CorsIssue.js +1 -38
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/IssueAggregator.js +8 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/IssuesManager.js +6 -12
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/PermissionElementIssue.js +243 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementActivationDisabled.md +7 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementActivationDisabledWithOccluder.md +9 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementActivationDisabledWithOccluderParent.md +9 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementCspFrameAncestorsMissing.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementFencedFrameDisallowed.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementFontSizeTooLarge.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementFontSizeTooSmall.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementGeolocationDeprecated.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInsetBoxShadowUnsupported.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInvalidDisplayStyle.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInvalidSizeValue.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInvalidType.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementInvalidTypeActivation.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementLowContrast.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementNonOpaqueColor.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementPaddingBottomUnsupported.md +6 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementPaddingRightUnsupported.md +6 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementPermissionsPolicyBlocked.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementRegistrationFailed.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementRequestInProgress.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementSecurityChecksFailed.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementTypeNotSupported.md +5 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/permissionElementUntrustedEvent.md +7 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/issues_manager.js +2 -1
- package/build/vendor/chrome-devtools-frontend/front_end/models/logs/NetworkLog.js +0 -8
- package/build/vendor/chrome-devtools-frontend/front_end/models/source_map_scopes/NamesResolver.js +4 -8
- package/build/vendor/chrome-devtools-frontend/front_end/models/stack_trace/StackTrace.js +30 -1
- package/build/vendor/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceImpl.js +70 -1
- package/build/vendor/chrome-devtools-frontend/front_end/models/stack_trace/StackTraceModel.js +82 -30
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/EventsSerializer.js +10 -2
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/LanternComputationData.js +2 -2
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/ModelImpl.js +0 -3
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/Processor.js +18 -19
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/Styles.js +12 -4
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/extras/Initiators.js +46 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/extras/TraceTree.js +4 -3
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/extras/extras.js +1 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/LargestImagePaintHandler.js +2 -2
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/LayoutShiftsHandler.js +1 -1
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/MetaHandler.js +6 -0
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/NetworkRequestsHandler.js +10 -1
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/handlers/PageLoadMetricsHandler.js +44 -27
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/helpers/Timing.js +9 -2
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/Common.js +1 -6
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/LCPBreakdown.js +2 -2
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/LCPDiscovery.js +2 -4
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/NetworkDependencyTree.js +3 -2
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/insights/RenderBlocking.js +1 -1
- package/build/vendor/chrome-devtools-frontend/front_end/models/trace/types/TraceEvents.js +33 -11
- package/build/vendor/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/decode/decode.js +51 -18
- package/build/vendor/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/encode/encoder.js +1 -1
- package/build/vendor/chrome-devtools-frontend/front_end/third_party/source-map-scopes-codec/package/src/scopes.js +4 -0
- package/build/vendor/chrome-devtools-frontend/mcp/HostBindings.js +4 -0
- package/build/vendor/chrome-devtools-frontend/mcp/mcp.js +4 -0
- package/package.json +17 -10
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/SameSiteInvalidSameParty.md +0 -8
- package/build/vendor/chrome-devtools-frontend/front_end/models/issues_manager/descriptions/SameSiteSamePartyCrossPartyContextSet.md +0 -10
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import process from 'node:process';
|
|
7
|
+
import readline from 'node:readline';
|
|
8
|
+
import { parseArgs } from 'node:util';
|
|
9
|
+
import { logger, flushLogs, saveLogsToFile } from '../../logger.js';
|
|
10
|
+
import { WatchdogMessageType } from '../types.js';
|
|
11
|
+
import { ClearcutSender } from './clearcut-sender.js';
|
|
12
|
+
function parseWatchdogArgs() {
|
|
13
|
+
const { values } = parseArgs({
|
|
14
|
+
options: {
|
|
15
|
+
'parent-pid': { type: 'string' },
|
|
16
|
+
'app-version': { type: 'string' },
|
|
17
|
+
'os-type': { type: 'string' },
|
|
18
|
+
'log-file': { type: 'string' },
|
|
19
|
+
'clearcut-endpoint': { type: 'string' },
|
|
20
|
+
'clearcut-force-flush-interval-ms': { type: 'string' },
|
|
21
|
+
'clearcut-include-pid-header': { type: 'boolean' },
|
|
22
|
+
},
|
|
23
|
+
strict: true,
|
|
24
|
+
});
|
|
25
|
+
// Verify required arguments
|
|
26
|
+
const parentPid = parseInt(values['parent-pid'] ?? '', 10);
|
|
27
|
+
const appVersion = values['app-version'];
|
|
28
|
+
const osType = parseInt(values['os-type'] ?? '', 10);
|
|
29
|
+
if (isNaN(parentPid) || !appVersion || isNaN(osType)) {
|
|
30
|
+
console.error('Invalid arguments provided for watchdog process: ', JSON.stringify({ parentPid, appVersion, osType }));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
// Parse Optional Arguments
|
|
34
|
+
const logFile = values['log-file'];
|
|
35
|
+
const clearcutEndpoint = values['clearcut-endpoint'];
|
|
36
|
+
const clearcutIncludePidHeader = values['clearcut-include-pid-header'];
|
|
37
|
+
let clearcutForceFlushIntervalMs;
|
|
38
|
+
if (values['clearcut-force-flush-interval-ms']) {
|
|
39
|
+
const parsed = parseInt(values['clearcut-force-flush-interval-ms'], 10);
|
|
40
|
+
if (!isNaN(parsed)) {
|
|
41
|
+
clearcutForceFlushIntervalMs = parsed;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
parentPid,
|
|
46
|
+
appVersion,
|
|
47
|
+
osType,
|
|
48
|
+
logFile,
|
|
49
|
+
clearcutEndpoint,
|
|
50
|
+
clearcutForceFlushIntervalMs,
|
|
51
|
+
clearcutIncludePidHeader,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function main() {
|
|
55
|
+
const { parentPid, appVersion, osType, logFile, clearcutEndpoint, clearcutForceFlushIntervalMs, clearcutIncludePidHeader, } = parseWatchdogArgs();
|
|
56
|
+
let logStream;
|
|
57
|
+
if (logFile) {
|
|
58
|
+
logStream = saveLogsToFile(logFile);
|
|
59
|
+
}
|
|
60
|
+
const exit = (code) => {
|
|
61
|
+
if (!logStream) {
|
|
62
|
+
process.exit(code);
|
|
63
|
+
}
|
|
64
|
+
void flushLogs(logStream).finally(() => {
|
|
65
|
+
process.exit(code);
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
logger('Watchdog started', JSON.stringify({
|
|
69
|
+
pid: process.pid,
|
|
70
|
+
parentPid,
|
|
71
|
+
version: appVersion,
|
|
72
|
+
osType,
|
|
73
|
+
}, null, 2));
|
|
74
|
+
const sender = new ClearcutSender({
|
|
75
|
+
appVersion,
|
|
76
|
+
osType: osType,
|
|
77
|
+
clearcutEndpoint,
|
|
78
|
+
forceFlushIntervalMs: clearcutForceFlushIntervalMs,
|
|
79
|
+
includePidHeader: clearcutIncludePidHeader,
|
|
80
|
+
});
|
|
81
|
+
let isShuttingDown = false;
|
|
82
|
+
function onParentDeath(reason) {
|
|
83
|
+
if (isShuttingDown) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
isShuttingDown = true;
|
|
87
|
+
logger(`Parent death detected (${reason}). Sending shutdown event...`);
|
|
88
|
+
sender
|
|
89
|
+
.sendShutdownEvent()
|
|
90
|
+
.then(() => {
|
|
91
|
+
logger('Shutdown event sent. Exiting.');
|
|
92
|
+
exit(0);
|
|
93
|
+
})
|
|
94
|
+
.catch(err => {
|
|
95
|
+
logger('Failed to send shutdown event', err);
|
|
96
|
+
exit(1);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
process.stdin.on('end', () => onParentDeath('stdin end'));
|
|
100
|
+
process.stdin.on('close', () => onParentDeath('stdin close'));
|
|
101
|
+
process.on('disconnect', () => onParentDeath('ipc disconnect'));
|
|
102
|
+
const rl = readline.createInterface({
|
|
103
|
+
input: process.stdin,
|
|
104
|
+
terminal: false,
|
|
105
|
+
});
|
|
106
|
+
rl.on('line', line => {
|
|
107
|
+
try {
|
|
108
|
+
if (!line.trim()) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const msg = JSON.parse(line);
|
|
112
|
+
if (msg.type === WatchdogMessageType.LOG_EVENT && msg.payload) {
|
|
113
|
+
sender.enqueueEvent(msg.payload);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
logger('Failed to parse IPC message', err);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
main();
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
console.error('Watchdog fatal error:', err);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { logger } from '../logger.js';
|
|
9
|
+
export class WatchdogClient {
|
|
10
|
+
#childProcess;
|
|
11
|
+
constructor(config, options) {
|
|
12
|
+
const watchdogPath = fileURLToPath(new URL('./watchdog/main.js', import.meta.url));
|
|
13
|
+
const args = [
|
|
14
|
+
watchdogPath,
|
|
15
|
+
`--parent-pid=${config.parentPid}`,
|
|
16
|
+
`--app-version=${config.appVersion}`,
|
|
17
|
+
`--os-type=${config.osType}`,
|
|
18
|
+
];
|
|
19
|
+
if (config.logFile) {
|
|
20
|
+
args.push(`--log-file=${config.logFile}`);
|
|
21
|
+
}
|
|
22
|
+
if (config.clearcutEndpoint) {
|
|
23
|
+
args.push(`--clearcut-endpoint=${config.clearcutEndpoint}`);
|
|
24
|
+
}
|
|
25
|
+
if (config.clearcutForceFlushIntervalMs) {
|
|
26
|
+
args.push(`--clearcut-force-flush-interval-ms=${config.clearcutForceFlushIntervalMs}`);
|
|
27
|
+
}
|
|
28
|
+
if (config.clearcutIncludePidHeader) {
|
|
29
|
+
args.push('--clearcut-include-pid-header');
|
|
30
|
+
}
|
|
31
|
+
const spawner = options?.spawn ?? spawn;
|
|
32
|
+
this.#childProcess = spawner(process.execPath, args, {
|
|
33
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
34
|
+
detached: true,
|
|
35
|
+
});
|
|
36
|
+
this.#childProcess.unref();
|
|
37
|
+
this.#childProcess.on('error', err => {
|
|
38
|
+
logger('Watchdog process error:', err);
|
|
39
|
+
});
|
|
40
|
+
this.#childProcess.on('exit', (code, signal) => {
|
|
41
|
+
logger(`Watchdog exited with code ${code} and signal ${signal}`);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
send(message) {
|
|
45
|
+
if (this.#childProcess.stdin &&
|
|
46
|
+
!this.#childProcess.stdin.destroyed &&
|
|
47
|
+
this.#childProcess.pid) {
|
|
48
|
+
try {
|
|
49
|
+
const line = JSON.stringify(message) + '\n';
|
|
50
|
+
this.#childProcess.stdin.write(line);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
logger('Failed to write to watchdog stdin', err);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
logger('Watchdog stdin not available, dropping message');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { zod } from '../third_party/index.js';
|
|
7
|
+
import { ToolCategory } from './categories.js';
|
|
8
|
+
import { defineTool } from './ToolDefinition.js';
|
|
9
|
+
export const connectToBrowser = defineTool({
|
|
10
|
+
name: 'connect_to_browser',
|
|
11
|
+
description: 'Connect to a different Chrome browser instance at runtime. ' +
|
|
12
|
+
'Disconnects from the current browser (without closing it) and attaches to the new one. ' +
|
|
13
|
+
'Useful for WXT extension development: run `wxt dev`, parse the debug URL from stdout, ' +
|
|
14
|
+
'then call this tool to attach. Retries automatically if the browser is still starting up.',
|
|
15
|
+
annotations: {
|
|
16
|
+
title: 'Connect to Browser',
|
|
17
|
+
category: ToolCategory.NAVIGATION,
|
|
18
|
+
readOnlyHint: false,
|
|
19
|
+
},
|
|
20
|
+
schema: {
|
|
21
|
+
browserUrl: zod
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('HTTP URL to the browser\'s DevTools endpoint, e.g. "http://127.0.0.1:9222". ' +
|
|
25
|
+
'Exactly one of browserUrl or wsEndpoint is required.'),
|
|
26
|
+
wsEndpoint: zod
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('WebSocket URL to connect to, e.g. "ws://127.0.0.1:9222/devtools/browser/...". ' +
|
|
30
|
+
'Exactly one of browserUrl or wsEndpoint is required.'),
|
|
31
|
+
wsHeaders: zod
|
|
32
|
+
.record(zod.string())
|
|
33
|
+
.optional()
|
|
34
|
+
.describe('Optional HTTP headers for WebSocket connection (only used with wsEndpoint).'),
|
|
35
|
+
timeout: zod
|
|
36
|
+
.number()
|
|
37
|
+
.int()
|
|
38
|
+
.positive()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe('Maximum time in milliseconds to wait for the browser to become available. ' +
|
|
41
|
+
'Defaults to 30000 (30 seconds). The tool retries every 2 seconds within this window.'),
|
|
42
|
+
},
|
|
43
|
+
handler: async (request, response, context) => {
|
|
44
|
+
const { browserUrl, wsEndpoint, wsHeaders, timeout } = request.params;
|
|
45
|
+
// Validate: exactly one connection method required
|
|
46
|
+
if (!browserUrl && !wsEndpoint) {
|
|
47
|
+
throw new Error('Either browserUrl or wsEndpoint is required. ' +
|
|
48
|
+
'Example: browserUrl "http://127.0.0.1:9222" or wsEndpoint "ws://127.0.0.1:9222/devtools/browser/..."');
|
|
49
|
+
}
|
|
50
|
+
if (browserUrl && wsEndpoint) {
|
|
51
|
+
throw new Error('Provide only one of browserUrl or wsEndpoint, not both.');
|
|
52
|
+
}
|
|
53
|
+
// Validate URL protocols
|
|
54
|
+
if (browserUrl && !browserUrl.startsWith('http://') && !browserUrl.startsWith('https://')) {
|
|
55
|
+
throw new Error(`Invalid browserUrl: "${browserUrl}". Must start with http:// or https://`);
|
|
56
|
+
}
|
|
57
|
+
if (wsEndpoint && !wsEndpoint.startsWith('ws://') && !wsEndpoint.startsWith('wss://')) {
|
|
58
|
+
throw new Error(`Invalid wsEndpoint: "${wsEndpoint}". Must start with ws:// or wss://`);
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const result = await context.reconnectBrowser({
|
|
62
|
+
browserURL: browserUrl,
|
|
63
|
+
wsEndpoint,
|
|
64
|
+
wsHeaders,
|
|
65
|
+
timeout,
|
|
66
|
+
});
|
|
67
|
+
response.appendResponseLine(`Connected to browser at ${wsEndpoint ?? browserUrl}`);
|
|
68
|
+
response.appendResponseLine(`WebSocket endpoint: ${result.wsEndpoint}`);
|
|
69
|
+
response.appendResponseLine('');
|
|
70
|
+
response.appendResponseLine('Pages:');
|
|
71
|
+
for (const page of result.pages) {
|
|
72
|
+
const marker = page.selected ? ' (selected)' : '';
|
|
73
|
+
response.appendResponseLine(` [${page.index}] ${page.url}${marker}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
response.setIsError(true);
|
|
78
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
79
|
+
response.appendResponseLine(`Failed to connect: ${message}`);
|
|
80
|
+
response.appendResponseLine('');
|
|
81
|
+
response.appendResponseLine('Troubleshooting:');
|
|
82
|
+
response.appendResponseLine(' - Ensure Chrome was launched with --remote-debugging-port=<port>');
|
|
83
|
+
response.appendResponseLine(' - Verify the URL is reachable: curl ' + (browserUrl ?? wsEndpoint));
|
|
84
|
+
if (timeout) {
|
|
85
|
+
response.appendResponseLine(' - Try increasing the timeout (current: ' + timeout + 'ms)');
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
response.appendResponseLine(' - Try setting a longer timeout (default is 30s)');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { zod } from '../third_party/index.js';
|
|
7
|
+
import { ToolCategory } from './categories.js';
|
|
8
|
+
import { defineTool } from './ToolDefinition.js';
|
|
9
|
+
export const evaluateInExtensionWorker = defineTool({
|
|
10
|
+
name: 'evaluate_in_extension_worker',
|
|
11
|
+
description: 'Execute a JavaScript expression in a Chrome extension background service worker. ' +
|
|
12
|
+
'Useful for inspecting extension state (e.g., McpHub connections, offscreen status) ' +
|
|
13
|
+
'that is not accessible from page contexts.',
|
|
14
|
+
annotations: {
|
|
15
|
+
category: ToolCategory.DEBUGGING,
|
|
16
|
+
readOnlyHint: false,
|
|
17
|
+
},
|
|
18
|
+
schema: {
|
|
19
|
+
expression: zod
|
|
20
|
+
.string()
|
|
21
|
+
.describe('JavaScript expression to evaluate in the service worker.'),
|
|
22
|
+
extensionId: zod
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Extension ID to target. If omitted, the first extension service worker found is used.'),
|
|
26
|
+
},
|
|
27
|
+
handler: async (request, response, context) => {
|
|
28
|
+
const result = await context.evaluateInExtensionWorker(request.params.expression, request.params.extensionId);
|
|
29
|
+
response.appendResponseLine(JSON.stringify(result, null, 2));
|
|
30
|
+
},
|
|
31
|
+
});
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { zod } from '../third_party/index.js';
|
|
7
|
+
import { ToolCategory } from './categories.js';
|
|
8
|
+
import { defineTool } from './ToolDefinition.js';
|
|
9
|
+
const EXTENSIONS_CONDITION = 'experimentalExtensionSupport';
|
|
10
|
+
export const installExtension = defineTool({
|
|
11
|
+
name: 'install_extension',
|
|
12
|
+
description: 'Installs a Chrome extension from the given path.',
|
|
13
|
+
annotations: {
|
|
14
|
+
category: ToolCategory.EXTENSIONS,
|
|
15
|
+
readOnlyHint: false,
|
|
16
|
+
conditions: [EXTENSIONS_CONDITION],
|
|
17
|
+
},
|
|
18
|
+
schema: {
|
|
19
|
+
path: zod
|
|
20
|
+
.string()
|
|
21
|
+
.describe('Absolute path to the unpacked extension folder.'),
|
|
22
|
+
},
|
|
23
|
+
handler: async (request, response, context) => {
|
|
24
|
+
const { path } = request.params;
|
|
25
|
+
const id = await context.installExtension(path);
|
|
26
|
+
response.appendResponseLine(`Extension installed. Id: ${id}`);
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
export const uninstallExtension = defineTool({
|
|
30
|
+
name: 'uninstall_extension',
|
|
31
|
+
description: 'Uninstalls a Chrome extension by its ID.',
|
|
32
|
+
annotations: {
|
|
33
|
+
category: ToolCategory.EXTENSIONS,
|
|
34
|
+
readOnlyHint: false,
|
|
35
|
+
conditions: [EXTENSIONS_CONDITION],
|
|
36
|
+
},
|
|
37
|
+
schema: {
|
|
38
|
+
id: zod.string().describe('ID of the extension to uninstall.'),
|
|
39
|
+
},
|
|
40
|
+
handler: async (request, response, context) => {
|
|
41
|
+
const { id } = request.params;
|
|
42
|
+
await context.uninstallExtension(id);
|
|
43
|
+
response.appendResponseLine(`Extension uninstalled. Id: ${id}`);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
export const listExtensions = defineTool({
|
|
47
|
+
name: 'list_extensions',
|
|
48
|
+
description: 'Lists all extensions via this server, including their name, ID, version, and enabled status.',
|
|
49
|
+
annotations: {
|
|
50
|
+
category: ToolCategory.EXTENSIONS,
|
|
51
|
+
readOnlyHint: true,
|
|
52
|
+
conditions: [EXTENSIONS_CONDITION],
|
|
53
|
+
},
|
|
54
|
+
schema: {},
|
|
55
|
+
handler: async (_request, response, _context) => {
|
|
56
|
+
response.setListExtensions();
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
export const reloadExtension = defineTool({
|
|
60
|
+
name: 'reload_extension',
|
|
61
|
+
description: 'Reloads an unpacked Chrome extension by its ID.',
|
|
62
|
+
annotations: {
|
|
63
|
+
category: ToolCategory.EXTENSIONS,
|
|
64
|
+
readOnlyHint: false,
|
|
65
|
+
conditions: [EXTENSIONS_CONDITION],
|
|
66
|
+
},
|
|
67
|
+
schema: {
|
|
68
|
+
id: zod.string().describe('ID of the extension to reload.'),
|
|
69
|
+
},
|
|
70
|
+
handler: async (request, response, context) => {
|
|
71
|
+
const { id } = request.params;
|
|
72
|
+
const extension = context.getExtension(id);
|
|
73
|
+
if (!extension) {
|
|
74
|
+
throw new Error(`Extension with ID ${id} not found.`);
|
|
75
|
+
}
|
|
76
|
+
await context.installExtension(extension.path);
|
|
77
|
+
response.appendResponseLine('Extension reloaded.');
|
|
78
|
+
},
|
|
79
|
+
});
|
package/build/src/tools/input.js
CHANGED
|
@@ -103,11 +103,16 @@ async function selectOption(handle, aXNode, value) {
|
|
|
103
103
|
throw new Error(`Could not find option with text "${value}"`);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
+
function hasOptionChildren(aXNode) {
|
|
107
|
+
return aXNode.children.some(child => child.role === 'option');
|
|
108
|
+
}
|
|
106
109
|
async function fillFormElement(uid, value, context) {
|
|
107
110
|
const handle = await context.getElementByUid(uid);
|
|
108
111
|
try {
|
|
109
112
|
const aXNode = context.getAXNodeByUid(uid);
|
|
110
|
-
|
|
113
|
+
// We assume that combobox needs to be handled as select if it has
|
|
114
|
+
// role='combobox' and option children.
|
|
115
|
+
if (aXNode && aXNode.role === 'combobox' && hasOptionChildren(aXNode)) {
|
|
111
116
|
await selectOption(handle, aXNode, value);
|
|
112
117
|
}
|
|
113
118
|
else {
|
package/build/src/tools/pages.js
CHANGED
|
@@ -185,7 +185,6 @@ export const resizePage = defineTool({
|
|
|
185
185
|
},
|
|
186
186
|
handler: async (request, response, context) => {
|
|
187
187
|
const page = context.getSelectedPage();
|
|
188
|
-
// @ts-expect-error internal API for now.
|
|
189
188
|
await page.resize({
|
|
190
189
|
contentWidth: request.params.width,
|
|
191
190
|
contentHeight: request.params.height,
|
package/build/src/tools/tools.js
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
+
import * as browserTools from './browser.js';
|
|
6
7
|
import * as consoleTools from './console.js';
|
|
7
8
|
import * as emulationTools from './emulation.js';
|
|
9
|
+
import * as extensionTools from './extension.js';
|
|
8
10
|
import * as inputTools from './input.js';
|
|
9
11
|
import * as networkTools from './network.js';
|
|
10
12
|
import * as pagesTools from './pages.js';
|
|
@@ -14,8 +16,10 @@ import * as scriptTools from './script.js';
|
|
|
14
16
|
import * as snapshotTools from './snapshot.js';
|
|
15
17
|
import * as webmcpTools from './webmcp.js';
|
|
16
18
|
const tools = [
|
|
19
|
+
...Object.values(browserTools),
|
|
17
20
|
...Object.values(consoleTools),
|
|
18
21
|
...Object.values(emulationTools),
|
|
22
|
+
...Object.values(extensionTools),
|
|
19
23
|
...Object.values(inputTools),
|
|
20
24
|
...Object.values(networkTools),
|
|
21
25
|
...Object.values(pagesTools),
|
|
@@ -100,6 +100,15 @@ export const WEB_MCP_BRIDGE_SCRIPT = `
|
|
|
100
100
|
return false;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
function getTargetOrigin() {
|
|
104
|
+
var origin = window.location && typeof window.location.origin === 'string'
|
|
105
|
+
? window.location.origin
|
|
106
|
+
: '';
|
|
107
|
+
// about:blank, file://, and sandboxed contexts expose "null" origin.
|
|
108
|
+
// Use "*" so postMessage remains functional in those contexts.
|
|
109
|
+
return origin && origin !== 'null' ? origin : '*';
|
|
110
|
+
}
|
|
111
|
+
|
|
103
112
|
// Initial check
|
|
104
113
|
checkWebMCPAvailable();
|
|
105
114
|
|
|
@@ -125,7 +134,7 @@ export const WEB_MCP_BRIDGE_SCRIPT = `
|
|
|
125
134
|
type: 'mcp',
|
|
126
135
|
direction: 'client-to-server',
|
|
127
136
|
payload: payload
|
|
128
|
-
},
|
|
137
|
+
}, getTargetOrigin());
|
|
129
138
|
|
|
130
139
|
return true;
|
|
131
140
|
} catch (err) {
|
|
@@ -169,7 +178,7 @@ export const WEB_MCP_BRIDGE_SCRIPT = `
|
|
|
169
178
|
type: 'mcp',
|
|
170
179
|
direction: 'client-to-server',
|
|
171
180
|
payload: 'mcp-check-ready'
|
|
172
|
-
},
|
|
181
|
+
}, getTargetOrigin());
|
|
173
182
|
},
|
|
174
183
|
|
|
175
184
|
/**
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
export class ExtensionRegistry {
|
|
9
|
+
#extensions = new Map();
|
|
10
|
+
async registerExtension(id, extensionPath) {
|
|
11
|
+
const manifestPath = path.join(extensionPath, 'manifest.json');
|
|
12
|
+
const manifestContent = await fs.readFile(manifestPath, 'utf-8');
|
|
13
|
+
const manifest = JSON.parse(manifestContent);
|
|
14
|
+
const name = manifest.name ?? 'Unknown';
|
|
15
|
+
const version = manifest.version ?? 'Unknown';
|
|
16
|
+
const extension = {
|
|
17
|
+
id,
|
|
18
|
+
name,
|
|
19
|
+
version,
|
|
20
|
+
isEnabled: true,
|
|
21
|
+
path: extensionPath,
|
|
22
|
+
};
|
|
23
|
+
this.#extensions.set(extension.id, extension);
|
|
24
|
+
return extension;
|
|
25
|
+
}
|
|
26
|
+
remove(id) {
|
|
27
|
+
this.#extensions.delete(id);
|
|
28
|
+
}
|
|
29
|
+
list() {
|
|
30
|
+
return Array.from(this.#extensions.values());
|
|
31
|
+
}
|
|
32
|
+
getById(id) {
|
|
33
|
+
return this.#extensions.get(id);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Converts a given string to snake_case.
|
|
8
|
+
* This function handles camelCase, PascalCase, and acronyms, including transitions between letters and numbers.
|
|
9
|
+
* It uses Unicode-aware regular expressions (`\p{L}`, `\p{N}`, `\p{Lu}`, `\p{Ll}` with the `u` flag)
|
|
10
|
+
* to correctly process letters and numbers from various languages.
|
|
11
|
+
*
|
|
12
|
+
* @param text The input string to convert to snake_case.
|
|
13
|
+
* @returns The snake_case version of the input string.
|
|
14
|
+
*/
|
|
15
|
+
export function toSnakeCase(text) {
|
|
16
|
+
if (!text) {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
// First, handle case-based transformations to insert underscores correctly.
|
|
20
|
+
// 1. Add underscore between a letter and a number.
|
|
21
|
+
// e.g., "version2" -> "version_2"
|
|
22
|
+
// 2. Add underscore between an uppercase letter sequence and a following uppercase+lowercase sequence.
|
|
23
|
+
// e.g., "APIFlags" -> "API_Flags"
|
|
24
|
+
// 3. Add underscore between a lowercase/number and an uppercase letter.
|
|
25
|
+
// e.g., "lastName" -> "last_Name", "version_2Update" -> "version_2_Update"
|
|
26
|
+
// 4. Replace sequences of non-alphanumeric with a single underscore
|
|
27
|
+
// 5. Remove any leading or trailing underscores.
|
|
28
|
+
const result = text
|
|
29
|
+
.replace(/(\p{L})(\p{N})/gu, '$1_$2') // 1
|
|
30
|
+
.replace(/(\p{Lu}+)(\p{Lu}\p{Ll})/gu, '$1_$2') // 2
|
|
31
|
+
.replace(/(\p{Ll}|\p{N})(\p{Lu})/gu, '$1_$2') // 3
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.replace(/[^\p{L}\p{N}]+/gu, '_') // 4
|
|
34
|
+
.replace(/^_|_$/g, ''); // 5
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
@@ -29,13 +29,31 @@ export function decode(input) {
|
|
|
29
29
|
}
|
|
30
30
|
return bytes;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Note: if input can be very large (larger than the max string size), callers should
|
|
34
|
+
* expect this to throw an error.
|
|
35
|
+
*/
|
|
32
36
|
export function encode(input) {
|
|
33
37
|
return new Promise((resolve, reject) => {
|
|
34
38
|
const reader = new FileReader();
|
|
35
|
-
reader.onerror = () => reject(new Error('failed to convert to base64'));
|
|
39
|
+
reader.onerror = () => reject(new Error('failed to convert to base64: internal error'));
|
|
36
40
|
reader.onload = () => {
|
|
41
|
+
// The input was too large to encode as a string. The caller should anticipate
|
|
42
|
+
// this and use a workaround. See TimelinePanel.ts innerSaveToFile for an example.
|
|
43
|
+
// For more information, see crbug.com/436482118.
|
|
44
|
+
if (reader.result === '') {
|
|
45
|
+
reject(new Error('failed to convert to base64: input too large to encode as base64 string'));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// This string can be very large, so take care to not double memory. `split`
|
|
49
|
+
// was used here before, which always results in new strings in V8. By using
|
|
50
|
+
// slice instead, we leverage the sliced string optimization in V8 and avoid
|
|
51
|
+
// doubling the memory requirement (even if temporarily: that is a potential
|
|
52
|
+
// source of OOM crashes given large enough input, such as is common with
|
|
53
|
+
// Performance traces).
|
|
37
54
|
const blobAsUrl = reader.result;
|
|
38
|
-
const
|
|
55
|
+
const index = blobAsUrl.indexOf(',');
|
|
56
|
+
const base64 = blobAsUrl.slice(index + 1);
|
|
39
57
|
resolve(base64);
|
|
40
58
|
};
|
|
41
59
|
reader.readAsDataURL(new Blob([input]));
|
|
@@ -8,7 +8,14 @@ export const debounce = function (func, delay) {
|
|
|
8
8
|
let timer;
|
|
9
9
|
const debounced = (...args) => {
|
|
10
10
|
clearTimeout(timer);
|
|
11
|
-
timer = setTimeout(() => func(...args), delay);
|
|
11
|
+
timer = setTimeout(() => func(...args), testDebounceOverride ? 0 : delay);
|
|
12
12
|
};
|
|
13
13
|
return debounced;
|
|
14
14
|
};
|
|
15
|
+
let testDebounceOverride = false;
|
|
16
|
+
export function enableTestOverride() {
|
|
17
|
+
testDebounceOverride = true;
|
|
18
|
+
}
|
|
19
|
+
export function disableTestOverride() {
|
|
20
|
+
testDebounceOverride = false;
|
|
21
|
+
}
|
|
@@ -64,3 +64,14 @@ export function compressStream(stream) {
|
|
|
64
64
|
const cs = new CompressionStream('gzip');
|
|
65
65
|
return stream.pipeThrough(cs);
|
|
66
66
|
}
|
|
67
|
+
export function createMonitoredStream(stream, onProgress) {
|
|
68
|
+
let bytesRead = 0;
|
|
69
|
+
const progressTransformer = new TransformStream({
|
|
70
|
+
transform(chunk, controller) {
|
|
71
|
+
bytesRead += chunk.byteLength;
|
|
72
|
+
onProgress(bytesRead);
|
|
73
|
+
controller.enqueue(chunk);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
return stream.pipeThrough(progressTransformer);
|
|
77
|
+
}
|
|
@@ -57,7 +57,12 @@ export class ObjectWrapper {
|
|
|
57
57
|
// new listeners.
|
|
58
58
|
for (const listener of [...listeners]) {
|
|
59
59
|
if (!listener.disposed) {
|
|
60
|
-
|
|
60
|
+
try {
|
|
61
|
+
listener.listener.call(listener.thisObject, event);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.error(`Event listener for ${String(eventType)} throw an error:`, err);
|
|
65
|
+
}
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
}
|
|
@@ -263,6 +263,9 @@ export class ParsedURL {
|
|
|
263
263
|
return '';
|
|
264
264
|
}
|
|
265
265
|
static extractName(url) {
|
|
266
|
+
if (url.endsWith('/')) {
|
|
267
|
+
url = url.slice(0, -1);
|
|
268
|
+
}
|
|
266
269
|
let index = url.lastIndexOf('/');
|
|
267
270
|
const pathAndQuery = index !== -1 ? url.substr(index + 1) : url;
|
|
268
271
|
index = pathAndQuery.indexOf('?');
|
|
@@ -229,6 +229,12 @@ export class ResourceType {
|
|
|
229
229
|
const regex = new RegExp('^application(.*json$|\/json\+.*)');
|
|
230
230
|
return regex.test(contentType) ? 'application/json' : contentType;
|
|
231
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Checks whether the given MIME type represents JavaScript content.
|
|
234
|
+
*/
|
|
235
|
+
static isJavaScriptMimeType(mimeType) {
|
|
236
|
+
return mimeType === 'application/javascript' || mimeType === 'text/javascript';
|
|
237
|
+
}
|
|
232
238
|
/**
|
|
233
239
|
* Adds suffixes iff the mimeType is 'text/javascript' to denote whether the JS is minified or from
|
|
234
240
|
* a source map.
|