michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=78: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsHistory.h" michael@0: michael@0: #include "jsapi.h" michael@0: #include "mozilla/dom/HistoryBinding.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIWebNavigation.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsISHistory.h" michael@0: #include "nsISHistoryInternal.h" michael@0: #include "mozilla/Preferences.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: static const char* sAllowPushStatePrefStr = michael@0: "browser.history.allowPushState"; michael@0: static const char* sAllowReplaceStatePrefStr = michael@0: "browser.history.allowReplaceState"; michael@0: michael@0: // michael@0: // History class implementation michael@0: // michael@0: NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsHistory) michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHistory) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHistory) michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsHistory) michael@0: NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMHistory) // Empty, needed for extension compat michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: nsHistory::nsHistory(nsPIDOMWindow* aInnerWindow) michael@0: : mInnerWindow(do_GetWeakReference(aInnerWindow)) michael@0: { michael@0: SetIsDOMBinding(); michael@0: } michael@0: michael@0: nsHistory::~nsHistory() michael@0: { michael@0: } michael@0: michael@0: nsPIDOMWindow* michael@0: nsHistory::GetParentObject() const michael@0: { michael@0: nsCOMPtr win(do_QueryReferent(mInnerWindow)); michael@0: return win; michael@0: } michael@0: michael@0: JSObject* michael@0: nsHistory::WrapObject(JSContext* aCx) michael@0: { michael@0: return HistoryBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: uint32_t michael@0: nsHistory::GetLength(ErrorResult& aRv) const michael@0: { michael@0: nsCOMPtr win(do_QueryReferent(mInnerWindow)); michael@0: if (!win || !win->HasActiveDocument()) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: // Get session History from docshell michael@0: nsCOMPtr sHistory = GetSessionHistory(); michael@0: if (!sHistory) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: int32_t len; michael@0: nsresult rv = sHistory->GetCount(&len); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: aRv.Throw(rv); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: return len >= 0 ? len : 0; michael@0: } michael@0: michael@0: void michael@0: nsHistory::GetState(JSContext* aCx, JS::MutableHandle aResult, michael@0: ErrorResult& aRv) const michael@0: { michael@0: nsCOMPtr win(do_QueryReferent(mInnerWindow)); michael@0: if (!win) { michael@0: aRv.Throw(NS_ERROR_NOT_AVAILABLE); michael@0: return; michael@0: } michael@0: michael@0: if (!win->HasActiveDocument()) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr doc = michael@0: do_QueryInterface(win->GetExtantDoc()); michael@0: if (!doc) { michael@0: aRv.Throw(NS_ERROR_NOT_AVAILABLE); michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr variant; michael@0: doc->GetStateObject(getter_AddRefs(variant)); michael@0: michael@0: if (variant) { michael@0: aRv = variant->GetAsJSVal(aResult); michael@0: michael@0: if (aRv.Failed()) { michael@0: return; michael@0: } michael@0: michael@0: if (!JS_WrapValue(aCx, aResult)) { michael@0: aRv.Throw(NS_ERROR_OUT_OF_MEMORY); michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: aResult.setNull(); michael@0: } michael@0: michael@0: void michael@0: nsHistory::Go(int32_t aDelta, ErrorResult& aRv) michael@0: { michael@0: nsCOMPtr win(do_QueryReferent(mInnerWindow)); michael@0: if (!win || !win->HasActiveDocument()) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: michael@0: return; michael@0: } michael@0: michael@0: if (!aDelta) { michael@0: nsCOMPtr window(do_GetInterface(GetDocShell())); michael@0: michael@0: if (window && window->IsHandlingResizeEvent()) { michael@0: // history.go(0) (aka location.reload()) was called on a window michael@0: // that is handling a resize event. Sites do this since Netscape michael@0: // 4.x needed it, but we don't, and it's a horrible experience michael@0: // for nothing. In stead of reloading the page, just clear michael@0: // style data and reflow the page since some sites may use this michael@0: // trick to work around gecko reflow bugs, and this should have michael@0: // the same effect. michael@0: michael@0: nsCOMPtr doc = window->GetExtantDoc(); michael@0: michael@0: nsIPresShell *shell; michael@0: nsPresContext *pcx; michael@0: if (doc && (shell = doc->GetShell()) && (pcx = shell->GetPresContext())) { michael@0: pcx->RebuildAllStyleData(NS_STYLE_HINT_REFLOW); michael@0: } michael@0: michael@0: return; michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr session_history = GetSessionHistory(); michael@0: nsCOMPtr webnav(do_QueryInterface(session_history)); michael@0: if (!webnav) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: michael@0: return; michael@0: } michael@0: michael@0: int32_t curIndex = -1; michael@0: int32_t len = 0; michael@0: session_history->GetIndex(&curIndex); michael@0: session_history->GetCount(&len); michael@0: michael@0: int32_t index = curIndex + aDelta; michael@0: if (index > -1 && index < len) michael@0: webnav->GotoIndex(index); michael@0: michael@0: // Ignore the return value from GotoIndex(), since returning errors michael@0: // from GotoIndex() can lead to exceptions and a possible leak michael@0: // of history length michael@0: } michael@0: michael@0: void michael@0: nsHistory::Back(ErrorResult& aRv) michael@0: { michael@0: nsCOMPtr win(do_QueryReferent(mInnerWindow)); michael@0: if (!win || !win->HasActiveDocument()) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr sHistory = GetSessionHistory(); michael@0: nsCOMPtr webNav(do_QueryInterface(sHistory)); michael@0: if (!webNav) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: michael@0: return; michael@0: } michael@0: michael@0: webNav->GoBack(); michael@0: } michael@0: michael@0: void michael@0: nsHistory::Forward(ErrorResult& aRv) michael@0: { michael@0: nsCOMPtr win(do_QueryReferent(mInnerWindow)); michael@0: if (!win || !win->HasActiveDocument()) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr sHistory = GetSessionHistory(); michael@0: nsCOMPtr webNav(do_QueryInterface(sHistory)); michael@0: if (!webNav) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: michael@0: return; michael@0: } michael@0: michael@0: webNav->GoForward(); michael@0: } michael@0: michael@0: void michael@0: nsHistory::PushState(JSContext* aCx, JS::Handle aData, michael@0: const nsAString& aTitle, const nsAString& aUrl, michael@0: ErrorResult& aRv) michael@0: { michael@0: PushOrReplaceState(aCx, aData, aTitle, aUrl, aRv, false); michael@0: } michael@0: michael@0: void michael@0: nsHistory::ReplaceState(JSContext* aCx, JS::Handle aData, michael@0: const nsAString& aTitle, const nsAString& aUrl, michael@0: ErrorResult& aRv) michael@0: { michael@0: PushOrReplaceState(aCx, aData, aTitle, aUrl, aRv, true); michael@0: } michael@0: michael@0: void michael@0: nsHistory::PushOrReplaceState(JSContext* aCx, JS::Handle aData, michael@0: const nsAString& aTitle, const nsAString& aUrl, michael@0: ErrorResult& aRv, bool aReplace) michael@0: { michael@0: nsCOMPtr win(do_QueryReferent(mInnerWindow)); michael@0: if (!win) { michael@0: aRv.Throw(NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: return; michael@0: } michael@0: michael@0: if (!win->HasActiveDocument()) { michael@0: aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); michael@0: michael@0: return; michael@0: } michael@0: michael@0: // Check that PushState hasn't been pref'ed off. michael@0: if (!Preferences::GetBool(aReplace ? sAllowReplaceStatePrefStr : michael@0: sAllowPushStatePrefStr, false)) { michael@0: return; michael@0: } michael@0: michael@0: // AddState might run scripts, so we need to hold a strong reference to the michael@0: // docShell here to keep it from going away. michael@0: nsCOMPtr docShell = win->GetDocShell(); michael@0: michael@0: if (!docShell) { michael@0: aRv.Throw(NS_ERROR_FAILURE); michael@0: michael@0: return; michael@0: } michael@0: michael@0: // The "replace" argument tells the docshell to whether to add a new michael@0: // history entry or modify the current one. michael@0: michael@0: aRv = docShell->AddState(aData, aTitle, aUrl, aReplace, aCx); michael@0: } michael@0: michael@0: nsIDocShell* michael@0: nsHistory::GetDocShell() const michael@0: { michael@0: nsCOMPtr win = do_QueryReferent(mInnerWindow); michael@0: if (!win) { michael@0: return nullptr; michael@0: } michael@0: return win->GetDocShell(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsHistory::GetSessionHistory() const michael@0: { michael@0: nsIDocShell *docShell = GetDocShell(); michael@0: NS_ENSURE_TRUE(docShell, nullptr); michael@0: michael@0: // Get the root DocShell from it michael@0: nsCOMPtr root; michael@0: docShell->GetSameTypeRootTreeItem(getter_AddRefs(root)); michael@0: nsCOMPtr webNav(do_QueryInterface(root)); michael@0: NS_ENSURE_TRUE(webNav, nullptr); michael@0: michael@0: nsCOMPtr shistory; michael@0: michael@0: // Get SH from nsIWebNavigation michael@0: webNav->GetSessionHistory(getter_AddRefs(shistory)); michael@0: michael@0: return shistory.forget(); michael@0: }