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: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | /** |
michael@0 | 7 | * Crash Monitor |
michael@0 | 8 | * |
michael@0 | 9 | * Monitors execution of a program to detect possible crashes. After |
michael@0 | 10 | * program termination, the monitor can be queried during the next run |
michael@0 | 11 | * to determine whether the last run exited cleanly or not. |
michael@0 | 12 | * |
michael@0 | 13 | * The monitoring is done by registering and listening for special |
michael@0 | 14 | * notifications, or checkpoints, known to be sent by the monitored |
michael@0 | 15 | * program as different stages in the execution are reached. As they |
michael@0 | 16 | * are observed, these notifications are written asynchronously to a |
michael@0 | 17 | * checkpoint file. |
michael@0 | 18 | * |
michael@0 | 19 | * During next program startup the crash monitor reads the checkpoint |
michael@0 | 20 | * file from the last session. If notifications are missing, a crash |
michael@0 | 21 | * has likely happened. By inspecting the notifications present, it is |
michael@0 | 22 | * possible to determine what stages were reached in the program |
michael@0 | 23 | * before the crash. |
michael@0 | 24 | * |
michael@0 | 25 | * Note that since the file is written asynchronously it is possible |
michael@0 | 26 | * that a received notification is lost if the program crashes right |
michael@0 | 27 | * after a checkpoint, but before crash monitor has been able to write |
michael@0 | 28 | * it to disk. Thus, while the presence of a notification in the |
michael@0 | 29 | * checkpoint file tells us that the corresponding stage was reached |
michael@0 | 30 | * during the last run, the absence of a notification after a crash |
michael@0 | 31 | * does not necessarily tell us that the checkpoint wasn't reached. |
michael@0 | 32 | */ |
michael@0 | 33 | |
michael@0 | 34 | this.EXPORTED_SYMBOLS = [ "CrashMonitor" ]; |
michael@0 | 35 | |
michael@0 | 36 | const Cu = Components.utils; |
michael@0 | 37 | const Cr = Components.results; |
michael@0 | 38 | |
michael@0 | 39 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 40 | Cu.import("resource://gre/modules/osfile.jsm"); |
michael@0 | 41 | Cu.import("resource://gre/modules/Promise.jsm"); |
michael@0 | 42 | Cu.import("resource://gre/modules/Task.jsm"); |
michael@0 | 43 | Cu.import("resource://gre/modules/AsyncShutdown.jsm"); |
michael@0 | 44 | |
michael@0 | 45 | const NOTIFICATIONS = [ |
michael@0 | 46 | "final-ui-startup", |
michael@0 | 47 | "sessionstore-windows-restored", |
michael@0 | 48 | "quit-application-granted", |
michael@0 | 49 | "quit-application", |
michael@0 | 50 | "profile-change-net-teardown", |
michael@0 | 51 | "profile-change-teardown", |
michael@0 | 52 | "profile-before-change", |
michael@0 | 53 | "sessionstore-final-state-write-complete" |
michael@0 | 54 | ]; |
michael@0 | 55 | |
michael@0 | 56 | let CrashMonitorInternal = { |
michael@0 | 57 | |
michael@0 | 58 | /** |
michael@0 | 59 | * Notifications received during the current session. |
michael@0 | 60 | * |
michael@0 | 61 | * Object where a property with a value of |true| means that the |
michael@0 | 62 | * notification of the same name has been received at least once by |
michael@0 | 63 | * the CrashMonitor during this session. Notifications that have not |
michael@0 | 64 | * yet been received are not present as properties. |NOTIFICATIONS| |
michael@0 | 65 | * lists the notifications tracked by the CrashMonitor. |
michael@0 | 66 | */ |
michael@0 | 67 | checkpoints: {}, |
michael@0 | 68 | |
michael@0 | 69 | /** |
michael@0 | 70 | * Notifications received during previous session. |
michael@0 | 71 | * |
michael@0 | 72 | * Available after |loadPreviousCheckpoints|. Promise which resolves |
michael@0 | 73 | * to an object containing a set of properties, where a property |
michael@0 | 74 | * with a value of |true| means that the notification with the same |
michael@0 | 75 | * name as the property name was received at least once last |
michael@0 | 76 | * session. |
michael@0 | 77 | */ |
michael@0 | 78 | previousCheckpoints: null, |
michael@0 | 79 | |
michael@0 | 80 | /* Deferred for AsyncShutdown blocker */ |
michael@0 | 81 | profileBeforeChangeDeferred: Promise.defer(), |
michael@0 | 82 | |
michael@0 | 83 | /** |
michael@0 | 84 | * Path to checkpoint file. |
michael@0 | 85 | * |
michael@0 | 86 | * Each time a new notification is received, this file is written to |
michael@0 | 87 | * disc to reflect the information in |checkpoints|. Although Firefox for |
michael@0 | 88 | * Desktop and Metro share the same profile, they need to keep record of |
michael@0 | 89 | * crashes separately. |
michael@0 | 90 | */ |
michael@0 | 91 | path: (Services.metro && Services.metro.immersive) ? |
michael@0 | 92 | OS.Path.join(OS.Constants.Path.profileDir, "metro", "sessionCheckpoints.json"): |
michael@0 | 93 | OS.Path.join(OS.Constants.Path.profileDir, "sessionCheckpoints.json"), |
michael@0 | 94 | |
michael@0 | 95 | /** |
michael@0 | 96 | * Load checkpoints from previous session asynchronously. |
michael@0 | 97 | * |
michael@0 | 98 | * @return {Promise} A promise that resolves/rejects once loading is complete |
michael@0 | 99 | */ |
michael@0 | 100 | loadPreviousCheckpoints: function () { |
michael@0 | 101 | this.previousCheckpoints = Task.spawn(function*() { |
michael@0 | 102 | let data; |
michael@0 | 103 | try { |
michael@0 | 104 | data = yield OS.File.read(CrashMonitorInternal.path, { encoding: "utf-8" }); |
michael@0 | 105 | } catch (ex if ex instanceof OS.File.Error) { |
michael@0 | 106 | if (!ex.becauseNoSuchFile) { |
michael@0 | 107 | Cu.reportError("Error while loading crash monitor data: " + ex.toString()); |
michael@0 | 108 | } |
michael@0 | 109 | |
michael@0 | 110 | return null; |
michael@0 | 111 | } |
michael@0 | 112 | |
michael@0 | 113 | let notifications; |
michael@0 | 114 | try { |
michael@0 | 115 | notifications = JSON.parse(data); |
michael@0 | 116 | } catch (ex) { |
michael@0 | 117 | Cu.reportError("Error while parsing crash monitor data: " + ex); |
michael@0 | 118 | return null; |
michael@0 | 119 | } |
michael@0 | 120 | |
michael@0 | 121 | return Object.freeze(notifications); |
michael@0 | 122 | }); |
michael@0 | 123 | |
michael@0 | 124 | return this.previousCheckpoints; |
michael@0 | 125 | } |
michael@0 | 126 | }; |
michael@0 | 127 | |
michael@0 | 128 | this.CrashMonitor = { |
michael@0 | 129 | |
michael@0 | 130 | /** |
michael@0 | 131 | * Notifications received during previous session. |
michael@0 | 132 | * |
michael@0 | 133 | * Return object containing the set of notifications received last |
michael@0 | 134 | * session as keys with values set to |true|. |
michael@0 | 135 | * |
michael@0 | 136 | * @return {Promise} A promise resolving to previous checkpoints |
michael@0 | 137 | */ |
michael@0 | 138 | get previousCheckpoints() { |
michael@0 | 139 | if (!CrashMonitorInternal.initialized) { |
michael@0 | 140 | throw new Error("CrashMonitor must be initialized before getting previous checkpoints"); |
michael@0 | 141 | } |
michael@0 | 142 | |
michael@0 | 143 | return CrashMonitorInternal.previousCheckpoints |
michael@0 | 144 | }, |
michael@0 | 145 | |
michael@0 | 146 | /** |
michael@0 | 147 | * Initialize CrashMonitor. |
michael@0 | 148 | * |
michael@0 | 149 | * Should only be called from the CrashMonitor XPCOM component. |
michael@0 | 150 | * |
michael@0 | 151 | * @return {Promise} |
michael@0 | 152 | */ |
michael@0 | 153 | init: function () { |
michael@0 | 154 | if (CrashMonitorInternal.initialized) { |
michael@0 | 155 | throw new Error("CrashMonitor.init() must only be called once!"); |
michael@0 | 156 | } |
michael@0 | 157 | |
michael@0 | 158 | let promise = CrashMonitorInternal.loadPreviousCheckpoints(); |
michael@0 | 159 | // Add "profile-after-change" to checkpoint as this method is |
michael@0 | 160 | // called after receiving it |
michael@0 | 161 | CrashMonitorInternal.checkpoints["profile-after-change"] = true; |
michael@0 | 162 | |
michael@0 | 163 | NOTIFICATIONS.forEach(function (aTopic) { |
michael@0 | 164 | Services.obs.addObserver(this, aTopic, false); |
michael@0 | 165 | }, this); |
michael@0 | 166 | |
michael@0 | 167 | // Add shutdown blocker for profile-before-change |
michael@0 | 168 | AsyncShutdown.profileBeforeChange.addBlocker( |
michael@0 | 169 | "CrashMonitor: Writing notifications to file after receiving profile-before-change", |
michael@0 | 170 | CrashMonitorInternal.profileBeforeChangeDeferred.promise |
michael@0 | 171 | ); |
michael@0 | 172 | |
michael@0 | 173 | CrashMonitorInternal.initialized = true; |
michael@0 | 174 | if (Services.metro && Services.metro.immersive) { |
michael@0 | 175 | OS.File.makeDir(OS.Path.join(OS.Constants.Path.profileDir, "metro")); |
michael@0 | 176 | } |
michael@0 | 177 | return promise; |
michael@0 | 178 | }, |
michael@0 | 179 | |
michael@0 | 180 | /** |
michael@0 | 181 | * Handle registered notifications. |
michael@0 | 182 | * |
michael@0 | 183 | * Update checkpoint file for every new notification received. |
michael@0 | 184 | */ |
michael@0 | 185 | observe: function (aSubject, aTopic, aData) { |
michael@0 | 186 | if (!(aTopic in CrashMonitorInternal.checkpoints)) { |
michael@0 | 187 | // If this is the first time this notification is received, |
michael@0 | 188 | // remember it and write it to file |
michael@0 | 189 | CrashMonitorInternal.checkpoints[aTopic] = true; |
michael@0 | 190 | Task.spawn(function() { |
michael@0 | 191 | try { |
michael@0 | 192 | let data = JSON.stringify(CrashMonitorInternal.checkpoints); |
michael@0 | 193 | |
michael@0 | 194 | /* Write to the checkpoint file asynchronously, off the main |
michael@0 | 195 | * thread, for performance reasons. Note that this means |
michael@0 | 196 | * that there's not a 100% guarantee that the file will be |
michael@0 | 197 | * written by the time the notification completes. The |
michael@0 | 198 | * exception is profile-before-change which has a shutdown |
michael@0 | 199 | * blocker. */ |
michael@0 | 200 | yield OS.File.writeAtomic( |
michael@0 | 201 | CrashMonitorInternal.path, |
michael@0 | 202 | data, {tmpPath: CrashMonitorInternal.path + ".tmp"}); |
michael@0 | 203 | |
michael@0 | 204 | } finally { |
michael@0 | 205 | // Resolve promise for blocker |
michael@0 | 206 | if (aTopic == "profile-before-change") { |
michael@0 | 207 | CrashMonitorInternal.profileBeforeChangeDeferred.resolve(); |
michael@0 | 208 | } |
michael@0 | 209 | } |
michael@0 | 210 | }); |
michael@0 | 211 | } |
michael@0 | 212 | |
michael@0 | 213 | if (NOTIFICATIONS.every(elem => elem in CrashMonitorInternal.checkpoints)) { |
michael@0 | 214 | // All notifications received, unregister observers |
michael@0 | 215 | NOTIFICATIONS.forEach(function (aTopic) { |
michael@0 | 216 | Services.obs.removeObserver(this, aTopic); |
michael@0 | 217 | }, this); |
michael@0 | 218 | } |
michael@0 | 219 | } |
michael@0 | 220 | }; |
michael@0 | 221 | Object.freeze(this.CrashMonitor); |