2010-05-13 13:38:21 -07:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ts=2 et sw=2 tw=80: */
|
|
|
|
#ifdef 0
|
|
|
|
/* ***** 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 Inspector Module.
|
|
|
|
*
|
|
|
|
* The Initial Developer of the Original Code is
|
|
|
|
* The Mozilla Foundation.
|
|
|
|
* Portions created by the Initial Developer are Copyright (C) 2010
|
|
|
|
* the Initial Developer. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Contributor(s):
|
|
|
|
* Rob Campbell <rcampbell@mozilla.com> (original author)
|
2010-07-30 04:30:55 -07:00
|
|
|
* Mihai Șucan <mihai.sucan@gmail.com>
|
2010-08-09 12:02:20 -07:00
|
|
|
* Julian Viereck <jviereck@mozilla.com>
|
2011-06-16 11:24:20 -07:00
|
|
|
* Paul Rouget <paul@mozilla.com>
|
2011-08-17 05:16:15 -07:00
|
|
|
* Kyle Simpson <ksimpson@mozilla.com>
|
2010-05-13 13:38:21 -07:00
|
|
|
*
|
|
|
|
* 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 ***** */
|
|
|
|
#endif
|
|
|
|
|
2010-09-03 11:34:09 -07:00
|
|
|
#include insideOutBox.js
|
|
|
|
|
2010-05-13 13:38:21 -07:00
|
|
|
const INSPECTOR_INVISIBLE_ELEMENTS = {
|
|
|
|
"head": true,
|
|
|
|
"base": true,
|
|
|
|
"basefont": true,
|
|
|
|
"isindex": true,
|
|
|
|
"link": true,
|
|
|
|
"meta": true,
|
|
|
|
"script": true,
|
|
|
|
"style": true,
|
|
|
|
"title": true,
|
|
|
|
};
|
|
|
|
|
2011-06-16 11:24:20 -07:00
|
|
|
// Inspector notifications dispatched through the nsIObserverService.
|
|
|
|
const INSPECTOR_NOTIFICATIONS = {
|
|
|
|
// Fires once the Inspector highlights an element in the page.
|
|
|
|
HIGHLIGHTING: "inspector-highlighting",
|
|
|
|
|
|
|
|
// Fires once the Inspector stops highlighting any element.
|
|
|
|
UNHIGHLIGHTING: "inspector-unhighlighting",
|
|
|
|
|
|
|
|
// Fires once the Inspector completes the initialization and opens up on
|
|
|
|
// screen.
|
|
|
|
OPENED: "inspector-opened",
|
|
|
|
|
|
|
|
// Fires once the Inspector is closed.
|
|
|
|
CLOSED: "inspector-closed",
|
2011-08-17 05:16:15 -07:00
|
|
|
|
|
|
|
// Event notifications for the attribute-value editor
|
|
|
|
EDITOR_OPENED: "inspector-editor-opened",
|
|
|
|
EDITOR_CLOSED: "inspector-editor-closed",
|
|
|
|
EDITOR_SAVED: "inspector-editor-saved",
|
2011-06-16 11:24:20 -07:00
|
|
|
};
|
|
|
|
|
2010-05-13 13:38:21 -07:00
|
|
|
///////////////////////////////////////////////////////////////////////////
|
2011-07-11 10:18:11 -07:00
|
|
|
//// Highlighter
|
2010-05-13 13:38:21 -07:00
|
|
|
|
|
|
|
/**
|
2011-07-11 10:18:11 -07:00
|
|
|
* A highlighter mechanism.
|
|
|
|
*
|
|
|
|
* The highlighter is built dynamically once the Inspector is invoked:
|
|
|
|
* <stack id="highlighter-container">
|
|
|
|
* <vbox id="highlighter-veil-container">...</vbox>
|
|
|
|
* <box id="highlighter-controls>...</vbox>
|
|
|
|
* </stack>
|
2010-05-13 13:38:21 -07:00
|
|
|
*
|
2011-06-16 11:24:20 -07:00
|
|
|
* @param nsIDOMNode aBrowser
|
|
|
|
* The xul:browser object for the content window being highlighted.
|
2010-05-13 13:38:21 -07:00
|
|
|
*/
|
2011-07-11 10:18:11 -07:00
|
|
|
function Highlighter(aBrowser)
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2011-06-16 11:24:20 -07:00
|
|
|
this._init(aBrowser);
|
2010-05-13 13:38:21 -07:00
|
|
|
}
|
|
|
|
|
2011-07-11 10:18:11 -07:00
|
|
|
Highlighter.prototype = {
|
2011-06-16 11:24:20 -07:00
|
|
|
|
2011-07-11 10:18:11 -07:00
|
|
|
_init: function Highlighter__init(aBrowser)
|
2011-06-16 11:24:20 -07:00
|
|
|
{
|
|
|
|
this.browser = aBrowser;
|
|
|
|
let stack = this.browser.parentNode;
|
|
|
|
this.win = this.browser.contentWindow;
|
|
|
|
this._highlighting = false;
|
|
|
|
|
2011-07-11 10:18:11 -07:00
|
|
|
this.highlighterContainer = document.createElement("stack");
|
|
|
|
this.highlighterContainer.id = "highlighter-container";
|
|
|
|
|
2011-09-01 09:33:33 -07:00
|
|
|
this.veilContainer = document.createElement("vbox");
|
|
|
|
this.veilContainer.id = "highlighter-veil-container";
|
2011-07-11 10:18:11 -07:00
|
|
|
|
|
|
|
let controlsBox = document.createElement("box");
|
|
|
|
controlsBox.id = "highlighter-controls";
|
|
|
|
|
|
|
|
// The veil will make the whole page darker except
|
|
|
|
// for the region of the selected box.
|
2011-09-01 09:33:33 -07:00
|
|
|
this.buildVeil(this.veilContainer);
|
2011-06-16 11:24:20 -07:00
|
|
|
|
2011-07-11 10:18:11 -07:00
|
|
|
// The controlsBox will host the different interactive
|
|
|
|
// elements of the highlighter (buttons, toolbars, ...).
|
|
|
|
this.buildControls(controlsBox);
|
2011-06-16 11:24:20 -07:00
|
|
|
|
2011-09-01 09:33:33 -07:00
|
|
|
this.highlighterContainer.appendChild(this.veilContainer);
|
2011-07-11 10:18:11 -07:00
|
|
|
this.highlighterContainer.appendChild(controlsBox);
|
|
|
|
|
|
|
|
stack.appendChild(this.highlighterContainer);
|
|
|
|
|
|
|
|
this.browser.addEventListener("resize", this, true);
|
|
|
|
this.browser.addEventListener("scroll", this, true);
|
|
|
|
|
|
|
|
this.handleResize();
|
2011-06-16 11:24:20 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2011-07-11 10:18:11 -07:00
|
|
|
* Build the veil:
|
|
|
|
*
|
|
|
|
* <vbox id="highlighter-veil-container">
|
|
|
|
* <box id="highlighter-veil-topbox" class="highlighter-veil"/>
|
|
|
|
* <hbox id="highlighter-veil-middlebox">
|
|
|
|
* <box id="highlighter-veil-leftbox" class="highlighter-veil"/>
|
|
|
|
* <box id="highlighter-veil-transparentbox"/>
|
|
|
|
* <box id="highlighter-veil-rightbox" class="highlighter-veil"/>
|
|
|
|
* </hbox>
|
|
|
|
* <box id="highlighter-veil-bottombox" class="highlighter-veil"/>
|
|
|
|
* </vbox>
|
|
|
|
*
|
|
|
|
* @param nsIDOMNode aParent
|
2011-06-16 11:24:20 -07:00
|
|
|
*/
|
2011-07-11 10:18:11 -07:00
|
|
|
buildVeil: function Highlighter_buildVeil(aParent)
|
2011-06-16 11:24:20 -07:00
|
|
|
{
|
2011-07-11 10:18:11 -07:00
|
|
|
|
|
|
|
// We will need to resize these boxes to surround a node.
|
|
|
|
// See highlightRectangle().
|
|
|
|
|
|
|
|
this.veilTopBox = document.createElement("box");
|
|
|
|
this.veilTopBox.id = "highlighter-veil-topbox";
|
|
|
|
this.veilTopBox.className = "highlighter-veil";
|
|
|
|
|
|
|
|
this.veilMiddleBox = document.createElement("hbox");
|
|
|
|
this.veilMiddleBox.id = "highlighter-veil-middlebox";
|
|
|
|
|
|
|
|
this.veilLeftBox = document.createElement("box");
|
|
|
|
this.veilLeftBox.id = "highlighter-veil-leftbox";
|
|
|
|
this.veilLeftBox.className = "highlighter-veil";
|
|
|
|
|
|
|
|
this.veilTransparentBox = document.createElement("box");
|
|
|
|
this.veilTransparentBox.id = "highlighter-veil-transparentbox";
|
|
|
|
|
|
|
|
// We don't need any references to veilRightBox and veilBottomBox.
|
|
|
|
// These boxes are automatically resized (flex=1)
|
|
|
|
|
|
|
|
let veilRightBox = document.createElement("box");
|
|
|
|
veilRightBox.id = "highlighter-veil-rightbox";
|
|
|
|
veilRightBox.className = "highlighter-veil";
|
|
|
|
|
|
|
|
let veilBottomBox = document.createElement("box");
|
|
|
|
veilBottomBox.id = "highlighter-veil-bottombox";
|
|
|
|
veilBottomBox.className = "highlighter-veil";
|
|
|
|
|
|
|
|
this.veilMiddleBox.appendChild(this.veilLeftBox);
|
|
|
|
this.veilMiddleBox.appendChild(this.veilTransparentBox);
|
|
|
|
this.veilMiddleBox.appendChild(veilRightBox);
|
|
|
|
|
|
|
|
aParent.appendChild(this.veilTopBox);
|
|
|
|
aParent.appendChild(this.veilMiddleBox);
|
|
|
|
aParent.appendChild(veilBottomBox);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Build the controls:
|
|
|
|
*
|
|
|
|
* <box id="highlighter-close-button"/>
|
|
|
|
*
|
|
|
|
* @param nsIDOMNode aParent
|
|
|
|
*/
|
|
|
|
buildControls: function Highlighter_buildControls(aParent)
|
|
|
|
{
|
|
|
|
let closeButton = document.createElement("box");
|
|
|
|
closeButton.id = "highlighter-close-button";
|
|
|
|
closeButton.appendChild(document.createElement("image"));
|
|
|
|
|
|
|
|
closeButton.setAttribute("onclick", "InspectorUI.closeInspectorUI(false);");
|
|
|
|
|
|
|
|
aParent.appendChild(closeButton);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destroy the nodes.
|
|
|
|
*/
|
|
|
|
destroy: function Highlighter_destroy()
|
|
|
|
{
|
|
|
|
this.browser.removeEventListener("scroll", this, true);
|
|
|
|
this.browser.removeEventListener("resize", this, true);
|
2011-06-16 11:24:20 -07:00
|
|
|
this._highlightRect = null;
|
|
|
|
this._highlighting = false;
|
2011-07-11 10:18:11 -07:00
|
|
|
this.veilTopBox = null;
|
|
|
|
this.veilLeftBox = null;
|
|
|
|
this.veilMiddleBox = null;
|
|
|
|
this.veilTransparentBox = null;
|
2011-09-01 09:33:33 -07:00
|
|
|
this.veilContainer = null;
|
2011-06-16 11:24:20 -07:00
|
|
|
this.node = null;
|
2011-07-11 10:18:11 -07:00
|
|
|
this.highlighterContainer.parentNode.removeChild(this.highlighterContainer);
|
|
|
|
this.highlighterContainer = null;
|
2011-06-16 11:24:20 -07:00
|
|
|
this.win = null
|
|
|
|
this.browser = null;
|
2011-08-02 14:28:49 -07:00
|
|
|
this.toolbar = null;
|
2011-06-16 11:24:20 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Is the highlighter highlighting? Public method for querying the state
|
|
|
|
* of the highlighter.
|
|
|
|
*/
|
|
|
|
get isHighlighting() {
|
|
|
|
return this._highlighting;
|
|
|
|
},
|
2010-05-13 13:38:21 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Highlight this.node, unhilighting first if necessary.
|
|
|
|
*
|
2011-06-16 11:24:20 -07:00
|
|
|
* @param boolean aScroll
|
2010-05-13 13:38:21 -07:00
|
|
|
* Boolean determining whether to scroll or not.
|
|
|
|
*/
|
2011-07-11 10:18:11 -07:00
|
|
|
highlight: function Highlighter_highlight(aScroll)
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
|
|
|
// node is not set or node is not highlightable, bail
|
2011-06-16 11:24:20 -07:00
|
|
|
if (!this.node || !this.isNodeHighlightable()) {
|
2010-05-13 13:38:21 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-09-16 02:01:38 -07:00
|
|
|
if (aScroll) {
|
|
|
|
this.node.scrollIntoView();
|
|
|
|
}
|
|
|
|
|
2011-06-16 11:24:20 -07:00
|
|
|
let clientRect = this.node.getBoundingClientRect();
|
|
|
|
|
2011-09-16 02:01:38 -07:00
|
|
|
// Go up in the tree of frames to determine the correct rectangle.
|
2011-06-16 11:24:20 -07:00
|
|
|
// clientRect is read-only, we need to be able to change properties.
|
|
|
|
let rect = {top: clientRect.top,
|
|
|
|
left: clientRect.left,
|
|
|
|
width: clientRect.width,
|
|
|
|
height: clientRect.height};
|
2010-05-13 13:38:21 -07:00
|
|
|
|
2011-06-16 11:24:20 -07:00
|
|
|
let frameWin = this.node.ownerDocument.defaultView;
|
|
|
|
|
2011-09-16 02:01:38 -07:00
|
|
|
// We iterate through all the parent windows.
|
|
|
|
while (true) {
|
|
|
|
|
|
|
|
// Does the selection overflow on the right of its window?
|
|
|
|
let diffx = frameWin.innerWidth - (rect.left + rect.width);
|
|
|
|
if (diffx < 0) {
|
|
|
|
rect.width += diffx;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Does the selection overflow on the bottom of its window?
|
|
|
|
let diffy = frameWin.innerHeight - (rect.top + rect.height);
|
|
|
|
if (diffy < 0) {
|
|
|
|
rect.height += diffy;
|
2011-06-16 11:24:20 -07:00
|
|
|
}
|
|
|
|
|
2011-09-16 02:01:38 -07:00
|
|
|
// Does the selection overflow on the left of its window?
|
2011-06-16 11:24:20 -07:00
|
|
|
if (rect.left < 0) {
|
|
|
|
rect.width += rect.left;
|
|
|
|
rect.left = 0;
|
|
|
|
}
|
|
|
|
|
2011-09-16 02:01:38 -07:00
|
|
|
// Does the selection overflow on the top of its window?
|
|
|
|
if (rect.top < 0) {
|
|
|
|
rect.height += rect.top;
|
|
|
|
rect.top = 0;
|
2011-06-16 11:24:20 -07:00
|
|
|
}
|
2011-09-16 02:01:38 -07:00
|
|
|
|
|
|
|
// Selection has been clipped to fit in its own window.
|
|
|
|
|
|
|
|
// Are we in the top-level window?
|
|
|
|
if (frameWin.parent === frameWin || !frameWin.frameElement) {
|
|
|
|
break;
|
2011-06-16 11:24:20 -07:00
|
|
|
}
|
|
|
|
|
2011-09-16 02:01:38 -07:00
|
|
|
// We are in an iframe.
|
|
|
|
// We take into account the parent iframe position and its
|
|
|
|
// offset (borders and padding).
|
|
|
|
let frameRect = frameWin.frameElement.getBoundingClientRect();
|
|
|
|
|
|
|
|
let [offsetTop, offsetLeft] =
|
|
|
|
InspectorUI.getIframeContentOffset(frameWin.frameElement);
|
|
|
|
|
|
|
|
rect.top += frameRect.top + offsetTop;
|
|
|
|
rect.left += frameRect.left + offsetLeft;
|
2011-06-16 11:24:20 -07:00
|
|
|
|
|
|
|
frameWin = frameWin.parent;
|
2011-09-16 02:01:38 -07:00
|
|
|
}
|
2011-06-16 11:24:20 -07:00
|
|
|
|
|
|
|
this.highlightRectangle(rect);
|
|
|
|
|
|
|
|
if (this._highlighting) {
|
|
|
|
Services.obs.notifyObservers(null,
|
|
|
|
INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, null);
|
2010-05-13 13:38:21 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Highlight the given node.
|
|
|
|
*
|
2011-06-16 11:24:20 -07:00
|
|
|
* @param nsIDOMNode aNode
|
2010-05-13 13:38:21 -07:00
|
|
|
* a DOM element to be highlighted
|
2011-06-16 11:24:20 -07:00
|
|
|
* @param object aParams
|
2010-05-13 13:38:21 -07:00
|
|
|
* extra parameters object
|
|
|
|
*/
|
2011-07-11 10:18:11 -07:00
|
|
|
highlightNode: function Highlighter_highlightNode(aNode, aParams)
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2010-07-16 07:12:39 -07:00
|
|
|
this.node = aNode;
|
|
|
|
this.highlight(aParams && aParams.scroll);
|
2010-05-13 13:38:21 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2011-06-16 11:24:20 -07:00
|
|
|
* Highlight a rectangular region.
|
2010-05-13 13:38:21 -07:00
|
|
|
*
|
2011-06-16 11:24:20 -07:00
|
|
|
* @param object aRect
|
|
|
|
* The rectangle region to highlight.
|
2010-05-13 13:38:21 -07:00
|
|
|
* @returns boolean
|
2011-06-16 11:24:20 -07:00
|
|
|
* True if the rectangle was highlighted, false otherwise.
|
2010-05-13 13:38:21 -07:00
|
|
|
*/
|
2011-07-11 10:18:11 -07:00
|
|
|
highlightRectangle: function Highlighter_highlightRectangle(aRect)
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2011-07-11 10:18:11 -07:00
|
|
|
let oldRect = this._highlightRect;
|
|
|
|
|
|
|
|
if (oldRect && aRect.top == oldRect.top && aRect.left == oldRect.left &&
|
|
|
|
aRect.width == oldRect.width && aRect.height == oldRect.height) {
|
|
|
|
return this._highlighting; // same rectangle
|
|
|
|
}
|
|
|
|
|
2011-06-16 11:24:20 -07:00
|
|
|
if (aRect.left >= 0 && aRect.top >= 0 &&
|
|
|
|
aRect.width > 0 && aRect.height > 0) {
|
|
|
|
// The bottom div and the right div are flexibles (flex=1).
|
|
|
|
// We don't need to resize them.
|
2011-07-11 10:18:11 -07:00
|
|
|
this.veilTopBox.style.height = aRect.top + "px";
|
|
|
|
this.veilLeftBox.style.width = aRect.left + "px";
|
|
|
|
this.veilMiddleBox.style.height = aRect.height + "px";
|
|
|
|
this.veilTransparentBox.style.width = aRect.width + "px";
|
2011-06-16 11:24:20 -07:00
|
|
|
|
|
|
|
this._highlighting = true;
|
|
|
|
} else {
|
|
|
|
this.unhighlight();
|
2010-05-13 13:38:21 -07:00
|
|
|
}
|
|
|
|
|
2011-06-16 11:24:20 -07:00
|
|
|
this._highlightRect = aRect;
|
2010-05-13 13:38:21 -07:00
|
|
|
|
2011-06-16 11:24:20 -07:00
|
|
|
return this._highlighting;
|
2010-05-13 13:38:21 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2011-06-16 11:24:20 -07:00
|
|
|
* Clear the highlighter surface.
|
2010-05-13 13:38:21 -07:00
|
|
|
*/
|
2011-07-11 10:18:11 -07:00
|
|
|
unhighlight: function Highlighter_unhighlight()
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2011-06-16 11:24:20 -07:00
|
|
|
this._highlighting = false;
|
2011-07-11 10:18:11 -07:00
|
|
|
this.veilMiddleBox.style.height = 0;
|
|
|
|
this.veilTransparentBox.style.width = 0;
|
2011-06-16 11:24:20 -07:00
|
|
|
Services.obs.notifyObservers(null,
|
|
|
|
INSPECTOR_NOTIFICATIONS.UNHIGHLIGHTING, null);
|
2010-05-13 13:38:21 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the midpoint of a line from pointA to pointB.
|
|
|
|
*
|
2011-06-16 11:24:20 -07:00
|
|
|
* @param object aPointA
|
2010-05-13 13:38:21 -07:00
|
|
|
* An object with x and y properties.
|
2011-06-16 11:24:20 -07:00
|
|
|
* @param object aPointB
|
2010-05-13 13:38:21 -07:00
|
|
|
* An object with x and y properties.
|
2011-06-16 11:24:20 -07:00
|
|
|
* @returns object
|
2010-05-13 13:38:21 -07:00
|
|
|
* An object with x and y properties.
|
|
|
|
*/
|
2011-07-11 10:18:11 -07:00
|
|
|
midPoint: function Highlighter_midPoint(aPointA, aPointB)
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
|
|
|
let pointC = { };
|
2010-07-16 07:12:39 -07:00
|
|
|
pointC.x = (aPointB.x - aPointA.x) / 2 + aPointA.x;
|
|
|
|
pointC.y = (aPointB.y - aPointA.y) / 2 + aPointA.y;
|
2010-05-13 13:38:21 -07:00
|
|
|
return pointC;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the node under the highlighter rectangle. Useful for testing.
|
|
|
|
* Calculation based on midpoint of diagonal from top left to bottom right
|
|
|
|
* of panel.
|
|
|
|
*
|
2011-06-16 11:24:20 -07:00
|
|
|
* @returns nsIDOMNode|null
|
|
|
|
* Returns the node under the current highlighter rectangle. Null is
|
|
|
|
* returned if there is no node highlighted.
|
2010-05-13 13:38:21 -07:00
|
|
|
*/
|
|
|
|
get highlitNode()
|
|
|
|
{
|
2011-06-16 11:24:20 -07:00
|
|
|
// Not highlighting? Bail.
|
|
|
|
if (!this._highlighting || !this._highlightRect) {
|
2010-05-13 13:38:21 -07:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
let a = {
|
2011-06-16 11:24:20 -07:00
|
|
|
x: this._highlightRect.left,
|
|
|
|
y: this._highlightRect.top
|
2010-05-13 13:38:21 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
let b = {
|
2011-06-16 11:24:20 -07:00
|
|
|
x: a.x + this._highlightRect.width,
|
|
|
|
y: a.y + this._highlightRect.height
|
2010-05-13 13:38:21 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
// Get midpoint of diagonal line.
|
|
|
|
let midpoint = this.midPoint(a, b);
|
|
|
|
|
2010-07-30 04:30:55 -07:00
|
|
|
return InspectorUI.elementFromPoint(this.win.document, midpoint.x,
|
|
|
|
midpoint.y);
|
2010-05-13 13:38:21 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Is this.node highlightable?
|
|
|
|
*
|
|
|
|
* @returns boolean
|
2011-06-16 11:24:20 -07:00
|
|
|
* True if the node is highlightable or false otherwise.
|
2010-05-13 13:38:21 -07:00
|
|
|
*/
|
2011-07-11 10:18:11 -07:00
|
|
|
isNodeHighlightable: function Highlighter_isNodeHighlightable()
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2011-06-16 11:24:20 -07:00
|
|
|
if (!this.node || this.node.nodeType != Node.ELEMENT_NODE) {
|
2010-05-13 13:38:21 -07:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
let nodeName = this.node.nodeName.toLowerCase();
|
|
|
|
return !INSPECTOR_INVISIBLE_ELEMENTS[nodeName];
|
|
|
|
},
|
|
|
|
|
2011-06-16 11:24:20 -07:00
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Event Handling
|
|
|
|
|
2011-07-11 10:18:11 -07:00
|
|
|
attachInspectListeners: function Highlighter_attachInspectListeners()
|
2011-06-16 11:24:20 -07:00
|
|
|
{
|
|
|
|
this.browser.addEventListener("mousemove", this, true);
|
2011-07-11 10:18:11 -07:00
|
|
|
this.browser.addEventListener("click", this, true);
|
2011-06-16 11:24:20 -07:00
|
|
|
this.browser.addEventListener("dblclick", this, true);
|
|
|
|
this.browser.addEventListener("mousedown", this, true);
|
|
|
|
this.browser.addEventListener("mouseup", this, true);
|
|
|
|
},
|
|
|
|
|
2011-07-11 10:18:11 -07:00
|
|
|
detachInspectListeners: function Highlighter_detachInspectListeners()
|
2011-06-16 11:24:20 -07:00
|
|
|
{
|
|
|
|
this.browser.removeEventListener("mousemove", this, true);
|
2011-07-11 10:18:11 -07:00
|
|
|
this.browser.removeEventListener("click", this, true);
|
2011-06-16 11:24:20 -07:00
|
|
|
this.browser.removeEventListener("dblclick", this, true);
|
|
|
|
this.browser.removeEventListener("mousedown", this, true);
|
|
|
|
this.browser.removeEventListener("mouseup", this, true);
|
|
|
|
},
|
|
|
|
|
2011-07-11 10:18:11 -07:00
|
|
|
|
2010-05-13 13:38:21 -07:00
|
|
|
/**
|
2011-06-16 11:24:20 -07:00
|
|
|
* Generic event handler.
|
2010-05-13 13:38:21 -07:00
|
|
|
*
|
2011-06-16 11:24:20 -07:00
|
|
|
* @param nsIDOMEvent aEvent
|
|
|
|
* The DOM event object.
|
2010-05-13 13:38:21 -07:00
|
|
|
*/
|
2011-07-11 10:18:11 -07:00
|
|
|
handleEvent: function Highlighter_handleEvent(aEvent)
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2011-06-16 11:24:20 -07:00
|
|
|
switch (aEvent.type) {
|
|
|
|
case "click":
|
|
|
|
this.handleClick(aEvent);
|
|
|
|
break;
|
|
|
|
case "mousemove":
|
|
|
|
this.handleMouseMove(aEvent);
|
|
|
|
break;
|
|
|
|
case "resize":
|
|
|
|
this.handleResize(aEvent);
|
|
|
|
break;
|
|
|
|
case "dblclick":
|
|
|
|
case "mousedown":
|
|
|
|
case "mouseup":
|
|
|
|
aEvent.stopPropagation();
|
|
|
|
aEvent.preventDefault();
|
|
|
|
break;
|
2011-07-11 10:18:11 -07:00
|
|
|
case "scroll":
|
|
|
|
this.highlight();
|
|
|
|
break;
|
2011-06-16 11:24:20 -07:00
|
|
|
}
|
2010-05-13 13:38:21 -07:00
|
|
|
},
|
|
|
|
|
2011-06-16 11:24:20 -07:00
|
|
|
/**
|
2011-07-11 10:18:11 -07:00
|
|
|
* Handle clicks.
|
2011-06-16 11:24:20 -07:00
|
|
|
*
|
|
|
|
* @param nsIDOMEvent aEvent
|
|
|
|
* The DOM event.
|
|
|
|
*/
|
2011-07-11 10:18:11 -07:00
|
|
|
handleClick: function Highlighter_handleClick(aEvent)
|
2011-06-16 11:24:20 -07:00
|
|
|
{
|
|
|
|
// Stop inspection when the user clicks on a node.
|
2011-07-11 10:18:11 -07:00
|
|
|
if (aEvent.button == 0) {
|
|
|
|
let win = aEvent.target.ownerDocument.defaultView;
|
|
|
|
InspectorUI.stopInspecting();
|
|
|
|
win.focus();
|
2011-06-16 11:24:20 -07:00
|
|
|
}
|
2011-07-11 10:18:11 -07:00
|
|
|
aEvent.preventDefault();
|
|
|
|
aEvent.stopPropagation();
|
2011-06-16 11:24:20 -07:00
|
|
|
},
|
2010-05-13 13:38:21 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle mousemoves in panel when InspectorUI.inspecting is true.
|
|
|
|
*
|
2011-06-16 11:24:20 -07:00
|
|
|
* @param nsiDOMEvent aEvent
|
2010-05-13 13:38:21 -07:00
|
|
|
* The MouseEvent triggering the method.
|
|
|
|
*/
|
2011-07-11 10:18:11 -07:00
|
|
|
handleMouseMove: function Highlighter_handleMouseMove(aEvent)
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2011-06-16 11:24:20 -07:00
|
|
|
let element = InspectorUI.elementFromPoint(aEvent.target.ownerDocument,
|
|
|
|
aEvent.clientX, aEvent.clientY);
|
2010-05-13 13:38:21 -07:00
|
|
|
if (element && element != this.node) {
|
|
|
|
InspectorUI.inspectNode(element);
|
|
|
|
}
|
|
|
|
},
|
2010-08-09 12:02:20 -07:00
|
|
|
|
|
|
|
/**
|
2011-06-16 11:24:20 -07:00
|
|
|
* Handle window resize events.
|
2010-08-09 12:02:20 -07:00
|
|
|
*/
|
2011-07-11 10:18:11 -07:00
|
|
|
handleResize: function Highlighter_handleResize()
|
2011-06-16 11:24:20 -07:00
|
|
|
{
|
|
|
|
this.highlight();
|
|
|
|
},
|
2010-05-13 13:38:21 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
//// InspectorUI
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Main controller class for the Inspector.
|
|
|
|
*/
|
|
|
|
var InspectorUI = {
|
|
|
|
browser: null,
|
2011-07-26 08:28:54 -07:00
|
|
|
tools: {},
|
2011-09-26 09:43:37 -07:00
|
|
|
toolEvents: {},
|
2010-09-03 11:34:09 -07:00
|
|
|
showTextNodesWithWhitespace: false,
|
2010-05-13 13:38:21 -07:00
|
|
|
inspecting: false,
|
2010-09-09 14:52:43 -07:00
|
|
|
treeLoaded: false,
|
2011-09-21 03:16:25 -07:00
|
|
|
get enabled()
|
|
|
|
{
|
|
|
|
return gPrefService.getBoolPref("devtools.inspector.enabled");
|
|
|
|
},
|
2011-08-23 13:34:32 -07:00
|
|
|
isDirty: false,
|
2010-05-13 13:38:21 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Toggle the inspector interface elements on or off.
|
|
|
|
*
|
2010-07-16 07:12:39 -07:00
|
|
|
* @param aEvent
|
2010-05-13 13:38:21 -07:00
|
|
|
* The event that requested the UI change. Toolbar button or menu.
|
|
|
|
*/
|
2010-07-16 07:12:39 -07:00
|
|
|
toggleInspectorUI: function IUI_toggleInspectorUI(aEvent)
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2010-09-03 11:34:09 -07:00
|
|
|
if (this.isTreePanelOpen) {
|
2011-05-04 07:45:40 -07:00
|
|
|
this.closeInspectorUI();
|
2010-05-13 13:38:21 -07:00
|
|
|
} else {
|
|
|
|
this.openInspectorUI();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2010-07-16 07:12:39 -07:00
|
|
|
/**
|
|
|
|
* Toggle the status of the inspector, starting or stopping it. Invoked
|
|
|
|
* from the toolbar's Inspect button.
|
|
|
|
*/
|
|
|
|
toggleInspection: function IUI_toggleInspection()
|
|
|
|
{
|
|
|
|
if (this.inspecting) {
|
|
|
|
this.stopInspecting();
|
|
|
|
} else {
|
|
|
|
this.startInspecting();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2010-05-13 13:38:21 -07:00
|
|
|
/**
|
|
|
|
* Is the tree panel open?
|
|
|
|
*
|
|
|
|
* @returns boolean
|
|
|
|
*/
|
2010-09-03 11:34:09 -07:00
|
|
|
get isTreePanelOpen()
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
|
|
|
return this.treePanel && this.treePanel.state == "open";
|
|
|
|
},
|
|
|
|
|
2010-09-03 11:34:09 -07:00
|
|
|
/**
|
|
|
|
* Return the default selection element for the inspected document.
|
|
|
|
*/
|
|
|
|
get defaultSelection()
|
|
|
|
{
|
|
|
|
let doc = this.win.document;
|
2011-08-23 13:34:32 -07:00
|
|
|
return doc.documentElement ? doc.documentElement.lastElementChild : null;
|
2010-09-03 11:34:09 -07:00
|
|
|
},
|
|
|
|
|
2010-09-09 14:52:43 -07:00
|
|
|
initializeTreePanel: function IUI_initializeTreePanel()
|
|
|
|
{
|
|
|
|
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.win.document.documentElement);
|
|
|
|
this.treeLoaded = true;
|
2011-08-17 05:16:15 -07:00
|
|
|
this.editingContext = null;
|
|
|
|
this.editingEvents = {};
|
2011-05-26 10:38:09 -07:00
|
|
|
|
2011-06-16 11:24:20 -07:00
|
|
|
// initialize the highlighter
|
2011-05-26 10:38:09 -07:00
|
|
|
this.initializeHighlighter();
|
2010-09-09 14:52:43 -07:00
|
|
|
},
|
|
|
|
|
2010-05-13 13:38:21 -07:00
|
|
|
/**
|
|
|
|
* Open the inspector's tree panel and initialize it.
|
|
|
|
*/
|
2010-07-16 07:12:39 -07:00
|
|
|
openTreePanel: function IUI_openTreePanel()
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
|
|
|
if (!this.treePanel) {
|
2010-09-03 11:34:09 -07:00
|
|
|
this.treePanel = document.getElementById("inspector-tree-panel");
|
2010-05-13 13:38:21 -07:00
|
|
|
this.treePanel.hidden = false;
|
|
|
|
}
|
2010-09-03 11:34:09 -07:00
|
|
|
|
2010-09-09 14:52:43 -07:00
|
|
|
this.treeIFrame = document.getElementById("inspector-tree-iframe");
|
|
|
|
if (!this.treeIFrame) {
|
|
|
|
let resizerBox = document.getElementById("tree-panel-resizer-box");
|
|
|
|
this.treeIFrame = document.createElement("iframe");
|
|
|
|
this.treeIFrame.setAttribute("id", "inspector-tree-iframe");
|
|
|
|
this.treeIFrame.setAttribute("flex", "1");
|
|
|
|
this.treeIFrame.setAttribute("type", "content");
|
|
|
|
this.treeIFrame.setAttribute("onclick", "InspectorUI.onTreeClick(event)");
|
2011-08-17 05:16:15 -07:00
|
|
|
this.treeIFrame.setAttribute("ondblclick", "InspectorUI.onTreeDblClick(event);");
|
2010-09-09 14:52:43 -07:00
|
|
|
this.treeIFrame = this.treePanel.insertBefore(this.treeIFrame, resizerBox);
|
|
|
|
}
|
2011-05-26 10:38:09 -07:00
|
|
|
|
|
|
|
this.treePanel.addEventListener("popupshown", function treePanelShown() {
|
|
|
|
InspectorUI.treePanel.removeEventListener("popupshown",
|
|
|
|
treePanelShown, false);
|
|
|
|
|
2011-09-21 03:16:25 -07:00
|
|
|
InspectorUI.treeIFrame.addEventListener("load",
|
|
|
|
function loadedInitializeTreePanel() {
|
|
|
|
InspectorUI.treeIFrame.removeEventListener("load",
|
|
|
|
loadedInitializeTreePanel, true);
|
|
|
|
InspectorUI.initializeTreePanel();
|
|
|
|
}, true);
|
2011-05-26 10:38:09 -07:00
|
|
|
|
|
|
|
let src = InspectorUI.treeIFrame.getAttribute("src");
|
|
|
|
if (src != "chrome://browser/content/inspector.html") {
|
|
|
|
InspectorUI.treeIFrame.setAttribute("src",
|
|
|
|
"chrome://browser/content/inspector.html");
|
|
|
|
} else {
|
|
|
|
InspectorUI.treeIFrame.contentWindow.location.reload();
|
|
|
|
}
|
|
|
|
|
|
|
|
}, false);
|
|
|
|
|
2010-09-03 11:34:09 -07:00
|
|
|
const panelWidthRatio = 7 / 8;
|
|
|
|
const panelHeightRatio = 1 / 5;
|
2011-05-26 10:38:09 -07:00
|
|
|
|
|
|
|
let width = parseInt(this.win.outerWidth * panelWidthRatio);
|
|
|
|
let height = parseInt(this.win.outerHeight * panelHeightRatio);
|
|
|
|
let y = Math.min(window.screen.availHeight - height, this.win.innerHeight);
|
|
|
|
|
|
|
|
this.treePanel.openPopup(this.browser, "overlap", 0, 0,
|
2010-09-03 11:34:09 -07:00
|
|
|
false, false);
|
2011-05-26 10:38:09 -07:00
|
|
|
|
|
|
|
this.treePanel.moveTo(80, y);
|
|
|
|
this.treePanel.sizeTo(width, height);
|
2010-09-03 11:34:09 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
createObjectBox: function IUI_createObjectBox(object, isRoot)
|
|
|
|
{
|
|
|
|
let tag = this.domplateUtils.getNodeTag(object);
|
|
|
|
if (tag)
|
|
|
|
return tag.replace({object: object}, this.treeBrowserDocument);
|
|
|
|
},
|
|
|
|
|
|
|
|
getParentObject: function IUI_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 == 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
|
2010-05-13 13:38:21 -07:00
|
|
|
}
|
2010-09-03 11:34:09 -07:00
|
|
|
|
|
|
|
if (parentNode.nodeType == Node.DOCUMENT_NODE) {
|
|
|
|
if (parentNode.defaultView) {
|
|
|
|
return parentNode.defaultView.frameElement;
|
|
|
|
}
|
2011-07-05 07:54:46 -07:00
|
|
|
// parent is document element, but no window at defaultView.
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (!parentNode.localName) {
|
2010-09-03 11:34:09 -07:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return parentNode;
|
|
|
|
},
|
|
|
|
|
|
|
|
getChildObject: function IUI_getChildObject(node, index, previousSibling)
|
|
|
|
{
|
|
|
|
if (!node)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
if (node.contentDocument) {
|
|
|
|
// then the node is a frame
|
|
|
|
if (index == 0) {
|
2011-07-05 07:54:46 -07:00
|
|
|
return node.contentDocument.documentElement; // the node's HTMLElement
|
2010-09-03 11:34:09 -07:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node instanceof GetSVGDocument) {
|
2011-06-30 06:17:48 -07:00
|
|
|
let svgDocument = node.getSVGDocument();
|
|
|
|
if (svgDocument) {
|
|
|
|
// then the node is a frame
|
|
|
|
if (index == 0) {
|
2011-07-05 07:54:46 -07:00
|
|
|
return svgDocument.documentElement; // the node's SVGElement
|
2011-06-30 06:17:48 -07:00
|
|
|
}
|
|
|
|
return null;
|
2010-09-03 11:34:09 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let child = null;
|
|
|
|
if (previousSibling) // then we are walking
|
|
|
|
child = this.getNextSibling(previousSibling);
|
|
|
|
else
|
|
|
|
child = this.getFirstChild(node);
|
|
|
|
|
|
|
|
if (this.showTextNodesWithWhitespace)
|
|
|
|
return child;
|
|
|
|
|
|
|
|
for (; child; child = this.getNextSibling(child)) {
|
|
|
|
if (!this.domplateUtils.isWhitespaceText(child))
|
|
|
|
return child;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null; // we have no children worth showing.
|
|
|
|
},
|
|
|
|
|
|
|
|
getFirstChild: function IUI_getFirstChild(node)
|
|
|
|
{
|
|
|
|
this.treeWalker = node.ownerDocument.createTreeWalker(node,
|
|
|
|
NodeFilter.SHOW_ALL, null, false);
|
|
|
|
return this.treeWalker.firstChild();
|
|
|
|
},
|
|
|
|
|
|
|
|
getNextSibling: function IUI_getNextSibling(node)
|
|
|
|
{
|
|
|
|
let next = this.treeWalker.nextSibling();
|
|
|
|
|
|
|
|
if (!next)
|
|
|
|
delete this.treeWalker;
|
|
|
|
|
|
|
|
return next;
|
2010-05-13 13:38:21 -07:00
|
|
|
},
|
|
|
|
|
2010-07-16 07:12:39 -07:00
|
|
|
/**
|
2011-09-21 03:16:25 -07:00
|
|
|
* Open inspector UI and HTML tree. Add listeners for document scrolling,
|
|
|
|
* resize, tabContainer.TabSelect and others. If a node is provided, then
|
|
|
|
* start inspecting it.
|
|
|
|
*
|
|
|
|
* @param [optional] aNode
|
|
|
|
* The node to inspect.
|
2010-05-13 13:38:21 -07:00
|
|
|
*/
|
2011-09-21 03:16:25 -07:00
|
|
|
openInspectorUI: function IUI_openInspectorUI(aNode)
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2011-09-21 03:16:25 -07:00
|
|
|
// Observer used to inspect the specified element from content after the
|
|
|
|
// inspector UI has been opened.
|
|
|
|
function inspectObserver(aElement) {
|
|
|
|
Services.obs.removeObserver(boundInspectObserver,
|
|
|
|
INSPECTOR_NOTIFICATIONS.OPENED,
|
|
|
|
false);
|
|
|
|
this.inspectNode(aElement);
|
|
|
|
this.stopInspecting();
|
|
|
|
};
|
|
|
|
var boundInspectObserver = inspectObserver.bind(this, aNode);
|
|
|
|
|
|
|
|
if (aNode) {
|
|
|
|
// Add the observer to inspect the node after initialization finishes.
|
|
|
|
Services.obs.addObserver(boundInspectObserver,
|
|
|
|
INSPECTOR_NOTIFICATIONS.OPENED,
|
|
|
|
false);
|
|
|
|
}
|
|
|
|
// Start initialization.
|
2010-05-13 13:38:21 -07:00
|
|
|
this.browser = gBrowser.selectedBrowser;
|
|
|
|
this.win = this.browser.contentWindow;
|
2010-08-25 12:47:36 -07:00
|
|
|
this.winID = this.getWindowID(this.win);
|
2011-08-02 14:28:49 -07:00
|
|
|
this.toolbar = document.getElementById("inspector-toolbar");
|
|
|
|
|
2011-09-26 09:43:37 -07:00
|
|
|
this.initTools();
|
|
|
|
|
2010-09-03 11:34:09 -07:00
|
|
|
if (!this.domplate) {
|
|
|
|
Cu.import("resource:///modules/domplate.jsm", this);
|
|
|
|
this.domplateUtils.setDOM(window);
|
|
|
|
}
|
2010-08-18 16:54:06 -07:00
|
|
|
|
|
|
|
this.openTreePanel();
|
|
|
|
|
2011-08-04 10:04:42 -07:00
|
|
|
this.toolbar.hidden = false;
|
2010-05-20 10:34:04 -07:00
|
|
|
this.inspectCmd.setAttribute("checked", true);
|
2011-08-23 13:34:32 -07:00
|
|
|
|
|
|
|
gBrowser.addProgressListener(InspectorProgressListener);
|
2010-09-03 11:34:09 -07:00
|
|
|
},
|
2010-08-25 12:47:36 -07:00
|
|
|
|
2011-09-26 09:43:37 -07:00
|
|
|
/**
|
|
|
|
* Register and initialize any included tools.
|
|
|
|
*/
|
|
|
|
initTools: function IUI_initTools()
|
|
|
|
{
|
2011-09-26 09:46:44 -07:00
|
|
|
// Style inspector
|
|
|
|
if (Services.prefs.getBoolPref("devtools.styleinspector.enabled") &&
|
|
|
|
!this.toolRegistered("styleinspector")) {
|
|
|
|
let stylePanel = this.StyleInspector.createPanel(true);
|
|
|
|
this.registerTool({
|
|
|
|
id: "styleinspector",
|
|
|
|
label: InspectorUI.StyleInspector.l10n("style.highlighter.button.label"),
|
|
|
|
tooltiptext: InspectorUI.StyleInspector.l10n("style.highlighter.button.tooltip"),
|
|
|
|
accesskey: InspectorUI.StyleInspector.l10n("style.highlighter.accesskey"),
|
|
|
|
context: stylePanel,
|
|
|
|
get isOpen() stylePanel.isOpen(),
|
|
|
|
onSelect: stylePanel.selectNode,
|
|
|
|
show: stylePanel.showTool,
|
|
|
|
hide: stylePanel.hideTool,
|
|
|
|
dim: stylePanel.dimTool,
|
|
|
|
panel: stylePanel,
|
|
|
|
unregister: stylePanel.destroy,
|
|
|
|
});
|
|
|
|
this.stylePanel = stylePanel;
|
|
|
|
}
|
2011-09-26 09:43:37 -07:00
|
|
|
},
|
|
|
|
|
2010-09-03 11:34:09 -07:00
|
|
|
/**
|
|
|
|
* Initialize highlighter.
|
|
|
|
*/
|
|
|
|
initializeHighlighter: function IUI_initializeHighlighter()
|
|
|
|
{
|
2011-07-11 10:18:11 -07:00
|
|
|
this.highlighter = new Highlighter(this.browser);
|
|
|
|
this.highlighterReady();
|
2010-09-03 11:34:09 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize the InspectorStore.
|
|
|
|
*/
|
|
|
|
initializeStore: function IUI_initializeStore()
|
|
|
|
{
|
|
|
|
// First time opened, add the TabSelect listener
|
|
|
|
if (InspectorStore.isEmpty())
|
2010-08-25 12:47:36 -07:00
|
|
|
gBrowser.tabContainer.addEventListener("TabSelect", this, false);
|
2010-09-01 10:28:56 -07:00
|
|
|
|
2010-09-03 11:34:09 -07:00
|
|
|
// Has this windowID been inspected before?
|
2010-09-01 10:28:56 -07:00
|
|
|
if (InspectorStore.hasID(this.winID)) {
|
|
|
|
let selectedNode = InspectorStore.getValue(this.winID, "selectedNode");
|
|
|
|
if (selectedNode) {
|
|
|
|
this.inspectNode(selectedNode);
|
|
|
|
}
|
|
|
|
} else {
|
2010-09-03 11:34:09 -07:00
|
|
|
// First time inspecting, set state to no selection + live inspection.
|
2010-09-01 10:28:56 -07:00
|
|
|
InspectorStore.addStore(this.winID);
|
|
|
|
InspectorStore.setValue(this.winID, "selectedNode", null);
|
2010-09-03 11:34:09 -07:00
|
|
|
InspectorStore.setValue(this.winID, "inspecting", true);
|
2010-09-01 10:28:56 -07:00
|
|
|
this.win.addEventListener("pagehide", this, true);
|
|
|
|
}
|
2010-05-13 13:38:21 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Close inspector UI and associated panels. Unhighlight and stop inspecting.
|
2010-08-25 12:47:36 -07:00
|
|
|
* Remove event listeners for document scrolling, resize,
|
|
|
|
* tabContainer.TabSelect and others.
|
|
|
|
*
|
2011-05-04 07:45:40 -07:00
|
|
|
* @param boolean aKeepStore
|
|
|
|
* Tells if you want the store associated to the current tab/window to
|
|
|
|
* be cleared or not. Set this to true to not clear the store, or false
|
|
|
|
* otherwise.
|
2010-05-13 13:38:21 -07:00
|
|
|
*/
|
2011-05-04 07:45:40 -07:00
|
|
|
closeInspectorUI: function IUI_closeInspectorUI(aKeepStore)
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2011-08-17 05:16:15 -07:00
|
|
|
// if currently editing an attribute value, closing the
|
|
|
|
// highlighter/HTML panel dismisses the editor
|
|
|
|
if (this.editingContext)
|
|
|
|
this.closeEditor();
|
|
|
|
|
2010-09-03 11:34:09 -07:00
|
|
|
if (this.closing || !this.win || !this.browser) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.closing = true;
|
2011-08-02 14:28:49 -07:00
|
|
|
this.toolbar.hidden = true;
|
2010-09-03 11:34:09 -07:00
|
|
|
|
2011-08-23 13:34:32 -07:00
|
|
|
gBrowser.removeProgressListener(InspectorProgressListener);
|
|
|
|
|
2011-05-04 07:45:40 -07:00
|
|
|
if (!aKeepStore) {
|
2010-08-25 12:47:36 -07:00
|
|
|
InspectorStore.deleteStore(this.winID);
|
|
|
|
this.win.removeEventListener("pagehide", this, true);
|
|
|
|
} else {
|
|
|
|
// Update the store before closing.
|
2010-09-03 11:34:09 -07:00
|
|
|
if (this.selection) {
|
|
|
|
InspectorStore.setValue(this.winID, "selectedNode",
|
|
|
|
this.selection);
|
|
|
|
}
|
2010-08-25 12:47:36 -07:00
|
|
|
InspectorStore.setValue(this.winID, "inspecting", this.inspecting);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (InspectorStore.isEmpty()) {
|
|
|
|
gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
|
|
|
|
}
|
|
|
|
|
2010-05-13 13:38:21 -07:00
|
|
|
this.stopInspecting();
|
2011-09-26 09:43:37 -07:00
|
|
|
|
|
|
|
this.saveToolState(this.winID);
|
|
|
|
this.toolsDo(function IUI_toolsHide(aTool) {
|
|
|
|
this.unregisterTool(aTool);
|
|
|
|
}.bind(this));
|
|
|
|
|
2011-06-16 11:24:20 -07:00
|
|
|
if (this.highlighter) {
|
|
|
|
this.highlighter.destroy();
|
|
|
|
this.highlighter = null;
|
2010-05-13 13:38:21 -07:00
|
|
|
}
|
2010-09-03 11:34:09 -07:00
|
|
|
|
|
|
|
if (this.treePanelDiv) {
|
|
|
|
this.treePanelDiv.ownerPanel = null;
|
|
|
|
let parent = this.treePanelDiv.parentNode;
|
|
|
|
parent.removeChild(this.treePanelDiv);
|
|
|
|
delete this.treePanelDiv;
|
|
|
|
delete this.treeBrowserDocument;
|
|
|
|
}
|
|
|
|
|
2011-08-20 10:22:10 -07:00
|
|
|
if (this.treeIFrame) {
|
|
|
|
let parent = this.treeIFrame.parentNode;
|
|
|
|
parent.removeChild(this.treeIFrame);
|
2010-09-03 11:34:09 -07:00
|
|
|
delete this.treeIFrame;
|
2011-08-20 10:22:10 -07:00
|
|
|
}
|
2010-09-03 11:34:09 -07:00
|
|
|
delete this.ioBox;
|
|
|
|
|
|
|
|
if (this.domplate) {
|
|
|
|
this.domplateUtils.setDOM(null);
|
|
|
|
delete this.domplate;
|
|
|
|
delete this.HTMLTemplates;
|
|
|
|
delete this.domplateUtils;
|
2010-05-13 13:38:21 -07:00
|
|
|
}
|
2010-09-03 11:34:09 -07:00
|
|
|
|
2010-05-20 10:34:04 -07:00
|
|
|
this.inspectCmd.setAttribute("checked", false);
|
2010-05-13 13:38:21 -07:00
|
|
|
this.browser = this.win = null; // null out references to browser and window
|
2010-08-25 12:47:36 -07:00
|
|
|
this.winID = null;
|
2010-09-03 11:34:09 -07:00
|
|
|
this.selection = null;
|
2010-09-09 14:52:43 -07:00
|
|
|
this.treeLoaded = false;
|
2011-05-26 10:38:09 -07:00
|
|
|
|
|
|
|
this.treePanel.addEventListener("popuphidden", function treePanelHidden() {
|
2011-08-20 10:22:10 -07:00
|
|
|
this.removeEventListener("popuphidden", treePanelHidden, false);
|
|
|
|
|
2011-05-26 10:38:09 -07:00
|
|
|
InspectorUI.closing = false;
|
2011-06-16 11:24:20 -07:00
|
|
|
Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.CLOSED, null);
|
2011-05-26 10:38:09 -07:00
|
|
|
}, false);
|
|
|
|
|
|
|
|
this.treePanel.hidePopup();
|
|
|
|
delete this.treePanel;
|
2011-09-26 09:46:44 -07:00
|
|
|
delete this.stylePanel;
|
2010-05-13 13:38:21 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Begin inspecting webpage, attach page event listeners, activate
|
|
|
|
* highlighter event listeners.
|
|
|
|
*/
|
2010-07-16 07:12:39 -07:00
|
|
|
startInspecting: function IUI_startInspecting()
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2011-08-17 05:16:15 -07:00
|
|
|
// if currently editing an attribute value, starting
|
|
|
|
// "live inspection" mode closes the editor
|
|
|
|
if (this.editingContext)
|
|
|
|
this.closeEditor();
|
|
|
|
|
2011-07-26 08:28:54 -07:00
|
|
|
document.getElementById("inspector-inspect-toolbutton").checked = true;
|
2010-05-13 13:38:21 -07:00
|
|
|
this.attachPageListeners();
|
|
|
|
this.inspecting = true;
|
2011-09-26 09:46:44 -07:00
|
|
|
this.toolsDim(true);
|
2011-09-01 09:33:33 -07:00
|
|
|
this.highlighter.veilContainer.removeAttribute("locked");
|
2010-05-13 13:38:21 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stop inspecting webpage, detach page listeners, disable highlighter
|
|
|
|
* event listeners.
|
2011-08-02 14:08:38 -07:00
|
|
|
* @param aPreventScroll
|
|
|
|
* Prevent scroll in the HTML tree?
|
2010-05-13 13:38:21 -07:00
|
|
|
*/
|
2011-08-02 14:08:38 -07:00
|
|
|
stopInspecting: function IUI_stopInspecting(aPreventScroll)
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2011-06-16 11:24:20 -07:00
|
|
|
if (!this.inspecting) {
|
2010-05-13 13:38:21 -07:00
|
|
|
return;
|
2011-06-16 11:24:20 -07:00
|
|
|
}
|
|
|
|
|
2011-07-26 08:28:54 -07:00
|
|
|
document.getElementById("inspector-inspect-toolbutton").checked = false;
|
2010-05-13 13:38:21 -07:00
|
|
|
this.detachPageListeners();
|
|
|
|
this.inspecting = false;
|
2011-09-26 09:46:44 -07:00
|
|
|
this.toolsDim(false);
|
2010-09-03 11:34:09 -07:00
|
|
|
if (this.highlighter.node) {
|
2011-08-02 14:08:38 -07:00
|
|
|
this.select(this.highlighter.node, true, true, !aPreventScroll);
|
2011-06-16 11:24:20 -07:00
|
|
|
} else {
|
|
|
|
this.select(null, true, true);
|
2010-07-16 07:12:39 -07:00
|
|
|
}
|
2011-09-01 09:33:33 -07:00
|
|
|
this.highlighter.veilContainer.setAttribute("locked", true);
|
2010-05-13 13:38:21 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2010-09-03 11:34:09 -07:00
|
|
|
* Select an object in the tree view.
|
|
|
|
* @param aNode
|
|
|
|
* node to inspect
|
|
|
|
* @param forceUpdate
|
|
|
|
* force an update?
|
|
|
|
* @param aScroll
|
|
|
|
* force scroll?
|
2010-05-13 13:38:21 -07:00
|
|
|
*/
|
2010-09-03 11:34:09 -07:00
|
|
|
select: function IUI_select(aNode, forceUpdate, aScroll)
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2011-08-17 05:16:15 -07:00
|
|
|
// if currently editing an attribute value, using the
|
|
|
|
// highlighter dismisses the editor
|
|
|
|
if (this.editingContext)
|
|
|
|
this.closeEditor();
|
|
|
|
|
2010-09-03 11:34:09 -07:00
|
|
|
if (!aNode)
|
|
|
|
aNode = this.defaultSelection;
|
|
|
|
|
|
|
|
if (forceUpdate || aNode != this.selection) {
|
|
|
|
this.selection = aNode;
|
|
|
|
if (!this.inspecting) {
|
|
|
|
this.highlighter.highlightNode(this.selection);
|
|
|
|
}
|
2011-06-16 11:24:20 -07:00
|
|
|
this.ioBox.select(this.selection, true, true, aScroll);
|
2010-09-03 11:34:09 -07:00
|
|
|
}
|
2011-09-26 09:43:37 -07:00
|
|
|
|
|
|
|
this.toolsSelect();
|
2010-05-13 13:38:21 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Event Handling
|
|
|
|
|
2011-07-11 10:18:11 -07:00
|
|
|
highlighterReady: function IUI_highlighterReady()
|
2010-09-09 14:52:43 -07:00
|
|
|
{
|
2011-06-16 11:24:20 -07:00
|
|
|
// Setup the InspectorStore or restore state
|
|
|
|
this.initializeStore();
|
|
|
|
|
|
|
|
if (InspectorStore.getValue(this.winID, "inspecting")) {
|
|
|
|
this.startInspecting();
|
|
|
|
}
|
|
|
|
|
2011-09-26 09:43:37 -07:00
|
|
|
this.restoreToolState(this.winID);
|
|
|
|
|
2011-06-16 11:24:20 -07:00
|
|
|
this.win.focus();
|
|
|
|
Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.OPENED, null);
|
|
|
|
},
|
|
|
|
|
2010-05-13 13:38:21 -07:00
|
|
|
/**
|
|
|
|
* Main callback handler for events.
|
|
|
|
*
|
|
|
|
* @param event
|
|
|
|
* The event to be handled.
|
|
|
|
*/
|
2010-07-16 07:12:39 -07:00
|
|
|
handleEvent: function IUI_handleEvent(event)
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2010-08-25 12:47:36 -07:00
|
|
|
let winID = null;
|
|
|
|
let win = null;
|
2010-09-03 11:34:09 -07:00
|
|
|
let inspectorClosed = false;
|
2010-08-25 12:47:36 -07:00
|
|
|
|
2010-05-13 13:38:21 -07:00
|
|
|
switch (event.type) {
|
|
|
|
case "TabSelect":
|
2010-08-25 12:47:36 -07:00
|
|
|
winID = this.getWindowID(gBrowser.selectedBrowser.contentWindow);
|
2010-09-03 11:34:09 -07:00
|
|
|
if (this.isTreePanelOpen && winID != this.winID) {
|
2011-05-04 07:45:40 -07:00
|
|
|
this.closeInspectorUI(true);
|
2010-09-03 11:34:09 -07:00
|
|
|
inspectorClosed = true;
|
2010-08-25 12:47:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (winID && InspectorStore.hasID(winID)) {
|
2010-09-03 11:34:09 -07:00
|
|
|
if (inspectorClosed && this.closing) {
|
2011-05-26 10:38:09 -07:00
|
|
|
Services.obs.addObserver(function reopenInspectorForTab() {
|
|
|
|
Services.obs.removeObserver(reopenInspectorForTab,
|
2011-06-16 11:24:20 -07:00
|
|
|
INSPECTOR_NOTIFICATIONS.CLOSED, false);
|
|
|
|
|
2010-09-03 11:34:09 -07:00
|
|
|
InspectorUI.openInspectorUI();
|
2011-06-16 11:24:20 -07:00
|
|
|
}, INSPECTOR_NOTIFICATIONS.CLOSED, false);
|
2010-09-03 11:34:09 -07:00
|
|
|
} else {
|
|
|
|
this.openInspectorUI();
|
|
|
|
}
|
2011-05-26 10:38:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (InspectorStore.isEmpty()) {
|
2010-08-25 12:47:36 -07:00
|
|
|
gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "pagehide":
|
|
|
|
win = event.originalTarget.defaultView;
|
|
|
|
// Skip iframes/frames.
|
|
|
|
if (!win || win.frameElement || win.top != win) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
win.removeEventListener(event.type, this, true);
|
|
|
|
|
|
|
|
winID = this.getWindowID(win);
|
|
|
|
if (winID && winID != this.winID) {
|
|
|
|
InspectorStore.deleteStore(winID);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (InspectorStore.isEmpty()) {
|
|
|
|
gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
|
|
|
|
}
|
2010-05-13 13:38:21 -07:00
|
|
|
break;
|
|
|
|
case "keypress":
|
|
|
|
switch (event.keyCode) {
|
|
|
|
case KeyEvent.DOM_VK_RETURN:
|
|
|
|
case KeyEvent.DOM_VK_ESCAPE:
|
2011-06-16 11:24:20 -07:00
|
|
|
if (this.inspecting) {
|
|
|
|
this.stopInspecting();
|
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
|
|
|
}
|
2010-05-13 13:38:21 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2010-09-03 11:34:09 -07:00
|
|
|
* Handle click events in the html tree panel.
|
|
|
|
* @param aEvent
|
|
|
|
* The mouse event.
|
2010-05-13 13:38:21 -07:00
|
|
|
*/
|
2010-09-03 11:34:09 -07:00
|
|
|
onTreeClick: function IUI_onTreeClick(aEvent)
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2011-08-17 05:16:15 -07:00
|
|
|
// if currently editing an attribute value, clicking outside
|
|
|
|
// the editor dismisses the editor
|
|
|
|
if (this.editingContext) {
|
|
|
|
this.closeEditor();
|
|
|
|
|
|
|
|
// clicking outside the editor ONLY closes the editor
|
|
|
|
// so, cancel the rest of the processing of this event
|
|
|
|
aEvent.preventDefault();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-09-03 11:34:09 -07:00
|
|
|
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);
|
2010-05-13 13:38:21 -07:00
|
|
|
}
|
|
|
|
|
2010-09-03 11:34:09 -07:00
|
|
|
if (node) {
|
2011-08-02 14:08:38 -07:00
|
|
|
if (hitTwisty) {
|
2010-09-03 11:34:09 -07:00
|
|
|
this.ioBox.toggleObject(node);
|
2011-08-02 14:08:38 -07:00
|
|
|
} else {
|
|
|
|
if (this.inspecting) {
|
2011-09-26 09:46:44 -07:00
|
|
|
this.toolsSelect();
|
2011-08-02 14:08:38 -07:00
|
|
|
this.stopInspecting(true);
|
|
|
|
} else {
|
|
|
|
this.select(node, true, false);
|
|
|
|
this.highlighter.highlightNode(node);
|
2011-09-26 09:46:44 -07:00
|
|
|
this.toolsSelect();
|
2011-08-02 14:08:38 -07:00
|
|
|
}
|
|
|
|
}
|
2010-09-03 11:34:09 -07:00
|
|
|
}
|
2010-05-13 13:38:21 -07:00
|
|
|
},
|
|
|
|
|
2011-08-17 05:16:15 -07:00
|
|
|
/**
|
|
|
|
* 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 IUI_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 IUI_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, 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 IUI_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 IUI_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 IUI_handleEditorKeypress(aEvent)
|
|
|
|
{
|
|
|
|
if (aEvent.which == KeyEvent.DOM_VK_RETURN) {
|
|
|
|
this.saveEditor();
|
|
|
|
} else if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE) {
|
|
|
|
this.closeEditor();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Close the editor and cleanup.
|
|
|
|
*/
|
|
|
|
closeEditor: function IUI_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, INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED,
|
|
|
|
null);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Commit the edits made in the editor, then close it.
|
|
|
|
*/
|
|
|
|
saveEditor: function IUI_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;
|
|
|
|
|
2011-08-23 13:34:32 -07:00
|
|
|
this.isDirty = true;
|
|
|
|
|
2011-08-17 05:16:15 -07:00
|
|
|
// event notification
|
|
|
|
Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.EDITOR_SAVED,
|
|
|
|
null);
|
|
|
|
|
|
|
|
this.closeEditor();
|
|
|
|
},
|
|
|
|
|
2010-05-13 13:38:21 -07:00
|
|
|
/**
|
2010-09-03 11:34:09 -07:00
|
|
|
* Attach event listeners to content window and child windows to enable
|
2010-05-13 13:38:21 -07:00
|
|
|
* highlighting and click to stop inspection.
|
|
|
|
*/
|
2010-07-16 07:12:39 -07:00
|
|
|
attachPageListeners: function IUI_attachPageListeners()
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2011-06-16 11:24:20 -07:00
|
|
|
this.browser.addEventListener("keypress", this, true);
|
|
|
|
this.highlighter.attachInspectListeners();
|
2010-05-13 13:38:21 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Detach event listeners from content window and child windows
|
|
|
|
* to disable highlighting.
|
|
|
|
*/
|
2010-07-16 07:12:39 -07:00
|
|
|
detachPageListeners: function IUI_detachPageListeners()
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2011-06-16 11:24:20 -07:00
|
|
|
this.browser.removeEventListener("keypress", this, true);
|
|
|
|
this.highlighter.detachInspectListeners();
|
2010-05-13 13:38:21 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Utility Methods
|
|
|
|
|
|
|
|
/**
|
|
|
|
* inspect the given node, highlighting it on the page and selecting the
|
|
|
|
* correct row in the tree panel
|
|
|
|
*
|
2010-07-16 07:12:39 -07:00
|
|
|
* @param aNode
|
2010-05-13 13:38:21 -07:00
|
|
|
* the element in the document to inspect
|
|
|
|
*/
|
2010-07-16 07:12:39 -07:00
|
|
|
inspectNode: function IUI_inspectNode(aNode)
|
2010-05-13 13:38:21 -07:00
|
|
|
{
|
2010-09-03 11:34:09 -07:00
|
|
|
this.select(aNode, true, true);
|
2011-06-16 11:24:20 -07:00
|
|
|
this.highlighter.highlightNode(aNode);
|
2010-05-13 13:38:21 -07:00
|
|
|
},
|
|
|
|
|
2010-07-30 04:30:55 -07:00
|
|
|
/**
|
2010-09-03 11:34:09 -07:00
|
|
|
* Find an element from the given coordinates. This method descends through
|
2010-07-30 04:30:55 -07:00
|
|
|
* frames to find the element the user clicked inside frames.
|
|
|
|
*
|
|
|
|
* @param DOMDocument aDocument the document to look into.
|
|
|
|
* @param integer aX
|
|
|
|
* @param integer aY
|
|
|
|
* @returns Node|null the element node found at the given coordinates.
|
|
|
|
*/
|
|
|
|
elementFromPoint: function IUI_elementFromPoint(aDocument, aX, aY)
|
|
|
|
{
|
|
|
|
let node = aDocument.elementFromPoint(aX, aY);
|
|
|
|
if (node && node.contentDocument) {
|
2011-09-16 02:01:38 -07:00
|
|
|
if (node instanceof HTMLIFrameElement) {
|
|
|
|
let rect = node.getBoundingClientRect();
|
|
|
|
|
|
|
|
// Gap between the iframe and its content window.
|
|
|
|
let [offsetTop, offsetLeft] = this.getIframeContentOffset(node);
|
|
|
|
|
|
|
|
aX -= rect.left + offsetLeft;
|
|
|
|
aY -= rect.top + offsetTop;
|
|
|
|
|
|
|
|
if (aX < 0 || aY < 0) {
|
|
|
|
// Didn't reach the content document, still over the iframe.
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (node instanceof HTMLIFrameElement ||
|
|
|
|
node instanceof HTMLFrameElement) {
|
|
|
|
let subnode = this.elementFromPoint(node.contentDocument, aX, aY);
|
|
|
|
if (subnode) {
|
|
|
|
node = subnode;
|
|
|
|
}
|
2010-07-30 04:30:55 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return node;
|
|
|
|
},
|
|
|
|
|
2010-05-13 13:38:21 -07:00
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
//// Utility functions
|
|
|
|
|
2011-09-16 02:01:38 -07:00
|
|
|
/**
|
|
|
|
* Returns iframe content offset (iframe border + padding).
|
|
|
|
* Note: this function shouldn't need to exist, had the platform provided a
|
|
|
|
* suitable API for determining the offset between the iframe's content and
|
|
|
|
* its bounding client rect. Bug 626359 should provide us with such an API.
|
|
|
|
*
|
|
|
|
* @param aIframe
|
|
|
|
* The iframe.
|
|
|
|
* @returns array [offsetTop, offsetLeft]
|
|
|
|
* offsetTop is the distance from the top of the iframe and the
|
|
|
|
* top of the content document.
|
|
|
|
* offsetLeft is the distance from the left of the iframe and the
|
|
|
|
* left of the content document.
|
|
|
|
*/
|
|
|
|
getIframeContentOffset: function IUI_getIframeContentOffset(aIframe)
|
|
|
|
{
|
|
|
|
let style = aIframe.contentWindow.getComputedStyle(aIframe, null);
|
|
|
|
|
|
|
|
let paddingTop = parseInt(style.getPropertyValue("padding-top"));
|
|
|
|
let paddingLeft = parseInt(style.getPropertyValue("padding-left"));
|
|
|
|
|
|
|
|
let borderTop = parseInt(style.getPropertyValue("border-top-width"));
|
|
|
|
let borderLeft = parseInt(style.getPropertyValue("border-left-width"));
|
|
|
|
|
|
|
|
return [borderTop + paddingTop, borderLeft + paddingLeft];
|
|
|
|
},
|
|
|
|
|
2010-09-03 11:34:09 -07:00
|
|
|
/**
|
|
|
|
* Does the given object have a class attribute?
|
|
|
|
* @param aNode
|
|
|
|
* the DOM node.
|
|
|
|
* @param aClass
|
|
|
|
* The class string.
|
|
|
|
* @returns boolean
|
|
|
|
*/
|
|
|
|
hasClass: function IUI_hasClass(aNode, aClass)
|
|
|
|
{
|
|
|
|
if (!(aNode instanceof 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 IUI_addClass(aNode, aClass)
|
|
|
|
{
|
|
|
|
if (aNode instanceof 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 IUI_removeClass(aNode, aClass)
|
|
|
|
{
|
|
|
|
if (aNode instanceof Element)
|
|
|
|
aNode.classList.remove(aClass);
|
|
|
|
},
|
|
|
|
|
2010-08-25 12:47:36 -07:00
|
|
|
/**
|
|
|
|
* Retrieve the unique ID of a window object.
|
|
|
|
*
|
|
|
|
* @param nsIDOMWindow aWindow
|
|
|
|
* @returns integer ID
|
|
|
|
*/
|
|
|
|
getWindowID: function IUI_getWindowID(aWindow)
|
|
|
|
{
|
|
|
|
if (!aWindow) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
let util = {};
|
|
|
|
|
|
|
|
try {
|
|
|
|
util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
|
|
|
|
getInterface(Ci.nsIDOMWindowUtils);
|
|
|
|
} catch (ex) { }
|
|
|
|
|
|
|
|
return util.currentInnerWindowID;
|
|
|
|
},
|
|
|
|
|
2010-05-13 13:38:21 -07:00
|
|
|
/**
|
2010-09-03 11:34:09 -07:00
|
|
|
* 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 IUI_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;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2010-05-13 13:38:21 -07:00
|
|
|
* @param msg
|
|
|
|
* text message to send to the log
|
|
|
|
*/
|
|
|
|
_log: function LOG(msg)
|
|
|
|
{
|
|
|
|
Services.console.logStringMessage(msg);
|
|
|
|
},
|
2011-05-26 10:38:09 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Debugging function.
|
|
|
|
* @param msg
|
|
|
|
* text to show with the stack trace.
|
|
|
|
*/
|
|
|
|
_trace: function TRACE(msg)
|
|
|
|
{
|
|
|
|
this._log("TRACE: " + msg);
|
|
|
|
let frame = Components.stack.caller;
|
|
|
|
while (frame = frame.caller) {
|
|
|
|
if (frame.language == Ci.nsIProgrammingLanguage.JAVASCRIPT ||
|
|
|
|
frame.language == Ci.nsIProgrammingLanguage.JAVASCRIPT2) {
|
|
|
|
this._log("filename: " + frame.filename + " lineNumber: " + frame.lineNumber +
|
|
|
|
" functionName: " + frame.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this._log("END TRACE");
|
|
|
|
},
|
2011-07-26 08:28:54 -07:00
|
|
|
|
2011-09-26 09:43:37 -07:00
|
|
|
/**
|
|
|
|
* Get the toolbar button name for a given id string. Used by the
|
|
|
|
* registerTools API to retrieve a consistent name for toolbar buttons
|
|
|
|
* based on the ID of the tool.
|
|
|
|
* @param anId String
|
|
|
|
* id of the tool to be buttonized
|
|
|
|
* @returns String
|
|
|
|
*/
|
|
|
|
getToolbarButtonId: function IUI_createButtonId(anId)
|
|
|
|
{
|
|
|
|
return "inspector-" + anId + "-toolbutton";
|
|
|
|
},
|
|
|
|
|
2011-07-26 08:28:54 -07:00
|
|
|
/**
|
|
|
|
* Register an external tool with the inspector.
|
|
|
|
*
|
|
|
|
* aRegObj = {
|
|
|
|
* id: "toolname",
|
|
|
|
* context: myTool,
|
|
|
|
* label: "Button label",
|
|
|
|
* icon: "chrome://somepath.png",
|
|
|
|
* tooltiptext: "Button tooltip",
|
|
|
|
* accesskey: "S",
|
2011-09-26 09:43:37 -07:00
|
|
|
* isOpen: object.property, (getter) returning true if tool is open.
|
2011-07-26 08:28:54 -07:00
|
|
|
* onSelect: object.method,
|
2011-09-26 09:43:37 -07:00
|
|
|
* show: object.method, called to show the tool when button is pressed.
|
|
|
|
* hide: object.method, called to hide the tool when button is pressed.
|
2011-09-26 09:46:44 -07:00
|
|
|
* dim: object.method, called to disable a tool during highlighting.
|
2011-09-26 09:43:37 -07:00
|
|
|
* unregister: object.method, called when tool should be destroyed.
|
2011-07-26 08:28:54 -07:00
|
|
|
* panel: myTool.panel
|
|
|
|
* }
|
|
|
|
*
|
2011-09-26 09:43:37 -07:00
|
|
|
* @param aRegObj Object
|
|
|
|
* The Registration Object used to register this tool described
|
|
|
|
* above. The tool should cache this object for later deregistration.
|
2011-07-26 08:28:54 -07:00
|
|
|
*/
|
2011-09-26 09:43:37 -07:00
|
|
|
registerTool: function IUI_registerTool(aRegObj)
|
|
|
|
{
|
|
|
|
if (this.toolRegistered(aRegObj.id)) {
|
2011-07-26 08:28:54 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-09-26 09:43:37 -07:00
|
|
|
this.tools[aRegObj.id] = aRegObj;
|
|
|
|
|
|
|
|
let buttonContainer = document.getElementById("inspector-tools");
|
2011-07-26 08:28:54 -07:00
|
|
|
let btn = document.createElement("toolbarbutton");
|
2011-09-26 09:43:37 -07:00
|
|
|
let buttonId = this.getToolbarButtonId(aRegObj.id);
|
|
|
|
btn.setAttribute("id", buttonId);
|
2011-07-26 08:28:54 -07:00
|
|
|
btn.setAttribute("label", aRegObj.label);
|
|
|
|
btn.setAttribute("tooltiptext", aRegObj.tooltiptext);
|
|
|
|
btn.setAttribute("accesskey", aRegObj.accesskey);
|
|
|
|
btn.setAttribute("image", aRegObj.icon || "");
|
2011-09-26 09:43:37 -07:00
|
|
|
buttonContainer.appendChild(btn);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Save the registered tool's toolbar button's click handler so we can remove
|
|
|
|
* it at deregistration time.
|
|
|
|
* @param aButton XUL:toolbarbutton
|
|
|
|
* @param aCallback Function the click event handler for the button
|
|
|
|
*/
|
|
|
|
function bindToolEvent(aWidget, aEvent, aCallback)
|
|
|
|
{
|
|
|
|
let toolEvent = aWidget.id + "_" + aEvent;
|
|
|
|
InspectorUI.toolEvents[toolEvent] = aCallback;
|
|
|
|
aWidget.addEventListener(aEvent, aCallback, false);
|
|
|
|
}
|
2011-07-26 08:28:54 -07:00
|
|
|
|
2011-09-26 09:43:37 -07:00
|
|
|
bindToolEvent(btn, "click",
|
|
|
|
function IUI_toolButtonClick(aEvent) {
|
|
|
|
if (btn.checked) {
|
|
|
|
this.toolHide(aRegObj);
|
2011-07-26 08:28:54 -07:00
|
|
|
} else {
|
2011-09-26 09:43:37 -07:00
|
|
|
this.toolShow(aRegObj);
|
2011-07-26 08:28:54 -07:00
|
|
|
}
|
2011-09-26 09:43:37 -07:00
|
|
|
}.bind(this));
|
|
|
|
|
|
|
|
if (aRegObj.panel) {
|
|
|
|
bindToolEvent(aRegObj.panel, "popuphiding",
|
|
|
|
function IUI_toolPanelHiding() {
|
|
|
|
btn.checked = false;
|
|
|
|
});
|
|
|
|
}
|
2011-07-26 08:28:54 -07:00
|
|
|
},
|
|
|
|
|
2011-09-26 09:43:37 -07:00
|
|
|
/**
|
|
|
|
* Show the specified tool.
|
|
|
|
* @param aTool Object (see comment for IUI_registerTool)
|
|
|
|
*/
|
|
|
|
toolShow: function IUI_toolShow(aTool)
|
|
|
|
{
|
|
|
|
aTool.show.call(aTool.context, this.selection);
|
|
|
|
document.getElementById(this.getToolbarButtonId(aTool.id)).checked = true;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Hide the specified tool.
|
|
|
|
* @param aTool Object (see comment for IUI_registerTool)
|
|
|
|
*/
|
|
|
|
toolHide: function IUI_toolHide(aTool)
|
|
|
|
{
|
|
|
|
aTool.hide.call(aTool.context);
|
|
|
|
document.getElementById(this.getToolbarButtonId(aTool.id)).checked = false;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unregister the registered tool, unbinding click events for the buttons
|
|
|
|
* and showing and hiding events for the panel.
|
|
|
|
* @param aRegObj Object
|
|
|
|
* The registration object used to register the tool.
|
|
|
|
*/
|
|
|
|
unregisterTool: function IUI_unregisterTool(aRegObj)
|
|
|
|
{
|
|
|
|
let button = document.getElementById(this.getToolbarButtonId(aRegObj.id));
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unregister the click handler for the registered tool's button.
|
|
|
|
* @param aButton XUL:toolbarbutton
|
|
|
|
*/
|
|
|
|
function unbindToolEvent(aWidget, aEvent)
|
|
|
|
{
|
|
|
|
let toolEvent = aWidget.id + "_" + aEvent;
|
|
|
|
if (!InspectorUI.toolEvents[toolEvent]) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
aWidget.removeEventListener(aEvent, InspectorUI.toolEvents[toolEvent], false);
|
|
|
|
delete InspectorUI.toolEvents[toolEvent]
|
|
|
|
}
|
|
|
|
|
|
|
|
let buttonContainer = document.getElementById("inspector-tools");
|
|
|
|
unbindToolEvent(button, "click");
|
|
|
|
|
|
|
|
if (aRegObj.panel)
|
|
|
|
unbindToolEvent(aRegObj.panel, "popuphiding");
|
|
|
|
|
|
|
|
buttonContainer.removeChild(button);
|
|
|
|
|
|
|
|
if (aRegObj.unregister)
|
|
|
|
aRegObj.unregister.call(aRegObj.context);
|
|
|
|
|
|
|
|
delete this.tools[aRegObj.id];
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Save a list of open tools to the inspector store.
|
|
|
|
*
|
|
|
|
* @param aWinID The ID of the window used to save the associated tools
|
|
|
|
*/
|
2011-07-26 08:28:54 -07:00
|
|
|
saveToolState: function IUI_saveToolState(aWinID)
|
|
|
|
{
|
|
|
|
let openTools = {};
|
|
|
|
this.toolsDo(function IUI_toolsSetId(aTool) {
|
2011-09-26 09:43:37 -07:00
|
|
|
if (aTool.isOpen) {
|
2011-07-26 08:28:54 -07:00
|
|
|
openTools[aTool.id] = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
InspectorStore.setValue(aWinID, "openTools", openTools);
|
|
|
|
},
|
|
|
|
|
2011-09-26 09:43:37 -07:00
|
|
|
/**
|
|
|
|
* Restore tools previously save using saveToolState().
|
|
|
|
*
|
|
|
|
* @param aWinID The ID of the window to which the associated tools are to be
|
|
|
|
* restored.
|
|
|
|
*/
|
2011-07-26 08:28:54 -07:00
|
|
|
restoreToolState: function IUI_restoreToolState(aWinID)
|
|
|
|
{
|
|
|
|
let openTools = InspectorStore.getValue(aWinID, "openTools");
|
|
|
|
if (openTools) {
|
|
|
|
this.toolsDo(function IUI_toolsOnShow(aTool) {
|
|
|
|
if (aTool.id in openTools) {
|
2011-09-26 09:43:37 -07:00
|
|
|
this.toolShow(aTool);
|
2011-07-26 08:28:54 -07:00
|
|
|
}
|
2011-09-26 09:43:37 -07:00
|
|
|
}.bind(this));
|
2011-07-26 08:28:54 -07:00
|
|
|
}
|
|
|
|
},
|
2011-09-26 09:43:37 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* For each tool in the tools collection select the current node that is
|
|
|
|
* selected in the highlighter
|
|
|
|
*/
|
|
|
|
toolsSelect: function IUI_toolsSelect()
|
|
|
|
{
|
|
|
|
this.toolsDo(function IUI_toolsOnSelect(aTool) {
|
|
|
|
if (aTool.isOpen) {
|
|
|
|
aTool.onSelect.call(aTool.context, InspectorUI.selection);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2011-09-26 09:46:44 -07:00
|
|
|
/**
|
|
|
|
* Dim or undim each tool in the tools collection
|
|
|
|
* @param aState true = dim, false = undim
|
|
|
|
*/
|
|
|
|
toolsDim: function IUI_toolsDim(aState)
|
|
|
|
{
|
|
|
|
this.toolsDo(function IUI_toolsOnSelect(aTool) {
|
|
|
|
if (aTool.isOpen && "dim" in aTool) {
|
|
|
|
aTool.dim.call(aTool.context, aState);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2011-07-26 08:28:54 -07:00
|
|
|
/**
|
|
|
|
* Loop through all registered tools and pass each into the provided function
|
|
|
|
* @param aFunction The function to which each tool is to be passed
|
|
|
|
*/
|
|
|
|
toolsDo: function IUI_toolsDo(aFunction)
|
|
|
|
{
|
|
|
|
for each (let tool in this.tools) {
|
|
|
|
aFunction(tool);
|
|
|
|
}
|
|
|
|
},
|
2011-09-26 09:43:37 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if a tool is registered?
|
|
|
|
* @param aId The id of the tool to check
|
|
|
|
*/
|
|
|
|
toolRegistered: function IUI_toolRegistered(aId)
|
|
|
|
{
|
|
|
|
return aId in this.tools;
|
|
|
|
},
|
2011-07-26 08:28:54 -07:00
|
|
|
};
|
2010-05-13 13:38:21 -07:00
|
|
|
|
2010-08-25 12:47:36 -07:00
|
|
|
/**
|
|
|
|
* The Inspector store is used for storing data specific to each tab window.
|
|
|
|
*/
|
|
|
|
var InspectorStore = {
|
|
|
|
store: {},
|
|
|
|
length: 0,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if there is any data recorded for any tab/window.
|
|
|
|
*
|
|
|
|
* @returns boolean True if there are no stores for any window/tab, or false
|
|
|
|
* otherwise.
|
|
|
|
*/
|
|
|
|
isEmpty: function IS_isEmpty()
|
|
|
|
{
|
|
|
|
return this.length == 0 ? true : false;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a new store.
|
|
|
|
*
|
|
|
|
* @param string aID The Store ID you want created.
|
|
|
|
* @returns boolean True if the store was added successfully, or false
|
|
|
|
* otherwise.
|
|
|
|
*/
|
|
|
|
addStore: function IS_addStore(aID)
|
|
|
|
{
|
|
|
|
let result = false;
|
|
|
|
|
|
|
|
if (!(aID in this.store)) {
|
|
|
|
this.store[aID] = {};
|
|
|
|
this.length++;
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete a store by ID.
|
|
|
|
*
|
|
|
|
* @param string aID The store ID you want deleted.
|
|
|
|
* @returns boolean True if the store was removed successfully, or false
|
|
|
|
* otherwise.
|
|
|
|
*/
|
|
|
|
deleteStore: function IS_deleteStore(aID)
|
|
|
|
{
|
|
|
|
let result = false;
|
|
|
|
|
|
|
|
if (aID in this.store) {
|
|
|
|
delete this.store[aID];
|
|
|
|
this.length--;
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check store existence.
|
|
|
|
*
|
|
|
|
* @param string aID The store ID you want to check.
|
|
|
|
* @returns boolean True if the store ID is registered, or false otherwise.
|
|
|
|
*/
|
|
|
|
hasID: function IS_hasID(aID)
|
|
|
|
{
|
|
|
|
return (aID in this.store);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve a value from a store for a given key.
|
|
|
|
*
|
|
|
|
* @param string aID The store ID you want to read the value from.
|
|
|
|
* @param string aKey The key name of the value you want.
|
|
|
|
* @returns mixed the value associated to your store and key.
|
|
|
|
*/
|
|
|
|
getValue: function IS_getValue(aID, aKey)
|
|
|
|
{
|
2010-09-03 11:34:09 -07:00
|
|
|
if (!this.hasID(aID))
|
|
|
|
return null;
|
|
|
|
if (aKey in this.store[aID])
|
|
|
|
return this.store[aID][aKey];
|
|
|
|
return null;
|
2010-08-25 12:47:36 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set a value for a given key and store.
|
|
|
|
*
|
|
|
|
* @param string aID The store ID where you want to store the value into.
|
|
|
|
* @param string aKey The key name for which you want to save the value.
|
|
|
|
* @param mixed aValue The value you want stored.
|
|
|
|
* @returns boolean True if the value was stored successfully, or false
|
|
|
|
* otherwise.
|
|
|
|
*/
|
|
|
|
setValue: function IS_setValue(aID, aKey, aValue)
|
|
|
|
{
|
|
|
|
let result = false;
|
|
|
|
|
|
|
|
if (aID in this.store) {
|
|
|
|
this.store[aID][aKey] = aValue;
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete a value for a given key and store.
|
|
|
|
*
|
|
|
|
* @param string aID The store ID where you want to store the value into.
|
|
|
|
* @param string aKey The key name for which you want to save the value.
|
|
|
|
* @returns boolean True if the value was removed successfully, or false
|
|
|
|
* otherwise.
|
|
|
|
*/
|
|
|
|
deleteValue: function IS_deleteValue(aID, aKey)
|
|
|
|
{
|
|
|
|
let result = false;
|
|
|
|
|
|
|
|
if (aID in this.store && aKey in this.store[aID]) {
|
|
|
|
delete this.store[aID][aKey];
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-08-23 13:34:32 -07:00
|
|
|
/**
|
|
|
|
* The InspectorProgressListener object is an nsIWebProgressListener which
|
|
|
|
* handles onStateChange events for the inspected browser. If the user makes
|
|
|
|
* changes to the web page and he tries to navigate away, he is prompted to
|
|
|
|
* confirm page navigation, such that he's given the chance to prevent the loss
|
|
|
|
* of edits.
|
|
|
|
*/
|
|
|
|
var InspectorProgressListener = {
|
|
|
|
onStateChange:
|
|
|
|
function IPL_onStateChange(aProgress, aRequest, aFlag, aStatus)
|
|
|
|
{
|
|
|
|
// Remove myself if the Inspector is no longer open.
|
|
|
|
if (!InspectorUI.isTreePanelOpen) {
|
|
|
|
gBrowser.removeProgressListener(InspectorProgressListener);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Skip non-start states.
|
|
|
|
if (!(aFlag & Ci.nsIWebProgressListener.STATE_START)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the request is about to happen in a new window, we are not concerned
|
|
|
|
// about the request.
|
|
|
|
if (aProgress.DOMWindow != InspectorUI.win) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (InspectorUI.isDirty) {
|
|
|
|
this.showNotification(aRequest);
|
|
|
|
} else {
|
|
|
|
InspectorUI.closeInspectorUI();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Show an asynchronous notification which asks the user to confirm or cancel
|
|
|
|
* the page navigation request.
|
|
|
|
*
|
|
|
|
* @param nsIRequest aRequest
|
|
|
|
* The request initiated by the user or by the page itself.
|
|
|
|
* @returns void
|
|
|
|
*/
|
|
|
|
showNotification: function IPL_showNotification(aRequest)
|
|
|
|
{
|
|
|
|
aRequest.suspend();
|
|
|
|
|
|
|
|
let notificationBox = gBrowser.getNotificationBox(InspectorUI.browser);
|
|
|
|
let notification = notificationBox.
|
|
|
|
getNotificationWithValue("inspector-page-navigation");
|
|
|
|
|
|
|
|
if (notification) {
|
|
|
|
notificationBox.removeNotification(notification, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
let cancelRequest = function onCancelRequest() {
|
|
|
|
if (aRequest) {
|
|
|
|
aRequest.cancel(Cr.NS_BINDING_ABORTED);
|
|
|
|
aRequest.resume(); // needed to allow the connection to be cancelled.
|
|
|
|
aRequest = null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let eventCallback = function onNotificationCallback(aEvent) {
|
|
|
|
if (aEvent == "removed") {
|
|
|
|
cancelRequest();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let buttons = [
|
|
|
|
{
|
|
|
|
id: "inspector.confirmNavigationAway.buttonLeave",
|
|
|
|
label: InspectorUI.strings.
|
|
|
|
GetStringFromName("confirmNavigationAway.buttonLeave"),
|
|
|
|
accessKey: InspectorUI.strings.
|
|
|
|
GetStringFromName("confirmNavigationAway.buttonLeaveAccesskey"),
|
|
|
|
callback: function onButtonLeave() {
|
|
|
|
if (aRequest) {
|
|
|
|
aRequest.resume();
|
|
|
|
aRequest = null;
|
|
|
|
InspectorUI.closeInspectorUI();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: "inspector.confirmNavigationAway.buttonStay",
|
|
|
|
label: InspectorUI.strings.
|
|
|
|
GetStringFromName("confirmNavigationAway.buttonStay"),
|
|
|
|
accessKey: InspectorUI.strings.
|
|
|
|
GetStringFromName("confirmNavigationAway.buttonStayAccesskey"),
|
|
|
|
callback: cancelRequest
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
let message = InspectorUI.strings.
|
|
|
|
GetStringFromName("confirmNavigationAway.message");
|
|
|
|
|
|
|
|
notification = notificationBox.appendNotification(message,
|
|
|
|
"inspector-page-navigation", "chrome://browser/skin/Info.png",
|
|
|
|
notificationBox.PRIORITY_WARNING_HIGH, buttons, eventCallback);
|
|
|
|
|
|
|
|
// Make sure this not a transient notification, to avoid the automatic
|
|
|
|
// transient notification removal.
|
|
|
|
notification.persistence = -1;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2010-08-18 16:54:06 -07:00
|
|
|
/////////////////////////////////////////////////////////////////////////
|
2011-08-23 13:34:32 -07:00
|
|
|
//// Initializers
|
2010-08-18 16:54:06 -07:00
|
|
|
|
2010-05-20 10:34:04 -07:00
|
|
|
XPCOMUtils.defineLazyGetter(InspectorUI, "inspectCmd", function () {
|
|
|
|
return document.getElementById("Tools:Inspect");
|
|
|
|
});
|
2011-08-23 13:34:32 -07:00
|
|
|
|
|
|
|
XPCOMUtils.defineLazyGetter(InspectorUI, "strings", function () {
|
|
|
|
return Services.strings.
|
|
|
|
createBundle("chrome://browser/locale/inspector.properties");
|
|
|
|
});
|
|
|
|
|
2011-09-26 09:46:44 -07:00
|
|
|
XPCOMUtils.defineLazyGetter(InspectorUI, "StyleInspector", function () {
|
|
|
|
var obj = {};
|
|
|
|
Cu.import("resource:///modules/devtools/StyleInspector.jsm", obj);
|
|
|
|
return obj.StyleInspector;
|
|
|
|
});
|
|
|
|
|