text: /* global AppWindow, AppChrome, MocksHelper, MockL10n, PopupWindow,
            MockModalDialog, MockService, MockPromise,
            MockSettingsListener, BookmarksDatabase,
            Service, IconsHelper, process */
  
  /* exported MockBookmarksDatabase */
  'use strict';
  
  require('/shared/js/component_utils.js');
  require('/shared/js/event_safety.js');
  require('/shared/elements/gaia_progress/script.js');
  require('/shared/elements/gaia_pin_card/script.js');
  require('/shared/test/unit/mocks/mock_l20n.js');
  requireApp('system/test/unit/mock_lazy_loader.js');
  require('/shared/test/unit/mocks/mock_service.js');
  require('/shared/test/unit/mocks/mock_promise.js');
  require('/shared/test/unit/mocks/mock_icons_helper.js');
  require('/shared/test/unit/mocks/mock_url_helper.js');
  require('/shared/test/unit/mocks/mock_bookmarks_database.js');
  requireApp('system/test/unit/mock_app_window.js');
  requireApp('system/test/unit/mock_system_banner.js');
  requireApp('system/test/unit/mock_popup_window.js');
  requireApp('system/test/unit/mock_modal_dialog.js');
  requireApp('system/shared/test/unit/mocks/mock_settings_listener.js');
  
  function mockEvent(data) {
    data.stopPropagation = sinon.spy();
    data.stopImmediatePropagation = sinon.spy();
    data.preventDefault = sinon.spy();
    data.currentTarget = data.target;
    return data;
  }
  
  const PINNING_PREF = 'dev.gaia.pinning_the_web';
  
  var mocksForAppChrome = new MocksHelper([
    'AppWindow', 'ModalDialog', 'PopupWindow', 'BookmarksDatabase',
    'Service', 'LazyLoader', 'SettingsListener', 'IconsHelper',
    'SystemBanner'
  ]).init();
  
  suite('system/AppChrome', function() {
    var stubById, realL10n, app, chrome;
    mocksForAppChrome.attachTestHelpers();
  
    setup(function(done) {
      realL10n = document.l10n;
      document.l10n = MockL10n;
      this.sinon.useFakeTimers();
  
      stubById = this.sinon.stub(document, 'getElementById');
      stubById.returns(document.createElement('div'));
  
      requireApp('system/js/base_ui.js');
      requireApp('system/js/app_chrome.js', function() {
        this.sinon.stub(AppChrome.prototype, 'setSiteIcon');
        this.sinon.stub(AppChrome.prototype, 'setPinPreviewIcon');
        app = new AppWindow(cloneConfig(fakeWebSite));
        app.contextmenu = {
          isShown: function() {return false;}
        };
  
        chrome = new AppChrome(app);
        done();
      }.bind(this));
    });
  
    teardown(function() {
      document.l10n = realL10n;
      stubById.restore();
    });
  
    function cloneConfig(config) {
      return JSON.parse(JSON.stringify(config));
    }
    var fakeWebSite = {
      url: 'http://google.com/index.html',
      origin: 'app://google.com',
      chrome: {
        scrollable: true
      }
    };
  
    var fakeWebApp = {
      url: 'http://example.com/index.html',
      webManifestURL: 'http://example.com/manifest.webmanifest',
      webManifest: {
        'name': 'Example Web App',
        'short_name': 'Example'
      },
      chrome: {
        scrollable: true
      }
    };
  
    var fakeAppWithName = {
      url: 'app://communications.gaiamobile.org/dialer/index.html',
      name: 'Phone',
      manifest: {name: 'Dialer'},
      manifestURL: 'app://communications.gaiamobile.org/manifest.webapp',
      origin: 'app://communications.gaiamobile.org',
      chrome: {
        scrollable: false
      }
    };
  
    var fakeSearchApp = {
      url: 'chrome://gaia/content/search/newtab.html',
      name: 'Browser',
      manifest: {
        name: 'Browser',
        role: 'search'
      },
      manifestURL: 'chrome://gaia/content/search/manifest.webapp',
      origin: 'chrome://gaia/content/search',
      chrome: {
          navigation: true
      }
    };
  
    var fakePrivateLandingPage = {
      url: 'chrome://gaia/content/search/',
      origin: 'app://www.fake',
      isPrivate: true
    };
  
    var fakeAppConfigBar = {
      url: 'app://www.fake/index.html',
      chrome: {
        bar: true
      }
    };
  
    var fakeAppMaximized = {
      url: 'chrome://gaia/content/search/newtab.html',
      chrome: {
        maximized: true
      }
    };
  
    var fadeTransitionEndEvent = new CustomEvent('transitionend');
    fadeTransitionEndEvent.propertyName = 'background-color';
  
    test('it sets currentOrigin to the app origin', function() {
      assert.isTrue(chrome._currentOrigin === app.origin);
    });
  
    suite('Old Navigation - Application events', function() {
      test('app is loading', function() {
        var stubShowProgress = this.sinon.stub(chrome, 'show');
        var spyProgressStart = this.sinon.spy(chrome.progress, 'start');
        assert.isFalse(chrome.progress.hasAttribute('animated'));
        chrome.handleEvent(mockEvent({ type: '_loading' }));
        assert.equal(chrome.progress.getAttribute('data-l10n-id'),
          'gaia-progress-loading');
        assert.isTrue(stubShowProgress.calledWith(chrome.progress));
        assert.isTrue(spyProgressStart.calledOnce);
      });
  
      test('app is loaded', function() {
        var stubHideProgress = this.sinon.stub(chrome, 'hide');
        var spyProgressStop = this.sinon.spy(chrome.progress, 'stop');
        chrome.handleEvent(mockEvent({ type: '_loaded' }));
        assert.equal(chrome.progress.getAttribute('data-l10n-id'),
          'gaia-progress-loaded');
        assert.isTrue(stubHideProgress.calledWith(chrome.progress));
        assert.isTrue(spyProgressStop.calledOnce);
      });
  
      test('app location is changed', function() {
        this.sinon.stub(chrome, 'hidePinDialogCard');
  
        var stubHandleLocationChange =
          this.sinon.stub(chrome, 'handleLocationChange');
        chrome.handleEvent(mockEvent({ type: '_locationchange' }));
        assert.isTrue(stubHandleLocationChange.called);
        assert.isTrue(chrome.hidePinDialogCard.called);
        assert.isTrue(chrome.setSiteIcon.calledWith());
      });
  
      test('app location is changed - private browser landing page', function() {
        var app = new AppWindow(fakePrivateLandingPage);
        this.sinon.stub(app, 'isBrowser').returns(true);
        this.sinon.stub(app, 'isPrivateBrowser').returns(true);
  
        var chrome = new AppChrome(app);
        this.sinon.stub(chrome, 'hidePinDialogCard');
        chrome.handleEvent(mockEvent({ type: '_locationchange' }));
        assert.equal(chrome.title.dataset.l10nId, 'search-or-enter-address');
        assert.isTrue(chrome.hidePinDialogCard.called);
      });
  
      test('add bookmark', function() {
        var stubSelectOne = this.sinon.stub(MockModalDialog, 'selectOne');
        chrome.onAddBookmark();
        assert.isTrue(stubSelectOne.called);
      });
  
      test('app ssl state is changed', function() {
        var app = new AppWindow(cloneConfig(fakeWebSite));
        this.sinon.stub(app, 'getSSLState', function() {
          return 'broken';
        });
        var chrome = new AppChrome(app);
        var stubHandleSecurityChanged =
          this.sinon.spy(chrome, 'handleSecurityChanged');
        chrome.handleEvent(mockEvent({ type: '_securitychange' }));
        assert.isTrue(stubHandleSecurityChanged.called);
        assert.equal(chrome.sslIndicator.dataset.ssl, 'broken');
      });
    });
  
    suite('Views', function() {
      test('Regular view for bar only', function() {
        var app = new AppWindow(fakeAppConfigBar);
  
        var spyView = this.sinon.spy(AppChrome.prototype, 'view');
        new AppChrome(app); // jshint ignore:line
        assert.isTrue(spyView.called);
      });
  
      test('Combined view for navigation + bar', function() {
        var app = new AppWindow(cloneConfig(fakeWebSite));
  
        var spyView = this.sinon.spy(AppChrome.prototype, 'combinedView');
        new AppChrome(app); // jshint ignore:line
        assert.isTrue(spyView.called);
      });
    });
  
  
    suite('Button events', function() {
      test('back', function() {
        var stubBack = this.sinon.stub(app, 'back');
        assert.equal(chrome.backButton.getAttribute('data-l10n-id'),
          'back-button');
        chrome.handleEvent(
          mockEvent({ type: 'click', target: chrome.backButton }));
        assert.isTrue(stubBack.called);
      });
  
      test('forward', function() {
        var stubForward = this.sinon.stub(app, 'forward');
        assert.equal(chrome.forwardButton.getAttribute('data-l10n-id'),
          'forward-button');
        chrome.handleEvent(
          mockEvent({ type: 'click', target: chrome.forwardButton }));
        assert.isTrue(stubForward.called);
      });
  
      test('reload', function() {
        var stubReload = this.sinon.stub(app, 'reload');
        assert.equal(chrome.reloadButton.getAttribute('data-l10n-id'),
          'reload-button');
        var evt = mockEvent({ type: 'click', target: chrome.reloadButton });
        chrome.handleEvent(evt);
        assert.isTrue(stubReload.called);
        assert.isTrue(evt.stopPropagation.called);
      });
  
      test('stop', function() {
        var stubStop = this.sinon.stub(app, 'stop');
        assert.equal(chrome.stopButton.getAttribute('data-l10n-id'),
          'stop-button');
        chrome.handleEvent(
          mockEvent({ type: 'click', target: chrome.stopButton }));
        assert.isTrue(stubStop.called);
      });
  
      test('windows', function(done) {
        var app = new AppWindow(fakeSearchApp);
        var chrome = new AppChrome(app);
        assert.equal(chrome.windowsButton.getAttribute('data-l10n-id'),
          'windows-button');
        window.addEventListener('taskmanagershow', function() {
          done();
        });
        chrome.handleEvent(
          mockEvent({ type: 'click', target: chrome.windowsButton }));
      });
  
      test('location changed', function() {
        this.sinon.stub(app, 'isBrowser').returns(true);
        var stub1 = this.sinon.stub(app, 'canGoForward');
        var stub2 = this.sinon.stub(app, 'canGoBack');
  
        chrome.handleEvent(mockEvent({ type: '_locationchange' }));
  
        stub1.getCall(0).args[0](true);
        assert.equal(chrome.forwardButton.disabled, false);
        stub1.getCall(0).args[0](false);
        assert.equal(chrome.forwardButton.disabled, true);
  
        stub2.getCall(0).args[0](true);
        assert.equal(chrome.backButton.disabled, false);
        stub2.getCall(0).args[0](false);
        assert.equal(chrome.backButton.disabled, true);
      });
  
      test('location#anchor changed', function() {
        this.sinon.stub(app, 'isBrowser').returns(true);
  
        chrome._currentURL = fakeWebSite.url;
        chrome.containerElement.classList.add('scrollable');
        chrome.handleEvent({ type: '_locationchange',
                             detail: fakeWebSite.url + '#anchor' });
        assert.isTrue(chrome.containerElement.classList.contains('scrollable'));
      });
  
      test('location changed - without navigation', function() {
        var app = new AppWindow(fakeAppWithName);
        var chrome = new AppChrome(app);
        this.sinon.stub(app, 'isBrowser').returns(false);
        var stub1 = this.sinon.stub(app, 'canGoForward');
        var stub2 = this.sinon.stub(app, 'canGoBack');
  
        var evt = new CustomEvent('_locationchange');
        app.element.dispatchEvent(evt);
  
        stub1.getCall(0).args[0](true);
        assert.equal(chrome.forwardButton.disabled, true);
        stub1.getCall(0).args[0](false);
        assert.equal(chrome.forwardButton.disabled, true);
  
        stub2.getCall(0).args[0](true);
        assert.equal(chrome.backButton.disabled, true);
        stub2.getCall(0).args[0](false);
        assert.equal(chrome.backButton.disabled, true);
      });
    });
  
  
    suite('Navigation events', function() {
      setup(function() {
        chrome.setSiteIcon.reset();
        chrome.setPinPreviewIcon.reset();
      });
  
      test('loadstart', function() {
        chrome.handleEvent(mockEvent({ type: 'mozbrowserloadstart' }));
        assert.isTrue(chrome.containerElement.classList.contains('loading'));
        assert.isFalse(chrome.setSiteIcon.calledOnce);
        assert.isTrue(chrome.setPinPreviewIcon.notCalled);
      });
  
      test('loadend', function() {
        chrome.handleEvent(mockEvent({ type: 'mozbrowserloadend' }));
        assert.isFalse(chrome.containerElement.classList.contains('loading'));
        assert.isTrue(chrome.setSiteIcon.calledOnce);
        assert.isTrue(chrome.setPinPreviewIcon.calledOnce);
        assert.equal(0, chrome.setSiteIcon.getCall(0).args.length,
                  'setSiteIcon passed 0 argument');
      });
  
      test('namechanged - does not set when we have a fixed title', function() {
        chrome._fixedTitle = true;
        chrome.title.textContent = 'foo';
        chrome.handleEvent(mockEvent({ type: '_namechanged' }));
        assert.equal(chrome.title.textContent, 'foo');
      });
  
      suite('error', function() {
        var app, chrome;
  
        setup(function() {
          app = new AppWindow(cloneConfig(fakeWebSite));
        });
  
        test('scrollable chrome without bar', function() {
          app.config.chrome.bar = false;
          app.config.chrome.scrollable = true;
          chrome = new AppChrome(app);
          chrome.containerElement.classList.add('scrollable');
          chrome.handleEvent({ type: 'mozbrowsererror', detail: {
            type: 'offline'
          }});
          assert.isTrue(chrome.element.classList.contains('maximized'));
        });
  
        test('not scrollable chrome without bar', function() {
          app.config.chrome.bar = false;
          app.config.chrome.scrollable = false;
          chrome = new AppChrome(app);
          chrome.handleEvent({ type: 'mozbrowsererror', detail: {
            type: 'offline'
          }});
          assert.isFalse(chrome.element.classList.contains('maximized'));
        });
  
        test('scrollable chrome with bar', function() {
          app.config.chrome.bar = true;
          app.config.chrome.scrollable = true;
          chrome = new AppChrome(app);
          chrome.handleEvent({ type: 'mozbrowsererror', detail: {
            type: 'fatal'
          }});
          assert.isFalse(chrome.element.classList.contains('maximized'));
        });
      });
    });
  
    suite('handleScrollEvent', function() {
      var app, chrome;
  
      setup(function() {
        app = new AppWindow(cloneConfig(fakeWebSite));
        app.config.chrome.bar = true;
        chrome = new AppChrome(app);
        this.sinon.stub(chrome, 'expand');
        this.sinon.stub(chrome, 'collapse');
      });
  
      test('does nothing if is not scrollable', function() {
        chrome.containerElement.classList.remove('scrollable');
        chrome.scrollable = {
          scrollTop: 100,
          scrollTopMax: 1
        };
        chrome.handleEvent(mockEvent({ type: 'scroll'}));
        assert.isFalse(chrome.expand.called);
        assert.isFalse(chrome.collapse.called);
      });
  
      test('collapses when scrollTop > scrollTopMax', function() {
        chrome.containerElement.classList.add('scrollable');
        chrome.scrollable = {
          scrollTop: 100,
          scrollTopMax: 55
        };
        chrome.handleEvent(mockEvent({ type: 'scroll'}));
        assert.isFalse(chrome.expand.called);
        assert.isTrue(chrome.collapse.called);
      });
  
      test('expands when scrollTop > scrollTopMax', function() {
        chrome.containerElement.classList.add('scrollable');
        chrome.scrollable = {
          scrollTop: 50,
          scrollTopMax: 55
        };
        chrome.handleEvent(mockEvent({ type: 'scroll'}));
        assert.isTrue(chrome.expand.called);
        assert.isFalse(chrome.collapse.called);
      });
    });
  
    suite('handleScrollAreaChanged', function() {
      var app, chrome, appVisible, containerHeight, classListAddSpy, event;
  
      setup(function() {
        app = new AppWindow(cloneConfig(fakeWebSite));
        app.config.chrome.bar = true;
  
        this.sinon.stub(app, 'isVisible', () => appVisible);
        appVisible = true;
  
        chrome = new AppChrome(app);
        chrome.containerElement.classList.remove('scrollable');
        Object.defineProperty(chrome.containerElement, 'clientHeight', {
          configurable: true,
          get: () => containerHeight
        });
        classListAddSpy =
          this.sinon.spy(chrome.containerElement.classList, 'add');
  
        event = {
          target: app.browser.element,
          detail: { height: 200 }
        };
        containerHeight = 100;
      });
  
      test('sets scrollable if browser height > container height', function() {
        chrome.handleScrollAreaChanged(event);
        assert.isTrue(chrome.containerElement.classList.contains('scrollable'));
      });
  
      test('does nothing if browser height <= container height', function() {
        containerHeight = 200;
        chrome.handleScrollAreaChanged(event);
        assert.isFalse(chrome.containerElement.classList.contains('scrollable'));
      });
  
      test('does nothing if container already scrollable', function() {
        chrome.containerElement.classList.add('scrollable');
  
        chrome.handleScrollAreaChanged(event);
        assert.isTrue(classListAddSpy.calledOnce);
      });
  
      test('does nothing if app not visible', function() {
        appVisible = false;
        chrome.handleScrollAreaChanged(event);
        assert.isFalse(classListAddSpy.called);
      });
  
      test('does nothing if incorrect target', function() {
        event.target = null;
        chrome.handleScrollAreaChanged(event);
        assert.isFalse(classListAddSpy.called);
      });
    });
  
    suite('setPinDialogCard', function() {
      setup(function() {
        this.sinon.stub(chrome, 'setOrigin');
        chrome.pinDialog.classList.add('hidden');
        chrome.setPinDialogCard();
      });
  
      test('displays pin card dialog', function() {
        assert.isTrue(chrome.setOrigin.called, 'sets the origin');
        assert.isFalse(chrome.pinDialog.classList.contains('hidden'),
          'shows the pinDialog');
      });
  
      test('clicking scrim hides dialog', function() {
        assert.isFalse(chrome.pinDialog.classList.contains('hidden'),
          'pinDialog is shown');
        chrome.handleEvent(mockEvent({ type: 'click', target: chrome.pinScrim }));
        assert.isTrue(chrome.pinDialog.classList.contains('hidden'),
          'pinDialog is hidden');
      });
    });
  
    suite('setOrigin', function() {
      var DOMAIN, SUBDOMAIN;
  
      setup(function() {
        DOMAIN = 'firefox.org';
        SUBDOMAIN = 'test.';
      });
  
      test('no subdomains', function() {
        chrome._currentOrigin = 'http://' + DOMAIN;
        chrome.setOrigin();
        var tld = chrome.originElement.querySelector('.tld');
        assert.equal(tld.textContent, DOMAIN);
        chrome.originElement.removeChild(tld);
        assert.equal(chrome.originElement.textContent, '');
      });
  
      test('with subdomains', function() {
        chrome._currentOrigin = 'http://' + SUBDOMAIN + DOMAIN;
        chrome.setOrigin();
        var tld = chrome.originElement.querySelector('.tld');
        assert.equal(tld.textContent, DOMAIN);
        chrome.originElement.removeChild(tld);
        assert.equal(chrome.originElement.textContent, SUBDOMAIN);
      });
    });
  
    suite('URLBar', function() {
      test('click when collapsed', function() {
        MockService.mockQueryWith('locked', false);
        var stubIsBrowser = this.sinon.stub(app, 'isBrowser').returns(true);
        var stubExpand = this.sinon.stub(chrome, 'expand');
        chrome.handleEvent(mockEvent({ type: 'click', target: chrome.title }));
        assert.isTrue(stubExpand.called);
        stubExpand.restore();
        stubIsBrowser.restore();
      });
  
      test('click when expanded', function() {
        MockService.mockQueryWith('locked', false);
        chrome.maximize();
        var stubDispatchEvent = this.sinon.stub(window, 'dispatchEvent');
        chrome.handleEvent(mockEvent({ type: 'click', target: chrome.title }));
        assert.isTrue(stubDispatchEvent.called);
        stubDispatchEvent.restore();
      });
  
      test('should set the name when created', function() {
        var app = new AppWindow(cloneConfig(fakeWebSite));
        app.name = 'Phone';
        var chrome = new AppChrome(app);
        assert.equal(chrome.title.textContent, 'Phone');
      });
  
      test('should update the name when it changes', function() {
        var app = new AppWindow(cloneConfig(fakeWebSite));
        app.name = 'Phone';
        var chrome = new AppChrome(app);
        assert.equal(chrome.title.textContent, 'Phone');
  
        app.name = 'Phone2';
        var evt = new CustomEvent('_namechanged');
        app.element.dispatchEvent(evt);
        assert.equal(chrome.title.textContent, 'Phone2');
      });
  
      test('should not update the name of homescreen on change', function() {
        var app = new AppWindow(cloneConfig(fakeWebSite));
        app.isHomescreen = true;
        app.name = 'Home Screen';
        var chrome = new AppChrome(app);
        assert.equal(chrome.title.textContent, '');
  
        app.name = 'Home Screen 2';
        var evt = new CustomEvent('_namechanged');
        app.element.dispatchEvent(evt);
        assert.equal(chrome.title.textContent, '');
      });
  
      test('localized app is not immediately overridden by titlechange event',
        function() {
        var app = new AppWindow(cloneConfig(fakeWebSite));
        app.name = 'Phone';
  
        var chrome = new AppChrome(app);
        chrome.handleEvent({ type: 'mozbrowsertitlechange',
                             detail: 'Do not update' });
        assert.equal(chrome.title.textContent, 'Phone');
      });
    });
  
    suite('mozbrowserlocationchange', function() {
      var subject = null;
      var observeSpy, unobserveSpy;
  
      setup(function() {
        var website = new AppWindow(cloneConfig(fakeWebSite));
        subject = new AppChrome(website);
        subject._registerEvents();
      });
  
      teardown(function() {
        subject._unregisterEvents();
      });
  
      test('should not do anything on apps with manifests', function() {
        var app = new AppWindow(fakeAppWithName);
        var chrome = new AppChrome(app);
  
        observeSpy = this.sinon.spy(MockSettingsListener, 'observe');
        unobserveSpy = this.sinon.spy(MockSettingsListener, 'unobserve');
  
        chrome._registerEvents();
  
        sinon.assert.calledOnce(observeSpy);
        sinon.assert.calledWith(observeSpy, PINNING_PREF);
        sinon.assert.notCalled(unobserveSpy);
  
        var evt = new CustomEvent('mozbrowserlocationchange', {
          detail: 'app://communications.gaiamobile.org/calllog.html'
        });
        chrome.app.element.dispatchEvent(evt);
        this.sinon.clock.tick(500);
        chrome._unregisterEvents();
  
        sinon.assert.calledWith(unobserveSpy, PINNING_PREF);
        sinon.assert.calledOnce(unobserveSpy);
      });
  
      test('browser start page should always have the same title',
      function() {
        var app = new AppWindow(fakeSearchApp);
        var chrome = new AppChrome(app);
        var titleEvent = new CustomEvent('mozbrowsertitlechange', {
          detail: 'Bing'
        });
        chrome.app.element.dispatchEvent(titleEvent);
        assert.equal(chrome.title.getAttribute('data-l10n-id'),
          'search-or-enter-address');
      });
  
      test('pins the chrome if the new url is in the scope', function() {
        var app, chrome;
        this.sinon.stub(Service, 'request', function() {
          return {
            then: function(callback) {
              callback(true);
            }
          };
        });
        fakeSearchApp.chrome.pinned = true;
        fakeSearchApp.chrome.scrollable = true;
        fakeSearchApp.chrome.url = 'http://aaa.com';
        app = new AppWindow(fakeSearchApp);
        this.sinon.stub(app, 'isBrowser').returns(true);
        chrome = new AppChrome(app);
        chrome.handleLocationChange();
        assert.isTrue(chrome.pinned);
        assert.isFalse(app.element.classList.contains('collapsible'));
      });
  
      test('unpins the chrome if the new url is in the scope', function() {
        var app, chrome;
        this.sinon.stub(Service, 'request', function() {
          return {
            then: function(callback) {
              callback(false);
            }
          };
        });
        fakeSearchApp.chrome.pinned = true;
        fakeSearchApp.chrome.scrollable = true;
        fakeSearchApp.chrome.url = 'http://aaa.com';
        app = new AppWindow(fakeSearchApp);
        this.sinon.stub(app, 'isBrowser').returns(true);
        chrome = new AppChrome(app);
        chrome.handleLocationChange();
        assert.isFalse(chrome.pinned);
        assert.isTrue(app.element.classList.contains('collapsible'));
      });
  
      test('doesnt make it scrollable if the config doesnt say so', function() {
        fakeSearchApp.chrome.url = 'http://aaa.com';
        fakeSearchApp.chrome.scrollable = false;
        var app = new AppWindow(fakeSearchApp);
        this.sinon.stub(app, 'isBrowser').returns(true);
        var chrome = new AppChrome(app);
        chrome.handleLocationChange();
  
        app.config.url = 'http://test.com';
        chrome.handleLocationChange();
        assert.isFalse(app.element.classList.contains('collapsible'));
      });
  
      test('it makes it collapsible again navigating away of a pin', function() {
        var app, chrome, isPinned;
        isPinned = true;
  
        this.sinon.stub(Service, 'request', function() {
          return {
            then: function(callback) {
              callback(isPinned);
            }
          };
        });
        fakeSearchApp.chrome.pinned = true;
        fakeSearchApp.chrome.scrollable = true;
        fakeSearchApp.chrome.url = 'http://aaa.com';
        app = new AppWindow(fakeSearchApp);
        this.sinon.stub(app, 'isBrowser').returns(true);
        chrome = new AppChrome(app);
        chrome.handleLocationChange();
        assert.isTrue(chrome.pinned);
        assert.isFalse(app.element.classList.contains('collapsible'));
        isPinned = false;
        app.config.url = 'http://blabla.com';
        app.config.chrome.scrollable = true;
        chrome.handleLocationChange();
        assert.isTrue(app.element.classList.contains('collapsible'));
      });
    });
  
    suite('Maximized', function() {
      var subject = null;
      var cbSpy = null;
      setup(function() {
        var app = new AppWindow(cloneConfig(fakeWebSite));
        subject = new AppChrome(app);
        cbSpy = this.sinon.spy();
  
        subject.maximize(cbSpy);
      });
  
      test('should add the maximized css class on the element', function() {
        assert.isTrue(subject.element.classList.contains('maximized'));
      });
  
      test('should trigger the callback after the transition', function() {
        sinon.assert.notCalled(cbSpy);
        subject.element.dispatchEvent(new CustomEvent('transitionend'));
        sinon.assert.calledOnce(cbSpy);
      });
  
      test('should trigger the callback after a safety timeout', function() {
        sinon.assert.notCalled(cbSpy);
        this.sinon.clock.tick(500);
        sinon.assert.calledOnce(cbSpy);
      });
  
      test('collapse should remove the maximized css class', function() {
        var app = new AppWindow(cloneConfig(fakeWebSite));
        var subject = new AppChrome(app);
        subject.element.classList.add('maximized');
  
        subject.collapse();
        assert.isFalse(subject.element.classList.contains('maximized'));
      });
  
      test('rocketbar-overlayclosed should collapse the urlbar', function() {
        var app = new AppWindow(cloneConfig(fakeWebSite));
        var subject = new AppChrome(app);
        subject.maximize();
  
        window.dispatchEvent(new CustomEvent('rocketbar-overlayclosed'));
        assert.isFalse(subject.element.classList.contains('maximized'));
      });
    });
  
    suite('Theme-Color', function() {
      var app, chrome, stubRequestAnimationFrame, appPublishStub;
  
      setup(function() {
        this.sinon.clock.restore();
        app = new AppWindow(cloneConfig(fakeWebSite));
        chrome = new AppChrome(app);
        stubRequestAnimationFrame =
          this.sinon.stub(window, 'requestAnimationFrame').yieldsAsync();
        appPublishStub = this.sinon.stub(app, 'publish');
      });
  
      teardown(function(done) {
        // setThemeColor triggers a rAF loop that doesn't finish until
        // it gets a transitionend event. Some tests dispatch this event
        // but others don't so we dispatch it again here in case it hasn't
        // been dispatched yet.
        //
        // If we fail to do this, the rAF loop triggered by a previous test
        // might still be running when we start the next test which will
        // mean we *sometimes* get surprising values for
        // stubRequestAnimationFrame.callCount.
        //
        // Note that here we only make sure the rAF loop on |chrome| has
        // finished. Individual tests are responsible for firing a
        // transitionend event at the element of any additional AppChrome
        // objects they create.
        chrome.element.dispatchEvent(fadeTransitionEndEvent);
  
        // To ensure the transitionend event has been processed, wait for a
        // *real* requestAnimationFrame tick.
        window.requestAnimationFrame.restore();
        // Make sure we pass null to |done| otherwise it will complain that
        // its argument is not an Error object.
        window.requestAnimationFrame(done.bind(null, null));
      });
  
      test('metachange already set', function() {
        app.themeColor = 'orange';
  
        chrome = new AppChrome(app);
        assert.equal(chrome.element.style.backgroundColor, 'orange');
      });
  
      test('metachange added', function() {
        chrome.handleEvent({
          type: 'mozbrowsermetachange',
          detail: {
            name: 'theme-color',
            type: 'added',
            content: 'orange'
          }
        });
        assert.equal(chrome.element.style.backgroundColor, 'orange');
      });
  
      test('metachange removed', function() {
        chrome.handleEvent({
          type: 'mozbrowsermetachange',
          detail: {
            name: 'theme-color',
            type: 'removed'
          }
        });
        assert.equal(chrome.element.style.backgroundColor, '');
        assert.isTrue(app.element.classList.contains('light'));
      });
  
      test('metachange changed', function() {
        chrome.handleEvent({
          type: 'mozbrowsermetachange',
          detail: {
            name: 'theme-color',
            type: 'changed',
            content: 'red'
          }
        });
        assert.equal(chrome.element.style.backgroundColor, 'red');
      });
  
      test('theme resets on navigation', function() {
        chrome.setThemeColor('orange');
        chrome.handleEvent(mockEvent({type: 'mozbrowserloadstart'}));
        chrome.handleEvent(mockEvent({type: 'mozbrowserloadend'}));
        assert.equal(chrome.element.style.backgroundColor, '');
      });
  
      test('dark color have light icons', function(done) {
        var initiallyLight = app.element.classList.contains('light');
        chrome.setThemeColor('black');
        window.setTimeout(function() {
          chrome.element.dispatchEvent(fadeTransitionEndEvent);
          assert.isTrue(stubRequestAnimationFrame.called);
          assert.isFalse(app.element.classList.contains('light'));
          assert.isFalse(chrome.useLightTheming());
          sinon.assert.callCount(appPublishStub.withArgs('titlestatechanged'),
            initiallyLight ? 1 : 0);
          done();
        }, 0);
      });
  
      test('light color have dark icons', function(done) {
        var initiallyLight = app.element.classList.contains('light');
        chrome.setThemeColor('white');
        window.setTimeout(function() {
          chrome.element.dispatchEvent(fadeTransitionEndEvent);
          assert.isTrue(stubRequestAnimationFrame.called);
          assert.isTrue(app.element.classList.contains('light'));
          assert.isTrue(chrome.useLightTheming());
          sinon.assert.callCount(appPublishStub.withArgs('titlestatechanged'),
            initiallyLight ? 0 : 1);
          done();
        }, 0);
      });
  
      test('popup window will use rear window color theme', function(done) {
        var popup = new PopupWindow(cloneConfig(fakeWebSite));
        var popupChrome = new AppChrome(popup);
        this.sinon.stub(popup, 'getBottomMostWindow').returns(app);
        chrome.setThemeColor('black');
        popupChrome.setThemeColor('white');
        popup.appChrome = popupChrome;
        app.appChrome = chrome;
        window.setTimeout(function() {
          chrome.element.dispatchEvent(fadeTransitionEndEvent);
          assert.isTrue(stubRequestAnimationFrame.called);
          assert.equal(chrome.useLightTheming(), popupChrome.useLightTheming());
          assert.equal(app.themeColor, 'black');
          assert.equal(popup.themeColor, 'black');
          sinon.assert.calledOnce(appPublishStub.withArgs('titlestatechanged'));
          // End popup rAF look so it doesn't interfere with other tests
          popupChrome.element.dispatchEvent(fadeTransitionEndEvent);
          done();
        }, 0);
      });
  
      test('popup window should not be collapsible', function() {
        var popup = new PopupWindow(cloneConfig(fakeAppWithName));
        this.sinon.stub(popup, 'isBrowser').returns(true);
        this.sinon.stub(popup, 'getBottomMostWindow').returns(app);
        var popupChrome = new AppChrome(popup);
        this.sinon.stub(Service, 'request').returns({
          then: function(cb) {
            cb(false);
          }
        });
        popupChrome.handleLocationChange();
        assert.isFalse(popupChrome.pinned);
        assert.isFalse(popup.element.classList.contains('collapsible'));
      });
  
      test('browser scrollable background is black', function() {
        assert.equal(chrome.scrollable.style.backgroundColor, '');
        chrome.setThemeColor('black');
        assert.equal(chrome.scrollable.style.backgroundColor, 'black');
      });
  
      test('should stop requesting frames when transition ends', function(done) {
        chrome.setThemeColor('white');
        chrome.element.dispatchEvent(fadeTransitionEndEvent);
        window.setTimeout(function() {
          sinon.assert.calledOnce(stubRequestAnimationFrame);
          done();
        }, 0);
      });
  
      test('should ignore unrelated transition events', function(done) {
        chrome.setThemeColor('white');
        window.setTimeout(function() {
          var previousCallCount = stubRequestAnimationFrame.callCount;
          var transformEvent = new CustomEvent('transitionend');
          transformEvent.propertyName = 'transform';
          chrome.element.dispatchEvent(transformEvent);
          window.setTimeout(function() {
            assert.isTrue(stubRequestAnimationFrame.callCount > previousCallCount,
                         'requestAnimationFrame continues to be called after ' +
                         'an unrelated transition event');
            done();
          }, 0);
        }, 0);
      });
  
      test('homescreen scrollable background is unset', function() {
        app.isHomescreen = true;
        var chrome = new AppChrome(app);
  
        assert.equal(chrome.scrollable.style.backgroundColor, '');
        chrome.setThemeColor('black');
        assert.equal(chrome.scrollable.style.backgroundColor, '');
      });
  
      test('does not set for private windows', function() {
        app = new AppWindow(cloneConfig(fakeWebSite));
        this.sinon.stub(app, 'isPrivateBrowser').returns(true);
        chrome = new AppChrome(app);
        assert.equal(chrome.scrollable.style.backgroundColor, '');
        chrome.setThemeColor('black');
        assert.equal(chrome.scrollable.style.backgroundColor, 'rgb(57, 46, 84)');
      });
    });
  
    suite('Search request', function() {
      var stubDispatch;
  
      setup(function() {
        stubDispatch = this.sinon.stub(window, 'dispatchEvent');
      });
  
      test('When screen is unlocked, dispatch the request.', function() {
        MockService.mockQueryWith('locked', false);
        this.sinon.stub(chrome, 'isMaximized').returns(true);
        chrome.handleEvent(mockEvent({ type: 'click', target: chrome.title }));
        assert.isTrue(stubDispatch.called);
      });
  
      test('When a contextmenu is shown, do not dispatch.', function() {
        MockService.mockQueryWith('locked', false);
        this.sinon.stub(chrome, 'isMaximized').returns(true);
        this.sinon.stub(chrome.app.contextmenu, 'isShown').returns(true);
        chrome.handleEvent(mockEvent({ type: 'click', target: chrome.title }));
        assert.isFalse(stubDispatch.called);
      });
  
      test('When screen is locked, do not dispatch the event.', function() {
        MockService.mockQueryWith('locked', true);
        this.sinon.stub(chrome, 'isMaximized').returns(true);
        chrome.handleEvent(mockEvent({ type: 'click', target: chrome.title }));
        assert.isFalse(stubDispatch.called);
      });
    });
  
    suite('titlestatechanged', function() {
      test('scroll event - active app', function() {
        var app = new AppWindow(fakeAppMaximized);
        var chrome = new AppChrome(app);
        var appPublishStub = this.sinon.stub(app, 'publish');
        this.sinon.stub(app, 'isActive').returns(true);
  
        chrome.handleEvent(mockEvent({ type: 'scroll' }));
        assert.isTrue(appPublishStub.called);
        assert.isTrue(appPublishStub.calledWith('titlestatechanged'));
      });
  
      test('scroll event - inactive app', function() {
        var app = new AppWindow(fakeAppMaximized);
        var chrome = new AppChrome(app);
        var appPublishStub = this.sinon.stub(app, 'publish');
        this.sinon.stub(app, 'isActive').returns(false);
  
        chrome.handleEvent(mockEvent({ type: 'scroll' }));
        assert.isTrue(appPublishStub.notCalled);
      });
  
      test('set transparent color', function() {
        var appPublishStub = this.sinon.stub(app, 'publish');
  
        chrome.setThemeColor('transparent');
        assert.isTrue(appPublishStub.called);
        assert.isTrue(appPublishStub.calledWith('titlestatechanged'));
      });
  
      test('unset color', function() {
        var appPublishStub = this.sinon.stub(app, 'publish');
  
        chrome.setThemeColor('');
        assert.isTrue(appPublishStub.called);
        assert.isTrue(appPublishStub.calledWith('titlestatechanged'));
      });
    });
  
    suite('reConfig', function() {
      test('removes the search-app class if not the search app', function() {
        var app = new AppWindow(fakeSearchApp);
        var chrome = new AppChrome(app);
        assert.isTrue(chrome.app.element.classList.contains('search-app'));
        chrome.app.config.manifest = null;
        chrome.reConfig();
        assert.isFalse(chrome.app.element.classList.contains('search-app'));
      });
  
      test('sets the site icon', function() {
        var app = new AppWindow(fakeSearchApp);
        var chrome = new AppChrome(app);
        assert.isTrue(chrome.setSiteIcon.called);
      });
    });
  
    suite('transition events', function() {
      var app, chrome;
      setup(function() {
        app = new AppWindow(fakeAppMaximized);
        chrome = new AppChrome(app);
        this.sinon.stub(app, 'publish');
        this.sinon.stub(app, 'isActive').returns(true);
        this.sinon.stub(chrome, 'isMaximized').returns(false);
  
        for (var i = 0; i < 10; i++) {
          chrome.handleEvent(mockEvent({ type: 'scroll' }));
        }
      });
  
      test('should not publish when a control transition ends', function() {
        // Can not fire transitionend events on disabled buttons.
        chrome.reloadButton.disabled = false;
        chrome.reloadButton.dispatchEvent(new CustomEvent('transitionend'));
        sinon.assert.notCalled(app.publish.withArgs('chromecollapsed'));
      });
  
      test('should publish when the element transition ends', function() {
        chrome.element.dispatchEvent(new CustomEvent('transitionend'));
        sinon.assert.calledOnce(app.publish.withArgs('chromecollapsed'));
      });
  
      test('publishes chromeexpanded when transition ends', function() {
        chrome.isMaximized.returns(true);
        chrome.element.dispatchEvent(new CustomEvent('transitionend'));
        sinon.assert.calledOnce(app.publish.withArgs('chromeexpanded'));
      });
  
      test('should only publish once', function() {
        chrome.element.dispatchEvent(new CustomEvent('transitionend'));
        this.sinon.clock.tick(250);
        sinon.assert.calledOnce(app.publish.withArgs('chromecollapsed'));
      });
    });
  
    suite('Pinning the web', function() {
      setup(function() {
        [chrome.siteIcon, chrome.pinScrim].forEach(function(element) {
          this.sinon.stub(element, 'addEventListener');
          this.sinon.stub(element, 'removeEventListener');
        }.bind(this));
      });
  
      // Skip test since we are disabling pinning door hanger in 2.5
      // See https://bugzilla.mozilla.org/show_bug.cgi?id=1207710
      // test('adds listeners when the setting is enabled', function() {
      //   MockSettingsListener.mTriggerCallback(PINNING_PREF, true);
      //   assert.isTrue(chrome.siteIcon.addEventListener.calledWith('click'));
      //   assert.isTrue(chrome.pinScrim.addEventListener.calledWith('click'));
      // });
  
      // Skip test since we are disabling pinning door hanger in 2.5
      // See https://bugzilla.mozilla.org/show_bug.cgi?id=1207710
      // test('removes listeners when the setting is enabled', function() {
      //   MockSettingsListener.mTriggerCallback(PINNING_PREF, false);
      //   assert.isFalse(chrome.siteIcon.addEventListener.calledWith('click'));
      //   assert.isFalse(chrome.pinScrim.addEventListener.calledWith('click'));
      // });
    });
  
    suite('setSiteIcon', function() {
      var fakeIconURI = 'data://someimage';
      var getIconPromise;
      var combinedChrome;
  
      setup(function() {
        var app = new AppWindow(cloneConfig(fakeWebSite));
        combinedChrome = new AppChrome(app);
        combinedChrome.setSiteIcon.restore();
        getIconPromise = new MockPromise();
        this.sinon.stub(combinedChrome.app, 'getSiteIconUrl')
                       .returns(getIconPromise);
      });
  
      test('asks app for url when no argument is provided', function() {
        assert.ok(combinedChrome.useCombinedChrome());
        combinedChrome.setSiteIcon();
        getIconPromise.mFulfillToValue({originalUrl: fakeIconURI, blob: {}});
        assert.equal(combinedChrome._currentIconUrl, fakeIconURI);
      });
  
      test('failure to get icon', function() {
        // set a default
        combinedChrome.siteIcon.style.backgroundImage = `url(${fakeIconURI})`;
  
        combinedChrome.setSiteIcon();
        getIconPromise.mRejectToError();
  
        assert.equal(combinedChrome.siteIcon.style.backgroundImage, '');
      });
  
      test('has no effect for private browsers', function() {
        this.sinon.stub(combinedChrome.app, 'isPrivateBrowser').returns(true);
        var siteIcon = combinedChrome.siteIcon;
        var origSiteIcon = siteIcon && siteIcon.style.backgroundImage;
        var origClassName = siteIcon && siteIcon.className;
  
        combinedChrome.setSiteIcon(fakeIconURI);
  
        var newSiteIcon = siteIcon && siteIcon.style.backgroundImage;
        var newClassName = siteIcon && siteIcon.className;
  
        assert.equal(origSiteIcon, newSiteIcon);
        assert.equal(origClassName, newClassName);
        assert.ok(!combinedChrome.app.getSiteIconUrl.called);
      });
  
      // test('click and pref disabled', function() {
      //   MockSettingsListener.mTriggerCallback(PINNING_PREF, false);
      //   this.sinon.stub(combinedChrome, 'setPinDialogCard');
      //   this.sinon.stub(combinedChrome.app, 'isBrowser').returns(true);
      //   this.sinon.stub(combinedChrome, 'isMaximized').returns(true);
      //   combinedChrome.siteIcon.dispatchEvent(new CustomEvent('click'));
      //   assert.isFalse(combinedChrome.setPinDialogCard.called);
      // });
  
      // test('click and pref enabled', function() {
      //   MockSettingsListener.mTriggerCallback(PINNING_PREF, true);
      //   this.sinon.stub(combinedChrome, 'setPinDialogCard');
      //   this.sinon.stub(combinedChrome.app, 'isBrowser').returns(true);
      //   this.sinon.stub(combinedChrome, 'isMaximized').returns(true);
      //   combinedChrome.siteIcon.dispatchEvent(new CustomEvent('click'));
      //   assert.isTrue(combinedChrome.setPinDialogCard.called);
      // });
  
      test('click, but no browser', function() {
        MockSettingsListener.mTriggerCallback(PINNING_PREF, true);
        this.sinon.stub(combinedChrome, 'setPinDialogCard');
        this.sinon.stub(combinedChrome.app, 'isBrowser').returns(false);
        this.sinon.stub(combinedChrome, 'isMaximized').returns(true);
        combinedChrome.siteIcon.dispatchEvent(new CustomEvent('click'));
        assert.isFalse(combinedChrome.setPinDialogCard.called);
      });
  
      test('click, but loading', function() {
        MockSettingsListener.mTriggerCallback(PINNING_PREF, true);
        this.sinon.stub(combinedChrome, 'setPinDialogCard');
        this.sinon.stub(combinedChrome.app, 'isBrowser').returns(true);
        this.sinon.stub(combinedChrome, 'isMaximized').returns(true);
        combinedChrome.app.loading = true;
        combinedChrome.siteIcon.dispatchEvent(new CustomEvent('click'));
        assert.isFalse(combinedChrome.setPinDialogCard.called);
        combinedChrome.app.loading = false;
      });
  
      test('click, but not maximized', function() {
        MockSettingsListener.mTriggerCallback(PINNING_PREF, true);
        this.sinon.stub(combinedChrome, 'setPinDialogCard');
        this.sinon.stub(combinedChrome.app, 'isBrowser').returns(true);
        this.sinon.stub(combinedChrome, 'isMaximized').returns(false);
        combinedChrome.siteIcon.dispatchEvent(new CustomEvent('click'));
        assert.isFalse(combinedChrome.setPinDialogCard.called);
      });
    });
  
    suite('Pin page', function() {
      var chrome, requestStub;
  
      setup(function() {
        var app = new AppWindow(cloneConfig(fakeWebSite));
        chrome = new AppChrome(app);
        requestStub = this.sinon.stub(Service, 'request').returns(
          new Promise(function() {}));
      });
  
      teardown(function() {
        requestStub.restore();
      });
  
      test('Pin page', function() {
        chrome.pinPage();
        assert.isTrue(Service.request.calledWith('Places:setPinned',
          fakeWebSite.url));
      });
    });
  
    suite('Unpin page', function() {
      var chrome, requestStub;
  
      setup(function() {
        var app = new AppWindow(cloneConfig(fakeWebSite));
        chrome = new AppChrome(app);
        requestStub = this.sinon.stub(Service, 'request').returns(
          new Promise(function() {}));
      });
  
      teardown(function() {
        requestStub.restore();
      });
  
      test('Unpin page', function() {
        chrome.unpinPage();
        assert.isTrue(Service.request.calledWith('Places:setPinned',
          fakeWebSite.url));
      });
    });
  
    suite('getSiteUrl', function() {
      test('no webManifest', function() {
        app = new AppWindow(cloneConfig(fakeWebSite));
        assert.ok(chrome.getSiteUrl(), 'http://google.com/index.html');
      });
      test('webManifest', function() {
        app = new AppWindow(cloneConfig(fakeWebApp));
        assert.ok(chrome.getSiteUrl(), 'http://example.com/index.html');
      });
      test('webManifest with start_url', function() {
        var config = cloneConfig(fakeWebApp);
        config.webManifest.start_url = '/start.html';
        config.url = 'http://example.com/some/path/foo.html';
        app = new AppWindow(config);
        assert.ok(chrome.getSiteUrl(), 'http://example.com/start.html');
      });
    });
  
    suite('Pin site without manifest', function() {
      setup(function() {
        this.sinon.stub(chrome, 'getSiteUrl').
          returns('http://google.com/');
        this.sinon.stub(IconsHelper, 'getIcon').
          returns(Promise.resolve('http://google.com/favicon.ico'));
        this.sinon.stub(BookmarksDatabase, 'put', () => {
          chrome.handleEvent({
            type: 'pins-scopechange',
            detail: {
              type: 'added',
              url: 'http://google.com/index.html',
              name: 'google.com'
            }
          });
          return Promise.resolve();
        });
        this.sinon.stub(chrome.systemBanner, 'show');
        this.sinon.spy(chrome, 'pin');
        this.sinon.stub(chrome.app, 'inScope').returns(true);
        this.sinon.stub(chrome.app, 'matchesOriginAndName').returns(true);
      });
  
      test('Browser chrome collapsed and bookmark saved', function() {
        chrome.element.classList.add('maximized');
        chrome.pinDialog.classList.remove('hidden');
        chrome.handleEvent(
          mockEvent({ type: 'click', target: chrome.pinButton }));
        assert.isTrue(IconsHelper.getIcon.called);
        process.nextTick(function() {
          assert.isTrue(BookmarksDatabase.put.calledWithMatch({
            type: 'url',
            iconable: false,
            frecency: 1,
            pinned: true,
            pinnedFrom: 'http://google.com/index.html',
            id: 'http://google.com/',
            name: 'google.com',
            url: 'http://google.com/',
            icon: 'http://google.com/favicon.ico'
          }, 'http://google.com/'));
          assert(chrome.systemBanner.show.called);
        });
      });
    });
  
    suite('Pin site with manifest', function() {
      var chrome;
      var putBookmarkPromise;
      var getIconPromise;
  
      setup(function() {
        var app = new AppWindow(cloneConfig(fakeWebApp));
        this.sinon.stub(app, 'inScope').returns(true);
        chrome = new AppChrome(app);
        this.sinon.stub(chrome, 'getSiteUrl').
          returns('http://example.com/');
        getIconPromise = new MockPromise();
        this.sinon.stub(IconsHelper, 'getIcon')
          .returns(getIconPromise);
        putBookmarkPromise = new MockPromise();
        this.sinon.stub(BookmarksDatabase, 'put')
          .returns(putBookmarkPromise);
        this.sinon.stub(chrome.systemBanner, 'show');
      });
  
      test('Browser chrome collapsed and bookmark saved no scope', function() {
        chrome.element.classList.add('maximized');
        chrome.pinDialog.classList.remove('hidden');
        this.sinon.stub(chrome.app, 'matchesOriginAndName').returns(true);
  
        chrome.handleEvent(mockEvent({
          type: 'click', target: chrome.pinButton
        }));
  
        getIconPromise.mFulfillToValue('http://example.com/favicon.ico');
        putBookmarkPromise.mFulfillToValue();
  
        chrome.handleEvent(mockEvent({
          type: 'pins-scopechange',
          detail: {
            type: 'added',
            url: 'http://example.com/index.html',
            name: 'Example'
          }
        }));
  
        assert.isTrue(IconsHelper.getIcon.called);
        assert.isTrue(BookmarksDatabase.put.calledWithMatch({
          type: 'url',
          iconable: false,
          frecency: 1,
          pinned: true,
          pinnedFrom: 'http://example.com/index.html',
          id: 'http://example.com/',
          url: 'http://example.com/',
          webManifestUrl: 'http://example.com/manifest.webmanifest',
          webManifest: { name: 'Example Web App', short_name: 'Example' },
          name: 'Example',
          icon: 'http://example.com/favicon.ico'
        }, 'http://example.com/'));
        assert(chrome.systemBanner.show.called);
      });
    });
  
    suite('Unpin site', function() {
      var removeBookmarkPromise;
  
      setup(function() {
        removeBookmarkPromise = new MockPromise();
        this.sinon.stub(BookmarksDatabase, 'remove')
          .returns(removeBookmarkPromise);
  
        this.sinon.stub(chrome, 'getSiteUrl').returns('http://example.com/');
        this.sinon.stub(chrome.systemBanner, 'show');
        this.sinon.stub(chrome.app, 'inScope').returns(true);
        chrome.pinned = true;
      });
  
      test('Dialog reflects pinned state', function() {
        this.sinon.stub(chrome, 'unpinSite');
        chrome.setPinDialogCard();
        chrome.handleEvent(
          mockEvent({ type: 'click', target: chrome.pinButton }));
        assert.isTrue(chrome.unpinSite.calledOnce);
      });
      test('Site is removed and unpinned', function() {
        chrome.unpinSite();
        removeBookmarkPromise.mFulfillToValue();
        chrome.handleEvent({
          type: 'pins-scopechange',
          detail: {
            url: 'http://example.com',
            name: 'Example',
            action: 'remove'
          }
        });
        assert.isTrue(BookmarksDatabase.remove
                      .calledWithMatch('http://example.com'));
        assert.isTrue(chrome.systemBanner.show.calledOnce);
      });
    });
  
    suite('handleBookmarksScopeChange', function() {
      var evt, isInScope, matches;
  
      setup(function() {
        evt = {
          detail: {
            action: 'add',
            url: 'http://example.com/bla/index.html',
            scope: '/bla',
            name: 'Example'
          }
        };
        var app = new AppWindow(cloneConfig(fakeWebApp));
        chrome = new AppChrome(app);
        this.sinon.stub(chrome.app, 'inScope', function() {
          return isInScope;
        });
        this.sinon.stub(chrome.app, 'matchesOriginAndName', function() {
          return matches;
        });
        this.sinon.stub(chrome, 'pin');
        this.sinon.stub(chrome, 'unpin');
      });
  
      test('does nothing if scope provided and not in the scope', function() {
        isInScope = false;
        chrome.handleBookmarksScopeChange(evt);
        assert(chrome.pin.notCalled);
      });
  
      test('no scope and does not match origin and name', function() {
        matches = false;
        evt.detail.scope = false;
        chrome.handleBookmarksScopeChange(evt);
        assert(chrome.pin.notCalled);
      });
  
      test('pins when scope added and is inScope', function() {
        isInScope = true;
        chrome.handleBookmarksScopeChange(evt);
        assert(chrome.pin.called);
      });
  
      test('pins when no inScope but matches origin and name', function() {
        matches = true;
        evt.detail.scope = false;
        chrome.handleBookmarksScopeChange(evt);
        assert(chrome.pin.called);
      });
  
      test('unpins when removed and inScope', function() {
        isInScope = true;
        evt.detail.action = 'remove';
        chrome.handleBookmarksScopeChange(evt);
        assert(chrome.unpin.called);
      });
    });
  
    suite('Default icon', function() {
      var chrome;
      var getIconPromise;
  
      setup(function() {
        var app = new AppWindow(cloneConfig(fakeWebSite));
        chrome = new AppChrome(app);
        getIconPromise = new MockPromise();
        this.sinon.stub(chrome.app, 'getSiteIconUrl').returns(getIconPromise);
  
        chrome.app.config.url = 'http://origin1/';
        chrome.handleEvent(mockEvent({ type: '_locationchange' }));
        chrome.setSiteIcon.reset();
      });
  
      test('Icon is not set to default when same origin', function() {
        chrome.handleEvent(mockEvent({ type: '_locationchange' }));
  
        assert.isFalse(chrome.setSiteIcon.called);
      });
  
      test('Icon is set to default when same origin', function() {
        chrome.app.config.url = 'http://origin2/';
        chrome.handleEvent({ type: '_locationchange' });
  
        assert.isTrue(chrome.setSiteIcon.calledOnce);
        assert.equal(0, chrome.setSiteIcon.getCall(0).args.length);
      });
    });
  });
  