LCOV - code coverage report
Current view: top level - dom/media - MediaRecorder.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 1 718 0.1 %
Date: 2017-07-14 16:53:18 Functions: 0 98 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim:set ts=2 sw=2 sts=2 et cindent: */
       3             : /* This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "MediaRecorder.h"
       8             : #include "AudioNodeEngine.h"
       9             : #include "AudioNodeStream.h"
      10             : #include "DOMMediaStream.h"
      11             : #include "EncodedBufferCache.h"
      12             : #include "MediaDecoder.h"
      13             : #include "MediaEncoder.h"
      14             : #include "mozilla/StaticPtr.h"
      15             : #include "mozilla/DOMEventTargetHelper.h"
      16             : #include "mozilla/Preferences.h"
      17             : #include "mozilla/dom/AudioStreamTrack.h"
      18             : #include "mozilla/dom/BlobEvent.h"
      19             : #include "mozilla/dom/File.h"
      20             : #include "mozilla/dom/RecordErrorEvent.h"
      21             : #include "mozilla/dom/VideoStreamTrack.h"
      22             : #include "nsAutoPtr.h"
      23             : #include "nsContentUtils.h"
      24             : #include "nsError.h"
      25             : #include "nsIDocument.h"
      26             : #include "nsIPermissionManager.h"
      27             : #include "nsIPrincipal.h"
      28             : #include "nsIScriptError.h"
      29             : #include "nsMimeTypes.h"
      30             : #include "nsProxyRelease.h"
      31             : #include "nsTArray.h"
      32             : #include "GeckoProfiler.h"
      33             : #include "nsContentTypeParser.h"
      34             : #include "nsCharSeparatedTokenizer.h"
      35             : 
      36             : #ifdef LOG
      37             : #undef LOG
      38             : #endif
      39             : 
      40             : mozilla::LazyLogModule gMediaRecorderLog("MediaRecorder");
      41             : #define LOG(type, msg) MOZ_LOG(gMediaRecorderLog, type, msg)
      42             : 
      43             : namespace mozilla {
      44             : 
      45             : namespace dom {
      46             : 
      47             : /**
      48             : + * MediaRecorderReporter measures memory being used by the Media Recorder.
      49             : + *
      50             : + * It is a singleton reporter and the single class object lives as long as at
      51             : + * least one Recorder is registered. In MediaRecorder, the reporter is unregistered
      52             : + * when it is destroyed.
      53             : + */
      54             : class MediaRecorderReporter final : public nsIMemoryReporter
      55             : {
      56             : public:
      57             :   NS_DECL_THREADSAFE_ISUPPORTS
      58           0 :   MediaRecorderReporter() {};
      59             :   static MediaRecorderReporter* UniqueInstance();
      60             :   void InitMemoryReporter();
      61             : 
      62           0 :   static void AddMediaRecorder(MediaRecorder *aRecorder)
      63             :   {
      64           0 :     GetRecorders().AppendElement(aRecorder);
      65           0 :   }
      66             : 
      67           0 :   static void RemoveMediaRecorder(MediaRecorder *aRecorder)
      68             :   {
      69           0 :     RecordersArray& recorders = GetRecorders();
      70           0 :     recorders.RemoveElement(aRecorder);
      71           0 :     if (recorders.IsEmpty()) {
      72           0 :       sUniqueInstance = nullptr;
      73             :     }
      74           0 :   }
      75             : 
      76             :   NS_IMETHOD
      77           0 :   CollectReports(nsIHandleReportCallback* aHandleReport,
      78             :                  nsISupports* aData, bool aAnonymize) override
      79             :   {
      80           0 :     int64_t amount = 0;
      81           0 :     RecordersArray& recorders = GetRecorders();
      82           0 :     for (size_t i = 0; i < recorders.Length(); ++i) {
      83           0 :       amount += recorders[i]->SizeOfExcludingThis(MallocSizeOf);
      84             :     }
      85             : 
      86           0 :     MOZ_COLLECT_REPORT(
      87             :       "explicit/media/recorder", KIND_HEAP, UNITS_BYTES, amount,
      88           0 :       "Memory used by media recorder.");
      89             : 
      90           0 :     return NS_OK;
      91             :   }
      92             : 
      93             : private:
      94           0 :   MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
      95             :   virtual ~MediaRecorderReporter();
      96             :   static StaticRefPtr<MediaRecorderReporter> sUniqueInstance;
      97             :   typedef nsTArray<MediaRecorder*> RecordersArray;
      98           0 :   static RecordersArray& GetRecorders()
      99             :   {
     100           0 :     return UniqueInstance()->mRecorders;
     101             :   }
     102             :   RecordersArray mRecorders;
     103             : };
     104           0 : NS_IMPL_ISUPPORTS(MediaRecorderReporter, nsIMemoryReporter);
     105             : 
     106             : NS_IMPL_CYCLE_COLLECTION_CLASS(MediaRecorder)
     107             : 
     108           0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaRecorder,
     109             :                                                   DOMEventTargetHelper)
     110           0 :   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStream)
     111           0 :   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioNode)
     112           0 :   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
     113           0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     114             : 
     115           0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaRecorder,
     116             :                                                 DOMEventTargetHelper)
     117           0 :   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStream)
     118           0 :   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioNode)
     119           0 :   tmp->UnRegisterActivityObserver();
     120           0 :   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
     121           0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     122             : 
     123           0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaRecorder)
     124           0 :   NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity)
     125           0 : NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
     126             : 
     127           0 : NS_IMPL_ADDREF_INHERITED(MediaRecorder, DOMEventTargetHelper)
     128           0 : NS_IMPL_RELEASE_INHERITED(MediaRecorder, DOMEventTargetHelper)
     129             : 
     130             : /**
     131             :  * Session is an object to represent a single recording event.
     132             :  * In original design, all recording context is stored in MediaRecorder, which causes
     133             :  * a problem if someone calls MediaRecoder::Stop and MedaiRecorder::Start quickly.
     134             :  * To prevent blocking main thread, media encoding is executed in a second thread,
     135             :  * named as Read Thread. For the same reason, we do not wait Read Thread shutdown in
     136             :  * MediaRecorder::Stop. If someone call MediaRecoder::Start before Read Thread shutdown,
     137             :  * the same recording context in MediaRecoder might be access by two Reading Threads,
     138             :  * which cause a  problem.
     139             :  * In the new design, we put recording context into Session object, including Read
     140             :  * Thread.  Each Session has its own recording context and Read Thread, problem is been
     141             :  * resolved.
     142             :  *
     143             :  * Life cycle of a Session object.
     144             :  * 1) Initialization Stage (in main thread)
     145             :  *    Setup media streams in MSG, and bind MediaEncoder with Source Stream when mStream is available.
     146             :  *    Resource allocation, such as encoded data cache buffer and MediaEncoder.
     147             :  *    Create read thread.
     148             :  *    Automatically switch to Extract stage in the end of this stage.
     149             :  * 2) Extract Stage (in Read Thread)
     150             :  *    Pull encoded A/V frames from MediaEncoder, dispatch to OnDataAvailable handler.
     151             :  *    Unless a client calls Session::Stop, Session object keeps stay in this stage.
     152             :  * 3) Destroy Stage (in main thread)
     153             :  *    Switch from Extract stage to Destroy stage by calling Session::Stop.
     154             :  *    Release session resource and remove associated streams from MSG.
     155             :  *
     156             :  * Lifetime of MediaRecorder and Session objects.
     157             :  * 1) MediaRecorder creates a Session in MediaRecorder::Start function and holds
     158             :  *    a reference to Session. Then the Session registers itself to
     159             :  *    ShutdownObserver and also holds a reference to MediaRecorder.
     160             :  *    Therefore, the reference dependency in gecko is:
     161             :  *    ShutdownObserver -> Session <-> MediaRecorder, note that there is a cycle
     162             :  *    reference between Session and MediaRecorder.
     163             :  * 2) A Session is destroyed in DestroyRunnable after MediaRecorder::Stop being called
     164             :  *    _and_ all encoded media data been passed to OnDataAvailable handler.
     165             :  * 3) MediaRecorder::Stop is called by user or the document is going to
     166             :  *    inactive or invisible.
     167             :  */
     168             : class MediaRecorder::Session: public nsIObserver,
     169             :                               public PrincipalChangeObserver<MediaStreamTrack>,
     170             :                               public DOMMediaStream::TrackListener
     171             : {
     172             :   NS_DECL_THREADSAFE_ISUPPORTS
     173             : 
     174             :   // Main thread task.
     175             :   // Create a blob event and send back to client.
     176           0 :   class PushBlobRunnable : public Runnable
     177             :   {
     178             :   public:
     179           0 :     explicit PushBlobRunnable(Session* aSession)
     180           0 :       : Runnable("dom::MediaRecorder::Session::PushBlobRunnable")
     181           0 :       , mSession(aSession)
     182           0 :     { }
     183             : 
     184           0 :     NS_IMETHOD Run() override
     185             :     {
     186           0 :       LOG(LogLevel::Debug, ("Session.PushBlobRunnable s=(%p)", mSession.get()));
     187           0 :       MOZ_ASSERT(NS_IsMainThread());
     188             : 
     189           0 :       RefPtr<MediaRecorder> recorder = mSession->mRecorder;
     190           0 :       if (!recorder) {
     191           0 :         return NS_OK;
     192             :       }
     193             : 
     194           0 :       nsresult rv = recorder->CreateAndDispatchBlobEvent(mSession->GetEncodedData());
     195           0 :       if (NS_FAILED(rv)) {
     196           0 :         recorder->NotifyError(rv);
     197             :       }
     198             : 
     199           0 :       return NS_OK;
     200             :     }
     201             : 
     202             :   private:
     203             :     RefPtr<Session> mSession;
     204             :   };
     205             : 
     206             :   // Notify encoder error, run in main thread task. (Bug 1095381)
     207           0 :   class EncoderErrorNotifierRunnable : public Runnable
     208             :   {
     209             :   public:
     210           0 :     explicit EncoderErrorNotifierRunnable(Session* aSession)
     211           0 :       : Runnable("dom::MediaRecorder::Session::EncoderErrorNotifierRunnable")
     212           0 :       , mSession(aSession)
     213           0 :     { }
     214             : 
     215           0 :     NS_IMETHOD Run() override
     216             :     {
     217           0 :       LOG(LogLevel::Debug, ("Session.ErrorNotifyRunnable s=(%p)", mSession.get()));
     218           0 :       MOZ_ASSERT(NS_IsMainThread());
     219             : 
     220           0 :       RefPtr<MediaRecorder> recorder = mSession->mRecorder;
     221           0 :       if (!recorder) {
     222           0 :         return NS_OK;
     223             :       }
     224             : 
     225           0 :       if (mSession->IsEncoderError()) {
     226           0 :         recorder->NotifyError(NS_ERROR_UNEXPECTED);
     227             :       }
     228           0 :       return NS_OK;
     229             :     }
     230             : 
     231             :   private:
     232             :     RefPtr<Session> mSession;
     233             :   };
     234             : 
     235             :   // Fire start event and set mimeType, run in main thread task.
     236           0 :   class DispatchStartEventRunnable : public Runnable
     237             :   {
     238             :   public:
     239           0 :     DispatchStartEventRunnable(Session* aSession, const nsAString& aEventName)
     240           0 :       : Runnable("dom::MediaRecorder::Session::DispatchStartEventRunnable")
     241             :       , mSession(aSession)
     242           0 :       , mEventName(aEventName)
     243           0 :     { }
     244             : 
     245           0 :     NS_IMETHOD Run() override
     246             :     {
     247           0 :       LOG(LogLevel::Debug, ("Session.DispatchStartEventRunnable s=(%p)", mSession.get()));
     248           0 :       MOZ_ASSERT(NS_IsMainThread());
     249             : 
     250           0 :       NS_ENSURE_TRUE(mSession->mRecorder, NS_OK);
     251           0 :       RefPtr<MediaRecorder> recorder = mSession->mRecorder;
     252             : 
     253           0 :       recorder->SetMimeType(mSession->mMimeType);
     254           0 :       recorder->DispatchSimpleEvent(mEventName);
     255             : 
     256           0 :       return NS_OK;
     257             :     }
     258             : 
     259             :   private:
     260             :     RefPtr<Session> mSession;
     261             :     nsString mEventName;
     262             :   };
     263             : 
     264             :   // Record thread task and it run in Media Encoder thread.
     265             :   // Fetch encoded Audio/Video data from MediaEncoder.
     266             :   class ExtractRunnable : public Runnable
     267             :   {
     268             :   public:
     269           0 :     explicit ExtractRunnable(Session* aSession)
     270           0 :       : Runnable("dom::MediaRecorder::Session::ExtractRunnable")
     271           0 :       , mSession(aSession)
     272             :     {
     273           0 :     }
     274             : 
     275           0 :     ~ExtractRunnable()
     276           0 :     {}
     277             : 
     278           0 :     NS_IMETHOD Run() override
     279             :     {
     280           0 :       MOZ_ASSERT(mSession->mReadThread->EventTarget()->IsOnCurrentThread());
     281             : 
     282           0 :       LOG(LogLevel::Debug, ("Session.ExtractRunnable shutdown = %d", mSession->mEncoder->IsShutdown()));
     283           0 :       if (!mSession->mEncoder->IsShutdown()) {
     284           0 :         mSession->Extract(false);
     285           0 :         if (NS_FAILED(NS_DispatchToCurrentThread(this))) {
     286           0 :           NS_WARNING("Failed to dispatch ExtractRunnable to encoder thread");
     287             :         }
     288             :       } else {
     289             :         // Flush out remaining encoded data.
     290           0 :         mSession->Extract(true);
     291           0 :         if (NS_FAILED(NS_DispatchToMainThread(
     292             :                         new DestroyRunnable(mSession.forget())))) {
     293           0 :           MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed");
     294             :         }
     295             :       }
     296           0 :       return NS_OK;
     297             :     }
     298             : 
     299             :   private:
     300             :     RefPtr<Session> mSession;
     301             :   };
     302             : 
     303             :   // For Ensure recorder has tracks to record.
     304           0 :   class TracksAvailableCallback : public OnTracksAvailableCallback
     305             :   {
     306             :   public:
     307           0 :     explicit TracksAvailableCallback(Session *aSession, TrackRate aTrackRate)
     308           0 :      : mSession(aSession)
     309           0 :      , mTrackRate(aTrackRate) {}
     310             : 
     311           0 :     virtual void NotifyTracksAvailable(DOMMediaStream* aStream)
     312             :     {
     313           0 :       if (mSession->mStopIssued) {
     314           0 :         return;
     315             :       }
     316             : 
     317           0 :       MOZ_RELEASE_ASSERT(aStream);
     318           0 :       mSession->MediaStreamReady(*aStream);
     319             : 
     320           0 :       uint8_t trackTypes = 0;
     321           0 :       nsTArray<RefPtr<mozilla::dom::AudioStreamTrack>> audioTracks;
     322           0 :       aStream->GetAudioTracks(audioTracks);
     323           0 :       if (!audioTracks.IsEmpty()) {
     324           0 :         trackTypes |= ContainerWriter::CREATE_AUDIO_TRACK;
     325           0 :         mSession->ConnectMediaStreamTrack(*audioTracks[0]);
     326             :       }
     327             : 
     328           0 :       nsTArray<RefPtr<mozilla::dom::VideoStreamTrack>> videoTracks;
     329           0 :       aStream->GetVideoTracks(videoTracks);
     330           0 :       if (!videoTracks.IsEmpty()) {
     331           0 :         trackTypes |= ContainerWriter::CREATE_VIDEO_TRACK;
     332           0 :         mSession->ConnectMediaStreamTrack(*videoTracks[0]);
     333             :       }
     334             : 
     335           0 :       if (audioTracks.Length() > 1 ||
     336           0 :           videoTracks.Length() > 1) {
     337             :         // When MediaRecorder supports multiple tracks, we should set up a single
     338             :         // MediaInputPort from the input stream, and let main thread check
     339             :         // track principals async later.
     340           0 :         nsPIDOMWindowInner* window = mSession->mRecorder->GetParentObject();
     341           0 :         nsIDocument* document = window ? window->GetExtantDoc() : nullptr;
     342           0 :         nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
     343           0 :                                         NS_LITERAL_CSTRING("Media"),
     344             :                                         document,
     345             :                                         nsContentUtils::eDOM_PROPERTIES,
     346           0 :                                         "MediaRecorderMultiTracksNotSupported");
     347           0 :         mSession->DoSessionEndTask(NS_ERROR_ABORT);
     348           0 :         return;
     349             :       }
     350             : 
     351           0 :       NS_ASSERTION(trackTypes != 0, "TracksAvailableCallback without any tracks available");
     352             : 
     353             :       // Check that we may access the tracks' content.
     354           0 :       if (!mSession->MediaStreamTracksPrincipalSubsumes()) {
     355           0 :         LOG(LogLevel::Warning, ("Session.NotifyTracksAvailable MediaStreamTracks principal check failed"));
     356           0 :         mSession->DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
     357           0 :         return;
     358             :       }
     359             : 
     360           0 :       LOG(LogLevel::Debug, ("Session.NotifyTracksAvailable track type = (%d)", trackTypes));
     361           0 :       mSession->InitEncoder(trackTypes, mTrackRate);
     362             :     }
     363             :   private:
     364             :     RefPtr<Session> mSession;
     365             :     TrackRate mTrackRate;
     366             :   };
     367             :   // Main thread task.
     368             :   // To delete RecordingSession object.
     369           0 :   class DestroyRunnable : public Runnable
     370             :   {
     371             :   public:
     372           0 :     explicit DestroyRunnable(Session* aSession)
     373           0 :       : Runnable("dom::MediaRecorder::Session::DestroyRunnable")
     374           0 :       , mSession(aSession)
     375             :     {
     376           0 :     }
     377             : 
     378           0 :     explicit DestroyRunnable(already_AddRefed<Session> aSession)
     379           0 :       : Runnable("dom::MediaRecorder::Session::DestroyRunnable")
     380           0 :       , mSession(aSession)
     381             :     {
     382           0 :     }
     383             : 
     384           0 :     NS_IMETHOD Run() override
     385             :     {
     386           0 :       LOG(LogLevel::Debug, ("Session.DestroyRunnable session refcnt = (%d) stopIssued %d s=(%p)",
     387             :                          (int)mSession->mRefCnt, mSession->mStopIssued, mSession.get()));
     388           0 :       MOZ_ASSERT(NS_IsMainThread() && mSession);
     389           0 :       RefPtr<MediaRecorder> recorder = mSession->mRecorder;
     390           0 :       if (!recorder) {
     391           0 :         return NS_OK;
     392             :       }
     393             :       // SourceMediaStream is ended, and send out TRACK_EVENT_END notification.
     394             :       // Read Thread will be terminate soon.
     395             :       // We need to switch MediaRecorder to "Stop" state first to make sure
     396             :       // MediaRecorder is not associated with this Session anymore, then, it's
     397             :       // safe to delete this Session.
     398             :       // Also avoid to run if this session already call stop before
     399           0 :       if (!mSession->mStopIssued) {
     400           0 :         ErrorResult result;
     401           0 :         mSession->mStopIssued = true;
     402           0 :         recorder->Stop(result);
     403           0 :         if (NS_FAILED(NS_DispatchToMainThread(new DestroyRunnable(mSession.forget())))) {
     404           0 :           MOZ_ASSERT(false, "NS_DispatchToMainThread failed");
     405             :         }
     406           0 :         return NS_OK;
     407             :       }
     408             : 
     409             :       // Dispatch stop event and clear MIME type.
     410           0 :       mSession->mMimeType = NS_LITERAL_STRING("");
     411           0 :       recorder->SetMimeType(mSession->mMimeType);
     412           0 :       recorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop"));
     413           0 :       mSession->BreakCycle();
     414           0 :       return NS_OK;
     415             :     }
     416             : 
     417             :   private:
     418             :     // Call mSession::Release automatically while DestroyRunnable be destroy.
     419             :     RefPtr<Session> mSession;
     420             :   };
     421             : 
     422             :   friend class EncoderErrorNotifierRunnable;
     423             :   friend class PushBlobRunnable;
     424             :   friend class ExtractRunnable;
     425             :   friend class DestroyRunnable;
     426             :   friend class TracksAvailableCallback;
     427             : 
     428             : public:
     429           0 :   Session(MediaRecorder* aRecorder, int32_t aTimeSlice)
     430           0 :     : mRecorder(aRecorder)
     431             :     , mTimeSlice(aTimeSlice)
     432             :     , mStopIssued(false)
     433             :     , mIsStartEventFired(false)
     434             :     , mNeedSessionEndTask(true)
     435           0 :     , mSelectedVideoTrackID(TRACK_NONE)
     436             :   {
     437           0 :     MOZ_ASSERT(NS_IsMainThread());
     438             : 
     439             :     uint32_t maxMem = Preferences::GetUint("media.recorder.max_memory",
     440           0 :                                            MAX_ALLOW_MEMORY_BUFFER);
     441           0 :     mEncodedBufferCache = new EncodedBufferCache(maxMem);
     442           0 :     mLastBlobTimeStamp = TimeStamp::Now();
     443           0 :   }
     444             : 
     445           0 :   void PrincipalChanged(MediaStreamTrack* aTrack) override
     446             :   {
     447           0 :     NS_ASSERTION(mMediaStreamTracks.Contains(aTrack),
     448             :                  "Principal changed for unrecorded track");
     449           0 :     if (!MediaStreamTracksPrincipalSubsumes()) {
     450           0 :       DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
     451             :     }
     452           0 :   }
     453             : 
     454           0 :   void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override
     455             :   {
     456           0 :     LOG(LogLevel::Warning, ("Session.NotifyTrackAdded %p Raising error due to track set change", this));
     457           0 :     DoSessionEndTask(NS_ERROR_ABORT);
     458           0 :   }
     459             : 
     460           0 :   void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override
     461             :   {
     462             :     // Handle possible early removal of direct video listener
     463           0 :     if (mEncoder) {
     464           0 :       RefPtr<VideoStreamTrack> videoTrack = aTrack->AsVideoStreamTrack();
     465           0 :       if (videoTrack) {
     466           0 :         videoTrack->RemoveDirectListener(mEncoder->GetVideoSink());
     467             :       }
     468             :     }
     469             : 
     470           0 :     RefPtr<MediaInputPort> foundInputPort;
     471           0 :     for (RefPtr<MediaInputPort> inputPort : mInputPorts) {
     472           0 :       if (aTrack->IsForwardedThrough(inputPort)) {
     473           0 :         foundInputPort = inputPort;
     474           0 :         break;
     475             :       }
     476             :     }
     477             : 
     478           0 :     if (foundInputPort) {
     479             :       // A recorded track was removed or ended. End it in the recording.
     480             :       // Don't raise an error.
     481           0 :       foundInputPort->Destroy();
     482           0 :       DebugOnly<bool> removed = mInputPorts.RemoveElement(foundInputPort);
     483           0 :       MOZ_ASSERT(removed);
     484           0 :       return;
     485             :     }
     486             : 
     487           0 :     LOG(LogLevel::Warning, ("Session.NotifyTrackRemoved %p Raising error due to track set change", this));
     488           0 :     DoSessionEndTask(NS_ERROR_ABORT);
     489             :   }
     490             : 
     491           0 :   void Start()
     492             :   {
     493           0 :     LOG(LogLevel::Debug, ("Session.Start %p", this));
     494           0 :     MOZ_ASSERT(NS_IsMainThread());
     495             : 
     496             :     // Create a Track Union Stream
     497           0 :     MediaStreamGraph* gm = mRecorder->GetSourceMediaStream()->Graph();
     498           0 :     TrackRate trackRate = gm->GraphRate();
     499           0 :     mTrackUnionStream = gm->CreateTrackUnionStream();
     500           0 :     MOZ_ASSERT(mTrackUnionStream, "CreateTrackUnionStream failed");
     501             : 
     502           0 :     mTrackUnionStream->SetAutofinish(true);
     503             : 
     504           0 :     DOMMediaStream* domStream = mRecorder->Stream();
     505           0 :     if (domStream) {
     506             :       // Get the available tracks from the DOMMediaStream.
     507             :       // The callback will report back tracks that we have to connect to
     508             :       // mTrackUnionStream and listen to principal changes on.
     509           0 :       TracksAvailableCallback* tracksAvailableCallback = new TracksAvailableCallback(this, trackRate);
     510           0 :       domStream->OnTracksAvailable(tracksAvailableCallback);
     511             :     } else {
     512             :       // Check that we may access the audio node's content.
     513           0 :       if (!AudioNodePrincipalSubsumes()) {
     514           0 :         LOG(LogLevel::Warning, ("Session.Start AudioNode principal check failed"));
     515           0 :         DoSessionEndTask(NS_ERROR_DOM_SECURITY_ERR);
     516           0 :         return;
     517             :       }
     518             :       // Bind this Track Union Stream with Source Media.
     519             :       RefPtr<MediaInputPort> inputPort =
     520           0 :         mTrackUnionStream->AllocateInputPort(mRecorder->GetSourceMediaStream());
     521           0 :       mInputPorts.AppendElement(inputPort.forget());
     522           0 :       MOZ_ASSERT(mInputPorts[mInputPorts.Length()-1]);
     523             : 
     524             :       // Web Audio node has only audio.
     525           0 :       InitEncoder(ContainerWriter::CREATE_AUDIO_TRACK, trackRate);
     526             :     }
     527             :   }
     528             : 
     529           0 :   void Stop()
     530             :   {
     531           0 :     LOG(LogLevel::Debug, ("Session.Stop %p", this));
     532           0 :     MOZ_ASSERT(NS_IsMainThread());
     533           0 :     mStopIssued = true;
     534           0 :     CleanupStreams();
     535           0 :     if (mNeedSessionEndTask) {
     536           0 :       LOG(LogLevel::Debug, ("Session.Stop mNeedSessionEndTask %p", this));
     537             :       // End the Session directly if there is no ExtractRunnable.
     538           0 :       DoSessionEndTask(NS_OK);
     539             :     }
     540             :     // If we don't do this, the Session will be purged only when the navigator exit
     541             :     // by the ShutdownObserver and the memory and number of threads will quickly
     542             :     // grows with each couple stop/start.
     543           0 :     nsContentUtils::UnregisterShutdownObserver(this);
     544           0 :   }
     545             : 
     546           0 :   nsresult Pause()
     547             :   {
     548           0 :     LOG(LogLevel::Debug, ("Session.Pause"));
     549           0 :     MOZ_ASSERT(NS_IsMainThread());
     550             : 
     551           0 :     NS_ENSURE_TRUE(mTrackUnionStream, NS_ERROR_FAILURE);
     552           0 :     mTrackUnionStream->Suspend();
     553           0 :     if (mEncoder) {
     554           0 :       mEncoder->Suspend();
     555             :     }
     556             : 
     557           0 :     return NS_OK;
     558             :   }
     559             : 
     560           0 :   nsresult Resume()
     561             :   {
     562           0 :     LOG(LogLevel::Debug, ("Session.Resume"));
     563           0 :     MOZ_ASSERT(NS_IsMainThread());
     564             : 
     565           0 :     NS_ENSURE_TRUE(mTrackUnionStream, NS_ERROR_FAILURE);
     566           0 :     if (mEncoder) {
     567           0 :       mEncoder->Resume();
     568             :     }
     569           0 :     mTrackUnionStream->Resume();
     570             : 
     571           0 :     return NS_OK;
     572             :   }
     573             : 
     574           0 :   nsresult RequestData()
     575             :   {
     576           0 :     LOG(LogLevel::Debug, ("Session.RequestData"));
     577           0 :     MOZ_ASSERT(NS_IsMainThread());
     578             : 
     579           0 :     if (NS_FAILED(NS_DispatchToMainThread(new EncoderErrorNotifierRunnable(this))) ||
     580           0 :         NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this)))) {
     581           0 :       MOZ_ASSERT(false, "RequestData NS_DispatchToMainThread failed");
     582             :       return NS_ERROR_FAILURE;
     583             :     }
     584             : 
     585           0 :     return NS_OK;
     586             :   }
     587             : 
     588           0 :   already_AddRefed<nsIDOMBlob> GetEncodedData()
     589             :   {
     590           0 :     MOZ_ASSERT(NS_IsMainThread());
     591           0 :     return mEncodedBufferCache->ExtractBlob(mRecorder->GetParentObject(),
     592           0 :                                             mMimeType);
     593             :   }
     594             : 
     595           0 :   bool IsEncoderError()
     596             :   {
     597           0 :     if (mEncoder && mEncoder->HasError()) {
     598           0 :       return true;
     599             :     }
     600           0 :     return false;
     601             :   }
     602             : 
     603             :   size_t
     604           0 :   SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
     605             :   {
     606           0 :     return (mEncoder ?  mEncoder->SizeOfExcludingThis(aMallocSizeOf) : 0);
     607             :   }
     608             : 
     609             : 
     610             : private:
     611             :   // Only DestroyRunnable is allowed to delete Session object.
     612           0 :   virtual ~Session()
     613           0 :   {
     614           0 :     LOG(LogLevel::Debug, ("Session.~Session (%p)", this));
     615           0 :     CleanupStreams();
     616           0 :     if (mReadThread) {
     617           0 :       mReadThread->Shutdown();
     618           0 :       mReadThread = nullptr;
     619             :       // Inside the if() so that if we delete after xpcom-shutdown's Observe(), we
     620             :       // won't try to remove it after the observer service is shut down.
     621             :       // Unregistering for safety in case Stop() was never called
     622           0 :       nsContentUtils::UnregisterShutdownObserver(this);
     623             :     }
     624           0 :   }
     625             :   // Pull encoded media data from MediaEncoder and put into EncodedBufferCache.
     626             :   // Destroy this session object in the end of this function.
     627             :   // If the bool aForceFlush is true, we will force to dispatch a
     628             :   // PushBlobRunnable to main thread.
     629           0 :   void Extract(bool aForceFlush)
     630             :   {
     631           0 :     MOZ_ASSERT(mReadThread->EventTarget()->IsOnCurrentThread());
     632           0 :     LOG(LogLevel::Debug, ("Session.Extract %p", this));
     633             : 
     634           0 :     AUTO_PROFILER_LABEL("MediaRecorder::Session::Extract", OTHER);
     635             : 
     636             :     // Pull encoded media data from MediaEncoder
     637           0 :     nsTArray<nsTArray<uint8_t> > encodedBuf;
     638           0 :     mEncoder->GetEncodedData(&encodedBuf, mMimeType);
     639             : 
     640             :     // Append pulled data into cache buffer.
     641           0 :     for (uint32_t i = 0; i < encodedBuf.Length(); i++) {
     642           0 :       if (!encodedBuf[i].IsEmpty()) {
     643           0 :         mEncodedBufferCache->AppendBuffer(encodedBuf[i]);
     644             :         // Fire the start event when encoded data is available.
     645           0 :         if (!mIsStartEventFired) {
     646           0 :           NS_DispatchToMainThread(
     647           0 :             new DispatchStartEventRunnable(this, NS_LITERAL_STRING("start")));
     648           0 :           mIsStartEventFired = true;
     649             :         }
     650             :       }
     651             :     }
     652             : 
     653             :     // Whether push encoded data back to onDataAvailable automatically or we
     654             :     // need a flush.
     655           0 :     bool pushBlob = false;
     656           0 :     if ((mTimeSlice > 0) &&
     657           0 :         ((TimeStamp::Now()-mLastBlobTimeStamp).ToMilliseconds() > mTimeSlice)) {
     658           0 :       pushBlob = true;
     659             :     }
     660           0 :     if (pushBlob || aForceFlush) {
     661             :       // Fire the start event before the blob.
     662           0 :       if (!mIsStartEventFired) {
     663           0 :         NS_DispatchToMainThread(
     664           0 :           new DispatchStartEventRunnable(this, NS_LITERAL_STRING("start")));
     665           0 :         mIsStartEventFired = true;
     666             :       }
     667           0 :       if (NS_FAILED(NS_DispatchToMainThread(new EncoderErrorNotifierRunnable(this)))) {
     668           0 :         MOZ_ASSERT(false, "NS_DispatchToMainThread EncoderErrorNotifierRunnable failed");
     669             :       }
     670           0 :       if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this)))) {
     671           0 :         MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed");
     672             :       } else {
     673           0 :         mLastBlobTimeStamp = TimeStamp::Now();
     674             :       }
     675             :     }
     676           0 :   }
     677             : 
     678           0 :   void MediaStreamReady(DOMMediaStream& aStream) {
     679           0 :     mMediaStream = &aStream;
     680           0 :     aStream.RegisterTrackListener(this);
     681           0 :   }
     682             : 
     683           0 :   void ConnectMediaStreamTrack(MediaStreamTrack& aTrack)
     684             :   {
     685           0 :     mMediaStreamTracks.AppendElement(&aTrack);
     686           0 :     aTrack.AddPrincipalChangeObserver(this);
     687             :     RefPtr<MediaInputPort> inputPort =
     688           0 :       aTrack.ForwardTrackContentsTo(mTrackUnionStream);
     689           0 :     MOZ_ASSERT(inputPort);
     690           0 :     mInputPorts.AppendElement(inputPort.forget());
     691           0 :     MOZ_ASSERT(mInputPorts[mInputPorts.Length()-1]);
     692           0 :   }
     693             : 
     694           0 :   bool PrincipalSubsumes(nsIPrincipal* aPrincipal)
     695             :   {
     696           0 :     if (!mRecorder->GetOwner())
     697           0 :       return false;
     698           0 :     nsCOMPtr<nsIDocument> doc = mRecorder->GetOwner()->GetExtantDoc();
     699           0 :     if (!doc) {
     700           0 :       return false;
     701             :     }
     702           0 :     if (!aPrincipal) {
     703           0 :       return false;
     704             :     }
     705             :     bool subsumes;
     706           0 :     if (NS_FAILED(doc->NodePrincipal()->Subsumes(aPrincipal, &subsumes))) {
     707           0 :       return false;
     708             :     }
     709           0 :     return subsumes;
     710             :   }
     711             : 
     712           0 :   bool MediaStreamTracksPrincipalSubsumes()
     713             :   {
     714           0 :     MOZ_ASSERT(mRecorder->mDOMStream);
     715           0 :     nsCOMPtr<nsIPrincipal> principal = nullptr;
     716           0 :     for (RefPtr<MediaStreamTrack>& track : mMediaStreamTracks) {
     717           0 :       nsContentUtils::CombineResourcePrincipals(&principal, track->GetPrincipal());
     718             :     }
     719           0 :     return PrincipalSubsumes(principal);
     720             :   }
     721             : 
     722           0 :   bool AudioNodePrincipalSubsumes()
     723             :   {
     724           0 :     MOZ_ASSERT(mRecorder->mAudioNode != nullptr);
     725           0 :     nsIDocument* doc = mRecorder->mAudioNode->GetOwner()
     726           0 :                        ? mRecorder->mAudioNode->GetOwner()->GetExtantDoc()
     727           0 :                        : nullptr;
     728           0 :     nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr;
     729           0 :     return PrincipalSubsumes(principal);
     730             :   }
     731             : 
     732           0 :   void InitEncoder(uint8_t aTrackTypes, TrackRate aTrackRate)
     733             :   {
     734           0 :     LOG(LogLevel::Debug, ("Session.InitEncoder %p", this));
     735           0 :     MOZ_ASSERT(NS_IsMainThread());
     736             : 
     737           0 :     if (!mRecorder) {
     738           0 :       LOG(LogLevel::Debug, ("Session.InitEncoder failure, mRecorder is null %p", this));
     739           0 :       return;
     740             :     }
     741             :     // Allocate encoder and bind with union stream.
     742             :     // At this stage, the API doesn't allow UA to choose the output mimeType format.
     743             : 
     744           0 :     mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(""),
     745             :                                            mRecorder->GetAudioBitrate(),
     746             :                                            mRecorder->GetVideoBitrate(),
     747             :                                            mRecorder->GetBitrate(),
     748           0 :                                            aTrackTypes, aTrackRate);
     749             : 
     750           0 :     if (!mEncoder) {
     751           0 :       LOG(LogLevel::Debug, ("Session.InitEncoder !mEncoder %p", this));
     752           0 :       DoSessionEndTask(NS_ERROR_ABORT);
     753           0 :       return;
     754             :     }
     755             : 
     756             :     // Media stream is ready but UA issues a stop method follow by start method.
     757             :     // The Session::stop would clean the mTrackUnionStream. If the AfterTracksAdded
     758             :     // comes after stop command, this function would crash.
     759           0 :     if (!mTrackUnionStream) {
     760           0 :       LOG(LogLevel::Debug, ("Session.InitEncoder !mTrackUnionStream %p", this));
     761           0 :       DoSessionEndTask(NS_OK);
     762           0 :       return;
     763             :     }
     764           0 :     mTrackUnionStream->AddListener(mEncoder.get());
     765             : 
     766           0 :     nsTArray<RefPtr<mozilla::dom::VideoStreamTrack>> videoTracks;
     767           0 :     DOMMediaStream* domStream = mRecorder->Stream();
     768           0 :     if (domStream) {
     769           0 :       domStream->GetVideoTracks(videoTracks);
     770           0 :       if (!videoTracks.IsEmpty()) {
     771             :         // Right now, the MediaRecorder hasn't dealt with multiple video track
     772             :         // issues. So we just bind with the first video track. Bug 1276928 is
     773             :         // the following.
     774           0 :         videoTracks[0]->AddDirectListener(mEncoder->GetVideoSink());
     775             :       }
     776             :     }
     777             : 
     778             :     // Try to use direct listeners if possible
     779           0 :     if (domStream && domStream->GetInputStream()) {
     780           0 :       mInputStream = domStream->GetInputStream()->AsSourceStream();
     781           0 :       if (mInputStream) {
     782           0 :         mInputStream->AddDirectListener(mEncoder.get());
     783           0 :         mEncoder->SetDirectConnect(true);
     784             :       }
     785             :     }
     786             : 
     787             :     // Create a thread to read encode media data from MediaEncoder.
     788           0 :     if (!mReadThread) {
     789           0 :       nsresult rv = NS_NewNamedThread("Media_Encoder", getter_AddRefs(mReadThread));
     790           0 :       if (NS_FAILED(rv)) {
     791           0 :         LOG(LogLevel::Debug, ("Session.InitEncoder !mReadThread %p", this));
     792           0 :         DoSessionEndTask(rv);
     793           0 :         return;
     794             :       }
     795             :     }
     796             : 
     797             :     // In case source media stream does not notify track end, receive
     798             :     // shutdown notification and stop Read Thread.
     799           0 :     nsContentUtils::RegisterShutdownObserver(this);
     800             : 
     801           0 :     nsCOMPtr<nsIRunnable> event = new ExtractRunnable(this);
     802           0 :     if (NS_FAILED(mReadThread->EventTarget()->Dispatch(event.forget(), NS_DISPATCH_NORMAL))) {
     803           0 :       NS_WARNING("Failed to dispatch ExtractRunnable at beginning");
     804           0 :       LOG(LogLevel::Debug, ("Session.InitEncoder !ReadThread->Dispatch %p", this));
     805           0 :       DoSessionEndTask(NS_ERROR_ABORT);
     806             :     }
     807             :     // Set mNeedSessionEndTask to false because the
     808             :     // ExtractRunnable/DestroyRunnable will take the response to
     809             :     // end the session.
     810           0 :     mNeedSessionEndTask = false;
     811             :   }
     812             :   // application should get blob and onstop event
     813           0 :   void DoSessionEndTask(nsresult rv)
     814             :   {
     815           0 :     MOZ_ASSERT(NS_IsMainThread());
     816           0 :     CleanupStreams();
     817           0 :     NS_DispatchToMainThread(
     818           0 :       new DispatchStartEventRunnable(this, NS_LITERAL_STRING("start")));
     819             : 
     820           0 :     if (NS_FAILED(rv)) {
     821           0 :       NS_DispatchToMainThread(
     822           0 :         NewRunnableMethod<nsresult>("dom::MediaRecorder::NotifyError",
     823             :                                     mRecorder,
     824             :                                     &MediaRecorder::NotifyError,
     825           0 :                                     rv));
     826             :     }
     827           0 :     if (NS_FAILED(NS_DispatchToMainThread(new EncoderErrorNotifierRunnable(this)))) {
     828           0 :       MOZ_ASSERT(false, "NS_DispatchToMainThread EncoderErrorNotifierRunnable failed");
     829             :     }
     830           0 :     if (rv != NS_ERROR_DOM_SECURITY_ERR) {
     831             :       // Don't push a blob if there was a security error.
     832           0 :       if (NS_FAILED(NS_DispatchToMainThread(new PushBlobRunnable(this)))) {
     833           0 :         MOZ_ASSERT(false, "NS_DispatchToMainThread PushBlobRunnable failed");
     834             :       }
     835             :     }
     836           0 :     if (NS_FAILED(NS_DispatchToMainThread(new DestroyRunnable(this)))) {
     837           0 :       MOZ_ASSERT(false, "NS_DispatchToMainThread DestroyRunnable failed");
     838             :     }
     839           0 :     mNeedSessionEndTask = false;
     840           0 :   }
     841           0 :   void CleanupStreams()
     842             :   {
     843           0 :     if (mInputStream) {
     844           0 :       if (mEncoder) {
     845           0 :         mInputStream->RemoveDirectListener(mEncoder.get());
     846             :       }
     847           0 :       mInputStream = nullptr;
     848             :     }
     849             : 
     850           0 :     if (mTrackUnionStream) {
     851           0 :       if (mEncoder) {
     852           0 :         nsTArray<RefPtr<mozilla::dom::VideoStreamTrack>> videoTracks;
     853           0 :         DOMMediaStream* domStream = mRecorder->Stream();
     854           0 :         if (domStream) {
     855           0 :           domStream->GetVideoTracks(videoTracks);
     856           0 :           if (!videoTracks.IsEmpty()) {
     857           0 :             videoTracks[0]->RemoveDirectListener(mEncoder->GetVideoSink());
     858             :           }
     859             :         }
     860             :       }
     861             : 
     862             :       // Sometimes the MediaEncoder might be initialized fail and go to
     863             :       // |CleanupStreams|. So the mEncoder might be a nullptr in this case.
     864           0 :       if (mEncoder && mSelectedVideoTrackID != TRACK_NONE) {
     865           0 :         mTrackUnionStream->RemoveVideoOutput(mEncoder->GetVideoSink(), mSelectedVideoTrackID);
     866             :       }
     867           0 :       if (mEncoder) {
     868           0 :         mTrackUnionStream->RemoveListener(mEncoder.get());
     869             :       }
     870           0 :       mTrackUnionStream->Destroy();
     871           0 :       mTrackUnionStream = nullptr;
     872             :     }
     873             : 
     874           0 :     for (RefPtr<MediaInputPort>& inputPort : mInputPorts) {
     875           0 :       MOZ_ASSERT(inputPort);
     876           0 :       inputPort->Destroy();
     877             :     }
     878           0 :     mInputPorts.Clear();
     879             : 
     880           0 :     if (mMediaStream) {
     881           0 :       mMediaStream->UnregisterTrackListener(this);
     882           0 :       mMediaStream = nullptr;
     883             :     }
     884             : 
     885           0 :     for (RefPtr<MediaStreamTrack>& track : mMediaStreamTracks) {
     886           0 :       track->RemovePrincipalChangeObserver(this);
     887             :     }
     888           0 :     mMediaStreamTracks.Clear();
     889           0 :   }
     890             : 
     891           0 :   NS_IMETHOD Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) override
     892             :   {
     893           0 :     MOZ_ASSERT(NS_IsMainThread());
     894           0 :     LOG(LogLevel::Debug, ("Session.Observe XPCOM_SHUTDOWN %p", this));
     895           0 :     if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
     896             :       // Force stop Session to terminate Read Thread.
     897           0 :       mEncoder->Cancel();
     898           0 :       if (mReadThread) {
     899           0 :         mReadThread->Shutdown();
     900           0 :         mReadThread = nullptr;
     901             :       }
     902           0 :       nsContentUtils::UnregisterShutdownObserver(this);
     903           0 :       BreakCycle();
     904           0 :       Stop();
     905             :     }
     906             : 
     907           0 :     return NS_OK;
     908             :   }
     909             : 
     910             :   // Break the cycle reference between Session and MediaRecorder.
     911           0 :   void BreakCycle()
     912             :   {
     913           0 :     MOZ_ASSERT(NS_IsMainThread());
     914           0 :     if (mRecorder) {
     915           0 :       mRecorder->RemoveSession(this);
     916           0 :       mRecorder = nullptr;
     917             :     }
     918           0 :   }
     919             : 
     920             : private:
     921             :   // Hold reference to MediaRecoder that ensure MediaRecorder is alive
     922             :   // if there is an active session. Access ONLY on main thread.
     923             :   RefPtr<MediaRecorder> mRecorder;
     924             : 
     925             :   // Receive track data from source and dispatch to Encoder.
     926             :   // Pause/ Resume controller.
     927             :   RefPtr<ProcessedMediaStream> mTrackUnionStream;
     928             :   RefPtr<SourceMediaStream> mInputStream;
     929             :   nsTArray<RefPtr<MediaInputPort>> mInputPorts;
     930             : 
     931             :   // Stream currently recorded.
     932             :   RefPtr<DOMMediaStream> mMediaStream;
     933             : 
     934             :   // Tracks currently recorded. This should be a subset of mMediaStream's track
     935             :   // set.
     936             :   nsTArray<RefPtr<MediaStreamTrack>> mMediaStreamTracks;
     937             : 
     938             :   // Runnable thread for read data from MediaEncode.
     939             :   nsCOMPtr<nsIThread> mReadThread;
     940             :   // MediaEncoder pipeline.
     941             :   RefPtr<MediaEncoder> mEncoder;
     942             :   // A buffer to cache encoded meda data.
     943             :   nsAutoPtr<EncodedBufferCache> mEncodedBufferCache;
     944             :   // Current session mimeType
     945             :   nsString mMimeType;
     946             :   // Timestamp of the last fired dataavailable event.
     947             :   TimeStamp mLastBlobTimeStamp;
     948             :   // The interval of passing encoded data from EncodedBufferCache to onDataAvailable
     949             :   // handler. "mTimeSlice < 0" means Session object does not push encoded data to
     950             :   // onDataAvailable, instead, it passive wait the client side pull encoded data
     951             :   // by calling requestData API.
     952             :   const int32_t mTimeSlice;
     953             :   // Indicate this session's stop has been called.
     954             :   bool mStopIssued;
     955             :   // Indicate the session had fire start event. Encoding thread only.
     956             :   bool mIsStartEventFired;
     957             :   // False if the InitEncoder called successfully, ensure the
     958             :   // ExtractRunnable/DestroyRunnable will end the session.
     959             :   // Main thread only.
     960             :   bool mNeedSessionEndTask;
     961             :   TrackID mSelectedVideoTrackID;
     962             : };
     963             : 
     964           0 : NS_IMPL_ISUPPORTS(MediaRecorder::Session, nsIObserver)
     965             : 
     966           0 : MediaRecorder::~MediaRecorder()
     967             : {
     968           0 :   if (mPipeStream != nullptr) {
     969           0 :     mInputPort->Destroy();
     970           0 :     mPipeStream->Destroy();
     971             :   }
     972           0 :   LOG(LogLevel::Debug, ("~MediaRecorder (%p)", this));
     973           0 :   UnRegisterActivityObserver();
     974           0 : }
     975             : 
     976           0 : MediaRecorder::MediaRecorder(DOMMediaStream& aSourceMediaStream,
     977           0 :                              nsPIDOMWindowInner* aOwnerWindow)
     978             :   : DOMEventTargetHelper(aOwnerWindow)
     979           0 :   , mState(RecordingState::Inactive)
     980             : {
     981           0 :   MOZ_ASSERT(aOwnerWindow);
     982           0 :   MOZ_ASSERT(aOwnerWindow->IsInnerWindow());
     983           0 :   mDOMStream = &aSourceMediaStream;
     984             : 
     985           0 :   RegisterActivityObserver();
     986           0 : }
     987             : 
     988           0 : MediaRecorder::MediaRecorder(AudioNode& aSrcAudioNode,
     989             :                              uint32_t aSrcOutput,
     990           0 :                              nsPIDOMWindowInner* aOwnerWindow)
     991             :   : DOMEventTargetHelper(aOwnerWindow)
     992           0 :   , mState(RecordingState::Inactive)
     993             : {
     994           0 :   MOZ_ASSERT(aOwnerWindow);
     995           0 :   MOZ_ASSERT(aOwnerWindow->IsInnerWindow());
     996             : 
     997             :   // Only AudioNodeStream of kind EXTERNAL_STREAM stores output audio data in
     998             :   // the track (see AudioNodeStream::AdvanceOutputSegment()). That means track
     999             :   // union stream in recorder session won't be able to copy data from the
    1000             :   // stream of non-destination node. Create a pipe stream in this case.
    1001           0 :   if (aSrcAudioNode.NumberOfOutputs() > 0) {
    1002           0 :     AudioContext* ctx = aSrcAudioNode.Context();
    1003           0 :     AudioNodeEngine* engine = new AudioNodeEngine(nullptr);
    1004             :     AudioNodeStream::Flags flags =
    1005             :       AudioNodeStream::EXTERNAL_OUTPUT |
    1006           0 :       AudioNodeStream::NEED_MAIN_THREAD_FINISHED;
    1007           0 :     mPipeStream = AudioNodeStream::Create(ctx, engine, flags, ctx->Graph());
    1008           0 :     AudioNodeStream* ns = aSrcAudioNode.GetStream();
    1009           0 :     if (ns) {
    1010             :       mInputPort =
    1011           0 :         mPipeStream->AllocateInputPort(aSrcAudioNode.GetStream(),
    1012             :                                        TRACK_ANY, TRACK_ANY,
    1013           0 :                                        0, aSrcOutput);
    1014             :     }
    1015             :   }
    1016           0 :   mAudioNode = &aSrcAudioNode;
    1017             : 
    1018           0 :   RegisterActivityObserver();
    1019           0 : }
    1020             : 
    1021             : void
    1022           0 : MediaRecorder::RegisterActivityObserver()
    1023             : {
    1024           0 :   if (nsPIDOMWindowInner* window = GetOwner()) {
    1025           0 :     mDocument = window->GetExtantDoc();
    1026           0 :     if (mDocument) {
    1027           0 :       mDocument->RegisterActivityObserver(
    1028           0 :         NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
    1029             :     }
    1030             :   }
    1031           0 : }
    1032             : 
    1033             : void
    1034           0 : MediaRecorder::UnRegisterActivityObserver()
    1035             : {
    1036           0 :   if (mDocument) {
    1037           0 :     mDocument->UnregisterActivityObserver(
    1038           0 :       NS_ISUPPORTS_CAST(nsIDocumentActivity*, this));
    1039             :   }
    1040           0 : }
    1041             : 
    1042             : void
    1043           0 : MediaRecorder::SetMimeType(const nsString &aMimeType)
    1044             : {
    1045           0 :   mMimeType = aMimeType;
    1046           0 : }
    1047             : 
    1048             : void
    1049           0 : MediaRecorder::GetMimeType(nsString &aMimeType)
    1050             : {
    1051           0 :   aMimeType = mMimeType;
    1052           0 : }
    1053             : 
    1054             : void
    1055           0 : MediaRecorder::Start(const Optional<int32_t>& aTimeSlice, ErrorResult& aResult)
    1056             : {
    1057           0 :   LOG(LogLevel::Debug, ("MediaRecorder.Start %p", this));
    1058           0 :   if (mState != RecordingState::Inactive) {
    1059           0 :     aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    1060           0 :     return;
    1061             :   }
    1062             : 
    1063           0 :   if (GetSourceMediaStream()->IsFinished() || GetSourceMediaStream()->IsDestroyed()) {
    1064           0 :     aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    1065           0 :     return;
    1066             :   }
    1067             : 
    1068           0 :   nsTArray<RefPtr<MediaStreamTrack>> tracks;
    1069           0 :   if (mDOMStream) {
    1070           0 :     mDOMStream->GetTracks(tracks);
    1071             :   }
    1072           0 :   if (!tracks.IsEmpty()) {
    1073             :     // If there are tracks already available that we're not allowed
    1074             :     // to record, we should throw a security error.
    1075           0 :     bool subsumes = false;
    1076             :     nsPIDOMWindowInner* window;
    1077             :     nsIDocument* doc;
    1078           0 :     if (!(window = GetOwner()) ||
    1079           0 :         !(doc = window->GetExtantDoc()) ||
    1080           0 :         NS_FAILED(doc->NodePrincipal()->Subsumes(
    1081           0 :                     mDOMStream->GetPrincipal(), &subsumes)) ||
    1082           0 :         !subsumes) {
    1083           0 :       aResult.Throw(NS_ERROR_DOM_SECURITY_ERR);
    1084           0 :       return;
    1085             :     }
    1086             :   }
    1087             : 
    1088           0 :   int32_t timeSlice = 0;
    1089           0 :   if (aTimeSlice.WasPassed()) {
    1090           0 :     if (aTimeSlice.Value() < 0) {
    1091           0 :       aResult.Throw(NS_ERROR_INVALID_ARG);
    1092           0 :       return;
    1093             :     }
    1094             : 
    1095           0 :     timeSlice = aTimeSlice.Value();
    1096             :   }
    1097           0 :   MediaRecorderReporter::AddMediaRecorder(this);
    1098           0 :   mState = RecordingState::Recording;
    1099             :   // Start a session.
    1100           0 :   mSessions.AppendElement();
    1101           0 :   mSessions.LastElement() = new Session(this, timeSlice);
    1102           0 :   mSessions.LastElement()->Start();
    1103             : }
    1104             : 
    1105             : void
    1106           0 : MediaRecorder::Stop(ErrorResult& aResult)
    1107             : {
    1108           0 :   LOG(LogLevel::Debug, ("MediaRecorder.Stop %p", this));
    1109           0 :   MediaRecorderReporter::RemoveMediaRecorder(this);
    1110           0 :   if (mState == RecordingState::Inactive) {
    1111           0 :     aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    1112           0 :     return;
    1113             :   }
    1114           0 :   mState = RecordingState::Inactive;
    1115           0 :   MOZ_ASSERT(mSessions.Length() > 0);
    1116           0 :   mSessions.LastElement()->Stop();
    1117             : }
    1118             : 
    1119             : void
    1120           0 : MediaRecorder::Pause(ErrorResult& aResult)
    1121             : {
    1122           0 :   LOG(LogLevel::Debug, ("MediaRecorder.Pause"));
    1123           0 :   if (mState != RecordingState::Recording) {
    1124           0 :     aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    1125           0 :     return;
    1126             :   }
    1127             : 
    1128           0 :   MOZ_ASSERT(mSessions.Length() > 0);
    1129           0 :   nsresult rv = mSessions.LastElement()->Pause();
    1130           0 :   if (NS_FAILED(rv)) {
    1131           0 :     NotifyError(rv);
    1132           0 :     return;
    1133             :   }
    1134           0 :   mState = RecordingState::Paused;
    1135             : }
    1136             : 
    1137             : void
    1138           0 : MediaRecorder::Resume(ErrorResult& aResult)
    1139             : {
    1140           0 :   LOG(LogLevel::Debug, ("MediaRecorder.Resume"));
    1141           0 :   if (mState != RecordingState::Paused) {
    1142           0 :     aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    1143           0 :     return;
    1144             :   }
    1145             : 
    1146           0 :   MOZ_ASSERT(mSessions.Length() > 0);
    1147           0 :   nsresult rv = mSessions.LastElement()->Resume();
    1148           0 :   if (NS_FAILED(rv)) {
    1149           0 :     NotifyError(rv);
    1150           0 :     return;
    1151             :   }
    1152           0 :   mState = RecordingState::Recording;
    1153             : }
    1154             : 
    1155             : void
    1156           0 : MediaRecorder::RequestData(ErrorResult& aResult)
    1157             : {
    1158           0 :   if (mState == RecordingState::Inactive) {
    1159           0 :     aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    1160           0 :     return;
    1161             :   }
    1162           0 :   MOZ_ASSERT(mSessions.Length() > 0);
    1163           0 :   nsresult rv = mSessions.LastElement()->RequestData();
    1164           0 :   if (NS_FAILED(rv)) {
    1165           0 :     NotifyError(rv);
    1166             :   }
    1167             : }
    1168             : 
    1169             : JSObject*
    1170           0 : MediaRecorder::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
    1171             : {
    1172           0 :   return MediaRecorderBinding::Wrap(aCx, this, aGivenProto);
    1173             : }
    1174             : 
    1175             : /* static */ already_AddRefed<MediaRecorder>
    1176           0 : MediaRecorder::Constructor(const GlobalObject& aGlobal,
    1177             :                            DOMMediaStream& aStream,
    1178             :                            const MediaRecorderOptions& aInitDict,
    1179             :                            ErrorResult& aRv)
    1180             : {
    1181           0 :   nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
    1182           0 :   if (!ownerWindow) {
    1183           0 :     aRv.Throw(NS_ERROR_FAILURE);
    1184           0 :     return nullptr;
    1185             :   }
    1186             : 
    1187           0 :   if (!IsTypeSupported(aInitDict.mMimeType)) {
    1188           0 :     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
    1189           0 :     return nullptr;
    1190             :   }
    1191             : 
    1192           0 :   RefPtr<MediaRecorder> object = new MediaRecorder(aStream, ownerWindow);
    1193           0 :   object->SetOptions(aInitDict);
    1194           0 :   return object.forget();
    1195             : }
    1196             : 
    1197             : /* static */ already_AddRefed<MediaRecorder>
    1198           0 : MediaRecorder::Constructor(const GlobalObject& aGlobal,
    1199             :                            AudioNode& aSrcAudioNode,
    1200             :                            uint32_t aSrcOutput,
    1201             :                            const MediaRecorderOptions& aInitDict,
    1202             :                            ErrorResult& aRv)
    1203             : {
    1204             :   // Allow recording from audio node only when pref is on.
    1205           0 :   if (!Preferences::GetBool("media.recorder.audio_node.enabled", false)) {
    1206             :     // Pretending that this constructor is not defined.
    1207           0 :     NS_NAMED_LITERAL_STRING(argStr, "Argument 1 of MediaRecorder.constructor");
    1208           0 :     NS_NAMED_LITERAL_STRING(typeStr, "MediaStream");
    1209           0 :     aRv.ThrowTypeError<MSG_DOES_NOT_IMPLEMENT_INTERFACE>(argStr, typeStr);
    1210           0 :     return nullptr;
    1211             :   }
    1212             : 
    1213           0 :   nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
    1214           0 :   if (!ownerWindow) {
    1215           0 :     aRv.Throw(NS_ERROR_FAILURE);
    1216           0 :     return nullptr;
    1217             :   }
    1218             : 
    1219             :   // aSrcOutput doesn't matter to destination node because it has no output.
    1220           0 :   if (aSrcAudioNode.NumberOfOutputs() > 0 &&
    1221           0 :        aSrcOutput >= aSrcAudioNode.NumberOfOutputs()) {
    1222           0 :     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
    1223           0 :     return nullptr;
    1224             :   }
    1225             : 
    1226           0 :   if (!IsTypeSupported(aInitDict.mMimeType)) {
    1227           0 :     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
    1228           0 :     return nullptr;
    1229             :   }
    1230             : 
    1231             :   RefPtr<MediaRecorder> object = new MediaRecorder(aSrcAudioNode,
    1232             :                                                      aSrcOutput,
    1233           0 :                                                      ownerWindow);
    1234           0 :   object->SetOptions(aInitDict);
    1235           0 :   return object.forget();
    1236             : }
    1237             : 
    1238             : void
    1239           0 : MediaRecorder::SetOptions(const MediaRecorderOptions& aInitDict)
    1240             : {
    1241           0 :   SetMimeType(aInitDict.mMimeType);
    1242           0 :   mAudioBitsPerSecond = aInitDict.mAudioBitsPerSecond.WasPassed() ?
    1243           0 :                         aInitDict.mAudioBitsPerSecond.Value() : 0;
    1244           0 :   mVideoBitsPerSecond = aInitDict.mVideoBitsPerSecond.WasPassed() ?
    1245           0 :                         aInitDict.mVideoBitsPerSecond.Value() : 0;
    1246           0 :   mBitsPerSecond = aInitDict.mBitsPerSecond.WasPassed() ?
    1247           0 :                    aInitDict.mBitsPerSecond.Value() : 0;
    1248             :   // We're not handling dynamic changes yet. Eventually we'll handle
    1249             :   // setting audio, video and/or total -- and anything that isn't set,
    1250             :   // we'll derive. Calculated versions require querying bitrates after
    1251             :   // the encoder is Init()ed. This happens only after data is
    1252             :   // available and thus requires dynamic changes.
    1253             :   //
    1254             :   // Until dynamic changes are supported, I prefer to be safe and err
    1255             :   // slightly high
    1256           0 :   if (aInitDict.mBitsPerSecond.WasPassed() && !aInitDict.mVideoBitsPerSecond.WasPassed()) {
    1257           0 :     mVideoBitsPerSecond = mBitsPerSecond;
    1258             :   }
    1259           0 : }
    1260             : 
    1261             : static char const *const gWebMAudioEncoderCodecs[2] = {
    1262             :   "opus",
    1263             :   // no VP9 yet
    1264             :   nullptr,
    1265             : };
    1266             : static char const *const gWebMVideoEncoderCodecs[4] = {
    1267             :   "opus",
    1268             :   "vp8",
    1269             :   "vp8.0",
    1270             :   // no VP9 yet
    1271             :   nullptr,
    1272             : };
    1273             : static char const *const gOggAudioEncoderCodecs[2] = {
    1274             :   "opus",
    1275             :   // we could support vorbis here too, but don't
    1276             :   nullptr,
    1277             : };
    1278             : 
    1279             : template <class String>
    1280             : static bool
    1281           0 : CodecListContains(char const *const * aCodecs, const String& aCodec)
    1282             : {
    1283           0 :   for (int32_t i = 0; aCodecs[i]; ++i) {
    1284           0 :     if (aCodec.EqualsASCII(aCodecs[i]))
    1285           0 :       return true;
    1286             :   }
    1287           0 :   return false;
    1288             : }
    1289             : 
    1290             : /* static */
    1291             : bool
    1292           0 : MediaRecorder::IsTypeSupported(GlobalObject& aGlobal, const nsAString& aMIMEType)
    1293             : {
    1294           0 :   return IsTypeSupported(aMIMEType);
    1295             : }
    1296             : 
    1297             : /* static */
    1298             : bool
    1299           0 : MediaRecorder::IsTypeSupported(const nsAString& aMIMEType)
    1300             : {
    1301           0 :   char const* const* codeclist = nullptr;
    1302             : 
    1303           0 :   if (aMIMEType.IsEmpty()) {
    1304           0 :     return true;
    1305             :   }
    1306             : 
    1307           0 :   nsContentTypeParser parser(aMIMEType);
    1308           0 :   nsAutoString mimeType;
    1309           0 :   nsresult rv = parser.GetType(mimeType);
    1310           0 :   if (NS_FAILED(rv)) {
    1311           0 :     return false;
    1312             :   }
    1313             : 
    1314             :   // effectively a 'switch (mimeType) {'
    1315           0 :   if (mimeType.EqualsLiteral(AUDIO_OGG)) {
    1316           0 :     if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled()) {
    1317           0 :       codeclist = gOggAudioEncoderCodecs;
    1318             :     }
    1319             :   }
    1320             : #ifdef MOZ_WEBM_ENCODER
    1321           0 :   else if (mimeType.EqualsLiteral(VIDEO_WEBM) &&
    1322           0 :            MediaEncoder::IsWebMEncoderEnabled()) {
    1323           0 :     codeclist = gWebMVideoEncoderCodecs;
    1324             :   }
    1325             : #endif
    1326             : 
    1327             :   // codecs don't matter if we don't support the container
    1328           0 :   if (!codeclist) {
    1329           0 :     return false;
    1330             :   }
    1331             :   // now filter on codecs, and if needed rescind support
    1332           0 :   nsAutoString codecstring;
    1333           0 :   rv = parser.GetParameter("codecs", codecstring);
    1334             : 
    1335           0 :   nsTArray<nsString> codecs;
    1336           0 :   if (!ParseCodecsString(codecstring, codecs)) {
    1337           0 :     return false;
    1338             :   }
    1339           0 :   for (const nsString& codec : codecs) {
    1340           0 :     if (!CodecListContains(codeclist, codec)) {
    1341             :       // Totally unsupported codec
    1342           0 :       return false;
    1343             :     }
    1344             :   }
    1345             : 
    1346           0 :   return true;
    1347             : }
    1348             : 
    1349             : nsresult
    1350           0 : MediaRecorder::CreateAndDispatchBlobEvent(already_AddRefed<nsIDOMBlob>&& aBlob)
    1351             : {
    1352           0 :   MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
    1353             : 
    1354           0 :   BlobEventInit init;
    1355           0 :   init.mBubbles = false;
    1356           0 :   init.mCancelable = false;
    1357             : 
    1358           0 :   nsCOMPtr<nsIDOMBlob> blob = aBlob;
    1359           0 :   init.mData = static_cast<Blob*>(blob.get());
    1360             : 
    1361             :   RefPtr<BlobEvent> event =
    1362           0 :     BlobEvent::Constructor(this,
    1363           0 :                            NS_LITERAL_STRING("dataavailable"),
    1364           0 :                            init);
    1365           0 :   event->SetTrusted(true);
    1366           0 :   return DispatchDOMEvent(nullptr, event, nullptr, nullptr);
    1367             : }
    1368             : 
    1369             : void
    1370           0 : MediaRecorder::DispatchSimpleEvent(const nsAString & aStr)
    1371             : {
    1372           0 :   MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
    1373           0 :   nsresult rv = CheckInnerWindowCorrectness();
    1374           0 :   if (NS_FAILED(rv)) {
    1375           0 :     return;
    1376             :   }
    1377             : 
    1378           0 :   RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
    1379           0 :   event->InitEvent(aStr, false, false);
    1380           0 :   event->SetTrusted(true);
    1381             : 
    1382           0 :   rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
    1383           0 :   if (NS_FAILED(rv)) {
    1384           0 :     NS_ERROR("Failed to dispatch the event!!!");
    1385           0 :     return;
    1386             :   }
    1387             : }
    1388             : 
    1389             : void
    1390           0 : MediaRecorder::NotifyError(nsresult aRv)
    1391             : {
    1392           0 :   MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
    1393           0 :   nsresult rv = CheckInnerWindowCorrectness();
    1394           0 :   if (NS_FAILED(rv)) {
    1395           0 :     return;
    1396             :   }
    1397           0 :   nsString errorMsg;
    1398           0 :   switch (aRv) {
    1399             :   case NS_ERROR_DOM_SECURITY_ERR:
    1400           0 :     errorMsg = NS_LITERAL_STRING("SecurityError");
    1401           0 :     break;
    1402             :   case NS_ERROR_OUT_OF_MEMORY:
    1403           0 :     errorMsg = NS_LITERAL_STRING("OutOfMemoryError");
    1404           0 :     break;
    1405             :   default:
    1406           0 :     errorMsg = NS_LITERAL_STRING("GenericError");
    1407             :   }
    1408             : 
    1409           0 :   RecordErrorEventInit init;
    1410           0 :   init.mBubbles = false;
    1411           0 :   init.mCancelable = false;
    1412           0 :   init.mName = errorMsg;
    1413             : 
    1414             :   RefPtr<RecordErrorEvent> event =
    1415           0 :     RecordErrorEvent::Constructor(this, NS_LITERAL_STRING("error"), init);
    1416           0 :   event->SetTrusted(true);
    1417             : 
    1418           0 :   rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
    1419           0 :   if (NS_FAILED(rv)) {
    1420           0 :     NS_ERROR("Failed to dispatch the error event!!!");
    1421           0 :     return;
    1422             :   }
    1423           0 :   return;
    1424             : }
    1425             : 
    1426             : void
    1427           0 : MediaRecorder::RemoveSession(Session* aSession)
    1428             : {
    1429           0 :   LOG(LogLevel::Debug, ("MediaRecorder.RemoveSession (%p)", aSession));
    1430           0 :   mSessions.RemoveElement(aSession);
    1431           0 : }
    1432             : 
    1433             : void
    1434           0 : MediaRecorder::NotifyOwnerDocumentActivityChanged()
    1435             : {
    1436           0 :   nsPIDOMWindowInner* window = GetOwner();
    1437           0 :   NS_ENSURE_TRUE_VOID(window);
    1438           0 :   nsIDocument* doc = window->GetExtantDoc();
    1439           0 :   NS_ENSURE_TRUE_VOID(doc);
    1440             : 
    1441           0 :   LOG(LogLevel::Debug, ("MediaRecorder %p document IsActive %d isVisible %d\n",
    1442             :                      this, doc->IsActive(), doc->IsVisible()));
    1443           0 :   if (!doc->IsActive() || !doc->IsVisible()) {
    1444             :     // Stop the session.
    1445           0 :     ErrorResult result;
    1446           0 :     Stop(result);
    1447           0 :     result.SuppressException();
    1448             :   }
    1449             : }
    1450             : 
    1451             : MediaStream*
    1452           0 : MediaRecorder::GetSourceMediaStream()
    1453             : {
    1454           0 :   if (mDOMStream != nullptr) {
    1455           0 :     return mDOMStream->GetPlaybackStream();
    1456             :   }
    1457           0 :   MOZ_ASSERT(mAudioNode != nullptr);
    1458           0 :   return mPipeStream ? mPipeStream.get() : mAudioNode->GetStream();
    1459             : }
    1460             : 
    1461             : size_t
    1462           0 : MediaRecorder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
    1463             : {
    1464           0 :   size_t amount = 42;
    1465           0 :   for (size_t i = 0; i < mSessions.Length(); ++i) {
    1466           0 :     amount += mSessions[i]->SizeOfExcludingThis(aMallocSizeOf);
    1467             :   }
    1468           0 :   return amount;
    1469             : }
    1470             : 
    1471           3 : StaticRefPtr<MediaRecorderReporter> MediaRecorderReporter::sUniqueInstance;
    1472             : 
    1473           0 : MediaRecorderReporter* MediaRecorderReporter::UniqueInstance()
    1474             : {
    1475           0 :   if (!sUniqueInstance) {
    1476           0 :     sUniqueInstance = new MediaRecorderReporter();
    1477           0 :     sUniqueInstance->InitMemoryReporter();
    1478             :   }
    1479           0 :   return sUniqueInstance;
    1480             :  }
    1481             : 
    1482           0 : void MediaRecorderReporter::InitMemoryReporter()
    1483             : {
    1484           0 :   RegisterWeakMemoryReporter(this);
    1485           0 : }
    1486             : 
    1487           0 : MediaRecorderReporter::~MediaRecorderReporter()
    1488             : {
    1489           0 :   UnregisterWeakMemoryReporter(this);
    1490           0 : }
    1491             : 
    1492             : } // namespace dom
    1493             : } // namespace mozilla

Generated by: LCOV version 1.13