tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

MediaElementEventRunners.cpp (8875B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "MediaElementEventRunners.h"
      6 
      7 #include <stdint.h>
      8 
      9 #include "MediaProfilerMarkers.h"
     10 #include "mozilla/Casting.h"
     11 #include "mozilla/FlowMarkers.h"
     12 #include "mozilla/ProfilerState.h"
     13 #include "mozilla/dom/HTMLMediaElement.h"
     14 #include "mozilla/dom/HTMLVideoElement.h"
     15 #include "mozilla/dom/MediaError.h"
     16 #include "mozilla/dom/TimeRanges.h"
     17 
     18 extern mozilla::LazyLogModule gMediaElementEventsLog;
     19 #define LOG_EVENT(type, msg) MOZ_LOG(gMediaElementEventsLog, type, msg)
     20 
     21 namespace mozilla::dom {
     22 
     23 nsMediaEventRunner::nsMediaEventRunner(const char* aName,
     24                                       HTMLMediaElement* aElement,
     25                                       const nsAString& aEventName)
     26    : mElement(aElement),
     27      mName(aName),
     28      mEventName(aEventName),
     29      mLoadID(mElement->GetCurrentLoadID()) {}
     30 
     31 bool nsMediaEventRunner::IsCancelled() const {
     32  return !mElement || mElement->GetCurrentLoadID() != mLoadID;
     33 }
     34 
     35 nsresult nsMediaEventRunner::FireEvent(const nsAString& aName) {
     36  nsresult rv = NS_OK;
     37  if (mElement) {
     38    ReportProfilerMarker();
     39    rv = RefPtr { mElement } -> FireEvent(aName);
     40  }
     41  return rv;
     42 }
     43 
     44 void nsMediaEventRunner::ReportProfilerMarker() {
     45  if (!profiler_is_collecting_markers()) {
     46    return;
     47  }
     48  // Report the buffered range.
     49  if (mEventName.EqualsLiteral("progress")) {
     50    RefPtr<TimeRanges> buffered = mElement->Buffered();
     51    if (buffered && buffered->Length() > 0) {
     52      for (size_t i = 0; i < buffered->Length(); ++i) {
     53        profiler_add_marker("progress", geckoprofiler::category::MEDIA_PLAYBACK,
     54                            {}, BufferedUpdateMarker{},
     55                            AssertedCast<uint64_t>(buffered->Start(i) * 1000),
     56                            AssertedCast<uint64_t>(buffered->End(i) * 1000),
     57                            GetElementDurationMs(),
     58                            Flow::FromPointer(mElement.get()));
     59      }
     60    }
     61  } else if (mEventName.EqualsLiteral("resize")) {
     62    MOZ_ASSERT(mElement->HasVideo());
     63    auto mediaInfo = mElement->GetMediaInfo();
     64    profiler_add_marker("resize", geckoprofiler::category::MEDIA_PLAYBACK, {},
     65                        VideoResizeMarker{}, mediaInfo.mVideo.mDisplay.width,
     66                        mediaInfo.mVideo.mDisplay.height,
     67                        Flow::FromPointer(mElement.get()));
     68  } else if (mEventName.EqualsLiteral("loadedmetadata")) {
     69    nsString src;
     70    mElement->GetCurrentSrc(src);
     71    auto mediaInfo = mElement->GetMediaInfo();
     72    profiler_add_marker(
     73        "loadedmetadata", geckoprofiler::category::MEDIA_PLAYBACK, {},
     74        MetadataMarker{}, src,
     75        mediaInfo.HasAudio() ? mediaInfo.mAudio.mMimeType : "none"_ns,
     76        mediaInfo.HasVideo() ? mediaInfo.mVideo.mMimeType : "none"_ns,
     77        Flow::FromPointer(mElement.get()));
     78  } else if (mEventName.EqualsLiteral("error")) {
     79    auto* error = mElement->GetError();
     80    nsString message;
     81    error->GetMessage(message);
     82    profiler_add_marker("error", geckoprofiler::category::MEDIA_PLAYBACK, {},
     83                        ErrorMarker{}, message,
     84                        Flow::FromPointer(mElement.get()));
     85  } else {
     86    auto eventName = NS_ConvertUTF16toUTF8(mEventName);
     87    PROFILER_MARKER(eventName, MEDIA_PLAYBACK, {}, FlowMarker,
     88                    Flow::FromPointer(mElement.get()));
     89  }
     90 }
     91 
     92 uint64_t nsMediaEventRunner::GetElementDurationMs() const {
     93  MOZ_ASSERT(!IsCancelled());
     94  double duration = mElement->Duration();
     95 
     96  if (duration == std::numeric_limits<double>::infinity()) {
     97    return std::numeric_limits<uint64_t>::max();
     98  }
     99 
    100  if (std::isnan(duration) || duration <= 0) {
    101    // Duration is unknown or invalid
    102    return 0;
    103  }
    104  return AssertedCast<uint64_t>(duration * 1000);
    105 }
    106 
    107 NS_IMPL_CYCLE_COLLECTION(nsMediaEventRunner, mElement)
    108 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsMediaEventRunner)
    109 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsMediaEventRunner)
    110 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsMediaEventRunner)
    111  NS_INTERFACE_MAP_ENTRY(nsINamed)
    112  NS_INTERFACE_MAP_ENTRY(nsIRunnable)
    113  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRunnable)
    114 NS_INTERFACE_MAP_END
    115 
    116 NS_IMETHODIMP nsAsyncEventRunner::Run() {
    117  // Silently cancel if our load has been cancelled or element has been CCed.
    118  return IsCancelled() ? NS_OK : FireEvent(mEventName);
    119 }
    120 
    121 nsResolveOrRejectPendingPlayPromisesRunner::
    122    nsResolveOrRejectPendingPlayPromisesRunner(
    123        HTMLMediaElement* aElement, nsTArray<RefPtr<PlayPromise>>&& aPromises,
    124        nsresult aError)
    125    : nsMediaEventRunner("nsResolveOrRejectPendingPlayPromisesRunner",
    126                         aElement),
    127      mPromises(std::move(aPromises)),
    128      mError(aError) {
    129  mElement->mPendingPlayPromisesRunners.AppendElement(this);
    130 }
    131 
    132 void nsResolveOrRejectPendingPlayPromisesRunner::ResolveOrReject() {
    133  if (NS_SUCCEEDED(mError)) {
    134    PlayPromise::ResolvePromisesWithUndefined(mPromises);
    135  } else {
    136    PlayPromise::RejectPromises(mPromises, mError);
    137  }
    138 }
    139 
    140 NS_IMETHODIMP nsResolveOrRejectPendingPlayPromisesRunner::Run() {
    141  if (!IsCancelled()) {
    142    ResolveOrReject();
    143  }
    144 
    145  mElement->mPendingPlayPromisesRunners.RemoveElement(this);
    146  return NS_OK;
    147 }
    148 
    149 NS_IMETHODIMP nsNotifyAboutPlayingRunner::Run() {
    150  if (!IsCancelled()) {
    151    FireEvent(u"playing"_ns);
    152  }
    153  return nsResolveOrRejectPendingPlayPromisesRunner::Run();
    154 }
    155 
    156 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsResolveOrRejectPendingPlayPromisesRunner,
    157                                   nsMediaEventRunner, mPromises)
    158 NS_IMPL_ADDREF_INHERITED(nsResolveOrRejectPendingPlayPromisesRunner,
    159                         nsMediaEventRunner)
    160 NS_IMPL_RELEASE_INHERITED(nsResolveOrRejectPendingPlayPromisesRunner,
    161                          nsMediaEventRunner)
    162 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
    163    nsResolveOrRejectPendingPlayPromisesRunner)
    164 NS_INTERFACE_MAP_END_INHERITING(nsMediaEventRunner)
    165 
    166 NS_IMETHODIMP nsSourceErrorEventRunner::Run() {
    167  // Silently cancel if our load has been cancelled.
    168  if (IsCancelled()) {
    169    return NS_OK;
    170  }
    171  LOG_EVENT(LogLevel::Debug,
    172            ("%p Dispatching simple event source error", mElement.get()));
    173  if (profiler_is_collecting_markers()) {
    174    profiler_add_marker("sourceerror", geckoprofiler::category::MEDIA_PLAYBACK,
    175                        {}, ErrorMarker{}, mErrorDetails,
    176                        Flow::FromPointer(mElement.get()));
    177  }
    178  return nsContentUtils::DispatchTrustedEvent(mElement->OwnerDoc(), mSource,
    179                                              u"error"_ns, CanBubble::eNo,
    180                                              Cancelable::eNo);
    181 }
    182 
    183 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsSourceErrorEventRunner, nsMediaEventRunner,
    184                                   mSource)
    185 NS_IMPL_ADDREF_INHERITED(nsSourceErrorEventRunner, nsMediaEventRunner)
    186 NS_IMPL_RELEASE_INHERITED(nsSourceErrorEventRunner, nsMediaEventRunner)
    187 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSourceErrorEventRunner)
    188 NS_INTERFACE_MAP_END_INHERITING(nsMediaEventRunner)
    189 
    190 NS_IMETHODIMP nsTimeupdateRunner::Run() {
    191  if (IsCancelled() || !ShouldDispatchTimeupdate()) {
    192    return NS_OK;
    193  }
    194  // After dispatching `timeupdate`, if the timeupdate event listener takes lots
    195  // of time then we end up spending all time handling just timeupdate events.
    196  // The spec is vague in this situation, so we choose to update time after we
    197  // dispatch the event in order to solve that issue.
    198  nsresult rv = FireEvent(mEventName);
    199  if (NS_WARN_IF(NS_FAILED(rv))) {
    200    LOG_EVENT(LogLevel::Debug,
    201              ("%p Failed to dispatch 'timeupdate'", mElement.get()));
    202  } else {
    203    mElement->UpdateLastTimeupdateDispatchTime();
    204  }
    205  return rv;
    206 }
    207 
    208 bool nsTimeupdateRunner::ShouldDispatchTimeupdate() const {
    209  if (mIsMandatory) {
    210    return true;
    211  }
    212 
    213  // If the main thread is busy, tasks may be delayed and dispatched at
    214  // unexpected times. Ensure we don't dispatch `timeupdate` more often
    215  // than once per `TIMEUPDATE_MS`.
    216  const TimeStamp& lastTime = mElement->LastTimeupdateDispatchTime();
    217  return lastTime.IsNull() || TimeStamp::Now() - lastTime >
    218                                  TimeDuration::FromMilliseconds(TIMEUPDATE_MS);
    219 }
    220 
    221 void nsTimeupdateRunner::ReportProfilerMarker() {
    222  if (!profiler_is_collecting_markers()) {
    223    return;
    224  }
    225  auto* videoElement = mElement->AsHTMLVideoElement();
    226  profiler_add_marker("timeupdate", geckoprofiler::category::MEDIA_PLAYBACK, {},
    227                      TimeUpdateMarker{},
    228                      AssertedCast<uint64_t>(mElement->CurrentTime() * 1000),
    229                      GetElementDurationMs(),
    230                      videoElement ? videoElement->MozPaintedFrames() : 0,
    231                      Flow::FromPointer(mElement.get()));
    232 }
    233 
    234 #undef LOG_EVENT
    235 }  // namespace mozilla::dom