Bug 1407561 - Lazy loading of tooltip text when user hovers the status column. r=gasolin
☠☠ backed out by 6b456d6a8752 ☠ ☠
authorabhinav <abhinav.koppula@gmail.com>
Tue, 31 Oct 2017 08:27:21 +0530
changeset 440620 38eb288306914a8a09733f1912a201e419635f49
parent 440615 3b95ef3233000cbee8c45135e801455293b5f9e2
child 440621 f47a6037f56577d433b0a899b2bbb6d9567a4019
push id8118
push userryanvm@gmail.com
push dateFri, 03 Nov 2017 00:38:34 +0000
treeherdermozilla-beta@1c336e874ae8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgasolin
bugs1407561
milestone58.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 1407561 - Lazy loading of tooltip text when user hovers the status column. r=gasolin MozReview-Commit-ID: BXP8B10Q5LT
devtools/client/netmonitor/src/components/RequestListColumnStatus.js
devtools/client/netmonitor/test/browser_net_brotli.js
devtools/client/netmonitor/test/browser_net_cached-status.js
devtools/client/netmonitor/test/browser_net_content-type.js
devtools/client/netmonitor/test/browser_net_cyrillic-01.js
devtools/client/netmonitor/test/browser_net_cyrillic-02.js
devtools/client/netmonitor/test/browser_net_filter-01.js
devtools/client/netmonitor/test/browser_net_filter-02.js
devtools/client/netmonitor/test/browser_net_filter-flags.js
devtools/client/netmonitor/test/browser_net_json-long.js
devtools/client/netmonitor/test/browser_net_json-malformed.js
devtools/client/netmonitor/test/browser_net_json_custom_mime.js
devtools/client/netmonitor/test/browser_net_json_text_mime.js
devtools/client/netmonitor/test/browser_net_jsonp.js
devtools/client/netmonitor/test/browser_net_large-response.js
devtools/client/netmonitor/test/browser_net_post-data-01.js
devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
devtools/client/netmonitor/test/browser_net_service-worker-status.js
devtools/client/netmonitor/test/browser_net_simple-request-data.js
devtools/client/netmonitor/test/browser_net_sort-01.js
devtools/client/netmonitor/test/browser_net_sort-02.js
devtools/client/netmonitor/test/browser_net_status-codes.js
devtools/client/netmonitor/test/browser_net_streaming-response.js
--- a/devtools/client/netmonitor/src/components/RequestListColumnStatus.js
+++ b/devtools/client/netmonitor/src/components/RequestListColumnStatus.js
@@ -28,47 +28,58 @@ class RequestListColumnStatus extends Co
     };
   }
 
   shouldComponentUpdate(nextProps) {
     return !propertiesEqual(UPDATED_STATUS_PROPS, this.props.item, nextProps.item);
   }
 
   render() {
-    let { fromCache, fromServiceWorker, status, statusText } = this.props.item;
-    let code, title;
+    let { item } = this.props;
+    let { fromCache, fromServiceWorker, status, statusText } = item;
+    let code;
 
     if (status) {
       if (fromCache) {
         code = "cached";
       } else if (fromServiceWorker) {
         code = "service worker";
       } else {
         code = status;
       }
-
-      if (statusText) {
-        if (fromCache && fromServiceWorker) {
-          title = L10N.getFormatStr("netmonitor.status.tooltip.cachedworker",
-            status, statusText);
-        } else if (fromCache) {
-          title = L10N.getFormatStr("netmonitor.status.tooltip.cached",
-            status, statusText);
-        } else if (fromServiceWorker) {
-          title = L10N.getFormatStr("netmonitor.status.tooltip.worker",
-            status, statusText);
-        } else {
-          title = L10N.getFormatStr("netmonitor.status.tooltip.simple",
-            status, statusText);
-        }
-      }
     }
 
     return (
-      div({ className: "requests-list-column requests-list-status", title },
-        div({ className: "requests-list-status-icon", "data-code": code }),
+      div({
+        className: "requests-list-column requests-list-status",
+        onMouseOver: function ({ target }) {
+          if (status && statusText && !target.title) {
+            target.title = getColumnTitle(item);
+          }
+        },
+      },
+      div({ className: "requests-list-status-icon", "data-code": code }),
         div({ className: "requests-list-status-code" }, status)
       )
     );
   }
 }
 
+function getColumnTitle(item) {
+  let { fromCache, fromServiceWorker, status, statusText } = item;
+  let title;
+  if (fromCache && fromServiceWorker) {
+    title = L10N.getFormatStr("netmonitor.status.tooltip.cachedworker",
+      status, statusText);
+  } else if (fromCache) {
+    title = L10N.getFormatStr("netmonitor.status.tooltip.cached",
+      status, statusText);
+  } else if (fromServiceWorker) {
+    title = L10N.getFormatStr("netmonitor.status.tooltip.worker",
+      status, statusText);
+  } else {
+    title = L10N.getFormatStr("netmonitor.status.tooltip.simple",
+      status, statusText);
+  }
+  return title;
+}
+
 module.exports = RequestListColumnStatus;
