Bug 1359682 - tap thumbnail to open response sidebar panel draft
authorFred Lin <gasolin@mozilla.com>
Thu, 04 May 2017 10:32:40 +0800
changeset 572388 a81b6d30169d7bfbeaa5107adb80cc7514f21d56
parent 572162 b25ad0674afd563e888dc07981baa626e8d794db
child 627004 a21ea3409af83213eee7cdce48b1c647fb039706
push id57054
push userbmo:gasolin@mozilla.com
push dateThu, 04 May 2017 02:58:11 +0000
bugs1359682
milestone55.0a1
Bug 1359682 - tap thumbnail to open response sidebar panel MozReview-Commit-ID: LAms0jnBNqA
devtools/client/netmonitor/src/components/request-list-column-cause.js
devtools/client/netmonitor/src/components/request-list-column-domain.js
devtools/client/netmonitor/src/components/request-list-column-file.js
devtools/client/netmonitor/src/components/request-list-content.js
devtools/client/netmonitor/src/components/request-list-item.js
devtools/client/netmonitor/test/browser.ini
devtools/client/netmonitor/test/browser_net_thumbnail-click.js
--- a/devtools/client/netmonitor/src/components/request-list-column-cause.js
+++ b/devtools/client/netmonitor/src/components/request-list-column-cause.js
@@ -12,43 +12,43 @@ const {
 
 const { div } = DOM;
 
 const RequestListColumnCause = createClass({
   displayName: "RequestListColumnCause",
 
   propTypes: {
     item: PropTypes.object.isRequired,
-    onCauseBadgeClick: PropTypes.func.isRequired,
+    onCauseBadgeMouseDown: PropTypes.func.isRequired,
   },
 
   shouldComponentUpdate(nextProps) {
     return this.props.item.cause !== nextProps.item.cause;
   },
 
   render() {
     let {
       item: { cause },
-      onCauseBadgeClick,
+      onCauseBadgeMouseDown,
     } = this.props;
 
     let causeType = "unknown";
     let causeHasStack = false;
 
     if (cause) {
       // Legacy server might send a numeric value. Display it as "unknown"
       causeType = typeof cause.type === "string" ? cause.type : "unknown";
       causeHasStack = cause.stacktrace && cause.stacktrace.length > 0;
     }
 
     return (
       div({ className: "requests-list-column requests-list-cause", title: causeType },
         causeHasStack && div({
           className: "requests-list-cause-stack",
-          onClick: onCauseBadgeClick,
+          onMouseDown: onCauseBadgeMouseDown,
         }, "JS"),
         causeType
       )
     );
   }
 });
 
 module.exports = RequestListColumnCause;
--- a/devtools/client/netmonitor/src/components/request-list-column-domain.js
+++ b/devtools/client/netmonitor/src/components/request-list-column-domain.js
@@ -21,25 +21,25 @@ const UPDATED_DOMAIN_PROPS = [
   "urlDetails",
 ];
 
 const RequestListColumnDomain = createClass({
   displayName: "RequestListColumnDomain",
 
   propTypes: {
     item: PropTypes.object.isRequired,
-    onSecurityIconClick: PropTypes.func.isRequired,
+    onSecurityIconMouseDown: PropTypes.func.isRequired,
   },
 
   shouldComponentUpdate(nextProps) {
     return !propertiesEqual(UPDATED_DOMAIN_PROPS, this.props.item, nextProps.item);
   },
 
   render() {
-    let { item, onSecurityIconClick } = this.props;
+    let { item, onSecurityIconMouseDown } = this.props;
     let { remoteAddress, remotePort, securityState,
       urlDetails: { host, isLocal } } = item;
     let iconClassList = ["requests-security-state-icon"];
     let iconTitle;
     let title = host + (remoteAddress ?
       ` (${getFormattedIPAndPort(remoteAddress, remotePort)})` : "");
 
     if (isLocal) {
@@ -49,17 +49,17 @@ const RequestListColumnDomain = createCl
       iconClassList.push(`security-state-${securityState}`);
       iconTitle = L10N.getStr(`netmonitor.security.state.${securityState}`);
     }
 
     return (
       div({ className: "requests-list-column requests-list-domain", title },
         div({
           className: iconClassList.join(" "),
-          onMouseDown: onSecurityIconClick,
+          onMouseDown: onSecurityIconMouseDown,
           title: iconTitle,
         }),
         host,
       )
     );
   }
 });
 
--- a/devtools/client/netmonitor/src/components/request-list-column-file.js
+++ b/devtools/client/netmonitor/src/components/request-list-column-file.js
@@ -18,33 +18,38 @@ const UPDATED_FILE_PROPS = [
   "urlDetails",
 ];
 
 const RequestListColumnFile = createClass({
   displayName: "RequestListColumnFile",
 
   propTypes: {
     item: PropTypes.object.isRequired,
+    onThumbnailMouseDown: PropTypes.func.isRequired,
   },
 
   shouldComponentUpdate(nextProps) {
     return !propertiesEqual(UPDATED_FILE_PROPS, this.props.item, nextProps.item);
   },
 
   render() {
-    let { responseContentDataUri, urlDetails } = this.props.item;
+    let {
+      item: { responseContentDataUri, urlDetails },
+      onThumbnailMouseDown
+    } = this.props;
 
     return (
       div({
         className: "requests-list-column requests-list-file",
         title: urlDetails.unicodeUrl,
       },
         img({
           className: "requests-list-icon",
           src: responseContentDataUri,
+          onMouseDown: onThumbnailMouseDown,
         }),
         urlDetails.baseNameWithQuery
       )
     );
   }
 });
 
 module.exports = RequestListColumnFile;
--- a/devtools/client/netmonitor/src/components/request-list-content.js
+++ b/devtools/client/netmonitor/src/components/request-list-content.js
@@ -35,20 +35,21 @@ const RequestListContent = createClass({
   displayName: "RequestListContent",
 
   propTypes: {
     columns: PropTypes.object.isRequired,
     dispatch: PropTypes.func.isRequired,
     displayedRequests: PropTypes.object.isRequired,
     firstRequestStartedMillis: PropTypes.number.isRequired,
     fromCache: PropTypes.bool,
-    onCauseBadgeClick: PropTypes.func.isRequired,
+    onCauseBadgeMouseDown: PropTypes.func.isRequired,
     onItemMouseDown: PropTypes.func.isRequired,
-    onSecurityIconClick: PropTypes.func.isRequired,
+    onSecurityIconMouseDown: PropTypes.func.isRequired,
     onSelectDelta: PropTypes.func.isRequired,
+    onThumbnailMouseDown: PropTypes.func.isRequired,
     scale: PropTypes.number,
     selectedRequestId: PropTypes.string,
   },
 
   componentWillMount() {
     const { dispatch } = this.props;
     this.contextMenu = new RequestListContextMenu({
       cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
@@ -219,19 +220,20 @@ const RequestListContent = createClass({
     this.shouldScrollBottom = false;
   },
 
   render() {
     const {
       columns,
       displayedRequests,
       firstRequestStartedMillis,
-      onCauseBadgeClick,
+      onCauseBadgeMouseDown,
       onItemMouseDown,
-      onSecurityIconClick,
+      onSecurityIconMouseDown,
+      onThumbnailMouseDown,
       selectedRequestId,
     } = this.props;
 
     return (
       div({ className: "requests-list-wrapper"},
         div({ className: "requests-list-table"},
           div({
             ref: "contentEl",
@@ -245,18 +247,19 @@ const RequestListContent = createClass({
               columns,
               item,
               index,
               isSelected: item.id === selectedRequestId,
               key: item.id,
               onContextMenu: this.onContextMenu,
               onFocusedNodeChange: this.onFocusedNodeChange,
               onMouseDown: () => onItemMouseDown(item.id),
-              onCauseBadgeClick: () => onCauseBadgeClick(item.cause),
-              onSecurityIconClick: () => onSecurityIconClick(item.securityState),
+              onCauseBadgeMouseDown: () => onCauseBadgeMouseDown(item.cause),
+              onSecurityIconMouseDown: () => onSecurityIconMouseDown(item.securityState),
+              onThumbnailMouseDown: () => onThumbnailMouseDown(),
             }))
           )
         )
       )
     );
   },
 });
 
@@ -268,26 +271,33 @@ module.exports = connect(
     selectedRequestId: state.requests.selectedId,
     scale: getWaterfallScale(state),
   }),
   (dispatch) => ({
     dispatch,
     /**
      * A handler that opens the stack trace tab when a stack trace is available
      */
-    onCauseBadgeClick: (cause) => {
+    onCauseBadgeMouseDown: (cause) => {
       if (cause.stacktrace && cause.stacktrace.length > 0) {
         dispatch(Actions.selectDetailsPanelTab("stack-trace"));
       }
     },
     onItemMouseDown: (id) => dispatch(Actions.selectRequest(id)),
     /**
      * A handler that opens the security tab in the details view if secure or
      * broken security indicator is clicked.
      */
-    onSecurityIconClick: (securityState) => {
+    onSecurityIconMouseDown: (securityState) => {
       if (securityState && securityState !== "insecure") {
         dispatch(Actions.selectDetailsPanelTab("security"));
       }
     },
     onSelectDelta: (delta) => dispatch(Actions.selectDelta(delta)),
+    /**
+     * A handler that opens the response tab in the details view if
+     * the thumbnail is clicked.
+     */
+    onThumbnailMouseDown: () => {
+      dispatch(Actions.selectDetailsPanelTab("response"));
+    },
   }),
 )(RequestListContent);
--- a/devtools/client/netmonitor/src/components/request-list-item.js
+++ b/devtools/client/netmonitor/src/components/request-list-item.js
@@ -71,21 +71,22 @@ const RequestListItem = createClass({
 
   propTypes: {
     columns: PropTypes.object.isRequired,
     item: PropTypes.object.isRequired,
     index: PropTypes.number.isRequired,
     isSelected: PropTypes.bool.isRequired,
     firstRequestStartedMillis: PropTypes.number.isRequired,
     fromCache: PropTypes.bool,
-    onCauseBadgeClick: PropTypes.func.isRequired,
+    onCauseBadgeMouseDown: PropTypes.func.isRequired,
     onContextMenu: PropTypes.func.isRequired,
     onFocusedNodeChange: PropTypes.func,
     onMouseDown: PropTypes.func.isRequired,
-    onSecurityIconClick: PropTypes.func.isRequired,
+    onSecurityIconMouseDown: PropTypes.func.isRequired,
+    onThumbnailMouseDown: PropTypes.func.isRequired,
     waterfallWidth: PropTypes.number,
   },
 
   componentDidMount() {
     if (this.props.isSelected) {
       this.refs.listItem.focus();
     }
   },
@@ -110,18 +111,19 @@ const RequestListItem = createClass({
       columns,
       item,
       index,
       isSelected,
       firstRequestStartedMillis,
       fromCache,
       onContextMenu,
       onMouseDown,
-      onCauseBadgeClick,
-      onSecurityIconClick,
+      onCauseBadgeMouseDown,
+      onSecurityIconMouseDown,
+      onThumbnailMouseDown,
     } = this.props;
 
     let classList = ["request-list-item", index % 2 ? "odd" : "even"];
     isSelected && classList.push("selected");
     fromCache && classList.push("fromCache");
 
     return (
       div({
@@ -129,22 +131,22 @@ const RequestListItem = createClass({
         className: classList.join(" "),
         "data-id": item.id,
         tabIndex: 0,
         onContextMenu,
         onMouseDown,
       },
         columns.get("status") && RequestListColumnStatus({ item }),
         columns.get("method") && RequestListColumnMethod({ item }),
-        columns.get("file") && RequestListColumnFile({ item }),
+        columns.get("file") && RequestListColumnFile({ item, onThumbnailMouseDown }),
         columns.get("protocol") && RequestListColumnProtocol({ item }),
         columns.get("scheme") && RequestListColumnScheme({ item }),
-        columns.get("domain") && RequestListColumnDomain({ item, onSecurityIconClick }),
+        columns.get("domain") && RequestListColumnDomain({ item, onSecurityIconMouseDown }),
         columns.get("remoteip") && RequestListColumnRemoteIP({ item }),
-        columns.get("cause") && RequestListColumnCause({ item, onCauseBadgeClick }),
+        columns.get("cause") && RequestListColumnCause({ item, onCauseBadgeMouseDown }),
         columns.get("type") && RequestListColumnType({ item }),
         columns.get("cookies") && RequestListColumnCookies({ item }),
         columns.get("setCookies") && RequestListColumnSetCookies({ item }),
         columns.get("transferred") && RequestListColumnTransferredSize({ item }),
         columns.get("contentSize") && RequestListColumnContentSize({ item }),
         columns.get("waterfall") &&
           RequestListColumnWaterfall({ item, firstRequestStartedMillis }),
       )
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -154,13 +154,14 @@ skip-if = true # Bug 1258809
 [browser_net_simple-request.js]
 [browser_net_sort-01.js]
 [browser_net_sort-02.js]
 [browser_net_statistics-01.js]
 [browser_net_statistics-02.js]
 [browser_net_status-codes.js]
 [browser_net_streaming-response.js]
 [browser_net_throttle.js]
+[browser_net_thumbnail-click.js]
 [browser_net_timeline_ticks.js]
 skip-if = true # TODO: fix the test
 [browser_net_timing-division.js]
 [browser_net_truncate.js]
 [browser_net_persistent_logs.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_thumbnail-click.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test that clicking on the file thumbnail opens the response details tab.
+ */
+
+add_task(function* () {
+  let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
+  let { document } = monitor.panelWin;
+
+  yield performRequestsAndWait();
+
+  let wait = waitForDOM(document, "#response-panel");
+
+  let request = document.querySelectorAll(".request-list-item")[5];
+  let icon = request.querySelector(".requests-list-icon");
+
+  info("Clicking thumbnail of the sixth request.");
+  EventUtils.synthesizeMouseAtCenter(icon, {}, monitor.panelWin);
+
+  yield wait;
+
+  ok(document.querySelector("#response-tab[aria-selected=true]"),
+     "Response tab is selected.");
+  ok(document.querySelector(".response-image-box"),
+     "Response image preview is shown.");
+
+  yield teardown(monitor);
+
+  function* performRequestsAndWait() {
+    let onAllEvents = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
+    yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+      content.wrappedJSObject.performRequests();
+    });
+    yield onAllEvents;
+  }
+});