tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ComponentPerfTimer.jsx (5761B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
      6 import { perfService as perfSvc } from "content-src/lib/perf-service";
      7 import React from "react";
      8 
      9 // Currently record only a fixed set of sections. This will prevent data
     10 // from custom sections from showing up or from topstories.
     11 const RECORDED_SECTIONS = ["highlights", "topsites"];
     12 
     13 export class ComponentPerfTimer extends React.Component {
     14  constructor(props) {
     15    super(props);
     16    // Just for test dependency injection:
     17    this.perfSvc = this.props.perfSvc || perfSvc;
     18 
     19    this._sendBadStateEvent = this._sendBadStateEvent.bind(this);
     20    this._sendPaintedEvent = this._sendPaintedEvent.bind(this);
     21    this._reportMissingData = false;
     22    this._timestampHandled = false;
     23    this._recordedFirstRender = false;
     24  }
     25 
     26  componentDidMount() {
     27    if (!RECORDED_SECTIONS.includes(this.props.id)) {
     28      return;
     29    }
     30 
     31    this._maybeSendPaintedEvent();
     32  }
     33 
     34  componentDidUpdate() {
     35    if (!RECORDED_SECTIONS.includes(this.props.id)) {
     36      return;
     37    }
     38 
     39    this._maybeSendPaintedEvent();
     40  }
     41 
     42  /**
     43   * Call the given callback after the upcoming frame paints.
     44   *
     45   * Note: Both setTimeout and requestAnimationFrame are throttled when the page
     46   * is hidden, so this callback may get called up to a second or so after the
     47   * requestAnimationFrame "paint" for hidden tabs.
     48   *
     49   * Newtabs hidden while loading will presumably be fairly rare (other than
     50   * preloaded tabs, which we will be filtering out on the server side), so such
     51   * cases should get lost in the noise.
     52   *
     53   * If we decide that it's important to find out when something that's hidden
     54   * has "painted", however, another option is to post a message to this window.
     55   * That should happen even faster than setTimeout, and, at least as of this
     56   * writing, it's not throttled in hidden windows in Firefox.
     57   *
     58   * @param {Function} callback
     59   *
     60   * @returns void
     61   */
     62  _afterFramePaint(callback) {
     63    requestAnimationFrame(() => setTimeout(callback, 0));
     64  }
     65 
     66  _maybeSendBadStateEvent() {
     67    // Follow up bugs:
     68    // https://github.com/mozilla/activity-stream/issues/3691
     69    if (!this.props.initialized) {
     70      // Remember to report back when data is available.
     71      this._reportMissingData = true;
     72    } else if (this._reportMissingData) {
     73      this._reportMissingData = false;
     74      // Report how long it took for component to become initialized.
     75      this._sendBadStateEvent();
     76    }
     77  }
     78 
     79  _maybeSendPaintedEvent() {
     80    // If we've already handled a timestamp, don't do it again.
     81    if (this._timestampHandled || !this.props.initialized) {
     82      return;
     83    }
     84 
     85    // And if we haven't, we're doing so now, so remember that. Even if
     86    // something goes wrong in the callback, we can't try again, as we'd be
     87    // sending back the wrong data, and we have to do it here, so that other
     88    // calls to this method while waiting for the next frame won't also try to
     89    // handle it.
     90    this._timestampHandled = true;
     91    this._afterFramePaint(this._sendPaintedEvent);
     92  }
     93 
     94  /**
     95   * Triggered by call to render. Only first call goes through due to
     96   * `_recordedFirstRender`.
     97   */
     98  _ensureFirstRenderTsRecorded() {
     99    // Used as t0 for recording how long component took to initialize.
    100    if (!this._recordedFirstRender) {
    101      this._recordedFirstRender = true;
    102      // topsites_first_render_ts, highlights_first_render_ts.
    103      const key = `${this.props.id}_first_render_ts`;
    104      this.perfSvc.mark(key);
    105    }
    106  }
    107 
    108  /**
    109   * Creates `SAVE_SESSION_PERF_DATA` with timestamp in ms
    110   * of how much longer the data took to be ready for display than it would
    111   * have been the ideal case.
    112   * https://github.com/mozilla/ping-centre/issues/98
    113   */
    114  _sendBadStateEvent() {
    115    // highlights_data_ready_ts, topsites_data_ready_ts.
    116    const dataReadyKey = `${this.props.id}_data_ready_ts`;
    117    this.perfSvc.mark(dataReadyKey);
    118 
    119    try {
    120      const firstRenderKey = `${this.props.id}_first_render_ts`;
    121      // value has to be Int32.
    122      const value = parseInt(
    123        this.perfSvc.getMostRecentAbsMarkStartByName(dataReadyKey) -
    124          this.perfSvc.getMostRecentAbsMarkStartByName(firstRenderKey),
    125        10
    126      );
    127      this.props.dispatch(
    128        ac.OnlyToMain({
    129          type: at.SAVE_SESSION_PERF_DATA,
    130          // highlights_data_late_by_ms, topsites_data_late_by_ms.
    131          data: { [`${this.props.id}_data_late_by_ms`]: value },
    132        })
    133      );
    134    } catch (ex) {
    135      // If this failed, it's likely because the `privacy.resistFingerprinting`
    136      // pref is true.
    137    }
    138  }
    139 
    140  _sendPaintedEvent() {
    141    // Record first_painted event but only send if topsites.
    142    if (this.props.id !== "topsites") {
    143      return;
    144    }
    145 
    146    // topsites_first_painted_ts.
    147    const key = `${this.props.id}_first_painted_ts`;
    148    this.perfSvc.mark(key);
    149 
    150    try {
    151      const data = {};
    152      data[key] = this.perfSvc.getMostRecentAbsMarkStartByName(key);
    153 
    154      this.props.dispatch(
    155        ac.OnlyToMain({
    156          type: at.SAVE_SESSION_PERF_DATA,
    157          data,
    158        })
    159      );
    160    } catch (ex) {
    161      // If this failed, it's likely because the `privacy.resistFingerprinting`
    162      // pref is true.  We should at least not blow up, and should continue
    163      // to set this._timestampHandled to avoid going through this again.
    164    }
    165  }
    166 
    167  render() {
    168    if (RECORDED_SECTIONS.includes(this.props.id)) {
    169      this._ensureFirstRenderTsRecorded();
    170      this._maybeSendBadStateEvent();
    171    }
    172    return this.props.children;
    173  }
    174 }