Skip to content

Commit fa9f811

Browse files
kenossDevtools-frontend LUCI CQ
authored andcommitted
Prerender2Fallback: Show pipeline status in PreloadingGrid
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 "Prefetch fallback ready" message on PreloadingGrid if prefetch ahead of prerender is triggered and succeeded, and prerender failed. For this purpose, this CL introduces `PreloadPipeline` in `PreloadingModel`. demo: https://docs.google.com/document/d/13ttSjpkGn0HgnlsN-_3tnpUhOiPsTCHxHNzptrdkXgk/edit?tab=t.0#heading=h.vw5iwf1qq001 Bug: 364509578 Change-Id: I7c2d59b63075ff2139375de89ad40f10e29df152 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6068033 Reviewed-by: Domenic Denicola <[email protected]> Reviewed-by: Danil Somsikov <[email protected]> Commit-Queue: Ken Okada <[email protected]>
1 parent 2b37d5a commit fa9f811

File tree

4 files changed

+317
-64
lines changed

4 files changed

+317
-64
lines changed

front_end/core/sdk/PreloadingModel.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,32 @@ export class PreloadingModel extends SDKModel<EventTypes> {
166166
return document.preloadingAttempts.getAllRepresentative(null, document.sources);
167167
}
168168

169+
// Precondition: `pipelineId` should exists.
170+
// Postcondition: The return value is not empty.
171+
private getPipelineById(pipelineId: Protocol.Preload.PreloadPipelineId):
172+
Map<Protocol.Preload.SpeculationAction, PreloadingAttempt>|null {
173+
const document = this.currentDocument();
174+
if (document === null) {
175+
return null;
176+
}
177+
178+
return document.preloadingAttempts.getPipeline(pipelineId, document.sources);
179+
}
180+
181+
// Returns attemtps that are sit in the same preload pipeline.
182+
getPipeline(attempt: PreloadingAttempt): PreloadPipeline {
183+
let pipelineNullable = null;
184+
if (attempt.pipelineId !== null) {
185+
pipelineNullable = this.getPipelineById(attempt.pipelineId);
186+
}
187+
if (pipelineNullable === null) {
188+
const pipeline = new Map();
189+
pipeline.set(attempt.action, attempt);
190+
return new PreloadPipeline(pipeline);
191+
}
192+
return new PreloadPipeline(pipelineNullable);
193+
}
194+
169195
private onPrimaryPageChanged(
170196
event: Common.EventTarget.EventTargetEvent<{frame: ResourceTreeFrame, type: PrimaryPageChangeType}>): void {
171197
const {frame, type} = event.data;
@@ -501,6 +527,40 @@ function makePreloadingAttemptId(key: Protocol.Preload.PreloadingAttemptKey): Pr
501527
return `${key.loaderId}:${action}:${key.url}:${targetHint}`;
502528
}
503529

