Bug 1711463 - [devtools] Update EvaluationContextSelector items on DOCUMENT_EVENT resources. r=jdescottes.
authorNicolas Chevobbe <nchevobbe@mozilla.com>
Wed, 19 May 2021 16:58:07 +0000
changeset 580042 7f821d1b5ef846f1891a0471c2879c2082ad727a
parent 580041 8056959c90795b45693aced8d28c9b0040d1595b
child 580043 fbcd65f308c238b223ca2c48748362cd10c6564e
push id38476
push userdluca@mozilla.com
push dateWed, 19 May 2021 21:47:56 +0000
treeherdermozilla-central@1bbf4362b266 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdescottes
bugs1711463
milestone90.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 1711463 - [devtools] Update EvaluationContextSelector items on DOCUMENT_EVENT resources. r=jdescottes. This patch adds a `refreshTargets` action that is called from the toolbox when a dom-interactive DOCUMENT_EVENT is received. This action will only update a new state property, `lastTargetRefresh`, with the current timestamp, so the `EvaluationContextSelector` component will re-render; as the target information are directly retrieved from the target fronts, any changes in the title/url will be shown in the component. Differential Revision: https://phabricator.services.mozilla.com/D115205
devtools/client/framework/actions/targets.js
devtools/client/framework/reducers/targets.js
devtools/client/framework/toolbox.js
devtools/client/webconsole/components/Input/EvaluationContextSelector.js
devtools/client/webconsole/test/browser/browser_jsterm_evaluation_context_selector_targets_update.js
--- a/devtools/client/framework/actions/targets.js
+++ b/devtools/client/framework/actions/targets.js
@@ -16,13 +16,18 @@ function unregisterTarget(targetFront) {
  * @param {String} targetActorID: The actorID of the target we want to select.
  */
 function selectTarget(targetActorID) {
   return function({ dispatch, getState }) {
     dispatch({ type: "SELECT_TARGET", targetActorID });
   };
 }
 
+function refreshTargets() {
+  return { type: "REFRESH_TARGETS" };
+}
+
 module.exports = {
   registerTarget,
   unregisterTarget,
   selectTarget,
+  refreshTargets,
 };
--- a/devtools/client/framework/reducers/targets.js
+++ b/devtools/client/framework/reducers/targets.js
@@ -3,16 +3,20 @@
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 "use strict";
 
 const initialReducerState = {
   // Array of targetFront
   targets: [],
   // The selected targetFront instance
   selected: null,
+  // timestamp of the last time a target was updated (i.e. url/title was updated).
+  // This is used by the EvaluationContextSelector component to re-render the list of
+  // targets when the list itself did not change (no addition/removal)
+  lastTargetRefresh: Date.now(),
 };
 
 exports.reducer = targetsReducer;
 function targetsReducer(state = initialReducerState, action) {
   switch (action.type) {
     case "SELECT_TARGET": {
       const { targetActorID } = action;
 
@@ -35,33 +39,48 @@ function targetsReducer(state = initialR
 
     case "REGISTER_TARGET": {
       return {
         ...state,
         targets: [...state.targets, action.targetFront],
       };
     }
 
+    case "REFRESH_TARGETS": {
+      // The data _in_ targetFront was updated, so we only need to mutate the state,
+      // while keeping the same values.
+      return {
+        ...state,
+        lastTargetRefresh: Date.now(),
+      };
+    }
+
     case "UNREGISTER_TARGET": {
       const targets = state.targets.filter(
         target => target !== action.targetFront
       );
 
       let { selected } = state;
       if (selected === action.targetFront) {
         selected = null;
       }
 
       return { ...state, targets, selected };
     }
   }
   return state;
 }
 
-exports.getToolboxTargets = getToolboxTargets;
 function getToolboxTargets(state) {
   return state.targets.targets;
 }
 
-exports.getSelectedTarget = getSelectedTarget;
 function getSelectedTarget(state) {
   return state.targets.selected;
 }
+
+function getLastTargetRefresh(state) {
+  return state.targets.lastTargetRefresh;
+}
+
+exports.getToolboxTargets = getToolboxTargets;
+exports.getSelectedTarget = getSelectedTarget;
+exports.getLastTargetRefresh = getLastTargetRefresh;
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -57,18 +57,19 @@ loader.lazyRequireGetter(
   this,
   "createToolboxStore",
   "devtools/client/framework/store",
   true
 );
 loader.lazyRequireGetter(
   this,
   [
+    "refreshTargets",
+    "registerTarget",
     "registerWalkerListeners",
-    "registerTarget",
     "selectTarget",
     "unregisterTarget",
   ],
   "devtools/client/framework/actions/index",
   true
 );
 
 loader.lazyRequireGetter(
@@ -4314,30 +4315,34 @@ Toolbox.prototype = {
         // Reset the count on console.clear
         if (level === "clear") {
           errors = 0;
         }
       }
 
       if (
         resource.resourceType === this.resourceCommand.TYPES.DOCUMENT_EVENT &&
-        resource?.targetFront.isTopLevel &&
         !resource.isFrameSwitching &&
         // `url` is set on the targetFront when we receive dom-loading, and `title` when
         // `dom-interactive` is received. Here we're only updating the window title in
         // the "newer" event.
         resource.name === "dom-interactive"
       ) {
         // the targetFront title and url are update on dom-interactive, so delay refreshing
         // the host title a bit in order for the event listener in targetCommand to be
         // executed.
         setTimeout(() => {
-          this._refreshHostTitle();
+          // Update the EvaluationContext selector so url/title of targets can be updated
+          this.store.dispatch(refreshTargets());
+
+          if (resource.targetFront.isTopLevel) {
+            this._refreshHostTitle();
+            this._setDebugTargetData();
+          }
         }, 0);
-        this._setDebugTargetData();
       }
     }
 
     this.setErrorCount(errors);
   },
 
   _onResourceUpdated(resources) {
     let errors = this._errorCount || 0;
--- a/devtools/client/webconsole/components/Input/EvaluationContextSelector.js
+++ b/devtools/client/webconsole/components/Input/EvaluationContextSelector.js
@@ -43,28 +43,35 @@ loader.lazyGetter(this, "MenuList", func
 
 class EvaluationContextSelector extends Component {
   static get propTypes() {
     return {
       selectTarget: PropTypes.func.isRequired,
       updateInstantEvaluationResultForCurrentExpression:
         PropTypes.func.isRequired,
       selectedTarget: PropTypes.object,
+      lastTargetRefresh: PropTypes.number,
       targets: PropTypes.array,
       webConsoleUI: PropTypes.object.isRequired,
     };
   }
 
   shouldComponentUpdate(nextProps) {
     if (this.props.selectedTarget !== nextProps.selectedTarget) {
       return true;
     }
+
+    if (this.props.lastTargetRefresh !== nextProps.lastTargetRefresh) {
+      return true;
+    }
+
     if (this.props.targets.length !== nextProps.targets.length) {
       return true;
     }
+
     for (let i = 0; i < nextProps.targets.length; i++) {
       const target = this.props.targets[i];
       const nextTarget = nextProps.targets[i];
       if (target.url != nextTarget.url || target.name != nextTarget.name) {
         return true;
       }
     }
     return false;
@@ -201,16 +208,17 @@ class EvaluationContextSelector extends 
     );
   }
 }
 
 const toolboxConnected = connect(
   state => ({
     targets: targetSelectors.getToolboxTargets(state),
     selectedTarget: targetSelectors.getSelectedTarget(state),
+    lastTargetRefresh: targetSelectors.getLastTargetRefresh(state),
   }),
   dispatch => ({
     selectTarget: actorID => dispatch(frameworkActions.selectTarget(actorID)),
   }),
   undefined,
   { storeKey: "toolbox-store" }
 )(EvaluationContextSelector);
 
--- a/devtools/client/webconsole/test/browser/browser_jsterm_evaluation_context_selector_targets_update.js
+++ b/devtools/client/webconsole/test/browser/browser_jsterm_evaluation_context_selector_targets_update.js
@@ -57,37 +57,45 @@ add_task(async function() {
   info("Add another iframe");
   ContentTask.spawn(gBrowser.selectedBrowser, [IFRAME_PATH], function(path) {
     const iframe = content.document.createElement("iframe");
     iframe.src = `http://test1.example.org/${path}?id=iframe-2`;
     content.document.body.append(iframe);
   });
 
   // Wait until the new iframe is rendered in the context selector.
-  await waitFor(() => getContextSelectorItems(hud).length === 4);
+  await waitFor(() => {
+    const items = getContextSelectorItems(hud);
+    return (
+      items.length === 4 &&
+      items.some(el =>
+        el
+          .querySelector(".label")
+          ?.textContent.includes("iframe-2|test1.example.org")
+      )
+    );
+  });
 
   const expectedSecondIframeItem = {
-    // The title is set in a script, but we don't update the context selector entry (it
-    // should be "iframe-2|test1.example.org:80").
-    label: `http://test1.example.org/${IFRAME_PATH}?id=iframe-2`,
+    label: `iframe-2|test1.example.org`,
     tooltip: `http://test1.example.org/${IFRAME_PATH}?id=iframe-2`,
   };
 
   await checkContextSelectorMenu(hud, [
     {
       ...expectedTopItem,
       checked: true,
     },
     expectedSeparatorItem,
     {
-      ...expectedSecondIframeItem,
+      ...expectedFirstIframeItem,
       checked: false,
     },
     {
-      ...expectedFirstIframeItem,
+      ...expectedSecondIframeItem,
       checked: false,
     },
   ]);
 
   info("Select the first iframe");
   selectTargetInContextSelector(hud, expectedFirstIframeItem.label);
 
   await waitFor(() =>