Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Bug 385434: Add support for HTML5 onhashchange. r=smaug, sr=sicking
  • Loading branch information
jlebar committed Jun 26, 2009
1 parent 0f90334 commit 8e7e8ce
Show file tree
Hide file tree
Showing 22 changed files with 490 additions and 14 deletions.
5 changes: 3 additions & 2 deletions content/base/public/nsIDocument.h
Expand Up @@ -105,8 +105,8 @@ class nsIBoxObject;

// IID for the nsIDocument interface
#define NS_IDOCUMENT_IID \
{0x282f1cd0, 0x6dfb, 0x4cff, \
{0x83, 0x3e, 0x05, 0x98, 0x52, 0xe6, 0xd8, 0x59 } }
{0x2c155ed0, 0x3302, 0x4cff, \
{0x9d, 0xb3, 0xed, 0x0c, 0xcd, 0xfc, 0x50, 0x06 } }

// Flag for AddStyleSheet().
#define NS_STYLESHEET_FROM_CATALOG (1 << 0)
Expand Down Expand Up @@ -635,6 +635,7 @@ class nsIDocument : public nsINode

enum ReadyState { READYSTATE_UNINITIALIZED = 0, READYSTATE_LOADING = 1, READYSTATE_INTERACTIVE = 3, READYSTATE_COMPLETE = 4};
virtual void SetReadyStateInternal(ReadyState rs) = 0;
virtual ReadyState GetReadyStateEnum() = 0;

// notify that one or two content nodes changed state
// either may be nsnull, but not both
Expand Down
1 change: 1 addition & 0 deletions content/base/src/nsContentUtils.cpp
Expand Up @@ -402,6 +402,7 @@ nsContentUtils::InitializeEventTable() {
{ &nsGkAtoms::onload, { NS_LOAD, EventNameType_All }},
{ &nsGkAtoms::onunload, { NS_PAGE_UNLOAD,
(EventNameType_HTMLXUL | EventNameType_SVGSVG) }},
{ &nsGkAtoms::onhashchange, { NS_HASHCHANGE, EventNameType_HTMLXUL }},
{ &nsGkAtoms::onbeforeunload, { NS_BEFORE_PAGE_UNLOAD, EventNameType_HTMLXUL }},
{ &nsGkAtoms::onabort, { NS_IMAGE_ABORT,
(EventNameType_HTMLXUL | EventNameType_SVGSVG) }},
Expand Down
5 changes: 5 additions & 0 deletions content/base/src/nsDocument.cpp
Expand Up @@ -7424,6 +7424,11 @@ nsDocument::SetReadyStateInternal(ReadyState rs)
// TODO fire "readystatechange"
}

nsIDocument::ReadyState
nsDocument::GetReadyStateEnum()
{
return mReadyState;
}

NS_IMETHODIMP
nsDocument::GetReadyState(nsAString& aReadyState)
Expand Down
1 change: 1 addition & 0 deletions content/base/src/nsDocument.h
Expand Up @@ -768,6 +768,7 @@ class nsDocument : public nsIDocument,
virtual void EndLoad();

virtual void SetReadyStateInternal(ReadyState rs);
virtual ReadyState GetReadyStateEnum();