--- a/devtools/client/netmonitor/test/browser_net_brotli.js
+++ b/devtools/client/netmonitor/test/browser_net_brotli.js
@@ -26,16 +26,21 @@ add_task(function* () {
   store.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, BROTLI_REQUESTS);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
+  let requestItem = document.querySelector(".request-list-item");
+  let requestsListStatus = requestItem.querySelector(".requests-list-status");
+  EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+  yield waitUntil(() => requestsListStatus.title);
+
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState()).get(0),
     "GET", HTTPS_CONTENT_TYPE_SJS + "?fmt=br", {
       status: 200,
       statusText: "Connected",
       type: "plain",
--- a/devtools/client/netmonitor/test/browser_net_cached-status.js
+++ b/devtools/client/netmonitor/test/browser_net_cached-status.js
@@ -91,16 +91,22 @@ add_task(function* () {
   info("Performing requests #1...");
   yield performRequestsAndWait();
 
   info("Performing requests #2...");
   yield performRequestsAndWait();
 
   let index = 0;
   for (let request of REQUEST_DATA) {
+    let requestItem = document.querySelectorAll(".request-list-item")[index];
+    requestItem.scrollIntoView();
+    let requestsListStatus = requestItem.querySelector(".requests-list-status");
+    EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+    yield waitUntil(() => requestsListStatus.title);
+
     info("Verifying request #" + index);
     yield verifyRequestItemTarget(
       document,
       getDisplayedRequests(store.getState()),
       getSortedRequests(store.getState()).get(index),
       request.method,
       request.uri,
       request.details
--- a/devtools/client/netmonitor/test/browser_net_content-type.js
+++ b/devtools/client/netmonitor/test/browser_net_content-type.js
@@ -22,16 +22,23 @@ add_task(function* () {
   store.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
+  for (let requestItem of document.querySelectorAll(".request-list-item")) {
+    let requestsListStatus = requestItem.querySelector(".requests-list-status");
+    requestItem.scrollIntoView();
+    EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+    yield waitUntil(() => requestsListStatus.title);
+  }
+
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState()).get(0),
     "GET",
     CONTENT_TYPE_SJS + "?fmt=xml",
     {
       status: 200,
--- a/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
@@ -21,16 +21,22 @@ add_task(function* () {
   store.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
+  let requestItem = document.querySelectorAll(".request-list-item")[0];
+  let requestsListStatus = requestItem.querySelector(".requests-list-status");
+  requestItem.scrollIntoView();
+  EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+  yield waitUntil(() => requestsListStatus.title);
+
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState()).get(0),
     "GET",
     CONTENT_TYPE_SJS + "?fmt=txt",
     {
       status: 200,
--- a/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
@@ -17,16 +17,22 @@ add_task(function* () {
     getDisplayedRequests,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
 
   let wait = waitForNetworkEvents(monitor, 1);
   tab.linkedBrowser.reload();
   yield wait;
 
+  let requestItem = document.querySelectorAll(".request-list-item")[0];
+  let requestsListStatus = requestItem.querySelector(".requests-list-status");
+  requestItem.scrollIntoView();
+  EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+  yield waitUntil(() => requestsListStatus.title);
+
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState()).get(0),
     "GET",
     CYRILLIC_URL,
     {
       status: 200,
--- a/devtools/client/netmonitor/test/browser_net_filter-01.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-01.js
@@ -158,169 +158,177 @@ add_task(function* () {
     "There should be a selected item in the requests menu.");
   is(getSelectedIndex(store.getState()), 0,
     "The first item should be selected in the requests menu.");
   is(!!document.querySelector(".network-details-panel"), true,
     "The network details panel should render correctly.");
 
   // First test with single filters...
   testFilterButtons(monitor, "all");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-html-button"));
   testFilterButtons(monitor, "html");
-  testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Reset filters
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-css-button"));
   testFilterButtons(monitor, "css");
-  testContents([0, 1, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 1, 0, 0, 0, 0, 0, 0, 0]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-js-button"));
   testFilterButtons(monitor, "js");
-  testContents([0, 0, 1, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 1, 0, 0, 0, 0, 0, 0]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-xhr-button"));
   testFilterButtons(monitor, "xhr");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 0]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 0]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   EventUtils.sendMouseEvent({ type: "click" },
      document.querySelector(".requests-list-filter-fonts-button"));
   testFilterButtons(monitor, "fonts");
-  testContents([0, 0, 0, 1, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 1, 0, 0, 0, 0, 0]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-images-button"));
   testFilterButtons(monitor, "images");
-  testContents([0, 0, 0, 0, 1, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 0, 1, 0, 0, 0, 0]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-media-button"));
   testFilterButtons(monitor, "media");
-  testContents([0, 0, 0, 0, 0, 1, 1, 0, 0]);
+  yield testContents([0, 0, 0, 0, 0, 1, 1, 0, 0]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-flash-button"));
   testFilterButtons(monitor, "flash");
-  testContents([0, 0, 0, 0, 0, 0, 0, 1, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 1, 0]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-ws-button"));
   testFilterButtons(monitor, "ws");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 1]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 1]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
 
   testFilterButtons(monitor, "all");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   // Text in filter box that matches nothing should hide all.
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   setFreetextFilter("foobar");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Text in filter box that matches should filter out everything else.
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   setFreetextFilter("sample");
-  testContents([1, 1, 1, 0, 0, 0, 0, 0, 0]);
+  yield testContents([1, 1, 1, 0, 0, 0, 0, 0, 0]);
 
   // Text in filter box that matches should filter out everything else.
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   setFreetextFilter("SAMPLE");
-  testContents([1, 1, 1, 0, 0, 0, 0, 0, 0]);
+  yield testContents([1, 1, 1, 0, 0, 0, 0, 0, 0]);
 
   // Test negative filtering (only show unmatched items)
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   setFreetextFilter("-sample");
-  testContents([0, 0, 0, 1, 1, 1, 1, 1, 1]);
+  yield testContents([0, 0, 0, 1, 1, 1, 1, 1, 1]);
 
   // ...then combine multiple filters together.
 
   // Enable filtering for html and css; should show request of both type.
   setFreetextFilter("");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-html-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-css-button"));
   testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
-  testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
 
   // Html and css filter enabled and text filter should show just the html and css match.
   // Should not show both the items matching the button plus the items matching the text.
   setFreetextFilter("sample");
-  testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-flash-button"));
   setFreetextFilter("");
   testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0]);
-  testContents([1, 1, 0, 0, 0, 0, 0, 1, 0]);
+  yield testContents([1, 1, 0, 0, 0, 0, 0, 1, 0]);
 
   // Disable some filters. Only one left active.
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-css-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-flash-button"));
   testFilterButtons(monitor, "html");
-  testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Disable last active filter. Should toggle to all.
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-html-button"));
   testFilterButtons(monitor, "all");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   // Enable few filters and click on all. Only "all" should be checked.
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-html-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-css-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-ws-button"));
   testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 1]);
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   testFilterButtons(monitor, "all");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   yield teardown(monitor);
 
   function getSelectedIndex(state) {
     if (!state.requests.selectedId) {
       return -1;
     }
     return getSortedRequests(state).findIndex(r => r.id === state.requests.selectedId);
   }
 
-  function testContents(visibility) {
+  function* testContents(visibility) {
+    let requestItems = document.querySelectorAll(".request-list-item");
+    for (let requestItem of requestItems) {
+      requestItem.scrollIntoView();
+      let requestsListStatus = requestItem.querySelector(".requests-list-status");
+      EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+      yield waitUntil(() => requestsListStatus.title);
+    }
+
     isnot(getSelectedRequest(store.getState()), undefined,
       "There should still be a selected item after filtering.");
     is(getSelectedIndex(store.getState()), 0,
       "The first item should be still selected after filtering.");
 
     const items = getSortedRequests(store.getState());
     const visibleItems = getDisplayedRequests(store.getState());
 
--- a/devtools/client/netmonitor/test/browser_net_filter-02.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-02.js
@@ -157,60 +157,69 @@ add_task(function* () {
   isnot(getSelectedRequest(store.getState()), null,
     "There should be a selected item in the requests menu.");
   is(getSelectedIndex(store.getState()), 0,
     "The first item should be selected in the requests menu.");
   is(!!document.querySelector(".network-details-panel"), true,
     "The network details panel should be visible after toggle button was pressed.");
 
   testFilterButtons(monitor, "all");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   info("Testing html filtering.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-html-button"));
   testFilterButtons(monitor, "html");
-  testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   info("Performing more requests.");
   wait = waitForNetworkEvents(monitor, 9);
   yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
   yield wait;
 
   info("Testing html filtering again.");
   testFilterButtons(monitor, "html");
-  testContents([1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   info("Performing more requests.");
   wait = waitForNetworkEvents(monitor, 9);
   yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
   yield wait;
 
   info("Testing html filtering again.");
   testFilterButtons(monitor, "html");
-  testContents([1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
-                0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
+                      0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   info("Resetting filters.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".requests-list-filter-all-button"));
   testFilterButtons(monitor, "all");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                      1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   yield teardown(monitor);
 
   function getSelectedIndex(state) {
     if (!state.requests.selectedId) {
       return -1;
     }
     return getSortedRequests(state).findIndex(r => r.id === state.requests.selectedId);
   }
 
-  function testContents(visibility) {
+  function* testContents(visibility) {
+    let requestItems = document.querySelectorAll(".request-list-item");
+    for (let requestItem of requestItems) {
+      requestItem.scrollIntoView();
+      let requestsListStatus = requestItem.querySelector(".requests-list-status");
+      EventUtils.synthesizeMouse(requestsListStatus, 0, 0, { type: "mousemove" },
+        monitor.panelWin);
+      yield waitUntil(() => requestsListStatus.title != null);
+    }
+
     isnot(getSelectedRequest(store.getState()), null,
       "There should still be a selected item after filtering.");
     is(getSelectedIndex(store.getState()), 0,
       "The first item should be still selected after filtering.");
     is(!!document.querySelector(".network-details-panel"), true,
       "The network details panel should still be visible after filtering.");
 
     const items = getSortedRequests(store.getState());
--- a/devtools/client/netmonitor/test/browser_net_filter-flags.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-flags.js
@@ -153,200 +153,209 @@ add_task(function* () {
 
   let waitNetwork = waitForNetworkEvents(monitor, REQUESTS.length);
   loadCommonFrameScript();
   yield performRequestsInContent(REQUESTS);
   yield waitNetwork;
 
   // Test running flag once requests finish running
   setFreetextFilter("is:running");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Test cached flag
   setFreetextFilter("is:from-cache");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
 
   setFreetextFilter("is:cached");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
 
   // Test negative cached flag
   setFreetextFilter("-is:from-cache");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 0]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 0]);
 
   setFreetextFilter("-is:cached");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 0]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 0]);
 
   // Test status-code flag
   setFreetextFilter("status-code:200");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 0]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 0]);
 
   // Test status-code negative flag
   setFreetextFilter("-status-code:200");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
 
   // Test mime-type flag
   setFreetextFilter("mime-type:HtmL");
-  testContents([1, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([1, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Test mime-type negative flag
   setFreetextFilter("-mime-type:HtmL");
-  testContents([0, 0, 1, 1, 1, 1, 1, 1, 1, 1]);
+  yield testContents([0, 0, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   // Test method flag
   setFreetextFilter("method:get");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   // Test unmatched method flag
   setFreetextFilter("method:post");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Test scheme flag (all requests are http)
   setFreetextFilter("scheme:http");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   setFreetextFilter("scheme:https");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Test regex filter
   setFreetextFilter("regexp:content.*?Sam");
-  testContents([1, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([1, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Test set-cookie-name flag
   setFreetextFilter("set-cookie-name:name2");
-  testContents([0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   setFreetextFilter("set-cookie-name:not-existing");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Test set-cookie-value flag
   setFreetextFilter("set-cookie-value:value2");
-  testContents([0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   setFreetextFilter("set-cookie-value:not-existing");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Test set-cookie-domain flag
   setFreetextFilter("set-cookie-domain:.example.com");
-  testContents([0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   setFreetextFilter("set-cookie-domain:.foo.example.com");
-  testContents([0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   setFreetextFilter("set-cookie-domain:.not-existing.example.com");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Test size
   setFreetextFilter("size:-1");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   setFreetextFilter("size:0");
-  testContents([0, 0, 0, 0, 1, 1, 1, 1, 0, 1]);
+  yield testContents([0, 0, 0, 0, 1, 1, 1, 1, 0, 1]);
 
   setFreetextFilter("size:34");
-  testContents([0, 0, 1, 1, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 1, 1, 0, 0, 0, 0, 0, 0]);
 
   // Testing the lower bound
   setFreetextFilter("size:9.659k");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 1, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 1, 0]);
 
   // Testing the actual value
   setFreetextFilter("size:10989");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 1, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 1, 0]);
 
   // Testing the upper bound
   setFreetextFilter("size:11.804k");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 1, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 1, 0]);
 
   // Test transferred
   setFreetextFilter("transferred:200");
-  testContents([0, 0, 0, 0, 1, 1, 1, 1, 0, 0]);
+  yield testContents([0, 0, 0, 0, 1, 1, 1, 1, 0, 0]);
 
   setFreetextFilter("transferred:234");
-  testContents([1, 0, 1, 0, 0, 0, 0, 0, 0, 1]);
+  yield testContents([1, 0, 1, 0, 0, 0, 0, 0, 0, 1]);
 
   setFreetextFilter("transferred:248");
-  testContents([0, 0, 1, 1, 0, 0, 0, 0, 0, 1]);
+  yield testContents([0, 0, 1, 1, 0, 0, 0, 0, 0, 1]);
 
   // Test larger-than
   setFreetextFilter("larger-than:-1");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   setFreetextFilter("larger-than:0");
-  testContents([1, 1, 1, 1, 0, 0, 0, 0, 1, 0]);
+  yield testContents([1, 1, 1, 1, 0, 0, 0, 0, 1, 0]);
 
   setFreetextFilter("larger-than:33");
-  testContents([0, 0, 1, 1, 0, 0, 0, 0, 1, 0]);
+  yield testContents([0, 0, 1, 1, 0, 0, 0, 0, 1, 0]);
 
   setFreetextFilter("larger-than:34");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 1, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 1, 0]);
 
   setFreetextFilter("larger-than:10.73k");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 1, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 1, 0]);
 
   setFreetextFilter("larger-than:10.732k");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Test transferred-larger-than
   setFreetextFilter("transferred-larger-than:-1");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   setFreetextFilter("transferred-larger-than:214");
-  testContents([1, 1, 1, 1, 0, 0, 0, 0, 1, 1]);
+  yield testContents([1, 1, 1, 1, 0, 0, 0, 0, 1, 1]);
 
   setFreetextFilter("transferred-larger-than:247");
-  testContents([0, 1, 1, 1, 0, 0, 0, 0, 1, 0]);
+  yield testContents([0, 1, 1, 1, 0, 0, 0, 0, 1, 0]);
 
   setFreetextFilter("transferred-larger-than:248");
-  testContents([0, 1, 0, 1, 0, 0, 0, 0, 1, 0]);
+  yield testContents([0, 1, 0, 1, 0, 0, 0, 0, 1, 0]);
 
   setFreetextFilter("transferred-larger-than:10.73k");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Test cause
   setFreetextFilter("cause:xhr");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   setFreetextFilter("cause:script");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Test has-response-header
   setFreetextFilter("has-response-header:Content-Type");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   setFreetextFilter("has-response-header:Last-Modified");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Test remote-ip
   setFreetextFilter("remote-ip:127.0.0.1");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   setFreetextFilter("remote-ip:192.168.1.2");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Test domain
   setFreetextFilter("domain:example.com");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   setFreetextFilter("domain:wrongexample.com");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Test protocol
   setFreetextFilter("protocol:http/1");
-  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
+  yield testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   setFreetextFilter("protocol:http/2");
-  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
+  yield testContents([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
   // Test mixing flags
   setFreetextFilter("-mime-type:HtmL status-code:200");
-  testContents([0, 0, 1, 1, 1, 1, 1, 1, 1, 0]);
+  yield testContents([0, 0, 1, 1, 1, 1, 1, 1, 1, 0]);
 
   yield teardown(monitor);
 
-  function testContents(visibility) {
+  function* testContents(visibility) {
+    let requestItems = document.querySelectorAll(".request-list-item");
+    for (let requestItem of requestItems) {
+      requestItem.scrollIntoView();
+      let requestsListStatus = requestItem.querySelector(".requests-list-status");
+      EventUtils.synthesizeMouse(requestsListStatus, 0, 0, { type: "mousemove" },
+        monitor.panelWin);
+      yield waitUntil(() => requestsListStatus.title);
+    }
+
     const items = getSortedRequests(store.getState());
     const visibleItems = getDisplayedRequests(store.getState());
 
     is(items.size, visibility.length,
       "There should be a specific amount of items in the requests menu.");
     is(visibleItems.size, visibility.filter(e => e).length,
       "There should be a specific amount of visible items in the requests menu.");
 
--- a/devtools/client/netmonitor/test/browser_net_json-long.js
+++ b/devtools/client/netmonitor/test/browser_net_json-long.js
@@ -27,16 +27,21 @@ add_task(function* () {
   store.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
+  let requestItem = document.querySelector(".request-list-item");
+  let requestsListStatus = requestItem.querySelector(".requests-list-status");
+  EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+  yield waitUntil(() => requestsListStatus.title);
+
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState()).get(0),
     "GET",
     CONTENT_TYPE_SJS + "?fmt=json-long",
     {
       status: 200,
--- a/devtools/client/netmonitor/test/browser_net_json-malformed.js
+++ b/devtools/client/netmonitor/test/browser_net_json-malformed.js
@@ -22,16 +22,21 @@ add_task(function* () {
   store.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
+  let requestItem = document.querySelector(".request-list-item");
+  let requestsListStatus = requestItem.querySelector(".requests-list-status");
+  EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+  yield waitUntil(() => requestsListStatus.title);
+
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState()).get(0),
     "GET",
     CONTENT_TYPE_SJS + "?fmt=json-malformed",
     {
       status: 200,
--- a/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
@@ -22,16 +22,21 @@ add_task(function* () {
   store.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
+  let requestItem = document.querySelector(".request-list-item");
+  let requestsListStatus = requestItem.querySelector(".requests-list-status");
+  EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+  yield waitUntil(() => requestsListStatus.title);
+
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState()).get(0),
     "GET",
     CONTENT_TYPE_SJS + "?fmt=json-custom-mime",
     {
       status: 200,
--- a/devtools/client/netmonitor/test/browser_net_json_text_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_text_mime.js
@@ -23,16 +23,21 @@ add_task(function* () {
   store.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
+  let requestItem = document.querySelector(".request-list-item");
+  let requestsListStatus = requestItem.querySelector(".requests-list-status");
+  EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+  yield waitUntil(() => requestsListStatus.title);
+
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState()).get(0),
     "GET",
     CONTENT_TYPE_SJS + "?fmt=json-text-mime",
     {
       status: 200,
--- a/devtools/client/netmonitor/test/browser_net_jsonp.js
+++ b/devtools/client/netmonitor/test/browser_net_jsonp.js
@@ -23,16 +23,24 @@ add_task(function* () {
   store.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
+  let requestItems = document.querySelectorAll(".request-list-item");
+  for (let requestItem of requestItems) {
+    requestItem.scrollIntoView();
+    let requestsListStatus = requestItem.querySelector(".requests-list-status");
+    EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+    yield waitUntil(() => requestsListStatus.title);
+  }
+
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState()).get(0),
     "GET",
     CONTENT_TYPE_SJS + "?fmt=jsonp&jsonp=$_0123Fun",
     {
       status: 200,
--- a/devtools/client/netmonitor/test/browser_net_large-response.js
+++ b/devtools/client/netmonitor/test/browser_net_large-response.js
@@ -27,16 +27,22 @@ add_task(function* () {
   store.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, HTML_LONG_URL, function* (url) {
     content.wrappedJSObject.performRequests(1, url);
   });
   yield wait;
 
+  let requestItem = document.querySelector(".request-list-item");
+  requestItem.scrollIntoView();
+  let requestsListStatus = requestItem.querySelector(".requests-list-status");
+  EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+  yield waitUntil(() => requestsListStatus.title);
+
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState()).get(0),
     "GET",
     CONTENT_TYPE_SJS + "?fmt=html-long",
     {
       status: 200,
--- a/devtools/client/netmonitor/test/browser_net_post-data-01.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-01.js
@@ -26,16 +26,24 @@ add_task(function* () {
   store.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 0, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
+  let requestItems = document.querySelectorAll(".request-list-item");
+  for (let requestItem of requestItems) {
+    requestItem.scrollIntoView();
+    let requestsListStatus = requestItem.querySelector(".requests-list-status");
+    EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+    yield waitUntil(() => requestsListStatus.title);
+  }
+
   verifyRequestItemTarget(
     document,
     getDisplayedRequests(store.getState()),
     getSortedRequests(store.getState()).get(0),
     "POST",
     SIMPLE_SJS + "?foo=bar&baz=42&type=urlencoded",
     {
       status: 200,
--- a/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
+++ b/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
@@ -24,17 +24,17 @@ add_task(function* () {
 
   // Perform first batch of requests.
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  verifyRequest(0);
+  yield verifyRequest(0);
 
   // Switch to the webconsole.
   let onWebConsole = monitor.toolbox.once("webconsole-selected");
   monitor.toolbox.selectTool("webconsole");
   yield onWebConsole;
 
   // Switch back to the netmonitor.
   let onNetMonitor = monitor.toolbox.once("netmonitor-selected");
@@ -48,21 +48,28 @@ add_task(function* () {
 
   // Perform another batch of requests.
   wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  verifyRequest(1);
+  yield verifyRequest(1);
 
   return teardown(monitor);
 
-  function verifyRequest(index) {
+  function* verifyRequest(index) {
+    let requestItems = document.querySelectorAll(".request-list-item");
+    for (let requestItem of requestItems) {
+      requestItem.scrollIntoView();
+      let requestsListStatus = requestItem.querySelector(".requests-list-status");
+      EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+      yield waitUntil(() => requestsListStatus.title);
+    }
     verifyRequestItemTarget(
       document,
       getDisplayedRequests(store.getState()),
       getSortedRequests(store.getState()).get(index),
       "GET",
       CONTENT_TYPE_SJS + "?fmt=json-long",
       {
         status: 200,
--- a/devtools/client/netmonitor/test/browser_net_service-worker-status.js
+++ b/devtools/client/netmonitor/test/browser_net_service-worker-status.js
@@ -47,16 +47,24 @@ add_task(function* () {
 
   info("Performing requests...");
   let wait = waitForNetworkEvents(monitor, REQUEST_DATA.length);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
+  let requestItems = document.querySelectorAll(".request-list-item");
+  for (let requestItem of requestItems) {
+    requestItem.scrollIntoView();
+    let requestsListStatus = requestItem.querySelector(".requests-list-status");
+    EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+    yield waitUntil(() => requestsListStatus.title);
+  }
+
   let index = 0;
   for (let request of REQUEST_DATA) {
     let item = getSortedRequests(store.getState()).get(index);
 
     info(`Verifying request #${index}`);
     yield verifyRequestItemTarget(
       document,
       getDisplayedRequests(store.getState()),
--- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -226,16 +226,22 @@ function test() {
         "The httpVersion data has an incorrect value.");
       is(requestItem.status, "200",
         "The status data has an incorrect value.");
       is(requestItem.statusText, "Och Aye",
         "The statusText data has an incorrect value.");
       is(requestItem.headersSize, 330,
         "The headersSize data has an incorrect value.");
 
+      let requestListItem = document.querySelector(".request-list-item");
+      requestListItem.scrollIntoView();
+      let requestsListStatus = requestListItem.querySelector(".requests-list-status");
+      EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+      await waitUntil(() => requestsListStatus.title);
+
       verifyRequestItemTarget(
         document,
         getDisplayedRequests(store.getState()),
         requestItem,
         "GET",
         SIMPLE_SJS,
         {
           status: "200",
--- a/devtools/client/netmonitor/test/browser_net_sort-01.js
+++ b/devtools/client/netmonitor/test/browser_net_sort-01.js
@@ -57,59 +57,59 @@ add_task(function* () {
   isnot(getSelectedRequest(store.getState()), undefined,
     "There should be a selected item in the requests menu.");
   is(getSelectedIndex(store.getState()), 0,
     "The first item should be selected in the requests menu.");
   is(!!document.querySelector(".network-details-panel"), true,
     "The network details panel should be visible after toggle button was pressed.");
 
   testHeaders();
-  testContents([0, 2, 4, 3, 1], 0);
+  yield testContents([0, 2, 4, 3, 1], 0);
 
   info("Testing status sort, ascending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-status-button"));
   testHeaders("status", "ascending");
-  testContents([0, 1, 2, 3, 4], 0);
+  yield testContents([0, 1, 2, 3, 4], 0);
 
   info("Performing more requests.");
   wait = waitForNetworkEvents(monitor, 5);
   yield performRequestsInContent(requests);
   yield wait;
 
   info("Testing status sort again, ascending.");
   testHeaders("status", "ascending");
-  testContents([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0);
+  yield testContents([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0);
 
   info("Testing status sort, descending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-status-button"));
   testHeaders("status", "descending");
-  testContents([9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 9);
+  yield testContents([9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 9);
 
   info("Performing more requests.");
   wait = waitForNetworkEvents(monitor, 5);
   yield performRequestsInContent(requests);
   yield wait;
 
   info("Testing status sort again, descending.");
   testHeaders("status", "descending");
-  testContents([14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 14);
+  yield testContents([14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 14);
 
   info("Testing status sort yet again, ascending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-status-button"));
   testHeaders("status", "ascending");
-  testContents([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 0);
+  yield testContents([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 0);
 
   info("Testing status sort yet again, descending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-status-button"));
   testHeaders("status", "descending");
-  testContents([14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 14);
+  yield testContents([14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 14);
 
   return teardown(monitor);
 
   function testHeaders(sortType, direction) {
     let doc = monitor.panelWin.document;
     let target = doc.querySelector("#requests-list-" + sortType + "-button");
     let headers = doc.querySelectorAll(".requests-list-header-button");
 
@@ -136,31 +136,39 @@ add_task(function* () {
 
   function getSelectedIndex(state) {
     if (!state.requests.selectedId) {
       return -1;
     }
     return getSortedRequests(state).findIndex(r => r.id === state.requests.selectedId);
   }
 
-  function testContents(order, selection) {
+  function* testContents(order, selection) {
     isnot(getSelectedRequest(store.getState()), undefined,
       "There should still be a selected item after sorting.");
     is(getSelectedIndex(store.getState()), selection,
       "The first item should be still selected after sorting.");
     is(!!document.querySelector(".network-details-panel"), true,
       "The network details panel should still be visible after sorting.");
 
     is(getSortedRequests(store.getState()).length, order.length,
       "There should be a specific number of items in the requests menu.");
     is(getDisplayedRequests(store.getState()).length, order.length,
       "There should be a specific number of visbile items in the requests menu.");
     is(document.querySelectorAll(".request-list-item").length, order.length,
       "The visible items in the requests menu are, in fact, visible!");
 
+    let requestItems = document.querySelectorAll(".request-list-item");
+    for (let requestItem of requestItems) {
+      requestItem.scrollIntoView();
+      let requestsListStatus = requestItem.querySelector(".requests-list-status");
+      EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+      yield waitUntil(() => requestsListStatus.title);
+    }
+
     for (let i = 0, len = order.length / 5; i < len; i++) {
       verifyRequestItemTarget(
         document,
         getDisplayedRequests(store.getState()),
         getSortedRequests(store.getState()).get(order[i]),
         "GET1", SORTING_SJS + "?index=1", {
           fuzzyUrl: true,
           status: 101,
--- a/devtools/client/netmonitor/test/browser_net_sort-02.js
+++ b/devtools/client/netmonitor/test/browser_net_sort-02.js
@@ -57,143 +57,143 @@ add_task(function* () {
   isnot(getSelectedRequest(store.getState()), undefined,
     "There should be a selected item in the requests menu.");
   is(getSelectedIndex(store.getState()), 0,
     "The first item should be selected in the requests menu.");
   is(!!document.querySelector(".network-details-panel"), true,
     "The network details panel should be visible after toggle button was pressed.");
 
   testHeaders();
-  testContents([0, 2, 4, 3, 1]);
+  yield testContents([0, 2, 4, 3, 1]);
 
   info("Testing status sort, ascending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-status-button"));
   testHeaders("status", "ascending");
-  testContents([0, 1, 2, 3, 4]);
+  yield testContents([0, 1, 2, 3, 4]);
 
   info("Testing status sort, descending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-status-button"));
   testHeaders("status", "descending");
-  testContents([4, 3, 2, 1, 0]);
+  yield testContents([4, 3, 2, 1, 0]);
 
   info("Testing status sort, ascending. Checking sort loops correctly.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-status-button"));
   testHeaders("status", "ascending");
-  testContents([0, 1, 2, 3, 4]);
+  yield testContents([0, 1, 2, 3, 4]);
 
   info("Testing method sort, ascending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-method-button"));
   testHeaders("method", "ascending");
-  testContents([0, 1, 2, 3, 4]);
+  yield testContents([0, 1, 2, 3, 4]);
 
   info("Testing method sort, descending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-method-button"));
   testHeaders("method", "descending");
-  testContents([4, 3, 2, 1, 0]);
+  yield testContents([4, 3, 2, 1, 0]);
 
   info("Testing method sort, ascending. Checking sort loops correctly.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-method-button"));
   testHeaders("method", "ascending");
-  testContents([0, 1, 2, 3, 4]);
+  yield testContents([0, 1, 2, 3, 4]);
 
   info("Testing file sort, ascending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-file-button"));
   testHeaders("file", "ascending");
-  testContents([0, 1, 2, 3, 4]);
+  yield testContents([0, 1, 2, 3, 4]);
 
   info("Testing file sort, descending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-file-button"));
   testHeaders("file", "descending");
-  testContents([4, 3, 2, 1, 0]);
+  yield testContents([4, 3, 2, 1, 0]);
 
   info("Testing file sort, ascending. Checking sort loops correctly.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-file-button"));
   testHeaders("file", "ascending");
-  testContents([0, 1, 2, 3, 4]);
+  yield testContents([0, 1, 2, 3, 4]);
 
   info("Testing type sort, ascending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-type-button"));
   testHeaders("type", "ascending");
-  testContents([0, 1, 2, 3, 4]);
+  yield testContents([0, 1, 2, 3, 4]);
 
   info("Testing type sort, descending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-type-button"));
   testHeaders("type", "descending");
-  testContents([4, 3, 2, 1, 0]);
+  yield testContents([4, 3, 2, 1, 0]);
 
   info("Testing type sort, ascending. Checking sort loops correctly.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-type-button"));
   testHeaders("type", "ascending");
-  testContents([0, 1, 2, 3, 4]);
+  yield testContents([0, 1, 2, 3, 4]);
 
   info("Testing transferred sort, ascending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-transferred-button"));
   testHeaders("transferred", "ascending");
-  testContents([0, 1, 2, 3, 4]);
+  yield testContents([0, 1, 2, 3, 4]);
 
   info("Testing transferred sort, descending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-transferred-button"));
   testHeaders("transferred", "descending");
-  testContents([4, 3, 2, 1, 0]);
+  yield testContents([4, 3, 2, 1, 0]);
 
   info("Testing transferred sort, ascending. Checking sort loops correctly.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-transferred-button"));
   testHeaders("transferred", "ascending");
-  testContents([0, 1, 2, 3, 4]);
+  yield testContents([0, 1, 2, 3, 4]);
 
   info("Testing size sort, ascending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-contentSize-button"));
   testHeaders("contentSize", "ascending");
-  testContents([0, 1, 2, 3, 4]);
+  yield testContents([0, 1, 2, 3, 4]);
 
   info("Testing size sort, descending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-contentSize-button"));
   testHeaders("contentSize", "descending");
-  testContents([4, 3, 2, 1, 0]);
+  yield testContents([4, 3, 2, 1, 0]);
 
   info("Testing size sort, ascending. Checking sort loops correctly.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-contentSize-button"));
   testHeaders("contentSize", "ascending");
-  testContents([0, 1, 2, 3, 4]);
+  yield testContents([0, 1, 2, 3, 4]);
 
   info("Testing waterfall sort, ascending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-waterfall-button"));
   testHeaders("waterfall", "ascending");
-  testContents([0, 2, 4, 3, 1]);
+  yield testContents([0, 2, 4, 3, 1]);
 
   info("Testing waterfall sort, descending.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-waterfall-button"));
   testHeaders("waterfall", "descending");
-  testContents([4, 2, 0, 1, 3]);
+  yield testContents([4, 2, 0, 1, 3]);
 
   info("Testing waterfall sort, ascending. Checking sort loops correctly.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-list-waterfall-button"));
   testHeaders("waterfall", "ascending");
-  testContents([0, 2, 4, 3, 1]);
+  yield testContents([0, 2, 4, 3, 1]);
 
   return teardown(monitor);
 
   function getSelectedIndex(state) {
     if (!state.requests.selectedId) {
       return -1;
     }
     return getSortedRequests(state).findIndex(r => r.id === state.requests.selectedId);
@@ -220,31 +220,39 @@ add_task(function* () {
           : L10N.getStr("networkMenu.sortedDesc");
         ok(header.getAttribute("title").includes(sorted),
           "The " + header.id +
           " header includes the used sorting in the 'title' attribute.");
       }
     }
   }
 
-  function testContents([a, b, c, d, e]) {
+  function* testContents([a, b, c, d, e]) {
     isnot(getSelectedRequest(store.getState()), undefined,
       "There should still be a selected item after sorting.");
     is(getSelectedIndex(store.getState()), a,
       "The first item should be still selected after sorting.");
     is(!!document.querySelector(".network-details-panel"), true,
       "The network details panel should still be visible after sorting.");
 
     is(getSortedRequests(store.getState()).length, 5,
       "There should be a total of 5 items in the requests menu.");
     is(getDisplayedRequests(store.getState()).length, 5,
       "There should be a total of 5 visible items in the requests menu.");
     is(document.querySelectorAll(".request-list-item").length, 5,
       "The visible items in the requests menu are, in fact, visible!");
 
+    let requestItems = document.querySelectorAll(".request-list-item");
+    for (let requestItem of requestItems) {
+      requestItem.scrollIntoView();
+      let requestsListStatus = requestItem.querySelector(".requests-list-status");
+      EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+      yield waitUntil(() => requestsListStatus.title);
+    }
+
     verifyRequestItemTarget(
       document,
       getDisplayedRequests(store.getState()),
       getSortedRequests(store.getState()).get(a),
       "GET1", SORTING_SJS + "?index=1", {
         fuzzyUrl: true,
         status: 101,
         statusText: "Meh",
--- a/devtools/client/netmonitor/test/browser_net_status-codes.js
+++ b/devtools/client/netmonitor/test/browser_net_status-codes.js
@@ -106,16 +106,24 @@ add_task(function* () {
 
   return teardown(monitor);
 
   /**
    * A helper that verifies all requests show the correct information and caches
    * request list items to requestItems array.
    */
   function* verifyRequests() {
+    let requestListItems = document.querySelectorAll(".request-list-item");
+    for (let requestItem of requestListItems) {
+      requestItem.scrollIntoView();
+      let requestsListStatus = requestItem.querySelector(".requests-list-status");
+      EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+      yield waitUntil(() => requestsListStatus.title);
+    }
+
     info("Verifying requests contain correct information.");
     let index = 0;
     for (let request of REQUEST_DATA) {
       let item = getSortedRequests(store.getState()).get(index);
       requestItems[index] = item;
 
       info("Verifying request #" + index);
       yield verifyRequestItemTarget(
--- a/devtools/client/netmonitor/test/browser_net_streaming-response.js
+++ b/devtools/client/netmonitor/test/browser_net_streaming-response.js
@@ -30,16 +30,24 @@ add_task(function* () {
   for (let [fmt] of REQUESTS) {
     let url = CONTENT_TYPE_SJS + "?fmt=" + fmt;
     yield ContentTask.spawn(tab.linkedBrowser, { url }, function* (args) {
       content.wrappedJSObject.performRequests(1, args.url);
     });
   }
   yield wait;
 
+  let requestItems = document.querySelectorAll(".request-list-item");
+  for (let requestItem of requestItems) {
+    requestItem.scrollIntoView();
+    let requestsListStatus = requestItem.querySelector(".requests-list-status");
+    EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
+    yield waitUntil(() => requestsListStatus.title);
+  }
+
   REQUESTS.forEach(([ fmt ], i) => {
     verifyRequestItemTarget(
       document,
       getDisplayedRequests(store.getState()),
       getSortedRequests(store.getState()).get(i),
       "GET",
       CONTENT_TYPE_SJS + "?fmt=" + fmt,
       {