Skip to content
Permalink
Browse files
<rdar://problem/7214236> and http://webkit.org/b/32052 - Implement HT…
…ML5 state object history API

Reviewed by Sam Weinig.

WebCore:

Tests: fast/loader/stateobjects/document-destroyed-navigate-back.html
       fast/loader/stateobjects/document-destroyed-navigate-back-with-fragment-scroll.html
       fast/loader/stateobjects/popstate-after-load-complete-addeventlistener.html
       fast/loader/stateobjects/popstate-after-load-complete-body-attribute.html
       fast/loader/stateobjects/popstate-after-load-complete-window-attribute.html
       fast/loader/stateobjects/pushstate-object-types.html
       fast/loader/stateobjects/pushstate-then-replacestate.html
       fast/loader/stateobjects/pushstate-with-fragment-urls-and-hashchange.html
       fast/loader/stateobjects/replacestate-then-pushstate.html
       http/tests/loading/state-object-security-exception.html

Derived sources and project file changes:
* DerivedSources.cpp:
* DerivedSources.make:
* GNUmakefile.am
* WebCore.pro
* WebCore.vcproj/WebCore.vcproj:
* WebCore.xcodeproj/project.pbxproj:

Add the new PopStateEvent:
* dom/PopStateEvent.cpp: Added.
(WebCore::PopStateEvent::PopStateEvent):
(WebCore::PopStateEvent::initPopStateEvent):
* dom/PopStateEvent.h: Added.
(WebCore::PopStateEvent::create):
(WebCore::PopStateEvent::isPopStateEvent):
(WebCore::PopStateEvent::state):
* dom/PopStateEvent.idl: Added.
* bindings/js/JSPopStateEventCustom.cpp: Added.
(WebCore::JSPopStateEvent::initPopStateEvent):
(WebCore::JSPopStateEvent::state):
* bindings/js/JSEventCustom.cpp:
(WebCore::toJS):
* dom/Event.cpp:
(WebCore::Event::isPopStateEvent):
* dom/Event.h:
* dom/EventNames.h:

Add the "onpopstate" attribute:
* html/HTMLAttributeNames.in:
* html/HTMLBodyElement.cpp:
(WebCore::HTMLBodyElement::parseMappedAttribute):
* html/HTMLBodyElement.idl:
* html/HTMLFrameSetElement.cpp:
(WebCore::HTMLFrameSetElement::parseMappedAttribute):
* html/HTMLFrameSetElement.h:
* html/HTMLFrameSetElement.idl:
* page/DOMWindow.h:
* page/DOMWindow.idl:

Add pushState and replaceState management to the loader and history machinery:
* bindings/js/JSHistoryCustom.cpp:
(WebCore::JSHistory::pushState):
(WebCore::JSHistory::replaceState):
* loader/HistoryController.cpp:
(WebCore::HistoryController::updateForSameDocumentNavigation): Augmented from "scrollToAnchor()", combining
  both the same-document fragment scroll case with the new same-document state object activation case.
(WebCore::HistoryController::pushState):
(WebCore::HistoryController::replaceState):
* loader/HistoryController.h:
* history/BackForwardList.cpp:
(WebCore::BackForwardList::addItem): Use insertItemAfterCurrent.
(WebCore::BackForwardList::insertItemAfterCurrent): Optionally insert the item without clearing the forward
  list, as pushStateItem might've selectively cleared only certain items, with the bulk of the forward list
  meant to remain.
(WebCore::BackForwardList::pushStateItem): Clear the forward list *only* for the state item's document, then
  insert the new item.
(WebCore::BackForwardList::removeItem):
* history/BackForwardList.h:
* page/History.cpp:
(WebCore::History::urlForState):
(WebCore::History::stateObjectAdded):
* page/History.h:
* page/History.idl:

Let HistoryItems and Documents associate with each other, as well as letting HistoryItems contain state objects:
* history/HistoryItem.cpp:
(WebCore::HistoryItem::HistoryItem):
(WebCore::HistoryItem::~HistoryItem):
(WebCore::HistoryItem::setStateObject):
(WebCore::HistoryItem::setDocument):
(WebCore::HistoryItem::documentDetached):
* history/HistoryItem.h:
(WebCore::HistoryItem::stateObject):
(WebCore::HistoryItem::document):
* dom/Document.cpp:
(WebCore::Document::detach): Notify all back/forward history items owned by this Document that it
  is going away.