virtual void ContentStatesChanged(nsIContent* aContent1,
nsIContent* aContent2,
Expand Down
1 change: 1 addition & 0 deletions content/base/src/nsGkAtomList.h
Expand Up @@ -637,6 +637,7 @@ GK_ATOM(ondrop, "ondrop")
GK_ATOM(onerror, "onerror")
GK_ATOM(onfocus, "onfocus")
GK_ATOM(onget, "onget")
GK_ATOM(onhashchange, "onhashchange")
GK_ATOM(oninput, "oninput")
GK_ATOM(onkeydown, "onkeydown")
GK_ATOM(onkeypress, "onkeypress")
Expand Down
6 changes: 5 additions & 1 deletion content/events/src/nsDOMEvent.cpp
Expand Up @@ -59,7 +59,7 @@
static const char* const sEventNames[] = {
"mousedown", "mouseup", "click", "dblclick", "mouseover",
"mouseout", "mousemove", "contextmenu", "keydown", "keyup", "keypress",
"focus", "blur", "load", "beforeunload", "unload", "abort", "error",
"focus", "blur", "load", "beforeunload", "unload", "hashchange", "abort", "error",
"submit", "reset", "change", "select", "input", "paint" ,"text",
"compositionstart", "compositionend", "popupshowing", "popupshown",
"popuphiding", "popuphidden", "close", "command", "broadcast", "commandupdate",
Expand Down Expand Up @@ -556,6 +556,8 @@ nsDOMEvent::SetEventType(const nsAString& aEventTypeArg)
mEvent->message = NS_PAGE_SHOW;
else if (atom == nsGkAtoms::onpagehide)
mEvent->message = NS_PAGE_HIDE;
else if (atom == nsGkAtoms::onhashchange)
mEvent->message = NS_HASHCHANGE;
} else if (mEvent->eventStructType == NS_MUTATION_EVENT) {
if (atom == nsGkAtoms::onDOMAttrModified)
mEvent->message = NS_MUTATION_ATTRMODIFIED;
Expand Down Expand Up @@ -1268,6 +1270,8 @@ const char* nsDOMEvent::GetEventName(PRUint32 aEventType)
return sEventNames[eDOMEvents_beforeunload];
case NS_PAGE_UNLOAD:
return sEventNames[eDOMEvents_unload];
case NS_HASHCHANGE:
return sEventNames[eDOMEvents_hashchange];
case NS_IMAGE_ABORT:
return sEventNames[eDOMEvents_abort];
case NS_LOAD_ERROR:
Expand Down
1 change: 1 addition & 0 deletions content/events/src/nsDOMEvent.h
Expand Up @@ -77,6 +77,7 @@ class nsDOMEvent : public nsIDOMEvent,
eDOMEvents_load,
eDOMEvents_beforeunload,
eDOMEvents_unload,
eDOMEvents_hashchange,
eDOMEvents_abort,
eDOMEvents_error,
eDOMEvents_submit,
Expand Down
39 changes: 32 additions & 7 deletions docshell/base/nsDocShell.cpp
Expand Up @@ -7809,8 +7809,12 @@ nsDocShell::InternalLoad(nsIURI * aURI,
aLoadType == LOAD_HISTORY ||
aLoadType == LOAD_LINK) && allowScroll) {
PRBool wasAnchor = PR_FALSE;
PRBool doHashchange = PR_FALSE;
nscoord cx, cy;
NS_ENSURE_SUCCESS(ScrollIfAnchor(aURI, &wasAnchor, aLoadType, &cx, &cy), NS_ERROR_FAILURE);
NS_ENSURE_SUCCESS(ScrollIfAnchor(aURI, &wasAnchor, aLoadType, &cx, &cy,
&doHashchange),
NS_ERROR_FAILURE);

if (wasAnchor) {
mLoadType = aLoadType;
mURIResultedInDocument = PR_TRUE;
Expand Down Expand Up @@ -7903,6 +7907,14 @@ nsDocShell::InternalLoad(nsIURI * aURI,
shEntry->SetTitle(mTitle);
}

if (doHashchange) {
nsCOMPtr<nsPIDOMWindow> window =
do_QueryInterface(mScriptGlobal);

if (window)
window->DispatchAsyncHashchange();
}

return NS_OK;
}
}
Expand Down Expand Up @@ -8520,18 +8532,21 @@ nsDocShell::CheckClassifier(nsIChannel *aChannel)
return NS_OK;
}

