Redirect should not circumvent same-origin restrictions

Check whether we have access to the audio data when the format is set.
At this point we have enough information to determine this. The old approach
based on when the src was changed was incorrect because at the point, we
only know the new src; none of the response headers have been read yet.

This new approach also removes the incorrect message reported in 619114.

Bug: 826552, 619114
Change-Id: I95119b3a1e399c05d0fbd2da71f87967978efff6
Reviewed-on: https://chromium-review.googlesource.com/1069540
Commit-Queue: Raymond Toy <rtoy@chromium.org>
Reviewed-by: Yutaka Hirano <yhirano@chromium.org>
Reviewed-by: Hongchan Choi <hongchan@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#564313}(cherry picked from commit 761c75d2d607638ff53c764b4925bcca9be601d8)
Reviewed-on: https://chromium-review.googlesource.com/1089070
Reviewed-by: Raymond Toy <rtoy@chromium.org>
Cr-Commit-Position: refs/branch-heads/3440@{#210}
Cr-Branched-From: 010ddcfda246975d194964ccf20038ebbdec6084-refs/heads/master@{#561733}
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/media-element-audio-source-node-redirect-expected.txt b/third_party/WebKit/LayoutTests/http/tests/security/media-element-audio-source-node-redirect-expected.txt
new file mode 100644
index 0000000..c1ba686
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/media-element-audio-source-node-redirect-expected.txt
@@ -0,0 +1,11 @@
+CONSOLE WARNING: line 21: The Web Audio autoplay policy will be re-enabled in Chrome 70 (October 2018). Please check that your website is compatible with it. https://goo.gl/7K7WLu
+CONSOLE MESSAGE: MediaElementAudioSource outputs zeroes due to CORS access restrictions for http://127.0.0.1:8000/serviceworker/resources/redirect.php?Redirect=http://localhost:8000/security/resources/webaudio/laughter.wav
+MediaElementAudioSourceNode with redirection
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS All samples correctly zeroed.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/media-element-audio-source-node-redirect.html b/third_party/WebKit/LayoutTests/http/tests/security/media-element-audio-source-node-redirect.html
new file mode 100644
index 0000000..a166ec87
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/http/tests/security/media-element-audio-source-node-redirect.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<html>
+  <head>
+    <script src="resources/webaudio/compatibility.js"></script>
+    <script src="resources/webaudio/media-element-audio-source-node-test.js"></script>
+    <script src="/js-test-resources/js-test.js"></script>
+  </head>
+
+  <body>
+    <div id="description"></div>
+    <div id="console"></div>
+
+    <script>
+      description("MediaElementAudioSourceNode with redirection");
+
+      var src = "/serviceworker/resources/redirect.php?Redirect=http://localhost:8000/security/resources/webaudio/laughter.wav";
+      var data;
+
+      function checkResult (e) {
+        data = e.getChannelData(0);
+        var count = 0;
+
+        // Count the number of non-zero values. Since this is a cross-origin source, all the values
+        // should be zero.
+        for (var k = 0; k < data.length; ++k) {
+            if (data[k] != 0) {
+                ++count;
+            }
+        }
+        if (count > 0) {
+            testFailed("Expected all zeros but found " + count + " non-zero values out of " + data.length + ".");
+        } else {
+            testPassed("All samples correctly zeroed.");
+        }
+      }
+
+      runTest(src, checkResult);
+
+    </script>
+  </body>
+</html>
diff --git a/third_party/WebKit/LayoutTests/http/tests/security/resources/webaudio/media-element-audio-source-node-test.js b/third_party/WebKit/LayoutTests/http/tests/security/resources/webaudio/media-element-audio-source-node-test.js
index 20a5463..58c6699 100644
--- a/third_party/WebKit/LayoutTests/http/tests/security/resources/webaudio/media-element-audio-source-node-test.js
+++ b/third_party/WebKit/LayoutTests/http/tests/security/resources/webaudio/media-element-audio-source-node-test.js
@@ -41,6 +41,8 @@
         context.resume().then(() => {
                               spn.onaudioprocess = function(e) {
                                 checkResult(e.inputBuffer);
+                                // Stop the context so we don't keep getting called anymore.
+                                context.close();
                                 finishJSTest();
                               }
           });
diff --git a/third_party/blink/renderer/modules/webaudio/base_audio_context.cc b/third_party/blink/renderer/modules/webaudio/base_audio_context.cc
index 1bde585..44244ea 100644
--- a/third_party/blink/renderer/modules/webaudio/base_audio_context.cc
+++ b/third_party/blink/renderer/modules/webaudio/base_audio_context.cc
@@ -1058,4 +1058,21 @@
   }
 }
 
