'use strict';
/* global applications, BrowserConfigHelper, AppWindow, Service */
/* jshint nonew: false */
(function(exports) {
/**
* AppWindowFactory handle the launch request from gecko and
* wrap the config with properer parameters.
*
* If gecko is asking us to open a webapp,
* AppWindowFactory would do the instantiation and let
* AppWindowManager to do the following app opening control via
* event <code>launchapp</code>.
*
* If gecko is asking us to open an inline activity page,
* AppWindowFactory would wrap the configuration and sent it to
* AppWindowFactory for it to do instantiation via event
* <code>launchactivity</code>.
*
* 
*
* @class AppWindowFactory
*/
function AppWindowFactory() {
this.preHandleEvent = this.preHandleEvent.bind(this);
}
AppWindowFactory.prototype = {
name: 'AppWindowFactory',
/**
* Indicate whether this class is started or not.
* @access private
* @type {Boolean}
* @memberof AppWindowFactory.prototype
*/
_started: false,
/**
* Register all event handlers.
* @memberof AppWindowFactory.prototype
*/
start: function awf_start() {
if (this._started) {
return;
}
this._started = true;
window.addEventListener('webapps-launch', this.preHandleEvent);
window.addEventListener('webapps-close', this.preHandleEvent);
window.addEventListener('open-app', this.preHandleEvent);
window.addEventListener('openwindow', this.preHandleEvent);
window.addEventListener('appopenwindow', this.preHandleEvent);
window.addEventListener('applicationready', (function appReady(e) {
window.removeEventListener('applicationready', appReady);
this._handlePendingEvents();
}).bind(this));
Service.registerState('isLaunchingWindow', this);
},
/**
* Unregister all event handlers.
* @memberof AppWindowFactory.prototype
*/
stop: function awf_stop() {
if (!this._started) {
return;
}
this._started = false;
window.removeEventListener('webapps-launch', this.preHandleEvent);
window.removeEventListener('webapps-close', this.preHandleEvent);
window.removeEventListener('open-app', this.preHandleEvent);
window.removeEventListener('openwindow', this.preHandleEvent);
window.removeEventListener('appopenwindow', this.preHandleEvent);
},
/**
* Queue events until AppWindowFactory is ready to handle them.
*/
_queueEvents: [],
_queuePendingEvent: function(evt) {
this._queueEvents.push(evt);
},
_handlePendingEvents: function() {
this._queueEvents.forEach((function(evt) {
this.handleEvent(evt);
}).bind(this));
this._queueEvents = [];
},
preHandleEvent: function(evt) {
if (applications.ready) {
this.handleEvent(evt);
} else {
this._queuePendingEvent(evt);
}
},
handleEvent: function awf_handleEvent(evt) {
var detail = evt.detail;
if (evt.type === '_opened' || evt.type === '_terminated') {
if (this._launchingApp === detail) {
this.forgetLastLaunchingWindow();
}
return;
}
if (!detail.url && !detail.manifestURL) {
return;
}
var config = new BrowserConfigHelper(detail);
config.evtType = evt.type;
switch (evt.type) {
case 'openwindow':
case 'appopenwindow':
case 'webapps-launch':
config.timestamp = detail.timestamp;
// TODO: Look up current opened window list,
// and then create a new instance here.
this.launch(config);
break;
case 'open-app':
// System Message Handler API is asking us to open the specific URL
// that handles the pending system message.
// We will launch it in background if it's not handling an activity.
config.isSystemMessage = true;
if (detail.isActivity) {
config.isActivity = true;
if (detail.target.disposition &&
detail.target.disposition == 'inline') {
config.inline = true;
}
}
config.changeURL = !detail.onlyShowApp;
config.stayBackground = !detail.showApp;
if (detail.extra && detail.extra.manifestURL) {
config.parentApp = detail.extra.manifestURL;
}
// TODO: Create activity window instance
// or background app window instance for system message here.
this.launch(config);
break;
case 'webapps-close':
this.publish('killapp', config);
break;
}
},
/**
* Browser Configuration
* @typedef {Object} BrowserConfig
* @property {String} origin the same as appURL.
* @property {String} manifestURL the same as manifestURL.
* @property {Object} manifest the parsed manifest object.
* If the app is not an entry point app,
* the manifest would be the reference of application
* manifest stored in Applications module. But if the app
* is an entry point app, we will do deep clone to
* generate a new object and replace the properties of
* entry point to proper position.
* @property {String} name the name of the app, retrieved from manifest.
* @property {Boolean} oop Indicate it's running out of process or in
* process.
*/
/**
* Launch an app window.
* @param {BrowserConfig} config Generated by BrowserConfigHelper.
* @memberof AppWindowFactory.prototype
*/
launch: function awf_launch(config) {
if (config.url === window.location.href) {
return;
}
if (config.isActivity && config.inline) {
this.publish('launchactivity', config);
return;
}
// The rocketbar currently handles the management of normal search app
// launches. Requests for the 'newtab' page will continue to filter
// through and publish the launchapp event.
if (config.manifest && config.manifest.role === 'search' &&
config.url.indexOf('newtab.html') === -1) {
return;
}
var app = Service.query('getApp', config.origin, config.manifestURL);
if (app) {
if (config.evtType == 'appopenwindow') {
app.browser.element.src = config.url;
}
app.reviveBrowser();
// Always relaunch background app locally
this.publish('launchapp', config);
} else {
var launchApp = () => {
// homescreenWindowManager already listens webapps-launch and
// open-app. We don't need to check if the launched app is homescreen.
this.forgetLastLaunchingWindow();
this.trackLauchingWindow(config);
this.publish('launchapp', config);
};
if (Service.query('MultiScreenController.enabled')) {
Service.request('chooseDisplay', config).catch(launchApp);
} else {
launchApp();
}
}
},
trackLauchingWindow: function(config) {
var app = new AppWindow(config);
if (config.stayBackground) {
return;
}
this._launchingApp = app;
this._launchingApp.element.addEventListener('_opened', this);
this._launchingApp.element.addEventListener('_terminated', this);
},
forgetLastLaunchingWindow: function() {
if (this._launchingApp && this._launchingApp.element) {
this._launchingApp.element.removeEventListener('_opened', this);
this._launchingApp.element.removeEventListener('_terminated', this);
}
this._launchingApp = null;
},
isLaunchingWindow: function() {
return !!this._launchingApp;
},
/**
* Publish a CustomEvent.
* @param {String} event The name of the event.
* @param {Object} detail The data passed when initializing the event.
* @memberof AppWindowFactory.prototype
*/
publish: function awf_publish(event, detail, scope) {
scope = scope || window;
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, true, false, detail);
scope.dispatchEvent(evt);
}
};
exports.AppWindowFactory = AppWindowFactory;
}(window));