Bug 1394681 - Properly open links. r=rickychien
authorJan Odvarko <odvarko@gmail.com>
Wed, 06 Sep 2017 12:27:17 +0200
changeset 379232 13af2a4eee010f9c8861ebb9292bd80febfd33f3
parent 379231 c5412efabadb8810f8e527cae0d410b6baabec39
child 379233 f18ee8840162b9e1a19946eac2735d6bc30f1efd
push id94617
push userryanvm@gmail.com
push dateWed, 06 Sep 2017 18:55:29 +0000
treeherdermozilla-inbound@13af2a4eee01 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrickychien
bugs1394681
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 1394681 - Properly open links. r=rickychien MozReview-Commit-ID: HdtFyQnIOUj
devtools/client/netmonitor/index.html
devtools/client/netmonitor/src/components/app.js
devtools/client/netmonitor/src/components/cookies-panel.js
devtools/client/netmonitor/src/components/headers-panel.js
devtools/client/netmonitor/src/components/monitor-panel.js
devtools/client/netmonitor/src/components/network-details-panel.js
devtools/client/netmonitor/src/components/params-panel.js
devtools/client/netmonitor/src/components/properties-view.js
devtools/client/netmonitor/src/components/response-panel.js
devtools/client/netmonitor/src/components/security-panel.js
devtools/client/netmonitor/src/components/stack-trace-panel.js
devtools/client/netmonitor/src/components/tabbox-panel.js
devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
--- a/devtools/client/netmonitor/index.html
+++ b/devtools/client/netmonitor/index.html
@@ -33,43 +33,55 @@
       // Inject EventEmitter into global window.
       EventEmitter.decorate(window);
       // Inject to global window for testing
       window.store = store;
 
       window.Netmonitor = {
         bootstrap({ toolbox }) {
           this.mount = document.querySelector("#mount");
+
           const connection = {
             tabConnection: {
               tabTarget: toolbox.target,
             },
             toolbox,
           };
+
+          const openLink = (link) => {
+            let parentDoc = toolbox.doc;
+            let iframe = parentDoc.getElementById("toolbox-panel-iframe-netmonitor");
+            let top = iframe.ownerDocument.defaultView.top;
+            top.openUILinkIn(link, "tab");
+          };
+
           const App = createFactory(require("./src/components/app"));
           const sourceMapService = toolbox.sourceMapURLService;
-          render(Provider({ store }, App({ sourceMapService })), this.mount);
+          const app = App({ sourceMapService, openLink });
+          render(Provider({ store }, app), this.mount);
           return onFirefoxConnect(connection, actions, store.getState);
         },
 
         destroy() {
           unmountComponentAtNode(this.mount);
           return onDisconnect();
-        }
+        },
       };
 
-      // Implement support for chrome://devtools/content/netmonitor/index.html?type=tab&id=1234 URLs
+      // Implement support for:
+      // chrome://devtools/content/netmonitor/index.html?type=tab&id=1234 URLs
       // where 1234 is the tab id, you can retrieve from about:debugging#tabs links.
       // Simply copy the id from about:devtools-toolbox?type=tab&id=1234 URLs.
 
       // URL constructor doesn't support chrome: scheme
       let href = window.location.href.replace(/chrome:/, "http://");
       let url = new window.URL(href);
 
