<commit_before>/**
 * Copyright 2015 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {layoutRectLtwh} from '../layout-rect';
import {registerServiceBuilder, getService} from '../service';
import {Services} from '../services';
import {whenDocumentComplete} from '../document-ready';
import {getMode} from '../mode';
import {isCanary} from '../experiments';
import {throttle} from '../utils/rate-limit';
import {dict, map} from '../utils/object';

/**
 * Maximum number of tick events we allow to accumulate in the performance
 * instance's queue before we start dropping those events and can no longer
 * be forwarded to the actual `tick` function when it is set.
 */
const QUEUE_LIMIT = 50;

/**
 * Fields:
 * {{
 *   label: string,
 *   delta: (number|null|undefined),
 *   value: (number|null|undefined)
 * }}
 * @typedef {!JsonObject}
 */
let TickEventDef;


/**
 * Increments the value, else defaults to 0 for the given object key.
 * @param {!Object<string, (string|number|boolean|Array|Object|null)>} obj
 * @param {?string} name
 */
function incOrDef(obj, name) {
  if (!name) {
    return;
  }

  if (!obj[name]) {
    obj[name] = 1;
  } else {
    obj[name]++;
  }
}


/**
 * Performance holds the mechanism to call `tick` to stamp out important
 * events in the lifecycle of the AMP runtime. It can hold a small amount
 * of tick events to forward to the external `tick` function when it is set.
 */
export class Performance {

  /**
   * @param {!Window} win
   */
  constructor(win) {
    /** @const {!Window} */
    this.win = win;

    /** @private @const {number} */
    this.initTime_ = this.win.Date.now();

    /** @const @private {!Array<TickEventDef>} */
    this.events_ = [];

    /** @private {?./viewer-impl.Viewer} */
    this.viewer_ = null;

    /** @private {?./resources-impl.Resources} */
    this.resources_ = null;

    /** @private {boolean} */
    this.isMessagingReady_ = false;

    /** @private {boolean} */
    this.isPerformanceTrackingOn_ = false;

    /** @private {!Object<string,boolean>} */
    this.enabledExperiments_ = map();
    /** @private {string} */
    this.ampexp_ = '';

    // Add RTV version as experiment ID, so we can slice the data by version.
    this.addEnabledExperiment('rtv-' + getMode(this.win).rtvVersion);
    if (isCanary(this.win)) {
      this.addEnabledExperiment('canary');
    }

    // Tick window.onload event.
    whenDocumentComplete(win.document).then(() => {
      this.onload_();
      this.flush();
    });

    this.registerPaintTimingObserver_();
  }

  /**
   * Listens to viewer and resource events.
   * @return {!Promise}
   */
  coreServicesAvailable() {
    this.viewer_ = Services.viewerForDoc(this.win.document);
    this.resources_ = Services.resourcesForDoc(this.win.document);

    this.isPerformanceTrackingOn_ = this.viewer_.isEmbedded() &&
        this.viewer_.getParam('csi') === '1';

    // This is for redundancy. Call flush on any visibility change.
    this.viewer_.onVisibilityChanged(this.flush.bind(this));

    // Does not need to wait for messaging ready since it will be queued
    // if it isn't ready.
    this.measureUserPerceivedVisualCompletenessTime_();

    // Can be null which would mean this AMP page is not embedded
    // and has no messaging channel.
    const channelPromise = this.viewer_.whenMessagingReady();

    this.viewer_.whenFirstVisible().then(() => {
      this.tick('ofv');
      this.flush();
    });

    // We don't check `isPerformanceTrackingOn` here since there are some
    // events that we call on the viewer even though performance tracking
    // is off we only need to know if the AMP page has a messaging
    // channel or not.
    if (!channelPromise) {
      return Promise.resolve();
    }

    return channelPromise.then(() => {
      this.isMessagingReady_ = true;

      // Tick the "messaging ready" signal.
      this.tickDelta('msr', this.win.Date.now() - this.initTime_);

      // Forward all queued ticks to the viewer since messaging
      // is now ready.
      this.flushQueuedTicks_();

      // Send all csi ticks through.
      this.flush();
    });
  }

  onload_() {
    this.tick('ol');
    this.tickLegacyFirstPaintTime_();
  }

  /**
   * Reports first pain and first contentful paint timings.
   * See https://github.com/WICG/paint-timing
   */
  registerPaintTimingObserver_() {
    if (!this.win.PerformancePaintTiming) {
      return;
    }
    const observer = new this.win.PerformanceObserver(list => {
      list.getEntries().forEach(entry => {
        if (entry.name == 'first-paint') {
          this.tickDelta('fp', entry.startTime + entry.duration);
        }
        else if (entry.name == 'first-contentful-paint') {
          this.tickDelta('fcp', entry.startTime + entry.duration);
        }
      });
    });

    observer.observe({entryTypes: ['paint']});
  }

