Commit 657a055a authored by Yutaka Hirano's avatar Yutaka Hirano Committed by Commit Bot

[media] Treat cross-origin redirect as TAINTED only for no-cors requests

With https://crrev.com/a9cbaa7a40e2b2723cfc2f266c42f4980038a949,
WebMediaPlayer blindly treats a resource experiencing cross-origin
redirects as TAINTED. In fact, it should be treated as TAINTED only
when its request mode is "no-cors".

The added tests are provided by hongchan@chromium.org.

Bug: 899745, 901383
Change-Id: Idb66407552085b053818f3e4a9d8d5ff3ddeaf45
Reviewed-on: https://chromium-review.googlesource.com/c/1325281Reviewed-by: default avatarHongchan Choi <hongchan@chromium.org>
Reviewed-by: default avatarFredrik Hubinette <hubbe@chromium.org>
Commit-Queue: Yutaka Hirano <yhirano@chromium.org>
Cr-Commit-Position: refs/heads/master@{#606681}
parent a684019a
......@@ -290,6 +290,10 @@ bool MultibufferDataSource::IsCorsCrossOrigin() const {
return url_data()->is_cors_cross_origin();
}
UrlData::CORSMode MultibufferDataSource::cors_mode() const {
return url_data()->cors_mode();
}
void MultibufferDataSource::MediaPlaybackRateChanged(double playback_rate) {
DCHECK(render_task_runner_->BelongsToCurrentThread());
......
......@@ -83,6 +83,9 @@ class MEDIA_BLINK_EXPORT MultibufferDataSource : public DataSource {
// This must be called after the response arrives.
bool IsCorsCrossOrigin() const;
// Returns the CORSMode of the underlying UrlData.
UrlData::CORSMode cors_mode() const;
// Notifies changes in playback state for controlling media buffering
// behavior.
void MediaPlaybackRateChanged(double playback_rate);
......
......@@ -1186,27 +1186,25 @@ void WebMediaPlayerImpl::Paint(cc::PaintCanvas* canvas,
context_support);
}
bool WebMediaPlayerImpl::HasSingleSecurityOrigin() const {
bool WebMediaPlayerImpl::WouldTaintOrigin() const {
if (demuxer_found_hls_) {
// HLS manifests might pull segments from a different origin. We can't know
// for sure, so we conservatively say no here.
return false;
return true;
}
if (data_source_)
return data_source_->HasSingleOrigin();
return true;
}
if (!data_source_)
return false;
bool WebMediaPlayerImpl::WouldTaintOrigin() const {
if (!HasSingleSecurityOrigin()) {
// When the resource is redirected to another origin we think it as
// tainted. This is actually not specified, and is under discussion.
// See https://github.com/whatwg/fetch/issues/737.
// When the resource is redirected to another origin we think it as
// tainted. This is actually not specified, and is under discussion.
// See https://github.com/whatwg/fetch/issues/737.
if (!data_source_->HasSingleOrigin() &&
data_source_->cors_mode() == UrlData::CORS_UNSPECIFIED) {
return true;
}
return data_source_ && data_source_->IsCorsCrossOrigin();
return data_source_->IsCorsCrossOrigin();
}
double WebMediaPlayerImpl::MediaTimeForTimeValue(double timeValue) const {
......
......@@ -341,8 +341,6 @@ class MEDIA_BLINK_EXPORT WebMediaPlayerImpl
void OnPipelineResumed();
void OnDemuxerOpened();
bool HasSingleSecurityOrigin() const;
// Pipeline::Client overrides.
void OnError(PipelineStatus status) override;
void OnEnded() override;
......
/**
* @class RecorderProcessor
* @extends AudioWorkletProcessor
*
* A simple recorder AudioWorkletProcessor. Returns the recorded buffer to the
* node when recording is finished.
*/
class RecorderProcessor extends AudioWorkletProcessor {
/**
* @param {*} options
* @param {number} options.duration A duration to record in seconds.
* @param {number} options.channelCount A channel count to record.
*/
constructor(options) {
super();
this._createdAt = currentTime;
this._elapsed = 0;
this._recordDuration = options.duration || 1;
this._recordChannelCount = options.channelCount || 1;
this._recordBufferLength = sampleRate * this._recordDuration;
this._recordBuffer = [];
for (let i = 0; i < this._recordChannelCount; ++i) {
this._recordBuffer[i] = new Float32Array(this._recordBufferLength);
}
}
process(inputs, outputs) {
if (this._recordBufferLength <= currentFrame) {
this.port.postMessage({
type: 'recordfinished',
recordBuffer: this._recordBuffer
});
return false;
}
// Records the incoming data from |inputs| and also bypasses the data to
// |outputs|.
const input = inputs[0];
const output = outputs[0];
for (let channel = 0; channel < input.length; ++channel) {
const inputChannel = input[channel];
const outputChannel = output[channel];
outputChannel.set(inputChannel);
const buffer = this._recordBuffer[channel];
const capacity = buffer.length - currentFrame;
buffer.set(inputChannel.slice(0, capacity), currentFrame);
}
return true;
}
}
registerProcessor('recorder-processor', RecorderProcessor);
<!DOCTYPE html>
<html>
<head>
<title>
Test if MediaElementAudioSourceNode works for cross-origin redirects with
"cors" request mode.
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/common/get-host-info.sub.js"></script>
</head>
<body>
<script id="layout-test-code">
const audit = Audit.createTaskRunner();
const context = new AudioContext();
context.suspend();
const host_info = get_host_info();
const audioElement = document.createElement('audio');
audioElement.loop = true;
audioElement.crossOrigin = 'anonymous';
const wav =
host_info.HTTPS_ORIGIN + '/webaudio/resources/4ch-440.wav?' +
'pipe=header(access-control-allow-origin,*)';
audioElement.src =
host_info.HTTPS_REMOTE_ORIGIN +
'/fetch/api/resources/redirect.py?location=' +
encodeURIComponent(wav);
let source;
let workletRecorder;
audit.define(
{label: 'setting-up-graph'},
(task, should) => {
source = new MediaElementAudioSourceNode(context, {
mediaElement: audioElement
});
workletRecorder = new AudioWorkletNode(
context, 'recorder-processor', {channelCount: 4});
source.connect(workletRecorder).connect(context.destination);
task.done();
});
// The recorded data from MESN must be non-zero. The source file contains
// 4 channels of sine wave.
audit.define(
{label: 'start-playback-and-capture'},
(task, should) => {
workletRecorder.port.onmessage = (event) => {
if (event.data.type === 'recordfinished') {
for (let i = 0; i < event.data.recordBuffer.length; ++i) {
const channelData = event.data.recordBuffer[i];
should(channelData, `Recorded channel #${i}`)
.notBeConstantValueOf(0);
}
}
task.done();
};
context.resume();
audioElement.play();
});
Promise.all([
context.audioWorklet.addModule('/webaudio/js/worklet-recorder.js')
]).then(() => {
audit.run();
});
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>
Test if MediaElementAudioSourceNode works for cross-origin redirects with
"no-cors" request mode.
</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/webaudio/resources/audit.js"></script>
<script src="/common/get-host-info.sub.js"></script>
</head>
<body>
<script id="layout-test-code">
const audit = Audit.createTaskRunner();
const context = new AudioContext();
context.suspend();
const host_info = get_host_info();
const audioElement = document.createElement('audio');
audioElement.loop = true;
const wav =
host_info.HTTPS_ORIGIN + '/webaudio/resources/4ch-440.wav?' +
'pipe=header(access-control-allow-origin,*)';
audioElement.src =
host_info.HTTPS_REMOTE_ORIGIN +
'/fetch/api/resources/redirect.py?location=' +
encodeURIComponent(wav);
let source;
let workletRecorder;
audit.define(
{label: 'setting-up-graph'},
(task, should) => {
source = new MediaElementAudioSourceNode(context, {
mediaElement: audioElement
});
workletRecorder = new AudioWorkletNode(
context, 'recorder-processor', {channelCount: 4});
source.connect(workletRecorder).connect(context.destination);
task.done();
});
// The recorded data from MESN must be non-zero. The source file contains
// 4 channels of sine wave.
audit.define(
{label: 'start-playback-and-capture'},
(task, should) => {
workletRecorder.port.onmessage = (event) => {
if (event.data.type === 'recordfinished') {
for (let i = 0; i < event.data.recordBuffer.length; ++i) {
const channelData = event.data.recordBuffer[i];
should(channelData, `Recorded channel #${i}`)
.beConstantValueOf(0);
}
}
task.done();
};
context.resume();
audioElement.play();
});
Promise.all([
context.audioWorklet.addModule('/webaudio/js/worklet-recorder.js')
]).then(() => {
audit.run();
});
</script>
</body>
</html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment