Skip to content

Commit 56e03d4

Browse files
kenossDevtools-frontend LUCI CQ
authored andcommitted
Prerender2Fallback: Show pipeline details in PreloadingDetailsReportView
If Prerender2FallbackPrefetchSpecRules is enabled, Chrome triggers prefetch ahead of prerender and use prefetch's result for navigation if prerender failed to prevent additional fetch. This CL shows "(automatically fell back to prefetch)" if prefetch ahead of prerender is triggered and succeeded and prerender failed. demo: https://docs.google.com/document/d/13ttSjpkGn0HgnlsN-_3tnpUhOiPsTCHxHNzptrdkXgk/edit?tab=t.0#heading=h.vw5iwf1qq001 Bug: 364509578 Change-Id: Ica6c52051024f8ccb63e6db4ba8e5fbd6b0ef211 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6069240 Commit-Queue: Ken Okada <[email protected]> Reviewed-by: Danil Somsikov <[email protected]>
1 parent 62d797f commit 56e03d4

File tree

4 files changed

+179
-41
lines changed

4 files changed

+179
-41
lines changed

front_end/core/sdk/PreloadingModel.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,27 @@ export class PreloadPipeline {
559559
getPrerender(): PreloadingAttempt|null {
560560
return this.inner.get(Protocol.Preload.SpeculationAction.Prerender) || null;
561561
}
562+
563+
// Returns attempts in the order: prefetch < prerender.
564+
getAttempts(): PreloadingAttempt[] {
565+
const ret = [];
566+
567+
const prefetch = this.getPrefetch();
568+
if (prefetch !== null) {
569+
ret.push(prefetch);
570+
}
571+
572+
const prerender = this.getPrerender();
573+
if (prerender !== null) {
574+
ret.push(prerender);
575+
}
576+
577+
if (ret.length === 0) {
578+
throw new Error('unreachable');
579+
}
580+
581+
return ret;
582+
}
562583
}
563584

