You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ag...@apache.org on 2014/03/10 04:34:28 UTC

[4/5] git commit: Use URLs everywhere instead of paths. Removes need for "root" filesystem

Use URLs everywhere instead of paths. Removes need for "root" filesystem

This also removes CRX file special-casing in launch logic by:
1. Installing CRX resources into a "www" subdir (like Serve does)
2. Taking advantage of the new allowFurtherRemapping setting for aliases


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/b471b8b5
Tree: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/tree/b471b8b5
Diff: http://git-wip-us.apache.org/repos/asf/cordova-app-harness/diff/b471b8b5

Branch: refs/heads/master
Commit: b471b8b536ef141c0c1bdae4cf01f189ac2ea52e
Parents: 584de8e
Author: Andrew Grieve <ag...@chromium.org>
Authored: Sun Mar 9 22:59:32 2014 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Sun Mar 9 22:59:32 2014 -0400

----------------------------------------------------------------------
 www/cdvah/js/AddCtrl.js                 |   4 +-
 www/cdvah/js/AppsService.js             |   4 +-
 www/cdvah/js/CdvhPackageHandler.js      |  17 +-
 www/cdvah/js/ContextMenuInjectScript.js |   4 +-
 www/cdvah/js/CrxInstaller.js            |  14 +-
 www/cdvah/js/Installer.js               |  62 +++---
 www/cdvah/js/ListCtrl.js                |   2 +-
 www/cdvah/js/ResourcesLoader.js         | 308 +++++++--------------------
 www/cdvah/js/ServeInstaller.js          |  25 ++-
 www/cdvah/js/UrlRemap.js                |   4 +-
 www/cdvah/js/app.js                     |  26 ++-
 11 files changed, 144 insertions(+), 326 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/b471b8b5/www/cdvah/js/AddCtrl.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/AddCtrl.js b/www/cdvah/js/AddCtrl.js
index 405c9a0..9d0af8b 100644
--- a/www/cdvah/js/AddCtrl.js
+++ b/www/cdvah/js/AddCtrl.js
@@ -2,7 +2,7 @@
     'use strict';
 
     /* global myApp */