NS_IMETHODIMP
nsresult
nsDocShell::ScrollIfAnchor(nsIURI * aURI, PRBool * aWasAnchor,
PRUint32 aLoadType, nscoord *cx, nscoord *cy)
PRUint32 aLoadType, nscoord *cx, nscoord *cy,
PRBool * aDoHashchange)
{
NS_ASSERTION(aURI, "null uri arg");
NS_ASSERTION(aWasAnchor, "null anchor arg");
NS_PRECONDITION(aDoHashchange, "null hashchange arg");

if (aURI == nsnull || aWasAnchor == nsnull) {
if (!aURI || !aWasAnchor) {
return NS_ERROR_FAILURE;
}

*aWasAnchor = PR_FALSE;
*aDoHashchange = PR_FALSE;

if (!mCurrentURI) {
return NS_OK;
Expand Down Expand Up @@ -8562,7 +8577,7 @@ nsDocShell::ScrollIfAnchor(nsIURI * aURI, PRBool * aWasAnchor,
const char kHash = '#';

// Split the new URI into a left and right part
// (assume we're parsing it out right
// (assume we're parsing it out right)
nsACString::const_iterator urlStart, urlEnd, refStart, refEnd;
newSpec.BeginReading(urlStart);
newSpec.EndReading(refEnd);
Expand All @@ -8589,8 +8604,10 @@ nsDocShell::ScrollIfAnchor(nsIURI * aURI, PRBool * aWasAnchor,
const nsACString& sNewRef = Substring(refStart, refEnd);

// Split the current URI in a left and right part
nsACString::const_iterator currentLeftStart, currentLeftEnd;
nsACString::const_iterator currentLeftStart, currentLeftEnd,
currentRefStart, currentRefEnd;
currentSpec.BeginReading(currentLeftStart);
currentSpec.EndReading(currentRefEnd);

PRInt32 hashCurrent = currentSpec.FindChar(kHash);
if (hashCurrent == 0) {
Expand All @@ -8600,9 +8617,13 @@ nsDocShell::ScrollIfAnchor(nsIURI * aURI, PRBool * aWasAnchor,
if (hashCurrent > 0) {
currentLeftEnd = currentLeftStart;
currentLeftEnd.advance(hashCurrent);

currentRefStart = currentLeftEnd;
++currentRefStart; // advance past '#'
}
else {
currentSpec.EndReading(currentLeftEnd);
// no hash at all in currentSpec
currentLeftEnd = currentRefStart = currentRefEnd;
}

// If we have no new anchor, we do not want to scroll, unless there is a
Expand Down Expand Up @@ -8632,6 +8653,10 @@ nsDocShell::ScrollIfAnchor(nsIURI * aURI, PRBool * aWasAnchor,
// Now we know we are dealing with an anchor
*aWasAnchor = PR_TRUE;

// We should fire a hashchange event once we're done here if the two hashes
// are different.
*aDoHashchange = !Substring(currentRefStart, currentRefEnd).Equals(sNewRef);

// Both the new and current URIs refer to the same page. We can now
// browse to the hash stored in the new URI.
//
Expand Down
11 changes: 9 additions & 2 deletions docshell/base/nsDocShell.h
Expand Up @@ -360,8 +360,15 @@ class nsDocShell : public nsDocLoader,
// complete.
nsresult CheckClassifier(nsIChannel *aChannel);

NS_IMETHOD ScrollIfAnchor(nsIURI * aURI, PRBool * aWasAnchor,
PRUint32 aLoadType, nscoord *cx, nscoord *cy);
nsresult ScrollIfAnchor(nsIURI * aURI, PRBool * aWasAnchor,
PRUint32 aLoadType, nscoord *cx, nscoord *cy,
PRBool * aDoHashchange);

// Dispatches the hashchange event to the current thread, if the document's
// readystate is "complete".
nsresult DispatchAsyncHashchange();

nsresult FireHashchange();

// Returns PR_TRUE if would have called FireOnLocationChange,
// but did not because aFireOnLocationChange was false on entry.
Expand Down
5 changes: 5 additions & 0 deletions docshell/test/Makefile.in
Expand Up @@ -72,6 +72,11 @@ _TEST_FILES = \
test_bug402210.html \
test_bug475636.html \
file_bug475636.sjs \
test_bug385434.html \
file_bug385434_1.html \
file_bug385434_2.html \
file_bug385434_3.html \
file_bug385434_4.html \
$(NULL)

libs:: $(_TEST_FILES)
Expand Down
29 changes: 29 additions & 0 deletions docshell/test/file_bug385434_1.html
@@ -0,0 +1,29 @@
<!--
Inner frame for test of bug 385434.
https://bugzilla.mozilla.org/show_bug.cgi?id=385434
-->
<html>
<head>
<script type="application/javascript">
function hashchange() {
parent.onIframeHashchange();
}

function load() {
parent.onIframeLoad();
}

function scroll() {
parent.onIframeScroll();
}
</script>
</head>

<body onscroll="scroll()" onload="load()" onhashchange="hashchange()">
<a href="#link1" id="link1">link1</a>
<!-- Our parent loads us in an iframe with height 100px, so this spacer ensures
that switching between #link1 and #link2 causes us to scroll -->
<div style="height:200px;"></div>
<a href="#link2" id="link2">link2</a>
</body>
</html>
26 changes: 26 additions & 0 deletions docshell/test/file_bug385434_2.html
@@ -0,0 +1,26 @@
<!--
Inner frame for test of bug 385434.
https://bugzilla.mozilla.org/show_bug.cgi?id=385434
-->
<html>
<head>
<script type="application/javascript">
function hashchange(e) {
// pass the event back to the parent so it can check its properties.
parent.gSampleEvent = e;

parent.statusMsg("Hashchange in 2.");
parent.onIframeHashchange();
}

function load() {
parent.statusMsg("Loading 2.");
parent.onIframeLoad();
}
</script>
</head>

<frameset onload="load()" onhashchange="hashchange(event)">
<frame src="about:blank" />
</frameset>
</html>
27 changes: 27 additions & 0 deletions docshell/test/file_bug385434_3.html
@@ -0,0 +1,27 @@
<!--
Inner frame for test of bug 385434.
https://bugzilla.mozilla.org/show_bug.cgi?id=385434
-->
<html>
<head>
<script type="application/javascript">
// Notify our parent if we have a hashchange and once we're done loading.
window.addEventListener("hashchange", parent.onIframeHashchange, false);
window.addEventListener("load", parent.onIframeLoad, false);

// This shouldn't trigger a hashchange, because we haven't finished loading
// the document.
window.location.hash = "1";

window.addEventListener("DOMContentLoaded", function() {
// This also shouldn't trigger a hashchange, becuase the readystate is
// "interactive", not "complete" during DOMContentLoaded.
window.location.hash = "2";
}, false);

</script>
</head>

<body>
</body>
</html>
36 changes: 36 additions & 0 deletions docshell/test/file_bug385434_4.html
@@ -0,0 +1,36 @@
<!--
Inner frame for test of bug 385434.
https://bugzilla.mozilla.org/show_bug.cgi?id=385434
-->
<html>
<head>
<script type="application/javascript">
window.addEventListener("hashchange", function() {
parent.statusMsg("Hashchange in 4a.");
parent.onIframeHashchange();
}, false);

window.addEventListener("load", function() {
parent.statusMsg("Load listener.");
document.location.hash = "foo";

// synchronously wipe out the whole document. The new document shouldn't
// have a hashchange event dispatched to it.
document.open();

window.addEventListener("hashchange", function() {
parent.statusMsg("Hashchange in 4a after document.open()");
parent.onIframeHashchange();
}, false);

}, false);

</script>
</head>
<body>
<!-- This page is loaded in an iframe 100px high, so the div below forces #foo
off the screen, and we can count that focusing it will trigger a scroll.-->
<div style="height:200px"></div>
<a name="foo">Foo</a>
</body>
</html>

0 comments on commit 8e7e8ce

Please sign in to comment.