Source: js/wrapper_factory.js

'use strict';
/*global applications, Service, AppWindow */

(function(window) {
  /**
   * WrapperFactory deals with opening a window for bookmarked web page.
   * Usually the request is coming from homescreen app.
   * @module WrapperFactory
   */
  var WrapperFactory = {
    name: 'WrapperFactory',
    start: function() {
      window.addEventListener('mozbrowseropenwindow', this, true);
      Service.registerState('isLaunchingWindow', this);
    },
    stop: function() {
      window.removeEventListener('mozbrowseropenwindow', this, true);
    },

    isLaunchingWindow: function() {
      return !!this._launchingApp;
    },

    forgetLastLaunchingWindow: function() {
      if (this._launchingApp && this._launchingApp.element) {
        this._launchingApp.element.removeEventListener('_opened', this);
        this._launchingApp.element.removeEventListener('_terminated', this);
      }
      this._launchingApp = null;
    },

    handleEvent: function wf_handleEvent(evt) {
      if (evt.type === '_opened' || evt.type === '_terminated') {
        if (this._launchingApp === evt.detail) {
          this.forgetLastLaunchingWindow();
        }
        return;
      }
      var detail = evt.detail;

      // If it's a normal window.open request, ignore.
      if (typeof detail.features !== 'string') {
        return;
      }

      // Turn ',' separated 'key=value' string into object for easy access
      var features = detail.features
        .split(',')
        .reduce(function(acc, feature) {
          feature = feature
            .split('=')
            .map(function(featureElem) { return featureElem.trim(); });
          if (feature.length !== 2) {
            return acc;
          }

          acc[decodeURIComponent(feature[0])] = decodeURIComponent(feature[1]);
          return acc;
        }, {});

      // Handles only call to window.open with `remote=true` feature.
      if (!('remote' in features) || features.remote !== 'true') {
        return;
      }

      var callerOrigin;

      // Examine permission
      // XXX: Ask app window about this.
      // We can skip the permission check for events against the system window.
      if (evt.target !== window) {
        var callerIframe = evt.target;
        var manifestURL = callerIframe.getAttribute('mozapp');

        var callerApp = applications.getByManifestURL(manifestURL);
        if (!this.hasPermission(callerApp, 'open-remote-window')) {
          return;
        }

        callerOrigin = callerApp.origin;
      } else {
        callerOrigin = location.origin;
      }

      // So, we are going to open a remote window.
      evt.stopImmediatePropagation();

      var name = detail.name;
      var url = detail.url;
      var app;

      // Use fake origin for named windows in order to be able to reuse them,
      // otherwise always open a new window for '_blank'.
      var origin = null;
      if (name == '_blank') {

        // If we already have a browser and we receive an open request,
        // display it in the current browser frame.
        var activeApp = Service.query('AppWindowManager.getActiveWindow');
        if (activeApp && (activeApp.isBrowser() || activeApp.isSearch())) {
          activeApp.navigate(url);
          return;
        }

        origin = url;
        app = Service.query('AppWindowManager.getApp', origin);
        // Just bring on top if a wrapper window is
        // already running with this url.
        if (app && app.windowName == '_blank') {
          this.publish('launchapp', { origin: origin });
          return;
        }
      } else {
        origin = 'window:' + name + ',source:' + callerOrigin;
        app = Service.query('AppWindowManager.getApp', origin);
        if (app && app.windowName === name) {
          if (app.iframe.src === url) {
            // If the url is already loaded, just display the app
            this.publish('launchapp', { origin: origin });
            return;
          } else {
            // Wrapper context shouldn't be shared between two apps -> killing
            this.publish('killapp', { origin: origin });
          }
        }
      }

      // TODO: Put this into browser_config_helper.
      var browser_config = this.generateBrowserConfig(features);

      // If we don't reuse an existing app, open a brand new one
      browser_config.url = url;
      browser_config.origin = origin;
      browser_config.windowName = name;
      if (!browser_config.title) {
        browser_config.title = url;
      }

      if (Service.query('MultiScreenController.enabled')) {
        Service.request('chooseDisplay', browser_config)
          .catch(this.launchWrapper.bind(this, browser_config));
      } else {
        this.launchWrapper(browser_config);
      }
    },

    launchWrapper: function wf_launchWrapper(config) {
      var app = Service.query('AppWindowManager.getApp', config.origin);
      if (!app) {
        config.chrome = {
          scrollable: true
        };
        this.forgetLastLaunchingWindow();
        this.trackLauchingWindow(config);
      } else {
        app.updateName(config.title);
      }

      this.publish('launchapp', { origin: config.origin });
    },

    trackLauchingWindow: function(config) {
      this._launchingApp = new AppWindow(config);
      this._launchingApp.element.addEventListener('_opened', this);
      this._launchingApp.element.addEventListener('_terminated', this);
    },

    hasPermission: function wf_hasPermission(app, permission) {
      var mozPerms = navigator.mozPermissionSettings;
      if (!mozPerms) {
        return false;
      }

      var value = mozPerms.get(permission, app.manifestURL, app.origin, false);

      return (value === 'allow');
    },

    generateBrowserConfig: function wf_generateBrowserConfig(features) {
      var config = {};
      config.title = features.name;
      config.icon = features.icon || '';

      if ('originName' in features) {
        config.originName = features.originName;
        config.originURL = features.originUrl;
      }

      if ('searchName' in features) {
        config.searchName = features.searchName;
        config.searchURL = features.searchUrl;
      }

      if ('remote' in features) {
        config.oop = true;
      }

      return config;
    },

    publish: function wf_publish(event, detail) {
      var evt = new CustomEvent(event, { detail: detail });
      window.dispatchEvent(evt);
    }
  };
  window.WrapperFactory = WrapperFactory;
}(window));