<?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="placesMenuBindings" xmlns="http://www.mozilla.org/xbl" xmlns:xbl="http://www.mozilla.org/xbl" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <binding id="places-popup-base" extends="chrome://global/content/bindings/popup.xml#popup"> <content> <xul:hbox flex="1"> <xul:vbox class="menupopup-drop-indicator-bar" hidden="true"> <xul:image class="menupopup-drop-indicator" mousethrough="always"/> </xul:vbox> <xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical" smoothscroll="false"> <children/> </xul:arrowscrollbox> </xul:hbox> </content> <implementation> <field name="AppConstants" readonly="true"> (Components.utils.import("resource://gre/modules/AppConstants.jsm", {})).AppConstants; </field> <field name="_indicatorBar"> document.getAnonymousElementByAttribute(this, "class", "menupopup-drop-indicator-bar"); </field> <field name="_scrollBox"> document.getAnonymousElementByAttribute(this, "class", "popup-internal-box"); </field> <!-- This is the view that manage the popup --> <field name="_rootView">PlacesUIUtils.getViewForNode(this);</field> <!-- Check if we should hide the drop indicator for the target --> <method name="_hideDropIndicator"> <parameter name="aEvent"/> <body><![CDATA[ let target = aEvent.target; // Don't draw the drop indicator outside of markers or if current // node is not a Places node. let betweenMarkers = (this._startMarker.compareDocumentPosition(target) & Node.DOCUMENT_POSITION_FOLLOWING) && (this._endMarker.compareDocumentPosition(target) & Node.DOCUMENT_POSITION_PRECEDING); // Hide the dropmarker if current node is not a Places node. return !(target && target._placesNode && betweenMarkers); ]]></body> </method> <!-- This function returns information about where to drop when dragging over this popup insertion point --> <method name="_getDropPoint"> <parameter name="aEvent"/> <body><![CDATA[ // Can't drop if the menu isn't a folder let resultNode = this._placesNode; if (!PlacesUtils.nodeIsFolder(resultNode) || PlacesControllerDragHelper.disallowInsertion(resultNode)) { return null; } var dropPoint = { ip: null, folderElt: null }; // The element we are dragging over let elt = aEvent.target; if (elt.localName == "menupopup") elt = elt.parentNode; // Calculate positions taking care of arrowscrollbox let scrollbox = this._scrollBox; let eventY = aEvent.layerY + (scrollbox.boxObject.y - this.boxObject.y); let scrollboxOffset = scrollbox.scrollBoxObject.y - (scrollbox.boxObject.y - this.boxObject.y); let eltY = elt.boxObject.y - scrollboxOffset; let eltHeight = elt.boxObject.height; if (!elt._placesNode) { // If we are dragging over a non places node drop at the end. dropPoint.ip = new InsertionPoint( PlacesUtils.getConcreteItemId(resultNode), -1, Ci.nsITreeView.DROP_ON); // We can set folderElt if we are dropping over a static menu that // has an internal placespopup. let isMenu = elt.localName == "menu" || (elt.localName == "toolbarbutton" && elt.getAttribute("type") == "menu"); if (isMenu && elt.lastChild && elt.lastChild.hasAttribute("placespopup")) dropPoint.folderElt = elt; return dropPoint; } let tagName = PlacesUtils.nodeIsTagQuery(elt._placesNode) ? elt._placesNode.title : null; if ((PlacesUtils.nodeIsFolder(elt._placesNode) && !PlacesUIUtils.isContentsReadOnly(elt._placesNode)) || PlacesUtils.nodeIsTagQuery(elt._placesNode)) { // This is a folder or a tag container. if (eventY - eltY < eltHeight * 0.20) { // If mouse is in the top part of the element, drop above folder. dropPoint.ip = new InsertionPoint( PlacesUtils.getConcreteItemId(resultNode), -1, Ci.nsITreeView.DROP_BEFORE, tagName, elt._placesNode.itemId); return dropPoint; } else if (eventY - eltY < eltHeight * 0.80) { // If mouse is in the middle of the element, drop inside folder. dropPoint.ip = new InsertionPoint( PlacesUtils.getConcreteItemId(elt._placesNode), -1, Ci.nsITreeView.DROP_ON, tagName); dropPoint.folderElt = elt; return dropPoint; } } else if (eventY - eltY <= eltHeight / 2) { // This is a non-folder node or a readonly folder. // If the mouse is above the middle, drop above this item. dropPoint.ip = new InsertionPoint( PlacesUtils.getConcreteItemId(resultNode), -1, Ci.nsITreeView.DROP_BEFORE, tagName, elt._placesNode.itemId); return dropPoint; } // Drop below the item. dropPoint.ip = new InsertionPoint( PlacesUtils.getConcreteItemId(resultNode), -1, Ci.nsITreeView.DROP_AFTER, tagName, elt._placesNode.itemId); return dropPoint; ]]></body> </method> <!-- Sub-menus should be opened when the mouse drags over them, and closed when the mouse drags off. The overFolder object manages opening and closing of folders when the mouse hovers. --> <field name="_overFolder"><![CDATA[({ _self: this, _folder: {elt: null, openTimer: null, hoverTime: 350, closeTimer: null}, _closeMenuTimer: null, get elt() { return this._folder.elt; }, set elt(val) { return this._folder.elt = val; }, get openTimer() { return this._folder.openTimer; }, set openTimer(val) { return this._folder.openTimer = val; }, get hoverTime() { return this._folder.hoverTime; }, set hoverTime(val) { return this._folder.hoverTime = val; }, get closeTimer() { return this._folder.closeTimer; }, set closeTimer(val) { return this._folder.closeTimer = val; }, get closeMenuTimer() { return this._closeMenuTimer; }, set closeMenuTimer(val) { return this._closeMenuTimer = val; }, setTimer: function OF__setTimer(aTime) { var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT); return timer; }, notify: function OF__notify(aTimer) { // Function to process all timer notifications. if (aTimer == this._folder.openTimer) { // Timer to open a submenu that's being dragged over. this._folder.elt.lastChild.setAttribute("autoopened", "true"); this._folder.elt.lastChild.showPopup(this._folder.elt); this._folder.openTimer = null; } else if (aTimer == this._folder.closeTimer) { // Timer to close a submenu that's been dragged off of. // Only close the submenu if the mouse isn't being dragged over any // of its child menus. var draggingOverChild = PlacesControllerDragHelper .draggingOverChildNode(this._folder.elt); if (draggingOverChild) this._folder.elt = null; this.clear(); // Close any parent folders which aren't being dragged over. // (This is necessary because of the above code that keeps a folder // open while its children are being dragged over.) if (!draggingOverChild) this.closeParentMenus(); } else if (aTimer == this.closeMenuTimer) { // Timer to close this menu after the drag exit. var popup = this._self; // if we are no more dragging we can leave the menu open to allow // for better D&D bookmark organization if (PlacesControllerDragHelper.getSession() && !PlacesControllerDragHelper.draggingOverChildNode(popup.parentNode)) { popup.hidePopup(); // Close any parent menus that aren't being dragged over; // otherwise they'll stay open because they couldn't close // while this menu was being dragged over. this.closeParentMenus(); } this._closeMenuTimer = null; } }, // Helper function to close all parent menus of this menu, // as long as none of the parent's children are currently being // dragged over. closeParentMenus: function OF__closeParentMenus() { var popup = this._self; var parent = popup.parentNode; while (parent) { if (parent.localName == "menupopup" && parent._placesNode) { if (PlacesControllerDragHelper.draggingOverChildNode(parent.parentNode)) break; parent.hidePopup(); } parent = parent.parentNode; } }, // The mouse is no longer dragging over the stored menubutton. // Close the menubutton, clear out drag styles, and clear all // timers for opening/closing it. clear: function OF__clear() { if (this._folder.elt && this._folder.elt.lastChild) { if (!this._folder.elt.lastChild.hasAttribute("dragover")) this._folder.elt.lastChild.hidePopup(); // remove menuactive style this._folder.elt.removeAttribute("_moz-menuactive"); this._folder.elt = null; } if (this._folder.openTimer) { this._folder.openTimer.cancel(); this._folder.openTimer = null; } if (this._folder.closeTimer) { this._folder.closeTimer.cancel(); this._folder.closeTimer = null; } } })]]></field> <method name="_cleanupDragDetails"> <body><![CDATA[ // Called on dragend and drop. PlacesControllerDragHelper.currentDropTarget = null; this._rootView._draggedElt = null; this.removeAttribute("dragover"); this.removeAttribute("dragstart"); this._indicatorBar.hidden = true; ]]></body> </method> </implementation> <handlers> <handler event="DOMMenuItemActive"><![CDATA[ let elt = event.target; if (elt.parentNode != this) return; if (this.AppConstants.platform === "macosx") { // XXX: The following check is a temporary hack until bug 420033 is // resolved. let parentElt = elt.parent; while (parentElt) { if (parentElt.id == "bookmarksMenuPopup" || parentElt.id == "goPopup") return; parentElt = parentElt.parentNode; } } if (window.XULBrowserWindow) { let elt = event.target; let placesNode = elt._placesNode; var linkURI; if (placesNode && PlacesUtils.nodeIsURI(placesNode)) linkURI = placesNode.uri; else if (elt.hasAttribute("targetURI")) linkURI = elt.getAttribute("targetURI"); if (linkURI) window.XULBrowserWindow.setOverLink(linkURI, null); } ]]></handler> <handler event="DOMMenuItemInactive"><![CDATA[ let elt = event.target; if (elt.parentNode != this) return; if (window.XULBrowserWindow) window.XULBrowserWindow.setOverLink("", null); ]]></handler> <handler event="dragstart"><![CDATA[ let elt = event.target; if (!elt._placesNode) return; let draggedElt = elt._placesNode; // Force a copy action if parent node is a query or we are dragging a // not-removable node. if (!PlacesControllerDragHelper.canMoveNode(draggedElt, elt)) event.dataTransfer.effectAllowed = "copyLink"; // Activate the view and cache the dragged element. this._rootView._draggedElt = draggedElt; this._rootView.controller.setDataTransfer(event); this.setAttribute("dragstart", "true"); event.stopPropagation(); ]]></handler> <handler event="drop"><![CDATA[ PlacesControllerDragHelper.currentDropTarget = event.target; let dropPoint = this._getDropPoint(event); if (dropPoint && dropPoint.ip) { PlacesControllerDragHelper.onDrop(dropPoint.ip, event.dataTransfer) .then(null, Components.utils.reportError); event.preventDefault(); } this._cleanupDragDetails(); event.stopPropagation(); ]]></handler> <handler event="dragover"><![CDATA[ PlacesControllerDragHelper.currentDropTarget = event.target; let dt = event.dataTransfer; let dropPoint = this._getDropPoint(event); if (!dropPoint || !dropPoint.ip || !PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)) { this._indicatorBar.hidden = true; event.stopPropagation(); return; } // Mark this popup as being dragged over. this.setAttribute("dragover", "true"); if (dropPoint.folderElt) { // We are dragging over a folder. // _overFolder should take the care of opening it on a timer. if (this._overFolder.elt && this._overFolder.elt != dropPoint.folderElt) { // We are dragging over a new folder, let's clear old values this._overFolder.clear(); } if (!this._overFolder.elt) { this._overFolder.elt = dropPoint.folderElt; // Create the timer to open this folder. this._overFolder.openTimer = this._overFolder .setTimer(this._overFolder.hoverTime); } // Since we are dropping into a folder set the corresponding style. dropPoint.folderElt.setAttribute("_moz-menuactive", true); } else { // We are not dragging over a folder. // Clear out old _overFolder information. this._overFolder.clear(); } // Autoscroll the popup strip if we drag over the scroll buttons. let anonid = event.originalTarget.getAttribute('anonid'); let scrollDir = 0; if (anonid == "scrollbutton-up") { scrollDir = -1; } else if (anonid == "scrollbutton-down") { scrollDir = 1; } if (scrollDir != 0) { this._scrollBox.scrollByIndex(scrollDir, false); } // Check if we should hide the drop indicator for this target. if (dropPoint.folderElt || this._hideDropIndicator(event)) { this._indicatorBar.hidden = true; event.preventDefault(); event.stopPropagation(); return; } // We should display the drop indicator relative to the arrowscrollbox. let sbo = this._scrollBox.scrollBoxObject; let newMarginTop = 0; if (scrollDir == 0) { let elt = this.firstChild; while (elt && event.screenY > elt.boxObject.screenY + elt.boxObject.height / 2) elt = elt.nextSibling; newMarginTop = elt ? elt.boxObject.screenY - sbo.screenY : sbo.height; } else if (scrollDir == 1) newMarginTop = sbo.height; // Set the new marginTop based on arrowscrollbox. newMarginTop += sbo.y - this._scrollBox.boxObject.y; this._indicatorBar.firstChild.style.marginTop = newMarginTop + "px"; this._indicatorBar.hidden = false; event.preventDefault(); event.stopPropagation(); ]]></handler> <handler event="dragexit"><![CDATA[ PlacesControllerDragHelper.currentDropTarget = null; this.removeAttribute("dragover"); // If we have not moved to a valid new target clear the drop indicator // this happens when moving out of the popup. let target = event.relatedTarget; if (!target || !this.contains(target)) this._indicatorBar.hidden = true; // Close any folder being hovered over if (this._overFolder.elt) { this._overFolder.closeTimer = this._overFolder .setTimer(this._overFolder.hoverTime); } // The autoopened attribute is set when this folder was automatically // opened after the user dragged over it. If this attribute is set, // auto-close the folder on drag exit. // We should also try to close this popup if the drag has started // from here, the timer will check if we are dragging over a child. if (this.hasAttribute("autoopened") || this.hasAttribute("dragstart")) { this._overFolder.closeMenuTimer = this._overFolder .setTimer(this._overFolder.hoverTime); } event.stopPropagation(); ]]></handler> <handler event="dragend"><![CDATA[ this._cleanupDragDetails(); ]]></handler> </handlers> </binding> <!-- Most of this is copied from the arrowpanel binding in popup.xml --> <binding id="places-popup-arrow" extends="chrome://browser/content/places/menu.xml#places-popup-base"> <content flip="both" side="top" position="bottomcenter topright"> <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"> <xul:vbox class="menupopup-drop-indicator-bar" hidden="true"> <xul:image class="menupopup-drop-indicator" mousethrough="always"/> </xul:vbox> <xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical" smoothscroll="false"> <children/> </xul:arrowscrollbox> </xul:box> </xul:vbox> </content> <implementation> <constructor><![CDATA[ this.style.pointerEvents = 'none'; ]]></constructor> <method name="adjustArrowPosition"> <body><![CDATA[ var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow"); var anchor = this.anchorNode; if (!anchor) { arrow.hidden = true; 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 this panel has a "sliding" arrow, we may have previously set margins... arrowbox.style.removeProperty("transform"); 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"); } } arrow.hidden = false; ]]></body> </method> </implementation> <handlers> <handler event="popupshowing" phase="target"><![CDATA[ this.adjustArrowPosition(); this.setAttribute("animate", "open"); ]]></handler> <handler event="popupshown" phase="target"><![CDATA[ this.setAttribute("panelopen", "true"); let disablePointerEvents; if (!this.hasAttribute("disablepointereventsfortransition")) { let container = document.getAnonymousElementByAttribute(this, "anonid", "container"); let cs = getComputedStyle(container); let transitionProp = cs.transitionProperty; let transitionTime = parseFloat(cs.transitionDuration); disablePointerEvents = (transitionProp.includes("transform") || transitionProp == "all") && transitionTime > 0; this.setAttribute("disablepointereventsfortransition", disablePointerEvents); } else { disablePointerEvents = this.getAttribute("disablepointereventsfortransition") == "true"; } if (!disablePointerEvents) { this.style.removeProperty("pointer-events"); } ]]></handler> <handler event="transitionend"><![CDATA[ if (event.originalTarget.getAttribute("anonid") == "container" && event.propertyName == "transform") { this.style.removeProperty("pointer-events"); } ]]></handler> <handler event="popuphiding" phase="target"><![CDATA[ this.setAttribute("animate", "cancel"); ]]></handler> <handler event="popuphidden" phase="target"><![CDATA[ this.removeAttribute("panelopen"); if (this.getAttribute("disablepointereventsfortransition") == "true") { this.style.pointerEvents = 'none'; } this.removeAttribute("animate"); ]]></handler> </handlers> </binding> </bindings>