gecko/toolkit/content/widgets/popup.xml

715 lines
26 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="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);
]]>
</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;"/>
<property name="page" onset="if (val) this.setAttribute('page', 'true');
else this.removeAttribute('page');
return val;"
onget="return this.getAttribute('page') == 'true';"/>
<!-- Given the supplied element within a page, set the tooltip's text to the text
for that element. Returns true if text was assigned, and false if the no text
is set, which normally would be used to cancel tooltip display.
Note that DefaultTooltipTextProvider::GetNodeText() from nsDocShellTreeOwner.cpp
also performs the same function, but for embedded clients that don't use a XUL/JS
layer. These two should be kept synchronized.
-->
<method name="fillInPageTooltip">
<parameter name="tipElement"/>
<body>
<![CDATA[
// Don't show the tooltip if the tooltip node is a document or disconnected.
if (!tipElement || !tipElement.ownerDocument ||
(tipElement.ownerDocument.compareDocumentPosition(tipElement) & document.DOCUMENT_POSITION_DISCONNECTED)) {
return false;
}
var defView = tipElement.ownerDocument.defaultView;
// XXX Work around bug 350679:
// "Tooltips can be fired in documents with no view".
if (!defView)
return false;
const XLinkNS = "http://www.w3.org/1999/xlink";
const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
var titleText = null;
var XLinkTitleText = null;
var SVGTitleText = null;
var lookingForSVGTitle = true;
var direction = tipElement.ownerDocument.dir;
// If the element is invalid per HTML5 Forms specifications and has no title,
// show the constraint validation error message.
if ((tipElement instanceof HTMLInputElement ||
tipElement instanceof HTMLTextAreaElement ||
tipElement instanceof HTMLSelectElement ||
tipElement instanceof HTMLButtonElement) &&
!tipElement.hasAttribute('title') &&
(!tipElement.form || !tipElement.form.noValidate)) {
// If the element is barred from constraint validation or valid,
// the validation message will be the empty string.
titleText = tipElement.validationMessage || null;
}
// If the element is an <input type='file'> without a title, we should show
// the current file selection.
if (!titleText &&
tipElement instanceof HTMLInputElement &&
tipElement.type == 'file' &&
!tipElement.hasAttribute('title')) {
let files = tipElement.files;
if (files.length == 0) {
try {
var bundle = Components.classes['@mozilla.org/intl/stringbundle;1']
.getService(Components.interfaces.nsIStringBundleService)
.createBundle("chrome://global/locale/layout/HtmlForm.properties");
if (tipElement.multiple) {
titleText = bundle.GetStringFromName("NoFilesSelected");
} else {
titleText = bundle.GetStringFromName("NoFileSelected");
}
} catch(e) {}
} else {
titleText = files[0].name;
for (let i=1; i<files.length; ++i) {
titleText += "\n" + files[i].name;
}
}
}
while ((titleText == null) && (XLinkTitleText == null) &&
(SVGTitleText == null) && tipElement) {
if (tipElement.nodeType == Node.ELEMENT_NODE &&
tipElement.namespaceURI != XULNS) {
titleText = tipElement.getAttribute("title");
if ((tipElement instanceof HTMLAnchorElement ||
tipElement instanceof HTMLAreaElement ||
tipElement instanceof HTMLLinkElement ||
tipElement instanceof SVGAElement) && tipElement.href) {
XLinkTitleText = tipElement.getAttributeNS(XLinkNS, "title");
}
if (lookingForSVGTitle &&
(!(tipElement instanceof SVGElement) ||
tipElement.parentNode.nodeType == Node.DOCUMENT_NODE)) {
lookingForSVGTitle = false;
}
if (lookingForSVGTitle) {
for (let childNode of tipElement.childNodes) {
if (childNode instanceof SVGTitleElement) {
SVGTitleText = childNode.textContent;
break;
}
}
}
direction = defView.getComputedStyle(tipElement, "")
.getPropertyValue("direction");
}
tipElement = tipElement.parentNode;
}
this.style.direction = direction;
return [titleText, XLinkTitleText, SVGTitleText].some(function (t) {
if (t && /\S/.test(t)) {
// Make CRLF and CR render one line break each.
this.label = t.replace(/\r\n?/g, '\n');
return true;
}
return false;
}, this);
return false;
]]>
</body>
</method>
</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="popupshowing"><![CDATA[
if (this.page && !this.fillInPageTooltip(this.triggerNode)) {
event.preventDefault();
}
]]></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>