service worker: Don't control a subframe of an insecure context

We must check isSecureContext when creating the network provider to
adhere to https://w3c.github.io/webappsec/specs/powerfulfeatures/#settings-privileged.

We already did this for getRegistration(), register(), unregister() but must
also do this when deciding whether to control an in-scope document.

BUG=607543
CQ_INCLUDE_TRYBOTS=tryserver.chromium.linux:linux_site_isolation

Review-Url: https://codereview.chromium.org/2009453002
Cr-Commit-Position: refs/heads/master@{#398229}
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index a554a89..15dd1d7 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -104,6 +104,7 @@
 #include "chrome/common/pepper_permission_util.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/render_messages.h"
+#include "chrome/common/secure_origin_whitelist.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/installer/util/google_update_settings.h"
@@ -2531,6 +2532,11 @@
   }
 }
 
+void ChromeContentBrowserClient::GetSchemesBypassingSecureContextCheckWhitelist(
+    std::set<std::string>* schemes) {
+  return ::GetSchemesBypassingSecureContextCheckWhitelist(schemes);
+}
+
 void ChromeContentBrowserClient::GetURLRequestAutoMountHandlers(
     std::vector<storage::URLRequestAutoMountHandler>* handlers) {
   for (size_t i = 0; i < extra_parts_.size(); ++i)
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index 02e4a33d..b6dbc6258 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -232,6 +232,8 @@
       content::WebContents* web_contents) override;
   void GetAdditionalAllowedSchemesForFileSystem(
       std::vector<std::string>* additional_schemes) override;
+  void GetSchemesBypassingSecureContextCheckWhitelist(
+      std::set<std::string>* schemes) override;
   void GetURLRequestAutoMountHandlers(
       std::vector<storage::URLRequestAutoMountHandler>* handlers) override;
   void GetAdditionalFileSystemBackends(
diff --git a/chrome/browser/extensions/service_worker_apitest.cc b/chrome/browser/extensions/service_worker_apitest.cc
index e434593..2c9e0ee 100644
--- a/chrome/browser/extensions/service_worker_apitest.cc
+++ b/chrome/browser/extensions/service_worker_apitest.cc
@@ -25,6 +25,7 @@
 #include "content/public/browser/permission_type.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_switches.h"
+#include "content/public/common/origin_util.h"
 #include "content/public/common/page_type.h"
 #include "content/public/test/background_sync_test_util.h"
 #include "content/public/test/browser_test_utils.h"
@@ -33,6 +34,7 @@
 #include "extensions/browser/process_manager.h"
 #include "extensions/test/background_page_watcher.h"
 #include "extensions/test/extension_test_message_listener.h"
+#include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 
 namespace extensions {
@@ -634,9 +636,24 @@
       kFlagNone);
   ASSERT_TRUE(extension);
   ASSERT_TRUE(StartEmbeddedTestServer());
-  GURL page_url = embedded_test_server()->GetURL(
-      "/extensions/api_test/service_worker/web_accessible_resources/"
-      "webpage.html");
+
+  // Service workers can only control secure contexts
+  // (https://w3c.github.io/webappsec-secure-contexts/). For documents, this
+  // typically means the document must have a secure origin AND all its ancestor
+  // frames must have documents with secure origins.  However, extension pages
+  // are considered secure, even if they have an ancestor document that is an
+  // insecure context (see GetSchemesBypassingSecureContextCheckWhitelist). So
+  // extension service workers must be able to control an extension page
+  // embedded in an insecure context. To test this, set up an insecure
+  // (non-localhost, non-https) URL for the web page. This page will create
+  // iframes that load extension pages that must be controllable by service
+  // worker.
+  host_resolver()->AddRule("a.com", "127.0.0.1");
+  GURL page_url =
+      embedded_test_server()->GetURL("a.com",
+                                     "/extensions/api_test/service_worker/"
+                                     "web_accessible_resources/webpage.html");
+  EXPECT_FALSE(content::IsOriginSecure(page_url));
 
   content::WebContents* web_contents = AddTab(browser(), page_url);
   std::string result;
diff --git a/content/browser/service_worker/service_worker_browsertest.cc b/content/browser/service_worker/service_worker_browsertest.cc
index 60c6b2b..21cdf65 100644
--- a/content/browser/service_worker/service_worker_browsertest.cc
+++ b/content/browser/service_worker/service_worker_browsertest.cc
@@ -528,11 +528,12 @@
   void AddControlleeOnIOThread() {
     ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO));
     std::unique_ptr<ServiceWorkerProviderHost> host(
-        new ServiceWorkerProviderHost(33 /* dummy render process id */,
-                                      MSG_ROUTING_NONE /* render_frame_id */,
-                                      1 /* dummy provider_id */,
-                                      SERVICE_WORKER_PROVIDER_FOR_WINDOW,
-                                      wrapper()->context()->AsWeakPtr(), NULL));
+        new ServiceWorkerProviderHost(
+            33 /* dummy render process id */,
+            MSG_ROUTING_NONE /* render_frame_id */, 1 /* dummy provider_id */,
+            SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+            ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+            wrapper()->context()->AsWeakPtr(), NULL));
     host->SetDocumentUrl(
         embedded_test_server()->GetURL("/service_worker/host"));
     host->AssociateRegistration(registration_.get(),
diff --git a/content/browser/service_worker/service_worker_context_core.cc b/content/browser/service_worker/service_worker_context_core.cc
index 704f821..5afed8a 100644
--- a/content/browser/service_worker/service_worker_context_core.cc
+++ b/content/browser/service_worker/service_worker_context_core.cc
@@ -631,13 +631,13 @@
                                                   int provider_id) {
   ProviderMap* map = GetProviderMapForProcess(process_id);
   ServiceWorkerProviderHost* transferee = map->Lookup(provider_id);
-  ServiceWorkerProviderHost* replacement =
-      new ServiceWorkerProviderHost(process_id,
-                                    transferee->frame_id(),
-                                    provider_id,
-                                    transferee->provider_type(),
-                                    AsWeakPtr(),
-                                    transferee->dispatcher_host());
+  ServiceWorkerProviderHost* replacement = new ServiceWorkerProviderHost(
+      process_id, transferee->frame_id(), provider_id,
+      transferee->provider_type(),
+      transferee->is_parent_frame_secure()
+          ? ServiceWorkerProviderHost::FrameSecurityLevel::SECURE
+          : ServiceWorkerProviderHost::FrameSecurityLevel::INSECURE,
+      AsWeakPtr(), transferee->dispatcher_host());
   map->Replace(provider_id, replacement);
   transferee->PrepareForCrossSiteTransfer();
   return base::WrapUnique(transferee);
diff --git a/content/browser/service_worker/service_worker_context_request_handler_unittest.cc b/content/browser/service_worker/service_worker_context_request_handler_unittest.cc
index 22296bc..dbc170c 100644
--- a/content/browser/service_worker/service_worker_context_request_handler_unittest.cc
+++ b/content/browser/service_worker/service_worker_context_request_handler_unittest.cc
@@ -49,11 +49,12 @@
 
     // An empty host.
     std::unique_ptr<ServiceWorkerProviderHost> host(
-        new ServiceWorkerProviderHost(helper_->mock_render_process_id(),
-                                      MSG_ROUTING_NONE /* render_frame_id */,
-                                      1 /* provider_id */,
-                                      SERVICE_WORKER_PROVIDER_FOR_WINDOW,
-                                      context()->AsWeakPtr(), nullptr));
+        new ServiceWorkerProviderHost(
+            helper_->mock_render_process_id(),
+            MSG_ROUTING_NONE /* render_frame_id */, 1 /* provider_id */,
+            SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+            ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+            context()->AsWeakPtr(), nullptr));
     provider_host_ = host->AsWeakPtr();
     context()->AddProviderHost(std::move(host));
 
