174 lines
5.6 KiB
JavaScript
174 lines
5.6 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
|
|
import { perfService as perfSvc } from "content-src/lib/perf-service";
|
|
import React from "react";
|
|
|
|
// Currently record only a fixed set of sections. This will prevent data
|
|
// from custom sections from showing up or from topstories.
|
|
const RECORDED_SECTIONS = ["highlights", "topsites"];
|
|
|
|
export class ComponentPerfTimer extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
// Just for test dependency injection:
|
|
this.perfSvc = this.props.perfSvc || perfSvc;
|
|
|
|
this._sendBadStateEvent = this._sendBadStateEvent.bind(this);
|
|
this._sendPaintedEvent = this._sendPaintedEvent.bind(this);
|
|
this._reportMissingData = false;
|
|
this._timestampHandled = false;
|
|
this._recordedFirstRender = false;
|
|
}
|
|
|
|
componentDidMount() {
|
|
if (!RECORDED_SECTIONS.includes(this.props.id)) {
|
|
return;
|
|
}
|
|
|
|
this._maybeSendPaintedEvent();
|
|
}
|
|
|
|
componentDidUpdate() {
|
|
if (!RECORDED_SECTIONS.includes(this.props.id)) {
|
|
return;
|
|
}
|
|
|
|
this._maybeSendPaintedEvent();
|
|
}
|
|
|
|
/**
|
|
* Call the given callback after the upcoming frame paints.
|
|
*
|
|
* @note Both setTimeout and requestAnimationFrame are throttled when the page
|
|
* is hidden, so this callback may get called up to a second or so after the
|
|
* requestAnimationFrame "paint" for hidden tabs.
|
|
*
|
|
* Newtabs hidden while loading will presumably be fairly rare (other than
|
|
* preloaded tabs, which we will be filtering out on the server side), so such
|
|
* cases should get lost in the noise.
|
|
*
|
|
* If we decide that it's important to find out when something that's hidden
|
|
* has "painted", however, another option is to post a message to this window.
|
|
* That should happen even faster than setTimeout, and, at least as of this
|
|
* writing, it's not throttled in hidden windows in Firefox.
|
|
*
|
|
* @param {Function} callback
|
|
*
|
|
* @returns void
|
|
*/
|
|
_afterFramePaint(callback) {
|
|
requestAnimationFrame(() => setTimeout(callback, 0));
|
|
}
|
|
|
|
_maybeSendBadStateEvent() {
|
|
// Follow up bugs:
|
|
// https://github.com/mozilla/activity-stream/issues/3691
|
|
if (!this.props.initialized) {
|
|
// Remember to report back when data is available.
|
|
this._reportMissingData = true;
|
|
} else if (this._reportMissingData) {
|
|
this._reportMissingData = false;
|
|
// Report how long it took for component to become initialized.
|
|
this._sendBadStateEvent();
|
|
}
|
|
}
|
|
|
|
_maybeSendPaintedEvent() {
|
|
// If we've already handled a timestamp, don't do it again.
|
|
if (this._timestampHandled || !this.props.initialized) {
|
|
return;
|
|
}
|
|
|
|
// And if we haven't, we're doing so now, so remember that. Even if
|
|
// something goes wrong in the callback, we can't try again, as we'd be
|
|
// sending back the wrong data, and we have to do it here, so that other
|
|
// calls to this method while waiting for the next frame won't also try to
|
|
// handle it.
|
|
this._timestampHandled = true;
|
|
this._afterFramePaint(this._sendPaintedEvent);
|
|
}
|
|
|
|
/**
|
|
* Triggered by call to render. Only first call goes through due to
|
|
* `_recordedFirstRender`.
|
|
*/
|
|
_ensureFirstRenderTsRecorded() {
|
|
// Used as t0 for recording how long component took to initialize.
|
|
if (!this._recordedFirstRender) {
|
|
this._recordedFirstRender = true;
|
|
// topsites_first_render_ts, highlights_first_render_ts.
|
|
const key = `${this.props.id}_first_render_ts`;
|
|
this.perfSvc.mark(key);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates `SAVE_SESSION_PERF_DATA` with timestamp in ms
|
|
* of how much longer the data took to be ready for display than it would
|
|
* have been the ideal case.
|
|
* https://github.com/mozilla/ping-centre/issues/98
|
|
*/
|
|
_sendBadStateEvent() {
|
|
// highlights_data_ready_ts, topsites_data_ready_ts.
|
|
const dataReadyKey = `${this.props.id}_data_ready_ts`;
|
|
this.perfSvc.mark(dataReadyKey);
|
|
|
|
try {
|
|
const firstRenderKey = `${this.props.id}_first_render_ts`;
|
|
// value has to be Int32.
|
|
const value = parseInt(
|
|
this.perfSvc.getMostRecentAbsMarkStartByName(dataReadyKey) -
|
|
this.perfSvc.getMostRecentAbsMarkStartByName(firstRenderKey),
|
|
10
|
|
);
|
|
this.props.dispatch(
|
|
ac.OnlyToMain({
|
|
type: at.SAVE_SESSION_PERF_DATA,
|
|
// highlights_data_late_by_ms, topsites_data_late_by_ms.
|
|
data: { [`${this.props.id}_data_late_by_ms`]: value },
|
|
})
|
|
);
|
|
} catch (ex) {
|
|
// If this failed, it's likely because the `privacy.resistFingerprinting`
|
|
// pref is true.
|
|
}
|
|
}
|
|
|
|
_sendPaintedEvent() {
|
|
// Record first_painted event but only send if topsites.
|
|
if (this.props.id !== "topsites") {
|
|
return;
|
|
}
|
|
|
|
// topsites_first_painted_ts.
|
|
const key = `${this.props.id}_first_painted_ts`;
|
|
this.perfSvc.mark(key);
|
|
|
|
try {
|
|
const data = {};
|
|
data[key] = this.perfSvc.getMostRecentAbsMarkStartByName(key);
|
|
|
|
this.props.dispatch(
|
|
ac.OnlyToMain({
|
|
type: at.SAVE_SESSION_PERF_DATA,
|
|
data,
|
|
})
|
|
);
|
|
} catch (ex) {
|
|
// If this failed, it's likely because the `privacy.resistFingerprinting`
|
|
// pref is true. We should at least not blow up, and should continue
|
|
// to set this._timestampHandled to avoid going through this again.
|
|
}
|
|
}
|
|
|
|
render() {
|
|
if (RECORDED_SECTIONS.includes(this.props.id)) {
|
|
this._ensureFirstRenderTsRecorded();
|
|
this._maybeSendBadStateEvent();
|
|
}
|
|
return this.props.children;
|
|
}
|
|
}
|