Merge remote-tracking branch 'origin/master' into custom

This commit is contained in:
Roy Tam 2019-12-28 21:20:53 +08:00
commit b74770e3f1
35 changed files with 953 additions and 661 deletions

View file

@ -0,0 +1,71 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This is a light-weight tree iterator for `for` loops when full iterator
* functionality isn't required.
*/
#ifndef mozilla_dom_SimpleTreeIterator_h
#define mozilla_dom_SimpleTreeIterator_h
#include "nsINode.h"
#include "nsTArray.h"
#include "mozilla/dom/Element.h"
namespace mozilla {
namespace dom {
class SimpleTreeIterator {
public:
/**
* Initialize an iterator with aRoot. After that it can be iterated with a
* range-based for loop. At the moment, that's the only supported form of use
* for this iterator.
*/
explicit SimpleTreeIterator(nsINode& aRoot)
: mCurrent(&aRoot)
{
mTree.AppendElement(&aRoot);
}
// Basic support for range-based for loops.
// This will modify the iterator as it goes.
SimpleTreeIterator& begin() { return *this; }
SimpleTreeIterator end() { return SimpleTreeIterator(); }
bool operator!=(const SimpleTreeIterator& aOther) {
return mCurrent != aOther.mCurrent;
}
void operator++() { Next(); }
nsINode* operator*() { return mCurrent; }
private:
// Constructor used only for end() to represent a drained iterator.
SimpleTreeIterator()
: mCurrent(nullptr)
{}
void Next() {
MOZ_ASSERT(mCurrent, "Don't call Next() when we have no current node");
mCurrent = mCurrent->GetNextNode(mTree.LastElement());
}
// The current node.
nsINode* mCurrent;
// The DOM tree that we're inside of right now.
AutoTArray<nsINode*, 1> mTree;
};
} // namespace dom
} // namespace mozilla
#endif // mozilla_dom_SimpleTreeIterator_h

View file

