gecko/toolkit/content/widgets/popup.xml
Shane Caraveo 236b63bcd6 Bug 785952 - Fix arrow panel issues with side arrows and RTL. r=enn
--HG--
extra : rebase_source : 93f61063e2d904c12adb60d2943c8a73d42c9334
2012-08-28 09:47:19 -07:00

568 lines
20 KiB
XML

<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<bindings id="popupBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="popup-base">
<resources>
<stylesheet src="chrome://global/skin/popup.css"/>
</resources>
<implementation implements="nsIDOMXULPopupElement">
<property name="label" onget="return this.getAttribute('label');"
onset="this.setAttribute('label', val); return val;"/>
<property name="position" onget="return this.getAttribute('position');"
onset="this.setAttribute('position', val); return val;"/>
<property name="popupBoxObject">
<getter>
return this.boxObject.QueryInterface(Components.interfaces.nsIPopupBoxObject);
</getter>
</property>
<property name="state" readonly="true"
onget="return this.popupBoxObject.popupState"/>
<property name="triggerNode" readonly="true"
onget="return this.popupBoxObject.triggerNode"/>
<property name="anchorNode" readonly="true"
onget="return this.popupBoxObject.anchorNode"/>
<method name="openPopup">
<parameter name="aAnchorElement"/>
<parameter name="aPosition"/>
<parameter name="aX"/>
<parameter name="aY"/>
<parameter name="aIsContextMenu"/>
<parameter name="aAttributesOverride"/>
<parameter name="aTriggerEvent"/>
<body>
<![CDATA[
try {
var popupBox = this.popupBoxObject;
if (popupBox)
popupBox.openPopup(aAnchorElement, aPosition, aX, aY,
aIsContextMenu, aAttributesOverride, aTriggerEvent);
} catch(e) {}
]]>
</body>
</method>
<method name="openPopupAtScreen">
<parameter name="aX"/>
<parameter name="aY"/>
<parameter name="aIsContextMenu"/>
<parameter name="aTriggerEvent"/>
<body>
<![CDATA[
try {
var popupBox = this.popupBoxObject;
if (popupBox)
popupBox.openPopupAtScreen(aX, aY, aIsContextMenu, aTriggerEvent);
} catch(e) {}
]]>
</body>
</method>
<method name="showPopup">
<parameter name="element"/>
<parameter name="xpos"/>
<parameter name="ypos"/>
<parameter name="popuptype"/>
<parameter name="anchoralignment"/>
<parameter name="popupalignment"/>
<body>
<![CDATA[
var popupBox = null;
var menuBox = null;
try {
popupBox = this.popupBoxObject;
} catch(e) {}
try {
menuBox = this.parentNode.boxObject;
} catch(e) {}
if (menuBox instanceof Components.interfaces.nsIMenuBoxObject)
menuBox.openMenu(true);
else if (popupBox)
popupBox.showPopup(element, this, xpos, ypos, popuptype, anchoralignment, popupalignment);
]]>
</body>
</method>
<method name="hidePopup">
<body>
<![CDATA[
var popupBox = null;
var menuBox = null;
try {
popupBox = this.boxObject.QueryInterface(Components.interfaces.nsIPopupBoxObject);
} catch(e) {}
try {
menuBox = this.parentNode.boxObject;
} catch(e) {}
if (menuBox instanceof Components.interfaces.nsIMenuBoxObject)
menuBox.openMenu(false);
else if (popupBox)
popupBox.hidePopup();
]]>
</body>
</method>
<property name="autoPosition">
<getter>
<![CDATA[
return this.popupBoxObject.autoPosition;
]]>
</getter>
<setter>
<![CDATA[
return this.popupBoxObject.autoPosition = val;
]]>
</setter>
</property>
<method name="enableKeyboardNavigator">
<parameter name="aEnableKeyboardNavigator"/>
<body>
<![CDATA[
this.popupBoxObject.enableKeyboardNavigator(aEnableKeyboardNavigator);
]]>
</body>
</method>
<method name="enableRollup">
<parameter name="aEnableRollup"/>
<body>
<![CDATA[
this.popupBoxObject.enableRollup(aEnableRollup);
]]>
</body>
</method>
<method name="sizeTo">
<parameter name="aWidth"/>
<parameter name="aHeight"/>
<body>
<![CDATA[
this.popupBoxObject.sizeTo(aWidth, aHeight);
]]>
</body>
</method>
<method name="moveTo">
<parameter name="aLeft"/>
<parameter name="aTop"/>
<body>
<![CDATA[
this.popupBoxObject.moveTo(aLeft, aTop);
]]>
</body>
</method>
<method name="getOuterScreenRect">
<body>
<![CDATA[
return this.popupBoxObject.getOuterScreenRect();
]]>
</body>
</method>
</implementation>
</binding>
<binding id="popup"
extends="chrome://global/content/bindings/popup.xml#popup-base">
<content>
<xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
smoothscroll="false">
<children/>
</xul:arrowscrollbox>
</content>
<implementation implements="nsIAccessibleProvider">
<property name="accessibleType" readonly="true">
<getter>
<![CDATA[
return Components.interfaces.nsIAccessibleProvider.XULMenupopup;
]]>
</getter>
</property>
</implementation>
<handlers>
<handler event="popupshowing" phase="target">
<![CDATA[
var array = [];
var width = 0;
for (var menuitem = this.firstChild; menuitem; menuitem = menuitem.nextSibling) {
if (menuitem.localName == "menuitem" && menuitem.hasAttribute("acceltext")) {
var accel = document.getAnonymousElementByAttribute(menuitem, "anonid", "accel");
if (accel && accel.boxObject) {
array.push(accel);
if (accel.boxObject.width > width)
width = accel.boxObject.width;
}
}
}
for (var i = 0; i < array.length; i++)
array[i].width = width;
]]>
</handler>
</handlers>
</binding>
<binding id="panel"
extends="chrome://global/content/bindings/popup.xml#popup-base">
<!-- This separate binding for dialog-like panels - not menu, list or autocomplete popups
exposes the popup as an alert or a pane, depending on whether it is always intended
to get keyboard navigation when it opens -->
<implementation implements="nsIDOMXULPopupElement, nsIAccessibleProvider">
<property name="accessibleType" readonly="true">
<getter>
<![CDATA[
return (this.getAttribute("noautofocus") == "true") ?
Components.interfaces.nsIAccessibleProvider.XULAlert :
Components.interfaces.nsIAccessibleProvider.XULPane;
]]></getter>
</property>
<field name="_prevFocus">0</field>
<field name="_dragBindingAlive">true</field>
<constructor>
<![CDATA[
if (this.getAttribute("backdrag") == "true" && !this._draggableStarted) {
this._draggableStarted = true;
try {
let tmp = {};
Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
let draghandle = new tmp.WindowDraggingElement(this);
draghandle.mouseDownCheck = function () this._dragBindingAlive;
} catch (e) {}
}
]]>
</constructor>
</implementation>
<handlers>
<handler event="popupshowing"><![CDATA[
// Capture the previous focus before has a chance to get set inside the panel
try {
this._prevFocus = document.commandDispatcher.focusedElement;
if (!this._prevFocus) // Content window has focus
this._prevFocus = document.commandDispatcher.focusedWindow;
} catch (ex) {
this._prevFocus = document.activeElement;
}
]]></handler>
<handler event="popupshown"><![CDATA[
// Fire event for accessibility APIs
var alertEvent = document.createEvent("Events");
alertEvent.initEvent("AlertActive", true, true);
this.dispatchEvent(alertEvent);
]]></handler>
<handler event="popuphiding"><![CDATA[
try {
this._currentFocus = document.commandDispatcher.focusedElement;
} catch (e) {
this._currentFocus = document.activeElement;
}
]]></handler>
<handler event="popuphidden"><![CDATA[
var currentFocus = this._currentFocus;
var prevFocus = this._prevFocus;
this._currentFocus = null;
this._prevFocus = null;
if (prevFocus && currentFocus && this.getAttribute("norestorefocus") != "true") {
// Try to restore focus
try {
if (document.commandDispatcher.focusedWindow != window)
return; // Focus has already been set to a window outside of this panel
} catch(ex) {}
while (currentFocus) {
if (currentFocus == this) {
// Focus was set on an element inside this panel,
// so we need to move it back to where it was previously
try {
let fm = Components.classes["@mozilla.org/focus-manager;1"]
.getService(Components.interfaces.nsIFocusManager);
fm.setFocus(prevFocus, fm.FLAG_NOSCROLL);
} catch(e) {
prevFocus.focus();
}
return;
}
currentFocus = currentFocus.parentNode;
}
}
]]></handler>
</handlers>
</binding>
<binding id="arrowpanel" extends="chrome://global/content/bindings/popup.xml#panel">
<content flip="both" side="top" position="bottomcenter topleft">
<xul:box anonid="container" class="panel-arrowcontainer" flex="1"
xbl:inherits="side,panelopen">
<xul:box anonid="arrowbox" class="panel-arrowbox">
<xul:image anonid="arrow" class="panel-arrow"/>
</xul:box>
<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[
var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
var anchor = this.anchorNode;
if (!anchor) {
arrow.hidden = true;
return;
}
// Returns whether the first float is smaller than the second float or
// equals to it in a range of epsilon.
function smallerTo(aFloat1, aFloat2, aEpsilon)
{
return aFloat1 <= (aFloat2 + aEpsilon);
}
let popupRect = this.getBoundingClientRect();
let popupLeft = window.mozInnerScreenX + popupRect.left;
let popupTop = window.mozInnerScreenY + popupRect.top;
let popupRight = popupLeft + popupRect.width;
let popupBottom = popupTop + popupRect.height;
let anchorRect = anchor.getBoundingClientRect();
let anchorLeft = anchor.ownerDocument.defaultView.mozInnerScreenX + anchorRect.left;
let anchorTop = anchor.ownerDocument.defaultView.mozInnerScreenY + anchorRect.top;
let anchorRight = anchorLeft + anchorRect.width;
let anchorBottom = anchorTop + anchorRect.height;
try {
let anchorWindow = anchor.ownerDocument.defaultView;
if (anchorWindow != window) {
let utils = anchorWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
let spp = utils.screenPixelsPerCSSPixel;
anchorLeft *= spp;
anchorRight *= spp;
anchorTop *= spp;
anchorBottom *= spp;
}
} catch(ex) { }
const epsilon = 0.2;
var horizPos = smallerTo(popupRight, anchorLeft, epsilon) ? -1 : smallerTo(anchorRight, popupLeft, epsilon) ? 1 : 0;
var vertPos = smallerTo(popupBottom, anchorTop, epsilon) ? -1 : smallerTo(anchorBottom, popupTop, epsilon) ? 1 : 0;
var anchorClass = "";
var hideAnchor = false;
if (horizPos == 0) {
container.orient = "vertical";
arrowbox.orient = "";
if (vertPos == 0) {
hideAnchor = true;
}
else {
let pack = "";
// We have to guess where to position the arrow given that we don't
// have access to the parameters passed to |openPopup|.
// If the popup is on the left of the anchor.
if (smallerTo(popupLeft, anchorLeft, epsilon) && smallerTo(popupRight, anchorRight, epsilon)) {
pack = "end";
// If the popup is on the right of the anchor.
} else if (smallerTo(anchorLeft, popupLeft, epsilon) && smallerTo(anchorRight, popupRight, epsilon)) {
pack = "start"; //(popupLeft + popupRect.width / 2 > anchorRight) ? "start" : "end";
// If the popup is not on the right nor on the left.
// Basically, that means one is above the other and one is bigger
// than the other.
// In that case, we can't easily choose a position for the arrow so
// we have to guess depending on which side the popup is more close to.
} else {
pack = (Math.abs(popupLeft - anchorLeft) < Math.abs(popupRight - anchorRight)) ? "start" : "end";
}
// In RTL, everything should be inverted.
if (window.getComputedStyle(this).direction == "rtl") {
pack = (pack == "start") ? "end" : "start";
}
arrowbox.pack = pack;
if (vertPos == 1) {
container.dir = "";
anchorClass = "top";
}
else if (vertPos == -1) {
container.dir = "reverse";
anchorClass = "bottom";
}
}
}
else if (vertPos == 0) {
container.orient = "";
arrowbox.orient = "vertical";
if (horizPos == 0) {
hideAnchor = true;
}
else {
let dir = "";
arrowbox.pack = popupTop + popupRect.height / 2 < anchorTop ? "end" : "start";
if (horizPos == 1) {
anchorClass = "left";
}
else if (horizPos == -1) {
dir = "reverse";
anchorClass = "right";
}
// In RTL, everything should be inverted.
if (window.getComputedStyle(this).direction == "rtl") {
dir = (dir == "") ? "reverse" : "";
}
container.dir = dir;
}
}
else {
hideAnchor = true;
}
arrow.hidden = hideAnchor;
arrow.setAttribute("side", anchorClass);
this.setAttribute("side", anchorClass);
// 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);
}
]]>
</handler>
<handler event="popuphiding" phase="target">
clearTimeout(this._fadeTimer);
this.style.removeProperty("opacity");
</handler>
<handler event="transitionend" phase="target">
<![CDATA[
if (event.propertyName == "opacity" &&
event.originalTarget == this) {
this.hidePopup();
this.style.removeProperty("opacity");
}
]]>
</handler>
<handler event="popupshown" phase="target">
this.setAttribute("panelopen", "true");
</handler>
<handler event="popuphidden" phase="target">
this.removeAttribute("panelopen");
</handler>
</handlers>
</binding>
<binding id="tooltip" extends="chrome://global/content/bindings/popup.xml#popup-base">
<content>
<children>
<xul:label class="tooltip-label" xbl:inherits="xbl:text=label" flex="1"/>
</children>
</content>
<implementation implements="nsIAccessibleProvider">
<property name="accessibleType" readonly="true">
<getter>
return Components.interfaces.nsIAccessibleProvider.XULTooltip;
</getter>
</property>
<field name="_mouseOutCount">0</field>
<field name="_isMouseOver">false</field>
<property name="label"
onget="return this.getAttribute('label');"
onset="this.setAttribute('label', val); return val;"/>
</implementation>
<handlers>
<handler event="mouseover"><![CDATA[
var rel = event.relatedTarget;
//dump("ENTERING " + (rel ? rel.localName : "null") + "\n");
if (!rel)
return;
// find out if the node we entered from is one of our anonymous children
while (rel) {
if (rel == this)
break;
rel = rel.parentNode;
}
// if the exited node is not a descendant of ours, we are entering for the first time
if (rel != this)
this._isMouseOver = true;
]]></handler>
<handler event="mouseout"><![CDATA[
var rel = event.relatedTarget;
//dump("LEAVING " + (rel ? rel.localName : "null") + "\n");
// relatedTarget is null when the titletip is first shown: a mouseout event fires
// because the mouse is exiting the main window and entering the titletip "window".
// relatedTarget is also null when the mouse exits the main window completely,
// so count how many times relatedTarget was null after titletip is first shown
// and hide popup the 2nd time
if (!rel) {
++this._mouseOutCount;
if (this._mouseOutCount > 1)
this.hidePopup();
return;
}
// find out if the node we are entering is one of our anonymous children
while (rel) {
if (rel == this)
break;
rel = rel.parentNode;
}
// if the entered node is not a descendant of ours, hide the tooltip
if (rel != this && this._isMouseOver) {
this.hidePopup();
}
]]></handler>
<handler event="popuphiding"><![CDATA[
this._isMouseOver = false;
this._mouseOutCount = 0;
]]></handler>
</handlers>
</binding>
<binding id="popup-scrollbars" extends="chrome://global/content/bindings/popup.xml#popup">
<content>
<xul:hbox class="popup-internal-box" flex="1" orient="vertical" style="overflow: auto;">
<children/>
</xul:hbox>
</content>
</binding>
</bindings>