突变观察者未被移除。为什么? (795978错误)

时间:2013-06-19 21:13:51

标签: javascript firefox

我在FF 17,偶尔我会在下面看到这个错误。我不知道这是什么或浏览器试图沟通的内容。

我不知道还有其他JavaScript文件正在运行,看起来它是浏览器的一部分吗?

我的印象或假设是浏览器代码是用编译语言而不是JavaScript编写的。

[20:50:48.771] TypeError: this._containers is undefined @ resource:///modules/devtools/MarkupView.jsm:322

代码指的是:

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const Cc = Components.classes;
const Cu = Components.utils;
const Ci = Components.interfaces;

// Page size for pageup/pagedown
const PAGE_SIZE = 10;

var EXPORTED_SYMBOLS = ["MarkupView"];

Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
Cu.import("resource:///modules/devtools/CssRuleView.jsm");
Cu.import("resource:///modules/devtools/Templater.jsm");
Cu.import("resource:///modules/devtools/Undo.jsm")

/**
 * Vocabulary for the purposes of this file:
 *
 * MarkupContainer - the structure that holds an editor and its
 *  immediate children in the markup panel.
 * Node - A content node.
 * object.elt - A UI element in the markup panel.
 */

/**
 * The markup tree.  Manages the mapping of nodes to MarkupContainers,
 * updating based on mutations, and the undo/redo bindings.
 *
 * @param Inspector aInspector
 *        The inspector we're watching.
 * @param iframe aFrame
 *        An iframe in which the caller has kindly loaded markup-view.xhtml.
 */
function MarkupView(aInspector, aFrame)
{
  this._inspector = aInspector;
  this._frame = aFrame;
  this.doc = this._frame.contentDocument;
  this._elt = this.doc.querySelector("#root");

  this.undo = new UndoStack();
  this.undo.installController(this._frame.ownerDocument.defaultView);

  this._containers = new WeakMap();

  this._observer = new this.doc.defaultView.MutationObserver(this._mutationObserver.bind(this));

  this._boundSelect = this._onSelect.bind(this);
  this._inspector.on("select", this._boundSelect);
  this._onSelect();

  this._boundKeyDown = this._onKeyDown.bind(this);
  this._frame.addEventListener("keydown", this._boundKeyDown, false);

  this._boundFocus = this._onFocus.bind(this);
  this._frame.addEventListener("focus", this._boundFocus, false);
}

