Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | this.EXPORTED_SYMBOLS = ["CustomizeMode"]; |
michael@0 | 8 | |
michael@0 | 9 | const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; |
michael@0 | 10 | |
michael@0 | 11 | const kPrefCustomizationDebug = "browser.uiCustomization.debug"; |
michael@0 | 12 | const kPrefCustomizationAnimation = "browser.uiCustomization.disableAnimation"; |
michael@0 | 13 | const kPaletteId = "customization-palette"; |
michael@0 | 14 | const kAboutURI = "about:customizing"; |
michael@0 | 15 | const kDragDataTypePrefix = "text/toolbarwrapper-id/"; |
michael@0 | 16 | const kPlaceholderClass = "panel-customization-placeholder"; |
michael@0 | 17 | const kSkipSourceNodePref = "browser.uiCustomization.skipSourceNodeCheck"; |
michael@0 | 18 | const kToolbarVisibilityBtn = "customization-toolbar-visibility-button"; |
michael@0 | 19 | const kDrawInTitlebarPref = "browser.tabs.drawInTitlebar"; |
michael@0 | 20 | const kMaxTransitionDurationMs = 2000; |
michael@0 | 21 | |
michael@0 | 22 | const kPanelItemContextMenu = "customizationPanelItemContextMenu"; |
michael@0 | 23 | const kPaletteItemContextMenu = "customizationPaletteItemContextMenu"; |
michael@0 | 24 | |
michael@0 | 25 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 26 | Cu.import("resource:///modules/CustomizableUI.jsm"); |
michael@0 | 27 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 28 | Cu.import("resource://gre/modules/Task.jsm"); |
michael@0 | 29 | Cu.import("resource://gre/modules/Promise.jsm"); |
michael@0 | 30 | |
michael@0 | 31 | XPCOMUtils.defineLazyModuleGetter(this, "DragPositionManager", |
michael@0 | 32 | "resource:///modules/DragPositionManager.jsm"); |
michael@0 | 33 | XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry", |
michael@0 | 34 | "resource:///modules/BrowserUITelemetry.jsm"); |
michael@0 | 35 | |
michael@0 | 36 | let gModuleName = "[CustomizeMode]"; |
michael@0 | 37 | #include logging.js |
michael@0 | 38 | |
michael@0 | 39 | let gDisableAnimation = null; |
michael@0 | 40 | |
michael@0 | 41 | let gDraggingInToolbars; |
michael@0 | 42 | |
michael@0 | 43 | function CustomizeMode(aWindow) { |
michael@0 | 44 | if (gDisableAnimation === null) { |
michael@0 | 45 | gDisableAnimation = Services.prefs.getPrefType(kPrefCustomizationAnimation) == Ci.nsIPrefBranch.PREF_BOOL && |
michael@0 | 46 | Services.prefs.getBoolPref(kPrefCustomizationAnimation); |
michael@0 | 47 | } |
michael@0 | 48 | this.window = aWindow; |
michael@0 | 49 | this.document = aWindow.document; |
michael@0 | 50 | this.browser = aWindow.gBrowser; |
michael@0 | 51 | |
michael@0 | 52 | // There are two palettes - there's the palette that can be overlayed with |
michael@0 | 53 | // toolbar items in browser.xul. This is invisible, and never seen by the |
michael@0 | 54 | // user. Then there's the visible palette, which gets populated and displayed |
michael@0 | 55 | // to the user when in customizing mode. |
michael@0 | 56 | this.visiblePalette = this.document.getElementById(kPaletteId); |
michael@0 | 57 | this.paletteEmptyNotice = this.document.getElementById("customization-empty"); |
michael@0 | 58 | this.paletteSpacer = this.document.getElementById("customization-spacer"); |
michael@0 | 59 | this.tipPanel = this.document.getElementById("customization-tipPanel"); |
michael@0 | 60 | #ifdef CAN_DRAW_IN_TITLEBAR |
michael@0 | 61 | this._updateTitlebarButton(); |
michael@0 | 62 | Services.prefs.addObserver(kDrawInTitlebarPref, this, false); |
michael@0 | 63 | this.window.addEventListener("unload", this); |
michael@0 | 64 | #endif |
michael@0 | 65 | }; |
michael@0 | 66 | |
michael@0 | 67 | CustomizeMode.prototype = { |
michael@0 | 68 | _changed: false, |
michael@0 | 69 | _transitioning: false, |
michael@0 | 70 | window: null, |
michael@0 | 71 | document: null, |
michael@0 | 72 | // areas is used to cache the customizable areas when in customization mode. |
michael@0 | 73 | areas: null, |
michael@0 | 74 | // When in customizing mode, we swap out the reference to the invisible |
michael@0 | 75 | // palette in gNavToolbox.palette for our visiblePalette. This way, for the |
michael@0 | 76 | // customizing browser window, when widgets are removed from customizable |
michael@0 | 77 | // areas and added to the palette, they're added to the visible palette. |
michael@0 | 78 | // _stowedPalette is a reference to the old invisible palette so we can |
michael@0 | 79 | // restore gNavToolbox.palette to its original state after exiting |
michael@0 | 80 | // customization mode. |
michael@0 | 81 | _stowedPalette: null, |
michael@0 | 82 | _dragOverItem: null, |
michael@0 | 83 | _customizing: false, |
michael@0 | 84 | _skipSourceNodeCheck: null, |
michael@0 | 85 | _mainViewContext: null, |
michael@0 | 86 | |
michael@0 | 87 | get panelUIContents() { |
michael@0 | 88 | return this.document.getElementById("PanelUI-contents"); |
michael@0 | 89 | }, |
michael@0 | 90 | |
michael@0 | 91 | get _handler() { |
michael@0 | 92 | return this.window.CustomizationHandler; |
michael@0 | 93 | }, |
michael@0 | 94 | |
michael@0 | 95 | uninit: function() { |
michael@0 | 96 | #ifdef CAN_DRAW_IN_TITLEBAR |
michael@0 | 97 | Services.prefs.removeObserver(kDrawInTitlebarPref, this); |
michael@0 | 98 | #endif |
michael@0 | 99 | }, |
michael@0 | 100 | |
michael@0 | 101 | toggle: function() { |
michael@0 | 102 | if (this._handler.isEnteringCustomizeMode || this._handler.isExitingCustomizeMode) { |
michael@0 | 103 | this._wantToBeInCustomizeMode = !this._wantToBeInCustomizeMode; |
michael@0 | 104 | return; |
michael@0 | 105 | } |
michael@0 | 106 | if (this._customizing) { |
michael@0 | 107 | this.exit(); |
michael@0 | 108 | } else { |
michael@0 | 109 | this.enter(); |
michael@0 | 110 | } |
michael@0 | 111 | }, |
michael@0 | 112 | |
michael@0 | 113 | enter: function() { |
michael@0 | 114 | this._wantToBeInCustomizeMode = true; |
michael@0 | 115 | |
michael@0 | 116 | if (this._customizing || this._handler.isEnteringCustomizeMode) { |
michael@0 | 117 | return; |
michael@0 | 118 | } |
michael@0 | 119 | |
michael@0 | 120 | // Exiting; want to re-enter once we've done that. |
michael@0 | 121 | if (this._handler.isExitingCustomizeMode) { |
michael@0 | 122 | LOG("Attempted to enter while we're in the middle of exiting. " + |
michael@0 | 123 | "We'll exit after we've entered"); |
michael@0 | 124 | return; |
michael@0 | 125 | } |
michael@0 | 126 | |
michael@0 | 127 | |
michael@0 | 128 | // We don't need to switch to kAboutURI, or open a new tab at |
michael@0 | 129 | // kAboutURI if we're already on it. |
michael@0 | 130 | if (this.browser.selectedBrowser.currentURI.spec != kAboutURI) { |
michael@0 | 131 | this.window.switchToTabHavingURI(kAboutURI, true, { |
michael@0 | 132 | skipTabAnimation: true, |
michael@0 | 133 | }); |
michael@0 | 134 | return; |
michael@0 | 135 | } |
michael@0 | 136 | |
michael@0 | 137 | let window = this.window; |
michael@0 | 138 | let document = this.document; |
michael@0 | 139 | |
michael@0 | 140 | this._handler.isEnteringCustomizeMode = true; |
michael@0 | 141 | |
michael@0 | 142 | // Always disable the reset button at the start of customize mode, it'll be re-enabled |
michael@0 | 143 | // if necessary when we finish entering: |
michael@0 | 144 | let resetButton = this.document.getElementById("customization-reset-button"); |
michael@0 | 145 | resetButton.setAttribute("disabled", "true"); |
michael@0 | 146 | |
michael@0 | 147 | Task.spawn(function() { |
michael@0 | 148 | // We shouldn't start customize mode until after browser-delayed-startup has finished: |
michael@0 | 149 | if (!this.window.gBrowserInit.delayedStartupFinished) { |
michael@0 | 150 | let delayedStartupDeferred = Promise.defer(); |
michael@0 | 151 | let delayedStartupObserver = function(aSubject) { |
michael@0 | 152 | if (aSubject == this.window) { |
michael@0 | 153 | Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished"); |
michael@0 | 154 | delayedStartupDeferred.resolve(); |
michael@0 | 155 | } |
michael@0 | 156 | }.bind(this); |
michael@0 | 157 | Services.obs.addObserver(delayedStartupObserver, "browser-delayed-startup-finished", false); |
michael@0 | 158 | yield delayedStartupDeferred.promise; |
michael@0 | 159 | } |
michael@0 | 160 | |
michael@0 | 161 | let toolbarVisibilityBtn = document.getElementById(kToolbarVisibilityBtn); |
michael@0 | 162 | let togglableToolbars = window.getTogglableToolbars(); |
michael@0 | 163 | let bookmarksToolbar = document.getElementById("PersonalToolbar"); |
michael@0 | 164 | if (togglableToolbars.length == 0) { |
michael@0 | 165 | toolbarVisibilityBtn.setAttribute("hidden", "true"); |
michael@0 | 166 | } else { |
michael@0 | 167 | toolbarVisibilityBtn.removeAttribute("hidden"); |
michael@0 | 168 | } |
michael@0 | 169 | |
michael@0 | 170 | // Disable lightweight themes while in customization mode since |
michael@0 | 171 | // they don't have large enough images to pad the full browser window. |
michael@0 | 172 | if (this.document.documentElement._lightweightTheme) |
michael@0 | 173 | this.document.documentElement._lightweightTheme.disable(); |
michael@0 | 174 | |
michael@0 | 175 | CustomizableUI.dispatchToolboxEvent("beforecustomization", {}, window); |
michael@0 | 176 | CustomizableUI.notifyStartCustomizing(this.window); |
michael@0 | 177 | |
michael@0 | 178 | // Add a keypress listener to the document so that we can quickly exit |
michael@0 | 179 | // customization mode when pressing ESC. |
michael@0 | 180 | document.addEventListener("keypress", this); |
michael@0 | 181 | |
michael@0 | 182 | // Same goes for the menu button - if we're customizing, a click on the |
michael@0 | 183 | // menu button means a quick exit from customization mode. |
michael@0 | 184 | window.PanelUI.hide(); |
michael@0 | 185 | window.PanelUI.menuButton.addEventListener("command", this); |
michael@0 | 186 | window.PanelUI.menuButton.open = true; |
michael@0 | 187 | window.PanelUI.beginBatchUpdate(); |
michael@0 | 188 | |
michael@0 | 189 | // The menu panel is lazy, and registers itself when the popup shows. We |
michael@0 | 190 | // need to force the menu panel to register itself, or else customization |
michael@0 | 191 | // is really not going to work. We pass "true" to ensureReady to |
michael@0 | 192 | // indicate that we're handling calling startBatchUpdate and |
michael@0 | 193 | // endBatchUpdate. |
michael@0 | 194 | if (!window.PanelUI.isReady()) { |
michael@0 | 195 | yield window.PanelUI.ensureReady(true); |
michael@0 | 196 | } |
michael@0 | 197 | |
michael@0 | 198 | // Hide the palette before starting the transition for increased perf. |
michael@0 | 199 | this.visiblePalette.hidden = true; |
michael@0 | 200 | this.visiblePalette.removeAttribute("showing"); |
michael@0 | 201 | |
michael@0 | 202 | // Disable the button-text fade-out mask |
michael@0 | 203 | // during the transition for increased perf. |
michael@0 | 204 | let panelContents = window.PanelUI.contents; |
michael@0 | 205 | panelContents.setAttribute("customize-transitioning", "true"); |
michael@0 | 206 | |
michael@0 | 207 | // Move the mainView in the panel to the holder so that we can see it |
michael@0 | 208 | // while customizing. |
michael@0 | 209 | let mainView = window.PanelUI.mainView; |
michael@0 | 210 | let panelHolder = document.getElementById("customization-panelHolder"); |
michael@0 | 211 | panelHolder.appendChild(mainView); |
michael@0 | 212 | |
michael@0 | 213 | let customizeButton = document.getElementById("PanelUI-customize"); |
michael@0 | 214 | customizeButton.setAttribute("enterLabel", customizeButton.getAttribute("label")); |
michael@0 | 215 | customizeButton.setAttribute("label", customizeButton.getAttribute("exitLabel")); |
michael@0 | 216 | customizeButton.setAttribute("enterTooltiptext", customizeButton.getAttribute("tooltiptext")); |
michael@0 | 217 | customizeButton.setAttribute("tooltiptext", customizeButton.getAttribute("exitTooltiptext")); |
michael@0 | 218 | |
michael@0 | 219 | this._transitioning = true; |
michael@0 | 220 | |
michael@0 | 221 | let customizer = document.getElementById("customization-container"); |
michael@0 | 222 | customizer.parentNode.selectedPanel = customizer; |
michael@0 | 223 | customizer.hidden = false; |
michael@0 | 224 | |
michael@0 | 225 | yield this._doTransition(true); |
michael@0 | 226 | |
michael@0 | 227 | // Let everybody in this window know that we're about to customize. |
michael@0 | 228 | CustomizableUI.dispatchToolboxEvent("customizationstarting", {}, window); |
michael@0 | 229 | |
michael@0 | 230 | this._mainViewContext = mainView.getAttribute("context"); |
michael@0 | 231 | if (this._mainViewContext) { |
michael@0 | 232 | mainView.removeAttribute("context"); |
michael@0 | 233 | } |
michael@0 | 234 | |
michael@0 | 235 | this._showPanelCustomizationPlaceholders(); |
michael@0 | 236 | |
michael@0 | 237 | yield this._wrapToolbarItems(); |
michael@0 | 238 | this.populatePalette(); |
michael@0 | 239 | |
michael@0 | 240 | this._addDragHandlers(this.visiblePalette); |
michael@0 | 241 | |
michael@0 | 242 | window.gNavToolbox.addEventListener("toolbarvisibilitychange", this); |
michael@0 | 243 | |
michael@0 | 244 | document.getElementById("PanelUI-help").setAttribute("disabled", true); |
michael@0 | 245 | document.getElementById("PanelUI-quit").setAttribute("disabled", true); |
michael@0 | 246 | |
michael@0 | 247 | this._updateResetButton(); |
michael@0 | 248 | this._updateUndoResetButton(); |
michael@0 | 249 | |
michael@0 | 250 | this._skipSourceNodeCheck = Services.prefs.getPrefType(kSkipSourceNodePref) == Ci.nsIPrefBranch.PREF_BOOL && |
michael@0 | 251 | Services.prefs.getBoolPref(kSkipSourceNodePref); |
michael@0 | 252 | |
michael@0 | 253 | let customizableToolbars = document.querySelectorAll("toolbar[customizable=true]:not([autohide=true]):not([collapsed=true])"); |
michael@0 | 254 | for (let toolbar of customizableToolbars) |
michael@0 | 255 | toolbar.setAttribute("customizing", true); |
michael@0 | 256 | |
michael@0 | 257 | CustomizableUI.addListener(this); |
michael@0 | 258 | window.PanelUI.endBatchUpdate(); |
michael@0 | 259 | this._customizing = true; |
michael@0 | 260 | this._transitioning = false; |
michael@0 | 261 | |
michael@0 | 262 | // Show the palette now that the transition has finished. |
michael@0 | 263 | this.visiblePalette.hidden = false; |
michael@0 | 264 | window.setTimeout(() => { |
michael@0 | 265 | // Force layout reflow to ensure the animation runs, |
michael@0 | 266 | // and make it async so it doesn't affect the timing. |
michael@0 | 267 | this.visiblePalette.clientTop; |
michael@0 | 268 | this.visiblePalette.setAttribute("showing", "true"); |
michael@0 | 269 | }, 0); |
michael@0 | 270 | this.paletteSpacer.hidden = true; |
michael@0 | 271 | this._updateEmptyPaletteNotice(); |
michael@0 | 272 | |
michael@0 | 273 | this.maybeShowTip(panelHolder); |
michael@0 | 274 | |
michael@0 | 275 | this._handler.isEnteringCustomizeMode = false; |
michael@0 | 276 | panelContents.removeAttribute("customize-transitioning"); |
michael@0 | 277 | |
michael@0 | 278 | CustomizableUI.dispatchToolboxEvent("customizationready", {}, window); |
michael@0 | 279 | this._enableOutlinesTimeout = window.setTimeout(() => { |
michael@0 | 280 | this.document.getElementById("nav-bar").setAttribute("showoutline", "true"); |
michael@0 | 281 | this.panelUIContents.setAttribute("showoutline", "true"); |
michael@0 | 282 | delete this._enableOutlinesTimeout; |
michael@0 | 283 | }, 0); |
michael@0 | 284 | |
michael@0 | 285 | // It's possible that we didn't enter customize mode via the menu panel, |
michael@0 | 286 | // meaning we didn't kick off about:customizing preloading. If that's |
michael@0 | 287 | // the case, let's kick it off for the next time we load this mode. |
michael@0 | 288 | window.gCustomizationTabPreloader.ensurePreloading(); |
michael@0 | 289 | if (!this._wantToBeInCustomizeMode) { |
michael@0 | 290 | this.exit(); |
michael@0 | 291 | } |
michael@0 | 292 | }.bind(this)).then(null, function(e) { |
michael@0 | 293 | ERROR(e); |
michael@0 | 294 | // We should ensure this has been called, and calling it again doesn't hurt: |
michael@0 | 295 | window.PanelUI.endBatchUpdate(); |
michael@0 | 296 | this._handler.isEnteringCustomizeMode = false; |
michael@0 | 297 | }.bind(this)); |
michael@0 | 298 | }, |
michael@0 | 299 | |
michael@0 | 300 | exit: function() { |
michael@0 | 301 | this._wantToBeInCustomizeMode = false; |
michael@0 | 302 | |
michael@0 | 303 | if (!this._customizing || this._handler.isExitingCustomizeMode) { |
michael@0 | 304 | return; |
michael@0 | 305 | } |
michael@0 | 306 | |
michael@0 | 307 | // Entering; want to exit once we've done that. |
michael@0 | 308 | if (this._handler.isEnteringCustomizeMode) { |
michael@0 | 309 | LOG("Attempted to exit while we're in the middle of entering. " + |
michael@0 | 310 | "We'll exit after we've entered"); |
michael@0 | 311 | return; |
michael@0 | 312 | } |
michael@0 | 313 | |
michael@0 | 314 | if (this.resetting) { |
michael@0 | 315 | LOG("Attempted to exit while we're resetting. " + |
michael@0 | 316 | "We'll exit after resetting has finished."); |
michael@0 | 317 | return; |
michael@0 | 318 | } |
michael@0 | 319 | |
michael@0 | 320 | this.hideTip(); |
michael@0 | 321 | |
michael@0 | 322 | this._handler.isExitingCustomizeMode = true; |
michael@0 | 323 | |
michael@0 | 324 | if (this._enableOutlinesTimeout) { |
michael@0 | 325 | this.window.clearTimeout(this._enableOutlinesTimeout); |
michael@0 | 326 | } else { |
michael@0 | 327 | this.document.getElementById("nav-bar").removeAttribute("showoutline"); |
michael@0 | 328 | this.panelUIContents.removeAttribute("showoutline"); |
michael@0 | 329 | } |
michael@0 | 330 | |
michael@0 | 331 | this._removeExtraToolbarsIfEmpty(); |
michael@0 | 332 | |
michael@0 | 333 | CustomizableUI.removeListener(this); |
michael@0 | 334 | |
michael@0 | 335 | this.document.removeEventListener("keypress", this); |
michael@0 | 336 | this.window.PanelUI.menuButton.removeEventListener("command", this); |
michael@0 | 337 | this.window.PanelUI.menuButton.open = false; |
michael@0 | 338 | |
michael@0 | 339 | this.window.PanelUI.beginBatchUpdate(); |
michael@0 | 340 | |
michael@0 | 341 | this._removePanelCustomizationPlaceholders(); |
michael@0 | 342 | |
michael@0 | 343 | let window = this.window; |
michael@0 | 344 | let document = this.document; |
michael@0 | 345 | let documentElement = document.documentElement; |
michael@0 | 346 | |
michael@0 | 347 | // Hide the palette before starting the transition for increased perf. |
michael@0 | 348 | this.paletteSpacer.hidden = false; |
michael@0 | 349 | this.visiblePalette.hidden = true; |
michael@0 | 350 | this.visiblePalette.removeAttribute("showing"); |
michael@0 | 351 | this.paletteEmptyNotice.hidden = true; |
michael@0 | 352 | |
michael@0 | 353 | // Disable the button-text fade-out mask |
michael@0 | 354 | // during the transition for increased perf. |
michael@0 | 355 | let panelContents = window.PanelUI.contents; |
michael@0 | 356 | panelContents.setAttribute("customize-transitioning", "true"); |
michael@0 | 357 | |
michael@0 | 358 | // Disable the reset and undo reset buttons while transitioning: |
michael@0 | 359 | let resetButton = this.document.getElementById("customization-reset-button"); |
michael@0 | 360 | let undoResetButton = this.document.getElementById("customization-undo-reset-button"); |
michael@0 | 361 | undoResetButton.hidden = resetButton.disabled = true; |
michael@0 | 362 | |
michael@0 | 363 | this._transitioning = true; |
michael@0 | 364 | |
michael@0 | 365 | Task.spawn(function() { |
michael@0 | 366 | yield this.depopulatePalette(); |
michael@0 | 367 | |
michael@0 | 368 | yield this._doTransition(false); |
michael@0 | 369 | |
michael@0 | 370 | let browser = document.getElementById("browser"); |
michael@0 | 371 | if (this.browser.selectedBrowser.currentURI.spec == kAboutURI) { |
michael@0 | 372 | let custBrowser = this.browser.selectedBrowser; |
michael@0 | 373 | if (custBrowser.canGoBack) { |
michael@0 | 374 | // If there's history to this tab, just go back. |
michael@0 | 375 | // Note that this throws an exception if the previous document has a |
michael@0 | 376 | // problematic URL (e.g. about:idontexist) |
michael@0 | 377 | try { |
michael@0 | 378 | custBrowser.goBack(); |
michael@0 | 379 | } catch (ex) { |
michael@0 | 380 | ERROR(ex); |
michael@0 | 381 | } |
michael@0 | 382 | } else { |
michael@0 | 383 | // If we can't go back, we're removing the about:customization tab. |
michael@0 | 384 | // We only do this if we're the top window for this window (so not |
michael@0 | 385 | // a dialog window, for example). |
michael@0 | 386 | if (window.getTopWin(true) == window) { |
michael@0 | 387 | let customizationTab = this.browser.selectedTab; |
michael@0 | 388 | if (this.browser.browsers.length == 1) { |
michael@0 | 389 | window.BrowserOpenTab(); |
michael@0 | 390 | } |
michael@0 | 391 | this.browser.removeTab(customizationTab); |
michael@0 | 392 | } |
michael@0 | 393 | } |
michael@0 | 394 | } |
michael@0 | 395 | browser.parentNode.selectedPanel = browser; |
michael@0 | 396 | let customizer = document.getElementById("customization-container"); |
michael@0 | 397 | customizer.hidden = true; |
michael@0 | 398 | |
michael@0 | 399 | window.gNavToolbox.removeEventListener("toolbarvisibilitychange", this); |
michael@0 | 400 | |
michael@0 | 401 | DragPositionManager.stop(); |
michael@0 | 402 | this._removeDragHandlers(this.visiblePalette); |
michael@0 | 403 | |
michael@0 | 404 | yield this._unwrapToolbarItems(); |
michael@0 | 405 | |
michael@0 | 406 | if (this._changed) { |
michael@0 | 407 | // XXXmconley: At first, it seems strange to also persist the old way with |
michael@0 | 408 | // currentset - but this might actually be useful for switching |
michael@0 | 409 | // to old builds. We might want to keep this around for a little |
michael@0 | 410 | // bit. |
michael@0 | 411 | this.persistCurrentSets(); |
michael@0 | 412 | } |
michael@0 | 413 | |
michael@0 | 414 | // And drop all area references. |
michael@0 | 415 | this.areas = []; |
michael@0 | 416 | |
michael@0 | 417 | // Let everybody in this window know that we're starting to |
michael@0 | 418 | // exit customization mode. |
michael@0 | 419 | CustomizableUI.dispatchToolboxEvent("customizationending", {}, window); |
michael@0 | 420 | |
michael@0 | 421 | window.PanelUI.setMainView(window.PanelUI.mainView); |
michael@0 | 422 | window.PanelUI.menuButton.disabled = false; |
michael@0 | 423 | |
michael@0 | 424 | let customizeButton = document.getElementById("PanelUI-customize"); |
michael@0 | 425 | customizeButton.setAttribute("exitLabel", customizeButton.getAttribute("label")); |
michael@0 | 426 | customizeButton.setAttribute("label", customizeButton.getAttribute("enterLabel")); |
michael@0 | 427 | customizeButton.setAttribute("exitTooltiptext", customizeButton.getAttribute("tooltiptext")); |
michael@0 | 428 | customizeButton.setAttribute("tooltiptext", customizeButton.getAttribute("enterTooltiptext")); |
michael@0 | 429 | |
michael@0 | 430 | // We have to use setAttribute/removeAttribute here instead of the |
michael@0 | 431 | // property because the XBL property will be set later, and right |
michael@0 | 432 | // now we'd be setting an expando, which breaks the XBL property. |
michael@0 | 433 | document.getElementById("PanelUI-help").removeAttribute("disabled"); |
michael@0 | 434 | document.getElementById("PanelUI-quit").removeAttribute("disabled"); |
michael@0 | 435 | |
michael@0 | 436 | panelContents.removeAttribute("customize-transitioning"); |
michael@0 | 437 | |
michael@0 | 438 | // We need to set this._customizing to false before removing the tab |
michael@0 | 439 | // or the TabSelect event handler will think that we are exiting |
michael@0 | 440 | // customization mode for a second time. |
michael@0 | 441 | this._customizing = false; |
michael@0 | 442 | |
michael@0 | 443 | let mainView = window.PanelUI.mainView; |
michael@0 | 444 | if (this._mainViewContext) { |
michael@0 | 445 | mainView.setAttribute("context", this._mainViewContext); |
michael@0 | 446 | } |
michael@0 | 447 | |
michael@0 | 448 | if (this.document.documentElement._lightweightTheme) |
michael@0 | 449 | this.document.documentElement._lightweightTheme.enable(); |
michael@0 | 450 | |
michael@0 | 451 | let customizableToolbars = document.querySelectorAll("toolbar[customizable=true]:not([autohide=true])"); |
michael@0 | 452 | for (let toolbar of customizableToolbars) |
michael@0 | 453 | toolbar.removeAttribute("customizing"); |
michael@0 | 454 | |
michael@0 | 455 | this.window.PanelUI.endBatchUpdate(); |
michael@0 | 456 | this._changed = false; |
michael@0 | 457 | this._transitioning = false; |
michael@0 | 458 | this._handler.isExitingCustomizeMode = false; |
michael@0 | 459 | CustomizableUI.dispatchToolboxEvent("aftercustomization", {}, window); |
michael@0 | 460 | CustomizableUI.notifyEndCustomizing(window); |
michael@0 | 461 | |
michael@0 | 462 | if (this._wantToBeInCustomizeMode) { |
michael@0 | 463 | this.enter(); |
michael@0 | 464 | } |
michael@0 | 465 | }.bind(this)).then(null, function(e) { |
michael@0 | 466 | ERROR(e); |
michael@0 | 467 | // We should ensure this has been called, and calling it again doesn't hurt: |
michael@0 | 468 | window.PanelUI.endBatchUpdate(); |
michael@0 | 469 | this._handler.isExitingCustomizeMode = false; |
michael@0 | 470 | }.bind(this)); |
michael@0 | 471 | }, |
michael@0 | 472 | |
michael@0 | 473 | /** |
michael@0 | 474 | * The customize mode transition has 3 phases when entering: |
michael@0 | 475 | * 1) Pre-customization mode |
michael@0 | 476 | * This is the starting phase of the browser. |
michael@0 | 477 | * 2) customize-entering |
michael@0 | 478 | * This phase is a transition, optimized for smoothness. |
michael@0 | 479 | * 3) customize-entered |
michael@0 | 480 | * After the transition completes, this phase draws all of the |
michael@0 | 481 | * expensive detail that isn't necessary during the second phase. |
michael@0 | 482 | * |
michael@0 | 483 | * Exiting customization mode has a similar set of phases, but in reverse |
michael@0 | 484 | * order - customize-entered, customize-exiting, pre-customization mode. |
michael@0 | 485 | * |
michael@0 | 486 | * When in the customize-entering, customize-entered, or customize-exiting |
michael@0 | 487 | * phases, there is a "customizing" attribute set on the main-window to simplify |
michael@0 | 488 | * excluding certain styles while in any phase of customize mode. |
michael@0 | 489 | */ |
michael@0 | 490 | _doTransition: function(aEntering) { |
michael@0 | 491 | let deferred = Promise.defer(); |
michael@0 | 492 | let deck = this.document.getElementById("content-deck"); |
michael@0 | 493 | |
michael@0 | 494 | let customizeTransitionEnd = function(aEvent) { |
michael@0 | 495 | if (aEvent != "timedout" && |
michael@0 | 496 | (aEvent.originalTarget != deck || aEvent.propertyName != "margin-left")) { |
michael@0 | 497 | return; |
michael@0 | 498 | } |
michael@0 | 499 | this.window.clearTimeout(catchAllTimeout); |
michael@0 | 500 | // Bug 962677: We let the event loop breathe for before we do the final |
michael@0 | 501 | // stage of the transition to improve perceived performance. |
michael@0 | 502 | this.window.setTimeout(function () { |
michael@0 | 503 | deck.removeEventListener("transitionend", customizeTransitionEnd); |
michael@0 | 504 | |
michael@0 | 505 | if (!aEntering) { |
michael@0 | 506 | this.document.documentElement.removeAttribute("customize-exiting"); |
michael@0 | 507 | this.document.documentElement.removeAttribute("customizing"); |
michael@0 | 508 | } else { |
michael@0 | 509 | this.document.documentElement.setAttribute("customize-entered", true); |
michael@0 | 510 | this.document.documentElement.removeAttribute("customize-entering"); |
michael@0 | 511 | } |
michael@0 | 512 | CustomizableUI.dispatchToolboxEvent("customization-transitionend", aEntering, this.window); |
michael@0 | 513 | |
michael@0 | 514 | deferred.resolve(); |
michael@0 | 515 | }.bind(this), 0); |
michael@0 | 516 | }.bind(this); |
michael@0 | 517 | deck.addEventListener("transitionend", customizeTransitionEnd); |
michael@0 | 518 | |
michael@0 | 519 | if (gDisableAnimation) { |
michael@0 | 520 | this.document.getElementById("tab-view-deck").setAttribute("fastcustomizeanimation", true); |
michael@0 | 521 | } |
michael@0 | 522 | if (aEntering) { |
michael@0 | 523 | this.document.documentElement.setAttribute("customizing", true); |
michael@0 | 524 | this.document.documentElement.setAttribute("customize-entering", true); |
michael@0 | 525 | } else { |
michael@0 | 526 | this.document.documentElement.setAttribute("customize-exiting", true); |
michael@0 | 527 | this.document.documentElement.removeAttribute("customize-entered"); |
michael@0 | 528 | } |
michael@0 | 529 | |
michael@0 | 530 | let catchAll = () => customizeTransitionEnd("timedout"); |
michael@0 | 531 | let catchAllTimeout = this.window.setTimeout(catchAll, kMaxTransitionDurationMs); |
michael@0 | 532 | return deferred.promise; |
michael@0 | 533 | }, |
michael@0 | 534 | |
michael@0 | 535 | maybeShowTip: function(aAnchor) { |
michael@0 | 536 | let shown = false; |
michael@0 | 537 | const kShownPref = "browser.customizemode.tip0.shown"; |
michael@0 | 538 | try { |
michael@0 | 539 | shown = Services.prefs.getBoolPref(kShownPref); |
michael@0 | 540 | } catch (ex) {} |
michael@0 | 541 | if (shown) |
michael@0 | 542 | return; |
michael@0 | 543 | |
michael@0 | 544 | let anchorNode = aAnchor || this.document.getElementById("customization-panelHolder"); |
michael@0 | 545 | let messageNode = this.tipPanel.querySelector(".customization-tipPanel-contentMessage"); |
michael@0 | 546 | if (!messageNode.childElementCount) { |
michael@0 | 547 | // Put the tip contents in the popup. |
michael@0 | 548 | let bundle = this.document.getElementById("bundle_browser"); |
michael@0 | 549 | const kLabelClass = "customization-tipPanel-link"; |
michael@0 | 550 | messageNode.innerHTML = bundle.getFormattedString("customizeTips.tip0", [ |
michael@0 | 551 | "<label class=\"customization-tipPanel-em\" value=\"" + |
michael@0 | 552 | bundle.getString("customizeTips.tip0.hint") + "\"/>", |
michael@0 | 553 | this.document.getElementById("bundle_brand").getString("brandShortName"), |
michael@0 | 554 | "<label class=\"" + kLabelClass + " text-link\" value=\"" + |
michael@0 | 555 | bundle.getString("customizeTips.tip0.learnMore") + "\"/>" |
michael@0 | 556 | ]); |
michael@0 | 557 | |
michael@0 | 558 | messageNode.querySelector("." + kLabelClass).addEventListener("click", () => { |
michael@0 | 559 | let url = Services.urlFormatter.formatURLPref("browser.customizemode.tip0.learnMoreUrl"); |
michael@0 | 560 | let browser = this.browser; |
michael@0 | 561 | browser.selectedTab = browser.addTab(url); |
michael@0 | 562 | this.hideTip(); |
michael@0 | 563 | }); |
michael@0 | 564 | } |
michael@0 | 565 | |
michael@0 | 566 | this.tipPanel.hidden = false; |
michael@0 | 567 | this.tipPanel.openPopup(anchorNode); |
michael@0 | 568 | Services.prefs.setBoolPref(kShownPref, true); |
michael@0 | 569 | }, |
michael@0 | 570 | |
michael@0 | 571 | hideTip: function() { |
michael@0 | 572 | this.tipPanel.hidePopup(); |
michael@0 | 573 | }, |
michael@0 | 574 | |
michael@0 | 575 | _getCustomizableChildForNode: function(aNode) { |
michael@0 | 576 | // NB: adjusted from _getCustomizableParent to keep that method fast |
michael@0 | 577 | // (it's used during drags), and avoid multiple DOM loops |
michael@0 | 578 | let areas = CustomizableUI.areas; |
michael@0 | 579 | // Caching this length is important because otherwise we'll also iterate |
michael@0 | 580 | // over items we add to the end from within the loop. |
michael@0 | 581 | let numberOfAreas = areas.length; |
michael@0 | 582 | for (let i = 0; i < numberOfAreas; i++) { |
michael@0 | 583 | let area = areas[i]; |
michael@0 | 584 | let areaNode = aNode.ownerDocument.getElementById(area); |
michael@0 | 585 | let customizationTarget = areaNode && areaNode.customizationTarget; |
michael@0 | 586 | if (customizationTarget && customizationTarget != areaNode) { |
michael@0 | 587 | areas.push(customizationTarget.id); |
michael@0 | 588 | } |
michael@0 | 589 | let overflowTarget = areaNode.getAttribute("overflowtarget"); |
michael@0 | 590 | if (overflowTarget) { |
michael@0 | 591 | areas.push(overflowTarget); |
michael@0 | 592 | } |
michael@0 | 593 | } |
michael@0 | 594 | areas.push(kPaletteId); |
michael@0 | 595 | |
michael@0 | 596 | while (aNode && aNode.parentNode) { |
michael@0 | 597 | let parent = aNode.parentNode; |
michael@0 | 598 | if (areas.indexOf(parent.id) != -1) { |
michael@0 | 599 | return aNode; |
michael@0 | 600 | } |
michael@0 | 601 | aNode = parent; |
michael@0 | 602 | } |
michael@0 | 603 | return null; |
michael@0 | 604 | }, |
michael@0 | 605 | |
michael@0 | 606 | addToToolbar: function(aNode) { |
michael@0 | 607 | aNode = this._getCustomizableChildForNode(aNode); |
michael@0 | 608 | if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) { |
michael@0 | 609 | aNode = aNode.firstChild; |
michael@0 | 610 | } |
michael@0 | 611 | CustomizableUI.addWidgetToArea(aNode.id, CustomizableUI.AREA_NAVBAR); |
michael@0 | 612 | if (!this._customizing) { |
michael@0 | 613 | CustomizableUI.dispatchToolboxEvent("customizationchange"); |
michael@0 | 614 | } |
michael@0 | 615 | }, |
michael@0 | 616 | |
michael@0 | 617 | addToPanel: function(aNode) { |
michael@0 | 618 | aNode = this._getCustomizableChildForNode(aNode); |
michael@0 | 619 | if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) { |
michael@0 | 620 | aNode = aNode.firstChild; |
michael@0 | 621 | } |
michael@0 | 622 | CustomizableUI.addWidgetToArea(aNode.id, CustomizableUI.AREA_PANEL); |
michael@0 | 623 | if (!this._customizing) { |
michael@0 | 624 | CustomizableUI.dispatchToolboxEvent("customizationchange"); |
michael@0 | 625 | } |
michael@0 | 626 | }, |
michael@0 | 627 | |
michael@0 | 628 | removeFromArea: function(aNode) { |
michael@0 | 629 | aNode = this._getCustomizableChildForNode(aNode); |
michael@0 | 630 | if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) { |
michael@0 | 631 | aNode = aNode.firstChild; |
michael@0 | 632 | } |
michael@0 | 633 | CustomizableUI.removeWidgetFromArea(aNode.id); |
michael@0 | 634 | if (!this._customizing) { |
michael@0 | 635 | CustomizableUI.dispatchToolboxEvent("customizationchange"); |
michael@0 | 636 | } |
michael@0 | 637 | }, |
michael@0 | 638 | |
michael@0 | 639 | populatePalette: function() { |
michael@0 | 640 | let fragment = this.document.createDocumentFragment(); |
michael@0 | 641 | let toolboxPalette = this.window.gNavToolbox.palette; |
michael@0 | 642 | |
michael@0 | 643 | try { |
michael@0 | 644 | let unusedWidgets = CustomizableUI.getUnusedWidgets(toolboxPalette); |
michael@0 | 645 | for (let widget of unusedWidgets) { |
michael@0 | 646 | let paletteItem = this.makePaletteItem(widget, "palette"); |
michael@0 | 647 | fragment.appendChild(paletteItem); |
michael@0 | 648 | } |
michael@0 | 649 | |
michael@0 | 650 | this.visiblePalette.appendChild(fragment); |
michael@0 | 651 | this._stowedPalette = this.window.gNavToolbox.palette; |
michael@0 | 652 | this.window.gNavToolbox.palette = this.visiblePalette; |
michael@0 | 653 | } catch (ex) { |
michael@0 | 654 | ERROR(ex); |
michael@0 | 655 | } |
michael@0 | 656 | }, |
michael@0 | 657 | |
michael@0 | 658 | //XXXunf Maybe this should use -moz-element instead of wrapping the node? |
michael@0 | 659 | // Would ensure no weird interactions/event handling from original node, |
michael@0 | 660 | // and makes it possible to put this in a lazy-loaded iframe/real tab |
michael@0 | 661 | // while still getting rid of the need for overlays. |
michael@0 | 662 | makePaletteItem: function(aWidget, aPlace) { |
michael@0 | 663 | let widgetNode = aWidget.forWindow(this.window).node; |
michael@0 | 664 | let wrapper = this.createOrUpdateWrapper(widgetNode, aPlace); |
michael@0 | 665 | wrapper.appendChild(widgetNode); |
michael@0 | 666 | return wrapper; |
michael@0 | 667 | }, |
michael@0 | 668 | |
michael@0 | 669 | depopulatePalette: function() { |
michael@0 | 670 | return Task.spawn(function() { |
michael@0 | 671 | this.visiblePalette.hidden = true; |
michael@0 | 672 | let paletteChild = this.visiblePalette.firstChild; |
michael@0 | 673 | let nextChild; |
michael@0 | 674 | while (paletteChild) { |
michael@0 | 675 | nextChild = paletteChild.nextElementSibling; |
michael@0 | 676 | let provider = CustomizableUI.getWidget(paletteChild.id).provider; |
michael@0 | 677 | if (provider == CustomizableUI.PROVIDER_XUL) { |
michael@0 | 678 | let unwrappedPaletteItem = |
michael@0 | 679 | yield this.deferredUnwrapToolbarItem(paletteChild); |
michael@0 | 680 | this._stowedPalette.appendChild(unwrappedPaletteItem); |
michael@0 | 681 | } else if (provider == CustomizableUI.PROVIDER_API) { |
michael@0 | 682 | //XXXunf Currently this doesn't destroy the (now unused) node. It would |
michael@0 | 683 | // be good to do so, but we need to keep strong refs to it in |
michael@0 | 684 | // CustomizableUI (can't iterate of WeakMaps), and there's the |
michael@0 | 685 | // question of what behavior wrappers should have if consumers |
michael@0 | 686 | // keep hold of them. |
michael@0 | 687 | //widget.destroyInstance(widgetNode); |
michael@0 | 688 | } else if (provider == CustomizableUI.PROVIDER_SPECIAL) { |
michael@0 | 689 | this.visiblePalette.removeChild(paletteChild); |
michael@0 | 690 | } |
michael@0 | 691 | |
michael@0 | 692 | paletteChild = nextChild; |
michael@0 | 693 | } |
michael@0 | 694 | this.visiblePalette.hidden = false; |
michael@0 | 695 | this.window.gNavToolbox.palette = this._stowedPalette; |
michael@0 | 696 | }.bind(this)).then(null, ERROR); |
michael@0 | 697 | }, |
michael@0 | 698 | |
michael@0 | 699 | isCustomizableItem: function(aNode) { |
michael@0 | 700 | return aNode.localName == "toolbarbutton" || |
michael@0 | 701 | aNode.localName == "toolbaritem" || |
michael@0 | 702 | aNode.localName == "toolbarseparator" || |
michael@0 | 703 | aNode.localName == "toolbarspring" || |
michael@0 | 704 | aNode.localName == "toolbarspacer"; |
michael@0 | 705 | }, |
michael@0 | 706 | |
michael@0 | 707 | isWrappedToolbarItem: function(aNode) { |
michael@0 | 708 | return aNode.localName == "toolbarpaletteitem"; |
michael@0 | 709 | }, |
michael@0 | 710 | |
michael@0 | 711 | deferredWrapToolbarItem: function(aNode, aPlace) { |
michael@0 | 712 | let deferred = Promise.defer(); |
michael@0 | 713 | |
michael@0 | 714 | dispatchFunction(function() { |
michael@0 | 715 | let wrapper = this.wrapToolbarItem(aNode, aPlace); |
michael@0 | 716 | deferred.resolve(wrapper); |
michael@0 | 717 | }.bind(this)) |
michael@0 | 718 | |
michael@0 | 719 | return deferred.promise; |
michael@0 | 720 | }, |
michael@0 | 721 | |
michael@0 | 722 | wrapToolbarItem: function(aNode, aPlace) { |
michael@0 | 723 | if (!this.isCustomizableItem(aNode)) { |
michael@0 | 724 | return aNode; |
michael@0 | 725 | } |
michael@0 | 726 | let wrapper = this.createOrUpdateWrapper(aNode, aPlace); |
michael@0 | 727 | |
michael@0 | 728 | // It's possible that this toolbar node is "mid-flight" and doesn't have |
michael@0 | 729 | // a parent, in which case we skip replacing it. This can happen if a |
michael@0 | 730 | // toolbar item has been dragged into the palette. In that case, we tell |
michael@0 | 731 | // CustomizableUI to remove the widget from its area before putting the |
michael@0 | 732 | // widget in the palette - so the node will have no parent. |
michael@0 | 733 | if (aNode.parentNode) { |
michael@0 | 734 | aNode = aNode.parentNode.replaceChild(wrapper, aNode); |
michael@0 | 735 | } |
michael@0 | 736 | wrapper.appendChild(aNode); |
michael@0 | 737 | return wrapper; |
michael@0 | 738 | }, |
michael@0 | 739 | |
michael@0 | 740 | createOrUpdateWrapper: function(aNode, aPlace, aIsUpdate) { |
michael@0 | 741 | let wrapper; |
michael@0 | 742 | if (aIsUpdate && aNode.parentNode && aNode.parentNode.localName == "toolbarpaletteitem") { |
michael@0 | 743 | wrapper = aNode.parentNode; |
michael@0 | 744 | aPlace = wrapper.getAttribute("place"); |
michael@0 | 745 | } else { |
michael@0 | 746 | wrapper = this.document.createElement("toolbarpaletteitem"); |
michael@0 | 747 | // "place" is used by toolkit to add the toolbarpaletteitem-palette |
michael@0 | 748 | // binding to a toolbarpaletteitem, which gives it a label node for when |
michael@0 | 749 | // it's sitting in the palette. |
michael@0 | 750 | wrapper.setAttribute("place", aPlace); |
michael@0 | 751 | } |
michael@0 | 752 | |
michael@0 | 753 | |
michael@0 | 754 | // Ensure the wrapped item doesn't look like it's in any special state, and |
michael@0 | 755 | // can't be interactved with when in the customization palette. |
michael@0 | 756 | if (aNode.hasAttribute("command")) { |
michael@0 | 757 | wrapper.setAttribute("itemcommand", aNode.getAttribute("command")); |
michael@0 | 758 | aNode.removeAttribute("command"); |
michael@0 | 759 | } |
michael@0 | 760 | |
michael@0 | 761 | if (aNode.hasAttribute("observes")) { |
michael@0 | 762 | wrapper.setAttribute("itemobserves", aNode.getAttribute("observes")); |
michael@0 | 763 | aNode.removeAttribute("observes"); |
michael@0 | 764 | } |
michael@0 | 765 | |
michael@0 | 766 | if (aNode.getAttribute("checked") == "true") { |
michael@0 | 767 | wrapper.setAttribute("itemchecked", "true"); |
michael@0 | 768 | aNode.removeAttribute("checked"); |
michael@0 | 769 | } |
michael@0 | 770 | |
michael@0 | 771 | if (aNode.hasAttribute("id")) { |
michael@0 | 772 | wrapper.setAttribute("id", "wrapper-" + aNode.getAttribute("id")); |
michael@0 | 773 | } |
michael@0 | 774 | |
michael@0 | 775 | if (aNode.hasAttribute("label")) { |
michael@0 | 776 | wrapper.setAttribute("title", aNode.getAttribute("label")); |
michael@0 | 777 | } else if (aNode.hasAttribute("title")) { |
michael@0 | 778 | wrapper.setAttribute("title", aNode.getAttribute("title")); |
michael@0 | 779 | } |
michael@0 | 780 | |
michael@0 | 781 | if (aNode.hasAttribute("flex")) { |
michael@0 | 782 | wrapper.setAttribute("flex", aNode.getAttribute("flex")); |
michael@0 | 783 | } |
michael@0 | 784 | |
michael@0 | 785 | if (aPlace == "panel") { |
michael@0 | 786 | if (aNode.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) { |
michael@0 | 787 | wrapper.setAttribute("haswideitem", "true"); |
michael@0 | 788 | } else if (wrapper.hasAttribute("haswideitem")) { |
michael@0 | 789 | wrapper.removeAttribute("haswideitem"); |
michael@0 | 790 | } |
michael@0 | 791 | } |
michael@0 | 792 | |
michael@0 | 793 | let removable = aPlace == "palette" || CustomizableUI.isWidgetRemovable(aNode); |
michael@0 | 794 | wrapper.setAttribute("removable", removable); |
michael@0 | 795 | |
michael@0 | 796 | let contextMenuAttrName = aNode.getAttribute("context") ? "context" : |
michael@0 | 797 | aNode.getAttribute("contextmenu") ? "contextmenu" : ""; |
michael@0 | 798 | let currentContextMenu = aNode.getAttribute(contextMenuAttrName); |
michael@0 | 799 | let contextMenuForPlace = aPlace == "panel" ? |
michael@0 | 800 | kPanelItemContextMenu : |
michael@0 | 801 | kPaletteItemContextMenu; |
michael@0 | 802 | if (aPlace != "toolbar") { |
michael@0 | 803 | wrapper.setAttribute("context", contextMenuForPlace); |
michael@0 | 804 | } |
michael@0 | 805 | // Only keep track of the menu if it is non-default. |
michael@0 | 806 | if (currentContextMenu && |
michael@0 | 807 | currentContextMenu != contextMenuForPlace) { |
michael@0 | 808 | aNode.setAttribute("wrapped-context", currentContextMenu); |
michael@0 | 809 | aNode.setAttribute("wrapped-contextAttrName", contextMenuAttrName) |
michael@0 | 810 | aNode.removeAttribute(contextMenuAttrName); |
michael@0 | 811 | } else if (currentContextMenu == contextMenuForPlace) { |
michael@0 | 812 | aNode.removeAttribute(contextMenuAttrName); |
michael@0 | 813 | } |
michael@0 | 814 | |
michael@0 | 815 | // Only add listeners for newly created wrappers: |
michael@0 | 816 | if (!aIsUpdate) { |
michael@0 | 817 | wrapper.addEventListener("mousedown", this); |
michael@0 | 818 | wrapper.addEventListener("mouseup", this); |
michael@0 | 819 | } |
michael@0 | 820 | |
michael@0 | 821 | return wrapper; |
michael@0 | 822 | }, |
michael@0 | 823 | |
michael@0 | 824 | deferredUnwrapToolbarItem: function(aWrapper) { |
michael@0 | 825 | let deferred = Promise.defer(); |
michael@0 | 826 | dispatchFunction(function() { |
michael@0 | 827 | let item = null; |
michael@0 | 828 | try { |
michael@0 | 829 | item = this.unwrapToolbarItem(aWrapper); |
michael@0 | 830 | } catch (ex) { |
michael@0 | 831 | Cu.reportError(ex); |
michael@0 | 832 | } |
michael@0 | 833 | deferred.resolve(item); |
michael@0 | 834 | }.bind(this)); |
michael@0 | 835 | return deferred.promise; |
michael@0 | 836 | }, |
michael@0 | 837 | |
michael@0 | 838 | unwrapToolbarItem: function(aWrapper) { |
michael@0 | 839 | if (aWrapper.nodeName != "toolbarpaletteitem") { |
michael@0 | 840 | return aWrapper; |
michael@0 | 841 | } |
michael@0 | 842 | aWrapper.removeEventListener("mousedown", this); |
michael@0 | 843 | aWrapper.removeEventListener("mouseup", this); |
michael@0 | 844 | |
michael@0 | 845 | let place = aWrapper.getAttribute("place"); |
michael@0 | 846 | |
michael@0 | 847 | let toolbarItem = aWrapper.firstChild; |
michael@0 | 848 | if (!toolbarItem) { |
michael@0 | 849 | ERROR("no toolbarItem child for " + aWrapper.tagName + "#" + aWrapper.id); |
michael@0 | 850 | aWrapper.remove(); |
michael@0 | 851 | return null; |
michael@0 | 852 | } |
michael@0 | 853 | |
michael@0 | 854 | if (aWrapper.hasAttribute("itemobserves")) { |
michael@0 | 855 | toolbarItem.setAttribute("observes", aWrapper.getAttribute("itemobserves")); |
michael@0 | 856 | } |
michael@0 | 857 | |
michael@0 | 858 | if (aWrapper.hasAttribute("itemchecked")) { |
michael@0 | 859 | toolbarItem.checked = true; |
michael@0 | 860 | } |
michael@0 | 861 | |
michael@0 | 862 | if (aWrapper.hasAttribute("itemcommand")) { |
michael@0 | 863 | let commandID = aWrapper.getAttribute("itemcommand"); |
michael@0 | 864 | toolbarItem.setAttribute("command", commandID); |
michael@0 | 865 | |
michael@0 | 866 | //XXX Bug 309953 - toolbarbuttons aren't in sync with their commands after customizing |
michael@0 | 867 | let command = this.document.getElementById(commandID); |
michael@0 | 868 | if (command && command.hasAttribute("disabled")) { |
michael@0 | 869 | toolbarItem.setAttribute("disabled", command.getAttribute("disabled")); |
michael@0 | 870 | } |
michael@0 | 871 | } |
michael@0 | 872 | |
michael@0 | 873 | let wrappedContext = toolbarItem.getAttribute("wrapped-context"); |
michael@0 | 874 | if (wrappedContext) { |
michael@0 | 875 | let contextAttrName = toolbarItem.getAttribute("wrapped-contextAttrName"); |
michael@0 | 876 | toolbarItem.setAttribute(contextAttrName, wrappedContext); |
michael@0 | 877 | toolbarItem.removeAttribute("wrapped-contextAttrName"); |
michael@0 | 878 | toolbarItem.removeAttribute("wrapped-context"); |
michael@0 | 879 | } else if (place == "panel") { |
michael@0 | 880 | toolbarItem.setAttribute("context", kPanelItemContextMenu); |
michael@0 | 881 | } |
michael@0 | 882 | |
michael@0 | 883 | if (aWrapper.parentNode) { |
michael@0 | 884 | aWrapper.parentNode.replaceChild(toolbarItem, aWrapper); |
michael@0 | 885 | } |
michael@0 | 886 | return toolbarItem; |
michael@0 | 887 | }, |
michael@0 | 888 | |
michael@0 | 889 | _wrapToolbarItems: function() { |
michael@0 | 890 | let window = this.window; |
michael@0 | 891 | // Add drag-and-drop event handlers to all of the customizable areas. |
michael@0 | 892 | return Task.spawn(function() { |
michael@0 | 893 | this.areas = []; |
michael@0 | 894 | for (let area of CustomizableUI.areas) { |
michael@0 | 895 | let target = CustomizableUI.getCustomizeTargetForArea(area, window); |
michael@0 | 896 | this._addDragHandlers(target); |
michael@0 | 897 | for (let child of target.children) { |
michael@0 | 898 | if (this.isCustomizableItem(child)) { |
michael@0 | 899 | yield this.deferredWrapToolbarItem(child, CustomizableUI.getPlaceForItem(child)); |
michael@0 | 900 | } |
michael@0 | 901 | } |
michael@0 | 902 | this.areas.push(target); |
michael@0 | 903 | } |
michael@0 | 904 | }.bind(this)).then(null, ERROR); |
michael@0 | 905 | }, |
michael@0 | 906 | |
michael@0 | 907 | _addDragHandlers: function(aTarget) { |
michael@0 | 908 | aTarget.addEventListener("dragstart", this, true); |
michael@0 | 909 | aTarget.addEventListener("dragover", this, true); |
michael@0 | 910 | aTarget.addEventListener("dragexit", this, true); |
michael@0 | 911 | aTarget.addEventListener("drop", this, true); |
michael@0 | 912 | aTarget.addEventListener("dragend", this, true); |
michael@0 | 913 | }, |
michael@0 | 914 | |
michael@0 | 915 | _wrapItemsInArea: function(target) { |
michael@0 | 916 | for (let child of target.children) { |
michael@0 | 917 | if (this.isCustomizableItem(child)) { |
michael@0 | 918 | this.wrapToolbarItem(child, CustomizableUI.getPlaceForItem(child)); |
michael@0 | 919 | } |
michael@0 | 920 | } |
michael@0 | 921 | }, |
michael@0 | 922 | |
michael@0 | 923 | _removeDragHandlers: function(aTarget) { |
michael@0 | 924 | aTarget.removeEventListener("dragstart", this, true); |
michael@0 | 925 | aTarget.removeEventListener("dragover", this, true); |
michael@0 | 926 | aTarget.removeEventListener("dragexit", this, true); |
michael@0 | 927 | aTarget.removeEventListener("drop", this, true); |
michael@0 | 928 | aTarget.removeEventListener("dragend", this, true); |
michael@0 | 929 | }, |
michael@0 | 930 | |
michael@0 | 931 | _unwrapItemsInArea: function(target) { |
michael@0 | 932 | for (let toolbarItem of target.children) { |
michael@0 | 933 | if (this.isWrappedToolbarItem(toolbarItem)) { |
michael@0 | 934 | this.unwrapToolbarItem(toolbarItem); |
michael@0 | 935 | } |
michael@0 | 936 | } |
michael@0 | 937 | }, |
michael@0 | 938 | |
michael@0 | 939 | _unwrapToolbarItems: function() { |
michael@0 | 940 | return Task.spawn(function() { |
michael@0 | 941 | for (let target of this.areas) { |
michael@0 | 942 | for (let toolbarItem of target.children) { |
michael@0 | 943 | if (this.isWrappedToolbarItem(toolbarItem)) { |
michael@0 | 944 | yield this.deferredUnwrapToolbarItem(toolbarItem); |
michael@0 | 945 | } |
michael@0 | 946 | } |
michael@0 | 947 | this._removeDragHandlers(target); |
michael@0 | 948 | } |
michael@0 | 949 | }.bind(this)).then(null, ERROR); |
michael@0 | 950 | }, |
michael@0 | 951 | |
michael@0 | 952 | _removeExtraToolbarsIfEmpty: function() { |
michael@0 | 953 | let toolbox = this.window.gNavToolbox; |
michael@0 | 954 | for (let child of toolbox.children) { |
michael@0 | 955 | if (child.hasAttribute("customindex")) { |
michael@0 | 956 | let placements = CustomizableUI.getWidgetIdsInArea(child.id); |
michael@0 | 957 | if (!placements.length) { |
michael@0 | 958 | CustomizableUI.removeExtraToolbar(child.id); |
michael@0 | 959 | } |
michael@0 | 960 | } |
michael@0 | 961 | } |
michael@0 | 962 | }, |
michael@0 | 963 | |
michael@0 | 964 | persistCurrentSets: function(aSetBeforePersisting) { |
michael@0 | 965 | let document = this.document; |
michael@0 | 966 | let toolbars = document.querySelectorAll("toolbar[customizable='true'][currentset]"); |
michael@0 | 967 | for (let toolbar of toolbars) { |
michael@0 | 968 | if (aSetBeforePersisting) { |
michael@0 | 969 | let set = toolbar.currentSet; |
michael@0 | 970 | toolbar.setAttribute("currentset", set); |
michael@0 | 971 | } |
michael@0 | 972 | // Persist the currentset attribute directly on hardcoded toolbars. |
michael@0 | 973 | document.persist(toolbar.id, "currentset"); |
michael@0 | 974 | } |
michael@0 | 975 | }, |
michael@0 | 976 | |
michael@0 | 977 | reset: function() { |
michael@0 | 978 | this.resetting = true; |
michael@0 | 979 | // Disable the reset button temporarily while resetting: |
michael@0 | 980 | let btn = this.document.getElementById("customization-reset-button"); |
michael@0 | 981 | BrowserUITelemetry.countCustomizationEvent("reset"); |
michael@0 | 982 | btn.disabled = true; |
michael@0 | 983 | return Task.spawn(function() { |
michael@0 | 984 | this._removePanelCustomizationPlaceholders(); |
michael@0 | 985 | yield this.depopulatePalette(); |
michael@0 | 986 | yield this._unwrapToolbarItems(); |
michael@0 | 987 | |
michael@0 | 988 | CustomizableUI.reset(); |
michael@0 | 989 | |
michael@0 | 990 | yield this._wrapToolbarItems(); |
michael@0 | 991 | this.populatePalette(); |
michael@0 | 992 | |
michael@0 | 993 | this.persistCurrentSets(true); |
michael@0 | 994 | |
michael@0 | 995 | this._updateResetButton(); |
michael@0 | 996 | this._updateUndoResetButton(); |
michael@0 | 997 | this._updateEmptyPaletteNotice(); |
michael@0 | 998 | this._showPanelCustomizationPlaceholders(); |
michael@0 | 999 | this.resetting = false; |
michael@0 | 1000 | if (!this._wantToBeInCustomizeMode) { |
michael@0 | 1001 | this.exit(); |
michael@0 | 1002 | } |
michael@0 | 1003 | }.bind(this)).then(null, ERROR); |
michael@0 | 1004 | }, |
michael@0 | 1005 | |
michael@0 | 1006 | undoReset: function() { |
michael@0 | 1007 | this.resetting = true; |
michael@0 | 1008 | |
michael@0 | 1009 | return Task.spawn(function() { |
michael@0 | 1010 | this._removePanelCustomizationPlaceholders(); |
michael@0 | 1011 | yield this.depopulatePalette(); |
michael@0 | 1012 | yield this._unwrapToolbarItems(); |
michael@0 | 1013 | |
michael@0 | 1014 | CustomizableUI.undoReset(); |
michael@0 | 1015 | |
michael@0 | 1016 | yield this._wrapToolbarItems(); |
michael@0 | 1017 | this.populatePalette(); |
michael@0 | 1018 | |
michael@0 | 1019 | this.persistCurrentSets(true); |
michael@0 | 1020 | |
michael@0 | 1021 | this._updateResetButton(); |
michael@0 | 1022 | this._updateUndoResetButton(); |
michael@0 | 1023 | this._updateEmptyPaletteNotice(); |
michael@0 | 1024 | this.resetting = false; |
michael@0 | 1025 | }.bind(this)).then(null, ERROR); |
michael@0 | 1026 | }, |
michael@0 | 1027 | |
michael@0 | 1028 | _onToolbarVisibilityChange: function(aEvent) { |
michael@0 | 1029 | let toolbar = aEvent.target; |
michael@0 | 1030 | if (aEvent.detail.visible && toolbar.getAttribute("customizable") == "true") { |
michael@0 | 1031 | toolbar.setAttribute("customizing", "true"); |
michael@0 | 1032 | } else { |
michael@0 | 1033 | toolbar.removeAttribute("customizing"); |
michael@0 | 1034 | } |
michael@0 | 1035 | this._onUIChange(); |
michael@0 | 1036 | }, |
michael@0 | 1037 | |
michael@0 | 1038 | onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) { |
michael@0 | 1039 | this._onUIChange(); |
michael@0 | 1040 | }, |
michael@0 | 1041 | |
michael@0 | 1042 | onWidgetAdded: function(aWidgetId, aArea, aPosition) { |
michael@0 | 1043 | this._onUIChange(); |
michael@0 | 1044 | }, |
michael@0 | 1045 | |
michael@0 | 1046 | onWidgetRemoved: function(aWidgetId, aArea) { |
michael@0 | 1047 | this._onUIChange(); |
michael@0 | 1048 | }, |
michael@0 | 1049 | |
michael@0 | 1050 | onWidgetBeforeDOMChange: function(aNodeToChange, aSecondaryNode, aContainer) { |
michael@0 | 1051 | if (aContainer.ownerDocument.defaultView != this.window || this.resetting) { |
michael@0 | 1052 | return; |
michael@0 | 1053 | } |
michael@0 | 1054 | if (aContainer.id == CustomizableUI.AREA_PANEL) { |
michael@0 | 1055 | this._removePanelCustomizationPlaceholders(); |
michael@0 | 1056 | } |
michael@0 | 1057 | // If we get called for widgets that aren't in the window yet, they might not have |
michael@0 | 1058 | // a parentNode at all. |
michael@0 | 1059 | if (aNodeToChange.parentNode) { |
michael@0 | 1060 | this.unwrapToolbarItem(aNodeToChange.parentNode); |
michael@0 | 1061 | } |
michael@0 | 1062 | if (aSecondaryNode) { |
michael@0 | 1063 | this.unwrapToolbarItem(aSecondaryNode.parentNode); |
michael@0 | 1064 | } |
michael@0 | 1065 | }, |
michael@0 | 1066 | |
michael@0 | 1067 | onWidgetAfterDOMChange: function(aNodeToChange, aSecondaryNode, aContainer) { |
michael@0 | 1068 | if (aContainer.ownerDocument.defaultView != this.window || this.resetting) { |
michael@0 | 1069 | return; |
michael@0 | 1070 | } |
michael@0 | 1071 | // If the node is still attached to the container, wrap it again: |
michael@0 | 1072 | if (aNodeToChange.parentNode) { |
michael@0 | 1073 | let place = CustomizableUI.getPlaceForItem(aNodeToChange); |
michael@0 | 1074 | this.wrapToolbarItem(aNodeToChange, place); |
michael@0 | 1075 | if (aSecondaryNode) { |
michael@0 | 1076 | this.wrapToolbarItem(aSecondaryNode, place); |
michael@0 | 1077 | } |
michael@0 | 1078 | } else { |
michael@0 | 1079 | // If not, it got removed. |
michael@0 | 1080 | |
michael@0 | 1081 | // If an API-based widget is removed while customizing, append it to the palette. |
michael@0 | 1082 | // The _applyDrop code itself will take care of positioning it correctly, if |
michael@0 | 1083 | // applicable. We need the code to be here so removing widgets using CustomizableUI's |
michael@0 | 1084 | // API also does the right thing (and adds it to the palette) |
michael@0 | 1085 | let widgetId = aNodeToChange.id; |
michael@0 | 1086 | let widget = CustomizableUI.getWidget(widgetId); |
michael@0 | 1087 | if (widget.provider == CustomizableUI.PROVIDER_API) { |
michael@0 | 1088 | let paletteItem = this.makePaletteItem(widget, "palette"); |
michael@0 | 1089 | this.visiblePalette.appendChild(paletteItem); |
michael@0 | 1090 | } |
michael@0 | 1091 | } |
michael@0 | 1092 | if (aContainer.id == CustomizableUI.AREA_PANEL) { |
michael@0 | 1093 | this._showPanelCustomizationPlaceholders(); |
michael@0 | 1094 | } |
michael@0 | 1095 | }, |
michael@0 | 1096 | |
michael@0 | 1097 | onWidgetDestroyed: function(aWidgetId) { |
michael@0 | 1098 | let wrapper = this.document.getElementById("wrapper-" + aWidgetId); |
michael@0 | 1099 | if (wrapper) { |
michael@0 | 1100 | let wasInPanel = wrapper.parentNode == this.panelUIContents; |
michael@0 | 1101 | wrapper.remove(); |
michael@0 | 1102 | if (wasInPanel) { |
michael@0 | 1103 | this._showPanelCustomizationPlaceholders(); |
michael@0 | 1104 | } |
michael@0 | 1105 | } |
michael@0 | 1106 | }, |
michael@0 | 1107 | |
michael@0 | 1108 | onWidgetAfterCreation: function(aWidgetId, aArea) { |
michael@0 | 1109 | // If the node was added to an area, we would have gotten an onWidgetAdded notification, |
michael@0 | 1110 | // plus associated DOM change notifications, so only do stuff for the palette: |
michael@0 | 1111 | if (!aArea) { |
michael@0 | 1112 | let widgetNode = this.document.getElementById(aWidgetId); |
michael@0 | 1113 | if (widgetNode) { |
michael@0 | 1114 | this.wrapToolbarItem(widgetNode, "palette"); |
michael@0 | 1115 | } else { |
michael@0 | 1116 | let widget = CustomizableUI.getWidget(aWidgetId); |
michael@0 | 1117 | this.visiblePalette.appendChild(this.makePaletteItem(widget, "palette")); |
michael@0 | 1118 | } |
michael@0 | 1119 | } |
michael@0 | 1120 | }, |
michael@0 | 1121 | |
michael@0 | 1122 | onAreaNodeRegistered: function(aArea, aContainer) { |
michael@0 | 1123 | if (aContainer.ownerDocument == this.document) { |
michael@0 | 1124 | this._wrapItemsInArea(aContainer); |
michael@0 | 1125 | this._addDragHandlers(aContainer); |
michael@0 | 1126 | DragPositionManager.add(this.window, aArea, aContainer); |
michael@0 | 1127 | this.areas.push(aContainer); |
michael@0 | 1128 | } |
michael@0 | 1129 | }, |
michael@0 | 1130 | |
michael@0 | 1131 | onAreaNodeUnregistered: function(aArea, aContainer, aReason) { |
michael@0 | 1132 | if (aContainer.ownerDocument == this.document && aReason == CustomizableUI.REASON_AREA_UNREGISTERED) { |
michael@0 | 1133 | this._unwrapItemsInArea(aContainer); |
michael@0 | 1134 | this._removeDragHandlers(aContainer); |
michael@0 | 1135 | DragPositionManager.remove(this.window, aArea, aContainer); |
michael@0 | 1136 | let index = this.areas.indexOf(aContainer); |
michael@0 | 1137 | this.areas.splice(index, 1); |
michael@0 | 1138 | } |
michael@0 | 1139 | }, |
michael@0 | 1140 | |
michael@0 | 1141 | _onUIChange: function() { |
michael@0 | 1142 | this._changed = true; |
michael@0 | 1143 | if (!this.resetting) { |
michael@0 | 1144 | this._updateResetButton(); |
michael@0 | 1145 | this._updateUndoResetButton(); |
michael@0 | 1146 | this._updateEmptyPaletteNotice(); |
michael@0 | 1147 | } |
michael@0 | 1148 | CustomizableUI.dispatchToolboxEvent("customizationchange"); |
michael@0 | 1149 | }, |
michael@0 | 1150 | |
michael@0 | 1151 | _updateEmptyPaletteNotice: function() { |
michael@0 | 1152 | let paletteItems = this.visiblePalette.getElementsByTagName("toolbarpaletteitem"); |
michael@0 | 1153 | this.paletteEmptyNotice.hidden = !!paletteItems.length; |
michael@0 | 1154 | }, |
michael@0 | 1155 | |
michael@0 | 1156 | _updateResetButton: function() { |
michael@0 | 1157 | let btn = this.document.getElementById("customization-reset-button"); |
michael@0 | 1158 | btn.disabled = CustomizableUI.inDefaultState; |
michael@0 | 1159 | }, |
michael@0 | 1160 | |
michael@0 | 1161 | _updateUndoResetButton: function() { |
michael@0 | 1162 | let undoResetButton = this.document.getElementById("customization-undo-reset-button"); |
michael@0 | 1163 | undoResetButton.hidden = !CustomizableUI.canUndoReset; |
michael@0 | 1164 | }, |
michael@0 | 1165 | |
michael@0 | 1166 | handleEvent: function(aEvent) { |
michael@0 | 1167 | switch(aEvent.type) { |
michael@0 | 1168 | case "toolbarvisibilitychange": |
michael@0 | 1169 | this._onToolbarVisibilityChange(aEvent); |
michael@0 | 1170 | break; |
michael@0 | 1171 | case "dragstart": |
michael@0 | 1172 | this._onDragStart(aEvent); |
michael@0 | 1173 | break; |
michael@0 | 1174 | case "dragover": |
michael@0 | 1175 | this._onDragOver(aEvent); |
michael@0 | 1176 | break; |
michael@0 | 1177 | case "drop": |
michael@0 | 1178 | this._onDragDrop(aEvent); |
michael@0 | 1179 | break; |
michael@0 | 1180 | case "dragexit": |
michael@0 | 1181 | this._onDragExit(aEvent); |
michael@0 | 1182 | break; |
michael@0 | 1183 | case "dragend": |
michael@0 | 1184 | this._onDragEnd(aEvent); |
michael@0 | 1185 | break; |
michael@0 | 1186 | case "command": |
michael@0 | 1187 | if (aEvent.originalTarget == this.window.PanelUI.menuButton) { |
michael@0 | 1188 | this.exit(); |
michael@0 | 1189 | aEvent.preventDefault(); |
michael@0 | 1190 | } |
michael@0 | 1191 | break; |
michael@0 | 1192 | case "mousedown": |
michael@0 | 1193 | this._onMouseDown(aEvent); |
michael@0 | 1194 | break; |
michael@0 | 1195 | case "mouseup": |
michael@0 | 1196 | this._onMouseUp(aEvent); |
michael@0 | 1197 | break; |
michael@0 | 1198 | case "keypress": |
michael@0 | 1199 | if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) { |
michael@0 | 1200 | this.exit(); |
michael@0 | 1201 | } |
michael@0 | 1202 | break; |
michael@0 | 1203 | #ifdef CAN_DRAW_IN_TITLEBAR |
michael@0 | 1204 | case "unload": |
michael@0 | 1205 | this.uninit(); |
michael@0 | 1206 | break; |
michael@0 | 1207 | #endif |
michael@0 | 1208 | } |
michael@0 | 1209 | }, |
michael@0 | 1210 | |
michael@0 | 1211 | #ifdef CAN_DRAW_IN_TITLEBAR |
michael@0 | 1212 | observe: function(aSubject, aTopic, aData) { |
michael@0 | 1213 | switch (aTopic) { |
michael@0 | 1214 | case "nsPref:changed": |
michael@0 | 1215 | this._updateResetButton(); |
michael@0 | 1216 | this._updateTitlebarButton(); |
michael@0 | 1217 | this._updateUndoResetButton(); |
michael@0 | 1218 | break; |
michael@0 | 1219 | } |
michael@0 | 1220 | }, |
michael@0 | 1221 | |
michael@0 | 1222 | _updateTitlebarButton: function() { |
michael@0 | 1223 | let drawInTitlebar = true; |
michael@0 | 1224 | try { |
michael@0 | 1225 | drawInTitlebar = Services.prefs.getBoolPref(kDrawInTitlebarPref); |
michael@0 | 1226 | } catch (ex) { } |
michael@0 | 1227 | let button = this.document.getElementById("customization-titlebar-visibility-button"); |
michael@0 | 1228 | // Drawing in the titlebar means 'hiding' the titlebar: |
michael@0 | 1229 | if (drawInTitlebar) { |
michael@0 | 1230 | button.removeAttribute("checked"); |
michael@0 | 1231 | } else { |
michael@0 | 1232 | button.setAttribute("checked", "true"); |
michael@0 | 1233 | } |
michael@0 | 1234 | }, |
michael@0 | 1235 | |
michael@0 | 1236 | toggleTitlebar: function(aShouldShowTitlebar) { |
michael@0 | 1237 | // Drawing in the titlebar means not showing the titlebar, hence the negation: |
michael@0 | 1238 | Services.prefs.setBoolPref(kDrawInTitlebarPref, !aShouldShowTitlebar); |
michael@0 | 1239 | }, |
michael@0 | 1240 | #endif |
michael@0 | 1241 | |
michael@0 | 1242 | _onDragStart: function(aEvent) { |
michael@0 | 1243 | __dumpDragData(aEvent); |
michael@0 | 1244 | let item = aEvent.target; |
michael@0 | 1245 | while (item && item.localName != "toolbarpaletteitem") { |
michael@0 | 1246 | if (item.localName == "toolbar") { |
michael@0 | 1247 | return; |
michael@0 | 1248 | } |
michael@0 | 1249 | item = item.parentNode; |
michael@0 | 1250 | } |
michael@0 | 1251 | |
michael@0 | 1252 | let draggedItem = item.firstChild; |
michael@0 | 1253 | let placeForItem = CustomizableUI.getPlaceForItem(item); |
michael@0 | 1254 | let isRemovable = placeForItem == "palette" || |
michael@0 | 1255 | CustomizableUI.isWidgetRemovable(draggedItem); |
michael@0 | 1256 | if (item.classList.contains(kPlaceholderClass) || !isRemovable) { |
michael@0 | 1257 | return; |
michael@0 | 1258 | } |
michael@0 | 1259 | |
michael@0 | 1260 | let dt = aEvent.dataTransfer; |
michael@0 | 1261 | let documentId = aEvent.target.ownerDocument.documentElement.id; |
michael@0 | 1262 | let isInToolbar = placeForItem == "toolbar"; |
michael@0 | 1263 | |
michael@0 | 1264 | dt.mozSetDataAt(kDragDataTypePrefix + documentId, draggedItem.id, 0); |
michael@0 | 1265 | dt.effectAllowed = "move"; |
michael@0 | 1266 | |
michael@0 | 1267 | let itemRect = draggedItem.getBoundingClientRect(); |
michael@0 | 1268 | let itemCenter = {x: itemRect.left + itemRect.width / 2, |
michael@0 | 1269 | y: itemRect.top + itemRect.height / 2}; |
michael@0 | 1270 | this._dragOffset = {x: aEvent.clientX - itemCenter.x, |
michael@0 | 1271 | y: aEvent.clientY - itemCenter.y}; |
michael@0 | 1272 | |
michael@0 | 1273 | gDraggingInToolbars = new Set(); |
michael@0 | 1274 | |
michael@0 | 1275 | // Hack needed so that the dragimage will still show the |
michael@0 | 1276 | // item as it appeared before it was hidden. |
michael@0 | 1277 | this._initializeDragAfterMove = function() { |
michael@0 | 1278 | // For automated tests, we sometimes start exiting customization mode |
michael@0 | 1279 | // before this fires, which leaves us with placeholders inserted after |
michael@0 | 1280 | // we've exited. So we need to check that we are indeed customizing. |
michael@0 | 1281 | if (this._customizing && !this._transitioning) { |
michael@0 | 1282 | item.hidden = true; |
michael@0 | 1283 | this._showPanelCustomizationPlaceholders(); |
michael@0 | 1284 | DragPositionManager.start(this.window); |
michael@0 | 1285 | if (item.nextSibling) { |
michael@0 | 1286 | this._setDragActive(item.nextSibling, "before", draggedItem.id, isInToolbar); |
michael@0 | 1287 | this._dragOverItem = item.nextSibling; |
michael@0 | 1288 | } else if (isInToolbar && item.previousSibling) { |
michael@0 | 1289 | this._setDragActive(item.previousSibling, "after", draggedItem.id, isInToolbar); |
michael@0 | 1290 | this._dragOverItem = item.previousSibling; |
michael@0 | 1291 | } |
michael@0 | 1292 | } |
michael@0 | 1293 | this._initializeDragAfterMove = null; |
michael@0 | 1294 | this.window.clearTimeout(this._dragInitializeTimeout); |
michael@0 | 1295 | }.bind(this); |
michael@0 | 1296 | this._dragInitializeTimeout = this.window.setTimeout(this._initializeDragAfterMove, 0); |
michael@0 | 1297 | }, |
michael@0 | 1298 | |
michael@0 | 1299 | _onDragOver: function(aEvent) { |
michael@0 | 1300 | if (this._isUnwantedDragDrop(aEvent)) { |
michael@0 | 1301 | return; |
michael@0 | 1302 | } |
michael@0 | 1303 | if (this._initializeDragAfterMove) { |
michael@0 | 1304 | this._initializeDragAfterMove(); |
michael@0 | 1305 | } |
michael@0 | 1306 | |
michael@0 | 1307 | __dumpDragData(aEvent); |
michael@0 | 1308 | |
michael@0 | 1309 | let document = aEvent.target.ownerDocument; |
michael@0 | 1310 | let documentId = document.documentElement.id; |
michael@0 | 1311 | if (!aEvent.dataTransfer.mozTypesAt(0)) { |
michael@0 | 1312 | return; |
michael@0 | 1313 | } |
michael@0 | 1314 | |
michael@0 | 1315 | let draggedItemId = |
michael@0 | 1316 | aEvent.dataTransfer.mozGetDataAt(kDragDataTypePrefix + documentId, 0); |
michael@0 | 1317 | let draggedWrapper = document.getElementById("wrapper-" + draggedItemId); |
michael@0 | 1318 | let targetArea = this._getCustomizableParent(aEvent.currentTarget); |
michael@0 | 1319 | let originArea = this._getCustomizableParent(draggedWrapper); |
michael@0 | 1320 | |
michael@0 | 1321 | // Do nothing if the target or origin are not customizable. |
michael@0 | 1322 | if (!targetArea || !originArea) { |
michael@0 | 1323 | return; |
michael@0 | 1324 | } |
michael@0 | 1325 | |
michael@0 | 1326 | // Do nothing if the widget is not allowed to be removed. |
michael@0 | 1327 | if (targetArea.id == kPaletteId && |
michael@0 | 1328 | !CustomizableUI.isWidgetRemovable(draggedItemId)) { |
michael@0 | 1329 | return; |
michael@0 | 1330 | } |
michael@0 | 1331 | |
michael@0 | 1332 | // Do nothing if the widget is not allowed to move to the target area. |
michael@0 | 1333 | if (targetArea.id != kPaletteId && |
michael@0 | 1334 | !CustomizableUI.canWidgetMoveToArea(draggedItemId, targetArea.id)) { |
michael@0 | 1335 | return; |
michael@0 | 1336 | } |
michael@0 | 1337 | |
michael@0 | 1338 | let targetIsToolbar = CustomizableUI.getAreaType(targetArea.id) == "toolbar"; |
michael@0 | 1339 | let targetNode = this._getDragOverNode(aEvent, targetArea, targetIsToolbar, draggedItemId); |
michael@0 | 1340 | |
michael@0 | 1341 | // We need to determine the place that the widget is being dropped in |
michael@0 | 1342 | // the target. |
michael@0 | 1343 | let dragOverItem, dragValue; |
michael@0 | 1344 | if (targetNode == targetArea.customizationTarget) { |
michael@0 | 1345 | // We'll assume if the user is dragging directly over the target, that |
michael@0 | 1346 | // they're attempting to append a child to that target. |
michael@0 | 1347 | dragOverItem = (targetIsToolbar ? this._findVisiblePreviousSiblingNode(targetNode.lastChild) : |
michael@0 | 1348 | targetNode.lastChild) || targetNode; |
michael@0 | 1349 | dragValue = "after"; |
michael@0 | 1350 | } else { |
michael@0 | 1351 | let targetParent = targetNode.parentNode; |
michael@0 | 1352 | let position = Array.indexOf(targetParent.children, targetNode); |
michael@0 | 1353 | if (position == -1) { |
michael@0 | 1354 | dragOverItem = targetIsToolbar ? this._findVisiblePreviousSiblingNode(targetNode.lastChild) : |
michael@0 | 1355 | targetParent.lastChild; |
michael@0 | 1356 | dragValue = "after"; |
michael@0 | 1357 | } else { |
michael@0 | 1358 | dragOverItem = targetParent.children[position]; |
michael@0 | 1359 | if (!targetIsToolbar) { |
michael@0 | 1360 | dragValue = "before"; |
michael@0 | 1361 | } else { |
michael@0 | 1362 | dragOverItem = this._findVisiblePreviousSiblingNode(targetParent.children[position]); |
michael@0 | 1363 | // Check if the aDraggedItem is hovered past the first half of dragOverItem |
michael@0 | 1364 | let window = dragOverItem.ownerDocument.defaultView; |
michael@0 | 1365 | let direction = window.getComputedStyle(dragOverItem, null).direction; |
michael@0 | 1366 | let itemRect = dragOverItem.getBoundingClientRect(); |
michael@0 | 1367 | let dropTargetCenter = itemRect.left + (itemRect.width / 2); |
michael@0 | 1368 | let existingDir = dragOverItem.getAttribute("dragover"); |
michael@0 | 1369 | if ((existingDir == "before") == (direction == "ltr")) { |
michael@0 | 1370 | dropTargetCenter += (parseInt(dragOverItem.style.borderLeftWidth) || 0) / 2; |
michael@0 | 1371 | } else { |
michael@0 | 1372 | dropTargetCenter -= (parseInt(dragOverItem.style.borderRightWidth) || 0) / 2; |
michael@0 | 1373 | } |
michael@0 | 1374 | let before = direction == "ltr" ? aEvent.clientX < dropTargetCenter : aEvent.clientX > dropTargetCenter; |
michael@0 | 1375 | dragValue = before ? "before" : "after"; |
michael@0 | 1376 | } |
michael@0 | 1377 | } |
michael@0 | 1378 | } |
michael@0 | 1379 | |
michael@0 | 1380 | if (this._dragOverItem && dragOverItem != this._dragOverItem) { |
michael@0 | 1381 | this._cancelDragActive(this._dragOverItem, dragOverItem); |
michael@0 | 1382 | } |
michael@0 | 1383 | |
michael@0 | 1384 | if (dragOverItem != this._dragOverItem || dragValue != dragOverItem.getAttribute("dragover")) { |
michael@0 | 1385 | if (dragOverItem != targetArea.customizationTarget) { |
michael@0 | 1386 | this._setDragActive(dragOverItem, dragValue, draggedItemId, targetIsToolbar); |
michael@0 | 1387 | } else if (targetIsToolbar) { |
michael@0 | 1388 | this._updateToolbarCustomizationOutline(this.window, targetArea); |
michael@0 | 1389 | } |
michael@0 | 1390 | this._dragOverItem = dragOverItem; |
michael@0 | 1391 | } |
michael@0 | 1392 | |
michael@0 | 1393 | aEvent.preventDefault(); |
michael@0 | 1394 | aEvent.stopPropagation(); |
michael@0 | 1395 | }, |
michael@0 | 1396 | |
michael@0 | 1397 | _onDragDrop: function(aEvent) { |
michael@0 | 1398 | if (this._isUnwantedDragDrop(aEvent)) { |
michael@0 | 1399 | return; |
michael@0 | 1400 | } |
michael@0 | 1401 | |
michael@0 | 1402 | __dumpDragData(aEvent); |
michael@0 | 1403 | this._initializeDragAfterMove = null; |
michael@0 | 1404 | this.window.clearTimeout(this._dragInitializeTimeout); |
michael@0 | 1405 | |
michael@0 | 1406 | let targetArea = this._getCustomizableParent(aEvent.currentTarget); |
michael@0 | 1407 | let document = aEvent.target.ownerDocument; |
michael@0 | 1408 | let documentId = document.documentElement.id; |
michael@0 | 1409 | let draggedItemId = |
michael@0 | 1410 | aEvent.dataTransfer.mozGetDataAt(kDragDataTypePrefix + documentId, 0); |
michael@0 | 1411 | let draggedWrapper = document.getElementById("wrapper-" + draggedItemId); |
michael@0 | 1412 | let originArea = this._getCustomizableParent(draggedWrapper); |
michael@0 | 1413 | if (this._dragSizeMap) { |
michael@0 | 1414 | this._dragSizeMap.clear(); |
michael@0 | 1415 | } |
michael@0 | 1416 | // Do nothing if the target area or origin area are not customizable. |
michael@0 | 1417 | if (!targetArea || !originArea) { |
michael@0 | 1418 | return; |
michael@0 | 1419 | } |
michael@0 | 1420 | let targetNode = this._dragOverItem; |
michael@0 | 1421 | let dropDir = targetNode.getAttribute("dragover"); |
michael@0 | 1422 | // Need to insert *after* this node if we promised the user that: |
michael@0 | 1423 | if (targetNode != targetArea && dropDir == "after") { |
michael@0 | 1424 | if (targetNode.nextSibling) { |
michael@0 | 1425 | targetNode = targetNode.nextSibling; |
michael@0 | 1426 | } else { |
michael@0 | 1427 | targetNode = targetArea; |
michael@0 | 1428 | } |
michael@0 | 1429 | } |
michael@0 | 1430 | // If the target node is a placeholder, get its sibling as the real target. |
michael@0 | 1431 | while (targetNode.classList.contains(kPlaceholderClass) && targetNode.nextSibling) { |
michael@0 | 1432 | targetNode = targetNode.nextSibling; |
michael@0 | 1433 | } |
michael@0 | 1434 | if (targetNode.tagName == "toolbarpaletteitem") { |
michael@0 | 1435 | targetNode = targetNode.firstChild; |
michael@0 | 1436 | } |
michael@0 | 1437 | |
michael@0 | 1438 | this._cancelDragActive(this._dragOverItem, null, true); |
michael@0 | 1439 | this._removePanelCustomizationPlaceholders(); |
michael@0 | 1440 | |
michael@0 | 1441 | try { |
michael@0 | 1442 | this._applyDrop(aEvent, targetArea, originArea, draggedItemId, targetNode); |
michael@0 | 1443 | } catch (ex) { |
michael@0 | 1444 | ERROR(ex, ex.stack); |
michael@0 | 1445 | } |
michael@0 | 1446 | |
michael@0 | 1447 | this._showPanelCustomizationPlaceholders(); |
michael@0 | 1448 | }, |
michael@0 | 1449 | |
michael@0 | 1450 | _applyDrop: function(aEvent, aTargetArea, aOriginArea, aDraggedItemId, aTargetNode) { |
michael@0 | 1451 | let document = aEvent.target.ownerDocument; |
michael@0 | 1452 | let draggedItem = document.getElementById(aDraggedItemId); |
michael@0 | 1453 | draggedItem.hidden = false; |
michael@0 | 1454 | draggedItem.removeAttribute("mousedown"); |
michael@0 | 1455 | |
michael@0 | 1456 | // Do nothing if the target was dropped onto itself (ie, no change in area |
michael@0 | 1457 | // or position). |
michael@0 | 1458 | if (draggedItem == aTargetNode) { |
michael@0 | 1459 | return; |
michael@0 | 1460 | } |
michael@0 | 1461 | |
michael@0 | 1462 | // Is the target area the customization palette? |
michael@0 | 1463 | if (aTargetArea.id == kPaletteId) { |
michael@0 | 1464 | // Did we drag from outside the palette? |
michael@0 | 1465 | if (aOriginArea.id !== kPaletteId) { |
michael@0 | 1466 | if (!CustomizableUI.isWidgetRemovable(aDraggedItemId)) { |
michael@0 | 1467 | return; |
michael@0 | 1468 | } |
michael@0 | 1469 | |
michael@0 | 1470 | CustomizableUI.removeWidgetFromArea(aDraggedItemId); |
michael@0 | 1471 | BrowserUITelemetry.countCustomizationEvent("remove"); |
michael@0 | 1472 | // Special widgets are removed outright, we can return here: |
michael@0 | 1473 | if (CustomizableUI.isSpecialWidget(aDraggedItemId)) { |
michael@0 | 1474 | return; |
michael@0 | 1475 | } |
michael@0 | 1476 | } |
michael@0 | 1477 | draggedItem = draggedItem.parentNode; |
michael@0 | 1478 | |
michael@0 | 1479 | // If the target node is the palette itself, just append |
michael@0 | 1480 | if (aTargetNode == this.visiblePalette) { |
michael@0 | 1481 | this.visiblePalette.appendChild(draggedItem); |
michael@0 | 1482 | } else { |
michael@0 | 1483 | // The items in the palette are wrapped, so we need the target node's parent here: |
michael@0 | 1484 | this.visiblePalette.insertBefore(draggedItem, aTargetNode.parentNode); |
michael@0 | 1485 | } |
michael@0 | 1486 | if (aOriginArea.id !== kPaletteId) { |
michael@0 | 1487 | // The dragend event already fires when the item moves within the palette. |
michael@0 | 1488 | this._onDragEnd(aEvent); |
michael@0 | 1489 | } |
michael@0 | 1490 | return; |
michael@0 | 1491 | } |
michael@0 | 1492 | |
michael@0 | 1493 | if (!CustomizableUI.canWidgetMoveToArea(aDraggedItemId, aTargetArea.id)) { |
michael@0 | 1494 | return; |
michael@0 | 1495 | } |
michael@0 | 1496 | |
michael@0 | 1497 | // Skipintoolbarset items won't really be moved: |
michael@0 | 1498 | if (draggedItem.getAttribute("skipintoolbarset") == "true") { |
michael@0 | 1499 | // These items should never leave their area: |
michael@0 | 1500 | if (aTargetArea != aOriginArea) { |
michael@0 | 1501 | return; |
michael@0 | 1502 | } |
michael@0 | 1503 | let place = draggedItem.parentNode.getAttribute("place"); |
michael@0 | 1504 | this.unwrapToolbarItem(draggedItem.parentNode); |
michael@0 | 1505 | if (aTargetNode == aTargetArea.customizationTarget) { |
michael@0 | 1506 | aTargetArea.customizationTarget.appendChild(draggedItem); |
michael@0 | 1507 | } else { |
michael@0 | 1508 | this.unwrapToolbarItem(aTargetNode.parentNode); |
michael@0 | 1509 | aTargetArea.customizationTarget.insertBefore(draggedItem, aTargetNode); |
michael@0 | 1510 | this.wrapToolbarItem(aTargetNode, place); |
michael@0 | 1511 | } |
michael@0 | 1512 | this.wrapToolbarItem(draggedItem, place); |
michael@0 | 1513 | BrowserUITelemetry.countCustomizationEvent("move"); |
michael@0 | 1514 | return; |
michael@0 | 1515 | } |
michael@0 | 1516 | |
michael@0 | 1517 | // Is the target the customization area itself? If so, we just add the |
michael@0 | 1518 | // widget to the end of the area. |
michael@0 | 1519 | if (aTargetNode == aTargetArea.customizationTarget) { |
michael@0 | 1520 | CustomizableUI.addWidgetToArea(aDraggedItemId, aTargetArea.id); |
michael@0 | 1521 | // For the purposes of BrowserUITelemetry, we consider both moving a widget |
michael@0 | 1522 | // within the same area, and adding a widget from one area to another area |
michael@0 | 1523 | // as a "move". An "add" is only when we move an item from the palette into |
michael@0 | 1524 | // an area. |
michael@0 | 1525 | let custEventType = aOriginArea.id == kPaletteId ? "add" : "move"; |
michael@0 | 1526 | BrowserUITelemetry.countCustomizationEvent(custEventType); |
michael@0 | 1527 | this._onDragEnd(aEvent); |
michael@0 | 1528 | return; |
michael@0 | 1529 | } |
michael@0 | 1530 | |
michael@0 | 1531 | // We need to determine the place that the widget is being dropped in |
michael@0 | 1532 | // the target. |
michael@0 | 1533 | let placement; |
michael@0 | 1534 | let itemForPlacement = aTargetNode; |
michael@0 | 1535 | // Skip the skipintoolbarset items when determining the place of the item: |
michael@0 | 1536 | while (itemForPlacement && itemForPlacement.getAttribute("skipintoolbarset") == "true" && |
michael@0 | 1537 | itemForPlacement.parentNode && |
michael@0 | 1538 | itemForPlacement.parentNode.nodeName == "toolbarpaletteitem") { |
michael@0 | 1539 | itemForPlacement = itemForPlacement.parentNode.nextSibling; |
michael@0 | 1540 | if (itemForPlacement && itemForPlacement.nodeName == "toolbarpaletteitem") { |
michael@0 | 1541 | itemForPlacement = itemForPlacement.firstChild; |
michael@0 | 1542 | } |
michael@0 | 1543 | } |
michael@0 | 1544 | if (itemForPlacement && !itemForPlacement.classList.contains(kPlaceholderClass)) { |
michael@0 | 1545 | let targetNodeId = (itemForPlacement.nodeName == "toolbarpaletteitem") ? |
michael@0 | 1546 | itemForPlacement.firstChild && itemForPlacement.firstChild.id : |
michael@0 | 1547 | itemForPlacement.id; |
michael@0 | 1548 | placement = CustomizableUI.getPlacementOfWidget(targetNodeId); |
michael@0 | 1549 | } |
michael@0 | 1550 | if (!placement) { |
michael@0 | 1551 | LOG("Could not get a position for " + aTargetNode.nodeName + "#" + aTargetNode.id + "." + aTargetNode.className); |
michael@0 | 1552 | } |
michael@0 | 1553 | let position = placement ? placement.position : null; |
michael@0 | 1554 | |
michael@0 | 1555 | // Is the target area the same as the origin? Since we've already handled |
michael@0 | 1556 | // the possibility that the target is the customization palette, we know |
michael@0 | 1557 | // that the widget is moving within a customizable area. |
michael@0 | 1558 | if (aTargetArea == aOriginArea) { |
michael@0 | 1559 | CustomizableUI.moveWidgetWithinArea(aDraggedItemId, position); |
michael@0 | 1560 | } else { |
michael@0 | 1561 | CustomizableUI.addWidgetToArea(aDraggedItemId, aTargetArea.id, position); |
michael@0 | 1562 | } |
michael@0 | 1563 | |
michael@0 | 1564 | this._onDragEnd(aEvent); |
michael@0 | 1565 | |
michael@0 | 1566 | // For BrowserUITelemetry, an "add" is only when we move an item from the palette |
michael@0 | 1567 | // into an area. Otherwise, it's a move. |
michael@0 | 1568 | let custEventType = aOriginArea.id == kPaletteId ? "add" : "move"; |
michael@0 | 1569 | BrowserUITelemetry.countCustomizationEvent(custEventType); |
michael@0 | 1570 | |
michael@0 | 1571 | // If we dropped onto a skipintoolbarset item, manually correct the drop location: |
michael@0 | 1572 | if (aTargetNode != itemForPlacement) { |
michael@0 | 1573 | let draggedWrapper = draggedItem.parentNode; |
michael@0 | 1574 | let container = draggedWrapper.parentNode; |
michael@0 | 1575 | container.insertBefore(draggedWrapper, aTargetNode.parentNode); |
michael@0 | 1576 | } |
michael@0 | 1577 | }, |
michael@0 | 1578 | |
michael@0 | 1579 | _onDragExit: function(aEvent) { |
michael@0 | 1580 | if (this._isUnwantedDragDrop(aEvent)) { |
michael@0 | 1581 | return; |
michael@0 | 1582 | } |
michael@0 | 1583 | |
michael@0 | 1584 | __dumpDragData(aEvent); |
michael@0 | 1585 | |
michael@0 | 1586 | // When leaving customization areas, cancel the drag on the last dragover item |
michael@0 | 1587 | // We've attached the listener to areas, so aEvent.currentTarget will be the area. |
michael@0 | 1588 | // We don't care about dragexit events fired on descendants of the area, |
michael@0 | 1589 | // so we check that the event's target is the same as the area to which the listener |
michael@0 | 1590 | // was attached. |
michael@0 | 1591 | if (this._dragOverItem && aEvent.target == aEvent.currentTarget) { |
michael@0 | 1592 | this._cancelDragActive(this._dragOverItem); |
michael@0 | 1593 | this._dragOverItem = null; |
michael@0 | 1594 | } |
michael@0 | 1595 | }, |
michael@0 | 1596 | |
michael@0 | 1597 | /** |
michael@0 | 1598 | * To workaround bug 460801 we manually forward the drop event here when dragend wouldn't be fired. |
michael@0 | 1599 | */ |
michael@0 | 1600 | _onDragEnd: function(aEvent) { |
michael@0 | 1601 | if (this._isUnwantedDragDrop(aEvent)) { |
michael@0 | 1602 | return; |
michael@0 | 1603 | } |
michael@0 | 1604 | this._initializeDragAfterMove = null; |
michael@0 | 1605 | this.window.clearTimeout(this._dragInitializeTimeout); |
michael@0 | 1606 | __dumpDragData(aEvent, "_onDragEnd"); |
michael@0 | 1607 | |
michael@0 | 1608 | let document = aEvent.target.ownerDocument; |
michael@0 | 1609 | document.documentElement.removeAttribute("customizing-movingItem"); |
michael@0 | 1610 | |
michael@0 | 1611 | let documentId = document.documentElement.id; |
michael@0 | 1612 | if (!aEvent.dataTransfer.mozTypesAt(0)) { |
michael@0 | 1613 | return; |
michael@0 | 1614 | } |
michael@0 | 1615 | |
michael@0 | 1616 | let draggedItemId = |
michael@0 | 1617 | aEvent.dataTransfer.mozGetDataAt(kDragDataTypePrefix + documentId, 0); |
michael@0 | 1618 | |
michael@0 | 1619 | let draggedWrapper = document.getElementById("wrapper-" + draggedItemId); |
michael@0 | 1620 | draggedWrapper.hidden = false; |
michael@0 | 1621 | draggedWrapper.removeAttribute("mousedown"); |
michael@0 | 1622 | if (this._dragOverItem) { |
michael@0 | 1623 | this._cancelDragActive(this._dragOverItem); |
michael@0 | 1624 | this._dragOverItem = null; |
michael@0 | 1625 | } |
michael@0 | 1626 | this._updateToolbarCustomizationOutline(this.window); |
michael@0 | 1627 | this._showPanelCustomizationPlaceholders(); |
michael@0 | 1628 | DragPositionManager.stop(); |
michael@0 | 1629 | }, |
michael@0 | 1630 | |
michael@0 | 1631 | _isUnwantedDragDrop: function(aEvent) { |
michael@0 | 1632 | // The simulated events generated by synthesizeDragStart/synthesizeDrop in |
michael@0 | 1633 | // mochitests are used only for testing whether the right data is being put |
michael@0 | 1634 | // into the dataTransfer. Neither cause a real drop to occur, so they don't |
michael@0 | 1635 | // set the source node. There isn't a means of testing real drag and drops, |
michael@0 | 1636 | // so this pref skips the check but it should only be set by test code. |
michael@0 | 1637 | if (this._skipSourceNodeCheck) { |
michael@0 | 1638 | return false; |
michael@0 | 1639 | } |
michael@0 | 1640 | |
michael@0 | 1641 | /* Discard drag events that originated from a separate window to |
michael@0 | 1642 | prevent content->chrome privilege escalations. */ |
michael@0 | 1643 | let mozSourceNode = aEvent.dataTransfer.mozSourceNode; |
michael@0 | 1644 | // mozSourceNode is null in the dragStart event handler or if |
michael@0 | 1645 | // the drag event originated in an external application. |
michael@0 | 1646 | return !mozSourceNode || |
michael@0 | 1647 | mozSourceNode.ownerDocument.defaultView != this.window; |
michael@0 | 1648 | }, |
michael@0 | 1649 | |
michael@0 | 1650 | _setDragActive: function(aItem, aValue, aDraggedItemId, aInToolbar) { |
michael@0 | 1651 | if (!aItem) { |
michael@0 | 1652 | return; |
michael@0 | 1653 | } |
michael@0 | 1654 | |
michael@0 | 1655 | if (aItem.getAttribute("dragover") != aValue) { |
michael@0 | 1656 | aItem.setAttribute("dragover", aValue); |
michael@0 | 1657 | |
michael@0 | 1658 | let window = aItem.ownerDocument.defaultView; |
michael@0 | 1659 | let draggedItem = window.document.getElementById(aDraggedItemId); |
michael@0 | 1660 | if (!aInToolbar) { |
michael@0 | 1661 | this._setGridDragActive(aItem, draggedItem, aValue); |
michael@0 | 1662 | } else { |
michael@0 | 1663 | let targetArea = this._getCustomizableParent(aItem); |
michael@0 | 1664 | this._updateToolbarCustomizationOutline(window, targetArea); |
michael@0 | 1665 | let makeSpaceImmediately = false; |
michael@0 | 1666 | if (!gDraggingInToolbars.has(targetArea.id)) { |
michael@0 | 1667 | gDraggingInToolbars.add(targetArea.id); |
michael@0 | 1668 | let draggedWrapper = this.document.getElementById("wrapper-" + aDraggedItemId); |
michael@0 | 1669 | let originArea = this._getCustomizableParent(draggedWrapper); |
michael@0 | 1670 | makeSpaceImmediately = originArea == targetArea; |
michael@0 | 1671 | } |
michael@0 | 1672 | // Calculate width of the item when it'd be dropped in this position |
michael@0 | 1673 | let width = this._getDragItemSize(aItem, draggedItem).width; |
michael@0 | 1674 | let direction = window.getComputedStyle(aItem).direction; |
michael@0 | 1675 | let prop, otherProp; |
michael@0 | 1676 | // If we're inserting before in ltr, or after in rtl: |
michael@0 | 1677 | if ((aValue == "before") == (direction == "ltr")) { |
michael@0 | 1678 | prop = "borderLeftWidth"; |
michael@0 | 1679 | otherProp = "border-right-width"; |
michael@0 | 1680 | } else { |
michael@0 | 1681 | // otherwise: |
michael@0 | 1682 | prop = "borderRightWidth"; |
michael@0 | 1683 | otherProp = "border-left-width"; |
michael@0 | 1684 | } |
michael@0 | 1685 | if (makeSpaceImmediately) { |
michael@0 | 1686 | aItem.setAttribute("notransition", "true"); |
michael@0 | 1687 | } |
michael@0 | 1688 | aItem.style[prop] = width + 'px'; |
michael@0 | 1689 | aItem.style.removeProperty(otherProp); |
michael@0 | 1690 | if (makeSpaceImmediately) { |
michael@0 | 1691 | // Force a layout flush: |
michael@0 | 1692 | aItem.getBoundingClientRect(); |
michael@0 | 1693 | aItem.removeAttribute("notransition"); |
michael@0 | 1694 | } |
michael@0 | 1695 | } |
michael@0 | 1696 | } |
michael@0 | 1697 | }, |
michael@0 | 1698 | _cancelDragActive: function(aItem, aNextItem, aNoTransition) { |
michael@0 | 1699 | this._updateToolbarCustomizationOutline(aItem.ownerDocument.defaultView); |
michael@0 | 1700 | let currentArea = this._getCustomizableParent(aItem); |
michael@0 | 1701 | if (!currentArea) { |
michael@0 | 1702 | return; |
michael@0 | 1703 | } |
michael@0 | 1704 | let isToolbar = CustomizableUI.getAreaType(currentArea.id) == "toolbar"; |
michael@0 | 1705 | if (isToolbar) { |
michael@0 | 1706 | if (aNoTransition) { |
michael@0 | 1707 | aItem.setAttribute("notransition", "true"); |
michael@0 | 1708 | } |
michael@0 | 1709 | aItem.removeAttribute("dragover"); |
michael@0 | 1710 | // Remove both property values in the case that the end padding |
michael@0 | 1711 | // had been set. |
michael@0 | 1712 | aItem.style.removeProperty("border-left-width"); |
michael@0 | 1713 | aItem.style.removeProperty("border-right-width"); |
michael@0 | 1714 | if (aNoTransition) { |
michael@0 | 1715 | // Force a layout flush: |
michael@0 | 1716 | aItem.getBoundingClientRect(); |
michael@0 | 1717 | aItem.removeAttribute("notransition"); |
michael@0 | 1718 | } |
michael@0 | 1719 | } else { |
michael@0 | 1720 | aItem.removeAttribute("dragover"); |
michael@0 | 1721 | if (aNextItem) { |
michael@0 | 1722 | let nextArea = this._getCustomizableParent(aNextItem); |
michael@0 | 1723 | if (nextArea == currentArea) { |
michael@0 | 1724 | // No need to do anything if we're still dragging in this area: |
michael@0 | 1725 | return; |
michael@0 | 1726 | } |
michael@0 | 1727 | } |
michael@0 | 1728 | // Otherwise, clear everything out: |
michael@0 | 1729 | let positionManager = DragPositionManager.getManagerForArea(currentArea); |
michael@0 | 1730 | positionManager.clearPlaceholders(currentArea, aNoTransition); |
michael@0 | 1731 | } |
michael@0 | 1732 | }, |
michael@0 | 1733 | |
michael@0 | 1734 | _setGridDragActive: function(aDragOverNode, aDraggedItem, aValue) { |
michael@0 | 1735 | let targetArea = this._getCustomizableParent(aDragOverNode); |
michael@0 | 1736 | let draggedWrapper = this.document.getElementById("wrapper-" + aDraggedItem.id); |
michael@0 | 1737 | let originArea = this._getCustomizableParent(draggedWrapper); |
michael@0 | 1738 | let positionManager = DragPositionManager.getManagerForArea(targetArea); |
michael@0 | 1739 | let draggedSize = this._getDragItemSize(aDragOverNode, aDraggedItem); |
michael@0 | 1740 | let isWide = aDraggedItem.classList.contains(CustomizableUI.WIDE_PANEL_CLASS); |
michael@0 | 1741 | positionManager.insertPlaceholder(targetArea, aDragOverNode, isWide, draggedSize, |
michael@0 | 1742 | originArea == targetArea); |
michael@0 | 1743 | }, |
michael@0 | 1744 | |
michael@0 | 1745 | _getDragItemSize: function(aDragOverNode, aDraggedItem) { |
michael@0 | 1746 | // Cache it good, cache it real good. |
michael@0 | 1747 | if (!this._dragSizeMap) |
michael@0 | 1748 | this._dragSizeMap = new WeakMap(); |
michael@0 | 1749 | if (!this._dragSizeMap.has(aDraggedItem)) |
michael@0 | 1750 | this._dragSizeMap.set(aDraggedItem, new WeakMap()); |
michael@0 | 1751 | let itemMap = this._dragSizeMap.get(aDraggedItem); |
michael@0 | 1752 | let targetArea = this._getCustomizableParent(aDragOverNode); |
michael@0 | 1753 | let currentArea = this._getCustomizableParent(aDraggedItem); |
michael@0 | 1754 | // Return the size for this target from cache, if it exists. |
michael@0 | 1755 | let size = itemMap.get(targetArea); |
michael@0 | 1756 | if (size) |
michael@0 | 1757 | return size; |
michael@0 | 1758 | |
michael@0 | 1759 | // Calculate size of the item when it'd be dropped in this position. |
michael@0 | 1760 | let currentParent = aDraggedItem.parentNode; |
michael@0 | 1761 | let currentSibling = aDraggedItem.nextSibling; |
michael@0 | 1762 | const kAreaType = "cui-areatype"; |
michael@0 | 1763 | let areaType, currentType; |
michael@0 | 1764 | |
michael@0 | 1765 | if (targetArea != currentArea) { |
michael@0 | 1766 | // Move the widget temporarily next to the placeholder. |
michael@0 | 1767 | aDragOverNode.parentNode.insertBefore(aDraggedItem, aDragOverNode); |
michael@0 | 1768 | // Update the node's areaType. |
michael@0 | 1769 | areaType = CustomizableUI.getAreaType(targetArea.id); |
michael@0 | 1770 | currentType = aDraggedItem.hasAttribute(kAreaType) && |
michael@0 | 1771 | aDraggedItem.getAttribute(kAreaType); |
michael@0 | 1772 | if (areaType) |
michael@0 | 1773 | aDraggedItem.setAttribute(kAreaType, areaType); |
michael@0 | 1774 | this.wrapToolbarItem(aDraggedItem, areaType || "palette"); |
michael@0 | 1775 | CustomizableUI.onWidgetDrag(aDraggedItem.id, targetArea.id); |
michael@0 | 1776 | } else { |
michael@0 | 1777 | aDraggedItem.parentNode.hidden = false; |
michael@0 | 1778 | } |
michael@0 | 1779 | |
michael@0 | 1780 | // Fetch the new size. |
michael@0 | 1781 | let rect = aDraggedItem.parentNode.getBoundingClientRect(); |
michael@0 | 1782 | size = {width: rect.width, height: rect.height}; |
michael@0 | 1783 | // Cache the found value of size for this target. |
michael@0 | 1784 | itemMap.set(targetArea, size); |
michael@0 | 1785 | |
michael@0 | 1786 | if (targetArea != currentArea) { |
michael@0 | 1787 | this.unwrapToolbarItem(aDraggedItem.parentNode); |
michael@0 | 1788 | // Put the item back into its previous position. |
michael@0 | 1789 | currentParent.insertBefore(aDraggedItem, currentSibling); |
michael@0 | 1790 | // restore the areaType |
michael@0 | 1791 | if (areaType) { |
michael@0 | 1792 | if (currentType === false) |
michael@0 | 1793 | aDraggedItem.removeAttribute(kAreaType); |
michael@0 | 1794 | else |
michael@0 | 1795 | aDraggedItem.setAttribute(kAreaType, currentType); |
michael@0 | 1796 | } |
michael@0 | 1797 | this.createOrUpdateWrapper(aDraggedItem, null, true); |
michael@0 | 1798 | CustomizableUI.onWidgetDrag(aDraggedItem.id); |
michael@0 | 1799 | } else { |
michael@0 | 1800 | aDraggedItem.parentNode.hidden = true; |
michael@0 | 1801 | } |
michael@0 | 1802 | return size; |
michael@0 | 1803 | }, |
michael@0 | 1804 | |
michael@0 | 1805 | _getCustomizableParent: function(aElement) { |
michael@0 | 1806 | let areas = CustomizableUI.areas; |
michael@0 | 1807 | areas.push(kPaletteId); |
michael@0 | 1808 | while (aElement) { |
michael@0 | 1809 | if (areas.indexOf(aElement.id) != -1) { |
michael@0 | 1810 | return aElement; |
michael@0 | 1811 | } |
michael@0 | 1812 | aElement = aElement.parentNode; |
michael@0 | 1813 | } |
michael@0 | 1814 | return null; |
michael@0 | 1815 | }, |
michael@0 | 1816 | |
michael@0 | 1817 | _getDragOverNode: function(aEvent, aAreaElement, aInToolbar, aDraggedItemId) { |
michael@0 | 1818 | let expectedParent = aAreaElement.customizationTarget || aAreaElement; |
michael@0 | 1819 | // Our tests are stupid. Cope: |
michael@0 | 1820 | if (!aEvent.clientX && !aEvent.clientY) { |
michael@0 | 1821 | return aEvent.target; |
michael@0 | 1822 | } |
michael@0 | 1823 | // Offset the drag event's position with the offset to the center of |
michael@0 | 1824 | // the thing we're dragging |
michael@0 | 1825 | let dragX = aEvent.clientX - this._dragOffset.x; |
michael@0 | 1826 | let dragY = aEvent.clientY - this._dragOffset.y; |
michael@0 | 1827 | |
michael@0 | 1828 | // Ensure this is within the container |
michael@0 | 1829 | let boundsContainer = expectedParent; |
michael@0 | 1830 | // NB: because the panel UI itself is inside a scrolling container, we need |
michael@0 | 1831 | // to use the parent bounds (otherwise, if the panel UI is scrolled down, |
michael@0 | 1832 | // the numbers we get are in window coordinates which leads to various kinds |
michael@0 | 1833 | // of weirdness) |
michael@0 | 1834 | if (boundsContainer == this.panelUIContents) { |
michael@0 | 1835 | boundsContainer = boundsContainer.parentNode; |
michael@0 | 1836 | } |
michael@0 | 1837 | let bounds = boundsContainer.getBoundingClientRect(); |
michael@0 | 1838 | dragX = Math.min(bounds.right, Math.max(dragX, bounds.left)); |
michael@0 | 1839 | dragY = Math.min(bounds.bottom, Math.max(dragY, bounds.top)); |
michael@0 | 1840 | |
michael@0 | 1841 | let targetNode; |
michael@0 | 1842 | if (aInToolbar) { |
michael@0 | 1843 | targetNode = aAreaElement.ownerDocument.elementFromPoint(dragX, dragY); |
michael@0 | 1844 | while (targetNode && targetNode.parentNode != expectedParent) { |
michael@0 | 1845 | targetNode = targetNode.parentNode; |
michael@0 | 1846 | } |
michael@0 | 1847 | } else { |
michael@0 | 1848 | let positionManager = DragPositionManager.getManagerForArea(aAreaElement); |
michael@0 | 1849 | // Make it relative to the container: |
michael@0 | 1850 | dragX -= bounds.left; |
michael@0 | 1851 | // NB: but if we're in the panel UI, we need to use the actual panel |
michael@0 | 1852 | // contents instead of the scrolling container to determine our origin |
michael@0 | 1853 | // offset against: |
michael@0 | 1854 | if (expectedParent == this.panelUIContents) { |
michael@0 | 1855 | dragY -= this.panelUIContents.getBoundingClientRect().top; |
michael@0 | 1856 | } else { |
michael@0 | 1857 | dragY -= bounds.top; |
michael@0 | 1858 | } |
michael@0 | 1859 | // Find the closest node: |
michael@0 | 1860 | targetNode = positionManager.find(aAreaElement, dragX, dragY, aDraggedItemId); |
michael@0 | 1861 | } |
michael@0 | 1862 | return targetNode || aEvent.target; |
michael@0 | 1863 | }, |
michael@0 | 1864 | |
michael@0 | 1865 | _onMouseDown: function(aEvent) { |
michael@0 | 1866 | LOG("_onMouseDown"); |
michael@0 | 1867 | if (aEvent.button != 0) { |
michael@0 | 1868 | return; |
michael@0 | 1869 | } |
michael@0 | 1870 | let doc = aEvent.target.ownerDocument; |
michael@0 | 1871 | doc.documentElement.setAttribute("customizing-movingItem", true); |
michael@0 | 1872 | let item = this._getWrapper(aEvent.target); |
michael@0 | 1873 | if (item && !item.classList.contains(kPlaceholderClass) && |
michael@0 | 1874 | item.getAttribute("removable") == "true") { |
michael@0 | 1875 | item.setAttribute("mousedown", "true"); |
michael@0 | 1876 | } |
michael@0 | 1877 | }, |
michael@0 | 1878 | |
michael@0 | 1879 | _onMouseUp: function(aEvent) { |
michael@0 | 1880 | LOG("_onMouseUp"); |
michael@0 | 1881 | if (aEvent.button != 0) { |
michael@0 | 1882 | return; |
michael@0 | 1883 | } |
michael@0 | 1884 | let doc = aEvent.target.ownerDocument; |
michael@0 | 1885 | doc.documentElement.removeAttribute("customizing-movingItem"); |
michael@0 | 1886 | let item = this._getWrapper(aEvent.target); |
michael@0 | 1887 | if (item) { |
michael@0 | 1888 | item.removeAttribute("mousedown"); |
michael@0 | 1889 | } |
michael@0 | 1890 | }, |
michael@0 | 1891 | |
michael@0 | 1892 | _getWrapper: function(aElement) { |
michael@0 | 1893 | while (aElement && aElement.localName != "toolbarpaletteitem") { |
michael@0 | 1894 | if (aElement.localName == "toolbar") |
michael@0 | 1895 | return null; |
michael@0 | 1896 | aElement = aElement.parentNode; |
michael@0 | 1897 | } |
michael@0 | 1898 | return aElement; |
michael@0 | 1899 | }, |
michael@0 | 1900 | |
michael@0 | 1901 | _showPanelCustomizationPlaceholders: function() { |
michael@0 | 1902 | let doc = this.document; |
michael@0 | 1903 | let contents = this.panelUIContents; |
michael@0 | 1904 | let narrowItemsAfterWideItem = 0; |
michael@0 | 1905 | let node = contents.lastChild; |
michael@0 | 1906 | while (node && !node.classList.contains(CustomizableUI.WIDE_PANEL_CLASS) && |
michael@0 | 1907 | (!node.firstChild || !node.firstChild.classList.contains(CustomizableUI.WIDE_PANEL_CLASS))) { |
michael@0 | 1908 | if (!node.hidden && !node.classList.contains(kPlaceholderClass)) { |
michael@0 | 1909 | narrowItemsAfterWideItem++; |
michael@0 | 1910 | } |
michael@0 | 1911 | node = node.previousSibling; |
michael@0 | 1912 | } |
michael@0 | 1913 | |
michael@0 | 1914 | let orphanedItems = narrowItemsAfterWideItem % CustomizableUI.PANEL_COLUMN_COUNT; |
michael@0 | 1915 | let placeholders = CustomizableUI.PANEL_COLUMN_COUNT - orphanedItems; |
michael@0 | 1916 | |
michael@0 | 1917 | let currentPlaceholderCount = contents.querySelectorAll("." + kPlaceholderClass).length; |
michael@0 | 1918 | if (placeholders > currentPlaceholderCount) { |
michael@0 | 1919 | while (placeholders-- > currentPlaceholderCount) { |
michael@0 | 1920 | let placeholder = doc.createElement("toolbarpaletteitem"); |
michael@0 | 1921 | placeholder.classList.add(kPlaceholderClass); |
michael@0 | 1922 | //XXXjaws The toolbarbutton child here is only necessary to get |
michael@0 | 1923 | // the styling right here. |
michael@0 | 1924 | let placeholderChild = doc.createElement("toolbarbutton"); |
michael@0 | 1925 | placeholderChild.classList.add(kPlaceholderClass + "-child"); |
michael@0 | 1926 | placeholder.appendChild(placeholderChild); |
michael@0 | 1927 | contents.appendChild(placeholder); |
michael@0 | 1928 | } |
michael@0 | 1929 | } else if (placeholders < currentPlaceholderCount) { |
michael@0 | 1930 | while (placeholders++ < currentPlaceholderCount) { |
michael@0 | 1931 | contents.querySelectorAll("." + kPlaceholderClass)[0].remove(); |
michael@0 | 1932 | } |
michael@0 | 1933 | } |
michael@0 | 1934 | }, |
michael@0 | 1935 | |
michael@0 | 1936 | _removePanelCustomizationPlaceholders: function() { |
michael@0 | 1937 | let contents = this.panelUIContents; |
michael@0 | 1938 | let oldPlaceholders = contents.getElementsByClassName(kPlaceholderClass); |
michael@0 | 1939 | while (oldPlaceholders.length) { |
michael@0 | 1940 | contents.removeChild(oldPlaceholders[0]); |
michael@0 | 1941 | } |
michael@0 | 1942 | }, |
michael@0 | 1943 | |
michael@0 | 1944 | /** |
michael@0 | 1945 | * Update toolbar customization targets during drag events to add or remove |
michael@0 | 1946 | * outlines to indicate that an area is customizable. |
michael@0 | 1947 | * |
michael@0 | 1948 | * @param aWindow The XUL window in which outlines should be updated. |
michael@0 | 1949 | * @param {Element} [aToolbarArea=null] The element of the customizable toolbar area to add the |
michael@0 | 1950 | * outline to. If aToolbarArea is falsy, the outline will be |
michael@0 | 1951 | * removed from all toolbar areas. |
michael@0 | 1952 | */ |
michael@0 | 1953 | _updateToolbarCustomizationOutline: function(aWindow, aToolbarArea = null) { |
michael@0 | 1954 | // Remove the attribute from existing customization targets |
michael@0 | 1955 | for (let area of CustomizableUI.areas) { |
michael@0 | 1956 | if (CustomizableUI.getAreaType(area) != CustomizableUI.TYPE_TOOLBAR) { |
michael@0 | 1957 | continue; |
michael@0 | 1958 | } |
michael@0 | 1959 | let target = CustomizableUI.getCustomizeTargetForArea(area, aWindow); |
michael@0 | 1960 | target.removeAttribute("customizing-dragovertarget"); |
michael@0 | 1961 | } |
michael@0 | 1962 | |
michael@0 | 1963 | // Now set the attribute on the desired target |
michael@0 | 1964 | if (aToolbarArea) { |
michael@0 | 1965 | if (CustomizableUI.getAreaType(aToolbarArea.id) != CustomizableUI.TYPE_TOOLBAR) |
michael@0 | 1966 | return; |
michael@0 | 1967 | let target = CustomizableUI.getCustomizeTargetForArea(aToolbarArea.id, aWindow); |
michael@0 | 1968 | target.setAttribute("customizing-dragovertarget", true); |
michael@0 | 1969 | } |
michael@0 | 1970 | }, |
michael@0 | 1971 | |
michael@0 | 1972 | _findVisiblePreviousSiblingNode: function(aReferenceNode) { |
michael@0 | 1973 | while (aReferenceNode && |
michael@0 | 1974 | aReferenceNode.localName == "toolbarpaletteitem" && |
michael@0 | 1975 | aReferenceNode.firstChild.hidden) { |
michael@0 | 1976 | aReferenceNode = aReferenceNode.previousSibling; |
michael@0 | 1977 | } |
michael@0 | 1978 | return aReferenceNode; |
michael@0 | 1979 | }, |
michael@0 | 1980 | }; |
michael@0 | 1981 | |
michael@0 | 1982 | function __dumpDragData(aEvent, caller) { |
michael@0 | 1983 | if (!gDebug) { |
michael@0 | 1984 | return; |
michael@0 | 1985 | } |
michael@0 | 1986 | let str = "Dumping drag data (" + (caller ? caller + " in " : "") + "CustomizeMode.jsm) {\n"; |
michael@0 | 1987 | str += " type: " + aEvent["type"] + "\n"; |
michael@0 | 1988 | for (let el of ["target", "currentTarget", "relatedTarget"]) { |
michael@0 | 1989 | if (aEvent[el]) { |
michael@0 | 1990 | str += " " + el + ": " + aEvent[el] + "(localName=" + aEvent[el].localName + "; id=" + aEvent[el].id + ")\n"; |
michael@0 | 1991 | } |
michael@0 | 1992 | } |
michael@0 | 1993 | for (let prop in aEvent.dataTransfer) { |
michael@0 | 1994 | if (typeof aEvent.dataTransfer[prop] != "function") { |
michael@0 | 1995 | str += " dataTransfer[" + prop + "]: " + aEvent.dataTransfer[prop] + "\n"; |
michael@0 | 1996 | } |
michael@0 | 1997 | } |
michael@0 | 1998 | str += "}"; |
michael@0 | 1999 | LOG(str); |
michael@0 | 2000 | } |
michael@0 | 2001 | |
michael@0 | 2002 | function dispatchFunction(aFunc) { |
michael@0 | 2003 | Services.tm.currentThread.dispatch(aFunc, Ci.nsIThread.DISPATCH_NORMAL); |
michael@0 | 2004 | } |