@ -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,123 @@ 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 ;
// Step 8 - Clear all event listeners out of our DOM tree
for ( nsINode * node : SimpleTreeIterator ( * this ) ) {
if ( EventListenerManager * elm = node - > GetExistingListenerManager ( ) ) {
elm - > RemoveAllListeners ( ) ;
}
}
// 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 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 ( ) ;
}
}
}
// 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).
// Step 10 - Remove all of our DOM children without firing any mutation events.
DisconnectNodeTree ( ) ;
// Hold onto ourselves on the offchance that we're down to one ref
nsCOMPtr < nsIDocument > kungFuDeathGrip = this ;
// --- At this point our tree is clean and we can switch to the new URI ---
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 ;
}
# endif
// 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 ( ) ;
// Per spec, we pass false here so that a new Window is created.
rv = window - > SetNewDocument ( this , nullptr ,
/* aForceReuseInnerWindow */ false ) ;
if ( rv . Failed ( ) ) {
// 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 ;
}
# ifdef DEBUG
if ( templateContentsOwner ) {
templateContentsOwner - > mWillReparent = willReparent ;
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 ;
}
mWillReparent = willReparent ;
# endif
// And use the security info of the caller document as well, since
// it's the thing providing our data.
mSecurityInfo = callerDoc - > GetSecurityInfo ( ) ;
// 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 ;
}
mDidDocumentOpen = true ;
// 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 ) ;
// 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 ) ;
if ( shell ) {
// Prepare the docshell and the document viewer for the impending
// out-of-band document.write()
shell - > PrepareForNewContentModel ( ) ;
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! " ) ;
// 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 ;
CreateAndAddWyciwygChannel ( ) ;
// Step 15.
SetReadyStateInternal ( nsIDocument : : READYSTATE_LOADING ,
/* updateTimingInformation = */ false ) ;
- - mWriteLevel ;
SetReadyStateInternal ( nsIDocument : : READYSTATE_LOADING ) ;
// Step 16 happened with step 14 above.
// 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 +1692,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