// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

[
  ["AllPagesList", "popup_autocomplete", "cmd_openLocation"],
  ["HistoryList", "history-items", "cmd_history"],
  ["BookmarkList", "bookmarks-items", "cmd_bookmarks"],
#ifdef MOZ_SERVICES_SYNC
  ["RemoteTabsList", "remotetabs-items", "cmd_remoteTabs"]
#endif
].forEach(function(aPanel) {
  let [name, id, command] = aPanel;
  XPCOMUtils.defineLazyGetter(window, name, function() {
    return new AwesomePanel(id, command);
  });
});

/**
 * Cache of commonly used elements.
 */
let Elements = {};

[
  ["contentShowing",     "bcast_contentShowing"],
  ["urlbarState",        "bcast_urlbarState"],
  ["mainKeyset",         "mainKeyset"],
  ["stack",              "stack"],
  ["tabList",            "tabs"],
  ["tabs",               "tabs-container"],
  ["controls",           "browser-controls"],
  ["panelUI",            "panel-container"],
  ["toolbarContainer",   "toolbar-container"],
  ["browsers",           "browsers"],
  ["contentViewport",    "content-viewport"],
  ["contentNavigator",   "content-navigator"]
].forEach(function (aElementGlobal) {
  let [name, id] = aElementGlobal;
  XPCOMUtils.defineLazyGetter(Elements, name, function() {
    return document.getElementById(id);
  });
});

/**
 * Cache of commonly used string bundles.
 */
var Strings = {};
[
  ["browser",    "chrome://browser/locale/browser.properties"],
  ["brand",      "chrome://branding/locale/brand.properties"]
].forEach(function (aStringBundle) {
  let [name, bundle] = aStringBundle;
  XPCOMUtils.defineLazyGetter(Strings, name, function() {
    return Services.strings.createBundle(bundle);
  });
});

const TOOLBARSTATE_LOADING  = 1;
const TOOLBARSTATE_LOADED   = 2;

