☠☠ backed out by 52b2bf6fc12e ☠ ☠ | |
author | Mark Hammond <mhammond@skippinet.com.au> |
Fri, 21 Dec 2012 12:26:32 +1100 | |
changeset 116741 | c928f50fe4fcf2922c613256e0afa2e04ad7315c |
parent 116740 | f9ea385ca51f084cab27534157ef61a9dbb21ae6 |
child 116742 | cca7f05e2c053daeedd7a95e843fe29f740db8e4 |
push id | 24072 |
push user | Ms2ger@gmail.com |
push date | Sat, 22 Dec 2012 13:18:22 +0000 |
treeherder | mozilla-central@ea373e534245 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | enndeakin |
bugs | 798226 |
milestone | 20.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/layout/xul/base/src/nsMenuPopupFrame.cpp +++ b/layout/xul/base/src/nsMenuPopupFrame.cpp @@ -550,16 +550,18 @@ nsMenuPopupFrame::InitializePopup(nsICon mPopupState = ePopupShowing; mAnchorContent = aAnchorContent; mTriggerContent = aTriggerContent; mXPos = aXPos; mYPos = aYPos; mAdjustOffsetForContextMenu = false; mPosition = POPUPPOSITION_UNKNOWN; + mVFlip = false; + mHFlip = false; // if aAttributesOverride is true, then the popupanchor, popupalign and // position attributes on the <popup> override those values passed in. // If false, those attributes are only used if the values passed in are empty if (aAnchorContent) { nsAutoString anchor, align, position, flip; mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupanchor, anchor); mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::popupalign, align); @@ -1178,22 +1180,29 @@ nsMenuPopupFrame::SetPopupPosition(nsIFr } else { // with no anchor, the popup is positioned relative to the root frame anchorRect = rootScreenRect; screenPoint = anchorRect.TopLeft() + nsPoint(margin.left, margin.top); } // mXPos and mYPos specify an additonal offset passed to OpenPopup that - // should be added to the position - if (IsDirectionRTL()) - screenPoint.x -= presContext->CSSPixelsToAppUnits(mXPos); - else - screenPoint.x += presContext->CSSPixelsToAppUnits(mXPos); - screenPoint.y += presContext->CSSPixelsToAppUnits(mYPos); + // should be added to the position. We also add the offset to the anchor + // pos so a later flip/resize takes the offset into account. + nscoord anchorXOffset = presContext->CSSPixelsToAppUnits(mXPos); + if (IsDirectionRTL()) { + screenPoint.x -= anchorXOffset; + anchorRect.x -= anchorXOffset; + } else { + screenPoint.x += anchorXOffset; + anchorRect.x += anchorXOffset; + } + nscoord anchorYOffset = presContext->CSSPixelsToAppUnits(mYPos); + screenPoint.y += anchorYOffset; + anchorRect.y += anchorYOffset; // If this is a noautohide popup, set the screen coordinates of the popup. // This way, the popup stays at the location where it was opened even when // the window is moved. Popups at the parent level follow the parent // window as it is moved and remained anchored, so we want to maintain the // anchoring instead. if (IsNoAutoHide() && PopupLevel(true) != ePopupLevelParent) { // Account for the margin that will end up being added to the screen coordinate
--- a/toolkit/content/tests/widgets/Makefile.in +++ b/toolkit/content/tests/widgets/Makefile.in @@ -16,16 +16,17 @@ include $(DEPTH)/config/autoconf.mk tree_shared.js \ $(NULL) MOCHITEST_FILES = \ test_contextmenu_nested.xul \ test_tree_column_reorder.xul \ tree_shared.js \ test_mousecapture_area.html \ + test_popupanchor.xul \ popup_shared.js \ test_videocontrols.html \ test_videocontrols_video_direction.html \ test_videocontrols_audio_direction.html \ test_audiocontrols_dimensions.html \ videocontrols_direction-1-ref.html \ videocontrols_direction-1a.html \ videocontrols_direction-1b.html \
new file mode 100644 --- /dev/null +++ b/toolkit/content/tests/widgets/test_popupanchor.xul @@ -0,0 +1,271 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="/tests/SimpleTest/test.css" type="text/css"?> + +<window title="Popup Anchor Tests" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <title>Popup Popup Tests</title> + + <panel id="testPanel" + type="arrow" + noautohide="true"> + </panel> + + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + +<script> +<![CDATA[ +var anchor, panel, arrow; + +function isArrowPositionedOn(side, offset) { + var arrowRect = arrow.getBoundingClientRect(); + var arrowMidX = (arrowRect.left + arrowRect.right) / 2; + var arrowMidY = (arrowRect.top + arrowRect.bottom) / 2; + var panelRect = panel.getBoundingClientRect(); + var panelMidX = (panelRect.left + panelRect.right) / 2; + var panelMidY = (panelRect.top + panelRect.bottom) / 2; + // First check the "flip" of the panel is correct. If we are expecting the + // arrow to be pointing to the left side of the anchor, the arrow must + // also be on the left side of the panel (and vice-versa) + // XXX - on OSX, the arrow seems to always be exactly in the center, hence + // the 'equals' sign in the "<=" and ">=" comparisons. NFI why though... + switch (side) { + case "left": + ok(arrowMidX <= panelMidX, "arrow should be on the left of the panel"); + break; + case "right": + ok(arrowMidX >= panelMidX, "arrow should be on the right of the panel"); + break; + case "top": + ok(arrowMidY <= panelMidY, "arrow should be on the top of the panel"); + break; + case "bottom": + ok(arrowMidY >= panelMidY, "arrow should be on the bottom of the panel"); + break; + default: + ok(false, "invalid position " + where); + break; + } + function is_close(got, exp, msg) { + // on some platforms we see differences of a fraction of a pixel - so + // allow any difference of < 1 pixels as being OK. + ok(Math.abs(got - exp) < 1, msg + ": " + got + " should be equal to " + exp); + } + // Now check the arrow really is pointing where we expect. The middle of + // the arrow should be pointing exactly to the left (or right) side of the + // anchor rect, +- any offsets. + offset = offset || 0; // no param means no offset expected. + var anchorRect = anchor.getBoundingClientRect(); + var anchorPos = anchorRect[side]; + switch (side) { + case "left": + case "right": + is_close(arrowMidX - anchorPos, offset, "arrow should be " + offset + "px from " + side + " side of anchor"); + is_close(panelRect.top, anchorRect.bottom, "top of panel should be at bottom of anchor"); + break; + case "top": + case "bottom": + is_close(arrowMidY - anchorPos, offset, "arrow should be " + offset + "px from " + side + " side of anchor"); + is_close(panelRect.right, anchorRect.left, "right of panel should be left of anchor"); + break; + default: + ok(false, "unknown side " + side); + break; + } +} + +function openPopup(position, callback) { + panel.addEventListener("popupshown", function popupshown() { + panel.removeEventListener("popupshown", popupshown); + callback(); + }, false); + panel.openPopup(anchor, position); +} + +var tests = { + // A panel with the anchor after_end - the anchor should not move on resize + simpleResizeHorizontal: function(next, bHorizontal) { + openPopup("after_end", function() { + isArrowPositionedOn("right"); + var origPanelRect = panel.getBoundingClientRect(); + panel.sizeTo(100, 100); + isArrowPositionedOn("right"); // should not have flipped, so still "right" + panel.sizeTo(origPanelRect.width, origPanelRect.height); + isArrowPositionedOn("right"); // should not have flipped, so still "right" + next(); + }); + }, + + simpleResizeVertical: function(next) { + openPopup("start_after", function() { + isArrowPositionedOn("bottom"); + var origPanelRect = panel.getBoundingClientRect(); + panel.sizeTo(100, 100); + isArrowPositionedOn("bottom"); // should not have flipped + panel.sizeTo(origPanelRect.width, origPanelRect.height); + isArrowPositionedOn("bottom"); // should not have flipped + next(); + }); + }, + + flippingResizeHorizontal: function(next, bHorizontal) { + openPopup("after_end", function() { + isArrowPositionedOn("right"); + panel.sizeTo(anchor.getBoundingClientRect().left + 50, 50); + isArrowPositionedOn("left"); // check it flipped and has zero offset. + next(); + }); + }, + + flippingResizeVertical: function(next, bHorizontal) { + openPopup("start_after", function() { + isArrowPositionedOn("bottom"); + panel.sizeTo(50, anchor.getBoundingClientRect().top + 50); + isArrowPositionedOn("top"); // check it flipped and has zero offset. + next(); + }); + }, + + simpleMoveToAnchorHorizontal: function(next) { + openPopup("after_end", function() { + isArrowPositionedOn("right"); + panel.moveToAnchor(anchor, "after_end", 20, 0); + // the anchor and the panel should have moved 20px right without flipping. + isArrowPositionedOn("right", 20); + panel.moveToAnchor(anchor, "after_end", -20, 0); + // the anchor and the panel should have moved 20px left without flipping. + isArrowPositionedOn("right", -20); + next(); + }); + }, + + simpleMoveToAnchorVertical: function(next) { + openPopup("start_after", function() { + isArrowPositionedOn("bottom"); + panel.moveToAnchor(anchor, "start_after", 0, 20); + // the anchor and the panel should have moved 20px down without flipping. + isArrowPositionedOn("bottom", 20); + panel.moveToAnchor(anchor, "start_after", 0, -20); + // the anchor and the panel should have moved 20px up without flipping. + isArrowPositionedOn("bottom", -20); + next(); + }); + }, + + // Do a moveToAnchor that causes the panel to flip horizontally + flippingMoveToAnchorHorizontal: function(next) { + var anchorRight = anchor.getBoundingClientRect().right; + // Size the panel such that it only just fits from the left-hand side of + // the window to the right of the anchor - thus, it will fit when + // anchored to the right-hand side of the anchor. + panel.sizeTo(anchorRight - 10, 100); + openPopup("after_end", function() { + isArrowPositionedOn("right"); + // Ask for it to be anchored 1/2 way between the left edge of the window + // and the anchor right - it can't fit with the panel on the left/arrow + // on the right, so it must flip (arrow on the left, panel on the right) + var offset = Math.floor(-anchorRight / 2); + panel.moveToAnchor(anchor, "after_end", offset, 0); + isArrowPositionedOn("left", offset); // should have flipped and have the offset. + // resize back to original and move to a zero offset - it should flip back. + panel.sizeTo(anchorRight - 10, 100); + panel.moveToAnchor(anchor, "after_end", 0, 0); + isArrowPositionedOn("right"); // should have flipped back and no offset + next(); + }); + }, + + // Do a moveToAnchor that causes the panel to flip vertically + flippingMoveToAnchorVertical: function(next) { + var anchorBottom = anchor.getBoundingClientRect().bottom; + // See comments above in flippingMoveToAnchorHorizontal, but read + // "top/bottom" instead of "left/right" + panel.sizeTo(100, anchorBottom - 10); + openPopup("start_after", function() { + isArrowPositionedOn("bottom"); + var offset = Math.floor(-anchorBottom / 2); + panel.moveToAnchor(anchor, "start_after", 0, offset); + isArrowPositionedOn("top", offset); + panel.sizeTo(100, anchorBottom - 10); + panel.moveToAnchor(anchor, "start_after", 0, 0); + isArrowPositionedOn("bottom"); + next(); + }); + }, + + veryWidePanel: function(next) { + openPopup("after_end", function() { + var origArrowRect = arrow.getBoundingClientRect(); + // Now move it such that the arrow can't be at either end of the panel but + // instead somewhere in the middle as that is the only way things fit. + // XXX - these tests might not be quite correct even when bug 812943 + // is fixed. + panel.sizeTo(window.innerWidth - 10, 60); + todo_is(panel.getBoundingClientRect().width, window.innerWidth - 10, "Bug 812943 - width is what we requested.") + // the arrow should not have moved. + var curArrowRect = arrow.getBoundingClientRect(); + todo_is(curArrowRect.left, origArrowRect.left, "Bug 812943 - arrow should not have moved"); + is(curArrowRect.top, origArrowRect.top, "arrow should not have moved up or down"); + next(); + }); + } +} + +function runTest() { + panel.sizeTo(50, 50); + + var testIter = Iterator(tests); + + function runNextTest() { + var name, func; + try { + [name, func] = testIter.next(); + } catch (err if err instanceof StopIteration) { + // out of tests + SimpleTest.finish(); + return; + } + SimpleTest.info("sub-test " + name + " starting"); + try { + func.call(tests, function() { + setTimeout(function() { + panel.hidePopup(); + panel.sizeTo(50, 50); + runNextTest(); + }, 0); + }); + } catch (ex) { + SimpleTest.ok(false, "sub-test " + name + " failed: " + ex.toString() +"\n"+ex.stack); + runNextTest(); + } + } + runNextTest(); +} + +SimpleTest.waitForExplicitFinish(); + +addEventListener("load", function() { + anchor = document.getElementById("anchor"); + panel = document.getElementById("testPanel"); + arrow = document.getAnonymousElementByAttribute(panel, "anonid", "arrow"); + // Cancel the arrow panel slide-in transition (bug 767133) so the size and + // position are "stable" enough to test without jumping through hoops... + arrow.style.transition = "none"; + runTest(); +}); + +]]> +</script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<!-- Our tests assume at least 100px around the anchor on all sides, else the + panel may flip when we don't expect it to +--> +<div style="margin: 100px 100px 100px 100px;"> + <p id="display">The anchor --> <span id="anchor">v</span> <--</p> +</div> +</body> + +</window>
--- a/toolkit/content/widgets/popup.xml +++ b/toolkit/content/widgets/popup.xml @@ -335,20 +335,55 @@ <xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1"> <children/> <xul:box class="panel-inner-arrowcontentfooter" xbl:inherits="footertype" hidden="true"/> </xul:box> </xul:box> </content> <implementation> <field name="_fadeTimer">null</field> - </implementation> - <handlers> - <handler event="popupshowing" phase="target"> - <![CDATA[ + <method name="sizeTo"> + <parameter name="aWidth"/> + <parameter name="aHeight"/> + <body> + <![CDATA[ + this.popupBoxObject.sizeTo(aWidth, aHeight); + if (this.state == "open") + this.adjustArrowPosition(); + ]]> + </body> + </method> + <method name="moveTo"> + <parameter name="aLeft"/> + <parameter name="aTop"/> + <body> + <![CDATA[ + this.popupBoxObject.moveTo(aLeft, aTop); + if (this.state == "open") + this.adjustArrowPosition(); + ]]> + </body> + </method> + <method name="moveToAnchor"> + <parameter name="aAnchorElement"/> + <parameter name="aPosition"/> + <parameter name="aX"/> + <parameter name="aY"/> + <parameter name="aAttributesOverride"/> + <body> + <![CDATA[ + this.popupBoxObject.moveToAnchor(aAnchorElement, aPosition, aX, aY, aAttributesOverride); + if (this.state == "open") + this.adjustArrowPosition(); + ]]> + </body> + </method> + <method name="adjustArrowPosition"> + <body> + <![CDATA[ var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow"); var anchor = this.anchorNode; if (!anchor) { arrow.hidden = true; return; } @@ -384,17 +419,24 @@ } else { container.dir = ""; this.setAttribute("side", "top"); } } arrow.hidden = false; - + ]]> + </body> + </method> + </implementation> + <handlers> + <handler event="popupshowing" phase="target"> + <![CDATA[ + this.adjustArrowPosition(); // set fading var fade = this.getAttribute("fade"); var fadeDelay = (fade == "fast") ? 1 : fade == "slow" ? 4000 : 0; if (fadeDelay) { this._fadeTimer = setTimeout(function (self) { self.style.opacity = 0.2; }, fadeDelay, this); } @@ -503,9 +545,8 @@ <content> <xul:hbox class="popup-internal-box" flex="1" orient="vertical" style="overflow: auto;"> <children/> </xul:hbox> </content> </binding> </bindings> -