(WebCore::Document::registerHistoryItem): Manage the list of back/forward history items this document owns.
(WebCore::Document::unregisterHistoryItem): Ditto.
* dom/Document.h:

Add the ability for Documents, DocumentLoaders, and FrameLoaderClients to be notified when a Documents
URL changes as the result of pushState(), replaceState(), or a popstate navigation:
* dom/Document.cpp:
(WebCore::Document::implicitClose): If there's a pending state object, dispatch the popstate event.
(WebCore::Document::updateURLForPushOrReplaceState):
(WebCore::Document::statePopped): If loading is complete, dispatch the popstate event. Otherwise, set
  the pending state object.
* loader/DocumentLoader.cpp:
(WebCore::DocumentLoader::replaceRequestURLForSameDocumentNavigation):
* loader/DocumentLoader.h:
* loader/FrameLoaderClient.h:
* loader/EmptyClients.h:
(WebCore::EmptyFrameLoaderClient::dispatchDidChangeStateObjectForPageForFrame):

Change handling of "loading a HistoryItem" to distinguish between new-Document navigations and same-Document
navigations, combining the old concept of anchor scrolls with the new concept of state object navigations:
* loader/FrameLoader.cpp:
(WebCore::FrameLoader::loadInSameDocument):
(WebCore::FrameLoader::continueFragmentScrollAfterNavigationPolicy):
(WebCore::FrameLoader::navigateWithinDocument):
(WebCore::FrameLoader::navigateToDifferentDocument):
(WebCore::FrameLoader::loadItem):
* loader/FrameLoader.h:
* page/Page.cpp:
(WebCore::Page::goToItem): Changed to allow state object activations to pass through without the load stopping.

WebKit/chromium:

* src/FrameLoaderClientImpl.cpp:
(WebKit::FrameLoaderClientImpl::dispatchDidPushStateWithinPage):
(WebKit::FrameLoaderClientImpl::dispatchDidReplaceStateWithinPage):
(WebKit::FrameLoaderClientImpl::dispatchDidPopStateWithinPage):
* src/FrameLoaderClientImpl.h:

WebKit/gtk:

* WebCoreSupport/FrameLoaderClientGtk.cpp:
(WebKit::FrameLoaderClient::dispatchDidPushStateWithinPage):
(WebKit::FrameLoaderClient::dispatchDidReplaceStateWithinPage):
(WebKit::FrameLoaderClient::dispatchDidPopStateWithinPage):
* WebCoreSupport/FrameLoaderClientGtk.h:

WebKit/mac:

* WebCoreSupport/WebFrameLoaderClient.h:
* WebCoreSupport/WebFrameLoaderClient.mm:
(WebFrameLoaderClient::dispatchDidPushStateWithinPage):
(WebFrameLoaderClient::dispatchDidReplaceStateWithinPage):
(WebFrameLoaderClient::dispatchDidPopStateWithinPage):
* WebView/WebDelegateImplementationCaching.h:
* WebView/WebFrameLoadDelegatePrivate.h:
* WebView/WebView.mm:
(-[WebView _cacheFrameLoadDelegateImplementations]):

WebKit/qt:

* WebCoreSupport/FrameLoaderClientQt.cpp:
(WebCore::FrameLoaderClientQt::dispatchDidPushStateWithinPage):
(WebCore::FrameLoaderClientQt::dispatchDidReplaceStateWithinPage):
(WebCore::FrameLoaderClientQt::dispatchDidPopStateWithinPage):
* WebCoreSupport/FrameLoaderClientQt.h:

WebKit/win:

* Interfaces/IWebFrameLoadDelegatePrivate2.idl:
* WebCoreSupport/WebFrameLoaderClient.cpp:
(WebFrameLoaderClient::dispatchDidPushStateWithinPage):
(WebFrameLoaderClient::dispatchDidReplaceStateWithinPage):
(WebFrameLoaderClient::dispatchDidPopStateWithinPage):
* WebCoreSupport/WebFrameLoaderClient.h:

WebKit/wx:

