Bug 1147699 - Part 14: Add a test for FetchEvent.request.context when intercepting loads coming from plugins; r=nsm
authorEhsan Akhgari <ehsan@mozilla.com>
Thu, 26 Mar 2015 17:10:39 -0400
changeset 236610 e7e532d69293158d2f98bb6f21b326d68c4ff563
parent 236609 a34afcef2bd5bf71dd8ea67fb418023d19c00503
child 236611 f448a71407475ae65db53ff31d8c2a57ea62ecfa
push id28514
push usercbook@mozilla.com
push dateTue, 31 Mar 2015 12:46:33 +0000
treeherdermozilla-central@c20f8549d631 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnsm
bugs1147699
milestone39.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
Bug 1147699 - Part 14: Add a test for FetchEvent.request.context when intercepting loads coming from plugins; r=nsm
dom/cache/DBSchema.cpp
dom/workers/test/serviceworkers/fetch/context/context_test.js
dom/workers/test/serviceworkers/fetch/context/index.html
dom/workers/test/serviceworkers/fetch/context/xml.xml
dom/workers/test/serviceworkers/mochitest.ini
dom/workers/test/serviceworkers/test_request_context.html
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -15,23 +15,24 @@
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 #include "nsCRT.h"
 #include "nsHttp.h"
 #include "mozilla/dom/HeadersBinding.h"
 #include "mozilla/dom/RequestBinding.h"
 #include "mozilla/dom/ResponseBinding.h"
 #include "Types.h"
