Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | "use strict"; |
michael@0 | 8 | |
michael@0 | 9 | this.EXPORTED_SYMBOLS = [ |
michael@0 | 10 | "DeferredTask", |
michael@0 | 11 | ]; |
michael@0 | 12 | |
michael@0 | 13 | /** |
michael@0 | 14 | * Sets up a function or an asynchronous task whose execution can be triggered |
michael@0 | 15 | * after a defined delay. Multiple attempts to run the task before the delay |
michael@0 | 16 | * has passed are coalesced. The task cannot be re-entered while running, but |
michael@0 | 17 | * can be executed again after a previous run finished. |
michael@0 | 18 | * |
michael@0 | 19 | * A common use case occurs when a data structure should be saved into a file |
michael@0 | 20 | * every time the data changes, using asynchronous calls, and multiple changes |
michael@0 | 21 | * to the data may happen within a short time: |
michael@0 | 22 | * |
michael@0 | 23 | * let saveDeferredTask = new DeferredTask(function* () { |
michael@0 | 24 | * yield OS.File.writeAtomic(...); |
michael@0 | 25 | * // Any uncaught exception will be reported. |
michael@0 | 26 | * }, 2000); |
michael@0 | 27 | * |
michael@0 | 28 | * // The task is ready, but will not be executed until requested. |
michael@0 | 29 | * |
michael@0 | 30 | * The "arm" method can be used to start the internal timer that will result in |
michael@0 | 31 | * the eventual execution of the task. Multiple attempts to arm the timer don't |
michael@0 | 32 | * introduce further delays: |
michael@0 | 33 | * |
michael@0 | 34 | * saveDeferredTask.arm(); |
michael@0 | 35 | * |
michael@0 | 36 | * // The task will be executed in 2 seconds from now. |
michael@0 | 37 | * |
michael@0 | 38 | * yield waitOneSecond(); |
michael@0 | 39 | * saveDeferredTask.arm(); |
michael@0 | 40 | * |
michael@0 | 41 | * // The task will be executed in 1 second from now. |
michael@0 | 42 | * |
michael@0 | 43 | * The timer can be disarmed to reset the delay, or just to cancel execution: |
michael@0 | 44 | * |
michael@0 | 45 | * saveDeferredTask.disarm(); |
michael@0 | 46 | * saveDeferredTask.arm(); |
michael@0 | 47 | * |
michael@0 | 48 | * // The task will be executed in 2 seconds from now. |
michael@0 | 49 | * |
michael@0 | 50 | * When the internal timer fires and the execution of the task starts, the task |
michael@0 | 51 | * cannot be canceled anymore. It is however possible to arm the timer again |
michael@0 | 52 | * during the execution of the task, in which case the task will need to finish |
michael@0 | 53 | * before the timer is started again, thus guaranteeing a time of inactivity |
michael@0 | 54 | * between executions that is at least equal to the provided delay. |
michael@0 | 55 | * |
michael@0 | 56 | * The "finalize" method can be used to ensure that the task terminates |
michael@0 | 57 | * properly. The promise it returns is resolved only after the last execution |
michael@0 | 58 | * of the task is finished. To guarantee that the task is executed for the |
michael@0 | 59 | * last time, the method prevents any attempt to arm the timer again. |
michael@0 | 60 | * |
michael@0 | 61 | * If the timer is already armed when the "finalize" method is called, then the |
michael@0 | 62 | * task is executed immediately. If the task was already running at this point, |
michael@0 | 63 | * then one last execution from start to finish will happen again, immediately |
michael@0 | 64 | * after the current execution terminates. If the timer is not armed, the |
michael@0 | 65 | * "finalize" method only ensures that any running task terminates. |
michael@0 | 66 | * |
michael@0 | 67 | * For example, during shutdown, you may want to ensure that any pending write |
michael@0 | 68 | * is processed, using the latest version of the data if the timer is armed: |
michael@0 | 69 | * |
michael@0 | 70 | * AsyncShutdown.profileBeforeChange.addBlocker( |
michael@0 | 71 | * "Example service: shutting down", |
michael@0 | 72 | * () => saveDeferredTask.finalize() |
michael@0 | 73 | * ); |
michael@0 | 74 | * |
michael@0 | 75 | * Instead, if you are going to delete the saved data from disk anyways, you |
michael@0 | 76 | * might as well prevent any pending write from starting, while still ensuring |
michael@0 | 77 | * that any write that is currently in progress terminates, so that the file is |
michael@0 | 78 | * not in use anymore: |
michael@0 | 79 | * |
michael@0 | 80 | * saveDeferredTask.disarm(); |
michael@0 | 81 | * saveDeferredTask.finalize().then(() => OS.File.remove(...)) |
michael@0 | 82 | * .then(null, Components.utils.reportError); |
michael@0 | 83 | */ |
michael@0 | 84 | |
michael@0 | 85 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 86 | //// Globals |
michael@0 | 87 | |
michael@0 | 88 | const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; |
michael@0 | 89 | |
michael@0 | 90 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 91 | |
michael@0 | 92 | XPCOMUtils.defineLazyModuleGetter(this, "Promise", |
michael@0 | 93 | "resource://gre/modules/Promise.jsm"); |
michael@0 | 94 | XPCOMUtils.defineLazyModuleGetter(this, "Task", |
michael@0 | 95 | "resource://gre/modules/Task.jsm"); |
michael@0 | 96 | |
michael@0 | 97 | const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer", |
michael@0 | 98 | "initWithCallback"); |
michael@0 | 99 | |
michael@0 | 100 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 101 | //// DeferredTask |
michael@0 | 102 | |
michael@0 | 103 | /** |
michael@0 | 104 | * Sets up a task whose execution can be triggered after a delay. |
michael@0 | 105 | * |
michael@0 | 106 | * @param aTaskFn |
michael@0 | 107 | * Function or generator function to execute. This argument is passed to |
michael@0 | 108 | * the "Task.spawn" method every time the task should be executed. This |
michael@0 | 109 | * task is never re-entered while running. |
michael@0 | 110 | * @param aDelayMs |
michael@0 | 111 | * Time between executions, in milliseconds. Multiple attempts to run |
michael@0 | 112 | * the task before the delay has passed are coalesced. This time of |
michael@0 | 113 | * inactivity is guaranteed to pass between multiple executions of the |
michael@0 | 114 | * task, except on finalization, when the task may restart immediately |
michael@0 | 115 | * after the previous execution finished. |
michael@0 | 116 | */ |
michael@0 | 117 | this.DeferredTask = function (aTaskFn, aDelayMs) { |
michael@0 | 118 | this._taskFn = aTaskFn; |
michael@0 | 119 | this._delayMs = aDelayMs; |
michael@0 | 120 | } |
michael@0 | 121 | |
michael@0 | 122 | this.DeferredTask.prototype = { |
michael@0 | 123 | /** |
michael@0 | 124 | * Function or generator function to execute. |
michael@0 | 125 | */ |
michael@0 | 126 | _taskFn: null, |
michael@0 | 127 | |
michael@0 | 128 | /** |
michael@0 | 129 | * Time between executions, in milliseconds. |
michael@0 | 130 | */ |
michael@0 | 131 | _delayMs: null, |
michael@0 | 132 | |
michael@0 | 133 | /** |
michael@0 | 134 | * Indicates whether the task is currently requested to start again later, |
michael@0 | 135 | * regardless of whether it is currently running. |
michael@0 | 136 | */ |
michael@0 | 137 | get isArmed() this._armed, |
michael@0 | 138 | _armed: false, |
michael@0 | 139 | |
michael@0 | 140 | /** |
michael@0 | 141 | * Indicates whether the task is currently running. This is always true when |
michael@0 | 142 | * read from code inside the task function, but can also be true when read |
michael@0 | 143 | * from external code, in case the task is an asynchronous generator function. |
michael@0 | 144 | */ |
michael@0 | 145 | get isRunning() !!this._runningPromise, |
michael@0 | 146 | |
michael@0 | 147 | /** |
michael@0 | 148 | * Promise resolved when the current execution of the task terminates, or null |
michael@0 | 149 | * if the task is not currently running. |
michael@0 | 150 | */ |
michael@0 | 151 | _runningPromise: null, |
michael@0 | 152 | |
michael@0 | 153 | /** |
michael@0 | 154 | * nsITimer used for triggering the task after a delay, or null in case the |
michael@0 | 155 | * task is running or there is no task scheduled for execution. |
michael@0 | 156 | */ |
michael@0 | 157 | _timer: null, |
michael@0 | 158 | |
michael@0 | 159 | /** |
michael@0 | 160 | * Actually starts the timer with the delay specified on construction. |
michael@0 | 161 | */ |
michael@0 | 162 | _startTimer: function () |
michael@0 | 163 | { |
michael@0 | 164 | this._timer = new Timer(this._timerCallback.bind(this), this._delayMs, |
michael@0 | 165 | Ci.nsITimer.TYPE_ONE_SHOT); |
michael@0 | 166 | }, |
michael@0 | 167 | |
michael@0 | 168 | /** |
michael@0 | 169 | * Requests the execution of the task after the delay specified on |
michael@0 | 170 | * construction. Multiple calls don't introduce further delays. If the task |
michael@0 | 171 | * is running, the delay will start when the current execution finishes. |
michael@0 | 172 | * |
michael@0 | 173 | * The task will always be executed on a different tick of the event loop, |
michael@0 | 174 | * even if the delay specified on construction is zero. Multiple "arm" calls |
michael@0 | 175 | * within the same tick of the event loop are guaranteed to result in a single |
michael@0 | 176 | * execution of the task. |
michael@0 | 177 | * |
michael@0 | 178 | * @note By design, this method doesn't provide a way for the caller to detect |
michael@0 | 179 | * when the next execution terminates, or collect a result. In fact, |
michael@0 | 180 | * doing that would often result in duplicate processing or logging. If |
michael@0 | 181 | * a special operation or error logging is needed on completion, it can |
michael@0 | 182 | * be better handled from within the task itself, for example using a |
michael@0 | 183 | * try/catch/finally clause in the task. The "finalize" method can be |
michael@0 | 184 | * used in the common case of waiting for completion on shutdown. |
michael@0 | 185 | */ |
michael@0 | 186 | arm: function () |
michael@0 | 187 | { |
michael@0 | 188 | if (this._finalized) { |
michael@0 | 189 | throw new Error("Unable to arm timer, the object has been finalized."); |
michael@0 | 190 | } |
michael@0 | 191 | |
michael@0 | 192 | this._armed = true; |
michael@0 | 193 | |
michael@0 | 194 | // In case the timer callback is running, do not create the timer now, |
michael@0 | 195 | // because this will be handled by the timer callback itself. Also, the |
michael@0 | 196 | // timer is not restarted in case it is already running. |
michael@0 | 197 | if (!this._runningPromise && !this._timer) { |
michael@0 | 198 | this._startTimer(); |
michael@0 | 199 | } |
michael@0 | 200 | }, |
michael@0 | 201 | |
michael@0 | 202 | /** |
michael@0 | 203 | * Cancels any request for a delayed the execution of the task, though the |
michael@0 | 204 | * task itself cannot be canceled in case it is already running. |
michael@0 | 205 | * |
michael@0 | 206 | * This method stops any currently running timer, thus the delay will restart |
michael@0 | 207 | * from its original value in case the "arm" method is called again. |
michael@0 | 208 | */ |
michael@0 | 209 | disarm: function () { |
michael@0 | 210 | this._armed = false; |
michael@0 | 211 | if (this._timer) { |
michael@0 | 212 | // Calling the "cancel" method and discarding the timer reference makes |
michael@0 | 213 | // sure that the timer callback will not be called later, even if the |
michael@0 | 214 | // timer thread has already posted the timer event on the main thread. |
michael@0 | 215 | this._timer.cancel(); |
michael@0 | 216 | this._timer = null; |
michael@0 | 217 | } |
michael@0 | 218 | }, |
michael@0 | 219 | |
michael@0 | 220 | /** |
michael@0 | 221 | * Ensures that any pending task is executed from start to finish, while |
michael@0 | 222 | * preventing any attempt to arm the timer again. |
michael@0 | 223 | * |
michael@0 | 224 | * - If the task is running and the timer is armed, then one last execution |
michael@0 | 225 | * from start to finish will happen again, immediately after the current |
michael@0 | 226 | * execution terminates, then the returned promise will be resolved. |
michael@0 | 227 | * - If the task is running and the timer is not armed, the returned promise |
michael@0 | 228 | * will be resolved when the current execution terminates. |
michael@0 | 229 | * - If the task is not running and the timer is armed, then the task is |
michael@0 | 230 | * started immediately, and the returned promise resolves when the new |
michael@0 | 231 | * execution terminates. |
michael@0 | 232 | * - If the task is not running and the timer is not armed, the method returns |
michael@0 | 233 | * a resolved promise. |
michael@0 | 234 | * |
michael@0 | 235 | * @return {Promise} |
michael@0 | 236 | * @resolves After the last execution of the task is finished. |
michael@0 | 237 | * @rejects Never. |
michael@0 | 238 | */ |
michael@0 | 239 | finalize: function () { |
michael@0 | 240 | if (this._finalized) { |
michael@0 | 241 | throw new Error("The object has been already finalized."); |
michael@0 | 242 | } |
michael@0 | 243 | this._finalized = true; |
michael@0 | 244 | |
michael@0 | 245 | // If the timer is armed, it means that the task is not running but it is |
michael@0 | 246 | // scheduled for execution. Cancel the timer and run the task immediately. |
michael@0 | 247 | if (this._timer) { |
michael@0 | 248 | this.disarm(); |
michael@0 | 249 | this._timerCallback(); |
michael@0 | 250 | } |
michael@0 | 251 | |
michael@0 | 252 | // Wait for the operation to be completed, or resolve immediately. |
michael@0 | 253 | if (this._runningPromise) { |
michael@0 | 254 | return this._runningPromise; |
michael@0 | 255 | } |
michael@0 | 256 | return Promise.resolve(); |
michael@0 | 257 | }, |
michael@0 | 258 | _finalized: false, |
michael@0 | 259 | |
michael@0 | 260 | /** |
michael@0 | 261 | * Timer callback used to run the delayed task. |
michael@0 | 262 | */ |
michael@0 | 263 | _timerCallback: function () |
michael@0 | 264 | { |
michael@0 | 265 | let runningDeferred = Promise.defer(); |
michael@0 | 266 | |
michael@0 | 267 | // All these state changes must occur at the same time directly inside the |
michael@0 | 268 | // timer callback, to prevent race conditions and to ensure that all the |
michael@0 | 269 | // methods behave consistently even if called from inside the task. This |
michael@0 | 270 | // means that the assignment of "this._runningPromise" must complete before |
michael@0 | 271 | // the task gets a chance to start. |
michael@0 | 272 | this._timer = null; |
michael@0 | 273 | this._armed = false; |
michael@0 | 274 | this._runningPromise = runningDeferred.promise; |
michael@0 | 275 | |
michael@0 | 276 | runningDeferred.resolve(Task.spawn(function () { |
michael@0 | 277 | // Execute the provided function asynchronously. |
michael@0 | 278 | yield Task.spawn(this._taskFn).then(null, Cu.reportError); |
michael@0 | 279 | |
michael@0 | 280 | // Now that the task has finished, we check the state of the object to |
michael@0 | 281 | // determine if we should restart the task again. |
michael@0 | 282 | if (this._armed) { |
michael@0 | 283 | if (!this._finalized) { |
michael@0 | 284 | this._startTimer(); |
michael@0 | 285 | } else { |
michael@0 | 286 | // Execute the task again immediately, for the last time. The isArmed |
michael@0 | 287 | // property should return false while the task is running, and should |
michael@0 | 288 | // remain false after the last execution terminates. |
michael@0 | 289 | this._armed = false; |
michael@0 | 290 | yield Task.spawn(this._taskFn).then(null, Cu.reportError); |
michael@0 | 291 | } |
michael@0 | 292 | } |
michael@0 | 293 | |
michael@0 | 294 | // Indicate that the execution of the task has finished. This happens |
michael@0 | 295 | // synchronously with the previous state changes in the function. |
michael@0 | 296 | this._runningPromise = null; |
michael@0 | 297 | }.bind(this)).then(null, Cu.reportError)); |
michael@0 | 298 | }, |
michael@0 | 299 | }; |