* WebKitSupport/FrameLoaderClientWx.cpp:
(WebCore::FrameLoaderClientWx::dispatchDidPushStateWithinPage):
(WebCore::FrameLoaderClientWx::dispatchDidReplaceStateWithinPage):
(WebCore::FrameLoaderClientWx::dispatchDidPopStateWithinPage):
* WebKitSupport/FrameLoaderClientWx.h:

WebKitTools:

Keep DRT-win building...

* DumpRenderTree/win/FrameLoadDelegate.h:
(FrameLoadDelegate::didPushStateWithinPageForFrame):
(FrameLoadDelegate::didReplaceStateWithinPageForFrame):
(FrameLoadDelegate::didPopStateWithinPageForFrame):

LayoutTests:

Update expected results of old tests:
* fast/dom/Window/window-appendages-cleared-expected.txt:
* fast/dom/Window/window-properties-expected.txt:
* http/tests/security/cross-frame-access-enumeration-expected.txt:

New tests:
* fast/loader/stateobjects: Added.
* fast/loader/stateobjects/document-destroyed-navigate-back-expected.txt: Added.
* fast/loader/stateobjects/document-destroyed-navigate-back-with-fragment-scroll-expected.txt: Added.
* fast/loader/stateobjects/document-destroyed-navigate-back-with-fragment-scroll.html: Added.
* fast/loader/stateobjects/document-destroyed-navigate-back.html: Added.
* fast/loader/stateobjects/popstate-after-load-complete-addeventlistener-expected.txt: Added.
* fast/loader/stateobjects/popstate-after-load-complete-addeventlistener.html: Added.
* fast/loader/stateobjects/popstate-after-load-complete-body-attribute-expected.txt: Added.
* fast/loader/stateobjects/popstate-after-load-complete-body-attribute.html: Added.
* fast/loader/stateobjects/popstate-after-load-complete-body-inline-attribute-expected.txt: Added.
* fast/loader/stateobjects/popstate-after-load-complete-body-inline-attribute.html: Added.
* fast/loader/stateobjects/popstate-after-load-complete-window-attribute-expected.txt: Added.
* fast/loader/stateobjects/popstate-after-load-complete-window-attribute.html: Added.
* fast/loader/stateobjects/pushstate-object-types-expected.txt: Added.
* fast/loader/stateobjects/pushstate-object-types.html: Added.
* fast/loader/stateobjects/pushstate-then-replacestate-expected.txt: Added.
* fast/loader/stateobjects/pushstate-then-replacestate.html: Added.
* fast/loader/stateobjects/pushstate-with-fragment-urls-and-hashchange-expected.txt: Added.
* fast/loader/stateobjects/pushstate-with-fragment-urls-and-hashchange.html: Added.
* fast/loader/stateobjects/replacestate-then-pushstate-expected.txt: Added.
* fast/loader/stateobjects/replacestate-then-pushstate.html: Added.
* fast/loader/stateobjects/resources: Added.
* fast/loader/stateobjects/resources/navigate-back.html: Added.
* http/tests/loading/state-object-security-exception-expected.txt: Added.
* http/tests/loading/state-object-security-exception.html: Added.



Canonical link: https://commits.webkit.org/43071@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@51644 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
beidson committed Dec 3, 2009
1 parent d3cecf6 commit e544495d282d4726fcb491e0e441ddba338b5ec1
Showing 94 changed files with 2,151 additions and 255 deletions.
@@ -1,3 +1,41 @@
2009-12-03 Brady Eidson <beidson@apple.com>

Reviewed by Sam Weinig.

<rdar://problem/7214236> and http://webkit.org/b/32052 - Implement HTML5 state object history API

Update expected results of old tests:
* fast/dom/Window/window-appendages-cleared-expected.txt:
* fast/dom/Window/window-properties-expected.txt:
* http/tests/security/cross-frame-access-enumeration-expected.txt:

