Bug 1387477 - report source map errors due to missing sources; r?jdescottes draft
authorTom Tromey <tom@tromey.com>
Fri, 04 Aug 2017 13:14:11 -0600
changeset 642802 d624175e81b08559901231cdd90cf060b541e97e
parent 642801 bf01128ff941328409288465dbcd111d9eb0ecf4
child 725099 142d1d2bd384e14549506bb44b7909e32510af4b
push id72865
push userbmo:ttromey@mozilla.com
push dateTue, 08 Aug 2017 18:47:27 +0000
reviewersjdescottes
bugs1387477
milestone57.0a1
Bug 1387477 - report source map errors due to missing sources; r?jdescottes MozReview-Commit-ID: 5UbkJH8fvLn
.eslintignore
devtools/client/framework/toolbox.js
devtools/client/locales/en-US/toolbox.properties
devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_location_debugger_link.js
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_sourcemap_nosource.js
devtools/client/webconsole/new-console-output/test/mochitest/code_bundle_nosource.js
devtools/client/webconsole/new-console-output/test/mochitest/code_bundle_nosource.js.map
devtools/client/webconsole/new-console-output/test/mochitest/code_nosource.js
devtools/client/webconsole/new-console-output/test/mochitest/head.js
--- a/.eslintignore
+++ b/.eslintignore
@@ -169,16 +169,17 @@ devtools/client/debugger/test/mochitest/
 devtools/client/debugger/test/mochitest/code_binary_search_absolute.js
 devtools/client/debugger/test/mochitest/code_math.min.js
 devtools/client/debugger/test/mochitest/code_math_bogus_map.js
 devtools/client/debugger/test/mochitest/code_ugly*
 devtools/client/debugger/test/mochitest/code_worker-source-map.js
 devtools/client/framework/test/code_ugly*
 devtools/client/inspector/markup/test/events_bundle.js
 devtools/client/netmonitor/test/xhr_bundle.js
