browsertime 23.3.2 → 23.4.0
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/CHANGELOG.md +17 -0
- package/bin/browsertime.js +1 -0
- package/browserscripts/pageinfo/largestContentfulPaintInfo.js +38 -3
- package/browserscripts/timings/largestContentfulPaint.js +35 -2
- package/browserscripts/timings/userTimings.js +17 -9
- package/lib/chrome/webdriver/chromium.js +18 -0
- package/lib/chrome/webdriver/devtools/domComplete.js +21 -0
- package/lib/chrome/webdriver/devtools/element.js +29 -0
- package/lib/chrome/webdriver/devtools/fcp.js +23 -0
- package/lib/chrome/webdriver/devtools/inp.js +70 -0
- package/lib/chrome/webdriver/devtools/lcp.js +53 -0
- package/lib/chrome/webdriver/devtools/loadEventEnd.js +21 -0
- package/lib/chrome/webdriver/devtools/loaf.js +60 -0
- package/lib/chrome/webdriver/devtools/longtask.js +24 -0
- package/lib/chrome/webdriver/devtools/ttfb.js +21 -0
- package/lib/firefox/settings/firefoxPreferences.js +11 -2
- package/lib/firefox/webdriver/firefox.js +9 -2
- package/lib/support/cli.js +7 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Browsertime changelog (we do [semantic versioning](https://semver.org))
|
|
2
2
|
|
|
3
|
+
## 23.4.0 - 2024-11-22
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
* Collect more information about the largest contentful paint [#2206](https://github.com/sitespeedio/browsertime/pull/2206)
|
|
7
|
+
* Add extra timings and new custom timeline to the Chrome timeline [#2217](https://github.com/sitespeedio/browsertime/pull/2217). Inspired by [
|
|
8
|
+
Andy Davies](https://github.com/andydavies) cool [https://github.com/andydavies/perf-timeline-to-devtools-profile](https://github.com/andydavies/perf-timeline-to-devtools-profile).
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
* More configuration tuning for Firefox [#2216](https://github.com/sitespeedio/browsertime/pull/2216).
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## 23.3.3 - 2024-11-22
|
|
15
|
+
### Fixed
|
|
16
|
+
* Disable search suggestion requests for Firefox [#2215](https://github.com/sitespeedio/browsertime/pull/2215)
|
|
17
|
+
* Fix for including HTTP requests in the Firefox profile [#2214](https://github.com/sitespeedio/browsertime/pull/2214).
|
|
18
|
+
* Fix when running --enabkleProfileRun and get visual metrics. It used to generate an error log, this fixes that [#2213](https://github.com/sitespeedio/browsertime/pull/2213).
|
|
19
|
+
|
|
3
20
|
## 23.3.2 - 2024-11-21
|
|
4
21
|
### Fixed
|
|
5
22
|
* Disable some calls back to home from Firefox [#2212](https://github.com/sitespeedio/browsertime/pull/2212).
|
package/bin/browsertime.js
CHANGED
|
@@ -162,6 +162,7 @@ async function run(urls, options) {
|
|
|
162
162
|
if (options.enableProfileRun) {
|
|
163
163
|
if (options.browser === 'firefox') {
|
|
164
164
|
options.firefox.geckoProfiler = true;
|
|
165
|
+
options.firefox.collectMozLog = true;
|
|
165
166
|
} else if (options.browser === 'chrome') {
|
|
166
167
|
options.chrome.timeline = true;
|
|
167
168
|
options.cpu = true;
|
|
@@ -35,24 +35,59 @@
|
|
|
35
35
|
const observer = new PerformanceObserver(list => {});
|
|
36
36
|
observer.observe({ type: 'largest-contentful-paint', buffered: true });
|
|
37
37
|
const entries = observer.takeRecords();
|
|
38
|
+
|
|
39
|
+
const navEntries = performance.getEntriesByType('navigation');
|
|
40
|
+
const navEntry = navEntries.length > 0 ? navEntries[0] : null;
|
|
41
|
+
const ttfb = navEntry ? navEntry.responseStart : 0;
|
|
42
|
+
|
|
38
43
|
const candidates = [];
|
|
39
44
|
for (let entry of entries) {
|
|
40
45
|
const element = entry.element;
|
|
46
|
+
const lcpTime = Math.max(entry.renderTime,entry.loadTime);
|
|
47
|
+
let resourceLoadDelay = 0;
|
|
48
|
+
let resourceLoadDuration = 0;
|
|
49
|
+
let elementRenderDelay = 0;
|
|
50
|
+
|
|
51
|
+
if (navEntry) {
|
|
52
|
+
if (entry.url) {
|
|
53
|
+
const resourceEntries = performance.getEntriesByName(entry.url);
|
|
54
|
+
const resourceEntry = resourceEntries.length > 0 ? resourceEntries[resourceEntries.length - 1] : null;
|
|
55
|
+
if (resourceEntry) {
|
|
56
|
+
resourceLoadDelay = resourceEntry.startTime - ttfb;
|
|
57
|
+
resourceLoadDuration = resourceEntry.responseEnd - resourceEntry.startTime;
|
|
58
|
+
elementRenderDelay = lcpTime - resourceEntry.responseEnd;
|
|
59
|
+
} else {
|
|
60
|
+
// If resource entry not found, set delays appropriately
|
|
61
|
+
resourceLoadDelay = 0;
|
|
62
|
+
resourceLoadDuration = 0;
|
|
63
|
+
elementRenderDelay = lcpTime - ttfb;
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
// For inline resources (text elements or inline images)
|
|
67
|
+
resourceLoadDelay = 0;
|
|
68
|
+
resourceLoadDuration = 0;
|
|
69
|
+
elementRenderDelay = lcpTime - ttfb;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
41
73
|
candidates.push(
|
|
42
74
|
{
|
|
43
75
|
duration: entry.duration,
|
|
44
76
|
id: entry.id,
|
|
45
77
|
url: entry.url,
|
|
46
78
|
loadTime: Number(entry.loadTime.toFixed(0)),
|
|
47
|
-
renderTime: Number(
|
|
79
|
+
renderTime: Number(lcpTime.toFixed(0)),
|
|
48
80
|
size: entry.size,
|
|
49
81
|
startTime: Number(entry.startTime.toFixed(0)),
|
|
50
82
|
tagName: element ? element.tagName : '',
|
|
51
83
|
className: element ? element.className : '',
|
|
52
84
|
domPath: element ? (getDomPath(element)).join( ' > ') : '',
|
|
53
|
-
tag: element ? (element.cloneNode(false)).outerHTML : ''
|
|
85
|
+
tag: element ? (element.cloneNode(false)).outerHTML : '',
|
|
86
|
+
ttfb: Number(ttfb.toFixed(0)),
|
|
87
|
+
resourceLoadDelay: Number(resourceLoadDelay.toFixed(0)),
|
|
88
|
+
resourceLoadDuration: Number(resourceLoadDuration.toFixed(0)),
|
|
89
|
+
elementRenderDelay: Number(elementRenderDelay.toFixed(0))
|
|
54
90
|
}
|
|
55
|
-
|
|
56
91
|
)
|
|
57
92
|
}
|
|
58
93
|
return candidates;
|
|
@@ -34,20 +34,53 @@
|
|
|
34
34
|
const observer = new PerformanceObserver(list => {});
|
|
35
35
|
observer.observe({ type: 'largest-contentful-paint', buffered: true });
|
|
36
36
|
const entries = observer.takeRecords();
|
|
37
|
+
const navEntries = performance.getEntriesByType('navigation');
|
|
38
|
+
const navEntry = navEntries.length > 0 ? navEntries[0] : null;
|
|
39
|
+
const ttfb = navEntry ? navEntry.responseStart : 0;
|
|
37
40
|
if (entries.length > 0) {
|
|
38
41
|
const largestEntry = entries[entries.length - 1];
|
|
42
|
+
const lcpTime = Math.max(largestEntry.renderTime,largestEntry.loadTime);
|
|
43
|
+
let resourceLoadDelay = 0;
|
|
44
|
+
let resourceLoadDuration = 0;
|
|
45
|
+
let elementRenderDelay = 0;
|
|
46
|
+
|
|
47
|
+
if (navEntry) {
|
|
48
|
+
if (largestEntry.url) {
|
|
49
|
+
const resourceEntries = performance.getEntriesByName(largestEntry.url);
|
|
50
|
+
const resourceEntry = resourceEntries.length > 0 ? resourceEntries[resourceEntries.length - 1] : null;
|
|
51
|
+
if (resourceEntry) {
|
|
52
|
+
resourceLoadDelay = resourceEntry.startTime - ttfb;
|
|
53
|
+
resourceLoadDuration = resourceEntry.responseEnd - resourceEntry.startTime;
|
|
54
|
+
elementRenderDelay = lcpTime - resourceEntry.responseEnd;
|
|
55
|
+
} else {
|
|
56
|
+
// If resource entry not found, set delays appropriately
|
|
57
|
+
resourceLoadDelay = 0;
|
|
58
|
+
resourceLoadDuration = 0;
|
|
59
|
+
elementRenderDelay = lcpTime - ttfb;
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
// For inline resources (text elements or inline images)
|
|
63
|
+
resourceLoadDelay = 0;
|
|
64
|
+
resourceLoadDuration = 0;
|
|
65
|
+
elementRenderDelay = lcpTime - ttfb;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
39
68
|
return {
|
|
40
69
|
duration: largestEntry.duration,
|
|
41
70
|
id: largestEntry.id,
|
|
42
71
|
url: largestEntry.url,
|
|
43
72
|
loadTime: Number(largestEntry.loadTime.toFixed(0)),
|
|
44
|
-
renderTime: Number(
|
|
73
|
+
renderTime: Number(lcpTime.toFixed(0)),
|
|
45
74
|
size: largestEntry.size,
|
|
46
75
|
startTime: Number(largestEntry.startTime.toFixed(0)),
|
|
47
76
|
tagName: largestEntry.element ? largestEntry.element.tagName : '',
|
|
48
77
|
className :largestEntry.element ? largestEntry.element.className : '',
|
|
49
78
|
domPath: largestEntry.element ? (getDomPath(largestEntry.element)).join( ' > ') : '',
|
|
50
|
-
tag: largestEntry.element ? (largestEntry.element.cloneNode(false)).outerHTML : ''
|
|
79
|
+
tag: largestEntry.element ? (largestEntry.element.cloneNode(false)).outerHTML : '',
|
|
80
|
+
ttfb: Number(ttfb.toFixed(0)),
|
|
81
|
+
resourceLoadDelay: Number(resourceLoadDelay.toFixed(0)),
|
|
82
|
+
resourceLoadDuration: Number(resourceLoadDuration.toFixed(0)),
|
|
83
|
+
elementRenderDelay: Number(elementRenderDelay.toFixed(0))
|
|
51
84
|
};
|
|
52
85
|
} else return;
|
|
53
86
|
})();
|
|
@@ -8,10 +8,14 @@
|
|
|
8
8
|
);
|
|
9
9
|
|
|
10
10
|
for (const mark of myMarks) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
if (mark.detail && mark.detail.devtools) {
|
|
12
|
+
continue;
|
|
13
|
+
} else {
|
|
14
|
+
marks.push({
|
|
15
|
+
name: mark.name,
|
|
16
|
+
startTime: mark.startTime
|
|
17
|
+
});
|
|
18
|
+
}
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
const myMeasures = Array.prototype.slice.call(
|
|
@@ -19,11 +23,15 @@
|
|
|
19
23
|
);
|
|
20
24
|
|
|
21
25
|
for (const measure of myMeasures) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
if (measure.detail && measure.detail.devtools) {
|
|
27
|
+
continue;
|
|
28
|
+
} else {
|
|
29
|
+
measures.push({
|
|
30
|
+
name: measure.name,
|
|
31
|
+
duration: measure.duration,
|
|
32
|
+
startTime: measure.startTime
|
|
33
|
+
});
|
|
34
|
+
}
|
|
27
35
|
}
|
|
28
36
|
}
|
|
29
37
|
|
|
@@ -18,6 +18,14 @@ import { ChromeDevtoolsProtocol } from '../chromeDevtoolsProtocol.js';
|
|
|
18
18
|
import { NetworkManager } from '../networkManager.js';
|
|
19
19
|
import { Android, isAndroidConfigured } from '../../android/index.js';
|
|
20
20
|
import { getRenderBlocking } from './traceUtilities.js';
|
|
21
|
+
import { loafTimeLine } from './devtools/loaf.js';
|
|
22
|
+
import { lcpTimeLine } from './devtools/lcp.js';
|
|
23
|
+
import { fcpTimeLine } from './devtools/fcp.js';
|
|
24
|
+
import { ttfbTimeLine } from './devtools/ttfb.js';
|
|
25
|
+
import { elementTimeLine } from './devtools/element.js';
|
|
26
|
+
import { longtaskTimeLine } from './devtools/longtask.js';
|
|
27
|
+
import { domCompleteTimeLine } from './devtools/domComplete.js';
|
|
28
|
+
import { loadEventEndTimeLine } from './devtools/loadEventEnd.js';
|
|
21
29
|
const unlink = promisify(_unlink);
|
|
22
30
|
const rm = promisify(_rm);
|
|
23
31
|
|
|
@@ -192,6 +200,16 @@ export class Chromium {
|
|
|
192
200
|
this.isTracing &&
|
|
193
201
|
this.options.chrome.timelineRecordingType !== 'custom'
|
|
194
202
|
) {
|
|
203
|
+
if (this.options.chrome.timelineExtras != false) {
|
|
204
|
+
await runner.runScript(ttfbTimeLine, 'TTFB_SCRIPT');
|
|
205
|
+
await runner.runScript(loafTimeLine, 'LOAF_SCRIPT');
|
|
206
|
+
await runner.runScript(fcpTimeLine, 'FCP_SCRIPT');
|
|
207
|
+
await runner.runScript(lcpTimeLine, 'LCP_SCRIPT');
|
|
208
|
+
await runner.runScript(elementTimeLine, 'ELEMENT_SCRIPT');
|
|
209
|
+
await runner.runScript(longtaskTimeLine, 'LONGTASK_SCRIPT');
|
|
210
|
+
await runner.runScript(domCompleteTimeLine, 'DOMCOMPLETE_SCRIPT');
|
|
211
|
+
await runner.runScript(loadEventEndTimeLine, 'LOADEVENTEND_SCRIPT');
|
|
212
|
+
}
|
|
195
213
|
// We are ready and can stop collecting events
|
|
196
214
|
this.isTracing = false;
|
|
197
215
|
this.events = await this.cdpClient.stopTrace();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// https://developer.chrome.com/docs/devtools/performance/extension
|
|
2
|
+
|
|
3
|
+
export const domCompleteTimeLine = `
|
|
4
|
+
(function () {
|
|
5
|
+
performance.mark('DOM Complete', {
|
|
6
|
+
startTime: window.performance.getEntriesByType('navigation')[0].domComplete,
|
|
7
|
+
detail: {
|
|
8
|
+
devtools: {
|
|
9
|
+
dataType: 'marker',
|
|
10
|
+
trackGroup: 'Browsertime Timeline',
|
|
11
|
+
track: 'Metrics',
|
|
12
|
+
color: 'tertiary-dark',
|
|
13
|
+
tooltipText: 'DOM Complete',
|
|
14
|
+
properties: [
|
|
15
|
+
['DOMComplete', window.performance.getEntriesByType('navigation')[0].domComplete]
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
})();
|
|
21
|
+
`;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// https://developer.chrome.com/docs/devtools/performance/extension
|
|
2
|
+
|
|
3
|
+
export const elementTimeLine = `
|
|
4
|
+
(function () {
|
|
5
|
+
const observer = new PerformanceObserver(list => { });
|
|
6
|
+
observer.observe({ type: 'element', buffered: true, includeSoftNavigationObservations: true });
|
|
7
|
+
const entries = observer.takeRecords();
|
|
8
|
+
for (let entry of entries) {
|
|
9
|
+
performance.mark(entry.identifier, {
|
|
10
|
+
startTime: entry.renderTime,
|
|
11
|
+
detail: {
|
|
12
|
+
devtools: {
|
|
13
|
+
dataType: 'marker',
|
|
14
|
+
track: 'Metrics',
|
|
15
|
+
trackGroup: 'Browsertime Timeline',
|
|
16
|
+
color: 'primary',
|
|
17
|
+
tooltipText: entry.identifier + ' element',
|
|
18
|
+
properties: [
|
|
19
|
+
['tagName', '' + entry.element ? entry.element.tagName:''],
|
|
20
|
+
['className', '' + entry.element ? entry.element.className:''],
|
|
21
|
+
['URL', '' + entry.url]
|
|
22
|
+
['Render', entry.renderTime]
|
|
23
|
+
],
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
})();
|
|
29
|
+
`;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// https://developer.chrome.com/docs/devtools/performance/extension
|
|
2
|
+
|
|
3
|
+
export const fcpTimeLine = `
|
|
4
|
+
(function () {
|
|
5
|
+
const observer = new PerformanceObserver(list => { });
|
|
6
|
+
observer.observe({ type: 'paint', buffered: true, includeSoftNavigationObservations: true });
|
|
7
|
+
const entries = observer.takeRecords();
|
|
8
|
+
for (let entry of entries) {
|
|
9
|
+
performance.mark(entry.name === 'first-contentful-paint' ? 'FCP': 'FP', {
|
|
10
|
+
startTime: entry.startTime,
|
|
11
|
+
detail: {
|
|
12
|
+
devtools: {
|
|
13
|
+
dataType: 'marker',
|
|
14
|
+
track: 'Metrics',
|
|
15
|
+
trackGroup: 'Browsertime Timeline',
|
|
16
|
+
color: 'primary',
|
|
17
|
+
tooltipText: entry.name
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
})();
|
|
23
|
+
`;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Adopted from https://github.com/andydavies/perf-timeline-to-devtools-profile
|
|
2
|
+
// https://developer.chrome.com/docs/devtools/performance/extension
|
|
3
|
+
|
|
4
|
+
export const inpTimeLine = `
|
|
5
|
+
(function() {
|
|
6
|
+
const observer = new PerformanceObserver(list => {});
|
|
7
|
+
for (const type of ['event']) {
|
|
8
|
+
observer.observe({ type, buffered: true, durationThreshold: 0, includeSoftNavigationObservations: true });
|
|
9
|
+
}
|
|
10
|
+
let inpEventCount = 0;
|
|
11
|
+
const entries = observer.takeRecords();
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
if(entry.entryType == 'event' && entry.interactionId > 0) {
|
|
14
|
+
inpEventCount++;
|
|
15
|
+
performance.measure('Input Delay', {
|
|
16
|
+
start: entry.startTime,
|
|
17
|
+
end: entry.processingStart,
|
|
18
|
+
detail: {
|
|
19
|
+
devtools: {
|
|
20
|
+
dataType: 'track-entry',
|
|
21
|
+
track: 'INP.' + inpEventCount,
|
|
22
|
+
trackGroup: 'Browsertime Timeline',
|
|
23
|
+
color: 'primary',
|
|
24
|
+
tooltipText: 'Input Delay',
|
|
25
|
+
properties: [
|
|
26
|
+
['name', '' + entry.name],
|
|
27
|
+
['target', '' + entry.target.tagName]
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
performance.measure('Processing Time', {
|
|
33
|
+
start: entry.processingStart,
|
|
34
|
+
end: entry.processingEnd,
|
|
35
|
+
detail: {
|
|
36
|
+
devtools: {
|
|
37
|
+
|
|
38
|
+
dataType: 'track-entry',
|
|
39
|
+
track: 'INP.' + inpEventCount,
|
|
40
|
+
trackGroup: 'Browsertime Timeline',
|
|
41
|
+
color: 'primary',
|
|
42
|
+
tooltipText: 'Processing Time',
|
|
43
|
+
properties: [
|
|
44
|
+
['name', '' + entry.name],
|
|
45
|
+
['target', '' + entry.target.tagName]
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
performance.measure('Presentation Delay', {
|
|
51
|
+
start: entry.processingEnd,
|
|
52
|
+
end: entry.startTime + entry.duration,
|
|
53
|
+
detail: {
|
|
54
|
+
devtools: {
|
|
55
|
+
dataType: 'track-entry',
|
|
56
|
+
track: 'INP.' + inpEventCount,
|
|
57
|
+
trackGroup: 'Browsertime Timeline',
|
|
58
|
+
color: 'primary',
|
|
59
|
+
tooltipText: 'Presentation Delay',
|
|
60
|
+
properties: [
|
|
61
|
+
['name', '' + entry.name],
|
|
62
|
+
['target', '' + entry.target.tagName]
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
})();
|
|
70
|
+
`;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// https://developer.chrome.com/docs/devtools/performance/extension
|
|
2
|
+
|
|
3
|
+
export const lcpTimeLine = `
|
|
4
|
+
(function () {
|
|
5
|
+
const observer = new PerformanceObserver(list => { });
|
|
6
|
+
observer.observe({ type: 'largest-contentful-paint', buffered: true, durationThreshold: 0, includeSoftNavigationObservations: true });
|
|
7
|
+
const entries = observer.takeRecords();
|
|
8
|
+
entries.forEach((entry, index) => {
|
|
9
|
+
if (index === entries.length - 1) {
|
|
10
|
+
// This is the LCP
|
|
11
|
+
performance.mark('LCP', {
|
|
12
|
+
startTime: Math.max(entry.renderTime, entry.loadTime),
|
|
13
|
+
detail: {
|
|
14
|
+
devtools: {
|
|
15
|
+
dataType: 'marker',
|
|
16
|
+
track: 'Metrics',
|
|
17
|
+
trackGroup: 'Browsertime Timeline',
|
|
18
|
+
color: 'primary-dark',
|
|
19
|
+
tooltipText: 'LCP ' + entry.element ? entry.element.className : '',
|
|
20
|
+
properties: [
|
|
21
|
+
['size', '' + entry.size],
|
|
22
|
+
['tagName', '' + entry.element ? entry.element.tagName : ''],
|
|
23
|
+
['className', '' + entry.element ? entry.element.className : ''],
|
|
24
|
+
['URL', '' + entry.url],
|
|
25
|
+
['Render', Math.max(entry.renderTime, entry.loadTime)]
|
|
26
|
+
],
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
} else {
|
|
31
|
+
// LCP candidates
|
|
32
|
+
performance.mark('LCP candidate', {
|
|
33
|
+
startTime: Math.max(entry.renderTime, entry.loadTime),
|
|
34
|
+
detail: {
|
|
35
|
+
devtools: {
|
|
36
|
+
dataType: 'marker',
|
|
37
|
+
track: 'LCP',
|
|
38
|
+
trackGroup: 'Browsertime Timeline',
|
|
39
|
+
color: 'primary-dark',
|
|
40
|
+
tooltipText: 'LCP Candidate ' + entry.element ? entry.element.className : '',
|
|
41
|
+
properties: [
|
|
42
|
+
['size', '' + entry.size],
|
|
43
|
+
['tagName', '' + entry.element ? entry.element.tagName : ''],
|
|
44
|
+
['className', '' + entry.element ? entry.element.className : ''],
|
|
45
|
+
['URL', '' + entry.url]
|
|
46
|
+
],
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
})();
|
|
53
|
+
`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// https://developer.chrome.com/docs/devtools/performance/extension
|
|
2
|
+
|
|
3
|
+
export const loadEventEndTimeLine = `
|
|
4
|
+
(function () {
|
|
5
|
+
performance.mark('Load Event End', {
|
|
6
|
+
startTime: performance.getEntriesByType('navigation')[0].loadEventEnd,
|
|
7
|
+
detail: {
|
|
8
|
+
devtools: {
|
|
9
|
+
dataType: 'marker',
|
|
10
|
+
trackGroup: 'Browsertime Timeline',
|
|
11
|
+
track: 'Metrics',
|
|
12
|
+
color: 'secondary',
|
|
13
|
+
tooltipText: 'Load Event End',
|
|
14
|
+
properties: [
|
|
15
|
+
['LoadEventEnd', window.performance.getEntriesByType('navigation')[0].loadEventEnd]
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
})();
|
|
21
|
+
`;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Adopted from https://github.com/andydavies/perf-timeline-to-devtools-profile
|
|
2
|
+
// https://developer.chrome.com/docs/devtools/performance/extension
|
|
3
|
+
|
|
4
|
+
export const loafTimeLine = `
|
|
5
|
+
(function () {
|
|
6
|
+
const observer = new PerformanceObserver(list => { });
|
|
7
|
+
observer.observe({ type: 'long-animation-frame', buffered: true, durationThreshold: 0 });
|
|
8
|
+
const entries = observer.takeRecords();
|
|
9
|
+
for (let entry of entries) {
|
|
10
|
+
|
|
11
|
+
// Set script entries before LoAF so Chrome can display them in the right order
|
|
12
|
+
for(const script of entry.scripts) {
|
|
13
|
+
performance.measure('script', {
|
|
14
|
+
start: script.startTime,
|
|
15
|
+
end: script.startTime + script.duration,
|
|
16
|
+
detail: {
|
|
17
|
+
devtools: {
|
|
18
|
+
dataType: 'track-entry',
|
|
19
|
+
track: 'LoAF',
|
|
20
|
+
trackGroup: 'Browsertime Timeline',
|
|
21
|
+
color: 'secondary',
|
|
22
|
+
tooltipText: script.sourceURL
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
performance.measure('LoAF', {
|
|
29
|
+
start: entry.startTime,
|
|
30
|
+
end: entry.startTime + entry.duration,
|
|
31
|
+
detail: {
|
|
32
|
+
devtools: {
|
|
33
|
+
dataType: 'track-entry',
|
|
34
|
+
track: 'LoAF',
|
|
35
|
+
trackGroup: 'Browsertime Timeline',
|
|
36
|
+
color: 'primary',
|
|
37
|
+
tooltipText: 'LoAF'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if(entry.styleAndLayoutStart > 0) {
|
|
43
|
+
performance.measure('Layout + Paint', {
|
|
44
|
+
start: entry.styleAndLayoutStart,
|
|
45
|
+
end: entry.startTime + entry.duration,
|
|
46
|
+
detail: {
|
|
47
|
+
devtools: {
|
|
48
|
+
|
|
49
|
+
dataType: 'track-entry',
|
|
50
|
+
track: 'LoAF',
|
|
51
|
+
trackGroup: 'Browsertime Timeline',
|
|
52
|
+
color: 'secondary-light',
|
|
53
|
+
tooltipText: 'Layout + Paint'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
})();
|
|
60
|
+
`;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// https://developer.chrome.com/docs/devtools/performance/extension
|
|
2
|
+
|
|
3
|
+
export const longtaskTimeLine = `
|
|
4
|
+
(function () {
|
|
5
|
+
const observer = new PerformanceObserver(list => { });
|
|
6
|
+
observer.observe({ type: 'longtask', buffered: true, durationThreshold: 0 });
|
|
7
|
+
const entries = observer.takeRecords();
|
|
8
|
+
for (let entry of entries) {
|
|
9
|
+
performance.measure('Long Task', {
|
|
10
|
+
start: entry.startTime,
|
|
11
|
+
end: entry.startTime + entry.duration,
|
|
12
|
+
detail: {
|
|
13
|
+
devtools: {
|
|
14
|
+
dataType: 'track-entry',
|
|
15
|
+
track: 'Long Task',
|
|
16
|
+
trackGroup: 'Browsertime Timeline',
|
|
17
|
+
color: 'error',
|
|
18
|
+
tooltipText: 'Long Task'
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
})();
|
|
24
|
+
`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// https://developer.chrome.com/docs/devtools/performance/extension
|
|
2
|
+
|
|
3
|
+
export const ttfbTimeLine = `
|
|
4
|
+
(function () {
|
|
5
|
+
performance.mark('TTFB', {
|
|
6
|
+
startTime: window.performance.getEntriesByType('navigation')[0].responseStart,
|
|
7
|
+
detail: {
|
|
8
|
+
devtools: {
|
|
9
|
+
dataType: 'marker',
|
|
10
|
+
trackGroup: 'Browsertime Timeline',
|
|
11
|
+
track: 'Metrics',
|
|
12
|
+
color: 'tertiary-light',
|
|
13
|
+
tooltipText: 'Time To First Byte (TTFB)',
|
|
14
|
+
properties: [
|
|
15
|
+
['TTFB', window.performance.getEntriesByType('navigation')[0].responseStart]
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
})();
|
|
21
|
+
`;
|
|
@@ -57,7 +57,7 @@ export const defaultFirefoxPreferences = {
|
|
|
57
57
|
'security.turn_off_all_security_so_that_viruses_can_take_over_this_computer': true,
|
|
58
58
|
'xpinstall.signatures.required': false,
|
|
59
59
|
// Prevent Remote Settings to issue non local connections.
|
|
60
|
-
'services.settings.server': '
|
|
60
|
+
'services.settings.server': 'data:,#remote-settings-dummy/v1',
|
|
61
61
|
// Ensure autoplay is enabled for all platforms.
|
|
62
62
|
'media.autoplay.default': 0, // 0=Allowed, 1=Blocked, 2=Prompt
|
|
63
63
|
'media.autoplay.enabled.user-gestures-needed': true,
|
|
@@ -195,5 +195,14 @@ export const defaultFirefoxPreferences = {
|
|
|
195
195
|
'browser.newtabpage.activity-stream.newtabWallpapers.enabled': false,
|
|
196
196
|
'browser.newtabpage.activity-stream.newtabWallpapers.v2.enabled': false,
|
|
197
197
|
// Don't pull sponsored Top Sites content from the network
|
|
198
|
-
'browser.newtabpage.activity-stream.showSponsoredTopSites': false
|
|
198
|
+
'browser.newtabpage.activity-stream.showSponsoredTopSites': false,
|
|
199
|
+
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1930110
|
|
200
|
+
// See https://github.com/sitespeedio/browsertime/issues/2211
|
|
201
|
+
'browser.search.serpEventTelemetryCategorization.enabled': false,
|
|
202
|
+
// Default Glean to "record but don't report" mode, and to never trigger
|
|
203
|
+
// activity-based ping submission. Docs:
|
|
204
|
+
// https://firefox-source-docs.mozilla.org/toolkit/components/glean/dev/preferences.html
|
|
205
|
+
'telemetry.fog.test.localhost_port': -1,
|
|
206
|
+
'telemetry.fog.test.activity_limit': -1,
|
|
207
|
+
'telemetry.fog.test.inactivity_limit': -1
|
|
199
208
|
};
|
|
@@ -325,7 +325,9 @@ export class Firefox {
|
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
if (this.firefoxConfig.geckoProfiler && this.options.visualMetrics) {
|
|
328
|
-
const profileFilename =
|
|
328
|
+
const profileFilename = this.options.enableProfileRun
|
|
329
|
+
? `geckoProfile-${index}-extra.json.gz`
|
|
330
|
+
: `geckoProfile-${index}.json.gz`;
|
|
329
331
|
const profileSubdir = pathToFolder(result.url, this.options);
|
|
330
332
|
|
|
331
333
|
try {
|
|
@@ -361,7 +363,12 @@ export class Firefox {
|
|
|
361
363
|
geckoProfile.meta.visualMetrics = result.visualMetrics;
|
|
362
364
|
|
|
363
365
|
await this.storageManager.writeJson(
|
|
364
|
-
path.join(
|
|
366
|
+
path.join(
|
|
367
|
+
profileSubdir,
|
|
368
|
+
this.options.enableProfileRun
|
|
369
|
+
? `geckoProfile-${index}-extra.json.gz`
|
|
370
|
+
: `geckoProfile-${index}.json`
|
|
371
|
+
),
|
|
365
372
|
geckoProfile,
|
|
366
373
|
true
|
|
367
374
|
);
|
package/lib/support/cli.js
CHANGED
|
@@ -288,6 +288,13 @@ export function parseCommandLine() {
|
|
|
288
288
|
type: 'boolean',
|
|
289
289
|
group: 'chrome'
|
|
290
290
|
})
|
|
291
|
+
.option('chrome.timelineExtras', {
|
|
292
|
+
describe:
|
|
293
|
+
'If you collect the timeline using --chrome.timeline or --enableProfileRun this will add some extra timings and tracks to your timeline.',
|
|
294
|
+
type: 'boolean',
|
|
295
|
+
default: true,
|
|
296
|
+
group: 'chrome'
|
|
297
|
+
})
|
|
291
298
|
.option('chrome.timelineRecordingType', {
|
|
292
299
|
alias: 'chrome.traceRecordingType',
|
|
293
300
|
describe: 'Expose the start/stop commands for the chrome trace',
|
package/package.json
CHANGED