Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 6 | Components.utils.import("resource://gre/modules/Services.jsm"); |
michael@0 | 7 | |
michael@0 | 8 | /** |
michael@0 | 9 | * The base view implements everything that's common to the toolbar and |
michael@0 | 10 | * menu views. |
michael@0 | 11 | */ |
michael@0 | 12 | function PlacesViewBase(aPlace, aOptions) { |
michael@0 | 13 | this.place = aPlace; |
michael@0 | 14 | this.options = aOptions; |
michael@0 | 15 | this._controller = new PlacesController(this); |
michael@0 | 16 | this._viewElt.controllers.appendController(this._controller); |
michael@0 | 17 | } |
michael@0 | 18 | |
michael@0 | 19 | PlacesViewBase.prototype = { |
michael@0 | 20 | // The xul element that holds the entire view. |
michael@0 | 21 | _viewElt: null, |
michael@0 | 22 | get viewElt() this._viewElt, |
michael@0 | 23 | |
michael@0 | 24 | get associatedElement() this._viewElt, |
michael@0 | 25 | |
michael@0 | 26 | get controllers() this._viewElt.controllers, |
michael@0 | 27 | |
michael@0 | 28 | // The xul element that represents the root container. |
michael@0 | 29 | _rootElt: null, |
michael@0 | 30 | |
michael@0 | 31 | // Set to true for views that are represented by native widgets (i.e. |
michael@0 | 32 | // the native mac menu). |
michael@0 | 33 | _nativeView: false, |
michael@0 | 34 | |
michael@0 | 35 | QueryInterface: XPCOMUtils.generateQI( |
michael@0 | 36 | [Components.interfaces.nsINavHistoryResultObserver, |
michael@0 | 37 | Components.interfaces.nsISupportsWeakReference]), |
michael@0 | 38 | |
michael@0 | 39 | _place: "", |
michael@0 | 40 | get place() this._place, |
michael@0 | 41 | set place(val) { |
michael@0 | 42 | this._place = val; |
michael@0 | 43 | |
michael@0 | 44 | let history = PlacesUtils.history; |
michael@0 | 45 | let queries = { }, options = { }; |
michael@0 | 46 | history.queryStringToQueries(val, queries, { }, options); |
michael@0 | 47 | if (!queries.value.length) |
michael@0 | 48 | queries.value = [history.getNewQuery()]; |
michael@0 | 49 | |
michael@0 | 50 | let result = history.executeQueries(queries.value, queries.value.length, |
michael@0 | 51 | options.value); |
michael@0 | 52 | result.addObserver(this, false); |
michael@0 | 53 | return val; |
michael@0 | 54 | }, |
michael@0 | 55 | |
michael@0 | 56 | _result: null, |
michael@0 | 57 | get result() this._result, |
michael@0 | 58 | set result(val) { |
michael@0 | 59 | if (this._result == val) |
michael@0 | 60 | return val; |
michael@0 | 61 | |
michael@0 | 62 | if (this._result) { |
michael@0 | 63 | this._result.removeObserver(this); |
michael@0 | 64 | this._resultNode.containerOpen = false; |
michael@0 | 65 | } |
michael@0 | 66 | |
michael@0 | 67 | if (this._rootElt.localName == "menupopup") |
michael@0 | 68 | this._rootElt._built = false; |
michael@0 | 69 | |
michael@0 | 70 | this._result = val; |
michael@0 | 71 | if (val) { |
michael@0 | 72 | this._resultNode = val.root; |
michael@0 | 73 | this._rootElt._placesNode = this._resultNode; |
michael@0 | 74 | this._domNodes = new Map(); |
michael@0 | 75 | this._domNodes.set(this._resultNode, this._rootElt); |
michael@0 | 76 | |
michael@0 | 77 | // This calls _rebuild through invalidateContainer. |
michael@0 | 78 | this._resultNode.containerOpen = true; |
michael@0 | 79 | } |
michael@0 | 80 | else { |
michael@0 | 81 | this._resultNode = null; |
michael@0 | 82 | delete this._domNodes; |
michael@0 | 83 | } |
michael@0 | 84 | |
michael@0 | 85 | return val; |
michael@0 | 86 | }, |
michael@0 | 87 | |
michael@0 | 88 | _options: null, |
michael@0 | 89 | get options() this._options, |
michael@0 | 90 | set options(val) { |
michael@0 | 91 | if (!val) |
michael@0 | 92 | val = {}; |
michael@0 | 93 | |
michael@0 | 94 | if (!("extraClasses" in val)) |
michael@0 | 95 | val.extraClasses = {}; |
michael@0 | 96 | this._options = val; |
michael@0 | 97 | |
michael@0 | 98 | return val; |
michael@0 | 99 | }, |
michael@0 | 100 | |
michael@0 | 101 | /** |
michael@0 | 102 | * Gets the DOM node used for the given places node. |
michael@0 | 103 | * |
michael@0 | 104 | * @param aPlacesNode |
michael@0 | 105 | * a places result node. |
michael@0 | 106 | * @throws if there is no DOM node set for aPlacesNode. |
michael@0 | 107 | */ |
michael@0 | 108 | _getDOMNodeForPlacesNode: |
michael@0 | 109 | function PVB__getDOMNodeForPlacesNode(aPlacesNode) { |
michael@0 | 110 | let node = this._domNodes.get(aPlacesNode, null); |
michael@0 | 111 | if (!node) { |
michael@0 | 112 | throw new Error("No DOM node set for aPlacesNode.\nnode.type: " + |
michael@0 | 113 | aPlacesNode.type + ". node.parent: " + aPlacesNode); |
michael@0 | 114 | } |
michael@0 | 115 | return node; |
michael@0 | 116 | }, |
michael@0 | 117 | |
michael@0 | 118 | get controller() this._controller, |
michael@0 | 119 | |
michael@0 | 120 | get selType() "single", |
michael@0 | 121 | selectItems: function() { }, |
michael@0 | 122 | selectAll: function() { }, |
michael@0 | 123 | |
michael@0 | 124 | get selectedNode() { |
michael@0 | 125 | if (this._contextMenuShown) { |
michael@0 | 126 | let popup = document.popupNode; |
michael@0 | 127 | return popup._placesNode || popup.parentNode._placesNode || null; |
michael@0 | 128 | } |
michael@0 | 129 | return null; |
michael@0 | 130 | }, |
michael@0 | 131 | |
michael@0 | 132 | get hasSelection() this.selectedNode != null, |
michael@0 | 133 | |
michael@0 | 134 | get selectedNodes() { |
michael@0 | 135 | let selectedNode = this.selectedNode; |
michael@0 | 136 | return selectedNode ? [selectedNode] : []; |
michael@0 | 137 | }, |
michael@0 | 138 | |
michael@0 | 139 | get removableSelectionRanges() { |
michael@0 | 140 | // On static content the current selectedNode would be the selection's |
michael@0 | 141 | // parent node. We don't want to allow removing a node when the |
michael@0 | 142 | // selection is not explicit. |
michael@0 | 143 | if (document.popupNode && |
michael@0 | 144 | (document.popupNode == "menupopup" || !document.popupNode._placesNode)) |
michael@0 | 145 | return []; |
michael@0 | 146 | |
michael@0 | 147 | return [this.selectedNodes]; |
michael@0 | 148 | }, |
michael@0 | 149 | |
michael@0 | 150 | get draggableSelection() [this._draggedElt], |
michael@0 | 151 | |
michael@0 | 152 | get insertionPoint() { |
michael@0 | 153 | // There is no insertion point for history queries, so bail out now and |
michael@0 | 154 | // save a lot of work when updating commands. |
michael@0 | 155 | let resultNode = this._resultNode; |
michael@0 | 156 | if (PlacesUtils.nodeIsQuery(resultNode) && |
michael@0 | 157 | PlacesUtils.asQuery(resultNode).queryOptions.queryType == |
michael@0 | 158 | Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) |
michael@0 | 159 | return null; |
michael@0 | 160 | |
michael@0 | 161 | // By default, the insertion point is at the top level, at the end. |
michael@0 | 162 | let index = PlacesUtils.bookmarks.DEFAULT_INDEX; |
michael@0 | 163 | let container = this._resultNode; |
michael@0 | 164 | let orientation = Ci.nsITreeView.DROP_BEFORE; |
michael@0 | 165 | let isTag = false; |
michael@0 | 166 | |
michael@0 | 167 | let selectedNode = this.selectedNode; |
michael@0 | 168 | if (selectedNode) { |
michael@0 | 169 | let popup = document.popupNode; |
michael@0 | 170 | if (!popup._placesNode || popup._placesNode == this._resultNode || |
michael@0 | 171 | popup._placesNode.itemId == -1) { |
michael@0 | 172 | // If a static menuitem is selected, or if the root node is selected, |
michael@0 | 173 | // the insertion point is inside the folder, at the end. |
michael@0 | 174 | container = selectedNode; |
michael@0 | 175 | orientation = Ci.nsITreeView.DROP_ON; |
michael@0 | 176 | } |
michael@0 | 177 | else { |
michael@0 | 178 | // In all other cases the insertion point is before that node. |
michael@0 | 179 | container = selectedNode.parent; |
michael@0 | 180 | index = container.getChildIndex(selectedNode); |
michael@0 | 181 | isTag = PlacesUtils.nodeIsTagQuery(container); |
michael@0 | 182 | } |
michael@0 | 183 | } |
michael@0 | 184 | |
michael@0 | 185 | if (PlacesControllerDragHelper.disallowInsertion(container)) |
michael@0 | 186 | return null; |
michael@0 | 187 | |
michael@0 | 188 | return new InsertionPoint(PlacesUtils.getConcreteItemId(container), |
michael@0 | 189 | index, orientation, isTag); |
michael@0 | 190 | }, |
michael@0 | 191 | |
michael@0 | 192 | buildContextMenu: function PVB_buildContextMenu(aPopup) { |
michael@0 | 193 | this._contextMenuShown = true; |
michael@0 | 194 | window.updateCommands("places"); |
michael@0 | 195 | return this.controller.buildContextMenu(aPopup); |
michael@0 | 196 | }, |
michael@0 | 197 | |
michael@0 | 198 | destroyContextMenu: function PVB_destroyContextMenu(aPopup) { |
michael@0 | 199 | this._contextMenuShown = false; |
michael@0 | 200 | }, |
michael@0 | 201 | |
michael@0 | 202 | _cleanPopup: function PVB_cleanPopup(aPopup, aDelay) { |
michael@0 | 203 | // Remove Places nodes from the popup. |
michael@0 | 204 | let child = aPopup._startMarker; |
michael@0 | 205 | while (child.nextSibling != aPopup._endMarker) { |
michael@0 | 206 | let sibling = child.nextSibling; |
michael@0 | 207 | if (sibling._placesNode && !aDelay) { |
michael@0 | 208 | aPopup.removeChild(sibling); |
michael@0 | 209 | } |
michael@0 | 210 | else if (sibling._placesNode && aDelay) { |
michael@0 | 211 | // HACK (bug 733419): the popups originating from the OS X native |
michael@0 | 212 | // menubar don't live-update while open, thus we don't clean it |
michael@0 | 213 | // until the next popupshowing, to avoid zombie menuitems. |
michael@0 | 214 | if (!aPopup._delayedRemovals) |
michael@0 | 215 | aPopup._delayedRemovals = []; |
michael@0 | 216 | aPopup._delayedRemovals.push(sibling); |
michael@0 | 217 | child = child.nextSibling; |
michael@0 | 218 | } |
michael@0 | 219 | else { |
michael@0 | 220 | child = child.nextSibling; |
michael@0 | 221 | } |
michael@0 | 222 | } |
michael@0 | 223 | }, |
michael@0 | 224 | |
michael@0 | 225 | _rebuildPopup: function PVB__rebuildPopup(aPopup) { |
michael@0 | 226 | let resultNode = aPopup._placesNode; |
michael@0 | 227 | if (!resultNode.containerOpen) |
michael@0 | 228 | return; |
michael@0 | 229 | |
michael@0 | 230 | if (this.controller.hasCachedLivemarkInfo(resultNode)) { |
michael@0 | 231 | this._setEmptyPopupStatus(aPopup, false); |
michael@0 | 232 | aPopup._built = true; |
michael@0 | 233 | this._populateLivemarkPopup(aPopup); |
michael@0 | 234 | return; |
michael@0 | 235 | } |
michael@0 | 236 | |
michael@0 | 237 | this._cleanPopup(aPopup); |
michael@0 | 238 | |
michael@0 | 239 | let cc = resultNode.childCount; |
michael@0 | 240 | if (cc > 0) { |
michael@0 | 241 | this._setEmptyPopupStatus(aPopup, false); |
michael@0 | 242 | |
michael@0 | 243 | for (let i = 0; i < cc; ++i) { |
michael@0 | 244 | let child = resultNode.getChild(i); |
michael@0 | 245 | this._insertNewItemToPopup(child, aPopup, null); |
michael@0 | 246 | } |
michael@0 | 247 | } |
michael@0 | 248 | else { |
michael@0 | 249 | this._setEmptyPopupStatus(aPopup, true); |
michael@0 | 250 | } |
michael@0 | 251 | aPopup._built = true; |
michael@0 | 252 | }, |
michael@0 | 253 | |
michael@0 | 254 | _removeChild: function PVB__removeChild(aChild) { |
michael@0 | 255 | // If document.popupNode pointed to this child, null it out, |
michael@0 | 256 | // otherwise controller's command-updating may rely on the removed |
michael@0 | 257 | // item still being "selected". |
michael@0 | 258 | if (document.popupNode == aChild) |
michael@0 | 259 | document.popupNode = null; |
michael@0 | 260 | |
michael@0 | 261 | aChild.parentNode.removeChild(aChild); |
michael@0 | 262 | }, |
michael@0 | 263 | |
michael@0 | 264 | _setEmptyPopupStatus: |
michael@0 | 265 | function PVB__setEmptyPopupStatus(aPopup, aEmpty) { |
michael@0 | 266 | if (!aPopup._emptyMenuitem) { |
michael@0 | 267 | let label = PlacesUIUtils.getString("bookmarksMenuEmptyFolder"); |
michael@0 | 268 | aPopup._emptyMenuitem = document.createElement("menuitem"); |
michael@0 | 269 | aPopup._emptyMenuitem.setAttribute("label", label); |
michael@0 | 270 | aPopup._emptyMenuitem.setAttribute("disabled", true); |
michael@0 | 271 | aPopup._emptyMenuitem.className = "bookmark-item"; |
michael@0 | 272 | if (typeof this.options.extraClasses.entry == "string") |
michael@0 | 273 | aPopup._emptyMenuitem.classList.add(this.options.extraClasses.entry); |
michael@0 | 274 | } |
michael@0 | 275 | |
michael@0 | 276 | if (aEmpty) { |
michael@0 | 277 | aPopup.setAttribute("emptyplacesresult", "true"); |
michael@0 | 278 | // Don't add the menuitem if there is static content. |
michael@0 | 279 | if (!aPopup._startMarker.previousSibling && |
michael@0 | 280 | !aPopup._endMarker.nextSibling) |
michael@0 | 281 | aPopup.insertBefore(aPopup._emptyMenuitem, aPopup._endMarker); |
michael@0 | 282 | } |
michael@0 | 283 | else { |
michael@0 | 284 | aPopup.removeAttribute("emptyplacesresult"); |
michael@0 | 285 | try { |
michael@0 | 286 | aPopup.removeChild(aPopup._emptyMenuitem); |
michael@0 | 287 | } catch (ex) {} |
michael@0 | 288 | } |
michael@0 | 289 | }, |
michael@0 | 290 | |
michael@0 | 291 | _createMenuItemForPlacesNode: |
michael@0 | 292 | function PVB__createMenuItemForPlacesNode(aPlacesNode) { |
michael@0 | 293 | this._domNodes.delete(aPlacesNode); |
michael@0 | 294 | |
michael@0 | 295 | let element; |
michael@0 | 296 | let type = aPlacesNode.type; |
michael@0 | 297 | if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) { |
michael@0 | 298 | element = document.createElement("menuseparator"); |
michael@0 | 299 | element.setAttribute("class", "small-separator"); |
michael@0 | 300 | } |
michael@0 | 301 | else { |
michael@0 | 302 | let itemId = aPlacesNode.itemId; |
michael@0 | 303 | if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI) { |
michael@0 | 304 | element = document.createElement("menuitem"); |
michael@0 | 305 | element.className = "menuitem-iconic bookmark-item menuitem-with-favicon"; |
michael@0 | 306 | element.setAttribute("scheme", |
michael@0 | 307 | PlacesUIUtils.guessUrlSchemeForUI(aPlacesNode.uri)); |
michael@0 | 308 | } |
michael@0 | 309 | else if (PlacesUtils.containerTypes.indexOf(type) != -1) { |
michael@0 | 310 | element = document.createElement("menu"); |
michael@0 | 311 | element.setAttribute("container", "true"); |
michael@0 | 312 | |
michael@0 | 313 | if (aPlacesNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) { |
michael@0 | 314 | element.setAttribute("query", "true"); |
michael@0 | 315 | if (PlacesUtils.nodeIsTagQuery(aPlacesNode)) |
michael@0 | 316 | element.setAttribute("tagContainer", "true"); |
michael@0 | 317 | else if (PlacesUtils.nodeIsDay(aPlacesNode)) |
michael@0 | 318 | element.setAttribute("dayContainer", "true"); |
michael@0 | 319 | else if (PlacesUtils.nodeIsHost(aPlacesNode)) |
michael@0 | 320 | element.setAttribute("hostContainer", "true"); |
michael@0 | 321 | } |
michael@0 | 322 | else if (itemId != -1) { |
michael@0 | 323 | PlacesUtils.livemarks.getLivemark({ id: itemId }) |
michael@0 | 324 | .then(aLivemark => { |
michael@0 | 325 | element.setAttribute("livemark", "true"); |
michael@0 | 326 | #ifdef XP_MACOSX |
michael@0 | 327 | // OS X native menubar doesn't track list-style-images since |
michael@0 | 328 | // it doesn't have a frame (bug 733415). Thus enforce updating. |
michael@0 | 329 | element.setAttribute("image", ""); |
michael@0 | 330 | element.removeAttribute("image"); |
michael@0 | 331 | #endif |
michael@0 | 332 | this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark); |
michael@0 | 333 | }, () => undefined); |
michael@0 | 334 | } |
michael@0 | 335 | |
michael@0 | 336 | let popup = document.createElement("menupopup"); |
michael@0 | 337 | popup._placesNode = PlacesUtils.asContainer(aPlacesNode); |
michael@0 | 338 | |
michael@0 | 339 | if (!this._nativeView) { |
michael@0 | 340 | popup.setAttribute("placespopup", "true"); |
michael@0 | 341 | } |
michael@0 | 342 | |
michael@0 | 343 | element.appendChild(popup); |
michael@0 | 344 | element.className = "menu-iconic bookmark-item"; |
michael@0 | 345 | if (typeof this.options.extraClasses.entry == "string") { |
michael@0 | 346 | element.classList.add(this.options.extraClasses.entry); |
michael@0 | 347 | } |
michael@0 | 348 | |
michael@0 | 349 | this._domNodes.set(aPlacesNode, popup); |
michael@0 | 350 | } |
michael@0 | 351 | else |
michael@0 | 352 | throw "Unexpected node"; |
michael@0 | 353 | |
michael@0 | 354 | element.setAttribute("label", PlacesUIUtils.getBestTitle(aPlacesNode)); |
michael@0 | 355 | |
michael@0 | 356 | let icon = aPlacesNode.icon; |
michael@0 | 357 | if (icon) |
michael@0 | 358 | element.setAttribute("image", icon); |
michael@0 | 359 | } |
michael@0 | 360 | |
michael@0 | 361 | element._placesNode = aPlacesNode; |
michael@0 | 362 | if (!this._domNodes.has(aPlacesNode)) |
michael@0 | 363 | this._domNodes.set(aPlacesNode, element); |
michael@0 | 364 | |
michael@0 | 365 | return element; |
michael@0 | 366 | }, |
michael@0 | 367 | |
michael@0 | 368 | _insertNewItemToPopup: |
michael@0 | 369 | function PVB__insertNewItemToPopup(aNewChild, aPopup, aBefore) { |
michael@0 | 370 | let element = this._createMenuItemForPlacesNode(aNewChild); |
michael@0 | 371 | let before = aBefore || aPopup._endMarker; |
michael@0 | 372 | |
michael@0 | 373 | if (element.localName == "menuitem" || element.localName == "menu") { |
michael@0 | 374 | if (typeof this.options.extraClasses.entry == "string") |
michael@0 | 375 | element.classList.add(this.options.extraClasses.entry); |
michael@0 | 376 | } |
michael@0 | 377 | |
michael@0 | 378 | aPopup.insertBefore(element, before); |
michael@0 | 379 | return element; |
michael@0 | 380 | }, |
michael@0 | 381 | |
michael@0 | 382 | _setLivemarkSiteURIMenuItem: |
michael@0 | 383 | function PVB__setLivemarkSiteURIMenuItem(aPopup) { |
michael@0 | 384 | let livemarkInfo = this.controller.getCachedLivemarkInfo(aPopup._placesNode); |
michael@0 | 385 | let siteUrl = livemarkInfo && livemarkInfo.siteURI ? |
michael@0 | 386 | livemarkInfo.siteURI.spec : null; |
michael@0 | 387 | if (!siteUrl && aPopup._siteURIMenuitem) { |
michael@0 | 388 | aPopup.removeChild(aPopup._siteURIMenuitem); |
michael@0 | 389 | aPopup._siteURIMenuitem = null; |
michael@0 | 390 | aPopup.removeChild(aPopup._siteURIMenuseparator); |
michael@0 | 391 | aPopup._siteURIMenuseparator = null; |
michael@0 | 392 | } |
michael@0 | 393 | else if (siteUrl && !aPopup._siteURIMenuitem) { |
michael@0 | 394 | // Add "Open (Feed Name)" menuitem. |
michael@0 | 395 | aPopup._siteURIMenuitem = document.createElement("menuitem"); |
michael@0 | 396 | aPopup._siteURIMenuitem.className = "openlivemarksite-menuitem"; |
michael@0 | 397 | if (typeof this.options.extraClasses.entry == "string") { |
michael@0 | 398 | aPopup._siteURIMenuitem.classList.add(this.options.extraClasses.entry); |
michael@0 | 399 | } |
michael@0 | 400 | aPopup._siteURIMenuitem.setAttribute("targetURI", siteUrl); |
michael@0 | 401 | aPopup._siteURIMenuitem.setAttribute("oncommand", |
michael@0 | 402 | "openUILink(this.getAttribute('targetURI'), event);"); |
michael@0 | 403 | |
michael@0 | 404 | // If a user middle-clicks this item we serve the oncommand event. |
michael@0 | 405 | // We are using checkForMiddleClick because of Bug 246720. |
michael@0 | 406 | // Note: stopPropagation is needed to avoid serving middle-click |
michael@0 | 407 | // with BT_onClick that would open all items in tabs. |
michael@0 | 408 | aPopup._siteURIMenuitem.setAttribute("onclick", |
michael@0 | 409 | "checkForMiddleClick(this, event); event.stopPropagation();"); |
michael@0 | 410 | let label = |
michael@0 | 411 | PlacesUIUtils.getFormattedString("menuOpenLivemarkOrigin.label", |
michael@0 | 412 | [aPopup.parentNode.getAttribute("label")]) |
michael@0 | 413 | aPopup._siteURIMenuitem.setAttribute("label", label); |
michael@0 | 414 | aPopup.insertBefore(aPopup._siteURIMenuitem, aPopup._startMarker); |
michael@0 | 415 | |
michael@0 | 416 | aPopup._siteURIMenuseparator = document.createElement("menuseparator"); |
michael@0 | 417 | aPopup.insertBefore(aPopup._siteURIMenuseparator, aPopup._startMarker); |
michael@0 | 418 | } |
michael@0 | 419 | }, |
michael@0 | 420 | |
michael@0 | 421 | /** |
michael@0 | 422 | * Add, update or remove the livemark status menuitem. |
michael@0 | 423 | * @param aPopup |
michael@0 | 424 | * The livemark container popup |
michael@0 | 425 | * @param aStatus |
michael@0 | 426 | * The livemark status |
michael@0 | 427 | */ |
michael@0 | 428 | _setLivemarkStatusMenuItem: |
michael@0 | 429 | function PVB_setLivemarkStatusMenuItem(aPopup, aStatus) { |
michael@0 | 430 | let statusMenuitem = aPopup._statusMenuitem; |
michael@0 | 431 | if (!statusMenuitem) { |
michael@0 | 432 | // Create the status menuitem and cache it in the popup object. |
michael@0 | 433 | statusMenuitem = document.createElement("menuitem"); |
michael@0 | 434 | statusMenuitem.className = "livemarkstatus-menuitem"; |
michael@0 | 435 | if (typeof this.options.extraClasses.entry == "string") { |
michael@0 | 436 | statusMenuitem.classList.add(this.options.extraClasses.entry); |
michael@0 | 437 | } |
michael@0 | 438 | statusMenuitem.setAttribute("disabled", true); |
michael@0 | 439 | aPopup._statusMenuitem = statusMenuitem; |
michael@0 | 440 | } |
michael@0 | 441 | |
michael@0 | 442 | if (aStatus == Ci.mozILivemark.STATUS_LOADING || |
michael@0 | 443 | aStatus == Ci.mozILivemark.STATUS_FAILED) { |
michael@0 | 444 | // Status has changed, update the cached status menuitem. |
michael@0 | 445 | let stringId = aStatus == Ci.mozILivemark.STATUS_LOADING ? |
michael@0 | 446 | "bookmarksLivemarkLoading" : "bookmarksLivemarkFailed"; |
michael@0 | 447 | statusMenuitem.setAttribute("label", PlacesUIUtils.getString(stringId)); |
michael@0 | 448 | if (aPopup._startMarker.nextSibling != statusMenuitem) |
michael@0 | 449 | aPopup.insertBefore(statusMenuitem, aPopup._startMarker.nextSibling); |
michael@0 | 450 | } |
michael@0 | 451 | else { |
michael@0 | 452 | // The livemark has finished loading. |
michael@0 | 453 | if (aPopup._statusMenuitem.parentNode == aPopup) |
michael@0 | 454 | aPopup.removeChild(aPopup._statusMenuitem); |
michael@0 | 455 | } |
michael@0 | 456 | }, |
michael@0 | 457 | |
michael@0 | 458 | toggleCutNode: function PVB_toggleCutNode(aPlacesNode, aValue) { |
michael@0 | 459 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 460 | |
michael@0 | 461 | // We may get the popup for menus, but we need the menu itself. |
michael@0 | 462 | if (elt.localName == "menupopup") |
michael@0 | 463 | elt = elt.parentNode; |
michael@0 | 464 | if (aValue) |
michael@0 | 465 | elt.setAttribute("cutting", "true"); |
michael@0 | 466 | else |
michael@0 | 467 | elt.removeAttribute("cutting"); |
michael@0 | 468 | }, |
michael@0 | 469 | |
michael@0 | 470 | nodeURIChanged: function PVB_nodeURIChanged(aPlacesNode, aURIString) { |
michael@0 | 471 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 472 | |
michael@0 | 473 | // Here we need the <menu>. |
michael@0 | 474 | if (elt.localName == "menupopup") |
michael@0 | 475 | elt = elt.parentNode; |
michael@0 | 476 | |
michael@0 | 477 | elt.setAttribute("scheme", PlacesUIUtils.guessUrlSchemeForUI(aURIString)); |
michael@0 | 478 | }, |
michael@0 | 479 | |
michael@0 | 480 | nodeIconChanged: function PVB_nodeIconChanged(aPlacesNode) { |
michael@0 | 481 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 482 | |
michael@0 | 483 | // There's no UI representation for the root node, thus there's nothing to |
michael@0 | 484 | // be done when the icon changes. |
michael@0 | 485 | if (elt == this._rootElt) |
michael@0 | 486 | return; |
michael@0 | 487 | |
michael@0 | 488 | // Here we need the <menu>. |
michael@0 | 489 | if (elt.localName == "menupopup") |
michael@0 | 490 | elt = elt.parentNode; |
michael@0 | 491 | |
michael@0 | 492 | let icon = aPlacesNode.icon; |
michael@0 | 493 | if (!icon) |
michael@0 | 494 | elt.removeAttribute("image"); |
michael@0 | 495 | else if (icon != elt.getAttribute("image")) |
michael@0 | 496 | elt.setAttribute("image", icon); |
michael@0 | 497 | }, |
michael@0 | 498 | |
michael@0 | 499 | nodeAnnotationChanged: |
michael@0 | 500 | function PVB_nodeAnnotationChanged(aPlacesNode, aAnno) { |
michael@0 | 501 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 502 | |
michael@0 | 503 | // All livemarks have a feedURI, so use it as our indicator of a livemark |
michael@0 | 504 | // being modified. |
michael@0 | 505 | if (aAnno == PlacesUtils.LMANNO_FEEDURI) { |
michael@0 | 506 | let menu = elt.parentNode; |
michael@0 | 507 | if (!menu.hasAttribute("livemark")) { |
michael@0 | 508 | menu.setAttribute("livemark", "true"); |
michael@0 | 509 | #ifdef XP_MACOSX |
michael@0 | 510 | // OS X native menubar doesn't track list-style-images since |
michael@0 | 511 | // it doesn't have a frame (bug 733415). Thus enforce updating. |
michael@0 | 512 | menu.setAttribute("image", ""); |
michael@0 | 513 | menu.removeAttribute("image"); |
michael@0 | 514 | #endif |
michael@0 | 515 | } |
michael@0 | 516 | |
michael@0 | 517 | PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId }) |
michael@0 | 518 | .then(aLivemark => { |
michael@0 | 519 | // Controller will use this to build the meta data for the node. |
michael@0 | 520 | this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark); |
michael@0 | 521 | this.invalidateContainer(aPlacesNode); |
michael@0 | 522 | }, () => undefined); |
michael@0 | 523 | } |
michael@0 | 524 | }, |
michael@0 | 525 | |
michael@0 | 526 | nodeTitleChanged: |
michael@0 | 527 | function PVB_nodeTitleChanged(aPlacesNode, aNewTitle) { |
michael@0 | 528 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 529 | |
michael@0 | 530 | // There's no UI representation for the root node, thus there's |
michael@0 | 531 | // nothing to be done when the title changes. |
michael@0 | 532 | if (elt == this._rootElt) |
michael@0 | 533 | return; |
michael@0 | 534 | |
michael@0 | 535 | // Here we need the <menu>. |
michael@0 | 536 | if (elt.localName == "menupopup") |
michael@0 | 537 | elt = elt.parentNode; |
michael@0 | 538 | |
michael@0 | 539 | if (!aNewTitle && elt.localName != "toolbarbutton") { |
michael@0 | 540 | // Many users consider toolbars as shortcuts containers, so explicitly |
michael@0 | 541 | // allow empty labels on toolbarbuttons. For any other element try to be |
michael@0 | 542 | // smarter, guessing a title from the uri. |
michael@0 | 543 | elt.setAttribute("label", PlacesUIUtils.getBestTitle(aPlacesNode)); |
michael@0 | 544 | } |
michael@0 | 545 | else { |
michael@0 | 546 | elt.setAttribute("label", aNewTitle); |
michael@0 | 547 | } |
michael@0 | 548 | }, |
michael@0 | 549 | |
michael@0 | 550 | nodeRemoved: |
michael@0 | 551 | function PVB_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) { |
michael@0 | 552 | let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode); |
michael@0 | 553 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 554 | |
michael@0 | 555 | // Here we need the <menu>. |
michael@0 | 556 | if (elt.localName == "menupopup") |
michael@0 | 557 | elt = elt.parentNode; |
michael@0 | 558 | |
michael@0 | 559 | if (parentElt._built) { |
michael@0 | 560 | parentElt.removeChild(elt); |
michael@0 | 561 | |
michael@0 | 562 | // Figure out if we need to show the "<Empty>" menu-item. |
michael@0 | 563 | // TODO Bug 517701: This doesn't seem to handle the case of an empty |
michael@0 | 564 | // root. |
michael@0 | 565 | if (parentElt._startMarker.nextSibling == parentElt._endMarker) |
michael@0 | 566 | this._setEmptyPopupStatus(parentElt, true); |
michael@0 | 567 | } |
michael@0 | 568 | }, |
michael@0 | 569 | |
michael@0 | 570 | nodeHistoryDetailsChanged: |
michael@0 | 571 | function PVB_nodeHistoryDetailsChanged(aPlacesNode, aTime, aCount) { |
michael@0 | 572 | if (aPlacesNode.parent && |
michael@0 | 573 | this.controller.hasCachedLivemarkInfo(aPlacesNode.parent)) { |
michael@0 | 574 | // Find the node in the parent. |
michael@0 | 575 | let popup = this._getDOMNodeForPlacesNode(aPlacesNode.parent); |
michael@0 | 576 | for (let child = popup._startMarker.nextSibling; |
michael@0 | 577 | child != popup._endMarker; |
michael@0 | 578 | child = child.nextSibling) { |
michael@0 | 579 | if (child._placesNode && child._placesNode.uri == aPlacesNode.uri) { |
michael@0 | 580 | if (aCount) |
michael@0 | 581 | child.setAttribute("visited", "true"); |
michael@0 | 582 | else |
michael@0 | 583 | child.removeAttribute("visited"); |
michael@0 | 584 | break; |
michael@0 | 585 | } |
michael@0 | 586 | } |
michael@0 | 587 | } |
michael@0 | 588 | }, |
michael@0 | 589 | |
michael@0 | 590 | nodeTagsChanged: function() { }, |
michael@0 | 591 | nodeDateAddedChanged: function() { }, |
michael@0 | 592 | nodeLastModifiedChanged: function() { }, |
michael@0 | 593 | nodeKeywordChanged: function() { }, |
michael@0 | 594 | sortingChanged: function() { }, |
michael@0 | 595 | batching: function() { }, |
michael@0 | 596 | |
michael@0 | 597 | nodeInserted: |
michael@0 | 598 | function PVB_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) { |
michael@0 | 599 | let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode); |
michael@0 | 600 | if (!parentElt._built) |
michael@0 | 601 | return; |
michael@0 | 602 | |
michael@0 | 603 | let index = Array.indexOf(parentElt.childNodes, parentElt._startMarker) + |
michael@0 | 604 | aIndex + 1; |
michael@0 | 605 | this._insertNewItemToPopup(aPlacesNode, parentElt, |
michael@0 | 606 | parentElt.childNodes[index]); |
michael@0 | 607 | this._setEmptyPopupStatus(parentElt, false); |
michael@0 | 608 | }, |
michael@0 | 609 | |
michael@0 | 610 | nodeMoved: |
michael@0 | 611 | function PBV_nodeMoved(aPlacesNode, |
michael@0 | 612 | aOldParentPlacesNode, aOldIndex, |
michael@0 | 613 | aNewParentPlacesNode, aNewIndex) { |
michael@0 | 614 | // Note: the current implementation of moveItem does not actually |
michael@0 | 615 | // use this notification when the item in question is moved from one |
michael@0 | 616 | // folder to another. Instead, it calls nodeRemoved and nodeInserted |
michael@0 | 617 | // for the two folders. Thus, we can assume old-parent == new-parent. |
michael@0 | 618 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 619 | |
michael@0 | 620 | // Here we need the <menu>. |
michael@0 | 621 | if (elt.localName == "menupopup") |
michael@0 | 622 | elt = elt.parentNode; |
michael@0 | 623 | |
michael@0 | 624 | // If our root node is a folder, it might be moved. There's nothing |
michael@0 | 625 | // we need to do in that case. |
michael@0 | 626 | if (elt == this._rootElt) |
michael@0 | 627 | return; |
michael@0 | 628 | |
michael@0 | 629 | let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode); |
michael@0 | 630 | if (parentElt._built) { |
michael@0 | 631 | // Move the node. |
michael@0 | 632 | parentElt.removeChild(elt); |
michael@0 | 633 | let index = Array.indexOf(parentElt.childNodes, parentElt._startMarker) + |
michael@0 | 634 | aNewIndex + 1; |
michael@0 | 635 | parentElt.insertBefore(elt, parentElt.childNodes[index]); |
michael@0 | 636 | } |
michael@0 | 637 | }, |
michael@0 | 638 | |
michael@0 | 639 | containerStateChanged: |
michael@0 | 640 | function PVB_containerStateChanged(aPlacesNode, aOldState, aNewState) { |
michael@0 | 641 | if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED || |
michael@0 | 642 | aNewState == Ci.nsINavHistoryContainerResultNode.STATE_CLOSED) { |
michael@0 | 643 | this.invalidateContainer(aPlacesNode); |
michael@0 | 644 | |
michael@0 | 645 | if (PlacesUtils.nodeIsFolder(aPlacesNode)) { |
michael@0 | 646 | let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions; |
michael@0 | 647 | if (queryOptions.excludeItems) { |
michael@0 | 648 | return; |
michael@0 | 649 | } |
michael@0 | 650 | |
michael@0 | 651 | PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId }) |
michael@0 | 652 | .then(aLivemark => { |
michael@0 | 653 | let shouldInvalidate = |
michael@0 | 654 | !this.controller.hasCachedLivemarkInfo(aPlacesNode); |
michael@0 | 655 | this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark); |
michael@0 | 656 | if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED) { |
michael@0 | 657 | aLivemark.registerForUpdates(aPlacesNode, this); |
michael@0 | 658 | // Prioritize the current livemark. |
michael@0 | 659 | aLivemark.reload(); |
michael@0 | 660 | PlacesUtils.livemarks.reloadLivemarks(); |
michael@0 | 661 | if (shouldInvalidate) |
michael@0 | 662 | this.invalidateContainer(aPlacesNode); |
michael@0 | 663 | } |
michael@0 | 664 | else { |
michael@0 | 665 | aLivemark.unregisterForUpdates(aPlacesNode); |
michael@0 | 666 | } |
michael@0 | 667 | }, () => undefined); |
michael@0 | 668 | } |
michael@0 | 669 | } |
michael@0 | 670 | }, |
michael@0 | 671 | |
michael@0 | 672 | _populateLivemarkPopup: function PVB__populateLivemarkPopup(aPopup) |
michael@0 | 673 | { |
michael@0 | 674 | this._setLivemarkSiteURIMenuItem(aPopup); |
michael@0 | 675 | // Show the loading status only if there are no entries yet. |
michael@0 | 676 | if (aPopup._startMarker.nextSibling == aPopup._endMarker) |
michael@0 | 677 | this._setLivemarkStatusMenuItem(aPopup, Ci.mozILivemark.STATUS_LOADING); |
michael@0 | 678 | |
michael@0 | 679 | PlacesUtils.livemarks.getLivemark({ id: aPopup._placesNode.itemId }) |
michael@0 | 680 | .then(aLivemark => { |
michael@0 | 681 | let placesNode = aPopup._placesNode; |
michael@0 | 682 | if (!placesNode.containerOpen) |
michael@0 | 683 | return; |
michael@0 | 684 | |
michael@0 | 685 | if (aLivemark.status != Ci.mozILivemark.STATUS_LOADING) |
michael@0 | 686 | this._setLivemarkStatusMenuItem(aPopup, aLivemark.status); |
michael@0 | 687 | this._cleanPopup(aPopup, |
michael@0 | 688 | this._nativeView && aPopup.parentNode.hasAttribute("open")); |
michael@0 | 689 | |
michael@0 | 690 | let children = aLivemark.getNodesForContainer(placesNode); |
michael@0 | 691 | for (let i = 0; i < children.length; i++) { |
michael@0 | 692 | let child = children[i]; |
michael@0 | 693 | this.nodeInserted(placesNode, child, i); |
michael@0 | 694 | if (child.accessCount) |
michael@0 | 695 | this._getDOMNodeForPlacesNode(child).setAttribute("visited", true); |
michael@0 | 696 | else |
michael@0 | 697 | this._getDOMNodeForPlacesNode(child).removeAttribute("visited"); |
michael@0 | 698 | } |
michael@0 | 699 | }, Components.utils.reportError); |
michael@0 | 700 | }, |
michael@0 | 701 | |
michael@0 | 702 | invalidateContainer: function PVB_invalidateContainer(aPlacesNode) { |
michael@0 | 703 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 704 | elt._built = false; |
michael@0 | 705 | |
michael@0 | 706 | // If the menupopup is open we should live-update it. |
michael@0 | 707 | if (elt.parentNode.open) |
michael@0 | 708 | this._rebuildPopup(elt); |
michael@0 | 709 | }, |
michael@0 | 710 | |
michael@0 | 711 | uninit: function PVB_uninit() { |
michael@0 | 712 | if (this._result) { |
michael@0 | 713 | this._result.removeObserver(this); |
michael@0 | 714 | this._resultNode.containerOpen = false; |
michael@0 | 715 | this._resultNode = null; |
michael@0 | 716 | this._result = null; |
michael@0 | 717 | } |
michael@0 | 718 | |
michael@0 | 719 | if (this._controller) { |
michael@0 | 720 | this._controller.terminate(); |
michael@0 | 721 | // Removing the controller will fail if it is already no longer there. |
michael@0 | 722 | // This can happen if the view element was removed/reinserted without |
michael@0 | 723 | // our knowledge. There is no way to check for that having happened |
michael@0 | 724 | // without the possibility of an exception. :-( |
michael@0 | 725 | try { |
michael@0 | 726 | this._viewElt.controllers.removeController(this._controller); |
michael@0 | 727 | } catch (ex) { |
michael@0 | 728 | } finally { |
michael@0 | 729 | this._controller = null; |
michael@0 | 730 | } |
michael@0 | 731 | } |
michael@0 | 732 | |
michael@0 | 733 | delete this._viewElt._placesView; |
michael@0 | 734 | }, |
michael@0 | 735 | |
michael@0 | 736 | get isRTL() { |
michael@0 | 737 | if ("_isRTL" in this) |
michael@0 | 738 | return this._isRTL; |
michael@0 | 739 | |
michael@0 | 740 | return this._isRTL = document.defaultView |
michael@0 | 741 | .getComputedStyle(this.viewElt, "") |
michael@0 | 742 | .direction == "rtl"; |
michael@0 | 743 | }, |
michael@0 | 744 | |
michael@0 | 745 | get ownerWindow() window, |
michael@0 | 746 | |
michael@0 | 747 | /** |
michael@0 | 748 | * Adds an "Open All in Tabs" menuitem to the bottom of the popup. |
michael@0 | 749 | * @param aPopup |
michael@0 | 750 | * a Places popup. |
michael@0 | 751 | */ |
michael@0 | 752 | _mayAddCommandsItems: function PVB__mayAddCommandsItems(aPopup) { |
michael@0 | 753 | // The command items are never added to the root popup. |
michael@0 | 754 | if (aPopup == this._rootElt) |
michael@0 | 755 | return; |
michael@0 | 756 | |
michael@0 | 757 | let hasMultipleURIs = false; |
michael@0 | 758 | |
michael@0 | 759 | // Check if the popup contains at least 2 menuitems with places nodes. |
michael@0 | 760 | // We don't currently support opening multiple uri nodes when they are not |
michael@0 | 761 | // populated by the result. |
michael@0 | 762 | if (aPopup._placesNode.childCount > 0) { |
michael@0 | 763 | let currentChild = aPopup.firstChild; |
michael@0 | 764 | let numURINodes = 0; |
michael@0 | 765 | while (currentChild) { |
michael@0 | 766 | if (currentChild.localName == "menuitem" && currentChild._placesNode) { |
michael@0 | 767 | if (++numURINodes == 2) |
michael@0 | 768 | break; |
michael@0 | 769 | } |
michael@0 | 770 | currentChild = currentChild.nextSibling; |
michael@0 | 771 | } |
michael@0 | 772 | hasMultipleURIs = numURINodes > 1; |
michael@0 | 773 | } |
michael@0 | 774 | |
michael@0 | 775 | if (!hasMultipleURIs) { |
michael@0 | 776 | aPopup.setAttribute("singleitempopup", "true"); |
michael@0 | 777 | } else { |
michael@0 | 778 | aPopup.removeAttribute("singleitempopup"); |
michael@0 | 779 | } |
michael@0 | 780 | |
michael@0 | 781 | if (!hasMultipleURIs) { |
michael@0 | 782 | // We don't have to show any option. |
michael@0 | 783 | if (aPopup._endOptOpenAllInTabs) { |
michael@0 | 784 | aPopup.removeChild(aPopup._endOptOpenAllInTabs); |
michael@0 | 785 | aPopup._endOptOpenAllInTabs = null; |
michael@0 | 786 | |
michael@0 | 787 | aPopup.removeChild(aPopup._endOptSeparator); |
michael@0 | 788 | aPopup._endOptSeparator = null; |
michael@0 | 789 | } |
michael@0 | 790 | } |
michael@0 | 791 | else if (!aPopup._endOptOpenAllInTabs) { |
michael@0 | 792 | // Create a separator before options. |
michael@0 | 793 | aPopup._endOptSeparator = document.createElement("menuseparator"); |
michael@0 | 794 | aPopup._endOptSeparator.className = "bookmarks-actions-menuseparator"; |
michael@0 | 795 | aPopup.appendChild(aPopup._endOptSeparator); |
michael@0 | 796 | |
michael@0 | 797 | // Add the "Open All in Tabs" menuitem. |
michael@0 | 798 | aPopup._endOptOpenAllInTabs = document.createElement("menuitem"); |
michael@0 | 799 | aPopup._endOptOpenAllInTabs.className = "openintabs-menuitem"; |
michael@0 | 800 | |
michael@0 | 801 | if (typeof this.options.extraClasses.entry == "string") |
michael@0 | 802 | aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.entry); |
michael@0 | 803 | if (typeof this.options.extraClasses.footer == "string") |
michael@0 | 804 | aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.footer); |
michael@0 | 805 | |
michael@0 | 806 | aPopup._endOptOpenAllInTabs.setAttribute("oncommand", |
michael@0 | 807 | "PlacesUIUtils.openContainerNodeInTabs(this.parentNode._placesNode, event, " + |
michael@0 | 808 | "PlacesUIUtils.getViewForNode(this));"); |
michael@0 | 809 | aPopup._endOptOpenAllInTabs.setAttribute("onclick", |
michael@0 | 810 | "checkForMiddleClick(this, event); event.stopPropagation();"); |
michael@0 | 811 | aPopup._endOptOpenAllInTabs.setAttribute("label", |
michael@0 | 812 | gNavigatorBundle.getString("menuOpenAllInTabs.label")); |
michael@0 | 813 | aPopup.appendChild(aPopup._endOptOpenAllInTabs); |
michael@0 | 814 | } |
michael@0 | 815 | }, |
michael@0 | 816 | |
michael@0 | 817 | _ensureMarkers: function PVB__ensureMarkers(aPopup) { |
michael@0 | 818 | if (aPopup._startMarker) |
michael@0 | 819 | return; |
michael@0 | 820 | |
michael@0 | 821 | // _startMarker is an hidden menuseparator that lives before places nodes. |
michael@0 | 822 | aPopup._startMarker = document.createElement("menuseparator"); |
michael@0 | 823 | aPopup._startMarker.hidden = true; |
michael@0 | 824 | aPopup.insertBefore(aPopup._startMarker, aPopup.firstChild); |
michael@0 | 825 | |
michael@0 | 826 | // _endMarker is a DOM node that lives after places nodes, specified with |
michael@0 | 827 | // the 'insertionPoint' option or will be a hidden menuseparator. |
michael@0 | 828 | let node = ("insertionPoint" in this.options) ? |
michael@0 | 829 | aPopup.querySelector(this.options.insertionPoint) : null; |
michael@0 | 830 | if (node) { |
michael@0 | 831 | aPopup._endMarker = node; |
michael@0 | 832 | } else { |
michael@0 | 833 | aPopup._endMarker = document.createElement("menuseparator"); |
michael@0 | 834 | aPopup._endMarker.hidden = true; |
michael@0 | 835 | } |
michael@0 | 836 | aPopup.appendChild(aPopup._endMarker); |
michael@0 | 837 | |
michael@0 | 838 | // Move the markers to the right position. |
michael@0 | 839 | let firstNonStaticNodeFound = false; |
michael@0 | 840 | for (let i = 0; i < aPopup.childNodes.length; i++) { |
michael@0 | 841 | let child = aPopup.childNodes[i]; |
michael@0 | 842 | // Menus that have static content at the end, but are initially empty, |
michael@0 | 843 | // use a special "builder" attribute to figure out where to start |
michael@0 | 844 | // inserting places nodes. |
michael@0 | 845 | if (child.getAttribute("builder") == "end") { |
michael@0 | 846 | aPopup.insertBefore(aPopup._endMarker, child); |
michael@0 | 847 | break; |
michael@0 | 848 | } |
michael@0 | 849 | |
michael@0 | 850 | if (child._placesNode && !firstNonStaticNodeFound) { |
michael@0 | 851 | firstNonStaticNodeFound = true; |
michael@0 | 852 | aPopup.insertBefore(aPopup._startMarker, child); |
michael@0 | 853 | } |
michael@0 | 854 | } |
michael@0 | 855 | if (!firstNonStaticNodeFound) { |
michael@0 | 856 | aPopup.insertBefore(aPopup._startMarker, aPopup._endMarker); |
michael@0 | 857 | } |
michael@0 | 858 | }, |
michael@0 | 859 | |
michael@0 | 860 | _onPopupShowing: function PVB__onPopupShowing(aEvent) { |
michael@0 | 861 | // Avoid handling popupshowing of inner views. |
michael@0 | 862 | let popup = aEvent.originalTarget; |
michael@0 | 863 | |
michael@0 | 864 | this._ensureMarkers(popup); |
michael@0 | 865 | |
michael@0 | 866 | // Remove any delayed element, see _cleanPopup for details. |
michael@0 | 867 | if ("_delayedRemovals" in popup) { |
michael@0 | 868 | while (popup._delayedRemovals.length > 0) { |
michael@0 | 869 | popup.removeChild(popup._delayedRemovals.shift()); |
michael@0 | 870 | } |
michael@0 | 871 | } |
michael@0 | 872 | |
michael@0 | 873 | if (popup._placesNode && PlacesUIUtils.getViewForNode(popup) == this) { |
michael@0 | 874 | if (!popup._placesNode.containerOpen) |
michael@0 | 875 | popup._placesNode.containerOpen = true; |
michael@0 | 876 | if (!popup._built) |
michael@0 | 877 | this._rebuildPopup(popup); |
michael@0 | 878 | |
michael@0 | 879 | this._mayAddCommandsItems(popup); |
michael@0 | 880 | } |
michael@0 | 881 | }, |
michael@0 | 882 | |
michael@0 | 883 | _addEventListeners: |
michael@0 | 884 | function PVB__addEventListeners(aObject, aEventNames, aCapturing) { |
michael@0 | 885 | for (let i = 0; i < aEventNames.length; i++) { |
michael@0 | 886 | aObject.addEventListener(aEventNames[i], this, aCapturing); |
michael@0 | 887 | } |
michael@0 | 888 | }, |
michael@0 | 889 | |
michael@0 | 890 | _removeEventListeners: |
michael@0 | 891 | function PVB__removeEventListeners(aObject, aEventNames, aCapturing) { |
michael@0 | 892 | for (let i = 0; i < aEventNames.length; i++) { |
michael@0 | 893 | aObject.removeEventListener(aEventNames[i], this, aCapturing); |
michael@0 | 894 | } |
michael@0 | 895 | }, |
michael@0 | 896 | }; |
michael@0 | 897 | |
michael@0 | 898 | function PlacesToolbar(aPlace) { |
michael@0 | 899 | let startTime = Date.now(); |
michael@0 | 900 | // Add some smart getters for our elements. |
michael@0 | 901 | let thisView = this; |
michael@0 | 902 | [ |
michael@0 | 903 | ["_viewElt", "PlacesToolbar"], |
michael@0 | 904 | ["_rootElt", "PlacesToolbarItems"], |
michael@0 | 905 | ["_dropIndicator", "PlacesToolbarDropIndicator"], |
michael@0 | 906 | ["_chevron", "PlacesChevron"], |
michael@0 | 907 | ["_chevronPopup", "PlacesChevronPopup"] |
michael@0 | 908 | ].forEach(function (elementGlobal) { |
michael@0 | 909 | let [name, id] = elementGlobal; |
michael@0 | 910 | thisView.__defineGetter__(name, function () { |
michael@0 | 911 | let element = document.getElementById(id); |
michael@0 | 912 | if (!element) |
michael@0 | 913 | return null; |
michael@0 | 914 | |
michael@0 | 915 | delete thisView[name]; |
michael@0 | 916 | return thisView[name] = element; |
michael@0 | 917 | }); |
michael@0 | 918 | }); |
michael@0 | 919 | |
michael@0 | 920 | this._viewElt._placesView = this; |
michael@0 | 921 | |
michael@0 | 922 | this._addEventListeners(this._viewElt, this._cbEvents, false); |
michael@0 | 923 | this._addEventListeners(this._rootElt, ["popupshowing", "popuphidden"], true); |
michael@0 | 924 | this._addEventListeners(this._rootElt, ["overflow", "underflow"], true); |
michael@0 | 925 | this._addEventListeners(window, ["resize", "unload"], false); |
michael@0 | 926 | |
michael@0 | 927 | // If personal-bookmarks has been dragged to the tabs toolbar, |
michael@0 | 928 | // we have to track addition and removals of tabs, to properly |
michael@0 | 929 | // recalculate the available space for bookmarks. |
michael@0 | 930 | // TODO (bug 734730): Use a performant mutation listener when available. |
michael@0 | 931 | if (this._viewElt.parentNode.parentNode == document.getElementById("TabsToolbar")) { |
michael@0 | 932 | this._addEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false); |
michael@0 | 933 | } |
michael@0 | 934 | |
michael@0 | 935 | PlacesViewBase.call(this, aPlace); |
michael@0 | 936 | |
michael@0 | 937 | Services.telemetry.getHistogramById("FX_BOOKMARKS_TOOLBAR_INIT_MS") |
michael@0 | 938 | .add(Date.now() - startTime); |
michael@0 | 939 | } |
michael@0 | 940 | |
michael@0 | 941 | PlacesToolbar.prototype = { |
michael@0 | 942 | __proto__: PlacesViewBase.prototype, |
michael@0 | 943 | |
michael@0 | 944 | _cbEvents: ["dragstart", "dragover", "dragexit", "dragend", "drop", |
michael@0 | 945 | "mousemove", "mouseover", "mouseout"], |
michael@0 | 946 | |
michael@0 | 947 | QueryInterface: function PT_QueryInterface(aIID) { |
michael@0 | 948 | if (aIID.equals(Ci.nsIDOMEventListener) || |
michael@0 | 949 | aIID.equals(Ci.nsITimerCallback)) |
michael@0 | 950 | return this; |
michael@0 | 951 | |
michael@0 | 952 | return PlacesViewBase.prototype.QueryInterface.apply(this, arguments); |
michael@0 | 953 | }, |
michael@0 | 954 | |
michael@0 | 955 | uninit: function PT_uninit() { |
michael@0 | 956 | this._removeEventListeners(this._viewElt, this._cbEvents, false); |
michael@0 | 957 | this._removeEventListeners(this._rootElt, ["popupshowing", "popuphidden"], |
michael@0 | 958 | true); |
michael@0 | 959 | this._removeEventListeners(this._rootElt, ["overflow", "underflow"], true); |
michael@0 | 960 | this._removeEventListeners(window, ["resize", "unload"], false); |
michael@0 | 961 | this._removeEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false); |
michael@0 | 962 | |
michael@0 | 963 | if (this._chevron._placesView) { |
michael@0 | 964 | this._chevron._placesView.uninit(); |
michael@0 | 965 | } |
michael@0 | 966 | |
michael@0 | 967 | PlacesViewBase.prototype.uninit.apply(this, arguments); |
michael@0 | 968 | }, |
michael@0 | 969 | |
michael@0 | 970 | _openedMenuButton: null, |
michael@0 | 971 | _allowPopupShowing: true, |
michael@0 | 972 | |
michael@0 | 973 | _rebuild: function PT__rebuild() { |
michael@0 | 974 | // Clear out references to existing nodes, since they will be removed |
michael@0 | 975 | // and re-added. |
michael@0 | 976 | if (this._overFolder.elt) |
michael@0 | 977 | this._clearOverFolder(); |
michael@0 | 978 | |
michael@0 | 979 | this._openedMenuButton = null; |
michael@0 | 980 | while (this._rootElt.hasChildNodes()) { |
michael@0 | 981 | this._rootElt.removeChild(this._rootElt.firstChild); |
michael@0 | 982 | } |
michael@0 | 983 | |
michael@0 | 984 | let cc = this._resultNode.childCount; |
michael@0 | 985 | for (let i = 0; i < cc; ++i) { |
michael@0 | 986 | this._insertNewItem(this._resultNode.getChild(i), null); |
michael@0 | 987 | } |
michael@0 | 988 | |
michael@0 | 989 | if (this._chevronPopup.hasAttribute("type")) { |
michael@0 | 990 | // Chevron has already been initialized, but since we are forcing |
michael@0 | 991 | // a rebuild of the toolbar, it has to be rebuilt. |
michael@0 | 992 | // Otherwise, it will be initialized when the toolbar overflows. |
michael@0 | 993 | this._chevronPopup.place = this.place; |
michael@0 | 994 | } |
michael@0 | 995 | }, |
michael@0 | 996 | |
michael@0 | 997 | _insertNewItem: |
michael@0 | 998 | function PT__insertNewItem(aChild, aBefore) { |
michael@0 | 999 | this._domNodes.delete(aChild); |
michael@0 | 1000 | |
michael@0 | 1001 | let type = aChild.type; |
michael@0 | 1002 | let button; |
michael@0 | 1003 | if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) { |
michael@0 | 1004 | button = document.createElement("toolbarseparator"); |
michael@0 | 1005 | } |
michael@0 | 1006 | else { |
michael@0 | 1007 | button = document.createElement("toolbarbutton"); |
michael@0 | 1008 | button.className = "bookmark-item"; |
michael@0 | 1009 | button.setAttribute("label", aChild.title); |
michael@0 | 1010 | let icon = aChild.icon; |
michael@0 | 1011 | if (icon) |
michael@0 | 1012 | button.setAttribute("image", icon); |
michael@0 | 1013 | |
michael@0 | 1014 | if (PlacesUtils.containerTypes.indexOf(type) != -1) { |
michael@0 | 1015 | button.setAttribute("type", "menu"); |
michael@0 | 1016 | button.setAttribute("container", "true"); |
michael@0 | 1017 | |
michael@0 | 1018 | if (PlacesUtils.nodeIsQuery(aChild)) { |
michael@0 | 1019 | button.setAttribute("query", "true"); |
michael@0 | 1020 | if (PlacesUtils.nodeIsTagQuery(aChild)) |
michael@0 | 1021 | button.setAttribute("tagContainer", "true"); |
michael@0 | 1022 | } |
michael@0 | 1023 | else if (PlacesUtils.nodeIsFolder(aChild)) { |
michael@0 | 1024 | PlacesUtils.livemarks.getLivemark({ id: aChild.itemId }) |
michael@0 | 1025 | .then(aLivemark => { |
michael@0 | 1026 | button.setAttribute("livemark", "true"); |
michael@0 | 1027 | this.controller.cacheLivemarkInfo(aChild, aLivemark); |
michael@0 | 1028 | }, () => undefined); |
michael@0 | 1029 | } |
michael@0 | 1030 | |
michael@0 | 1031 | let popup = document.createElement("menupopup"); |
michael@0 | 1032 | popup.setAttribute("placespopup", "true"); |
michael@0 | 1033 | button.appendChild(popup); |
michael@0 | 1034 | popup._placesNode = PlacesUtils.asContainer(aChild); |
michael@0 | 1035 | popup.setAttribute("context", "placesContext"); |
michael@0 | 1036 | |
michael@0 | 1037 | this._domNodes.set(aChild, popup); |
michael@0 | 1038 | } |
michael@0 | 1039 | else if (PlacesUtils.nodeIsURI(aChild)) { |
michael@0 | 1040 | button.setAttribute("scheme", |
michael@0 | 1041 | PlacesUIUtils.guessUrlSchemeForUI(aChild.uri)); |
michael@0 | 1042 | } |
michael@0 | 1043 | } |
michael@0 | 1044 | |
michael@0 | 1045 | button._placesNode = aChild; |
michael@0 | 1046 | if (!this._domNodes.has(aChild)) |
michael@0 | 1047 | this._domNodes.set(aChild, button); |
michael@0 | 1048 | |
michael@0 | 1049 | if (aBefore) |
michael@0 | 1050 | this._rootElt.insertBefore(button, aBefore); |
michael@0 | 1051 | else |
michael@0 | 1052 | this._rootElt.appendChild(button); |
michael@0 | 1053 | }, |
michael@0 | 1054 | |
michael@0 | 1055 | _updateChevronPopupNodesVisibility: |
michael@0 | 1056 | function PT__updateChevronPopupNodesVisibility() { |
michael@0 | 1057 | for (let i = 0, node = this._chevronPopup._startMarker.nextSibling; |
michael@0 | 1058 | node != this._chevronPopup._endMarker; |
michael@0 | 1059 | i++, node = node.nextSibling) { |
michael@0 | 1060 | node.hidden = this._rootElt.childNodes[i].style.visibility != "hidden"; |
michael@0 | 1061 | } |
michael@0 | 1062 | }, |
michael@0 | 1063 | |
michael@0 | 1064 | _onChevronPopupShowing: |
michael@0 | 1065 | function PT__onChevronPopupShowing(aEvent) { |
michael@0 | 1066 | // Handle popupshowing only for the chevron popup, not for nested ones. |
michael@0 | 1067 | if (aEvent.target != this._chevronPopup) |
michael@0 | 1068 | return; |
michael@0 | 1069 | |
michael@0 | 1070 | if (!this._chevron._placesView) |
michael@0 | 1071 | this._chevron._placesView = new PlacesMenu(aEvent, this.place); |
michael@0 | 1072 | |
michael@0 | 1073 | this._updateChevronPopupNodesVisibility(); |
michael@0 | 1074 | }, |
michael@0 | 1075 | |
michael@0 | 1076 | handleEvent: function PT_handleEvent(aEvent) { |
michael@0 | 1077 | switch (aEvent.type) { |
michael@0 | 1078 | case "unload": |
michael@0 | 1079 | this.uninit(); |
michael@0 | 1080 | break; |
michael@0 | 1081 | case "resize": |
michael@0 | 1082 | // This handler updates nodes visibility in both the toolbar |
michael@0 | 1083 | // and the chevron popup when a window resize does not change |
michael@0 | 1084 | // the overflow status of the toolbar. |
michael@0 | 1085 | this.updateChevron(); |
michael@0 | 1086 | break; |
michael@0 | 1087 | case "overflow": |
michael@0 | 1088 | if (!this._isOverflowStateEventRelevant(aEvent)) |
michael@0 | 1089 | return; |
michael@0 | 1090 | this._onOverflow(); |
michael@0 | 1091 | break; |
michael@0 | 1092 | case "underflow": |
michael@0 | 1093 | if (!this._isOverflowStateEventRelevant(aEvent)) |
michael@0 | 1094 | return; |
michael@0 | 1095 | this._onUnderflow(); |
michael@0 | 1096 | break; |
michael@0 | 1097 | case "TabOpen": |
michael@0 | 1098 | case "TabClose": |
michael@0 | 1099 | this.updateChevron(); |
michael@0 | 1100 | break; |
michael@0 | 1101 | case "dragstart": |
michael@0 | 1102 | this._onDragStart(aEvent); |
michael@0 | 1103 | break; |
michael@0 | 1104 | case "dragover": |
michael@0 | 1105 | this._onDragOver(aEvent); |
michael@0 | 1106 | break; |
michael@0 | 1107 | case "dragexit": |
michael@0 | 1108 | this._onDragExit(aEvent); |
michael@0 | 1109 | break; |
michael@0 | 1110 | case "dragend": |
michael@0 | 1111 | this._onDragEnd(aEvent); |
michael@0 | 1112 | break; |
michael@0 | 1113 | case "drop": |
michael@0 | 1114 | this._onDrop(aEvent); |
michael@0 | 1115 | break; |
michael@0 | 1116 | case "mouseover": |
michael@0 | 1117 | this._onMouseOver(aEvent); |
michael@0 | 1118 | break; |
michael@0 | 1119 | case "mousemove": |
michael@0 | 1120 | this._onMouseMove(aEvent); |
michael@0 | 1121 | break; |
michael@0 | 1122 | case "mouseout": |
michael@0 | 1123 | this._onMouseOut(aEvent); |
michael@0 | 1124 | break; |
michael@0 | 1125 | case "popupshowing": |
michael@0 | 1126 | this._onPopupShowing(aEvent); |
michael@0 | 1127 | break; |
michael@0 | 1128 | case "popuphidden": |
michael@0 | 1129 | this._onPopupHidden(aEvent); |
michael@0 | 1130 | break; |
michael@0 | 1131 | default: |
michael@0 | 1132 | throw "Trying to handle unexpected event."; |
michael@0 | 1133 | } |
michael@0 | 1134 | }, |
michael@0 | 1135 | |
michael@0 | 1136 | updateOverflowStatus: function() { |
michael@0 | 1137 | if (this._rootElt.scrollLeftMax > 0) { |
michael@0 | 1138 | this._onOverflow(); |
michael@0 | 1139 | } else { |
michael@0 | 1140 | this._onUnderflow(); |
michael@0 | 1141 | } |
michael@0 | 1142 | }, |
michael@0 | 1143 | |
michael@0 | 1144 | _isOverflowStateEventRelevant: function PT_isOverflowStateEventRelevant(aEvent) { |
michael@0 | 1145 | // Ignore events not aimed at ourselves, as well as purely vertical ones: |
michael@0 | 1146 | return aEvent.target == aEvent.currentTarget && aEvent.detail > 0; |
michael@0 | 1147 | }, |
michael@0 | 1148 | |
michael@0 | 1149 | _onOverflow: function PT_onOverflow() { |
michael@0 | 1150 | // Attach the popup binding to the chevron popup if it has not yet |
michael@0 | 1151 | // been initialized. |
michael@0 | 1152 | if (!this._chevronPopup.hasAttribute("type")) { |
michael@0 | 1153 | this._chevronPopup.setAttribute("place", this.place); |
michael@0 | 1154 | this._chevronPopup.setAttribute("type", "places"); |
michael@0 | 1155 | } |
michael@0 | 1156 | this._chevron.collapsed = false; |
michael@0 | 1157 | this.updateChevron(); |
michael@0 | 1158 | }, |
michael@0 | 1159 | |
michael@0 | 1160 | _onUnderflow: function PT_onUnderflow() { |
michael@0 | 1161 | this.updateChevron(); |
michael@0 | 1162 | this._chevron.collapsed = true; |
michael@0 | 1163 | }, |
michael@0 | 1164 | |
michael@0 | 1165 | updateChevron: function PT_updateChevron() { |
michael@0 | 1166 | // If the chevron is collapsed there's nothing to update. |
michael@0 | 1167 | if (this._chevron.collapsed) |
michael@0 | 1168 | return; |
michael@0 | 1169 | |
michael@0 | 1170 | // Update the chevron on a timer. This will avoid repeated work when |
michael@0 | 1171 | // lot of changes happen in a small timeframe. |
michael@0 | 1172 | if (this._updateChevronTimer) |
michael@0 | 1173 | this._updateChevronTimer.cancel(); |
michael@0 | 1174 | |
michael@0 | 1175 | this._updateChevronTimer = this._setTimer(100); |
michael@0 | 1176 | }, |
michael@0 | 1177 | |
michael@0 | 1178 | _updateChevronTimerCallback: function PT__updateChevronTimerCallback() { |
michael@0 | 1179 | let scrollRect = this._rootElt.getBoundingClientRect(); |
michael@0 | 1180 | let childOverflowed = false; |
michael@0 | 1181 | for (let i = 0; i < this._rootElt.childNodes.length; i++) { |
michael@0 | 1182 | let child = this._rootElt.childNodes[i]; |
michael@0 | 1183 | // Once a child overflows, all the next ones will. |
michael@0 | 1184 | if (!childOverflowed) { |
michael@0 | 1185 | let childRect = child.getBoundingClientRect(); |
michael@0 | 1186 | childOverflowed = this.isRTL ? (childRect.left < scrollRect.left) |
michael@0 | 1187 | : (childRect.right > scrollRect.right); |
michael@0 | 1188 | |
michael@0 | 1189 | } |
michael@0 | 1190 | child.style.visibility = childOverflowed ? "hidden" : "visible"; |
michael@0 | 1191 | } |
michael@0 | 1192 | |
michael@0 | 1193 | // We rebuild the chevron on popupShowing, so if it is open |
michael@0 | 1194 | // we must update it. |
michael@0 | 1195 | if (this._chevron.open) |
michael@0 | 1196 | this._updateChevronPopupNodesVisibility(); |
michael@0 | 1197 | }, |
michael@0 | 1198 | |
michael@0 | 1199 | nodeInserted: |
michael@0 | 1200 | function PT_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) { |
michael@0 | 1201 | let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode); |
michael@0 | 1202 | if (parentElt == this._rootElt) { |
michael@0 | 1203 | let children = this._rootElt.childNodes; |
michael@0 | 1204 | this._insertNewItem(aPlacesNode, |
michael@0 | 1205 | aIndex < children.length ? children[aIndex] : null); |
michael@0 | 1206 | this.updateChevron(); |
michael@0 | 1207 | return; |
michael@0 | 1208 | } |
michael@0 | 1209 | |
michael@0 | 1210 | PlacesViewBase.prototype.nodeInserted.apply(this, arguments); |
michael@0 | 1211 | }, |
michael@0 | 1212 | |
michael@0 | 1213 | nodeRemoved: |
michael@0 | 1214 | function PT_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) { |
michael@0 | 1215 | let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode); |
michael@0 | 1216 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 1217 | |
michael@0 | 1218 | // Here we need the <menu>. |
michael@0 | 1219 | if (elt.localName == "menupopup") |
michael@0 | 1220 | elt = elt.parentNode; |
michael@0 | 1221 | |
michael@0 | 1222 | if (parentElt == this._rootElt) { |
michael@0 | 1223 | this._removeChild(elt); |
michael@0 | 1224 | this.updateChevron(); |
michael@0 | 1225 | return; |
michael@0 | 1226 | } |
michael@0 | 1227 | |
michael@0 | 1228 | PlacesViewBase.prototype.nodeRemoved.apply(this, arguments); |
michael@0 | 1229 | }, |
michael@0 | 1230 | |
michael@0 | 1231 | nodeMoved: |
michael@0 | 1232 | function PT_nodeMoved(aPlacesNode, |
michael@0 | 1233 | aOldParentPlacesNode, aOldIndex, |
michael@0 | 1234 | aNewParentPlacesNode, aNewIndex) { |
michael@0 | 1235 | let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode); |
michael@0 | 1236 | if (parentElt == this._rootElt) { |
michael@0 | 1237 | // Container is on the toolbar. |
michael@0 | 1238 | |
michael@0 | 1239 | // Move the element. |
michael@0 | 1240 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 1241 | |
michael@0 | 1242 | // Here we need the <menu>. |
michael@0 | 1243 | if (elt.localName == "menupopup") |
michael@0 | 1244 | elt = elt.parentNode; |
michael@0 | 1245 | |
michael@0 | 1246 | this._removeChild(elt); |
michael@0 | 1247 | this._rootElt.insertBefore(elt, this._rootElt.childNodes[aNewIndex]); |
michael@0 | 1248 | |
michael@0 | 1249 | // The chevron view may get nodeMoved after the toolbar. In such a case, |
michael@0 | 1250 | // we should ensure (by manually swapping menuitems) that the actual nodes |
michael@0 | 1251 | // are in the final position before updateChevron tries to updates their |
michael@0 | 1252 | // visibility, or the chevron may go out of sync. |
michael@0 | 1253 | // Luckily updateChevron runs on a timer, so, by the time it updates |
michael@0 | 1254 | // nodes, the menu has already handled the notification. |
michael@0 | 1255 | |
michael@0 | 1256 | this.updateChevron(); |
michael@0 | 1257 | return; |
michael@0 | 1258 | } |
michael@0 | 1259 | |
michael@0 | 1260 | PlacesViewBase.prototype.nodeMoved.apply(this, arguments); |
michael@0 | 1261 | }, |
michael@0 | 1262 | |
michael@0 | 1263 | nodeAnnotationChanged: |
michael@0 | 1264 | function PT_nodeAnnotationChanged(aPlacesNode, aAnno) { |
michael@0 | 1265 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 1266 | if (elt == this._rootElt) |
michael@0 | 1267 | return; |
michael@0 | 1268 | |
michael@0 | 1269 | // We're notified for the menupopup, not the containing toolbarbutton. |
michael@0 | 1270 | if (elt.localName == "menupopup") |
michael@0 | 1271 | elt = elt.parentNode; |
michael@0 | 1272 | |
michael@0 | 1273 | if (elt.parentNode == this._rootElt) { |
michael@0 | 1274 | // Node is on the toolbar. |
michael@0 | 1275 | |
michael@0 | 1276 | // All livemarks have a feedURI, so use it as our indicator. |
michael@0 | 1277 | if (aAnno == PlacesUtils.LMANNO_FEEDURI) { |
michael@0 | 1278 | elt.setAttribute("livemark", true); |
michael@0 | 1279 | |
michael@0 | 1280 | PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId }) |
michael@0 | 1281 | .then(aLivemark => { |
michael@0 | 1282 | this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark); |
michael@0 | 1283 | this.invalidateContainer(aPlacesNode); |
michael@0 | 1284 | }, Components.utils.reportError); |
michael@0 | 1285 | } |
michael@0 | 1286 | } |
michael@0 | 1287 | else { |
michael@0 | 1288 | // Node is in a submenu. |
michael@0 | 1289 | PlacesViewBase.prototype.nodeAnnotationChanged.apply(this, arguments); |
michael@0 | 1290 | } |
michael@0 | 1291 | }, |
michael@0 | 1292 | |
michael@0 | 1293 | nodeTitleChanged: function PT_nodeTitleChanged(aPlacesNode, aNewTitle) { |
michael@0 | 1294 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 1295 | |
michael@0 | 1296 | // There's no UI representation for the root node, thus there's |
michael@0 | 1297 | // nothing to be done when the title changes. |
michael@0 | 1298 | if (elt == this._rootElt) |
michael@0 | 1299 | return; |
michael@0 | 1300 | |
michael@0 | 1301 | PlacesViewBase.prototype.nodeTitleChanged.apply(this, arguments); |
michael@0 | 1302 | |
michael@0 | 1303 | // Here we need the <menu>. |
michael@0 | 1304 | if (elt.localName == "menupopup") |
michael@0 | 1305 | elt = elt.parentNode; |
michael@0 | 1306 | |
michael@0 | 1307 | if (elt.parentNode == this._rootElt) { |
michael@0 | 1308 | // Node is on the toolbar |
michael@0 | 1309 | this.updateChevron(); |
michael@0 | 1310 | } |
michael@0 | 1311 | }, |
michael@0 | 1312 | |
michael@0 | 1313 | invalidateContainer: function PT_invalidateContainer(aPlacesNode) { |
michael@0 | 1314 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 1315 | if (elt == this._rootElt) { |
michael@0 | 1316 | // Container is the toolbar itself. |
michael@0 | 1317 | this._rebuild(); |
michael@0 | 1318 | return; |
michael@0 | 1319 | } |
michael@0 | 1320 | |
michael@0 | 1321 | PlacesViewBase.prototype.invalidateContainer.apply(this, arguments); |
michael@0 | 1322 | }, |
michael@0 | 1323 | |
michael@0 | 1324 | _overFolder: { elt: null, |
michael@0 | 1325 | openTimer: null, |
michael@0 | 1326 | hoverTime: 350, |
michael@0 | 1327 | closeTimer: null }, |
michael@0 | 1328 | |
michael@0 | 1329 | _clearOverFolder: function PT__clearOverFolder() { |
michael@0 | 1330 | // The mouse is no longer dragging over the stored menubutton. |
michael@0 | 1331 | // Close the menubutton, clear out drag styles, and clear all |
michael@0 | 1332 | // timers for opening/closing it. |
michael@0 | 1333 | if (this._overFolder.elt && this._overFolder.elt.lastChild) { |
michael@0 | 1334 | if (!this._overFolder.elt.lastChild.hasAttribute("dragover")) { |
michael@0 | 1335 | this._overFolder.elt.lastChild.hidePopup(); |
michael@0 | 1336 | } |
michael@0 | 1337 | this._overFolder.elt.removeAttribute("dragover"); |
michael@0 | 1338 | this._overFolder.elt = null; |
michael@0 | 1339 | } |
michael@0 | 1340 | if (this._overFolder.openTimer) { |
michael@0 | 1341 | this._overFolder.openTimer.cancel(); |
michael@0 | 1342 | this._overFolder.openTimer = null; |
michael@0 | 1343 | } |
michael@0 | 1344 | if (this._overFolder.closeTimer) { |
michael@0 | 1345 | this._overFolder.closeTimer.cancel(); |
michael@0 | 1346 | this._overFolder.closeTimer = null; |
michael@0 | 1347 | } |
michael@0 | 1348 | }, |
michael@0 | 1349 | |
michael@0 | 1350 | /** |
michael@0 | 1351 | * This function returns information about where to drop when dragging over |
michael@0 | 1352 | * the toolbar. The returned object has the following properties: |
michael@0 | 1353 | * - ip: the insertion point for the bookmarks service. |
michael@0 | 1354 | * - beforeIndex: child index to drop before, for the drop indicator. |
michael@0 | 1355 | * - folderElt: the folder to drop into, if applicable. |
michael@0 | 1356 | */ |
michael@0 | 1357 | _getDropPoint: function PT__getDropPoint(aEvent) { |
michael@0 | 1358 | let result = this.result; |
michael@0 | 1359 | if (!PlacesUtils.nodeIsFolder(this._resultNode)) |
michael@0 | 1360 | return null; |
michael@0 | 1361 | |
michael@0 | 1362 | let dropPoint = { ip: null, beforeIndex: null, folderElt: null }; |
michael@0 | 1363 | let elt = aEvent.target; |
michael@0 | 1364 | if (elt._placesNode && elt != this._rootElt && |
michael@0 | 1365 | elt.localName != "menupopup") { |
michael@0 | 1366 | let eltRect = elt.getBoundingClientRect(); |
michael@0 | 1367 | let eltIndex = Array.indexOf(this._rootElt.childNodes, elt); |
michael@0 | 1368 | if (PlacesUtils.nodeIsFolder(elt._placesNode) && |
michael@0 | 1369 | !PlacesUtils.nodeIsReadOnly(elt._placesNode)) { |
michael@0 | 1370 | // This is a folder. |
michael@0 | 1371 | // If we are in the middle of it, drop inside it. |
michael@0 | 1372 | // Otherwise, drop before it, with regards to RTL mode. |
michael@0 | 1373 | let threshold = eltRect.width * 0.25; |
michael@0 | 1374 | if (this.isRTL ? (aEvent.clientX > eltRect.right - threshold) |
michael@0 | 1375 | : (aEvent.clientX < eltRect.left + threshold)) { |
michael@0 | 1376 | // Drop before this folder. |
michael@0 | 1377 | dropPoint.ip = |
michael@0 | 1378 | new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode), |
michael@0 | 1379 | eltIndex, Ci.nsITreeView.DROP_BEFORE); |
michael@0 | 1380 | dropPoint.beforeIndex = eltIndex; |
michael@0 | 1381 | } |
michael@0 | 1382 | else if (this.isRTL ? (aEvent.clientX > eltRect.left + threshold) |
michael@0 | 1383 | : (aEvent.clientX < eltRect.right - threshold)) { |
michael@0 | 1384 | // Drop inside this folder. |
michael@0 | 1385 | dropPoint.ip = |
michael@0 | 1386 | new InsertionPoint(PlacesUtils.getConcreteItemId(elt._placesNode), |
michael@0 | 1387 | -1, Ci.nsITreeView.DROP_ON, |
michael@0 | 1388 | PlacesUtils.nodeIsTagQuery(elt._placesNode)); |
michael@0 | 1389 | dropPoint.beforeIndex = eltIndex; |
michael@0 | 1390 | dropPoint.folderElt = elt; |
michael@0 | 1391 | } |
michael@0 | 1392 | else { |
michael@0 | 1393 | // Drop after this folder. |
michael@0 | 1394 | let beforeIndex = |
michael@0 | 1395 | (eltIndex == this._rootElt.childNodes.length - 1) ? |
michael@0 | 1396 | -1 : eltIndex + 1; |
michael@0 | 1397 | |
michael@0 | 1398 | dropPoint.ip = |
michael@0 | 1399 | new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode), |
michael@0 | 1400 | beforeIndex, Ci.nsITreeView.DROP_BEFORE); |
michael@0 | 1401 | dropPoint.beforeIndex = beforeIndex; |
michael@0 | 1402 | } |
michael@0 | 1403 | } |
michael@0 | 1404 | else { |
michael@0 | 1405 | // This is a non-folder node or a read-only folder. |
michael@0 | 1406 | // Drop before it with regards to RTL mode. |
michael@0 | 1407 | let threshold = eltRect.width * 0.5; |
michael@0 | 1408 | if (this.isRTL ? (aEvent.clientX > eltRect.left + threshold) |
michael@0 | 1409 | : (aEvent.clientX < eltRect.left + threshold)) { |
michael@0 | 1410 | // Drop before this bookmark. |
michael@0 | 1411 | dropPoint.ip = |
michael@0 | 1412 | new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode), |
michael@0 | 1413 | eltIndex, Ci.nsITreeView.DROP_BEFORE); |
michael@0 | 1414 | dropPoint.beforeIndex = eltIndex; |
michael@0 | 1415 | } |
michael@0 | 1416 | else { |
michael@0 | 1417 | // Drop after this bookmark. |
michael@0 | 1418 | let beforeIndex = |
michael@0 | 1419 | eltIndex == this._rootElt.childNodes.length - 1 ? |
michael@0 | 1420 | -1 : eltIndex + 1; |
michael@0 | 1421 | dropPoint.ip = |
michael@0 | 1422 | new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode), |
michael@0 | 1423 | beforeIndex, Ci.nsITreeView.DROP_BEFORE); |
michael@0 | 1424 | dropPoint.beforeIndex = beforeIndex; |
michael@0 | 1425 | } |
michael@0 | 1426 | } |
michael@0 | 1427 | } |
michael@0 | 1428 | else { |
michael@0 | 1429 | // We are most likely dragging on the empty area of the |
michael@0 | 1430 | // toolbar, we should drop after the last node. |
michael@0 | 1431 | dropPoint.ip = |
michael@0 | 1432 | new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode), |
michael@0 | 1433 | -1, Ci.nsITreeView.DROP_BEFORE); |
michael@0 | 1434 | dropPoint.beforeIndex = -1; |
michael@0 | 1435 | } |
michael@0 | 1436 | |
michael@0 | 1437 | return dropPoint; |
michael@0 | 1438 | }, |
michael@0 | 1439 | |
michael@0 | 1440 | _setTimer: function PT_setTimer(aTime) { |
michael@0 | 1441 | let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); |
michael@0 | 1442 | timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT); |
michael@0 | 1443 | return timer; |
michael@0 | 1444 | }, |
michael@0 | 1445 | |
michael@0 | 1446 | notify: function PT_notify(aTimer) { |
michael@0 | 1447 | if (aTimer == this._updateChevronTimer) { |
michael@0 | 1448 | this._updateChevronTimer = null; |
michael@0 | 1449 | this._updateChevronTimerCallback(); |
michael@0 | 1450 | } |
michael@0 | 1451 | |
michael@0 | 1452 | // * Timer to turn off indicator bar. |
michael@0 | 1453 | else if (aTimer == this._ibTimer) { |
michael@0 | 1454 | this._dropIndicator.collapsed = true; |
michael@0 | 1455 | this._ibTimer = null; |
michael@0 | 1456 | } |
michael@0 | 1457 | |
michael@0 | 1458 | // * Timer to open a menubutton that's being dragged over. |
michael@0 | 1459 | else if (aTimer == this._overFolder.openTimer) { |
michael@0 | 1460 | // Set the autoopen attribute on the folder's menupopup so that |
michael@0 | 1461 | // the menu will automatically close when the mouse drags off of it. |
michael@0 | 1462 | this._overFolder.elt.lastChild.setAttribute("autoopened", "true"); |
michael@0 | 1463 | this._overFolder.elt.open = true; |
michael@0 | 1464 | this._overFolder.openTimer = null; |
michael@0 | 1465 | } |
michael@0 | 1466 | |
michael@0 | 1467 | // * Timer to close a menubutton that's been dragged off of. |
michael@0 | 1468 | else if (aTimer == this._overFolder.closeTimer) { |
michael@0 | 1469 | // Close the menubutton if we are not dragging over it or one of |
michael@0 | 1470 | // its children. The autoopened attribute will let the menu know to |
michael@0 | 1471 | // close later if the menu is still being dragged over. |
michael@0 | 1472 | let currentPlacesNode = PlacesControllerDragHelper.currentDropTarget; |
michael@0 | 1473 | let inHierarchy = false; |
michael@0 | 1474 | while (currentPlacesNode) { |
michael@0 | 1475 | if (currentPlacesNode == this._rootElt) { |
michael@0 | 1476 | inHierarchy = true; |
michael@0 | 1477 | break; |
michael@0 | 1478 | } |
michael@0 | 1479 | currentPlacesNode = currentPlacesNode.parentNode; |
michael@0 | 1480 | } |
michael@0 | 1481 | // The _clearOverFolder() function will close the menu for |
michael@0 | 1482 | // _overFolder.elt. So null it out if we don't want to close it. |
michael@0 | 1483 | if (inHierarchy) |
michael@0 | 1484 | this._overFolder.elt = null; |
michael@0 | 1485 | |
michael@0 | 1486 | // Clear out the folder and all associated timers. |
michael@0 | 1487 | this._clearOverFolder(); |
michael@0 | 1488 | } |
michael@0 | 1489 | }, |
michael@0 | 1490 | |
michael@0 | 1491 | _onMouseOver: function PT__onMouseOver(aEvent) { |
michael@0 | 1492 | let button = aEvent.target; |
michael@0 | 1493 | if (button.parentNode == this._rootElt && button._placesNode && |
michael@0 | 1494 | PlacesUtils.nodeIsURI(button._placesNode)) |
michael@0 | 1495 | window.XULBrowserWindow.setOverLink(aEvent.target._placesNode.uri, null); |
michael@0 | 1496 | }, |
michael@0 | 1497 | |
michael@0 | 1498 | _onMouseOut: function PT__onMouseOut(aEvent) { |
michael@0 | 1499 | window.XULBrowserWindow.setOverLink("", null); |
michael@0 | 1500 | }, |
michael@0 | 1501 | |
michael@0 | 1502 | _cleanupDragDetails: function PT__cleanupDragDetails() { |
michael@0 | 1503 | // Called on dragend and drop. |
michael@0 | 1504 | PlacesControllerDragHelper.currentDropTarget = null; |
michael@0 | 1505 | this._draggedElt = null; |
michael@0 | 1506 | if (this._ibTimer) |
michael@0 | 1507 | this._ibTimer.cancel(); |
michael@0 | 1508 | |
michael@0 | 1509 | this._dropIndicator.collapsed = true; |
michael@0 | 1510 | }, |
michael@0 | 1511 | |
michael@0 | 1512 | _onDragStart: function PT__onDragStart(aEvent) { |
michael@0 | 1513 | // Sub menus have their own d&d handlers. |
michael@0 | 1514 | let draggedElt = aEvent.target; |
michael@0 | 1515 | if (draggedElt.parentNode != this._rootElt || !draggedElt._placesNode) |
michael@0 | 1516 | return; |
michael@0 | 1517 | |
michael@0 | 1518 | if (draggedElt.localName == "toolbarbutton" && |
michael@0 | 1519 | draggedElt.getAttribute("type") == "menu") { |
michael@0 | 1520 | // If the drag gesture on a container is toward down we open instead |
michael@0 | 1521 | // of dragging. |
michael@0 | 1522 | let translateY = this._cachedMouseMoveEvent.clientY - aEvent.clientY; |
michael@0 | 1523 | let translateX = this._cachedMouseMoveEvent.clientX - aEvent.clientX; |
michael@0 | 1524 | if ((translateY) >= Math.abs(translateX/2)) { |
michael@0 | 1525 | // Don't start the drag. |
michael@0 | 1526 | aEvent.preventDefault(); |
michael@0 | 1527 | // Open the menu. |
michael@0 | 1528 | draggedElt.open = true; |
michael@0 | 1529 | return; |
michael@0 | 1530 | } |
michael@0 | 1531 | |
michael@0 | 1532 | // If the menu is open, close it. |
michael@0 | 1533 | if (draggedElt.open) { |
michael@0 | 1534 | draggedElt.lastChild.hidePopup(); |
michael@0 | 1535 | draggedElt.open = false; |
michael@0 | 1536 | } |
michael@0 | 1537 | } |
michael@0 | 1538 | |
michael@0 | 1539 | // Activate the view and cache the dragged element. |
michael@0 | 1540 | this._draggedElt = draggedElt._placesNode; |
michael@0 | 1541 | this._rootElt.focus(); |
michael@0 | 1542 | |
michael@0 | 1543 | this._controller.setDataTransfer(aEvent); |
michael@0 | 1544 | aEvent.stopPropagation(); |
michael@0 | 1545 | }, |
michael@0 | 1546 | |
michael@0 | 1547 | _onDragOver: function PT__onDragOver(aEvent) { |
michael@0 | 1548 | // Cache the dataTransfer |
michael@0 | 1549 | PlacesControllerDragHelper.currentDropTarget = aEvent.target; |
michael@0 | 1550 | let dt = aEvent.dataTransfer; |
michael@0 | 1551 | |
michael@0 | 1552 | let dropPoint = this._getDropPoint(aEvent); |
michael@0 | 1553 | if (!dropPoint || !dropPoint.ip || |
michael@0 | 1554 | !PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)) { |
michael@0 | 1555 | this._dropIndicator.collapsed = true; |
michael@0 | 1556 | aEvent.stopPropagation(); |
michael@0 | 1557 | return; |
michael@0 | 1558 | } |
michael@0 | 1559 | |
michael@0 | 1560 | if (this._ibTimer) { |
michael@0 | 1561 | this._ibTimer.cancel(); |
michael@0 | 1562 | this._ibTimer = null; |
michael@0 | 1563 | } |
michael@0 | 1564 | |
michael@0 | 1565 | if (dropPoint.folderElt || aEvent.originalTarget == this._chevron) { |
michael@0 | 1566 | // Dropping over a menubutton or chevron button. |
michael@0 | 1567 | // Set styles and timer to open relative menupopup. |
michael@0 | 1568 | let overElt = dropPoint.folderElt || this._chevron; |
michael@0 | 1569 | if (this._overFolder.elt != overElt) { |
michael@0 | 1570 | this._clearOverFolder(); |
michael@0 | 1571 | this._overFolder.elt = overElt; |
michael@0 | 1572 | this._overFolder.openTimer = this._setTimer(this._overFolder.hoverTime); |
michael@0 | 1573 | } |
michael@0 | 1574 | if (!this._overFolder.elt.hasAttribute("dragover")) |
michael@0 | 1575 | this._overFolder.elt.setAttribute("dragover", "true"); |
michael@0 | 1576 | |
michael@0 | 1577 | this._dropIndicator.collapsed = true; |
michael@0 | 1578 | } |
michael@0 | 1579 | else { |
michael@0 | 1580 | // Dragging over a normal toolbarbutton, |
michael@0 | 1581 | // show indicator bar and move it to the appropriate drop point. |
michael@0 | 1582 | let ind = this._dropIndicator; |
michael@0 | 1583 | ind.parentNode.collapsed = false; |
michael@0 | 1584 | let halfInd = ind.clientWidth / 2; |
michael@0 | 1585 | let translateX; |
michael@0 | 1586 | if (this.isRTL) { |
michael@0 | 1587 | halfInd = Math.ceil(halfInd); |
michael@0 | 1588 | translateX = 0 - this._rootElt.getBoundingClientRect().right - halfInd; |
michael@0 | 1589 | if (this._rootElt.firstChild) { |
michael@0 | 1590 | if (dropPoint.beforeIndex == -1) |
michael@0 | 1591 | translateX += this._rootElt.lastChild.getBoundingClientRect().left; |
michael@0 | 1592 | else { |
michael@0 | 1593 | translateX += this._rootElt.childNodes[dropPoint.beforeIndex] |
michael@0 | 1594 | .getBoundingClientRect().right; |
michael@0 | 1595 | } |
michael@0 | 1596 | } |
michael@0 | 1597 | } |
michael@0 | 1598 | else { |
michael@0 | 1599 | halfInd = Math.floor(halfInd); |
michael@0 | 1600 | translateX = 0 - this._rootElt.getBoundingClientRect().left + |
michael@0 | 1601 | halfInd; |
michael@0 | 1602 | if (this._rootElt.firstChild) { |
michael@0 | 1603 | if (dropPoint.beforeIndex == -1) |
michael@0 | 1604 | translateX += this._rootElt.lastChild.getBoundingClientRect().right; |
michael@0 | 1605 | else { |
michael@0 | 1606 | translateX += this._rootElt.childNodes[dropPoint.beforeIndex] |
michael@0 | 1607 | .getBoundingClientRect().left; |
michael@0 | 1608 | } |
michael@0 | 1609 | } |
michael@0 | 1610 | } |
michael@0 | 1611 | |
michael@0 | 1612 | ind.style.transform = "translate(" + Math.round(translateX) + "px)"; |
michael@0 | 1613 | ind.style.MozMarginStart = (-ind.clientWidth) + "px"; |
michael@0 | 1614 | ind.collapsed = false; |
michael@0 | 1615 | |
michael@0 | 1616 | // Clear out old folder information. |
michael@0 | 1617 | this._clearOverFolder(); |
michael@0 | 1618 | } |
michael@0 | 1619 | |
michael@0 | 1620 | aEvent.preventDefault(); |
michael@0 | 1621 | aEvent.stopPropagation(); |
michael@0 | 1622 | }, |
michael@0 | 1623 | |
michael@0 | 1624 | _onDrop: function PT__onDrop(aEvent) { |
michael@0 | 1625 | PlacesControllerDragHelper.currentDropTarget = aEvent.target; |
michael@0 | 1626 | |
michael@0 | 1627 | let dropPoint = this._getDropPoint(aEvent); |
michael@0 | 1628 | if (dropPoint && dropPoint.ip) { |
michael@0 | 1629 | PlacesControllerDragHelper.onDrop(dropPoint.ip, aEvent.dataTransfer) |
michael@0 | 1630 | aEvent.preventDefault(); |
michael@0 | 1631 | } |
michael@0 | 1632 | |
michael@0 | 1633 | this._cleanupDragDetails(); |
michael@0 | 1634 | aEvent.stopPropagation(); |
michael@0 | 1635 | }, |
michael@0 | 1636 | |
michael@0 | 1637 | _onDragExit: function PT__onDragExit(aEvent) { |
michael@0 | 1638 | PlacesControllerDragHelper.currentDropTarget = null; |
michael@0 | 1639 | |
michael@0 | 1640 | // Set timer to turn off indicator bar (if we turn it off |
michael@0 | 1641 | // here, dragenter might be called immediately after, creating |
michael@0 | 1642 | // flicker). |
michael@0 | 1643 | if (this._ibTimer) |
michael@0 | 1644 | this._ibTimer.cancel(); |
michael@0 | 1645 | this._ibTimer = this._setTimer(10); |
michael@0 | 1646 | |
michael@0 | 1647 | // If we hovered over a folder, close it now. |
michael@0 | 1648 | if (this._overFolder.elt) |
michael@0 | 1649 | this._overFolder.closeTimer = this._setTimer(this._overFolder.hoverTime); |
michael@0 | 1650 | }, |
michael@0 | 1651 | |
michael@0 | 1652 | _onDragEnd: function PT_onDragEnd(aEvent) { |
michael@0 | 1653 | this._cleanupDragDetails(); |
michael@0 | 1654 | }, |
michael@0 | 1655 | |
michael@0 | 1656 | _onPopupShowing: function PT__onPopupShowing(aEvent) { |
michael@0 | 1657 | if (!this._allowPopupShowing) { |
michael@0 | 1658 | this._allowPopupShowing = true; |
michael@0 | 1659 | aEvent.preventDefault(); |
michael@0 | 1660 | return; |
michael@0 | 1661 | } |
michael@0 | 1662 | |
michael@0 | 1663 | let parent = aEvent.target.parentNode; |
michael@0 | 1664 | if (parent.localName == "toolbarbutton") |
michael@0 | 1665 | this._openedMenuButton = parent; |
michael@0 | 1666 | |
michael@0 | 1667 | PlacesViewBase.prototype._onPopupShowing.apply(this, arguments); |
michael@0 | 1668 | }, |
michael@0 | 1669 | |
michael@0 | 1670 | _onPopupHidden: function PT__onPopupHidden(aEvent) { |
michael@0 | 1671 | let popup = aEvent.target; |
michael@0 | 1672 | let placesNode = popup._placesNode; |
michael@0 | 1673 | // Avoid handling popuphidden of inner views |
michael@0 | 1674 | if (placesNode && PlacesUIUtils.getViewForNode(popup) == this) { |
michael@0 | 1675 | // UI performance: folder queries are cheap, keep the resultnode open |
michael@0 | 1676 | // so we don't rebuild its contents whenever the popup is reopened. |
michael@0 | 1677 | // Though, we want to always close feed containers so their expiration |
michael@0 | 1678 | // status will be checked at next opening. |
michael@0 | 1679 | if (!PlacesUtils.nodeIsFolder(placesNode) || |
michael@0 | 1680 | this.controller.hasCachedLivemarkInfo(placesNode)) { |
michael@0 | 1681 | placesNode.containerOpen = false; |
michael@0 | 1682 | } |
michael@0 | 1683 | } |
michael@0 | 1684 | |
michael@0 | 1685 | let parent = popup.parentNode; |
michael@0 | 1686 | if (parent.localName == "toolbarbutton") { |
michael@0 | 1687 | this._openedMenuButton = null; |
michael@0 | 1688 | // Clear the dragover attribute if present, if we are dragging into a |
michael@0 | 1689 | // folder in the hierachy of current opened popup we don't clear |
michael@0 | 1690 | // this attribute on clearOverFolder. See Notify for closeTimer. |
michael@0 | 1691 | if (parent.hasAttribute("dragover")) |
michael@0 | 1692 | parent.removeAttribute("dragover"); |
michael@0 | 1693 | } |
michael@0 | 1694 | }, |
michael@0 | 1695 | |
michael@0 | 1696 | _onMouseMove: function PT__onMouseMove(aEvent) { |
michael@0 | 1697 | // Used in dragStart to prevent dragging folders when dragging down. |
michael@0 | 1698 | this._cachedMouseMoveEvent = aEvent; |
michael@0 | 1699 | |
michael@0 | 1700 | if (this._openedMenuButton == null || |
michael@0 | 1701 | PlacesControllerDragHelper.getSession()) |
michael@0 | 1702 | return; |
michael@0 | 1703 | |
michael@0 | 1704 | let target = aEvent.originalTarget; |
michael@0 | 1705 | if (this._openedMenuButton != target && |
michael@0 | 1706 | target.localName == "toolbarbutton" && |
michael@0 | 1707 | target.type == "menu") { |
michael@0 | 1708 | this._openedMenuButton.open = false; |
michael@0 | 1709 | target.open = true; |
michael@0 | 1710 | } |
michael@0 | 1711 | } |
michael@0 | 1712 | }; |
michael@0 | 1713 | |
michael@0 | 1714 | /** |
michael@0 | 1715 | * View for Places menus. This object should be created during the first |
michael@0 | 1716 | * popupshowing that's dispatched on the menu. |
michael@0 | 1717 | */ |
michael@0 | 1718 | function PlacesMenu(aPopupShowingEvent, aPlace, aOptions) { |
michael@0 | 1719 | this._rootElt = aPopupShowingEvent.target; // <menupopup> |
michael@0 | 1720 | this._viewElt = this._rootElt.parentNode; // <menu> |
michael@0 | 1721 | this._viewElt._placesView = this; |
michael@0 | 1722 | this._addEventListeners(this._rootElt, ["popupshowing", "popuphidden"], true); |
michael@0 | 1723 | this._addEventListeners(window, ["unload"], false); |
michael@0 | 1724 | |
michael@0 | 1725 | #ifdef XP_MACOSX |
michael@0 | 1726 | // Must walk up to support views in sub-menus, like Bookmarks Toolbar menu. |
michael@0 | 1727 | for (let elt = this._viewElt.parentNode; elt; elt = elt.parentNode) { |
michael@0 | 1728 | if (elt.localName == "menubar") { |
michael@0 | 1729 | this._nativeView = true; |
michael@0 | 1730 | break; |
michael@0 | 1731 | } |
michael@0 | 1732 | } |
michael@0 | 1733 | #endif |
michael@0 | 1734 | |
michael@0 | 1735 | PlacesViewBase.call(this, aPlace, aOptions); |
michael@0 | 1736 | this._onPopupShowing(aPopupShowingEvent); |
michael@0 | 1737 | } |
michael@0 | 1738 | |
michael@0 | 1739 | PlacesMenu.prototype = { |
michael@0 | 1740 | __proto__: PlacesViewBase.prototype, |
michael@0 | 1741 | |
michael@0 | 1742 | QueryInterface: function PM_QueryInterface(aIID) { |
michael@0 | 1743 | if (aIID.equals(Ci.nsIDOMEventListener)) |
michael@0 | 1744 | return this; |
michael@0 | 1745 | |
michael@0 | 1746 | return PlacesViewBase.prototype.QueryInterface.apply(this, arguments); |
michael@0 | 1747 | }, |
michael@0 | 1748 | |
michael@0 | 1749 | _removeChild: function PM_removeChild(aChild) { |
michael@0 | 1750 | PlacesViewBase.prototype._removeChild.apply(this, arguments); |
michael@0 | 1751 | }, |
michael@0 | 1752 | |
michael@0 | 1753 | uninit: function PM_uninit() { |
michael@0 | 1754 | this._removeEventListeners(this._rootElt, ["popupshowing", "popuphidden"], |
michael@0 | 1755 | true); |
michael@0 | 1756 | this._removeEventListeners(window, ["unload"], false); |
michael@0 | 1757 | |
michael@0 | 1758 | PlacesViewBase.prototype.uninit.apply(this, arguments); |
michael@0 | 1759 | }, |
michael@0 | 1760 | |
michael@0 | 1761 | handleEvent: function PM_handleEvent(aEvent) { |
michael@0 | 1762 | switch (aEvent.type) { |
michael@0 | 1763 | case "unload": |
michael@0 | 1764 | this.uninit(); |
michael@0 | 1765 | break; |
michael@0 | 1766 | case "popupshowing": |
michael@0 | 1767 | this._onPopupShowing(aEvent); |
michael@0 | 1768 | break; |
michael@0 | 1769 | case "popuphidden": |
michael@0 | 1770 | this._onPopupHidden(aEvent); |
michael@0 | 1771 | break; |
michael@0 | 1772 | } |
michael@0 | 1773 | }, |
michael@0 | 1774 | |
michael@0 | 1775 | _onPopupHidden: function PM__onPopupHidden(aEvent) { |
michael@0 | 1776 | // Avoid handling popuphidden of inner views. |
michael@0 | 1777 | let popup = aEvent.originalTarget; |
michael@0 | 1778 | let placesNode = popup._placesNode; |
michael@0 | 1779 | if (!placesNode || PlacesUIUtils.getViewForNode(popup) != this) |
michael@0 | 1780 | return; |
michael@0 | 1781 | |
michael@0 | 1782 | // UI performance: folder queries are cheap, keep the resultnode open |
michael@0 | 1783 | // so we don't rebuild its contents whenever the popup is reopened. |
michael@0 | 1784 | // Though, we want to always close feed containers so their expiration |
michael@0 | 1785 | // status will be checked at next opening. |
michael@0 | 1786 | if (!PlacesUtils.nodeIsFolder(placesNode) || |
michael@0 | 1787 | this.controller.hasCachedLivemarkInfo(placesNode)) |
michael@0 | 1788 | placesNode.containerOpen = false; |
michael@0 | 1789 | |
michael@0 | 1790 | // The autoopened attribute is set for folders which have been |
michael@0 | 1791 | // automatically opened when dragged over. Turn off this attribute |
michael@0 | 1792 | // when the folder closes because it is no longer applicable. |
michael@0 | 1793 | popup.removeAttribute("autoopened"); |
michael@0 | 1794 | popup.removeAttribute("dragstart"); |
michael@0 | 1795 | } |
michael@0 | 1796 | }; |
michael@0 | 1797 | |
michael@0 | 1798 | function PlacesPanelMenuView(aPlace, aViewId, aRootId, aOptions) { |
michael@0 | 1799 | this._viewElt = document.getElementById(aViewId); |
michael@0 | 1800 | this._rootElt = document.getElementById(aRootId); |
michael@0 | 1801 | this._viewElt._placesView = this; |
michael@0 | 1802 | this.options = aOptions; |
michael@0 | 1803 | |
michael@0 | 1804 | PlacesViewBase.call(this, aPlace, aOptions); |
michael@0 | 1805 | } |
michael@0 | 1806 | |
michael@0 | 1807 | PlacesPanelMenuView.prototype = { |
michael@0 | 1808 | __proto__: PlacesViewBase.prototype, |
michael@0 | 1809 | |
michael@0 | 1810 | QueryInterface: function PAMV_QueryInterface(aIID) { |
michael@0 | 1811 | return PlacesViewBase.prototype.QueryInterface.apply(this, arguments); |
michael@0 | 1812 | }, |
michael@0 | 1813 | |
michael@0 | 1814 | uninit: function PAMV_uninit() { |
michael@0 | 1815 | PlacesViewBase.prototype.uninit.apply(this, arguments); |
michael@0 | 1816 | }, |
michael@0 | 1817 | |
michael@0 | 1818 | _insertNewItem: |
michael@0 | 1819 | function PAMV__insertNewItem(aChild, aBefore) { |
michael@0 | 1820 | this._domNodes.delete(aChild); |
michael@0 | 1821 | |
michael@0 | 1822 | let type = aChild.type; |
michael@0 | 1823 | let button; |
michael@0 | 1824 | if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) { |
michael@0 | 1825 | button = document.createElement("toolbarseparator"); |
michael@0 | 1826 | button.setAttribute("class", "small-separator"); |
michael@0 | 1827 | } |
michael@0 | 1828 | else { |
michael@0 | 1829 | button = document.createElement("toolbarbutton"); |
michael@0 | 1830 | button.className = "bookmark-item"; |
michael@0 | 1831 | if (typeof this.options.extraClasses.entry == "string") |
michael@0 | 1832 | button.classList.add(this.options.extraClasses.entry); |
michael@0 | 1833 | button.setAttribute("label", aChild.title); |
michael@0 | 1834 | let icon = aChild.icon; |
michael@0 | 1835 | if (icon) |
michael@0 | 1836 | button.setAttribute("image", icon); |
michael@0 | 1837 | |
michael@0 | 1838 | if (PlacesUtils.containerTypes.indexOf(type) != -1) { |
michael@0 | 1839 | button.setAttribute("container", "true"); |
michael@0 | 1840 | |
michael@0 | 1841 | if (PlacesUtils.nodeIsQuery(aChild)) { |
michael@0 | 1842 | button.setAttribute("query", "true"); |
michael@0 | 1843 | if (PlacesUtils.nodeIsTagQuery(aChild)) |
michael@0 | 1844 | button.setAttribute("tagContainer", "true"); |
michael@0 | 1845 | } |
michael@0 | 1846 | else if (PlacesUtils.nodeIsFolder(aChild)) { |
michael@0 | 1847 | PlacesUtils.livemarks.getLivemark({ id: aChild.itemId }) |
michael@0 | 1848 | .then(aLivemark => { |
michael@0 | 1849 | button.setAttribute("livemark", "true"); |
michael@0 | 1850 | this.controller.cacheLivemarkInfo(aChild, aLivemark); |
michael@0 | 1851 | }, () => undefined); |
michael@0 | 1852 | } |
michael@0 | 1853 | } |
michael@0 | 1854 | else if (PlacesUtils.nodeIsURI(aChild)) { |
michael@0 | 1855 | button.setAttribute("scheme", |
michael@0 | 1856 | PlacesUIUtils.guessUrlSchemeForUI(aChild.uri)); |
michael@0 | 1857 | } |
michael@0 | 1858 | } |
michael@0 | 1859 | |
michael@0 | 1860 | button._placesNode = aChild; |
michael@0 | 1861 | if (!this._domNodes.has(aChild)) |
michael@0 | 1862 | this._domNodes.set(aChild, button); |
michael@0 | 1863 | |
michael@0 | 1864 | this._rootElt.insertBefore(button, aBefore); |
michael@0 | 1865 | }, |
michael@0 | 1866 | |
michael@0 | 1867 | nodeInserted: |
michael@0 | 1868 | function PAMV_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) { |
michael@0 | 1869 | let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode); |
michael@0 | 1870 | if (parentElt != this._rootElt) |
michael@0 | 1871 | return; |
michael@0 | 1872 | |
michael@0 | 1873 | let children = this._rootElt.childNodes; |
michael@0 | 1874 | this._insertNewItem(aPlacesNode, |
michael@0 | 1875 | aIndex < children.length ? children[aIndex] : null); |
michael@0 | 1876 | }, |
michael@0 | 1877 | |
michael@0 | 1878 | nodeRemoved: |
michael@0 | 1879 | function PAMV_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) { |
michael@0 | 1880 | let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode); |
michael@0 | 1881 | if (parentElt != this._rootElt) |
michael@0 | 1882 | return; |
michael@0 | 1883 | |
michael@0 | 1884 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 1885 | this._removeChild(elt); |
michael@0 | 1886 | }, |
michael@0 | 1887 | |
michael@0 | 1888 | nodeMoved: |
michael@0 | 1889 | function PAMV_nodeMoved(aPlacesNode, |
michael@0 | 1890 | aOldParentPlacesNode, aOldIndex, |
michael@0 | 1891 | aNewParentPlacesNode, aNewIndex) { |
michael@0 | 1892 | let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode); |
michael@0 | 1893 | if (parentElt != this._rootElt) |
michael@0 | 1894 | return; |
michael@0 | 1895 | |
michael@0 | 1896 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 1897 | this._removeChild(elt); |
michael@0 | 1898 | this._rootElt.insertBefore(elt, this._rootElt.childNodes[aNewIndex]); |
michael@0 | 1899 | }, |
michael@0 | 1900 | |
michael@0 | 1901 | nodeAnnotationChanged: |
michael@0 | 1902 | function PAMV_nodeAnnotationChanged(aPlacesNode, aAnno) { |
michael@0 | 1903 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 1904 | // There's no UI representation for the root node. |
michael@0 | 1905 | if (elt == this._rootElt) |
michael@0 | 1906 | return; |
michael@0 | 1907 | |
michael@0 | 1908 | if (elt.parentNode != this._rootElt) |
michael@0 | 1909 | return; |
michael@0 | 1910 | |
michael@0 | 1911 | // All livemarks have a feedURI, so use it as our indicator. |
michael@0 | 1912 | if (aAnno == PlacesUtils.LMANNO_FEEDURI) { |
michael@0 | 1913 | elt.setAttribute("livemark", true); |
michael@0 | 1914 | |
michael@0 | 1915 | PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId }) |
michael@0 | 1916 | .then(aLivemark => { |
michael@0 | 1917 | this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark); |
michael@0 | 1918 | this.invalidateContainer(aPlacesNode); |
michael@0 | 1919 | }, Components.utils.reportError); |
michael@0 | 1920 | } |
michael@0 | 1921 | }, |
michael@0 | 1922 | |
michael@0 | 1923 | nodeTitleChanged: function PAMV_nodeTitleChanged(aPlacesNode, aNewTitle) { |
michael@0 | 1924 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 1925 | |
michael@0 | 1926 | // There's no UI representation for the root node. |
michael@0 | 1927 | if (elt == this._rootElt) |
michael@0 | 1928 | return; |
michael@0 | 1929 | |
michael@0 | 1930 | PlacesViewBase.prototype.nodeTitleChanged.apply(this, arguments); |
michael@0 | 1931 | }, |
michael@0 | 1932 | |
michael@0 | 1933 | invalidateContainer: function PAMV_invalidateContainer(aPlacesNode) { |
michael@0 | 1934 | let elt = this._getDOMNodeForPlacesNode(aPlacesNode); |
michael@0 | 1935 | if (elt != this._rootElt) |
michael@0 | 1936 | return; |
michael@0 | 1937 | |
michael@0 | 1938 | // Container is the toolbar itself. |
michael@0 | 1939 | while (this._rootElt.hasChildNodes()) { |
michael@0 | 1940 | this._rootElt.removeChild(this._rootElt.firstChild); |
michael@0 | 1941 | } |
michael@0 | 1942 | |
michael@0 | 1943 | for (let i = 0; i < this._resultNode.childCount; ++i) { |
michael@0 | 1944 | this._insertNewItem(this._resultNode.getChild(i), null); |
michael@0 | 1945 | } |
michael@0 | 1946 | } |
michael@0 | 1947 | }; |