Bug 1530138 - Added resend option in WebConsole r=nchevobbe,Honza
authorChristoph Walcher <christoph-wa@gmx.de>
Tue, 14 May 2019 12:47:08 +0000
changeset 535691 dc20a50efcf1d6343019ab8cabe882698f2823fd
parent 535690 c9f1a1db8fa2190a88c09eb624b321a07da6c3e1
child 535692 e80a258b4aed3eb6599967b22a12e2b6314482d2
push id2082
push userffxbld-merge
push dateMon, 01 Jul 2019 08:34:18 +0000
treeherdermozilla-release@2fb19d0466d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnchevobbe, Honza
bugs1530138
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 1530138 - Added resend option in WebConsole r=nchevobbe,Honza Added resend option in WebConsole Differential Revision: https://phabricator.services.mozilla.com/D30779
devtools/client/locales/en-US/webconsole.properties
devtools/client/netmonitor/src/actions/batching.js
devtools/client/netmonitor/src/actions/requests.js
devtools/client/netmonitor/src/api.js
devtools/client/netmonitor/src/constants.js
devtools/client/netmonitor/src/middleware/batching.js
devtools/client/webconsole/components/message-types/NetworkEventMessage.js
devtools/client/webconsole/test/fixtures/serviceContainer.js
devtools/client/webconsole/test/mochitest/browser.ini
devtools/client/webconsole/test/mochitest/browser_webconsole_network_messages_resend_request.js
devtools/client/webconsole/utils/context-menu.js
devtools/client/webconsole/webconsole-wrapper.js
--- a/devtools/client/locales/en-US/webconsole.properties
+++ b/devtools/client/locales/en-US/webconsole.properties
@@ -162,16 +162,22 @@ webconsole.menu.openURL.label=Open URL i
 webconsole.menu.openURL.accesskey=T
 
 # LOCALIZATION NOTE (webconsole.menu.openInNetworkPanel.label)
 # Label used for a context-menu item displayed for network message logs. Clicking on it
 # opens the network message in the Network panel
 webconsole.menu.openInNetworkPanel.label=Open in Network Panel
 webconsole.menu.openInNetworkPanel.accesskey=N
 
+# LOCALIZATION NOTE (webconsole.menu.resendNetworkRequest.label)
+# Label used for a context-menu item displayed for network message logs. Clicking on it
+# resends the network request
+webconsole.menu.resendNetworkRequest.label=Resend Request
+webconsole.menu.resendNetworkRequest.accesskey=n
+
 # LOCALIZATION NOTE (webconsole.menu.storeAsGlobalVar.label)
 # Label used for a context-menu item displayed for object/variable logs. Clicking on it
 # creates a new global variable pointing to the logged variable.
 webconsole.menu.storeAsGlobalVar.label=Store as global variable
 webconsole.menu.storeAsGlobalVar.accesskey=S
 
 # LOCALIZATION NOTE (webconsole.menu.copyMessage.label)
 # Label used for a context-menu item displayed for any log. Clicking on it will copy the
--- a/devtools/client/netmonitor/src/actions/batching.js
+++ b/devtools/client/netmonitor/src/actions/batching.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
   BATCH_ACTIONS,
   BATCH_ENABLE,
   BATCH_RESET,
