diff --git a/config/gni/devtools_grd_files.gni b/config/gni/devtools_grd_files.gni index 93da3dd9ef..1d1221debe 100644 --- a/config/gni/devtools_grd_files.gni +++ b/config/gni/devtools_grd_files.gni @@ -847,6 +847,7 @@ grd_files_debug_sources = [ "front_end/panels/application/components/InterestGroupAccessGrid.js", "front_end/panels/application/components/OriginTrialTreeView.js", "front_end/panels/application/components/PermissionsPolicySection.js", + "front_end/panels/application/components/ProtocolHandlersView.js", "front_end/panels/application/components/ReportsGrid.js", "front_end/panels/application/components/StackTrace.js", "front_end/panels/application/components/TrustTokensView.js", @@ -857,6 +858,7 @@ grd_files_debug_sources = [ "front_end/panels/application/components/originTrialTokenRows.css.js", "front_end/panels/application/components/originTrialTreeView.css.js", "front_end/panels/application/components/permissionsPolicySection.css.js", + "front_end/panels/application/components/protocolHandlersView.css.js", "front_end/panels/application/components/reportingApiGrid.css.js", "front_end/panels/application/components/stackTraceLinkButton.css.js", "front_end/panels/application/components/stackTraceRow.css.js", diff --git a/front_end/core/host/UserMetrics.ts b/front_end/core/host/UserMetrics.ts index 6e6095bd41..ca9e5fd0e2 100644 --- a/front_end/core/host/UserMetrics.ts +++ b/front_end/core/host/UserMetrics.ts @@ -345,7 +345,8 @@ export enum Action { PerfPanelTraceImported = 54, PerfPanelTraceExported = 55, StackFrameRestarted = 56, - MaxValue = 57, + CaptureTestProtocolClicked = 57, + MaxValue = 58, } /* eslint-disable @typescript-eslint/naming-convention */ diff --git a/front_end/core/i18n/locales/en-US.json b/front_end/core/i18n/locales/en-US.json index cca4541cde..c474b5008f 100644 --- a/front_end/core/i18n/locales/en-US.json +++ b/front_end/core/i18n/locales/en-US.json @@ -2426,6 +2426,9 @@ "panels/application/AppManifestView.ts | primaryManifestIconFromS": { "message": "Primary manifest icon from {PH1}" }, + "panels/application/AppManifestView.ts | protocolHandlers": { + "message": "Protocol Handlers" + }, "panels/application/AppManifestView.ts | screenshot": { "message": "Screenshot" }, @@ -3197,6 +3200,24 @@ "panels/application/components/PermissionsPolicySection.ts | showDetails": { "message": "Show details" }, + "panels/application/components/ProtocolHandlersView.ts | manifest": { + "message": "manifest" + }, + "panels/application/components/ProtocolHandlersView.ts | needHelpReadOur": { + "message": "Need help? Read {PH1}." + }, + "panels/application/components/ProtocolHandlersView.ts | protocolDetected": { + "message": "Found valid protocol handler registration in the {PH1}. With the app installed, test the registered protocols." + }, + "panels/application/components/ProtocolHandlersView.ts | protocolHandlerRegistrations": { + "message": "URL protocol handler registration for PWAs" + }, + "panels/application/components/ProtocolHandlersView.ts | protocolNotDetected": { + "message": "Define protocol handlers in the {PH1} to register your app as a handler for custom protocols when your app is installed." + }, + "panels/application/components/ProtocolHandlersView.ts | testProtocol": { + "message": "Test protocol" + }, "panels/application/components/ReportsGrid.ts | destination": { "message": "Destination" }, diff --git a/front_end/core/i18n/locales/en-XL.json b/front_end/core/i18n/locales/en-XL.json index 89ad5bc0d3..7a4c928121 100644 --- a/front_end/core/i18n/locales/en-XL.json +++ b/front_end/core/i18n/locales/en-XL.json @@ -2426,6 +2426,9 @@ "panels/application/AppManifestView.ts | primaryManifestIconFromS": { "message": "P̂ŕîḿâŕŷ ḿâńîf́êśt̂ íĉón̂ f́r̂óm̂ {PH1}" }, + "panels/application/AppManifestView.ts | protocolHandlers": { + "message": "P̂ŕôt́ôćôĺ Ĥán̂d́l̂ér̂ś" + }, "panels/application/AppManifestView.ts | screenshot": { "message": "Ŝćr̂éêńŝh́ôt́" }, @@ -3197,6 +3200,24 @@ "panels/application/components/PermissionsPolicySection.ts | showDetails": { "message": "Ŝh́ôẃ d̂ét̂áîĺŝ" }, + "panels/application/components/ProtocolHandlersView.ts | manifest": { + "message": "m̂án̂íf̂éŝt́" + }, + "panels/application/components/ProtocolHandlersView.ts | needHelpReadOur": { + "message": "N̂éêd́ ĥél̂ṕ? R̂éâd́ {PH1}." + }, + "panels/application/components/ProtocolHandlersView.ts | protocolDetected": { + "message": "F̂óûńd̂ v́âĺîd́ p̂ŕôt́ôćôĺ ĥán̂d́l̂ér̂ ŕêǵîśt̂ŕât́îón̂ ín̂ t́ĥé {PH1}. Ŵít̂h́ t̂h́ê áp̂ṕ îńŝt́âĺl̂éd̂, t́êśt̂ t́ĥé r̂éĝíŝt́êŕêd́ p̂ŕôt́ôćôĺŝ." + }, + "panels/application/components/ProtocolHandlersView.ts | protocolHandlerRegistrations": { + "message": "ÛŔL̂ ṕr̂ót̂óĉól̂ h́âńd̂ĺêŕ r̂éĝíŝt́r̂át̂íôń f̂ór̂ ṔŴÁŝ" + }, + "panels/application/components/ProtocolHandlersView.ts | protocolNotDetected": { + "message": "D̂éf̂ín̂é p̂ŕôt́ôćôĺ ĥán̂d́l̂ér̂ś îń t̂h́ê {PH1} t́ô ŕêǵîśt̂ér̂ ýôúr̂ áp̂ṕ âś â h́âńd̂ĺêŕ f̂ór̂ ćûśt̂óm̂ ṕr̂ót̂óĉól̂ś ŵh́êń ŷóûŕ âṕp̂ íŝ ín̂śt̂ál̂ĺêd́." + }, + "panels/application/components/ProtocolHandlersView.ts | testProtocol": { + "message": "T̂éŝt́ p̂ŕôt́ôćôĺ" + }, "panels/application/components/ReportsGrid.ts | destination": { "message": "D̂éŝt́îńât́îón̂" }, diff --git a/front_end/panels/application/AppManifestView.ts b/front_end/panels/application/AppManifestView.ts index 310f7e2ada..3b290acf35 100644 --- a/front_end/panels/application/AppManifestView.ts +++ b/front_end/panels/application/AppManifestView.ts @@ -15,6 +15,7 @@ import * as Components from '../../ui/legacy/components/utils/utils.js'; import * as UI from '../../ui/legacy/legacy.js'; import type * as Protocol from '../../generated/protocol.js'; import * as IconButton from '../../ui/components/icon_button/icon_button.js'; +import * as ApplicationComponents from './components/components.js'; const UIStrings = { /** @@ -44,6 +45,10 @@ const UIStrings = { /** *@description Text in App Manifest View of the Application panel */ + protocolHandlers: 'Protocol Handlers', + /** + *@description Text in App Manifest View of the Application panel + */ icons: 'Icons', /** *@description Text for the name of something @@ -423,6 +428,7 @@ export class AppManifestView extends UI.Widget.VBox implements SDK.TargetManager private target?: SDK.Target.Target; private resourceTreeModel?: SDK.ResourceTreeModel.ResourceTreeModel|null; private serviceWorkerManager?: SDK.ServiceWorkerManager.ServiceWorkerManager|null; + private protocolHandlersView: ApplicationComponents.ProtocolHandlersView.ProtocolHandlersView; constructor() { super(true); @@ -448,8 +454,10 @@ export class AppManifestView extends UI.Widget.VBox implements SDK.TargetManager this.errorsSection = this.reportView.appendSection(i18nString(UIStrings.errorsAndWarnings)); this.installabilitySection = this.reportView.appendSection(i18nString(UIStrings.installability)); this.identitySection = this.reportView.appendSection(i18nString(UIStrings.identity)); - this.presentationSection = this.reportView.appendSection(i18nString(UIStrings.presentation)); + const protocolHandlersSection = this.reportView.appendSection(i18nString(UIStrings.protocolHandlers)); + this.protocolHandlersView = new ApplicationComponents.ProtocolHandlersView.ProtocolHandlersView(); + protocolHandlersSection.contentElement.append(this.protocolHandlersView); this.iconsSection = this.reportView.appendSection(i18nString(UIStrings.icons), 'report-section-icons'); this.shortcutSections = []; this.screenshotsSections = []; @@ -701,6 +709,9 @@ export class AppManifestView extends UI.Widget.VBox implements SDK.TargetManager this.newNoteUrlField.appendChild(link); } + const protocolHandlers = parsedManifest['protocol_handlers'] || []; + this.protocolHandlersView.data = {protocolHandlers, manifestLink: url}; + const icons = parsedManifest['icons'] || []; this.iconsSection.clearContent(); diff --git a/front_end/panels/application/components/BUILD.gn b/front_end/panels/application/components/BUILD.gn index f565f83ada..a095abab6d 100644 --- a/front_end/panels/application/components/BUILD.gn +++ b/front_end/panels/application/components/BUILD.gn @@ -16,6 +16,7 @@ generate_css("css_files") { "originTrialTokenRows.css", "originTrialTreeView.css", "permissionsPolicySection.css", + "protocolHandlersView.css", "reportingApiGrid.css", "stackTraceLinkButton.css", "stackTraceRow.css", @@ -33,6 +34,7 @@ devtools_module("components") { "InterestGroupAccessGrid.ts", "OriginTrialTreeView.ts", "PermissionsPolicySection.ts", + "ProtocolHandlersView.ts", "ReportsGrid.ts", "StackTrace.ts", "TrustTokensView.ts", @@ -54,6 +56,7 @@ devtools_module("components") { "../../../ui/components/expandable_list:bundle", "../../../ui/components/icon_button:bundle", "../../../ui/components/report_view:bundle", + "../../../ui/components/text_prompt:bundle", "../../../ui/components/tree_outline:bundle", "../../../ui/legacy:bundle", "../../../ui/legacy/components/source_frame:bundle", diff --git a/front_end/panels/application/components/ProtocolHandlersView.ts b/front_end/panels/application/components/ProtocolHandlersView.ts new file mode 100644 index 0000000000..a02bb63584 --- /dev/null +++ b/front_end/panels/application/components/ProtocolHandlersView.ts @@ -0,0 +1,182 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import * as Host from '../../../core/host/host.js'; +import * as i18n from '../../../core/i18n/i18n.js'; +import * as Platform from '../../../core/platform/platform.js'; +import * as Buttons from '../../../ui/components/buttons/buttons.js'; +import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js'; +import * as IconButton from '../../../ui/components/icon_button/icon_button.js'; +import * as Input from '../../../ui/components/input/input.js'; +// inspectorCommonStyles is imported for the chrome-select class that is used for the dropdown +// eslint-disable-next-line rulesdir/es_modules_import +import inspectorCommonStyles from '../../../ui/legacy/inspectorCommon.css.js'; +import * as UI from '../../../ui/legacy/legacy.js'; +import * as LitHtml from '../../../ui/lit-html/lit-html.js'; + +import protocolHandlersViewStyles from './protocolHandlersView.css.js'; + +const PROTOCOL_DOCUMENT_URL = 'https://web.dev/url-protocol-handler/'; +const UIStrings = { + /** + *@description Status message for when protocol handlers are detected in the manifest + *@example {protocolhandler/manifest.json} PH1 + */ + protocolDetected: + 'Found valid protocol handler registration in the {PH1}. With the app installed, test the registered protocols.', + /** + *@description Status message for when protocol handlers are not detected in the manifest + *@example {protocolhandler/manifest.json} PH1 + */ + protocolNotDetected: + 'Define protocol handlers in the {PH1} to register your app as a handler for custom protocols when your app is installed.', + /** + *@description Text wrapping a link pointing to more information on handling protocol handlers + *@example {https://example.com/} PH1 + */ + needHelpReadOur: 'Need help? Read {PH1}.', + /** + *@description Link text for more information on URL protocol handler registrations for PWAs + */ + protocolHandlerRegistrations: 'URL protocol handler registration for PWAs', + /** + *@description In text hyperlink to the PWA manifest + */ + manifest: 'manifest', + /** + *@description Text for test protocol button + */ + testProtocol: 'Test protocol', +}; + +const str_ = i18n.i18n.registerUIStrings('panels/application/components/ProtocolHandlersView.ts', UIStrings); +const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); + +interface HTMLSelectElementEvent extends Event { + target: HTMLSelectElement; +} + +interface HTMLInputElementEvent extends Event { + target: HTMLInputElement; +} + +export interface ProtocolHandler { + protocol: string; + url: string; +} + +export interface ProtocolHandlersData { + protocolHandlers: ProtocolHandler[]; + manifestLink: Platform.DevToolsPath.UrlString; +} + +export class ProtocolHandlersView extends HTMLElement { + static readonly litTagName = LitHtml.literal`devtools-protocol-handlers-view`; + readonly #shadow = this.attachShadow({mode: 'open'}); + #protocolHandlers: ProtocolHandler[] = []; + #manifestLink: Platform.DevToolsPath.UrlString = Platform.DevToolsPath.EmptyUrlString; + #selectedProtocolState: string = ''; + #queryInputState: string = ''; + + set data(data: ProtocolHandlersData) { + const isNewManifest = this.#manifestLink !== data.manifestLink; + this.#protocolHandlers = data.protocolHandlers; + this.#manifestLink = data.manifestLink; + if (isNewManifest) { + this.#update(); + } + } + + #update(): void { + this.#queryInputState = ''; + this.#selectedProtocolState = this.#protocolHandlers[0]?.protocol ?? ''; + this.#render(); + } + + #renderStatusMessage(): LitHtml.TemplateResult { + const manifestInTextLink = UI.XLink.XLink.create(this.#manifestLink, i18nString(UIStrings.manifest)); + const statusString = this.#protocolHandlers.length > 0 ? UIStrings.protocolDetected : UIStrings.protocolNotDetected; + const iconData: IconButton.Icon.IconData = { + iconName: this.#protocolHandlers.length > 0 ? 'ic_checkmark_16x16' : 'ic_info_black_18dp', + color: this.#protocolHandlers.length > 0 ? 'var( --color-ic-file-image)' : 'var(--color-link)', + width: '16px', + }; + return LitHtml.html` +