dropTargetShim.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. #ifdef 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 file,
  4. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #endif
  6. /**
  7. * This singleton provides a custom drop target detection. We need this because
  8. * the default DnD target detection relies on the cursor's position. We want
  9. * to pick a drop target based on the dragged site's position.
  10. */
  11. var gDropTargetShim = {
  12. /**
  13. * Cache for the position of all cells, cleaned after drag finished.
  14. */
  15. _cellPositions: null,
  16. /**
  17. * The last drop target that was hovered.
  18. */
  19. _lastDropTarget: null,
  20. /**
  21. * Initializes the drop target shim.
  22. */
  23. init: function() {
  24. gGrid.node.addEventListener("dragstart", this, true);
  25. },
  26. /**
  27. * Add all event listeners needed during a drag operation.
  28. */
  29. _addEventListeners: function() {
  30. gGrid.node.addEventListener("dragend", this);
  31. let docElement = document.documentElement;
  32. docElement.addEventListener("dragover", this);
  33. docElement.addEventListener("dragenter", this);
  34. docElement.addEventListener("drop", this);
  35. },
  36. /**
  37. * Remove all event listeners that were needed during a drag operation.
  38. */
  39. _removeEventListeners: function() {
  40. gGrid.node.removeEventListener("dragend", this);
  41. let docElement = document.documentElement;
  42. docElement.removeEventListener("dragover", this);
  43. docElement.removeEventListener("dragenter", this);
  44. docElement.removeEventListener("drop", this);
  45. },
  46. /**
  47. * Handles all shim events.
  48. */
  49. handleEvent: function(aEvent) {
  50. switch (aEvent.type) {
  51. case "dragstart":
  52. this._dragstart(aEvent);
  53. break;
  54. case "dragenter":
  55. aEvent.preventDefault();
  56. break;
  57. case "dragover":
  58. this._dragover(aEvent);
  59. break;
  60. case "drop":
  61. this._drop(aEvent);
  62. break;
  63. case "dragend":
  64. this._dragend(aEvent);
  65. break;
  66. }
  67. },
  68. /**
  69. * Handles the 'dragstart' event.
  70. * @param aEvent The 'dragstart' event.
  71. */
  72. _dragstart: function(aEvent) {
  73. if (aEvent.target.classList.contains("newtab-link")) {
  74. gGrid.lock();
  75. this._addEventListeners();
  76. }
  77. },
  78. /**
  79. * Handles the 'dragover' event.
  80. * @param aEvent The 'dragover' event.
  81. */
  82. _dragover: function(aEvent) {
  83. // XXX bug 505521 - Use the dragover event to retrieve the
  84. // current mouse coordinates while dragging.
  85. let sourceNode = aEvent.dataTransfer.mozSourceNode.parentNode;
  86. gDrag.drag(sourceNode._newtabSite, aEvent);
  87. // Find the current drop target, if there's one.
  88. this._updateDropTarget(aEvent);
  89. // If we have a valid drop target,
  90. // let the drag-and-drop service know.
  91. if (this._lastDropTarget) {
  92. aEvent.preventDefault();
  93. }
  94. },
  95. /**
  96. * Handles the 'drop' event.
  97. * @param aEvent The 'drop' event.
  98. */
  99. _drop: function(aEvent) {
  100. // We're accepting all drops.
  101. aEvent.preventDefault();
  102. // remember that drop event was seen, this explicitly
  103. // assumes that drop event preceeds dragend event
  104. this._dropSeen = true;
  105. // Make sure to determine the current drop target
  106. // in case the dragover event hasn't been fired.
  107. this._updateDropTarget(aEvent);
  108. // A site was successfully dropped.
  109. this._dispatchEvent(aEvent, "drop", this._lastDropTarget);
  110. },
  111. /**
  112. * Handles the 'dragend' event.
  113. * @param aEvent The 'dragend' event.
  114. */
  115. _dragend: function(aEvent) {
  116. if (this._lastDropTarget) {
  117. if (aEvent.dataTransfer.mozUserCancelled || !this._dropSeen) {
  118. // The drag operation was cancelled or no drop event was generated
  119. this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
  120. this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
  121. }
  122. // Clean up.
  123. this._lastDropTarget = null;
  124. this._cellPositions = null;
  125. }
  126. this._dropSeen = false;
  127. gGrid.unlock();
  128. this._removeEventListeners();
  129. },
  130. /**
  131. * Tries to find the current drop target and will fire
  132. * appropriate dragenter, dragexit, and dragleave events.
  133. * @param aEvent The current drag event.
  134. */
  135. _updateDropTarget: function(aEvent) {
  136. // Let's see if we find a drop target.
  137. let target = this._findDropTarget(aEvent);
  138. if (target != this._lastDropTarget) {
  139. if (this._lastDropTarget)
  140. // We left the last drop target.
  141. this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
  142. if (target)
  143. // We're now hovering a (new) drop target.
  144. this._dispatchEvent(aEvent, "dragenter", target);
  145. if (this._lastDropTarget)
  146. // We left the last drop target.
  147. this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
  148. this._lastDropTarget = target;
  149. }
  150. },
  151. /**
  152. * Determines the current drop target by matching the dragged site's position
  153. * against all cells in the grid.
  154. * @return The currently hovered drop target or null.
  155. */
  156. _findDropTarget: function() {
  157. // These are the minimum intersection values - we want to use the cell if
  158. // the site is >= 50% hovering its position.
  159. let minWidth = gDrag.cellWidth / 2;
  160. let minHeight = gDrag.cellHeight / 2;
  161. let cellPositions = this._getCellPositions();
  162. let rect = gTransformation.getNodePosition(gDrag.draggedSite.node);
  163. // Compare each cell's position to the dragged site's position.
  164. for (let i = 0; i < cellPositions.length; i++) {
  165. let inter = rect.intersect(cellPositions[i].rect);
  166. // If the intersection is big enough we found a drop target.
  167. if (inter.width >= minWidth && inter.height >= minHeight)
  168. return cellPositions[i].cell;
  169. }
  170. // No drop target found.
  171. return null;
  172. },
  173. /**
  174. * Gets the positions of all cell nodes.
  175. * @return The (cached) cell positions.
  176. */
  177. _getCellPositions: function() {
  178. if (this._cellPositions)
  179. return this._cellPositions;
  180. return this._cellPositions = gGrid.cells.map(function(cell) {
  181. return {cell: cell, rect: gTransformation.getNodePosition(cell.node)};
  182. });
  183. },
  184. /**
  185. * Dispatches a custom DragEvent on the given target node.
  186. * @param aEvent The source event.
  187. * @param aType The event type.
  188. * @param aTarget The target node that receives the event.
  189. */
  190. _dispatchEvent: function(aEvent, aType, aTarget) {
  191. let node = aTarget.node;
  192. let event = document.createEvent("DragEvent");
  193. // The event should not bubble to prevent recursion.
  194. event.initDragEvent(aType, false, true, window, 0, 0, 0, 0, 0, false, false,
  195. false, false, 0, node, aEvent.dataTransfer);
  196. node.dispatchEvent(event);
  197. }
  198. };