Bug 1387477 - report source map errors due to missing sources; r?jdescottes
MozReview-Commit-ID: 5UbkJH8fvLn
--- 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"
+ );
+}