Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "mozilla/dom/Element.h"
8 : #include "mozilla/dom/HTMLMediaElement.h"
9 : #include "mozilla/dom/HTMLTrackElement.h"
10 : #include "mozilla/dom/HTMLTrackElementBinding.h"
11 : #include "mozilla/dom/HTMLUnknownElement.h"
12 : #include "nsIContentPolicy.h"
13 : #include "mozilla/LoadInfo.h"
14 : #include "WebVTTListener.h"
15 : #include "nsAttrValueInlines.h"
16 : #include "nsCOMPtr.h"
17 : #include "nsContentPolicyUtils.h"
18 : #include "nsContentUtils.h"
19 : #include "nsCycleCollectionParticipant.h"
20 : #include "nsGenericHTMLElement.h"
21 : #include "nsGkAtoms.h"
22 : #include "nsIAsyncVerifyRedirectCallback.h"
23 : #include "nsICachingChannel.h"
24 : #include "nsIChannelEventSink.h"
25 : #include "nsIContentPolicy.h"
26 : #include "nsIContentSecurityPolicy.h"
27 : #include "nsIDocument.h"
28 : #include "nsIDOMEventTarget.h"
29 : #include "nsIDOMHTMLMediaElement.h"
30 : #include "nsIHttpChannel.h"
31 : #include "nsIInterfaceRequestor.h"
32 : #include "nsILoadGroup.h"
33 : #include "nsIObserver.h"
34 : #include "nsIStreamListener.h"
35 : #include "nsISupportsImpl.h"
36 : #include "nsISupportsPrimitives.h"
37 : #include "nsMappedAttributes.h"
38 : #include "nsNetUtil.h"
39 : #include "nsRuleData.h"
40 : #include "nsStyleConsts.h"
41 : #include "nsThreadUtils.h"
42 : #include "nsVideoFrame.h"
43 :
44 : static mozilla::LazyLogModule gTrackElementLog("nsTrackElement");
45 : #define LOG(type, msg) MOZ_LOG(gTrackElementLog, type, msg)
46 :
47 : // Replace the usual NS_IMPL_NS_NEW_HTML_ELEMENT(Track) so
48 : // we can return an UnknownElement instead when pref'd off.
49 : nsGenericHTMLElement*
50 0 : NS_NewHTMLTrackElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
51 : mozilla::dom::FromParser aFromParser)
52 : {
53 0 : return new mozilla::dom::HTMLTrackElement(aNodeInfo);
54 : }
55 :
56 : namespace mozilla {
57 : namespace dom {
58 :
59 : // Map html attribute string values to TextTrackKind enums.
60 : static constexpr nsAttrValue::EnumTable kKindTable[] = {
61 : { "subtitles", static_cast<int16_t>(TextTrackKind::Subtitles) },
62 : { "captions", static_cast<int16_t>(TextTrackKind::Captions) },
63 : { "descriptions", static_cast<int16_t>(TextTrackKind::Descriptions) },
64 : { "chapters", static_cast<int16_t>(TextTrackKind::Chapters) },
65 : { "metadata", static_cast<int16_t>(TextTrackKind::Metadata) },
66 : { nullptr, 0 }
67 : };
68 :
69 : // Invalid values are treated as "metadata" in ParseAttribute, but if no value
70 : // at all is specified, it's treated as "subtitles" in GetKind
71 : static constexpr const nsAttrValue::EnumTable* kKindTableInvalidValueDefault = &kKindTable[4];
72 :
73 : class WindowDestroyObserver final : public nsIObserver
74 : {
75 : NS_DECL_ISUPPORTS
76 :
77 : public:
78 0 : explicit WindowDestroyObserver(HTMLTrackElement* aElement, uint64_t aWinID)
79 0 : : mTrackElement(aElement)
80 0 : , mInnerID(aWinID)
81 : {
82 0 : RegisterWindowDestroyObserver();
83 0 : }
84 0 : void RegisterWindowDestroyObserver()
85 : {
86 0 : nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
87 0 : if (obs) {
88 0 : obs->AddObserver(this, "inner-window-destroyed", false);
89 : }
90 0 : }
91 0 : void UnRegisterWindowDestroyObserver()
92 : {
93 0 : nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
94 0 : if (obs) {
95 0 : obs->RemoveObserver(this, "inner-window-destroyed");
96 : }
97 0 : mTrackElement = nullptr;
98 0 : }
99 0 : NS_IMETHODIMP Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) override
100 : {
101 0 : MOZ_ASSERT(NS_IsMainThread());
102 0 : if (strcmp(aTopic, "inner-window-destroyed") == 0) {
103 0 : nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
104 0 : NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
105 : uint64_t innerID;
106 0 : nsresult rv = wrapper->GetData(&innerID);
107 0 : NS_ENSURE_SUCCESS(rv, rv);
108 0 : if (innerID == mInnerID) {
109 0 : if (mTrackElement) {
110 0 : mTrackElement->NotifyShutdown();
111 : }
112 0 : UnRegisterWindowDestroyObserver();
113 : }
114 : }
115 0 : return NS_OK;
116 : }
117 :
118 : private:
119 0 : ~WindowDestroyObserver() {};
120 : HTMLTrackElement* mTrackElement;
121 : uint64_t mInnerID;
122 : };
123 0 : NS_IMPL_ISUPPORTS(WindowDestroyObserver, nsIObserver);
124 :
125 : /** HTMLTrackElement */
126 0 : HTMLTrackElement::HTMLTrackElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
127 : : nsGenericHTMLElement(aNodeInfo)
128 : , mLoadResourceDispatched(false)
129 0 : , mWindowDestroyObserver(nullptr)
130 : {
131 0 : nsISupports* parentObject = OwnerDoc()->GetParentObject();
132 0 : NS_ENSURE_TRUE_VOID(parentObject);
133 0 : nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject);
134 0 : if (window) {
135 0 : mWindowDestroyObserver = new WindowDestroyObserver(this, window->WindowID());
136 : }
137 : }
138 :
139 0 : HTMLTrackElement::~HTMLTrackElement()
140 : {
141 0 : if (mWindowDestroyObserver) {
142 0 : mWindowDestroyObserver->UnRegisterWindowDestroyObserver();
143 : }
144 0 : NotifyShutdown();
145 0 : }
146 :
147 0 : NS_IMPL_ELEMENT_CLONE(HTMLTrackElement)
148 :
149 0 : NS_IMPL_ADDREF_INHERITED(HTMLTrackElement, Element)
150 0 : NS_IMPL_RELEASE_INHERITED(HTMLTrackElement, Element)
151 :
152 0 : NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLTrackElement, nsGenericHTMLElement,
153 : mTrack, mMediaParent, mListener)
154 :
155 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLTrackElement)
156 0 : NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
157 :
158 : void
159 0 : HTMLTrackElement::GetKind(DOMString& aKind) const
160 : {
161 0 : GetEnumAttr(nsGkAtoms::kind, kKindTable[0].tag, aKind);
162 0 : }
163 :
164 : void
165 0 : HTMLTrackElement::OnChannelRedirect(nsIChannel* aChannel,
166 : nsIChannel* aNewChannel,
167 : uint32_t aFlags)
168 : {
169 0 : NS_ASSERTION(aChannel == mChannel, "Channels should match!");
170 0 : mChannel = aNewChannel;
171 0 : }
172 :
173 : JSObject*
174 0 : HTMLTrackElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
175 : {
176 0 : return HTMLTrackElementBinding::Wrap(aCx, this, aGivenProto);
177 : }
178 :
179 : TextTrack*
180 0 : HTMLTrackElement::GetTrack()
181 : {
182 0 : if (!mTrack) {
183 0 : CreateTextTrack();
184 : }
185 :
186 0 : return mTrack;
187 : }
188 :
189 : void
190 0 : HTMLTrackElement::CreateTextTrack()
191 : {
192 0 : nsString label, srcLang;
193 0 : GetSrclang(srcLang);
194 0 : GetLabel(label);
195 :
196 : TextTrackKind kind;
197 0 : if (const nsAttrValue* value = GetParsedAttr(nsGkAtoms::kind)) {
198 0 : kind = static_cast<TextTrackKind>(value->GetEnumValue());
199 : } else {
200 0 : kind = TextTrackKind::Subtitles;
201 : }
202 :
203 : nsISupports* parentObject =
204 0 : OwnerDoc()->GetParentObject();
205 :
206 0 : NS_ENSURE_TRUE_VOID(parentObject);
207 :
208 0 : nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject);
209 : mTrack = new TextTrack(window, kind, label, srcLang,
210 : TextTrackMode::Disabled,
211 : TextTrackReadyState::NotLoaded,
212 0 : TextTrackSource::Track);
213 0 : mTrack->SetTrackElement(this);
214 :
215 0 : if (mMediaParent) {
216 0 : mMediaParent->AddTextTrack(mTrack);
217 : }
218 : }
219 :
220 : bool
221 0 : HTMLTrackElement::ParseAttribute(int32_t aNamespaceID,
222 : nsIAtom* aAttribute,
223 : const nsAString& aValue,
224 : nsAttrValue& aResult)
225 : {
226 0 : if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::kind) {
227 : // Case-insensitive lookup, with the first element as the default.
228 : return aResult.ParseEnumValue(aValue, kKindTable, false,
229 0 : kKindTableInvalidValueDefault);
230 : }
231 :
232 : // Otherwise call the generic implementation.
233 0 : return nsGenericHTMLElement::ParseAttribute(aNamespaceID,
234 : aAttribute,
235 : aValue,
236 0 : aResult);
237 : }
238 :
239 : void
240 0 : HTMLTrackElement::SetSrc(const nsAString& aSrc, ErrorResult& aError)
241 : {
242 0 : SetHTMLAttr(nsGkAtoms::src, aSrc, aError);
243 0 : uint16_t oldReadyState = ReadyState();
244 0 : SetReadyState(TextTrackReadyState::NotLoaded);
245 0 : if (!mMediaParent) {
246 0 : return;
247 : }
248 0 : if (mTrack && (oldReadyState != TextTrackReadyState::NotLoaded)) {
249 : // Remove all the cues in MediaElement.
250 0 : mMediaParent->RemoveTextTrack(mTrack);
251 : // Recreate mTrack.
252 0 : CreateTextTrack();
253 : }
254 : // Stop WebVTTListener.
255 0 : mListener = nullptr;
256 0 : if (mChannel) {
257 0 : mChannel->Cancel(NS_BINDING_ABORTED);
258 0 : mChannel = nullptr;
259 : }
260 :
261 0 : DispatchLoadResource();
262 : }
263 :
264 : void
265 0 : HTMLTrackElement::DispatchLoadResource()
266 : {
267 0 : if (!mLoadResourceDispatched) {
268 : RefPtr<Runnable> r =
269 0 : NewRunnableMethod("dom::HTMLTrackElement::LoadResource",
270 : this,
271 0 : &HTMLTrackElement::LoadResource);
272 0 : nsContentUtils::RunInStableState(r.forget());
273 0 : mLoadResourceDispatched = true;
274 : }
275 0 : }
276 :
277 : void
278 0 : HTMLTrackElement::LoadResource()
279 : {
280 0 : mLoadResourceDispatched = false;
281 :
282 : // Find our 'src' url
283 0 : nsAutoString src;
284 0 : if (!GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
285 0 : return;
286 : }
287 :
288 0 : nsCOMPtr<nsIURI> uri;
289 0 : nsresult rv = NewURIFromString(src, getter_AddRefs(uri));
290 0 : NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
291 0 : LOG(LogLevel::Info, ("%p Trying to load from src=%s", this,
292 : NS_ConvertUTF16toUTF8(src).get()));
293 :
294 0 : if (mChannel) {
295 0 : mChannel->Cancel(NS_BINDING_ABORTED);
296 0 : mChannel = nullptr;
297 : }
298 :
299 : // According to https://www.w3.org/TR/html5/embedded-content-0.html#sourcing-out-of-band-text-tracks
300 : //
301 : // "8: If the track element's parent is a media element then let CORS mode
302 : // be the state of the parent media element's crossorigin content attribute.
303 : // Otherwise, let CORS mode be No CORS."
304 : //
305 0 : CORSMode corsMode = mMediaParent ? mMediaParent->GetCORSMode() : CORS_NONE;
306 :
307 : // Determine the security flag based on corsMode.
308 : nsSecurityFlags secFlags;
309 0 : if (CORS_NONE == corsMode) {
310 : // Same-origin is required for track element.
311 0 : secFlags = nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS;
312 : } else {
313 0 : secFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
314 0 : if (CORS_ANONYMOUS == corsMode) {
315 0 : secFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
316 0 : } else if (CORS_USE_CREDENTIALS == corsMode) {
317 0 : secFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
318 : } else {
319 0 : NS_WARNING("Unknown CORS mode.");
320 0 : secFlags = nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS;
321 : }
322 : }
323 :
324 0 : nsCOMPtr<nsIChannel> channel;
325 0 : nsCOMPtr<nsILoadGroup> loadGroup = OwnerDoc()->GetDocumentLoadGroup();
326 0 : rv = NS_NewChannel(getter_AddRefs(channel),
327 : uri,
328 0 : static_cast<Element*>(this),
329 : secFlags,
330 : nsIContentPolicy::TYPE_INTERNAL_TRACK,
331 : loadGroup,
332 : nullptr, // aCallbacks
333 0 : nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI);
334 :
335 0 : NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
336 :
337 0 : mListener = new WebVTTListener(this);
338 0 : rv = mListener->LoadResource();
339 0 : NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
340 0 : channel->SetNotificationCallbacks(mListener);
341 :
342 0 : LOG(LogLevel::Debug, ("opening webvtt channel"));
343 0 : rv = channel->AsyncOpen2(mListener);
344 :
345 0 : if (NS_FAILED(rv)) {
346 0 : SetReadyState(TextTrackReadyState::FailedToLoad);
347 0 : return;
348 : }
349 :
350 0 : mChannel = channel;
351 : }
352 :
353 : nsresult
354 0 : HTMLTrackElement::BindToTree(nsIDocument* aDocument,
355 : nsIContent* aParent,
356 : nsIContent* aBindingParent,
357 : bool aCompileEventHandlers)
358 : {
359 0 : nsresult rv = nsGenericHTMLElement::BindToTree(aDocument,
360 : aParent,
361 : aBindingParent,
362 0 : aCompileEventHandlers);
363 0 : NS_ENSURE_SUCCESS(rv, rv);
364 :
365 0 : LOG(LogLevel::Debug, ("Track Element bound to tree."));
366 0 : if (!aParent || !aParent->IsNodeOfType(nsINode::eMEDIA)) {
367 0 : return NS_OK;
368 : }
369 :
370 : // Store our parent so we can look up its frame for display.
371 0 : if (!mMediaParent) {
372 0 : mMediaParent = static_cast<HTMLMediaElement*>(aParent);
373 :
374 : // TODO: separate notification for 'alternate' tracks?
375 0 : mMediaParent->NotifyAddedSource();
376 0 : LOG(LogLevel::Debug, ("Track element sent notification to parent."));
377 :
378 : // We may already have a TextTrack at this point if GetTrack() has already
379 : // been called. This happens, for instance, if script tries to get the
380 : // TextTrack before its mTrackElement has been bound to the DOM tree.
381 0 : if (!mTrack) {
382 0 : CreateTextTrack();
383 : }
384 0 : DispatchLoadResource();
385 : }
386 :
387 0 : return NS_OK;
388 : }
389 :
390 : void
391 0 : HTMLTrackElement::UnbindFromTree(bool aDeep, bool aNullParent)
392 : {
393 0 : if (mMediaParent && aNullParent) {
394 : // mTrack can be null if HTMLTrackElement::LoadResource has never been
395 : // called.
396 0 : if (mTrack) {
397 0 : mMediaParent->RemoveTextTrack(mTrack);
398 0 : mMediaParent->UpdateReadyState();
399 : }
400 0 : mMediaParent = nullptr;
401 : }
402 :
403 0 : nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
404 0 : }
405 :
406 : uint16_t
407 0 : HTMLTrackElement::ReadyState() const
408 : {
409 0 : if (!mTrack) {
410 0 : return TextTrackReadyState::NotLoaded;
411 : }
412 :
413 0 : return mTrack->ReadyState();
414 : }
415 :
416 : void
417 0 : HTMLTrackElement::SetReadyState(uint16_t aReadyState)
418 : {
419 0 : if (ReadyState() == aReadyState) {
420 0 : return;
421 : }
422 :
423 0 : if (mTrack) {
424 0 : switch (aReadyState) {
425 : case TextTrackReadyState::Loaded:
426 0 : DispatchTrackRunnable(NS_LITERAL_STRING("load"));
427 0 : break;
428 : case TextTrackReadyState::FailedToLoad:
429 0 : DispatchTrackRunnable(NS_LITERAL_STRING("error"));
430 0 : break;
431 : }
432 0 : mTrack->SetReadyState(aReadyState);
433 : }
434 : }
435 :
436 : void
437 0 : HTMLTrackElement::DispatchTrackRunnable(const nsString& aEventName)
438 : {
439 0 : nsIDocument* doc = OwnerDoc();
440 0 : if (!doc) {
441 0 : return;
442 : }
443 0 : nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<const nsString>(
444 : "dom::HTMLTrackElement::DispatchTrustedEvent",
445 : this,
446 : &HTMLTrackElement::DispatchTrustedEvent,
447 0 : aEventName);
448 : doc->Dispatch("HTMLTrackElement::DispatchTrackRunnable",
449 0 : TaskCategory::Other, runnable.forget());
450 : }
451 :
452 : void
453 0 : HTMLTrackElement::DispatchTrustedEvent(const nsAString& aName)
454 : {
455 0 : nsIDocument* doc = OwnerDoc();
456 0 : if (!doc) {
457 0 : return;
458 : }
459 0 : nsContentUtils::DispatchTrustedEvent(doc, static_cast<nsIContent*>(this),
460 0 : aName, false, false);
461 : }
462 :
463 : void
464 0 : HTMLTrackElement::DropChannel()
465 : {
466 0 : mChannel = nullptr;
467 0 : }
468 :
469 : void
470 0 : HTMLTrackElement::NotifyShutdown()
471 : {
472 0 : if (mChannel) {
473 0 : mChannel->Cancel(NS_BINDING_ABORTED);
474 : }
475 0 : mChannel = nullptr;
476 0 : mListener = nullptr;
477 0 : }
478 :
479 : } // namespace dom
480 : } // namespace mozilla
|