toolkit/components/osfile/modules/osfile_async_worker.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

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 if (this.Components) {
michael@0 2 throw new Error("This worker can only be loaded from a worker thread");
michael@0 3 }
michael@0 4
michael@0 5 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 6 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 7 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 8
michael@0 9
michael@0 10 // Worker thread for osfile asynchronous front-end
michael@0 11
michael@0 12 // Exception names to be posted from the worker thread to main thread
michael@0 13 const EXCEPTION_NAMES = {
michael@0 14 EvalError: "EvalError",
michael@0 15 InternalError: "InternalError",
michael@0 16 RangeError: "RangeError",
michael@0 17 ReferenceError: "ReferenceError",
michael@0 18 SyntaxError: "SyntaxError",
michael@0 19 TypeError: "TypeError",
michael@0 20 URIError: "URIError"
michael@0 21 };
michael@0 22
michael@0 23 (function(exports) {
michael@0 24 "use strict";
michael@0 25
michael@0 26 // Timestamps, for use in Telemetry.
michael@0 27 // The object is set to |null| once it has been sent
michael@0 28 // to the main thread.
michael@0 29 let timeStamps = {
michael@0 30 entered: Date.now(),
michael@0 31 loaded: null
michael@0 32 };
michael@0 33
michael@0 34 importScripts("resource://gre/modules/osfile.jsm");
michael@0 35
michael@0 36 let SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm");
michael@0 37 let LOG = SharedAll.LOG.bind(SharedAll, "Agent");
michael@0 38
michael@0 39 // Post a message to the parent, decorate it with statistics if
michael@0 40 // necessary. Use this instead of self.postMessage.
michael@0 41 function post(message, ...transfers) {
michael@0 42 if (timeStamps) {
michael@0 43 message.timeStamps = timeStamps;
michael@0 44 timeStamps = null;
michael@0 45 }
michael@0 46 self.postMessage(message, ...transfers);
michael@0 47 }
michael@0 48
michael@0 49 /**
michael@0 50 * Communications with the controller.
michael@0 51 *
michael@0 52 * Accepts messages:
michael@0 53 * {fun:function_name, args:array_of_arguments_or_null, id:id}
michael@0 54 *
michael@0 55 * Sends messages:
michael@0 56 * {ok: result, id:id} / {fail: serialized_form_of_OS.File.Error, id:id}
michael@0 57 */
michael@0 58 self.onmessage = function onmessage(msg) {
michael@0 59 let data = msg.data;
michael@0 60 LOG("Received message", data);
michael@0 61 let id = data.id;
michael@0 62
michael@0 63 let start;
michael@0 64 let options;
michael@0 65 if (data.args) {
michael@0 66 options = data.args[data.args.length - 1];
michael@0 67 }
michael@0 68 // If |outExecutionDuration| option was supplied, start measuring the
michael@0 69 // duration of the operation.
michael@0 70 if (options && typeof options === "object" && "outExecutionDuration" in options) {
michael@0 71 start = Date.now();
michael@0 72 }
michael@0 73
michael@0 74 let result;
michael@0 75 let exn;
michael@0 76 let durationMs;
michael@0 77 try {
michael@0 78 let method = data.fun;
michael@0 79 LOG("Calling method", method);
michael@0 80 result = Agent[method].apply(Agent, data.args);
michael@0 81 LOG("Method", method, "succeeded");
michael@0 82 } catch (ex) {
michael@0 83 exn = ex;
michael@0 84 LOG("Error while calling agent method", exn, exn.moduleStack || exn.stack || "");
michael@0 85 }
michael@0 86
michael@0 87 if (start) {
michael@0 88 // Record duration
michael@0 89 durationMs = Date.now() - start;
michael@0 90 LOG("Method took", durationMs, "ms");
michael@0 91 }
michael@0 92
michael@0 93 // Now, post a reply, possibly as an uncaught error.
michael@0 94 // We post this message from outside the |try ... catch| block
michael@0 95 // to avoid capturing errors that take place during |postMessage| and
michael@0 96 // built-in serialization.
michael@0 97 if (!exn) {
michael@0 98 LOG("Sending positive reply", result, "id is", id);
michael@0 99 if (result instanceof Meta) {
michael@0 100 if ("transfers" in result.meta) {
michael@0 101 // Take advantage of zero-copy transfers
michael@0 102 post({ok: result.data, id: id, durationMs: durationMs},
michael@0 103 result.meta.transfers);
michael@0 104 } else {
michael@0 105 post({ok: result.data, id:id, durationMs: durationMs});
michael@0 106 }
michael@0 107 if (result.meta.shutdown || false) {
michael@0 108 // Time to close the worker
michael@0 109 self.close();
michael@0 110 }
michael@0 111 } else {
michael@0 112 post({ok: result, id:id, durationMs: durationMs});
michael@0 113 }
michael@0 114 } else if (exn == StopIteration) {
michael@0 115 // StopIteration cannot be serialized automatically
michael@0 116 LOG("Sending back StopIteration");
michael@0 117 post({StopIteration: true, id: id, durationMs: durationMs});
michael@0 118 } else if (exn instanceof exports.OS.File.Error) {
michael@0 119 LOG("Sending back OS.File error", exn, "id is", id);
michael@0 120 // Instances of OS.File.Error know how to serialize themselves
michael@0 121 // (deserialization ensures that we end up with OS-specific
michael@0 122 // instances of |OS.File.Error|)
michael@0 123 post({fail: exports.OS.File.Error.toMsg(exn), id:id, durationMs: durationMs});
michael@0 124 } else if (exn.constructor.name in EXCEPTION_NAMES) {
michael@0 125 LOG("Sending back exception", exn.constructor.name);
michael@0 126 post({fail: {exn: exn.constructor.name, message: exn.message,
michael@0 127 fileName: exn.moduleName || exn.fileName, lineNumber: exn.lineNumber},
michael@0 128 id: id, durationMs: durationMs});
michael@0 129 } else {
michael@0 130 // Other exceptions do not, and should be propagated through DOM's
michael@0 131 // built-in mechanism for uncaught errors, although this mechanism
michael@0 132 // may lose interesting information.
michael@0 133 LOG("Sending back regular error", exn, exn.moduleStack || exn.stack, "id is", id);
michael@0 134
michael@0 135 throw exn;
michael@0 136 }
michael@0 137 };
michael@0 138
michael@0 139 /**
michael@0 140 * A data structure used to track opened resources
michael@0 141 */
michael@0 142 let ResourceTracker = function ResourceTracker() {
michael@0 143 // A number used to generate ids
michael@0 144 this._idgen = 0;
michael@0 145 // A map from id to resource
michael@0 146 this._map = new Map();
michael@0 147 };
michael@0 148 ResourceTracker.prototype = {
michael@0 149 /**
michael@0 150 * Get a resource from its unique identifier.
michael@0 151 */
michael@0 152 get: function(id) {
michael@0 153 let result = this._map.get(id);
michael@0 154 if (result == null) {
michael@0 155 return result;
michael@0 156 }
michael@0 157 return result.resource;
michael@0 158 },
michael@0 159 /**
michael@0 160 * Remove a resource from its unique identifier.
michael@0 161 */
michael@0 162 remove: function(id) {
michael@0 163 if (!this._map.has(id)) {
michael@0 164 throw new Error("Cannot find resource id " + id);
michael@0 165 }
michael@0 166 this._map.delete(id);
michael@0 167 },
michael@0 168 /**
michael@0 169 * Add a resource, return a new unique identifier
michael@0 170 *
michael@0 171 * @param {*} resource A resource.
michael@0 172 * @param {*=} info Optional information. For debugging purposes.
michael@0 173 *
michael@0 174 * @return {*} A unique identifier. For the moment, this is a number,
michael@0 175 * but this might not remain the case forever.
michael@0 176 */
michael@0 177 add: function(resource, info) {
michael@0 178 let id = this._idgen++;
michael@0 179 this._map.set(id, {resource:resource, info:info});
michael@0 180 return id;
michael@0 181 },
michael@0 182 /**
michael@0 183 * Return a list of all open resources i.e. the ones still present in
michael@0 184 * ResourceTracker's _map.
michael@0 185 */
michael@0 186 listOpenedResources: function listOpenedResources() {
michael@0 187 return [resource.info.path for ([id, resource] of this._map)];
michael@0 188 }
michael@0 189 };
michael@0 190
michael@0 191 /**
michael@0 192 * A map of unique identifiers to opened files.
michael@0 193 */
michael@0 194 let OpenedFiles = new ResourceTracker();
michael@0 195
michael@0 196 /**
michael@0 197 * Execute a function in the context of a given file.
michael@0 198 *
michael@0 199 * @param {*} id A unique identifier, as used by |OpenFiles|.
michael@0 200 * @param {Function} f A function to call.
michael@0 201 * @param {boolean} ignoreAbsent If |true|, the error is ignored. Otherwise, the error causes an exception.
michael@0 202 * @return The return value of |f()|
michael@0 203 *
michael@0 204 * This function attempts to get the file matching |id|. If
michael@0 205 * the file exists, it executes |f| within the |this| set
michael@0 206 * to the corresponding file. Otherwise, it throws an error.
michael@0 207 */
michael@0 208 let withFile = function withFile(id, f, ignoreAbsent) {
michael@0 209 let file = OpenedFiles.get(id);
michael@0 210 if (file == null) {
michael@0 211 if (!ignoreAbsent) {
michael@0 212 throw OS.File.Error.closed("accessing file");
michael@0 213 }
michael@0 214 return undefined;
michael@0 215 }
michael@0 216 return f.call(file);
michael@0 217 };
michael@0 218
michael@0 219 let OpenedDirectoryIterators = new ResourceTracker();
michael@0 220 let withDir = function withDir(fd, f, ignoreAbsent) {
michael@0 221 let file = OpenedDirectoryIterators.get(fd);
michael@0 222 if (file == null) {
michael@0 223 if (!ignoreAbsent) {
michael@0 224 throw OS.File.Error.closed("accessing directory");
michael@0 225 }
michael@0 226 return undefined;
michael@0 227 }
michael@0 228 if (!(file instanceof File.DirectoryIterator)) {
michael@0 229 throw new Error("file is not a directory iterator " + file.__proto__.toSource());
michael@0 230 }
michael@0 231 return f.call(file);
michael@0 232 };
michael@0 233
michael@0 234 let Type = exports.OS.Shared.Type;
michael@0 235
michael@0 236 let File = exports.OS.File;
michael@0 237
michael@0 238 /**
michael@0 239 * A constructor used to return data to the caller thread while
michael@0 240 * also executing some specific treatment (e.g. shutting down
michael@0 241 * the current thread, transmitting data instead of copying it).
michael@0 242 *
michael@0 243 * @param {object=} data The data to return to the caller thread.
michael@0 244 * @param {object=} meta Additional instructions, as an object
michael@0 245 * that may contain the following fields:
michael@0 246 * - {bool} shutdown If |true|, shut down the current thread after
michael@0 247 * having sent the result.
michael@0 248 * - {Array} transfers An array of objects that should be transferred
michael@0 249 * instead of being copied.
michael@0 250 *
michael@0 251 * @constructor
michael@0 252 */
michael@0 253 let Meta = function Meta(data, meta) {
michael@0 254 this.data = data;
michael@0 255 this.meta = meta;
michael@0 256 };
michael@0 257
michael@0 258 /**
michael@0 259 * The agent.
michael@0 260 *
michael@0 261 * It is in charge of performing method-specific deserialization
michael@0 262 * of messages, calling the function/method of OS.File and serializing
michael@0 263 * back the results.
michael@0 264 */
michael@0 265 let Agent = {
michael@0 266 // Update worker's OS.Shared.DEBUG flag message from controller.
michael@0 267 SET_DEBUG: function(aDEBUG) {
michael@0 268 SharedAll.Config.DEBUG = aDEBUG;
michael@0 269 },
michael@0 270 // Return worker's current OS.Shared.DEBUG value to controller.
michael@0 271 // Note: This is used for testing purposes.
michael@0 272 GET_DEBUG: function() {
michael@0 273 return SharedAll.Config.DEBUG;
michael@0 274 },
michael@0 275 /**
michael@0 276 * Execute shutdown sequence, returning data on leaked file descriptors.
michael@0 277 *
michael@0 278 * @param {bool} If |true|, kill the worker if this would not cause
michael@0 279 * leaks.
michael@0 280 */
michael@0 281 Meta_shutdown: function(kill) {
michael@0 282 let result = {
michael@0 283 openedFiles: OpenedFiles.listOpenedResources(),
michael@0 284 openedDirectoryIterators: OpenedDirectoryIterators.listOpenedResources(),
michael@0 285 killed: false // Placeholder
michael@0 286 };
michael@0 287
michael@0 288 // Is it safe to kill the worker?
michael@0 289 let safe = result.openedFiles.length == 0
michael@0 290 && result.openedDirectoryIterators.length == 0;
michael@0 291 result.killed = safe && kill;
michael@0 292
michael@0 293 return new Meta(result, {shutdown: result.killed});
michael@0 294 },
michael@0 295 // Functions of OS.File
michael@0 296 stat: function stat(path, options) {
michael@0 297 return exports.OS.File.Info.toMsg(
michael@0 298 exports.OS.File.stat(Type.path.fromMsg(path), options));
michael@0 299 },
michael@0 300 setPermissions: function setPermissions(path, options = {}) {
michael@0 301 return exports.OS.File.setPermissions(Type.path.fromMsg(path), options);
michael@0 302 },
michael@0 303 setDates: function setDates(path, accessDate, modificationDate) {
michael@0 304 return exports.OS.File.setDates(Type.path.fromMsg(path), accessDate,
michael@0 305 modificationDate);
michael@0 306 },
michael@0 307 getCurrentDirectory: function getCurrentDirectory() {
michael@0 308 return exports.OS.Shared.Type.path.toMsg(File.getCurrentDirectory());
michael@0 309 },
michael@0 310 setCurrentDirectory: function setCurrentDirectory(path) {
michael@0 311 File.setCurrentDirectory(exports.OS.Shared.Type.path.fromMsg(path));
michael@0 312 },
michael@0 313 copy: function copy(sourcePath, destPath, options) {
michael@0 314 return File.copy(Type.path.fromMsg(sourcePath),
michael@0 315 Type.path.fromMsg(destPath), options);
michael@0 316 },
michael@0 317 move: function move(sourcePath, destPath, options) {
michael@0 318 return File.move(Type.path.fromMsg(sourcePath),
michael@0 319 Type.path.fromMsg(destPath), options);
michael@0 320 },
michael@0 321 getAvailableFreeSpace: function getAvailableFreeSpace(sourcePath) {
michael@0 322 return Type.uint64_t.toMsg(
michael@0 323 File.getAvailableFreeSpace(Type.path.fromMsg(sourcePath)));
michael@0 324 },
michael@0 325 makeDir: function makeDir(path, options) {
michael@0 326 return File.makeDir(Type.path.fromMsg(path), options);
michael@0 327 },
michael@0 328 removeEmptyDir: function removeEmptyDir(path, options) {
michael@0 329 return File.removeEmptyDir(Type.path.fromMsg(path), options);
michael@0 330 },
michael@0 331 remove: function remove(path) {
michael@0 332 return File.remove(Type.path.fromMsg(path));
michael@0 333 },
michael@0 334 open: function open(path, mode, options) {
michael@0 335 let filePath = Type.path.fromMsg(path);
michael@0 336 let file = File.open(filePath, mode, options);
michael@0 337 return OpenedFiles.add(file, {
michael@0 338 // Adding path information to keep track of opened files
michael@0 339 // to report leaks when debugging.
michael@0 340 path: filePath
michael@0 341 });
michael@0 342 },
michael@0 343 openUnique: function openUnique(path, options) {
michael@0 344 let filePath = Type.path.fromMsg(path);
michael@0 345 let openedFile = OS.Shared.AbstractFile.openUnique(filePath, options);
michael@0 346 let resourceId = OpenedFiles.add(openedFile.file, {
michael@0 347 // Adding path information to keep track of opened files
michael@0 348 // to report leaks when debugging.
michael@0 349 path: openedFile.path
michael@0 350 });
michael@0 351
michael@0 352 return {
michael@0 353 path: openedFile.path,
michael@0 354 file: resourceId
michael@0 355 };
michael@0 356 },
michael@0 357 read: function read(path, bytes, options) {
michael@0 358 let data = File.read(Type.path.fromMsg(path), bytes, options);
michael@0 359 if (typeof data == "string") {
michael@0 360 return data;
michael@0 361 }
michael@0 362 return new Meta({
michael@0 363 buffer: data.buffer,
michael@0 364 byteOffset: data.byteOffset,
michael@0 365 byteLength: data.byteLength
michael@0 366 }, {
michael@0 367 transfers: [data.buffer]
michael@0 368 });
michael@0 369 },
michael@0 370 exists: function exists(path) {
michael@0 371 return File.exists(Type.path.fromMsg(path));
michael@0 372 },
michael@0 373 writeAtomic: function writeAtomic(path, buffer, options) {
michael@0 374 if (options.tmpPath) {
michael@0 375 options.tmpPath = Type.path.fromMsg(options.tmpPath);
michael@0 376 }
michael@0 377 return File.writeAtomic(Type.path.fromMsg(path),
michael@0 378 Type.voidptr_t.fromMsg(buffer),
michael@0 379 options
michael@0 380 );
michael@0 381 },
michael@0 382 removeDir: function(path, options) {
michael@0 383 return File.removeDir(Type.path.fromMsg(path), options);
michael@0 384 },
michael@0 385 new_DirectoryIterator: function new_DirectoryIterator(path, options) {
michael@0 386 let directoryPath = Type.path.fromMsg(path);
michael@0 387 let iterator = new File.DirectoryIterator(directoryPath, options);
michael@0 388 return OpenedDirectoryIterators.add(iterator, {
michael@0 389 // Adding path information to keep track of opened directory
michael@0 390 // iterators to report leaks when debugging.
michael@0 391 path: directoryPath
michael@0 392 });
michael@0 393 },
michael@0 394 // Methods of OS.File
michael@0 395 File_prototype_close: function close(fd) {
michael@0 396 return withFile(fd,
michael@0 397 function do_close() {
michael@0 398 try {
michael@0 399 return this.close();
michael@0 400 } finally {
michael@0 401 OpenedFiles.remove(fd);
michael@0 402 }
michael@0 403 });
michael@0 404 },
michael@0 405 File_prototype_stat: function stat(fd) {
michael@0 406 return withFile(fd,
michael@0 407 function do_stat() {
michael@0 408 return exports.OS.File.Info.toMsg(this.stat());
michael@0 409 });
michael@0 410 },
michael@0 411 File_prototype_setPermissions: function setPermissions(fd, options = {}) {
michael@0 412 return withFile(fd,
michael@0 413 function do_setPermissions() {
michael@0 414 return this.setPermissions(options);
michael@0 415 });
michael@0 416 },
michael@0 417 File_prototype_setDates: function setDates(fd, accessTime, modificationTime) {
michael@0 418 return withFile(fd,
michael@0 419 function do_setDates() {
michael@0 420 return this.setDates(accessTime, modificationTime);
michael@0 421 });
michael@0 422 },
michael@0 423 File_prototype_read: function read(fd, nbytes, options) {
michael@0 424 return withFile(fd,
michael@0 425 function do_read() {
michael@0 426 let data = this.read(nbytes, options);
michael@0 427 return new Meta({
michael@0 428 buffer: data.buffer,
michael@0 429 byteOffset: data.byteOffset,
michael@0 430 byteLength: data.byteLength
michael@0 431 }, {
michael@0 432 transfers: [data.buffer]
michael@0 433 });
michael@0 434 }
michael@0 435 );
michael@0 436 },
michael@0 437 File_prototype_readTo: function readTo(fd, buffer, options) {
michael@0 438 return withFile(fd,
michael@0 439 function do_readTo() {
michael@0 440 return this.readTo(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer),
michael@0 441 options);
michael@0 442 });
michael@0 443 },
michael@0 444 File_prototype_write: function write(fd, buffer, options) {
michael@0 445 return withFile(fd,
michael@0 446 function do_write() {
michael@0 447 return this.write(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer),
michael@0 448 options);
michael@0 449 });
michael@0 450 },
michael@0 451 File_prototype_setPosition: function setPosition(fd, pos, whence) {
michael@0 452 return withFile(fd,
michael@0 453 function do_setPosition() {
michael@0 454 return this.setPosition(pos, whence);
michael@0 455 });
michael@0 456 },
michael@0 457 File_prototype_getPosition: function getPosition(fd) {
michael@0 458 return withFile(fd,
michael@0 459 function do_getPosition() {
michael@0 460 return this.getPosition();
michael@0 461 });
michael@0 462 },
michael@0 463 File_prototype_flush: function flush(fd) {
michael@0 464 return withFile(fd,
michael@0 465 function do_flush() {
michael@0 466 return this.flush();
michael@0 467 });
michael@0 468 },
michael@0 469 // Methods of OS.File.DirectoryIterator
michael@0 470 DirectoryIterator_prototype_next: function next(dir) {
michael@0 471 return withDir(dir,
michael@0 472 function do_next() {
michael@0 473 try {
michael@0 474 return File.DirectoryIterator.Entry.toMsg(this.next());
michael@0 475 } catch (x) {
michael@0 476 if (x == StopIteration) {
michael@0 477 OpenedDirectoryIterators.remove(dir);
michael@0 478 }
michael@0 479 throw x;
michael@0 480 }
michael@0 481 }, false);
michael@0 482 },
michael@0 483 DirectoryIterator_prototype_nextBatch: function nextBatch(dir, size) {
michael@0 484 return withDir(dir,
michael@0 485 function do_nextBatch() {
michael@0 486 let result;
michael@0 487 try {
michael@0 488 result = this.nextBatch(size);
michael@0 489 } catch (x) {
michael@0 490 OpenedDirectoryIterators.remove(dir);
michael@0 491 throw x;
michael@0 492 }
michael@0 493 return result.map(File.DirectoryIterator.Entry.toMsg);
michael@0 494 }, false);
michael@0 495 },
michael@0 496 DirectoryIterator_prototype_close: function close(dir) {
michael@0 497 return withDir(dir,
michael@0 498 function do_close() {
michael@0 499 this.close();
michael@0 500 OpenedDirectoryIterators.remove(dir);
michael@0 501 }, true);// ignore error to support double-closing |DirectoryIterator|
michael@0 502 },
michael@0 503 DirectoryIterator_prototype_exists: function exists(dir) {
michael@0 504 return withDir(dir,
michael@0 505 function do_exists() {
michael@0 506 return this.exists();
michael@0 507 });
michael@0 508 }
michael@0 509 };
michael@0 510 if (!SharedAll.Constants.Win) {
michael@0 511 Agent.unixSymLink = function unixSymLink(sourcePath, destPath) {
michael@0 512 return File.unixSymLink(Type.path.fromMsg(sourcePath),
michael@0 513 Type.path.fromMsg(destPath));
michael@0 514 };
michael@0 515 }
michael@0 516
michael@0 517 timeStamps.loaded = Date.now();
michael@0 518 })(this);

mercurial