browser/components/customizableui/src/CustomizeMode.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/customizableui/src/CustomizeMode.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2004 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +this.EXPORTED_SYMBOLS = ["CustomizeMode"];
    1.11 +
    1.12 +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
    1.13 +
    1.14 +const kPrefCustomizationDebug = "browser.uiCustomization.debug";
    1.15 +const kPrefCustomizationAnimation = "browser.uiCustomization.disableAnimation";
    1.16 +const kPaletteId = "customization-palette";
    1.17 +const kAboutURI = "about:customizing";
    1.18 +const kDragDataTypePrefix = "text/toolbarwrapper-id/";
    1.19 +const kPlaceholderClass = "panel-customization-placeholder";
    1.20 +const kSkipSourceNodePref = "browser.uiCustomization.skipSourceNodeCheck";
    1.21 +const kToolbarVisibilityBtn = "customization-toolbar-visibility-button";
    1.22 +const kDrawInTitlebarPref = "browser.tabs.drawInTitlebar";
    1.23 +const kMaxTransitionDurationMs = 2000;
    1.24 +
    1.25 +const kPanelItemContextMenu = "customizationPanelItemContextMenu";
    1.26 +const kPaletteItemContextMenu = "customizationPaletteItemContextMenu";
    1.27 +
    1.28 +Cu.import("resource://gre/modules/Services.jsm");
    1.29 +Cu.import("resource:///modules/CustomizableUI.jsm");
    1.30 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.31 +Cu.import("resource://gre/modules/Task.jsm");
    1.32 +Cu.import("resource://gre/modules/Promise.jsm");
    1.33 +
    1.34 +XPCOMUtils.defineLazyModuleGetter(this, "DragPositionManager",
    1.35 +                                  "resource:///modules/DragPositionManager.jsm");
    1.36 +XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
    1.37 +                                  "resource:///modules/BrowserUITelemetry.jsm");
    1.38 +
    1.39 +let gModuleName = "[CustomizeMode]";
    1.40 +#include logging.js
    1.41 +
    1.42 +let gDisableAnimation = null;
    1.43 +
    1.44 +let gDraggingInToolbars;
    1.45 +
    1.46 +function CustomizeMode(aWindow) {
    1.47 +  if (gDisableAnimation === null) {
    1.48 +    gDisableAnimation = Services.prefs.getPrefType(kPrefCustomizationAnimation) == Ci.nsIPrefBranch.PREF_BOOL &&
    1.49 +                        Services.prefs.getBoolPref(kPrefCustomizationAnimation);
    1.50 +  }
    1.51 +  this.window = aWindow;
    1.52 +  this.document = aWindow.document;
    1.53 +  this.browser = aWindow.gBrowser;
    1.54 +
    1.55 +  // There are two palettes - there's the palette that can be overlayed with
    1.56 +  // toolbar items in browser.xul. This is invisible, and never seen by the
    1.57 +  // user. Then there's the visible palette, which gets populated and displayed
    1.58 +  // to the user when in customizing mode.
    1.59 +  this.visiblePalette = this.document.getElementById(kPaletteId);
    1.60 +  this.paletteEmptyNotice = this.document.getElementById("customization-empty");
    1.61 +  this.paletteSpacer = this.document.getElementById("customization-spacer");
    1.62 +  this.tipPanel = this.document.getElementById("customization-tipPanel");
    1.63 +#ifdef CAN_DRAW_IN_TITLEBAR
    1.64 +  this._updateTitlebarButton();
    1.65 +  Services.prefs.addObserver(kDrawInTitlebarPref, this, false);
    1.66 +  this.window.addEventListener("unload", this);
    1.67 +#endif
    1.68 +};
    1.69 +
    1.70 +CustomizeMode.prototype = {
    1.71 +  _changed: false,
    1.72 +  _transitioning: false,
    1.73 +  window: null,
    1.74 +  document: null,
    1.75 +  // areas is used to cache the customizable areas when in customization mode.
    1.76 +  areas: null,
    1.77 +  // When in customizing mode, we swap out the reference to the invisible
    1.78 +  // palette in gNavToolbox.palette for our visiblePalette. This way, for the
    1.79 +  // customizing browser window, when widgets are removed from customizable
    1.80 +  // areas and added to the palette, they're added to the visible palette.
    1.81 +  // _stowedPalette is a reference to the old invisible palette so we can
    1.82 +  // restore gNavToolbox.palette to its original state after exiting
    1.83 +  // customization mode.
    1.84 +  _stowedPalette: null,
    1.85 +  _dragOverItem: null,
    1.86 +  _customizing: false,
    1.87 +  _skipSourceNodeCheck: null,
    1.88 +  _mainViewContext: null,
    1.89 +
    1.90 +  get panelUIContents() {
    1.91 +    return this.document.getElementById("PanelUI-contents");
    1.92 +  },
    1.93 +
    1.94 +  get _handler() {
    1.95 +    return this.window.CustomizationHandler;
    1.96 +  },
    1.97 +
    1.98 +  uninit: function() {
    1.99 +#ifdef CAN_DRAW_IN_TITLEBAR
   1.100 +    Services.prefs.removeObserver(kDrawInTitlebarPref, this);
   1.101 +#endif
   1.102 +  },
   1.103 +
   1.104 +  toggle: function() {
   1.105 +    if (this._handler.isEnteringCustomizeMode || this._handler.isExitingCustomizeMode) {
   1.106 +      this._wantToBeInCustomizeMode = !this._wantToBeInCustomizeMode;
   1.107 +      return;
   1.108 +    }
   1.109 +    if (this._customizing) {
   1.110 +      this.exit();
   1.111 +    } else {
   1.112 +      this.enter();
   1.113 +    }
   1.114 +  },
   1.115 +
   1.116 +  enter: function() {
   1.117 +    this._wantToBeInCustomizeMode = true;
   1.118 +
   1.119 +    if (this._customizing || this._handler.isEnteringCustomizeMode) {
   1.120 +      return;
   1.121 +    }
   1.122 +
   1.123 +    // Exiting; want to re-enter once we've done that.
   1.124 +    if (this._handler.isExitingCustomizeMode) {
   1.125 +      LOG("Attempted to enter while we're in the middle of exiting. " +
   1.126 +          "We'll exit after we've entered");
   1.127 +      return;
   1.128 +    }
   1.129 +
   1.130 +
   1.131 +    // We don't need to switch to kAboutURI, or open a new tab at
   1.132 +    // kAboutURI if we're already on it.
   1.133 +    if (this.browser.selectedBrowser.currentURI.spec != kAboutURI) {
   1.134 +      this.window.switchToTabHavingURI(kAboutURI, true, {
   1.135 +        skipTabAnimation: true,
   1.136 +      });
   1.137 +      return;
   1.138 +    }
   1.139 +
   1.140 +    let window = this.window;
   1.141 +    let document = this.document;
   1.142 +
   1.143 +    this._handler.isEnteringCustomizeMode = true;
   1.144 +
   1.145 +    // Always disable the reset button at the start of customize mode, it'll be re-enabled
   1.146 +    // if necessary when we finish entering:
   1.147 +    let resetButton = this.document.getElementById("customization-reset-button");
   1.148 +    resetButton.setAttribute("disabled", "true");
   1.149 +
   1.150 +    Task.spawn(function() {
   1.151 +      // We shouldn't start customize mode until after browser-delayed-startup has finished:
   1.152 +      if (!this.window.gBrowserInit.delayedStartupFinished) {
   1.153 +        let delayedStartupDeferred = Promise.defer();
   1.154 +        let delayedStartupObserver = function(aSubject) {
   1.155 +          if (aSubject == this.window) {
   1.156 +            Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
   1.157 +            delayedStartupDeferred.resolve();
   1.158 +          }
   1.159 +        }.bind(this);
   1.160 +        Services.obs.addObserver(delayedStartupObserver, "browser-delayed-startup-finished", false);
   1.161 +        yield delayedStartupDeferred.promise;
   1.162 +      }
   1.163 +
   1.164 +      let toolbarVisibilityBtn = document.getElementById(kToolbarVisibilityBtn);
   1.165 +      let togglableToolbars = window.getTogglableToolbars();
   1.166 +      let bookmarksToolbar = document.getElementById("PersonalToolbar");
   1.167 +      if (togglableToolbars.length == 0) {
   1.168 +        toolbarVisibilityBtn.setAttribute("hidden", "true");
   1.169 +      } else {
   1.170 +        toolbarVisibilityBtn.removeAttribute("hidden");
   1.171 +      }
   1.172 +
   1.173 +      // Disable lightweight themes while in customization mode since
   1.174 +      // they don't have large enough images to pad the full browser window.
   1.175 +      if (this.document.documentElement._lightweightTheme)
   1.176 +        this.document.documentElement._lightweightTheme.disable();
   1.177 +
   1.178 +      CustomizableUI.dispatchToolboxEvent("beforecustomization", {}, window);
   1.179 +      CustomizableUI.notifyStartCustomizing(this.window);
   1.180 +
   1.181 +      // Add a keypress listener to the document so that we can quickly exit
   1.182 +      // customization mode when pressing ESC.
   1.183 +      document.addEventListener("keypress", this);
   1.184 +
   1.185 +      // Same goes for the menu button - if we're customizing, a click on the
   1.186 +      // menu button means a quick exit from customization mode.
   1.187 +      window.PanelUI.hide();
   1.188 +      window.PanelUI.menuButton.addEventListener("command", this);
   1.189 +      window.PanelUI.menuButton.open = true;
   1.190 +      window.PanelUI.beginBatchUpdate();
   1.191 +
   1.192 +      // The menu panel is lazy, and registers itself when the popup shows. We
   1.193 +      // need to force the menu panel to register itself, or else customization
   1.194 +      // is really not going to work. We pass "true" to ensureReady to
   1.195 +      // indicate that we're handling calling startBatchUpdate and
   1.196 +      // endBatchUpdate.
   1.197 +      if (!window.PanelUI.isReady()) {
   1.198 +        yield window.PanelUI.ensureReady(true);
   1.199 +      }
   1.200 +
   1.201 +      // Hide the palette before starting the transition for increased perf.
   1.202 +      this.visiblePalette.hidden = true;
   1.203 +      this.visiblePalette.removeAttribute("showing");
   1.204 +
   1.205 +      // Disable the button-text fade-out mask
   1.206 +      // during the transition for increased perf.
   1.207 +      let panelContents = window.PanelUI.contents;
   1.208 +      panelContents.setAttribute("customize-transitioning", "true");
   1.209 +
   1.210 +      // Move the mainView in the panel to the holder so that we can see it
   1.211 +      // while customizing.
   1.212 +      let mainView = window.PanelUI.mainView;
   1.213 +      let panelHolder = document.getElementById("customization-panelHolder");
   1.214 +      panelHolder.appendChild(mainView);
   1.215 +
   1.216 +      let customizeButton = document.getElementById("PanelUI-customize");
   1.217 +      customizeButton.setAttribute("enterLabel", customizeButton.getAttribute("label"));
   1.218 +      customizeButton.setAttribute("label", customizeButton.getAttribute("exitLabel"));
   1.219 +      customizeButton.setAttribute("enterTooltiptext", customizeButton.getAttribute("tooltiptext"));
   1.220 +      customizeButton.setAttribute("tooltiptext", customizeButton.getAttribute("exitTooltiptext"));
   1.221 +
   1.222 +      this._transitioning = true;
   1.223 +
   1.224 +      let customizer = document.getElementById("customization-container");
   1.225 +      customizer.parentNode.selectedPanel = customizer;
   1.226 +      customizer.hidden = false;
   1.227 +
   1.228 +      yield this._doTransition(true);
   1.229 +
   1.230 +      // Let everybody in this window know that we're about to customize.
   1.231 +      CustomizableUI.dispatchToolboxEvent("customizationstarting", {}, window);
   1.232 +
   1.233 +      this._mainViewContext = mainView.getAttribute("context");
   1.234 +      if (this._mainViewContext) {
   1.235 +        mainView.removeAttribute("context");
   1.236 +      }
   1.237 +
   1.238 +      this._showPanelCustomizationPlaceholders();
   1.239 +
   1.240 +      yield this._wrapToolbarItems();
   1.241 +      this.populatePalette();
   1.242 +
   1.243 +      this._addDragHandlers(this.visiblePalette);
   1.244 +
   1.245 +      window.gNavToolbox.addEventListener("toolbarvisibilitychange", this);
   1.246 +
   1.247 +      document.getElementById("PanelUI-help").setAttribute("disabled", true);
   1.248 +      document.getElementById("PanelUI-quit").setAttribute("disabled", true);
   1.249 +
   1.250 +      this._updateResetButton();
   1.251 +      this._updateUndoResetButton();
   1.252 +
   1.253 +      this._skipSourceNodeCheck = Services.prefs.getPrefType(kSkipSourceNodePref) == Ci.nsIPrefBranch.PREF_BOOL &&
   1.254 +                                  Services.prefs.getBoolPref(kSkipSourceNodePref);
   1.255 +
   1.256 +      let customizableToolbars = document.querySelectorAll("toolbar[customizable=true]:not([autohide=true]):not([collapsed=true])");
   1.257 +      for (let toolbar of customizableToolbars)
   1.258 +        toolbar.setAttribute("customizing", true);
   1.259 +
   1.260 +      CustomizableUI.addListener(this);
   1.261 +      window.PanelUI.endBatchUpdate();
   1.262 +      this._customizing = true;
   1.263 +      this._transitioning = false;
   1.264 +
   1.265 +      // Show the palette now that the transition has finished.
   1.266 +      this.visiblePalette.hidden = false;
   1.267 +      window.setTimeout(() => {
   1.268 +        // Force layout reflow to ensure the animation runs,
   1.269 +        // and make it async so it doesn't affect the timing.
   1.270 +        this.visiblePalette.clientTop;
   1.271 +        this.visiblePalette.setAttribute("showing", "true");
   1.272 +      }, 0);
   1.273 +      this.paletteSpacer.hidden = true;
   1.274 +      this._updateEmptyPaletteNotice();
   1.275 +
   1.276 +      this.maybeShowTip(panelHolder);
   1.277 +
   1.278 +      this._handler.isEnteringCustomizeMode = false;
   1.279 +      panelContents.removeAttribute("customize-transitioning");
   1.280 +
   1.281 +      CustomizableUI.dispatchToolboxEvent("customizationready", {}, window);
   1.282 +      this._enableOutlinesTimeout = window.setTimeout(() => {
   1.283 +        this.document.getElementById("nav-bar").setAttribute("showoutline", "true");
   1.284 +        this.panelUIContents.setAttribute("showoutline", "true");
   1.285 +        delete this._enableOutlinesTimeout;
   1.286 +      }, 0);
   1.287 +
   1.288 +      // It's possible that we didn't enter customize mode via the menu panel,
   1.289 +      // meaning we didn't kick off about:customizing preloading. If that's
   1.290 +      // the case, let's kick it off for the next time we load this mode.
   1.291 +      window.gCustomizationTabPreloader.ensurePreloading();
   1.292 +      if (!this._wantToBeInCustomizeMode) {
   1.293 +        this.exit();
   1.294 +      }
   1.295 +    }.bind(this)).then(null, function(e) {
   1.296 +      ERROR(e);
   1.297 +      // We should ensure this has been called, and calling it again doesn't hurt:
   1.298 +      window.PanelUI.endBatchUpdate();
   1.299 +      this._handler.isEnteringCustomizeMode = false;
   1.300 +    }.bind(this));
   1.301 +  },
   1.302 +
   1.303 +  exit: function() {
   1.304 +    this._wantToBeInCustomizeMode = false;
   1.305 +
   1.306 +    if (!this._customizing || this._handler.isExitingCustomizeMode) {
   1.307 +      return;
   1.308 +    }
   1.309 +
   1.310 +    // Entering; want to exit once we've done that.
   1.311 +    if (this._handler.isEnteringCustomizeMode) {
   1.312 +      LOG("Attempted to exit while we're in the middle of entering. " +
   1.313 +          "We'll exit after we've entered");
   1.314 +      return;
   1.315 +    }
   1.316 +
   1.317 +    if (this.resetting) {
   1.318 +      LOG("Attempted to exit while we're resetting. " +
   1.319 +          "We'll exit after resetting has finished.");
   1.320 +      return;
   1.321 +    }
   1.322 +
   1.323 +    this.hideTip();
   1.324 +
   1.325 +    this._handler.isExitingCustomizeMode = true;
   1.326 +
   1.327 +    if (this._enableOutlinesTimeout) {
   1.328 +      this.window.clearTimeout(this._enableOutlinesTimeout);
   1.329 +    } else {
   1.330 +      this.document.getElementById("nav-bar").removeAttribute("showoutline");
   1.331 +      this.panelUIContents.removeAttribute("showoutline");
   1.332 +    }
   1.333 +
   1.334 +    this._removeExtraToolbarsIfEmpty();
   1.335 +
   1.336 +    CustomizableUI.removeListener(this);
   1.337 +
   1.338 +    this.document.removeEventListener("keypress", this);
   1.339 +    this.window.PanelUI.menuButton.removeEventListener("command", this);
   1.340 +    this.window.PanelUI.menuButton.open = false;
   1.341 +
   1.342 +    this.window.PanelUI.beginBatchUpdate();
   1.343 +
   1.344 +    this._removePanelCustomizationPlaceholders();
   1.345 +
   1.346 +    let window = this.window;
   1.347 +    let document = this.document;
   1.348 +    let documentElement = document.documentElement;
   1.349 +
   1.350 +    // Hide the palette before starting the transition for increased perf.
   1.351 +    this.paletteSpacer.hidden = false;
   1.352 +    this.visiblePalette.hidden = true;
   1.353 +    this.visiblePalette.removeAttribute("showing");
   1.354 +    this.paletteEmptyNotice.hidden = true;
   1.355 +
   1.356 +    // Disable the button-text fade-out mask
   1.357 +    // during the transition for increased perf.
   1.358 +    let panelContents = window.PanelUI.contents;
   1.359 +    panelContents.setAttribute("customize-transitioning", "true");
   1.360 +
   1.361 +    // Disable the reset and undo reset buttons while transitioning:
   1.362 +    let resetButton = this.document.getElementById("customization-reset-button");
   1.363 +    let undoResetButton = this.document.getElementById("customization-undo-reset-button");
   1.364 +    undoResetButton.hidden = resetButton.disabled = true;
   1.365 +
   1.366 +    this._transitioning = true;
   1.367 +
   1.368 +    Task.spawn(function() {
   1.369 +      yield this.depopulatePalette();
   1.370 +
   1.371 +      yield this._doTransition(false);
   1.372 +
   1.373 +      let browser = document.getElementById("browser");
   1.374 +      if (this.browser.selectedBrowser.currentURI.spec == kAboutURI) {
   1.375 +        let custBrowser = this.browser.selectedBrowser;
   1.376 +        if (custBrowser.canGoBack) {
   1.377 +          // If there's history to this tab, just go back.
   1.378 +          // Note that this throws an exception if the previous document has a
   1.379 +          // problematic URL (e.g. about:idontexist)
   1.380 +          try {
   1.381 +            custBrowser.goBack();
   1.382 +          } catch (ex) {
   1.383 +            ERROR(ex);
   1.384 +          }
   1.385 +        } else {
   1.386 +          // If we can't go back, we're removing the about:customization tab.
   1.387 +          // We only do this if we're the top window for this window (so not
   1.388 +          // a dialog window, for example).
   1.389 +          if (window.getTopWin(true) == window) {
   1.390 +            let customizationTab = this.browser.selectedTab;
   1.391 +            if (this.browser.browsers.length == 1) {
   1.392 +              window.BrowserOpenTab();
   1.393 +            }
   1.394 +            this.browser.removeTab(customizationTab);
   1.395 +          }
   1.396 +        }
   1.397 +      }
   1.398 +      browser.parentNode.selectedPanel = browser;
   1.399 +      let customizer = document.getElementById("customization-container");
   1.400 +      customizer.hidden = true;
   1.401 +
   1.402 +      window.gNavToolbox.removeEventListener("toolbarvisibilitychange", this);
   1.403 +
   1.404 +      DragPositionManager.stop();
   1.405 +      this._removeDragHandlers(this.visiblePalette);
   1.406 +
   1.407 +      yield this._unwrapToolbarItems();
   1.408 +
   1.409 +      if (this._changed) {
   1.410 +        // XXXmconley: At first, it seems strange to also persist the old way with
   1.411 +        //             currentset - but this might actually be useful for switching
   1.412 +        //             to old builds. We might want to keep this around for a little
   1.413 +        //             bit.
   1.414 +        this.persistCurrentSets();
   1.415 +      }
   1.416 +
   1.417 +      // And drop all area references.
   1.418 +      this.areas = [];
   1.419 +
   1.420 +      // Let everybody in this window know that we're starting to
   1.421 +      // exit customization mode.
   1.422 +      CustomizableUI.dispatchToolboxEvent("customizationending", {}, window);
   1.423 +
   1.424 +      window.PanelUI.setMainView(window.PanelUI.mainView);
   1.425 +      window.PanelUI.menuButton.disabled = false;
   1.426 +
   1.427 +      let customizeButton = document.getElementById("PanelUI-customize");
   1.428 +      customizeButton.setAttribute("exitLabel", customizeButton.getAttribute("label"));
   1.429 +      customizeButton.setAttribute("label", customizeButton.getAttribute("enterLabel"));
   1.430 +      customizeButton.setAttribute("exitTooltiptext", customizeButton.getAttribute("tooltiptext"));
   1.431 +      customizeButton.setAttribute("tooltiptext", customizeButton.getAttribute("enterTooltiptext"));
   1.432 +
   1.433 +      // We have to use setAttribute/removeAttribute here instead of the
   1.434 +      // property because the XBL property will be set later, and right
   1.435 +      // now we'd be setting an expando, which breaks the XBL property.
   1.436 +      document.getElementById("PanelUI-help").removeAttribute("disabled");
   1.437 +      document.getElementById("PanelUI-quit").removeAttribute("disabled");
   1.438 +
   1.439 +      panelContents.removeAttribute("customize-transitioning");
   1.440 +
   1.441 +      // We need to set this._customizing to false before removing the tab
   1.442 +      // or the TabSelect event handler will think that we are exiting
   1.443 +      // customization mode for a second time.
   1.444 +      this._customizing = false;
   1.445 +
   1.446 +      let mainView = window.PanelUI.mainView;
   1.447 +      if (this._mainViewContext) {
   1.448 +        mainView.setAttribute("context", this._mainViewContext);
   1.449 +      }
   1.450 +
   1.451 +      if (this.document.documentElement._lightweightTheme)
   1.452 +        this.document.documentElement._lightweightTheme.enable();
   1.453 +
   1.454 +      let customizableToolbars = document.querySelectorAll("toolbar[customizable=true]:not([autohide=true])");
   1.455 +      for (let toolbar of customizableToolbars)
   1.456 +        toolbar.removeAttribute("customizing");
   1.457 +
   1.458 +      this.window.PanelUI.endBatchUpdate();
   1.459 +      this._changed = false;
   1.460 +      this._transitioning = false;
   1.461 +      this._handler.isExitingCustomizeMode = false;
   1.462 +      CustomizableUI.dispatchToolboxEvent("aftercustomization", {}, window);
   1.463 +      CustomizableUI.notifyEndCustomizing(window);
   1.464 +
   1.465 +      if (this._wantToBeInCustomizeMode) {
   1.466 +        this.enter();
   1.467 +      }
   1.468 +    }.bind(this)).then(null, function(e) {
   1.469 +      ERROR(e);
   1.470 +      // We should ensure this has been called, and calling it again doesn't hurt:
   1.471 +      window.PanelUI.endBatchUpdate();
   1.472 +      this._handler.isExitingCustomizeMode = false;
   1.473 +    }.bind(this));
   1.474 +  },
   1.475 +
   1.476 +  /**
   1.477 +   * The customize mode transition has 3 phases when entering:
   1.478 +   * 1) Pre-customization mode
   1.479 +   *    This is the starting phase of the browser.
   1.480 +   * 2) customize-entering
   1.481 +   *    This phase is a transition, optimized for smoothness.
   1.482 +   * 3) customize-entered
   1.483 +   *    After the transition completes, this phase draws all of the
   1.484 +   *    expensive detail that isn't necessary during the second phase.
   1.485 +   *
   1.486 +   * Exiting customization mode has a similar set of phases, but in reverse
   1.487 +   * order - customize-entered, customize-exiting, pre-customization mode.
   1.488 +   *
   1.489 +   * When in the customize-entering, customize-entered, or customize-exiting
   1.490 +   * phases, there is a "customizing" attribute set on the main-window to simplify
   1.491 +   * excluding certain styles while in any phase of customize mode.
   1.492 +   */
   1.493 +  _doTransition: function(aEntering) {
   1.494 +    let deferred = Promise.defer();
   1.495 +    let deck = this.document.getElementById("content-deck");
   1.496 +
   1.497 +    let customizeTransitionEnd = function(aEvent) {
   1.498 +      if (aEvent != "timedout" &&
   1.499 +          (aEvent.originalTarget != deck || aEvent.propertyName != "margin-left")) {
   1.500 +        return;
   1.501 +      }
   1.502 +      this.window.clearTimeout(catchAllTimeout);
   1.503 +      // Bug 962677: We let the event loop breathe for before we do the final
   1.504 +      // stage of the transition to improve perceived performance.
   1.505 +      this.window.setTimeout(function () {
   1.506 +        deck.removeEventListener("transitionend", customizeTransitionEnd);
   1.507 +
   1.508 +        if (!aEntering) {
   1.509 +          this.document.documentElement.removeAttribute("customize-exiting");
   1.510 +          this.document.documentElement.removeAttribute("customizing");
   1.511 +        } else {
   1.512 +          this.document.documentElement.setAttribute("customize-entered", true);
   1.513 +          this.document.documentElement.removeAttribute("customize-entering");
   1.514 +        }
   1.515 +        CustomizableUI.dispatchToolboxEvent("customization-transitionend", aEntering, this.window);
   1.516 +
   1.517 +        deferred.resolve();
   1.518 +      }.bind(this), 0);
   1.519 +    }.bind(this);
   1.520 +    deck.addEventListener("transitionend", customizeTransitionEnd);
   1.521 +
   1.522 +    if (gDisableAnimation) {
   1.523 +      this.document.getElementById("tab-view-deck").setAttribute("fastcustomizeanimation", true);
   1.524 +    }
   1.525 +    if (aEntering) {
   1.526 +      this.document.documentElement.setAttribute("customizing", true);
   1.527 +      this.document.documentElement.setAttribute("customize-entering", true);
   1.528 +    } else {
   1.529 +      this.document.documentElement.setAttribute("customize-exiting", true);
   1.530 +      this.document.documentElement.removeAttribute("customize-entered");
   1.531 +    }
   1.532 +
   1.533 +    let catchAll = () => customizeTransitionEnd("timedout");
   1.534 +    let catchAllTimeout = this.window.setTimeout(catchAll, kMaxTransitionDurationMs);
   1.535 +    return deferred.promise;
   1.536 +  },
   1.537 +
   1.538 +  maybeShowTip: function(aAnchor) {
   1.539 +    let shown = false;
   1.540 +    const kShownPref = "browser.customizemode.tip0.shown";
   1.541 +    try {
   1.542 +      shown = Services.prefs.getBoolPref(kShownPref);
   1.543 +    } catch (ex) {}
   1.544 +    if (shown)
   1.545 +      return;
   1.546 +
   1.547 +    let anchorNode = aAnchor || this.document.getElementById("customization-panelHolder");
   1.548 +    let messageNode = this.tipPanel.querySelector(".customization-tipPanel-contentMessage");
   1.549 +    if (!messageNode.childElementCount) {
   1.550 +      // Put the tip contents in the popup.
   1.551 +      let bundle = this.document.getElementById("bundle_browser");
   1.552 +      const kLabelClass = "customization-tipPanel-link";
   1.553 +      messageNode.innerHTML = bundle.getFormattedString("customizeTips.tip0", [
   1.554 +        "<label class=\"customization-tipPanel-em\" value=\"" +
   1.555 +          bundle.getString("customizeTips.tip0.hint") + "\"/>",
   1.556 +        this.document.getElementById("bundle_brand").getString("brandShortName"),
   1.557 +        "<label class=\"" + kLabelClass + " text-link\" value=\"" +
   1.558 +        bundle.getString("customizeTips.tip0.learnMore") + "\"/>"
   1.559 +      ]);
   1.560 +
   1.561 +      messageNode.querySelector("." + kLabelClass).addEventListener("click", () => {
   1.562 +        let url = Services.urlFormatter.formatURLPref("browser.customizemode.tip0.learnMoreUrl");
   1.563 +        let browser = this.browser;
   1.564 +        browser.selectedTab = browser.addTab(url);
   1.565 +        this.hideTip();
   1.566 +      });
   1.567 +    }
   1.568 +
   1.569 +    this.tipPanel.hidden = false;
   1.570 +    this.tipPanel.openPopup(anchorNode);
   1.571 +    Services.prefs.setBoolPref(kShownPref, true);
   1.572 +  },
   1.573 +
   1.574 +  hideTip: function() {
   1.575 +    this.tipPanel.hidePopup();
   1.576 +  },
   1.577 +
   1.578 +  _getCustomizableChildForNode: function(aNode) {
   1.579 +    // NB: adjusted from _getCustomizableParent to keep that method fast
   1.580 +    // (it's used during drags), and avoid multiple DOM loops
   1.581 +    let areas = CustomizableUI.areas;
   1.582 +    // Caching this length is important because otherwise we'll also iterate
   1.583 +    // over items we add to the end from within the loop.
   1.584 +    let numberOfAreas = areas.length;
   1.585 +    for (let i = 0; i < numberOfAreas; i++) {
   1.586 +      let area = areas[i];
   1.587 +      let areaNode = aNode.ownerDocument.getElementById(area);
   1.588 +      let customizationTarget = areaNode && areaNode.customizationTarget;
   1.589 +      if (customizationTarget && customizationTarget != areaNode) {
   1.590 +        areas.push(customizationTarget.id);
   1.591 +      }
   1.592 +      let overflowTarget = areaNode.getAttribute("overflowtarget");
   1.593 +      if (overflowTarget) {
   1.594 +        areas.push(overflowTarget);
   1.595 +      }
   1.596 +    }
   1.597 +    areas.push(kPaletteId);
   1.598 +
   1.599 +    while (aNode && aNode.parentNode) {
   1.600 +      let parent = aNode.parentNode;
   1.601 +      if (areas.indexOf(parent.id) != -1) {
   1.602 +        return aNode;
   1.603 +      }
   1.604 +      aNode = parent;
   1.605 +    }
   1.606 +    return null;
   1.607 +  },
   1.608 +
   1.609 +  addToToolbar: function(aNode) {
   1.610 +    aNode = this._getCustomizableChildForNode(aNode);
   1.611 +    if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
   1.612 +      aNode = aNode.firstChild;
   1.613 +    }
   1.614 +    CustomizableUI.addWidgetToArea(aNode.id, CustomizableUI.AREA_NAVBAR);
   1.615 +    if (!this._customizing) {
   1.616 +      CustomizableUI.dispatchToolboxEvent("customizationchange");
   1.617 +    }
   1.618 +  },
   1.619 +
   1.620 +  addToPanel: function(aNode) {
   1.621 +    aNode = this._getCustomizableChildForNode(aNode);
   1.622 +    if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
   1.623 +      aNode = aNode.firstChild;
   1.624 +    }
   1.625 +    CustomizableUI.addWidgetToArea(aNode.id, CustomizableUI.AREA_PANEL);
   1.626 +    if (!this._customizing) {
   1.627 +      CustomizableUI.dispatchToolboxEvent("customizationchange");
   1.628 +    }
   1.629 +  },
   1.630 +
   1.631 +  removeFromArea: function(aNode) {
   1.632 +    aNode = this._getCustomizableChildForNode(aNode);
   1.633 +    if (aNode.localName == "toolbarpaletteitem" && aNode.firstChild) {
   1.634 +      aNode = aNode.firstChild;
   1.635 +    }
   1.636 +    CustomizableUI.removeWidgetFromArea(aNode.id);
   1.637 +    if (!this._customizing) {
   1.638 +      CustomizableUI.dispatchToolboxEvent("customizationchange");
   1.639 +    }
   1.640 +  },
   1.641 +
   1.642 +  populatePalette: function() {
   1.643 +    let fragment = this.document.createDocumentFragment();
   1.644 +    let toolboxPalette = this.window.gNavToolbox.palette;
   1.645 +
   1.646 +    try {
   1.647 +      let unusedWidgets = CustomizableUI.getUnusedWidgets(toolboxPalette);
   1.648 +      for (let widget of unusedWidgets) {
   1.649 +        let paletteItem = this.makePaletteItem(widget, "palette");
   1.650 +        fragment.appendChild(paletteItem);
   1.651 +      }
   1.652 +
   1.653 +      this.visiblePalette.appendChild(fragment);
   1.654 +      this._stowedPalette = this.window.gNavToolbox.palette;
   1.655 +      this.window.gNavToolbox.palette = this.visiblePalette;
   1.656 +    } catch (ex) {
   1.657 +      ERROR(ex);
   1.658 +    }
   1.659 +  },
   1.660 +
   1.661 +  //XXXunf Maybe this should use -moz-element instead of wrapping the node?
   1.662 +  //       Would ensure no weird interactions/event handling from original node,
   1.663 +  //       and makes it possible to put this in a lazy-loaded iframe/real tab
   1.664 +  //       while still getting rid of the need for overlays.
   1.665 +  makePaletteItem: function(aWidget, aPlace) {
   1.666 +    let widgetNode = aWidget.forWindow(this.window).node;
   1.667 +    let wrapper = this.createOrUpdateWrapper(widgetNode, aPlace);
   1.668 +    wrapper.appendChild(widgetNode);
   1.669 +    return wrapper;
   1.670 +  },
   1.671 +
   1.672 +  depopulatePalette: function() {
   1.673 +    return Task.spawn(function() {
   1.674 +      this.visiblePalette.hidden = true;
   1.675 +      let paletteChild = this.visiblePalette.firstChild;
   1.676 +      let nextChild;
   1.677 +      while (paletteChild) {
   1.678 +        nextChild = paletteChild.nextElementSibling;
   1.679 +        let provider = CustomizableUI.getWidget(paletteChild.id).provider;
   1.680 +        if (provider == CustomizableUI.PROVIDER_XUL) {
   1.681 +          let unwrappedPaletteItem =
   1.682 +            yield this.deferredUnwrapToolbarItem(paletteChild);
   1.683 +          this._stowedPalette.appendChild(unwrappedPaletteItem);
   1.684 +        } else if (provider == CustomizableUI.PROVIDER_API) {
   1.685 +          //XXXunf Currently this doesn't destroy the (now unused) node. It would
   1.686 +          //       be good to do so, but we need to keep strong refs to it in
   1.687 +          //       CustomizableUI (can't iterate of WeakMaps), and there's the
   1.688 +          //       question of what behavior wrappers should have if consumers
   1.689 +          //       keep hold of them.
   1.690 +          //widget.destroyInstance(widgetNode);
   1.691 +        } else if (provider == CustomizableUI.PROVIDER_SPECIAL) {
   1.692 +          this.visiblePalette.removeChild(paletteChild);
   1.693 +        }
   1.694 +
   1.695 +        paletteChild = nextChild;
   1.696 +      }
   1.697 +      this.visiblePalette.hidden = false;
   1.698 +      this.window.gNavToolbox.palette = this._stowedPalette;
   1.699 +    }.bind(this)).then(null, ERROR);
   1.700 +  },
   1.701 +
   1.702 +  isCustomizableItem: function(aNode) {
   1.703 +    return aNode.localName == "toolbarbutton" ||
   1.704 +           aNode.localName == "toolbaritem" ||
   1.705 +           aNode.localName == "toolbarseparator" ||
   1.706 +           aNode.localName == "toolbarspring" ||
   1.707 +           aNode.localName == "toolbarspacer";
   1.708 +  },
   1.709 +
   1.710 +  isWrappedToolbarItem: function(aNode) {
   1.711 +    return aNode.localName == "toolbarpaletteitem";
   1.712 +  },
   1.713 +
   1.714 +  deferredWrapToolbarItem: function(aNode, aPlace) {
   1.715 +    let deferred = Promise.defer();
   1.716 +
   1.717 +    dispatchFunction(function() {
   1.718 +      let wrapper = this.wrapToolbarItem(aNode, aPlace);
   1.719 +      deferred.resolve(wrapper);
   1.720 +    }.bind(this))
   1.721 +
   1.722 +    return deferred.promise;
   1.723 +  },
   1.724 +
   1.725 +  wrapToolbarItem: function(aNode, aPlace) {
   1.726 +    if (!this.isCustomizableItem(aNode)) {
   1.727 +      return aNode;
   1.728 +    }
   1.729 +    let wrapper = this.createOrUpdateWrapper(aNode, aPlace);
   1.730 +
   1.731 +    // It's possible that this toolbar node is "mid-flight" and doesn't have
   1.732 +    // a parent, in which case we skip replacing it. This can happen if a
   1.733 +    // toolbar item has been dragged into the palette. In that case, we tell
   1.734 +    // CustomizableUI to remove the widget from its area before putting the
   1.735 +    // widget in the palette - so the node will have no parent.
   1.736 +    if (aNode.parentNode) {
   1.737 +      aNode = aNode.parentNode.replaceChild(wrapper, aNode);
   1.738 +    }
   1.739 +    wrapper.appendChild(aNode);
   1.740 +    return wrapper;
   1.741 +  },
   1.742 +
   1.743 +  createOrUpdateWrapper: function(aNode, aPlace, aIsUpdate) {
   1.744 +    let wrapper;
   1.745 +    if (aIsUpdate && aNode.parentNode && aNode.parentNode.localName == "toolbarpaletteitem") {
   1.746 +      wrapper = aNode.parentNode;
   1.747 +      aPlace = wrapper.getAttribute("place");
   1.748 +    } else {
   1.749 +      wrapper = this.document.createElement("toolbarpaletteitem");
   1.750 +      // "place" is used by toolkit to add the toolbarpaletteitem-palette
   1.751 +      // binding to a toolbarpaletteitem, which gives it a label node for when
   1.752 +      // it's sitting in the palette.
   1.753 +      wrapper.setAttribute("place", aPlace);
   1.754 +    }
   1.755 +
   1.756 +
   1.757 +    // Ensure the wrapped item doesn't look like it's in any special state, and
   1.758 +    // can't be interactved with when in the customization palette.
   1.759 +    if (aNode.hasAttribute("command")) {
   1.760 +      wrapper.setAttribute("itemcommand", aNode.getAttribute("command"));
   1.761 +      aNode.removeAttribute("command");
   1.762 +    }
   1.763 +
   1.764 +    if (aNode.hasAttribute("observes")) {
   1.765 +      wrapper.setAttribute("itemobserves", aNode.getAttribute("observes"));
   1.766 +      aNode.removeAttribute("observes");
   1.767 +    }
   1.768 +
   1.769 +    if (aNode.getAttribute("checked") == "true") {
   1.770 +      wrapper.setAttribute("itemchecked", "true");
   1.771 +      aNode.removeAttribute("checked");
   1.772 +    }
   1.773 +
   1.774 +    if (aNode.hasAttribute("id")) {
   1.775 +      wrapper.setAttribute("id", "wrapper-" + aNode.getAttribute("id"));
   1.776 +    }
   1.777 +
   1.778 +    if (aNode.hasAttribute("label")) {
   1.779 +      wrapper.setAttribute("title", aNode.getAttribute("label"));
   1.780 +    } else if (aNode.hasAttribute("title")) {
   1.781 +      wrapper.setAttribute("title", aNode.getAttribute("title"));
   1.782 +    }
   1.783 +
   1.784 +    if (aNode.hasAttribute("flex")) {
   1.785 +      wrapper.setAttribute("flex", aNode.getAttribute("flex"));
   1.786 +    }
   1.787 +
   1.788 +    if (aPlace == "panel") {
   1.789 +      if (aNode.classList.contains(CustomizableUI.WIDE_PANEL_CLASS)) {
   1.790 +        wrapper.setAttribute("haswideitem", "true");
   1.791 +      } else if (wrapper.hasAttribute("haswideitem")) {
   1.792 +        wrapper.removeAttribute("haswideitem");
   1.793 +      }
   1.794 +    }
   1.795 +
   1.796 +    let removable = aPlace == "palette" || CustomizableUI.isWidgetRemovable(aNode);
   1.797 +    wrapper.setAttribute("removable", removable);
   1.798 +
   1.799 +    let contextMenuAttrName = aNode.getAttribute("context") ? "context" :
   1.800 +                                aNode.getAttribute("contextmenu") ? "contextmenu" : "";
   1.801 +    let currentContextMenu = aNode.getAttribute(contextMenuAttrName);
   1.802 +    let contextMenuForPlace = aPlace == "panel" ?
   1.803 +                                kPanelItemContextMenu :
   1.804 +                                kPaletteItemContextMenu;
   1.805 +    if (aPlace != "toolbar") {
   1.806 +      wrapper.setAttribute("context", contextMenuForPlace);
   1.807 +    }
   1.808 +    // Only keep track of the menu if it is non-default.
   1.809 +    if (currentContextMenu &&
   1.810 +        currentContextMenu != contextMenuForPlace) {
   1.811 +      aNode.setAttribute("wrapped-context", currentContextMenu);
   1.812 +      aNode.setAttribute("wrapped-contextAttrName", contextMenuAttrName)
   1.813 +      aNode.removeAttribute(contextMenuAttrName);
   1.814 +    } else if (currentContextMenu == contextMenuForPlace) {
   1.815 +      aNode.removeAttribute(contextMenuAttrName);
   1.816 +    }
   1.817 +
   1.818 +    // Only add listeners for newly created wrappers:
   1.819 +    if (!aIsUpdate) {
   1.820 +      wrapper.addEventListener("mousedown", this);
   1.821 +      wrapper.addEventListener("mouseup", this);
   1.822 +    }
   1.823 +
   1.824 +    return wrapper;
   1.825 +  },
   1.826 +
   1.827 +  deferredUnwrapToolbarItem: function(aWrapper) {
   1.828 +    let deferred = Promise.defer();
   1.829 +    dispatchFunction(function() {
   1.830 +      let item = null;
   1.831 +      try {
   1.832 +        item = this.unwrapToolbarItem(aWrapper);
   1.833 +      } catch (ex) {
   1.834 +        Cu.reportError(ex);
   1.835 +      }
   1.836 +      deferred.resolve(item);
   1.837 +    }.bind(this));
   1.838 +    return deferred.promise;
   1.839 +  },
   1.840 +
   1.841 +  unwrapToolbarItem: function(aWrapper) {
   1.842 +    if (aWrapper.nodeName != "toolbarpaletteitem") {
   1.843 +      return aWrapper;
   1.844 +    }
   1.845 +    aWrapper.removeEventListener("mousedown", this);
   1.846 +    aWrapper.removeEventListener("mouseup", this);
   1.847 +
   1.848 +    let place = aWrapper.getAttribute("place");
   1.849 +
   1.850 +    let toolbarItem = aWrapper.firstChild;
   1.851 +    if (!toolbarItem) {
   1.852 +      ERROR("no toolbarItem child for " + aWrapper.tagName + "#" + aWrapper.id);
   1.853 +      aWrapper.remove();
   1.854 +      return null;
   1.855 +    }
   1.856 +
   1.857 +    if (aWrapper.hasAttribute("itemobserves")) {
   1.858 +      toolbarItem.setAttribute("observes", aWrapper.getAttribute("itemobserves"));
   1.859 +    }
   1.860 +
   1.861 +    if (aWrapper.hasAttribute("itemchecked")) {
   1.862 +      toolbarItem.checked = true;
   1.863 +    }
   1.864 +
   1.865 +    if (aWrapper.hasAttribute("itemcommand")) {
   1.866 +      let commandID = aWrapper.getAttribute("itemcommand");
   1.867 +      toolbarItem.setAttribute("command", commandID);
   1.868 +
   1.869 +      //XXX Bug 309953 - toolbarbuttons aren't in sync with their commands after customizing
   1.870 +      let command = this.document.getElementById(commandID);
   1.871 +      if (command && command.hasAttribute("disabled")) {
   1.872 +        toolbarItem.setAttribute("disabled", command.getAttribute("disabled"));
   1.873 +      }
   1.874 +    }
   1.875 +
   1.876 +    let wrappedContext = toolbarItem.getAttribute("wrapped-context");
   1.877 +    if (wrappedContext) {
   1.878 +      let contextAttrName = toolbarItem.getAttribute("wrapped-contextAttrName");
   1.879 +      toolbarItem.setAttribute(contextAttrName, wrappedContext);
   1.880 +      toolbarItem.removeAttribute("wrapped-contextAttrName");
   1.881 +      toolbarItem.removeAttribute("wrapped-context");
   1.882 +    } else if (place == "panel") {
   1.883 +      toolbarItem.setAttribute("context", kPanelItemContextMenu);
   1.884 +    }
   1.885 +
   1.886 +    if (aWrapper.parentNode) {
   1.887 +      aWrapper.parentNode.replaceChild(toolbarItem, aWrapper);
   1.888 +    }
   1.889 +    return toolbarItem;
   1.890 +  },
   1.891 +
   1.892 +  _wrapToolbarItems: function() {
   1.893 +    let window = this.window;
   1.894 +    // Add drag-and-drop event handlers to all of the customizable areas.
   1.895 +    return Task.spawn(function() {
   1.896 +      this.areas = [];
   1.897 +      for (let area of CustomizableUI.areas) {
   1.898 +        let target = CustomizableUI.getCustomizeTargetForArea(area, window);
   1.899 +        this._addDragHandlers(target);
   1.900 +        for (let child of target.children) {
   1.901 +          if (this.isCustomizableItem(child)) {
   1.902 +            yield this.deferredWrapToolbarItem(child, CustomizableUI.getPlaceForItem(child));
   1.903 +          }
   1.904 +        }
   1.905 +        this.areas.push(target);
   1.906 +      }
   1.907 +    }.bind(this)).then(null, ERROR);
   1.908 +  },
   1.909 +
   1.910 +  _addDragHandlers: function(aTarget) {
   1.911 +    aTarget.addEventListener("dragstart", this, true);
   1.912 +    aTarget.addEventListener("dragover", this, true);
   1.913 +    aTarget.addEventListener("dragexit", this, true);
   1.914 +    aTarget.addEventListener("drop", this, true);
   1.915 +    aTarget.addEventListener("dragend", this, true);
   1.916 +  },
   1.917 +
   1.918 +  _wrapItemsInArea: function(target) {
   1.919 +    for (let child of target.children) {
   1.920 +      if (this.isCustomizableItem(child)) {
   1.921 +        this.wrapToolbarItem(child, CustomizableUI.getPlaceForItem(child));
   1.922 +      }
   1.923 +    }
   1.924 +  },
   1.925 +
   1.926 +  _removeDragHandlers: function(aTarget) {
   1.927 +    aTarget.removeEventListener("dragstart", this, true);
   1.928 +    aTarget.removeEventListener("dragover", this, true);
   1.929 +    aTarget.removeEventListener("dragexit", this, true);
   1.930 +    aTarget.removeEventListener("drop", this, true);
   1.931 +    aTarget.removeEventListener("dragend", this, true);
   1.932 +  },
   1.933 +
   1.934 +  _unwrapItemsInArea: function(target) {
   1.935 +    for (let toolbarItem of target.children) {
   1.936 +      if (this.isWrappedToolbarItem(toolbarItem)) {
   1.937 +        this.unwrapToolbarItem(toolbarItem);
   1.938 +      }
   1.939 +    }
   1.940 +  },
   1.941 +
   1.942 +  _unwrapToolbarItems: function() {
   1.943 +    return Task.spawn(function() {
   1.944 +      for (let target of this.areas) {
   1.945 +        for (let toolbarItem of target.children) {
   1.946 +          if (this.isWrappedToolbarItem(toolbarItem)) {
   1.947 +            yield this.deferredUnwrapToolbarItem(toolbarItem);
   1.948 +          }
   1.949 +        }
   1.950 +        this._removeDragHandlers(target);
   1.951 +      }
   1.952 +    }.bind(this)).then(null, ERROR);
   1.953 +  },
   1.954 +
   1.955 +  _removeExtraToolbarsIfEmpty: function() {
   1.956 +    let toolbox = this.window.gNavToolbox;
   1.957 +    for (let child of toolbox.children) {
   1.958 +      if (child.hasAttribute("customindex")) {
   1.959 +        let placements = CustomizableUI.getWidgetIdsInArea(child.id);
   1.960 +        if (!placements.length) {
   1.961 +          CustomizableUI.removeExtraToolbar(child.id);
   1.962 +        }
   1.963 +      }
   1.964 +    }
   1.965 +  },
   1.966 +
   1.967 +  persistCurrentSets: function(aSetBeforePersisting)  {
   1.968 +    let document = this.document;
   1.969 +    let toolbars = document.querySelectorAll("toolbar[customizable='true'][currentset]");
   1.970 +    for (let toolbar of toolbars) {
   1.971 +      if (aSetBeforePersisting) {
   1.972 +        let set = toolbar.currentSet;
   1.973 +        toolbar.setAttribute("currentset", set);
   1.974 +      }
   1.975 +      // Persist the currentset attribute directly on hardcoded toolbars.
   1.976 +      document.persist(toolbar.id, "currentset");
   1.977 +    }
   1.978 +  },
   1.979 +
   1.980 +  reset: function() {
   1.981 +    this.resetting = true;
   1.982 +    // Disable the reset button temporarily while resetting:
   1.983 +    let btn = this.document.getElementById("customization-reset-button");
   1.984 +    BrowserUITelemetry.countCustomizationEvent("reset");
   1.985 +    btn.disabled = true;
   1.986 +    return Task.spawn(function() {
   1.987 +      this._removePanelCustomizationPlaceholders();
   1.988 +      yield this.depopulatePalette();
   1.989 +      yield this._unwrapToolbarItems();
   1.990 +
   1.991 +      CustomizableUI.reset();
   1.992 +
   1.993 +      yield this._wrapToolbarItems();
   1.994 +      this.populatePalette();
   1.995 +
   1.996 +      this.persistCurrentSets(true);
   1.997 +
   1.998 +      this._updateResetButton();
   1.999 +      this._updateUndoResetButton();
  1.1000 +      this._updateEmptyPaletteNotice();
  1.1001 +      this._showPanelCustomizationPlaceholders();
  1.1002 +      this.resetting = false;
  1.1003 +      if (!this._wantToBeInCustomizeMode) {
  1.1004 +        this.exit();
  1.1005 +      }
  1.1006 +    }.bind(this)).then(null, ERROR);
  1.1007 +  },
  1.1008 +
  1.1009 +  undoReset: function() {
  1.1010 +    this.resetting = true;
  1.1011 +
  1.1012 +    return Task.spawn(function() {
  1.1013 +      this._removePanelCustomizationPlaceholders();
  1.1014 +      yield this.depopulatePalette();
  1.1015 +      yield this._unwrapToolbarItems();
  1.1016 +
  1.1017 +      CustomizableUI.undoReset();
  1.1018 +
  1.1019 +      yield this._wrapToolbarItems();
  1.1020 +      this.populatePalette();
  1.1021 +
  1.1022 +      this.persistCurrentSets(true);
  1.1023 +
  1.1024 +      this._updateResetButton();
  1.1025 +      this._updateUndoResetButton();
  1.1026 +      this._updateEmptyPaletteNotice();
  1.1027 +      this.resetting = false;
  1.1028 +    }.bind(this)).then(null, ERROR);
  1.1029 +  },
  1.1030 +
  1.1031 +  _onToolbarVisibilityChange: function(aEvent) {
  1.1032 +    let toolbar = aEvent.target;
  1.1033 +    if (aEvent.detail.visible && toolbar.getAttribute("customizable") == "true") {
  1.1034 +      toolbar.setAttribute("customizing", "true");
  1.1035 +    } else {
  1.1036 +      toolbar.removeAttribute("customizing");
  1.1037 +    }
  1.1038 +    this._onUIChange();
  1.1039 +  },
  1.1040 +
  1.1041 +  onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) {
  1.1042 +    this._onUIChange();
  1.1043 +  },
  1.1044 +
  1.1045 +  onWidgetAdded: function(aWidgetId, aArea, aPosition) {
  1.1046 +    this._onUIChange();
  1.1047 +  },
  1.1048 +
  1.1049 +  onWidgetRemoved: function(aWidgetId, aArea) {
  1.1050 +    this._onUIChange();
  1.1051 +  },
  1.1052 +
  1.1053 +  onWidgetBeforeDOMChange: function(aNodeToChange, aSecondaryNode, aContainer) {
  1.1054 +    if (aContainer.ownerDocument.defaultView != this.window || this.resetting) {
  1.1055 +      return;
  1.1056 +    }
  1.1057 +    if (aContainer.id == CustomizableUI.AREA_PANEL) {
  1.1058 +      this._removePanelCustomizationPlaceholders();
  1.1059 +    }
  1.1060 +    // If we get called for widgets that aren't in the window yet, they might not have
  1.1061 +    // a parentNode at all.
  1.1062 +    if (aNodeToChange.parentNode) {
  1.1063 +      this.unwrapToolbarItem(aNodeToChange.parentNode);
  1.1064 +    }
  1.1065 +    if (aSecondaryNode) {
  1.1066 +      this.unwrapToolbarItem(aSecondaryNode.parentNode);
  1.1067 +    }
  1.1068 +  },
  1.1069 +
  1.1070 +  onWidgetAfterDOMChange: function(aNodeToChange, aSecondaryNode, aContainer) {
  1.1071 +    if (aContainer.ownerDocument.defaultView != this.window || this.resetting) {
  1.1072 +      return;
  1.1073 +    }
  1.1074 +    // If the node is still attached to the container, wrap it again:
  1.1075 +    if (aNodeToChange.parentNode) {
  1.1076 +      let place = CustomizableUI.getPlaceForItem(aNodeToChange);
  1.1077 +      this.wrapToolbarItem(aNodeToChange, place);
  1.1078 +      if (aSecondaryNode) {
  1.1079 +        this.wrapToolbarItem(aSecondaryNode, place);
  1.1080 +      }
  1.1081 +    } else {
  1.1082 +      // If not, it got removed.
  1.1083 +
  1.1084 +      // If an API-based widget is removed while customizing, append it to the palette.
  1.1085 +      // The _applyDrop code itself will take care of positioning it correctly, if
  1.1086 +      // applicable. We need the code to be here so removing widgets using CustomizableUI's
  1.1087 +      // API also does the right thing (and adds it to the palette)
  1.1088 +      let widgetId = aNodeToChange.id;
  1.1089 +      let widget = CustomizableUI.getWidget(widgetId);
  1.1090 +      if (widget.provider == CustomizableUI.PROVIDER_API) {
  1.1091 +        let paletteItem = this.makePaletteItem(widget, "palette");
  1.1092 +        this.visiblePalette.appendChild(paletteItem);
  1.1093 +      }
  1.1094 +    }
  1.1095 +    if (aContainer.id == CustomizableUI.AREA_PANEL) {
  1.1096 +      this._showPanelCustomizationPlaceholders();
  1.1097 +    }
  1.1098 +  },
  1.1099 +
  1.1100 +  onWidgetDestroyed: function(aWidgetId) {
  1.1101 +    let wrapper = this.document.getElementById("wrapper-" + aWidgetId);
  1.1102 +    if (wrapper) {
  1.1103 +      let wasInPanel = wrapper.parentNode == this.panelUIContents;
  1.1104 +      wrapper.remove();
  1.1105 +      if (wasInPanel) {
  1.1106 +        this._showPanelCustomizationPlaceholders();
  1.1107 +      }
  1.1108 +    }
  1.1109 +  },
  1.1110 +
  1.1111 +  onWidgetAfterCreation: function(aWidgetId, aArea) {
  1.1112 +    // If the node was added to an area, we would have gotten an onWidgetAdded notification,
  1.1113 +    // plus associated DOM change notifications, so only do stuff for the palette:
  1.1114 +    if (!aArea) {
  1.1115 +      let widgetNode = this.document.getElementById(aWidgetId);
  1.1116 +      if (widgetNode) {
  1.1117 +        this.wrapToolbarItem(widgetNode, "palette");
  1.1118 +      } else {
  1.1119 +        let widget = CustomizableUI.getWidget(aWidgetId);
  1.1120 +        this.visiblePalette.appendChild(this.makePaletteItem(widget, "palette"));
  1.1121 +      }
  1.1122 +    }
  1.1123 +  },
  1.1124 +
  1.1125 +  onAreaNodeRegistered: function(aArea, aContainer) {
  1.1126 +    if (aContainer.ownerDocument == this.document) {
  1.1127 +      this._wrapItemsInArea(aContainer);
  1.1128 +      this._addDragHandlers(aContainer);
  1.1129 +      DragPositionManager.add(this.window, aArea, aContainer);
  1.1130 +      this.areas.push(aContainer);
  1.1131 +    }
  1.1132 +  },
  1.1133 +
  1.1134 +  onAreaNodeUnregistered: function(aArea, aContainer, aReason) {
  1.1135 +    if (aContainer.ownerDocument == this.document && aReason == CustomizableUI.REASON_AREA_UNREGISTERED) {
  1.1136 +      this._unwrapItemsInArea(aContainer);
  1.1137 +      this._removeDragHandlers(aContainer);
  1.1138 +      DragPositionManager.remove(this.window, aArea, aContainer);
  1.1139 +      let index = this.areas.indexOf(aContainer);
  1.1140 +      this.areas.splice(index, 1);
  1.1141 +    }
  1.1142 +  },
  1.1143 +
  1.1144 +  _onUIChange: function() {
  1.1145 +    this._changed = true;
  1.1146 +    if (!this.resetting) {
  1.1147 +      this._updateResetButton();
  1.1148 +      this._updateUndoResetButton();
  1.1149 +      this._updateEmptyPaletteNotice();
  1.1150 +    }
  1.1151 +    CustomizableUI.dispatchToolboxEvent("customizationchange");
  1.1152 +  },
  1.1153 +
  1.1154 +  _updateEmptyPaletteNotice: function() {
  1.1155 +    let paletteItems = this.visiblePalette.getElementsByTagName("toolbarpaletteitem");
  1.1156 +    this.paletteEmptyNotice.hidden = !!paletteItems.length;
  1.1157 +  },
  1.1158 +
  1.1159 +  _updateResetButton: function() {
  1.1160 +    let btn = this.document.getElementById("customization-reset-button");
  1.1161 +    btn.disabled = CustomizableUI.inDefaultState;
  1.1162 +  },
  1.1163 +
  1.1164 +  _updateUndoResetButton: function() {
  1.1165 +    let undoResetButton =  this.document.getElementById("customization-undo-reset-button");
  1.1166 +    undoResetButton.hidden = !CustomizableUI.canUndoReset;
  1.1167 +  },
  1.1168 +
  1.1169 +  handleEvent: function(aEvent) {
  1.1170 +    switch(aEvent.type) {
  1.1171 +      case "toolbarvisibilitychange":
  1.1172 +        this._onToolbarVisibilityChange(aEvent);
  1.1173 +        break;
  1.1174 +      case "dragstart":
  1.1175 +        this._onDragStart(aEvent);
  1.1176 +        break;
  1.1177 +      case "dragover":
  1.1178 +        this._onDragOver(aEvent);
  1.1179 +        break;
  1.1180 +      case "drop":
  1.1181 +        this._onDragDrop(aEvent);
  1.1182 +        break;
  1.1183 +      case "dragexit":
  1.1184 +        this._onDragExit(aEvent);
  1.1185 +        break;
  1.1186 +      case "dragend":
  1.1187 +        this._onDragEnd(aEvent);
  1.1188 +        break;
  1.1189 +      case "command":
  1.1190 +        if (aEvent.originalTarget == this.window.PanelUI.menuButton) {
  1.1191 +          this.exit();
  1.1192 +          aEvent.preventDefault();
  1.1193 +        }
  1.1194 +        break;
  1.1195 +      case "mousedown":
  1.1196 +        this._onMouseDown(aEvent);
  1.1197 +        break;
  1.1198 +      case "mouseup":
  1.1199 +        this._onMouseUp(aEvent);
  1.1200 +        break;
  1.1201 +      case "keypress":
  1.1202 +        if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
  1.1203 +          this.exit();
  1.1204 +        }
  1.1205 +        break;
  1.1206 +#ifdef CAN_DRAW_IN_TITLEBAR
  1.1207 +      case "unload":
  1.1208 +        this.uninit();
  1.1209 +        break;
  1.1210 +#endif
  1.1211 +    }
  1.1212 +  },
  1.1213 +
  1.1214 +#ifdef CAN_DRAW_IN_TITLEBAR
  1.1215 +  observe: function(aSubject, aTopic, aData) {
  1.1216 +    switch (aTopic) {
  1.1217 +      case "nsPref:changed":
  1.1218 +        this._updateResetButton();
  1.1219 +        this._updateTitlebarButton();
  1.1220 +        this._updateUndoResetButton();
  1.1221 +        break;
  1.1222 +    }
  1.1223 +  },
  1.1224 +
  1.1225 +  _updateTitlebarButton: function() {
  1.1226 +    let drawInTitlebar = true;
  1.1227 +    try {
  1.1228 +      drawInTitlebar = Services.prefs.getBoolPref(kDrawInTitlebarPref);
  1.1229 +    } catch (ex) { }
  1.1230 +    let button = this.document.getElementById("customization-titlebar-visibility-button");
  1.1231 +    // Drawing in the titlebar means 'hiding' the titlebar:
  1.1232 +    if (drawInTitlebar) {
  1.1233 +      button.removeAttribute("checked");
  1.1234 +    } else {
  1.1235 +      button.setAttribute("checked", "true");
  1.1236 +    }
  1.1237 +  },
  1.1238 +
  1.1239 +  toggleTitlebar: function(aShouldShowTitlebar) {
  1.1240 +    // Drawing in the titlebar means not showing the titlebar, hence the negation:
  1.1241 +    Services.prefs.setBoolPref(kDrawInTitlebarPref, !aShouldShowTitlebar);
  1.1242 +  },
  1.1243 +#endif
  1.1244 +
  1.1245 +  _onDragStart: function(aEvent) {
  1.1246 +    __dumpDragData(aEvent);
  1.1247 +    let item = aEvent.target;
  1.1248 +    while (item && item.localName != "toolbarpaletteitem") {
  1.1249 +      if (item.localName == "toolbar") {
  1.1250 +        return;
  1.1251 +      }
  1.1252 +      item = item.parentNode;
  1.1253 +    }
  1.1254 +
  1.1255 +    let draggedItem = item.firstChild;
  1.1256 +    let placeForItem = CustomizableUI.getPlaceForItem(item);
  1.1257 +    let isRemovable = placeForItem == "palette" ||
  1.1258 +                      CustomizableUI.isWidgetRemovable(draggedItem);
  1.1259 +    if (item.classList.contains(kPlaceholderClass) || !isRemovable) {
  1.1260 +      return;
  1.1261 +    }
  1.1262 +
  1.1263 +    let dt = aEvent.dataTransfer;
  1.1264 +    let documentId = aEvent.target.ownerDocument.documentElement.id;
  1.1265 +    let isInToolbar = placeForItem == "toolbar";
  1.1266 +
  1.1267 +    dt.mozSetDataAt(kDragDataTypePrefix + documentId, draggedItem.id, 0);
  1.1268 +    dt.effectAllowed = "move";
  1.1269 +
  1.1270 +    let itemRect = draggedItem.getBoundingClientRect();
  1.1271 +    let itemCenter = {x: itemRect.left + itemRect.width / 2,
  1.1272 +                      y: itemRect.top + itemRect.height / 2};
  1.1273 +    this._dragOffset = {x: aEvent.clientX - itemCenter.x,
  1.1274 +                        y: aEvent.clientY - itemCenter.y};
  1.1275 +
  1.1276 +    gDraggingInToolbars = new Set();
  1.1277 +
  1.1278 +    // Hack needed so that the dragimage will still show the
  1.1279 +    // item as it appeared before it was hidden.
  1.1280 +    this._initializeDragAfterMove = function() {
  1.1281 +      // For automated tests, we sometimes start exiting customization mode
  1.1282 +      // before this fires, which leaves us with placeholders inserted after
  1.1283 +      // we've exited. So we need to check that we are indeed customizing.
  1.1284 +      if (this._customizing && !this._transitioning) {
  1.1285 +        item.hidden = true;
  1.1286 +        this._showPanelCustomizationPlaceholders();
  1.1287 +        DragPositionManager.start(this.window);
  1.1288 +        if (item.nextSibling) {
  1.1289 +          this._setDragActive(item.nextSibling, "before", draggedItem.id, isInToolbar);
  1.1290 +          this._dragOverItem = item.nextSibling;
  1.1291 +        } else if (isInToolbar && item.previousSibling) {
  1.1292 +          this._setDragActive(item.previousSibling, "after", draggedItem.id, isInToolbar);
  1.1293 +          this._dragOverItem = item.previousSibling;
  1.1294 +        }
  1.1295 +      }
  1.1296 +      this._initializeDragAfterMove = null;
  1.1297 +      this.window.clearTimeout(this._dragInitializeTimeout);
  1.1298 +    }.bind(this);
  1.1299 +    this._dragInitializeTimeout = this.window.setTimeout(this._initializeDragAfterMove, 0);
  1.1300 +  },
  1.1301 +
  1.1302 +  _onDragOver: function(aEvent) {
  1.1303 +    if (this._isUnwantedDragDrop(aEvent)) {
  1.1304 +      return;
  1.1305 +    }
  1.1306 +    if (this._initializeDragAfterMove) {
  1.1307 +      this._initializeDragAfterMove();
  1.1308 +    }
  1.1309 +
  1.1310 +    __dumpDragData(aEvent);
  1.1311 +
  1.1312 +    let document = aEvent.target.ownerDocument;
  1.1313 +    let documentId = document.documentElement.id;
  1.1314 +    if (!aEvent.dataTransfer.mozTypesAt(0)) {
  1.1315 +      return;
  1.1316 +    }
  1.1317 +
  1.1318 +    let draggedItemId =
  1.1319 +      aEvent.dataTransfer.mozGetDataAt(kDragDataTypePrefix + documentId, 0);
  1.1320 +    let draggedWrapper = document.getElementById("wrapper-" + draggedItemId);
  1.1321 +    let targetArea = this._getCustomizableParent(aEvent.currentTarget);
  1.1322 +    let originArea = this._getCustomizableParent(draggedWrapper);
  1.1323 +
  1.1324 +    // Do nothing if the target or origin are not customizable.
  1.1325 +    if (!targetArea || !originArea) {
  1.1326 +      return;
  1.1327 +    }
  1.1328 +
  1.1329 +    // Do nothing if the widget is not allowed to be removed.
  1.1330 +    if (targetArea.id == kPaletteId &&
  1.1331 +       !CustomizableUI.isWidgetRemovable(draggedItemId)) {
  1.1332 +      return;
  1.1333 +    }
  1.1334 +
  1.1335 +    // Do nothing if the widget is not allowed to move to the target area.
  1.1336 +    if (targetArea.id != kPaletteId &&
  1.1337 +        !CustomizableUI.canWidgetMoveToArea(draggedItemId, targetArea.id)) {
  1.1338 +      return;
  1.1339 +    }
  1.1340 +
  1.1341 +    let targetIsToolbar = CustomizableUI.getAreaType(targetArea.id) == "toolbar";
  1.1342 +    let targetNode = this._getDragOverNode(aEvent, targetArea, targetIsToolbar, draggedItemId);
  1.1343 +
  1.1344 +    // We need to determine the place that the widget is being dropped in
  1.1345 +    // the target.
  1.1346 +    let dragOverItem, dragValue;
  1.1347 +    if (targetNode == targetArea.customizationTarget) {
  1.1348 +      // We'll assume if the user is dragging directly over the target, that
  1.1349 +      // they're attempting to append a child to that target.
  1.1350 +      dragOverItem = (targetIsToolbar ? this._findVisiblePreviousSiblingNode(targetNode.lastChild) :
  1.1351 +                                        targetNode.lastChild) || targetNode;
  1.1352 +      dragValue = "after";
  1.1353 +    } else {
  1.1354 +      let targetParent = targetNode.parentNode;
  1.1355 +      let position = Array.indexOf(targetParent.children, targetNode);
  1.1356 +      if (position == -1) {
  1.1357 +        dragOverItem = targetIsToolbar ? this._findVisiblePreviousSiblingNode(targetNode.lastChild) :
  1.1358 +                                         targetParent.lastChild;
  1.1359 +        dragValue = "after";
  1.1360 +      } else {
  1.1361 +        dragOverItem = targetParent.children[position];
  1.1362 +        if (!targetIsToolbar) {
  1.1363 +          dragValue = "before";
  1.1364 +        } else {
  1.1365 +          dragOverItem = this._findVisiblePreviousSiblingNode(targetParent.children[position]);
  1.1366 +          // Check if the aDraggedItem is hovered past the first half of dragOverItem
  1.1367 +          let window = dragOverItem.ownerDocument.defaultView;
  1.1368 +          let direction = window.getComputedStyle(dragOverItem, null).direction;
  1.1369 +          let itemRect = dragOverItem.getBoundingClientRect();
  1.1370 +          let dropTargetCenter = itemRect.left + (itemRect.width / 2);
  1.1371 +          let existingDir = dragOverItem.getAttribute("dragover");
  1.1372 +          if ((existingDir == "before") == (direction == "ltr")) {
  1.1373 +            dropTargetCenter += (parseInt(dragOverItem.style.borderLeftWidth) || 0) / 2;
  1.1374 +          } else {
  1.1375 +            dropTargetCenter -= (parseInt(dragOverItem.style.borderRightWidth) || 0) / 2;
  1.1376 +          }
  1.1377 +          let before = direction == "ltr" ? aEvent.clientX < dropTargetCenter : aEvent.clientX > dropTargetCenter;
  1.1378 +          dragValue = before ? "before" : "after";
  1.1379 +        }
  1.1380 +      }
  1.1381 +    }
  1.1382 +
  1.1383 +    if (this._dragOverItem && dragOverItem != this._dragOverItem) {
  1.1384 +      this._cancelDragActive(this._dragOverItem, dragOverItem);
  1.1385 +    }
  1.1386 +
  1.1387 +    if (dragOverItem != this._dragOverItem || dragValue != dragOverItem.getAttribute("dragover")) {
  1.1388 +      if (dragOverItem != targetArea.customizationTarget) {
  1.1389 +        this._setDragActive(dragOverItem, dragValue, draggedItemId, targetIsToolbar);
  1.1390 +      } else if (targetIsToolbar) {
  1.1391 +        this._updateToolbarCustomizationOutline(this.window, targetArea);
  1.1392 +      }
  1.1393 +      this._dragOverItem = dragOverItem;
  1.1394 +    }
  1.1395 +
  1.1396 +    aEvent.preventDefault();
  1.1397 +    aEvent.stopPropagation();
  1.1398 +  },
  1.1399 +
  1.1400 +  _onDragDrop: function(aEvent) {
  1.1401 +    if (this._isUnwantedDragDrop(aEvent)) {
  1.1402 +      return;
  1.1403 +    }
  1.1404 +
  1.1405 +    __dumpDragData(aEvent);
  1.1406 +    this._initializeDragAfterMove = null;
  1.1407 +    this.window.clearTimeout(this._dragInitializeTimeout);
  1.1408 +
  1.1409 +    let targetArea = this._getCustomizableParent(aEvent.currentTarget);
  1.1410 +    let document = aEvent.target.ownerDocument;
  1.1411 +    let documentId = document.documentElement.id;
  1.1412 +    let draggedItemId =
  1.1413 +      aEvent.dataTransfer.mozGetDataAt(kDragDataTypePrefix + documentId, 0);
  1.1414 +    let draggedWrapper = document.getElementById("wrapper-" + draggedItemId);
  1.1415 +    let originArea = this._getCustomizableParent(draggedWrapper);
  1.1416 +    if (this._dragSizeMap) {
  1.1417 +      this._dragSizeMap.clear();
  1.1418 +    }
  1.1419 +    // Do nothing if the target area or origin area are not customizable.
  1.1420 +    if (!targetArea || !originArea) {
  1.1421 +      return;
  1.1422 +    }
  1.1423 +    let targetNode = this._dragOverItem;
  1.1424 +    let dropDir = targetNode.getAttribute("dragover");
  1.1425 +    // Need to insert *after* this node if we promised the user that:
  1.1426 +    if (targetNode != targetArea && dropDir == "after") {
  1.1427 +      if (targetNode.nextSibling) {
  1.1428 +        targetNode = targetNode.nextSibling;
  1.1429 +      } else {
  1.1430 +        targetNode = targetArea;
  1.1431 +      }
  1.1432 +    }
  1.1433 +    // If the target node is a placeholder, get its sibling as the real target.
  1.1434 +    while (targetNode.classList.contains(kPlaceholderClass) && targetNode.nextSibling) {
  1.1435 +      targetNode = targetNode.nextSibling;
  1.1436 +    }
  1.1437 +    if (targetNode.tagName == "toolbarpaletteitem") {
  1.1438 +      targetNode = targetNode.firstChild;
  1.1439 +    }
  1.1440 +
  1.1441 +    this._cancelDragActive(this._dragOverItem, null, true);
  1.1442 +    this._removePanelCustomizationPlaceholders();
  1.1443 +
  1.1444 +    try {
  1.1445 +      this._applyDrop(aEvent, targetArea, originArea, draggedItemId, targetNode);
  1.1446 +    } catch (ex) {
  1.1447 +      ERROR(ex, ex.stack);
  1.1448 +    }
  1.1449 +
  1.1450 +    this._showPanelCustomizationPlaceholders();
  1.1451 +  },
  1.1452 +
  1.1453 +  _applyDrop: function(aEvent, aTargetArea, aOriginArea, aDraggedItemId, aTargetNode) {
  1.1454 +    let document = aEvent.target.ownerDocument;
  1.1455 +    let draggedItem = document.getElementById(aDraggedItemId);
  1.1456 +    draggedItem.hidden = false;
  1.1457 +    draggedItem.removeAttribute("mousedown");
  1.1458 +
  1.1459 +    // Do nothing if the target was dropped onto itself (ie, no change in area
  1.1460 +    // or position).
  1.1461 +    if (draggedItem == aTargetNode) {
  1.1462 +      return;
  1.1463 +    }
  1.1464 +
  1.1465 +    // Is the target area the customization palette?
  1.1466 +    if (aTargetArea.id == kPaletteId) {
  1.1467 +      // Did we drag from outside the palette?
  1.1468 +      if (aOriginArea.id !== kPaletteId) {
  1.1469 +        if (!CustomizableUI.isWidgetRemovable(aDraggedItemId)) {
  1.1470 +          return;
  1.1471 +        }
  1.1472 +
  1.1473 +        CustomizableUI.removeWidgetFromArea(aDraggedItemId);
  1.1474 +        BrowserUITelemetry.countCustomizationEvent("remove");
  1.1475 +        // Special widgets are removed outright, we can return here:
  1.1476 +        if (CustomizableUI.isSpecialWidget(aDraggedItemId)) {
  1.1477 +          return;
  1.1478 +        }
  1.1479 +      }
  1.1480 +      draggedItem = draggedItem.parentNode;
  1.1481 +
  1.1482 +      // If the target node is the palette itself, just append
  1.1483 +      if (aTargetNode == this.visiblePalette) {
  1.1484 +        this.visiblePalette.appendChild(draggedItem);
  1.1485 +      } else {
  1.1486 +        // The items in the palette are wrapped, so we need the target node's parent here:
  1.1487 +        this.visiblePalette.insertBefore(draggedItem, aTargetNode.parentNode);
  1.1488 +      }
  1.1489 +      if (aOriginArea.id !== kPaletteId) {
  1.1490 +        // The dragend event already fires when the item moves within the palette.
  1.1491 +        this._onDragEnd(aEvent);
  1.1492 +      }
  1.1493 +      return;
  1.1494 +    }
  1.1495 +
  1.1496 +    if (!CustomizableUI.canWidgetMoveToArea(aDraggedItemId, aTargetArea.id)) {
  1.1497 +      return;
  1.1498 +    }
  1.1499 +
  1.1500 +    // Skipintoolbarset items won't really be moved:
  1.1501 +    if (draggedItem.getAttribute("skipintoolbarset") == "true") {
  1.1502 +      // These items should never leave their area:
  1.1503 +      if (aTargetArea != aOriginArea) {
  1.1504 +        return;
  1.1505 +      }
  1.1506 +      let place = draggedItem.parentNode.getAttribute("place");
  1.1507 +      this.unwrapToolbarItem(draggedItem.parentNode);
  1.1508 +      if (aTargetNode == aTargetArea.customizationTarget) {
  1.1509 +        aTargetArea.customizationTarget.appendChild(draggedItem);
  1.1510 +      } else {
  1.1511 +        this.unwrapToolbarItem(aTargetNode.parentNode);
  1.1512 +        aTargetArea.customizationTarget.insertBefore(draggedItem, aTargetNode);
  1.1513 +        this.wrapToolbarItem(aTargetNode, place);
  1.1514 +      }
  1.1515 +      this.wrapToolbarItem(draggedItem, place);
  1.1516 +      BrowserUITelemetry.countCustomizationEvent("move");
  1.1517 +      return;
  1.1518 +    }
  1.1519 +
  1.1520 +    // Is the target the customization area itself? If so, we just add the
  1.1521 +    // widget to the end of the area.
  1.1522 +    if (aTargetNode == aTargetArea.customizationTarget) {
  1.1523 +      CustomizableUI.addWidgetToArea(aDraggedItemId, aTargetArea.id);
  1.1524 +      // For the purposes of BrowserUITelemetry, we consider both moving a widget
  1.1525 +      // within the same area, and adding a widget from one area to another area
  1.1526 +      // as a "move". An "add" is only when we move an item from the palette into
  1.1527 +      // an area.
  1.1528 +      let custEventType = aOriginArea.id == kPaletteId ? "add" : "move";
  1.1529 +      BrowserUITelemetry.countCustomizationEvent(custEventType);
  1.1530 +      this._onDragEnd(aEvent);
  1.1531 +      return;
  1.1532 +    }
  1.1533 +
  1.1534 +    // We need to determine the place that the widget is being dropped in
  1.1535 +    // the target.
  1.1536 +    let placement;
  1.1537 +    let itemForPlacement = aTargetNode;
  1.1538 +    // Skip the skipintoolbarset items when determining the place of the item:
  1.1539 +    while (itemForPlacement && itemForPlacement.getAttribute("skipintoolbarset") == "true" &&
  1.1540 +           itemForPlacement.parentNode &&
  1.1541 +           itemForPlacement.parentNode.nodeName == "toolbarpaletteitem") {
  1.1542 +      itemForPlacement = itemForPlacement.parentNode.nextSibling;
  1.1543 +      if (itemForPlacement && itemForPlacement.nodeName == "toolbarpaletteitem") {
  1.1544 +        itemForPlacement = itemForPlacement.firstChild;
  1.1545 +      }
  1.1546 +    }
  1.1547 +    if (itemForPlacement && !itemForPlacement.classList.contains(kPlaceholderClass)) {
  1.1548 +      let targetNodeId = (itemForPlacement.nodeName == "toolbarpaletteitem") ?
  1.1549 +                            itemForPlacement.firstChild && itemForPlacement.firstChild.id :
  1.1550 +                            itemForPlacement.id;
  1.1551 +      placement = CustomizableUI.getPlacementOfWidget(targetNodeId);
  1.1552 +    }
  1.1553 +    if (!placement) {
  1.1554 +      LOG("Could not get a position for " + aTargetNode.nodeName + "#" + aTargetNode.id + "." + aTargetNode.className);
  1.1555 +    }
  1.1556 +    let position = placement ? placement.position : null;
  1.1557 +
  1.1558 +    // Is the target area the same as the origin? Since we've already handled
  1.1559 +    // the possibility that the target is the customization palette, we know
  1.1560 +    // that the widget is moving within a customizable area.
  1.1561 +    if (aTargetArea == aOriginArea) {
  1.1562 +      CustomizableUI.moveWidgetWithinArea(aDraggedItemId, position);
  1.1563 +    } else {
  1.1564 +      CustomizableUI.addWidgetToArea(aDraggedItemId, aTargetArea.id, position);
  1.1565 +    }
  1.1566 +
  1.1567 +    this._onDragEnd(aEvent);
  1.1568 +
  1.1569 +    // For BrowserUITelemetry, an "add" is only when we move an item from the palette
  1.1570 +    // into an area. Otherwise, it's a move.
  1.1571 +    let custEventType = aOriginArea.id == kPaletteId ? "add" : "move";
  1.1572 +    BrowserUITelemetry.countCustomizationEvent(custEventType);
  1.1573 +
  1.1574 +    // If we dropped onto a skipintoolbarset item, manually correct the drop location:
  1.1575 +    if (aTargetNode != itemForPlacement) {
  1.1576 +      let draggedWrapper = draggedItem.parentNode;
  1.1577 +      let container = draggedWrapper.parentNode;
  1.1578 +      container.insertBefore(draggedWrapper, aTargetNode.parentNode);
  1.1579 +    }
  1.1580 +  },
  1.1581 +
  1.1582 +  _onDragExit: function(aEvent) {
  1.1583 +    if (this._isUnwantedDragDrop(aEvent)) {
  1.1584 +      return;
  1.1585 +    }
  1.1586 +
  1.1587 +    __dumpDragData(aEvent);
  1.1588 +
  1.1589 +    // When leaving customization areas, cancel the drag on the last dragover item
  1.1590 +    // We've attached the listener to areas, so aEvent.currentTarget will be the area.
  1.1591 +    // We don't care about dragexit events fired on descendants of the area,
  1.1592 +    // so we check that the event's target is the same as the area to which the listener
  1.1593 +    // was attached.
  1.1594 +    if (this._dragOverItem && aEvent.target == aEvent.currentTarget) {
  1.1595 +      this._cancelDragActive(this._dragOverItem);
  1.1596 +      this._dragOverItem = null;
  1.1597 +    }
  1.1598 +  },
  1.1599 +
  1.1600 +  /**
  1.1601 +   * To workaround bug 460801 we manually forward the drop event here when dragend wouldn't be fired.
  1.1602 +   */
  1.1603 +  _onDragEnd: function(aEvent) {
  1.1604 +    if (this._isUnwantedDragDrop(aEvent)) {
  1.1605 +      return;
  1.1606 +    }
  1.1607 +    this._initializeDragAfterMove = null;
  1.1608 +    this.window.clearTimeout(this._dragInitializeTimeout);
  1.1609 +    __dumpDragData(aEvent, "_onDragEnd");
  1.1610 +
  1.1611 +    let document = aEvent.target.ownerDocument;
  1.1612 +    document.documentElement.removeAttribute("customizing-movingItem");
  1.1613 +
  1.1614 +    let documentId = document.documentElement.id;
  1.1615 +    if (!aEvent.dataTransfer.mozTypesAt(0)) {
  1.1616 +      return;
  1.1617 +    }
  1.1618 +
  1.1619 +    let draggedItemId =
  1.1620 +      aEvent.dataTransfer.mozGetDataAt(kDragDataTypePrefix + documentId, 0);
  1.1621 +
  1.1622 +    let draggedWrapper = document.getElementById("wrapper-" + draggedItemId);
  1.1623 +    draggedWrapper.hidden = false;
  1.1624 +    draggedWrapper.removeAttribute("mousedown");
  1.1625 +    if (this._dragOverItem) {
  1.1626 +      this._cancelDragActive(this._dragOverItem);
  1.1627 +      this._dragOverItem = null;
  1.1628 +    }
  1.1629 +    this._updateToolbarCustomizationOutline(this.window);
  1.1630 +    this._showPanelCustomizationPlaceholders();
  1.1631 +    DragPositionManager.stop();
  1.1632 +  },
  1.1633 +
  1.1634 +  _isUnwantedDragDrop: function(aEvent) {
  1.1635 +    // The simulated events generated by synthesizeDragStart/synthesizeDrop in
  1.1636 +    // mochitests are used only for testing whether the right data is being put
  1.1637 +    // into the dataTransfer. Neither cause a real drop to occur, so they don't
  1.1638 +    // set the source node. There isn't a means of testing real drag and drops,
  1.1639 +    // so this pref skips the check but it should only be set by test code.
  1.1640 +    if (this._skipSourceNodeCheck) {
  1.1641 +      return false;
  1.1642 +    }
  1.1643 +
  1.1644 +    /* Discard drag events that originated from a separate window to
  1.1645 +       prevent content->chrome privilege escalations. */
  1.1646 +    let mozSourceNode = aEvent.dataTransfer.mozSourceNode;
  1.1647 +    // mozSourceNode is null in the dragStart event handler or if
  1.1648 +    // the drag event originated in an external application.
  1.1649 +    return !mozSourceNode ||
  1.1650 +           mozSourceNode.ownerDocument.defaultView != this.window;
  1.1651 +  },
  1.1652 +
  1.1653 +  _setDragActive: function(aItem, aValue, aDraggedItemId, aInToolbar) {
  1.1654 +    if (!aItem) {
  1.1655 +      return;
  1.1656 +    }
  1.1657 +
  1.1658 +    if (aItem.getAttribute("dragover") != aValue) {
  1.1659 +      aItem.setAttribute("dragover", aValue);
  1.1660 +
  1.1661 +      let window = aItem.ownerDocument.defaultView;
  1.1662 +      let draggedItem = window.document.getElementById(aDraggedItemId);
  1.1663 +      if (!aInToolbar) {
  1.1664 +        this._setGridDragActive(aItem, draggedItem, aValue);
  1.1665 +      } else {
  1.1666 +        let targetArea = this._getCustomizableParent(aItem);
  1.1667 +        this._updateToolbarCustomizationOutline(window, targetArea);
  1.1668 +        let makeSpaceImmediately = false;
  1.1669 +        if (!gDraggingInToolbars.has(targetArea.id)) {
  1.1670 +          gDraggingInToolbars.add(targetArea.id);
  1.1671 +          let draggedWrapper = this.document.getElementById("wrapper-" + aDraggedItemId);
  1.1672 +          let originArea = this._getCustomizableParent(draggedWrapper);
  1.1673 +          makeSpaceImmediately = originArea == targetArea;
  1.1674 +        }
  1.1675 +        // Calculate width of the item when it'd be dropped in this position
  1.1676 +        let width = this._getDragItemSize(aItem, draggedItem).width;
  1.1677 +        let direction = window.getComputedStyle(aItem).direction;
  1.1678 +        let prop, otherProp;
  1.1679 +        // If we're inserting before in ltr, or after in rtl:
  1.1680 +        if ((aValue == "before") == (direction == "ltr")) {
  1.1681 +          prop = "borderLeftWidth";
  1.1682 +          otherProp = "border-right-width";
  1.1683 +        } else {
  1.1684 +          // otherwise:
  1.1685 +          prop = "borderRightWidth";
  1.1686 +          otherProp = "border-left-width";
  1.1687 +        }
  1.1688 +        if (makeSpaceImmediately) {
  1.1689 +          aItem.setAttribute("notransition", "true");
  1.1690 +        }
  1.1691 +        aItem.style[prop] = width + 'px';
  1.1692 +        aItem.style.removeProperty(otherProp);
  1.1693 +        if (makeSpaceImmediately) {
  1.1694 +          // Force a layout flush:
  1.1695 +          aItem.getBoundingClientRect();
  1.1696 +          aItem.removeAttribute("notransition");
  1.1697 +        }
  1.1698 +      }
  1.1699 +    }
  1.1700 +  },
  1.1701 +  _cancelDragActive: function(aItem, aNextItem, aNoTransition) {
  1.1702 +    this._updateToolbarCustomizationOutline(aItem.ownerDocument.defaultView);
  1.1703 +    let currentArea = this._getCustomizableParent(aItem);
  1.1704 +    if (!currentArea) {
  1.1705 +      return;
  1.1706 +    }
  1.1707 +    let isToolbar = CustomizableUI.getAreaType(currentArea.id) == "toolbar";
  1.1708 +    if (isToolbar) {
  1.1709 +      if (aNoTransition) {
  1.1710 +        aItem.setAttribute("notransition", "true");
  1.1711 +      }
  1.1712 +      aItem.removeAttribute("dragover");
  1.1713 +      // Remove both property values in the case that the end padding
  1.1714 +      // had been set.
  1.1715 +      aItem.style.removeProperty("border-left-width");
  1.1716 +      aItem.style.removeProperty("border-right-width");
  1.1717 +      if (aNoTransition) {
  1.1718 +        // Force a layout flush:
  1.1719 +        aItem.getBoundingClientRect();
  1.1720 +        aItem.removeAttribute("notransition");
  1.1721 +      }
  1.1722 +    } else  {
  1.1723 +      aItem.removeAttribute("dragover");
  1.1724 +      if (aNextItem) {
  1.1725 +        let nextArea = this._getCustomizableParent(aNextItem);
  1.1726 +        if (nextArea == currentArea) {
  1.1727 +          // No need to do anything if we're still dragging in this area:
  1.1728 +          return;
  1.1729 +        }
  1.1730 +      }
  1.1731 +      // Otherwise, clear everything out:
  1.1732 +      let positionManager = DragPositionManager.getManagerForArea(currentArea);
  1.1733 +      positionManager.clearPlaceholders(currentArea, aNoTransition);
  1.1734 +    }
  1.1735 +  },
  1.1736 +
  1.1737 +  _setGridDragActive: function(aDragOverNode, aDraggedItem, aValue) {
  1.1738 +    let targetArea = this._getCustomizableParent(aDragOverNode);
  1.1739 +    let draggedWrapper = this.document.getElementById("wrapper-" + aDraggedItem.id);
  1.1740 +    let originArea = this._getCustomizableParent(draggedWrapper);
  1.1741 +    let positionManager = DragPositionManager.getManagerForArea(targetArea);
  1.1742 +    let draggedSize = this._getDragItemSize(aDragOverNode, aDraggedItem);
  1.1743 +    let isWide = aDraggedItem.classList.contains(CustomizableUI.WIDE_PANEL_CLASS);
  1.1744 +    positionManager.insertPlaceholder(targetArea, aDragOverNode, isWide, draggedSize,
  1.1745 +                                      originArea == targetArea);
  1.1746 +  },
  1.1747 +
  1.1748 +  _getDragItemSize: function(aDragOverNode, aDraggedItem) {
  1.1749 +    // Cache it good, cache it real good.
  1.1750 +    if (!this._dragSizeMap)
  1.1751 +      this._dragSizeMap = new WeakMap();
  1.1752 +    if (!this._dragSizeMap.has(aDraggedItem))
  1.1753 +      this._dragSizeMap.set(aDraggedItem, new WeakMap());
  1.1754 +    let itemMap = this._dragSizeMap.get(aDraggedItem);
  1.1755 +    let targetArea = this._getCustomizableParent(aDragOverNode);
  1.1756 +    let currentArea = this._getCustomizableParent(aDraggedItem);
  1.1757 +    // Return the size for this target from cache, if it exists.
  1.1758 +    let size = itemMap.get(targetArea);
  1.1759 +    if (size)
  1.1760 +      return size;
  1.1761 +
  1.1762 +    // Calculate size of the item when it'd be dropped in this position.
  1.1763 +    let currentParent = aDraggedItem.parentNode;
  1.1764 +    let currentSibling = aDraggedItem.nextSibling;
  1.1765 +    const kAreaType = "cui-areatype";
  1.1766 +    let areaType, currentType;
  1.1767 +
  1.1768 +    if (targetArea != currentArea) {
  1.1769 +      // Move the widget temporarily next to the placeholder.
  1.1770 +      aDragOverNode.parentNode.insertBefore(aDraggedItem, aDragOverNode);
  1.1771 +      // Update the node's areaType.
  1.1772 +      areaType = CustomizableUI.getAreaType(targetArea.id);
  1.1773 +      currentType = aDraggedItem.hasAttribute(kAreaType) &&
  1.1774 +                    aDraggedItem.getAttribute(kAreaType);
  1.1775 +      if (areaType)
  1.1776 +        aDraggedItem.setAttribute(kAreaType, areaType);
  1.1777 +      this.wrapToolbarItem(aDraggedItem, areaType || "palette");
  1.1778 +      CustomizableUI.onWidgetDrag(aDraggedItem.id, targetArea.id);
  1.1779 +    } else {
  1.1780 +      aDraggedItem.parentNode.hidden = false;
  1.1781 +    }
  1.1782 +
  1.1783 +    // Fetch the new size.
  1.1784 +    let rect = aDraggedItem.parentNode.getBoundingClientRect();
  1.1785 +    size = {width: rect.width, height: rect.height};
  1.1786 +    // Cache the found value of size for this target.
  1.1787 +    itemMap.set(targetArea, size);
  1.1788 +
  1.1789 +    if (targetArea != currentArea) {
  1.1790 +      this.unwrapToolbarItem(aDraggedItem.parentNode);
  1.1791 +      // Put the item back into its previous position.
  1.1792 +      currentParent.insertBefore(aDraggedItem, currentSibling);
  1.1793 +      // restore the areaType
  1.1794 +      if (areaType) {
  1.1795 +        if (currentType === false)
  1.1796 +          aDraggedItem.removeAttribute(kAreaType);
  1.1797 +        else
  1.1798 +          aDraggedItem.setAttribute(kAreaType, currentType);
  1.1799 +      }
  1.1800 +      this.createOrUpdateWrapper(aDraggedItem, null, true);
  1.1801 +      CustomizableUI.onWidgetDrag(aDraggedItem.id);
  1.1802 +    } else {
  1.1803 +      aDraggedItem.parentNode.hidden = true;
  1.1804 +    }
  1.1805 +    return size;
  1.1806 +  },
  1.1807 +
  1.1808 +  _getCustomizableParent: function(aElement) {
  1.1809 +    let areas = CustomizableUI.areas;
  1.1810 +    areas.push(kPaletteId);
  1.1811 +    while (aElement) {
  1.1812 +      if (areas.indexOf(aElement.id) != -1) {
  1.1813 +        return aElement;
  1.1814 +      }
  1.1815 +      aElement = aElement.parentNode;
  1.1816 +    }
  1.1817 +    return null;
  1.1818 +  },
  1.1819 +
  1.1820 +  _getDragOverNode: function(aEvent, aAreaElement, aInToolbar, aDraggedItemId) {
  1.1821 +    let expectedParent = aAreaElement.customizationTarget || aAreaElement;
  1.1822 +    // Our tests are stupid. Cope:
  1.1823 +    if (!aEvent.clientX  && !aEvent.clientY) {
  1.1824 +      return aEvent.target;
  1.1825 +    }
  1.1826 +    // Offset the drag event's position with the offset to the center of
  1.1827 +    // the thing we're dragging
  1.1828 +    let dragX = aEvent.clientX - this._dragOffset.x;
  1.1829 +    let dragY = aEvent.clientY - this._dragOffset.y;
  1.1830 +
  1.1831 +    // Ensure this is within the container
  1.1832 +    let boundsContainer = expectedParent;
  1.1833 +    // NB: because the panel UI itself is inside a scrolling container, we need
  1.1834 +    // to use the parent bounds (otherwise, if the panel UI is scrolled down,
  1.1835 +    // the numbers we get are in window coordinates which leads to various kinds
  1.1836 +    // of weirdness)
  1.1837 +    if (boundsContainer == this.panelUIContents) {
  1.1838 +      boundsContainer = boundsContainer.parentNode;
  1.1839 +    }
  1.1840 +    let bounds = boundsContainer.getBoundingClientRect();
  1.1841 +    dragX = Math.min(bounds.right, Math.max(dragX, bounds.left));
  1.1842 +    dragY = Math.min(bounds.bottom, Math.max(dragY, bounds.top));
  1.1843 +
  1.1844 +    let targetNode;
  1.1845 +    if (aInToolbar) {
  1.1846 +      targetNode = aAreaElement.ownerDocument.elementFromPoint(dragX, dragY);
  1.1847 +      while (targetNode && targetNode.parentNode != expectedParent) {
  1.1848 +        targetNode = targetNode.parentNode;
  1.1849 +      }
  1.1850 +    } else {
  1.1851 +      let positionManager = DragPositionManager.getManagerForArea(aAreaElement);
  1.1852 +      // Make it relative to the container:
  1.1853 +      dragX -= bounds.left;
  1.1854 +      // NB: but if we're in the panel UI, we need to use the actual panel
  1.1855 +      // contents instead of the scrolling container to determine our origin
  1.1856 +      // offset against:
  1.1857 +      if (expectedParent == this.panelUIContents) {
  1.1858 +        dragY -= this.panelUIContents.getBoundingClientRect().top;
  1.1859 +      } else {
  1.1860 +        dragY -= bounds.top;
  1.1861 +      }
  1.1862 +      // Find the closest node:
  1.1863 +      targetNode = positionManager.find(aAreaElement, dragX, dragY, aDraggedItemId);
  1.1864 +    }
  1.1865 +    return targetNode || aEvent.target;
  1.1866 +  },
  1.1867 +
  1.1868 +  _onMouseDown: function(aEvent) {
  1.1869 +    LOG("_onMouseDown");
  1.1870 +    if (aEvent.button != 0) {
  1.1871 +      return;
  1.1872 +    }
  1.1873 +    let doc = aEvent.target.ownerDocument;
  1.1874 +    doc.documentElement.setAttribute("customizing-movingItem", true);
  1.1875 +    let item = this._getWrapper(aEvent.target);
  1.1876 +    if (item && !item.classList.contains(kPlaceholderClass) &&
  1.1877 +        item.getAttribute("removable") == "true") {
  1.1878 +      item.setAttribute("mousedown", "true");
  1.1879 +    }
  1.1880 +  },
  1.1881 +
  1.1882 +  _onMouseUp: function(aEvent) {
  1.1883 +    LOG("_onMouseUp");
  1.1884 +    if (aEvent.button != 0) {
  1.1885 +      return;
  1.1886 +    }
  1.1887 +    let doc = aEvent.target.ownerDocument;
  1.1888 +    doc.documentElement.removeAttribute("customizing-movingItem");
  1.1889 +    let item = this._getWrapper(aEvent.target);
  1.1890 +    if (item) {
  1.1891 +      item.removeAttribute("mousedown");
  1.1892 +    }
  1.1893 +  },
  1.1894 +
  1.1895 +  _getWrapper: function(aElement) {
  1.1896 +    while (aElement && aElement.localName != "toolbarpaletteitem") {
  1.1897 +      if (aElement.localName == "toolbar")
  1.1898 +        return null;
  1.1899 +      aElement = aElement.parentNode;
  1.1900 +    }
  1.1901 +    return aElement;
  1.1902 +  },
  1.1903 +
  1.1904 +  _showPanelCustomizationPlaceholders: function() {
  1.1905 +    let doc = this.document;
  1.1906 +    let contents = this.panelUIContents;
  1.1907 +    let narrowItemsAfterWideItem = 0;
  1.1908 +    let node = contents.lastChild;
  1.1909 +    while (node && !node.classList.contains(CustomizableUI.WIDE_PANEL_CLASS) &&
  1.1910 +           (!node.firstChild || !node.firstChild.classList.contains(CustomizableUI.WIDE_PANEL_CLASS))) {
  1.1911 +      if (!node.hidden && !node.classList.contains(kPlaceholderClass)) {
  1.1912 +        narrowItemsAfterWideItem++;
  1.1913 +      }
  1.1914 +      node = node.previousSibling;
  1.1915 +    }
  1.1916 +
  1.1917 +    let orphanedItems = narrowItemsAfterWideItem % CustomizableUI.PANEL_COLUMN_COUNT;
  1.1918 +    let placeholders = CustomizableUI.PANEL_COLUMN_COUNT - orphanedItems;
  1.1919 +
  1.1920 +    let currentPlaceholderCount = contents.querySelectorAll("." + kPlaceholderClass).length;
  1.1921 +    if (placeholders > currentPlaceholderCount) {
  1.1922 +      while (placeholders-- > currentPlaceholderCount) {
  1.1923 +        let placeholder = doc.createElement("toolbarpaletteitem");
  1.1924 +        placeholder.classList.add(kPlaceholderClass);
  1.1925 +        //XXXjaws The toolbarbutton child here is only necessary to get
  1.1926 +        //  the styling right here.
  1.1927 +        let placeholderChild = doc.createElement("toolbarbutton");
  1.1928 +        placeholderChild.classList.add(kPlaceholderClass + "-child");
  1.1929 +        placeholder.appendChild(placeholderChild);
  1.1930 +        contents.appendChild(placeholder);
  1.1931 +      }
  1.1932 +    } else if (placeholders < currentPlaceholderCount) {
  1.1933 +      while (placeholders++ < currentPlaceholderCount) {
  1.1934 +        contents.querySelectorAll("." + kPlaceholderClass)[0].remove();
  1.1935 +      }
  1.1936 +    }
  1.1937 +  },
  1.1938 +
  1.1939 +  _removePanelCustomizationPlaceholders: function() {
  1.1940 +    let contents = this.panelUIContents;
  1.1941 +    let oldPlaceholders = contents.getElementsByClassName(kPlaceholderClass);
  1.1942 +    while (oldPlaceholders.length) {
  1.1943 +      contents.removeChild(oldPlaceholders[0]);
  1.1944 +    }
  1.1945 +  },
  1.1946 +
  1.1947 +  /**
  1.1948 +   * Update toolbar customization targets during drag events to add or remove
  1.1949 +   * outlines to indicate that an area is customizable.
  1.1950 +   *
  1.1951 +   * @param aWindow                       The XUL window in which outlines should be updated.
  1.1952 +   * @param {Element} [aToolbarArea=null] The element of the customizable toolbar area to add the
  1.1953 +   *                                      outline to. If aToolbarArea is falsy, the outline will be
  1.1954 +   *                                      removed from all toolbar areas.
  1.1955 +   */
  1.1956 +  _updateToolbarCustomizationOutline: function(aWindow, aToolbarArea = null) {
  1.1957 +    // Remove the attribute from existing customization targets
  1.1958 +    for (let area of CustomizableUI.areas) {
  1.1959 +      if (CustomizableUI.getAreaType(area) != CustomizableUI.TYPE_TOOLBAR) {
  1.1960 +        continue;
  1.1961 +      }
  1.1962 +      let target = CustomizableUI.getCustomizeTargetForArea(area, aWindow);
  1.1963 +      target.removeAttribute("customizing-dragovertarget");
  1.1964 +    }
  1.1965 +
  1.1966 +    // Now set the attribute on the desired target
  1.1967 +    if (aToolbarArea) {
  1.1968 +      if (CustomizableUI.getAreaType(aToolbarArea.id) != CustomizableUI.TYPE_TOOLBAR)
  1.1969 +        return;
  1.1970 +      let target = CustomizableUI.getCustomizeTargetForArea(aToolbarArea.id, aWindow);
  1.1971 +      target.setAttribute("customizing-dragovertarget", true);
  1.1972 +    }
  1.1973 +  },
  1.1974 +
  1.1975 +  _findVisiblePreviousSiblingNode: function(aReferenceNode) {
  1.1976 +    while (aReferenceNode &&
  1.1977 +           aReferenceNode.localName == "toolbarpaletteitem" &&
  1.1978 +           aReferenceNode.firstChild.hidden) {
  1.1979 +      aReferenceNode = aReferenceNode.previousSibling;
  1.1980 +    }
  1.1981 +    return aReferenceNode;
  1.1982 +  },
  1.1983 +};
  1.1984 +
  1.1985 +function __dumpDragData(aEvent, caller) {
  1.1986 +  if (!gDebug) {
  1.1987 +    return;
  1.1988 +  }
  1.1989 +  let str = "Dumping drag data (" + (caller ? caller + " in " : "") + "CustomizeMode.jsm) {\n";
  1.1990 +  str += "  type: " + aEvent["type"] + "\n";
  1.1991 +  for (let el of ["target", "currentTarget", "relatedTarget"]) {
  1.1992 +    if (aEvent[el]) {
  1.1993 +      str += "  " + el + ": " + aEvent[el] + "(localName=" + aEvent[el].localName + "; id=" + aEvent[el].id + ")\n";
  1.1994 +    }
  1.1995 +  }
  1.1996 +  for (let prop in aEvent.dataTransfer) {
  1.1997 +    if (typeof aEvent.dataTransfer[prop] != "function") {
  1.1998 +      str += "  dataTransfer[" + prop + "]: " + aEvent.dataTransfer[prop] + "\n";
  1.1999 +    }
  1.2000 +  }
  1.2001 +  str += "}";
  1.2002 +  LOG(str);
  1.2003 +}
  1.2004 +
  1.2005 +function dispatchFunction(aFunc) {
  1.2006 +  Services.tm.currentThread.dispatch(aFunc, Ci.nsIThread.DISPATCH_NORMAL);
  1.2007 +}

mercurial