Bug 1361853 - use client-side source map service in network monitor; r=Honza
authorTom Tromey <tom@tromey.com>
Wed, 10 May 2017 14:07:06 -0600
changeset 360199 ec3dbc16d7bba15c5dd3fc8c2b67052b1831a01f
parent 360198 6e91260036184d11db9c8e3ce0b9e31655d57c20
child 360200 7d243e41d8f91882281464e9e6aa9999698d12c4
push id43253
push userttromey@mozilla.com
push dateTue, 23 May 2017 20:42:48 +0000
treeherderautoland@ec3dbc16d7bb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersHonza
bugs1361853
milestone55.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 1361853 - use client-side source map service in network monitor; r=Honza MozReview-Commit-ID: A0lDkK12x8E
.eslintignore
devtools/client/netmonitor/index.html
devtools/client/netmonitor/src/components/app.js
devtools/client/netmonitor/src/components/monitor-panel.js
devtools/client/netmonitor/src/components/network-details-panel.js
devtools/client/netmonitor/src/components/stack-trace-panel.js
devtools/client/netmonitor/src/components/tabbox-panel.js
devtools/client/netmonitor/test/browser.ini
devtools/client/netmonitor/test/browser_net_cause_source_map.js
devtools/client/netmonitor/test/html_maps-test-page.html
devtools/client/netmonitor/test/xhr_bundle.js
devtools/client/netmonitor/test/xhr_bundle.js.map
devtools/client/netmonitor/test/xhr_original.js
--- a/.eslintignore
+++ b/.eslintignore
@@ -73,17 +73,17 @@ browser/extensions/pdfjs/content/web**
 browser/extensions/pocket/content/panels/js/tmpl.js
 browser/extensions/pocket/content/panels/js/vendor/**
 browser/locales/**
 # generated or library files in activity-stream
 browser/extensions/activity-stream/data/content/activity-stream.bundle.js
 browser/extensions/activity-stream/vendor/**
 # imported from chromium
 browser/extensions/mortar/**
-
+
 # devtools/ exclusions
 devtools/client/canvasdebugger/**
 devtools/client/commandline/**
 devtools/client/debugger/**
 devtools/client/framework/**
 !devtools/client/framework/devtools.js
 !devtools/client/framework/devtools-browser.js
 !devtools/client/framework/selection.js
@@ -168,16 +168,17 @@ devtools/server/actors/utils/automation-
 # Ignore devtools files testing sourcemaps / code style
 devtools/client/debugger/test/mochitest/code_binary_search.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/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/netmonitor/index.html
+++ b/devtools/client/netmonitor/index.html
@@ -40,17 +40,18 @@
           this.mount = document.querySelector("#mount");
           const connection = {
             tabConnection: {
               tabTarget: toolbox.target,
             },
             toolbox,
           };
           const App = createFactory(require("./src/components/app"));
-          render(Provider({ store }, App()), this.mount);
+          const sourceMapService = toolbox.sourceMapURLService;
+          render(Provider({ store }, App({ sourceMapService })), this.mount);
           return onFirefoxConnect(connection, actions, store.getState);
         },
 
         destroy() {
           unmountComponentAtNode(this.mount);
           return onDisconnect();
         }
       };
--- a/devtools/client/netmonitor/src/components/app.js
+++ b/devtools/client/netmonitor/src/components/app.js
@@ -16,25 +16,27 @@ const MonitorPanel = createFactory(requi
 const StatisticsPanel = createFactory(require("./statistics-panel"));
 
 const { div } = DOM;
 
 /*
  * App component
  * The top level component for representing main panel
  */
