opera-devtools-mcp 0.2.3 → 0.3.0
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 +104 -7
- package/build/src/McpContext.js +365 -365
- package/build/src/McpPage.js +475 -48
- package/build/src/McpResponse.js +462 -288
- package/build/src/SlimMcpResponse.js +1 -1
- package/build/src/TextSnapshot.js +101 -22
- package/build/src/ToolHandler.js +142 -20
- package/build/src/bin/check-latest-version.js +27 -1
- package/build/src/bin/chrome-devtools-cli-options.js +573 -10
- package/build/src/bin/chrome-devtools-mcp-cli-options.js +119 -32
- package/build/src/bin/chrome-devtools-mcp-main.js +50 -7
- package/build/src/bin/chrome-devtools-mcp.js +7 -4
- package/build/src/bin/chrome-devtools.js +57 -14
- package/build/src/bin/opera-devtools-cli-options.js +3 -829
- package/build/src/bin/opera-devtools-mcp-cli-options.js +3 -348
- package/build/src/bin/opera-devtools-mcp-main.js +3 -37
- package/build/src/bin/opera-devtools-mcp.js +4 -19
- package/build/src/bin/opera-devtools.js +4 -185
- package/build/src/browser.js +45 -5
- package/build/src/{PageCollector.js → collectors/PageCollector.js} +64 -133
- package/build/src/collectors/ServiceWorkerCollector.js +171 -0
- package/build/src/daemon/client.js +75 -17
- package/build/src/daemon/daemon.js +92 -30
- package/build/src/daemon/utils.js +24 -8
- package/build/src/{DevtoolsUtils.js → devtools/DevtoolsUtils.js} +94 -88
- package/build/src/devtools/McpHostBindingAdapter.js +165 -0
- package/build/src/{issue-descriptions.js → devtools/issueDescriptions.js} +8 -6
- package/build/src/formatters/ConsoleFormatter.js +44 -11
- package/build/src/formatters/HeapSnapshotFormatter.js +140 -9
- package/build/src/formatters/IssueFormatter.js +7 -7
- package/build/src/formatters/NetworkFormatter.js +10 -8
- package/build/src/index.js +101 -232
- package/build/src/opera/branding.js +34 -0
- package/build/src/opera/browserFlags.js +85 -0
- package/build/src/opera/browserLaunch.js +52 -0
- package/build/src/opera/policy.js +50 -0
- package/build/src/opera/serviceWorkerRetry.js +37 -0
- package/build/src/opera/toolHandlerHooks.js +39 -0
- package/build/src/{tools → opera/tools}/opera.js +9 -21
- package/build/src/processors/HeapSnapshotManager.js +258 -0
- package/build/src/{trace-processing/parse.js → processors/PerformanceTrace.js} +9 -9
- package/build/src/telemetry/ClearcutLogger.js +12 -119
- package/build/src/telemetry/WatchdogClient.js +5 -5
- package/build/src/telemetry/errors.js +1 -1
- package/build/src/telemetry/flagUtils.js +6 -6
- package/build/src/telemetry/{toolMetricsUtils.js → metricsRegistry.js} +4 -11
- package/build/src/telemetry/persistence.js +3 -3
- package/build/src/telemetry/transformation.js +184 -0
- package/build/src/telemetry/watchdog/ClearcutSender.js +11 -11
- package/build/src/telemetry/watchdog/main.js +6 -6
- package/build/src/third_party/THIRD_PARTY_NOTICES +3493 -0
- package/build/src/third_party/bundled-packages.json +9 -0
- package/build/src/third_party/devtools-formatter-worker.js +15340 -7
- package/build/src/third_party/devtools-heap-snapshot-worker.js +10337 -7
- package/build/src/third_party/index.js +179960 -26
- package/build/src/third_party/issue-descriptions/CoepCorpNotSameOrigin.md +2 -2
- package/build/src/third_party/issue-descriptions/CoepCorpNotSameOriginAfterDefaultedToSameOriginByCoep.md +3 -3
- package/build/src/third_party/issue-descriptions/CoepCorpNotSameSite.md +2 -2
- package/build/src/third_party/issue-descriptions/CoepFrameResourceNeedsCoepHeader.md +1 -1
- package/build/src/third_party/issue-descriptions/CompatibilityModeQuirks.md +1 -1
- package/build/src/third_party/issue-descriptions/CookieAttributeValueExceedsMaxSize.md +2 -2
- package/build/src/third_party/issue-descriptions/LowTextContrast.md +1 -1
- package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorRead.md +1 -1
- package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureErrorSet.md +1 -1
- package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnRead.md +1 -1
- package/build/src/third_party/issue-descriptions/SameSiteNoneInsecureWarnSet.md +1 -1
- package/build/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeRead.md +3 -3
- package/build/src/third_party/issue-descriptions/SameSiteUnspecifiedLaxAllowUnsafeSet.md +3 -3
- package/build/src/third_party/issue-descriptions/bounceTrackingMitigations.md +1 -1
- package/build/src/third_party/issue-descriptions/connectionAllowlistEmbeddingRequirementNotSatisfied.md +26 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistIFrameAttributeLoosensEmbeddingRequirement.md +19 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowConnectionAllowlistFrom.md +21 -0
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidAllowlistItemType.md +2 -2
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidHeader.md +2 -2
- package/build/src/third_party/issue-descriptions/connectionAllowlistInvalidUrlPattern.md +3 -3
- package/build/src/third_party/issue-descriptions/connectionAllowlistItemNotInnerList.md +2 -2
- package/build/src/third_party/issue-descriptions/connectionAllowlistMoreThanOneList.md +2 -2
- package/build/src/third_party/issue-descriptions/connectionAllowlistReportingEndpointNotToken.md +1 -1
- package/build/src/third_party/issue-descriptions/cookieCrossSiteRedirectDowngrade.md +3 -3
- package/build/src/third_party/issue-descriptions/cookieExcludeBlockedWithinRelatedWebsiteSet.md +1 -1
- package/build/src/third_party/issue-descriptions/cookieExcludePortMismatch.md +1 -1
- package/build/src/third_party/issue-descriptions/cookieExcludeSchemeMismatch.md +1 -1
- package/build/src/third_party/issue-descriptions/cookieExcludeThirdPartyPhaseoutSet.md +1 -1
- package/build/src/third_party/issue-descriptions/cookieWarnThirdPartyPhaseoutRead.md +1 -1
- package/build/src/third_party/issue-descriptions/corsAllowCredentialsRequired.md +1 -1
- package/build/src/third_party/issue-descriptions/corsDisabledScheme.md +2 -2
- package/build/src/third_party/issue-descriptions/corsDisallowedByMode.md +2 -2
- package/build/src/third_party/issue-descriptions/corsHeaderDisallowedByPreflightResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/corsInvalidHeaderValues.md +3 -3
- package/build/src/third_party/issue-descriptions/corsLocalNetworkAccessPermissionDenied.md +1 -1
- package/build/src/third_party/issue-descriptions/corsMethodDisallowedByPreflightResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/corsNoCorsRedirectModeNotFollow.md +1 -1
- package/build/src/third_party/issue-descriptions/corsOriginMismatch.md +3 -3
- package/build/src/third_party/issue-descriptions/corsPreflightResponseInvalid.md +2 -2
- package/build/src/third_party/issue-descriptions/corsRedirectContainsCredentials.md +3 -3
- package/build/src/third_party/issue-descriptions/corsWildcardOriginNotAllowed.md +4 -3
- package/build/src/third_party/issue-descriptions/cspEvalViolation.md +4 -4
- package/build/src/third_party/issue-descriptions/cspInlineViolation.md +2 -2
- package/build/src/third_party/issue-descriptions/cspTrustedTypesPolicyViolation.md +2 -2
- package/build/src/third_party/issue-descriptions/cspTrustedTypesSinkViolation.md +4 -4
- package/build/src/third_party/issue-descriptions/cspURLViolation.md +4 -4
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsEmptyList.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestAccountsNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsFetchFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestDnsInvalidRecord.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestEmailVerificationWellKnownNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestInvalidEmail.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestJwksInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestKeyBindingSigningFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestRpOriginIsOpaque.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenInvalidSdJwt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenMalformedSdJwt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidAudience.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidIssuedAt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidNonce.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidSdHash.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbInvalidTyp.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingAud.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingCnf.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingIat.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingNonce.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbMissingSdHash.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationKbSignatureFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmail.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidEmailVerified.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidHolderKey.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuedAt.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtInvalidIssuer.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtJwksMissingKeys.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingCnf.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingEmail.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIat.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtMissingIss.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtSignatureFailed.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestTokenVerificationSdJwtUnsupportedHeaderAlg.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestUserLoggedOut.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownAccountsEndpointCrossOrigin.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownHttpNotFound.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidContentType.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownInvalidResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownIssuanceEndpointCrossOrigin.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownListEmpty.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingAccountsEndpoint.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownMissingIssuanceEndpoint.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownNoResponse.md +1 -0
- package/build/src/third_party/issue-descriptions/emailVerificationRequestWellKnownUnsupportedSigningAlgorithm.md +1 -0
- package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsHttpNotFound.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsInvalidResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestAccountsNoResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestApprovalDeclined.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestCanceled.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestErrorFetchingSignin.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestErrorIdToken.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenHttpNotFound.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidRequest.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenInvalidResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestIdTokenNoResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestInvalidSigninResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestHttpNotFound.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestInvalidResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestManifestNoResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthRequestTooManyRequests.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidAccountsResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestInvalidConfigOrWellKnown.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoAccountSharingPermission.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoApiPermission.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNoReturningUserFromFetchedAccounts.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotIframe.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotPotentiallyTrustworthy.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSameOrigin.md +1 -1
- package/build/src/third_party/issue-descriptions/federatedAuthUserInfoRequestNotSignedInWithIdp.md +1 -1
- package/build/src/third_party/issue-descriptions/fetchingPartitionedBlobURL.md +2 -2
- package/build/src/third_party/issue-descriptions/genericBackUINavigationWouldSkipAd.md +4 -0
- package/build/src/third_party/issue-descriptions/genericFormAriaLabelledByToNonExistingIdError.md +2 -2
- package/build/src/third_party/issue-descriptions/genericFormAutocompleteAttributeEmptyError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormDuplicateIdForInputError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormEmptyIdAndNameAttributesForInputError.md +2 -2
- package/build/src/third_party/issue-descriptions/genericFormInputAssignedAutocompleteValueToIdOrNameAttributeError.md +2 -2
- package/build/src/third_party/issue-descriptions/genericFormInputHasWrongButWellIntendedAutocompleteValueError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormInputWithNoLabelError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormLabelForMatchesNonExistingIdError.md +2 -2
- package/build/src/third_party/issue-descriptions/genericFormLabelForNameError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormLabelHasNeitherForNorNestedInputError.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormModelContextMissingToolName.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormModelContextParameterMissingName.md +1 -1
- package/build/src/third_party/issue-descriptions/genericFormModelContextRequiredParameterMissingName.md +1 -1
- package/build/src/third_party/issue-descriptions/genericNavigationEntryMarkedSkippable.md +2 -2
- package/build/src/third_party/issue-descriptions/heavyAd.md +1 -1
- package/build/src/third_party/issue-descriptions/lazyLoadImageZeroSize.md +7 -0
- package/build/src/third_party/issue-descriptions/navigatingPartitionedBlobURL.md +3 -3
- package/build/src/third_party/issue-descriptions/permissionElementActivationDisabled.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluder.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementActivationDisabledWithOccluderParent.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementFencedFrameDisallowed.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementFontSizeTooLarge.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementFontSizeTooSmall.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementInsetBoxShadowUnsupported.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementInvalidDisplayStyle.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementInvalidSizeValue.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementInvalidType.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementInvalidTypeActivation.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementNonOpaqueColor.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementPaddingBottomUnsupported.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementPaddingRightUnsupported.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementRequestInProgress.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementSecurityChecksFailed.md +1 -1
- package/build/src/third_party/issue-descriptions/permissionElementTypeNotSupported.md +2 -2
- package/build/src/third_party/issue-descriptions/permissionElementUntrustedEvent.md +1 -1
- package/build/src/third_party/issue-descriptions/placeholderDescriptionForInvisibleIssues.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedOptGroupChild.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityDisallowedSelectChild.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentAttributesSelectDescendant.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentLegendChild.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityInteractiveContentOptionChild.md +1 -1
- package/build/src/third_party/issue-descriptions/selectElementAccessibilityNonPhrasingContentOptionChild.md +1 -1
- package/build/src/third_party/issue-descriptions/selectivePermissionsIntervention.md +3 -3
- package/build/src/third_party/issue-descriptions/sharedArrayBuffer.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorCrossOriginNoCorsRequest.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorMatchingDictionaryNotUsed.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryUseErrorUnexpectedContentDictionaryHeader.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorCossOriginNoCorsRequest.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorDisallowedBySettings.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorExpiredResponse.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorFeatureDisabled.md +2 -2
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorInvalidStructuredHeader.md +1 -1
- package/build/src/third_party/issue-descriptions/sharedDictionaryWriteErrorNavigationRequest.md +2 -2
- package/build/src/third_party/issue-descriptions/sriInvalidSignatureHeader.md +4 -4
- package/build/src/third_party/issue-descriptions/sriInvalidSignatureInputHeader.md +4 -4
- package/build/src/third_party/issue-descriptions/sriMissingSignatureHeader.md +1 -1
- package/build/src/third_party/issue-descriptions/sriMissingSignatureInputHeader.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsIncorrectLength.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsNotByteSequence.md +4 -4
- package/build/src/third_party/issue-descriptions/sriSignatureHeaderValueIsParameterized.md +4 -4
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentName.md +1 -1
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidComponentType.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidDerivedComponentParameter.md +1 -1
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidHeaderComponentParameter.md +1 -1
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderInvalidParameter.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderKeyIdLength.md +5 -5
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingLabel.md +3 -3
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderMissingRequiredParameters.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderValueMissingComponents.md +2 -2
- package/build/src/third_party/issue-descriptions/sriSignatureInputHeaderValueNotInnerList.md +2 -2
- package/build/src/third_party/issue-descriptions/sriValidationFailedIntegrityMismatch.md +4 -4
- package/build/src/third_party/issue-descriptions/sriValidationFailedInvalidLength.md +3 -3
- package/build/src/third_party/issue-descriptions/sriValidationFailedSignatureExpired.md +2 -2
- package/build/src/third_party/issue-descriptions/sriValidationFailedSignatureMismatch.md +2 -2
- package/build/src/third_party/issue-descriptions/stylesheetLateImport.md +1 -1
- package/build/src/third_party/issue-descriptions/summaryElementAccessibilityInteractiveContentSummaryDescendant.md +1 -1
- package/build/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestLength.md +2 -2
- package/build/src/third_party/issue-descriptions/unencodedDigestIncorrectDigestType.md +3 -3
- package/build/src/third_party/issue-descriptions/unencodedDigestMalformedDictionary.md +2 -2
- package/build/src/third_party/issue-descriptions/unencodedDigestUnknownAlgorithm.md +1 -1
- package/build/src/third_party/lighthouse-devtools-mcp-bundle.js +3177 -6340
- package/build/src/tools/ToolDefinition.js +2 -2
- package/build/src/tools/categories.js +3 -0
- package/build/src/tools/console.js +13 -0
- package/build/src/tools/emulation.js +30 -3
- package/build/src/tools/extensions.js +8 -1
- package/build/src/tools/input.js +246 -128
- package/build/src/tools/lighthouse.js +14 -7
- package/build/src/tools/memory.js +289 -25
- package/build/src/tools/network.js +10 -7
- package/build/src/tools/pages.js +33 -92
- package/build/src/tools/performance.js +67 -52
- package/build/src/tools/pwa.js +128 -0
- package/build/src/tools/screencast.js +35 -12
- package/build/src/tools/screenshot.js +234 -75
- package/build/src/tools/script.js +130 -45
- package/build/src/tools/slim/tools.js +3 -0
- package/build/src/tools/snapshot.js +7 -4
- package/build/src/tools/thirdPartyDeveloper.js +19 -9
- package/build/src/tools/tools.js +3 -1
- package/build/src/tools/webmcp.js +3 -1
- package/build/src/utils/WaitForHelper.js +299 -0
- package/build/src/utils/check-for-updates.js +8 -2
- package/build/src/utils/files.js +42 -3
- package/build/src/utils/logger.js +51 -0
- package/build/src/utils/polyfill.js +11 -0
- package/build/src/utils/url.js +43 -0
- package/build/src/version.js +1 -1
- package/package.json +42 -33
- package/build/src/DevToolsConnectionAdapter.js +0 -70
- package/build/src/HeapSnapshotManager.js +0 -110
- package/build/src/Mutex.js +0 -38
- package/build/src/WaitForHelper.js +0 -195
- package/build/src/bin/cliDefinitions.js +0 -616
- package/build/src/logger.js +0 -37
- package/build/src/polyfill.js +0 -8
- package/build/src/telemetry/metricUtils.js +0 -15
- package/build/src/third_party/issue-descriptions/SameSiteExcludeContextDowngradeRead.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteExcludeContextDowngradeSet.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteExcludeNavigationContextDowngrade.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteWarnCrossDowngradeRead.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteWarnCrossDowngradeSet.md +0 -8
- package/build/src/third_party/issue-descriptions/SameSiteWarnStrictLaxDowngradeStrict.md +0 -8
- package/build/src/third_party/issue-descriptions/arInsecureContext.md +0 -7
- package/build/src/third_party/issue-descriptions/arInvalidInfoHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arInvalidRegisterOsSourceHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arInvalidRegisterOsTriggerHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arInvalidRegisterSourceHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arInvalidRegisterTriggerHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arNavigationRegistrationUniqueScopeAlreadySet.md +0 -5
- package/build/src/third_party/issue-descriptions/arNavigationRegistrationWithoutTransientUserActivation.md +0 -6
- package/build/src/third_party/issue-descriptions/arNoRegisterOsSourceHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arNoRegisterOsTriggerHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arNoRegisterSourceHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arNoRegisterTriggerHeader.md +0 -5
- package/build/src/third_party/issue-descriptions/arNoWebOrOsSupport.md +0 -4
- package/build/src/third_party/issue-descriptions/arOsSourceIgnored.md +0 -18
- package/build/src/third_party/issue-descriptions/arOsTriggerIgnored.md +0 -19
- package/build/src/third_party/issue-descriptions/arPermissionPolicyDisabled.md +0 -8
- package/build/src/third_party/issue-descriptions/arSourceAndTriggerHeaders.md +0 -9
- package/build/src/third_party/issue-descriptions/arSourceIgnored.md +0 -13
- package/build/src/third_party/issue-descriptions/arTriggerIgnored.md +0 -12
- package/build/src/third_party/issue-descriptions/arUntrustworthyReportingOrigin.md +0 -10
- package/build/src/third_party/issue-descriptions/arWebAndOsHeaders.md +0 -11
- package/build/src/utils/string.js +0 -37
- package/build/src/utils/types.js +0 -7
package/build/src/tools/pages.js
CHANGED
|
@@ -3,55 +3,10 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
import { logger } from '../logger.js';
|
|
7
6
|
import { zod } from '../third_party/index.js';
|
|
7
|
+
import { logger } from '../utils/logger.js';
|
|
8
8
|
import { ToolCategory } from './categories.js';
|
|
9
9
|
import { CLOSE_PAGE_ERROR, definePageTool, defineTool, timeoutSchema, } from './ToolDefinition.js';
|
|
10
|
-
async function navigateWithInterception(page, action, allowListString, timeout) {
|
|
11
|
-
const allowList = allowListString
|
|
12
|
-
? allowListString.split(',').map((p) => new URLPattern(p.trim()))
|
|
13
|
-
: undefined;
|
|
14
|
-
const requestHandler = (interceptedRequest) => {
|
|
15
|
-
if (!interceptedRequest.isNavigationRequest()) {
|
|
16
|
-
void interceptedRequest.continue();
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
const requestUrl = interceptedRequest.url();
|
|
20
|
-
const isAllowed = allowList.some((pattern) => pattern.test(requestUrl));
|
|
21
|
-
if (isAllowed) {
|
|
22
|
-
void interceptedRequest.continue();
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
logger(`Blocking request to: ${requestUrl}`);
|
|
26
|
-
void interceptedRequest.abort('blockedbyclient');
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
const cleanupInterception = async () => {
|
|
30
|
-
if (allowList) {
|
|
31
|
-
page.pptrPage.off('request', requestHandler);
|
|
32
|
-
await page.pptrPage.setRequestInterception(false).catch(error => {
|
|
33
|
-
logger(`Failed to disable request interception`, error);
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
if (allowList) {
|
|
38
|
-
await page.pptrPage.setRequestInterception(true);
|
|
39
|
-
page.pptrPage.on('request', requestHandler);
|
|
40
|
-
}
|
|
41
|
-
try {
|
|
42
|
-
await page.waitForEventsAfterAction(async () => {
|
|
43
|
-
try {
|
|
44
|
-
await action();
|
|
45
|
-
}
|
|
46
|
-
finally {
|
|
47
|
-
await cleanupInterception();
|
|
48
|
-
}
|
|
49
|
-
}, { timeout });
|
|
50
|
-
}
|
|
51
|
-
finally {
|
|
52
|
-
await cleanupInterception();
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
10
|
export const listPages = defineTool(args => {
|
|
56
11
|
return {
|
|
57
12
|
name: 'list_pages',
|
|
@@ -62,6 +17,7 @@ export const listPages = defineTool(args => {
|
|
|
62
17
|
},
|
|
63
18
|
schema: {},
|
|
64
19
|
blockedByDialog: false,
|
|
20
|
+
verifyFilesSchema: {},
|
|
65
21
|
handler: async (_request, response) => {
|
|
66
22
|
response.setIncludePages(true);
|
|
67
23
|
response.setListThirdPartyDeveloperTools();
|
|
@@ -86,6 +42,7 @@ export const selectPage = defineTool({
|
|
|
86
42
|
.describe('Whether to focus the page and bring it to the top.'),
|
|
87
43
|
},
|
|
88
44
|
blockedByDialog: false,
|
|
45
|
+
verifyFilesSchema: {},
|
|
89
46
|
handler: async (request, response, context) => {
|
|
90
47
|
const page = context.getPageById(request.params.pageId);
|
|
91
48
|
context.selectPage(page);
|
|
@@ -110,6 +67,7 @@ export const closePage = defineTool({
|
|
|
110
67
|
.describe('The ID of the page to close. Call list_pages to list pages.'),
|
|
111
68
|
},
|
|
112
69
|
blockedByDialog: false,
|
|
70
|
+
verifyFilesSchema: {},
|
|
113
71
|
handler: async (request, response, context) => {
|
|
114
72
|
try {
|
|
115
73
|
await context.closePage(request.params.pageId);
|
|
@@ -126,7 +84,7 @@ export const closePage = defineTool({
|
|
|
126
84
|
response.setListThirdPartyDeveloperTools();
|
|
127
85
|
},
|
|
128
86
|
});
|
|
129
|
-
export const newPage = defineTool(
|
|
87
|
+
export const newPage = defineTool(() => {
|
|
130
88
|
return {
|
|
131
89
|
name: 'new_page',
|
|
132
90
|
description: `Open a new tab and load a URL. Use project URL if not specified otherwise.`,
|
|
@@ -146,28 +104,23 @@ export const newPage = defineTool(args => {
|
|
|
146
104
|
.describe('If specified, the page is created in an isolated browser context with the given name. ' +
|
|
147
105
|
'Pages in the same browser context share cookies and storage. ' +
|
|
148
106
|
'Pages in different browser contexts are fully isolated.'),
|
|
149
|
-
...(args?.experimentalNavigationAllowlist
|
|
150
|
-
? {
|
|
151
|
-
allowList: zod
|
|
152
|
-
.string()
|
|
153
|
-
.optional()
|
|
154
|
-
.describe('Optional comma-separated list of URL patterns to allow. If provided, all other navigations will be blocked.'),
|
|
155
|
-
}
|
|
156
|
-
: {}),
|
|
157
107
|
...timeoutSchema,
|
|
158
108
|
},
|
|
159
109
|
blockedByDialog: false,
|
|
110
|
+
verifyFilesSchema: {},
|
|
160
111
|
handler: async (request, response, context) => {
|
|
161
112
|
const page = await context.newPage(request.params.background, request.params.isolatedContext);
|
|
162
|
-
await
|
|
163
|
-
|
|
164
|
-
|
|
113
|
+
await page.waitForEventsAfterAction(async () => {
|
|
114
|
+
await page.pptrPage.goto(request.params.url, {
|
|
115
|
+
timeout: request.params.timeout,
|
|
116
|
+
});
|
|
117
|
+
}, { timeout: request.params.timeout });
|
|
165
118
|
response.setIncludePages(true);
|
|
166
119
|
response.setListThirdPartyDeveloperTools();
|
|
167
120
|
},
|
|
168
121
|
};
|
|
169
122
|
});
|
|
170
|
-
export const navigatePage = definePageTool(
|
|
123
|
+
export const navigatePage = definePageTool(() => {
|
|
171
124
|
return {
|
|
172
125
|
name: 'navigate_page',
|
|
173
126
|
description: `Go to a URL, or back, forward, or reload. Use project URL if not specified otherwise.`,
|
|
@@ -186,24 +139,17 @@ export const navigatePage = definePageTool(args => {
|
|
|
186
139
|
.optional()
|
|
187
140
|
.describe('Whether to ignore cache on reload.'),
|
|
188
141
|
handleBeforeUnload: zod
|
|
189
|
-
.enum(['accept', '
|
|
142
|
+
.enum(['accept', 'dismiss'])
|
|
190
143
|
.optional()
|
|
191
144
|
.describe('Whether to auto accept or beforeunload dialogs triggered by this navigation. Default is accept.'),
|
|
192
145
|
initScript: zod
|
|
193
146
|
.string()
|
|
194
147
|
.optional()
|
|
195
148
|
.describe('A JavaScript script to be executed on each new document before any other scripts for the next navigation.'),
|
|
196
|
-
...(args?.experimentalNavigationAllowlist
|
|
197
|
-
? {
|
|
198
|
-
allowList: zod
|
|
199
|
-
.string()
|
|
200
|
-
.optional()
|
|
201
|
-
.describe('Optional comma-separated list of URL patterns to allow. If provided, all other navigations will be blocked.'),
|
|
202
|
-
}
|
|
203
|
-
: {}),
|
|
204
149
|
...timeoutSchema,
|
|
205
150
|
},
|
|
206
151
|
blockedByDialog: false,
|
|
152
|
+
verifyFilesSchema: {},
|
|
207
153
|
handler: async (request, response) => {
|
|
208
154
|
const page = request.page;
|
|
209
155
|
const options = {
|
|
@@ -215,29 +161,14 @@ export const navigatePage = definePageTool(args => {
|
|
|
215
161
|
if (!request.params.type) {
|
|
216
162
|
request.params.type = 'url';
|
|
217
163
|
}
|
|
218
|
-
const handleBeforeUnload = request.params.handleBeforeUnload ?? 'accept';
|
|
219
|
-
const dialogHandler = (dialog) => {
|
|
220
|
-
if (dialog.type() === 'beforeunload') {
|
|
221
|
-
if (handleBeforeUnload === 'accept') {
|
|
222
|
-
response.appendResponseLine(`Accepted a beforeunload dialog.`);
|
|
223
|
-
void dialog.accept();
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
response.appendResponseLine(`Declined a beforeunload dialog.`);
|
|
227
|
-
void dialog.dismiss();
|
|
228
|
-
}
|
|
229
|
-
// We are not going to report the dialog like regular dialogs.
|
|
230
|
-
page.clearDialog();
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
164
|
let initScriptId;
|
|
234
165
|
if (request.params.initScript) {
|
|
235
166
|
const { identifier } = await page.pptrPage.evaluateOnNewDocument(request.params.initScript);
|
|
236
167
|
initScriptId = identifier;
|
|
237
168
|
}
|
|
238
|
-
page.pptrPage.on('dialog', dialogHandler);
|
|
239
169
|
try {
|
|
240
|
-
|
|
170
|
+
const action = request.params.handleBeforeUnload ?? 'accept';
|
|
171
|
+
const result = await page.waitForEventsAfterAction(async () => {
|
|
241
172
|
switch (request.params.type) {
|
|
242
173
|
case 'url':
|
|
243
174
|
if (!request.params.url) {
|
|
@@ -282,15 +213,21 @@ export const navigatePage = definePageTool(args => {
|
|
|
282
213
|
}
|
|
283
214
|
break;
|
|
284
215
|
}
|
|
285
|
-
},
|
|
216
|
+
}, {
|
|
217
|
+
timeout: request.params.timeout,
|
|
218
|
+
handleDialog: { beforeunload: action },
|
|
219
|
+
});
|
|
220
|
+
if (result.dialogHandled) {
|
|
221
|
+
response.appendResponseLine(`${action === 'dismiss' ? 'Dismissed' : 'Accepted'} a beforeunload dialog.`);
|
|
222
|
+
page.clearDialog();
|
|
223
|
+
}
|
|
286
224
|
}
|
|
287
225
|
finally {
|
|
288
|
-
page.pptrPage.off('dialog', dialogHandler);
|
|
289
226
|
if (initScriptId) {
|
|
290
227
|
await page.pptrPage
|
|
291
228
|
.removeScriptToEvaluateOnNewDocument(initScriptId)
|
|
292
229
|
.catch(error => {
|
|
293
|
-
logger(`Failed to remove init script`, error);
|
|
230
|
+
logger?.(`Failed to remove init script`, error);
|
|
294
231
|
});
|
|
295
232
|
}
|
|
296
233
|
}
|
|
@@ -312,7 +249,8 @@ export const resizePage = definePageTool({
|
|
|
312
249
|
height: zod.number().describe('Page height'),
|
|
313
250
|
},
|
|
314
251
|
blockedByDialog: false,
|
|
315
|
-
|
|
252
|
+
verifyFilesSchema: {},
|
|
253
|
+
handler: async (request, response) => {
|
|
316
254
|
const page = request.page;
|
|
317
255
|
try {
|
|
318
256
|
const browser = page.pptrPage.browser();
|
|
@@ -354,7 +292,8 @@ export const handleDialog = definePageTool({
|
|
|
354
292
|
.describe('Optional prompt text to enter into the dialog.'),
|
|
355
293
|
},
|
|
356
294
|
blockedByDialog: false,
|
|
357
|
-
|
|
295
|
+
verifyFilesSchema: {},
|
|
296
|
+
handler: async (request, response) => {
|
|
358
297
|
const page = request.page;
|
|
359
298
|
const dialog = page.getDialog();
|
|
360
299
|
if (!dialog) {
|
|
@@ -367,7 +306,7 @@ export const handleDialog = definePageTool({
|
|
|
367
306
|
}
|
|
368
307
|
catch (err) {
|
|
369
308
|
// Likely already handled by the user outside of MCP.
|
|
370
|
-
logger(err);
|
|
309
|
+
logger?.(err);
|
|
371
310
|
}
|
|
372
311
|
response.appendResponseLine('Successfully accepted the dialog');
|
|
373
312
|
break;
|
|
@@ -378,7 +317,7 @@ export const handleDialog = definePageTool({
|
|
|
378
317
|
}
|
|
379
318
|
catch (err) {
|
|
380
319
|
// Likely already handled.
|
|
381
|
-
logger(err);
|
|
320
|
+
logger?.(err);
|
|
382
321
|
}
|
|
383
322
|
response.appendResponseLine('Successfully dismissed the dialog');
|
|
384
323
|
break;
|
|
@@ -402,10 +341,12 @@ export const getTabId = definePageTool({
|
|
|
402
341
|
.describe(`The ID of the page to get the tab ID for. Call ${listPages().name} to get available pages.`),
|
|
403
342
|
},
|
|
404
343
|
blockedByDialog: false,
|
|
344
|
+
verifyFilesSchema: {},
|
|
405
345
|
handler: async (request, response, context) => {
|
|
406
346
|
const page = context.getPageById(request.params.pageId);
|
|
407
347
|
const tabId = page.pptrPage._tabId;
|
|
408
348
|
response.setTabId(tabId);
|
|
349
|
+
response.appendResponseLine(`Tab ID: ${tabId}`);
|
|
409
350
|
},
|
|
410
351
|
});
|
|
411
352
|
//# sourceMappingURL=pages.js.map
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import zlib from 'node:zlib';
|
|
7
|
-
import { logger } from '../logger.js';
|
|
8
7
|
import { zod, DevTools } from '../third_party/index.js';
|
|
9
|
-
import { parseRawTraceBuffer, traceResultIsSuccess, } from '../
|
|
8
|
+
import { parseRawTraceBuffer, traceResultIsSuccess, } from '../processors/PerformanceTrace.js';
|
|
9
|
+
import { logger } from '../utils/logger.js';
|
|
10
10
|
import { ToolCategory } from './categories.js';
|
|
11
11
|
import { definePageTool } from './ToolDefinition.js';
|
|
12
12
|
const filePathSchema = zod
|
|
@@ -32,8 +32,10 @@ export const startTrace = definePageTool({
|
|
|
32
32
|
filePath: filePathSchema,
|
|
33
33
|
},
|
|
34
34
|
blockedByDialog: true,
|
|
35
|
+
verifyFilesSchema: {
|
|
36
|
+
filePath: true,
|
|
37
|
+
},
|
|
35
38
|
handler: async (request, response, context) => {
|
|
36
|
-
context.validatePath(request.params.filePath);
|
|
37
39
|
if (context.isRunningPerformanceTrace()) {
|
|
38
40
|
response.appendResponseLine('Error: a performance trace is already running. Use performance_stop_trace to stop it. Only one trace can be running at any given time.');
|
|
39
41
|
return;
|
|
@@ -41,47 +43,54 @@ export const startTrace = definePageTool({
|
|
|
41
43
|
context.setIsRunningPerformanceTrace(true);
|
|
42
44
|
const page = request.page;
|
|
43
45
|
const pageUrlForTracing = page.pptrPage.url();
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
'disabled-by-default-devtools.timeline.frame',
|
|
62
|
-
'disabled-by-default-devtools.timeline.stack',
|
|
63
|
-
'disabled-by-default-v8.cpu_profiler',
|
|
64
|
-
'disabled-by-default-v8.cpu_profiler.hires',
|
|
65
|
-
'latencyInfo',
|
|
66
|
-
'loading',
|
|
67
|
-
'disabled-by-default-lighthouse',
|
|
68
|
-
'v8.execute',
|
|
69
|
-
'v8',
|
|
70
|
-
];
|
|
71
|
-
await page.pptrPage.tracing.start({
|
|
72
|
-
categories,
|
|
73
|
-
});
|
|
74
|
-
if (request.params.reload) {
|
|
75
|
-
await page.pptrPage.goto(pageUrlForTracing, {
|
|
76
|
-
waitUntil: ['load'],
|
|
46
|
+
try {
|
|
47
|
+
if (request.params.reload) {
|
|
48
|
+
// Before starting the recording, navigate to about:blank to clear out any state.
|
|
49
|
+
// We use `load` because `networkidle0` is known to be flaky for about:blank in Puppeteer.
|
|
50
|
+
await page.pptrPage.goto('about:blank', {
|
|
51
|
+
waitUntil: 'load',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const categories = [
|
|
55
|
+
'-*',
|
|
56
|
+
...DevTools.TracingDefaultCategories,
|
|
57
|
+
// These categories are optional in DevTools, but enabled by default in the DevTools UI, so we enable them here too.
|
|
58
|
+
...DevTools.TracingOptionalCategories.JsSampling,
|
|
59
|
+
...DevTools.TracingOptionalCategories.Screenshot,
|
|
60
|
+
];
|
|
61
|
+
await page.pptrPage.tracing.start({
|
|
62
|
+
categories,
|
|
77
63
|
});
|
|
64
|
+
if (request.params.reload) {
|
|
65
|
+
await page.pptrPage.goto(pageUrlForTracing, {
|
|
66
|
+
waitUntil: ['load'],
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (request.params.autoStop) {
|
|
70
|
+
await new Promise(resolve => setTimeout(resolve, 5_000));
|
|
71
|
+
await stopTracingAndAppendOutput(page, response, context, request.params.filePath);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
response.appendResponseLine(`The performance trace is being recorded. Use performance_stop_trace to stop it.`);
|
|
75
|
+
}
|
|
78
76
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
catch (error) {
|
|
78
|
+
// If a setup step (navigation, tracing.start) throws before
|
|
79
|
+
// stopTracingAndAppendOutput runs, the running flag would otherwise stay
|
|
80
|
+
// stuck `true` for the rest of the session, blocking all future traces.
|
|
81
|
+
// Unwind here: stop tracing if it was started and clear the flag. When
|
|
82
|
+
// autoStop already ran stopTracingAndAppendOutput the flag is already
|
|
83
|
+
// false and this is a no-op.
|
|
84
|
+
if (context.isRunningPerformanceTrace()) {
|
|
85
|
+
try {
|
|
86
|
+
await page.pptrPage.tracing.stop();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Tracing may not have started yet; ignore.
|
|
90
|
+
}
|
|
91
|
+
context.setIsRunningPerformanceTrace(false);
|
|
92
|
+
}
|
|
93
|
+
throw error;
|
|
85
94
|
}
|
|
86
95
|
},
|
|
87
96
|
});
|
|
@@ -96,13 +105,15 @@ export const stopTrace = definePageTool({
|
|
|
96
105
|
filePath: filePathSchema,
|
|
97
106
|
},
|
|
98
107
|
blockedByDialog: true,
|
|
108
|
+
verifyFilesSchema: {
|
|
109
|
+
filePath: true,
|
|
110
|
+
},
|
|
99
111
|
handler: async (request, response, context) => {
|
|
100
|
-
context.validatePath(request.params.filePath);
|
|
101
112
|
if (!context.isRunningPerformanceTrace()) {
|
|
102
113
|
return;
|
|
103
114
|
}
|
|
104
115
|
const page = request.page;
|
|
105
|
-
await stopTracingAndAppendOutput(page
|
|
116
|
+
await stopTracingAndAppendOutput(page, response, context, request.params.filePath);
|
|
106
117
|
},
|
|
107
118
|
});
|
|
108
119
|
export const analyzeInsight = definePageTool({
|
|
@@ -121,6 +132,7 @@ export const analyzeInsight = definePageTool({
|
|
|
121
132
|
.describe('The name of the Insight you want more information on. For example: "DocumentLatency" or "LCPBreakdown"'),
|
|
122
133
|
},
|
|
123
134
|
blockedByDialog: false,
|
|
135
|
+
verifyFilesSchema: {},
|
|
124
136
|
handler: async (request, response, context) => {
|
|
125
137
|
const lastRecording = context.recordedTraces().at(-1);
|
|
126
138
|
if (!lastRecording) {
|
|
@@ -132,7 +144,7 @@ export const analyzeInsight = definePageTool({
|
|
|
132
144
|
});
|
|
133
145
|
async function stopTracingAndAppendOutput(page, response, context, filePath) {
|
|
134
146
|
try {
|
|
135
|
-
const traceEventsBuffer = await page.tracing.stop();
|
|
147
|
+
const traceEventsBuffer = await page.pptrPage.tracing.stop();
|
|
136
148
|
if (filePath && traceEventsBuffer) {
|
|
137
149
|
let dataToWrite = traceEventsBuffer;
|
|
138
150
|
if (filePath.endsWith('.gz')) {
|
|
@@ -147,10 +159,13 @@ async function stopTracingAndAppendOutput(page, response, context, filePath) {
|
|
|
147
159
|
});
|
|
148
160
|
});
|
|
149
161
|
}
|
|
150
|
-
const file = await context.saveFile(dataToWrite, filePath, filePath.endsWith('.gz') ? '.
|
|
162
|
+
const file = await context.saveFile(dataToWrite, filePath, filePath.endsWith('.gz') ? '.gz' : '.json');
|
|
151
163
|
response.appendResponseLine(`The raw trace data was saved to ${file.filename}.`);
|
|
152
164
|
}
|
|
153
|
-
const result = await parseRawTraceBuffer(traceEventsBuffer
|
|
165
|
+
const result = await parseRawTraceBuffer(traceEventsBuffer, {
|
|
166
|
+
cpuThrottling: page.cpuThrottlingRate,
|
|
167
|
+
networkThrottling: page.networkConditions ?? undefined,
|
|
168
|
+
});
|
|
154
169
|
response.appendResponseLine('The performance trace has been stopped.');
|
|
155
170
|
if (traceResultIsSuccess(result)) {
|
|
156
171
|
if (context.isCruxEnabled()) {
|
|
@@ -169,8 +184,8 @@ async function stopTracingAndAppendOutput(page, response, context, filePath) {
|
|
|
169
184
|
}
|
|
170
185
|
/** We tell CrUXManager to fetch data so it's available when DevTools.PerformanceTraceFormatter is invoked */
|
|
171
186
|
async function populateCruxData(result) {
|
|
172
|
-
logger('populateCruxData called');
|
|
173
|
-
const cruxManager = DevTools.CrUXManager.instance();
|
|
187
|
+
logger?.('populateCruxData called');
|
|
188
|
+
const cruxManager = DevTools.CrUXManager.CrUXManager.instance();
|
|
174
189
|
// go/jtfbx. Yes, we're aware this API key is public. ;)
|
|
175
190
|
cruxManager.setEndpointForTesting('https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=AIzaSyBn5gimNjhiEyA_euicSKko6IlD3HdgUfk');
|
|
176
191
|
const cruxSetting = DevTools.Common.Settings.Settings.instance().createSetting('field-data', {
|
|
@@ -182,13 +197,13 @@ async function populateCruxData(result) {
|
|
|
182
197
|
urls.push(result.parsedTrace.data.Meta.mainFrameURL);
|
|
183
198
|
const urlSet = new Set(urls);
|
|
184
199
|
if (urlSet.size === 0) {
|
|
185
|
-
logger('No URLs found for CrUX data');
|
|
200
|
+
logger?.('No URLs found for CrUX data');
|
|
186
201
|
return;
|
|
187
202
|
}
|
|
188
|
-
logger(`Fetching CrUX data for ${urlSet.size} URLs: ${Array.from(urlSet).join(', ')}`);
|
|
203
|
+
logger?.(`Fetching CrUX data for ${urlSet.size} URLs: ${Array.from(urlSet).join(', ')}`);
|
|
189
204
|
const cruxData = await Promise.all(Array.from(urlSet).map(async (url) => {
|
|
190
205
|
const data = await cruxManager.getFieldDataForPage(url);
|
|
191
|
-
logger(`CrUX data for ${url}: ${data ? 'found' : 'not found'}`);
|
|
206
|
+
logger?.(`CrUX data for ${url}: ${data ? 'found' : 'not found'}`);
|
|
192
207
|
return data;
|
|
193
208
|
}));
|
|
194
209
|
result.parsedTrace.metadata.cruxFieldData = cruxData;
|
|
@@ -0,0 +1,128 @@
|
|
|
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 manifestIdSchema = zod
|
|
10
|
+
.string()
|
|
11
|
+
.describe('The manifest ID of the web app: the resolved `id` member of its manifest. ' +
|
|
12
|
+
'If `id` is omitted, it defaults to the resolved `start_url` ' +
|
|
13
|
+
'(e.g. "https://example.com/"). See https://w3c.github.io/manifest/#id-member.');
|
|
14
|
+
const displayModeSchema = zod
|
|
15
|
+
.enum(['standalone', 'browser'])
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Optional user display mode preference applied after install. ' +
|
|
18
|
+
'"standalone" opens the app in its own window; "browser" opens it as a ' +
|
|
19
|
+
'tab. Installs via the PWA CDP domain default to "browser" because they ' +
|
|
20
|
+
'do not simulate the install dialog, so pass "standalone" to get an ' +
|
|
21
|
+
'app-window experience.');
|
|
22
|
+
export const installPwa = defineTool({
|
|
23
|
+
name: 'install_pwa',
|
|
24
|
+
description: 'Installs a Progressive Web App (PWA) identified by its manifest ID. ' +
|
|
25
|
+
'This installs through the PWA CDP domain without a user gesture or install ' +
|
|
26
|
+
'dialog. DevTools installs default to browser display mode.',
|
|
27
|
+
annotations: {
|
|
28
|
+
category: ToolCategory.PWA,
|
|
29
|
+
readOnlyHint: false,
|
|
30
|
+
},
|
|
31
|
+
schema: {
|
|
32
|
+
manifestId: manifestIdSchema,
|
|
33
|
+
installUrlOrBundleUrl: zod
|
|
34
|
+
.string()
|
|
35
|
+
.describe('The location of the app or bundle. For a normal site this is the page ' +
|
|
36
|
+
'URL; for an Isolated Web App it can be a file:// or http(s):// ' +
|
|
37
|
+
'signed web bundle.'),
|
|
38
|
+
displayMode: displayModeSchema,
|
|
39
|
+
},
|
|
40
|
+
blockedByDialog: false,
|
|
41
|
+
// We do not validate file paths for remote browser instances
|
|
42
|
+
// because they are on the remote host and not accessed by the MCP server.
|
|
43
|
+
verifyFilesSchema: {
|
|
44
|
+
installUrlOrBundleUrl: {
|
|
45
|
+
local: true,
|
|
46
|
+
remote: false,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
handler: async (request, response, context) => {
|
|
50
|
+
const { manifestId, installUrlOrBundleUrl, displayMode } = request.params;
|
|
51
|
+
await context.installPWA({
|
|
52
|
+
manifestId,
|
|
53
|
+
installUrlOrBundleUrl,
|
|
54
|
+
displayMode,
|
|
55
|
+
});
|
|
56
|
+
response.appendResponseLine(`Installed PWA with manifest ID: ${manifestId}`);
|
|
57
|
+
if (displayMode) {
|
|
58
|
+
response.appendResponseLine(`Display mode set to: ${displayMode}`);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
export const uninstallPwa = defineTool({
|
|
63
|
+
name: 'uninstall_pwa',
|
|
64
|
+
description: 'Uninstalls a Progressive Web App identified by its manifest ID and ' +
|
|
65
|
+
'closes any open app windows.',
|
|
66
|
+
annotations: {
|
|
67
|
+
category: ToolCategory.PWA,
|
|
68
|
+
readOnlyHint: false,
|
|
69
|
+
},
|
|
70
|
+
schema: {
|
|
71
|
+
manifestId: manifestIdSchema,
|
|
72
|
+
},
|
|
73
|
+
blockedByDialog: false,
|
|
74
|
+
verifyFilesSchema: {},
|
|
75
|
+
handler: async (request, response, context) => {
|
|
76
|
+
const { manifestId } = request.params;
|
|
77
|
+
await context.uninstallPWA({ manifestId });
|
|
78
|
+
response.appendResponseLine(`Uninstalled PWA with manifest ID: ${manifestId}`);
|
|
79
|
+
response.setIncludePages(true);
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
export const launchPwa = defineTool({
|
|
83
|
+
name: 'launch_pwa',
|
|
84
|
+
description: 'Launches an installed Progressive Web App using its saved display mode. ' +
|
|
85
|
+
'Optionally opens a specific URL within the same app instead of the default ' +
|
|
86
|
+
'start URL.',
|
|
87
|
+
annotations: {
|
|
88
|
+
category: ToolCategory.PWA,
|
|
89
|
+
readOnlyHint: false,
|
|
90
|
+
},
|
|
91
|
+
schema: {
|
|
92
|
+
manifestId: manifestIdSchema,
|
|
93
|
+
url: zod
|
|
94
|
+
.string()
|
|
95
|
+
.optional()
|
|
96
|
+
.describe('Optional URL within the app to open instead of the default start URL.'),
|
|
97
|
+
},
|
|
98
|
+
blockedByDialog: false,
|
|
99
|
+
verifyFilesSchema: {},
|
|
100
|
+
handler: async (request, response, context) => {
|
|
101
|
+
const { manifestId, url } = request.params;
|
|
102
|
+
const page = await context.launchPWA({ manifestId, url });
|
|
103
|
+
response.appendResponseLine(`Launched PWA with manifest ID: ${manifestId} (${page.url()})`);
|
|
104
|
+
response.setIncludePages(true);
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
export const getOsAppState = defineTool({
|
|
108
|
+
name: 'get_os_app_state',
|
|
109
|
+
description: 'Returns the OS integration state (badge count and registered file ' +
|
|
110
|
+
'handlers) for an installed web app, identified by its manifest ID.',
|
|
111
|
+
annotations: {
|
|
112
|
+
category: ToolCategory.PWA,
|
|
113
|
+
readOnlyHint: true,
|
|
114
|
+
},
|
|
115
|
+
schema: {
|
|
116
|
+
manifestId: manifestIdSchema,
|
|
117
|
+
},
|
|
118
|
+
blockedByDialog: false,
|
|
119
|
+
verifyFilesSchema: {},
|
|
120
|
+
handler: async (request, response, context) => {
|
|
121
|
+
const { manifestId } = request.params;
|
|
122
|
+
const state = await context.getPWAState({ manifestId });
|
|
123
|
+
response.appendResponseLine(`OS app state for manifest ID: ${manifestId}`);
|
|
124
|
+
response.appendResponseLine(`Badge count: ${state.badgeCount}`);
|
|
125
|
+
response.appendResponseLine(`File handlers: ${JSON.stringify(state.fileHandlers)}`);
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
//# sourceMappingURL=pwa.js.map
|
|
@@ -7,7 +7,6 @@ import fs from 'node:fs/promises';
|
|
|
7
7
|
import os from 'node:os';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { zod } from '../third_party/index.js';
|
|
10
|
-
import { ensureExtension } from '../utils/files.js';
|
|
11
10
|
import { ToolCategory } from './categories.js';
|
|
12
11
|
import { definePageTool } from './ToolDefinition.js';
|
|
13
12
|
async function generateTempFilePath() {
|
|
@@ -30,23 +29,32 @@ export const startScreencast = definePageTool(args => ({
|
|
|
30
29
|
.describe(`Output file path (${supportedExtensions.join(',')} are supported). Uses mkdtemp to generate a unique path if not provided.`),
|
|
31
30
|
},
|
|
32
31
|
blockedByDialog: false,
|
|
32
|
+
verifyFilesSchema: {
|
|
33
|
+
filePath: true,
|
|
34
|
+
},
|
|
33
35
|
handler: async (request, response, context) => {
|
|
34
|
-
context.validatePath(request.params.filePath);
|
|
35
36
|
if (context.getScreenRecorder() !== null) {
|
|
36
37
|
response.appendResponseLine('Error: a screencast recording is already in progress. Use screencast_stop to stop it before starting a new one.');
|
|
37
38
|
return;
|
|
38
39
|
}
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
const requestedFilePath = request.params.filePath;
|
|
41
|
+
const filePath = requestedFilePath ?? (await generateTempFilePath());
|
|
42
|
+
// Match the extension case-insensitively so e.g. `.WEBM` is recognized as
|
|
43
|
+
// WebM. An explicitly requested but unsupported extension is rejected
|
|
44
|
+
// rather than being silently rewritten to `.mp4` (which would change both
|
|
45
|
+
// the format and the output path from what was requested). A missing
|
|
46
|
+
// extension falls back to `.mp4`. The matched extension is normalized to
|
|
47
|
+
// lower case.
|
|
48
|
+
const requestedExtension = path.extname(filePath);
|
|
49
|
+
const matchedExtension = supportedExtensions.find(supportedExtension => supportedExtension === requestedExtension.toLowerCase());
|
|
50
|
+
if (!matchedExtension && requestedExtension !== '') {
|
|
51
|
+
throw new Error(`Unsupported screencast file extension "${requestedExtension}". ` +
|
|
52
|
+
`Supported formats: ${supportedExtensions.join(', ')} (case-insensitive).`);
|
|
48
53
|
}
|
|
49
|
-
const
|
|
54
|
+
const enforcedExtension = matchedExtension ?? '.mp4';
|
|
55
|
+
const format = (matchedExtension?.substring(1) ??
|
|
56
|
+
'mp4');
|
|
57
|
+
const resolvedPath = await context.ensureExtension(filePath, enforcedExtension);
|
|
50
58
|
const page = request.page;
|
|
51
59
|
let recorder;
|
|
52
60
|
try {
|
|
@@ -57,6 +65,19 @@ export const startScreencast = definePageTool(args => ({
|
|
|
57
65
|
});
|
|
58
66
|
}
|
|
59
67
|
catch (err) {
|
|
68
|
+
// If we generated a temporary directory for this recording, remove it so
|
|
69
|
+
// a failed start (e.g. ffmpeg missing) does not leak an empty directory.
|
|
70
|
+
if (requestedFilePath === undefined) {
|
|
71
|
+
try {
|
|
72
|
+
await fs.rm(path.dirname(resolvedPath), {
|
|
73
|
+
recursive: true,
|
|
74
|
+
force: true,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// no-op
|
|
79
|
+
}
|
|
80
|
+
}
|
|
60
81
|
const message = err instanceof Error ? err.message : String(err);
|
|
61
82
|
if (message.includes('ENOENT') && message.includes('ffmpeg')) {
|
|
62
83
|
throw new Error('ffmpeg is required for screencast recording but was not found. ' +
|
|
@@ -78,9 +99,11 @@ export const stopScreencast = definePageTool({
|
|
|
78
99
|
},
|
|
79
100
|
schema: {},
|
|
80
101
|
blockedByDialog: false,
|
|
102
|
+
verifyFilesSchema: {},
|
|
81
103
|
handler: async (_request, response, context) => {
|
|
82
104
|
const data = context.getScreenRecorder();
|
|
83
105
|
if (!data) {
|
|
106
|
+
response.appendResponseLine('Error: no active screencast recording to stop.');
|
|
84
107
|
return;
|
|
85
108
|
}
|
|
86
109
|
try {
|