UPGRADE: Correctly handle navigations.

The behavior of 'upgrade-insecure-requests' has regressed for iframes and
navigations due to some recent changes in those bits of the loader. Of
course, I was an idiot, and didn't write layout tests for those pieces,
but relied on unit tests. The unit tests kept working, because they hard
coded the expected inputs! Hooray! The web, however, broke. Bleh.

This patch adjusts things such that iframes and navigations are correctly
upgraded in the presence of the directive.

As a drive-by, it also disables the mixed content warning that we show for
insecure form actions on secure pages if upgrading is active (as part of
the UIR promise is that we won't break your security indicators).

BUG=610441,610188

Review-Url: https://codereview.chromium.org/1964303003
Cr-Commit-Position: refs/heads/master@{#392890}
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/resources/post-origin-to-parent.html b/third_party/WebKit/LayoutTests/http/tests/security/resources/post-origin-to-parent.html
new file mode 100644
index 0000000..8d620198
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/resources/post-origin-to-parent.html
@@ -0,0 +1,3 @@
+<script>
+    window.parent.postMessage({ 'origin': document.origin }, '*');
+</script>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/upgrade-insecure-requests/form-upgrade.html b/third_party/WebKit/LayoutTests/http/tests/security/upgrade-insecure-requests/form-upgrade.html
new file mode 100644
index 0000000..32f1658
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/upgrade-insecure-requests/form-upgrade.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<head>
+<title>Upgrade Insecure Requests: Form Submission.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+async_test(t => {
+    var i = document.createElement('iframe');
+    i.srcdoc = "<meta http-equiv='Content-Security-Policy' content='upgrade-insecure-requests'>" +
+               "<form action='http://127.0.0.1:8443/security/resources/post-origin-to-parent.html'></form>" +
+               "<script>document.querySelector('form').submit()</scr" + "ipt>";
+
+    window.addEventListener('message', t.step_func(e => {
+        if (e.source == i.contentWindow) {
+            assert_equals("https://127.0.0.1:8443", e.data.origin);
+            t.done();
+        }
+    }));
+
+    document.body.appendChild(i);
+}, "Same-host form submissions are upgraded.");
+
+async_test(t => {
+    var i = document.createElement('iframe');
+    i.srcdoc = "<meta http-equiv='Content-Security-Policy' content='upgrade-insecure-requests'>" +
+               "<form action='http://example.test:8443/security/resources/post-origin-to-parent.html'></form>" +
+               "<script>document.querySelector('form').submit()</scr" + "ipt>";
+
+    window.addEventListener('message', t.step_func(e => {
+        if (e.source == i.contentWindow) {
+            assert_equals("https://example.test:8443", e.data.origin);
+            t.done();
+        }
+    }));
+
+    document.body.appendChild(i);
+}, "Cross-host form submissions are upgraded.");
+</script>
+</body>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/upgrade-insecure-requests/iframe-upgrade.https.html b/third_party/WebKit/LayoutTests/http/tests/security/upgrade-insecure-requests/iframe-upgrade.https.html
new file mode 100644
index 0000000..e6bd69d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/upgrade-insecure-requests/iframe-upgrade.https.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<head>
+<title>Upgrade Insecure Requests: IFrames.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
+</head>
+<body>
+<script>
+async_test(t => {
+    var i = document.createElement('iframe');
+
+    // This is a bit of a hack. UPGRADE doesn't upgrade the port number, so we
+    // specify this non-existent URL ('http' over port 8443). If UPGRADE doesn't
+    // work, it won't load.
+    i.src = "http://127.0.0.1:8443/security/resources/post-origin-to-parent.html";
+
+    window.addEventListener('message', t.step_func(e => {
+        if (e.source == i.contentWindow) {
+            assert_equals("https://127.0.0.1:8443", e.data.origin);
+            t.done();
+        }
+    }));
+    document.body.appendChild(i);
+}, "Same-host frames are upgraded.");
+
+async_test(t => {
+    var i = document.createElement('iframe');
+
+    // This is a bit of a hack. UPGRADE doesn't upgrade the port number, so we
+    // specify this non-existent URL ('http' over port 8443). If UPGRADE doesn't
+    // work, it won't load.
+    i.src = "http://example.test:8443/security/resources/post-origin-to-parent.html";
+
+    window.addEventListener('message', t.step_func(e => {
+        if (e.source == i.contentWindow) {
+            assert_equals("https://example.test:8443", e.data.origin);
+            t.done();
+        }
+    }));
+    document.body.appendChild(i);
+}, "Cross-host frames are upgraded.");
+
+async_test(t => {
+    var i = document.createElement('iframe');
+    i.srcdoc = "<a href='http://127.0.0.1:8443/security/resources/post-origin-to-parent.html'>Navigate!</a>" +
+               "<script>document.querySelector('a').click()</scr" + "ipt>";
+
+    window.addEventListener('message', t.step_func(e => {
+        if (e.source == i.contentWindow) {
+            assert_equals("https://127.0.0.1:8443", e.data.origin);
+            t.done();
+        }
+    }));
+
+    document.body.appendChild(i);
+}, "Upgrade policy cascades to nested, same-host frames.");
+
+async_test(t => {
+    var i = document.createElement('iframe');
+    i.srcdoc = "<a href='http://example.test:8443/security/resources/post-origin-to-parent.html'>Navigate!</a>" +
+               "<script>document.querySelector('a').click()</scr" + "ipt>";
+
+    window.addEventListener('message', t.step_func(e => {
+        if (e.source == i.contentWindow) {
+            assert_equals("https://example.test:8443", e.data.origin);
+            t.done();
+        }
+    }));
+
+    document.body.appendChild(i);
+}, "Upgrade policy cascades to nested, cross-host frames.");
+</script>
+</body>
diff --git a/third_party/WebKit/Source/core/html/HTMLFormElement.cpp b/third_party/WebKit/Source/core/html/HTMLFormElement.cpp
index c5200ed..5fb7ef4 100644
--- a/third_party/WebKit/Source/core/html/HTMLFormElement.cpp
+++ b/third_party/WebKit/Source/core/html/HTMLFormElement.cpp
@@ -489,12 +489,15 @@
 {
     if (name == actionAttr) {
         m_attributes.parseAction(value);
-        // If the new action attribute is pointing to insecure "action" location from a secure page
-        // it is marked as "passive" mixed content.
-        KURL actionURL = document().completeURL(m_attributes.action().isEmpty() ? document().url().getString() : m_attributes.action());
-        if (MixedContentChecker::isMixedFormAction(document().frame(), actionURL))
-            UseCounter::count(document().frame(), UseCounter::MixedContentFormPresent);
         logUpdateAttributeIfIsolatedWorldAndInDocument("form", actionAttr, oldValue, value);
+
+        if (document().getInsecureRequestsPolicy() != SecurityContext::InsecureRequestsUpgrade) {
+            // If we're not upgrading insecure requests, and the new action attribute is pointing to
+            // an insecure "action" location from a secure page it is marked as "passive" mixed content.
+            KURL actionURL = document().completeURL(m_attributes.action().isEmpty() ? document().url().getString() : m_attributes.action());
+            if (MixedContentChecker::isMixedFormAction(document().frame(), actionURL))
+                UseCounter::count(document().frame(), UseCounter::MixedContentFormPresent);
+        }
     } else if (name == targetAttr) {
         m_attributes.setTarget(value);
     } else if (name == methodAttr) {
diff --git a/third_party/WebKit/Source/core/loader/FormSubmission.cpp b/third_party/WebKit/Source/core/loader/FormSubmission.cpp
index 1ba1c83..d42aa99 100644
--- a/third_party/WebKit/Source/core/loader/FormSubmission.cpp
+++ b/third_party/WebKit/Source/core/loader/FormSubmission.cpp
@@ -34,6 +34,7 @@
 #include "core/InputTypeNames.h"
 #include "core/dom/Document.h"
 #include "core/events/Event.h"
+#include "core/frame/UseCounter.h"
 #include "core/html/FormData.h"
 #include "core/html/HTMLFormControlElement.h"
 #include "core/html/HTMLFormElement.h"
@@ -196,6 +197,14 @@
 
     Document& document = form->document();
     KURL actionURL = document.completeURL(copiedAttributes.action().isEmpty() ? document.url().getString() : copiedAttributes.action());
+
+    if (document.getInsecureRequestsPolicy() == SecurityContext::InsecureRequestsUpgrade && actionURL.protocolIs("http")) {
+        UseCounter::count(document, UseCounter::UpgradeInsecureRequestsUpgradedRequest);
+        actionURL.setProtocol("https");
+        if (actionURL.port() == 80)
+            actionURL.setPort(443);
+    }
+
     bool isMailtoForm = actionURL.protocolIs("mailto");
     bool isMultiPartForm = false;
     AtomicString encodingType = copiedAttributes.encodingType();
diff --git a/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp b/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
index 167da90..17bfc33 100644
--- a/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
+++ b/third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
@@ -666,9 +666,11 @@
     if (fetchRequest.resourceRequest().frameType() != WebURLRequest::FrameTypeNone)
         fetchRequest.mutableResourceRequest().addHTTPHeaderField("Upgrade-Insecure-Requests", "1");
 
-    if (m_document && m_document->getInsecureRequestsPolicy() == SecurityContext::InsecureRequestsUpgrade && url.protocolIs("http")) {
-        ASSERT(m_document->insecureNavigationsToUpgrade());
+    // If we don't yet have an |m_document| (because we're loading an iframe, for instance), check the FrameLoader's policy.
+    SecurityContext::InsecureRequestsPolicy relevantPolicy = m_document ? m_document->getInsecureRequestsPolicy() : frame()->loader().getInsecureRequestsPolicy();
+    SecurityContext::InsecureNavigationsSet* relevantNavigationSet = m_document ? m_document->insecureNavigationsToUpgrade() : frame()->loader().insecureNavigationsToUpgrade();
 
+    if (url.protocolIs("http") && relevantPolicy == SecurityContext::InsecureRequestsUpgrade) {
         // We always upgrade requests that meet any of the following criteria:
         //
         // 1. Are for subresources (including nested frames).
@@ -678,7 +680,7 @@
         if (request.frameType() == WebURLRequest::FrameTypeNone
             || request.frameType() == WebURLRequest::FrameTypeNested
             || request.requestContext() == WebURLRequest::RequestContextForm
-            || (!url.host().isNull() && m_document->insecureNavigationsToUpgrade()->contains(url.host().impl()->hash())))
+            || (!url.host().isNull() && relevantNavigationSet->contains(url.host().impl()->hash())))
         {
             UseCounter::count(m_document, UseCounter::UpgradeInsecureRequestsUpgradedRequest);
             url.setProtocol("https");