diff --git a/devtools_grd_files.gni b/devtools_grd_files.gni index f169c5bea7..9801cc477a 100644 --- a/devtools_grd_files.gni +++ b/devtools_grd_files.gni @@ -56,6 +56,7 @@ grd_files_release_sources = [ "front_end/Images/flex-justify-content-space-around-icon.svg", "front_end/Images/flex-justify-content-space-between-icon.svg", "front_end/Images/flex-justify-content-space-evenly-icon.svg", + "front_end/Images/ic_checkmark_16x16.svg", "front_end/Images/ic_delete_filter.svg", "front_end/Images/ic_delete_list.svg", "front_end/Images/ic_info_black_18dp.svg", diff --git a/devtools_image_files.gni b/devtools_image_files.gni index 1444305274..e5d6a8021e 100644 --- a/devtools_image_files.gni +++ b/devtools_image_files.gni @@ -51,6 +51,7 @@ devtools_image_files = [ "flex-align-self-flex-start-icon.svg", "flex-align-self-stretch-icon.svg", "baseline-icon.svg", + "ic_checkmark_16x16.svg", "ic_info_black_18dp.svg", "ic_page_next_16x16_icon.svg", "ic_page_prev_16x16_icon.svg", diff --git a/front_end/Images/ic_checkmark_16x16.svg b/front_end/Images/ic_checkmark_16x16.svg new file mode 100644 index 0000000000..f306be060a --- /dev/null +++ b/front_end/Images/ic_checkmark_16x16.svg @@ -0,0 +1,3 @@ + + + diff --git a/front_end/network/BUILD.gn b/front_end/network/BUILD.gn index 4f56380622..014caecba9 100644 --- a/front_end/network/BUILD.gn +++ b/front_end/network/BUILD.gn @@ -43,6 +43,7 @@ devtools_module("network") { "../components:bundle", "../cookie_table:bundle", "../data_grid:bundle", + "../elements:bundle", "../har_importer:bundle", "../host:bundle", "../mobile_throttling:bundle", diff --git a/front_end/network/RequestTrustTokensView.ts b/front_end/network/RequestTrustTokensView.ts index db0c9d6a51..4f28df190c 100644 --- a/front_end/network/RequestTrustTokensView.ts +++ b/front_end/network/RequestTrustTokensView.ts @@ -65,6 +65,10 @@ export class RequestTrustTokensReport extends HTMLElement { // clang-format off LitHtml.render(LitHtml.html` ${this.renderParameterSection()} + ${this.renderResultSection()} `, this.shadow); // clang-format on @@ -89,16 +107,14 @@ export class RequestTrustTokensReport extends HTMLElement { if (!this.trustTokenData || !this.trustTokenData.params) { return LitHtml.nothing; } - // Disabled until https://crbug.com/1079231 is fixed. - // clang-format off + return LitHtml.html` ${renderRowWithCodeValue(ls`Type`, this.trustTokenData.params.type.toString())} ${this.renderRefreshPolicy(this.trustTokenData.params)} ${this.renderIssuers(this.trustTokenData.params)} - - `; - // clang-format on + ${this.renderIssuerAndTopLevelOriginFromResult()} + `; } private renderRefreshPolicy(params: Protocol.Network.TrustTokenParams): LitHtml.TemplateResult|{} { @@ -113,30 +129,126 @@ export class RequestTrustTokensReport extends HTMLElement { return LitHtml.nothing; } - // Disabled until https://crbug.com/1079231 is fixed. - // clang-format off return LitHtml.html` ${ls`Issuers`} - - `; - // clang-format on + `; + } + + // The issuer and top level origin are technically parameters but reported in the + // result structure due to the timing when they are calculated in the backend. + // Nonetheless, we show them as part of the parameter section. + private renderIssuerAndTopLevelOriginFromResult(): LitHtml.TemplateResult|{} { + if (!this.trustTokenData || !this.trustTokenData.result) { + return LitHtml.nothing; + } + + return LitHtml.html` + ${renderSimpleRowIfValuePresent(ls`Top level origin`, this.trustTokenData.result.topLevelOrigin)} + ${renderSimpleRowIfValuePresent(ls`Issuer`, this.trustTokenData.result.issuerOrigin)}`; + } + + private renderResultSection(): LitHtml.TemplateResult|{} { + if (!this.trustTokenData || !this.trustTokenData.result) { + return LitHtml.nothing; + } + return LitHtml.html` + + + ${ls`Status`} +
+ + +
+ ${getSimplifiedStatusTextForStatusCode(this.trustTokenData.result.status)} + ${getDetailedTextForStatusCode(this.trustTokenData.result.status)} +
+
+
+ ${this.renderIssuedTokenCount(this.trustTokenData.result)} +
`; + } + + private renderIssuedTokenCount(result: Protocol.Network.TrustTokenOperationDoneEvent): LitHtml.TemplateResult|{} { + if (result.type !== Protocol.Network.TrustTokenOperationType.Issuance) { + return LitHtml.nothing; + } + return renderSimpleRowIfValuePresent(ls`Number of issued tokens`, result.issuedTokenCount); } } +const SUCCESS_ICON_DATA: Components.Icon.IconWithName = { + color: 'rgb(12, 164, 12)', + iconName: 'ic_checkmark_16x16', + width: '12px', +}; + +const FAILURE_ICON_DATA: Components.Icon.IconWithName = { + color: '', + iconName: 'error_icon', + width: '12px', +}; + +function statusConsideredSuccess(status: Protocol.Network.TrustTokenOperationDoneEventStatus): boolean { + return status === Protocol.Network.TrustTokenOperationDoneEventStatus.Ok || + status === Protocol.Network.TrustTokenOperationDoneEventStatus.AlreadyExists || + status === Protocol.Network.TrustTokenOperationDoneEventStatus.FulfilledLocally; +} + +function getIconForStatusCode(status: Protocol.Network.TrustTokenOperationDoneEventStatus): + Components.Icon.IconWithName { + return statusConsideredSuccess(status) ? SUCCESS_ICON_DATA : FAILURE_ICON_DATA; +} + +function getSimplifiedStatusTextForStatusCode(status: Protocol.Network.TrustTokenOperationDoneEventStatus): string { + return statusConsideredSuccess(status) ? ls`Success` : ls`Failure`; +} + +function getDetailedTextForStatusCode(status: Protocol.Network.TrustTokenOperationDoneEventStatus): string|null { + switch (status) { + case Protocol.Network.TrustTokenOperationDoneEventStatus.Ok: + return null; + case Protocol.Network.TrustTokenOperationDoneEventStatus.AlreadyExists: + return ls`The operations result was served from cache.`; + case Protocol.Network.TrustTokenOperationDoneEventStatus.FulfilledLocally: + return ls`The operation was fulfilled locally, no request was sent.`; + case Protocol.Network.TrustTokenOperationDoneEventStatus.InvalidArgument: + return ls`A client-provided argument was malformed or otherwise invalid.`; + case Protocol.Network.TrustTokenOperationDoneEventStatus.ResourceExhausted: + return ls`Either no inputs for this operation are available or the output exceeds the operations quota.`; + case Protocol.Network.TrustTokenOperationDoneEventStatus.BadResponse: + return ls`The servers response was malformed or otherwise invalid.`; + case Protocol.Network.TrustTokenOperationDoneEventStatus.FailedPrecondition: + case Protocol.Network.TrustTokenOperationDoneEventStatus.Unavailable: + case Protocol.Network.TrustTokenOperationDoneEventStatus.InternalError: + case Protocol.Network.TrustTokenOperationDoneEventStatus.UnknownError: + return ls`The operation failed for an unknown reason.`; + } +} + +function renderSimpleRowIfValuePresent(name: string, value: T|undefined): LitHtml.TemplateResult|{} { + if (value === undefined) { + return LitHtml.nothing; + } + + return LitHtml.html` + + ${name} + ${value} + `; +} + function renderRowWithCodeValue(name: string, value: string): LitHtml.TemplateResult { - // Disabled until https://crbug.com/1079231 is fixed. - // clang-format off return LitHtml.html` ${name} ${value} `; - // clang-format on } customElements.define('devtools-trust-token-report', RequestTrustTokensReport); diff --git a/front_end/network/network_strings.grdp b/front_end/network/network_strings.grdp index c8b20f2bff..a3e902cc14 100644 --- a/front_end/network/network_strings.grdp +++ b/front_end/network/network_strings.grdp @@ -12,6 +12,9 @@ No custom headers + + Either no inputs for this operation are available or the output exceeds the operations quota. + Caching @@ -60,6 +63,9 @@ Trust Tokens + + The operations result was served from cache. + Stalled @@ -204,6 +210,9 @@ Request call stack + + Number of issued tokens + $1s4 resources @@ -312,6 +321,9 @@ Show overview + + Success + CoepFrameResourceNeedsCoepHeader @@ -462,12 +474,18 @@ network + + Result + respondWith From HTTP cache + + The operation was fulfilled locally, no request was sent. + Text @@ -477,6 +495,9 @@ Cross-Origin Resource Sharing error: $1sPreflightDisallowedRedirect + + The servers response was malformed or otherwise invalid. + Active client experiment variation IDs. @@ -522,6 +543,9 @@ View source + + A client-provided argument was malformed or otherwise invalid. + Copy as Node.js fetch @@ -570,6 +594,12 @@ Capture screenshots when loading a page + + Failure + + + Top level origin + To embed this frame in your document, the response needs to enable the cross-origin embedder policy by specifying the following response header: @@ -597,6 +627,9 @@ Valid from + + The operation failed for an unknown reason. + Cache storage cache name: $1sv1 diff --git a/front_end/ui/components/ReportView.ts b/front_end/ui/components/ReportView.ts index e9a16531a1..e9336be756 100644 --- a/front_end/ui/components/ReportView.ts +++ b/front_end/ui/components/ReportView.ts @@ -149,6 +149,7 @@ export class ReportRow extends HTMLElement { .value { color: var(--color-text-primary); flex: auto; + margin-inline-start: 0px; padding: 0 6px; white-space: pre; } diff --git a/test/unittests/front_end/network/RequestTrustTokensView_test.ts b/test/unittests/front_end/network/RequestTrustTokensView_test.ts index d650a8aee3..cbb6a1c121 100644 --- a/test/unittests/front_end/network/RequestTrustTokensView_test.ts +++ b/test/unittests/front_end/network/RequestTrustTokensView_test.ts @@ -3,17 +3,19 @@ // found in the LICENSE file. import * as Network from '../../../../front_end/network/network.js'; - -import {assertShadowRoot, renderElementIntoDOM} from '../helpers/DOMHelpers.js'; +import {assertNotNull, getElementsWithinComponent, getElementWithinComponent, renderElementIntoDOM} from '../helpers/DOMHelpers.js'; const {assert} = chai; describe('RequestTrustTokensView', () => { - it('renders the RefreshPolicy for redemptions', () => { + const renderRequestTrustTokensView = () => { const component = new Network.RequestTrustTokensView.RequestTrustTokensReport(); renderElementIntoDOM(component); - assertShadowRoot(component.shadowRoot); + return component; + }; + it('renders the RefreshPolicy for redemptions', () => { + const component = renderRequestTrustTokensView(); component.data = { params: { type: Protocol.Network.TrustTokenOperationType.Redemption, @@ -21,16 +23,13 @@ describe('RequestTrustTokensView', () => { }, } as Network.RequestTrustTokensView.RequestTrustTokensReportData; - const [typeSpan, refreshPolicySpan] = component.shadowRoot.querySelectorAll('span.code'); + const [typeSpan, refreshPolicySpan] = getElementsWithinComponent(component, 'span.code', HTMLElement); assert.strictEqual(typeSpan.textContent, 'Redemption'); assert.strictEqual(refreshPolicySpan.textContent, 'UseCached'); }); it('renders all issuers as a list', () => { - const component = new Network.RequestTrustTokensView.RequestTrustTokensReport(); - renderElementIntoDOM(component); - assertShadowRoot(component.shadowRoot); - + const component = renderRequestTrustTokensView(); const expectedIssuers = ['example.org', 'foo.dev', 'bar.com']; component.data = { params: { @@ -39,9 +38,39 @@ describe('RequestTrustTokensView', () => { }, } as Network.RequestTrustTokensView.RequestTrustTokensReportData; - const issuerElements = component.shadowRoot.querySelectorAll('ul.issuers-list > li'); + const issuerElements = getElementsWithinComponent(component, 'ul.issuers-list > li', HTMLElement); const actualIssuers = [...issuerElements].map(e => e.textContent); assert.deepStrictEqual(actualIssuers.sort(), expectedIssuers.sort()); }); + + it('renders a result section with success status for successful requests', () => { + const component = renderRequestTrustTokensView(); + component.data = { + result: { + status: Protocol.Network.TrustTokenOperationDoneEventStatus.Ok, + type: Protocol.Network.TrustTokenOperationType.Issuance, + requestId: 'mockId', + }, + }; + + const simpleText = getElementWithinComponent(component, 'div.status-text > span', HTMLElement); + assertNotNull(simpleText); + assert.strictEqual(simpleText.textContent, 'Success'); + }); + + it('renders a result section with failure status for failed requests', () => { + const component = renderRequestTrustTokensView(); + component.data = { + result: { + status: Protocol.Network.TrustTokenOperationDoneEventStatus.BadResponse, + type: Protocol.Network.TrustTokenOperationType.Issuance, + requestId: 'mockId', + }, + }; + + const simpleText = getElementWithinComponent(component, 'div.status-text > span', HTMLElement); + assertNotNull(simpleText); + assert.strictEqual(simpleText.textContent, 'Failure'); + }); });