tor-browser

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

test_suspendTimeouts.js (6587B)


      1 "use strict";
      2 
      3 // The debugger uses nsIDOMWindowUtils::suspendTimeouts and ...::resumeTimeouts
      4 // to ensure that content event handlers do not run while a JavaScript
      5 // invocation is stepping or paused at a breakpoint. If a worker thread sends
      6 // messages to the content while the content is paused, those messages must not
      7 // run until the JavaScript invocation interrupted by the debugger has completed.
      8 //
      9 // Bug 1426467 is that calling nsIDOMWindowUtils::resumeTimeouts actually
     10 // delivers deferred messages itself, calling the content's 'onmessage' handler.
     11 // But the debugger calls suspend/resume around each individual interruption of
     12 // the debuggee -- each step, say -- meaning that hitting the "step into" button
     13 // causes you to step from the debuggee directly into an onmessage handler,
     14 // since the onmessage handler is the next function call the debugger sees.
     15 //
     16 // In other words, delivering deferred messages from resumeTimeouts, as it is
     17 // used by the debugger, breaks the run-to-completion rule. They must not be
     18 // delivered until after the JavaScript invocation at hand is complete. That's
     19 // what this test checks.
     20 //
     21 // For this test to detect the bug, the following steps must take place in
     22 // order:
     23 //
     24 // 1) The content page must call suspendTimeouts.
     25 // 2) A runnable conveying a message from the worker thread must attempt to
     26 //    deliver the message, see that the content page has suspended such things,
     27 //    and hold the message for later delivery.
     28 // 3) The content page must call resumeTimeouts.
     29 //
     30 // In a correct implementation, the message from the worker thread is delivered
     31 // only after the main thread returns to the event loop after calling
     32 // resumeTimeouts in step 3). In the buggy implementation, the onmessage handler
     33 // is called directly from the call to resumeTimeouts, so that the onmessage
     34 // handlers run in the midst of whatever JavaScript invocation resumed timeouts
     35 // (say, stepping in the debugger), in violation of the run-to-completion rule.
     36 //
     37 // In this specific bug, the handlers are called from resumeTimeouts, but
     38 // really, running them any time before that invocation returns to the main
     39 // event loop would be a bug.
     40 //
     41 // Posting the message and calling resumeTimeouts take place in different
     42 // threads, but if 2) and 3) don't occur in that order, the worker's message
     43 // will never be delayed and the test will pass spuriously. But the worker
     44 // can't communicate with the content page directly, to let it know that it
     45 // should proceed with step 3): the purpose of suspendTimeouts is to pause
     46 // all such communication.
     47 //
     48 // So instead, the content page creates a MessageChannel, and passes one
     49 // MessagePort to the worker and the other to this mochitest (which has its
     50 // own window, separate from the one calling suspendTimeouts). The worker
     51 // notifies the mochitest when it has posted the message, and then the
     52 // mochitest calls into the content to carry out step 3).
     53 
     54 // To help you follow all the callbacks and event handlers, this code pulls out
     55 // event handler functions so that control flows from top to bottom.
     56 
     57 window.onload = function () {
     58  // This mochitest is not complete until we call SimpleTest.finish. Don't just
     59  // exit as soon as we return to the main event loop.
     60  SimpleTest.waitForExplicitFinish();
     61 
     62  const iframe = document.createElement("iframe");
     63  iframe.src =
     64    "http://mochi.test:8888/chrome/devtools/server/tests/chrome/suspendTimeouts_content.html";
     65  iframe.onload = iframe_onload_handler;
     66  document.body.appendChild(iframe);
     67 
     68  function iframe_onload_handler() {
     69    const content = iframe.contentWindow.wrappedJSObject;
     70 
     71    const windowUtils = iframe.contentWindow.windowUtils;
     72 
     73    // Hand over the suspend and resume functions to the content page, along
     74    // with some testing utilities.
     75    content.suspendTimeouts = function () {
     76      SimpleTest.info("test_suspendTimeouts", "calling suspendTimeouts");
     77      windowUtils.suspendTimeouts();
     78    };
     79    content.resumeTimeouts = function () {
     80      windowUtils.resumeTimeouts();
     81      SimpleTest.info("test_suspendTimeouts", "resumeTimeouts called");
     82    };
     83    content.info = function (message) {
     84      SimpleTest.info("suspendTimeouts_content.js", message);
     85    };
     86    content.ok = SimpleTest.ok;
     87    content.finish = finish;
     88 
     89    SimpleTest.info(
     90      "Disappointed with National Tautology Day? Well, it is what it is."
     91    );
     92 
     93    // Once the worker has sent a message to its parent (which should get delayed),
     94    // it sends us a message directly on this channel.
     95    const workerPort = content.create_channel();
     96    workerPort.onmessage = handle_worker_echo;
     97 
     98    // Have content send the worker a message that it should echo back to both
     99    // content and us. The echo to content should get delayed; the echo to us
    100    // should cause our handle_worker_echo to be called.
    101    content.start_worker();
    102 
    103    function handle_worker_echo({ data }) {
    104      info(`mochitest received message from worker: ${data}`);
    105 
    106      // As it turns out, it's not correct to assume that, if the worker posts a
    107      // message to its parent via the global `postMessage` function, and then
    108      // posts a message to the mochitest via the MessagePort, those two
    109      // messages will be delivered in the order they were sent.
    110      //
    111      // - Messages sent via the worker's global's postMessage go through two
    112      //   ThrottledEventQueues (one in the worker, and one on the parent), and
    113      //   eventually find their way into the thread's primary event queue,
    114      //   which is a PrioritizedEventQueue.
    115      //
    116      // - Messages sent via a MessageChannel whose ports are owned by different
    117      //   threads are passed as IPDL messages.
    118      //
    119      // There's basically no reliable way to ensure that delivery to content
    120      // has been attempted and the runnable deferred; there are too many
    121      // variables affecting the order in which things are processed. Delaying
    122      // for a second is the best I could think of.
    123      //
    124      // Fortunately, this tactic failing can only cause spurious test passes
    125      // (the runnable never gets deferred, so things work by accident), not
    126      // spurious failures. Without some kind of trustworthy notification that
    127      // the runnable has been deferred, perhaps via some special white-box
    128      // testing API, we can't do better.
    129      setTimeout(() => {
    130        content.resume_timeouts();
    131      }, 1000);
    132    }
    133 
    134    function finish() {
    135      SimpleTest.info("suspendTimeouts_content.js", "called finish");
    136      SimpleTest.finish();
    137    }
    138  }
    139 };