diff --git a/content/browser/service_worker/service_worker_context_unittest.cc b/content/browser/service_worker/service_worker_context_unittest.cc
index d4de332..05409c8 100644
--- a/content/browser/service_worker/service_worker_context_unittest.cc
+++ b/content/browser/service_worker/service_worker_context_unittest.cc
@@ -532,25 +532,33 @@
   // Host1 (provider_id=1): process_id=1, origin1.
   ServiceWorkerProviderHost* host1(new ServiceWorkerProviderHost(
       kRenderProcessId1, MSG_ROUTING_NONE, provider_id++,
-      SERVICE_WORKER_PROVIDER_FOR_WINDOW, context()->AsWeakPtr(), nullptr));
+      SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+      ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+      context()->AsWeakPtr(), nullptr));
   host1->SetDocumentUrl(kOrigin1);
 
   // Host2 (provider_id=2): process_id=2, origin2.
   ServiceWorkerProviderHost* host2(new ServiceWorkerProviderHost(
       kRenderProcessId2, MSG_ROUTING_NONE, provider_id++,
-      SERVICE_WORKER_PROVIDER_FOR_WINDOW, context()->AsWeakPtr(), nullptr));
+      SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+      ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+      context()->AsWeakPtr(), nullptr));
   host2->SetDocumentUrl(kOrigin2);
 
   // Host3 (provider_id=3): process_id=2, origin1.
   ServiceWorkerProviderHost* host3(new ServiceWorkerProviderHost(
       kRenderProcessId2, MSG_ROUTING_NONE, provider_id++,
-      SERVICE_WORKER_PROVIDER_FOR_WINDOW, context()->AsWeakPtr(), nullptr));
+      SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+      ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+      context()->AsWeakPtr(), nullptr));
   host3->SetDocumentUrl(kOrigin1);
 
   // Host4 (provider_id=4): process_id=2, origin2, for ServiceWorker.
   ServiceWorkerProviderHost* host4(new ServiceWorkerProviderHost(
       kRenderProcessId2, MSG_ROUTING_NONE, provider_id++,
-      SERVICE_WORKER_PROVIDER_FOR_CONTROLLER, context()->AsWeakPtr(), nullptr));
+      SERVICE_WORKER_PROVIDER_FOR_CONTROLLER,
+      ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+      context()->AsWeakPtr(), nullptr));
   host4->SetDocumentUrl(kOrigin2);
 
   context()->AddProviderHost(base::WrapUnique(host1));
diff --git a/content/browser/service_worker/service_worker_controllee_request_handler.cc b/content/browser/service_worker/service_worker_controllee_request_handler.cc
index 74b8e80..c6652dc 100644
--- a/content/browser/service_worker/service_worker_controllee_request_handler.cc
+++ b/content/browser/service_worker/service_worker_controllee_request_handler.cc
@@ -5,6 +5,7 @@
 #include "content/browser/service_worker/service_worker_controllee_request_handler.h"
 
 #include <memory>
+#include <set>
 #include <string>
 
 #include "base/trace_event/trace_event.h"
@@ -213,6 +214,17 @@
     return;
   }
 
+  if (!provider_host_->IsContextSecureForServiceWorker()) {
+    // TODO(falken): Figure out a way to surface in the page's DevTools
+    // console that the service worker was blocked for security.
+    job_->FallbackToNetwork();
+    TRACE_EVENT_ASYNC_END1(
+        "ServiceWorker",
+        "ServiceWorkerControlleeRequestHandler::PrepareForMainResource",
+        job_.get(), "Info", "Insecure context");
+    return;
+  }
+
   if (need_to_update) {
     force_update_started_ = true;
     context_->UpdateServiceWorker(
diff --git a/content/browser/service_worker/service_worker_controllee_request_handler_unittest.cc b/content/browser/service_worker/service_worker_controllee_request_handler_unittest.cc
index c31d4ba..918627f 100644
--- a/content/browser/service_worker/service_worker_controllee_request_handler_unittest.cc
+++ b/content/browser/service_worker/service_worker_controllee_request_handler_unittest.cc
@@ -105,8 +105,8 @@
     helper_.reset(helper);
 
     // A new unstored registration/version.
-    scope_ = GURL("http://host/scope/");
-    script_url_ = GURL("http://host/script.js");
+    scope_ = GURL("https://host/scope/");
+    script_url_ = GURL("https://host/script.js");
     registration_ = new ServiceWorkerRegistration(
         scope_, 1L, context()->AsWeakPtr());
     version_ = new ServiceWorkerVersion(
@@ -119,10 +119,11 @@
 
     // An empty host.
     std::unique_ptr<ServiceWorkerProviderHost> host(
-        new ServiceWorkerProviderHost(helper_->mock_render_process_id(),
-                                      MSG_ROUTING_NONE, kMockProviderId,
-                                      SERVICE_WORKER_PROVIDER_FOR_WINDOW,
-                                      context()->AsWeakPtr(), NULL));
+        new ServiceWorkerProviderHost(
+            helper_->mock_render_process_id(), MSG_ROUTING_NONE,
+            kMockProviderId, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+            ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+            context()->AsWeakPtr(), NULL));
     provider_host_ = host->AsWeakPtr();
     context()->AddProviderHost(std::move(host));
 
@@ -180,7 +181,7 @@
 
   // Conduct a main resource load.
   ServiceWorkerRequestTestResources test_resources(
-      this, GURL("http://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
+      this, GURL("https://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
   ServiceWorkerURLRequestJob* sw_job = test_resources.MaybeCreateJob();
 
   EXPECT_FALSE(sw_job->ShouldFallbackToNetwork());
@@ -209,7 +210,7 @@
 
   // Conduct a main resource load.
   ServiceWorkerRequestTestResources test_resources(
-      this, GURL("http://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
+      this, GURL("https://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
   ServiceWorkerURLRequestJob* sw_job = test_resources.MaybeCreateJob();
 
   EXPECT_FALSE(sw_job->ShouldFallbackToNetwork());
@@ -239,7 +240,7 @@
 
   // Conduct a main resource load.
   ServiceWorkerRequestTestResources test_resources(
-      this, GURL("http://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
+      this, GURL("https://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
   ServiceWorkerURLRequestJob* job = test_resources.MaybeCreateJob();
 
   base::RunLoop().RunUntilIdle();
@@ -271,7 +272,7 @@
 
   // Conduct a main resource load.
   ServiceWorkerRequestTestResources test_resources(
-      this, GURL("http://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
+      this, GURL("https://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
   ServiceWorkerURLRequestJob* sw_job = test_resources.MaybeCreateJob();
 
   EXPECT_FALSE(sw_job->ShouldFallbackToNetwork());
@@ -297,7 +298,7 @@
   base::RunLoop().RunUntilIdle();
 
   ServiceWorkerRequestTestResources main_test_resources(
-      this, GURL("http://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
+      this, GURL("https://host/scope/doc"), RESOURCE_TYPE_MAIN_FRAME);
   ServiceWorkerURLRequestJob* main_job = main_test_resources.MaybeCreateJob();
 
   EXPECT_FALSE(main_job->ShouldFallbackToNetwork());
@@ -312,7 +313,7 @@
   EXPECT_EQ(version_, provider_host_->controlling_version());
 
   ServiceWorkerRequestTestResources sub_test_resources(
-      this, GURL("http://host/scope/doc/subresource"), RESOURCE_TYPE_IMAGE);
+      this, GURL("https://host/scope/doc/subresource"), RESOURCE_TYPE_IMAGE);
   ServiceWorkerURLRequestJob* sub_job = sub_test_resources.MaybeCreateJob();
 
   // This job shouldn't be created because this worker doesn't have fetch
diff --git a/content/browser/service_worker/service_worker_dispatcher_host.cc b/content/browser/service_worker/service_worker_dispatcher_host.cc
index 12a1dfc..de52af6 100644
--- a/content/browser/service_worker/service_worker_dispatcher_host.cc
+++ b/content/browser/service_worker/service_worker_dispatcher_host.cc
@@ -746,7 +746,8 @@
 void ServiceWorkerDispatcherHost::OnProviderCreated(
     int provider_id,
     int route_id,
-    ServiceWorkerProviderType provider_type) {
+    ServiceWorkerProviderType provider_type,
+    bool is_parent_frame_secure) {
   // TODO(pkasting): Remove ScopedTracker below once crbug.com/477117 is fixed.
   tracked_objects::ScopedTracker tracking_profile(
       FROM_HERE_WITH_EXPLICIT_FUNCTION(
@@ -768,8 +769,10 @@
     // Retrieve the provider host previously created for navigation requests.
     ServiceWorkerNavigationHandleCore* navigation_handle_core =
         GetContext()->GetNavigationHandleCore(provider_id);
-    if (navigation_handle_core != nullptr)
+    if (navigation_handle_core != nullptr) {
       provider_host = navigation_handle_core->RetrievePreCreatedHost();
+      provider_host->set_parent_frame_secure(is_parent_frame_secure);
+    }
 
     // If no host is found, the navigation has been cancelled in the meantime.
     // Just return as the navigation will be stopped in the renderer as well.
@@ -784,10 +787,14 @@
           this, bad_message::SWDH_PROVIDER_CREATED_NO_HOST);
       return;
     }
+    ServiceWorkerProviderHost::FrameSecurityLevel parent_frame_security_level =
+        is_parent_frame_secure
+            ? ServiceWorkerProviderHost::FrameSecurityLevel::SECURE
+            : ServiceWorkerProviderHost::FrameSecurityLevel::INSECURE;
     provider_host = std::unique_ptr<ServiceWorkerProviderHost>(
-        new ServiceWorkerProviderHost(render_process_id_, route_id, provider_id,
-                                      provider_type, GetContext()->AsWeakPtr(),
-                                      this));
+        new ServiceWorkerProviderHost(
+            render_process_id_, route_id, provider_id, provider_type,
+            parent_frame_security_level, GetContext()->AsWeakPtr(), this));
   }
   GetContext()->AddProviderHost(std::move(provider_host));
 }
diff --git a/content/browser/service_worker/service_worker_dispatcher_host.h b/content/browser/service_worker/service_worker_dispatcher_host.h
index 0907ec9..95330b4 100644
--- a/content/browser/service_worker/service_worker_dispatcher_host.h
+++ b/content/browser/service_worker/service_worker_dispatcher_host.h
@@ -116,7 +116,8 @@
                                  int provider_id);
   void OnProviderCreated(int provider_id,
                          int route_id,
-                         ServiceWorkerProviderType provider_type);
+                         ServiceWorkerProviderType provider_type,
+                         bool is_parent_frame_secure);
   void OnProviderDestroyed(int provider_id);
   void OnSetHostedVersionId(int provider_id, int64_t version_id);
   void OnWorkerReadyForInspection(int embedded_worker_id);
diff --git a/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc b/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc
index 376ffa2..3aec7f1 100644
--- a/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc
+++ b/content/browser/service_worker/service_worker_dispatcher_host_unittest.cc
@@ -150,7 +150,8 @@
                            const GURL& pattern) {
     const int64_t kProviderId = 99;
     dispatcher_host_->OnMessageReceived(ServiceWorkerHostMsg_ProviderCreated(
-        kProviderId, MSG_ROUTING_NONE, type));
+        kProviderId, MSG_ROUTING_NONE, type,
+        true /* is_parent_frame_secure */));
     helper_->SimulateAddProcessToPattern(pattern,
                                          helper_->mock_render_process_id());
     provider_host_ = context()->GetProviderHost(
@@ -234,8 +235,9 @@
   ServiceWorkerProviderHost* CreateServiceWorkerProviderHost(int provider_id) {
     return new ServiceWorkerProviderHost(
         helper_->mock_render_process_id(), kRenderFrameId, provider_id,
-        SERVICE_WORKER_PROVIDER_FOR_WINDOW, context()->AsWeakPtr(),
-        dispatcher_host_.get());
+        SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+        ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+        context()->AsWeakPtr(), dispatcher_host_.get());
   }
 
   TestBrowserThreadBundle browser_thread_bundle_;
@@ -496,12 +498,14 @@
   int process_id = helper_->mock_render_process_id();
 
   dispatcher_host_->OnMessageReceived(ServiceWorkerHostMsg_ProviderCreated(
-      kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW));
+      kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+      true /* is_parent_frame_secure */));
   EXPECT_TRUE(context()->GetProviderHost(process_id, kProviderId));
 
   // Two with the same ID should be seen as a bad message.
   dispatcher_host_->OnMessageReceived(ServiceWorkerHostMsg_ProviderCreated(
-      kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW));
+      kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+      true /* is_parent_frame_secure */));
   EXPECT_EQ(1, dispatcher_host_->bad_messages_received_count_);
 
   dispatcher_host_->OnMessageReceived(
@@ -516,7 +520,8 @@
   // Deletion of the dispatcher_host should cause providers for that
   // process to get deleted as well.
   dispatcher_host_->OnMessageReceived(ServiceWorkerHostMsg_ProviderCreated(
-      kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW));
+      kProviderId, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+      true /* is_parent_frame_secure */));
   EXPECT_TRUE(context()->GetProviderHost(process_id, kProviderId));
   EXPECT_TRUE(dispatcher_host_->HasOneRef());
   dispatcher_host_ = NULL;
@@ -652,7 +657,8 @@
   // the old dispatcher cleaned up the old provider host, the new one won't
   // complain.
   new_dispatcher_host->OnMessageReceived(ServiceWorkerHostMsg_ProviderCreated(
-      provider_id, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW));
+      provider_id, MSG_ROUTING_NONE, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+      true /* is_parent_frame_secure */));
   EXPECT_EQ(0, new_dispatcher_host->bad_messages_received_count_);
 }
 
