Bug 489415. Handle media streams with data from different principals in the media cache. r=bzbarsky,doublec

This commit is contained in:
Robert O'Callahan 2009-05-14 09:52:50 +12:00
Родитель 14352e6712
Коммит 0442e8caf5
8 изменённых файлов: 301 добавлений и 45 удалений

Просмотреть файл

@ -172,11 +172,15 @@ void nsHTMLMediaElement::QueueLoadFromSourceTask()
NS_DispatchToMainThread(event);
}
class nsHTMLMediaElement::MediaLoadListener : public nsIStreamListener
class nsHTMLMediaElement::MediaLoadListener : public nsIStreamListener,
public nsIChannelEventSink,
public nsIInterfaceRequestor
{
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSICHANNELEVENTSINK
NS_DECL_NSIINTERFACEREQUESTOR
public:
MediaLoadListener(nsHTMLMediaElement* aElement)
@ -190,7 +194,9 @@ private:
nsCOMPtr<nsIStreamListener> mNextListener;
};
NS_IMPL_ISUPPORTS2(nsHTMLMediaElement::MediaLoadListener, nsIRequestObserver, nsIStreamListener)
NS_IMPL_ISUPPORTS4(nsHTMLMediaElement::MediaLoadListener, nsIRequestObserver,
nsIStreamListener, nsIChannelEventSink,
nsIInterfaceRequestor)
NS_IMETHODIMP nsHTMLMediaElement::MediaLoadListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
{
@ -252,6 +258,21 @@ NS_IMETHODIMP nsHTMLMediaElement::MediaLoadListener::OnDataAvailable(nsIRequest*
return mNextListener->OnDataAvailable(aRequest, aContext, aStream, aOffset, aCount);
}
NS_IMETHODIMP nsHTMLMediaElement::MediaLoadListener::OnChannelRedirect(nsIChannel* aOldChannel,
nsIChannel* aNewChannel,
PRUint32 aFlags)
{
nsCOMPtr<nsIChannelEventSink> sink = do_QueryInterface(mNextListener);
if (sink)
return sink->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
return NS_OK;
}
NS_IMETHODIMP nsHTMLMediaElement::MediaLoadListener::GetInterface(const nsIID & aIID, void **aResult)
{
return QueryInterface(aIID, aResult);
}
NS_IMPL_ADDREF_INHERITED(nsHTMLMediaElement, nsGenericHTMLElement)
NS_IMPL_RELEASE_INHERITED(nsHTMLMediaElement, nsGenericHTMLElement)
@ -513,8 +534,10 @@ nsresult nsHTMLMediaElement::LoadResource(nsIURI* aURI)
// The listener holds a strong reference to us. This creates a reference
// cycle which is manually broken in the listener's OnStartRequest method
// after it is finished with the element.
nsCOMPtr<nsIStreamListener> loadListener = new MediaLoadListener(this);
nsRefPtr<MediaLoadListener> loadListener = new MediaLoadListener(this);
if (!loadListener) return NS_ERROR_OUT_OF_MEMORY;
mChannel->SetNotificationCallbacks(loadListener);
nsCOMPtr<nsIStreamListener> listener;
if (ShouldCheckAllowOrigin()) {

Просмотреть файл

@ -41,6 +41,8 @@
#include "nsTArray.h"
#include "nsAutoLock.h"
#include "nsIPrincipal.h"
#include "nsCOMPtr.h"
/**
* Media applications want fast, "on demand" random access to media data,
@ -186,6 +188,13 @@
* we must not acquire any nsMediaDecoder locks or nsMediaStream locks
* while holding the nsMediaCache lock. But it's OK to hold those locks
* and then get the nsMediaCache lock.
*
* nsMediaCache associates a principal with each stream. CacheClientSeek
* can trigger new HTTP requests; due to redirects to other domains,
* each HTTP load can return data with a different principal. This
* principal must be passed to NotifyDataReceived, and nsMediaCache
* will detect when different principals are associated with data in the
* same stream, and replace them with a null principal.
*/
class nsMediaCache;
// defined in nsMediaStream.h
@ -215,7 +224,8 @@ public:
mStreamOffset(0), mStreamLength(-1), mPlaybackBytesPerSecond(10000),
mPinCount(0), mCurrentMode(MODE_PLAYBACK), mClosed(PR_FALSE),
mIsSeekable(PR_FALSE), mCacheSuspended(PR_FALSE),
mMetadataInPartialBlockBuffer(PR_FALSE) {}
mMetadataInPartialBlockBuffer(PR_FALSE),
mUsingNullPrincipal(PR_FALSE) {}
~nsMediaCacheStream();
// Set up this stream with the cache. Can fail on OOM. Must be called
@ -236,6 +246,8 @@ public:
void Close();
// This returns true when the stream has been closed
PRBool IsClosed() const { return mClosed; }
// Get the principal for this stream.
nsIPrincipal* GetCurrentPrincipal() { return mPrincipal; }
// These callbacks are called on the main thread by the client
// when data has been received via the channel.
@ -263,7 +275,9 @@ public:
// the starting offset is known via NotifyDataStarted or because
// the cache requested the offset in
// nsMediaChannelStream::CacheClientSeek, or because it defaulted to 0.
void NotifyDataReceived(PRInt64 aSize, const char* aData);
// We pass in the principal that was used to load this data.
void NotifyDataReceived(PRInt64 aSize, const char* aData,
nsIPrincipal* aPrincipal);
// Notifies the cache that the channel has closed with the given status.
void NotifyDataEnded(nsresult aStatus);
@ -363,9 +377,12 @@ private:
// This is used to NotifyAll to wake up threads that might be
// blocked on reading from this stream.
void CloseInternal(nsAutoMonitor* aMonitor);
// Update mPrincipal given that data has been received from aPrincipal
void UpdatePrincipal(nsIPrincipal* aPrincipal);
// This field is main-thread-only.
nsMediaChannelStream* mClient;
// These fields are main-thread-only.
nsMediaChannelStream* mClient;
nsCOMPtr<nsIPrincipal> mPrincipal;
// All other fields are all protected by the cache's monitor and
// can be accessed by by any thread.
@ -401,6 +418,9 @@ private:
PRPackedBool mCacheSuspended;
// true if some data in mPartialBlockBuffer has been read as metadata
PRPackedBool mMetadataInPartialBlockBuffer;
// true if mPrincipal is a null principal because we saw data from
// multiple origins
PRPackedBool mUsingNullPrincipal;
// Data received for the block containing mChannelOffset. Data needs
// to wait here so we can write back a complete block. The first

Просмотреть файл

@ -44,6 +44,8 @@
#include "nsIPrincipal.h"
#include "nsIURI.h"
#include "nsIStreamListener.h"
#include "nsIChannelEventSink.h"
#include "nsIInterfaceRequestor.h"
#include "prlock.h"
#include "nsMediaCache.h"
#include "nsTimeStamp.h"
@ -149,8 +151,6 @@ public:
}
// The following can be called on the main thread only:
// Get the current principal for the channel
already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
// Get the decoder
nsMediaDecoder* Decoder() { return mDecoder; }
// Close the stream, stop any listeners, channels, etc.
@ -161,6 +161,8 @@ public:
virtual void Suspend() = 0;
// Resume any downloads that have been suspended.
virtual void Resume() = 0;
// Get the current principal for the channel
virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal() = 0;
// These methods are called off the main thread.
// The mode is initially MODE_PLAYBACK.
@ -317,6 +319,7 @@ public:
virtual nsresult Close();
virtual void Suspend();
virtual void Resume();
virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
// Return PR_TRUE if the stream has been closed.
PRBool IsClosed() const { return mCacheStream.IsClosed(); }
@ -337,13 +340,18 @@ public:
virtual PRBool IsSuspendedByCache();
protected:
class Listener : public nsIStreamListener {
class Listener : public nsIStreamListener,
public nsIInterfaceRequestor,
public nsIChannelEventSink
{
public:
Listener(nsMediaChannelStream* aStream) : mStream(aStream) {}
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSICHANNELEVENTSINK
NS_DECL_NSIINTERFACEREQUESTOR
void Revoke() { mStream = nsnull; }
@ -358,10 +366,12 @@ protected:
nsresult OnDataAvailable(nsIRequest* aRequest,
nsIInputStream* aStream,
PRUint32 aCount);
nsresult OnChannelRedirect(nsIChannel* aOld, nsIChannel* aNew, PRUint32 aFlags);
// Opens the channel, using an HTTP byte range request to start at aOffset
// if possible. Main thread only.
nsresult OpenChannel(nsIStreamListener** aStreamListener, PRInt64 aOffset);
void SetupChannelHeaders();
// Closes the channel. Main thread only.
void CloseChannel();
@ -373,9 +383,9 @@ protected:
PRUint32 *aWriteCount);
// Main thread access only
PRInt64 mLastSeekOffset;
nsRefPtr<Listener> mListener;
PRUint32 mSuspendCount;
PRPackedBool mSeeking;
// Any thread access
nsMediaCacheStream mCacheStream;

Просмотреть файл

@ -1390,10 +1390,47 @@ nsMediaCacheStream::NotifyDataStarted(PRInt64 aOffset)
}
void
nsMediaCacheStream::NotifyDataReceived(PRInt64 aSize, const char* aData)
nsMediaCacheStream::UpdatePrincipal(nsIPrincipal* aPrincipal)
{
if (!mPrincipal) {
NS_ASSERTION(!mUsingNullPrincipal, "Are we using a null principal or not?");
if (mUsingNullPrincipal) {
// Don't let mPrincipal be set to anything
return;
}
mPrincipal = aPrincipal;
return;
}
if (mPrincipal == aPrincipal) {
// Common case
NS_ASSERTION(!mUsingNullPrincipal, "We can't receive data from a null principal");
return;
}
if (mUsingNullPrincipal) {
// We've already fallen back to a null principal, so nothing more
// to do.
return;
}
PRBool equal;
nsresult rv = mPrincipal->Equals(aPrincipal, &equal);
if (NS_SUCCEEDED(rv) && equal)
return;
// Principals are not equal, so set mPrincipal to a null principal.
mPrincipal = do_CreateInstance("@mozilla.org/nullprincipal;1");
mUsingNullPrincipal = PR_TRUE;
}
void
nsMediaCacheStream::NotifyDataReceived(PRInt64 aSize, const char* aData,
nsIPrincipal* aPrincipal)
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
UpdatePrincipal(aPrincipal);
nsAutoMonitor mon(gMediaCache->Monitor());
PRInt64 size = aSize;
const char* data = aData;

Просмотреть файл

@ -65,7 +65,7 @@ using mozilla::TimeStamp;
nsMediaChannelStream::nsMediaChannelStream(nsMediaDecoder* aDecoder,
nsIChannel* aChannel, nsIURI* aURI)
: nsMediaStream(aDecoder, aChannel, aURI),
mSuspendCount(0), mSeeking(PR_FALSE),
mLastSeekOffset(0), mSuspendCount(0),
mCacheStream(this),
mLock(nsAutoLock::NewLock("media.channel.stream")),
mCacheSuspendCount(0)
@ -89,7 +89,9 @@ nsMediaChannelStream::~nsMediaChannelStream()
// disconnect the old listener from the nsMediaChannelStream and hook up
// a new listener, so notifications from the old channel are discarded
// and don't confuse us.
NS_IMPL_ISUPPORTS2(nsMediaChannelStream::Listener, nsIRequestObserver, nsIStreamListener)
NS_IMPL_ISUPPORTS4(nsMediaChannelStream::Listener,
nsIRequestObserver, nsIStreamListener, nsIChannelEventSink,
nsIInterfaceRequestor)
nsresult
nsMediaChannelStream::Listener::OnStartRequest(nsIRequest* aRequest,
@ -122,6 +124,22 @@ nsMediaChannelStream::Listener::OnDataAvailable(nsIRequest* aRequest,
return mStream->OnDataAvailable(aRequest, aStream, aCount);
}
nsresult
nsMediaChannelStream::Listener::OnChannelRedirect(nsIChannel* aOldChannel,
nsIChannel* aNewChannel,
PRUint32 aFlags)
{
if (!mStream)
return NS_OK;
return mStream->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
}
nsresult
nsMediaChannelStream::Listener::GetInterface(const nsIID & aIID, void **aResult)
{
return QueryInterface(aIID, aResult);
}
nsresult
nsMediaChannelStream::OnStartRequest(nsIRequest* aRequest)
{
@ -148,7 +166,7 @@ nsMediaChannelStream::OnStartRequest(nsIRequest* aRequest)
ranges);
PRBool acceptsRanges = ranges.EqualsLiteral("bytes");
if (!mSeeking) {
if (mLastSeekOffset == 0) {
// Look for duration headers from known Ogg content systems. In the case
// of multiple options for obtaining the duration the order of precedence is;
// 1) The Media resource metadata if possible (done by the decoder itself).
@ -172,12 +190,12 @@ nsMediaChannelStream::OnStartRequest(nsIRequest* aRequest)
PRUint32 responseStatus = 0;
hc->GetResponseStatus(&responseStatus);
if (mSeeking && responseStatus == HTTP_OK_CODE) {
if (mLastSeekOffset > 0 && responseStatus == HTTP_OK_CODE) {
// If we get an OK response but we were seeking, we have to assume
// that seeking doesn't work. We also need to tell the cache that
// it's getting data for the start of the stream.
mCacheStream.NotifyDataStarted(0);
} else if (!mSeeking &&
} else if (mLastSeekOffset == 0 &&
(responseStatus == HTTP_OK_CODE ||
responseStatus == HTTP_PARTIAL_RESPONSE_CODE)) {
// We weren't seeking and got a valid response status,
@ -243,6 +261,20 @@ nsMediaChannelStream::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
return NS_OK;
}
nsresult
nsMediaChannelStream::OnChannelRedirect(nsIChannel* aOld, nsIChannel* aNew,
PRUint32 aFlags)
{
mChannel = aNew;
SetupChannelHeaders();
return NS_OK;
}
struct CopySegmentClosure {
nsCOMPtr<nsIPrincipal> mPrincipal;
nsMediaChannelStream* mStream;
};
NS_METHOD
nsMediaChannelStream::CopySegmentToCache(nsIInputStream *aInStream,
void *aClosure,
@ -251,8 +283,9 @@ nsMediaChannelStream::CopySegmentToCache(nsIInputStream *aInStream,
PRUint32 aCount,
PRUint32 *aWriteCount)
{
nsMediaChannelStream* stream = static_cast<nsMediaChannelStream*>(aClosure);
stream->mCacheStream.NotifyDataReceived(aCount, aFromSegment);
CopySegmentClosure* closure = static_cast<CopySegmentClosure*>(aClosure);
closure->mStream->mCacheStream.NotifyDataReceived(aCount, aFromSegment,
closure->mPrincipal);
*aWriteCount = aCount;
return NS_OK;
}
@ -269,10 +302,17 @@ nsMediaChannelStream::OnDataAvailable(nsIRequest* aRequest,
mChannelStatistics.AddBytes(aCount);
}
CopySegmentClosure closure;
nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
if (secMan && mChannel) {
secMan->GetChannelPrincipal(mChannel, getter_AddRefs(closure.mPrincipal));
}
closure.mStream = this;
PRUint32 count = aCount;
while (count > 0) {
PRUint32 read;
nsresult rv = aStream->ReadSegments(CopySegmentToCache, this, count,
nsresult rv = aStream->ReadSegments(CopySegmentToCache, &closure, count,
&read);
if (NS_FAILED(rv))
return rv;
@ -310,7 +350,7 @@ nsresult nsMediaChannelStream::OpenChannel(nsIStreamListener** aStreamListener,
*aStreamListener = nsnull;
}
mSeeking = aOffset != 0;
mLastSeekOffset = aOffset;
mListener = new Listener(this);
NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
@ -319,6 +359,8 @@ nsresult nsMediaChannelStream::OpenChannel(nsIStreamListener** aStreamListener,
*aStreamListener = mListener;
NS_ADDREF(*aStreamListener);
} else {
mChannel->SetNotificationCallbacks(mListener.get());
nsCOMPtr<nsIStreamListener> listener = mListener.get();
// Ensure that if we're loading cross domain, that the server is sending
@ -342,18 +384,7 @@ nsresult nsMediaChannelStream::OpenChannel(nsIStreamListener** aStreamListener,
NS_ENSURE_SUCCESS(rv, rv);
}
// Use a byte range request from the start of the resource.
// This enables us to detect if the stream supports byte range
// requests, and therefore seeking, early.
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
if (hc) {
nsCAutoString rangeString("bytes=");
rangeString.AppendInt(aOffset);
rangeString.Append("-");
hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, PR_FALSE);
} else {
NS_ASSERTION(aOffset == 0, "Don't know how to seek on this channel type");
}
SetupChannelHeaders();
nsresult rv = mChannel->AsyncOpen(listener, nsnull);
NS_ENSURE_SUCCESS(rv, rv);
@ -362,6 +393,23 @@ nsresult nsMediaChannelStream::OpenChannel(nsIStreamListener** aStreamListener,
return NS_OK;
}
void nsMediaChannelStream::SetupChannelHeaders()
{
// Always use a byte range request even if we're reading from the start
// of the resource.
// This enables us to detect if the stream supports byte range
// requests, and therefore seeking, early.
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
if (hc) {
nsCAutoString rangeString("bytes=");
rangeString.AppendInt(mLastSeekOffset);
rangeString.Append("-");
hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, PR_FALSE);
} else {
NS_ASSERTION(mLastSeekOffset == 0, "Don't know how to seek on this channel type");
}
}
nsresult nsMediaChannelStream::Close()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
@ -371,6 +419,14 @@ nsresult nsMediaChannelStream::Close()
return NS_OK;
}
already_AddRefed<nsIPrincipal> nsMediaChannelStream::GetCurrentPrincipal()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
nsCOMPtr<nsIPrincipal> principal = mCacheStream.GetCurrentPrincipal();
return principal.forget();
}
void nsMediaChannelStream::CloseChannel()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
@ -616,6 +672,7 @@ public:
virtual nsresult Close();
virtual void Suspend() {}
virtual void Resume() {}
virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
// These methods are called off the main thread.
@ -757,6 +814,18 @@ nsresult nsMediaFileStream::Close()
return NS_OK;
}
already_AddRefed<nsIPrincipal> nsMediaFileStream::GetCurrentPrincipal()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
nsCOMPtr<nsIPrincipal> principal;
nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
if (!secMan || !mChannel)
return nsnull;
secMan->GetChannelPrincipal(mChannel, getter_AddRefs(principal));
return principal.forget();
}
nsresult nsMediaFileStream::Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes)
{
nsAutoLock lock(mLock);
@ -831,18 +900,6 @@ nsMediaStream::Open(nsMediaDecoder* aDecoder, nsIURI* aURI,
return NS_OK;
}
already_AddRefed<nsIPrincipal> nsMediaStream::GetCurrentPrincipal()
{
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
nsCOMPtr<nsIPrincipal> principal;
nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
if (!secMan || !mChannel)
return nsnull;
secMan->GetChannelPrincipal(mChannel, getter_AddRefs(principal));
return principal.forget();
}
void nsMediaStream::MoveLoadsToBackground() {
NS_ASSERTION(!mLoadInBackground, "Why are you calling this more than once?");
mLoadInBackground = PR_TRUE;

Просмотреть файл

@ -61,6 +61,7 @@ _TEST_FILES = \
ifdef MOZ_OGG
_TEST_FILES += \
dynamic_redirect.sjs \
test_access_control.html \
file_access_controls.html \
test_bug448534.html \
@ -82,6 +83,7 @@ _TEST_FILES += \
test_info_leak.html \
test_onloadedmetadata.html \
test_load_candidates.html \
test_mixed_principals.html \
test_play.html \
test_progress1.html \
test_progress3.html \

Просмотреть файл

@ -0,0 +1,39 @@
// Return seek.ogv file content for the first request with a given key.
// All subsequent requests return a redirect to a different-origin resource.
function handleRequest(request, response)
{
var key = request.queryString.match(/^key=(.*)$/);
if (getState(key[1]) == "redirect") {
var origin = request.host == "localhost" ? "example.org" : "localhost:8888";
response.setStatusLine(request.httpVersion, 303, "See Other");
response.setHeader("Location", "http://" + origin + "/tests/content/media/video/test/seek.ogv");
response.setHeader("Content-Type", "text/html");
return;
}
setState(key[1], "redirect");
var file = Components.classes["@mozilla.org/file/directory_service;1"].
getService(Components.interfaces.nsIProperties).
get("CurWorkD", Components.interfaces.nsILocalFile);
var fis = Components.classes['@mozilla.org/network/file-input-stream;1'].
createInstance(Components.interfaces.nsIFileInputStream);
var bis = Components.classes["@mozilla.org/binaryinputstream;1"].
createInstance(Components.interfaces.nsIBinaryInputStream);
var paths = "tests/content/media/video/test/seek.ogv";
var split = paths.split("/");
for(var i = 0; i < split.length; ++i) {
file.append(split[i]);
}
fis.init(file, -1, -1, false);
dump("file=" + file + "\n");
bis.setInputStream(fis);
var bytes = bis.readBytes(bis.available());
response.setStatusLine(request.httpVersion, 206, "Partial Content");
response.setHeader("Content-Range", "bytes 0-" + (bytes.length - 1) + "/" + bytes.length);
response.setHeader("Content-Length", ""+bytes.length, false);
response.setHeader("Content-Type", "video/ogg", false);
response.write(bytes, bytes.length);
bis.close();
}

Просмотреть файл

@ -0,0 +1,68 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=489415
-->
<head>
<title>Test for Bug 489415</title>
<script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=489415">Mozilla Bug 489415</a>
<p id="display"></p>
<video id="v1" autoplay onended="loaded('v1')"></video>
<video id="v2" autoplay onended="loaded('v2')"></video>
<pre id="test">
<script type="text/javascript">
SimpleTest.waitForExplicitFinish();
var v1 = document.getElementById("v1");
var v2 = document.getElementById("v2");
var count = 0;
function loaded(id) {
var c = document.createElement("canvas");
var ctx = c.getContext("2d");
var v = document.getElementById(id);
ctx.drawImage(v, 0, 0);
try {
c.toDataURL();
ok(false, "Failed to throw exception in toDataURL for " + id);
} catch (ex) {
ok(true, "Threw exception in toDataURL for " + id);
}
if (++count == 2) {
SimpleTest.finish();
}
}
// Generate a random key. The first load with that key will return
// data, the second and subsequent loads with that key will return a redirect
// to a different origin ('localhost:8888' will be redirected to 'example.org',
// and 'example.org' will be redirected to 'localhost:8888'). We rely on the
// fact that Ogg will do a seek to the end of the resource, triggering a new
// load with the same key which will return a same-origin resource.
// Loading data from two different origins should be detected by the media
// cache and result in a null principal so that the canvas usage above fails.
var key = Math.floor(Math.random()*100000000);
// In v1, try loading from same-origin first and then getting redirected to
// another origin.
v1.src = "http://localhost:8888/tests/content/media/video/test/dynamic_redirect.sjs?key=v1_" + key;
v1.load();
// In v2, try loading cross-origin first and then getting redirected to
// our origin.
v2.src = "http://example.org/tests/content/media/video/test/dynamic_redirect.sjs?key=v2_" + key;
v2.load();
</script>
</pre>
</body>
</html>