Merge mozilla-central and b2g-inbound
authorEd Morley <emorley@mozilla.com>
Wed, 11 Jun 2014 18:06:52 +0100
changeset 208971 6368f6787d3e75765f085a2356341b2b7facf8b5
parent 208957 4876594bacaa3d1e0acf0f1b21b231e220cf1c32 (current diff)
parent 208970 618a4eb46fdaa9430cd607b0d705a8e285902324 (diff)
child 208972 353980e68b9668849d197bfda63bf6b5b7640fac
push id3857
push userraliiev@mozilla.com
push dateTue, 02 Sep 2014 16:39:23 +0000
treeherdermozilla-beta@5638b907b505 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central and b2g-inbound
dom/apps/src/Webapps.jsm
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="508c85713fdf35eb2b3c8e8340e1d2ee306e6aec"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="28e9b207fa9a6f42ad94459ddb3057f7e240f796"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="8e4420c0c5c8e8c8e58a000278a7129403769f96"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="a819a94a572c7b32556435491ed8eaab841a95ff"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="cabebb87fcd32f8596af08e6b5e80764ee0157dd"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="508c85713fdf35eb2b3c8e8340e1d2ee306e6aec"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="28e9b207fa9a6f42ad94459ddb3057f7e240f796"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="cabebb87fcd32f8596af08e6b5e80764ee0157dd"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="276ce45e78b09c4a4ee643646f691d22804754c1">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="508c85713fdf35eb2b3c8e8340e1d2ee306e6aec"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="28e9b207fa9a6f42ad94459ddb3057f7e240f796"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="cabebb87fcd32f8596af08e6b5e80764ee0157dd"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="508c85713fdf35eb2b3c8e8340e1d2ee306e6aec"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="28e9b207fa9a6f42ad94459ddb3057f7e240f796"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="8e4420c0c5c8e8c8e58a000278a7129403769f96"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="a819a94a572c7b32556435491ed8eaab841a95ff"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="cabebb87fcd32f8596af08e6b5e80764ee0157dd"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="508c85713fdf35eb2b3c8e8340e1d2ee306e6aec"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="28e9b207fa9a6f42ad94459ddb3057f7e240f796"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="cabebb87fcd32f8596af08e6b5e80764ee0157dd"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "489903115b8067c6d565e37626213da4ba592331", 
+    "revision": "d6cf58c5909ec588ac43c88358181201e5f581ad", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="508c85713fdf35eb2b3c8e8340e1d2ee306e6aec"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="28e9b207fa9a6f42ad94459ddb3057f7e240f796"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="cabebb87fcd32f8596af08e6b5e80764ee0157dd"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="508c85713fdf35eb2b3c8e8340e1d2ee306e6aec"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="28e9b207fa9a6f42ad94459ddb3057f7e240f796"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="508c85713fdf35eb2b3c8e8340e1d2ee306e6aec"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="28e9b207fa9a6f42ad94459ddb3057f7e240f796"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="cabebb87fcd32f8596af08e6b5e80764ee0157dd"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="508c85713fdf35eb2b3c8e8340e1d2ee306e6aec"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="28e9b207fa9a6f42ad94459ddb3057f7e240f796"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="dbb66e540194a187326cece28ae0b51cdd500184"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="cabebb87fcd32f8596af08e6b5e80764ee0157dd"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/dom/apps/src/PermissionsInstaller.jsm
+++ b/dom/apps/src/PermissionsInstaller.jsm
@@ -51,17 +51,18 @@ this.PermissionsInstaller = {
    *        The just-installed app configuration.
    *        The properties used are manifestURL, origin and manifest.
    * @param boolean aIsReinstall
    *        Indicates the app was just re-installed
    * @param function aOnError
    *        A function called if an error occurs
    * @returns void
    **/
-  installPermissions: function installPermissions(aApp, aIsReinstall, aOnError) {
+  installPermissions: function installPermissions(aApp, aIsReinstall, aOnError,
+                                                  aIsSystemUpdate) {
     try {
       let newManifest = new ManifestHelper(aApp.manifest, aApp.origin);
       if (!newManifest.permissions && !aIsReinstall) {
         return;
       }
 
       if (aIsReinstall) {
         // Compare the original permissions against the new permissions
@@ -140,32 +141,47 @@ this.PermissionsInstaller = {
                " is not a valid Webapps permission name.");
           continue;
         }
 
         let expandedPermNames =
           expandPermissions(permName,
                             newManifest.permissions[permName].access);
         for (let idx in expandedPermNames) {
+
+          let isPromptPermission =
+            PermissionsTable[permName][appStatus] === PROMPT_ACTION;
+
           // We silently upgrade the permission to whatever the permission
           // is for certified apps (ALLOW or PROMPT) only if the
           // following holds true:
           // * The app is preinstalled
           // * The permission that would be granted is PROMPT
           // * The app is privileged
           let permission =
-            aApp.isPreinstalled &&
-            PermissionsTable[permName][appStatus] === PROMPT_ACTION &&
+            aApp.isPreinstalled && isPromptPermission &&
             appStatus === "privileged"
                 ? PermissionsTable[permName]["certified"]
                 : PermissionsTable[permName][appStatus];
 
-          this._setPermission(expandedPermNames[idx],
-                              PERM_TO_STRING[permission],
-                              aApp);
+          let permValue = PERM_TO_STRING[permission];
+          if (!aIsSystemUpdate && isPromptPermission) {
+            // If it's not a system update, then we should keep the prompt
+            // permissions that have been granted or denied previously.
+            permValue =
+              PermissionSettingsModule.getPermission(permName,
+                                                     aApp.manifestURL,
+                                                     aApp.origin,
+                                                     false);
+            if (permValue === "unknown") {
+              permValue = PERM_TO_STRING[permission];
+            }
+          }
+
+          this._setPermission(expandedPermNames[idx], permValue, aApp);
         }
       }
     }
     catch (ex) {
       dump("Caught webapps install permissions error for " + aApp.origin);
       Cu.reportError(ex);
       if (aOnError) {
         aOnError();
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -346,32 +346,33 @@ this.DOMApplicationRegistry = {
 
     // Create or Update the DataStore for this app
     let results = yield this._readManifests([{ id: aId }]);
     let app = this.webapps[aId];
     this.updateDataStore(app.localId, app.origin, app.manifestURL,
                          results[0].manifest, app.appStatus);
   }),
 
-  updatePermissionsForApp: function(aId, aIsPreinstalled) {
+  updatePermissionsForApp: function(aId, aIsPreinstalled, aIsSystemUpdate) {
     if (!this.webapps[aId]) {
       return;
     }
 
     // Install the permissions for this app, as if we were updating
     // to cleanup the old ones if needed.
     // TODO It's not clear what this should do when there are multiple profiles.
     if (supportUseCurrentProfile()) {
       this._readManifests([{ id: aId }]).then((aResult) => {
         let data = aResult[0];
         PermissionsInstaller.installPermissions({
           manifest: data.manifest,
           manifestURL: this.webapps[aId].manifestURL,
           origin: this.webapps[aId].origin,
-          isPreinstalled: aIsPreinstalled
+          isPreinstalled: aIsPreinstalled,
+          isSystemUpdate: aIsSystemUpdate
         }, true, function() {
           debug("Error installing permissions for " + aId);
         });
       });
     }
   },
 
   updateOfflineCacheForApp: function(aId) {
@@ -593,17 +594,18 @@ this.DOMApplicationRegistry = {
         // At first run, install preloaded apps and set up their permissions.
         for (let id in this.webapps) {
           let isPreinstalled = this.installPreinstalledApp(id);
           this.removeIfHttpsDuplicate(id);
           if (!this.webapps[id]) {
             continue;
           }
           this.updateOfflineCacheForApp(id);
-          this.updatePermissionsForApp(id, isPreinstalled);
+          this.updatePermissionsForApp(id, isPreinstalled,
+                                       true /* isSystemUpdate */);
         }
         // Need to update the persisted list of apps since
         // installPreinstalledApp() removes the ones failing to install.
         this._saveApps();
       }
 
       // DataStores must be initialized at startup.
       for (let id in this.webapps) {
--- a/dom/apps/tests/file_packaged_app.template.webapp
+++ b/dom/apps/tests/file_packaged_app.template.webapp
@@ -1,13 +1,19 @@
 {
   "name" : "NAMETOKEN",
   "version" : "VERSIONTOKEN",
   "size" : PACKAGESIZETOKEN,
   "package_path": "PACKAGEPATHTOKEN",
   "description": "Updated even faster than Firefox, just to annoy slashdotters",
+  "permissions": {
+     "geolocation": {},
+     "audio-capture": {},
+     "video-capture": {},
+     "downloads": {}
+   },
   "launch_path": "tests/dom/apps/tests/file_packaged_app.sjs",
   "developer": {
     "name": "DEVELOPERTOKEN",
     "url": "DEVELOPERURLTOKEN"
   },
   "default_locale": "en-US"
 }
--- a/dom/apps/tests/test_packaged_app_update.html
+++ b/dom/apps/tests/test_packaged_app_update.html
@@ -50,17 +50,17 @@ function checkForUpdate(aExpected, aOnSu
     } else {
       PackagedTestHelper.next();
     }
   };
 }
 
 function checkLastAppState(aMiniManifestURL, aExpectedReady, aExpectedDownload,
                            aExpectedVersion, aCb) {
-  ok(true, aExpectedReady ? "App downloaded" : "App download applied");
+  info(aExpectedReady ? "App downloaded" : "App download applied");
   var expected = {
     name: PackagedTestHelper.gAppName,
     manifestURL: aMiniManifestURL,
     installOrigin: PackagedTestHelper.gInstallOrigin,
     progress: 0,
     installState: aExpectedReady ? "updating" : "installed",
     downloadAvailable: aExpectedDownload,
     downloading: false,
@@ -86,115 +86,192 @@ function updateApp(aExpectedReady, aPrev
         navigator.mozApps.mgmt.applyDownload(lApp);
     });
 
     checkForUpdate(true, ondownloadsuccesshandler, ondownloadappliedhandler, null,
                    true);
 
 }
 
+var initialPermissionState = {
+  "geolocation": "prompt",
+  "audio-capture": "prompt",
+  "video-capture": "prompt",
+  "downloads": "deny"
+}
+
+var permissionsToSet = {
+  "geolocation": "allow",
+  "audio-capture": "deny"
+}
+
+var permissionsToCheck = {
+  "geolocation": "allow",
+  "audio-capture": "deny",
+  "video-capture": "prompt",
+  "downloads": "deny"
+}
+
+function validatePermissions(aList, aDontFail) {
+  var gApp = PackagedTestHelper.gApp;
+  var mozPermissions = window.navigator.mozPermissionSettings;
+  var permission;
+  for (permission in aList) {
+    var permValue = mozPermissions.get(permission, gApp.manifestURL,
+                                       gApp.origin, false);
+    var wouldFail = permValue != aList[permission];
+    var checkFun = (aDontFail && wouldFail) ? todo_is : is;
+    checkFun(permValue, aList[permission],
+            "Permission " + permission + " should be " + aList[permission]);
+  }
+}
+
 
 var steps = [
   function() {
     // Set up
     SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.addPermission("webapps-manage", true, document);
-    ok(true, "Set up");
-    PackagedTestHelper.next();
+    info("Set up");
+    // Note that without useCurrentProfile the permissions just aren't added.
+    SpecialPowers.pushPermissions(
+      [{'type': 'permissions', 'allow': true, 'context': document}],
+      function() {
+        SpecialPowers.pushPrefEnv(
+           {"set": [["dom.mozPermissionSettings.enabled", true],
+                    ["dom.webapps.useCurrentProfile", true]]},
+           PackagedTestHelper.next);
+      }
+    );
   },
   function() {
-    ok(true, "autoConfirmAppInstall");
+    info("autoConfirmAppInstall");
     SpecialPowers.autoConfirmAppInstall(PackagedTestHelper.next);
   },
   function() {
     PackagedTestHelper.setAppVersion(2, PackagedTestHelper.next);
   },
   function() {
-    ok(true, "== TEST == Install packaged app");
+    info("== TEST == Install packaged app");
     navigator.mozApps.mgmt.oninstall = function(evt) {
-      ok(true, "Got oninstall event");
+      info("Got oninstall event");
       PackagedTestHelper.gApp = evt.application;
       PackagedTestHelper.gApp.ondownloaderror = function() {
         ok(false, "Download error " + PackagedTestHelper.gApp.downloadError.name);
         PackagedTestHelper.finish();
       };
       PackagedTestHelper.gApp.ondownloadsuccess =
         checkLastAppState.bind(undefined, miniManifestURL, false, false,
                                2, PackagedTestHelper.next);
     };
 
     var request = navigator.mozApps.installPackage(miniManifestURL);
     request.onerror = PackagedTestHelper.mozAppsError;
     request.onsuccess = function() {
-      ok(true, "Application installed");
+      info("Application installed");
     };
   },
   function() {
-    ok(true, "== TEST == Check for Update and try to download it without update available");
+    info("== TEST == Permissions installed correctly");
+    validatePermissions(initialPermissionState);
+    PackagedTestHelper.next();
+  },
+  function() {
+    info("== TEST == Check for Update and try to download it without update available");
 
     function onerror() {
       is(PackagedTestHelper.gApp.downloadError.name, "NO_DOWNLOAD_AVAILABLE", "Download not available");
       ok(!PackagedTestHelper.gApp.readyToApplyDownload, "Not ready to apply download");
       PackagedTestHelper.next();
     }
     function onsuccess() {
       ok(false, "ondownloadsuccess fired");
       PackagedTestHelper.next();
     }
 
     checkForUpdate(false, onsuccess, null, onerror, true);
   },
   function() {
+    info("== TEST == Remember permissions");
+    var gApp = PackagedTestHelper.gApp;
+    var mozPermissions = window.navigator.mozPermissionSettings;
+    var permission;
+    for (permission in permissionsToSet) {
+      try {
+        window.navigator.mozPermissionSettings.set(permission,
+                                                   permissionsToSet[permission],
+                                                   gApp.manifestURL,
+                                                   gApp.origin, false);
+      } catch (e) {
+        ok(false,
+           "mozPermissionSettings.set failed for " + permission + " - " + e);
+      }
+    }
+    PackagedTestHelper.next();
+  },
+  function() {
+    info("== TEST == Check that the permissions have been saved");
+    // Since the permission API isn't really synchronous, just log any errors
+    // here.
+    validatePermissions(permissionsToCheck, true /*dontFail*/);
+    PackagedTestHelper.next();
+  },
+  function() {
     PackagedTestHelper.setAppVersion(3, PackagedTestHelper.next);
   },
   function() {
-    ok(true, "== TEST == Update packaged app");
+    info("== TEST == Update packaged app");
     updateApp(true, 2, 3);
   },
   function() {
-    ok(true, "== TEST == Check for Update after getting a new package");
+    info("== TEST == Check that saved permissions were kept");
+    validatePermissions(permissionsToCheck);
+    PackagedTestHelper.next();
+  },
+  function() {
+    info("== TEST == Check for Update after getting a new package");
     checkForUpdate(false);
   },
   function() {
     PackagedTestHelper.setAppVersion(4, PackagedTestHelper.next, true);
   },
   function() {
-    ok(true, "== TEST == Update packaged app - same package");
+    info("== TEST == Update packaged app - same package");
     updateApp(false, 3, 3);
   },
   function() {
-    ok(true, "== TEST == Check for Update after getting the same package");
+    info("== TEST == Check for Update after getting the same package");
     checkForUpdate(false);
   },
   function() {
     PackagedTestHelper.setAppVersion(1, PackagedTestHelper.next);
   },
   function() {
-    ok(true, "== TEST == Update packaged app - Updating a pending app");
+    info("== TEST == Update packaged app - Updating a pending app");
     miniManifestURL = PackagedTestHelper.gSJS +
                       "?getManifest=true" +
                       "&appName=arandomname" +
                       "&appToFail1";
     PackagedTestHelper.checkAppDownloadError(miniManifestURL,
                                             "MANIFEST_MISMATCH", 2, false, true,
                                              "arandomname",
                                              function () {
       checkForUpdate(false, null, null, null, false,
                      function (request) {
         if (request.error.name === "PENDING_APP_NOT_UPDATABLE") {
-          ok(true, "Got expected PENDING_APP_NOT_UPDATEABLE");
+          info("Got expected PENDING_APP_NOT_UPDATEABLE");
         } else {
           ok(false, "Got unexpected " + request.error.name);
         }
         PackagedTestHelper.next();
       });
     });
   },
   function() {
-    ok(true, "all done!\n");
+    info("all done!\n");
     PackagedTestHelper.finish();
   }
 ];
 
 PackagedTestHelper.setSteps(steps);
 // appToUpdate added to the URL so we get a unique URL for this app.
 // Unique in this case meaning different from the ones used on the
 // install tests
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/marionette/manifest.ini
@@ -0,0 +1,15 @@
+[DEFAULT]
+; true if the test requires an emulator, otherwise false
+qemu = false
+
+; true if the test is compatible with the browser, otherwise false
+browser = true
+
+; true if the test is compatible with b2g, otherwise false
+b2g = true
+
+; true if the test should be skipped
+skip = false
+
+[test_touchcaret.py]
+b2g = false ; Bug 1020261
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/marionette/test_touchcaret.py
@@ -0,0 +1,329 @@
+# -*- coding: utf-8 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from by import By
+from marionette import Actions
+from marionette_test import MarionetteTestCase
+
+
+class TouchCaretTest(MarionetteTestCase):
+    _input_selector = (By.ID, 'input')
+    _textarea_selector = (By.ID, 'textarea')
+    _contenteditable_selector = (By.ID, 'contenteditable')
+    _large_expiration_time = 3000 * 20  # 60 seconds
+
+    def setUp(self):
+        # Code to execute before a test is being run.
+        MarionetteTestCase.setUp(self)
+        self.actions = Actions(self.marionette)
+        self.original_expiration_time = self.expiration_time
+
+    def tearDown(self):
+        # Code to execute after a test is being run.
+        self.expiration_time = self.original_expiration_time
+        MarionetteTestCase.tearDown(self)
+
+    @property
+    def expiration_time(self):
+        'Return touch caret expiration time in milliseconds.'
+        return self.marionette.execute_script(
+            'return SpecialPowers.getIntPref("touchcaret.expiration.time");')
+
+    @expiration_time.setter
+    def expiration_time(self, expiration_time):
+        'Set touch caret expiration time in milliseconds.'
+        self.marionette.execute_script(
+            'SpecialPowers.setIntPref("touchcaret.expiration.time", arguments[0]);',
+            script_args=[expiration_time])
+
+    def openTestHtml(self, enabled=True, expiration_time=None):
+        '''Open html for testing and locate elements, enable/disable touch caret, and
+        set touch caret expiration time in milliseconds).
+
+        '''
+        self.marionette.execute_script(
+            'SpecialPowers.setBoolPref("touchcaret.enabled", %s);' %
+            ('true' if enabled else 'false'))
+
+        # Set a larger expiration time to avoid intermittent test failures.
+        if expiration_time is not None:
+            self.expiration_time = expiration_time
+
+        test_html = self.marionette.absolute_url('test_touchcaret.html')
+        self.marionette.navigate(test_html)
+
+        self._input = self.marionette.find_element(*self._input_selector)
+        self._textarea = self.marionette.find_element(*self._textarea_selector)
+        self._contenteditable = self.marionette.find_element(*self._contenteditable_selector)
+
+    def is_input_or_textarea(self, element):
+        '''Return True if element is either <input> or <textarea>'''
+        return element.tag_name in ('input', 'textarea')
+
+    def get_js_selection_cmd(self, element):
+        '''Return a command snippet to get selection object.
+
+        If the element is <input> or <textarea>, return the selection object
+        associated with it. Otherwise, return the current selection object.
+
+        Note: "element" must be provided as the first argument to
+        execute_script().
+
+        '''
+        if self.is_input_or_textarea(element):
+            # We must unwrap sel so that DOMRect could be returned to Python
+            # side.
+            return '''var sel = SpecialPowers.wrap(arguments[0]).editor.selection;
+                   sel = SpecialPowers.unwrap(sel);'''
+        else:
+            return '''var sel = window.getSelection();'''
+
+    def caret_rect(self, element):
+        '''Return the caret's DOMRect object.
+
+        If the element is either <input> or <textarea>, return the caret's
+        DOMRect within the element. Otherwise, return the DOMRect of the
+        current selected caret.
+
+        '''
+        cmd = self.get_js_selection_cmd(element) +\
+            '''return sel.getRangeAt(0).getClientRects()[0];'''
+        return self.marionette.execute_script(cmd, script_args=[element])
+
+    def caret_location(self, element):
+        '''Return caret's center location by the number of characters offset
+        within the given element.
+
+        Return (x, y) coordinates of the caret's center by the number of
+        characters offset relative to the top left-hand corner of the given
+        element.
+
+        '''
+        rect = self.caret_rect(element)
+        x = rect['left'] + rect['width'] / 2.0 - element.location['x']
+        y = rect['top'] + rect['height'] / 2.0 - element.location['y']
+        return x, y
+
+    def touch_caret_location(self, element):
+        '''Return touch caret's location (based on current caret location).
+
+        Return (x, y) coordinates of the touch caret's tip relative to the top
+        left-hand corner of the given element.
+
+        '''
+        rect = self.caret_rect(element)
+        x = rect['left'] - element.location['x']
+
+        # Touch caret's tip is below the bottom of the caret. Add 5px to y
+        # should be sufficient to locate it.
+        y = rect['bottom'] + 5 - element.location['y']
+
+        return x, y
+
+    def move_caret_by_offset(self, element, offset, backward=False):
+        '''Move caret in the element by offset.'''
+        cmd = self.get_js_selection_cmd(element) +\
+            '''sel.modify("move", arguments[1], "character");'''
+        direction = 'backward' if backward else 'forward'
+
+        for i in range(offset):
+            self.marionette.execute_script(
+                cmd, script_args=[element, direction])
+
+    def move_caret_to_front(self, element):
+        if self.is_input_or_textarea(element):
+            cmd = '''arguments[0].setSelectionRange(0, 0);'''
+        else:
+            cmd = '''var sel = window.getSelection();
+                  sel.collapse(arguments[0].firstChild, 0);'''
+
+        self.marionette.execute_script(cmd, script_args=[element])
+
+    def move_caret_to_end(self, element):
+        if self.is_input_or_textarea(element):
+            cmd = '''var len = arguments[0].value.length;
+                  arguments[0].setSelectionRange(len, len);'''
+        else:
+            cmd = '''var sel = window.getSelection();
+                  sel.collapse(arguments[0].lastChild, arguments[0].lastChild.length);'''
+
+        self.marionette.execute_script(cmd, script_args=[element])
+
+    def get_content(self, element):
+        '''Return the content of the element.'''
+        if self.is_input_or_textarea(element):
+            return element.get_attribute('value')
+        else:
+            return element.text
+
+    def _test_move_caret_to_the_right_by_one_character(self, el, assertFunc):
+        content_to_add = '!'
+        target_content = self.get_content(el)
+        target_content = target_content[:1] + content_to_add + target_content[1:]
+
+        # Get touch caret (x, y) at position 1 and 2.
+        self.move_caret_to_front(el)
+        caret0_x, caret0_y = self.caret_location(el)
+        touch_caret0_x, touch_caret0_y = self.touch_caret_location(el)
+        self.move_caret_by_offset(el, 1)
+        touch_caret1_x, touch_caret1_y = self.touch_caret_location(el)
+
+        # Tap the front of the input to make touch caret appear.
+        el.tap(caret0_x, caret0_y)
+
+        # Move touch caret
+        self.actions.flick(el, touch_caret0_x, touch_caret0_y,
+                           touch_caret1_x, touch_caret1_y).perform()
+
+        el.send_keys(content_to_add)
+        assertFunc(target_content, self.get_content(el))
+
+    def _test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self, el, assertFunc):
+        content_to_add = '!'
+        target_content = self.get_content(el) + content_to_add
+
+        # Tap the front of the input to make touch caret appear.
+        self.move_caret_to_front(el)
+        el.tap(*self.caret_location(el))
+
+        # Move touch caret to the bottom-right corner of the element.
+        src_x, src_y = self.touch_caret_location(el)
+        dest_x, dest_y = el.size['width'], el.size['height']
+        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
+
+        el.send_keys(content_to_add)
+        assertFunc(target_content, self.get_content(el))
+
+    def _test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self, el, assertFunc):
+        content_to_add = '!'
+        target_content = content_to_add + self.get_content(el)
+
+        # Tap to make touch caret appear. Note: it's strange that when the caret
+        # is at the end, the rect of the caret in <textarea> cannot be obtained.
+        # A bug perhaps.
+        self.move_caret_to_end(el)
+        self.move_caret_by_offset(el, 1, backward=True)
+        el.tap(*self.caret_location(el))
+
+        # Move touch caret to the top-left corner of the input box.
+        src_x, src_y = self.touch_caret_location(el)
+        dest_x, dest_y = 0, 0
+        self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
+
+        el.send_keys(content_to_add)
+        assertFunc(target_content, self.get_content(el))
+
+    def _test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self, el, assertFunc):
+        content_to_add = '!'
+        non_target_content = content_to_add + self.get_content(el)
+
+        # Get touch caret expiration time in millisecond, and convert it to second.
+        timeout = self.expiration_time / 1000.0
+
+        # Tap to make touch caret appear. Note: it's strange that when the caret
+        # is at the end, the rect of the caret in <textarea> cannot be obtained.
+        # A bug perhaps.
+        self.move_caret_to_end(el)
+        self.move_caret_by_offset(el, 1, backward=True)
+        el.tap(*self.caret_location(el))
+
+        # Wait until touch caret disappears, then pretend to move it to the
+        # top-left corner of the input box.
+        src_x, src_y = self.touch_caret_location(el)
+        dest_x, dest_y = 0, 0
+        self.actions.wait(timeout).flick(el, src_x, src_y, dest_x, dest_y).perform()
+
+        el.send_keys(content_to_add)
+        assertFunc(non_target_content, self.get_content(el))
+
+    ########################################################################
+    # <input> test cases with touch caret enabled
+    ########################################################################
+    def test_input_move_caret_to_the_right_by_one_character(self):
+        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
+        self._test_move_caret_to_the_right_by_one_character(self._input, self.assertEqual)
+
+    def test_input_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
+        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
+        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._input, self.assertEqual)
+
+    def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
+        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
+        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._input, self.assertEqual)
+
+    def test_input_touch_caret_timeout(self):
+        self.openTestHtml(enabled=True)
+        self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._input, self.assertNotEqual)
+
+    ########################################################################
+    # <input> test cases with touch caret disabled
+    ########################################################################
+    def test_input_move_caret_to_the_right_by_one_character_disabled(self):
+        self.openTestHtml(enabled=False)
+        self._test_move_caret_to_the_right_by_one_character(self._input, self.assertNotEqual)
+
+    def test_input_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
+        self.openTestHtml(enabled=False)
+        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._input, self.assertNotEqual)
+
+    ########################################################################
+    # <textarea> test cases with touch caret enabled
+    ########################################################################
+    def test_textarea_move_caret_to_the_right_by_one_character(self):
+        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
+        self._test_move_caret_to_the_right_by_one_character(self._textarea, self.assertEqual)
+
+    def test_textarea_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
+        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
+        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._textarea, self.assertEqual)
+
+    def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
+        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
+        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._textarea, self.assertEqual)
+
+    def test_textarea_touch_caret_timeout(self):
+        self.openTestHtml(enabled=True)
+        self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._textarea, self.assertNotEqual)
+
+    ########################################################################
+    # <textarea> test cases with touch caret disabled
+    ########################################################################
+    def test_textarea_move_caret_to_the_right_by_one_character_disabled(self):
+        self.openTestHtml(enabled=False)
+        self._test_move_caret_to_the_right_by_one_character(self._textarea, self.assertNotEqual)
+
+    def test_textarea_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
+        self.openTestHtml(enabled=False)
+        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._textarea, self.assertNotEqual)
+
+    ########################################################################
+    # <div> contenteditable test cases with touch caret enabled
+    ########################################################################
+    def test_contenteditable_move_caret_to_the_right_by_one_character(self):
+        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
+        self._test_move_caret_to_the_right_by_one_character(self._contenteditable, self.assertEqual)
+
+    def test_contenteditable_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self):
+        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
+        self._test_move_caret_to_end_by_dragging_touch_caret_to_bottom_right_corner(self._contenteditable, self.assertEqual)
+
+    def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self):
+        self.openTestHtml(enabled=True, expiration_time=self._large_expiration_time)
+        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._contenteditable, self.assertEqual)
+
+    def test_contenteditable_touch_caret_timeout(self):
+        self.openTestHtml(enabled=True)
+        self._test_touch_caret_timeout_by_dragging_it_to_top_left_corner_after_timout(self._contenteditable, self.assertNotEqual)
+
+    ########################################################################
+    # <div> contenteditable test cases with touch caret disabled
+    ########################################################################
+    def test_contenteditable_move_caret_to_the_right_by_one_character_disabled(self):
+        self.openTestHtml(enabled=False)
+        self._test_move_caret_to_the_right_by_one_character(self._contenteditable, self.assertNotEqual)
+
+    def test_contenteditable_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner_disabled(self):
+        self.openTestHtml(enabled=False)
+        self._test_move_caret_to_front_by_dragging_touch_caret_to_top_left_corner(self._contenteditable, self.assertNotEqual)
--- a/testing/marionette/client/marionette/tests/unit-tests.ini
+++ b/testing/marionette/client/marionette/tests/unit-tests.ini
@@ -23,10 +23,13 @@ skip = false
 [include:../../../../../dom/mobileconnection/tests/marionette/manifest.ini]
 [include:../../../../../dom/system/gonk/tests/marionette/manifest.ini]
 [include:../../../../../dom/icc/tests/marionette/manifest.ini]
 [include:../../../../../dom/system/tests/marionette/manifest.ini]
 [include:../../../../../dom/nfc/tests/marionette/manifest.ini]
 [include:../../../../../dom/events/test/marionette/manifest.ini]
 [include:../../../../../dom/wifi/test/marionette/manifest.ini]
 
+; layout tests
+[include:../../../../../layout/base/tests/marionette/manifest.ini]
+
 ; loop tests
 [include:../../../../../browser/components/loop/manifest.ini]
new file mode 100644
--- /dev/null
+++ b/testing/marionette/client/marionette/www/test_touchcaret.html
@@ -0,0 +1,17 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html>
+<html id="html">
+  <head>
+    <title>Bug 960897: Marionette tests for touch caret</title>
+  </head>
+  <body>
+    <div><input id="input" value="ABCDEFGHI"></input></div>
+    <br />
+    <div><textarea name="textarea" id="textarea" rows="4" cols="6">ABCDEFGHI</textarea></div>
+    <br />
+    <div style="width: 10em; height: 2em; word-wrap: break-word; overflow: auto;" contenteditable="true" id="contenteditable">ABCDEFGHI</div>
+  </body>
+</html>