diff --git a/content/browser/service_worker/service_worker_handle_unittest.cc b/content/browser/service_worker/service_worker_handle_unittest.cc
index 1483da4..d5a2109 100644
--- a/content/browser/service_worker/service_worker_handle_unittest.cc
+++ b/content/browser/service_worker/service_worker_handle_unittest.cc
@@ -107,8 +107,9 @@
 
     provider_host_.reset(new ServiceWorkerProviderHost(
         helper_->mock_render_process_id(), kRenderFrameId, 1,
-        SERVICE_WORKER_PROVIDER_FOR_WINDOW, helper_->context()->AsWeakPtr(),
-        dispatcher_host_.get()));
+        SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+        ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+        helper_->context()->AsWeakPtr(), dispatcher_host_.get()));
 
     helper_->SimulateAddProcessToPattern(pattern,
                                          helper_->mock_render_process_id());
diff --git a/content/browser/service_worker/service_worker_job_unittest.cc b/content/browser/service_worker/service_worker_job_unittest.cc
index 985da5f..05bb765 100644
--- a/content/browser/service_worker/service_worker_job_unittest.cc
+++ b/content/browser/service_worker/service_worker_job_unittest.cc
@@ -185,11 +185,12 @@
 std::unique_ptr<ServiceWorkerProviderHost>
 ServiceWorkerJobTest::CreateControllee() {
   return std::unique_ptr<ServiceWorkerProviderHost>(
-      new ServiceWorkerProviderHost(33 /* dummy render_process id */,
-                                    MSG_ROUTING_NONE /* render_frame_id */,
-                                    1 /* dummy provider_id */,
-                                    SERVICE_WORKER_PROVIDER_FOR_WINDOW,
-                                    helper_->context()->AsWeakPtr(), NULL));
+      new ServiceWorkerProviderHost(
+          33 /* dummy render_process id */,
+          MSG_ROUTING_NONE /* render_frame_id */, 1 /* dummy provider_id */,
+          SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+          ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+          helper_->context()->AsWeakPtr(), NULL));
 }
 
 TEST_F(ServiceWorkerJobTest, SameDocumentSameRegistration) {
diff --git a/content/browser/service_worker/service_worker_provider_host.cc b/content/browser/service_worker/service_worker_provider_host.cc
index 21e0ddf..97caabd 100644
--- a/content/browser/service_worker/service_worker_provider_host.cc
+++ b/content/browser/service_worker/service_worker_provider_host.cc
@@ -21,8 +21,11 @@
 #include "content/common/service_worker/service_worker_messages.h"
 #include "content/common/service_worker/service_worker_types.h"
 #include "content/common/service_worker/service_worker_utils.h"
+#include "content/public/browser/content_browser_client.h"
 #include "content/public/common/browser_side_navigation_policy.h"
 #include "content/public/common/child_process_host.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/origin_util.h"
 
 namespace content {
 
@@ -54,7 +57,8 @@
   return std::unique_ptr<ServiceWorkerProviderHost>(
       new ServiceWorkerProviderHost(
           ChildProcessHost::kInvalidUniqueID, MSG_ROUTING_NONE, provider_id,
-          SERVICE_WORKER_PROVIDER_FOR_WINDOW, context, nullptr));
+          SERVICE_WORKER_PROVIDER_FOR_WINDOW, FrameSecurityLevel::UNINITIALIZED,
+          context, nullptr));
 }
 
 ServiceWorkerProviderHost::ServiceWorkerProviderHost(
@@ -62,6 +66,7 @@
     int route_id,
     int provider_id,
     ServiceWorkerProviderType provider_type,
+    FrameSecurityLevel parent_frame_security_level,
     base::WeakPtr<ServiceWorkerContextCore> context,
     ServiceWorkerDispatcherHost* dispatcher_host)
     : client_uuid_(base::GenerateGUID()),
@@ -70,6 +75,7 @@
       render_thread_id_(kDocumentMainThreadId),
       provider_id_(provider_id),
       provider_type_(provider_type),
+      parent_frame_security_level_(parent_frame_security_level),
       context_(context),
       dispatcher_host_(dispatcher_host),
       allow_association_(true) {
@@ -111,6 +117,20 @@
   return MSG_ROUTING_NONE;
 }
 
