Repository: cordova-app-harness Updated Branches: refs/heads/master ba6a40533 -> 18f4ce7af
Write per-app cordova_plugins.js to contain only required plugins This also adds plugin metadata for CRX-based apps Project: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/commit/18f4ce7a Tree: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/tree/18f4ce7a Diff: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/diff/18f4ce7a Branch: refs/heads/master Commit: 18f4ce7afd0692cd286c3cf402ca4e009188dfb4 Parents: 6904274 Author: Andrew Grieve <[email protected]> Authored: Thu May 1 11:17:21 2014 -0400 Committer: Andrew Grieve <[email protected]> Committed: Thu May 1 11:19:09 2014 -0400 ---------------------------------------------------------------------- www/cdvah/js/CrxInstaller.js | 76 ++++++++++++++++++++++++++++++++++++ www/cdvah/js/Installer.js | 39 ++++++------------ www/cdvah/js/PluginMetadata.js | 65 +++++++++++++++++++++++++++--- www/cdvah/js/ResourcesLoader.js | 12 ++++++ www/cdvah/js/ServeInstaller.js | 75 +++++++++++++++++++++-------------- 5 files changed, 206 insertions(+), 61 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/18f4ce7a/www/cdvah/js/CrxInstaller.js ---------------------------------------------------------------------- diff --git a/www/cdvah/js/CrxInstaller.js b/www/cdvah/js/CrxInstaller.js index a6fdd5a..38d3bce 100644 --- a/www/cdvah/js/CrxInstaller.js +++ b/www/cdvah/js/CrxInstaller.js @@ -1,5 +1,66 @@ (function(){ 'use strict'; + // TODO: put these constants into an npm module. + // TODO: Move CRX support into MobileChromeApps/harness. + var DEFAULT_PLUGINS = [ + 'org.apache.cordova.file', + 'org.apache.cordova.inappbrowser', + 'org.apache.cordova.network-information', + 'org.apache.cordova.keyboard', + 'org.apache.cordova.statusbar', + 'org.chromium.navigation', + 'org.chromium.bootstrap', + 'org.chromium.i18n', + 'org.chromium.polyfill.CustomEvent', + 'org.chromium.polyfill.xhr_features', + 'org.chromium.polyfill.blob_constructor' + ]; + + var PLUGIN_MAP = { + 'alarms': ['org.chromium.alarms'], + 'fileSystem': ['org.chromium.fileSystem', + 'org.chromium.FileChooser'], + 'gcm': ['org.chromium.gcm'], + 'identity': ['org.chromium.identity'], + 'idle': ['org.chromium.idle'], + 'notifications': ['org.chromium.notifications'], + 'payments': ['com.google.payments'], + 'power': ['org.chromium.power'], + 'pushMessaging': ['org.chromium.pushMessaging'], + 'socket': ['org.chromium.socket'], + 'storage': ['org.chromium.storage'], + 'syncFileSystem': ['org.chromium.syncFileSystem'], + 'unlimitedStorage': [] + }; + function extractPluginsFromManifest(manifest) { + var permissions = [], + plugins = [], + i; + if (manifest.permissions) { + for (i = 0; i < manifest.permissions.length; ++i) { + if (typeof manifest.permissions[i] === 'string') { + var matchPatternParts = /<all_urls>|([^:]+:\/\/[^\/]+)(\/.*)$/.exec(manifest.permissions[i]); + if (!matchPatternParts) { + permissions.push(manifest.permissions[i]); + } + } else { + permissions = permissions.concat(Object.keys(manifest.permissions[i])); + } + } + } + for (i = 0; i < permissions.length; i++) { + var pluginsForPermission = PLUGIN_MAP[permissions[i]]; + if (pluginsForPermission) { + for (var j = 0; j < pluginsForPermission.length; ++j) { + plugins.push(pluginsForPermission[j]); + } + } else { + console.warn('Permission not supported: ' + permissions[i] + ' (skipping)'); + } + } + return DEFAULT_PLUGINS.concat(plugins); + } + /* global myApp */ myApp.run(['$q', 'Installer', 'AppsService', 'ResourcesLoader', 'urlCleanup', function($q, Installer, AppsService, ResourcesLoader, urlCleanup){ @@ -13,6 +74,20 @@ CrxInstaller.prototype.type = 'crx'; + CrxInstaller.prototype.getPluginMetadata = function() { + return ResourcesLoader.readJSONFileContents(this.installPath + '/www/manifest.json') + .then(function(manifestJson) { + var pluginIds = extractPluginsFromManifest(manifestJson); + var harnessPluginMetadata = cordova.require('cordova/plugin_list').metadata; + var ret = {}; + // Make all versions match what is installed. + for (var i = 0; i < pluginIds.length; ++i) { + ret[pluginIds[i]] = harnessPluginMetadata[pluginIds[i]] || '0'; + } + return ret; + }); + }; + CrxInstaller.prototype.doUpdateApp = function() { var installPath = this.installPath; var platformConfig = location.pathname.replace(/\/[^\/]*$/, '/crx_files/config.' + platformId + '.xml'); @@ -26,6 +101,7 @@ return ResourcesLoader.extractZipFile(crxFile, installPath + '/www'); }).then(function() { // Copy in the config.<platform>.xml file from the harness. + // TODO: We should be constructing this based on installed plugins. return ResourcesLoader.downloadFromUrl(platformConfig, targetConfig); }); }; http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/18f4ce7a/www/cdvah/js/Installer.js ---------------------------------------------------------------------- diff --git a/www/cdvah/js/Installer.js b/www/cdvah/js/Installer.js index 339430c..76894c2 100644 --- a/www/cdvah/js/Installer.js +++ b/www/cdvah/js/Installer.js @@ -31,24 +31,6 @@ }); } - function getAppPlugins(cordovaPluginsFile) { - console.log('Reading plugins from: ' + cordovaPluginsFile); - return ResourcesLoader.readFileContents(cordovaPluginsFile) - .then(function(contents) { - if (!contents) { - throw new Error('cordova_plugins.js file is empty. Something has gone wrong with "cordova prepare".'); - } - - // Extract the JSON data from inside the JS file. - // It's between two magic comments created by Plugman. - var startIndex = contents.indexOf('TOP OF METADATA') + 16; - var endIndex = contents.indexOf('// BOTTOM OF METADATA'); - var target = contents.substring(startIndex, endIndex); - var metadata = JSON.parse(target); - return metadata; - }); - } - function Installer(url, appId) { this.url = url; this.appId = appId || ''; @@ -70,21 +52,27 @@ return this.doUpdateApp() .then(function() { self.lastUpdated = new Date(); - if (self.type === 'crx') { - // No cordova_plugins.js to read for .crx-based apps. - return $q.when({}); - } else { - return getAppPlugins(installPath + '/www/cordova_plugins.js'); - } + return self.getPluginMetadata(); }, null, function(status) { self.updatingStatus = Math.round(status * 100); }).then(function(metadata) { self.plugins = PluginMetadata.process(metadata); + var pluginIds = Object.keys(metadata); + var newPluginsFileData = PluginMetadata.createNewPluginListFile(pluginIds); + return ResourcesLoader.writeFileContents(installPath + '/www/cordova_plugins.js', newPluginsFileData); }).finally(function() { self.updatingStatus = null; }); }; + Installer.prototype.doUpdateApp = function() { + throw new Error('Installer ' + this.type + ' failed to implement doUpdateApp.'); + }; + + Installer.prototype.getPluginMetadata = function() { + throw new Error('Installer ' + this.type + ' failed to implement getPluginMetadata.'); + }; + Installer.prototype.deleteFiles = function() { this.lastUpdated = null; if (this.installPath) { @@ -124,9 +112,8 @@ UrlRemap.injectJsForUrl('^(?!' + harnessUrl + ')', injectString); // Allow navigations back to the menu. UrlRemap.setResetUrl('^' + harnessUrl); - // Override cordova.js, cordova_plugins.js, and www/plugins to point at bundled plugins. + // Override cordova.js, and www/plugins to point at bundled plugins. UrlRemap.aliasUri('^(?!app-harness://).*/www/cordova\\.js.*', '.+', 'app-harness:///cordova.js', false /* redirect */, true /* allowFurtherRemapping */); - UrlRemap.aliasUri('^(?!app-harness://).*/www/cordova_plugins\\.js.*', '.+', 'app-harness:///cordova_plugins.js', false /* redirect */, true /* allowFurtherRemapping */); UrlRemap.aliasUri('^(?!app-harness://).*/www/plugins/.*', '^.*?/www/plugins/' , 'app-harness:///plugins/', false /* redirect */, true /* allowFurtherRemapping */); // Make any references to www/ point to the app's install location. http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/18f4ce7a/www/cdvah/js/PluginMetadata.js ---------------------------------------------------------------------- diff --git a/www/cdvah/js/PluginMetadata.js b/www/cdvah/js/PluginMetadata.js index 8a2bed4..f1ee97b 100644 --- a/www/cdvah/js/PluginMetadata.js +++ b/www/cdvah/js/PluginMetadata.js @@ -2,7 +2,8 @@ 'use strict'; /* global myApp */ myApp.factory('PluginMetadata', function() { - var harnessPlugins = cordova.require('cordova/plugin_list').metadata; + var harnessPluginList = cordova.require('cordova/plugin_list'); + var harnessPluginMetadata = harnessPluginList.metadata; // Returns -1, (a > b), 0 (a = b), or 1 (a < b). function semverCompare(a, b) { @@ -20,6 +21,20 @@ } return { + extractPluginMetadata: function(pluginListFileContents) { + if (!pluginListFileContents) { + throw new Error('cordova_plugins.js file is empty. Something has gone wrong with "cordova prepare".'); + } + + // Extract the JSON data from inside the JS file. + // It's between two magic comments created by Plugman. + var startIndex = pluginListFileContents.indexOf('TOP OF METADATA') + 16; + var endIndex = pluginListFileContents.indexOf('// BOTTOM OF METADATA'); + var target = pluginListFileContents.substring(startIndex, endIndex); + var metadata = JSON.parse(target); + return metadata; + }, + // Returns an object with plugin matching data. process: function(childPlugins) { var results = { @@ -36,24 +51,62 @@ } Object.keys(childPlugins).forEach(function(plugin) { - if (!harnessPlugins[plugin]) { + if (!harnessPluginMetadata[plugin]) { results.missing.push({ id: plugin, version: childPlugins[plugin] }); } else { - switch(semverCompare(harnessPlugins[plugin], childPlugins[plugin])) { + switch(semverCompare(harnessPluginMetadata[plugin], childPlugins[plugin])) { case -1: // Child older. - results.older.push({ id: plugin, versions: { harness: harnessPlugins[plugin], child: childPlugins[plugin] } }); + results.older.push({ id: plugin, versions: { harness: harnessPluginMetadata[plugin], child: childPlugins[plugin] } }); break; case 1: // Child newer. - results.newer.push({ id: plugin, versions: { harness: harnessPlugins[plugin], child: childPlugins[plugin] } }); + results.newer.push({ id: plugin, versions: { harness: harnessPluginMetadata[plugin], child: childPlugins[plugin] } }); break; case 0: // Match! - results.matched.push({ id: plugin, version: harnessPlugins[plugin] }); + results.matched.push({ id: plugin, version: harnessPluginMetadata[plugin] }); break; } } }); return results; + }, + // This creates the contents for the app's cordova_plugins.js file. + // Right now, it contains the harness's plugins with all plugins not listed + // in the target app removed. + // TODO: is to also add in plugin .js that is exists in the app but *not* + // in the harness. This will allow for JS-only plugins to work. + createNewPluginListFile: function(appPluginIds) { + function startsWith(a, b) { + return a.lastIndexOf(b, 0) === 0; + } + + function isPluginIdEnabled(id) { + for (var i = 0; i < appPluginIds.length; ++i) { + if (startsWith(id, appPluginIds[i])) { + return true; + } + } + return false; + } + var newPluginList = harnessPluginList.filter(function(entry) { + return isPluginIdEnabled(entry.id); + }); + var newMetadata = {}; + for (var i = 0; i < appPluginIds.length; ++i) { + var pluginId = appPluginIds[i]; + if (pluginId in harnessPluginMetadata) { + newMetadata[pluginId] = harnessPluginMetadata[pluginId]; + } + } + var ret = 'cordova.define("cordova/plugin_list", function(require, exports, module) {\n' + + 'module.exports = ' + JSON.stringify(newPluginList, null, 4) + ';\n' + + 'module.exports.metadata =\n' + + '// TOP OF METADATA\n' + + JSON.stringify(newMetadata, null, 4) + '\n' + + '// BOTTOM OF METADATA\n' + + '});\n'; + + return ret; } }; }); http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/18f4ce7a/www/cdvah/js/ResourcesLoader.js ---------------------------------------------------------------------- diff --git a/www/cdvah/js/ResourcesLoader.js b/www/cdvah/js/ResourcesLoader.js index e862aa3..6151672 100644 --- a/www/cdvah/js/ResourcesLoader.js +++ b/www/cdvah/js/ResourcesLoader.js @@ -144,6 +144,18 @@ return writeToFile(url, contents, true /* append */); }, + moveFile: function(fromUrl, toUrl) { + return resolveURL(fromUrl) + .then(function(fromEntry) { + return ensureDirectoryExists(dirName(toUrl)) + .then(function(destEntry) { + var deferred = $q.defer(); + fromEntry.moveTo(destEntry, baseName(toUrl), deferred.reslove, deferred.reject); + return deferred; + }); + }); + }, + deleteDirectory: function(url) { return resolveURL(url) .then(function(dirEntry) { http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/18f4ce7a/www/cdvah/js/ServeInstaller.js ---------------------------------------------------------------------- diff --git a/www/cdvah/js/ServeInstaller.js b/www/cdvah/js/ServeInstaller.js index 56f41e6..2f3d521 100644 --- a/www/cdvah/js/ServeInstaller.js +++ b/www/cdvah/js/ServeInstaller.js @@ -4,7 +4,7 @@ var ASSET_MANIFEST_PATH = 'installmanifest.json'; /* global myApp */ - myApp.run(['$q', 'Installer', 'AppsService', 'ResourcesLoader', 'urlCleanup', function($q, Installer, AppsService, ResourcesLoader, urlCleanup) { + myApp.run(['$q', 'Installer', 'AppsService', 'ResourcesLoader', 'urlCleanup', 'PluginMetadata', function($q, Installer, AppsService, ResourcesLoader, urlCleanup, PluginMetadata) { var platformId = cordova.require('cordova/platform').id; function ServeInstaller(url, appId) { @@ -71,7 +71,6 @@ .then(deferred.resolve, deferred.reject); return deferred.promise; } - // TODO: update should be more atomic. Maybe download to a new directory? ServeInstaller.prototype.doUpdateApp = function() { if (this._assetManifest) { @@ -83,8 +82,9 @@ }); }; - ServeInstaller.prototype._doUpdateAppForReal = function() { + ServeInstaller.prototype._bulkDownload = function(files) { var installPath = this.installPath; + var wwwPath = this._cachedProjectJson.wwwPath; var deferred = $q.defer(); var self = this; // Write the asset manifest to disk at most every 2 seconds. @@ -99,12 +99,43 @@ } }, 2000); - fetchMetaServeData(this.url) + console.log('Number of files to fetch: ' + files.length); + var i = 0; + var totalFiles = files.length + 1; // + 1 for the updateAppMeta. + deferred.notify((i + 1) / totalFiles); + function downloadNext() { + if (i > 0) { + self._assetManifest[files[i - 1].path] = files[i - 1].etag; + assetManifestDirty = 1; + } + if (!files[i]) { + assetManifestDirty = 2; + deferred.resolve(); + return; + } + deferred.notify((i + 1) / totalFiles); + + var sourceUrl = self.url + wwwPath + files[i].path; + var destPath = installPath + '/www' + files[i].path; + if (files[i].path == '/cordova_plugins.js') { + destPath = installPath + '/orig-cordova_plugins.js'; + } + console.log(destPath); + i += 1; + return ResourcesLoader.downloadFromUrl(sourceUrl, destPath).then(downloadNext); + } + downloadNext(); + }; + + ServeInstaller.prototype._doUpdateAppForReal = function() { + var installPath = this.installPath; + var self = this; + + return fetchMetaServeData(this.url) .then(function(meta) { self._cachedProjectJson = meta.projectJson; self._cachedConfigXml = meta.configXml; self.appId = self.appId || meta.appId; - var wwwPath = self._cachedProjectJson.wwwPath; var files = self._cachedProjectJson.wwwFileList; files = files.filter(function(f) { // Don't download cordova.js or plugins. We want to use the version bundled with the harness. @@ -113,32 +144,18 @@ var haveAlready = self._assetManifest[f.path] == f.etag; return (!isPlugin && !haveAlready); }); - console.log('Number of files to fetch: ' + files.length); - var i = 0; - var totalFiles = files.length + 1; // + 1 for the updateAppMeta. - deferred.notify((i + 1) / totalFiles); - function downloadNext() { - if (i > 0) { - self._assetManifest[files[i - 1].path] = files[i - 1].etag; - assetManifestDirty = 1; - } - if (!files[i]) { - assetManifestDirty = 2; - deferred.resolve(); - return; - } - deferred.notify((i + 1) / totalFiles); - - var sourceUrl = self.url + wwwPath + files[i].path; - var destPath = installPath + '/www' + files[i].path; - console.log(destPath); - i += 1; - return ResourcesLoader.downloadFromUrl(sourceUrl, destPath).then(downloadNext); - } return ResourcesLoader.writeFileContents(installPath + '/config.xml', self._cachedConfigXml) - .then(downloadNext); + .then(function() { + return self._bulkDownload(files); + }); + }); + }; + + ServeInstaller.prototype.getPluginMetadata = function() { + return ResourcesLoader.readFileContents(this.installPath + '/orig-cordova_plugins.js') + .then(function(contents) { + return PluginMetadata.extractPluginMetadata(contents); }); - return deferred.promise; }; function createFromUrl(url) {