New tests:
* fast/loader/stateobjects: Added.
* fast/loader/stateobjects/document-destroyed-navigate-back-expected.txt: Added.
* fast/loader/stateobjects/document-destroyed-navigate-back-with-fragment-scroll-expected.txt: Added.
* fast/loader/stateobjects/document-destroyed-navigate-back-with-fragment-scroll.html: Added.
* fast/loader/stateobjects/document-destroyed-navigate-back.html: Added.
* fast/loader/stateobjects/popstate-after-load-complete-addeventlistener-expected.txt: Added.
* fast/loader/stateobjects/popstate-after-load-complete-addeventlistener.html: Added.
* fast/loader/stateobjects/popstate-after-load-complete-body-attribute-expected.txt: Added.
* fast/loader/stateobjects/popstate-after-load-complete-body-attribute.html: Added.
* fast/loader/stateobjects/popstate-after-load-complete-body-inline-attribute-expected.txt: Added.
* fast/loader/stateobjects/popstate-after-load-complete-body-inline-attribute.html: Added.
* fast/loader/stateobjects/popstate-after-load-complete-window-attribute-expected.txt: Added.
* fast/loader/stateobjects/popstate-after-load-complete-window-attribute.html: Added.
* fast/loader/stateobjects/pushstate-object-types-expected.txt: Added.
* fast/loader/stateobjects/pushstate-object-types.html: Added.
* fast/loader/stateobjects/pushstate-then-replacestate-expected.txt: Added.
* fast/loader/stateobjects/pushstate-then-replacestate.html: Added.
* fast/loader/stateobjects/pushstate-with-fragment-urls-and-hashchange-expected.txt: Added.
* fast/loader/stateobjects/pushstate-with-fragment-urls-and-hashchange.html: Added.
* fast/loader/stateobjects/replacestate-then-pushstate-expected.txt: Added.
* fast/loader/stateobjects/replacestate-then-pushstate.html: Added.
* fast/loader/stateobjects/resources: Added.
* fast/loader/stateobjects/resources/navigate-back.html: Added.
* http/tests/loading/state-object-security-exception-expected.txt: Added.
* http/tests/loading/state-object-security-exception.html: Added.

2009-12-03 Gustavo Noronha Silva <gustavo.noronha@collabora.co.uk>

Reviewed by Xan Lopez.
@@ -2,6 +2,8 @@ PASS history.back == "LEFTOVER" is false
PASS history.forward == "LEFTOVER" is false
PASS history.go == "LEFTOVER" is false
PASS history.length == "LEFTOVER" is false
PASS history.pushState == "LEFTOVER" is false
PASS history.replaceState == "LEFTOVER" is false
PASS location.assign == "LEFTOVER" is false
PASS location.hash == "LEFTOVER" is false
PASS location.host == "LEFTOVER" is false
@@ -1737,6 +1737,8 @@ window.history.back [function]
window.history.forward [function]
window.history.go [function]
window.history.length [number]
window.history.pushState [function]
window.history.replaceState [function]
window.innerHeight [number]
window.innerWidth [number]
window.length [number]
@@ -1813,6 +1815,7 @@ window.onpageshow [null]
window.onpause [null]
window.onplay [null]
window.onplaying [null]
window.onpopstate [null]
window.onprogress [null]
window.onratechange [null]
window.onreset [null]
@@ -0,0 +1,22 @@
main frame - has 1 onunload handler(s)
ALERT: History length is 2
ALERT: State popped - FirstEntry (type string)
ALERT: State popped - SecondEntry (type string)
ALERT: Navigating back...
main frame - has 1 onunload handler(s)
ALERT: History length is 2
ALERT: window.location is file:///Volumes/Data/svn/OpenSource/LayoutTests/fast/loader/stateobjects/document-destroyed-navigate-back.html?SecondEntryShouldNeverBeReactivated
main frame - has 1 onunload handler(s)
ALERT: History length is 1
ALERT: window.location is file:///Volumes/Data/svn/OpenSource/LayoutTests/fast/loader/stateobjects/document-destroyed-navigate-back.html?FirstEntryShouldNeverBeReactivated
ALERT: Test completed
This test:
-Builds up a list of state object entries.
-Navigates through them to verify that the popstate event is fired.
-Navigates away to a new document, with the old document being destroyed.
-Navigates back to the state object entries and verifies the popstate event is not fired.

History length is 1
window.location is file:///Volumes/Data/svn/OpenSource/LayoutTests/fast/loader/stateobjects/document-destroyed-navigate-back.html?FirstEntryShouldNeverBeReactivated
Test completed

