/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ // // This file calls getElementById without waiting for an onload event, so it // must have a defer attribute or be included at the end of the . // // This module is responsible for launching apps and for allowing // the user to switch among apps and kill apps. Specifically, it handles: // launching apps, // killing apps // keeping track of the set of running apps (which we call tasks here) // keeping track of which task is displayed (the foreground task) // changing the foreground task // hiding all apps to display the homescreen // displaying the app switcher to allow the user to switch and kill apps // performing appropriate transition animations between: // the homescreen and an app // the homescreen and the switcher // an app and the homescreen // the switcher and the homescreen // the switcher and the current foreground task // the switcher and a different task // Handling Home key events to switch to the homescreen and the switcher // // The public API of the module is small. It defines an WindowManager object // with these methods: // // launch(origin): switch to the specified running app // kill(origin, callback): stop specified app // reload(origin): reload the given app // getDisplayedApp(): return the origin of the currently displayed app // setOrientationForApp(origin): set the phone to orientation to a given app // getAppFrame(origin): returns the iframe element for the specified origin // which is assumed to be running. This is only currently used // for tests and chrome stuff: see the end of the file // getRunningApps(): get the app references of the running apps. // // TODO // The "origin" does not actually refer to app's origin but rather a identifier // of the app reference that one gets from |getDisplayedApp()| or // iterates |getRunningApps|. The string is make up of the specified // launching entry point, origin, or the website url launched by wrapper. // It would be ideal if the variable get correctly named and it's rule is being // properly documented. // See https://bugzilla.mozilla.org/show_bug.cgi?id=796629 // var WindowManager = (function() { 'use strict'; function debug(str) { dump('WindowManager: ' + str + '\n'); } // Holds the origin of the home screen, which should be the first // app we launch through web activity during boot var homescreen = null; var homescreenURL = ''; var homescreenManifestURL = ''; var ftu = null; var ftuManifestURL = ''; var ftuURL = ''; var isRunningFirstRunApp = false; // keep the reference of inline activity frame here var inlineActivityFrames = []; var activityCallerOrigin = ''; // Some document elements we use var windows = document.getElementById('windows'); var screenElement = document.getElementById('screen'); var wrapperHeader = document.querySelector('#wrapper-activity-indicator'); var wrapperFooter = document.querySelector('#wrapper-footer'); var kTransitionTimeout = 1000; // Set this to true to debugging the transitions and state change var slowTransition = false; if (slowTransition) { windows.classList.add('slow-transition'); } // // The set of running apps. // This is a map from app origin to an object like this: // { // name: the app's name // manifest: the app's manifest object // frame: the iframe element that the app is displayed in // launchTime: last time when app gets active // } // var runningApps = {}; var numRunningApps = 0; // appendFrame() and removeFrame() maintain this count var nextAppId = 0; // to give each app's iframe a unique id attribute // The origin of the currently displayed app, or null if there isn't one var displayedApp = null; // Function to hide init starting logo function handleInitlogo(callback) { var initlogo = document.getElementById('initlogo'); initlogo.classList.add('hide'); initlogo.addEventListener('transitionend', function delInitlogo() { initlogo.removeEventListener('transitionend', delInitlogo); initlogo.parentNode.removeChild(initlogo); if (callback) { callback(); } }); }; // Public function. Return the origin of the currently displayed app // or null if there is none. function getDisplayedApp() { return displayedApp || null; } function requireFullscreen(origin) { var app = runningApps[origin]; if (!app) return false; var manifest = app.manifest; if (manifest.entry_points && manifest.type == 'certified') { var entryPoint = manifest.entry_points[origin.split('/')[3]]; if (entryPoint) return entryPoint.fullscreen; return false; } else { return manifest.fullscreen; } } // Make the specified app the displayed app. // Public function. Pass null to make the homescreen visible function launch(origin) { // If the origin is indeed valid we make that app as the displayed app. if (isRunning(origin)) { setDisplayedApp(origin); return; } // If the origin is null, make the homescreen visible. if (origin == null) { setDisplayedApp(homescreen); return; } // At this point, we have no choice but to show the homescreen. // We cannot launch/relaunch a given app based on the "origin" because // we would need the manifest URL and the specific entry point. console.warn('No running app is being identified as "' + origin + '". ' + 'Showing home screen instead.'); setDisplayedApp(homescreen); } function isRunning(origin) { return runningApps.hasOwnProperty(origin); } function getAppFrame(origin) { if (isRunning(origin)) return runningApps[origin].frame; else return null; } // 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 function setAppSize(origin, changeActivityFrame) { var app = runningApps[origin]; if (!app) return; var frame = app.frame; var manifest = app.manifest; var cssWidth = window.innerWidth + 'px'; var cssHeight = window.innerHeight - StatusBar.height; if ('wrapper' in frame.dataset) { cssHeight -= 10; } cssHeight += 'px'; if (!screenElement.classList.contains('attention') && requireFullscreen(origin)) { cssHeight = window.innerHeight + 'px'; } frame.style.width = cssWidth; frame.style.height = cssHeight; // We will call setInlineActivityFrameSize() // if changeActivityFrame is not explicitly set to false. if (changeActivityFrame !== false) setInlineActivityFrameSize(); } // App's height is relevant to keyboard height function setAppHeight(keyboardHeight) { var app = runningApps[displayedApp]; if (!app) return; var frame = app.frame; var manifest = app.manifest; var cssHeight = window.innerHeight - StatusBar.height - keyboardHeight + 'px'; if (!screenElement.classList.contains('attention') && requireFullscreen(displayedApp)) { cssHeight = window.innerHeight - keyboardHeight + 'px'; } frame.style.height = cssHeight; setInlineActivityFrameSize(); } // Copy the dimension of the currently displayed app function setInlineActivityFrameSize() { if (!inlineActivityFrames.length) return; var app = runningApps[displayedApp]; var appFrame = app.frame; var frame = inlineActivityFrames[inlineActivityFrames.length - 1]; frame.style.width = appFrame.style.width; if (document.mozFullScreen) { frame.style.height = window.innerHeight + 'px'; frame.style.top = '0px'; } else { if ('wrapper' in appFrame.dataset) { frame.style.height = window.innerHeight - StatusBar.height + 'px'; } else { frame.style.height = appFrame.style.height; } frame.style.top = appFrame.offsetTop + 'px'; } } function setFrameBackgroundBlob(frame, blob, transparent) { URL.revokeObjectURL(frame.dataset.bgObjectURL); delete frame.dataset.bgObjectURL; var objectURL = URL.createObjectURL(blob); frame.dataset.bgObjectURL = objectURL; var backgroundCSS = '-moz-linear-gradient(top, rgba(0,0,0,0.5) 0%, rgba(0,0,0,0.5) 100%),' + 'url(' + objectURL + '),' + ((transparent) ? 'transparent' : '#fff'); frame.style.background = backgroundCSS; } function clearFrameBackground(frame) { if (!('bgObjectURL' in frame.dataset)) return; URL.revokeObjectURL(frame.dataset.bgObjectURL); delete frame.dataset.bgObjectURL; frame.style.background = ''; } var openFrame = null; var closeFrame = null; var openCallback = null; var closeCallback = null; var transitionOpenCallback = null; var transitionCloseCallback = null; // Use setOpenFrame() to reset the CSS classes set // to the current openFrame (before overwriting the reference) function setOpenFrame(frame) { if (openFrame) { removeFrameClasses(openFrame); } openFrame = frame; } // Use setCloseFrame() to reset the CSS classes set // to the current closeFrame (before overwriting the reference) function setCloseFrame(frame) { if (closeFrame) { removeFrameClasses(closeFrame); // closeFrame should not be set to active closeFrame.classList.remove('active'); } closeFrame = frame; } // Remove these visible className from frame so we will not ended // up having a frozen frame in the middle of the transition function removeFrameClasses(frame) { var classNames = ['opening', 'closing', 'opening-switching', 'opening-card', 'closing-card']; var classList = frame.classList; classNames.forEach(function removeClass(className) { classList.remove(className); }); } windows.addEventListener('transitionend', function frameTransitionend(evt) { var prop = evt.propertyName; var frame = evt.target; if (prop !== 'transform') return; var classList = frame.classList; if (classList.contains('inlineActivity')) { if (classList.contains('active')) { if (openFrame) openFrame.firstChild.focus(); setOpenFrame(null); } else { windows.removeChild(frame); } return; } if (screenElement.classList.contains('switch-app')) { if (classList.contains('closing')) { classList.remove('closing'); classList.add('closing-card'); if (openFrame) { if (openFrame.classList.contains('opening-card')) { openFrame.classList.remove('opening-card'); openFrame.classList.add('opening-switching'); } else { // Skip the opening-card and opening-switching transition // because the closing-card transition had already finished here. if (openFrame.classList.contains('fullscreen-app')) { screenElement.classList.add('fullscreen-app'); } openFrame.classList.add('opening'); } } } else if (classList.contains('closing-card')) { windowClosed(frame); setTimeout(closeCallback); closeCallback = null; } else if (classList.contains('opening-switching')) { // If the opening app need to be full screen, switch to full screen if (classList.contains('fullscreen-app')) { screenElement.classList.add('fullscreen-app'); } classList.remove('opening-switching'); classList.add('opening'); } else if (classList.contains('opening')) { windowOpened(frame); setTimeout(openCallback); openCallback = null; setCloseFrame(null); setOpenFrame(null); screenElement.classList.remove('switch-app'); } return; } if (classList.contains('opening')) { windowOpened(frame); setTimeout(openCallback); openCallback = null; setOpenFrame(null); } else if (classList.contains('closing')) { windowClosed(frame); setTimeout(closeCallback); closeCallback = null; setCloseFrame(null); } }); // Executes when the opening transition scale the app // to full size. function windowOpened(frame) { var iframe = frame.firstChild; frame.classList.add('active'); windows.classList.add('active'); if ('wrapper' in frame.dataset) { wrapperFooter.classList.add('visible'); } // Take the focus away from the currently displayed app var app = runningApps[displayedApp]; if (app && app.iframe) app.iframe.blur(); // Give the focus to the frame iframe.focus(); if (!TrustedUIManager.isVisible() && !isRunningFirstRunApp) { // Set homescreen visibility to false toggleHomescreen(false); } // Set displayedApp to the new value displayedApp = iframe.dataset.frameOrigin; // Set orientation for the new app setOrientationForApp(displayedApp); // Dispatch an 'appopen' event. var manifestURL = runningApps[displayedApp].manifestURL; var evt = document.createEvent('CustomEvent'); evt.initCustomEvent('appopen', true, false, { manifestURL: manifestURL, origin: displayedApp }); frame.dispatchEvent(evt); } // Executes when app closing transition finishes. function windowClosed(frame) { var iframe = frame.firstChild; // If the FTU is closing, make sure we save this state if (iframe.src == ftuURL) { isRunningFirstRunApp = false; document.getElementById('screen').classList.remove('ftu'); window.asyncStorage.setItem('ftu.enabled', false); // Done with FTU, letting everyone know var evt = document.createEvent('CustomEvent'); evt.initCustomEvent('ftudone', /* canBubble */ true, /* cancelable */ false, {}); window.dispatchEvent(evt); } frame.classList.remove('active'); windows.classList.remove('active'); // set the closed frame visibility to false if ('setVisible' in iframe) iframe.setVisible(false); screenElement.classList.remove('fullscreen-app'); } // The following things needs to happen when firstpaint happens. // We centralize all that here but not all of them applies. windows.addEventListener('mozbrowserfirstpaint', function firstpaint(evt) { var iframe = evt.target; var frame = iframe.parentNode; // remove the unpainted flag delete iframe.dataset.unpainted; setTimeout(function firstpainted() { // Save the screenshot // Remove the background only until we actually got the screenshot, // because the getScreenshot() call will be pushed back by // painting/loading in the child process; when we got the screenshot, // that means the app is mostly loaded. // (as opposed to plain white firstpaint) saveAppScreenshot(frame, function screenshotTaken() { // Remove the default background frame.classList.remove('default-background'); // Remove the screenshot from frame clearFrameBackground(frame); }); }); }); // setFrameBackground() will attach the screenshot background to // the given frame. // The callback could be sync or async (depend on whether we need // the screenshot from database or not) function setFrameBackground(frame, callback, transparent) { var iframe = frame.firstChild; // If the frame is painted, or there is already background image present // start the transition right away. if (!('unpainted' in iframe.dataset) || ('bgObjectURL' in frame.dataset)) { callback(); return; } // Get the screenshot from the database getAppScreenshotFromDatabase(iframe.src || iframe.dataset.frameOrigin, function(screenshot) { // If firstpaint is faster than database, we will not transition // with screenshot. if (!('unpainted' in iframe.dataset)) { callback(); return; } if (!screenshot) { // put a default background frame.classList.add('default-background'); callback(); return; } // set the screenshot as the background of the frame itself. // we are safe to do so since there is nothing on it yet. setFrameBackgroundBlob(frame, screenshot, transparent); // start the transition callback(); }); } // On-disk database for window manager. // It's only for app screenshots right now. var database = null; var DB_SCREENSHOT_OBJSTORE = 'screenshots'; (function openDatabase() { var DB_VERSION = 2; var DB_NAME = 'window_manager'; var req = window.indexedDB.open(DB_NAME, DB_VERSION); req.onerror = function() { console.error('Window Manager: opening database failed.'); }; req.onupgradeneeded = function databaseUpgradeneeded() { database = req.result; if (database.objectStoreNames.contains(DB_SCREENSHOT_OBJSTORE)) database.deleteObjectStore(DB_SCREENSHOT_OBJSTORE); var store = database.createObjectStore( DB_SCREENSHOT_OBJSTORE, { keyPath: 'url' }); }; req.onsuccess = function databaseSuccess() { database = req.result; }; })(); function putAppScreenshotToDatabase(url, data) { if (!database) return; var txn = database.transaction(DB_SCREENSHOT_OBJSTORE, 'readwrite'); txn.onerror = function() { console.warn( 'Window Manager: transaction error while trying to save screenshot.'); }; var store = txn.objectStore(DB_SCREENSHOT_OBJSTORE); var req = store.put({ url: url, screenshot: data }); req.onerror = function(evt) { console.warn( 'Window Manager: put error while trying to save screenshot.'); }; } function getAppScreenshotFromDatabase(url, callback) { if (!database) { console.warn( 'Window Manager: Neither database nor app frame is ' + 'ready for getting screenshot.'); callback(); return; } var req = database.transaction(DB_SCREENSHOT_OBJSTORE) .objectStore(DB_SCREENSHOT_OBJSTORE).get(url); req.onsuccess = function() { if (!req.result) { console.log('Window Manager: No screenshot in database. ' + 'This is expected from a fresh installed app.'); callback(); return; } callback(req.result.screenshot, true); } req.onerror = function(evt) { console.warn('Window Manager: get screenshot from database failed.'); callback(); }; } function deleteAppScreenshotFromDatabase(url) { var txn = database.transaction(DB_SCREENSHOT_OBJSTORE); var store = txn.objectStore(DB_SCREENSHOT_OBJSTORE); store.delete(url); } function getAppScreenshotFromFrame(frame, callback) { if (!frame) { callback(); return; } var iframe = frame.firstChild; var req = iframe.getScreenshot(iframe.offsetWidth, iframe.offsetHeight); req.onsuccess = function gotScreenshotFromFrame(evt) { var result = evt.target.result; callback(result, false); }; req.onerror = function gotScreenshotFromFrameError(evt) { console.warn('Window Manager: getScreenshot failed.'); callback(); }; } // Meta method for get the screenshot from the app frame, // and save it to database. function saveAppScreenshot(frame, callback) { getAppScreenshotFromFrame(frame, function gotScreenshot(screenshot) { if (callback) callback(screenshot); if (!screenshot) return; var iframe = frame.firstChild; putAppScreenshotToDatabase(iframe.src || iframe.dataset.frameOrigin, screenshot); }); } // Perform an "open" animation for the app's iframe function openWindow(origin, callback) { var app = runningApps[origin]; setOpenFrame(app.frame); openCallback = callback || function() {}; // set the size of the opening app setAppSize(origin); if (origin === homescreen) { // We cannot apply background screenshot to home screen app since // the screenshot is encoded in JPEG and the alpha channel is // not perserved. See // https://bugzilla.mozilla.org/show_bug.cgi?id=801676#c33 // If that resolves, // setFrameBackground(openFrame, gotBackground, true); // will simply work here. // Call the openCallback only once. We have to use tmp var as // openCallback can be a method calling the callback // (like the `removeFrame` callback in `kill()` ). var tmpCallback = openCallback; openCallback = null; tmpCallback(); windows.classList.add('active'); openFrame.classList.add('homescreen'); openFrame.firstChild.focus(); setOpenFrame(null); displayedApp = origin; return; } if (requireFullscreen(origin)) screenElement.classList.add('fullscreen-app'); transitionOpenCallback = function startOpeningTransition() { // We have been canceled by another transition. if (!openFrame || transitionOpenCallback != startOpeningTransition) return; // Make sure we're not called twice. transitionOpenCallback = null; if (!screenElement.classList.contains('switch-app')) { openFrame.classList.add('opening'); } else if (!openFrame.classList.contains('opening')) { openFrame.classList.add('opening-card'); } }; if ('unpainted' in openFrame.firstChild.dataset) { waitForNextPaintOrBackground(openFrame, transitionOpenCallback); } else { waitForNextPaint(openFrame, transitionOpenCallback); } // Set the frame to be visible. if ('setVisible' in openFrame.firstChild) { if (!AttentionScreen.isFullyVisible()) { openFrame.firstChild.setVisible(true); } else { // If attention screen is fully visible now, // don't give the open frame visible. // This is the case that homescreen is restarted behind attention screen openFrame.firstChild.setVisible(false); } } } function waitForNextPaintOrBackground(frame, callback) { var waiting = true; function proceed() { if (waiting) { waiting = false; callback(); } } waitForNextPaint(frame, proceed); setFrameBackground(frame, proceed); } function waitForNextPaint(frame, callback) { function onNextPaint() { clearTimeout(timeout); callback(); } var iframe = frame.firstChild; // Register a timeout in case we don't receive // nextpaint in an acceptable time frame. var timeout = setTimeout(function() { if ('removeNextPaintListener' in iframe) iframe.removeNextPaintListener(onNextPaint); callback(); }, kTransitionTimeout); if ('addNextPaintListener' in iframe) iframe.addNextPaintListener(onNextPaint); } // Perform a "close" animation for the app's iframe function closeWindow(origin, callback) { var app = runningApps[origin]; setCloseFrame(app.frame); closeCallback = callback || function() {}; // Animate the window close. Ensure the homescreen is in the // foreground since it will be shown during the animation. var homescreenFrame = ensureHomescreen(); // invoke openWindow to show homescreen here openWindow(homescreen, null); // Take keyboard focus away from the closing window closeFrame.firstChild.blur(); // set orientation for homescreen app setOrientationForApp(homescreen); // Set the size of both homescreen app and the closing app // since the orientation had changed. setAppSize(homescreen); setAppSize(origin); // Send a synthentic 'appwillclose' event. // The keyboard uses this and the appclose event to know when to close // See https://github.com/andreasgal/gaia/issues/832 var evt = document.createEvent('CustomEvent'); evt.initCustomEvent('appwillclose', true, false, { origin: origin }); closeFrame.dispatchEvent(evt); if ('wrapper' in closeFrame.dataset) { wrapperHeader.classList.remove('visible'); wrapperFooter.classList.remove('visible'); } transitionCloseCallback = function startClosingTransition() { // We have been canceled by another transition. if (!closeFrame || transitionCloseCallback != startClosingTransition) return; // Make sure we're not called twice. transitionCloseCallback = null; // Start the transition closeFrame.classList.add('closing'); closeFrame.classList.remove('active'); }; waitForNextPaint(homescreenFrame, transitionCloseCallback); } // Perform a "switching" animation for the closing frame and the opening frame function switchWindow(origin, callback) { // This will trigger different transition to both openWindow() // and closeWindow() transition. screenElement.classList.add('switch-app'); // Ask closeWindow() to start closing the displayedApp closeWindow(displayedApp, callback); // Ask openWindow() to show a card on the right waiting to be opened openWindow(origin); } // Ensure the homescreen is loaded and return its frame. Restarts // the homescreen app if it was killed in the background. // Note: this function would not invoke openWindow(homescreen), // which should be handled in setDisplayedApp and in closeWindow() function ensureHomescreen(reset) { // If the url of the homescreen is not known at this point do nothing. if (!homescreen || !homescreenManifestURL) { return null; } if (!isRunning(homescreen)) { var app = Applications.getByManifestURL(homescreenManifestURL); appendFrame(null, homescreen, homescreenURL, app.manifest.name, app.manifest, app.manifestURL); runningApps[homescreen].iframe.dataset.start = Date.now(); setAppSize(homescreen); } else if (reset) { runningApps[homescreen].iframe.src = homescreenURL; setAppSize(homescreen); } return runningApps[homescreen].frame; } function retrieveHomescreen(callback) { var lock = navigator.mozSettings.createLock(); var setting = lock.get('homescreen.manifestURL'); setting.onsuccess = function() { var app = Applications.getByManifestURL(this.result['homescreen.manifestURL']); // XXX This is a one-day workaround to not break everybody and make sure // work can continue. if (!app) { var tmpURL = document.location.toString() .replace('system', 'homescreen') .replace('index.html', 'manifest.webapp'); app = Applications.getByManifestURL(tmpURL); } if (app) { homescreenManifestURL = app.manifestURL; homescreen = app.origin; homescreenURL = app.origin + '/index.html#root'; callback(app); } } } function skipFTU() { document.getElementById('screen').classList.remove('ftuStarting'); handleInitlogo(); setDisplayedApp(homescreen); } // Check if the FTU was executed or not, if not, get a // reference to the app and launch it. function retrieveFTU() { window.asyncStorage.getItem('ftu.enabled', function getItem(launchFTU) { document.getElementById('screen').classList.add('ftuStarting'); if (launchFTU === false) { skipFTU(); return; } var lock = navigator.mozSettings.createLock(); var req = lock.get('ftu.manifestURL'); req.onsuccess = function() { ftuManifestURL = this.result['ftu.manifestURL']; if (!ftuManifestURL) { dump('FTU manifest cannot be found skipping.\n'); skipFTU(); return; } ftu = Applications.getByManifestURL(ftuManifestURL); if (!ftu) { dump('Opps, bogus FTU manifest.\n'); skipFTU(); return; } ftuURL = ftu.origin + ftu.manifest.entry_points['ftu'].launch_path; ftu.launch('ftu'); }; req.onerror = function() { dump('Couldn\'t get the ftu manifestURL.\n'); skipFTU(); }; }); } // Hide current app function hideCurrentApp(callback) { if (displayedApp == null || displayedApp == homescreen) return; toggleHomescreen(true); var frame = getAppFrame(displayedApp); frame.classList.add('back'); frame.classList.remove('restored'); if (callback) { frame.addEventListener('transitionend', function execCallback() { frame.style.visibility = 'hidden'; frame.removeEventListener('transitionend', execCallback); callback(); }); } } // If app parameter is passed, // it means there's a specific app needs to be restored // instead of current app function restoreCurrentApp(app) { if (app) { // Restore app visibility immediately but don't open it. var frame = getAppFrame(app); frame.style.visibility = 'visible'; frame.classList.remove('back'); } else { app = displayedApp; toggleHomescreen(false); var frame = getAppFrame(app); frame.style.visibility = 'visible'; frame.classList.remove('back'); frame.classList.add('restored'); frame.addEventListener('transitionend', function removeRestored() { frame.removeEventListener('transitionend', removeRestored); frame.classList.remove('restored'); }); } } function toggleHomescreen(visible) { var homescreenFrame = ensureHomescreen(); if (homescreenFrame && 'setVisible' in homescreenFrame.firstChild) homescreenFrame.firstChild.setVisible(visible); } // Switch to a different app function setDisplayedApp(origin, callback) { var currentApp = displayedApp, newApp = origin || homescreen; var isFirstRunApplication = !currentApp && (origin == ftuURL); var homescreenFrame = null; if (!isFirstRunApplication) { // Returns the frame reference of the home screen app. // Restarts the homescreen app if it was killed in the background. homescreenFrame = ensureHomescreen(); } // Cancel transitions waiting to be started. transitionOpenCallback = null; transitionCloseCallback = null; // Discard any existing activity stopInlineActivity(true); // Before starting a new transition, let's make sure current transitions // are stopped and the state classes are cleaned up. // visibility status should also be reset. if (openFrame && 'setVisible' in openFrame.firstChild) openFrame.firstChild.setVisible(false); if (closeFrame && 'setVisible' in closeFrame.firstChild) closeFrame.firstChild.setVisible(false); if (!isFirstRunApplication && newApp == homescreen && !AttentionScreen.isFullyVisible()) { toggleHomescreen(true); } setOpenFrame(null); setCloseFrame(null); screenElement.classList.remove('switch-app'); screenElement.classList.remove('fullscreen-app'); // Dispatch an appwillopen event only when we open an app if (newApp != currentApp) { var evt = document.createEvent('CustomEvent'); evt.initCustomEvent('appwillopen', true, true, { origin: newApp }); var app = runningApps[newApp]; // Allows listeners to cancel app opening and so stay on homescreen if (!app.frame.dispatchEvent(evt)) { if (typeof(callback) == 'function') callback(); return; } var iframe = app.iframe; // unpainted means that the app is cold booting // if it is, we're going to listen for Browser API's loadend event // which indicates that the iframe's document load is complete // // if the app is not cold booting (is in memory) we will listen // to appopen event, which is fired when the transition to the // app window is complete. // // [w] - warm boot (app is in memory, just transition to it) // [c] - cold boot (app has to be booted, we show it's document load // time) var type; if ('unpainted' in iframe.dataset) { type = 'mozbrowserloadend'; } else { iframe.dataset.start = Date.now(); type = 'appopen'; } app.frame.addEventListener(type, function apploaded(e) { e.target.removeEventListener(e.type, apploaded); var evt = document.createEvent('CustomEvent'); evt.initCustomEvent('apploadtime', true, false, { time: parseInt(Date.now() - iframe.dataset.start), type: (e.type == 'appopen') ? 'w' : 'c' }); iframe.dispatchEvent(evt); }); } // Case 1: the app is already displayed if (currentApp && currentApp == newApp) { if (newApp == homescreen) { // relaunch homescreen openWindow(homescreen, callback); } else if (callback) { // Just run the callback right away if it is not homescreen callback(); } } // Case 2: null --> app else if (isFirstRunApplication) { isRunningFirstRunApp = true; openWindow(newApp, function windowOpened() { handleInitlogo(function() { var mainScreen = document.getElementById('screen'); mainScreen.classList.add('ftu'); mainScreen.classList.remove('ftuStarting'); }); }); } // Case 3: null->homescreen || homescreen->app else if ((!currentApp && newApp == homescreen) || (currentApp == homescreen && newApp)) { openWindow(newApp, callback); } // Case 4: app->homescreen else if (currentApp && currentApp != homescreen && newApp == homescreen) { // For screenshot to catch current window size closeWindow(currentApp, callback); } // Case 5: app-to-app transition else { switchWindow(newApp, callback); } // Set homescreen as active, // to control the z-index between homescreen & keyboard iframe if ((newApp == homescreen) && homescreenFrame) { homescreenFrame.classList.add('active'); } else { homescreenFrame.classList.remove('active'); } // Record the time when app was launched, // need this to display apps in proper order on CardsView. // We would also need this to determined the freshness of the frame // for making screenshots. if (newApp) runningApps[newApp].launchTime = Date.now(); // If the app has a attention screen open, displaying it AttentionScreen.showForOrigin(newApp); } function setOrientationForApp(origin) { if (origin == null) { // No app is currently running. screen.mozLockOrientation('portrait-primary'); return; } var app = runningApps[origin]; if (!app) return; var manifest = app.manifest; if (manifest.orientation) { var rv = screen.mozLockOrientation(manifest.orientation); if (rv === false) { console.warn('screen.mozLockOrientation() returned false for', origin, 'orientation', manifest.orientation); } } else { // If no orientation was requested, then let it rotate screen.mozUnlockOrientation(); } } var isOutOfProcessDisabled = false; SettingsListener.observe('debug.oop.disabled', false, function(value) { isOutOfProcessDisabled = value; }); function createFrame(origFrame, origin, url, name, manifest, manifestURL) { var iframe = origFrame || document.createElement('iframe'); iframe.setAttribute('mozallowfullscreen', 'true'); var frame = document.createElement('div'); frame.appendChild(iframe); frame.className = 'appWindow'; iframe.dataset.frameOrigin = origin; // Save original frame URL in order to restore it on frame load error iframe.dataset.frameURL = url; // Note that we don't set the frame size here. That will happen // when we display the app in setDisplayedApp() // frames are began unpainted. iframe.dataset.unpainted = true; if (!manifestURL) { frame.setAttribute('data-wrapper', 'true'); return frame; } // Most apps currently need to be hosted in a special 'mozbrowser' iframe. // They also need to be marked as 'mozapp' to be recognized as apps by the // platform. iframe.setAttribute('mozbrowser', 'true'); // These apps currently have bugs preventing them from being // run out of process. All other apps will be run OOP. // var outOfProcessBlackList = [ 'Browser' // Requires nested content processes (bug 761935). This is not // on the schedule for v1. ]; if (!isOutOfProcessDisabled && outOfProcessBlackList.indexOf(name) === -1) { // FIXME: content shouldn't control this directly iframe.setAttribute('remote', 'true'); } iframe.setAttribute('mozapp', manifestURL); iframe.src = url; return frame; } function appendFrame(origFrame, origin, url, name, manifest, manifestURL) { // Create the