+bool BaseAudioContext::WouldTaintOrigin(const KURL& url) const {
+  // Data URLs don't taint the origin.
+  if (url.ProtocolIsData()) {
+    return false;
+  }
+
+  Document* document = GetDocument();
+  if (document && document->GetSecurityOrigin()) {
+    // The origin is tainted if and only if we cannot read content from the URL.
+    return !document->GetSecurityOrigin()->CanRequest(url);
+  }
+
+  // Be conservative and assume it's tainted if it's not a data url and if we
+  // can't get the security origin of the document.
+  return true;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/webaudio/base_audio_context.h b/third_party/blink/renderer/modules/webaudio/base_audio_context.h
index 0132fd6d..c24b333 100644
--- a/third_party/blink/renderer/modules/webaudio/base_audio_context.h
+++ b/third_party/blink/renderer/modules/webaudio/base_audio_context.h
@@ -346,6 +346,13 @@
   // Does nothing when the worklet global scope does not exist.
   void UpdateWorkletGlobalScopeOnRenderingThread();
 
+  // Returns true if the URL would taint the origin so that we shouldn't be
+  // allowing media to played through webaudio.
+  // TODO(crbug.com/845913): This should really be on an AudioContext.  Move
+  // this when we move the media stuff from BaseAudioContext to AudioContext, as
+  // requried by the spec.
+  bool WouldTaintOrigin(const KURL& url) const;
+
  protected:
   enum ContextType { kRealtimeContext, kOfflineContext };
 
diff --git a/third_party/blink/renderer/modules/webaudio/media_element_audio_source_node.cc b/third_party/blink/renderer/modules/webaudio/media_element_audio_source_node.cc
index 128b81d..d55da1f 100644
--- a/third_party/blink/renderer/modules/webaudio/media_element_audio_source_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/media_element_audio_source_node.cc
@@ -48,10 +48,7 @@
       media_element_(media_element),
       source_number_of_channels_(0),
       source_sample_rate_(0),
-      passes_current_src_cors_access_check_(
-          PassesCurrentSrcCORSAccessCheck(media_element.currentSrc())),
-      maybe_print_cors_message_(!passes_current_src_cors_access_check_),
-      current_src_string_(media_element.currentSrc().GetString()) {
+      is_origin_tainted_(false) {
   DCHECK(IsMainThread());
   // Default to stereo. This could change depending on what the media element
   // .src is set to.
@@ -87,6 +84,12 @@
 
 void MediaElementAudioSourceHandler::SetFormat(size_t number_of_channels,
                                                float source_sample_rate) {
+  bool is_tainted = WouldTaintOrigin();
+
+  if (is_tainted) {
+    PrintCORSMessage(MediaElement()->currentSrc().GetString());
+  }
+
   if (number_of_channels != source_number_of_channels_ ||
       source_sample_rate != source_sample_rate_) {
     if (!number_of_channels ||
@@ -99,13 +102,16 @@
       Locker<MediaElementAudioSourceHandler> locker(*this);
       source_number_of_channels_ = 0;
       source_sample_rate_ = 0;
+      is_origin_tainted_ = is_tainted;
       return;
     }
 
-    // Synchronize with process() to protect m_sourceNumberOfChannels,
-    // m_sourceSampleRate, and m_multiChannelResampler.
+    // Synchronize with process() to protect |source_number_of_channels_|,
+    // |source_sample_rate_|, |multi_channel_resampler_|. and
+    // |is_origin_tainted_|.
     Locker<MediaElementAudioSourceHandler> locker(*this);
 
+    is_origin_tainted_ = is_tainted;
     source_number_of_channels_ = number_of_channels;
     source_sample_rate_ = source_sample_rate;
 
@@ -128,36 +134,20 @@
   }
 }
 
-bool MediaElementAudioSourceHandler::PassesCORSAccessCheck() {
-  DCHECK(MediaElement());
+bool MediaElementAudioSourceHandler::WouldTaintOrigin() {
+  // If we're cross-origin and allowed access vie CORS, we're not tainted.
+  if (MediaElement()->GetWebMediaPlayer()->DidPassCORSAccessCheck()) {
+    return false;
+  }
 
-  return (MediaElement()->GetWebMediaPlayer() &&
-          MediaElement()->GetWebMediaPlayer()->DidPassCORSAccessCheck()) ||
-         passes_current_src_cors_access_check_;
-}
+  // Handles the case where the url is a redirect to another site that we're not
+  // allowed to access.
+  if (!MediaElement()->HasSingleSecurityOrigin()) {
+    return true;
+  }
 
-void MediaElementAudioSourceHandler::OnCurrentSrcChanged(
-    const KURL& current_src) {
-  DCHECK(IsMainThread());
-
-  // Synchronize with process().
-  Locker<MediaElementAudioSourceHandler> locker(*this);
-
-  passes_current_src_cors_access_check_ =
-      PassesCurrentSrcCORSAccessCheck(current_src);
-
-  // Make a note if we need to print a console message and save the |curentSrc|
-  // for use in the message.  Need to wait until later to print the message in
-  // case HTMLMediaElement allows access.
-  maybe_print_cors_message_ = !passes_current_src_cors_access_check_;
-  current_src_string_ = current_src.GetString();
-}
-
-bool MediaElementAudioSourceHandler::PassesCurrentSrcCORSAccessCheck(
-    const KURL& current_src) {
-  DCHECK(IsMainThread());
-  return Context()->GetSecurityOrigin() &&
-         Context()->GetSecurityOrigin()->CanRequest(current_src);
+  // Test to see if the current media URL taint the origin of the audio context?
+  return Context()->WouldTaintOrigin(MediaElement()->currentSrc());
 }
 
 void MediaElementAudioSourceHandler::PrintCORSMessage(const String& message) {
@@ -206,16 +196,7 @@
       provider.ProvideInput(output_bus, number_of_frames);
     }
     // Output silence if we don't have access to the element.
-    if (!PassesCORSAccessCheck()) {
-      if (maybe_print_cors_message_) {
-        // Print a CORS message, but just once for each change in the current
-        // media element source, and only if we have a document to print to.
-        maybe_print_cors_message_ = false;
-        PostCrossThreadTask(
-            *task_runner_, FROM_HERE,
-            CrossThreadBind(&MediaElementAudioSourceHandler::PrintCORSMessage,
-                            WrapRefCounted(this), current_src_string_));
-      }
+    if (is_origin_tainted_) {
       output_bus->Zero();
     }
   } else {
@@ -300,10 +281,6 @@
                                                 sample_rate);
 }
 
-void MediaElementAudioSourceNode::OnCurrentSrcChanged(const KURL& current_src) {
-  GetMediaElementAudioSourceHandler().OnCurrentSrcChanged(current_src);
-}
-
 void MediaElementAudioSourceNode::lock() {
   GetMediaElementAudioSourceHandler().lock();
 }
diff --git a/third_party/blink/renderer/modules/webaudio/media_element_audio_source_node.h b/third_party/blink/renderer/modules/webaudio/media_element_audio_source_node.h
index fdc16a8..311d563c 100644
--- a/third_party/blink/renderer/modules/webaudio/media_element_audio_source_node.h
+++ b/third_party/blink/renderer/modules/webaudio/media_element_audio_source_node.h
@@ -61,7 +61,6 @@
   // Helpers for AudioSourceProviderClient implementation of
   // MediaElementAudioSourceNode.
   void SetFormat(size_t number_of_channels, float sample_rate);
-  void OnCurrentSrcChanged(const KURL& current_src);
   void lock() EXCLUSIVE_LOCK_FUNCTION(GetProcessLock());
   void unlock() UNLOCK_FUNCTION(GetProcessLock());
 
@@ -78,11 +77,9 @@
   // As an audio source, we will never propagate silence.
   bool PropagatesSilence() const override { return false; }
 
-  // Must be called only on the audio thread.
-  bool PassesCORSAccessCheck();
-
-  // Must be called only on the main thread.
-  bool PassesCurrentSrcCORSAccessCheck(const KURL& current_src);
+  // Returns true if the origin of the media element is tainted so that the
+  // audio should be muted when playing through WebAudio.
+  bool WouldTaintOrigin();
 
   // Print warning if CORS restrictions cause MediaElementAudioSource to output
   // zeroes.
@@ -102,23 +99,13 @@
 
   std::unique_ptr<MultiChannelResampler> multi_channel_resampler_;
 
-  // |m_passesCurrentSrcCORSAccessCheck| holds the value of
-  // context()->getSecurityOrigin() &&
-  // context()->getSecurityOrigin()->canRequest(mediaElement()->currentSrc()),
-  // updated in the ctor and onCurrentSrcChanged() on the main thread and used
-  // in passesCORSAccessCheck() on the audio thread, protected by
-  // |m_processLock|.
-  bool passes_current_src_cors_access_check_;
-
-  // Indicates if we need to print a CORS message if the current source has
-  // changed and we have no access to it. Must be protected by |m_processLock|.
-  bool maybe_print_cors_message_;
-
-  // The value of mediaElement()->currentSrc().string() in the ctor and
-  // onCurrentSrcChanged().  Protected by |m_processLock|.
-  String current_src_string_;
-
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+  // True if the orgin would be tainted by the media element.  In this case,
+  // this node outputs silence.  This can happen if the media element source is
+  // a cross-origin source which we're not allowed to access due to CORS
+  // restrictions.
+  bool is_origin_tainted_;
 };
 
 class MediaElementAudioSourceNode final : public AudioNode,
@@ -142,7 +129,6 @@
 
   // AudioSourceProviderClient functions:
   void SetFormat(size_t number_of_channels, float sample_rate) override;
-  void OnCurrentSrcChanged(const KURL& current_src) override;
   void lock() override EXCLUSIVE_LOCK_FUNCTION(
       GetMediaElementAudioSourceHandler().GetProcessLock());
   void unlock() override