1<!DOCTYPE html>
2<!--
3Copyright (c) 2013 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7<link rel="import" href="/tracing/base/raf.html">
8<link rel="import" href="/tracing/base/timing.html">
9
10<script>
11'use strict';
12
13tr.exportTo('tr.b', function() {
14  var Timing = tr.b.Timing;
15  /**
16   * A task is a combination of a run callback, a set of subtasks, and an after
17   * task.
18   *
19   * When executed, a task does the following things:
20   * 1. Runs its callback
21   * 2. Runs its subtasks
22   * 3. Runs its after callback.
23   *
24   * The list of subtasks and after task can be mutated inside step #1 but as
25   * soon as the task's callback returns, the subtask list and after task is
26   * fixed and cannot be changed again.
27   *
28   * Use task.after().after().after() to describe the toplevel passes that make
29   * up your computation. Then, use subTasks to add detail to each subtask as it
30   * runs. For example:
31   *    var pieces = [];
32   *    taskA = new Task(function() { pieces = getPieces(); });
33   *    taskA.after(function(taskA) {
34   *      pieces.forEach(function(piece) {
35   *        taskA.subTask(function(taskB) { piece.process(); }, this);
36   *      });
37   *    });
38   *
39   * @constructor
40   */
41  function Task(runCb, thisArg) {
42    if (runCb !== undefined && thisArg === undefined)
43      throw new Error('Almost certainly, you meant to pass a thisArg.');
44    this.runCb_ = runCb;
45    this.thisArg_ = thisArg;
46    this.afterTask_ = undefined;
47    this.subTasks_ = [];
48  }
49
50  Task.prototype = {
51    get name() {
52      return this.runCb_.name;
53    },
54
55    /*
56     * See constructor documentation on semantics of subtasks.
57     */
58    subTask: function(cb, thisArg) {
59      if (cb instanceof Task)
60        this.subTasks_.push(cb);
61      else
62        this.subTasks_.push(new Task(cb, thisArg));
63      return this.subTasks_[this.subTasks_.length - 1];
64    },
65
66    /**
67     * Runs the current task and returns the task that should be executed next.
68     */
69    run: function() {
70      if (this.runCb_ !== undefined)
71        this.runCb_.call(this.thisArg_, this);
72      var subTasks = this.subTasks_;
73      this.subTasks_ = undefined; // Prevent more subTasks from being posted.
74
75      if (!subTasks.length)
76        return this.afterTask_;
77
78      // If there are subtasks, then we want to execute all the subtasks and
79      // then this task's afterTask. To make this happen, we update the
80      // afterTask of all the subtasks so the point upward to each other, e.g.
81      // subTask[0].afterTask to subTask[1] and so on. Then, the last subTask's
82      // afterTask points at this task's afterTask.
83      for (var i = 1; i < subTasks.length; i++)
84        subTasks[i - 1].afterTask_ = subTasks[i];
85      subTasks[subTasks.length - 1].afterTask_ = this.afterTask_;
86      return subTasks[0];
87    },
88
89    /*
90     * See constructor documentation on semantics of after tasks.
91     */
92    after: function(cb, thisArg) {
93      if (this.afterTask_)
94        throw new Error('Has an after task already');
95      if (cb instanceof Task)
96        this.afterTask_ = cb;
97      else
98        this.afterTask_ = new Task(cb, thisArg);
99      return this.afterTask_;
100    },
101
102    /*
103     * See constructor documentation on semantics of after tasks.
104     * Note: timedAfter doesn't work when a task throws an exception.
105     * This is because task system doesn't support catching currently.
106     * At the time of writing, this is considered to be an acceptable tradeoff.
107     */
108    timedAfter: function(groupName, cb, thisArg, opt_args) {
109      if (cb.name === '')
110        throw new Error('Anonymous Task is not allowed');
111      return this.namedTimedAfter(groupName, cb.name, cb, thisArg, opt_args);
112    },
113
114    /*
115     * See constructor documentation on semantics of after tasks.
116     * Note: namedTimedAfter doesn't work when a task throws an exception.
117     * This is because task system doesn't support catching currently.
118     * At the time of writing, this is considered to be an acceptable tradeoff.
119     */
120    namedTimedAfter: function(groupName, name, cb, thisArg, opt_args) {
121      if (this.afterTask_)
122        throw new Error('Has an after task already');
123      var realTask;
124      if (cb instanceof Task)
125        realTask = cb;
126      else
127        realTask = new Task(cb, thisArg);
128      this.afterTask_ = new Task(function(task) {
129        var markedTask = Timing.mark(groupName, name, opt_args);
130        task.subTask(realTask, thisArg);
131        task.subTask(function() {
132          markedTask.end();
133        }, thisArg);
134      }, thisArg);
135      return this.afterTask_;
136    },
137
138    /*
139     * Adds a task after the chain of tasks.
140     */
141    enqueue: function(cb, thisArg) {
142      var lastTask = this;
143      while (lastTask.afterTask_)
144        lastTask = lastTask.afterTask_;
145      return lastTask.after(cb, thisArg);
146    }
147  };
148
149  Task.RunSynchronously = function(task) {
150    var curTask = task;
151    while (curTask)
152      curTask = curTask.run();
153  }
154
155  /**
156   * Runs a task using raf.requestIdleCallback, returning
157   * a promise for its completion.
158   */
159  Task.RunWhenIdle = function(task) {
160    return new Promise(function(resolve, reject) {
161      var curTask = task;
162      function runAnother() {
163        try {
164          curTask = curTask.run();
165        } catch (e) {
166          reject(e);
167          console.error(e.stack);
168          return;
169        }
170
171        if (curTask) {
172          tr.b.requestIdleCallback(runAnother);
173          return;
174        }
175
176        resolve();
177      }
178      tr.b.requestIdleCallback(runAnother);
179    });
180  }
181
182  return {
183    Task: Task
184  };
185});
186</script>
187