@ -210,6 +210,7 @@ EXPORTS.mozilla.dom += [
'ScreenOrientation.h',
'ScriptSettings.h',
'ShadowRoot.h',
'SimpleTreeIterator.h',
'StructuredCloneHolder.h',
'StructuredCloneTags.h',
'StyleSheetList.h',

View file

@ -1968,22 +1968,10 @@ nsDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup)
}
void
nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
nsIPrincipal* aPrincipal)
{
NS_PRECONDITION(aURI, "Null URI passed to ResetToURI");
if (gDocumentLeakPRLog && MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
PR_LogPrint("DOCUMENT %p ResetToURI %s", this,
aURI->GetSpecOrDefault().get());
}
mSecurityInfo = nullptr;
mDocumentLoadGroup = nullptr;
nsDocument::DisconnectNodeTree() {
// Delete references to sub-documents and kill the subdocument map,
// if any. It holds strong references
// if any. This is not strictly needed, but makes the node tree
// teardown a bit faster.
delete mSubDocuments;
mSubDocuments = nullptr;
@ -2019,6 +2007,22 @@ nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
"After removing all children, there should be no root elem");
}
mInUnlinkOrDeletion = oldVal;
}
void
nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
nsIPrincipal* aPrincipal)
{
NS_PRECONDITION(aURI, "Null URI passed to ResetToURI");
if (gDocumentLeakPRLog && MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
PR_LogPrint("DOCUMENT %p ResetToURI %s", this,
aURI->GetSpecOrDefault().get());
}
mSecurityInfo = nullptr;
mDocumentLoadGroup = nullptr;
// Reset our stylesheets
ResetStylesheetsToURI(aURI);
@ -2029,6 +2033,8 @@ nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
mListenerManager = nullptr;
}
DisconnectNodeTree();
// Release the stylesheets list.
mDOMStyleSheets = nullptr;
@ -4506,18 +4512,6 @@ nsDocument::SetScriptGlobalObject(nsIScriptGlobalObject *aScriptGlobalObject)
mLayoutHistoryState = nullptr;
SetScopeObject(aScriptGlobalObject);
mHasHadDefaultView = true;
#ifdef DEBUG
if (!mWillReparent) {
// We really shouldn't have a wrapper here but if we do we need to make sure
// it has the correct parent.
JSObject *obj = GetWrapperPreserveColor();
if (obj) {
JSObject *newScope = aScriptGlobalObject->GetGlobalJSObject();
NS_ASSERTION(js::GetGlobalForObjectCrossCompartment(obj) == newScope,
"Wrong scope, this is really bad!");
}
}
#endif
if (mAllowDNSPrefetch) {
nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
@ -9077,7 +9071,8 @@ nsDocument::CloneDocHelper(nsDocument* clone) const
}
void
nsDocument::SetReadyStateInternal(ReadyState rs)
nsDocument::SetReadyStateInternal(ReadyState rs,
bool updateTimingInformation)
{
mReadyState = rs;
if (rs == READYSTATE_UNINITIALIZED) {
@ -9086,7 +9081,12 @@ nsDocument::SetReadyStateInternal(ReadyState rs)
// transition undetectable by Web content.
return;
}
if (mTiming) {
if (updateTimingInformation && READYSTATE_LOADING == rs) {
mLoadingTimeStamp = mozilla::TimeStamp::Now();
}
if (updateTimingInformation && mTiming) {
switch (rs) {
case READYSTATE_LOADING:
mTiming->NotifyDOMLoading(nsIDocument::GetDocumentURI());
@ -9102,10 +9102,6 @@ nsDocument::SetReadyStateInternal(ReadyState rs)
break;
}
}
// At the time of loading start, we don't have timing object, record time.
if (READYSTATE_LOADING == rs) {
mLoadingTimeStamp = mozilla::TimeStamp::Now();
}
RefPtr<AsyncEventDispatcher> asyncDispatcher =
new AsyncEventDispatcher(this, NS_LITERAL_STRING("readystatechange"),

View file

@ -704,7 +704,11 @@ public:
virtual void BeginLoad() override;
virtual void EndLoad() override;
virtual void SetReadyStateInternal(ReadyState rs) override;
// Set the readystate of the document. If updateTimingInformation is true,
// this will record relevant timestamps in the document's performance timing.
// Some consumers like document.open() don't want to do that.
virtual void SetReadyStateInternal(ReadyState rs,
bool updateTimingInformation = true) override;
virtual void ContentStateChanged(nsIContent* aContent,
mozilla::EventStates aStateMask)
@ -916,6 +920,14 @@ public:
UpdateFrameRequestCallbackSchedulingState();
}
void SetLoadEventFiring(bool aFiring) override { mLoadEventFiring = aFiring; }
bool SkipLoadEventAfterClose() override {
bool skip = mSkipLoadEventAfterClose;
mSkipLoadEventAfterClose = false;
return skip;
}
virtual nsIDocument* GetTemplateContentsOwner() override;
NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsDocument,
@ -1255,6 +1267,11 @@ protected:
*/
Element* GetTitleElement();
/**
* Perform tree disconnection needed by ResetToURI and document.open()
*/
void DisconnectNodeTree();
public:
// Get our title
virtual void GetTitle(nsString& aTitle) override;
@ -1458,6 +1475,20 @@ public:
// additional sheets and sheets from the nsStyleSheetService.
bool mStyleSetFilled:1;
// The HTML spec has a "iframe load in progress" flag, but that doesn't seem
// to have the right semantics. See <https://github.com/whatwg/html/issues/4292>.
// What we have instead is a flag that is set while the window's 'load' event is
// firing if this document is the window's document.
bool mLoadEventFiring : 1;
// The HTML spec has a "mute iframe load" flag, but that doesn't seem to have
// the right semantics. See <https://github.com/whatwg/html/issues/4292>.
// What we have instead is a flag that is set if completion of our document
// via document.close() should skip firing the load event. Note that this
// flag is only relevant for HTML documents, but lives here for reasons that
// are documented above on SkipLoadEventAfterClose().
bool mSkipLoadEventAfterClose : 1;
uint8_t mPendingFullscreenRequests;
uint8_t mXMLDeclarationBits;
@ -1615,11 +1646,6 @@ private:
// Set to true when the document is possibly controlled by the ServiceWorker.
// Used to prevent multiple requests to ServiceWorkerManager.
bool mMaybeServiceWorkerControlled;
#ifdef DEBUG
public:
bool mWillReparent;
#endif
};
class nsDocumentOnStack

View file

@ -909,10 +909,6 @@ public:
*/
nsresult GetSrcdocData(nsAString& aSrcdocData);
bool DidDocumentOpen() {
return mDidDocumentOpen;
}
already_AddRefed<mozilla::dom::AnonymousContent>
InsertAnonymousContent(mozilla::dom::Element& aElement,
mozilla::ErrorResult& aError);
@ -1448,7 +1444,7 @@ public:
virtual void EndLoad() = 0;
enum ReadyState { READYSTATE_UNINITIALIZED = 0, READYSTATE_LOADING = 1, READYSTATE_INTERACTIVE = 3, READYSTATE_COMPLETE = 4};
virtual void SetReadyStateInternal(ReadyState rs) = 0;
virtual void SetReadyStateInternal(ReadyState rs, bool updateTimingInformation = true) = 0;
ReadyState GetReadyStateEnum()
{
return mReadyState;
@ -2186,6 +2182,19 @@ public:
mAllowXULXBL = eTriTrue;
}
/**
* Flag whether we're about to fire the window's load event for this document.
*/
virtual void SetLoadEventFiring(bool aFiring) = 0;
/**
* Test whether we should be firing a load event for this document after a
* document.close().
* This method should only be called at the point when the load event is about
* to be fired, since it resets `skip`.
*/
virtual bool SkipLoadEventAfterClose() = 0;
/**
* Returns the template content owner document that owns the content of
* HTMLTemplateElement.
@ -3146,11 +3155,6 @@ protected:
// Whether the document was created by a srcdoc iframe.
bool mIsSrcdocDocument : 1;
// Records whether we've done a document.open. If this is true, it's possible
// for nodes from this document to have outdated wrappers in their wrapper
// caches.
bool mDidDocumentOpen : 1;
// Whether this document has a display document and thus is considered to
// be a resource document. Normally this is the same as !!mDisplayDocument,
// but mDisplayDocument is cleared during Unlink. mHasDisplayDocument is

View file

@ -1549,27 +1549,6 @@ AdoptNodeIntoOwnerDoc(nsINode *aParent, nsINode *aNode)
return NS_OK;
}
static nsresult
CheckForOutdatedParent(nsINode* aParent, nsINode* aNode)
{
if (JSObject* existingObjUnrooted = aNode->GetWrapper()) {
JS::Rooted<JSObject*> existingObj(RootingCx(), existingObjUnrooted);
AutoJSContext cx;
nsIGlobalObject* global = aParent->OwnerDoc()->GetScopeObject();
MOZ_ASSERT(global);
if (js::GetGlobalForObjectCrossCompartment(existingObj) !=
global->GetGlobalJSObject()) {
JSAutoCompartment ac(cx, existingObj);
nsresult rv = ReparentWrapper(cx, existingObj);
NS_ENSURE_SUCCESS(rv, rv);
}
}
return NS_OK;
}
static nsresult
ReparentWrappersInSubtree(nsIContent* aRoot)
{
@ -1631,9 +1610,6 @@ nsINode::doInsertChildAt(nsIContent* aKid, uint32_t aIndex,
if (OwnerDoc() != aKid->OwnerDoc()) {
rv = AdoptNodeIntoOwnerDoc(this, aKid);
NS_ENSURE_SUCCESS(rv, rv);
} else if (OwnerDoc()->DidDocumentOpen()) {
rv = CheckForOutdatedParent(this, aKid);
NS_ENSURE_SUCCESS(rv, rv);
}
uint32_t childCount = aChildArray.ChildCount();
@ -2481,11 +2457,6 @@ nsINode::ReplaceOrInsertBefore(bool aReplace, nsINode* aNewChild,
if (aError.Failed()) {
return nullptr;
}
} else if (doc->DidDocumentOpen()) {
aError = CheckForOutdatedParent(this, aNewChild);
if (aError.Failed()) {
return nullptr;
}
}
/*

View file

@ -113,19 +113,25 @@ var testFramesLoaded = function() {
// test that a document can be framed under a javascript: URL opened by the
// same site as the frame
// We can't set a load event listener before calling document.open/document.write, because those will remove such listeners. So we need to define a function that the new window will be able to call.
function frameInJSURILoaded(win) {
var test = win.document.getElementById("sameorigin3")
.contentDocument.getElementById("test");
ok(test != null, "frame under javascript: URL should have loaded.");
win.close();
// run last test
if (!isUnique) {
testFrameInDataURI();
} else {
testFrameNotLoadedInDataURI();
}
}
var testFrameInJSURI = function() {
var html = '<iframe id="sameorigin3" src="http://mochi.test:8888/tests/dom/base/test/file_x-frame-options_page.sjs?testid=sameorigin3&xfo=sameorigin"></iframe>';
var win = window.open();
win.onload = function() {
var test = win.document.getElementById("sameorigin3")
.contentDocument.getElementById("test");
ok(test != null, "frame under javascript: URL should have loaded.");
win.close();
// run last test
testFrameInDataURI();
}
win.location.href = "javascript:document.write('"+html+"');document.close();";
win.location.href = "javascript:document.open(); onload = opener.frameInJSURILoaded.bind(null, window); document.write('"+html+"');document.close();";
}
// test that a document can be framed under a data: URL opened by the

View file

@ -514,8 +514,9 @@ private:
{
// NS_IF_RELEASE because we might have been unlinked before
nsISupports* ptr = GetISupports();
NS_IF_RELEASE(ptr);
// Clear mPtrBits before the release to prevent reentrance.
mPtrBits = 0;
NS_IF_RELEASE(ptr);
}
uintptr_t mPtrBits;

View file

@ -166,11 +166,11 @@ EventListenerManager::~EventListenerManager()
// XXX azakai: Is there any reason to not just call Disconnect
// from right here, if not previously called?
NS_ASSERTION(!mTarget, "didn't call Disconnect");
RemoveAllListeners();
RemoveAllListenersSilently();
}
void
EventListenerManager::RemoveAllListeners()
EventListenerManager::RemoveAllListenersSilently()
{
if (mClearingListeners) {
return;
@ -1329,7 +1329,7 @@ void
EventListenerManager::Disconnect()
{
mTarget = nullptr;
RemoveAllListeners();
RemoveAllListenersSilently();
}
void
@ -1734,6 +1734,21 @@ EventListenerManager::IsApzAwareEvent(nsIAtom* aEvent)
return false;
}
void
EventListenerManager::RemoveAllListeners()
{
while (!mListeners.IsEmpty()) {
size_t idx = mListeners.Length() - 1;
nsCOMPtr<nsIAtom> type = mListeners.ElementAt(idx).mTypeAtom;
EventMessage message = mListeners.ElementAt(idx).mEventMessage;
mListeners.RemoveElementAt(idx);
NotifyEventListenerRemoved(type);
if (IsDeviceType(message)) {
DisableDevice(message);
}
}
}
already_AddRefed<nsIScriptGlobalObject>
EventListenerManager::GetScriptGlobalAndDocument(nsIDocument** aDoc)
{

View file

@ -472,6 +472,12 @@ public:
bool IsApzAwareListener(Listener* aListener);
bool IsApzAwareEvent(nsIAtom* aEvent);
/**
* Remove all event listeners from the event target this EventListenerManager
* is for.
*/
void RemoveAllListeners();
protected:
void HandleEventInternal(nsPresContext* aPresContext,
WidgetEvent* aEvent,
@ -604,7 +610,7 @@ protected:
const nsAString& aTypeString,
const EventListenerFlags& aFlags,
bool aAllEvents = false);
void RemoveAllListeners();
void RemoveAllListenersSilently();
void NotifyEventListenerRemoved(nsIAtom* aUserType);
const EventTypeData* GetTypeDataForIID(const nsIID& aIID);
const EventTypeData* GetTypeDataForEventName(nsIAtom* aName);

View file

@ -8,6 +8,7 @@
#include "mozilla/dom/HTMLMediaElementBinding.h"
#include "mozilla/dom/HTMLSourceElement.h"
#include "mozilla/dom/ElementInlines.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/AsyncEventDispatcher.h"
@ -171,6 +172,22 @@ static const unsigned short MEDIA_ERR_NETWORK = 2;
static const unsigned short MEDIA_ERR_DECODE = 3;
static const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
static void
ResolvePromisesWithUndefined(const nsTArray<RefPtr<Promise>>& aPromises)
{
for (auto& promise : aPromises) {
promise->MaybeResolveWithUndefined();
}
}
static void
RejectPromises(const nsTArray<RefPtr<Promise>>& aPromises, nsresult aError)
{
for (auto& promise : aPromises) {
promise->MaybeReject(aError);
}
}
// Under certain conditions there may be no-one holding references to
// a media element from script, DOM parent, etc, but the element may still
// fire meaningful events in the future so we can't destroy it yet:
@ -261,6 +278,75 @@ public:
}
};
/*
* If no error is passed while constructing an instance, the instance will
* resolve the passed promises with undefined; otherwise, the instance will
* reject the passed promises with the passed error.
*
* The constructor appends the constructed instance into the passed media
* element's mPendingPlayPromisesRunners member and once the the runner is run
* (whether fulfilled or canceled), it removes itself from
* mPendingPlayPromisesRunners.
*/
class HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner : public nsMediaEvent
{
nsTArray<RefPtr<Promise>> mPromises;
nsresult mError;
public:
nsResolveOrRejectPendingPlayPromisesRunner(HTMLMediaElement* aElement,
nsTArray<RefPtr<Promise>>&& aPromises,
nsresult aError = NS_OK)
: nsMediaEvent(aElement)
, mPromises(Move(aPromises))
, mError(aError)
{
mElement->mPendingPlayPromisesRunners.AppendElement(this);
}
void ResolveOrReject()
{
if (NS_SUCCEEDED(mError)) {
ResolvePromisesWithUndefined(mPromises);
} else {
RejectPromises(mPromises, mError);
}
}
NS_IMETHOD Run() override
{
if (!IsCancelled()) {
ResolveOrReject();
}
mElement->mPendingPlayPromisesRunners.RemoveElement(this);
return NS_OK;
}
};
class HTMLMediaElement::nsNotifyAboutPlayingRunner : public nsResolveOrRejectPendingPlayPromisesRunner
{
public:
nsNotifyAboutPlayingRunner(HTMLMediaElement* aElement,
nsTArray<RefPtr<Promise>>&& aPendingPlayPromises)
: nsResolveOrRejectPendingPlayPromisesRunner(aElement,
Move(aPendingPlayPromises))
{
}
NS_IMETHOD Run() override
{
if (IsCancelled()) {
mElement->mPendingPlayPromisesRunners.RemoveElement(this);
return NS_OK;
}
mElement->DispatchEvent(NS_LITERAL_STRING("playing"));
return nsResolveOrRejectPendingPlayPromisesRunner::Run();
}
};
class nsSourceErrorEventRunner : public nsMediaEvent
{
private:
@ -826,6 +912,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTM
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
#endif
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedVideoStreamTrack)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPlayPromises)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLElement)
@ -853,6 +940,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement, nsGenericHTMLE
NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
#endif
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPlayPromises)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLMediaElement)
@ -1044,6 +1132,14 @@ void HTMLMediaElement::AbortExistingLoads()
// with a different load ID to silently be cancelled.
mCurrentLoadID++;
// Immediately reject or resolve the already-dispatched
// nsResolveOrRejectPendingPlayPromisesRunners. These runners won't be
// executed again later since the mCurrentLoadID had been changed.
for (auto& runner : mPendingPlayPromisesRunners) {
runner->ResolveOrReject();
}
mPendingPlayPromisesRunners.Clear();
if (mChannelLoader) {
mChannelLoader->Cancel();
mChannelLoader = nullptr;
@ -1107,7 +1203,10 @@ void HTMLMediaElement::AbortExistingLoads()
NS_ASSERTION(!mDecoder && !mSrcStream, "How did someone setup a new stream/decoder already?");
// ChangeNetworkState() will call UpdateAudioChannelPlayingState()
// indirectly which depends on mPaused. So we need to update mPaused first.
mPaused = true;
if (!mPaused) {
mPaused = true;
RejectPromises(TakePendingPlayPromises(), NS_ERROR_DOM_MEDIA_ABORT_ERR);
}
ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY);
ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING);
@ -1149,6 +1248,7 @@ void HTMLMediaElement::NoSupportedMediaSourceError(const nsACString& aErrorDetai
mErrorSink->SetError(MEDIA_ERR_SRC_NOT_SUPPORTED, aErrorDetails);
ChangeDelayLoadStatus(false);
UpdateAudioChannelPlayingState();
RejectPromises(TakePendingPlayPromises(), NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR);
}
typedef void (HTMLMediaElement::*SyncSectionFn)();
@ -1953,14 +2053,7 @@ HTMLMediaElement::Seek(double aTime,
// aTime should be non-NaN.
MOZ_ASSERT(!mozilla::IsNaN(aTime));
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(OwnerDoc()->GetInnerWindow());
if (!global) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
RefPtr<Promise> promise = Promise::Create(global, aRv);
RefPtr<Promise> promise = CreateDOMPromise(aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
@ -2208,6 +2301,7 @@ HTMLMediaElement::Pause(ErrorResult& aRv)
if (!oldPaused) {
FireTimeUpdate(false);
DispatchAsyncEvent(NS_LITERAL_STRING("pause"));
AsyncRejectPendingPlayPromises(NS_ERROR_DOM_MEDIA_ABORT_ERR);
}
}
@ -3200,36 +3294,86 @@ HTMLMediaElement::NotifyXPCOMShutdown()
ShutdownDecoder();
}
void
already_AddRefed<Promise>
HTMLMediaElement::Play(ErrorResult& aRv)
{
if (!IsAllowedToPlay()) {
MaybeDoLoad();
return;
// A blocked media element will be resumed later, so we return a pending
// promise which might be resolved/rejected depends on the result of
// resuming the blocked media element.
RefPtr<Promise> promise = CreateDOMPromise(aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
mPendingPlayPromises.AppendElement(promise);
return promise.forget();
}
nsresult rv = PlayInternal();
if (NS_FAILED(rv)) {
aRv.Throw(rv);
}
RefPtr<Promise> promise = PlayInternal(aRv);
OpenUnsupportedMediaWithExternalAppIfNeeded();
return promise.forget();
}
nsresult
HTMLMediaElement::PlayInternal()
already_AddRefed<Promise>
HTMLMediaElement::PlayInternal(ErrorResult& aRv)
{
MOZ_ASSERT(!aRv.Failed());
// 4.8.12.8
// When the play() method on a media element is invoked, the user agent must
// run the following steps.
// 4.8.12.8 - Step 1:
// If the media element is not allowed to play, return a promise rejected
// with a "NotAllowedError" DOMException and abort these steps.
if (!IsAllowedToPlay()) {
// NOTE: for promise-based-play, will return a rejected promise here.
aRv.Throw(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
return nullptr;
}
// 4.8.12.8 - Step 2:
// If the media element's error attribute is not null and its code
// attribute has the value MEDIA_ERR_SRC_NOT_SUPPORTED, return a promise
// rejected with a "NotSupportedError" DOMException and abort these steps.
if (GetError() && GetError()->Code() == MEDIA_ERR_SRC_NOT_SUPPORTED) {
aRv.Throw(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR);
return nullptr;
}
// 4.8.12.8 - Step 3:
// Let promise be a new promise and append promise to the list of pending
// play promises.
RefPtr<Promise> promise = CreateDOMPromise(aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
mPendingPlayPromises.AppendElement(promise);
// Play was not blocked so assume user interacted with the element.
mHasUserInteraction = true;
StopSuspendingAfterFirstFrame();
SetPlayedOrSeeked(true);
// 4.8.12.8 - Step 4:
// If the media element's networkState attribute has the value NETWORK_EMPTY,
// invoke the media element's resource selection algorithm.
MaybeDoLoad();
if (mSuspendedForPreloadNone) {
ResumeLoad(PRELOAD_ENOUGH);
}
// 4.8.12.8 - Step 5:
// If the playback has ended and the direction of playback is forwards,
// seek to the earliest possible position of the media resource.
// Even if we just did Load() or ResumeLoad(), we could already have a decoder
// here if we managed to clone an existing decoder.
if (mDecoder) {
@ -3239,7 +3383,14 @@ HTMLMediaElement::PlayInternal()
if (!mPausedForInactiveDocumentOrChannel) {
nsresult rv = mDecoder->Play();
if (NS_FAILED(rv)) {
return rv;
// We don't need to remove the _promise_ from _mPendingPlayPromises_ here.
// If something wrong between |mPendingPlayPromises.AppendElement(promise);|
// and here, the _promise_ should already have been rejected. Otherwise,
// the _promise_ won't be returned to JS at all, so just leave it in the
// _mPendingPlayPromises_ and let it be resolved/rejected with the
// following actions and the promise-resolution won't be observed at all.
aRv.Throw(rv);
return nullptr;
}
}
}
@ -3248,7 +3399,7 @@ HTMLMediaElement::PlayInternal()
mCurrentPlayRangeStart = CurrentTime();
}
bool oldPaused = mPaused;
const bool oldPaused = mPaused;
mPaused = false;
mAutoplaying = false;
SetAudioChannelSuspended(nsISuspendedTypes::NONE_SUSPENDED);
@ -3265,8 +3416,27 @@ HTMLMediaElement::PlayInternal()
// media, the event would be pending until media is resumed.
// TODO: If the playback has ended, then the user agent must set
// seek to the effective start.
// 4.8.12.8 - Step 6:
// If the media element's paused attribute is true, run the following steps:
if (oldPaused) {
// 6.1. Change the value of paused to false. (Already done.)
// This step is uplifted because the "block-media-playback" feature needs
// the mPaused to be false before UpdateAudioChannelPlayingState() being
// called.
// 6.2. If the show poster flag is true, set the element's show poster flag
// to false and run the time marches on steps.
// 6.3. Queue a task to fire a simple event named play at the element.
DispatchAsyncEvent(NS_LITERAL_STRING("play"));
// 6.4. If the media element's readyState attribute has the value
// HAVE_NOTHING, HAVE_METADATA, or HAVE_CURRENT_DATA, queue a task to
// fire a simple event named waiting at the element.
// Otherwise, the media element's readyState attribute has the value
// HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA: notify about playing for the
// element.
switch (mReadyState) {
case nsIDOMHTMLMediaElement::HAVE_NOTHING:
DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
@ -3279,12 +3449,20 @@ HTMLMediaElement::PlayInternal()
case nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA:
case nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA:
FireTimeUpdate(false);
DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
NotifyAboutPlaying();
break;
}
} else if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) {
// 7. Otherwise, if the media element's readyState attribute has the value
// HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA, take pending play promises and
// queue a task to resolve pending play promises with the result.
AsyncResolvePendingPlayPromises();
}
return NS_OK;
// 8. Set the media element's autoplaying flag to false. (Already done.)
// 9. Return promise.
return promise.forget();
}
void
@ -3302,9 +3480,10 @@ NS_IMETHODIMP HTMLMediaElement::Play()
return NS_OK;
}
nsresult rv = PlayInternal();
if (NS_FAILED(rv)) {
return rv;
ErrorResult rv;
RefPtr<Promise> toBeIgnored = PlayInternal(rv);
if (rv.Failed()) {
return rv.StealNSResult();
}
OpenUnsupportedMediaWithExternalAppIfNeeded();
@ -4463,7 +4642,12 @@ void HTMLMediaElement::PlaybackEnded()
return;
}
Pause();
FireTimeUpdate(false);
if (!mPaused) {
Pause();
AsyncRejectPendingPlayPromises(NS_ERROR_DOM_MEDIA_ABORT_ERR);
}
if (mSrcStream) {
// A MediaStream that goes from inactive to active shall be eligible for
@ -4471,7 +4655,6 @@ void HTMLMediaElement::PlaybackEnded()
mAutoplaying = true;
}
FireTimeUpdate(false);
DispatchAsyncEvent(NS_LITERAL_STRING("ended"));
}
@ -4865,7 +5048,7 @@ void HTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
DispatchAsyncEvent(NS_LITERAL_STRING("canplay"));
if (!mPaused) {
mWaitingForKey = NOT_WAITING_FOR_KEY;
DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
NotifyAboutPlaying();
}
}
@ -5111,7 +5294,14 @@ nsresult HTMLMediaElement::DispatchAsyncEvent(const nsAString& aName)
return NS_OK;
}
nsCOMPtr<nsIRunnable> event = new nsAsyncEventRunner(aName, this);
nsCOMPtr<nsIRunnable> event;
if (aName.EqualsLiteral("playing")) {
event = new nsNotifyAboutPlayingRunner(this, TakePendingPlayPromises());
} else {
event = new nsAsyncEventRunner(aName, this);
}
NS_DispatchToMainThread(event);
return NS_OK;
@ -6601,5 +6791,64 @@ HTMLMediaElement::MarkAsContentSource(CallerAPI aAPI)
}
}
nsTArray<RefPtr<Promise>>
HTMLMediaElement::TakePendingPlayPromises()
{
return Move(mPendingPlayPromises);
}
void
HTMLMediaElement::NotifyAboutPlaying()
{
// Stick to the DispatchAsyncEvent() call path for now because we want to
// trigger some telemetry-related codes in the DispatchAsyncEvent() method.
DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
}
already_AddRefed<Promise>
HTMLMediaElement::CreateDOMPromise(ErrorResult& aRv) const
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(OwnerDoc()->GetInnerWindow());
if (!global) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
return Promise::Create(global, aRv);
}
void
HTMLMediaElement::AsyncResolvePendingPlayPromises()
{
if (mShuttingDown) {
return;
}
nsCOMPtr<nsIRunnable> event
= new nsResolveOrRejectPendingPlayPromisesRunner(this,
TakePendingPlayPromises());
NS_DispatchToMainThread(event);
}
void
HTMLMediaElement::AsyncRejectPendingPlayPromises(nsresult aError)
{
if (mShuttingDown) {
return;
}
nsCOMPtr<nsIRunnable> event
= new nsResolveOrRejectPendingPlayPromisesRunner(this,
TakePendingPlayPromises(),
aError);
NS_DispatchToMainThread(event);
}
} // namespace dom
} // namespace mozilla

