title: How does Firefox Inspector's Walker work?
body: <p>In Firefox, at the start of modules/devtools/inspector/inspector-panel.js you see some references to a "walker", shown at the end of this snippet:</p>
  
  <pre><code> ...
  
  /**
   * Represents an open instance of the Inspector for a tab.
   * The inspector controls the highlighter, the breadcrumbs,
   * the markup view, and the sidebar (computed view, rule view
   * and layout view).
   *
   * Events:
   * - ready
   *      Fired when the inspector panel is opened for the first time and ready to
   *      use
   * - new-root
   *      Fired after a new root (navigation to a new page) event was fired by
   *      the walker, and taken into account by the inspector (after the markup
   *      view has been reloaded)
   * - markuploaded
   *      Fired when the markup-view frame has loaded
   * - layout-change
   *      Fired when the layout of the inspector changes
   * - breadcrumbs-updated
   *      Fired when the breadcrumb widget updates to a new node
   * - layoutview-updated
   *      Fired when the layoutview (box model) updates to a new node
   * - markupmutation
   *      Fired after markup mutations have been processed by the markup-view
   * - computed-view-refreshed
   *      Fired when the computed rules view updates to a new node
   * - computed-view-property-expanded
   *      Fired when a property is expanded in the computed rules view
   * - computed-view-property-collapsed
   *      Fired when a property is collapsed in the computed rules view
   * - rule-view-refreshed
   *      Fired when the rule view updates to a new node
   */
  function InspectorPanel(iframeWindow, toolbox) {
    this._toolbox = toolbox;
    this._target = toolbox._target;
    this.panelDoc = iframeWindow.document;
    this.panelWin = iframeWindow;
    this.panelWin.inspector = this;
    this._inspector = null;
  
    this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
    this._target.on("will-navigate", this._onBeforeNavigate);
  
    EventEmitter.decorate(this);
  }
  
  exports.InspectorPanel = InspectorPanel;
  
  InspectorPanel.prototype = {
    /**
     * open is effectively an asynchronous constructor
     */
    open: function InspectorPanel_open() {
      return this.target.makeRemote().then(() =&gt; {
        return this._getWalker();
      }).then(() =&gt; {
        return this._getDefaultNodeForSelection();
      }).then(defaultSelection =&gt; {
        return this._deferredOpen(defaultSelection);
      }).then(null, console.error);
    },
  
    get inspector() {
      if (!this._target.form) {
        throw new Error("Target.inspector requires an initialized remote actor.");
      }
      if (!this._inspector) {
        this._inspector = InspectorFront(this._target.client, this._target.form);
      }
      return this._inspector;
    },
  
    _deferredOpen: function(defaultSelection) {
      let deferred = promise.defer();
  
      this.outerHTMLEditable = this._target.client.traits.editOuterHTML;
  
      this.onNewRoot = this.onNewRoot.bind(this);
      this.walker.on("new-root", this.onNewRoot);
  
      this.nodemenu = this.panelDoc.getElementById("inspector-node-popup");
      this.lastNodemenuItem = this.nodemenu.lastChild;
      this._setupNodeMenu = this._setupNodeMenu.bind(this);
      this._resetNodeMenu = this._resetNodeMenu.bind(this);
      this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true);
      this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
  
      // Create an empty selection
      this._selection = new Selection(this.walker);
      this.onNewSelection = this.onNewSelection.bind(this);
      this.selection.on("new-node-front", this.onNewSelection);
      this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
      this.selection.on("before-new-node-front", this.onBeforeNewSelection);
      this.onDetached = this.onDetached.bind(this);
      this.selection.on("detached-front", this.onDetached);
  
      this.breadcrumbs = new HTMLBreadcrumbs(this);
  
      if (this.target.isLocalTab) {
        this.browser = this.target.tab.linkedBrowser;
        this.scheduleLayoutChange = this.scheduleLayoutChange.bind(this);
        this.browser.addEventListener("resize", this.scheduleLayoutChange, true);
  
        // Show a warning when the debugger is paused.
        // We show the warning only when the inspector
        // is selected.
        this.updateDebuggerPausedWarning = function() {
          let notificationBox = this._toolbox.getNotificationBox();
          let notification = notificationBox.getNotificationWithValue("inspector-script-paused");
          if (!notification &amp;&amp; this._toolbox.currentToolId == "inspector" &amp;&amp;
              this.target.isThreadPaused) {
            let message = this.strings.GetStringFromName("debuggerPausedWarning.message");
            notificationBox.appendNotification(message,
              "inspector-script-paused", "", notificationBox.PRIORITY_WARNING_HIGH);
          }
  
          if (notification &amp;&amp; this._toolbox.currentToolId != "inspector") {
            notificationBox.removeNotification(notification);
          }
  
          if (notification &amp;&amp; !this.target.isThreadPaused) {
            notificationBox.removeNotification(notification);
          }
  
        }.bind(this);
        this.target.on("thread-paused", this.updateDebuggerPausedWarning);
        this.target.on("thread-resumed", this.updateDebuggerPausedWarning);
        this._toolbox.on("select", this.updateDebuggerPausedWarning);
        this.updateDebuggerPausedWarning();
      }
  
      this.highlighter = new Highlighter(this.target, this, this._toolbox);
      let button = this.panelDoc.getElementById("inspector-inspect-toolbutton");
      this.onLockStateChanged = function() {
        if (this.highlighter.locked) {
          button.removeAttribute("checked");
          this._toolbox.raise();
        } else {
          button.setAttribute("checked", "true");
        }
      }.bind(this);
      this.highlighter.on("locked", this.onLockStateChanged);
      this.highlighter.on("unlocked", this.onLockStateChanged);
  
      this._initMarkup();
      this.isReady = false;
  
      this.once("markuploaded", function() {
        this.isReady = true;
  
        // All the components are initialized. Let's select a node.
        this._selection.setNodeFront(defaultSelection);
  
        this.markup.expandNode(this.selection.nodeFront);
  
        this.emit("ready");
        deferred.resolve(this);
      }.bind(this));
  
      this.setupSearchBox();
      this.setupSidebar();
  
      return deferred.promise;
    },
  
    _onBeforeNavigate: function() {
      this._defaultNode = null;
      this.selection.setNodeFront(null);
      this._destroyMarkup();
      this.isDirty = false;
    },
  
    _getWalker: function() {
      return this.inspector.getWalker().then(walker =&gt; {
        this.walker = walker;
        return this.inspector.getPageStyle();
      }).then(pageStyle =&gt; {
        this.pageStyle = pageStyle;
      });
    },
  
    ...
  </code></pre>
  
  <p>I didn't see this Promise documented anywhere in the <a href="https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs" rel="nofollow">Addon APIs</a>, is there any documentation (or even source comments) on what this is, and how it is used?</p>
  
  <p>Could it be used to add special styling or append some icons to certain elements in the DOM tree view of the Firefox DevTools Inspector?</p>