tor-browser

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

TopSiteImpressionWrapper.jsx (4041B)


      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 } from "common/Actions.mjs";
      6 import React from "react";
      7 
      8 const VISIBLE = "visible";
      9 const VISIBILITY_CHANGE_EVENT = "visibilitychange";
     10 
     11 // Per analytical requirement, we set the minimal intersection ratio to
     12 // 0.5, and an impression is identified when the wrapped item has at least
     13 // 50% visibility.
     14 //
     15 // This constant is exported for unit test
     16 export const INTERSECTION_RATIO = 0.5;
     17 
     18 /**
     19 * Impression wrapper for a TopSite tile.
     20 *
     21 * It makses use of the Intersection Observer API to detect the visibility,
     22 * and relies on page visibility to ensure the impression is reported
     23 * only when the component is visible on the page.
     24 */
     25 export class TopSiteImpressionWrapper extends React.PureComponent {
     26  _dispatchImpressionStats() {
     27    const { actionType, tile } = this.props;
     28    if (!actionType) {
     29      return;
     30    }
     31 
     32    this.props.dispatch(
     33      ac.OnlyToMain({
     34        type: actionType,
     35        data: {
     36          type: "impression",
     37          ...tile,
     38        },
     39      })
     40    );
     41  }
     42 
     43  setImpressionObserverOrAddListener() {
     44    const { props } = this;
     45 
     46    if (!props.dispatch) {
     47      return;
     48    }
     49 
     50    if (props.document.visibilityState === VISIBLE) {
     51      this.setImpressionObserver();
     52    } else {
     53      // We should only ever send the latest impression stats ping, so remove any
     54      // older listeners.
     55      if (this._onVisibilityChange) {
     56        props.document.removeEventListener(
     57          VISIBILITY_CHANGE_EVENT,
     58          this._onVisibilityChange
     59        );
     60      }
     61 
     62      this._onVisibilityChange = () => {
     63        if (props.document.visibilityState === VISIBLE) {
     64          this.setImpressionObserver();
     65          props.document.removeEventListener(
     66            VISIBILITY_CHANGE_EVENT,
     67            this._onVisibilityChange
     68          );
     69        }
     70      };
     71      props.document.addEventListener(
     72        VISIBILITY_CHANGE_EVENT,
     73        this._onVisibilityChange
     74      );
     75    }
     76  }
     77 
     78  /**
     79   * Set an impression observer for the wrapped component. It makes use of
     80   * the Intersection Observer API to detect if the wrapped component is
     81   * visible with a desired ratio, and only sends impression if that's the case.
     82   *
     83   * See more details about Intersection Observer API at:
     84   * https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
     85   */
     86  setImpressionObserver() {
     87    const { props } = this;
     88 
     89    if (!props.tile) {
     90      return;
     91    }
     92 
     93    this._handleIntersect = entries => {
     94      if (
     95        entries.some(
     96          entry =>
     97            entry.isIntersecting &&
     98            entry.intersectionRatio >= INTERSECTION_RATIO
     99        )
    100      ) {
    101        this._dispatchImpressionStats();
    102        this.impressionObserver.unobserve(this.refs.topsite_impression_wrapper);
    103      }
    104    };
    105 
    106    const options = { threshold: INTERSECTION_RATIO };
    107    this.impressionObserver = new props.IntersectionObserver(
    108      this._handleIntersect,
    109      options
    110    );
    111    this.impressionObserver.observe(this.refs.topsite_impression_wrapper);
    112  }
    113 
    114  componentDidMount() {
    115    if (this.props.tile) {
    116      this.setImpressionObserverOrAddListener();
    117    }
    118  }
    119 
    120  componentWillUnmount() {
    121    if (this._handleIntersect && this.impressionObserver) {
    122      this.impressionObserver.unobserve(this.refs.topsite_impression_wrapper);
    123    }
    124    if (this._onVisibilityChange) {
    125      this.props.document.removeEventListener(
    126        VISIBILITY_CHANGE_EVENT,
    127        this._onVisibilityChange
    128      );
    129    }
    130  }
    131 
    132  render() {
    133    return (
    134      <div
    135        ref={"topsite_impression_wrapper"}
    136        className="topsite-impression-observer"
    137      >
    138        {this.props.children}
    139      </div>
    140    );
    141  }
    142 }
    143 
    144 TopSiteImpressionWrapper.defaultProps = {
    145  IntersectionObserver: globalThis.IntersectionObserver,
    146  document: globalThis.document,
    147  actionType: null,
    148  tile: null,
    149 };