-      // If query parameters are given in a chrome tab, the inspector is running in standalone.
+      // If query parameters are given in a chrome tab, the inspector
+      // is running in standalone.
       if (window.location.protocol === "chrome:" && url.search.length > 1) {
         const { targetFromURL } = require("devtools/client/framework/target-from-url");
 
         (async function () {
           let target = await targetFromURL(url);
           // Start the network event listening as it is done in the toolbox code
           await target.activeConsole.startListeners([
             "NetworkActivity",
@@ -78,14 +90,15 @@
           let toolbox = {
             target,
             viewSourceInDebugger() {
               throw new Error("toolbox.viewSourceInDebugger is not implement from a tab");
             }
           };
           window.Netmonitor.bootstrap({ toolbox });
         })().catch(e => {
-          window.alert("Unable to start the network monitor:" + e.message + "\n" + e.stack);
+          window.alert("Unable to start the network monitor:" +
+            e.message + "\n" + e.stack);
         });
       }
     </script>
   </body>
 </html>
--- a/devtools/client/netmonitor/src/components/app.js
+++ b/devtools/client/netmonitor/src/components/app.js
@@ -16,27 +16,32 @@ 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, sourceMapService }) {
+function App({
+  openLink,
+  sourceMapService,
+  statisticsOpen,
+}) {
   return (
     div({ className: "network-monitor" },
-      !statisticsOpen ? MonitorPanel({sourceMapService}) : StatisticsPanel()
+      !statisticsOpen ? MonitorPanel({ openLink, sourceMapService }) : StatisticsPanel()
     )
   );
 }
 
 App.displayName = "App";
 
 App.propTypes = {
-  statisticsOpen: PropTypes.bool.isRequired,
+  openLink: PropTypes.func,
   // Service to enable the source map feature.
   sourceMapService: PropTypes.object,
+  statisticsOpen: PropTypes.bool.isRequired,
 };
 
 module.exports = connect(
   (state) => ({ statisticsOpen: state.ui.statisticsOpen }),
 )(App);
--- a/devtools/client/netmonitor/src/components/cookies-panel.js
+++ b/devtools/client/netmonitor/src/components/cookies-panel.js
@@ -26,16 +26,17 @@ const SECTION_NAMES = [
 ];
 
 /*
  * Cookies panel component
  * This tab lists full details of any cookies sent with the request or response
  */
 function CookiesPanel({
   request,
+  openLink,
 }) {
   let {
     requestCookies = { cookies: [] },
     responseCookies = { cookies: [] },
   } = request;
 
   requestCookies = requestCookies.cookies || requestCookies;
   responseCookies = responseCookies.cookies || responseCookies;
@@ -57,25 +58,27 @@ function CookiesPanel({
   }
 
   return (
     div({ className: "panel-container" },
       PropertiesView({
         object,
         filterPlaceHolder: COOKIES_FILTER_TEXT,
         sectionNames: SECTION_NAMES,
+        openLink,
       })
     )
   );
 }
 
 CookiesPanel.displayName = "CookiesPanel";
 
 CookiesPanel.propTypes = {
   request: PropTypes.object.isRequired,
+  openLink: PropTypes.func,
 };
 
 /**
  * Mapping array to dict for TreeView usage.
  * Since TreeView only support Object(dict) format.
  *
  * @param {Object[]} arr - key-value pair array like cookies or params
  * @returns {Object}
--- a/devtools/client/netmonitor/src/components/headers-panel.js
+++ b/devtools/client/netmonitor/src/components/headers-panel.js
@@ -49,17 +49,18 @@ const SUMMARY_VERSION = L10N.getStr("net
  * Lists basic information about the request
  */
 const HeadersPanel = createClass({
   displayName: "HeadersPanel",
 
   propTypes: {
     cloneSelectedRequest: PropTypes.func.isRequired,
     request: PropTypes.object.isRequired,
-    renderValue: PropTypes.func
+    renderValue: PropTypes.func,
+    openLink: PropTypes.func,
   },
 
   getInitialState() {
     return {
       rawHeadersOpened: false,
     };
   },
 
@@ -120,16 +121,17 @@ const HeadersPanel = createClass({
           url: headerDocURL,
         }) : null
       )
     );
   },
 
   render() {
     const {
+      openLink,
       cloneSelectedRequest,
       request: {
         fromCache,
         fromServiceWorker,
         httpVersion,
         method,
         remoteAddress,
         remotePort,
@@ -251,15 +253,16 @@ const HeadersPanel = createClass({
           summaryVersion,
           summaryRawHeaders,
         ),
         PropertiesView({
           object,
           filterPlaceHolder: HEADERS_FILTER_TEXT,
           sectionNames: Object.keys(object),
           renderValue: this.renderValue,
+          openLink,
         }),
       )
     );
   }
 });
 
 module.exports = HeadersPanel;
