browser/components/customizableui/src/CustomizableUI.jsm

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/customizableui/src/CustomizableUI.jsm	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,4036 @@
     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 = ["CustomizableUI"];
    1.11 +
    1.12 +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
    1.13 +
    1.14 +Cu.import("resource://gre/modules/Services.jsm");
    1.15 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.16 +XPCOMUtils.defineLazyModuleGetter(this, "PanelWideWidgetTracker",
    1.17 +  "resource:///modules/PanelWideWidgetTracker.jsm");
    1.18 +XPCOMUtils.defineLazyModuleGetter(this, "CustomizableWidgets",
    1.19 +  "resource:///modules/CustomizableWidgets.jsm");
    1.20 +XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
    1.21 +  "resource://gre/modules/DeferredTask.jsm");
    1.22 +XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
    1.23 +  "resource://gre/modules/PrivateBrowsingUtils.jsm");
    1.24 +XPCOMUtils.defineLazyModuleGetter(this, "Promise",
    1.25 +  "resource://gre/modules/Promise.jsm");
    1.26 +XPCOMUtils.defineLazyGetter(this, "gWidgetsBundle", function() {
    1.27 +  const kUrl = "chrome://browser/locale/customizableui/customizableWidgets.properties";
    1.28 +  return Services.strings.createBundle(kUrl);
    1.29 +});
    1.30 +XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
    1.31 +  "resource://gre/modules/ShortcutUtils.jsm");
    1.32 +XPCOMUtils.defineLazyServiceGetter(this, "gELS",
    1.33 +  "@mozilla.org/eventlistenerservice;1", "nsIEventListenerService");
    1.34 +
    1.35 +const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
    1.36 +
    1.37 +const kSpecialWidgetPfx = "customizableui-special-";
    1.38 +
    1.39 +const kPrefCustomizationState        = "browser.uiCustomization.state";
    1.40 +const kPrefCustomizationAutoAdd      = "browser.uiCustomization.autoAdd";
    1.41 +const kPrefCustomizationDebug        = "browser.uiCustomization.debug";
    1.42 +const kPrefDrawInTitlebar            = "browser.tabs.drawInTitlebar";
    1.43 +
    1.44 +/**
    1.45 + * The keys are the handlers that are fired when the event type (the value)
    1.46 + * is fired on the subview. A widget that provides a subview has the option
    1.47 + * of providing onViewShowing and onViewHiding event handlers.
    1.48 + */
    1.49 +const kSubviewEvents = [
    1.50 +  "ViewShowing",
    1.51 +  "ViewHiding"
    1.52 +];
    1.53 +
    1.54 +/**
    1.55 + * gPalette is a map of every widget that CustomizableUI.jsm knows about, keyed
    1.56 + * on their IDs.
    1.57 + */
    1.58 +let gPalette = new Map();
    1.59 +
    1.60 +/**
    1.61 + * gAreas maps area IDs to Sets of properties about those areas. An area is a
    1.62 + * place where a widget can be put.
    1.63 + */
    1.64 +let gAreas = new Map();
    1.65 +
    1.66 +/**
    1.67 + * gPlacements maps area IDs to Arrays of widget IDs, indicating that the widgets
    1.68 + * are placed within that area (either directly in the area node, or in the
    1.69 + * customizationTarget of the node).
    1.70 + */
    1.71 +let gPlacements = new Map();
    1.72 +
    1.73 +/**
    1.74 + * gFuturePlacements represent placements that will happen for areas that have
    1.75 + * not yet loaded (due to lazy-loading). This can occur when add-ons register
    1.76 + * widgets.
    1.77 + */
    1.78 +let gFuturePlacements = new Map();
    1.79 +
    1.80 +//XXXunf Temporary. Need a nice way to abstract functions to build widgets
    1.81 +//       of these types.
    1.82 +let gSupportedWidgetTypes = new Set(["button", "view", "custom"]);
    1.83 +
    1.84 +/**
    1.85 + * gPanelsForWindow is a list of known panels in a window which we may need to close
    1.86 + * should command events fire which target them.
    1.87 + */
    1.88 +let gPanelsForWindow = new WeakMap();
    1.89 +
    1.90 +/**
    1.91 + * gSeenWidgets remembers which widgets the user has seen for the first time
    1.92 + * before. This way, if a new widget is created, and the user has not seen it
    1.93 + * before, it can be put in its default location. Otherwise, it remains in the
    1.94 + * palette.
    1.95 + */
    1.96 +let gSeenWidgets = new Set();
    1.97 +
    1.98 +/**
    1.99 + * gDirtyAreaCache is a set of area IDs for areas where items have been added,
   1.100 + * moved or removed at least once. This set is persisted, and is used to
   1.101 + * optimize building of toolbars in the default case where no toolbars should
   1.102 + * be "dirty".
   1.103 + */
   1.104 +let gDirtyAreaCache = new Set();
   1.105 +
   1.106 +/**
   1.107 + * gPendingBuildAreas is a map from area IDs to map from build nodes to their
   1.108 + * existing children at the time of node registration, that are waiting
   1.109 + * for the area to be registered
   1.110 + */
   1.111 +let gPendingBuildAreas = new Map();
   1.112 +
   1.113 +let gSavedState = null;
   1.114 +let gRestoring = false;
   1.115 +let gDirty = false;
   1.116 +let gInBatchStack = 0;
   1.117 +let gResetting = false;
   1.118 +let gUndoResetting = false;
   1.119 +
   1.120 +/**
   1.121 + * gBuildAreas maps area IDs to actual area nodes within browser windows.
   1.122 + */
   1.123 +let gBuildAreas = new Map();
   1.124 +
   1.125 +/**
   1.126 + * gBuildWindows is a map of windows that have registered build areas, mapped
   1.127 + * to a Set of known toolboxes in that window.
   1.128 + */
   1.129 +let gBuildWindows = new Map();
   1.130 +
   1.131 +let gNewElementCount = 0;
   1.132 +let gGroupWrapperCache = new Map();
   1.133 +let gSingleWrapperCache = new WeakMap();
   1.134 +let gListeners = new Set();
   1.135 +
   1.136 +let gUIStateBeforeReset = {
   1.137 +  uiCustomizationState: null,
   1.138 +  drawInTitlebar: null,
   1.139 +};
   1.140 +
   1.141 +let gModuleName = "[CustomizableUI]";
   1.142 +#include logging.js
   1.143 +
   1.144 +let CustomizableUIInternal = {
   1.145 +  initialize: function() {
   1.146 +    LOG("Initializing");
   1.147 +
   1.148 +    this.addListener(this);
   1.149 +    this._defineBuiltInWidgets();
   1.150 +    this.loadSavedState();
   1.151 +
   1.152 +    let panelPlacements = [
   1.153 +      "edit-controls",
   1.154 +      "zoom-controls",
   1.155 +      "new-window-button",
   1.156 +      "privatebrowsing-button",
   1.157 +      "save-page-button",
   1.158 +      "print-button",
   1.159 +      "history-panelmenu",
   1.160 +      "fullscreen-button",
   1.161 +      "find-button",
   1.162 +      "preferences-button",
   1.163 +      "add-ons-button",
   1.164 +      "developer-button",
   1.165 +    ];
   1.166 +
   1.167 +    if (gPalette.has("switch-to-metro-button")) {
   1.168 +      panelPlacements.push("switch-to-metro-button");
   1.169 +    }
   1.170 +
   1.171 +#ifdef NIGHTLY_BUILD
   1.172 +    if (gPalette.has("e10s-button")) {
   1.173 +      let newWindowIndex = panelPlacements.indexOf("new-window-button");
   1.174 +      if (newWindowIndex > -1) {
   1.175 +        panelPlacements.splice(newWindowIndex + 1, 0, "e10s-button");
   1.176 +      }
   1.177 +    }
   1.178 +#endif
   1.179 +
   1.180 +    let showCharacterEncoding = Services.prefs.getComplexValue(
   1.181 +      "browser.menu.showCharacterEncoding",
   1.182 +      Ci.nsIPrefLocalizedString
   1.183 +    ).data;
   1.184 +    if (showCharacterEncoding == "true") {
   1.185 +      panelPlacements.push("characterencoding-button");
   1.186 +    }
   1.187 +
   1.188 +    this.registerArea(CustomizableUI.AREA_PANEL, {
   1.189 +      anchor: "PanelUI-menu-button",
   1.190 +      type: CustomizableUI.TYPE_MENU_PANEL,
   1.191 +      defaultPlacements: panelPlacements
   1.192 +    }, true);
   1.193 +    PanelWideWidgetTracker.init();
   1.194 +
   1.195 +    this.registerArea(CustomizableUI.AREA_NAVBAR, {
   1.196 +      legacy: true,
   1.197 +      type: CustomizableUI.TYPE_TOOLBAR,
   1.198 +      overflowable: true,
   1.199 +      defaultPlacements: [
   1.200 +        "urlbar-container",
   1.201 +        "search-container",
   1.202 +        "webrtc-status-button",
   1.203 +        "bookmarks-menu-button",
   1.204 +        "downloads-button",
   1.205 +        "home-button",
   1.206 +        "social-share-button",
   1.207 +      ],
   1.208 +      defaultCollapsed: false,
   1.209 +    }, true);
   1.210 +#ifndef XP_MACOSX
   1.211 +    this.registerArea(CustomizableUI.AREA_MENUBAR, {
   1.212 +      legacy: true,
   1.213 +      type: CustomizableUI.TYPE_TOOLBAR,
   1.214 +      defaultPlacements: [
   1.215 +        "menubar-items",
   1.216 +      ],
   1.217 +      get defaultCollapsed() {
   1.218 +#ifdef MENUBAR_CAN_AUTOHIDE
   1.219 +#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_QT)
   1.220 +        return true;
   1.221 +#else
   1.222 +        // This is duplicated logic from /browser/base/jar.mn
   1.223 +        // for win6BrowserOverlay.xul.
   1.224 +        return Services.appinfo.OS == "WINNT" &&
   1.225 +               Services.sysinfo.getProperty("version") != "5.1";
   1.226 +#endif
   1.227 +#endif
   1.228 +        return false;
   1.229 +      }
   1.230 +    }, true);
   1.231 +#endif
   1.232 +    this.registerArea(CustomizableUI.AREA_TABSTRIP, {
   1.233 +      legacy: true,
   1.234 +      type: CustomizableUI.TYPE_TOOLBAR,
   1.235 +      defaultPlacements: [
   1.236 +        "tabbrowser-tabs",
   1.237 +        "new-tab-button",
   1.238 +        "alltabs-button",
   1.239 +      ],
   1.240 +      defaultCollapsed: null,
   1.241 +    }, true);
   1.242 +    this.registerArea(CustomizableUI.AREA_BOOKMARKS, {
   1.243 +      legacy: true,
   1.244 +      type: CustomizableUI.TYPE_TOOLBAR,
   1.245 +      defaultPlacements: [
   1.246 +        "personal-bookmarks",
   1.247 +      ],
   1.248 +      defaultCollapsed: true,
   1.249 +    }, true);
   1.250 +
   1.251 +    this.registerArea(CustomizableUI.AREA_ADDONBAR, {
   1.252 +      type: CustomizableUI.TYPE_TOOLBAR,
   1.253 +      legacy: true,
   1.254 +      defaultPlacements: ["addonbar-closebutton", "status-bar"],
   1.255 +      defaultCollapsed: false,
   1.256 +    }, true);
   1.257 +  },
   1.258 +
   1.259 +  get _builtinToolbars() {
   1.260 +    return new Set([
   1.261 +      CustomizableUI.AREA_NAVBAR,
   1.262 +      CustomizableUI.AREA_BOOKMARKS,
   1.263 +      CustomizableUI.AREA_TABSTRIP,
   1.264 +      CustomizableUI.AREA_ADDONBAR,
   1.265 +#ifndef XP_MACOSX
   1.266 +      CustomizableUI.AREA_MENUBAR,
   1.267 +#endif
   1.268 +    ]);
   1.269 +  },
   1.270 +
   1.271 +  _defineBuiltInWidgets: function() {
   1.272 +    //XXXunf Need to figure out how to auto-add new builtin widgets in new
   1.273 +    //       app versions to already customized areas.
   1.274 +    for (let widgetDefinition of CustomizableWidgets) {
   1.275 +      this.createBuiltinWidget(widgetDefinition);
   1.276 +    }
   1.277 +  },
   1.278 +
   1.279 +  wrapWidget: function(aWidgetId) {
   1.280 +    if (gGroupWrapperCache.has(aWidgetId)) {
   1.281 +      return gGroupWrapperCache.get(aWidgetId);
   1.282 +    }
   1.283 +
   1.284 +    let provider = this.getWidgetProvider(aWidgetId);
   1.285 +    if (!provider) {
   1.286 +      return null;
   1.287 +    }
   1.288 +
   1.289 +    if (provider == CustomizableUI.PROVIDER_API) {
   1.290 +      let widget = gPalette.get(aWidgetId);
   1.291 +      if (!widget.wrapper) {
   1.292 +        widget.wrapper = new WidgetGroupWrapper(widget);
   1.293 +        gGroupWrapperCache.set(aWidgetId, widget.wrapper);
   1.294 +      }
   1.295 +      return widget.wrapper;
   1.296 +    }
   1.297 +
   1.298 +    // PROVIDER_SPECIAL gets treated the same as PROVIDER_XUL.
   1.299 +    let wrapper = new XULWidgetGroupWrapper(aWidgetId);
   1.300 +    gGroupWrapperCache.set(aWidgetId, wrapper);
   1.301 +    return wrapper;
   1.302 +  },
   1.303 +
   1.304 +  registerArea: function(aName, aProperties, aInternalCaller) {
   1.305 +    if (typeof aName != "string" || !/^[a-z0-9-_]{1,}$/i.test(aName)) {
   1.306 +      throw new Error("Invalid area name");
   1.307 +    }
   1.308 +
   1.309 +    let areaIsKnown = gAreas.has(aName);
   1.310 +    let props = areaIsKnown ? gAreas.get(aName) : new Map();
   1.311 +    const kImmutableProperties = new Set(["type", "legacy", "overflowable"]);
   1.312 +    for (let key in aProperties) {
   1.313 +      if (areaIsKnown && kImmutableProperties.has(key) &&
   1.314 +          props.get(key) != aProperties[key]) {
   1.315 +        throw new Error("An area cannot change the property for '" + key + "'");
   1.316 +      }
   1.317 +      //XXXgijs for special items, we need to make sure they have an appropriate ID
   1.318 +      // so we aren't perpetually in a non-default state:
   1.319 +      if (key == "defaultPlacements" && Array.isArray(aProperties[key])) {
   1.320 +        props.set(key, aProperties[key].map(x => this.isSpecialWidget(x) ? this.ensureSpecialWidgetId(x) : x ));
   1.321 +      } else {
   1.322 +        props.set(key, aProperties[key]);
   1.323 +      }
   1.324 +    }
   1.325 +    // Default to a toolbar:
   1.326 +    if (!props.has("type")) {
   1.327 +      props.set("type", CustomizableUI.TYPE_TOOLBAR);
   1.328 +    }
   1.329 +    if (props.get("type") == CustomizableUI.TYPE_TOOLBAR) {
   1.330 +      // Check aProperties instead of props because this check is only interested
   1.331 +      // in the passed arguments, not the state of a potentially pre-existing area.
   1.332 +      if (!aInternalCaller && aProperties["defaultCollapsed"]) {
   1.333 +        throw new Error("defaultCollapsed is only allowed for default toolbars.")
   1.334 +      }
   1.335 +      if (!props.has("defaultCollapsed")) {
   1.336 +        props.set("defaultCollapsed", true);
   1.337 +      }
   1.338 +    } else if (props.has("defaultCollapsed")) {
   1.339 +      throw new Error("defaultCollapsed only applies for TYPE_TOOLBAR areas.");
   1.340 +    }
   1.341 +    // Sanity check type:
   1.342 +    let allTypes = [CustomizableUI.TYPE_TOOLBAR, CustomizableUI.TYPE_MENU_PANEL];
   1.343 +    if (allTypes.indexOf(props.get("type")) == -1) {
   1.344 +      throw new Error("Invalid area type " + props.get("type"));
   1.345 +    }
   1.346 +
   1.347 +    // And to no placements:
   1.348 +    if (!props.has("defaultPlacements")) {
   1.349 +      props.set("defaultPlacements", []);
   1.350 +    }
   1.351 +    // Sanity check default placements array:
   1.352 +    if (!Array.isArray(props.get("defaultPlacements"))) {
   1.353 +      throw new Error("Should provide an array of default placements");
   1.354 +    }
   1.355 +
   1.356 +    if (!areaIsKnown) {
   1.357 +      gAreas.set(aName, props);
   1.358 +
   1.359 +      if (props.get("legacy") && !gPlacements.has(aName)) {
   1.360 +        // Guarantee this area exists in gFuturePlacements, to avoid checking it in
   1.361 +        // various places elsewhere.
   1.362 +        gFuturePlacements.set(aName, new Set());
   1.363 +      } else {
   1.364 +        this.restoreStateForArea(aName);
   1.365 +      }
   1.366 +
   1.367 +      // If we have pending build area nodes, register all of them
   1.368 +      if (gPendingBuildAreas.has(aName)) {
   1.369 +        let pendingNodes = gPendingBuildAreas.get(aName);
   1.370 +        for (let [pendingNode, existingChildren] of pendingNodes) {
   1.371 +          this.registerToolbarNode(pendingNode, existingChildren);
   1.372 +        }
   1.373 +        gPendingBuildAreas.delete(aName);
   1.374 +      }
   1.375 +    }
   1.376 +  },
   1.377 +
   1.378 +  unregisterArea: function(aName, aDestroyPlacements) {
   1.379 +    if (typeof aName != "string" || !/^[a-z0-9-_]{1,}$/i.test(aName)) {
   1.380 +      throw new Error("Invalid area name");
   1.381 +    }
   1.382 +    if (!gAreas.has(aName) && !gPlacements.has(aName)) {
   1.383 +      throw new Error("Area not registered");
   1.384 +    }
   1.385 +
   1.386 +    // Move all the widgets out
   1.387 +    this.beginBatchUpdate();
   1.388 +    try {
   1.389 +      let placements = gPlacements.get(aName);
   1.390 +      if (placements) {
   1.391 +        // Need to clone this array so removeWidgetFromArea doesn't modify it
   1.392 +        placements = [...placements];
   1.393 +        placements.forEach(this.removeWidgetFromArea, this);
   1.394 +      }
   1.395 +
   1.396 +      // Delete all remaining traces.
   1.397 +      gAreas.delete(aName);
   1.398 +      // Only destroy placements when necessary:
   1.399 +      if (aDestroyPlacements) {
   1.400 +        gPlacements.delete(aName);
   1.401 +      } else {
   1.402 +        // Otherwise we need to re-set them, as removeFromArea will have emptied
   1.403 +        // them out:
   1.404 +        gPlacements.set(aName, placements);
   1.405 +      }
   1.406 +      gFuturePlacements.delete(aName);
   1.407 +      let existingAreaNodes = gBuildAreas.get(aName);
   1.408 +      if (existingAreaNodes) {
   1.409 +        for (let areaNode of existingAreaNodes) {
   1.410 +          this.notifyListeners("onAreaNodeUnregistered", aName, areaNode.customizationTarget,
   1.411 +                               CustomizableUI.REASON_AREA_UNREGISTERED);
   1.412 +        }
   1.413 +      }
   1.414 +      gBuildAreas.delete(aName);
   1.415 +    } finally {
   1.416 +      this.endBatchUpdate(true);
   1.417 +    }
   1.418 +  },
   1.419 +
   1.420 +  registerToolbarNode: function(aToolbar, aExistingChildren) {
   1.421 +    let area = aToolbar.id;
   1.422 +    if (gBuildAreas.has(area) && gBuildAreas.get(area).has(aToolbar)) {
   1.423 +      return;
   1.424 +    }
   1.425 +    let document = aToolbar.ownerDocument;
   1.426 +    let areaProperties = gAreas.get(area);
   1.427 +
   1.428 +    // If this area is not registered, try to do it automatically:
   1.429 +    if (!areaProperties) {
   1.430 +      // If there's no defaultset attribute and this isn't a legacy extra toolbar,
   1.431 +      // we assume that we should wait for registerArea to be called:
   1.432 +      if (!aToolbar.hasAttribute("defaultset") &&
   1.433 +          !aToolbar.hasAttribute("customindex")) {
   1.434 +        if (!gPendingBuildAreas.has(area)) {
   1.435 +          gPendingBuildAreas.set(area, new Map());
   1.436 +        }
   1.437 +        let pendingNodes = gPendingBuildAreas.get(area);
   1.438 +        pendingNodes.set(aToolbar, aExistingChildren);
   1.439 +        return;
   1.440 +      }
   1.441 +      let props = {type: CustomizableUI.TYPE_TOOLBAR, legacy: true};
   1.442 +      let defaultsetAttribute = aToolbar.getAttribute("defaultset") || "";
   1.443 +      props.defaultPlacements = defaultsetAttribute.split(',').filter(s => s);
   1.444 +      this.registerArea(area, props);
   1.445 +      areaProperties = gAreas.get(area);
   1.446 +    }
   1.447 +
   1.448 +    this.beginBatchUpdate();
   1.449 +    try {
   1.450 +      let placements = gPlacements.get(area);
   1.451 +      if (!placements && areaProperties.has("legacy")) {
   1.452 +        let legacyState = aToolbar.getAttribute("currentset");
   1.453 +        if (legacyState) {
   1.454 +          legacyState = legacyState.split(",").filter(s => s);
   1.455 +        }
   1.456 +
   1.457 +        // Manually restore the state here, so the legacy state can be converted. 
   1.458 +        this.restoreStateForArea(area, legacyState);
   1.459 +        placements = gPlacements.get(area);
   1.460 +      }
   1.461 +
   1.462 +      // Check that the current children and the current placements match. If
   1.463 +      // not, mark it as dirty:
   1.464 +      if (aExistingChildren.length != placements.length ||
   1.465 +          aExistingChildren.every((id, i) => id == placements[i])) {
   1.466 +        gDirtyAreaCache.add(area);
   1.467 +      }
   1.468 +
   1.469 +      if (areaProperties.has("overflowable")) {
   1.470 +        aToolbar.overflowable = new OverflowableToolbar(aToolbar);
   1.471 +      }
   1.472 +
   1.473 +      this.registerBuildArea(area, aToolbar);
   1.474 +
   1.475 +      // We only build the toolbar if it's been marked as "dirty". Dirty means
   1.476 +      // one of the following things:
   1.477 +      // 1) Items have been added, moved or removed from this toolbar before.
   1.478 +      // 2) The number of children of the toolbar does not match the length of
   1.479 +      //    the placements array for that area.
   1.480 +      //
   1.481 +      // This notion of being "dirty" is stored in a cache which is persisted
   1.482 +      // in the saved state.
   1.483 +      if (gDirtyAreaCache.has(area)) {
   1.484 +        this.buildArea(area, placements, aToolbar);
   1.485 +      }
   1.486 +      this.notifyListeners("onAreaNodeRegistered", area, aToolbar.customizationTarget);
   1.487 +      aToolbar.setAttribute("currentset", placements.join(","));
   1.488 +    } finally {
   1.489 +      this.endBatchUpdate();
   1.490 +    }
   1.491 +  },
   1.492 +
   1.493 +  buildArea: function(aArea, aPlacements, aAreaNode) {
   1.494 +    let document = aAreaNode.ownerDocument;
   1.495 +    let window = document.defaultView;
   1.496 +    let inPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(window);
   1.497 +    let container = aAreaNode.customizationTarget;
   1.498 +    let areaIsPanel = gAreas.get(aArea).get("type") == CustomizableUI.TYPE_MENU_PANEL;
   1.499 +
   1.500 +    if (!container) {
   1.501 +      throw new Error("Expected area " + aArea
   1.502 +                      + " to have a customizationTarget attribute.");
   1.503 +    }
   1.504 +
   1.505 +    // Restore nav-bar visibility since it may have been hidden
   1.506 +    // through a migration path (bug 938980) or an add-on.
   1.507 +    if (aArea == CustomizableUI.AREA_NAVBAR) {
   1.508 +      aAreaNode.collapsed = false;
   1.509 +    }
   1.510 +
   1.511 +    this.beginBatchUpdate();
   1.512 +
   1.513 +    try {
   1.514 +      let currentNode = container.firstChild;
   1.515 +      let placementsToRemove = new Set();
   1.516 +      for (let id of aPlacements) {
   1.517 +        while (currentNode && currentNode.getAttribute("skipintoolbarset") == "true") {
   1.518 +          currentNode = currentNode.nextSibling;
   1.519 +        }
   1.520 +
   1.521 +        if (currentNode && currentNode.id == id) {
   1.522 +          currentNode = currentNode.nextSibling;
   1.523 +          continue;
   1.524 +        }
   1.525 +
   1.526 +        if (this.isSpecialWidget(id) && areaIsPanel) {
   1.527 +          placementsToRemove.add(id);
   1.528 +          continue;
   1.529 +        }
   1.530 +
   1.531 +        let [provider, node] = this.getWidgetNode(id, window);
   1.532 +        if (!node) {
   1.533 +          LOG("Unknown widget: " + id);
   1.534 +          continue;
   1.535 +        }
   1.536 +
   1.537 +        // If the placements have items in them which are (now) no longer removable,
   1.538 +        // we shouldn't be moving them:
   1.539 +        if (provider == CustomizableUI.PROVIDER_API) {
   1.540 +          let widgetInfo = gPalette.get(id);
   1.541 +          if (!widgetInfo.removable && aArea != widgetInfo.defaultArea) {
   1.542 +            placementsToRemove.add(id);
   1.543 +            continue;
   1.544 +          }
   1.545 +        } else if (provider == CustomizableUI.PROVIDER_XUL &&
   1.546 +                   node.parentNode != container && !this.isWidgetRemovable(node)) {
   1.547 +          placementsToRemove.add(id);
   1.548 +          continue;
   1.549 +        } // Special widgets are always removable, so no need to check them
   1.550 +
   1.551 +        if (inPrivateWindow && provider == CustomizableUI.PROVIDER_API) {
   1.552 +          let widget = gPalette.get(id);
   1.553 +          if (!widget.showInPrivateBrowsing && inPrivateWindow) {
   1.554 +            continue;
   1.555 +          }
   1.556 +        }
   1.557 +
   1.558 +        this.ensureButtonContextMenu(node, aAreaNode);
   1.559 +        if (node.localName == "toolbarbutton") {
   1.560 +          if (areaIsPanel) {
   1.561 +            node.setAttribute("wrap", "true");
   1.562 +          } else {
   1.563 +            node.removeAttribute("wrap");
   1.564 +          }
   1.565 +        }
   1.566 +
   1.567 +        this.insertWidgetBefore(node, currentNode, container, aArea);
   1.568 +        if (gResetting) {
   1.569 +          this.notifyListeners("onWidgetReset", node, container);
   1.570 +        } else if (gUndoResetting) {
   1.571 +          this.notifyListeners("onWidgetUndoMove", node, container);
   1.572 +        }
   1.573 +      }
   1.574 +
   1.575 +      if (currentNode) {
   1.576 +        let palette = aAreaNode.toolbox ? aAreaNode.toolbox.palette : null;
   1.577 +        let limit = currentNode.previousSibling;
   1.578 +        let node = container.lastChild;
   1.579 +        while (node && node != limit) {
   1.580 +          let previousSibling = node.previousSibling;
   1.581 +          // Nodes opt-in to removability. If they're removable, and we haven't
   1.582 +          // seen them in the placements array, then we toss them into the palette
   1.583 +          // if one exists. If no palette exists, we just remove the node. If the
   1.584 +          // node is not removable, we leave it where it is. However, we can only
   1.585 +          // safely touch elements that have an ID - both because we depend on
   1.586 +          // IDs, and because such elements are not intended to be widgets
   1.587 +          // (eg, titlebar-placeholder elements).
   1.588 +          if (node.id && node.getAttribute("skipintoolbarset") != "true") {
   1.589 +            if (this.isWidgetRemovable(node)) {
   1.590 +              if (palette && !this.isSpecialWidget(node.id)) {
   1.591 +                palette.appendChild(node);
   1.592 +                this.removeLocationAttributes(node);
   1.593 +              } else {
   1.594 +                container.removeChild(node);
   1.595 +              }
   1.596 +            } else {
   1.597 +              this.setLocationAttributes(currentNode, aArea);
   1.598 +              node.setAttribute("removable", false);
   1.599 +              LOG("Adding non-removable widget to placements of " + aArea + ": " +
   1.600 +                  node.id);
   1.601 +              gPlacements.get(aArea).push(node.id);
   1.602 +              gDirty = true;
   1.603 +            }
   1.604 +          }
   1.605 +          node = previousSibling;
   1.606 +        }
   1.607 +      }
   1.608 +
   1.609 +      // If there are placements in here which aren't removable from their original area,
   1.610 +      // we remove them from this area's placement array. They will (have) be(en) added
   1.611 +      // to their original area's placements array in the block above this one.
   1.612 +      if (placementsToRemove.size) {
   1.613 +        let placementAry = gPlacements.get(aArea);
   1.614 +        for (let id of placementsToRemove) {
   1.615 +          let index = placementAry.indexOf(id);
   1.616 +          placementAry.splice(index, 1);
   1.617 +        }
   1.618 +      }
   1.619 +
   1.620 +      if (gResetting) {
   1.621 +        this.notifyListeners("onAreaReset", aArea, container);
   1.622 +      }
   1.623 +    } finally {
   1.624 +      this.endBatchUpdate();
   1.625 +    }
   1.626 +  },
   1.627 +
   1.628 +  addPanelCloseListeners: function(aPanel) {
   1.629 +    gELS.addSystemEventListener(aPanel, "click", this, false);
   1.630 +    gELS.addSystemEventListener(aPanel, "keypress", this, false);
   1.631 +    let win = aPanel.ownerDocument.defaultView;
   1.632 +    if (!gPanelsForWindow.has(win)) {
   1.633 +      gPanelsForWindow.set(win, new Set());
   1.634 +    }
   1.635 +    gPanelsForWindow.get(win).add(this._getPanelForNode(aPanel));
   1.636 +  },
   1.637 +
   1.638 +  removePanelCloseListeners: function(aPanel) {
   1.639 +    gELS.removeSystemEventListener(aPanel, "click", this, false);
   1.640 +    gELS.removeSystemEventListener(aPanel, "keypress", this, false);
   1.641 +    let win = aPanel.ownerDocument.defaultView;
   1.642 +    let panels = gPanelsForWindow.get(win);
   1.643 +    if (panels) {
   1.644 +      panels.delete(this._getPanelForNode(aPanel));
   1.645 +    }
   1.646 +  },
   1.647 +
   1.648 +  ensureButtonContextMenu: function(aNode, aAreaNode) {
   1.649 +    const kPanelItemContextMenu = "customizationPanelItemContextMenu";
   1.650 +
   1.651 +    let currentContextMenu = aNode.getAttribute("context") ||
   1.652 +                             aNode.getAttribute("contextmenu");
   1.653 +    let place = CustomizableUI.getPlaceForItem(aAreaNode);
   1.654 +    let contextMenuForPlace = place == "panel" ?
   1.655 +                                kPanelItemContextMenu :
   1.656 +                                null;
   1.657 +    if (contextMenuForPlace && !currentContextMenu) {
   1.658 +      aNode.setAttribute("context", contextMenuForPlace);
   1.659 +    } else if (currentContextMenu == kPanelItemContextMenu &&
   1.660 +               contextMenuForPlace != kPanelItemContextMenu) {
   1.661 +      aNode.removeAttribute("context");
   1.662 +      aNode.removeAttribute("contextmenu");
   1.663 +    }
   1.664 +  },
   1.665 +
   1.666 +  getWidgetProvider: function(aWidgetId) {
   1.667 +    if (this.isSpecialWidget(aWidgetId)) {
   1.668 +      return CustomizableUI.PROVIDER_SPECIAL;
   1.669 +    }
   1.670 +    if (gPalette.has(aWidgetId)) {
   1.671 +      return CustomizableUI.PROVIDER_API;
   1.672 +    }
   1.673 +    // If this was an API widget that was destroyed, return null:
   1.674 +    if (gSeenWidgets.has(aWidgetId)) {
   1.675 +      return null;
   1.676 +    }
   1.677 +
   1.678 +    // We fall back to the XUL provider, but we don't know for sure (at this
   1.679 +    // point) whether it exists there either. So the API is technically lying.
   1.680 +    // Ideally, it would be able to return an error value (or throw an
   1.681 +    // exception) if it really didn't exist. Our code calling this function
   1.682 +    // handles that fine, but this is a public API.
   1.683 +    return CustomizableUI.PROVIDER_XUL;
   1.684 +  },
   1.685 +
   1.686 +  getWidgetNode: function(aWidgetId, aWindow) {
   1.687 +    let document = aWindow.document;
   1.688 +
   1.689 +    if (this.isSpecialWidget(aWidgetId)) {
   1.690 +      let widgetNode = document.getElementById(aWidgetId) ||
   1.691 +                       this.createSpecialWidget(aWidgetId, document);
   1.692 +      return [ CustomizableUI.PROVIDER_SPECIAL, widgetNode];
   1.693 +    }
   1.694 +
   1.695 +    let widget = gPalette.get(aWidgetId);
   1.696 +    if (widget) {
   1.697 +      // If we have an instance of this widget already, just use that.
   1.698 +      if (widget.instances.has(document)) {
   1.699 +        LOG("An instance of widget " + aWidgetId + " already exists in this "
   1.700 +            + "document. Reusing.");
   1.701 +        return [ CustomizableUI.PROVIDER_API,
   1.702 +                 widget.instances.get(document) ];
   1.703 +      }
   1.704 +
   1.705 +      return [ CustomizableUI.PROVIDER_API,
   1.706 +               this.buildWidget(document, widget) ];
   1.707 +    }
   1.708 +
   1.709 +    LOG("Searching for " + aWidgetId + " in toolbox.");
   1.710 +    let node = this.findWidgetInWindow(aWidgetId, aWindow);
   1.711 +    if (node) {
   1.712 +      return [ CustomizableUI.PROVIDER_XUL, node ];
   1.713 +    }
   1.714 +
   1.715 +    LOG("No node for " + aWidgetId + " found.");
   1.716 +    return [null, null];
   1.717 +  },
   1.718 +
   1.719 +  registerMenuPanel: function(aPanelContents) {
   1.720 +    if (gBuildAreas.has(CustomizableUI.AREA_PANEL) &&
   1.721 +        gBuildAreas.get(CustomizableUI.AREA_PANEL).has(aPanelContents)) {
   1.722 +      return;
   1.723 +    }
   1.724 +
   1.725 +    let document = aPanelContents.ownerDocument;
   1.726 +
   1.727 +    aPanelContents.toolbox = document.getElementById("navigator-toolbox");
   1.728 +    aPanelContents.customizationTarget = aPanelContents;
   1.729 +
   1.730 +    this.addPanelCloseListeners(this._getPanelForNode(aPanelContents));
   1.731 +
   1.732 +    let placements = gPlacements.get(CustomizableUI.AREA_PANEL);
   1.733 +    this.buildArea(CustomizableUI.AREA_PANEL, placements, aPanelContents);
   1.734 +    this.notifyListeners("onAreaNodeRegistered", CustomizableUI.AREA_PANEL, aPanelContents);
   1.735 +
   1.736 +    for (let child of aPanelContents.children) {
   1.737 +      if (child.localName != "toolbarbutton") {
   1.738 +        if (child.localName == "toolbaritem") {
   1.739 +          this.ensureButtonContextMenu(child, aPanelContents);
   1.740 +        }
   1.741 +        continue;
   1.742 +      }
   1.743 +      this.ensureButtonContextMenu(child, aPanelContents);
   1.744 +      child.setAttribute("wrap", "true");
   1.745 +    }
   1.746 +
   1.747 +    this.registerBuildArea(CustomizableUI.AREA_PANEL, aPanelContents);
   1.748 +  },
   1.749 +
   1.750 +  onWidgetAdded: function(aWidgetId, aArea, aPosition) {
   1.751 +    this.insertNode(aWidgetId, aArea, aPosition, true);
   1.752 +
   1.753 +    if (!gResetting) {
   1.754 +      this._clearPreviousUIState();
   1.755 +    }
   1.756 +  },
   1.757 +
   1.758 +  onWidgetRemoved: function(aWidgetId, aArea) {
   1.759 +    let areaNodes = gBuildAreas.get(aArea);
   1.760 +    if (!areaNodes) {
   1.761 +      return;
   1.762 +    }
   1.763 +
   1.764 +    let area = gAreas.get(aArea);
   1.765 +    let isToolbar = area.get("type") == CustomizableUI.TYPE_TOOLBAR;
   1.766 +    let isOverflowable = isToolbar && area.get("overflowable");
   1.767 +    let showInPrivateBrowsing = gPalette.has(aWidgetId)
   1.768 +                              ? gPalette.get(aWidgetId).showInPrivateBrowsing
   1.769 +                              : true;
   1.770 +
   1.771 +    for (let areaNode of areaNodes) {
   1.772 +      let window = areaNode.ownerDocument.defaultView;
   1.773 +      if (!showInPrivateBrowsing &&
   1.774 +          PrivateBrowsingUtils.isWindowPrivate(window)) {
   1.775 +        continue;
   1.776 +      }
   1.777 +
   1.778 +      let widgetNode = window.document.getElementById(aWidgetId);
   1.779 +      if (!widgetNode) {
   1.780 +        INFO("Widget not found, unable to remove");
   1.781 +        continue;
   1.782 +      }
   1.783 +      let container = areaNode.customizationTarget;
   1.784 +      if (isOverflowable) {
   1.785 +        container = areaNode.overflowable.getContainerFor(widgetNode);
   1.786 +      }
   1.787 +
   1.788 +      this.notifyListeners("onWidgetBeforeDOMChange", widgetNode, null, container, true);
   1.789 +
   1.790 +      // We remove location attributes here to make sure they're gone too when a
   1.791 +      // widget is removed from a toolbar to the palette. See bug 930950.
   1.792 +      this.removeLocationAttributes(widgetNode);
   1.793 +      // We also need to remove the panel context menu if it's there:
   1.794 +      this.ensureButtonContextMenu(widgetNode);
   1.795 +      widgetNode.removeAttribute("wrap");
   1.796 +      if (gPalette.has(aWidgetId) || this.isSpecialWidget(aWidgetId)) {
   1.797 +        container.removeChild(widgetNode);
   1.798 +      } else {
   1.799 +        areaNode.toolbox.palette.appendChild(widgetNode);
   1.800 +      }
   1.801 +      this.notifyListeners("onWidgetAfterDOMChange", widgetNode, null, container, true);
   1.802 +
   1.803 +      if (isToolbar) {
   1.804 +        areaNode.setAttribute("currentset", gPlacements.get(aArea).join(','));
   1.805 +      }
   1.806 +
   1.807 +      let windowCache = gSingleWrapperCache.get(window);
   1.808 +      if (windowCache) {
   1.809 +        windowCache.delete(aWidgetId);
   1.810 +      }
   1.811 +    }
   1.812 +    if (!gResetting) {
   1.813 +      this._clearPreviousUIState();
   1.814 +    }
   1.815 +  },
   1.816 +
   1.817 +  onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) {
   1.818 +    this.insertNode(aWidgetId, aArea, aNewPosition);
   1.819 +    if (!gResetting) {
   1.820 +      this._clearPreviousUIState();
   1.821 +    }
   1.822 +  },
   1.823 +
   1.824 +  onCustomizeEnd: function(aWindow) {
   1.825 +    this._clearPreviousUIState();
   1.826 +  },
   1.827 +
   1.828 +  registerBuildArea: function(aArea, aNode) {
   1.829 +    // We ensure that the window is registered to have its customization data
   1.830 +    // cleaned up when unloading.
   1.831 +    let window = aNode.ownerDocument.defaultView;
   1.832 +    if (window.closed) {
   1.833 +      return;
   1.834 +    }
   1.835 +    this.registerBuildWindow(window);
   1.836 +
   1.837 +    // Also register this build area's toolbox.
   1.838 +    if (aNode.toolbox) {
   1.839 +      gBuildWindows.get(window).add(aNode.toolbox);
   1.840 +    }
   1.841 +
   1.842 +    if (!gBuildAreas.has(aArea)) {
   1.843 +      gBuildAreas.set(aArea, new Set());
   1.844 +    }
   1.845 +
   1.846 +    gBuildAreas.get(aArea).add(aNode);
   1.847 +
   1.848 +    // Give a class to all customize targets to be used for styling in Customize Mode
   1.849 +    let customizableNode = this.getCustomizeTargetForArea(aArea, window);
   1.850 +    customizableNode.classList.add("customization-target");
   1.851 +  },
   1.852 +
   1.853 +  registerBuildWindow: function(aWindow) {
   1.854 +    if (!gBuildWindows.has(aWindow)) {
   1.855 +      gBuildWindows.set(aWindow, new Set());
   1.856 +
   1.857 +      aWindow.addEventListener("unload", this);
   1.858 +      aWindow.addEventListener("command", this, true);
   1.859 +
   1.860 +      this.notifyListeners("onWindowOpened", aWindow);
   1.861 +    }
   1.862 +  },
   1.863 +
   1.864 +  unregisterBuildWindow: function(aWindow) {
   1.865 +    aWindow.removeEventListener("unload", this);
   1.866 +    aWindow.removeEventListener("command", this, true);
   1.867 +    gPanelsForWindow.delete(aWindow);
   1.868 +    gBuildWindows.delete(aWindow);
   1.869 +    gSingleWrapperCache.delete(aWindow);
   1.870 +    let document = aWindow.document;
   1.871 +
   1.872 +    for (let [areaId, areaNodes] of gBuildAreas) {
   1.873 +      let areaProperties = gAreas.get(areaId);
   1.874 +      for (let node of areaNodes) {
   1.875 +        if (node.ownerDocument == document) {
   1.876 +          this.notifyListeners("onAreaNodeUnregistered", areaId, node.customizationTarget,
   1.877 +                               CustomizableUI.REASON_WINDOW_CLOSED);
   1.878 +          if (areaProperties.has("overflowable")) {
   1.879 +            node.overflowable.uninit();
   1.880 +            node.overflowable = null;
   1.881 +          }
   1.882 +          areaNodes.delete(node);
   1.883 +        }
   1.884 +      }
   1.885 +    }
   1.886 +
   1.887 +    for (let [,widget] of gPalette) {
   1.888 +      widget.instances.delete(document);
   1.889 +      this.notifyListeners("onWidgetInstanceRemoved", widget.id, document);
   1.890 +    }
   1.891 +
   1.892 +    for (let [area, areaMap] of gPendingBuildAreas) {
   1.893 +      let toDelete = [];
   1.894 +      for (let [areaNode, ] of areaMap) {
   1.895 +        if (areaNode.ownerDocument == document) {
   1.896 +          toDelete.push(areaNode);
   1.897 +        }
   1.898 +      }
   1.899 +      for (let areaNode of toDelete) {
   1.900 +        areaMap.delete(toDelete);
   1.901 +      }
   1.902 +    }
   1.903 +
   1.904 +    this.notifyListeners("onWindowClosed", aWindow);
   1.905 +  },
   1.906 +
   1.907 +  setLocationAttributes: function(aNode, aArea) {
   1.908 +    let props = gAreas.get(aArea);
   1.909 +    if (!props) {
   1.910 +      throw new Error("Expected area " + aArea + " to have a properties Map " +
   1.911 +                      "associated with it.");
   1.912 +    }
   1.913 +
   1.914 +    aNode.setAttribute("cui-areatype", props.get("type") || "");
   1.915 +    let anchor = props.get("anchor");
   1.916 +    if (anchor) {
   1.917 +      aNode.setAttribute("cui-anchorid", anchor);
   1.918 +    } else {
   1.919 +      aNode.removeAttribute("cui-anchorid");
   1.920 +    }
   1.921 +  },
   1.922 +
   1.923 +  removeLocationAttributes: function(aNode) {
   1.924 +    aNode.removeAttribute("cui-areatype");
   1.925 +    aNode.removeAttribute("cui-anchorid");
   1.926 +  },
   1.927 +
   1.928 +  insertNode: function(aWidgetId, aArea, aPosition, isNew) {
   1.929 +    let areaNodes = gBuildAreas.get(aArea);
   1.930 +    if (!areaNodes) {
   1.931 +      return;
   1.932 +    }
   1.933 +
   1.934 +    let placements = gPlacements.get(aArea);
   1.935 +    if (!placements) {
   1.936 +      ERROR("Could not find any placements for " + aArea +
   1.937 +            " when moving a widget.");
   1.938 +      return;
   1.939 +    }
   1.940 +
   1.941 +    // Go through each of the nodes associated with this area and move the
   1.942 +    // widget to the requested location.
   1.943 +    for (let areaNode of areaNodes) {
   1.944 +      this.insertNodeInWindow(aWidgetId, areaNode, isNew);
   1.945 +    }
   1.946 +  },
   1.947 +
   1.948 +  insertNodeInWindow: function(aWidgetId, aAreaNode, isNew) {
   1.949 +    let window = aAreaNode.ownerDocument.defaultView;
   1.950 +    let showInPrivateBrowsing = gPalette.has(aWidgetId)
   1.951 +                              ? gPalette.get(aWidgetId).showInPrivateBrowsing
   1.952 +                              : true;
   1.953 +
   1.954 +    if (!showInPrivateBrowsing && PrivateBrowsingUtils.isWindowPrivate(window)) {
   1.955 +      return;
   1.956 +    }
   1.957 +
   1.958 +    let [, widgetNode] = this.getWidgetNode(aWidgetId, window);
   1.959 +    if (!widgetNode) {
   1.960 +      ERROR("Widget '" + aWidgetId + "' not found, unable to move");
   1.961 +      return;
   1.962 +    }
   1.963 +
   1.964 +    let areaId = aAreaNode.id;
   1.965 +    if (isNew) {
   1.966 +      this.ensureButtonContextMenu(widgetNode, aAreaNode);
   1.967 +      if (widgetNode.localName == "toolbarbutton" && areaId == CustomizableUI.AREA_PANEL) {
   1.968 +        widgetNode.setAttribute("wrap", "true");
   1.969 +      }
   1.970 +    }
   1.971 +
   1.972 +    let [insertionContainer, nextNode] = this.findInsertionPoints(widgetNode, aAreaNode);
   1.973 +    this.insertWidgetBefore(widgetNode, nextNode, insertionContainer, areaId);
   1.974 +
   1.975 +    if (gAreas.get(areaId).get("type") == CustomizableUI.TYPE_TOOLBAR) {
   1.976 +      aAreaNode.setAttribute("currentset", gPlacements.get(areaId).join(','));
   1.977 +    }
   1.978 +  },
   1.979 +
   1.980 +  findInsertionPoints: function(aNode, aAreaNode) {
   1.981 +    let areaId = aAreaNode.id;
   1.982 +    let props = gAreas.get(areaId);
   1.983 +
   1.984 +    // For overflowable toolbars, rely on them (because the work is more complicated):
   1.985 +    if (props.get("type") == CustomizableUI.TYPE_TOOLBAR && props.get("overflowable")) {
   1.986 +      return aAreaNode.overflowable.findOverflowedInsertionPoints(aNode);
   1.987 +    }
   1.988 +
   1.989 +    let container = aAreaNode.customizationTarget;
   1.990 +    let placements = gPlacements.get(areaId);
   1.991 +    let nodeIndex = placements.indexOf(aNode.id);
   1.992 +
   1.993 +    while (++nodeIndex < placements.length) {
   1.994 +      let nextNodeId = placements[nodeIndex];
   1.995 +      let nextNode = container.getElementsByAttribute("id", nextNodeId).item(0);
   1.996 +
   1.997 +      if (nextNode) {
   1.998 +        return [container, nextNode];
   1.999 +      }
  1.1000 +    }
  1.1001 +
  1.1002 +    return [container, null];
  1.1003 +  },
  1.1004 +
  1.1005 +  insertWidgetBefore: function(aNode, aNextNode, aContainer, aArea) {
  1.1006 +    this.notifyListeners("onWidgetBeforeDOMChange", aNode, aNextNode, aContainer);
  1.1007 +    this.setLocationAttributes(aNode, aArea);
  1.1008 +    aContainer.insertBefore(aNode, aNextNode);
  1.1009 +    this.notifyListeners("onWidgetAfterDOMChange", aNode, aNextNode, aContainer);
  1.1010 +  },
  1.1011 +
  1.1012 +  handleEvent: function(aEvent) {
  1.1013 +    switch (aEvent.type) {
  1.1014 +      case "command":
  1.1015 +        if (!this._originalEventInPanel(aEvent)) {
  1.1016 +          break;
  1.1017 +        }
  1.1018 +        aEvent = aEvent.sourceEvent;
  1.1019 +        // Fall through
  1.1020 +      case "click":
  1.1021 +      case "keypress":
  1.1022 +        this.maybeAutoHidePanel(aEvent);
  1.1023 +        break;
  1.1024 +      case "unload":
  1.1025 +        this.unregisterBuildWindow(aEvent.currentTarget);
  1.1026 +        break;
  1.1027 +    }
  1.1028 +  },
  1.1029 +
  1.1030 +  _originalEventInPanel: function(aEvent) {
  1.1031 +    let e = aEvent.sourceEvent;
  1.1032 +    if (!e) {
  1.1033 +      return false;
  1.1034 +    }
  1.1035 +    let node = this._getPanelForNode(e.target);
  1.1036 +    if (!node) {
  1.1037 +      return false;
  1.1038 +    }
  1.1039 +    let win = e.view;
  1.1040 +    let panels = gPanelsForWindow.get(win);
  1.1041 +    return !!panels && panels.has(node);
  1.1042 +  },
  1.1043 +
  1.1044 +  isSpecialWidget: function(aId) {
  1.1045 +    return (aId.startsWith(kSpecialWidgetPfx) ||
  1.1046 +            aId.startsWith("separator") ||
  1.1047 +            aId.startsWith("spring") ||
  1.1048 +            aId.startsWith("spacer"));
  1.1049 +  },
  1.1050 +
  1.1051 +  ensureSpecialWidgetId: function(aId) {
  1.1052 +    let nodeType = aId.match(/spring|spacer|separator/)[0];
  1.1053 +    // If the ID we were passed isn't a generated one, generate one now:
  1.1054 +    if (nodeType == aId) {
  1.1055 +      // Ids are differentiated through a unique count suffix.
  1.1056 +      return kSpecialWidgetPfx + aId + (++gNewElementCount);
  1.1057 +    }
  1.1058 +    return aId;
  1.1059 +  },
  1.1060 +
  1.1061 +  createSpecialWidget: function(aId, aDocument) {
  1.1062 +    let nodeName = "toolbar" + aId.match(/spring|spacer|separator/)[0];
  1.1063 +    let node = aDocument.createElementNS(kNSXUL, nodeName);
  1.1064 +    node.id = this.ensureSpecialWidgetId(aId);
  1.1065 +    if (nodeName == "toolbarspring") {
  1.1066 +      node.flex = 1;
  1.1067 +    }
  1.1068 +    return node;
  1.1069 +  },
  1.1070 +
  1.1071 +  /* Find a XUL-provided widget in a window. Don't try to use this
  1.1072 +   * for an API-provided widget or a special widget.
  1.1073 +   */
  1.1074 +  findWidgetInWindow: function(aId, aWindow) {
  1.1075 +    if (!gBuildWindows.has(aWindow)) {
  1.1076 +      throw new Error("Build window not registered");
  1.1077 +    }
  1.1078 +
  1.1079 +    if (!aId) {
  1.1080 +      ERROR("findWidgetInWindow was passed an empty string.");
  1.1081 +      return null;
  1.1082 +    }
  1.1083 +
  1.1084 +    let document = aWindow.document;
  1.1085 +
  1.1086 +    // look for a node with the same id, as the node may be
  1.1087 +    // in a different toolbar.
  1.1088 +    let node = document.getElementById(aId);
  1.1089 +    if (node) {
  1.1090 +      let parent = node.parentNode;
  1.1091 +      while (parent && !(parent.customizationTarget ||
  1.1092 +                         parent == aWindow.gNavToolbox.palette)) {
  1.1093 +        parent = parent.parentNode;
  1.1094 +      }
  1.1095 +
  1.1096 +      if (parent) {
  1.1097 +        let nodeInArea = node.parentNode.localName == "toolbarpaletteitem" ?
  1.1098 +                         node.parentNode : node;
  1.1099 +        // Check if we're in a customization target, or in the palette:
  1.1100 +        if ((parent.customizationTarget == nodeInArea.parentNode &&
  1.1101 +             gBuildWindows.get(aWindow).has(parent.toolbox)) ||
  1.1102 +            aWindow.gNavToolbox.palette == nodeInArea.parentNode) {
  1.1103 +          // Normalize the removable attribute. For backwards compat, if
  1.1104 +          // the widget is not located in a toolbox palette then absence
  1.1105 +          // of the "removable" attribute means it is not removable.
  1.1106 +          if (!node.hasAttribute("removable")) {
  1.1107 +            // If we first see this in customization mode, it may be in the
  1.1108 +            // customization palette instead of the toolbox palette.
  1.1109 +            node.setAttribute("removable", !parent.customizationTarget);
  1.1110 +          }
  1.1111 +          return node;
  1.1112 +        }
  1.1113 +      }
  1.1114 +    }
  1.1115 +
  1.1116 +    let toolboxes = gBuildWindows.get(aWindow);
  1.1117 +    for (let toolbox of toolboxes) {
  1.1118 +      if (toolbox.palette) {
  1.1119 +        // Attempt to locate a node with a matching ID within
  1.1120 +        // the palette.
  1.1121 +        let node = toolbox.palette.getElementsByAttribute("id", aId)[0];
  1.1122 +        if (node) {
  1.1123 +          // Normalize the removable attribute. For backwards compat, this
  1.1124 +          // is optional if the widget is located in the toolbox palette,
  1.1125 +          // and defaults to *true*, unlike if it was located elsewhere.
  1.1126 +          if (!node.hasAttribute("removable")) {
  1.1127 +            node.setAttribute("removable", true);
  1.1128 +          }
  1.1129 +          return node;
  1.1130 +        }
  1.1131 +      }
  1.1132 +    }
  1.1133 +    return null;
  1.1134 +  },
  1.1135 +
  1.1136 +  buildWidget: function(aDocument, aWidget) {
  1.1137 +    if (typeof aWidget == "string") {
  1.1138 +      aWidget = gPalette.get(aWidget);
  1.1139 +    }
  1.1140 +    if (!aWidget) {
  1.1141 +      throw new Error("buildWidget was passed a non-widget to build.");
  1.1142 +    }
  1.1143 +
  1.1144 +    LOG("Building " + aWidget.id + " of type " + aWidget.type);
  1.1145 +
  1.1146 +    let node;
  1.1147 +    if (aWidget.type == "custom") {
  1.1148 +      if (aWidget.onBuild) {
  1.1149 +        node = aWidget.onBuild(aDocument);
  1.1150 +      }
  1.1151 +      if (!node || !(node instanceof aDocument.defaultView.XULElement))
  1.1152 +        ERROR("Custom widget with id " + aWidget.id + " does not return a valid node");
  1.1153 +    }
  1.1154 +    else {
  1.1155 +      if (aWidget.onBeforeCreated) {
  1.1156 +        aWidget.onBeforeCreated(aDocument);
  1.1157 +      }
  1.1158 +      node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
  1.1159 +
  1.1160 +      node.setAttribute("id", aWidget.id);
  1.1161 +      node.setAttribute("widget-id", aWidget.id);
  1.1162 +      node.setAttribute("widget-type", aWidget.type);
  1.1163 +      if (aWidget.disabled) {
  1.1164 +        node.setAttribute("disabled", true);
  1.1165 +      }
  1.1166 +      node.setAttribute("removable", aWidget.removable);
  1.1167 +      node.setAttribute("overflows", aWidget.overflows);
  1.1168 +      node.setAttribute("label", this.getLocalizedProperty(aWidget, "label"));
  1.1169 +      let additionalTooltipArguments = [];
  1.1170 +      if (aWidget.shortcutId) {
  1.1171 +        let keyEl = aDocument.getElementById(aWidget.shortcutId);
  1.1172 +        if (keyEl) {
  1.1173 +          additionalTooltipArguments.push(ShortcutUtils.prettifyShortcut(keyEl));
  1.1174 +        } else {
  1.1175 +          ERROR("Key element with id '" + aWidget.shortcutId + "' for widget '" + aWidget.id +
  1.1176 +                "' not found!");
  1.1177 +        }
  1.1178 +      }
  1.1179 +
  1.1180 +      let tooltip = this.getLocalizedProperty(aWidget, "tooltiptext", additionalTooltipArguments);
  1.1181 +      node.setAttribute("tooltiptext", tooltip);
  1.1182 +      node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional");
  1.1183 +
  1.1184 +      let commandHandler = this.handleWidgetCommand.bind(this, aWidget, node);
  1.1185 +      node.addEventListener("command", commandHandler, false);
  1.1186 +      let clickHandler = this.handleWidgetClick.bind(this, aWidget, node);
  1.1187 +      node.addEventListener("click", clickHandler, false);
  1.1188 +
  1.1189 +      // If the widget has a view, and has view showing / hiding listeners,
  1.1190 +      // hook those up to this widget.
  1.1191 +      if (aWidget.type == "view") {
  1.1192 +        LOG("Widget " + aWidget.id + " has a view. Auto-registering event handlers.");
  1.1193 +        let viewNode = aDocument.getElementById(aWidget.viewId);
  1.1194 +
  1.1195 +        if (viewNode) {
  1.1196 +          // PanelUI relies on the .PanelUI-subView class to be able to show only
  1.1197 +          // one sub-view at a time.
  1.1198 +          viewNode.classList.add("PanelUI-subView");
  1.1199 +
  1.1200 +          for (let eventName of kSubviewEvents) {
  1.1201 +            let handler = "on" + eventName;
  1.1202 +            if (typeof aWidget[handler] == "function") {
  1.1203 +              viewNode.addEventListener(eventName, aWidget[handler], false);
  1.1204 +            }
  1.1205 +          }
  1.1206 +
  1.1207 +          LOG("Widget " + aWidget.id + " showing and hiding event handlers set.");
  1.1208 +        } else {
  1.1209 +          ERROR("Could not find the view node with id: " + aWidget.viewId +
  1.1210 +                ", for widget: " + aWidget.id + ".");
  1.1211 +        }
  1.1212 +      }
  1.1213 +
  1.1214 +      if (aWidget.onCreated) {
  1.1215 +        aWidget.onCreated(node);
  1.1216 +      }
  1.1217 +    }
  1.1218 +
  1.1219 +    aWidget.instances.set(aDocument, node);
  1.1220 +    return node;
  1.1221 +  },
  1.1222 +
  1.1223 +  getLocalizedProperty: function(aWidget, aProp, aFormatArgs, aDef) {
  1.1224 +    if (typeof aWidget == "string") {
  1.1225 +      aWidget = gPalette.get(aWidget);
  1.1226 +    }
  1.1227 +    if (!aWidget) {
  1.1228 +      throw new Error("getLocalizedProperty was passed a non-widget to work with.");
  1.1229 +    }
  1.1230 +    let def, name;
  1.1231 +    // Let widgets pass their own string identifiers or strings, so that
  1.1232 +    // we can use strings which aren't the default (in case string ids change)
  1.1233 +    // and so that non-builtin-widgets can also provide labels, tooltips, etc.
  1.1234 +    if (aWidget[aProp]) {
  1.1235 +      name = aWidget[aProp];
  1.1236 +      // By using this as the default, if a widget provides a full string rather
  1.1237 +      // than a string ID for localization, we will fall back to that string
  1.1238 +      // and return that.
  1.1239 +      def = aDef || name;
  1.1240 +    } else {
  1.1241 +      name = aWidget.id + "." + aProp;
  1.1242 +      def = aDef || "";
  1.1243 +    }
  1.1244 +    try {
  1.1245 +      if (Array.isArray(aFormatArgs) && aFormatArgs.length) {
  1.1246 +        return gWidgetsBundle.formatStringFromName(name, aFormatArgs,
  1.1247 +          aFormatArgs.length) || def;
  1.1248 +      }
  1.1249 +      return gWidgetsBundle.GetStringFromName(name) || def;
  1.1250 +    } catch(ex) {
  1.1251 +      if (!def) {
  1.1252 +        ERROR("Could not localize property '" + name + "'.");
  1.1253 +      }
  1.1254 +    }
  1.1255 +    return def;
  1.1256 +  },
  1.1257 +
  1.1258 +  handleWidgetCommand: function(aWidget, aNode, aEvent) {
  1.1259 +    LOG("handleWidgetCommand");
  1.1260 +
  1.1261 +    if (aWidget.type == "button") {
  1.1262 +      if (aWidget.onCommand) {
  1.1263 +        try {
  1.1264 +          aWidget.onCommand.call(null, aEvent);
  1.1265 +        } catch (e) {
  1.1266 +          ERROR(e);
  1.1267 +        }
  1.1268 +      } else {
  1.1269 +        //XXXunf Need to think this through more, and formalize.
  1.1270 +        Services.obs.notifyObservers(aNode,
  1.1271 +                                     "customizedui-widget-command",
  1.1272 +                                     aWidget.id);
  1.1273 +      }
  1.1274 +    } else if (aWidget.type == "view") {
  1.1275 +      let ownerWindow = aNode.ownerDocument.defaultView;
  1.1276 +      let area = this.getPlacementOfWidget(aNode.id).area;
  1.1277 +      let anchor = aNode;
  1.1278 +      if (area != CustomizableUI.AREA_PANEL) {
  1.1279 +        let wrapper = this.wrapWidget(aWidget.id).forWindow(ownerWindow);
  1.1280 +        if (wrapper && wrapper.anchor) {
  1.1281 +          this.hidePanelForNode(aNode);
  1.1282 +          anchor = wrapper.anchor;
  1.1283 +        }
  1.1284 +      }
  1.1285 +      ownerWindow.PanelUI.showSubView(aWidget.viewId, anchor, area);
  1.1286 +    }
  1.1287 +  },
  1.1288 +
  1.1289 +  handleWidgetClick: function(aWidget, aNode, aEvent) {
  1.1290 +    LOG("handleWidgetClick");
  1.1291 +    if (aWidget.onClick) {
  1.1292 +      try {
  1.1293 +        aWidget.onClick.call(null, aEvent);
  1.1294 +      } catch(e) {
  1.1295 +        Cu.reportError(e);
  1.1296 +      }
  1.1297 +    } else {
  1.1298 +      //XXXunf Need to think this through more, and formalize.
  1.1299 +      Services.obs.notifyObservers(aNode, "customizedui-widget-click", aWidget.id);
  1.1300 +    }
  1.1301 +  },
  1.1302 +
  1.1303 +  _getPanelForNode: function(aNode) {
  1.1304 +    let panel = aNode;
  1.1305 +    while (panel && panel.localName != "panel")
  1.1306 +      panel = panel.parentNode;
  1.1307 +    return panel;
  1.1308 +  },
  1.1309 +
  1.1310 +  /*
  1.1311 +   * If people put things in the panel which need more than single-click interaction,
  1.1312 +   * we don't want to close it. Right now we check for text inputs and menu buttons.
  1.1313 +   * We also check for being outside of any toolbaritem/toolbarbutton, ie on a blank
  1.1314 +   * part of the menu.
  1.1315 +   */
  1.1316 +  _isOnInteractiveElement: function(aEvent) {
  1.1317 +    function getMenuPopupForDescendant(aNode) {
  1.1318 +      let lastPopup = null;
  1.1319 +      while (aNode && aNode.parentNode &&
  1.1320 +             aNode.parentNode.localName.startsWith("menu")) {
  1.1321 +        lastPopup = aNode.localName == "menupopup" ? aNode : lastPopup;
  1.1322 +        aNode = aNode.parentNode;
  1.1323 +      }
  1.1324 +      return lastPopup;
  1.1325 +    }
  1.1326 +
  1.1327 +    let target = aEvent.originalTarget;
  1.1328 +    let panel = this._getPanelForNode(aEvent.currentTarget);
  1.1329 +    // This can happen in e.g. customize mode. If there's no panel,
  1.1330 +    // there's clearly nothing for us to close; pretend we're interactive.
  1.1331 +    if (!panel) {
  1.1332 +      return true;
  1.1333 +    }
  1.1334 +    // We keep track of:
  1.1335 +    // whether we're in an input container (text field)
  1.1336 +    let inInput = false;
  1.1337 +    // whether we're in a popup/context menu
  1.1338 +    let inMenu = false;
  1.1339 +    // whether we're in a toolbarbutton/toolbaritem
  1.1340 +    let inItem = false;
  1.1341 +    // whether the current menuitem has a valid closemenu attribute
  1.1342 +    let menuitemCloseMenu = "auto";
  1.1343 +    // whether the toolbarbutton/item has a valid closemenu attribute.
  1.1344 +    let closemenu = "auto";
  1.1345 +
  1.1346 +    // While keeping track of that, we go from the original target back up,
  1.1347 +    // to the panel if we have to. We bail as soon as we find an input,
  1.1348 +    // a toolbarbutton/item, or the panel:
  1.1349 +    while (true && target) {
  1.1350 +      let tagName = target.localName;
  1.1351 +      inInput = tagName == "input" || tagName == "textbox";
  1.1352 +      inItem = tagName == "toolbaritem" || tagName == "toolbarbutton";
  1.1353 +      let isMenuItem = tagName == "menuitem";
  1.1354 +      inMenu = inMenu || isMenuItem;
  1.1355 +      if (inItem && target.hasAttribute("closemenu")) {
  1.1356 +        let closemenuVal = target.getAttribute("closemenu");
  1.1357 +        closemenu = (closemenuVal == "single" || closemenuVal == "none") ?
  1.1358 +                    closemenuVal : "auto";
  1.1359 +      }
  1.1360 +
  1.1361 +      if (isMenuItem && target.hasAttribute("closemenu")) {
  1.1362 +        let closemenuVal = target.getAttribute("closemenu");
  1.1363 +        menuitemCloseMenu = (closemenuVal == "single" || closemenuVal == "none") ?
  1.1364 +                            closemenuVal : "auto";
  1.1365 +      }
  1.1366 +      // This isn't in the loop condition because we want to break before
  1.1367 +      // changing |target| if any of these conditions are true
  1.1368 +      if (inInput || inItem || target == panel) {
  1.1369 +        break;
  1.1370 +      }
  1.1371 +      // We need specific code for popups: the item on which they were invoked
  1.1372 +      // isn't necessarily in their parentNode chain:
  1.1373 +      if (isMenuItem) {
  1.1374 +        let topmostMenuPopup = getMenuPopupForDescendant(target);
  1.1375 +        target = (topmostMenuPopup && topmostMenuPopup.triggerNode) ||
  1.1376 +                 target.parentNode;
  1.1377 +      } else {
  1.1378 +        target = target.parentNode;
  1.1379 +      }
  1.1380 +    }
  1.1381 +    // If the user clicked a menu item...
  1.1382 +    if (inMenu) {
  1.1383 +      // We care if we're in an input also,
  1.1384 +      // or if the user specified closemenu!="auto":
  1.1385 +      if (inInput || menuitemCloseMenu != "auto") {
  1.1386 +        return true;
  1.1387 +      }
  1.1388 +      // Otherwise, we're probably fine to close the panel
  1.1389 +      return false;
  1.1390 +    }
  1.1391 +    // If we're not in a menu, and we *are* in a type="menu" toolbarbutton,
  1.1392 +    // we'll now interact with the menu
  1.1393 +    if (inItem && target.getAttribute("type") == "menu") {
  1.1394 +      return true;
  1.1395 +    }
  1.1396 +    // If we're not in a menu, and we *are* in a type="menu-button" toolbarbutton,
  1.1397 +    // it depends whether we're in the dropmarker or the 'real' button:
  1.1398 +    if (inItem && target.getAttribute("type") == "menu-button") {
  1.1399 +      // 'real' button (which has a single action):
  1.1400 +      if (target.getAttribute("anonid") == "button") {
  1.1401 +        return closemenu != "none";
  1.1402 +      }
  1.1403 +      // otherwise, this is the outer button, and the user will now
  1.1404 +      // interact with the menu:
  1.1405 +      return true;
  1.1406 +    }
  1.1407 +    return inInput || !inItem;
  1.1408 +  },
  1.1409 +
  1.1410 +  hidePanelForNode: function(aNode) {
  1.1411 +    let panel = this._getPanelForNode(aNode);
  1.1412 +    if (panel) {
  1.1413 +      panel.hidePopup();
  1.1414 +    }
  1.1415 +  },
  1.1416 +
  1.1417 +  maybeAutoHidePanel: function(aEvent) {
  1.1418 +    if (aEvent.type == "keypress") {
  1.1419 +      if (aEvent.keyCode != aEvent.DOM_VK_RETURN) {
  1.1420 +        return;
  1.1421 +      }
  1.1422 +      // If the user hit enter/return, we don't check preventDefault - it makes sense
  1.1423 +      // that this was prevented, but we probably still want to close the panel.
  1.1424 +      // If consumers don't want this to happen, they should specify the closemenu
  1.1425 +      // attribute.
  1.1426 +
  1.1427 +    } else if (aEvent.type != "command") { // mouse events:
  1.1428 +      if (aEvent.defaultPrevented || aEvent.button != 0) {
  1.1429 +        return;
  1.1430 +      }
  1.1431 +      let isInteractive = this._isOnInteractiveElement(aEvent);
  1.1432 +      LOG("maybeAutoHidePanel: interactive ? " + isInteractive);
  1.1433 +      if (isInteractive) {
  1.1434 +        return;
  1.1435 +      }
  1.1436 +    }
  1.1437 +
  1.1438 +    // We can't use event.target because we might have passed a panelview
  1.1439 +    // anonymous content boundary as well, and so target points to the
  1.1440 +    // panelmultiview in that case. Unfortunately, this means we get
  1.1441 +    // anonymous child nodes instead of the real ones, so looking for the 
  1.1442 +    // 'stoooop, don't close me' attributes is more involved.
  1.1443 +    let target = aEvent.originalTarget;
  1.1444 +    let closemenu = "auto";
  1.1445 +    let widgetType = "button";
  1.1446 +    while (target.parentNode && target.localName != "panel") {
  1.1447 +      closemenu = target.getAttribute("closemenu");
  1.1448 +      widgetType = target.getAttribute("widget-type");
  1.1449 +      if (closemenu == "none" || closemenu == "single" ||
  1.1450 +          widgetType == "view") {
  1.1451 +        break;
  1.1452 +      }
  1.1453 +      target = target.parentNode;
  1.1454 +    }
  1.1455 +    if (closemenu == "none" || widgetType == "view") {
  1.1456 +      return;
  1.1457 +    }
  1.1458 +
  1.1459 +    if (closemenu == "single") {
  1.1460 +      let panel = this._getPanelForNode(target);
  1.1461 +      let multiview = panel.querySelector("panelmultiview");
  1.1462 +      if (multiview.showingSubView) {
  1.1463 +        multiview.showMainView();
  1.1464 +        return;
  1.1465 +      }
  1.1466 +    }
  1.1467 +
  1.1468 +    // If we get here, we can actually hide the popup:
  1.1469 +    this.hidePanelForNode(aEvent.target);
  1.1470 +  },
  1.1471 +
  1.1472 +  getUnusedWidgets: function(aWindowPalette) {
  1.1473 +    let window = aWindowPalette.ownerDocument.defaultView;
  1.1474 +    let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
  1.1475 +    // We use a Set because there can be overlap between the widgets in
  1.1476 +    // gPalette and the items in the palette, especially after the first
  1.1477 +    // customization, since programmatically generated widgets will remain
  1.1478 +    // in the toolbox palette.
  1.1479 +    let widgets = new Set();
  1.1480 +
  1.1481 +    // It's possible that some widgets have been defined programmatically and
  1.1482 +    // have not been overlayed into the palette. We can find those inside
  1.1483 +    // gPalette.
  1.1484 +    for (let [id, widget] of gPalette) {
  1.1485 +      if (!widget.currentArea) {
  1.1486 +        if (widget.showInPrivateBrowsing || !isWindowPrivate) {
  1.1487 +          widgets.add(id);
  1.1488 +        }
  1.1489 +      }
  1.1490 +    }
  1.1491 +
  1.1492 +    LOG("Iterating the actual nodes of the window palette");
  1.1493 +    for (let node of aWindowPalette.children) {
  1.1494 +      LOG("In palette children: " + node.id);
  1.1495 +      if (node.id && !this.getPlacementOfWidget(node.id)) {
  1.1496 +        widgets.add(node.id);
  1.1497 +      }
  1.1498 +    }
  1.1499 +
  1.1500 +    return [...widgets];
  1.1501 +  },
  1.1502 +
  1.1503 +  getPlacementOfWidget: function(aWidgetId, aOnlyRegistered, aDeadAreas) {
  1.1504 +    if (aOnlyRegistered && !this.widgetExists(aWidgetId)) {
  1.1505 +      return null;
  1.1506 +    }
  1.1507 +
  1.1508 +    for (let [area, placements] of gPlacements) {
  1.1509 +      if (!gAreas.has(area) && !aDeadAreas) {
  1.1510 +        continue;
  1.1511 +      }
  1.1512 +      let index = placements.indexOf(aWidgetId);
  1.1513 +      if (index != -1) {
  1.1514 +        return { area: area, position: index };
  1.1515 +      }
  1.1516 +    }
  1.1517 +
  1.1518 +    return null;
  1.1519 +  },
  1.1520 +
  1.1521 +  widgetExists: function(aWidgetId) {
  1.1522 +    if (gPalette.has(aWidgetId) || this.isSpecialWidget(aWidgetId)) {
  1.1523 +      return true;
  1.1524 +    }
  1.1525 +
  1.1526 +    // Destroyed API widgets are in gSeenWidgets, but not in gPalette:
  1.1527 +    if (gSeenWidgets.has(aWidgetId)) {
  1.1528 +      return false;
  1.1529 +    }
  1.1530 +
  1.1531 +    // We're assuming XUL widgets always exist, as it's much harder to check,
  1.1532 +    // and checking would be much more error prone.
  1.1533 +    return true;
  1.1534 +  },
  1.1535 +
  1.1536 +  addWidgetToArea: function(aWidgetId, aArea, aPosition, aInitialAdd) {
  1.1537 +    if (!gAreas.has(aArea)) {
  1.1538 +      throw new Error("Unknown customization area: " + aArea);
  1.1539 +    }
  1.1540 +
  1.1541 +    // Hack: don't want special widgets in the panel (need to check here as well
  1.1542 +    // as in canWidgetMoveToArea because the menu panel is lazy):
  1.1543 +    if (gAreas.get(aArea).get("type") == CustomizableUI.TYPE_MENU_PANEL &&
  1.1544 +        this.isSpecialWidget(aWidgetId)) {
  1.1545 +      return;
  1.1546 +    }
  1.1547 +
  1.1548 +    // If this is a lazy area that hasn't been restored yet, we can't yet modify
  1.1549 +    // it - would would at least like to add to it. So we keep track of it in
  1.1550 +    // gFuturePlacements,  and use that to add it when restoring the area. We
  1.1551 +    // throw away aPosition though, as that can only be bogus if the area hasn't
  1.1552 +    // yet been restorted (caller can't possibly know where its putting the
  1.1553 +    // widget in relation to other widgets).
  1.1554 +    if (this.isAreaLazy(aArea)) {
  1.1555 +      gFuturePlacements.get(aArea).add(aWidgetId);
  1.1556 +      return;
  1.1557 +    }
  1.1558 +
  1.1559 +    if (this.isSpecialWidget(aWidgetId)) {
  1.1560 +      aWidgetId = this.ensureSpecialWidgetId(aWidgetId);
  1.1561 +    }
  1.1562 +
  1.1563 +    let oldPlacement = this.getPlacementOfWidget(aWidgetId, false, true);
  1.1564 +    if (oldPlacement && oldPlacement.area == aArea) {
  1.1565 +      this.moveWidgetWithinArea(aWidgetId, aPosition);
  1.1566 +      return;
  1.1567 +    }
  1.1568 +
  1.1569 +    // Do nothing if the widget is not allowed to move to the target area.
  1.1570 +    if (!this.canWidgetMoveToArea(aWidgetId, aArea)) {
  1.1571 +      return;
  1.1572 +    }
  1.1573 +
  1.1574 +    if (oldPlacement) {
  1.1575 +      this.removeWidgetFromArea(aWidgetId);
  1.1576 +    }
  1.1577 +
  1.1578 +    if (!gPlacements.has(aArea)) {
  1.1579 +      gPlacements.set(aArea, [aWidgetId]);
  1.1580 +      aPosition = 0;
  1.1581 +    } else {
  1.1582 +      let placements = gPlacements.get(aArea);
  1.1583 +      if (typeof aPosition != "number") {
  1.1584 +        aPosition = placements.length;
  1.1585 +      }
  1.1586 +      if (aPosition < 0) {
  1.1587 +        aPosition = 0;
  1.1588 +      }
  1.1589 +      placements.splice(aPosition, 0, aWidgetId);
  1.1590 +    }
  1.1591 +
  1.1592 +    let widget = gPalette.get(aWidgetId);
  1.1593 +    if (widget) {
  1.1594 +      widget.currentArea = aArea;
  1.1595 +      widget.currentPosition = aPosition;
  1.1596 +    }
  1.1597 +
  1.1598 +    // We initially set placements with addWidgetToArea, so in that case
  1.1599 +    // we don't consider the area "dirtied".
  1.1600 +    if (!aInitialAdd) {
  1.1601 +      gDirtyAreaCache.add(aArea);
  1.1602 +    }
  1.1603 +
  1.1604 +    gDirty = true;
  1.1605 +    this.saveState();
  1.1606 +
  1.1607 +    this.notifyListeners("onWidgetAdded", aWidgetId, aArea, aPosition);
  1.1608 +  },
  1.1609 +
  1.1610 +  removeWidgetFromArea: function(aWidgetId) {
  1.1611 +    let oldPlacement = this.getPlacementOfWidget(aWidgetId, false, true);
  1.1612 +    if (!oldPlacement) {
  1.1613 +      return;
  1.1614 +    }
  1.1615 +
  1.1616 +    if (!this.isWidgetRemovable(aWidgetId)) {
  1.1617 +      return;
  1.1618 +    }
  1.1619 +
  1.1620 +    let placements = gPlacements.get(oldPlacement.area);
  1.1621 +    let position = placements.indexOf(aWidgetId);
  1.1622 +    if (position != -1) {
  1.1623 +      placements.splice(position, 1);
  1.1624 +    }
  1.1625 +
  1.1626 +    let widget = gPalette.get(aWidgetId);
  1.1627 +    if (widget) {
  1.1628 +      widget.currentArea = null;
  1.1629 +      widget.currentPosition = null;
  1.1630 +    }
  1.1631 +
  1.1632 +    gDirty = true;
  1.1633 +    this.saveState();
  1.1634 +    gDirtyAreaCache.add(oldPlacement.area);
  1.1635 +
  1.1636 +    this.notifyListeners("onWidgetRemoved", aWidgetId, oldPlacement.area);
  1.1637 +  },
  1.1638 +
  1.1639 +  moveWidgetWithinArea: function(aWidgetId, aPosition) {
  1.1640 +    let oldPlacement = this.getPlacementOfWidget(aWidgetId);
  1.1641 +    if (!oldPlacement) {
  1.1642 +      return;
  1.1643 +    }
  1.1644 +
  1.1645 +    let placements = gPlacements.get(oldPlacement.area);
  1.1646 +    if (typeof aPosition != "number") {
  1.1647 +      aPosition = placements.length;
  1.1648 +    } else if (aPosition < 0) {
  1.1649 +      aPosition = 0;
  1.1650 +    } else if (aPosition > placements.length) {
  1.1651 +      aPosition = placements.length;
  1.1652 +    }
  1.1653 +
  1.1654 +    let widget = gPalette.get(aWidgetId);
  1.1655 +    if (widget) {
  1.1656 +      widget.currentPosition = aPosition;
  1.1657 +      widget.currentArea = oldPlacement.area;
  1.1658 +    }
  1.1659 +
  1.1660 +    if (aPosition == oldPlacement.position) {
  1.1661 +      return;
  1.1662 +    }
  1.1663 +
  1.1664 +    placements.splice(oldPlacement.position, 1);
  1.1665 +    // If we just removed the item from *before* where it is now added,
  1.1666 +    // we need to compensate the position offset for that:
  1.1667 +    if (oldPlacement.position < aPosition) {
  1.1668 +      aPosition--;
  1.1669 +    }
  1.1670 +    placements.splice(aPosition, 0, aWidgetId);
  1.1671 +
  1.1672 +    gDirty = true;
  1.1673 +    gDirtyAreaCache.add(oldPlacement.area);
  1.1674 +
  1.1675 +    this.saveState();
  1.1676 +
  1.1677 +    this.notifyListeners("onWidgetMoved", aWidgetId, oldPlacement.area,
  1.1678 +                         oldPlacement.position, aPosition);
  1.1679 +  },
  1.1680 +
  1.1681 +  // Note that this does not populate gPlacements, which is done lazily so that
  1.1682 +  // the legacy state can be migrated, which is only available once a browser
  1.1683 +  // window is openned.
  1.1684 +  // The panel area is an exception here, since it has no legacy state and is 
  1.1685 +  // built lazily - and therefore wouldn't otherwise result in restoring its
  1.1686 +  // state immediately when a browser window opens, which is important for
  1.1687 +  // other consumers of this API.
  1.1688 +  loadSavedState: function() {
  1.1689 +    let state = null;
  1.1690 +    try {
  1.1691 +      state = Services.prefs.getCharPref(kPrefCustomizationState);
  1.1692 +    } catch (e) {
  1.1693 +      LOG("No saved state found");
  1.1694 +      // This will fail if nothing has been customized, so silently fall back to
  1.1695 +      // the defaults.
  1.1696 +    }
  1.1697 +
  1.1698 +    if (!state) {
  1.1699 +      return;
  1.1700 +    }
  1.1701 +    try {
  1.1702 +      gSavedState = JSON.parse(state);
  1.1703 +      if (typeof gSavedState != "object" || gSavedState === null) {
  1.1704 +        throw "Invalid saved state";
  1.1705 +      }
  1.1706 +    } catch(e) {
  1.1707 +      Services.prefs.clearUserPref(kPrefCustomizationState);
  1.1708 +      gSavedState = {};
  1.1709 +      LOG("Error loading saved UI customization state, falling back to defaults.");
  1.1710 +    }
  1.1711 +
  1.1712 +    if (!("placements" in gSavedState)) {
  1.1713 +      gSavedState.placements = {};
  1.1714 +    }
  1.1715 +
  1.1716 +    gSeenWidgets = new Set(gSavedState.seen || []);
  1.1717 +    gDirtyAreaCache = new Set(gSavedState.dirtyAreaCache || []);
  1.1718 +    gNewElementCount = gSavedState.newElementCount || 0;
  1.1719 +  },
  1.1720 +
  1.1721 +  restoreStateForArea: function(aArea, aLegacyState) {
  1.1722 +    let placementsPreexisted = gPlacements.has(aArea);
  1.1723 +
  1.1724 +    this.beginBatchUpdate();
  1.1725 +    try {
  1.1726 +      gRestoring = true;
  1.1727 +
  1.1728 +      let restored = false;
  1.1729 +      if (placementsPreexisted) {
  1.1730 +        LOG("Restoring " + aArea + " from pre-existing placements");
  1.1731 +        for (let [position, id] in Iterator(gPlacements.get(aArea))) {
  1.1732 +          this.moveWidgetWithinArea(id, position);
  1.1733 +        }
  1.1734 +        gDirty = false;
  1.1735 +        restored = true;
  1.1736 +      } else {
  1.1737 +        gPlacements.set(aArea, []);
  1.1738 +      }
  1.1739 +
  1.1740 +      if (!restored && gSavedState && aArea in gSavedState.placements) {
  1.1741 +        LOG("Restoring " + aArea + " from saved state");
  1.1742 +        let placements = gSavedState.placements[aArea];
  1.1743 +        for (let id of placements)
  1.1744 +          this.addWidgetToArea(id, aArea);
  1.1745 +        gDirty = false;
  1.1746 +        restored = true;
  1.1747 +      }
  1.1748 +
  1.1749 +      if (!restored && aLegacyState) {
  1.1750 +        LOG("Restoring " + aArea + " from legacy state");
  1.1751 +        for (let id of aLegacyState)
  1.1752 +          this.addWidgetToArea(id, aArea);
  1.1753 +        // Don't override dirty state, to ensure legacy state is saved here and
  1.1754 +        // therefore only used once.
  1.1755 +        restored = true;
  1.1756 +      }
  1.1757 +
  1.1758 +      if (!restored) {
  1.1759 +        LOG("Restoring " + aArea + " from default state");
  1.1760 +        let defaults = gAreas.get(aArea).get("defaultPlacements");
  1.1761 +        if (defaults) {
  1.1762 +          for (let id of defaults)
  1.1763 +            this.addWidgetToArea(id, aArea, null, true);
  1.1764 +        }
  1.1765 +        gDirty = false;
  1.1766 +      }
  1.1767 +
  1.1768 +      // Finally, add widgets to the area that were added before the it was able
  1.1769 +      // to be restored. This can occur when add-ons register widgets for a
  1.1770 +      // lazily-restored area before it's been restored.
  1.1771 +      if (gFuturePlacements.has(aArea)) {
  1.1772 +        for (let id of gFuturePlacements.get(aArea))
  1.1773 +          this.addWidgetToArea(id, aArea);
  1.1774 +      }
  1.1775 +
  1.1776 +      LOG("Placements for " + aArea + ":\n\t" + gPlacements.get(aArea).join("\n\t"));
  1.1777 +
  1.1778 +      gRestoring = false;
  1.1779 +    } finally {
  1.1780 +      this.endBatchUpdate();
  1.1781 +    }
  1.1782 +  },
  1.1783 +
  1.1784 +  saveState: function() {
  1.1785 +    if (gInBatchStack || !gDirty) {
  1.1786 +      return;
  1.1787 +    }
  1.1788 +    let state = { placements: gPlacements,
  1.1789 +                  seen: gSeenWidgets,
  1.1790 +                  dirtyAreaCache: gDirtyAreaCache,
  1.1791 +                  newElementCount: gNewElementCount };
  1.1792 +
  1.1793 +    LOG("Saving state.");
  1.1794 +    let serialized = JSON.stringify(state, this.serializerHelper);
  1.1795 +    LOG("State saved as: " + serialized);
  1.1796 +    Services.prefs.setCharPref(kPrefCustomizationState, serialized);
  1.1797 +    gDirty = false;
  1.1798 +  },
  1.1799 +
  1.1800 +  serializerHelper: function(aKey, aValue) {
  1.1801 +    if (typeof aValue == "object" && aValue.constructor.name == "Map") {
  1.1802 +      let result = {};
  1.1803 +      for (let [mapKey, mapValue] of aValue)
  1.1804 +        result[mapKey] = mapValue;
  1.1805 +      return result;
  1.1806 +    }
  1.1807 +
  1.1808 +    if (typeof aValue == "object" && aValue.constructor.name == "Set") {
  1.1809 +      return [...aValue];
  1.1810 +    }
  1.1811 +
  1.1812 +    return aValue;
  1.1813 +  },
  1.1814 +
  1.1815 +  beginBatchUpdate: function() {
  1.1816 +    gInBatchStack++;
  1.1817 +  },
  1.1818 +
  1.1819 +  endBatchUpdate: function(aForceDirty) {
  1.1820 +    gInBatchStack--;
  1.1821 +    if (aForceDirty === true) {
  1.1822 +      gDirty = true;
  1.1823 +    }
  1.1824 +    if (gInBatchStack == 0) {
  1.1825 +      this.saveState();
  1.1826 +    } else if (gInBatchStack < 0) {
  1.1827 +      throw new Error("The batch editing stack should never reach a negative number.");
  1.1828 +    }
  1.1829 +  },
  1.1830 +
  1.1831 +  addListener: function(aListener) {
  1.1832 +    gListeners.add(aListener);
  1.1833 +  },
  1.1834 +
  1.1835 +  removeListener: function(aListener) {
  1.1836 +    if (aListener == this) {
  1.1837 +      return;
  1.1838 +    }
  1.1839 +
  1.1840 +    gListeners.delete(aListener);
  1.1841 +  },
  1.1842 +
  1.1843 +  notifyListeners: function(aEvent, ...aArgs) {
  1.1844 +    if (gRestoring) {
  1.1845 +      return;
  1.1846 +    }
  1.1847 +
  1.1848 +    for (let listener of gListeners) {
  1.1849 +      try {
  1.1850 +        if (typeof listener[aEvent] == "function") {
  1.1851 +          listener[aEvent].apply(listener, aArgs);
  1.1852 +        }
  1.1853 +      } catch (e) {
  1.1854 +        ERROR(e + " -- " + e.fileName + ":" + e.lineNumber);
  1.1855 +      }
  1.1856 +    }
  1.1857 +  },
  1.1858 +
  1.1859 +  _dispatchToolboxEventToWindow: function(aEventType, aDetails, aWindow) {
  1.1860 +    let evt = new aWindow.CustomEvent(aEventType, {
  1.1861 +      bubbles: true,
  1.1862 +      cancelable: true,
  1.1863 +      detail: aDetails
  1.1864 +    });
  1.1865 +    aWindow.gNavToolbox.dispatchEvent(evt);
  1.1866 +  },
  1.1867 +
  1.1868 +  dispatchToolboxEvent: function(aEventType, aDetails={}, aWindow=null) {
  1.1869 +    if (aWindow) {
  1.1870 +      return this._dispatchToolboxEventToWindow(aEventType, aDetails, aWindow);
  1.1871 +    }
  1.1872 +    for (let [win, ] of gBuildWindows) {
  1.1873 +      this._dispatchToolboxEventToWindow(aEventType, aDetails, win);
  1.1874 +    }
  1.1875 +  },
  1.1876 +
  1.1877 +  createWidget: function(aProperties) {
  1.1878 +    let widget = this.normalizeWidget(aProperties, CustomizableUI.SOURCE_EXTERNAL);
  1.1879 +    //XXXunf This should probably throw.
  1.1880 +    if (!widget) {
  1.1881 +      return;
  1.1882 +    }
  1.1883 +
  1.1884 +    gPalette.set(widget.id, widget);
  1.1885 +
  1.1886 +    // Clear our caches:
  1.1887 +    gGroupWrapperCache.delete(widget.id);
  1.1888 +    for (let [win, ] of gBuildWindows) {
  1.1889 +      let cache = gSingleWrapperCache.get(win);
  1.1890 +      if (cache) {
  1.1891 +        cache.delete(widget.id);
  1.1892 +      }
  1.1893 +    }
  1.1894 +
  1.1895 +    this.notifyListeners("onWidgetCreated", widget.id);
  1.1896 +
  1.1897 +    if (widget.defaultArea) {
  1.1898 +      let area = gAreas.get(widget.defaultArea);
  1.1899 +      //XXXgijs this won't have any effect for legacy items. Sort of OK because
  1.1900 +      // consumers can modify currentset? Maybe?
  1.1901 +      if (area.has("defaultPlacements")) {
  1.1902 +        area.get("defaultPlacements").push(widget.id);
  1.1903 +      } else {
  1.1904 +        area.set("defaultPlacements", [widget.id]);
  1.1905 +      }
  1.1906 +    }
  1.1907 +
  1.1908 +    // Look through previously saved state to see if we're restoring a widget.
  1.1909 +    let seenAreas = new Set();
  1.1910 +    let widgetMightNeedAutoAdding = true;
  1.1911 +    for (let [area, placements] of gPlacements) {
  1.1912 +      seenAreas.add(area);
  1.1913 +      let areaIsRegistered = gAreas.has(area);
  1.1914 +      let index = gPlacements.get(area).indexOf(widget.id);
  1.1915 +      if (index != -1) {
  1.1916 +        widgetMightNeedAutoAdding = false;
  1.1917 +        if (areaIsRegistered) {
  1.1918 +          widget.currentArea = area;
  1.1919 +          widget.currentPosition = index;
  1.1920 +        }
  1.1921 +        break;
  1.1922 +      }
  1.1923 +    }
  1.1924 +
  1.1925 +    // Also look at saved state data directly in areas that haven't yet been
  1.1926 +    // restored. Can't rely on this for restored areas, as they may have
  1.1927 +    // changed.
  1.1928 +    if (widgetMightNeedAutoAdding && gSavedState) {
  1.1929 +      for (let area of Object.keys(gSavedState.placements)) {
  1.1930 +        if (seenAreas.has(area)) {
  1.1931 +          continue;
  1.1932 +        }
  1.1933 +
  1.1934 +        let areaIsRegistered = gAreas.has(area);
  1.1935 +        let index = gSavedState.placements[area].indexOf(widget.id);
  1.1936 +        if (index != -1) {
  1.1937 +          widgetMightNeedAutoAdding = false;
  1.1938 +          if (areaIsRegistered) {
  1.1939 +            widget.currentArea = area;
  1.1940 +            widget.currentPosition = index;
  1.1941 +          }
  1.1942 +          break;
  1.1943 +        }
  1.1944 +      }
  1.1945 +    }
  1.1946 +
  1.1947 +    // If we're restoring the widget to it's old placement, fire off the
  1.1948 +    // onWidgetAdded event - our own handler will take care of adding it to
  1.1949 +    // any build areas.
  1.1950 +    if (widget.currentArea) {
  1.1951 +      this.notifyListeners("onWidgetAdded", widget.id, widget.currentArea,
  1.1952 +                           widget.currentPosition);
  1.1953 +    } else if (widgetMightNeedAutoAdding) {
  1.1954 +      let autoAdd = true;
  1.1955 +      try {
  1.1956 +        autoAdd = Services.prefs.getBoolPref(kPrefCustomizationAutoAdd);
  1.1957 +      } catch (e) {}
  1.1958 +
  1.1959 +      // If the widget doesn't have an existing placement, and it hasn't been
  1.1960 +      // seen before, then add it to its default area so it can be used.
  1.1961 +      // If the widget is not removable, we *have* to add it to its default
  1.1962 +      // area here.
  1.1963 +      let canBeAutoAdded = autoAdd && !gSeenWidgets.has(widget.id);
  1.1964 +      if (!widget.currentArea && (!widget.removable || canBeAutoAdded)) {
  1.1965 +        this.beginBatchUpdate();
  1.1966 +        try {
  1.1967 +          gSeenWidgets.add(widget.id);
  1.1968 +
  1.1969 +          if (widget.defaultArea) {
  1.1970 +            if (this.isAreaLazy(widget.defaultArea)) {
  1.1971 +              gFuturePlacements.get(widget.defaultArea).add(widget.id);
  1.1972 +            } else {
  1.1973 +              this.addWidgetToArea(widget.id, widget.defaultArea);
  1.1974 +            }
  1.1975 +          }
  1.1976 +        } finally {
  1.1977 +          this.endBatchUpdate(true);
  1.1978 +        }
  1.1979 +      }
  1.1980 +    }
  1.1981 +
  1.1982 +    this.notifyListeners("onWidgetAfterCreation", widget.id, widget.currentArea);
  1.1983 +    return widget.id;
  1.1984 +  },
  1.1985 +
  1.1986 +  createBuiltinWidget: function(aData) {
  1.1987 +    // This should only ever be called on startup, before any windows are
  1.1988 +    // opened - so we know there's no build areas to handle. Also, builtin
  1.1989 +    // widgets are expected to be (mostly) static, so shouldn't affect the
  1.1990 +    // current placement settings.
  1.1991 +    let widget = this.normalizeWidget(aData, CustomizableUI.SOURCE_BUILTIN);
  1.1992 +    if (!widget) {
  1.1993 +      ERROR("Error creating builtin widget: " + aData.id);
  1.1994 +      return;
  1.1995 +    }
  1.1996 +
  1.1997 +    LOG("Creating built-in widget with id: " + widget.id);
  1.1998 +    gPalette.set(widget.id, widget);
  1.1999 +  },
  1.2000 +
  1.2001 +  // Returns true if the area will eventually lazily restore (but hasn't yet).
  1.2002 +  isAreaLazy: function(aArea) {
  1.2003 +    if (gPlacements.has(aArea)) {
  1.2004 +      return false;
  1.2005 +    }
  1.2006 +    return gAreas.get(aArea).has("legacy");
  1.2007 +  },
  1.2008 +
  1.2009 +  //XXXunf Log some warnings here, when the data provided isn't up to scratch.
  1.2010 +  normalizeWidget: function(aData, aSource) {
  1.2011 +    let widget = {
  1.2012 +      implementation: aData,
  1.2013 +      source: aSource || "addon",
  1.2014 +      instances: new Map(),
  1.2015 +      currentArea: null,
  1.2016 +      removable: true,
  1.2017 +      overflows: true,
  1.2018 +      defaultArea: null,
  1.2019 +      shortcutId: null,
  1.2020 +      tooltiptext: null,
  1.2021 +      showInPrivateBrowsing: true,
  1.2022 +    };
  1.2023 +
  1.2024 +    if (typeof aData.id != "string" || !/^[a-z0-9-_]{1,}$/i.test(aData.id)) {
  1.2025 +      ERROR("Given an illegal id in normalizeWidget: " + aData.id);
  1.2026 +      return null;
  1.2027 +    }
  1.2028 +
  1.2029 +    delete widget.implementation.currentArea;
  1.2030 +    widget.implementation.__defineGetter__("currentArea", function() widget.currentArea);
  1.2031 +
  1.2032 +    const kReqStringProps = ["id"];
  1.2033 +    for (let prop of kReqStringProps) {
  1.2034 +      if (typeof aData[prop] != "string") {
  1.2035 +        ERROR("Missing required property '" + prop + "' in normalizeWidget: "
  1.2036 +              + aData.id);
  1.2037 +        return null;
  1.2038 +      }
  1.2039 +      widget[prop] = aData[prop];
  1.2040 +    }
  1.2041 +
  1.2042 +    const kOptStringProps = ["label", "tooltiptext", "shortcutId"];
  1.2043 +    for (let prop of kOptStringProps) {
  1.2044 +      if (typeof aData[prop] == "string") {
  1.2045 +        widget[prop] = aData[prop];
  1.2046 +      }
  1.2047 +    }
  1.2048 +
  1.2049 +    const kOptBoolProps = ["removable", "showInPrivateBrowsing", "overflows"];
  1.2050 +    for (let prop of kOptBoolProps) {
  1.2051 +      if (typeof aData[prop] == "boolean") {
  1.2052 +        widget[prop] = aData[prop];
  1.2053 +      }
  1.2054 +    }
  1.2055 +
  1.2056 +    if (aData.defaultArea && gAreas.has(aData.defaultArea)) {
  1.2057 +      widget.defaultArea = aData.defaultArea;
  1.2058 +    } else if (!widget.removable) {
  1.2059 +      ERROR("Widget '" + widget.id + "' is not removable but does not specify " +
  1.2060 +            "a valid defaultArea. That's not possible; it must specify a " +
  1.2061 +            "valid defaultArea as well.");
  1.2062 +      return null;
  1.2063 +    }
  1.2064 +
  1.2065 +    if ("type" in aData && gSupportedWidgetTypes.has(aData.type)) {
  1.2066 +      widget.type = aData.type;
  1.2067 +    } else {
  1.2068 +      widget.type = "button";
  1.2069 +    }
  1.2070 +
  1.2071 +    widget.disabled = aData.disabled === true;
  1.2072 +
  1.2073 +    this.wrapWidgetEventHandler("onBeforeCreated", widget);
  1.2074 +    this.wrapWidgetEventHandler("onClick", widget);
  1.2075 +    this.wrapWidgetEventHandler("onCreated", widget);
  1.2076 +
  1.2077 +    if (widget.type == "button") {
  1.2078 +      widget.onCommand = typeof aData.onCommand == "function" ?
  1.2079 +                           aData.onCommand :
  1.2080 +                           null;
  1.2081 +    } else if (widget.type == "view") {
  1.2082 +      if (typeof aData.viewId != "string") {
  1.2083 +        ERROR("Expected a string for widget " + widget.id + " viewId, but got "
  1.2084 +              + aData.viewId);
  1.2085 +        return null;
  1.2086 +      }
  1.2087 +      widget.viewId = aData.viewId;
  1.2088 +
  1.2089 +      this.wrapWidgetEventHandler("onViewShowing", widget);
  1.2090 +      this.wrapWidgetEventHandler("onViewHiding", widget);
  1.2091 +    } else if (widget.type == "custom") {
  1.2092 +      this.wrapWidgetEventHandler("onBuild", widget);
  1.2093 +    }
  1.2094 +
  1.2095 +    if (gPalette.has(widget.id)) {
  1.2096 +      return null;
  1.2097 +    }
  1.2098 +
  1.2099 +    return widget;
  1.2100 +  },
  1.2101 +
  1.2102 +  wrapWidgetEventHandler: function(aEventName, aWidget) {
  1.2103 +    if (typeof aWidget.implementation[aEventName] != "function") {
  1.2104 +      aWidget[aEventName] = null;
  1.2105 +      return;
  1.2106 +    }
  1.2107 +    aWidget[aEventName] = function(...aArgs) {
  1.2108 +      // Wrap inside a try...catch to properly log errors, until bug 862627 is
  1.2109 +      // fixed, which in turn might help bug 503244.
  1.2110 +      try {
  1.2111 +        // Don't copy the function to the normalized widget object, instead
  1.2112 +        // keep it on the original object provided to the API so that
  1.2113 +        // additional methods can be implemented and used by the event
  1.2114 +        // handlers.
  1.2115 +        return aWidget.implementation[aEventName].apply(aWidget.implementation,
  1.2116 +                                                        aArgs);
  1.2117 +      } catch (e) {
  1.2118 +        Cu.reportError(e);
  1.2119 +      }
  1.2120 +    };
  1.2121 +  },
  1.2122 +
  1.2123 +  destroyWidget: function(aWidgetId) {
  1.2124 +    let widget = gPalette.get(aWidgetId);
  1.2125 +    if (!widget) {
  1.2126 +      gGroupWrapperCache.delete(aWidgetId);
  1.2127 +      for (let [window, ] of gBuildWindows) {
  1.2128 +        let windowCache = gSingleWrapperCache.get(window);
  1.2129 +        if (windowCache) {
  1.2130 +          windowCache.delete(aWidgetId);
  1.2131 +        }
  1.2132 +      }
  1.2133 +      return;
  1.2134 +    }
  1.2135 +
  1.2136 +    // Remove it from the default placements of an area if it was added there:
  1.2137 +    if (widget.defaultArea) {
  1.2138 +      let area = gAreas.get(widget.defaultArea);
  1.2139 +      if (area) {
  1.2140 +        let defaultPlacements = area.get("defaultPlacements");
  1.2141 +        // We can assume this is present because if a widget has a defaultArea,
  1.2142 +        // we automatically create a defaultPlacements array for that area.
  1.2143 +        let widgetIndex = defaultPlacements.indexOf(aWidgetId);
  1.2144 +        if (widgetIndex != -1) {
  1.2145 +          defaultPlacements.splice(widgetIndex, 1);
  1.2146 +        }
  1.2147 +      }
  1.2148 +    }
  1.2149 +
  1.2150 +    // This will not remove the widget from gPlacements - we want to keep the
  1.2151 +    // setting so the widget gets put back in it's old position if/when it
  1.2152 +    // returns.
  1.2153 +    for (let [window, ] of gBuildWindows) {
  1.2154 +      let windowCache = gSingleWrapperCache.get(window);
  1.2155 +      if (windowCache) {
  1.2156 +        windowCache.delete(aWidgetId);
  1.2157 +      }
  1.2158 +      let widgetNode = window.document.getElementById(aWidgetId) ||
  1.2159 +                       window.gNavToolbox.palette.getElementsByAttribute("id", aWidgetId)[0];
  1.2160 +      if (widgetNode) {
  1.2161 +        let container = widgetNode.parentNode
  1.2162 +        this.notifyListeners("onWidgetBeforeDOMChange", widgetNode, null,
  1.2163 +                             container, true);
  1.2164 +        widgetNode.remove();
  1.2165 +        this.notifyListeners("onWidgetAfterDOMChange", widgetNode, null,
  1.2166 +                             container, true);
  1.2167 +      }
  1.2168 +      if (widget.type == "view") {
  1.2169 +        let viewNode = window.document.getElementById(widget.viewId);
  1.2170 +        if (viewNode) {
  1.2171 +          for (let eventName of kSubviewEvents) {
  1.2172 +            let handler = "on" + eventName;
  1.2173 +            if (typeof widget[handler] == "function") {
  1.2174 +              viewNode.removeEventListener(eventName, widget[handler], false);
  1.2175 +            }
  1.2176 +          }
  1.2177 +        }
  1.2178 +      }
  1.2179 +    }
  1.2180 +
  1.2181 +    gPalette.delete(aWidgetId);
  1.2182 +    gGroupWrapperCache.delete(aWidgetId);
  1.2183 +
  1.2184 +    this.notifyListeners("onWidgetDestroyed", aWidgetId);
  1.2185 +  },
  1.2186 +
  1.2187 +  getCustomizeTargetForArea: function(aArea, aWindow) {
  1.2188 +    let buildAreaNodes = gBuildAreas.get(aArea);
  1.2189 +    if (!buildAreaNodes) {
  1.2190 +      return null;
  1.2191 +    }
  1.2192 +
  1.2193 +    for (let node of buildAreaNodes) {
  1.2194 +      if (node.ownerDocument.defaultView === aWindow) {
  1.2195 +        return node.customizationTarget ? node.customizationTarget : node;
  1.2196 +      }
  1.2197 +    }
  1.2198 +
  1.2199 +    return null;
  1.2200 +  },
  1.2201 +
  1.2202 +  reset: function() {
  1.2203 +    gResetting = true;
  1.2204 +    this._resetUIState();
  1.2205 +
  1.2206 +    // Rebuild each registered area (across windows) to reflect the state that
  1.2207 +    // was reset above.
  1.2208 +    this._rebuildRegisteredAreas();
  1.2209 +
  1.2210 +    gResetting = false;
  1.2211 +  },
  1.2212 +
  1.2213 +  _resetUIState: function() {
  1.2214 +    try {
  1.2215 +      gUIStateBeforeReset.drawInTitlebar = Services.prefs.getBoolPref(kPrefDrawInTitlebar);
  1.2216 +      gUIStateBeforeReset.uiCustomizationState = Services.prefs.getCharPref(kPrefCustomizationState);
  1.2217 +    } catch(e) { }
  1.2218 +
  1.2219 +    this._resetExtraToolbars();
  1.2220 +
  1.2221 +    Services.prefs.clearUserPref(kPrefCustomizationState);
  1.2222 +    Services.prefs.clearUserPref(kPrefDrawInTitlebar);
  1.2223 +    LOG("State reset");
  1.2224 +
  1.2225 +    // Reset placements to make restoring default placements possible.
  1.2226 +    gPlacements = new Map();
  1.2227 +    gDirtyAreaCache = new Set();
  1.2228 +    gSeenWidgets = new Set();
  1.2229 +    // Clear the saved state to ensure that defaults will be used.
  1.2230 +    gSavedState = null;
  1.2231 +    // Restore the state for each area to its defaults
  1.2232 +    for (let [areaId,] of gAreas) {
  1.2233 +      this.restoreStateForArea(areaId);
  1.2234 +    }
  1.2235 +  },
  1.2236 +
  1.2237 +  _resetExtraToolbars: function(aFilter = null) {
  1.2238 +    let firstWindow = true; // Only need to unregister and persist once
  1.2239 +    for (let [win, ] of gBuildWindows) {
  1.2240 +      let toolbox = win.gNavToolbox;
  1.2241 +      for (let child of toolbox.children) {
  1.2242 +        let matchesFilter = !aFilter || aFilter == child.id;
  1.2243 +        if (child.hasAttribute("customindex") && matchesFilter) {
  1.2244 +          let toolbarId = "toolbar" + child.getAttribute("customindex");
  1.2245 +          toolbox.toolbarset.removeAttribute(toolbarId);
  1.2246 +          if (firstWindow) {
  1.2247 +            win.document.persist(toolbox.toolbarset.id, toolbarId);
  1.2248 +            // We have to unregister it properly to ensure we don't kill
  1.2249 +            // XUL widgets which might be in here
  1.2250 +            this.unregisterArea(child.id, true);
  1.2251 +          }
  1.2252 +          child.remove();
  1.2253 +        }
  1.2254 +      }
  1.2255 +      firstWindow = false;
  1.2256 +    }
  1.2257 +  },
  1.2258 +
  1.2259 +  _rebuildRegisteredAreas: function() {
  1.2260 +    for (let [areaId, areaNodes] of gBuildAreas) {
  1.2261 +      let placements = gPlacements.get(areaId);
  1.2262 +      let isFirstChangedToolbar = true;
  1.2263 +      for (let areaNode of areaNodes) {
  1.2264 +        this.buildArea(areaId, placements, areaNode);
  1.2265 +
  1.2266 +        let area = gAreas.get(areaId);
  1.2267 +        if (area.get("type") == CustomizableUI.TYPE_TOOLBAR) {
  1.2268 +          let defaultCollapsed = area.get("defaultCollapsed");
  1.2269 +          let win = areaNode.ownerDocument.defaultView;
  1.2270 +          if (defaultCollapsed !== null) {
  1.2271 +            win.setToolbarVisibility(areaNode, !defaultCollapsed, isFirstChangedToolbar);
  1.2272 +          }
  1.2273 +        }
  1.2274 +        isFirstChangedToolbar = false;
  1.2275 +      }
  1.2276 +    }
  1.2277 +  },
  1.2278 +
  1.2279 +  /**
  1.2280 +   * Undoes a previous reset, restoring the state of the UI to the state prior to the reset.
  1.2281 +   */
  1.2282 +  undoReset: function() {
  1.2283 +    if (gUIStateBeforeReset.uiCustomizationState == null ||
  1.2284 +        gUIStateBeforeReset.drawInTitlebar == null) {
  1.2285 +      return;
  1.2286 +    }
  1.2287 +    gUndoResetting = true;
  1.2288 +
  1.2289 +    let uiCustomizationState = gUIStateBeforeReset.uiCustomizationState;
  1.2290 +    let drawInTitlebar = gUIStateBeforeReset.drawInTitlebar;
  1.2291 +
  1.2292 +    // Need to clear the previous state before setting the prefs
  1.2293 +    // because pref observers may check if there is a previous UI state.
  1.2294 +    this._clearPreviousUIState();
  1.2295 +
  1.2296 +    Services.prefs.setCharPref(kPrefCustomizationState, uiCustomizationState);
  1.2297 +    Services.prefs.setBoolPref(kPrefDrawInTitlebar, drawInTitlebar);
  1.2298 +    this.loadSavedState();
  1.2299 +    // If the user just customizes toolbar/titlebar visibility, gSavedState will be null
  1.2300 +    // and we don't need to do anything else here:
  1.2301 +    if (gSavedState) {
  1.2302 +      for (let areaId of Object.keys(gSavedState.placements)) {
  1.2303 +        let placements = gSavedState.placements[areaId];
  1.2304 +        gPlacements.set(areaId, placements);
  1.2305 +      }
  1.2306 +      this._rebuildRegisteredAreas();
  1.2307 +    }
  1.2308 +
  1.2309 +    gUndoResetting = false;
  1.2310 +  },
  1.2311 +
  1.2312 +  _clearPreviousUIState: function() {
  1.2313 +    Object.getOwnPropertyNames(gUIStateBeforeReset).forEach((prop) => {
  1.2314 +      gUIStateBeforeReset[prop] = null;
  1.2315 +    });
  1.2316 +  },
  1.2317 +
  1.2318 +  removeExtraToolbar: function(aToolbarId) {
  1.2319 +    this._resetExtraToolbars(aToolbarId);
  1.2320 +  },
  1.2321 +
  1.2322 +  /**
  1.2323 +   * @param {String|Node} aWidget - widget ID or a widget node (preferred for performance).
  1.2324 +   * @return {Boolean} whether the widget is removable
  1.2325 +   */
  1.2326 +  isWidgetRemovable: function(aWidget) {
  1.2327 +    let widgetId;
  1.2328 +    let widgetNode;
  1.2329 +    if (typeof aWidget == "string") {
  1.2330 +      widgetId = aWidget;
  1.2331 +    } else {
  1.2332 +      widgetId = aWidget.id;
  1.2333 +      widgetNode = aWidget;
  1.2334 +    }
  1.2335 +    let provider = this.getWidgetProvider(widgetId);
  1.2336 +
  1.2337 +    if (provider == CustomizableUI.PROVIDER_API) {
  1.2338 +      return gPalette.get(widgetId).removable;
  1.2339 +    }
  1.2340 +
  1.2341 +    if (provider == CustomizableUI.PROVIDER_XUL) {
  1.2342 +      if (gBuildWindows.size == 0) {
  1.2343 +        // We don't have any build windows to look at, so just assume for now
  1.2344 +        // that its removable.
  1.2345 +        return true;
  1.2346 +      }
  1.2347 +
  1.2348 +      if (!widgetNode) {
  1.2349 +        // Pick any of the build windows to look at.
  1.2350 +        let [window,] = [...gBuildWindows][0];
  1.2351 +        [, widgetNode] = this.getWidgetNode(widgetId, window);
  1.2352 +      }
  1.2353 +      // If we don't have a node, we assume it's removable. This can happen because
  1.2354 +      // getWidgetProvider returns PROVIDER_XUL by default, but this will also happen
  1.2355 +      // for API-provided widgets which have been destroyed.
  1.2356 +      if (!widgetNode) {
  1.2357 +        return true;
  1.2358 +      }
  1.2359 +      return widgetNode.getAttribute("removable") == "true";
  1.2360 +    }
  1.2361 +
  1.2362 +    // Otherwise this is either a special widget, which is always removable, or
  1.2363 +    // an API widget which has already been removed from gPalette. Returning true
  1.2364 +    // here allows us to then remove its ID from any placements where it might
  1.2365 +    // still occur.
  1.2366 +    return true;
  1.2367 +  },
  1.2368 +
  1.2369 +  canWidgetMoveToArea: function(aWidgetId, aArea) {
  1.2370 +    let placement = this.getPlacementOfWidget(aWidgetId);
  1.2371 +    if (placement && placement.area != aArea) {
  1.2372 +      // Special widgets can't move to the menu panel.
  1.2373 +      if (this.isSpecialWidget(aWidgetId) && gAreas.has(aArea) &&
  1.2374 +          gAreas.get(aArea).get("type") == CustomizableUI.TYPE_MENU_PANEL) {
  1.2375 +        return false;
  1.2376 +      }
  1.2377 +      // For everything else, just return whether the widget is removable.
  1.2378 +      return this.isWidgetRemovable(aWidgetId);
  1.2379 +    }
  1.2380 +
  1.2381 +    return true;
  1.2382 +  },
  1.2383 +
  1.2384 +  ensureWidgetPlacedInWindow: function(aWidgetId, aWindow) {
  1.2385 +    let placement = this.getPlacementOfWidget(aWidgetId);
  1.2386 +    if (!placement) {
  1.2387 +      return false;
  1.2388 +    }
  1.2389 +    let areaNodes = gBuildAreas.get(placement.area);
  1.2390 +    if (!areaNodes) {
  1.2391 +      return false;
  1.2392 +    }
  1.2393 +    let container = [...areaNodes].filter((n) => n.ownerDocument.defaultView == aWindow);
  1.2394 +    if (!container.length) {
  1.2395 +      return false;
  1.2396 +    }
  1.2397 +    let existingNode = container[0].getElementsByAttribute("id", aWidgetId)[0];
  1.2398 +    if (existingNode) {
  1.2399 +      return true;
  1.2400 +    }
  1.2401 +
  1.2402 +    this.insertNodeInWindow(aWidgetId, container[0], true);
  1.2403 +    return true;
  1.2404 +  },
  1.2405 +
  1.2406 +  get inDefaultState() {
  1.2407 +    for (let [areaId, props] of gAreas) {
  1.2408 +      let defaultPlacements = props.get("defaultPlacements");
  1.2409 +      // Areas without default placements (like legacy ones?) get skipped
  1.2410 +      if (!defaultPlacements) {
  1.2411 +        continue;
  1.2412 +      }
  1.2413 +
  1.2414 +      let currentPlacements = gPlacements.get(areaId);
  1.2415 +      // We're excluding all of the placement IDs for items that do not exist,
  1.2416 +      // and items that have removable="false",
  1.2417 +      // because we don't want to consider them when determining if we're
  1.2418 +      // in the default state. This way, if an add-on introduces a widget
  1.2419 +      // and is then uninstalled, the leftover placement doesn't cause us to
  1.2420 +      // automatically assume that the buttons are not in the default state.
  1.2421 +      let buildAreaNodes = gBuildAreas.get(areaId);
  1.2422 +      if (buildAreaNodes && buildAreaNodes.size) {
  1.2423 +        let container = [...buildAreaNodes][0];
  1.2424 +        let removableOrDefault = (itemNodeOrItem) => {
  1.2425 +          let item = (itemNodeOrItem && itemNodeOrItem.id) || itemNodeOrItem;
  1.2426 +          let isRemovable = this.isWidgetRemovable(itemNodeOrItem);
  1.2427 +          let isInDefault = defaultPlacements.indexOf(item) != -1;
  1.2428 +          return isRemovable || isInDefault;
  1.2429 +        };
  1.2430 +        // Toolbars have a currentSet property which also deals correctly with overflown
  1.2431 +        // widgets (if any) - use that instead:
  1.2432 +        if (props.get("type") == CustomizableUI.TYPE_TOOLBAR) {
  1.2433 +          let currentSet = container.currentSet;
  1.2434 +          currentPlacements = currentSet ? currentSet.split(',') : [];
  1.2435 +          currentPlacements = currentPlacements.filter(removableOrDefault);
  1.2436 +        } else {
  1.2437 +          // Clone the array so we don't modify the actual placements...
  1.2438 +          currentPlacements = [...currentPlacements];
  1.2439 +          currentPlacements = currentPlacements.filter((item) => {
  1.2440 +            let itemNode = container.getElementsByAttribute("id", item)[0];
  1.2441 +            return itemNode && removableOrDefault(itemNode || item);
  1.2442 +          });
  1.2443 +        }
  1.2444 +
  1.2445 +        if (props.get("type") == CustomizableUI.TYPE_TOOLBAR) {
  1.2446 +          let attribute = container.getAttribute("type") == "menubar" ? "autohide" : "collapsed";
  1.2447 +          let collapsed = container.getAttribute(attribute) == "true";
  1.2448 +          let defaultCollapsed = props.get("defaultCollapsed");
  1.2449 +          if (defaultCollapsed !== null && collapsed != defaultCollapsed) {
  1.2450 +            LOG("Found " + areaId + " had non-default toolbar visibility (expected " + defaultCollapsed + ", was " + collapsed + ")");
  1.2451 +            return false;
  1.2452 +          }
  1.2453 +        }
  1.2454 +      }
  1.2455 +      LOG("Checking default state for " + areaId + ":\n" + currentPlacements.join(",") +
  1.2456 +          "\nvs.\n" + defaultPlacements.join(","));
  1.2457 +
  1.2458 +      if (currentPlacements.length != defaultPlacements.length) {
  1.2459 +        return false;
  1.2460 +      }
  1.2461 +
  1.2462 +      for (let i = 0; i < currentPlacements.length; ++i) {
  1.2463 +        if (currentPlacements[i] != defaultPlacements[i]) {
  1.2464 +          LOG("Found " + currentPlacements[i] + " in " + areaId + " where " +
  1.2465 +              defaultPlacements[i] + " was expected!");
  1.2466 +          return false;
  1.2467 +        }
  1.2468 +      }
  1.2469 +    }
  1.2470 +
  1.2471 +    if (Services.prefs.prefHasUserValue(kPrefDrawInTitlebar)) {
  1.2472 +      LOG(kPrefDrawInTitlebar + " pref is non-default");
  1.2473 +      return false;
  1.2474 +    }
  1.2475 +
  1.2476 +    return true;
  1.2477 +  },
  1.2478 +
  1.2479 +  setToolbarVisibility: function(aToolbarId, aIsVisible) {
  1.2480 +    // We only persist the attribute the first time.
  1.2481 +    let isFirstChangedToolbar = true;
  1.2482 +    for (let window of CustomizableUI.windows) {
  1.2483 +      let toolbar = window.document.getElementById(aToolbarId);
  1.2484 +      if (toolbar) {
  1.2485 +        window.setToolbarVisibility(toolbar, aIsVisible, isFirstChangedToolbar);
  1.2486 +        isFirstChangedToolbar = false;
  1.2487 +      }
  1.2488 +    }
  1.2489 +  },
  1.2490 +};
  1.2491 +Object.freeze(CustomizableUIInternal);
  1.2492 +
  1.2493 +this.CustomizableUI = {
  1.2494 +  /**
  1.2495 +   * Constant reference to the ID of the menu panel.
  1.2496 +   */
  1.2497 +  get AREA_PANEL() "PanelUI-contents",
  1.2498 +  /**
  1.2499 +   * Constant reference to the ID of the navigation toolbar.
  1.2500 +   */
  1.2501 +  get AREA_NAVBAR() "nav-bar",
  1.2502 +  /**
  1.2503 +   * Constant reference to the ID of the menubar's toolbar.
  1.2504 +   */
  1.2505 +  get AREA_MENUBAR() "toolbar-menubar",
  1.2506 +  /**
  1.2507 +   * Constant reference to the ID of the tabstrip toolbar.
  1.2508 +   */
  1.2509 +  get AREA_TABSTRIP() "TabsToolbar",
  1.2510 +  /**
  1.2511 +   * Constant reference to the ID of the bookmarks toolbar.
  1.2512 +   */
  1.2513 +  get AREA_BOOKMARKS() "PersonalToolbar",
  1.2514 +  /**
  1.2515 +   * Constant reference to the ID of the addon-bar toolbar shim.
  1.2516 +   * Do not use, this will be removed as soon as reasonably possible.
  1.2517 +   * @deprecated
  1.2518 +   */
  1.2519 +  get AREA_ADDONBAR() "addon-bar",
  1.2520 +  /**
  1.2521 +   * Constant indicating the area is a menu panel.
  1.2522 +   */
  1.2523 +  get TYPE_MENU_PANEL() "menu-panel",
  1.2524 +  /**
  1.2525 +   * Constant indicating the area is a toolbar.
  1.2526 +   */
  1.2527 +  get TYPE_TOOLBAR() "toolbar",
  1.2528 +
  1.2529 +  /**
  1.2530 +   * Constant indicating a XUL-type provider.
  1.2531 +   */
  1.2532 +  get PROVIDER_XUL() "xul",
  1.2533 +  /**
  1.2534 +   * Constant indicating an API-type provider.
  1.2535 +   */
  1.2536 +  get PROVIDER_API() "api",
  1.2537 +  /**
  1.2538 +   * Constant indicating dynamic (special) widgets: spring, spacer, and separator.
  1.2539 +   */
  1.2540 +  get PROVIDER_SPECIAL() "special",
  1.2541 +
  1.2542 +  /**
  1.2543 +   * Constant indicating the widget is built-in
  1.2544 +   */
  1.2545 +  get SOURCE_BUILTIN() "builtin",
  1.2546 +  /**
  1.2547 +   * Constant indicating the widget is externally provided
  1.2548 +   * (e.g. by add-ons or other items not part of the builtin widget set).
  1.2549 +   */
  1.2550 +  get SOURCE_EXTERNAL() "external",
  1.2551 +
  1.2552 +  /**
  1.2553 +   * The class used to distinguish items that span the entire menu panel.
  1.2554 +   */
  1.2555 +  get WIDE_PANEL_CLASS() "panel-wide-item",
  1.2556 +  /**
  1.2557 +   * The (constant) number of columns in the menu panel.
  1.2558 +   */
  1.2559 +  get PANEL_COLUMN_COUNT() 3,
  1.2560 +
  1.2561 +  /**
  1.2562 +   * Constant indicating the reason the event was fired was a window closing
  1.2563 +   */
  1.2564 +  get REASON_WINDOW_CLOSED() "window-closed",
  1.2565 +  /**
  1.2566 +   * Constant indicating the reason the event was fired was an area being
  1.2567 +   * unregistered separately from window closing mechanics.
  1.2568 +   */
  1.2569 +  get REASON_AREA_UNREGISTERED() "area-unregistered",
  1.2570 +
  1.2571 +
  1.2572 +  /**
  1.2573 +   * An iteratable property of windows managed by CustomizableUI.
  1.2574 +   * Note that this can *only* be used as an iterator. ie:
  1.2575 +   *     for (let window of CustomizableUI.windows) { ... }
  1.2576 +   */
  1.2577 +  windows: {
  1.2578 +    "@@iterator": function*() {
  1.2579 +      for (let [window,] of gBuildWindows)
  1.2580 +        yield window;
  1.2581 +    }
  1.2582 +  },
  1.2583 +
  1.2584 +  /**
  1.2585 +   * Add a listener object that will get fired for various events regarding
  1.2586 +   * customization.
  1.2587 +   *
  1.2588 +   * @param aListener the listener object to add
  1.2589 +   *
  1.2590 +   * Not all event handler methods need to be defined.
  1.2591 +   * CustomizableUI will catch exceptions. Events are dispatched
  1.2592 +   * synchronously on the UI thread, so if you can delay any/some of your
  1.2593 +   * processing, that is advisable. The following event handlers are supported:
  1.2594 +   *   - onWidgetAdded(aWidgetId, aArea, aPosition)
  1.2595 +   *     Fired when a widget is added to an area. aWidgetId is the widget that
  1.2596 +   *     was added, aArea the area it was added to, and aPosition the position
  1.2597 +   *     in which it was added.
  1.2598 +   *   - onWidgetMoved(aWidgetId, aArea, aOldPosition, aNewPosition)
  1.2599 +   *     Fired when a widget is moved within its area. aWidgetId is the widget
  1.2600 +   *     that was moved, aArea the area it was moved in, aOldPosition its old
  1.2601 +   *     position, and aNewPosition its new position.
  1.2602 +   *   - onWidgetRemoved(aWidgetId, aArea)
  1.2603 +   *     Fired when a widget is removed from its area. aWidgetId is the widget
  1.2604 +   *     that was removed, aArea the area it was removed from.
  1.2605 +   *
  1.2606 +   *   - onWidgetBeforeDOMChange(aNode, aNextNode, aContainer, aIsRemoval)
  1.2607 +   *     Fired *before* a widget's DOM node is acted upon by CustomizableUI
  1.2608 +   *     (to add, move or remove it). aNode is the DOM node changed, aNextNode
  1.2609 +   *     the DOM node (if any) before which a widget will be inserted,
  1.2610 +   *     aContainer the *actual* DOM container (could be an overflow panel in
  1.2611 +   *     case of an overflowable toolbar), and aWasRemoval is true iff the
  1.2612 +   *     action about to happen is the removal of the DOM node.
  1.2613 +   *   - onWidgetAfterDOMChange(aNode, aNextNode, aContainer, aWasRemoval)
  1.2614 +   *     Like onWidgetBeforeDOMChange, but fired after the change to the DOM
  1.2615 +   *     node of the widget.
  1.2616 +   *
  1.2617 +   *   - onWidgetReset(aNode, aContainer)
  1.2618 +   *     Fired after a reset to default placements moves a widget's node to a
  1.2619 +   *     different location. aNode is the widget's node, aContainer is the
  1.2620 +   *     area it was moved into (NB: it might already have been there and been
  1.2621 +   *     moved to a different position!)
  1.2622 +   *   - onWidgetUndoMove(aNode, aContainer)
  1.2623 +   *     Fired after undoing a reset to default placements moves a widget's
  1.2624 +   *     node to a different location. aNode is the widget's node, aContainer
  1.2625 +   *     is the area it was moved into (NB: it might already have been there
  1.2626 +   *     and been moved to a different position!)
  1.2627 +   *   - onAreaReset(aArea, aContainer)
  1.2628 +   *     Fired after a reset to default placements is complete on an area's
  1.2629 +   *     DOM node. Note that this is fired for each DOM node. aArea is the area
  1.2630 +   *     that was reset, aContainer the DOM node that was reset.
  1.2631 +   *
  1.2632 +   *   - onWidgetCreated(aWidgetId)
  1.2633 +   *     Fired when a widget with id aWidgetId has been created, but before it
  1.2634 +   *     is added to any placements or any DOM nodes have been constructed.
  1.2635 +   *     Only fired for API-based widgets.
  1.2636 +   *   - onWidgetAfterCreation(aWidgetId, aArea)
  1.2637 +   *     Fired after a widget with id aWidgetId has been created, and has been
  1.2638 +   *     added to either its default area or the area in which it was placed
  1.2639 +   *     previously. If the widget has no default area and/or it has never
  1.2640 +   *     been placed anywhere, aArea may be null. Only fired for API-based
  1.2641 +   *     widgets.
  1.2642 +   *   - onWidgetDestroyed(aWidgetId)
  1.2643 +   *     Fired when widgets are destroyed. aWidgetId is the widget that is
  1.2644 +   *     being destroyed. Only fired for API-based widgets.
  1.2645 +   *   - onWidgetInstanceRemoved(aWidgetId, aDocument)
  1.2646 +   *     Fired when a window is unloaded and a widget's instance is destroyed
  1.2647 +   *     because of this. Only fired for API-based widgets.
  1.2648 +   *
  1.2649 +   *   - onWidgetDrag(aWidgetId, aArea)
  1.2650 +   *     Fired both when and after customize mode drag handling system tries
  1.2651 +   *     to determine the width and height of widget aWidgetId when dragged to a
  1.2652 +   *     different area. aArea will be the area the item is dragged to, or
  1.2653 +   *     undefined after the measurements have been done and the node has been
  1.2654 +   *     moved back to its 'regular' area.
  1.2655 +   *
  1.2656 +   *   - onCustomizeStart(aWindow)
  1.2657 +   *     Fired when opening customize mode in aWindow.
  1.2658 +   *   - onCustomizeEnd(aWindow)
  1.2659 +   *     Fired when exiting customize mode in aWindow.
  1.2660 +   *
  1.2661 +   *   - onWidgetOverflow(aNode, aContainer)
  1.2662 +   *     Fired when a widget's DOM node is overflowing its container, a toolbar,
  1.2663 +   *     and will be displayed in the overflow panel.
  1.2664 +   *   - onWidgetUnderflow(aNode, aContainer)
  1.2665 +   *     Fired when a widget's DOM node is *not* overflowing its container, a
  1.2666 +   *     toolbar, anymore.
  1.2667 +   *   - onWindowOpened(aWindow)
  1.2668 +   *     Fired when a window has been opened that is managed by CustomizableUI,
  1.2669 +   *     once all of the prerequisite setup has been done.
  1.2670 +   *   - onWindowClosed(aWindow)
  1.2671 +   *     Fired when a window that has been managed by CustomizableUI has been
  1.2672 +   *     closed.
  1.2673 +   *   - onAreaNodeRegistered(aArea, aContainer)
  1.2674 +   *     Fired after an area node is first built when it is registered. This
  1.2675 +   *     is often when the window has opened, but in the case of add-ons,
  1.2676 +   *     could fire when the node has just been registered with CustomizableUI
  1.2677 +   *     after an add-on update or disable/enable sequence.
  1.2678 +   *   - onAreaNodeUnregistered(aArea, aContainer, aReason)
  1.2679 +   *     Fired when an area node is explicitly unregistered by an API caller,
  1.2680 +   *     or by a window closing. The aReason parameter indicates which of
  1.2681 +   *     these is the case.
  1.2682 +   */
  1.2683 +  addListener: function(aListener) {
  1.2684 +    CustomizableUIInternal.addListener(aListener);
  1.2685 +  },
  1.2686 +  /**
  1.2687 +   * Remove a listener added with addListener
  1.2688 +   * @param aListener the listener object to remove
  1.2689 +   */
  1.2690 +  removeListener: function(aListener) {
  1.2691 +    CustomizableUIInternal.removeListener(aListener);
  1.2692 +  },
  1.2693 +
  1.2694 +  /**
  1.2695 +   * Register a customizable area with CustomizableUI.
  1.2696 +   * @param aName   the name of the area to register. Can only contain
  1.2697 +   *                alphanumeric characters, dashes (-) and underscores (_).
  1.2698 +   * @param aProps  the properties of the area. The following properties are
  1.2699 +   *                recognized:
  1.2700 +   *                - type:   the type of area. Either TYPE_TOOLBAR (default) or
  1.2701 +   *                          TYPE_MENU_PANEL;
  1.2702 +   *                - anchor: for a menu panel or overflowable toolbar, the
  1.2703 +   *                          anchoring node for the panel.
  1.2704 +   *                - legacy: set to true if you want customizableui to
  1.2705 +   *                          automatically migrate the currentset attribute
  1.2706 +   *                - overflowable: set to true if your toolbar is overflowable.
  1.2707 +   *                                This requires an anchor, and only has an
  1.2708 +   *                                effect for toolbars.
  1.2709 +   *                - defaultPlacements: an array of widget IDs making up the
  1.2710 +   *                                     default contents of the area
  1.2711 +   *                - defaultCollapsed: (INTERNAL ONLY) applies if the type is TYPE_TOOLBAR, specifies
  1.2712 +   *                                    if toolbar is collapsed by default (default to true).
  1.2713 +   *                                    Specify null to ensure that reset/inDefaultArea don't care
  1.2714 +   *                                    about a toolbar's collapsed state
  1.2715 +   */
  1.2716 +  registerArea: function(aName, aProperties) {
  1.2717 +    CustomizableUIInternal.registerArea(aName, aProperties);
  1.2718 +  },
  1.2719 +  /**
  1.2720 +   * Register a concrete node for a registered area. This method is automatically
  1.2721 +   * called from any toolbar in the main browser window that has its
  1.2722 +   * "customizable" attribute set to true. There should normally be no need to
  1.2723 +   * call it yourself.
  1.2724 +   *
  1.2725 +   * Note that ideally, you should register your toolbar using registerArea
  1.2726 +   * before any of the toolbars have their XBL bindings constructed (which
  1.2727 +   * will happen when they're added to the DOM and are not hidden). If you
  1.2728 +   * don't, and your toolbar has a defaultset attribute, CustomizableUI will
  1.2729 +   * register it automatically. If your toolbar does not have a defaultset
  1.2730 +   * attribute, the node will be saved for processing when you call
  1.2731 +   * registerArea. Note that CustomizableUI won't restore state in the area,
  1.2732 +   * allow the user to customize it in customize mode, or otherwise deal
  1.2733 +   * with it, until the area has been registered.
  1.2734 +   */
  1.2735 +  registerToolbarNode: function(aToolbar, aExistingChildren) {
  1.2736 +    CustomizableUIInternal.registerToolbarNode(aToolbar, aExistingChildren);
  1.2737 +  },
  1.2738 +  /**
  1.2739 +   * Register the menu panel node. This method should not be called by anyone
  1.2740 +   * apart from the built-in PanelUI.
  1.2741 +   * @param aPanel the panel DOM node being registered.
  1.2742 +   */
  1.2743 +  registerMenuPanel: function(aPanel) {
  1.2744 +    CustomizableUIInternal.registerMenuPanel(aPanel);
  1.2745 +  },
  1.2746 +  /**
  1.2747 +   * Unregister a customizable area. The inverse of registerArea.
  1.2748 +   *
  1.2749 +   * Unregistering an area will remove all the (removable) widgets in the
  1.2750 +   * area, which will return to the panel, and destroy all other traces
  1.2751 +   * of the area within CustomizableUI. Note that this means the *contents*
  1.2752 +   * of the area's DOM nodes will be moved to the panel or removed, but
  1.2753 +   * the area's DOM nodes *themselves* will stay.
  1.2754 +   *
  1.2755 +   * Furthermore, by default the placements of the area will be kept in the
  1.2756 +   * saved state (!) and restored if you re-register the area at a later
  1.2757 +   * point. This is useful for e.g. add-ons that get disabled and then
  1.2758 +   * re-enabled (e.g. when they update).
  1.2759 +   *
  1.2760 +   * You can override this last behaviour (and destroy the placements
  1.2761 +   * information in the saved state) by passing true for aDestroyPlacements.
  1.2762 +   *
  1.2763 +   * @param aName              the name of the area to unregister
  1.2764 +   * @param aDestroyPlacements whether to destroy the placements information
  1.2765 +   *                           for the area, too.
  1.2766 +   */
  1.2767 +  unregisterArea: function(aName, aDestroyPlacements) {
  1.2768 +    CustomizableUIInternal.unregisterArea(aName, aDestroyPlacements);
  1.2769 +  },
  1.2770 +  /**
  1.2771 +   * Add a widget to an area.
  1.2772 +   * If the area to which you try to add is not known to CustomizableUI,
  1.2773 +   * this will throw.
  1.2774 +   * If the area to which you try to add has not yet been restored from its
  1.2775 +   * legacy state, this will postpone the addition.
  1.2776 +   * If the area to which you try to add is the same as the area in which
  1.2777 +   * the widget is currently placed, this will do the same as
  1.2778 +   * moveWidgetWithinArea.
  1.2779 +   * If the widget cannot be removed from its original location, this will
  1.2780 +   * no-op.
  1.2781 +   *
  1.2782 +   * This will fire an onWidgetAdded notification,
  1.2783 +   * and an onWidgetBeforeDOMChange and onWidgetAfterDOMChange notification
  1.2784 +   * for each window CustomizableUI knows about.
  1.2785 +   *
  1.2786 +   * @param aWidgetId the ID of the widget to add
  1.2787 +   * @param aArea     the ID of the area to add the widget to
  1.2788 +   * @param aPosition the position at which to add the widget. If you do not
  1.2789 +   *                  pass a position, the widget will be added to the end
  1.2790 +   *                  of the area.
  1.2791 +   */
  1.2792 +  addWidgetToArea: function(aWidgetId, aArea, aPosition) {
  1.2793 +    CustomizableUIInternal.addWidgetToArea(aWidgetId, aArea, aPosition);
  1.2794 +  },
  1.2795 +  /**
  1.2796 +   * Remove a widget from its area. If the widget cannot be removed from its
  1.2797 +   * area, or is not in any area, this will no-op. Otherwise, this will fire an
  1.2798 +   * onWidgetRemoved notification, and an onWidgetBeforeDOMChange and
  1.2799 +   * onWidgetAfterDOMChange notification for each window CustomizableUI knows
  1.2800 +   * about.
  1.2801 +   *
  1.2802 +   * @param aWidgetId the ID of the widget to remove
  1.2803 +   */
  1.2804 +  removeWidgetFromArea: function(aWidgetId) {
  1.2805 +    CustomizableUIInternal.removeWidgetFromArea(aWidgetId);
  1.2806 +  },
  1.2807 +  /**
  1.2808 +   * Move a widget within an area.
  1.2809 +   * If the widget is not in any area, this will no-op.
  1.2810 +   * If the widget is already at the indicated position, this will no-op.
  1.2811 +   *
  1.2812 +   * Otherwise, this will move the widget and fire an onWidgetMoved notification,
  1.2813 +   * and an onWidgetBeforeDOMChange and onWidgetAfterDOMChange notification for
  1.2814 +   * each window CustomizableUI knows about.
  1.2815 +   *
  1.2816 +   * @param aWidgetId the ID of the widget to move
  1.2817 +   * @param aPosition the position to move the widget to.
  1.2818 +   *                  Negative values or values greater than the number of
  1.2819 +   *                  widgets will be interpreted to mean moving the widget to
  1.2820 +   *                  respectively the first or last position.
  1.2821 +   */
  1.2822 +  moveWidgetWithinArea: function(aWidgetId, aPosition) {
  1.2823 +    CustomizableUIInternal.moveWidgetWithinArea(aWidgetId, aPosition);
  1.2824 +  },
  1.2825 +  /**
  1.2826 +   * Ensure a XUL-based widget created in a window after areas were
  1.2827 +   * initialized moves to its correct position.
  1.2828 +   * This is roughly equivalent to manually looking up the position and using
  1.2829 +   * insertItem in the old API, but a lot less work for consumers.
  1.2830 +   * Always prefer this over using toolbar.insertItem (which might no-op
  1.2831 +   * because it delegates to addWidgetToArea) or, worse, moving items in the
  1.2832 +   * DOM yourself.
  1.2833 +   *
  1.2834 +   * @param aWidgetId the ID of the widget that was just created
  1.2835 +   * @param aWindow the window in which you want to ensure it was added.
  1.2836 +   *
  1.2837 +   * NB: why is this API per-window, you wonder? Because if you need this,
  1.2838 +   * presumably you yourself need to create the widget in all the windows
  1.2839 +   * and need to loop through them anyway.
  1.2840 +   */
  1.2841 +  ensureWidgetPlacedInWindow: function(aWidgetId, aWindow) {
  1.2842 +    return CustomizableUIInternal.ensureWidgetPlacedInWindow(aWidgetId, aWindow);
  1.2843 +  },
  1.2844 +  /**
  1.2845 +   * Start a batch update of items.
  1.2846 +   * During a batch update, the customization state is not saved to the user's
  1.2847 +   * preferences file, in order to reduce (possibly sync) IO.
  1.2848 +   * Calls to begin/endBatchUpdate may be nested.
  1.2849 +   *
  1.2850 +   * Callers should ensure that NO MATTER WHAT they call endBatchUpdate once
  1.2851 +   * for each call to beginBatchUpdate, even if there are exceptions in the
  1.2852 +   * code in the batch update. Otherwise, for the duration of the
  1.2853 +   * Firefox session, customization state is never saved. Typically, you
  1.2854 +   * would do this using a try...finally block.
  1.2855 +   */
  1.2856 +  beginBatchUpdate: function() {
  1.2857 +    CustomizableUIInternal.beginBatchUpdate();
  1.2858 +  },
  1.2859 +  /**
  1.2860 +   * End a batch update. See the documentation for beginBatchUpdate above.
  1.2861 +   *
  1.2862 +   * State is not saved if we believe it is identical to the last known
  1.2863 +   * saved state. State is only ever saved when all batch updates have
  1.2864 +   * finished (ie there has been 1 endBatchUpdate call for each
  1.2865 +   * beginBatchUpdate call). If any of the endBatchUpdate calls pass
  1.2866 +   * aForceDirty=true, we will flush to the prefs file.
  1.2867 +   *
  1.2868 +   * @param aForceDirty force CustomizableUI to flush to the prefs file when
  1.2869 +   *                    all batch updates have finished.
  1.2870 +   */
  1.2871 +  endBatchUpdate: function(aForceDirty) {
  1.2872 +    CustomizableUIInternal.endBatchUpdate(aForceDirty);
  1.2873 +  },
  1.2874 +  /**
  1.2875 +   * Create a widget.
  1.2876 +   *
  1.2877 +   * To create a widget, you should pass an object with its desired
  1.2878 +   * properties. The following properties are supported:
  1.2879 +   *
  1.2880 +   * - id:            the ID of the widget (required).
  1.2881 +   * - type:          a string indicating the type of widget. Possible types
  1.2882 +   *                  are:
  1.2883 +   *                  'button' - for simple button widgets (the default)
  1.2884 +   *                  'view'   - for buttons that open a panel or subview,
  1.2885 +   *                             depending on where they are placed.
  1.2886 +   *                  'custom' - for fine-grained control over the creation
  1.2887 +   *                             of the widget.
  1.2888 +   * - viewId:        Only useful for views (and required there): the id of the
  1.2889 +   *                  <panelview> that should be shown when clicking the widget.
  1.2890 +   * - onBuild(aDoc): Only useful for custom widgets (and required there); a
  1.2891 +   *                  function that will be invoked with the document in which
  1.2892 +   *                  to build a widget. Should return the DOM node that has
  1.2893 +   *                  been constructed.
  1.2894 +   * - onBeforeCreated(aDoc): Attached to all non-custom widgets; a function
  1.2895 +   *                  that will be invoked before the widget gets a DOM node
  1.2896 +   *                  constructed, passing the document in which that will happen.
  1.2897 +   *                  This is useful especially for 'view' type widgets that need
  1.2898 +   *                  to construct their views on the fly (e.g. from bootstrapped
  1.2899 +   *                  add-ons)
  1.2900 +   * - onCreated(aNode): Attached to all widgets; a function that will be invoked
  1.2901 +   *                  whenever the widget has a DOM node constructed, passing the
  1.2902 +   *                  constructed node as an argument.
  1.2903 +   * - onCommand(aEvt): Only useful for button widgets; a function that will be
  1.2904 +   *                    invoked when the user activates the button.
  1.2905 +   * - onClick(aEvt): Attached to all widgets; a function that will be invoked
  1.2906 +   *                  when the user clicks the widget.
  1.2907 +   * - onViewShowing(aEvt): Only useful for views; a function that will be
  1.2908 +   *                  invoked when a user shows your view.
  1.2909 +   * - onViewHiding(aEvt): Only useful for views; a function that will be
  1.2910 +   *                  invoked when a user hides your view.
  1.2911 +   * - tooltiptext:   string to use for the tooltip of the widget
  1.2912 +   * - label:         string to use for the label of the widget
  1.2913 +   * - removable:     whether the widget is removable (optional, default: true)
  1.2914 +   *                  NB: if you specify false here, you must provide a
  1.2915 +   *                  defaultArea, too.
  1.2916 +   * - overflows:     whether widget can overflow when in an overflowable
  1.2917 +   *                  toolbar (optional, default: true)
  1.2918 +   * - defaultArea:   default area to add the widget to
  1.2919 +   *                  (optional, default: none; required if non-removable)
  1.2920 +   * - shortcutId:    id of an element that has a shortcut for this widget
  1.2921 +   *                  (optional, default: null). This is only used to display
  1.2922 +   *                  the shortcut as part of the tooltip for builtin widgets
  1.2923 +   *                  (which have strings inside
  1.2924 +   *                  customizableWidgets.properties). If you're in an add-on,
  1.2925 +   *                  you should not set this property.
  1.2926 +   * - showInPrivateBrowsing: whether to show the widget in private browsing
  1.2927 +   *                          mode (optional, default: true)
  1.2928 +   *
  1.2929 +   * @param aProperties the specifications for the widget.
  1.2930 +   * @return a wrapper around the created widget (see getWidget)
  1.2931 +   */
  1.2932 +  createWidget: function(aProperties) {
  1.2933 +    return CustomizableUIInternal.wrapWidget(
  1.2934 +      CustomizableUIInternal.createWidget(aProperties)
  1.2935 +    );
  1.2936 +  },
  1.2937 +  /**
  1.2938 +   * Destroy a widget
  1.2939 +   *
  1.2940 +   * If the widget is part of the default placements in an area, this will
  1.2941 +   * remove it from there. It will also remove any DOM instances. However,
  1.2942 +   * it will keep the widget in the placements for whatever area it was
  1.2943 +   * in at the time. You can remove it from there yourself by calling
  1.2944 +   * CustomizableUI.removeWidgetFromArea(aWidgetId).
  1.2945 +   *
  1.2946 +   * @param aWidgetId the ID of the widget to destroy
  1.2947 +   */
  1.2948 +  destroyWidget: function(aWidgetId) {
  1.2949 +    CustomizableUIInternal.destroyWidget(aWidgetId);
  1.2950 +  },
  1.2951 +  /**
  1.2952 +   * Get a wrapper object with information about the widget.
  1.2953 +   * The object provides the following properties
  1.2954 +   * (all read-only unless otherwise indicated):
  1.2955 +   *
  1.2956 +   * - id:            the widget's ID;
  1.2957 +   * - type:          the type of widget (button, view, custom). For
  1.2958 +   *                  XUL-provided widgets, this is always 'custom';
  1.2959 +   * - provider:      the provider type of the widget, id est one of
  1.2960 +   *                  PROVIDER_API or PROVIDER_XUL;
  1.2961 +   * - forWindow(w):  a method to obtain a single window wrapper for a widget,
  1.2962 +   *                  in the window w passed as the only argument;
  1.2963 +   * - instances:     an array of all instances (single window wrappers)
  1.2964 +   *                  of the widget. This array is NOT live;
  1.2965 +   * - areaType:      the type of the widget's current area
  1.2966 +   * - isGroup:       true; will be false for wrappers around single widget nodes;
  1.2967 +   * - source:        for API-provided widgets, whether they are built-in to
  1.2968 +   *                  Firefox or add-on-provided;
  1.2969 +   * - disabled:      for API-provided widgets, whether the widget is currently
  1.2970 +   *                  disabled. NB: this property is writable, and will toggle
  1.2971 +   *                  all the widgets' nodes' disabled states;
  1.2972 +   * - label:         for API-provied widgets, the label of the widget;
  1.2973 +   * - tooltiptext:   for API-provided widgets, the tooltip of the widget;
  1.2974 +   * - showInPrivateBrowsing: for API-provided widgets, whether the widget is
  1.2975 +   *                          visible in private browsing;
  1.2976 +   *
  1.2977 +   * Single window wrappers obtained through forWindow(someWindow) or from the
  1.2978 +   * instances array have the following properties
  1.2979 +   * (all read-only unless otherwise indicated):
  1.2980 +   *
  1.2981 +   * - id:            the widget's ID;
  1.2982 +   * - type:          the type of widget (button, view, custom). For
  1.2983 +   *                  XUL-provided widgets, this is always 'custom';
  1.2984 +   * - provider:      the provider type of the widget, id est one of
  1.2985 +   *                  PROVIDER_API or PROVIDER_XUL;
  1.2986 +   * - node:          reference to the corresponding DOM node;
  1.2987 +   * - anchor:        the anchor on which to anchor panels opened from this
  1.2988 +   *                  node. This will point to the overflow chevron on
  1.2989 +   *                  overflowable toolbars if and only if your widget node
  1.2990 +   *                  is overflowed, to the anchor for the panel menu
  1.2991 +   *                  if your widget is inside the panel menu, and to the
  1.2992 +   *                  node itself in all other cases;
  1.2993 +   * - overflowed:    boolean indicating whether the node is currently in the
  1.2994 +   *                  overflow panel of the toolbar;
  1.2995 +   * - isGroup:       false; will be true for the group widget;
  1.2996 +   * - label:         for API-provided widgets, convenience getter for the
  1.2997 +   *                  label attribute of the DOM node;
  1.2998 +   * - tooltiptext:   for API-provided widgets, convenience getter for the
  1.2999 +   *                  tooltiptext attribute of the DOM node;
  1.3000 +   * - disabled:      for API-provided widgets, convenience getter *and setter*
  1.3001 +   *                  for the disabled state of this single widget. Note that
  1.3002 +   *                  you may prefer to use the group wrapper's getter/setter
  1.3003 +   *                  instead.
  1.3004 +   *
  1.3005 +   * @param aWidgetId the ID of the widget whose information you need
  1.3006 +   * @return a wrapper around the widget as described above, or null if the
  1.3007 +   *         widget is known not to exist (anymore). NB: non-null return
  1.3008 +   *         is no guarantee the widget exists because we cannot know in
  1.3009 +   *         advance if a XUL widget exists or not.
  1.3010 +   */
  1.3011 +  getWidget: function(aWidgetId) {
  1.3012 +    return CustomizableUIInternal.wrapWidget(aWidgetId);
  1.3013 +  },
  1.3014 +  /**
  1.3015 +   * Get an array of widget wrappers (see getWidget) for all the widgets
  1.3016 +   * which are currently not in any area (so which are in the palette).
  1.3017 +   *
  1.3018 +   * @param aWindowPalette the palette (and by extension, the window) in which
  1.3019 +   *                       CustomizableUI should look. This matters because of
  1.3020 +   *                       course XUL-provided widgets could be available in
  1.3021 +   *                       some windows but not others, and likewise
  1.3022 +   *                       API-provided widgets might not exist in a private
  1.3023 +   *                       window (because of the showInPrivateBrowsing
  1.3024 +   *                       property).
  1.3025 +   *
  1.3026 +   * @return an array of widget wrappers (see getWidget)
  1.3027 +   */
  1.3028 +  getUnusedWidgets: function(aWindowPalette) {
  1.3029 +    return CustomizableUIInternal.getUnusedWidgets(aWindowPalette).map(
  1.3030 +      CustomizableUIInternal.wrapWidget,
  1.3031 +      CustomizableUIInternal
  1.3032 +    );
  1.3033 +  },
  1.3034 +  /**
  1.3035 +   * Get an array of all the widget IDs placed in an area. This is roughly
  1.3036 +   * equivalent to fetching the currentset attribute and splitting by commas
  1.3037 +   * in the legacy APIs. Modifying the array will not affect CustomizableUI.
  1.3038 +   *
  1.3039 +   * @param aArea the ID of the area whose placements you want to obtain.
  1.3040 +   * @return an array containing the widget IDs that are in the area.
  1.3041 +   *
  1.3042 +   * NB: will throw if called too early (before placements have been fetched)
  1.3043 +   *     or if the area is not currently known to CustomizableUI.
  1.3044 +   */
  1.3045 +  getWidgetIdsInArea: function(aArea) {
  1.3046 +    if (!gAreas.has(aArea)) {
  1.3047 +      throw new Error("Unknown customization area: " + aArea);
  1.3048 +    }
  1.3049 +    if (!gPlacements.has(aArea)) {
  1.3050 +      throw new Error("Area not yet restored");
  1.3051 +    }
  1.3052 +
  1.3053 +    // We need to clone this, as we don't want to let consumers muck with placements
  1.3054 +    return [...gPlacements.get(aArea)];
  1.3055 +  },
  1.3056 +  /**
  1.3057 +   * Get an array of widget wrappers for all the widgets in an area. This is
  1.3058 +   * the same as calling getWidgetIdsInArea and .map() ing the result through
  1.3059 +   * CustomizableUI.getWidget. Careful: this means that if there are IDs in there
  1.3060 +   * which don't have corresponding DOM nodes (like in the old-style currentset
  1.3061 +   * attribute), there might be nulls in this array, or items for which
  1.3062 +   * wrapper.forWindow(win) will return null.
  1.3063 +   *
  1.3064 +   * @param aArea the ID of the area whose widgets you want to obtain.
  1.3065 +   * @return an array of widget wrappers and/or null values for the widget IDs
  1.3066 +   *         placed in an area.
  1.3067 +   *
  1.3068 +   * NB: will throw if called too early (before placements have been fetched)
  1.3069 +   *     or if the area is not currently known to CustomizableUI.
  1.3070 +   */
  1.3071 +  getWidgetsInArea: function(aArea) {
  1.3072 +    return this.getWidgetIdsInArea(aArea).map(
  1.3073 +      CustomizableUIInternal.wrapWidget,
  1.3074 +      CustomizableUIInternal
  1.3075 +    );
  1.3076 +  },
  1.3077 +  /**
  1.3078 +   * Obtain an array of all the area IDs known to CustomizableUI.
  1.3079 +   * This array is created for you, so is modifiable without CustomizableUI
  1.3080 +   * being affected.
  1.3081 +   */
  1.3082 +  get areas() {
  1.3083 +    return [area for ([area, props] of gAreas)];
  1.3084 +  },
  1.3085 +  /**
  1.3086 +   * Check what kind of area (toolbar or menu panel) an area is. This is
  1.3087 +   * useful if you have a widget that needs to behave differently depending
  1.3088 +   * on its location. Note that widget wrappers have a convenience getter
  1.3089 +   * property (areaType) for this purpose.
  1.3090 +   *
  1.3091 +   * @param aArea the ID of the area whose type you want to know
  1.3092 +   * @return TYPE_TOOLBAR or TYPE_MENU_PANEL depending on the area, null if
  1.3093 +   *         the area is unknown.
  1.3094 +   */
  1.3095 +  getAreaType: function(aArea) {
  1.3096 +    let area = gAreas.get(aArea);
  1.3097 +    return area ? area.get("type") : null;
  1.3098 +  },
  1.3099 +  /**
  1.3100 +   * Check if a toolbar is collapsed by default.
  1.3101 +   *
  1.3102 +   * @param aArea the ID of the area whose default-collapsed state you want to know.
  1.3103 +   * @return `true` or `false` depending on the area, null if the area is unknown,
  1.3104 +   *         or its collapsed state cannot normally be controlled by the user
  1.3105 +   */
  1.3106 +  isToolbarDefaultCollapsed: function(aArea) {
  1.3107 +    let area = gAreas.get(aArea);
  1.3108 +    return area ? area.get("defaultCollapsed") : null;
  1.3109 +  },
  1.3110 +  /**
  1.3111 +   * Obtain the DOM node that is the customize target for an area in a
  1.3112 +   * specific window.
  1.3113 +   *
  1.3114 +   * Areas can have a customization target that does not correspond to the
  1.3115 +   * node itself. In particular, toolbars that have a customizationtarget
  1.3116 +   * attribute set will have their customization target set to that node.
  1.3117 +   * This means widgets will end up in the customization target, not in the
  1.3118 +   * DOM node with the ID that corresponds to the area ID. This is useful
  1.3119 +   * because it lets you have fixed content in a toolbar (e.g. the panel
  1.3120 +   * menu item in the navbar) and have all the customizable widgets use
  1.3121 +   * the customization target.
  1.3122 +   *
  1.3123 +   * Using this API yourself is discouraged; you should generally not need
  1.3124 +   * to be asking for the DOM container node used for a particular area.
  1.3125 +   * In particular, if you're wanting to check it in relation to a widget's
  1.3126 +   * node, your DOM node might not be a direct child of the customize target
  1.3127 +   * in a window if, for instance, the window is in customization mode, or if
  1.3128 +   * this is an overflowable toolbar and the widget has been overflowed.
  1.3129 +   *
  1.3130 +   * @param aArea   the ID of the area whose customize target you want to have
  1.3131 +   * @param aWindow the window where you want to fetch the DOM node.
  1.3132 +   * @return the customize target DOM node for aArea in aWindow
  1.3133 +   */
  1.3134 +  getCustomizeTargetForArea: function(aArea, aWindow) {
  1.3135 +    return CustomizableUIInternal.getCustomizeTargetForArea(aArea, aWindow);
  1.3136 +  },
  1.3137 +  /**
  1.3138 +   * Reset the customization state back to its default.
  1.3139 +   *
  1.3140 +   * This is the nuclear option. You should never call this except if the user
  1.3141 +   * explicitly requests it. Firefox does this when the user clicks the
  1.3142 +   * "Restore Defaults" button in customize mode.
  1.3143 +   */
  1.3144 +  reset: function() {
  1.3145 +    CustomizableUIInternal.reset();
  1.3146 +  },
  1.3147 +
  1.3148 +  /**
  1.3149 +   * Undo the previous reset, can only be called immediately after a reset.
  1.3150 +   * @return a promise that will be resolved when the operation is complete.
  1.3151 +   */
  1.3152 +  undoReset: function() {
  1.3153 +    CustomizableUIInternal.undoReset();
  1.3154 +  },
  1.3155 +
  1.3156 +  /**
  1.3157 +   * Remove a custom toolbar added in a previous version of Firefox or using
  1.3158 +   * an add-on. NB: only works on the customizable toolbars generated by
  1.3159 +   * the toolbox itself. Intended for use from CustomizeMode, not by
  1.3160 +   * other consumers.
  1.3161 +   * @param aToolbarId the ID of the toolbar to remove
  1.3162 +   */
  1.3163 +  removeExtraToolbar: function(aToolbarId) {
  1.3164 +    CustomizableUIInternal.removeExtraToolbar(aToolbarId);
  1.3165 +  },
  1.3166 +
  1.3167 +  /**
  1.3168 +   * Can the last Restore Defaults operation be undone.
  1.3169 +   *
  1.3170 +   * @return A boolean stating whether an undo of the
  1.3171 +   *         Restore Defaults can be performed.
  1.3172 +   */
  1.3173 +  get canUndoReset() {
  1.3174 +    return gUIStateBeforeReset.uiCustomizationState != null ||
  1.3175 +           gUIStateBeforeReset.drawInTitlebar != null;
  1.3176 +  },
  1.3177 +
  1.3178 +  /**
  1.3179 +   * Get the placement of a widget. This is by far the best way to obtain
  1.3180 +   * information about what the state of your widget is. The internals of
  1.3181 +   * this call are cheap (no DOM necessary) and you will know where the user
  1.3182 +   * has put your widget.
  1.3183 +   *
  1.3184 +   * @param aWidgetId the ID of the widget whose placement you want to know
  1.3185 +   * @return
  1.3186 +   *   {
  1.3187 +   *     area: "somearea", // The ID of the area where the widget is placed
  1.3188 +   *     position: 42 // the index in the placements array corresponding to
  1.3189 +   *                  // your widget.
  1.3190 +   *   }
  1.3191 +   *
  1.3192 +   *   OR
  1.3193 +   *
  1.3194 +   *   null // if the widget is not placed anywhere (ie in the palette)
  1.3195 +   */
  1.3196 +  getPlacementOfWidget: function(aWidgetId) {
  1.3197 +    return CustomizableUIInternal.getPlacementOfWidget(aWidgetId, true);
  1.3198 +  },
  1.3199 +  /**
  1.3200 +   * Check if a widget can be removed from the area it's in.
  1.3201 +   *
  1.3202 +   * Note that if you're wanting to move the widget somewhere, you should
  1.3203 +   * generally be checking canWidgetMoveToArea, because that will return
  1.3204 +   * true if the widget is already in the area where you want to move it (!).
  1.3205 +   *
  1.3206 +   * NB: oh, also, this method might lie if the widget in question is a
  1.3207 +   *     XUL-provided widget and there are no windows open, because it
  1.3208 +   *     can obviously not check anything in this case. It will return
  1.3209 +   *     true. You will be able to move the widget elsewhere. However,
  1.3210 +   *     once the user reopens a window, the widget will move back to its
  1.3211 +   *     'proper' area automagically.
  1.3212 +   *
  1.3213 +   * @param aWidgetId a widget ID or DOM node to check
  1.3214 +   * @return true if the widget can be removed from its area,
  1.3215 +   *          false otherwise.
  1.3216 +   */
  1.3217 +  isWidgetRemovable: function(aWidgetId) {
  1.3218 +    return CustomizableUIInternal.isWidgetRemovable(aWidgetId);
  1.3219 +  },
  1.3220 +  /**
  1.3221 +   * Check if a widget can be moved to a particular area. Like
  1.3222 +   * isWidgetRemovable but better, because it'll return true if the widget
  1.3223 +   * is already in the right area.
  1.3224 +   *
  1.3225 +   * @param aWidgetId the widget ID or DOM node you want to move somewhere
  1.3226 +   * @param aArea     the area ID you want to move it to.
  1.3227 +   * @return true if this is possible, false if it is not. The same caveats as
  1.3228 +   *              for isWidgetRemovable apply, however, if no windows are open.
  1.3229 +   */
  1.3230 +  canWidgetMoveToArea: function(aWidgetId, aArea) {
  1.3231 +    return CustomizableUIInternal.canWidgetMoveToArea(aWidgetId, aArea);
  1.3232 +  },
  1.3233 +  /**
  1.3234 +   * Whether we're in a default state. Note that non-removable non-default
  1.3235 +   * widgets and non-existing widgets are not taken into account in determining
  1.3236 +   * whether we're in the default state.
  1.3237 +   *
  1.3238 +   * NB: this is a property with a getter. The getter is NOT cheap, because
  1.3239 +   * it does smart things with non-removable non-default items, non-existent
  1.3240 +   * items, and so forth. Please don't call unless necessary.
  1.3241 +   */
  1.3242 +  get inDefaultState() {
  1.3243 +    return CustomizableUIInternal.inDefaultState;
  1.3244 +  },
  1.3245 +
  1.3246 +  /**
  1.3247 +   * Set a toolbar's visibility state in all windows.
  1.3248 +   * @param aToolbarId    the toolbar whose visibility should be adjusted
  1.3249 +   * @param aIsVisible    whether the toolbar should be visible
  1.3250 +   */
  1.3251 +  setToolbarVisibility: function(aToolbarId, aIsVisible) {
  1.3252 +    CustomizableUIInternal.setToolbarVisibility(aToolbarId, aIsVisible);
  1.3253 +  },
  1.3254 +
  1.3255 +  /**
  1.3256 +   * Get a localized property off a (widget?) object.
  1.3257 +   *
  1.3258 +   * NB: this is unlikely to be useful unless you're in Firefox code, because
  1.3259 +   *     this code uses the builtin widget stringbundle, and can't be told
  1.3260 +   *     to use add-on-provided strings. It's mainly here as convenience for
  1.3261 +   *     custom builtin widgets that build their own DOM but use the same
  1.3262 +   *     stringbundle as the other builtin widgets.
  1.3263 +   *
  1.3264 +   * @param aWidget     the object whose property we should use to fetch a
  1.3265 +   *                    localizable string;
  1.3266 +   * @param aProp       the property on the object to use for the fetching;
  1.3267 +   * @param aFormatArgs (optional) any extra arguments to use for a formatted
  1.3268 +   *                    string;
  1.3269 +   * @param aDef        (optional) the default to return if we don't find the
  1.3270 +   *                    string in the stringbundle;
  1.3271 +   *
  1.3272 +   * @return the localized string, or aDef if the string isn't in the bundle.
  1.3273 +   *         If no default is provided,
  1.3274 +   *           if aProp exists on aWidget, we'll return that,
  1.3275 +   *           otherwise we'll return the empty string
  1.3276 +   *
  1.3277 +   */
  1.3278 +  getLocalizedProperty: function(aWidget, aProp, aFormatArgs, aDef) {
  1.3279 +    return CustomizableUIInternal.getLocalizedProperty(aWidget, aProp,
  1.3280 +      aFormatArgs, aDef);
  1.3281 +  },
  1.3282 +  /**
  1.3283 +   * Given a node, walk up to the first panel in its ancestor chain, and
  1.3284 +   * close it.
  1.3285 +   *
  1.3286 +   * @param aNode a node whose panel should be closed;
  1.3287 +   */
  1.3288 +  hidePanelForNode: function(aNode) {
  1.3289 +    CustomizableUIInternal.hidePanelForNode(aNode);
  1.3290 +  },
  1.3291 +  /**
  1.3292 +   * Check if a widget is a "special" widget: a spring, spacer or separator.
  1.3293 +   *
  1.3294 +   * @param aWidgetId the widget ID to check.
  1.3295 +   * @return true if the widget is 'special', false otherwise.
  1.3296 +   */
  1.3297 +  isSpecialWidget: function(aWidgetId) {
  1.3298 +    return CustomizableUIInternal.isSpecialWidget(aWidgetId);
  1.3299 +  },
  1.3300 +  /**
  1.3301 +   * Add listeners to a panel that will close it. For use from the menu panel
  1.3302 +   * and overflowable toolbar implementations, unlikely to be useful for
  1.3303 +   * consumers.
  1.3304 +   *
  1.3305 +   * @param aPanel the panel to which listeners should be attached.
  1.3306 +   */
  1.3307 +  addPanelCloseListeners: function(aPanel) {
  1.3308 +    CustomizableUIInternal.addPanelCloseListeners(aPanel);
  1.3309 +  },
  1.3310 +  /**
  1.3311 +   * Remove close listeners that have been added to a panel with
  1.3312 +   * addPanelCloseListeners. For use from the menu panel and overflowable
  1.3313 +   * toolbar implementations, unlikely to be useful for consumers.
  1.3314 +   *
  1.3315 +   * @param aPanel the panel from which listeners should be removed.
  1.3316 +   */
  1.3317 +  removePanelCloseListeners: function(aPanel) {
  1.3318 +    CustomizableUIInternal.removePanelCloseListeners(aPanel);
  1.3319 +  },
  1.3320 +  /**
  1.3321 +   * Notify listeners a widget is about to be dragged to an area. For use from
  1.3322 +   * Customize Mode only, do not use otherwise.
  1.3323 +   *
  1.3324 +   * @param aWidgetId the ID of the widget that is being dragged to an area.
  1.3325 +   * @param aArea     the ID of the area to which the widget is being dragged.
  1.3326 +   */
  1.3327 +  onWidgetDrag: function(aWidgetId, aArea) {
  1.3328 +    CustomizableUIInternal.notifyListeners("onWidgetDrag", aWidgetId, aArea);
  1.3329 +  },
  1.3330 +  /**
  1.3331 +   * Notify listeners that a window is entering customize mode. For use from
  1.3332 +   * Customize Mode only, do not use otherwise.
  1.3333 +   * @param aWindow the window entering customize mode
  1.3334 +   */
  1.3335 +  notifyStartCustomizing: function(aWindow) {
  1.3336 +    CustomizableUIInternal.notifyListeners("onCustomizeStart", aWindow);
  1.3337 +  },
  1.3338 +  /**
  1.3339 +   * Notify listeners that a window is exiting customize mode. For use from
  1.3340 +   * Customize Mode only, do not use otherwise.
  1.3341 +   * @param aWindow the window exiting customize mode
  1.3342 +   */
  1.3343 +  notifyEndCustomizing: function(aWindow) {
  1.3344 +    CustomizableUIInternal.notifyListeners("onCustomizeEnd", aWindow);
  1.3345 +  },
  1.3346 +
  1.3347 +  /**
  1.3348 +   * Notify toolbox(es) of a particular event. If you don't pass aWindow,
  1.3349 +   * all toolboxes will be notified. For use from Customize Mode only,
  1.3350 +   * do not use otherwise.
  1.3351 +   * @param aEvent the name of the event to send.
  1.3352 +   * @param aDetails optional, the details of the event.
  1.3353 +   * @param aWindow optional, the window in which to send the event.
  1.3354 +   */
  1.3355 +  dispatchToolboxEvent: function(aEvent, aDetails={}, aWindow=null) {
  1.3356 +    CustomizableUIInternal.dispatchToolboxEvent(aEvent, aDetails, aWindow);
  1.3357 +  },
  1.3358 +
  1.3359 +  /**
  1.3360 +   * Check whether an area is overflowable.
  1.3361 +   *
  1.3362 +   * @param aAreaId the ID of an area to check for overflowable-ness
  1.3363 +   * @return true if the area is overflowable, false otherwise.
  1.3364 +   */
  1.3365 +  isAreaOverflowable: function(aAreaId) {
  1.3366 +    let area = gAreas.get(aAreaId);
  1.3367 +    return area ? area.get("type") == this.TYPE_TOOLBAR && area.get("overflowable")
  1.3368 +                : false;
  1.3369 +  },
  1.3370 +  /**
  1.3371 +   * Obtain a string indicating the place of an element. This is intended
  1.3372 +   * for use from customize mode; You should generally use getPlacementOfWidget
  1.3373 +   * instead, which is cheaper because it does not use the DOM.
  1.3374 +   *
  1.3375 +   * @param aElement the DOM node whose place we need to check
  1.3376 +   * @return "toolbar" if the node is in a toolbar, "panel" if it is in the
  1.3377 +   *         menu panel, "palette" if it is in the (visible!) customization
  1.3378 +   *         palette, undefined otherwise.
  1.3379 +   */
  1.3380 +  getPlaceForItem: function(aElement) {
  1.3381 +    let place;
  1.3382 +    let node = aElement;
  1.3383 +    while (node && !place) {
  1.3384 +      if (node.localName == "toolbar")
  1.3385 +        place = "toolbar";
  1.3386 +      else if (node.id == CustomizableUI.AREA_PANEL)
  1.3387 +        place = "panel";
  1.3388 +      else if (node.id == "customization-palette")
  1.3389 +        place = "palette";
  1.3390 +
  1.3391 +      node = node.parentNode;
  1.3392 +    }
  1.3393 +    return place;
  1.3394 +  },
  1.3395 +
  1.3396 +  /**
  1.3397 +   * Check if a toolbar is builtin or not.
  1.3398 +   * @param aToolbarId the ID of the toolbar you want to check
  1.3399 +   */
  1.3400 +  isBuiltinToolbar: function(aToolbarId) {
  1.3401 +    return CustomizableUIInternal._builtinToolbars.has(aToolbarId);
  1.3402 +  },
  1.3403 +};
  1.3404 +Object.freeze(this.CustomizableUI);
  1.3405 +Object.freeze(this.CustomizableUI.windows);
  1.3406 +
  1.3407 +/**
  1.3408 + * All external consumers of widgets are really interacting with these wrappers
  1.3409 + * which provide a common interface.
  1.3410 + */
  1.3411 +
  1.3412 +/**
  1.3413 + * WidgetGroupWrapper is the common interface for interacting with an entire
  1.3414 + * widget group - AKA, all instances of a widget across a series of windows.
  1.3415 + * This particular wrapper is only used for widgets created via the provider
  1.3416 + * API.
  1.3417 + */
  1.3418 +function WidgetGroupWrapper(aWidget) {
  1.3419 +  this.isGroup = true;
  1.3420 +
  1.3421 +  const kBareProps = ["id", "source", "type", "disabled", "label", "tooltiptext",
  1.3422 +                      "showInPrivateBrowsing"];
  1.3423 +  for (let prop of kBareProps) {
  1.3424 +    let propertyName = prop;
  1.3425 +    this.__defineGetter__(propertyName, function() aWidget[propertyName]);
  1.3426 +  }
  1.3427 +
  1.3428 +  this.__defineGetter__("provider", function() CustomizableUI.PROVIDER_API);
  1.3429 +
  1.3430 +  this.__defineSetter__("disabled", function(aValue) {
  1.3431 +    aValue = !!aValue;
  1.3432 +    aWidget.disabled = aValue;
  1.3433 +    for (let [,instance] of aWidget.instances) {
  1.3434 +      instance.disabled = aValue;
  1.3435 +    }
  1.3436 +  });
  1.3437 +
  1.3438 +  this.forWindow = function WidgetGroupWrapper_forWindow(aWindow) {
  1.3439 +    let wrapperMap;
  1.3440 +    if (!gSingleWrapperCache.has(aWindow)) {
  1.3441 +      wrapperMap = new Map();
  1.3442 +      gSingleWrapperCache.set(aWindow, wrapperMap);
  1.3443 +    } else {
  1.3444 +      wrapperMap = gSingleWrapperCache.get(aWindow);
  1.3445 +    }
  1.3446 +    if (wrapperMap.has(aWidget.id)) {
  1.3447 +      return wrapperMap.get(aWidget.id);
  1.3448 +    }
  1.3449 +
  1.3450 +    let instance = aWidget.instances.get(aWindow.document);
  1.3451 +    if (!instance &&
  1.3452 +        (aWidget.showInPrivateBrowsing || !PrivateBrowsingUtils.isWindowPrivate(aWindow))) {
  1.3453 +      instance = CustomizableUIInternal.buildWidget(aWindow.document,
  1.3454 +                                                    aWidget);
  1.3455 +    }
  1.3456 +
  1.3457 +    let wrapper = new WidgetSingleWrapper(aWidget, instance);
  1.3458 +    wrapperMap.set(aWidget.id, wrapper);
  1.3459 +    return wrapper;
  1.3460 +  };
  1.3461 +
  1.3462 +  this.__defineGetter__("instances", function() {
  1.3463 +    // Can't use gBuildWindows here because some areas load lazily:
  1.3464 +    let placement = CustomizableUIInternal.getPlacementOfWidget(aWidget.id);
  1.3465 +    if (!placement) {
  1.3466 +      return [];
  1.3467 +    }
  1.3468 +    let area = placement.area;
  1.3469 +    let buildAreas = gBuildAreas.get(area);
  1.3470 +    if (!buildAreas) {
  1.3471 +      return [];
  1.3472 +    }
  1.3473 +    return [this.forWindow(node.ownerDocument.defaultView) for (node of buildAreas)];
  1.3474 +  });
  1.3475 +
  1.3476 +  this.__defineGetter__("areaType", function() {
  1.3477 +    let areaProps = gAreas.get(aWidget.currentArea);
  1.3478 +    return areaProps && areaProps.get("type");
  1.3479 +  });
  1.3480 +
  1.3481 +  Object.freeze(this);
  1.3482 +}
  1.3483 +
  1.3484 +/**
  1.3485 + * A WidgetSingleWrapper is a wrapper around a single instance of a widget in
  1.3486 + * a particular window.
  1.3487 + */
  1.3488 +function WidgetSingleWrapper(aWidget, aNode) {
  1.3489 +  this.isGroup = false;
  1.3490 +
  1.3491 +  this.node = aNode;
  1.3492 +  this.provider = CustomizableUI.PROVIDER_API;
  1.3493 +
  1.3494 +  const kGlobalProps = ["id", "type"];
  1.3495 +  for (let prop of kGlobalProps) {
  1.3496 +    this[prop] = aWidget[prop];
  1.3497 +  }
  1.3498 +
  1.3499 +  const kNodeProps = ["label", "tooltiptext"];
  1.3500 +  for (let prop of kNodeProps) {
  1.3501 +    let propertyName = prop;
  1.3502 +    // Look at the node for these, instead of the widget data, to ensure the
  1.3503 +    // wrapper always reflects this live instance.
  1.3504 +    this.__defineGetter__(propertyName,
  1.3505 +                          function() aNode.getAttribute(propertyName));
  1.3506 +  }
  1.3507 +
  1.3508 +  this.__defineGetter__("disabled", function() aNode.disabled);
  1.3509 +  this.__defineSetter__("disabled", function(aValue) {
  1.3510 +    aNode.disabled = !!aValue;
  1.3511 +  });
  1.3512 +
  1.3513 +  this.__defineGetter__("anchor", function() {
  1.3514 +    let anchorId;
  1.3515 +    // First check for an anchor for the area:
  1.3516 +    let placement = CustomizableUIInternal.getPlacementOfWidget(aWidget.id);
  1.3517 +    if (placement) {
  1.3518 +      anchorId = gAreas.get(placement.area).get("anchor");
  1.3519 +    }
  1.3520 +    if (!anchorId) {
  1.3521 +      anchorId = aNode.getAttribute("cui-anchorid");
  1.3522 +    }
  1.3523 +
  1.3524 +    return anchorId ? aNode.ownerDocument.getElementById(anchorId)
  1.3525 +                    : aNode;
  1.3526 +  });
  1.3527 +
  1.3528 +  this.__defineGetter__("overflowed", function() {
  1.3529 +    return aNode.getAttribute("overflowedItem") == "true";
  1.3530 +  });
  1.3531 +
  1.3532 +  Object.freeze(this);
  1.3533 +}
  1.3534 +
  1.3535 +/**
  1.3536 + * XULWidgetGroupWrapper is the common interface for interacting with an entire
  1.3537 + * widget group - AKA, all instances of a widget across a series of windows.
  1.3538 + * This particular wrapper is only used for widgets created via the old-school
  1.3539 + * XUL method (overlays, or programmatically injecting toolbaritems, or other
  1.3540 + * such things).
  1.3541 + */
  1.3542 +//XXXunf Going to need to hook this up to some events to keep it all live.
  1.3543 +function XULWidgetGroupWrapper(aWidgetId) {
  1.3544 +  this.isGroup = true;
  1.3545 +  this.id = aWidgetId;
  1.3546 +  this.type = "custom";
  1.3547 +  this.provider = CustomizableUI.PROVIDER_XUL;
  1.3548 +
  1.3549 +  this.forWindow = function XULWidgetGroupWrapper_forWindow(aWindow) {
  1.3550 +    let wrapperMap;
  1.3551 +    if (!gSingleWrapperCache.has(aWindow)) {
  1.3552 +      wrapperMap = new Map();
  1.3553 +      gSingleWrapperCache.set(aWindow, wrapperMap);
  1.3554 +    } else {
  1.3555 +      wrapperMap = gSingleWrapperCache.get(aWindow);
  1.3556 +    }
  1.3557 +    if (wrapperMap.has(aWidgetId)) {
  1.3558 +      return wrapperMap.get(aWidgetId);
  1.3559 +    }
  1.3560 +
  1.3561 +    let instance = aWindow.document.getElementById(aWidgetId);
  1.3562 +    if (!instance) {
  1.3563 +      // Toolbar palettes aren't part of the document, so elements in there
  1.3564 +      // won't be found via document.getElementById().
  1.3565 +      instance = aWindow.gNavToolbox.palette.getElementsByAttribute("id", aWidgetId)[0];
  1.3566 +    }
  1.3567 +
  1.3568 +    let wrapper = new XULWidgetSingleWrapper(aWidgetId, instance, aWindow.document);
  1.3569 +    wrapperMap.set(aWidgetId, wrapper);
  1.3570 +    return wrapper;
  1.3571 +  };
  1.3572 +
  1.3573 +  this.__defineGetter__("areaType", function() {
  1.3574 +    let placement = CustomizableUIInternal.getPlacementOfWidget(aWidgetId);
  1.3575 +    if (!placement) {
  1.3576 +      return null;
  1.3577 +    }
  1.3578 +
  1.3579 +    let areaProps = gAreas.get(placement.area);
  1.3580 +    return areaProps && areaProps.get("type");
  1.3581 +  });
  1.3582 +
  1.3583 +  this.__defineGetter__("instances", function() {
  1.3584 +    return [this.forWindow(win) for ([win,] of gBuildWindows)];
  1.3585 +  });
  1.3586 +
  1.3587 +  Object.freeze(this);
  1.3588 +}
  1.3589 +
  1.3590 +/**
  1.3591 + * A XULWidgetSingleWrapper is a wrapper around a single instance of a XUL 
  1.3592 + * widget in a particular window.
  1.3593 + */
  1.3594 +function XULWidgetSingleWrapper(aWidgetId, aNode, aDocument) {
  1.3595 +  this.isGroup = false;
  1.3596 +
  1.3597 +  this.id = aWidgetId;
  1.3598 +  this.type = "custom";
  1.3599 +  this.provider = CustomizableUI.PROVIDER_XUL;
  1.3600 +
  1.3601 +  let weakDoc = Cu.getWeakReference(aDocument);
  1.3602 +  // If we keep a strong ref, the weak ref will never die, so null it out:
  1.3603 +  aDocument = null;
  1.3604 +
  1.3605 +  this.__defineGetter__("node", function() {
  1.3606 +    // If we've set this to null (further down), we're sure there's nothing to
  1.3607 +    // be gotten here, so bail out early:
  1.3608 +    if (!weakDoc) {
  1.3609 +      return null;
  1.3610 +    }
  1.3611 +    if (aNode) {
  1.3612 +      // Return the last known node if it's still in the DOM...
  1.3613 +      if (aNode.ownerDocument.contains(aNode)) {
  1.3614 +        return aNode;
  1.3615 +      }
  1.3616 +      // ... or the toolbox
  1.3617 +      let toolbox = aNode.ownerDocument.defaultView.gNavToolbox;
  1.3618 +      if (toolbox && toolbox.palette && aNode.parentNode == toolbox.palette) {
  1.3619 +        return aNode;
  1.3620 +      }
  1.3621 +      // If it isn't, clear the cached value and fall through to the "slow" case:
  1.3622 +      aNode = null;
  1.3623 +    }
  1.3624 +
  1.3625 +    let doc = weakDoc.get();
  1.3626 +    if (doc) {
  1.3627 +      // Store locally so we can cache the result:
  1.3628 +      aNode = CustomizableUIInternal.findWidgetInWindow(aWidgetId, doc.defaultView);
  1.3629 +      return aNode;
  1.3630 +    }
  1.3631 +    // The weakref to the document is dead, we're done here forever more:
  1.3632 +    weakDoc = null;
  1.3633 +    return null;
  1.3634 +  });
  1.3635 +
  1.3636 +  this.__defineGetter__("anchor", function() {
  1.3637 +    let anchorId;
  1.3638 +    // First check for an anchor for the area:
  1.3639 +    let placement = CustomizableUIInternal.getPlacementOfWidget(aWidgetId);
  1.3640 +    if (placement) {
  1.3641 +      anchorId = gAreas.get(placement.area).get("anchor");
  1.3642 +    }
  1.3643 +
  1.3644 +    let node = this.node;
  1.3645 +    if (!anchorId && node) {
  1.3646 +      anchorId = node.getAttribute("cui-anchorid");
  1.3647 +    }
  1.3648 +
  1.3649 +    return (anchorId && node) ? node.ownerDocument.getElementById(anchorId) : node;
  1.3650 +  });
  1.3651 +
  1.3652 +  this.__defineGetter__("overflowed", function() {
  1.3653 +    let node = this.node;
  1.3654 +    if (!node) {
  1.3655 +      return false;
  1.3656 +    }
  1.3657 +    return node.getAttribute("overflowedItem") == "true";
  1.3658 +  });
  1.3659 +
  1.3660 +  Object.freeze(this);
  1.3661 +}
  1.3662 +
  1.3663 +const LAZY_RESIZE_INTERVAL_MS = 200;
  1.3664 +
  1.3665 +function OverflowableToolbar(aToolbarNode) {
  1.3666 +  this._toolbar = aToolbarNode;
  1.3667 +  this._collapsed = new Map();
  1.3668 +  this._enabled = true;
  1.3669 +
  1.3670 +  this._toolbar.setAttribute("overflowable", "true");
  1.3671 +  let doc = this._toolbar.ownerDocument;
  1.3672 +  this._target = this._toolbar.customizationTarget;
  1.3673 +  this._list = doc.getElementById(this._toolbar.getAttribute("overflowtarget"));
  1.3674 +  this._list.toolbox = this._toolbar.toolbox;
  1.3675 +  this._list.customizationTarget = this._list;
  1.3676 +
  1.3677 +  let window = this._toolbar.ownerDocument.defaultView;
  1.3678 +  if (window.gBrowserInit.delayedStartupFinished) {
  1.3679 +    this.init();
  1.3680 +  } else {
  1.3681 +    Services.obs.addObserver(this, "browser-delayed-startup-finished", false);
  1.3682 +  }
  1.3683 +}
  1.3684 +
  1.3685 +OverflowableToolbar.prototype = {
  1.3686 +  initialized: false,
  1.3687 +  _forceOnOverflow: false,
  1.3688 +
  1.3689 +  observe: function(aSubject, aTopic, aData) {
  1.3690 +    if (aTopic == "browser-delayed-startup-finished" &&
  1.3691 +        aSubject == this._toolbar.ownerDocument.defaultView) {
  1.3692 +      Services.obs.removeObserver(this, "browser-delayed-startup-finished");
  1.3693 +      this.init();
  1.3694 +    }
  1.3695 +  },
  1.3696 +
  1.3697 +  init: function() {
  1.3698 +    let doc = this._toolbar.ownerDocument;
  1.3699 +    let window = doc.defaultView;
  1.3700 +    window.addEventListener("resize", this);
  1.3701 +    window.gNavToolbox.addEventListener("customizationstarting", this);
  1.3702 +    window.gNavToolbox.addEventListener("aftercustomization", this);
  1.3703 +
  1.3704 +    let chevronId = this._toolbar.getAttribute("overflowbutton");
  1.3705 +    this._chevron = doc.getElementById(chevronId);
  1.3706 +    this._chevron.addEventListener("command", this);
  1.3707 +
  1.3708 +    let panelId = this._toolbar.getAttribute("overflowpanel");
  1.3709 +    this._panel = doc.getElementById(panelId);
  1.3710 +    this._panel.addEventListener("popuphiding", this);
  1.3711 +    CustomizableUIInternal.addPanelCloseListeners(this._panel);
  1.3712 +
  1.3713 +    CustomizableUI.addListener(this);
  1.3714 +
  1.3715 +    // The 'overflow' event may have been fired before init was called.
  1.3716 +    if (this._toolbar.overflowedDuringConstruction) {
  1.3717 +      this.onOverflow(this._toolbar.overflowedDuringConstruction);
  1.3718 +      this._toolbar.overflowedDuringConstruction = null;
  1.3719 +    }
  1.3720 +
  1.3721 +    this.initialized = true;
  1.3722 +  },
  1.3723 +
  1.3724 +  uninit: function() {
  1.3725 +    this._toolbar.removeEventListener("overflow", this._toolbar);
  1.3726 +    this._toolbar.removeEventListener("underflow", this._toolbar);
  1.3727 +    this._toolbar.removeAttribute("overflowable");
  1.3728 +
  1.3729 +    if (!this.initialized) {
  1.3730 +      Services.obs.removeObserver(this, "browser-delayed-startup-finished");
  1.3731 +      return;
  1.3732 +    }
  1.3733 +
  1.3734 +    this._disable();
  1.3735 +
  1.3736 +    let window = this._toolbar.ownerDocument.defaultView;
  1.3737 +    window.removeEventListener("resize", this);
  1.3738 +    window.gNavToolbox.removeEventListener("customizationstarting", this);
  1.3739 +    window.gNavToolbox.removeEventListener("aftercustomization", this);
  1.3740 +    this._chevron.removeEventListener("command", this);
  1.3741 +    this._panel.removeEventListener("popuphiding", this);
  1.3742 +    CustomizableUI.removeListener(this);
  1.3743 +    CustomizableUIInternal.removePanelCloseListeners(this._panel);
  1.3744 +  },
  1.3745 +
  1.3746 +  handleEvent: function(aEvent) {
  1.3747 +    switch(aEvent.type) {
  1.3748 +      case "resize":
  1.3749 +        this._onResize(aEvent);
  1.3750 +        break;
  1.3751 +      case "command":
  1.3752 +        if (aEvent.target == this._chevron) {
  1.3753 +          this._onClickChevron(aEvent);
  1.3754 +        } else {
  1.3755 +          this._panel.hidePopup();
  1.3756 +        }
  1.3757 +        break;
  1.3758 +      case "popuphiding":
  1.3759 +        this._onPanelHiding(aEvent);
  1.3760 +        break;
  1.3761 +      case "customizationstarting":
  1.3762 +        this._disable();
  1.3763 +        break;
  1.3764 +      case "aftercustomization":
  1.3765 +        this._enable();
  1.3766 +        break;
  1.3767 +    }
  1.3768 +  },
  1.3769 +
  1.3770 +  show: function() {
  1.3771 +    let deferred = Promise.defer();
  1.3772 +    if (this._panel.state == "open") {
  1.3773 +      deferred.resolve();
  1.3774 +      return deferred.promise;
  1.3775 +    }
  1.3776 +    let doc = this._panel.ownerDocument;
  1.3777 +    this._panel.hidden = false;
  1.3778 +    let contextMenu = doc.getElementById(this._panel.getAttribute("context"));
  1.3779 +    gELS.addSystemEventListener(contextMenu, 'command', this, true);
  1.3780 +    let anchor = doc.getAnonymousElementByAttribute(this._chevron, "class", "toolbarbutton-icon");
  1.3781 +    this._panel.openPopup(anchor || this._chevron);
  1.3782 +    this._chevron.open = true;
  1.3783 +
  1.3784 +    this._panel.addEventListener("popupshown", function onPopupShown() {
  1.3785 +      this.removeEventListener("popupshown", onPopupShown);
  1.3786 +      deferred.resolve();
  1.3787 +    });
  1.3788 +
  1.3789 +    return deferred.promise;
  1.3790 +  },
  1.3791 +
  1.3792 +  _onClickChevron: function(aEvent) {
  1.3793 +    if (this._chevron.open) {
  1.3794 +      this._panel.hidePopup();
  1.3795 +      this._chevron.open = false;
  1.3796 +    } else {
  1.3797 +      this.show();
  1.3798 +    }
  1.3799 +  },
  1.3800 +
  1.3801 +  _onPanelHiding: function(aEvent) {
  1.3802 +    this._chevron.open = false;
  1.3803 +    let doc = aEvent.target.ownerDocument;
  1.3804 +    let contextMenu = doc.getElementById(this._panel.getAttribute("context"));
  1.3805 +    gELS.removeSystemEventListener(contextMenu, 'command', this, true);
  1.3806 +  },
  1.3807 +
  1.3808 +  onOverflow: function(aEvent) {
  1.3809 +    if (!this._enabled ||
  1.3810 +        (aEvent && aEvent.target != this._toolbar.customizationTarget))
  1.3811 +      return;
  1.3812 +
  1.3813 +    let child = this._target.lastChild;
  1.3814 +
  1.3815 +    while (child && this._target.scrollLeftMax > 0) {
  1.3816 +      let prevChild = child.previousSibling;
  1.3817 +
  1.3818 +      if (child.getAttribute("overflows") != "false") {
  1.3819 +        this._collapsed.set(child.id, this._target.clientWidth);
  1.3820 +        child.setAttribute("overflowedItem", true);
  1.3821 +        child.setAttribute("cui-anchorid", this._chevron.id);
  1.3822 +        CustomizableUIInternal.notifyListeners("onWidgetOverflow", child, this._target);
  1.3823 +
  1.3824 +        this._list.insertBefore(child, this._list.firstChild);
  1.3825 +        if (!this._toolbar.hasAttribute("overflowing")) {
  1.3826 +          CustomizableUI.addListener(this);
  1.3827 +        }
  1.3828 +        this._toolbar.setAttribute("overflowing", "true");
  1.3829 +      }
  1.3830 +      child = prevChild;
  1.3831 +    };
  1.3832 +
  1.3833 +    let win = this._target.ownerDocument.defaultView;
  1.3834 +    win.UpdateUrlbarSearchSplitterState();
  1.3835 +  },
  1.3836 +
  1.3837 +  _onResize: function(aEvent) {
  1.3838 +    if (!this._lazyResizeHandler) {
  1.3839 +      this._lazyResizeHandler = new DeferredTask(this._onLazyResize.bind(this),
  1.3840 +                                                 LAZY_RESIZE_INTERVAL_MS);
  1.3841 +    }
  1.3842 +    this._lazyResizeHandler.arm();
  1.3843 +  },
  1.3844 +
  1.3845 +  _moveItemsBackToTheirOrigin: function(shouldMoveAllItems) {
  1.3846 +    let placements = gPlacements.get(this._toolbar.id);
  1.3847 +    while (this._list.firstChild) {
  1.3848 +      let child = this._list.firstChild;
  1.3849 +      let minSize = this._collapsed.get(child.id);
  1.3850 +
  1.3851 +      if (!shouldMoveAllItems &&
  1.3852 +          minSize &&
  1.3853 +          this._target.clientWidth <= minSize) {
  1.3854 +        return;
  1.3855 +      }
  1.3856 +
  1.3857 +      this._collapsed.delete(child.id);
  1.3858 +      let beforeNodeIndex = placements.indexOf(child.id) + 1;
  1.3859 +      // If this is a skipintoolbarset item, meaning it doesn't occur in the placements list,
  1.3860 +      // we're inserting it at the end. This will mean first-in, first-out (more or less)
  1.3861 +      // leading to as little change in order as possible.
  1.3862 +      if (beforeNodeIndex == 0) {
  1.3863 +        beforeNodeIndex = placements.length;
  1.3864 +      }
  1.3865 +      let inserted = false;
  1.3866 +      for (; beforeNodeIndex < placements.length; beforeNodeIndex++) {
  1.3867 +        let beforeNode = this._target.getElementsByAttribute("id", placements[beforeNodeIndex])[0];
  1.3868 +        if (beforeNode) {
  1.3869 +          this._target.insertBefore(child, beforeNode);
  1.3870 +          inserted = true;
  1.3871 +          break;
  1.3872 +        }
  1.3873 +      }
  1.3874 +      if (!inserted) {
  1.3875 +        this._target.appendChild(child);
  1.3876 +      }
  1.3877 +      child.removeAttribute("cui-anchorid");
  1.3878 +      child.removeAttribute("overflowedItem");
  1.3879 +      CustomizableUIInternal.notifyListeners("onWidgetUnderflow", child, this._target);
  1.3880 +    }
  1.3881 +
  1.3882 +    let win = this._target.ownerDocument.defaultView;
  1.3883 +    win.UpdateUrlbarSearchSplitterState();
  1.3884 +
  1.3885 +    if (!this._collapsed.size) {
  1.3886 +      this._toolbar.removeAttribute("overflowing");
  1.3887 +      CustomizableUI.removeListener(this);
  1.3888 +    }
  1.3889 +  },
  1.3890 +
  1.3891 +  _onLazyResize: function() {
  1.3892 +    if (!this._enabled)
  1.3893 +      return;
  1.3894 +
  1.3895 +    if (this._target.scrollLeftMax > 0) {
  1.3896 +      this.onOverflow();
  1.3897 +    } else {
  1.3898 +      this._moveItemsBackToTheirOrigin();
  1.3899 +    }
  1.3900 +  },
  1.3901 +
  1.3902 +  _disable: function() {
  1.3903 +    this._enabled = false;
  1.3904 +    this._moveItemsBackToTheirOrigin(true);
  1.3905 +    if (this._lazyResizeHandler) {
  1.3906 +      this._lazyResizeHandler.disarm();
  1.3907 +    }
  1.3908 +  },
  1.3909 +
  1.3910 +  _enable: function() {
  1.3911 +    this._enabled = true;
  1.3912 +    this.onOverflow();
  1.3913 +  },
  1.3914 +
  1.3915 +  onWidgetBeforeDOMChange: function(aNode, aNextNode, aContainer) {
  1.3916 +    if (aContainer != this._target && aContainer != this._list) {
  1.3917 +      return;
  1.3918 +    }
  1.3919 +    // When we (re)move an item, update all the items that come after it in the list
  1.3920 +    // with the minsize *of the item before the to-be-removed node*. This way, we
  1.3921 +    // ensure that we try to move items back as soon as that's possible.
  1.3922 +    if (aNode.parentNode == this._list) {
  1.3923 +      let updatedMinSize;
  1.3924 +      if (aNode.previousSibling) {
  1.3925 +        updatedMinSize = this._collapsed.get(aNode.previousSibling.id);
  1.3926 +      } else {
  1.3927 +        // Force (these) items to try to flow back into the bar:
  1.3928 +        updatedMinSize = 1;
  1.3929 +      }
  1.3930 +      let nextItem = aNode.nextSibling;
  1.3931 +      while (nextItem) {
  1.3932 +        this._collapsed.set(nextItem.id, updatedMinSize);
  1.3933 +        nextItem = nextItem.nextSibling;
  1.3934 +      }
  1.3935 +    }
  1.3936 +  },
  1.3937 +
  1.3938 +  onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer) {
  1.3939 +    if (aContainer != this._target && aContainer != this._list) {
  1.3940 +      return;
  1.3941 +    }
  1.3942 +
  1.3943 +    let nowInBar = aNode.parentNode == aContainer;
  1.3944 +    let nowOverflowed = aNode.parentNode == this._list;
  1.3945 +    let wasOverflowed = this._collapsed.has(aNode.id);
  1.3946 +
  1.3947 +    // If this wasn't overflowed before...
  1.3948 +    if (!wasOverflowed) {
  1.3949 +      // ... but it is now, then we added to the overflow panel. Exciting stuff:
  1.3950 +      if (nowOverflowed) {
  1.3951 +        // NB: we're guaranteed that it has a previousSibling, because if it didn't,
  1.3952 +        // we would have added it to the toolbar instead. See getOverflowedNextNode.
  1.3953 +        let prevId = aNode.previousSibling.id;
  1.3954 +        let minSize = this._collapsed.get(prevId);
  1.3955 +        this._collapsed.set(aNode.id, minSize);
  1.3956 +        aNode.setAttribute("cui-anchorid", this._chevron.id);
  1.3957 +        aNode.setAttribute("overflowedItem", true);
  1.3958 +        CustomizableUIInternal.notifyListeners("onWidgetOverflow", aNode, this._target);
  1.3959 +      }
  1.3960 +      // If it is not overflowed and not in the toolbar, and was not overflowed
  1.3961 +      // either, it moved out of the toolbar. That means there's now space in there!
  1.3962 +      // Let's try to move stuff back:
  1.3963 +      else if (!nowInBar) {
  1.3964 +        this._moveItemsBackToTheirOrigin(true);
  1.3965 +      }
  1.3966 +      // If it's in the toolbar now, then we don't care. An overflow event may
  1.3967 +      // fire afterwards; that's ok!
  1.3968 +    }
  1.3969 +    // If it used to be overflowed...
  1.3970 +    else {
  1.3971 +      // ... and isn't anymore, let's remove our bookkeeping:
  1.3972 +      if (!nowOverflowed) {
  1.3973 +        this._collapsed.delete(aNode.id);
  1.3974 +        aNode.removeAttribute("cui-anchorid");
  1.3975 +        aNode.removeAttribute("overflowedItem");
  1.3976 +        CustomizableUIInternal.notifyListeners("onWidgetUnderflow", aNode, this._target);
  1.3977 +
  1.3978 +        if (!this._collapsed.size) {
  1.3979 +          this._toolbar.removeAttribute("overflowing");
  1.3980 +          CustomizableUI.removeListener(this);
  1.3981 +        }
  1.3982 +      }
  1.3983 +      // but if it still is, it must have changed places. Bookkeep:
  1.3984 +      else {
  1.3985 +        if (aNode.previousSibling) {
  1.3986 +          let prevId = aNode.previousSibling.id;
  1.3987 +          let minSize = this._collapsed.get(prevId);
  1.3988 +          this._collapsed.set(aNode.id, minSize);
  1.3989 +        } else {
  1.3990 +          // If it's now the first item in the overflow list,
  1.3991 +          // maybe we can return it:
  1.3992 +          this._moveItemsBackToTheirOrigin();
  1.3993 +        }
  1.3994 +      }
  1.3995 +    }
  1.3996 +  },
  1.3997 +
  1.3998 +  findOverflowedInsertionPoints: function(aNode) {
  1.3999 +    let newNodeCanOverflow = aNode.getAttribute("overflows") != "false";
  1.4000 +    let areaId = this._toolbar.id;
  1.4001 +    let placements = gPlacements.get(areaId);
  1.4002 +    let nodeIndex = placements.indexOf(aNode.id);
  1.4003 +    let nodeBeforeNewNodeIsOverflown = false;
  1.4004 +
  1.4005 +    let loopIndex = -1;
  1.4006 +    while (++loopIndex < placements.length) {
  1.4007 +      let nextNodeId = placements[loopIndex];
  1.4008 +      if (loopIndex > nodeIndex) {
  1.4009 +        if (newNodeCanOverflow && this._collapsed.has(nextNodeId)) {
  1.4010 +          let nextNode = this._list.getElementsByAttribute("id", nextNodeId).item(0);
  1.4011 +          if (nextNode) {
  1.4012 +            return [this._list, nextNode];
  1.4013 +          }
  1.4014 +        }
  1.4015 +        if (!nodeBeforeNewNodeIsOverflown || !newNodeCanOverflow) {
  1.4016 +          let nextNode = this._target.getElementsByAttribute("id", nextNodeId).item(0);
  1.4017 +          if (nextNode) {
  1.4018 +            return [this._target, nextNode];
  1.4019 +          }
  1.4020 +        }
  1.4021 +      } else if (loopIndex < nodeIndex && this._collapsed.has(nextNodeId)) {
  1.4022 +        nodeBeforeNewNodeIsOverflown = true;
  1.4023 +      }
  1.4024 +    }
  1.4025 +
  1.4026 +    let containerForAppending = (this._collapsed.size && newNodeCanOverflow) ?
  1.4027 +                                this._list : this._target;
  1.4028 +    return [containerForAppending, null];
  1.4029 +  },
  1.4030 +
  1.4031 +  getContainerFor: function(aNode) {
  1.4032 +    if (aNode.getAttribute("overflowedItem") == "true") {
  1.4033 +      return this._list;
  1.4034 +    }
  1.4035 +    return this._target;
  1.4036 +  },
  1.4037 +};
  1.4038 +
  1.4039 +CustomizableUIInternal.initialize();

mercurial