Bug 1532225 - Allow double-click RequestItem to perform request in new tab. r=Honza
authorLaphets <wenqing4@illinois.edu>
Thu, 18 Apr 2019 12:54:38 +0000
changeset 470071 65929c6f1931da9adb0b61e6d88b9f5acd927f51
parent 470070 a3dc461058c55ccfb3865ccb26c256ff7edb4896
child 470072 17cce14873205ea418028024254852a6d407a0b1
push id112843
push useraiakab@mozilla.com
push dateFri, 19 Apr 2019 09:50:22 +0000
treeherdermozilla-inbound@c06f27cbfe40 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersHonza
bugs1532225
milestone68.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 1532225 - Allow double-click RequestItem to perform request in new tab. r=Honza Differential Revision: https://phabricator.services.mozilla.com/D27720
devtools/client/netmonitor/src/components/RequestListContent.js
devtools/client/netmonitor/src/components/RequestListItem.js
devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
--- a/devtools/client/netmonitor/src/components/RequestListContent.js
+++ b/devtools/client/netmonitor/src/components/RequestListContent.js
@@ -13,16 +13,19 @@ const { HTMLTooltip } = require("devtool
 const Actions = require("../actions/index");
 const { formDataURI } = require("../utils/request-utils");
 const {
   getDisplayedRequests,
   getSelectedRequest,
   getWaterfallScale,
 } = require("../selectors/index");
 
+loader.lazyRequireGetter(this, "openRequestInTab",
+  "devtools/client/netmonitor/src/utils/firefox/open-request-in-tab",
+  true);
 loader.lazyGetter(this, "setImageTooltip", function() {
   return require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper")
     .setImageTooltip;
 });
 loader.lazyGetter(this, "getImageDimensions", function() {
   return require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper")
     .getImageDimensions;
 });
@@ -71,16 +74,18 @@ class RequestListContent extends Compone
 
   constructor(props) {
     super(props);
     this.isScrolledToBottom = this.isScrolledToBottom.bind(this);
     this.onHover = this.onHover.bind(this);
     this.onScroll = this.onScroll.bind(this);
     this.onResize = this.onResize.bind(this);
     this.onKeyDown = this.onKeyDown.bind(this);
+    this.openRequestInTab = this.openRequestInTab.bind(this);
+    this.onDoubleClick = this.onDoubleClick.bind(this);
     this.onContextMenu = this.onContextMenu.bind(this);
     this.onFocusedNodeChange = this.onFocusedNodeChange.bind(this);
   }
 
   componentWillMount() {
     this.tooltip = new HTMLTooltip(window.parent.document, { type: "arrow" });
     window.addEventListener("resize", this.onResize);
   }
@@ -232,32 +237,50 @@ class RequestListContent extends Compone
     if (delta) {
       // Prevent scrolling when pressing navigation keys.
       evt.preventDefault();
       evt.stopPropagation();
       this.props.onSelectDelta(delta);
     }
   }
 