View file

@ -16,7 +16,6 @@
#include "DecoderTraits.h"
#include "nsIAudioChannelAgent.h"
#include "mozilla/Attributes.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/TextTrackManager.h"
#include "mozilla/WeakPtr.h"
#include "MediaDecoder.h"
@ -78,6 +77,7 @@ namespace dom {
class MediaError;
class MediaSource;
class Promise;
class TextTrackList;
class AudioTrackList;
class VideoTrackList;
@ -549,7 +549,7 @@ public:
SetHTMLBoolAttr(nsGkAtoms::loop, aValue, aRv);
}
void Play(ErrorResult& aRv);
already_AddRefed<Promise> Play(ErrorResult& aRv);
void Pause(ErrorResult& aRv);
@ -835,7 +835,7 @@ protected:
nsTArray<Pair<nsString, RefPtr<MediaInputPort>>> mTrackPorts;
};
nsresult PlayInternal();
already_AddRefed<Promise> PlayInternal(ErrorResult& aRv);
/** Use this method to change the mReadyState member, so required
* events can be fired.
@ -981,6 +981,7 @@ protected:
void AbortExistingLoads();
/**
* These are the dedicated media source failure steps.
* Called when all potential resources are exhausted. Changes network
* state to NETWORK_NO_SOURCE, and sends error event with code
* MEDIA_ERR_SRC_NOT_SUPPORTED.
@ -1285,6 +1286,8 @@ protected:
void MaybeNotifyMediaResumed(SuspendTypes aSuspend);
class nsAsyncEventRunner;
class nsNotifyAboutPlayingRunner;
class nsResolveOrRejectPendingPlayPromisesRunner;
using nsGenericHTMLElement::DispatchEvent;
// For nsAsyncEventRunner.
nsresult DispatchEvent(const nsAString& aName);
@ -1293,6 +1296,24 @@ protected:
// triggers play() after loaded fail. eg. preload the data before start play.
void OpenUnsupportedMediaWithExternalAppIfNeeded() const;
// This method moves the mPendingPlayPromises into a temperate object. So the
// mPendingPlayPromises is cleared after this method call.
nsTArray<RefPtr<Promise>> TakePendingPlayPromises();
// This method snapshots the mPendingPlayPromises by TakePendingPlayPromises()
// and queues a task to resolve them.
void AsyncResolvePendingPlayPromises();
// This method snapshots the mPendingPlayPromises by TakePendingPlayPromises()
// and queues a task to reject them.
void AsyncRejectPendingPlayPromises(nsresult aError);
// This method snapshots the mPendingPlayPromises by TakePendingPlayPromises()
// and queues a task to resolve them also to dispatch a "playing" event.
void NotifyAboutPlaying();
already_AddRefed<Promise> CreateDOMPromise(ErrorResult& aRv) const;
// The current decoder. Load() has been called on this decoder.
// At most one of mDecoder and mSrcStream can be non-null.
RefPtr<MediaDecoder> mDecoder;
@ -1684,6 +1705,17 @@ private:
Visibility mVisibilityState;
UniquePtr<ErrorSink> mErrorSink;
// A list of pending play promises. The elements are pushed during the play()
// method call and are resolved/rejected during further playback steps.
nsTArray<RefPtr<Promise>> mPendingPlayPromises;
// A list of already-dispatched but not yet run
// nsResolveOrRejectPendingPlayPromisesRunners.
// Runners whose Run() method is called remove themselves from this list.
// We keep track of these because the load algorithm resolves/rejects all
// already-dispatched pending play promises.
nsTArray<nsResolveOrRejectPendingPlayPromisesRunner*> mPendingPlayPromisesRunners;
};
} // namespace dom

View file

@ -15,6 +15,7 @@
#include "nsPrintfCString.h"
#include "nsReadableUtils.h"
#include "nsUnicharUtils.h"
#include "nsIDocumentLoader.h"
#include "nsIHTMLContentSink.h"
#include "nsIXMLContentSink.h"
#include "nsHTMLParts.h"
@ -84,6 +85,7 @@
#include "mozilla/dom/EncodingUtils.h"
#include "mozilla/dom/FallbackEncoding.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/LoadInfo.h"
#include "nsIEditingSession.h"
#include "nsIEditor.h"
@ -107,12 +109,14 @@
#include "nsIImageDocument.h"
#include "mozilla/dom/HTMLBodyElement.h"
#include "mozilla/dom/HTMLDocumentBinding.h"
#include "mozilla/dom/SimpleTreeIterator.h"
#include "nsCharsetSource.h"
#include "nsIStringBundle.h"
#include "nsDOMClassInfo.h"
#include "nsFocusManager.h"
#include "nsIFrame.h"
#include "nsIContent.h"
#include "nsIStructuredCloneContainer.h"
#include "nsLayoutStylesheetCache.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/StyleSheetInlines.h"
@ -842,6 +846,24 @@ nsHTMLDocument::EndLoad()
if (turnOnEditing) {
EditingStateChanged();
}
if (!GetWindow()) {
// This is a document that's not in a window. For example, this could be an
// XMLHttpRequest responseXML document, or a document created via DOMParser
// or DOMImplementation. We don't reach this code normally for such
// documents (which is not obviously correct), but can reach it via
// document.open()/document.close().
//
// Such documents don't fire load events, but per spec should set their
// readyState to "complete" when parsing and all loading of subresources is
// done. Parsing is done now, and documents not in a window don't load
// subresources, so just go ahead and mark ourselves as complete.
SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE,
/* updateTimingInformation = */ false);
// Reset mSkipLoadEventAfterClose just in case.
mSkipLoadEventAfterClose = false;
}
}
void
@ -1410,19 +1432,21 @@ already_AddRefed<nsIDocument>
nsHTMLDocument::Open(JSContext* cx,
const nsAString& aType,
const nsAString& aReplace,
ErrorResult& rv)
ErrorResult& aError)
{
// Implements the "When called with two arguments (or fewer)" steps here:
// https://html.spec.whatwg.org/multipage/webappapis.html#opening-the-input-stream
// Implements
// <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
NS_ASSERTION(nsContentUtils::CanCallerAccess(static_cast<nsIDOMHTMLDocument*>(this)),
"XOW should have caught this!");
// Step 1 - Throw if we're the wrong type of document.
if (!IsHTMLDocument() || mDisableDocWrite || !IsMasterDocument()) {
// No calling document.open() on XHTML
rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
// Set up the content type for insertion
nsAutoCString contentType;
contentType.AssignLiteral("text/html");
@ -1435,51 +1459,7 @@ nsHTMLDocument::Open(JSContext* cx,
contentType.AssignLiteral("text/plain");
}
// If we already have a parser we ignore the document.open call.
if (mParser || mParserAborted) {
// The WHATWG spec says: "If the document has an active parser that isn't
// a script-created parser, and the insertion point associated with that
// parser's input stream is not undefined (that is, it does point to
// somewhere in the input stream), then the method does nothing. Abort
// these steps and return the Document object on which the method was
// invoked."
// Note that aborting a parser leaves the parser "active" with its
// insertion point "not undefined". We track this using mParserAborted,
// because aborting a parser nulls out mParser.
nsCOMPtr<nsIDocument> ret = this;
return ret.forget();
}
// No calling document.open() without a script global object
if (!mScriptGlobalObject) {
nsCOMPtr<nsIDocument> ret = this;
return ret.forget();
}
nsPIDOMWindowOuter* outer = GetWindow();
if (!outer || (GetInnerWindow() != outer->GetCurrentInnerWindow())) {
nsCOMPtr<nsIDocument> ret = this;
return ret.forget();
}
// check whether we're in the middle of unload. If so, ignore this call.
nsCOMPtr<nsIDocShell> shell(mDocumentContainer);
if (!shell) {
// We won't be able to create a parser anyway.
nsCOMPtr<nsIDocument> ret = this;
return ret.forget();
}
bool inUnload;
shell->GetIsInUnload(&inUnload);
if (inUnload) {
nsCOMPtr<nsIDocument> ret = this;
return ret.forget();
}
// Note: We want to use GetEntryDocument here because this document
// should inherit the security information of the document that's opening us,
// (since if it's secure, then it's presumably trusted).
// Step 3 - Get the entryDocument for security checks
nsCOMPtr<nsIDocument> callerDoc = GetEntryDocument();
if (!callerDoc) {
// If we're called from C++ or in some other way without an originating
@ -1489,67 +1469,39 @@ nsHTMLDocument::Open(JSContext* cx,
// change the principals of a document for security reasons we'll have to
// refuse to go ahead with this call.
rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
// Grab a reference to the calling documents security info (if any)
// and URIs as they may be lost in the call to Reset().
nsCOMPtr<nsISupports> securityInfo = callerDoc->GetSecurityInfo();
nsCOMPtr<nsIURI> uri = callerDoc->GetDocumentURI();
nsCOMPtr<nsIURI> baseURI = callerDoc->GetBaseURI();
nsCOMPtr<nsIPrincipal> callerPrincipal = callerDoc->NodePrincipal();
nsCOMPtr<nsIChannel> callerChannel = callerDoc->GetChannel();
// We're called from script. Make sure the script is from the same
// origin, not just that the caller can access the document. This is
// needed to keep document principals from ever changing, which is
// needed because of the way we use our XOW code, and is a sane
// thing to do anyways.
bool equals = false;
if (NS_FAILED(callerPrincipal->Equals(NodePrincipal(), &equals)) ||
!equals) {
#ifdef DEBUG
nsCOMPtr<nsIURI> callerDocURI = callerDoc->GetDocumentURI();
nsCOMPtr<nsIURI> thisURI = nsIDocument::GetDocumentURI();
printf("nsHTMLDocument::Open callerDoc %s this %s\n",
callerDocURI ? callerDocURI->GetSpecOrDefault().get() : "",
thisURI ? thisURI->GetSpecOrDefault().get() : "");
#endif
rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
// Step 4 - Throw if we're not same-origin
if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
// Stop current loads targeted at the window this document is in.
if (mScriptGlobalObject) {
nsCOMPtr<nsIContentViewer> cv;
shell->GetContentViewer(getter_AddRefs(cv));
if (cv) {
bool okToUnload;
if (NS_SUCCEEDED(cv->PermitUnload(&okToUnload)) && !okToUnload) {
// We don't want to unload, so stop here, but don't throw an
// exception.
nsCOMPtr<nsIDocument> ret = this;
return ret.forget();
}
// Now double-check that our invariants still hold.
if (!mScriptGlobalObject) {
nsCOMPtr<nsIDocument> ret = this;
return ret.forget();
}
nsPIDOMWindowOuter* outer = GetWindow();
if (!outer || (GetInnerWindow() != outer->GetCurrentInnerWindow())) {
nsCOMPtr<nsIDocument> ret = this;
return ret.forget();
}
// Step 5 - If we have an active parser, abort with no-op
if (mParser || mParserAborted) {
nsCOMPtr<nsIDocument> ret = this;
return ret.forget();
}
// Step 6 - Check if document.open() is called during unload
nsCOMPtr<nsIDocShell> shell(mDocumentContainer);
if (shell) {
bool inUnload;
shell->GetIsInUnload(&inUnload);
if (inUnload) {
nsCOMPtr<nsIDocument> ret = this;
return ret.forget();
}
}
// Step 7 - Stop existing navigation of our browsing context (and all
// other loads it's doing) if we're the active document of our browsing
// context. If there's no existing navigation, we don't want to stop
// anything.
if (shell && IsCurrentActiveDocument() &&
mScriptGlobalObject) {
nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(shell));
webnav->Stop(nsIWebNavigation::STOP_NETWORK);
@ -1560,189 +1512,121 @@ nsHTMLDocument::Open(JSContext* cx,
EnsureOnloadBlocker();
}
// The open occurred after the document finished loading.
// So we reset the document and then reinitialize it.
nsCOMPtr<nsIChannel> channel;
nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
rv = NS_NewChannel(getter_AddRefs(channel),
uri,
callerDoc,
nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
nsIContentPolicy::TYPE_OTHER,
group);
if (rv.Failed()) {
return nullptr;
}
if (callerChannel) {
nsLoadFlags callerLoadFlags;
rv = callerChannel->GetLoadFlags(&callerLoadFlags);
if (rv.Failed()) {
return nullptr;
}
nsLoadFlags loadFlags;
rv = channel->GetLoadFlags(&loadFlags);
if (rv.Failed()) {
return nullptr;
}
loadFlags |= callerLoadFlags & nsIRequest::INHIBIT_PERSISTENT_CACHING;
rv = channel->SetLoadFlags(loadFlags);
if (rv.Failed()) {
return nullptr;
}
// If the user has allowed mixed content on the rootDoc, then we should propogate it
// down to the new document channel.
bool rootHasSecureConnection = false;
bool allowMixedContent = false;
bool isDocShellRoot = false;
nsresult rvalue = shell->GetAllowMixedContentAndConnectionData(&rootHasSecureConnection, &allowMixedContent, &isDocShellRoot);
if (NS_SUCCEEDED(rvalue) && allowMixedContent && isDocShellRoot) {
shell->SetMixedContentChannel(channel);
// Step 8 - Clear all event listeners out of our DOM tree
for (nsINode* node : SimpleTreeIterator(*this)) {
if (EventListenerManager* elm = node->GetExistingListenerManager()) {
elm->RemoveAllListeners();
}
}
// Before we reset the doc notify the globalwindow of the change,
// but only if we still have a window (i.e. our window object the
// current inner window in our outer window).
// Hold onto ourselves on the offchance that we're down to one ref
nsCOMPtr<nsIDocument> kungFuDeathGrip = this;
if (nsPIDOMWindowInner *window = GetInnerWindow()) {
// Remember the old scope in case the call to SetNewDocument changes it.
nsCOMPtr<nsIScriptGlobalObject> oldScope(do_QueryReferent(mScopeObject));
#ifdef DEBUG
bool willReparent = mWillReparent;
mWillReparent = true;
nsDocument* templateContentsOwner =
static_cast<nsDocument*>(mTemplateContentsOwner.get());
if (templateContentsOwner) {
templateContentsOwner->mWillReparent = true;
// Step 9 - Clear event listeners from our window, if we have one.
//
// Note that we explicitly want the inner window, and only if we're its
// document. We want to do this (per spec) even when we're not the "active
// document", so we can't go through GetWindow(), because it might forward to
// the wrong inner.
if (nsPIDOMWindowInner* win = GetInnerWindow()) {
if (win->GetExtantDoc() == this) {
if (EventListenerManager* elm =
nsGlobalWindow::Cast(win)->GetExistingListenerManager()) {
elm->RemoveAllListeners();
}
}
#endif
}
// Per spec, we pass false here so that a new Window is created.
rv = window->SetNewDocument(this, nullptr,
/* aForceReuseInnerWindow */ false);
if (rv.Failed()) {
// Step 10 - Remove all of our DOM children without firing any mutation events.
DisconnectNodeTree();
// --- At this point our tree is clean and we can switch to the new URI ---
// Step 11 - If we're the current document in our docshell, do the
// equivalent of pushState() with the new URL we should have.
if (shell && IsCurrentActiveDocument()) {
nsCOMPtr<nsIURI> newURI = callerDoc->GetDocumentURI();
// UpdateURLAndHistory might do various member-setting, so make sure we're
// holding strong refs to all the refcounted args on the stack. We can
// assume that our caller is holding on to "this" already.
nsCOMPtr<nsIURI> currentURI = nsIDocument::GetDocumentURI();
bool equalURIs;
nsresult rv = currentURI->Equals(newURI, &equalURIs);
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return nullptr;
}
nsCOMPtr<nsIStructuredCloneContainer> stateContainer(mStateObjectContainer);
rv = shell->UpdateURLAndHistory(this, newURI, stateContainer, EmptyString(),
/* aReplace = */ true, currentURI,
equalURIs);
if (NS_WARN_IF(NS_FAILED(rv))) {
aError.Throw(rv);
return nullptr;
}
#ifdef DEBUG
if (templateContentsOwner) {
templateContentsOwner->mWillReparent = willReparent;
}
// And use the security info of the caller document as well, since
// it's the thing providing our data.
mSecurityInfo = callerDoc->GetSecurityInfo();
mWillReparent = willReparent;
#endif
// Now make sure we're not flagged as the initial document anymore, now
// that we've had stuff done to us. From now on, if anyone tries to
// document.open() us, they get a new inner window.
// This is not mentioned in the spec, but that's probably a spec bug.
// See <https://github.com/whatwg/html/issues/4299>.
// Since our URL may be changing away from about:blank here, we really want
// to unset this flag on any document.open(), since only about:blank can be
// an initial document.
SetIsInitialDocument(false);
nsCOMPtr<nsIScriptGlobalObject> newScope(do_QueryReferent(mScopeObject));
JS::Rooted<JSObject*> wrapper(cx, GetWrapper());
if (oldScope && newScope != oldScope && wrapper) {
JSAutoCompartment ac(cx, wrapper);
rv = mozilla::dom::ReparentWrapper(cx, wrapper);
if (rv.Failed()) {
return nullptr;
}
// And let our docloader know that it will need to track our load event.
nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
}
// Also reparent the template contents owner document
// because its global is set to the same as this document.
if (mTemplateContentsOwner) {
JS::Rooted<JSObject*> contentsOwnerWrapper(cx,
mTemplateContentsOwner->GetWrapper());
if (contentsOwnerWrapper) {
rv = mozilla::dom::ReparentWrapper(cx, contentsOwnerWrapper);
if (rv.Failed()) {
return nullptr;
}
}
}
}
}
// Step 12
mSkipLoadEventAfterClose = mLoadEventFiring;
mDidDocumentOpen = true;
// Preliminary to steps 13-16. Set our ready state to uninitialized before
// we do anything else, so we can then proceed to later ready state levels.
SetReadyStateInternal(READYSTATE_UNINITIALIZED,
/* updateTimingInformation = */ false);
// Call Reset(), this will now do the full reset
Reset(channel, group);
if (baseURI) {
mDocumentBaseURI = baseURI;
}
// Store the security info of the caller now that we're done
// resetting the document.
mSecurityInfo = securityInfo;
// Step 13 - Set our compatibility mode to standards.
SetCompatibilityMode(eCompatibility_FullStandards);
// Step 14 - Create a new parser associated with document.
// This also does step 16 implicitly.
mParserAborted = false;
mParser = nsHtml5Module::NewHtml5Parser();
nsHtml5Module::Initialize(mParser, this, uri, shell, channel);
nsHtml5Module::Initialize(mParser, this, nsIDocument::GetDocumentURI(), shell, nullptr);
if (mReferrerPolicySet) {
// CSP may have set the referrer policy, so a speculative parser should
// start with the new referrer policy.
nsHtml5TreeOpExecutor* executor = nullptr;
executor = static_cast<nsHtml5TreeOpExecutor*> (mParser->GetContentSink());
executor = static_cast<nsHtml5TreeOpExecutor*>(mParser->GetContentSink());
if (executor && mReferrerPolicySet) {
executor->SetSpeculationReferrerPolicy(static_cast<ReferrerPolicy>(mReferrerPolicy));
executor->SetSpeculationReferrerPolicy(
static_cast<ReferrerPolicy>(mReferrerPolicy));
}
}
// This will be propagated to the parser when someone actually calls write()
SetContentTypeInternal(contentType);
if (shell) {
// Prepare the docshell and the document viewer for the impending
// out-of-band document.write()
shell->PrepareForNewContentModel();
// Prepare the docshell and the document viewer for the impending
// out of band document.write()
shell->PrepareForNewContentModel();
// Now check whether we were opened with a "replace" argument. If
// so, we need to tell the docshell to not create a new history
// entry for this load. Otherwise, make sure that we're doing a normal load,
// not whatever type of load was previously done on this docshell.
shell->SetLoadType(aReplace.LowerCaseEqualsLiteral("replace") ?
LOAD_NORMAL_REPLACE : LOAD_NORMAL);
nsCOMPtr<nsIContentViewer> cv;
shell->GetContentViewer(getter_AddRefs(cv));
if (cv) {
cv->LoadStart(this);
nsCOMPtr<nsIContentViewer> cv;
shell->GetContentViewer(getter_AddRefs(cv));
if (cv) {
cv->LoadStart(this);
}
}
// Add a wyciwyg channel request into the document load group
NS_ASSERTION(!mWyciwygChannel, "nsHTMLDocument::Open(): wyciwyg "
"channel already exists!");
// Step 15.
SetReadyStateInternal(nsIDocument::READYSTATE_LOADING,
/* updateTimingInformation = */ false);
// In case the editor is listening and will see the new channel
// being added, make sure mWriteLevel is non-zero so that the editor
// knows that document.open/write/close() is being called on this
// document.
++mWriteLevel;
// Step 16 happened with step 14 above.
CreateAndAddWyciwygChannel();
--mWriteLevel;
SetReadyStateInternal(nsIDocument::READYSTATE_LOADING);
// After changing everything around, make sure that the principal on the
// document's compartment exactly matches NodePrincipal().
DebugOnly<JSObject*> wrapper = GetWrapperPreserveColor();
MOZ_ASSERT_IF(wrapper,
JS_GetCompartmentPrincipals(js::GetObjectCompartment(wrapper)) ==
nsJSPrincipals::get(NodePrincipal()));
return kungFuDeathGrip.forget();
}
// Step 17.
nsCOMPtr<nsIDocument> ret = this;
return ret.forget();
}
NS_IMETHODIMP
nsHTMLDocument::Clear()
@ -1806,15 +1690,6 @@ nsHTMLDocument::Close(ErrorResult& rv)
if (GetShell()) {
FlushPendingNotifications(Flush_Layout);
}
// Removing the wyciwygChannel here is wrong when document.close() is
// called from within the document itself. However, legacy requires the
// channel to be removed here. Otherwise, the load event never fires.
NS_ASSERTION(mWyciwygChannel, "nsHTMLDocument::Close(): Trying to remove "
"nonexistent wyciwyg channel!");
RemoveWyciwygChannel();
NS_ASSERTION(!mWyciwygChannel, "nsHTMLDocument::Close(): "
"nsIWyciwygChannel could not be removed!");
}
void

View file

@ -529,7 +529,6 @@ skip-if = toolkit == 'android' # plugins not supported
[test_bug196523.html]
[test_bug199692.html]
skip-if = toolkit == 'android' #bug 811644
[test_bug172261.html]
[test_bug255820.html]
[test_bug259332.html]
[test_bug311681.html]

View file

@ -1,67 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=172261
-->
<head>
<title>Test for Bug 172261</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=172261">Mozilla Bug 172261</a>
<p id="display">
<iframe id="test"></iframe>
</p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
/** Test for Bug 172261 **/
SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout("untriaged");
var callable = false;
function toggleCallable() { callable = true; }
var doTestInIframe = false;
// Shouldn't do history stuff from inside onload
addLoadEvent(function() { setTimeout(startTest, 10) });
function startTest() {
// First, create a dummy document. Use onunload handlers to make sure
// bfcache doesn't screw us up.
var doc = $("test").contentDocument;
doc.write("<html><body onunload=''>First</body></html>");
doc.close();
// Now write our test document
doc.write("<html><script>window.onerror = parent.onerror; if (parent.doTestInIframe) { parent.is(document.domain, parent.document.domain, 'Domains should match'); parent.toggleCallable(); } <" + "/script><body>Second</body></html>");
doc.close();
$("test").onload = goForward;
history.back();
}
function goForward() {
$("test").onload = doTest;
doTestInIframe = true;
history.forward();
}
function doTest() {
is($("test").contentDocument.domain, document.domain,
"Domains should match 2");
is($("test").contentDocument.location.href, location.href,
"Locations should match");
is(callable, true, "Subframe should be able to call us");
SimpleTest.finish();
}
</script>
</pre>
</body>
</html>

View file

@ -28,7 +28,7 @@ SimpleTest.waitForExplicitFinish();
is(document.characterSet, "UTF-8",
"Unexpected character set for our document");
var testsLeft = 4;
var testsLeft = 3;
function testFinished() {
--testsLeft;
@ -42,29 +42,11 @@ function charsetTestFinished(id, doc, charsetTarget) {
testFinished();
}
function f2Continue() {
// Commented out pending discussion at the WHATWG
// $("f2").
// setAttribute("onload",
// "charsetTestFinished('f2 reloaded', this.contentDocument, 'us-ascii');");
$("f2").
setAttribute("onload",
"testFinished();");
$("f2").contentWindow.location.reload();
}
function f3Continue() {
var doc = $("f3").contentDocument;
is(doc.defaultView.getComputedStyle(doc.body, "").color, "rgb(0, 180, 0)",
"Wrong color before reload");
$("f3").
setAttribute("onload",
'var doc = this.contentDocument; ' +
'is(doc.defaultView.getComputedStyle(doc.body, "").color, ' +
' "rgb(0, 180, 0)",' +
' "Wrong color after reload");' +
"charsetTestFinished('f1', this.contentDocument, 'UTF-8')");
$("f3").contentWindow.location.reload();
"Wrong color");
charsetTestFinished('f3', doc, "UTF-8");
}
function runTest() {
@ -74,12 +56,7 @@ function runTest() {
doc.open();
doc.write('<html></html>');
doc.close();
is(doc.characterSet, "UTF-8",
"Unexpected character set for first frame after write");
$("f1").
setAttribute("onload",
"charsetTestFinished('f1', this.contentDocument, 'UTF-8')");
$("f1").contentWindow.location.reload();
charsetTestFinished("f1", doc, "UTF-8");
doc = $("f2").contentDocument;
is(doc.characterSet, "UTF-8",
@ -96,12 +73,11 @@ function runTest() {
"Unexpected character set for second frame after write");
$("f2").
setAttribute("onload",
"charsetTestFinished('f2', this.contentDocument, 'UTF-8');" +
"f2Continue()");
"charsetTestFinished('f2', this.contentDocument, 'UTF-8');");
doc = $("f3").contentDocument;
is(doc.characterSet, "UTF-8",
"Unexpected initial character set for first frame");
"Unexpected initial character set for third frame");
doc.open();
var str = '<html><head>';
str += '<style>body { color: rgb(255, 0, 0) }</style>';
@ -111,7 +87,7 @@ function runTest() {
doc.write(str);
doc.close();
is(doc.characterSet, "UTF-8",
"Unexpected character set for first frame after write");
"Unexpected character set for third frame after write");
$("f3").setAttribute("onload", "f3Continue()");
}

View file

@ -108,7 +108,7 @@ function messageReceiver(evt) {
is(testResult, "undefined", "Props on new window's child should go away when loading");
break;
case 6:
is(testResult, "undefined", "Props on new window's child should go away when writing");
is(testResult, "6", "Props on new window's child should go away when writing");
break;
case 7:
is(testResult, "7", "Props on different-domain window opened from different-domain new window can stay");

View file

@ -68,7 +68,7 @@ interface HTMLMediaElement : HTMLElement {
[SetterThrows]
attribute boolean loop;
[Throws]
void play();
Promise<void> play();
[Throws]
void pause();