chrome-devtools-frontend 1.0.968818 → 1.0.970391
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/gni/devtools_grd_files.gni +64 -0
- package/front_end/core/i18n/locales/en-US.json +18 -0
- package/front_end/core/i18n/locales/en-XL.json +18 -0
- package/front_end/core/sdk/ChildTargetManager.ts +2 -2
- package/front_end/core/sdk/Connections.ts +6 -1
- package/front_end/core/sdk/NetworkManager.ts +4 -3
- package/front_end/entrypoints/lighthouse_worker/LighthouseService.ts +84 -13
- package/front_end/models/persistence/NetworkPersistenceManager.ts +205 -41
- package/front_end/models/timeline_model/TimelineFrameModel.ts +21 -7
- package/front_end/panels/animation/animationTimeline.css +0 -3
- package/front_end/panels/application/components/ReportsGrid.ts +19 -4
- package/front_end/panels/application/components/trustTokensViewDeleteButton.css +0 -1
- package/front_end/panels/console/consolePinPane.css +0 -17
- package/front_end/panels/css_overview/cssOverviewCompletedView.css +0 -1
- package/front_end/panels/elements/components/adornerSettingsPane.css +0 -1
- package/front_end/panels/elements/components/computedStyleTrace.css +1 -1
- package/front_end/panels/elements/components/elementsBreadcrumbs.css +0 -1
- package/front_end/panels/elements/computedStyleWidgetTree.css +2 -2
- package/front_end/panels/elements/elementsTreeOutline.css +0 -2
- package/front_end/panels/emulation/deviceModeView.css +0 -1
- package/front_end/panels/event_listeners/eventListenersView.css +0 -1
- package/front_end/panels/issues/components/hideIssuesMenu.css +0 -1
- package/front_end/panels/lighthouse/LighthouseController.ts +25 -1
- package/front_end/panels/lighthouse/LighthouseProtocolService.ts +37 -5
- package/front_end/panels/lighthouse/LighthouseStartView.ts +1 -0
- package/front_end/panels/lighthouse/LighthouseStatusView.ts +5 -5
- package/front_end/panels/media/playerListView.css +0 -1
- package/front_end/panels/network/networkLogView.css +0 -4
- package/front_end/panels/network/requestPayloadTree.css +0 -2
- package/front_end/panels/network/signedExchangeInfoTree.css +0 -1
- package/front_end/panels/settings/emulation/components/userAgentClientHintsForm.css +0 -4
- package/front_end/panels/settings/emulation/devicesSettingsTab.css +0 -1
- package/front_end/panels/sources/CSSPlugin.ts +2 -0
- package/front_end/panels/sources/watchExpressionsSidebarPane.css +0 -1
- package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +75 -3
- package/front_end/panels/webauthn/webauthnPane.css +0 -12
- package/front_end/services/puppeteer/PuppeteerConnection.ts +107 -0
- package/front_end/services/puppeteer/puppeteer.ts +9 -0
- package/front_end/third_party/codemirror/package/addon/fold/foldgutter.css +1 -5
- package/front_end/third_party/codemirror.next/README.chromium +10 -0
- package/front_end/third_party/codemirror.next/chunk/codemirror.js +1 -1
- package/front_end/third_party/codemirror.next/chunk/cpp.js +2 -1
- package/front_end/third_party/codemirror.next/chunk/markdown.js +2 -2
- package/front_end/third_party/codemirror.next/chunk/python.js +2 -1
- package/front_end/third_party/codemirror.next/codemirror.next.d.ts +574 -553
- package/front_end/third_party/codemirror.next/package.json +11 -11
- package/front_end/ui/components/adorners/adorner.css +0 -4
- package/front_end/ui/components/buttons/button.css +0 -4
- package/front_end/ui/components/data_grid/dataGrid.css +0 -4
- package/front_end/ui/components/icon_button/iconButton.css +0 -1
- package/front_end/ui/components/markdown_view/MarkdownLinksMap.ts +2 -2
- package/front_end/ui/legacy/TabbedPane.ts +1 -1
- package/front_end/ui/legacy/components/perf_ui/TimelineOverviewPane.ts +2 -2
- package/front_end/ui/legacy/components/source_frame/SourceFrame.ts +4 -2
- package/front_end/ui/legacy/tabbedPane.css +0 -4
- package/front_end/ui/legacy/textButton.css +0 -1
- package/front_end/ui/legacy/toolbar.css +0 -1
- package/package.json +2 -2
- package/scripts/hosted_mode/server.js +5 -0
@@ -4,6 +4,7 @@
|
|
4
4
|
|
5
5
|
import * as Common from '../../core/common/common.js';
|
6
6
|
import * as Platform from '../../core/platform/platform.js';
|
7
|
+
import * as Root from '../../core/root/root.js';
|
7
8
|
import * as SDK from '../../core/sdk/sdk.js';
|
8
9
|
import * as Protocol from '../../generated/protocol.js';
|
9
10
|
import * as Workspace from '../workspace/workspace.js';
|
@@ -31,6 +32,7 @@ export class NetworkPersistenceManager extends Common.ObjectWrapper.ObjectWrappe
|
|
31
32
|
private activeInternal: boolean;
|
32
33
|
private enabled: boolean;
|
33
34
|
private eventDescriptors: Common.EventTarget.EventDescriptor[];
|
35
|
+
#headerOverridesMap: Map<string, HeaderOverrideWithRegex[]> = new Map();
|
34
36
|
|
35
37
|
private constructor(workspace: Workspace.Workspace.WorkspaceImpl) {
|
36
38
|
super();
|
@@ -363,29 +365,97 @@ export class NetworkPersistenceManager extends Common.ObjectWrapper.ObjectWrappe
|
|
363
365
|
}
|
364
366
|
}
|
365
367
|
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
368
|
+
async generateHeaderPatterns(uiSourceCode: Workspace.UISourceCode.UISourceCode):
|
369
|
+
Promise<{headerPatterns: Set<string>, path: string, overridesWithRegex: HeaderOverrideWithRegex[]}> {
|
370
|
+
const headerPatterns = new Set<string>();
|
371
|
+
const content = (await uiSourceCode.requestContent()).content || '';
|
372
|
+
let headerOverrides: HeaderOverride[] = [];
|
373
|
+
try {
|
374
|
+
headerOverrides = JSON.parse(content) as HeaderOverride[];
|
375
|
+
if (!headerOverrides.every(isHeaderOverride)) {
|
376
|
+
throw 'Type mismatch after parsing';
|
373
377
|
}
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
378
|
+
} catch (e) {
|
379
|
+
console.error('Failed to parse', uiSourceCode.url(), 'for locally overriding headers.');
|
380
|
+
return {headerPatterns, path: '', overridesWithRegex: []};
|
381
|
+
}
|
382
|
+
const relativePath = FileSystemWorkspaceBinding.relativePath(uiSourceCode).join('/');
|
383
|
+
const decodedPath = this.decodeLocalPathToUrlPath(relativePath).slice(0, -HEADERS_FILENAME.length);
|
384
|
+
|
385
|
+
const overridesWithRegex: HeaderOverrideWithRegex[] = [];
|
386
|
+
for (const headerOverride of headerOverrides) {
|
387
|
+
headerPatterns.add('http?://' + decodedPath + headerOverride.applyTo);
|
388
|
+
|
389
|
+
// Most servers have the concept of a "directory index", which is a
|
390
|
+
// default resource name for a request targeting a "directory", e. g.
|
391
|
+
// requesting "example.com/path/" would result in the same response as
|
392
|
+
// requesting "example.com/path/index.html". To match this behavior we
|
393
|
+
// generate an additional pattern without "index.html" as the longer
|
394
|
+
// pattern would not match against a shorter request.
|
395
|
+
const {head, tail} = extractDirectoryIndex(headerOverride.applyTo);
|
396
|
+
if (tail) {
|
397
|
+
headerPatterns.add('http?://' + decodedPath + head);
|
398
|
+
|
399
|
+
const pattern = escapeRegex(decodedPath + head) + '(' + escapeRegex(tail) + ')?';
|
400
|
+
const regex = new RegExp('^https?:\/\/' + pattern + '$');
|
401
|
+
overridesWithRegex.push({
|
402
|
+
applyToRegex: regex,
|
403
|
+
headers: headerOverride.headers,
|
404
|
+
});
|
405
|
+
} else {
|
406
|
+
const regex = new RegExp('^https?:\/\/' + escapeRegex(decodedPath + headerOverride.applyTo) + '$');
|
407
|
+
overridesWithRegex.push({
|
408
|
+
applyToRegex: regex,
|
409
|
+
headers: headerOverride.headers,
|
410
|
+
});
|
382
411
|
}
|
412
|
+
}
|
413
|
+
return {headerPatterns, path: decodedPath, overridesWithRegex};
|
414
|
+
}
|
415
|
+
|
416
|
+
async updateInterceptionPatternsForTests(): Promise<void> {
|
417
|
+
await this.#innerUpdateInterceptionPatterns();
|
418
|
+
}
|
419
|
+
|
420
|
+
private updateInterceptionPatterns(): void {
|
421
|
+
void this.updateInterceptionThrottler.schedule(this.#innerUpdateInterceptionPatterns.bind(this));
|
422
|
+
}
|
383
423
|
|
424
|
+
async #innerUpdateInterceptionPatterns(): Promise<void> {
|
425
|
+
this.#headerOverridesMap.clear();
|
426
|
+
if (!this.activeInternal || !this.projectInternal) {
|
384
427
|
return SDK.NetworkManager.MultitargetNetworkManager.instance().setInterceptionHandlerForPatterns(
|
385
|
-
|
386
|
-
|
387
|
-
|
428
|
+
[], this.interceptionHandlerBound);
|
429
|
+
}
|
430
|
+
let patterns = new Set<string>();
|
431
|
+
for (const uiSourceCode of this.projectInternal.uiSourceCodes()) {
|
432
|
+
const pattern = this.patternForFileSystemUISourceCode(uiSourceCode);
|
433
|
+
if (Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.HEADER_OVERRIDES) &&
|
434
|
+
uiSourceCode.name() === HEADERS_FILENAME) {
|
435
|
+
const {headerPatterns, path, overridesWithRegex} = await this.generateHeaderPatterns(uiSourceCode);
|
436
|
+
if (headerPatterns.size > 0) {
|
437
|
+
patterns = new Set([...patterns, ...headerPatterns]);
|
438
|
+
this.#headerOverridesMap.set(path, overridesWithRegex);
|
439
|
+
}
|
440
|
+
} else {
|
441
|
+
patterns.add(pattern);
|
442
|
+
}
|
443
|
+
// Most servers have the concept of a "directory index", which is a
|
444
|
+
// default resource name for a request targeting a "directory", e. g.
|
445
|
+
// requesting "example.com/path/" would result in the same response as
|
446
|
+
// requesting "example.com/path/index.html". To match this behavior we
|
447
|
+
// generate an additional pattern without "index.html" as the longer
|
448
|
+
// pattern would not match against a shorter request.
|
449
|
+
const {head, tail} = extractDirectoryIndex(pattern);
|
450
|
+
if (tail) {
|
451
|
+
patterns.add(head);
|
452
|
+
}
|
388
453
|
}
|
454
|
+
|
455
|
+
return SDK.NetworkManager.MultitargetNetworkManager.instance().setInterceptionHandlerForPatterns(
|
456
|
+
Array.from(patterns).map(
|
457
|
+
pattern => ({urlPattern: pattern, requestStage: Protocol.Fetch.RequestStage.Response})),
|
458
|
+
this.interceptionHandlerBound);
|
389
459
|
}
|
390
460
|
|
391
461
|
private async onUISourceCodeRemoved(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> {
|
@@ -409,7 +479,7 @@ export class NetworkPersistenceManager extends Common.ObjectWrapper.ObjectWrappe
|
|
409
479
|
await this.unbind(uiSourceCode);
|
410
480
|
}
|
411
481
|
|
412
|
-
|
482
|
+
async setProject(project: Workspace.Workspace.Project|null): Promise<void> {
|
413
483
|
if (project === this.projectInternal) {
|
414
484
|
return;
|
415
485
|
}
|
@@ -452,6 +522,49 @@ export class NetworkPersistenceManager extends Common.ObjectWrapper.ObjectWrappe
|
|
452
522
|
}
|
453
523
|
}
|
454
524
|
|
525
|
+
mergeHeaders(baseHeaders: Protocol.Fetch.HeaderEntry[], overrideHeaders: Protocol.Network.Headers):
|
526
|
+
Protocol.Fetch.HeaderEntry[] {
|
527
|
+
const result: Protocol.Fetch.HeaderEntry[] = [];
|
528
|
+
const headerMap = new Map<string, string>();
|
529
|
+
for (const header of baseHeaders) {
|
530
|
+
headerMap.set(header.name, header.value);
|
531
|
+
}
|
532
|
+
for (const [headerName, headerValue] of Object.entries(overrideHeaders)) {
|
533
|
+
headerMap.set(headerName, headerValue);
|
534
|
+
}
|
535
|
+
headerMap.forEach((headerValue, headerName) => {
|
536
|
+
result.push({name: headerName, value: headerValue});
|
537
|
+
});
|
538
|
+
return result;
|
539
|
+
}
|
540
|
+
|
541
|
+
#maybeMergeHeadersForPathSegment(path: string, requestUrl: string, headers: Protocol.Fetch.HeaderEntry[]):
|
542
|
+
Protocol.Fetch.HeaderEntry[] {
|
543
|
+
const headerOverrides = this.#headerOverridesMap.get(path) || [];
|
544
|
+
for (const headerOverride of headerOverrides) {
|
545
|
+
if (headerOverride.applyToRegex.test(requestUrl)) {
|
546
|
+
headers = this.mergeHeaders(headers, headerOverride.headers);
|
547
|
+
}
|
548
|
+
}
|
549
|
+
return headers;
|
550
|
+
}
|
551
|
+
|
552
|
+
handleHeaderInterception(interceptedRequest: SDK.NetworkManager.InterceptedRequest): Protocol.Fetch.HeaderEntry[] {
|
553
|
+
let result: Protocol.Fetch.HeaderEntry[] = interceptedRequest.responseHeaders || [];
|
554
|
+
const urlSegments = this.encodedPathFromUrl(interceptedRequest.request.url).split('/');
|
555
|
+
// Traverse the hierarchy of overrides from the most general to the most
|
556
|
+
// specific. Check with empty string first to match overrides applying to
|
557
|
+
// all domains.
|
558
|
+
// e.g. '', 'www.example.com/', 'www.example.com/path/', ...
|
559
|
+
let path = '';
|
560
|
+
result = this.#maybeMergeHeadersForPathSegment(path, interceptedRequest.request.url, result);
|
561
|
+
for (const segment of urlSegments) {
|
562
|
+
path += segment + '/';
|
563
|
+
result = this.#maybeMergeHeadersForPathSegment(path, interceptedRequest.request.url, result);
|
564
|
+
}
|
565
|
+
return result;
|
566
|
+
}
|
567
|
+
|
455
568
|
private async interceptionHandler(interceptedRequest: SDK.NetworkManager.InterceptedRequest): Promise<void> {
|
456
569
|
const method = interceptedRequest.request.method;
|
457
570
|
if (!this.activeInternal || (method !== 'GET' && method !== 'POST')) {
|
@@ -460,9 +573,16 @@ export class NetworkPersistenceManager extends Common.ObjectWrapper.ObjectWrappe
|
|
460
573
|
const proj = this.projectInternal as FileSystem;
|
461
574
|
const path = proj.fileSystemPath() + '/' + this.encodedPathFromUrl(interceptedRequest.request.url);
|
462
575
|
const fileSystemUISourceCode = proj.uiSourceCodeForURL(path);
|
463
|
-
|
576
|
+
let responseHeaders: Protocol.Fetch.HeaderEntry[] = [];
|
577
|
+
if (Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.HEADER_OVERRIDES)) {
|
578
|
+
responseHeaders = this.handleHeaderInterception(interceptedRequest);
|
579
|
+
}
|
580
|
+
if (!fileSystemUISourceCode && !responseHeaders.length) {
|
464
581
|
return;
|
465
582
|
}
|
583
|
+
if (!responseHeaders.length) {
|
584
|
+
responseHeaders = interceptedRequest.responseHeaders || [];
|
585
|
+
}
|
466
586
|
|
467
587
|
let mimeType = '';
|
468
588
|
if (interceptedRequest.responseHeaders) {
|
@@ -477,32 +597,41 @@ export class NetworkPersistenceManager extends Common.ObjectWrapper.ObjectWrappe
|
|
477
597
|
if (!mimeType) {
|
478
598
|
const expectedResourceType =
|
479
599
|
Common.ResourceType.resourceTypes[interceptedRequest.resourceType] || Common.ResourceType.resourceTypes.Other;
|
480
|
-
mimeType = fileSystemUISourceCode
|
600
|
+
mimeType = fileSystemUISourceCode?.mimeType() || '';
|
481
601
|
if (Common.ResourceType.ResourceType.fromMimeType(mimeType) !== expectedResourceType) {
|
482
602
|
mimeType = expectedResourceType.canonicalMimeType();
|
483
603
|
}
|
484
604
|
}
|
485
|
-
const project = fileSystemUISourceCode.project() as FileSystem;
|
486
|
-
|
487
|
-
this.originalResponseContentPromises.set(
|
488
|
-
fileSystemUISourceCode, interceptedRequest.responseBody().then(response => {
|
489
|
-
if (response.error || response.content === null) {
|
490
|
-
return null;
|
491
|
-
}
|
492
|
-
if (response.encoded) {
|
493
|
-
const text = atob(response.content);
|
494
|
-
const data = new Uint8Array(text.length);
|
495
|
-
for (let i = 0; i < text.length; ++i) {
|
496
|
-
data[i] = text.charCodeAt(i);
|
497
|
-
}
|
498
|
-
return new TextDecoder('utf-8').decode(data);
|
499
|
-
}
|
500
|
-
return response.content;
|
501
|
-
}));
|
502
605
|
|
503
|
-
|
504
|
-
|
505
|
-
|
606
|
+
if (fileSystemUISourceCode) {
|
607
|
+
this.originalResponseContentPromises.set(
|
608
|
+
fileSystemUISourceCode, interceptedRequest.responseBody().then(response => {
|
609
|
+
if (response.error || response.content === null) {
|
610
|
+
return null;
|
611
|
+
}
|
612
|
+
if (response.encoded) {
|
613
|
+
const text = atob(response.content);
|
614
|
+
const data = new Uint8Array(text.length);
|
615
|
+
for (let i = 0; i < text.length; ++i) {
|
616
|
+
data[i] = text.charCodeAt(i);
|
617
|
+
}
|
618
|
+
return new TextDecoder('utf-8').decode(data);
|
619
|
+
}
|
620
|
+
return response.content;
|
621
|
+
}));
|
622
|
+
|
623
|
+
const project = fileSystemUISourceCode.project() as FileSystem;
|
624
|
+
const blob = await project.requestFileBlob(fileSystemUISourceCode);
|
625
|
+
if (blob) {
|
626
|
+
void interceptedRequest.continueRequestWithContent(
|
627
|
+
new Blob([blob], {type: mimeType}), /* encoded */ false, responseHeaders);
|
628
|
+
}
|
629
|
+
} else {
|
630
|
+
const responseBody = await interceptedRequest.responseBody();
|
631
|
+
if (!responseBody.error && responseBody.content) {
|
632
|
+
void interceptedRequest.continueRequestWithContent(
|
633
|
+
new Blob([responseBody.content], {type: mimeType}), /* encoded */ true, responseHeaders);
|
634
|
+
}
|
506
635
|
}
|
507
636
|
}
|
508
637
|
}
|
@@ -512,6 +641,8 @@ const RESERVED_FILENAMES = new Set<string>([
|
|
512
641
|
'com8', 'com9', 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9',
|
513
642
|
]);
|
514
643
|
|
644
|
+
const HEADERS_FILENAME = '.headers';
|
645
|
+
|
515
646
|
// TODO(crbug.com/1167717): Make this a const enum again
|
516
647
|
// eslint-disable-next-line rulesdir/const_enum
|
517
648
|
export enum Events {
|
@@ -521,3 +652,36 @@ export enum Events {
|
|
521
652
|
export type EventTypes = {
|
522
653
|
[Events.ProjectChanged]: Workspace.Workspace.Project|null,
|
523
654
|
};
|
655
|
+
|
656
|
+
interface HeaderOverride {
|
657
|
+
applyTo: string;
|
658
|
+
headers: Protocol.Network.Headers;
|
659
|
+
}
|
660
|
+
|
661
|
+
interface HeaderOverrideWithRegex {
|
662
|
+
applyToRegex: RegExp;
|
663
|
+
headers: Protocol.Network.Headers;
|
664
|
+
}
|
665
|
+
|
666
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
667
|
+
function isHeaderOverride(arg: any): arg is HeaderOverride {
|
668
|
+
if (!(arg && arg.applyTo && typeof (arg.applyTo === 'string') && arg.headers && Object.keys(arg.headers).length)) {
|
669
|
+
return false;
|
670
|
+
}
|
671
|
+
return Object.values(arg.headers).every(value => typeof value === 'string');
|
672
|
+
}
|
673
|
+
|
674
|
+
export function escapeRegex(pattern: string): string {
|
675
|
+
return Platform.StringUtilities.escapeCharacters(pattern, '[]{}()\\.^$+|-,?').replaceAll('*', '.*');
|
676
|
+
}
|
677
|
+
|
678
|
+
export function extractDirectoryIndex(pattern: string): {head: string, tail?: string} {
|
679
|
+
const lastSlash = pattern.lastIndexOf('/');
|
680
|
+
const tail = lastSlash >= 0 ? pattern.slice(lastSlash + 1) : pattern;
|
681
|
+
const head = lastSlash >= 0 ? pattern.slice(0, lastSlash + 1) : '';
|
682
|
+
const regex = new RegExp('^' + escapeRegex(tail) + '$');
|
683
|
+
if (regex.test('index.html') || regex.test('index.htm') || regex.test('index.php')) {
|
684
|
+
return {head, tail};
|
685
|
+
}
|
686
|
+
return {head: pattern};
|
687
|
+
}
|
@@ -138,10 +138,10 @@ export class TimelineFrameModel {
|
|
138
138
|
}
|
139
139
|
this.lastBeginFrame = startTime;
|
140
140
|
|
141
|
-
this.beginFrameQueue.addFrameIfNotExists(seqId, startTime, false);
|
141
|
+
this.beginFrameQueue.addFrameIfNotExists(seqId, startTime, false, false);
|
142
142
|
}
|
143
143
|
|
144
|
-
handleDroppedFrame(startTime: number, seqId: number): void {
|
144
|
+
handleDroppedFrame(startTime: number, seqId: number, isPartial: boolean): void {
|
145
145
|
if (!this.lastFrame) {
|
146
146
|
this.startFrame(startTime);
|
147
147
|
}
|
@@ -149,8 +149,9 @@ export class TimelineFrameModel {
|
|
149
149
|
// This line handles the case where no BeginFrame event is issued for
|
150
150
|
// the dropped frame. In this situation, add a BeginFrame to the queue
|
151
151
|
// as if it actually occurred.
|
152
|
-
this.beginFrameQueue.addFrameIfNotExists(seqId, startTime, true);
|
152
|
+
this.beginFrameQueue.addFrameIfNotExists(seqId, startTime, true, isPartial);
|
153
153
|
this.beginFrameQueue.setDropped(seqId, true);
|
154
|
+
this.beginFrameQueue.setPartial(seqId, isPartial);
|
154
155
|
}
|
155
156
|
|
156
157
|
handleDrawFrame(startTime: number, seqId: number): void {
|
@@ -187,6 +188,9 @@ export class TimelineFrameModel {
|
|
187
188
|
if (frame.isDropped) {
|
188
189
|
this.lastFrame.dropped = true;
|
189
190
|
}
|
191
|
+
if (frame.isPartial) {
|
192
|
+
this.lastFrame.isPartial = true;
|
193
|
+
}
|
190
194
|
}
|
191
195
|
}
|
192
196
|
this.mainFrameCommitted = false;
|
@@ -318,7 +322,7 @@ export class TimelineFrameModel {
|
|
318
322
|
} else if (event.name === RecordType.NeedsBeginFrameChanged) {
|
319
323
|
this.handleNeedFrameChanged(timestamp, event.args['data'] && event.args['data']['needsBeginFrame']);
|
320
324
|
} else if (event.name === RecordType.DroppedFrame) {
|
321
|
-
this.handleDroppedFrame(timestamp, event.args['frameSeqId']);
|
325
|
+
this.handleDroppedFrame(timestamp, event.args['frameSeqId'], event.args['hasPartialUpdate']);
|
322
326
|
}
|
323
327
|
}
|
324
328
|
|
@@ -426,6 +430,7 @@ export class TimelineFrame {
|
|
426
430
|
cpuTime: number;
|
427
431
|
idle: boolean;
|
428
432
|
dropped: boolean;
|
433
|
+
isPartial: boolean;
|
429
434
|
layerTree: TracingFrameLayerTree|null;
|
430
435
|
paints: LayerPaintEvent[];
|
431
436
|
mainFrameId: number|undefined;
|
@@ -439,6 +444,7 @@ export class TimelineFrame {
|
|
439
444
|
this.cpuTime = 0;
|
440
445
|
this.idle = false;
|
441
446
|
this.dropped = false;
|
447
|
+
this.isPartial = false;
|
442
448
|
this.layerTree = null;
|
443
449
|
this.paints = [];
|
444
450
|
this.mainFrameId = undefined;
|
@@ -545,10 +551,12 @@ class BeginFrameInfo {
|
|
545
551
|
seqId: number;
|
546
552
|
startTime: number;
|
547
553
|
isDropped: boolean;
|
548
|
-
|
554
|
+
isPartial: boolean;
|
555
|
+
constructor(seqId: number, startTime: number, isDropped: boolean, isPartial: boolean) {
|
549
556
|
this.seqId = seqId;
|
550
557
|
this.startTime = startTime;
|
551
558
|
this.isDropped = isDropped;
|
559
|
+
this.isPartial = isPartial;
|
552
560
|
}
|
553
561
|
}
|
554
562
|
|
@@ -570,9 +578,9 @@ export class TimelineFrameBeginFrameQueue {
|
|
570
578
|
}
|
571
579
|
|
572
580
|
// Add a BeginFrame to the queue, if it does not already exit.
|
573
|
-
addFrameIfNotExists(seqId: number, startTime: number, isDropped: boolean): void {
|
581
|
+
addFrameIfNotExists(seqId: number, startTime: number, isDropped: boolean, isPartial: boolean): void {
|
574
582
|
if (!(seqId in this.mapFrames)) {
|
575
|
-
this.mapFrames[seqId] = new BeginFrameInfo(seqId, startTime, isDropped);
|
583
|
+
this.mapFrames[seqId] = new BeginFrameInfo(seqId, startTime, isDropped, isPartial);
|
576
584
|
this.queueFrames.push(seqId);
|
577
585
|
}
|
578
586
|
}
|
@@ -584,6 +592,12 @@ export class TimelineFrameBeginFrameQueue {
|
|
584
592
|
}
|
585
593
|
}
|
586
594
|
|
595
|
+
setPartial(seqId: number, isPartial: boolean): void {
|
596
|
+
if (seqId in this.mapFrames) {
|
597
|
+
this.mapFrames[seqId].isPartial = isPartial;
|
598
|
+
}
|
599
|
+
}
|
600
|
+
|
587
601
|
processPendingBeginFramesOnDrawFrame(seqId: number): BeginFrameInfo[] {
|
588
602
|
const framesToVisualize: BeginFrameInfo[] = [];
|
589
603
|
|
@@ -338,7 +338,6 @@ text.animation-timeline-grid-label {
|
|
338
338
|
background: var(--color-background-elevation-2);
|
339
339
|
opacity: 0%;
|
340
340
|
border-right: 1px solid var(--color-details-hairline);
|
341
|
-
cursor: pointer;
|
342
341
|
}
|
343
342
|
|
344
343
|
.animation-buffer-preview:focus-visible {
|
@@ -410,7 +409,6 @@ text.animation-timeline-grid-label {
|
|
410
409
|
background-color: transparent;
|
411
410
|
flex: 1 0 auto;
|
412
411
|
text-align: center;
|
413
|
-
cursor: pointer;
|
414
412
|
}
|
415
413
|
|
416
414
|
.animation-playback-rate-button:first-child {
|
@@ -454,7 +452,6 @@ text.animation-timeline-grid-label {
|
|
454
452
|
box-shadow: 0 1px 4px 0 var(--color-background-highlight);
|
455
453
|
z-index: 100;
|
456
454
|
display: none;
|
457
|
-
cursor: pointer;
|
458
455
|
font-weight: 700;
|
459
456
|
color: var(--color-background);
|
460
457
|
}
|
@@ -19,6 +19,21 @@ const UIStrings = {
|
|
19
19
|
*(https://developers.google.com/web/updates/2018/09/reportingapi#sending)
|
20
20
|
*/
|
21
21
|
noReportsToDisplay: 'No reports to display',
|
22
|
+
/**
|
23
|
+
*@description Column header for a table displaying Reporting API reports.
|
24
|
+
*Status is one of 'Queued', 'Pending', 'MarkedForRemoval' or 'Success'.
|
25
|
+
*/
|
26
|
+
status: 'Status',
|
27
|
+
/**
|
28
|
+
*@description Column header for a table displaying Reporting API reports.
|
29
|
+
*Destination is the name of the endpoint the report is being sent to.
|
30
|
+
*/
|
31
|
+
destination: 'Destination',
|
32
|
+
/**
|
33
|
+
*@description Column header for a table displaying Reporting API reports.
|
34
|
+
*The column contains the timestamp of when a report was generated.
|
35
|
+
*/
|
36
|
+
generatedAt: 'Generated at',
|
22
37
|
};
|
23
38
|
const str_ = i18n.i18n.registerUIStrings('panels/application/components/ReportsGrid.ts', UIStrings);
|
24
39
|
export const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
@@ -38,7 +53,7 @@ export class ReportsGridStatusHeader extends HTMLElement {
|
|
38
53
|
// Disabled until https://crbug.com/1079231 is fixed.
|
39
54
|
// clang-format off
|
40
55
|
render(html`
|
41
|
-
${
|
56
|
+
${i18nString(UIStrings.status)}
|
42
57
|
<x-link href="https://web.dev/reporting-api/#report-status">
|
43
58
|
<${IconButton.Icon.Icon.litTagName} class="inline-icon" .data=${{
|
44
59
|
iconName: 'help_outline',
|
@@ -93,7 +108,7 @@ export class ReportsGrid extends HTMLElement {
|
|
93
108
|
},
|
94
109
|
{
|
95
110
|
id: 'status',
|
96
|
-
title:
|
111
|
+
title: i18nString(UIStrings.status),
|
97
112
|
widthWeighting: 20,
|
98
113
|
hideable: false,
|
99
114
|
visible: true,
|
@@ -103,14 +118,14 @@ export class ReportsGrid extends HTMLElement {
|
|
103
118
|
},
|
104
119
|
{
|
105
120
|
id: 'destination',
|
106
|
-
title:
|
121
|
+
title: i18nString(UIStrings.destination),
|
107
122
|
widthWeighting: 20,
|
108
123
|
hideable: false,
|
109
124
|
visible: true,
|
110
125
|
},
|
111
126
|
{
|
112
127
|
id: 'timestamp',
|
113
|
-
title:
|
128
|
+
title: i18nString(UIStrings.generatedAt),
|
114
129
|
widthWeighting: 20,
|
115
130
|
hideable: false,
|
116
131
|
visible: true,
|
@@ -71,23 +71,6 @@
|
|
71
71
|
overflow: hidden;
|
72
72
|
}
|
73
73
|
|
74
|
-
.console-delete-pin {
|
75
|
-
position: absolute;
|
76
|
-
top: 8px;
|
77
|
-
left: 8px;
|
78
|
-
opacity: 70%;
|
79
|
-
cursor: pointer;
|
80
|
-
}
|
81
|
-
|
82
|
-
.console-delete-pin:hover,
|
83
|
-
.console-delete-pin:focus-visible {
|
84
|
-
opacity: 100%;
|
85
|
-
}
|
86
|
-
|
87
|
-
:host-context(.-theme-with-dark-background) .console-delete-pin {
|
88
|
-
filter: brightness(2);
|
89
|
-
}
|
90
|
-
|
91
74
|
.console-pin-name:focus-within {
|
92
75
|
background: var(--color-background);
|
93
76
|
box-shadow: var(--legacy-focus-ring-active-shadow) inset;
|
@@ -15,7 +15,7 @@
|
|
15
15
|
|
16
16
|
.tree-outline li:hover {
|
17
17
|
background-color: var(--legacy-focus-bg-color);
|
18
|
-
cursor:
|
18
|
+
cursor: text;
|
19
19
|
}
|
20
20
|
|
21
21
|
.tree-outline li::before {
|
@@ -35,7 +35,7 @@
|
|
35
35
|
margin: 1px 0 0;
|
36
36
|
padding: 1em 0;
|
37
37
|
width: 100%;
|
38
|
-
cursor:
|
38
|
+
cursor: text;
|
39
39
|
color: var(--color-text-secondary);
|
40
40
|
font-size: 11px;
|
41
41
|
font-weight: 400;
|
@@ -203,7 +203,6 @@ select {
|
|
203
203
|
border-radius: 5px;
|
204
204
|
border: 1px solid var(--issue-color-yellow);
|
205
205
|
background-color: var(--issue-color-yellow);
|
206
|
-
cursor: pointer;
|
207
206
|
}
|
208
207
|
|
209
208
|
.elements-gutter-decoration.elements-has-decorated-children {
|
@@ -278,7 +277,6 @@ select {
|
|
278
277
|
position: absolute;
|
279
278
|
top: 0;
|
280
279
|
left: 0;
|
281
|
-
cursor: pointer;
|
282
280
|
width: 15px;
|
283
281
|
height: 15px;
|
284
282
|
}
|