зеркало из https://github.com/mozilla/gecko-dev.git
Bug 500328 - Implement History.pushState(), History.replaceState() methods. r=smaug, r=zpao, sr=sicking
This commit is contained in:
Родитель
1444ebd2bf
Коммит
731c252671
|
@ -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)
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче