Bug 1562021 - part3 : cancel old VTT listener when we start a new load. r=jya,baku
authorAlastor Wu <alwu@mozilla.com>
Wed, 03 Jul 2019 23:23:29 +0000
changeset 481468 4fa9eb527c78ca714064470b9b4a2ad63eed6689
parent 481467 da1f4f3f0c5c27d5fe228b7e0c8c8ef05ca010ac
child 481469 1ff85088fbf482a4a518e74150b7bc6036552374
push id89225
push useralwu@mozilla.com
push dateFri, 05 Jul 2019 18:06:03 +0000
treeherderautoland@084252265e0c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjya, baku
bugs1562021
milestone69.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
Bug 1562021 - part3 : cancel old VTT listener when we start a new load. r=jya,baku When we start a new load, all previous data fetching from the previous listener and all state changing applied to track element should be ignored. Therefore, we add a new method `Cancel()` which owner of the listener should call when we would like to discard current listener. Differential Revision: https://phabricator.services.mozilla.com/D36407
dom/html/HTMLTrackElement.cpp
dom/media/WebVTTListener.cpp
dom/media/WebVTTListener.h
dom/media/webvtt/WebVTTParserWrapper.jsm
dom/media/webvtt/nsIWebVTTParserWrapper.idl
--- a/dom/html/HTMLTrackElement.cpp
+++ b/dom/html/HTMLTrackElement.cpp
@@ -294,19 +294,25 @@ void HTMLTrackElement::LoadResource(RefP
 
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NewURIFromString(src, getter_AddRefs(uri));
   NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
   LOG("Trying to load from src=%s", NS_ConvertUTF16toUTF8(src).get());
 
   if (mChannel) {
     mChannel->Cancel(NS_BINDING_ABORTED);
+    mChannel->SetNotificationCallbacks(nullptr);
     mChannel = nullptr;
   }
 
+  if (mListener) {
+    mListener->Cancel();
+    mListener = nullptr;
+  }
+
   // According to
   // https://www.w3.org/TR/html5/embedded-content-0.html#sourcing-out-of-band-text-tracks
   //
   // "8: If the track element's parent is a media element then let CORS mode
   // be the state of the parent media element's crossorigin content attribute.
   // Otherwise, let CORS mode be No CORS."
   //
   CORSMode corsMode =
--- a/dom/media/WebVTTListener.cpp
+++ b/dom/media/WebVTTListener.cpp
@@ -58,42 +58,56 @@ WebVTTListener::WebVTTListener(HTMLTrack
 WebVTTListener::~WebVTTListener() { LOG("destroyed."); }
 
 NS_IMETHODIMP
 WebVTTListener::GetInterface(const nsIID& aIID, void** aResult) {
   return QueryInterface(aIID, aResult);
 }
 
 nsresult WebVTTListener::LoadResource() {
+  if (IsCanceled()) {
+    return NS_OK;
+  }
   // Exit if we failed to create the WebVTTParserWrapper (vtt.jsm)
   NS_ENSURE_SUCCESS(mParserWrapperError, mParserWrapperError);
 
   mElement->SetReadyState(TextTrackReadyState::Loading);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebVTTListener::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
                                        nsIChannel* aNewChannel, uint32_t aFlags,
                                        nsIAsyncVerifyRedirectCallback* cb) {
+  if (IsCanceled()) {
+    return NS_OK;
+  }
   if (mElement) {
     mElement->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
   }
   cb->OnRedirectVerifyCallback(NS_OK);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebVTTListener::OnStartRequest(nsIRequest* aRequest) {
+  if (IsCanceled()) {
+    return NS_OK;
+  }
+
   LOG("OnStartRequest");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebVTTListener::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+  if (IsCanceled()) {
+    return NS_OK;
+  }
+
   LOG("OnStopRequest");
   if (NS_FAILED(aStatus)) {
     LOG("Got error status");
     mElement->SetReadyState(TextTrackReadyState::FailedToLoad);
   }
   // Attempt to parse any final data the parser might still have.
   mParserWrapper->Flush();
   if (mElement->ReadyState() != TextTrackReadyState::FailedToLoad) {
@@ -106,32 +120,37 @@ WebVTTListener::OnStopRequest(nsIRequest
 }
 
 nsresult WebVTTListener::ParseChunk(nsIInputStream* aInStream, void* aClosure,
                                     const char* aFromSegment,
                                     uint32_t aToOffset, uint32_t aCount,
                                     uint32_t* aWriteCount) {
   nsCString buffer(aFromSegment, aCount);
   WebVTTListener* listener = static_cast<WebVTTListener*>(aClosure);
+  MOZ_ASSERT(!listener->IsCanceled());
 
   if (NS_FAILED(listener->mParserWrapper->Parse(buffer))) {
     LOG_WIHTOUT_ADDRESS(
         "WebVTTListener=%p, Unable to parse chunk of WEBVTT text. Aborting.",
         listener);
     *aWriteCount = 0;
     return NS_ERROR_FAILURE;
   }
 
   *aWriteCount = aCount;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebVTTListener::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream,
                                 uint64_t aOffset, uint32_t aCount) {
+  if (IsCanceled()) {
+    return NS_OK;
+  }
+
   LOG("OnDataAvailable");
   uint32_t count = aCount;
   while (count > 0) {
     uint32_t read;
     nsresult rv = aStream->ReadSegments(ParseChunk, this, count, &read);
     NS_ENSURE_SUCCESS(rv, rv);
     if (!read) {
       return NS_ERROR_FAILURE;
@@ -139,16 +158,17 @@ WebVTTListener::OnDataAvailable(nsIReque
     count -= read;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebVTTListener::OnCue(JS::Handle<JS::Value> aCue, JSContext* aCx) {
+  MOZ_ASSERT(!IsCanceled());
   if (!aCue.isObject()) {
     return NS_ERROR_FAILURE;
   }
 
   JS::Rooted<JSObject*> obj(aCx, &aCue.toObject());
   TextTrackCue* cue = nullptr;
   nsresult rv = UNWRAP_OBJECT(VTTCue, &obj, cue);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -156,25 +176,38 @@ WebVTTListener::OnCue(JS::Handle<JS::Val
   cue->SetTrackElement(mElement);
   mElement->mTrack->AddCue(*cue);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebVTTListener::OnRegion(JS::Handle<JS::Value> aRegion, JSContext* aCx) {
+  MOZ_ASSERT(!IsCanceled());
   // Nothing for this callback to do.
   return NS_OK;
 }
 
 NS_IMETHODIMP
 WebVTTListener::OnParsingError(int32_t errorCode, JSContext* cx) {
+  MOZ_ASSERT(!IsCanceled());
   // We only care about files that have a bad WebVTT file signature right now
   // as that means the file failed to load.
   if (errorCode == ErrorCodes::BadSignature) {
     LOG("parsing error");
     mElement->SetReadyState(TextTrackReadyState::FailedToLoad);
   }
   return NS_OK;
 }
 
+bool WebVTTListener::IsCanceled() const { return mCancel; }
+
+void WebVTTListener::Cancel() {
+  MOZ_ASSERT(!IsCanceled(), "Do not cancel canceled listener again!");
+  LOG("Cancel listen to channel's response.");
+  mCancel = true;
+  mParserWrapper->Cancel();
+  mParserWrapper = nullptr;
+  mElement = nullptr;
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/media/WebVTTListener.h
+++ b/dom/media/WebVTTListener.h
@@ -37,26 +37,35 @@ class WebVTTListener final : public nsIW
   explicit WebVTTListener(HTMLTrackElement* aElement);
 
   /**
    * Loads the WebVTTListener. Must call this in order for the listener to be
    * ready to parse data that is passed to it.
    */
   nsresult LoadResource();
 
+  /**
+   * When this listener is not going to be used anymore, its owner should take
+   * a responsibility to call `Cancel()` to prevent this listener making any
+   * changes for the track element.
+   */
+  bool IsCanceled() const;
+  void Cancel();
+
  private:
   ~WebVTTListener();
 
   // List of error codes returned from the WebVTT parser that we care about.
   enum ErrorCodes { BadSignature = 0 };
   static nsresult ParseChunk(nsIInputStream* aInStream, void* aClosure,
                              const char* aFromSegment, uint32_t aToOffset,
                              uint32_t aCount, uint32_t* aWriteCount);
 
   RefPtr<HTMLTrackElement> mElement;
   nsCOMPtr<nsIWebVTTParserWrapper> mParserWrapper;
   nsresult mParserWrapperError;
+  bool mCancel = false;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_WebVTTListener_h
--- a/dom/media/webvtt/WebVTTParserWrapper.jsm
+++ b/dom/media/webvtt/WebVTTParserWrapper.jsm
@@ -38,16 +38,22 @@ WebVTTParserWrapper.prototype =
     this.parser.oncue = callback.onCue;
     this.parser.onregion = callback.onRegion;
     this.parser.onparsingerror = function(e) {
       // Passing the just the error code back is enough for our needs.
       callback.onParsingError(("code" in e) ? e.code : -1);
     };
   },
 
+  cancel: function() {
+    this.parser.oncue = null;
+    this.parser.onregion = null;
+    this.parser.onparsingerror = null;
+  },
+
   convertCueToDOMTree: function(window, cue)
   {
     return WebVTT.convertCueToDOMTree(window, cue.text);
   },
 
   processCues: function(window, cues, overlay, controls)
   {
     WebVTT.processCues(window, cues, overlay, controls);
--- a/dom/media/webvtt/nsIWebVTTParserWrapper.idl
+++ b/dom/media/webvtt/nsIWebVTTParserWrapper.idl
@@ -48,16 +48,21 @@ interface nsIWebVTTParserWrapper : nsISu
    * and onRegion callbacks.
    *
    * @param callback The nsIWebVTTListener object that exposes onCue and
    *                 onRegion callbacks for the parser.
    */
   void watch(in nsIWebVTTListener callback);
 
   /**
+   * Cancel watching notifications which parser would send.
+   */
+  void cancel();
+
+  /**
    * Convert the text content of a WebVTT cue to a document fragment so that
    * we can display it on the page.
    *
    * @param window A window object with which the document fragment will be
    *               created.
    * @param cue    The cue whose content will be converted to a document
    *               fragment.
    */