+devtools/client/webconsole/new-console-output/test/mochitest/code_bundle_nosource.js
 devtools/server/tests/unit/babel_and_browserify_script_with_source_map.js
 devtools/server/tests/unit/setBreakpoint*
 devtools/server/tests/unit/sourcemapped.js
 
 # dom/ exclusions
 dom/animation/**
 dom/archivereader/**
 dom/asmjscache/**
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -544,27 +544,52 @@ Toolbox.prototype = {
       return this._sourceMapService;
     }
     // Uses browser loader to access the `Worker` global.
     let service = this.browserRequire("devtools/client/shared/source-map/index");
 
     // Provide a wrapper for the service that reports errors more nicely.
     this._sourceMapService = new Proxy(service, {
       get: (target, name) => {
-        if (name === "getOriginalURLs") {
-          return (urlInfo) => {
-            return target.getOriginalURLs(urlInfo)
-              .catch(text => {
-                let message = L10N.getFormatStr("toolbox.sourceMapFailure",
-                                                text, urlInfo.url, urlInfo.sourceMapURL);
-                this.target.logErrorInPage(message, "source map");
-              });
-          };
+        switch (name) {
+          case "getOriginalURLs":
+            return (urlInfo) => {
+              return target.getOriginalURLs(urlInfo)
+                .catch(text => {
+                  let message = L10N.getFormatStr("toolbox.sourceMapFailure",
+                                                  text, urlInfo.url,
+                                                  urlInfo.sourceMapURL);
+                  this.target.logErrorInPage(message, "source map");
+                  // It's ok to swallow errors here, because a null
+                  // result just means that no source map was found.
+                  return null;
+                });
+            };
+
+          case "getOriginalSourceText":
+            return (originalSource) => {
+              return target.getOriginalSourceText(originalSource)
+                .catch(text => {
+                  let message = L10N.getFormatStr("toolbox.sourceMapSourceFailure",
+                                                  text, originalSource.url);
+                  this.target.logErrorInPage(message, "source map");
+                  // Also replace the result with the error text.
+                  // Note that this result has to have the same form
+                  // as whatever the upstream getOriginalSourceText
+                  // returns.
+                  return {
+                    text: message,
+                    contentType: "text/plain",
+                  };
+                });
+            };
+
+          default:
+            return target[name];
         }
-        return target[name];
       },
     });
 
     this._sourceMapService.startSourceMapWorker(SOURCE_MAP_WORKER);
     return this._sourceMapService;
   },
 
   /**
--- a/devtools/client/locales/en-US/toolbox.properties
+++ b/devtools/client/locales/en-US/toolbox.properties
@@ -179,8 +179,15 @@ toolbox.closebutton.tooltip=Close Develo
 toolbox.allToolsButton.tooltip=Select another tool
 
 # LOCALIZATION NOTE (toolbox.sourceMapFailure): This is shown in the web console
 # when there is a failure to fetch or parse a source map.
 # The text of the error: %1$S
 # The URL that caused DevTools to try to fetch a source map: %2$S
 # The URL of the source map itself: %3$S
 toolbox.sourceMapFailure=Source map error: %1$S\nResource URL: %2$S\nSource Map URL: %3$S
+
+# LOCALIZATION NOTE (toolbox.sourceMapSourceFailure): This is shown in
+# the web console when there is a failure to fetch or parse an
+# original source that was mentioned in a source map.
+# The text of the error: %1$S
+# The URL of the source: %2$S
+toolbox.sourceMapSourceFailure=Error while fetching an original source: %1$S\nSource URL: %2$S
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -1,12 +1,14 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
+  code_bundle_nosource.js
+  code_bundle_nosource.js.map
   head.js
   test-batching.html
   test-console.html
   test-console-filters.html
   test-console-group.html
   test-console-table.html
   test-location-debugger-link-console-log.js
   test-location-debugger-link-errors.js
@@ -48,13 +50,14 @@ skip-if = (os == 'linux' && bits == 32 &
 [browser_webconsole_network_messages_click.js]
 [browser_webconsole_nodes_highlight.js]
 [browser_webconsole_nodes_select.js]
 [browser_webconsole_object_inspector_entries.js]
 [browser_webconsole_object_inspector.js]
 [browser_webconsole_observer_notifications.js]
 [browser_webconsole_shows_reqs_in_netmonitor.js]
 [browser_webconsole_sourcemap_error.js]
+[browser_webconsole_sourcemap_nosource.js]
 [browser_webconsole_stacktrace_location_debugger_link.js]
 [browser_webconsole_stacktrace_location_scratchpad_link.js]
 [browser_webconsole_string.js]
 [browser_webconsole_timestamps.js]
 [browser_webconsole_warn_about_replaced_api.js]
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_location_debugger_link.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_location_debugger_link.js
@@ -35,40 +35,8 @@ add_task(function* () {
   yield toolbox.selectTool("webconsole");
   yield testOpenInDebugger(hud, toolbox, "Blah Blah");
 
   // // check again the first node.
   info("Selecting the console again");
   yield toolbox.selectTool("webconsole");
   yield testOpenInDebugger(hud, toolbox, "document.bar");
 });
-
-function* testOpenInDebugger(hud, toolbox, text) {
-  info(`Testing message with text "${text}"`);
-  let messageNode = yield waitFor(() => findMessage(hud, text));
-  let frameLinkNode = messageNode.querySelector(".message-location .frame-link");
-  ok(frameLinkNode, "The message does have a location link");
-  yield checkClickOnNode(hud, toolbox, frameLinkNode);
-}
-
-function* checkClickOnNode(hud, toolbox, frameLinkNode) {
-  info("checking click on node location");
-
-  let url = frameLinkNode.getAttribute("data-url");
-  ok(url, `source url found ("${url}")`);
-
-  let line = frameLinkNode.getAttribute("data-line");
-  ok(line, `source line found ("${line}")`);
-
-  let onSourceInDebuggerOpened = once(hud.ui, "source-in-debugger-opened");
-
-  EventUtils.sendMouseEvent({ type: "click" },
-    frameLinkNode.querySelector(".frame-link-filename"));
-
-  yield onSourceInDebuggerOpened;
-
-  let dbg = toolbox.getPanel("jsdebugger");
-  is(
-    dbg._selectors.getSelectedSource(dbg._getState()).get("url"),
-    url,
-    "expected source url"
-  );
-}
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_sourcemap_nosource.js
@@ -0,0 +1,54 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that a missing original source is reported.
+
+const JS_URL = URL_ROOT + "code_bundle_nosource.js";
+
+const PAGE_URL = `data:text/html,
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Empty test page to test source map with missing original source</title>
+  </head>
+
+  <body>
+    <script src="${JS_URL}"></script>
+  </body>
+
+</html>`;
+
+add_task(function* () {
+  // Force the new debugger UI, in case this gets uplifted with the old
+  // debugger still turned on
+  yield pushPref("devtools.debugger.new-debugger-frontend", true);
+  yield pushPref("devtools.source-map.client-service.enabled", true);
+
+  const hud = yield openNewTabAndConsole(PAGE_URL);
+  const toolbox = hud.ui.newConsoleOutput.toolbox;
+
+  info("Finding \"here\" message and waiting for source map to be applied");
+  yield waitFor(() => {
+    let node = findMessage(hud, "here");
+    if (!node) {
+      return false;
+    }
+    let frameLinkNode = node.querySelector(".message-location .frame-link");
+    let url = frameLinkNode.getAttribute("data-url");
+    return url.includes("nosuchfile");
+  });
+
+  yield testOpenInDebugger(hud, toolbox, "here");
+
+  info("Selecting the console again");
+  yield toolbox.selectTool("webconsole");
+
+  const node = yield waitFor(() => findMessage(hud, "original source"));
+  ok(node, "source map error is displayed in web console");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/code_bundle_nosource.js
@@ -0,0 +1,93 @@
+/******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+/******/
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+/******/
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId]) {
+/******/ 			return installedModules[moduleId].exports;
+/******/ 		}
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			i: moduleId,
+/******/ 			l: false,
+/******/ 			exports: {}
+/******/ 		};
+/******/
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ 		// Flag the module as loaded
+/******/ 		module.l = true;
+/******/
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+/******/
+/******/
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+/******/
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+/******/
+/******/ 	// define getter function for harmony exports
+/******/ 	__webpack_require__.d = function(exports, name, getter) {
+/******/ 		if(!__webpack_require__.o(exports, name)) {
+/******/ 			Object.defineProperty(exports, name, {
+/******/ 				configurable: false,
+/******/ 				enumerable: true,
+/******/ 				get: getter
+/******/ 			});
+/******/ 		}
+/******/ 	};
+/******/
+/******/ 	// getDefaultExport function for compatibility with non-harmony modules
+/******/ 	__webpack_require__.n = function(module) {
+/******/ 		var getter = module && module.__esModule ?
+/******/ 			function getDefault() { return module['default']; } :
+/******/ 			function getModuleExports() { return module; };
+/******/ 		__webpack_require__.d(getter, 'a', getter);
+/******/ 		return getter;
+/******/ 	};
+/******/
+/******/ 	// Object.prototype.hasOwnProperty.call
+/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
+/******/
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+/******/
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(__webpack_require__.s = 0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Original source code for the cross-domain source map test.
+// The generated file was made with
+//    webpack --devtool nosources-source-map code_nosource.js code_bundle_nosource.js
+// ... and then the bundle was edited to change the source name.
+
+
+
+function f() {
+  console.log("here");
+}
+
+f();
+
+// Avoid script GC.
+window.f = f;
+
+
+/***/ })
+/******/ ]);
+//# sourceMappingURL=code_bundle_nosource.js.map
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/code_bundle_nosource.js.map
@@ -0,0 +1,1 @@
+{"version":3,"sources":["webpack:///webpack/bootstrap 5f603779212cf1264c9b","nosuchfile.js"],"names":[],"mappings":";AAAA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,mCAA2B,0BAA0B,EAAE;AACvD,yCAAiC,eAAe;AAChD;AACA;AACA;;AAEA;AACA,8DAAsD,+DAA+D;;AAErH;AACA;;AAEA;AACA;;;;;;;;AC7DA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA","file":"code_bundle_nosource.js","sourceRoot":""}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/code_nosource.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Original source code for the cross-domain source map test.
+// The generated file was made with
+//    webpack --devtool nosources-source-map code_nosource.js code_bundle_nosource.js
+// ... and then the bundle was edited to change the source name.
+
+"use strict";
+
+function f() {
+  console.log("here");
+}
+
+f();
+
+// Avoid script GC.
+window.f = f;
--- a/devtools/client/webconsole/new-console-output/test/mochitest/head.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/head.js
@@ -1,15 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* import-globals-from ../../../../framework/test/shared-head.js */
 /* exported WCUL10n, openNewTabAndConsole, waitForMessages, waitFor, findMessage,
-   openContextMenu, hideContextMenu, loadDocument, waitForNodeMutation */
+   openContextMenu, hideContextMenu, loadDocument,
+   waitForNodeMutation, testOpenInDebugger, checkClickOnNode */
 
 "use strict";
 
 // shared-head.js handles imports, constants, and utility functions
 // Load the shared-head file first.
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
@@ -198,8 +199,56 @@ function waitForNodeMutation(node, obser
   return new Promise(resolve => {
     const observer = new MutationObserver(mutations => {
       resolve(mutations);
       observer.disconnect();
     });
     observer.observe(node, observeConfig);
   });
 }
