gecko/extensions/inspector/resources/content/viewers/dom/dom.js

1502 lines
43 KiB
JavaScript
Raw Normal View History

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 2001
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Joe Hewitt <hewitt@netscape.com> (original author)
* Jason Barnabe <jason_barnabe@fastmail.fm>
* Shawn Wilsher <me@shawnwilsher.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/***************************************************************
* DOMViewer --------------------------------------------
* Views all nodes within a document.
****************************************************************/
//////////// global variables /////////////////////
var viewer;
//////////// global constants ////////////////////
const kDOMViewCID = "@mozilla.org/inspector/dom-view;1";
const kClipboardHelperCID = "@mozilla.org/widget/clipboardhelper;1";
const kPromptServiceCID = "@mozilla.org/embedcomp/prompt-service;1";
const nsIDOMNode = Components.interfaces.nsIDOMNode;
const nsIDOMElement = Components.interfaces.nsIDOMElement;
//////////////////////////////////////////////////
window.addEventListener("load", DOMViewer_initialize, false);
window.addEventListener("unload", DOMViewer_destroy, false);
function DOMViewer_initialize()
{
viewer = new DOMViewer();
viewer.initialize(parent.FrameExchange.receiveData(window));
}
function DOMViewer_destroy()
{
PrefUtils.removeObserver("inspector", PrefChangeObserver);
viewer.removeClickListeners();
viewer = null;
}
////////////////////////////////////////////////////////////////////////////
//// class DOMViewer
function DOMViewer() // implements inIViewer
{
this.mObsMan = new ObserverManager(this);
this.mDOMTree = document.getElementById("trDOMTree");
this.mDOMTreeBody = document.getElementById("trDOMTreeBody");
// prepare and attach the DOM DataSource
this.mDOMView = XPCU.createInstance(kDOMViewCID, "inIDOMView");
this.mDOMView.showSubDocuments = true;
this.mDOMView.whatToShow &= ~(NodeFilter.SHOW_ATTRIBUTE); // hide attribute nodes
this.mDOMTree.treeBoxObject.view = this.mDOMView;
PrefUtils.addObserver("inspector", PrefChangeObserver);
}
DOMViewer.prototype =
{
mSubject: null,
mDOMView: null,
// searching stuff
mSearchResults: null,
mSearchCurrentIdx: null,
mSearchDirection: null,
mColumns: null,
mFlashSelected: null,
mFlashes: 0,
mFindDir: null,
mFindParams: null,
mFindType: null,
mFindWalker: null,
mSelecting: false,
////////////////////////////////////////////////////////////////////////////
//// interface inIViewer
//// attributes
get uid() { return "dom" },
get pane() { return this.mPanel },
get editable() { return true; },
get selection() { return this.mSelection },
get subject() { return this.mSubject },
set subject(aObject) {
this.mSubject = aObject;
this.mDOMView.rootNode = aObject;
this.mObsMan.dispatchEvent("subjectChange", { subject: aObject });
this.setInitialSelection(aObject);
},
//// methods
/**
* Properly sets up the DOM Viewer
*
* @param aPane The panel this references.
*/
initialize: function initialize(aPane)
{
//this.initColumns();
this.mPanel = aPane;
this.setAnonContent(PrefUtils.getPref("inspector.dom.showAnon"));
this.setProcessingInstructions(PrefUtils.getPref("inspector.dom.showProcessingInstructions"));
this.setAccessibleNodes(PrefUtils.getPref("inspector.dom.showAccessibleNodes"));
this.setWhitespaceNodes(PrefUtils.getPref("inspector.dom.showWhitespaceNodes"));
this.setFlashSelected(PrefUtils.getPref("inspector.blink.on"));
aPane.notifyViewerReady(this);
},
destroy: function()
{
this.mDOMTree.treeBoxObject.view = null;
},
isCommandEnabled: function(aCommand)
{
var clipboardNode = null;
var selectedNode = null;
var parentNode = null;
if (/^cmdEditPaste/.test(aCommand)) {
if (this.mPanel.panelset.clipboardFlavor != "inspector/dom-node")
return false;
clipboardNode = this.mPanel.panelset.getClipboardData();
}
if (/^cmdEdit(Paste|Insert)/.test(aCommand)) {
selectedNode = new XPCNativeWrapper(viewer.selectedNode, "nodeType",
"parentNode", "childNodes");
if (selectedNode.parentNode)
parentNode = new XPCNativeWrapper(selectedNode.parentNode, "nodeType");
}
switch (aCommand) {
case "cmdEditPaste":
case "cmdEditPasteBefore":
return this.isValidChild(parentNode, clipboardNode);
case "cmdEditPasteReplace":
return this.isValidChild(parentNode, clipboardNode, selectedNode);
case "cmdEditPasteFirstChild":
case "cmdEditPasteLastChild":
return this.isValidChild(selectedNode, clipboardNode);
case "cmdEditPasteAsParent":
return this.isValidChild(clipboardNode, selectedNode) &&
this.isValidChild(parentNode, clipboardNode, selectedNode);
case "cmdEditInsertAfter":
return parentNode instanceof nsIDOMElement;
case "cmdEditInsertBefore":
return parentNode instanceof nsIDOMElement;
case "cmdEditInsertFirstChild":
return selectedNode instanceof nsIDOMElement;
case "cmdEditInsertLastChild":
return selectedNode instanceof nsIDOMElement;
case "cmdEditCut":
case "cmdEditCopy":
case "cmdEditDelete":
return this.selectedNode != null;
}
return false;
},
/**
* Determines whether the passed parent/child combination is valid.
* @param the parent
* @param the child
* @param the node the child is replacing (optional)
* @return true if the passed parent can have the passed child as a child,
* false otherwise
*/
isValidChild: function isValidChild(parent, child, replaced)
{
// the document (fragment) node must be an only child and can't be replaced
if (parent == null)
return false;
// the only types that can ever have children
if (parent.nodeType != nsIDOMNode.ELEMENT_NODE &&
parent.nodeType != nsIDOMNode.DOCUMENT_NODE &&
parent.nodeType != nsIDOMNode.DOCUMENT_FRAGMENT_NODE)
return false;
// the only types that can't ever be children
if (child.nodeType == nsIDOMNode.DOCUMENT_NODE ||
child.nodeType == nsIDOMNode.DOCUMENT_FRAGMENT_NODE ||
child.nodeType == nsIDOMNode.ATTRIBUTE_NODE)
return false;
// doctypes can only be the children of documents
if (child.nodeType == nsIDOMNode.DOCUMENT_TYPE_NODE &&
parent.nodeType != nsIDOMNode.DOCUMENT_NODE)
return false;
// only elements and fragments can have text, cdata, and entities as
// children
if (parent.nodeType != nsIDOMNode.ELEMENT_NODE &&
parent.nodeType != nsIDOMNode.DOCUMENT_FRAGMENT_NODE &&
(child.nodeType == nsIDOMNode.TEXT_NODE ||
child.nodeType == nsIDOMNode.CDATA_NODE ||
child.nodeType == nsIDOMNode.ENTITY_NODE))
return false;
// documents can only have one document element or doctype
if (parent.nodeType == nsIDOMNode.DOCUMENT_NODE &&
(child.nodeType == nsIDOMNode.ELEMENT_NODE ||
child.nodeType == nsIDOMNode.DOCUMENT_TYPE_NODE) &&
(!replaced || child.nodeType != replaced.nodeType))
for (var i = 0; i < parent.childNodes.length; i++)
if (parent.childNodes[i].nodeType == child.nodeType)
return false;
return true;
},
getCommand: function(aCommand)
{
if (aCommand in window)
return new window[aCommand]();
return null;
},
////////////////////////////////////////////////////////////////////////////
//// event dispatching
addObserver: function(aEvent, aObserver) { this.mObsMan.addObserver(aEvent, aObserver); },
removeObserver: function(aEvent, aObserver) { this.mObsMan.removeObserver(aEvent, aObserver); },
////////////////////////////////////////////////////////////////////////////
//// UI Commands
showFindDialog: function()
{
var win = openDialog("chrome://inspector/content/viewers/dom/findDialog.xul",
"_blank", "chrome,dependent", this.mFindType, this.mFindDir, this.mFindParams);
},
/**
* Toggles the setting for displaying anonymous content.
*/
toggleAnonContent: function toggleAnonContent()
{
var value = PrefUtils.getPref("inspector.dom.showAnon");
PrefUtils.setPref("inspector.dom.showAnon", !value);
},
/**
* Sets the UI and filters for anonymous content.
*
* @param aValue The value that we should be setting things to.
*/
setAnonContent: function setAnonContent(aValue)
{
this.mDOMView.showAnonymousContent = aValue;
this.mPanel.panelset.setCommandAttribute("cmd:toggleAnon", "checked",
aValue);
},
toggleSubDocs: function()
{
var val = !this.mDOMView.showSubDocuments;
this.mDOMView.showSubDocuments = val;
this.mPanel.panelset.setCommandAttribute("cmd:toggleSubDocs", "checked", val);
},
/**
* Toggles the visibility of Processing Instructions.
*/
toggleProcessingInstructions: function toggleProcessingInstructions()
{
var value = PrefUtils.getPref("inspector.dom.showProcessingInstructions");
PrefUtils.setPref("inspector.dom.showProcessingInstructions", !value);
},
/**
* Sets the visibility of Processing Instructions.
*
* @param aValue The visibility of the instructions.
*/
setProcessingInstructions: function setProcessingInstructions(aValue)
{
this.mPanel.panelset.setCommandAttribute("cmd:toggleProcessingInstructions",
"checked", aValue);
if (aValue) {
this.mDOMView.whatToShow |= NodeFilter.SHOW_PROCESSING_INSTRUCTION;
} else {
this.mDOMView.whatToShow &= ~NodeFilter.SHOW_PROCESSING_INSTRUCTION;
}
},
/**
* Toggle state of 'Show Accessible Nodes' option.
*/
toggleAccessibleNodes: function toggleAccessibleNodes()
{
var value = PrefUtils.getPref("inspector.dom.showAccessibleNodes");
PrefUtils.setPref("inspector.dom.showAccessibleNodes", !value);
},
/**
* Set state of 'Show Accessible Nodes' option.
*
* @param Boolean aValue - if true then accessible nodes will be shown
*/
setAccessibleNodes: function setAccessibleNodes(aValue)
{
if (!("@mozilla.org/accessibleRetrieval;1" in Components.classes))
aValue = false;
this.mDOMView.showAccessibleNodes = aValue;
this.mPanel.panelset.setCommandAttribute("cmd:toggleAccessibleNodes",
"checked", aValue);
this.onItemSelected();
},
/**
* Return state of 'Show Accessible Nodes' option.
*/
getAccessibleNodes: function getAccessibleNodes()
{
return this.mDOMView.showAccessibleNodes;
},
/**
* Toggles the value for whitespace nodes.
*/
toggleWhitespaceNodes: function toggleWhitespaceNodes()
{
var value = PrefUtils.getPref("inspector.dom.showWhitespaceNodes");
PrefUtils.setPref("inspector.dom.showWhitespaceNodes", !value);
},
/**
* Sets the UI for displaying whitespace nodes.
*
* @param aValue The value we are to use to determine the state of the UI.
*/
setWhitespaceNodes: function setWhitespaceNodes(aValue)
{
this.mPanel.panelset.setCommandAttribute("cmd:toggleWhitespaceNodes",
"checked", aValue);
this.mDOMView.showWhitespaceNodes = aValue;
},
showColumnsDialog: function()
{
var win = openDialog("chrome://inspector/content/viewers/dom/columnsDialog.xul",
"_blank", "chrome,dependent", this);
},
cmdShowPseudoClasses: function()
{
var idx = this.mDOMTree.currentIndex;
var node = this.getNodeFromRowIndex(idx);
if (node)
openDialog("chrome://inspector/content/viewers/dom/pseudoClassDialog.xul",
"_blank", "chrome", node);
},
cmdBlink: function()
{
this.flashElement(this.selectedNode);
},
cmdBlinkIsValid: function()
{
return this.selectedNode ? (this.selectedNode.nodeType == nsIDOMNode.ELEMENT_NODE) : false;
},
onItemSelected: function()
{
var idx = this.mDOMTree.currentIndex;
this.mSelection = this.getNodeFromRowIndex(idx);
this.mObsMan.dispatchEvent("selectionChange", { selection: this.mSelection } );
if (this.mSelection) {
if (this.mFlashSelected)
this.flashElement(this.mSelection);
}
viewer.pane.panelset.updateAllCommands();
},
setInitialSelection: function(aObject)
{
var fireSelected = this.mDOMTree.currentIndex == 0;
if (this.mPanel.params)
this.selectElementInTree(this.mPanel.params);
else
this.selectElementInTree(aObject, true);
if (fireSelected)
this.onItemSelected();
},
onContextCreate: function(aPP)
{
var mi, cmd;
for (var i = 0; i < aPP.childNodes.length; ++i) {
mi = aPP.childNodes[i];
if (mi.hasAttribute("observes")) {
cmd = document.getElementById(mi.getAttribute("observes"));
if (cmd && cmd.hasAttribute("isvalid")) {
try {
var isValid = new Function(cmd.getAttribute("isvalid"));
} catch (ex) { /* die quietly on syntax error in handler */ }
if (!isValid())
mi.setAttribute("hidden", "true");
else
mi.removeAttribute("hidden");
}
}
}
},
onCommandPopupShowing: function onCommandPopupShowing(aMenuPopup) {
for (var i = 0; i < aMenuPopup.childNodes.length; i++) {
var commandId = aMenuPopup.childNodes[i].getAttribute("command");
if (viewer.isCommandEnabled(commandId))
document.getElementById(commandId).setAttribute("disabled", "false");
else
document.getElementById(commandId).setAttribute("disabled", "true");
}
},
cmdInspectBrowserIsValid: function()
{
var node = viewer.selectedNode;
if (!node || node.nodeType != nsIDOMNode.ELEMENT_NODE) return false;
var n = node.localName.toLowerCase();
return n == "tabbrowser" || n == "browser" || n == "iframe" || n == "frame" || n == "editor";
},
cmdInspectBrowser: function()
{
var node = this.selectedNode;
var n = node.localName.toLowerCase();
if (node && n == "browser" && node.namespaceURI == kXULNSURI) {
// xul browser
this.subject = node.contentDocument;
} else if (node && n == "tabbrowser" && node.namespaceURI == kXULNSURI) {
// xul tabbrowser
this.subject = node.contentDocument;
} else if (n == "iframe" && node.namespaceURI == kXULNSURI) {
// xul iframe
this.subject = node.contentDocument;
} else if (n == "iframe" || n == "frame") {
// html iframe or frame
this.subject = node.contentDocument;
} else if (n == "editor") {
// editor
this.subject = node.contentDocument;
}
},
cmdInspectInNewWindow: function()
{
var node = this.selectedNode;
if (node)
inspectObject(node);
},
////////////////////////////////////////////////////////////////////////////
//// XML Serialization
cmdCopySelectedXML: function()
{
var node = this.selectedNode;
if (node) {
var xml = this.toXML(node);
var helper = XPCU.getService(kClipboardHelperCID, "nsIClipboardHelper");
helper.copyString(xml);
}
},
toXML: function(aNode)
{
// we'll use XML serializer, if available
if (typeof XMLSerializer != "undefined")
return (new XMLSerializer()).serializeToString(aNode);
else
return this._toXML(aNode, 0);
},
// not the most complete serialization ever conceived, but it'll do for now
_toXML: function(aNode, aLevel)
{
if (!aNode) return "";
var s = "";
var indent = "";
for (var i = 0; i < aLevel; ++i)
indent += " ";
var line = indent;
if (aNode.nodeType == nsIDOMNode.ELEMENT_NODE) {
line += "<" + aNode.localName;
var attrIndent = "";
for (i = 0; i < line.length; ++i)
attrIndent += " ";
for (i = 0; i < aNode.attributes.length; ++i) {
var a = aNode.attributes[i];
var attr = " " + a.localName + '="' + InsUtil.unicodeToEntity(a.nodeValue) + '"';
if (line.length + attr.length > 80) {
s += line + (i < aNode.attributes.length-1 ? "\n"+attrIndent : "");
line = "";
}
line += attr;
}
s += line;
if (aNode.childNodes.length == 0)
s += "/>\n";
else {
s += ">\n";
for (i = 0; i < aNode.childNodes.length; ++i)
s += this._toXML(aNode.childNodes[i], aLevel+1);
s += indent + "</" + aNode.localName + ">\n";
}
} else if (aNode.nodeType == nsIDOMNode.TEXT_NODE) {
s += InsUtil.unicodeToEntity(aNode.data);
} else if (aNode.nodeType == nsIDOMNode.COMMENT_NODE) {
s += line + "<!--" + InsUtil.unicodeToEntity(aNode.data) + "-->\n";
} else if (aNode.nodeType == nsIDOMNode.DOCUMENT_NODE) {
s += this._toXML(aNode.documentElement);
}
return s;
},
////////////////////////////////////////////////////////////////////////////
//// Click Selection
selectByClick: function()
{
if (this.mSelecting) {
this.stopSelectByClick();
this.removeClickListeners();
} else {
// wait until after user releases the mouse after selecting this command from a UI element
window.setTimeout("viewer.startSelectByClick()", 10);
}
},
startSelectByClick: function()
{
this.mSelecting = true;
this.mSelectDocs = this.getAllDocuments();
for (var i = 0; i < this.mSelectDocs.length; ++i) {
this.mSelectDocs[i].addEventListener("mousedown", MouseDownListener, true);
this.mSelectDocs[i].addEventListener("mouseup", EventCanceller, true);
this.mSelectDocs[i].addEventListener("click", ListenerRemover, true);
// If use moves the mouse out of the original target area, there
// will be no onclick event fired.... so we have to deal with
// that.
this.mSelectDocs[i].addEventListener("mouseout", ListenerRemover, true);
}
this.mPanel.panelset.setCommandAttribute("cmd:selectByClick", "checked", "true");
},
selectByClickOver: function(aTarget)
{
if (this.mLastOver)
this.flasher.stop();
this.flasher.element = aTarget;
this.flasher.start(-1, 1, true);
this.mLastOver = aTarget;
},
doSelectByClick: function(aTarget)
{
if (aTarget.nodeType == nsIDOMNode.TEXT_NODE)
aTarget = aTarget.parentNode;
this.stopSelectByClick();
this.selectElementInTree(aTarget);
},
stopSelectByClick: function()
{
this.mSelecting = false;
this.mPanel.panelset.setCommandAttribute("cmd:selectByClick", "checked", null);
},
removeClickListeners: function()
{
for (var i = 0; i < this.mSelectDocs.length; ++i) {
this.mSelectDocs[i].removeEventListener("mousedown", MouseDownListener, true);
this.mSelectDocs[i].removeEventListener("mouseup", EventCanceller, true);
this.mSelectDocs[i].removeEventListener("click", ListenerRemover, true);
this.mSelectDocs[i].removeEventListener("mouseout", ListenerRemover, true);
}
},
////////////////////////////////////////////////////////////////////////////
//// Find Methods
startFind: function(aType, aDir)
{
this.mFindType = aType;
this.mFindDir = aDir;
this.mFindParams = [];
for (var i = 2; i < arguments.length; ++i)
this.mFindParams[i-2] = arguments[i];
var fn = null;
switch (aType) {
case "id":
fn = "doFindElementById";
break;
case "tag":
fn = "doFindElementsByTagName";
break;
case "attr":
fn = "doFindElementsByAttr";
break;
};
this.mFindFn = fn;
this.mFindWalker = this.createDOMWalker(this.mDOMView.rootNode);
this.findNext();
},
findNext: function()
{
var walker = this.mFindWalker;
var result = null;
if (walker) {
while (walker.currentNode) {
//dump((walker.currentNode ? walker.currentNode.localName : "") + "\n");
if (this[this.mFindFn](walker)) {
result = walker.currentNode;
walker.nextNode();
break;
}
walker.nextNode();
}
if (result) {
this.selectElementInTree(result);
this.mDOMTree.focus();
} else {
var bundle = this.mPanel.panelset.stringBundle;
var msg = bundle.getString("findNodesDocumentEnd.message");
var title = bundle.getString("findNodesDocumentEnd.title");
var promptService = XPCU.getService(kPromptServiceCID, "nsIPromptService");
promptService.alert(window, title, msg);
}
}
},
doFindElementById: function(aWalker)
{
var re = new RegExp(this.mFindParams[0], "i");
var node = aWalker.currentNode;
if (!node)
return false;
if (node.nodeType != Components.interfaces.nsIDOMNode.ELEMENT_NODE)
return false;
for (var i = 0; i < node.attributes.length; i++) {
var attr = node.attributes[i];
if (attr.isId && re.test(attr.nodeValue)) {
return true;
}
}
return false;
},
doFindElementsByTagName: function(aWalker)
{
var re = new RegExp(this.mFindParams[0], "i");
return aWalker.currentNode
&& aWalker.currentNode.nodeType == nsIDOMNode.ELEMENT_NODE
&& re.test(aWalker.currentNode.localName);
},
doFindElementsByAttr: function(aWalker)
{
var re = new RegExp(this.mFindParams[1], "i");
return aWalker.currentNode
&& aWalker.currentNode.nodeType == nsIDOMNode.ELEMENT_NODE
&& (this.mFindParams[1] == ""
? aWalker.currentNode.hasAttribute(this.mFindParams[0])
: re.test(aWalker.currentNode.getAttribute(this.mFindParams[0])));
},
///////////////////////////////////////////////////////////////////////////
// Takes an element from the document being inspected, finds the treeitem
// which represents it in the DOM tree and selects that treeitem.
//
// @param aEl - element from the document being inspected
///////////////////////////////////////////////////////////////////////////
selectElementInTree: function(aEl, aExpand, aQuickie)
{
var bx = this.mDOMTree.treeBoxObject;
if (!aEl) {
bx.view.selection.select(null);
return false;
}
// Keep searching until a pre-created ancestor is
// found, and then open each ancestor until
// the found element is created
var walker = this.createDOMWalker(aEl);
var line = [];
var parent = aEl;
var index = null;
while (parent) {
index = this.getRowIndexFromNode(parent);
line.push(parent);
if (index < 0) {
// row for this node hasn't been created yet
parent = walker.parentNode();
} else
break;
}
// we've got all the ancestors, now open them
// one-by-one from the top on down
var view = bx.view;
var lastIndex;
for (var i = line.length-1; i >= 0; i--) {
index = this.getRowIndexFromNode(line[i]);
if (index < 0)
break; // can't find row, so stop trying to descend
if ((aExpand || i > 0) && !view.isContainerOpen(index)) {
view.toggleOpenState(index);
}
lastIndex = index;
}
if (!aQuickie && lastIndex >= 0) {
view.selection.select(lastIndex);
bx.ensureRowIsVisible(lastIndex);
}
return aQuickie;
},
///////////////////////////////////////////////////////////////////////////
// Remember which rows are open, and which row is selected. Then rebuild tree,
// re-open previously opened rows, and re-select previously selected row
///////////////////////////////////////////////////////////////////////////
rebuild: function()
{
var selNode = this.getNodeFromRowIndex(this.mDOMTree.currentIndex);
this.mDOMTree.view.selection.select(null);
var opened = [];
var i;
for (i = 0; i < this.mDOMView.rowCount; ++i) {
if (this.mDOMView.isContainerOpen(i))
opened.push(this.getNodeFromRowIndex(i));
}
this.mDOMView.rebuild();
for (i = 0; i < opened.length; ++i)
this.selectElementInTree(opened[i], true, true);
this.selectElementInTree(selNode);
},
createDOMWalker: function(aRoot)
{
var walker = XPCU.createInstance("@mozilla.org/inspector/deep-tree-walker;1", "inIDeepTreeWalker");
walker.showAnonymousContent = this.mDOMView.showAnonymousContent;
walker.showSubDocuments = this.mDOMView.showSubDocuments;
walker.init(aRoot, Components.interfaces.nsIDOMNodeFilter.SHOW_ALL);
return walker;
},
////////////////////////////////////////////////////////////////////////////
//// Columns
initColumns: function()
{
var colPref = PrefUtils.getPref("inspector.dom.columns");
var cols = colPref.split(",")
this.mColumns = cols;
this.mColumnHash = {};
},
// XX re-implement custom columns code some-day
saveColumns: function()
{
var cols = this.mColumns.join(",");
PrefUtils.setPref("inspector.dom.columns", cols);
},
////////////////////////////////////////////////////////////////////////////
//// Flashing
get flasher()
{
if (!("mFlasher" in this)) {
this.mFlasher = new Flasher(PrefUtils.getPref("inspector.blink.border-color"),
PrefUtils.getPref("inspector.blink.border-width"),
PrefUtils.getPref("inspector.blink.duration"),
PrefUtils.getPref("inspector.blink.speed"),
PrefUtils.getPref("inspector.blink.invert"));
}
return this.mFlasher;
},
flashElement: function(aElement)
{
// make sure we only try to flash element nodes, and don't
// flash the documentElement (it's too darn big!)
if (aElement.nodeType == nsIDOMNode.ELEMENT_NODE &&
aElement != aElement.ownerDocument.documentElement) {
var flasher = this.flasher;
if (flasher.flashing)
flasher.stop();
try {
flasher.element = aElement;
flasher.start();
} catch (ex) {
}
}
},
/**
* Toggles the preference for flashing selected elements.
*/
toggleFlashSelected: function toggleFlashSelected()
{
var value = PrefUtils.getPref("inspector.blink.on");
PrefUtils.setPref("inspector.blink.on", !value);
},
/**
* Sets the object's value and the command for flashing selected objects.
*
* @param aValue The value to set it to.
*/
setFlashSelected: function setFlashSelected(aValue)
{
this.mFlashSelected = aValue;
this.mPanel.panelset.setCommandAttribute("cmd:flashSelected", "checked",
aValue);
},
////////////////////////////////////////////////////////////////////////////
//// Prefs
/**
* Called by PrefChangeObserver.
*
* @param aName The name of the preference that has been changed.
*/
onPrefChanged: function onPrefChanged(aName)
{
var value = PrefUtils.getPref(aName);
if (aName == "inspector.dom.showAnon") {
this.setAnonContent(value);
} else if (aName == "inspector.dom.showProcessingInstructions") {
this.setProcessingInstructions(value);
} else if (aName == "inspector.dom.showAccessibleNodes") {
this.setAccessibleNodes(value);
} else if (aName == "inspector.dom.showWhitespaceNodes") {
this.setWhitespaceNodes(value);
} else if (aName == "inspector.blink.on") {
this.setFlashSelected(value);
// don't need to rebuild for this
return;
} else if (this.mFlasher) {
if (aName == "inspector.blink.border-color") {
this.mFlasher.color = value;
} else if (aName == "inspector.blink.border-width") {
this.mFlasher.thickness = value;
} else if (aName == "inspector.blink.duration") {
this.mFlasher.duration = value;
} else if (aName == "inspector.blink.speed") {
this.mFlasher.speed = value;
} else if (aName == "inspector.blink.invert") {
this.mFlasher.invert = value;
}
// don't need to rebuild for these
return;
}
this.rebuild();
},
////////////////////////////////////////////////////////////////////////////
//// Uncategorized
getAllDocuments: function()
{
var doc = this.mDOMView.rootNode;
var results = [doc];
this.findDocuments(doc, results);
return results;
},
findDocuments: function(aDoc, aArray)
{
this.addKidsToArray(aDoc.getElementsByTagName("frame"), aArray);
this.addKidsToArray(aDoc.getElementsByTagName("iframe"), aArray);
this.addKidsToArray(aDoc.getElementsByTagNameNS(kXULNSURI, "browser"), aArray);
this.addKidsToArray(aDoc.getElementsByTagNameNS(kXULNSURI, "tabbrowser"), aArray);
this.addKidsToArray(aDoc.getElementsByTagNameNS(kXULNSURI, "editor"), aArray);
},
addKidsToArray: function(aKids, aArray)
{
for (var i = 0; i < aKids.length; ++i) {
try {
aArray.push(aKids[i].contentDocument);
// Now recurse down into the kid and look for documents there
this.findDocuments(aKids[i].contentDocument, aArray);
} catch (ex) {
// if we can't access the content document, skip it
}
}
},
get selectedNode()
{
var index = this.mDOMTree.currentIndex;
return index >= 0 ? this.getNodeFromRowIndex(index) : null;
},
getNodeFromRowIndex: function(aIndex)
{
try {
return this.mDOMView.getNodeFromRowIndex(aIndex);
} catch (ex) {
return null;
}
},
getRowIndexFromNode: function(aNode)
{
try {
return this.mDOMView.getRowIndexFromNode(aNode);
} catch (ex) {
return -1;
}
}
};
////////////////////////////////////////////////////////////////////////////
//// Command Objects
function cmdEditDelete() {}
cmdEditDelete.prototype =
{
node: null,
nextSibling: null,
parentNode: null,
// remove this line for bug 179621, Phase Three
txnType: "standard",
// required for nsITransaction
QueryInterface: txnQueryInterface,
merge: txnMerge,
isTransient: false,
redoTransaction: txnRedoTransaction,
doTransaction: function()
{
var node = this.node ? this.node : viewer.selectedNode;
if (node) {
this.node = node;
this.nextSibling = node.nextSibling;
this.parentNode = node.parentNode;
var selectNode = this.nextSibling;
if (!selectNode) selectNode = node.previousSibling;
if (!selectNode) selectNode = this.parentNode;
viewer.selectElementInTree(selectNode);
node.parentNode.removeChild(node);
}
},
undoTransaction: function()
{
if (this.node)
this.parentNode.insertBefore(this.node, this.nextSibling);
viewer.selectElementInTree(this.node);
}
};
function cmdEditCut() {}
cmdEditCut.prototype =
{
cmdCopy: null,
cmdDelete: null,
// remove this line for bug 179621, Phase Three
txnType: "standard",
// required for nsITransaction
QueryInterface: txnQueryInterface,
merge: txnMerge,
isTransient: false,
redoTransaction: txnRedoTransaction,
doTransaction: function()
{
if (!this.cmdCopy) {
this.cmdDelete = new cmdEditDelete();
this.cmdCopy = new cmdEditCopy();
}
this.cmdCopy.doTransaction();
this.cmdDelete.doTransaction();
},
undoTransaction: function()
{
this.cmdDelete.undoTransaction();
}
};
function cmdEditCopy() {}
cmdEditCopy.prototype =
{
copiedNode: null,
// remove this line for bug 179621, Phase Three
txnType: "standard",
// required for nsITransaction
QueryInterface: txnQueryInterface,
merge: txnMerge,
isTransient: true,
redoTransaction: txnRedoTransaction,
doTransaction: function()
{
var copiedNode = null;
if (!this.copiedNode) {
copiedNode = viewer.selectedNode.cloneNode(true);
if (copiedNode) {
this.copiedNode = copiedNode;
}
} else
copiedNode = this.copiedNode;
viewer.pane.panelset.setClipboardData(copiedNode, "inspector/dom-node", null);
}
};
/**
* Pastes the node on the clipboard as the next sibling of the selected node.
*/
function cmdEditPaste() {}
cmdEditPaste.prototype =
{
pastedNode: null,
pastedBefore: null,
// remove this line for bug 179621, Phase Three
txnType: "standard",
// required for nsITransaction
QueryInterface: txnQueryInterface,
merge: txnMerge,
isTransient: false,
redoTransaction: txnRedoTransaction,
doTransaction: function doTransaction()
{
var node = this.pastedNode ? this.pastedNode : viewer.pane.panelset.getClipboardData();
var selected = this.pastedBefore ? this.pastedBefore : viewer.selectedNode;
if (selected) {
this.pastedNode = node.cloneNode(true);
this.pastedBefore = selected;
selected.parentNode.insertBefore(this.pastedNode, selected.nextSibling);
return false;
}
return true;
},
undoTransaction: function undoTransaction()
{
if (this.pastedNode)
this.pastedNode.parentNode.removeChild(this.pastedNode);
}
};
/**
* Pastes the node on the clipboard as the previous sibling of the selected
* node.
*/
function cmdEditPasteBefore() {}
cmdEditPasteBefore.prototype =
{
pastedNode: null,
pastedBefore: null,
// remove this line for bug 179621, Phase Three
txnType: "standard",
// required for nsITransaction
QueryInterface: txnQueryInterface,
merge: txnMerge,
isTransient: false,
redoTransaction: txnRedoTransaction,
doTransaction: function doTransaction()
{
var node = this.pastedNode ? this.pastedNode : viewer.pane.panelset.getClipboardData();
var selected = this.pastedBefore ? this.pastedBefore : viewer.selectedNode;
if (selected) {
this.pastedNode = node.cloneNode(true);
this.pastedBefore = selected;
selected.parentNode.insertBefore(this.pastedNode, selected);
return false;
}
return true;
},
undoTransaction: function undoTransaction()
{
if (this.pastedNode)
this.pastedNode.parentNode.removeChild(this.pastedNode);
}
};
/**
* Pastes the node on the clipboard in the place of the selected node,
* overwriting it.
*/
function cmdEditPasteReplace() {}
cmdEditPasteReplace.prototype =
{
pastedNode: null,
originalNode: null,
// remove this line for bug 179621, Phase Three
txnType: "standard",
// required for nsITransaction
QueryInterface: txnQueryInterface,
merge: txnMerge,
isTransient: false,
redoTransaction: txnRedoTransaction,
doTransaction: function doTransaction()
{
var node = this.pastedNode ? this.pastedNode : viewer.pane.panelset.getClipboardData();
var selected = this.originalNode ? this.originalNode : viewer.selectedNode;
if (selected) {
this.pastedNode = node.cloneNode(true);
this.originalNode = selected;
selected.parentNode.replaceChild(this.pastedNode, selected);
return false;
}
return true;
},
undoTransaction: function undoTransaction()
{
if (this.pastedNode)
this.pastedNode.parentNode.replaceChild(this.originalNode, this.pastedNode);
}
};
/**
* Pastes the node on the clipboard as the first child of the selected node.
*/
function cmdEditPasteFirstChild() {}
cmdEditPasteFirstChild.prototype =
{
pastedNode: null,
pastedBefore: null,
// remove this line for bug 179621, Phase Three
txnType: "standard",
// required for nsITransaction
QueryInterface: txnQueryInterface,
merge: txnMerge,
isTransient: false,
redoTransaction: txnRedoTransaction,
doTransaction: function doTransaction()
{
var node = this.pastedNode ? this.pastedNode : viewer.pane.panelset.getClipboardData();
var selected = this.pastedBefore ? this.pastedBefore : viewer.selectedNode;
if (selected) {
this.pastedNode = node.cloneNode(true);
this.pastedBefore = selected.firstChild;
selected.insertBefore(this.pastedNode, this.pastedBefore);
return false;
}
return true;
},
undoTransaction: function undoTransaction()
{
if (this.pastedNode)
this.pastedNode.parentNode.removeChild(this.pastedNode);
}
};
/**
* Pastes the node on the clipboard as the last child of the selected node.
*/
function cmdEditPasteLastChild() {}
cmdEditPasteLastChild.prototype =
{
pastedNode: null,
selectedNode: null,
// remove this line for bug 179621, Phase Three
txnType: "standard",
// required for nsITransaction
QueryInterface: txnQueryInterface,
merge: txnMerge,
isTransient: false,
redoTransaction: txnRedoTransaction,
doTransaction: function doTransaction()
{
var node = this.pastedNode ? this.pastedNode : viewer.pane.panelset.getClipboardData();
var selected = this.selectedNode ? this.selectedNode : viewer.selectedNode;
if (selected) {
this.pastedNode = node.cloneNode(true);
this.selectedNode = selected;
selected.appendChild(this.pastedNode);
return false;
}
return true;
},
undoTransaction: function undoTransaction()
{
if (this.selectedNode)
this.selectedNode.removeChild(this.pastedNode);
}
};
/**
* Pastes the node on the clipboard in the place of the selected node, making
* the selected node its child.
*/
function cmdEditPasteAsParent() {}
cmdEditPasteAsParent.prototype =
{
pastedNode: null,
originalNode: null,
originalParentNode: null,
// remove this line for bug 179621, Phase Three
txnType: "standard",
// required for nsITransaction
QueryInterface: txnQueryInterface,
merge: txnMerge,
isTransient: false,
redoTransaction: txnRedoTransaction,
doTransaction: function doTransaction()
{
var node = this.pastedNode ? this.pastedNode : viewer.pane.panelset.getClipboardData();
var selected = this.originalNode ? this.originalNode : viewer.selectedNode;
var parent = this.originalParentNode ? this.originalParentNode : selected.parentNode;
if (selected) {
this.pastedNode = node.cloneNode(true);
this.originalNode = selected;
this.originalParentNode = parent;
parent.replaceChild(this.pastedNode, selected);
this.pastedNode.appendChild(selected);
return false;
}
return true;
},
undoTransaction: function undoTransaction()
{
if (this.pastedNode)
this.originalParentNode.replaceChild(this.originalNode, this.pastedNode);
}
};
/**
* Generic prototype for inserting a new node somewhere
*/
function InsertNode() {}
InsertNode.prototype =
{
insertedNode: null,
originalNode: null,
attr: null,
// remove this line for bug 179621, Phase Three
txnType: "standard",
// required for nsITransaction
QueryInterface: txnQueryInterface,
merge: txnMerge,
isTransient: false,
redoTransaction: txnRedoTransaction,
insertNode: function insertNode()
{
},
createNode: function createNode()
{
var doc = this.originalNode.ownerDocument;
if (!this.attr) {
this.attr = { type: null, value: null, namespaceURI: null, accepted: false };
window.openDialog("chrome://inspector/content/viewers/dom/insertDialog.xul",
"insert", "chrome,modal,centerscreen", doc, this.attr);
}
if (this.attr.accepted) {
switch (this.attr.type) {
case nsIDOMNode.ELEMENT_NODE:
this.insertedNode = doc.createElementNS(this.attr.namespaceURI, this.attr.value);
break;
case nsIDOMNode.TEXT_NODE:
this.insertedNode = doc.createTextNode(this.attr.value);
break;
}
return true;
}
return false;
},
doTransaction: function doTransaction()
{
var selected = this.originalNode ? this.originalNode : viewer.selectedNode;
if (selected) {
this.originalNode = selected;
if (this.createNode()) {
this.insertNode();
return false;
}
}
return true;
},
undoTransaction: function undoTransaction()
{
if (this.insertedNode)
this.insertedNode.parentNode.removeChild(this.insertedNode);
}
};
/**
* Inserts a node after the selected node.
*/
function cmdEditInsertAfter() {}
cmdEditInsertAfter.prototype = new InsertNode();
cmdEditInsertAfter.prototype.insertNode = function insertNode() {
this.originalNode.parentNode.insertBefore(this.insertedNode, this.originalNode.nextSibling);
};
/**
* Inserts a node before the selected node.
*/
function cmdEditInsertBefore() {}
cmdEditInsertBefore.prototype = new InsertNode();
cmdEditInsertBefore.prototype.insertNode = function insertNode() {
this.originalNode.parentNode.insertBefore(this.insertedNode, this.originalNode);
};
/**
* Inserts a node as the first child of the selected node.
*/
function cmdEditInsertFirstChild() {}
cmdEditInsertFirstChild.prototype = new InsertNode();
cmdEditInsertFirstChild.prototype.insertNode = function insertNode() {
this.originalNode.insertBefore(this.insertedNode, this.originalNode.firstChild);
};
/**
* Inserts a node as the last child of the selected node.
*/
function cmdEditInsertLastChild() {}
cmdEditInsertLastChild.prototype = new InsertNode();
cmdEditInsertLastChild.prototype.insertNode = function insertNode() {
this.originalNode.appendChild(this.insertedNode);
};
////////////////////////////////////////////////////////////////////////////
//// Listener Objects
var MouseDownListener = {
handleEvent: function(aEvent)
{
var target = viewer.mDOMView.showAnonymousContent ? aEvent.originalTarget : aEvent.target;
if (aEvent.type == "mousedown") {
aEvent.stopPropagation();
aEvent.preventDefault();
viewer.doSelectByClick(target);
}
else if (aEvent.type == "mouseover")
viewer.selectByClickOver(target);
}
};
var EventCanceller = {
handleEvent: function(aEvent)
{
aEvent.stopPropagation();
aEvent.preventDefault();
}
};
var ListenerRemover = {
handleEvent: function(aEvent)
{
if (!viewer.mSelecting) {
if (aEvent.type == "click") {
aEvent.stopPropagation();
aEvent.preventDefault();
}
viewer.removeClickListeners();
}
}
};
var PrefChangeObserver = {
observe: function(aSubject, aTopic, aData)
{
viewer.onPrefChanged(aData);
}
};
function gColumnAddListener(aIndex)
{
viewer.onColumnAdd(aIndex);
}
function gColumnRemoveListener(aIndex)
{
viewer.onColumnRemove(aIndex);
}
function dumpDOM2(aNode)
{
dump(DOMViewer.prototype.toXML(aNode));
}