564585
class PreloadingAttemptRegistry {

front_end/panels/application/preloading/PreloadingView.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,18 +425,19 @@ export class PreloadingAttemptView extends UI.Widget.VBox {
425425
if (preloadingAttempt === null) {
426426
this.preloadingDetails.data = null;
427427
} else {
428+
const pipeline = this.model.getPipeline(preloadingAttempt);
428429
const ruleSets = preloadingAttempt.ruleSetIds.map(id => this.model.getRuleSetById(id)).filter(x => x !== null) as
429430
Protocol.Preload.RuleSet[];
430431
this.preloadingDetails.data = {
431-
preloadingAttempt,
432+
pipeline,
432433
ruleSets,
433434
pageURL: pageURL(),
434435
};
435436
}
436437
}
437438

438439
render(): void {
439-
// Update preloaidng grid
440+
// Update preloading grid
440441
const filteringRuleSetId = this.ruleSetSelector.getSelected();
441442
const rows = this.model.getRepresentativePreloadingAttempts(filteringRuleSetId).map(({id, value}) => {
442443
const attempt = value;

front_end/panels/application/preloading/components/PreloadingDetailsReportView.test.ts

Lines changed: 110 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,76 @@ describeWithEnvironment('PreloadingDetailsReportView', () => {
5252
it('renders prerendering details', async () => {
5353
const url = 'https://example.com/prerendered.html' as Platform.DevToolsPath.UrlString;
5454
const data: PreloadingComponents.PreloadingDetailsReportView.PreloadingDetailsReportViewData = {
55-
preloadingAttempt: {
55+
pipeline: SDK.PreloadingModel.PreloadPipeline.newFromAttemptsForTesting([
56+
{
57+
action: Protocol.Preload.SpeculationAction.Prefetch,
58+
key: {
59+
loaderId: 'loaderId' as Protocol.Network.LoaderId,
60+
action: Protocol.Preload.SpeculationAction.Prefetch,
61+
url,
62+
targetHint: undefined,
63+
},
64+
pipelineId: 'pipelineId:1' as Protocol.Preload.PreloadPipelineId,
65+
status: SDK.PreloadingModel.PreloadingStatus.SUCCESS,
66+
prefetchStatus: Protocol.Preload.PrefetchStatus.PrefetchResponseUsed,
67+
requestId: 'requestId:1' as Protocol.Network.RequestId,
68+
ruleSetIds: ['ruleSetId'] as Protocol.Preload.RuleSetId[],
69+
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
70+
},
71+
{
72+
action: Protocol.Preload.SpeculationAction.Prerender,
73+
key: {
74+
loaderId: 'loaderId' as Protocol.Network.LoaderId,
75+
action: Protocol.Preload.SpeculationAction.Prerender,
76+
url,
77+
targetHint: undefined,
78+
},
79+
pipelineId: 'pipelineId:1' as Protocol.Preload.PreloadPipelineId,
80+
status: SDK.PreloadingModel.PreloadingStatus.RUNNING,
81+
prerenderStatus: null,
82+
disallowedMojoInterface: null,
83+
mismatchedHeaders: null,
84+
ruleSetIds: ['ruleSetId'] as Protocol.Preload.RuleSetId[],
85+
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
86+
},
87+
]),
88+
ruleSets: [
89+
{
90+
id: 'ruleSetId' as Protocol.Preload.RuleSetId,
91+
loaderId: 'loaderId' as Protocol.Network.LoaderId,
92+
sourceText: `
93+
{
94+
"prefetch": [
95+
{
96+
"source": "list",
97+
"urls": ["/subresource.js"]
98+
}
99+
]
100+
}
101+
`,
102+
},
103+
],
104+
pageURL: 'https://example.com/' as Platform.DevToolsPath.UrlString,
105+
};
106+
107+
const component = await renderPreloadingDetailsReportView(data);
108+
const report = getElementWithinComponent(component, 'devtools-report', ReportView.ReportView.Report);
109+
110+
const keys = getCleanTextContentFromElements(report, 'devtools-report-key');
111+
const values = getCleanTextContentFromElements(report, 'devtools-report-value');
112+
assert.deepEqual(zip2(keys, values), [
113+
['URL', url],
114+
['Action', 'Prerender'],
115+
['Status', 'Speculative load is running.'],
116+
['Rule set', 'example.com/'],
117+
]);
118+
});
119+
120+
// Prerender2FallbackPrefetchSpecRules disabled case.
121+
it('doesn\'t render (automatically fell back to prefetch) if prerender alone', async () => {
122+
const url = 'https://example.com/prerendered.html' as Platform.DevToolsPath.UrlString;
123+
const data: PreloadingComponents.PreloadingDetailsReportView.PreloadingDetailsReportViewData = {
124+
pipeline: SDK.PreloadingModel.PreloadPipeline.newFromAttemptsForTesting([{
56125
action: Protocol.Preload.SpeculationAction.Prerender,
57126
key: {
58127
loaderId: 'loaderId' as Protocol.Network.LoaderId,
@@ -67,7 +136,7 @@ describeWithEnvironment('PreloadingDetailsReportView', () => {
67136
mismatchedHeaders: null,
68137
ruleSetIds: ['ruleSetId'] as Protocol.Preload.RuleSetId[],
69138
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
70-
},
139+
}]),
71140
ruleSets: [
72141
{
73142
id: 'ruleSetId' as Protocol.Preload.RuleSetId,
@@ -105,22 +174,39 @@ describeWithEnvironment('PreloadingDetailsReportView', () => {
105174
it('renders prerendering details with cancelled reason', async () => {
106175
const url = 'https://example.com/prerendered.html' as Platform.DevToolsPath.UrlString;
107176
const data: PreloadingComponents.PreloadingDetailsReportView.PreloadingDetailsReportViewData = {
108-
preloadingAttempt: {
109-
action: Protocol.Preload.SpeculationAction.Prerender,
110-
key: {
111-
loaderId: 'loaderId' as Protocol.Network.LoaderId,
177+
pipeline: SDK.PreloadingModel.PreloadPipeline.newFromAttemptsForTesting([
178+
{
179+
action: Protocol.Preload.SpeculationAction.Prefetch,
180+
key: {
181+
loaderId: 'loaderId' as Protocol.Network.LoaderId,
182+
action: Protocol.Preload.SpeculationAction.Prefetch,
183+
url,
184+
targetHint: undefined,
185+
},
186+
pipelineId: 'pipelineId:1' as Protocol.Preload.PreloadPipelineId,
187+
status: SDK.PreloadingModel.PreloadingStatus.SUCCESS,
188+
prefetchStatus: Protocol.Preload.PrefetchStatus.PrefetchResponseUsed,
189+
requestId: 'requestId:1' as Protocol.Network.RequestId,
190+
ruleSetIds: ['ruleSetId'] as Protocol.Preload.RuleSetId[],
191+
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
192+
},
193+
{
112194
action: Protocol.Preload.SpeculationAction.Prerender,
113-
url,
114-
targetHint: undefined,
195+
key: {
196+
loaderId: 'loaderId' as Protocol.Network.LoaderId,
197+
action: Protocol.Preload.SpeculationAction.Prerender,
198+
url,
199+
targetHint: undefined,
200+
},
201+
pipelineId: 'pipelineId:1' as Protocol.Preload.PreloadPipelineId,
202+
status: SDK.PreloadingModel.PreloadingStatus.FAILURE,
203+
prerenderStatus: Protocol.Preload.PrerenderFinalStatus.MojoBinderPolicy,
204+
disallowedMojoInterface: 'device.mojom.GamepadMonitor',
205+
mismatchedHeaders: null,
206+
ruleSetIds: ['ruleSetId'] as Protocol.Preload.RuleSetId[],
207+
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
115208
},
116-
pipelineId: 'pipelineId:1' as Protocol.Preload.PreloadPipelineId,
117-
status: SDK.PreloadingModel.PreloadingStatus.FAILURE,
118-
prerenderStatus: Protocol.Preload.PrerenderFinalStatus.MojoBinderPolicy,
119-
disallowedMojoInterface: 'device.mojom.GamepadMonitor',
120-
mismatchedHeaders: null,
121-
ruleSetIds: ['ruleSetId'] as Protocol.Preload.RuleSetId[],
122-
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
123-
},
209+
]),
124210
ruleSets: [
125211
{
126212
id: 'ruleSetId' as Protocol.Preload.RuleSetId,
@@ -147,8 +233,8 @@ describeWithEnvironment('PreloadingDetailsReportView', () => {
147233
const values = getCleanTextContentFromElements(report, 'devtools-report-value');
148234
assert.deepEqual(zip2(keys, values), [
149235
['URL', url],
150-
['Action', 'Prerender'],
151-
['Status', 'Speculative load failed.'],
236+
['Action', 'Prerender (automatically fell back to prefetch)'],
237+
['Status', 'Speculative load failed, but fallback to prefetch succeeded.'],
152238
[
153239
'Failure reason',
154240
'The prerendered page used a forbidden JavaScript API that is currently not supported. (Internal Mojo interface: device.mojom.GamepadMonitor)',
@@ -166,7 +252,7 @@ describeWithEnvironment('PreloadingDetailsReportView', () => {
166252

167253
const url = 'https://example.com/prefetch.html' as Platform.DevToolsPath.UrlString;
168254
const data: PreloadingComponents.PreloadingDetailsReportView.PreloadingDetailsReportViewData = {
169-
preloadingAttempt: {
255+
pipeline: SDK.PreloadingModel.PreloadPipeline.newFromAttemptsForTesting([{
170256
action: Protocol.Preload.SpeculationAction.Prefetch,
171257
key: {
172258
loaderId: 'loaderId' as Protocol.Network.LoaderId,
@@ -180,7 +266,7 @@ describeWithEnvironment('PreloadingDetailsReportView', () => {
180266
requestId: 'requestId:1' as Protocol.Network.RequestId,
181267
ruleSetIds: ['ruleSetId'] as Protocol.Preload.RuleSetId[],
182268
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
183-
},
269+
}]),
184270
ruleSets: [
185271
{
186272
id: 'ruleSetId' as Protocol.Preload.RuleSetId,
@@ -227,7 +313,7 @@ describeWithEnvironment('PreloadingDetailsReportView', () => {
227313

228314
const url = 'https://example.com/prefetch.html' as Platform.DevToolsPath.UrlString;
229315
const data: PreloadingComponents.PreloadingDetailsReportView.PreloadingDetailsReportViewData = {
230-
preloadingAttempt: {
316+
pipeline: SDK.PreloadingModel.PreloadPipeline.newFromAttemptsForTesting([{
231317
action: Protocol.Preload.SpeculationAction.Prefetch,
232318
key: {
233319
loaderId: 'loaderId' as Protocol.Network.LoaderId,
@@ -241,7 +327,7 @@ describeWithEnvironment('PreloadingDetailsReportView', () => {
241327
requestId: 'requestId:1' as Protocol.Network.RequestId,
242328
ruleSetIds: ['ruleSetId'] as Protocol.Preload.RuleSetId[],
243329
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
244-
},
330+
}]),
245331
ruleSets: [
246332
{
247333
id: 'ruleSetId' as Protocol.Preload.RuleSetId,
@@ -278,4 +364,6 @@ describeWithEnvironment('PreloadingDetailsReportView', () => {
278364
['Rule set', 'example.com/speculation-rules.json'],
279365
]);
280366
});
367+
368+
// TODO: Add test for pipeline
281369
});

front_end/panels/application/preloading/components/PreloadingDetailsReportView.ts

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ const UIStrings = {
5151
*@description Header of rule set
5252
*/
5353
detailsRuleSet: 'Rule set',
54+
/**
55+
*@description Description: status
56+
*/
57+
automaticallyFellBackToPrefetch: '(automatically fell back to prefetch)',
5458
/**
5559
*@description Description: status
5660
*/
@@ -75,6 +79,10 @@ const UIStrings = {
7579
*@description Description: status
7680
*/
7781
detailedStatusFailure: 'Speculative load failed.',
82+
/**
83+
*@description Description: status
84+
*/
85+
detailedStatusFallbackToPrefetch: 'Speculative load failed, but fallback to prefetch succeeded.',
7886
/**
7987
*@description button: Contents of button to inspect prerendered page
8088
*/
@@ -123,7 +131,7 @@ const coordinator = Coordinator.RenderCoordinator.RenderCoordinator.instance();
123131

124132
export type PreloadingDetailsReportViewData = PreloadingDetailsReportViewDataInternal|null;
125133
interface PreloadingDetailsReportViewDataInternal {
126-
preloadingAttempt: SDK.PreloadingModel.PreloadingAttempt;
134+
pipeline: SDK.PreloadingModel.PreloadPipeline;
127135
ruleSets: Protocol.Preload.RuleSet[];
128136
pageURL: Platform.DevToolsPath.UrlString;
129137
requestResolver?: Logs.RequestResolver.RequestResolver;
@@ -158,24 +166,23 @@ export class PreloadingDetailsReportView extends LegacyWrapper.LegacyWrapper.Wra
158166
return;
159167
}
160168

161-
const detailedStatus = PreloadingUIUtils.detailedStatus(this.#data.preloadingAttempt);
169+
const pipeline = this.#data.pipeline;
162170
const pageURL = this.#data.pageURL;
171+
const isFallbackToPrefetch = pipeline.getPrerender()?.status === SDK.PreloadingModel.PreloadingStatus.FAILURE &&
172+
(pipeline.getPrefetch()?.status === SDK.PreloadingModel.PreloadingStatus.READY ||
173+
pipeline.getPrefetch()?.status === SDK.PreloadingModel.PreloadingStatus.SUCCESS);
163174

164175
// Disabled until https://crbug.com/1079231 is fixed.
165176
// clang-format off
166177
LitHtml.render(html`
167-
<devtools-report .data=${{reportTitle: 'Speculative Loading Attempt'}}
168-
jslog=${VisualLogging.section('preloading-details')}>
178+
<devtools-report
179+
.data=${{reportTitle: 'Speculative Loading Attempt'}}
180+
jslog=${VisualLogging.section('preloading-details')}>
169181
<devtools-report-section-header>${i18nString(UIStrings.detailsDetailedInformation)}</devtools-report-section-header>
170182
171183
${this.#url()}
172-
${this.#action()}
173-
174-
<devtools-report-key>${i18nString(UIStrings.detailsStatus)}</devtools-report-key>
175-
<devtools-report-value>
176-
${detailedStatus}
177-
</devtools-report-value>
178-
184+
${this.#action(isFallbackToPrefetch)}
185+
${this.#status(isFallbackToPrefetch)}
179186
${this.#maybePrefetchFailureReason()}
180187
${this.#maybePrerenderFailureReason()}
181188
@@ -188,7 +195,7 @@ export class PreloadingDetailsReportView extends LegacyWrapper.LegacyWrapper.Wra
188195

189196
#url(): LitHtml.LitTemplate {
190197
assertNotNullOrUndefined(this.#data);
191-
const attempt = this.#data.preloadingAttempt;
198+
const attempt = this.#data.pipeline.getOriginallyTriggered();
192199

193200
let value;
194201
if (attempt.action === Protocol.Preload.SpeculationAction.Prefetch && attempt.requestId !== undefined) {
@@ -230,11 +237,16 @@ export class PreloadingDetailsReportView extends LegacyWrapper.LegacyWrapper.Wra
230237
// clang-format on
231238
}
232239

233-
#action(): LitHtml.LitTemplate {
240+
#action(isFallbackToPrefetch: boolean): LitHtml.LitTemplate {
234241
assertNotNullOrUndefined(this.#data);
235-
const attempt = this.#data.preloadingAttempt;
242+
const attempt = this.#data.pipeline.getOriginallyTriggered();
236243

237-
const action = PreloadingString.capitalizedAction(this.#data.preloadingAttempt.action);
244+
const action = PreloadingString.capitalizedAction(attempt.action);
245+
246+
let maybeFellback: LitHtml.LitTemplate = LitHtml.nothing;
247+
if (isFallbackToPrefetch) {
248+
maybeFellback = html`${i18nString(UIStrings.automaticallyFellBackToPrefetch)}`;
249+
}
238250

239251
let maybeInspectButton: LitHtml.LitTemplate = LitHtml.nothing;
240252
(() => {
@@ -281,16 +293,32 @@ export class PreloadingDetailsReportView extends LegacyWrapper.LegacyWrapper.Wra
281293
<devtools-report-value>
282294
<div class="text-ellipsis" title="">
283295
${action}
296+
${maybeFellback}
284297
${maybeInspectButton}
285298
</div>
286299
</devtools-report-value>
287300
`;
288301
// clang-format on
289302
}
290303

304+
#status(isFallbackToPrefetch: boolean): LitHtml.LitTemplate {
305+
assertNotNullOrUndefined(this.#data);
306+
const attempt = this.#data.pipeline.getOriginallyTriggered();
307+
308+
const detailedStatus = isFallbackToPrefetch ? i18nString(UIStrings.detailedStatusFallbackToPrefetch) :
309+
PreloadingUIUtils.detailedStatus(attempt);
310+
311+
return html`
312+
<devtools-report-key>${i18nString(UIStrings.detailsStatus)}</devtools-report-key>
313+
<devtools-report-value>
314+
${detailedStatus}
315+
</devtools-report-value>
316+
`;
317+
}
318+
291319
#maybePrefetchFailureReason(): LitHtml.LitTemplate {
292320
assertNotNullOrUndefined(this.#data);
293-
const attempt = this.#data.preloadingAttempt;
321+
const attempt = this.#data.pipeline.getOriginallyTriggered();
294322

295323
if (attempt.action !== Protocol.Preload.SpeculationAction.Prefetch) {
296324
return LitHtml.nothing;
@@ -311,7 +339,7 @@ export class PreloadingDetailsReportView extends LegacyWrapper.LegacyWrapper.Wra
311339

312340
#maybePrerenderFailureReason(): LitHtml.LitTemplate {
313341
assertNotNullOrUndefined(this.#data);
314-
const attempt = this.#data.preloadingAttempt;
342+
const attempt = this.#data.pipeline.getOriginallyTriggered();
315343

316344
if (attempt.action !== Protocol.Preload.SpeculationAction.Prerender) {
317345
return LitHtml.nothing;

0 commit comments

Comments
 (0)