+  BATCH_FLUSH,
 } = require("../constants");
 
 /**
  * Process multiple actions at once as part of one dispatch, and produce only one
  * state update at the end. This action is not processed by any reducer, but by a
  * special store enhancer.
  */
 function batchActions(actions) {
@@ -30,13 +31,20 @@ function batchEnable(enabled) {
 }
 
 function batchReset() {
   return {
     type: BATCH_RESET,
   };
 }
 
+function batchFlush() {
+  return {
+    type: BATCH_FLUSH,
+  };
+}
+
 module.exports = {
   batchActions,
   batchEnable,
   batchReset,
+  batchFlush,
 };
--- a/devtools/client/netmonitor/src/actions/requests.js
+++ b/devtools/client/netmonitor/src/actions/requests.js
@@ -8,17 +8,17 @@ const {
   ADD_REQUEST,
   CLEAR_REQUESTS,
   CLONE_SELECTED_REQUEST,
   REMOVE_SELECTED_CUSTOM_REQUEST,
   SEND_CUSTOM_REQUEST,
   TOGGLE_RECORDING,
   UPDATE_REQUEST,
 } = require("../constants");
-const { getSelectedRequest } = require("../selectors/index");
+const { getSelectedRequest, getRequestById } = require("../selectors/index");
 
 function addRequest(id, data, batch) {
   return {
     type: ADD_REQUEST,
     id,
     data,
     meta: { batch },
   };
@@ -41,36 +41,41 @@ function cloneSelectedRequest() {
   return {
     type: CLONE_SELECTED_REQUEST,
   };
 }
 
 /**
  * Send a new HTTP request using the data in the custom request form.
  */
-function sendCustomRequest(connector) {
+function sendCustomRequest(connector, requestId = null) {
   return (dispatch, getState) => {
-    const selected = getSelectedRequest(getState());
+    let request;
+    if (requestId) {
+      request = getRequestById(getState(), requestId);
+    } else {
+      request = getSelectedRequest(getState());
+    }
 
-    if (!selected) {
+    if (!request) {
       return;
     }
 
     // Send a new HTTP request using the data in the custom request form
     const data = {
-      cause: selected.cause,
-      url: selected.url,
-      method: selected.method,
-      httpVersion: selected.httpVersion,
+      cause: request.cause,
+      url: request.url,
+      method: request.method,
+      httpVersion: request.httpVersion,
     };
-    if (selected.requestHeaders) {
-      data.headers = selected.requestHeaders.headers;
+    if (request.requestHeaders) {
+      data.headers = request.requestHeaders.headers;
     }
-    if (selected.requestPostData) {
-      data.body = selected.requestPostData.postData.text;
+    if (request.requestPostData) {
+      data.body = request.requestPostData.postData.text;
     }
 
     connector.sendHTTPRequest(data, (response) => {
       return dispatch({
         type: SEND_CUSTOM_REQUEST,
         id: response.eventActor.actor,
       });
     });
--- a/devtools/client/netmonitor/src/api.js
+++ b/devtools/client/netmonitor/src/api.js
@@ -198,11 +198,24 @@ NetMonitorAPI.prototype = {
     };
 
     this.harExportConnector = new Connector();
     this.harExportConnectorReady =
       this.connectBackend(this.harExportConnector, connection);
     await this.harExportConnectorReady;
     return this.harExportConnector;
   },
+
+  /**
+   * Resends a given network request
+   * @param {String} requestId
+   *        Id of the network request
+   */
+  resendRequest(requestId) {
+    // Flush queued requests.
+    this.store.dispatch(Actions.batchFlush());
+    // Send custom request with same url, headers and body as the request
+    // with the given requestId.
+    this.store.dispatch(Actions.sendCustomRequest(this.connector, requestId));
+  },
 };
 
 exports.NetMonitorAPI = NetMonitorAPI;
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -4,16 +4,17 @@
 
 "use strict";
 
 const actionTypes = {
   ADD_REQUEST: "ADD_REQUEST",
   ADD_TIMING_MARKER: "ADD_TIMING_MARKER",
   BATCH_ACTIONS: "BATCH_ACTIONS",
   BATCH_ENABLE: "BATCH_ENABLE",
+  BATCH_FLUSH: "BATCH_FLUSH",
   CLEAR_REQUESTS: "CLEAR_REQUESTS",
   CLEAR_TIMING_MARKERS: "CLEAR_TIMING_MARKERS",
   CLONE_SELECTED_REQUEST: "CLONE_SELECTED_REQUEST",
   ENABLE_REQUEST_FILTER_TYPE_ONLY: "ENABLE_REQUEST_FILTER_TYPE_ONLY",
   OPEN_NETWORK_DETAILS: "OPEN_NETWORK_DETAILS",
   RESIZE_NETWORK_DETAILS: "RESIZE_NETWORK_DETAILS",
   ENABLE_PERSISTENT_LOGS: "ENABLE_PERSISTENT_LOGS",
   DISABLE_BROWSER_CACHE: "DISABLE_BROWSER_CACHE",
--- a/devtools/client/netmonitor/src/middleware/batching.js
+++ b/devtools/client/netmonitor/src/middleware/batching.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { BATCH_ACTIONS, BATCH_ENABLE, BATCH_RESET } = require("../constants");
+const { BATCH_ACTIONS, BATCH_ENABLE, BATCH_RESET, BATCH_FLUSH } = require("../constants");
 
 const REQUESTS_REFRESH_RATE = 50; // ms
 
 /**
  * Middleware that watches for actions with a "batch = true" value in their meta field.
  * These actions are queued and dispatched as one batch after a timeout.
  * Special actions that are handled by this middleware:
  * - BATCH_ENABLE can be used to enable and disable the batching.
@@ -25,16 +25,20 @@ function batchingMiddleware(store) {
       if (action.type === BATCH_ENABLE) {
         return setEnabled(action.enabled);
       }
 
       if (action.type === BATCH_RESET) {
         return resetQueue();
       }
 
+      if (action.type === BATCH_FLUSH) {
+        return flushQueue();
+      }
+
       if (action.meta && action.meta.batch) {
         if (!enabled) {
           next(action);
           return Promise.resolve();
         }
 
         queuedActions.push(action);
 
@@ -61,16 +65,22 @@ function batchingMiddleware(store) {
       queuedActions = [];
 
       if (flushTask) {
         flushTask.cancel();
         flushTask = null;
       }
     }
 
+    function flushQueue() {
+      if (flushTask) {
+        flushTask.runNow();
+      }
+    }
+
     function flushActions() {
       const actions = queuedActions;
       queuedActions = [];
 
       next({
         type: BATCH_ACTIONS,
         actions,
       });
--- a/devtools/client/webconsole/components/message-types/NetworkEventMessage.js
+++ b/devtools/client/webconsole/components/message-types/NetworkEventMessage.js
@@ -22,16 +22,17 @@ const Services = require("Services");
 const isMacOS = Services.appinfo.OS === "Darwin";
 
 NetworkEventMessage.displayName = "NetworkEventMessage";
 
 NetworkEventMessage.propTypes = {
   message: PropTypes.object.isRequired,
   serviceContainer: PropTypes.shape({
     openNetworkPanel: PropTypes.func.isRequired,
+    resendNetworkRequest: PropTypes.func.isRequired,
   }),
   timestampsVisible: PropTypes.bool.isRequired,
   networkMessageUpdate: PropTypes.object.isRequired,
 };
 
 /**
  * This component is responsible for rendering network messages
  * in the Console panel.
--- a/devtools/client/webconsole/test/fixtures/serviceContainer.js
+++ b/devtools/client/webconsole/test/fixtures/serviceContainer.js
@@ -10,16 +10,17 @@ module.exports = {
   proxy: {
     client: {},
     releaseActor: actor => console.log("Release actor", actor),
   },
   onViewSourceInDebugger: () => {},
   onViewSourceInStyleEditor: () => {},
   onViewSourceInScratchpad: () => {},
   openNetworkPanel: () => {},
+  resendNetworkRequest: () => {},
   sourceMapService: {
     subscribe: () => {},
     originalPositionFor: () => {
       return new Promise(resolve => {
         resolve();
       });
     },
   },
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -335,16 +335,17 @@ skip-if = true #	Bug 1404382
 [browser_webconsole_message_categories.js]
 [browser_webconsole_multiple_windows_and_tabs.js]
 [browser_webconsole_network_attach.js]
 [browser_webconsole_network_exceptions.js]
 [browser_webconsole_network_messages_expand.js]
 skip-if = true  # Bug 1438979
 [browser_webconsole_network_message_ctrl_click.js]
 [browser_webconsole_network_messages_openinnet.js]
+[browser_webconsole_network_messages_resend_request.js]
 [browser_webconsole_network_messages_status_code.js]
 [browser_webconsole_network_requests_from_chrome.js]
 [browser_webconsole_network_reset_filter.js]
 [browser_webconsole_nodes_highlight.js]
 [browser_webconsole_nodes_select.js]
 [browser_webconsole_non_javascript_mime_warning.js]
 [browser_webconsole_non_javascript_mime_worker_error.js]
 [browser_webconsole_object_ctrl_click.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_network_messages_resend_request.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf8,Test that 'Resend Request' context menu " +
+                "item resends the selected request and select it in netmonitor panel.";
+
+const TEST_FILE = "test-network-request.html";
+const TEST_PATH = "http://example.com/browser/devtools/client/webconsole/" +
+                  "test/mochitest/";
+
+add_task(async function task() {
+  await pushPref("devtools.webconsole.filter.net", true);
+
+  const hud = await openNewTabAndConsole(TEST_URI);
+
+  const documentUrl = TEST_PATH + TEST_FILE;
+  await loadDocument(documentUrl);
+  info("Document loaded.");
+
+  await resendNetworkRequest(hud, documentUrl);
+});
+
+/**
+ * Resends a network request logged in the webconsole
+ *
+ * @param {Object} hud
+ * @param {String} url
+ *        URL of the request as logged in the netmonitor.
+ */
+async function resendNetworkRequest(hud, url) {
+  const message = await waitFor(() => findMessage(hud, url));
+
+  const menuPopup = await openContextMenu(hud, message);
+  const openResendRequestMenuItem =
+    menuPopup.querySelector("#console-menu-resend-network-request");
+  ok(openResendRequestMenuItem, "resend network request item is enabled");
+
+  // Wait for message containing the resent request url
+  const onNewRequestMessage = waitForMessage(hud, url);
+  openResendRequestMenuItem.click();
+  await onNewRequestMessage;
+
+  ok(true, "The resent request url is correct.");
+}
--- a/devtools/client/webconsole/utils/context-menu.js
+++ b/devtools/client/webconsole/utils/context-menu.js
@@ -79,16 +79,27 @@ function createContextMenu(webConsoleUI,
       id: "console-menu-open-in-network-panel",
       label: l10n.getStr("webconsole.menu.openInNetworkPanel.label"),
       accesskey: l10n.getStr("webconsole.menu.openInNetworkPanel.accesskey"),
       visible: source === MESSAGE_SOURCE.NETWORK,
       click: () => serviceContainer.openNetworkPanel(message.messageId),
     }));
   }
 
+  // Resend Network message.
+  if (serviceContainer.resendNetworkRequest && request) {
+    menu.append(new MenuItem({
+      id: "console-menu-resend-network-request",
+      label: l10n.getStr("webconsole.menu.resendNetworkRequest.label"),
+      accesskey: l10n.getStr("webconsole.menu.resendNetworkRequest.accesskey"),
+      visible: source === MESSAGE_SOURCE.NETWORK,
+      click: () => serviceContainer.resendNetworkRequest(message.messageId),
+    }));
+  }
+
   // Open URL in a new tab for a network request.
   menu.append(new MenuItem({
     id: "console-menu-open-url",
     label: l10n.getStr("webconsole.menu.openURL.label"),
     accesskey: l10n.getStr("webconsole.menu.openURL.accesskey"),
     visible: source === MESSAGE_SOURCE.NETWORK,
     click: () => {
       if (!request) {
--- a/devtools/client/webconsole/webconsole-wrapper.js
+++ b/devtools/client/webconsole/webconsole-wrapper.js
@@ -305,16 +305,21 @@ class WebConsoleWrapper {
                                        null, { "session_id": this.toolbox.sessionId }
             );
           }),
           openNetworkPanel: (requestId) => {
             return this.toolbox.selectTool("netmonitor").then((panel) => {
               return panel.panelWin.Netmonitor.inspectRequest(requestId);
             });
           },
+          resendNetworkRequest: (requestId) => {
+            return this.toolbox.getNetMonitorAPI().then((api) => {
+              return api.resendRequest(requestId);
+            });
+          },
           sourceMapService: this.toolbox ? this.toolbox.sourceMapURLService : null,
           highlightDomElement: async (grip, options = {}) => {
             await this.toolbox.initInspector();
             if (!this.toolbox.highlighter) {
               return null;
             }
             const nodeFront = await this.toolbox.walker.gripToNodeFront(grip);
             return this.toolbox.highlighter.highlight(nodeFront, options);