Bug 500328 - Implement History.pushState(), History.replaceState() methods. r=smaug, r=zpao, sr=sicking

This commit is contained in:
Justin Lebar 2009-09-01 09:45:05 -07:00
Родитель 1444ebd2bf
Коммит 731c252671
54 изменённых файлов: 2155 добавлений и 130 удалений

Просмотреть файл

@ -1334,7 +1334,15 @@ SessionStoreService.prototype = {
}
catch (ex) { debug(ex); }
}
if (aEntry.docIdentifier) {
entry.docIdentifier = aEntry.docIdentifier;
}
if (aEntry.stateData) {
entry.stateData = aEntry.stateData;
}
if (!(aEntry instanceof Ci.nsISHContainer)) {
return entry;
}
@ -2038,9 +2046,11 @@ SessionStoreService.prototype = {
delete this._windows[aWindow.__SSi]._restoring;
}
// helper hash for ensuring unique frame IDs
// helper hashes for ensuring unique frame IDs and unique document
// identifiers.
var idMap = { used: {} };
this.restoreHistory(aWindow, aTabs, aTabData, idMap);
var docIdentMap = {};
this.restoreHistory(aWindow, aTabs, aTabData, idMap, docIdentMap);
},
/**
@ -2054,7 +2064,8 @@ SessionStoreService.prototype = {
* @param aIdMap
* Hash for ensuring unique frame IDs
*/
restoreHistory: function sss_restoreHistory(aWindow, aTabs, aTabData, aIdMap) {
restoreHistory:
function sss_restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap) {
var _this = this;
while (aTabs.length > 0 && (!aTabData[0]._tabStillLoading || !aTabs[0].parentNode)) {
aTabs.shift(); // this tab got removed before being completely restored
@ -2090,7 +2101,8 @@ SessionStoreService.prototype = {
//XXXzpao Wallpaper patch for bug 514751
if (!tabData.entries[i].url)
continue;
history.addEntry(this._deserializeHistoryEntry(tabData.entries[i], aIdMap), true);
history.addEntry(this._deserializeHistoryEntry(tabData.entries[i],
aIdMap, aDocIdentMap), true);
}
// make sure to reset the capabilities and attributes, in case this tab gets reused
@ -2152,7 +2164,9 @@ SessionStoreService.prototype = {
browser.loadURI(tabData.userTypedValue, null, null, true);
}
aWindow.setTimeout(function(){ _this.restoreHistory(aWindow, aTabs, aTabData, aIdMap); }, 0);
aWindow.setTimeout(function(){
_this.restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap);
}, 0);
},
/**
@ -2163,7 +2177,9 @@ SessionStoreService.prototype = {
* Hash for ensuring unique frame IDs
* @returns nsISHEntry
*/
_deserializeHistoryEntry: function sss_deserializeHistoryEntry(aEntry, aIdMap) {
_deserializeHistoryEntry:
function sss_deserializeHistoryEntry(aEntry, aIdMap, aDocIdentMap) {
var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
createInstance(Ci.nsISHEntry);
@ -2195,7 +2211,11 @@ SessionStoreService.prototype = {
}
shEntry.ID = id;
}
if (aEntry.stateData) {
shEntry.stateData = aEntry.stateData;
}
if (aEntry.scroll) {
var scrollPos = (aEntry.scroll || "0,0").split(",");
scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
@ -2216,6 +2236,28 @@ SessionStoreService.prototype = {
shEntry.postData = stream;
}
if (aEntry.docIdentifier) {
// Get a new document identifier for this entry to ensure that history
// entries after a session restore are considered to have different
// documents from the history entries before the session restore.
// Document identifiers are 64-bit ints, so JS will loose precision and
// start assigning all entries the same doc identifier if these ever get
// large enough.
//
// It's a potential security issue if document identifiers aren't
// globally unique, but shEntry.setUniqueDocIdentifier() below guarantees
// that we won't re-use a doc identifier within a given instance of the
// application.
let ident = aDocIdentMap[aEntry.docIdentifier];
if (!ident) {
shEntry.setUniqueDocIdentifier();
aDocIdentMap[aEntry.docIdentifier] = shEntry.docIdentifier;
}
else {
shEntry.docIdentifier = ident;
}
}
if (aEntry.owner_b64) { // Firefox 3
var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].
createInstance(Ci.nsIStringInputStream);
@ -2237,7 +2279,8 @@ SessionStoreService.prototype = {
//XXXzpao Wallpaper patch for bug 514751
if (!aEntry.children[i].url)
continue;
shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap), i);
shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap,
aDocIdentMap), i);
}
}

Просмотреть файл

@ -108,6 +108,7 @@ _BROWSER_TEST_FILES = \
browser_491577.js \
browser_493467.js \
browser_495495.js \
browser_500328.js \
browser_506482.js \
browser_514751.js \
browser_522545.js \

Просмотреть файл

@ -0,0 +1,135 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is sessionstore test code.
*
* The Initial Developer of the Original Code is
* Justin Lebar <justin.lebar@gmail.com>
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
function checkState(tab) {
// Go back and then forward, and make sure that the state objects received
// from the popState event are as we expect them to be.
//
// We also add a node to the document's body when after going back and make
// sure it's still there after we go forward -- this is to test that the two
// history entries correspond to the same document.
let popStateCount = 0;
tab.linkedBrowser.addEventListener("popstate", function(aEvent) {
let contentWindow = tab.linkedBrowser.contentWindow;
if (popStateCount == 0) {
popStateCount++;
ok(aEvent.state, "Event should have a state property.");
is(JSON.stringify(aEvent.state), JSON.stringify({obj1:1}),
"first popstate object.");
// Add a node with id "new-elem" to the document.
let doc = contentWindow.document;
ok(!doc.getElementById("new-elem"),
"doc shouldn't contain new-elem before we add it.");
let elem = doc.createElement("div");
elem.id = "new-elem";
doc.body.appendChild(elem);
contentWindow.history.forward();
}
else if (popStateCount == 1) {
popStateCount++;
is(JSON.stringify(aEvent.state), JSON.stringify({obj3:3}),
"second popstate object.");
// Make sure that the new-elem node is present in the document. If it's
// not, then this history entry has a different doc identifier than the
// previous entry, which is bad.
let doc = contentWindow.document;
let newElem = doc.getElementById("new-elem");
ok(newElem, "doc should contain new-elem.");
newElem.parentNode.removeChild(newElem);
ok(!doc.getElementById("new-elem"), "new-elem should be removed.");
// Clean up after ourselves and finish the test.
tab.linkedBrowser.removeEventListener("popstate", arguments.callee, false);
gBrowser.removeTab(tab);
finish();
}
}, true);
tab.linkedBrowser.contentWindow.history.back();
}
function test() {
// Tests session restore functionality of history.pushState and
// history.replaceState(). (Bug 500328)
let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
waitForExplicitFinish();
// We open a new blank window, let it load, and then load in
// http://example.com. We need to load the blank window first, otherwise the
// docshell gets confused and doesn't have a current history entry.
let tab = gBrowser.addTab("about:blank");
let tabBrowser = tab.linkedBrowser;
tabBrowser.addEventListener("load", function(aEvent) {
tabBrowser.removeEventListener("load", arguments.callee, true);
tabBrowser.loadURI("http://example.com", null, null);
tabBrowser.addEventListener("load", function(aEvent) {
tabBrowser.removeEventListener("load", arguments.callee, true);
// After these push/replaceState calls, the window should have three
// history entries:
// testURL (state object: null) <-- oldest
// testURL (state object: {obj1:1})
// page2 (state object: {obj3:3}) <-- newest
let contentWindow = tab.linkedBrowser.contentWindow;
let history = contentWindow.history;
history.pushState({obj1:1}, "title-obj1");
history.pushState({obj2:2}, "title-obj2", "page2");
history.replaceState({obj3:3}, "title-obj3");
let state = ss.getTabState(tab);
// In order to make sure that setWindowState actually modifies the
// window's state, we modify the state here. checkState will fail if
// this change isn't overwritten by setWindowState.
history.replaceState({should_be_overwritten:true}, "title-overwritten");
// Restore the state and make sure it looks right, after giving the event
// loop a chance to flush.
ss.setTabState(tab, state, true);
executeSoon(function() { checkState(tab); });
}, true);
}, true);
}

Просмотреть файл

