Bug 812859 - A JavaScript module to mark deprecation;r=dolske
authorDavid Rajchenbach-Teller <dteller@mozilla.com>
Thu, 10 Jan 2013 20:55:42 +0100
changeset 118548 a45739b82322808e89df914442ad9b991671a4d9
parent 118547 11879a83e5e764e45342a1a04e636dfdc528b684
child 118549 7dfbcbcfad0b79a70cb8d3aa922644bbd1c891c6
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersdolske
bugs812859
milestone21.0a1
Bug 812859 - A JavaScript module to mark deprecation;r=dolske
modules/libpref/src/init/all.js
toolkit/content/Deprecated.jsm
toolkit/content/Makefile.in
toolkit/content/tests/browser/Makefile.in
toolkit/content/tests/browser/browser_Deprecated.js
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -363,16 +363,19 @@ pref("toolkit.telemetry.infoURL", "http:
 // Determines whether full SQL strings are returned when they might contain sensitive info
 // i.e. dynamically constructed SQL strings or SQL executed by addons against addon DBs
 pref("toolkit.telemetry.debugSlowSql", false);
 
 // Identity module
 pref("toolkit.identity.enabled", false);
 pref("toolkit.identity.debug", false);
 
+// Enable deprecation warnings.
+pref("devtools.errorconsole.deprecation_warnings", true);
+
 // Disable remote debugging protocol logging
 pref("devtools.debugger.log", false);
 // Disable remote debugging connections
 pref("devtools.debugger.remote-enabled", false);
 pref("devtools.debugger.remote-port", 6000);
 // Force debugger server binding on the loopback interface
 pref("devtools.debugger.force-local", true);
 
new file mode 100644
--- /dev/null
+++ b/toolkit/content/Deprecated.jsm
@@ -0,0 +1,81 @@
+/* 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [ "Deprecated" ];
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const PREF_DEPRECATION_WARNINGS = "devtools.errorconsole.deprecation_warnings";
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+// A flag that indicates whether deprecation warnings should be logged.
+let logWarnings = Services.prefs.getBoolPref(PREF_DEPRECATION_WARNINGS);
+
+Services.prefs.addObserver(PREF_DEPRECATION_WARNINGS,
+  function (aSubject, aTopic, aData) {
+    logWarnings = Services.prefs.getBoolPref(PREF_DEPRECATION_WARNINGS);
+  }, false);
+
+/**
+ * Build a callstack log message.
+ *
+ * @param nsIStackFrame aStack
+ *        A callstack to be converted into a string log message.
+ */
+function stringifyCallstack (aStack) {
+  // If aStack is invalid, use Components.stack (ignoring the last frame).
+  if (!aStack || !(aStack instanceof Ci.nsIStackFrame)) {
+    aStack = Components.stack.caller;
+  }
+
+  let frame = aStack.caller;
+  let msg = "";
+  // Get every frame in the callstack.
+  while (frame) {
+    msg += frame.filename + " " + frame.lineNumber +
+      " " + frame.name + "\n";
+    frame = frame.caller;
+  }
+  return msg;
+}
+
+const Deprecated = {
+  /**
+   * Log a deprecation warning.
+   *
+   * @param string aText
+   *        Deprecation warning text.
+   * @param string aUrl
+   *        A URL pointing to documentation describing deprecation
+   *        and the way to address it.
+   * @param nsIStackFrame aStack
+   *        An optional callstack. If it is not provided a
+   *        snapshot of the current JavaScript callstack will be
+   *        logged.
+   */
+  warning: function (aText, aUrl, aStack) {
+    if (!logWarnings) {
+      return;
+    }
+
+    // If URL is not provided, report an error.
+    if (!aUrl) {
+      Cu.reportError("Error in Deprecated.warning: warnings must " +
+        "provide a URL documenting this deprecation.");
+      return;
+    }
+
+    let textMessage = "DEPRECATION WARNING: " + aText +
+      "\nYou may find more details about this deprecation at: " +
+      aUrl + "\n" +
+      // Append a callstack part to the deprecation message.
+      stringifyCallstack(aStack);
+
+    // Report deprecation warning.
+    Cu.reportError(textMessage);
+  }
+};
\ No newline at end of file
--- a/toolkit/content/Makefile.in
+++ b/toolkit/content/Makefile.in
@@ -54,16 +54,17 @@ ifdef MOZ_TOOLKIT_SEARCH
 DEFINES += -DMOZ_TOOLKIT_SEARCH
 endif
 
 TEST_DIRS += tests
 
 EXTRA_JS_MODULES = \
   debug.js \
   DeferredTask.jsm \
+  Deprecated.jsm \
   Dict.jsm \
   Geometry.jsm \
   InlineSpellChecker.jsm \
   PageMenu.jsm \
   PopupNotifications.jsm \
   PropertyListUtils.jsm \
   Task.jsm \
   $(NULL)
--- a/toolkit/content/tests/browser/Makefile.in
+++ b/toolkit/content/tests/browser/Makefile.in
@@ -24,11 +24,12 @@ MOCHITEST_BROWSER_FILES = \
   browser_Geometry.js \
   browser_InlineSpellChecker.js \
   browser_save_resend_postdata.js \
   browser_browserDrop.js \
   browser_Services.js \
   browser_DeferredTask.js \
   browser_default_image_filename.js \
   browser_Troubleshoot.js \
+  browser_Deprecated.js \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/browser_Deprecated.js
@@ -0,0 +1,137 @@
+/* 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/. */
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const PREF_DEPRECATION_WARNINGS = "devtools.errorconsole.deprecation_warnings";
+
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/Deprecated.jsm", this);
+
+// Using this named functions to test deprecation and the properly logged
+// callstacks.
+function basicDeprecatedFunction () {
+  Deprecated.warning("this method is deprecated.", "http://example.com");
+  return true;
+}
+
+function deprecationFunctionBogusCallstack () {
+  Deprecated.warning("this method is deprecated.", "http://example.com", {
+    caller: {}
+  });
+  return true;
+}
+
+function deprecationFunctionCustomCallstack () {
+  // Get the nsIStackFrame that will contain the name of this function.
+  function getStack () {
+    return Components.stack;
+  }
+  Deprecated.warning("this method is deprecated.", "http://example.com",
+    getStack());
+  return true;
+}
+
+let tests = [
+// Test deprecation warning without passing the callstack.
+{
+  deprecatedFunction: basicDeprecatedFunction,
+  expectedObservation: function (aMessage) {
+    testAMessage(aMessage);
+    ok(aMessage.errorMessage.indexOf("basicDeprecatedFunction") > 0,
+      "Callstack is correctly logged.");
+  }
+},
+// Test a reported error when URL to documentation is not passed.
+{
+  deprecatedFunction: function () {
+    Deprecated.warning("this method is deprecated.");
+    return true;
+  },
+  expectedObservation: function (aMessage) {
+    ok(aMessage.errorMessage.indexOf("must provide a URL") > 0,
+      "Deprecation warning logged an empty URL argument.");
+  }
+},
+// Test deprecation with a bogus callstack passed as an argument (it will be
+// replaced with the current call stack).
+{
+  deprecatedFunction: deprecationFunctionBogusCallstack,
+  expectedObservation: function (aMessage) {
+    testAMessage(aMessage);
+    ok(aMessage.errorMessage.indexOf("deprecationFunctionBogusCallstack") > 0,
+      "Callstack is correctly logged.");
+  }
+},
+// When pref is unset Deprecated.warning should not log anything.
+{
+  deprecatedFunction: basicDeprecatedFunction,
+  expectedObservation: function (aMessage) {
+    // Nothing should be logged when pref is false.
+    ok(false, "Deprecated warning should not log anything when pref is unset.");
+  },
+  // Set pref to false.
+  logWarnings: false
+},
+// Test deprecation with a valid custom callstack passed as an argument.
+{
+  deprecatedFunction: deprecationFunctionCustomCallstack,
+  expectedObservation: function (aMessage) {
+    testAMessage(aMessage);
+    ok(aMessage.errorMessage.indexOf("deprecationFunctionCustomCallstack") > 0,
+      "Callstack is correctly logged.");
+    finish();
+  },
+  // Set pref to true.
+  logWarnings: true
+}];
+
+function test() {
+  waitForExplicitFinish();
+
+  // Check if Deprecated is loaded.
+  ok(Deprecated, "Deprecated object exists");
+
+  // Run all test cases.
+  tests.forEach(testDeprecated);
+}
+
+// Test Consle Message attributes.
+function testAMessage (aMessage) {
+  ok(aMessage.errorMessage.indexOf("DEPRECATION WARNING: " +
+    "this method is deprecated.") === 0,
+    "Deprecation is correctly logged.");
+  ok(aMessage.errorMessage.indexOf("http://example.com") > 0,
+    "URL is correctly logged.");
+}
+
+function testDeprecated (test) {
+  // Deprecation warnings will be logged only when the preference is set.
+  if (typeof test.logWarnings !== "undefined") {
+    Services.prefs.setBoolPref(PREF_DEPRECATION_WARNINGS, test.logWarnings);
+  }
+
+  // Create a console listener.
+  let consoleListener = {
+    observe: function (aMessage) {
+      // Ignore unexpected messages.
+      if (!(aMessage instanceof Ci.nsIScriptError)) {
+        return;
+      }
+      if (aMessage.errorMessage.indexOf("DEPRECATION WARNING: ") < 0 &&
+          aMessage.errorMessage.indexOf("must provide a URL") < 0) {
+        return;
+      }
+      ok(aMessage instanceof Ci.nsIScriptError,
+        "Deprecation log message is an instance of type nsIScriptError.");
+      test.expectedObservation(aMessage);
+    }
+  };
+  // Register a listener that contains the tests.
+  Services.console.registerListener(consoleListener);
+  // Run the deprecated function.
+  test.deprecatedFunction();
+  // Unregister a listener.
+  Services.console.unregisterListener(consoleListener);
+}
\ No newline at end of file