  /**
   * Tick fp time based on Chrome's legacy paint timing API when
   * appropriate.
   * `registerPaintTimingObserver_` calls the standards based API and this
   * method does nothing if it is available.
   */
  tickLegacyFirstPaintTime_() {
    // Detect deprecated first pain time API
    // https://bugs.chromium.org/p/chromium/issues/detail?id=621512
    // We'll use this until something better is available.
    if (!this.win.PerformancePaintTiming
        && this.win.chrome
        && typeof this.win.chrome.loadTimes == 'function') {<commit_msg>Ensure we flush performance ticks when the PerformanceObserver fires. (#11375)<commit_after>/**
 * Copyright 2015 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {layoutRectLtwh} from '../layout-rect';
import {registerServiceBuilder, getService} from '../service';
import {Services} from '../services';
import {whenDocumentComplete} from '../document-ready';
import {getMode} from '../mode';
import {isCanary} from '../experiments';
import {throttle} from '../utils/rate-limit';
import {dict, map} from '../utils/object';

/**
 * Maximum number of tick events we allow to accumulate in the performance
 * instance's queue before we start dropping those events and can no longer
 * be forwarded to the actual `tick` function when it is set.
 */
const QUEUE_LIMIT = 50;

/**
 * Fields:
 * {{
 *   label: string,
 *   delta: (number|null|undefined),
 *   value: (number|null|undefined)
 * }}
 * @typedef {!JsonObject}
 */
let TickEventDef;


/**
 * Increments the value, else defaults to 0 for the given object key.
 * @param {!Object<string, (string|number|boolean|Array|Object|null)>} obj
 * @param {?string} name
 */
function incOrDef(obj, name) {
  if (!name) {
    return;
  }

  if (!obj[name]) {
    obj[name] = 1;
  } else {
    obj[name]++;
  }
}


/**
 * Performance holds the mechanism to call `tick` to stamp out important
 * events in the lifecycle of the AMP runtime. It can hold a small amount
 * of tick events to forward to the external `tick` function when it is set.
 */
export class Performance {

  /**
   * @param {!Window} win
   */
  constructor(win) {
    /** @const {!Window} */
    this.win = win;

    /** @private @const {number} */
    this.initTime_ = this.win.Date.now();

    /** @const @private {!Array<TickEventDef>} */
    this.events_ = [];

    /** @private {?./viewer-impl.Viewer} */
    this.viewer_ = null;

    /** @private {?./resources-impl.Resources} */
    this.resources_ = null;

    /** @private {boolean} */
    this.isMessagingReady_ = false;

    /** @private {boolean} */
    this.isPerformanceTrackingOn_ = false;

    /** @private {!Object<string,boolean>} */
    this.enabledExperiments_ = map();
    /** @private {string} */
    this.ampexp_ = '';

    // Add RTV version as experiment ID, so we can slice the data by version.
    this.addEnabledExperiment('rtv-' + getMode(this.win).rtvVersion);
    if (isCanary(this.win)) {
      this.addEnabledExperiment('canary');
    }

    // Tick window.onload event.
    whenDocumentComplete(win.document).then(() => this.onload_());
    this.registerPaintTimingObserver_();
  }

  /**
   * Listens to viewer and resource events.
   * @return {!Promise}
   */
  coreServicesAvailable() {
    this.viewer_ = Services.viewerForDoc(this.win.document);
    this.resources_ = Services.resourcesForDoc(this.win.document);

    this.isPerformanceTrackingOn_ = this.viewer_.isEmbedded() &&
        this.viewer_.getParam('csi') === '1';

    // This is for redundancy. Call flush on any visibility change.
    this.viewer_.onVisibilityChanged(this.flush.bind(this));

    // Does not need to wait for messaging ready since it will be queued
    // if it isn't ready.
    this.measureUserPerceivedVisualCompletenessTime_();

    // Can be null which would mean this AMP page is not embedded
    // and has no messaging channel.
    const channelPromise = this.viewer_.whenMessagingReady();

    this.viewer_.whenFirstVisible().then(() => {
      this.tick('ofv');
      this.flush();
    });

    // We don't check `isPerformanceTrackingOn` here since there are some
    // events that we call on the viewer even though performance tracking
    // is off we only need to know if the AMP page has a messaging
    // channel or not.
    if (!channelPromise) {
      return Promise.resolve();
    }

    return channelPromise.then(() => {
      this.isMessagingReady_ = true;

      // Tick the "messaging ready" signal.
      this.tickDelta('msr', this.win.Date.now() - this.initTime_);

      // Forward all queued ticks to the viewer since messaging
      // is now ready.
      this.flushQueuedTicks_();

      // Send all csi ticks through.
      this.flush();
    });
  }

  onload_() {
    this.tick('ol');
    this.tickLegacyFirstPaintTime_();
    this.flush();
  }

  /**
   * Reports first pain and first contentful paint timings.
   * See https://github.com/WICG/paint-timing
   */
  registerPaintTimingObserver_() {
    if (!this.win.PerformancePaintTiming) {
      return;
    }
    const observer = new this.win.PerformanceObserver(list => {
      list.getEntries().forEach(entry => {
        if (entry.name == 'first-paint') {
          this.tickDelta('fp', entry.startTime + entry.duration);
          this.flush();
        }
        else if (entry.name == 'first-contentful-paint') {
          this.tickDelta('fcp', entry.startTime + entry.duration);
          this.flush();
        }
      });
    });

    observer.observe({entryTypes: ['paint']});
  }

  /**
   * Tick fp time based on Chrome's legacy paint timing API when
   * appropriate.
   * `registerPaintTimingObserver_` calls the standards based API and this
   * method does nothing if it is available.
   */
  tickLegacyFirstPaintTime_() {
    // Detect deprecated first pain time API
    // https://bugs.chromium.org/p/chromium/issues/detail?id=621512
    // We'll use this until something better is available.
    if (!this.win.PerformancePaintTiming
        && this.win.chrome
        && typeof this.win.chrome.loadTimes == 'function') {
{'fraction_non_alphanumeric': 0.1319219593192196, 'fraction_numerical': 0.0035699460356994604, 'mean_word_length': 2.931462140992167, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 19, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3605577', 'n_tokens_mistral': 3688, 'n_tokens_neox': 3513, 'n_words': 1083}