Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | /** |
michael@0 | 6 | * ProgressReporter |
michael@0 | 7 | * |
michael@0 | 8 | * This class is used by long-winded tasks to report progress to observers. |
michael@0 | 9 | * If a task has subtasks that want to report their own progress, these |
michael@0 | 10 | * subtasks can have their own progress reporters which are hooked up to the |
michael@0 | 11 | * parent progress reporter, resulting in a tree structure. A parent progress |
michael@0 | 12 | * reporter will calculate its progress value as a weighted sum of its |
michael@0 | 13 | * subreporters' progress values. |
michael@0 | 14 | * |
michael@0 | 15 | * A progress reporter has a state, an action, and a progress value. |
michael@0 | 16 | * |
michael@0 | 17 | * - state is one of STATE_WAITING, STATE_DOING and STATE_FINISHED. |
michael@0 | 18 | * - action is a string that describes the current task. |
michael@0 | 19 | * - progress is the progress value as a number between 0 and 1, or NaN if |
michael@0 | 20 | * indeterminate. |
michael@0 | 21 | * |
michael@0 | 22 | * A progress reporter starts out in the WAITING state. The DOING state is |
michael@0 | 23 | * entered with the begin method which also sets the action. While the task is |
michael@0 | 24 | * executing, the progress value can be updated with the setProgress method. |
michael@0 | 25 | * When a task has finished, it can call the finish method which is just a |
michael@0 | 26 | * shorthand for setProgress(1); this will set the state to FINISHED. |
michael@0 | 27 | * |
michael@0 | 28 | * Progress observers can be added with the addListener method which takes a |
michael@0 | 29 | * function callback. Whenever the progress value or state change, all |
michael@0 | 30 | * listener callbacks will be called with the progress reporter object. The |
michael@0 | 31 | * observer can get state, progress value and action by calling the getter |
michael@0 | 32 | * methods getState(), getProgress() and getAction(). |
michael@0 | 33 | * |
michael@0 | 34 | * Creating child progress reporters for subtasks can be done with the |
michael@0 | 35 | * addSubreporter(s) methods. If a progress reporter has subreporters, normal |
michael@0 | 36 | * progress report functions (setProgress and finish) can no longer be called. |
michael@0 | 37 | * Instead, the parent reporter will listen to progress changes on its |
michael@0 | 38 | * subreporters and update its state automatically, and then notify its own |
michael@0 | 39 | * listeners. |
michael@0 | 40 | * When adding a subreporter, you are expected to provide an estimated |
michael@0 | 41 | * duration for the subtask. This value will be used as a weight when |
michael@0 | 42 | * calculating the progress of the parent reporter. |
michael@0 | 43 | */ |
michael@0 | 44 | |
michael@0 | 45 | "use strict"; |
michael@0 | 46 | |
michael@0 | 47 | const gDebugExpectedDurations = false; |
michael@0 | 48 | |
michael@0 | 49 | function ProgressReporter() { |
michael@0 | 50 | this._observers = []; |
michael@0 | 51 | this._subreporters = []; |
michael@0 | 52 | this._subreporterExpectedDurationsSum = 0; |
michael@0 | 53 | this._progress = 0; |
michael@0 | 54 | this._state = ProgressReporter.STATE_WAITING; |
michael@0 | 55 | this._action = ""; |
michael@0 | 56 | } |
michael@0 | 57 | |
michael@0 | 58 | ProgressReporter.STATE_WAITING = 0; |
michael@0 | 59 | ProgressReporter.STATE_DOING = 1; |
michael@0 | 60 | ProgressReporter.STATE_FINISHED = 2; |
michael@0 | 61 | |
michael@0 | 62 | ProgressReporter.prototype = { |
michael@0 | 63 | getProgress: function () { |
michael@0 | 64 | return this._progress; |
michael@0 | 65 | }, |
michael@0 | 66 | getState: function () { |
michael@0 | 67 | return this._state; |
michael@0 | 68 | }, |
michael@0 | 69 | setAction: function (action) { |
michael@0 | 70 | this._action = action; |
michael@0 | 71 | this._reportProgress(); |
michael@0 | 72 | }, |
michael@0 | 73 | getAction: function () { |
michael@0 | 74 | switch (this._state) { |
michael@0 | 75 | case ProgressReporter.STATE_WAITING: |
michael@0 | 76 | return "Waiting for preceding tasks to finish..."; |
michael@0 | 77 | case ProgressReporter.STATE_DOING: |
michael@0 | 78 | return this._action; |
michael@0 | 79 | case ProgressReporter.STATE_FINISHED: |
michael@0 | 80 | return "Finished."; |
michael@0 | 81 | default: |
michael@0 | 82 | throw "Broken state"; |
michael@0 | 83 | } |
michael@0 | 84 | }, |
michael@0 | 85 | addListener: function (callback) { |
michael@0 | 86 | this._observers.push(callback); |
michael@0 | 87 | }, |
michael@0 | 88 | addSubreporter: function (expectedDuration) { |
michael@0 | 89 | this._subreporterExpectedDurationsSum += expectedDuration; |
michael@0 | 90 | var subreporter = new ProgressReporter(); |
michael@0 | 91 | var self = this; |
michael@0 | 92 | subreporter.addListener(function (progress) { |
michael@0 | 93 | self._recalculateProgressFromSubreporters(); |
michael@0 | 94 | self._recalculateStateAndActionFromSubreporters(); |
michael@0 | 95 | self._reportProgress(); |
michael@0 | 96 | }); |
michael@0 | 97 | this._subreporters.push({ expectedDuration: expectedDuration, reporter: subreporter }); |
michael@0 | 98 | return subreporter; |
michael@0 | 99 | }, |
michael@0 | 100 | addSubreporters: function (expectedDurations) { |
michael@0 | 101 | var reporters = {}; |
michael@0 | 102 | for (var key in expectedDurations) { |
michael@0 | 103 | reporters[key] = this.addSubreporter(expectedDurations[key]); |
michael@0 | 104 | } |
michael@0 | 105 | return reporters; |
michael@0 | 106 | }, |
michael@0 | 107 | begin: function (action) { |
michael@0 | 108 | this._startTime = Date.now(); |
michael@0 | 109 | this._state = ProgressReporter.STATE_DOING; |
michael@0 | 110 | this._action = action; |
michael@0 | 111 | this._reportProgress(); |
michael@0 | 112 | }, |
michael@0 | 113 | setProgress: function (progress) { |
michael@0 | 114 | if (this._subreporters.length > 0) |
michael@0 | 115 | throw "Can't call setProgress on a progress reporter with subreporters"; |
michael@0 | 116 | if (progress != this._progress && |
michael@0 | 117 | (progress == 1 || |
michael@0 | 118 | (isNaN(progress) != isNaN(this._progress)) || |
michael@0 | 119 | (progress - this._progress >= 0.01))) { |
michael@0 | 120 | this._progress = progress; |
michael@0 | 121 | if (progress == 1) |
michael@0 | 122 | this._transitionToFinished(); |
michael@0 | 123 | this._reportProgress(); |
michael@0 | 124 | } |
michael@0 | 125 | }, |
michael@0 | 126 | finish: function () { |
michael@0 | 127 | this.setProgress(1); |
michael@0 | 128 | }, |
michael@0 | 129 | _recalculateProgressFromSubreporters: function () { |
michael@0 | 130 | if (this._subreporters.length == 0) |
michael@0 | 131 | throw "Can't _recalculateProgressFromSubreporters on a progress reporter without any subreporters"; |
michael@0 | 132 | this._progress = 0; |
michael@0 | 133 | for (var i = 0; i < this._subreporters.length; i++) { |
michael@0 | 134 | var expectedDuration = this._subreporters[i].expectedDuration; |
michael@0 | 135 | var reporter = this._subreporters[i].reporter; |
michael@0 | 136 | this._progress += reporter.getProgress() * expectedDuration / this._subreporterExpectedDurationsSum; |
michael@0 | 137 | } |
michael@0 | 138 | }, |
michael@0 | 139 | _recalculateStateAndActionFromSubreporters: function () { |
michael@0 | 140 | if (this._subreporters.length == 0) |
michael@0 | 141 | throw "Can't _recalculateStateAndActionFromSubreporters on a progress reporter without any subreporters"; |
michael@0 | 142 | var actions = []; |
michael@0 | 143 | var allWaiting = true; |
michael@0 | 144 | var allFinished = true; |
michael@0 | 145 | for (var i = 0; i < this._subreporters.length; i++) { |
michael@0 | 146 | var expectedDuration = this._subreporters[i].expectedDuration; |
michael@0 | 147 | var reporter = this._subreporters[i].reporter; |
michael@0 | 148 | var state = reporter.getState(); |
michael@0 | 149 | if (state != ProgressReporter.STATE_WAITING) |
michael@0 | 150 | allWaiting = false; |
michael@0 | 151 | if (state != ProgressReporter.STATE_FINISHED) |
michael@0 | 152 | allFinished = false; |
michael@0 | 153 | if (state == ProgressReporter.STATE_DOING) |
michael@0 | 154 | actions.push(reporter.getAction()); |
michael@0 | 155 | } |
michael@0 | 156 | if (allFinished) { |
michael@0 | 157 | this._transitionToFinished(); |
michael@0 | 158 | } else if (!allWaiting) { |
michael@0 | 159 | this._state = ProgressReporter.STATE_DOING; |
michael@0 | 160 | if (actions.length == 0) { |
michael@0 | 161 | this._action = "About to start next task..." |
michael@0 | 162 | } else { |
michael@0 | 163 | this._action = actions.join("\n"); |
michael@0 | 164 | } |
michael@0 | 165 | } |
michael@0 | 166 | }, |
michael@0 | 167 | _transitionToFinished: function () { |
michael@0 | 168 | this._state = ProgressReporter.STATE_FINISHED; |
michael@0 | 169 | |
michael@0 | 170 | if (gDebugExpectedDurations) { |
michael@0 | 171 | this._realDuration = Date.now() - this._startTime; |
michael@0 | 172 | if (this._subreporters.length) { |
michael@0 | 173 | for (var i = 0; i < this._subreporters.length; i++) { |
michael@0 | 174 | var expectedDuration = this._subreporters[i].expectedDuration; |
michael@0 | 175 | var reporter = this._subreporters[i].reporter; |
michael@0 | 176 | var realDuration = reporter._realDuration; |
michael@0 | 177 | dump("For reporter with expectedDuration " + expectedDuration + ", real duration was " + realDuration + "\n"); |
michael@0 | 178 | } |
michael@0 | 179 | } |
michael@0 | 180 | } |
michael@0 | 181 | }, |
michael@0 | 182 | _reportProgress: function () { |
michael@0 | 183 | for (var i = 0; i < this._observers.length; i++) { |
michael@0 | 184 | this._observers[i](this); |
michael@0 | 185 | } |
michael@0 | 186 | }, |
michael@0 | 187 | }; |