Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / html / HTMLTrackElement.cpp
blobe0e31dbb1b38712a0c942fcc6a4c74c92619ff32
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/. */
7 #include "mozilla/dom/HTMLTrackElement.h"
8 #include "mozilla/dom/Element.h"
9 #include "mozilla/dom/HTMLMediaElement.h"
10 #include "mozilla/dom/WebVTTListener.h"
11 #include "mozilla/LoadInfo.h"
12 #include "mozilla/StaticPrefs_media.h"
13 #include "mozilla/dom/HTMLTrackElementBinding.h"
14 #include "mozilla/dom/UnbindContext.h"
15 #include "nsAttrValueInlines.h"
16 #include "nsCOMPtr.h"
17 #include "nsContentUtils.h"
18 #include "nsCycleCollectionParticipant.h"
19 #include "nsGenericHTMLElement.h"
20 #include "nsGkAtoms.h"
21 #include "nsIContentPolicy.h"
22 #include "mozilla/dom/Document.h"
23 #include "nsILoadGroup.h"
24 #include "nsIObserver.h"
25 #include "nsIObserverService.h"
26 #include "nsIScriptError.h"
27 #include "nsISupportsImpl.h"
28 #include "nsISupportsPrimitives.h"
29 #include "nsNetUtil.h"
30 #include "nsStyleConsts.h"
31 #include "nsThreadUtils.h"
33 extern mozilla::LazyLogModule gTextTrackLog;
34 #define LOG(msg, ...) \
35 MOZ_LOG(gTextTrackLog, LogLevel::Verbose, \
36 ("TextTrackElement=%p, " msg, this, ##__VA_ARGS__))
38 // Replace the usual NS_IMPL_NS_NEW_HTML_ELEMENT(Track) so
39 // we can return an UnknownElement instead when pref'd off.
40 nsGenericHTMLElement* NS_NewHTMLTrackElement(
41 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
42 mozilla::dom::FromParser aFromParser) {
43 RefPtr<mozilla::dom::NodeInfo> nodeInfo(aNodeInfo);
44 auto* nim = nodeInfo->NodeInfoManager();
45 return new (nim) mozilla::dom::HTMLTrackElement(nodeInfo.forget());
48 namespace mozilla::dom {
50 // Map html attribute string values to TextTrackKind enums.
51 static constexpr nsAttrValue::EnumTable kKindTable[] = {
52 {"subtitles", static_cast<int16_t>(TextTrackKind::Subtitles)},
53 {"captions", static_cast<int16_t>(TextTrackKind::Captions)},
54 {"descriptions", static_cast<int16_t>(TextTrackKind::Descriptions)},
55 {"chapters", static_cast<int16_t>(TextTrackKind::Chapters)},
56 {"metadata", static_cast<int16_t>(TextTrackKind::Metadata)},
57 {nullptr, 0}};
59 // Invalid values are treated as "metadata" in ParseAttribute, but if no value
60 // at all is specified, it's treated as "subtitles" in GetKind
61 static constexpr const nsAttrValue::EnumTable* kKindTableInvalidValueDefault =
62 &kKindTable[4];
64 class WindowDestroyObserver final : public nsIObserver {
65 NS_DECL_ISUPPORTS
67 public:
68 explicit WindowDestroyObserver(HTMLTrackElement* aElement, uint64_t aWinID)
69 : mTrackElement(aElement), mInnerID(aWinID) {
70 RegisterWindowDestroyObserver();
72 void RegisterWindowDestroyObserver() {
73 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
74 if (obs) {
75 obs->AddObserver(this, "inner-window-destroyed", false);
78 void UnRegisterWindowDestroyObserver() {
79 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
80 if (obs) {
81 obs->RemoveObserver(this, "inner-window-destroyed");
83 mTrackElement = nullptr;
85 NS_IMETHODIMP Observe(nsISupports* aSubject, const char* aTopic,
86 const char16_t* aData) override {
87 MOZ_ASSERT(NS_IsMainThread());
88 if (strcmp(aTopic, "inner-window-destroyed") == 0) {
89 nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
90 NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
91 uint64_t innerID;
92 nsresult rv = wrapper->GetData(&innerID);
93 NS_ENSURE_SUCCESS(rv, rv);
94 if (innerID == mInnerID) {
95 if (mTrackElement) {
96 mTrackElement->CancelChannelAndListener();
98 UnRegisterWindowDestroyObserver();
101 return NS_OK;
104 private:
105 ~WindowDestroyObserver() = default;
107 HTMLTrackElement* mTrackElement;
108 uint64_t mInnerID;
110 NS_IMPL_ISUPPORTS(WindowDestroyObserver, nsIObserver);
112 /** HTMLTrackElement */
113 HTMLTrackElement::HTMLTrackElement(
114 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
115 : nsGenericHTMLElement(std::move(aNodeInfo)),
116 mLoadResourceDispatched(false),
117 mWindowDestroyObserver(nullptr) {
118 nsISupports* parentObject = OwnerDoc()->GetParentObject();
119 NS_ENSURE_TRUE_VOID(parentObject);
120 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject);
121 if (window) {
122 mWindowDestroyObserver =
123 new WindowDestroyObserver(this, window->WindowID());
127 HTMLTrackElement::~HTMLTrackElement() {
128 if (mWindowDestroyObserver) {
129 mWindowDestroyObserver->UnRegisterWindowDestroyObserver();
131 CancelChannelAndListener();
134 NS_IMPL_ELEMENT_CLONE(HTMLTrackElement)
136 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLTrackElement, nsGenericHTMLElement,
137 mTrack, mMediaParent, mListener)
139 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLTrackElement,
140 nsGenericHTMLElement)
142 void HTMLTrackElement::GetKind(DOMString& aKind) const {
143 GetEnumAttr(nsGkAtoms::kind, kKindTable[0].tag, aKind);
146 void HTMLTrackElement::OnChannelRedirect(nsIChannel* aChannel,
147 nsIChannel* aNewChannel,
148 uint32_t aFlags) {
149 NS_ASSERTION(aChannel == mChannel, "Channels should match!");
150 mChannel = aNewChannel;
153 JSObject* HTMLTrackElement::WrapNode(JSContext* aCx,
154 JS::Handle<JSObject*> aGivenProto) {
155 return HTMLTrackElement_Binding::Wrap(aCx, this, aGivenProto);
158 TextTrack* HTMLTrackElement::GetTrack() {
159 if (!mTrack) {
160 CreateTextTrack();
162 return mTrack;
165 void HTMLTrackElement::CreateTextTrack() {
166 nsISupports* parentObject = OwnerDoc()->GetParentObject();
167 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject);
168 if (!parentObject) {
169 nsContentUtils::ReportToConsole(
170 nsIScriptError::errorFlag, "Media"_ns, OwnerDoc(),
171 nsContentUtils::eDOM_PROPERTIES,
172 "Using track element in non-window context");
173 return;
176 nsString label, srcLang;
177 GetSrclang(srcLang);
178 GetLabel(label);
180 TextTrackKind kind;
181 if (const nsAttrValue* value = GetParsedAttr(nsGkAtoms::kind)) {
182 kind = static_cast<TextTrackKind>(value->GetEnumValue());
183 } else {
184 kind = TextTrackKind::Subtitles;
187 MOZ_ASSERT(!mTrack, "No need to recreate a text track!");
188 mTrack =
189 new TextTrack(window, kind, label, srcLang, TextTrackMode::Disabled,
190 TextTrackReadyState::NotLoaded, TextTrackSource::Track);
191 mTrack->SetTrackElement(this);
194 bool HTMLTrackElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
195 const nsAString& aValue,
196 nsIPrincipal* aMaybeScriptedPrincipal,
197 nsAttrValue& aResult) {
198 if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::kind) {
199 // Case-insensitive lookup, with the first element as the default.
200 return aResult.ParseEnumValue(aValue, kKindTable, false,
201 kKindTableInvalidValueDefault);
204 // Otherwise call the generic implementation.
205 return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
206 aMaybeScriptedPrincipal, aResult);
209 void HTMLTrackElement::SetSrc(const nsAString& aSrc, ErrorResult& aError) {
210 LOG("Set src=%s", NS_ConvertUTF16toUTF8(aSrc).get());
212 nsAutoString src;
213 if (GetAttr(nsGkAtoms::src, src) && src == aSrc) {
214 LOG("No need to reload for same src url");
215 return;
218 SetHTMLAttr(nsGkAtoms::src, aSrc, aError);
219 SetReadyState(TextTrackReadyState::NotLoaded);
220 if (!mMediaParent) {
221 return;
224 // Stop WebVTTListener.
225 mListener = nullptr;
226 if (mChannel) {
227 mChannel->CancelWithReason(NS_BINDING_ABORTED,
228 "HTMLTrackElement::SetSrc"_ns);
229 mChannel = nullptr;
232 MaybeDispatchLoadResource();
235 void HTMLTrackElement::MaybeClearAllCues() {
236 // Empty track's cue list whenever the track element's `src` attribute set,
237 // changed, or removed,
238 // https://html.spec.whatwg.org/multipage/media.html#sourcing-out-of-band-text-tracks:attr-track-src
239 if (!mTrack) {
240 return;
242 mTrack->ClearAllCues();
245 // This function will run partial steps from `start-the-track-processing-model`
246 // and finish the rest of steps in `LoadResource()` during the stable state.
247 // https://html.spec.whatwg.org/multipage/media.html#start-the-track-processing-model
248 void HTMLTrackElement::MaybeDispatchLoadResource() {
249 MOZ_ASSERT(mTrack, "Should have already created text track!");
251 // step2, if the text track's text track mode is not set to one of hidden or
252 // showing, then return.
253 if (mTrack->Mode() == TextTrackMode::Disabled) {
254 LOG("Do not load resource for disable track");
255 return;
258 // step3, if the text track's track element does not have a media element as a
259 // parent, return.
260 if (!mMediaParent) {
261 LOG("Do not load resource for track without media element");
262 return;
265 if (ReadyState() == TextTrackReadyState::Loaded) {
266 LOG("Has already loaded resource");
267 return;
270 // step5, await a stable state and run the rest of steps.
271 if (!mLoadResourceDispatched) {
272 RefPtr<WebVTTListener> listener = new WebVTTListener(this);
273 RefPtr<Runnable> r = NewRunnableMethod<RefPtr<WebVTTListener>>(
274 "dom::HTMLTrackElement::LoadResource", this,
275 &HTMLTrackElement::LoadResource, std::move(listener));
276 nsContentUtils::RunInStableState(r.forget());
277 mLoadResourceDispatched = true;
281 void HTMLTrackElement::LoadResource(RefPtr<WebVTTListener>&& aWebVTTListener) {
282 LOG("LoadResource");
283 mLoadResourceDispatched = false;
285 nsAutoString src;
286 if (!GetAttr(nsGkAtoms::src, src) || src.IsEmpty()) {
287 LOG("Fail to load because no src");
288 SetReadyState(TextTrackReadyState::FailedToLoad);
289 return;
292 nsCOMPtr<nsIURI> uri;
293 nsresult rv = NewURIFromString(src, getter_AddRefs(uri));
294 NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
295 LOG("Trying to load from src=%s", NS_ConvertUTF16toUTF8(src).get());
297 CancelChannelAndListener();
299 // According to
300 // https://www.w3.org/TR/html5/embedded-content-0.html#sourcing-out-of-band-text-tracks
302 // "8: If the track element's parent is a media element then let CORS mode
303 // be the state of the parent media element's crossorigin content attribute.
304 // Otherwise, let CORS mode be No CORS."
306 CORSMode corsMode =
307 mMediaParent ? AttrValueToCORSMode(
308 mMediaParent->GetParsedAttr(nsGkAtoms::crossorigin))
309 : CORS_NONE;
311 // Determine the security flag based on corsMode.
312 nsSecurityFlags secFlags;
313 if (CORS_NONE == corsMode) {
314 // Same-origin is required for track element.
315 secFlags = nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT;
316 } else {
317 secFlags = nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT;
318 if (CORS_ANONYMOUS == corsMode) {
319 secFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
320 } else if (CORS_USE_CREDENTIALS == corsMode) {
321 secFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
322 } else {
323 NS_WARNING("Unknown CORS mode.");
324 secFlags = nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT;
328 mListener = std::move(aWebVTTListener);
329 // This will do 6. Set the text track readiness state to loading.
330 rv = mListener->LoadResource();
331 NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
333 Document* doc = OwnerDoc();
334 if (!doc) {
335 return;
338 // 9. End the synchronous section, continuing the remaining steps in parallel.
339 nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
340 "dom::HTMLTrackElement::LoadResource",
341 [self = RefPtr<HTMLTrackElement>(this), this, uri, secFlags]() {
342 if (!mListener) {
343 // Shutdown got called, abort.
344 return;
346 nsCOMPtr<nsIChannel> channel;
347 nsCOMPtr<nsILoadGroup> loadGroup = OwnerDoc()->GetDocumentLoadGroup();
348 nsresult rv = NS_NewChannel(getter_AddRefs(channel), uri,
349 static_cast<Element*>(this), secFlags,
350 nsIContentPolicy::TYPE_INTERNAL_TRACK,
351 nullptr, // PerformanceStorage
352 loadGroup);
354 if (NS_FAILED(rv)) {
355 LOG("create channel failed.");
356 SetReadyState(TextTrackReadyState::FailedToLoad);
357 return;
360 channel->SetNotificationCallbacks(mListener);
362 LOG("opening webvtt channel");
363 rv = channel->AsyncOpen(mListener);
365 if (NS_FAILED(rv)) {
366 SetReadyState(TextTrackReadyState::FailedToLoad);
367 return;
369 mChannel = channel;
371 doc->Dispatch(runnable.forget());
374 nsresult HTMLTrackElement::BindToTree(BindContext& aContext, nsINode& aParent) {
375 nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent);
376 NS_ENSURE_SUCCESS(rv, rv);
378 LOG("Track Element bound to tree.");
379 auto* parent = HTMLMediaElement::FromNode(aParent);
380 if (!parent) {
381 return NS_OK;
384 // Store our parent so we can look up its frame for display.
385 if (!mMediaParent) {
386 mMediaParent = parent;
388 // TODO: separate notification for 'alternate' tracks?
389 mMediaParent->NotifyAddedSource();
390 LOG("Track element sent notification to parent.");
392 // We may already have a TextTrack at this point if GetTrack() has already
393 // been called. This happens, for instance, if script tries to get the
394 // TextTrack before its mTrackElement has been bound to the DOM tree.
395 if (!mTrack) {
396 CreateTextTrack();
398 // As `CreateTextTrack()` might fail, so we have to check it again.
399 if (mTrack) {
400 LOG("Add text track to media parent");
401 mMediaParent->AddTextTrack(mTrack);
403 MaybeDispatchLoadResource();
406 return NS_OK;
409 void HTMLTrackElement::UnbindFromTree(UnbindContext& aContext) {
410 if (mMediaParent && aContext.IsUnbindRoot(this)) {
411 // mTrack can be null if HTMLTrackElement::LoadResource has never been
412 // called.
413 if (mTrack) {
414 mMediaParent->RemoveTextTrack(mTrack);
415 mMediaParent->UpdateReadyState();
417 mMediaParent = nullptr;
420 nsGenericHTMLElement::UnbindFromTree(aContext);
423 TextTrackReadyState HTMLTrackElement::ReadyState() const {
424 if (!mTrack) {
425 return TextTrackReadyState::NotLoaded;
428 return mTrack->ReadyState();
431 void HTMLTrackElement::SetReadyState(TextTrackReadyState aReadyState) {
432 if (ReadyState() == aReadyState) {
433 return;
436 if (mTrack) {
437 switch (aReadyState) {
438 case TextTrackReadyState::Loaded:
439 LOG("dispatch 'load' event");
440 DispatchTrackRunnable(u"load"_ns);
441 break;
442 case TextTrackReadyState::FailedToLoad:
443 LOG("dispatch 'error' event");
444 DispatchTrackRunnable(u"error"_ns);
445 break;
446 default:
447 break;
449 mTrack->SetReadyState(aReadyState);
453 void HTMLTrackElement::DispatchTrackRunnable(const nsString& aEventName) {
454 Document* doc = OwnerDoc();
455 if (!doc) {
456 return;
458 nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<const nsString>(
459 "dom::HTMLTrackElement::DispatchTrustedEvent", this,
460 &HTMLTrackElement::DispatchTrustedEvent, aEventName);
461 doc->Dispatch(runnable.forget());
464 void HTMLTrackElement::DispatchTrustedEvent(const nsAString& aName) {
465 Document* doc = OwnerDoc();
466 if (!doc) {
467 return;
469 nsContentUtils::DispatchTrustedEvent(doc, this, aName, CanBubble::eNo,
470 Cancelable::eNo);
473 void HTMLTrackElement::CancelChannelAndListener() {
474 if (mChannel) {
475 mChannel->CancelWithReason(NS_BINDING_ABORTED,
476 "HTMLTrackElement::CancelChannelAndListener"_ns);
477 mChannel->SetNotificationCallbacks(nullptr);
478 mChannel = nullptr;
481 if (mListener) {
482 mListener->Cancel();
483 mListener = nullptr;
487 void HTMLTrackElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
488 const nsAttrValue* aValue,
489 const nsAttrValue* aOldValue,
490 nsIPrincipal* aMaybeScriptedPrincipal,
491 bool aNotify) {
492 if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::src) {
493 MaybeClearAllCues();
494 // In spec, `start the track processing model` step10, while fetching is
495 // ongoing, if the track URL changes, then we have to set the `FailedToLoad`
496 // state.
497 // https://html.spec.whatwg.org/multipage/media.html#sourcing-out-of-band-text-tracks:text-track-failed-to-load-3
498 if (ReadyState() == TextTrackReadyState::Loading && aValue != aOldValue) {
499 SetReadyState(TextTrackReadyState::FailedToLoad);
502 return nsGenericHTMLElement::AfterSetAttr(
503 aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
506 void HTMLTrackElement::DispatchTestEvent(const nsAString& aName) {
507 if (!StaticPrefs::media_webvtt_testing_events()) {
508 return;
510 DispatchTrustedEvent(aName);
513 #undef LOG
515 } // namespace mozilla::dom