+bool ServiceWorkerProviderHost::IsContextSecureForServiceWorker() const {
+  DCHECK(document_url_.is_valid());
+  if (!OriginCanAccessServiceWorkers(document_url_))
+    return false;
+
+  if (is_parent_frame_secure())
+    return true;
+
+  std::set<std::string> schemes;
+  GetContentClient()->browser()->GetSchemesBypassingSecureContextCheckWhitelist(
+      &schemes);
+  return schemes.find(document_url().scheme()) != schemes.end();
+}
+
 void ServiceWorkerProviderHost::OnVersionAttributesChanged(
     ServiceWorkerRegistration* registration,
     ChangedVersionAttributesMask changed_mask,
@@ -165,6 +185,7 @@
 void ServiceWorkerProviderHost::SetControllerVersionAttribute(
     ServiceWorkerVersion* version,
     bool notify_controllerchange) {
+  CHECK(!version || IsContextSecureForServiceWorker());
   if (version == controlling_version_.get())
     return;
 
diff --git a/content/browser/service_worker/service_worker_provider_host.h b/content/browser/service_worker/service_worker_provider_host.h
index 0372b11..294193f 100644
--- a/content/browser/service_worker/service_worker_provider_host.h
+++ b/content/browser/service_worker/service_worker_provider_host.h
@@ -61,6 +61,8 @@
   static std::unique_ptr<ServiceWorkerProviderHost> PreCreateNavigationHost(
       base::WeakPtr<ServiceWorkerContextCore> context);
 
+  enum class FrameSecurityLevel { UNINITIALIZED, INSECURE, SECURE };
+
   // When this provider host is for a Service Worker context, |route_id| is
   // MSG_ROUTING_NONE. When this provider host is for a Document,
   // |route_id| is the frame ID of the Document. When this provider host is for
@@ -72,6 +74,7 @@
                             int route_id,
                             int provider_id,
                             ServiceWorkerProviderType provider_type,
+                            FrameSecurityLevel parent_frame_security_level,
                             base::WeakPtr<ServiceWorkerContextCore> context,
                             ServiceWorkerDispatcherHost* dispatcher_host);
   virtual ~ServiceWorkerProviderHost();
@@ -82,6 +85,26 @@
   int frame_id() const;
   int route_id() const { return route_id_; }
 
+  bool is_parent_frame_secure() const {
+    return parent_frame_security_level_ == FrameSecurityLevel::SECURE;
+  }
+  void set_parent_frame_secure(bool is_parent_frame_secure) {
+    CHECK_EQ(parent_frame_security_level_, FrameSecurityLevel::UNINITIALIZED);
+    parent_frame_security_level_ = is_parent_frame_secure
+                                       ? FrameSecurityLevel::SECURE
+                                       : FrameSecurityLevel::INSECURE;
+  }
+
+  // Returns whether this provider host is secure enough to have a service
+  // worker controller.
+  // Analogous to Blink's Document::isSecureContext. Because of how service
+  // worker intercepts main resource requests, this check must be done
+  // browser-side once the URL is known (see comments in
+  // ServiceWorkerNetworkProvider::CreateForNavigation). This function uses
+  // |document_url_| and |is_parent_frame_secure_| to determine context
+  // security, so they must be set properly before calling this function.
+  bool IsContextSecureForServiceWorker() const;
+
   bool IsHostToRunningServiceWorker() {
     return running_hosted_version_.get() != NULL;
   }
@@ -259,6 +282,7 @@
                            UpdateForceBypassCache);
   FRIEND_TEST_ALL_PREFIXES(ServiceWorkerContextRequestHandlerTest,
                            ServiceWorkerDataRequestAnnotation);
+  FRIEND_TEST_ALL_PREFIXES(ServiceWorkerProviderHostTest, ContextSecurity);
 
   struct OneShotGetReadyCallback {
     GetRegistrationForReadyCallback callback;
@@ -307,6 +331,7 @@
   int render_thread_id_;
   int provider_id_;
   ServiceWorkerProviderType provider_type_;
+  FrameSecurityLevel parent_frame_security_level_;
   GURL document_url_;
   GURL topmost_frame_url_;
 
diff --git a/content/browser/service_worker/service_worker_provider_host_unittest.cc b/content/browser/service_worker/service_worker_provider_host_unittest.cc
index cd478f0..86b89f2 100644
--- a/content/browser/service_worker/service_worker_provider_host_unittest.cc
+++ b/content/browser/service_worker/service_worker_provider_host_unittest.cc
@@ -13,18 +13,35 @@
 #include "content/browser/service_worker/service_worker_register_job.h"
 #include "content/browser/service_worker/service_worker_registration.h"
 #include "content/browser/service_worker/service_worker_version.h"
+#include "content/public/common/origin_util.h"
 #include "content/public/test/test_browser_thread_bundle.h"
+#include "content/test/test_content_browser_client.h"
+#include "content/test/test_content_client.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace content {
 
+const char kServiceWorkerScheme[] = "i-can-use-service-worker";
+
+class ServiceWorkerTestContentClient : public TestContentClient {
+ public:
+  void AddServiceWorkerSchemes(std::set<std::string>* schemes) override {
+    schemes->insert(kServiceWorkerScheme);
+  }
+};
+
 class ServiceWorkerProviderHostTest : public testing::Test {
  protected:
   ServiceWorkerProviderHostTest()
-      : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {}
+      : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) {
+    SetContentClient(&test_content_client_);
+  }
   ~ServiceWorkerProviderHostTest() override {}
 
   void SetUp() override {
+    old_content_browser_client_ =
+        SetBrowserClientForTesting(&test_content_browser_client_);
+
     helper_.reset(new EmbeddedWorkerTestHelper(base::FilePath()));
     context_ = helper_->context();
     script_url_ = GURL("http://www.example.com/service_worker.js");
@@ -35,16 +52,18 @@
 
     // Prepare provider hosts (for the same process).
     std::unique_ptr<ServiceWorkerProviderHost> host1(
-        new ServiceWorkerProviderHost(helper_->mock_render_process_id(),
-                                      MSG_ROUTING_NONE, 1 /* provider_id */,
-                                      SERVICE_WORKER_PROVIDER_FOR_WINDOW,
-                                      context_->AsWeakPtr(), NULL));
+        new ServiceWorkerProviderHost(
+            helper_->mock_render_process_id(), MSG_ROUTING_NONE,
+            1 /* provider_id */, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+            ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+            context_->AsWeakPtr(), NULL));
     host1->SetDocumentUrl(GURL("http://www.example.com/example1.html"));
     std::unique_ptr<ServiceWorkerProviderHost> host2(
-        new ServiceWorkerProviderHost(helper_->mock_render_process_id(),
-                                      MSG_ROUTING_NONE, 2 /* provider_id */,
-                                      SERVICE_WORKER_PROVIDER_FOR_WINDOW,
-                                      context_->AsWeakPtr(), NULL));
+        new ServiceWorkerProviderHost(
+            helper_->mock_render_process_id(), MSG_ROUTING_NONE,
+            2 /* provider_id */, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+            ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+            context_->AsWeakPtr(), NULL));
     host2->SetDocumentUrl(GURL("http://www.example.com/example2.html"));
     provider_host1_ = host1->AsWeakPtr();
     provider_host2_ = host2->AsWeakPtr();
@@ -56,6 +75,7 @@
     registration1_ = 0;
     registration2_ = 0;
     helper_.reset();
+    SetBrowserClientForTesting(old_content_browser_client_);
   }
 
   bool PatternHasProcessToRun(const GURL& pattern) const {
@@ -70,6 +90,9 @@
   base::WeakPtr<ServiceWorkerProviderHost> provider_host1_;
   base::WeakPtr<ServiceWorkerProviderHost> provider_host2_;
   GURL script_url_;
+  ServiceWorkerTestContentClient test_content_client_;
+  TestContentBrowserClient test_content_browser_client_;
+  ContentBrowserClient* old_content_browser_client_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerProviderHostTest);
@@ -128,4 +151,36 @@
   ASSERT_EQ(provider_host1_->MatchRegistration(), nullptr);
 }
 
+TEST_F(ServiceWorkerProviderHostTest, ContextSecurity) {
+  using FrameSecurityLevel = ServiceWorkerProviderHost::FrameSecurityLevel;
+
+  // Insecure document URL.
+  provider_host1_->SetDocumentUrl(GURL("http://host"));
+  provider_host1_->parent_frame_security_level_ = FrameSecurityLevel::SECURE;
+  EXPECT_FALSE(provider_host1_->IsContextSecureForServiceWorker());
+
+  // Insecure parent frame.
+  provider_host1_->SetDocumentUrl(GURL("https://host"));
+  provider_host1_->parent_frame_security_level_ = FrameSecurityLevel::INSECURE;
+  EXPECT_FALSE(provider_host1_->IsContextSecureForServiceWorker());
+
+  // Secure URL and parent frame.
+  provider_host1_->SetDocumentUrl(GURL("https://host"));
+  provider_host1_->parent_frame_security_level_ = FrameSecurityLevel::SECURE;
+  EXPECT_TRUE(provider_host1_->IsContextSecureForServiceWorker());
+
+  // Exceptional service worker scheme.
+  GURL url(std::string(kServiceWorkerScheme) + "://host");
+  EXPECT_TRUE(url.is_valid());
+  provider_host1_->SetDocumentUrl(url);
+  provider_host1_->parent_frame_security_level_ = FrameSecurityLevel::SECURE;
+  EXPECT_FALSE(IsOriginSecure(url));
+  EXPECT_TRUE(OriginCanAccessServiceWorkers(url));
+  EXPECT_TRUE(provider_host1_->IsContextSecureForServiceWorker());
+
+  // Exceptional service worker scheme with insecure parent frame.
+  provider_host1_->parent_frame_security_level_ = FrameSecurityLevel::INSECURE;
+  EXPECT_FALSE(provider_host1_->IsContextSecureForServiceWorker());
+}
+
 }  // namespace content
