From f261ffc197322a2e4b20aef47ab2da99faecdba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Z=C3=BCnd?= Date: Wed, 16 Dec 2020 13:26:28 +0100 Subject: [PATCH] Report Trust Token operation result in network panel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This CL adds the 'Results' portion of the Trust Token tab in the Network panel. Please note that the issuer and top-level origin are reported by the result event due to the way the backend is implemented. The two origins are considered part of the parameters though, and are displayed in the 'Parameters' section for that reason. Screenshots: https://imgur.com/a/XceM0Xl R=petermueller@chromium.org Bug: chromium:1126824 Change-Id: I23e6a5dcddb4be8ffbe95dc25bee75ffaedc2064 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2584968 Commit-Queue: Simon Zünd Reviewed-by: Sigurd Schneider --- devtools_grd_files.gni | 1 + devtools_image_files.gni | 1 + front_end/Images/ic_checkmark_16x16.svg | 3 + front_end/network/BUILD.gn | 1 + front_end/network/RequestTrustTokensView.ts | 138 ++++++++++++++++-- front_end/network/network_strings.grdp | 33 +++++ front_end/ui/components/ReportView.ts | 1 + .../network/RequestTrustTokensView_test.ts | 49 +++++-- 8 files changed, 204 insertions(+), 23 deletions(-) create mode 100644 front_end/Images/ic_checkmark_16x16.svg 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`}
    ${params.issuers.map(issuer => LitHtml.html`
  • ${issuer}
  • `)}
-
- `; - // 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'); + }); });