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 provides a custom drop target detection. We need this because michael@0: * the default DnD target detection relies on the cursor's position. We want michael@0: * to pick a drop target based on the dragged site's position. michael@0: */ michael@0: let gDropTargetShim = { michael@0: /** michael@0: * Cache for the position of all cells, cleaned after drag finished. michael@0: */ michael@0: _cellPositions: null, michael@0: michael@0: /** michael@0: * The last drop target that was hovered. michael@0: */ michael@0: _lastDropTarget: null, michael@0: michael@0: /** michael@0: * Initializes the drop target shim. michael@0: */ michael@0: init: function () { michael@0: gGrid.node.addEventListener("dragstart", this, true); michael@0: }, michael@0: michael@0: /** michael@0: * Add all event listeners needed during a drag operation. michael@0: */ michael@0: _addEventListeners: function () { michael@0: gGrid.node.addEventListener("dragend", this); michael@0: michael@0: let docElement = document.documentElement; michael@0: docElement.addEventListener("dragover", this); michael@0: docElement.addEventListener("dragenter", this); michael@0: docElement.addEventListener("drop", this); michael@0: }, michael@0: michael@0: /** michael@0: * Remove all event listeners that were needed during a drag operation. michael@0: */ michael@0: _removeEventListeners: function () { michael@0: gGrid.node.removeEventListener("dragend", this); michael@0: michael@0: let docElement = document.documentElement; michael@0: docElement.removeEventListener("dragover", this); michael@0: docElement.removeEventListener("dragenter", this); michael@0: docElement.removeEventListener("drop", this); michael@0: }, michael@0: michael@0: /** michael@0: * Handles all shim events. michael@0: */ michael@0: handleEvent: function (aEvent) { michael@0: switch (aEvent.type) { michael@0: case "dragstart": michael@0: this._dragstart(aEvent); michael@0: break; michael@0: case "dragenter": michael@0: aEvent.preventDefault(); michael@0: break; michael@0: case "dragover": michael@0: this._dragover(aEvent); michael@0: break; michael@0: case "drop": michael@0: this._drop(aEvent); michael@0: break; michael@0: case "dragend": michael@0: this._dragend(aEvent); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Handles the 'dragstart' event. michael@0: * @param aEvent The 'dragstart' event. michael@0: */ michael@0: _dragstart: function (aEvent) { michael@0: if (aEvent.target.classList.contains("newtab-link")) { michael@0: gGrid.lock(); michael@0: this._addEventListeners(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Handles the 'dragover' event. michael@0: * @param aEvent The 'dragover' event. michael@0: */ michael@0: _dragover: function (aEvent) { michael@0: // XXX bug 505521 - Use the dragover event to retrieve the michael@0: // current mouse coordinates while dragging. michael@0: let sourceNode = aEvent.dataTransfer.mozSourceNode.parentNode; michael@0: gDrag.drag(sourceNode._newtabSite, aEvent); michael@0: michael@0: // Find the current drop target, if there's one. michael@0: this._updateDropTarget(aEvent); michael@0: michael@0: // If we have a valid drop target, michael@0: // let the drag-and-drop service know. michael@0: if (this._lastDropTarget) { michael@0: aEvent.preventDefault(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Handles the 'drop' event. michael@0: * @param aEvent The 'drop' event. michael@0: */ michael@0: _drop: function (aEvent) { michael@0: // We're accepting all drops. michael@0: aEvent.preventDefault(); michael@0: michael@0: // Make sure to determine the current drop target michael@0: // in case the dragover event hasn't been fired. michael@0: this._updateDropTarget(aEvent); michael@0: michael@0: // A site was successfully dropped. michael@0: this._dispatchEvent(aEvent, "drop", this._lastDropTarget); michael@0: }, michael@0: michael@0: /** michael@0: * Handles the 'dragend' event. michael@0: * @param aEvent The 'dragend' event. michael@0: */ michael@0: _dragend: function (aEvent) { michael@0: if (this._lastDropTarget) { michael@0: if (aEvent.dataTransfer.mozUserCancelled) { michael@0: // The drag operation was cancelled. michael@0: this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget); michael@0: this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget); michael@0: } michael@0: michael@0: // Clean up. michael@0: this._lastDropTarget = null; michael@0: this._cellPositions = null; michael@0: } michael@0: michael@0: gGrid.unlock(); michael@0: this._removeEventListeners(); michael@0: }, michael@0: michael@0: /** michael@0: * Tries to find the current drop target and will fire michael@0: * appropriate dragenter, dragexit, and dragleave events. michael@0: * @param aEvent The current drag event. michael@0: */ michael@0: _updateDropTarget: function (aEvent) { michael@0: // Let's see if we find a drop target. michael@0: let target = this._findDropTarget(aEvent); michael@0: michael@0: if (target != this._lastDropTarget) { michael@0: if (this._lastDropTarget) michael@0: // We left the last drop target. michael@0: this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget); michael@0: michael@0: if (target) michael@0: // We're now hovering a (new) drop target. michael@0: this._dispatchEvent(aEvent, "dragenter", target); michael@0: michael@0: if (this._lastDropTarget) michael@0: // We left the last drop target. michael@0: this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget); michael@0: michael@0: this._lastDropTarget = target; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Determines the current drop target by matching the dragged site's position michael@0: * against all cells in the grid. michael@0: * @return The currently hovered drop target or null. michael@0: */ michael@0: _findDropTarget: function () { michael@0: // These are the minimum intersection values - we want to use the cell if michael@0: // the site is >= 50% hovering its position. michael@0: let minWidth = gDrag.cellWidth / 2; michael@0: let minHeight = gDrag.cellHeight / 2; michael@0: michael@0: let cellPositions = this._getCellPositions(); michael@0: let rect = gTransformation.getNodePosition(gDrag.draggedSite.node); michael@0: michael@0: // Compare each cell's position to the dragged site's position. michael@0: for (let i = 0; i < cellPositions.length; i++) { michael@0: let inter = rect.intersect(cellPositions[i].rect); michael@0: michael@0: // If the intersection is big enough we found a drop target. michael@0: if (inter.width >= minWidth && inter.height >= minHeight) michael@0: return cellPositions[i].cell; michael@0: } michael@0: michael@0: // No drop target found. michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Gets the positions of all cell nodes. michael@0: * @return The (cached) cell positions. michael@0: */ michael@0: _getCellPositions: function DropTargetShim_getCellPositions() { michael@0: if (this._cellPositions) michael@0: return this._cellPositions; michael@0: michael@0: return this._cellPositions = gGrid.cells.map(function (cell) { michael@0: return {cell: cell, rect: gTransformation.getNodePosition(cell.node)}; michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Dispatches a custom DragEvent on the given target node. michael@0: * @param aEvent The source event. michael@0: * @param aType The event type. michael@0: * @param aTarget The target node that receives the event. michael@0: */ michael@0: _dispatchEvent: function (aEvent, aType, aTarget) { michael@0: let node = aTarget.node; michael@0: let event = document.createEvent("DragEvents"); michael@0: michael@0: // The event should not bubble to prevent recursion. michael@0: event.initDragEvent(aType, false, true, window, 0, 0, 0, 0, 0, false, false, michael@0: false, false, 0, node, aEvent.dataTransfer); michael@0: michael@0: node.dispatchEvent(event); michael@0: } michael@0: };