Bug 1550030 - Part 1: Implement the DOM mutation breakpoint context menu items in the markup view. r=loganfsmyth,jdescottes
authorGabriel Luong <gabriel.luong@gmail.com>
Wed, 26 Jun 2019 15:16:22 -0400
changeset 480556 1fb51bb3f412c4d68c9f8c40b19657d37908e75e
parent 480555 274e4c25f128c96bc200e64fad31d1fbe938f781
child 480626 f92db7c15e5b608d68267be722ce5df84e1590c0
push id113557
push usergabriel.luong@gmail.com
push dateFri, 28 Jun 2019 21:26:09 +0000
treeherdermozilla-inbound@1fb51bb3f412 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersloganfsmyth, jdescottes
bugs1550030
milestone69.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 1550030 - Part 1: Implement the DOM mutation breakpoint context menu items in the markup view. r=loganfsmyth,jdescottes This implements the context menu items for the DOM mutation breakpoint. In addition, there were some server changes to: - Update the mutationBreakpoints form for the NodeActor - Expose the mutationBreakpoints form - Moved the setMutationBreakpoints method from the Node spec to Walker spec since the Node spec only consisted of getter methods. It made more sense that the setter went into the Walker spec to be more consistent with how the Walker and Node spec have been arranged. Unit tests will be followed up in Part 2 immediately. Differential Revision: https://phabricator.services.mozilla.com/D36074
devtools/client/inspector/markup/markup-context-menu.js
devtools/client/inspector/markup/markup.js
devtools/client/locales/en-US/inspector.properties
devtools/client/preferences/devtools-client.js
devtools/server/actors/inspector/node.js
devtools/server/actors/inspector/walker.js
devtools/shared/fronts/node.js
devtools/shared/specs/inspector.js
devtools/shared/specs/node.js
--- a/devtools/client/inspector/markup/markup-context-menu.js
+++ b/devtools/client/inspector/markup/markup-context-menu.js
@@ -468,16 +468,47 @@ class MarkupContextMenu {
       disabled: !isSelectionElement || !markupContainer ||
                 !markupContainer.isPreviewable(),
       click: () => this._copyImageDataUri(),
     }));
 
     return copySubmenu;
   }
 
