/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ 'use strict'; /* globals marionetteScriptFinished, waitFor */ var GaiaApps = { normalizeName: function(name) { return name.replace(/[- ]+/g, '').toLowerCase(); }, getInstalledApps: function() { let req = navigator.mozApps.mgmt.getAll(); req.onsuccess = function() { marionetteScriptFinished(req.result); }; }, getRunningApps: function(includeSystemApps) { let runningApps = GaiaApps.getApps(includeSystemApps); // Return a simplified version of the runningApps object which can be // JSON-serialized. let apps = {}; for (let app in runningApps) { let anApp = {}; for (let key in runningApps[app]) { if (['name', 'origin', 'manifest'].indexOf(key) > -1) { anApp[key] = runningApps[app][key]; } } apps[runningApps[app].origin] = anApp; } return apps; }, getApps: function(includeSystemApps) { let service = window.wrappedJSObject.Service; let apps = includeSystemApps ? service.query('getApps') : service.query('snapshot'); return apps; }, getRunningAppOrigin: function(name) { let apps = GaiaApps.getApps(true); for (let id in apps) { if (apps[id].name == name) { return apps[id].origin; } } return undefined; }, getPermission: function(appName, permissionName) { GaiaApps.locateWithName(appName, function(app) { console.log('Getting permission \'' + permissionName + '\' for ' + appName); var mozPerms = navigator.mozPermissionSettings; var result = mozPerms.get( permissionName, app.manifestURL, app.origin, false ); marionetteScriptFinished(result); }); }, setPermission: function(appName, permissionName, value) { GaiaApps.locateWithName(appName, function(app) { console.log('Setting permission \'' + permissionName + '\' for ' + appName + 'to \'' + value + '\''); var mozPerms = navigator.mozPermissionSettings; mozPerms.set( permissionName, value, app.manifestURL, app.origin, false ); marionetteScriptFinished(); }); }, setPermissionByUrl: function(manifestUrl, permissionName, value, entryPoint) { GaiaApps.locateWithManifestURL(manifestUrl, entryPoint, function(app) { console.log('Setting permission \'' + permissionName + '\' for ' + manifestUrl + 'to \'' + value + '\''); var mozPerms = navigator.mozPermissionSettings; mozPerms.set( permissionName, value, app.manifestURL, app.origin, false ); marionetteScriptFinished(); }); }, sendLocateResponse: function(aCallback, app, appName, launchPath, entryPoint) { var callback = aCallback || marionetteScriptFinished; if (callback === marionetteScriptFinished) { var result = false; if (typeof(app) === 'object') { result = { name: app.manifest.name, origin: app.origin, entryPoint: entryPoint || null, normalizedName: appName }; } callback(result); } else { callback(app, appName, launchPath, entryPoint); } }, locateWithName: function(name, aCallback) { var callback = aCallback || marionetteScriptFinished; let apps = window.wrappedJSObject.applications || window.wrappedJSObject.Applications; let installedApps = apps.installedApps; let normalizedSearchName = GaiaApps.normalizeName(name); for (let manifestURL in installedApps) { let app = installedApps[manifestURL]; let entryPoints = app.manifest.entry_points; if (entryPoints) { for (let ep in entryPoints) { let currentEntryPoint = entryPoints[ep]; let appName = currentEntryPoint.name; let launchPath = currentEntryPoint.launch_path; let locales = currentEntryPoint.locales; if (normalizedSearchName === GaiaApps.normalizeName(appName)) { return GaiaApps.sendLocateResponse(callback, app, appName, launchPath, ep); } else if (locales) { for (let id in locales) { let localisedAppName = locales[id].name; if (localisedAppName && normalizedSearchName === GaiaApps.normalizeName(localisedAppName)) { return GaiaApps.sendLocateResponse(callback, app, appName, launchPath, ep); } } } } } else { let appName = app.manifest.name; let launchPath = app.manifest.launch_path; let locales = app.manifest.locales; if (normalizedSearchName === GaiaApps.normalizeName(appName)) { return GaiaApps.sendLocateResponse(callback, app, appName, launchPath); } else if (locales) { for (let id in locales) { let localisedAppName = locales[id].name; if (localisedAppName && normalizedSearchName === GaiaApps.normalizeName(localisedAppName)) { return GaiaApps.sendLocateResponse(callback, app, appName, launchPath); } } } } } callback(false); }, locateWithManifestURL: function(manifestURL, entryPoint, aCallback) { var callback = aCallback || marionetteScriptFinished; var apps = window.wrappedJSObject.applications || window.wrappedJSObject.Applications; var app = apps.getByManifestURL(manifestURL); var launchPath; if (app != null) { GaiaApps._formatApp(app, manifestURL, entryPoint, callback, launchPath); } else { callback(false); } }, _formatApp: function(app, manifestURL, entryPoint, callback, launchPath) { if (entryPoint) { if (app.manifest.entry_points[entryPoint]) { launchPath = app.manifest.entry_points[entryPoint].launch_path; } else { app = null; } } else { launchPath = app.manifest.launch_path; } GaiaApps.sendLocateResponse(callback, app, manifestURL, launchPath, entryPoint); }, // Returns the number of running apps. // if includeSystemApps is true then system always-running // apps (eg Homescreen) will be counted numRunningApps: function(includeSystemApps) { return Object.keys(GaiaApps.getApps(includeSystemApps)).length; }, isRunning: function(origin) { var apps = GaiaApps.getApps(true); for (var id in apps) { if (apps[id].origin === origin) { return true; } } return false; }, // Kills the specified app. kill: function(aOrigin, aCallback) { var callback = aCallback || marionetteScriptFinished; if (!GaiaApps.isRunning(aOrigin)) { callback(false); } else { window.addEventListener('appterminated', function gt_onAppTerminated() { window.removeEventListener('appterminated', gt_onAppTerminated); waitFor( function() { console.log('app with origin \'' + aOrigin + '\' has terminated'); callback(true); }, function() { return !GaiaApps.isRunning(aOrigin); } ); }); console.log('terminating app with origin \'' + aOrigin + '\''); window.wrappedJSObject.Service.request('AppWindowManager:kill', aOrigin); } }, // Kills all running apps that may be killed by the user, defined as // being accessible by the Cards View/Stack Manager killAll: function() { let originsToClose = []; let that = this; let apps = GaiaApps.getApps(); for (let id in apps) { let origin = apps[id].origin; originsToClose.push(origin); } if (!originsToClose.length) { marionetteScriptFinished(true); return; } originsToClose.slice(0).forEach(function(origin) { GaiaApps.kill(origin, function() {}); }); // Even after the 'appterminated' event has been fired for an app, // it can still exist in the apps list, so wait until 1 or fewer // apps are running (since we don't close the homescreen app). waitFor( function() { marionetteScriptFinished(true); }, function() { return that.numRunningApps() <= 1; } ); }, launch: function(app, appName, launchPath, entryPoint) { if (app) { let manifestURL = app.manifestURL; let origin = app.origin; let sendResponse = function() { let result = GaiaApps.getDisplayedApp(); marionetteScriptFinished(result); }; let displayedApp = GaiaApps.getDisplayedApp(); if ((displayedApp.manifestURL == manifestURL && displayedApp.entryPoint == entryPoint) || (!displayedApp.manifestURL && displayedApp.origin == origin)) { console.log(`app with origin: ${displayedApp.origin} and manifestURL: ${displayedApp.manifestURL} and entryPoint: ${displayedApp.entryPoint} is already running`); sendResponse(); } else { window.addEventListener('windowopened', function appOpen() { window.removeEventListener('windowopened', appOpen); waitFor( function() { console.log(`app with origin: ${displayedApp.origin} and manifestURL: ${displayedApp.manifestURL} and entryPoint: ${displayedApp.entryPoint} has launched`); sendResponse(); }, function() { // wait for the displayed app to have expected manifestURL/origin let displayedApp = GaiaApps.getDisplayedApp(); if (displayedApp.manifestURL) { return displayedApp.manifestURL == manifestURL && displayedApp.entryPoint == entryPoint; } else { return displayedApp.origin == origin; } } ); }); console.log('launching app with name \'' + appName + '\''); app.launch(entryPoint || null); } } else { marionetteScriptFinished(false); } }, // Launches app with the specified name (e.g., 'Calculator'); returns the // an object with the app frame if successful, false if the app can't be // found, or times out if the app frame can't be found after launching the // app. launchWithName: function(name) { GaiaApps.locateWithName(name, this.launch.bind(this)); }, // Launches app with the specified manifestURL. returns the // an object with the app frame if successful, false if the app can't be // found, or times out if the app frame can't be found after launching the // app. // // This is prefered over launchWithName because localized builds have // different names launchWithManifestURL: function(manifestURL, entryPoint) { GaiaApps.locateWithManifestURL(manifestURL, entryPoint, this.launch); }, close: function(app, appName, entryPoint) { if (app) { let origin = GaiaApps.getRunningAppOrigin(appName); GaiaApps.kill(origin); } else { marionetteScriptFinished(false); } }, // Closes app with the specified name (e.g., 'Calculator'); returns nothing closeWithName: function(name) { GaiaApps.locateWithName(name, this.close); }, closeWithManifestURL: function(manifestURL, entryPoint) { GaiaApps.locateWithManifestURL(manifestURL, entryPoint, this.close); }, /** * Returns the currently displayed app. * This is more like getDisplayedFrame really, * as we return what frame the user is interacting with */ getDisplayedApp: function() { let app = window.wrappedJSObject.Service.query('getTopMostWindow'); if (!app || app.isLockscreen) { // fallback to app window. app = window.wrappedJSObject.Service .query('AppWindowManager.getActiveWindow').getTopMostWindow(); } let currentEntryPoint = null; // When browser is on a website, app.manifest is null if (app.manifest) { let entryPoints = app.manifest.entry_points; if (entryPoints) { for (let ep in entryPoints) { if (entryPoints[ep].launch_path == app.manifest.launch_path) { currentEntryPoint = ep; break; } } } } let result = { frame: (app.browser) ? app.browser.element : app.frame.firstChild, src: (app.browser) ? app.browser.element.src : app.iframe.src, name: app.name, origin: app.origin, manifestURL: app.manifestURL, entryPoint: currentEntryPoint }; return result; }, /** * Install the app with the specified ManifestURL */ install: function(manifestURL) { let req = navigator.mozApps.install(manifestURL); req.onsuccess = function() { marionetteScriptFinished(true); }; req.onerror = function() { marionetteScriptFinished(req.error); }; }, installPackage: function(manifestURL) { let req = navigator.mozApps.installPackage(manifestURL); req.onsuccess = function() { marionetteScriptFinished(true); }; req.onerror = function() { marionetteScriptFinished(req.error); }; }, /** * Uninstall the app with the specified ManifestURL */ uninstall: function(manifestURL) { GaiaApps.locateWithManifestURL(manifestURL, null, function uninstall(app) { if (typeof(app) !== 'object') { // App is already not there, so nothing to do here marionetteScriptFinished(true); return; } let req = navigator.mozApps.mgmt.uninstall(app); req.onsuccess = function() { marionetteScriptFinished(true); }; req.onerror = function() { marionetteScriptFinished(req.error); }; }); } };