@@ -0,0 +1,23 @@
main frame - has 1 onunload handler(s)
ALERT: History length is 2
ALERT: State popped - FirstEntry (type string)
ALERT: hashChanged - location is file:///Volumes/Data/svn/OpenSource/LayoutTests/fast/loader/stateobjects/document-destroyed-navigate-back-with-fragment-scroll.html#FirstEntry
ALERT: State popped - SecondEntry (type string)
ALERT: hashChanged - location is file:///Volumes/Data/svn/OpenSource/LayoutTests/fast/loader/stateobjects/document-destroyed-navigate-back-with-fragment-scroll.html#SecondEntry
ALERT: Navigating back...
main frame - has 1 onunload handler(s)
ALERT: History length is 2
ALERT: window.location is file:///Volumes/Data/svn/OpenSource/LayoutTests/fast/loader/stateobjects/document-destroyed-navigate-back-with-fragment-scroll.html#SecondEntryShouldNeverBeReactivated
ALERT: hashChanged - location is file:///Volumes/Data/svn/OpenSource/LayoutTests/fast/loader/stateobjects/document-destroyed-navigate-back-with-fragment-scroll.html#FirstEntryShouldNeverBeReactivated
ALERT: Test completed
This test:
-Builds up a list of state object entries with fragment URL.
-Navigates through them to verify that the popstate and hashchanged events are fired.
-Navigates away to a new document, with the old document being destroyed.
-Navigates back to the state object entries and verifies the popstate event is not fired.

History length is 2
window.location is file:///Volumes/Data/svn/OpenSource/LayoutTests/fast/loader/stateobjects/document-destroyed-navigate-back-with-fragment-scroll.html#SecondEntryShouldNeverBeReactivated
hashChanged - location is file:///Volumes/Data/svn/OpenSource/LayoutTests/fast/loader/stateobjects/document-destroyed-navigate-back-with-fragment-scroll.html#FirstEntryShouldNeverBeReactivated
Test completed

@@ -0,0 +1,95 @@
<html>
<head>
<script>

if (window.layoutTestController) {
if (!sessionStorage.stage)
layoutTestController.clearBackForwardList();
layoutTestController.dumpAsText();
layoutTestController.waitUntilDone();
}

function log(txt)
{
document.getElementById("logger").innerText += txt + "\n";
// alert the messages also so DumpRenderTree can capture and log messages across multiple documents.
alert(txt);
}

function endTest(msg)
{
log(msg);
sessionStorage.clear();
if (window.layoutTestController)
layoutTestController.notifyDone();
}

function runSecondStageOfTest()
{
log("History length is " + history.length);
log("window.location is " + window.location);
sessionStorage.stage = 3;
history.back();
}

function runThirdStageOfTest()
{
log("History length is " + history.length);
log("window.location is " + window.location);
endTest("Test completed");
}

function loaded()
{
if (sessionStorage.stage) {
if (sessionStorage.stage == 2)
runSecondStageOfTest();
else if (sessionStorage.stage == 3)
endTest("Shouldn't reach this case when doing fragment scrolls");
else
endTest("Unexpected stage value");
} else
runFirstStageOfTest();
}

function runFirstStageOfTest()
{
history.replaceState("FirstEntry", null, "#FirstEntry");
history.pushState("SecondEntry", null, "#SecondEntry");

log("History length is " + history.length);
history.back();
}

function statePopped()
{
log("State popped - " + event.state + " (type " + typeof event.state + ")");
if (event.state == "FirstEntry") {
history.replaceState("FirstEntryShouldNeverBeReactivated", null, "#FirstEntryShouldNeverBeReactivated");
history.forward();
} else if (event.state == "SecondEntry") {
history.replaceState("SecondEntryShouldNeverBeReactivated", null, "#SecondEntryShouldNeverBeReactivated");
window.location = "resources/navigate-back.html";
} else
endTest("Unexpected state popped - " + event.state);
}

function hashChanged()
{
log("hashChanged - location is " + window.location);
if (window.location.hash == "#FirstEntryShouldNeverBeReactivated")
endTest("Test completed");
}

</script>
<body onload="loaded();" onpopstate="statePopped();" onhashchange="hashChanged();" onunload="/* disable page cache */">
<pre>
This test:
-Builds up a list of state object entries with fragment URL.
-Navigates through them to verify that the popstate and hashchanged events are fired.
-Navigates away to a new document, with the old document being destroyed.
-Navigates back to the state object entries and verifies the popstate event is not fired.
</pre><br>
<pre id="logger"></pre>
</body>
</html>
@@ -0,0 +1,88 @@
<html>
<head>
<script>