530+
export class PreloadPipeline {
531+
private inner: Map<Protocol.Preload.SpeculationAction, PreloadingAttempt>;
532+
533+
constructor(inner: Map<Protocol.Preload.SpeculationAction, PreloadingAttempt>) {
534+
if (inner.size === 0) {
535+
throw new Error('unreachable');
536+
}
537+
538+
this.inner = inner;
539+
}
540+
541+
static newFromAttemptsForTesting(attempts: PreloadingAttempt[]): PreloadPipeline {
542+
const inner = new Map();
543+
for (const attempt of attempts) {
544+
inner.set(attempt.action, attempt);
545+
}
546+
return new PreloadPipeline(inner);
547+
}
548+
549+
getOriginallyTriggered(): PreloadingAttempt {
550+
const attempt = this.getPrerender() || this.getPrefetch();
551+
assertNotNullOrUndefined(attempt);
552+
return attempt;
553+
}
554+
555+
getPrefetch(): PreloadingAttempt|null {
556+
return this.inner.get(Protocol.Preload.SpeculationAction.Prefetch) || null;
557+
}
558+
559+
getPrerender(): PreloadingAttempt|null {
560+
return this.inner.get(Protocol.Preload.SpeculationAction.Prerender) || null;
561+
}
562+
}
563+
504564
class PreloadingAttemptRegistry {
505565
private map: Map<PreloadingAttemptId, PreloadingAttemptInternal> =
506566
new Map<PreloadingAttemptId, PreloadingAttemptInternal>();
@@ -582,6 +642,25 @@ class PreloadingAttemptRegistry {
582642
.filter(({value}) => this.isAttemptRepresentative(value));
583643
}
584644

645+
getPipeline(pipelineId: Protocol.Preload.PreloadPipelineId, sources: SourceRegistry):
646+
Map<Protocol.Preload.SpeculationAction, PreloadingAttempt>|null {
647+
const pipeline = this.pipelines.get(pipelineId);
648+
649+
if (pipeline === undefined || pipeline.size === 0) {
650+
return null;
651+
}
652+
653+
const map: {[key: PreloadingAttemptId]: PreloadingAttemptInternal} = {};
654+
for (const [id, attempt] of this.map.entries()) {
655+
map[id] = attempt;
656+
}
657+
return new Map(pipeline.entries().map(([action, id]) => {
658+
const attempt = this.getById(id, sources);
659+
assertNotNullOrUndefined(attempt);
660+
return [action, attempt];
661+
}));
662+
}
663+
585664
upsert(attempt: PreloadingAttemptInternal): void {
586665
const id = makePreloadingAttemptId(attempt.key);
587666

front_end/panels/application/preloading/PreloadingView.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,8 @@ export class PreloadingRuleSetView extends UI.Widget.VBox {
327327

328328
export class PreloadingAttemptView extends UI.Widget.VBox {
329329
private model: SDK.PreloadingModel.PreloadingModel;
330+
// Note that we use id of (representative) preloading attempt while we show pipelines in grid.
331+
// This is because `NOT_TRIGGERED` preloading attempts don't have pipeline id and we can use it.
330332
private focusedPreloadingAttemptId: SDK.PreloadingModel.PreloadingAttemptId|null = null;
331333

332334
private readonly warningsContainer: HTMLDivElement;
@@ -438,13 +440,14 @@ export class PreloadingAttemptView extends UI.Widget.VBox {
438440
const filteringRuleSetId = this.ruleSetSelector.getSelected();
439441
const rows = this.model.getRepresentativePreloadingAttempts(filteringRuleSetId).map(({id, value}) => {
440442
const attempt = value;
443+
const pipeline = this.model.getPipeline(attempt);
441444
const ruleSets = attempt.ruleSetIds.flatMap(id => {
442445
const ruleSet = this.model.getRuleSetById(id);
443446
return ruleSet === null ? [] : [ruleSet];
444447
});
445448
return {
446449
id,
447-
attempt,
450+
pipeline,
448451
ruleSets,
449452
};
450453
});

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

Lines changed: 161 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describeWithEnvironment('PreloadingGrid', () => {
3636
{
3737
rows: [{
3838
id: 'id',
39-
attempt: {
39+
pipeline: SDK.PreloadingModel.PreloadPipeline.newFromAttemptsForTesting([{
4040
action: Protocol.Preload.SpeculationAction.Prefetch,
4141
key: {
4242
loaderId: 'loaderId:1' as Protocol.Network.LoaderId,
@@ -49,7 +49,7 @@ describeWithEnvironment('PreloadingGrid', () => {
4949
requestId: 'requestId:1' as Protocol.Network.RequestId,
5050
ruleSetIds: ['ruleSetId:0.1'] as Protocol.Preload.RuleSetId[],
5151
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
52-
} as SDK.PreloadingModel.PreloadingAttempt,
52+
}]),
5353
ruleSets: [
5454
{
5555
id: 'ruleSetId:0.1' as Protocol.Preload.RuleSetId,
@@ -81,7 +81,7 @@ describeWithEnvironment('PreloadingGrid', () => {
8181
{
8282
rows: [{
8383
id: 'id',
84-
attempt: {
84+
pipeline: SDK.PreloadingModel.PreloadPipeline.newFromAttemptsForTesting([{
8585
action: Protocol.Preload.SpeculationAction.Prefetch,
8686
key: {
8787
loaderId: 'loaderId:1' as Protocol.Network.LoaderId,
@@ -94,7 +94,7 @@ describeWithEnvironment('PreloadingGrid', () => {
9494
requestId: 'requestId:1' as Protocol.Network.RequestId,
9595
ruleSetIds: ['ruleSetId:0.1'] as Protocol.Preload.RuleSetId[],
9696
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
97-
} as SDK.PreloadingModel.PreloadingAttempt,
97+
}]),
9898
ruleSets: [
9999
{
100100
id: 'ruleSetId:0.1' as Protocol.Preload.RuleSetId,
@@ -126,7 +126,7 @@ describeWithEnvironment('PreloadingGrid', () => {
126126
{
127127
rows: [{
128128
id: 'id',
129-
attempt: {
129+
pipeline: SDK.PreloadingModel.PreloadPipeline.newFromAttemptsForTesting([{
130130
action: Protocol.Preload.SpeculationAction.Prefetch,
131131
key: {
132132
loaderId: 'loaderId:1' as Protocol.Network.LoaderId,
@@ -139,7 +139,7 @@ describeWithEnvironment('PreloadingGrid', () => {
139139
requestId: 'requestId:1' as Protocol.Network.RequestId,
140140
ruleSetIds: ['ruleSetId:0.1'] as Protocol.Preload.RuleSetId[],
141141
nodeIds: [] as Protocol.DOM.BackendNodeId[],
142-
} as SDK.PreloadingModel.PreloadingAttempt,
142+
}]),
143143
ruleSets: [
144144
{
145145
id: 'ruleSetId:0.1' as Protocol.Preload.RuleSetId,
@@ -173,7 +173,7 @@ describeWithEnvironment('PreloadingGrid', () => {
173173
rows: [
174174
{
175175
id: 'id',
176-
attempt: {
176+
pipeline: SDK.PreloadingModel.PreloadPipeline.newFromAttemptsForTesting([{
177177
action: Protocol.Preload.SpeculationAction.Prefetch,
178178
key: {
179179
loaderId: 'loaderId:1' as Protocol.Network.LoaderId,
@@ -186,12 +186,12 @@ describeWithEnvironment('PreloadingGrid', () => {
186186
requestId: 'requestId:1' as Protocol.Network.RequestId,
187187
ruleSetIds: ['ruleSetId:0.1'] as Protocol.Preload.RuleSetId[],
188188
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
189-
} as SDK.PreloadingModel.PreloadingAttempt,
189+
}]),
190190
ruleSets: [],
191191
},
192192
{
193193
id: 'id',
194-
attempt: {
194+
pipeline: SDK.PreloadingModel.PreloadPipeline.newFromAttemptsForTesting([{
195195
action: Protocol.Preload.SpeculationAction.Prefetch,
196196
key: {
197197
loaderId: 'loaderId:1' as Protocol.Network.LoaderId,
@@ -204,7 +204,7 @@ describeWithEnvironment('PreloadingGrid', () => {
204204
requestId: 'requestId:2' as Protocol.Network.RequestId,
205205
ruleSetIds: ['ruleSetId:0.2', 'ruleSetId:0.3'] as Protocol.Preload.RuleSetId[],
206206
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
207-
} as SDK.PreloadingModel.PreloadingAttempt,
207+
}]),
208208
ruleSets: [
209209
{
210210
id: 'ruleSetId:0.2' as Protocol.Preload.RuleSetId,
@@ -253,7 +253,7 @@ describeWithEnvironment('PreloadingGrid', () => {
253253
{
254254
rows: [{
255255
id: 'id',
256-
attempt: {
256+
pipeline: SDK.PreloadingModel.PreloadPipeline.newFromAttemptsForTesting([{
257257
action: Protocol.Preload.SpeculationAction.Prerender,
258258
key: {
259259
loaderId: 'loaderId:1' as Protocol.Network.LoaderId,
@@ -265,10 +265,9 @@ describeWithEnvironment('PreloadingGrid', () => {
265265
prerenderStatus: Protocol.Preload.PrerenderFinalStatus.MojoBinderPolicy,
266266
disallowedMojoInterface: 'device.mojom.GamepadMonitor',
267267
mismatchedHeaders: null,
268-
requestId: 'requestId:1' as Protocol.Network.RequestId,
269268
ruleSetIds: ['ruleSetId:0.1'] as Protocol.Preload.RuleSetId[],
270269
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
271-
} as SDK.PreloadingModel.PreloadingAttempt,
270+
}]),
272271
ruleSets: [
273272
{
274273
id: 'ruleSetId:0.1' as Protocol.Preload.RuleSetId,
@@ -306,4 +305,153 @@ describeWithEnvironment('PreloadingGrid', () => {
306305
const icon = div!.children[0];
307306
assert.include(icon.shadowRoot!.innerHTML, 'cross-circle-filled');
308307
});
308+
309+
it('shows a warning if a prerender fallbacks to prefetch', async () => {
310+
const grid = await assertRenderResult(
311+
{
312+
rows: [{
313+
id: 'id',
314+
pipeline: SDK.PreloadingModel.PreloadPipeline.newFromAttemptsForTesting([
315+
{
316+
action: Protocol.Preload.SpeculationAction.Prefetch,
317+
key: {
318+
loaderId: 'loaderId:1' as Protocol.Network.LoaderId,
319+
action: Protocol.Preload.SpeculationAction.Prefetch,
320+
url: 'https://example.com/prerendered.html' as Platform.DevToolsPath.UrlString,
321+
},
322+
pipelineId: 'pipelineId:1' as Protocol.Preload.PreloadPipelineId,
323+
status: SDK.PreloadingModel.PreloadingStatus.SUCCESS,
324+
prefetchStatus: Protocol.Preload.PrefetchStatus.PrefetchResponseUsed,
325+
requestId: 'requestId:1' as Protocol.Network.RequestId,
326+
ruleSetIds: ['ruleSetId:0.1'] as Protocol.Preload.RuleSetId[],
327+
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
328+
},
329+
{
330+
action: Protocol.Preload.SpeculationAction.Prerender,
331+
key: {
332+
loaderId: 'loaderId:1' as Protocol.Network.LoaderId,
333+
action: Protocol.Preload.SpeculationAction.Prerender,
334+
url: 'https://example.com/prerendered.html' as Platform.DevToolsPath.UrlString,
335+
},
336+
pipelineId: 'pipelineId:1' as Protocol.Preload.PreloadPipelineId,
337+
status: SDK.PreloadingModel.PreloadingStatus.FAILURE,
338+
prerenderStatus: Protocol.Preload.PrerenderFinalStatus.MojoBinderPolicy,
339+
disallowedMojoInterface: 'device.mojom.GamepadMonitor',
340+
mismatchedHeaders: null,
341+
ruleSetIds: ['ruleSetId:0.1'] as Protocol.Preload.RuleSetId[],
342+
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
343+
},
344+
] as SDK.PreloadingModel.PreloadingAttempt[]),
345+
ruleSets: [
346+
{
347+
id: 'ruleSetId:0.1' as Protocol.Preload.RuleSetId,
348+
loaderId: 'loaderId:1' as Protocol.Network.LoaderId,
349+
sourceText: `
350+
{
351+
"prerender":[
352+
{
353+
"source": "list",
354+
"urls": ["/prerendered.html"]
355+
}
356+
]
357+
}
358+
`,
359+
},
360+
],
361+
}],
362+
pageURL: 'https://example.com/' as Platform.DevToolsPath.UrlString,
363+
},
364+
['URL', 'Action', 'Rule set', 'Status'],
365+
[
366+
[
367+
'/prerendered.html',
368+
'Prerender',
369+
'example.com/',
370+
'Prefetch fallback ready',
371+
],
372+
],
373+
);
374+
375+
assert.isNotNull(grid.shadowRoot);
376+
const cell = getCellByIndexes(grid.shadowRoot, {row: 1, column: 3});
377+
const div = cell.querySelector('div');
378+
assert.strictEqual(div!.getAttribute('style'), 'color:var(--sys-color-orange-bright);');
379+
const icon = div!.children[0];
380+
assert.include(icon.shadowRoot!.innerHTML, 'warning-filled');
381+
});
382+
383+
it('shows failure if both prefetch and prerender failed', async () => {
384+
const grid = await assertRenderResult(
385+
{
386+
rows: [{
387+
id: 'id',
388+
pipeline: SDK.PreloadingModel.PreloadPipeline.newFromAttemptsForTesting([
389+
{
390+
action: Protocol.Preload.SpeculationAction.Prefetch,
391+
key: {
392+
loaderId: 'loaderId:1' as Protocol.Network.LoaderId,
393+
action: Protocol.Preload.SpeculationAction.Prefetch,
394+
url: 'https://example.com/prerendered.html' as Platform.DevToolsPath.UrlString,
395+
},
396+
pipelineId: 'pipelineId:1' as Protocol.Preload.PreloadPipelineId,
397+
status: SDK.PreloadingModel.PreloadingStatus.FAILURE,
398+
prefetchStatus: Protocol.Preload.PrefetchStatus.PrefetchFailedNon2XX,
399+
requestId: 'requestId:1' as Protocol.Network.RequestId,
400+
ruleSetIds: ['ruleSetId:0.1'] as Protocol.Preload.RuleSetId[],
401+
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
402+
},
403+
{
404+
action: Protocol.Preload.SpeculationAction.Prerender,
405+
key: {
406+
loaderId: 'loaderId:1' as Protocol.Network.LoaderId,
407+
action: Protocol.Preload.SpeculationAction.Prerender,
408+
url: 'https://example.com/prerendered.html' as Platform.DevToolsPath.UrlString,
409+
},
410+
pipelineId: 'pipelineId:1' as Protocol.Preload.PreloadPipelineId,
411+
status: SDK.PreloadingModel.PreloadingStatus.FAILURE,
412+
prerenderStatus: Protocol.Preload.PrerenderFinalStatus.PrerenderFailedDuringPrefetch,
413+
disallowedMojoInterface: null,
414+
mismatchedHeaders: null,
415+
ruleSetIds: ['ruleSetId:0.1'] as Protocol.Preload.RuleSetId[],
416+
nodeIds: [1] as Protocol.DOM.BackendNodeId[],
417+
},
418+
] as SDK.PreloadingModel.PreloadingAttempt[]),
419+
ruleSets: [
420+
{
421+
id: 'ruleSetId:0.1' as Protocol.Preload.RuleSetId,
422+
loaderId: 'loaderId:1' as Protocol.Network.LoaderId,
423+
sourceText: `
424+
{
425+
"prerender":[
426+
{
427+
"source": "list",
428+
"urls": ["/prerendered.html"]
429+
}
430+
]
431+
}
432+
`,
433+
},
434+
],
435+
}],
436+
pageURL: 'https://example.com/' as Platform.DevToolsPath.UrlString,
437+
},
438+
['URL', 'Action', 'Rule set', 'Status'],
439+
[
440+
[
441+
'/prerendered.html',
442+
'Prerender',
443+
'example.com/',
444+
// TODO(kenoss): Add string for Protocol.Preload.PrerenderFinalStatus.PrerenderFailedDuringPrefetch.
445+
'Failure -',
446+
],
447+
],
448+
);
449+
450+
assert.isNotNull(grid.shadowRoot);
451+
const cell = getCellByIndexes(grid.shadowRoot, {row: 1, column: 3});
452+
const div = cell.querySelector('div');
453+
assert.strictEqual(div!.getAttribute('style'), 'color:var(--sys-color-error);');
454+
const icon = div!.children[0];
455+
assert.include(icon.shadowRoot!.innerHTML, 'cross-circle-filled');
456+
});
309457
});

0 commit comments

Comments
 (0)