Firefox Inspector的Walker如何工作?

时间:2014-02-01 08:02:14

标签: javascript firefox firefox-addon promise firefox-developer-tools

在Firefox中,在modules / devtools / inspector / inspector-panel.js的开头,你会看到一些对“walker”的引用,显示在这个片段的末尾:

 ...

/**
 * 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(() => {
      return this._getWalker();
    }).then(() => {
      return this._getDefaultNodeForSelection();
    }).then(defaultSelection => {
      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 && this._toolbox.currentToolId == "inspector" &&
            this.target.isThreadPaused) {
          let message = this.strings.GetStringFromName("debuggerPausedWarning.message");
          notificationBox.appendNotification(message,
            "inspector-script-paused", "", notificationBox.PRIORITY_WARNING_HIGH);
        }

        if (notification && this._toolbox.currentToolId != "inspector") {
          notificationBox.removeNotification(notification);
        }

        if (notification && !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 => {
      this.walker = walker;
      return this.inspector.getPageStyle();
    }).then(pageStyle => {
      this.pageStyle = pageStyle;
    });
  },

  ...

我没有在Addon APIs的任何地方看到这个Promise有记录,是否有任何文档(甚至源代码注释)是什么,以及如何使用它?

是否可以用它来添加特殊样式或在Firefox DevTools Inspector的DOM树视图中为某些元素添加一些图标?

1 个答案:

答案 0 :(得分:1)

每当" walker"在devtools代码中提到,它通常是指工具包/ devtools / server / actors / inspector.js中的 WalkerActor 类。

演员是javascript类,专门用于从当前检查的页面和上下文中获取信息或操纵它们。

您看作用户的工具的UI部分不会直接执行此操作。实际上,devtools使用客户端 - 服务器协议在工具箱(托管您使用的所有面板)和作为被检查页面的一部分运行的actor之间进行通信。这是允许使用工具检查远程设备的原因。

特别是对于WalkerActor,它的作用是遍历DOM树并将有关节点的信息提供给检查器面板,以便它可以显示在工具中。

在页面上打开devtools检查器时看到的内容是DOM树的一部分(只是因为它尚未完全展开而且尚未检索到折叠的节点)由WalkerActor检索并(通过协议)发送到检查员面板。

面板的实际UI渲染在客户端上完成(这意味着,工具箱端,与演员/页面端相比),在browser / devtools / markupview / markup中完成-view.js

在此文件中,MarkupView和MarkupContainer类特别负责显示节点。它们并不是Addons API的特定组成部分,但由于特权代码可以访问gDevTools全局变量,所以它应该相对容易掌握它们:

Cu.import("resource://gre/modules/devtools/Loader.jsm");
let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
let toolbox = gDevTools.getToolbox(target);
let inspectorPanel = toolbox.getPanel("inspector");
inspector.markup // Returns the instance of MarkupView that is currently loaded in the inspector