Backed out changeset 0c5a4939300c (bug 1454889) for causing frequent Leaks (Bug 1378025). a=backout
authorNarcis Beleuzu <nbeleuzu@mozilla.com>
Mon, 07 May 2018 12:06:25 +0300
changeset 473207 fb435df9797a471419670fe2d053432556959a9d
parent 473206 2a4b0d1e9a8e9c9ffe48d3a6a09e6ac7b78ee71a
child 473208 43e0ab28dfe2589e85ff83349c15eacd624e18b4
child 473840 857a4e93dac83da915dc4f31070bdf5f8022288d
child 473879 36b2570a3efc1c78cafc2aa51a8bae67e6d9b5a9
push id1728
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:12:27 +0000
treeherdermozilla-release@c296fde26f5f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs1454889, 1378025
milestone61.0a1
backs out0c5a4939300c48255f926c49c56bfb66ed17184a
first release with
nightly linux32
fb435df9797a / 61.0a1 / 20180507100128 / files
nightly linux64
fb435df9797a / 61.0a1 / 20180507100128 / files
nightly mac
fb435df9797a / 61.0a1 / 20180507100128 / files
nightly win32
fb435df9797a / 61.0a1 / 20180507100128 / files
nightly win64
fb435df9797a / 61.0a1 / 20180507100128 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changeset 0c5a4939300c (bug 1454889) for causing frequent Leaks (Bug 1378025). a=backout
browser/modules/webrtcUI.jsm
dom/file/nsHostObjectProtocolHandler.cpp
dom/file/nsHostObjectProtocolHandler.h
dom/file/nsHostObjectURI.h
dom/html/HTMLMediaElement.cpp
dom/media/test/mochitest.ini
dom/media/test/test_streams_element_capture_createObjectURL.html
dom/media/test/test_streams_individual_pause.html
dom/media/test/test_video_dimensions.html
dom/url/URL.cpp
dom/url/URL.h
dom/url/URLMainThread.cpp
dom/url/URLMainThread.h
dom/webidl/URL.webidl
--- a/browser/modules/webrtcUI.jsm
+++ b/browser/modules/webrtcUI.jsm
@@ -665,17 +665,17 @@ function prompt(aBrowser, aRequest) {
           let constraints = { video: { mediaSource: type, deviceId: {exact: deviceId } } };
           chromeWin.navigator.mediaDevices.getUserMedia(constraints).then(stream => {
             if (video.deviceId != deviceId) {
               // The user has selected a different device or closed the panel
               // before getUserMedia finished.
               stream.getTracks().forEach(t => t.stop());
               return;
             }
-            video.srcObject = stream;
+            video.src = chromeWin.URL.createObjectURL(stream);
             video.stream = stream;
             doc.getElementById("webRTC-preview").hidden = false;
             video.onloadedmetadata = function(e) {
               video.play();
             };
           });
         };
         menupopup.addEventListener("command", menupopup._commandEventListener);
--- a/dom/file/nsHostObjectProtocolHandler.cpp
+++ b/dom/file/nsHostObjectProtocolHandler.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsHostObjectProtocolHandler.h"
 
+#include "DOMMediaStream.h"
 #include "mozilla/dom/ChromeUtils.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/BlobImpl.h"
 #include "mozilla/dom/IPCBlobUtils.h"
 #include "mozilla/dom/MediaSource.h"
 #include "mozilla/ipc/IPCStreamUtils.h"
