summaryrefslogtreecommitdiffstats
path: root/dom/html/HTMLTrackElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/html/HTMLTrackElement.cpp')
-rw-r--r--dom/html/HTMLTrackElement.cpp471
1 files changed, 471 insertions, 0 deletions
diff --git a/dom/html/HTMLTrackElement.cpp b/dom/html/HTMLTrackElement.cpp
new file mode 100644
index 000000000..758018f8d
--- /dev/null
+++ b/dom/html/HTMLTrackElement.cpp
@@ -0,0 +1,471 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/HTMLTrackElement.h"
+#include "mozilla/dom/HTMLTrackElementBinding.h"
+#include "mozilla/dom/HTMLUnknownElement.h"
+#include "nsIContentPolicy.h"
+#include "mozilla/LoadInfo.h"
+#include "WebVTTListener.h"
+#include "nsAttrValueInlines.h"
+#include "nsCOMPtr.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentUtils.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsICachingChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIContentPolicy.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIDocument.h"
+#include "nsIDOMEventTarget.h"
+#include "nsIDOMHTMLMediaElement.h"
+#include "nsIHttpChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsILoadGroup.h"
+#include "nsIObserver.h"
+#include "nsIStreamListener.h"
+#include "nsISupportsImpl.h"
+#include "nsISupportsPrimitives.h"
+#include "nsMappedAttributes.h"
+#include "nsNetUtil.h"
+#include "nsRuleData.h"
+#include "nsStyleConsts.h"
+#include "nsThreadUtils.h"
+#include "nsVideoFrame.h"
+
+static mozilla::LazyLogModule gTrackElementLog("nsTrackElement");
+#define LOG(type, msg) MOZ_LOG(gTrackElementLog, type, msg)
+
+// Replace the usual NS_IMPL_NS_NEW_HTML_ELEMENT(Track) so
+// we can return an UnknownElement instead when pref'd off.
+nsGenericHTMLElement*
+NS_NewHTMLTrackElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
+ mozilla::dom::FromParser aFromParser)
+{
+ return new mozilla::dom::HTMLTrackElement(aNodeInfo);
+}
+
+namespace mozilla {
+namespace dom {
+
+// Map html attribute string values to TextTrackKind enums.
+static constexpr nsAttrValue::EnumTable kKindTable[] = {
+ { "subtitles", static_cast<int16_t>(TextTrackKind::Subtitles) },
+ { "captions", static_cast<int16_t>(TextTrackKind::Captions) },
+ { "descriptions", static_cast<int16_t>(TextTrackKind::Descriptions) },
+ { "chapters", static_cast<int16_t>(TextTrackKind::Chapters) },
+ { "metadata", static_cast<int16_t>(TextTrackKind::Metadata) },
+ { nullptr, 0 }
+};
+
+// Invalid values are treated as "metadata" in ParseAttribute, but if no value
+// at all is specified, it's treated as "subtitles" in GetKind
+static constexpr const nsAttrValue::EnumTable* kKindTableInvalidValueDefault = &kKindTable[4];
+
+class WindowDestroyObserver final : public nsIObserver
+{
+ NS_DECL_ISUPPORTS
+
+public:
+ explicit WindowDestroyObserver(HTMLTrackElement* aElement, uint64_t aWinID)
+ : mTrackElement(aElement)
+ , mInnerID(aWinID)
+ {
+ RegisterWindowDestroyObserver();
+ }
+ void RegisterWindowDestroyObserver()
+ {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->AddObserver(this, "inner-window-destroyed", false);
+ }
+ }
+ void UnRegisterWindowDestroyObserver()
+ {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->RemoveObserver(this, "inner-window-destroyed");
+ }
+ mTrackElement = nullptr;
+ }
+ NS_IMETHODIMP Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (strcmp(aTopic, "inner-window-destroyed") == 0) {
+ nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+ NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+ uint64_t innerID;
+ nsresult rv = wrapper->GetData(&innerID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (innerID == mInnerID) {
+ if (mTrackElement) {
+ mTrackElement->NotifyShutdown();
+ }
+ UnRegisterWindowDestroyObserver();
+ }
+ }
+ return NS_OK;
+ }
+
+private:
+ ~WindowDestroyObserver() {};
+ HTMLTrackElement* mTrackElement;
+ uint64_t mInnerID;
+};
+NS_IMPL_ISUPPORTS(WindowDestroyObserver, nsIObserver);
+
+/** HTMLTrackElement */
+HTMLTrackElement::HTMLTrackElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+ : nsGenericHTMLElement(aNodeInfo)
+ , mLoadResourceDispatched(false)
+ , mWindowDestroyObserver(nullptr)
+{
+ nsISupports* parentObject = OwnerDoc()->GetParentObject();
+ NS_ENSURE_TRUE_VOID(parentObject);
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject);
+ if (window) {
+ mWindowDestroyObserver = new WindowDestroyObserver(this, window->WindowID());
+ }
+}
+
+HTMLTrackElement::~HTMLTrackElement()
+{
+ if (mWindowDestroyObserver) {
+ mWindowDestroyObserver->UnRegisterWindowDestroyObserver();
+ }
+ NotifyShutdown();
+}
+
+NS_IMPL_ELEMENT_CLONE(HTMLTrackElement)
+
+NS_IMPL_ADDREF_INHERITED(HTMLTrackElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLTrackElement, Element)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLTrackElement, nsGenericHTMLElement,
+ mTrack, mMediaParent, mListener)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLTrackElement)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
+
+void
+HTMLTrackElement::GetKind(DOMString& aKind) const
+{
+ GetEnumAttr(nsGkAtoms::kind, kKindTable[0].tag, aKind);
+}
+
+void
+HTMLTrackElement::OnChannelRedirect(nsIChannel* aChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aFlags)
+{
+ NS_ASSERTION(aChannel == mChannel, "Channels should match!");
+ mChannel = aNewChannel;
+}
+
+JSObject*
+HTMLTrackElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLTrackElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+TextTrack*
+HTMLTrackElement::GetTrack()
+{
+ if (!mTrack) {
+ CreateTextTrack();
+ }
+
+ return mTrack;
+}
+
+void
+HTMLTrackElement::CreateTextTrack()
+{
+ nsString label, srcLang;
+ GetSrclang(srcLang);
+ GetLabel(label);
+
+ TextTrackKind kind;
+ if (const nsAttrValue* value = GetParsedAttr(nsGkAtoms::kind)) {
+ kind = static_cast<TextTrackKind>(value->GetEnumValue());
+ } else {
+ kind = TextTrackKind::Subtitles;
+ }
+
+ nsISupports* parentObject =
+ OwnerDoc()->GetParentObject();
+
+ NS_ENSURE_TRUE_VOID(parentObject);
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(parentObject);
+ mTrack = new TextTrack(window, kind, label, srcLang,
+ TextTrackMode::Disabled,
+ TextTrackReadyState::NotLoaded,
+ TextTrackSource::Track);
+ mTrack->SetTrackElement(this);
+
+ if (mMediaParent) {
+ mMediaParent->AddTextTrack(mTrack);
+ }
+}
+
+bool
+HTMLTrackElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::kind) {
+ // Case-insensitive lookup, with the first element as the default.
+ return aResult.ParseEnumValue(aValue, kKindTable, false,
+ kKindTableInvalidValueDefault);
+ }
+
+ // Otherwise call the generic implementation.
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID,
+ aAttribute,
+ aValue,
+ aResult);
+}
+
+void
+HTMLTrackElement::SetSrc(const nsAString& aSrc, ErrorResult& aError)
+{
+ SetHTMLAttr(nsGkAtoms::src, aSrc, aError);
+ uint16_t oldReadyState = ReadyState();
+ SetReadyState(TextTrackReadyState::NotLoaded);
+ if (!mMediaParent) {
+ return;
+ }
+ if (mTrack && (oldReadyState != TextTrackReadyState::NotLoaded)) {
+ // Remove all the cues in MediaElement.
+ mMediaParent->RemoveTextTrack(mTrack);
+ // Recreate mTrack.
+ CreateTextTrack();
+ }
+ // Stop WebVTTListener.
+ mListener = nullptr;
+ if (mChannel) {
+ mChannel->Cancel(NS_BINDING_ABORTED);
+ mChannel = nullptr;
+ }
+
+ DispatchLoadResource();
+}
+
+void
+HTMLTrackElement::DispatchLoadResource()
+{
+ if (!mLoadResourceDispatched) {
+ RefPtr<Runnable> r = NewRunnableMethod(this, &HTMLTrackElement::LoadResource);
+ nsContentUtils::RunInStableState(r.forget());
+ mLoadResourceDispatched = true;
+ }
+}
+
+void
+HTMLTrackElement::LoadResource()
+{
+ mLoadResourceDispatched = false;
+
+ // Find our 'src' url
+ nsAutoString src;
+ if (!GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NewURIFromString(src, getter_AddRefs(uri));
+ NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
+ LOG(LogLevel::Info, ("%p Trying to load from src=%s", this,
+ NS_ConvertUTF16toUTF8(src).get()));
+
+ if (mChannel) {
+ mChannel->Cancel(NS_BINDING_ABORTED);
+ mChannel = 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 = mMediaParent ? mMediaParent->GetCORSMode() : CORS_NONE;
+
+ // Determine the security flag based on corsMode.
+ nsSecurityFlags secFlags;
+ if (CORS_NONE == corsMode) {
+ // Same-origin is required for track element.
+ secFlags = nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS;
+ } else {
+ secFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
+ if (CORS_ANONYMOUS == corsMode) {
+ secFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
+ } else if (CORS_USE_CREDENTIALS == corsMode) {
+ secFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
+ } else {
+ NS_WARNING("Unknown CORS mode.");
+ secFlags = nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS;
+ }
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsILoadGroup> loadGroup = OwnerDoc()->GetDocumentLoadGroup();
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ uri,
+ static_cast<Element*>(this),
+ secFlags,
+ nsIContentPolicy::TYPE_INTERNAL_TRACK,
+ loadGroup,
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI);
+
+ NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
+
+ mListener = new WebVTTListener(this);
+ rv = mListener->LoadResource();
+ NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
+ channel->SetNotificationCallbacks(mListener);
+
+ LOG(LogLevel::Debug, ("opening webvtt channel"));
+ rv = channel->AsyncOpen2(mListener);
+
+ if (NS_FAILED(rv)) {
+ SetReadyState(TextTrackReadyState::FailedToLoad);
+ return;
+ }
+
+ mChannel = channel;
+}
+
+nsresult
+HTMLTrackElement::BindToTree(nsIDocument* aDocument,
+ nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLElement::BindToTree(aDocument,
+ aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LOG(LogLevel::Debug, ("Track Element bound to tree."));
+ if (!aParent || !aParent->IsNodeOfType(nsINode::eMEDIA)) {
+ return NS_OK;
+ }
+
+ // Store our parent so we can look up its frame for display.
+ if (!mMediaParent) {
+ mMediaParent = static_cast<HTMLMediaElement*>(aParent);
+
+ // TODO: separate notification for 'alternate' tracks?
+ mMediaParent->NotifyAddedSource();
+ LOG(LogLevel::Debug, ("Track element sent notification to parent."));
+
+ // We may already have a TextTrack at this point if GetTrack() has already
+ // been called. This happens, for instance, if script tries to get the
+ // TextTrack before its mTrackElement has been bound to the DOM tree.
+ if (!mTrack) {
+ CreateTextTrack();
+ }
+ DispatchLoadResource();
+ }
+
+ return NS_OK;
+}
+
+void
+HTMLTrackElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ if (mMediaParent && aNullParent) {
+ // mTrack can be null if HTMLTrackElement::LoadResource has never been
+ // called.
+ if (mTrack) {
+ mMediaParent->RemoveTextTrack(mTrack);
+ }
+ mMediaParent = nullptr;
+ }
+
+ nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+uint16_t
+HTMLTrackElement::ReadyState() const
+{
+ if (!mTrack) {
+ return TextTrackReadyState::NotLoaded;
+ }
+
+ return mTrack->ReadyState();
+}
+
+void
+HTMLTrackElement::SetReadyState(uint16_t aReadyState)
+{
+ if (ReadyState() == aReadyState) {
+ return;
+ }
+
+ if (mTrack) {
+ switch (aReadyState) {
+ case TextTrackReadyState::Loaded:
+ DispatchTrackRunnable(NS_LITERAL_STRING("load"));
+ break;
+ case TextTrackReadyState::FailedToLoad:
+ DispatchTrackRunnable(NS_LITERAL_STRING("error"));
+ break;
+ }
+ mTrack->SetReadyState(aReadyState);
+ }
+}
+
+void
+HTMLTrackElement::DispatchTrackRunnable(const nsString& aEventName)
+{
+ nsCOMPtr<nsIRunnable> runnable =
+ NewRunnableMethod
+ <const nsString>(this,
+ &HTMLTrackElement::DispatchTrustedEvent,
+ aEventName);
+ NS_DispatchToMainThread(runnable);
+}
+
+void
+HTMLTrackElement::DispatchTrustedEvent(const nsAString& aName)
+{
+ nsIDocument* doc = OwnerDoc();
+ if (!doc) {
+ return;
+ }
+ nsContentUtils::DispatchTrustedEvent(doc, static_cast<nsIContent*>(this),
+ aName, false, false);
+}
+
+void
+HTMLTrackElement::DropChannel()
+{
+ mChannel = nullptr;
+}
+
+void
+HTMLTrackElement::NotifyShutdown()
+{
+ if (mChannel) {
+ mChannel->Cancel(NS_BINDING_ABORTED);
+ }
+ mChannel = nullptr;
+ mListener = nullptr;
+}
+
+} // namespace dom
+} // namespace mozilla