Source code

Revision control

Other Tools

1
<?xml version="1.0"?>
2
<!-- This Source Code Form is subject to the terms of the Mozilla Public
3
- License, v. 2.0. If a copy of the MPL was not distributed with this
4
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
5
6
<bindings id="popupBindings"
10
11
<binding id="popup">
12
<content>
13
<xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
14
smoothscroll="false">
15
<children/>
16
</xul:arrowscrollbox>
17
</content>
18
19
<implementation>
20
<field name="scrollBox" readonly="true">
21
document.getAnonymousElementByAttribute(this, "class", "popup-internal-box");
22
</field>
23
24
<field name="AUTOSCROLL_INTERVAL">25</field>
25
<field name="NOT_DRAGGING">0</field>
26
<field name="DRAG_OVER_BUTTON">-1</field>
27
<field name="DRAG_OVER_POPUP">1</field>
28
29
<field name="_draggingState">this.NOT_DRAGGING</field>
30
<field name="_scrollTimer">0</field>
31
32
<method name="_enableDragScrolling">
33
<!-- when overItem is true, drag started over menuitem; when false, drag
34
started while the popup was opening.
35
-->
36
<parameter name="overItem"/>
37
<body>
38
<![CDATA[
39
if (!this._draggingState) {
40
this.setCaptureAlways();
41
this._draggingState = overItem ? this.DRAG_OVER_POPUP : this.DRAG_OVER_BUTTON;
42
}
43
]]>
44
</body>
45
</method>
46
47
<method name="_clearScrollTimer">
48
<body>
49
<![CDATA[
50
if (this._scrollTimer) {
51
this.ownerGlobal.clearInterval(this._scrollTimer);
52
this._scrollTimer = 0;
53
}
54
]]>
55
</body>
56
</method>
57
58
<constructor><![CDATA[
59
// Enable the drag-to-scroll events only in menulist popups.
60
if (!this.parentNode || this.parentNode.localName != "menulist") {
61
return;
62
}
63
64
// XBL bindings might be constructed more than once.
65
if (this.eventListenersAdded) {
66
return;
67
}
68
this.eventListenersAdded = true;
69
70
this.addEventListener("popupshown", () => {
71
// Enable drag scrolling even when the mouse wasn't used. The
72
// mousemove handler will remove it if the mouse isn't down.
73
this._enableDragScrolling(false);
74
});
75
76
this.addEventListener("popuphidden", () => {
77
this._draggingState = this.NOT_DRAGGING;
78
this._clearScrollTimer();
79
this.releaseCapture();
80
});
81
82
this.addEventListener("mousedown", event => {
83
if (event.button != 0) {
84
return;
85
}
86
87
if (this.state == "open" &&
88
(event.target.localName == "menuitem" ||
89
event.target.localName == "menu" ||
90
event.target.localName == "menucaption")) {
91
this._enableDragScrolling(true);
92
}
93
});
94
95
this.addEventListener("mouseup", event => {
96
if (event.button != 0) {
97
return;
98
}
99
100
this._draggingState = this.NOT_DRAGGING;
101
this._clearScrollTimer();
102
});
103
104
this.addEventListener("mousemove", event => {
105
if (!this._draggingState) {
106
return;
107
}
108
109
this._clearScrollTimer();
110
111
// If the user released the mouse before the popup opens, we will
112
// still be capturing, so check that the button is still pressed. If
113
// not, release the capture and do nothing else. This also handles if
114
// the dropdown was opened via the keyboard.
115
if (!(event.buttons & 1)) {
116
this._draggingState = this.NOT_DRAGGING;
117
this.releaseCapture();
118
return;
119
}
120
121
// If dragging outside the top or bottom edge of the popup, but within
122
// the popup area horizontally, scroll the list in that direction. The
123
// _draggingState flag is used to ensure that scrolling does not start
124
// until the mouse has moved over the popup first, preventing
125
// scrolling while over the dropdown button.
126
let popupRect = this.getOuterScreenRect();
127
if (event.screenX >= popupRect.left &&
128
event.screenX <= popupRect.right) {
129
if (this._draggingState == this.DRAG_OVER_BUTTON) {
130
if (event.screenY > popupRect.top &&
131
event.screenY < popupRect.bottom) {
132
this._draggingState = this.DRAG_OVER_POPUP;
133
}
134
}
135
136
if (this._draggingState == this.DRAG_OVER_POPUP &&
137
(event.screenY <= popupRect.top ||
138
event.screenY >= popupRect.bottom)) {
139
let scrollAmount = event.screenY <= popupRect.top ? -1 : 1;
140
this.scrollBox.scrollByIndex(scrollAmount, true);
141
142
let win = this.ownerGlobal;
143
this._scrollTimer = win.setInterval(() => {
144
this.scrollBox.scrollByIndex(scrollAmount, true);
145
}, this.AUTOSCROLL_INTERVAL);
146
}
147
}
148
});
149
]]></constructor>
150
</implementation>
151
152
<handlers>
153
<handler event="popupshowing" phase="target">
154
<![CDATA[
155
var array = [];
156
var width = 0;
157
for (var menuitem = this.firstElementChild; menuitem; menuitem = menuitem.nextElementSibling) {
158
if (menuitem.localName == "menuitem" && menuitem.hasAttribute("acceltext")) {
159
var accel = menuitem.menuAccel;
160
if (accel) {
161
array.push(accel);
162
let accelWidth = accel.getBoundingClientRect().width;
163
if (accelWidth > width) {
164
width = accelWidth;
165
}
166
}
167
}
168
}
169
for (var i = 0; i < array.length; i++)
170
array[i].width = width;
171
]]>
172
</handler>
173
</handlers>
174
</binding>
175
176
<binding id="panel">
177
<implementation>
178
<field name="_prevFocus">0</field>
179
</implementation>
180
181
<handlers>
182
<handler event="popupshowing"><![CDATA[
183
// Capture the previous focus before has a chance to get set inside the panel
184
try {
185
this._prevFocus = Cu
186
.getWeakReference(document.commandDispatcher.focusedElement);
187
if (this._prevFocus.get())
188
return;
189
} catch (ex) { }
190
191
this._prevFocus = Cu.getWeakReference(document.activeElement);
192
]]></handler>
193
<handler event="popupshown"><![CDATA[
194
// Fire event for accessibility APIs
195
var alertEvent = document.createEvent("Events");
196
alertEvent.initEvent("AlertActive", true, true);
197
this.dispatchEvent(alertEvent);
198
]]></handler>
199
<handler event="popuphiding"><![CDATA[
200
try {
201
this._currentFocus = document.commandDispatcher.focusedElement;
202
} catch (e) {
203
this._currentFocus = document.activeElement;
204
}
205
]]></handler>
206
<handler event="popuphidden"><![CDATA[
207
function doFocus() {
208
// Focus was set on an element inside this panel,
209
// so we need to move it back to where it was previously
210
try {
211
let fm = Cc["@mozilla.org/focus-manager;1"]
212
.getService(Ci.nsIFocusManager);
213
fm.setFocus(prevFocus, fm.FLAG_NOSCROLL);
214
} catch (e) {
215
prevFocus.focus();
216
}
217
}
218
var currentFocus = this._currentFocus;
219
var prevFocus = this._prevFocus ? this._prevFocus.get() : null;
220
this._currentFocus = null;
221
this._prevFocus = null;
222
223
// Avoid changing focus if focus changed while we hide the popup
224
// (This can happen e.g. if the popup is hiding as a result of a
225
// click/keypress that focused something)
226
let nowFocus;
227
try {
228
nowFocus = document.commandDispatcher.focusedElement;
229
} catch (e) {
230
nowFocus = document.activeElement;
231
}
232
if (nowFocus && nowFocus != currentFocus)
233
return;
234
235
if (prevFocus && this.getAttribute("norestorefocus") != "true") {
236
// Try to restore focus
237
try {
238
if (document.commandDispatcher.focusedWindow != window)
239
return; // Focus has already been set to a window outside of this panel
240
} catch (ex) {}
241
242
if (!currentFocus) {
243
doFocus();
244
return;
245
}
246
while (currentFocus) {
247
if (currentFocus == this) {
248
doFocus();
249
return;
250
}
251
currentFocus = currentFocus.parentNode;
252
}
253
}
254
]]></handler>
255
</handlers>
256
</binding>
257
258
<binding id="arrowpanel" extends="chrome://global/content/bindings/popup.xml#panel">
259
<content flip="both" side="top" position="bottomcenter topleft" consumeoutsideclicks="false">
260
<xul:vbox anonid="container" class="panel-arrowcontainer" flex="1"
261
xbl:inherits="side,panelopen">
262
<xul:box anonid="arrowbox" class="panel-arrowbox">
263
<xul:image anonid="arrow" class="panel-arrow" xbl:inherits="side"/>
264
</xul:box>
265
<xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1">
266
<children/>
267
</xul:box>
268
</xul:vbox>
269
</content>
270
<implementation>
271
<field name="_fadeTimer">null</field>
272
<method name="adjustArrowPosition">
273
<body>
274
<![CDATA[
275
var anchor = this.anchorNode;
276
if (!anchor) {
277
return;
278
}
279
280
var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
281
var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
282
283
var position = this.alignmentPosition;
284
var offset = this.alignmentOffset;
285
286
this.setAttribute("arrowposition", position);
287
288
if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
289
container.orient = "horizontal";
290
arrowbox.orient = "vertical";
291
if (position.indexOf("_after") > 0) {
292
arrowbox.pack = "end";
293
} else {
294
arrowbox.pack = "start";
295
}
296
arrowbox.style.transform = "translate(0, " + -offset + "px)";
297
298
// The assigned side stays the same regardless of direction.
299
var isRTL = (window.getComputedStyle(this).direction == "rtl");
300
301
if (position.indexOf("start_") == 0) {
302
container.dir = "reverse";
303
this.setAttribute("side", isRTL ? "left" : "right");
304
} else {
305
container.dir = "";
306
this.setAttribute("side", isRTL ? "right" : "left");
307
}
308
} else if (position.indexOf("before_") == 0 || position.indexOf("after_") == 0) {
309
container.orient = "";
310
arrowbox.orient = "";
311
if (position.indexOf("_end") > 0) {
312
arrowbox.pack = "end";
313
} else {
314
arrowbox.pack = "start";
315
}
316
arrowbox.style.transform = "translate(" + -offset + "px, 0)";
317
318
if (position.indexOf("before_") == 0) {
319
container.dir = "reverse";
320
this.setAttribute("side", "bottom");
321
} else {
322
container.dir = "";
323
this.setAttribute("side", "top");
324
}
325
}
326
]]>
327
</body>
328
</method>
329
</implementation>
330
<handlers>
331
<handler event="popupshowing" phase="target">
332
<![CDATA[
333
var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
334
arrow.hidden = this.anchorNode == null;
335
document.getAnonymousElementByAttribute(this, "anonid", "arrowbox")
336
.style.removeProperty("transform");
337
338
if (this.getAttribute("animate") != "false") {
339
this.setAttribute("animate", "open");
340
// the animating attribute prevents user interaction during transition
341
// it is removed when popupshown fires
342
this.setAttribute("animating", "true");
343
}
344
345
// set fading
346
var fade = this.getAttribute("fade");
347
var fadeDelay = 0;
348
if (fade == "fast") {
349
fadeDelay = 1;
350
} else if (fade == "slow") {
351
fadeDelay = 4000;
352
} else {
353
return;
354
}
355
356
this._fadeTimer = setTimeout(() => this.hidePopup(true), fadeDelay, this);
357
]]>
358
</handler>
359
<handler event="popuphiding" phase="target">
360
let animate = (this.getAttribute("animate") != "false");
361
362
if (this._fadeTimer) {
363
clearTimeout(this._fadeTimer);
364
if (animate) {
365
this.setAttribute("animate", "fade");
366
}
367
} else if (animate) {
368
this.setAttribute("animate", "cancel");
369
}
370
</handler>
371
<handler event="popupshown" phase="target">
372
this.removeAttribute("animating");
373
this.setAttribute("panelopen", "true");
374
</handler>
375
<handler event="popuphidden" phase="target">
376
this.removeAttribute("panelopen");
377
if (this.getAttribute("animate") != "false") {
378
this.removeAttribute("animate");
379
}
380
</handler>
381
<handler event="popuppositioned" phase="target">
382
this.adjustArrowPosition();
383
</handler>
384
</handlers>
385
</binding>
386
</bindings>