我在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