browser/components/sessionstore/src/SessionWorker.js

Wed, 31 Dec 2014 07:53:36 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:53:36 +0100
branch
TOR_BUG_3246
changeset 5
4ab42b5ab56c
permissions
-rw-r--r--

Correct small whitespace inconsistency, lost while renaming variables.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 /**
     6  * A worker dedicated to handle I/O for Session Store.
     7  */
     9 "use strict";
    11 importScripts("resource://gre/modules/osfile.jsm");
    13 let File = OS.File;
    14 let Encoder = new TextEncoder();
    15 let Decoder = new TextDecoder();
    17 /**
    18  * Communications with the controller.
    19  *
    20  * Accepts messages:
    21  * {fun:function_name, args:array_of_arguments_or_null, id: custom_id}
    22  *
    23  * Sends messages:
    24  * {ok: result, id: custom_id, telemetry: {}} /
    25  * {fail: serialized_form_of_OS.File.Error, id: custom_id}
    26  */
    27 self.onmessage = function (msg) {
    28   let data = msg.data;
    29   if (!(data.fun in Agent)) {
    30     throw new Error("Cannot find method " + data.fun);
    31   }
    33   let result;
    34   let id = data.id;
    36   try {
    37     result = Agent[data.fun].apply(Agent, data.args) || {};
    38   } catch (ex if ex instanceof OS.File.Error) {
    39     // Instances of OS.File.Error know how to serialize themselves
    40     // (deserialization ensures that we end up with OS-specific
    41     // instances of |OS.File.Error|)
    42     self.postMessage({fail: OS.File.Error.toMsg(ex), id: id});
    43     return;
    44   }
    46   // Other exceptions do not, and should be propagated through DOM's
    47   // built-in mechanism for uncaught errors, although this mechanism
    48   // may lose interesting information.
    49   self.postMessage({
    50     ok: result.result,
    51     id: id,
    52     telemetry: result.telemetry || {}
    53   });
    54 };
    56 let Agent = {
    57   // Boolean that tells whether we already made a
    58   // call to write(). We will only attempt to move
    59   // sessionstore.js to sessionstore.bak on the
    60   // first write.
    61   hasWrittenState: false,
    63   // The path to sessionstore.js
    64   path: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"),
    66   // The path to sessionstore.bak
    67   backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"),
    69   /**
    70    * NO-OP to start the worker.
    71    */
    72   init: function () {
    73     return {result: true};
    74   },
    76   /**
    77    * Write the session to disk.
    78    */
    79   write: function (stateString) {
    80     let exn;
    81     let telemetry = {};
    83     if (!this.hasWrittenState) {
    84       try {
    85         let startMs = Date.now();
    86         File.move(this.path, this.backupPath);
    87         telemetry.FX_SESSION_RESTORE_BACKUP_FILE_MS = Date.now() - startMs;
    88       } catch (ex if isNoSuchFileEx(ex)) {
    89         // Ignore exceptions about non-existent files.
    90       } catch (ex) {
    91         // Throw the exception after we wrote the state to disk
    92         // so that the backup can't interfere with the actual write.
    93         exn = ex;
    94       }
    96       this.hasWrittenState = true;
    97     }
    99     let ret = this._write(stateString, telemetry);
   101     if (exn) {
   102       throw exn;
   103     }
   105     return ret;
   106   },
   108   /**
   109    * Extract all sorts of useful statistics from a state string,
   110    * for use with Telemetry.
   111    *
   112    * @return {object}
   113    */
   114   gatherTelemetry: function (stateString) {
   115     return Statistics.collect(stateString);
   116   },
   118   /**
   119    * Write a stateString to disk
   120    */
   121   _write: function (stateString, telemetry = {}) {
   122     let bytes = Encoder.encode(stateString);
   123     let startMs = Date.now();
   124     let result = File.writeAtomic(this.path, bytes, {tmpPath: this.path + ".tmp"});
   125     telemetry.FX_SESSION_RESTORE_WRITE_FILE_MS = Date.now() - startMs;
   126     telemetry.FX_SESSION_RESTORE_FILE_SIZE_BYTES = bytes.byteLength;
   127     return {result: result, telemetry: telemetry};
   128   },
   130   /**
   131    * Creates a copy of sessionstore.js.
   132    */
   133   createBackupCopy: function (ext) {
   134     try {
   135       return {result: File.copy(this.path, this.backupPath + ext)};
   136     } catch (ex if isNoSuchFileEx(ex)) {
   137       // Ignore exceptions about non-existent files.
   138       return {result: true};
   139     }
   140   },
   142   /**
   143    * Removes a backup copy.
   144    */
   145   removeBackupCopy: function (ext) {
   146     try {
   147       return {result: File.remove(this.backupPath + ext)};
   148     } catch (ex if isNoSuchFileEx(ex)) {
   149       // Ignore exceptions about non-existent files.
   150       return {result: true};
   151     }
   152   },
   154   /**
   155    * Wipes all files holding session data from disk.
   156    */
   157   wipe: function () {
   158     let exn;
   160     // Erase session state file
   161     try {
   162       File.remove(this.path);
   163     } catch (ex if isNoSuchFileEx(ex)) {
   164       // Ignore exceptions about non-existent files.
   165     } catch (ex) {
   166       // Don't stop immediately.
   167       exn = ex;
   168     }
   170     // Erase any backup, any file named "sessionstore.bak[-buildID]".
   171     let iter = new File.DirectoryIterator(OS.Constants.Path.profileDir);
   172     for (let entry in iter) {
   173       if (!entry.isDir && entry.path.startsWith(this.backupPath)) {
   174         try {
   175           File.remove(entry.path);
   176         } catch (ex) {
   177           // Don't stop immediately.
   178           exn = exn || ex;
   179         }
   180       }
   181     }
   183     if (exn) {
   184       throw exn;
   185     }
   187     return {result: true};
   188   }
   189 };
   191 function isNoSuchFileEx(aReason) {
   192   return aReason instanceof OS.File.Error && aReason.becauseNoSuchFile;
   193 }
   195 /**
   196  * Estimate the number of bytes that a data structure will use on disk
   197  * once serialized.
   198  */
   199 function getByteLength(str) {
   200   return Encoder.encode(JSON.stringify(str)).byteLength;
   201 }
   203 /**
   204  * Tools for gathering statistics on a state string.
   205  */
   206 let Statistics = {
   207   collect: function(stateString) {
   208     let start = Date.now();
   209     let TOTAL_PREFIX = "FX_SESSION_RESTORE_TOTAL_";
   210     let INDIVIDUAL_PREFIX = "FX_SESSION_RESTORE_INDIVIDUAL_";
   211     let SIZE_SUFFIX = "_SIZE_BYTES";
   213     let state = JSON.parse(stateString);
   215     // Gather all data
   216     let subsets = {};
   217     this.gatherSimpleData(state, subsets);
   218     this.gatherComplexData(state, subsets);
   220     // Extract telemetry
   221     let telemetry = {};
   222     for (let k of Object.keys(subsets)) {
   223       let obj = subsets[k];
   224       telemetry[TOTAL_PREFIX + k + SIZE_SUFFIX] = getByteLength(obj);
   226       if (Array.isArray(obj)) {
   227         let size = obj.map(getByteLength);
   228         telemetry[INDIVIDUAL_PREFIX + k + SIZE_SUFFIX] = size;
   229       }
   230     }
   232     let stop = Date.now();
   233     telemetry["FX_SESSION_RESTORE_EXTRACTING_STATISTICS_DURATION_MS"] = stop - start;
   234     return {
   235       telemetry: telemetry
   236     };
   237   },
   239   /**
   240    * Collect data that doesn't require a recursive walk through the
   241    * data structure.
   242    */
   243   gatherSimpleData: function(state, subsets) {
   244     // The subset of sessionstore.js dealing with open windows
   245     subsets.OPEN_WINDOWS = state.windows;
   247     // The subset of sessionstore.js dealing with closed windows
   248     subsets.CLOSED_WINDOWS = state._closedWindows;
   250     // The subset of sessionstore.js dealing with closed tabs
   251     // in open windows
   252     subsets.CLOSED_TABS_IN_OPEN_WINDOWS = [];
   254     // The subset of sessionstore.js dealing with cookies
   255     // in both open and closed windows
   256     subsets.COOKIES = [];
   258     for (let winData of state.windows) {
   259       let closedTabs = winData._closedTabs || [];
   260       subsets.CLOSED_TABS_IN_OPEN_WINDOWS.push(...closedTabs);
   262       let cookies = winData.cookies || [];
   263       subsets.COOKIES.push(...cookies);
   264     }
   266     for (let winData of state._closedWindows) {
   267       let cookies = winData.cookies || [];
   268       subsets.COOKIES.push(...cookies);
   269     }
   270   },
   272   /**
   273    * Walk through a data structure, recursively.
   274    *
   275    * @param {object} root The object from which to start walking.
   276    * @param {function(key, value)} cb Callback, called for each
   277    * item except the root. Returns |true| to walk the subtree rooted
   278    * at |value|, |false| otherwise   */
   279   walk: function(root, cb) {
   280     if (!root || typeof root !== "object") {
   281       return;
   282     }
   283     for (let k of Object.keys(root)) {
   284       let obj = root[k];
   285       let stepIn = cb(k, obj);
   286       if (stepIn) {
   287         this.walk(obj, cb);
   288       }
   289     }
   290   },
   292   /**
   293    * Collect data that requires walking through the data structure
   294    */
   295   gatherComplexData: function(state, subsets) {
   296     // The subset of sessionstore.js dealing with DOM storage
   297     subsets.DOM_STORAGE = [];
   298     // The subset of sessionstore.js storing form data
   299     subsets.FORMDATA = [];
   300     // The subset of sessionstore.js storing history
   301     subsets.HISTORY = [];
   304     this.walk(state, function(k, value) {
   305       let dest;
   306       switch (k) {
   307         case "entries":
   308           subsets.HISTORY.push(value);
   309           return true;
   310         case "storage":
   311           subsets.DOM_STORAGE.push(value);
   312           // Never visit storage, it's full of weird stuff
   313           return false;
   314         case "formdata":
   315           subsets.FORMDATA.push(value);
   316           // Never visit formdata, it's full of weird stuff
   317           return false;
   318         case "cookies": // Don't visit these places, they are full of weird stuff
   319         case "extData":
   320           return false;
   321         default:
   322           return true;
   323       }
   324     });
   326     return subsets;
   327   },
   329 };

mercurial