Bug 1275856 - Capture MediaRecorder DOMExceptions early in order to capture JS traces. r?jib
In order to expose the JS stack on aync exceptions from the MediaRecorder,
these exceptions must be created at the time of the operation which led to the
exception. E.g. during the start() operation. This changeset creates the
exceptions ahead of time in order to expose the JS stack traces.
MozReview-Commit-ID: HgDJrpjgidD
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -104,23 +104,27 @@ private:
NS_IMPL_ISUPPORTS(MediaRecorderReporter, nsIMemoryReporter);
NS_IMPL_CYCLE_COLLECTION_CLASS(MediaRecorder)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaRecorder,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDOMStream)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioNode)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSecurityDomException)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mUnknownDomException)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MediaRecorder,
DOMEventTargetHelper)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDOMStream)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioNode)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSecurityDomException)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mUnknownDomException)
tmp->UnRegisterActivityObserver();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaRecorder)
NS_INTERFACE_MAP_ENTRY(nsIDocumentActivity)
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
@@ -1050,16 +1054,19 @@ MediaRecorder::GetMimeType(nsString &aMi
{
aMimeType = mMimeType;
}
void
MediaRecorder::Start(const Optional<int32_t>& aTimeSlice, ErrorResult& aResult)
{
LOG(LogLevel::Debug, ("MediaRecorder.Start %p", this));
+
+ InitializeDomExceptions();
+
if (mState != RecordingState::Inactive) {
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
if (GetSourceMediaStream()->IsFinished() || GetSourceMediaStream()->IsDestroyed()) {
aResult.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
@@ -1392,22 +1399,37 @@ MediaRecorder::NotifyError(nsresult aRv)
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
nsresult rv = CheckInnerWindowCorrectness();
if (NS_FAILED(rv)) {
return;
}
MediaRecorderErrorEventInit init;
init.mBubbles = false;
init.mCancelable = false;
+ // These DOMExceptions have been created earlier so they can contain stack
+ // traces. We attach the appropriate one here to be fired. We should have
+ // exceptions here, but defensively check.
switch (aRv) {
case NS_ERROR_DOM_SECURITY_ERR:
- init.mError = DOMException::Create(aRv);
+ if (!mSecurityDomException) {
+ LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
+ "mSecurityDomException was not initialized"));
+ mSecurityDomException = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR);
+ }
+ init.mError = mSecurityDomException.forget();
break;
default:
- init.mError = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR);
+ if (!mUnknownDomException) {
+ LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
+ "mUnknownDomException was not initialized"));
+ mUnknownDomException = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR);
+ }
+ LOG(LogLevel::Debug, ("MediaRecorder.NotifyError: "
+ "mUnknownDomException being fired for aRv: %X", uint32_t(aRv)));
+ init.mError = mUnknownDomException.forget();
}
RefPtr<MediaRecorderErrorEvent> event = MediaRecorderErrorEvent::Constructor(
this, NS_LITERAL_STRING("error"), init);
event->SetTrusted(true);
rv = DispatchDOMEvent(nullptr, event, nullptr, nullptr);
if (NS_FAILED(rv)) {
@@ -1445,16 +1467,23 @@ MediaRecorder::GetSourceMediaStream()
{
if (mDOMStream != nullptr) {
return mDOMStream->GetPlaybackStream();
}
MOZ_ASSERT(mAudioNode != nullptr);
return mPipeStream ? mPipeStream.get() : mAudioNode->GetStream();
}
+void
+MediaRecorder::InitializeDomExceptions()
+{
+ mSecurityDomException = DOMException::Create(NS_ERROR_DOM_SECURITY_ERR);
+ mUnknownDomException = DOMException::Create(NS_ERROR_DOM_UNKNOWN_ERR);
+}
+
size_t
MediaRecorder::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
size_t amount = 42;
for (size_t i = 0; i < mSessions.Length(); ++i) {
amount += mSessions[i]->SizeOfExcludingThis(aMallocSizeOf);
}
return amount;
--- a/dom/media/MediaRecorder.h
+++ b/dom/media/MediaRecorder.h
@@ -23,16 +23,17 @@ class ErrorResult;
class MediaInputPort;
struct MediaRecorderOptions;
class MediaStream;
class GlobalObject;
namespace dom {
class AudioNode;
+class DOMException;
/**
* Implementation of https://dvcs.w3.org/hg/dap/raw-file/default/media-stream-capture/MediaRecorder.html
* The MediaRecorder accepts a mediaStream as input source passed from UA. When recorder starts,
* a MediaEncoder will be created and accept the mediaStream as input source.
* Encoder will get the raw data by track data changes, encode it by selected MIME Type, then store the encoded in EncodedBufferCache object.
* The encoded data will be extracted on every timeslice passed from Start function call or by RequestData function.
* Thread model:
@@ -128,16 +129,22 @@ protected:
void SetMimeType(const nsString &aMimeType);
void SetOptions(const MediaRecorderOptions& aInitDict);
MediaRecorder(const MediaRecorder& x) = delete; // prevent bad usage
// Remove session pointer.
void RemoveSession(Session* aSession);
// Functions for Session to query input source info.
MediaStream* GetSourceMediaStream();
+ // Create DOMExceptions capturing the JS stack for async errors. These are
+ // created ahead of time rather than on demand when firing an error as the JS
+ // stack of the operation that started the async behavior will not be
+ // available at the time the error event is fired. Note, depending on when
+ // this is called there may not be a JS stack to capture.
+ void InitializeDomExceptions();
// DOM wrapper for source media stream. Will be null when input is audio node.
RefPtr<DOMMediaStream> mDOMStream;
// Source audio node. Will be null when input is a media stream.
RefPtr<AudioNode> mAudioNode;
// Pipe stream connecting non-destination source node and session track union
// stream of recorder. Will be null when input is media stream or destination
// node.
RefPtr<AudioNodeStream> mPipeStream;
@@ -154,16 +161,22 @@ protected:
// It specifies the container format as well as the audio and video capture formats.
nsString mMimeType;
uint32_t mAudioBitsPerSecond;
uint32_t mVideoBitsPerSecond;
uint32_t mBitsPerSecond;
+ // DOMExceptions that are created early and possibly thrown in NotifyError.
+ // Creating them early allows us to capture the JS stack for which cannot be
+ // done at the time the error event is fired.
+ RefPtr<DOMException> mSecurityDomException;
+ RefPtr<DOMException> mUnknownDomException;
+
private:
// Register MediaRecorder into Document to listen the activity changes.
void RegisterActivityObserver();
void UnRegisterActivityObserver();
bool CheckPermission(const nsString &aType);
};