'use strict';
/* global AppWindow, PopupWindow, ActivityWindow, SettingsListener, Service,
AttentionWindow, MozActivity, GlobalOverlayWindow, TrustedWindow */
(function(exports) {
var ENABLE_IN_APP_SHEET = false;
SettingsListener.observe('in-app-sheet.enabled', false, function(value) {
ENABLE_IN_APP_SHEET = value;
});
/**
* ChildWindowFactory is a submodule of AppWindow,
* its responsbility is to:
*
* (1) deal with window.open request
* from mozbrowser iframe to open a proper new window.
* (2) deal with launchactivity request to open activity window.
*
* When an app is opening a page within same origin via window.open,
* the generated new app window would be linked to its caller.
* You could refer to the opener via this.previousWindow
* and refer the openee via this.nextWindow.
*
* On the other hand, if "dialog" feature is specified in
* window.open, we will open a front window, which is an instance of
* PopupWindow, on the caller and set it as the rear window of the callee.
* You could access the dialog's opener via this.rearWindow
* and access the openee via this.frontWindow in the opener.
*
* At most an appWindow instance could have one front window and
* one next window.
*
* @param {AppWindow} app The ordering window of this factory.
*/
var ChildWindowFactory = function ChildWindowFactory(app) {
this.app = app;
this.app.element.addEventListener('mozbrowseropenwindow', this);
this.app.element.addEventListener('_launchactivity',
this.createActivityWindow.bind(this));
this.app.element.addEventListener('_launchtrusted',
this.createTrustedWindow.bind(this));
};
ChildWindowFactory.prototype.handleEvent =
function cwf_handleEvent(evt) {
this.app.debug('[cwf] handling ' + evt.type);
// Prevent Gecko's default handler from opening the window.
evt.preventDefault();
// Handle event from child window.
if (evt.detail && evt.detail.instanceID &&
evt.detail.instanceID !== this.app.instanceID) {
if (this['_handle_child_' + evt.type]) {
this['_handle_child_' + evt.type](evt);
}
return;
}
// Skip to wrapperWindowFactory.
if (this.isHomescreen) {
// XXX: Launch wrapper window here.
return;
}
// should never be part of the app
// except while FTU is running: windows must be closable & parented by FTU
if (evt.detail.name == '_blank' &&
!Service.query('isFtuRunning') &&
evt.detail.features !== 'attention' &&
evt.detail.features !== 'global-clickthrough-overlay') {
this.createNewWindow(evt);
evt.stopPropagation();
return;
}
// Check if this is a call to open the url in an known app.
if (evt.detail.isApp) {
this.app.publish('openwindow',
{ manifestURL: evt.detail.name,
url: evt.detail.url,
timestamp: Date.now() });
evt.stopPropagation();
return;
}
var caught = false;
switch (evt.detail.features) {
case 'dialog':
// Only open popupWindow by app/http/https prefix
if (/^(app|http|https):\/\//i.test(evt.detail.url)) {
caught = this.createPopupWindow(evt);
} else {
caught = this.launchActivity(evt);
}
break;
case 'alwaysLowered':
if (this.app.hasPermission('open-hidden-window')) {
caught = this.createPopupWindow(evt);
}
break;
case 'attention':
// Open attentionWindow
if (!this.createAttentionWindow(evt)) {
this.createPopupWindow(evt);
}
break;
case 'global-clickthrough-overlay':
// Open GlobalOverlayWindow.
this.createGlobalOverlayWindow(evt);
break;
case 'mozhaidasheet':
// This feature is for internal usage only
// before we have final API to open an inner sheet.
caught = this.createChildWindow(evt);
break;
default:
if (ENABLE_IN_APP_SHEET) {
caught = this.createChildWindow(evt);
} else {
caught = this.createPopupWindow(evt);
}
break;
}
if (caught) {
evt.stopPropagation();
}
};
ChildWindowFactory.prototype.createPopupWindow = function(evt) {
if (this.app.frontWindow &&
(this.app.frontWindow.isTransitioning() ||
this.app.frontWindow.isActive())) {
return false;
}
var stayBackground = evt.detail.features.indexOf('alwaysLowered') >= 0;
var configObject = {
url: evt.detail.url,
name: this.app.name,
iframe: evt.detail.frameElement,
origin: this.app.origin,
rearWindow: this.app,
stayBackground: stayBackground
};
var childWindow = new PopupWindow(configObject);
if (!stayBackground) {
childWindow.element.addEventListener('_opened', this);
childWindow.element.addEventListener('_closing', this);
}
childWindow.open();
return true;
};
ChildWindowFactory.prototype._sameOrigin = function(url1, url2) {
var a = url1.split('/');
var b = url2.split('/');
return (a[0] === b[0] && a[2] === b[2]);
};
ChildWindowFactory.prototype.createNewWindow = function(evt) {
if (!this.app.isActive() || this.app.isTransitioning()) {
return false;
}
var parentAllowFullscreenAttr =
evt.target.getAttribute('mozallowfullscreen');
var iframe = evt.detail.frameElement;
// This new window should be allowed to go full screen.
if (parentAllowFullscreenAttr) {
iframe.setAttribute('mozallowfullscreen', parentAllowFullscreenAttr);
}
var configObject = {
url: evt.detail.url,
name: evt.detail.name,
iframe: iframe,
isPrivate: this.app.isPrivateBrowser()
};
window.dispatchEvent(new CustomEvent('openwindow', {
detail: configObject
}));
return true;
};
ChildWindowFactory.prototype.createChildWindow = function(evt) {
if (!this.app.isActive() || this.app.isTransitioning()) {
return false;
}
var configObject = {
url: evt.detail.url,
name: this.app.name,
iframe: evt.detail.frameElement,
origin: this.app.origin,
isPrivate: this.app.isPrivateBrowser()
};
if (this._sameOrigin(this.app.origin, evt.detail.url)) {
configObject.manifestURL = this.app.manifestURL;
configObject.previousWindow = this.app;
} else {
configObject.name = '';
configObject.origin = evt.detail.url;
}
var childWindow = new AppWindow(configObject);
childWindow.requestOpen();
return true;
};
ChildWindowFactory.prototype.createAttentionWindow = function(evt) {
if (!this.app || !this.app.hasPermission('attention')) {
console.error('Cannot create attention window. ' +
'Invalid of underprivileged app');
return false;
}
// Canceling any full screen web content
if (document.mozFullScreen) {
document.mozCancelFullScreen();
}
var attentionFrame = evt.detail.frameElement;
var attention = new AttentionWindow({
iframe: attentionFrame,
url: evt.detail.url,
name: evt.detail.name,
manifestURL: this.app.manifestURL,
origin: this.app.origin,
parentWindow: this.app
});
this.app.attentionWindow = attention;
attention.requestOpen();
return true;
};
ChildWindowFactory.prototype.createGlobalOverlayWindow = function(evt) {
if (!this.app || !this.app.hasPermission('global-clickthrough-overlay')) {
console.error('Cannot create global overlay window. ' +
'Invalid of underprivileged app');
return false;
}
var overlayFrame = evt.detail.frameElement;
var overlay = new GlobalOverlayWindow({
iframe: overlayFrame,
url: evt.detail.url,
name: evt.detail.name,
manifestURL: this.app.manifestURL,
origin: this.app.origin,
parentWindow: this.app
});
this.app.overlayWindow = overlay;
overlay.requestOpen();
return true;
};
ChildWindowFactory.prototype._handle_child__opened = function(evt) {
// Do nothing if we are not active or we are being killing.
if (!this.app.isVisible() || this.app._killed) {
return;
}
this.app.setVisible(false, true);
};
ChildWindowFactory.prototype._handle_child__closing = function(evt) {
// Do nothing if we are not active or we are being killing.
if (!this.app.isVisible() || this.app._killed) {
return;
}
this.app.setOrientation();
if (Service.query('getTopMostWindow').getBottomMostWindow() ===
this.app.getBottomMostWindow()) {
this.app.setVisible(true, true);
}
// An activity handled by ActivityWindow is always an inline activity.
// All window activities are handled by AppWindow. All inline
// activities have a rearWindow. Once this inline activity is killed,
// make rear window visible to screen reader and the focus should be
// transfered to its rear window.
evt.detail.rearWindow._setVisibleForScreenReader(true);
evt.detail.rearWindow.focus();
};
ChildWindowFactory.prototype.createActivityWindow = function(evt) {
var configuration = evt.detail;
var top = this.app.getTopMostWindow();
// If the lastActivity is the same as launch request, we don't need to
// create another activity.
if (top.manifestURL == configuration.manifestURL &&
top.url == configuration.url) {
return;
}
// We need to blur the opener before opening the new one.
top.setNFCFocus(false);
var activity = new ActivityWindow(configuration, top);
activity.element.addEventListener('_closing', this);
activity.element.addEventListener('_opened', this);
activity.open();
// Make topmost window browser element invisibile to screen reader.
top._setVisibleForScreenReader(false);
};
ChildWindowFactory.prototype.createTrustedWindow = function(evt) {
var configuration = evt.detail;
var top = this.app.getTopMostWindow();
var trusted = new TrustedWindow(configuration, top);
trusted.element.addEventListener('_opened', this);
trusted.element.addEventListener('_closing', this);
trusted.open();
};
ChildWindowFactory.prototype.launchActivity = function(evt) {
var activity = new MozActivity({
name: 'view',
data: {
type: 'url',
url: evt.detail.url
}
});
activity.onerror = function() {
this.app.debug(
'view activity error:' + activity.error.name + evt.detail.url);
};
return true;
};
exports.ChildWindowFactory = ChildWindowFactory;
}(window));