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 2013/05/23 22:03:47 UTC

[24/30] git commit: Support installation of crx files

Support installation of crx files

Conflicts:
	www/index.html


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

Branch: refs/heads/master
Commit: 4d4e0b46caec4db39cb2d3113c208657939029f6
Parents: 6492417
Author: Shravan Narayan <sh...@google.com>
Authored: Fri Apr 26 14:39:15 2013 -0400
Committer: Braden Shepherdson <br...@google.com>
Committed: Thu May 16 15:29:29 2013 -0400

----------------------------------------------------------------------
 www/cdvah_index.html                               |    1 +
 www/cdvah_js/CrxPackageHandler.js                  |   54 ++++
 www/crx_files/config.android.xml                   |   47 +++
 www/crx_files/config.ios.xml                       |   51 ++++
 www/crx_files/www/chromeapp.html                   |   15 +
 www/crx_files/www/chromeappstyles.css              |   14 +
 www/crx_files/www/chromebgpage.html                |   11 +
 www/crx_files/www/cordova_plugins.json             |    1 +
 www/crx_files/www/plugins/chrome.common/events.js  |   53 ++++
 .../www/plugins/chrome.fileSystem/fileSystem.js    |  142 ++++++++++
 www/crx_files/www/plugins/chrome.i18n/i18n.js      |  185 ++++++++++++
 .../www/plugins/chrome.identity/identity.js        |  161 +++++++++++
 www/crx_files/www/plugins/chrome.socket/socket.js  |  216 +++++++++++++++
 .../www/plugins/chrome.storage/storage.js          |  202 ++++++++++++++
 .../www/plugins/chrome/api/app/runtime.js          |    7 +
 www/crx_files/www/plugins/chrome/api/app/window.js |  156 +++++++++++
 www/crx_files/www/plugins/chrome/api/bootstrap.js  |   13 +
 .../www/plugins/chrome/api/helpers/stubs.js        |   11 +
 www/crx_files/www/plugins/chrome/api/mobile.js     |   58 ++++
 www/crx_files/www/plugins/chrome/api/runtime.js    |   58 ++++
 20 files changed, 1456 insertions(+), 0 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/cdvah_index.html
----------------------------------------------------------------------
diff --git a/www/cdvah_index.html b/www/cdvah_index.html
index 8850f80..3eaede1 100644
--- a/www/cdvah_index.html
+++ b/www/cdvah_index.html
@@ -14,6 +14,7 @@
         <script type="text/javascript" src="cdvah_js/AppBundleAlias.js"></script>
         <script type="text/javascript" src="cdvah_js/KnownExtensionDownloader.js"></script>
         <script type="text/javascript" src="cdvah_js/CdvhPackageHandler.js"></script>
