зеркало из https://github.com/mozilla/pjs.git
Bug 703031 - [highlighter] Refactor the highlighter code. Create highlighter.jsm - Patch C; r=rcampbell
This commit is contained in:
Родитель
aee37c71de
Коммит
f8119fe33a
|
@ -361,7 +361,7 @@ TreePanel.prototype = {
|
|||
this.IUI.stopInspecting(true);
|
||||
} else {
|
||||
this.IUI.select(node, true, false);
|
||||
this.IUI.highlighter.highlightNode(node);
|
||||
this.IUI.highlighter.highlight(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,144 @@
|
|||
//// Highlighter
|
||||
/* -*- 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 Highlighter 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)
|
||||
* Mihai Șucan <mihai.sucan@gmail.com>
|
||||
* Julian Viereck <jviereck@mozilla.com>
|
||||
* Paul Rouget <paul@mozilla.com>
|
||||
* Kyle Simpson <ksimpson@mozilla.com>
|
||||
* Johan Charlez <johan.charlez@gmail.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/devtools/LayoutHelpers.jsm");
|
||||
|
||||
var EXPORTED_SYMBOLS = ["Highlighter"];
|
||||
|
||||
const INSPECTOR_INVISIBLE_ELEMENTS = {
|
||||
"head": true,
|
||||
"base": true,
|
||||
"basefont": true,
|
||||
"isindex": true,
|
||||
"link": true,
|
||||
"meta": true,
|
||||
"script": true,
|
||||
"style": true,
|
||||
"title": true,
|
||||
};
|
||||
|
||||
/**
|
||||
* 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>
|
||||
* The highlighter is built dynamically into the browser element.
|
||||
* The caller is in charge of destroying the highlighter (ie, the highlighter
|
||||
* won't be destroyed if a new tab is selected for example).
|
||||
*
|
||||
* API:
|
||||
*
|
||||
* // Constructor and destructor.
|
||||
* // @param aWindow - browser.xul window.
|
||||
* Highlighter(aWindow);
|
||||
* void destroy();
|
||||
*
|
||||
* // Highlight a node.
|
||||
* // @param aNode - node to highlight
|
||||
* // @param aScroll - scroll to ensure the node is visible
|
||||
* void highlight(aNode, aScroll);
|
||||
*
|
||||
* // Get the selected node.
|
||||
* DOMNode getNode();
|
||||
*
|
||||
* // Lock and unlock the select node.
|
||||
* void lock();
|
||||
* void unlock();
|
||||
*
|
||||
* // Show and hide the highlighter
|
||||
* void show();
|
||||
* void hide();
|
||||
* boolean isHidden();
|
||||
*
|
||||
* // Redraw the highlighter if the visible portion of the node has changed.
|
||||
* void invalidateSize(aScroll);
|
||||
*
|
||||
* // Is a node highlightable.
|
||||
* boolean isNodeHighlightable(aNode);
|
||||
*
|
||||
* // Add/Remove lsiteners
|
||||
* // @param aEvent - event name
|
||||
* // @param aListener - function callback
|
||||
* void addListener(aEvent, aListener);
|
||||
* void removeListener(aEvent, aListener);
|
||||
*
|
||||
* Events:
|
||||
*
|
||||
* "closed" - Highlighter is closing
|
||||
* "nodeselected" - A new node has been selected
|
||||
* "highlighting" - Highlighter is highlighting
|
||||
* "locked" - The selected node has been locked
|
||||
* "unlocked" - The selected ndoe has been unlocked
|
||||
*
|
||||
* Structure:
|
||||
*
|
||||
* <stack id="highlighter-container">
|
||||
* <vbox id="highlighter-veil-container">...</vbox>
|
||||
* <box id="highlighter-controls>...</vbox>
|
||||
* </stack>
|
||||
*
|
||||
* @param object aInspector
|
||||
* The InspectorUI instance.
|
||||
*/
|
||||
function Highlighter(aInspector)
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param object aWindow
|
||||
*/
|
||||
function Highlighter(aWindow)
|
||||
{
|
||||
this.IUI = aInspector;
|
||||
this.chromeWin = aWindow;
|
||||
this.tabbrowser = aWindow.gBrowser;
|
||||
this.chromeDoc = aWindow.document;
|
||||
this.browser = aWindow.gBrowser.selectedBrowser;
|
||||
this.events = {};
|
||||
|
||||
this._init();
|
||||
}
|
||||
|
||||
Highlighter.prototype = {
|
||||
_init: function Highlighter__init()
|
||||
{
|
||||
this.browser = this.IUI.browser;
|
||||
this.chromeDoc = this.IUI.chromeDoc;
|
||||
|
||||
let stack = this.browser.parentNode;
|
||||
this.win = this.browser.contentWindow;
|
||||
this._highlighting = false;
|
||||
|
@ -49,28 +164,23 @@ Highlighter.prototype = {
|
|||
|
||||
this.buildInfobar(controlsBox);
|
||||
|
||||
if (!this.IUI.store.getValue(this.winID, "inspecting")) {
|
||||
this.veilContainer.setAttribute("locked", true);
|
||||
this.nodeInfo.container.setAttribute("locked", true);
|
||||
}
|
||||
|
||||
this.browser.addEventListener("resize", this, true);
|
||||
this.browser.addEventListener("scroll", this, true);
|
||||
|
||||
this.transitionDisabler = null;
|
||||
|
||||
this.computeZoomFactor();
|
||||
this.handleResize();
|
||||
this.unlock();
|
||||
this.hide();
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the nodes.
|
||||
* Destroy the nodes. Remove listeners.
|
||||
*/
|
||||
destroy: function Highlighter_destroy()
|
||||
{
|
||||
this.IUI.win.clearTimeout(this.transitionDisabler);
|
||||
this.browser.removeEventListener("scroll", this, true);
|
||||
this.browser.removeEventListener("resize", this, true);
|
||||
this.detachKeysListeners();
|
||||
this.detachMouseListeners();
|
||||
this.detachPageListeners();
|
||||
|
||||
this.chromeWin.clearTimeout(this.transitionDisabler);
|
||||
this.boundCloseEventHandler = null;
|
||||
this._contentRect = null;
|
||||
this._highlightRect = null;
|
||||
|
@ -87,83 +197,62 @@ Highlighter.prototype = {
|
|||
this.win = null
|
||||
this.browser = null;
|
||||
this.chromeDoc = null;
|
||||
this.IUI = null;
|
||||
this.chromeWin = null;
|
||||
this.tabbrowser = null;
|
||||
|
||||
this.emitEvent("closed");
|
||||
this.removeAllListeners();
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlight this.node, unhilighting first if necessary.
|
||||
* Show the veil, and select a node.
|
||||
* If no node is specified, the previous selected node is highlighted if any.
|
||||
* If no node was selected, the root element is selected.
|
||||
*
|
||||
* @param boolean aScroll
|
||||
* Boolean determining whether to scroll or not.
|
||||
* @param aNode [optional] - The node to be selected.
|
||||
* @param aScroll [optional] boolean
|
||||
* Should we scroll to ensure that the selected node is visible.
|
||||
*/
|
||||
highlight: function Highlighter_highlight(aScroll)
|
||||
highlight: function Highlighter_highlight(aNode, aScroll)
|
||||
{
|
||||
if (this.hidden)
|
||||
this.show();
|
||||
|
||||
let oldNode = this.node;
|
||||
|
||||
if (!aNode) {
|
||||
if (!this.node)
|
||||
this.node = this.win.document.documentElement;
|
||||
} else {
|
||||
this.node = aNode;
|
||||
}
|
||||
|
||||
if (oldNode !== this.node) {
|
||||
this.updateInfobar();
|
||||
}
|
||||
|
||||
this.invalidateSize(!!aScroll);
|
||||
|
||||
if (oldNode !== this.node) {
|
||||
this.emitEvent("nodeselected");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the highlighter size and position.
|
||||
*/
|
||||
invalidateSize: function Highlighter_invalidateSize(aScroll)
|
||||
{
|
||||
let rect = null;
|
||||
|
||||
if (this.node && this.isNodeHighlightable(this.node)) {
|
||||
|
||||
if (aScroll) {
|
||||
if (aScroll &&
|
||||
this.node.scrollIntoView) { // XUL elements don't have such method
|
||||
this.node.scrollIntoView();
|
||||
}
|
||||
|
||||
let clientRect = this.node.getBoundingClientRect();
|
||||
|
||||
// Go up in the tree of frames to determine the correct rectangle.
|
||||
// clientRect is read-only, we need to be able to change properties.
|
||||
rect = {top: clientRect.top,
|
||||
left: clientRect.left,
|
||||
width: clientRect.width,
|
||||
height: clientRect.height};
|
||||
|
||||
let frameWin = this.node.ownerDocument.defaultView;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Does the selection overflow on the left of its window?
|
||||
if (rect.left < 0) {
|
||||
rect.width += rect.left;
|
||||
rect.left = 0;
|
||||
}
|
||||
|
||||
// Does the selection overflow on the top of its window?
|
||||
if (rect.top < 0) {
|
||||
rect.height += rect.top;
|
||||
rect.top = 0;
|
||||
}
|
||||
|
||||
// Selection has been clipped to fit in its own window.
|
||||
|
||||
// Are we in the top-level window?
|
||||
if (frameWin.parent === frameWin || !frameWin.frameElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 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] =
|
||||
this.IUI.getIframeContentOffset(frameWin.frameElement);
|
||||
|
||||
rect.top += frameRect.top + offsetTop;
|
||||
rect.left += frameRect.left + offsetLeft;
|
||||
|
||||
frameWin = frameWin.parent;
|
||||
}
|
||||
rect = LayoutHelpers.getDirtyRect(this.node);
|
||||
}
|
||||
|
||||
this.highlightRectangle(rect);
|
||||
|
@ -171,11 +260,78 @@ Highlighter.prototype = {
|
|||
this.moveInfobar();
|
||||
|
||||
if (this._highlighting) {
|
||||
Services.obs.notifyObservers(null,
|
||||
INSPECTOR_NOTIFICATIONS.HIGHLIGHTING, null);
|
||||
this.emitEvent("highlighting");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the selected node.
|
||||
*
|
||||
* @returns node
|
||||
*/
|
||||
getNode: function() {
|
||||
return this.node;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the highlighter if it has been hidden.
|
||||
*/
|
||||
show: function() {
|
||||
if (!this.hidden) return;
|
||||
this.veilContainer.removeAttribute("hidden");
|
||||
this.nodeInfo.container.removeAttribute("hidden");
|
||||
this.attachKeysListeners();
|
||||
this.attachPageListeners();
|
||||
this.invalidateSize();
|
||||
this.hidden = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the highlighter, the veil and the infobar.
|
||||
*/
|
||||
hide: function() {
|
||||
if (this.hidden) return;
|
||||
this.veilContainer.setAttribute("hidden", "true");
|
||||
this.nodeInfo.container.setAttribute("hidden", "true");
|
||||
this.detachKeysListeners();
|
||||
this.detachPageListeners();
|
||||
this.hidden = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Is the highlighter visible?
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
isHidden: function() {
|
||||
return this.hidden;
|
||||
},
|
||||
|
||||
/**
|
||||
* Lock a node. Stops the inspection.
|
||||
*/
|
||||
lock: function() {
|
||||
if (this.locked === true) return;
|
||||
this.veilContainer.setAttribute("locked", "true");
|
||||
this.nodeInfo.container.setAttribute("locked", "true");
|
||||
this.detachMouseListeners();
|
||||
this.locked = true;
|
||||
this.emitEvent("locked");
|
||||
},
|
||||
|
||||
/**
|
||||
* Start inspecting.
|
||||
* Unlock the current node (if any), and select any node being hovered.
|
||||
*/
|
||||
unlock: function() {
|
||||
if (this.locked === false) return;
|
||||
this.veilContainer.removeAttribute("locked");
|
||||
this.nodeInfo.container.removeAttribute("locked");
|
||||
this.attachMouseListeners();
|
||||
this.locked = false;
|
||||
this.emitEvent("unlocked");
|
||||
},
|
||||
|
||||
/**
|
||||
* Is the specified node highlightable?
|
||||
*
|
||||
|
@ -313,29 +469,6 @@ Highlighter.prototype = {
|
|||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Is the highlighter highlighting? Public method for querying the state
|
||||
* of the highlighter.
|
||||
*/
|
||||
get isHighlighting() {
|
||||
return this._highlighting;
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlight the given node.
|
||||
*
|
||||
* @param nsIDOMNode aNode
|
||||
* a DOM element to be highlighted
|
||||
* @param object aParams
|
||||
* extra parameters object
|
||||
*/
|
||||
highlightNode: function Highlighter_highlightNode(aNode, aParams)
|
||||
{
|
||||
this.node = aNode;
|
||||
this.updateInfobar();
|
||||
this.highlight(aParams && aParams.scroll);
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlight a rectangular region.
|
||||
*
|
||||
|
@ -355,14 +488,10 @@ Highlighter.prototype = {
|
|||
|
||||
if (oldRect && aRect.top == oldRect.top && aRect.left == oldRect.left &&
|
||||
aRect.width == oldRect.width && aRect.height == oldRect.height) {
|
||||
return this._highlighting; // same rectangle
|
||||
return; // same rectangle
|
||||
}
|
||||
|
||||
// adjust rect for zoom scaling
|
||||
let aRectScaled = {};
|
||||
for (let prop in aRect) {
|
||||
aRectScaled[prop] = aRect[prop] * this.zoom;
|
||||
}
|
||||
let aRectScaled = LayoutHelpers.getZoomedRect(this.win, aRect);
|
||||
|
||||
if (aRectScaled.left >= 0 && aRectScaled.top >= 0 &&
|
||||
aRectScaled.width > 0 && aRectScaled.height > 0) {
|
||||
|
@ -384,7 +513,7 @@ Highlighter.prototype = {
|
|||
this._contentRect = aRect; // save orig (non-scaled) rect
|
||||
this._highlightRect = aRectScaled; // and save the scaled rect.
|
||||
|
||||
return this._highlighting;
|
||||
return;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -396,8 +525,6 @@ Highlighter.prototype = {
|
|||
this.veilMiddleBox.style.height = 0;
|
||||
this.veilTransparentBox.style.width = 0;
|
||||
this.veilTransparentBox.style.visibility = "hidden";
|
||||
Services.obs.notifyObservers(null,
|
||||
INSPECTOR_NOTIFICATIONS.UNHIGHLIGHTING, null);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -543,7 +670,7 @@ Highlighter.prototype = {
|
|||
// Get midpoint of diagonal line.
|
||||
let midpoint = this.midPoint(a, b);
|
||||
|
||||
return this.IUI.elementFromPoint(this.win.document, midpoint.x,
|
||||
return LayoutHelpers.getElementFromPoint(this.win.document, midpoint.x,
|
||||
midpoint.y);
|
||||
},
|
||||
|
||||
|
@ -557,10 +684,50 @@ Highlighter.prototype = {
|
|||
.screenPixelsPerCSSPixel;
|
||||
},
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
//// Event Emitter Mechanism
|
||||
|
||||
addListener: function Highlighter_addListener(aEvent, aListener)
|
||||
{
|
||||
if (!(aEvent in this.events))
|
||||
this.events[aEvent] = [];
|
||||
this.events[aEvent].push(aListener);
|
||||
},
|
||||
|
||||
removeListener: function Highlighter_removeListener(aEvent, aListener)
|
||||
{
|
||||
if (!(aEvent in this.events))
|
||||
return;
|
||||
let idx = this.events[aEvent].indexOf(aListener);
|
||||
if (idx > -1)
|
||||
this.events[aEvent].splice(idx, 1);
|
||||
},
|
||||
|
||||
emitEvent: function Highlighter_emitEvent(aEvent, aArgv)
|
||||
{
|
||||
if (!(aEvent in this.events))
|
||||
return;
|
||||
|
||||
let listeners = this.events[aEvent];
|
||||
let highlighter = this;
|
||||
listeners.forEach(function(aListener) {
|
||||
try {
|
||||
aListener.apply(highlighter, aArgv);
|
||||
} catch(e) {}
|
||||
});
|
||||
},
|
||||
|
||||
removeAllListeners: function Highlighter_removeAllIsteners()
|
||||
{
|
||||
for (let event in this.events) {
|
||||
delete this.events[event];
|
||||
}
|
||||
},
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
//// Event Handling
|
||||
|
||||
attachInspectListeners: function Highlighter_attachInspectListeners()
|
||||
attachMouseListeners: function Highlighter_attachMouseListeners()
|
||||
{
|
||||
this.browser.addEventListener("mousemove", this, true);
|
||||
this.browser.addEventListener("click", this, true);
|
||||
|
@ -569,7 +736,7 @@ Highlighter.prototype = {
|
|||
this.browser.addEventListener("mouseup", this, true);
|
||||
},
|
||||
|
||||
detachInspectListeners: function Highlighter_detachInspectListeners()
|
||||
detachMouseListeners: function Highlighter_detachMouseListeners()
|
||||
{
|
||||
this.browser.removeEventListener("mousemove", this, true);
|
||||
this.browser.removeEventListener("click", this, true);
|
||||
|
@ -578,6 +745,29 @@ Highlighter.prototype = {
|
|||
this.browser.removeEventListener("mouseup", this, true);
|
||||
},
|
||||
|
||||
attachPageListeners: function Highlighter_attachPageListeners()
|
||||
{
|
||||
this.browser.addEventListener("resize", this, true);
|
||||
this.browser.addEventListener("scroll", this, true);
|
||||
},
|
||||
|
||||
detachPageListeners: function Highlighter_detachPageListeners()
|
||||
{
|
||||
this.browser.removeEventListener("resize", this, true);
|
||||
this.browser.removeEventListener("scroll", this, true);
|
||||
},
|
||||
|
||||
attachKeysListeners: function Highlighter_attachKeysListeners()
|
||||
{
|
||||
this.browser.addEventListener("keypress", this, true);
|
||||
this.highlighterContainer.addEventListener("keypress", this, true);
|
||||
},
|
||||
|
||||
detachKeysListeners: function Highlighter_detachKeysListeners()
|
||||
{
|
||||
this.browser.removeEventListener("keypress", this, true);
|
||||
this.highlighterContainer.removeEventListener("keypress", this, true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Generic event handler.
|
||||
|
@ -595,9 +785,10 @@ Highlighter.prototype = {
|
|||
this.handleMouseMove(aEvent);
|
||||
break;
|
||||
case "resize":
|
||||
case "scroll":
|
||||
this.computeZoomFactor();
|
||||
this.brieflyDisableTransitions();
|
||||
this.handleResize(aEvent);
|
||||
this.invalidateSize();
|
||||
break;
|
||||
case "dblclick":
|
||||
case "mousedown":
|
||||
|
@ -605,10 +796,78 @@ Highlighter.prototype = {
|
|||
aEvent.stopPropagation();
|
||||
aEvent.preventDefault();
|
||||
break;
|
||||
case "scroll":
|
||||
this.brieflyDisableTransitions();
|
||||
this.highlight();
|
||||
break;
|
||||
case "keypress":
|
||||
switch (aEvent.keyCode) {
|
||||
case this.chromeWin.KeyEvent.DOM_VK_RETURN:
|
||||
this.locked ? this.unlock() : this.lock();
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopPropagation();
|
||||
break;
|
||||
case this.chromeWin.KeyEvent.DOM_VK_LEFT:
|
||||
let node;
|
||||
if (this.node) {
|
||||
node = this.node.parentNode;
|
||||
} else {
|
||||
node = this.defaultSelection;
|
||||
}
|
||||
if (node && this.isNodeHighlightable(node)) {
|
||||
this.highlight(node);
|
||||
}
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopPropagation();
|
||||
break;
|
||||
case this.chromeWin.KeyEvent.DOM_VK_RIGHT:
|
||||
if (this.node) {
|
||||
// Find the first child that is highlightable.
|
||||
for (let i = 0; i < this.node.childNodes.length; i++) {
|
||||
node = this.node.childNodes[i];
|
||||
if (node && this.isNodeHighlightable(node)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node = this.defaultSelection;
|
||||
}
|
||||
if (node && this.isNodeHighlightable(node)) {
|
||||
this.highlight(node, true);
|
||||
}
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopPropagation();
|
||||
break;
|
||||
case this.chromeWin.KeyEvent.DOM_VK_UP:
|
||||
if (this.node) {
|
||||
// Find a previous sibling that is highlightable.
|
||||
node = this.node.previousSibling;
|
||||
while (node && !this.isNodeHighlightable(node)) {
|
||||
node = node.previousSibling;
|
||||
}
|
||||
} else {
|
||||
node = this.defaultSelection;
|
||||
}
|
||||
if (node && this.isNodeHighlightable(node)) {
|
||||
this.highlight(node, true);
|
||||
}
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopPropagation();
|
||||
break;
|
||||
case this.chromeWin.KeyEvent.DOM_VK_DOWN:
|
||||
if (this.node) {
|
||||
// Find a next sibling that is highlightable.
|
||||
node = this.node.nextSibling;
|
||||
while (node && !this.isNodeHighlightable(node)) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
} else {
|
||||
node = this.defaultSelection;
|
||||
}
|
||||
if (node && this.isNodeHighlightable(node)) {
|
||||
this.highlight(node, true);
|
||||
}
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopPropagation();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -619,13 +878,13 @@ Highlighter.prototype = {
|
|||
brieflyDisableTransitions: function Highlighter_brieflyDisableTransitions()
|
||||
{
|
||||
if (this.transitionDisabler) {
|
||||
this.IUI.win.clearTimeout(this.transitionDisabler);
|
||||
this.chromeWin.clearTimeout(this.transitionDisabler);
|
||||
} else {
|
||||
this.veilContainer.setAttribute("disable-transitions", "true");
|
||||
this.nodeInfo.container.setAttribute("disable-transitions", "true");
|
||||
}
|
||||
this.transitionDisabler =
|
||||
this.IUI.win.setTimeout(function() {
|
||||
this.chromeWin.setTimeout(function() {
|
||||
this.veilContainer.removeAttribute("disable-transitions");
|
||||
this.nodeInfo.container.removeAttribute("disable-transitions");
|
||||
this.transitionDisabler = null;
|
||||
|
@ -643,7 +902,7 @@ Highlighter.prototype = {
|
|||
// Stop inspection when the user clicks on a node.
|
||||
if (aEvent.button == 0) {
|
||||
let win = aEvent.target.ownerDocument.defaultView;
|
||||
this.IUI.stopInspecting();
|
||||
this.lock();
|
||||
win.focus();
|
||||
}
|
||||
aEvent.preventDefault();
|
||||
|
@ -651,27 +910,19 @@ Highlighter.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Handle mousemoves in panel when InspectorUI.inspecting is true.
|
||||
* Handle mousemoves in panel.
|
||||
*
|
||||
* @param nsiDOMEvent aEvent
|
||||
* The MouseEvent triggering the method.
|
||||
*/
|
||||
handleMouseMove: function Highlighter_handleMouseMove(aEvent)
|
||||
{
|
||||
let element = this.IUI.elementFromPoint(aEvent.target.ownerDocument,
|
||||
let element = LayoutHelpers.getElementFromPoint(aEvent.target.ownerDocument,
|
||||
aEvent.clientX, aEvent.clientY);
|
||||
if (element && element != this.node) {
|
||||
this.IUI.inspectNode(element);
|
||||
this.highlight(element);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle window resize events.
|
||||
*/
|
||||
handleResize: function Highlighter_handleResize()
|
||||
{
|
||||
this.highlight();
|
||||
},
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -52,27 +52,11 @@ Cu.import("resource://gre/modules/Services.jsm");
|
|||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/TreePanel.jsm");
|
||||
Cu.import("resource:///modules/devtools/CssRuleView.jsm");
|
||||
|
||||
const INSPECTOR_INVISIBLE_ELEMENTS = {
|
||||
"head": true,
|
||||
"base": true,
|
||||
"basefont": true,
|
||||
"isindex": true,
|
||||
"link": true,
|
||||
"meta": true,
|
||||
"script": true,
|
||||
"style": true,
|
||||
"title": true,
|
||||
};
|
||||
Cu.import("resource:///modules/highlighter.jsm");
|
||||
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
|
||||
|
||||
// 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",
|
||||
|
@ -298,8 +282,11 @@ InspectorUI.prototype = {
|
|||
|
||||
this.progressListener = new InspectorProgressListener(this);
|
||||
|
||||
this.chromeWin.addEventListener("keypress", this, false);
|
||||
|
||||
// initialize the highlighter
|
||||
this.initializeHighlighter();
|
||||
this.highlighter = new Highlighter(this.chromeWin);
|
||||
this.highlighterReady();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -335,17 +322,6 @@ InspectorUI.prototype = {
|
|||
// Extras go here.
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize highlighter.
|
||||
*/
|
||||
initializeHighlighter: function IUI_initializeHighlighter()
|
||||
{
|
||||
this.highlighter = new Highlighter(this);
|
||||
this.browser.addEventListener("keypress", this, true);
|
||||
this.highlighter.highlighterContainer.addEventListener("keypress", this, true);
|
||||
this.highlighterReady();
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the InspectorStore.
|
||||
*/
|
||||
|
@ -419,8 +395,9 @@ InspectorUI.prototype = {
|
|||
this.tabbrowser.tabContainer.removeEventListener("TabSelect", this, false);
|
||||
}
|
||||
|
||||
this.chromeWin.removeEventListener("keypress", this, false);
|
||||
|
||||
this.stopInspecting();
|
||||
this.browser.removeEventListener("keypress", this, true);
|
||||
|
||||
this.saveToolState(this.winID);
|
||||
this.toolsDo(function IUI_toolsHide(aTool) {
|
||||
|
@ -431,9 +408,6 @@ InspectorUI.prototype = {
|
|||
this.hideSidebar();
|
||||
|
||||
if (this.highlighter) {
|
||||
this.highlighter.highlighterContainer.removeEventListener("keypress",
|
||||
this,
|
||||
true);
|
||||
this.highlighter.destroy();
|
||||
this.highlighter = null;
|
||||
}
|
||||
|
@ -470,12 +444,10 @@ InspectorUI.prototype = {
|
|||
this.treePanel.closeEditor();
|
||||
|
||||
this.inspectToolbutton.checked = true;
|
||||
this.highlighter.attachInspectListeners();
|
||||
|
||||
this.inspecting = true;
|
||||
this.toolsDim(true);
|
||||
this.highlighter.veilContainer.removeAttribute("locked");
|
||||
this.highlighter.nodeInfo.container.removeAttribute("locked");
|
||||
this.highlighter.unlock();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -491,21 +463,15 @@ InspectorUI.prototype = {
|
|||
}
|
||||
|
||||
this.inspectToolbutton.checked = false;
|
||||
// Detach event listeners from content window and child windows to disable
|
||||
// highlighting. We still want to be notified if the user presses "ESCAPE"
|
||||
// to close the inspector, or "RETURN" to unlock the node, so we don't
|
||||
// remove the "keypress" event until the highlighter is removed.
|
||||
this.highlighter.detachInspectListeners();
|
||||
|
||||
this.inspecting = false;
|
||||
this.toolsDim(false);
|
||||
if (this.highlighter.node) {
|
||||
this.select(this.highlighter.node, true, true, !aPreventScroll);
|
||||
if (this.highlighter.getNode()) {
|
||||
this.select(this.highlighter.getNode(), true, true, !aPreventScroll);
|
||||
} else {
|
||||
this.select(null, true, true);
|
||||
}
|
||||
this.highlighter.veilContainer.setAttribute("locked", true);
|
||||
this.highlighter.nodeInfo.container.setAttribute("locked", true);
|
||||
this.highlighter.lock();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -530,7 +496,7 @@ InspectorUI.prototype = {
|
|||
if (forceUpdate || aNode != this.selection) {
|
||||
this.selection = aNode;
|
||||
if (!this.inspecting) {
|
||||
this.highlighter.highlightNode(this.selection);
|
||||
this.highlighter.highlight(this.selection);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -549,7 +515,7 @@ InspectorUI.prototype = {
|
|||
*/
|
||||
nodeChanged: function IUI_nodeChanged(aUpdater)
|
||||
{
|
||||
this.highlighter.highlight();
|
||||
this.highlighter.invalidateSize();
|
||||
this.toolsOnChanged(aUpdater);
|
||||
},
|
||||
|
||||
|
@ -561,6 +527,20 @@ InspectorUI.prototype = {
|
|||
// Setup the InspectorStore or restore state
|
||||
this.initializeStore();
|
||||
|
||||
let self = this;
|
||||
|
||||
this.highlighter.addListener("locked", function() {
|
||||
self.stopInspecting();
|
||||
});
|
||||
|
||||
this.highlighter.addListener("unlocked", function() {
|
||||
self.startInspecting();
|
||||
});
|
||||
|
||||
this.highlighter.addListener("nodeselected", function() {
|
||||
self.select(self.highlighter.getNode(), false, false);
|
||||
});
|
||||
|
||||
if (this.store.getValue(this.winID, "inspecting")) {
|
||||
this.startInspecting();
|
||||
}
|
||||
|
@ -570,6 +550,8 @@ InspectorUI.prototype = {
|
|||
this.win.focus();
|
||||
Services.obs.notifyObservers({wrappedJSObject: this},
|
||||
INSPECTOR_NOTIFICATIONS.OPENED, null);
|
||||
|
||||
this.highlighter.highlight();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -636,74 +618,6 @@ InspectorUI.prototype = {
|
|||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
break;
|
||||
case this.chromeWin.KeyEvent.DOM_VK_RETURN:
|
||||
this.toggleInspection();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
break;
|
||||
case this.chromeWin.KeyEvent.DOM_VK_LEFT:
|
||||
let node;
|
||||
if (this.selection) {
|
||||
node = this.selection.parentNode;
|
||||
} else {
|
||||
node = this.defaultSelection;
|
||||
}
|
||||
if (node && this.highlighter.isNodeHighlightable(node)) {
|
||||
this.inspectNode(node, true);
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
break;
|
||||
case this.chromeWin.KeyEvent.DOM_VK_RIGHT:
|
||||
if (this.selection) {
|
||||
// Find the first child that is highlightable.
|
||||
for (let i = 0; i < this.selection.childNodes.length; i++) {
|
||||
node = this.selection.childNodes[i];
|
||||
if (node && this.highlighter.isNodeHighlightable(node)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
node = this.defaultSelection;
|
||||
}
|
||||
if (node && this.highlighter.isNodeHighlightable(node)) {
|
||||
this.inspectNode(node, true);
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
break;
|
||||
case this.chromeWin.KeyEvent.DOM_VK_UP:
|
||||
if (this.selection) {
|
||||
// Find a previous sibling that is highlightable.
|
||||
node = this.selection.previousSibling;
|
||||
while (node && !this.highlighter.isNodeHighlightable(node)) {
|
||||
node = node.previousSibling;
|
||||
}
|
||||
} else {
|
||||
node = this.defaultSelection;
|
||||
}
|
||||
if (node && this.highlighter.isNodeHighlightable(node)) {
|
||||
this.inspectNode(node, true);
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
break;
|
||||
case this.chromeWin.KeyEvent.DOM_VK_DOWN:
|
||||
if (this.selection) {
|
||||
// Find a next sibling that is highlightable.
|
||||
node = this.selection.nextSibling;
|
||||
while (node && !this.highlighter.isNodeHighlightable(node)) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
} else {
|
||||
node = this.defaultSelection;
|
||||
}
|
||||
if (node && this.highlighter.isNodeHighlightable(node)) {
|
||||
this.inspectNode(node, true);
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -826,77 +740,12 @@ InspectorUI.prototype = {
|
|||
inspectNode: function IUI_inspectNode(aNode, aScroll)
|
||||
{
|
||||
this.select(aNode, true, true);
|
||||
this.highlighter.highlightNode(aNode, { scroll: aScroll });
|
||||
},
|
||||
|
||||
/**
|
||||
* Find an element from the given coordinates. This method descends through
|
||||
* 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) {
|
||||
if (node instanceof Ci.nsIDOMHTMLIFrameElement) {
|
||||
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 Ci.nsIDOMHTMLIFrameElement ||
|
||||
node instanceof Ci.nsIDOMHTMLFrameElement) {
|
||||
let subnode = this.elementFromPoint(node.contentDocument, aX, aY);
|
||||
if (subnode) {
|
||||
node = subnode;
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
this.highlighter.highlight(aNode, aScroll);
|
||||
},
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
//// Utility functions
|
||||
|
||||
/**
|
||||
* 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];
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the unique ID of a window object.
|
||||
*
|
||||
|
@ -1252,6 +1101,12 @@ InspectorUI.prototype = {
|
|||
this.getToolbarButtonId(tool.id)).removeAttribute("checked");
|
||||
}.bind(this));
|
||||
}
|
||||
if (this.store.getValue(this.winID, "inspecting")) {
|
||||
this.highlighter.unlock();
|
||||
} else {
|
||||
this.highlighter.lock();
|
||||
}
|
||||
|
||||
Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.STATE_RESTORED, null);
|
||||
},
|
||||
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
/* -*- 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 LayoutHelpers 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)
|
||||
* Mihai Șucan <mihai.sucan@gmail.com>
|
||||
* Julian Viereck <jviereck@mozilla.com>
|
||||
* Paul Rouget <paul@mozilla.com>
|
||||
* Kyle Simpson <ksimpson@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;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
|
||||
var EXPORTED_SYMBOLS = ["LayoutHelpers"];
|
||||
|
||||
LayoutHelpers = {
|
||||
|
||||
/**
|
||||
* Compute the position and the dimensions for the visible portion
|
||||
* of a node, relativalely to the root window.
|
||||
*
|
||||
* @param nsIDOMNode aNode
|
||||
* a DOM element to be highlighted
|
||||
*/
|
||||
getDirtyRect: function LH_getDirectyRect(aNode) {
|
||||
let frameWin = aNode.ownerDocument.defaultView;
|
||||
let clientRect = aNode.getBoundingClientRect();
|
||||
|
||||
// Go up in the tree of frames to determine the correct rectangle.
|
||||
// clientRect is read-only, we need to be able to change properties.
|
||||
rect = {top: clientRect.top,
|
||||
left: clientRect.left,
|
||||
width: clientRect.width,
|
||||
height: clientRect.height};
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Does the selection overflow on the left of its window?
|
||||
if (rect.left < 0) {
|
||||
rect.width += rect.left;
|
||||
rect.left = 0;
|
||||
}
|
||||
|
||||
// Does the selection overflow on the top of its window?
|
||||
if (rect.top < 0) {
|
||||
rect.height += rect.top;
|
||||
rect.top = 0;
|
||||
}
|
||||
|
||||
// Selection has been clipped to fit in its own window.
|
||||
|
||||
// Are we in the top-level window?
|
||||
if (frameWin.parent === frameWin || !frameWin.frameElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 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] =
|
||||
this.getIframeContentOffset(frameWin.frameElement);
|
||||
|
||||
rect.top += frameRect.top + offsetTop;
|
||||
rect.left += frameRect.left + offsetLeft;
|
||||
|
||||
frameWin = frameWin.parent;
|
||||
}
|
||||
|
||||
return rect;
|
||||
},
|
||||
|
||||
/**
|
||||
* 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 LH_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];
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply the page zoom factor.
|
||||
*/
|
||||
getZoomedRect: function LH_getZoomedRect(aWin, aRect) {
|
||||
// get page zoom factor, if any
|
||||
let zoom =
|
||||
aWin.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIDOMWindowUtils)
|
||||
.screenPixelsPerCSSPixel;
|
||||
|
||||
// adjust rect for zoom scaling
|
||||
let aRectScaled = {};
|
||||
for (let prop in aRect) {
|
||||
aRectScaled[prop] = aRect[prop] * zoom;
|
||||
}
|
||||
|
||||
return aRectScaled;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Find an element from the given coordinates. This method descends through
|
||||
* 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.
|
||||
*/
|
||||
getElementFromPoint: function LH_elementFromPoint(aDocument, aX, aY)
|
||||
{
|
||||
let node = aDocument.elementFromPoint(aX, aY);
|
||||
if (node && node.contentDocument) {
|
||||
if (node instanceof Ci.nsIDOMHTMLIFrameElement) {
|
||||
let rect = node.getBoundingClientRect();
|
||||
|
||||
// Gap between the iframe and its content window.
|
||||
let [offsetTop, offsetLeft] = LayoutHelpers.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 Ci.nsIDOMHTMLIFrameElement ||
|
||||
node instanceof Ci.nsIDOMHTMLFrameElement) {
|
||||
let subnode = this.getElementFromPoint(node.contentDocument, aX, aY);
|
||||
if (subnode) {
|
||||
node = subnode;
|
||||
}
|
||||
}
|
||||
}
|
||||
return node;
|
||||
},
|
||||
};
|
|
@ -54,3 +54,4 @@ include $(topsrcdir)/config/rules.mk
|
|||
libs::
|
||||
$(NSINSTALL) $(srcdir)/Templater.jsm $(FINAL_TARGET)/modules/devtools
|
||||
$(NSINSTALL) $(srcdir)/Promise.jsm $(FINAL_TARGET)/modules/devtools
|
||||
$(NSINSTALL) $(srcdir)/LayoutHelpers.jsm $(FINAL_TARGET)/modules/devtools
|
||||
|
|
Загрузка…
Ссылка в новой задаче