author | Thaddee Tyl <thaddee.tyl@gmail.com> |
Tue, 12 Jun 2012 13:43:00 +0300 | |
changeset 101799 | 1dd1770cc77eaeed188db07da8a8254475767296 |
parent 101798 | 960b6d4ea73b52450320ae7c00702f3c233f523d |
child 101800 | 95852ec078fb49ba547bed20abb4b886efb1f5d8 |
push id | 1316 |
push user | akeybl@mozilla.com |
push date | Mon, 27 Aug 2012 22:37:00 +0000 |
treeherder | mozilla-beta@db4b09302ee2 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | rcampbell |
bugs | 724585 |
milestone | 16.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/devtools/highlighter/InsideOutBox.jsm +++ b/browser/devtools/highlighter/InsideOutBox.jsm @@ -123,16 +123,19 @@ InsideOutBoxView = { * The view requiring the InsideOutBox. * @param aBox * The box object containing the InsideOutBox. Required to add/remove * children during box manipulation (toggling opened or closed). */ var EXPORTED_SYMBOLS = ["InsideOutBox"]; +const Cu = Components.utils; +Cu.import("resource:///modules/devtools/LayoutHelpers.jsm"); + function InsideOutBox(aView, aBox) { this.view = aView; this.box = aBox; this.rootObject = null; this.rootObjectBox = null; @@ -207,17 +210,19 @@ InsideOutBox.prototype = let objectBox = this.createObjectBox(aObject); if (!objectBox) { return null; } this.selectObjectBox(objectBox, forceOpen); if (makeBoxVisible) { this.openObjectBox(objectBox); if (scrollIntoView) { - objectBox.scrollIntoView(true); + // We want to center the label of the element, not the whole tag + // (which includes all of its children, and is vertically huge). + LayoutHelpers.scrollIntoViewIfNeeded(objectBox.firstElementChild); } } return objectBox; }, /** * Expands/contracts the given object, depending on its state. * @param aObject
--- a/browser/devtools/shared/LayoutHelpers.jsm +++ b/browser/devtools/shared/LayoutHelpers.jsm @@ -181,18 +181,17 @@ LayoutHelpers = { * 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) - { + 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); @@ -209,9 +208,87 @@ LayoutHelpers = { let subnode = this.getElementFromPoint(node.contentDocument, aX, aY); if (subnode) { node = subnode; } } } return node; }, + + /** + * Scroll the document so that the element "elem" appears in the viewport. + * + * @param Element elem the element that needs to appear in the viewport. + * @param bool centered true if you want it centered, false if you want it to + * appear on the top of the viewport. It is true by default, and that is + * usually what you want. + */ + scrollIntoViewIfNeeded: + function LH_scrollIntoViewIfNeeded(elem, centered) { + // We want to default to centering the element in the page, + // so as to keep the context of the element. + centered = centered === undefined? true: !!centered; + + let win = elem.ownerDocument.defaultView; + let clientRect = elem.getBoundingClientRect(); + + // The following are always from the {top, bottom, left, right} + // of the viewport, to the {top, …} of the box. + // Think of them as geometrical vectors, it helps. + // The origin is at the top left. + + let topToBottom = clientRect.bottom; + let bottomToTop = clientRect.top - win.innerHeight; + let leftToRight = clientRect.right; + let rightToLeft = clientRect.left - win.innerWidth; + let xAllowed = true; // We allow one translation on the x axis, + let yAllowed = true; // and one on the y axis. + + // Whatever `centered` is, the behavior is the same if the box is + // (even partially) visible. + + if ((topToBottom > 0 || !centered) && topToBottom <= elem.offsetHeight) { + win.scrollBy(0, topToBottom - elem.offsetHeight); + yAllowed = false; + } else + if ((bottomToTop < 0 || !centered) && bottomToTop >= -elem.offsetHeight) { + win.scrollBy(0, bottomToTop + elem.offsetHeight); + yAllowed = false; + } + + if ((leftToRight > 0 || !centered) && leftToRight <= elem.offsetWidth) { + if (xAllowed) { + win.scrollBy(leftToRight - elem.offsetWidth, 0); + xAllowed = false; + } + } else + if ((rightToLeft < 0 || !centered) && rightToLeft >= -elem.offsetWidth) { + if (xAllowed) { + win.scrollBy(rightToLeft + elem.offsetWidth, 0); + xAllowed = false; + } + } + + // If we want it centered, and the box is completely hidden, + // then we center it explicitly. + + if (centered) { + + if (yAllowed && (topToBottom <= 0 || bottomToTop >= 0)) { + win.scroll(win.scrollX, + win.scrollY + clientRect.top + - (win.innerHeight - elem.offsetHeight) / 2); + } + + if (xAllowed && (leftToRight <= 0 || rightToLeft <= 0)) { + win.scroll(win.scrollX + clientRect.left + - (win.innerWidth - elem.offsetWidth) / 2, + win.scrollY); + } + } + + if (win.parent !== win) { + // We are inside an iframe. + LH_scrollIntoViewIfNeeded(win.frameElement, centered); + } + }, };
--- a/browser/devtools/shared/test/Makefile.in +++ b/browser/devtools/shared/test/Makefile.in @@ -15,22 +15,25 @@ include $(topsrcdir)/config/rules.mk _BROWSER_TEST_FILES = \ browser_browser_basic.js \ browser_promise_basic.js \ browser_require_basic.js \ browser_templater_basic.js \ browser_toolbar_basic.js \ browser_toolbar_tooltip.js \ browser_toolbar_webconsole_errors_count.js \ + browser_layoutHelpers.js \ head.js \ $(NULL) _BROWSER_TEST_PAGES = \ browser_templater_basic.html \ browser_toolbar_basic.html \ browser_toolbar_webconsole_errors_count.html \ + browser_layoutHelpers.html \ + browser_layoutHelpers_iframe.html \ $(NULL) libs:: $(_BROWSER_TEST_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir) libs:: $(_BROWSER_TEST_PAGES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644 --- /dev/null +++ b/browser/devtools/shared/test/browser_layoutHelpers.html @@ -0,0 +1,25 @@ +<!doctype html> +<meta charset=utf-8> +<title> Layout Helpers </title> + +<style> + html { + height: 300%; + width: 300%; + } + div#some { + position: absolute; + background: black; + width: 2px; + height: 2px; + } + iframe { + position: absolute; + width: 40px; + height: 40px; + border: 0; + } +</style> + +<div id=some></div> +<iframe id=frame src='./browser_layoutHelpers_iframe.html'></iframe>
new file mode 100644 --- /dev/null +++ b/browser/devtools/shared/test/browser_layoutHelpers.js @@ -0,0 +1,99 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that scrollIntoViewIfNeeded works properly. + +let imported = {}; +Components.utils.import("resource:///modules/devtools/LayoutHelpers.jsm", + imported); +registerCleanupFunction(function () { + imported = {}; +}); + +let LayoutHelpers = imported.LayoutHelpers; + +const TEST_URI = "http://example.com/browser/browser/devtools/shared/test/browser_layoutHelpers.html"; + +function test() { + addTab(TEST_URI, function(browser, tab) { + info("Starting browser_layoutHelpers.js"); + let doc = browser.contentDocument; + runTest(doc.defaultView, doc.getElementById('some')); + gBrowser.removeCurrentTab(); + finish(); + }); +} + +function runTest(win, some) { + some.style.top = win.innerHeight + 'px'; + some.style.left = win.innerWidth + 'px'; + // The tests start with a black 2x2 pixels square below bottom right. + // Do not resize the window during the tests. + + win.scroll(win.innerWidth / 2, win.innerHeight + 2); // Above the viewport. + LayoutHelpers.scrollIntoViewIfNeeded(some); + is(win.scrollY, Math.floor(win.innerHeight / 2) + 1, + 'Element completely hidden above should appear centered.'); + + win.scroll(win.innerWidth / 2, win.innerHeight + 1); // On the top edge. + LayoutHelpers.scrollIntoViewIfNeeded(some); + is(win.scrollY, win.innerHeight, + 'Element partially visible above should appear above.'); + + win.scroll(win.innerWidth / 2, 0); // Just below the viewport. + LayoutHelpers.scrollIntoViewIfNeeded(some); + is(win.scrollY, Math.floor(win.innerHeight / 2) + 1, + 'Element completely hidden below should appear centered.'); + + win.scroll(win.innerWidth / 2, 1); // On the bottom edge. + LayoutHelpers.scrollIntoViewIfNeeded(some); + is(win.scrollY, 2, + 'Element partially visible below should appear below.'); + + + win.scroll(win.innerWidth / 2, win.innerHeight + 2); // Above the viewport. + LayoutHelpers.scrollIntoViewIfNeeded(some, false); + is(win.scrollY, win.innerHeight, + 'Element completely hidden above should appear above ' + + 'if parameter is false.'); + + win.scroll(win.innerWidth / 2, win.innerHeight + 1); // On the top edge. + LayoutHelpers.scrollIntoViewIfNeeded(some, false); + is(win.scrollY, win.innerHeight, + 'Element partially visible above should appear above ' + + 'if parameter is false.'); + + win.scroll(win.innerWidth / 2, 0); // Below the viewport. + LayoutHelpers.scrollIntoViewIfNeeded(some, false); + is(win.scrollY, 2, + 'Element completely hidden below should appear below ' + + 'if parameter is false.'); + + win.scroll(win.innerWidth / 2, 1); // On the bottom edge. + LayoutHelpers.scrollIntoViewIfNeeded(some, false); + is(win.scrollY, 2, + 'Element partially visible below should appear below ' + + 'if parameter is false.'); + + // The case of iframes. + win.scroll(0, 0); + + let frame = win.document.getElementById('frame'); + let fwin = frame.contentWindow; + + frame.style.top = win.innerHeight + 'px'; + frame.style.left = win.innerWidth + 'px'; + + fwin.addEventListener('load', function frameLoad() { + let some = fwin.document.getElementById('some'); + LayoutHelpers.scrollIntoViewIfNeeded(some); + is(win.scrollX, Math.floor(win.innerWidth / 2) + 20, + 'Scrolling from an iframe should center the iframe vertically.'); + is(win.scrollY, Math.floor(win.innerHeight / 2) + 20, + 'Scrolling from an iframe should center the iframe horizontally.'); + is(fwin.scrollX, Math.floor(fwin.innerWidth / 2) + 1, + 'Scrolling from an iframe should center the element vertically.'); + is(fwin.scrollY, Math.floor(fwin.innerHeight / 2) + 1, + 'Scrolling from an iframe should center the element horizontally.'); + }, false); +}
new file mode 100644 --- /dev/null +++ b/browser/devtools/shared/test/browser_layoutHelpers_iframe.html @@ -0,0 +1,19 @@ +<!doctype html> +<meta charset=utf-8> +<title> Layout Helpers </title> + +<style> + html { + height: 300%; + width: 300%; + } + div#some { + position: absolute; + background: black; + width: 2px; + height: 2px; + } +</style> + +<div id=some></div> +