--- a/devtools/client/netmonitor/src/components/monitor-panel.js
+++ b/devtools/client/netmonitor/src/components/monitor-panel.js
@@ -35,16 +35,17 @@ const MonitorPanel = createClass({
 
   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,
+    openLink: PropTypes.func,
     updateRequest: PropTypes.func.isRequired,
   },
 
   getInitialState() {
     return {
       isVerticalSpliter: MediaQueryList.matches,
     };
   },
@@ -99,17 +100,23 @@ const MonitorPanel = createClass({
 
   onLayoutChange() {
     this.setState({
       isVerticalSpliter: MediaQueryList.matches,
     });
   },
 
   render() {
-    let { isEmpty, networkDetailsOpen, sourceMapService } = this.props;
+    let {
+      isEmpty,
+      networkDetailsOpen,
+      sourceMapService,
+      openLink
+    } = 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({
@@ -118,16 +125,17 @@ const MonitorPanel = createClass({
           initialHeight: `${initialHeight}px`,
           minSize: "50px",
           maxSize: "80%",
           splitterSize: "1px",
           startPanel: RequestList({ isEmpty }),
           endPanel: networkDetailsOpen && NetworkDetailsPanel({
             ref: "endPanel",
             sourceMapService,
+            openLink,
           }),
           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
@@ -23,30 +23,32 @@ const { div } = DOM;
  * Network details panel component
  */
 function NetworkDetailsPanel({
   activeTabId,
   cloneSelectedRequest,
   request,
   selectTab,
   sourceMapService,
+  openLink,
 }) {
   if (!request) {
     return null;
   }
 
   return (
     div({ className: "network-details-panel" },
       !request.isCustom ?
         TabboxPanel({
           activeTabId,
+          cloneSelectedRequest,
           request,
           selectTab,
           sourceMapService,
-          cloneSelectedRequest,
+          openLink,
         }) :
         CustomRequestPanel({
           request,
         })
     )
   );
 }
 
@@ -55,16 +57,17 @@ NetworkDetailsPanel.displayName = "Netwo
 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,
+  openLink: PropTypes.func,
 };
 
 module.exports = connect(
   (state) => ({
     activeTabId: state.ui.detailsPanelSelectedTab,
     request: getSelectedRequest(state),
   }),
   (dispatch) => ({
--- a/devtools/client/netmonitor/src/components/params-panel.js
+++ b/devtools/client/netmonitor/src/components/params-panel.js
@@ -29,17 +29,20 @@ const SECTION_NAMES = [
   PARAMS_POST_PAYLOAD,
   PARAMS_QUERY_STRING,
 ];
 
 /*
  * Params panel component
  * Displays the GET parameters and POST data of a request
  */
-function ParamsPanel({ request }) {
+function ParamsPanel({
+  openLink,
+  request,
+}) {
   let {
     formDataSections,
     mimeType,
     requestPostData,
     url,
   } = request;
   let postData = requestPostData ? requestPostData.postData.text : null;
   let query = getUrlQuery(url);
@@ -87,25 +90,27 @@ function ParamsPanel({ request }) {
   }
 
   return (
     div({ className: "panel-container" },
       PropertiesView({
         object,
         filterPlaceHolder: PARAMS_FILTER_TEXT,
         sectionNames: SECTION_NAMES,
+        openLink,
       })
     )
   );
 }
 
 ParamsPanel.displayName = "ParamsPanel";
 
 ParamsPanel.propTypes = {
   request: PropTypes.object.isRequired,
+  openLink: PropTypes.func,
 };
 
 /**
  * Mapping array to dict for TreeView usage.
  * Since TreeView only support Object(dict) format.
  * This function also deal with duplicate key case
  * (for multiple selection and query params with same keys)
  *
--- a/devtools/client/netmonitor/src/components/properties-view.js
+++ b/devtools/client/netmonitor/src/components/properties-view.js
@@ -44,16 +44,17 @@ const PropertiesView = createClass({
   displayName: "PropertiesView",
 
   propTypes: {
     object: PropTypes.object,
     enableInput: PropTypes.bool,
     expandableStrings: PropTypes.bool,
     filterPlaceHolder: PropTypes.string,
     sectionNames: PropTypes.array,
+    openLink: PropTypes.func,
   },
 
   getDefaultProps() {
     return {
       enableInput: true,
       enableFilter: true,
       expandableStrings: false,
       filterPlaceHolder: "",
@@ -143,16 +144,17 @@ const PropertiesView = createClass({
       decorator,
       enableInput,
       expandableStrings,
       filterPlaceHolder,
       object,
       renderRow,
       renderValue,
       sectionNames,
+      openLink,
     } = this.props;
 
     return (
       div({ className: "properties-view" },
         this.shouldRenderSearchBox(object) &&
           div({ className: "searchbox-section" },
             SearchBox({
               delay: FILTER_SEARCH_DELAY,
@@ -176,16 +178,17 @@ const PropertiesView = createClass({
             useQuotes: false,
             expandedNodes: TreeViewClass.getExpandedNodes(
               object,
               {maxLevel: AUTO_EXPAND_MAX_LEVEL, maxNodes: AUTO_EXPAND_MAX_NODES}
             ),
             onFilter: (props) => this.onFilter(props, sectionNames),
             renderRow: renderRow || this.renderRowWithEditor,
             renderValue: renderValue || this.renderValueWithRep,
+            openLink,
           }),
         ),
       )
     );
   }
 });
 
 module.exports = PropertiesView;
--- a/devtools/client/netmonitor/src/components/response-panel.js
+++ b/devtools/client/netmonitor/src/components/response-panel.js
@@ -28,16 +28,17 @@ const RESPONSE_PAYLOAD = L10N.getStr("re
  * Response panel component
  * Displays the GET parameters and POST data of a request
  */
 const ResponsePanel = createClass({
   displayName: "ResponsePanel",
 
   propTypes: {
     request: PropTypes.object.isRequired,
+    openLink: PropTypes.func,
   },
 
   getInitialState() {
     return {
       imageDimensions: {
         width: 0,
         height: 0,
       },
@@ -105,17 +106,18 @@ const ResponsePanel = createClass({
 
       return result;
     }
 
     return null;
   },
 
   render() {
-    let { responseContent, url } = this.props.request;
+    let { openLink, request } = this.props;
+    let { responseContent, url } = request;
 
     if (!responseContent || typeof responseContent.content.text !== "string") {
       return null;
     }
 
     let { encoding, mimeType, text } = responseContent.content;
 
     if (mimeType.includes("image/")) {
@@ -170,15 +172,16 @@ const ResponsePanel = createClass({
       div({ className: "panel-container" },
         error && div({ className: "response-error-header", title: error },
           error
         ),
         PropertiesView({
           object,
           filterPlaceHolder: JSON_FILTER_TEXT,
           sectionNames: Object.keys(object),
+          openLink,
         }),
       )
     );
   }
 });
 
 module.exports = ResponsePanel;
--- a/devtools/client/netmonitor/src/components/security-panel.js
+++ b/devtools/client/netmonitor/src/components/security-panel.js
@@ -19,17 +19,20 @@ const PropertiesView = createFactory(req
 const { div, input, span } = DOM;
 
 /*
  * Security panel component
  * If the site is being served over HTTPS, you get an extra tab labeled "Security".
  * This contains details about the secure connection used including the protocol,
  * the cipher suite, and certificate details
  */
-function SecurityPanel({ request }) {
+function SecurityPanel({
+  openLink,
+  request,
+}) {
   const { securityInfo, url } = request;
 
   if (!securityInfo || !url) {
     return null;
   }
 
   const notAvailable = L10N.getStr("netmonitor.security.notAvailable");
   let object;
@@ -96,24 +99,26 @@ function SecurityPanel({ request }) {
   }
 
   return div({ className: "panel-container security-panel" },
     PropertiesView({
       object,
       renderValue: (props) => renderValue(props, securityInfo.weaknessReasons),
       enableFilter: false,
       expandedNodes: TreeViewClass.getExpandedNodes(object),
+      openLink,
     })
   );
 }
 
 SecurityPanel.displayName = "SecurityPanel";
 
 SecurityPanel.propTypes = {
   request: PropTypes.object.isRequired,
+  openLink: PropTypes.func,
 };
 
 function renderValue(props, weaknessReasons = []) {
   const { member, value } = props;
 
   // Hide object summary
   if (typeof member.value === "object") {
     return null;
--- a/devtools/client/netmonitor/src/components/stack-trace-panel.js
+++ b/devtools/client/netmonitor/src/components/stack-trace-panel.js
@@ -11,31 +11,37 @@ 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, sourceMapService }) {
+function StackTracePanel({
+  openLink,
+  request,
+  sourceMapService,
+}) {
   let { stacktrace } = request.cause;
 
   return (
     div({ className: "panel-container" },
       StackTrace({
         stacktrace,
         onViewSourceInDebugger: ({ url, line }) => viewSourceInDebugger(url, line),
         sourceMapService,
+        openLink,
       }),
     )
   );
 }
 
 StackTracePanel.displayName = "StackTracePanel";
 
 StackTracePanel.propTypes = {
   request: PropTypes.object.isRequired,
   // Service to enable the source map feature.
   sourceMapService: PropTypes.object,
+  openLink: PropTypes.func,
 };
 
 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
  * Display the network request details
  */
 function TabboxPanel({
   activeTabId,
   cloneSelectedRequest = ()=>{},
   request,
   selectTab,
   sourceMapService,
+  openLink,
 }) {
   if (!request) {
     return null;
   }
 
   return (
     Tabbar({
       activeTabId,
@@ -53,64 +54,65 @@ function TabboxPanel({
       onSelect: selectTab,
       renderOnlySelected: true,
       showAllTabsMenu: true,
     },
       TabPanel({
         id: PANELS.HEADERS,
         title: HEADERS_TITLE,
       },
-        HeadersPanel({ request, cloneSelectedRequest }),
+        HeadersPanel({ request, cloneSelectedRequest, openLink }),
       ),
       TabPanel({
         id: PANELS.COOKIES,
         title: COOKIES_TITLE,
       },
-        CookiesPanel({ request }),
+        CookiesPanel({ request, openLink }),
       ),
       TabPanel({
         id: PANELS.PARAMS,
         title: PARAMS_TITLE,
       },
-        ParamsPanel({ request }),
+        ParamsPanel({ request, openLink }),
       ),
       TabPanel({
         id: PANELS.RESPONSE,
         title: RESPONSE_TITLE,
       },
-        ResponsePanel({ request }),
+        ResponsePanel({ request, openLink }),
       ),
       TabPanel({
         id: PANELS.TIMINGS,
         title: TIMINGS_TITLE,
       },
         TimingsPanel({ request }),
       ),
       request.cause && request.cause.stacktrace && request.cause.stacktrace.length > 0 &&
       TabPanel({
         id: PANELS.STACK_TRACE,
         title: STACK_TRACE_TITLE,
       },
-        StackTracePanel({ request, sourceMapService }),
+        StackTracePanel({ request, sourceMapService, openLink }),
       ),
       request.securityState && request.securityState !== "insecure" &&
       TabPanel({
         id: PANELS.SECURITY,
         title: SECURITY_TITLE,
       },
-        SecurityPanel({ request }),
+        SecurityPanel({ request, openLink }),
       ),
     )
   );
 }
 
 TabboxPanel.displayName = "TabboxPanel";
 
 TabboxPanel.propTypes = {
   activeTabId: PropTypes.string,
   cloneSelectedRequest: PropTypes.func,
   request: PropTypes.object,
   selectTab: PropTypes.func.isRequired,
   // Service to enable the source map feature.
   sourceMapService: PropTypes.object,
+  openLink: PropTypes.func,
 };
 
 module.exports = connect()(TabboxPanel);
--- a/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/network-event-message.js
@@ -102,16 +102,17 @@ function NetworkEventMessage({
 
   // Only render the attachment if the network-event is
   // actually opened (performance optimization).
   const attachment = open && dom.div({className: "network-info devtools-monospace"},
     TabboxPanel({
       activeTabId: networkMessageActiveTabId,
       request: networkMessageUpdate,
       sourceMapService: serviceContainer.sourceMapService,
+      openLink: serviceContainer.openLink,
       selectTab: (tabId) => {
         dispatch(actions.selectNetworkMessageTab(tabId));
       },
     })
   );
 
   return Message({
     dispatch,
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -75,17 +75,19 @@ NewConsoleOutputWrapper.prototype = {
       attachRefToHud,
       emitNewMessage: (node, messageId) => {
         this.jsterm.hud.emit("new-messages", new Set([{
           node,
           messageId,
         }]));
       },
       hudProxyClient: this.jsterm.hud.proxy.client,
-      openLink: url => this.jsterm.hud.owner.openLink(url),
+      openLink: url => {
+        this.jsterm.hud.owner.openLink(url);
+      },
       createElement: nodename => {
         return this.document.createElementNS("http://www.w3.org/1999/xhtml", nodename);
       },
     };
 
     // Set `openContextMenu` this way so, `serviceContainer` variable
     // is available in the current scope and we can pass it into
     // `createContextMenu` method.