'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));