Source code

Revision control

Other Tools

1
<?xml version="1.0"?>
2
3
<!-- This Source Code Form is subject to the terms of the Mozilla Public
4
- License, v. 2.0. If a copy of the MPL was not distributed with this
5
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
6
7
<!-- import-globals-from controller.js -->
8
9
<bindings id="placesMenuBindings"
14
15
<binding id="places-popup-base"
17
<content>
18
<xul:hbox flex="1">
19
<xul:vbox class="menupopup-drop-indicator-bar" hidden="true">
20
<xul:image class="menupopup-drop-indicator" mousethrough="always"/>
21
</xul:vbox>
22
<xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
23
smoothscroll="false">
24
<children/>
25
</xul:arrowscrollbox>
26
</xul:hbox>
27
</content>
28
29
<implementation>
30
31
<field name="AppConstants" readonly="true">
32
(ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {})).AppConstants;
33
</field>
34
35
<field name="_indicatorBar">
36
document.getAnonymousElementByAttribute(this, "class",
37
"menupopup-drop-indicator-bar");
38
</field>
39
40
<field name="_scrollBox">
41
document.getAnonymousElementByAttribute(this, "class",
42
"popup-internal-box");
43
</field>
44
45
<!-- This is the view that manage the popup -->
46
<field name="_rootView">PlacesUIUtils.getViewForNode(this);</field>
47
48
<!-- Check if we should hide the drop indicator for the target -->
49
<method name="_hideDropIndicator">
50
<parameter name="aEvent"/>
51
<body><![CDATA[
52
let target = aEvent.target;
53
54
// Don't draw the drop indicator outside of markers or if current
55
// node is not a Places node.
56
let betweenMarkers =
57
(this._startMarker.compareDocumentPosition(target) & Node.DOCUMENT_POSITION_FOLLOWING) &&
58
(this._endMarker.compareDocumentPosition(target) & Node.DOCUMENT_POSITION_PRECEDING);
59
60
// Hide the dropmarker if current node is not a Places node.
61
return !(target && target._placesNode && betweenMarkers);
62
]]></body>
63
</method>
64
65
<!-- This function returns information about where to drop when
66
dragging over this popup insertion point -->
67
<method name="_getDropPoint">
68
<parameter name="aEvent"/>
69
<body><![CDATA[
70
// Can't drop if the menu isn't a folder
71
let resultNode = this._placesNode;
72
73
if (!PlacesUtils.nodeIsFolder(resultNode) ||
74
this._rootView.controller.disallowInsertion(resultNode)) {
75
return null;
76
}
77
78
var dropPoint = { ip: null, folderElt: null };
79
80
// The element we are dragging over
81
let elt = aEvent.target;
82
if (elt.localName == "menupopup")
83
elt = elt.parentNode;
84
85
let eventY = aEvent.clientY;
86
let {y: eltY, height: eltHeight} = elt.getBoundingClientRect();
87
88
if (!elt._placesNode) {
89
// If we are dragging over a non places node drop at the end.
90
dropPoint.ip = new PlacesInsertionPoint({
91
parentId: PlacesUtils.getConcreteItemId(resultNode),
92
parentGuid: PlacesUtils.getConcreteItemGuid(resultNode),
93
});
94
// We can set folderElt if we are dropping over a static menu that
95
// has an internal placespopup.
96
let isMenu = elt.localName == "menu" ||
97
(elt.localName == "toolbarbutton" &&
98
elt.getAttribute("type") == "menu");
99
if (isMenu && elt.lastElementChild &&
100
elt.lastElementChild.hasAttribute("placespopup"))
101
dropPoint.folderElt = elt;
102
return dropPoint;
103
}
104
105
let tagName = PlacesUtils.nodeIsTagQuery(elt._placesNode) ?
106
elt._placesNode.title : null;
107
if ((PlacesUtils.nodeIsFolder(elt._placesNode) &&
108
!PlacesUIUtils.isFolderReadOnly(elt._placesNode)) ||
109
PlacesUtils.nodeIsTagQuery(elt._placesNode)) {
110
// This is a folder or a tag container.
111
if (eventY - eltY < eltHeight * 0.20) {
112
// If mouse is in the top part of the element, drop above folder.
113
dropPoint.ip = new PlacesInsertionPoint({
114
parentId: PlacesUtils.getConcreteItemId(resultNode),
115
parentGuid: PlacesUtils.getConcreteItemGuid(resultNode),
116
orientation: Ci.nsITreeView.DROP_BEFORE,
117
tagName,
118
dropNearNode: elt._placesNode,
119
});
120
return dropPoint;
121
} else if (eventY - eltY < eltHeight * 0.80) {
122
// If mouse is in the middle of the element, drop inside folder.
123
dropPoint.ip = new PlacesInsertionPoint({
124
parentId: PlacesUtils.getConcreteItemId(elt._placesNode),
125
parentGuid: PlacesUtils.getConcreteItemGuid(elt._placesNode),
126
tagName,
127
});
128
dropPoint.folderElt = elt;
129
return dropPoint;
130
}
131
} else if (eventY - eltY <= eltHeight / 2) {
132
// This is a non-folder node or a readonly folder.
133
// If the mouse is above the middle, drop above this item.
134
dropPoint.ip = new PlacesInsertionPoint({
135
parentId: PlacesUtils.getConcreteItemId(resultNode),
136
parentGuid: PlacesUtils.getConcreteItemGuid(resultNode),
137
orientation: Ci.nsITreeView.DROP_BEFORE,
138
tagName,
139
dropNearNode: elt._placesNode,
140
});
141
return dropPoint;
142
}
143
144
// Drop below the item.
145
dropPoint.ip = new PlacesInsertionPoint({
146
parentId: PlacesUtils.getConcreteItemId(resultNode),
147
parentGuid: PlacesUtils.getConcreteItemGuid(resultNode),
148
orientation: Ci.nsITreeView.DROP_AFTER,
149
tagName,
150
dropNearNode: elt._placesNode,
151
});
152
return dropPoint;
153
]]></body>
154
</method>
155
156
<!-- Sub-menus should be opened when the mouse drags over them, and closed
157
when the mouse drags off. The overFolder object manages opening and
158
closing of folders when the mouse hovers. -->
159
<field name="_overFolder"><![CDATA[({
160
_self: this,
161
_folder: {elt: null,
162
openTimer: null,
163
hoverTime: 350,
164
closeTimer: null},
165
_closeMenuTimer: null,
166
167
get elt() {
168
return this._folder.elt;
169
},
170
set elt(val) {
171
return this._folder.elt = val;
172
},
173
174
get openTimer() {
175
return this._folder.openTimer;
176
},
177
set openTimer(val) {
178
return this._folder.openTimer = val;
179
},
180
181
get hoverTime() {
182
return this._folder.hoverTime;
183
},
184
set hoverTime(val) {
185
return this._folder.hoverTime = val;
186
},
187
188
get closeTimer() {
189
return this._folder.closeTimer;
190
},
191
set closeTimer(val) {
192
return this._folder.closeTimer = val;
193
},
194
195
get closeMenuTimer() {
196
return this._closeMenuTimer;
197
},
198
set closeMenuTimer(val) {
199
return this._closeMenuTimer = val;
200
},
201
202
setTimer: function OF__setTimer(aTime) {
203
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
204
timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT);
205
return timer;
206
},
207
208
notify: function OF__notify(aTimer) {
209
// Function to process all timer notifications.
210
211
if (aTimer == this._folder.openTimer) {
212
// Timer to open a submenu that's being dragged over.
213
this._folder.elt.lastElementChild.setAttribute("autoopened", "true");
214
this._folder.elt.lastElementChild.openPopup();
215
this._folder.openTimer = null;
216
} else if (aTimer == this._folder.closeTimer) {
217
// Timer to close a submenu that's been dragged off of.
218
// Only close the submenu if the mouse isn't being dragged over any
219
// of its child menus.
220
var draggingOverChild = PlacesControllerDragHelper
221
.draggingOverChildNode(this._folder.elt);
222
if (draggingOverChild)
223
this._folder.elt = null;
224
this.clear();
225
226
// Close any parent folders which aren't being dragged over.
227
// (This is necessary because of the above code that keeps a folder
228
// open while its children are being dragged over.)
229
if (!draggingOverChild)
230
this.closeParentMenus();
231
} else if (aTimer == this.closeMenuTimer) {
232
// Timer to close this menu after the drag exit.
233
var popup = this._self;
234
// if we are no more dragging we can leave the menu open to allow
235
// for better D&D bookmark organization
236
if (PlacesControllerDragHelper.getSession() &&
237
!PlacesControllerDragHelper.draggingOverChildNode(popup.parentNode)) {
238
popup.hidePopup();
239
// Close any parent menus that aren't being dragged over;
240
// otherwise they'll stay open because they couldn't close
241
// while this menu was being dragged over.
242
this.closeParentMenus();
243
}
244
this._closeMenuTimer = null;
245
}
246
},
247
248
// Helper function to close all parent menus of this menu,
249
// as long as none of the parent's children are currently being
250
// dragged over.
251
closeParentMenus: function OF__closeParentMenus() {
252
var popup = this._self;
253
var parent = popup.parentNode;
254
while (parent) {
255
if (parent.localName == "menupopup" && parent._placesNode) {
256
if (PlacesControllerDragHelper.draggingOverChildNode(parent.parentNode))
257
break;
258
parent.hidePopup();
259
}
260
parent = parent.parentNode;
261
}
262
},
263
264
// The mouse is no longer dragging over the stored menubutton.
265
// Close the menubutton, clear out drag styles, and clear all
266
// timers for opening/closing it.
267
clear: function OF__clear() {
268
if (this._folder.elt && this._folder.elt.lastElementChild) {
269
if (!this._folder.elt.lastElementChild.hasAttribute("dragover"))
270
this._folder.elt.lastElementChild.hidePopup();
271
// remove menuactive style
272
this._folder.elt.removeAttribute("_moz-menuactive");
273
this._folder.elt = null;
274
}
275
if (this._folder.openTimer) {
276
this._folder.openTimer.cancel();
277
this._folder.openTimer = null;
278
}
279
if (this._folder.closeTimer) {
280
this._folder.closeTimer.cancel();
281
this._folder.closeTimer = null;
282
}
283
},
284
})]]></field>
285
286
<method name="_cleanupDragDetails">
287
<body><![CDATA[
288
// Called on dragend and drop.
289
PlacesControllerDragHelper.currentDropTarget = null;
290
this._rootView._draggedElt = null;
291
this.removeAttribute("dragover");
292
this.removeAttribute("dragstart");
293
this._indicatorBar.hidden = true;
294
]]></body>
295
</method>
296
297
</implementation>
298
299
<handlers>
300
<handler event="DOMMenuItemActive"><![CDATA[
301
let elt = event.target;
302
if (elt.parentNode != this)
303
return;
304
305
if (this.AppConstants.platform === "macosx") {
306
// XXX: The following check is a temporary hack until bug 420033 is
307
// resolved.
308
let parentElt = elt.parent;
309
while (parentElt) {
310
if (parentElt.id == "bookmarksMenuPopup" ||
311
parentElt.id == "goPopup")
312
return;
313
314
parentElt = parentElt.parentNode;
315
}
316
}
317
318
if (window.XULBrowserWindow) {
319
let placesNode = elt._placesNode;
320
321
var linkURI;
322
if (placesNode && PlacesUtils.nodeIsURI(placesNode))
323
linkURI = placesNode.uri;
324
else if (elt.hasAttribute("targetURI"))
325
linkURI = elt.getAttribute("targetURI");
326
327
if (linkURI)
328
window.XULBrowserWindow.setOverLink(linkURI, null);
329
}
330
]]></handler>
331
332
<handler event="DOMMenuItemInactive"><![CDATA[
333
let elt = event.target;
334
if (elt.parentNode != this)
335
return;
336
337
if (window.XULBrowserWindow)
338
window.XULBrowserWindow.setOverLink("", null);
339
]]></handler>
340
341
<handler event="dragstart"><![CDATA[
342
let elt = event.target;
343
if (!elt._placesNode)
344
return;
345
346
let draggedElt = elt._placesNode;
347
348
// Force a copy action if parent node is a query or we are dragging a
349
// not-removable node.
350
if (!this._rootView.controller.canMoveNode(draggedElt))
351
event.dataTransfer.effectAllowed = "copyLink";
352
353
// Activate the view and cache the dragged element.
354
this._rootView._draggedElt = draggedElt;
355
this._rootView.controller.setDataTransfer(event);
356
this.setAttribute("dragstart", "true");
357
event.stopPropagation();
358
]]></handler>
359
360
<handler event="drop"><![CDATA[
361
PlacesControllerDragHelper.currentDropTarget = event.target;
362
363
let dropPoint = this._getDropPoint(event);
364
if (dropPoint && dropPoint.ip) {
365
PlacesControllerDragHelper.onDrop(dropPoint.ip, event.dataTransfer)
366
.catch(Cu.reportError);
367
event.preventDefault();
368
}
369
370
this._cleanupDragDetails();
371
event.stopPropagation();
372
]]></handler>
373
374
<handler event="dragover"><![CDATA[
375
PlacesControllerDragHelper.currentDropTarget = event.target;
376
let dt = event.dataTransfer;
377
378
let dropPoint = this._getDropPoint(event);
379
if (!dropPoint || !dropPoint.ip ||
380
!PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)) {
381
this._indicatorBar.hidden = true;
382
event.stopPropagation();
383
return;
384
}
385
386
// Mark this popup as being dragged over.
387
this.setAttribute("dragover", "true");
388
389
if (dropPoint.folderElt) {
390
// We are dragging over a folder.
391
// _overFolder should take the care of opening it on a timer.
392
if (this._overFolder.elt &&
393
this._overFolder.elt != dropPoint.folderElt) {
394
// We are dragging over a new folder, let's clear old values
395
this._overFolder.clear();
396
}
397
if (!this._overFolder.elt) {
398
this._overFolder.elt = dropPoint.folderElt;
399
// Create the timer to open this folder.
400
this._overFolder.openTimer = this._overFolder
401
.setTimer(this._overFolder.hoverTime);
402
}
403
// Since we are dropping into a folder set the corresponding style.
404
dropPoint.folderElt.setAttribute("_moz-menuactive", true);
405
} else {
406
// We are not dragging over a folder.
407
// Clear out old _overFolder information.
408
this._overFolder.clear();
409
}
410
411
// Autoscroll the popup strip if we drag over the scroll buttons.
412
let anonid = event.originalTarget.getAttribute("anonid");
413
let scrollDir = 0;
414
if (anonid == "scrollbutton-up") {
415
scrollDir = -1;
416
} else if (anonid == "scrollbutton-down") {
417
scrollDir = 1;
418
}
419
if (scrollDir != 0) {
420
this._scrollBox.scrollByIndex(scrollDir, true);
421
}
422
423
// Check if we should hide the drop indicator for this target.
424
if (dropPoint.folderElt || this._hideDropIndicator(event)) {
425
this._indicatorBar.hidden = true;
426
event.preventDefault();
427
event.stopPropagation();
428
return;
429
}
430
431
// We should display the drop indicator relative to the arrowscrollbox.
432
let scrollRect = this._scrollBox.getBoundingClientRect();
433
let newMarginTop = 0;
434
if (scrollDir == 0) {
435
let elt = this.firstElementChild;
436
while (elt && event.screenY > elt.screenY +
437
elt.getBoundingClientRect().height / 2)
438
elt = elt.nextElementSibling;
439
newMarginTop = elt ? elt.screenY - this._scrollBox.screenY :
440
scrollRect.height;
441
} else if (scrollDir == 1) {
442
newMarginTop = scrollRect.height;
443
}
444
445
// Set the new marginTop based on arrowscrollbox.
446
newMarginTop += scrollRect.y - this._scrollBox.getBoundingClientRect().y;
447
this._indicatorBar.firstElementChild.style.marginTop = newMarginTop + "px";
448
this._indicatorBar.hidden = false;
449
450
event.preventDefault();
451
event.stopPropagation();
452
]]></handler>
453
454
<handler event="dragexit"><![CDATA[
455
PlacesControllerDragHelper.currentDropTarget = null;
456
this.removeAttribute("dragover");
457
458
// If we have not moved to a valid new target clear the drop indicator
459
// this happens when moving out of the popup.
460
let target = event.relatedTarget;
461
if (!target || !this.contains(target))
462
this._indicatorBar.hidden = true;
463
464
// Close any folder being hovered over
465
if (this._overFolder.elt) {
466
this._overFolder.closeTimer = this._overFolder
467
.setTimer(this._overFolder.hoverTime);
468
}
469
470
// The autoopened attribute is set when this folder was automatically
471
// opened after the user dragged over it. If this attribute is set,
472
// auto-close the folder on drag exit.
473
// We should also try to close this popup if the drag has started
474
// from here, the timer will check if we are dragging over a child.
475
if (this.hasAttribute("autoopened") ||
476
this.hasAttribute("dragstart")) {
477
this._overFolder.closeMenuTimer = this._overFolder
478
.setTimer(this._overFolder.hoverTime);
479
}
480
481
event.stopPropagation();
482
]]></handler>
483
484
<handler event="dragend"><![CDATA[
485
this._cleanupDragDetails();
486
]]></handler>
487
488
</handlers>
489
</binding>
490
491
<!-- Most of this is copied from the arrowpanel binding in popup.xml -->
492
<binding id="places-popup-arrow"
494
<content flip="both" side="top" position="bottomcenter topright">
495
<xul:vbox anonid="container" class="panel-arrowcontainer" flex="1"
496
xbl:inherits="side,panelopen">
497
<xul:box anonid="arrowbox" class="panel-arrowbox">
498
<xul:image anonid="arrow" class="panel-arrow" xbl:inherits="side"/>
499
</xul:box>
500
<xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1">
501
<xul:vbox class="menupopup-drop-indicator-bar" hidden="true">
502
<xul:image class="menupopup-drop-indicator" mousethrough="always"/>
503
</xul:vbox>
504
<xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
505
smoothscroll="false">
506
<children/>
507
</xul:arrowscrollbox>
508
</xul:box>
509
</xul:vbox>
510
</content>
511
512
<implementation>
513
<constructor><![CDATA[
514
this.style.pointerEvents = "none";
515
]]></constructor>
516
<method name="adjustArrowPosition">
517
<body><![CDATA[
518
var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow");
519
520
var anchor = this.anchorNode;
521
if (!anchor) {
522
arrow.hidden = true;
523
return;
524
}
525
526
var container = document.getAnonymousElementByAttribute(this, "anonid", "container");
527
var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox");
528
529
var position = this.alignmentPosition;
530
var offset = this.alignmentOffset;
531
532
this.setAttribute("arrowposition", position);
533
534
// if this panel has a "sliding" arrow, we may have previously set margins...
535
arrowbox.style.removeProperty("transform");
536
if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
537
container.orient = "horizontal";
538
arrowbox.orient = "vertical";
539
if (position.indexOf("_after") > 0) {
540
arrowbox.pack = "end";
541
} else {
542
arrowbox.pack = "start";
543
}
544
arrowbox.style.transform = "translate(0, " + -offset + "px)";
545
546
// The assigned side stays the same regardless of direction.
547
let isRTL = this.matches(":-moz-locale-dir(rtl)");
548
549
if (position.indexOf("start_") == 0) {
550
container.dir = "reverse";
551
this.setAttribute("side", isRTL ? "left" : "right");
552
} else {
553
container.dir = "";
554
this.setAttribute("side", isRTL ? "right" : "left");
555
}
556
} else if (position.indexOf("before_") == 0 || position.indexOf("after_") == 0) {
557
container.orient = "";
558
arrowbox.orient = "";
559
if (position.indexOf("_end") > 0) {
560
arrowbox.pack = "end";
561
} else {
562
arrowbox.pack = "start";
563
}
564
arrowbox.style.transform = "translate(" + -offset + "px, 0)";
565
566
if (position.indexOf("before_") == 0) {
567
container.dir = "reverse";
568
this.setAttribute("side", "bottom");
569
} else {
570
container.dir = "";
571
this.setAttribute("side", "top");
572
}
573
}
574
575
arrow.hidden = false;
576
]]></body>
577
</method>
578
</implementation>
579
580
<handlers>
581
<handler event="popupshowing" phase="target"><![CDATA[
582
this.adjustArrowPosition();
583
this.setAttribute("animate", "open");
584
]]></handler>
585
<handler event="popupshown" phase="target"><![CDATA[
586
this.setAttribute("panelopen", "true");
587
let disablePointerEvents;
588
if (!this.hasAttribute("disablepointereventsfortransition")) {
589
let container = document.getAnonymousElementByAttribute(this, "anonid", "container");
590
let cs = getComputedStyle(container);
591
let transitionProp = cs.transitionProperty;
592
let transitionTime = parseFloat(cs.transitionDuration);
593
disablePointerEvents = (transitionProp.includes("transform") ||
594
transitionProp == "all") &&
595
transitionTime > 0;
596
this.setAttribute("disablepointereventsfortransition", disablePointerEvents);
597
} else {
598
disablePointerEvents = this.getAttribute("disablepointereventsfortransition") == "true";
599
}
600
if (!disablePointerEvents) {
601
this.style.removeProperty("pointer-events");
602
}
603
]]></handler>
604
<handler event="transitionend"><![CDATA[
605
if (event.originalTarget.getAttribute("anonid") == "container" &&
606
(event.propertyName == "transform" || event.propertyName == "-moz-window-transform")) {
607
this.style.removeProperty("pointer-events");
608
}
609
]]></handler>
610
<handler event="popuphiding" phase="target"><![CDATA[
611
this.setAttribute("animate", "cancel");
612
]]></handler>
613
<handler event="popuphidden" phase="target"><![CDATA[
614
this.removeAttribute("panelopen");
615
if (this.getAttribute("disablepointereventsfortransition") == "true") {
616
this.style.pointerEvents = "none";
617
}
618
this.removeAttribute("animate");
619
]]></handler>
620
</handlers>
621
</binding>
622
</bindings>