Align document.open() with overhauled spec.

This commit is contained in:
Fedor 2020-03-12 20:39:51 +03:00
parent cbc0c961db
commit 6b9ec91973
32 changed files with 644 additions and 633 deletions

View File

@ -11835,35 +11835,45 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
{ {
// Implements History.pushState and History.replaceState // Implements History.pushState and History.replaceState
// Here's what we do, roughly in the order specified by HTML5: // Here's what we do, roughly in the order specified by HTML5. The specific
// 1. Serialize aData using structured clone. // steps we are executing are at
// 2. If the third argument is present, // <https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate>
// a. Resolve the url, relative to the first script's base URL // and
// b. If (a) fails, raise a SECURITY_ERR // <https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps>.
// c. Compare the resulting absolute URL to the document's address. If // This function basically implements #dom-history-pushstate and
// any part of the URLs difer other than the <path>, <query>, and // UpdateURLAndHistory implements #url-and-history-update-steps.
// <fragment> components, raise a SECURITY_ERR and abort. //
// 3. If !aReplace: // A. Serialize aData using structured clone. This is #dom-history-pushstate
// step 5.
// B. If the third argument is present, #dom-history-pushstate step 7.
// 7.1. Resolve the url, relative to our document.
// 7.2. If (a) fails, raise a SECURITY_ERR
// 7.4. Compare the resulting absolute URL to the document's address. If
// any part of the URLs difer other than the <path>, <query>, and
// <fragment> components, raise a SECURITY_ERR and abort.
// C. If !aReplace, #url-and-history-update-steps steps 2.1-2.3:
// Remove from the session history all entries after the current entry, // Remove from the session history all entries after the current entry,
// as we would after a regular navigation, and save the current // as we would after a regular navigation, and save the current
// entry's scroll position (bug 590573). // entry's scroll position (bug 590573).
// 4. As apropriate, either add a state object entry to the session history // D. #url-and-history-update-steps step 2.4 or step 3. As apropriate,
// after the current entry with the following properties, or modify the // either add a state object entry to the session history after the
// current session history entry to set // current entry with the following properties, or modify the current
// session history entry to set
// a. cloned data as the state object, // a. cloned data as the state object,
// b. if the third argument was present, the absolute URL found in // b. if the third argument was present, the absolute URL found in
// step 2 // step 2
// Also clear the new history entry's POST data (see bug 580069). // Also clear the new history entry's POST data (see bug 580069).
// 5. If aReplace is false (i.e. we're doing a pushState instead of a // E. If aReplace is false (i.e. we're doing a pushState instead of a
// replaceState), notify bfcache that we've navigated to a new page. // replaceState), notify bfcache that we've navigated to a new page.
// 6. If the third argument is present, set the document's current address // F. If the third argument is present, set the document's current address
// to the absolute URL found in step 2. // to the absolute URL found in step B. This is
// #url-and-history-update-steps step 4.
// //
// It's important that this function not run arbitrary scripts after step 1 // It's important that this function not run arbitrary scripts after step 1
// and before completing step 5. For example, if a script called // and before completing step 5. For example, if a script called
// history.back() before we completed step 5, bfcache might destroy an // history.back() before we completed step 5, bfcache might destroy an
// active content viewer. Since EvictOutOfRangeContentViewers at the end of // active content viewer. Since EvictOutOfRangeContentViewers at the end of
// step 5 might run script, we can't just put a script blocker around the // step E might run script, we can't just put a script blocker around the
// critical section. // critical section.
// //
// Note that we completely ignore the aTitle parameter. // Note that we completely ignore the aTitle parameter.
@ -11883,7 +11893,9 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
nsCOMPtr<nsIDocument> document = GetDocument(); nsCOMPtr<nsIDocument> document = GetDocument();
NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
// Step 1: Serialize aData using structured clone. // Step A: Serialize aData using structured clone.
// https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate
// step 5.
nsCOMPtr<nsIStructuredCloneContainer> scContainer; nsCOMPtr<nsIStructuredCloneContainer> scContainer;
// scContainer->Init might cause arbitrary JS to run, and this code might // scContainer->Init might cause arbitrary JS to run, and this code might
@ -11926,7 +11938,9 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
NS_ENSURE_TRUE(scSize <= (uint32_t)maxStateObjSize, NS_ERROR_ILLEGAL_VALUE); NS_ENSURE_TRUE(scSize <= (uint32_t)maxStateObjSize, NS_ERROR_ILLEGAL_VALUE);
// Step 2: Resolve aURL // Step B: Resolve aURL.
// https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate
// step 7.
bool equalURIs = true; bool equalURIs = true;
nsCOMPtr<nsIURI> currentURI; nsCOMPtr<nsIURI> currentURI;
if (sURIFixup && mCurrentURI) { if (sURIFixup && mCurrentURI) {
@ -11935,12 +11949,11 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
} else { } else {
currentURI = mCurrentURI; currentURI = mCurrentURI;
} }
nsCOMPtr<nsIURI> oldURI = currentURI;
nsCOMPtr<nsIURI> newURI; nsCOMPtr<nsIURI> newURI;
if (aURL.Length() == 0) { if (aURL.Length() == 0) {
newURI = currentURI; newURI = currentURI;
} else { } else {
// 2a: Resolve aURL relative to mURI // 7.1: Resolve aURL relative to mURI
nsIURI* docBaseURI = document->GetDocBaseURI(); nsIURI* docBaseURI = document->GetDocBaseURI();
if (!docBaseURI) { if (!docBaseURI) {
@ -11956,12 +11969,12 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
rv = NS_NewURI(getter_AddRefs(newURI), aURL, charset.get(), docBaseURI); rv = NS_NewURI(getter_AddRefs(newURI), aURL, charset.get(), docBaseURI);
// 2b: If 2a fails, raise a SECURITY_ERR // 7.2: If 7.1 fails, raise a SECURITY_ERR
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
return NS_ERROR_DOM_SECURITY_ERR; return NS_ERROR_DOM_SECURITY_ERR;
} }
// 2c: Same-origin check. // 7.4 and 7.5: Same-origin check.
if (!nsContentUtils::URIIsLocalFile(newURI)) { if (!nsContentUtils::URIIsLocalFile(newURI)) {
// In addition to checking that the security manager says that // In addition to checking that the security manager says that
// the new URI has the same origin as our current URI, we also // the new URI has the same origin as our current URI, we also
@ -12012,18 +12025,36 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
} // end of same-origin check } // end of same-origin check
// Step 3: Create a new entry in the session history. This will erase // Step 8: call "URL and history update steps"
// all SHEntries after the new entry and make this entry the current rv = UpdateURLAndHistory(document, newURI, scContainer, aTitle, aReplace,
// one. This operation may modify mOSHE, which we need later, so we currentURI, equalURIs);
// keep a reference here. NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(mOSHE, NS_ERROR_FAILURE);
return NS_OK;
}
nsresult
nsDocShell::UpdateURLAndHistory(nsIDocument* aDocument, nsIURI* aNewURI,
nsIStructuredCloneContainer* aData,
const nsAString& aTitle, bool aReplace,
nsIURI* aCurrentURI, bool aEqualURIs)
{
// Implements
// https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps
// Step 2, if aReplace is false: Create a new entry in the session
// history. This will erase all SHEntries after the new entry and make this
// entry the current one. This operation may modify mOSHE, which we need
// later, so we keep a reference here.
NS_ENSURE_TRUE(mOSHE || aReplace, NS_ERROR_FAILURE);
nsCOMPtr<nsISHEntry> oldOSHE = mOSHE; nsCOMPtr<nsISHEntry> oldOSHE = mOSHE;
mLoadType = LOAD_PUSHSTATE; mLoadType = LOAD_PUSHSTATE;
nsCOMPtr<nsISHEntry> newSHEntry; nsCOMPtr<nsISHEntry> newSHEntry;
if (!aReplace) { if (!aReplace) {
// Save the current scroll position (bug 590573). // Step 2.
// Save the current scroll position (bug 590573). Step 2.3.
nscoord cx = 0, cy = 0; nscoord cx = 0, cy = 0;
GetCurScrollPos(ScrollOrientation_X, &cx); GetCurScrollPos(ScrollOrientation_X, &cx);
GetCurScrollPos(ScrollOrientation_Y, &cy); GetCurScrollPos(ScrollOrientation_Y, &cy);
@ -12034,10 +12065,10 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
// Since we're not changing which page we have loaded, pass // Since we're not changing which page we have loaded, pass
// true for aCloneChildren. // true for aCloneChildren.
rv = AddToSessionHistory(newURI, nullptr, nsresult rv = AddToSessionHistory(aNewURI, nullptr,
document->NodePrincipal(), // triggeringPrincipal aDocument->NodePrincipal(), // triggeringPrincipal
nullptr, true, nullptr, true,
getter_AddRefs(newSHEntry)); getter_AddRefs(newSHEntry));
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE); NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE);
@ -12060,15 +12091,26 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
mOSHE = newSHEntry; mOSHE = newSHEntry;
} else { } else {
// Step 3.
newSHEntry = mOSHE; newSHEntry = mOSHE;
newSHEntry->SetURI(newURI);
newSHEntry->SetOriginalURI(newURI); // Since we're not changing which page we have loaded, pass
if (!newSHEntry) {
nsresult rv = AddToSessionHistory(
aNewURI, nullptr,
aDocument->NodePrincipal(), // triggeringPrincipal
nullptr, true, getter_AddRefs(newSHEntry));
NS_ENSURE_SUCCESS(rv, rv);
mOSHE = newSHEntry;
}
newSHEntry->SetURI(aNewURI);
newSHEntry->SetOriginalURI(aNewURI);
newSHEntry->SetLoadReplace(false); newSHEntry->SetLoadReplace(false);
} }
// Step 4: Modify new/original session history entry and clear its POST // Step 2.4 and 3: Modify new/original session history entry and clear its
// data, if there is any. // POST data, if there is any.
newSHEntry->SetStateData(scContainer); newSHEntry->SetStateData(aData);
newSHEntry->SetPostData(nullptr); newSHEntry->SetPostData(nullptr);
// If this push/replaceState changed the document's current URI and the new // If this push/replaceState changed the document's current URI and the new
@ -12076,39 +12118,48 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
// SHEntry's URI was modified in this way by a push/replaceState call // SHEntry's URI was modified in this way by a push/replaceState call
// set URIWasModified to true for the current SHEntry (bug 669671). // set URIWasModified to true for the current SHEntry (bug 669671).
bool sameExceptHashes = true, oldURIWasModified = false; bool sameExceptHashes = true, oldURIWasModified = false;
newURI->EqualsExceptRef(currentURI, &sameExceptHashes); aNewURI->EqualsExceptRef(aCurrentURI, &sameExceptHashes);
oldOSHE->GetURIWasModified(&oldURIWasModified); // mOSHE might be null on replace. Only check if we're not replacing.
if (oldOSHE) {
oldOSHE->GetURIWasModified(&oldURIWasModified);
}
newSHEntry->SetURIWasModified(!sameExceptHashes || oldURIWasModified); newSHEntry->SetURIWasModified(!sameExceptHashes || oldURIWasModified);
// Step 5: If aReplace is false, indicating that we're doing a pushState // Step E as described at the top of AddState: If aReplace is false,
// rather than a replaceState, notify bfcache that we've added a page to // indicating that we're doing a pushState rather than a replaceState, notify
// the history so it can evict content viewers if appropriate. Otherwise // bfcache that we've added a page to the history so it can evict content
// call ReplaceEntry so that we notify nsIHistoryListeners that an entry // viewers if appropriate. Otherwise call ReplaceEntry so that we notify
// was replaced. // nsIHistoryListeners that an entry was replaced. We may not have a root
// session history if this call is coming from a document.open() in a docshell
// subtree that disables session history.
nsCOMPtr<nsISHistory> rootSH; nsCOMPtr<nsISHistory> rootSH;
GetRootSessionHistory(getter_AddRefs(rootSH)); GetRootSessionHistory(getter_AddRefs(rootSH));
NS_ENSURE_TRUE(rootSH, NS_ERROR_UNEXPECTED); NS_ENSURE_TRUE(rootSH, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsISHistoryInternal> internalSH = do_QueryInterface(rootSH); nsCOMPtr<nsISHistoryInternal> internalSH = do_QueryInterface(rootSH);
NS_ENSURE_TRUE(internalSH, NS_ERROR_UNEXPECTED); NS_ENSURE_TRUE(internalSH, NS_ERROR_UNEXPECTED);
nsresult rv;
if (rootSH) {
if (!aReplace) {
int32_t curIndex = -1;
rv = rootSH->GetIndex(&curIndex);
if (NS_SUCCEEDED(rv) && curIndex > -1) {
internalSH->EvictOutOfRangeContentViewers(curIndex);
}
} else {
nsCOMPtr<nsISHEntry> rootSHEntry = GetRootSHEntry(newSHEntry);
if (!aReplace) { int32_t index = -1;
int32_t curIndex = -1; rv = rootSH->GetIndexOfEntry(rootSHEntry, &index);
rv = rootSH->GetIndex(&curIndex); if (NS_SUCCEEDED(rv) && index > -1) {
if (NS_SUCCEEDED(rv) && curIndex > -1) { internalSH->ReplaceEntry(index, rootSHEntry);
internalSH->EvictOutOfRangeContentViewers(curIndex); }
}
} else {
nsCOMPtr<nsISHEntry> rootSHEntry = GetRootSHEntry(newSHEntry);
int32_t index = -1;
rv = rootSH->GetIndexOfEntry(rootSHEntry, &index);
if (NS_SUCCEEDED(rv) && index > -1) {
internalSH->ReplaceEntry(index, rootSHEntry);
} }
} }
// Step 6: If the document's URI changed, update document's URI and update // Step 4: If the document's URI changed, update document's URI and update
// global history. // global history.
// //
// We need to call FireOnLocationChange so that the browser's address bar // We need to call FireOnLocationChange so that the browser's address bar
@ -12121,35 +12172,35 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
// notification is allowed only when we know docshell is not loading a new // notification is allowed only when we know docshell is not loading a new
// document and it requires LOCATION_CHANGE_SAME_DOCUMENT flag. Otherwise, // document and it requires LOCATION_CHANGE_SAME_DOCUMENT flag. Otherwise,
// FireOnLocationChange(...) breaks security UI. // FireOnLocationChange(...) breaks security UI.
if (!equalURIs) { if (!aEqualURIs) {
document->SetDocumentURI(newURI); aDocument->SetDocumentURI(aNewURI);
// We can't trust SetCurrentURI to do always fire locationchange events // We can't trust SetCurrentURI to do always fire locationchange events
// when we expect it to, so we hack around that by doing it ourselves... // when we expect it to, so we hack around that by doing it ourselves...
SetCurrentURI(newURI, nullptr, false, LOCATION_CHANGE_SAME_DOCUMENT); SetCurrentURI(aNewURI, nullptr, false, LOCATION_CHANGE_SAME_DOCUMENT);
if (mLoadType != LOAD_ERROR_PAGE) { if (mLoadType != LOAD_ERROR_PAGE) {
FireDummyOnLocationChange(); FireDummyOnLocationChange();
} }
AddURIVisit(newURI, oldURI, oldURI, 0); AddURIVisit(aNewURI, aCurrentURI, aCurrentURI, 0);
// AddURIVisit doesn't set the title for the new URI in global history, // AddURIVisit doesn't set the title for the new URI in global history,
// so do that here. // so do that here.
if (mUseGlobalHistory && !UsePrivateBrowsing()) { if (mUseGlobalHistory && !UsePrivateBrowsing()) {
nsCOMPtr<IHistory> history = services::GetHistoryService(); nsCOMPtr<IHistory> history = services::GetHistoryService();
if (history) { if (history) {
history->SetURITitle(newURI, mTitle); history->SetURITitle(aNewURI, mTitle);
} else if (mGlobalHistory) { } else if (mGlobalHistory) {
mGlobalHistory->SetPageTitle(newURI, mTitle); mGlobalHistory->SetPageTitle(aNewURI, mTitle);
} }
} }
// Inform the favicon service that our old favicon applies to this new // Inform the favicon service that our old favicon applies to this new
// URI. // URI.
CopyFavicon(oldURI, newURI, document->NodePrincipal(), UsePrivateBrowsing()); CopyFavicon(aCurrentURI, aNewURI, aDocument->NodePrincipal(), UsePrivateBrowsing());
} else { } else {
FireDummyOnLocationChange(); FireDummyOnLocationChange();
} }
document->SetStateObject(scContainer); aDocument->SetStateObject(aData);
return NS_OK; return NS_OK;
} }

View File

@ -26,6 +26,7 @@ interface nsIChannel;
interface nsIContentViewer; interface nsIContentViewer;
interface nsIDOMEventTarget; interface nsIDOMEventTarget;
interface nsIDocShellLoadInfo; interface nsIDocShellLoadInfo;
interface nsIDocument;
interface nsIEditor; interface nsIEditor;
interface nsIEditingSession; interface nsIEditingSession;
interface nsISimpleEnumerator; interface nsISimpleEnumerator;
@ -35,6 +36,7 @@ interface nsISHEntry;
interface nsILayoutHistoryState; interface nsILayoutHistoryState;
interface nsISecureBrowserUI; interface nsISecureBrowserUI;
interface nsIScriptGlobalObject; interface nsIScriptGlobalObject;
interface nsIStructuredCloneContainer;
interface nsIDOMStorage; interface nsIDOMStorage;
interface nsIPrincipal; interface nsIPrincipal;
interface nsIWebBrowserPrint; interface nsIWebBrowserPrint;
@ -210,6 +212,30 @@ interface nsIDocShell : nsIDocShellTreeItem
void addState(in jsval aData, in DOMString aTitle, void addState(in jsval aData, in DOMString aTitle,
in DOMString aURL, in boolean aReplace); in DOMString aURL, in boolean aReplace);
/**
* Helper for addState and document.open that does just the
* history-manipulation guts.
*
* Arguments the spec defines:
*
* @param aDocument the document we're manipulating. This will get the new URI.
* @param aNewURI the new URI.
* @param aData The serialized state data. May be null.
* @param aTitle The new title. May be empty.
* @param aReplace whether this should replace the exising SHEntry.
*
* Arguments we need internally because deriving them from the
* others is a bit complicated:
*
* @param aCurrentURI the current URI we're working with. Might be null.
* @param aEqualURIs whether the two URIs involved are equal.
*/
[nostdcall]
void updateURLAndHistory(in nsIDocument aDocument, in nsIURI aNewURI,
in nsIStructuredCloneContainer aData, in AString aTitle,
in boolean aReplace, in nsIURI aCurrentURI,
in boolean aEqualURIs);
/** /**
* Creates a DocShellLoadInfo object that you can manipulate and then pass * Creates a DocShellLoadInfo object that you can manipulate and then pass
* to loadURI. * to loadURI.

View File

@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Bug 1379762</title>
</head>
<script type="text/just-data">
onunload = null; // enable bfcache
++opener.testCount;
onpageshow = function(e) {
opener.ok(!e.persisted, "Pageshow should not be coming from bfcache " + opener.testCount);
}
if (opener.testCount == 1) {
onload = function () {
setTimeout(function() {
document.write(testScript);
}, 0);
}
} else if (opener.testCount == 2) {
// Do this async, just in case.
setTimeout(function() {
history.back();
}, 0);
} else if (opener.testCount == 3) {
// Do this async, just in case.
setTimeout(function() {
history.forward();
}, 0);
} else if (opener.testCount == 4) {
onload = function() {
opener.nextTest();
window.close();
}
}
</script>
<script>
var data = document.querySelector("script[type='text/just-data']").textContent;
// Store the string that does all out work in a global variable, so we can
// get at it later.
var testScript = "<script>" + data + "</" + "script>";
document.write(testScript);
</script>
</html>

View File

@ -1,27 +1,16 @@
<html> <html>
<head> <head>
<script> <script>
function run() { function start() {
var length = history.length;
document.open(); document.open();
document.write("<h5 id='dynamic'>document.written content</h5>"); document.write("<h5 id='dynamic'>document.written content</h5>");
document.close(); document.close();
window.history.go(-1); opener.is(history.length, length,
"document.open/close should not change history");
opener.nextTest();
window.close();
} }
function start() {
if (++opener.testCount == 1) {
setTimeout(run, 0);
}
}
window.addEventListener("pageshow",
function() {
++opener.file_document_write_1_loadCount;
if (opener.file_document_write_1_loadCount == 2) {
opener.setTimeout("isTestDynamic()", 0);
}
opener.ok(opener.file_document_write_1_loadCount <= 2);
});
</script> </script>
</head> </head>
<body onload="start();"> <body onload="start();">

View File

@ -58,8 +58,8 @@ skip-if = (toolkit == 'android') || (!debug && (os == 'mac' || os == 'win')) # B
[test_reserved.html] [test_reserved.html]
skip-if = (toolkit == 'android') || (debug && e10s) #too slow on Android 4.3 aws only; bug 1030403; bug 1263213 for debug e10s skip-if = (toolkit == 'android') || (debug && e10s) #too slow on Android 4.3 aws only; bug 1030403; bug 1263213 for debug e10s
[test_sessionhistory.html] [test_sessionhistory.html]
skip-if = toolkit == 'android' #RANDOM skip-if = toolkit == 'android' #RANDOM on Android
support-files = file_bug1379762-1.html file_bug1379762-2.html support-files = file_bug1379762-1.html
[test_sibling-matching-parent.html] [test_sibling-matching-parent.html]
[test_sibling-off-domain.html] [test_sibling-off-domain.html]
[test_triggeringprincipal_frame_nav.html] [test_triggeringprincipal_frame_nav.html]

View File

@ -33,7 +33,6 @@ var testFiles =
"file_scrollRestoration.html", "file_scrollRestoration.html",
"file_bug1300461.html", "file_bug1300461.html",
"file_bug1379762-1.html", "file_bug1379762-1.html",
"file_bug1379762-2.html",
]; ];
var testCount = 0; // Used by the test files. var testCount = 0; // Used by the test files.
@ -51,15 +50,6 @@ function nextTest_() {
} }
} }
// Needed by file_document_write_1.html
window.file_document_write_1_loadCount = 0;
function isTestDynamic() {
var dyn = testWindow.document.getElementById("dynamic");
is(dyn, null, "Should have gone back to the static page!");
nextTest();
testWindow.close();
}
function nextTest() { function nextTest() {
setTimeout(nextTest_, 0); setTimeout(nextTest_, 0);
} }

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', 'ScreenOrientation.h',
'ScriptSettings.h', 'ScriptSettings.h',
'ShadowRoot.h', 'ShadowRoot.h',
'SimpleTreeIterator.h',
'StructuredCloneHolder.h', 'StructuredCloneHolder.h',
'StructuredCloneTags.h', 'StructuredCloneTags.h',
'StyleSheetList.h', 'StyleSheetList.h',

View File

@ -1968,22 +1968,10 @@ nsDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup)
} }
void void
nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup, nsDocument::DisconnectNodeTree() {
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;
// Delete references to sub-documents and kill the subdocument map, // 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; delete mSubDocuments;
mSubDocuments = nullptr; mSubDocuments = nullptr;
@ -2019,6 +2007,22 @@ nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
"After removing all children, there should be no root elem"); "After removing all children, there should be no root elem");
} }
mInUnlinkOrDeletion = oldVal; 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 // Reset our stylesheets
ResetStylesheetsToURI(aURI); ResetStylesheetsToURI(aURI);
@ -2029,6 +2033,8 @@ nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
mListenerManager = nullptr; mListenerManager = nullptr;
} }
DisconnectNodeTree();
// Release the stylesheets list. // Release the stylesheets list.
mDOMStyleSheets = nullptr; mDOMStyleSheets = nullptr;
@ -4506,18 +4512,6 @@ nsDocument::SetScriptGlobalObject(nsIScriptGlobalObject *aScriptGlobalObject)
mLayoutHistoryState = nullptr; mLayoutHistoryState = nullptr;
SetScopeObject(aScriptGlobalObject); SetScopeObject(aScriptGlobalObject);
mHasHadDefaultView = true; 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) { if (mAllowDNSPrefetch) {
nsCOMPtr<nsIDocShell> docShell(mDocumentContainer); nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
@ -9077,7 +9071,8 @@ nsDocument::CloneDocHelper(nsDocument* clone) const
} }
void void
nsDocument::SetReadyStateInternal(ReadyState rs) nsDocument::SetReadyStateInternal(ReadyState rs,
bool updateTimingInformation)
{ {
mReadyState = rs; mReadyState = rs;
if (rs == READYSTATE_UNINITIALIZED) { if (rs == READYSTATE_UNINITIALIZED) {
@ -9086,7 +9081,12 @@ nsDocument::SetReadyStateInternal(ReadyState rs)
// transition undetectable by Web content. // transition undetectable by Web content.
return; return;
} }
if (mTiming) {
if (updateTimingInformation && READYSTATE_LOADING == rs) {
mLoadingTimeStamp = mozilla::TimeStamp::Now();
}
if (updateTimingInformation && mTiming) {
switch (rs) { switch (rs) {
case READYSTATE_LOADING: case READYSTATE_LOADING:
mTiming->NotifyDOMLoading(nsIDocument::GetDocumentURI()); mTiming->NotifyDOMLoading(nsIDocument::GetDocumentURI());
@ -9102,10 +9102,6 @@ nsDocument::SetReadyStateInternal(ReadyState rs)
break; 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 = RefPtr<AsyncEventDispatcher> asyncDispatcher =
new AsyncEventDispatcher(this, NS_LITERAL_STRING("readystatechange"), new AsyncEventDispatcher(this, NS_LITERAL_STRING("readystatechange"),

View File

@ -704,7 +704,11 @@ public:
virtual void BeginLoad() override; virtual void BeginLoad() override;
virtual void EndLoad() 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, virtual void ContentStateChanged(nsIContent* aContent,
mozilla::EventStates aStateMask) mozilla::EventStates aStateMask)
@ -916,6 +920,14 @@ public:
UpdateFrameRequestCallbackSchedulingState(); UpdateFrameRequestCallbackSchedulingState();
} }
void SetLoadEventFiring(bool aFiring) override { mLoadEventFiring = aFiring; }
bool SkipLoadEventAfterClose() override {
bool skip = mSkipLoadEventAfterClose;
mSkipLoadEventAfterClose = false;
return skip;
}
virtual nsIDocument* GetTemplateContentsOwner() override; virtual nsIDocument* GetTemplateContentsOwner() override;
NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsDocument, NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsDocument,
@ -1255,6 +1267,11 @@ protected:
*/ */
Element* GetTitleElement(); Element* GetTitleElement();
/**
* Perform tree disconnection needed by ResetToURI and document.open()
*/
void DisconnectNodeTree();
public: public:
// Get our title // Get our title
virtual void GetTitle(nsString& aTitle) override; virtual void GetTitle(nsString& aTitle) override;
@ -1458,6 +1475,20 @@ public:
// additional sheets and sheets from the nsStyleSheetService. // additional sheets and sheets from the nsStyleSheetService.
bool mStyleSetFilled:1; 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 mPendingFullscreenRequests;
uint8_t mXMLDeclarationBits; uint8_t mXMLDeclarationBits;
@ -1615,11 +1646,6 @@ private:
// Set to true when the document is possibly controlled by the ServiceWorker. // Set to true when the document is possibly controlled by the ServiceWorker.
// Used to prevent multiple requests to ServiceWorkerManager. // Used to prevent multiple requests to ServiceWorkerManager.
bool mMaybeServiceWorkerControlled; bool mMaybeServiceWorkerControlled;
#ifdef DEBUG
public:
bool mWillReparent;
#endif
}; };
class nsDocumentOnStack class nsDocumentOnStack