if (window.layoutTestController) {
if (!sessionStorage.stage)
layoutTestController.clearBackForwardList();
layoutTestController.dumpAsText();
layoutTestController.waitUntilDone();
}

function log(txt)
{
document.getElementById("logger").innerText += txt + "\n";
// alert the messages also so DumpRenderTree can capture and log messages across multiple documents.
alert(txt);
}

function endTest(msg)
{
log(msg);
sessionStorage.clear();
if (window.layoutTestController)
layoutTestController.notifyDone();
}

function runSecondStageOfTest()
{
log("History length is " + history.length);
log("window.location is " + window.location);
sessionStorage.stage = 3;
history.back();
}

function runThirdStageOfTest()
{
log("History length is " + history.length);
log("window.location is " + window.location);
endTest("Test completed");
}

function loaded()
{
if (sessionStorage.stage) {
if (sessionStorage.stage == 2)
runSecondStageOfTest();
else if (sessionStorage.stage == 3)
runThirdStageOfTest();
else
endTest("Unexpected stage value");
} else
runFirstStageOfTest();
}

function runFirstStageOfTest()
{
history.replaceState("FirstEntry", null, "?FirstEntry");
history.pushState("SecondEntry", null, "?SecondEntry");

log("History length is " + history.length);
history.back();
}

function statePopped()
{
log("State popped - " + event.state + " (type " + typeof event.state + ")");
if (event.state == "FirstEntry") {
history.replaceState("FirstEntryShouldNeverBeReactivated", null, "?FirstEntryShouldNeverBeReactivated");
history.forward();
} else if (event.state == "SecondEntry") {
history.replaceState("SecondEntryShouldNeverBeReactivated", null, "?SecondEntryShouldNeverBeReactivated");
window.location = "resources/navigate-back.html";
} else
endTest("Unexpected state popped - " + event.state);
}

</script>
<body onload="loaded();" onpopstate="statePopped();" onunload="/* disable page cache */">
<pre>
This test:
-Builds up a list of state object entries.
-Navigates through them to verify that the popstate event is fired.
-Navigates away to a new document, with the old document being destroyed.
-Navigates back to the state object entries and verifies the popstate event is not fired.
</pre><br>
<pre id="logger"></pre>
</body>
</html>
@@ -0,0 +1,11 @@
This test does the following:
-Listens for the popstate event using addEventListener
-Makes a call to pushState()
-Makes sure the history length is correct
-Goes back, and makes sure the popstate event is correct
-Goes forward, and makes sure the popstate event is correct

History length is 2
State popped - null (type object)
State popped - StateStringData (type string)

@@ -0,0 +1,46 @@
<html>
<head>
<script>

if (window.layoutTestController) {
layoutTestController.clearBackForwardList();
layoutTestController.dumpAsText();
layoutTestController.waitUntilDone();
}

function log(txt)
{
document.getElementById("logger").innerText += txt + "\n";
}

function runTest()
{
history.pushState("StateStringData", "New title");
log("History length is " + history.length);
history.back();
}

function statePopped()
{
log("State popped - " + event.state + " (type " + typeof event.state + ")");
if (event.state == null)
history.forward();
else if (window.layoutTestController)
layoutTestController.notifyDone();
}

window.addEventListener("popstate", statePopped);

</script>
<body onload="runTest();">
<pre>
This test does the following:
-Listens for the popstate event using addEventListener
-Makes a call to pushState()
-Makes sure the history length is correct
-Goes back, and makes sure the popstate event is correct
-Goes forward, and makes sure the popstate event is correct
</pre><br>
<pre id="logger"></pre>
</body>
</html>
@@ -0,0 +1,11 @@
This test does the following:
-Uses body.onpopstate to add a popstate handler (both by using the inline attribute and a script-assigned attribute)
-Makes a call to pushState()
-Makes sure the history length is correct
-Goes back, and makes sure the popstate event is correct
-Goes forward, and makes sure the popstate event is correct

History length is 2
State popped - null (type object)
State popped - StateStringData (type string)

0 comments on commit e544495

Please sign in to comment.