diff --git a/content/browser/service_worker/service_worker_request_handler_unittest.cc b/content/browser/service_worker/service_worker_request_handler_unittest.cc
index 56dcac6..7316c91 100644
--- a/content/browser/service_worker/service_worker_request_handler_unittest.cc
+++ b/content/browser/service_worker/service_worker_request_handler_unittest.cc
@@ -42,20 +42,20 @@
     helper_.reset(new EmbeddedWorkerTestHelper(base::FilePath()));
 
     // A new unstored registration/version.
-    registration_ = new ServiceWorkerRegistration(
-        GURL("http://host/scope/"), 1L, context()->AsWeakPtr());
+    registration_ = new ServiceWorkerRegistration(GURL("https://host/scope/"),
+                                                  1L, context()->AsWeakPtr());
     version_ = new ServiceWorkerVersion(registration_.get(),
-                                        GURL("http://host/script.js"),
-                                        1L,
+                                        GURL("https://host/script.js"), 1L,
                                         context()->AsWeakPtr());
 
     // An empty host.
     std::unique_ptr<ServiceWorkerProviderHost> host(
-        new ServiceWorkerProviderHost(helper_->mock_render_process_id(),
-                                      MSG_ROUTING_NONE, kMockProviderId,
-                                      SERVICE_WORKER_PROVIDER_FOR_WINDOW,
-                                      context()->AsWeakPtr(), nullptr));
-    host->SetDocumentUrl(GURL("http://host/scope/"));
+        new ServiceWorkerProviderHost(
+            helper_->mock_render_process_id(), MSG_ROUTING_NONE,
+            kMockProviderId, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+            ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+            context()->AsWeakPtr(), nullptr));
+    host->SetDocumentUrl(GURL("https://host/scope/"));
     provider_host_ = host->AsWeakPtr();
     context()->AddProviderHost(std::move(host));
 
diff --git a/content/browser/service_worker/service_worker_storage_unittest.cc b/content/browser/service_worker/service_worker_storage_unittest.cc
index ac3281e..478ace7 100644
--- a/content/browser/service_worker/service_worker_storage_unittest.cc
+++ b/content/browser/service_worker/service_worker_storage_unittest.cc
@@ -1276,6 +1276,7 @@
   std::unique_ptr<ServiceWorkerProviderHost> host(new ServiceWorkerProviderHost(
       33 /* dummy render process id */, MSG_ROUTING_NONE,
       1 /* dummy provider_id */, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+      ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
       context()->AsWeakPtr(), NULL));
   registration_->active_version()->AddControllee(host.get());
 
@@ -1327,6 +1328,7 @@
   std::unique_ptr<ServiceWorkerProviderHost> host(new ServiceWorkerProviderHost(
       33 /* dummy render process id */, MSG_ROUTING_NONE,
       1 /* dummy provider_id */, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+      ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
       context()->AsWeakPtr(), NULL));
   registration_->active_version()->AddControllee(host.get());
 
@@ -1486,6 +1488,7 @@
   std::unique_ptr<ServiceWorkerProviderHost> host(new ServiceWorkerProviderHost(
       33 /* dummy render process id */, MSG_ROUTING_NONE,
       1 /* dummy provider_id */, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+      ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
       context()->AsWeakPtr(), NULL));
   registration_->active_version()->AddControllee(host.get());
 
diff --git a/content/browser/service_worker/service_worker_url_request_job_unittest.cc b/content/browser/service_worker/service_worker_url_request_job_unittest.cc
index 8da106c4..53b3fe81 100644
--- a/content/browser/service_worker/service_worker_url_request_job_unittest.cc
+++ b/content/browser/service_worker/service_worker_url_request_job_unittest.cc
@@ -143,13 +143,9 @@
     helper_.reset(helper);
 
     registration_ = new ServiceWorkerRegistration(
-        GURL("http://example.com/"),
-        1L,
-        helper_->context()->AsWeakPtr());
+        GURL("https://example.com/"), 1L, helper_->context()->AsWeakPtr());
     version_ = new ServiceWorkerVersion(
-        registration_.get(),
-        GURL("http://example.com/service_worker.js"),
-        1L,
+        registration_.get(), GURL("https://example.com/service_worker.js"), 1L,
         helper_->context()->AsWeakPtr());
     std::vector<ServiceWorkerDatabase::ResourceRecord> records;
     records.push_back(
@@ -181,10 +177,11 @@
     std::unique_ptr<ServiceWorkerProviderHost> provider_host(
         new ServiceWorkerProviderHost(
             helper_->mock_render_process_id(), MSG_ROUTING_NONE, kProviderID,
-            SERVICE_WORKER_PROVIDER_FOR_WINDOW, helper_->context()->AsWeakPtr(),
-            nullptr));
+            SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+            ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+            helper_->context()->AsWeakPtr(), nullptr));
     provider_host_ = provider_host->AsWeakPtr();
-    provider_host->SetDocumentUrl(GURL("http://example.com/"));
+    provider_host->SetDocumentUrl(GURL("https://example.com/"));
     registration_->SetActiveVersion(version_);
     provider_host->AssociateRegistration(registration_.get(),
                                          false /* notify_controllerchange */);
@@ -198,7 +195,7 @@
 
     url_request_job_factory_.reset(new net::URLRequestJobFactoryImpl);
     url_request_job_factory_->SetProtocolHandler(
-        "http",
+        "https",
         base::WrapUnique(new MockHttpProtocolHandler(
             provider_host->AsWeakPtr(), browser_context_->GetResourceContext(),
             blob_storage_context->AsWeakPtr(), this)));
