michael@0: #ifdef 0 michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #endif michael@0: michael@0: /** michael@0: * This singleton implements site dragging functionality. michael@0: */ michael@0: let gDrag = { michael@0: /** michael@0: * The site offset to the drag start point. michael@0: */ michael@0: _offsetX: null, michael@0: _offsetY: null, michael@0: michael@0: /** michael@0: * The site that is dragged. michael@0: */ michael@0: _draggedSite: null, michael@0: get draggedSite() this._draggedSite, michael@0: michael@0: /** michael@0: * The cell width/height at the point the drag started. michael@0: */ michael@0: _cellWidth: null, michael@0: _cellHeight: null, michael@0: get cellWidth() this._cellWidth, michael@0: get cellHeight() this._cellHeight, michael@0: michael@0: /** michael@0: * Start a new drag operation. michael@0: * @param aSite The site that's being dragged. michael@0: * @param aEvent The 'dragstart' event. michael@0: */ michael@0: start: function Drag_start(aSite, aEvent) { michael@0: this._draggedSite = aSite; michael@0: michael@0: // Mark nodes as being dragged. michael@0: let selector = ".newtab-site, .newtab-control, .newtab-thumbnail"; michael@0: let parentCell = aSite.node.parentNode; michael@0: let nodes = parentCell.querySelectorAll(selector); michael@0: for (let i = 0; i < nodes.length; i++) michael@0: nodes[i].setAttribute("dragged", "true"); michael@0: michael@0: parentCell.setAttribute("dragged", "true"); michael@0: michael@0: this._setDragData(aSite, aEvent); michael@0: michael@0: // Store the cursor offset. michael@0: let node = aSite.node; michael@0: let rect = node.getBoundingClientRect(); michael@0: this._offsetX = aEvent.clientX - rect.left; michael@0: this._offsetY = aEvent.clientY - rect.top; michael@0: michael@0: // Store the cell dimensions. michael@0: let cellNode = aSite.cell.node; michael@0: this._cellWidth = cellNode.offsetWidth; michael@0: this._cellHeight = cellNode.offsetHeight; michael@0: michael@0: gTransformation.freezeSitePosition(aSite); michael@0: }, michael@0: michael@0: /** michael@0: * Handles the 'drag' event. michael@0: * @param aSite The site that's being dragged. michael@0: * @param aEvent The 'drag' event. michael@0: */ michael@0: drag: function Drag_drag(aSite, aEvent) { michael@0: // Get the viewport size. michael@0: let {clientWidth, clientHeight} = document.documentElement; michael@0: michael@0: // We'll want a padding of 5px. michael@0: let border = 5; michael@0: michael@0: // Enforce minimum constraints to keep the drag image inside the window. michael@0: let left = Math.max(scrollX + aEvent.clientX - this._offsetX, border); michael@0: let top = Math.max(scrollY + aEvent.clientY - this._offsetY, border); michael@0: michael@0: // Enforce maximum constraints to keep the drag image inside the window. michael@0: left = Math.min(left, scrollX + clientWidth - this.cellWidth - border); michael@0: top = Math.min(top, scrollY + clientHeight - this.cellHeight - border); michael@0: michael@0: // Update the drag image's position. michael@0: gTransformation.setSitePosition(aSite, {left: left, top: top}); michael@0: }, michael@0: michael@0: /** michael@0: * Ends the current drag operation. michael@0: * @param aSite The site that's being dragged. michael@0: * @param aEvent The 'dragend' event. michael@0: */ michael@0: end: function Drag_end(aSite, aEvent) { michael@0: let nodes = gGrid.node.querySelectorAll("[dragged]") michael@0: for (let i = 0; i < nodes.length; i++) michael@0: nodes[i].removeAttribute("dragged"); michael@0: michael@0: // Slide the dragged site back into its cell (may be the old or the new cell). michael@0: gTransformation.slideSiteTo(aSite, aSite.cell, {unfreeze: true}); michael@0: michael@0: this._draggedSite = null; michael@0: }, michael@0: michael@0: /** michael@0: * Checks whether we're responsible for a given drag event. michael@0: * @param aEvent The drag event to check. michael@0: * @return Whether we should handle this drag and drop operation. michael@0: */ michael@0: isValid: function Drag_isValid(aEvent) { michael@0: let link = gDragDataHelper.getLinkFromDragEvent(aEvent); michael@0: michael@0: // Check that the drag data is non-empty. michael@0: // Can happen when dragging places folders. michael@0: if (!link || !link.url) { michael@0: return false; michael@0: } michael@0: michael@0: // Check that we're not accepting URLs which would inherit the caller's michael@0: // principal (such as javascript: or data:). michael@0: return gLinkChecker.checkLoadURI(link.url); michael@0: }, michael@0: michael@0: /** michael@0: * Initializes the drag data for the current drag operation. michael@0: * @param aSite The site that's being dragged. michael@0: * @param aEvent The 'dragstart' event. michael@0: */ michael@0: _setDragData: function Drag_setDragData(aSite, aEvent) { michael@0: let {url, title} = aSite; michael@0: michael@0: let dt = aEvent.dataTransfer; michael@0: dt.mozCursor = "default"; michael@0: dt.effectAllowed = "move"; michael@0: dt.setData("text/plain", url); michael@0: dt.setData("text/uri-list", url); michael@0: dt.setData("text/x-moz-url", url + "\n" + title); michael@0: dt.setData("text/html", "" + url + ""); michael@0: michael@0: // Create and use an empty drag element. We don't want to use the default michael@0: // drag image with its default opacity. michael@0: let dragElement = document.createElementNS(HTML_NAMESPACE, "div"); michael@0: dragElement.classList.add("newtab-drag"); michael@0: let scrollbox = document.getElementById("newtab-scrollbox"); michael@0: scrollbox.appendChild(dragElement); michael@0: dt.setDragImage(dragElement, 0, 0); michael@0: michael@0: // After the 'dragstart' event has been processed we can remove the michael@0: // temporary drag element from the DOM. michael@0: setTimeout(function () scrollbox.removeChild(dragElement), 0); michael@0: } michael@0: };