summaryrefslogtreecommitdiffstats
path: root/toolkit/components/crashmonitor
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /toolkit/components/crashmonitor
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'toolkit/components/crashmonitor')
-rw-r--r--toolkit/components/crashmonitor/CrashMonitor.jsm224
-rw-r--r--toolkit/components/crashmonitor/crashmonitor.manifest3
-rw-r--r--toolkit/components/crashmonitor/moz.build16
-rw-r--r--toolkit/components/crashmonitor/nsCrashMonitor.js29
-rw-r--r--toolkit/components/crashmonitor/test/unit/.eslintrc.js7
-rw-r--r--toolkit/components/crashmonitor/test/unit/head.js22
-rw-r--r--toolkit/components/crashmonitor/test/unit/test_init.js17
-rw-r--r--toolkit/components/crashmonitor/test/unit/test_invalid_file.js22
-rw-r--r--toolkit/components/crashmonitor/test/unit/test_invalid_json.js18
-rw-r--r--toolkit/components/crashmonitor/test/unit/test_missing_file.js13
-rw-r--r--toolkit/components/crashmonitor/test/unit/test_register.js24
-rw-r--r--toolkit/components/crashmonitor/test/unit/test_valid_file.js20
-rw-r--r--toolkit/components/crashmonitor/test/unit/xpcshell.ini11
13 files changed, 426 insertions, 0 deletions
diff --git a/toolkit/components/crashmonitor/CrashMonitor.jsm b/toolkit/components/crashmonitor/CrashMonitor.jsm
new file mode 100644
index 000000000..34f4f26d1
--- /dev/null
+++ b/toolkit/components/crashmonitor/CrashMonitor.jsm
@@ -0,0 +1,224 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Crash Monitor
+ *
+ * Monitors execution of a program to detect possible crashes. After
+ * program termination, the monitor can be queried during the next run
+ * to determine whether the last run exited cleanly or not.
+ *
+ * The monitoring is done by registering and listening for special
+ * notifications, or checkpoints, known to be sent by the monitored
+ * program as different stages in the execution are reached. As they
+ * are observed, these notifications are written asynchronously to a
+ * checkpoint file.
+ *
+ * During next program startup the crash monitor reads the checkpoint
+ * file from the last session. If notifications are missing, a crash
+ * has likely happened. By inspecting the notifications present, it is
+ * possible to determine what stages were reached in the program
+ * before the crash.
+ *
+ * Note that since the file is written asynchronously it is possible
+ * that a received notification is lost if the program crashes right
+ * after a checkpoint, but before crash monitor has been able to write
+ * it to disk. Thus, while the presence of a notification in the
+ * checkpoint file tells us that the corresponding stage was reached
+ * during the last run, the absence of a notification after a crash
+ * does not necessarily tell us that the checkpoint wasn't reached.
+ */
+
+this.EXPORTED_SYMBOLS = [ "CrashMonitor" ];
+
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/AsyncShutdown.jsm");
+
+const NOTIFICATIONS = [
+ "final-ui-startup",
+ "sessionstore-windows-restored",
+ "quit-application-granted",
+ "quit-application",
+ "profile-change-net-teardown",
+ "profile-change-teardown",
+ "profile-before-change",
+ "sessionstore-final-state-write-complete"
+];
+
+var CrashMonitorInternal = {
+
+ /**
+ * Notifications received during the current session.
+ *
+ * Object where a property with a value of |true| means that the
+ * notification of the same name has been received at least once by
+ * the CrashMonitor during this session. Notifications that have not
+ * yet been received are not present as properties. |NOTIFICATIONS|
+ * lists the notifications tracked by the CrashMonitor.
+ */
+ checkpoints: {},
+
+ /**
+ * Notifications received during previous session.
+ *
+ * Available after |loadPreviousCheckpoints|. Promise which resolves
+ * to an object containing a set of properties, where a property
+ * with a value of |true| means that the notification with the same
+ * name as the property name was received at least once last
+ * session.
+ */
+ previousCheckpoints: null,
+
+ /* Deferred for AsyncShutdown blocker */
+ profileBeforeChangeDeferred: Promise.defer(),
+
+ /**
+ * Path to checkpoint file.
+ *
+ * Each time a new notification is received, this file is written to
+ * disc to reflect the information in |checkpoints|.
+ */
+ path: OS.Path.join(OS.Constants.Path.profileDir, "sessionCheckpoints.json"),
+
+ /**
+ * Load checkpoints from previous session asynchronously.
+ *
+ * @return {Promise} A promise that resolves/rejects once loading is complete
+ */
+ loadPreviousCheckpoints: function () {
+ this.previousCheckpoints = Task.spawn(function*() {
+ let data;
+ try {
+ data = yield OS.File.read(CrashMonitorInternal.path, { encoding: "utf-8" });
+ } catch (ex) {
+ if (!(ex instanceof OS.File.Error)) {
+ throw ex;
+ }
+ if (!ex.becauseNoSuchFile) {
+ Cu.reportError("Error while loading crash monitor data: " + ex.toString());
+ }
+
+ return null;
+ }
+
+ let notifications;
+ try {
+ notifications = JSON.parse(data);
+ } catch (ex) {
+ Cu.reportError("Error while parsing crash monitor data: " + ex);
+ return null;
+ }
+
+ // If `notifications` isn't an object, then the monitor data isn't valid.
+ if (Object(notifications) !== notifications) {
+ Cu.reportError("Error while parsing crash monitor data: invalid monitor data");
+ return null;
+ }
+
+ return Object.freeze(notifications);
+ });
+
+ return this.previousCheckpoints;
+ }
+};
+
+this.CrashMonitor = {
+
+ /**
+ * Notifications received during previous session.
+ *
+ * Return object containing the set of notifications received last
+ * session as keys with values set to |true|.
+ *
+ * @return {Promise} A promise resolving to previous checkpoints
+ */
+ get previousCheckpoints() {
+ if (!CrashMonitorInternal.initialized) {
+ throw new Error("CrashMonitor must be initialized before getting previous checkpoints");
+ }
+
+ return CrashMonitorInternal.previousCheckpoints
+ },
+
+ /**
+ * Initialize CrashMonitor.
+ *
+ * Should only be called from the CrashMonitor XPCOM component.
+ *
+ * @return {Promise}
+ */
+ init: function () {
+ if (CrashMonitorInternal.initialized) {
+ throw new Error("CrashMonitor.init() must only be called once!");
+ }
+
+ let promise = CrashMonitorInternal.loadPreviousCheckpoints();
+ // Add "profile-after-change" to checkpoint as this method is
+ // called after receiving it
+ CrashMonitorInternal.checkpoints["profile-after-change"] = true;
+
+ NOTIFICATIONS.forEach(function (aTopic) {
+ Services.obs.addObserver(this, aTopic, false);
+ }, this);
+
+ // Add shutdown blocker for profile-before-change
+ OS.File.profileBeforeChange.addBlocker(
+ "CrashMonitor: Writing notifications to file after receiving profile-before-change",
+ CrashMonitorInternal.profileBeforeChangeDeferred.promise,
+ () => this.checkpoints
+ );
+
+ CrashMonitorInternal.initialized = true;
+ return promise;
+ },
+
+ /**
+ * Handle registered notifications.
+ *
+ * Update checkpoint file for every new notification received.
+ */
+ observe: function (aSubject, aTopic, aData) {
+ if (!(aTopic in CrashMonitorInternal.checkpoints)) {
+ // If this is the first time this notification is received,
+ // remember it and write it to file
+ CrashMonitorInternal.checkpoints[aTopic] = true;
+ Task.spawn(function* () {
+ try {
+ let data = JSON.stringify(CrashMonitorInternal.checkpoints);
+
+ /* Write to the checkpoint file asynchronously, off the main
+ * thread, for performance reasons. Note that this means
+ * that there's not a 100% guarantee that the file will be
+ * written by the time the notification completes. The
+ * exception is profile-before-change which has a shutdown
+ * blocker. */
+ yield OS.File.writeAtomic(
+ CrashMonitorInternal.path,
+ data, {tmpPath: CrashMonitorInternal.path + ".tmp"});
+
+ } finally {
+ // Resolve promise for blocker
+ if (aTopic == "profile-before-change") {
+ CrashMonitorInternal.profileBeforeChangeDeferred.resolve();
+ }
+ }
+ });
+ }
+
+ if (NOTIFICATIONS.every(elem => elem in CrashMonitorInternal.checkpoints)) {
+ // All notifications received, unregister observers
+ NOTIFICATIONS.forEach(function (aTopic) {
+ Services.obs.removeObserver(this, aTopic);
+ }, this);
+ }
+ }
+};
+Object.freeze(this.CrashMonitor);
diff --git a/toolkit/components/crashmonitor/crashmonitor.manifest b/toolkit/components/crashmonitor/crashmonitor.manifest
new file mode 100644
index 000000000..59e336f82
--- /dev/null
+++ b/toolkit/components/crashmonitor/crashmonitor.manifest
@@ -0,0 +1,3 @@
+component {d9d75e86-8f17-4c57-993e-f738f0d86d42} nsCrashMonitor.js
+contract @mozilla.org/toolkit/crashmonitor;1 {d9d75e86-8f17-4c57-993e-f738f0d86d42}
+category profile-after-change CrashMonitor @mozilla.org/toolkit/crashmonitor;1
diff --git a/toolkit/components/crashmonitor/moz.build b/toolkit/components/crashmonitor/moz.build
new file mode 100644
index 000000000..4656f6ab8
--- /dev/null
+++ b/toolkit/components/crashmonitor/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+
+EXTRA_JS_MODULES += [
+ 'CrashMonitor.jsm',
+]
+
+EXTRA_COMPONENTS += [
+ 'crashmonitor.manifest',
+ 'nsCrashMonitor.js',
+]
diff --git a/toolkit/components/crashmonitor/nsCrashMonitor.js b/toolkit/components/crashmonitor/nsCrashMonitor.js
new file mode 100644
index 000000000..41e0dc901
--- /dev/null
+++ b/toolkit/components/crashmonitor/nsCrashMonitor.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var Scope = {}
+Components.utils.import("resource://gre/modules/CrashMonitor.jsm", Scope);
+var MonitorAPI = Scope.CrashMonitor;
+
+function CrashMonitor() {}
+
+CrashMonitor.prototype = {
+
+ classID: Components.ID("{d9d75e86-8f17-4c57-993e-f738f0d86d42}"),
+ contractID: "@mozilla.org/toolkit/crashmonitor;1",
+
+ QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver]),
+
+ observe: function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "profile-after-change":
+ MonitorAPI.init();
+ }
+ },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([CrashMonitor]);
diff --git a/toolkit/components/crashmonitor/test/unit/.eslintrc.js b/toolkit/components/crashmonitor/test/unit/.eslintrc.js
new file mode 100644
index 000000000..d35787cd2
--- /dev/null
+++ b/toolkit/components/crashmonitor/test/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/toolkit/components/crashmonitor/test/unit/head.js b/toolkit/components/crashmonitor/test/unit/head.js
new file mode 100644
index 000000000..6d7d50d0c
--- /dev/null
+++ b/toolkit/components/crashmonitor/test/unit/head.js
@@ -0,0 +1,22 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+
+var sessionCheckpointsPath;
+
+/**
+ * Start the tasks of the different tests
+ */
+function run_test()
+{
+ do_get_profile();
+ sessionCheckpointsPath = OS.Path.join(OS.Constants.Path.profileDir,
+ "sessionCheckpoints.json");
+ Components.utils.import("resource://gre/modules/CrashMonitor.jsm");
+ run_next_test();
+}
diff --git a/toolkit/components/crashmonitor/test/unit/test_init.js b/toolkit/components/crashmonitor/test/unit/test_init.js
new file mode 100644
index 000000000..d72f46aca
--- /dev/null
+++ b/toolkit/components/crashmonitor/test/unit/test_init.js
@@ -0,0 +1,17 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test that calling |init| twice throws an error
+ */
+add_task(function test_init() {
+ CrashMonitor.init();
+ try {
+ CrashMonitor.init();
+ do_check_true(false);
+ } catch (ex) {
+ do_check_true(true);
+ }
+});
diff --git a/toolkit/components/crashmonitor/test/unit/test_invalid_file.js b/toolkit/components/crashmonitor/test/unit/test_invalid_file.js
new file mode 100644
index 000000000..cc55a2755
--- /dev/null
+++ b/toolkit/components/crashmonitor/test/unit/test_invalid_file.js
@@ -0,0 +1,22 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test with sessionCheckpoints.json containing invalid data
+ */
+add_task(function* test_invalid_file() {
+ // Write bogus data to checkpoint file
+ let data = "1234";
+ yield OS.File.writeAtomic(sessionCheckpointsPath, data,
+ {tmpPath: sessionCheckpointsPath + ".tmp"});
+
+ // An invalid file will cause |init| to return null
+ let status = yield CrashMonitor.init();
+ do_check_true(status === null ? true : false);
+
+ // and |previousCheckpoints| will be null
+ let checkpoints = yield CrashMonitor.previousCheckpoints;
+ do_check_true(checkpoints === null ? true : false);
+});
diff --git a/toolkit/components/crashmonitor/test/unit/test_invalid_json.js b/toolkit/components/crashmonitor/test/unit/test_invalid_json.js
new file mode 100644
index 000000000..f3b05208a
--- /dev/null
+++ b/toolkit/components/crashmonitor/test/unit/test_invalid_json.js
@@ -0,0 +1,18 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test with sessionCheckpoints.json containing invalid JSON data
+ */
+add_task(function* test_invalid_file() {
+ // Write bogus data to checkpoint file
+ let data = "[}";
+ yield OS.File.writeAtomic(sessionCheckpointsPath, data,
+ {tmpPath: sessionCheckpointsPath + ".tmp"});
+
+ CrashMonitor.init();
+ let checkpoints = yield CrashMonitor.previousCheckpoints;
+ do_check_eq(checkpoints, null);
+});
diff --git a/toolkit/components/crashmonitor/test/unit/test_missing_file.js b/toolkit/components/crashmonitor/test/unit/test_missing_file.js
new file mode 100644
index 000000000..9ce31da95
--- /dev/null
+++ b/toolkit/components/crashmonitor/test/unit/test_missing_file.js
@@ -0,0 +1,13 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test with non-existing sessionCheckpoints.json
+ */
+add_task(function* test_missing_file() {
+ CrashMonitor.init();
+ let checkpoints = yield CrashMonitor.previousCheckpoints;
+ do_check_eq(checkpoints, null);
+});
diff --git a/toolkit/components/crashmonitor/test/unit/test_register.js b/toolkit/components/crashmonitor/test/unit/test_register.js
new file mode 100644
index 000000000..33c73a5ae
--- /dev/null
+++ b/toolkit/components/crashmonitor/test/unit/test_register.js
@@ -0,0 +1,24 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test that CrashMonitor.jsm is correctly loaded from XPCOM component
+ */
+add_task(function test_register() {
+ let cm = Components.classes["@mozilla.org/toolkit/crashmonitor;1"]
+ .createInstance(Components.interfaces.nsIObserver);
+
+ // Send "profile-after-change" to trigger the initialization
+ cm.observe(null, "profile-after-change", null);
+
+ // If CrashMonitor was initialized properly a new call to |init|
+ // should fail
+ try {
+ CrashMonitor.init();
+ do_check_true(false);
+ } catch (ex) {
+ do_check_true(true);
+ }
+});
diff --git a/toolkit/components/crashmonitor/test/unit/test_valid_file.js b/toolkit/components/crashmonitor/test/unit/test_valid_file.js
new file mode 100644
index 000000000..d2f214cc0
--- /dev/null
+++ b/toolkit/components/crashmonitor/test/unit/test_valid_file.js
@@ -0,0 +1,20 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test with sessionCheckpoints.json containing valid data
+ */
+add_task(function* test_valid_file() {
+ // Write valid data to checkpoint file
+ let data = JSON.stringify({"final-ui-startup": true});
+ yield OS.File.writeAtomic(sessionCheckpointsPath, data,
+ {tmpPath: sessionCheckpointsPath + ".tmp"});
+
+ CrashMonitor.init();
+ let checkpoints = yield CrashMonitor.previousCheckpoints;
+
+ do_check_true(checkpoints["final-ui-startup"]);
+ do_check_eq(Object.keys(checkpoints).length, 1);
+});
diff --git a/toolkit/components/crashmonitor/test/unit/xpcshell.ini b/toolkit/components/crashmonitor/test/unit/xpcshell.ini
new file mode 100644
index 000000000..cd86b2535
--- /dev/null
+++ b/toolkit/components/crashmonitor/test/unit/xpcshell.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+head = head.js
+tail =
+skip-if = toolkit == 'android'
+
+[test_init.js]
+[test_valid_file.js]
+[test_invalid_file.js]
+[test_invalid_json.js]
+[test_missing_file.js]
+[test_register.js]