Skip to content

Commit

Permalink
🏗Add navigation ticks to CSI (#27311)
Browse files Browse the repository at this point in the history
* Add navigation ticks to CSI

* add test coverage

* fix tests

* switch to tickdelta
  • Loading branch information
kevinkimball committed Apr 2, 2020
1 parent db9d408 commit 8393b73
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 22 deletions.
44 changes: 26 additions & 18 deletions extensions/amp-viewer-integration/TICKEVENTS.md
Expand Up @@ -24,21 +24,29 @@ Every start label has an assumed e\_`label` for its "end" counterpart label.
As an example if we executed `perf.tick('label')` we assume we have a counterpart
`perf.tick('e_label')`.

| Name | id | Description |
| ------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Install Styles | `is` | Set when the styles are installed. |
| End Install Styles | `e_is` | Set when the styles are done installing. |
| Window load event | `ol` | Window load event fired. |
| First viewport ready | `pc` | Fires when non-ad resources above the fold fired their load event measured from the time the user clicks (So takes pre-rendering into account) |
| Make Body Visible | `mbv` | Make Body Visible Executes. |
| On First Visible | `ofv` | The first time the page has been turned visible. |
| First paint time | `fp` | The time on the first non-blank paint of the page. |
| First contentful paint time | `fcp` | First paint with content. See https://github.com/WICG/paint-timing |
| First input delay | `fid` | Millisecond delay in handling the first user input on the page. See https://github.com/WICG/event-timing |
| First input delay, polyfill value | `fid-polyfill` | Millisecond delay in handling the first user input on the page, reported by [a polyfill](https://github.com/GoogleChromeLabs/first-input-delay) |
| Layout Jank, first exit | `lj` | The aggregate jank score when the user leaves the page (navigation, tab switching, dismissing application) for the first time. See https://gist.github.com/skobes/2f296da1b0a88cc785a4bf10a42bca07 |
| Layout Jank, second exit | `lj-2` | The aggregate jank score when the user leaves the page (navigation, tab switching, dismissing application) for the second time. |
| Cumulative Layout Shift, first exit | `cls` | The aggregate layout shift score when the user leaves the page (navigation, tab switching, dismissing application) for the first time. See https://web.dev/layout-instability-api |
| Cumulative Layout Shift, second exit | `cls-2` | The aggregate layout shift score when the user leaves the page (navigation, tab switching, dismissing application) for the second time. |
| Largest Contentful Paint, load time | `lcpl` | The time in milliseconds for the first contentful element to display. This is the load time version of this metric. See https://github.com/WICG/largest-contentful-paint |
| Largest Contentful Paint, render time | `lcpr` | The time in milliseconds for the first contentful element to display. This is the render time version of this metric. https://github.com/WICG/largest-contentful-paint |
| Name | id | Description |
| ------------------------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Install Styles | `is` | Set when the styles are installed. |
| End Install Styles | `e_is` | Set when the styles are done installing. |
| Window load event | `ol` | Window load event fired. |
| First viewport ready | `pc` | Fires when non-ad resources above the fold fired their load event measured from the time the user clicks (So takes pre-rendering into account) |
| Make Body Visible | `mbv` | Make Body Visible Executes. |
| On First Visible | `ofv` | The first time the page has been turned visible. |
| First paint time | `fp` | The time on the first non-blank paint of the page. |
| First contentful paint time | `fcp` | First paint with content. See https://github.com/WICG/paint-timing |
| First input delay | `fid` | Millisecond delay in handling the first user input on the page. See https://github.com/WICG/event-timing |
| First input delay, polyfill value | `fid-polyfill` | Millisecond delay in handling the first user input on the page, reported by [a polyfill](https://github.com/GoogleChromeLabs/first-input-delay) |
| Layout Jank, first exit | `lj` | The aggregate jank score when the user leaves the page (navigation, tab switching, dismissing application) for the first time. See https://gist.github.com/skobes/2f296da1b0a88cc785a4bf10a42bca07 |
| Layout Jank, second exit | `lj-2` | The aggregate jank score when the user leaves the page (navigation, tab switching, dismissing application) for the second time. |
| Cumulative Layout Shift, first exit | `cls` | The aggregate layout shift score when the user leaves the page (navigation, tab switching, dismissing application) for the first time. See https://web.dev/layout-instability-api |
| Cumulative Layout Shift, second exit | `cls-2` | The aggregate layout shift score when the user leaves the page (navigation, tab switching, dismissing application) for the second time. |
| Largest Contentful Paint, load time | `lcpl` | The time in milliseconds for the first contentful element to display. This is the load time version of this metric. See https://github.com/WICG/largest-contentful-paint |
| Largest Contentful Paint, render time | `lcpr` | The time in milliseconds for the first contentful element to display. This is the render time version of this metric. https://github.com/WICG/largest-contentful-paint |
| DOM Complete | `domComplete` | Time immediately before the browser sets the current document readiness of the current document to complete |
| DOM Content Loaded Event End | `domContentLoadedEventEnd` | Time immediately after the current document's DOMContentLoaded event completes |
| DOM Content Loaded Event Start | `domContentLoadedEventStart` | Time immediately before the user agent fires the DOMContentLoaded event at the current document |
| DOM Interactive | `domInteractive` | Time immediately before the user agent sets the current document readiness of the current document to interactive |
| Load Event End | `loadEventEnd` | Time when the load event of the current document is completed |
| Load Event Start | `loadEventStart` | Time immediately before the load event of the current document is fired |
| Request Start | `requestStart` | Time immediately before the user agent starts requesting the resource from the server |
| Response Start | `responseStart` | Time immediately after the user agent's HTTP parser receives the first byte of the response from the server |
25 changes: 25 additions & 0 deletions src/service/performance-impl.js
Expand Up @@ -146,6 +146,13 @@ export class Performance {
'largest-contentful-paint'
);

/**
* Whether the user agent supports the navigation timing API
*
* @private {boolean}
*/
this.supportsNavigation_ = supportedEntryTypes.includes('navigation');

/**
* The latest reported largest contentful paint time, where the loadTime
* is specified.
Expand Down Expand Up @@ -290,6 +297,7 @@ export class Performance {
let recordedFirstPaint = false;
let recordedFirstContentfulPaint = false;
let recordedFirstInputDelay = false;
let recordedNavigation = false;
const processEntry = (entry) => {
if (entry.name == 'first-paint' && !recordedFirstPaint) {
this.tickDelta('fp', entry.startTime + entry.duration);
Expand Down Expand Up @@ -319,6 +327,18 @@ export class Performance {
if (entry.renderTime) {
this.largestContentfulPaintRenderTime_ = entry.renderTime;
}
} else if (entry.entryType == 'navigation' && !recordedNavigation) {
[
'domComplete',
'domContentLoadedEventEnd',
'domContentLoadedEventStart',
'domInteractive',
'loadEventEnd',
'loadEventStart',
'requestStart',
'responseStart',
].forEach((label) => this.tick(label, entry[label]));
recordedNavigation = true;
}
};

Expand Down Expand Up @@ -348,6 +368,11 @@ export class Performance {
lcpObserver.observe({type: 'largest-contentful-paint', buffered: true});
}

if (this.supportsNavigation_) {
const navigationObserver = this.createPerformanceObserver_(processEntry);
navigationObserver.observe({type: 'navigation', buffered: true});
}

if (entryTypesToObserve.length === 0) {
return;
}
Expand Down
89 changes: 85 additions & 4 deletions test/unit/test-performance.js
Expand Up @@ -637,16 +637,16 @@ describes.realWin('performance', {amp: true}, (env) => {
() => {
clock.tick(100);
whenFirstVisibleResolve();
expect(tickSpy).to.have.callCount(4);
const initialCount = tickSpy.callCount;
return ampdoc.whenFirstVisible().then(() => {
clock.tick(400);
expect(tickSpy).to.have.callCount(5);
expect(tickSpy).to.have.callCount(initialCount + 1);
whenViewportLayoutCompleteResolve();
return perf.whenViewportLayoutComplete_().then(() => {
expect(tickSpy).to.have.callCount(5);
expect(tickSpy).to.have.callCount(initialCount + 1);
expect(tickSpy.withArgs('ofv')).to.be.calledOnce;
return whenFirstVisiblePromise.then(() => {
expect(tickSpy).to.have.callCount(6);
expect(tickSpy).to.have.callCount(initialCount + 2);
expect(tickSpy.withArgs('pc')).to.be.calledOnce;
expect(Number(tickSpy.withArgs('pc').args[0][1])).to.equal(400);
});
Expand Down Expand Up @@ -1270,4 +1270,85 @@ describes.realWin('PeformanceObserver metrics', {amp: true}, (env) => {
});
});
});

describe('forwards navigation metrics', () => {
let PerformanceObserverConstructorStub, performanceObserver;
beforeEach(() => {
// Stub and fake the PerformanceObserver constructor.
const PerformanceObserverStub = env.sandbox.stub();
PerformanceObserverStub.callsFake((callback) => {
performanceObserver = new PerformanceObserverImpl(callback);
return performanceObserver;
});
PerformanceObserverConstructorStub = env.sandbox.stub(
env.win,
'PerformanceObserver'
);
PerformanceObserverConstructorStub.callsFake(PerformanceObserverStub);
});

it('after performance service registered', () => {
// Pretend that the Navigation API exists.
PerformanceObserverConstructorStub.supportedEntryTypes = ['navigation'];

installPerformanceService(env.win);

const perf = Services.performanceFor(env.win);

// Fake fid that occured before the Performance service is started.
performanceObserver.triggerCallback({
getEntries() {
return [
{
entryType: 'navigation',
domComplete: 0,
domContentLoadedEventEnd: 1,
domContentLoadedEventStart: 2,
domInteractive: 3,
loadEventEnd: 4,
loadEventStart: 5,
requestStart: 6,
responseStart: 7,
},
];
},
});

expect(perf.events_.length).to.equal(8);
expect(perf.events_).to.be.jsonEqual([
{
label: 'domComplete',
delta: 0,
},
{
label: 'domContentLoadedEventEnd',
delta: 1,
},
{
label: 'domContentLoadedEventStart',
delta: 2,
},
{
label: 'domInteractive',
delta: 3,
},
{
label: 'loadEventEnd',
delta: 4,
},
{
label: 'loadEventStart',
delta: 5,
},
{
label: 'requestStart',
delta: 6,
},
{
label: 'responseStart',
delta: 7,
},
]);
});
});
});

0 comments on commit 8393b73

Please sign in to comment.