Bug 1259563 - Display links to error message documentation alongside relevant errors (2/2); r=bgrins
authorMorgan Phillips <winter2718@gmail.com>
Mon, 11 Apr 2016 10:27:50 -0700
changeset 330544 8defde6e5e29c7eb48d0011206d3ff24db88ecf2
parent 330543 05ce0b155439161c3d453ec1b793a87c57e1d11c
child 330545 0cd33f35bcb4feea6fcdfbc0f6f09912e2ad106c
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgrins
bugs1259563
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1259563 - Display links to error message documentation alongside relevant errors (2/2); r=bgrins
devtools/client/themes/webconsole.css
devtools/client/webconsole/console-output.js
devtools/client/webconsole/jsterm.js
devtools/client/webconsole/test/browser_webconsole_jsterm.js
devtools/server/actors/webconsole.js
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -505,16 +505,20 @@ a {
   text-decoration: none;
 }
 
 .cm-s-mozilla a[class]:hover,
 .cm-s-mozilla a[class]:focus {
   text-decoration: underline;
 }
 
+a.learn-more-link.webconsole-learn-more-link {
+    font-style: normal;
+}
+
 /* Open DOMNode in inspector button */
 .open-inspector {
   background: url("chrome://devtools/skin/images/vview-open-inspector.png") no-repeat 0 0;
   padding-left: 16px;
   margin-left: 5px;
   cursor: pointer;
 }
 
--- a/devtools/client/webconsole/console-output.js
+++ b/devtools/client/webconsole/console-output.js
@@ -1,10 +1,10 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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";
 
 const {Cc, Ci, Cu} = require("chrome");
 
@@ -1336,18 +1336,20 @@ Messages.Extended.prototype = Heritage.e
  * The JavaScriptEvalOutput message.
  *
  * @constructor
  * @extends Messages.Extended
  * @param object evalResponse
  *        The evaluation response packet received from the server.
  * @param string [errorMessage]
  *        Optional error message to display.
+ * @param string [errorDocLink]
+ * Optional error doc URL to link to.
  */