var BrowserUI = {
  _edit: null,
  _title: null,
  _throbber: null,
  _favicon: null,
  _dialogs: [],

  _domWillOpenModalDialog: function(aBrowser) {
    // We're about to open a modal dialog, make sure the opening
    // tab is brought to the front.

    for (let i = 0; i < Browser.tabs.length; i++) {
      if (Browser._tabs[i].browser == aBrowser) {
        Browser.selectedTab = Browser.tabs[i];
        break;
      }
    }
  },

  _titleChanged: function(aBrowser) {
    let url = this.getDisplayURI(aBrowser);
    let contentTitle = aBrowser.contentTitle;
    let caption = contentTitle || url;
    let tabCaption = contentTitle || (Util.isURLEmpty(url) ? "" : url);

    if (contentTitle == "" && !Util.isURLEmpty(aBrowser.userTypedValue))
      caption = tabCaption = aBrowser.userTypedValue;
    else if (Util.isURLEmpty(url))
      caption = "";

    let tab = Browser.getTabForBrowser(aBrowser);
    if (tab)
      tab.chromeTab.updateTitle(tabCaption);

    let browser = Browser.selectedBrowser;
    if (browser && aBrowser != browser)
      return;

    if (caption) {
      this._title.value = caption;
      this._title.classList.remove("placeholder");
    } else {
      this._title.value = this._title.getAttribute("placeholder");
      this._title.classList.add("placeholder");
    }
  },

  /*
   * Dispatched by window.close() to allow us to turn window closes into tabs
   * closes.
   */
  _domWindowClose: function(aBrowser) {
     // Find the relevant tab, and close it.
     let browsers = Browser.browsers;
     for (let i = 0; i < browsers.length; i++) {
      if (browsers[i] == aBrowser) {
        Browser.closeTab(Browser.getTabAtIndex(i));
        return { preventDefault: true };
      }
    }
  },

  _updateButtons: function(aBrowser) {
    let back = document.getElementById("cmd_back");
    let forward = document.getElementById("cmd_forward");

    back.setAttribute("disabled", !aBrowser.canGoBack);
    forward.setAttribute("disabled", !aBrowser.canGoForward);
  },

  _updateToolbar: function _updateToolbar() {
    let mode = Elements.urlbarState.getAttribute("mode");
    if (mode == "edit" && AwesomeScreen.activePanel)
      return;

    if (Browser.selectedTab.isLoading() && mode != "loading")
      Elements.urlbarState.setAttribute("mode", "loading");
    else if (mode != "view")
      Elements.urlbarState.setAttribute("mode", "view");
  },

  _tabSelect: function(aEvent) {
    let browser = Browser.selectedBrowser;
    this._titleChanged(browser);
    this._updateToolbar();
    this._updateButtons(browser);
    this._updateIcon(browser.mIconURL);
    this.updateStar();
  },

  _toolbarLocked: 0,

  isToolbarLocked: function isToolbarLocked() {
    return this._toolbarLocked;
  },

  lockToolbar: function lockToolbar() {
    if (Util.isTablet())
      return;
    this._toolbarLocked++;
    document.getElementById("toolbar-moveable-container").top = "0";
    if (this._toolbarLocked == 1)
      Browser.forceChromeReflow();
  },

  unlockToolbar: function unlockToolbar() {
    if (!this._toolbarLocked)
      return;

    this._toolbarLocked--;
    if (!this._toolbarLocked)
      document.getElementById("toolbar-moveable-container").top = "";
  },

  _setURL: function _setURL(aURL) {
    if (AwesomeScreen.activePanel)
      this._edit.defaultValue = aURL;
    else
      this._edit.value = aURL;
  },

  _editURI: function _editURI(aEdit) {
    Elements.urlbarState.setAttribute("mode", "edit");
    this._edit.defaultValue = this._edit.value;
  },

  _showURI: function _showURI() {
    // Replace the web page title by the url of the page
    let urlString = this.getDisplayURI(Browser.selectedBrowser);
    if (Util.isURLEmpty(urlString))
      urlString = "";

    this._edit.value = urlString;
  },

  updateAwesomeHeader: function updateAwesomeHeader(aString) {
    document.getElementById("awesome-header").hidden = (aString != "");

    // During an awesome search we always show the popup_autocomplete/AllPagesList
    // panel since this looks in every places and the rationale behind typing
    // is to find something, whereever it is.
    if (AwesomeScreen.activePanel != AllPagesList) {
      let inputField = this._edit;
      let oldClickSelectsAll = inputField.clickSelectsAll;
      inputField.clickSelectsAll = false;

      AwesomeScreen.activePanel = AllPagesList;

      // changing the searchString property call updateAwesomeHeader again
      inputField.controller.searchString = aString;
      inputField.readOnly = false;
      inputField.clickSelectsAll = oldClickSelectsAll;
      return;
    }

    let event = document.createEvent("Events");
    event.initEvent("onsearchbegin", true, true);
    this._edit.dispatchEvent(event);
  },

  _closeOrQuit: function _closeOrQuit() {
    // Close active dialog, if we have one. If not then close the application.
    if (AwesomeScreen.activePanel) {
      AwesomeScreen.activePanel = null;
    } else if (this.activeDialog) {
      this.activeDialog.close();
    } else {
      // Check to see if we should really close the window
      if (Browser.closing())
        window.close();
    }
  },

  get activeDialog() {
    // Return the topmost dialog
    if (this._dialogs.length)
      return this._dialogs[this._dialogs.length - 1];
    return null;
  },

  pushDialog: function pushDialog(aDialog) {
    // If we have a dialog push it on the stack and set the attr for CSS
    if (aDialog) {
      this.lockToolbar();
      this._dialogs.push(aDialog);
      document.getElementById("toolbar-main").setAttribute("dialog", "true");
      Elements.contentShowing.setAttribute("disabled", "true");
    }
  },

  popDialog: function popDialog() {
    if (this._dialogs.length) {
      this._dialogs.pop();
      this.unlockToolbar();
    }

    // If no more dialogs are being displayed, remove the attr for CSS
    if (!this._dialogs.length) {
      document.getElementById("toolbar-main").removeAttribute("dialog");
      Elements.contentShowing.removeAttribute("disabled");
    }
  },

  pushPopup: function pushPopup(aPanel, aElements) {
    this._hidePopup();
    this._popup =  { "panel": aPanel,
                     "elements": (aElements instanceof Array) ? aElements : [aElements] };
    this._dispatchPopupChanged(true);
  },

  popPopup: function popPopup(aPanel) {
    if (!this._popup || aPanel != this._popup.panel)
      return;
    this._popup = null;
    this._dispatchPopupChanged(false);
  },

  // Will the on-screen keyboard cover the whole screen when opened?
  _isKeyboardFullscreen: function _isKeyboardFullscreen() {
#ifdef ANDROID
    if (!Util.isPortrait()) {
      switch (Services.prefs.getIntPref("widget.ime.android.landscape_fullscreen")) {
        case 1:
          return true;
        case -1: {
          let threshold = Services.prefs.getIntPref("widget.ime.android.fullscreen_threshold");
          let dpi = Util.displayDPI;
          return (window.innerHeight * 100 < threshold * dpi);
        }
      }
    }
#endif
    return false;
  },

  _dispatchPopupChanged: function _dispatchPopupChanged(aVisible) {
    let stack = document.getElementById("stack");
    let event = document.createEvent("UIEvents");
    event.initUIEvent("PopupChanged", true, true, window, aVisible);
    event.popup = this._popup;
    stack.dispatchEvent(event);
  },

  _hidePopup: function _hidePopup() {
    if (!this._popup)
      return;
    let panel = this._popup.panel;
    if (panel.hide)
      panel.hide();
  },

  _isEventInsidePopup: function _isEventInsidePopup(aEvent) {
    if (!this._popup)
      return false;
    let elements = this._popup.elements;
    let targetNode = aEvent ? aEvent.target : null;
    while (targetNode && elements.indexOf(targetNode) == -1)
      targetNode = targetNode.parentNode;
    return targetNode ? true : false;
  },

  switchPane: function switchPane(aPanelId) {
    this.blurFocusedElement();

    let panels = document.getElementById("panel-items")
    let panel = aPanelId ? document.getElementById(aPanelId) : panels.selectedPanel;
    let oldPanel = panels.selectedPanel;

    if (oldPanel != panel) {
      panels.selectedPanel = panel;
      let button = document.getElementsByAttribute("linkedpanel", aPanelId)[0];
      if (button)
        button.checked = true;

      let event = document.createEvent("Events");
      event.initEvent("ToolPanelHidden", true, true);
      oldPanel.dispatchEvent(event);
    }

    let event = document.createEvent("Events");
    event.initEvent("ToolPanelShown", true, true);
    panel.dispatchEvent(event);
  },

  get toolbarH() {
    if (!this._toolbarH) {
      let toolbar = document.getElementById("toolbar-main");
      this._toolbarH = toolbar.boxObject.height;
    }
    return this._toolbarH;
  },

  sizeControls: function(windowW, windowH) {
    // tabs
    Elements.tabList.resize();
    AwesomeScreen.doResize(windowW, windowH);

    // content navigator helper
    document.getElementById("content-navigator").contentHasChanged();
  },

  init: function() {
    this._edit = document.getElementById("urlbar-edit");
    this._title = document.getElementById("urlbar-title");
    this._throbber = document.getElementById("urlbar-throbber");
    this._favicon = document.getElementById("urlbar-favicon");
    this._favicon.addEventListener("error", this, false);

    this._edit.addEventListener("click", this, false);
    this._edit.addEventListener("mousedown", this, false);

    window.addEventListener("NavigationPanelShown", this, false);
    window.addEventListener("NavigationPanelHidden", this, false);

    Elements.browsers.addEventListener("PanFinished", this, true);
#if MOZ_PLATFORM_MAEMO == 6
    Elements.browsers.addEventListener("SizeChanged", this, true);
#endif

    // listen content messages
    messageManager.addMessageListener("DOMLinkAdded", this);
    messageManager.addMessageListener("DOMTitleChanged", this);
    messageManager.addMessageListener("DOMWillOpenModalDialog", this);
    messageManager.addMessageListener("DOMWindowClose", this);

    messageManager.addMessageListener("Browser:OpenURI", this);
    messageManager.addMessageListener("Browser:SaveAs:Return", this);

    // listening mousedown for automatically dismiss some popups (e.g. larry)
    window.addEventListener("mousedown", this, true);

    // listening escape to dismiss dialog on VK_ESCAPE
    window.addEventListener("keypress", this, true);

    // listening AppCommand to handle special keys
    window.addEventListener("AppCommand", this, true);

    // Initialize the number of tabs in toolbar
    TabsPopup.init();

    // We can delay some initialization until after startup.  We wait until
    // the first page is shown, then dispatch a UIReadyDelayed event.
    messageManager.addMessageListener("pageshow", function() {
      if (getBrowser().currentURI.spec == "about:blank")
        return;

      messageManager.removeMessageListener("pageshow", arguments.callee, true);

      setTimeout(function() {
        let event = document.createEvent("Events");
        event.initEvent("UIReadyDelayed", true, false);
        window.dispatchEvent(event);
      }, 0);
    });

    // Only load IndexedDB.js when we actually need it. A general fix will happen in bug 647079.
    messageManager.addMessageListener("IndexedDB:Prompt", function(aMessage) {
      return IndexedDB.receiveMessage(aMessage);
    });

    // Delay the panel UI and Sync initialization.
    window.addEventListener("UIReadyDelayed", function(aEvent) {
      window.removeEventListener(aEvent.type, arguments.callee, false);

      // We unhide the panelUI so the XBL and settings can initialize
      Elements.panelUI.hidden = false;

      // Login Manager and Form History initialization
      Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
      Cc["@mozilla.org/satchel/form-history;1"].getService(Ci.nsIFormHistory2);

      // Listen tabs event
      Elements.tabs.addEventListener("TabSelect", BrowserUI, true);
      Elements.tabs.addEventListener("TabOpen", BrowserUI, true);
      Elements.tabs.addEventListener("TabRemove", BrowserUI, true);

      // Init the tool panel views
      ExtensionsView.init();
      DownloadsView.init();
      ConsoleView.init();

#ifdef MOZ_SERVICES_SYNC
      // Init the sync system
      WeaveGlue.init();
#endif

      Services.prefs.addObserver("browser.ui.layout.tablet", BrowserUI, false);
      Services.obs.addObserver(BrowserSearch, "browser-search-engine-modified", false);
      messageManager.addMessageListener("Browser:MozApplicationManifest", OfflineApps);

      // Init helpers
      BadgeHandlers.register(BrowserUI._edit.popup);
      FormHelperUI.init();
      FindHelperUI.init();
      FullScreenVideo.init();
      NewTabPopup.init();
      WebappsUI.init();
      CapturePickerUI.init();

      // If some add-ons were disabled during during an application update, alert user
      let addonIDs = AddonManager.getStartupChanges("disabled");
      if (addonIDs.length > 0) {
        let disabledStrings = Strings.browser.GetStringFromName("alertAddonsDisabled");
        let label = PluralForm.get(addonIDs.length, disabledStrings).replace("#1", addonIDs.length);
        let image = "chrome://browser/skin/images/alert-addons-30.png";

        let alerts = Cc["@mozilla.org/toaster-alerts-service;1"].getService(Ci.nsIAlertsService);
        alerts.showAlertNotification(image, Strings.browser.GetStringFromName("alertAddons"), label, false, "", null);
      }

#ifdef MOZ_UPDATER
      // Check for updates in progress
      let updatePrompt = Cc["@mozilla.org/updates/update-prompt;1"].createInstance(Ci.nsIUpdatePrompt);
      updatePrompt.checkForUpdates();
#endif
    }, false);

    let panels = document.getElementById("panel-items");
    let panelViews = { // Use strings to avoid lazy-loading objects too soon.
      "prefs-container": "PreferencesView",
      "downloads-container": "DownloadsView",
      "addons-container": "ExtensionsView",
      "console-container": "ConsoleView"
    };

    // Some initialization can be delayed until a panel is selected.
    panels.addEventListener("ToolPanelShown", function(aEvent) {
      let viewName = panelViews[panels.selectedPanel.id];
      if (viewName)
        window[viewName].delayedInit();
    }, true);

#ifndef MOZ_OFFICIAL_BRANDING
      setTimeout(function() {
        let startup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup).getStartupInfo();
        for (let name in startup) {
          if (name != "process")
            Services.console.logStringMessage("[timing] " + name + ": " + (startup[name] - startup.process) + "ms");
        }
      }, 3000);
#endif
  },

  uninit: function() {
    Services.obs.removeObserver(BrowserSearch, "browser-search-engine-modified");
    Services.prefs.removeObserver("browser.ui.layout.tablet", BrowserUI);
    messageManager.removeMessageListener("Browser:MozApplicationManifest", OfflineApps);
    ExtensionsView.uninit();
    ConsoleView.uninit();
  },

  observe: function observe(aSubject, aTopic, aData) {
    if (aTopic == "nsPref:changed" && aData == "browser.ui.layout.tablet")
      this.updateTabletLayout();
  },

  updateTabletLayout: function updateTabletLayout() {
    let wasTablet = Elements.urlbarState.hasAttribute("tablet");
    let isTablet = Util.isTablet({ forceUpdate: true });
    if (wasTablet == isTablet)
      return;

    if (isTablet) {
      this.unlockToolbar();
      Elements.urlbarState.setAttribute("tablet", "true");
    } else {
      Elements.urlbarState.removeAttribute("tablet");
    }

    // Tablet mode changes the size of the thumbnails
    // in the tabs container. Hence we have to force a
    // thumbnail update on all tabs.
    setTimeout(function(self) {
      self._updateAllTabThumbnails();
    }, 0, this);
  },

  _updateAllTabThumbnails: function() {
    let tabs = Browser.tabs;

    tabs.forEach(function(tab) {
      tab.updateThumbnail({ force: true });
    });
  },

  update: function(aState) {
    let browser = Browser.selectedBrowser;

    switch (aState) {
      case TOOLBARSTATE_LOADED:
        this._updateToolbar();

        this._updateIcon(browser.mIconURL);
        this.unlockToolbar();
        break;

      case TOOLBARSTATE_LOADING:
        this._updateToolbar();

        browser.mIconURL = "";
        this._updateIcon();
        this.lockToolbar();
        break;
    }
  },

  _updateIcon: function(aIconSrc) {
    this._favicon.src = aIconSrc || "";
    if (Browser.selectedTab.isLoading()) {
      this._throbber.hidden = false;
      this._throbber.setAttribute("loading", "true");
      this._favicon.hidden = true;
    }
    else {
      this._favicon.hidden = false;
      this._throbber.hidden = true;
      this._throbber.removeAttribute("loading");
    }
  },

  getDisplayURI: function(browser) {
    let uri = browser.currentURI;
    try {
      uri = gURIFixup.createExposableURI(uri);
    } catch (ex) {}

    return uri.spec;
  },

  /* Set the location to the current content */
  updateURI: function(aOptions) {
    aOptions = aOptions || {};

    let browser = Browser.selectedBrowser;
    let urlString = this.getDisplayURI(browser);
    if (Util.isURLEmpty(urlString))
      urlString = "";

    this._setURL(urlString);

    if ("captionOnly" in aOptions && aOptions.captionOnly)
      return;

    // Update the navigation buttons
    this._updateButtons(browser);

    // Check for a bookmarked page
    this.updateStar();
  },

  goToURI: function(aURI) {
    aURI = aURI || this._edit.value;
    if (!aURI)
      return;

    // Make sure we're online before attempting to load
    Util.forceOnline();

    // Close the autocomplete panel and quickly update the urlbar value
    this.closeAutoComplete();
    this._edit.value = aURI;

    let postData = {};
    aURI = Browser.getShortcutOrURI(aURI, postData);
    Browser.loadURI(aURI, { flags: Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP, postData: postData });

    // If a page goes from remote (about:blank) to local (about:home), fennec
    // create a tab for the local page and then close the about:blank tab.
    // If the newly created tab spawn a new column the sidebars position can
    // be messed up. Hopefully, the viewable area should be maximized when a
    // new page is opened, so a call to Browser.hideSidebars() fill this
    // requirement and fix the sidebars position.
    Browser.hideSidebars();
    Elements.tabList.removeClosedTab();

    // Delay doing the fixup so the raw URI is passed to loadURIWithFlags
    // and the proper third-party fixup can be done
    let fixupFlags = Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
    let uri = gURIFixup.createFixupURI(aURI, fixupFlags);
    gHistSvc.markPageAsTyped(uri);

    this._titleChanged(Browser.selectedBrowser);
  },

  showAutoComplete: function showAutoComplete() {
    if (this.isAutoCompleteOpen())
      return;

    this.hidePanel();
    this._hidePopup();
    if (this.activeDialog)
      this.activeDialog.close();
    AwesomeScreen.activePanel = AllPagesList;
  },

  closeAutoComplete: function closeAutoComplete() {
    if (this.isAutoCompleteOpen())
      this._edit.popup.closePopup();

    AwesomeScreen.activePanel = null;
  },

  isAutoCompleteOpen: function isAutoCompleteOpen() {
    return AwesomeScreen.activePanel == AllPagesList;
  },

  doOpenSearch: function doOpenSearch(aName) {
    // save the current value of the urlbar
    let searchValue = this._edit.value;

    // Give the new page lots of room
    Browser.hideSidebars();
    this.closeAutoComplete();

    // Make sure we're online before attempting to load
    Util.forceOnline();

    let engine = Services.search.getEngineByName(aName);
    let submission = engine.getSubmission(searchValue, null);
    Browser.loadURI(submission.uri.spec, { postData: submission.postData });

    // loadURI may open a new tab, so get the selectedBrowser afterward.
    Browser.selectedBrowser.userTypedValue = submission.uri.spec;
    this._titleChanged(Browser.selectedBrowser);
  },

  updateUIFocus: function _updateUIFocus() {
    if (Elements.contentShowing.getAttribute("disabled") == "true")
      Browser.selectedBrowser.messageManager.sendAsyncMessage("Browser:Blur", { });
  },

  updateStar: function() {
    let uri = getBrowser().currentURI;
    if (uri.spec == "about:blank") {
      this._setStar(false);
      return;
    }

    PlacesUtils.asyncGetBookmarkIds(uri, function(aItemIds) {
      this._setStar(aItemIds.length > 0)
    }, this);
  },

  _setStar: function _setStar(aIsStarred) {
    let buttons = document.getElementsByClassName("tool-star");
    for (let i = 0; i < buttons.length; i++) {
      if (aIsStarred)
        buttons[i].setAttribute("starred", "true");
      else
        buttons[i].removeAttribute("starred");
    }
  },

  newTab: function newTab(aURI, aOwner) {
    aURI = aURI || "about:blank";
    let tab = Browser.addTab(aURI, true, aOwner);

    this.hidePanel();

    if (aURI == "about:blank") {
      // Display awesomebar UI
      this.showAutoComplete();
    }
    else {
      // Give the new page lots of room
      Browser.hideSidebars();
      this.closeAutoComplete();
    }

    return tab;
  },

  newOrSelectTab: function newOrSelectTab(aURI, aOwner) {
    let tabs = Browser.tabs;
    for (let i = 0; i < tabs.length; i++) {
      if (tabs[i].browser.currentURI.spec == aURI) {
        Browser.selectedTab = tabs[i];
        return;
      }
    }
    this.newTab(aURI, aOwner);
  },

  closeTab: function closeTab(aTab) {
    // If no tab is passed in, assume the current tab
    Browser.closeTab(aTab || Browser.selectedTab);
  },

  selectTab: function selectTab(aTab) {
    AwesomeScreen.activePanel = null;
    Browser.selectedTab = aTab;
    Elements.tabList.removeClosedTab();
  },

  undoCloseTab: function undoCloseTab(aIndex) {
    let tab = null;
    let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
    if (ss.getClosedTabCount(window) > (aIndex || 0)) {
      let chromeTab = ss.undoCloseTab(window, aIndex || 0);
      tab = Browser.getTabFromChrome(chromeTab);
    }
    return tab;
  },

  isTabsVisible: function isTabsVisible() {
    // The _1, _2 and _3 are to make the js2 emacs mode happy
    let [leftvis,_1,_2,_3] = Browser.computeSidebarVisibility();
    return (leftvis > 0.002);
  },

  showPanel: function showPanel(aPanelId) {
    if (AwesomeScreen.activePanel)
      AwesomeScreen.activePanel = null; // Hide the awesomescreen.

    Elements.panelUI.left = 0;
    Elements.panelUI.hidden = false;
    Elements.contentShowing.setAttribute("disabled", "true");

    this.switchPane(aPanelId);
  },

  hidePanel: function hidePanel() {
    if (!this.isPanelVisible())
      return;
    Elements.panelUI.hidden = true;
    Elements.contentShowing.removeAttribute("disabled");
    this.blurFocusedElement();

    let panels = document.getElementById("panel-items")
    let event = document.createEvent("Events");
    event.initEvent("ToolPanelHidden", true, true);
    panels.selectedPanel.dispatchEvent(event);
  },

  isPanelVisible: function isPanelVisible() {
    return (!Elements.panelUI.hidden && Elements.panelUI.left == 0);
  },

  blurFocusedElement: function blurFocusedElement() {
    let focusedElement = document.commandDispatcher.focusedElement;
    if (focusedElement)
      focusedElement.blur();
  },

  switchTask: function switchTask() {
    try {
      let shell = Cc["@mozilla.org/browser/shell-service;1"].createInstance(Ci.nsIShellService);
      shell.switchTask();
    } catch(e) { }
  },

  handleEscape: function (aEvent) {
    aEvent.stopPropagation();

    // Check open popups
    if (this._popup) {
      this._hidePopup();
      return;
    }

    // Check open dialogs
    let dialog = this.activeDialog;
    if (dialog && dialog != AwesomeScreen.activePanel) {
      dialog.close();
      return;
    }

    // Check active panel
    if (AwesomeScreen.activePanel) {
      AwesomeScreen.activePanel = null;
      return;
    }

    // Check open modal elements
    let modalElementsLength = document.getElementsByClassName("modal-block").length;
    if (modalElementsLength > 0)
      return;

    // Check open panel
    if (this.isPanelVisible()) {
      this.hidePanel();
      return;
    }

    // Check content helper
    let contentHelper = document.getElementById("content-navigator");
    if (contentHelper.isActive) {
      contentHelper.model.hide();
      return;
    }

    // Only if there are no dialogs, popups, or panels open
    let tab = Browser.selectedTab;
    let browser = tab.browser;

    if (browser.canGoBack) {
      browser.goBack();
    } else if (tab.owner) {
      // When going back, always return to the owner (not a sibling).
      Browser.selectedTab = tab.owner;
      this.closeTab(tab);
    }
#ifdef ANDROID
    else {
      window.QueryInterface(Ci.nsIDOMChromeWindow).minimize();
      if (tab.closeOnExit)
        this.closeTab(tab);
    }
#endif
  },

  handleEvent: function handleEvent(aEvent) {
    switch (aEvent.type) {
      // Browser events
      case "TabSelect":
        this._tabSelect(aEvent);
        break;
      case "TabOpen":
        Elements.tabList.removeClosedTab();
        Browser.hidePartialTabSidebar();
        break;
      case "TabRemove":
        Browser.hidePartialTabSidebar();
        break;
      case "PanFinished": {
        let tabs = Elements.tabList;
        let [tabsVisibility,,oldLeftWidth, oldRightWidth] = Browser.computeSidebarVisibility();
        if (tabsVisibility == 0.0 && tabs.hasClosedTab) {
          let { x: x1, y: y1 } = Browser.getScrollboxPosition(Browser.controlsScrollboxScroller);
          tabs.removeClosedTab();

          // If the tabs sidebar lives on the left side of the window, width
          // variation should be taken into account to reposition the sidebars
          if (tabs.getBoundingClientRect().left < 0) {
            let [,, leftWidth, rightWidth] = Browser.computeSidebarVisibility();
            let delta = (oldLeftWidth - leftWidth) || (oldRightWidth - rightWidth);
            x1 += (x1 == leftWidth) ? delta : -delta;
          }

          Browser.controlsScrollboxScroller.scrollTo(x1, 0);
          Browser.tryFloatToolbar(0, 0);
        }
        break;
      }
      case "SizeChanged":
        this.sizeControls(ViewableAreaObserver.width, ViewableAreaObserver.height);
        break;
      // Window events
      case "keypress":
        // Ignore events re-dispatched from content; we already
        // handled them when they were originally fired.
        if (aEvent.target == Elements.mainKeyset)
          break;
        if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE)
          this.handleEscape(aEvent);
        break;
      case "AppCommand":
        aEvent.stopPropagation();
        switch (aEvent.command) {
          case "Menu":
            this.doCommand("cmd_menu");
            break;
          case "Search":
            if (!AwesomeScreen.activePanel)
              AllPagesList.doCommand();
            else
              this.doCommand("cmd_opensearch");
            break;
          default:
            break;
        }
        break;
      // URL textbox events
      case "click":
        if (this._edit.readOnly)
          this._edit.readOnly = false;
        break;
      case "mousedown":
        if (!this._isEventInsidePopup(aEvent))
          this._hidePopup();
        break;
      // Favicon events
      case "error":
        this._favicon.src = "";
        break;
      // Awesome popup event
      case "NavigationPanelShown":
        this._edit.collapsed = false;
        this._title.collapsed = true;

        if (!this._edit.readOnly)
          this._edit.focus();

        // Disabled the search button if no search engines are available
        let button = document.getElementById("urlbar-icons");
        if (BrowserSearch.engines.length)
          button.removeAttribute("disabled");
        else
          button.setAttribute("disabled", "true");

        break;
      case "NavigationPanelHidden": {
        this._edit.collapsed = true;
        this._title.collapsed = false;
        this._updateToolbar();

        let button = document.getElementById("urlbar-icons");
        button.removeAttribute("open");
        button.removeAttribute("disabled");
        break;
      }
    }
  },

  receiveMessage: function receiveMessage(aMessage) {
    let browser = aMessage.target;
    let json = aMessage.json;
    switch (aMessage.name) {
      case "DOMTitleChanged":
        this._titleChanged(browser);
        break;
      case "DOMWillOpenModalDialog":
        return this._domWillOpenModalDialog(browser);
        break;
      case "DOMWindowClose":
        return this._domWindowClose(browser);
        break;
      case "DOMLinkAdded":
        // checks for an icon to use for a web app
        // apple-touch-icon size is 57px and default size is 16px
        let rel = json.rel.toLowerCase().split(" ");
        if (rel.indexOf("icon") != -1) {
          // We use the sizes attribute if available
          // see http://www.whatwg.org/specs/web-apps/current-work/multipage/links.html#rel-icon
          let size = 16;
          if (json.sizes) {
            let sizes = json.sizes.toLowerCase().split(" ");
            sizes.forEach(function(item) {
              if (item != "any") {
                let [w, h] = item.split("x");
                size = Math.max(Math.min(w, h), size);
              }
            });
          }
          if (size > browser.appIcon.size) {
            browser.appIcon.href = json.href;
            browser.appIcon.size = size;
          }
        }
        else if ((rel.indexOf("apple-touch-icon") != -1) && (browser.appIcon.size < 57)) {
          // XXX should we support apple-touch-icon-precomposed ?
          // see http://developer.apple.com/safari/library/documentation/appleapplications/reference/safariwebcontent/configuringwebapplications/configuringwebapplications.html
          browser.appIcon.href = json.href;
          browser.appIcon.size = 57;
        }

        // Handle favicon changes
        if (Browser.selectedBrowser == browser)
          this._updateIcon(Browser.selectedBrowser.mIconURL);
        break;
      case "Browser:SaveAs:Return":
        if (json.type != Ci.nsIPrintSettings.kOutputFormatPDF)
          return;

        let dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
        let db = dm.DBConnection;
        let stmt = db.createStatement("UPDATE moz_downloads SET endTime = :endTime, state = :state WHERE id = :id");
        stmt.params.endTime = Date.now() * 1000;
        stmt.params.state = Ci.nsIDownloadManager.DOWNLOAD_FINISHED;
        stmt.params.id = json.id;
        stmt.execute();
        stmt.finalize();

        let download = dm.getDownload(json.id);
#ifdef ANDROID
        // since our content process doesn't have write permissions to the
        // downloads dir, we save it to the tmp dir and then move it here
        let dlFile = download.targetFile;
        if (!dlFile.exists())
          dlFile.create(file.NORMAL_FILE_TYPE, 0666);
        let tmpDir = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
        let tmpFile = tmpDir.clone();
        tmpFile.append(dlFile.leafName);

        // we sometimes race with the content process, so make sure its finished
        // creating/writing the file
        while (!tmpFile.exists());
        tmpFile.moveTo(dlFile.parent, dlFile.leafName);
#endif
        try {
          DownloadsView.downloadCompleted(download);
          let element = DownloadsView.getElementForDownload(json.id);
          element.setAttribute("state", Ci.nsIDownloadManager.DOWNLOAD_FINISHED);
          element.setAttribute("endTime", Date.now());
          element.setAttribute("referrer", json.referrer);
          DownloadsView._updateTime(element);
          DownloadsView._updateStatus(element);
        }
        catch(e) {}
        Services.obs.notifyObservers(download, "dl-done", null);
        break;

      case "Browser:OpenURI":
        let referrerURI = null;
        if (json.referrer)
          referrerURI = Services.io.newURI(json.referrer, null, null);
        Browser.addTab(json.uri, json.bringFront, Browser.selectedTab, { referrerURI: referrerURI });
        break;
    }
  },

  supportsCommand : function(cmd) {
    var isSupported = false;
    switch (cmd) {
      case "cmd_back":
      case "cmd_forward":
      case "cmd_reload":
      case "cmd_forceReload":
      case "cmd_stop":
      case "cmd_go":
      case "cmd_openLocation":
      case "cmd_star":
      case "cmd_opensearch":
      case "cmd_bookmarks":
      case "cmd_history":
      case "cmd_remoteTabs":
      case "cmd_quit":
      case "cmd_close":
      case "cmd_menu":
      case "cmd_showTabs":
      case "cmd_newTab":
      case "cmd_closeTab":
      case "cmd_undoCloseTab":
      case "cmd_actions":
      case "cmd_panel":
      case "cmd_sanitize":
      case "cmd_zoomin":
      case "cmd_zoomout":
      case "cmd_volumeLeft":
      case "cmd_volumeRight":
      case "cmd_lockscreen":
        isSupported = true;
        break;
      default:
        isSupported = false;
        break;
    }
    return isSupported;
  },

  isCommandEnabled : function(cmd) {
    // disable all commands during the first-run sidebar discovery
    let broadcaster = document.getElementById("bcast_uidiscovery");
    if (broadcaster && broadcaster.getAttribute("mode") == "discovery")
      return false;

    let elem = document.getElementById(cmd);
    if (elem && elem.getAttribute("disabled") == "true")
      return false;
    return true;
  },

  doCommand : function(cmd) {
    if (!this.isCommandEnabled(cmd))
      return;
    let browser = getBrowser();
    switch (cmd) {
      case "cmd_back":
        browser.goBack();
        break;
      case "cmd_forward":
        browser.goForward();
        break;
      case "cmd_reload":
        browser.reload();
        break;
      case "cmd_forceReload":
      {
        // Simulate a new page
        browser.lastLocation = null;

        const reloadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
                            Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
        browser.reloadWithFlags(reloadFlags);
        break;
      }
      case "cmd_stop":
        browser.stop();
        break;
      case "cmd_go":
        this.goToURI();
        break;
      case "cmd_openLocation":
        this.showAutoComplete();
        break;
      case "cmd_star":
      {
        BookmarkPopup.toggle();
        this._setStar(true);

        let bookmarkURI = browser.currentURI;
        PlacesUtils.asyncGetBookmarkIds(bookmarkURI, function (aItemIds) {
          if (!aItemIds.length) {
            let bookmarkTitle = browser.contentTitle || bookmarkURI.spec;
            try {
              let bookmarkService = PlacesUtils.bookmarks;
              let bookmarkId = bookmarkService.insertBookmark(BookmarkList.panel.mobileRoot, bookmarkURI,
                                                              bookmarkService.DEFAULT_INDEX,
                                                              bookmarkTitle);
            } catch (e) {
              // Insert failed; reset the star state.
              this.updateStar();
            }

            // XXX Used for browser-chrome tests
            let event = document.createEvent("Events");
            event.initEvent("BookmarkCreated", true, false);
            window.dispatchEvent(event);
          }
        }, this);
        break;
      }
      case "cmd_opensearch":
        this.blurFocusedElement();
        BrowserSearch.toggle();
        break;
      case "cmd_bookmarks":
        AwesomeScreen.activePanel = BookmarkList;
        break;
      case "cmd_history":
        AwesomeScreen.activePanel = HistoryList;
        break;
      case "cmd_remoteTabs":
        if (Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED) {
          // We have to set activePanel before showing sync's dialog
          // to make the sure the dialog stacking is correct.
          AwesomeScreen.activePanel = RemoteTabsList;
          WeaveGlue.open();
        } else {
          AwesomeScreen.activePanel = RemoteTabsList;
        }

        break;
      case "cmd_quit":
        // Only close one window
        this._closeOrQuit();
        break;
      case "cmd_close":
        this._closeOrQuit();
        break;
      case "cmd_menu":
        AppMenu.toggle();
        break;
      case "cmd_showTabs":
        if (Util.isPortrait())
          TabsPopup.toggle();
        else
          TabletSidebar.toggle();
        break;
      case "cmd_newTab":
        this.newTab();
        break;
      case "cmd_closeTab":
        this.closeTab();
        break;
      case "cmd_undoCloseTab":
        this.undoCloseTab();
        break;
      case "cmd_sanitize":
      {
        let title = Strings.browser.GetStringFromName("clearPrivateData.title");
        let message = Strings.browser.GetStringFromName("clearPrivateData.message");
        let clear = Services.prompt.confirm(window, title, message);
        if (clear) {
          // disable the button temporarily to indicate something happened
          let button = document.getElementById("prefs-clear-data");
          button.disabled = true;
          setTimeout(function() { button.disabled = false; }, 5000);

          Sanitizer.sanitize();
        }
        break;
      }
      case "cmd_panel":
        if (this.isPanelVisible())
          this.hidePanel();
        else
          this.showPanel();
        break;
      case "cmd_zoomin":
        Browser.zoom(-1);
        break;
      case "cmd_zoomout":
        Browser.zoom(1);
        break;
      case "cmd_volumeLeft":
        // Zoom in (portrait) or out (landscape)
        Browser.zoom(Util.isPortrait() ? -1 : 1);
        break;
      case "cmd_volumeRight":
        // Zoom out (portrait) or in (landscape)
        Browser.zoom(Util.isPortrait() ? 1 : -1);
        break;
      case "cmd_lockscreen":
      {
        let locked = Services.prefs.getBoolPref("toolkit.screen.lock");
        Services.prefs.setBoolPref("toolkit.screen.lock", !locked);

        let strings = Strings.browser;
        let alerts = Cc["@mozilla.org/toaster-alerts-service;1"].getService(Ci.nsIAlertsService);
        alerts.showAlertNotification(null, strings.GetStringFromName("alertLockScreen"),
                                     strings.GetStringFromName("alertLockScreen." + (!locked ? "locked" : "unlocked")), false, "", null);
        break;
      }
    }
  }
};
