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