Coverage Report

Created: 2018-09-25 14:53

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