MarkupView.prototype = {
  _selectedContainer: null,

  /**
   * Return the selected node.
   */
  get selected() {
    return this._selectedContainer ? this._selectedContainer.node : null;
  },

  template: function MT_template(aName, aDest, aOptions)
  {
    let node = this.doc.getElementById("template-" + aName).cloneNode(true);
    node.removeAttribute("id");
    template(node, aDest, aOptions);
    return node;
  },

  /**
   * Get the MarkupContainer object for a given node, or undefined if
   * none exists.
   */
  getContainer: function MT_getContainer(aNode)
  {
    return this._containers.get(aNode);
  },

  /**
   * Highlight the given element in the markup panel.
   */
  _onSelect: function MT__onSelect()
  {
    if (this._inspector.selection) {
      this.showNode(this._inspector.selection);
    }
    this.selectNode(this._inspector.selection);
  },

  /**
   * Create a TreeWalker to find the next/previous
   * node for selection.
   */
  _selectionWalker: function MT__seletionWalker(aStart)
  {
    let walker = this.doc.createTreeWalker(
      aStart || this._elt,
      Ci.nsIDOMNodeFilter.SHOW_ELEMENT,
      function(aElement) {
        if (aElement.container && aElement.container.visible) {
          return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
        }
        return Ci.nsIDOMNodeFilter.FILTER_SKIP;
      },
      false
    );
    walker.currentNode = this._selectedContainer.elt;
    return walker;
  },

  /**
   * Key handling.
   */
  _onKeyDown: function MT__KeyDown(aEvent)
  {
    let handled = true;

    // Ignore keystrokes that originated in editors.
    if (aEvent.target.tagName.toLowerCase() === "input" ||
        aEvent.target.tagName.toLowerCase() === "textarea") {
      return;
    }

    switch(aEvent.keyCode) {
      case Ci.nsIDOMKeyEvent.DOM_VK_DELETE:
      case Ci.nsIDOMKeyEvent.DOM_VK_BACK_SPACE:
        this.deleteNode(this._selectedContainer.node);
        break;
      case Ci.nsIDOMKeyEvent.DOM_VK_HOME:
        this.navigate(this._containers.get(this._rootNode.firstChild));
        break;
      case Ci.nsIDOMKeyEvent.DOM_VK_LEFT:
        this.collapseNode(this._selectedContainer.node);
        break;
      case Ci.nsIDOMKeyEvent.DOM_VK_RIGHT:
        this.expandNode(this._selectedContainer.node);
        break;
      case Ci.nsIDOMKeyEvent.DOM_VK_UP:
        let prev = this._selectionWalker().previousNode();
        if (prev) {
          this.navigate(prev.container);
        }
        break;
      case Ci.nsIDOMKeyEvent.DOM_VK_DOWN:
        let next = this._selectionWalker().nextNode();
        if (next) {
          this.navigate(next.container);
        }
        break;
      case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP: {
        let walker = this._selectionWalker();
        let selection = this._selectedContainer;
        for (let i = 0; i < PAGE_SIZE; i++) {
          let prev = walker.previousNode();
          if (!prev) {
            break;
          }
          selection = prev.container;
        }
        this.navigate(selection);
        break;
      }
      case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN: {
        let walker = this._selectionWalker();
        let selection = this._selectedContainer;
        for (let i = 0; i < PAGE_SIZE; i++) {
          let next = walker.nextNode();
          if (!next) {
            break;
          }
          selection = next.container;
        }
        this.navigate(selection);
        break;
      }
      default:
        handled = false;
    }
    if (handled) {
      aEvent.stopPropagation();
      aEvent.preventDefault();
    }
  },

  /**
   * Delete a node from the DOM.
   * This is an undoable action.
   */
  deleteNode: function MC__deleteNode(aNode)
  {
    let doc = nodeDocument(aNode);
    if (aNode === doc ||
        aNode === doc.documentElement ||
        aNode.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE) {
      return;
    }

    let parentNode = aNode.parentNode;
    let sibling = aNode.nextSibling;

    this.undo.do(function() {
      if (aNode.selected) {
        this.navigate(this._containers.get(parentNode));
      }
      parentNode.removeChild(aNode);
    }.bind(this), function() {
      parentNode.insertBefore(aNode, sibling);
    });
  },

  /**
   * If an editable item is focused, select its container.
   */
  _onFocus: function MC__onFocus(aEvent) {
    let parent = aEvent.target;
    while (!parent.container) {
      parent = parent.parentNode;
    }
    if (parent) {
      this.navigate(parent.container, true);
    }
  },

  /**
   * Handle a user-requested navigation to a given MarkupContainer,
   * updating the inspector's currently-selected node.
   *
   * @param MarkupContainer aContainer
   *        The container we're navigating to.
   * @param aIgnoreFocus aIgnoreFocus
   *        If falsy, keyboard focus will be moved to the container too.
   */
  navigate: function MT__navigate(aContainer, aIgnoreFocus)
  {
    if (!aContainer) {
      return;
    }

    let node = aContainer.node;
    this.showNode(node);
    this.selectNode(node);

    if (this._inspector._IUI.highlighter.isNodeHighlightable(node)) {
      this._inspector._IUI.select(node, true, false, "treepanel");
      this._inspector._IUI.highlighter.highlight(node);
    }

    if (!aIgnoreFocus) {
      aContainer.focus();
    }
  },

  /**
   * Make sure a node is included in the markup tool.
   *
   * @param DOMNode aNode
   *        The node in the content document.
   *
   * @returns MarkupContainer The MarkupContainer object for this element.
   */
  importNode: function MT_importNode(aNode, aExpand)
  {
    if (!aNode) {
      return null;
    }

    if (this._containers.has(aNode)) {
      return this._containers.get(aNode);
    }

    this._observer.observe(aNode, {
      attributes: true,
      childList: true,
      characterData: true,
    });

    let walker = documentWalker(aNode);
    let parent = walker.parentNode();
    if (parent) {
      // Make sure parents of this node are imported too.
      var container = new MarkupContainer(this, aNode);
    } else {
      var container = new RootContainer(this, aNode);
      this._elt.appendChild(container.elt);
      this._rootNode = aNode;
      aNode.addEventListener("load", function MP_watch_contentLoaded(aEvent) {
        // Fake a childList mutation here.
        this._mutationObserver([{target: aEvent.target, type: "childList"}]);
      }.bind(this), true);

    }

    this._containers.set(aNode, container);
    container.expanded = aExpand;

    this._updateChildren(container);

    if (parent) {
      this.importNode(parent, true);
    }
    return container;
  },

  /**
   * Mutation observer used for included nodes.
   */
  _mutationObserver: function MT__mutationObserver(aMutations)
  {
    for (let mutation of aMutations) {
      let container = this._containers.get(mutation.target);
      if (!container) {
        // Container might not exist if this came from a load event for an iframe
        // we're not viewing.
        continue;
      }
      if (mutation.type === "attributes" || mutation.type === "characterData") {
        container.update();
      } else if (mutation.type === "childList") {
        this._updateChildren(container);
      }
    }
    this._inspector._emit("markupmutation");
  },

  /**
   * Make sure the given node's parents are expanded and the
   * node is scrolled on to screen.
   */
  showNode: function MT_showNode(aNode)
  {
    this.importNode(aNode);
    let walker = documentWalker(aNode);
    let parent;
    while (parent = walker.parentNode()) {
      this.expandNode(parent);
    }
    LayoutHelpers.scrollIntoViewIfNeeded(this._containers.get(aNode).editor.elt, false);
  },

  /**
   * Expand the container's children.
   */
  _expandContainer: function MT__expandContainer(aContainer)
  {
    if (aContainer.hasChildren && !aContainer.expanded) {
      aContainer.expanded = true;
      this._updateChildren(aContainer);
    }
  },

  /**
   * Expand the node's children.
   */
  expandNode: function MT_expandNode(aNode)
  {
    let container = this._containers.get(aNode);
    this._expandContainer(container);
  },

  /**
   * Expand the entire tree beneath a container.
   *
   * @param aContainer The container to expand.
   */
  _expandAll: function MT_expandAll(aContainer)
  {
    this._expandContainer(aContainer);
    let child = aContainer.children.firstChild;
    while (child) {
      this._expandAll(child.container);
      child = child.nextSibling;
    }
  },

  /**
   * Expand the entire tree beneath a node.
   *
   * @param aContainer The node to expand, or null
   *        to start from the top.
   */
  expandAll: function MT_expandAll(aNode)
  {
    aNode = aNode || this._rootNode;
    this._expandAll(this._containers.get(aNode));
  },

  /**
   * Collapse the node's children.
   */
  collapseNode: function MT_collapseNode(aNode)
  {
    let container = this._containers.get(aNode);
    container.expanded = false;
  },

  /**
   * Mark the given node selected.
   */
  selectNode: function MT_selectNode(aNode)
  {
    let container = this._containers.get(aNode);
    if (this._selectedContainer === container) {
      return false;
    }
    if (this._selectedContainer) {
      this._selectedContainer.selected = false;
    }
    this._selectedContainer = container;
    if (aNode) {
      this._selectedContainer.selected = true;
    }

    this._selectedContainer.focus();

    return true;
  },

  /**
   * Called when the markup panel initiates a change on a node.
   */
  nodeChanged: function MT_nodeChanged(aNode)
  {
    if (aNode === this._inspector.selection) {
      this._inspector.change("markupview");
    }
  },

  /**
   * Make sure all children of the given container's node are
   * imported and attached to the container in the right order.
   */
  _updateChildren: function MT__updateChildren(aContainer)
  {
    // Get a tree walker pointing at the first child of the node.
    let treeWalker = documentWalker(aContainer.node);
    let child = treeWalker.firstChild();
    aContainer.hasChildren = !!child;
    if (aContainer.expanded) {
      let lastContainer = null;
      while (child) {
        let container = this.importNode(child, false);

        // Make sure children are in the right order.
        let before = lastContainer ? lastContainer.nextSibling : aContainer.children.firstChild;
        aContainer.children.insertBefore(container.elt, before);
        lastContainer = container.elt;
        child = treeWalker.nextSibling();
      }

      while (aContainer.children.lastChild != lastContainer) {
        aContainer.children.removeChild(aContainer.children.lastChild);
      }
    }
  },

  /**
   * Tear down the markup panel.
   */
  destroy: function MT_destroy()
  {
    this.undo.destroy();
    delete this.undo;

    this._frame.addEventListener("focus", this._boundFocus, false);
    delete this._boundFocus;

    this._frame.removeEventListener("keydown", this._boundKeyDown, true);
    delete this._boundKeyDown;

    this._inspector.removeListener("select", this._boundSelect);
    delete this._boundSelect;

    delete this._elt;

    delete this._containers;
    this._observer.disconnect();
    delete this._observer;
  }
};


