Bug 1053898 - Update Walker and Node actors to listen to slotchange events on shadow roots;r=bgrins draft
authorJulian Descottes <jdescottes@mozilla.com>
Mon, 05 Mar 2018 18:24:53 +0100
changeset 773666 d4f9698a772c1ac3bc749303e96ef132c247d850
parent 773665 a71f90edb139122d0561b4a51ef42082e64e381e
child 773667 b42b7e42c86d5ad1e4e3d435a3f2908dda08d1f1
push id104269
push userjdescottes@mozilla.com
push dateWed, 28 Mar 2018 08:16:27 +0000
reviewersbgrins
bugs1053898
milestone61.0a1
Bug 1053898 - Update Walker and Node actors to listen to slotchange events on shadow roots;r=bgrins MozReview-Commit-ID: 9LfiR7EFq3I
devtools/client/inspector/markup/markup.js
devtools/server/actors/inspector/node.js
devtools/server/actors/inspector/walker.js
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -1018,17 +1018,18 @@ MarkupView.prototype = {
         // Container might not exist if this came from a load event for a node
         // we're not viewing.
         continue;
       }
 
       if (type === "attributes" || type === "characterData"
         || type === "events" || type === "pseudoClassLock") {
         container.update();
-      } else if (type === "childList" || type === "nativeAnonymousChildList") {
+      } else if (type === "childList" || type === "nativeAnonymousChildList"
+        || type === "slotchange") {
         container.childrenDirty = true;
         // Update the children to take care of changes in the markup view DOM
         // and update container (and its subtree) DOM tree depth level for
         // accessibility where necessary.
         this._updateChildren(container, {flash: true}).then(() =>
           container.updateLevel());
       } else if (type === "inlineTextChild") {
         container.childrenDirty = true;
--- a/devtools/server/actors/inspector/node.js
+++ b/devtools/server/actors/inspector/node.js
@@ -72,16 +72,24 @@ const NodeActor = protocol.ActorClassWit
     protocol.Actor.prototype.destroy.call(this);
 
     if (this.mutationObserver) {
       if (!Cu.isDeadWrapper(this.mutationObserver)) {
         this.mutationObserver.disconnect();
       }
       this.mutationObserver = null;
     }
+
+    if (this.slotchangeListener) {
+      if (!InspectorActorUtils.isNodeDead(this)) {
+        this.rawNode.removeEventListener("slotchange", this.slotchangeListener);
+      }
+      this.slotchangeListener = null;
+    }
+
     this.rawNode = null;
     this.walker = null;
   },
 
   // Returns the JSON representation of this object over the wire.
   form: function(detail) {
     if (detail === "actorid") {
       return this.actorID;
@@ -163,16 +171,24 @@ const NodeActor = protocol.ActorClassWit
       characterData: true,
       characterDataOldValue: true,
       childList: true,
       subtree: true
     });
     this.mutationObserver = observer;
   },
 
+  /**
+   * Watch for all "slotchange" events on the node.
+   */
+  watchSlotchange: function(callback) {
+    this.slotchangeListener = callback;
+    this.rawNode.addEventListener("slotchange", this.slotchangeListener);
+  },
+
   get isBeforePseudoElement() {
     return this.rawNode.nodeName === "_moz_generated_content_before";
   },
 
   get isAfterPseudoElement() {
     return this.rawNode.nodeName === "_moz_generated_content_after";
   },
 
--- a/devtools/server/actors/inspector/walker.js
+++ b/devtools/server/actors/inspector/walker.js
@@ -134,16 +134,17 @@ var WalkerActor = protocol.ActorClassWit
     this._orphaned = new Set();
 
     // The client can tell the walker that it is interested in a node
     // even when it is orphaned with the `retainNode` method.  This
     // list contains orphaned nodes that were so retained.
     this._retainedOrphans = new Set();
 
     this.onMutations = this.onMutations.bind(this);
+    this.onSlotchange = this.onSlotchange.bind(this);
     this.onFrameLoad = this.onFrameLoad.bind(this);
     this.onFrameUnload = this.onFrameUnload.bind(this);
     this._throttledEmitNewMutations = throttle(this._emitNewMutations.bind(this),
       MUTATIONS_THROTTLING_DELAY);
 
     tabActor.on("will-navigate", this.onFrameUnload);
     tabActor.on("window-ready", this.onFrameLoad);
 
@@ -303,16 +304,21 @@ var WalkerActor = protocol.ActorClassWit
     // Add the node actor as a child of this walker actor, assigning
     // it an actorID.
     this.manage(actor);
     this._refMap.set(node, actor);
 
     if (node.nodeType === Ci.nsIDOMNode.DOCUMENT_NODE) {
       actor.watchDocument(this.onMutations);
     }
+
+    if (actor.isShadowRoot) {
+      actor.watchSlotchange(this.onSlotchange);
+    }
+
     return actor;
   },
 
   _onReflows: function(reflows) {
     // Going through the nodes the walker knows about, see which ones have
     // had their display changed and send a display-change event if any
     let changes = [];
     for (let [node, actor] of this._refMap) {
@@ -1642,16 +1648,29 @@ var WalkerActor = protocol.ActorClassWit
     this.queueMutation({
       type: "inlineTextChild",
       target: parentActor.actorID,
       inlineTextChild:
         inlineTextChild ? inlineTextChild.form() : undefined
     });
   },
 
+  onSlotchange: function(event) {
+    let target = event.target;
+    let targetActor = this.getNode(target);
+    if (!targetActor) {
+      return;
+    }
+
+    this.queueMutation({
+      type: "slotchange",
+      target: targetActor.actorID
+    });
+  },
+
   onFrameLoad: function({ window, isTopLevel }) {
     let { readyState } = window.document;
     if (readyState != "interactive" && readyState != "complete") {
       window.addEventListener("DOMContentLoaded",
         this.onFrameLoad.bind(this, { window, isTopLevel }),
         { once: true });
       return;
     }