-function App({ statisticsOpen }) {
+function App({ statisticsOpen, sourceMapService }) {
   return (
     div({ className: "network-monitor" },
-      !statisticsOpen ? MonitorPanel() : StatisticsPanel()
+      !statisticsOpen ? MonitorPanel({sourceMapService}) : StatisticsPanel()
     )
   );
 }
 
 App.displayName = "App";
 
 App.propTypes = {
   statisticsOpen: PropTypes.bool.isRequired,
+  // Service to enable the source map feature.
+  sourceMapService: PropTypes.object,
 };
 
 module.exports = connect(
   (state) => ({ statisticsOpen: state.ui.statisticsOpen }),
 )(App);
--- a/devtools/client/netmonitor/src/components/monitor-panel.js
+++ b/devtools/client/netmonitor/src/components/monitor-panel.js
@@ -33,16 +33,18 @@ const MediaQueryList = window.matchMedia
 const MonitorPanel = createClass({
   displayName: "MonitorPanel",
 
   propTypes: {
     isEmpty: PropTypes.bool.isRequired,
     networkDetailsOpen: PropTypes.bool.isRequired,
     openNetworkDetails: PropTypes.func.isRequired,
     request: PropTypes.object,
+    // Service to enable the source map feature.
+    sourceMapService: PropTypes.object,
     updateRequest: PropTypes.func.isRequired,
   },
 
   getInitialState() {
     return {
       isVerticalSpliter: MediaQueryList.matches,
     };
   },
@@ -97,33 +99,36 @@ const MonitorPanel = createClass({
 
   onLayoutChange() {
     this.setState({
       isVerticalSpliter: MediaQueryList.matches,
     });
   },
 
   render() {
-    let { isEmpty, networkDetailsOpen } = this.props;
+    let { isEmpty, networkDetailsOpen, sourceMapService } = this.props;
     let initialWidth = Services.prefs.getIntPref(
         "devtools.netmonitor.panes-network-details-width");
     let initialHeight = Services.prefs.getIntPref(
         "devtools.netmonitor.panes-network-details-height");
     return (
       div({ className: "monitor-panel" },
         Toolbar(),
         SplitBox({
           className: "devtools-responsive-container",
           initialWidth: `${initialWidth}px`,
           initialHeight: `${initialHeight}px`,
           minSize: "50px",
           maxSize: "80%",
           splitterSize: "1px",
           startPanel: RequestList({ isEmpty }),
-          endPanel: networkDetailsOpen && NetworkDetailsPanel({ ref: "endPanel" }),
+          endPanel: networkDetailsOpen && NetworkDetailsPanel({
+            ref: "endPanel",
+            sourceMapService,
+          }),
           endPanelCollapsed: !networkDetailsOpen,
           endPanelControl: true,
           vert: this.state.isVerticalSpliter,
         }),
       )
     );
   }
 });
--- a/devtools/client/netmonitor/src/components/network-details-panel.js
+++ b/devtools/client/netmonitor/src/components/network-details-panel.js
@@ -22,28 +22,30 @@ const { div } = DOM;
 /*
  * Network details panel component
  */
 function NetworkDetailsPanel({
   activeTabId,
   cloneSelectedRequest,
   request,
   selectTab,
+  sourceMapService,
 }) {
   if (!request) {
     return null;
   }
 
   return (
     div({ className: "network-details-panel" },
       !request.isCustom ?
         TabboxPanel({
           activeTabId,
           request,
           selectTab,
+          sourceMapService,
         }) :
         CustomRequestPanel({
           cloneSelectedRequest,
           request,
         })
     )
   );
 }
@@ -51,16 +53,18 @@ function NetworkDetailsPanel({
 NetworkDetailsPanel.displayName = "NetworkDetailsPanel";
 
 NetworkDetailsPanel.propTypes = {
   activeTabId: PropTypes.string,
   cloneSelectedRequest: PropTypes.func.isRequired,
   open: PropTypes.bool,
   request: PropTypes.object,
   selectTab: PropTypes.func.isRequired,
+  // Service to enable the source map feature.
+  sourceMapService: PropTypes.object,
 };
 
 module.exports = connect(
   (state) => ({
     activeTabId: state.ui.detailsPanelSelectedTab,
     request: getSelectedRequest(state),
   }),
   (dispatch) => ({
--- a/devtools/client/netmonitor/src/components/stack-trace-panel.js
+++ b/devtools/client/netmonitor/src/components/stack-trace-panel.js
@@ -11,28 +11,31 @@ const {
 } = require("devtools/client/shared/vendor/react");
 const { viewSourceInDebugger } = require("../connector/index");
 
 const { div } = DOM;
 
 // Components
 const StackTrace = createFactory(require("devtools/client/shared/components/stack-trace"));
 
-function StackTracePanel({ request }) {
+function StackTracePanel({ request, sourceMapService }) {
   let { stacktrace } = request.cause;
 
   return (
     div({ className: "panel-container" },
       StackTrace({
         stacktrace,
         onViewSourceInDebugger: ({ url, line }) => viewSourceInDebugger(url, line),
+        sourceMapService,
       }),
     )
   );
 }
 
 StackTracePanel.displayName = "StackTracePanel";
 
 StackTracePanel.propTypes = {
   request: PropTypes.object.isRequired,
+  // Service to enable the source map feature.
+  sourceMapService: PropTypes.object,
 };
 
 module.exports = StackTracePanel;
--- a/devtools/client/netmonitor/src/components/tabbox-panel.js
+++ b/devtools/client/netmonitor/src/components/tabbox-panel.js
@@ -36,16 +36,17 @@ const TIMINGS_TITLE = L10N.getStr("netmo
  * Tabbox panel component
  * Display the network request details
  */
 function TabboxPanel({
   activeTabId,
   cloneSelectedRequest,
   request,
   selectTab,
+  sourceMapService,
 }) {
   if (!request) {
     return null;
   }
 
   return (
     Tabbar({
       activeTabId,
@@ -83,17 +84,17 @@ function TabboxPanel({
       },
         TimingsPanel({ request }),
       ),
       request.cause && request.cause.stacktrace && request.cause.stacktrace.length > 0 &&
       TabPanel({
         id: "stack-trace",
         title: STACK_TRACE_TITLE,
       },
-        StackTracePanel({ request }),
+        StackTracePanel({ request, sourceMapService }),
       ),
       request.securityState && request.securityState !== "insecure" &&
       TabPanel({
         id: "security",
         title: SECURITY_TITLE,
       },
         SecurityPanel({ request }),
       ),
@@ -103,16 +104,18 @@ function TabboxPanel({
 
 TabboxPanel.displayName = "TabboxPanel";
 
 TabboxPanel.propTypes = {
   activeTabId: PropTypes.string,
   cloneSelectedRequest: PropTypes.func.isRequired,
   request: PropTypes.object,
   selectTab: PropTypes.func.isRequired,
+  // Service to enable the source map feature.
+  sourceMapService: PropTypes.object,
 };
 
 module.exports = connect(
   (state) => ({
     activeTabId: state.ui.detailsPanelSelectedTab,
     request: getSelectedRequest(state),
   }),
   (dispatch) => ({
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -17,16 +17,17 @@ support-files =
   html_infinite-get-page.html
   html_json-b64.html
   html_json-basic.html
   html_json-custom-mime-test-page.html
   html_json-long-test-page.html
   html_json-malformed-test-page.html
   html_json-text-mime-test-page.html
   html_jsonp-test-page.html
+  html_maps-test-page.html
   html_navigate-test-page.html
   html_params-test-page.html
   html_post-data-test-page.html
   html_post-json-test-page.html
   html_post-raw-test-page.html
   html_post-raw-with-headers-test-page.html
   html_simple-test-page.html
   html_single-get-page.html
@@ -45,24 +46,28 @@ support-files =
   sjs_simple-test-server.sjs
   sjs_sorting-test-server.sjs
   sjs_status-codes-test-server.sjs
   sjs_truncate-test-server.sjs
   test-image.png
   service-workers/status-codes.html
   service-workers/status-codes-service-worker.js
   !/devtools/client/framework/test/shared-head.js
+  xhr_bundle.js
+  xhr_bundle.js.map
+  xhr_original.js
 
 [browser_net_accessibility-01.js]
 [browser_net_accessibility-02.js]
 [browser_net_api-calls.js]
 [browser_net_autoscroll.js]
 [browser_net_cached-status.js]
 [browser_net_cause.js]
 [browser_net_cause_redirect.js]
+[browser_net_cause_source_map.js]
 [browser_net_service-worker-status.js]
 [browser_net_charts-01.js]
 [browser_net_charts-02.js]
 [browser_net_charts-03.js]
 [browser_net_charts-04.js]
 [browser_net_charts-05.js]
 [browser_net_charts-06.js]
 [browser_net_charts-07.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_cause_source_map.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if request cause is reported correctly when using source maps.
+ */
+
+const CAUSE_FILE_NAME = "html_maps-test-page.html";
+const CAUSE_URL = EXAMPLE_URL + CAUSE_FILE_NAME;
+
+const N_EXPECTED_REQUESTS = 4;
+
+add_task(function* () {
+  // the initNetMonitor function clears the network request list after the
+  // page is loaded. That's why we first load a bogus page from SIMPLE_URL,
+  // and only then load the real thing from CAUSE_URL - we want to catch
+  // all the requests the page is making, not only the XHRs.
+  // We can't use about:blank here, because initNetMonitor checks that the
+  // page has actually made at least one request.
+  let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
+
+  let { document, store, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+
+  store.dispatch(Actions.batchEnable(false));
+  let waitPromise = waitForNetworkEvents(monitor, N_EXPECTED_REQUESTS);
+  tab.linkedBrowser.loadURI(CAUSE_URL);
+  yield waitPromise;
+
+  info("Clicking item and waiting for details panel to open");
+  waitPromise = waitForDOM(document, ".network-details-panel");
+  let xhrRequestItem = document.querySelectorAll(".request-list-item")[3];
+  EventUtils.sendMouseEvent({ type: "mousedown" }, xhrRequestItem);
+  yield waitPromise;
+
+  info("Clicking stack tab and waiting for stack panel to open");
+  waitPromise = waitForDOM(document, "#stack-trace-panel");
+  let stackTab = document.querySelector("#stack-trace-tab");
+  EventUtils.sendMouseEvent({ type: "click" }, stackTab);
+  yield waitPromise;
+
+  info("Waiting for source maps to be applied");
+  yield waitUntil(() => {
+    let frames = document.querySelectorAll(".frame-link");
+    return frames && frames.length >= 2 &&
+      frames[0].textContent.includes("xhr_original") &&
+      frames[1].textContent.includes("xhr_original");
+  });
+
+  let frames = document.querySelectorAll(".frame-link");
+  is(frames.length, 3, "should have 3 stack frames");
+  is(frames[0].textContent, `reallydoxhr xhr_original.js:6`);
+  is(frames[1].textContent, `doxhr xhr_original.js:10`);
+
+  yield teardown(monitor);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_maps-test-page.html
@@ -0,0 +1,24 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+    <meta http-equiv="Pragma" content="no-cache" />
+    <meta http-equiv="Expires" content="0" />
+    <title>Network Monitor source maps test page</title>
+    <link rel="stylesheet" type="text/css" href="stylesheet_request" />
+  </head>
+
+  <body>
+    <script type="text/javascript" src="xhr_bundle.js" charset="utf-8"></script>
+    <script type="text/javascript">
+      "use strict";
+
+      /* globals doxhr */
+      doxhr();
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/xhr_bundle.js
@@ -0,0 +1,91 @@
+/******/ (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;
+/******/
+/******/ 	// identity function for calling harmony imports with the correct context
+/******/ 	__webpack_require__.i = function(value) { return value; };
+/******/
+/******/ 	// 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";
+
+
+function reallydoxhr() {
+  let z = new XMLHttpRequest();
+  z.open("get", "test-image.png", true);
+  z.send();
+}
+
+function doxhr() {
+  reallydoxhr();
+}
+
+window.doxhr = doxhr;
+
+
+/***/ })
+/******/ ]);
+//# sourceMappingURL=xhr_bundle.js.map
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/xhr_bundle.js.map
@@ -0,0 +1,1 @@
+{"version":3,"sources":["webpack:///webpack/bootstrap 1f90f505700f55e4a0b4","webpack:///./xhr_original.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,mDAA2C,cAAc;;AAEzD;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;;;;;;;;AChEA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA","file":"xhr_bundle.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// identity function for calling harmony imports with the correct context\n \t__webpack_require__.i = function(value) { return value; };\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 0);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 1f90f505700f55e4a0b4","\"use strict\";\n\nfunction reallydoxhr() {\n  let z = new XMLHttpRequest();\n  z.open(\"get\", \"test-image.png\", true);\n  z.send();\n}\n\nfunction doxhr() {\n  reallydoxhr();\n}\n\nwindow.doxhr = doxhr;\n\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./xhr_original.js\n// module id = 0\n// module chunks = 0"],"sourceRoot":""}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/xhr_original.js
@@ -0,0 +1,13 @@
+"use strict";
+
+function reallydoxhr() {
+  let z = new XMLHttpRequest();
+  z.open("get", "test-image.png", true);
+  z.send();
+}
+
+function doxhr() {
+  reallydoxhr();
+}
+
+window.doxhr = doxhr;