Bug 1387477 - report source map errors due to missing sources; r=jdescottes
authorTom Tromey <tom@tromey.com>
Fri, 04 Aug 2017 13:14:11 -0600
changeset 373503 eb95d04c5948578cdee8b0d2e77321e60bda7d75
parent 373502 6f1193b844cd10cd26dce4a73c94883fa6916349
child 373504 ab50c7685fa2721450d5aadebe3028f762346761
push id32303
push usercbook@mozilla.com
push dateWed, 09 Aug 2017 09:34:07 +0000
treeherdermozilla-central@c93fa2271ee7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdescottes
bugs1387477
milestone57.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 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"
+  );
+}