+#include "nsIContentPolicy.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
-const int32_t DBSchema::kMaxWipeSchemaVersion = 4;
-const int32_t DBSchema::kLatestSchemaVersion = 4;
+const int32_t DBSchema::kMaxWipeSchemaVersion = 5;
+const int32_t DBSchema::kLatestSchemaVersion = 5;
 const int32_t DBSchema::kMaxEntriesPerStatement = 255;
 
 // If any of the static_asserts below fail, it means that you have changed
 // the corresponding WebIDL enum in a way that may be incompatible with the
 // existing data stored in the DOM Cache.  You would need to update the Cache
 // database schema accordingly and adjust the failing static_assert.
 static_assert(int(HeadersGuardEnum::None) == 0 &&
               int(HeadersGuardEnum::Request) == 1 &&
@@ -46,16 +47,50 @@ static_assert(int(RequestMode::Same_orig
               int(RequestMode::Cors_with_forced_preflight) == 3 &&
               int(RequestMode::EndGuard_) == 4,
               "RequestMode values are as expected");
 static_assert(int(RequestCredentials::Omit) == 0 &&
               int(RequestCredentials::Same_origin) == 1 &&
               int(RequestCredentials::Include) == 2 &&
               int(RequestCredentials::EndGuard_) == 3,
               "RequestCredentials values are as expected");
+static_assert(int(RequestContext::Audio) == 0 &&
+              int(RequestContext::Beacon) == 1 &&
+              int(RequestContext::Cspreport) == 2 &&
+              int(RequestContext::Download) == 3 &&
+              int(RequestContext::Embed) == 4 &&
+              int(RequestContext::Eventsource) == 5 &&
+              int(RequestContext::Favicon) == 6 &&
+              int(RequestContext::Fetch) == 7 &&
+              int(RequestContext::Font) == 8 &&
+              int(RequestContext::Form) == 9 &&
+              int(RequestContext::Frame) == 10 &&
+              int(RequestContext::Hyperlink) == 11 &&
+              int(RequestContext::Iframe) == 12 &&
+              int(RequestContext::Image) == 13 &&
+              int(RequestContext::Imageset) == 14 &&
+              int(RequestContext::Import) == 15 &&
+              int(RequestContext::Internal) == 16 &&
+              int(RequestContext::Location) == 17 &&
+              int(RequestContext::Manifest) == 18 &&
+              int(RequestContext::Object) == 19 &&
+              int(RequestContext::Ping) == 20 &&
+              int(RequestContext::Plugin) == 21 &&
+              int(RequestContext::Prefetch) == 22 &&
+              int(RequestContext::Script) == 23 &&
+              int(RequestContext::Serviceworker) == 24 &&
+              int(RequestContext::Sharedworker) == 25 &&
+              int(RequestContext::Subresource) == 26 &&
+              int(RequestContext::Style) == 27 &&
+              int(RequestContext::Track) == 28 &&
+              int(RequestContext::Video) == 29 &&
+              int(RequestContext::Worker) == 30 &&
+              int(RequestContext::Xmlhttprequest) == 31 &&
+              int(RequestContext::Xslt) == 32,
+              "RequestContext values are as expected");
 static_assert(int(RequestCache::Default) == 0 &&
               int(RequestCache::No_store) == 1 &&
               int(RequestCache::Reload) == 2 &&
               int(RequestCache::No_cache) == 3 &&
               int(RequestCache::Force_cache) == 4 &&
               int(RequestCache::Only_if_cached) == 5 &&
               int(RequestCache::EndGuard_) == 6,
               "RequestCache values are as expected");
@@ -71,16 +106,45 @@ static_assert(int(ResponseType::Basic) =
 // Namespace enum in a way that may be incompatible with the existing data
 // stored in the DOM Cache.  You would need to update the Cache database schema
 // accordingly and adjust the failing static_assert.
 static_assert(DEFAULT_NAMESPACE == 0 &&
               CHROME_ONLY_NAMESPACE == 1 &&
               NUMBER_OF_NAMESPACES == 2,
               "Namespace values are as expected");
 
+// If the static_asserts below fails, it means that you have changed the
+// nsContentPolicy enum in a way that may be incompatible with the existing data
+// stored in the DOM Cache.  You would need to update the Cache database schema
+// accordingly and adjust the failing static_assert.
+static_assert(nsIContentPolicy::TYPE_INVALID == 0 &&
+              nsIContentPolicy::TYPE_OTHER == 1 &&
+              nsIContentPolicy::TYPE_SCRIPT == 2 &&
+              nsIContentPolicy::TYPE_IMAGE == 3 &&
+              nsIContentPolicy::TYPE_STYLESHEET == 4 &&
+              nsIContentPolicy::TYPE_OBJECT == 5 &&
+              nsIContentPolicy::TYPE_DOCUMENT == 6 &&
+              nsIContentPolicy::TYPE_SUBDOCUMENT == 7 &&
+              nsIContentPolicy::TYPE_REFRESH == 8 &&
+              nsIContentPolicy::TYPE_XBL == 9 &&
+              nsIContentPolicy::TYPE_PING == 10 &&
+              nsIContentPolicy::TYPE_XMLHTTPREQUEST == 11 &&
+              nsIContentPolicy::TYPE_DATAREQUEST == 11 &&
+              nsIContentPolicy::TYPE_OBJECT_SUBREQUEST == 12 &&
+              nsIContentPolicy::TYPE_DTD == 13 &&
+              nsIContentPolicy::TYPE_FONT == 14 &&
+              nsIContentPolicy::TYPE_MEDIA == 15 &&
+              nsIContentPolicy::TYPE_WEBSOCKET == 16 &&
+              nsIContentPolicy::TYPE_CSP_REPORT == 17 &&
+              nsIContentPolicy::TYPE_XSLT == 18 &&
+              nsIContentPolicy::TYPE_BEACON == 19 &&
+              nsIContentPolicy::TYPE_FETCH == 20 &&
+              nsIContentPolicy::TYPE_IMAGESET == 21,
+              "nsContentPolicytType values are as expected");
+
 using mozilla::void_t;
 
 // static
 nsresult
 DBSchema::CreateSchema(mozIStorageConnection* aConn)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConn);
@@ -134,16 +198,18 @@ DBSchema::CreateSchema(mozIStorageConnec
         "id INTEGER NOT NULL PRIMARY KEY, "
         "request_method TEXT NOT NULL, "
         "request_url TEXT NOT NULL, "
         "request_url_no_query TEXT NOT NULL, "
         "request_referrer TEXT NOT NULL, "
         "request_headers_guard INTEGER NOT NULL, "
         "request_mode INTEGER NOT NULL, "
         "request_credentials INTEGER NOT NULL, "
+        "request_contentpolicytype INTEGER NOT NULL, "
+        "request_context INTEGER NOT NULL, "
         "request_cache INTEGER NOT NULL, "
         "request_body_id TEXT NULL, "
         "response_type INTEGER NOT NULL, "
         "response_url TEXT NOT NULL, "
         "response_status INTEGER NOT NULL, "
         "response_status_text TEXT NOT NULL, "
         "response_headers_guard INTEGER NOT NULL, "
         "response_body_id TEXT NULL, "
@@ -1016,27 +1082,29 @@ DBSchema::InsertEntry(mozIStorageConnect
     "INSERT INTO entries ("
       "request_method, "
       "request_url, "
       "request_url_no_query, "
       "request_referrer, "
       "request_headers_guard, "
       "request_mode, "
       "request_credentials, "
+      "request_contentpolicytype, "
+      "request_context, "
       "request_cache, "
       "request_body_id, "
       "response_type, "
       "response_url, "
       "response_status, "
       "response_status_text, "
       "response_headers_guard, "
       "response_body_id, "
       "response_security_info, "
       "cache_id "
-    ") VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17)"
+    ") VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19)"
   ), getter_AddRefs(state));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindUTF8StringParameter(0, aRequest.method());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindStringParameter(1, aRequest.url());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
@@ -1054,47 +1122,55 @@ DBSchema::InsertEntry(mozIStorageConnect
   rv = state->BindInt32Parameter(5, static_cast<int32_t>(aRequest.mode()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindInt32Parameter(6,
     static_cast<int32_t>(aRequest.credentials()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindInt32Parameter(7,
+    static_cast<int32_t>(aRequest.contentPolicyType()));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(8,
+    static_cast<int32_t>(aRequest.context()));
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  rv = state->BindInt32Parameter(9,
     static_cast<int32_t>(aRequest.requestCache()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = BindId(state, 8, aRequestBodyId);
+  rv = BindId(state, 10, aRequestBodyId);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = state->BindInt32Parameter(9, static_cast<int32_t>(aResponse.type()));
+  rv = state->BindInt32Parameter(11, static_cast<int32_t>(aResponse.type()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = state->BindStringParameter(10, aResponse.url());
+  rv = state->BindStringParameter(12, aResponse.url());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = state->BindInt32Parameter(11, aResponse.status());
+  rv = state->BindInt32Parameter(13, aResponse.status());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = state->BindUTF8StringParameter(12, aResponse.statusText());
+  rv = state->BindUTF8StringParameter(14, aResponse.statusText());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = state->BindInt32Parameter(13,
+  rv = state->BindInt32Parameter(15,
     static_cast<int32_t>(aResponse.headersGuard()));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = BindId(state, 14, aResponseBodyId);
+  rv = BindId(state, 16, aResponseBodyId);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = state->BindBlobParameter(15, reinterpret_cast<const uint8_t*>
+  rv = state->BindBlobParameter(17, reinterpret_cast<const uint8_t*>
                                   (aResponse.securityInfo().get()),
                                 aResponse.securityInfo().Length());
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  rv = state->BindInt32Parameter(16, aCacheId);
+  rv = state->BindInt32Parameter(18, aCacheId);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->Execute();
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT last_insert_rowid()"
   ), getter_AddRefs(state));
@@ -1270,16 +1346,18 @@ DBSchema::ReadRequest(mozIStorageConnect
     "SELECT "
       "request_method, "
       "request_url, "
       "request_url_no_query, "
       "request_referrer, "
       "request_headers_guard, "
       "request_mode, "
       "request_credentials, "
+      "request_contentpolicytype, "
+      "request_context, "
       "request_cache, "
       "request_body_id "
     "FROM entries "
     "WHERE id=?1;"
   ), getter_AddRefs(state));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindInt32Parameter(0, aEntryId);
@@ -1313,29 +1391,41 @@ DBSchema::ReadRequest(mozIStorageConnect
   aSavedRequestOut->mValue.mode() = static_cast<RequestMode>(mode);
 
   int32_t credentials;
   rv = state->GetInt32(6, &credentials);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedRequestOut->mValue.credentials() =
     static_cast<RequestCredentials>(credentials);
 
+  int32_t requestContentPolicyType;
+  rv = state->GetInt32(7, &requestContentPolicyType);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  aSavedRequestOut->mValue.contentPolicyType() =
+    static_cast<nsContentPolicyType>(requestContentPolicyType);
+
+  int32_t requestContext;
+  rv = state->GetInt32(8, &requestContext);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  aSavedRequestOut->mValue.context() =
+    static_cast<RequestContext>(requestContext);
+
   int32_t requestCache;
-  rv = state->GetInt32(7, &requestCache);
+  rv = state->GetInt32(9, &requestCache);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedRequestOut->mValue.requestCache() =
     static_cast<RequestCache>(requestCache);
 
   bool nullBody = false;
-  rv = state->GetIsNull(8, &nullBody);
+  rv = state->GetIsNull(10, &nullBody);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   aSavedRequestOut->mHasBodyId = !nullBody;
 
   if (aSavedRequestOut->mHasBodyId) {
-    rv = ExtractId(state, 8, &aSavedRequestOut->mBodyId);
+    rv = ExtractId(state, 10, &aSavedRequestOut->mBodyId);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   }
 
   rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
     "SELECT "
       "name, "
       "value "
     "FROM request_headers "
--- a/dom/workers/test/serviceworkers/fetch/context/context_test.js
+++ b/dom/workers/test/serviceworkers/fetch/context/context_test.js
@@ -1,13 +1,14 @@
 self.addEventListener("fetch", function(event) {
   if (event.request.url.indexOf("index.html") >= 0 ||
       event.request.url.indexOf("register.html") >= 0 ||
       event.request.url.indexOf("unregister.html") >= 0 ||
       event.request.url.indexOf("ping.html") >= 0 ||
+      event.request.url.indexOf("xml.xml") >= 0 ||
       event.request.url.indexOf("csp-violate.sjs") >= 0) {
     // Handle pass-through requests
     event.respondWith(fetch(event.request));
   } else if (event.request.url.indexOf("fetch.txt") >= 0) {
     var body = event.request.context == "fetch" ?
                "so fetch" : "so unfetch";
     event.respondWith(new Response(body));
   } else if (event.request.url.indexOf("img.jpg") >= 0) {
@@ -49,16 +50,59 @@ self.addEventListener("fetch", function(
     // FIXME: Bug 1148044: This should be "frame".
     if (event.request.context == "iframe") {
       event.respondWith(fetch("context_test.js"));
     }
   } else if (event.request.url.indexOf("newwindow") >= 0) {
     respondToServiceWorker(event, "newwindow");
   } else if (event.request.url.indexOf("ping") >= 0) {
     respondToServiceWorker(event, "ping");
+  } else if (event.request.url.indexOf("plugin") >= 0) {
+    respondToServiceWorker(event, "plugin");
+  } else if (event.request.url.indexOf("script.js") >= 0) {
+    if (event.request.context == "script") {
+      event.respondWith(new Response(""));
+    }
+  } else if (event.request.url.indexOf("style.css") >= 0) {
+    respondToServiceWorker(event, "style");
+  } else if (event.request.url.indexOf("track") >= 0) {
+    respondToServiceWorker(event, "track");
+  } else if (event.request.url.indexOf("xhr") >= 0) {
+    if (event.request.context == "xmlhttprequest") {
+      event.respondWith(new Response(""));
+    }
+  } else if (event.request.url.indexOf("xslt") >= 0) {
+    respondToServiceWorker(event, "xslt");
+  } else if (event.request.url.indexOf("cache") >= 0) {
+    var cache;
+    var origContext = event.request.context;
+    event.respondWith(caches.open("cache")
+      .then(function(c) {
+        cache = c;
+        // Store the Request in the cache.
+        return cache.put(event.request, new Response("fake"));
+      }).then(function() {
+        // Read it back.
+        return cache.keys(event.request);
+      }).then(function(res) {
+        var req = res[0];
+        // Check to see if the context remained the same.
+        var success = req.context === origContext;
+        return clients.matchAll()
+               .then(function(clients) {
+                 // Report it back to the main page.
+                 clients.forEach(function(c) {
+                   c.postMessage({data: "cache", success: success});
+                 });
+      })}).then(function() {
+        // Cleanup.
+        return caches.delete("cache");
+      }).then(function() {
+        return new Response("ack");
+      }));
   }
   // Fail any request that we don't know about.
   try {
     event.respondWith(Promise.reject(event.request.url));
   } catch(e) {
     // Eat up the possible InvalidStateError exception that we may get if some
     // code above has called respondWith too.
   }
--- a/dom/workers/test/serviceworkers/fetch/context/index.html
+++ b/dom/workers/test/serviceworkers/fetch/context/index.html
@@ -1,10 +1,13 @@
 <!DOCTYPE html>
 <script>
+  var isAndroid = navigator.userAgent.contains("Android");
+  var isB2G = !isAndroid && /Mobile|Tablet/.test(navigator.userAgent);
+
   function ok(v, msg) {
     window.parent.postMessage({status: "ok", result: !!v, message: msg}, "*");
   }
 
   function is(a, b, msg) {
     ok(a === b, msg + ", expected '" + b + "', got '" + a + "'");
   }
 
@@ -180,18 +183,16 @@
       // Request has the correct context, otherwise the Promise will get
       // rejected and the test will fail.
       frame.onload = resolve;
       frame.onerror = reject;
     });
   }
 
   function testInternal() {
-    var isB2G = !navigator.userAgent.contains("Android") &&
-                /Mobile|Tablet/.test(navigator.userAgent);
     if (isB2G) {
       // We can't open new windows on b2g, so skip this part of the test there.
       return Promise.resolve();
     }
     return new Promise(function(resolve, reject) {
       // Test this with a new window opened through script.  There are of course
       // other possible ways of testing this too.
       var win = window.open("newwindow", "_blank", "width=100,height=100");
@@ -218,32 +219,164 @@
           is(e.data.context, "ping", "Expected the ping context on an anchor ping");
           navigator.serviceWorker.removeEventListener("message", onMessage);
           resolve();
         }
       }, false);
     });
   }
 
+  function testPlugin() {
+    var isMobile = /Mobile|Tablet/.test(navigator.userAgent);
+    if (isMobile) {
+      // We can't use plugins on mobile, so skip this part of the test there.
+      return Promise.resolve();
+    }
+    return new Promise(function(resolve, reject) {
+      var embed = document.createElement("embed");
+      embed.type = "application/x-test";
+      embed.setAttribute("posturl", "plugin");
+      embed.setAttribute("postmode", "stream");
+      embed.setAttribute("streammode", "normal");
+      embed.setAttribute("src", "fetch.txt");
+      document.documentElement.appendChild(embed);
+      navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+        if (e.data.data == "plugin") {
+          is(e.data.context, "plugin", "Expected the plugin context on a request coming from a plugin");
+          navigator.serviceWorker.removeEventListener("message", onMessage);
+          resolve();
+        }
+      }, false);
+    });
+  }
+
+  function testScript() {
+    return new Promise(function(resolve, reject) {
+      var script = document.createElement("script");
+      script.src = "script.js";
+      document.documentElement.appendChild(script);
+      // The service worker will respond with an existing script only if the
+      // Request has the correct context, otherwise the Promise will get
+      // rejected and the test will fail.
+      script.onload = resolve;
+      script.onerror = reject;
+    });
+  }
+
+  function testStyle() {
+    return new Promise(function(resolve, reject) {
+      var link = document.createElement("link");
+      link.rel = "stylesheet";
+      link.href = "style.css";
+      document.documentElement.appendChild(link);
+      navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+        if (e.data.data == "style") {
+          is(e.data.context, "style", "Expected the style context on a request coming from a stylesheet");
+          navigator.serviceWorker.removeEventListener("message", onMessage);
+          resolve();
+        }
+      }, false);
+    });
+  }
+
+  function testTrack() {
+    return new Promise(function(resolve, reject) {
+      var video = document.createElement("video");
+      var track = document.createElement("track");
+      track.src = "track";
+      video.appendChild(track);
+      document.documentElement.appendChild(video);
+      navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+        if (e.data.data == "track") {
+          // FIXME: Bug 1147668: This should be "track".
+          is(e.data.context, "audio", "Expected the audio context on a request coming from a track");
+          navigator.serviceWorker.removeEventListener("message", onMessage);
+          resolve();
+        }
+      }, false);
+    });
+  }
+
+  function testXHR() {
+    return new Promise(function(resolve, reject) {
+      var xhr = new XMLHttpRequest();
+      xhr.open("get", "xhr", true);
+      xhr.send();
+      // The service worker will respond with an existing resource only if the
+      // Request has the correct context, otherwise the Promise will get
+      // rejected and the test will fail.
+      xhr.onload = resolve;
+      xhr.onerror = reject;
+    });
+  }
+
+  function testXSLT() {
+    return new Promise(function(resolve, reject) {
+      var iframe = document.createElement("iframe");
+      iframe.src = "xml.xml";
+      document.documentElement.appendChild(iframe);
+      navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+        if (e.data.data == "xslt") {
+          is(e.data.context, "xslt", "Expected the xslt context on an XSLT stylesheet");
+          navigator.serviceWorker.removeEventListener("message", onMessage);
+          resolve();
+        }
+      }, false);
+    });
+  }
+
+  function testCache() {
+    if (isAndroid) {
+      // FIXME: Re-enable this test on Android once bug 1148818 gets fixed.
+      return Promise.resolve();
+    }
+    return new Promise(function(resolve, reject) {
+      // Issue an XHR that will be intercepted by the SW in order to start off
+      // the test with a RequestContext value that is not the default ("fetch").
+      // This needs to run inside a fetch event handler because synthesized
+      // RequestContext objects can only have the "fetch" context, and we'd
+      // prefer to test the more general case of some other RequestContext value.
+      var xhr = new XMLHttpRequest();
+      xhr.open("get", "cache", true);
+      xhr.send();
+      navigator.serviceWorker.addEventListener("message", function onMessage(e) {
+        if (e.data.data == "cache") {
+          ok(e.data.success, "The RequestContext can be persisted in the cache.");
+          navigator.serviceWorker.removeEventListener("message", onMessage);
+          resolve();
+        }
+      }, false);
+    });
+  }
+
   Promise.all([
     testFetch(),
     testImage(),
     testImageSrcSet(),
     testPicture(),
     testAudio(),
     testVideo(),
     testBeacon(),
     testCSPReport(),
     testEmbed(),
     testObject(),
     testFont(),
     testIFrame(),
     testFrame(),
     testInternal(),
     testPing(),
+    testPlugin(),
+    testScript(),
+    testStyle(),
+    testTrack(),
+    testXHR(),
+    testXSLT(),
+
+    // Also, test to see if the type of the request can be persisted in the database.
+    testCache(),
   ])
   .then(function() {
     finish();
   }, function(e) {
     ok(false, "A promise was rejected: " + e);
     finish();
   });
 </script>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch/context/xml.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="xslt"?>
+<root/>
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -30,16 +30,17 @@ support-files =
   fetch/context/register.html
   fetch/context/unregister.html
   fetch/context/context_test.js
   fetch/context/realimg.jpg
   fetch/context/realaudio.ogg
   fetch/context/beacon.sjs
   fetch/context/csp-violate.sjs
   fetch/context/ping.html
+  fetch/context/xml.xml
   fetch/https/index.html
   fetch/https/register.html
   fetch/https/unregister.html
   fetch/https/https_test.js
   fetch/https/clonedresponse/index.html
   fetch/https/clonedresponse/register.html
   fetch/https/clonedresponse/unregister.html
   fetch/https/clonedresponse/https_test.js
--- a/dom/workers/test/serviceworkers/test_request_context.html
+++ b/dom/workers/test/serviceworkers/test_request_context.html
@@ -12,16 +12,48 @@
 <body>
 <p id="display"></p>
 <div id="content">
 <iframe></iframe>
 </div>
 <pre id="test"></pre>
 <script class="testbody" type="text/javascript">
 
+  // Copied from /dom/plugins/test/mochitest/utils.js
+  function getTestPlugin(pluginName) {
+    var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
+                                   .getService(SpecialPowers.Ci.nsIPluginHost);
+    var tags = ph.getPluginTags();
+    var name = pluginName || "Test Plug-in";
+    for (var tag of tags) {
+      if (tag.name == name) {
+        return tag;
+      }
+    }
+
+    ok(false, "Could not find plugin tag with plugin name '" + name + "'");
+    return null;
+  }
+  function setTestPluginEnabledState(newEnabledState, pluginName) {
+    var oldEnabledState = SpecialPowers.setTestPluginEnabledState(newEnabledState, pluginName);
+    if (!oldEnabledState) {
+      return;
+    }
+    var plugin = getTestPlugin(pluginName);
+    while (plugin.enabledState != newEnabledState) {
+      // Run a nested event loop to wait for the preference change to
+      // propagate to the child. Yuck!
+      SpecialPowers.Services.tm.currentThread.processNextEvent(true);
+    }
+    SimpleTest.registerCleanupFunction(function() {
+      SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
+    });
+  }
+  setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+
   var iframe;
   function runTest() {
     iframe = document.querySelector("iframe");
     iframe.src = "/tests/dom/workers/test/serviceworkers/fetch/context/register.html";
     window.onmessage = function(e) {
       if (e.data.status == "ok") {
         ok(e.data.result, e.data.message);
       } else if (e.data.status == "registrationdone") {
@@ -37,16 +69,17 @@
   }
 
   SimpleTest.waitForExplicitFinish();
   onload = function() {
     SpecialPowers.pushPrefEnv({"set": [
       ["beacon.enabled", true],
       ["browser.send_pings", true],
       ["browser.send_pings.max_per_link", -1],
+      ["dom.caches.enabled", true],
       ["dom.image.picture.enabled", true],
       ["dom.image.srcset.enabled", true],
       ["dom.serviceWorkers.exemptFromPerDomainMax", true],
       ["dom.serviceWorkers.enabled", true],
       ["dom.serviceWorkers.testing.enabled", true],
     ]}, runTest);
   };
 </script>