mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-10 01:40:17 +01:00
1074 lines
30 KiB
JavaScript
1074 lines
30 KiB
JavaScript
|
/* ***** 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)
|
||
|
*
|
||
|
* 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;
|
||
|
var gEntityConverter;
|
||
|
|
||
|
//////////// 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";
|
||
|
|
||
|
//////////////////////////////////////////////////
|
||
|
|
||
|
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 = 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
|
||
|
|
||
|
initialize: function(aPane)
|
||
|
{
|
||
|
//this.initColumns();
|
||
|
|
||
|
this.mPanel = aPane;
|
||
|
aPane.notifyViewerReady(this);
|
||
|
|
||
|
this.setAnonContent(PrefUtils.getPref("inspector.dom.showAnon"), false);
|
||
|
this.setWhitespaceNodes(PrefUtils.getPref("inspector.dom.showWhitespaceNodes"));
|
||
|
this.setFlashSelected(PrefUtils.getPref("inspector.blink.on"));
|
||
|
},
|
||
|
|
||
|
destroy: function()
|
||
|
{
|
||
|
this.mDOMTree.treeBoxObject.view = null;
|
||
|
},
|
||
|
|
||
|
isCommandEnabled: function(aCommand)
|
||
|
{
|
||
|
switch (aCommand) {
|
||
|
case "cmdEditPaste":
|
||
|
var canPaste = this.mPanel.panelset.clipboardFlavor == "inspector/dom-node";
|
||
|
if (canPaste) {
|
||
|
var node = this.mPanel.panelset.getClipboardData();
|
||
|
canPaste = node ? node.nodeType != Node.ATTRIBUTE_NODE : false;
|
||
|
}
|
||
|
return canPaste;
|
||
|
case "cmdEditInsert":
|
||
|
return false;
|
||
|
case "cmdEditCut":
|
||
|
case "cmdEditCopy":
|
||
|
case "cmdEditDelete":
|
||
|
return this.selectedNode != null;
|
||
|
}
|
||
|
return false;
|
||
|
},
|
||
|
|
||
|
getCommand: function(aCommand)
|
||
|
{
|
||
|
switch (aCommand) {
|
||
|
case "cmdEditCut":
|
||
|
return new cmdEditCut();
|
||
|
case "cmdEditCopy":
|
||
|
return new cmdEditCopy();
|
||
|
case "cmdEditPaste":
|
||
|
return new cmdEditPaste();
|
||
|
case "cmdEditDelete":
|
||
|
return new cmdEditDelete();
|
||
|
}
|
||
|
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);
|
||
|
},
|
||
|
|
||
|
toggleAnonContent: function(aRebuild)
|
||
|
{
|
||
|
this.setAnonContent(!this.mDOMView.showAnonymousContent, aRebuild);
|
||
|
},
|
||
|
|
||
|
setAnonContent: function(aValue, aRebuild)
|
||
|
{
|
||
|
this.mDOMView.showAnonymousContent = aValue;
|
||
|
this.mPanel.panelset.setCommandAttribute("cmd:toggleAnon", "checked", aValue);
|
||
|
PrefUtils.setPref("inspector.dom.showAnon", aValue);
|
||
|
|
||
|
if (aRebuild) {
|
||
|
this.rebuild();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
toggleSubDocs: function()
|
||
|
{
|
||
|
var val = !this.mDOMView.showSubDocuments;
|
||
|
this.mDOMView.showSubDocuments = val;
|
||
|
this.mPanel.panelset.setCommandAttribute("cmd:toggleSubDocs", "checked", val);
|
||
|
},
|
||
|
|
||
|
setWhitespaceNodes: function(aValue)
|
||
|
{
|
||
|
// Do this first so we ensure the checkmark is set in the case
|
||
|
// we are starting with whitespace nodes enabled.
|
||
|
this.mPanel.panelset.setCommandAttribute("cmd:toggleWhitespaceNodes", "checked", aValue);
|
||
|
|
||
|
// The rest of the stuff is redundant to do if we are not changing
|
||
|
// the value, so just bail here if we're setting the same value.
|
||
|
if (this.mDOMView.showWhitespaceNodes == aValue) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this.mDOMView.showWhitespaceNodes = aValue;
|
||
|
PrefUtils.setPref("inspector.dom.showWhitespaceNodes", aValue);
|
||
|
this.rebuild();
|
||
|
},
|
||
|
|
||
|
toggleWhitespaceNodes: function()
|
||
|
{
|
||
|
this.setWhitespaceNodes(!this.mDOMView.showWhitespaceNodes);
|
||
|
},
|
||
|
|
||
|
toggleAttributes: function()
|
||
|
{
|
||
|
alert("NOT YET IMPLEMENTED");
|
||
|
},
|
||
|
|
||
|
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 == Node.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);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
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");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
cmdInspectBrowserIsValid: function()
|
||
|
{
|
||
|
var node = viewer.selectedNode;
|
||
|
if (!node || node.nodeType != Node.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)
|
||
|
{
|
||
|
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 == Node.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 + '="' + 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 == Node.TEXT_NODE) {
|
||
|
s += unicodeToEntity(aNode.data);
|
||
|
} else if (aNode.nodeType == Node.COMMENT_NODE) {
|
||
|
s += line + "<!--" + unicodeToEntity(aNode.data) + "-->\n";
|
||
|
}
|
||
|
|
||
|
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 == Node.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");
|
||
|
|
||
|
return aWalker.currentNode
|
||
|
&& "id" in aWalker.currentNode
|
||
|
&& re.test(aWalker.currentNode.id);
|
||
|
},
|
||
|
|
||
|
doFindElementsByTagName: function(aWalker)
|
||
|
{
|
||
|
var re = new RegExp(this.mFindParams[0], "i");
|
||
|
|
||
|
return aWalker.currentNode
|
||
|
&& aWalker.currentNode.nodeType == Node.ELEMENT_NODE
|
||
|
&& re.test(aWalker.currentNode.localName);
|
||
|
},
|
||
|
|
||
|
doFindElementsByAttr: function(aWalker)
|
||
|
{
|
||
|
var re = new RegExp(this.mFindParams[1], "i");
|
||
|
|
||
|
return aWalker.currentNode
|
||
|
&& aWalker.currentNode.nodeType == Node.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 == Node.ELEMENT_NODE &&
|
||
|
aElement != aElement.ownerDocument.documentElement) {
|
||
|
var flasher = this.flasher;
|
||
|
|
||
|
if (flasher.flashing)
|
||
|
flasher.stop();
|
||
|
|
||
|
try {
|
||
|
flasher.element = aElement;
|
||
|
flasher.start();
|
||
|
} catch (ex) {
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
toggleFlashSelected: function()
|
||
|
{
|
||
|
this.setFlashSelected(!this.mFlashSelected);
|
||
|
},
|
||
|
|
||
|
setFlashSelected: function(aValue)
|
||
|
{
|
||
|
this.mFlashSelected = aValue;
|
||
|
this.mPanel.panelset.setCommandAttribute("cmd:flashSelected", "checked", aValue);
|
||
|
PrefUtils.setPref("inspector.blink.on", aValue);
|
||
|
},
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////
|
||
|
//// Prefs
|
||
|
|
||
|
onPrefChanged: function(aName)
|
||
|
{
|
||
|
if (aName == "inspector.dom.showAnon")
|
||
|
this.setFlashSelected(PrefUtils.getPref("inspector.blink.on"));
|
||
|
|
||
|
if (aName == "inspector.blink.on")
|
||
|
this.setFlashSelected(PrefUtils.getPref("inspector.blink.on"));
|
||
|
|
||
|
if (this.mFlasher) {
|
||
|
if (aName == "inspector.blink.border-color") {
|
||
|
this.mFlasher.color = PrefUtils.getPref("inspector.blink.border-color");
|
||
|
} else if (aName == "inspector.blink.border-width") {
|
||
|
this.mFlasher.thickness = PrefUtils.getPref("inspector.blink.border-width");
|
||
|
} else if (aName == "inspector.blink.duration") {
|
||
|
this.mFlasher.duration = PrefUtils.getPref("inspector.blink.duration");
|
||
|
} else if (aName == "inspector.blink.speed") {
|
||
|
this.mFlasher.speed = PrefUtils.getPref("inspector.blink.speed");
|
||
|
} else if (aName == "inspector.blink.invert") {
|
||
|
this.mFlasher.invert = PrefUtils.getPref("inspector.blink.invert");
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////
|
||
|
//// 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;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
canPaste: function(aFlavour)
|
||
|
{
|
||
|
return aFlavour == "Inspector-DOM-Node";
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////
|
||
|
//// 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) {
|
||
|
if (this.nextSibling)
|
||
|
this.parentNode.insertBefore(this.node, this.nextSibling);
|
||
|
else
|
||
|
this.parentNode.appendChild(this.node);
|
||
|
}
|
||
|
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();
|
||
|
this.cmdCopy.undoTransaction();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
function cmdEditCopy() {}
|
||
|
cmdEditCopy.prototype =
|
||
|
{
|
||
|
copiedNode: null,
|
||
|
previousData: null,
|
||
|
previousFlavor: 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 copiedNode = null;
|
||
|
if (!this.copiedNode) {
|
||
|
copiedNode = viewer.selectedNode;
|
||
|
if (copiedNode) {
|
||
|
this.copiedNode = copiedNode;
|
||
|
this.previousData = viewer.pane.panelset.getClipboardData();
|
||
|
this.previousFlavor = viewer.pane.panelset.clipboardFlavor;
|
||
|
}
|
||
|
} else
|
||
|
copiedNode = this.copiedNode;
|
||
|
|
||
|
viewer.pane.panelset.setClipboardData(copiedNode, "inspector/dom-node");
|
||
|
},
|
||
|
|
||
|
undoTransaction: function()
|
||
|
{
|
||
|
viewer.pane.panelset.setClipboardData(this.previousData, this.previousFlavor);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
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()
|
||
|
{
|
||
|
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;
|
||
|
if (selected.nextSibling)
|
||
|
selected.parentNode.insertBefore(this.pastedNode, selected.nextSibling);
|
||
|
else
|
||
|
selected.parentNode.appendChild(this.pastedNode);
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
},
|
||
|
|
||
|
undoTransaction: function()
|
||
|
{
|
||
|
if (this.pastedNode)
|
||
|
this.pastedNode.parentNode.removeChild(this.pastedNode);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////
|
||
|
//// 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));
|
||
|
}
|
||
|
|
||
|
function unicodeToEntity(text)
|
||
|
{
|
||
|
const charTable = {
|
||
|
'&': "&",
|
||
|
'<': "<",
|
||
|
'>': ">",
|
||
|
'"': """
|
||
|
};
|
||
|
|
||
|
function charTableLookup(letter) {
|
||
|
return charTable[letter];
|
||
|
}
|
||
|
|
||
|
function convertEntity(letter) {
|
||
|
try {
|
||
|
return gEntityConverter.ConvertToEntity(letter, entityVersion);
|
||
|
} catch (ex) {
|
||
|
return letter;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!gEntityConverter) {
|
||
|
try {
|
||
|
gEntityConverter =
|
||
|
Components.classes["@mozilla.org/intl/entityconverter;1"]
|
||
|
.createInstance(Components.interfaces.nsIEntityConverter);
|
||
|
} catch (ex) { }
|
||
|
}
|
||
|
|
||
|
const entityVersion = Components.interfaces.nsIEntityConverter.entityW3C;
|
||
|
|
||
|
var str = text;
|
||
|
|
||
|
// replace chars in our charTable
|
||
|
str = str.replace(/[<>&"]/g, charTableLookup);
|
||
|
|
||
|
// replace chars > 0x7f via nsIEntityConverter
|
||
|
str = str.replace(/[^\0-\u007f]/g, convertEntity);
|
||
|
|
||
|
return str;
|
||
|
}
|