Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2156 lines
64 KiB
JavaScript
Executable file
2156 lines
64 KiB
JavaScript
Executable file
/* global AppChrome */
|
|
/* global AudioChannelController */
|
|
/* global BrowserFrame */
|
|
/* global layoutManager */
|
|
/* global ManifestHelper */
|
|
/* global OrientationManager */
|
|
/* global ScreenLayout */
|
|
/* global SettingsCache */
|
|
/* global Service */
|
|
/* global focusManager */
|
|
'use strict';
|
|
|
|
(function(exports) {
|
|
// Turn on this flag to debug all windows.
|
|
var DEBUG = false;
|
|
// Turn on this flag to print all trace in debugging function.
|
|
var TRACE = false;
|
|
var _id = 0;
|
|
|
|
/**
|
|
* AppWindow creates, contains, manages a
|
|
* [mozbrowser](https://developer.mozilla.org/en-US/docs/WebAPI/Browser)
|
|
* iframe. AppWindow is directly managed by AppWindowManager,
|
|
* by call resize(), open(), close() on AppWindow.
|
|
*
|
|
* Basically AppWindow would manipulate all mozbrowser events
|
|
* fired from the mozbrowser iframe by itself and show relevant UI.
|
|
*
|
|
* AppWindow is also the parent class of ActivityWindow and HomescreenWindow.
|
|
* Mostly they do the same thing but is different at some points
|
|
* like the way transitioning.
|
|
*
|
|
* About creating an AppWindow,
|
|
* you need to provide at least the web app/page URL.
|
|
* If you have also provided the manifest,
|
|
* then you would get an AppWindow object which is a web app.
|
|
* Otherwise you would get an AppWindow which is in 'Wrapper' type.
|
|
* The only one different thing between web app and web page is
|
|
* just the manifestURL.
|
|
*
|
|
* ##### Life cycle state machine of an appWindow instance
|
|
* 
|
|
*
|
|
* @example
|
|
* var app = new AppWindow({
|
|
* url: 'http://uitest.gaiamobile.org:8080/index.html',
|
|
* manifestURL: 'http://uitest.gaiamobile.org:8080/manifest.webapp'
|
|
* });
|
|
* app.open();
|
|
*
|
|
* @class AppWindow
|
|
* @mixes BrowserMixin into AppWindow.prototype
|
|
*/
|
|
var AppWindow = function AppWindow(configuration) {
|
|
this.reConfig(configuration);
|
|
this.render();
|
|
/**
|
|
* This is fired when the app window is instantiated.
|
|
* @event AppWindow#appcreated
|
|
*/
|
|
this.publish('created');
|
|
|
|
if (DEBUG || this._DEBUG) {
|
|
this.constructor[this.instanceID] = this;
|
|
}
|
|
|
|
this.launchTime = Date.now();
|
|
focusManager.addUI(this);
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* When this option is set to true,
|
|
* an app would not be removed while it's crashed.
|
|
* And while it's opened next, we will re-render the mozbrowser iframe.
|
|
* @type {Boolean}
|
|
*/
|
|
AppWindow.SUSPENDING_ENABLED = false;
|
|
SettingsCache.observe('app-suspending.enabled', false, function(value) {
|
|
AppWindow.SUSPENDING_ENABLED = !!value;
|
|
});
|
|
|
|
/**
|
|
* Change this if new window has its own styles.
|
|
*
|
|
* @type string
|
|
* @memberof AppWindow
|
|
*/
|
|
AppWindow.prototype.CLASS_LIST = 'appWindow';
|
|
AppWindow.prototype._DEBUG = false;
|
|
|
|
/**
|
|
* Generate instanceID of this instance.
|
|
*/
|
|
AppWindow.prototype.generateID = function() {
|
|
if (!this.instanceID) {
|
|
this.instanceID = this.CLASS_NAME + '_' + _id;
|
|
_id++;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Generate all configurations we need.
|
|
* @param {Object} configuration Initial configuration object
|
|
* Includes manifestURL, manifest, url, origin, name.
|
|
*/
|
|
AppWindow.prototype.reConfig = function aw_reConfig(configuration) {
|
|
// Some modules are querying appWindow.manifestURL or appWindow.origin
|
|
// so we inject all configurations into appWindow first.
|
|
for (var key in configuration) {
|
|
this[key] = configuration[key];
|
|
}
|
|
|
|
this.browser_config = configuration;
|
|
// Store initial configuration in this.config
|
|
this.config = configuration;
|
|
this.config.chrome = (this.manifest && this.manifest.chrome) ?
|
|
this.manifest.chrome :
|
|
this.config.chrome;
|
|
|
|
if (!this.config.chrome) {
|
|
this.config.chrome = {
|
|
scrollable: this.isBrowser(),
|
|
maximized: this.isBrowser()
|
|
};
|
|
} else if (this.config.chrome.navigation) {
|
|
this.config.chrome.scrollable = !this.isFullScreen();
|
|
// This is for backward compatibility with application that
|
|
// requests the |navigation| flag in their manifest.
|
|
this.config.chrome.maximized = true;
|
|
}
|
|
|
|
if (!this.manifest && this.config && this.config.title) {
|
|
this.updateName(this.config.title);
|
|
} else {
|
|
this.name = new ManifestHelper(this.manifest).name;
|
|
}
|
|
|
|
// Get icon splash
|
|
this.getIconForSplash();
|
|
|
|
this.generateID();
|
|
/**
|
|
* The instanceID of the root window of this window.
|
|
* @type {String}
|
|
*/
|
|
this.groupID = this.getRootWindow().instanceID;
|
|
if (this.previousWindow) {
|
|
this.previousWindow.setNextWindow(this);
|
|
} else if (this.rearWindow) {
|
|
this.rearWindow.setFrontWindow(this);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Update the name of this window.
|
|
* @param {String} name The new name.
|
|
*/
|
|
AppWindow.prototype.updateName = function aw_updateName(name) {
|
|
if (this.config && this.config.title) {
|
|
this.config.title = name;
|
|
}
|
|
this.name = name;
|
|
};
|
|
|
|
/**
|
|
* Represent the current pagee visibility state,
|
|
* i.e. what is currently visible. Possible value:
|
|
* 'foreground': setVisible(true)
|
|
* 'background': setVisible(false)
|
|
*
|
|
* Default value is foreground.
|
|
*/
|
|
AppWindow.prototype._visibilityState = 'foreground';
|
|
|
|
/**
|
|
* The rotating degree of current frame.
|
|
*/
|
|
AppWindow.prototype.rotatingDegree = 0;
|
|
|
|
AppWindow.prototype._dump = function aw__dump() {
|
|
console.log('======================');
|
|
try {
|
|
throw new Error('e');
|
|
} catch (e) {
|
|
this.debug(e.stack);
|
|
}
|
|
console.log('======================');
|
|
};
|
|
|
|
/**
|
|
* In order to prevent flashing of unpainted frame
|
|
* during switching from one to another,
|
|
* many event listener & callbacks are employed.
|
|
*
|
|
* When 'cardviewbeforeshow' is received, the screenshotOverlay is shown.
|
|
*
|
|
* When 'cardviewclosed' is received, the screenshotOverlay is hidden.
|
|
*
|
|
* When 'sheetsgesturebegin' is received, the screenshotOverlay is shown.
|
|
*
|
|
* When 'sheetsgestureend' is received the screenshotOverlay is hidden.
|
|
*
|
|
* However, setVisible *never* controls the visibility of the
|
|
* screenshotOverlay. The visibility of it is entirely event driven or can
|
|
* be triggered manually be calling _showScreenshotOverlay and
|
|
* _hideScreenshotOverlay.
|
|
*/
|
|
AppWindow.prototype.setVisible =
|
|
function aw_setVisible(visible) {
|
|
this.debug('set visibility -> ', visible);
|
|
this.setVisibleForScreenReader(visible);
|
|
if (visible) {
|
|
// If this window is not the lockscreen, and the screen is locked,
|
|
// we need to aria-hide the window.
|
|
this._showFrame();
|
|
} else {
|
|
this._hideFrame();
|
|
}
|
|
|
|
if (this.frontWindow) {
|
|
this.frontWindow.setVisible(visible);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Set screen reader visibility.
|
|
* @type {Boolean} A flag indicating if it should be visible to the screen
|
|
* reader.
|
|
*/
|
|
AppWindow.prototype.setVisibleForScreenReader =
|
|
function aw_setVisibleForScreenReader(visible) {
|
|
this.element.setAttribute('aria-hidden', !visible);
|
|
};
|
|
|
|
/**
|
|
* _showFrame turn on the frame visibility.
|
|
* So this shouldn't be invoked by others directly.
|
|
*/
|
|
AppWindow.prototype._showFrame = function aw__showFrame() {
|
|
this.debug('before showing frame');
|
|
|
|
// If we're already showing, do nothing!
|
|
if (!this.browser.element.classList.contains('hidden')) {
|
|
return;
|
|
}
|
|
|
|
this.browser.element.classList.remove('hidden');
|
|
this._setVisible(true);
|
|
|
|
if (this.isHomescreen) {
|
|
return;
|
|
}
|
|
|
|
// Getting a new screenshot to force compositing before
|
|
// removing the screenshot overlay if it exists.
|
|
if (this.screenshotOverlay &&
|
|
this.screenshotOverlay.classList.contains('visible')) {
|
|
this.tryWaitForFullRepaint(this._hideScreenshotOverlay.bind(this));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* _hideFrame will turn off the frame visibility.
|
|
* So this shouldn't be invoked by others directly.
|
|
*/
|
|
AppWindow.prototype._hideFrame = function aw__hideFrame() {
|
|
this.debug('before hiding frame');
|
|
|
|
// If we're already hidden, we have nothing to do!
|
|
if (this.browser.element.classList.contains('hidden')) {
|
|
return;
|
|
}
|
|
|
|
this._setVisible(false);
|
|
this.browser.element.classList.add('hidden');
|
|
};
|
|
|
|
/**
|
|
* An appWindow is active means:
|
|
* 1. Going to be opened.
|
|
* 2. Already opened.
|
|
*
|
|
* Note: The element has active class unless it's at closed state.
|
|
* But a closing instance is not recognized as active.
|
|
*
|
|
* @return {Boolean} The instance is active or not.
|
|
*/
|
|
AppWindow.prototype.isActive = function aw_isActive() {
|
|
if (!this.element) {
|
|
return false;
|
|
}
|
|
|
|
if (this.element.classList.contains('will-become-active')) {
|
|
return true;
|
|
}
|
|
|
|
if (this.transitionController) {
|
|
return (this.transitionController._transitionState == 'opened' ||
|
|
this.transitionController._transitionState == 'opening');
|
|
} else {
|
|
// Before the transition controller is inited
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* TODO: Integrate swipe transition.
|
|
*
|
|
* @return {Boolean} The instance is transitioning or not.
|
|
*/
|
|
AppWindow.prototype.isTransitioning = function aw_isTransitioning() {
|
|
if (this.transitionController) {
|
|
return (this.transitionController._transitionState == 'opening' ||
|
|
this.transitionController._transitionState == 'closing');
|
|
} else {
|
|
// Fallback
|
|
return (this.element.classList.contains('transition-opening') ||
|
|
this.element.classList.contains('transition-closing'));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* If the instance is crashed but not resumed yet,
|
|
* it would be in suspended state.
|
|
*
|
|
* The state would be turned off once we render the browser again.
|
|
* @type {Boolean}
|
|
*/
|
|
AppWindow.prototype.suspended = false;
|
|
|
|
/**
|
|
* Re-render the browser element with the same config.
|
|
*/
|
|
AppWindow.prototype.reviveBrowser = function() {
|
|
if (this.browser) {
|
|
return;
|
|
}
|
|
this.debug(' ...revived!');
|
|
this.browser = new BrowserFrame(this.browser_config);
|
|
this.browserContainer.appendChild(this.browser.element);
|
|
this.iframe = this.browser.element;
|
|
this.launchTime = Date.now();
|
|
this.suspended = false;
|
|
this.element.classList.remove('suspended');
|
|
// Launch as background by default.
|
|
this._setVisible(false);
|
|
this.publish('resumed');
|
|
};
|
|
|
|
/**
|
|
* Remove the browser element and clear the states of the browser.
|
|
* @fires AppWindow#suspended
|
|
*/
|
|
AppWindow.prototype.destroyBrowser = function() {
|
|
if (!this.browser) {
|
|
return;
|
|
}
|
|
this.loading = false;
|
|
this.loaded = false;
|
|
this.suspended = true;
|
|
this.element.classList.add('suspended');
|
|
this.browserContainer.removeChild(this.browser.element);
|
|
this.browser = null;
|
|
this.publish('suspended');
|
|
};
|
|
|
|
/**
|
|
* Kill an instance.
|
|
*
|
|
* 1. If this instance has any living activity window as callee,
|
|
* all of the activity window callee would be killed.
|
|
* 2. If this instance is active, it would request to close.
|
|
*
|
|
* @fires AppWindow#appterminated
|
|
*/
|
|
AppWindow.prototype.kill = function aw_kill(evt) {
|
|
if (this._killed) {
|
|
return;
|
|
}
|
|
|
|
if (!this.isHomescreen) {
|
|
this._killed = true;
|
|
}
|
|
|
|
if (DEBUG || this._DEBUG) {
|
|
this.constructor[this.instanceID] = null;
|
|
}
|
|
|
|
// Remove callee <-> caller reference before we remove the window.
|
|
if (this.callerWindow) {
|
|
this.callerWindow.calleeWindow = null;
|
|
this.callerWindow = null;
|
|
}
|
|
if (this.calleeWindow) {
|
|
this.calleeWindow.callerWindow = null;
|
|
this.calleeWindow = null;
|
|
}
|
|
|
|
// Kill any front window.
|
|
if (this.frontWindow) {
|
|
this.frontWindow.kill();
|
|
this.frontWindow = null;
|
|
}
|
|
|
|
// Kill any next window.
|
|
if (this.nextWindow) {
|
|
this.nextWindow.kill();
|
|
this.nextWindow = null;
|
|
}
|
|
|
|
// Kill any attention window.
|
|
if (this.attentionWindow) {
|
|
this.attentionWindow.kill();
|
|
this.attentionWindow = null;
|
|
}
|
|
|
|
// Remove registered global events
|
|
this.constructor.REGISTERED_GLOBAL_EVENTS.forEach(function iterator(evt) {
|
|
this.debug('removing ' + evt + ' global event handler ...');
|
|
window.removeEventListener(evt, this);
|
|
}, this);
|
|
|
|
// If the app is the currently displayed app, switch to the homescreen
|
|
if (this.isActive() && !this.isHomescreen) {
|
|
|
|
var fallbackTimeout;
|
|
var onClosed = function() {
|
|
clearTimeout(fallbackTimeout);
|
|
this.element.removeEventListener('_closed', onClosed);
|
|
this.destroy();
|
|
this.publish('terminated');
|
|
}.bind(this);
|
|
|
|
this.element.addEventListener('_closed', onClosed);
|
|
fallbackTimeout = setTimeout(onClosed,
|
|
this.transitionController.CLOSING_TRANSITION_TIMEOUT);
|
|
|
|
if (this.previousWindow) {
|
|
this.previousWindow.getBottomMostWindow().open('in-from-left');
|
|
this.close('out-to-right');
|
|
} else {
|
|
this.requestClose();
|
|
}
|
|
} else {
|
|
this.destroy();
|
|
/**
|
|
* Fired when the instance is terminated.
|
|
* @event AppWindow#appterminated
|
|
*/
|
|
this.publish('terminated');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* An appWindow is dead if somebody requested it to be killed.
|
|
*
|
|
* @return {Boolean} The instance is dead or not.
|
|
*/
|
|
AppWindow.prototype.isDead = function aw_isDead() {
|
|
return (this._killed || !this.element);
|
|
};
|
|
|
|
/**
|
|
* Destroy the instance.
|
|
* @fires AppWindow#appdestroyed
|
|
*/
|
|
AppWindow.prototype.destroy = function aw_destroy() {
|
|
/**
|
|
* Fired before the instance id destroyed.
|
|
* @event AppWindow#appwilldestroy
|
|
*/
|
|
// Remove previous -> next reference.
|
|
if (this.previousWindow) {
|
|
this.previousWindow.unsetNextWindow();
|
|
this.previousWindow = null;
|
|
}
|
|
|
|
// Remove rear -> front reference.
|
|
if (this.rearWindow) {
|
|
this.rearWindow.unsetFrontWindow();
|
|
this.rearWindow = null;
|
|
}
|
|
|
|
// Remove attention reference.
|
|
if (this.parentWindow) {
|
|
this.parentWindow.unsetAttentionWindow();
|
|
this.parentWindow = null;
|
|
}
|
|
|
|
this.publish('willdestroy');
|
|
this.uninstallSubComponents();
|
|
|
|
focusManager.removeUI(this);
|
|
|
|
if (this.element) {
|
|
if (this.element.parentNode) {
|
|
this.element.parentNode.removeChild(this.element);
|
|
}
|
|
this.element = null;
|
|
}
|
|
|
|
/**
|
|
* Fired after the instance id destroyed.
|
|
* @event AppWindow#appdestroyed
|
|
*/
|
|
this.publish('destroyed');
|
|
};
|
|
|
|
AppWindow.prototype.containerElement = document.getElementById('windows');
|
|
|
|
AppWindow.prototype.view = function aw_view() {
|
|
return '<div class=" ' + this.CLASS_LIST +
|
|
' " id="' + this.instanceID +
|
|
'" transition-state="closed">' +
|
|
'<div class="titlebar">' +
|
|
' <div class="notifications-shadow"></div>' +
|
|
'</div>' +
|
|
'<div class="identification-overlay">' +
|
|
'<div>' +
|
|
'<div class="icon"></div>' +
|
|
'<span class="title"></span>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="fade-overlay"></div>' +
|
|
'<div class="touch-blocker"></div>' +
|
|
'<div class="browser-container">' +
|
|
' <div class="screenshot-overlay"></div>' +
|
|
'</div>' +
|
|
'</div>';
|
|
};
|
|
|
|
/**
|
|
* Render the mozbrowser iframe and some overlays.
|
|
* @inner
|
|
*/
|
|
AppWindow.prototype._render = function aw__render() {
|
|
if (this.element) {
|
|
return;
|
|
}
|
|
/**
|
|
* Fired before this element is appended to the DOM tree.
|
|
* @event AppWindow#appwillrender
|
|
*/
|
|
this.publish('willrender');
|
|
|
|
var range = document.createRange();
|
|
var fragment = range.createContextualFragment(this.view());
|
|
|
|
// window.open would offer the iframe so we don't need to generate.
|
|
if (this.iframe) {
|
|
this.browser = {
|
|
element: this.iframe
|
|
};
|
|
} else {
|
|
this.browser = new BrowserFrame(this.browser_config);
|
|
}
|
|
this.element = fragment.getElementById(this.instanceID);
|
|
|
|
// For gaiauitest usage.
|
|
this.element.dataset.manifestName = this.manifest ? this.manifest.name : '';
|
|
|
|
// XXX: Remove following two lines once mozbrowser element is moved
|
|
// into appWindow.
|
|
this.frame = this.element;
|
|
this.iframe = this.browser.element;
|
|
this.iframe.dataset.frameType = 'window';
|
|
this.iframe.dataset.frameOrigin = this.origin;
|
|
this.iframe.dataset.url = this.config.url;
|
|
|
|
if (this.isFullScreen()) {
|
|
this.element.classList.add('fullscreen-app');
|
|
}
|
|
|
|
if (this.isBrowser()) {
|
|
this.element.classList.add('browser');
|
|
}
|
|
|
|
this.browserContainer = this.element.querySelector('.browser-container');
|
|
this.browserContainer.appendChild(this.browser.element);
|
|
|
|
this.containerElement.appendChild(fragment);
|
|
|
|
this.screenshotOverlay = this.element.querySelector('.screenshot-overlay');
|
|
this.fadeOverlay = this.element.querySelector('.fade-overlay');
|
|
|
|
var overlay = '.identification-overlay';
|
|
this.identificationOverlay = this.element.querySelector(overlay);
|
|
var icon = '.identification-overlay .icon';
|
|
this.identificationIcon = this.element.querySelector(icon);
|
|
var title = '.identification-overlay .title';
|
|
this.identificationTitle = this.element.querySelector(title);
|
|
|
|
// Launched as background: set visibility and overlay screenshot.
|
|
// homescreen is launched at background under FTU/lockscreen too.
|
|
if (this.config.stayBackground || this.isHomescreen) {
|
|
this.setVisible(false);
|
|
}
|
|
|
|
/**
|
|
* Fired after the app window element is appended to the DOM tree.
|
|
* @event AppWindow#apprendered
|
|
*/
|
|
this.publish('rendered');
|
|
this._rendered = true;
|
|
|
|
setTimeout(this.setFrameBackground.bind(this));
|
|
};
|
|
|
|
AppWindow.prototype.render = function aw_render() {
|
|
this._render();
|
|
this._registerEvents();
|
|
this.installSubComponents();
|
|
// Pre determine the rotation degree.
|
|
this.determineRotationDegree();
|
|
};
|
|
|
|
/**
|
|
* Check an appWindow is a regular browsing window, not tied to a particular
|
|
* application.
|
|
*
|
|
* @return {Boolean} is the current instance a browsing window.
|
|
*/
|
|
AppWindow.prototype.isBrowser = function aw_isbrowser() {
|
|
return !this.manifestURL;
|
|
};
|
|
|
|
/**
|
|
* Check an appWindow contains a certified application
|
|
*
|
|
* @return {Boolean} is the current instance a certified application.
|
|
*/
|
|
AppWindow.prototype.isCertified = function aw_iscertified() {
|
|
return this.config.manifest && 'certified' === this.config.manifest.type;
|
|
};
|
|
|
|
/**
|
|
* Try to navigate the current frame to a given url. If the current instance
|
|
* is not a browsing window, we reconfigure it to be a browser window, and
|
|
* give it a clean browser frame.
|
|
* @param {String} url The url to navigate to
|
|
*/
|
|
AppWindow.prototype.navigate = function aw_isbrowser(url) {
|
|
|
|
// Bug 1071882 - We currently only support navigating browser windows and
|
|
// apps with a role="search", which we use for the browser landing page.
|
|
var appIsSearch = this.manifest && this.manifest.role === 'search';
|
|
if (!this.isBrowser() && !appIsSearch) {
|
|
console.warn('Tried to navigate an illegal app window.');
|
|
return;
|
|
}
|
|
|
|
// Kill any front window.
|
|
if (this.frontWindow) {
|
|
this.frontWindow.kill();
|
|
this.frontWindow = null;
|
|
}
|
|
|
|
if (!this.isBrowser()) {
|
|
// Handle navigating from an app -> browser.
|
|
this.manifestURL = null;
|
|
this.manifest = null;
|
|
this.element.classList.add('browser');
|
|
|
|
// Reset the browser
|
|
this.reConfig({
|
|
url: url,
|
|
title: url,
|
|
oop: true
|
|
});
|
|
this.browserContainer.removeChild(this.browser.element);
|
|
this.browser = new BrowserFrame(this.browser_config);
|
|
this.browserContainer.appendChild(this.browser.element);
|
|
this.iframe = this.browser.element;
|
|
this.launchTime = Date.now();
|
|
this.appChrome && this.appChrome.reConfig();
|
|
// Register audio channels for new browser frame.
|
|
this._unregisterAudioChannels();
|
|
this._registerAudioChannels();
|
|
}
|
|
|
|
this.browser.element.src = url;
|
|
};
|
|
|
|
/**
|
|
* The event is necessary for edge gesture swiper.
|
|
* It means the app window is swiped in.
|
|
*
|
|
* @access private
|
|
* @event AppWindow~_swipein
|
|
*/
|
|
|
|
/**
|
|
* The event is necessary for edge gesture swiper.
|
|
* It means the app window is swiped out.
|
|
*
|
|
* @access private
|
|
* @event AppWindow~_swipeout
|
|
*/
|
|
AppWindow.REGISTERED_EVENTS =
|
|
['mozbrowserclose', 'mozbrowsererror', 'mozbrowservisibilitychange',
|
|
'mozbrowserloadend', 'mozbrowseractivitydone', 'mozbrowserloadstart',
|
|
'mozbrowsertitlechange', 'mozbrowserlocationchange',
|
|
'mozbrowsermetachange', 'mozbrowsericonchange',
|
|
'_localized', '_swipein', '_swipeout', '_kill_suspended',
|
|
'_orientationchange', '_focus', '_hidewindow', '_sheetsgesturebegin',
|
|
'_sheetsgestureend', '_cardviewbeforeshow', '_cardviewclosed',
|
|
'_closed'];
|
|
|
|
AppWindow.REGISTERED_GLOBAL_EVENTS =
|
|
['back'];
|
|
|
|
AppWindow.SUB_COMPONENTS = {
|
|
'transitionController': window.AppTransitionController,
|
|
'modalDialog': window.AppModalDialog,
|
|
'valueSelector': window.ValueSelector,
|
|
'authDialog': window.AppAuthenticationDialog,
|
|
'contextmenu': window.BrowserContextMenu,
|
|
'childWindowFactory': window.ChildWindowFactory,
|
|
};
|
|
|
|
AppWindow.prototype.openAnimation = 'invoked';
|
|
AppWindow.prototype.closeAnimation = 'fade-out';
|
|
|
|
/**
|
|
* Install sub components belong to this window instance.
|
|
* The necessary components are based on AppWindow.SUB_COMPONENTS,
|
|
* now they are AppModalDialog/AppAuthenticationDialog.
|
|
*
|
|
* @example
|
|
* // Implement a new dialog
|
|
* var myDialog = function myDialog(app) {
|
|
* };
|
|
*
|
|
* var appWindow = new AppWindow();
|
|
* AppWindow.SUB_COMPONENTS.push(myDialog);
|
|
*
|
|
* app.installSubComponents();
|
|
*/
|
|
AppWindow.prototype.installSubComponents =
|
|
function aw_installSubComponents() {
|
|
this.debug('installing sub components...');
|
|
for (var componentName in this.constructor.SUB_COMPONENTS) {
|
|
if (this.constructor.SUB_COMPONENTS[componentName]) {
|
|
this[componentName] =
|
|
new this.constructor.SUB_COMPONENTS[componentName](this);
|
|
this[componentName].start && this[componentName].start();
|
|
}
|
|
}
|
|
|
|
// Need to wait for mozbrowserloadstart to get allowedAudioChannels.
|
|
this.browser.element.addEventListener(
|
|
'mozbrowserloadstart',
|
|
function onloadstart() {
|
|
this.browser.element.removeEventListener(
|
|
'mozbrowserloadstart', onloadstart
|
|
);
|
|
this._registerAudioChannels();
|
|
}.bind(this)
|
|
);
|
|
|
|
if (this.manifest) {
|
|
var that = this;
|
|
that.element.addEventListener('_opened', function onOpened() {
|
|
that.element.removeEventListener('_opened', onOpened);
|
|
that.appChrome = new AppChrome(that);
|
|
});
|
|
} else {
|
|
this.appChrome = new AppChrome(this);
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype.uninstallSubComponents =
|
|
function aw_uninstallSubComponents() {
|
|
for (var componentName in this.constructor.SUB_COMPONENTS) {
|
|
if (this[componentName] && this[componentName].destroy) {
|
|
this[componentName].destroy();
|
|
}
|
|
this[componentName] = null;
|
|
}
|
|
|
|
this._unregisterAudioChannels();
|
|
|
|
if (this.appChrome) {
|
|
this.appChrome.destroy();
|
|
this.appChrome = null;
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype._registerAudioChannels =
|
|
function aw_registerAudioChannels() {
|
|
this.audioChannels = new Map();
|
|
var audioChannels = this.browser.element.allowedAudioChannels;
|
|
audioChannels && audioChannels.forEach((audioChannel) => {
|
|
this.audioChannels.set(
|
|
audioChannel.name,
|
|
new AudioChannelController(this, audioChannel)
|
|
);
|
|
this.debug('Registered ' + audioChannel.name + ' audio channel');
|
|
});
|
|
};
|
|
|
|
AppWindow.prototype._unregisterAudioChannels =
|
|
function aw_unregisterAudioChannels() {
|
|
if (this.audioChannels) {
|
|
this.audioChannels.forEach(function(audioChannel) {
|
|
audioChannel.destroy();
|
|
});
|
|
this.audioChannels = null;
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype._handle__localized = function aw__handle__localized() {
|
|
if (!this.manifest) {
|
|
return;
|
|
}
|
|
this.name = new ManifestHelper(this.manifest).name;
|
|
|
|
if (this.identificationTitle) {
|
|
this.identificationTitle.textContent = this.name;
|
|
}
|
|
|
|
// For uitest.
|
|
this.element.dataset.localizedName = this.name;
|
|
this.publish('namechanged');
|
|
};
|
|
|
|
AppWindow.prototype._handle__orientationchange = function() {
|
|
if (this.isActive()) {
|
|
// Will be resized by the AppWindowManager
|
|
return;
|
|
}
|
|
|
|
// Resize only the overlays not the app
|
|
var width = layoutManager.width;
|
|
var height = layoutManager.height;
|
|
|
|
this.iframe.style.width = this.width + 'px';
|
|
this.iframe.style.height = this.height + 'px';
|
|
|
|
this.element.style.width = width + 'px';
|
|
this.element.style.height = height + 'px';
|
|
|
|
// The homescreen doesn't have an identification overlay
|
|
if (this.isHomescreen) {
|
|
return;
|
|
}
|
|
|
|
// If the screenshot doesn't match the new orientation hide it
|
|
if (this.width != width) {
|
|
this.screenshotOverlay.style.visibility = 'hidden';
|
|
} else {
|
|
this.screenshotOverlay.style.visibility = '';
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype._handle_mozbrowservisibilitychange =
|
|
function aw__handle_mozbrowservisibilitychange(evt) {
|
|
var type = evt.detail.visible ? 'foreground' : 'background';
|
|
this.publish(type);
|
|
};
|
|
|
|
AppWindow.prototype._handle_mozbrowseractivitydone =
|
|
function aw__handle_mozbrowseractivitydone(evt) {
|
|
// In case we're not ActivityWindow but we're launched
|
|
// as window disposition activity.
|
|
if (this.isActive() && this.callerWindow) {
|
|
var caller = this.callerWindow;
|
|
var callerBottom = caller.getBottomMostWindow();
|
|
var calleeBottom = this.getBottomMostWindow();
|
|
caller.calleeWindow = null;
|
|
this.callerWindow = null;
|
|
// No transition when the callee is caller
|
|
if (callerBottom !== calleeBottom) {
|
|
callerBottom.open('in-from-left');
|
|
calleeBottom.close('out-to-right');
|
|
}
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype._handle_mozbrowserclose =
|
|
function aw__handle_mozbrowserclose(evt) {
|
|
this.kill();
|
|
};
|
|
|
|
AppWindow.prototype._handle_mozbrowsererror =
|
|
function aw__handle_mozbrowsererror(evt) {
|
|
if (evt.detail.type !== 'fatal') {
|
|
return;
|
|
}
|
|
// Send event instead of call crash reporter directly.
|
|
this.publish('crashed');
|
|
|
|
if (this.constructor.SUSPENDING_ENABLED && !this.isActive()) {
|
|
this.debug(' ..sleep! I will come back.');
|
|
this.destroyBrowser();
|
|
if (this.frontWindow) {
|
|
this.frontWindow.kill();
|
|
}
|
|
} else {
|
|
this.kill(evt);
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype._handle_mozbrowserloadstart =
|
|
function aw__handle_mozbrowserloadstart(evt) {
|
|
this.loading = true;
|
|
this._changeState('loading', true);
|
|
this.publish('loading');
|
|
};
|
|
|
|
AppWindow.prototype._handle_mozbrowsertitlechange =
|
|
function aw__handle_handle_mozbrowsertitlechange(evt) {
|
|
this.title = evt.detail;
|
|
this.publish('titlechange');
|
|
|
|
if (this.identificationTitle && !this.manifest) {
|
|
this.identificationTitle.textContent = this.title;
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype._handle_mozbrowserloadend =
|
|
function aw__handle_mozbrowserloadend(evt) {
|
|
if (!this.loaded) {
|
|
// Perf test needs.
|
|
this.publish('loadtime', {
|
|
time: parseInt(Date.now() - this.launchTime),
|
|
timestamp: this.timestamp,
|
|
type: 'c',
|
|
src: this.config.url
|
|
});
|
|
}
|
|
this.loading = false;
|
|
this.loaded = true;
|
|
this.element.classList.add('render');
|
|
// Bug 1043408 - Marionette tests relies on the render class of the
|
|
// iframe parent in order to starts. So let's replicate the 'render'
|
|
// class on browser-container until the proper patch on the external
|
|
// repo.
|
|
this.browserContainer.classList.add('render');
|
|
// Force removing background image.
|
|
this.element.style.backgroundImage = 'none';
|
|
this._changeState('loading', false);
|
|
this.publish('loaded');
|
|
var backgroundColor = evt.detail.backgroundColor;
|
|
this.debug('bgcolor= ', backgroundColor);
|
|
/* When rotating the screen, the child may take some time to reflow.
|
|
* If the child takes longer than layers.orientation.sync.timeout
|
|
* to respond, gecko will go ahead and draw anyways. This code
|
|
* uses a simple heuristic to guess the least distracting color
|
|
* we should draw in the blank space. */
|
|
|
|
/* Only allow opaque colors */
|
|
// TODOEVME - this kept throwing errors when homescreen launched,
|
|
// bgcolor was null
|
|
if (backgroundColor && backgroundColor.indexOf('rgb(') != -1 &&
|
|
!this.isHomescreen) {
|
|
this.debug('setting background color..');
|
|
this.browser.element.style.backgroundColor = backgroundColor;
|
|
}
|
|
/**
|
|
* If the window is active, we should focus it when it loaded. We didn't
|
|
* focus it when this window was opened and defered to here.
|
|
**/
|
|
if (this.getBottomMostWindow().isActive() && this.isActive()) {
|
|
focusManager.focus();
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype._handle_mozbrowserlocationchange =
|
|
function aw__handle_mozbrowserlocationchange(evt) {
|
|
this.favicons = {};
|
|
this.config.url = evt.detail;
|
|
// Integration test needs to locate the frame by this attribute.
|
|
this.browser.element.dataset.url = evt.detail;
|
|
this.publish('locationchange');
|
|
};
|
|
|
|
|
|
AppWindow.prototype._handle_mozbrowsericonchange =
|
|
function aw__handle_mozbrowsericonchange(evt) {
|
|
|
|
var href = evt.detail.href;
|
|
var sizes = evt.detail.sizes;
|
|
|
|
if (!('favicons' in this)) {
|
|
this.favicons = {};
|
|
}
|
|
|
|
if (!(href in this.favicons)) {
|
|
this.favicons[href] = {sizes: []};
|
|
}
|
|
|
|
if (sizes && this.favicons[href].sizes.indexOf(sizes) === -1) {
|
|
this.favicons[href].sizes.push(sizes);
|
|
}
|
|
|
|
if (this.identificationIcon) {
|
|
this.identificationIcon.style.backgroundImage =
|
|
'url("' + evt.detail.href + '")';
|
|
}
|
|
this.publish('iconchange');
|
|
};
|
|
|
|
AppWindow.prototype._handle_mozbrowsermetachange =
|
|
function aw__handle_mozbrowsermetachange(evt) {
|
|
|
|
var detail = evt.detail;
|
|
|
|
switch (detail.name) {
|
|
case 'theme-color':
|
|
if (!detail.type) {
|
|
return;
|
|
}
|
|
// If the theme-color meta is removed, let's reset the color.
|
|
var color = '';
|
|
|
|
// Otherwise, set it to the color that has been asked.
|
|
if (detail.type !== 'removed') {
|
|
color = detail.content;
|
|
}
|
|
this.themeColor = color;
|
|
|
|
this.publish('themecolorchange');
|
|
break;
|
|
|
|
case 'application-name':
|
|
// Apps have a compulsory name field in their manifest
|
|
// which takes precedence.
|
|
if (!this.isBrowser()) {
|
|
return;
|
|
}
|
|
this.updateName(detail.content);
|
|
this.publish('namechanged');
|
|
break;
|
|
}
|
|
|
|
};
|
|
|
|
AppWindow.prototype._handle_back = function aw__handle_back(evt) {
|
|
if (document.activeElement !== this.iframe) {
|
|
return;
|
|
}
|
|
if (!this.config.url.startsWith('app://')) {
|
|
var goBackReq = this.iframe.getCanGoBack();
|
|
goBackReq.onsuccess = () => {
|
|
if (goBackReq.result) {
|
|
this.iframe.goBack();
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype._registerEvents = function aw__registerEvents() {
|
|
if (this.element === null) {
|
|
this._dump();
|
|
return;
|
|
}
|
|
this.constructor.REGISTERED_EVENTS.forEach(function iterator(evt) {
|
|
this.debug('adding ' + evt + ' event handler ...');
|
|
this.element.addEventListener(evt, this);
|
|
}, this);
|
|
this.constructor.REGISTERED_GLOBAL_EVENTS.forEach(function iterator(evt) {
|
|
this.debug('adding ' + evt + ' global event handler ...');
|
|
window.addEventListener(evt, this);
|
|
}, this);
|
|
};
|
|
|
|
/**
|
|
* General event handler interface.
|
|
* Child classes shouldn't change this.
|
|
* @param {DOMEvent} evt The event.
|
|
*/
|
|
AppWindow.prototype.handleEvent = function aw_handleEvent(evt) {
|
|
// We are rendering inline activities + popup inside this element too,
|
|
// so we need to prevent ourselves to be affected
|
|
// by the mozbrowser events of the callee.
|
|
|
|
// WebAPI testing is using mozbrowserloadend event to know
|
|
// the first app is loaded so we cannot stop the propagation here.
|
|
// but we don't want subsequent mozbrowser events to bubble to the
|
|
// used-to-be-rear-window
|
|
if (this.rearWindow && evt.type.startsWith('mozbrowser')) {
|
|
evt.stopPropagation();
|
|
}
|
|
this.debug(' Handling ' + evt.type + ' event...');
|
|
if (this['_handle_' + evt.type]) {
|
|
this['_handle_' + evt.type](evt);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* A temp variable to store current screenshot blob.
|
|
* We should store the blob and create objectURL
|
|
* once we need to display the image,
|
|
* and revoke right away after we finish rendering the image.
|
|
*/
|
|
AppWindow.prototype._screenshotBlob = undefined;
|
|
|
|
AppWindow.prototype.CLASS_NAME = 'AppWindow';
|
|
|
|
AppWindow.prototype.debug = function aw_debug(msg) {
|
|
if (DEBUG || this._DEBUG) {
|
|
console.log('[' + this.CLASS_NAME + ']' +
|
|
'[' + (this.name || this.origin) + ']' +
|
|
'[' + this.instanceID + ']' +
|
|
'[' + Service.currentTime() + '] ' +
|
|
Array.slice(arguments).concat());
|
|
|
|
if (TRACE) {
|
|
console.trace();
|
|
}
|
|
}
|
|
};
|
|
|
|
// Force debug output.
|
|
AppWindow.prototype.forceDebug = function aw_debug(msg) {
|
|
console.log('[Dump:' + this.CLASS_NAME + ']' +
|
|
'[' + (this.name || this.origin) + ']' +
|
|
'[' + Service.currentTime() + ']' +
|
|
Array.slice(arguments).concat());
|
|
};
|
|
|
|
AppWindow.prototype.show = function aw_show() {
|
|
if (!this.element || !this.element.classList.contains('hidden')) {
|
|
return;
|
|
}
|
|
this.element.classList.remove('hidden');
|
|
this.publish('shown');
|
|
};
|
|
|
|
AppWindow.prototype.hide = function aw_hide() {
|
|
if (!this.element || this.element.classList.contains('hidden')) {
|
|
return;
|
|
}
|
|
this.debug('hidden the entire app window.');
|
|
this.element.classList.add('hidden');
|
|
this.publish('hidden');
|
|
};
|
|
|
|
AppWindow.prototype.queueShow = function aw_queueShow() {
|
|
this.element.classList.add('will-become-active');
|
|
// bug 1033921: notify current app changed
|
|
this.publish('will-become-active');
|
|
};
|
|
|
|
AppWindow.prototype.cancelQueuedShow = function aw_cancelQueuedShow() {
|
|
this.element.classList.remove('will-become-active');
|
|
};
|
|
|
|
AppWindow.prototype.queueHide = function aw_queueHide() {
|
|
this.element.classList.add('will-become-inactive');
|
|
this.publish('will-become-inactive');
|
|
};
|
|
|
|
/**
|
|
* Wait for a full repaint of the mozbrowser iframe.
|
|
*/
|
|
AppWindow.prototype.tryWaitForFullRepaint = function onTWFRepaint(callback) {
|
|
if (!callback) {
|
|
return;
|
|
}
|
|
|
|
if (this.isHomescreen) {
|
|
setTimeout(callback);
|
|
return;
|
|
}
|
|
|
|
this.debug('trying wait for full repaint by screenshot enforcing..');
|
|
|
|
this.getScreenshot(function() {
|
|
setTimeout(callback);
|
|
}, 1, 1, 400);
|
|
};
|
|
|
|
/**
|
|
* Request a screenshot ObjectURL temporarily.
|
|
* The image would be discarded after 200ms or the revoke callback
|
|
* is invoked.
|
|
*/
|
|
AppWindow.prototype.requestScreenshotURL =
|
|
function aw__requestScreenshotURL() {
|
|
if (this.frontWindow) {
|
|
return this.frontWindow.requestScreenshotURL();
|
|
}
|
|
if (!this._screenshotBlob) {
|
|
return null;
|
|
}
|
|
var screenshotURL = URL.createObjectURL(this._screenshotBlob);
|
|
setTimeout(function onTimeout() {
|
|
if (screenshotURL) {
|
|
URL.revokeObjectURL(screenshotURL);
|
|
screenshotURL = null;
|
|
}
|
|
}, 200);
|
|
|
|
return screenshotURL;
|
|
};
|
|
|
|
/**
|
|
* Show screenshot overlay and hide the iframe.
|
|
*/
|
|
AppWindow.prototype._showScreenshotOverlay =
|
|
function aw__showScreenshotOverlay() {
|
|
if (!this.screenshotOverlay ||
|
|
this.screenshotOverlay.classList.contains('visible')) {
|
|
return;
|
|
}
|
|
|
|
if (this.identificationOverlay) {
|
|
this.identificationOverlay.classList.add('visible');
|
|
}
|
|
|
|
this.screenshotOverlay.classList.add('visible');
|
|
|
|
var screenshotURL = this.requestScreenshotURL();
|
|
this.screenshotOverlay.style.backgroundImage =
|
|
'url(' + screenshotURL + ')';
|
|
};
|
|
|
|
/**
|
|
* Check if current visibility state is screenshot or not,
|
|
* to hide the screenshot overlay.
|
|
*/
|
|
AppWindow.prototype._hideScreenshotOverlay =
|
|
function aw__hideScreenshotOverlay() {
|
|
|
|
if (!this.screenshotOverlay ||
|
|
!this.screenshotOverlay.classList.contains('visible')) {
|
|
return;
|
|
}
|
|
|
|
this.screenshotOverlay.classList.remove('visible');
|
|
this.screenshotOverlay.style.backgroundImage = '';
|
|
|
|
if (this.identificationOverlay) {
|
|
var overlay = this.identificationOverlay;
|
|
// A white flash can occur when removing the screenshot
|
|
// so we trigger this transition after a tick to hide it.
|
|
setTimeout(function nextTick() {
|
|
overlay.classList.remove('visible');
|
|
});
|
|
}
|
|
};
|
|
|
|
// Get cached screenshot Blob if there is one.
|
|
// Note: the caller should revoke the created ObjectURL once it's finishing.
|
|
AppWindow.prototype.getCachedScreenshotBlob =
|
|
function aw_getCachedScreenshotBlob() {
|
|
return this._screenshotBlob;
|
|
};
|
|
|
|
// Save and update screenshot Blob.
|
|
AppWindow.prototype.renewCachedScreenshotBlob =
|
|
function aw_renewScreenshot(screenshotBlob) {
|
|
this._screenshotBlob = screenshotBlob;
|
|
};
|
|
|
|
/**
|
|
* Event prefix presents the object type
|
|
* when publishing an event from the element.
|
|
* Always 'app' for now.
|
|
*
|
|
* @type {String}
|
|
*/
|
|
AppWindow.prototype.eventPrefix = 'app';
|
|
|
|
/**
|
|
* Publish an event.
|
|
*
|
|
* @param {String} event Event name, without object type prefix.
|
|
* @param {Object} detail Parameters in JSON format.
|
|
*/
|
|
AppWindow.prototype.publish = function(event, detail) {
|
|
// Dispatch internal event before external events.
|
|
this.broadcast(event, detail);
|
|
var evt = new CustomEvent(this.eventPrefix + event,
|
|
{
|
|
bubbles: true,
|
|
detail: detail || this
|
|
});
|
|
|
|
this.debug('publishing external event: ' + event +
|
|
(detail ? JSON.stringify(detail) : ''));
|
|
|
|
// Publish external event.
|
|
if (this.rearWindow && this.element) {
|
|
this.element.dispatchEvent(evt);
|
|
} else {
|
|
window.dispatchEvent(evt);
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype.broadcast = function aw_broadcast(event, detail) {
|
|
// Broadcast internal event.
|
|
if (this.element) {
|
|
var internalEvent = new CustomEvent('_' + event,
|
|
{
|
|
bubbles: false,
|
|
detail: detail || this
|
|
});
|
|
|
|
this.debug('publishing internal event: ' + event);
|
|
this.element.dispatchEvent(internalEvent);
|
|
}
|
|
};
|
|
|
|
var OrientationRotationArray = [
|
|
'portrait-primary', 'portrait-secondary', 'portrait',
|
|
'landscape-primary', 'landscape-secondary', 'landscape', 'default'];
|
|
|
|
var OrientationRotationTable = {
|
|
'portrait-primary': [0, 180, 0, 90,
|
|
270, 90, OrientationManager.isDefaultPortrait() ? 0 : 90],
|
|
'landscape-primary': [270, 90, 270, 0,
|
|
180, 0, OrientationManager.isDefaultPortrait() ? 270 : 0],
|
|
'portrait-secondary': [180, 0, 180, 270,
|
|
90, 270, OrientationManager.isDefaultPortrait() ? 180 : 270],
|
|
'landscape-secondary': [90, 270, 90, 180,
|
|
0, 180, OrientationManager.isDefaultPortrait() ? 180 : 90]
|
|
};
|
|
|
|
AppWindow.prototype.determineRotationDegree =
|
|
function aw__determineRotationDegree() {
|
|
if (!this.manifest) {
|
|
return 0;
|
|
}
|
|
|
|
var appOrientation = this.manifest.orientation;
|
|
var orientation = this.determineOrientation(appOrientation);
|
|
var table =
|
|
OrientationRotationTable[
|
|
OrientationManager.defaultOrientation];
|
|
var degree = table[OrientationRotationArray.indexOf(orientation)];
|
|
this.rotatingDegree = degree;
|
|
if (degree == 90 || degree == 270) {
|
|
this.element.classList.add('perpendicular');
|
|
}
|
|
return degree;
|
|
};
|
|
|
|
AppWindow.prototype.determineClosingRotationDegree =
|
|
function aw__determineClosingRotationDegree() {
|
|
if (!this.manifest) {
|
|
return 0;
|
|
}
|
|
|
|
// XXX: Assume homescreen's orientation is just device default.
|
|
var homeOrientation = OrientationManager.defaultOrientation;
|
|
var currentOrientation = OrientationManager
|
|
.fetchCurrentOrientation();
|
|
this.debug(currentOrientation);
|
|
var table = OrientationRotationTable[homeOrientation];
|
|
var degree = table[OrientationRotationArray.indexOf(currentOrientation)];
|
|
return Math.abs(360 - degree) % 360;
|
|
};
|
|
|
|
/**
|
|
* All apps in TV are fullscreen. We keep this function for future extension.
|
|
* @return {Boolean} We're a fullscreen app or not.
|
|
*/
|
|
AppWindow.prototype.isFullScreen = function aw_isFullScreen() {
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Detect whether this app is resized 100% width and height by its manifest.
|
|
* The difference between fullscreen and fullscreen_layout is the software
|
|
* home button. And we don't have it in TV. So, we just make them are all
|
|
* fullscreen and fullscreen_layout. There is another difference between them
|
|
* that fullscreen_layout is used for the mozRequestFullScreen.
|
|
* @return {Boolean} We're a fullscreen_layout app or not.
|
|
*/
|
|
AppWindow.prototype.isFullScreenLayout = function aw_isFullScreenLayout() {
|
|
return true;
|
|
};
|
|
|
|
AppWindow.prototype._defaultOrientation = null;
|
|
|
|
AppWindow.prototype.determineOrientation =
|
|
function aw_determineOrientation(orientation) {
|
|
if (this._defaultOrientation) {
|
|
return this._defaultOrientation;
|
|
} else if (!orientation) {
|
|
this._defaultOrientation = 'default';
|
|
return this._defaultOrientation;
|
|
}
|
|
|
|
if (!Array.isArray(orientation)) {
|
|
orientation = [orientation];
|
|
}
|
|
|
|
this._defaultOrientation = orientation[0];
|
|
|
|
return this._defaultOrientation;
|
|
};
|
|
|
|
AppWindow.prototype._resize = function aw__resize() {
|
|
var height, width;
|
|
this.debug('force RESIZE...');
|
|
if (layoutManager.keyboardEnabled) {
|
|
/**
|
|
* The event is dispatched on the app window only when keyboard is up.
|
|
*
|
|
* @access private
|
|
* @event AppWindow~_withkeyboard
|
|
*/
|
|
this.broadcast('withkeyboard');
|
|
} else {
|
|
/**
|
|
* The event is dispatched on the app window only when keyboard is hidden.
|
|
*
|
|
* @access private
|
|
* @event AppWindow~_withoutkeyboard
|
|
*/
|
|
this.broadcast('withoutkeyboard');
|
|
}
|
|
height = layoutManager.height;
|
|
|
|
// If we have sidebar in the future, change layoutManager then.
|
|
width = layoutManager.width;
|
|
|
|
this.width = width;
|
|
this.height = height;
|
|
this.element.style.width = this.width + 'px';
|
|
this.element.style.height = this.height + 'px';
|
|
|
|
this.iframe.style.width = '';
|
|
this.iframe.style.height = '';
|
|
|
|
this.resized = true;
|
|
if (this.screenshotOverlay) {
|
|
this.screenshotOverlay.style.visibility = '';
|
|
}
|
|
|
|
/**
|
|
* Fired when the app is resized.
|
|
*
|
|
* @event AppWindow#appresize
|
|
*/
|
|
this.publish('resize');
|
|
this.debug('W:', this.width, 'H:', this.height);
|
|
};
|
|
|
|
/**
|
|
* Set the size of the app's iframe to match the size of the screen.
|
|
* We have to call this on resize events (which happen when the
|
|
* phone orientation is changed). And also when an app is launched
|
|
* and each time an app is brought to the front, since the
|
|
* orientation could have changed since it was last displayed
|
|
*
|
|
* An appWindow instance only resizes if it's active.
|
|
* Since we already pre-config the size in window.css,
|
|
* if an app is launched by system message we don't need to resize
|
|
* it.
|
|
*
|
|
* 
|
|
*/
|
|
AppWindow.prototype.resize = function aw_resize() {
|
|
if (this.isDead()) {
|
|
return;
|
|
}
|
|
this.debug('request RESIZE...active? ', this.isActive());
|
|
var bottom = this.getBottomMostWindow();
|
|
if (!bottom.isActive() || this.isTransitioning()) {
|
|
return;
|
|
}
|
|
var top = this.getTopMostWindow();
|
|
if (top.instanceID != this.instanceID) {
|
|
bottom._resize();
|
|
top.resize();
|
|
} else {
|
|
// resize myself if no child.
|
|
this.debug(' will resize... ');
|
|
this._resize();
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype.getTopMostWindow = function() {
|
|
var win = this;
|
|
while (win.frontWindow) {
|
|
win = win.frontWindow;
|
|
}
|
|
|
|
return win;
|
|
};
|
|
|
|
AppWindow.prototype.getBottomMostWindow = function() {
|
|
var win = this;
|
|
|
|
while (win.rearWindow) {
|
|
win = win.rearWindow;
|
|
}
|
|
|
|
return win;
|
|
};
|
|
|
|
/**
|
|
* Lock the orientation for this app anyway.
|
|
*/
|
|
AppWindow.prototype.lockOrientation = function() {
|
|
var manifest = this.manifest || this.config.manifest;
|
|
var orientation = manifest ? (manifest.orientation ||
|
|
OrientationManager.globalOrientation) :
|
|
OrientationManager.globalOrientation;
|
|
if (orientation) {
|
|
var rv = screen.mozLockOrientation(orientation);
|
|
if (rv === false) {
|
|
console.warn('screen.mozLockOrientation() returned false for',
|
|
this.origin, 'orientation', orientation);
|
|
} else {
|
|
this.debug(' locking screen orientation to ' + orientation);
|
|
}
|
|
} else { // If no orientation was requested, then let it rotate
|
|
screen.mozUnlockOrientation();
|
|
this.debug(' Unlocking screen orientation..');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Lock or unlock orientation for this app.
|
|
* Note: if we have front window, the request would be delivered
|
|
* to the front active window.
|
|
*/
|
|
AppWindow.prototype.setOrientation =
|
|
function aw_setOrientation() {
|
|
if (!this.getBottomMostWindow().isActive()) {
|
|
return;
|
|
}
|
|
if (this.frontWindow && this.frontWindow.isActive()) {
|
|
this.frontWindow.setOrientation();
|
|
} else {
|
|
this.lockOrientation();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* fade out to window to black.
|
|
*/
|
|
AppWindow.prototype.fadeOut = function aw__fadeout() {
|
|
if (!this.isActive() && this.element) {
|
|
this.element.classList.add('fadeout');
|
|
this.debug(' fade out >>>> ');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* fade in the window from black.
|
|
*/
|
|
AppWindow.prototype.fadeIn = function aw__fadein() {
|
|
if (this.isActive()) {
|
|
this.element.classList.remove('fadeout');
|
|
this.debug(' fade in <<<<< ');
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype.setCalleeWindow =
|
|
function aw_setCalleeWindow(callee) {
|
|
this.calleeWindow = callee;
|
|
callee.callerWindow = this;
|
|
};
|
|
|
|
AppWindow.prototype.unsetCalleeWindow =
|
|
function aw_unsetCalleeWindow() {
|
|
if (this.calleeWindow.callerWindow) {
|
|
this.calleeWindow.callerWindow = null;
|
|
}
|
|
this.calleeWindow = null;
|
|
};
|
|
|
|
AppWindow.prototype.unsetAttentionWindow =
|
|
function aw_unsetAttentionWindow() {
|
|
this.attentionWindow = null;
|
|
};
|
|
|
|
/**
|
|
* Modify an attribute on this.element
|
|
* @param {String} type State type.
|
|
* @param {String} state State name.
|
|
*/
|
|
AppWindow.prototype._changeState = function aw__changeState(type, state) {
|
|
if (this.element) {
|
|
this.element.setAttribute(type + '-state', state.toString());
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Mixin the appWindow prototype with {mixin} object.
|
|
* @param {Object} mixin The object to be mixed.
|
|
*/
|
|
AppWindow.addMixin = function AW_addMixin(mixin) {
|
|
for (var prop in mixin) {
|
|
if (mixin.hasOwnProperty(prop)) {
|
|
// Put event handler function into an array,
|
|
// if the name of the propery is '_on'.
|
|
if (!this.prototype.hasOwnProperty(prop)) {
|
|
this.prototype[prop] = mixin[prop];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype.preloadSplash = function aw_preloadSplash() {
|
|
if (this._splash || this.config.icon) {
|
|
if (this.config.icon) {
|
|
this._splash = this.config.icon;
|
|
} else {
|
|
// origin might contain a pathname too, so need to parse it to find the
|
|
// "real origin"
|
|
var url = this.config.origin.split('/');
|
|
var origin = url[0] + '//' + url[2];
|
|
this._splash = origin + this._splash;
|
|
}
|
|
// Start to load the image in background to avoid flickering if possible.
|
|
var img = new Image();
|
|
img.src = this._splash;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The preferred CSS size of the icon used for cold launch splash for phones.
|
|
*/
|
|
AppWindow.prototype.SPLASH_ICON_SIZE_TINY = 60;
|
|
|
|
/**
|
|
* The preferred CSS size of the icon used for cold launch splash for
|
|
* other devices.
|
|
*/
|
|
AppWindow.prototype.SPLASH_ICON_SIZE_NOT_TINY = 90;
|
|
|
|
AppWindow.prototype.getIconForSplash =
|
|
function aw_getIconForSplash(manifest) {
|
|
var icons = this.manifest ?
|
|
('icons' in this.manifest ? this.manifest.icons : null) : null;
|
|
if (!icons) {
|
|
return null;
|
|
}
|
|
|
|
var targetedPixelSize = 2 * (ScreenLayout.getCurrentLayout('tiny') ?
|
|
this.SPLASH_ICON_SIZE_TINY : this.SPLASH_ICON_SIZE_NOT_TINY) *
|
|
Math.ceil(window.devicePixelRatio || 1);
|
|
|
|
var preferredSize = Number.MAX_VALUE;
|
|
var max = 0;
|
|
|
|
for (var size in icons) {
|
|
size = parseInt(size, 10);
|
|
if (size > max) {
|
|
max = size;
|
|
}
|
|
|
|
if (size >= targetedPixelSize && size < preferredSize) {
|
|
preferredSize = size;
|
|
}
|
|
}
|
|
// If there is an icon matching the preferred size, we return the result,
|
|
// if there isn't, we will return the maximum available size.
|
|
if (preferredSize === Number.MAX_VALUE) {
|
|
preferredSize = max;
|
|
}
|
|
|
|
this._splash = icons[preferredSize];
|
|
this.preloadSplash();
|
|
return icons[preferredSize];
|
|
};
|
|
|
|
/**
|
|
* Set splash as our inital background.
|
|
* If the content is loaded then just skip.
|
|
*/
|
|
AppWindow.prototype.setFrameBackground =
|
|
function aw_setFrameBackground() {
|
|
if (!this.isHomescreen &&
|
|
!this.loaded && !this.splashed && this.element) {
|
|
|
|
if (this._splash) {
|
|
this.splashed = true;
|
|
this.element.style.backgroundImage = 'url("' + this._splash + '")';
|
|
|
|
var iconCSSSize = 2 * (ScreenLayout.getCurrentLayout('tiny') ?
|
|
this.SPLASH_ICON_SIZE_TINY : this.SPLASH_ICON_SIZE_NOT_TINY);
|
|
this.element.style.backgroundSize =
|
|
iconCSSSize + 'px ' + iconCSSSize + 'px';
|
|
|
|
if (this.identificationIcon) {
|
|
this.identificationIcon.style.backgroundImage =
|
|
'url("' + this._splash + '")';
|
|
}
|
|
}
|
|
|
|
if (this.identificationTitle) {
|
|
this.identificationTitle.textContent = this.name;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* The app window is requesting to be opened.
|
|
*
|
|
* @fires AppWindow#apprequestopen
|
|
*/
|
|
AppWindow.prototype.requestOpen = function aw_requestOpen() {
|
|
/**
|
|
* Fired once we'd like to be opened.
|
|
* The one who makes decision to call open() would be AppWindowManager.
|
|
*
|
|
* @event AppWindow#apprequestopen
|
|
*/
|
|
this.publish('requestopen');
|
|
};
|
|
|
|
/**
|
|
* The app window is requesting to be closed.
|
|
*
|
|
* @fires AppWindow#apprequestclose
|
|
*/
|
|
AppWindow.prototype.requestClose = function aw_requestClose() {
|
|
/**
|
|
* Fired once we'd like to be closed.
|
|
* The one who makes decision to call close() would be AppWindowManager.
|
|
*
|
|
* @event AppWindow#apprequestclose
|
|
*/
|
|
this.publish('requestclose');
|
|
};
|
|
|
|
/**
|
|
* Change URL only when we're at background.
|
|
* @param {String} url URL.
|
|
*/
|
|
AppWindow.prototype.modifyURLatBackground = function aw_changeURL(url) {
|
|
// XXX: If the app is in foreground, it's too risky to change it's
|
|
// URL. We still change it because the home app is an overlay on top of any
|
|
// app.
|
|
var iframe = this.browser.element;
|
|
// If the app is opened and it is loaded to the correct page,
|
|
// then there is nothing to do.
|
|
if (iframe.src !== url) {
|
|
// Rewrite the URL of the app frame to the requested URL.
|
|
// XXX: We could ended opening URLs not for the app frame
|
|
// in the app frame. But we don't care.
|
|
iframe.src = url;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Do resize and/or ensure repaint before opening the app.
|
|
* @param {Function} callback Callback when app is ready to be opened.
|
|
*/
|
|
AppWindow.prototype.ready = function aw_ready(callback) {
|
|
if (!this.element) {
|
|
return;
|
|
}
|
|
|
|
this.debug('requesting to open');
|
|
if (!this.loaded ||
|
|
(this.screenshotOverlay &&
|
|
this.screenshotOverlay.classList.contains('visible'))) {
|
|
this.debug('loaded yet');
|
|
setTimeout(callback);
|
|
return;
|
|
} else {
|
|
var invoked = false;
|
|
this.waitForNextPaint(function() {
|
|
if (invoked) {
|
|
return;
|
|
}
|
|
invoked = true;
|
|
setTimeout(callback);
|
|
});
|
|
if (this.isHomescreen) {
|
|
this.setVisible(true);
|
|
return;
|
|
}
|
|
this.tryWaitForFullRepaint(function() {
|
|
if (invoked) {
|
|
return;
|
|
}
|
|
invoked = true;
|
|
setTimeout(callback);
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Open the window; the detail is done in appTransitionController.
|
|
* @param {String} animation The animation class name.
|
|
*/
|
|
AppWindow.prototype.open = function aw_open(animation) {
|
|
// Request "open" to our internal transition controller.
|
|
if (this.transitionController) {
|
|
this.transitionController.requireOpen(animation);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Close the window; the detail is done in appTransitionController.
|
|
* @param {String} animation The animation class name.
|
|
*/
|
|
AppWindow.prototype.close = function aw_close(animation) {
|
|
// Request "close" to our internal transition controller.
|
|
if (this.transitionController) {
|
|
this.transitionController.requireClose(animation);
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype._handle__swipein = function aw_swipein() {
|
|
// Revive the browser element if it's got killed in background.
|
|
this.reviveBrowser();
|
|
// Request "open" to our internal transition controller.
|
|
if (this.transitionController) {
|
|
this.transitionController.switchTransitionState('opened');
|
|
this.publish('opened');
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype._handle__swipeout = function aw_swipeout() {
|
|
// Request "close" to our internal transition controller.
|
|
if (this.transitionController) {
|
|
this.transitionController.switchTransitionState('closed');
|
|
this.publish('closed');
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype._handle__sheetsgesturebegin = function aw_sgbegin() {
|
|
// If we're the active we shouldn't do anything at this point. We'll get
|
|
// our frame hidden on swipeout.
|
|
if (this.isActive()) {
|
|
this.debug('no screenshot for active app during sheetsgesturebegin');
|
|
return;
|
|
}
|
|
|
|
// For inactive apps we'll already have a screenshot blob ready for use.
|
|
this.debug('showing screenshot during sheetsgesturebegin');
|
|
this._showScreenshotOverlay();
|
|
};
|
|
|
|
AppWindow.prototype._handle__sheetsgestureend = function aw_sgend() {
|
|
if (this.isActive()) {
|
|
this.debug('nothing to do during sheetsgestureend');
|
|
return;
|
|
}
|
|
this.debug('hiding screenshot on sheetsgestureend');
|
|
this._hideScreenshotOverlay();
|
|
};
|
|
|
|
AppWindow.prototype._handle__cardviewbeforeshow = function aw_cvbeforeshow() {
|
|
this.debug('showing screenshot for cardsview.');
|
|
this._showScreenshotOverlay();
|
|
};
|
|
|
|
AppWindow.prototype._handle__cardviewclosed = function aw_cvclosed() {
|
|
this.debug('hiding screenshot after cardsview closed.');
|
|
this._hideScreenshotOverlay();
|
|
};
|
|
|
|
AppWindow.prototype._handle__closed = function aw_closed() {
|
|
//
|
|
// Never take screenshots of the homescreen.
|
|
//
|
|
// We don't need the screenshot of homescreen because:
|
|
// 1. Homescreen background is transparent,
|
|
// currently gecko only sends JPG to us.
|
|
// See bug 878003.
|
|
// 2. Homescreen screenshot isn't required by card view.
|
|
// Since getScreenshot takes additional memory usage,
|
|
// let's early return here.
|
|
// 3. We want to remove this long term, see bug 1072781.
|
|
//
|
|
if (this.getBottomMostWindow().isHomescreen) {
|
|
return;
|
|
}
|
|
// Update screenshot blob here to avoid slowing down closing transitions.
|
|
this.getScreenshot();
|
|
};
|
|
|
|
AppWindow.prototype._handle__kill_suspended = function aw() {
|
|
if (this.suspended) {
|
|
this.kill();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Link child window to this app window instance.
|
|
* If there's already one, kill it at first.
|
|
* @param {ChildWindow} childWindow The child window instance.
|
|
*/
|
|
AppWindow.prototype.setNextWindow = function aw_setNextWindow(nextWindow) {
|
|
if (this.nextWindow) {
|
|
console.warn('There is already alive child window, killing...',
|
|
this.nextWindow.instanceID);
|
|
this.nextWindow.kill();
|
|
}
|
|
this.nextWindow = nextWindow;
|
|
};
|
|
|
|
AppWindow.prototype.unsetNextWindow = function aw_unsetNextWindow() {
|
|
if (this.nextWindow) {
|
|
this.nextWindow = null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Build bottom/top window relationship.
|
|
* If there's already one, kill it at first.
|
|
* @param {AppWindow} fronWindow The front window instance.
|
|
*/
|
|
AppWindow.prototype.setFrontWindow = function aw_setFrontWindow(frontWindow) {
|
|
if (this.frontWindow) {
|
|
console.warn('There is already alive child window, killing...',
|
|
this.frontWindow.instanceID);
|
|
this.frontWindow.kill();
|
|
}
|
|
this.frontWindow = frontWindow;
|
|
};
|
|
|
|
AppWindow.prototype.unsetFrontWindow = function aw_unsetFrontWindow() {
|
|
if (this.frontWindow) {
|
|
this.frontWindow = null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get the previous window reference of current active one.
|
|
* @return {AppWindow} The previous one in the app sheet chain.
|
|
*/
|
|
AppWindow.prototype.getPrev = function() {
|
|
var current = this.getActiveWindow();
|
|
if (current) {
|
|
return current.previousWindow;
|
|
} else {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get the next window reference of current active one.
|
|
* @return {AppWindow} The next one in the app sheet chain.
|
|
*/
|
|
AppWindow.prototype.getNext = function() {
|
|
var current = this.getActiveWindow();
|
|
if (current) {
|
|
return current.nextWindow;
|
|
} else {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get the active window reference in the app sheet chain.
|
|
* If there's no active window, return null.
|
|
* @return {AppWindow} The active window in the app sheet chain.
|
|
*/
|
|
AppWindow.prototype.getActiveWindow = function() {
|
|
var app = this;
|
|
while (app) {
|
|
if (app.isActive()) {
|
|
return app;
|
|
}
|
|
app = app.nextWindow;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
/**
|
|
* Get the root window reference.
|
|
* If there's no parent, return ourself.
|
|
* @return {AppWindow} The first one in the app sheet chain.
|
|
*/
|
|
AppWindow.prototype.getRootWindow = function() {
|
|
var app = this;
|
|
while (app.previousWindow) {
|
|
app = app.previousWindow;
|
|
}
|
|
return app;
|
|
};
|
|
|
|
/**
|
|
* Get the leaf window reference.
|
|
* If there's no child, return ourself.
|
|
* @return {AppWindow} The last one in the app sheet chain.
|
|
*/
|
|
AppWindow.prototype.getLeafWindow = function() {
|
|
var app = this;
|
|
while (app.nextWindow) {
|
|
app = app.nextWindow;
|
|
}
|
|
return app;
|
|
};
|
|
|
|
AppWindow.prototype.getFrameForScreenshot = function() {
|
|
var top = this.getTopMostWindow();
|
|
return top.browser ? top.browser.element : null;
|
|
};
|
|
|
|
/**
|
|
* Indicate we are in viewport or not.
|
|
* @return {Boolean} We are in viewport or outside viewport.
|
|
*/
|
|
AppWindow.prototype.isVisible = function() {
|
|
var bottomMostWindow = this.getBottomMostWindow();
|
|
return bottomMostWindow.isActive() && this.isActive();
|
|
};
|
|
|
|
/**
|
|
* Apply a transform to the element
|
|
* @param {Object} nameValues object with transform property names as keys
|
|
* and values to apply to the element
|
|
* @memberOf AppWindow.prototype
|
|
*/
|
|
AppWindow.prototype.transform = function(nameValues) {
|
|
var strFunctions = Object.keys(nameValues).map(function(key) {
|
|
return key + '(' + nameValues[key] + ')';
|
|
}, this).join(' ');
|
|
this.applyStyle({ MozTransform: strFunctions });
|
|
};
|
|
|
|
/**
|
|
* Batch apply style properties
|
|
* @param {Object} nameValues object with style property names as keys
|
|
* and values to apply to the element
|
|
* @memberOf AppWindow.prototype
|
|
*/
|
|
AppWindow.prototype.applyStyle = function(nameValues) {
|
|
var dirty = this._dirtyStyleProperties || (this._dirtyStyleProperties = {});
|
|
var style = this.element.style;
|
|
for (var property in nameValues) {
|
|
if (undefined === nameValues[property]) {
|
|
delete style[[property]];
|
|
} else {
|
|
style[property] = nameValues[property];
|
|
}
|
|
dirty[property] = true;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Remove inline style properties
|
|
* @param {Object} nameValues object with style property names as keys
|
|
* @memberOf AppWindow.prototype
|
|
*/
|
|
AppWindow.prototype.unapplyStyle = function(nameValues) {
|
|
var style = this.element.style;
|
|
for (var pname in nameValues) {
|
|
style[pname] = '';
|
|
delete style[pname];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Show the default contextmenu for an AppWindow
|
|
* @memberOf AppWindow.prototype
|
|
*/
|
|
AppWindow.prototype.showDefaultContextMenu = function() {
|
|
if (this.contextmenu) {
|
|
this.contextmenu.showDefaultMenu();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Hide the contextmenu for an AppWindow
|
|
* @memberOf AppWindow.prototype
|
|
*/
|
|
AppWindow.prototype.hideContextMenu = function() {
|
|
if (this.contextmenu) {
|
|
this.contextmenu.hide();
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype._handle__focus = function() {
|
|
focusManager.focus();
|
|
};
|
|
|
|
/**
|
|
* Request to be foreground to the visibilityManager.
|
|
* If it's fine to let us to be active, this.setVisible(true) will be called.
|
|
*/
|
|
AppWindow.prototype.requestForeground =
|
|
function aw_requestForeground() {
|
|
this.publish('requestforeground');
|
|
};
|
|
|
|
/**
|
|
* The window is killable by user manual action or not.
|
|
* @return {Boolean} The app is killable or not.
|
|
*/
|
|
AppWindow.prototype.killable = function() {
|
|
// This property is updated whenever an attentionWindow
|
|
// is created or destroyed.
|
|
if (this.attentionWindow || this.isHomescreen) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
};
|
|
|
|
AppWindow.prototype.hasPermission =
|
|
function aw_hasPermission(name) {
|
|
if (typeof(this._hasPermission) !== 'undefined' &&
|
|
this._hasPermission[name]) {
|
|
return this._hasPermission[name];
|
|
}
|
|
|
|
var mozPerms = navigator.mozPermissionSettings;
|
|
if (!mozPerms || !this.manifestURL) {
|
|
return false;
|
|
}
|
|
|
|
var value =
|
|
mozPerms.get(name, this.manifestURL, this.origin, false);
|
|
|
|
if (!this._hasPermission) {
|
|
this._hasPermission = {};
|
|
}
|
|
this._hasPermission[name] = (value === 'allow');
|
|
return this._hasPermission[name];
|
|
};
|
|
|
|
AppWindow.prototype.isHidden = function() {
|
|
return !this.element || this.element.classList.contains('hidden');
|
|
};
|
|
|
|
/**
|
|
* _hidewindow event handler.
|
|
*
|
|
* The event occurs when there's a higher priority window
|
|
* which is not an AppWindow show up.
|
|
* AppWindowManager will redirect the event to the current
|
|
* active app.
|
|
*
|
|
* If the event is because of attention window coming,
|
|
* evt.detail will be the instance of the attention window.
|
|
* If we are the opener of the attention window,
|
|
* we should not be sent to background due to
|
|
* @param {Event} evt The hidewindow event
|
|
*/
|
|
AppWindow.prototype._handle__hidewindow = function (evt) {
|
|
var attention = evt.detail;
|
|
if (attention.parentWindow &&
|
|
attention.parentWindow.instanceID === this.instanceID) {
|
|
return;
|
|
}
|
|
|
|
this.setVisible(false);
|
|
};
|
|
|
|
AppWindow.prototype.getElement = function() {
|
|
return this.element;
|
|
};
|
|
|
|
AppWindow.prototype.isFocusable = function() {
|
|
return this.loaded && this.isVisible();
|
|
};
|
|
|
|
exports.AppWindow = AppWindow;
|
|
}(window));
|