/**
 * The main structure for storing a document node in the markup
 * tree.  Manages creation of the editor for the node and
 * a <ul> for placing child elements, and expansion/collapsing
 * of the element.
 *
 * @param MarkupView aMarkupView
 *        The markup view that owns this container.
 * @param DOMNode aNode
 *        The node to display.
 */
function MarkupContainer(aMarkupView, aNode)
{
  this.markup = aMarkupView;
  this.doc = this.markup.doc;
  this.undo = this.markup.undo;
  this.node = aNode;

  if (aNode.nodeType == Ci.nsIDOMNode.TEXT_NODE) {
    this.editor = new TextEditor(this, aNode, "text");
  } else if (aNode.nodeType == Ci.nsIDOMNode.COMMENT_NODE) {
    this.editor = new TextEditor(this, aNode, "comment");
  } else if (aNode.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
    this.editor = new ElementEditor(this, aNode);
  } else if (aNode.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE) {
    this.editor = new DoctypeEditor(this, aNode);
  } else {
    this.editor = new GenericEditor(this.markup, aNode);
  }

  // The template will fill the following properties
  this.elt = null;
  this.expander = null;
  this.codeBox = null;
  this.children = null;
  let options = { stack: "markup-view.xhtml" };
  this.markup.template("container", this, options);

  this.elt.container = this;

  this.expander.addEventListener("click", function() {
    this.markup.navigate(this);

    if (this.expanded) {
      this.markup.collapseNode(this.node);
    } else {
      this.markup.expandNode(this.node);
    }
  }.bind(this));

  this.codeBox.insertBefore(this.editor.elt, this.children);

  this.editor.elt.addEventListener("mousedown", function(evt) {
    this.markup.navigate(this);
  }.bind(this), false);

  if (this.editor.closeElt) {
    this.codeBox.appendChild(this.editor.closeElt);
  }

}

