Bug 703031 - [highlighter] Refactor the highlighter code. Create highlighter.jsm - Patch C; r=rcampbell

This commit is contained in:
Paul Rouget 2011-12-07 21:13:02 +01:00
Родитель aee37c71de
Коммит f8119fe33a
5 изменённых файлов: 639 добавлений и 328 удалений

Просмотреть файл

@ -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