зеркало из https://github.com/mozilla/gecko-dev.git
385 строки
13 KiB
XML
385 строки
13 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/. -->
|
|
|
|
<!-- This files relies on these specific Chrome/XBL globals -->
|
|
<!-- globals PopupBoxObject -->
|
|
|
|
<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">
|
|
<content>
|
|
<xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
|
|
smoothscroll="false">
|
|
<children/>
|
|
</xul:arrowscrollbox>
|
|
</content>
|
|
|
|
<implementation>
|
|
<field name="scrollBox" readonly="true">
|
|
document.getAnonymousElementByAttribute(this, "class", "popup-internal-box");
|
|
</field>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="popupshowing" phase="target">
|
|
<![CDATA[
|
|
var array = [];
|
|
var width = 0;
|
|
for (var menuitem = this.firstElementChild; menuitem; menuitem = menuitem.nextElementSibling) {
|
|
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">
|
|
<implementation>
|
|
<field name="_prevFocus">0</field>
|
|
</implementation>
|
|
|
|
<handlers>
|
|
<handler event="popupshowing"><![CDATA[
|
|
// Capture the previous focus before has a chance to get set inside the panel
|
|
try {
|
|
this._prevFocus = Cu
|
|
.getWeakReference(document.commandDispatcher.focusedElement);
|
|
if (this._prevFocus.get())
|
|
return;
|
|
} catch (ex) { }
|
|
|
|
this._prevFocus = Cu.getWeakReference(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[
|
|
function doFocus() {
|
|
// Focus was set on an element inside this panel,
|
|
// so we need to move it back to where it was previously
|
|
try {
|
|
let fm = Cc["@mozilla.org/focus-manager;1"]
|
|
.getService(Ci.nsIFocusManager);
|
|
fm.setFocus(prevFocus, fm.FLAG_NOSCROLL);
|
|
} catch (e) {
|
|
prevFocus.focus();
|
|
}
|
|
}
|
|
var currentFocus = this._currentFocus;
|
|
var prevFocus = this._prevFocus ? this._prevFocus.get() : null;
|
|
this._currentFocus = null;
|
|
this._prevFocus = null;
|
|
|
|
// Avoid changing focus if focus changed while we hide the popup
|
|
// (This can happen e.g. if the popup is hiding as a result of a
|
|
// click/keypress that focused something)
|
|
let nowFocus;
|
|
try {
|
|
nowFocus = document.commandDispatcher.focusedElement;
|
|
} catch (e) {
|
|
nowFocus = document.activeElement;
|
|
}
|
|
if (nowFocus && nowFocus != currentFocus)
|
|
return;
|
|
|
|
if (prevFocus && 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) {}
|
|
|
|
if (!currentFocus) {
|
|
doFocus();
|
|
return;
|
|
}
|
|
while (currentFocus) {
|
|
if (currentFocus == this) {
|
|
doFocus();
|
|
return;
|
|
}
|
|
currentFocus = currentFocus.parentNode;
|
|
}
|
|
}
|
|
]]></handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
<binding id="arrowpanel" extends="chrome://global/content/bindings/popup.xml#panel">
|
|
<resources>
|
|
<!-- Fixes an issue with the "test_arrowpanel.xul" animation on Mac, see bug 1470880. -->
|
|
<stylesheet src="data:text/css,"/>
|
|
</resources>
|
|
|
|
<content flip="both" side="top" position="bottomcenter topleft" consumeoutsideclicks="false">
|
|
<xul:vbox 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" xbl:inherits="side"/>
|
|
</xul:box>
|
|
<xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1">
|
|
<children/>
|
|
</xul:box>
|
|
</xul:vbox>
|
|
</content>
|
|
<implementation>
|
|
<field name="_fadeTimer">null</field>
|
|
<method name="adjustArrowPosition">
|
|
<body>
|
|
<![CDATA[
|
|
var anchor = this.anchorNode;
|
|
if (!anchor) {
|
|
return;
|
|
}
|
|
|
|
var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
|
|
var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
|
|
|
|
var position = this.alignmentPosition;
|
|
var offset = this.alignmentOffset;
|
|
|
|
this.setAttribute("arrowposition", position);
|
|
|
|
if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
|
|
container.orient = "horizontal";
|
|
arrowbox.orient = "vertical";
|
|
if (position.indexOf("_after") > 0) {
|
|
arrowbox.pack = "end";
|
|
} else {
|
|
arrowbox.pack = "start";
|
|
}
|
|
arrowbox.style.transform = "translate(0, " + -offset + "px)";
|
|
|
|
// The assigned side stays the same regardless of direction.
|
|
var isRTL = (window.getComputedStyle(this).direction == "rtl");
|
|
|
|
if (position.indexOf("start_") == 0) {
|
|
container.dir = "reverse";
|
|
this.setAttribute("side", isRTL ? "left" : "right");
|
|
} else {
|
|
container.dir = "";
|
|
this.setAttribute("side", isRTL ? "right" : "left");
|
|
}
|
|
} else if (position.indexOf("before_") == 0 || position.indexOf("after_") == 0) {
|
|
container.orient = "";
|
|
arrowbox.orient = "";
|
|
if (position.indexOf("_end") > 0) {
|
|
arrowbox.pack = "end";
|
|
} else {
|
|
arrowbox.pack = "start";
|
|
}
|
|
arrowbox.style.transform = "translate(" + -offset + "px, 0)";
|
|
|
|
if (position.indexOf("before_") == 0) {
|
|
container.dir = "reverse";
|
|
this.setAttribute("side", "bottom");
|
|
} else {
|
|
container.dir = "";
|
|
this.setAttribute("side", "top");
|
|
}
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
</implementation>
|
|
<handlers>
|
|
<handler event="popupshowing" phase="target">
|
|
<![CDATA[
|
|
var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
|
|
arrow.hidden = this.anchorNode == null;
|
|
document.getAnonymousElementByAttribute(this, "anonid", "arrowbox")
|
|
.style.removeProperty("transform");
|
|
|
|
if (this.getAttribute("animate") != "false") {
|
|
this.setAttribute("animate", "open");
|
|
// the animating attribute prevents user interaction during transition
|
|
// it is removed when popupshown fires
|
|
this.setAttribute("animating", "true");
|
|
}
|
|
|
|
// set fading
|
|
var fade = this.getAttribute("fade");
|
|
var fadeDelay = 0;
|
|
if (fade == "fast") {
|
|
fadeDelay = 1;
|
|
} else if (fade == "slow") {
|
|
fadeDelay = 4000;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
this._fadeTimer = setTimeout(() => this.hidePopup(true), fadeDelay, this);
|
|
]]>
|
|
</handler>
|
|
<handler event="popuphiding" phase="target">
|
|
let animate = (this.getAttribute("animate") != "false");
|
|
|
|
if (this._fadeTimer) {
|
|
clearTimeout(this._fadeTimer);
|
|
if (animate) {
|
|
this.setAttribute("animate", "fade");
|
|
}
|
|
} else if (animate) {
|
|
this.setAttribute("animate", "cancel");
|
|
}
|
|
</handler>
|
|
<handler event="popupshown" phase="target">
|
|
this.removeAttribute("animating");
|
|
this.setAttribute("panelopen", "true");
|
|
</handler>
|
|
<handler event="popuphidden" phase="target">
|
|
this.removeAttribute("panelopen");
|
|
if (this.getAttribute("animate") != "false") {
|
|
this.removeAttribute("animate");
|
|
}
|
|
</handler>
|
|
<handler event="popuppositioned" phase="target">
|
|
this.adjustArrowPosition();
|
|
</handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
<binding id="popup-scrollbars" extends="chrome://global/content/bindings/popup.xml#popup">
|
|
<content>
|
|
<xul:scrollbox class="popup-internal-box" flex="1" orient="vertical" style="overflow: auto;">
|
|
<children/>
|
|
</xul:scrollbox>
|
|
</content>
|
|
<implementation>
|
|
<field name="AUTOSCROLL_INTERVAL">25</field>
|
|
<field name="NOT_DRAGGING">0</field>
|
|
<field name="DRAG_OVER_BUTTON">-1</field>
|
|
<field name="DRAG_OVER_POPUP">1</field>
|
|
|
|
<field name="_draggingState">this.NOT_DRAGGING</field>
|
|
<field name="_scrollTimer">0</field>
|
|
|
|
<method name="enableDragScrolling">
|
|
<!-- when overItem is true, drag started over menuitem; when false, drag
|
|
started while the popup was opening.
|
|
-->
|
|
<parameter name="overItem"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (!this._draggingState) {
|
|
this.setCaptureAlways();
|
|
this._draggingState = overItem ? this.DRAG_OVER_POPUP : this.DRAG_OVER_BUTTON;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
<method name="_clearScrollTimer">
|
|
<body>
|
|
<![CDATA[
|
|
if (this._scrollTimer) {
|
|
this.ownerGlobal.clearInterval(this._scrollTimer);
|
|
this._scrollTimer = 0;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
</implementation>
|
|
<handlers>
|
|
<handler event="popupshown">
|
|
// Enable drag scrolling even when the mouse wasn't used. The mousemove
|
|
// handler will remove it if the mouse isn't down.
|
|
this.enableDragScrolling(false);
|
|
</handler>
|
|
|
|
<handler event="popuphidden">
|
|
<![CDATA[
|
|
this._draggingState = this.NOT_DRAGGING;
|
|
this._clearScrollTimer();
|
|
this.releaseCapture();
|
|
]]>
|
|
</handler>
|
|
|
|
<handler event="mousedown" button="0">
|
|
<![CDATA[
|
|
if (this.state == "open" &&
|
|
(event.target.localName == "menuitem" ||
|
|
event.target.localName == "menu" ||
|
|
event.target.localName == "menucaption")) {
|
|
this.enableDragScrolling(true);
|
|
}
|
|
]]>
|
|
</handler>
|
|
<handler event="mouseup" button="0">
|
|
<![CDATA[
|
|
this._draggingState = this.NOT_DRAGGING;
|
|
this._clearScrollTimer();
|
|
]]>
|
|
</handler>
|
|
<handler event="mousemove">
|
|
<![CDATA[
|
|
if (!this._draggingState) {
|
|
return;
|
|
}
|
|
|
|
this._clearScrollTimer();
|
|
|
|
// If the user released the mouse before the popup opens, we will
|
|
// still be capturing, so check that the button is still pressed. If
|
|
// not, release the capture and do nothing else. This also handles if
|
|
// the dropdown was opened via the keyboard.
|
|
if (!(event.buttons & 1)) {
|
|
this._draggingState = this.NOT_DRAGGING;
|
|
this.releaseCapture();
|
|
return;
|
|
}
|
|
|
|
// If dragging outside the top or bottom edge of the popup, but within
|
|
// the popup area horizontally, scroll the list in that direction. The
|
|
// _draggingState flag is used to ensure that scrolling does not start
|
|
// until the mouse has moved over the popup first, preventing scrolling
|
|
// while over the dropdown button.
|
|
let popupRect = this.getOuterScreenRect();
|
|
if (event.screenX >= popupRect.left && event.screenX <= popupRect.right) {
|
|
if (this._draggingState == this.DRAG_OVER_BUTTON) {
|
|
if (event.screenY > popupRect.top && event.screenY < popupRect.bottom) {
|
|
this._draggingState = this.DRAG_OVER_POPUP;
|
|
}
|
|
}
|
|
|
|
if (this._draggingState == this.DRAG_OVER_POPUP &&
|
|
(event.screenY <= popupRect.top || event.screenY >= popupRect.bottom)) {
|
|
let scrollAmount = event.screenY <= popupRect.top ? -1 : 1;
|
|
this.scrollBox.scrollByIndex(scrollAmount);
|
|
|
|
let win = this.ownerGlobal;
|
|
this._scrollTimer = win.setInterval(() => {
|
|
this.scrollBox.scrollByIndex(scrollAmount);
|
|
}, this.AUTOSCROLL_INTERVAL);
|
|
}
|
|
}
|
|
]]>
|
|
</handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
</bindings>
|