summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docshell/base/nsDocShell.cpp183
-rw-r--r--docshell/base/nsIDocShell.idl26
-rw-r--r--docshell/test/navigation/file_bug1379762-2.html43
-rw-r--r--docshell/test/navigation/file_document_write_1.html23
-rw-r--r--docshell/test/navigation/mochitest.ini4
-rw-r--r--docshell/test/navigation/test_sessionhistory.html10
-rw-r--r--dom/base/SimpleTreeIterator.h71
-rwxr-xr-xdom/base/moz.build1
-rw-r--r--dom/base/nsDocument.cpp62
-rw-r--r--dom/base/nsDocument.h38
-rw-r--r--dom/base/nsIDocument.h24
-rw-r--r--dom/base/nsINode.cpp29
-rw-r--r--dom/base/test/test_x-frame-options.html26
-rw-r--r--dom/bindings/CallbackObject.h3
-rw-r--r--dom/events/EventListenerManager.cpp21
-rw-r--r--dom/events/EventListenerManager.h8
-rw-r--r--dom/html/nsHTMLDocument.cpp397
-rw-r--r--dom/html/test/mochitest.ini1
-rw-r--r--dom/html/test/test_bug172261.html67
-rw-r--r--dom/html/test/test_bug255820.html38
-rw-r--r--dom/tests/mochitest/bugs/test_bug346659.html2
-rw-r--r--js/xpconnect/crashtests/crashtests.list2
-rw-r--r--layout/base/nsDocumentViewer.cpp5
-rw-r--r--layout/style/test/test_bug1232829.html1
-rw-r--r--parser/htmlparser/tests/mochitest/test_bug715739.html34
-rw-r--r--security/manager/ssl/tests/mochitest/mixedcontent/test_bug329869.html2
-rw-r--r--testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1-1.html5
-rw-r--r--testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1.html5
-rw-r--r--testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_open_write-1.html7
-rw-r--r--testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_open_write.html6
-rw-r--r--uriloader/base/nsDocLoader.cpp102
-rw-r--r--uriloader/base/nsDocLoader.h23
32 files changed, 640 insertions, 629 deletions
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
index d67780317..ebaf07bcd 100644
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -11843,35 +11843,45 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
{
// Implements History.pushState and History.replaceState
- // Here's what we do, roughly in the order specified by HTML5:
- // 1. Serialize aData using structured clone.
- // 2. If the third argument is present,
- // a. Resolve the url, relative to the first script's base URL
- // b. If (a) fails, raise a SECURITY_ERR
- // c. 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.
- // 3. If !aReplace:
+ // Here's what we do, roughly in the order specified by HTML5. The specific
+ // steps we are executing are at
+ // <https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate>
+ // and
+ // <https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps>.
+ // This function basically implements #dom-history-pushstate and
+ // UpdateURLAndHistory implements #url-and-history-update-steps.
+ //
+ // 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,
// as we would after a regular navigation, and save the current
// entry's scroll position (bug 590573).
- // 4. As apropriate, either add a state object entry to the session history
- // after the current entry with the following properties, or modify the
- // current session history entry to set
+ // D. #url-and-history-update-steps step 2.4 or step 3. As apropriate,
+ // either add a state object entry to the session history after the
+ // current entry with the following properties, or modify the current
+ // session history entry to set
// a. cloned data as the state object,
// b. if the third argument was present, the absolute URL found in
// step 2
// 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.
- // 6. If the third argument is present, set the document's current address
- // to the absolute URL found in step 2.
+ // F. If the third argument is present, set the document's current address
+ // 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
// and before completing step 5. For example, if a script called
// history.back() before we completed step 5, bfcache might destroy an
// 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.
//
// Note that we completely ignore the aTitle parameter.
@@ -11891,7 +11901,9 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
nsCOMPtr<nsIDocument> document = GetDocument();
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;
// scContainer->Init might cause arbitrary JS to run, and this code might
@@ -11934,7 +11946,9 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
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;
nsCOMPtr<nsIURI> currentURI;
if (sURIFixup && mCurrentURI) {
@@ -11943,12 +11957,11 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
} else {
currentURI = mCurrentURI;
}
- nsCOMPtr<nsIURI> oldURI = currentURI;
nsCOMPtr<nsIURI> newURI;
if (aURL.Length() == 0) {
newURI = currentURI;
} else {
- // 2a: Resolve aURL relative to mURI
+ // 7.1: Resolve aURL relative to mURI
nsIURI* docBaseURI = document->GetDocBaseURI();
if (!docBaseURI) {
@@ -11964,12 +11977,12 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
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)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
- // 2c: Same-origin check.
+ // 7.4 and 7.5: Same-origin check.
if (!nsContentUtils::URIIsLocalFile(newURI)) {
// In addition to checking that the security manager says that
// the new URI has the same origin as our current URI, we also
@@ -12020,18 +12033,36 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
} // end of same-origin check
- // Step 3: 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, NS_ERROR_FAILURE);
+ // Step 8: call "URL and history update steps"
+ rv = UpdateURLAndHistory(document, newURI, scContainer, aTitle, aReplace,
+ currentURI, equalURIs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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;
mLoadType = LOAD_PUSHSTATE;
nsCOMPtr<nsISHEntry> newSHEntry;
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;
GetCurScrollPos(ScrollOrientation_X, &cx);
GetCurScrollPos(ScrollOrientation_Y, &cy);
@@ -12042,10 +12073,10 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
// Since we're not changing which page we have loaded, pass
// true for aCloneChildren.
- rv = AddToSessionHistory(newURI, nullptr,
- document->NodePrincipal(), // triggeringPrincipal
- nullptr, true,
- getter_AddRefs(newSHEntry));
+ nsresult rv = AddToSessionHistory(aNewURI, nullptr,
+ aDocument->NodePrincipal(), // triggeringPrincipal
+ nullptr, true,
+ getter_AddRefs(newSHEntry));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE);
@@ -12068,15 +12099,26 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
mOSHE = newSHEntry;
} else {
+ // Step 3.
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);
}
- // Step 4: Modify new/original session history entry and clear its POST
- // data, if there is any.
- newSHEntry->SetStateData(scContainer);
+ // Step 2.4 and 3: Modify new/original session history entry and clear its
+ // POST data, if there is any.
+ newSHEntry->SetStateData(aData);
newSHEntry->SetPostData(nullptr);
// If this push/replaceState changed the document's current URI and the new
@@ -12084,39 +12126,48 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
// SHEntry's URI was modified in this way by a push/replaceState call
// set URIWasModified to true for the current SHEntry (bug 669671).
bool sameExceptHashes = true, oldURIWasModified = false;
- newURI->EqualsExceptRef(currentURI, &sameExceptHashes);
- oldOSHE->GetURIWasModified(&oldURIWasModified);
+ aNewURI->EqualsExceptRef(aCurrentURI, &sameExceptHashes);
+ // mOSHE might be null on replace. Only check if we're not replacing.
+ if (oldOSHE) {
+ oldOSHE->GetURIWasModified(&oldURIWasModified);
+ }
newSHEntry->SetURIWasModified(!sameExceptHashes || oldURIWasModified);
- // Step 5: If aReplace is false, indicating that we're doing a pushState
- // rather than a replaceState, notify bfcache that we've added a page to
- // the history so it can evict content viewers if appropriate. Otherwise
- // call ReplaceEntry so that we notify nsIHistoryListeners that an entry
- // was replaced.
+ // Step E as described at the top of AddState: If aReplace is false,
+ // indicating that we're doing a pushState rather than a replaceState, notify
+ // bfcache that we've added a page to the history so it can evict content
+ // viewers if appropriate. Otherwise call ReplaceEntry so that we notify
+ // 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;
GetRootSessionHistory(getter_AddRefs(rootSH));
NS_ENSURE_TRUE(rootSH, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsISHistoryInternal> internalSH = do_QueryInterface(rootSH);
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 curIndex = -1;
- rv = rootSH->GetIndex(&curIndex);
- if (NS_SUCCEEDED(rv) && curIndex > -1) {
- 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);
+ 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.
//
// We need to call FireOnLocationChange so that the browser's address bar
@@ -12129,35 +12180,35 @@ nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
// notification is allowed only when we know docshell is not loading a new
// document and it requires LOCATION_CHANGE_SAME_DOCUMENT flag. Otherwise,
// FireOnLocationChange(...) breaks security UI.
- if (!equalURIs) {
- document->SetDocumentURI(newURI);
+ if (!aEqualURIs) {
+ aDocument->SetDocumentURI(aNewURI);
// 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...
- SetCurrentURI(newURI, nullptr, false, LOCATION_CHANGE_SAME_DOCUMENT);
+ SetCurrentURI(aNewURI, nullptr, false, LOCATION_CHANGE_SAME_DOCUMENT);
if (mLoadType != LOAD_ERROR_PAGE) {
FireDummyOnLocationChange();
}
- AddURIVisit(newURI, oldURI, oldURI, 0);
+ AddURIVisit(aNewURI, aCurrentURI, aCurrentURI, 0);
// AddURIVisit doesn't set the title for the new URI in global history,
// so do that here.
if (mUseGlobalHistory && !UsePrivateBrowsing()) {
nsCOMPtr<IHistory> history = services::GetHistoryService();
if (history) {
- history->SetURITitle(newURI, mTitle);
+ history->SetURITitle(aNewURI, mTitle);
} else if (mGlobalHistory) {
- mGlobalHistory->SetPageTitle(newURI, mTitle);
+ mGlobalHistory->SetPageTitle(aNewURI, mTitle);
}
}
// Inform the favicon service that our old favicon applies to this new
// URI.
- CopyFavicon(oldURI, newURI, document->NodePrincipal(), UsePrivateBrowsing());
+ CopyFavicon(aCurrentURI, aNewURI, aDocument->NodePrincipal(), UsePrivateBrowsing());
} else {
FireDummyOnLocationChange();
}
- document->SetStateObject(scContainer);
+ aDocument->SetStateObject(aData);
return NS_OK;
}
diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl
index d205e5b0c..d2812bd9c 100644
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -26,6 +26,7 @@ interface nsIChannel;
interface nsIContentViewer;
interface nsIDOMEventTarget;
interface nsIDocShellLoadInfo;
+interface nsIDocument;
interface nsIEditor;
interface nsIEditingSession;
interface nsISimpleEnumerator;
@@ -35,6 +36,7 @@ interface nsISHEntry;
interface nsILayoutHistoryState;
interface nsISecureBrowserUI;
interface nsIScriptGlobalObject;
+interface nsIStructuredCloneContainer;
interface nsIDOMStorage;
interface nsIPrincipal;
interface nsIWebBrowserPrint;
@@ -211,6 +213,30 @@ interface nsIDocShell : nsIDocShellTreeItem
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
* to loadURI.
*/
diff --git a/docshell/test/navigation/file_bug1379762-2.html b/docshell/test/navigation/file_bug1379762-2.html
deleted file mode 100644
index 86033cb2e..000000000
--- a/docshell/test/navigation/file_bug1379762-2.html
+++ /dev/null
@@ -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>
diff --git a/docshell/test/navigation/file_document_write_1.html b/docshell/test/navigation/file_document_write_1.html
index e0281f7cd..169046a9b 100644
--- a/docshell/test/navigation/file_document_write_1.html
+++ b/docshell/test/navigation/file_document_write_1.html
@@ -1,27 +1,16 @@
<html>
<head>
<script>
- function run() {
+ function start() {
+ var length = history.length;
document.open();
document.write("<h5 id='dynamic'>document.written content</h5>");
document.close();
- window.history.go(-1);
- }
-
- function start() {
- if (++opener.testCount == 1) {
- setTimeout(run, 0);
- }
+ opener.is(history.length, length,
+ "document.open/close should not change history");
+ opener.nextTest();
+ window.close();
}
-
- 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>
</head>
<body onload="start();">
diff --git a/docshell/test/navigation/mochitest.ini b/docshell/test/navigation/mochitest.ini
index 8cff81ad1..e2ee307e4 100644
--- a/docshell/test/navigation/mochitest.ini
+++ b/docshell/test/navigation/mochitest.ini
@@ -58,8 +58,8 @@ skip-if = (toolkit == 'android') || (!debug && (os == 'mac' || os == 'win')) # B
[test_reserved.html]
skip-if = (toolkit == 'android') || (debug && e10s) #too slow on Android 4.3 aws only; bug 1030403; bug 1263213 for debug e10s
[test_sessionhistory.html]
-skip-if = toolkit == 'android' #RANDOM
-support-files = file_bug1379762-1.html file_bug1379762-2.html
+skip-if = toolkit == 'android' #RANDOM on Android
+support-files = file_bug1379762-1.html
[test_sibling-matching-parent.html]
[test_sibling-off-domain.html]
[test_triggeringprincipal_frame_nav.html]
diff --git a/docshell/test/navigation/test_sessionhistory.html b/docshell/test/navigation/test_sessionhistory.html
index 10b0cbcaf..e5978acfa 100644
--- a/docshell/test/navigation/test_sessionhistory.html
+++ b/docshell/test/navigation/test_sessionhistory.html
@@ -33,7 +33,6 @@ var testFiles =
"file_scrollRestoration.html",
"file_bug1300461.html",
"file_bug1379762-1.html",
- "file_bug1379762-2.html",
];
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() {
setTimeout(nextTest_, 0);
}
diff --git a/dom/base/SimpleTreeIterator.h b/dom/base/SimpleTreeIterator.h
new file mode 100644
index 000000000..7ca504082
--- /dev/null
+++ b/dom/base/SimpleTreeIterator.h
@@ -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
diff --git a/dom/base/moz.build b/dom/base/moz.build
index ebb76d617..75ddefded 100755
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -210,6 +210,7 @@ EXPORTS.mozilla.dom += [
'ScreenOrientation.h',
'ScriptSettings.h',
'ShadowRoot.h',
+ 'SimpleTreeIterator.h',
'StructuredCloneHolder.h',
'StructuredCloneTags.h',
'StyleSheetList.h',
diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp
index e2be6b664..afe88a454 100644
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1968,22 +1968,10 @@ nsDocument::Reset(nsIChannel* aChannel, nsILoadGroup* aLoadGroup)
}
void
-nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
- nsIPrincipal* aPrincipal)
-{
- NS_PRECONDITION(aURI, "Null URI passed to ResetToURI");
-
- if (gDocumentLeakPRLog && MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
- PR_LogPrint("DOCUMENT %p ResetToURI %s", this,
- aURI->GetSpecOrDefault().get());
- }
-
- mSecurityInfo = nullptr;
-
- mDocumentLoadGroup = nullptr;
-
+nsDocument::DisconnectNodeTree() {
// Delete references to sub-documents and kill the subdocument map,
- // if any. It holds strong references
+ // if any. This is not strictly needed, but makes the node tree
+ // teardown a bit faster.
delete mSubDocuments;
mSubDocuments = nullptr;
@@ -2019,6 +2007,22 @@ nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
"After removing all children, there should be no root elem");
}
mInUnlinkOrDeletion = oldVal;
+}
+
+void
+nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
+ nsIPrincipal* aPrincipal)
+{
+ NS_PRECONDITION(aURI, "Null URI passed to ResetToURI");
+
+ if (gDocumentLeakPRLog && MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
+ PR_LogPrint("DOCUMENT %p ResetToURI %s", this,
+ aURI->GetSpecOrDefault().get());
+ }
+
+ mSecurityInfo = nullptr;
+
+ mDocumentLoadGroup = nullptr;
// Reset our stylesheets
ResetStylesheetsToURI(aURI);
@@ -2029,6 +2033,8 @@ nsDocument::ResetToURI(nsIURI *aURI, nsILoadGroup *aLoadGroup,
mListenerManager = nullptr;
}
+ DisconnectNodeTree();
+
// Release the stylesheets list.
mDOMStyleSheets = nullptr;
@@ -4506,18 +4512,6 @@ nsDocument::SetScriptGlobalObject(nsIScriptGlobalObject *aScriptGlobalObject)
mLayoutHistoryState = nullptr;
SetScopeObject(aScriptGlobalObject);
mHasHadDefaultView = true;
-#ifdef DEBUG
- if (!mWillReparent) {
- // We really shouldn't have a wrapper here but if we do we need to make sure
- // it has the correct parent.
- JSObject *obj = GetWrapperPreserveColor();
- if (obj) {
- JSObject *newScope = aScriptGlobalObject->GetGlobalJSObject();
- NS_ASSERTION(js::GetGlobalForObjectCrossCompartment(obj) == newScope,
- "Wrong scope, this is really bad!");
- }
- }
-#endif
if (mAllowDNSPrefetch) {
nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
@@ -9077,7 +9071,8 @@ nsDocument::CloneDocHelper(nsDocument* clone) const
}
void
-nsDocument::SetReadyStateInternal(ReadyState rs)
+nsDocument::SetReadyStateInternal(ReadyState rs,
+ bool updateTimingInformation)
{
mReadyState = rs;
if (rs == READYSTATE_UNINITIALIZED) {
@@ -9086,7 +9081,12 @@ nsDocument::SetReadyStateInternal(ReadyState rs)
// transition undetectable by Web content.
return;
}
- if (mTiming) {
+
+ if (updateTimingInformation && READYSTATE_LOADING == rs) {
+ mLoadingTimeStamp = mozilla::TimeStamp::Now();
+ }
+
+ if (updateTimingInformation && mTiming) {
switch (rs) {
case READYSTATE_LOADING:
mTiming->NotifyDOMLoading(nsIDocument::GetDocumentURI());
@@ -9102,10 +9102,6 @@ nsDocument::SetReadyStateInternal(ReadyState rs)
break;
}
}
- // At the time of loading start, we don't have timing object, record time.
- if (READYSTATE_LOADING == rs) {
- mLoadingTimeStamp = mozilla::TimeStamp::Now();
- }
RefPtr<AsyncEventDispatcher> asyncDispatcher =
new AsyncEventDispatcher(this, NS_LITERAL_STRING("readystatechange"),
diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h
index ac600eb43..8ea4993f0 100644
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -704,7 +704,11 @@ public:
virtual void BeginLoad() override;
virtual void EndLoad() override;
- virtual void SetReadyStateInternal(ReadyState rs) override;
+ // Set the readystate of the document. If updateTimingInformation is true,
+ // this will record relevant timestamps in the document's performance timing.
+ // Some consumers like document.open() don't want to do that.
+ virtual void SetReadyStateInternal(ReadyState rs,
+ bool updateTimingInformation = true) override;
virtual void ContentStateChanged(nsIContent* aContent,
mozilla::EventStates aStateMask)
@@ -916,6 +920,14 @@ public:
UpdateFrameRequestCallbackSchedulingState();
}
+ void SetLoadEventFiring(bool aFiring) override { mLoadEventFiring = aFiring; }
+
+ bool SkipLoadEventAfterClose() override {
+ bool skip = mSkipLoadEventAfterClose;
+ mSkipLoadEventAfterClose = false;
+ return skip;
+ }
+
virtual nsIDocument* GetTemplateContentsOwner() override;
NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsDocument,
@@ -1255,6 +1267,11 @@ protected:
*/
Element* GetTitleElement();
+ /**
+ * Perform tree disconnection needed by ResetToURI and document.open()
+ */
+ void DisconnectNodeTree();
+
public:
// Get our title
virtual void GetTitle(nsString& aTitle) override;
@@ -1458,6 +1475,20 @@ public:
// additional sheets and sheets from the nsStyleSheetService.
bool mStyleSetFilled:1;
+ // The HTML spec has a "iframe load in progress" flag, but that doesn't seem
+ // to have the right semantics. See <https://github.com/whatwg/html/issues/4292>.
+ // What we have instead is a flag that is set while the window's 'load' event is
+ // firing if this document is the window's document.
+ bool mLoadEventFiring : 1;
+
+ // The HTML spec has a "mute iframe load" flag, but that doesn't seem to have
+ // the right semantics. See <https://github.com/whatwg/html/issues/4292>.
+ // What we have instead is a flag that is set if completion of our document
+ // via document.close() should skip firing the load event. Note that this
+ // flag is only relevant for HTML documents, but lives here for reasons that
+ // are documented above on SkipLoadEventAfterClose().
+ bool mSkipLoadEventAfterClose : 1;
+
uint8_t mPendingFullscreenRequests;
uint8_t mXMLDeclarationBits;
@@ -1615,11 +1646,6 @@ private:
// Set to true when the document is possibly controlled by the ServiceWorker.
// Used to prevent multiple requests to ServiceWorkerManager.
bool mMaybeServiceWorkerControlled;
-
-#ifdef DEBUG
-public:
- bool mWillReparent;
-#endif
};
class nsDocumentOnStack
diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h
index d76a12d71..fdaee39ca 100644
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -909,10 +909,6 @@ public:
*/
nsresult GetSrcdocData(nsAString& aSrcdocData);
- bool DidDocumentOpen() {
- return mDidDocumentOpen;
- }
-
already_AddRefed<mozilla::dom::AnonymousContent>
InsertAnonymousContent(mozilla::dom::Element& aElement,
mozilla::ErrorResult& aError);
@@ -1448,7 +1444,7 @@ public:
virtual void EndLoad() = 0;
enum ReadyState { READYSTATE_UNINITIALIZED = 0, READYSTATE_LOADING = 1, READYSTATE_INTERACTIVE = 3, READYSTATE_COMPLETE = 4};
- virtual void SetReadyStateInternal(ReadyState rs) = 0;
+ virtual void SetReadyStateInternal(ReadyState rs, bool updateTimingInformation = true) = 0;
ReadyState GetReadyStateEnum()
{
return mReadyState;
@@ -2187,6 +2183,19 @@ public:
}
/**
+ * Flag whether we're about to fire the window's load event for this document.
+ */
+ virtual void SetLoadEventFiring(bool aFiring) = 0;
+
+ /**
+ * Test whether we should be firing a load event for this document after a
+ * document.close().
+ * This method should only be called at the point when the load event is about
+ * to be fired, since it resets `skip`.
+ */
+ virtual bool SkipLoadEventAfterClose() = 0;
+
+ /**
* Returns the template content owner document that owns the content of
* HTMLTemplateElement.
*/
@@ -3146,11 +3155,6 @@ protected:
// Whether the document was created by a srcdoc iframe.
bool mIsSrcdocDocument : 1;
- // Records whether we've done a document.open. If this is true, it's possible
- // for nodes from this document to have outdated wrappers in their wrapper
- // caches.
- bool mDidDocumentOpen : 1;
-
// Whether this document has a display document and thus is considered to
// be a resource document. Normally this is the same as !!mDisplayDocument,
// but mDisplayDocument is cleared during Unlink. mHasDisplayDocument is
diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp
index ca507a5fc..212110b72 100644
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -1550,27 +1550,6 @@ AdoptNodeIntoOwnerDoc(nsINode *aParent, nsINode *aNode)
}
static nsresult
-CheckForOutdatedParent(nsINode* aParent, nsINode* aNode)
-{
- if (JSObject* existingObjUnrooted = aNode->GetWrapper()) {
- JS::Rooted<JSObject*> existingObj(RootingCx(), existingObjUnrooted);
-
- AutoJSContext cx;
- nsIGlobalObject* global = aParent->OwnerDoc()->GetScopeObject();
- MOZ_ASSERT(global);
-
- if (js::GetGlobalForObjectCrossCompartment(existingObj) !=
- global->GetGlobalJSObject()) {
- JSAutoCompartment ac(cx, existingObj);
- nsresult rv = ReparentWrapper(cx, existingObj);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- }
-
- return NS_OK;
-}
-
-static nsresult
ReparentWrappersInSubtree(nsIContent* aRoot)
{
MOZ_ASSERT(ShouldUseXBLScope(aRoot));
@@ -1631,9 +1610,6 @@ nsINode::doInsertChildAt(nsIContent* aKid, uint32_t aIndex,
if (OwnerDoc() != aKid->OwnerDoc()) {
rv = AdoptNodeIntoOwnerDoc(this, aKid);
NS_ENSURE_SUCCESS(rv, rv);
- } else if (OwnerDoc()->DidDocumentOpen()) {
- rv = CheckForOutdatedParent(this, aKid);
- NS_ENSURE_SUCCESS(rv, rv);
}
uint32_t childCount = aChildArray.ChildCount();
@@ -2481,11 +2457,6 @@ nsINode::ReplaceOrInsertBefore(bool aReplace, nsINode* aNewChild,
if (aError.Failed()) {
return nullptr;
}
- } else if (doc->DidDocumentOpen()) {
- aError = CheckForOutdatedParent(this, aNewChild);
- if (aError.Failed()) {
- return nullptr;
- }
}
/*
diff --git a/dom/base/test/test_x-frame-options.html b/dom/base/test/test_x-frame-options.html
index a0c7acdc3..8e8cffcc3 100644
--- a/dom/base/test/test_x-frame-options.html
+++ b/dom/base/test/test_x-frame-options.html
@@ -113,19 +113,25 @@ var testFramesLoaded = function() {
// test that a document can be framed under a javascript: URL opened by the
// same site as the frame
+// We can't set a load event listener before calling document.open/document.write, because those will remove such listeners. So we need to define a function that the new window will be able to call.
+function frameInJSURILoaded(win) {
+ var test = win.document.getElementById("sameorigin3")
+ .contentDocument.getElementById("test");
+ ok(test != null, "frame under javascript: URL should have loaded.");
+ win.close();
+
+ // run last test
+ if (!isUnique) {
+ testFrameInDataURI();
+ } else {
+ testFrameNotLoadedInDataURI();
+ }
+}
+
var testFrameInJSURI = function() {
var html = '<iframe id="sameorigin3" src="http://mochi.test:8888/tests/dom/base/test/file_x-frame-options_page.sjs?testid=sameorigin3&xfo=sameorigin"></iframe>';
var win = window.open();
- win.onload = function() {
- var test = win.document.getElementById("sameorigin3")
- .contentDocument.getElementById("test");
- ok(test != null, "frame under javascript: URL should have loaded.");
- win.close();
-
- // run last test
- testFrameInDataURI();
- }
- win.location.href = "javascript:document.write('"+html+"');document.close();";
+ win.location.href = "javascript:document.open(); onload = opener.frameInJSURILoaded.bind(null, window); document.write('"+html+"');document.close();";
}
// test that a document can be framed under a data: URL opened by the
diff --git a/dom/bindings/CallbackObject.h b/dom/bindings/CallbackObject.h
index 8a3d45dfc..5cc98fd5d 100644
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -514,8 +514,9 @@ private:
{
// NS_IF_RELEASE because we might have been unlinked before
nsISupports* ptr = GetISupports();
- NS_IF_RELEASE(ptr);
+ // Clear mPtrBits before the release to prevent reentrance.
mPtrBits = 0;
+ NS_IF_RELEASE(ptr);
}
uintptr_t mPtrBits;
diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp
index fe896870c..0774c3296 100644
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -166,11 +166,11 @@ EventListenerManager::~EventListenerManager()
// XXX azakai: Is there any reason to not just call Disconnect
// from right here, if not previously called?
NS_ASSERTION(!mTarget, "didn't call Disconnect");
- RemoveAllListeners();
+ RemoveAllListenersSilently();
}
void
-EventListenerManager::RemoveAllListeners()
+EventListenerManager::RemoveAllListenersSilently()
{
if (mClearingListeners) {
return;
@@ -1329,7 +1329,7 @@ void
EventListenerManager::Disconnect()
{
mTarget = nullptr;
- RemoveAllListeners();
+ RemoveAllListenersSilently();
}
void
@@ -1734,6 +1734,21 @@ EventListenerManager::IsApzAwareEvent(nsIAtom* aEvent)
return false;
}
+void
+EventListenerManager::RemoveAllListeners()
+{
+ while (!mListeners.IsEmpty()) {
+ size_t idx = mListeners.Length() - 1;
+ nsCOMPtr<nsIAtom> type = mListeners.ElementAt(idx).mTypeAtom;
+ EventMessage message = mListeners.ElementAt(idx).mEventMessage;
+ mListeners.RemoveElementAt(idx);
+ NotifyEventListenerRemoved(type);
+ if (IsDeviceType(message)) {
+ DisableDevice(message);
+ }
+ }
+}
+
already_AddRefed<nsIScriptGlobalObject>
EventListenerManager::GetScriptGlobalAndDocument(nsIDocument** aDoc)
{
diff --git a/dom/events/EventListenerManager.h b/dom/events/EventListenerManager.h
index 6b0927788..36637cfd7 100644
--- a/dom/events/EventListenerManager.h
+++ b/dom/events/EventListenerManager.h
@@ -472,6 +472,12 @@ public:
bool IsApzAwareListener(Listener* aListener);
bool IsApzAwareEvent(nsIAtom* aEvent);
+ /**
+ * Remove all event listeners from the event target this EventListenerManager
+ * is for.
+ */
+ void RemoveAllListeners();
+
protected:
void HandleEventInternal(nsPresContext* aPresContext,
WidgetEvent* aEvent,
@@ -604,7 +610,7 @@ protected:
const nsAString& aTypeString,
const EventListenerFlags& aFlags,
bool aAllEvents = false);
- void RemoveAllListeners();
+ void RemoveAllListenersSilently();
void NotifyEventListenerRemoved(nsIAtom* aUserType);
const EventTypeData* GetTypeDataForIID(const nsIID& aIID);
const EventTypeData* GetTypeDataForEventName(nsIAtom* aName);
diff --git a/dom/html/nsHTMLDocument.cpp b/dom/html/nsHTMLDocument.cpp
index d64c27727..0f2d90673 100644
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -15,6 +15,7 @@
#include "nsPrintfCString.h"
#include "nsReadableUtils.h"
#include "nsUnicharUtils.h"
+#include "nsIDocumentLoader.h"
#include "nsIHTMLContentSink.h"
#include "nsIXMLContentSink.h"
#include "nsHTMLParts.h"
@@ -84,6 +85,7 @@
#include "mozilla/dom/EncodingUtils.h"
#include "mozilla/dom/FallbackEncoding.h"
+#include "mozilla/EventListenerManager.h"
#include "mozilla/LoadInfo.h"
#include "nsIEditingSession.h"
#include "nsIEditor.h"
@@ -107,12 +109,14 @@
#include "nsIImageDocument.h"
#include "mozilla/dom/HTMLBodyElement.h"
#include "mozilla/dom/HTMLDocumentBinding.h"
+#include "mozilla/dom/SimpleTreeIterator.h"
#include "nsCharsetSource.h"
#include "nsIStringBundle.h"
#include "nsDOMClassInfo.h"
#include "nsFocusManager.h"
#include "nsIFrame.h"
#include "nsIContent.h"
+#include "nsIStructuredCloneContainer.h"
#include "nsLayoutStylesheetCache.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/StyleSheetInlines.h"
@@ -842,6 +846,24 @@ nsHTMLDocument::EndLoad()
if (turnOnEditing) {
EditingStateChanged();
}
+
+ if (!GetWindow()) {
+ // This is a document that's not in a window. For example, this could be an
+ // XMLHttpRequest responseXML document, or a document created via DOMParser
+ // or DOMImplementation. We don't reach this code normally for such
+ // documents (which is not obviously correct), but can reach it via
+ // document.open()/document.close().
+ //
+ // Such documents don't fire load events, but per spec should set their
+ // readyState to "complete" when parsing and all loading of subresources is
+ // done. Parsing is done now, and documents not in a window don't load
+ // subresources, so just go ahead and mark ourselves as complete.
+ SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE,
+ /* updateTimingInformation = */ false);
+
+ // Reset mSkipLoadEventAfterClose just in case.
+ mSkipLoadEventAfterClose = false;
+ }
}
void
@@ -1410,19 +1432,21 @@ already_AddRefed<nsIDocument>
nsHTMLDocument::Open(JSContext* cx,
const nsAString& aType,
const nsAString& aReplace,
- ErrorResult& rv)
+ ErrorResult& aError)
{
- // Implements the "When called with two arguments (or fewer)" steps here:
- // https://html.spec.whatwg.org/multipage/webappapis.html#opening-the-input-stream
+ // Implements
+ // <https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-open-steps>
NS_ASSERTION(nsContentUtils::CanCallerAccess(static_cast<nsIDOMHTMLDocument*>(this)),
"XOW should have caught this!");
+
+ // Step 1 - Throw if we're the wrong type of document.
if (!IsHTMLDocument() || mDisableDocWrite || !IsMasterDocument()) {
- // No calling document.open() on XHTML
- rv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
+ // Set up the content type for insertion
nsAutoCString contentType;
contentType.AssignLiteral("text/html");
@@ -1435,51 +1459,7 @@ nsHTMLDocument::Open(JSContext* cx,
contentType.AssignLiteral("text/plain");
}
- // If we already have a parser we ignore the document.open call.
- if (mParser || mParserAborted) {
- // The WHATWG spec says: "If the document has an active parser that isn't
- // a script-created parser, and the insertion point associated with that
- // parser's input stream is not undefined (that is, it does point to
- // somewhere in the input stream), then the method does nothing. Abort
- // these steps and return the Document object on which the method was
- // invoked."
- // Note that aborting a parser leaves the parser "active" with its
- // insertion point "not undefined". We track this using mParserAborted,
- // because aborting a parser nulls out mParser.
- nsCOMPtr<nsIDocument> ret = this;
- return ret.forget();
- }
-
- // No calling document.open() without a script global object
- if (!mScriptGlobalObject) {
- nsCOMPtr<nsIDocument> ret = this;
- return ret.forget();
- }
-
- nsPIDOMWindowOuter* outer = GetWindow();
- if (!outer || (GetInnerWindow() != outer->GetCurrentInnerWindow())) {
- nsCOMPtr<nsIDocument> ret = this;
- return ret.forget();
- }
-
- // check whether we're in the middle of unload. If so, ignore this call.
- nsCOMPtr<nsIDocShell> shell(mDocumentContainer);
- if (!shell) {
- // We won't be able to create a parser anyway.
- nsCOMPtr<nsIDocument> ret = this;
- return ret.forget();
- }
-
- bool inUnload;
- shell->GetIsInUnload(&inUnload);
- if (inUnload) {
- nsCOMPtr<nsIDocument> ret = this;
- return ret.forget();
- }
-
- // Note: We want to use GetEntryDocument here because this document
- // should inherit the security information of the document that's opening us,
- // (since if it's secure, then it's presumably trusted).
+ // Step 3 - Get the entryDocument for security checks
nsCOMPtr<nsIDocument> callerDoc = GetEntryDocument();
if (!callerDoc) {
// If we're called from C++ or in some other way without an originating
@@ -1489,67 +1469,39 @@ nsHTMLDocument::Open(JSContext* cx,
// change the principals of a document for security reasons we'll have to
// refuse to go ahead with this call.
- rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
- // Grab a reference to the calling documents security info (if any)
- // and URIs as they may be lost in the call to Reset().
- nsCOMPtr<nsISupports> securityInfo = callerDoc->GetSecurityInfo();
- nsCOMPtr<nsIURI> uri = callerDoc->GetDocumentURI();
- nsCOMPtr<nsIURI> baseURI = callerDoc->GetBaseURI();
- nsCOMPtr<nsIPrincipal> callerPrincipal = callerDoc->NodePrincipal();
- nsCOMPtr<nsIChannel> callerChannel = callerDoc->GetChannel();
-
- // We're called from script. Make sure the script is from the same
- // origin, not just that the caller can access the document. This is
- // needed to keep document principals from ever changing, which is
- // needed because of the way we use our XOW code, and is a sane
- // thing to do anyways.
-
- bool equals = false;
- if (NS_FAILED(callerPrincipal->Equals(NodePrincipal(), &equals)) ||
- !equals) {
-
-#ifdef DEBUG
- nsCOMPtr<nsIURI> callerDocURI = callerDoc->GetDocumentURI();
- nsCOMPtr<nsIURI> thisURI = nsIDocument::GetDocumentURI();
- printf("nsHTMLDocument::Open callerDoc %s this %s\n",
- callerDocURI ? callerDocURI->GetSpecOrDefault().get() : "",
- thisURI ? thisURI->GetSpecOrDefault().get() : "");
-#endif
-
- rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ // Step 4 - Throw if we're not same-origin
+ if (!callerDoc->NodePrincipal()->Equals(NodePrincipal())) {
+ aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
- // Stop current loads targeted at the window this document is in.
- if (mScriptGlobalObject) {
- nsCOMPtr<nsIContentViewer> cv;
- shell->GetContentViewer(getter_AddRefs(cv));
-
- if (cv) {
- bool okToUnload;
- if (NS_SUCCEEDED(cv->PermitUnload(&okToUnload)) && !okToUnload) {
- // We don't want to unload, so stop here, but don't throw an
- // exception.
- nsCOMPtr<nsIDocument> ret = this;
- return ret.forget();
- }
-
- // Now double-check that our invariants still hold.
- if (!mScriptGlobalObject) {
- nsCOMPtr<nsIDocument> ret = this;
- return ret.forget();
- }
-
- nsPIDOMWindowOuter* outer = GetWindow();
- if (!outer || (GetInnerWindow() != outer->GetCurrentInnerWindow())) {
- nsCOMPtr<nsIDocument> ret = this;
- return ret.forget();
- }
+ // Step 5 - If we have an active parser, abort with no-op
+ if (mParser || mParserAborted) {
+ nsCOMPtr<nsIDocument> ret = this;
+ return ret.forget();
+ }
+
+ // Step 6 - Check if document.open() is called during unload
+ nsCOMPtr<nsIDocShell> shell(mDocumentContainer);
+ if (shell) {
+ bool inUnload;
+ shell->GetIsInUnload(&inUnload);
+ if (inUnload) {
+ nsCOMPtr<nsIDocument> ret = this;
+ return ret.forget();
}
+ }
+ // Step 7 - Stop existing navigation of our browsing context (and all
+ // other loads it's doing) if we're the active document of our browsing
+ // context. If there's no existing navigation, we don't want to stop
+ // anything.
+ if (shell && IsCurrentActiveDocument() &&
+ mScriptGlobalObject) {
nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(shell));
webnav->Stop(nsIWebNavigation::STOP_NETWORK);
@@ -1560,189 +1512,121 @@ nsHTMLDocument::Open(JSContext* cx,
EnsureOnloadBlocker();
}
- // The open occurred after the document finished loading.
- // So we reset the document and then reinitialize it.
- nsCOMPtr<nsIChannel> channel;
- nsCOMPtr<nsILoadGroup> group = do_QueryReferent(mDocumentLoadGroup);
- rv = NS_NewChannel(getter_AddRefs(channel),
- uri,
- callerDoc,
- nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL,
- nsIContentPolicy::TYPE_OTHER,
- group);
-
- if (rv.Failed()) {
- return nullptr;
- }
-
- if (callerChannel) {
- nsLoadFlags callerLoadFlags;
- rv = callerChannel->GetLoadFlags(&callerLoadFlags);
- if (rv.Failed()) {
- return nullptr;
- }
-
- nsLoadFlags loadFlags;
- rv = channel->GetLoadFlags(&loadFlags);
- if (rv.Failed()) {
- return nullptr;
- }
-
- loadFlags |= callerLoadFlags & nsIRequest::INHIBIT_PERSISTENT_CACHING;
-
- rv = channel->SetLoadFlags(loadFlags);
- if (rv.Failed()) {
- return nullptr;
+ // 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).
-
- // Hold onto ourselves on the offchance that we're down to one ref
- nsCOMPtr<nsIDocument> kungFuDeathGrip = this;
-
- if (nsPIDOMWindowInner *window = GetInnerWindow()) {
- // Remember the old scope in case the call to SetNewDocument changes it.
- nsCOMPtr<nsIScriptGlobalObject> oldScope(do_QueryReferent(mScopeObject));
-
-#ifdef DEBUG
- bool willReparent = mWillReparent;
- mWillReparent = true;
+ // Step 10 - Remove all of our DOM children without firing any mutation events.
+ DisconnectNodeTree();
- nsDocument* templateContentsOwner =
- static_cast<nsDocument*>(mTemplateContentsOwner.get());
+ // --- At this point our tree is clean and we can switch to the new URI ---
- 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;
- }
-
- // 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;
- }
- }
- }
- }
- }
+ // And let our docloader know that it will need to track our load event.
+ nsDocShell::Cast(shell)->SetDocumentOpenedButNotLoaded();
+ }
- mDidDocumentOpen = true;
+ // Step 12
+ mSkipLoadEventAfterClose = mLoadEventFiring;
- // Call Reset(), this will now do the full reset
- Reset(channel, group);
- if (baseURI) {
- mDocumentBaseURI = baseURI;
- }
+ // 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);
- // 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 +1690,6 @@ nsHTMLDocument::Close(ErrorResult& rv)
if (GetShell()) {
FlushPendingNotifications(Flush_Layout);
}
-
- // Removing the wyciwygChannel here is wrong when document.close() is
- // called from within the document itself. However, legacy requires the
- // channel to be removed here. Otherwise, the load event never fires.
- NS_ASSERTION(mWyciwygChannel, "nsHTMLDocument::Close(): Trying to remove "
- "nonexistent wyciwyg channel!");
- RemoveWyciwygChannel();
- NS_ASSERTION(!mWyciwygChannel, "nsHTMLDocument::Close(): "
- "nsIWyciwygChannel could not be removed!");
}
void
diff --git a/dom/html/test/mochitest.ini b/dom/html/test/mochitest.ini
index b9da7def8..024de1cd9 100644
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -529,7 +529,6 @@ skip-if = toolkit == 'android' # plugins not supported
[test_bug196523.html]
[test_bug199692.html]
skip-if = toolkit == 'android' #bug 811644
-[test_bug172261.html]
[test_bug255820.html]
[test_bug259332.html]
[test_bug311681.html]
diff --git a/dom/html/test/test_bug172261.html b/dom/html/test/test_bug172261.html
deleted file mode 100644
index 2b5d752cd..000000000
--- a/dom/html/test/test_bug172261.html
+++ /dev/null
@@ -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>
-
diff --git a/dom/html/test/test_bug255820.html b/dom/html/test/test_bug255820.html
index 20727fee4..18073497b 100644
--- a/dom/html/test/test_bug255820.html
+++ b/dom/html/test/test_bug255820.html
@@ -28,7 +28,7 @@ SimpleTest.waitForExplicitFinish();
is(document.characterSet, "UTF-8",
"Unexpected character set for our document");
-var testsLeft = 4;
+var testsLeft = 3;
function testFinished() {
--testsLeft;
@@ -42,29 +42,11 @@ function charsetTestFinished(id, doc, charsetTarget) {
testFinished();
}
-function f2Continue() {
-// Commented out pending discussion at the WHATWG
-// $("f2").
-// setAttribute("onload",
-// "charsetTestFinished('f2 reloaded', this.contentDocument, 'us-ascii');");
- $("f2").
- setAttribute("onload",
- "testFinished();");
- $("f2").contentWindow.location.reload();
-}
-
function f3Continue() {
var doc = $("f3").contentDocument;
is(doc.defaultView.getComputedStyle(doc.body, "").color, "rgb(0, 180, 0)",
- "Wrong color before reload");
- $("f3").
- setAttribute("onload",
- 'var doc = this.contentDocument; ' +
- 'is(doc.defaultView.getComputedStyle(doc.body, "").color, ' +
- ' "rgb(0, 180, 0)",' +
- ' "Wrong color after reload");' +
- "charsetTestFinished('f1', this.contentDocument, 'UTF-8')");
- $("f3").contentWindow.location.reload();
+ "Wrong color");
+ charsetTestFinished('f3', doc, "UTF-8");
}
function runTest() {
@@ -74,12 +56,7 @@ function runTest() {
doc.open();
doc.write('<html></html>');
doc.close();
- is(doc.characterSet, "UTF-8",
- "Unexpected character set for first frame after write");
- $("f1").
- setAttribute("onload",
- "charsetTestFinished('f1', this.contentDocument, 'UTF-8')");
- $("f1").contentWindow.location.reload();
+ charsetTestFinished("f1", doc, "UTF-8");
doc = $("f2").contentDocument;
is(doc.characterSet, "UTF-8",
@@ -96,12 +73,11 @@ function runTest() {
"Unexpected character set for second frame after write");
$("f2").
setAttribute("onload",
- "charsetTestFinished('f2', this.contentDocument, 'UTF-8');" +
- "f2Continue()");
+ "charsetTestFinished('f2', this.contentDocument, 'UTF-8');");
doc = $("f3").contentDocument;
is(doc.characterSet, "UTF-8",
- "Unexpected initial character set for first frame");
+ "Unexpected initial character set for third frame");
doc.open();
var str = '<html><head>';
str += '<style>body { color: rgb(255, 0, 0) }</style>';
@@ -111,7 +87,7 @@ function runTest() {
doc.write(str);
doc.close();
is(doc.characterSet, "UTF-8",
- "Unexpected character set for first frame after write");
+ "Unexpected character set for third frame after write");
$("f3").setAttribute("onload", "f3Continue()");
}
diff --git a/dom/tests/mochitest/bugs/test_bug346659.html b/dom/tests/mochitest/bugs/test_bug346659.html
index 78c1fc659..8596de7b1 100644
--- a/dom/tests/mochitest/bugs/test_bug346659.html
+++ b/dom/tests/mochitest/bugs/test_bug346659.html
@@ -108,7 +108,7 @@ function messageReceiver(evt) {
is(testResult, "undefined", "Props on new window's child should go away when loading");
break;
case 6:
- is(testResult, "undefined", "Props on new window's child should go away when writing");
+ is(testResult, "6", "Props on new window's child should go away when writing");
break;
case 7:
is(testResult, "7", "Props on different-domain window opened from different-domain new window can stay");
diff --git a/js/xpconnect/crashtests/crashtests.list b/js/xpconnect/crashtests/crashtests.list
index 7325e2601..51f4b8643 100644
--- a/js/xpconnect/crashtests/crashtests.list
+++ b/js/xpconnect/crashtests/crashtests.list
@@ -43,7 +43,7 @@ load 732870.html
load 751995.html
load 761831.html
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
asserts(0-1) load 786142.html # We may hit bug 645229 here.
load 797583.html
diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp
index 5478c61b0..8baf1a464 100644
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -998,6 +998,9 @@ nsDocumentViewer::LoadComplete(nsresult aStatus)
// will depend on whether it's cached!
if(window &&
(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;
WidgetEvent event(true, eLoad);
event.mFlags.mBubbles = false;
@@ -1063,7 +1066,9 @@ nsDocumentViewer::LoadComplete(nsresult aStatus)
MakeUnique<DocLoadingTimelineMarker>("document::Load"));
}
+ d->SetLoadEventFiring(true);
EventDispatcher::Dispatch(window, mPresContext, &event, nullptr, &status);
+ d->SetLoadEventFiring(false);
if (timing) {
timing->NotifyLoadEventEnd();
}
diff --git a/layout/style/test/test_bug1232829.html b/layout/style/test/test_bug1232829.html
index 8981d56e0..65bea2014 100644
--- a/layout/style/test/test_bug1232829.html
+++ b/layout/style/test/test_bug1232829.html
@@ -19,7 +19,6 @@ function boom() {
setTimeout(function() {
var frameDoc = document.querySelector("iframe").contentDocument;
frameDoc.write("3");
- frameDoc.defaultView.history.back();
requestAnimationFrame(function() {
popup.close();
ok(true, "Didn't crash");
diff --git a/parser/htmlparser/tests/mochitest/test_bug715739.html b/parser/htmlparser/tests/mochitest/test_bug715739.html
index 597160a19..e59fdf332 100644
--- a/parser/htmlparser/tests/mochitest/test_bug715739.html
+++ b/parser/htmlparser/tests/mochitest/test_bug715739.html
@@ -33,24 +33,30 @@ function textChildren(node) {
return s;
}
+var f, d;
+
function tick() {
runNumber++;
- var f = document.getElementsByTagName("iframe")[0];
- var d = f.contentDocument;
+ f = document.getElementsByTagName("iframe")[0];
+ d = f.contentDocument;
if (runNumber == 1) {
- d.open();
- f.addEventListener("load", tick);
- d.write("X");
- d.write("\u003cscript>document.write('Y');\u003c/script>");
- d.write("Z");
- d.close();
+ frames[1].setTimeout(`
+ var d = parent.d;
+ var f = parent.f;
+ d.open();
+ f.addEventListener("load", parent.tick);
+ d.write("X");
+ d.write("\u003cscript>document.write('Y');\u003c/script>");
+ d.write("Z");
+ d.close();
+ `);
return;
}
if (runNumber == 2) {
var text = textChildren(d.body);
- is(text, "XYZ", "Wrong text before reload.");
+ is(text, "ABC", "Wrong text before reload.");
f.contentWindow.location.reload();
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>
</pre>
<div id="content" style="display: none">
<iframe></iframe>
+ <iframe></iframe>
</div>
+<script>
+ document.querySelectorAll("iframe")[1].src = blobURL;
+</script>
</body>
</html>
diff --git a/security/manager/ssl/tests/mochitest/mixedcontent/test_bug329869.html b/security/manager/ssl/tests/mochitest/mixedcontent/test_bug329869.html
index 3b8fae25e..596b65628 100644
--- a/security/manager/ssl/tests/mochitest/mixedcontent/test_bug329869.html
+++ b/security/manager/ssl/tests/mochitest/mixedcontent/test_bug329869.html
@@ -25,7 +25,7 @@
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();
}
diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1-1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1-1.html
index af0118a01..945c8d81f 100644
--- a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1-1.html
+++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1-1.html
@@ -5,11 +5,8 @@
opener.pages.push(2);
onload = 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();
- if (opener.started) {
- opener.start_test_wait();
- }
}, 100);
}
</script>
diff --git a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1.html b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1.html
index c2c31e76f..404d61d0c 100644
--- a/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1.html
+++ b/testing/web-platform/tests/html/browsers/history/the-history-interface/traverse_the_history_write_after_load_1.html
@@ -11,12 +11,11 @@
function() {
check_result = t.step_func(
function() {
- if (pages.length < 4) {
+ if (pages.length < 3) {
setTimeout(check_result, 500);
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, 2, 3], "Pages opened during history navigation");
+ assert_array_equals(pages, [2, 3, 1], "Pages opened during history navigation");
t.done();
}
)
diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_open_write-1.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_open_write-1.html
index 1c5a1db8f..e1a2e811c 100644
--- a/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_open_write-1.html
+++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_open_write-1.html
@@ -3,11 +3,16 @@
<script>
function f() {
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 () {
document.open();
document.write("<!doctype html>2<script>opener.postMessage('written', '*');<\/script>");
document.close();
- }), 100;
+ });
}
window.onload = f
diff --git a/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_open_write.html b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_open_write.html
index 0fc2a2c3d..905ef8874 100644
--- a/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_open_write.html
+++ b/testing/web-platform/tests/html/browsers/history/the-location-interface/reload_document_open_write.html
@@ -11,11 +11,11 @@ var data = [];
window.onmessage = t.step_func(function(e) {
data.push(e.data);
- if (data.length < 3) {
+ if (data.length == 2) {
win.location.reload();
- } else {
+ } else if (data.length >= 3) {
setTimeout(t.step_func(function() {
- assert_array_equals(data, ["original", "written", "written"]);
+ assert_array_equals(data, ["original", "written", "original"]);
t.done();
}), 500);
}
diff --git a/uriloader/base/nsDocLoader.cpp b/uriloader/base/nsDocLoader.cpp
index 69885b93f..a4beb4827 100644
--- a/uriloader/base/nsDocLoader.cpp
+++ b/uriloader/base/nsDocLoader.cpp
@@ -4,6 +4,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nspr.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/EventDispatcher.h"
#include "mozilla/Logging.h"
#include "nsDocLoader.h"
@@ -23,6 +25,7 @@
#include "nsQueryObject.h"
#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
#include "nsIStringBundle.h"
#include "nsIScriptSecurityManager.h"
@@ -36,7 +39,10 @@
#include "nsIAsyncVerifyRedirectCallback.h"
using mozilla::DebugOnly;
+using mozilla::eLoad;
+using mozilla::EventDispatcher;
using mozilla::LogLevel;
+using mozilla::WidgetEvent;
static NS_DEFINE_CID(kThisImplCID, NS_THIS_DOCLOADER_IMPL_CID);
@@ -114,7 +120,8 @@ nsDocLoader::nsDocLoader()
mIsLoadingDocument(false),
mIsRestoringDocument(false),
mDontFlushLayout(false),
- mIsFlushingLayout(false)
+ mIsFlushingLayout(false),
+ mDocumentOpenedButNotLoaded(false)
{
ClearInternalProgress();
@@ -289,7 +296,7 @@ nsDocLoader::IsBusy()
}
/* Is this document loader busy? */
- if (!mIsLoadingDocument) {
+ if (!IsBlockingLoadEvent()) {
return false;
}
@@ -414,6 +421,7 @@ nsDocLoader::OnStartRequest(nsIRequest *request, nsISupports *aCtxt)
if (!mIsLoadingDocument && (loadFlags & nsIChannel::LOAD_DOCUMENT_URI)) {
bJustStartedLoading = true;
mIsLoadingDocument = true;
+ mDocumentOpenedButNotLoaded = false;
ClearInternalProgress(); // only clear our progress if we are starting a new load....
}
@@ -480,10 +488,11 @@ nsDocLoader::OnStopRequest(nsIRequest *aRequest,
mLoadGroup->GetActiveCount(&count);
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(),
aStatus, (mIsLoadingDocument ? "true" : "false"),
- count));
+ (mDocumentOpenedButNotLoaded ? "true" : "false"), count));
}
bool bFireTransferring = false;
@@ -598,10 +607,9 @@ nsDocLoader::OnStopRequest(nsIRequest *aRequest,
RemoveRequestInfo(aRequest);
//
- // Only fire the DocLoaderIsEmpty(...) if the document loader has initiated a
- // load. This will handle removing the request from our hashtable as needed.
+ // Only fire the DocLoaderIsEmpty(...) if we may need to fire onload.
//
- if (mIsLoadingDocument) {
+ if (IsBlockingLoadEvent()) {
nsCOMPtr<nsIDocShell> ds = do_QueryInterface(static_cast<nsIRequestObserver*>(this));
bool doNotFlushLayout = false;
if (ds) {
@@ -647,7 +655,7 @@ NS_IMETHODIMP nsDocLoader::GetDocumentChannel(nsIChannel ** aChannel)
void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout)
{
- if (mIsLoadingDocument) {
+ if (IsBlockingLoadEvent()) {
/* In the unimagineably rude circumstance that onload event handlers
triggered by this function actually kill the window ... ok, it's
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(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.
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
// the layout flush.
- // Note, mDocumentRequest can be null if the flushing above re-entered this
- // method.
- if (!IsBusy() && mDocumentRequest) {
+ //
+ // Note, mDocumentRequest can be null while mDocumentOpenedButNotLoaded is
+ // 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
// we don't need it anymore to CalculateMaxProgress().
ClearInternalProgress();
@@ -715,7 +730,7 @@ void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout)
//
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.
RefPtr<nsDocLoader> parent = mParent;
@@ -733,6 +748,67 @@ void nsDocLoader::DocLoaderIsEmpty(bool aFlushLayout)
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);
+ }
+ }
}
}
}
diff --git a/uriloader/base/nsDocLoader.h b/uriloader/base/nsDocLoader.h
index 481b1397b..b469b8e07 100644
--- a/uriloader/base/nsDocLoader.h
+++ b/uriloader/base/nsDocLoader.h
@@ -109,6 +109,8 @@ public:
unsigned long mNotifyMask;
};
+ void SetDocumentOpenedButNotLoaded() { mDocumentOpenedButNotLoaded = true; }
+
protected:
virtual ~nsDocLoader();
@@ -302,6 +304,15 @@ protected:
bool mIsFlushingLayout;
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;
// 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);
void ClearRequestInfoHash();
int64_t CalculateMaxProgress();
-/// void DumpChannelInfo(void);
+ /// void DumpChannelInfo(void);
// used to clear our internal progress state between loads...
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)