1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5var sendRequestNatives = requireNative('sendRequest');
6
7function registerHooks(api) {
8  var chromeTest = api.compiledApi;
9  var apiFunctions = api.apiFunctions;
10
11  apiFunctions.setHandleRequest('notifyPass', function() {
12    requireAsync('testNatives').then(function(natives) {
13      natives.NotifyPass();
14    });
15  });
16
17  apiFunctions.setHandleRequest('notifyFail', function(message) {
18    requireAsync('testNatives').then(function(natives) {
19      natives.NotifyFail(message);
20    });
21  });
22
23  apiFunctions.setHandleRequest('log', function() {
24    requireAsync('testNatives').then(function(natives) {
25      natives.Log($Array.join(arguments, ' '));
26    });
27  });
28
29}
30
31function testDone(runNextTest) {
32    // Use a promise here to allow previous test contexts to be eligible for
33    // garbage collection.
34    Promise.resolve().then(function() {
35      runNextTest();
36    });
37}
38
39function exportTests(tests, runTests, exports) {
40  $Array.forEach(tests, function(test) {
41    exports[test.name] = function() {
42      runTests([test]);
43      return true;
44    };
45  });
46}
47
48/**
49 * A fake implementation of setTimeout and clearTimeout.
50 * @constructor
51 */
52function TimeoutManager() {
53  this.timeouts_ = {};
54  this.nextTimeoutId_ = 0;
55  this.currentTime = 0;
56  this.autorunEnabled_ = false;
57}
58
59/**
60 * Installs setTimeout and clearTimeout into the global object.
61 */
62TimeoutManager.prototype.installGlobals = function() {
63  var global = sendRequestNatives.GetGlobal({});
64  global.setTimeout = this.setTimeout_.bind(this);
65  global.clearTimeout = this.clearTimeout_.bind(this);
66};
67
68/**
69 * Starts auto-running of timeout callbacks. Until |numCallbacksToRun| callbacks
70 * have run, any timeout callbacks set by calls to setTimeout (including before
71 * the call to run) will cause the currentTime to be advanced to the time of
72 * the timeout.
73 */
74TimeoutManager.prototype.run = function(numCallbacksToRun) {
75  this.numCallbacksToRun_ = numCallbacksToRun;
76  Promise.resolve().then(this.autoRun_.bind(this));
77};
78
79/**
80 * Runs timeout callbacks with earliest timeout.
81 * @private
82 */
83TimeoutManager.prototype.autoRun_ = function() {
84  if (this.numCallbacksToRun_ <= 0 || $Object.keys(this.timeouts_).length == 0)
85    return;
86
87  // Bucket the timeouts by their timeout time.
88  var timeoutsByTimeout = {};
89  var timeoutIds = $Object.keys(this.timeouts_);
90  for (var i = 0; i < timeoutIds.length; i++) {
91    var timeout = this.timeouts_[timeoutIds[i]];
92    var timeMs = timeout.timeMs;
93    if (!timeoutsByTimeout[timeMs])
94      timeoutsByTimeout[timeMs] = [];
95    timeoutsByTimeout[timeMs].push(timeout);
96  }
97  this.currentTime =
98      $Function.apply(Math.min, null, $Object.keys((timeoutsByTimeout)));
99  // Run all timeouts in the earliest timeout bucket.
100  var timeouts = timeoutsByTimeout[this.currentTime];
101  for (var i = 0; i < timeouts.length; i++) {
102    var currentTimeout = timeouts[i];
103    if (!this.timeouts_[currentTimeout.id])
104      continue;
105    this.numCallbacksToRun_--;
106    delete this.timeouts_[currentTimeout.id];
107    try {
108      currentTimeout.target();
109    } catch (e) {
110      console.log('error calling timeout target ' + e.stack);
111    }
112  }
113  // Continue running any later callbacks.
114  Promise.resolve().then(this.autoRun_.bind(this));
115};
116
117/**
118 * A fake implementation of setTimeout. This does not support passing callback
119 * arguments.
120 * @private
121 */
122TimeoutManager.prototype.setTimeout_ = function(target, timeoutMs) {
123  var timeoutId = this.nextTimeoutId_++;
124  this.timeouts_[timeoutId] = {
125    id: timeoutId,
126    target: target,
127    timeMs: timeoutMs + this.currentTime,
128  };
129  if (this.autorunEnabled_)
130    Promise.resolve().then(this.autoRun_.bind(this));
131  return timeoutId;
132};
133
134/**
135 * A fake implementation of clearTimeout.
136 * @private
137 */
138TimeoutManager.prototype.clearTimeout_ = function(timeoutId) {
139  if (this.timeouts_[timeoutId])
140    delete this.timeouts_[timeoutId];
141};
142
143exports.registerHooks = registerHooks;
144exports.testDone = testDone;
145exports.exportTests = exportTests;
146exports.TimeoutManager = TimeoutManager;
147