browser/components/customizableui/content/panelUI.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/customizableui/content/panelUI.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,521 @@
     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 file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
     1.9 +                                  "resource:///modules/CustomizableUI.jsm");
    1.10 +XPCOMUtils.defineLazyModuleGetter(this, "ScrollbarSampler",
    1.11 +                                  "resource:///modules/ScrollbarSampler.jsm");
    1.12 +XPCOMUtils.defineLazyModuleGetter(this, "Promise",
    1.13 +                                  "resource://gre/modules/Promise.jsm");
    1.14 +XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
    1.15 +                                  "resource://gre/modules/ShortcutUtils.jsm");
    1.16 +/**
    1.17 + * Maintains the state and dispatches events for the main menu panel.
    1.18 + */
    1.19 +
    1.20 +const PanelUI = {
    1.21 +  /** Panel events that we listen for. **/
    1.22 +  get kEvents() ["popupshowing", "popupshown", "popuphiding", "popuphidden"],
    1.23 +  /**
    1.24 +   * Used for lazily getting and memoizing elements from the document. Lazy
    1.25 +   * getters are set in init, and memoizing happens after the first retrieval.
    1.26 +   */
    1.27 +  get kElements() {
    1.28 +    return {
    1.29 +      contents: "PanelUI-contents",
    1.30 +      mainView: "PanelUI-mainView",
    1.31 +      multiView: "PanelUI-multiView",
    1.32 +      helpView: "PanelUI-helpView",
    1.33 +      menuButton: "PanelUI-menu-button",
    1.34 +      panel: "PanelUI-popup",
    1.35 +      scroller: "PanelUI-contents-scroller"
    1.36 +    };
    1.37 +  },
    1.38 +
    1.39 +  _initialized: false,
    1.40 +  init: function() {
    1.41 +    for (let [k, v] of Iterator(this.kElements)) {
    1.42 +      // Need to do fresh let-bindings per iteration
    1.43 +      let getKey = k;
    1.44 +      let id = v;
    1.45 +      this.__defineGetter__(getKey, function() {
    1.46 +        delete this[getKey];
    1.47 +        return this[getKey] = document.getElementById(id);
    1.48 +      });
    1.49 +    }
    1.50 +
    1.51 +    this.menuButton.addEventListener("mousedown", this);
    1.52 +    this.menuButton.addEventListener("keypress", this);
    1.53 +    this._overlayScrollListenerBoundFn = this._overlayScrollListener.bind(this);
    1.54 +    window.matchMedia("(-moz-overlay-scrollbars)").addListener(this._overlayScrollListenerBoundFn);
    1.55 +    CustomizableUI.addListener(this);
    1.56 +    this._initialized = true;
    1.57 +  },
    1.58 +
    1.59 +  _eventListenersAdded: false,
    1.60 +  _ensureEventListenersAdded: function() {
    1.61 +    if (this._eventListenersAdded)
    1.62 +      return;
    1.63 +    this._addEventListeners();
    1.64 +  },
    1.65 +
    1.66 +  _addEventListeners: function() {
    1.67 +    for (let event of this.kEvents) {
    1.68 +      this.panel.addEventListener(event, this);
    1.69 +    }
    1.70 +
    1.71 +    this.helpView.addEventListener("ViewShowing", this._onHelpViewShow, false);
    1.72 +    this._eventListenersAdded = true;
    1.73 +  },
    1.74 +
    1.75 +  uninit: function() {
    1.76 +    for (let event of this.kEvents) {
    1.77 +      this.panel.removeEventListener(event, this);
    1.78 +    }
    1.79 +    this.helpView.removeEventListener("ViewShowing", this._onHelpViewShow);
    1.80 +    this.menuButton.removeEventListener("mousedown", this);
    1.81 +    this.menuButton.removeEventListener("keypress", this);
    1.82 +    window.matchMedia("(-moz-overlay-scrollbars)").removeListener(this._overlayScrollListenerBoundFn);
    1.83 +    CustomizableUI.removeListener(this);
    1.84 +    this._overlayScrollListenerBoundFn = null;
    1.85 +  },
    1.86 +
    1.87 +  /**
    1.88 +   * Customize mode extracts the mainView and puts it somewhere else while the
    1.89 +   * user customizes. Upon completion, this function can be called to put the
    1.90 +   * panel back to where it belongs in normal browsing mode.
    1.91 +   *
    1.92 +   * @param aMainView
    1.93 +   *        The mainView node to put back into place.
    1.94 +   */
    1.95 +  setMainView: function(aMainView) {
    1.96 +    this._ensureEventListenersAdded();
    1.97 +    this.multiView.setMainView(aMainView);
    1.98 +  },
    1.99 +
   1.100 +  /**
   1.101 +   * Opens the menu panel if it's closed, or closes it if it's
   1.102 +   * open.
   1.103 +   *
   1.104 +   * @param aEvent the event that triggers the toggle.
   1.105 +   */
   1.106 +  toggle: function(aEvent) {
   1.107 +    // Don't show the panel if the window is in customization mode,
   1.108 +    // since this button doubles as an exit path for the user in this case.
   1.109 +    if (document.documentElement.hasAttribute("customizing")) {
   1.110 +      return;
   1.111 +    }
   1.112 +    this._ensureEventListenersAdded();
   1.113 +    if (this.panel.state == "open") {
   1.114 +      this.hide();
   1.115 +    } else if (this.panel.state == "closed") {
   1.116 +      this.show(aEvent);
   1.117 +    }
   1.118 +  },
   1.119 +
   1.120 +  /**
   1.121 +   * Opens the menu panel. If the event target has a child with the
   1.122 +   * toolbarbutton-icon attribute, the panel will be anchored on that child.
   1.123 +   * Otherwise, the panel is anchored on the event target itself.
   1.124 +   *
   1.125 +   * @param aEvent the event (if any) that triggers showing the menu.
   1.126 +   */
   1.127 +  show: function(aEvent) {
   1.128 +    let deferred = Promise.defer();
   1.129 +
   1.130 +    this.ensureReady().then(() => {
   1.131 +      if (this.panel.state == "open" ||
   1.132 +          document.documentElement.hasAttribute("customizing")) {
   1.133 +        deferred.resolve();
   1.134 +        return;
   1.135 +      }
   1.136 +
   1.137 +      let editControlPlacement = CustomizableUI.getPlacementOfWidget("edit-controls");
   1.138 +      if (editControlPlacement && editControlPlacement.area == CustomizableUI.AREA_PANEL) {
   1.139 +        updateEditUIVisibility();
   1.140 +      }
   1.141 +
   1.142 +      let personalBookmarksPlacement = CustomizableUI.getPlacementOfWidget("personal-bookmarks");
   1.143 +      if (personalBookmarksPlacement &&
   1.144 +          personalBookmarksPlacement.area == CustomizableUI.AREA_PANEL) {
   1.145 +        PlacesToolbarHelper.customizeChange();
   1.146 +      }
   1.147 +
   1.148 +      let anchor;
   1.149 +      if (!aEvent ||
   1.150 +          aEvent.type == "command") {
   1.151 +        anchor = this.menuButton;
   1.152 +      } else {
   1.153 +        anchor = aEvent.target;
   1.154 +      }
   1.155 +
   1.156 +      this.panel.addEventListener("popupshown", function onPopupShown() {
   1.157 +        this.removeEventListener("popupshown", onPopupShown);
   1.158 +        // As an optimization for the customize mode transition, we preload
   1.159 +        // about:customizing in the background once the menu panel is first
   1.160 +        // shown.
   1.161 +        gCustomizationTabPreloader.ensurePreloading();
   1.162 +        deferred.resolve();
   1.163 +      });
   1.164 +
   1.165 +      let iconAnchor =
   1.166 +        document.getAnonymousElementByAttribute(anchor, "class",
   1.167 +                                                "toolbarbutton-icon");
   1.168 +      this.panel.openPopup(iconAnchor || anchor);
   1.169 +    });
   1.170 +
   1.171 +    return deferred.promise;
   1.172 +  },
   1.173 +
   1.174 +  /**
   1.175 +   * If the menu panel is being shown, hide it.
   1.176 +   */
   1.177 +  hide: function() {
   1.178 +    if (document.documentElement.hasAttribute("customizing")) {
   1.179 +      return;
   1.180 +    }
   1.181 +
   1.182 +    this.panel.hidePopup();
   1.183 +  },
   1.184 +
   1.185 +  handleEvent: function(aEvent) {
   1.186 +    switch (aEvent.type) {
   1.187 +      case "popupshowing":
   1.188 +        this._adjustLabelsForAutoHyphens();
   1.189 +        // Fall through
   1.190 +      case "popupshown":
   1.191 +        // Fall through
   1.192 +      case "popuphiding":
   1.193 +        // Fall through
   1.194 +      case "popuphidden":
   1.195 +        this._updatePanelButton(aEvent.target);
   1.196 +        break;
   1.197 +      case "mousedown":
   1.198 +        if (aEvent.button == 0)
   1.199 +          this.toggle(aEvent);
   1.200 +        break;
   1.201 +      case "keypress":
   1.202 +        this.toggle(aEvent);
   1.203 +        break;
   1.204 +    }
   1.205 +  },
   1.206 +
   1.207 +  isReady: function() {
   1.208 +    return !!this._isReady;
   1.209 +  },
   1.210 +
   1.211 +  /**
   1.212 +   * Registering the menu panel is done lazily for performance reasons. This
   1.213 +   * method is exposed so that CustomizationMode can force panel-readyness in the
   1.214 +   * event that customization mode is started before the panel has been opened
   1.215 +   * by the user.
   1.216 +   *
   1.217 +   * @param aCustomizing (optional) set to true if this was called while entering
   1.218 +   *        customization mode. If that's the case, we trust that customization
   1.219 +   *        mode will handle calling beginBatchUpdate and endBatchUpdate.
   1.220 +   *
   1.221 +   * @return a Promise that resolves once the panel is ready to roll.
   1.222 +   */
   1.223 +  ensureReady: function(aCustomizing=false) {
   1.224 +    if (this._readyPromise) {
   1.225 +      return this._readyPromise;
   1.226 +    }
   1.227 +    this._readyPromise = Task.spawn(function() {
   1.228 +      if (!this._initialized) {
   1.229 +        let delayedStartupDeferred = Promise.defer();
   1.230 +        let delayedStartupObserver = (aSubject, aTopic, aData) => {
   1.231 +          if (aSubject == window) {
   1.232 +            Services.obs.removeObserver(delayedStartupObserver, "browser-delayed-startup-finished");
   1.233 +            delayedStartupDeferred.resolve();
   1.234 +          }
   1.235 +        };
   1.236 +        Services.obs.addObserver(delayedStartupObserver, "browser-delayed-startup-finished", false);
   1.237 +        yield delayedStartupDeferred.promise;
   1.238 +      }
   1.239 +
   1.240 +      this.contents.setAttributeNS("http://www.w3.org/XML/1998/namespace", "lang",
   1.241 +                                   getLocale());
   1.242 +      if (!this._scrollWidth) {
   1.243 +        // In order to properly center the contents of the panel, while ensuring
   1.244 +        // that we have enough space on either side to show a scrollbar, we have to
   1.245 +        // do a bit of hackery. In particular, we calculate a new width for the
   1.246 +        // scroller, based on the system scrollbar width.
   1.247 +        this._scrollWidth =
   1.248 +          (yield ScrollbarSampler.getSystemScrollbarWidth()) + "px";
   1.249 +        let cstyle = window.getComputedStyle(this.scroller);
   1.250 +        let widthStr = cstyle.width;
   1.251 +        // Get the calculated padding on the left and right sides of
   1.252 +        // the scroller too. We'll use that in our final calculation so
   1.253 +        // that if a scrollbar appears, we don't have the contents right
   1.254 +        // up against the edge of the scroller.
   1.255 +        let paddingLeft = cstyle.paddingLeft;
   1.256 +        let paddingRight = cstyle.paddingRight;
   1.257 +        let calcStr = [widthStr, this._scrollWidth,
   1.258 +                       paddingLeft, paddingRight].join(" + ");
   1.259 +        this.scroller.style.width = "calc(" + calcStr + ")";
   1.260 +      }
   1.261 +
   1.262 +      if (aCustomizing) {
   1.263 +        CustomizableUI.registerMenuPanel(this.contents);
   1.264 +      } else {
   1.265 +        this.beginBatchUpdate();
   1.266 +        try {
   1.267 +          CustomizableUI.registerMenuPanel(this.contents);
   1.268 +        } finally {
   1.269 +          this.endBatchUpdate();
   1.270 +        }
   1.271 +      }
   1.272 +      this._updateQuitTooltip();
   1.273 +      this.panel.hidden = false;
   1.274 +      this._isReady = true;
   1.275 +    }.bind(this)).then(null, Cu.reportError);
   1.276 +
   1.277 +    return this._readyPromise;
   1.278 +  },
   1.279 +
   1.280 +  /**
   1.281 +   * Switch the panel to the main view if it's not already
   1.282 +   * in that view.
   1.283 +   */
   1.284 +  showMainView: function() {
   1.285 +    this._ensureEventListenersAdded();
   1.286 +    this.multiView.showMainView();
   1.287 +  },
   1.288 +
   1.289 +  /**
   1.290 +   * Switch the panel to the help view if it's not already
   1.291 +   * in that view.
   1.292 +   */
   1.293 +  showHelpView: function(aAnchor) {
   1.294 +    this._ensureEventListenersAdded();
   1.295 +    this.multiView.showSubView("PanelUI-helpView", aAnchor);
   1.296 +  },
   1.297 +
   1.298 +  /**
   1.299 +   * Shows a subview in the panel with a given ID.
   1.300 +   *
   1.301 +   * @param aViewId the ID of the subview to show.
   1.302 +   * @param aAnchor the element that spawned the subview.
   1.303 +   * @param aPlacementArea the CustomizableUI area that aAnchor is in.
   1.304 +   */
   1.305 +  showSubView: function(aViewId, aAnchor, aPlacementArea) {
   1.306 +    this._ensureEventListenersAdded();
   1.307 +    let viewNode = document.getElementById(aViewId);
   1.308 +    if (!viewNode) {
   1.309 +      Cu.reportError("Could not show panel subview with id: " + aViewId);
   1.310 +      return;
   1.311 +    }
   1.312 +
   1.313 +    if (!aAnchor) {
   1.314 +      Cu.reportError("Expected an anchor when opening subview with id: " + aViewId);
   1.315 +      return;
   1.316 +    }
   1.317 +
   1.318 +    if (aPlacementArea == CustomizableUI.AREA_PANEL) {
   1.319 +      this.multiView.showSubView(aViewId, aAnchor);
   1.320 +    } else if (!aAnchor.open) {
   1.321 +      aAnchor.open = true;
   1.322 +      // Emit the ViewShowing event so that the widget definition has a chance
   1.323 +      // to lazily populate the subview with things.
   1.324 +      let evt = document.createEvent("CustomEvent");
   1.325 +      evt.initCustomEvent("ViewShowing", true, true, viewNode);
   1.326 +      viewNode.dispatchEvent(evt);
   1.327 +      if (evt.defaultPrevented) {
   1.328 +        return;
   1.329 +      }
   1.330 +
   1.331 +      let tempPanel = document.createElement("panel");
   1.332 +      tempPanel.setAttribute("type", "arrow");
   1.333 +      tempPanel.setAttribute("id", "customizationui-widget-panel");
   1.334 +      tempPanel.setAttribute("class", "cui-widget-panel");
   1.335 +      tempPanel.setAttribute("context", "");
   1.336 +      document.getElementById(CustomizableUI.AREA_NAVBAR).appendChild(tempPanel);
   1.337 +      // If the view has a footer, set a convenience class on the panel.
   1.338 +      tempPanel.classList.toggle("cui-widget-panelWithFooter",
   1.339 +                                 viewNode.querySelector(".panel-subview-footer"));
   1.340 +
   1.341 +      let multiView = document.createElement("panelmultiview");
   1.342 +      multiView.setAttribute("nosubviews", "true");
   1.343 +      tempPanel.appendChild(multiView);
   1.344 +      multiView.setAttribute("mainViewIsSubView", "true");
   1.345 +      multiView.setMainView(viewNode);
   1.346 +      viewNode.classList.add("cui-widget-panelview");
   1.347 +      CustomizableUI.addPanelCloseListeners(tempPanel);
   1.348 +
   1.349 +      let panelRemover = function() {
   1.350 +        tempPanel.removeEventListener("popuphidden", panelRemover);
   1.351 +        viewNode.classList.remove("cui-widget-panelview");
   1.352 +        CustomizableUI.removePanelCloseListeners(tempPanel);
   1.353 +        let evt = new CustomEvent("ViewHiding", {detail: viewNode});
   1.354 +        viewNode.dispatchEvent(evt);
   1.355 +        aAnchor.open = false;
   1.356 +
   1.357 +        this.multiView.appendChild(viewNode);
   1.358 +        tempPanel.parentElement.removeChild(tempPanel);
   1.359 +      }.bind(this);
   1.360 +      tempPanel.addEventListener("popuphidden", panelRemover);
   1.361 +
   1.362 +      let iconAnchor =
   1.363 +        document.getAnonymousElementByAttribute(aAnchor, "class",
   1.364 +                                                "toolbarbutton-icon");
   1.365 +
   1.366 +      tempPanel.openPopup(iconAnchor || aAnchor, "bottomcenter topright");
   1.367 +    }
   1.368 +  },
   1.369 +
   1.370 +  /**
   1.371 +   * Open a dialog window that allow the user to customize listed character sets.
   1.372 +   */
   1.373 +  onCharsetCustomizeCommand: function() {
   1.374 +    this.hide();
   1.375 +    window.openDialog("chrome://global/content/customizeCharset.xul",
   1.376 +                      "PrefWindow",
   1.377 +                      "chrome,modal=yes,resizable=yes",
   1.378 +                      "browser");
   1.379 +  },
   1.380 +
   1.381 +  onWidgetAfterDOMChange: function(aNode, aNextNode, aContainer, aWasRemoval) {
   1.382 +    if (aContainer != this.contents) {
   1.383 +      return;
   1.384 +    }
   1.385 +    if (aWasRemoval) {
   1.386 +      aNode.removeAttribute("auto-hyphens");
   1.387 +    }
   1.388 +  },
   1.389 +
   1.390 +  onWidgetBeforeDOMChange: function(aNode, aNextNode, aContainer, aIsRemoval) {
   1.391 +    if (aContainer != this.contents) {
   1.392 +      return;
   1.393 +    }
   1.394 +    if (!aIsRemoval &&
   1.395 +        (this.panel.state == "open" ||
   1.396 +         document.documentElement.hasAttribute("customizing"))) {
   1.397 +      this._adjustLabelsForAutoHyphens(aNode);
   1.398 +    }
   1.399 +  },
   1.400 +
   1.401 +  /** 
   1.402 +   * Signal that we're about to make a lot of changes to the contents of the
   1.403 +   * panels all at once. For performance, we ignore the mutations.
   1.404 +   */
   1.405 +  beginBatchUpdate: function() {
   1.406 +    this._ensureEventListenersAdded();
   1.407 +    this.multiView.ignoreMutations = true;
   1.408 +  },
   1.409 +
   1.410 +  /**
   1.411 +   * Signal that we're done making bulk changes to the panel. We now pay
   1.412 +   * attention to mutations. This automatically synchronizes the multiview
   1.413 +   * container with whichever view is displayed if the panel is open.
   1.414 +   */
   1.415 +  endBatchUpdate: function(aReason) {
   1.416 +    this._ensureEventListenersAdded();
   1.417 +    this.multiView.ignoreMutations = false;
   1.418 +  },
   1.419 +
   1.420 +  _adjustLabelsForAutoHyphens: function(aNode) {
   1.421 +    let toolbarButtons = aNode ? [aNode] :
   1.422 +                                 this.contents.querySelectorAll(".toolbarbutton-1");
   1.423 +    for (let node of toolbarButtons) {
   1.424 +      let label = node.getAttribute("label");
   1.425 +      if (!label) {
   1.426 +        continue;
   1.427 +      }
   1.428 +      if (label.contains("\u00ad")) {
   1.429 +        node.setAttribute("auto-hyphens", "off");
   1.430 +      } else {
   1.431 +        node.removeAttribute("auto-hyphens");
   1.432 +      }
   1.433 +    }
   1.434 +  },
   1.435 +
   1.436 +  /**
   1.437 +   * Sets the anchor node into the open or closed state, depending
   1.438 +   * on the state of the panel.
   1.439 +   */
   1.440 +  _updatePanelButton: function() {
   1.441 +    this.menuButton.open = this.panel.state == "open" ||
   1.442 +                           this.panel.state == "showing";
   1.443 +  },
   1.444 +
   1.445 +  _onHelpViewShow: function(aEvent) {
   1.446 +    // Call global menu setup function
   1.447 +    buildHelpMenu();
   1.448 +
   1.449 +    let helpMenu = document.getElementById("menu_HelpPopup");
   1.450 +    let items = this.getElementsByTagName("vbox")[0];
   1.451 +    let attrs = ["oncommand", "onclick", "label", "key", "disabled"];
   1.452 +    let NSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
   1.453 +
   1.454 +    // Remove all buttons from the view
   1.455 +    while (items.firstChild) {
   1.456 +      items.removeChild(items.firstChild);
   1.457 +    }
   1.458 +
   1.459 +    // Add the current set of menuitems of the Help menu to this view
   1.460 +    let menuItems = Array.prototype.slice.call(helpMenu.getElementsByTagName("menuitem"));
   1.461 +    let fragment = document.createDocumentFragment();
   1.462 +    for (let node of menuItems) {
   1.463 +      if (node.hidden)
   1.464 +        continue;
   1.465 +      let button = document.createElementNS(NSXUL, "toolbarbutton");
   1.466 +      // Copy specific attributes from a menuitem of the Help menu
   1.467 +      for (let attrName of attrs) {
   1.468 +        if (!node.hasAttribute(attrName))
   1.469 +          continue;
   1.470 +        button.setAttribute(attrName, node.getAttribute(attrName));
   1.471 +      }
   1.472 +      button.setAttribute("class", "subviewbutton");
   1.473 +      fragment.appendChild(button);
   1.474 +    }
   1.475 +    items.appendChild(fragment);
   1.476 +  },
   1.477 +
   1.478 +  _updateQuitTooltip: function() {
   1.479 +#ifndef XP_WIN
   1.480 +#ifdef XP_MACOSX
   1.481 +    let tooltipId = "quit-button.tooltiptext.mac";
   1.482 +#else
   1.483 +    let tooltipId = "quit-button.tooltiptext.linux2";
   1.484 +#endif
   1.485 +    let brands = Services.strings.createBundle("chrome://branding/locale/brand.properties");
   1.486 +    let stringArgs = [brands.GetStringFromName("brandShortName")];
   1.487 +
   1.488 +    let key = document.getElementById("key_quitApplication");
   1.489 +    stringArgs.push(ShortcutUtils.prettifyShortcut(key));
   1.490 +    let tooltipString = CustomizableUI.getLocalizedProperty({x: tooltipId}, "x", stringArgs);
   1.491 +    let quitButton = document.getElementById("PanelUI-quit");
   1.492 +    quitButton.setAttribute("tooltiptext", tooltipString);
   1.493 +#endif
   1.494 +  },
   1.495 +
   1.496 +  _overlayScrollListenerBoundFn: null,
   1.497 +  _overlayScrollListener: function(aMQL) {
   1.498 +    ScrollbarSampler.resetSystemScrollbarWidth();
   1.499 +    this._scrollWidth = null;
   1.500 +  },
   1.501 +};
   1.502 +
   1.503 +/**
   1.504 + * Gets the currently selected locale for display.
   1.505 + * @return  the selected locale or "en-US" if none is selected
   1.506 + */
   1.507 +function getLocale() {
   1.508 +  const PREF_SELECTED_LOCALE = "general.useragent.locale";
   1.509 +  try {
   1.510 +    let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE,
   1.511 +                                                Ci.nsIPrefLocalizedString);
   1.512 +    if (locale)
   1.513 +      return locale;
   1.514 +  }
   1.515 +  catch (e) { }
   1.516 +
   1.517 +  try {
   1.518 +    return Services.prefs.getCharPref(PREF_SELECTED_LOCALE);
   1.519 +  }
   1.520 +  catch (e) { }
   1.521 +
   1.522 +  return "en-US";
   1.523 +}
   1.524 +

mercurial