@@ -36,36 +37,45 @@ using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 // -----------------------------------------------------------------------
 // Hash table
 struct DataInfo
 {
   enum ObjectType {
     eBlobImpl,
+    eMediaStream,
     eMediaSource
   };
 
   DataInfo(BlobImpl* aBlobImpl, nsIPrincipal* aPrincipal)
     : mObjectType(eBlobImpl)
     , mBlobImpl(aBlobImpl)
     , mPrincipal(aPrincipal)
     , mRevoked(false)
   {}
 
+  DataInfo(DOMMediaStream* aMediaStream, nsIPrincipal* aPrincipal)
+    : mObjectType(eMediaStream)
+    , mMediaStream(aMediaStream)
+    , mPrincipal(aPrincipal)
+    , mRevoked(false)
+  {}
+
   DataInfo(MediaSource* aMediaSource, nsIPrincipal* aPrincipal)
     : mObjectType(eMediaSource)
     , mMediaSource(aMediaSource)
     , mPrincipal(aPrincipal)
     , mRevoked(false)
   {}
 
   ObjectType mObjectType;
 
   RefPtr<BlobImpl> mBlobImpl;
+  RefPtr<DOMMediaStream> mMediaStream;
   RefPtr<MediaSource> mMediaSource;
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCString mStack;
 
   // When a blobURL is revoked, we keep it alive for RELEASING_TIMER
   // milliseconds in order to support pending operations such as navigation,
   // download and so on.
@@ -293,19 +303,20 @@ class BlobURLsReporter final : public ns
               UNITS_COUNT,
               1,
               descString,
               aData);
         }
         continue;
       }
 
-      // Just report the path for the MediaSource.
+      // Just report the path for the DOMMediaStream or MediaSource.
       nsAutoCString path;
-      path = "media-source-urls/";
+      path = iter.UserData()->mObjectType == DataInfo::eMediaSource
+               ? "media-source-urls/" : "dom-media-stream-urls/";
       BuildPath(path, key, info, aAnonymize);
 
       NS_NAMED_LITERAL_CSTRING(desc,
         "An object URL allocated with URL.createObjectURL; the referenced "
         "data cannot be freed until all URLs for it have been explicitly "
         "invalidated with URL.revokeObjectURL.");
 
       aCallback->Callback(EmptyCString(), path, KIND_OTHER, UNITS_COUNT, 1,
@@ -614,16 +625,32 @@ nsHostObjectProtocolHandler::AddDataEntr
   rv = AddDataEntryInternal(aUri, aBlobImpl, aPrincipal);
   NS_ENSURE_SUCCESS(rv, rv);
 
   BroadcastBlobURLRegistration(aUri, aBlobImpl, aPrincipal);
   return NS_OK;
 }
 
 /* static */ nsresult
+nsHostObjectProtocolHandler::AddDataEntry(DOMMediaStream* aMediaStream,
+                                          nsIPrincipal* aPrincipal,
+                                          nsACString& aUri)
+{
+  Init();
+
+  nsresult rv = GenerateURIStringForBlobURL(aPrincipal, aUri);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = AddDataEntryInternal(aUri, aMediaStream, aPrincipal);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+/* static */ nsresult
 nsHostObjectProtocolHandler::AddDataEntry(MediaSource* aMediaSource,
                                           nsIPrincipal* aPrincipal,
                                           nsACString& aUri)
 {
   Init();
 
   nsresult rv = GenerateURIStringForBlobURL(aPrincipal, aUri);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -792,16 +819,19 @@ nsHostObjectProtocolHandler::Traverse(co
     return;
   }
 
   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCallback, "HostObjectProtocolHandler DataInfo.mBlobImpl");
   aCallback.NoteXPCOMChild(res->mBlobImpl);
 
   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCallback, "HostObjectProtocolHandler DataInfo.mMediaSource");
   aCallback.NoteXPCOMChild(res->mMediaSource);
+
+  NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCallback, "HostObjectProtocolHandler DataInfo.mMediaStream");
+  aCallback.NoteXPCOMChild(res->mMediaStream);
 }
 
 // -----------------------------------------------------------------------
 // Protocol handler
 
 NS_IMPL_ISUPPORTS(nsHostObjectProtocolHandler, nsIProtocolHandler,
     nsISupportsWeakReference)
 