@ -106,8 +106,8 @@ class nsIBoxObject;
// IID for the nsIDocument interface
#define NS_IDOCUMENT_IID \
{ 0xb04d9176, 0xf087, 0x4d3c, \
{ 0x87, 0x11, 0x13, 0x9d, 0x19, 0x95, 0x43, 0x55 } }
{ 0x6b2f1996, 0x95d4, 0x48db, \
{0xaf, 0xd1, 0xfd, 0xaa, 0x75, 0x4c, 0x79, 0x92 } }
// Flag for AddStyleSheet().
#define NS_STYLESHEET_FROM_CATALOG (1 << 0)
@ -1225,6 +1225,28 @@ public:
Doc_Theme_Bright
};
/**
* Returns the document's pending state object (serialized to JSON), or the
* empty string if one doesn't exist.
*
* This field serves as a waiting place for the history entry's state object:
* We set the field's value to the history entry's state object early on in
* the load, then after we fire onload we deserialize the field's value and
* fire a popstate event containing the resulting object.
*/
nsAString& GetPendingStateObject()
{
return mPendingStateObject;
}
/**
* Set the document's pending state object (as serialized to JSON).
*/
void SetPendingStateObject(nsAString &obj)
{
mPendingStateObject.Assign(obj);
}
/**
* Returns Doc_Theme_None if there is no lightweight theme specified,
* Doc_Theme_Dark for a dark theme, Doc_Theme_Bright for a light theme, and
@ -1388,6 +1410,8 @@ protected:
nsCOMPtr<nsIDocument> mDisplayDocument;
PRUint32 mEventsSuppressed;
nsString mPendingStateObject;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocument, NS_IDOCUMENT_IID)

Просмотреть файл

@ -407,6 +407,7 @@ nsContentUtils::InitializeEventTable() {
{ &nsGkAtoms::onchange, { NS_FORM_CHANGE, EventNameType_HTMLXUL }},
{ &nsGkAtoms::onselect, { NS_FORM_SELECTED, EventNameType_HTMLXUL }},
{ &nsGkAtoms::onload, { NS_LOAD, EventNameType_All }},
{ &nsGkAtoms::onpopstate, { NS_POPSTATE, EventNameType_HTMLXUL }},
{ &nsGkAtoms::onunload, { NS_PAGE_UNLOAD,
(EventNameType_HTMLXUL | EventNameType_SVGSVG) }},
{ &nsGkAtoms::onhashchange, { NS_HASHCHANGE, EventNameType_HTMLXUL }},

Просмотреть файл

@ -650,6 +650,7 @@ GK_ATOM(onkeypress, "onkeypress")
GK_ATOM(onkeyup, "onkeyup")
GK_ATOM(onLoad, "onLoad")
GK_ATOM(onload, "onload")
GK_ATOM(onpopstate, "onpopstate")
GK_ATOM(only, "only") // this one is not an event
GK_ATOM(onmousedown, "onmousedown")
GK_ATOM(onmousemove, "onmousemove")

Просмотреть файл

@ -82,6 +82,7 @@ CPPSRCS = \
nsDOMEventTargetHelper.cpp \
nsDOMScrollAreaEvent.cpp \
nsDOMTransitionEvent.cpp \
nsDOMPopStateEvent.cpp \
$(NULL)
# we don't want the shared lib, but we want to force the creation of a static lib.

Просмотреть файл

@ -55,12 +55,13 @@
#include "nsIURI.h"
#include "nsIScriptSecurityManager.h"
#include "nsIScriptError.h"
#include "nsDOMPopStateEvent.h"
static const char* const sEventNames[] = {
"mousedown", "mouseup", "click", "dblclick", "mouseover",
"mouseout", "mousemove", "contextmenu", "keydown", "keyup", "keypress",
"focus", "blur", "load", "beforeunload", "unload", "hashchange", "abort", "error",
"submit", "reset", "change", "select", "input" ,"text",
"focus", "blur", "load", "popstate", "beforeunload", "unload", "hashchange",
"abort", "error", "submit", "reset", "change", "select", "input", "text",
"compositionstart", "compositionend", "popupshowing", "popupshown",
"popuphiding", "popuphidden", "close", "command", "broadcast", "commandupdate",
"dragenter", "dragover", "dragexit", "dragdrop", "draggesture",
@ -1324,6 +1325,8 @@ const char* nsDOMEvent::GetEventName(PRUint32 aEventType)
return sEventNames[eDOMEvents_close];
case NS_LOAD:
return sEventNames[eDOMEvents_load];
case NS_POPSTATE:
return sEventNames[eDOMEvents_popstate];
case NS_BEFORE_PAGE_UNLOAD:
return sEventNames[eDOMEvents_beforeunload];
case NS_PAGE_UNLOAD:

Просмотреть файл

@ -58,7 +58,7 @@ class nsDOMEvent : public nsIDOMEvent,
{
public:
// Note: this enum must be kept in sync with mEventNames in nsDOMEvent.cpp
// Note: this enum must be kept in sync with sEventNames in nsDOMEvent.cpp
enum nsDOMEvents {
eDOMEvents_mousedown=0,
eDOMEvents_mouseup,
@ -74,6 +74,7 @@ public:
eDOMEvents_focus,
eDOMEvents_blur,
eDOMEvents_load,
eDOMEvents_popstate,
eDOMEvents_beforeunload,
eDOMEvents_unload,
eDOMEvents_hashchange,

Просмотреть файл

@ -0,0 +1,93 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is the Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsDOMPopStateEvent.h"
#include "nsCycleCollectionParticipant.h"
NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMPopStateEvent)
NS_IMPL_ADDREF_INHERITED(nsDOMPopStateEvent, nsDOMEvent)
NS_IMPL_RELEASE_INHERITED(nsDOMPopStateEvent, nsDOMEvent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMPopStateEvent, nsDOMEvent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mState)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMPopStateEvent, nsDOMEvent)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mState)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMPopStateEvent)
NS_INTERFACE_MAP_ENTRY(nsIDOMPopStateEvent)
NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(PopStateEvent)
NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
nsDOMPopStateEvent::~nsDOMPopStateEvent()
{
}
NS_IMETHODIMP
nsDOMPopStateEvent::GetState(nsIVariant **aState)
{
NS_PRECONDITION(aState, "null state arg");
NS_IF_ADDREF(*aState = mState);
return NS_OK;
}
NS_IMETHODIMP
nsDOMPopStateEvent::InitPopStateEvent(const nsAString &aTypeArg,
PRBool aCanBubbleArg,
PRBool aCancelableArg,
nsIVariant *aStateArg)
{
nsresult rv = nsDOMEvent::InitEvent(aTypeArg, aCanBubbleArg, aCancelableArg);
NS_ENSURE_SUCCESS(rv, rv);
mState = aStateArg;
return NS_OK;
}
nsresult NS_NewDOMPopStateEvent(nsIDOMEvent** aInstancePtrResult,
nsPresContext* aPresContext,
nsEvent* aEvent)
{
nsDOMPopStateEvent* event =
new nsDOMPopStateEvent(aPresContext, aEvent);
if (!event) {
return NS_ERROR_OUT_OF_MEMORY;
}
return CallQueryInterface(event, aInstancePtrResult);
}

Просмотреть файл

@ -0,0 +1,71 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is the Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef nsDOMPopStateEvent_h__
#define nsDOMPopStateEvent_h__
class nsEvent;
#include "nsIDOMPopStateEvent.h"
#include "nsDOMEvent.h"
#include "nsIVariant.h"
#include "nsCycleCollectionParticipant.h"
class nsDOMPopStateEvent : public nsDOMEvent,
public nsIDOMPopStateEvent
{
public:
nsDOMPopStateEvent(nsPresContext* aPresContext, nsEvent* aEvent)
: nsDOMEvent(aPresContext, aEvent) // state
{
}
virtual ~nsDOMPopStateEvent();
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDOMPopStateEvent, nsDOMEvent)
NS_DECL_NSIDOMPOPSTATEEVENT
NS_FORWARD_TO_NSDOMEVENT
protected:
nsCOMPtr<nsIVariant> mState;
};
nsresult NS_NewDOMPopStateEvent(nsIDOMEvent** aInstancePtrResult,
nsPresContext* aPresContext,
nsEvent* aEvent);
#endif // nsDOMPopStateEvent_h__

Просмотреть файл

@ -48,6 +48,7 @@
#include "nsFixedSizeAllocator.h"
#include "nsINode.h"
#include "nsPIDOMWindow.h"
#include "nsDOMPopStateEvent.h"
#define NS_TARGET_CHAIN_FORCE_CONTENT_DISPATCH (1 << 0)
#define NS_TARGET_CHAIN_WANTS_WILL_HANDLE_EVENT (1 << 1)
@ -796,6 +797,8 @@ nsEventDispatcher::CreateEvent(nsPresContext* aPresContext,
// is probably wrong!
if (aEventType.LowerCaseEqualsLiteral("transitionevent"))
return NS_NewDOMTransitionEvent(aDOMEvent, aPresContext, nsnull);
if (aEventType.LowerCaseEqualsLiteral("popstateevent"))
return NS_NewDOMPopStateEvent(aDOMEvent, aPresContext, nsnull);
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}

Просмотреть файл

@ -0,0 +1,17 @@
<!DOCTYPE HTML>
<html>
<body onload="test();">
<script>
function test() {
// Test that calling pushState() with a state object which calls
// history.back() doesn't crash. We need to make sure that there's at least
// one entry in the history before we do anything else.
history.pushState(null, "");
x = {};
x.toJSON = { history.back(); return "{a:1}"; };
history.pushState(x, "");
}
</script>
</body>
</html>

Просмотреть файл

@ -6,3 +6,4 @@ load 430628-1.html
load 432114-1.html
load 432114-2.html
load 436900-1.html
load 500328-1.html

Просмотреть файл

@ -109,6 +109,7 @@
#include "nsIURIClassifier.h"
#include "nsIOfflineCacheUpdate.h"
#include "nsCPrefetchService.h"
#include "nsJSON.h"
// we want to explore making the document own the load group
// so we can associate the document URI with the load group.
@ -655,6 +656,8 @@ DispatchPings(nsIContent *content, nsIURI *referrer)
ForEachPing(content, SendPing, &info);
}
static nsISHEntry* GetRootSHEntry(nsISHEntry *entry);
//*****************************************************************************
//*** nsDocShell: Object Management
//*****************************************************************************
@ -863,6 +866,14 @@ NS_IMETHODIMP nsDocShell::GetInterface(const nsIID & aIID, void **aSink)
mContentViewer->GetDOMDocument((nsIDOMDocument **) aSink);
return *aSink ? NS_OK : NS_NOINTERFACE;
}
else if (aIID.Equals(NS_GET_IID(nsIDocument)) &&
NS_SUCCEEDED(EnsureContentViewer())) {
nsCOMPtr<nsIDOMDocument> domDoc;
mContentViewer->GetDOMDocument(getter_AddRefs(domDoc));
if (!domDoc)
return NS_NOINTERFACE;
return domDoc->QueryInterface(aIID, aSink);
}
else if (aIID.Equals(NS_GET_IID(nsIApplicationCacheContainer))) {
*aSink = nsnull;
@ -1196,7 +1207,7 @@ nsDocShell::LoadURI(nsIURI * aURI,
// The parent was loaded normally. In this case, this *brand new* child really shouldn't
// have a SHEntry. If it does, it could be because the parent is replacing an
// existing frame with a new frame, in the onLoadHandler. We don't want this
// url to get into session history. Clear off shEntry, and set laod type to
// url to get into session history. Clear off shEntry, and set load type to
// LOAD_BYPASS_HISTORY.
PRBool inOnLoadHandler=PR_FALSE;
parentDS->GetIsExecutingOnLoadHandler(&inOnLoadHandler);
@ -3226,11 +3237,11 @@ nsDocShell::GetChildSHEntry(PRInt32 aChildOffset, nsISHEntry ** aResult)
NS_IMETHODIMP
nsDocShell::AddChildSHEntry(nsISHEntry * aCloneRef, nsISHEntry * aNewEntry,
PRInt32 aChildOffset)
PRInt32 aChildOffset, PRUint32 loadType)
{
nsresult rv;
if (mLSHE) {
if (mLSHE && loadType != LOAD_PUSHSTATE) {
/* You get here if you are currently building a
* hierarchy ie.,you just visited a frameset page
*/
@ -3285,7 +3296,8 @@ nsDocShell::AddChildSHEntry(nsISHEntry * aCloneRef, nsISHEntry * aNewEntry,
nsCOMPtr<nsIDocShellHistory> parent =
do_QueryInterface(GetAsSupports(mParent), &rv);
if (parent) {
rv = parent->AddChildSHEntry(aCloneRef, aNewEntry, aChildOffset);
rv = parent->AddChildSHEntry(aCloneRef, aNewEntry, aChildOffset,
loadType);
}
}
return rv;
@ -3313,7 +3325,7 @@ nsDocShell::DoAddChildSHEntry(nsISHEntry* aNewEntry, PRInt32 aChildOffset)
nsCOMPtr<nsIDocShellHistory> parent =
do_QueryInterface(GetAsSupports(mParent), &rv);
if (parent) {
rv = parent->AddChildSHEntry(mOSHE, aNewEntry, aChildOffset);
rv = parent->AddChildSHEntry(mOSHE, aNewEntry, aChildOffset, mLoadType);
}
@ -3457,7 +3469,6 @@ NS_IMETHODIMP nsDocShell::GotoIndex(PRInt32 aIndex)
}
NS_IMETHODIMP
nsDocShell::LoadURI(const PRUnichar * aURI,
PRUint32 aLoadFlags,
@ -3846,6 +3857,13 @@ nsDocShell::LoadErrorPage(nsIURI *aURI, const PRUnichar *aURL,
mFailedURI = aURI;
mFailedLoadType = mLoadType;
if (mLSHE) {
// If we don't give mLSHE a new doc identifier here, when we go back or
// forward to another SHEntry with the same doc identifier, the error
// page will persist.
mLSHE->SetUniqueDocIdentifier();
}
nsCAutoString url;
nsCAutoString charset;
if (aURI)
@ -5714,13 +5732,13 @@ nsDocShell::EndPageLoad(nsIWebProgress * aProgress,
// We're done with the URI classifier for this channel
mClassifier = nsnull;
//
// Notify the ContentViewer that the Document has finished loading...
//
// This will cause any OnLoad(...) handlers to fire, if it is a HTML
// document...
//
// Notify the ContentViewer that the Document has finished loading. This
// will cause any OnLoad(...) and PopState(...) handlers to fire.
if (!mEODForCurrentDocument && mContentViewer) {
// Set the pending state object which will be returned to the page in
// the popstate event.
SetDocPendingStateObj(mLSHE);
mIsExecutingOnLoadHandler = PR_TRUE;
mContentViewer->LoadComplete(aStatus);
mIsExecutingOnLoadHandler = PR_FALSE;
@ -7368,6 +7386,26 @@ nsDocShell::SetupNewViewer(nsIContentViewer * aNewViewer)
return NS_OK;
}
nsresult
nsDocShell::SetDocPendingStateObj(nsISHEntry *shEntry)
{
nsresult rv;
nsCOMPtr<nsIDocument> document = do_GetInterface(GetAsSupports(this));
NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
nsAutoString stateData;
if (shEntry) {
rv = shEntry->GetStateData(stateData);
NS_ENSURE_SUCCESS(rv, rv);
// if shEntry is null, we just set the pending state object to the
// empty string.
}
document->SetPendingStateObject(stateData);
return NS_OK;
}
nsresult
nsDocShell::CheckLoadingPermissions()
@ -7844,27 +7882,50 @@ nsDocShell::InternalLoad(nsIURI * aURI,
}
#endif
}
if ((aLoadType == LOAD_NORMAL ||
aLoadType == LOAD_STOP_CONTENT ||
LOAD_TYPE_HAS_FLAGS(aLoadType, LOAD_FLAGS_REPLACE_HISTORY) ||
aLoadType == LOAD_HISTORY ||
aLoadType == LOAD_LINK) && allowScroll) {
if (aLoadType == LOAD_NORMAL ||
aLoadType == LOAD_STOP_CONTENT ||
LOAD_TYPE_HAS_FLAGS(aLoadType, LOAD_FLAGS_REPLACE_HISTORY) ||
aLoadType == LOAD_HISTORY ||
aLoadType == LOAD_LINK) {
PRBool wasAnchor = PR_FALSE;
PRBool doHashchange = PR_FALSE;
nscoord cx, cy;
NS_ENSURE_SUCCESS(ScrollIfAnchor(aURI, &wasAnchor, aLoadType, &cx, &cy,
&doHashchange),
NS_ERROR_FAILURE);
if (wasAnchor) {
if (allowScroll) {
NS_ENSURE_SUCCESS(ScrollIfAnchor(aURI, &wasAnchor, aLoadType, &cx,
&cy, &doHashchange),
NS_ERROR_FAILURE);
}
// If this is a history load, aSHEntry will have document identifier X
// if it was created as a result of a History.pushState() from a
// SHEntry with doc ident X, or if it was created by changing the hash
// of the URI corresponding to a SHEntry with doc ident X.
PRBool sameDocIdent = PR_FALSE;
if (mOSHE && aSHEntry) {
PRUint64 ourDocIdent, otherDocIdent;
mOSHE->GetDocIdentifier(&ourDocIdent);
aSHEntry->GetDocIdentifier(&otherDocIdent);
sameDocIdent = (ourDocIdent == otherDocIdent);
}
// Do a short-circuited load if the new URI differs from the current
// URI only in the hash, or if the two entries belong to the same
// document and don't point to the same object.
//
// (If we didn't check that the SHEntries are different objects,
// history.go(0) would short-circuit instead of triggering a true
// load, and we wouldn't dispatch an onload event to the page.)
if (wasAnchor || (sameDocIdent && (mOSHE != aSHEntry))) {
mLoadType = aLoadType;
mURIResultedInDocument = PR_TRUE;
/* we need to assign mLSHE to aSHEntry right here, so that on History loads,
* SetCurrentURI() called from OnNewURI() will send proper
/* we need to assign mLSHE to aSHEntry right here, so that on History loads,
* SetCurrentURI() called from OnNewURI() will send proper
* onLocationChange() notifications to the browser to update
* back/forward buttons.
* back/forward buttons.
*/
SetHistoryEntry(&mLSHE, aSHEntry);
@ -7877,10 +7938,11 @@ nsDocShell::InternalLoad(nsIURI * aURI,
mOSHE->GetOwner(getter_AddRefs(owner));
}
OnNewURI(aURI, nsnull, owner, mLoadType, PR_TRUE);
nsCOMPtr<nsIInputStream> postData;
PRUint32 pageIdent = PR_UINT32_MAX;
nsCOMPtr<nsISupports> cacheKey;
if (mOSHE) {
/* save current position of scroller(s) (bug 59774) */
mOSHE->SetScrollPosition(cx, cy);
@ -7895,8 +7957,19 @@ nsDocShell::InternalLoad(nsIURI * aURI,
mOSHE->GetPageIdentifier(&pageIdent);
mOSHE->GetCacheKey(getter_AddRefs(cacheKey));
}
if (mLSHE && wasAnchor) {
// If it's an anchor load, set mLSHE's doc identifier to
// mOSHE's doc identifier -- These are the same documents,
// as far as HTML5 is concerned.
PRUint64 docIdent;
rv = mOSHE->GetDocIdentifier(&docIdent);
if (NS_SUCCEEDED(rv)) {
mLSHE->SetDocIdentifier(docIdent);
}
}
}
/* Assign mOSHE to mLSHE. This will either be a new entry created
* by OnNewURI() for normal loads or aSHEntry for history loads.
*/
@ -7913,7 +7986,7 @@ nsDocShell::InternalLoad(nsIURI * aURI,
// cache first
if (cacheKey)
mOSHE->SetCacheKey(cacheKey);
// Propagate our page ident to the new mOSHE so that
// we'll know it just differed by a scroll on the page.
if (pageIdent != PR_UINT32_MAX)
@ -7949,12 +8022,27 @@ nsDocShell::InternalLoad(nsIURI * aURI,
shEntry->SetTitle(mTitle);
}
if (doHashchange) {
nsCOMPtr<nsPIDOMWindow> window =
do_QueryInterface(mScriptGlobal);
if (sameDocIdent) {
// Set the doc's URI according to the new history entry's URI
nsCOMPtr<nsIURI> newURI;
mOSHE->GetURI(getter_AddRefs(newURI));
NS_ENSURE_TRUE(newURI, NS_ERROR_FAILURE);
nsCOMPtr<nsIDocument> doc =
do_GetInterface(GetAsSupports(this));
NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
if (window)
window->DispatchSyncHashchange();
doc->SetDocumentURI(newURI);
}
SetDocPendingStateObj(mOSHE);
// Dispatch the popstate and hashchange events, as appropriate
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mScriptGlobal);
if (window) {
window->DispatchSyncPopState();
if (doHashchange)
window->DispatchSyncHashchange();
}
return NS_OK;
@ -8837,6 +8925,14 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIChannel * aChannel, nsISupports* aOwner,
if (uploadChannel) {
uploadChannel->GetUploadStream(getter_AddRefs(inputStream));
}
// If the response status indicates an error, unlink this session
// history entry from any entries sharing its doc ident.
PRUint32 responseStatus;
nsresult rv = httpChannel->GetResponseStatus(&responseStatus);
if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) {
mLSHE->SetUniqueDocIdentifier();
}
}
}
/* Create SH Entry (mLSHE) only if there is a SessionHistory object (mSessionHistory) in
@ -8858,7 +8954,7 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIChannel * aChannel, nsISupports* aOwner,
aLoadType & LOAD_CMD_RELOAD)
updateHistory = PR_FALSE;
// Check if the url to be loaded is the same as the one already loaded.
// Check if the url to be loaded is the same as the one already loaded.
if (mCurrentURI)
aURI->Equals(mCurrentURI, &equalUri);
@ -8895,7 +8991,6 @@ nsDocShell::OnNewURI(nsIURI * aURI, nsIChannel * aChannel, nsISupports* aOwner,
SetHistoryEntry(&mLSHE, mOSHE);
}
/* If the user pressed shift-reload, cache will create a new cache key
* for the page. Save the new cacheKey in Session History.
* see bug 90098
@ -8985,7 +9080,283 @@ nsDocShell::SetReferrerURI(nsIURI * aURI)
//*****************************************************************************
// nsDocShell: Session History
//*****************************************************************************
//*****************************************************************************
nsresult
nsDocShell::StringifyJSValVariant(nsIVariant *aData, nsAString &aResult)
{
nsresult rv;
aResult.Truncate();
// First, try to extract a jsval from the variant |aData|. This works only
// if the variant implements GetAsJSVal.
jsval jsData;
rv = aData->GetAsJSVal(&jsData);
NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
// Now get the JSContext associated with the current document.
// First get the current document.
nsCOMPtr<nsIDocument> document = do_GetInterface(GetAsSupports(this));
NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
// Get the JSContext from the document, like we do in
// nsContentUtils::GetContextFromDocument().
nsIScriptGlobalObject *sgo = document->GetScopeObject();
NS_ENSURE_TRUE(sgo, NS_ERROR_FAILURE);
nsIScriptContext *scx = sgo->GetContext();
NS_ENSURE_TRUE(scx, NS_ERROR_FAILURE);
JSContext *cx = (JSContext *)scx->GetNativeContext();
// If our json call triggers a JS-to-C++ call, we want that call to use cx
// as the context. So we push cx onto the context stack.
nsCOMPtr<nsIJSContextStack> contextStack =
do_GetService("@mozilla.org/js/xpc/ContextStack;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
contextStack->Push(cx);
nsCOMPtr<nsIJSON> json = do_GetService("@mozilla.org/dom/json;1");
if(json) {
// Do the encoding
rv = json->EncodeFromJSVal(&jsData, cx, aResult);
}
else {
rv = NS_ERROR_FAILURE;
}
// Always pop the stack!
contextStack->Pop(&cx);
return rv;
}
NS_IMETHODIMP
nsDocShell::AddState(nsIVariant *aData, const nsAString& aTitle,
const nsAString& aURL, PRBool aReplace)
{
// Implements History.pushState and History.replaceState
// Here's what we do, roughly in the order specified by HTML5:
// 1. Serialize aData to JSON.
// 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:
// Remove from the session history all entries after the current entry,
// as we would after a regular navigation.
// 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
// a. cloned data as the state object,
// b. the given title as the title, and,
// c. if the third argument was present, the absolute URL found in
// step 2
// 5. 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.
//
// 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 EvictContentViewers at the end of step 5
// might run script, we can't just put a script blocker around the critical
// section.
nsresult rv;
nsCOMPtr<nsIDocument> document = do_GetInterface(GetAsSupports(this));
NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
mLoadType = LOAD_PUSHSTATE;
// Step 1: Clone aData by getting its JSON representation
nsString dataStr;
rv = StringifyJSValVariant(aData, dataStr);
NS_ENSURE_SUCCESS(rv, rv);
// Check that the state object isn't too long.
// Default max length: 640k chars.
PRInt32 maxStateObjSize = 0xA0000;
if (mPrefs) {
mPrefs->GetIntPref("browser.history.maxStateObjectSize",
&maxStateObjSize);
}
if (maxStateObjSize < 0)
maxStateObjSize = 0;
NS_ENSURE_TRUE(dataStr.Length() <= (PRUint32)maxStateObjSize,
NS_ERROR_ILLEGAL_VALUE);
// Step 2: Resolve aURL
PRBool equalURIs = PR_TRUE;
nsCOMPtr<nsIURI> oldURI = mCurrentURI;
nsCOMPtr<nsIURI> newURI;
if (aURL.Length() == 0) {
newURI = mCurrentURI;
}
else {
// 2a: Resolve aURL relative to mURI
nsIURI* docBaseURI = document->GetBaseURI();
if (!docBaseURI)
return NS_ERROR_FAILURE;
nsCAutoString spec;
docBaseURI->GetSpec(spec);
nsCAutoString charset;
rv = docBaseURI->GetOriginCharset(charset);
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
rv = NS_NewURI(getter_AddRefs(newURI), aURL,
charset.get(), docBaseURI);
// 2b: If 2a fails, raise a SECURITY_ERR
if (NS_FAILED(rv)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
// 2c: Same-origin check.
if (!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
// check that the two URIs have the same userpass. (The
// security manager says that |http://foo.com| and
// |http://me@foo.com| have the same origin.) mCurrentURI
// won't contain the password part of the userpass, so this
// means that it's never valid to specify a password in a
// pushState or replaceState URI.
nsCOMPtr<nsIScriptSecurityManager> secMan =
do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
NS_ENSURE_TRUE(secMan, NS_ERROR_FAILURE);
// It's very important that we check that newURI is of the same
// origin as mCurrentURI, not docBaseURI, because a page can
// set docBaseURI arbitrarily to any domain.
nsCAutoString currentUserPass, newUserPass;
NS_ENSURE_SUCCESS(mCurrentURI->GetUserPass(currentUserPass),
NS_ERROR_FAILURE);
NS_ENSURE_SUCCESS(newURI->GetUserPass(newUserPass),
NS_ERROR_FAILURE);
if (NS_FAILED(secMan->CheckSameOriginURI(mCurrentURI,
newURI, PR_TRUE)) ||
!currentUserPass.Equals(newUserPass)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
}
else {
// It's a file:// URI
nsCOMPtr<nsIScriptObjectPrincipal> docScriptObj =
do_QueryInterface(document);
if (!docScriptObj) {
return NS_ERROR_DOM_SECURITY_ERR;
}
nsCOMPtr<nsIPrincipal> principal = docScriptObj->GetPrincipal();
if (!principal ||
NS_FAILED(principal->CheckMayLoad(newURI, PR_TRUE))) {
return NS_ERROR_DOM_SECURITY_ERR;
}
}
mCurrentURI->Equals(newURI, &equalURIs);
} // end of same-origin check
nsCOMPtr<nsISHistory> sessionHistory = mSessionHistory;
if (!sessionHistory) {
// Get the handle to SH from the root docshell
GetRootSessionHistory(getter_AddRefs(sessionHistory));
}
NS_ENSURE_TRUE(sessionHistory, NS_ERROR_FAILURE);
nsCOMPtr<nsISHistoryInternal> shInternal =
do_QueryInterface(sessionHistory, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// 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);
nsCOMPtr<nsISHEntry> oldOSHE = mOSHE;
nsCOMPtr<nsISHEntry> newSHEntry;
if (!aReplace) {
rv = AddToSessionHistory(newURI, nsnull, nsnull,
getter_AddRefs(newSHEntry));
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE);
// Set the new SHEntry's document identifier, if we can.
PRUint64 ourDocIdent;
NS_ENSURE_SUCCESS(oldOSHE->GetDocIdentifier(&ourDocIdent),
NS_ERROR_FAILURE);
NS_ENSURE_SUCCESS(newSHEntry->SetDocIdentifier(ourDocIdent),
NS_ERROR_FAILURE);
// AddToSessionHistory may not modify mOSHE. In case it doesn't,
// we'll just set mOSHE here.
mOSHE = newSHEntry;
} else {
newSHEntry = mOSHE;
newSHEntry->SetURI(newURI);
}
// Step 4: Modify new/original session history entry
newSHEntry->SetStateData(dataStr);
// 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.
if (!aReplace) {
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);
PRInt32 curIndex = -1;
rv = rootSH->GetIndex(&curIndex);
if (NS_SUCCEEDED(rv) && curIndex > -1) {
internalSH->EvictContentViewers(curIndex - 1, curIndex);
}
}
// Step 6: If the document's URI changed, update document's URI and update
// global history
if (!equalURIs) {
SetCurrentURI(newURI, nsnull, PR_TRUE);
document->SetDocumentURI(newURI);
AddToGlobalHistory(newURI, PR_FALSE, oldURI);
}
// Try to set the title of the current history element
if (mOSHE)
mOSHE->SetTitle(aTitle);
// We need this to ensure that the back button is enabled after a
// pushState, if it wasn't already enabled.
FireOnLocationChange(this, nsnull, mCurrentURI);
return NS_OK;
}
PRBool
nsDocShell::ShouldAddToSessionHistory(nsIURI * aURI)
{
@ -9428,7 +9799,6 @@ nsDocShell::CloneAndReplace(nsISHEntry *aSrcEntry,
return rv;
}
void
nsDocShell::SwapHistoryEntries(nsISHEntry *aOldEntry, nsISHEntry *aNewEntry)
{
@ -9679,9 +10049,6 @@ nsresult
nsDocShell::AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect,
nsIChannel * aChannel)
{
if (mItemType != typeContent || !mGlobalHistory)
return NS_OK;
// If this is a POST request, we do not want to include this in global
// history, so return early.
nsCOMPtr<nsIHttpChannel> hchan(do_QueryInterface(aChannel));
@ -9692,16 +10059,26 @@ nsDocShell::AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect,
return NS_OK;
}
nsCOMPtr<nsIURI> referrer;
if (aChannel)
NS_GetReferrerFromChannel(aChannel, getter_AddRefs(referrer));
return AddToGlobalHistory(aURI, aRedirect, referrer);
}
nsresult
nsDocShell::AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect,
nsIURI * aReferrer)
{
if (mItemType != typeContent || !mGlobalHistory)
return NS_OK;
PRBool visited;
nsresult rv = mGlobalHistory->IsVisited(aURI, &visited);
if (NS_FAILED(rv))
return rv;
nsCOMPtr<nsIURI> referrer;
if (aChannel)
NS_GetReferrerFromChannel(aChannel, getter_AddRefs(referrer));
rv = mGlobalHistory->AddURI(aURI, aRedirect, !IsFrame(), referrer);
rv = mGlobalHistory->AddURI(aURI, aRedirect, !IsFrame(), aReferrer);
if (NS_FAILED(rv))
return rv;
@ -9714,6 +10091,7 @@ nsDocShell::AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect,
}
return NS_OK;
}
//*****************************************************************************

Просмотреть файл

@ -364,6 +364,10 @@ protected:
PRUint32 aLoadType, nscoord *cx, nscoord *cy,
PRBool * aDoHashchange);
// Tries to stringify a given variant by converting it to JSON. This only
// works if the variant is backed by a JSVal.
nsresult StringifyJSValVariant(nsIVariant *aData, nsAString &aResult);
// Returns PR_TRUE if would have called FireOnLocationChange,
// but did not because aFireOnLocationChange was false on entry.
// In this case it is the caller's responsibility to ensure
@ -465,8 +469,11 @@ protected:
PRUint32 aStateFlags);
// Global History
nsresult AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect,
nsIChannel * aChannel);
nsresult AddToGlobalHistory(nsIURI * aURI, PRBool aRedirect,
nsIURI * aReferrer);
// Helper Routines
nsresult ConfirmRepost(PRBool * aRepost);
@ -515,6 +522,11 @@ protected:
nsIChannel * aChannel,
nsresult aResult);
// Sets the current document's pending state object to the given SHEntry's
// state object. The pending state object is eventually given to the page
// in the PopState event.
nsresult SetDocPendingStateObj(nsISHEntry *shEntry);
nsresult CheckLoadingPermissions();
// Security checks to prevent frameset spoofing. See comments at

Просмотреть файл

@ -92,6 +92,7 @@ enum LoadType {
LOAD_BYPASS_HISTORY = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_BYPASS_HISTORY),
LOAD_STOP_CONTENT = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_STOP_CONTENT),
LOAD_STOP_CONTENT_AND_REPLACE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_STOP_CONTENT | nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY),
LOAD_PUSHSTATE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_PUSHSTATE, nsIWebNavigation::LOAD_FLAGS_NONE),
/**
* Load type for an error page. These loads are never triggered by users of
* Docshell. Instead, Docshell triggers the load itself when a
@ -122,6 +123,7 @@ static inline PRBool IsValidLoadType(PRUint32 aLoadType)
case LOAD_BYPASS_HISTORY:
case LOAD_STOP_CONTENT:
case LOAD_STOP_CONTENT_AND_REPLACE:
case LOAD_PUSHSTATE:
case LOAD_ERROR_PAGE:
return PR_TRUE;
}

Просмотреть файл

@ -69,8 +69,9 @@ interface nsISecureBrowserUI;
interface nsIDOMStorage;
interface nsIPrincipal;
interface nsIWebBrowserPrint;
interface nsIVariant;
[scriptable, uuid(c95eaff1-14e6-4db1-a806-46be97d5a9b6)]
[scriptable, uuid(3adde256-05a9-43a7-a190-f8fe75eecfd6)]
interface nsIDocShell : nsISupports
{
/**
@ -169,6 +170,13 @@ interface nsIDocShell : nsISupports
out nsIDocShell aDocShell,
out nsIRequest aRequest);
/**
* Do either a history.pushState() or history.replaceState() operation,
* depending on the value of aReplace.
*/
void addState(in nsIVariant aData, in DOMString aTitle,
in DOMString aURL, in boolean aReplace);
/**
* Creates a DocShellLoadInfo object that you can manipulate and then pass
* to loadURI.
@ -339,9 +347,10 @@ interface nsIDocShell : nsISupports
/**
* Load commands for the document
*/
const unsigned long LOAD_CMD_NORMAL = 0x1; // Normal load
const unsigned long LOAD_CMD_RELOAD = 0x2; // Reload
const unsigned long LOAD_CMD_HISTORY = 0x4; // Load from history
const unsigned long LOAD_CMD_NORMAL = 0x1; // Normal load
const unsigned long LOAD_CMD_RELOAD = 0x2; // Reload
const unsigned long LOAD_CMD_HISTORY = 0x4; // Load from history
const unsigned long LOAD_CMD_PUSHSTATE = 0x8; // History.pushState()
readonly attribute unsigned long busyFlags;

Просмотреть файл

@ -39,7 +39,7 @@
#include "nsISupports.idl"
interface nsISHEntry;
[scriptable, uuid(89caa9f0-8b1c-47fb-b0d3-f0aef0bff749)]
[scriptable, uuid(a89b80a8-3c44-4a25-9d2c-2fb42358b46e)]
interface nsIDocShellHistory : nsISupports
{
/**
@ -48,11 +48,12 @@ interface nsIDocShellHistory : nsISupports
nsISHEntry getChildSHEntry(in long aChildOffset);
/**
* Add a Child SHEntry for a frameset page
* Add a Child SHEntry for a frameset page, given the child's loadtype.
*/
void addChildSHEntry(in nsISHEntry aCloneReference,
in nsISHEntry aHistoryEntry,
in long aChildOffset);
in long aChildOffset,
in unsigned long aLoadType);
/*
* Whether this docshell should save entries in global history.

Просмотреть файл

@ -58,7 +58,7 @@ class nsDocShellEditorData;
[ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData);
[scriptable, uuid(09fecea6-5453-43ba-bf91-3ff32618f037)]
[scriptable, uuid(62b0603f-57ca-439e-a0fb-6f6978500755)]
interface nsISHEntry : nsIHistoryEntry
{
/** URI for the document */
@ -149,6 +149,21 @@ interface nsISHEntry : nsIHistoryEntry
*/
attribute unsigned long pageIdentifier;
/**
* docIdentifier is an integer that should be the same for two entries
* attached to the same docshell if and only if the two entries are entries
* for the same document. In practice, two entries A and B will have the
* same docIdentifier if they have the same pageIdentifier or if B was
* created by A calling history.pushState().
*/
attribute unsigned long long docIdentifier;
/**
* Changes this entry's doc identifier to a new value which is unique
* among those of all other entries.
*/
void setUniqueDocIdentifier();
/** attribute to set and get the cache key for the entry */
attribute nsISupports cacheKey;
@ -192,6 +207,12 @@ interface nsISHEntry : nsIHistoryEntry
*/
attribute nsISupports owner;
/**
* Get/set data associated with this history state via a pushState() call,
* encoded as JSON.
**/
attribute AString stateData;
/**
* Gets the owning pointer to the editor data assosicated with
* this shistory entry. This forgets its pointer, so free it when

Просмотреть файл

@ -75,6 +75,7 @@ protected:
static HistoryTracker *gHistoryTracker = nsnull;
static PRUint32 gEntryID = 0;
static PRUint64 gEntryDocIdentifier = 0;
nsresult nsSHEntry::Startup()
{
@ -104,6 +105,7 @@ nsSHEntry::nsSHEntry()
: mLoadType(0)
, mID(gEntryID++)
, mPageIdentifier(mID)
, mDocIdentifier(gEntryDocIdentifier++)
, mScrollPositionX(0)
, mScrollPositionY(0)
, mIsFrameNavigation(PR_FALSE)
@ -125,6 +127,7 @@ nsSHEntry::nsSHEntry(const nsSHEntry &other)
, mLoadType(0) // XXX why not copy?
, mID(other.mID)
, mPageIdentifier(other.mPageIdentifier)
, mDocIdentifier(other.mDocIdentifier)
, mScrollPositionX(0) // XXX why not copy?
, mScrollPositionY(0) // XXX why not copy?
, mIsFrameNavigation(other.mIsFrameNavigation)
@ -393,6 +396,30 @@ NS_IMETHODIMP nsSHEntry::SetPageIdentifier(PRUint32 aPageIdentifier)
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::GetDocIdentifier(PRUint64 * aResult)
{
*aResult = mDocIdentifier;
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::SetDocIdentifier(PRUint64 aDocIdentifier)
{
// This ensures that after a session restore, gEntryDocIdentifier is greater
// than all SHEntries' docIdentifiers, which ensures that we'll never repeat
// a doc identifier.
if (aDocIdentifier >= gEntryDocIdentifier)
gEntryDocIdentifier = aDocIdentifier + 1;
mDocIdentifier = aDocIdentifier;
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::SetUniqueDocIdentifier()
{
mDocIdentifier = gEntryDocIdentifier++;
return NS_OK;
}
NS_IMETHODIMP nsSHEntry::GetIsSubFrame(PRBool * aFlag)
{
*aFlag = mIsFrameNavigation;
@ -470,7 +497,7 @@ nsSHEntry::Create(nsIURI * aURI, const nsAString &aTitle,
mCacheKey = aCacheKey;
mContentType = aContentType;
mOwner = aOwner;
// Set the LoadType by default to loadHistory during creation
mLoadType = (PRUint32) nsIDocShellLoadInfo::loadHistory;
@ -866,3 +893,17 @@ nsSHEntry::HasDetachedEditor()
return mEditorData != nsnull;
}
NS_IMETHODIMP
nsSHEntry::GetStateData(nsAString &aStateData)
{
aStateData.Assign(mStateData);
return NS_OK;
}
NS_IMETHODIMP
nsSHEntry::SetStateData(const nsAString &aDataStr)
{
mStateData.Assign(aDataStr);
return NS_OK;
}

Просмотреть файл

@ -96,9 +96,10 @@ private:
nsCOMPtr<nsIInputStream> mPostData;
nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
nsCOMArray<nsISHEntry> mChildren;
PRUint32 mLoadType;
PRUint32 mLoadType;
PRUint32 mID;
PRUint32 mPageIdentifier;
PRInt64 mDocIdentifier;
PRInt32 mScrollPositionX;
PRInt32 mScrollPositionY;
PRPackedBool mIsFrameNavigation;
@ -115,6 +116,7 @@ private:
nsCOMPtr<nsISupports> mOwner;
nsExpirationState mExpirationState;
nsAutoPtr<nsDocShellEditorData> mEditorData;
nsString mStateData;
};
#endif /* nsSHEntry_h */

Просмотреть файл

@ -478,7 +478,7 @@ nsSHistory::PrintHistory()
nsCOMPtr<nsILayoutHistoryState> layoutHistoryState;
nsCOMPtr<nsIURI> uri;
nsXPIDLString title;
entry->GetLayoutHistoryState(getter_AddRefs(layoutHistoryState));
nsCOMPtr<nsIHistoryEntry> hEntry(do_QueryInterface(entry));
if (hEntry) {
@ -492,11 +492,12 @@ nsSHistory::PrintHistory()
uri->GetSpec(url);
printf("**** SH Transaction #%d, Entry = %x\n", index, entry.get());
printf("\t\t URL = %s\n", url);
printf("\t\t URL = %s\n", url.get());
printf("\t\t Title = %s\n", NS_LossyConvertUTF16toASCII(title).get());
printf("\t\t layout History Data = %x\n", layoutHistoryState);
printf("\t\t layout History Data = %x\n", layoutHistoryState.get());
#endif
nsCOMPtr<nsISHTransaction> next;
rv = txn->GetNext(getter_AddRefs(next));
if (NS_SUCCEEDED(rv) && next) {
@ -864,7 +865,7 @@ nsSHistory::EvictContentViewersInRange(PRInt32 aStart, PRInt32 aEnd)
if (viewer) {
NS_ASSERTION(ownerEntry,
"ContentViewer exists but its SHEntry is null");
#ifdef DEBUG_PAGE_CACHE
#ifdef DEBUG_PAGE_CACHE
nsCOMPtr<nsIURI> uri;
ownerEntry->GetURI(getter_AddRefs(uri));
nsCAutoString spec;
@ -1149,23 +1150,23 @@ nsSHistory::LoadEntry(PRInt32 aIndex, long aLoadType, PRUint32 aHistCmd)
mRequestedIndex = aIndex;
nsCOMPtr<nsISHEntry> prevEntry;
GetEntryAtIndex(mIndex, PR_FALSE, getter_AddRefs(prevEntry));
nsCOMPtr<nsISHEntry> nextEntry;
GetEntryAtIndex(mIndex, PR_FALSE, getter_AddRefs(prevEntry));
nsCOMPtr<nsISHEntry> nextEntry;
GetEntryAtIndex(mRequestedIndex, PR_FALSE, getter_AddRefs(nextEntry));
nsCOMPtr<nsIHistoryEntry> nHEntry(do_QueryInterface(nextEntry));
if (!nextEntry || !prevEntry || !nHEntry) {
if (!nextEntry || !prevEntry || !nHEntry) {
mRequestedIndex = -1;
return NS_ERROR_FAILURE;
}
// Send appropriate listener notifications
PRBool canNavigate = PR_TRUE;
// Get the uri for the entry we are about to visit
nsCOMPtr<nsIURI> nextURI;
nHEntry->GetURI(getter_AddRefs(nextURI));
if(mListener) {
if(mListener) {
nsCOMPtr<nsISHistoryListener> listener(do_QueryReferent(mListener));
if (listener) {
if (aHistCmd == HIST_CMD_BACK) {
@ -1222,7 +1223,6 @@ nsSHistory::LoadEntry(PRInt32 aIndex, long aLoadType, PRUint32 aHistCmd)
else
docShell = mRootDocShell;
}
if (!docShell) {
// we did not successfully go to the proper index.
@ -1235,8 +1235,6 @@ nsSHistory::LoadEntry(PRInt32 aIndex, long aLoadType, PRUint32 aHistCmd)
return InitiateLoad(nextEntry, docShell, aLoadType);
}
nsresult
nsSHistory::CompareFrames(nsISHEntry * aPrevEntry, nsISHEntry * aNextEntry, nsIDocShell * aParent, long aLoadType, PRBool * aIsFrameFound)
{

Просмотреть файл

@ -66,7 +66,7 @@ class nsSHistory: public PRCList,
public nsIWebNavigation
{
public:
nsSHistory();
nsSHistory();
NS_DECL_ISUPPORTS
NS_DECL_NSISHISTORY
@ -94,7 +94,7 @@ protected:
nsresult InitiateLoad(nsISHEntry * aFrameEntry, nsIDocShell * aFrameDS, long aLoadType);
NS_IMETHOD LoadEntry(PRInt32 aIndex, long aLoadType, PRUint32 histCmd);
#ifdef DEBUG
nsresult PrintHistory();
#endif

Просмотреть файл

@ -82,6 +82,7 @@
#include "nsIDOMNSEvent.h"
#include "nsIDOMKeyEvent.h"
#include "nsIDOMEventListener.h"
#include "nsIDOMPopStateEvent.h"
#include "nsContentUtils.h"
#include "nsDOMWindowUtils.h"
@ -1330,13 +1331,15 @@ static nsDOMClassInfoData sClassInfoData[] = {
NS_DEFINE_CLASSINFO_DATA(ScrollAreaEvent, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(PopStateEvent, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(EventListenerInfo, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(TransitionEvent, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
};
// Objects that shuld be constructable through |new Name();|
// Objects that should be constructable through |new Name();|
struct nsContractIDMapData
{
PRInt32 mDOMClassInfoID;
@ -1420,6 +1423,7 @@ jsval nsDOMClassInfo::sOnreset_id = JSVAL_VOID;
jsval nsDOMClassInfo::sOnchange_id = JSVAL_VOID;
jsval nsDOMClassInfo::sOnselect_id = JSVAL_VOID;
jsval nsDOMClassInfo::sOnload_id = JSVAL_VOID;
jsval nsDOMClassInfo::sOnpopstate_id = JSVAL_VOID;
jsval nsDOMClassInfo::sOnbeforeunload_id = JSVAL_VOID;
jsval nsDOMClassInfo::sOnunload_id = JSVAL_VOID;
jsval nsDOMClassInfo::sOnhashchange_id = JSVAL_VOID;
@ -1614,6 +1618,7 @@ nsDOMClassInfo::DefineStaticJSVals(JSContext *cx)
SET_JSVAL_TO_STRING(sOnchange_id, cx, "onchange");
SET_JSVAL_TO_STRING(sOnselect_id, cx, "onselect");
SET_JSVAL_TO_STRING(sOnload_id, cx, "onload");
SET_JSVAL_TO_STRING(sOnpopstate_id, cx, "onpopstate");
SET_JSVAL_TO_STRING(sOnbeforeunload_id, cx, "onbeforeunload");
SET_JSVAL_TO_STRING(sOnunload_id, cx, "onunload");
SET_JSVAL_TO_STRING(sOnhashchange_id, cx, "onhashchange");
@ -2232,6 +2237,11 @@ nsDOMClassInfo::Init()
DOM_CLASSINFO_UI_EVENT_MAP_ENTRIES
DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN(PopStateEvent, nsIDOMPopStateEvent)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMPopStateEvent)
DOM_CLASSINFO_EVENT_MAP_ENTRIES
DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN(HTMLDocument, nsIDOMHTMLDocument)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMHTMLDocument)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMNSHTMLDocument)
@ -7327,7 +7337,8 @@ nsEventReceiverSH::ReallyIsEventName(jsval id, jschar aFirstChar)
return (id == sOnpaint_id ||
id == sOnpageshow_id ||
id == sOnpagehide_id ||
id == sOnpaste_id);
id == sOnpaste_id ||
id == sOnpopstate_id);
case 'k' :
return (id == sOnkeydown_id ||
id == sOnkeypress_id ||

Просмотреть файл

@ -327,6 +327,7 @@ protected:
static jsval sOnchange_id;
static jsval sOnselect_id;
static jsval sOnload_id;
static jsval sOnpopstate_id;
static jsval sOnbeforeunload_id;
static jsval sOnunload_id;
static jsval sOnhashchange_id;

Просмотреть файл

@ -473,6 +473,7 @@ enum nsDOMClassInfoID {
eDOMClassInfo_PaintRequestList_id,
eDOMClassInfo_ScrollAreaEvent_id,
eDOMClassInfo_PopStateEvent_id,
eDOMClassInfo_EventListenerInfo_id,

Просмотреть файл

@ -79,6 +79,7 @@
#include "nsCycleCollector.h"
#include "nsCCUncollectableMarker.h"
#include "nsDOMThreadService.h"
#include "nsAutoJSValHolder.h"
// Interfaces Needed
#include "nsIFrame.h"
@ -112,6 +113,7 @@
#include "nsIDOMKeyEvent.h"
#include "nsIDOMMessageEvent.h"
#include "nsIDOMPopupBlockedEvent.h"
#include "nsIDOMPopStateEvent.h"
#include "nsIDOMOfflineResourceList.h"
#include "nsIDOMGeoGeolocation.h"
#include "nsPIDOMStorage.h"
@ -169,6 +171,7 @@
#include "nsIXULAppInfo.h"
#include "nsNetUtil.h"
#include "nsFocusManager.h"
#include "nsIJSON.h"
#ifdef MOZ_XUL
#include "nsXULPopupManager.h"
#include "nsIDOMXULControlElement.h"
@ -361,6 +364,8 @@ static const char sJSStackContractID[] = "@mozilla.org/js/xpc/ContextStack;1";
static const char kCryptoContractID[] = NS_CRYPTO_CONTRACTID;
static const char kPkcs11ContractID[] = NS_PKCS11_CONTRACTID;
static const char sPopStatePrefStr[] = "browser.history.allowPopState";
static PRBool
IsAboutBlank(nsIURI* aURI)
{
@ -6978,7 +6983,7 @@ nsGlobalWindow::DispatchSyncHashchange()
// Don't do anything if the window is frozen.
if (IsFrozen())
return NS_OK;
return NS_OK;
// Dispatch the hashchange event, which doesn't bubble and isn't cancelable,
// to the outer window.
@ -6987,7 +6992,119 @@ nsGlobalWindow::DispatchSyncHashchange()
PR_FALSE, PR_FALSE);
}
// Find an nsCanvasFrame under aFrame. Only search the principal
nsresult
nsGlobalWindow::DispatchSyncPopState()
{
FORWARD_TO_INNER(DispatchSyncPopState, (), NS_OK);
NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
"Must be safe to run script here.");
// Check that PopState hasn't been pref'ed off.
if (!nsContentUtils::GetBoolPref(sPopStatePrefStr, PR_FALSE))
return NS_OK;
nsresult rv = NS_OK;
// Bail if the window is frozen.
if (IsFrozen()) {
return NS_OK;
}
// Bail if there's no document or the document's readystate isn't "complete".
if (!mDoc) {
return NS_OK;
}
nsIDocument::ReadyState readyState = mDoc->GetReadyStateEnum();
if (readyState != nsIDocument::READYSTATE_COMPLETE) {
return NS_OK;
}
// Get the document's pending state object -- it contains the data we're
// going to send along with the popstate event. The object is serialized as
// JSON.
nsAString& stateObjJSON = mDoc->GetPendingStateObject();
nsCOMPtr<nsIVariant> stateObj;
// Parse the JSON, if there's any to parse.
if (!stateObjJSON.IsEmpty()) {
// Get the JSContext associated with our document. We need this for
// deserialization.
nsCOMPtr<nsIDocument> document = do_QueryInterface(mDocument);
NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
// Get the JSContext from the document, like we do in
// nsContentUtils::GetContextFromDocument().
nsIScriptGlobalObject *sgo = document->GetScopeObject();
NS_ENSURE_TRUE(sgo, NS_ERROR_FAILURE);
nsIScriptContext *scx = sgo->GetContext();
NS_ENSURE_TRUE(scx, NS_ERROR_FAILURE);
JSContext *cx = (JSContext*) scx->GetNativeContext();
// If our json call triggers a JS-to-C++ call, we want that call to use cx
// as the context. So we push cx onto the context stack.
nsCxPusher cxPusher;
jsval jsStateObj = JSVAL_NULL;
// Root the container which will hold our decoded state object.
nsAutoGCRoot(&jsStateObj, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// Deserialize the state object into an nsIVariant.
nsCOMPtr<nsIJSON> json = do_GetService("@mozilla.org/dom/json;1");
NS_ENSURE_TRUE(cxPusher.Push(cx), NS_ERROR_FAILURE);
rv = json->DecodeToJSVal(stateObjJSON, cx, &jsStateObj);
NS_ENSURE_SUCCESS(rv, rv);
cxPusher.Pop();
nsCOMPtr<nsIXPConnect> xpconnect = do_GetService(nsIXPConnect::GetCID());
NS_ENSURE_TRUE(xpconnect, NS_ERROR_FAILURE);
rv = xpconnect->JSValToVariant(cx, &jsStateObj, getter_AddRefs(stateObj));
NS_ENSURE_SUCCESS(rv, rv);
}
// Obtain a presentation shell for use in creating a popstate event.
nsIPresShell *shell = mDoc->GetPrimaryShell();
nsCOMPtr<nsPresContext> presContext;
if (shell) {
presContext = shell->GetPresContext();
}
// Create a new popstate event
nsCOMPtr<nsIDOMEvent> domEvent;
rv = nsEventDispatcher::CreateEvent(presContext, nsnull,
NS_LITERAL_STRING("popstateevent"),
getter_AddRefs(domEvent));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(domEvent);
NS_ENSURE_TRUE(privateEvent, NS_ERROR_FAILURE);
// Initialize the popstate event, which does bubble but isn't cancellable.
nsCOMPtr<nsIDOMPopStateEvent> popstateEvent = do_QueryInterface(domEvent);
rv = popstateEvent->InitPopStateEvent(NS_LITERAL_STRING("popstate"),
PR_TRUE, PR_FALSE,
stateObj);
NS_ENSURE_SUCCESS(rv, rv);
rv = privateEvent->SetTrusted(PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDOMEventTarget> outerWindow =
do_QueryInterface(GetOuterWindow());
NS_ENSURE_TRUE(outerWindow, NS_ERROR_UNEXPECTED);
rv = privateEvent->SetTarget(outerWindow);
NS_ENSURE_SUCCESS(rv, rv);
PRBool dummy; // default action
return DispatchEvent(popstateEvent, &dummy);
}
// Find an nsICanvasFrame under aFrame. Only search the principal
// child lists. aFrame must be non-null.
static nsCanvasFrame* FindCanvasFrame(nsIFrame* aFrame)
{

Просмотреть файл

@ -448,6 +448,8 @@ public:
virtual void SetReadyForFocus();
virtual void PageHidden();
virtual nsresult DispatchSyncHashchange();
virtual nsresult DispatchSyncPopState();
virtual nsresult SetArguments(nsIArray *aArguments, nsIPrincipal *aOrigin);
static PRBool DOMWindowDumpEnabled();

Просмотреть файл

@ -57,6 +57,13 @@
#include "nsReadableUtils.h"
#include "nsDOMClassInfo.h"
#include "nsContentUtils.h"
#include "nsIDOMNSDocument.h"
#include "nsISHistoryInternal.h"
static const char* sAllowPushStatePrefStr =
"browser.history.allowPushState";
static const char* sAllowReplaceStatePrefStr =
"browser.history.allowReplaceState";
//
// History class implementation
@ -258,7 +265,7 @@ nsHistory::Go(PRInt32 aDelta)
PRInt32 curIndex=-1;
PRInt32 len = 0;
nsresult rv = session_history->GetIndex(&curIndex);
nsresult rv = session_history->GetIndex(&curIndex);
rv = session_history->GetCount(&len);
PRInt32 index = curIndex + aDelta;
@ -272,6 +279,47 @@ nsHistory::Go(PRInt32 aDelta)
return NS_OK;
}
NS_IMETHODIMP
nsHistory::PushState(nsIVariant *aData, const nsAString& aTitle,
const nsAString& aURL)
{
// Check that PushState hasn't been pref'ed off.
if (!nsContentUtils::GetBoolPref(sAllowPushStatePrefStr, PR_FALSE))
return NS_OK;
NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
// AddState might run scripts, so we need to hold a strong reference to the
// docShell here to keep it from going away.
nsCOMPtr<nsIDocShell> docShell = mDocShell;
// PR_FALSE tells the docshell to add a new history entry instead of
// modifying the current one.
nsresult rv = mDocShell->AddState(aData, aTitle, aURL, PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
nsHistory::ReplaceState(nsIVariant *aData, const nsAString& aTitle,
const nsAString& aURL)
{
// Check that ReplaceState hasn't been pref'ed off
if (!nsContentUtils::GetBoolPref(sAllowReplaceStatePrefStr, PR_FALSE))
return NS_OK;
NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
// As in PushState(), we need to keep a strong reference to the docShell
// here.
nsCOMPtr<nsIDocShell> docShell = mDocShell;
// PR_TRUE tells the docshell to modify the current SHEntry, rather than
// create a new one.
return mDocShell->AddState(aData, aTitle, aURL, PR_TRUE);
}
NS_IMETHODIMP
nsHistory::Item(PRUint32 aIndex, nsAString& aReturn)
{

Просмотреть файл

@ -194,7 +194,6 @@ nsLocation::CheckURL(nsIURI* aURI, nsIDocShellLoadInfo** aLoadInfo)
return NS_ERROR_FAILURE;
nsCOMPtr<nsISupports> owner;
nsCOMPtr<nsIURI> sourceURI;
if (cx) {
// No cx means that there's no JS running, or at least no JS that
@ -222,7 +221,6 @@ nsLocation::CheckURL(nsIURI* aURI, nsIDocShellLoadInfo** aLoadInfo)
!principal)
return NS_ERROR_FAILURE;
owner = do_QueryInterface(principal);
principal->GetURI(getter_AddRefs(sourceURI));
}
// Create load info
@ -232,10 +230,12 @@ nsLocation::CheckURL(nsIURI* aURI, nsIDocShellLoadInfo** aLoadInfo)
loadInfo->SetOwner(owner);
// now set the referrer on the loadinfo
if (sourceURI) {
// Now set the referrer on the loadinfo. We need to do this in order to get
// the correct referrer URI from a document which was pushStated.
nsCOMPtr<nsIURI> sourceURI;
result = GetURI(getter_AddRefs(sourceURI));
if (NS_SUCCEEDED(result))
loadInfo->SetReferrer(sourceURI);
}
loadInfo.swap(*aLoadInfo);

Просмотреть файл

@ -461,6 +461,10 @@ public:
*/
virtual nsresult DispatchSyncHashchange() = 0;
/**
* Instructs this window to synchronously dispatch a popState event.
*/
virtual nsresult DispatchSyncPopState() = 0;
/**
* Tell this window that there is an observer for orientation changes

Просмотреть файл

@ -39,7 +39,9 @@
#include "domstubs.idl"
[scriptable, uuid(896d1d20-b4c4-11d2-bd93-00805f8ae3f4)]
interface nsIVariant;
[scriptable, uuid(208f2af7-9f2e-497c-8a53-9e7803280898)]
interface nsIDOMHistory : nsISupports
{
readonly attribute long length;
@ -52,4 +54,10 @@ interface nsIDOMHistory : nsISupports
void go([optional] in long aDelta);
DOMString item(in unsigned long index);
void pushState(in nsIVariant aData,
in DOMString aTitle,
[optional] in DOMString aURL);
void replaceState(in nsIVariant aData,
in DOMString aTitle,
[optional] in DOMString aURL);
};

Просмотреть файл

@ -83,6 +83,7 @@ XPIDLSRCS = \
nsIDOMOrientationEvent.idl \
nsIDOMScrollAreaEvent.idl \
nsIDOMTransitionEvent.idl \
nsIDOMPopStateEvent.idl \
$(NULL)
include $(topsrcdir)/config/rules.mk

Просмотреть файл

@ -0,0 +1,51 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is the Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsIDOMEvent.idl"
interface nsIVariant;
[scriptable, uuid(f3834fd5-0ef5-4ccd-a741-0b6685414342)]
interface nsIDOMPopStateEvent : nsIDOMEvent
{
/**
* The state associated with this popstate event
*/
readonly attribute nsIVariant state;
void initPopStateEvent(in DOMString typeArg,
in boolean canBubbleArg,
in boolean cancelableArg,
in nsIVariant stateArg);
};

Просмотреть файл

@ -42,10 +42,18 @@ interface nsIInputStream;
interface nsIOutputStream;
interface nsIScriptGlobalObject;
%{ C++
#include "jspubtd.h"
%}
native JSVal(jsval);
[ptr] native JSValPtr(jsval);
[ptr] native JSContext(JSContext);
/**
* Encode and decode JSON text.
*/
[scriptable, uuid(45464c36-efde-4cb5-8e00-07480533ff35)]
[scriptable, uuid(6fcf09ee-87d0-42ec-a72a-8d60114e974f)]
interface nsIJSON : nsISupports
{
AString encode(/* in JSObject value */);
@ -59,4 +67,9 @@ interface nsIJSON : nsISupports
void /* JSObject */ decodeFromStream(in nsIInputStream stream,
in long contentLength);
[noscript] AString encodeFromJSVal(in JSValPtr value, in JSContext cx);
// Make sure you GCroot the result of this function before using it.
[noscript] JSVal decodeToJSVal(in AString str, in JSContext cx);
};

Просмотреть файл

@ -51,6 +51,9 @@ CPPSRCS = \
nsJSON.cpp \
$(NULL)
EXPORTS = nsJSON.h \
$(NULL)
FORCE_STATIC_LIB = 1
LOCAL_INCLUDES = \

Просмотреть файл

@ -195,6 +195,27 @@ WriteCallback(const jschar *buf, uint32 len, void *data)
return JS_TRUE;
}
NS_IMETHODIMP
nsJSON::EncodeFromJSVal(jsval *value, JSContext *cx, nsAString &result)
{
result.Truncate();
// Begin a new request
JSAutoRequest ar(cx);
nsJSONWriter writer;
JSBool ok = JS_Stringify(cx, value, NULL, JSVAL_NULL,
WriteCallback, &writer);
if (!ok) {
return NS_ERROR_XPC_BAD_CONVERT_JS;
}
NS_ENSURE_TRUE(writer.DidWrite(), NS_ERROR_UNEXPECTED);
writer.FlushBuffer();
result.Assign(writer.mOutputString);
return NS_OK;
}
nsresult
nsJSON::EncodeInternal(nsJSONWriter *writer)
{
@ -382,6 +403,30 @@ nsJSON::DecodeFromStream(nsIInputStream *aStream, PRInt32 aContentLength)
return DecodeInternal(aStream, aContentLength, PR_TRUE);
}
NS_IMETHODIMP
nsJSON::DecodeToJSVal(const nsAString &str, JSContext *cx, jsval *result)
{
JSAutoRequest ar(cx);
JSONParser *parser = JS_BeginJSONParse(cx, result);
NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED);
JSBool ok = JS_ConsumeJSONText(cx, parser,
(jschar*)PromiseFlatString(str).get(),
(uint32)str.Length());
// Since we've called JS_BeginJSONParse, we have to call JS_FinishJSONParse,
// even if JS_ConsumeJSONText fails. But if either fails, we'll report an
// error.
ok = ok && JS_FinishJSONParse(cx, parser, JSVAL_NULL);
if (!ok) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
nsresult
nsJSON::DecodeInternal(nsIInputStream *aStream,
PRInt32 aContentLength,

Просмотреть файл

@ -77,6 +77,9 @@ _TEST_FILES = \
postMessage.jar \
postMessage.jar^headers^ \
test_bug477323.html \
test_bug500328.html \
file_bug500328_1.html \
file_bug500328_2.html \
$(NULL)
_CHROME_FILES = \

Просмотреть файл

@ -0,0 +1,34 @@
<!DOCTYPE HTML>
<html>
<!--
Inner frame for testing bug 500328.
https://bugzilla.mozilla.org/show_bug.cgi?id=500328
-->
<head>
<title>test 1</title>
</head>
<body onload="load();" onpopstate="popstate(event);">
<script type="application/javascript">
function load() {
if(parent && parent.onChildLoad)
parent.onChildLoad();
if(opener && opener.onChildLoad)
opener.onChildLoad();
}
function popstate(e) {
if(parent && parent.onChildLoad)
parent.onChildPopState(e);
if(opener && opener.onChildLoad)
opener.onChildPopState(e);
}
function navigateTo(loc) {
location = loc;
}
</script>
<a id="link-anchor1", href="#1">Link Anchor1</a>
<a id="link-self" href="file_bug500328_1.html">Self</a>
</body>
</html>

Просмотреть файл

@ -0,0 +1,17 @@
<!DOCTYPE HTML>
<html>
<!--
Inner frame for testing bug 500328.
https://bugzilla.mozilla.org/show_bug.cgi?id=500328
-->
<head>
</head>
<body>
<!--
Yes, this page is basically blank. But no, about:blank wouldn't do as a
replacement, because we use this page to test that pushstate has correct
same-origin checks.
-->
file_bug500328_2.html
</body>
</html>

Просмотреть файл

@ -0,0 +1,740 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=500328
-->
<head>
<title>Test for Bug 500328</title>
<script type="application/javascript" src="/MochiKit/packed.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.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=500328">Mozilla Bug 500328</a>
<p id="display"></p>
<div id="status"></div>
<div id="content">
<iframe id="iframe"></iframe>
<iframe id="iframe2"></iframe>
<a id="link">link</a>
</div>
<pre id="test">
<script type="application/javascript;version=1.7">
/** Test for Bug 500328 **/
SimpleTest.waitForExplicitFinish();
var iframe = document.getElementById("iframe");
var iframeCw = iframe.contentWindow;
var iframe2 = document.getElementById("iframe2");
var iframe2Cw = iframe2.contentWindow;
var unvisitedColor;
var visitedColor;
var gCallbackOnIframeLoad = false;
var gCallbackOnPopState = false;
var gNumPopStates = 0;
var gLastPopStateEvent;
var gGen;
function statusMsg(msg) {
var msgElem = document.createElement("p");
msgElem.appendChild(document.createTextNode(msg));
document.getElementById("status").appendChild(msgElem);
}
function longWait() {
setTimeout(function() { gGen.next(); }, 1000);
}
function shortWait() {
setTimeout(function() { gGen.next(); }, 0);
}
function onChildPopState(e) {
gNumPopStates++;
gLastPopStateEvent = e;
if (gCallbackOnPopState) {
statusMsg("Popstate(" + JSON.stringify(e.state) + "). Calling gGen.next().");
gCallbackOnPopState = false;
gGen.next();
}
else {
statusMsg("Popstate(" + JSON.stringify(e.state) + "). NOT calling gGen.next().");
}
}
function onChildLoad() {
if(gCallbackOnIframeLoad) {
statusMsg("Got load. About to call gGen.next().");
gCallbackOnIframeLoad = false;
gGen.next();
}
else {
statusMsg("Got load, but not calling gGen.next() because gCallbackOnIframeLoad was false.");
}
}
function enableChildLoadCallback() {
gCallbackOnIframeLoad = true;
}
function enableChildPopStateCallback() {
gCallbackOnPopState = true;
}
function clearPopStateCounter() {
gNumPopStates = 0;
}
function noPopStateExpected(msg) {
is(gNumPopStates, 0, msg);
// Even if there's an error, set gNumPopStates to 0 so other tests don't
// fail.
gNumPopStates = 0;
}
function popstateExpected(msg) {
is(gNumPopStates, 1, msg);
gNumPopStates = 0;
}
function getColor(elem) {
return document.defaultView.getComputedStyle(elem, "").color;
}
function getSHistory(theWindow)
{
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
const Ci = Components.interfaces;
var sh = theWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.sessionHistory;
if (!sh || sh == null)
throw("Couldn't get shistory for window!");
return sh;
}
function getChromeWin(theWindow)
{
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
const Ci = Components.interfaces;
return theWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow)
.QueryInterface(Ci.nsIDOMChromeWindow);
}
function getSHTitle(sh, offset)
{
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
if (!offset)
offset = 0;
// False instructs the SHistory not to modify its current index.
return sh.getEntryAtIndex(sh.index + offset, false).title;
}
// Tests that win's location ends with str
function locationEndsWith(win, str) {
var exp = new RegExp(str + "$");
ok(win.location.toString().match(exp),
"Wrong window location. Expected it to end with " +
str + ", but actuall was " + win.location);
}
function expectException(func, msg) {
var failed = false;
try {
func();
} catch(ex) {
failed = true;
}
ok(failed, msg + " succeeded, but should have failed.");
}
function runTest() {
// We can't enable universal XPConnect privleges in this function, because
// test 5 needs to be running at normal privleges in order to test the
// same-origin policy.
/**
* PRELIMINARY:
* 1. Clear the popstate counter
* 2. Get the visited and unvisited link colors.
*/
clearPopStateCounter();
// Set the link's href to somewhere we haven't been so we can get the
// unvisited link color.
var rand = Date.now() + "-" + Math.random();
$("link").href = rand;
unvisitedColor = getColor($("link"));
statusMsg("Unvisited color is " + unvisitedColor);
// Set the link's href to our current location so we can get the visited link
// color.
$("link").href = document.location;
visitedColor = getColor($("link"));
statusMsg("Visited color is " + visitedColor);
isnot(visitedColor, unvisitedColor, "visited/unvisited link colors are the same?");
// The URL of file_bug500328_1.html on http://localhost:8888
var innerLoc;
// Now we can start the tests
/**
* TEST 1 tests basic pushState functionality
*/
enableChildLoadCallback();
iframeCw.location = "file_bug500328_1.html";
yield;
innerLoc = iframeCw.location.toString();
// Load should be fired before popstate.
enableChildPopStateCallback();
yield;
popstateExpected("Expected initial popstate.");
statusMsg("Awake after first popstate.");
var testObj1 = 42;
var testObj2 = 4.2;
iframeCw.history.pushState(testObj1, "test 1");
is(iframeCw.location.search, "",
"First pushstate should leave us where we were.");
// Make sure that this pushstate doesn't trigger a hashchange.
iframeCw.onhashchange = function() {
ok(false, "Pushstate shouldn't trigger a hashchange.");
};
iframeCw.history.pushState(testObj2, "test 1#foo", "?test1#foo");
is(iframeCw.location.search, "?test1",
"Second pushstate should push us to '?test1'.");
is(iframeCw.location.hash, "#foo",
"Second pushstate should push us to '#foo'");
shortWait();
yield;
// Let the hashchange event fire, if it's going to.
longWait();
yield;
iframeCw.onhashchange = null;
statusMsg("About to go back to page 1.");
// We don't have to yield here because this back() and the resulting popstate
// are completely synchronous. In fact, if we did yield, JS would throw an
// error because we'd be calling gGen.next from within gGen.next.
iframeCw.history.back();
statusMsg("Awake after going back to page 1.");
popstateExpected("Going back to page 1 should trigger a popstate.");
is(JSON.stringify(gLastPopStateEvent.state), JSON.stringify(testObj1),
"Wrong state object popped after going back to page 1.");
ok(iframeCw.location.toString().match(/file_bug500328_1.html$/),
"Going back to page 1 hould take us to original page.");
iframeCw.history.back();
popstateExpected("Going back to page 0 should trigger a popstate.");
is(gLastPopStateEvent.state, null,
"Going back to page 0 should pop a null state.");
is(iframeCw.location.search, "",
"Going back to page 0 should clear the querystring.");
iframeCw.history.forward();
popstateExpected("Going forward to page 1 should trigger a popstate.");
is(JSON.stringify(gLastPopStateEvent.state), JSON.stringify(testObj1),
"Wrong state object popped after going forward to page 1.");
ok(iframeCw.location.toString().match(/file_bug500328_1.html$/),
"Going forward to page 1 should leave us at original page.");
statusMsg("About to go forward to page 2.");
iframeCw.history.forward();
statusMsg("Awake after going forward to page 2.");
popstateExpected("Going forward to page 2 should trigger a popstate.");
is(JSON.stringify(gLastPopStateEvent.state), JSON.stringify(testObj2),
"Wrong state object popped after going forward to page 2.");
ok(iframeCw.location.toString().match(/file_bug500328_1.html\?test1#foo$/),
"Going forward to page 2 took us to " + iframeCw.location.toString());
// The iframe's current location is file_bug500328_1.html?test1#foo.
// Clicking link1 should take us to file_bug500328_1.html?test1#1.
enableChildPopStateCallback();
sendMouseEvent({type:'click'}, 'link-anchor1', iframeCw);
yield;
popstateExpected("Clicking on link-anchor1 should trigger a popstate.");
is(iframeCw.location.search, "?test1",
"search should be ?test1 after clicking link.");
is(iframeCw.location.hash, "#1",
"hash should be #1 after clicking link.");
/*
* Reload file_bug500328_1.html; we're now going to test that link hrefs
* and colors are updated correctly on push/popstates.
*/
iframe.onload = onChildLoad;
enableChildLoadCallback();
iframeCw.location = "about:blank";
yield;
iframe.onload = null;
iframeCw.location = "file_bug500328_1.html";
enableChildPopStateCallback();
yield;
popstateExpected("No popstate after re-loading file_bug500328_1.html");
statusMsg("Done loading file_bug500328_1.html for the second time.");
var ifLink = iframeCw.document.getElementById("link-anchor1");
ifLink.href = rand;
// Poll the document until the link has the correct color, or this test times
// out. Unfortunately I can't come up with a more elegant way to do this.
// We could listen to MozAfterPaint, but that doesn't guarantee that we'll
// observe the new color.
while (getColor(ifLink) != unvisitedColor) {
// Dump so something shows up in the mochitest logs if we spin here.
dump("ifLink has wrong initial color. Spinning...\n");
setTimeout(function() { gGen.next(); }, 10);
yield;
}
// Navigate iframe2 to dir/${rand}
iframe2.onload = onChildLoad;
enableChildLoadCallback();
iframe2Cw.location = "mytestdir/" + rand;
yield;
// PushState the iframe into the mytestdir directory. This should cause
// ifLink to turn purple, since we just visited mytestdir/${rand} in iframe2.
iframeCw.history.pushState(null, "foo", "mytestdir/foo");
// Check that the link's color is now visitedColor
while (getColor(ifLink) != visitedColor) {
dump("ifLink has wrong color after pushstate. Spinning...\n");
setTimeout(function() { gGen.next(); }, 10);
yield;
}
ok(ifLink.href.match("mytestdir\\/" + rand + "$"),
"inner frame's link should end with 'mytestdir/${rand}'");
// Navigate out of the mytestdir directory. This should cause ifLink to turn
// blue again.
iframeCw.history.pushState(null, "bar", "../file_bug500328_1.html");
// Check that the link's color is back to the unvisited color.
while (getColor(ifLink) != unvisitedColor) {
dump("ifLink has wrong color after pushstating out of dir. Spinning...\n");
setTimeout(function() { gGen.next(); }, 10);
yield;
}
ok(!ifLink.href.match("mytestdir"),
"inner frame's link shouldn't contain 'mytestdir'.");
/*
* TEST 2 tests that pushstate's same-origin checks are correct.
*/
var filename = 'file_bug500328_2.html';
// Get the directory we're currently in
var dirname = document.location.pathname.replace(/[^\/]*$/, '');
statusMsg("Dirname is: " + dirname);
var loc = 'http://example.com' + dirname + filename;
statusMsg("About to transfer iframe to " + loc);
iframeCw.location = loc;
// We have to register a listener like this because this file is hosted on a
// different domain and can't notify us on load.
iframe.onload = onChildLoad;
enableChildLoadCallback();
yield;
// This function tries to pushstate and replacestate to the given URL and
// fails the test if the calls succeed.
var tryBadPushAndReplaceState = function(url) {
// XXX ex should be a SECURITY_ERR, not a plain Error.
var hist = iframeCw.history;
var url2 = url + dirname + filename;
expectException(function() { hist.pushState({}, "foo", url); },
'pushState to ' + url);
expectException(function() { hist.pushState({}, "foo", url2); },
'pushState to ' + url2);
expectException(function() { hist.replaceState({}, "foo", url); },
'replaceState to ' + url);
expectException(function() { hist.replaceState({}, "foo", url2); },
'replaceState to ' + url2);
}
// We're currently at http://example.com/[dirname]/[filename]
tryBadPushAndReplaceState("https://example.com");
tryBadPushAndReplaceState("http://foo.example.com");
tryBadPushAndReplaceState("http://example.com:1234");
tryBadPushAndReplaceState("http://example.com.a");
tryBadPushAndReplaceState("http://example.con");
tryBadPushAndReplaceState("http://eexample.com");
tryBadPushAndReplaceState("http://me@example.com");
/**
* TEST 3 tests that the session history entries' titles are properly sync'ed
* after push/pop states.
*
* We have to run this test in a popup rather than an iframe because only the
* root docshell has a session history object.
*/
statusMsg("About to open popup.");
var popup = window.open("file_bug500328_1.html", "popup0",
"height=200,width=200,location=yes," +
"menubar=yes,status=yes,toolbar=yes,dependent=yes");
var shistory = getSHistory(popup);
enableChildPopStateCallback();
yield;
popstateExpected("Didn't get popstate after opening window.");
popup.history.pushState(null, "title 0");
ok(!getChromeWin(popup).document
.getElementById("Browser:Back").hasAttribute("disabled"),
"Back button was not enabled after initial pushstate.");
popup.document.title = "title 1";
// Yield to the event loop so listeners will be notified of the title change
// and so that the hash change we trigger below generates a new session
// history entry.
shortWait();
yield;
// Check that the current session history entry's title has been updated to
// reflect the new document title.
is(getSHTitle(shistory), "title 1", "SHEntry title test 1");
// Change the page's hash to #1, which will trigger a popstate event.
// We don't have to wait, because this happens synchronously.
popup.location.hash = "#1";
popstateExpected("Didn't get popstate after changing hash.");
popup.document.title = "title 2";
// Yield so listeners will be notified of the title change we just performed.
shortWait();
yield;
is(getSHTitle(shistory), "title 2", "SHEntry title test 2");
// Go back. Happens synchronously. We should get a popstate.
statusMsg("About to go back.");
popup.history.go(-1);
popstateExpected("Didn't get a popstate after going back.");
// Even though we went back, we expect the SHEntry title to remain the same
// because the document didn't change.
is(getSHTitle(shistory), "title 2", "SHEntry title test 3");
popup.document.title = "Changed 1";
shortWait();
yield;
// This check is really a test of bug 509055.
is(getSHTitle(shistory), "Changed 1", "SHEntry title test 4");
popup.close();
/**
* TEST 4 tests replaceState and that we don't get double popstates on
* window.open. It also stress-tests the system and its interaction with
* bfcache by making many push/replace state calls.
*/
popup = window.open("file_bug500328_1.html", "popup1",
"height=200,width=200,location=yes," +
"menubar=yes,status=yes,toolbar=yes,dependent=yes");
// The initial about:blank load into the new window shouldn't result in us
// seeing a popstate. Once file_bug500328_1.html is loaded, it'll overwrite
// popup.onpopstate, and this assertion won't fire for that popstate and
// others after.
//
// If we fired the popstate event asynchronously, we'd expect this assert to
// fire.
popup.onpopstate = function() {
ok(false, "Initial load of popup shouldn't give us a popstate.");
};
shistory = getSHistory(popup);
enableChildPopStateCallback();
yield;
statusMsg("Awake after loading content into popup.");
popup.history.replaceState({n:1, ok:true}, "state 1", "good1.html");
locationEndsWith(popup, "good1.html");
is(getSHTitle(shistory), "state 1", "SHEntry title 'state 1'");
// Flush the event loop so our next load creates a new session history entry.
shortWait();
yield;
enableChildPopStateCallback();
popup.location = "file_bug500328_1.html";
yield;
// Flush the event loop so nsDocShell::OnNewURI runs and our load is recorded
// properly. OnNewURI is called after PopState fires, because OnNewURI
// results from an async event, while PopState is sync. We'd have to do the
// same thing if we were listening to onload here, so this isn't
// unreasonable.
shortWait();
yield;
// Now go back and make sure everything is as it should be.
enableChildPopStateCallback();
popup.history.back();
yield;
// Flush the event loop so the document's location is updated.
shortWait();
yield;
// We had some popstates above without corresponding popstateExpected()
// calls, so we need to clear the counter.
clearPopStateCounter();
locationEndsWith(popup, "good1.html");
is(JSON.stringify(gLastPopStateEvent.state), '{"n":1,"ok":true}',
"Wrong state popped after going back to initial state.");
// We're back at state 0, which was replaceState-ed to state1.html. Let's do
// some push/pop/replaces to make sure everything works OK when we involve
// large numbers of SHEntries.
for(var i = 2; i <= 30; i++) {
if (i % 3 == 0) {
popup.history.pushState({n:i, ok:true}, "state " + i, "good" + i + ".html");
}
else {
popup.history.pushState({n:i}, "state " + i, "state" + i + ".html");
for(var j = 0; j < i % 4; j++) {
popup.history.replaceState({n:i, nn:j}, "state " + i + ", " + j);
}
popup.history.replaceState({n:i, ok:true}, "state " + i, "good" + i + ".html");
}
}
for(var i = 29; i >= 1; i--) {
popup.history.back();
popstateExpected("Didn't get a popstate on iteration " + i);
locationEndsWith(popup, "good" + i + ".html");
is(gLastPopStateEvent.state.n, i, "Bad counter on last popstate event.");
ok(gLastPopStateEvent.state.ok,
"Last popstate event should have 'ok' set to true.");
}
popup.close();
/**
* TEST 5 tests misc security features and implementation details of
* Push/ReplaceState
*/
/*
* Test that we can't push/replace an object with a large (over 640k
* characters) JSON representation.
*/
// (In case you're curious, this loop generates an object which serializes to
// 694581 characters.)
var bigObject = new Object();
for(var i = 0; i < 51200; i++) {
bigObject[i] = i;
}
// statusMsg("Big object has size " + JSON.stringify(bigObject).length);
// We shouldn't be able to pushstate this large object, due to space
// constraints.
expectException(
function() { iframeCw.history.pushState(bigObject, "foo"); },
"pushState-ing large object");
expectException(
function() { iframeCw.history.replaceState(bigObject, "foo"); },
"replaceState-ing large object");
/*
* Make sure we can't push/replace state on an iframe of a different origin.
* This will work if this function has requested Universal XPConnect
* privileges, so any code which needs those privileges can't be in this
* function.
*/
enableChildLoadCallback();
iframeCw.location = "http://example.com";
iframe.onload = onChildLoad;
yield;
iframe.onload = null;
expectException(
function() { iframeCw.history.pushState({}, "foo"); },
"pushState-ing in a different origin");
expectException(
function() { iframeCw.history.replaceState({}, "foo"); },
"replaceState-ing in a different origin");
/*
* If we do the following:
* * Start at page A.
* * PushState to page B.
* * Refresh. The server responds with a 404
* * Go back.
* Then at the end, page A should be displayed, not the 404 page.
*/
enableChildLoadCallback();
iframe.onload = onChildLoad;
iframeCw.location = "about:blank";
yield;
iframe.onload = null;
enableChildPopStateCallback();
// navigate to http://localhost:8888/[...]/file_bug500328_1.html
iframeCw.location = innerLoc;
yield;
// Let the PopState handler finish. If we call refresh (below) from within
// the handler, we get slightly confused and can't tell that we're at a 404
// after the refresh.
shortWait();
yield;
// PushState to a URL which doesn't exist
iframeCw.history.pushState({}, "", rand);
// Refresh. We'll end up a 404 page.
iframe.onload = onChildLoad;
enableChildLoadCallback();
iframeCw.location.reload(true);
yield;
// Since the last page was a 404, going back should actually show the
// contents of the old page, instead of persisting the contents of the 404
// page.
clearPopStateCounter();
enableChildPopStateCallback();
iframeCw.history.back();
yield;
popstateExpected("Didn't get popstate after going back.");
// Make sure that we're actually showing the contents of
// file_bug500328_1.html, as opposed to the 404 page.
var identifierElem = iframeCw.document.getElementById("link-anchor1");
ok(identifierElem != undefined && identifierElem != null,
"iframe didn't contain file_bug500328_1.html's contents.");
/**
* TEST 6 tests that the referrer is set properly after push/replace states.
*/
/*
* First, a simple test:
* * Load file_bug500328_1.html into iframe
* * PushState to newpage1.html#foo
* * Instruct the iframe to load file_bug500328_1.html into itself.
* The referer should be newpage1.html, without the hash.
*
* This also tests that we can call pushState from within the onload handler.
*/
enableChildLoadCallback();
iframeCw.location = "file_bug500328_1.html";
yield;
// Run within the onload handler. This should work without issue.
iframeCw.history.pushState(null, "", "newpage1.html");
// iframeCw.navigateTo() causes the iframe to set its location on our
// behalf. We can't just set its location ourselves, because then *we*
// become the referrer.
enableChildPopStateCallback();
iframeCw.navigateTo("file_bug500328_1.html");
yield;
ok(iframeCw.document.referrer.toString().match(/newpage1.html$/),
"Wrong referrer after replaceState. Expected newpage1.html, but was " +
iframeCw.document.referrer);
/*
* We're back at file_bug500328_1.html. Now do the following:
* * replaceState to newpage2.html#foo
* * Click a link back to file_bug500328_1.html
* The referrer should be newpage2.html, without the hash.
*/
iframeCw.history.replaceState(null, null, "newpage2.html#foo");
enableChildPopStateCallback();
sendMouseEvent({type:'click'}, 'link-self', iframeCw);
yield;
ok(iframeCw.document.referrer.toString().match(/newpage2.html$/),
"Wrong referrer after replaceState. Expected newpage2.html, but was " +
iframeCw.document.referrer);
/*
* Set up a cycle with the popstate event to make sure it's properly
* collected.
*/
var evt = document.createEvent("popstateevent");
evt.initEvent("foo", false, false, evt);
/* */
SimpleTest.finish();
statusMsg("********** Finished tests ***********");
while(true)
{
yield;
// I don't think this will actually make the mochitest fail, but there's
// not much we can do about this. Realistically, though, regressions are
// not likely to fire extra events -- this trap is here mostly to catch
// errors made while wriring tests.
ok(false, "Got extra event!");
}
/*
statusMsg("XXXXXXXXXXXXXX");
while(true) {
yield;
statusMsg("Woken up.");
}
*/
}
// Important: Wait to start the tests until the page has loaded. Otherwise,
// the test will occasionally fail when it begins running before the iframes
// have finished their initial load of about:blank.
window.addEventListener('load', function() {
gGen = runTest();
gGen.next();
}, false);
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -57,7 +57,6 @@
native COMVARIANT(VARIANT);
[ptr] native COMVARIANTPtr(VARIANT);
native JSVal(jsval);
[ptr] native JSContextPtr(JSContext);
interface IDispatch;

Просмотреть файл

@ -59,11 +59,12 @@
/***************************************************************************/
// NB: JSVal is declared in nsIVariant.idl
[ptr] native JSContextPtr(JSContext);
[ptr] native JSClassPtr(JSClass);
[ptr] native JSObjectPtr(JSObject);
[ptr] native JSValPtr(jsval);
native JSVal(jsval);
native JSEqualityOp(JSEqualityOp);
native JSID(jsid);
[ptr] native voidPtrPtr(void*);
@ -530,6 +531,12 @@ interface nsIXPConnect : nsISupports
in nsIIDRef aIID,
[iid_is(aIID),retval] out nsQIResult result);
/**
* Wraps the given JSVal in a nsIVariant and returns the new variant.
*/
nsIVariant
jSValToVariant(in JSContextPtr cx, in JSValPtr aJSVal);
/**
* This only succeeds if the JSObject is a nsIXPConnectWrappedNative.
* A new wrapper is *never* constructed.

Просмотреть файл

@ -1370,6 +1370,25 @@ nsXPConnect::WrapJS(JSContext * aJSContext,
return NS_OK;
}
NS_IMETHODIMP
nsXPConnect::JSValToVariant(JSContext *cx,
jsval *aJSVal,
nsIVariant ** aResult)
{
NS_PRECONDITION(aJSVal, "bad param");
NS_PRECONDITION(aResult, "bad param");
*aResult = nsnull;
XPCCallContext ccx(NATIVE_CALLER, cx);
if(!ccx.IsValid())
return NS_ERROR_FAILURE;
*aResult = XPCVariant::newVariant(ccx, *aJSVal);
NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY);
return NS_OK;
}
/* void wrapJSAggregatedToNative (in nsISupports aOuter, in JSContextPtr aJSContext, in JSObjectPtr aJSObj, in nsIIDRef aIID, [iid_is (aIID), retval] out nsQIResult result); */
NS_IMETHODIMP
nsXPConnect::WrapJSAggregatedToNative(nsISupports *aOuter,

Просмотреть файл

@ -4198,6 +4198,10 @@ public:
static XPCVariant* newVariant(XPCCallContext& ccx, jsval aJSVal);
/**
* nsIVariant exposes a GetAsJSVal() method, which also returns mJSVal.
* But if you can, you should call this one, since it can be inlined.
*/
jsval GetJSVal() const {return mJSVal;}
XPCVariant(XPCCallContext& ccx, jsval aJSVal);

Просмотреть файл

@ -390,6 +390,14 @@ JSBool XPCVariant::InitializeData(XPCCallContext& ccx)
NS_SUCCEEDED(nsVariant::SetFromInterface(&mData, iid, wrapper));
}
NS_IMETHODIMP
XPCVariant::GetAsJSVal(jsval* result)
{
NS_PRECONDITION(result, "null result arg.");
*result = GetJSVal();
return NS_OK;
}
// static
JSBool
XPCVariant::VariantDataToJS(XPCLazyCallContext& lccx,
@ -399,43 +407,42 @@ XPCVariant::VariantDataToJS(XPCLazyCallContext& lccx,
{
// Get the type early because we might need to spoof it below.
PRUint16 type;
if(NS_FAILED(variant->GetDataType(&type)))
if (NS_FAILED(variant->GetDataType(&type)))
return JS_FALSE;
nsCOMPtr<XPCVariant> xpcvariant = do_QueryInterface(variant);
if(xpcvariant)
{
jsval realVal = xpcvariant->GetJSVal();
if(JSVAL_IS_PRIMITIVE(realVal) ||
type == nsIDataType::VTYPE_ARRAY ||
type == nsIDataType::VTYPE_EMPTY_ARRAY ||
type == nsIDataType::VTYPE_ID)
{
// Not a JSObject (or is a JSArray or is a JSObject representing
// an nsID),.
// So, just pass through the underlying data.
*pJSVal = realVal;
return JS_TRUE;
}
jsval realVal;
nsresult rv = variant->GetAsJSVal(&realVal);
if(xpcvariant->mReturnRawObject)
{
NS_ASSERTION(type == nsIDataType::VTYPE_INTERFACE ||
type == nsIDataType::VTYPE_INTERFACE_IS,
"Weird variant");
*pJSVal = realVal;
return JS_TRUE;
}
if(NS_SUCCEEDED(rv) &&
(JSVAL_IS_PRIMITIVE(realVal) ||
type == nsIDataType::VTYPE_ARRAY ||
type == nsIDataType::VTYPE_EMPTY_ARRAY ||
type == nsIDataType::VTYPE_ID))
{
// It's not a JSObject (or it's a JSArray or a JSObject representing an
// nsID). Just pass through the underlying data.
*pJSVal = realVal;
return JS_TRUE;
}
nsCOMPtr<XPCVariant> xpcvariant = do_QueryInterface(variant);
if(xpcvariant && xpcvariant->mReturnRawObject)
{
NS_ASSERTION(type == nsIDataType::VTYPE_INTERFACE ||
type == nsIDataType::VTYPE_INTERFACE_IS,
"Weird variant");
*pJSVal = realVal;
return JS_TRUE;
// else, it's an object and we really need to double wrap it if we've
// already decided that its 'natural' type is as some sort of interface.
// We just fall through to the code below and let it do what it does.
}
// The nsIVariant is not a XPCVariant (or we act like it isn't).
// So we extract the data and do the Right Thing.
// We ASSUME that the variant implementation can do these conversions...
nsXPTCVariant xpctvar;

Просмотреть файл

@ -998,7 +998,7 @@ DocumentViewerImpl::LoadComplete(nsresult aStatus)
nsCOMPtr<nsIPresShell> shell = mPresShell;
shell->FlushPendingNotifications(Flush_Layout);
}
nsresult rv = NS_OK;
NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
@ -1038,8 +1038,6 @@ DocumentViewerImpl::LoadComplete(nsresult aStatus)
// if navigator.xul's load is complete, the main nav window is visible
// mark that point.
//printf("DEBUG: getting uri from document (%p)\n", mDocument.get());
nsIURI *uri = mDocument ? mDocument->GetDocumentURI() : nsnull;
if (uri) {
@ -1097,6 +1095,10 @@ DocumentViewerImpl::LoadComplete(nsresult aStatus)
}
#endif
if (!mStopped) {
window->DispatchSyncPopState();
}
return rv;
}

Просмотреть файл

@ -2838,3 +2838,9 @@ pref("html5.flushtimer.continuedelay", 150);
// Time in milliseconds between timer firings once the timer has starting
// firing.
pref("html5.flushtimer.interval", 100);
// Push/Pop/Replace State prefs
pref("browser.history.allowPushState", true);
pref("browser.history.allowReplaceState", true);
pref("browser.history.allowPopState", true);
pref("browser.history.maxStateObjectSize", 655360);

Просмотреть файл

@ -251,5 +251,12 @@ Variant_base::GetAsWStringWithSize(PRUint32 *,
return NS_ERROR_CANNOT_CONVERT_DATA;
}
inline
NS_IMETHODIMP
Variant_base::GetAsJSVal(jsval *)
{
return NS_ERROR_CANNOT_CONVERT_DATA;
}
} // namespace storage
} // namespace mozilla

Просмотреть файл

@ -56,6 +56,7 @@
#include "nsTArray.h"
#include "nsTraceRefcnt.h"
#include "nsITransferable.h"
#include "nsIVariant.h"
class nsIRenderingContext;
class nsIRegion;
@ -246,6 +247,7 @@ class nsHashKey;
#define NS_HASHCHANGE (NS_STREAM_EVENT_START + 2)
#define NS_IMAGE_ABORT (NS_STREAM_EVENT_START + 3)
#define NS_LOAD_ERROR (NS_STREAM_EVENT_START + 4)
#define NS_POPSTATE (NS_STREAM_EVENT_START + 5)
#define NS_BEFORE_PAGE_UNLOAD (NS_STREAM_EVENT_START + 6)
#define NS_PAGE_RESTORE (NS_STREAM_EVENT_START + 7)

Просмотреть файл

@ -1098,6 +1098,7 @@ case _value: eventName.AssignWithConversion(_name) ; break
_ASSIGN_eventName(NS_MOUSE_MOVE,"NS_MOUSE_MOVE");
_ASSIGN_eventName(NS_MOVE,"NS_MOVE");
_ASSIGN_eventName(NS_LOAD,"NS_LOAD");
_ASSIGN_eventName(NS_POPSTATE,"NS_POPSTATE");
_ASSIGN_eventName(NS_PAGE_UNLOAD,"NS_PAGE_UNLOAD");
_ASSIGN_eventName(NS_HASHCHANGE,"NS_HASHCHANGE");
_ASSIGN_eventName(NS_PAINT,"NS_PAINT");

Просмотреть файл

@ -77,6 +77,11 @@ interface nsIDataType : nsISupports
const PRUint16 VTYPE_EMPTY = 255;
};
%{ C++
#include "jspubtd.h"
%}
native JSVal(jsval);
/**
* XPConnect has magic to transparently convert between nsIVariant and JS types.
@ -86,7 +91,7 @@ interface nsIDataType : nsISupports
* JS type anyway.
*/
[scriptable, uuid(6c9eb060-8c6a-11d5-90f3-0010a4e73d9a)]
[scriptable, uuid(81e4c2de-acac-4ad6-901a-b5fb1b851a0d)]
interface nsIVariant : nsISupports
{
[noscript] readonly attribute PRUint16 dataType;
@ -112,6 +117,7 @@ interface nsIVariant : nsISupports
[noscript] string getAsString();
[noscript] wstring getAsWString();
[noscript] nsISupports getAsISupports();
[noscript] JSVal getAsJSVal();
[noscript] void getAsInterface(out nsIIDPtr iid,
[iid_is(iid), retval] out nsQIResult iface);

Просмотреть файл

@ -1886,6 +1886,13 @@ NS_IMETHODIMP nsVariant::GetAsISupports(nsISupports **_retval)
return nsVariant::ConvertToISupports(mData, _retval);
}
/* jsval getAsJSVal() */
NS_IMETHODIMP nsVariant::GetAsJSVal(jsval *_retval)
{
// Can only get the JSVal from an XPCVariant.
return NS_ERROR_CANNOT_CONVERT_DATA;
}
/* void getAsInterface (out nsIIDPtr iid, [iid_is (iid), retval] out nsQIResult iface); */
NS_IMETHODIMP nsVariant::GetAsInterface(nsIID * *iid, void * *iface)
{