author | Chris Pearce <cpearce@mozilla.com> |
Fri, 13 Apr 2018 20:28:39 +1200 | |
changeset 414008 | 9a93ea4f7c11676cf9541671f4af7bbf15d7c2d1 |
parent 414007 | 294df035b797b3959f072e3fcc8433c055baebb3 |
child 414009 | 4b8fe40857db84a50f456815f36c2a583fc1ccaa |
push id | 33857 |
push user | ncsoregi@mozilla.com |
push date | Tue, 17 Apr 2018 21:54:38 +0000 |
treeherder | mozilla-central@1a1223d74b7b [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | bryce |
bugs | 1453176 |
milestone | 61.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -5,16 +5,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/HTMLMediaElement.h" #include "mozilla/dom/HTMLMediaElementBinding.h" #include "mozilla/dom/HTMLSourceElement.h" #include "mozilla/dom/HTMLAudioElement.h" #include "mozilla/dom/HTMLVideoElement.h" #include "mozilla/dom/ElementInlines.h" +#include "mozilla/dom/PlayPromise.h" #include "mozilla/dom/Promise.h" #include "mozilla/ArrayUtils.h" #include "mozilla/NotNull.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/dom/MediaEncryptedEvent.h" #include "mozilla/EMEUtils.h" #include "mozilla/EventDispatcher.h" @@ -119,17 +120,17 @@ #include <cmath> #ifdef XP_WIN #include "objbase.h" // Some Windows header defines this, so undef it as it conflicts with our // function of the same name. #undef GetCurrentTime #endif -static mozilla::LazyLogModule gMediaElementLog("nsMediaElement"); +mozilla::LazyLogModule gMediaElementLog("nsMediaElement"); static mozilla::LazyLogModule gMediaElementEventsLog("nsMediaElementEvents"); #define LOG(type, msg) MOZ_LOG(gMediaElementLog, type, msg) #define LOG_EVENT(type, msg) MOZ_LOG(gMediaElementEventsLog, type, msg) #include "nsIContentSecurityPolicy.h" #include "mozilla/Preferences.h" @@ -177,25 +178,25 @@ static const double THRESHOLD_LOW_PLAYBA // Media error values. These need to match the ones in MediaError.webidl. static const unsigned short MEDIA_ERR_ABORTED = 1; static const unsigned short MEDIA_ERR_NETWORK = 2; static const unsigned short MEDIA_ERR_DECODE = 3; static const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED = 4; static void -ResolvePromisesWithUndefined(const nsTArray<RefPtr<Promise>>& aPromises) +ResolvePromisesWithUndefined(const nsTArray<RefPtr<PlayPromise>>& aPromises) { for (auto& promise : aPromises) { promise->MaybeResolveWithUndefined(); } } static void -RejectPromises(const nsTArray<RefPtr<Promise>>& aPromises, nsresult aError) +RejectPromises(const nsTArray<RefPtr<PlayPromise>>& aPromises, nsresult aError) { for (auto& promise : aPromises) { promise->MaybeReject(aError); } } // Under certain conditions there may be no-one holding references to // a media element from script, DOM parent, etc, but the element may still @@ -296,26 +297,29 @@ public: * * The constructor appends the constructed instance into the passed media * element's mPendingPlayPromisesRunners member and once the the runner is run * (whether fulfilled or canceled), it removes itself from * mPendingPlayPromisesRunners. */ class HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner : public nsMediaEvent { - nsTArray<RefPtr<Promise>> mPromises; + nsTArray<RefPtr<PlayPromise>> mPromises; nsresult mError; public: - nsResolveOrRejectPendingPlayPromisesRunner(HTMLMediaElement* aElement, - nsTArray<RefPtr<Promise>>&& aPromises, - nsresult aError = NS_OK) - : nsMediaEvent("HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner", aElement) - , mPromises(Move(aPromises)) - , mError(aError) + nsResolveOrRejectPendingPlayPromisesRunner( + HTMLMediaElement* aElement, + nsTArray<RefPtr<PlayPromise>>&& aPromises, + nsresult aError = NS_OK) + : nsMediaEvent( + "HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner", + aElement) + , mPromises(Move(aPromises)) + , mError(aError) { mElement->mPendingPlayPromisesRunners.AppendElement(this); } void ResolveOrReject() { if (NS_SUCCEEDED(mError)) { ResolvePromisesWithUndefined(mPromises); @@ -333,20 +337,21 @@ public: mElement->mPendingPlayPromisesRunners.RemoveElement(this); return NS_OK; } }; class HTMLMediaElement::nsNotifyAboutPlayingRunner : public nsResolveOrRejectPendingPlayPromisesRunner { public: - nsNotifyAboutPlayingRunner(HTMLMediaElement* aElement, - nsTArray<RefPtr<Promise>>&& aPendingPlayPromises) - : nsResolveOrRejectPendingPlayPromisesRunner(aElement, - Move(aPendingPlayPromises)) + nsNotifyAboutPlayingRunner( + HTMLMediaElement* aElement, + nsTArray<RefPtr<PlayPromise>>&& aPendingPlayPromises) + : nsResolveOrRejectPendingPlayPromisesRunner(aElement, + Move(aPendingPlayPromises)) { } NS_IMETHOD Run() override { if (IsCancelled()) { mElement->mPendingPlayPromisesRunners.RemoveElement(this); return NS_OK; @@ -3960,17 +3965,17 @@ HTMLMediaElement::Play(ErrorResult& aRv) LOG(LogLevel::Debug, ("%p Play() called by JS readyState=%d", this, mReadyState)); if (mAudioChannelWrapper && mAudioChannelWrapper->IsPlaybackBlocked()) { MaybeDoLoad(); // A blocked media element will be resumed later, so we return a pending // promise which might be resolved/rejected depends on the result of // resuming the blocked media element. - RefPtr<Promise> promise = CreateDOMPromise(aRv); + RefPtr<PlayPromise> promise = CreatePlayPromise(aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } LOG(LogLevel::Debug, ("%p Play() call delayed by AudioChannelAgent", this)); mPendingPlayPromises.AppendElement(promise); @@ -3984,54 +3989,56 @@ HTMLMediaElement::Play(ErrorResult& aRv) return promise.forget(); } already_AddRefed<Promise> HTMLMediaElement::PlayInternal(ErrorResult& aRv) { MOZ_ASSERT(!aRv.Failed()); + RefPtr<PlayPromise> promise = CreatePlayPromise(aRv); + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + // 4.8.12.8 // When the play() method on a media element is invoked, the user agent must // run the following steps. // 4.8.12.8 - Step 1: // If the media element is not allowed to play, return a promise rejected // with a "NotAllowedError" DOMException and abort these steps. // Note: IsAllowedToPlay() needs to know whether there is an audio track // in the resource, and for that we need to be at readyState HAVE_METADATA // or above. So only reject here if we're at readyState HAVE_METADATA. If // we're below that, we'll we delay fulfilling the play promise until we've // reached readyState >= HAVE_METADATA below. if (mReadyState >= HAVE_METADATA && !IsAllowedToPlay()) { // NOTE: for promise-based-play, will return a rejected promise here. LOG(LogLevel::Debug, ("%p Play() promise rejected because not allowed to play.", this)); - aRv.Throw(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR); - return nullptr; + promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR); + return promise.forget(); } // 4.8.12.8 - Step 2: // If the media element's error attribute is not null and its code // attribute has the value MEDIA_ERR_SRC_NOT_SUPPORTED, return a promise // rejected with a "NotSupportedError" DOMException and abort these steps. if (GetError() && GetError()->Code() == MEDIA_ERR_SRC_NOT_SUPPORTED) { LOG(LogLevel::Debug, ("%p Play() promise rejected because source not supported.", this)); - aRv.Throw(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR); - return nullptr; + promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR); + return promise.forget(); } // 4.8.12.8 - Step 3: // Let promise be a new promise and append promise to the list of pending // play promises. - RefPtr<Promise> promise = CreateDOMPromise(aRv); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } mPendingPlayPromises.AppendElement(promise); if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE) { // The media load algorithm will be initiated by a user interaction. // We want to boost the channel priority for better responsiveness. // Note this must be done before UpdatePreloadAction() which will // update |mPreloadAction|. mUseUrgentStartForChannel = true; @@ -4071,18 +4078,18 @@ HTMLMediaElement::PlayInternal(ErrorResu // If something wrong between |mPendingPlayPromises.AppendElement(promise);| // and here, the _promise_ should already have been rejected. Otherwise, // the _promise_ won't be returned to JS at all, so just leave it in the // _mPendingPlayPromises_ and let it be resolved/rejected with the // following actions and the promise-resolution won't be observed at all. LOG(LogLevel::Debug, ("%p Play() promise rejected because failed to play MediaDecoder.", this)); - aRv.Throw(rv); - return nullptr; + promise->MaybeReject(rv); + return promise.forget(); } } } } else if (mReadyState < HAVE_METADATA) { mAttemptPlayUponLoadedMetadata = true; } if (mCurrentPlayRangeStart == -1.0) { @@ -7588,30 +7595,46 @@ HTMLMediaElement::UpdateCustomPolicyAfte AbstractThread* HTMLMediaElement::AbstractMainThread() const { MOZ_ASSERT(mAbstractMainThread); return mAbstractMainThread; } -nsTArray<RefPtr<Promise>> +nsTArray<RefPtr<PlayPromise>> HTMLMediaElement::TakePendingPlayPromises() { return Move(mPendingPlayPromises); } void HTMLMediaElement::NotifyAboutPlaying() { // Stick to the DispatchAsyncEvent() call path for now because we want to // trigger some telemetry-related codes in the DispatchAsyncEvent() method. DispatchAsyncEvent(NS_LITERAL_STRING("playing")); } +already_AddRefed<PlayPromise> +HTMLMediaElement::CreatePlayPromise(ErrorResult& aRv) const +{ + nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); + + if (!win) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + RefPtr<PlayPromise> promise = PlayPromise::Create(win->AsGlobal(), aRv); + LOG(LogLevel::Debug, ("%p created PlayPromise %p", this, promise.get())); + + return promise.forget(); +} + already_AddRefed<Promise> HTMLMediaElement::CreateDOMPromise(ErrorResult& aRv) const { nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); if (!win) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr;
--- a/dom/html/HTMLMediaElement.h +++ b/dom/html/HTMLMediaElement.h @@ -77,16 +77,17 @@ class nsRange; namespace mozilla { namespace dom { // Number of milliseconds between timeupdate events as defined by spec #define TIMEUPDATE_MS 250 class MediaError; class MediaSource; +class PlayPromise; class Promise; class TextTrackList; class AudioTrackList; class VideoTrackList; enum class StreamCaptureType : uint8_t { CAPTURE_ALL_TRACKS, @@ -1321,17 +1322,17 @@ protected: nsresult DispatchEvent(const nsAString& aName); // Open unsupported types media with the external app when the media element // triggers play() after loaded fail. eg. preload the data before start play. void OpenUnsupportedMediaWithExternalAppIfNeeded() const; // This method moves the mPendingPlayPromises into a temperate object. So the // mPendingPlayPromises is cleared after this method call. - nsTArray<RefPtr<Promise>> TakePendingPlayPromises(); + nsTArray<RefPtr<PlayPromise>> TakePendingPlayPromises(); // This method snapshots the mPendingPlayPromises by TakePendingPlayPromises() // and queues a task to resolve them. void AsyncResolvePendingPlayPromises(); // This method snapshots the mPendingPlayPromises by TakePendingPlayPromises() // and queues a task to reject them. void AsyncRejectPendingPlayPromises(nsresult aError); @@ -1775,16 +1776,19 @@ public: return mCount + 1; } private: TimeStamp mStartTime; TimeDuration mSum; uint32_t mCount; }; private: + + already_AddRefed<PlayPromise> CreatePlayPromise(ErrorResult& aRv) const; + /** * This function is called by AfterSetAttr and OnAttrSetButNotChanged. * It will not be called if the value is being unset. * * @param aNamespaceID the namespace of the attr being set * @param aName the localname of the attribute being set * @param aNotify Whether we plan to notify document observers. */ @@ -1836,17 +1840,17 @@ private: // This wrapper will handle all audio channel related stuffs, eg. the operations // of tab audio indicator, Fennec's media control. // Note: mAudioChannelWrapper might be null after GC happened. RefPtr<AudioChannelAgentCallback> mAudioChannelWrapper; // A list of pending play promises. The elements are pushed during the play() // method call and are resolved/rejected during further playback steps. - nsTArray<RefPtr<Promise>> mPendingPlayPromises; + nsTArray<RefPtr<PlayPromise>> mPendingPlayPromises; // A list of already-dispatched but not yet run // nsResolveOrRejectPendingPlayPromisesRunners. // Runners whose Run() method is called remove themselves from this list. // We keep track of these because the load algorithm resolves/rejects all // already-dispatched pending play promises. nsTArray<nsResolveOrRejectPendingPlayPromisesRunner*> mPendingPlayPromisesRunners;
new file mode 100644 --- /dev/null +++ b/dom/html/PlayPromise.cpp @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/PlayPromise.h" +#include "mozilla/Logging.h" +#include "mozilla/Telemetry.h" + +extern mozilla::LazyLogModule gMediaElementLog; + +#define PLAY_PROMISE_LOG(msg, ...) \ + MOZ_LOG(gMediaElementLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) + +namespace mozilla { +namespace dom { + +PlayPromise::PlayPromise(nsIGlobalObject* aGlobal) + : Promise(aGlobal) +{ +} + +PlayPromise::~PlayPromise() +{ + if (!mFulfilled && PromiseObj()) { + MaybeReject(NS_ERROR_DOM_ABORT_ERR); + } +} + +/* static */ +already_AddRefed<PlayPromise> +PlayPromise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv) +{ + RefPtr<PlayPromise> promise = new PlayPromise(aGlobal); + promise->CreateWrapper(nullptr, aRv); + return aRv.Failed() ? nullptr : promise.forget(); +} + +void +PlayPromise::MaybeResolveWithUndefined() +{ + if (mFulfilled) { + return; + } + mFulfilled = true; + PLAY_PROMISE_LOG("PlayPromise %p resolved with undefined", this); + auto reason = Telemetry::LABELS_MEDIA_PLAY_PROMISE_RESOLUTION::Resolved; + Telemetry::AccumulateCategorical(reason); + Promise::MaybeResolveWithUndefined(); +} + +using PlayLabel = Telemetry::LABELS_MEDIA_PLAY_PROMISE_RESOLUTION; + +struct PlayPromiseTelemetryResult +{ + nsresult mValue; + PlayLabel mLabel; + const char* mName; +}; + +static const PlayPromiseTelemetryResult sPlayPromiseTelemetryResults[] = { + { + NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR, + PlayLabel::NotAllowedErr, + "NotAllowedErr", + }, + { + NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR, + PlayLabel::SrcNotSupportedErr, + "SrcNotSupportedErr", + }, + { + NS_ERROR_DOM_MEDIA_ABORT_ERR, + PlayLabel::PauseAbortErr, + "PauseAbortErr", + }, + { + NS_ERROR_DOM_ABORT_ERR, + PlayLabel::AbortErr, + "AbortErr", + }, +}; + +static const PlayPromiseTelemetryResult* +FindPlayPromiseTelemetryResult(nsresult aReason) +{ + for (const auto& p : sPlayPromiseTelemetryResults) { + if (p.mValue == aReason) { + return &p; + } + } + return nullptr; +} + +static PlayLabel +ToPlayResultLabel(nsresult aReason) +{ + auto p = FindPlayPromiseTelemetryResult(aReason); + return p ? p->mLabel : PlayLabel::UnknownErr; +} + +static const char* +ToPlayResultStr(nsresult aReason) +{ + auto p = FindPlayPromiseTelemetryResult(aReason); + return p ? p->mName : "UnknownErr"; +} + +void +PlayPromise::MaybeReject(nsresult aReason) +{ + if (mFulfilled) { + return; + } + mFulfilled = true; + PLAY_PROMISE_LOG("PlayPromise %p rejected with 0x%x (%s)", + this, + static_cast<uint32_t>(aReason), + ToPlayResultStr(aReason)); + Telemetry::AccumulateCategorical(ToPlayResultLabel(aReason)); + Promise::MaybeReject(aReason); +} + +} // namespace dom +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/html/PlayPromise.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __PlayPromise_h__ +#define __PlayPromise_h__ + +#include "mozilla/dom/Promise.h" +#include "mozilla/Telemetry.h" + +namespace mozilla { +namespace dom { + +// Decorates a DOM Promise to report telemetry as to whether it was resolved +// or rejected and why. +class PlayPromise : public Promise +{ +public: + static already_AddRefed<PlayPromise> Create(nsIGlobalObject* aGlobal, + ErrorResult& aRv); + ~PlayPromise(); + void MaybeResolveWithUndefined(); + void MaybeReject(nsresult aReason); + +private: + explicit PlayPromise(nsIGlobalObject* aGlobal); + bool mFulfilled = false; +}; + +} // namespace dom +} // namespace mozilla + +#endif // __PlayPromise_h__
--- a/dom/html/moz.build +++ b/dom/html/moz.build @@ -117,16 +117,17 @@ EXPORTS.mozilla.dom += [ 'HTMLTimeElement.h', 'HTMLTitleElement.h', 'HTMLTrackElement.h', 'HTMLUnknownElement.h', 'HTMLVideoElement.h', 'ImageDocument.h', 'MediaError.h', 'nsBrowserElement.h', + 'PlayPromise.h', 'RadioNodeList.h', 'TextTrackManager.h', 'TimeRanges.h', 'ValidityState.h', ] UNIFIED_SOURCES += [ 'HTMLAllCollection.cpp', @@ -206,16 +207,17 @@ UNIFIED_SOURCES += [ 'nsGenericHTMLElement.cpp', 'nsGenericHTMLFrameElement.cpp', 'nsHTMLContentSink.cpp', 'nsHTMLDNSPrefetch.cpp', 'nsHTMLDocument.cpp', 'nsIConstraintValidation.cpp', 'nsRadioVisitor.cpp', 'nsTextEditorState.cpp', + 'PlayPromise.cpp', 'RadioNodeList.cpp', 'TextTrackManager.cpp', 'TimeRanges.cpp', 'ValidityState.cpp', 'VideoDocument.cpp', ] SOURCES += [
--- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -11312,16 +11312,25 @@ "expires_in_version": "60", "bug_numbers": [1230265], "kind": "linear", "high": 1000, "n_buckets": 100, "description": "720p VP9 decode benchmark measurement in frames per second", "releaseChannelCollection": "opt-out" }, + "MEDIA_PLAY_PROMISE_RESOLUTION": { + "record_in_processes": ["main", "content"], + "bug_numbers": [1453176], + "alert_emails": ["cpearce@mozilla.com", "drno@ohlmeier.org"], + "expires_in_version": "72", + "kind": "categorical", + "labels": ["Resolved", "NotAllowedErr", "SrcNotSupportedErr", "PauseAbortErr", "AbortErr", "UnknownErr"], + "description": "Records whether promise returned by HTMLMediaElement.play() successfully resolved, or the error code which it was rejected with." + }, "MEDIA_CODEC_USED": { "record_in_processes": ["main", "content"], "alert_emails": ["cpearce@mozilla.com"], "expires_in_version": "never", "keyed": true, "kind": "count", "description": "Count of use of audio/video codecs in HTMLMediaElements and WebAudio. Those with 'resource' prefix are approximate; report based on HTTP ContentType or sniffing. Those with 'webaudio' prefix are for WebAudio." },