@@ -240,7 +237,7 @@
                    const std::string& expected_response,
                    bool expect_valid_ssl) {
     request_ = url_request_context_.CreateRequest(
-        GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+        GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
         &url_request_delegate_);
 
     request_->set_method("GET");
@@ -377,7 +374,7 @@
 TEST_F(ServiceWorkerURLRequestJobTest, DeletedProviderHostBeforeFetchEvent) {
   version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
   request_ = url_request_context_.CreateRequest(
-      GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+      GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
       &url_request_delegate_);
 
   request_->set_method("GET");
@@ -521,7 +518,7 @@
   SetUpWithHelper(new StreamResponder(stream_url));
   version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
   request_ = url_request_context_.CreateRequest(
-      GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+      GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
       &url_request_delegate_);
   request_->set_method("GET");
   request_->Start();
@@ -569,7 +566,7 @@
 
   version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
   request_ = url_request_context_.CreateRequest(
-      GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+      GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
       &url_request_delegate_);
   request_->set_method("GET");
   request_->Start();
@@ -629,7 +626,7 @@
 
   version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
   request_ = url_request_context_.CreateRequest(
-      GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+      GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
       &url_request_delegate_);
   request_->set_method("GET");
   request_->Start();
@@ -671,7 +668,7 @@
 
   version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
   request_ = url_request_context_.CreateRequest(
-      GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+      GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
       &url_request_delegate_);
   request_->set_method("GET");
   request_->Start();
@@ -719,7 +716,7 @@
 
   version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
   request_ = url_request_context_.CreateRequest(
-      GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+      GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
       &url_request_delegate_);
   request_->set_method("GET");
   request_->Start();
@@ -769,7 +766,7 @@
 
   version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
   request_ = url_request_context_.CreateRequest(
-      GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+      GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
       &url_request_delegate_);
   request_->set_method("GET");
   request_->Start();
@@ -816,7 +813,7 @@
 
   version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
   request_ = url_request_context_.CreateRequest(
-      GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+      GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
       &url_request_delegate_);
   request_->set_method("GET");
   request_->Start();
@@ -846,7 +843,7 @@
   SetUpWithHelper(new EmbeddedWorkerTestHelper(base::FilePath()), false);
   version_->SetStatus(ServiceWorkerVersion::ACTIVATED);
   request_ = url_request_context_.CreateRequest(
-      GURL("http://example.com/foo.html"), net::DEFAULT_PRIORITY,
+      GURL("https://example.com/foo.html"), net::DEFAULT_PRIORITY,
       &url_request_delegate_);
   request_->set_method("GET");
   request_->Start();
diff --git a/content/browser/service_worker/service_worker_version_unittest.cc b/content/browser/service_worker/service_worker_version_unittest.cc
index 4bae9ed..c1fa1c3 100644
--- a/content/browser/service_worker/service_worker_version_unittest.cc
+++ b/content/browser/service_worker/service_worker_version_unittest.cc
@@ -622,6 +622,7 @@
   std::unique_ptr<ServiceWorkerProviderHost> host(new ServiceWorkerProviderHost(
       33 /* dummy render process id */, MSG_ROUTING_NONE /* render_frame_id */,
       1 /* dummy provider_id */, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+      ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
       helper_->context()->AsWeakPtr(), NULL));
   version_->AddControllee(host.get());
   EXPECT_TRUE(version_->timeout_timer_.IsRunning());
diff --git a/content/browser/service_worker/service_worker_write_to_cache_job_unittest.cc b/content/browser/service_worker/service_worker_write_to_cache_job_unittest.cc
index cc8de0f..b63291f 100644
--- a/content/browser/service_worker/service_worker_write_to_cache_job_unittest.cc
+++ b/content/browser/service_worker/service_worker_write_to_cache_job_unittest.cc
@@ -280,9 +280,11 @@
       int provider_id,
       const scoped_refptr<ServiceWorkerVersion>& version) {
     std::unique_ptr<ServiceWorkerProviderHost> host(
-        new ServiceWorkerProviderHost(process_id, MSG_ROUTING_NONE, provider_id,
-                                      SERVICE_WORKER_PROVIDER_FOR_WORKER,
-                                      context()->AsWeakPtr(), nullptr));
+        new ServiceWorkerProviderHost(
+            process_id, MSG_ROUTING_NONE, provider_id,
+            SERVICE_WORKER_PROVIDER_FOR_WORKER,
+            ServiceWorkerProviderHost::FrameSecurityLevel::SECURE,
+            context()->AsWeakPtr(), nullptr));
     base::WeakPtr<ServiceWorkerProviderHost> provider_host = host->AsWeakPtr();
     context()->AddProviderHost(std::move(host));
     provider_host->running_hosted_version_ = version;
diff --git a/content/child/service_worker/service_worker_network_provider.cc b/content/child/service_worker/service_worker_network_provider.cc
index 0632e81..ab7597c 100644
--- a/content/child/service_worker/service_worker_network_provider.cc
+++ b/content/child/service_worker/service_worker_network_provider.cc
@@ -11,6 +11,8 @@
 #include "content/common/service_worker/service_worker_messages.h"
 #include "content/common/service_worker/service_worker_utils.h"
 #include "content/public/common/browser_side_navigation_policy.h"
+#include "third_party/WebKit/public/web/WebLocalFrame.h"
+#include "third_party/WebKit/public/web/WebSandboxFlags.h"
 
 namespace content {
 
@@ -43,7 +45,7 @@
 ServiceWorkerNetworkProvider::CreateForNavigation(
     int route_id,
     const RequestNavigationParams& request_params,
-    blink::WebSandboxFlags sandbox_flags,
+    blink::WebLocalFrame* frame,
     bool content_initiated) {
   bool browser_side_navigation = IsBrowserSideNavigationEnabled();
   bool should_create_provider_for_window = false;
@@ -65,24 +67,33 @@
            service_worker_provider_id == kInvalidServiceWorkerProviderId);
   } else {
     should_create_provider_for_window =
-        (sandbox_flags & blink::WebSandboxFlags::Origin) !=
-        blink::WebSandboxFlags::Origin;
+        ((frame->effectiveSandboxFlags() & blink::WebSandboxFlags::Origin) !=
+         blink::WebSandboxFlags::Origin);
   }
 
   // Now create the ServiceWorkerNetworkProvider (with invalid id if needed).
   if (should_create_provider_for_window) {
+    // Ideally Document::isSecureContext would be called here, but the document
+    // is not created yet, and due to redirects the URL may change. So pass
+    // is_parent_frame_secure to the browser process, so it can determine the
+    // context security when deciding whether to allow a service worker to
+    // control the document.
+    bool is_parent_frame_secure =
+        !frame->parent() || frame->parent()->canHaveSecureChild();
+
     if (service_worker_provider_id == kInvalidServiceWorkerProviderId) {
       network_provider = std::unique_ptr<ServiceWorkerNetworkProvider>(
           new ServiceWorkerNetworkProvider(route_id,
-                                           SERVICE_WORKER_PROVIDER_FOR_WINDOW));
+                                           SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+                                           is_parent_frame_secure));
     } else {
       CHECK(browser_side_navigation);
       DCHECK(ServiceWorkerUtils::IsBrowserAssignedProviderId(
           service_worker_provider_id));
       network_provider = std::unique_ptr<ServiceWorkerNetworkProvider>(
-          new ServiceWorkerNetworkProvider(route_id,
-                                           SERVICE_WORKER_PROVIDER_FOR_WINDOW,
-                                           service_worker_provider_id));
+          new ServiceWorkerNetworkProvider(
+              route_id, SERVICE_WORKER_PROVIDER_FOR_WINDOW,
+              service_worker_provider_id, is_parent_frame_secure));
     }
   } else {
     network_provider = std::unique_ptr<ServiceWorkerNetworkProvider>(
@@ -94,7 +105,8 @@
 ServiceWorkerNetworkProvider::ServiceWorkerNetworkProvider(
     int route_id,
     ServiceWorkerProviderType provider_type,
-    int browser_provider_id)
+    int browser_provider_id,
+    bool is_parent_frame_secure)
     : provider_id_(browser_provider_id) {
   if (provider_id_ == kInvalidServiceWorkerProviderId)
     return;
@@ -104,15 +116,17 @@
       provider_id_, provider_type,
       ChildThreadImpl::current()->thread_safe_sender());
   ChildThreadImpl::current()->Send(new ServiceWorkerHostMsg_ProviderCreated(
-      provider_id_, route_id, provider_type));
+      provider_id_, route_id, provider_type, is_parent_frame_secure));
 }
 
 ServiceWorkerNetworkProvider::ServiceWorkerNetworkProvider(
     int route_id,
-    ServiceWorkerProviderType provider_type)
+    ServiceWorkerProviderType provider_type,
+    bool is_parent_frame_secure)
     : ServiceWorkerNetworkProvider(route_id,
                                    provider_type,
-                                   GetNextProviderId()) {}
+                                   GetNextProviderId(),
+                                   is_parent_frame_secure) {}
 
 ServiceWorkerNetworkProvider::ServiceWorkerNetworkProvider()
     : provider_id_(kInvalidServiceWorkerProviderId) {}
diff --git a/content/child/service_worker/service_worker_network_provider.h b/content/child/service_worker/service_worker_network_provider.h
index 178adf5..1d09950 100644
--- a/content/child/service_worker/service_worker_network_provider.h
+++ b/content/child/service_worker/service_worker_network_provider.h
@@ -15,7 +15,11 @@
 #include "base/supports_user_data.h"
 #include "content/common/content_export.h"
 #include "content/common/service_worker/service_worker_types.h"
-#include "third_party/WebKit/public/web/WebSandboxFlags.h"
+
+namespace blink {
+class WebDataSource;
+class WebLocalFrame;
+}  // namespace blink
 
 namespace content {
 
@@ -46,15 +50,18 @@
   static std::unique_ptr<ServiceWorkerNetworkProvider> CreateForNavigation(
       int route_id,
       const RequestNavigationParams& request_params,
-      blink::WebSandboxFlags sandbox_flags,
+      blink::WebLocalFrame* frame,
       bool content_initiated);
 
   // PlzNavigate
   // The |browser_provider_id| is initialized by the browser for navigations.
   ServiceWorkerNetworkProvider(int route_id,
                                ServiceWorkerProviderType type,
-                               int browser_provider_id);
-  ServiceWorkerNetworkProvider(int route_id, ServiceWorkerProviderType type);
+                               int browser_provider_id,
+                               bool is_parent_frame_secure);
+  ServiceWorkerNetworkProvider(int route_id,
+                               ServiceWorkerProviderType type,
+                               bool is_parent_frame_secure);
   ServiceWorkerNetworkProvider();
   ~ServiceWorkerNetworkProvider() override;
 
diff --git a/content/common/service_worker/service_worker_messages.h b/content/common/service_worker/service_worker_messages.h
index 05f22df..412c0fc 100644
--- a/content/common/service_worker/service_worker_messages.h
+++ b/content/common/service_worker/service_worker_messages.h
@@ -203,10 +203,21 @@
 // MSG_ROUTING_NONE. |provider_type| identifies whether this provider is for
 // Service Worker controllees (documents and Shared Workers) or for controllers
 // (Service Workers).
