зеркало из https://github.com/mozilla/gecko-dev.git
835 строки
25 KiB
JavaScript
835 строки
25 KiB
JavaScript
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
/* ***** 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 the Mozilla Tree Panel.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* The Mozilla Foundation.
|
|
* Portions created by the Initial Developer are Copyright (C) 2011
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Rob Campbell <rcampbell@mozilla.com> (original author)
|
|
* Mihai Șucan <mihai.sucan@gmail.com>
|
|
* Julian Viereck <jviereck@mozilla.com>
|
|
* Paul Rouget <paul@mozilla.com>
|
|
* Kyle Simpson <getify@mozilla.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 ***** */
|
|
|
|
const Cu = Components.utils;
|
|
|
|
Cu.import("resource:///modules/domplate.jsm");
|
|
Cu.import("resource:///modules/InsideOutBox.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
|
|
var EXPORTED_SYMBOLS = ["TreePanel", "DOMHelpers"];
|
|
|
|
const INSPECTOR_URI = "chrome://browser/content/inspector.html";
|
|
|
|
/**
|
|
* TreePanel
|
|
* A container for the Inspector's HTML Tree Panel widget constructor function.
|
|
* @param aContext nsIDOMWindow (xulwindow)
|
|
* @param aIUI global InspectorUI object
|
|
*/
|
|
function TreePanel(aContext, aIUI) {
|
|
this._init(aContext, aIUI);
|
|
};
|
|
|
|
TreePanel.prototype = {
|
|
showTextNodesWithWhitespace: false,
|
|
id: "treepanel", // DO NOT LOCALIZE
|
|
openInDock: true,
|
|
|
|
/**
|
|
* The tree panel container element.
|
|
* @returns xul:panel|xul:vbox|null
|
|
* xul:panel is returned when the tree panel is not docked, or
|
|
* xul:vbox when when the tree panel is docked.
|
|
* null is returned when no container is available.
|
|
*/
|
|
get container()
|
|
{
|
|
if (this.openInDock) {
|
|
return this.document.getElementById("inspector-tree-box");
|
|
}
|
|
|
|
return this.document.getElementById("inspector-tree-panel");
|
|
},
|
|
|
|
/**
|
|
* Main TreePanel boot-strapping method. Initialize the TreePanel with the
|
|
* originating context and the InspectorUI global.
|
|
* @param aContext nsIDOMWindow (xulwindow)
|
|
* @param aIUI global InspectorUI object
|
|
*/
|
|
_init: function TP__init(aContext, aIUI)
|
|
{
|
|
this.IUI = aIUI;
|
|
this.window = aContext;
|
|
this.document = this.window.document;
|
|
|
|
domplateUtils.setDOM(this.window);
|
|
|
|
this.DOMHelpers = new DOMHelpers(this.window);
|
|
|
|
let isOpen = this.isOpen.bind(this);
|
|
|
|
this.registrationObject = {
|
|
id: this.id,
|
|
label: this.IUI.strings.GetStringFromName("htmlPanel.label"),
|
|
tooltiptext: this.IUI.strings.GetStringFromName("htmlPanel.tooltiptext"),
|
|
accesskey: this.IUI.strings.GetStringFromName("htmlPanel.accesskey"),
|
|
context: this,
|
|
get isOpen() isOpen(),
|
|
show: this.open,
|
|
hide: this.close,
|
|
onSelect: this.select,
|
|
panel: this.openInDock ? null : this.container,
|
|
unregister: this.destroy,
|
|
};
|
|
this.editingEvents = {};
|
|
|
|
if (!this.openInDock) {
|
|
this._boundClose = this.close.bind(this);
|
|
this.container.addEventListener("popuphiding", this._boundClose, false);
|
|
}
|
|
|
|
// Register the HTML panel with the highlighter
|
|
this.IUI.registerTool(this.registrationObject);
|
|
},
|
|
|
|
/**
|
|
* Initialization function for the TreePanel.
|
|
*/
|
|
initializeIFrame: function TP_initializeIFrame()
|
|
{
|
|
if (!this.initializingTreePanel || this.treeLoaded) {
|
|
return;
|
|
}
|
|
this.treeBrowserDocument = this.treeIFrame.contentDocument;
|
|
this.treePanelDiv = this.treeBrowserDocument.createElement("div");
|
|
this.treeBrowserDocument.body.appendChild(this.treePanelDiv);
|
|
this.treePanelDiv.ownerPanel = this;
|
|
this.ioBox = new InsideOutBox(this, this.treePanelDiv);
|
|
this.ioBox.createObjectBox(this.IUI.win.document.documentElement);
|
|
this.treeLoaded = true;
|
|
this.treeIFrame.addEventListener("click", this.onTreeClick.bind(this), false);
|
|
this.treeIFrame.addEventListener("dblclick", this.onTreeDblClick.bind(this), false);
|
|
this.treeIFrame.focus();
|
|
delete this.initializingTreePanel;
|
|
Services.obs.notifyObservers(null,
|
|
this.IUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, null);
|
|
if (this.IUI.selection)
|
|
this.select(this.IUI.selection, true);
|
|
},
|
|
|
|
/**
|
|
* Open the inspector's tree panel and initialize it.
|
|
*/
|
|
open: function TP_open()
|
|
{
|
|
if (this.initializingTreePanel && !this.treeLoaded) {
|
|
return;
|
|
}
|
|
|
|
this.initializingTreePanel = true;
|
|
if (!this.openInDock)
|
|
this.container.hidden = false;
|
|
|
|
this.treeIFrame = this.document.getElementById("inspector-tree-iframe");
|
|
if (!this.treeIFrame) {
|
|
this.treeIFrame = this.document.createElement("iframe");
|
|
this.treeIFrame.setAttribute("id", "inspector-tree-iframe");
|
|
this.treeIFrame.flex = 1;
|
|
this.treeIFrame.setAttribute("type", "content");
|
|
}
|
|
|
|
if (this.openInDock) { // Create vbox
|
|
this.openDocked();
|
|
return;
|
|
}
|
|
|
|
let resizerBox = this.document.getElementById("tree-panel-resizer-box");
|
|
this.treeIFrame = this.container.insertBefore(this.treeIFrame, resizerBox);
|
|
|
|
let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
|
|
{
|
|
this.treeIFrame.removeEventListener("load",
|
|
boundLoadedInitializeTreePanel, true);
|
|
this.initializeIFrame();
|
|
}.bind(this);
|
|
|
|
let boundTreePanelShown = function treePanelShown()
|
|
{
|
|
this.container.removeEventListener("popupshown",
|
|
boundTreePanelShown, false);
|
|
|
|
this.treeIFrame.addEventListener("load",
|
|
boundLoadedInitializeTreePanel, true);
|
|
|
|
let src = this.treeIFrame.getAttribute("src");
|
|
if (src != INSPECTOR_URI) {
|
|
this.treeIFrame.setAttribute("src", INSPECTOR_URI);
|
|
} else {
|
|
this.treeIFrame.contentWindow.location.reload();
|
|
}
|
|
}.bind(this);
|
|
|
|
this.container.addEventListener("popupshown", boundTreePanelShown, false);
|
|
|
|
const panelWidthRatio = 7 / 8;
|
|
const panelHeightRatio = 1 / 5;
|
|
|
|
let width = parseInt(this.IUI.win.outerWidth * panelWidthRatio);
|
|
let height = parseInt(this.IUI.win.outerHeight * panelHeightRatio);
|
|
let y = Math.min(this.document.defaultView.screen.availHeight - height,
|
|
this.IUI.win.innerHeight);
|
|
|
|
this.container.openPopup(this.browser, "overlap", 0, 0,
|
|
false, false);
|
|
|
|
this.container.moveTo(80, y);
|
|
this.container.sizeTo(width, height);
|
|
},
|
|
|
|
openDocked: function TP_openDocked()
|
|
{
|
|
let treeBox = null;
|
|
let toolbar = this.IUI.toolbar.nextSibling; // Addons bar, typically
|
|
let toolbarParent =
|
|
this.IUI.browser.ownerDocument.getElementById("browser-bottombox");
|
|
treeBox = this.document.createElement("vbox");
|
|
treeBox.id = "inspector-tree-box";
|
|
treeBox.state = "open"; // for the registerTools API.
|
|
try {
|
|
treeBox.height =
|
|
Services.prefs.getIntPref("devtools.inspector.htmlHeight");
|
|
} catch(e) {
|
|
treeBox.height = 112;
|
|
}
|
|
|
|
treeBox.minHeight = 64;
|
|
treeBox.flex = 1;
|
|
toolbarParent.insertBefore(treeBox, toolbar);
|
|
|
|
this.IUI.toolbar.setAttribute("treepanel-open", "true");
|
|
|
|
treeBox.appendChild(this.treeIFrame);
|
|
|
|
let boundLoadedInitializeTreePanel = function loadedInitializeTreePanel()
|
|
{
|
|
this.treeIFrame.removeEventListener("load",
|
|
boundLoadedInitializeTreePanel, true);
|
|
this.initializeIFrame();
|
|
}.bind(this);
|
|
|
|
this.treeIFrame.addEventListener("load",
|
|
boundLoadedInitializeTreePanel, true);
|
|
|
|
let src = this.treeIFrame.getAttribute("src");
|
|
if (src != INSPECTOR_URI) {
|
|
this.treeIFrame.setAttribute("src", INSPECTOR_URI);
|
|
} else {
|
|
this.treeIFrame.contentWindow.location.reload();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Close the TreePanel.
|
|
*/
|
|
close: function TP_close()
|
|
{
|
|
if (this.openInDock) {
|
|
this.IUI.toolbar.removeAttribute("treepanel-open");
|
|
|
|
let treeBox = this.container;
|
|
Services.prefs.setIntPref("devtools.inspector.htmlHeight", treeBox.height);
|
|
let treeBoxParent = treeBox.parentNode;
|
|
treeBoxParent.removeChild(treeBox);
|
|
} else {
|
|
this.container.hidePopup();
|
|
}
|
|
|
|
if (this.treePanelDiv) {
|
|
this.treePanelDiv.ownerPanel = null;
|
|
let parent = this.treePanelDiv.parentNode;
|
|
parent.removeChild(this.treePanelDiv);
|
|
delete this.treePanelDiv;
|
|
delete this.treeBrowserDocument;
|
|
}
|
|
|
|
this.treeLoaded = false;
|
|
},
|
|
|
|
/**
|
|
* Is the TreePanel open?
|
|
* @returns boolean
|
|
*/
|
|
isOpen: function TP_isOpen()
|
|
{
|
|
if (this.openInDock)
|
|
return this.treeLoaded && this.container;
|
|
|
|
return this.treeLoaded && this.container.state == "open";
|
|
},
|
|
|
|
/**
|
|
* Create the ObjectBox for the given object.
|
|
* @param object nsIDOMNode
|
|
* @param isRoot boolean - Is this the root object?
|
|
* @returns InsideOutBox
|
|
*/
|
|
createObjectBox: function TP_createObjectBox(object, isRoot)
|
|
{
|
|
let tag = domplateUtils.getNodeTag(object);
|
|
if (tag)
|
|
return tag.replace({object: object}, this.treeBrowserDocument);
|
|
},
|
|
|
|
getParentObject: function TP_getParentObject(node)
|
|
{
|
|
return this.DOMHelpers.getParentObject(node);
|
|
},
|
|
|
|
getChildObject: function TP_getChildObject(node, index, previousSibling)
|
|
{
|
|
return this.DOMHelpers.getChildObject(node, index, previousSibling,
|
|
this.showTextNodesWithWhitespace);
|
|
},
|
|
|
|
getFirstChild: function TP_getFirstChild(node)
|
|
{
|
|
return this.DOMHelpers.getFirstChild(node);
|
|
},
|
|
|
|
getNextSibling: function TP_getNextSibling(node)
|
|
{
|
|
return this.DOMHelpers.getNextSibling(node);
|
|
},
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
// Event Handling
|
|
|
|
/**
|
|
* Handle click events in the html tree panel.
|
|
* @param aEvent
|
|
* The mouse event.
|
|
*/
|
|
onTreeClick: function TP_onTreeClick(aEvent)
|
|
{
|
|
let node;
|
|
let target = aEvent.target;
|
|
let hitTwisty = false;
|
|
if (this.hasClass(target, "twisty")) {
|
|
node = this.getRepObject(aEvent.target.nextSibling);
|
|
hitTwisty = true;
|
|
} else {
|
|
node = this.getRepObject(aEvent.target);
|
|
}
|
|
|
|
if (node) {
|
|
if (hitTwisty) {
|
|
this.ioBox.toggleObject(node);
|
|
} else {
|
|
if (this.IUI.inspecting) {
|
|
this.IUI.stopInspecting(true);
|
|
} else {
|
|
this.IUI.select(node, true, false);
|
|
this.IUI.highlighter.highlight(node);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Handle double-click events in the html tree panel.
|
|
* (double-clicking an attribute value allows it to be edited)
|
|
* @param aEvent
|
|
* The mouse event.
|
|
*/
|
|
onTreeDblClick: function TP_onTreeDblClick(aEvent)
|
|
{
|
|
// if already editing an attribute value, double-clicking elsewhere
|
|
// in the tree is the same as a click, which dismisses the editor
|
|
if (this.editingContext)
|
|
this.closeEditor();
|
|
|
|
let target = aEvent.target;
|
|
|
|
if (this.hasClass(target, "nodeValue")) {
|
|
let repObj = this.getRepObject(target);
|
|
let attrName = target.getAttribute("data-attributeName");
|
|
let attrVal = target.innerHTML;
|
|
|
|
this.editAttributeValue(target, repObj, attrName, attrVal);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Starts the editor for an attribute value.
|
|
* @param aAttrObj
|
|
* The DOM object representing the attribute value in the HTML Tree
|
|
* @param aRepObj
|
|
* The original DOM (target) object being inspected/edited
|
|
* @param aAttrName
|
|
* The name of the attribute being edited
|
|
* @param aAttrVal
|
|
* The current value of the attribute being edited
|
|
*/
|
|
editAttributeValue:
|
|
function TP_editAttributeValue(aAttrObj, aRepObj, aAttrName, aAttrVal)
|
|
{
|
|
let editor = this.treeBrowserDocument.getElementById("attribute-editor");
|
|
let editorInput =
|
|
this.treeBrowserDocument.getElementById("attribute-editor-input");
|
|
let attrDims = aAttrObj.getBoundingClientRect();
|
|
// figure out actual viewable viewport dimensions (sans scrollbars)
|
|
let viewportWidth = this.treeBrowserDocument.documentElement.clientWidth;
|
|
let viewportHeight = this.treeBrowserDocument.documentElement.clientHeight;
|
|
|
|
// saves the editing context for use when the editor is saved/closed
|
|
this.editingContext = {
|
|
attrObj: aAttrObj,
|
|
repObj: aRepObj,
|
|
attrName: aAttrName
|
|
};
|
|
|
|
// highlight attribute-value node in tree while editing
|
|
this.addClass(aAttrObj, "editingAttributeValue");
|
|
|
|
// show the editor
|
|
this.addClass(editor, "editing");
|
|
|
|
// offset the editor below the attribute-value node being edited
|
|
let editorVeritcalOffset = 2;
|
|
|
|
// keep the editor comfortably within the bounds of the viewport
|
|
let editorViewportBoundary = 5;
|
|
|
|
// outer editor is sized based on the <input> box inside it
|
|
editorInput.style.width = Math.min(attrDims.width, viewportWidth -
|
|
editorViewportBoundary) + "px";
|
|
editorInput.style.height = Math.min(attrDims.height, viewportHeight -
|
|
editorViewportBoundary) + "px";
|
|
let editorDims = editor.getBoundingClientRect();
|
|
|
|
// calculate position for the editor according to the attribute node
|
|
let editorLeft = attrDims.left + this.treeIFrame.contentWindow.scrollX -
|
|
// center the editor against the attribute value
|
|
((editorDims.width - attrDims.width) / 2);
|
|
let editorTop = attrDims.top + this.treeIFrame.contentWindow.scrollY +
|
|
attrDims.height + editorVeritcalOffset;
|
|
|
|
// but, make sure the editor stays within the visible viewport
|
|
editorLeft = Math.max(0, Math.min(
|
|
(this.treeIFrame.contentWindow.scrollX +
|
|
viewportWidth - editorDims.width),
|
|
editorLeft)
|
|
);
|
|
editorTop = Math.max(0, Math.min(
|
|
(this.treeIFrame.contentWindow.scrollY +
|
|
viewportHeight - editorDims.height),
|
|
editorTop)
|
|
);
|
|
|
|
// position the editor
|
|
editor.style.left = editorLeft + "px";
|
|
editor.style.top = editorTop + "px";
|
|
|
|
// set and select the text
|
|
editorInput.value = aAttrVal;
|
|
editorInput.select();
|
|
|
|
// listen for editor specific events
|
|
this.bindEditorEvent(editor, "click", function(aEvent) {
|
|
aEvent.stopPropagation();
|
|
});
|
|
this.bindEditorEvent(editor, "dblclick", function(aEvent) {
|
|
aEvent.stopPropagation();
|
|
});
|
|
this.bindEditorEvent(editor, "keypress",
|
|
this.handleEditorKeypress.bind(this));
|
|
|
|
// event notification
|
|
Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_OPENED,
|
|
null);
|
|
},
|
|
|
|
/**
|
|
* Handle binding an event handler for the editor.
|
|
* (saves the callback for easier unbinding later)
|
|
* @param aEditor
|
|
* The DOM object for the editor
|
|
* @param aEventName
|
|
* The name of the event to listen for
|
|
* @param aEventCallback
|
|
* The callback to bind to the event (and also to save for later
|
|
* unbinding)
|
|
*/
|
|
bindEditorEvent:
|
|
function TP_bindEditorEvent(aEditor, aEventName, aEventCallback)
|
|
{
|
|
this.editingEvents[aEventName] = aEventCallback;
|
|
aEditor.addEventListener(aEventName, aEventCallback, false);
|
|
},
|
|
|
|
/**
|
|
* Handle unbinding an event handler from the editor.
|
|
* (unbinds the previously bound and saved callback)
|
|
* @param aEditor
|
|
* The DOM object for the editor
|
|
* @param aEventName
|
|
* The name of the event being listened for
|
|
*/
|
|
unbindEditorEvent: function TP_unbindEditorEvent(aEditor, aEventName)
|
|
{
|
|
aEditor.removeEventListener(aEventName, this.editingEvents[aEventName],
|
|
false);
|
|
this.editingEvents[aEventName] = null;
|
|
},
|
|
|
|
/**
|
|
* Handle keypress events in the editor.
|
|
* @param aEvent
|
|
* The keyboard event.
|
|
*/
|
|
handleEditorKeypress: function TP_handleEditorKeypress(aEvent)
|
|
{
|
|
if (aEvent.which == this.window.KeyEvent.DOM_VK_RETURN) {
|
|
this.saveEditor();
|
|
aEvent.preventDefault();
|
|
aEvent.stopPropagation();
|
|
} else if (aEvent.keyCode == this.window.KeyEvent.DOM_VK_ESCAPE) {
|
|
this.closeEditor();
|
|
aEvent.preventDefault();
|
|
aEvent.stopPropagation();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Close the editor and cleanup.
|
|
*/
|
|
closeEditor: function TP_closeEditor()
|
|
{
|
|
let editor = this.treeBrowserDocument.getElementById("attribute-editor");
|
|
let editorInput =
|
|
this.treeBrowserDocument.getElementById("attribute-editor-input");
|
|
|
|
// remove highlight from attribute-value node in tree
|
|
this.removeClass(this.editingContext.attrObj, "editingAttributeValue");
|
|
|
|
// hide editor
|
|
this.removeClass(editor, "editing");
|
|
|
|
// stop listening for editor specific events
|
|
this.unbindEditorEvent(editor, "click");
|
|
this.unbindEditorEvent(editor, "dblclick");
|
|
this.unbindEditorEvent(editor, "keypress");
|
|
|
|
// clean up after the editor
|
|
editorInput.value = "";
|
|
editorInput.blur();
|
|
this.editingContext = null;
|
|
this.editingEvents = {};
|
|
|
|
// event notification
|
|
Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED,
|
|
null);
|
|
},
|
|
|
|
/**
|
|
* Commit the edits made in the editor, then close it.
|
|
*/
|
|
saveEditor: function TP_saveEditor()
|
|
{
|
|
let editorInput =
|
|
this.treeBrowserDocument.getElementById("attribute-editor-input");
|
|
|
|
// set the new attribute value on the original target DOM element
|
|
this.editingContext.repObj.setAttribute(this.editingContext.attrName,
|
|
editorInput.value);
|
|
|
|
// update the HTML tree attribute value
|
|
this.editingContext.attrObj.innerHTML = editorInput.value;
|
|
|
|
this.IUI.isDirty = true;
|
|
this.IUI.nodeChanged(this.registrationObject);
|
|
|
|
// event notification
|
|
Services.obs.notifyObservers(null, this.IUI.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED,
|
|
null);
|
|
|
|
this.closeEditor();
|
|
},
|
|
|
|
/**
|
|
* Simple tree select method.
|
|
* @param aNode the DOM node in the content document to select.
|
|
* @param aScroll boolean scroll to the visible node?
|
|
*/
|
|
select: function TP_select(aNode, aScroll)
|
|
{
|
|
if (this.ioBox)
|
|
this.ioBox.select(aNode, true, true, aScroll);
|
|
},
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//// Utility functions
|
|
|
|
/**
|
|
* Does the given object have a class attribute?
|
|
* @param aNode
|
|
* the DOM node.
|
|
* @param aClass
|
|
* The class string.
|
|
* @returns boolean
|
|
*/
|
|
hasClass: function TP_hasClass(aNode, aClass)
|
|
{
|
|
if (!(aNode instanceof this.window.Element))
|
|
return false;
|
|
return aNode.classList.contains(aClass);
|
|
},
|
|
|
|
/**
|
|
* Add the class name to the given object.
|
|
* @param aNode
|
|
* the DOM node.
|
|
* @param aClass
|
|
* The class string.
|
|
*/
|
|
addClass: function TP_addClass(aNode, aClass)
|
|
{
|
|
if (aNode instanceof this.window.Element)
|
|
aNode.classList.add(aClass);
|
|
},
|
|
|
|
/**
|
|
* Remove the class name from the given object
|
|
* @param aNode
|
|
* the DOM node.
|
|
* @param aClass
|
|
* The class string.
|
|
*/
|
|
removeClass: function TP_removeClass(aNode, aClass)
|
|
{
|
|
if (aNode instanceof this.window.Element)
|
|
aNode.classList.remove(aClass);
|
|
},
|
|
|
|
/**
|
|
* Get the "repObject" from the HTML panel's domplate-constructed DOM node.
|
|
* In this system, a "repObject" is the Object being Represented by the box
|
|
* object. It is the "real" object that we're building our facade around.
|
|
*
|
|
* @param element
|
|
* The element in the HTML panel the user clicked.
|
|
* @returns either a real node or null
|
|
*/
|
|
getRepObject: function TP_getRepObject(element)
|
|
{
|
|
let target = null;
|
|
for (let child = element; child; child = child.parentNode) {
|
|
if (this.hasClass(child, "repTarget"))
|
|
target = child;
|
|
|
|
if (child.repObject) {
|
|
if (!target && this.hasClass(child.repObject, "repIgnore"))
|
|
break;
|
|
else
|
|
return child.repObject;
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Destructor function. Cleanup.
|
|
*/
|
|
destroy: function TP_destroy()
|
|
{
|
|
if (this.isOpen()) {
|
|
this.close();
|
|
}
|
|
|
|
domplateUtils.setDOM(null);
|
|
|
|
if (this.DOMHelpers) {
|
|
this.DOMHelpers.destroy();
|
|
delete this.DOMHelpers;
|
|
}
|
|
|
|
if (this.treePanelDiv) {
|
|
this.treePanelDiv.ownerPanel = null;
|
|
let parent = this.treePanelDiv.parentNode;
|
|
parent.removeChild(this.treePanelDiv);
|
|
delete this.treePanelDiv;
|
|
delete this.treeBrowserDocument;
|
|
}
|
|
|
|
if (this.treeIFrame) {
|
|
this.treeIFrame.removeEventListener("dblclick", this.onTreeDblClick, false);
|
|
this.treeIFrame.removeEventListener("click", this.onTreeClick, false);
|
|
let parent = this.treeIFrame.parentNode;
|
|
parent.removeChild(this.treeIFrame);
|
|
delete this.treeIFrame;
|
|
}
|
|
|
|
if (this.ioBox) {
|
|
this.ioBox.destroy();
|
|
delete this.ioBox;
|
|
}
|
|
|
|
if (!this.openInDock) {
|
|
this.container.removeEventListener("popuphiding", this._boundClose, false);
|
|
delete this._boundClose;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* DOMHelpers
|
|
* Makes DOM traversal easier. Goes through iframes.
|
|
*
|
|
* @constructor
|
|
* @param nsIDOMWindow aWindow
|
|
* The content window, owning the document to traverse.
|
|
*/
|
|
function DOMHelpers(aWindow) {
|
|
this.window = aWindow;
|
|
};
|
|
|
|
DOMHelpers.prototype = {
|
|
getParentObject: function Helpers_getParentObject(node)
|
|
{
|
|
let parentNode = node ? node.parentNode : null;
|
|
|
|
if (!parentNode) {
|
|
// Documents have no parentNode; Attr, Document, DocumentFragment, Entity,
|
|
// and Notation. top level windows have no parentNode
|
|
if (node && node == this.window.Node.DOCUMENT_NODE) {
|
|
// document type
|
|
if (node.defaultView) {
|
|
let embeddingFrame = node.defaultView.frameElement;
|
|
if (embeddingFrame)
|
|
return embeddingFrame.parentNode;
|
|
}
|
|
}
|
|
// a Document object without a parentNode or window
|
|
return null; // top level has no parent
|
|
}
|
|
|
|
if (parentNode.nodeType == this.window.Node.DOCUMENT_NODE) {
|
|
if (parentNode.defaultView) {
|
|
return parentNode.defaultView.frameElement;
|
|
}
|
|
// parent is document element, but no window at defaultView.
|
|
return null;
|
|
}
|
|
|
|
if (!parentNode.localName)
|
|
return null;
|
|
|
|
return parentNode;
|
|
},
|
|
|
|
getChildObject: function Helpers_getChildObject(node, index, previousSibling,
|
|
showTextNodesWithWhitespace)
|
|
{
|
|
if (!node)
|
|
return null;
|
|
|
|
if (node.contentDocument) {
|
|
// then the node is a frame
|
|
if (index == 0) {
|
|
return node.contentDocument.documentElement; // the node's HTMLElement
|
|
}
|
|
return null;
|
|
}
|
|
|
|
if (node instanceof this.window.GetSVGDocument) {
|
|
let svgDocument = node.getSVGDocument();
|
|
if (svgDocument) {
|
|
// then the node is a frame
|
|
if (index == 0) {
|
|
return svgDocument.documentElement; // the node's SVGElement
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
let child = null;
|
|
if (previousSibling) // then we are walking
|
|
child = this.getNextSibling(previousSibling);
|
|
else
|
|
child = this.getFirstChild(node);
|
|
|
|
if (showTextNodesWithWhitespace)
|
|
return child;
|
|
|
|
for (; child; child = this.getNextSibling(child)) {
|
|
if (!this.isWhitespaceText(child))
|
|
return child;
|
|
}
|
|
|
|
return null; // we have no children worth showing.
|
|
},
|
|
|
|
getFirstChild: function Helpers_getFirstChild(node)
|
|
{
|
|
let SHOW_ALL = Components.interfaces.nsIDOMNodeFilter.SHOW_ALL;
|
|
this.treeWalker = node.ownerDocument.createTreeWalker(node,
|
|
SHOW_ALL, null, false);
|
|
return this.treeWalker.firstChild();
|
|
},
|
|
|
|
getNextSibling: function Helpers_getNextSibling(node)
|
|
{
|
|
let next = this.treeWalker.nextSibling();
|
|
|
|
if (!next)
|
|
delete this.treeWalker;
|
|
|
|
return next;
|
|
},
|
|
|
|
isWhitespaceText: function Helpers_isWhitespaceText(node)
|
|
{
|
|
return node.nodeType == this.window.Node.TEXT_NODE &&
|
|
!/[^\s]/.exec(node.nodeValue);
|
|
},
|
|
|
|
destroy: function Helpers_destroy()
|
|
{
|
|
delete this.window;
|
|
delete this.treeWalker;
|
|
}
|
|
};
|