+  /**
+   * Opens selected item in a new tab.
+   */
+  async openRequestInTab(id, url, requestHeaders, requestPostData) {
+    requestHeaders = requestHeaders ||
+      await this.props.connector.requestData(id, "requestHeaders");
+
+    requestPostData = requestPostData ||
+      await this.props.connector.requestData(id, "requestPostData");
+
+    openRequestInTab(url, requestHeaders, requestPostData);
+  }
+
+  onDoubleClick({ id, url, requestHeaders, requestPostData }) {
+    this.openRequestInTab(id, url, requestHeaders, requestPostData);
+  }
+
   onContextMenu(evt) {
     evt.preventDefault();
     const { selectedRequest, displayedRequests } = this.props;
 
     if (!this.contextMenu) {
       const {
         connector,
         cloneSelectedRequest,
         sendCustomRequest,
         openStatistics,
       } = this.props;
       this.contextMenu = new RequestListContextMenu({
         connector,
         cloneSelectedRequest,
         sendCustomRequest,
         openStatistics,
+        openRequestInTab: this.openRequestInTab,
       });
     }
 
     this.contextMenu.open(evt, selectedRequest, displayedRequests);
   }
 
   /**
    * If selection has just changed (by keyboard navigation), don't keep the list
@@ -304,16 +327,17 @@ class RequestListContent extends Compone
               connector,
               columns,
               item,
               index,
               isSelected: item.id === (selectedRequest && selectedRequest.id),
               key: item.id,
               onContextMenu: this.onContextMenu,
               onFocusedNodeChange: this.onFocusedNodeChange,
+              onDoubleClick: () => this.onDoubleClick(item),
               onMouseDown: () => onItemMouseDown(item.id),
               onCauseBadgeMouseDown: () => onCauseBadgeMouseDown(item.cause),
               onSecurityIconMouseDown: () => onSecurityIconMouseDown(item.securityState),
               onWaterfallMouseDown: () => onWaterfallMouseDown(),
               requestFilterTypes,
             }))
           ) // end of requests-list-row-group">
         )
--- a/devtools/client/netmonitor/src/components/RequestListItem.js
+++ b/devtools/client/netmonitor/src/components/RequestListItem.js
@@ -127,16 +127,17 @@ class RequestListItem extends Component 
       connector: PropTypes.object.isRequired,
       columns: PropTypes.object.isRequired,
       item: PropTypes.object.isRequired,
       index: PropTypes.number.isRequired,
       isSelected: PropTypes.bool.isRequired,
       firstRequestStartedMillis: PropTypes.number.isRequired,
       fromCache: PropTypes.bool,
       onCauseBadgeMouseDown: PropTypes.func.isRequired,
+      onDoubleClick: PropTypes.func.isRequired,
       onContextMenu: PropTypes.func.isRequired,
       onFocusedNodeChange: PropTypes.func,
       onMouseDown: PropTypes.func.isRequired,
       onSecurityIconMouseDown: PropTypes.func.isRequired,
       onWaterfallMouseDown: PropTypes.func.isRequired,
       requestFilterTypes: PropTypes.object.isRequired,
       waterfallWidth: PropTypes.number,
     };
@@ -187,16 +188,17 @@ class RequestListItem extends Component 
     const {
       connector,
       columns,
       item,
       index,
       isSelected,
       firstRequestStartedMillis,
       fromCache,
+      onDoubleClick,
       onContextMenu,
       onMouseDown,
       onCauseBadgeMouseDown,
       onSecurityIconMouseDown,
       onWaterfallMouseDown,
     } = this.props;
 
     const classList = ["request-list-item", index % 2 ? "odd" : "even"];
@@ -206,16 +208,17 @@ class RequestListItem extends Component 
     return (
       dom.tr({
         ref: "listItem",
         className: classList.join(" "),
         "data-id": item.id,
         tabIndex: 0,
         onContextMenu,
         onMouseDown,
+        onDoubleClick,
       },
         columns.status && RequestListColumnStatus({ item }),
         columns.method && RequestListColumnMethod({ item }),
         columns.domain && RequestListColumnDomain({
             item,
             onSecurityIconMouseDown,
         }),
         columns.file && RequestListColumnFile({ item }),
--- a/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
+++ b/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
@@ -13,17 +13,16 @@ const {
   getUrlBaseName,
   parseQueryString,
 } = require("../utils/request-utils");
 
 loader.lazyRequireGetter(this, "Curl", "devtools/client/shared/curl", true);
 loader.lazyRequireGetter(this, "saveAs", "devtools/client/shared/file-saver", true);
 loader.lazyRequireGetter(this, "copyString", "devtools/shared/platform/clipboard", true);
 loader.lazyRequireGetter(this, "showMenu", "devtools/client/shared/components/menu/utils", true);
-loader.lazyRequireGetter(this, "openRequestInTab", "devtools/client/netmonitor/src/utils/firefox/open-request-in-tab", true);
 loader.lazyRequireGetter(this, "HarMenuUtils", "devtools/client/netmonitor/src/har/har-menu-utils", true);
 
 class RequestListContextMenu {
   constructor(props) {
     this.props = props;
   }
 
   open(event, selectedRequest, requests) {
@@ -44,16 +43,17 @@ class RequestListContextMenu {
       responseContentAvailable,
       url,
     } = selectedRequest;
     const {
       connector,
       cloneSelectedRequest,
       sendCustomRequest,
       openStatistics,
+      openRequestInTab,
     } = this.props;
     const menu = [];
     const copySubmenu = [];
 
     copySubmenu.push({
       id: "request-list-context-copy-url",
       label: L10N.getStr("netmonitor.context.copyUrl"),
       accesskey: L10N.getStr("netmonitor.context.copyUrl.accesskey"),
@@ -197,17 +197,17 @@ class RequestListContextMenu {
       visible: copySubmenu.slice(15, 16).some((subMenu) => subMenu.visible),
     });
 
     menu.push({
       id: "request-list-context-newtab",
       label: L10N.getStr("netmonitor.context.newTab"),
       accesskey: L10N.getStr("netmonitor.context.newTab.accesskey"),
       visible: !!selectedRequest,
-      click: () => this.openRequestInTab(id, url, requestHeaders, requestPostData),
+      click: () => openRequestInTab(id, url, requestHeaders, requestPostData),
     });
 
     menu.push({
       id: "request-list-context-open-in-debugger",
       label: L10N.getStr("netmonitor.context.openInDebugger"),
       accesskey: L10N.getStr("netmonitor.context.openInDebugger.accesskey"),
       visible: !!(selectedRequest && mimeType && mimeType.includes("javascript")),
       click: () => this.openInDebugger(url),
@@ -233,29 +233,16 @@ class RequestListContextMenu {
 
     showMenu(menu, {
       screenX: event.screenX,
       screenY: event.screenY,
     });
   }
 
   /**
-   * Opens selected item in a new tab.
-   */
-  async openRequestInTab(id, url, requestHeaders, requestPostData) {
-    requestHeaders = requestHeaders ||
-      await this.props.connector.requestData(id, "requestHeaders");
-
-    requestPostData = requestPostData ||
-      await this.props.connector.requestData(id, "requestPostData");
-
-    openRequestInTab(url, requestHeaders, requestPostData);
-  }
-
-  /**
    * Opens selected item in the debugger
    */
   openInDebugger(url) {
     const toolbox = gDevTools.getToolbox(this.props.connector.getTabTarget());
     toolbox.viewSourceInDebugger(url, 0);
   }
 
   /**
--- a/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
+++ b/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /**
- * Tests if Open in new tab works.
+ * Tests if Open in new tab works by ContextMenu.
  */
 
 add_task(async function() {
   const { tab, monitor } = await initNetMonitor(OPEN_REQUEST_IN_TAB_URL);
   info("Starting test...");
 
   const { document, store, windowRequire } = monitor.panelWin;
   const contextMenuDoc = monitor.panelWin.parent.document;
@@ -18,36 +18,38 @@ add_task(async function() {
 
   store.dispatch(Actions.batchEnable(false));
 
   // Post data may be fetched by the Header panel,
   // so set the Timings panel as the new default.
   store.getState().ui.detailsPanelSelectedTab = "timings";
 
   // Open GET request in new tab
-  await performRequest("GET");
+  await performRequest(monitor, tab, "GET");
   newTab = await openLastRequestInTab();
   await checkTabResponse(newTab, "GET");
   gBrowser.removeCurrentTab();
 
   // Open POST request in new tab
-  await performRequest("POST", "application/x-www-form-urlencoded", "foo=bar&baz=42");
+  await performRequest(monitor, tab, "POST", "application/x-www-form-urlencoded",
+    "foo=bar&baz=42");
   newTab = await openLastRequestInTab();
   await checkTabResponse(newTab, "POST", "application/x-www-form-urlencoded",
     "foo=bar&amp;baz=42");
   gBrowser.removeCurrentTab();
 
   // Open POST application/json request in new tab
-  await performRequest("POST", "application/json", '{"foo":"bar"}');
+  await performRequest(monitor, tab, "POST", "application/json", '{"foo":"bar"}');
   newTab = await openLastRequestInTab();
   await checkTabResponse(newTab, "POST", "application/json", '{"foo":"bar"}');
   gBrowser.removeCurrentTab();
 
   await teardown(monitor);
 
+  // OpenLastRequestInTab by ContextMenu
   async function openLastRequestInTab() {
     const wait = waitForDOM(contextMenuDoc, "#request-list-context-newtab");
     const requestItems = document.querySelectorAll(".request-list-item");
     const lastRequest = requestItems[requestItems.length - 1];
     EventUtils.sendMouseEvent({ type: "mousedown" }, lastRequest);
     EventUtils.sendMouseEvent({ type: "contextmenu" }, lastRequest);
     await wait;
 
@@ -58,32 +60,90 @@ add_task(async function() {
     info("A new tab has been opened");
 
     const awaitedTab = gBrowser.selectedTab;
     await BrowserTestUtils.browserLoaded(awaitedTab.linkedBrowser);
     info("The tab load completed");
 
     return awaitedTab;
   }
+});
 
-  async function performRequest(method, contentType, payload) {
-    const wait = waitForNetworkEvents(monitor, 1);
-    await ContentTask.spawn(tab.linkedBrowser, [method, contentType, payload],
-      async function([method_, contentType_, payload_]) {
-        content.wrappedJSObject.performRequest(method_, contentType_, payload_);
-      }
-    );
-    await wait;
-    info("Performed request to test server");
-  }
+/**
+ * Tests if Open in new tab works by DoubleClick RequestItem.
+ */
+
+add_task(async function() {
+  const { tab, monitor } = await initNetMonitor(OPEN_REQUEST_IN_TAB_URL);
+  info("Starting test...");
+
+  const { document, store, windowRequire } = monitor.panelWin;
+  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+  let newTab;
+
+  store.dispatch(Actions.batchEnable(false));
+
+  // Post data may be fetched by the Header panel,
+  // so set the Timings panel as the new default.
+  store.getState().ui.detailsPanelSelectedTab = "timings";
+
+  // Open GET request in new tab
+  await performRequest(monitor, tab, "GET");
+  newTab = await openLastRequestInTab();
+  await checkTabResponse(newTab, "GET");
+  gBrowser.removeCurrentTab();
 
-  async function checkTabResponse(checkedTab, method, contentType, payload) {
-    await ContentTask.spawn(checkedTab.linkedBrowser, [method, contentType, payload],
-      async function([method_, contentType_, payload_]) {
-        const { body } = content.wrappedJSObject.document;
-        const expected = [method_, contentType_, payload_].join("\n");
-        info("Response from the server:" + body.innerHTML.replace(/\n/g, "\\n"));
-        ok(body.innerHTML.includes(expected),
-          "Tab method and data match original request");
-      }
-    );
+  // Open POST request in new tab
+  await performRequest(monitor, tab, "POST", "application/x-www-form-urlencoded",
+    "foo=bar&baz=42");
+  newTab = await openLastRequestInTab();
+  await checkTabResponse(newTab, "POST", "application/x-www-form-urlencoded",
+    "foo=bar&amp;baz=42");
+  gBrowser.removeCurrentTab();
+
+  // Open POST application/json request in new tab
+  await performRequest(monitor, tab, "POST", "application/json", '{"foo":"bar"}');
+  newTab = await openLastRequestInTab();
+  await checkTabResponse(newTab, "POST", "application/json", '{"foo":"bar"}');
+  gBrowser.removeCurrentTab();
+
+  await teardown(monitor);
+
+  // OpenLastRequestInTab by DoubleClick
+  async function openLastRequestInTab() {
+    const requestItems = document.querySelectorAll(".request-list-item");
+    const lastRequest = requestItems[requestItems.length - 1];
+
+    const onTabOpen = once(gBrowser.tabContainer, "TabOpen", false);
+    EventUtils.sendMouseEvent({ type: "dblclick" }, lastRequest);
+    await onTabOpen;
+    info("A new tab has been opened");
+
+    const awaitedTab = gBrowser.selectedTab;
+    await BrowserTestUtils.browserLoaded(awaitedTab.linkedBrowser);
+    info("The tab load completed");
+
+    return awaitedTab;
   }
 });
+
+async function performRequest(monitor, tab, method, contentType, payload) {
+  const wait = waitForNetworkEvents(monitor, 1);
+  await ContentTask.spawn(tab.linkedBrowser, [method, contentType, payload],
+    async function([method_, contentType_, payload_]) {
+      content.wrappedJSObject.performRequest(method_, contentType_, payload_);
+    }
+  );
+  await wait;
+  info("Performed request to test server");
+}
+
+async function checkTabResponse(checkedTab, method, contentType, payload) {
+  await ContentTask.spawn(checkedTab.linkedBrowser, [method, contentType, payload],
+    async function([method_, contentType_, payload_]) {
+      const { body } = content.wrappedJSObject.document;
+      const expected = [method_, contentType_, payload_].join("\n");
+      info("Response from the server:" + body.innerHTML.replace(/\n/g, "\\n"));
+      ok(body.innerHTML.includes(expected),
+        "Tab method and data match original request");
+    }
+  );
+}