1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "WebVTTListener.h"
7 #include "mozilla/CycleCollectedJSContext.h"
8 #include "mozilla/dom/Document.h"
9 #include "mozilla/dom/HTMLTrackElement.h"
10 #include "mozilla/dom/TextTrackCue.h"
11 #include "mozilla/dom/TextTrackRegion.h"
12 #include "mozilla/dom/VTTRegionBinding.h"
13 #include "nsComponentManagerUtils.h"
14 #include "nsIAsyncVerifyRedirectCallback.h"
15 #include "nsIInputStream.h"
16 
17 extern mozilla::LazyLogModule gTextTrackLog;
18 #define LOG(msg, ...)                     \
19   MOZ_LOG(gTextTrackLog, LogLevel::Debug, \
20           ("WebVTTListener=%p, " msg, this, ##__VA_ARGS__))
21 #define LOG_WIHTOUT_ADDRESS(msg, ...) \
22   MOZ_LOG(gTextTrackLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
23 
24 namespace mozilla::dom {
25 
NS_IMPL_CYCLE_COLLECTION(WebVTTListener,mElement,mParserWrapper)26 NS_IMPL_CYCLE_COLLECTION(WebVTTListener, mElement, mParserWrapper)
27 
28 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebVTTListener)
29   NS_INTERFACE_MAP_ENTRY(nsIWebVTTListener)
30   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
31   NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
32   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
33   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebVTTListener)
34 NS_INTERFACE_MAP_END
35 
36 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebVTTListener)
37 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebVTTListener)
38 
39 WebVTTListener::WebVTTListener(HTMLTrackElement* aElement)
40     : mElement(aElement), mParserWrapperError(NS_OK) {
41   MOZ_ASSERT(mElement, "Must pass an element to the callback");
42   LOG("Created listener for track element %p", aElement);
43   MOZ_DIAGNOSTIC_ASSERT(
44       CycleCollectedJSContext::Get() &&
45       !CycleCollectedJSContext::Get()->IsInStableOrMetaStableState());
46   mParserWrapper = do_CreateInstance(NS_WEBVTTPARSERWRAPPER_CONTRACTID,
47                                      &mParserWrapperError);
48   if (NS_SUCCEEDED(mParserWrapperError)) {
49     nsPIDOMWindowInner* window = mElement->OwnerDoc()->GetInnerWindow();
50     mParserWrapperError = mParserWrapper->LoadParser(window);
51   }
52   if (NS_SUCCEEDED(mParserWrapperError)) {
53     mParserWrapperError = mParserWrapper->Watch(this);
54   }
55 }
56 
~WebVTTListener()57 WebVTTListener::~WebVTTListener() { LOG("destroyed."); }
58 
59 NS_IMETHODIMP
GetInterface(const nsIID & aIID,void ** aResult)60 WebVTTListener::GetInterface(const nsIID& aIID, void** aResult) {
61   return QueryInterface(aIID, aResult);
62 }
63 
LoadResource()64 nsresult WebVTTListener::LoadResource() {
65   if (IsCanceled()) {
66     return NS_OK;
67   }
68   // Exit if we failed to create the WebVTTParserWrapper (vtt.jsm)
69   NS_ENSURE_SUCCESS(mParserWrapperError, mParserWrapperError);
70 
71   mElement->SetReadyState(TextTrackReadyState::Loading);
72   return NS_OK;
73 }
74 
75 NS_IMETHODIMP
AsyncOnChannelRedirect(nsIChannel * aOldChannel,nsIChannel * aNewChannel,uint32_t aFlags,nsIAsyncVerifyRedirectCallback * cb)76 WebVTTListener::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
77                                        nsIChannel* aNewChannel, uint32_t aFlags,
78                                        nsIAsyncVerifyRedirectCallback* cb) {
79   if (IsCanceled()) {
80     return NS_OK;
81   }
82   if (mElement) {
83     mElement->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
84   }
85   cb->OnRedirectVerifyCallback(NS_OK);
86   return NS_OK;
87 }
88 
89 NS_IMETHODIMP
OnStartRequest(nsIRequest * aRequest)90 WebVTTListener::OnStartRequest(nsIRequest* aRequest) {
91   if (IsCanceled()) {
92     return NS_OK;
93   }
94 
95   LOG("OnStartRequest");
96   mElement->DispatchTestEvent(u"mozStartedLoadingTextTrack"_ns);
97   return NS_OK;
98 }
99 
100 NS_IMETHODIMP
OnStopRequest(nsIRequest * aRequest,nsresult aStatus)101 WebVTTListener::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
102   if (IsCanceled()) {
103     return NS_OK;
104   }
105 
106   LOG("OnStopRequest");
107   if (NS_FAILED(aStatus)) {
108     LOG("Got error status");
109     mElement->SetReadyState(TextTrackReadyState::FailedToLoad);
110   }
111   // Attempt to parse any final data the parser might still have.
112   mParserWrapper->Flush();
113   if (mElement->ReadyState() != TextTrackReadyState::FailedToLoad) {
114     mElement->SetReadyState(TextTrackReadyState::Loaded);
115   }
116 
117   mElement->CancelChannelAndListener();
118 
119   return aStatus;
120 }
121 
ParseChunk(nsIInputStream * aInStream,void * aClosure,const char * aFromSegment,uint32_t aToOffset,uint32_t aCount,uint32_t * aWriteCount)122 nsresult WebVTTListener::ParseChunk(nsIInputStream* aInStream, void* aClosure,
123                                     const char* aFromSegment,
124                                     uint32_t aToOffset, uint32_t aCount,
125                                     uint32_t* aWriteCount) {
126   nsCString buffer(aFromSegment, aCount);
127   WebVTTListener* listener = static_cast<WebVTTListener*>(aClosure);
128   MOZ_ASSERT(!listener->IsCanceled());
129 
130   if (NS_FAILED(listener->mParserWrapper->Parse(buffer))) {
131     LOG_WIHTOUT_ADDRESS(
132         "WebVTTListener=%p, Unable to parse chunk of WEBVTT text. Aborting.",
133         listener);
134     *aWriteCount = 0;
135     return NS_ERROR_FAILURE;
136   }
137 
138   *aWriteCount = aCount;
139   return NS_OK;
140 }
141 
142 NS_IMETHODIMP
OnDataAvailable(nsIRequest * aRequest,nsIInputStream * aStream,uint64_t aOffset,uint32_t aCount)143 WebVTTListener::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream,
144                                 uint64_t aOffset, uint32_t aCount) {
145   if (IsCanceled()) {
146     return NS_OK;
147   }
148 
149   LOG("OnDataAvailable");
150   uint32_t count = aCount;
151   while (count > 0) {
152     uint32_t read;
153     nsresult rv = aStream->ReadSegments(ParseChunk, this, count, &read);
154     NS_ENSURE_SUCCESS(rv, rv);
155     if (!read) {
156       return NS_ERROR_FAILURE;
157     }
158     count -= read;
159   }
160 
161   return NS_OK;
162 }
163 
164 NS_IMETHODIMP
OnCue(JS::Handle<JS::Value> aCue,JSContext * aCx)165 WebVTTListener::OnCue(JS::Handle<JS::Value> aCue, JSContext* aCx) {
166   MOZ_ASSERT(!IsCanceled());
167   if (!aCue.isObject()) {
168     return NS_ERROR_FAILURE;
169   }
170 
171   JS::Rooted<JSObject*> obj(aCx, &aCue.toObject());
172   TextTrackCue* cue = nullptr;
173   nsresult rv = UNWRAP_OBJECT(VTTCue, &obj, cue);
174   NS_ENSURE_SUCCESS(rv, rv);
175 
176   cue->SetTrackElement(mElement);
177   mElement->mTrack->AddCue(*cue);
178 
179   return NS_OK;
180 }
181 
182 NS_IMETHODIMP
OnRegion(JS::Handle<JS::Value> aRegion,JSContext * aCx)183 WebVTTListener::OnRegion(JS::Handle<JS::Value> aRegion, JSContext* aCx) {
184   MOZ_ASSERT(!IsCanceled());
185   // Nothing for this callback to do.
186   return NS_OK;
187 }
188 
189 NS_IMETHODIMP
OnParsingError(int32_t errorCode,JSContext * cx)190 WebVTTListener::OnParsingError(int32_t errorCode, JSContext* cx) {
191   MOZ_ASSERT(!IsCanceled());
192   // We only care about files that have a bad WebVTT file signature right now
193   // as that means the file failed to load.
194   if (errorCode == ErrorCodes::BadSignature) {
195     LOG("parsing error");
196     mElement->SetReadyState(TextTrackReadyState::FailedToLoad);
197   }
198   return NS_OK;
199 }
200 
IsCanceled() const201 bool WebVTTListener::IsCanceled() const { return mCancel; }
202 
Cancel()203 void WebVTTListener::Cancel() {
204   MOZ_ASSERT(!IsCanceled(), "Do not cancel canceled listener again!");
205   LOG("Cancel listen to channel's response.");
206   mCancel = true;
207   mParserWrapper->Cancel();
208   mParserWrapper = nullptr;
209   mElement = nullptr;
210 }
211 
212 }  // namespace mozilla::dom
213