View File

@ -909,10 +909,6 @@ public:
*/ */
nsresult GetSrcdocData(nsAString& aSrcdocData); nsresult GetSrcdocData(nsAString& aSrcdocData);
bool DidDocumentOpen() {
return mDidDocumentOpen;
}
already_AddRefed<mozilla::dom::AnonymousContent> already_AddRefed<mozilla::dom::AnonymousContent>
InsertAnonymousContent(mozilla::dom::Element& aElement, InsertAnonymousContent(mozilla::dom::Element& aElement,
mozilla::ErrorResult& aError); mozilla::ErrorResult& aError);
@ -1448,7 +1444,7 @@ public:
virtual void EndLoad() = 0; virtual void EndLoad() = 0;
enum ReadyState { READYSTATE_UNINITIALIZED = 0, READYSTATE_LOADING = 1, READYSTATE_INTERACTIVE = 3, READYSTATE_COMPLETE = 4}; 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() ReadyState GetReadyStateEnum()
{ {
return mReadyState; return mReadyState;
@ -2186,6 +2182,19 @@ public:
mAllowXULXBL = eTriTrue; 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 * Returns the template content owner document that owns the content of
* HTMLTemplateElement. * HTMLTemplateElement.
@ -3146,11 +3155,6 @@ protected:
// Whether the document was created by a srcdoc iframe. // Whether the document was created by a srcdoc iframe.
bool mIsSrcdocDocument : 1; 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 // Whether this document has a display document and thus is considered to
// be a resource document. Normally this is the same as !!mDisplayDocument, // be a resource document. Normally this is the same as !!mDisplayDocument,
// but mDisplayDocument is cleared during Unlink. mHasDisplayDocument is // but mDisplayDocument is cleared during Unlink. mHasDisplayDocument is

View File

@ -1549,27 +1549,6 @@ AdoptNodeIntoOwnerDoc(nsINode *aParent, nsINode *aNode)
return NS_OK; 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 static nsresult
ReparentWrappersInSubtree(nsIContent* aRoot) ReparentWrappersInSubtree(nsIContent* aRoot)
{ {
@ -1631,9 +1610,6 @@ nsINode::doInsertChildAt(nsIContent* aKid, uint32_t aIndex,
if (OwnerDoc() != aKid->OwnerDoc()) { if (OwnerDoc() != aKid->OwnerDoc()) {
rv = AdoptNodeIntoOwnerDoc(this, aKid); rv = AdoptNodeIntoOwnerDoc(this, aKid);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
} else if (OwnerDoc()->DidDocumentOpen()) {
rv = CheckForOutdatedParent(this, aKid);
NS_ENSURE_SUCCESS(rv, rv);
} }
uint32_t childCount = aChildArray.ChildCount(); uint32_t childCount = aChildArray.ChildCount();
@ -2481,11 +2457,6 @@ nsINode::ReplaceOrInsertBefore(bool aReplace, nsINode* aNewChild,
if (aError.Failed()) { if (aError.Failed()) {
return nullptr; 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 // test that a document can be framed under a javascript: URL opened by the
// same site as the frame // 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 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 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(); var win = window.open();
win.onload = function() { win.location.href = "javascript:document.open(); onload = opener.frameInJSURILoaded.bind(null, window); document.write('"+html+"');document.close();";
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();";
} }
// test that a document can be framed under a data: URL opened by the // 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 // NS_IF_RELEASE because we might have been unlinked before
nsISupports* ptr = GetISupports(); nsISupports* ptr = GetISupports();
NS_IF_RELEASE(ptr); // Clear mPtrBits before the release to prevent reentrance.
mPtrBits = 0; mPtrBits = 0;
NS_IF_RELEASE(ptr);
} }
uintptr_t mPtrBits; uintptr_t mPtrBits;

View File

@ -166,11 +166,11 @@ EventListenerManager::~EventListenerManager()
// XXX azakai: Is there any reason to not just call Disconnect // XXX azakai: Is there any reason to not just call Disconnect
// from right here, if not previously called? // from right here, if not previously called?
NS_ASSERTION(!mTarget, "didn't call Disconnect"); NS_ASSERTION(!mTarget, "didn't call Disconnect");
RemoveAllListeners(); RemoveAllListenersSilently();
} }
void void
EventListenerManager::RemoveAllListeners() EventListenerManager::RemoveAllListenersSilently()
{ {
if (mClearingListeners) { if (mClearingListeners) {
return; return;
@ -1329,7 +1329,7 @@ void
EventListenerManager::Disconnect() EventListenerManager::Disconnect()
{ {
mTarget = nullptr; mTarget = nullptr;
RemoveAllListeners(); RemoveAllListenersSilently();
} }
void void
@ -1734,6 +1734,21 @@ EventListenerManager::IsApzAwareEvent(nsIAtom* aEvent)
return false; 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> already_AddRefed<nsIScriptGlobalObject>
EventListenerManager::GetScriptGlobalAndDocument(nsIDocument** aDoc) EventListenerManager::GetScriptGlobalAndDocument(nsIDocument** aDoc)
{ {

View File

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

View File

@ -15,6 +15,7 @@
#include "nsPrintfCString.h" #include "nsPrintfCString.h"
#include "nsReadableUtils.h" #include "nsReadableUtils.h"
#include "nsUnicharUtils.h" #include "nsUnicharUtils.h"
#include "nsIDocumentLoader.h"
#include "nsIHTMLContentSink.h" #include "nsIHTMLContentSink.h"
#include "nsIXMLContentSink.h" #include "nsIXMLContentSink.h"
#include "nsHTMLParts.h" #include "nsHTMLParts.h"
@ -84,6 +85,7 @@
#include "mozilla/dom/EncodingUtils.h" #include "mozilla/dom/EncodingUtils.h"
#include "mozilla/dom/FallbackEncoding.h" #include "mozilla/dom/FallbackEncoding.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/LoadInfo.h" #include "mozilla/LoadInfo.h"
#include "nsIEditingSession.h" #include "nsIEditingSession.h"
#include "nsIEditor.h" #include "nsIEditor.h"
@ -107,12 +109,14 @@
#include "nsIImageDocument.h" #include "nsIImageDocument.h"
#include "mozilla/dom/HTMLBodyElement.h" #include "mozilla/dom/HTMLBodyElement.h"
#include "mozilla/dom/HTMLDocumentBinding.h" #include "mozilla/dom/HTMLDocumentBinding.h"
#include "mozilla/dom/SimpleTreeIterator.h"
#include "nsCharsetSource.h" #include "nsCharsetSource.h"
#include "nsIStringBundle.h" #include "nsIStringBundle.h"
#include "nsDOMClassInfo.h" #include "nsDOMClassInfo.h"
#include "nsFocusManager.h" #include "nsFocusManager.h"
#include "nsIFrame.h" #include "nsIFrame.h"
#include "nsIContent.h" #include "nsIContent.h"
#include "nsIStructuredCloneContainer.h"
#include "nsLayoutStylesheetCache.h" #include "nsLayoutStylesheetCache.h"
#include "mozilla/StyleSheet.h" #include "mozilla/StyleSheet.h"
#include "mozilla/StyleSheetInlines.h" #include "mozilla/StyleSheetInlines.h"
@ -842,6 +846,24 @@ nsHTMLDocument::EndLoad()
if (turnOnEditing) { if (turnOnEditing) {
EditingStateChanged(); 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 void
@ -1410,19 +1432,21 @@ already_AddRefed<nsIDocument>
nsHTMLDocument::Open(JSContext* cx, nsHTMLDocument::Open(JSContext* cx,
const nsAString& aType, const nsAString& aType,
const nsAString& aReplace, const nsAString& aReplace,
ErrorResult& rv) ErrorResult& aError)
{ {
// Implements the "When called with two arguments (or fewer)" steps here: // Implements
// https://html.spec.whatwg.org/multipage/webappapis.html#opening-the-input-stream // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
NS_ASSERTION(nsContentUtils::CanCallerAccess(static_cast<nsIDOMHTMLDocument*>(this)), NS_ASSERTION(nsContentUtils::CanCallerAccess(static_cast<nsIDOMHTMLDocument*>(this)),
"XOW should have caught this!"); "XOW should have caught this!");
// Step 1 - Throw if we're the wrong type of document.
if (!IsHTMLDocument() || mDisableDocWrite || !IsMasterDocument()) { if (!IsHTMLDocument() || mDisableDocWrite || !IsMasterDocument()) {
// No calling document.open() on XHTML aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr; return nullptr;
} }
// Set up the content type for insertion
nsAutoCString contentType; nsAutoCString contentType;
contentType.AssignLiteral("text/html"); contentType.AssignLiteral("text/html");
@ -1435,51 +1459,7 @@ nsHTMLDocument::Open(JSContext* cx,
contentType.AssignLiteral("text/plain"); contentType.AssignLiteral("text/plain");
} }
// If we already have a parser we ignore the document.open call. // Step 3 - Get the entryDocument for security checks
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).
nsCOMPtr<nsIDocument> callerDoc = GetEntryDocument(); nsCOMPtr<nsIDocument> callerDoc = GetEntryDocument();
if (!callerDoc) { if (!callerDoc) {
// If we're called from C++ or in some other way without an originating // 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 // change the principals of a document for security reasons we'll have to
// refuse to go ahead with this call. // refuse to go ahead with this call.
rv.Throw(NS_ERROR_DOM_SECURITY_ERR); aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr; return nullptr;
} }
// Grab a reference to the calling documents security info (if any) // Step 4 - Throw if we're not same-origin
// and URIs as they may be lost in the call to Reset(). if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
nsCOMPtr<nsISupports> securityInfo = callerDoc->GetSecurityInfo(); aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
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);
return nullptr; return nullptr;
} }
// Stop current loads targeted at the window this document is in. // Step 5 - If we have an active parser, abort with no-op
if (mScriptGlobalObject) { if (mParser || mParserAborted) {
nsCOMPtr<nsIContentViewer> cv; nsCOMPtr<nsIDocument> ret = this;
shell->GetContentViewer(getter_AddRefs(cv)); return ret.forget();
}
if (cv) {
bool okToUnload; // Step 6 - Check if document.open() is called during unload
if (NS_SUCCEEDED(cv->PermitUnload(&okToUnload)) && !okToUnload) { nsCOMPtr<nsIDocShell> shell(mDocumentContainer);
// We don't want to unload, so stop here, but don't throw an if (shell) {
// exception. bool inUnload;
nsCOMPtr<nsIDocument> ret = this; shell->GetIsInUnload(&inUnload);
return ret.forget(); if (inUnload) {
} 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 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)); nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(shell));
webnav->Stop(nsIWebNavigation::STOP_NETWORK); webnav->Stop(nsIWebNavigation::STOP_NETWORK);
@ -1560,189 +1512,121 @@ nsHTMLDocument::Open(JSContext* cx,
EnsureOnloadBlocker(); EnsureOnloadBlocker();
} }
// The open occurred after the document finished loading. // Step 8 - Clear all event listeners out of our DOM tree
// So we reset the document and then reinitialize it. for (nsINode* node : SimpleTreeIterator(*this)) {
nsCOMPtr<nsIChannel> channel; if (EventListenerManager* elm = node->GetExistingListenerManager()) {
nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup); elm->RemoveAllListeners();
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);
} }
} }
// Before we reset the doc notify the globalwindow of the change, // Step 9 - Clear event listeners from our window, if we have one.
// but only if we still have a window (i.e. our window object the //
// current inner window in our outer window). // 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
// Hold onto ourselves on the offchance that we're down to one ref // document", so we can't go through GetWindow(), because it might forward to
nsCOMPtr<nsIDocument> kungFuDeathGrip = this; // the wrong inner.
if (nsPIDOMWindowInner* win = GetInnerWindow()) {
if (nsPIDOMWindowInner *window = GetInnerWindow()) { if (win->GetExtantDoc() == this) {
// Remember the old scope in case the call to SetNewDocument changes it. if (EventListenerManager* elm =
nsCOMPtr<nsIScriptGlobalObject> oldScope(do_QueryReferent(mScopeObject)); nsGlobalWindow::Cast(win)->GetExistingListenerManager()) {
elm->RemoveAllListeners();
#ifdef DEBUG }
bool willReparent = mWillReparent;
mWillReparent = true;
nsDocument* templateContentsOwner =
static_cast<nsDocument*>(mTemplateContentsOwner.get());
if (templateContentsOwner) {
templateContentsOwner->mWillReparent = true;
} }
#endif }
// Per spec, we pass false here so that a new Window is created. // Step 10 - Remove all of our DOM children without firing any mutation events.
rv = window->SetNewDocument(this, nullptr, DisconnectNodeTree();
/* aForceReuseInnerWindow */ false);
if (rv.Failed()) { // --- 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; return nullptr;
} }
#ifdef DEBUG // And use the security info of the caller document as well, since
if (templateContentsOwner) { // it's the thing providing our data.
templateContentsOwner->mWillReparent = willReparent; mSecurityInfo = callerDoc->GetSecurityInfo();
}
mWillReparent = willReparent; // This is not mentioned in the spec, but that's probably a spec bug.
#endif // See <https://github.com/whatwg/html/issues/4299>.
// Since our URL may be changing away from about:blank here, we really want
// Now make sure we're not flagged as the initial document anymore, now // to unset this flag on any document.open(), since only about:blank can be
// that we've had stuff done to us. From now on, if anyone tries to // an initial document.
// document.open() us, they get a new inner window.
SetIsInitialDocument(false); SetIsInitialDocument(false);
nsCOMPtr<nsIScriptGlobalObject> newScope(do_QueryReferent(mScopeObject)); // And let our docloader know that it will need to track our load event.
JS::Rooted<JSObject*> wrapper(cx, GetWrapper()); nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
if (oldScope && newScope != oldScope && wrapper) { }
JSAutoCompartment ac(cx, wrapper);
rv = mozilla::dom::ReparentWrapper(cx, wrapper);
if (rv.Failed()) {
return nullptr;
}
// Also reparent the template contents owner document // Step 12
// because its global is set to the same as this document. mSkipLoadEventAfterClose = mLoadEventFiring;
if (mTemplateContentsOwner) {
JS::Rooted<JSObject*> contentsOwnerWrapper(cx,
mTemplateContentsOwner->GetWrapper());
if (contentsOwnerWrapper) {
rv = mozilla::dom::ReparentWrapper(cx, contentsOwnerWrapper);
if (rv.Failed()) {
return nullptr;
}
}
}
}
}
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 // Step 13 - Set our compatibility mode to standards.
Reset(channel, group); SetCompatibilityMode(eCompatibility_FullStandards);
if (baseURI) {
mDocumentBaseURI = baseURI;
}
// Store the security info of the caller now that we're done
// resetting the document.
mSecurityInfo = securityInfo;
// Step 14 - Create a new parser associated with document.
// This also does step 16 implicitly.
mParserAborted = false; mParserAborted = false;
mParser = nsHtml5Module::NewHtml5Parser(); mParser = nsHtml5Module::NewHtml5Parser();
nsHtml5Module::Initialize(mParser, this, uri, shell, channel); nsHtml5Module::Initialize(mParser, this, nsIDocument::GetDocumentURI(), shell, nullptr);
if (mReferrerPolicySet) { if (mReferrerPolicySet) {
// CSP may have set the referrer policy, so a speculative parser should // CSP may have set the referrer policy, so a speculative parser should
// start with the new referrer policy. // start with the new referrer policy.
nsHtml5TreeOpExecutor* executor = nullptr; nsHtml5TreeOpExecutor* executor = nullptr;
executor = static_cast<nsHtml5TreeOpExecutor*> (mParser->GetContentSink()); executor = static_cast<nsHtml5TreeOpExecutor*>(mParser->GetContentSink());
if (executor && mReferrerPolicySet) { 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() if (shell) {
SetContentTypeInternal(contentType); // 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 nsCOMPtr<nsIContentViewer> cv;
// out of band document.write() shell->GetContentViewer(getter_AddRefs(cv));
shell->PrepareForNewContentModel(); if (cv) {
cv->LoadStart(this);
// 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);
} }
// Add a wyciwyg channel request into the document load group // Step 15.
NS_ASSERTION(!mWyciwygChannel, "nsHTMLDocument::Open(): wyciwyg " SetReadyStateInternal(nsIDocument::READYSTATE_LOADING,
"channel already exists!"); /* updateTimingInformation = */ false);
// In case the editor is listening and will see the new channel // Step 16 happened with step 14 above.
// 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 17.
nsCOMPtr<nsIDocument> ret = this;
--mWriteLevel; return ret.forget();
}
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();
}
NS_IMETHODIMP NS_IMETHODIMP
nsHTMLDocument::Clear() nsHTMLDocument::Clear()
@ -1806,15 +1690,6 @@ nsHTMLDocument::Close(ErrorResult& rv)
if (GetShell()) { if (GetShell()) {
FlushPendingNotifications(Flush_Layout); 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 void

View File

@ -529,7 +529,6 @@ skip-if = toolkit == 'android' # plugins not supported
[test_bug196523.html] [test_bug196523.html]
[test_bug199692.html] [test_bug199692.html]
skip-if = toolkit == 'android' #bug 811644 skip-if = toolkit == 'android' #bug 811644
[test_bug172261.html]
[test_bug255820.html] [test_bug255820.html]
[test_bug259332.html] [test_bug259332.html]
[test_bug311681.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", is(document.characterSet, "UTF-8",
"Unexpected character set for our document"); "Unexpected character set for our document");
var testsLeft = 4; var testsLeft = 3;
function testFinished() { function testFinished() {
--testsLeft; --testsLeft;
@ -42,29 +42,11 @@ function charsetTestFinished(id, doc, charsetTarget) {
testFinished(); 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() { function f3Continue() {
var doc = $("f3").contentDocument; var doc = $("f3").contentDocument;
is(doc.defaultView.getComputedStyle(doc.body, "").color, "rgb(0, 180, 0)", is(doc.defaultView.getComputedStyle(doc.body, "").color, "rgb(0, 180, 0)",
"Wrong color before reload"); "Wrong color");
$("f3"). charsetTestFinished('f3', doc, "UTF-8");
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();
} }
function runTest() { function runTest() {
@ -74,12 +56,7 @@ function runTest() {
doc.open(); doc.open();
doc.write('<html></html>'); doc.write('<html></html>');
doc.close(); doc.close();
is(doc.characterSet, "UTF-8", charsetTestFinished("f1", doc, "UTF-8");
"Unexpected character set for first frame after write");
$("f1").
setAttribute("onload",
"charsetTestFinished('f1', this.contentDocument, 'UTF-8')");
$("f1").contentWindow.location.reload();
doc = $("f2").contentDocument; doc = $("f2").contentDocument;
is(doc.characterSet, "UTF-8", is(doc.characterSet, "UTF-8",
@ -96,12 +73,11 @@ function runTest() {
"Unexpected character set for second frame after write"); "Unexpected character set for second frame after write");
$("f2"). $("f2").
setAttribute("onload", setAttribute("onload",
"charsetTestFinished('f2', this.contentDocument, 'UTF-8');" + "charsetTestFinished('f2', this.contentDocument, 'UTF-8');");
"f2Continue()");
doc = $("f3").contentDocument; doc = $("f3").contentDocument;
is(doc.characterSet, "UTF-8", is(doc.characterSet, "UTF-8",
"Unexpected initial character set for first frame"); "Unexpected initial character set for third frame");
doc.open(); doc.open();
var str = '<html><head>'; var str = '<html><head>';
str += '<style>body { color: rgb(255, 0, 0) }</style>'; str += '<style>body { color: rgb(255, 0, 0) }</style>';
@ -111,7 +87,7 @@ function runTest() {
doc.write(str); doc.write(str);
doc.close(); doc.close();
is(doc.characterSet, "UTF-8", 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()"); $("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"); is(testResult, "undefined", "Props on new window's child should go away when loading");
break; break;
case 6: 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; break;
case 7: case 7:
is(testResult, "7", "Props on different-domain window opened from different-domain new window can stay"); is(testResult, "7", "Props on different-domain window opened from different-domain new window can stay");

View File

@ -43,7 +43,7 @@ load 732870.html
load 751995.html load 751995.html
load 761831.html load 761831.html
asserts(0-1) load 752038.html # We may hit bug 645229 here. asserts(0-1) load 752038.html # We may hit bug 645229 here.
asserts(1) load 753162.html # We hit bug 675518 or bug 680086 here. load 753162.html
load 754311.html load 754311.html
asserts(0-1) load 786142.html # We may hit bug 645229 here. asserts(0-1) load 786142.html # We may hit bug 645229 here.
load 797583.html load 797583.html

View File

@ -998,6 +998,9 @@ nsDocumentViewer::LoadComplete(nsresult aStatus)
// will depend on whether it's cached! // will depend on whether it's cached!
if(window && if(window &&
(NS_SUCCEEDED(aStatus) || aStatus == NS_ERROR_PARSED_DATA_CACHED)) { (NS_SUCCEEDED(aStatus) || aStatus == NS_ERROR_PARSED_DATA_CACHED)) {
// If this code changes, the code in nsDocLoader::DocLoaderIsEmpty
// that fires load events for document.open() cases might need to
// be updated too.
nsEventStatus status = nsEventStatus_eIgnore; nsEventStatus status = nsEventStatus_eIgnore;
WidgetEvent event(true, eLoad); WidgetEvent event(true, eLoad);
event.mFlags.mBubbles = false; event.mFlags.mBubbles = false;
@ -1063,7 +1066,9 @@ nsDocumentViewer::LoadComplete(nsresult aStatus)
MakeUnique<DocLoadingTimelineMarker>("document::Load")); MakeUnique<DocLoadingTimelineMarker>("document::Load"));
} }
d->SetLoadEventFiring(true);
EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status); EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
d->SetLoadEventFiring(false);
if (timing) { if (timing) {
timing->NotifyLoadEventEnd(); timing->NotifyLoadEventEnd();
} }

View File

@ -19,7 +19,6 @@ function boom() {
setTimeout(function() { setTimeout(function() {
var frameDoc = document.querySelector("iframe").contentDocument; var frameDoc = document.querySelector("iframe").contentDocument;
frameDoc.write("3"); frameDoc.write("3");
frameDoc.defaultView.history.back();
requestAnimationFrame(function() { requestAnimationFrame(function() {
popup.close(); popup.close();
ok(true, "Didn't crash"); ok(true, "Didn't crash");

View File

@ -33,24 +33,30 @@ function textChildren(node) {
return s; return s;
} }
var f, d;
function tick() { function tick() {
runNumber++; runNumber++;
var f = document.getElementsByTagName("iframe")[0]; f = document.getElementsByTagName("iframe")[0];
var d = f.contentDocument; d = f.contentDocument;
if (runNumber == 1) { if (runNumber == 1) {
d.open(); frames[1].setTimeout(`
f.addEventListener("load", tick); var d = parent.d;
d.write("X"); var f = parent.f;
d.write("\u003cscript>document.write('Y');\u003c/script>"); d.open();
d.write("Z"); f.addEventListener("load", parent.tick);
d.close(); d.write("X");
d.write("\u003cscript>document.write('Y');\u003c/script>");
d.write("Z");
d.close();
`);
return; return;
} }
if (runNumber == 2) { if (runNumber == 2) {
var text = textChildren(d.body); var text = textChildren(d.body);
is(text, "XYZ", "Wrong text before reload."); is(text, "ABC", "Wrong text before reload.");
f.contentWindow.location.reload(); f.contentWindow.location.reload();
return; return;
} }
@ -63,10 +69,20 @@ function tick() {
} }
} }
// We want to trigger a document.open/write with a different window as the
// entry global. Let's give that window a blob URL so we don't have to set up
// extra helper files.
var blob = new Blob(["ABC"], { type: "text/html" });
var blobURL = URL.createObjectURL(blob);
</script> </script>
</pre> </pre>
<div id="content" style="display: none"> <div id="content" style="display: none">
<iframe></iframe> <iframe></iframe>
<iframe></iframe>
</div> </div>
<script>
document.querySelectorAll("iframe")[1].src = blobURL;
</script>
</body> </body>
</html> </html>

View File

@ -25,7 +25,7 @@
function afterNavigationTest() function afterNavigationTest()
{ {
isSecurityState("broken", "security still broken after navigation"); isSecurityState("secure", "when we navigate back, we're loading our secure page again and not loading an insecure script, so our security state is secure");
finish(); finish();
} }

View File

@ -5,11 +5,8 @@
opener.pages.push(2); opener.pages.push(2);
onload = function() { onload = function() {
setTimeout(function() { setTimeout(function() {
document.write("<!doctype html>3<script>opener.pages.push(3); if(!opener.started) {opener.started = true; history.go(-1);} opener.start_test_wait();<\/script>"); document.write("<!doctype html>3<script>opener.pages.push(3); if(!opener.started) {opener.started = true; history.go(-1);}<\/script>");
document.close(); document.close();
if (opener.started) {
opener.start_test_wait();
}
}, 100); }, 100);
} }
</script> </script>

View File

@ -11,12 +11,11 @@
function() { function() {
check_result = t.step_func( check_result = t.step_func(
function() { function() {
if (pages.length < 4) { if (pages.length < 3) {
setTimeout(check_result, 500); setTimeout(check_result, 500);
return return
} }
//The pass condition here is based on the idea that the spec is wrong and browsers are right assert_array_equals(pages, [2, 3, 1], "Pages opened during history navigation");
assert_array_equals(pages, [2, 3, 2, 3], "Pages opened during history navigation");
t.done(); t.done();
} }
) )

View File

@ -3,11 +3,16 @@
<script> <script>
function f() { function f() {
opener.postMessage("original", "*"); opener.postMessage("original", "*");
if (opener.data.length >= 2) {
// If we proceed here, then our document.write will be racing with the
// setTimeout in our opener. Just stop.
return;
}
setTimeout(function () { setTimeout(function () {
document.open(); document.open();
document.write("<!doctype html>2<script>opener.postMessage('written', '*');<\/script>"); document.write("<!doctype html>2<script>opener.postMessage('written', '*');<\/script>");
document.close(); document.close();
}), 100; });
} }
window.onload = f window.onload = f

View File

@ -11,11 +11,11 @@ var data = [];
window.onmessage = t.step_func(function(e) { window.onmessage = t.step_func(function(e) {
data.push(e.data); data.push(e.data);
if (data.length < 3) { if (data.length == 2) {
win.location.reload(); win.location.reload();
} else { } else if (data.length >= 3) {
setTimeout(t.step_func(function() { setTimeout(t.step_func(function() {
assert_array_equals(data, ["original", "written", "written"]); assert_array_equals(data, ["original", "written", "original"]);
t.done(); t.done();
}), 500); }), 500);
} }

View File

@ -4,6 +4,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nspr.h" #include "nspr.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/Logging.h" #include "mozilla/Logging.h"
#include "nsDocLoader.h" #include "nsDocLoader.h"
@ -23,6 +25,7 @@
#include "nsQueryObject.h" #include "nsQueryObject.h"
#include "nsIDOMWindow.h" #include "nsIDOMWindow.h"
#include "nsPIDOMWindow.h"
#include "nsIStringBundle.h" #include "nsIStringBundle.h"
#include "nsIScriptSecurityManager.h" #include "nsIScriptSecurityManager.h"
@ -36,7 +39,10 @@
#include "nsIAsyncVerifyRedirectCallback.h" #include "nsIAsyncVerifyRedirectCallback.h"
using mozilla::DebugOnly; using mozilla::DebugOnly;
using mozilla::eLoad;
using mozilla::EventDispatcher;
using mozilla::LogLevel; using mozilla::LogLevel;
using mozilla::WidgetEvent;
static NS_DEFINE_CID(kThisImplCID, NS_THIS_DOCLOADER_IMPL_CID); static NS_DEFINE_CID(kThisImplCID, NS_THIS_DOCLOADER_IMPL_CID);
@ -114,7 +120,8 @@ nsDocLoader::nsDocLoader()
mIsLoadingDocument(false), mIsLoadingDocument(false),
mIsRestoringDocument(false), mIsRestoringDocument(false),
mDontFlushLayout(false), mDontFlushLayout(false),
mIsFlushingLayout(false) mIsFlushingLayout(false),
mDocumentOpenedButNotLoaded(false)
{ {
ClearInternalProgress(); ClearInternalProgress();
@ -289,7 +296,7 @@ nsDocLoader::IsBusy()
} }
/* Is this document loader busy? */ /* Is this document loader busy? */
if (!mIsLoadingDocument) { if (!IsBlockingLoadEvent()) {
return false; return false;
} }
@ -414,6 +421,7 @@ nsDocLoader::OnStartRequest(nsIRequest *request, nsISupports *aCtxt)
if (!mIsLoadingDocument && (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)) { if (!mIsLoadingDocument && (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)) {
bJustStartedLoading = true; bJustStartedLoading = true;
mIsLoadingDocument = true; mIsLoadingDocument = true;
mDocumentOpenedButNotLoaded = false;
ClearInternalProgress(); // only clear our progress if we are starting a new load.... ClearInternalProgress(); // only clear our progress if we are starting a new load....
} }
@ -480,10 +488,11 @@ nsDocLoader::OnStopRequest(nsIRequest *aRequest,
mLoadGroup->GetActiveCount(&count); mLoadGroup->GetActiveCount(&count);
MOZ_LOG(gDocLoaderLog, LogLevel::Debug, MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
("DocLoader:%p: OnStopRequest[%p](%s) status=%x mIsLoadingDocument=%s, %u active URLs", ("DocLoader:%p: OnStopRequest[%p](%s) status=%x"
"mIsLoadingDocument=%s, mDocumentOpenedButNotLoaded=%s, %u active URLs",
this, aRequest, name.get(), this, aRequest, name.get(),
aStatus, (mIsLoadingDocument ? "true" : "false"), aStatus, (mIsLoadingDocument ? "true" : "false"),
count)); (mDocumentOpenedButNotLoaded ? "true" : "false"), count));
} }
bool bFireTransferring = false; bool bFireTransferring = false;
@ -598,10 +607,9 @@ nsDocLoader::OnStopRequest(nsIRequest *aRequest,
RemoveRequestInfo(aRequest); RemoveRequestInfo(aRequest);
// //
// Only fire the DocLoaderIsEmpty(...) if the document loader has initiated a // Only fire the DocLoaderIsEmpty(...) if we may need to fire onload.
// load. This will handle removing the request from our hashtable as needed.
// //
if (mIsLoadingDocument) { if (IsBlockingLoadEvent()) {
nsCOMPtr<nsIDocShell> ds = do_QueryInterface(static_cast<nsIRequestObserver*>(this)); nsCOMPtr<nsIDocShell> ds = do_QueryInterface(static_cast<nsIRequestObserver*>(this));
bool doNotFlushLayout = false; bool doNotFlushLayout = false;
if (ds) { if (ds) {
@ -647,7 +655,7 @@ NS_IMETHODIMP nsDocLoader::GetDocumentChannel(nsIChannel ** aChannel)
void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout) void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout)
{ {
if (mIsLoadingDocument) { if (IsBlockingLoadEvent()) {
/* In the unimagineably rude circumstance that onload event handlers /* In the unimagineably rude circumstance that onload event handlers
triggered by this function actually kill the window ... ok, it's triggered by this function actually kill the window ... ok, it's
not unimagineable; it's happened ... this deathgrip keeps this object not unimagineable; it's happened ... this deathgrip keeps this object
@ -660,7 +668,9 @@ void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout)
} }
NS_ASSERTION(!mIsFlushingLayout, "Someone screwed up"); NS_ASSERTION(!mIsFlushingLayout, "Someone screwed up");
NS_ASSERTION(mDocumentRequest, "No Document Request!"); // We may not have a document request if we are in a document.open() situation.
NS_ASSERTION(mDocumentRequest || mDocumentOpenedButNotLoaded,
"No Document Request!");
// The load group for this DocumentLoader is idle. Flush if we need to. // The load group for this DocumentLoader is idle. Flush if we need to.
if (aFlushLayout && !mDontFlushLayout) { if (aFlushLayout && !mDontFlushLayout) {
@ -687,9 +697,14 @@ void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout)
// And now check whether we're really busy; that might have changed with // And now check whether we're really busy; that might have changed with
// the layout flush. // the layout flush.
// Note, mDocumentRequest can be null if the flushing above re-entered this //
// method. // Note, mDocumentRequest can be null while mDocumentOpenedButNotLoaded is
if (!IsBusy() && mDocumentRequest) { // false if the flushing above re-entered this method. Exit in that case.
if (IsBusy() || (!mDocumentRequest && !mDocumentOpenedButNotLoaded)) {
return;
}
if (mDocumentRequest) {
// Clear out our request info hash, now that our load really is done and // Clear out our request info hash, now that our load really is done and
// we don't need it anymore to CalculateMaxProgress(). // we don't need it anymore to CalculateMaxProgress().
ClearInternalProgress(); ClearInternalProgress();
@ -715,7 +730,7 @@ void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout)
// //
mLoadGroup->SetDefaultLoadRequest(nullptr); mLoadGroup->SetDefaultLoadRequest(nullptr);
// Take a ref to our parent now so that we can call DocLoaderIsEmpty() on // Take a ref to our parent now so that we can call ChildDoneWithOnload() on
// it even if our onload handler removes us from the docloader tree. // it even if our onload handler removes us from the docloader tree.
RefPtr<nsDocLoader> parent = mParent; RefPtr<nsDocLoader> parent = mParent;
@ -733,6 +748,67 @@ void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout)
parent->ChildDoneWithOnload(this); parent->ChildDoneWithOnload(this);
} }
} }
} else {
MOZ_ASSERT(mDocumentOpenedButNotLoaded);
mDocumentOpenedButNotLoaded = false;
// Make sure we do the ChildEnteringOnload/ChildDoneWithOnload even if we
// plan to skip firing our own load event, because otherwise we might
// never end up firing our parent's load event.
RefPtr<nsDocLoader> parent = mParent;
if (!parent || parent->ChildEnteringOnload(this)) {
nsresult loadGroupStatus = NS_OK;
mLoadGroup->GetStatus(&loadGroupStatus);
// Make sure we're not canceling the loadgroup. If we are, then we should
// not fire a load event just like in the normal navigation case.
if (NS_SUCCEEDED(loadGroupStatus) ||
loadGroupStatus == NS_ERROR_PARSED_DATA_CACHED) {
// Can "doc" or "window" ever come back null here? Our state machine
// is complicated enough, so I wouldn't bet against it...
nsCOMPtr<nsIDocument> doc = do_GetInterface(GetAsSupports(this));
if (doc) {
doc->SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE,
/* updateTimingInformation = */ false);
nsCOMPtr<nsPIDOMWindowOuter> window = doc->GetWindow();
if (window && !doc->SkipLoadEventAfterClose()) {
MOZ_LOG(gDocLoaderLog, LogLevel::Debug,
("DocLoader:%p: Firing load event for document.open\n",
this));
// This is a very cut-down version of
// nsDocumentViewer::LoadComplete that doesn't do various things
// that are not relevant here because this wasn't an actual
// navigation.
WidgetEvent event(true, eLoad);
event.mFlags.mBubbles = false;
event.mFlags.mCancelable = false;
// Dispatching to |window|, but using |document| as the target,
// per spec.
event.mTarget = doc;
nsEventStatus unused = nsEventStatus_eIgnore;
doc->SetLoadEventFiring(true);
EventDispatcher::Dispatch(window, nullptr, &event, nullptr,
&unused);
doc->SetLoadEventFiring(false);
// Now unsuppress painting on the presshell, if we
// haven't done that yet.
nsCOMPtr<nsIPresShell> shell = doc->GetShell();
if (shell && !shell->IsDestroying()) {
shell->UnsuppressPainting();
if (!shell->IsDestroying()) {
shell->LoadComplete();
}
}
}
}
}
if (parent) {
parent->ChildDoneWithOnload(this);
}
}
} }
} }
} }

View File

@ -109,6 +109,8 @@ public:
unsigned long mNotifyMask; unsigned long mNotifyMask;
}; };
void SetDocumentOpenedButNotLoaded() { mDocumentOpenedButNotLoaded = true; }
protected: protected:
virtual ~nsDocLoader(); virtual ~nsDocLoader();
@ -302,6 +304,15 @@ protected:
bool mIsFlushingLayout; bool mIsFlushingLayout;
private: private:
/**
* This flag indicates that the loader is waiting for completion of
* a document.open-triggered "document load". This is set when
* document.open() happens and sets up a new parser and cleared out
* when we go to fire our load event or end up with a new document
* channel.
*/
bool mDocumentOpenedButNotLoaded;
static const PLDHashTableOps sRequestInfoHashOps; static const PLDHashTableOps sRequestInfoHashOps;
// A list of kids that are in the middle of their onload calls and will let // A list of kids that are in the middle of their onload calls and will let
@ -324,10 +335,20 @@ private:
nsRequestInfo *GetRequestInfo(nsIRequest* aRequest); nsRequestInfo *GetRequestInfo(nsIRequest* aRequest);
void ClearRequestInfoHash(); void ClearRequestInfoHash();
int64_t CalculateMaxProgress(); int64_t CalculateMaxProgress();
/// void DumpChannelInfo(void); /// void DumpChannelInfo(void);
// used to clear our internal progress state between loads... // used to clear our internal progress state between loads...
void ClearInternalProgress(); void ClearInternalProgress();
/**
* Used to test whether we might need to fire a load event. This
* can happen when we have a document load going on, or when we've
* had document.open() called and haven't fired the corresponding
* load event yet.
*/
bool IsBlockingLoadEvent() const {
return mIsLoadingDocument || mDocumentOpenedButNotLoaded;
}
}; };
NS_DEFINE_STATIC_IID_ACCESSOR(nsDocLoader, NS_THIS_DOCLOADER_IMPL_CID) NS_DEFINE_STATIC_IID_ACCESSOR(nsDocLoader, NS_THIS_DOCLOADER_IMPL_CID)