1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Mozilla code.
17 : *
18 : * The Initial Developer of the Original Code is the Mozilla Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 2007
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Chris Double <chris.double@double.co.nz>
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "mozilla/Util.h"
40 :
41 : #include "nsIDOMHTMLMediaElement.h"
42 : #include "nsIDOMHTMLSourceElement.h"
43 : #include "nsHTMLMediaElement.h"
44 : #include "nsTimeRanges.h"
45 : #include "nsGenericHTMLElement.h"
46 : #include "nsPresContext.h"
47 : #include "nsIPresShell.h"
48 : #include "nsGkAtoms.h"
49 : #include "nsSize.h"
50 : #include "nsIFrame.h"
51 : #include "nsIDocument.h"
52 : #include "nsIDOMDocument.h"
53 : #include "nsDOMError.h"
54 : #include "nsNodeInfoManager.h"
55 : #include "nsNetUtil.h"
56 : #include "nsXPCOMStrings.h"
57 : #include "nsThreadUtils.h"
58 : #include "nsIThreadInternal.h"
59 : #include "nsContentUtils.h"
60 : #include "nsFrameManager.h"
61 : #include "nsIScriptSecurityManager.h"
62 : #include "nsIXPConnect.h"
63 : #include "jsapi.h"
64 :
65 : #include "nsITimer.h"
66 :
67 : #include "nsEventDispatcher.h"
68 : #include "nsMediaError.h"
69 : #include "nsICategoryManager.h"
70 : #include "nsCharSeparatedTokenizer.h"
71 : #include "MediaResource.h"
72 :
73 : #include "nsIDOMHTMLVideoElement.h"
74 : #include "nsIContentPolicy.h"
75 : #include "nsContentPolicyUtils.h"
76 : #include "nsContentErrors.h"
77 : #include "nsCrossSiteListenerProxy.h"
78 : #include "nsCycleCollectionParticipant.h"
79 : #include "nsICachingChannel.h"
80 : #include "nsLayoutUtils.h"
81 : #include "nsVideoFrame.h"
82 : #include "BasicLayers.h"
83 : #include <limits>
84 : #include "nsIDocShellTreeItem.h"
85 : #include "nsIAsyncVerifyRedirectCallback.h"
86 : #include "nsIAppShell.h"
87 : #include "nsWidgetsCID.h"
88 :
89 : #include "nsIPrivateDOMEvent.h"
90 : #include "nsIDOMNotifyAudioAvailableEvent.h"
91 : #include "nsMediaFragmentURIParser.h"
92 : #include "nsURIHashKey.h"
93 : #include "nsContentUtils.h"
94 : #include "nsIScriptError.h"
95 :
96 : #ifdef MOZ_OGG
97 : #include "nsOggDecoder.h"
98 : #endif
99 : #ifdef MOZ_WAVE
100 : #include "nsWaveDecoder.h"
101 : #endif
102 : #ifdef MOZ_WEBM
103 : #include "nsWebMDecoder.h"
104 : #endif
105 : #ifdef MOZ_RAW
106 : #include "nsRawDecoder.h"
107 : #endif
108 :
109 : #ifdef PR_LOGGING
110 : static PRLogModuleInfo* gMediaElementLog;
111 : static PRLogModuleInfo* gMediaElementEventsLog;
112 : #define LOG(type, msg) PR_LOG(gMediaElementLog, type, msg)
113 : #define LOG_EVENT(type, msg) PR_LOG(gMediaElementEventsLog, type, msg)
114 : #else
115 : #define LOG(type, msg)
116 : #define LOG_EVENT(type, msg)
117 : #endif
118 :
119 : #include "nsIContentSecurityPolicy.h"
120 : #include "nsIChannelPolicy.h"
121 : #include "nsChannelPolicy.h"
122 :
123 : #include "mozilla/Preferences.h"
124 :
125 : using namespace mozilla;
126 : using namespace mozilla::dom;
127 : using namespace mozilla::layers;
128 :
129 : // Number of milliseconds between timeupdate events as defined by spec
130 : #define TIMEUPDATE_MS 250
131 :
132 : // Under certain conditions there may be no-one holding references to
133 : // a media element from script, DOM parent, etc, but the element may still
134 : // fire meaningful events in the future so we can't destroy it yet:
135 : // 1) If the element is delaying the load event (or would be, if it were
136 : // in a document), then events up to loadeddata or error could be fired,
137 : // so we need to stay alive.
138 : // 2) If the element is not paused and playback has not ended, then
139 : // we will (or might) play, sending timeupdate and ended events and possibly
140 : // audio output, so we need to stay alive.
141 : // 3) if the element is seeking then we will fire seeking events and possibly
142 : // start playing afterward, so we need to stay alive.
143 : // 4) If autoplay could start playback in this element (if we got enough data),
144 : // then we need to stay alive.
145 : // 5) if the element is currently loading and not suspended,
146 : // script might be waiting for progress events or a 'suspend' event,
147 : // so we need to stay alive. If we're already suspended then (all other
148 : // conditions being met) it's OK to just disappear without firing any more
149 : // events, since we have the freedom to remain suspended indefinitely. Note
150 : // that we could use this 'suspended' loophole to garbage-collect a suspended
151 : // element in case 4 even if it had 'autoplay' set, but we choose not to.
152 : // If someone throws away all references to a loading 'autoplay' element
153 : // sound should still eventually play.
154 : //
155 : // Media elements owned by inactive documents (i.e. documents not contained in any
156 : // document viewer) should never hold a self-reference because none of the
157 : // above conditions are allowed: the element will stop loading and playing
158 : // and never resume loading or playing unless its owner document changes to
159 : // an active document (which can only happen if there is an external reference
160 : // to the element).
161 : // Media elements with no owner doc should be able to hold a self-reference.
162 : // Something native must have created the element and may expect it to
163 : // stay alive to play.
164 :
165 : // It's very important that any change in state which could change the value of
166 : // needSelfReference in AddRemoveSelfReference be followed by a call to
167 : // AddRemoveSelfReference before this element could die!
168 : // It's especially important if needSelfReference would change to 'true',
169 : // since if we neglect to add a self-reference, this element might be
170 : // garbage collected while there are still event listeners that should
171 : // receive events. If we neglect to remove the self-reference then the element
172 : // just lives longer than it needs to.
173 :
174 : class nsMediaEvent : public nsRunnable
175 : {
176 : public:
177 :
178 0 : nsMediaEvent(nsHTMLMediaElement* aElement) :
179 : mElement(aElement),
180 0 : mLoadID(mElement->GetCurrentLoadID()) {}
181 0 : ~nsMediaEvent() {}
182 :
183 : NS_IMETHOD Run() = 0;
184 :
185 : protected:
186 0 : bool IsCancelled() {
187 0 : return mElement->GetCurrentLoadID() != mLoadID;
188 : }
189 :
190 : nsRefPtr<nsHTMLMediaElement> mElement;
191 : PRUint32 mLoadID;
192 : };
193 :
194 : class nsAsyncEventRunner : public nsMediaEvent
195 0 : {
196 : private:
197 : nsString mName;
198 :
199 : public:
200 0 : nsAsyncEventRunner(const nsAString& aName, nsHTMLMediaElement* aElement) :
201 0 : nsMediaEvent(aElement), mName(aName)
202 : {
203 0 : }
204 :
205 0 : NS_IMETHOD Run()
206 : {
207 : // Silently cancel if our load has been cancelled.
208 0 : if (IsCancelled())
209 0 : return NS_OK;
210 :
211 0 : return mElement->DispatchEvent(mName);
212 : }
213 : };
214 :
215 : class nsSourceErrorEventRunner : public nsMediaEvent
216 0 : {
217 : private:
218 : nsCOMPtr<nsIContent> mSource;
219 : public:
220 0 : nsSourceErrorEventRunner(nsHTMLMediaElement* aElement,
221 : nsIContent* aSource)
222 : : nsMediaEvent(aElement),
223 0 : mSource(aSource)
224 : {
225 0 : }
226 :
227 0 : NS_IMETHOD Run() {
228 : // Silently cancel if our load has been cancelled.
229 0 : if (IsCancelled())
230 0 : return NS_OK;
231 0 : LOG_EVENT(PR_LOG_DEBUG, ("%p Dispatching simple event source error", mElement.get()));
232 0 : return nsContentUtils::DispatchTrustedEvent(mElement->OwnerDoc(),
233 : mSource,
234 0 : NS_LITERAL_STRING("error"),
235 : false,
236 0 : true);
237 : }
238 : };
239 :
240 : /**
241 : * There is a reference cycle involving this class: MediaLoadListener
242 : * holds a reference to the nsHTMLMediaElement, which holds a reference
243 : * to an nsIChannel, which holds a reference to this listener.
244 : * We break the reference cycle in OnStartRequest by clearing mElement.
245 : */
246 : class nsHTMLMediaElement::MediaLoadListener MOZ_FINAL : public nsIStreamListener,
247 : public nsIChannelEventSink,
248 : public nsIInterfaceRequestor,
249 : public nsIObserver
250 0 : {
251 : NS_DECL_ISUPPORTS
252 : NS_DECL_NSIREQUESTOBSERVER
253 : NS_DECL_NSISTREAMLISTENER
254 : NS_DECL_NSICHANNELEVENTSINK
255 : NS_DECL_NSIOBSERVER
256 : NS_DECL_NSIINTERFACEREQUESTOR
257 :
258 : public:
259 0 : MediaLoadListener(nsHTMLMediaElement* aElement)
260 : : mElement(aElement),
261 0 : mLoadID(aElement->GetCurrentLoadID())
262 : {
263 0 : NS_ABORT_IF_FALSE(mElement, "Must pass an element to call back");
264 0 : }
265 :
266 : private:
267 : nsRefPtr<nsHTMLMediaElement> mElement;
268 : nsCOMPtr<nsIStreamListener> mNextListener;
269 : PRUint32 mLoadID;
270 : };
271 :
272 0 : NS_IMPL_ISUPPORTS5(nsHTMLMediaElement::MediaLoadListener, nsIRequestObserver,
273 : nsIStreamListener, nsIChannelEventSink,
274 : nsIInterfaceRequestor, nsIObserver)
275 :
276 : NS_IMETHODIMP
277 0 : nsHTMLMediaElement::MediaLoadListener::Observe(nsISupports* aSubject,
278 : const char* aTopic, const PRUnichar* aData)
279 : {
280 0 : nsContentUtils::UnregisterShutdownObserver(this);
281 :
282 : // Clear mElement to break cycle so we don't leak on shutdown
283 0 : mElement = nsnull;
284 0 : return NS_OK;
285 : }
286 :
287 0 : void nsHTMLMediaElement::ReportLoadError(const char* aMsg,
288 : const PRUnichar** aParams,
289 : PRUint32 aParamCount)
290 : {
291 : nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
292 : "Media",
293 : OwnerDoc(),
294 : nsContentUtils::eDOM_PROPERTIES,
295 : aMsg,
296 : aParams,
297 0 : aParamCount);
298 0 : }
299 :
300 :
301 0 : NS_IMETHODIMP nsHTMLMediaElement::MediaLoadListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
302 : {
303 0 : nsContentUtils::UnregisterShutdownObserver(this);
304 :
305 0 : if (!mElement) {
306 : // We've been notified by the shutdown observer, and are shutting down.
307 0 : return NS_BINDING_ABORTED;
308 : }
309 :
310 : // The element is only needed until we've had a chance to call
311 : // InitializeDecoderForChannel. So make sure mElement is cleared here.
312 0 : nsRefPtr<nsHTMLMediaElement> element;
313 0 : element.swap(mElement);
314 :
315 0 : if (mLoadID != element->GetCurrentLoadID()) {
316 : // The channel has been cancelled before we had a chance to create
317 : // a decoder. Abort, don't dispatch an "error" event, as the new load
318 : // may not be in an error state.
319 0 : return NS_BINDING_ABORTED;
320 : }
321 :
322 : // Don't continue to load if the request failed or has been canceled.
323 : nsresult status;
324 0 : nsresult rv = aRequest->GetStatus(&status);
325 0 : NS_ENSURE_SUCCESS(rv, rv);
326 0 : if (NS_FAILED(status)) {
327 0 : if (element)
328 0 : element->NotifyLoadError();
329 0 : return status;
330 : }
331 :
332 0 : nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
333 : bool succeeded;
334 0 : if (hc && NS_SUCCEEDED(hc->GetRequestSucceeded(&succeeded)) && !succeeded) {
335 0 : element->NotifyLoadError();
336 0 : PRUint32 responseStatus = 0;
337 0 : hc->GetResponseStatus(&responseStatus);
338 0 : nsAutoString code;
339 0 : code.AppendInt(responseStatus);
340 0 : nsAutoString src;
341 0 : element->GetCurrentSrc(src);
342 0 : const PRUnichar* params[] = { code.get(), src.get() };
343 0 : element->ReportLoadError("MediaLoadHttpError", params, ArrayLength(params));
344 0 : return NS_BINDING_ABORTED;
345 : }
346 :
347 0 : nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
348 0 : if (channel &&
349 0 : element &&
350 0 : NS_SUCCEEDED(rv = element->InitializeDecoderForChannel(channel, getter_AddRefs(mNextListener))) &&
351 0 : mNextListener) {
352 0 : rv = mNextListener->OnStartRequest(aRequest, aContext);
353 : } else {
354 : // If InitializeDecoderForChannel() returned an error, fire a network
355 : // error.
356 0 : if (NS_FAILED(rv) && !mNextListener && element) {
357 : // Load failed, attempt to load the next candidate resource. If there
358 : // are none, this will trigger a MEDIA_ERR_SRC_NOT_SUPPORTED error.
359 0 : element->NotifyLoadError();
360 : }
361 : // If InitializeDecoderForChannel did not return a listener (but may
362 : // have otherwise succeeded), we abort the connection since we aren't
363 : // interested in keeping the channel alive ourselves.
364 0 : rv = NS_BINDING_ABORTED;
365 : }
366 :
367 0 : return rv;
368 : }
369 :
370 0 : NS_IMETHODIMP nsHTMLMediaElement::MediaLoadListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext,
371 : nsresult aStatus)
372 : {
373 0 : if (mNextListener) {
374 0 : return mNextListener->OnStopRequest(aRequest, aContext, aStatus);
375 : }
376 0 : return NS_OK;
377 : }
378 :
379 0 : NS_IMETHODIMP nsHTMLMediaElement::MediaLoadListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
380 : nsIInputStream* aStream, PRUint32 aOffset,
381 : PRUint32 aCount)
382 : {
383 0 : if (!mNextListener) {
384 0 : NS_ERROR("Must have a chained listener; OnStartRequest should have canceled this request");
385 0 : return NS_BINDING_ABORTED;
386 : }
387 0 : return mNextListener->OnDataAvailable(aRequest, aContext, aStream, aOffset, aCount);
388 : }
389 :
390 0 : NS_IMETHODIMP nsHTMLMediaElement::MediaLoadListener::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
391 : nsIChannel* aNewChannel,
392 : PRUint32 aFlags,
393 : nsIAsyncVerifyRedirectCallback* cb)
394 : {
395 : // TODO is this really correct?? See bug #579329.
396 0 : if (mElement)
397 0 : mElement->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
398 0 : nsCOMPtr<nsIChannelEventSink> sink = do_QueryInterface(mNextListener);
399 0 : if (sink)
400 0 : return sink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, cb);
401 :
402 0 : cb->OnRedirectVerifyCallback(NS_OK);
403 0 : return NS_OK;
404 : }
405 :
406 0 : NS_IMETHODIMP nsHTMLMediaElement::MediaLoadListener::GetInterface(const nsIID & aIID, void **aResult)
407 : {
408 0 : return QueryInterface(aIID, aResult);
409 : }
410 :
411 0 : NS_IMPL_ADDREF_INHERITED(nsHTMLMediaElement, nsGenericHTMLElement)
412 0 : NS_IMPL_RELEASE_INHERITED(nsHTMLMediaElement, nsGenericHTMLElement)
413 :
414 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(nsHTMLMediaElement)
415 :
416 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsHTMLMediaElement, nsGenericHTMLElement)
417 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mSourcePointer)
418 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mLoadBlockedDoc)
419 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mSourceLoadCandidate)
420 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
421 :
422 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHTMLMediaElement, nsGenericHTMLElement)
423 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mSourcePointer)
424 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mLoadBlockedDoc)
425 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mSourceLoadCandidate)
426 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
427 :
428 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsHTMLMediaElement)
429 0 : NS_INTERFACE_MAP_ENTRY(nsIObserver)
430 0 : NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
431 :
432 : // nsIDOMHTMLMediaElement
433 0 : NS_IMPL_URI_ATTR(nsHTMLMediaElement, Src, src)
434 0 : NS_IMPL_BOOL_ATTR(nsHTMLMediaElement, Controls, controls)
435 0 : NS_IMPL_BOOL_ATTR(nsHTMLMediaElement, Autoplay, autoplay)
436 0 : NS_IMPL_BOOL_ATTR(nsHTMLMediaElement, Loop, loop)
437 0 : NS_IMPL_BOOL_ATTR(nsHTMLMediaElement, DefaultMuted, muted)
438 0 : NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(nsHTMLMediaElement, Preload, preload, NULL)
439 :
440 : /* readonly attribute nsIDOMHTMLMediaElement mozAutoplayEnabled; */
441 0 : NS_IMETHODIMP nsHTMLMediaElement::GetMozAutoplayEnabled(bool *aAutoplayEnabled)
442 : {
443 0 : *aAutoplayEnabled = mAutoplayEnabled;
444 :
445 0 : return NS_OK;
446 : }
447 :
448 : /* readonly attribute nsIDOMMediaError error; */
449 0 : NS_IMETHODIMP nsHTMLMediaElement::GetError(nsIDOMMediaError * *aError)
450 : {
451 0 : NS_IF_ADDREF(*aError = mError);
452 :
453 0 : return NS_OK;
454 : }
455 :
456 : /* readonly attribute boolean ended; */
457 0 : NS_IMETHODIMP nsHTMLMediaElement::GetEnded(bool *aEnded)
458 : {
459 0 : *aEnded = mDecoder ? mDecoder->IsEnded() : false;
460 :
461 0 : return NS_OK;
462 : }
463 :
464 : /* readonly attribute DOMString currentSrc; */
465 0 : NS_IMETHODIMP nsHTMLMediaElement::GetCurrentSrc(nsAString & aCurrentSrc)
466 : {
467 0 : nsCAutoString src;
468 0 : GetCurrentSpec(src);
469 0 : aCurrentSrc = NS_ConvertUTF8toUTF16(src);
470 0 : return NS_OK;
471 : }
472 :
473 : /* readonly attribute unsigned short networkState; */
474 0 : NS_IMETHODIMP nsHTMLMediaElement::GetNetworkState(PRUint16 *aNetworkState)
475 : {
476 0 : *aNetworkState = mNetworkState;
477 :
478 0 : return NS_OK;
479 : }
480 :
481 : nsresult
482 0 : nsHTMLMediaElement::OnChannelRedirect(nsIChannel *aChannel,
483 : nsIChannel *aNewChannel,
484 : PRUint32 aFlags)
485 : {
486 0 : NS_ASSERTION(aChannel == mChannel, "Channels should match!");
487 0 : mChannel = aNewChannel;
488 :
489 : // Handle forwarding of Range header so that the intial detection
490 : // of seeking support (via result code 206) works across redirects.
491 0 : nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
492 0 : NS_ENSURE_STATE(http);
493 :
494 0 : NS_NAMED_LITERAL_CSTRING(rangeHdr, "Range");
495 :
496 0 : nsCAutoString rangeVal;
497 0 : if (NS_SUCCEEDED(http->GetRequestHeader(rangeHdr, rangeVal))) {
498 0 : NS_ENSURE_STATE(!rangeVal.IsEmpty());
499 :
500 0 : http = do_QueryInterface(aNewChannel);
501 0 : NS_ENSURE_STATE(http);
502 :
503 0 : nsresult rv = http->SetRequestHeader(rangeHdr, rangeVal, false);
504 0 : NS_ENSURE_SUCCESS(rv, rv);
505 : }
506 :
507 0 : return NS_OK;
508 : }
509 :
510 0 : void nsHTMLMediaElement::AbortExistingLoads()
511 : {
512 : // Abort any already-running instance of the resource selection algorithm.
513 0 : mLoadWaitStatus = NOT_WAITING;
514 :
515 : // Set a new load ID. This will cause events which were enqueued
516 : // with a different load ID to silently be cancelled.
517 0 : mCurrentLoadID++;
518 :
519 0 : bool fireTimeUpdate = false;
520 :
521 0 : if (mDecoder) {
522 0 : RemoveMediaElementFromURITable();
523 0 : fireTimeUpdate = mDecoder->GetCurrentTime() != 0.0;
524 0 : mDecoder->Shutdown();
525 0 : mDecoder = nsnull;
526 : }
527 0 : mLoadingSrc = nsnull;
528 :
529 0 : if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING ||
530 : mNetworkState == nsIDOMHTMLMediaElement::NETWORK_IDLE)
531 : {
532 0 : DispatchEvent(NS_LITERAL_STRING("abort"));
533 : }
534 :
535 0 : mError = nsnull;
536 0 : mLoadedFirstFrame = false;
537 0 : mAutoplaying = true;
538 0 : mIsLoadingFromSourceChildren = false;
539 0 : mSuspendedAfterFirstFrame = false;
540 0 : mAllowSuspendAfterFirstFrame = true;
541 0 : mHaveQueuedSelectResource = false;
542 0 : mLoadIsSuspended = false;
543 0 : mSourcePointer = nsnull;
544 :
545 : // TODO: The playback rate must be set to the default playback rate.
546 :
547 0 : if (mNetworkState != nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
548 0 : mNetworkState = nsIDOMHTMLMediaElement::NETWORK_EMPTY;
549 0 : ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING);
550 0 : mPaused = true;
551 :
552 0 : if (fireTimeUpdate) {
553 : // Since we destroyed the decoder above, the current playback position
554 : // will now be reported as 0. The playback position was non-zero when
555 : // we destroyed the decoder, so fire a timeupdate event so that the
556 : // change will be reflected in the controls.
557 0 : FireTimeUpdate(false);
558 : }
559 0 : DispatchEvent(NS_LITERAL_STRING("emptied"));
560 : }
561 :
562 : // We may have changed mPaused, mAutoplaying, mNetworkState and other
563 : // things which can affect AddRemoveSelfReference
564 0 : AddRemoveSelfReference();
565 :
566 0 : mIsRunningSelectResource = false;
567 0 : }
568 :
569 0 : void nsHTMLMediaElement::NoSupportedMediaSourceError()
570 : {
571 0 : NS_ASSERTION(mDelayingLoadEvent, "Load event not delayed during source selection?");
572 :
573 0 : mError = new nsMediaError(nsIDOMMediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
574 0 : mNetworkState = nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE;
575 0 : DispatchAsyncEvent(NS_LITERAL_STRING("error"));
576 : // This clears mDelayingLoadEvent, so AddRemoveSelfReference will be called
577 0 : ChangeDelayLoadStatus(false);
578 0 : }
579 :
580 : typedef void (nsHTMLMediaElement::*SyncSectionFn)();
581 :
582 : // Runs a "synchronous section", a function that must run once the event loop
583 : // has reached a "stable state". See:
584 : // http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section
585 : class nsSyncSection : public nsMediaEvent
586 0 : {
587 : private:
588 : SyncSectionFn mClosure;
589 : public:
590 0 : nsSyncSection(nsHTMLMediaElement* aElement,
591 : SyncSectionFn aClosure) :
592 : nsMediaEvent(aElement),
593 0 : mClosure(aClosure)
594 : {
595 0 : }
596 :
597 0 : NS_IMETHOD Run() {
598 : // Silently cancel if our load has been cancelled.
599 0 : if (IsCancelled())
600 0 : return NS_OK;
601 0 : (mElement.get()->*mClosure)();
602 0 : return NS_OK;
603 : }
604 : };
605 :
606 : static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
607 :
608 : // Asynchronously awaits a stable state, whereupon aClosure runs on the main
609 : // thread. This adds an event which run aClosure to the appshell's list of
610 : // sections synchronous the next time control returns to the event loop.
611 0 : void AsyncAwaitStableState(nsHTMLMediaElement* aElement,
612 : SyncSectionFn aClosure)
613 : {
614 0 : nsCOMPtr<nsIRunnable> event = new nsSyncSection(aElement, aClosure);
615 0 : nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
616 0 : appShell->RunInStableState(event);
617 0 : }
618 :
619 0 : void nsHTMLMediaElement::QueueLoadFromSourceTask()
620 : {
621 0 : ChangeDelayLoadStatus(true);
622 0 : mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
623 0 : AsyncAwaitStableState(this, &nsHTMLMediaElement::LoadFromSourceChildren);
624 0 : }
625 :
626 0 : void nsHTMLMediaElement::QueueSelectResourceTask()
627 : {
628 : // Don't allow multiple async select resource calls to be queued.
629 0 : if (mHaveQueuedSelectResource)
630 0 : return;
631 0 : mHaveQueuedSelectResource = true;
632 0 : mNetworkState = nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE;
633 0 : AsyncAwaitStableState(this, &nsHTMLMediaElement::SelectResourceWrapper);
634 : }
635 :
636 : /* void load (); */
637 0 : NS_IMETHODIMP nsHTMLMediaElement::Load()
638 : {
639 0 : if (mIsRunningLoadMethod)
640 0 : return NS_OK;
641 0 : SetPlayedOrSeeked(false);
642 0 : mIsRunningLoadMethod = true;
643 0 : AbortExistingLoads();
644 0 : QueueSelectResourceTask();
645 0 : mIsRunningLoadMethod = false;
646 0 : return NS_OK;
647 : }
648 :
649 0 : static bool HasSourceChildren(nsIContent *aElement)
650 : {
651 0 : for (nsIContent* child = aElement->GetFirstChild();
652 : child;
653 0 : child = child->GetNextSibling()) {
654 0 : if (child->IsHTML(nsGkAtoms::source))
655 : {
656 0 : return true;
657 : }
658 : }
659 0 : return false;
660 : }
661 :
662 0 : void nsHTMLMediaElement::SelectResourceWrapper()
663 : {
664 0 : SelectResource();
665 0 : mIsRunningSelectResource = false;
666 0 : mHaveQueuedSelectResource = false;
667 0 : }
668 :
669 0 : void nsHTMLMediaElement::SelectResource()
670 : {
671 0 : if (!HasAttr(kNameSpaceID_None, nsGkAtoms::src) && !HasSourceChildren(this)) {
672 : // The media element has neither a src attribute nor any source
673 : // element children, abort the load.
674 0 : mNetworkState = nsIDOMHTMLMediaElement::NETWORK_EMPTY;
675 : // This clears mDelayingLoadEvent, so AddRemoveSelfReference will be called
676 0 : ChangeDelayLoadStatus(false);
677 0 : return;
678 : }
679 :
680 0 : ChangeDelayLoadStatus(true);
681 :
682 0 : mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
683 : // Load event was delayed, and still is, so no need to call
684 : // AddRemoveSelfReference, since it must still be held
685 0 : DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
686 :
687 : // Delay setting mIsRunningSeletResource until after UpdatePreloadAction
688 : // so that we don't lose our state change by bailing out of the preload
689 : // state update
690 0 : UpdatePreloadAction();
691 0 : mIsRunningSelectResource = true;
692 :
693 : // If we have a 'src' attribute, use that exclusively.
694 0 : nsAutoString src;
695 0 : if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
696 0 : nsCOMPtr<nsIURI> uri;
697 0 : nsresult rv = NewURIFromString(src, getter_AddRefs(uri));
698 0 : if (NS_SUCCEEDED(rv)) {
699 0 : LOG(PR_LOG_DEBUG, ("%p Trying load from src=%s", this, NS_ConvertUTF16toUTF8(src).get()));
700 0 : NS_ASSERTION(!mIsLoadingFromSourceChildren,
701 : "Should think we're not loading from source children by default");
702 0 : mLoadingSrc = uri;
703 0 : if (mPreloadAction == nsHTMLMediaElement::PRELOAD_NONE) {
704 : // preload:none media, suspend the load here before we make any
705 : // network requests.
706 0 : SuspendLoad();
707 : return;
708 : }
709 :
710 0 : rv = LoadResource();
711 0 : if (NS_SUCCEEDED(rv)) {
712 : return;
713 : }
714 : } else {
715 0 : const PRUnichar* params[] = { src.get() };
716 0 : ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
717 : }
718 0 : NoSupportedMediaSourceError();
719 : } else {
720 : // Otherwise, the source elements will be used.
721 0 : mIsLoadingFromSourceChildren = true;
722 0 : LoadFromSourceChildren();
723 : }
724 : }
725 :
726 0 : void nsHTMLMediaElement::NotifyLoadError()
727 : {
728 0 : if (!mIsLoadingFromSourceChildren) {
729 0 : LOG(PR_LOG_DEBUG, ("NotifyLoadError(), no supported media error"));
730 0 : NoSupportedMediaSourceError();
731 0 : } else if (mSourceLoadCandidate) {
732 0 : DispatchAsyncSourceError(mSourceLoadCandidate);
733 0 : QueueLoadFromSourceTask();
734 : } else {
735 0 : NS_WARNING("Should know the source we were loading from!");
736 : }
737 0 : }
738 :
739 0 : void nsHTMLMediaElement::NotifyAudioAvailable(float* aFrameBuffer,
740 : PRUint32 aFrameBufferLength,
741 : float aTime)
742 : {
743 : // Auto manage the memory for the frame buffer, so that if we add an early
744 : // return-on-error here in future, we won't forget to release the memory.
745 : // Otherwise we hand ownership of the memory over to the event created by
746 : // DispatchAudioAvailableEvent().
747 0 : nsAutoArrayPtr<float> frameBuffer(aFrameBuffer);
748 : // Do same-origin check on element and media before allowing MozAudioAvailable events.
749 0 : if (!mMediaSecurityVerified) {
750 0 : nsCOMPtr<nsIPrincipal> principal = GetCurrentPrincipal();
751 0 : nsresult rv = NodePrincipal()->Subsumes(principal, &mAllowAudioData);
752 0 : if (NS_FAILED(rv)) {
753 0 : mAllowAudioData = false;
754 : }
755 : }
756 :
757 0 : DispatchAudioAvailableEvent(frameBuffer.forget(), aFrameBufferLength, aTime);
758 0 : }
759 :
760 0 : void nsHTMLMediaElement::LoadFromSourceChildren()
761 : {
762 0 : NS_ASSERTION(mDelayingLoadEvent,
763 : "Should delay load event (if in document) during load");
764 0 : NS_ASSERTION(mIsLoadingFromSourceChildren,
765 : "Must remember we're loading from source children");
766 0 : while (true) {
767 0 : nsIContent* child = GetNextSource();
768 0 : if (!child) {
769 : // Exhausted candidates, wait for more candidates to be appended to
770 : // the media element.
771 0 : mLoadWaitStatus = WAITING_FOR_SOURCE;
772 0 : mNetworkState = nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE;
773 0 : ChangeDelayLoadStatus(false);
774 0 : ReportLoadError("MediaLoadExhaustedCandidates");
775 0 : return;
776 : }
777 :
778 : // Must have src attribute.
779 0 : nsAutoString src;
780 0 : if (!child->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
781 0 : ReportLoadError("MediaLoadSourceMissingSrc");
782 0 : DispatchAsyncSourceError(child);
783 0 : continue;
784 : }
785 :
786 : // If we have a type attribute, it must be a supported type.
787 0 : nsAutoString type;
788 0 : if (child->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type) &&
789 0 : GetCanPlay(type) == CANPLAY_NO) {
790 0 : DispatchAsyncSourceError(child);
791 0 : const PRUnichar* params[] = { type.get(), src.get() };
792 0 : ReportLoadError("MediaLoadUnsupportedType", params, ArrayLength(params));
793 0 : continue;
794 : }
795 0 : LOG(PR_LOG_DEBUG, ("%p Trying load from <source>=%s type=%s", this,
796 : NS_ConvertUTF16toUTF8(src).get(), NS_ConvertUTF16toUTF8(type).get()));
797 :
798 0 : nsCOMPtr<nsIURI> uri;
799 0 : NewURIFromString(src, getter_AddRefs(uri));
800 0 : if (!uri) {
801 0 : DispatchAsyncSourceError(child);
802 0 : const PRUnichar* params[] = { src.get() };
803 0 : ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
804 0 : continue;
805 : }
806 :
807 0 : mLoadingSrc = uri;
808 0 : NS_ASSERTION(mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING,
809 : "Network state should be loading");
810 :
811 0 : if (mPreloadAction == nsHTMLMediaElement::PRELOAD_NONE) {
812 : // preload:none media, suspend the load here before we make any
813 : // network requests.
814 0 : SuspendLoad();
815 : return;
816 : }
817 :
818 0 : if (NS_SUCCEEDED(LoadResource())) {
819 : return;
820 : }
821 :
822 : // If we fail to load, loop back and try loading the next resource.
823 0 : DispatchAsyncSourceError(child);
824 : }
825 : NS_NOTREACHED("Execution should not reach here!");
826 : }
827 :
828 0 : void nsHTMLMediaElement::SuspendLoad()
829 : {
830 0 : mLoadIsSuspended = true;
831 0 : mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
832 0 : DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
833 0 : ChangeDelayLoadStatus(false);
834 0 : }
835 :
836 0 : void nsHTMLMediaElement::ResumeLoad(PreloadAction aAction)
837 : {
838 0 : NS_ASSERTION(mLoadIsSuspended, "Can only resume preload if halted for one");
839 0 : mLoadIsSuspended = false;
840 0 : mPreloadAction = aAction;
841 0 : ChangeDelayLoadStatus(true);
842 0 : mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
843 0 : if (!mIsLoadingFromSourceChildren) {
844 : // We were loading from the element's src attribute.
845 0 : if (NS_FAILED(LoadResource())) {
846 0 : NoSupportedMediaSourceError();
847 : }
848 : } else {
849 : // We were loading from a child <source> element. Try to resume the
850 : // load of that child, and if that fails, try the next child.
851 0 : if (NS_FAILED(LoadResource())) {
852 0 : LoadFromSourceChildren();
853 : }
854 : }
855 0 : }
856 :
857 0 : static bool IsAutoplayEnabled()
858 : {
859 0 : return Preferences::GetBool("media.autoplay.enabled");
860 : }
861 :
862 0 : void nsHTMLMediaElement::UpdatePreloadAction()
863 : {
864 0 : PreloadAction nextAction = PRELOAD_UNDEFINED;
865 : // If autoplay is set, or we're playing, we should always preload data,
866 : // as we'll need it to play.
867 0 : if ((IsAutoplayEnabled() && HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay)) ||
868 0 : !mPaused)
869 : {
870 0 : nextAction = nsHTMLMediaElement::PRELOAD_ENOUGH;
871 : } else {
872 : // Find the appropriate preload action by looking at the attribute.
873 : const nsAttrValue* val = mAttrsAndChildren.GetAttr(nsGkAtoms::preload,
874 0 : kNameSpaceID_None);
875 : PRUint32 preloadDefault =
876 : Preferences::GetInt("media.preload.default",
877 0 : nsHTMLMediaElement::PRELOAD_ATTR_METADATA);
878 : PRUint32 preloadAuto =
879 : Preferences::GetInt("media.preload.auto",
880 0 : nsHTMLMediaElement::PRELOAD_ENOUGH);
881 0 : if (!val) {
882 : // Attribute is not set. Use the preload action specified by the
883 : // media.preload.default pref, or just preload metadata if not present.
884 0 : nextAction = static_cast<PreloadAction>(preloadDefault);
885 0 : } else if (val->Type() == nsAttrValue::eEnum) {
886 0 : PreloadAttrValue attr = static_cast<PreloadAttrValue>(val->GetEnumValue());
887 0 : if (attr == nsHTMLMediaElement::PRELOAD_ATTR_EMPTY ||
888 : attr == nsHTMLMediaElement::PRELOAD_ATTR_AUTO)
889 : {
890 0 : nextAction = static_cast<PreloadAction>(preloadAuto);
891 0 : } else if (attr == nsHTMLMediaElement::PRELOAD_ATTR_METADATA) {
892 0 : nextAction = nsHTMLMediaElement::PRELOAD_METADATA;
893 0 : } else if (attr == nsHTMLMediaElement::PRELOAD_ATTR_NONE) {
894 0 : nextAction = nsHTMLMediaElement::PRELOAD_NONE;
895 : }
896 : } else {
897 : // Use the suggested "missing value default" of "metadata", or the value
898 : // specified by the media.preload.default, if present.
899 0 : nextAction = static_cast<PreloadAction>(preloadDefault);
900 : }
901 : }
902 :
903 0 : if ((mBegun || mIsRunningSelectResource) && nextAction < mPreloadAction) {
904 : // We've started a load or are already downloading, and the preload was
905 : // changed to a state where we buffer less. We don't support this case,
906 : // so don't change the preload behaviour.
907 0 : return;
908 : }
909 :
910 0 : mPreloadAction = nextAction;
911 0 : if (nextAction == nsHTMLMediaElement::PRELOAD_ENOUGH) {
912 0 : if (mLoadIsSuspended) {
913 : // Our load was previouly suspended due to the media having preload
914 : // value "none". The preload value has changed to preload:auto, so
915 : // resume the load.
916 0 : ResumeLoad(PRELOAD_ENOUGH);
917 : } else {
918 : // Preload as much of the video as we can, i.e. don't suspend after
919 : // the first frame.
920 0 : StopSuspendingAfterFirstFrame();
921 : }
922 :
923 0 : } else if (nextAction == nsHTMLMediaElement::PRELOAD_METADATA) {
924 : // Ensure that the video can be suspended after first frame.
925 0 : mAllowSuspendAfterFirstFrame = true;
926 0 : if (mLoadIsSuspended) {
927 : // Our load was previouly suspended due to the media having preload
928 : // value "none". The preload value has changed to preload:metadata, so
929 : // resume the load. We'll pause the load again after we've read the
930 : // metadata.
931 0 : ResumeLoad(PRELOAD_METADATA);
932 : }
933 : }
934 : }
935 :
936 0 : nsresult nsHTMLMediaElement::LoadResource()
937 : {
938 0 : NS_ASSERTION(mDelayingLoadEvent,
939 : "Should delay load event (if in document) during load");
940 :
941 : // If a previous call to mozSetup() was made, kill that media resource
942 : // in order to use this new src instead.
943 0 : if (mAudioStream) {
944 0 : mAudioStream->Shutdown();
945 0 : mAudioStream = nsnull;
946 : }
947 :
948 0 : if (mChannel) {
949 0 : mChannel->Cancel(NS_BINDING_ABORTED);
950 0 : mChannel = nsnull;
951 : }
952 :
953 : // Set the media element's CORS mode only when loading a resource
954 0 : mCORSMode = AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin));
955 :
956 0 : nsHTMLMediaElement* other = LookupMediaElementURITable(mLoadingSrc);
957 0 : if (other) {
958 : // Clone it.
959 0 : nsresult rv = InitializeDecoderAsClone(other->mDecoder);
960 0 : if (NS_SUCCEEDED(rv))
961 0 : return rv;
962 : }
963 :
964 0 : PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
965 : nsresult rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_MEDIA,
966 : mLoadingSrc,
967 : NodePrincipal(),
968 : static_cast<nsGenericElement*>(this),
969 0 : EmptyCString(), // mime type
970 : nsnull, // extra
971 : &shouldLoad,
972 : nsContentUtils::GetContentPolicy(),
973 0 : nsContentUtils::GetSecurityManager());
974 0 : NS_ENSURE_SUCCESS(rv, rv);
975 0 : if (NS_CP_REJECTED(shouldLoad)) {
976 0 : return NS_ERROR_FAILURE;
977 : }
978 :
979 0 : nsCOMPtr<nsILoadGroup> loadGroup = GetDocumentLoadGroup();
980 :
981 : // check for a Content Security Policy to pass down to the channel
982 : // created to load the media content
983 0 : nsCOMPtr<nsIChannelPolicy> channelPolicy;
984 0 : nsCOMPtr<nsIContentSecurityPolicy> csp;
985 0 : rv = NodePrincipal()->GetCsp(getter_AddRefs(csp));
986 0 : NS_ENSURE_SUCCESS(rv,rv);
987 0 : if (csp) {
988 0 : channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1");
989 0 : channelPolicy->SetContentSecurityPolicy(csp);
990 0 : channelPolicy->SetLoadType(nsIContentPolicy::TYPE_MEDIA);
991 : }
992 0 : nsCOMPtr<nsIChannel> channel;
993 0 : rv = NS_NewChannel(getter_AddRefs(channel),
994 : mLoadingSrc,
995 : nsnull,
996 : loadGroup,
997 : nsnull,
998 : nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY,
999 0 : channelPolicy);
1000 0 : NS_ENSURE_SUCCESS(rv,rv);
1001 :
1002 : // The listener holds a strong reference to us. This creates a
1003 : // reference cycle, once we've set mChannel, which is manually broken
1004 : // in the listener's OnStartRequest method after it is finished with
1005 : // the element. The cycle will also be broken if we get a shutdown
1006 : // notification before OnStartRequest fires. Necko guarantees that
1007 : // OnStartRequest will eventually fire if we don't shut down first.
1008 0 : nsRefPtr<MediaLoadListener> loadListener = new MediaLoadListener(this);
1009 :
1010 0 : channel->SetNotificationCallbacks(loadListener);
1011 :
1012 0 : nsCOMPtr<nsIStreamListener> listener;
1013 0 : if (ShouldCheckAllowOrigin()) {
1014 : listener =
1015 : new nsCORSListenerProxy(loadListener,
1016 0 : NodePrincipal(),
1017 : channel,
1018 0 : GetCORSMode() == CORS_USE_CREDENTIALS,
1019 0 : &rv);
1020 : } else {
1021 0 : rv = nsContentUtils::GetSecurityManager()->
1022 : CheckLoadURIWithPrincipal(NodePrincipal(),
1023 : mLoadingSrc,
1024 0 : nsIScriptSecurityManager::STANDARD);
1025 0 : listener = loadListener;
1026 : }
1027 0 : NS_ENSURE_SUCCESS(rv, rv);
1028 :
1029 0 : nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(channel);
1030 0 : if (hc) {
1031 : // Use a byte range request from the start of the resource.
1032 : // This enables us to detect if the stream supports byte range
1033 : // requests, and therefore seeking, early.
1034 0 : hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"),
1035 0 : NS_LITERAL_CSTRING("bytes=0-"),
1036 0 : false);
1037 :
1038 0 : SetRequestHeaders(hc);
1039 : }
1040 :
1041 0 : rv = channel->AsyncOpen(listener, nsnull);
1042 0 : NS_ENSURE_SUCCESS(rv, rv);
1043 :
1044 : // Else the channel must be open and starting to download. If it encounters
1045 : // a non-catastrophic failure, it will set a new task to continue loading
1046 : // another candidate. It's safe to set it as mChannel now.
1047 0 : mChannel = channel;
1048 :
1049 : // loadListener will be unregistered either on shutdown or when
1050 : // OnStartRequest for the channel we just opened fires.
1051 0 : nsContentUtils::RegisterShutdownObserver(loadListener);
1052 0 : return NS_OK;
1053 : }
1054 :
1055 0 : nsresult nsHTMLMediaElement::LoadWithChannel(nsIChannel *aChannel,
1056 : nsIStreamListener **aListener)
1057 : {
1058 0 : NS_ENSURE_ARG_POINTER(aChannel);
1059 0 : NS_ENSURE_ARG_POINTER(aListener);
1060 :
1061 0 : *aListener = nsnull;
1062 :
1063 0 : AbortExistingLoads();
1064 :
1065 0 : nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(mLoadingSrc));
1066 0 : NS_ENSURE_SUCCESS(rv, rv);
1067 :
1068 0 : ChangeDelayLoadStatus(true);
1069 0 : rv = InitializeDecoderForChannel(aChannel, aListener);
1070 0 : if (NS_FAILED(rv)) {
1071 0 : ChangeDelayLoadStatus(false);
1072 0 : return rv;
1073 : }
1074 :
1075 0 : DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
1076 :
1077 0 : return NS_OK;
1078 : }
1079 :
1080 0 : NS_IMETHODIMP nsHTMLMediaElement::MozLoadFrom(nsIDOMHTMLMediaElement* aOther)
1081 : {
1082 0 : NS_ENSURE_ARG_POINTER(aOther);
1083 :
1084 0 : AbortExistingLoads();
1085 :
1086 0 : nsCOMPtr<nsIContent> content = do_QueryInterface(aOther);
1087 0 : nsHTMLMediaElement* other = static_cast<nsHTMLMediaElement*>(content.get());
1088 0 : if (!other || !other->mDecoder)
1089 0 : return NS_OK;
1090 :
1091 0 : ChangeDelayLoadStatus(true);
1092 :
1093 0 : mLoadingSrc = other->mLoadingSrc;
1094 0 : nsresult rv = InitializeDecoderAsClone(other->mDecoder);
1095 0 : if (NS_FAILED(rv)) {
1096 0 : ChangeDelayLoadStatus(false);
1097 0 : return rv;
1098 : }
1099 :
1100 0 : DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
1101 :
1102 0 : return NS_OK;
1103 : }
1104 :
1105 : /* readonly attribute unsigned short readyState; */
1106 0 : NS_IMETHODIMP nsHTMLMediaElement::GetReadyState(PRUint16 *aReadyState)
1107 : {
1108 0 : *aReadyState = mReadyState;
1109 :
1110 0 : return NS_OK;
1111 : }
1112 :
1113 : /* readonly attribute boolean seeking; */
1114 0 : NS_IMETHODIMP nsHTMLMediaElement::GetSeeking(bool *aSeeking)
1115 : {
1116 0 : *aSeeking = mDecoder && mDecoder->IsSeeking();
1117 :
1118 0 : return NS_OK;
1119 : }
1120 :
1121 : /* attribute double currentTime; */
1122 0 : NS_IMETHODIMP nsHTMLMediaElement::GetCurrentTime(double *aCurrentTime)
1123 : {
1124 0 : *aCurrentTime = mDecoder ? mDecoder->GetCurrentTime() : 0.0;
1125 0 : return NS_OK;
1126 : }
1127 :
1128 0 : NS_IMETHODIMP nsHTMLMediaElement::SetCurrentTime(double aCurrentTime)
1129 : {
1130 0 : StopSuspendingAfterFirstFrame();
1131 :
1132 0 : if (!mDecoder) {
1133 0 : LOG(PR_LOG_DEBUG, ("%p SetCurrentTime(%f) failed: no decoder", this, aCurrentTime));
1134 0 : return NS_ERROR_DOM_INVALID_STATE_ERR;
1135 : }
1136 :
1137 0 : if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
1138 0 : LOG(PR_LOG_DEBUG, ("%p SetCurrentTime(%f) failed: no source", this, aCurrentTime));
1139 0 : return NS_ERROR_DOM_INVALID_STATE_ERR;
1140 : }
1141 :
1142 : // Detect for a NaN and invalid values.
1143 0 : if (aCurrentTime != aCurrentTime) {
1144 0 : LOG(PR_LOG_DEBUG, ("%p SetCurrentTime(%f) failed: bad time", this, aCurrentTime));
1145 0 : return NS_ERROR_FAILURE;
1146 : }
1147 :
1148 : // Clamp the time to [0, duration] as required by the spec
1149 0 : double clampedTime = NS_MAX(0.0, aCurrentTime);
1150 0 : double duration = mDecoder->GetDuration();
1151 0 : if (duration >= 0) {
1152 0 : clampedTime = NS_MIN(clampedTime, duration);
1153 : }
1154 :
1155 0 : mPlayingBeforeSeek = IsPotentiallyPlaying();
1156 : // The media backend is responsible for dispatching the timeupdate
1157 : // event if it changes the playback position as a result of the seek.
1158 0 : LOG(PR_LOG_DEBUG, ("%p SetCurrentTime(%f) starting seek", this, aCurrentTime));
1159 0 : nsresult rv = mDecoder->Seek(clampedTime);
1160 :
1161 : // We changed whether we're seeking so we need to AddRemoveSelfReference
1162 0 : AddRemoveSelfReference();
1163 :
1164 0 : return rv;
1165 : }
1166 :
1167 : /* readonly attribute double duration; */
1168 0 : NS_IMETHODIMP nsHTMLMediaElement::GetDuration(double *aDuration)
1169 : {
1170 0 : *aDuration = mDecoder ? mDecoder->GetDuration() : std::numeric_limits<double>::quiet_NaN();
1171 0 : return NS_OK;
1172 : }
1173 :
1174 : /* readonly attribute nsIDOMHTMLTimeRanges seekable; */
1175 0 : NS_IMETHODIMP nsHTMLMediaElement::GetSeekable(nsIDOMTimeRanges** aSeekable)
1176 : {
1177 0 : nsRefPtr<nsTimeRanges> ranges = new nsTimeRanges();
1178 0 : if (mDecoder && mReadyState > nsIDOMHTMLMediaElement::HAVE_NOTHING) {
1179 0 : mDecoder->GetSeekable(ranges);
1180 : }
1181 0 : ranges.forget(aSeekable);
1182 0 : return NS_OK;
1183 : }
1184 :
1185 :
1186 : /* readonly attribute boolean paused; */
1187 0 : NS_IMETHODIMP nsHTMLMediaElement::GetPaused(bool *aPaused)
1188 : {
1189 0 : *aPaused = mPaused;
1190 :
1191 0 : return NS_OK;
1192 : }
1193 :
1194 : /* void pause (); */
1195 0 : NS_IMETHODIMP nsHTMLMediaElement::Pause()
1196 : {
1197 0 : if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
1198 0 : LOG(PR_LOG_DEBUG, ("Loading due to Pause()"));
1199 0 : nsresult rv = Load();
1200 0 : NS_ENSURE_SUCCESS(rv, rv);
1201 0 : } else if (mDecoder) {
1202 0 : mDecoder->Pause();
1203 : }
1204 :
1205 0 : bool oldPaused = mPaused;
1206 0 : mPaused = true;
1207 0 : mAutoplaying = false;
1208 : // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
1209 0 : AddRemoveSelfReference();
1210 :
1211 0 : if (!oldPaused) {
1212 0 : FireTimeUpdate(false);
1213 0 : DispatchAsyncEvent(NS_LITERAL_STRING("pause"));
1214 : }
1215 :
1216 0 : return NS_OK;
1217 : }
1218 :
1219 : /* attribute double volume; */
1220 0 : NS_IMETHODIMP nsHTMLMediaElement::GetVolume(double *aVolume)
1221 : {
1222 0 : *aVolume = mVolume;
1223 :
1224 0 : return NS_OK;
1225 : }
1226 :
1227 0 : NS_IMETHODIMP nsHTMLMediaElement::SetVolume(double aVolume)
1228 : {
1229 0 : if (aVolume < 0.0 || aVolume > 1.0)
1230 0 : return NS_ERROR_DOM_INDEX_SIZE_ERR;
1231 :
1232 0 : if (aVolume == mVolume)
1233 0 : return NS_OK;
1234 :
1235 0 : mVolume = aVolume;
1236 :
1237 0 : if (mDecoder && !mMuted) {
1238 0 : mDecoder->SetVolume(mVolume);
1239 0 : } else if (mAudioStream && !mMuted) {
1240 0 : mAudioStream->SetVolume(mVolume);
1241 : }
1242 :
1243 0 : DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
1244 :
1245 0 : return NS_OK;
1246 : }
1247 :
1248 : NS_IMETHODIMP
1249 0 : nsHTMLMediaElement::GetMozChannels(PRUint32 *aMozChannels)
1250 : {
1251 0 : if (!mDecoder && !mAudioStream) {
1252 0 : return NS_ERROR_DOM_INVALID_STATE_ERR;
1253 : }
1254 :
1255 0 : *aMozChannels = mChannels;
1256 0 : return NS_OK;
1257 : }
1258 :
1259 : NS_IMETHODIMP
1260 0 : nsHTMLMediaElement::GetMozSampleRate(PRUint32 *aMozSampleRate)
1261 : {
1262 0 : if (!mDecoder && !mAudioStream) {
1263 0 : return NS_ERROR_DOM_INVALID_STATE_ERR;
1264 : }
1265 :
1266 0 : *aMozSampleRate = mRate;
1267 0 : return NS_OK;
1268 : }
1269 :
1270 : NS_IMETHODIMP
1271 0 : nsHTMLMediaElement::GetMozFrameBufferLength(PRUint32 *aMozFrameBufferLength)
1272 : {
1273 : // The framebuffer (via MozAudioAvailable events) is only available
1274 : // when reading vs. writing audio directly.
1275 0 : if (!mDecoder) {
1276 0 : return NS_ERROR_DOM_INVALID_STATE_ERR;
1277 : }
1278 :
1279 0 : *aMozFrameBufferLength = mDecoder->GetFrameBufferLength();
1280 0 : return NS_OK;
1281 : }
1282 :
1283 : NS_IMETHODIMP
1284 0 : nsHTMLMediaElement::SetMozFrameBufferLength(PRUint32 aMozFrameBufferLength)
1285 : {
1286 0 : if (!mDecoder)
1287 0 : return NS_ERROR_DOM_INVALID_STATE_ERR;
1288 :
1289 0 : return mDecoder->RequestFrameBufferLength(aMozFrameBufferLength);
1290 : }
1291 :
1292 : /* attribute boolean muted; */
1293 0 : NS_IMETHODIMP nsHTMLMediaElement::GetMuted(bool *aMuted)
1294 : {
1295 0 : *aMuted = mMuted;
1296 :
1297 0 : return NS_OK;
1298 : }
1299 :
1300 0 : NS_IMETHODIMP nsHTMLMediaElement::SetMuted(bool aMuted)
1301 : {
1302 0 : if (aMuted == mMuted)
1303 0 : return NS_OK;
1304 :
1305 0 : mMuted = aMuted;
1306 :
1307 0 : if (mDecoder) {
1308 0 : mDecoder->SetVolume(mMuted ? 0.0 : mVolume);
1309 0 : } else if (mAudioStream) {
1310 0 : mAudioStream->SetVolume(mMuted ? 0.0 : mVolume);
1311 : }
1312 :
1313 0 : DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
1314 :
1315 0 : return NS_OK;
1316 : }
1317 :
1318 0 : class MediaElementSetForURI : public nsURIHashKey {
1319 : public:
1320 0 : MediaElementSetForURI(const nsIURI* aKey) : nsURIHashKey(aKey) {}
1321 : MediaElementSetForURI(const MediaElementSetForURI& toCopy)
1322 : : nsURIHashKey(toCopy), mElements(toCopy.mElements) {}
1323 : nsTArray<nsHTMLMediaElement*> mElements;
1324 : };
1325 :
1326 : typedef nsTHashtable<MediaElementSetForURI> MediaElementURITable;
1327 : // Elements in this table must have non-null mDecoder and mLoadingSrc, and those
1328 : // can't change while the element is in the table. The table is keyed by
1329 : // the element's mLoadingSrc. Each entry has a list of all elements with the
1330 : // same mLoadingSrc.
1331 : static MediaElementURITable* gElementTable;
1332 :
1333 : #ifdef DEBUG
1334 : // Returns the number of times aElement appears in the media element table
1335 : // for aURI. If this returns other than 0 or 1, there's a bug somewhere!
1336 : static unsigned
1337 0 : MediaElementTableCount(nsHTMLMediaElement* aElement, nsIURI* aURI)
1338 : {
1339 0 : if (!gElementTable || !aElement || !aURI) {
1340 0 : return 0;
1341 : }
1342 0 : MediaElementSetForURI* entry = gElementTable->GetEntry(aURI);
1343 0 : if (!entry) {
1344 0 : return 0;
1345 : }
1346 0 : PRUint32 count = 0;
1347 0 : for (PRUint32 i = 0; i < entry->mElements.Length(); ++i) {
1348 0 : nsHTMLMediaElement* elem = entry->mElements[i];
1349 0 : if (elem == aElement) {
1350 0 : count++;
1351 : }
1352 : }
1353 0 : return count;
1354 : }
1355 : #endif
1356 :
1357 : void
1358 0 : nsHTMLMediaElement::AddMediaElementToURITable()
1359 : {
1360 0 : NS_ASSERTION(mDecoder && mDecoder->GetResource(), "Call this only with decoder Load called");
1361 0 : NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
1362 : "Should not have entry for element in element table before addition");
1363 0 : if (!gElementTable) {
1364 0 : gElementTable = new MediaElementURITable();
1365 0 : gElementTable->Init();
1366 : }
1367 0 : MediaElementSetForURI* entry = gElementTable->PutEntry(mLoadingSrc);
1368 0 : entry->mElements.AppendElement(this);
1369 0 : NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 1,
1370 : "Should have a single entry for element in element table after addition");
1371 0 : }
1372 :
1373 : void
1374 0 : nsHTMLMediaElement::RemoveMediaElementFromURITable()
1375 : {
1376 0 : NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 1,
1377 : "Before remove, should have a single entry for element in element table");
1378 0 : NS_ASSERTION(mDecoder, "Don't call this without decoder!");
1379 0 : NS_ASSERTION(mLoadingSrc, "Can't have decoder without source!");
1380 0 : if (!gElementTable)
1381 0 : return;
1382 0 : MediaElementSetForURI* entry = gElementTable->GetEntry(mLoadingSrc);
1383 0 : if (!entry)
1384 0 : return;
1385 0 : entry->mElements.RemoveElement(this);
1386 0 : if (entry->mElements.IsEmpty()) {
1387 0 : gElementTable->RemoveEntry(mLoadingSrc);
1388 0 : if (gElementTable->Count() == 0) {
1389 0 : delete gElementTable;
1390 0 : gElementTable = nsnull;
1391 : }
1392 : }
1393 0 : NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
1394 : "After remove, should no longer have an entry in element table");
1395 : }
1396 :
1397 : nsHTMLMediaElement*
1398 0 : nsHTMLMediaElement::LookupMediaElementURITable(nsIURI* aURI)
1399 : {
1400 0 : if (!gElementTable)
1401 0 : return nsnull;
1402 0 : MediaElementSetForURI* entry = gElementTable->GetEntry(aURI);
1403 0 : if (!entry)
1404 0 : return nsnull;
1405 0 : for (PRUint32 i = 0; i < entry->mElements.Length(); ++i) {
1406 0 : nsHTMLMediaElement* elem = entry->mElements[i];
1407 : bool equal;
1408 : // Look for elements that have the same principal and CORS mode.
1409 : // Ditto for anything else that could cause us to send different headers.
1410 0 : if (NS_SUCCEEDED(elem->NodePrincipal()->Equals(NodePrincipal(), &equal)) && equal &&
1411 : elem->mCORSMode == mCORSMode) {
1412 0 : NS_ASSERTION(elem->mDecoder && elem->mDecoder->GetResource(), "Decoder gone");
1413 0 : MediaResource* resource = elem->mDecoder->GetResource();
1414 0 : if (resource->CanClone()) {
1415 0 : return elem;
1416 : }
1417 : }
1418 : }
1419 0 : return nsnull;
1420 : }
1421 :
1422 0 : nsHTMLMediaElement::nsHTMLMediaElement(already_AddRefed<nsINodeInfo> aNodeInfo)
1423 : : nsGenericHTMLElement(aNodeInfo),
1424 : mCurrentLoadID(0),
1425 : mNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY),
1426 : mReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING),
1427 : mLoadWaitStatus(NOT_WAITING),
1428 : mVolume(1.0),
1429 : mChannels(0),
1430 : mRate(0),
1431 : mPreloadAction(PRELOAD_UNDEFINED),
1432 : mMediaSize(-1,-1),
1433 : mLastCurrentTime(0.0),
1434 : mFragmentStart(-1.0),
1435 : mFragmentEnd(-1.0),
1436 : mAllowAudioData(false),
1437 : mBegun(false),
1438 : mLoadedFirstFrame(false),
1439 : mAutoplaying(true),
1440 : mAutoplayEnabled(true),
1441 : mPaused(true),
1442 : mMuted(false),
1443 : mPlayingBeforeSeek(false),
1444 : mPausedForInactiveDocument(false),
1445 : mWaitingFired(false),
1446 : mIsRunningLoadMethod(false),
1447 : mIsLoadingFromSourceChildren(false),
1448 : mDelayingLoadEvent(false),
1449 : mIsRunningSelectResource(false),
1450 : mHaveQueuedSelectResource(false),
1451 : mSuspendedAfterFirstFrame(false),
1452 : mAllowSuspendAfterFirstFrame(true),
1453 : mHasPlayedOrSeeked(false),
1454 : mHasSelfReference(false),
1455 : mShuttingDown(false),
1456 : mLoadIsSuspended(false),
1457 : mMediaSecurityVerified(false),
1458 0 : mCORSMode(CORS_NONE)
1459 : {
1460 : #ifdef PR_LOGGING
1461 0 : if (!gMediaElementLog) {
1462 0 : gMediaElementLog = PR_NewLogModule("nsMediaElement");
1463 : }
1464 0 : if (!gMediaElementEventsLog) {
1465 0 : gMediaElementEventsLog = PR_NewLogModule("nsMediaElementEvents");
1466 : }
1467 : #endif
1468 :
1469 0 : RegisterFreezableElement();
1470 0 : NotifyOwnerDocumentActivityChanged();
1471 0 : }
1472 :
1473 0 : nsHTMLMediaElement::~nsHTMLMediaElement()
1474 : {
1475 0 : NS_ASSERTION(!mHasSelfReference,
1476 : "How can we be destroyed if we're still holding a self reference?");
1477 :
1478 0 : if (mVideoFrameContainer) {
1479 0 : mVideoFrameContainer->ForgetElement();
1480 : }
1481 0 : UnregisterFreezableElement();
1482 0 : if (mDecoder) {
1483 0 : RemoveMediaElementFromURITable();
1484 0 : mDecoder->Shutdown();
1485 : }
1486 :
1487 0 : NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
1488 : "Destroyed media element should no longer be in element table");
1489 :
1490 0 : if (mChannel) {
1491 0 : mChannel->Cancel(NS_BINDING_ABORTED);
1492 : }
1493 0 : if (mAudioStream) {
1494 0 : mAudioStream->Shutdown();
1495 : }
1496 0 : }
1497 :
1498 0 : void nsHTMLMediaElement::StopSuspendingAfterFirstFrame()
1499 : {
1500 0 : mAllowSuspendAfterFirstFrame = false;
1501 0 : if (!mSuspendedAfterFirstFrame)
1502 0 : return;
1503 0 : mSuspendedAfterFirstFrame = false;
1504 0 : if (mDecoder) {
1505 0 : mDecoder->Resume(true);
1506 : }
1507 : }
1508 :
1509 0 : void nsHTMLMediaElement::SetPlayedOrSeeked(bool aValue)
1510 : {
1511 0 : if (aValue == mHasPlayedOrSeeked) {
1512 0 : return;
1513 : }
1514 :
1515 0 : mHasPlayedOrSeeked = aValue;
1516 :
1517 : // Force a reflow so that the poster frame hides or shows immediately.
1518 0 : nsIFrame* frame = GetPrimaryFrame();
1519 0 : if (!frame) {
1520 0 : return;
1521 : }
1522 0 : frame->PresContext()->PresShell()->FrameNeedsReflow(frame,
1523 : nsIPresShell::eTreeChange,
1524 0 : NS_FRAME_IS_DIRTY);
1525 : }
1526 :
1527 0 : NS_IMETHODIMP nsHTMLMediaElement::Play()
1528 : {
1529 0 : StopSuspendingAfterFirstFrame();
1530 0 : SetPlayedOrSeeked(true);
1531 :
1532 0 : if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
1533 0 : nsresult rv = Load();
1534 0 : NS_ENSURE_SUCCESS(rv, rv);
1535 : }
1536 0 : if (mLoadIsSuspended) {
1537 0 : ResumeLoad(PRELOAD_ENOUGH);
1538 : }
1539 : // Even if we just did Load() or ResumeLoad(), we could already have a decoder
1540 : // here if we managed to clone an existing decoder.
1541 0 : if (mDecoder) {
1542 0 : if (mDecoder->IsEnded()) {
1543 0 : SetCurrentTime(0);
1544 : }
1545 0 : if (!mPausedForInactiveDocument) {
1546 0 : nsresult rv = mDecoder->Play();
1547 0 : NS_ENSURE_SUCCESS(rv, rv);
1548 : }
1549 : }
1550 :
1551 : // TODO: If the playback has ended, then the user agent must set
1552 : // seek to the effective start.
1553 : // TODO: The playback rate must be set to the default playback rate.
1554 0 : if (mPaused) {
1555 0 : DispatchAsyncEvent(NS_LITERAL_STRING("play"));
1556 0 : switch (mReadyState) {
1557 : case nsIDOMHTMLMediaElement::HAVE_NOTHING:
1558 0 : DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
1559 0 : break;
1560 : case nsIDOMHTMLMediaElement::HAVE_METADATA:
1561 : case nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA:
1562 0 : FireTimeUpdate(false);
1563 0 : DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
1564 0 : break;
1565 : case nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA:
1566 : case nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA:
1567 0 : DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
1568 0 : break;
1569 : }
1570 : }
1571 :
1572 0 : mPaused = false;
1573 0 : mAutoplaying = false;
1574 : // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
1575 : // and our preload status.
1576 0 : AddRemoveSelfReference();
1577 0 : UpdatePreloadAction();
1578 :
1579 0 : return NS_OK;
1580 : }
1581 :
1582 0 : NS_IMPL_STRING_ATTR(nsHTMLMediaElement, Crossorigin, crossorigin)
1583 :
1584 0 : bool nsHTMLMediaElement::ParseAttribute(PRInt32 aNamespaceID,
1585 : nsIAtom* aAttribute,
1586 : const nsAString& aValue,
1587 : nsAttrValue& aResult)
1588 : {
1589 : // Mappings from 'preload' attribute strings to an enumeration.
1590 : static const nsAttrValue::EnumTable kPreloadTable[] = {
1591 : { "", nsHTMLMediaElement::PRELOAD_ATTR_EMPTY },
1592 : { "none", nsHTMLMediaElement::PRELOAD_ATTR_NONE },
1593 : { "metadata", nsHTMLMediaElement::PRELOAD_ATTR_METADATA },
1594 : { "auto", nsHTMLMediaElement::PRELOAD_ATTR_AUTO },
1595 : { 0 }
1596 : };
1597 :
1598 0 : if (aNamespaceID == kNameSpaceID_None) {
1599 0 : if (ParseImageAttribute(aAttribute, aValue, aResult)) {
1600 0 : return true;
1601 : }
1602 0 : if (aAttribute == nsGkAtoms::crossorigin) {
1603 0 : ParseCORSValue(aValue, aResult);
1604 0 : return true;
1605 : }
1606 0 : if (aAttribute == nsGkAtoms::preload) {
1607 0 : return aResult.ParseEnumValue(aValue, kPreloadTable, false);
1608 : }
1609 : }
1610 :
1611 : return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
1612 0 : aResult);
1613 : }
1614 :
1615 0 : void nsHTMLMediaElement::DoneCreatingElement()
1616 : {
1617 0 : if (HasAttr(kNameSpaceID_None, nsGkAtoms::muted))
1618 0 : mMuted = true;
1619 0 : }
1620 :
1621 0 : nsresult nsHTMLMediaElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
1622 : nsIAtom* aPrefix, const nsAString& aValue,
1623 : bool aNotify)
1624 : {
1625 : nsresult rv =
1626 : nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
1627 0 : aNotify);
1628 0 : if (NS_FAILED(rv))
1629 0 : return rv;
1630 0 : if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::src) {
1631 0 : Load();
1632 : }
1633 0 : if (aNotify && aNameSpaceID == kNameSpaceID_None) {
1634 0 : if (aName == nsGkAtoms::autoplay) {
1635 0 : StopSuspendingAfterFirstFrame();
1636 0 : if (mReadyState == nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) {
1637 0 : NotifyAutoplayDataReady();
1638 : }
1639 : // This attribute can affect AddRemoveSelfReference
1640 0 : AddRemoveSelfReference();
1641 0 : UpdatePreloadAction();
1642 0 : } else if (aName == nsGkAtoms::preload) {
1643 0 : UpdatePreloadAction();
1644 : }
1645 : }
1646 :
1647 0 : return rv;
1648 : }
1649 :
1650 0 : nsresult nsHTMLMediaElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttr,
1651 : bool aNotify)
1652 : {
1653 0 : nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttr, aNotify);
1654 0 : if (NS_FAILED(rv))
1655 0 : return rv;
1656 0 : if (aNotify && aNameSpaceID == kNameSpaceID_None) {
1657 0 : if (aAttr == nsGkAtoms::autoplay) {
1658 : // This attribute can affect AddRemoveSelfReference
1659 0 : AddRemoveSelfReference();
1660 0 : UpdatePreloadAction();
1661 0 : } else if (aAttr == nsGkAtoms::preload) {
1662 0 : UpdatePreloadAction();
1663 : }
1664 : }
1665 :
1666 0 : return rv;
1667 : }
1668 :
1669 0 : nsresult nsHTMLMediaElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
1670 : nsIContent* aBindingParent,
1671 : bool aCompileEventHandlers)
1672 : {
1673 : nsresult rv = nsGenericHTMLElement::BindToTree(aDocument,
1674 : aParent,
1675 : aBindingParent,
1676 0 : aCompileEventHandlers);
1677 0 : if (aDocument) {
1678 : mAutoplayEnabled =
1679 0 : IsAutoplayEnabled() && (!aDocument || !aDocument->IsStaticDocument()) &&
1680 0 : !IsEditable();
1681 : // The preload action depends on the value of the autoplay attribute.
1682 : // It's value may have changed, so update it.
1683 0 : UpdatePreloadAction();
1684 :
1685 0 : if (aDocument->HasAudioAvailableListeners()) {
1686 : // The document already has listeners for the "MozAudioAvailable"
1687 : // event, so the decoder must be notified so it initiates
1688 : // "MozAudioAvailable" event dispatch.
1689 0 : NotifyAudioAvailableListener();
1690 : }
1691 : }
1692 :
1693 0 : return rv;
1694 : }
1695 :
1696 0 : void nsHTMLMediaElement::UnbindFromTree(bool aDeep,
1697 : bool aNullParent)
1698 : {
1699 0 : if (!mPaused && mNetworkState != nsIDOMHTMLMediaElement::NETWORK_EMPTY)
1700 0 : Pause();
1701 0 : nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
1702 0 : }
1703 :
1704 : #ifdef MOZ_RAW
1705 : static const char gRawTypes[][16] = {
1706 : "video/x-raw",
1707 : "video/x-raw-yuv"
1708 : };
1709 :
1710 : static const char* gRawCodecs[] = {
1711 : nsnull
1712 : };
1713 :
1714 : static bool IsRawEnabled()
1715 : {
1716 : return Preferences::GetBool("media.raw.enabled");
1717 : }
1718 :
1719 : static bool IsRawType(const nsACString& aType)
1720 : {
1721 : if (!IsRawEnabled()) {
1722 : return false;
1723 : }
1724 :
1725 : for (PRUint32 i = 0; i < ArrayLength(gRawTypes); ++i) {
1726 : if (aType.EqualsASCII(gRawTypes[i])) {
1727 : return true;
1728 : }
1729 : }
1730 :
1731 : return false;
1732 : }
1733 : #endif
1734 : #ifdef MOZ_OGG
1735 : // See http://www.rfc-editor.org/rfc/rfc5334.txt for the definitions
1736 : // of Ogg media types and codec types
1737 : const char nsHTMLMediaElement::gOggTypes[3][16] = {
1738 : "video/ogg",
1739 : "audio/ogg",
1740 : "application/ogg"
1741 : };
1742 :
1743 : char const *const nsHTMLMediaElement::gOggCodecs[3] = {
1744 : "vorbis",
1745 : "theora",
1746 : nsnull
1747 : };
1748 :
1749 : bool
1750 0 : nsHTMLMediaElement::IsOggEnabled()
1751 : {
1752 0 : return Preferences::GetBool("media.ogg.enabled");
1753 : }
1754 :
1755 : bool
1756 0 : nsHTMLMediaElement::IsOggType(const nsACString& aType)
1757 : {
1758 0 : if (!IsOggEnabled()) {
1759 0 : return false;
1760 : }
1761 :
1762 0 : for (PRUint32 i = 0; i < ArrayLength(gOggTypes); ++i) {
1763 0 : if (aType.EqualsASCII(gOggTypes[i])) {
1764 0 : return true;
1765 : }
1766 : }
1767 :
1768 0 : return false;
1769 : }
1770 : #endif
1771 :
1772 : #ifdef MOZ_WAVE
1773 : // See http://www.rfc-editor.org/rfc/rfc2361.txt for the definitions
1774 : // of WAVE media types and codec types. However, the audio/vnd.wave
1775 : // MIME type described there is not used.
1776 : const char nsHTMLMediaElement::gWaveTypes[4][16] = {
1777 : "audio/x-wav",
1778 : "audio/wav",
1779 : "audio/wave",
1780 : "audio/x-pn-wav"
1781 : };
1782 :
1783 : char const *const nsHTMLMediaElement::gWaveCodecs[2] = {
1784 : "1", // Microsoft PCM Format
1785 : nsnull
1786 : };
1787 :
1788 : bool
1789 0 : nsHTMLMediaElement::IsWaveEnabled()
1790 : {
1791 0 : return Preferences::GetBool("media.wave.enabled");
1792 : }
1793 :
1794 : bool
1795 0 : nsHTMLMediaElement::IsWaveType(const nsACString& aType)
1796 : {
1797 0 : if (!IsWaveEnabled()) {
1798 0 : return false;
1799 : }
1800 :
1801 0 : for (PRUint32 i = 0; i < ArrayLength(gWaveTypes); ++i) {
1802 0 : if (aType.EqualsASCII(gWaveTypes[i])) {
1803 0 : return true;
1804 : }
1805 : }
1806 :
1807 0 : return false;
1808 : }
1809 : #endif
1810 :
1811 : #ifdef MOZ_WEBM
1812 : const char nsHTMLMediaElement::gWebMTypes[2][17] = {
1813 : "video/webm",
1814 : "audio/webm"
1815 : };
1816 :
1817 : char const *const nsHTMLMediaElement::gWebMCodecs[4] = {
1818 : "vp8",
1819 : "vp8.0",
1820 : "vorbis",
1821 : nsnull
1822 : };
1823 :
1824 : bool
1825 0 : nsHTMLMediaElement::IsWebMEnabled()
1826 : {
1827 0 : return Preferences::GetBool("media.webm.enabled");
1828 : }
1829 :
1830 : bool
1831 0 : nsHTMLMediaElement::IsWebMType(const nsACString& aType)
1832 : {
1833 0 : if (!IsWebMEnabled()) {
1834 0 : return false;
1835 : }
1836 :
1837 0 : for (PRUint32 i = 0; i < ArrayLength(gWebMTypes); ++i) {
1838 0 : if (aType.EqualsASCII(gWebMTypes[i])) {
1839 0 : return true;
1840 : }
1841 : }
1842 :
1843 0 : return false;
1844 : }
1845 : #endif
1846 :
1847 : /* static */
1848 : nsHTMLMediaElement::CanPlayStatus
1849 0 : nsHTMLMediaElement::CanHandleMediaType(const char* aMIMEType,
1850 : char const *const ** aCodecList)
1851 : {
1852 : #ifdef MOZ_RAW
1853 : if (IsRawType(nsDependentCString(aMIMEType))) {
1854 : *aCodecList = gRawCodecs;
1855 : return CANPLAY_MAYBE;
1856 : }
1857 : #endif
1858 : #ifdef MOZ_OGG
1859 0 : if (IsOggType(nsDependentCString(aMIMEType))) {
1860 0 : *aCodecList = gOggCodecs;
1861 0 : return CANPLAY_MAYBE;
1862 : }
1863 : #endif
1864 : #ifdef MOZ_WAVE
1865 0 : if (IsWaveType(nsDependentCString(aMIMEType))) {
1866 0 : *aCodecList = gWaveCodecs;
1867 0 : return CANPLAY_MAYBE;
1868 : }
1869 : #endif
1870 : #ifdef MOZ_WEBM
1871 0 : if (IsWebMType(nsDependentCString(aMIMEType))) {
1872 0 : *aCodecList = gWebMCodecs;
1873 0 : return CANPLAY_YES;
1874 : }
1875 : #endif
1876 0 : return CANPLAY_NO;
1877 : }
1878 :
1879 : /* static */
1880 0 : bool nsHTMLMediaElement::ShouldHandleMediaType(const char* aMIMEType)
1881 : {
1882 : #ifdef MOZ_RAW
1883 : if (IsRawType(nsDependentCString(aMIMEType)))
1884 : return true;
1885 : #endif
1886 : #ifdef MOZ_OGG
1887 0 : if (IsOggType(nsDependentCString(aMIMEType)))
1888 0 : return true;
1889 : #endif
1890 : #ifdef MOZ_WEBM
1891 0 : if (IsWebMType(nsDependentCString(aMIMEType)))
1892 0 : return true;
1893 : #endif
1894 : // We should not return true for Wave types, since there are some
1895 : // Wave codecs actually in use in the wild that we don't support, and
1896 : // we should allow those to be handled by plugins or helper apps.
1897 : // Furthermore people can play Wave files on most platforms by other
1898 : // means.
1899 0 : return false;
1900 : }
1901 :
1902 : static bool
1903 0 : CodecListContains(char const *const * aCodecs, const nsAString& aCodec)
1904 : {
1905 0 : for (PRInt32 i = 0; aCodecs[i]; ++i) {
1906 0 : if (aCodec.EqualsASCII(aCodecs[i]))
1907 0 : return true;
1908 : }
1909 0 : return false;
1910 : }
1911 :
1912 : /* static */
1913 : nsHTMLMediaElement::CanPlayStatus
1914 0 : nsHTMLMediaElement::GetCanPlay(const nsAString& aType)
1915 : {
1916 0 : nsContentTypeParser parser(aType);
1917 0 : nsAutoString mimeType;
1918 0 : nsresult rv = parser.GetType(mimeType);
1919 0 : if (NS_FAILED(rv))
1920 0 : return CANPLAY_NO;
1921 :
1922 0 : NS_ConvertUTF16toUTF8 mimeTypeUTF8(mimeType);
1923 : char const *const * supportedCodecs;
1924 : CanPlayStatus status = CanHandleMediaType(mimeTypeUTF8.get(),
1925 0 : &supportedCodecs);
1926 0 : if (status == CANPLAY_NO)
1927 0 : return CANPLAY_NO;
1928 :
1929 0 : nsAutoString codecs;
1930 0 : rv = parser.GetParameter("codecs", codecs);
1931 0 : if (NS_FAILED(rv)) {
1932 : // Parameter not found or whatever
1933 0 : return status;
1934 : }
1935 :
1936 0 : CanPlayStatus result = CANPLAY_YES;
1937 : // See http://www.rfc-editor.org/rfc/rfc4281.txt for the description
1938 : // of the 'codecs' parameter
1939 0 : nsCharSeparatedTokenizer tokenizer(codecs, ',');
1940 0 : bool expectMoreTokens = false;
1941 0 : while (tokenizer.hasMoreTokens()) {
1942 0 : const nsSubstring& token = tokenizer.nextToken();
1943 :
1944 0 : if (!CodecListContains(supportedCodecs, token)) {
1945 : // Totally unsupported codec
1946 0 : return CANPLAY_NO;
1947 : }
1948 0 : expectMoreTokens = tokenizer.lastTokenEndedWithSeparator();
1949 : }
1950 0 : if (expectMoreTokens) {
1951 : // Last codec name was empty
1952 0 : return CANPLAY_NO;
1953 : }
1954 0 : return result;
1955 : }
1956 :
1957 : NS_IMETHODIMP
1958 0 : nsHTMLMediaElement::CanPlayType(const nsAString& aType, nsAString& aResult)
1959 : {
1960 0 : switch (GetCanPlay(aType)) {
1961 : case CANPLAY_NO:
1962 0 : aResult.Truncate();
1963 0 : break;
1964 : case CANPLAY_YES:
1965 0 : aResult.AssignLiteral("probably");
1966 0 : break;
1967 : default:
1968 : case CANPLAY_MAYBE:
1969 0 : aResult.AssignLiteral("maybe");
1970 0 : break;
1971 : }
1972 0 : return NS_OK;
1973 : }
1974 :
1975 : already_AddRefed<nsMediaDecoder>
1976 0 : nsHTMLMediaElement::CreateDecoder(const nsACString& aType)
1977 : {
1978 : #ifdef MOZ_RAW
1979 : if (IsRawType(aType)) {
1980 : nsRefPtr<nsRawDecoder> decoder = new nsRawDecoder();
1981 : if (decoder->Init(this)) {
1982 : return decoder.forget();
1983 : }
1984 : }
1985 : #endif
1986 : #ifdef MOZ_OGG
1987 0 : if (IsOggType(aType)) {
1988 0 : nsRefPtr<nsOggDecoder> decoder = new nsOggDecoder();
1989 0 : if (decoder->Init(this)) {
1990 0 : return decoder.forget();
1991 : }
1992 : }
1993 : #endif
1994 : #ifdef MOZ_WAVE
1995 0 : if (IsWaveType(aType)) {
1996 0 : nsRefPtr<nsWaveDecoder> decoder = new nsWaveDecoder();
1997 0 : if (decoder->Init(this)) {
1998 0 : return decoder.forget();
1999 : }
2000 : }
2001 : #endif
2002 : #ifdef MOZ_WEBM
2003 0 : if (IsWebMType(aType)) {
2004 0 : nsRefPtr<nsWebMDecoder> decoder = new nsWebMDecoder();
2005 0 : if (decoder->Init(this)) {
2006 0 : return decoder.forget();
2007 : }
2008 : }
2009 : #endif
2010 0 : return nsnull;
2011 : }
2012 :
2013 0 : nsresult nsHTMLMediaElement::InitializeDecoderAsClone(nsMediaDecoder* aOriginal)
2014 : {
2015 0 : NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set");
2016 0 : NS_ASSERTION(mDecoder == nsnull, "Shouldn't have a decoder");
2017 :
2018 0 : MediaResource* originalResource = aOriginal->GetResource();
2019 0 : if (!originalResource)
2020 0 : return NS_ERROR_FAILURE;
2021 0 : nsRefPtr<nsMediaDecoder> decoder = aOriginal->Clone();
2022 0 : if (!decoder)
2023 0 : return NS_ERROR_FAILURE;
2024 :
2025 0 : LOG(PR_LOG_DEBUG, ("%p Cloned decoder %p from %p", this, decoder.get(), aOriginal));
2026 :
2027 0 : if (!decoder->Init(this)) {
2028 0 : LOG(PR_LOG_DEBUG, ("%p Failed to init cloned decoder %p", this, decoder.get()));
2029 0 : return NS_ERROR_FAILURE;
2030 : }
2031 :
2032 0 : double duration = aOriginal->GetDuration();
2033 0 : if (duration >= 0) {
2034 0 : decoder->SetDuration(duration);
2035 0 : decoder->SetSeekable(aOriginal->IsSeekable());
2036 : }
2037 :
2038 0 : MediaResource* resource = originalResource->CloneData(decoder);
2039 0 : if (!resource) {
2040 0 : LOG(PR_LOG_DEBUG, ("%p Failed to cloned stream for decoder %p", this, decoder.get()));
2041 0 : return NS_ERROR_FAILURE;
2042 : }
2043 :
2044 0 : mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
2045 :
2046 0 : nsresult rv = decoder->Load(resource, nsnull, aOriginal);
2047 0 : if (NS_FAILED(rv)) {
2048 0 : LOG(PR_LOG_DEBUG, ("%p Failed to load decoder/stream for decoder %p", this, decoder.get()));
2049 0 : return rv;
2050 : }
2051 :
2052 0 : return FinishDecoderSetup(decoder);
2053 : }
2054 :
2055 0 : nsresult nsHTMLMediaElement::InitializeDecoderForChannel(nsIChannel *aChannel,
2056 : nsIStreamListener **aListener)
2057 : {
2058 0 : NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set");
2059 0 : NS_ASSERTION(mDecoder == nsnull, "Shouldn't have a decoder");
2060 :
2061 0 : nsCAutoString mimeType;
2062 0 : aChannel->GetContentType(mimeType);
2063 :
2064 0 : nsRefPtr<nsMediaDecoder> decoder = CreateDecoder(mimeType);
2065 0 : if (!decoder) {
2066 0 : nsAutoString src;
2067 0 : GetCurrentSrc(src);
2068 0 : NS_ConvertUTF8toUTF16 mimeUTF16(mimeType);
2069 0 : const PRUnichar* params[] = { mimeUTF16.get(), src.get() };
2070 0 : ReportLoadError("MediaLoadUnsupportedMimeType", params, ArrayLength(params));
2071 0 : return NS_ERROR_FAILURE;
2072 : }
2073 :
2074 0 : LOG(PR_LOG_DEBUG, ("%p Created decoder %p for type %s", this, decoder.get(), mimeType.get()));
2075 :
2076 0 : mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
2077 :
2078 0 : MediaResource* resource = MediaResource::Create(decoder, aChannel);
2079 0 : if (!resource)
2080 0 : return NS_ERROR_OUT_OF_MEMORY;
2081 :
2082 0 : nsresult rv = decoder->Load(resource, aListener, nsnull);
2083 0 : if (NS_FAILED(rv)) {
2084 0 : return rv;
2085 : }
2086 :
2087 : // Decoder successfully created, the decoder now owns the MediaResource
2088 : // which owns the channel.
2089 0 : mChannel = nsnull;
2090 :
2091 0 : return FinishDecoderSetup(decoder);
2092 : }
2093 :
2094 0 : nsresult nsHTMLMediaElement::FinishDecoderSetup(nsMediaDecoder* aDecoder)
2095 : {
2096 0 : NS_ASSERTION(mLoadingSrc, "mLoadingSrc set up");
2097 :
2098 0 : mDecoder = aDecoder;
2099 0 : AddMediaElementToURITable();
2100 :
2101 : // Force a same-origin check before allowing events for this media resource.
2102 0 : mMediaSecurityVerified = false;
2103 :
2104 : // The new resource has not been suspended by us.
2105 0 : mPausedForInactiveDocument = false;
2106 : // But we may want to suspend it now.
2107 : // This will also do an AddRemoveSelfReference.
2108 0 : NotifyOwnerDocumentActivityChanged();
2109 :
2110 0 : nsresult rv = NS_OK;
2111 :
2112 0 : mDecoder->SetVolume(mMuted ? 0.0 : mVolume);
2113 :
2114 0 : if (!mPaused) {
2115 0 : SetPlayedOrSeeked(true);
2116 0 : if (!mPausedForInactiveDocument) {
2117 0 : rv = mDecoder->Play();
2118 : }
2119 : }
2120 :
2121 0 : if (OwnerDoc()->HasAudioAvailableListeners()) {
2122 0 : NotifyAudioAvailableListener();
2123 : }
2124 :
2125 0 : if (NS_FAILED(rv)) {
2126 0 : RemoveMediaElementFromURITable();
2127 0 : mDecoder->Shutdown();
2128 0 : mDecoder = nsnull;
2129 : }
2130 :
2131 0 : NS_ASSERTION(NS_SUCCEEDED(rv) == (MediaElementTableCount(this, mLoadingSrc) == 1),
2132 : "Media element should have single table entry if decode initialized");
2133 :
2134 0 : mBegun = true;
2135 0 : return rv;
2136 : }
2137 :
2138 0 : nsresult nsHTMLMediaElement::NewURIFromString(const nsAutoString& aURISpec, nsIURI** aURI)
2139 : {
2140 0 : NS_ENSURE_ARG_POINTER(aURI);
2141 :
2142 0 : *aURI = nsnull;
2143 :
2144 0 : nsCOMPtr<nsIDocument> doc = OwnerDoc();
2145 :
2146 0 : nsCOMPtr<nsIURI> baseURI = GetBaseURI();
2147 0 : nsCOMPtr<nsIURI> uri;
2148 0 : nsresult rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri),
2149 : aURISpec,
2150 : doc,
2151 0 : baseURI);
2152 0 : NS_ENSURE_SUCCESS(rv, rv);
2153 :
2154 : bool equal;
2155 0 : if (aURISpec.IsEmpty() &&
2156 0 : doc->GetDocumentURI() &&
2157 0 : NS_SUCCEEDED(doc->GetDocumentURI()->Equals(uri, &equal)) &&
2158 : equal) {
2159 : // It's not possible for a media resource to be embedded in the current
2160 : // document we extracted aURISpec from, so there's no point returning
2161 : // the current document URI just to let the caller attempt and fail to
2162 : // decode it.
2163 0 : return NS_ERROR_DOM_INVALID_STATE_ERR;
2164 : }
2165 :
2166 0 : uri.forget(aURI);
2167 0 : return NS_OK;
2168 : }
2169 :
2170 0 : void nsHTMLMediaElement::ProcessMediaFragmentURI()
2171 : {
2172 0 : nsCAutoString ref;
2173 0 : GetCurrentSpec(ref);
2174 0 : nsMediaFragmentURIParser parser(ref);
2175 0 : parser.Parse();
2176 0 : double start = parser.GetStartTime();
2177 0 : if (mDecoder) {
2178 0 : double end = parser.GetEndTime();
2179 0 : if (end < 0.0 || end > start) {
2180 0 : mFragmentEnd = end;
2181 : }
2182 : else {
2183 0 : start = -1.0;
2184 0 : end = -1.0;
2185 : }
2186 : }
2187 0 : if (start > 0.0) {
2188 0 : SetCurrentTime(start);
2189 0 : mFragmentStart = start;
2190 : }
2191 0 : }
2192 :
2193 0 : void nsHTMLMediaElement::MetadataLoaded(PRUint32 aChannels, PRUint32 aRate)
2194 : {
2195 0 : mChannels = aChannels;
2196 0 : mRate = aRate;
2197 0 : ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
2198 0 : DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
2199 0 : DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
2200 0 : if (mDecoder && mDecoder->IsSeekable()) {
2201 0 : ProcessMediaFragmentURI();
2202 0 : mDecoder->SetEndTime(mFragmentEnd);
2203 : }
2204 0 : }
2205 :
2206 0 : void nsHTMLMediaElement::FirstFrameLoaded(bool aResourceFullyLoaded)
2207 : {
2208 : ChangeReadyState(aResourceFullyLoaded ?
2209 : nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA :
2210 0 : nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
2211 0 : ChangeDelayLoadStatus(false);
2212 :
2213 0 : NS_ASSERTION(!mSuspendedAfterFirstFrame, "Should not have already suspended");
2214 :
2215 0 : if (mDecoder && mAllowSuspendAfterFirstFrame && mPaused &&
2216 0 : !aResourceFullyLoaded &&
2217 0 : !HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) &&
2218 : mPreloadAction == nsHTMLMediaElement::PRELOAD_METADATA) {
2219 0 : mSuspendedAfterFirstFrame = true;
2220 0 : mDecoder->Suspend();
2221 : }
2222 0 : }
2223 :
2224 0 : void nsHTMLMediaElement::ResourceLoaded()
2225 : {
2226 0 : mBegun = false;
2227 0 : mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
2228 0 : AddRemoveSelfReference();
2229 0 : if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_METADATA) {
2230 0 : ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
2231 : }
2232 : // Ensure a progress event is dispatched at the end of download.
2233 0 : DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
2234 : // The download has stopped.
2235 0 : DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
2236 0 : }
2237 :
2238 0 : void nsHTMLMediaElement::NetworkError()
2239 : {
2240 0 : Error(nsIDOMMediaError::MEDIA_ERR_NETWORK);
2241 0 : }
2242 :
2243 0 : void nsHTMLMediaElement::DecodeError()
2244 : {
2245 0 : nsAutoString src;
2246 0 : GetCurrentSrc(src);
2247 0 : const PRUnichar* params[] = { src.get() };
2248 0 : ReportLoadError("MediaLoadDecodeError", params, ArrayLength(params));
2249 :
2250 0 : if (mDecoder) {
2251 0 : RemoveMediaElementFromURITable();
2252 0 : mDecoder->Shutdown();
2253 0 : mDecoder = nsnull;
2254 : }
2255 0 : mLoadingSrc = nsnull;
2256 0 : if (mIsLoadingFromSourceChildren) {
2257 0 : mError = nsnull;
2258 0 : if (mSourceLoadCandidate) {
2259 0 : DispatchAsyncSourceError(mSourceLoadCandidate);
2260 0 : QueueLoadFromSourceTask();
2261 : } else {
2262 0 : NS_WARNING("Should know the source we were loading from!");
2263 : }
2264 : } else {
2265 0 : Error(nsIDOMMediaError::MEDIA_ERR_DECODE);
2266 : }
2267 0 : }
2268 :
2269 0 : void nsHTMLMediaElement::LoadAborted()
2270 : {
2271 0 : Error(nsIDOMMediaError::MEDIA_ERR_ABORTED);
2272 0 : }
2273 :
2274 0 : void nsHTMLMediaElement::Error(PRUint16 aErrorCode)
2275 : {
2276 0 : NS_ASSERTION(aErrorCode == nsIDOMMediaError::MEDIA_ERR_DECODE ||
2277 : aErrorCode == nsIDOMMediaError::MEDIA_ERR_NETWORK ||
2278 : aErrorCode == nsIDOMMediaError::MEDIA_ERR_ABORTED,
2279 : "Only use nsIDOMMediaError codes!");
2280 0 : mError = new nsMediaError(aErrorCode);
2281 0 : mBegun = false;
2282 0 : DispatchAsyncEvent(NS_LITERAL_STRING("error"));
2283 0 : if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
2284 0 : mNetworkState = nsIDOMHTMLMediaElement::NETWORK_EMPTY;
2285 0 : DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
2286 : } else {
2287 0 : mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
2288 : }
2289 0 : AddRemoveSelfReference();
2290 0 : ChangeDelayLoadStatus(false);
2291 0 : }
2292 :
2293 0 : void nsHTMLMediaElement::PlaybackEnded()
2294 : {
2295 0 : NS_ASSERTION(mDecoder->IsEnded(), "Decoder fired ended, but not in ended state");
2296 : // We changed the state of IsPlaybackEnded which can affect AddRemoveSelfReference
2297 0 : AddRemoveSelfReference();
2298 :
2299 0 : if (mDecoder && mDecoder->IsInfinite()) {
2300 0 : LOG(PR_LOG_DEBUG, ("%p, got duration by reaching the end of the resource", this));
2301 0 : DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
2302 : }
2303 :
2304 0 : if (HasAttr(kNameSpaceID_None, nsGkAtoms::loop)) {
2305 0 : SetCurrentTime(0);
2306 0 : return;
2307 : }
2308 :
2309 0 : FireTimeUpdate(false);
2310 0 : DispatchAsyncEvent(NS_LITERAL_STRING("ended"));
2311 : }
2312 :
2313 0 : void nsHTMLMediaElement::SeekStarted()
2314 : {
2315 0 : DispatchAsyncEvent(NS_LITERAL_STRING("seeking"));
2316 0 : FireTimeUpdate(false);
2317 0 : }
2318 :
2319 0 : void nsHTMLMediaElement::SeekCompleted()
2320 : {
2321 0 : mPlayingBeforeSeek = false;
2322 0 : SetPlayedOrSeeked(true);
2323 0 : DispatchAsyncEvent(NS_LITERAL_STRING("seeked"));
2324 : // We changed whether we're seeking so we need to AddRemoveSelfReference
2325 0 : AddRemoveSelfReference();
2326 0 : }
2327 :
2328 0 : void nsHTMLMediaElement::DownloadSuspended()
2329 : {
2330 0 : DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
2331 0 : if (mBegun) {
2332 0 : mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
2333 0 : AddRemoveSelfReference();
2334 0 : DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
2335 : }
2336 0 : }
2337 :
2338 0 : void nsHTMLMediaElement::DownloadResumed()
2339 : {
2340 0 : if (mBegun) {
2341 0 : mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
2342 0 : AddRemoveSelfReference();
2343 : }
2344 0 : }
2345 :
2346 0 : void nsHTMLMediaElement::DownloadStalled()
2347 : {
2348 0 : if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING) {
2349 0 : DispatchAsyncEvent(NS_LITERAL_STRING("stalled"));
2350 : }
2351 0 : }
2352 :
2353 0 : bool nsHTMLMediaElement::ShouldCheckAllowOrigin()
2354 : {
2355 0 : return mCORSMode != CORS_NONE;
2356 : }
2357 :
2358 0 : void nsHTMLMediaElement::UpdateReadyStateForData(NextFrameStatus aNextFrame)
2359 : {
2360 0 : if (mReadyState < nsIDOMHTMLMediaElement::HAVE_METADATA) {
2361 : // aNextFrame might have a next frame because the decoder can advance
2362 : // on its own thread before ResourceLoaded or MetadataLoaded gets
2363 : // a chance to run.
2364 : // The arrival of more data can't change us out of this readyState.
2365 0 : return;
2366 : }
2367 :
2368 0 : if (aNextFrame != NEXT_FRAME_AVAILABLE) {
2369 0 : ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
2370 0 : if (!mWaitingFired && aNextFrame == NEXT_FRAME_UNAVAILABLE_BUFFERING) {
2371 0 : FireTimeUpdate(false);
2372 0 : DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
2373 0 : mWaitingFired = true;
2374 : }
2375 0 : return;
2376 : }
2377 :
2378 : // Now see if we should set HAVE_ENOUGH_DATA.
2379 : // If it's something we don't know the size of, then we can't
2380 : // make a real estimate, so we go straight to HAVE_ENOUGH_DATA once
2381 : // we've downloaded enough data that our download rate is considered
2382 : // reliable. We have to move to HAVE_ENOUGH_DATA at some point or
2383 : // autoplay elements for live streams will never play. Otherwise we
2384 : // move to HAVE_ENOUGH_DATA if we can play through the entire media
2385 : // without stopping to buffer.
2386 0 : nsMediaDecoder::Statistics stats = mDecoder->GetStatistics();
2387 0 : if (stats.mTotalBytes < 0 ? stats.mDownloadRateReliable :
2388 : stats.mTotalBytes == stats.mDownloadPosition ||
2389 0 : mDecoder->CanPlayThrough())
2390 : {
2391 0 : ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
2392 0 : return;
2393 : }
2394 0 : ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA);
2395 : }
2396 :
2397 : #ifdef PR_LOGGING
2398 : static const char* gReadyStateToString[] = {
2399 : "HAVE_NOTHING",
2400 : "HAVE_METADATA",
2401 : "HAVE_CURRENT_DATA",
2402 : "HAVE_FUTURE_DATA",
2403 : "HAVE_ENOUGH_DATA"
2404 : };
2405 : #endif
2406 :
2407 0 : void nsHTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
2408 : {
2409 0 : nsMediaReadyState oldState = mReadyState;
2410 0 : mReadyState = aState;
2411 :
2412 0 : if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY ||
2413 : oldState == mReadyState) {
2414 0 : return;
2415 : }
2416 :
2417 0 : LOG(PR_LOG_DEBUG, ("%p Ready state changed to %s", this, gReadyStateToString[aState]));
2418 :
2419 : // Handle raising of "waiting" event during seek (see 4.8.10.9)
2420 0 : if (mPlayingBeforeSeek &&
2421 : oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
2422 0 : DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
2423 : }
2424 :
2425 0 : if (oldState < nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
2426 : mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
2427 0 : !mLoadedFirstFrame)
2428 : {
2429 0 : DispatchAsyncEvent(NS_LITERAL_STRING("loadeddata"));
2430 0 : mLoadedFirstFrame = true;
2431 : }
2432 :
2433 0 : if (mReadyState == nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA) {
2434 0 : mWaitingFired = false;
2435 : }
2436 :
2437 0 : if (oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
2438 : mReadyState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
2439 0 : DispatchAsyncEvent(NS_LITERAL_STRING("canplay"));
2440 : }
2441 :
2442 0 : if (mReadyState == nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) {
2443 0 : NotifyAutoplayDataReady();
2444 : }
2445 :
2446 0 : if (oldState < nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
2447 : mReadyState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA &&
2448 0 : IsPotentiallyPlaying()) {
2449 0 : DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
2450 : }
2451 :
2452 0 : if (oldState < nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA &&
2453 : mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) {
2454 0 : DispatchAsyncEvent(NS_LITERAL_STRING("canplaythrough"));
2455 : }
2456 : }
2457 :
2458 0 : bool nsHTMLMediaElement::CanActivateAutoplay()
2459 : {
2460 : return mAutoplaying &&
2461 : mPaused &&
2462 0 : HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) &&
2463 : mAutoplayEnabled &&
2464 0 : !IsEditable();
2465 : }
2466 :
2467 0 : void nsHTMLMediaElement::NotifyAutoplayDataReady()
2468 : {
2469 0 : if (CanActivateAutoplay()) {
2470 0 : mPaused = false;
2471 : // We changed mPaused which can affect AddRemoveSelfReference
2472 0 : AddRemoveSelfReference();
2473 :
2474 0 : if (mDecoder) {
2475 0 : SetPlayedOrSeeked(true);
2476 0 : mDecoder->Play();
2477 : }
2478 0 : DispatchAsyncEvent(NS_LITERAL_STRING("play"));
2479 : }
2480 0 : }
2481 :
2482 0 : VideoFrameContainer* nsHTMLMediaElement::GetVideoFrameContainer()
2483 : {
2484 0 : if (mVideoFrameContainer)
2485 0 : return mVideoFrameContainer;
2486 :
2487 : // If we have a print surface, this is just a static image so
2488 : // no image container is required
2489 0 : if (mPrintSurface)
2490 0 : return nsnull;
2491 :
2492 : // Only video frames need an image container.
2493 0 : nsCOMPtr<nsIDOMHTMLVideoElement> video = do_QueryObject(this);
2494 0 : if (!video)
2495 0 : return nsnull;
2496 :
2497 : mVideoFrameContainer =
2498 0 : new VideoFrameContainer(this, LayerManager::CreateImageContainer());
2499 0 : return mVideoFrameContainer;
2500 : }
2501 :
2502 0 : nsresult nsHTMLMediaElement::DispatchAudioAvailableEvent(float* aFrameBuffer,
2503 : PRUint32 aFrameBufferLength,
2504 : float aTime)
2505 : {
2506 : // Auto manage the memory for the frame buffer. If we fail and return
2507 : // an error, this ensures we free the memory in the frame buffer. Otherwise
2508 : // we hand off ownership of the frame buffer to the audioavailable event,
2509 : // which frees the memory when it's destroyed.
2510 0 : nsAutoArrayPtr<float> frameBuffer(aFrameBuffer);
2511 :
2512 0 : nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(OwnerDoc());
2513 0 : nsCOMPtr<nsIDOMEventTarget> target(do_QueryObject(this));
2514 0 : NS_ENSURE_TRUE(domDoc && target, NS_ERROR_INVALID_ARG);
2515 :
2516 0 : nsCOMPtr<nsIDOMEvent> event;
2517 0 : nsresult rv = domDoc->CreateEvent(NS_LITERAL_STRING("MozAudioAvailableEvent"),
2518 0 : getter_AddRefs(event));
2519 0 : nsCOMPtr<nsIDOMNotifyAudioAvailableEvent> audioavailableEvent(do_QueryInterface(event));
2520 0 : NS_ENSURE_SUCCESS(rv, rv);
2521 :
2522 0 : rv = audioavailableEvent->InitAudioAvailableEvent(NS_LITERAL_STRING("MozAudioAvailable"),
2523 : true, true, frameBuffer.forget(), aFrameBufferLength,
2524 0 : aTime, mAllowAudioData);
2525 0 : NS_ENSURE_SUCCESS(rv, rv);
2526 :
2527 : bool dummy;
2528 0 : return target->DispatchEvent(event, &dummy);
2529 : }
2530 :
2531 0 : nsresult nsHTMLMediaElement::DispatchEvent(const nsAString& aName)
2532 : {
2533 0 : LOG_EVENT(PR_LOG_DEBUG, ("%p Dispatching event %s", this,
2534 : NS_ConvertUTF16toUTF8(aName).get()));
2535 :
2536 : // Save events that occur while in the bfcache. These will be dispatched
2537 : // if the page comes out of the bfcache.
2538 0 : if (mPausedForInactiveDocument) {
2539 0 : mPendingEvents.AppendElement(aName);
2540 0 : return NS_OK;
2541 : }
2542 :
2543 : return nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
2544 : static_cast<nsIContent*>(this),
2545 : aName,
2546 : false,
2547 0 : true);
2548 : }
2549 :
2550 0 : nsresult nsHTMLMediaElement::DispatchAsyncEvent(const nsAString& aName)
2551 : {
2552 0 : LOG_EVENT(PR_LOG_DEBUG, ("%p Queuing event %s", this,
2553 : NS_ConvertUTF16toUTF8(aName).get()));
2554 :
2555 0 : nsCOMPtr<nsIRunnable> event = new nsAsyncEventRunner(aName, this);
2556 0 : NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
2557 0 : return NS_OK;
2558 : }
2559 :
2560 0 : nsresult nsHTMLMediaElement::DispatchPendingMediaEvents()
2561 : {
2562 0 : NS_ASSERTION(!mPausedForInactiveDocument,
2563 : "Must not be in bfcache when dispatching pending media events");
2564 :
2565 0 : PRUint32 count = mPendingEvents.Length();
2566 0 : for (PRUint32 i = 0; i < count; ++i) {
2567 0 : DispatchAsyncEvent(mPendingEvents[i]);
2568 : }
2569 0 : mPendingEvents.Clear();
2570 :
2571 0 : return NS_OK;
2572 : }
2573 :
2574 0 : bool nsHTMLMediaElement::IsPotentiallyPlaying() const
2575 : {
2576 : // TODO:
2577 : // playback has not stopped due to errors,
2578 : // and the element has not paused for user interaction
2579 : return
2580 0 : !mPaused &&
2581 : (mReadyState == nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA ||
2582 : mReadyState == nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) &&
2583 0 : !IsPlaybackEnded();
2584 : }
2585 :
2586 0 : bool nsHTMLMediaElement::IsPlaybackEnded() const
2587 : {
2588 : // TODO:
2589 : // the current playback position is equal to the effective end of the media resource.
2590 : // See bug 449157.
2591 : return mNetworkState >= nsIDOMHTMLMediaElement::HAVE_METADATA &&
2592 0 : mDecoder ? mDecoder->IsEnded() : false;
2593 : }
2594 :
2595 0 : already_AddRefed<nsIPrincipal> nsHTMLMediaElement::GetCurrentPrincipal()
2596 : {
2597 0 : if (!mDecoder)
2598 0 : return nsnull;
2599 :
2600 0 : return mDecoder->GetCurrentPrincipal();
2601 : }
2602 :
2603 0 : void nsHTMLMediaElement::UpdateMediaSize(nsIntSize size)
2604 : {
2605 0 : mMediaSize = size;
2606 0 : }
2607 :
2608 0 : void nsHTMLMediaElement::NotifyOwnerDocumentActivityChanged()
2609 : {
2610 0 : nsIDocument* ownerDoc = OwnerDoc();
2611 : bool pauseForInactiveDocument =
2612 0 : !ownerDoc->IsActive() || !ownerDoc->IsVisible();
2613 :
2614 0 : if (pauseForInactiveDocument != mPausedForInactiveDocument) {
2615 0 : mPausedForInactiveDocument = pauseForInactiveDocument;
2616 0 : if (mDecoder) {
2617 0 : if (pauseForInactiveDocument) {
2618 0 : mDecoder->Pause();
2619 0 : mDecoder->Suspend();
2620 : } else {
2621 0 : mDecoder->Resume(false);
2622 0 : DispatchPendingMediaEvents();
2623 0 : if (!mPaused && !mDecoder->IsEnded()) {
2624 0 : mDecoder->Play();
2625 : }
2626 : }
2627 : }
2628 : }
2629 :
2630 0 : AddRemoveSelfReference();
2631 0 : }
2632 :
2633 0 : void nsHTMLMediaElement::AddRemoveSelfReference()
2634 : {
2635 : // XXX we could release earlier here in many situations if we examined
2636 : // which event listeners are attached. Right now we assume there is a
2637 : // potential listener for every event. We would also have to keep the
2638 : // element alive if it was playing and producing audio output --- right now
2639 : // that's covered by the !mPaused check.
2640 0 : nsIDocument* ownerDoc = OwnerDoc();
2641 :
2642 : // See the comment at the top of this file for the explanation of this
2643 : // boolean expression.
2644 0 : bool needSelfReference = !mShuttingDown &&
2645 0 : ownerDoc->IsActive() &&
2646 : (mDelayingLoadEvent ||
2647 0 : (!mPaused && mDecoder && !mDecoder->IsEnded()) ||
2648 0 : (mDecoder && mDecoder->IsSeeking()) ||
2649 0 : CanActivateAutoplay() ||
2650 0 : mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING);
2651 :
2652 0 : if (needSelfReference != mHasSelfReference) {
2653 0 : mHasSelfReference = needSelfReference;
2654 0 : if (needSelfReference) {
2655 : // The observer service will hold a strong reference to us. This
2656 : // will do to keep us alive. We need to know about shutdown so that
2657 : // we can release our self-reference.
2658 0 : nsContentUtils::RegisterShutdownObserver(this);
2659 : } else {
2660 : // Dispatch Release asynchronously so that we don't destroy this object
2661 : // inside a call stack of method calls on this object
2662 : nsCOMPtr<nsIRunnable> event =
2663 0 : NS_NewRunnableMethod(this, &nsHTMLMediaElement::DoRemoveSelfReference);
2664 0 : NS_DispatchToMainThread(event);
2665 : }
2666 : }
2667 0 : }
2668 :
2669 0 : void nsHTMLMediaElement::DoRemoveSelfReference()
2670 : {
2671 : // We don't need the shutdown observer anymore. Unregistering releases
2672 : // its reference to us, which we were using as our self-reference.
2673 0 : nsContentUtils::UnregisterShutdownObserver(this);
2674 0 : }
2675 :
2676 0 : nsresult nsHTMLMediaElement::Observe(nsISupports* aSubject,
2677 : const char* aTopic, const PRUnichar* aData)
2678 : {
2679 0 : NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
2680 :
2681 0 : if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
2682 0 : mShuttingDown = true;
2683 0 : AddRemoveSelfReference();
2684 : }
2685 0 : return NS_OK;
2686 : }
2687 :
2688 : bool
2689 0 : nsHTMLMediaElement::IsNodeOfType(PRUint32 aFlags) const
2690 : {
2691 0 : return !(aFlags & ~(eCONTENT | eMEDIA));
2692 : }
2693 :
2694 0 : void nsHTMLMediaElement::DispatchAsyncSourceError(nsIContent* aSourceElement)
2695 : {
2696 0 : LOG_EVENT(PR_LOG_DEBUG, ("%p Queuing simple source error event", this));
2697 :
2698 0 : nsCOMPtr<nsIRunnable> event = new nsSourceErrorEventRunner(this, aSourceElement);
2699 0 : NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
2700 0 : }
2701 :
2702 0 : void nsHTMLMediaElement::NotifyAddedSource()
2703 : {
2704 : // If a source element is inserted as a child of a media element
2705 : // that has no src attribute and whose networkState has the value
2706 : // NETWORK_EMPTY, the user agent must invoke the media element's
2707 : // resource selection algorithm.
2708 0 : if (!HasAttr(kNameSpaceID_None, nsGkAtoms::src) &&
2709 : mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY)
2710 : {
2711 0 : QueueSelectResourceTask();
2712 : }
2713 :
2714 : // A load was paused in the resource selection algorithm, waiting for
2715 : // a new source child to be added, resume the resource selction algorithm.
2716 0 : if (mLoadWaitStatus == WAITING_FOR_SOURCE) {
2717 0 : QueueLoadFromSourceTask();
2718 : }
2719 0 : }
2720 :
2721 0 : nsIContent* nsHTMLMediaElement::GetNextSource()
2722 : {
2723 0 : nsCOMPtr<nsIDOMNode> thisDomNode = do_QueryObject(this);
2724 :
2725 0 : mSourceLoadCandidate = nsnull;
2726 :
2727 0 : nsresult rv = NS_OK;
2728 0 : if (!mSourcePointer) {
2729 : // First time this has been run, create a selection to cover children.
2730 0 : mSourcePointer = new nsRange();
2731 :
2732 0 : rv = mSourcePointer->SelectNodeContents(thisDomNode);
2733 0 : if (NS_FAILED(rv)) return nsnull;
2734 :
2735 0 : rv = mSourcePointer->Collapse(true);
2736 0 : if (NS_FAILED(rv)) return nsnull;
2737 : }
2738 :
2739 0 : while (true) {
2740 : #ifdef DEBUG
2741 0 : nsCOMPtr<nsIDOMNode> startContainer;
2742 0 : rv = mSourcePointer->GetStartContainer(getter_AddRefs(startContainer));
2743 0 : if (NS_FAILED(rv)) return nsnull;
2744 0 : NS_ASSERTION(startContainer == thisDomNode,
2745 : "Should only iterate over direct children");
2746 : #endif
2747 :
2748 0 : PRInt32 startOffset = 0;
2749 0 : rv = mSourcePointer->GetStartOffset(&startOffset);
2750 0 : NS_ENSURE_SUCCESS(rv, nsnull);
2751 :
2752 0 : if (PRUint32(startOffset) == GetChildCount())
2753 0 : return nsnull; // No more children.
2754 :
2755 : // Advance the range to the next child.
2756 0 : rv = mSourcePointer->SetStart(thisDomNode, startOffset + 1);
2757 0 : NS_ENSURE_SUCCESS(rv, nsnull);
2758 :
2759 0 : nsIContent* child = GetChildAt(startOffset);
2760 :
2761 : // If child is a <source> element, it is the next candidate.
2762 0 : if (child && child->IsHTML(nsGkAtoms::source)) {
2763 0 : mSourceLoadCandidate = child;
2764 0 : return child;
2765 : }
2766 : }
2767 : NS_NOTREACHED("Execution should not reach here!");
2768 : return nsnull;
2769 : }
2770 :
2771 0 : void nsHTMLMediaElement::ChangeDelayLoadStatus(bool aDelay)
2772 : {
2773 0 : if (mDelayingLoadEvent == aDelay)
2774 0 : return;
2775 :
2776 0 : mDelayingLoadEvent = aDelay;
2777 :
2778 0 : if (aDelay) {
2779 0 : mLoadBlockedDoc = OwnerDoc();
2780 0 : mLoadBlockedDoc->BlockOnload();
2781 0 : LOG(PR_LOG_DEBUG, ("%p ChangeDelayLoadStatus(%d) doc=0x%p", this, aDelay, mLoadBlockedDoc.get()));
2782 : } else {
2783 0 : if (mDecoder) {
2784 0 : mDecoder->MoveLoadsToBackground();
2785 : }
2786 0 : LOG(PR_LOG_DEBUG, ("%p ChangeDelayLoadStatus(%d) doc=0x%p", this, aDelay, mLoadBlockedDoc.get()));
2787 : // mLoadBlockedDoc might be null due to GC unlinking
2788 0 : if (mLoadBlockedDoc) {
2789 0 : mLoadBlockedDoc->UnblockOnload(false);
2790 0 : mLoadBlockedDoc = nsnull;
2791 : }
2792 : }
2793 :
2794 : // We changed mDelayingLoadEvent which can affect AddRemoveSelfReference
2795 0 : AddRemoveSelfReference();
2796 : }
2797 :
2798 0 : already_AddRefed<nsILoadGroup> nsHTMLMediaElement::GetDocumentLoadGroup()
2799 : {
2800 0 : if (!OwnerDoc()->IsActive()) {
2801 0 : NS_WARNING("Load group requested for media element in inactive document.");
2802 : }
2803 0 : return OwnerDoc()->GetDocumentLoadGroup();
2804 : }
2805 :
2806 : nsresult
2807 0 : nsHTMLMediaElement::CopyInnerTo(nsGenericElement* aDest) const
2808 : {
2809 0 : nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
2810 0 : NS_ENSURE_SUCCESS(rv, rv);
2811 0 : if (aDest->OwnerDoc()->IsStaticDocument()) {
2812 0 : nsHTMLMediaElement* dest = static_cast<nsHTMLMediaElement*>(aDest);
2813 0 : if (mPrintSurface) {
2814 0 : dest->mPrintSurface = mPrintSurface;
2815 0 : dest->mMediaSize = mMediaSize;
2816 : } else {
2817 0 : nsIFrame* frame = GetPrimaryFrame();
2818 : Element* element;
2819 0 : if (frame && frame->GetType() == nsGkAtoms::HTMLVideoFrame &&
2820 0 : static_cast<nsVideoFrame*>(frame)->ShouldDisplayPoster()) {
2821 0 : nsIContent* content = static_cast<nsVideoFrame*>(frame)->GetPosterImage();
2822 0 : element = content ? content->AsElement() : NULL;
2823 : } else {
2824 0 : element = const_cast<nsHTMLMediaElement*>(this);
2825 : }
2826 :
2827 : nsLayoutUtils::SurfaceFromElementResult res =
2828 : nsLayoutUtils::SurfaceFromElement(element,
2829 0 : nsLayoutUtils::SFE_WANT_NEW_SURFACE);
2830 0 : dest->mPrintSurface = res.mSurface;
2831 0 : dest->mMediaSize = nsIntSize(res.mSize.width, res.mSize.height);
2832 : }
2833 : }
2834 0 : return rv;
2835 : }
2836 :
2837 0 : nsresult nsHTMLMediaElement::GetBuffered(nsIDOMTimeRanges** aBuffered)
2838 : {
2839 0 : nsRefPtr<nsTimeRanges> ranges = new nsTimeRanges();
2840 0 : if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA && mDecoder) {
2841 : // If GetBuffered fails we ignore the error result and just return the
2842 : // time ranges we found up till the error.
2843 0 : mDecoder->GetBuffered(ranges);
2844 : }
2845 0 : ranges.forget(aBuffered);
2846 0 : return NS_OK;
2847 : }
2848 :
2849 0 : void nsHTMLMediaElement::SetRequestHeaders(nsIHttpChannel* aChannel)
2850 : {
2851 : // Send Accept header for video and audio types only (Bug 489071)
2852 0 : SetAcceptHeader(aChannel);
2853 :
2854 : // Apache doesn't send Content-Length when gzip transfer encoding is used,
2855 : // which prevents us from estimating the video length (if explicit Content-Duration
2856 : // and a length spec in the container are not present either) and from seeking.
2857 : // So, disable the standard "Accept-Encoding: gzip,deflate" that we usually send.
2858 : // See bug 614760.
2859 0 : aChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept-Encoding"),
2860 0 : EmptyCString(), false);
2861 :
2862 : // Set the Referer header
2863 0 : aChannel->SetReferrer(OwnerDoc()->GetDocumentURI());
2864 0 : }
2865 :
2866 0 : void nsHTMLMediaElement::FireTimeUpdate(bool aPeriodic)
2867 : {
2868 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
2869 :
2870 0 : TimeStamp now = TimeStamp::Now();
2871 0 : double time = 0;
2872 0 : GetCurrentTime(&time);
2873 :
2874 : // Fire a timupdate event if this is not a periodic update (i.e. it's a
2875 : // timeupdate event mandated by the spec), or if it's a periodic update
2876 : // and TIMEUPDATE_MS has passed since the last timeupdate event fired and
2877 : // the time has changed.
2878 0 : if (!aPeriodic ||
2879 : (mLastCurrentTime != time &&
2880 0 : (mTimeUpdateTime.IsNull() ||
2881 0 : now - mTimeUpdateTime >= TimeDuration::FromMilliseconds(TIMEUPDATE_MS)))) {
2882 0 : DispatchAsyncEvent(NS_LITERAL_STRING("timeupdate"));
2883 0 : mTimeUpdateTime = now;
2884 0 : mLastCurrentTime = time;
2885 : }
2886 0 : if (mFragmentEnd >= 0.0 && time >= mFragmentEnd) {
2887 0 : Pause();
2888 0 : mFragmentEnd = -1.0;
2889 0 : mFragmentStart = -1.0;
2890 0 : mDecoder->SetEndTime(mFragmentEnd);
2891 : }
2892 0 : }
2893 :
2894 0 : void nsHTMLMediaElement::GetCurrentSpec(nsCString& aString)
2895 : {
2896 0 : if (mLoadingSrc) {
2897 0 : mLoadingSrc->GetSpec(aString);
2898 : } else {
2899 0 : aString.Truncate();
2900 : }
2901 0 : }
2902 :
2903 : /* attribute double initialTime; */
2904 0 : NS_IMETHODIMP nsHTMLMediaElement::GetInitialTime(double *aTime)
2905 : {
2906 : // If there is no start fragment then the initalTime is zero.
2907 : // Clamp to duration if it is greater than duration.
2908 0 : double duration = 0.0;
2909 0 : nsresult rv = GetDuration(&duration);
2910 0 : NS_ENSURE_SUCCESS(rv, rv);
2911 :
2912 0 : *aTime = mFragmentStart < 0.0 ? 0.0 : mFragmentStart;
2913 0 : if (*aTime > duration) {
2914 0 : *aTime = duration;
2915 : }
2916 0 : return NS_OK;
2917 : }
2918 :
2919 : /* attribute double mozFragmentEnd; */
2920 0 : NS_IMETHODIMP nsHTMLMediaElement::GetMozFragmentEnd(double *aTime)
2921 : {
2922 0 : double duration = 0.0;
2923 0 : nsresult rv = GetDuration(&duration);
2924 0 : NS_ENSURE_SUCCESS(rv, rv);
2925 :
2926 : // If there is no end fragment, or the fragment end is greater than the
2927 : // duration, return the duration.
2928 0 : *aTime = (mFragmentEnd < 0.0 || mFragmentEnd > duration) ? duration : mFragmentEnd;
2929 0 : return NS_OK;
2930 : }
2931 :
2932 0 : void nsHTMLMediaElement::NotifyAudioAvailableListener()
2933 : {
2934 0 : if (mDecoder) {
2935 0 : mDecoder->NotifyAudioAvailableListener();
2936 : }
2937 4392 : }
|