text: /* global BookmarksDatabase */
  /* global eventSafety */
  /* global IconsHelper */
  /* global LazyLoader */
  /* global ModalDialog */
  /* global MozActivity */
  /* global SettingsListener */
  /* global Service */
  /* global SystemBanner */
  
  'use strict';
  
  (function(exports) {
    const PINNING_PREF = 'dev.gaia.pinning_the_web';
    // 32px + 4px padding added by the Icon renderer
    const ICON_SIZE = 32 + 4;
    const PREVIEW_ICON_SIZE = 64;
  
    var _id = 0;
  
    var newTabManifestURL = null;
    SettingsListener.observe('rocketbar.newTabAppURL', '',
      function(url) {
        // The application list in applications.js is not yet ready, so we store
        // only the manifestURL for now and we look up the application whenever
        // we trigger a new window.
        newTabManifestURL = url ? url.match(/(^.*?:\/\/.*?\/)/)[1] +
          'manifest.webapp' : '';
      });
  
    /**
     * The chrome UI of the AppWindow.
     *
     * @class AppChrome
     * @param {AppWindow} app The app window instance this chrome belongs to.
     * @extends BaseUI
     */
    var AppChrome = function AppChrome(app) {
      this.app = app;
      this.instanceID = _id++;
      this.containerElement = app.element;
      this._themeChanged = false;
      this.scrollable = app.browserContainer;
      this._currentOrigin = app.origin || '';
      this._currentIconUrl = '';
      this.render();
  
      if (this.app.themeColor && this.app.themeColor !== '') {
        // If titlestatechanged is fired during creation, appChrome won't have
        // been set yet.
        app.appChrome = this;
  
        this._themeChanged = true;
        this.setThemeColor(this.app.themeColor);
      }
  
      this.reConfig();
    };
  
    AppChrome.prototype = Object.create(window.BaseUI.prototype);
  
    AppChrome.prototype.CLASS_NAME = 'AppChrome';
  
    AppChrome.prototype.EVENT_PREFIX = 'chrome';
  
    AppChrome.prototype._DEBUG = false;
  
    AppChrome.prototype.reConfig = function() {
      var chrome = this.app.config.chrome;
      if (!chrome) {
        return;
      }
  
      this.pinned = chrome.pinned || false;
      this.debug('AppChrome#' + this.app.instanceID + ', pinned ' + this.pinned);
  
      if (this.isSearchApp()) {
        this._fixedTitle = true;
        this.app.element.classList.add('search-app');
      } else {
        this._fixedTitle = false;
        this.app.element.classList.remove('search-app');
  
        if (!this.app.isHomescreen) {
          this.title.textContent = this.app.name;
        }
      }
  
      if (this.app.isHomescreen || this.isSearchApp()) {
        this.title.setAttribute('data-l10n-id', 'search-or-enter-address');
      }
  
      if (chrome.bar) {
        this.app.element.classList.add('bar');
        this.bar.classList.add('visible');
      }
  
      if (chrome.scrollable) {
        this.app.element.classList.add('collapsible');
  
        if (this.app.isPrivateBrowser()) {
          this.element.classList.add('private');
        } else if (!this.app.themeColor) {
          this.app.element.classList.add('light');
        }
  
        this.scrollable.scrollgrab = true;
      }
  
      if (chrome.maximized) {
        this.expand();
  
        if (!this.app.isBrowser() && !this.app.isHomescreen) {
          this.app.element.classList.add('scrollable');
        }
      }
  
      this.setSiteIcon();
    };
  
    AppChrome.prototype.combinedView = function an_combinedView() {
      var className = this.CLASS_NAME + this.instanceID;
  
      return `<div class="chrome chrome-combined" id="${className}">
                <gaia-progress></gaia-progress>
                <section role="dialog" class="pin-dialog hidden">
                  <header>
                    <h2 class="pin-type" data-l10n-id="pinning-pin-type"></h2>
                  </header>
                  <div class="card-container">
                    <div class="pin-site-icon"></div>
                    <h2 class="pin-site-name"></h2>
                  </div>
                  <div class="footer-container">
                    <button data-l10n-id="pinning-pin" data-action="pin"
                      class="pin-button">
                      Pin
                    </button>
                    <footer>
                      <span data-l10n-id="from">from</span>
                      <span class="origin"></span>
                    </footer>
                  </div>
                </section>
                <div class="pin-scrim"></div>
                <div class="controls">
                  <button type="button" class="back-button"
                          data-l10n-id="back-button" disabled></button>
                  <button type="button" class="forward-button"
                          data-l10n-id="forward-button" disabled></button>
                  <div class="urlbar js-chrome-ssl-information">
                    <div class="urlbar-hit-area"></div>
                    <span class="pb-icon"></span>
                    <div class="site-icon"></div>
                    <div class="chrome-ssl-indicator chrome-title-container">
                      <span class="title" dir="auto"></span>
                    </div>
                    <button type="button" class="reload-button"
                            data-l10n-id="reload-button" disabled></button>
                    <button type="button" class="stop-button"
                            data-l10n-id="stop-button"></button>
                  </div>
                  <button type="button" class="menu-button" alt="Menu"></button>
                  <button type="button" class="windows-button"
                          data-l10n-id="windows-button"></button>
                </div>
              </div>`;
    };
  
    AppChrome.prototype.view = function an_view() {
      var className = this.CLASS_NAME + this.instanceID;
  
      return `<div class="chrome chrome-plain" id="${className}">
              <gaia-progress></gaia-progress>
              <section role="region" class="bar">
                <gaia-header action="close" class='js-chrome-ssl-information'>
                  <div class="chrome-ssl-indicator chrome-ssl-indicator-ltr">
                  </div>
                  <h1 class="chrome-title-container">
                    <bdi dir="auto" class="title"></bdi>
                  </h1>
                  <div class="chrome-ssl-indicator chrome-ssl-indicator-rtl">
                  </div>
                </gaia-header>
              </section>
            </div>`;
    };
  
    AppChrome.prototype.overflowMenuView = function an_overflowMenuView() {
      var template = `<gaia-overflow-menu>
  
       <button id="new-window" data-l10n-id="new-window">
       </button>
  
       <button id="new-private-window" data-l10n-id="new-private-window">
       </button>
  
       <button id="add-to-home" data-l10n-id="add-to-home-screen" hidden>
       </button>
  
       <button id="share" data-l10n-id="share">
       </button>
  
      </gaia-overflow-menu>`;
      return template;
    };
  
    AppChrome.prototype.__defineGetter__('height', function ac_getHeight() {
      if (this._height) {
        return this._height;
      }
  
      this._height = this.element.getBoundingClientRect().height;
      return this._height;
    });
  
    AppChrome.prototype._fetchElements = function ac__fetchElements() {
      this.element = this.containerElement.querySelector('.chrome');
  
      this.progress = this.element.querySelector('gaia-progress');
      this.reloadButton = this.element.querySelector('.reload-button');
      this.forwardButton = this.element.querySelector('.forward-button');
      this.stopButton = this.element.querySelector('.stop-button');
      this.backButton = this.element.querySelector('.back-button');
      this.menuButton = this.element.querySelector('.menu-button');
      this.windowsButton = this.element.querySelector('.windows-button');
      this.title = this.element.querySelector('.chrome-title-container > .title');
      this.urlbar = this.element.querySelector('.urlbar');
      this.siteIcon = this.element.querySelector('.site-icon');
      LazyLoader.load(['js/system_banner.js']).then(function() {
        this.systemBanner = new SystemBanner();
      }.bind(this));
  
      if (this.useCombinedChrome()) {
        this.pinDialog = this.element.querySelector('.pin-dialog');
        this.pinButton = this.element.querySelector('.pin-button');
        this.originElement = this.pinDialog.querySelector('.origin');
        this.pinCardContainer = this.pinDialog.querySelector('.card-container');
        this.pinSiteIcon = this.pinCardContainer.querySelector('.pin-site-icon');
        this.pinSiteName = this.pinCardContainer.querySelector('.pin-site-name');
        this.pinType = this.pinDialog.querySelector('.pin-type');
        this.pinScrim = this.element.querySelector('.pin-scrim');
      }
  
      this.sslIndicator =
        this.element.querySelector('.js-chrome-ssl-information');
  
      this.bar = this.element.querySelector('.bar');
      if (this.bar) {
        this.header = this.element.querySelector('gaia-header');
      }
  
      if (this.reloadButton) {
        this.reloadButton.disabled = !this.hasNavigation();
      }
    };
  
    AppChrome.prototype.handleEvent = function ac_handleEvent(evt) {
      switch (evt.type) {
        case 'rocketbar-overlayclosed':
          window.removeEventListener('rocketbar-overlayclosed', this);
          this.collapse();
          break;
  
        case 'rocketbar-activating':
          this.hidePinDialogCard();
          break;
  
        case 'click':
          this.handleClickEvent(evt);
          break;
  
        case 'action':
          this.handleActionEvent(evt);
          break;
  
        case 'scroll':
          this.handleScrollEvent(evt);
          break;
  
        case '_loading':
          this.show(this.progress);
          this.progress.start();
          break;
  
        case '_loaded':
          this.hide(this.progress);
          this.progress.stop();
          break;
  
        case 'mozbrowserloadstart':
          this.handleLoadStart(evt);
          break;
  
        case 'mozbrowserloadend':
          this.handleLoadEnd(evt);
          break;
  
        case 'mozbrowsererror':
          this.handleError(evt);
          break;
  
        case '_locationchange':
          this.hidePinDialogCard();
          this.handleLocationChange();
          break;
  
        case 'mozbrowserscrollareachanged':
          this.handleScrollAreaChanged(evt);
          break;
  
        case '_securitychange':
          this.handleSecurityChanged(evt);
          break;
  
        case '_namechanged':
          this.handleNameChanged();
          break;
  
        case 'mozbrowsermetachange':
          this.handleMetaChange(evt);
          break;
        case 'pins-scopechange':
          this.handleBookmarksScopeChange(evt);
          break;
      }
    };
  
    AppChrome.prototype.handleClickEvent = function ac_handleClickEvent(evt) {
      evt.stopPropagation(); // We'll handle all clicks here, thanks.
  
      switch (evt.currentTarget) {
        case this.reloadButton:
          this.app.reload();
          break;
  
        case this.stopButton:
          this.app.stop();
          break;
  
        case this.backButton:
          this.app.back();
          break;
  
        case this.forwardButton:
          this.app.forward();
          break;
  
        case this.siteIcon:
          evt.stopImmediatePropagation();
          this.onClickSiteIcon();
          break;
  
        case this.urlbar:
        case this.title:
          this.titleClicked();
          break;
  
        case this.menuButton:
          this.showOverflowMenu();
          break;
  
        case this.windowsButton:
          this.showWindows();
          break;
  
        case this.newWindowButton:
          evt.stopImmediatePropagation();
          // XXX: this isn't a function!
          // this.onNewWindow();
          break;
  
        case this.newPrivateWinButton:
          // Currently not in use, awaiting shared menu web components work.
          evt.stopImmediatePropagation();
          this.onNewPrivateWindow();
          break;
  
        case this.addToHomeButton:
          evt.stopImmediatePropagation();
          this.onAddToHome();
          break;
  
        case this.shareButton:
          evt.stopImmediatePropagation();
          this.onShare();
          break;
  
        case this.pinButton:
          if (this.pinButton.dataset.action == 'unpin') {
            this.unpinSite();
          } else {
            this.pinSite();
          }
          break;
  
        case this.pinScrim:
          this.hidePinDialogCard();
          break;
      }
    };
  
    /**
     * Pin current page in places database.
     */
    AppChrome.prototype.pinPage = function ac_pinPage() {
      Service && Service.request('Places:setPinned', this.app.config.url, true)
        .then(() => {
        this.app.debug('Succeeding in pinning ' + this.app.config.url);
        this.systemBanner.show('page-pinned-to-home-screen');
        var screenshotBlob = this.app.getCachedScreenshotBlob();
        if (screenshotBlob) {
          Service.request('Places:saveScreenshot', this.app.config.url,
                          screenshotBlob);
        }
        if (this._themeChanged) {
          Service.request('Places:saveThemeColor', this.app.config.url,
                          this.app.themeColor, true);
        }
      }, () => {
        console.error('Failed to pin ' + this.app.config.url);
      });
    };
  
    /**
     * Unpin current page in places database.
     */
    AppChrome.prototype.unpinPage = function ac_unpinPage() {
      Service && Service.request('Places:setPinned', this.app.config.url, false)
        .then(() => {
          this.app.debug('Succeeding in unpinning ' + this.app.config.url);
          this.systemBanner.show('page-unpinned-from-home-screen');
        }, () => {
          console.error('Failed to unpin ' + this.app.config.url);
        });
    };
  
    AppChrome.prototype.getSiteUrl = function() {
      var pageUrl = this.app.config.url;
      var webManifest = this.app.webManifest;
      var startURL = (webManifest && this.app.webManifest.start_url) ?
        new URL(this.app.webManifest.start_url, pageUrl) : null;
      return startURL ? startURL.href : pageUrl;
    };
  
    /**
     * Pin current site in bookmarks database.
     */
    AppChrome.prototype.pinSite = function ac_pinSite() {
      var siteObject = {};
      var manifestURL = this.app.webManifestURL;
      var manifestObject = this.app.webManifest;
      var pageURL = new URL(this.app.config.url);
      var hostname = pageURL.hostname;
  
      siteObject.type = 'url';
      siteObject.iconable = false;
      siteObject.icons = this.app.favicons;
      siteObject.frecency = 1;
      siteObject.pinned = true;
      siteObject.pinnedFrom = pageURL.href;
      siteObject.name = this.app.name || hostname;
  
      var siteUrl = this.getSiteUrl();
      siteObject.id = siteUrl;
      siteObject.url = siteUrl;
  
      if (manifestURL && manifestObject) {
        siteObject.webManifestUrl = manifestURL;
        siteObject.webManifest = manifestObject;
        siteObject.name = manifestObject.short_name || manifestObject.name ||
          hostname;
        if (manifestObject.scope) {
          siteObject.scope = manifestObject.scope;
        }
      }
  
      // Set the .icon property before saving for
      // backwards compatibility with verticalhome
      IconsHelper.getIcon(siteObject.url, null,
        {icons: this.app.favicons}, siteObject).then(icon => {
          siteObject.icon = icon;
          Service && Service.request('Places:pinSite', siteObject.id, siteObject)
            .then(() => {
              this.app.debug('pinSite: ' + siteObject.id);
              this.systemBanner.show('site-pinned-to-home-screen');
            })
            .catch((error) => {
              console.error('pinSite, Failed to pin site: ' + error);
            });
        });
    };
  
    /**
     * Unpin current site from bookmarks database.
     */
    AppChrome.prototype.unpinSite = function ac_pinSite() {
      if (!this.pinned) {
        return;
      }
      var siteId = this.getSiteUrl();
      BookmarksDatabase.remove(siteId)
        .then(() => {
          this.app.debug('unpinSite: ' + siteId);
          this.systemBanner.show('site-unpinned-from-home-screen');
          // 'removed' listener will call unpin()
        }, (evt) => {
          this.app.debug('unpinSite, unpinning cancelled for: ' + siteId);
        });
    };
  
    /**
     * Put browser chrome in pinned state.
     */
    AppChrome.prototype.pin = function ac_pin() {
      this.hidePinDialogCard();
      this.collapse();
      this.pinned = true;
      this.app.element.classList.remove('collapsible');
    };
  
    /**
     * Remove pinned state from the browser.
     */
    AppChrome.prototype.unpin = function ac_unpin() {
      this.hidePinDialogCard();
      this.pinned = false;
      if (this.app.config && this.app.config.chrome.scrollable) {
        this.app.element.classList.add('collapsible');
      }
      this.expand();
    };
  
    AppChrome.prototype.titleClicked = function ac_titleClicked() {
      var contextMenu = this.app.contextmenu && this.app.contextmenu.isShown();
      var locked = Service && Service.query('locked');
  
      if (locked || contextMenu) {
        return;
      }
  
      if (!this.isMaximized() && this.app.isBrowser()) {
        if (this.pinned) {
          this.app.element.classList.add('collapsible');
          this.scrollable.scrollTop = 0;
        }
        this.maximize();
      } else {
        window.dispatchEvent(new CustomEvent('global-search-request'));
      }
    };
  
    AppChrome.prototype.onClickSiteIcon = function onClickSiteIcon() {
      if (!this.app.isBrowser() || this.app.loading || !this.isMaximized()) {
        this.app.debug('Pinning is only enabled in the browser');
        return;
      }
  
      if (this.pinDialog.classList.contains('hidden')) {
        this.setPinDialogCard();
      } else {
        this.hidePinDialogCard();
      }
    };
  
    AppChrome.prototype.setPinDialogCard = function ac_setPinDialogCard() {
      if (this.pinned) {
        document.l10n.setAttributes(this.pinType, 'pinning-unpin-type', {
          'type': this.app.name
        });
        this.pinButton.setAttribute('data-l10n-id', 'pinning-unpin-site');
        this.pinButton.dataset.action = 'unpin';
      } else {
        document.l10n.setAttributes(this.pinType, 'pinning-pin-type', {
          'type': this.app.name
        });
        this.pinButton.setAttribute('data-l10n-id', 'pinning-pin');
        this.pinButton.dataset.action = 'pin';
      }
      this.pinSiteName.textContent = this.app.name;
      this.setOrigin();
      this.pinDialog.classList.remove('hidden');
    };
  
    AppChrome.prototype.setOrigin = function ac_setOrigin() {
      var origin = this._currentOrigin.split('://')[1].split('.');
      var tld = this.originElement.querySelector('.tld');
  
      if (!tld) {
        tld = document.createElement('span');
        tld.className = 'tld';
      } else {
        tld.remove();
      }
  
      tld.textContent = origin.slice(origin.length - 2, origin.length).join('.');
  
      if (origin.length > 2) {
        var subdomains = origin.slice(0, origin.length - 2).join('.');
        this.originElement.textContent = subdomains + '.';
      }
  
      this.originElement.appendChild(tld);
    };
  
    AppChrome.prototype.hidePinDialogCard = function ac_hidePinDialogCard(url) {
      this.pinDialog && this.pinDialog.classList.add('hidden');
    };
  
    AppChrome.prototype.expand = function ac_expand() {
      this.element.classList.add('maximized');
    };
  
    AppChrome.prototype.collapse = function ac_collapse() {
      this.hidePinDialogCard();
      this.element.classList.remove('maximized');
    };
  
    AppChrome.prototype.handleActionEvent = function ac_handleActionEvent(evt) {
      if (evt.detail.type === 'close') {
        this.app.kill();
      }
    };
  
    AppChrome.prototype.handleScrollEvent = function ac_handleScrollEvent(evt) {
      if (!this.containerElement.classList.contains('scrollable')) {
        return;
      }
  
      // Ideally we'd animate based on scroll position, but until we have
      // the necessary spec and implementation, we'll animate completely to
      // the expanded or collapsed state depending on whether it's at the
      // top or not.
      // XXX Open a bug since I wonder if there is scrollgrab rounding issue
      // somewhere. While panning from the bottom to the top, there is often
      // a scrollTop position of scrollTopMax - 1, which triggers the transition!
      if (this.scrollable.scrollTop >= this.scrollable.scrollTopMax - 1) {
        this.collapse();
        if (this.pinned) {
          this.app.element.classList.remove('collapsible');
        }
      } else if (!this.pinned) {
        this.expand();
      }
  
      if (this.app.isActive()) {
        this.app.publish('titlestatechanged');
      }
    };
  
    AppChrome.prototype._pinningObserver = function ac__pinningObserver(enabled) {
      // Disable the pinning doorhanger in 2.5 since we have removed all
      // functionality from it. Beyond 2.5 we will use the doorhanger for
      // Tracking protection and privacy configuration.
      // See https://bugzilla.mozilla.org/show_bug.cgi?id=1207710
      var targets = [
        // this.siteIcon, * See comment above. *
        this.pinScrim
      ];
      var method = enabled ? 'addEventListener' : 'removeEventListener';
      targets.forEach(element => {
        element[method]('click', this);
      });
    };
  
    AppChrome.prototype.handleBookmarksScopeChange = function (evt) {
      var scope = evt.detail.scope;
      var inScope = !!(scope && this.app.inScope(scope));
  
      if (scope && !inScope) {
        return;
      }
  
      var origin = new URL(evt.detail.url).origin;
      var name = evt.detail.name;
  
      if (inScope || this.app.matchesOriginAndName(origin, name)) {
        switch (evt.detail.action) {
          case 'add':
          case 'update':
            this.pin();
            break;
          case 'remove':
            this.unpin();
            break;
          default:
            this.app.debug('Unknown pins-scopechange action: ' +
                           evt.detail.action);
        }
      }
    };
  
    AppChrome.prototype._registerEvents = function ac__registerEvents() {
      if (this.useCombinedChrome()) {
        LazyLoader.load('../shared/js/bookmarks_database.js').then(() => {
          this.updateAddToHomeButton();
        }).catch((err) => {
          console.error(err);
        });
        LazyLoader.load('../shared/elements/gaia_overflow_menu/script.js');
  
        window.addEventListener('pins-scopechange', this);
        window.addEventListener('rocketbar-activating', this);
        this.stopButton.addEventListener('click', this);
        this.reloadButton.addEventListener('click', this);
        this.backButton.addEventListener('click', this);
        this.forwardButton.addEventListener('click', this);
        this.urlbar.addEventListener('click', this);
        this.title.addEventListener('click', this);
        this.scrollable.addEventListener('scroll', this);
        this.menuButton.addEventListener('click', this);
        this.windowsButton.addEventListener('click', this);
        this.pinButton.addEventListener('click', this);
  
        // Adding or removing the click listener, depending on
        // the 'Pinning the Web' setting enabled or disabled
        this._boundPinningObserver =
          this._boundPinningObserver || this._pinningObserver.bind(this);
        SettingsListener.observe(PINNING_PREF, '', this._boundPinningObserver);
  
      } else {
        this.header.addEventListener('action', this);
      }
  
      this.app.element.addEventListener('mozbrowserloadstart', this);
      this.app.element.addEventListener('mozbrowserloadend', this);
      this.app.element.addEventListener('mozbrowsererror', this);
      this.app.element.addEventListener('mozbrowsermetachange', this);
      this.app.element.addEventListener('mozbrowserscrollareachanged', this);
      this.app.element.addEventListener('_locationchange', this);
      this.app.element.addEventListener('_namechanged', this);
      this.app.element.addEventListener('_securitychange', this);
      this.app.element.addEventListener('_loading', this);
      this.app.element.addEventListener('_loaded', this);
  
      var element = this.element;
  
      var animEnd = function(evt) {
        if (evt && evt.target !== element) {
          return;
        }
        var publishEvent = this.isMaximized() ? 'expanded' : 'collapsed';
        this.app.publish('chrome' + publishEvent);
      }.bind(this);
  
      element.addEventListener('transitionend', animEnd);
    };
  
    AppChrome.prototype._unregisterEvents = function ac__unregisterEvents() {
  
      if (this.useCombinedChrome()) {
        window.removeEventListener('pins-scopechange', this);
        window.removeEventListener('rocketbar-activating', this);
        this.stopButton.removeEventListener('click', this);
        this.menuButton.removeEventListener('click', this);
        this.windowsButton.removeEventListener('click', this);
        this.pinButton.removeEventListener('click', this);
        this.reloadButton.removeEventListener('click', this);
        this.backButton.removeEventListener('click', this);
        this.forwardButton.removeEventListener('click', this);
        this.title.removeEventListener('click', this);
        this.scrollable.removeEventListener('scroll', this);
  
        SettingsListener.unobserve(PINNING_PREF, this._boundPinningObserver);
  
        if (this.newWindowButton) {
          this.newWindowButton.removeEventListener('click', this);
        }
  
        if (this.addToHomeButton) {
          this.addToHomeButton.removeEventListener('click', this);
        }
  
        if (this.shareButton) {
          this.shareButton.removeEventListener('click', this);
        }
      } else {
        this.header.removeEventListener('action', this);
      }
  
      if (!this.app) {
        return;
      }
  
      this.app.element.removeEventListener('mozbrowserloadstart', this);
      this.app.element.removeEventListener('mozbrowserloadend', this);
      this.app.element.removeEventListener('mozbrowsererror', this);
      this.app.element.removeEventListener('mozbrowsermetachange', this);
      this.app.element.removeEventListener('_locationchange', this);
      this.app.element.removeEventListener('_namechanged', this);
      this.app.element.removeEventListener('_loading', this);
      this.app.element.removeEventListener('_loaded', this);
      this.app = null;
    };
  
    // Name has priority over the rest
    AppChrome.prototype.handleNameChanged =
      function ac_handleNameChanged(evt) {
        if (this._fixedTitle) {
          return;
        }
        if (!this.app.isHomescreen && !this.isSearchApp()) {
          this.title.textContent = this.app.name;
        } else {
          this.title.setAttribute('data-l10n-id', 'search-or-enter-address');
        }
      };
  
    AppChrome.prototype.handleScrollAreaChanged = function(evt) {
      // Make sure the scroll-area-changed is coming from the right element.
      if (evt && (!this.app || !this.app.browser ||
                  evt.target !== this.app.browser.element)) {
        return;
      }
  
      // Check if the page has become scrollable and add the scrollable class.
      // We don't check if a page has stopped being scrollable to avoid oddness
      // with a page oscillating between scrollable/non-scrollable states, and
      // other similar issues that Firefox for Android is still dealing with
      // today.
      if (this.containerElement.classList.contains('scrollable')) {
        return;
      }
  
      // Cache the last given scroll area height so this function can be called
      // without an event object.
      if (evt) {
        this.browserScrollHeight = evt.detail.height;
      }
  
      // Don't respond to this event when we aren't visible. The container size
      // isn't updated in this case, which can ending up incorrectly causing it
      // to be labelled as scrollable.
      if (!this.app.isVisible()) {
        return;
      }
  
      // We allow the bar to collapse if the page is greater than the area of the
      // window with a collapsed bar. Strictly speaking, we'd allow it to
      // collapse if it was greater than the area of the window with the expanded
      // bar, but due to prevalent use of -webkit-box-sizing and plain mistakes,
      // this causes too many false-positives.
      if (this.browserScrollHeight > this.containerElement.clientHeight) {
        this.containerElement.classList.add('scrollable');
      }
    };
  
    AppChrome.prototype.handleSecurityChanged = function(evt) {
      var sslState = this.app.getSSLState();
      this.sslIndicator.dataset.ssl = sslState;
      this.sslIndicator.classList.toggle(
        'chrome-has-ssl-indicator', sslState === 'broken' || sslState === 'secure'
      );
      if (this.pinDialog) {
        this.pinDialog.classList.toggle('secure', sslState === 'secure');
      }
    };
  
    AppChrome.prototype.handleMetaChange =
      function ac__handleMetaChange(evt) {
        var detail = evt.detail;
        if (detail.name !== 'theme-color' || !detail.type) {
          return;
        }
  
        // If the theme-color meta is removed, let's reset the color.
        var color = '';
  
        // Otherwise, set it to the color that has been asked.
        if (detail.type !== 'removed') {
          color = detail.content;
        }
  
        this._themeChanged = true;
        this.setThemeColor(color);
        if (!this.app.isHomescreen && this.app.config.url) {
          Service && Service.request('Places:saveThemeColor',
                                     this.app.config.url, color, true);
        }
      };
  
    AppChrome.prototype.setThemeColor = function ac_setThemColor(color) {
      // Overwrite theme color for private windows and add private class
      if (this.app.isPrivateBrowser()) {
        color = '#392E54';
        this.containerElement.classList.add('private');
      }
  
      var bottomApp = this.app.getBottomMostWindow();
  
      if (this.app.CLASS_NAME === 'PopupWindow' &&
        bottomApp &&
        bottomApp.themeColor) {
        color = bottomApp.themeColor;
      }
  
      this.app.themeColor = color;
      this.element.style.backgroundColor = color;
  
      if (!this.app.isHomescreen) {
        this.scrollable.style.backgroundColor = color;
      }
  
      if (color === '') {
        this.app.element.classList.add('light');
        this.app.publish('titlestatechanged');
        return;
      }
  
      if (color === 'transparent') {
        this.app.element.classList.remove('light');
        this.app.publish('titlestatechanged');
        return;
      }
  
      var self = this;
      var finishedFade = false;
      var endBackgroundFade = function(evt) {
        if (evt && evt.propertyName != 'background-color') {
          return;
        }
        finishedFade = true;
        if (self.element) {
          self.element.removeEventListener('transitionend', endBackgroundFade);
        }
      };
      eventSafety(this.element, 'transitionend', endBackgroundFade, 1000);
  
      window.requestAnimationFrame(function updateAppColor() {
        if (finishedFade || !self.element) {
          return;
        }
  
        var computedColor = window.getComputedStyle(self.element).backgroundColor;
        var colorCodes = /rgb\((\d+), (\d+), (\d+)\)/.exec(computedColor);
        if (!colorCodes || colorCodes.length === 0) {
          return;
        }
  
        var r = parseInt(colorCodes[1]);
        var g = parseInt(colorCodes[2]);
        var b = parseInt(colorCodes[3]);
        var brightness =
          Math.sqrt((r*r) * 0.241 + (g*g) * 0.691 + (b*b) * 0.068);
  
        var wasLight = self.app.element.classList.contains('light');
        var isLight = brightness > 200;
        if (wasLight != isLight) {
          self.app.element.classList.toggle('light', isLight);
          self.app.publish('titlestatechanged');
        }
        window.requestAnimationFrame(updateAppColor);
      });
    };
  
    AppChrome.prototype.render = function() {
      this.publish('willrender');
  
      var view = this.useCombinedChrome() ? this.combinedView() : this.view();
      this.app.element.insertAdjacentHTML('afterbegin', view);
  
      this._fetchElements();
      this._registerEvents();
      this.publish('rendered');
    };
  
    AppChrome.prototype.useLightTheming = function ac_useLightTheming() {
      // The rear window should dictate the status bar color when the front
      // window is a popup.
      if (this.app.CLASS_NAME == 'PopupWindow' &&
          this.app.rearWindow &&
          this.app.rearWindow.appChrome) {
        return this.app.rearWindow.appChrome.useLightTheming();
      }
      // All other cases can use the front window.
      return this.app.element.classList.contains('light');
    };
  
    AppChrome.prototype.useCombinedChrome = function ac_useCombinedChrome(evt) {
      return this.app.config.chrome && !this.app.config.chrome.bar;
    };
  
    AppChrome.prototype.updateAddToHomeButton =
      function ac_updateAddToHomeButton() {
        if (!this.addToHomeButton || !BookmarksDatabase) {
          return;
        }
  
        // Enable/disable the bookmark option
        BookmarksDatabase.get(this._currentURL).then(function resolve(result) {
          this.addToHomeButton.hidden = !!result;
        }.bind(this),
        function reject() {
          this.addToHomeButton.hidden = true;
        }.bind(this));
      };
  
    AppChrome.prototype.handleLocationChange =
      function ac_handleLocationChange() {
        if (!this.app) {
          return;
        }
  
        // Check if this is just a location-change to an anchor tag.
        var anchorChange = false;
  
        if (this._currentURL && this.app.config.url) {
          anchorChange =
            this._currentURL.replace(/#.*/g, '') ===
            this.app.config.url.replace(/#.*/g, '');
        }
  
        this._currentURL = this.app.config.url;
        if (!this._fixedTitle && !this.app.isHomescreen) {
          this.title.textContent = this.app.name;
        }
  
        if (this.backButton && this.forwardButton) {
          this.app.canGoForward(function forwardSuccess(result) {
            if (!this.hasNavigation()) {
              return;
            }
            this.forwardButton.disabled = !result;
          }.bind(this));
  
          this.app.canGoBack(function backSuccess(result) {
            if (!this.hasNavigation()) {
              return;
            }
            this.backButton.disabled = !result;
          }.bind(this));
        }
  
        this.updateAddToHomeButton();
  
        // We only change the icon back to the default one if the new page is not
        // on the same domain than the previous one.
        // In both cases, we look for the best icon after `mozbrowserloadend`.
        var origin = new URL(this._currentURL).origin;
        if (this._currentOrigin !== origin) {
          this.setSiteIcon();
          this._currentOrigin = origin;
        }
  
        if (!this.app.isBrowser()) {
          return;
        }
  
        if (!anchorChange) {
          // Make the rocketbar unscrollable until the page resizes to the
          // appropriate height.
          this.containerElement.classList.remove('scrollable');
          this.scrollable.scrollTop = 0;
  
          Service.request('Places:isPinned', this._currentURL, true)
            .then((isPinned) => {
              isPinned ? this.pin() : this.unpin();
            });
        }
  
        // Set the title for the private browser landing page.
        // This is explicitly placed in the locationchange handler as it's
        // currently possibly to navigate back to the landing page with the
        // back button. Otherwise it could be in the constructor.
        if (this.app.isPrivateBrowser() &&
          this.app.config.url.startsWith('app:')) {
          this.title.dataset.l10nId = 'search-or-enter-address';
        }
      };
  
    AppChrome.prototype.handleLoadStart = function ac_handleLoadStart(evt) {
      this.containerElement.classList.add('loading');
      this._themeChanged = false;
    };
  
    AppChrome.prototype.handleLoadEnd = function ac_handleLoadEnd(evt) {
      this.containerElement.classList.remove('loading');
      if (!this._themeChanged) {
        this.setThemeColor('');
      }
      this.setSiteIcon();
      this.setPinPreviewIcon();
    };
  
    AppChrome.prototype.handleError = function ac_handleError(evt) {
      if (evt.detail && evt.detail.type === 'fatal') {
        return;
      }
      if (this.useCombinedChrome() && this.app.config.chrome.scrollable &&
        !this.pinned) {
        // When we get an error, keep the rocketbar maximized.
        this.expand();
        this.containerElement.classList.remove('scrollable');
      }
    };
  
    AppChrome.prototype.maximize = function ac_maximize(callback) {
      var element = this.element;
      this.expand();
      window.addEventListener('rocketbar-overlayclosed', this);
  
      if (!callback) {
        return;
      }
      eventSafety(element, 'transitionend', callback, 250);
    };
  
    AppChrome.prototype.isMaximized = function ac_isMaximized() {
      return this.element.classList.contains('maximized');
    };
  
    AppChrome.prototype.isSearch = function ac_isSearch() {
      var dataset = this.app.config;
      return dataset.searchURL && this._currentURL === dataset.searchURL;
    };
  
    AppChrome.prototype.isSearchApp = function() {
      return this.app.config.manifest &&
        this.app.config.manifest.role === 'search';
    };
  
    AppChrome.prototype.hasNavigation = function ac_hasNavigation(evt) {
      return this.app.isBrowser() ||
        (this.app.config.chrome && this.app.config.chrome.navigation);
    };
  
    AppChrome.prototype.addBookmark = function ac_addBookmark() {
      var dataset = this.app.config;
      var favicons = this.app.favicons;
  
      var name;
      if (this.isSearch()) {
        name = dataset.searchName;
      } else {
        name = this.title.textContent;
      }
      var url = this._currentURL;
  
      LazyLoader.load('../shared/js/icons_helper.js').then(() => {
        IconsHelper.getIcon(url, null, {icons: favicons}).then(icon => {
          var activity = new MozActivity({
            name: 'save-bookmark',
            data: {
              type: 'url',
              url: url,
              name: name,
              icon: icon,
              iconable: false
            }
          });
  
          if (this.addToHomeButton) {
            activity.onsuccess = function onsuccess() {
              this.addToHomeButton.hidden = true;
            }.bind(this);
          }
        });
      }).catch((err) => {
        console.error(err);
      });
    };
  
    AppChrome.prototype.onAddBookmark = function ac_onAddBookmark() {
      var self = this;
      function selected(value) {
        if (value) {
          self.addBookmark();
        }
      }
  
      var title = 'add-to-home-screen';
      var options = [];
  
      if (this.isSearch()) {
        var dataset = this.app.config;
        options.push({
          id: 'search',
          text: {
            raw: dataset.searchName
          }
        });
      } else {
        options.push({
          id: 'origin',
          text: {
            raw: this.title.textContent
          }
        });
      }
  
      ModalDialog.selectOne(title, options, selected);
    };
  
    AppChrome.prototype.showWindows = function ac_showWindows() {
      window.dispatchEvent(
        new CustomEvent('taskmanagershow',
                        { detail: { filter: 'browser-only' }})
      );
    };
  
    AppChrome.prototype.__defineGetter__('overflowMenu',
      // Instantiate the overflow menu when it's needed
      function ac_getOverflowMenu() {
        if (!this._overflowMenu && this.useCombinedChrome() &&
            window.GaiaOverflowMenu) {
          this.app.element.insertAdjacentHTML('afterbegin',
                                              this.overflowMenuView());
          this._overflowMenu = this.containerElement.
            querySelector('gaia-overflow-menu');
          this.newWindowButton = this._overflowMenu.
            querySelector('#new-window');
          this.newPrivateWinButton = this._overflowMenu.
            querySelector('#new-private-window');
          this.addToHomeButton = this._overflowMenu.
            querySelector('#add-to-home');
          this.shareButton = this._overflowMenu.
            querySelector('#share');
  
          this.newWindowButton.addEventListener('click', this);
          this.newPrivateWinButton.addEventListener('click', this);
          this.addToHomeButton.addEventListener('click', this);
          this.shareButton.addEventListener('click', this);
  
          this.updateAddToHomeButton();
        }
  
        return this._overflowMenu;
      });
  
    AppChrome.prototype.showOverflowMenu = function ac_showOverflowMenu() {
      this.overflowMenu.show();
    };
  
    AppChrome.prototype.hideOverflowMenu = function ac_hideOverflowMenu() {
      this.overflowMenu.hide();
    };
  
    /* Bug 1054466 switched the browser overflow menu to use the system style,
     * but we eventually want to switch back to the new style. We can do that
     * by removing this function.
     */
    AppChrome.prototype.showOverflowMenu = function ac_showOverflowMenu() {
      if (this.app.contextmenu) {
        var name = this.isSearch() ?
          this.app.config.searchName : this.title.textContent;
        this.app.contextmenu.showDefaultMenu(newTabManifestURL, name);
      }
    };
  
  
    /**
     * Display the website icon in the URL bar, if any.
     * If the url parameter is specified, it is loaded immediately. Otherwise,
     * we look for the best possible icon for this website.
     *
     * @param {string?} url
     */
    AppChrome.prototype.setSiteIcon = function ac_setSiteIcon() {
      if (!this.siteIcon || this.app.isPrivateBrowser()) {
        return;
      }
  
      this.app.getSiteIconUrl(ICON_SIZE)
        .then(iconObject => {
          // We compare the original icon URL, otherwise there is a flickering
          // effect because a different object url is created each time.
          if (this._currentIconUrl !== iconObject.originalUrl) {
            this.siteIcon.style.backgroundImage = iconObject.url;
            this._currentIconUrl = iconObject.originalUrl;
          }
        })
        .catch((err) => {
          this.siteIcon.style.backgroundImage = '';
          this.app.debug('setSiteIcon, error from getSiteIcon: %s', err);
        });
    };
  
    /**
     * Populate the pin preview doorhanger with current site/app icon.
     */
    AppChrome.prototype.setPinPreviewIcon = function ac_setPinPreviewIcon() {
      if (!this.pinSiteIcon) {
        return;
      }
      this.app.getSiteIconUrl(PREVIEW_ICON_SIZE).then(iconObject => {
        this.pinSiteIcon.style.backgroundImage = iconObject.url;
      }).catch((err) => {
        this.app.debug('setPinPreviewIcon, error from getSiteIcon: %s', err);
        this.pinSiteIcon.style.backgroundImage = '';
      });
    };
  
    exports.AppChrome = AppChrome;
  }(window));
  