-IPC_MESSAGE_CONTROL3(ServiceWorkerHostMsg_ProviderCreated,
+//
+// |is_parent_frame_secure| is false if the provider is created for a
+// document whose parent frame is not secure from the point of view of the
+// document; that is, blink::WebFrame::canHaveSecureChild() returns false.
+// This doesn't mean the document is necessarily an insecure context,
+// because the document may have a URL whose scheme is granted an exception
+// that allows bypassing the ancestor secure context check. See the
+// comment in blink::Document::isSecureContextImpl for more details.
+// If the provider is not created for a document, or the document does not have
+// a parent frame, |is_parent_frame_secure| is true.
+IPC_MESSAGE_CONTROL4(ServiceWorkerHostMsg_ProviderCreated,
                      int /* provider_id */,
                      int /* route_id */,
-                     content::ServiceWorkerProviderType /* provider_type */)
+                     content::ServiceWorkerProviderType /* provider_type */,
+                     bool /* is_parent_frame_secure */)
 
 // Informs the browser of a ServiceWorkerProvider being destroyed.
 IPC_MESSAGE_CONTROL1(ServiceWorkerHostMsg_ProviderDestroyed,
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 56b709c..d1f9ae0 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -596,6 +596,12 @@
   virtual void GetAdditionalAllowedSchemesForFileSystem(
       std::vector<std::string>* additional_schemes) {}
 
+  // |schemes| is a return value parameter that gets a whitelist of schemes that
+  // should bypass the Is Privileged Context check.
+  // See http://www.w3.org/TR/powerful-features/#settings-privileged
+  virtual void GetSchemesBypassingSecureContextCheckWhitelist(
+      std::set<std::string>* schemes) {}
+
   // Returns auto mount handlers for URL requests for FileSystem APIs.
   virtual void GetURLRequestAutoMountHandlers(
       std::vector<storage::URLRequestAutoMountHandler>* handlers) {}
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 90b3151..75ee581 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -3076,8 +3076,8 @@
   ServiceWorkerNetworkProvider::AttachToDocumentState(
       DocumentState::FromDataSource(datasource),
       ServiceWorkerNetworkProvider::CreateForNavigation(
-          routing_id_, navigation_state->request_params(),
-          frame->effectiveSandboxFlags(), content_initiated));
+          routing_id_, navigation_state->request_params(), frame,
+          content_initiated));
 }
 
 void RenderFrameImpl::didStartProvisionalLoad(blink::WebLocalFrame* frame,
diff --git a/content/renderer/service_worker/service_worker_context_client.cc b/content/renderer/service_worker/service_worker_context_client.cc
index 870159e..b75f6be 100644
--- a/content/renderer/service_worker/service_worker_context_client.cc
+++ b/content/renderer/service_worker/service_worker_context_client.cc
@@ -574,7 +574,8 @@
   // we can observe its requests.
   std::unique_ptr<ServiceWorkerNetworkProvider> provider(
       new ServiceWorkerNetworkProvider(MSG_ROUTING_NONE,
-                                       SERVICE_WORKER_PROVIDER_FOR_CONTROLLER));
+                                       SERVICE_WORKER_PROVIDER_FOR_CONTROLLER,
+                                       true /* is_parent_frame_secure */));
   provider_context_ = provider->context();
 
   // Tell the network provider about which version to load.
diff --git a/content/renderer/shared_worker/embedded_shared_worker_stub.cc b/content/renderer/shared_worker/embedded_shared_worker_stub.cc
index 3d9818d..d35a107 100644
--- a/content/renderer/shared_worker/embedded_shared_worker_stub.cc
+++ b/content/renderer/shared_worker/embedded_shared_worker_stub.cc
@@ -241,7 +241,8 @@
   // we can observe its requests.
   std::unique_ptr<ServiceWorkerNetworkProvider> provider(
       new ServiceWorkerNetworkProvider(
-          route_id_, SERVICE_WORKER_PROVIDER_FOR_SHARED_WORKER));
+          route_id_, SERVICE_WORKER_PROVIDER_FOR_SHARED_WORKER,
+          true /* is_parent_frame_secure */));
 
   // The provider is kept around for the lifetime of the DataSource
   // and ownership is transferred to the DataSource.
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/insecure-parent-frame.html b/third_party/WebKit/LayoutTests/http/tests/serviceworker/insecure-parent-frame.html
new file mode 100644
index 0000000..5f0cb06
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/serviceworker/insecure-parent-frame.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="../resources/get-host-info.js?pipe=sub"></script>
+<script src="resources/test-helpers.js"></script>
+<title>Page Title</title>
+<body></body>
+<script>
+var host_info = get_host_info();
+
+var saw_message = new Promise(resolve => {
+    window.addEventListener('message', e => resolve(e.data));
+  });
+
+// This test registers a service worker, then creates an in-scope
+// https iframe inside an insecure http iframe. The in-scope iframe
+// communicates whether it has a controller to the top-level frame.
+promise_test(t => {
+    var script = 'resources/empty-worker.js';
+    var scope = 'resources/insecure-inscope';
+    return service_worker_unregister_and_register(t, script, scope)
+      .then(reg => {
+          add_completion_callback(() => reg.unregister());
+          return wait_for_state(t, reg.installing, 'activated');
+        })
+      .then(() => {
+          var url = host_info.UNAUTHENTICATED_ORIGIN +
+              '/serviceworker/resources/insecure-parent.html';
+          return with_iframe(url);
+        })
+      .then(() => saw_message)
+      .then(data => assert_equals(data, 'PASS'));
+  }, 'Service worker does not control the subframe of an insecure frame');
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/insecure-inscope.html b/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/insecure-inscope.html
new file mode 100644
index 0000000..a426608
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/insecure-inscope.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Page Title</title>
+<script>
+if (navigator.serviceWorker.controller)
+  window.parent.postMessage('FAIL', '*');
+else
+  window.parent.postMessage('PASS', '*');
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/insecure-parent.html b/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/insecure-parent.html
new file mode 100644
index 0000000..6885e04
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/serviceworker/resources/insecure-parent.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="../../resources/get-host-info.js?pipe=sub"></script>
+<title>Page Title</title>
+<body></body>
+<script>
+// The subframe messages us.
+window.addEventListener('message', e => {
+    window.parent.postMessage(e.data, '*');
+  });
+
+var iframe = document.createElement('iframe');
+iframe.src = get_host_info().HTTP_ORIGIN +
+    '/serviceworker/resources/insecure-inscope.html';
+document.body.appendChild(iframe);
+</script>
diff --git a/third_party/WebKit/Source/core/dom/Document.cpp b/third_party/WebKit/Source/core/dom/Document.cpp
index 76c57e7..f9c5602 100644
--- a/third_party/WebKit/Source/core/dom/Document.cpp
+++ b/third_party/WebKit/Source/core/dom/Document.cpp
@@ -354,15 +354,6 @@
     return element.document().frame() && element.rootEditableElement();
 }
 
-static bool isOriginPotentiallyTrustworthy(SecurityOrigin* origin, String* errorMessage)
-{
-    if (origin->isPotentiallyTrustworthy())
-        return true;
-    if (errorMessage)
-        *errorMessage = origin->isPotentiallyTrustworthyErrorMessage();
-    return false;
-}
-
 uint64_t Document::s_globalTreeVersion = 0;
 
 static bool s_threadedParsingEnabledForTesting = true;
@@ -3320,7 +3311,7 @@
     setMimeType(other.contentType());
 }
 
-bool Document::isSecureContextImpl(String* errorMessage, const SecureContextCheck privilegeContextCheck) const
+bool Document::isSecureContextImpl(const SecureContextCheck privilegeContextCheck) const
 {
     // There may be exceptions for the secure context check defined for certain
     // schemes. The exceptions are applied only to the special scheme and to
@@ -3350,21 +3341,16 @@
     //
     // In all cases, a frame must be potentially trustworthy in addition to
     // having an exception listed in order for the exception to be granted.
-    if (!isOriginPotentiallyTrustworthy(getSecurityOrigin(), errorMessage))
+    if (!getSecurityOrigin()->isPotentiallyTrustworthy())
         return false;
 
     if (SchemeRegistry::schemeShouldBypassSecureContextCheck(getSecurityOrigin()->protocol()))
         return true;
 
     if (privilegeContextCheck == StandardSecureContextCheck) {
-        if (!m_frame)
-            return true;
-        Frame* parent = m_frame->tree().parent();
-        while (parent) {
-            if (!isOriginPotentiallyTrustworthy(parent->securityContext()->getSecurityOrigin(), errorMessage))
-                return false;
-            parent = parent->tree().parent();
-        }
+        Frame* parent = m_frame ? m_frame->tree().parent() : nullptr;
+        if (parent && !parent->canHaveSecureChild())
+            return false;
     }
     return true;
 }