+
+/**
+ * Search for a given message.  When found, simulate a click on the
+ * message's location, checking to make sure that the debugger opens
+ * the corresponding URL.
+ *
+ * @param {Object} hud
+ *        The webconsole
+ * @param {Object} toolbox
+ *        The toolbox
+ * @param {String} text
+ *        The text to search for.  This should be contained in the
+ *        message.  The searching is done with @see findMessage.
+ */
+function* testOpenInDebugger(hud, toolbox, text) {
+  info(`Finding message for open-in-debugger test; text is "${text}"`);
+  let messageNode = yield waitFor(() => findMessage(hud, text));
+  let frameLinkNode = messageNode.querySelector(".message-location .frame-link");
+  ok(frameLinkNode, "The message does have a location link");
+  yield checkClickOnNode(hud, toolbox, frameLinkNode);
+}
+
+/**
+ * Helper function for testOpenInDebugger.
+ */
+function* checkClickOnNode(hud, toolbox, frameLinkNode) {
+  info("checking click on node location");
+
+  let url = frameLinkNode.getAttribute("data-url");
+  ok(url, `source url found ("${url}")`);
+
+  let line = frameLinkNode.getAttribute("data-line");
+  ok(line, `source line found ("${line}")`);
+
+  let onSourceInDebuggerOpened = once(hud.ui, "source-in-debugger-opened");
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    frameLinkNode.querySelector(".frame-link-filename"));
+
+  yield onSourceInDebuggerOpened;
+
+  let dbg = toolbox.getPanel("jsdebugger");
+  is(
+    dbg._selectors.getSelectedSource(dbg._getState()).get("url"),
+    url,
+    "expected source url"
+  );
+}