-    myApp.controller('AddCtrl', ['$q', 'notifier', '$location', '$rootScope', '$scope', '$window', '$routeParams', 'AppsService', 'UrlCleanup', function($q, notifier, $location, $rootScope, $scope, $window, $routeParams, AppsService, UrlCleanup) {
+    myApp.controller('AddCtrl', ['$q', 'notifier', '$location', '$rootScope', '$scope', '$window', '$routeParams', 'AppsService', 'urlCleanup', function($q, notifier, $location, $rootScope, $scope, $window, $routeParams, AppsService, urlCleanup) {
         $scope.editing = $routeParams.appId;
         var editingApp;
 
@@ -44,7 +44,7 @@
                 // We deliberately disallow changing the type, since that wouldn't work at all.
                 var oldUrl = editingApp.url;
                 editingApp.appId = $scope.appData.appId;
-                editingApp.url = UrlCleanup($scope.appData.appUrl);
+                editingApp.url = urlCleanup($scope.appData.appUrl);
                 var urlChanged = oldUrl != editingApp.url;
                 var p = AppsService.editApp($scope.editing, editingApp).then(function() {
                     console.log('App edited');

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/b471b8b5/www/cdvah/js/AppsService.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/AppsService.js b/www/cdvah/js/AppsService.js
index c54164e..6749bcb 100644
--- a/www/cdvah/js/AppsService.js
+++ b/www/cdvah/js/AppsService.js
@@ -85,7 +85,7 @@
             addApp : function(installerType, appUrl) {
                 var installerFactory = _installerFactories[installerType];
                 return initHandlers().then(function() {
-                  return installerFactory.createFromUrl(appUrl);
+                    return installerFactory.createFromUrl(appUrl);
                 }).then(function(installer) {
                     _installers.push(installer);
                     return writeAppsJson()
@@ -117,7 +117,7 @@
             },
 
             updateApp : function(installer){
-                var installPath = INSTALL_DIRECTORY + '/' + installer.appId;
+                var installPath = INSTALL_DIRECTORY + '/' + encodeURIComponent(installer.appId);
                 return installer.updateApp(installPath)
                 .then(writeAppsJson);
             },

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/b471b8b5/www/cdvah/js/CdvhPackageHandler.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/CdvhPackageHandler.js b/www/cdvah/js/CdvhPackageHandler.js
index c3183e5..43f58dd 100644
--- a/www/cdvah/js/CdvhPackageHandler.js
+++ b/www/cdvah/js/CdvhPackageHandler.js
@@ -5,21 +5,6 @@
 
         var platformId = cordova.require('cordova/platform').id;
 
-        function copyFile(startUrl, targetLocation){
-            /************ Begin Work around for File system bug ************/
-            if(targetLocation.indexOf('file://') === 0) {
-                targetLocation = targetLocation.substring('file://'.length);
-            }
-            /************ End Work around for File system bug **************/
-            return ResourcesLoader.xhrGet(startUrl)
-            .then(function(xhr){
-                return ResourcesLoader.ensureDirectoryExists(targetLocation)
-                .then(function(){
-                    return ResourcesLoader.writeFileContents(targetLocation, xhr.responseText);
-                });
-            });
-        }
-
         AppsService.registerPackageHandler('cdvh', {
             extractPackageToDirectory : function (appName, fileName, outputDirectory){
                 var dataToAppend = ContextMenuInjectScript.getInjectString(appName);
@@ -39,7 +24,7 @@
                     if(fileExists){
                         return $q.all([
                             ResourcesLoader.appendFileContents(cordovaFile, dataToAppend),
-                            copyFile('app-bundle:///cdvh_files/www/cordova_plugins.js', pluginsFile)
+                            ResourcesLoader.downloadFromUrl('app-bundle:///cdvh_files/www/cordova_plugins.js', pluginsFile)
                         ]);
                     } else {
                         throw new Error('The package does not seem to have the files required for the platform: ' + platformId);

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/b471b8b5/www/cdvah/js/ContextMenuInjectScript.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/ContextMenuInjectScript.js b/www/cdvah/js/ContextMenuInjectScript.js
index c245105..8c0adba 100644
--- a/www/cdvah/js/ContextMenuInjectScript.js
+++ b/www/cdvah/js/ContextMenuInjectScript.js
@@ -4,7 +4,9 @@
     /* global appIndexPlaceHolder */
     myApp.factory('ContextMenuInjectScript', [ function () {
         var toInject = function() {
-            if (window.__cordovaAppHarnessData) return; // Short-circuit if I've run on this page before.
+            if (window.__cordovaAppHarnessData) {
+                return; // Short-circuit if I've run on this page before.
+            }
             console.log('Menu script injected.');
             var contextScript = document.createElement('script');
             contextScript.setAttribute('src', 'app-harness:///cdvahcm/ContextMenu.js');

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/b471b8b5/www/cdvah/js/CrxInstaller.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/CrxInstaller.js b/www/cdvah/js/CrxInstaller.js
index 34e4c04..50fdf8b 100644
--- a/www/cdvah/js/CrxInstaller.js
+++ b/www/cdvah/js/CrxInstaller.js
@@ -1,7 +1,7 @@
 (function(){
     'use strict';
     /* global myApp */
-    myApp.run(['$q', 'Installer', 'AppsService', 'ResourcesLoader', 'UrlCleanup', function($q, Installer, AppsService, ResourcesLoader, UrlCleanup){
+    myApp.run(['$q', 'Installer', 'AppsService', 'ResourcesLoader', 'urlCleanup', function($q, Installer, AppsService, ResourcesLoader, urlCleanup){
 
         var platformId = cordova.require('cordova/platform').id;
 
@@ -17,29 +17,23 @@
             var installPath = this.installPath;
             var platformConfig = location.pathname.replace(/\/[^\/]*$/, '/crx_files/config.' + platformId + '.xml');
             var targetConfig = installPath + '/config.xml';
-            var xhr;
 
             // The filename doesn't matter, but it needs to end with .crx for the zip plugin to unpack
             // it properly. So we always set the filename to package.crx.
             var crxFile = installPath.replace(/\/$/, '') + '/package.crx';
 
             return ResourcesLoader.downloadFromUrl(this.url, crxFile).then(function() {
-                return ResourcesLoader.extractZipFile(crxFile, installPath);
+                return ResourcesLoader.extractZipFile(crxFile, installPath + '/www');
             }).then(function() {
                 // Copy in the config.<platform>.xml file from the harness.
-                return ResourcesLoader.xhrGet(platformConfig);
-            }).then(function(_xhr){
-                xhr = _xhr;
-                return ResourcesLoader.ensureDirectoryExists(targetConfig);
-            }).then(function() {
-                return ResourcesLoader.writeFileContents(targetConfig, xhr.responseText);
+                return ResourcesLoader.downloadFromUrl(platformConfig, targetConfig);
             });
         };
 
         AppsService.registerInstallerFactory({
             type: 'crx',
             createFromUrl: function(url) {
-                url = UrlCleanup(url);
+                url = urlCleanup(url);
 
                 // TODO: Fix the missing appId, somehow.
                 return $q.when(new CrxInstaller(url, 'New Chrome App'));

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/b471b8b5/www/cdvah/js/Installer.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/Installer.js b/www/cdvah/js/Installer.js
index dc9ee04..704c679 100644
--- a/www/cdvah/js/Installer.js
+++ b/www/cdvah/js/Installer.js
@@ -98,8 +98,6 @@
             }
             var configLocation = installPath + '/config.xml';
 
-            var type = this.type;
-
             return getAppStartPageFromConfig(configLocation)
             .then(function(rawStartLocation) {
                 var urlutil = cordova.require('cordova/urlutil');
@@ -108,42 +106,36 @@
                 var installUrl = urlutil.makeAbsolute(installPath);
                 var injectString = ContextMenuInjectScript.getInjectString(appId, appIndex);
                 var startLocation = urlutil.makeAbsolute(rawStartLocation).replace('/cdvah/', '/');
-                // On iOS, file:// URLs can't be re-routed via an NSURLProtocol for top-level navications.
-                // http://stackoverflow.com/questions/12058203/using-a-custom-nsurlprotocol-on-ios-for-file-urls-causes-frame-load-interrup
-                // The work-around (using loadData:) breaks history.back().
-                // So, for file:// start pages, we just point to the install location.
-                if (cordova.platformId == 'ios') {
-                    startLocation = startLocation.replace(harnessDir, installUrl + '/www');
-                }
+                var useNativeStartLocation = cordova.platformId == 'ios';
 
-                // Inject the context menu script for all pages except the harness menu.
-                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.
-                UrlRemap.aliasUri('/cordova\\.js.*', '.+', harnessDir + '/cordova.js', false /* redirect */);
-                UrlRemap.aliasUri('/cordova_plugins\\.js.*', '.+', harnessDir + '/cordova_plugins.js', false /* redirect */);
+                // Use toNativeURL() so that scheme is file:/ instead of cdvfile:/ (file: has special access).
+                return ResourcesLoader.toNativeURL(installUrl)
+                .then(function(nativeInstallUrl) {
+                    nativeInstallUrl = nativeInstallUrl.replace(/\/$/, '');
+                    // Point right at the dest. location on iOS.
+                    if (useNativeStartLocation) {
+                        startLocation = startLocation.replace(harnessDir, nativeInstallUrl + '/www');
+                    }
 
-                // We want the /www/ for Cordova apps, and no /www/ for CRX apps.
-                var installSubdir = type == 'crx' ? '/' : '/www/';
-                if (startLocation.indexOf('chromeapp.html') >= 0) {
-                    var pluginsUrl = 'chrome-extension://[^/]+/plugins/';
-                    UrlRemap.aliasUri('^' + pluginsUrl, '^' + pluginsUrl, harnessDir + '/plugins/', false /* redirect */);
+                    // Inject the context menu script for all pages except the harness menu.
+                    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.
+                    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 */);
 
-                    var chromeExtensionUrl = 'chrome-extension://[^\/]+/(?!!gap_exec)';
-                    // Add the extra mapping for chrome-extension://aaaa... to point to the install location.
-                    UrlRemap.aliasUri('^' + chromeExtensionUrl, '^' + chromeExtensionUrl, installUrl + installSubdir, false /* redirect */);
-                } else {
-                    var pluginsUrl = startLocation.replace(/\/www\/.*/, '/www/plugins/');
-                    UrlRemap.aliasUri('^' + pluginsUrl, '^' + pluginsUrl, harnessDir + '/plugins/', false /* redirect */);
-                }
-                // Make any references to www/ point to the app's install location.
-                UrlRemap.aliasUri('^' + harnessDir, '^' + harnessDir, installUrl + installSubdir, false /* redirect */);
-                // Set-up app-harness: scheme to point at the harness.
-                UrlRemap.aliasUri('^app-harness:///cdvah/index.html', '^app-harness://', harnessDir, true);
-                return UrlRemap.aliasUri('^app-harness:', '^app-harness://', harnessDir, false)
-                .then(function() {
-                    return startLocation;
+                    // Make any references to www/ point to the app's install location.
+                    var harnessPrefixPattern = '^' + harnessDir.replace('file:///', 'file://.*?/');
+                    UrlRemap.aliasUri(harnessPrefixPattern, harnessPrefixPattern, nativeInstallUrl + '/www', false /* redirect */, true /* allowFurtherRemapping */);
+
+                    // Set-up app-harness: scheme to point at the harness.
+                    UrlRemap.aliasUri('^app-harness:///cdvah/index.html', '^app-harness://', harnessDir, true, false);
+                    return UrlRemap.aliasUri('^app-harness:', '^app-harness://', harnessDir, false, false)
+                    .then(function() {
+                        return startLocation;
+                    });
                 });
             });
         };

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/b471b8b5/www/cdvah/js/ListCtrl.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/ListCtrl.js b/www/cdvah/js/ListCtrl.js
index 000e342..7f7aca7 100644
--- a/www/cdvah/js/ListCtrl.js
+++ b/www/cdvah/js/ListCtrl.js
@@ -68,7 +68,7 @@
             .then(function(){
                 notifier.success('Updated successfully');
                 console.log('successfully updated');
-            }, function(error){
+            }, function(error) {
                 console.error('Error during updating of app ' + app.appId + ': ' + error);
                 notifier.error('' + error);
             });

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/b471b8b5/www/cdvah/js/ResourcesLoader.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/ResourcesLoader.js b/www/cdvah/js/ResourcesLoader.js
index d7d3efb..fa0e53d 100644
--- a/www/cdvah/js/ResourcesLoader.js
+++ b/www/cdvah/js/ResourcesLoader.js
@@ -1,153 +1,75 @@
+/* global myApp */
 (function() {
     'use strict';
 
-    /* global myApp */
-    myApp.factory('ResourcesLoader', ['$q', '$window', function($q, $window) {
-        var rootDir;
-
-        function initialiseFileSystem() {
+    myApp.factory('ResourcesLoader', ['$q', '$window', '$http', function($q, $window, $http) {
+        function resolveURL(url) {
             var d = $q.defer();
-            $window.resolveLocalFileSystemURL('file:///', function(entry) {
-                rootDir = entry;
-                d.resolve(entry);
-            }, d.reject);
+            $window.resolveLocalFileSystemURL(url, d.resolve, d.reject);
             return d.promise;
         }
 
-        //promise returns full path to downloaded file
-        function downloadFromUrl(url, fullFilePath) {
+        // promise returns URL of downloaded file
+        function fileTransferDownload(from, to) {
+            var urlutil = cordova.require('cordova/urlutil');
+            from = urlutil.makeAbsolute(from);
             var deferred = $q.defer();
 
-            var downloadFail = function(error) {
-                var str = 'There was an error while downloading the file ' + JSON.stringify(error);
-                deferred.reject(new Error(str));
-            };
-
-            var downloadSuccess = function(fileEntry) {
-                deferred.resolve(fileEntry.fullPath);
-            };
+            function downloadSuccess(fileEntry) {
+                deferred.resolve(fileEntry.toURL());
+            }
+            function fail() {
+                deferred.reject(new Error('Failed to download file: ' + from));
+            }
 
             var fileTransfer = new $window.FileTransfer();
-            fileTransfer.download(url, fullFilePath, downloadSuccess, downloadFail);
+            fileTransfer.download(from, to, downloadSuccess, fail);
             return deferred.promise;
         }
 
-        function trim(str) {
-            return str && str.replace(/^\s+|\s+$/g, '');
-        }
-
-        function fixFilePath(path) {
-            if(path && path.indexOf('file://') === 0) {
-                path = path.substring('file://'.length);
-            }
-            return path;
-        }
-
-        function getScheme(uri){
-            var ret = uri.match(/^[a-z0-9+.-]+(?=:)/);
-            if(!ret){
-                return;
-            }
-            return ret[0];
-        }
-
-        //promise returns the directory entry
-        function getDirectoryEntry(directoryName) {
+        function getFilePromisified(entry, path, opts) {
             var deferred = $q.defer();
-
-            var errorWhileGettingDirectoryEntry = function(error) {
-                var str = 'There was an error while getting the directory entry for directory ' + directoryName + ' ' + JSON.stringify(error);
-                deferred.reject(new Error(str));
-            };
-            var success = function(directoryEntry) {
-                deferred.resolve(directoryEntry);
-            };
-            rootDir.getDirectory(directoryName, {create: true, exclusive: false}, success, errorWhileGettingDirectoryEntry);
+            entry.getFile(path, opts, deferred.resolve, deferred.reject);
             return deferred.promise;
         }
 
-        //promise returns the file entry
-        function getFileEntry(fileName, createFlag) {
+        function getDirectoryPromisified(entry, path, opts) {
             var deferred = $q.defer();
-
-            var errorWhileGettingFileEntry = function(error) {
-                var str = 'There was an error while getting the file entry for file ' + fileName + ' ' + JSON.stringify(error);
-                deferred.reject(new Error(str));
-            };
-            var success = function(fileEntry) {
-                deferred.resolve(fileEntry);
-            };
-            // !! - ensures a boolean value
-            rootDir.getFile(fixFilePath(fileName), {create: !!createFlag, exclusive: false}, success, errorWhileGettingFileEntry);
+            entry.getDirectory(path, opts, deferred.resolve, deferred.reject);
             return deferred.promise;
         }
 
-        //promise returns the file
-        function getFile(fileName) {
-            return getFileEntry(fileName, true  /* create */).
-            then(function(fileEntry){
-                var deferred = $q.defer();
-
-                var errorWhileGettingFile = function(error) {
-                    var str = 'There was an error while getting the file for file ' + fileName + ' ' + JSON.stringify(error);
-                    deferred.reject(new Error(str));
-                };
-
-                fileEntry.file(deferred.resolve, errorWhileGettingFile);
-                return deferred.promise;
-            });
-        }
-
-        function truncateToDirectoryPath(path) {
+        function dirName(path) {
             return path.replace(/\/[^\/]+$/, '/');
         }
 
-        function getPathSegments(path){
-            //truncate leading and trailing slashes
-            if(path.charAt(0) === '/'){
-                path = path.substring(1);
-            }
-            if(path.charAt(path.length - 1) === '/'){
-                path = path.substring(0, path.length - 1);
-            }
-            var segments = path.split('/');
-            return segments;
+        function baseName(path) {
+            return path.replace(/.*\//, '');
         }
 
-        function ensureSingleDirectoryExists(directory){
-            var deferred = $q.defer();
-
-            var gotDirEntry = function(dirEntry) {
-                deferred.resolve(dirEntry.fullPath);
-            };
-
-            var failedToGetDirEntry = function(error) {
-                var str = 'There was an error checking the directory: ' + directory + ' ' + JSON.stringify(error);
-                deferred.reject(new Error(str));
-            };
+        function ensureDirectoryExists(url) {
+            var m = /(cdvfile:\/\/.*?\/.*?\/)(.*)/.exec(url);
+            var rootDir = m[1];
+            var segments = m[2].split('/');
 
-            rootDir.getDirectory(directory, {create: true, exclusive: false}, gotDirEntry, failedToGetDirEntry);
-            return deferred.promise;
+            return segments.reduce(function(p, seg) {
+                return !seg ? p : p.then(function(parentDirEntry) {
+                    return getDirectoryPromisified(parentDirEntry, seg, {create: true});
+                });
+            }, resolveURL(rootDir));
         }
 
-        function ensureDirectoryExists(directory){
-            directory = truncateToDirectoryPath(directory);
-            directory = fixFilePath(directory);
-            var segments = getPathSegments(directory);
-            var currentDir = directory.charAt(0) === '/'? '/' : '';
-            var promiseArr = [];
-            while(segments.length !== 0) {
-                currentDir +=  segments.shift() + '/';
-                promiseArr.push(ensureSingleDirectoryExists(currentDir));
-            }
-            return $q.all(promiseArr)
-            .then(function(paths){
-                return paths[paths.length - 1];
+        function createFileEntry(url) {
+            var rootUrl = dirName(url);
+            return ensureDirectoryExists(rootUrl)
+            .then(function(dirEntry) {
+                var path = decodeURI(baseName(url));
+                return getFilePromisified(dirEntry, path, {create: true});
             });
         }
 
-        function writeToFile(fileName, contents, append) {
-            return getFileEntry(fileName, true)
+        function writeToFile(url, contents, append) {
+            return createFileEntry(url)
             .then(function(fileEntry){
                 var deferred = $q.defer();
 
@@ -171,141 +93,70 @@
             });
         }
 
-        function xhrGet(url){
-            var deferred = $q.defer();
-            var xhr = new XMLHttpRequest();
-            xhr.onreadystatechange = function() {
-                if (xhr.readyState === 4) {
-                    if(xhr.status === 200 || xhr.status === 0) {
-                        deferred.resolve(xhr);
-                    } else {
-                        deferred.reject(new Error('XHR return status: ' + xhr.status + ' for url: ' + url));
-                    }
-                }
-            };
-            xhr.open('GET', url, true);
-            xhr.send();
-            return deferred.promise;
-        }
-
         return {
-            doesFileExist : function(fileName){
-                return initialiseFileSystem()
-                .then(function(){
-                    return getFileEntry(fileName, false /* create */);
-                })
-                .then(function(){
-                    return true;
-                }, function(){
-                    return false;
-                });
+            doesFileExist: function(url){
+                return resolveURL(url).then(function() { return true; }, function() { return false; });
             },
 
-            // returns a promise with a full path to the dir
-            ensureDirectoryExists : function(directory) {
-                return initialiseFileSystem()
-                .then(function(){
-                    return ensureDirectoryExists(directory);
-                });
+            toNativeURL: function(url){
+                return resolveURL(url).then(function(e) { return e.toNativeURL(); });
             },
 
             // returns a promise with a full path to the downloaded file
-            downloadFromUrl : downloadFromUrl,
+            downloadFromUrl: function(from, to) {
+                return ensureDirectoryExists(dirName(to))
+                .then(function() {
+                    return fileTransferDownload(from, to);
+                });
+            },
 
             //returns a promise with the contents of the file
-            readFileContents : function(fileName) {
-                var scheme = getScheme(fileName);
-                // assume file scheme by default
-                if (!scheme || scheme === 'file') {
-                    return initialiseFileSystem()
-                    .then(function(){
-                        return getFile(fileName);
-                    })
-                    .then(function(file){
-                        var deferred = $q.defer();
-
-                        var reader = new $window.FileReader();
-                        reader.onload = function(evt) {
-                            var text = evt.target.result;
-                            deferred.resolve(text);
-                        };
-                        reader.onerror = function(evt) {
-                            deferred.reject(new Error(evt));
-                        };
-                        reader.readAsText(file);
-
-                        return deferred.promise;
-                    });
-                } else if(scheme === 'http' || scheme === 'https') {
-                    return xhrGet(fileName)
-                    .then(function(xhr){
-                        return xhr.responseText;
-                    });
-                }
-                throw new Error('Cannot read file ' + fileName);
+            readFileContents: function(url) {
+                return this.xhrGet(url);
             },
 
             //returns a promise with the json contents of the file
-            readJSONFileContents : function(fileName) {
-                return this.readFileContents(fileName)
-                .then(function (text) {
-                    text = trim(text);
-                    var resultJson = {};
-                    if(text) {
-                        resultJson = JSON.parse(text);
-                    }
-                    return resultJson;
-                });
+            readJSONFileContents: function(url) {
+                return this.xhrGet(url, true);
             },
 
-            //returns a promise when file is written
-            writeFileContents : function(fileName, contents) {
-                return initialiseFileSystem()
-                .then(function(){
-                    var scheme = getScheme(fileName);
-                    // assume file scheme by default
-                    if(!scheme || scheme === 'file') {
-                        return writeToFile(fileName, contents, false /* append */);
-                    } else {
-                        throw new Error('Cannot write to ' + fileName);
+            xhrGet: function(url, json) {
+                var opts = json ? null : {transformResponse: []};
+                return $http.get(url, opts)
+                .then(function(response, status) {
+                    if (!response) {
+                        throw new Error('Got ' + status + ' when fetching ' + url);
                     }
+                    return response.data;
                 });
             },
 
-            //returns a promise when file is appended
-            appendFileContents : function(fileName, contents) {
-                return initialiseFileSystem()
-                .then(function(){
-                    return writeToFile(fileName, contents, true /* append */);
-                });
+            writeFileContents: function(url, contents) {
+                return writeToFile(url, contents, false /* append */);
             },
 
-            deleteDirectory : function(directoryName) {
-                return initialiseFileSystem()
-                .then(function(){
-                    return getDirectoryEntry(directoryName);
-                })
-                .then(function(dirEntry){
+            appendFileContents: function(url, contents) {
+                return writeToFile(url, contents, true /* append */);
+            },
+
+            deleteDirectory: function(url) {
+                return resolveURL(url)
+                .then(function(dirEntry) {
                     var deferred = $q.defer();
-                    var failedToDeleteDirectory = function(error) {
-                        var str = 'There was an error deleting the directory: ' + directoryName + ' ' + JSON.stringify(error);
-                        deferred.reject(new Error(str));
-                    };
-                    dirEntry.removeRecursively(deferred.resolve, failedToDeleteDirectory);
+                    dirEntry.removeRecursively(deferred.resolve, function(error) {
+                        deferred.reject(new Error('There was an error deleting the directory: ' + url + ' ' + JSON.stringify(error)));
+                    });
                     return deferred.promise;
-                });
+                }, function() {});
             },
 
-            extractZipFile : function(fileName, outputDirectory){
-                return initialiseFileSystem()
-                .then(function(){
-                    return ensureDirectoryExists(outputDirectory);
-                })
+            extractZipFile: function(zipUrl, outputDirectory) {
+                return ensureDirectoryExists(outputDirectory)
                 .then(function(){
                     var deferred = $q.defer();
 
                     var onZipDone = function(returnCode) {
-                        if(returnCode !== 0) {
+                        if (returnCode !== 0) {
                             deferred.reject(new Error('Failed to unzip! Bad URL?'));
                         } else {
                             deferred.resolve();
@@ -313,13 +164,10 @@
                     };
 
                     /* global zip */
-                    zip.unzip(fileName, outputDirectory, onZipDone);
+                    zip.unzip(zipUrl, outputDirectory, onZipDone);
                     return deferred.promise;
                 });
-            },
-
-            xhrGet : xhrGet
+            }
         };
     }]);
-
 })();

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/b471b8b5/www/cdvah/js/ServeInstaller.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/ServeInstaller.js b/www/cdvah/js/ServeInstaller.js
index 9a2d495..56f41e6 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', function($q, Installer, AppsService, ResourcesLoader, urlCleanup) {
         var platformId = cordova.require('cordova/platform').id;
 
         function ServeInstaller(url, appId) {
@@ -47,9 +47,10 @@
                 configXml: null,
                 appId: null
             };
-            ResourcesLoader.xhrGet(url + '/' + platformId + '/project.json')
-            .then(function(xhr) {
-                ret.projectJson = JSON.parse(xhr.responseText);
+            var projectJsonUrl = url + '/' + platformId + '/project.json';
+            ResourcesLoader.xhrGet(projectJsonUrl, true)
+            .then(function(data) {
+                ret.projectJson = data;
                 return ResourcesLoader.xhrGet(url + ret.projectJson.configPath);
             }, function(e) {
                 // If there was no :8000, try again with one appended.
@@ -61,12 +62,13 @@
                 }
                 deferred.reject(e);
             })
-            .then(function(xhr) {
-                ret.configXml = xhr.responseText;
+            .then(function(data) {
+                ret.configXml = data;
                 var configXml = new DOMParser().parseFromString(ret.configXml, 'text/xml');
                 ret.appId = configXml.firstChild.getAttribute('id');
-                deferred.resolve(ret);
-            }, deferred.reject);
+                return ret;
+            })
+            .then(deferred.resolve, deferred.reject);
             return deferred.promise;
         }
 
@@ -133,10 +135,7 @@
                     i += 1;
                     return ResourcesLoader.downloadFromUrl(sourceUrl, destPath).then(downloadNext);
                 }
-                return ResourcesLoader.ensureDirectoryExists(installPath + '/config.xml')
-                .then(function() {
-                    return ResourcesLoader.writeFileContents(installPath + '/config.xml', self._cachedConfigXml);
-                })
+                return ResourcesLoader.writeFileContents(installPath + '/config.xml', self._cachedConfigXml)
                 .then(downloadNext);
             });
             return deferred.promise;
@@ -144,7 +143,7 @@
 
         function createFromUrl(url) {
             // Strip platform and trailing slash if they exist.
-            url = UrlCleanup(url);
+            url = urlCleanup(url);
             // Fetch config.xml.
             return fetchMetaServeData(url)
             .then(function(meta) {

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/b471b8b5/www/cdvah/js/UrlRemap.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/UrlRemap.js b/www/cdvah/js/UrlRemap.js
index d095f76..d025461 100644
--- a/www/cdvah/js/UrlRemap.js
+++ b/www/cdvah/js/UrlRemap.js
@@ -6,9 +6,9 @@
         // URI aliasing : the ability to launch an app in the harness, query the document.location and get the same location as would have been got if you run the app separately
         // Without URI aliasing, document.location in the harness would give something like file:///APP_HARNESS_INSTALLED_APPS_LOCATION/www/index.html
 
-        function aliasUri(sourceUriMatchRegex, sourceUriReplaceRegex, replaceString, redirectToReplacedUrl){
+        function aliasUri(sourceUriMatchRegex, sourceUriReplaceRegex, replaceString, redirectToReplacedUrl, allowFurtherRemapping) {
             var deferred = $q.defer();
-            cordova.plugins.urlremap.addAlias(sourceUriMatchRegex, sourceUriReplaceRegex, replaceString, redirectToReplacedUrl, function(succeded) {
+            cordova.plugins.urlremap.addAlias(sourceUriMatchRegex, sourceUriReplaceRegex, replaceString, redirectToReplacedUrl, !!allowFurtherRemapping, function(succeded) {
                 if (succeded){
                     deferred.resolve();
                 } else {

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/b471b8b5/www/cdvah/js/app.js
----------------------------------------------------------------------
diff --git a/www/cdvah/js/app.js b/www/cdvah/js/app.js
index 78e88eb..51467f5 100644
--- a/www/cdvah/js/app.js
+++ b/www/cdvah/js/app.js
@@ -21,22 +21,20 @@ myApp.config(['$routeProvider', function($routeProvider){
     });
 }]);
 
-// foo
+myApp.factory('urlCleanup', function() {
+    return function(url) {
+        url = url.replace(/\/$/, '').replace(new RegExp(cordova.platformId + '$'), '').replace(/\/$/, '');
+        if (!/^(file|http|https)+:/.test(url)) {
+            url = 'http://' + url;
+        }
+        return url;
+    };
+});
+
 document.addEventListener('deviceready', function() {
     cordova.filesystem.getDataDirectory(false, function(dirEntry) {
-        var path = dirEntry.fullPath;
-        myApp.value('INSTALL_DIRECTORY', path + '/apps');
-        myApp.value('APPS_JSON', path + '/apps.json');
-
-        myApp.factory('UrlCleanup', function() {
-            return function(url) {
-                url = url.replace(/\/$/, '').replace(new RegExp(cordova.platformId + '$'), '').replace(/\/$/, '');
-                if (!/^[a-z]+:/.test(url)) {
-                    url = 'http://' + url;
-                }
-                return url;
-            };
-        });
+        myApp.value('INSTALL_DIRECTORY', dirEntry.toURL() + 'apps');
+        myApp.value('APPS_JSON', dirEntry.toURL() + 'apps.json');
 
         angular.bootstrap(document, ['CordovaAppHarness']);
     });