@@ -5841,12 +5827,15 @@
 
 bool Document::isSecureContext(String& errorMessage, const SecureContextCheck privilegeContextCheck) const
 {
-    return isSecureContextImpl(&errorMessage, privilegeContextCheck);
+    if (isSecureContextImpl(privilegeContextCheck))
+        return true;
+    errorMessage = SecurityOrigin::isPotentiallyTrustworthyErrorMessage();
+    return false;
 }
 
 bool Document::isSecureContext(const SecureContextCheck privilegeContextCheck) const
 {
-    return isSecureContextImpl(nullptr, privilegeContextCheck);
+    return isSecureContextImpl(privilegeContextCheck);
 }
 
 WebTaskRunner* Document::loadingTaskRunner() const
diff --git a/third_party/WebKit/Source/core/dom/Document.h b/third_party/WebKit/Source/core/dom/Document.h
index e9a814b..9f9492b 100644
--- a/third_party/WebKit/Source/core/dom/Document.h
+++ b/third_party/WebKit/Source/core/dom/Document.h
@@ -1143,7 +1143,7 @@
     bool childTypeAllowed(NodeType) const final;
     Node* cloneNode(bool deep) final;
     void cloneDataFromDocument(const Document&);
-    bool isSecureContextImpl(String* errorMessage, const SecureContextCheck priviligeContextCheck) const;
+    bool isSecureContextImpl(const SecureContextCheck priviligeContextCheck) const;
 
     ShadowCascadeOrder m_shadowCascadeOrder = ShadowCascadeNone;
 
diff --git a/third_party/WebKit/Source/core/frame/Frame.cpp b/third_party/WebKit/Source/core/frame/Frame.cpp
index 3e1d8f5..833e920 100644
--- a/third_party/WebKit/Source/core/frame/Frame.cpp
+++ b/third_party/WebKit/Source/core/frame/Frame.cpp
@@ -305,6 +305,15 @@
         childFrames[i]->didChangeVisibilityState();
 }
 
+bool Frame::canHaveSecureChild() const
+{
+    for (const Frame* parent = this; parent; parent = parent->tree().parent()) {
+        if (!parent->securityContext()->getSecurityOrigin()->isPotentiallyTrustworthy())
+            return false;
+    }
+    return true;
+}
+
 Frame::Frame(FrameClient* client, FrameHost* host, FrameOwner* owner)
     : m_treeNode(this)
     , m_host(host)
diff --git a/third_party/WebKit/Source/core/frame/Frame.h b/third_party/WebKit/Source/core/frame/Frame.h
index 423e882..698e71a 100644
--- a/third_party/WebKit/Source/core/frame/Frame.h
+++ b/third_party/WebKit/Source/core/frame/Frame.h
@@ -136,6 +136,15 @@
 
     virtual void didChangeVisibilityState();
 
+    // Use Document::isSecureContext() instead of this function to
+    // check whether this frame's document is a secure context.
+    //
+    // Returns whether it's possible for a document whose frame is a descendant
+    // of this frame to be a secure context, not considering scheme exceptions
+    // (since any document can be a secure context if it has a scheme
+    // exception). See Document::isSecureContextImpl for more details.
+    bool canHaveSecureChild() const;
+
 protected:
     Frame(FrameClient*, FrameHost*, FrameOwner*);
 
diff --git a/third_party/WebKit/Source/web/WebFrame.cpp b/third_party/WebKit/Source/web/WebFrame.cpp
index 1c4b5b4..89a33fd 100644
--- a/third_party/WebKit/Source/web/WebFrame.cpp
+++ b/third_party/WebKit/Source/web/WebFrame.cpp
@@ -285,6 +285,14 @@
     return fromFrame(toHTMLFrameOwnerElement(element)->contentFrame());
 }
 
+bool WebFrame::canHaveSecureChild() const
+{
+    Frame* frame = toImplBase()->frame();
+    if (!frame)
+        return false;
+    return frame->canHaveSecureChild();
+}
+
 bool WebFrame::isLoading() const
 {
     if (Frame* frame = toImplBase()->frame())
diff --git a/third_party/WebKit/Source/web/tests/WebFrameTest.cpp b/third_party/WebKit/Source/web/tests/WebFrameTest.cpp
index 0c55cb6..c375ac7 100644
--- a/third_party/WebKit/Source/web/tests/WebFrameTest.cpp
+++ b/third_party/WebKit/Source/web/tests/WebFrameTest.cpp
@@ -8618,4 +8618,62 @@
     EXPECT_EQ("world", toCoreString(result->ToString(mainFrame->mainWorldScriptContext()).ToLocalChecked()));
 }
 
+static void setSecurityOrigin(WebFrame* frame, PassRefPtr<SecurityOrigin> securityOrigin)
+{
+    Document* document = frame->document();
+    document->setSecurityOrigin(securityOrigin);
+}
+
+TEST_F(WebFrameTest, CanHaveSecureChild)
+{
+    FrameTestHelpers::WebViewHelper helper;
+    FrameTestHelpers::TestWebFrameClient client;
+    helper.initialize(true, &client, nullptr, nullptr);
+    WebFrame* mainFrame = helper.webView()->mainFrame();
+    RefPtr<SecurityOrigin> secureOrigin = SecurityOrigin::createFromString("https://example.com");
+    RefPtr<SecurityOrigin> insecureOrigin = SecurityOrigin::createFromString("http://example.com");
+
+    // Secure frame.
+    setSecurityOrigin(mainFrame, secureOrigin);
+    ASSERT_TRUE(mainFrame->canHaveSecureChild());
+
+    // Insecure frame.
+    setSecurityOrigin(mainFrame, insecureOrigin);
+    ASSERT_FALSE(mainFrame->canHaveSecureChild());
+
+    // Create a chain of frames.
+    FrameTestHelpers::loadFrame(mainFrame, "data:text/html,<iframe></iframe>");
+    WebFrame* childFrame = mainFrame->firstChild();
+    FrameTestHelpers::loadFrame(childFrame, "data:text/html,<iframe></iframe>");
+    WebFrame* grandchildFrame = childFrame->firstChild();
+
+    // Secure -> insecure -> secure frame.
+    setSecurityOrigin(mainFrame, secureOrigin);
+    setSecurityOrigin(childFrame, insecureOrigin);
+    setSecurityOrigin(grandchildFrame, secureOrigin);
+    ASSERT_TRUE(mainFrame->canHaveSecureChild());
+    ASSERT_FALSE(childFrame->canHaveSecureChild());
+    ASSERT_FALSE(grandchildFrame->canHaveSecureChild());
+
+    // A document in an insecure context can be considered secure if it has a
+    // scheme that bypasses the secure context check. But the exception doesn't
+    // apply to children of that document's frame.
+    SchemeRegistry::registerURLSchemeBypassingSecureContextCheck("very-special-scheme");
+    SchemeRegistry::registerURLSchemeAsSecure("very-special-scheme");
+    RefPtr<SecurityOrigin> specialOrigin = SecurityOrigin::createFromString("very-special-scheme://example.com");
+
+    setSecurityOrigin(mainFrame, insecureOrigin);
+    setSecurityOrigin(childFrame, specialOrigin);
+    setSecurityOrigin(grandchildFrame, secureOrigin);
+    ASSERT_FALSE(mainFrame->canHaveSecureChild());
+    ASSERT_FALSE(childFrame->canHaveSecureChild());
+    ASSERT_FALSE(grandchildFrame->canHaveSecureChild());
+    Document* mainDocument = mainFrame->document();
+    Document* childDocument = childFrame->document();
+    Document* grandchildDocument = grandchildFrame->document();
+    ASSERT_FALSE(mainDocument->isSecureContext());
+    ASSERT_TRUE(childDocument->isSecureContext());
+    ASSERT_FALSE(grandchildDocument->isSecureContext());
+}
+
 } // namespace blink
diff --git a/third_party/WebKit/public/web/WebFrame.h b/third_party/WebKit/public/web/WebFrame.h
index 121067b..52c488b 100644
--- a/third_party/WebKit/public/web/WebFrame.h
+++ b/third_party/WebKit/public/web/WebFrame.h
@@ -556,6 +556,15 @@
     // the given element is not a frame, iframe or if the frame is empty.
     BLINK_EXPORT static WebFrame* fromFrameOwnerElement(const WebElement&);
 
+    // Use WebDocument::isSecureContext() instead of this function to
+    // check whether this frame's document is a secure context.
+    //
+    // Returns whether it's possible for a document whose frame is a descendant
+    // of this frame to be a secure context, not considering scheme exceptions
+    // (since any document can be a secure context if it has a scheme
+    // exception). See Document::isSecureContextImpl for more details.
+    BLINK_EXPORT bool canHaveSecureChild() const;
+
 #if BLINK_IMPLEMENTATION
     static WebFrame* fromFrame(Frame*);