MarkupContainer.prototype = {
  /**
   * True if the current node has children.  The MarkupView
   * will set this attribute for the MarkupContainer.
   */
  _hasChildren: false,

  get hasChildren() {
    return this._hasChildren;
  },

  set hasChildren(aValue) {
    this._hasChildren = aValue;
    if (aValue) {
      this.expander.style.visibility = "visible";
    } else {
      this.expander.style.visibility = "hidden";
    }
  },

  /**
   * True if the node has been visually expanded in the tree.
   */
  get expanded() {
    return this.children.hasAttribute("expanded");
  },

  set expanded(aValue) {
    if (aValue) {
      this.expander.setAttribute("expanded", "");
      this.children.setAttribute("expanded", "");
    } else {
      this.expander.removeAttribute("expanded");
      this.children.removeAttribute("expanded");
    }
  },

  /**
   * True if the container is visible in the markup tree.
   */
  get visible()
  {
    return this.elt.getBoundingClientRect().height > 0;
  },

  /**
   * True if the container is currently selected.
   */
  _selected: false,

  get selected() {
    return this._selected;
  },

  set selected(aValue) {
    this._selected = aValue;
    if (this._selected) {
      this.editor.elt.classList.add("selected");
      if (this.editor.closeElt) {
        this.editor.closeElt.classList.add("selected");
      }
    } else {
      this.editor.elt.classList.remove("selected");
      if (this.editor.closeElt) {
        this.editor.closeElt.classList.remove("selected");
      }
    }
  },

  /**
   * Update the container's editor to the current state of the
   * viewed node.
   */
  update: function MC_update()
  {
    if (this.editor.update) {
      this.editor.update();
    }
  },

  /**
   * Try to put keyboard focus on the current editor.
   */
  focus: function MC_focus()
  {
    let focusable = this.editor.elt.querySelector("[tabindex]");
    if (focusable) {
      focusable.focus();
    }
  }
}

/**
 * Dummy container node used for the root document element.
 */
function RootContainer(aMarkupView, aNode)
{
  this.doc = aMarkupView.doc;
  this.elt = this.doc.createElement("ul");
  this.children = this.elt;
  this.node = aNode;
}

/**
 * Creates an editor for simple nodes.
 */
function GenericEditor(aContainer, aNode)
{
  this.elt = aContainer.doc.createElement("span");
  this.elt.className = "editor";
  this.elt.textContent = aNode.nodeName;
}

/**
 * Creates an editor for a DOCTYPE node.
 *
 * @param MarkupContainer aContainer The container owning this editor.
 * @param DOMNode aNode The node being edited.
 */
function DoctypeEditor(aContainer, aNode)
{
  this.elt = aContainer.doc.createElement("span");
  this.elt.className = "editor comment";
  this.elt.textContent = '<!DOCTYPE ' + aNode.name +
     (aNode.publicId ? ' PUBLIC "' +  aNode.publicId + '"': '') +
     (aNode.systemId ? ' "' + aNode.systemId + '"' : '') +
     '>';
}
// ... snip it

1 个答案:

答案 0 :(得分:3)

这是Firefox中报告的错误。见Bug 795978

我通过谷歌搜索“firefox MarkupView.jsm”