+        <script type="text/javascript" src="cdvah_js/CrxPackageHandler.js"></script>
         <script type="text/javascript" src="cdvah_js/ListCtrl.js"></script>
         <script type="text/javascript" src="cdvah_js/AddCtrl.js"></script>
     </head>

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/cdvah_js/CrxPackageHandler.js
----------------------------------------------------------------------
diff --git a/www/cdvah_js/CrxPackageHandler.js b/www/cdvah_js/CrxPackageHandler.js
new file mode 100644
index 0000000..2627fd7
--- /dev/null
+++ b/www/cdvah_js/CrxPackageHandler.js
@@ -0,0 +1,54 @@
+(function(){
+    "use strict";
+    /* global myApp */
+    myApp.run(["AppsService", "ResourcesLoader", function(AppsService, ResourcesLoader){
+
+        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("crx", {
+            extractPackageToDirectory : function (fileName, outputDirectory){
+                return ResourcesLoader.ensureDirectoryExists(outputDirectory + "/www")
+                .then(function(){
+                    return ResourcesLoader.extractZipFile(fileName, outputDirectory + "/www");
+                })
+                .then(function(){
+                    return Q.all([
+                        copyFile("cdv-app-harness:///direct/cordova.js", outputDirectory + "/www/cordova.js"),
+                        copyFile("cdv-app-harness:///direct/crx_files/config.android.xml", outputDirectory + "/config.android.xml"),
+                        copyFile("cdv-app-harness:///direct/crx_files/config.ios.xml", outputDirectory + "/config.ios.xml"),
+                        copyFile("cdv-app-harness:///direct/crx_files/www/cordova_plugins.json", outputDirectory + "/www/cordova_plugins.json"),
+                        copyFile("cdv-app-harness:///direct/crx_files/www/chromeapp.html", outputDirectory + "/www/chromeapp.html"),
+                        copyFile("cdv-app-harness:///direct/crx_files/www/chromeappstyles.css", outputDirectory + "/www/chromeappstyles.css"),
+                        copyFile("cdv-app-harness:///direct/crx_files/www/chromebgpage.html", outputDirectory + "/www/chromebgpage.html"),
+                        copyFile("cdv-app-harness:///direct/crx_files/www/plugins/chrome/api/app/runtime.js", outputDirectory + "/www/plugins/chrome/api/app/runtime.js"),
+                        copyFile("cdv-app-harness:///direct/crx_files/www/plugins/chrome/api/app/window.js", outputDirectory + "/www/plugins/chrome/api/app/window.js"),
+                        copyFile("cdv-app-harness:///direct/crx_files/www/plugins/chrome/api/bootstrap.js", outputDirectory + "/www/plugins/chrome/api/bootstrap.js"),
+                        copyFile("cdv-app-harness:///direct/crx_files/www/plugins/chrome/api/helpers/stubs.js", outputDirectory + "/www/plugins/chrome/api/helpers/stubs.js"),
+                        copyFile("cdv-app-harness:///direct/crx_files/www/plugins/chrome/api/mobile.js", outputDirectory + "/www/plugins/chrome/api/mobile.js"),
+                        copyFile("cdv-app-harness:///direct/crx_files/www/plugins/chrome/api/runtime.js", outputDirectory + "/www/plugins/chrome/api/runtime.js"),
+                        copyFile("cdv-app-harness:///direct/crx_files/www/plugins/chrome.common/events.js", outputDirectory + "/www/plugins/chrome.common/events.js"),
+                        copyFile("cdv-app-harness:///direct/crx_files/www/plugins/chrome.fileSystem/fileSystem.js", outputDirectory + "/www/plugins/chrome.fileSystem/fileSystem.js"),
+                        copyFile("cdv-app-harness:///direct/crx_files/www/plugins/chrome.i18n/i18n.js", outputDirectory + "/www/plugins/chrome.i18n/i18n.js"),
+                        copyFile("cdv-app-harness:///direct/crx_files/www/plugins/chrome.identity/identity.js", outputDirectory + "/www/plugins/chrome.identity/identity.js"),
+                        copyFile("cdv-app-harness:///direct/crx_files/www/plugins/chrome.socket/socket.js", outputDirectory + "/www/plugins/chrome.socket/socket.js"),
+                        copyFile("cdv-app-harness:///direct/crx_files/www/plugins/chrome.storage/storage.js", outputDirectory + "/www/plugins/chrome.storage/storage.js")
+                    ]);
+                });
+            }
+        });
+
+    }]);
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/config.android.xml
----------------------------------------------------------------------
diff --git a/www/crx_files/config.android.xml b/www/crx_files/config.android.xml
new file mode 100644
index 0000000..27da959
--- /dev/null
+++ b/www/crx_files/config.android.xml
@@ -0,0 +1,47 @@
+<?xml version='1.0' encoding='utf-8'?>
+<cordova>
+    <content src="index.html" />
+    <log level="DEBUG" />
+    <plugins>
+        <plugin name="App" value="org.apache.cordova.App" />
+        <plugin name="Geolocation" value="org.apache.cordova.GeoBroker" />
+        <plugin name="Device" value="org.apache.cordova.Device" />
+        <plugin name="Accelerometer" value="org.apache.cordova.AccelListener" />
+        <plugin name="Compass" value="org.apache.cordova.CompassListener" />
+        <plugin name="Media" value="org.apache.cordova.AudioHandler" />
+        <plugin name="Camera" value="org.apache.cordova.CameraLauncher" />
+        <plugin name="Contacts" value="org.apache.cordova.ContactManager" />
+        <plugin name="File" value="org.apache.cordova.FileUtils" />
+        <plugin name="NetworkStatus" value="org.apache.cordova.NetworkManager" />
+        <plugin name="Notification" value="org.apache.cordova.Notification" />
+        <plugin name="Storage" value="org.apache.cordova.Storage" />
+        <plugin name="FileTransfer" value="org.apache.cordova.FileTransfer" />
+        <plugin name="Capture" value="org.apache.cordova.Capture" />
+        <plugin name="Battery" value="org.apache.cordova.BatteryListener" />
+        <plugin name="SplashScreen" value="org.apache.cordova.SplashScreen" />
+        <plugin name="Echo" value="org.apache.cordova.Echo" />
+        <plugin name="Globalization" value="org.apache.cordova.Globalization" />
+        <plugin name="InAppBrowser" value="org.apache.cordova.InAppBrowser" />
+        <plugin name="Zip" value="org.apache.cordova.Zip" />
+        <plugin name="ChromeExtensionURLs" value="com.google.cordova.ChromeExtensionURLs">
+            <url-filter value="chrome-extension://" />
+        </plugin>
+        <plugin name="ChromeI18n" onload="true" value="com.google.cordova.ChromeI18n" />
+        <plugin name="ChromeIdentity" value="com.google.cordova.ChromeIdentity" />
+        <plugin name="ChromeSocket" value="com.google.cordova.ChromeSocket" />
+        <plugin name="ChromeStorage" value="com.google.cordova.ChromeStorage" />
+        <plugin name="CordovaAppHarnessRedirect" value="org.apache.cordova.cordovaappharness.CordovaAppHarnessRedirect">
+            <url-filter value="cdv-app-harness://" />
+        </plugin>
+    </plugins>
+    <content src="chromeapp.html" />
+    <access origin="*" />
+    <access origin="chrome-extension://*" />
+    <access origin="cdv-app-harness://*" />
+    <preference name="useBrowserHistory" value="true" />
+    <preference name="exit-on-suspend" value="false" />
+    <preference name="phonegap-version" value="1.9.0" />
+    <preference name="orientation" value="default" />
+    <preference name="target-device" value="universal" />
+    <preference name="fullscreen" value="false" />
+</cordova>

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/config.ios.xml
----------------------------------------------------------------------
diff --git a/www/crx_files/config.ios.xml b/www/crx_files/config.ios.xml
new file mode 100644
index 0000000..bfba1c9
--- /dev/null
+++ b/www/crx_files/config.ios.xml
@@ -0,0 +1,51 @@
+<?xml version='1.0' encoding='utf-8'?>
+<widget>
+    <content src="index.html" />
+    <plugins>
+        <plugin name="Device" value="CDVDevice" />
+        <plugin name="Logger" value="CDVLogger" />
+        <plugin name="Compass" value="CDVLocation" />
+        <plugin name="Accelerometer" value="CDVAccelerometer" />
+        <plugin name="Camera" value="CDVCamera" />
+        <plugin name="NetworkStatus" value="CDVConnection" />
+        <plugin name="Contacts" value="CDVContacts" />
+        <plugin name="Debug Console" value="CDVDebugConsole" />
+        <plugin name="Echo" value="CDVEcho" />
+        <plugin name="File" value="CDVFile" />
+        <plugin name="FileTransfer" value="CDVFileTransfer" />
+        <plugin name="Geolocation" value="CDVLocation" />
+        <plugin name="Notification" value="CDVNotification" />
+        <plugin name="Media" value="CDVSound" />
+        <plugin name="Capture" value="CDVCapture" />
+        <plugin name="SplashScreen" value="CDVSplashScreen" />
+        <plugin name="Battery" value="CDVBattery" />
+        <plugin name="Globalization" value="CDVGlobalization" />
+        <plugin name="InAppBrowser" value="CDVInAppBrowser" />
+        <plugin name="Zip" value="Zip" />
+        <plugin name="ChromeExtensionURLs" onload="true" value="ChromeExtensionURLs" />
+        <plugin name="ChromeI18n" value="ChromeI18n" />
+        <plugin name="ChromeSocket" value="ChromeSocket" />
+        <plugin name="ChromeStorage" value="ChromeStorage" />
+        <plugin name="CordovaAppHarnessRedirect" onload="true" value="CordovaAppHarnessRedirect" />
+    </plugins>
+    <content src="chromeapp.html" />
+    <access origin="*" />
+    <access origin="chrome-extension://*" />
+    <access origin="cdv-app-harness://*" />
+    <preference name="KeyboardDisplayRequiresUserAction" value="true" />
+    <preference name="SuppressesIncrementalRendering" value="false" />
+    <preference name="UIWebViewBounce" value="true" />
+    <preference name="TopActivityIndicator" value="gray" />
+    <preference name="EnableLocation" value="false" />
+    <preference name="EnableViewportScale" value="false" />
+    <preference name="AutoHideSplashScreen" value="true" />
+    <preference name="ShowSplashScreenSpinner" value="true" />
+    <preference name="MediaPlaybackRequiresUserAction" value="false" />
+    <preference name="AllowInlineMediaPlayback" value="false" />
+    <preference name="OpenAllWhitelistURLsInWebView" value="false" />
+    <preference name="BackupWebStorage" value="cloud" />
+    <preference name="phonegap-version" value="1.9.0" />
+    <preference name="orientation" value="default" />
+    <preference name="target-device" value="universal" />
+    <preference name="fullscreen" value="false" />
+</widget>

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/www/chromeapp.html
----------------------------------------------------------------------
diff --git a/www/crx_files/www/chromeapp.html b/www/crx_files/www/chromeapp.html
new file mode 100644
index 0000000..3cbd68d
--- /dev/null
+++ b/www/crx_files/www/chromeapp.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+        <meta name="format-detection" content="telephone=no">
+        <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
+        <script src="cordova.js"></script>
+    </head>
+    <body></body>
+</html>

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/www/chromeappstyles.css
----------------------------------------------------------------------
diff --git a/www/crx_files/www/chromeappstyles.css b/www/crx_files/www/chromeappstyles.css
new file mode 100644
index 0000000..277f4fe
--- /dev/null
+++ b/www/crx_files/www/chromeappstyles.css
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/*
+ * Contains extra user-agent styles to apply to all Chrome App windows.
+ *
+ * These should be kept in sync with:
+ * http://code.google.com/searchframe#OAMlx_jo-ck/src/chrome/renderer/resources/extensions/platform_app.css
+ */
+body { -webkit-user-select: none; }
+html, body { overflow: hidden; }

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/www/chromebgpage.html
----------------------------------------------------------------------
diff --git a/www/crx_files/www/chromebgpage.html b/www/crx_files/www/chromebgpage.html
new file mode 100644
index 0000000..b732eaa
--- /dev/null
+++ b/www/crx_files/www/chromebgpage.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2012 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<html>
+  <body>
+    <script>parent.chrome.mobile.impl.bgInit(this)</script>
+  </body>
+</html>

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/www/cordova_plugins.json
----------------------------------------------------------------------
diff --git a/www/crx_files/www/cordova_plugins.json b/www/crx_files/www/cordova_plugins.json
new file mode 100644
index 0000000..fe11f7c
--- /dev/null
+++ b/www/crx_files/www/cordova_plugins.json
@@ -0,0 +1 @@
+[{"file":"plugins/chrome/api/bootstrap.js","id":"chrome.bootstrap","runs":true},{"file":"plugins/chrome/api/app/runtime.js","id":"chrome.app.runtime","clobbers":["chrome.app.runtime"]},{"file":"plugins/chrome/api/app/window.js","id":"chrome.app.window","clobbers":["chrome.app.window"]},{"file":"plugins/chrome/api/helpers/stubs.js","id":"chrome.helpers.stubs"},{"file":"plugins/chrome/api/mobile.js","id":"chrome.mobile.impl","clobbers":["chrome.mobile.impl"]},{"file":"plugins/chrome/api/runtime.js","id":"chrome.runtime","clobbers":["chrome.runtime"]},{"file":"plugins/chrome.common/events.js","id":"chrome.common.events","clobbers":["chrome.Event"]},{"file":"plugins/chrome.fileSystem/fileSystem.js","id":"chrome.fileSystem.FileSystem","clobbers":["chrome.fileSystem"]},{"file":"plugins/chrome.i18n/i18n.js","id":"chrome.i18n.I18n","clobbers":["chrome.i18n"]},{"file":"plugins/chrome.identity/identity.js","id":"chrome.identity.Identity","clobbers":["chrome.identity","chrome.experimental.iden
 tity"]},{"file":"plugins/chrome.socket/socket.js","id":"chrome.socket.Socket","clobbers":["chrome.socket"]},{"file":"plugins/chrome.storage/storage.js","id":"chrome.storage.Storage","clobbers":["chrome.storage"]}]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/www/plugins/chrome.common/events.js
----------------------------------------------------------------------
diff --git a/www/crx_files/www/plugins/chrome.common/events.js b/www/crx_files/www/plugins/chrome.common/events.js
new file mode 100644
index 0000000..6f868b1
--- /dev/null
+++ b/www/crx_files/www/plugins/chrome.common/events.js
@@ -0,0 +1,53 @@
+cordova.define("chrome.common.events", function(require, exports, module) {// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var Event = function(opt_eventName) {
+  this.name = opt_eventName || '';
+  this.listeners = [];
+};
+
+// Deliberately not filtering functions that are already added.
+// I tested on desktop and it will call your callback once for each addListener.
+Event.prototype.addListener = function(cb) {
+  this.listeners.push(cb);
+};
+
+Event.prototype.findListener_ = function(cb) {
+  for(var i = 0; i < this.listeners.length; i++) {
+    if (this.listeners[i] == cb) {
+      return i;
+    }
+  }
+
+  return -1;
+};
+
+Event.prototype.removeListener = function(cb) {
+  var index = this.findListener_(cb);
+  if (index >= 0) {
+    this.listeners.splice(index, 1);
+  }
+};
+
+Event.prototype.hasListener = function(cb) {
+  return this.findListener_(cb) >= 0;
+};
+
+Event.prototype.hasListeners = function() {
+  return this.listeners.length > 0;
+};
+
+Event.prototype.fire = function() {
+  for (var i = 0; i < this.listeners.length; i++) {
+    this.listeners[i].apply(this, arguments);
+  }
+};
+
+// Stubs since we don't support Rules.
+Event.prototype.addRules = function() { };
+Event.prototype.getRules = function() { };
+Event.prototype.removeRules = function() { };
+
+module.exports = Event;
+});

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/www/plugins/chrome.fileSystem/fileSystem.js
----------------------------------------------------------------------
diff --git a/www/crx_files/www/plugins/chrome.fileSystem/fileSystem.js b/www/crx_files/www/plugins/chrome.fileSystem/fileSystem.js
new file mode 100644
index 0000000..ce613e5
--- /dev/null
+++ b/www/crx_files/www/plugins/chrome.fileSystem/fileSystem.js
@@ -0,0 +1,142 @@
+cordova.define("chrome.fileSystem.FileSystem", function(require, exports, module) {// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var platformId = cordova.require('cordova/platform').id;
+var FileEntry = cordova.require('cordova/plugin/FileEntry');
+
+exports.getDisplayPath = function(fileEntry, callback) {
+  callback(fileEntry.fullPath);
+};
+
+exports.getWritableEntry = function(fileEntry, callback) {
+  callback(null);
+};
+
+exports.isWritableEntry = function(fileEntry, callback) {
+  callback(false);
+};
+
+exports.chooseEntry = function(options, callback) {
+  // Ensure that the type is either unspecified or specified as 'openFile', as nothing else is supported.
+  if (options.type && options.type != 'openFile') {
+    // TODO(maxw): Determine a "more correct" way to fail here.
+    callback(null);
+    return;
+  }
+
+  // Create the callback for getFile.
+  // It creates a file entry and passes it to the chooseEntry callback.
+  var onFileReceived = function(nativeUri) {
+    var onUriResolved = function(fileEntry) {
+      callback(fileEntry);
+    };
+
+    var onUriResolveError = function(e) {
+      console.log(e.target.error.code);
+    };
+
+    resolveLocalFileSystemURI(nativeUri, onUriResolved, onUriResolveError);
+  };
+
+  if (platformId == 'ios') {
+    getFileIos(options, onFileReceived);
+  } else if (platformId == 'android') {
+    getFileAndroid(options, onFileReceived);
+  }
+};
+
+function getFileIos(options, onFileReceivedCallback) {
+  // Determine the media type.
+  var mediaType = determineMediaType(options.accepts, options.acceptsAllTypes);
+
+  // Prepare the options for getting the file.
+  var getFileOptions = { destinationType: navigator.camera.DestinationType.NATIVE_URI,
+                         sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY,
+                         mediaType: mediaType };
+
+  // Use the camera to get an image or video.
+  navigator.camera.getPicture(onFileReceivedCallback, null, getFileOptions);
+}
+
+function getFileAndroid(options, onFileReceivedCallback) {
+  var AndroidFileChooser = cordova.require('cordova/plugin/android/filechooser');
+
+  // Determine the relevant mime types.
+  var mimeTypes = determineMimeTypes(options.accepts, options.acceptsAllTypes);
+
+  // Use the file chooser to get a file.
+  AndroidFileChooser.chooseFile(onFileReceivedCallback, null, mimeTypes);
+}
+
+function determineMediaType(acceptOptions, acceptsAllTypes) {
+  if (acceptsAllTypes) {
+    return navigator.camera.MediaType.ALLMEDIA;
+  }
+
+  var imageMimeTypeRegex = /^image\//;
+  var videoMimeTypeRegex = /^video\//;
+  var imageExtensionRegex = /^(?:jpg|png)$/;
+  var videoExtensionRegex = /^mov$/;
+  var imagesAllowed = false;
+  var videosAllowed = false;
+
+  // Iterate through all accept options.
+  // If we see anything image related, allow images.  If we see anything video related, allow videos.
+  if (acceptOptions) {
+    for (var i = 0; i < acceptOptions.length; i++) {
+      if (acceptOptions[i].mimeTypes) {
+        for (var j = 0; j < acceptOptions[i].mimeTypes.length; j++) {
+          if (imageMimeTypeRegex.test(acceptOptions[i].mimeTypes[j])) {
+            imagesAllowed = true;
+          } else if (videoMimeTypeRegex.test(acceptOptions[i].mimeTypes[j])) {
+            videosAllowed = true;
+          }
+        }
+      }
+      if (acceptOptions[i].extensions) {
+        for (var k = 0; k < acceptOptions[i].extensions.length; k++) {
+          if (imageExtensionRegex.test(acceptOptions[i].extensions[k])) {
+            imagesAllowed = true;
+          } else if (videoExtensionRegex.test(acceptOptions[i].extensions[k])) {
+            videosAllowed = true;
+          }
+        }
+      }
+    }
+  }
+
+  if (imagesAllowed && !videosAllowed) {
+    return navigator.camera.MediaType.PICTURE;
+  } else if (!imagesAllowed && videosAllowed) {
+    return navigator.camera.MediaType.VIDEO;
+  }
+
+  return navigator.camera.MediaType.ALLMEDIA;
+}
+
+function determineMimeTypes(acceptOptions, acceptsAllTypes) {
+  if (acceptsAllTypes) {
+    return [ '*/*' ];
+  }
+
+  // Pull out all the mime types.
+  // TODO(maxw): Determine mime types from extensions and add them to the returned list.
+  var mimeTypes = [ ];
+  if (acceptOptions) {
+    for (var i = 0; i < acceptOptions.length; i++) {
+      if (acceptOptions[i].mimeTypes) {
+        for (var j = 0; j < acceptOptions[i].mimeTypes.length; j++) {
+          mimeTypes.push(acceptOptions[i].mimeTypes[j]);
+        }
+      }
+    }
+  }
+
+  if (mimeTypes.length !== 0) {
+    return mimeTypes;
+  }
+
+  return [ '*/*' ];
+}
+});

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/www/plugins/chrome.i18n/i18n.js
----------------------------------------------------------------------
diff --git a/www/crx_files/www/plugins/chrome.i18n/i18n.js b/www/crx_files/www/plugins/chrome.i18n/i18n.js
new file mode 100644
index 0000000..905466c
--- /dev/null
+++ b/www/crx_files/www/plugins/chrome.i18n/i18n.js
@@ -0,0 +1,185 @@
+cordova.define("chrome.i18n.I18n", function(require, exports, module) {// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var exec = cordova.require('cordova/exec');
+var runtime = require('chrome.runtime');
+
+exports.getAcceptLanguages = function(callback) {
+    // In the chrome implementation, acceptLanguage value can change with updates so we make a native call to get the system language
+    exec(callback, callback, 'ChromeI18n', 'getAcceptLanguages', []);
+};
+
+exports.getMessage = function(messageName, substitutions) {
+    // In the chrome implementation, the locale to translate to DOES NOT change with updates, it is keyed of window.navigator.language which is static for a session.
+    // This is implemented in _getLocalesToUse()
+    if(
+        (typeof messageName !== 'string') ||
+        (Array.isArray(substitutions) && substitutions.length > 9)
+    ) {
+        return;
+    }
+    messageName = messageName.toLowerCase();
+    var ret = '';
+    var localeChain = _getLocalesToUse();
+    var contentsForMessageName = _getMessageFromMessageJson(messageName, localeChain);
+    if(contentsForMessageName) {
+        ret = _applySubstitutions(contentsForMessageName, substitutions);
+    }
+    return ret;
+};
+
+function _endsWith(string, endString) {
+    if(endString.length > string.length) {
+        return false;
+    } else {
+        return (string.lastIndexOf(endString) === string.length - endString.length);
+    }
+}
+
+function _getFilePathForLocale(locale) {
+    return 'locales/' + locale.toLowerCase() + '/messages.json';
+}
+
+function _toLowerCaseMessageAndPlaceholders(obj) {
+    if(typeof obj !== 'undefined') {
+        var newObj = {};
+        for(var field in obj) {
+            if(obj[field].placeholders) {
+                var newPlaceholders = {};
+                for(var placeholderField in obj[field].placeholders) {
+                    newPlaceholders[placeholderField.toLowerCase()] = obj[field].placeholders[placeholderField];
+                }
+                obj[field].placeholders = newPlaceholders;
+            }
+            newObj[field.toLowerCase()] = obj[field];
+        }
+        return newObj;
+    }
+}
+
+function _getDefaultLocale() {
+    var manifestJson = runtime.getManifest();
+    if(manifestJson.default_locale) {
+        return manifestJson.default_locale;
+    } else {
+        throw new Error('Default locale not defined');
+    }
+}
+
+var memoizedJsonContents = {};
+function _getMessageFromMessageJson(messageName, localeChain) {
+    for(var i = 0; i < localeChain.length; i++) {
+        var locale = localeChain[i];
+        if (!memoizedJsonContents[locale]) {
+            var fileName = _getFilePathForLocale(locale);
+            var xhr = new XMLHttpRequest();
+            xhr.open('GET', fileName, false /* sync */);
+            xhr.send(null);
+            var contents = eval('(' + xhr.responseText + ')');
+            // convert all fields to lower case to check case insensitively
+            contents = _toLowerCaseMessageAndPlaceholders(contents);
+            memoizedJsonContents[locale] = contents;
+        }
+        if(memoizedJsonContents[locale][messageName]) {
+            return memoizedJsonContents[locale][messageName];
+        }
+    }
+}
+
+function _isLocaleAvailable(locale) {
+    var fileName = _getFilePathForLocale(locale);
+    var xhr = new XMLHttpRequest();
+    xhr.open('HEAD', fileName, false /* sync */);
+    xhr.send(null);
+    availableLocales[locale] = (xhr.status === 200);
+}
+
+var chosenLocales;
+function _getLocalesToUse() {
+    if(!chosenLocales) {
+        // language returned by window.navigator is in format en-US, need to change it to en_us
+        var windowLocale = window.navigator.language.replace('-', '_').toLowerCase();
+        var localesToUse = [windowLocale];
+        // Construct fallback chain
+        var lastIndex;
+        while((lastIndex = windowLocale.lastIndexOf('_')) !== -1) {
+            windowLocale = windowLocale.substring(0, lastIndex);
+            localesToUse.push(windowLocale);
+        }
+        var defaultLocale = _getDefaultLocale().toLowerCase();
+        if(localesToUse.indexOf(defaultLocale) == -1) {
+            localesToUse.push(defaultLocale);
+        }
+
+        chosenLocales = [] ;
+        for(var i = 0; i < localesToUse.length; i++) {
+            var currentLocale = localesToUse[i];
+            if(_isLocaleAvailable(currentLocale)) {
+                chosenLocales.push(currentLocale);
+            }
+        }
+    }
+    if(chosenLocales.length == 0) {
+        throw new Error('No usable locale.');
+    }
+    return chosenLocales;
+}
+
+function _getSubstitutionString(match, substitutions) {
+    if(match == '$$') {
+        return '$';
+    } else if(match == '$') {
+        return '';
+    }
+    else {
+        var locBaseOne = parseInt(match.substring(1));
+        if(isNaN(locBaseOne) || locBaseOne <= 0) {
+            return '';
+        }
+
+        if(substitutions[locBaseOne - 1]) {
+            return substitutions[locBaseOne - 1];
+        } else {
+            return '';
+        }
+    }
+}
+
+function _getPlaceholderText(placeholders, substitutions, match) {
+    // Switch to lower case to do case insensitive checking for matches
+    var placeholderField = match.substring(1, match.length - 1);
+    placeholderField = placeholderField.toLowerCase();
+    if(placeholders[placeholderField]) {
+        // form $1, $2 etc or '$$' or '$'
+        var placeholderText = placeholders[placeholderField].content.replace(/\$(([0-9]+)|\$)?/g, function(match) {
+            var substitutionString = _getSubstitutionString(match, substitutions);
+            return substitutionString;
+        });
+        return placeholderText;
+    } else {
+        return '';
+    }
+}
+
+function _applySubstitutions(messageObject, substitutions) {
+    if(typeof substitutions === 'undefined') {
+        substitutions = [];
+    } else if(Object.prototype.toString.call(substitutions) !== '[object Array]') {
+        substitutions = [substitutions];
+    }
+    // Look for any strings of form $WORD$ eg: $1stName$, form $1, $2 etc or any lone '$'
+    // Order of preference is as in this comment
+    var ret = messageObject.message.replace( /\$(([0-9a-zA-Z_]*\$)|([0-9]*))?/g, function(matchedString) {
+        // check which category of matchedString it is
+        if(matchedString.match(/\$[0-9a-zA-Z_]+\$/)) { // form $WORD$
+            var placeholderText = _getPlaceholderText(messageObject.placeholders, substitutions, matchedString);
+            return placeholderText;
+        } else { // form $1, $2 etc or '$$' or '$'
+            var substitutionString = _getSubstitutionString(matchedString, substitutions);
+            return substitutionString;
+        }
+    });
+    return ret;
+}
+});

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/www/plugins/chrome.identity/identity.js
----------------------------------------------------------------------
diff --git a/www/crx_files/www/plugins/chrome.identity/identity.js b/www/crx_files/www/plugins/chrome.identity/identity.js
new file mode 100644
index 0000000..54f1597
--- /dev/null
+++ b/www/crx_files/www/plugins/chrome.identity/identity.js
@@ -0,0 +1,161 @@
+cordova.define("chrome.identity.Identity", function(require, exports, module) {// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var platformId = cordova.require('cordova/platform').id;
+var runtime = require('chrome.runtime');
+
+exports.TokenDetails = function(interactive) {
+    this.interactive = interactive || false;
+};
+
+exports.WebAuthFlowDetails = function(url, interactive, width, height, left, top) {
+    if (typeof url == 'undefined') {
+        throw new Error('Url required');
+    }
+    this.url = url;
+    this.interactive = interactive || false;
+    this.width = width;
+    this.height = height;
+    this.left = left;
+    this.top = top;
+};
+
+exports.getAuthToken = function(details, callback) {
+    if(typeof details == 'function') {
+        callback = details;
+        details = new exports.TokenDetails();
+    }
+    if (typeof callback == 'undefined') {
+        chrome.runtime.lastError = { 'message' : 'Callback function required' };
+        // Not calling callback as it wasnt provided
+        return;
+    }
+    var win = function(token) {
+        callback(token);
+    };
+    var fail = function() {
+        callback();
+    };
+
+    if (platformId == 'android') {
+        // Use native implementation for logging into google accounts
+        cordova.exec(win, fail, 'ChromeIdentity', 'getAuthToken', [details]);
+    } else {
+        // Use web app oauth flow
+        _getAuthTokenJS(win, fail, details);
+    }
+};
+
+exports.launchWebAuthFlow = function(details, callback) {
+    var failed = false;
+    var failMessage;
+    if (typeof details == 'undefined') {
+        failed = true;
+        failMessage = 'WebAuthFlowDetails required';
+    } else if (typeof callback == 'undefined') {
+        failed = true;
+        failMessage = 'Callback function required';
+    }
+
+    if(failed === true) {
+        chrome.runtime.lastError = { 'message' : failMessage };
+        // Not calling callback as it wasnt provided
+        return;
+    }
+
+    var finalURL = details.url;
+    var extractedRedirectedURL = _getParameterFromUrl(finalURL, 'redirect_uri', '?', '#');
+
+    if(typeof extractedRedirectedURL == 'undefined') {
+        chrome.runtime.lastError = { 'message' : 'launchWebAuthFlow: Parameter redirect_uri not found.' };
+        callback();
+    } else {
+        _launchInAppBrowser(finalURL, extractedRedirectedURL, callback);
+    }
+};
+
+function _getAuthTokenJS(win, fail , details) {
+    var failed = false;
+    var failMessage;
+    if(!details.interactive) {
+        // We cannot support non interactive mode.
+        // This requires the ability to use invisible InAppBrowser windows, which is not currently supported
+        failed = true;
+        failMessage = 'Unsupported mode - Non interactive mode is not supported';
+    }
+    var manifestJson = runtime.getManifest();
+    if(typeof manifestJson == 'undefined') {
+        failed = true;
+        failMessage = 'manifest.json is not defined';
+    } else if(typeof manifestJson.oauth2 == 'undefined') {
+        failed = true;
+        failMessage = 'oauth2 missing from manifest.json';
+    } else if(typeof manifestJson.oauth2.client_id == 'undefined') {
+        failed = true;
+        failMessage = 'client_id missing from manifest.json';
+    } else if(typeof manifestJson.oauth2.scopes == 'undefined') {
+        failed = true;
+        failMessage = 'scopes missing from manifest.json';
+    }
+
+    if(failed === true) {
+        chrome.runtime.lastError = { 'message' : failMessage };
+        fail();
+    }
+
+    var authURLBase = 'https://accounts.google.com/o/oauth2/auth?response_type=token';
+    var redirect_uri = 'http://www.google.com';
+    var client_id = manifestJson.oauth2.client_id;
+    var scope = manifestJson.oauth2.scopes;
+    var finalURL = authURLBase + '&redirect_uri=' + encodeURIComponent(redirect_uri) + '&client_id=' + encodeURIComponent(client_id) + '&scope=' + encodeURIComponent(scope.join('&'));
+
+    _launchInAppBrowser(finalURL, redirect_uri, function(newLoc) {
+        var token = _getParameterFromUrl(newLoc, 'access_token', '#');
+        if(typeof token == 'undefined') {
+            chrome.runtime.lastError = { 'message' : 'The redirect uri did not have the access token' };
+            fail();
+        } else {
+            win(token);
+        }
+    });
+}
+
+function _getParameterFromUrl(url, param, startString, endString) {
+    var splitUrl = url;
+    var urlParts;
+    if(typeof startString != 'undefined') {
+        urlParts = splitUrl.split(startString);
+        if(urlParts.length < 2) {
+            return;
+        } else {
+            splitUrl = urlParts[1];
+        }
+    }
+    if(typeof endString != 'undefined') {
+        urlParts = splitUrl.split(endString);
+        splitUrl = urlParts[0];
+    }
+    var vars = splitUrl.split('&');
+    for (var i = 0; i < vars.length; i++) {
+        var pair = vars[i].split('=');
+        // If first entry with this name
+        if (pair[0] === param) {
+            return decodeURIComponent(pair[1]);
+        }
+    }
+}
+
+function _launchInAppBrowser(authURL, redirectedURL, callback) {
+    var oAuthBrowser = window.open(authURL, '_blank', 'location=yes');
+    var listener = function(event) {
+        var newLoc = event.url;
+        if(newLoc.indexOf(redirectedURL) === 0 && newLoc.indexOf('#') !== -1) {
+            oAuthBrowser.removeEventListener('loadstart', listener);
+            oAuthBrowser.close();
+            callback(newLoc);
+        }
+    };
+    oAuthBrowser.addEventListener('loadstart', listener);
+}
+});

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/www/plugins/chrome.socket/socket.js
----------------------------------------------------------------------
diff --git a/www/crx_files/www/plugins/chrome.socket/socket.js b/www/crx_files/www/plugins/chrome.socket/socket.js
new file mode 100644
index 0000000..389eec9
--- /dev/null
+++ b/www/crx_files/www/plugins/chrome.socket/socket.js
@@ -0,0 +1,216 @@
+cordova.define("chrome.socket.Socket", function(require, exports, module) {// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var platform = cordova.require('cordova/platform');
+var exec = cordova.require('cordova/exec');
+
+exports.create = function(socketMode, stuff, callback) {
+    if (typeof stuff == 'function') {
+        callback = stuff;
+        stuff = {};
+    }
+    var win = callback && function(socketId) {
+        var socketInfo = {
+            socketId: socketId
+        };
+        callback(socketInfo);
+    };
+    exec(win, null, 'ChromeSocket', 'create', [socketMode]);
+};
+
+exports.destroy = function(socketId) {
+    exec(null, null, 'ChromeSocket', 'destroy', [socketId]);
+};
+
+
+exports.connect = function(socketId, address, port, callback) {
+    var win = callback && function() {
+        callback(0);
+    };
+    var fail = callback && function() {
+        callback(-1);
+    };
+    exec(win, fail, 'ChromeSocket', 'connect', [socketId, address, port]);
+};
+
+exports.bind = function(socketId, address, port, callback) {
+    var win = callback && function() {
+        callback(0);
+    };
+    var fail = callback && function() {
+        callback(-1);
+    };
+    exec(win, fail, 'ChromeSocket', 'bind', [socketId, address, port]);
+};
+
+exports.disconnect = function(socketId) {
+    exec(null, null, 'ChromeSocket', 'disconnect', [socketId]);
+};
+
+
+exports.read = function(socketId, bufferSize, callback) {
+    if (typeof bufferSize == 'function') {
+        callback = bufferSize;
+        bufferSize = 0;
+    }
+    var win = callback && function(data) {
+        var readInfo = {
+            resultCode: data.byteLength || 1,
+            data: data
+        };
+        callback(readInfo);
+    };
+    var fail = callback && function() {
+        var readInfo = {
+            resultCode: 0
+        };
+        callback(readInfo);
+    };
+    exec(win, fail, 'ChromeSocket', 'read', [socketId, bufferSize]);
+};
+
+exports.write = function(socketId, data, callback) {
+    var type = Object.prototype.toString.call(data).slice(8, -1);
+    if (type != 'ArrayBuffer') {
+        throw new Error('chrome.socket.write - data is not an ArrayBuffer! (Got: ' + type + ')');
+    }
+    var win = callback && function(bytesWritten) {
+        var writeInfo = {
+            bytesWritten: bytesWritten
+        };
+        callback(writeInfo);
+    };
+    var fail = callback && function() {
+        var writeInfo = {
+            bytesWritten: 0
+        };
+        callback(writeInfo);
+    };
+    exec(win, fail, 'ChromeSocket', 'write', [socketId, data]);
+};
+
+
+exports.recvFrom = function(socketId, bufferSize, callback) {
+    if (typeof bufferSize == 'function') {
+        callback = bufferSize;
+        bufferSize = 0;
+    }
+    var win;
+    if (platform.id == 'android') {
+        win = callback && (function() {
+            var data;
+            var call = 0;
+            return function(arg) {
+                if (call === 0) {
+                    data = arg;
+                    call++;
+                } else {
+                    var recvFromInfo = {
+                        resultCode: data.byteLength || 1,
+                        data: data,
+                        address: arg.address,
+                        port: arg.port
+                    };
+
+                    callback(recvFromInfo);
+                }
+            };
+        })();
+    } else {
+        win = callback && function(data, address, port) {
+            var recvFromInfo = {
+                resultCode: data.byteLength || 1,
+                data: data,
+                address: address,
+                port: port
+            };
+            callback(recvFromInfo);
+        };
+    }
+
+    var fail = callback && function() {
+        var readInfo = {
+            resultCode: 0
+        };
+        callback(readInfo);
+    };
+    exec(win, fail, 'ChromeSocket', 'recvFrom', [socketId, bufferSize]);
+};
+
+exports.sendTo = function(socketId, data, address, port, callback) {
+    var type = Object.prototype.toString.call(data).slice(8, -1);
+    if (type != 'ArrayBuffer') {
+        throw new Error('chrome.socket.write - data is not an ArrayBuffer! (Got: ' + type + ')');
+    }
+    var win = callback && function(bytesWritten) {
+        var writeInfo = {
+            bytesWritten: bytesWritten
+        };
+        callback(writeInfo);
+    };
+    var fail = callback && function() {
+        var writeInfo = {
+            bytesWritten: 0
+        };
+        callback(writeInfo);
+    };
+    exec(win, fail, 'ChromeSocket', 'sendTo', [{ socketId: socketId, address: address, port: port }, data]);
+};
+
+
+exports.listen = function(socketId, address, port, backlog, callback) {
+    if (typeof backlog == 'function') {
+        callback = backlog;
+        backlog = 0;
+    }
+    var win = callback && function() {
+        callback(0);
+    };
+    var fail = callback && function() {
+        callback(-1);
+    };
+    exec(win, fail, 'ChromeSocket', 'listen', [socketId, address, port, backlog]);
+};
+
+exports.accept = function(socketId, callback) {
+    var win = callback && function(acceptedSocketId) {
+        var acceptInfo = {
+            resultCode: 0,
+            socketId: acceptedSocketId
+        };
+        callback(acceptInfo);
+    };
+    exec(win, null, 'ChromeSocket', 'accept', [socketId]);
+};
+
+
+exports.setKeepAlive = function() {
+    console.warn('chrome.socket.setKeepAlive not implemented yet');
+};
+
+exports.setNoDelay = function() {
+    console.warn('chrome.socket.setNoDelay not implemented yet');
+};
+
+exports.getInfo = function(socketId, callback) {
+    if (platform.id == 'android') {
+        console.warn('chrome.socket.getInfo not implemented yet');
+        return;
+    }
+    var win = callback && function(result) {
+        result.connected = !!result.connected;
+        callback(result);
+    };
+    exec(win, null, 'ChromeSocket', 'getInfo', [socketId]);
+};
+
+exports.getNetworkList = function(callback) {
+    if (platform.id == 'android') {
+        console.warn('chrome.socket.getNetworkList not implemented yet');
+        return;
+    }
+    exec(callback, null, 'ChromeSocket', 'getNetworkList', []);
+};
+
+});

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/www/plugins/chrome.storage/storage.js
----------------------------------------------------------------------
diff --git a/www/crx_files/www/plugins/chrome.storage/storage.js b/www/crx_files/www/plugins/chrome.storage/storage.js
new file mode 100644
index 0000000..4d6da75
--- /dev/null
+++ b/www/crx_files/www/plugins/chrome.storage/storage.js
@@ -0,0 +1,202 @@
+cordova.define("chrome.storage.Storage", function(require, exports, module) {// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var exports = module.exports;
+var exec = cordova.require('cordova/exec');
+
+function StorageChange(oldValue, newValue) {
+    this.oldValue = oldValue;
+    this.newValue = newValue;
+}
+
+function _jsonReplacer(key) {
+    // Don't use the value passed in since it has already gone through toJSON().
+    var value = this[key];
+    // Refer to:
+    // chrome/src/content/renderer/v8_value_converter_impl.cc&l=165
+    if (value && (typeof value == 'object' || typeof value == 'function')) {
+        var typeName = Object.prototype.toString.call(value).slice(8, -1);
+        if (typeName != 'Array' && typeName != 'Object') {
+            value = {};
+        }
+    }
+    return value;
+}
+
+function _scrubValues(o) {
+    if (typeof o != 'undefined') {
+        var t = JSON.stringify(o, _jsonReplacer);
+        return JSON.parse(t);
+    }
+}
+
+function _calculateChanges(oldKeyVals, newKeyVals) {
+    var ret = {};
+    for(var key in newKeyVals) {
+        if (newKeyVals.hasOwnProperty(key)) {
+            ret[key] = new StorageChange(oldKeyVals[key], newKeyVals[key]);
+        }
+    }
+    return ret;
+}
+
+function _convertToObject(obj) {
+    var ret;
+    if (Array.isArray(obj)) {
+        ret = {};
+        for(var i = 0; i < obj.length; i++) {
+            ret[obj[i]] = undefined;
+        }
+    } else if (typeof obj == 'object') {
+        ret = obj;
+    } else if (typeof obj === 'string') {
+        ret = {};
+        ret[obj] = undefined;
+    }
+    return ret;
+}
+
+function StorageArea(syncStorage, changedEvent) {
+    this._sync = syncStorage;
+    this._changedEvent = changedEvent;
+}
+
+StorageArea.prototype._getAreaName = function() {
+    return (this._sync? 'sync' : 'local');
+};
+
+StorageArea.prototype.get = function(keys, callback) {
+    if (typeof keys == 'function') {
+        callback = keys;
+        keys = null;
+    } else if (typeof keys === 'string') {
+        keys = [keys];
+    }
+    var win = callback && function(args) {
+        callback(args);
+    };
+    var fail = callback && function() {
+        callback();
+    };
+    var param = _scrubValues(keys);
+    exec(win, fail, 'ChromeStorage', 'get', [this._sync, param]);
+};
+
+StorageArea.prototype.getBytesInUse = function(keys, callback) {
+    if (typeof keys == 'function') {
+        callback = keys;
+        keys = null;
+    } else if (typeof keys === 'string') {
+        keys = [keys];
+    }
+    var win = callback && function(bytes) {
+        callback(bytes);
+    };
+    var fail = callback && function() {
+        callback(-1);
+    };
+    var param = _scrubValues(keys);
+    exec(win, fail, 'ChromeStorage', 'getBytesInUse', [this._sync, param]);
+};
+
+StorageArea.prototype.set = function(keyVals, callback) {
+    if (typeof keyVals == 'function') {
+        callback = keyVals;
+        keyVals = null;
+    }
+    var self = this;
+    var param = _scrubValues(keyVals);
+    var fail = callback && function() {
+        callback(-1);
+    };
+    var win;
+    if(self._changedEvent.hasListeners()) {
+        win = function(oldKeyVals) {
+            if(callback) {
+                callback(0);
+            }
+            var newKeyVals = _convertToObject(param);
+            var storageChanges = _calculateChanges(oldKeyVals, newKeyVals);
+            self._changedEvent.fire(storageChanges, self._getAreaName());
+        };
+    } else {
+        win = callback && function() {
+            callback(0);
+        };
+    }
+    exec(win, fail, 'ChromeStorage', 'set', [self._sync, param]);
+};
+
+StorageArea.prototype.remove = function(keys, callback) {
+    if (typeof keys == 'function') {
+        callback = keys;
+        keys = null;
+    } else if (typeof keys === 'string') {
+        keys = [keys];
+    }
+    var self = this;
+    var param = _scrubValues(keys);
+    var fail = callback && function() {
+        callback(-1);
+    };
+    var win;
+    if(self._changedEvent.hasListeners()) {
+        win = function(oldKeyVals) {
+            if(callback) {
+                callback(0);
+            }
+            var newKeyVals = _convertToObject(Object.keys(oldKeyVals));
+            var storageChanges = _calculateChanges(oldKeyVals, newKeyVals);
+            self._changedEvent.fire(storageChanges, self._getAreaName());
+        };
+    } else {
+        win = callback && function() {
+            callback(0);
+        };
+    }
+    exec(win, fail, 'ChromeStorage', 'remove', [self._sync, param]);
+};
+
+StorageArea.prototype.clear = function(callback) {
+    var self = this;
+    var fail = callback && function() {
+        callback(-1);
+    };
+    var win;
+    if(self._changedEvent.hasListeners()) {
+       win = function(oldKeyVals) {
+           if(callback) {
+               callback(0);
+           }
+           var newKeyVals = _convertToObject(Object.keys(oldKeyVals));
+           var storageChanges = _calculateChanges(oldKeyVals, newKeyVals);
+           self._changedEvent.fire(storageChanges, self._getAreaName());
+       };
+    } else {
+        win = callback && function() {
+            callback(0);
+        };
+    }
+    exec(win, fail, 'ChromeStorage', 'clear', [self._sync]);
+};
+
+// TODO(braden): How do we want to handle this event when we're not in a Chrome app?
+var Event = require('chrome.common.events');
+if (Event) {
+  exports.onChanged = new Event('onChanged');
+}
+
+var local = new StorageArea(false, exports.onChanged);
+local.QUOTA_BYTES = 5242880;
+var sync = new StorageArea(true, exports.onChanged);
+sync.MAX_ITEMS = 512;
+sync.MAX_WRITE_OPERATIONS_PER_HOUR = 1000;
+sync.QUOTA_BYTES_PER_ITEM = 4096;
+sync.MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE = 10;
+sync.QUOTA_BYTES = 102400;
+
+exports.local = local;
+exports.sync = sync;
+
+});

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/www/plugins/chrome/api/app/runtime.js
----------------------------------------------------------------------
diff --git a/www/crx_files/www/plugins/chrome/api/app/runtime.js b/www/crx_files/www/plugins/chrome/api/app/runtime.js
new file mode 100644
index 0000000..4a2288c
--- /dev/null
+++ b/www/crx_files/www/plugins/chrome/api/app/runtime.js
@@ -0,0 +1,7 @@
+cordova.define("chrome.app.runtime", function(require, exports, module) {// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var Event = require('chrome.common.events');
+exports.onLaunched = new Event('onLaunched');
+});

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/www/plugins/chrome/api/app/window.js
----------------------------------------------------------------------
diff --git a/www/crx_files/www/plugins/chrome/api/app/window.js b/www/crx_files/www/plugins/chrome/api/app/window.js
new file mode 100644
index 0000000..3a6484d
--- /dev/null
+++ b/www/crx_files/www/plugins/chrome/api/app/window.js
@@ -0,0 +1,156 @@
+cordova.define("chrome.app.window", function(require, exports, module) {// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var Event = require('chrome.common.events');
+var mobile = require('chrome.mobile.impl');
+
+// The AppWindow created by chrome.app.window.create.
+var createdAppWindow = null;
+var dummyNode = document.createElement('a');
+
+function AppWindow() {
+  this.contentWindow = mobile.fgWindow;
+  this.id = '';
+}
+
+function unsupportedApi(api) {
+  return function() {
+    console.warn(api + ' is not supported on mobile.');
+  };
+}
+
+AppWindow.prototype = {
+  restore: unsupportedApi('AppWindow.restore'),
+  moveTo: unsupportedApi('AppWindow.moveTo'),
+  clearAttention: unsupportedApi('AppWindow.clearAttention'),
+  minimize: unsupportedApi('AppWindow.minimize'),
+  drawAttention: unsupportedApi('AppWindow.drawAttention'),
+  focus: unsupportedApi('AppWindow.focus'),
+  resizeTo: unsupportedApi('AppWindow.resizeTo'),
+  maximize: unsupportedApi('AppWindow.maximize'),
+  close: unsupportedApi('AppWindow.close'),
+  setBounds: unsupportedApi('AppWindow.setBounds'),
+  onBoundsChanged: new Event('onBoundsChanged'),
+  onClosed: new Event('onClosed')
+};
+AppWindow.prototype.getBounds = function() {
+  return {
+    width: 0,
+    height: 0,
+    left: 0,
+    top: 0
+  };
+};
+
+function copyAttributes(srcNode, destNode) {
+  var attrs = srcNode.attributes;
+  for (var i = 0, attr; attr = attrs[i]; ++i) {
+    destNode.setAttribute(attr.name, attr.value);
+  }
+}
+
+function applyAttributes(attrText, destNode) {
+  dummyNode.innerHTML = '<a ' + attrText + '>';
+  copyAttributes(dummyNode.firstChild, destNode);
+}
+
+// Evals the scripts serially since sometimes browsers don't execute
+// them in the order they are injected :(.
+// TODO: This is clearly slower for multiple scripts. We could maybe see if
+// injecting after DOM mutation events fire?
+function evalScripts(rootNode, afterFunc) {
+  var scripts = Array.prototype.slice.call(rootNode.getElementsByTagName('script'));
+  var doc = rootNode.ownerDocument;
+  function helper() {
+    var script = scripts.shift();
+    if (!script) {
+      afterFunc && afterFunc();
+      // Don't bother with inline scripts since they aren't evalled on desktop.
+    } else if (script.src) {
+      var replacement = doc.createElement('script');
+      copyAttributes(script, replacement);
+      replacement.onload = helper;
+      script.parentNode.replaceChild(replacement, script);
+    } else {
+      helper();
+    }
+  }
+  helper();
+}
+
+function rewritePage(pageContent, filePath) {
+  var fgBody = mobile.fgWindow.document.body;
+  var fgHead = fgBody.previousElementSibling;
+
+  // fgHead.innerHTML causes a DOMException on Android 2.3.
+  while (fgHead.lastChild) {
+    fgHead.removeChild(fgHead.lastChild);
+  }
+
+  var startIndex = pageContent.search(/<html([\s\S]*?)>/i);
+  if (startIndex != -1) {
+    startIndex += RegExp.lastMatch.length;
+    // Copy over the attributes of the <html> tag.
+    applyAttributes(RegExp.lastParen, fgBody.parentNode);
+  } else {
+    startIndex = 0;
+  }
+
+  function afterBase() {
+    fgHead.insertAdjacentHTML('beforeend', headHtml);
+    evalScripts(fgHead, function() {
+      mobile.eventIframe.insertAdjacentHTML('afterend', pageContent);
+      evalScripts(fgBody)
+    });
+  }
+  // Put everything before the body tag in the head.
+  var endIndex = pageContent.search(/<body([\s\S]*?)>/i);
+  if (endIndex == -1) {
+    mobile.eventIframe.insertAdjacentHTML('afterend', 'Load error: Page is missing body tag.');
+  } else {
+    applyAttributes(RegExp.lastParen, fgBody);
+
+    // Don't bother removing the <body>, </body>, </html>. The browser's sanitizer removes them for us.
+    var headHtml = pageContent.slice(startIndex, endIndex);
+    pageContent = pageContent.slice(endIndex);
+
+    fgHead.insertAdjacentHTML('beforeend', '<link rel="stylesheet" href="chromeappstyles.css">');
+    var baseUrl = filePath.replace(/\/.*?$/, '');
+    if (baseUrl != filePath) {
+      fgHead.insertAdjacentHTML('beforeend', '<base href="' + encodeURIComponent(baseUrl) + '/">\n');
+      // setTimeout required for <base> to take effect for <link> elements (browser bug).
+      window.setTimeout(afterBase, 0);
+    } else {
+      afterBase();
+    }
+  }
+}
+
+exports.create = function(filePath, options, callback) {
+  if (createdAppWindow) {
+    console.log('ERROR - chrome.app.window.create called multiple times. This is unsupported.');
+    return;
+  }
+  createdAppWindow = new AppWindow();
+  var xhr = new XMLHttpRequest();
+  xhr.open('GET', filePath, true);
+  xhr.onreadystatechange = function() {
+    if (xhr.readyState == 4) {
+      // Call the callback before the page contents loads.
+      if (callback) {
+        callback(createdAppWindow);
+      }
+      var pageContent = xhr.responseText || 'Page load failed.';
+      rewritePage(pageContent, filePath);
+      cordova.fireWindowEvent('DOMContentReady');
+      cordova.fireWindowEvent('load');
+    }
+  };
+  xhr.send();
+};
+
+exports.current = function() {
+  return window == mobile.fgWindow ? createdAppWindow : null;
+};
+});

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/www/plugins/chrome/api/bootstrap.js
----------------------------------------------------------------------
diff --git a/www/crx_files/www/plugins/chrome/api/bootstrap.js b/www/crx_files/www/plugins/chrome/api/bootstrap.js
new file mode 100644
index 0000000..51c28dc
--- /dev/null
+++ b/www/crx_files/www/plugins/chrome/api/bootstrap.js
@@ -0,0 +1,13 @@
+cordova.define("chrome.bootstrap", function(require, exports, module) {// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Initialization code for the Chrome plugins API.
+// Adds a deviceready listener that initializes the Chrome wrapper.
+
+console.log('adding event');
+document.addEventListener('deviceready', function() {
+  console.log('deviceready caught');
+  require('chrome.mobile.impl').init();
+});
+});

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/www/plugins/chrome/api/helpers/stubs.js
----------------------------------------------------------------------
diff --git a/www/crx_files/www/plugins/chrome/api/helpers/stubs.js b/www/crx_files/www/plugins/chrome/api/helpers/stubs.js
new file mode 100644
index 0000000..ef57b83
--- /dev/null
+++ b/www/crx_files/www/plugins/chrome/api/helpers/stubs.js
@@ -0,0 +1,11 @@
+cordova.define("chrome.helpers.stubs", function(require, exports, module) {// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+exports.createStub = function(obj, propName, value) {
+  obj.__defineGetter__(propName, function() {
+    console.warn('Access made to stub: ' + obj.__namespace__ + '.' + propName);
+    return value;
+  });
+};
+});

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/www/plugins/chrome/api/mobile.js
----------------------------------------------------------------------
diff --git a/www/crx_files/www/plugins/chrome/api/mobile.js b/www/crx_files/www/plugins/chrome/api/mobile.js
new file mode 100644
index 0000000..28af8b8
--- /dev/null
+++ b/www/crx_files/www/plugins/chrome/api/mobile.js
@@ -0,0 +1,58 @@
+cordova.define("chrome.mobile.impl", function(require, exports, module) {// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var chrome = window.chrome;
+
+exports.fgWindow = window;
+exports.bgWindow = null;
+exports.eventIframe = null;
+
+function createBgChrome() {
+  return {
+    __proto__: chrome,
+    app: {
+      __proto__: chrome.app,
+      window: {
+        __proto__: chrome.app.window,
+        current: function() { return null; }
+      }
+    }
+  };
+}
+
+exports.init = function() {
+  // Self-destruct so that code in here can be GC'ed.
+  exports.init = null;
+  var iframe = document.createElement('iframe');
+  iframe.src = 'chromebgpage.html';
+  iframe.style.display = 'none';
+  exports.eventIframe = iframe;
+  document.body.appendChild(iframe);
+};
+
+exports.bgInit = function(bgWnd) {
+  // Self-destruct so that code in here can be GC'ed.
+  exports.bgInit = null;
+  exports.bgWindow = bgWnd;
+  bgWnd.chrome = createBgChrome();
+  bgWnd.cordova = cordova;
+  exports.fgWindow.opener = exports.bgWindow;
+
+  function onLoad() {
+    bgWnd.removeEventListener('load', onLoad, false);
+    setTimeout(function() {
+      chrome.app.runtime.onLaunched.fire();
+    }, 0);
+  }
+  bgWnd.addEventListener('load', onLoad, false);
+
+  var manifestJson = chrome.runtime.getManifest();
+  var scripts = manifestJson.app.background.scripts;
+  var toWrite = '';
+  for (var i = 0, src; src = scripts[i]; ++i) {
+    toWrite += '<script src="' + encodeURI(src) + '"></sc' + 'ript>\n';
+  }
+  bgWnd.document.write(toWrite);
+};
+});

http://git-wip-us.apache.org/repos/asf/cordova-app-harness/blob/4d4e0b46/www/crx_files/www/plugins/chrome/api/runtime.js
----------------------------------------------------------------------
diff --git a/www/crx_files/www/plugins/chrome/api/runtime.js b/www/crx_files/www/plugins/chrome/api/runtime.js
new file mode 100644
index 0000000..58b3970
--- /dev/null
+++ b/www/crx_files/www/plugins/chrome/api/runtime.js
@@ -0,0 +1,58 @@
+cordova.define("chrome.runtime", function(require, exports, module) {// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var argscheck = cordova.require('cordova/argscheck');
+var Event = require('chrome.common.events');
+var stubs = require('chrome.helpers.stubs');
+var mobile = require('chrome.mobile.impl');
+var manifestJson = null;
+
+exports.onSuspend = new Event('onSuspend');
+exports.onInstalled = new Event('onInstalled');
+exports.onStartup = new Event('onStartup');
+exports.onSuspendCanceled = new Event('onSuspendCanceled');
+exports.onUpdateAvailable = new Event('onUpdateAvailable');
+
+var original_addListener = exports.onSuspend.addListener;
+
+// Uses a trampoline to bind the Cordova pause event on the first call.
+exports.onSuspend.addListener = function(f) {
+  window.document.addEventListener('pause', exports.onSuspend.fire, false);
+  exports.onSuspend.addListener = original_addListener;
+  exports.onSuspend.addListener(f);
+};
+
+exports.getManifest = function() {
+  if (!manifestJson) {
+    var xhr = new XMLHttpRequest();
+    xhr.open('GET', 'manifest.json', false /* sync */);
+    xhr.send(null);
+    manifestJson = eval('(' + xhr.responseText + ')'); //JSON.parse(xhr.responseText);
+  }
+  return manifestJson;
+};
+
+exports.getBackgroundPage = function(callback) {
+  argscheck.checkArgs('f', 'chrome.runtime.getBackgroundPage', arguments);
+  setTimeout(function() {
+    callback(mobile.bgWindow);
+  }, 0);
+};
+
+exports.getURL = function(subResource) {
+  argscheck.checkArgs('s', 'chrome.runtime.getURL', arguments);
+  if (subResource.charAt(0) == '/') {
+    subResource = subResource.slice(1);
+  }
+  var prefix = location.href.replace(/[^\/]*$/, '');
+  return prefix + subResource;
+};
+
+exports.reload = function() {
+  location.reload();
+};
+
+stubs.createStub(exports, 'id', '{appId}');
+stubs.createStub(exports, 'requestUpdateCheck', function(){});
+});