Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | Components.utils.import("resource:///modules/CustomizableUI.jsm"); |
michael@0 | 8 | |
michael@0 | 9 | let gManagers = new WeakMap(); |
michael@0 | 10 | |
michael@0 | 11 | const kPaletteId = "customization-palette"; |
michael@0 | 12 | const kPlaceholderClass = "panel-customization-placeholder"; |
michael@0 | 13 | |
michael@0 | 14 | this.EXPORTED_SYMBOLS = ["DragPositionManager"]; |
michael@0 | 15 | |
michael@0 | 16 | function AreaPositionManager(aContainer) { |
michael@0 | 17 | // Caching the direction and bounds of the container for quick access later: |
michael@0 | 18 | let window = aContainer.ownerDocument.defaultView; |
michael@0 | 19 | this._dir = window.getComputedStyle(aContainer).direction; |
michael@0 | 20 | let containerRect = aContainer.getBoundingClientRect(); |
michael@0 | 21 | this._containerInfo = { |
michael@0 | 22 | left: containerRect.left, |
michael@0 | 23 | right: containerRect.right, |
michael@0 | 24 | top: containerRect.top, |
michael@0 | 25 | width: containerRect.width |
michael@0 | 26 | }; |
michael@0 | 27 | this._inPanel = aContainer.id == CustomizableUI.AREA_PANEL; |
michael@0 | 28 | this._horizontalDistance = null; |
michael@0 | 29 | this.update(aContainer); |
michael@0 | 30 | } |
michael@0 | 31 | |
michael@0 | 32 | AreaPositionManager.prototype = { |
michael@0 | 33 | _nodePositionStore: null, |
michael@0 | 34 | _wideCache: null, |
michael@0 | 35 | |
michael@0 | 36 | update: function(aContainer) { |
michael@0 | 37 | let window = aContainer.ownerDocument.defaultView; |
michael@0 | 38 | this._nodePositionStore = new WeakMap(); |
michael@0 | 39 | this._wideCache = new Set(); |
michael@0 | 40 | let last = null; |
michael@0 | 41 | let singleItemHeight; |
michael@0 | 42 | for (let child of aContainer.children) { |
michael@0 | 43 | if (child.hidden) { |
michael@0 | 44 | continue; |
michael@0 | 45 | } |
michael@0 | 46 | let isNodeWide = this._checkIfWide(child); |
michael@0 | 47 | if (isNodeWide) { |
michael@0 | 48 | this._wideCache.add(child.id); |
michael@0 | 49 | } |
michael@0 | 50 | let coordinates = this._lazyStoreGet(child); |
michael@0 | 51 | // We keep a baseline horizontal distance between non-wide nodes around |
michael@0 | 52 | // for use when we can't compare with previous/next nodes |
michael@0 | 53 | if (!this._horizontalDistance && last && !isNodeWide) { |
michael@0 | 54 | this._horizontalDistance = coordinates.left - last.left; |
michael@0 | 55 | } |
michael@0 | 56 | // We also keep the basic height of non-wide items for use below: |
michael@0 | 57 | if (!isNodeWide && !singleItemHeight) { |
michael@0 | 58 | singleItemHeight = coordinates.height; |
michael@0 | 59 | } |
michael@0 | 60 | last = !isNodeWide ? coordinates : null; |
michael@0 | 61 | } |
michael@0 | 62 | if (this._inPanel) { |
michael@0 | 63 | this._heightToWidthFactor = CustomizableUI.PANEL_COLUMN_COUNT; |
michael@0 | 64 | } else { |
michael@0 | 65 | this._heightToWidthFactor = this._containerInfo.width / singleItemHeight; |
michael@0 | 66 | } |
michael@0 | 67 | }, |
michael@0 | 68 | |
michael@0 | 69 | /** |
michael@0 | 70 | * Find the closest node in the container given the coordinates. |
michael@0 | 71 | * "Closest" is defined in a somewhat strange manner: we prefer nodes |
michael@0 | 72 | * which are in the same row over nodes that are in a different row. |
michael@0 | 73 | * In order to implement this, we use a weighted cartesian distance |
michael@0 | 74 | * where dy is more heavily weighted by a factor corresponding to the |
michael@0 | 75 | * ratio between the container's width and the height of its elements. |
michael@0 | 76 | */ |
michael@0 | 77 | find: function(aContainer, aX, aY, aDraggedItemId) { |
michael@0 | 78 | let closest = null; |
michael@0 | 79 | let minCartesian = Number.MAX_VALUE; |
michael@0 | 80 | let containerX = this._containerInfo.left; |
michael@0 | 81 | let containerY = this._containerInfo.top; |
michael@0 | 82 | for (let node of aContainer.children) { |
michael@0 | 83 | let coordinates = this._lazyStoreGet(node); |
michael@0 | 84 | let offsetX = coordinates.x - containerX; |
michael@0 | 85 | let offsetY = coordinates.y - containerY; |
michael@0 | 86 | let hDiff = offsetX - aX; |
michael@0 | 87 | let vDiff = offsetY - aY; |
michael@0 | 88 | // For wide widgets, we're always going to be further from the center |
michael@0 | 89 | // horizontally. Compensate: |
michael@0 | 90 | if (this.isWide(node)) { |
michael@0 | 91 | hDiff /= CustomizableUI.PANEL_COLUMN_COUNT; |
michael@0 | 92 | } |
michael@0 | 93 | // Then compensate for the height/width ratio so that we prefer items |
michael@0 | 94 | // which are in the same row: |
michael@0 | 95 | hDiff /= this._heightToWidthFactor; |
michael@0 | 96 | |
michael@0 | 97 | let cartesianDiff = hDiff * hDiff + vDiff * vDiff; |
michael@0 | 98 | if (cartesianDiff < minCartesian) { |
michael@0 | 99 | minCartesian = cartesianDiff; |
michael@0 | 100 | closest = node; |
michael@0 | 101 | } |
michael@0 | 102 | } |
michael@0 | 103 | |
michael@0 | 104 | // Now correct this node based on what we're dragging |
michael@0 | 105 | if (closest) { |
michael@0 | 106 | let doc = aContainer.ownerDocument; |
michael@0 | 107 | let draggedItem = doc.getElementById(aDraggedItemId); |
michael@0 | 108 | // If dragging a wide item, always pick the first item in a row: |
michael@0 | 109 | if (this._inPanel && draggedItem && |
michael@0 | 110 | draggedItem.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) { |
michael@0 | 111 | return this._firstInRow(closest); |
michael@0 | 112 | } |
michael@0 | 113 | let targetBounds = this._lazyStoreGet(closest); |
michael@0 | 114 | let farSide = this._dir == "ltr" ? "right" : "left"; |
michael@0 | 115 | let outsideX = targetBounds[farSide]; |
michael@0 | 116 | // Check if we're closer to the next target than to this one: |
michael@0 | 117 | // Only move if we're not targeting a node in a different row: |
michael@0 | 118 | if (aY > targetBounds.top && aY < targetBounds.bottom) { |
michael@0 | 119 | if ((this._dir == "ltr" && aX > outsideX) || |
michael@0 | 120 | (this._dir == "rtl" && aX < outsideX)) { |
michael@0 | 121 | return closest.nextSibling || aContainer; |
michael@0 | 122 | } |
michael@0 | 123 | } |
michael@0 | 124 | } |
michael@0 | 125 | return closest; |
michael@0 | 126 | }, |
michael@0 | 127 | |
michael@0 | 128 | /** |
michael@0 | 129 | * "Insert" a "placeholder" by shifting the subsequent children out of the |
michael@0 | 130 | * way. We go through all the children, and shift them based on the position |
michael@0 | 131 | * they would have if we had inserted something before aBefore. We use CSS |
michael@0 | 132 | * transforms for this, which are CSS transitioned. |
michael@0 | 133 | */ |
michael@0 | 134 | insertPlaceholder: function(aContainer, aBefore, aWide, aSize, aIsFromThisArea) { |
michael@0 | 135 | let isShifted = false; |
michael@0 | 136 | let shiftDown = aWide; |
michael@0 | 137 | for (let child of aContainer.children) { |
michael@0 | 138 | // Don't need to shift hidden nodes: |
michael@0 | 139 | if (child.getAttribute("hidden") == "true") { |
michael@0 | 140 | continue; |
michael@0 | 141 | } |
michael@0 | 142 | // If this is the node before which we're inserting, start shifting |
michael@0 | 143 | // everything that comes after. One exception is inserting at the end |
michael@0 | 144 | // of the menupanel, in which case we do not shift the placeholders: |
michael@0 | 145 | if (child == aBefore && !child.classList.contains(kPlaceholderClass)) { |
michael@0 | 146 | isShifted = true; |
michael@0 | 147 | // If the node before which we're inserting is wide, we should |
michael@0 | 148 | // shift everything one row down: |
michael@0 | 149 | if (!shiftDown && this.isWide(child)) { |
michael@0 | 150 | shiftDown = true; |
michael@0 | 151 | } |
michael@0 | 152 | } |
michael@0 | 153 | // If we're moving items before a wide node that were already there, |
michael@0 | 154 | // it's possible it's not necessary to shift nodes |
michael@0 | 155 | // including & after the wide node. |
michael@0 | 156 | if (this.__undoShift) { |
michael@0 | 157 | isShifted = false; |
michael@0 | 158 | } |
michael@0 | 159 | if (isShifted) { |
michael@0 | 160 | // Conversely, if we're adding something before a wide node, for |
michael@0 | 161 | // simplicity's sake we move everything including the wide node down: |
michael@0 | 162 | if (this.__moveDown) { |
michael@0 | 163 | shiftDown = true; |
michael@0 | 164 | } |
michael@0 | 165 | if (aIsFromThisArea && !this._lastPlaceholderInsertion) { |
michael@0 | 166 | child.setAttribute("notransition", "true"); |
michael@0 | 167 | } |
michael@0 | 168 | // Determine the CSS transform based on the next node: |
michael@0 | 169 | child.style.transform = this._getNextPos(child, shiftDown, aSize); |
michael@0 | 170 | } else { |
michael@0 | 171 | // If we're not shifting this node, reset the transform |
michael@0 | 172 | child.style.transform = ""; |
michael@0 | 173 | } |
michael@0 | 174 | } |
michael@0 | 175 | if (aContainer.lastChild && aIsFromThisArea && |
michael@0 | 176 | !this._lastPlaceholderInsertion) { |
michael@0 | 177 | // Flush layout: |
michael@0 | 178 | aContainer.lastChild.getBoundingClientRect(); |
michael@0 | 179 | // then remove all the [notransition] |
michael@0 | 180 | for (let child of aContainer.children) { |
michael@0 | 181 | child.removeAttribute("notransition"); |
michael@0 | 182 | } |
michael@0 | 183 | } |
michael@0 | 184 | delete this.__moveDown; |
michael@0 | 185 | delete this.__undoShift; |
michael@0 | 186 | this._lastPlaceholderInsertion = aBefore; |
michael@0 | 187 | }, |
michael@0 | 188 | |
michael@0 | 189 | isWide: function(aNode) { |
michael@0 | 190 | return this._wideCache.has(aNode.id); |
michael@0 | 191 | }, |
michael@0 | 192 | |
michael@0 | 193 | _checkIfWide: function(aNode) { |
michael@0 | 194 | return this._inPanel && aNode && aNode.firstChild && |
michael@0 | 195 | aNode.firstChild.classList.contains(CustomizableUI.WIDE_PANEL_CLASS); |
michael@0 | 196 | }, |
michael@0 | 197 | |
michael@0 | 198 | /** |
michael@0 | 199 | * Reset all the transforms in this container, optionally without |
michael@0 | 200 | * transitioning them. |
michael@0 | 201 | * @param aContainer the container in which to reset transforms |
michael@0 | 202 | * @param aNoTransition if truthy, adds a notransition attribute to the node |
michael@0 | 203 | * while resetting the transform. |
michael@0 | 204 | */ |
michael@0 | 205 | clearPlaceholders: function(aContainer, aNoTransition) { |
michael@0 | 206 | for (let child of aContainer.children) { |
michael@0 | 207 | if (aNoTransition) { |
michael@0 | 208 | child.setAttribute("notransition", true); |
michael@0 | 209 | } |
michael@0 | 210 | child.style.transform = ""; |
michael@0 | 211 | if (aNoTransition) { |
michael@0 | 212 | // Need to force a reflow otherwise this won't work. |
michael@0 | 213 | child.getBoundingClientRect(); |
michael@0 | 214 | child.removeAttribute("notransition"); |
michael@0 | 215 | } |
michael@0 | 216 | } |
michael@0 | 217 | // We snapped back, so we can assume there's no more |
michael@0 | 218 | // "last" placeholder insertion point to keep track of. |
michael@0 | 219 | if (aNoTransition) { |
michael@0 | 220 | this._lastPlaceholderInsertion = null; |
michael@0 | 221 | } |
michael@0 | 222 | }, |
michael@0 | 223 | |
michael@0 | 224 | _getNextPos: function(aNode, aShiftDown, aSize) { |
michael@0 | 225 | // Shifting down is easy: |
michael@0 | 226 | if (this._inPanel && aShiftDown) { |
michael@0 | 227 | return "translate(0, " + aSize.height + "px)"; |
michael@0 | 228 | } |
michael@0 | 229 | return this._diffWithNext(aNode, aSize); |
michael@0 | 230 | }, |
michael@0 | 231 | |
michael@0 | 232 | _diffWithNext: function(aNode, aSize) { |
michael@0 | 233 | let xDiff; |
michael@0 | 234 | let yDiff = null; |
michael@0 | 235 | let nodeBounds = this._lazyStoreGet(aNode); |
michael@0 | 236 | let side = this._dir == "ltr" ? "left" : "right"; |
michael@0 | 237 | let next = this._getVisibleSiblingForDirection(aNode, "next"); |
michael@0 | 238 | // First we determine the transform along the x axis. |
michael@0 | 239 | // Usually, there will be a next node to base this on: |
michael@0 | 240 | if (next) { |
michael@0 | 241 | let otherBounds = this._lazyStoreGet(next); |
michael@0 | 242 | xDiff = otherBounds[side] - nodeBounds[side]; |
michael@0 | 243 | // If the next node is a wide item in the panel, check if we could maybe |
michael@0 | 244 | // just move further out in the same row, without snapping to the next |
michael@0 | 245 | // one. This happens, for example, if moving an item that's before a wide |
michael@0 | 246 | // node within its own row of items. There will be space to drop this |
michael@0 | 247 | // item within the row, and the rest of the items do not need to shift. |
michael@0 | 248 | if (this.isWide(next)) { |
michael@0 | 249 | let otherXDiff = this._moveNextBasedOnPrevious(aNode, nodeBounds, |
michael@0 | 250 | this._firstInRow(aNode)); |
michael@0 | 251 | // If this has the same sign as our original shift, we're still |
michael@0 | 252 | // snapping to the start of the row. In this case, we should move |
michael@0 | 253 | // everything after us a row down, so as not to display two nodes on |
michael@0 | 254 | // top of each other: |
michael@0 | 255 | // (we would be able to get away with checking for equality instead of |
michael@0 | 256 | // equal signs here, but one of these is based on the x coordinate of |
michael@0 | 257 | // the first item in row N and one on that for row N - 1, so this is |
michael@0 | 258 | // safer, as their margins might differ) |
michael@0 | 259 | if ((otherXDiff < 0) == (xDiff < 0)) { |
michael@0 | 260 | this.__moveDown = true; |
michael@0 | 261 | } else { |
michael@0 | 262 | // Otherwise, we succeeded and can move further out. This also means |
michael@0 | 263 | // we can stop shifting the rest of the content: |
michael@0 | 264 | xDiff = otherXDiff; |
michael@0 | 265 | this.__undoShift = true; |
michael@0 | 266 | } |
michael@0 | 267 | } else { |
michael@0 | 268 | // We set this explicitly because otherwise some strange difference |
michael@0 | 269 | // between the height and the actual difference between line creeps in |
michael@0 | 270 | // and messes with alignments |
michael@0 | 271 | yDiff = otherBounds.top - nodeBounds.top; |
michael@0 | 272 | } |
michael@0 | 273 | } else { |
michael@0 | 274 | // We don't have a sibling whose position we can use. First, let's see |
michael@0 | 275 | // if we're also the first item (which complicates things): |
michael@0 | 276 | let firstNode = this._firstInRow(aNode); |
michael@0 | 277 | if (aNode == firstNode) { |
michael@0 | 278 | // Maybe we stored the horizontal distance between non-wide nodes, |
michael@0 | 279 | // if not, we'll use the width of the incoming node as a proxy: |
michael@0 | 280 | xDiff = this._horizontalDistance || aSize.width; |
michael@0 | 281 | } else { |
michael@0 | 282 | // If not, we should be able to get the distance to the previous node |
michael@0 | 283 | // and use the inverse, unless there's no room for another node (ie we |
michael@0 | 284 | // are the last node and there's no room for another one) |
michael@0 | 285 | xDiff = this._moveNextBasedOnPrevious(aNode, nodeBounds, firstNode); |
michael@0 | 286 | } |
michael@0 | 287 | } |
michael@0 | 288 | |
michael@0 | 289 | // If we've not determined the vertical difference yet, check it here |
michael@0 | 290 | if (yDiff === null) { |
michael@0 | 291 | // If the next node is behind rather than in front, we must have moved |
michael@0 | 292 | // vertically: |
michael@0 | 293 | if ((xDiff > 0 && this._dir == "rtl") || (xDiff < 0 && this._dir == "ltr")) { |
michael@0 | 294 | yDiff = aSize.height; |
michael@0 | 295 | } else { |
michael@0 | 296 | // Otherwise, we haven't |
michael@0 | 297 | yDiff = 0; |
michael@0 | 298 | } |
michael@0 | 299 | } |
michael@0 | 300 | return "translate(" + xDiff + "px, " + yDiff + "px)"; |
michael@0 | 301 | }, |
michael@0 | 302 | |
michael@0 | 303 | /** |
michael@0 | 304 | * Helper function to find the transform a node if there isn't a next node |
michael@0 | 305 | * to base that on. |
michael@0 | 306 | * @param aNode the node to transform |
michael@0 | 307 | * @param aNodeBounds the bounding rect info of this node |
michael@0 | 308 | * @param aFirstNodeInRow the first node in aNode's row |
michael@0 | 309 | */ |
michael@0 | 310 | _moveNextBasedOnPrevious: function(aNode, aNodeBounds, aFirstNodeInRow) { |
michael@0 | 311 | let next = this._getVisibleSiblingForDirection(aNode, "previous"); |
michael@0 | 312 | let otherBounds = this._lazyStoreGet(next); |
michael@0 | 313 | let side = this._dir == "ltr" ? "left" : "right"; |
michael@0 | 314 | let xDiff = aNodeBounds[side] - otherBounds[side]; |
michael@0 | 315 | // If, however, this means we move outside the container's box |
michael@0 | 316 | // (i.e. the row in which this item is placed is full) |
michael@0 | 317 | // we should move it to align with the first item in the next row instead |
michael@0 | 318 | let bound = this._containerInfo[this._dir == "ltr" ? "right" : "left"]; |
michael@0 | 319 | if ((this._dir == "ltr" && xDiff + aNodeBounds.right > bound) || |
michael@0 | 320 | (this._dir == "rtl" && xDiff + aNodeBounds.left < bound)) { |
michael@0 | 321 | xDiff = this._lazyStoreGet(aFirstNodeInRow)[side] - aNodeBounds[side]; |
michael@0 | 322 | } |
michael@0 | 323 | return xDiff; |
michael@0 | 324 | }, |
michael@0 | 325 | |
michael@0 | 326 | /** |
michael@0 | 327 | * Get position details from our cache. If the node is not yet cached, get its position |
michael@0 | 328 | * information and cache it now. |
michael@0 | 329 | * @param aNode the node whose position info we want |
michael@0 | 330 | * @return the position info |
michael@0 | 331 | */ |
michael@0 | 332 | _lazyStoreGet: function(aNode) { |
michael@0 | 333 | let rect = this._nodePositionStore.get(aNode); |
michael@0 | 334 | if (!rect) { |
michael@0 | 335 | // getBoundingClientRect() returns a DOMRect that is live, meaning that |
michael@0 | 336 | // as the element moves around, the rects values change. We don't want |
michael@0 | 337 | // that - we want a snapshot of what the rect values are right at this |
michael@0 | 338 | // moment, and nothing else. So we have to clone the values. |
michael@0 | 339 | let clientRect = aNode.getBoundingClientRect(); |
michael@0 | 340 | rect = { |
michael@0 | 341 | left: clientRect.left, |
michael@0 | 342 | right: clientRect.right, |
michael@0 | 343 | width: clientRect.width, |
michael@0 | 344 | height: clientRect.height, |
michael@0 | 345 | top: clientRect.top, |
michael@0 | 346 | bottom: clientRect.bottom, |
michael@0 | 347 | }; |
michael@0 | 348 | rect.x = rect.left + rect.width / 2; |
michael@0 | 349 | rect.y = rect.top + rect.height / 2; |
michael@0 | 350 | Object.freeze(rect); |
michael@0 | 351 | this._nodePositionStore.set(aNode, rect); |
michael@0 | 352 | } |
michael@0 | 353 | return rect; |
michael@0 | 354 | }, |
michael@0 | 355 | |
michael@0 | 356 | _firstInRow: function(aNode) { |
michael@0 | 357 | // XXXmconley: I'm not entirely sure why we need to take the floor of these |
michael@0 | 358 | // values - it looks like, periodically, we're getting fractional pixels back |
michael@0 | 359 | //from lazyStoreGet. I've filed bug 994247 to investigate. |
michael@0 | 360 | let bound = Math.floor(this._lazyStoreGet(aNode).top); |
michael@0 | 361 | let rv = aNode; |
michael@0 | 362 | let prev; |
michael@0 | 363 | while (rv && (prev = this._getVisibleSiblingForDirection(rv, "previous"))) { |
michael@0 | 364 | if (Math.floor(this._lazyStoreGet(prev).bottom) <= bound) { |
michael@0 | 365 | return rv; |
michael@0 | 366 | } |
michael@0 | 367 | rv = prev; |
michael@0 | 368 | } |
michael@0 | 369 | return rv; |
michael@0 | 370 | }, |
michael@0 | 371 | |
michael@0 | 372 | _getVisibleSiblingForDirection: function(aNode, aDirection) { |
michael@0 | 373 | let rv = aNode; |
michael@0 | 374 | do { |
michael@0 | 375 | rv = rv[aDirection + "Sibling"]; |
michael@0 | 376 | } while (rv && rv.getAttribute("hidden") == "true") |
michael@0 | 377 | return rv; |
michael@0 | 378 | } |
michael@0 | 379 | } |
michael@0 | 380 | |
michael@0 | 381 | let DragPositionManager = { |
michael@0 | 382 | start: function(aWindow) { |
michael@0 | 383 | let areas = CustomizableUI.areas.filter((area) => CustomizableUI.getAreaType(area) != "toolbar"); |
michael@0 | 384 | areas = areas.map((area) => CustomizableUI.getCustomizeTargetForArea(area, aWindow)); |
michael@0 | 385 | areas.push(aWindow.document.getElementById(kPaletteId)); |
michael@0 | 386 | for (let areaNode of areas) { |
michael@0 | 387 | let positionManager = gManagers.get(areaNode); |
michael@0 | 388 | if (positionManager) { |
michael@0 | 389 | positionManager.update(areaNode); |
michael@0 | 390 | } else { |
michael@0 | 391 | gManagers.set(areaNode, new AreaPositionManager(areaNode)); |
michael@0 | 392 | } |
michael@0 | 393 | } |
michael@0 | 394 | }, |
michael@0 | 395 | |
michael@0 | 396 | add: function(aWindow, aArea, aContainer) { |
michael@0 | 397 | if (CustomizableUI.getAreaType(aArea) != "toolbar") { |
michael@0 | 398 | return; |
michael@0 | 399 | } |
michael@0 | 400 | |
michael@0 | 401 | gManagers.set(aContainer, new AreaPositionManager(aContainer)); |
michael@0 | 402 | }, |
michael@0 | 403 | |
michael@0 | 404 | remove: function(aWindow, aArea, aContainer) { |
michael@0 | 405 | if (CustomizableUI.getAreaType(aArea) != "toolbar") { |
michael@0 | 406 | return; |
michael@0 | 407 | } |
michael@0 | 408 | |
michael@0 | 409 | gManagers.delete(aContainer); |
michael@0 | 410 | }, |
michael@0 | 411 | |
michael@0 | 412 | stop: function() { |
michael@0 | 413 | gManagers.clear(); |
michael@0 | 414 | }, |
michael@0 | 415 | |
michael@0 | 416 | getManagerForArea: function(aArea) { |
michael@0 | 417 | return gManagers.get(aArea); |
michael@0 | 418 | } |
michael@0 | 419 | }; |
michael@0 | 420 | |
michael@0 | 421 | Object.freeze(DragPositionManager); |
michael@0 | 422 |