tor-browser

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

test_layout-reflows-observer.js (9044B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Test the LayoutChangesObserver
      7 
      8 /* eslint-disable mozilla/use-chromeutils-generateqi */
      9 
     10 var {
     11  getLayoutChangesObserver,
     12  releaseLayoutChangesObserver,
     13  LayoutChangesObserver,
     14 } = require("resource://devtools/server/actors/reflow.js");
     15 const EventEmitter = require("resource://devtools/shared/event-emitter.js");
     16 
     17 // Override set/clearTimeout on LayoutChangesObserver to avoid depending on
     18 // time in this unit test. This means that LayoutChangesObserver.eventLoopTimer
     19 // will be the timeout callback instead of the timeout itself, so test cases
     20 // will need to execute it to fake a timeout
     21 LayoutChangesObserver.prototype._setTimeout = cb => cb;
     22 LayoutChangesObserver.prototype._clearTimeout = function () {};
     23 
     24 // Mock the targetActor since we only really want to test the LayoutChangesObserver
     25 // and don't want to depend on a window object, nor want to test protocol.js
     26 class MockTargetActor extends EventEmitter {
     27  constructor() {
     28    super();
     29    this.docShell = new MockDocShell();
     30    this.window = new MockWindow(this.docShell);
     31    this.windows = [this.window];
     32    this.attached = true;
     33  }
     34 
     35  get chromeEventHandler() {
     36    return this.docShell.chromeEventHandler;
     37  }
     38 
     39  isDestroyed() {
     40    return false;
     41  }
     42 }
     43 
     44 class MockWindow {
     45  constructor(docShell) {
     46    this.docShell = docShell;
     47  }
     48  QueryInterface() {
     49    const self = this;
     50    return {
     51      getInterface() {
     52        return {
     53          QueryInterface() {
     54            return self.docShell;
     55          },
     56        };
     57      },
     58    };
     59  }
     60  setTimeout(cb) {
     61    // Simply return the cb itself so that we can execute it in the test instead
     62    // of depending on a real timeout
     63    return cb;
     64  }
     65  clearTimeout() {}
     66 }
     67 
     68 class MockDocShell {
     69  constructor() {
     70    this.observer = null;
     71  }
     72  addWeakReflowObserver(observer) {
     73    this.observer = observer;
     74  }
     75  removeWeakReflowObserver() {}
     76  get chromeEventHandler() {
     77    return {
     78      addEventListener: (type, cb) => {
     79        if (type === "resize") {
     80          this.resizeCb = cb;
     81        }
     82      },
     83      removeEventListener: (type, cb) => {
     84        if (type === "resize" && cb === this.resizeCb) {
     85          this.resizeCb = null;
     86        }
     87      },
     88    };
     89  }
     90  mockResize() {
     91    if (this.resizeCb) {
     92      this.resizeCb();
     93    }
     94  }
     95 }
     96 
     97 function run_test() {
     98  instancesOfObserversAreSharedBetweenWindows();
     99  eventsAreBatched();
    100  noEventsAreSentWhenThereAreNoReflowsAndLoopTimeouts();
    101  observerIsAlreadyStarted();
    102  destroyStopsObserving();
    103  stoppingAndStartingSeveralTimesWorksCorrectly();
    104  reflowsArentStackedWhenStopped();
    105  stackedReflowsAreResetOnStop();
    106 }
    107 
    108 function instancesOfObserversAreSharedBetweenWindows() {
    109  info(
    110    "Checking that when requesting twice an instances of the observer " +
    111      "for the same WindowGlobalTargetActor, the instance is shared"
    112  );
    113 
    114  info("Checking 2 instances of the observer for the targetActor 1");
    115  const targetActor1 = new MockTargetActor();
    116  const obs11 = getLayoutChangesObserver(targetActor1);
    117  const obs12 = getLayoutChangesObserver(targetActor1);
    118  Assert.equal(obs11, obs12);
    119 
    120  info("Checking 2 instances of the observer for the targetActor 2");
    121  const targetActor2 = new MockTargetActor();
    122  const obs21 = getLayoutChangesObserver(targetActor2);
    123  const obs22 = getLayoutChangesObserver(targetActor2);
    124  Assert.equal(obs21, obs22);
    125 
    126  info(
    127    "Checking that observers instances for 2 different targetActors are " +
    128      "different"
    129  );
    130  Assert.notEqual(obs11, obs21);
    131 
    132  releaseLayoutChangesObserver(targetActor1);
    133  releaseLayoutChangesObserver(targetActor1);
    134  releaseLayoutChangesObserver(targetActor2);
    135  releaseLayoutChangesObserver(targetActor2);
    136 }
    137 
    138 function eventsAreBatched() {
    139  info(
    140    "Checking that reflow events are batched and only sent when the " +
    141      "timeout expires"
    142  );
    143 
    144  // Note that in this test, we mock the target actor and its window property, so we also
    145  // mock the setTimeout/clearTimeout mechanism and just call the callback manually
    146  const targetActor = new MockTargetActor();
    147  const observer = getLayoutChangesObserver(targetActor);
    148 
    149  const reflowsEvents = [];
    150  const onReflows = reflows => reflowsEvents.push(reflows);
    151  observer.on("reflows", onReflows);
    152 
    153  const resizeEvents = [];
    154  const onResize = () => resizeEvents.push("resize");
    155  observer.on("resize", onResize);
    156 
    157  info("Fake one reflow event");
    158  targetActor.window.docShell.observer.reflow();
    159  info("Checking that no batched reflow event has been emitted");
    160  Assert.equal(reflowsEvents.length, 0);
    161 
    162  info("Fake another reflow event");
    163  targetActor.window.docShell.observer.reflow();
    164  info("Checking that still no batched reflow event has been emitted");
    165  Assert.equal(reflowsEvents.length, 0);
    166 
    167  info("Fake a few of resize events too");
    168  targetActor.window.docShell.mockResize();
    169  targetActor.window.docShell.mockResize();
    170  targetActor.window.docShell.mockResize();
    171  info("Checking that still no batched resize event has been emitted");
    172  Assert.equal(resizeEvents.length, 0);
    173 
    174  info("Faking timeout expiration and checking that events are sent");
    175  observer.eventLoopTimer();
    176  Assert.equal(reflowsEvents.length, 1);
    177  Assert.equal(reflowsEvents[0].length, 2);
    178  Assert.equal(resizeEvents.length, 1);
    179 
    180  observer.off("reflows", onReflows);
    181  observer.off("resize", onResize);
    182  releaseLayoutChangesObserver(targetActor);
    183 }
    184 
    185 function noEventsAreSentWhenThereAreNoReflowsAndLoopTimeouts() {
    186  info(
    187    "Checking that if no reflows were detected and the event batching " +
    188      "loop expires, then no reflows event is sent"
    189  );
    190 
    191  const targetActor = new MockTargetActor();
    192  const observer = getLayoutChangesObserver(targetActor);
    193 
    194  const reflowsEvents = [];
    195  const onReflows = reflows => reflowsEvents.push(reflows);
    196  observer.on("reflows", onReflows);
    197 
    198  info("Faking timeout expiration and checking for reflows");
    199  observer.eventLoopTimer();
    200  Assert.equal(reflowsEvents.length, 0);
    201 
    202  observer.off("reflows", onReflows);
    203  releaseLayoutChangesObserver(targetActor);
    204 }
    205 
    206 function observerIsAlreadyStarted() {
    207  info("Checking that the observer is already started when getting it");
    208 
    209  const targetActor = new MockTargetActor();
    210  const observer = getLayoutChangesObserver(targetActor);
    211  Assert.ok(observer.isObserving);
    212 
    213  observer.stop();
    214  Assert.ok(!observer.isObserving);
    215 
    216  observer.start();
    217  Assert.ok(observer.isObserving);
    218 
    219  releaseLayoutChangesObserver(targetActor);
    220 }
    221 
    222 function destroyStopsObserving() {
    223  info("Checking that the destroying the observer stops it");
    224 
    225  const targetActor = new MockTargetActor();
    226  const observer = getLayoutChangesObserver(targetActor);
    227  Assert.ok(observer.isObserving);
    228 
    229  observer.destroy();
    230  Assert.ok(!observer.isObserving);
    231 
    232  releaseLayoutChangesObserver(targetActor);
    233 }
    234 
    235 function stoppingAndStartingSeveralTimesWorksCorrectly() {
    236  info(
    237    "Checking that the stopping and starting several times the observer" +
    238      " works correctly"
    239  );
    240 
    241  const targetActor = new MockTargetActor();
    242  const observer = getLayoutChangesObserver(targetActor);
    243 
    244  Assert.ok(observer.isObserving);
    245  observer.start();
    246  observer.start();
    247  observer.start();
    248  Assert.ok(observer.isObserving);
    249 
    250  observer.stop();
    251  Assert.ok(!observer.isObserving);
    252 
    253  observer.stop();
    254  observer.stop();
    255  Assert.ok(!observer.isObserving);
    256 
    257  releaseLayoutChangesObserver(targetActor);
    258 }
    259 
    260 function reflowsArentStackedWhenStopped() {
    261  info("Checking that when stopped, reflows aren't stacked in the observer");
    262 
    263  const targetActor = new MockTargetActor();
    264  const observer = getLayoutChangesObserver(targetActor);
    265 
    266  info("Stoping the observer");
    267  observer.stop();
    268 
    269  info("Faking reflows");
    270  targetActor.window.docShell.observer.reflow();
    271  targetActor.window.docShell.observer.reflow();
    272  targetActor.window.docShell.observer.reflow();
    273 
    274  info("Checking that reflows aren't recorded");
    275  Assert.equal(observer.reflows.length, 0);
    276 
    277  info("Starting the observer and faking more reflows");
    278  observer.start();
    279  targetActor.window.docShell.observer.reflow();
    280  targetActor.window.docShell.observer.reflow();
    281  targetActor.window.docShell.observer.reflow();
    282 
    283  info("Checking that reflows are recorded");
    284  Assert.equal(observer.reflows.length, 3);
    285 
    286  releaseLayoutChangesObserver(targetActor);
    287 }
    288 
    289 function stackedReflowsAreResetOnStop() {
    290  info("Checking that stacked reflows are reset on stop");
    291 
    292  const targetActor = new MockTargetActor();
    293  const observer = getLayoutChangesObserver(targetActor);
    294 
    295  targetActor.window.docShell.observer.reflow();
    296  Assert.equal(observer.reflows.length, 1);
    297 
    298  observer.stop();
    299  Assert.equal(observer.reflows.length, 0);
    300 
    301  targetActor.window.docShell.observer.reflow();
    302  Assert.equal(observer.reflows.length, 0);
    303 
    304  observer.start();
    305  Assert.equal(observer.reflows.length, 0);
    306 
    307  targetActor.window.docShell.observer.reflow();
    308  Assert.equal(observer.reflows.length, 1);
    309 
    310  releaseLayoutChangesObserver(targetActor);
    311 }