+  _getDOMBreakpointSubmenu(isSelectionElement) {
+    const menu = new Menu();
+    const mutationBreakpoints = this.selection.nodeFront.mutationBreakpoints;
+
+    menu.append(new MenuItem({
+      checked: mutationBreakpoints.subtree,
+      click: () => this.markup.toggleMutationBreakpoint("subtree"),
+      disabled: !isSelectionElement,
+      label: INSPECTOR_L10N.getStr("inspectorSubtreeModification.label"),
+      type: "checkbox",
+    }));
+
+    menu.append(new MenuItem({
+      checked: mutationBreakpoints.attribute,
+      click: () => this.markup.toggleMutationBreakpoint("attribute"),
+      disabled: !isSelectionElement,
+      label: INSPECTOR_L10N.getStr("inspectorAttributeModification.label"),
+      type: "checkbox",
+    }));
+
+    menu.append(new MenuItem({
+      checked: mutationBreakpoints.removal,
+      click: () => this.markup.toggleMutationBreakpoint("removal"),
+      disabled: !isSelectionElement,
+      label: INSPECTOR_L10N.getStr("inspectorNodeRemoval.label"),
+      type: "checkbox",
+    }));
+
+    return menu;
+  }
+
   /**
    * Link menu items can be shown or hidden depending on the context and
    * selected node, and their labels can vary.
    *
    * @return {Array} list of visible menu items related to links.
    */
   _getNodeLinkMenuItems() {
     const linkFollow = new MenuItem({
@@ -668,16 +699,24 @@ class MarkupContextMenu {
         INSPECTOR_L10N.getStr("inspectorAttributesSubmenu.accesskey"),
       submenu: this._getAttributesSubmenu(isEditableElement),
     }));
 
     menu.append(new MenuItem({
       type: "separator",
     }));
 
+    if (Services.prefs.getBoolPref("devtools.markup.mutationBreakpoints.enabled") &&
+        this.selection.nodeFront.mutationBreakpoints) {
+      menu.append(new MenuItem({
+        label: INSPECTOR_L10N.getStr("inspectorBreakpointSubmenu.label"),
+        submenu: this._getDOMBreakpointSubmenu(isSelectionElement),
+      }));
+    }
+
     menu.append(new MenuItem({
       id: "node-menu-useinconsole",
       label: INSPECTOR_L10N.getStr("inspectorUseInConsole.label"),
       click: () => this._useInConsole(),
     }));
 
     menu.append(new MenuItem({
       id: "node-menu-showdomproperties",
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -1131,16 +1131,28 @@ MarkupView.prototype = {
   scrollNodeIntoView() {
     if (!this.inspector.selection.isNode()) {
       return;
     }
 
     this.inspector.selection.nodeFront.scrollIntoView();
   },
 
+  async toggleMutationBreakpoint(name) {
+    if (!this.inspector.selection.isElementNode()) {
+      return;
+    }
+
+    const nodeFront = this.inspector.selection.nodeFront;
+    const mutationBreakpoints = nodeFront.mutationBreakpoints;
+    await this.walker.setMutationBreakpoints(nodeFront, {
+      [name]: !mutationBreakpoints[name],
+    });
+  },
+
   /**
    * If an editable item is focused, select its container.
    */
   _onFocus: function(event) {
     let parent = event.target;
     while (!parent.container) {
       parent = parent.parentNode;
     }
--- a/devtools/client/locales/en-US/inspector.properties
+++ b/devtools/client/locales/en-US/inspector.properties
@@ -289,16 +289,35 @@ inspectorAttributesSubmenu.accesskey=A
 # to current node
 inspectorAddAttribute.label=Add Attribute
 inspectorAddAttribute.accesskey=A
 
 # LOCALIZATION NOTE (inspectorPseudoClassSubmenu.label): This is the label
 # shown in the inspector contextual-menu for the sub-menu of the pseudo-classes.
 inspectorPseudoClassSubmenu.label=Change Pseudo-class
 
+# LOCALIZATION NOTE (inspectorBreakpointSubmenu.label): This is the label
+# shown in the inspector contextual-menu for the sub-menu of the DOM breakpoints.
+inspectorBreakpointSubmenu.label=Break on…
+
+# LOCALIZATION NOTE (inspectorSubtreeModification.label): This is the label shown
+# in the inspector contextual-menu for the item that lets users add a DOM breakpoint
+# for subtree modification.
+inspectorSubtreeModification.label=Subtree Modification
+
+# LOCALIZATION NOTE (inspectorAttributeModification.label): This is the label shown
+# in the inspector contextual-menu for the item that lets users add a DOM breakpoint
+# for attribute modification.
+inspectorAttributeModification.label=Attribute Modification
+
+# LOCALIZATION NOTE (inspectorNodeRemoval.label): This is the label shown
+# in the inspector contextual-menu for the item that lets users add a DOM breakpoint
+# for node removal.
+inspectorNodeRemoval.label=Node Removal
+
 # LOCALIZATION NOTE (inspectorSearchHTML.label3): This is the label that is
 # shown as the placeholder for the markup view search in the inspector.
 inspectorSearchHTML.label3=Search HTML
 
 # LOCALIZATION NOTE (inspectorImageDataUri.label): This is the label
 # shown in the inspector contextual-menu for the item that lets users copy
 # the URL embedding the image data encoded in Base 64 (what we name
 # here Image Data URL). For more information:
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -76,22 +76,22 @@ pref("devtools.layout.boxmodel.highlight
 pref("devtools.layout.boxmodel.highlightProperty", false);
 #endif
 
 // By how many times eyedropper will magnify pixels
 pref("devtools.eyedropper.zoom", 6);
 
 // Enable to collapse attributes that are too long.
 pref("devtools.markup.collapseAttributes", true);
-
 // Length to collapse attributes
 pref("devtools.markup.collapseAttributeLength", 120);
-
 // Whether to auto-beautify the HTML on copy.
 pref("devtools.markup.beautifyOnCopy", false);
+// Whether or not the DOM mutation breakpoints context menu are enabled in the markup view
+pref("devtools.markup.mutationBreakpoints.enabled", false);
 
 // DevTools default color unit
 pref("devtools.defaultColorUnit", "authored");
 
 // Enable the Memory tools
 pref("devtools.memory.enabled", true);
 
 pref("devtools.memory.custom-census-displays", "{}");
--- a/devtools/server/actors/inspector/node.js
+++ b/devtools/server/actors/inspector/node.js
@@ -529,26 +529,16 @@ const NodeActor = protocol.ActorClassWit
    */
   getOwnerGlobalDimensions: function() {
     const win = this.rawNode.ownerGlobal;
     return {
       innerWidth: win.innerWidth,
       innerHeight: win.innerHeight,
     };
   },
-
-  /**
-   * The breakpoint values to toggle off and on for this node. Only
-   * breakpoint types specified in 'bps' will be toggled.
-   *
-   * @param {Object} bps The subset of bp types to set the state for.
-   */
-  setMutationBreakpoints(bps) {
-    this.walker.setMutationBreakpoints(this, bps);
-  },
 });
 
 /**
  * Server side of a node list as returned by querySelectorAll()
  */
 const NodeListActor = protocol.ActorClassWithSpec(nodeListSpec, {
   typeName: "domnodelist",
 
--- a/devtools/server/actors/inspector/walker.js
+++ b/devtools/server/actors/inspector/walker.js
@@ -1779,19 +1779,19 @@ var WalkerActor = protocol.ActorClassWit
     }
 
     this._updateNodeMutationListeners(rawNode);
     this._updateDocumentMutationListeners(rawDoc);
 
     const actor = this.getNode(rawNode);
     if (actor) {
       this.queueMutation({
-        type: "mutationBreakpointUpdate",
         target: actor.actorID,
-        mutationBreakpoints: this.getMutationBreakpoints(rawNode),
+        type: "mutationBreakpoint",
+        mutationBreakpoints: this.getMutationBreakpoints(actor),
       });
     }
   },
 
   /**
    * Controls whether this DOM node has a listener attached.
    *
    * @param {Node} rawNode The DOM node.
--- a/devtools/shared/fronts/node.js
+++ b/devtools/shared/fronts/node.js
@@ -213,16 +213,18 @@ class NodeFront extends FrontClassWithSp
         });
       }
     } else if (change.type === "characterData") {
       this._form.nodeValue = change.newValue;
     } else if (change.type === "pseudoClassLock") {
       this._form.pseudoClassLocks = change.pseudoClassLocks;
     } else if (change.type === "events") {
       this._form.hasEventListeners = change.hasEventListeners;
+    } else if (change.type === "mutationBreakpoint") {
+      this._form.mutationBreakpoints = change.mutationBreakpoints;
     }
   }
 
   // Some accessors to make NodeFront feel more like a Node
 
   get id() {
     return this.getAttribute("id");
   }
@@ -338,16 +340,20 @@ class NodeFront extends FrontClassWithSp
     const cls = this.getAttribute("class");
     return cls && cls.indexOf(HIDDEN_CLASS) > -1;
   }
 
   get attributes() {
     return this._form.attrs;
   }
 
+  get mutationBreakpoints() {
+    return this._form.mutationBreakpoints;
+  }
+
   get pseudoClassLocks() {
     return this._form.pseudoClassLocks || [];
   }
   hasPseudoClassLock(pseudo) {
     return this.pseudoClassLocks.some(locked => locked === pseudo);
   }
 
   get displayType() {
--- a/devtools/shared/specs/inspector.js
+++ b/devtools/shared/specs/inspector.js
@@ -345,16 +345,25 @@ const walkerSpec = generateActorSpec({
     hasAccessibilityProperties: {
       request: {
         node: Arg(0, "nullable:domnode"),
       },
       response: {
         value: RetVal("boolean"),
       },
     },
+    setMutationBreakpoints: {
+      request: {
+        node: Arg(0, "nullable:domnode"),
+        subtree: Option(1, "nullable:boolean"),
+        removal: Option(1, "nullable:boolean"),
+        attribute: Option(1, "nullable:boolean"),
+      },
+      response: {},
+    },
   },
 });
 
 exports.walkerSpec = walkerSpec;
 
 const inspectorSpec = generateActorSpec({
   typeName: "inspector",
 
--- a/devtools/shared/specs/node.js
+++ b/devtools/shared/specs/node.js
@@ -40,22 +40,16 @@ types.addDictType("disconnectedNode", {
 types.addDictType("disconnectedNodeArray", {
   // The actual node list to return
   nodes: "array:domnode",
 
   // Nodes that are needed to connect those nodes to the root.
   newParents: "array:domnode",
 });
 
-types.addDictType("mutationBreakpointsRequest", {
-  subtree: "nullable:boolean",
-  removal: "nullable:boolean",
-  attribute: "nullable:boolean",
-});
-
 const nodeListSpec = generateActorSpec({
   typeName: "domnodelist",
 
   methods: {
     item: {
       request: { item: Arg(0) },
       response: RetVal("disconnectedNode"),
     },
@@ -135,19 +129,12 @@ const nodeSpec = generateActorSpec({
       response: {
         value: RetVal("string"),
       },
     },
     getOwnerGlobalDimensions: {
       request: {},
       response: RetVal("windowDimensions"),
     },
-
-    setMutationBreakpoints: {
-      request: {
-        breakpoints: Arg(0, "mutationBreakpointsRequest"),
-      },
-      response: {},
-    },
   },
 });
 
 exports.nodeSpec = nodeSpec;