-Messages.JavaScriptEvalOutput = function(evalResponse, errorMessage)
+Messages.JavaScriptEvalOutput = function(evalResponse, errorMessage, errorDocLink)
 {
   let severity = "log", msg, quoteStrings = true;
 
   // Store also the response packet from the back end. It might
   // be useful to extensions customizing the console output.
   this.response = evalResponse;
 
   if (typeof(errorMessage) !== "undefined") {
@@ -1360,17 +1362,23 @@ Messages.JavaScriptEvalOutput = function
 
   let options = {
     className: "cm-s-mozilla",
     timestamp: evalResponse.timestamp,
     category: "output",
     severity: severity,
     quoteStrings: quoteStrings,
   };
-  Messages.Extended.call(this, [msg], options);
+
+  let messages = [msg];
+  if (errorDocLink) {
+    messages.push(errorDocLink);
+  }
+
+  Messages.Extended.call(this, messages, options);
 };
 
 Messages.JavaScriptEvalOutput.prototype = Messages.Extended.prototype;
 
 /**
  * The ConsoleGeneric message is used for console API calls.
  *
  * @constructor
--- a/devtools/client/webconsole/jsterm.js
+++ b/devtools/client/webconsole/jsterm.js
@@ -1,10 +1,10 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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";
 
 const {Cc, Ci, Cu} = require("chrome");
 
@@ -303,16 +303,32 @@ JSTerm.prototype = {
       return;
     }
     if (response.error) {
       Cu.reportError("Evaluation error " + response.error + ": " +
                      response.message);
       return;
     }
     let errorMessage = response.exceptionMessage;
+    let errorDocURL = response.exceptionDocURL;
+
+    let errorDocLink;
+    if (errorDocURL) {
+      errorMessage += " ";
+      errorDocLink = this.hud.document.createElementNS(XHTML_NS, "a");
+      errorDocLink.className = "learn-more-link webconsole-learn-more-link";
+      errorDocLink.textContent = "[" + l10n.getStr("webConsoleMoreInfoLabel") + "]";
+      errorDocLink.title = errorDocURL;
+      errorDocLink.href = "#";
+      errorDocLink.draggable = false;
+      errorDocLink.addEventListener("click", () => {
+        this.hud.owner.openLink(errorDocURL);
+      });
+    }
+
     // Wrap thrown strings in Error objects, so `throw "foo"` outputs
     // "Error: foo"
     if (typeof(response.exception) === "string") {
       errorMessage = new Error(errorMessage).toString();
     }
     let result = response.result;
     let helperResult = response.helperResult;
     let helperHasRawOutput = !!(helperResult || {}).rawOutput;
@@ -351,17 +367,17 @@ JSTerm.prototype = {
     // Hide undefined results coming from JSTerm helper functions.
     if (!errorMessage && result && typeof result == "object" &&
         result.type == "undefined" &&
         helperResult && !helperHasRawOutput) {
       callback && callback();
       return;
     }
 
-    let msg = new Messages.JavaScriptEvalOutput(response, errorMessage);
+    let msg = new Messages.JavaScriptEvalOutput(response, errorMessage, errorDocLink);
     this.hud.output.addMessage(msg);
 
     if (callback) {
       let oldFlushCallback = this.hud._flushCallback;
       this.hud._flushCallback = () => {
         callback(msg.element);
         if (oldFlushCallback) {
           oldFlushCallback();
--- a/devtools/client/webconsole/test/browser_webconsole_jsterm.js
+++ b/devtools/client/webconsole/test/browser_webconsole_jsterm.js
@@ -161,9 +161,31 @@ function* testJSTerm(hud) {
   }, "thrown non-empty string generates error message");
 
   jsterm.clearOutput();
   yield jsterm.execute("throw { foo: 'bar' };");
   yield checkResult((node) => {
     return node.parentNode.getAttribute("severity") === "error" &&
       node.textContent === Object.prototype.toString();
   }, "thrown object generates error message");
+
+  // check that errors with entires in errordocs.js display links
+  // alongside their messages.
+  const ErrorDocs = require("devtools/server/actors/errordocs");
+
+  const ErrorDocStatements = {
+    "JSMSG_BAD_RADIX": "(42).toString(0);",
+    "JSMSG_BAD_ARRAY_LENGTH": "([]).length = -1",
+    "JSMSG_NEGATIVE_REPETITION_COUNT": "'abc'.repeat(-1);",
+    "JSMSG_BAD_FORMAL": "var f = Function('x y', 'return x + y;');",
+    "JSMSG_PRECISION_RANGE": "77.1234.toExponential(-1);",
+  };
+
+  for (let errorMessageName of Object.keys(ErrorDocStatements)) {
+    let url = ErrorDocs.GetURL(errorMessageName);
+
+    jsterm.clearOutput();
+    yield jsterm.execute(ErrorDocStatements[errorMessageName]);
+    yield checkResult((node) => {
+      return node.parentNode.getElementsByTagName("a")[0].title == url;
+    }, `error links to ${url}`);
+  }
 }
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -8,16 +8,17 @@
 
 const Services = require("Services");
 const { Cc, Ci, Cu } = require("chrome");
 const { DebuggerServer, ActorPool } = require("devtools/server/main");
 const { EnvironmentActor } = require("devtools/server/actors/environment");
 const { ThreadActor } = require("devtools/server/actors/script");
 const { ObjectActor, LongStringActor, createValueGrip, stringIsLong } = require("devtools/server/actors/object");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const ErrorDocs = require("devtools/server/actors/errordocs");
 
 loader.lazyRequireGetter(this, "NetworkMonitor", "devtools/shared/webconsole/network-monitor", true);
 loader.lazyRequireGetter(this, "NetworkMonitorChild", "devtools/shared/webconsole/network-monitor", true);
 loader.lazyRequireGetter(this, "ConsoleProgressListener", "devtools/shared/webconsole/network-monitor", true);
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 loader.lazyRequireGetter(this, "ServerLoggingListener", "devtools/shared/webconsole/server-logger", true);
 loader.lazyRequireGetter(this, "JSPropertyProvider", "devtools/shared/webconsole/js-property-provider", true);
 loader.lazyRequireGetter(this, "Parser", "resource://devtools/shared/Parser.jsm", true);
@@ -868,32 +869,38 @@ WebConsoleActor.prototype =
       selectedNodeActor: aRequest.selectedNodeActor,
       selectedObjectActor: aRequest.selectedObjectActor,
     };
 
     let evalInfo = this.evalWithDebugger(input, evalOptions);
     let evalResult = evalInfo.result;
     let helperResult = evalInfo.helperResult;
 
-    let result, errorMessage, errorGrip = null;
+    let result, errorDocURL, errorMessage, errorGrip = null;
     if (evalResult) {
       if ("return" in evalResult) {
         result = evalResult.return;
       } else if ("yield" in evalResult) {
         result = evalResult.yield;
       } else if ("throw" in evalResult) {
         let error = evalResult.throw;
         errorGrip = this.createValueGrip(error);
         // XXXworkers: Calling unsafeDereference() returns an object with no
         // toString method in workers. See Bug 1215120.
         let unsafeDereference = error && (typeof error === "object") &&
                                 error.unsafeDereference();
         errorMessage = unsafeDereference && unsafeDereference.toString
           ? unsafeDereference.toString()
           : String(error);
+
+          // It is possible that we won't have permission to unwrap an
+          // object and retrieve its errorMessageName.
+          try {
+            errorDocURL = ErrorDocs.GetURL(error && error.errorMessageName);
+          } catch (ex) {}
       }
     }
 
     // If a value is encountered that the debugger server doesn't support yet,
     // the console should remain functional.
     let resultGrip;
     try {
       resultGrip = this.createValueGrip(result);
@@ -905,16 +912,17 @@ WebConsoleActor.prototype =
 
     return {
       from: this.actorID,
       input: input,
       result: resultGrip,
       timestamp: timestamp,
       exception: errorGrip,
       exceptionMessage: this._createStringGrip(errorMessage),
+      exceptionDocURL: errorDocURL,
       helperResult: helperResult,
     };
   },
 
   /**
    * The Autocomplete request handler.
    *
    * @param object aRequest