Bug 1507193 - Don't leave dangling js promises after seeking. r=jya

Only SeekToNextFrame cares about promises, but prior to this patch the common
method HTMLMediaElement::Seek() would always return a promise.

When the caller was not SeekToNextFrame (e.g., SetCurrentTime, FastSeek), the
promise would end up *not* being exposed. When later rejected, we would catch
this and write an error to the js console.

This patch lifts the handling of the promise out of Seek() and into
SeekToNextFrame() so that we avoid creating promises that would not get exposed
to js.

Differential Revision: https://phabricator.services.mozilla.com/D42511

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Andreas Pehrson 2019-08-29 14:30:06 +00:00
Родитель 1faa130d15
Коммит 46f4203220
7 изменённых файлов: 50 добавлений и 84 удалений

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

@ -2804,8 +2804,7 @@ double HTMLMediaElement::CurrentTime() const {
void HTMLMediaElement::FastSeek(double aTime, ErrorResult& aRv) {
LOG(LogLevel::Debug, ("%p FastSeek(%f) called by JS", this, aTime));
RefPtr<Promise> tobeDropped =
Seek(aTime, SeekTarget::PrevSyncPoint, IgnoreErrors());
Seek(aTime, SeekTarget::PrevSyncPoint, IgnoreErrors());
}
already_AddRefed<Promise> HTMLMediaElement::SeekToNextFrame(ErrorResult& aRv) {
@ -2820,14 +2819,23 @@ already_AddRefed<Promise> HTMLMediaElement::SeekToNextFrame(ErrorResult& aRv) {
}
}
return Seek(CurrentTime(), SeekTarget::NextFrame, aRv);
Seek(CurrentTime(), SeekTarget::NextFrame, aRv);
if (aRv.Failed()) {
return nullptr;
}
mSeekDOMPromise = CreateDOMPromise(aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return do_AddRef(mSeekDOMPromise);
}
void HTMLMediaElement::SetCurrentTime(double aCurrentTime, ErrorResult& aRv) {
LOG(LogLevel::Debug,
("%p SetCurrentTime(%f) called by JS", this, aCurrentTime));
RefPtr<Promise> tobeDropped =
Seek(aCurrentTime, SeekTarget::Accurate, IgnoreErrors());
Seek(aCurrentTime, SeekTarget::Accurate, IgnoreErrors());
}
/**
@ -2857,9 +2865,8 @@ static bool IsInRanges(TimeRanges& aRanges, double aValue,
return false;
}
already_AddRefed<Promise> HTMLMediaElement::Seek(double aTime,
SeekTarget::Type aSeekType,
ErrorResult& aRv) {
void HTMLMediaElement::Seek(double aTime, SeekTarget::Type aSeekType,
ErrorResult& aRv) {
// Note: Seek is called both by synchronous code that expects errors thrown in
// aRv, as well as asynchronous code that expects a promise. Make sure all
// synchronous errors are returned using aRv, not promise rejections.
@ -2871,11 +2878,6 @@ already_AddRefed<Promise> HTMLMediaElement::Seek(double aTime,
// https://html.spec.whatwg.org/multipage/media.html#dom-media-seek
mShowPoster = false;
RefPtr<Promise> promise = CreateDOMPromise(aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
// Detect if user has interacted with element by seeking so that
// play will not be blocked when initiated by a script.
if (EventStateManager::IsHandlingUserInput()) {
@ -2887,7 +2889,7 @@ already_AddRefed<Promise> HTMLMediaElement::Seek(double aTime,
if (mSrcAttrStream) {
// do nothing since media streams have an empty Seekable range.
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
return;
}
if (mPlayed && mCurrentPlayRangeStart != -1.0) {
@ -2906,28 +2908,28 @@ already_AddRefed<Promise> HTMLMediaElement::Seek(double aTime,
if (mReadyState == HAVE_NOTHING) {
mDefaultPlaybackStartPosition = aTime;
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
return;
}
if (!mDecoder) {
// mDecoder must always be set in order to reach this point.
NS_ASSERTION(mDecoder, "SetCurrentTime failed: no decoder");
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
return;
}
// Clamp the seek target to inside the seekable ranges.
media::TimeIntervals seekableIntervals = mDecoder->GetSeekable();
if (seekableIntervals.IsInvalid()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
return;
}
RefPtr<TimeRanges> seekable =
new TimeRanges(ToSupports(OwnerDoc()), seekableIntervals);
uint32_t length = seekable->Length();
if (length == 0) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
return;
}
// If the position we want to seek to is not in a seekable range, we seek
@ -2988,11 +2990,6 @@ already_AddRefed<Promise> HTMLMediaElement::Seek(double aTime,
// We changed whether we're seeking so we need to AddRemoveSelfReference.
AddRemoveSelfReference();
// Keep the DOM promise.
mSeekDOMPromise = promise;
return promise.forget();
}
double HTMLMediaElement::Duration() const {
@ -5266,6 +5263,24 @@ void HTMLMediaElement::SeekCompleted() {
if (IsAudioTrackCurrentlySilent()) {
UpdateAudioTrackSilenceRange(mIsAudioTrackAudible);
}
if (mSeekDOMPromise) {
mAbstractMainThread->Dispatch(NS_NewRunnableFunction(
__func__, [promise = std::move(mSeekDOMPromise)] {
promise->MaybeResolveWithUndefined();
}));
}
MOZ_ASSERT(!mSeekDOMPromise);
}
void HTMLMediaElement::SeekAborted() {
if (mSeekDOMPromise) {
mAbstractMainThread->Dispatch(NS_NewRunnableFunction(
__func__, [promise = std::move(mSeekDOMPromise)] {
promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
}));
}
MOZ_ASSERT(!mSeekDOMPromise);
}
void HTMLMediaElement::NotifySuspendedByCache(bool aSuspendedByCache) {
@ -7235,30 +7250,6 @@ bool HasDebuggerOrTabsPrivilege(JSContext* aCx, JSObject* aObj) {
nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::tabs);
}
void HTMLMediaElement::AsyncResolveSeekDOMPromiseIfExists() {
MOZ_ASSERT(NS_IsMainThread());
if (mSeekDOMPromise) {
RefPtr<dom::Promise> promise = mSeekDOMPromise.forget();
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
"dom::HTMLMediaElement::AsyncResolveSeekDOMPromiseIfExists",
[promise]() { promise->MaybeResolveWithUndefined(); });
mAbstractMainThread->Dispatch(r.forget());
mSeekDOMPromise = nullptr;
}
}
void HTMLMediaElement::AsyncRejectSeekDOMPromiseIfExists() {
MOZ_ASSERT(NS_IsMainThread());
if (mSeekDOMPromise) {
RefPtr<dom::Promise> promise = mSeekDOMPromise.forget();
nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
"dom::HTMLMediaElement::AsyncRejectSeekDOMPromiseIfExists",
[promise]() { promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); });
mAbstractMainThread->Dispatch(r.forget());
mSeekDOMPromise = nullptr;
}
}
void HTMLMediaElement::ReportCanPlayTelemetry() {
LOG(LogLevel::Debug, ("%s", __func__));

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

@ -200,6 +200,10 @@ class HTMLMediaElement : public nsGenericHTMLElement,
// when the resource has completed seeking.
void SeekCompleted() final;
// Called by the video decoder object, on the main thread,
// when the resource has aborted seeking.
void SeekAborted() final;
// Called by the media stream, on the main thread, when the download
// has been suspended by the cache or because the element itself
// asked the decoder to suspend the download.
@ -699,17 +703,6 @@ class HTMLMediaElement : public nsGenericHTMLElement,
already_AddRefed<GMPCrashHelper> CreateGMPCrashHelper() override;
// The promise resolving/rejection is queued as a "micro-task" which will be
// handled immediately after the current JS task and before any pending JS
// tasks.
// At the time we are going to resolve/reject a promise, the "seeking" event
// task should already be queued but might yet be processed, so we queue one
// more task to file the promise resolving/rejection micro-tasks
// asynchronously to make sure that the micro-tasks are processed after the
// "seeking" event task.
void AsyncResolveSeekDOMPromiseIfExists() override;
void AsyncRejectSeekDOMPromiseIfExists() override;
nsISerialEventTarget* MainThreadEventTarget() {
return mMainThreadEventTarget;
}
@ -1185,8 +1178,7 @@ class HTMLMediaElement : public nsGenericHTMLElement,
// seek target, or PrevSyncPoint if a quicker but less precise seek is
// desired, and we'll seek to the sync point (keyframe and/or start of the
// next block of audio samples) preceeding seek target.
already_AddRefed<Promise> Seek(double aTime, SeekTarget::Type aSeekType,
ErrorResult& aRv);
void Seek(double aTime, SeekTarget::Type aSeekType, ErrorResult& aRv);
// Update the audio channel playing state
void UpdateAudioChannelPlayingState(bool aForcePlaying = false);

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

@ -627,7 +627,6 @@ void MediaDecoder::DiscardOngoingSeekIfExists() {
MOZ_ASSERT(NS_IsMainThread());
AbstractThread::AutoEnter context(AbstractMainThread());
mSeekRequest.DisconnectIfExists();
GetOwner()->AsyncRejectSeekDOMPromiseIfExists();
}
void MediaDecoder::CallSeek(const SeekTarget& aTarget) {
@ -836,14 +835,14 @@ void MediaDecoder::OnSeekResolved() {
mSeekRequest.Complete();
GetOwner()->SeekCompleted();
GetOwner()->AsyncResolveSeekDOMPromiseIfExists();
}
void MediaDecoder::OnSeekRejected() {
MOZ_ASSERT(NS_IsMainThread());
mSeekRequest.Complete();
mLogicallySeeking = false;
GetOwner()->AsyncRejectSeekDOMPromiseIfExists();
GetOwner()->SeekAborted();
}
void MediaDecoder::SeekingStarted() {

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

@ -497,7 +497,7 @@ class MediaDecoder : public DecoderDoctorLifeLogger<MediaDecoder> {
protected:
void NotifyReaderDataArrived();
void DiscardOngoingSeekIfExists();
virtual void CallSeek(const SeekTarget& aTarget);
void CallSeek(const SeekTarget& aTarget);
// Called by MediaResource when the principal of the resource has
// changed. Called on main thread only.

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

@ -94,6 +94,10 @@ class MediaDecoderOwner {
// when the resource has completed seeking.
virtual void SeekCompleted() = 0;
// Called by the video decoder object, on the main thread,
// when the resource has aborted seeking.
virtual void SeekAborted() = 0;
// Called by the media stream, on the main thread, when the download
// has been suspended by the cache or because the element itself
// asked the decoder to suspend the download.
@ -144,12 +148,6 @@ class MediaDecoderOwner {
// owner's track list.
virtual void RemoveMediaTracks() = 0;
// Called by the media decoder to notify the owner to resolve a seek promise.
virtual void AsyncResolveSeekDOMPromiseIfExists() = 0;
// Called by the media decoder to notify the owner to reject a seek promise.
virtual void AsyncRejectSeekDOMPromiseIfExists() = 0;
// Notified by the decoder that a decryption key is required before emitting
// further output.
virtual void NotifyWaitingForKey() {}

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

@ -1,7 +0,0 @@
[mediasource-seek-beyond-duration.html]
disabled:
if (os == "android") and not e10s: https://bugzilla.mozilla.org/show_bug.cgi?id=1499003
if (os == "android") and e10s: bug 1550895 (frequently fails on geckoview)
if (os == "android") and debug: https://bugzilla.mozilla.org/show_bug.cgi?id=1560524
expected: ERROR

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

@ -1,7 +0,0 @@
[mediasource-seek-during-pending-seek.html]
disabled:
if (os == "android") and not e10s: https://bugzilla.mozilla.org/show_bug.cgi?id=1499003
expected: ERROR
[Test seeking to a new location during a pending seek.]
disabled:
if (os == "android"): Frequently failing on geckoview (Bug 1563134), Bug 1553057