browser/components/newtab/content-src/components/DiscoveryStreamImpressionStats/ImpressionStats.jsx
author Ed Lee <edilee@mozilla.com>
Thu, 17 Jan 2019 20:53:45 +0000
changeset 511449 25af8129d9f2c71b724398b64407cbde3215f407
child 513154 64706783dc06674e49ddbac40532440d0c5a5e81
permissions -rw-r--r--
Bug 1520691 - Add Discovery components, search polish and bug fixes to Activity Stream r=k88hudson Differential Revision: https://phabricator.services.mozilla.com/D16784

import {actionCreators as ac} from "common/Actions.jsm";
import React from "react";

const VISIBLE = "visible";
const VISIBILITY_CHANGE_EVENT = "visibilitychange";

export class ImpressionStats extends React.PureComponent {
  // This checks if the given cards are the same as those in the last impression ping.
  // If so, it should not send the same impression ping again.
  _needsImpressionStats(cards) {
    if (!this.impressionCardGuids || (this.impressionCardGuids.length !== cards.length)) {
      return true;
    }

    for (let i = 0; i < cards.length; i++) {
      if (cards[i].id !== this.impressionCardGuids[i]) {
        return true;
      }
    }

    return false;
  }

  _dispatchImpressionStats() {
    const {props} = this;
    const cards = props.rows;

    if (this._needsImpressionStats(cards)) {
      props.dispatch(ac.ImpressionStats({
        source: props.source.toUpperCase(),
        tiles: cards.map(link => ({id: link.id})),
      }));
      this.impressionCardGuids = cards.map(link => link.id);
    }
  }

  // This sends an event when a user sees a set of new content. If content
  // changes while the page is hidden (i.e. preloaded or on a hidden tab),
  // only send the event if the page becomes visible again.
  sendImpressionStatsOrAddListener() {
    const {props} = this;

    if (!props.dispatch) {
      return;
    }

    if (props.document.visibilityState === VISIBLE) {
      this._dispatchImpressionStats();
    } else {
      // We should only ever send the latest impression stats ping, so remove any
      // older listeners.
      if (this._onVisibilityChange) {
        props.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
      }

      this._onVisibilityChange = () => {
        if (props.document.visibilityState === VISIBLE) {
          this._dispatchImpressionStats();
          props.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
        }
      };
      props.document.addEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
    }
  }

  componentDidMount() {
    if (this.props.rows.length) {
      this.sendImpressionStatsOrAddListener();
    }
  }

  componentDidUpdate(prevProps) {
    if (this.props.rows.length && this.props.rows !== prevProps.rows) {
      this.sendImpressionStatsOrAddListener();
    }
  }

  componentWillUnmount() {
    if (this._onVisibilityChange) {
      this.props.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
    }
  }

  render() {
    return this.props.children;
  }
}

ImpressionStats.defaultProps = {
  document: global.document,
  rows: [],
  source: "",
};