@@ -1039,16 +1069,29 @@ NS_GetStreamForBlobURI(nsIURI* aURI, nsI
   blobImpl->CreateInputStream(aStream, rv);
   if (NS_WARN_IF(rv.Failed())) {
     return rv.StealNSResult();
   }
 
   return NS_OK;
 }
 
+nsresult
+NS_GetStreamForMediaStreamURI(nsIURI* aURI, DOMMediaStream** aStream)
+{
+  DataInfo* info = GetDataInfoFromURI(aURI);
+  if (!info || info->mObjectType != DataInfo::eMediaStream) {
+    return NS_ERROR_DOM_BAD_URI;
+  }
+
+  RefPtr<DOMMediaStream> mediaStream = info->mMediaStream;
+  mediaStream.forget(aStream);
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 nsFontTableProtocolHandler::NewURI(const nsACString& aSpec,
                                    const char *aCharset,
                                    nsIURI *aBaseURI,
                                    nsIURI **aResult)
 {
   nsresult rv;
   nsCOMPtr<nsIURI> uri;
@@ -1139,12 +1182,17 @@ bool IsType(nsIURI* aUri, DataInfo::Obje
   return info->mObjectType == aType;
 }
 
 bool IsBlobURI(nsIURI* aUri)
 {
   return IsType(aUri, DataInfo::eBlobImpl);
 }
 
+bool IsMediaStreamURI(nsIURI* aUri)
+{
+  return IsType(aUri, DataInfo::eMediaStream);
+}
+
 bool IsMediaSourceURI(nsIURI* aUri)
 {
   return IsType(aUri, DataInfo::eMediaSource);
 }
--- a/dom/file/nsHostObjectProtocolHandler.h
+++ b/dom/file/nsHostObjectProtocolHandler.h
@@ -18,16 +18,17 @@
 #define BLOBURI_SCHEME "blob"
 #define FONTTABLEURI_SCHEME "moz-fonttable"
 #define RTSPURI_SCHEME "rtsp"
 
 class nsIPrincipal;
 
 namespace mozilla {
 class BlobURLsReporter;
+class DOMMediaStream;
 
 namespace dom {
 class BlobImpl;
 class BlobURLRegistrationData;
 class ContentParent;
 class MediaSource;
 } // namespace dom
 } // namespace mozilla
@@ -59,16 +60,19 @@ public:
   static nsresult GenerateURIStringForBlobURL(nsIPrincipal* aPrincipal,
                                               nsACString &aUri);
 
   // Methods for managing uri->object mapping
   // AddDataEntry creates the URI with the given scheme and returns it in aUri
   static nsresult AddDataEntry(mozilla::dom::BlobImpl* aBlobImpl,
                                nsIPrincipal* aPrincipal,
                                nsACString& aUri);
+  static nsresult AddDataEntry(mozilla::DOMMediaStream* aMediaStream,
+                               nsIPrincipal* aPrincipal,
+                               nsACString& aUri);
   static nsresult AddDataEntry(mozilla::dom::MediaSource* aMediaSource,
                                nsIPrincipal* aPrincipal,
                                nsACString& aUri);
   // IPC only
   static nsresult AddDataEntry(const nsACString& aURI,
                                nsIPrincipal* aPrincipal,
                                mozilla::dom::BlobImpl* aBlobImpl);
 
@@ -107,16 +111,17 @@ public:
   NS_IMETHOD GetScheme(nsACString &result) override;
   NS_IMETHOD NewURI(const nsACString & aSpec,
                     const char *aOriginCharset,
                     nsIURI *aBaseURI,
                     nsIURI **_retval) override;
 };
 
 bool IsBlobURI(nsIURI* aUri);
+bool IsMediaStreamURI(nsIURI* aUri);
 bool IsMediaSourceURI(nsIURI* aUri);
 
 inline bool IsRtspURI(nsIURI* aUri)
 {
   bool isRtsp;
   return NS_SUCCEEDED(aUri->SchemeIs(RTSPURI_SCHEME, &isRtsp)) && isRtsp;
 }
 
@@ -131,11 +136,14 @@ NS_GetBlobForBlobURI(nsIURI* aURI, mozil
 
 extern nsresult
 NS_GetBlobForBlobURISpec(const nsACString& aSpec, mozilla::dom::BlobImpl** aBlob);
 
 extern nsresult
 NS_GetStreamForBlobURI(nsIURI* aURI, nsIInputStream** aStream);
 
 extern nsresult
+NS_GetStreamForMediaStreamURI(nsIURI* aURI, mozilla::DOMMediaStream** aStream);
+
+extern nsresult
 NS_GetSourceForMediaSourceURI(nsIURI* aURI, mozilla::dom::MediaSource** aSource);
 
 #endif /* nsHostObjectProtocolHandler_h */
--- a/dom/file/nsHostObjectURI.h
+++ b/dom/file/nsHostObjectURI.h
@@ -14,18 +14,19 @@
 #include "nsIPrincipal.h"
 #include "nsISerializable.h"
 #include "nsIURIWithPrincipal.h"
 #include "nsSimpleURI.h"
 #include "nsIIPCSerializableURI.h"
 #include "nsProxyRelease.h"
 
 /**
- * These URIs refer to host objects with "blob" scheme. The underlying objects
- * can be Blobs or MediaSources.
+ * These URIs refer to host objects: Blobs, with scheme "blob",
+ * MediaStreams, with scheme "mediastream", and MediaSources, with scheme
+ * "mediasource".
  */
 class nsHostObjectURI final
   : public mozilla::net::nsSimpleURI
   , public nsIURIWithPrincipal
 {
 private:
   explicit nsHostObjectURI(nsIPrincipal* aPrincipal)
     : mozilla::net::nsSimpleURI()
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2092,17 +2092,18 @@ void HTMLMediaElement::SelectResource()
       mLoadingSrc = uri;
       mLoadingSrcTriggeringPrincipal = mSrcAttrTriggeringPrincipal;
       DDLOG(DDLogCategory::Property,
             "loading_src",
             nsCString(NS_ConvertUTF16toUTF8(src)));
       mMediaSource = mSrcMediaSource;
       DDLINKCHILD("mediasource", mMediaSource.get());
       UpdatePreloadAction();
-      if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE && !mMediaSource) {
+      if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE &&
+          !IsMediaStreamURI(mLoadingSrc) && !mMediaSource) {
         // preload:none media, suspend the load here before we make any
         // network requests.
         SuspendLoad();
         return;
       }
 
       rv = LoadResource();
       if (NS_SUCCEEDED(rv)) {
@@ -2421,17 +2422,18 @@ void HTMLMediaElement::LoadFromSourceChi
     DDLOG(DDLogCategory::Property,
           "loading_src",
           nsCString(NS_ConvertUTF16toUTF8(src)));
     mMediaSource = childSrc->GetSrcMediaSource();
     DDLINKCHILD("mediasource", mMediaSource.get());
     NS_ASSERTION(mNetworkState == NETWORK_LOADING,
                  "Network state should be loading");
 
-    if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE && !mMediaSource) {
+    if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE &&
+        !IsMediaStreamURI(mLoadingSrc) && !mMediaSource) {
       // preload:none media, suspend the load here before we make any
       // network requests.
       SuspendLoad();
       return;
     }
 
     if (NS_SUCCEEDED(LoadResource())) {
       return;
@@ -2570,16 +2572,30 @@ HTMLMediaElement::LoadResource()
     // Clone it.
     // TODO: remove the cast by storing ChannelMediaDecoder in the URI table.
     nsresult rv = InitializeDecoderAsClone(
       static_cast<ChannelMediaDecoder*>(other->mDecoder.get()));
     if (NS_SUCCEEDED(rv))
       return rv;
   }
 
+  if (IsMediaStreamURI(mLoadingSrc)) {
+    RefPtr<DOMMediaStream> stream;
+    nsresult rv = NS_GetStreamForMediaStreamURI(mLoadingSrc, getter_AddRefs(stream));
+    if (NS_FAILED(rv)) {
+      nsAutoString spec;
+      GetCurrentSrc(spec);
+      const char16_t* params[] = { spec.get() };
+      ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
+      return MediaResult(rv, "MediaLoadInvalidURI");
+    }
+    SetupSrcMediaStreamPlayback(stream);
+    return NS_OK;
+  }
+
   if (mMediaSource) {
     MediaDecoderInit decoderInit(
       this,
       mMuted ? 0.0 : mVolume,
       mPreservesPitch,
       mPlaybackRate,
       mPreloadAction == HTMLMediaElement::PRELOAD_METADATA,
       mHasSuspendTaint,
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -1128,16 +1128,19 @@ skip-if = toolkit == 'android' # bug 137
 [test_streams_autoplay.html]
 tags=msg capturestream
 [test_streams_capture_origin.html]
 skip-if = toolkit == 'android' # bug 1372457
 tags=msg capturestream
 [test_streams_element_capture.html]
 skip-if = toolkit == 'android' # bug 1372457
 tags=msg capturestream
+[test_streams_element_capture_createObjectURL.html]
+skip-if = android_version == '15' || android_version == '17' || (android_version == '19' && debug) # android(bug 1232305)
+tags=msg capturestream
 [test_streams_element_capture_playback.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 tags=msg capturestream
 [test_streams_element_capture_reset.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 tags=msg capturestream
 [test_streams_gc.html]
 skip-if = android_version == '15' || android_version == '17' || (android_version == '19' && debug) # android(bug 1232305)
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_streams_element_capture_createObjectURL.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test that a MediaStream captured from one element plays back in another</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var manager = new MediaTestManager;
+
+function checkDrawImage(vout) {
+  var canvas = document.createElement("canvas");
+  var ctx = canvas.getContext("2d");
+  ctx.drawImage(vout, 0, 0);
+  var imgData = ctx.getImageData(0, 0, 1, 1);
+  is(imgData.data[3], 255, "Check video frame pixel has been drawn");
+}
+
+function isGreaterThanOrEqualEps(a, b, msg) {
+  ok(a >= b - 0.01,
+     "Got " + a + ", expected at least " + b + "; " + msg);
+}
+
+function startTest(test, token) {
+  manager.started(token);
+
+  var v = document.createElement('video');
+  var vout = document.createElement('video');
+  vout.token = token;
+
+  v.src = test.name;
+  v.preload = "metadata"
+  var stream;
+
+  var checkEnded = function() {
+    is(stream.currentTime, vout.currentTime, test.name + " stream final currentTime");
+    if (test.duration) {
+      isGreaterThanOrEqualEps(vout.currentTime, test.duration,
+         test.name + " current time at end");
+    }
+    is(vout.readyState, vout.HAVE_CURRENT_DATA, test.name + " checking readyState");
+    ok(vout.ended, test.name + " checking playback has ended");
+    if (test.type.match(/^video/)) {
+      checkDrawImage(vout);
+    }
+    vout.remove();
+    URL.revokeObjectURL(vout.src);
+    manager.finished(vout.token);
+  };
+  vout.addEventListener("ended", checkEnded);
+
+  document.body.appendChild(vout);
+  v.onloadedmetadata = function () {
+    stream = v.mozCaptureStreamUntilEnded();
+    is(stream.currentTime, 0, test.name + " stream initial currentTime");
+    vout.src = URL.createObjectURL(stream);
+    v.play();
+    vout.play();
+  };
+}
+
+SpecialPowers.pushPrefEnv(
+  { "set": [["privacy.reduceTimerPrecision", false]]},
+  function() {
+    manager.runTests([getPlayableVideo(gSmallTests)], startTest);
+  });
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/test/test_streams_individual_pause.html
+++ b/dom/media/test/test_streams_individual_pause.html
@@ -26,18 +26,19 @@ async function startTest() {
   try {
     // This test expects fake devices so that the video color will change
     // over time, explicitly request fakes.
     await pushGetUserMediaTestPrefs({fakeAudio: true, fakeVideo: true});
     let stream = await navigator.mediaDevices.getUserMedia({video: true});
     let video1 = document.getElementById('video1');
     let video2 = document.getElementById('video2');
 
-    video1.srcObject = stream;
-    video2.srcObject = stream;
+    let src = URL.createObjectURL(stream);
+    video1.src = src;
+    video2.src = src;
 
     video1.onplaying = () => video1.pause();
 
     let v1PausedImageData;
     let v2PausedImageData;
 
     video1.onpause = function() {
       v1PausedImageData = getVideoImagePixelData(video1);
--- a/dom/media/test/test_video_dimensions.html
+++ b/dom/media/test/test_video_dimensions.html
@@ -49,17 +49,17 @@ var startTest = function(test, token) {
 
     numVideoElementsFinished += 1;
     if (v === v1) {
       removeNodeAndSource(v1);
       v2.load();
     }
 
     if (v === v2) {
-      vout.srcObject = v2.mozCaptureStreamUntilEnded();
+      vout.src = URL.createObjectURL(v2.mozCaptureStreamUntilEnded());
       v2.play();
       vout.play();
     }
 
     if (numVideoElementsFinished === 3) {
       removeNodeAndSource(v2);
       removeNodeAndSource(vout);
       manager.finished(token);
--- a/dom/url/URL.cpp
+++ b/dom/url/URL.cpp
@@ -59,16 +59,27 @@ URL::CreateObjectURL(const GlobalObject&
   if (NS_IsMainThread()) {
     URLMainThread::CreateObjectURL(aGlobal, aBlob, aResult, aRv);
   } else {
     URLWorker::CreateObjectURL(aGlobal, aBlob, aResult, aRv);
   }
 }
 
 void
+URL::CreateObjectURL(const GlobalObject& aGlobal, DOMMediaStream& aStream,
+                     nsAString& aResult, ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  DeprecationWarning(aGlobal, nsIDocument::eURLCreateObjectURL_MediaStream);
+
+  URLMainThread::CreateObjectURL(aGlobal, aStream, aResult, aRv);
+}
+
+void
 URL::CreateObjectURL(const GlobalObject& aGlobal, MediaSource& aSource,
                      nsAString& aResult, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   URLMainThread::CreateObjectURL(aGlobal, aSource, aResult, aRv);
 }
 
 void
--- a/dom/url/URL.h
+++ b/dom/url/URL.h
@@ -14,16 +14,17 @@
 #include "nsWrapperCache.h"
 
 class nsISupports;
 class nsIURI;
 
 namespace mozilla {
 
 class ErrorResult;
+class DOMMediaStream;
 
 namespace dom {
 
 class Blob;
 class MediaSource;
 class GlobalObject;
 
 class URL : public URLSearchParamsObserver
@@ -56,16 +57,20 @@ public:
                     const nsAString& aBase, ErrorResult& aRv);
 
 
   static void
   CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
                   nsAString& aResult, ErrorResult& aRv);
 
   static void
+  CreateObjectURL(const GlobalObject& aGlobal, DOMMediaStream& aStream,
+                  nsAString& aResult, ErrorResult& aRv);
+
+  static void
   CreateObjectURL(const GlobalObject& aGlobal, MediaSource& aSource,
                   nsAString& aResult, ErrorResult& aRv);
 
   static void
   RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aURL,
                   ErrorResult& aRv);
 
   static bool
--- a/dom/url/URLMainThread.cpp
+++ b/dom/url/URLMainThread.cpp
@@ -13,16 +13,44 @@
 #include "nsHostObjectProtocolHandler.h"
 #include "nsIURL.h"
 #include "nsIURIMutator.h"
 #include "nsNetUtil.h"
 
 namespace mozilla {
 namespace dom {
 
+namespace {
+
+template<typename T>
+void
+CreateObjectURLInternal(const GlobalObject& aGlobal, T aObject,
+                        nsAString& aResult, ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+  if (NS_WARN_IF(!global)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  nsCOMPtr<nsIPrincipal> principal =
+    nsContentUtils::ObjectPrincipal(aGlobal.Get());
+
+  nsAutoCString url;
+  aRv = nsHostObjectProtocolHandler::AddDataEntry(aObject, principal, url);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  global->RegisterHostObjectURI(url);
+  CopyASCIItoUTF16(url, aResult);
+}
+
+} // anonymous namespace
+
 /* static */ already_AddRefed<URLMainThread>
 URLMainThread::Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
                            const Optional<nsAString>& aBase, ErrorResult& aRv)
 {
   if (aBase.WasPassed()) {
     return Constructor(aGlobal.GetAsSupports(), aURL, aBase.Value(), aRv);
   }
 
@@ -67,34 +95,26 @@ URLMainThread::Constructor(nsISupports* 
   return url.forget();
 }
 
 /* static */ void
 URLMainThread::CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
                                nsAString& aResult, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
-  if (NS_WARN_IF(!global)) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return;
-  }
+  CreateObjectURLInternal(aGlobal, aBlob.Impl(), aResult, aRv);
+}
 
-  nsCOMPtr<nsIPrincipal> principal =
-    nsContentUtils::ObjectPrincipal(aGlobal.Get());
-
-  nsAutoCString url;
-  aRv = nsHostObjectProtocolHandler::AddDataEntry(aBlob.Impl(), principal, url);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return;
-  }
-
-  global->RegisterHostObjectURI(url);
-  CopyASCIItoUTF16(url, aResult);
+/* static */ void
+URLMainThread::CreateObjectURL(const GlobalObject& aGlobal,
+                               DOMMediaStream& aStream,
+                               nsAString& aResult, ErrorResult& aRv)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  CreateObjectURLInternal(aGlobal, &aStream, aResult, aRv);
 }
 
 /* static */ void
 URLMainThread::CreateObjectURL(const GlobalObject& aGlobal,
                                MediaSource& aSource,
                                nsAString& aResult, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/url/URLMainThread.h
+++ b/dom/url/URLMainThread.h
@@ -28,16 +28,20 @@ public:
   Constructor(nsISupports* aParent, const nsAString& aURL, nsIURI* aBase,
               ErrorResult& aRv);
 
   static void
   CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
                   nsAString& aResult, ErrorResult& aRv);
 
   static void
+  CreateObjectURL(const GlobalObject& aGlobal, DOMMediaStream& aStream,
+                  nsAString& aResult, ErrorResult& aRv);
+
+  static void
   CreateObjectURL(const GlobalObject& aGlobal, MediaSource& aSource,
                   nsAString& aResult, ErrorResult& aRv);
 
   static void
   RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aURL,
                   ErrorResult& aRv);
 
   static bool
--- a/dom/webidl/URL.webidl
+++ b/dom/webidl/URL.webidl
@@ -1,16 +1,17 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  * The origins of this IDL file are
  * http://url.spec.whatwg.org/#api
  * http://dev.w3.org/2006/webapi/FileAPI/#creating-revoking
+ * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#url
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 [Constructor(USVString url, optional USVString base),
  Exposed=(Window,Worker,WorkerDebugger)]
 interface URL {
@@ -39,16 +40,18 @@ interface URL {
 
   USVString toJSON();
 };
 
 partial interface URL {
   [Throws]
   static DOMString createObjectURL(Blob blob);
   [Throws]
+  static DOMString createObjectURL(MediaStream stream);
+  [Throws]
   static void revokeObjectURL(DOMString url);
   [ChromeOnly, Throws]
   static boolean isValidURL(DOMString url);
 };
 
 // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html
 partial interface URL {
   [Throws]