browser/components/customizableui/src/CustomizeMode.jsm

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

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 }

mercurial