diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /docshell | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'docshell')
497 files changed, 49699 insertions, 0 deletions
diff --git a/docshell/base/IHistory.h b/docshell/base/IHistory.h new file mode 100644 index 000000000..b1f5bb983 --- /dev/null +++ b/docshell/base/IHistory.h @@ -0,0 +1,144 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_IHistory_h_ +#define mozilla_IHistory_h_ + +#include "nsISupports.h" + +class nsIURI; + +namespace mozilla { + +namespace dom { +class Link; +} // namespace dom + +// 0057c9d3-b98e-4933-bdc5-0275d06705e1 +#define IHISTORY_IID \ + {0x0057c9d3, 0xb98e, 0x4933, {0xbd, 0xc5, 0x02, 0x75, 0xd0, 0x67, 0x05, 0xe1}} + +class IHistory : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(IHISTORY_IID) + + /** + * Registers the Link for notifications about the visited-ness of aURI. + * Consumers should assume that the URI is unvisited after calling this, and + * they will be notified if that state (unvisited) changes by having + * SetLinkState called on themselves. This function is guaranteed to run to + * completion before aLink is notified. After the node is notified, it will + * be unregistered. + * + * @note SetLinkState must not call RegisterVisitedCallback or + * UnregisterVisitedCallback. + * + * @pre aURI must not be null. + * @pre aLink may be null only in the parent (chrome) process. + * + * @param aURI + * The URI to check. + * @param aLink + * The link to update whenever the history status changes. The + * implementation will only hold onto a raw pointer, so if this + * object should be destroyed, be sure to call + * UnregisterVistedCallback first. + */ + NS_IMETHOD RegisterVisitedCallback(nsIURI* aURI, dom::Link* aLink) = 0; + + /** + * Unregisters a previously registered Link object. This must be called + * before destroying the registered object. + * + * @pre aURI must not be null. + * @pre aLink must not be null. + * + * @param aURI + * The URI that aLink was registered for. + * @param aLink + * The link object to unregister for aURI. + */ + NS_IMETHOD UnregisterVisitedCallback(nsIURI* aURI, dom::Link* aLink) = 0; + + enum VisitFlags + { + /** + * Indicates whether the URI was loaded in a top-level window. + */ + TOP_LEVEL = 1 << 0, + /** + * Indicates whether the URI was loaded as part of a permanent redirect. + */ + REDIRECT_PERMANENT = 1 << 1, + /** + * Indicates whether the URI was loaded as part of a temporary redirect. + */ + REDIRECT_TEMPORARY = 1 << 2, + /** + * Indicates the URI is redirecting (Response code 3xx). + */ + REDIRECT_SOURCE = 1 << 3, + /** + * Indicates the URI caused an error that is unlikely fixable by a + * retry, like a not found or unfetchable page. + */ + UNRECOVERABLE_ERROR = 1 << 4 + }; + + /** + * Adds a history visit for the URI. + * + * @pre aURI must not be null. + * + * @param aURI + * The URI of the page being visited. + * @param aLastVisitedURI + * The URI of the last visit in the chain. + * @param aFlags + * The VisitFlags describing this visit. + */ + NS_IMETHOD VisitURI(nsIURI* aURI, + nsIURI* aLastVisitedURI, + uint32_t aFlags) = 0; + + /** + * Set the title of the URI. + * + * @pre aURI must not be null. + * + * @param aURI + * The URI to set the title for. + * @param aTitle + * The title string. + */ + NS_IMETHOD SetURITitle(nsIURI* aURI, const nsAString& aTitle) = 0; + + /** + * Notifies about the visited status of a given URI. + * + * @param aURI + * The URI to notify about. + */ + NS_IMETHOD NotifyVisited(nsIURI* aURI) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IHistory, IHISTORY_IID) + +#define NS_DECL_IHISTORY \ + NS_IMETHOD RegisterVisitedCallback(nsIURI* aURI, \ + mozilla::dom::Link* aContent) override; \ + NS_IMETHOD UnregisterVisitedCallback(nsIURI* aURI, \ + mozilla::dom::Link* aContent) override; \ + NS_IMETHOD VisitURI(nsIURI* aURI, \ + nsIURI* aLastVisitedURI, \ + uint32_t aFlags) override; \ + NS_IMETHOD SetURITitle(nsIURI* aURI, const nsAString& aTitle) override; \ + NS_IMETHOD NotifyVisited(nsIURI* aURI) override; + +} // namespace mozilla + +#endif // mozilla_IHistory_h_ diff --git a/docshell/base/LoadContext.cpp b/docshell/base/LoadContext.cpp new file mode 100644 index 000000000..c4095a951 --- /dev/null +++ b/docshell/base/LoadContext.cpp @@ -0,0 +1,229 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/LoadContext.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/ScriptSettings.h" // for AutoJSAPI +#include "nsContentUtils.h" +#include "xpcpublic.h" + +bool +nsILoadContext::GetOriginAttributes(mozilla::DocShellOriginAttributes& aAttrs) +{ + mozilla::dom::AutoJSAPI jsapi; + bool ok = jsapi.Init(xpc::PrivilegedJunkScope()); + NS_ENSURE_TRUE(ok, false); + JS::Rooted<JS::Value> v(jsapi.cx()); + nsresult rv = GetOriginAttributes(&v); + NS_ENSURE_SUCCESS(rv, false); + NS_ENSURE_TRUE(v.isObject(), false); + JS::Rooted<JSObject*> obj(jsapi.cx(), &v.toObject()); + + // If we're JS-implemented, the object will be left in a different (System-Principaled) + // scope, so we may need to enter its compartment. + MOZ_ASSERT(nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(obj))); + JSAutoCompartment ac(jsapi.cx(), obj); + + mozilla::DocShellOriginAttributes attrs; + ok = attrs.Init(jsapi.cx(), v); + NS_ENSURE_TRUE(ok, false); + aAttrs = attrs; + return true; +} + +namespace mozilla { + +NS_IMPL_ISUPPORTS(LoadContext, nsILoadContext, nsIInterfaceRequestor) + +LoadContext::LoadContext(nsIPrincipal* aPrincipal, + nsILoadContext* aOptionalBase) + : mTopFrameElement(nullptr) + , mNestedFrameId(0) + , mIsContent(true) + , mUseRemoteTabs(false) +#ifdef DEBUG + , mIsNotNull(true) +#endif +{ + PrincipalOriginAttributes poa = BasePrincipal::Cast(aPrincipal)->OriginAttributesRef(); + mOriginAttributes.InheritFromDocToChildDocShell(poa); + if (!aOptionalBase) { + return; + } + + MOZ_ALWAYS_SUCCEEDS(aOptionalBase->GetIsContent(&mIsContent)); + MOZ_ALWAYS_SUCCEEDS(aOptionalBase->GetUseRemoteTabs(&mUseRemoteTabs)); +} + +//----------------------------------------------------------------------------- +// LoadContext::nsILoadContext +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +LoadContext::GetAssociatedWindow(mozIDOMWindowProxy**) +{ + MOZ_ASSERT(mIsNotNull); + + // can't support this in the parent process + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::GetTopWindow(mozIDOMWindowProxy**) +{ + MOZ_ASSERT(mIsNotNull); + + // can't support this in the parent process + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::GetTopFrameElement(nsIDOMElement** aElement) +{ + nsCOMPtr<nsIDOMElement> element = do_QueryReferent(mTopFrameElement); + element.forget(aElement); + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::GetNestedFrameId(uint64_t* aId) +{ + NS_ENSURE_ARG(aId); + *aId = mNestedFrameId; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::GetIsContent(bool* aIsContent) +{ + MOZ_ASSERT(mIsNotNull); + + NS_ENSURE_ARG_POINTER(aIsContent); + + *aIsContent = mIsContent; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) +{ + MOZ_ASSERT(mIsNotNull); + + NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing); + + *aUsePrivateBrowsing = mOriginAttributes.mPrivateBrowsingId > 0; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) +{ + MOZ_ASSERT(mIsNotNull); + + // We shouldn't need this on parent... + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::SetPrivateBrowsing(bool aUsePrivateBrowsing) +{ + MOZ_ASSERT(mIsNotNull); + + // We shouldn't need this on parent... + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::GetUseRemoteTabs(bool* aUseRemoteTabs) +{ + MOZ_ASSERT(mIsNotNull); + + NS_ENSURE_ARG_POINTER(aUseRemoteTabs); + + *aUseRemoteTabs = mUseRemoteTabs; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::SetRemoteTabs(bool aUseRemoteTabs) +{ + MOZ_ASSERT(mIsNotNull); + + // We shouldn't need this on parent... + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +LoadContext::GetIsInIsolatedMozBrowserElement(bool* aIsInIsolatedMozBrowserElement) +{ + MOZ_ASSERT(mIsNotNull); + + NS_ENSURE_ARG_POINTER(aIsInIsolatedMozBrowserElement); + + *aIsInIsolatedMozBrowserElement = mOriginAttributes.mInIsolatedMozBrowser; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::GetAppId(uint32_t* aAppId) +{ + MOZ_ASSERT(mIsNotNull); + + NS_ENSURE_ARG_POINTER(aAppId); + + *aAppId = mOriginAttributes.mAppId; + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::GetOriginAttributes(JS::MutableHandleValue aAttrs) +{ + JSContext* cx = nsContentUtils::GetCurrentJSContext(); + MOZ_ASSERT(cx); + + bool ok = ToJSValue(cx, mOriginAttributes, aAttrs); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + return NS_OK; +} + +NS_IMETHODIMP +LoadContext::IsTrackingProtectionOn(bool* aIsTrackingProtectionOn) +{ + MOZ_ASSERT(mIsNotNull); + + if (Preferences::GetBool("privacy.trackingprotection.enabled", false)) { + *aIsTrackingProtectionOn = true; + } else if ((mOriginAttributes.mPrivateBrowsingId > 0) && + Preferences::GetBool("privacy.trackingprotection.pbmode.enabled", false)) { + *aIsTrackingProtectionOn = true; + } else { + *aIsTrackingProtectionOn = false; + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// LoadContext::nsIInterfaceRequestor +//----------------------------------------------------------------------------- +NS_IMETHODIMP +LoadContext::GetInterface(const nsIID& aIID, void** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + if (aIID.Equals(NS_GET_IID(nsILoadContext))) { + *aResult = static_cast<nsILoadContext*>(this); + NS_ADDREF_THIS(); + return NS_OK; + } + + return NS_NOINTERFACE; +} + +} // namespace mozilla diff --git a/docshell/base/LoadContext.h b/docshell/base/LoadContext.h new file mode 100644 index 000000000..966e7b47f --- /dev/null +++ b/docshell/base/LoadContext.h @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef LoadContext_h +#define LoadContext_h + +#include "SerializedLoadContext.h" +#include "mozilla/Attributes.h" +#include "mozilla/BasePrincipal.h" +#include "nsIWeakReferenceUtils.h" +#include "mozilla/dom/Element.h" +#include "nsIInterfaceRequestor.h" +#include "nsILoadContext.h" + +namespace mozilla { + +/** + * Class that provides nsILoadContext info in Parent process. Typically copied + * from Child via SerializedLoadContext. + * + * Note: this is not the "normal" or "original" nsILoadContext. That is + * typically provided by nsDocShell. This is only used when the original + * docshell is in a different process and we need to copy certain values from + * it. + * + * Note: we also generate a new nsILoadContext using LoadContext(uint32_t aAppId) + * to separate the safebrowsing cookie. + */ + +class LoadContext final + : public nsILoadContext + , public nsIInterfaceRequestor +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSILOADCONTEXT + NS_DECL_NSIINTERFACEREQUESTOR + + // appId/inIsolatedMozBrowser arguments override those in SerializedLoadContext + // provided by child process. + LoadContext(const IPC::SerializedLoadContext& aToCopy, + dom::Element* aTopFrameElement, + DocShellOriginAttributes& aAttrs) + : mTopFrameElement(do_GetWeakReference(aTopFrameElement)) + , mNestedFrameId(0) + , mIsContent(aToCopy.mIsContent) + , mUseRemoteTabs(aToCopy.mUseRemoteTabs) + , mOriginAttributes(aAttrs) +#ifdef DEBUG + , mIsNotNull(aToCopy.mIsNotNull) +#endif + { + } + + // appId/inIsolatedMozBrowser arguments override those in SerializedLoadContext + // provided by child process. + LoadContext(const IPC::SerializedLoadContext& aToCopy, + uint64_t aNestedFrameId, + DocShellOriginAttributes& aAttrs) + : mTopFrameElement(nullptr) + , mNestedFrameId(aNestedFrameId) + , mIsContent(aToCopy.mIsContent) + , mUseRemoteTabs(aToCopy.mUseRemoteTabs) + , mOriginAttributes(aAttrs) +#ifdef DEBUG + , mIsNotNull(aToCopy.mIsNotNull) +#endif + { + } + + LoadContext(dom::Element* aTopFrameElement, + bool aIsContent, + bool aUsePrivateBrowsing, + bool aUseRemoteTabs, + const DocShellOriginAttributes& aAttrs) + : mTopFrameElement(do_GetWeakReference(aTopFrameElement)) + , mNestedFrameId(0) + , mIsContent(aIsContent) + , mUseRemoteTabs(aUseRemoteTabs) + , mOriginAttributes(aAttrs) +#ifdef DEBUG + , mIsNotNull(true) +#endif + { + } + + // Constructor taking reserved origin attributes. + explicit LoadContext(DocShellOriginAttributes& aAttrs) + : mTopFrameElement(nullptr) + , mNestedFrameId(0) + , mIsContent(false) + , mUseRemoteTabs(false) + , mOriginAttributes(aAttrs) +#ifdef DEBUG + , mIsNotNull(true) +#endif + { + } + + // Constructor for creating a LoadContext with a given principal's appId and + // browser flag. + explicit LoadContext(nsIPrincipal* aPrincipal, + nsILoadContext* aOptionalBase = nullptr); + +private: + ~LoadContext() {} + + nsWeakPtr mTopFrameElement; + uint64_t mNestedFrameId; + bool mIsContent; + bool mUseRemoteTabs; + DocShellOriginAttributes mOriginAttributes; +#ifdef DEBUG + bool mIsNotNull; +#endif +}; + +} // namespace mozilla + +#endif // LoadContext_h diff --git a/docshell/base/SerializedLoadContext.cpp b/docshell/base/SerializedLoadContext.cpp new file mode 100644 index 000000000..b8e3eb929 --- /dev/null +++ b/docshell/base/SerializedLoadContext.cpp @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SerializedLoadContext.h" +#include "nsNetUtil.h" +#include "nsIChannel.h" +#include "nsIPrivateBrowsingChannel.h" +#include "nsIWebSocketChannel.h" + +namespace IPC { + +SerializedLoadContext::SerializedLoadContext(nsILoadContext* aLoadContext) +{ + Init(aLoadContext); +} + +SerializedLoadContext::SerializedLoadContext(nsIChannel* aChannel) +{ + if (!aChannel) { + Init(nullptr); + return; + } + + nsCOMPtr<nsILoadContext> loadContext; + NS_QueryNotificationCallbacks(aChannel, loadContext); + Init(loadContext); + + if (!loadContext) { + // Attempt to retrieve the private bit from the channel if it has been + // overriden. + bool isPrivate = false; + bool isOverriden = false; + nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel); + if (pbChannel && + NS_SUCCEEDED(pbChannel->IsPrivateModeOverriden(&isPrivate, + &isOverriden)) && + isOverriden) { + mIsPrivateBitValid = true; + } + mOriginAttributes.SyncAttributesWithPrivateBrowsing(isPrivate); + } +} + +SerializedLoadContext::SerializedLoadContext(nsIWebSocketChannel* aChannel) +{ + nsCOMPtr<nsILoadContext> loadContext; + if (aChannel) { + NS_QueryNotificationCallbacks(aChannel, loadContext); + } + Init(loadContext); +} + +void +SerializedLoadContext::Init(nsILoadContext* aLoadContext) +{ + if (aLoadContext) { + mIsNotNull = true; + mIsPrivateBitValid = true; + aLoadContext->GetIsContent(&mIsContent); + aLoadContext->GetUseRemoteTabs(&mUseRemoteTabs); + if (!aLoadContext->GetOriginAttributes(mOriginAttributes)) { + NS_WARNING("GetOriginAttributes failed"); + } + } else { + mIsNotNull = false; + mIsPrivateBitValid = false; + // none of below values really matter when mIsNotNull == false: + // we won't be GetInterfaced to nsILoadContext + mIsContent = true; + mUseRemoteTabs = false; + } +} + +} // namespace IPC diff --git a/docshell/base/SerializedLoadContext.h b/docshell/base/SerializedLoadContext.h new file mode 100644 index 000000000..c81b39853 --- /dev/null +++ b/docshell/base/SerializedLoadContext.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef SerializedLoadContext_h +#define SerializedLoadContext_h + +#include "base/basictypes.h" +#include "ipc/IPCMessageUtils.h" +#include "mozilla/BasePrincipal.h" + +class nsILoadContext; + +/* + * This file contains the IPC::SerializedLoadContext class, which is used to + * copy data across IPDL from Child process contexts so it is available in the + * Parent. + */ + +class nsIChannel; +class nsIWebSocketChannel; + +namespace IPC { + +class SerializedLoadContext +{ +public: + SerializedLoadContext() + : mIsNotNull(false) + , mIsPrivateBitValid(false) + , mIsContent(false) + , mUseRemoteTabs(false) + { + Init(nullptr); + } + + explicit SerializedLoadContext(nsILoadContext* aLoadContext); + explicit SerializedLoadContext(nsIChannel* aChannel); + explicit SerializedLoadContext(nsIWebSocketChannel* aChannel); + + void Init(nsILoadContext* aLoadContext); + + bool IsNotNull() const { return mIsNotNull; } + bool IsPrivateBitValid() const { return mIsPrivateBitValid; } + + // used to indicate if child-side LoadContext * was null. + bool mIsNotNull; + // used to indicate if child-side mUsePrivateBrowsing flag is valid, even if + // mIsNotNull is false, i.e., child LoadContext was null. + bool mIsPrivateBitValid; + bool mIsContent; + bool mUseRemoteTabs; + mozilla::DocShellOriginAttributes mOriginAttributes; +}; + +// Function to serialize over IPDL +template<> +struct ParamTraits<SerializedLoadContext> +{ + typedef SerializedLoadContext paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + nsAutoCString suffix; + aParam.mOriginAttributes.CreateSuffix(suffix); + + WriteParam(aMsg, aParam.mIsNotNull); + WriteParam(aMsg, aParam.mIsContent); + WriteParam(aMsg, aParam.mIsPrivateBitValid); + WriteParam(aMsg, aParam.mUseRemoteTabs); + WriteParam(aMsg, suffix); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) + { + nsAutoCString suffix; + if (!ReadParam(aMsg, aIter, &aResult->mIsNotNull) || + !ReadParam(aMsg, aIter, &aResult->mIsContent) || + !ReadParam(aMsg, aIter, &aResult->mIsPrivateBitValid) || + !ReadParam(aMsg, aIter, &aResult->mUseRemoteTabs) || + !ReadParam(aMsg, aIter, &suffix)) { + return false; + } + return aResult->mOriginAttributes.PopulateFromSuffix(suffix); + } +}; + +} // namespace IPC + +#endif // SerializedLoadContext_h diff --git a/docshell/base/crashtests/1257730-1.html b/docshell/base/crashtests/1257730-1.html new file mode 100644 index 000000000..028a1adb8 --- /dev/null +++ b/docshell/base/crashtests/1257730-1.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<!-- +user_pref("browser.send_pings", true); +--> +<script> + +function boom() { + var aLink = document.createElement('a'); + document.body.appendChild(aLink); + aLink.ping = "ping"; + aLink.href = "href"; + aLink.click(); + + var baseElement = document.createElement('base'); + baseElement.setAttribute("href", "javascript:void 0"); + document.head.appendChild(baseElement); +} + +</script> +</head> +<body onload="boom();"></body> +</html> diff --git a/docshell/base/crashtests/1331295.html b/docshell/base/crashtests/1331295.html new file mode 100644 index 000000000..cdcb29e7f --- /dev/null +++ b/docshell/base/crashtests/1331295.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<script> +function boom() { + setTimeout(function(){ + var o=document.getElementById('b'); + document.getElementById('a').appendChild(o.parentNode.removeChild(o)); + },0); + var o=document.getElementById('c'); + var p=document.getElementById('b'); + p.id=[o.id, o.id=p.id][0]; + o=document.getElementById('b'); + o.setAttribute('sandbox', 'disc'); + window.location.reload(true); +} +</script> +</head> +<body onload="boom();"> +<header id='a'></header> +<output id='b'></output> +<iframe id='c' sandbox='allow-same-origin' src='http://a'></iframe> +</body> +</html> diff --git a/docshell/base/crashtests/1341657.html b/docshell/base/crashtests/1341657.html new file mode 100644 index 000000000..852b8cc80 --- /dev/null +++ b/docshell/base/crashtests/1341657.html @@ -0,0 +1,14 @@ +<html> + <head> + <script> + o1 = document.createElement("script"); + o2 = document.implementation.createDocument('', '', null); + o3 = document.createElement("iframe"); + document.documentElement.appendChild(o3); + o4 = o3.contentWindow; + o5 = document.createTextNode('o2.adoptNode(o3); try { o4.location = "" } catch(e) {}'); + o1.appendChild(o5); + document.documentElement.appendChild(o1); + </script> + </head> +</html>
\ No newline at end of file diff --git a/docshell/base/crashtests/369126-1.html b/docshell/base/crashtests/369126-1.html new file mode 100644 index 000000000..e9dacec30 --- /dev/null +++ b/docshell/base/crashtests/369126-1.html @@ -0,0 +1,16 @@ +<html class="reftest-wait">
+<head>
+<script>
+function boom()
+{
+ document.getElementById("frameset").removeChild(document.getElementById("frame"));
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<frameset id="frameset" onload="setTimeout(boom, 100)">
+ <frame id="frame" src="data:text/html,<body onUnload="location = 'http://www.mozilla.org/'">This frame's onunload tries to load another page.">
+</frameset>
+
+</html>
diff --git a/docshell/base/crashtests/403574-1.xhtml b/docshell/base/crashtests/403574-1.xhtml new file mode 100644 index 000000000..cdf7d43a4 --- /dev/null +++ b/docshell/base/crashtests/403574-1.xhtml @@ -0,0 +1,23 @@ +<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait"> +<head> + +<bindings xmlns="http://www.mozilla.org/xbl"><binding id="foo"><content> +<frame xmlns="http://www.w3.org/1999/xhtml"><children xmlns="http://www.mozilla.org/xbl"/></frame> +</content></binding></bindings> + +<script> + +function boom() +{ + document.getElementById("span").style.MozBinding = "url('#foo')"; + document.documentElement.removeAttribute("class"); +} + +</script> +</head> + +<body onload="setTimeout(boom, 100);"> +<span id="span"></span> +</body> + +</html> diff --git a/docshell/base/crashtests/40929-1-inner.html b/docshell/base/crashtests/40929-1-inner.html new file mode 100644 index 000000000..313046a34 --- /dev/null +++ b/docshell/base/crashtests/40929-1-inner.html @@ -0,0 +1,14 @@ +<html><head><title>Infinite Loop</title></head> +<body onLoad="initNav(); initNav();"> + +<script language="JavaScript"> + +function initNav() { + ++parent.i; + if (parent.i < 10) + window.location.href=window.location.href; +} + +</script> + +</body></html> diff --git a/docshell/base/crashtests/40929-1.html b/docshell/base/crashtests/40929-1.html new file mode 100644 index 000000000..90685d9f1 --- /dev/null +++ b/docshell/base/crashtests/40929-1.html @@ -0,0 +1,6 @@ +<html> +<head><title>Infinite Loop</title><script>var i=0;</script></head> +<body> +<iframe src="40929-1-inner.html"></iframe> +</body> +</html> diff --git a/docshell/base/crashtests/430124-1.html b/docshell/base/crashtests/430124-1.html new file mode 100644 index 000000000..8cdbc1d07 --- /dev/null +++ b/docshell/base/crashtests/430124-1.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<html> +<head></head> +<body onpagehide="document.getElementById('a').focus();"><div id="a"></div></body> +</html> diff --git a/docshell/base/crashtests/430628-1.html b/docshell/base/crashtests/430628-1.html new file mode 100644 index 000000000..4a68a5a01 --- /dev/null +++ b/docshell/base/crashtests/430628-1.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> +<head> +</head> +<body onpagehide="document.body.removeChild(document.getElementById('s'));"> +<span id="s" contenteditable="true"></span> +</body> +</html> diff --git a/docshell/base/crashtests/432114-1.html b/docshell/base/crashtests/432114-1.html new file mode 100644 index 000000000..8878d6605 --- /dev/null +++ b/docshell/base/crashtests/432114-1.html @@ -0,0 +1,8 @@ +<html>
+<head>
+<title>Bug - Crash [@ PL_DHashTableOperate] with DOMNodeInserted event listener removing window and frameset contenteditable</title>
+</head>
+<body>
+<iframe id="content" src="data:text/html;charset=utf-8,%3Cscript%3E%0Awindow.addEventListener%28%27DOMNodeInserted%27%2C%20function%28%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%3B%7D%2C%20true%29%3B%0A%3C/script%3E%0A%3Cframeset%20contenteditable%3D%22true%22%3E"></iframe>
+</body>
+</html>
diff --git a/docshell/base/crashtests/432114-2.html b/docshell/base/crashtests/432114-2.html new file mode 100644 index 000000000..f8a101946 --- /dev/null +++ b/docshell/base/crashtests/432114-2.html @@ -0,0 +1,16 @@ +<html class="reftest-wait">
+<head>
+<title>testcase2 Bug 432114 Crash [@ PL_DHashTableOperate] with DOMNodeInserted event listener removing window and frameset contenteditable</title>
+</head>
+<body>
+<script>
+ window.addEventListener("DOMNodeRemoved", function() {
+ setTimeout(function() {
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }, false);
+</script>
+<iframe id="content" src="data:application/xhtml+xml;charset=utf-8,%3Chtml%20xmlns%3D%22http%3A//www.w3.org/1999/xhtml%22%3E%0A%3Cframeset%20contenteditable%3D%22true%22/%3E%0A%3Cscript%3E%0Afunction%20doExecCommand%28%29%7B%0Adocument.execCommand%28%27formatBlock%27%2C%20false%2C%20%27p%27%29%3B%0A%7D%0AsetTimeout%28doExecCommand%2C100%29%3B%0Awindow.addEventListener%28%27DOMNodeRemoved%27%2C%20function%28%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%3B%7D%2C%20true%29%3B%0A%3C/script%3E%0A%3C/html%3E" style="width:1000px;height: 200px;"></iframe>
+
+</body>
+</html>
diff --git a/docshell/base/crashtests/436900-1-inner.html b/docshell/base/crashtests/436900-1-inner.html new file mode 100644 index 000000000..6fe35ccb1 --- /dev/null +++ b/docshell/base/crashtests/436900-1-inner.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> +<head> + +<meta http-equiv="refresh" content="0"> + +<script language="javascript"> + +location.hash += "+++"; + +function done() +{ + parent.document.documentElement.removeAttribute("class"); +} + +</script> +</head> +<body onload="setTimeout(done, 10)"> + +</body> +</html> diff --git a/docshell/base/crashtests/436900-1.html b/docshell/base/crashtests/436900-1.html new file mode 100644 index 000000000..582d1919d --- /dev/null +++ b/docshell/base/crashtests/436900-1.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +</head> +<body> +<iframe src="436900-1-inner.html#foo"></iframe> +</body> +</html> diff --git a/docshell/base/crashtests/436900-2-inner.html b/docshell/base/crashtests/436900-2-inner.html new file mode 100644 index 000000000..ea79f75e8 --- /dev/null +++ b/docshell/base/crashtests/436900-2-inner.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> +<head> + +<meta http-equiv="refresh" content="0"> + +<script language="javascript" id="foo+++"> + +location.hash += "+++"; + +function done() +{ + parent.document.documentElement.removeAttribute("class"); +} + +</script> +</head> +<body onload="setTimeout(done, 10)"> + +</body> +</html> diff --git a/docshell/base/crashtests/436900-2.html b/docshell/base/crashtests/436900-2.html new file mode 100644 index 000000000..2e1f0c1de --- /dev/null +++ b/docshell/base/crashtests/436900-2.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +</head> +<body> +<iframe src="436900-2-inner.html#foo"></iframe> +</body> +</html> diff --git a/docshell/base/crashtests/500328-1.html b/docshell/base/crashtests/500328-1.html new file mode 100644 index 000000000..fd97f84ae --- /dev/null +++ b/docshell/base/crashtests/500328-1.html @@ -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> diff --git a/docshell/base/crashtests/514779-1.xhtml b/docshell/base/crashtests/514779-1.xhtml new file mode 100644 index 000000000..16ac3d9d6 --- /dev/null +++ b/docshell/base/crashtests/514779-1.xhtml @@ -0,0 +1,9 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head></head> + +<body onunload="document.getElementById('tbody').appendChild(document.createElementNS('http://www.w3.org/1999/xhtml', 'span'))"> + <iframe/> + <tbody contenteditable="true" id="tbody">xy</tbody> +</body> + +</html> diff --git a/docshell/base/crashtests/614499-1.html b/docshell/base/crashtests/614499-1.html new file mode 100644 index 000000000..7053a3f52 --- /dev/null +++ b/docshell/base/crashtests/614499-1.html @@ -0,0 +1,20 @@ +<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var f = document.getElementById("f");
+
+ for (var i = 0; i < 50; ++i) {
+ f.contentWindow.history.pushState({}, "");
+ }
+
+ document.body.removeChild(f);
+}
+
+</script>
+</head>
+<body onload="boom();"><iframe id="f" src="data:text/html,1"></iframe></body>
+</html>
\ No newline at end of file diff --git a/docshell/base/crashtests/678872-1.html b/docshell/base/crashtests/678872-1.html new file mode 100644 index 000000000..9853bbdae --- /dev/null +++ b/docshell/base/crashtests/678872-1.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html> +<head> +<script> + +var f1, f2; + +function b1() +{ + f1 = document.getElementById("f1"); + f2 = document.getElementById("f2"); + f1.contentWindow.document.write("11"); + f1.contentWindow.history.back(); + setTimeout(b2, 0); +} + +function b2() +{ + f2.contentWindow.history.forward(); + f2.contentWindow.location.reload(); + f1.parentNode.removeChild(f1); +} + +</script> + + +</script> +</head> + +<body onload="setTimeout(b1, 0);"> + +<iframe id="f1" src="data:text/html,1"></iframe> +<iframe id="f2" src="data:text/html,2"></iframe> + +</body> +</html> diff --git a/docshell/base/crashtests/914521.html b/docshell/base/crashtests/914521.html new file mode 100644 index 000000000..9ae18b860 --- /dev/null +++ b/docshell/base/crashtests/914521.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<meta charset="UTF-8"> +<script> + +function f() +{ + function spin() { + for (var i = 0; i < 8; ++i) { + var x = new XMLHttpRequest(); + x.open('GET', 'data:text/html,' + i, false); + x.send(); + } + } + + window.addEventListener("popstate", spin, false); + window.close(); + window.location = "#c"; + finish(); +} + +function start() +{ + var html = "<script>" + f + "<\/script><body onload=f()>"; + var win = window.open("data:text/html," + encodeURIComponent(html), null, "width=300,height=300"); + win.finish = function() { document.documentElement.removeAttribute("class"); }; +} + +</script> +</head> +<body onload="start();"></body> +</html> diff --git a/docshell/base/crashtests/crashtests.list b/docshell/base/crashtests/crashtests.list new file mode 100644 index 000000000..bb3a709b9 --- /dev/null +++ b/docshell/base/crashtests/crashtests.list @@ -0,0 +1,16 @@ +load 40929-1.html +load 369126-1.html +load 403574-1.xhtml +load 430124-1.html +load 430628-1.html +load 432114-1.html +load 432114-2.html +load 436900-1.html +asserts(0-1) load 436900-2.html # bug 566159 +load 500328-1.html +load 514779-1.xhtml +load 614499-1.html +load 678872-1.html +skip-if(Android) pref(dom.disable_open_during_load,false) load 914521.html +pref(browser.send_pings,true) load 1257730-1.html +load 1341657.html diff --git a/docshell/base/moz.build b/docshell/base/moz.build new file mode 100644 index 000000000..1eb04c227 --- /dev/null +++ b/docshell/base/moz.build @@ -0,0 +1,88 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += [ + 'timeline', +] + +XPIDL_SOURCES += [ + 'nsCDefaultURIFixup.idl', + 'nsIClipboardCommands.idl', + 'nsIContentViewer.idl', + 'nsIContentViewerContainer.idl', + 'nsIContentViewerEdit.idl', + 'nsIContentViewerFile.idl', + 'nsIDocCharset.idl', + 'nsIDocShell.idl', + 'nsIDocShellLoadInfo.idl', + 'nsIDocShellTreeItem.idl', + 'nsIDocShellTreeOwner.idl', + 'nsIDocumentLoaderFactory.idl', + 'nsIDownloadHistory.idl', + 'nsIGlobalHistory2.idl', + 'nsILoadContext.idl', + 'nsIPrivacyTransitionObserver.idl', + 'nsIReflowObserver.idl', + 'nsIRefreshURI.idl', + 'nsIScrollable.idl', + 'nsITextScroll.idl', + 'nsIURIFixup.idl', + 'nsIWebNavigation.idl', + 'nsIWebNavigationInfo.idl', + 'nsIWebPageDescriptor.idl', +] + +XPIDL_MODULE = 'docshell' + +EXPORTS += [ + 'nsDocShellLoadTypes.h', + 'nsILinkHandler.h', + 'nsIScrollObserver.h', + 'nsIWebShellServices.h', + 'SerializedLoadContext.h', +] + +EXPORTS.mozilla += [ + 'IHistory.h', + 'LoadContext.h', +] + +UNIFIED_SOURCES += [ + 'LoadContext.cpp', + 'nsAboutRedirector.cpp', + 'nsDefaultURIFixup.cpp', + 'nsDocShell.cpp', + 'nsDocShellEditorData.cpp', + 'nsDocShellEnumerator.cpp', + 'nsDocShellLoadInfo.cpp', + 'nsDocShellTransferableHooks.cpp', + 'nsDownloadHistory.cpp', + 'nsDSURIContentListener.cpp', + 'nsWebNavigationInfo.cpp', + 'SerializedLoadContext.cpp', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '/docshell/shistory', + '/dom/base', + '/layout/base', + '/layout/generic', + '/layout/xul', + '/netwerk/protocol/viewsource', + '/tools/profiler', +] + +if CONFIG['MOZ_TOOLKIT_SEARCH']: + DEFINES['MOZ_TOOLKIT_SEARCH'] = True + +if CONFIG['MOZ_DEVTOOLS'] == 'all': + DEFINES['MOZ_DEVTOOLS_ALL'] = True + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/docshell/base/nsAboutRedirector.cpp b/docshell/base/nsAboutRedirector.cpp new file mode 100644 index 000000000..f300e0ce2 --- /dev/null +++ b/docshell/base/nsAboutRedirector.cpp @@ -0,0 +1,224 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAboutRedirector.h" +#include "nsNetUtil.h" +#include "nsAboutProtocolUtils.h" +#include "mozilla/ArrayUtils.h" +#include "nsIProtocolHandler.h" + +NS_IMPL_ISUPPORTS(nsAboutRedirector, nsIAboutModule) + +struct RedirEntry +{ + const char* id; + const char* url; + uint32_t flags; +}; + +/* + Entries which do not have URI_SAFE_FOR_UNTRUSTED_CONTENT will run with chrome + privileges. This is potentially dangerous. Please use + URI_SAFE_FOR_UNTRUSTED_CONTENT in the third argument to each map item below + unless your about: page really needs chrome privileges. Security review is + required before adding new map entries without + URI_SAFE_FOR_UNTRUSTED_CONTENT. Also note, however, that adding + URI_SAFE_FOR_UNTRUSTED_CONTENT will allow random web sites to link to that + URI. Perhaps we should separate the two concepts out... + */ +static RedirEntry kRedirMap[] = { + { + "", "chrome://global/content/about.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, + { "about", "chrome://global/content/aboutAbout.xhtml", 0 }, + { + "addons", "chrome://mozapps/content/extensions/extensions.xul", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "buildconfig", "chrome://global/content/buildconfig.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT + }, + { + "checkerboard", "chrome://global/content/aboutCheckerboard.xhtml", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::ALLOW_SCRIPT + }, + { "config", "chrome://global/content/config.xul", 0 }, +#ifdef MOZ_CRASHREPORTER + { "crashes", "chrome://global/content/crashes.xhtml", 0 }, +#endif + { + "credits", "https://www.mozilla.org/credits/", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT + }, +#ifdef MOZ_DEVTOOLS_ALL + { + "debugging", "chrome://devtools/content/aboutdebugging/aboutdebugging.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, +#endif + { + "license", "chrome://global/content/license.html", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::MAKE_LINKABLE + }, + { + "logo", "chrome://branding/content/about.png", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + // Linkable for testing reasons. + nsIAboutModule::MAKE_LINKABLE + }, + { + "memory", "chrome://global/content/aboutMemory.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "mozilla", "chrome://global/content/mozilla.xhtml", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT + }, + { + "neterror", "chrome://global/content/netError.xhtml", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::URI_CAN_LOAD_IN_CHILD | + nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT + }, + { + "networking", "chrome://global/content/aboutNetworking.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "newaddon", "chrome://mozapps/content/extensions/newaddon.xul", + nsIAboutModule::ALLOW_SCRIPT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT + }, + { + "performance", "chrome://global/content/aboutPerformance.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "plugins", "chrome://global/content/plugins.html", + nsIAboutModule::URI_MUST_LOAD_IN_CHILD + }, + { + "serviceworkers", "chrome://global/content/aboutServiceWorkers.xhtml", + nsIAboutModule::URI_CAN_LOAD_IN_CHILD | + nsIAboutModule::URI_MUST_LOAD_IN_CHILD | + nsIAboutModule::ALLOW_SCRIPT + }, +#ifndef ANDROID + { + "profiles", "chrome://global/content/aboutProfiles.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, +#endif + // about:srcdoc is unresolvable by specification. It is included here + // because the security manager would disallow srcdoc iframes otherwise. + { + "srcdoc", "about:blank", + nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | + nsIAboutModule::HIDE_FROM_ABOUTABOUT | + // Needs to be linkable so content can touch its own srcdoc frames + nsIAboutModule::MAKE_LINKABLE | + nsIAboutModule::URI_CAN_LOAD_IN_CHILD + }, + { + "support", "chrome://global/content/aboutSupport.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "telemetry", "chrome://global/content/aboutTelemetry.xhtml", + nsIAboutModule::ALLOW_SCRIPT + }, + { + "webrtc", "chrome://global/content/aboutwebrtc/aboutWebrtc.html", + nsIAboutModule::ALLOW_SCRIPT + } +}; +static const int kRedirTotal = mozilla::ArrayLength(kRedirMap); + +NS_IMETHODIMP +nsAboutRedirector::NewChannel(nsIURI* aURI, + nsILoadInfo* aLoadInfo, + nsIChannel** aResult) +{ + NS_ENSURE_ARG_POINTER(aURI); + NS_ASSERTION(aResult, "must not be null"); + + nsAutoCString path; + nsresult rv = NS_GetAboutModuleName(aURI, path); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv); + NS_ENSURE_SUCCESS(rv, rv); + + for (int i = 0; i < kRedirTotal; i++) { + if (!strcmp(path.get(), kRedirMap[i].id)) { + nsCOMPtr<nsIChannel> tempChannel; + nsCOMPtr<nsIURI> tempURI; + rv = NS_NewURI(getter_AddRefs(tempURI), kRedirMap[i].url); + NS_ENSURE_SUCCESS(rv, rv); + + // If tempURI links to an external URI (i.e. something other than + // chrome:// or resource://) then set the LOAD_REPLACE flag on the + // channel which forces the channel owner to reflect the displayed + // URL rather then being the systemPrincipal. + bool isUIResource = false; + rv = NS_URIChainHasFlags(tempURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, + &isUIResource); + NS_ENSURE_SUCCESS(rv, rv); + + nsLoadFlags loadFlags = + isUIResource ? static_cast<nsLoadFlags>(nsIChannel::LOAD_NORMAL) + : static_cast<nsLoadFlags>(nsIChannel::LOAD_REPLACE); + + rv = NS_NewChannelInternal(getter_AddRefs(tempChannel), + tempURI, + aLoadInfo, + nullptr, // aLoadGroup + nullptr, // aCallbacks + loadFlags); + NS_ENSURE_SUCCESS(rv, rv); + + tempChannel->SetOriginalURI(aURI); + + tempChannel.forget(aResult); + return rv; + } + } + + NS_ERROR("nsAboutRedirector called for unknown case"); + return NS_ERROR_ILLEGAL_VALUE; +} + +NS_IMETHODIMP +nsAboutRedirector::GetURIFlags(nsIURI* aURI, uint32_t* aResult) +{ + NS_ENSURE_ARG_POINTER(aURI); + + nsAutoCString name; + nsresult rv = NS_GetAboutModuleName(aURI, name); + NS_ENSURE_SUCCESS(rv, rv); + + for (int i = 0; i < kRedirTotal; i++) { + if (name.EqualsASCII(kRedirMap[i].id)) { + *aResult = kRedirMap[i].flags; + return NS_OK; + } + } + + NS_ERROR("nsAboutRedirector called for unknown case"); + return NS_ERROR_ILLEGAL_VALUE; +} + +nsresult +nsAboutRedirector::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult) +{ + RefPtr<nsAboutRedirector> about = new nsAboutRedirector(); + return about->QueryInterface(aIID, aResult); +} diff --git a/docshell/base/nsAboutRedirector.h b/docshell/base/nsAboutRedirector.h new file mode 100644 index 000000000..f8e6b2558 --- /dev/null +++ b/docshell/base/nsAboutRedirector.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAboutRedirector_h__ +#define nsAboutRedirector_h__ + +#include "nsIAboutModule.h" + +class nsAboutRedirector : public nsIAboutModule +{ +public: + NS_DECL_ISUPPORTS + + NS_DECL_NSIABOUTMODULE + + nsAboutRedirector() {} + + static nsresult Create(nsISupports* aOuter, REFNSIID aIID, void** aResult); + +protected: + virtual ~nsAboutRedirector() {} +}; + +/* 56ebedd4-6ccf-48e8-bdae-adc77f044567 */ +#define NS_ABOUT_REDIRECTOR_MODULE_CID \ +{ 0x56ebedd4, 0x6ccf, 0x48e8, \ + { 0xbd, 0xae, 0xad, 0xc7, 0x7f, 0x04, 0x45, 0x67 } } + +#endif // nsAboutRedirector_h__ diff --git a/docshell/base/nsCDefaultURIFixup.idl b/docshell/base/nsCDefaultURIFixup.idl new file mode 100644 index 000000000..60fffdf21 --- /dev/null +++ b/docshell/base/nsCDefaultURIFixup.idl @@ -0,0 +1,13 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +%{ C++ +// {214C48A0-B57F-11d4-959C-0020183BF181} +#define NS_DEFAULTURIFIXUP_CID \ +{ 0x214c48a0, 0xb57f, 0x11d4, { 0x95, 0x9c, 0x0, 0x20, 0x18, 0x3b, 0xf1, 0x81 } } +#define NS_URIFIXUP_CONTRACTID \ +"@mozilla.org/docshell/urifixup;1" +%} diff --git a/docshell/base/nsDSURIContentListener.cpp b/docshell/base/nsDSURIContentListener.cpp new file mode 100644 index 000000000..cfac54f7f --- /dev/null +++ b/docshell/base/nsDSURIContentListener.cpp @@ -0,0 +1,539 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDocShell.h" +#include "nsDSURIContentListener.h" +#include "nsIChannel.h" +#include "nsServiceManagerUtils.h" +#include "nsDocShellCID.h" +#include "nsIWebNavigationInfo.h" +#include "nsIDocument.h" +#include "nsIDOMWindow.h" +#include "nsNetUtil.h" +#include "nsQueryObject.h" +#include "nsIHttpChannel.h" +#include "nsIScriptSecurityManager.h" +#include "nsError.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsIConsoleService.h" +#include "nsIScriptError.h" +#include "nsDocShellLoadTypes.h" +#include "nsIMultiPartChannel.h" + +using namespace mozilla; + +nsDSURIContentListener::nsDSURIContentListener(nsDocShell* aDocShell) + : mDocShell(aDocShell) + , mExistingJPEGRequest(nullptr) + , mParentContentListener(nullptr) +{ +} + +nsDSURIContentListener::~nsDSURIContentListener() +{ +} + +nsresult +nsDSURIContentListener::Init() +{ + nsresult rv; + mNavInfo = do_GetService(NS_WEBNAVIGATION_INFO_CONTRACTID, &rv); + NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to get webnav info"); + return rv; +} + +NS_IMPL_ADDREF(nsDSURIContentListener) +NS_IMPL_RELEASE(nsDSURIContentListener) + +NS_INTERFACE_MAP_BEGIN(nsDSURIContentListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURIContentListener) + NS_INTERFACE_MAP_ENTRY(nsIURIContentListener) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +nsDSURIContentListener::OnStartURIOpen(nsIURI* aURI, bool* aAbortOpen) +{ + // If mDocShell is null here, that means someone's starting a load in our + // docshell after it's already been destroyed. Don't let that happen. + if (!mDocShell) { + *aAbortOpen = true; + return NS_OK; + } + + nsCOMPtr<nsIURIContentListener> parentListener; + GetParentContentListener(getter_AddRefs(parentListener)); + if (parentListener) { + return parentListener->OnStartURIOpen(aURI, aAbortOpen); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::DoContent(const nsACString& aContentType, + bool aIsContentPreferred, + nsIRequest* aRequest, + nsIStreamListener** aContentHandler, + bool* aAbortProcess) +{ + nsresult rv; + NS_ENSURE_ARG_POINTER(aContentHandler); + NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE); + + // Check whether X-Frame-Options permits us to load this content in an + // iframe and abort the load (unless we've disabled x-frame-options + // checking). + if (!CheckFrameOptions(aRequest)) { + *aAbortProcess = true; + return NS_OK; + } + + *aAbortProcess = false; + + // determine if the channel has just been retargeted to us... + nsLoadFlags loadFlags = 0; + nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest); + + if (aOpenedChannel) { + aOpenedChannel->GetLoadFlags(&loadFlags); + } + + if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) { + // XXX: Why does this not stop the content too? + mDocShell->Stop(nsIWebNavigation::STOP_NETWORK); + + mDocShell->SetLoadType(aIsContentPreferred ? LOAD_LINK : LOAD_NORMAL); + } + + // In case of multipart jpeg request (mjpeg) we don't really want to + // create new viewer since the one we already have is capable of + // rendering multipart jpeg correctly (see bug 625012) + nsCOMPtr<nsIChannel> baseChannel; + if (nsCOMPtr<nsIMultiPartChannel> mpchan = do_QueryInterface(aRequest)) { + mpchan->GetBaseChannel(getter_AddRefs(baseChannel)); + } + + bool reuseCV = baseChannel && baseChannel == mExistingJPEGRequest && + aContentType.EqualsLiteral("image/jpeg"); + + if (mExistingJPEGStreamListener && reuseCV) { + RefPtr<nsIStreamListener> copy(mExistingJPEGStreamListener); + copy.forget(aContentHandler); + rv = NS_OK; + } else { + rv = mDocShell->CreateContentViewer(aContentType, aRequest, aContentHandler); + if (NS_SUCCEEDED(rv) && reuseCV) { + mExistingJPEGStreamListener = *aContentHandler; + } else { + mExistingJPEGStreamListener = nullptr; + } + mExistingJPEGRequest = baseChannel; + } + + if (rv == NS_ERROR_REMOTE_XUL) { + aRequest->Cancel(rv); + *aAbortProcess = true; + return NS_OK; + } + + if (NS_FAILED(rv)) { + // we don't know how to handle the content + *aContentHandler = nullptr; + return rv; + } + + if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) { + nsCOMPtr<nsPIDOMWindowOuter> domWindow = + mDocShell ? mDocShell->GetWindow() : nullptr; + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); + domWindow->Focus(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::IsPreferred(const char* aContentType, + char** aDesiredContentType, + bool* aCanHandle) +{ + NS_ENSURE_ARG_POINTER(aCanHandle); + NS_ENSURE_ARG_POINTER(aDesiredContentType); + + // the docshell has no idea if it is the preferred content provider or not. + // It needs to ask its parent if it is the preferred content handler or not... + + nsCOMPtr<nsIURIContentListener> parentListener; + GetParentContentListener(getter_AddRefs(parentListener)); + if (parentListener) { + return parentListener->IsPreferred(aContentType, + aDesiredContentType, + aCanHandle); + } + // we used to return false here if we didn't have a parent properly registered + // at the top of the docshell hierarchy to dictate what content types this + // docshell should be a preferred handler for. But this really makes it hard + // for developers using iframe or browser tags because then they need to make + // sure they implement nsIURIContentListener otherwise all link clicks would + // get sent to another window because we said we weren't the preferred handler + // type. I'm going to change the default now... if we can handle the content, + // and someone didn't EXPLICITLY set a nsIURIContentListener at the top of our + // docshell chain, then we'll now always attempt to process the content + // ourselves... + return CanHandleContent(aContentType, true, aDesiredContentType, aCanHandle); +} + +NS_IMETHODIMP +nsDSURIContentListener::CanHandleContent(const char* aContentType, + bool aIsContentPreferred, + char** aDesiredContentType, + bool* aCanHandleContent) +{ + NS_PRECONDITION(aCanHandleContent, "Null out param?"); + NS_ENSURE_ARG_POINTER(aDesiredContentType); + + *aCanHandleContent = false; + *aDesiredContentType = nullptr; + + nsresult rv = NS_OK; + if (aContentType) { + uint32_t canHandle = nsIWebNavigationInfo::UNSUPPORTED; + rv = mNavInfo->IsTypeSupported(nsDependentCString(aContentType), + mDocShell, + &canHandle); + *aCanHandleContent = (canHandle != nsIWebNavigationInfo::UNSUPPORTED); + } + + return rv; +} + +NS_IMETHODIMP +nsDSURIContentListener::GetLoadCookie(nsISupports** aLoadCookie) +{ + NS_IF_ADDREF(*aLoadCookie = nsDocShell::GetAsSupports(mDocShell)); + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::SetLoadCookie(nsISupports* aLoadCookie) +{ +#ifdef DEBUG + RefPtr<nsDocLoader> cookieAsDocLoader = + nsDocLoader::GetAsDocLoader(aLoadCookie); + NS_ASSERTION(cookieAsDocLoader && cookieAsDocLoader == mDocShell, + "Invalid load cookie being set!"); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::GetParentContentListener( + nsIURIContentListener** aParentListener) +{ + if (mWeakParentContentListener) { + nsCOMPtr<nsIURIContentListener> tempListener = + do_QueryReferent(mWeakParentContentListener); + *aParentListener = tempListener; + NS_IF_ADDREF(*aParentListener); + } else { + *aParentListener = mParentContentListener; + NS_IF_ADDREF(*aParentListener); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDSURIContentListener::SetParentContentListener( + nsIURIContentListener* aParentListener) +{ + if (aParentListener) { + // Store the parent listener as a weak ref. Parents not supporting + // nsISupportsWeakReference assert but may still be used. + mParentContentListener = nullptr; + mWeakParentContentListener = do_GetWeakReference(aParentListener); + if (!mWeakParentContentListener) { + mParentContentListener = aParentListener; + } + } else { + mWeakParentContentListener = nullptr; + mParentContentListener = nullptr; + } + return NS_OK; +} + +bool +nsDSURIContentListener::CheckOneFrameOptionsPolicy(nsIHttpChannel* aHttpChannel, + const nsAString& aPolicy) +{ + static const char allowFrom[] = "allow-from"; + const uint32_t allowFromLen = ArrayLength(allowFrom) - 1; + bool isAllowFrom = + StringHead(aPolicy, allowFromLen).LowerCaseEqualsLiteral(allowFrom); + + // return early if header does not have one of the values with meaning + if (!aPolicy.LowerCaseEqualsLiteral("deny") && + !aPolicy.LowerCaseEqualsLiteral("sameorigin") && + !isAllowFrom) { + return true; + } + + nsCOMPtr<nsIURI> uri; + aHttpChannel->GetURI(getter_AddRefs(uri)); + + // XXXkhuey when does this happen? Is returning true safe here? + if (!mDocShell) { + return true; + } + + // We need to check the location of this window and the location of the top + // window, if we're not the top. X-F-O: SAMEORIGIN requires that the + // document must be same-origin with top window. X-F-O: DENY requires that + // the document must never be framed. + nsCOMPtr<nsPIDOMWindowOuter> thisWindow = mDocShell->GetWindow(); + // If we don't have DOMWindow there is no risk of clickjacking + if (!thisWindow) { + return true; + } + + // GetScriptableTop, not GetTop, because we want this to respect + // <iframe mozbrowser> boundaries. + nsCOMPtr<nsPIDOMWindowOuter> topWindow = thisWindow->GetScriptableTop(); + + // if the document is in the top window, it's not in a frame. + if (thisWindow == topWindow) { + return true; + } + + // Find the top docshell in our parent chain that doesn't have the system + // principal and use it for the principal comparison. Finding the top + // content-type docshell doesn't work because some chrome documents are + // loaded in content docshells (see bug 593387). + nsCOMPtr<nsIDocShellTreeItem> thisDocShellItem( + do_QueryInterface(static_cast<nsIDocShell*>(mDocShell))); + nsCOMPtr<nsIDocShellTreeItem> parentDocShellItem; + nsCOMPtr<nsIDocShellTreeItem> curDocShellItem = thisDocShellItem; + nsCOMPtr<nsIDocument> topDoc; + nsresult rv; + nsCOMPtr<nsIScriptSecurityManager> ssm = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + if (!ssm) { + MOZ_CRASH(); + } + + // Traverse up the parent chain and stop when we see a docshell whose + // parent has a system principal, or a docshell corresponding to + // <iframe mozbrowser>. + while (NS_SUCCEEDED( + curDocShellItem->GetParent(getter_AddRefs(parentDocShellItem))) && + parentDocShellItem) { + nsCOMPtr<nsIDocShell> curDocShell = do_QueryInterface(curDocShellItem); + if (curDocShell && curDocShell->GetIsMozBrowserOrApp()) { + break; + } + + bool system = false; + topDoc = parentDocShellItem->GetDocument(); + if (topDoc) { + if (NS_SUCCEEDED( + ssm->IsSystemPrincipal(topDoc->NodePrincipal(), &system)) && + system) { + // Found a system-principled doc: last docshell was top. + break; + } + } else { + return false; + } + curDocShellItem = parentDocShellItem; + } + + // If this document has the top non-SystemPrincipal docshell it is not being + // framed or it is being framed by a chrome document, which we allow. + if (curDocShellItem == thisDocShellItem) { + return true; + } + + // If the value of the header is DENY, and the previous condition is + // not met (current docshell is not the top docshell), prohibit the + // load. + if (aPolicy.LowerCaseEqualsLiteral("deny")) { + ReportXFOViolation(curDocShellItem, uri, eDENY); + return false; + } + + topDoc = curDocShellItem->GetDocument(); + nsCOMPtr<nsIURI> topUri; + topDoc->NodePrincipal()->GetURI(getter_AddRefs(topUri)); + + // If the X-Frame-Options value is SAMEORIGIN, then the top frame in the + // parent chain must be from the same origin as this document. + if (aPolicy.LowerCaseEqualsLiteral("sameorigin")) { + rv = ssm->CheckSameOriginURI(uri, topUri, true); + if (NS_FAILED(rv)) { + ReportXFOViolation(curDocShellItem, uri, eSAMEORIGIN); + return false; /* wasn't same-origin */ + } + } + + // If the X-Frame-Options value is "allow-from [uri]", then the top + // frame in the parent chain must be from that origin + if (isAllowFrom) { + if (aPolicy.Length() == allowFromLen || + (aPolicy[allowFromLen] != ' ' && + aPolicy[allowFromLen] != '\t')) { + ReportXFOViolation(curDocShellItem, uri, eALLOWFROM); + return false; + } + rv = NS_NewURI(getter_AddRefs(uri), Substring(aPolicy, allowFromLen)); + if (NS_FAILED(rv)) { + return false; + } + + rv = ssm->CheckSameOriginURI(uri, topUri, true); + if (NS_FAILED(rv)) { + ReportXFOViolation(curDocShellItem, uri, eALLOWFROM); + return false; + } + } + + return true; +} + +// Check if X-Frame-Options permits this document to be loaded as a subdocument. +// This will iterate through and check any number of X-Frame-Options policies +// in the request (comma-separated in a header, multiple headers, etc). +bool +nsDSURIContentListener::CheckFrameOptions(nsIRequest* aRequest) +{ + nsresult rv; + nsCOMPtr<nsIChannel> chan = do_QueryInterface(aRequest); + if (!chan) { + return true; + } + + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(chan); + if (!httpChannel) { + // check if it is hiding in a multipart channel + rv = mDocShell->GetHttpChannel(chan, getter_AddRefs(httpChannel)); + if (NS_FAILED(rv)) { + return false; + } + } + + if (!httpChannel) { + return true; + } + + nsAutoCString xfoHeaderCValue; + httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-Frame-Options"), + xfoHeaderCValue); + NS_ConvertUTF8toUTF16 xfoHeaderValue(xfoHeaderCValue); + + // if no header value, there's nothing to do. + if (xfoHeaderValue.IsEmpty()) { + return true; + } + + // iterate through all the header values (usually there's only one, but can + // be many. If any want to deny the load, deny the load. + nsCharSeparatedTokenizer tokenizer(xfoHeaderValue, ','); + while (tokenizer.hasMoreTokens()) { + const nsSubstring& tok = tokenizer.nextToken(); + if (!CheckOneFrameOptionsPolicy(httpChannel, tok)) { + // cancel the load and display about:blank + httpChannel->Cancel(NS_BINDING_ABORTED); + if (mDocShell) { + nsCOMPtr<nsIWebNavigation> webNav(do_QueryObject(mDocShell)); + if (webNav) { + webNav->LoadURI(u"about:blank", + 0, nullptr, nullptr, nullptr); + } + } + return false; + } + } + + return true; +} + +void +nsDSURIContentListener::ReportXFOViolation(nsIDocShellTreeItem* aTopDocShellItem, + nsIURI* aThisURI, + XFOHeader aHeader) +{ + MOZ_ASSERT(aTopDocShellItem, "Need a top docshell"); + + nsCOMPtr<nsPIDOMWindowOuter> topOuterWindow = aTopDocShellItem->GetWindow(); + if (!topOuterWindow) { + return; + } + + nsPIDOMWindowInner* topInnerWindow = topOuterWindow->GetCurrentInnerWindow(); + if (!topInnerWindow) { + return; + } + + nsCOMPtr<nsIURI> topURI; + + nsCOMPtr<nsIDocument> document = aTopDocShellItem->GetDocument(); + nsresult rv = document->NodePrincipal()->GetURI(getter_AddRefs(topURI)); + if (NS_FAILED(rv)) { + return; + } + + if (!topURI) { + return; + } + + nsCString topURIString; + nsCString thisURIString; + + rv = topURI->GetSpec(topURIString); + if (NS_FAILED(rv)) { + return; + } + + rv = aThisURI->GetSpec(thisURIString); + if (NS_FAILED(rv)) { + return; + } + + nsCOMPtr<nsIConsoleService> consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + nsCOMPtr<nsIScriptError> errorObject = + do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); + + if (!consoleService || !errorObject) { + return; + } + + nsString msg = NS_LITERAL_STRING("Load denied by X-Frame-Options: "); + msg.Append(NS_ConvertUTF8toUTF16(thisURIString)); + + switch (aHeader) { + case eDENY: + msg.AppendLiteral(" does not permit framing."); + break; + case eSAMEORIGIN: + msg.AppendLiteral(" does not permit cross-origin framing."); + break; + case eALLOWFROM: + msg.AppendLiteral(" does not permit framing by "); + msg.Append(NS_ConvertUTF8toUTF16(topURIString)); + msg.Append('.'); + break; + } + + rv = errorObject->InitWithWindowID(msg, EmptyString(), EmptyString(), 0, 0, + nsIScriptError::errorFlag, + "X-Frame-Options", + topInnerWindow->WindowID()); + if (NS_FAILED(rv)) { + return; + } + + consoleService->LogMessage(errorObject); +} diff --git a/docshell/base/nsDSURIContentListener.h b/docshell/base/nsDSURIContentListener.h new file mode 100644 index 000000000..f33d1c045 --- /dev/null +++ b/docshell/base/nsDSURIContentListener.h @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDSURIContentListener_h__ +#define nsDSURIContentListener_h__ + +#include "nsCOMPtr.h" +#include "nsIURIContentListener.h" +#include "nsWeakReference.h" + +class nsDocShell; +class nsIWebNavigationInfo; +class nsIHttpChannel; +class nsAString; + +class nsDSURIContentListener final + : public nsIURIContentListener + , public nsSupportsWeakReference +{ + friend class nsDocShell; + +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIURICONTENTLISTENER + + nsresult Init(); + +protected: + explicit nsDSURIContentListener(nsDocShell* aDocShell); + virtual ~nsDSURIContentListener(); + + void DropDocShellReference() + { + mDocShell = nullptr; + mExistingJPEGRequest = nullptr; + mExistingJPEGStreamListener = nullptr; + } + + // Determine if X-Frame-Options allows content to be framed + // as a subdocument + bool CheckFrameOptions(nsIRequest* aRequest); + bool CheckOneFrameOptionsPolicy(nsIHttpChannel* aHttpChannel, + const nsAString& aPolicy); + + enum XFOHeader + { + eDENY, + eSAMEORIGIN, + eALLOWFROM + }; + + void ReportXFOViolation(nsIDocShellTreeItem* aTopDocShellItem, + nsIURI* aThisURI, + XFOHeader aHeader); + +protected: + nsDocShell* mDocShell; + // Hack to handle multipart images without creating a new viewer + nsCOMPtr<nsIStreamListener> mExistingJPEGStreamListener; + nsCOMPtr<nsIChannel> mExistingJPEGRequest; + + // Store the parent listener in either of these depending on + // if supports weak references or not. Proper weak refs are + // preferred and encouraged! + nsWeakPtr mWeakParentContentListener; + nsIURIContentListener* mParentContentListener; + + nsCOMPtr<nsIWebNavigationInfo> mNavInfo; +}; + +#endif /* nsDSURIContentListener_h__ */ diff --git a/docshell/base/nsDefaultURIFixup.cpp b/docshell/base/nsDefaultURIFixup.cpp new file mode 100644 index 000000000..e519720ab --- /dev/null +++ b/docshell/base/nsDefaultURIFixup.cpp @@ -0,0 +1,1152 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsIProtocolHandler.h" +#include "nsCRT.h" + +#include "nsIFile.h" +#include <algorithm> + +#ifdef MOZ_TOOLKIT_SEARCH +#include "nsIBrowserSearchService.h" +#endif + +#include "nsIURIFixup.h" +#include "nsDefaultURIFixup.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/Tokenizer.h" +#include "nsIObserverService.h" +#include "nsXULAppAPI.h" + +// Used to check if external protocol schemes are usable +#include "nsCExternalHandlerService.h" +#include "nsIExternalProtocolService.h" + +using namespace mozilla; + +/* Implementation file */ +NS_IMPL_ISUPPORTS(nsDefaultURIFixup, nsIURIFixup) + +static bool sInitializedPrefCaches = false; +static bool sFixTypos = true; +static bool sDNSFirstForSingleWords = false; +static bool sFixupKeywords = true; + +nsDefaultURIFixup::nsDefaultURIFixup() +{ +} + +nsDefaultURIFixup::~nsDefaultURIFixup() +{ +} + +NS_IMETHODIMP +nsDefaultURIFixup::CreateExposableURI(nsIURI* aURI, nsIURI** aReturn) +{ + NS_ENSURE_ARG_POINTER(aURI); + NS_ENSURE_ARG_POINTER(aReturn); + + bool isWyciwyg = false; + aURI->SchemeIs("wyciwyg", &isWyciwyg); + + nsAutoCString userPass; + aURI->GetUserPass(userPass); + + // most of the time we can just AddRef and return + if (!isWyciwyg && userPass.IsEmpty()) { + *aReturn = aURI; + NS_ADDREF(*aReturn); + return NS_OK; + } + + // Rats, we have to massage the URI + nsCOMPtr<nsIURI> uri; + if (isWyciwyg) { + nsAutoCString path; + nsresult rv = aURI->GetPath(path); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t pathLength = path.Length(); + if (pathLength <= 2) { + return NS_ERROR_FAILURE; + } + + // Path is of the form "//123/http://foo/bar", with a variable number of + // digits. To figure out where the "real" URL starts, search path for a '/', + // starting at the third character. + int32_t slashIndex = path.FindChar('/', 2); + if (slashIndex == kNotFound) { + return NS_ERROR_FAILURE; + } + + // Get the charset of the original URI so we can pass it to our fixed up + // URI. + nsAutoCString charset; + aURI->GetOriginCharset(charset); + + rv = NS_NewURI(getter_AddRefs(uri), + Substring(path, slashIndex + 1, pathLength - slashIndex - 1), + charset.get()); + NS_ENSURE_SUCCESS(rv, rv); + } else { + // clone the URI so zapping user:pass doesn't change the original + nsresult rv = aURI->Clone(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + } + + // hide user:pass unless overridden by pref + if (Preferences::GetBool("browser.fixup.hide_user_pass", true)) { + uri->SetUserPass(EmptyCString()); + } + + uri.forget(aReturn); + return NS_OK; +} + +NS_IMETHODIMP +nsDefaultURIFixup::CreateFixupURI(const nsACString& aStringURI, + uint32_t aFixupFlags, + nsIInputStream** aPostData, nsIURI** aURI) +{ + nsCOMPtr<nsIURIFixupInfo> fixupInfo; + nsresult rv = GetFixupURIInfo(aStringURI, aFixupFlags, aPostData, + getter_AddRefs(fixupInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + fixupInfo->GetPreferredURI(aURI); + return rv; +} + +// Returns true if the URL contains a user:password@ or user@ +static bool +HasUserPassword(const nsACString& aStringURI) +{ + mozilla::Tokenizer parser(aStringURI); + mozilla::Tokenizer::Token token; + + // May start with any of "protocol:", "protocol://", "//", "://" + if (parser.Check(Tokenizer::TOKEN_WORD, token)) { // Skip protocol if any + } + if (parser.CheckChar(':')) { // Skip colon if found + } + while (parser.CheckChar('/')) { // Skip all of the following slashes + } + + while (parser.Next(token)) { + if (token.Type() == Tokenizer::TOKEN_CHAR) { + if (token.AsChar() == '/') { + return false; + } + if (token.AsChar() == '@') { + return true; + } + } + } + + return false; +} + +NS_IMETHODIMP +nsDefaultURIFixup::GetFixupURIInfo(const nsACString& aStringURI, + uint32_t aFixupFlags, + nsIInputStream** aPostData, + nsIURIFixupInfo** aInfo) +{ + NS_ENSURE_ARG(!aStringURI.IsEmpty()); + + nsresult rv; + + nsAutoCString uriString(aStringURI); + + // Eliminate embedded newlines, which single-line text fields now allow: + uriString.StripChars("\r\n"); + // Cleanup the empty spaces that might be on each end: + uriString.Trim(" "); + + NS_ENSURE_TRUE(!uriString.IsEmpty(), NS_ERROR_FAILURE); + + RefPtr<nsDefaultURIFixupInfo> info = new nsDefaultURIFixupInfo(uriString); + NS_ADDREF(*aInfo = info); + + nsCOMPtr<nsIIOService> ioService = + do_GetService(NS_IOSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString scheme; + ioService->ExtractScheme(aStringURI, scheme); + + // View-source is a pseudo scheme. We're interested in fixing up the stuff + // after it. The easiest way to do that is to call this method again with the + // "view-source:" lopped off and then prepend it again afterwards. + + if (scheme.LowerCaseEqualsLiteral("view-source")) { + nsCOMPtr<nsIURIFixupInfo> uriInfo; + // We disable keyword lookup and alternate URIs so that small typos don't + // cause us to look at very different domains + uint32_t newFixupFlags = aFixupFlags & ~FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP + & ~FIXUP_FLAGS_MAKE_ALTERNATE_URI; + + const uint32_t viewSourceLen = sizeof("view-source:") - 1; + nsAutoCString innerURIString(Substring(uriString, viewSourceLen, + uriString.Length() - + viewSourceLen)); + // Prevent recursion: + innerURIString.Trim(" "); + nsAutoCString innerScheme; + ioService->ExtractScheme(innerURIString, innerScheme); + if (innerScheme.LowerCaseEqualsLiteral("view-source")) { + return NS_ERROR_FAILURE; + } + + rv = GetFixupURIInfo(innerURIString, newFixupFlags, aPostData, + getter_AddRefs(uriInfo)); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + nsAutoCString spec; + nsCOMPtr<nsIURI> uri; + uriInfo->GetPreferredURI(getter_AddRefs(uri)); + if (!uri) { + return NS_ERROR_FAILURE; + } + uri->GetSpec(spec); + uriString.AssignLiteral("view-source:"); + uriString.Append(spec); + } else { + // Check for if it is a file URL + nsCOMPtr<nsIURI> uri; + FileURIFixup(uriString, getter_AddRefs(uri)); + // NB: FileURIFixup only returns a URI if it had to fix the protocol to + // do so, so passing in file:///foo/bar will not hit this path: + if (uri) { + uri.swap(info->mFixedURI); + info->mPreferredURI = info->mFixedURI; + info->mFixupChangedProtocol = true; + return NS_OK; + } + } + + if (!sInitializedPrefCaches) { + // Check if we want to fix up common scheme typos. + rv = Preferences::AddBoolVarCache(&sFixTypos, + "browser.fixup.typo.scheme", + sFixTypos); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to observe \"browser.fixup.typo.scheme\""); + + rv = Preferences::AddBoolVarCache(&sDNSFirstForSingleWords, + "browser.fixup.dns_first_for_single_words", + sDNSFirstForSingleWords); + MOZ_ASSERT(NS_SUCCEEDED(rv), + "Failed to observe \"browser.fixup.dns_first_for_single_words\""); + + rv = Preferences::AddBoolVarCache(&sFixupKeywords, "keyword.enabled", + sFixupKeywords); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to observe \"keyword.enabled\""); + sInitializedPrefCaches = true; + } + + // Fix up common scheme typos. + if (sFixTypos && (aFixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS)) { + // Fast-path for common cases. + if (scheme.IsEmpty() || + scheme.LowerCaseEqualsLiteral("http") || + scheme.LowerCaseEqualsLiteral("https") || + scheme.LowerCaseEqualsLiteral("ftp") || + scheme.LowerCaseEqualsLiteral("file")) { + // Do nothing. + } else if (scheme.LowerCaseEqualsLiteral("ttp")) { + // ttp -> http. + uriString.Replace(0, 3, "http"); + scheme.AssignLiteral("http"); + info->mFixupChangedProtocol = true; + } else if (scheme.LowerCaseEqualsLiteral("ttps")) { + // ttps -> https. + uriString.Replace(0, 4, "https"); + scheme.AssignLiteral("https"); + info->mFixupChangedProtocol = true; + } else if (scheme.LowerCaseEqualsLiteral("tps")) { + // tps -> https. + uriString.Replace(0, 3, "https"); + scheme.AssignLiteral("https"); + info->mFixupChangedProtocol = true; + } else if (scheme.LowerCaseEqualsLiteral("ps")) { + // ps -> https. + uriString.Replace(0, 2, "https"); + scheme.AssignLiteral("https"); + info->mFixupChangedProtocol = true; + } else if (scheme.LowerCaseEqualsLiteral("ile")) { + // ile -> file. + uriString.Replace(0, 3, "file"); + scheme.AssignLiteral("file"); + info->mFixupChangedProtocol = true; + } else if (scheme.LowerCaseEqualsLiteral("le")) { + // le -> file. + uriString.Replace(0, 2, "file"); + scheme.AssignLiteral("file"); + info->mFixupChangedProtocol = true; + } + } + + // Now we need to check whether "scheme" is something we don't + // really know about. + nsCOMPtr<nsIProtocolHandler> ourHandler, extHandler; + + ioService->GetProtocolHandler(scheme.get(), getter_AddRefs(ourHandler)); + extHandler = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "default"); + + if (ourHandler != extHandler || !PossiblyHostPortUrl(uriString)) { + // Just try to create an URL out of it + rv = NS_NewURI(getter_AddRefs(info->mFixedURI), uriString, nullptr); + + if (!info->mFixedURI && rv != NS_ERROR_MALFORMED_URI) { + return rv; + } + } + + if (info->mFixedURI && ourHandler == extHandler && sFixupKeywords && + (aFixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS)) { + nsCOMPtr<nsIExternalProtocolService> extProtService = + do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID); + if (extProtService) { + bool handlerExists = false; + rv = extProtService->ExternalProtocolHandlerExists(scheme.get(), + &handlerExists); + if (NS_FAILED(rv)) { + return rv; + } + // This basically means we're dealing with a theoretically valid + // URI... but we have no idea how to load it. (e.g. "christmas:humbug") + // It's more likely the user wants to search, and so we + // chuck this over to their preferred search provider instead: + if (!handlerExists) { + bool hasUserPassword = HasUserPassword(uriString); + if (!hasUserPassword) { + TryKeywordFixupForURIInfo(uriString, info, aPostData); + } else { + // If the given URL has a user:password we can't just pass it to the + // external protocol handler; we'll try using it with http instead later + info->mFixedURI = nullptr; + } + } + } + } + + if (info->mFixedURI) { + if (!info->mPreferredURI) { + if (aFixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI) { + info->mFixupCreatedAlternateURI = MakeAlternateURI(info->mFixedURI); + } + info->mPreferredURI = info->mFixedURI; + } + return NS_OK; + } + + // Fix up protocol string before calling KeywordURIFixup, because + // it cares about the hostname of such URIs: + nsCOMPtr<nsIURI> uriWithProtocol; + bool inputHadDuffProtocol = false; + + // Prune duff protocol schemes + // + // ://totallybroken.url.com + // //shorthand.url.com + // + if (StringBeginsWith(uriString, NS_LITERAL_CSTRING("://"))) { + uriString = StringTail(uriString, uriString.Length() - 3); + inputHadDuffProtocol = true; + } else if (StringBeginsWith(uriString, NS_LITERAL_CSTRING("//"))) { + uriString = StringTail(uriString, uriString.Length() - 2); + inputHadDuffProtocol = true; + } + + // NB: this rv gets returned at the end of this method if we never + // do a keyword fixup after this (because the pref or the flags passed + // might not let us). + rv = FixupURIProtocol(uriString, info, getter_AddRefs(uriWithProtocol)); + if (uriWithProtocol) { + info->mFixedURI = uriWithProtocol; + } + + // See if it is a keyword + // Test whether keywords need to be fixed up + if (sFixupKeywords && (aFixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP) && + !inputHadDuffProtocol) { + if (NS_SUCCEEDED(KeywordURIFixup(uriString, info, aPostData)) && + info->mPreferredURI) { + return NS_OK; + } + } + + // Did the caller want us to try an alternative URI? + // If so, attempt to fixup http://foo into http://www.foo.com + + if (info->mFixedURI && aFixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI) { + info->mFixupCreatedAlternateURI = MakeAlternateURI(info->mFixedURI); + } + + if (info->mFixedURI) { + info->mPreferredURI = info->mFixedURI; + return NS_OK; + } + + // If we still haven't been able to construct a valid URI, try to force a + // keyword match. This catches search strings with '.' or ':' in them. + if (sFixupKeywords && (aFixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP)) { + rv = TryKeywordFixupForURIInfo(aStringURI, info, aPostData); + } + + return rv; +} + +NS_IMETHODIMP +nsDefaultURIFixup::KeywordToURI(const nsACString& aKeyword, + nsIInputStream** aPostData, + nsIURIFixupInfo** aInfo) +{ + RefPtr<nsDefaultURIFixupInfo> info = new nsDefaultURIFixupInfo(aKeyword); + NS_ADDREF(*aInfo = info); + + if (aPostData) { + *aPostData = nullptr; + } + NS_ENSURE_STATE(Preferences::GetRootBranch()); + + // Strip leading "?" and leading/trailing spaces from aKeyword + nsAutoCString keyword(aKeyword); + if (StringBeginsWith(keyword, NS_LITERAL_CSTRING("?"))) { + keyword.Cut(0, 1); + } + keyword.Trim(" "); + + if (XRE_IsContentProcess()) { + dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); + if (!contentChild) { + return NS_ERROR_NOT_AVAILABLE; + } + + ipc::OptionalInputStreamParams postData; + ipc::OptionalURIParams uri; + nsAutoString providerName; + if (!contentChild->SendKeywordToURI(keyword, &providerName, &postData, + &uri)) { + return NS_ERROR_FAILURE; + } + + CopyUTF8toUTF16(keyword, info->mKeywordAsSent); + info->mKeywordProviderName = providerName; + + if (aPostData) { + nsTArray<ipc::FileDescriptor> fds; + nsCOMPtr<nsIInputStream> temp = DeserializeInputStream(postData, fds); + temp.forget(aPostData); + + MOZ_ASSERT(fds.IsEmpty()); + } + + nsCOMPtr<nsIURI> temp = DeserializeURI(uri); + info->mPreferredURI = temp.forget(); + return NS_OK; + } + +#ifdef MOZ_TOOLKIT_SEARCH + // Try falling back to the search service's default search engine + nsCOMPtr<nsIBrowserSearchService> searchSvc = + do_GetService("@mozilla.org/browser/search-service;1"); + if (searchSvc) { + nsCOMPtr<nsISearchEngine> defaultEngine; + searchSvc->GetDefaultEngine(getter_AddRefs(defaultEngine)); + if (defaultEngine) { + nsCOMPtr<nsISearchSubmission> submission; + nsAutoString responseType; + // We allow default search plugins to specify alternate + // parameters that are specific to keyword searches. + NS_NAMED_LITERAL_STRING(mozKeywordSearch, + "application/x-moz-keywordsearch"); + bool supportsResponseType = false; + defaultEngine->SupportsResponseType(mozKeywordSearch, + &supportsResponseType); + if (supportsResponseType) { + responseType.Assign(mozKeywordSearch); + } + + NS_ConvertUTF8toUTF16 keywordW(keyword); + defaultEngine->GetSubmission(keywordW, + responseType, + NS_LITERAL_STRING("keyword"), + getter_AddRefs(submission)); + + if (submission) { + nsCOMPtr<nsIInputStream> postData; + submission->GetPostData(getter_AddRefs(postData)); + if (aPostData) { + postData.forget(aPostData); + } else if (postData) { + // The submission specifies POST data (i.e. the search + // engine's "method" is POST), but our caller didn't allow + // passing post data back. No point passing back a URL that + // won't load properly. + return NS_ERROR_FAILURE; + } + + defaultEngine->GetName(info->mKeywordProviderName); + info->mKeywordAsSent = keywordW; + return submission->GetUri(getter_AddRefs(info->mPreferredURI)); + } + } + } +#endif + + // out of options + return NS_ERROR_NOT_AVAILABLE; +} + +// Helper to deal with passing around uri fixup stuff +nsresult +nsDefaultURIFixup::TryKeywordFixupForURIInfo(const nsACString& aURIString, + nsDefaultURIFixupInfo* aFixupInfo, + nsIInputStream** aPostData) +{ + nsCOMPtr<nsIURIFixupInfo> keywordInfo; + nsresult rv = KeywordToURI(aURIString, aPostData, + getter_AddRefs(keywordInfo)); + if (NS_SUCCEEDED(rv)) { + keywordInfo->GetKeywordProviderName(aFixupInfo->mKeywordProviderName); + keywordInfo->GetKeywordAsSent(aFixupInfo->mKeywordAsSent); + keywordInfo->GetPreferredURI(getter_AddRefs(aFixupInfo->mPreferredURI)); + } + return rv; +} + +bool +nsDefaultURIFixup::MakeAlternateURI(nsIURI* aURI) +{ + if (!Preferences::GetRootBranch()) { + return false; + } + if (!Preferences::GetBool("browser.fixup.alternate.enabled", true)) { + return false; + } + + // Code only works for http. Not for any other protocol including https! + bool isHttp = false; + aURI->SchemeIs("http", &isHttp); + if (!isHttp) { + return false; + } + + // Security - URLs with user / password info should NOT be fixed up + nsAutoCString userpass; + aURI->GetUserPass(userpass); + if (!userpass.IsEmpty()) { + return false; + } + + nsAutoCString oldHost; + nsAutoCString newHost; + aURI->GetHost(oldHost); + + // Count the dots + int32_t numDots = 0; + nsReadingIterator<char> iter; + nsReadingIterator<char> iterEnd; + oldHost.BeginReading(iter); + oldHost.EndReading(iterEnd); + while (iter != iterEnd) { + if (*iter == '.') { + numDots++; + } + ++iter; + } + + // Get the prefix and suffix to stick onto the new hostname. By default these + // are www. & .com but they could be any other value, e.g. www. & .org + + nsAutoCString prefix("www."); + nsAdoptingCString prefPrefix = + Preferences::GetCString("browser.fixup.alternate.prefix"); + if (prefPrefix) { + prefix.Assign(prefPrefix); + } + + nsAutoCString suffix(".com"); + nsAdoptingCString prefSuffix = + Preferences::GetCString("browser.fixup.alternate.suffix"); + if (prefSuffix) { + suffix.Assign(prefSuffix); + } + + if (numDots == 0) { + newHost.Assign(prefix); + newHost.Append(oldHost); + newHost.Append(suffix); + } else if (numDots == 1) { + if (!prefix.IsEmpty() && + oldHost.EqualsIgnoreCase(prefix.get(), prefix.Length())) { + newHost.Assign(oldHost); + newHost.Append(suffix); + } else if (!suffix.IsEmpty()) { + newHost.Assign(prefix); + newHost.Append(oldHost); + } else { + // Do nothing + return false; + } + } else { + // Do nothing + return false; + } + + if (newHost.IsEmpty()) { + return false; + } + + // Assign the new host string over the old one + aURI->SetHost(newHost); + return true; +} + +nsresult +nsDefaultURIFixup::FileURIFixup(const nsACString& aStringURI, nsIURI** aURI) +{ + nsAutoCString uriSpecOut; + + nsresult rv = ConvertFileToStringURI(aStringURI, uriSpecOut); + if (NS_SUCCEEDED(rv)) { + // if this is file url, uriSpecOut is already in FS charset + if (NS_SUCCEEDED(NS_NewURI(aURI, uriSpecOut.get(), nullptr))) { + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +nsresult +nsDefaultURIFixup::ConvertFileToStringURI(const nsACString& aIn, + nsCString& aResult) +{ + bool attemptFixup = false; + +#if defined(XP_WIN) + // Check for \ in the url-string or just a drive (PC) + if (aIn.Contains('\\') || + (aIn.Length() == 2 && (aIn.Last() == ':' || aIn.Last() == '|'))) { + attemptFixup = true; + } +#elif defined(XP_UNIX) + // Check if it starts with / (UNIX) + if (aIn.First() == '/') { + attemptFixup = true; + } +#else + // Do nothing (All others for now) +#endif + + if (attemptFixup) { + // Test if this is a valid path by trying to create a local file + // object. The URL of that is returned if successful. + + // NOTE: Please be sure to check that the call to NS_NewLocalFile + // rejects bad file paths when using this code on a new + // platform. + + nsCOMPtr<nsIFile> filePath; + nsresult rv; + + // this is not the real fix but a temporary fix + // in order to really fix the problem, we need to change the + // nsICmdLineService interface to use wstring to pass paramenters + // instead of string since path name and other argument could be + // in non ascii.(see bug 87127) Since it is too risky to make interface + // change right now, we decide not to do so now. + // Therefore, the aIn we receive here maybe already in damage form + // (e.g. treat every bytes as ISO-8859-1 and cast up to char16_t + // while the real data could be in file system charset ) + // we choice the following logic which will work for most of the case. + // Case will still failed only if it meet ALL the following condiction: + // 1. running on CJK, Russian, or Greek system, and + // 2. user type it from URL bar + // 3. the file name contains character in the range of + // U+00A1-U+00FF but encode as different code point in file + // system charset (e.g. ACP on window)- this is very rare case + // We should remove this logic and convert to File system charset here + // once we change nsICmdLineService to use wstring and ensure + // all the Unicode data come in is correctly converted. + // XXXbz nsICmdLineService doesn't hand back unicode, so in some cases + // what we have is actually a "utf8" version of a "utf16" string that's + // actually byte-expanded native-encoding data. Someone upstream needs + // to stop using AssignWithConversion and do things correctly. See bug + // 58866 for what happens if we remove this + // PossiblyByteExpandedFileName check. + NS_ConvertUTF8toUTF16 in(aIn); + if (PossiblyByteExpandedFileName(in)) { + // removes high byte + rv = NS_NewNativeLocalFile(NS_LossyConvertUTF16toASCII(in), false, + getter_AddRefs(filePath)); + } else { + // input is unicode + rv = NS_NewLocalFile(in, false, getter_AddRefs(filePath)); + } + + if (NS_SUCCEEDED(rv)) { + NS_GetURLSpecFromFile(filePath, aResult); + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} + +nsresult +nsDefaultURIFixup::FixupURIProtocol(const nsACString& aURIString, + nsDefaultURIFixupInfo* aFixupInfo, + nsIURI** aURI) +{ + nsAutoCString uriString(aURIString); + *aURI = nullptr; + + // Add ftp:// or http:// to front of url if it has no spec + // + // Should fix: + // + // no-scheme.com + // ftp.no-scheme.com + // ftp4.no-scheme.com + // no-scheme.com/query?foo=http://www.foo.com + // user:pass@no-scheme.com + // + int32_t schemeDelim = uriString.Find("://", 0); + int32_t firstDelim = uriString.FindCharInSet("/:"); + if (schemeDelim <= 0 || + (firstDelim != -1 && schemeDelim > firstDelim)) { + // find host name + int32_t hostPos = uriString.FindCharInSet("/:?#"); + if (hostPos == -1) { + hostPos = uriString.Length(); + } + + // extract host name + nsAutoCString hostSpec; + uriString.Left(hostSpec, hostPos); + + // insert url spec corresponding to host name + uriString.InsertLiteral("http://", 0); + aFixupInfo->mFixupChangedProtocol = true; + } // end if checkprotocol + + return NS_NewURI(aURI, uriString, nullptr); +} + +bool +nsDefaultURIFixup::PossiblyHostPortUrl(const nsACString& aUrl) +{ + // Oh dear, the protocol is invalid. Test if the protocol might + // actually be a url without a protocol: + // + // http://www.faqs.org/rfcs/rfc1738.html + // http://www.faqs.org/rfcs/rfc2396.html + // + // e.g. Anything of the form: + // + // <hostname>:<port> or + // <hostname>:<port>/ + // + // Where <hostname> is a string of alphanumeric characters and dashes + // separated by dots. + // and <port> is a 5 or less digits. This actually breaks the rfc2396 + // definition of a scheme which allows dots in schemes. + // + // Note: + // People expecting this to work with + // <user>:<password>@<host>:<port>/<url-path> will be disappointed! + // + // Note: Parser could be a lot tighter, tossing out silly hostnames + // such as those containing consecutive dots and so on. + + // Read the hostname which should of the form + // [a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*: + + nsACString::const_iterator iterBegin; + nsACString::const_iterator iterEnd; + aUrl.BeginReading(iterBegin); + aUrl.EndReading(iterEnd); + nsACString::const_iterator iter = iterBegin; + + while (iter != iterEnd) { + uint32_t chunkSize = 0; + // Parse a chunk of the address + while (iter != iterEnd && + (*iter == '-' || + nsCRT::IsAsciiAlpha(*iter) || + nsCRT::IsAsciiDigit(*iter))) { + ++chunkSize; + ++iter; + } + if (chunkSize == 0 || iter == iterEnd) { + return false; + } + if (*iter == ':') { + // Go onto checking the for the digits + break; + } + if (*iter != '.') { + // Whatever it is, it ain't a hostname! + return false; + } + ++iter; + } + if (iter == iterEnd) { + // No point continuing since there is no colon + return false; + } + ++iter; + + // Count the number of digits after the colon and before the + // next forward slash (or end of string) + + uint32_t digitCount = 0; + while (iter != iterEnd && digitCount <= 5) { + if (nsCRT::IsAsciiDigit(*iter)) { + digitCount++; + } else if (*iter == '/') { + break; + } else { + // Whatever it is, it ain't a port! + return false; + } + ++iter; + } + if (digitCount == 0 || digitCount > 5) { + // No digits or more digits than a port would have. + return false; + } + + // Yes, it's possibly a host:port url + return true; +} + +bool +nsDefaultURIFixup::PossiblyByteExpandedFileName(const nsAString& aIn) +{ + // XXXXX HACK XXXXX : please don't copy this code. + // There are cases where aIn contains the locale byte chars padded to short + // (thus the name "ByteExpanded"); whereas other cases + // have proper Unicode code points. + // This is a temporary fix. Please refer to 58866, 86948 + + nsReadingIterator<char16_t> iter; + nsReadingIterator<char16_t> iterEnd; + aIn.BeginReading(iter); + aIn.EndReading(iterEnd); + while (iter != iterEnd) { + if (*iter >= 0x0080 && *iter <= 0x00FF) { + return true; + } + ++iter; + } + return false; +} + +nsresult +nsDefaultURIFixup::KeywordURIFixup(const nsACString& aURIString, + nsDefaultURIFixupInfo* aFixupInfo, + nsIInputStream** aPostData) +{ + // These are keyword formatted strings + // "what is mozilla" + // "what is mozilla?" + // "docshell site:mozilla.org" - has no dot/colon in the first space-separated substring + // "?mozilla" - anything that begins with a question mark + // "?site:mozilla.org docshell" + // Things that have a quote before the first dot/colon + // "mozilla" - checked against a whitelist to see if it's a host or not + // ".mozilla", "mozilla." - ditto + + // These are not keyword formatted strings + // "www.blah.com" - first space-separated substring contains a dot, doesn't start with "?" + // "www.blah.com stuff" + // "nonQualifiedHost:80" - first space-separated substring contains a colon, doesn't start with "?" + // "nonQualifiedHost:80 args" + // "nonQualifiedHost?" + // "nonQualifiedHost?args" + // "nonQualifiedHost?some args" + // "blah.com." + + // Note: uint32_t(kNotFound) is greater than any actual location + // in practice. So if we cast all locations to uint32_t, then a < + // b guarantees that either b is kNotFound and a is found, or both + // are found and a found before b. + + uint32_t firstDotLoc = uint32_t(kNotFound); + uint32_t lastDotLoc = uint32_t(kNotFound); + uint32_t firstColonLoc = uint32_t(kNotFound); + uint32_t firstQuoteLoc = uint32_t(kNotFound); + uint32_t firstSpaceLoc = uint32_t(kNotFound); + uint32_t firstQMarkLoc = uint32_t(kNotFound); + uint32_t lastLSBracketLoc = uint32_t(kNotFound); + uint32_t lastSlashLoc = uint32_t(kNotFound); + uint32_t pos = 0; + uint32_t foundDots = 0; + uint32_t foundColons = 0; + uint32_t foundDigits = 0; + uint32_t foundRSBrackets = 0; + bool looksLikeIpv6 = true; + bool hasAsciiAlpha = false; + + nsACString::const_iterator iterBegin; + nsACString::const_iterator iterEnd; + aURIString.BeginReading(iterBegin); + aURIString.EndReading(iterEnd); + nsACString::const_iterator iter = iterBegin; + + while (iter != iterEnd) { + if (pos >= 1 && foundRSBrackets == 0) { + if (!(lastLSBracketLoc == 0 && + (*iter == ':' || + *iter == '.' || + *iter == ']' || + (*iter >= 'a' && *iter <= 'f') || + (*iter >= 'A' && *iter <= 'F') || + nsCRT::IsAsciiDigit(*iter)))) { + looksLikeIpv6 = false; + } + } + + // If we're at the end of the string or this is the first slash, + // check if the thing before the slash looks like ipv4: + if ((iterEnd - iter == 1 || + (lastSlashLoc == uint32_t(kNotFound) && *iter == '/')) && + // Need 2 or 3 dots + only digits + (foundDots == 2 || foundDots == 3) && + // and they should be all that came before now: + (foundDots + foundDigits == pos || + // or maybe there was also exactly 1 colon that came after the last dot, + // and the digits, dots and colon were all that came before now: + (foundColons == 1 && firstColonLoc > lastDotLoc && + foundDots + foundDigits + foundColons == pos))) { + // Hurray, we got ourselves some ipv4! + // At this point, there's no way we will do a keyword lookup, so just bail immediately: + return NS_OK; + } + + if (*iter == '.') { + ++foundDots; + lastDotLoc = pos; + if (firstDotLoc == uint32_t(kNotFound)) { + firstDotLoc = pos; + } + } else if (*iter == ':') { + ++foundColons; + if (firstColonLoc == uint32_t(kNotFound)) { + firstColonLoc = pos; + } + } else if (*iter == ' ' && firstSpaceLoc == uint32_t(kNotFound)) { + firstSpaceLoc = pos; + } else if (*iter == '?' && firstQMarkLoc == uint32_t(kNotFound)) { + firstQMarkLoc = pos; + } else if ((*iter == '\'' || *iter == '"') && + firstQuoteLoc == uint32_t(kNotFound)) { + firstQuoteLoc = pos; + } else if (*iter == '[') { + lastLSBracketLoc = pos; + } else if (*iter == ']') { + foundRSBrackets++; + } else if (*iter == '/') { + lastSlashLoc = pos; + } else if (nsCRT::IsAsciiAlpha(*iter)) { + hasAsciiAlpha = true; + } else if (nsCRT::IsAsciiDigit(*iter)) { + ++foundDigits; + } + + pos++; + iter++; + } + + if (lastLSBracketLoc > 0 || foundRSBrackets != 1) { + looksLikeIpv6 = false; + } + + // If there are only colons and only hexadecimal characters ([a-z][0-9]) + // enclosed in [], then don't do a keyword lookup + if (looksLikeIpv6) { + return NS_OK; + } + + nsAutoCString asciiHost; + nsAutoCString host; + + bool isValidAsciiHost = + aFixupInfo->mFixedURI && + NS_SUCCEEDED(aFixupInfo->mFixedURI->GetAsciiHost(asciiHost)) && + !asciiHost.IsEmpty(); + + bool isValidHost = + aFixupInfo->mFixedURI && + NS_SUCCEEDED(aFixupInfo->mFixedURI->GetHost(host)) && + !host.IsEmpty(); + + nsresult rv = NS_OK; + // We do keyword lookups if a space or quote preceded the dot, colon + // or question mark (or if the latter is not found, or if the input starts + // with a question mark) + if (((firstSpaceLoc < firstDotLoc || firstQuoteLoc < firstDotLoc) && + (firstSpaceLoc < firstColonLoc || firstQuoteLoc < firstColonLoc) && + (firstSpaceLoc < firstQMarkLoc || firstQuoteLoc < firstQMarkLoc)) || + firstQMarkLoc == 0) { + rv = TryKeywordFixupForURIInfo(aFixupInfo->mOriginalInput, aFixupInfo, + aPostData); + // ... or when the host is the same as asciiHost and there are no + // characters from [a-z][A-Z] + } else if (isValidAsciiHost && isValidHost && !hasAsciiAlpha && + host.EqualsIgnoreCase(asciiHost.get())) { + if (!sDNSFirstForSingleWords) { + rv = TryKeywordFixupForURIInfo(aFixupInfo->mOriginalInput, aFixupInfo, + aPostData); + } + } + // ... or if there is no question mark or colon, and there is either no + // dot, or exactly 1 and it is the first or last character of the input: + else if ((firstDotLoc == uint32_t(kNotFound) || + (foundDots == 1 && (firstDotLoc == 0 || + firstDotLoc == aURIString.Length() - 1))) && + firstColonLoc == uint32_t(kNotFound) && + firstQMarkLoc == uint32_t(kNotFound)) { + if (isValidAsciiHost && IsDomainWhitelisted(asciiHost, firstDotLoc)) { + return NS_OK; + } + + // ... unless there are no dots, and a slash, and alpha characters, and + // this is a valid host: + if (firstDotLoc == uint32_t(kNotFound) && + lastSlashLoc != uint32_t(kNotFound) && + hasAsciiAlpha && isValidAsciiHost) { + return NS_OK; + } + + // If we get here, we don't have a valid URI, or we did but the + // host is not whitelisted, so we do a keyword search *anyway*: + rv = TryKeywordFixupForURIInfo(aFixupInfo->mOriginalInput, aFixupInfo, + aPostData); + } + return rv; +} + +bool +nsDefaultURIFixup::IsDomainWhitelisted(const nsACString& aAsciiHost, + const uint32_t aDotLoc) +{ + if (sDNSFirstForSingleWords) { + return true; + } + // Check if this domain is whitelisted as an actual + // domain (which will prevent a keyword query) + // NB: any processing of the host here should stay in sync with + // code in the front-end(s) that set the pref. + + nsAutoCString pref("browser.fixup.domainwhitelist."); + + if (aDotLoc == aAsciiHost.Length() - 1) { + pref.Append(Substring(aAsciiHost, 0, aAsciiHost.Length() - 1)); + } else { + pref.Append(aAsciiHost); + } + + return Preferences::GetBool(pref.get(), false); +} + +NS_IMETHODIMP +nsDefaultURIFixup::IsDomainWhitelisted(const nsACString& aDomain, + const uint32_t aDotLoc, + bool* aResult) +{ + *aResult = IsDomainWhitelisted(aDomain, aDotLoc); + return NS_OK; +} + +/* Implementation of nsIURIFixupInfo */ +NS_IMPL_ISUPPORTS(nsDefaultURIFixupInfo, nsIURIFixupInfo) + +nsDefaultURIFixupInfo::nsDefaultURIFixupInfo(const nsACString& aOriginalInput) + : mFixupChangedProtocol(false) + , mFixupCreatedAlternateURI(false) +{ + mOriginalInput = aOriginalInput; +} + +nsDefaultURIFixupInfo::~nsDefaultURIFixupInfo() +{ +} + +NS_IMETHODIMP +nsDefaultURIFixupInfo::GetConsumer(nsISupports** aConsumer) +{ + *aConsumer = mConsumer; + NS_IF_ADDREF(*aConsumer); + return NS_OK; +} + +NS_IMETHODIMP +nsDefaultURIFixupInfo::SetConsumer(nsISupports* aConsumer) +{ + mConsumer = aConsumer; + return NS_OK; +} + +NS_IMETHODIMP +nsDefaultURIFixupInfo::GetPreferredURI(nsIURI** aPreferredURI) +{ + *aPreferredURI = mPreferredURI; + NS_IF_ADDREF(*aPreferredURI); + return NS_OK; +} + +NS_IMETHODIMP +nsDefaultURIFixupInfo::GetFixedURI(nsIURI** aFixedURI) +{ + *aFixedURI = mFixedURI; + NS_IF_ADDREF(*aFixedURI); + return NS_OK; +} + +NS_IMETHODIMP +nsDefaultURIFixupInfo::GetKeywordProviderName(nsAString& aResult) +{ + aResult = mKeywordProviderName; + return NS_OK; +} + +NS_IMETHODIMP +nsDefaultURIFixupInfo::GetKeywordAsSent(nsAString& aResult) +{ + aResult = mKeywordAsSent; + return NS_OK; +} + +NS_IMETHODIMP +nsDefaultURIFixupInfo::GetFixupChangedProtocol(bool* aResult) +{ + *aResult = mFixupChangedProtocol; + return NS_OK; +} + +NS_IMETHODIMP +nsDefaultURIFixupInfo::GetFixupCreatedAlternateURI(bool* aResult) +{ + *aResult = mFixupCreatedAlternateURI; + return NS_OK; +} + +NS_IMETHODIMP +nsDefaultURIFixupInfo::GetOriginalInput(nsACString& aResult) +{ + aResult = mOriginalInput; + return NS_OK; +} diff --git a/docshell/base/nsDefaultURIFixup.h b/docshell/base/nsDefaultURIFixup.h new file mode 100644 index 000000000..6aed66ca5 --- /dev/null +++ b/docshell/base/nsDefaultURIFixup.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSDEFAULTURIFIXUP_H +#define NSDEFAULTURIFIXUP_H + +#include "nsIURIFixup.h" + +class nsDefaultURIFixupInfo; + +/* Header file */ +class nsDefaultURIFixup : public nsIURIFixup +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIURIFIXUP + + nsDefaultURIFixup(); + +protected: + virtual ~nsDefaultURIFixup(); + +private: + /* additional members */ + nsresult FileURIFixup(const nsACString& aStringURI, nsIURI** aURI); + nsresult ConvertFileToStringURI(const nsACString& aIn, nsCString& aResult); + nsresult FixupURIProtocol(const nsACString& aIn, + nsDefaultURIFixupInfo* aFixupInfo, + nsIURI** aURI); + nsresult KeywordURIFixup(const nsACString& aStringURI, + nsDefaultURIFixupInfo* aFixupInfo, + nsIInputStream** aPostData); + nsresult TryKeywordFixupForURIInfo(const nsACString& aStringURI, + nsDefaultURIFixupInfo* aFixupInfo, + nsIInputStream** aPostData); + bool PossiblyByteExpandedFileName(const nsAString& aIn); + bool PossiblyHostPortUrl(const nsACString& aUrl); + bool MakeAlternateURI(nsIURI* aURI); + bool IsDomainWhitelisted(const nsACString& aAsciiHost, + const uint32_t aDotLoc); +}; + +class nsDefaultURIFixupInfo : public nsIURIFixupInfo +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIURIFIXUPINFO + + explicit nsDefaultURIFixupInfo(const nsACString& aOriginalInput); + + friend class nsDefaultURIFixup; + +protected: + virtual ~nsDefaultURIFixupInfo(); + +private: + nsCOMPtr<nsISupports> mConsumer; + nsCOMPtr<nsIURI> mPreferredURI; + nsCOMPtr<nsIURI> mFixedURI; + bool mFixupChangedProtocol; + bool mFixupCreatedAlternateURI; + nsString mKeywordProviderName; + nsString mKeywordAsSent; + nsCString mOriginalInput; +}; +#endif diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp new file mode 100644 index 000000000..ab119a016 --- /dev/null +++ b/docshell/base/nsDocShell.cpp @@ -0,0 +1,14853 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDocShell.h" + +#include <algorithm> + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/Casting.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ChromeUtils.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/TabChild.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "mozilla/dom/ScreenOrientation.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/dom/PermissionMessageUtils.h" +#include "mozilla/dom/workers/ServiceWorkerManager.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/StartupTimeline.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "Navigator.h" +#include "URIUtils.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/TabGroup.h" + +#include "nsIContent.h" +#include "nsIContentInlines.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIDOMElement.h" + +#include "nsArray.h" +#include "nsArrayUtils.h" +#include "nsICaptivePortalService.h" +#include "nsIDOMStorage.h" +#include "nsIContentViewer.h" +#include "nsIDocumentLoaderFactory.h" +#include "nsCURILoader.h" +#include "nsDocShellCID.h" +#include "nsDOMCID.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "mozilla/net/ReferrerPolicy.h" +#include "nsRect.h" +#include "prenv.h" +#include "nsIDOMWindow.h" +#include "nsIGlobalObject.h" +#include "nsIViewSourceChannel.h" +#include "nsIWebBrowserChrome.h" +#include "nsPoint.h" +#include "nsIObserverService.h" +#include "nsIPrompt.h" +#include "nsIAuthPrompt.h" +#include "nsIAuthPrompt2.h" +#include "nsIChannelEventSink.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIScriptSecurityManager.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScrollableFrame.h" +#include "nsContentPolicyUtils.h" // NS_CheckContentLoadPolicy(...) +#include "nsISeekableStream.h" +#include "nsAutoPtr.h" +#include "nsQueryObject.h" +#include "nsIWritablePropertyBag2.h" +#include "nsIAppShell.h" +#include "nsWidgetsCID.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsView.h" +#include "nsViewManager.h" +#include "nsIScriptChannel.h" +#include "nsITimedChannel.h" +#include "nsIPrivacyTransitionObserver.h" +#include "nsIReflowObserver.h" +#include "nsIScrollObserver.h" +#include "nsIDocShellTreeItem.h" +#include "nsIChannel.h" +#include "IHistory.h" +#include "nsViewSourceHandler.h" +#include "nsWhitespaceTokenizer.h" +#include "nsICookieService.h" +#include "nsIConsoleReportCollector.h" +#include "nsObjectLoadingContent.h" + +// we want to explore making the document own the load group +// so we can associate the document URI with the load group. +// until this point, we have an evil hack: +#include "nsIHttpChannelInternal.h" +#include "nsPILoadGroupInternal.h" + +// Local Includes +#include "nsDocShellLoadInfo.h" +#include "nsCDefaultURIFixup.h" +#include "nsDocShellEnumerator.h" +#include "nsSHistory.h" +#include "nsDocShellEditorData.h" +#include "GeckoProfiler.h" +#include "timeline/JavascriptTimelineMarker.h" + +// Helper Classes +#include "nsError.h" +#include "nsEscape.h" + +// Interfaces Needed +#include "nsIFormPOSTActionChannel.h" +#include "nsIUploadChannel.h" +#include "nsIUploadChannel2.h" +#include "nsIWebProgress.h" +#include "nsILayoutHistoryState.h" +#include "nsITimer.h" +#include "nsISHistoryInternal.h" +#include "nsIPrincipal.h" +#include "nsNullPrincipal.h" +#include "nsISHEntry.h" +#include "nsIWindowWatcher.h" +#include "nsIPromptFactory.h" +#include "nsITransportSecurityInfo.h" +#include "nsINode.h" +#include "nsINSSErrorsService.h" +#include "nsIApplicationCacheChannel.h" +#include "nsIApplicationCacheContainer.h" +#include "nsStreamUtils.h" +#include "nsIController.h" +#include "nsPICommandUpdater.h" +#include "nsIDOMHTMLAnchorElement.h" +#include "nsIWebBrowserChrome3.h" +#include "nsITabChild.h" +#include "nsISiteSecurityService.h" +#include "nsStructuredCloneContainer.h" +#include "nsIStructuredCloneContainer.h" +#ifdef MOZ_PLACES +#include "nsIFaviconService.h" +#include "mozIPlacesPendingOperation.h" +#include "mozIAsyncFavicons.h" +#endif +#include "nsINetworkPredictor.h" + +// Editor-related +#include "nsIEditingSession.h" + +#include "nsPIDOMWindow.h" +#include "nsGlobalWindow.h" +#include "nsPIWindowRoot.h" +#include "nsICachingChannel.h" +#include "nsIMultiPartChannel.h" +#include "nsIWyciwygChannel.h" + +// For reporting errors with the console service. +// These can go away if error reporting is propagated up past nsDocShell. +#include "nsIScriptError.h" + +// used to dispatch urls to default protocol handlers +#include "nsCExternalHandlerService.h" +#include "nsIExternalProtocolService.h" + +#include "nsFocusManager.h" + +#include "nsITextToSubURI.h" + +#include "nsIJARChannel.h" + +#include "mozilla/Logging.h" + +#include "nsISelectionDisplay.h" + +#include "nsIGlobalHistory2.h" + +#include "nsIFrame.h" +#include "nsSubDocumentFrame.h" + +// for embedding +#include "nsIWebBrowserChromeFocus.h" + +#if NS_PRINT_PREVIEW +#include "nsIDocumentViewerPrint.h" +#include "nsIWebBrowserPrint.h" +#endif + +#include "nsContentUtils.h" +#include "nsIContentSecurityPolicy.h" +#include "nsILoadInfo.h" +#include "nsSandboxFlags.h" +#include "nsXULAppAPI.h" +#include "nsDOMNavigationTiming.h" +#include "nsISecurityUITelemetry.h" +#include "nsIAppsService.h" +#include "nsDSURIContentListener.h" +#include "nsDocShellLoadTypes.h" +#include "nsDocShellTransferableHooks.h" +#include "nsICommandManager.h" +#include "nsIDOMNode.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIHttpChannel.h" +#include "nsIIDNService.h" +#include "nsIInputStreamChannel.h" +#include "nsINestedURI.h" +#include "nsISHContainer.h" +#include "nsISHistory.h" +#include "nsISecureBrowserUI.h" +#include "nsISocketProvider.h" +#include "nsIStringBundle.h" +#include "nsIURIFixup.h" +#include "nsIURILoader.h" +#include "nsIURL.h" +#include "nsIWebBrowserFind.h" +#include "nsIWidget.h" +#include "mozilla/dom/EncodingUtils.h" +#include "mozilla/dom/PerformanceNavigation.h" +#include "mozilla/dom/ScriptSettings.h" + +#ifdef MOZ_TOOLKIT_SEARCH +#include "nsIBrowserSearchService.h" +#endif + +#include "mozIThirdPartyUtil.h" + +static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); + +#if defined(DEBUG_bryner) || defined(DEBUG_chb) +//#define DEBUG_DOCSHELL_FOCUS +#define DEBUG_PAGE_CACHE +#endif + +#ifdef XP_WIN +#include <process.h> +#define getpid _getpid +#else +#include <unistd.h> // for getpid() +#endif + +using namespace mozilla; +using namespace mozilla::dom; +using mozilla::dom::workers::ServiceWorkerManager; + +// True means sUseErrorPages has been added to +// preferences var cache. +static bool gAddedPreferencesVarCache = false; + +bool nsDocShell::sUseErrorPages = false; + +// Number of documents currently loading +static int32_t gNumberOfDocumentsLoading = 0; + +// Global count of existing docshells. +static int32_t gDocShellCount = 0; + +// Global count of docshells with the private attribute set +static uint32_t gNumberOfPrivateDocShells = 0; + +// Global reference to the URI fixup service. +nsIURIFixup* nsDocShell::sURIFixup = 0; + +// True means we validate window targets to prevent frameset +// spoofing. Initialize this to a non-bolean value so we know to check +// the pref on the creation of the first docshell. +static uint32_t gValidateOrigin = 0xffffffff; + +// Hint for native dispatch of events on how long to delay after +// all documents have loaded in milliseconds before favoring normal +// native event dispatch priorites over performance +// Can be overridden with docshell.event_starvation_delay_hint pref. +#define NS_EVENT_STARVATION_DELAY_HINT 2000 + +#ifdef DEBUG +static mozilla::LazyLogModule gDocShellLog("nsDocShell"); +#endif +static mozilla::LazyLogModule gDocShellLeakLog("nsDocShellLeak");; + +const char kBrandBundleURL[] = "chrome://branding/locale/brand.properties"; +const char kAppstringsBundleURL[] = "chrome://global/locale/appstrings.properties"; + +static void +FavorPerformanceHint(bool aPerfOverStarvation) +{ + nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID); + if (appShell) { + appShell->FavorPerformanceHint( + aPerfOverStarvation, + Preferences::GetUint("docshell.event_starvation_delay_hint", + NS_EVENT_STARVATION_DELAY_HINT)); + } +} + +//***************************************************************************** +// <a ping> support +//***************************************************************************** + +#define PREF_PINGS_ENABLED "browser.send_pings" +#define PREF_PINGS_MAX_PER_LINK "browser.send_pings.max_per_link" +#define PREF_PINGS_REQUIRE_SAME_HOST "browser.send_pings.require_same_host" + +// Check prefs to see if pings are enabled and if so what restrictions might +// be applied. +// +// @param maxPerLink +// This parameter returns the number of pings that are allowed per link click +// +// @param requireSameHost +// This parameter returns true if pings are restricted to the same host as +// the document in which the click occurs. If the same host restriction is +// imposed, then we still allow for pings to cross over to different +// protocols and ports for flexibility and because it is not possible to send +// a ping via FTP. +// +// @returns +// true if pings are enabled and false otherwise. +// +static bool +PingsEnabled(int32_t* aMaxPerLink, bool* aRequireSameHost) +{ + bool allow = Preferences::GetBool(PREF_PINGS_ENABLED, false); + + *aMaxPerLink = 1; + *aRequireSameHost = true; + + if (allow) { + Preferences::GetInt(PREF_PINGS_MAX_PER_LINK, aMaxPerLink); + Preferences::GetBool(PREF_PINGS_REQUIRE_SAME_HOST, aRequireSameHost); + } + + return allow; +} + +typedef void (*ForEachPingCallback)(void* closure, nsIContent* content, + nsIURI* uri, nsIIOService* ios); + +static bool +IsElementAnchor(nsIContent* aContent) +{ + // Make sure we are dealing with either an <A> or <AREA> element in the HTML + // or XHTML namespace. + return aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area); +} + +static void +ForEachPing(nsIContent* aContent, ForEachPingCallback aCallback, void* aClosure) +{ + // NOTE: Using nsIDOMHTMLAnchorElement::GetPing isn't really worth it here + // since we'd still need to parse the resulting string. Instead, we + // just parse the raw attribute. It might be nice if the content node + // implemented an interface that exposed an enumeration of nsIURIs. + + // Make sure we are dealing with either an <A> or <AREA> element in the HTML + // or XHTML namespace. + if (!IsElementAnchor(aContent)) { + return; + } + + nsCOMPtr<nsIAtom> pingAtom = NS_Atomize("ping"); + if (!pingAtom) { + return; + } + + nsAutoString value; + aContent->GetAttr(kNameSpaceID_None, pingAtom, value); + if (value.IsEmpty()) { + return; + } + + nsCOMPtr<nsIIOService> ios = do_GetIOService(); + if (!ios) { + return; + } + + nsIDocument* doc = aContent->OwnerDoc(); + + nsWhitespaceTokenizer tokenizer(value); + + while (tokenizer.hasMoreTokens()) { + nsCOMPtr<nsIURI> uri, baseURI = aContent->GetBaseURI(); + ios->NewURI(NS_ConvertUTF16toUTF8(tokenizer.nextToken()), + doc->GetDocumentCharacterSet().get(), + baseURI, getter_AddRefs(uri)); + // if we can't generate a valid URI, then there is nothing to do + if (!uri) { + continue; + } + // Explicitly not allow loading data: URIs + bool isDataScheme = + (NS_SUCCEEDED(uri->SchemeIs("data", &isDataScheme)) && isDataScheme); + + if (!isDataScheme) { + aCallback(aClosure, aContent, uri, ios); + } + } +} + +//---------------------------------------------------------------------- + +// We wait this many milliseconds before killing the ping channel... +#define PING_TIMEOUT 10000 + +static void +OnPingTimeout(nsITimer* aTimer, void* aClosure) +{ + nsILoadGroup* loadGroup = static_cast<nsILoadGroup*>(aClosure); + if (loadGroup) { + loadGroup->Cancel(NS_ERROR_ABORT); + } +} + +class nsPingListener final + : public nsIStreamListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + nsPingListener() + { + } + + void SetLoadGroup(nsILoadGroup* aLoadGroup) { + mLoadGroup = aLoadGroup; + } + + nsresult StartTimeout(); + +private: + ~nsPingListener(); + + nsCOMPtr<nsILoadGroup> mLoadGroup; + nsCOMPtr<nsITimer> mTimer; +}; + +NS_IMPL_ISUPPORTS(nsPingListener, nsIStreamListener, nsIRequestObserver) + +nsPingListener::~nsPingListener() +{ + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } +} + +nsresult +nsPingListener::StartTimeout() +{ + nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID); + + if (timer) { + nsresult rv = timer->InitWithFuncCallback(OnPingTimeout, mLoadGroup, + PING_TIMEOUT, + nsITimer::TYPE_ONE_SHOT); + if (NS_SUCCEEDED(rv)) { + mTimer = timer; + return NS_OK; + } + } + + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsPingListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsPingListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, + nsIInputStream* aStream, uint64_t aOffset, + uint32_t aCount) +{ + uint32_t result; + return aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &result); +} + +NS_IMETHODIMP +nsPingListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, + nsresult aStatus) +{ + mLoadGroup = nullptr; + + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + return NS_OK; +} + +struct MOZ_STACK_CLASS SendPingInfo +{ + int32_t numPings; + int32_t maxPings; + bool requireSameHost; + nsIURI* target; + nsIURI* referrer; + nsIDocShell* docShell; + uint32_t referrerPolicy; +}; + +static void +SendPing(void* aClosure, nsIContent* aContent, nsIURI* aURI, + nsIIOService* aIOService) +{ + SendPingInfo* info = static_cast<SendPingInfo*>(aClosure); + if (info->maxPings > -1 && info->numPings >= info->maxPings) { + return; + } + + nsIDocument* doc = aContent->OwnerDoc(); + + nsCOMPtr<nsIChannel> chan; + NS_NewChannel(getter_AddRefs(chan), + aURI, + doc, + info->requireSameHost + ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED + : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_PING, + nullptr, // aLoadGroup + nullptr, // aCallbacks + nsIRequest::LOAD_NORMAL, // aLoadFlags, + aIOService); + + if (!chan) { + return; + } + + // Don't bother caching the result of this URI load, but do not exempt + // it from Safe Browsing. + chan->SetLoadFlags(nsIRequest::INHIBIT_CACHING | nsIChannel::LOAD_CLASSIFY_URI); + + nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(chan); + if (!httpChan) { + return; + } + + // This is needed in order for 3rd-party cookie blocking to work. + nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(httpChan); + if (httpInternal) { + httpInternal->SetDocumentURI(doc->GetDocumentURI()); + } + + httpChan->SetRequestMethod(NS_LITERAL_CSTRING("POST")); + + // Remove extraneous request headers (to reduce request size) + httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept"), + EmptyCString(), false); + httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept-language"), + EmptyCString(), false); + httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept-encoding"), + EmptyCString(), false); + + // Always send a Ping-To header. + nsAutoCString pingTo; + if (NS_SUCCEEDED(info->target->GetSpec(pingTo))) { + httpChan->SetRequestHeader(NS_LITERAL_CSTRING("Ping-To"), pingTo, false); + } + + nsCOMPtr<nsIScriptSecurityManager> sm = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + + if (sm && info->referrer) { + bool referrerIsSecure; + uint32_t flags = nsIProtocolHandler::URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT; + nsresult rv = NS_URIChainHasFlags(info->referrer, flags, &referrerIsSecure); + + // Default to sending less data if NS_URIChainHasFlags() fails. + referrerIsSecure = NS_FAILED(rv) || referrerIsSecure; + + bool sameOrigin = + NS_SUCCEEDED(sm->CheckSameOriginURI(info->referrer, aURI, false)); + + // If both the address of the document containing the hyperlink being + // audited and "ping URL" have the same origin or the document containing + // the hyperlink being audited was not retrieved over an encrypted + // connection, send a Ping-From header. + if (sameOrigin || !referrerIsSecure) { + nsAutoCString pingFrom; + if (NS_SUCCEEDED(info->referrer->GetSpec(pingFrom))) { + httpChan->SetRequestHeader(NS_LITERAL_CSTRING("Ping-From"), pingFrom, + false); + } + } + + // If the document containing the hyperlink being audited was not retrieved + // over an encrypted connection and its address does not have the same + // origin as "ping URL", send a referrer. + if (!sameOrigin && !referrerIsSecure) { + httpChan->SetReferrerWithPolicy(info->referrer, info->referrerPolicy); + } + } + + nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(httpChan); + if (!uploadChan) { + return; + } + + NS_NAMED_LITERAL_CSTRING(uploadData, "PING"); + + nsCOMPtr<nsIInputStream> uploadStream; + NS_NewPostDataStream(getter_AddRefs(uploadStream), false, uploadData); + if (!uploadStream) { + return; + } + + uploadChan->ExplicitSetUploadStream(uploadStream, + NS_LITERAL_CSTRING("text/ping"), + uploadData.Length(), + NS_LITERAL_CSTRING("POST"), false); + + // The channel needs to have a loadgroup associated with it, so that we can + // cancel the channel and any redirected channels it may create. + nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID); + if (!loadGroup) { + return; + } + nsCOMPtr<nsIInterfaceRequestor> callbacks = do_QueryInterface(info->docShell); + loadGroup->SetNotificationCallbacks(callbacks); + chan->SetLoadGroup(loadGroup); + + RefPtr<nsPingListener> pingListener = new nsPingListener(); + chan->AsyncOpen2(pingListener); + + // Even if AsyncOpen failed, we still count this as a successful ping. It's + // possible that AsyncOpen may have failed after triggering some background + // process that may have written something to the network. + info->numPings++; + + // Prevent ping requests from stalling and never being garbage collected... + if (NS_FAILED(pingListener->StartTimeout())) { + // If we failed to setup the timer, then we should just cancel the channel + // because we won't be able to ensure that it goes away in a timely manner. + chan->Cancel(NS_ERROR_ABORT); + return; + } + // if the channel openend successfully, then make the pingListener hold + // a strong reference to the loadgroup which is released in ::OnStopRequest + pingListener->SetLoadGroup(loadGroup); +} + +// Spec: http://whatwg.org/specs/web-apps/current-work/#ping +static void +DispatchPings(nsIDocShell* aDocShell, + nsIContent* aContent, + nsIURI* aTarget, + nsIURI* aReferrer, + uint32_t aReferrerPolicy) +{ + SendPingInfo info; + + if (!PingsEnabled(&info.maxPings, &info.requireSameHost)) { + return; + } + if (info.maxPings == 0) { + return; + } + + info.numPings = 0; + info.target = aTarget; + info.referrer = aReferrer; + info.referrerPolicy = aReferrerPolicy; + info.docShell = aDocShell; + + ForEachPing(aContent, SendPing, &info); +} + +static nsDOMNavigationTiming::Type +ConvertLoadTypeToNavigationType(uint32_t aLoadType) +{ + // Not initialized, assume it's normal load. + if (aLoadType == 0) { + aLoadType = LOAD_NORMAL; + } + + auto result = nsDOMNavigationTiming::TYPE_RESERVED; + switch (aLoadType) { + case LOAD_NORMAL: + case LOAD_NORMAL_EXTERNAL: + case LOAD_NORMAL_BYPASS_CACHE: + case LOAD_NORMAL_BYPASS_PROXY: + case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE: + case LOAD_NORMAL_REPLACE: + case LOAD_NORMAL_ALLOW_MIXED_CONTENT: + case LOAD_LINK: + case LOAD_STOP_CONTENT: + case LOAD_REPLACE_BYPASS_CACHE: + result = nsDOMNavigationTiming::TYPE_NAVIGATE; + break; + case LOAD_HISTORY: + result = nsDOMNavigationTiming::TYPE_BACK_FORWARD; + break; + case LOAD_RELOAD_NORMAL: + case LOAD_RELOAD_CHARSET_CHANGE: + case LOAD_RELOAD_BYPASS_CACHE: + case LOAD_RELOAD_BYPASS_PROXY: + case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: + case LOAD_RELOAD_ALLOW_MIXED_CONTENT: + result = nsDOMNavigationTiming::TYPE_RELOAD; + break; + case LOAD_STOP_CONTENT_AND_REPLACE: + case LOAD_REFRESH: + case LOAD_BYPASS_HISTORY: + case LOAD_ERROR_PAGE: + case LOAD_PUSHSTATE: + result = nsDOMNavigationTiming::TYPE_RESERVED; + break; + default: + // NS_NOTREACHED("Unexpected load type value"); + result = nsDOMNavigationTiming::TYPE_RESERVED; + break; + } + + return result; +} + +static nsISHEntry* GetRootSHEntry(nsISHEntry* aEntry); + +static void +IncreasePrivateDocShellCount() +{ + gNumberOfPrivateDocShells++; + if (gNumberOfPrivateDocShells > 1 || + !XRE_IsContentProcess()) { + return; + } + + mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton(); + cc->SendPrivateDocShellsExist(true); +} + +static void +DecreasePrivateDocShellCount() +{ + MOZ_ASSERT(gNumberOfPrivateDocShells > 0); + gNumberOfPrivateDocShells--; + if (!gNumberOfPrivateDocShells) { + if (XRE_IsContentProcess()) { + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + cc->SendPrivateDocShellsExist(false); + return; + } + + nsCOMPtr<nsIObserverService> obsvc = services::GetObserverService(); + if (obsvc) { + obsvc->NotifyObservers(nullptr, "last-pb-context-exited", nullptr); + } + } +} + +static uint64_t gDocshellIDCounter = 0; + +nsDocShell::nsDocShell() + : nsDocLoader() + , mDefaultScrollbarPref(Scrollbar_Auto, Scrollbar_Auto) + , mTreeOwner(nullptr) + , mChromeEventHandler(nullptr) + , mCharsetReloadState(eCharsetReloadInit) + , mChildOffset(0) + , mBusyFlags(BUSY_FLAGS_NONE) + , mAppType(nsIDocShell::APP_TYPE_UNKNOWN) + , mLoadType(0) + , mMarginWidth(-1) + , mMarginHeight(-1) + , mItemType(typeContent) + , mPreviousTransIndex(-1) + , mLoadedTransIndex(-1) + , mSandboxFlags(0) + , mOrientationLock(eScreenOrientation_None) + , mFullscreenAllowed(CHECK_ATTRIBUTES) + , mCreated(false) + , mAllowSubframes(true) + , mAllowPlugins(true) + , mAllowJavascript(true) + , mAllowMetaRedirects(true) + , mAllowImages(true) + , mAllowMedia(true) + , mAllowDNSPrefetch(true) + , mAllowWindowControl(true) + , mAllowContentRetargeting(true) + , mAllowContentRetargetingOnChildren(true) + , mUseErrorPages(false) + , mObserveErrorPages(true) + , mAllowAuth(true) + , mAllowKeywordFixup(false) + , mIsOffScreenBrowser(false) + , mIsActive(true) + , mDisableMetaRefreshWhenInactive(false) + , mIsPrerendered(false) + , mIsAppTab(false) + , mUseGlobalHistory(false) + , mUseRemoteTabs(false) + , mDeviceSizeIsPageSize(false) + , mWindowDraggingAllowed(false) + , mInFrameSwap(false) + , mInheritPrivateBrowsingId(true) + , mCanExecuteScripts(false) + , mFiredUnloadEvent(false) + , mEODForCurrentDocument(false) + , mURIResultedInDocument(false) + , mIsBeingDestroyed(false) + , mIsExecutingOnLoadHandler(false) + , mIsPrintingOrPP(false) + , mSavingOldViewer(false) + , mAffectPrivateSessionLifetime(true) + , mInvisible(false) + , mHasLoadedNonBlankURI(false) + , mBlankTiming(false) + , mCreatingDocument(false) +#ifdef DEBUG + , mInEnsureScriptEnv(false) +#endif + , mDefaultLoadFlags(nsIRequest::LOAD_NORMAL) + , mFrameType(FRAME_TYPE_REGULAR) + , mPrivateBrowsingId(0) + , mParentCharsetSource(0) + , mJSRunToCompletionDepth(0) + , mTouchEventsOverride(nsIDocShell::TOUCHEVENTS_OVERRIDE_NONE) +{ + AssertOriginAttributesMatchPrivateBrowsing(); + mHistoryID = ++gDocshellIDCounter; + if (gDocShellCount++ == 0) { + NS_ASSERTION(sURIFixup == nullptr, + "Huh, sURIFixup not null in first nsDocShell ctor!"); + + CallGetService(NS_URIFIXUP_CONTRACTID, &sURIFixup); + } + + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p created\n", this)); + +#ifdef DEBUG + // We're counting the number of |nsDocShells| to help find leaks + ++gNumberOfDocShells; + if (!PR_GetEnv("MOZ_QUIET")) { + printf_stderr("++DOCSHELL %p == %ld [pid = %d] [id = %llu]\n", + (void*)this, + gNumberOfDocShells, + getpid(), + AssertedCast<unsigned long long>(mHistoryID)); + } +#endif +} + +nsDocShell::~nsDocShell() +{ + MOZ_ASSERT(!mObserved); + + // Avoid notifying observers while we're in the dtor. + mIsBeingDestroyed = true; + + Destroy(); + + nsCOMPtr<nsISHistoryInternal> shPrivate(do_QueryInterface(mSessionHistory)); + if (shPrivate) { + shPrivate->SetRootDocShell(nullptr); + } + + if (--gDocShellCount == 0) { + NS_IF_RELEASE(sURIFixup); + } + + if (gDocShellLeakLog) { + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p destroyed\n", this)); + } + +#ifdef DEBUG + // We're counting the number of |nsDocShells| to help find leaks + --gNumberOfDocShells; + if (!PR_GetEnv("MOZ_QUIET")) { + printf_stderr("--DOCSHELL %p == %ld [pid = %d] [id = %llu]\n", + (void*)this, + gNumberOfDocShells, + getpid(), + AssertedCast<unsigned long long>(mHistoryID)); + } +#endif +} + +nsresult +nsDocShell::Init() +{ + nsresult rv = nsDocLoader::Init(); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(mLoadGroup, "Something went wrong!"); + + mContentListener = new nsDSURIContentListener(this); + rv = mContentListener->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + // We want to hold a strong ref to the loadgroup, so it better hold a weak + // ref to us... use an InterfaceRequestorProxy to do this. + nsCOMPtr<nsIInterfaceRequestor> proxy = + new InterfaceRequestorProxy(static_cast<nsIInterfaceRequestor*>(this)); + mLoadGroup->SetNotificationCallbacks(proxy); + + rv = nsDocLoader::AddDocLoaderAsChildOfRoot(this); + NS_ENSURE_SUCCESS(rv, rv); + + // Add as |this| a progress listener to itself. A little weird, but + // simpler than reproducing all the listener-notification logic in + // overrides of the various methods via which nsDocLoader can be + // notified. Note that this holds an nsWeakPtr to ourselves, so it's ok. + return AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT | + nsIWebProgress::NOTIFY_STATE_NETWORK); +} + +void +nsDocShell::DestroyChildren() +{ + nsCOMPtr<nsIDocShellTreeItem> shell; + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + shell = do_QueryObject(iter.GetNext()); + NS_ASSERTION(shell, "docshell has null child"); + + if (shell) { + shell->SetTreeOwner(nullptr); + } + } + + nsDocLoader::DestroyChildren(); +} + +NS_IMPL_ADDREF_INHERITED(nsDocShell, nsDocLoader) +NS_IMPL_RELEASE_INHERITED(nsDocShell, nsDocLoader) + +NS_INTERFACE_MAP_BEGIN(nsDocShell) + NS_INTERFACE_MAP_ENTRY(nsIDocShell) + NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeItem) + NS_INTERFACE_MAP_ENTRY(nsIWebNavigation) + NS_INTERFACE_MAP_ENTRY(nsIBaseWindow) + NS_INTERFACE_MAP_ENTRY(nsIScrollable) + NS_INTERFACE_MAP_ENTRY(nsITextScroll) + NS_INTERFACE_MAP_ENTRY(nsIDocCharset) + NS_INTERFACE_MAP_ENTRY(nsIRefreshURI) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIContentViewerContainer) + NS_INTERFACE_MAP_ENTRY(nsIWebPageDescriptor) + NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider) + NS_INTERFACE_MAP_ENTRY(nsILoadContext) + NS_INTERFACE_MAP_ENTRY(nsIWebShellServices) + NS_INTERFACE_MAP_ENTRY(nsILinkHandler) + NS_INTERFACE_MAP_ENTRY(nsIClipboardCommands) + NS_INTERFACE_MAP_ENTRY(nsIDOMStorageManager) + NS_INTERFACE_MAP_ENTRY(nsINetworkInterceptController) + NS_INTERFACE_MAP_ENTRY(nsIDeprecationWarner) +NS_INTERFACE_MAP_END_INHERITING(nsDocLoader) + +NS_IMETHODIMP +nsDocShell::GetInterface(const nsIID& aIID, void** aSink) +{ + NS_PRECONDITION(aSink, "null out param"); + + *aSink = nullptr; + + if (aIID.Equals(NS_GET_IID(nsICommandManager))) { + NS_ENSURE_SUCCESS(EnsureCommandHandler(), NS_ERROR_FAILURE); + *aSink = mCommandManager; + } else if (aIID.Equals(NS_GET_IID(nsIURIContentListener))) { + *aSink = mContentListener; + } else if ((aIID.Equals(NS_GET_IID(nsIScriptGlobalObject)) || + aIID.Equals(NS_GET_IID(nsIGlobalObject)) || + aIID.Equals(NS_GET_IID(nsPIDOMWindowOuter)) || + aIID.Equals(NS_GET_IID(mozIDOMWindowProxy)) || + aIID.Equals(NS_GET_IID(nsIDOMWindow)) || + aIID.Equals(NS_GET_IID(nsIDOMWindowInternal))) && + NS_SUCCEEDED(EnsureScriptEnvironment())) { + return mScriptGlobal->QueryInterface(aIID, aSink); + } else if (aIID.Equals(NS_GET_IID(nsIDOMDocument)) && + NS_SUCCEEDED(EnsureContentViewer())) { + mContentViewer->GetDOMDocument((nsIDOMDocument**)aSink); + return *aSink ? NS_OK : NS_NOINTERFACE; + } else if (aIID.Equals(NS_GET_IID(nsIDocument)) && + NS_SUCCEEDED(EnsureContentViewer())) { + nsCOMPtr<nsIDocument> doc = mContentViewer->GetDocument(); + doc.forget(aSink); + return *aSink ? NS_OK : NS_NOINTERFACE; + } else if (aIID.Equals(NS_GET_IID(nsIApplicationCacheContainer))) { + *aSink = nullptr; + + // Return application cache associated with this docshell, if any + + nsCOMPtr<nsIContentViewer> contentViewer; + GetContentViewer(getter_AddRefs(contentViewer)); + if (!contentViewer) { + return NS_ERROR_NO_INTERFACE; + } + + nsCOMPtr<nsIDOMDocument> domDoc; + contentViewer->GetDOMDocument(getter_AddRefs(domDoc)); + NS_ASSERTION(domDoc, "Should have a document."); + if (!domDoc) { + return NS_ERROR_NO_INTERFACE; + } + +#if defined(DEBUG) + MOZ_LOG(gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]: returning app cache container %p", + this, domDoc.get())); +#endif + return domDoc->QueryInterface(aIID, aSink); + } else if (aIID.Equals(NS_GET_IID(nsIPrompt)) && + NS_SUCCEEDED(EnsureScriptEnvironment())) { + nsresult rv; + nsCOMPtr<nsIWindowWatcher> wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the an auth prompter for our window so that the parenting + // of the dialogs works as it should when using tabs. + nsIPrompt* prompt; + rv = wwatch->GetNewPrompter(mScriptGlobal->AsOuter(), &prompt); + NS_ENSURE_SUCCESS(rv, rv); + + *aSink = prompt; + return NS_OK; + } else if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) || + aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) { + return NS_SUCCEEDED(GetAuthPrompt(PROMPT_NORMAL, aIID, aSink)) ? + NS_OK : NS_NOINTERFACE; + } else if (aIID.Equals(NS_GET_IID(nsISHistory))) { + nsCOMPtr<nsISHistory> shistory; + nsresult rv = GetSessionHistory(getter_AddRefs(shistory)); + if (NS_SUCCEEDED(rv) && shistory) { + shistory.forget(aSink); + return NS_OK; + } + return NS_NOINTERFACE; + } else if (aIID.Equals(NS_GET_IID(nsIWebBrowserFind))) { + nsresult rv = EnsureFind(); + if (NS_FAILED(rv)) { + return rv; + } + + *aSink = mFind; + NS_ADDREF((nsISupports*)*aSink); + return NS_OK; + } else if (aIID.Equals(NS_GET_IID(nsIEditingSession))) { + nsCOMPtr<nsIEditingSession> es; + GetEditingSession(getter_AddRefs(es)); + es.forget(aSink); + return *aSink ? NS_OK : NS_NOINTERFACE; + } else if (aIID.Equals(NS_GET_IID(nsIClipboardDragDropHookList)) && + NS_SUCCEEDED(EnsureTransferableHookData())) { + *aSink = mTransferableHookData; + NS_ADDREF((nsISupports*)*aSink); + return NS_OK; + } else if (aIID.Equals(NS_GET_IID(nsISelectionDisplay))) { + nsIPresShell* shell = GetPresShell(); + if (shell) { + return shell->QueryInterface(aIID, aSink); + } + } else if (aIID.Equals(NS_GET_IID(nsIDocShellTreeOwner))) { + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + nsresult rv = GetTreeOwner(getter_AddRefs(treeOwner)); + if (NS_SUCCEEDED(rv) && treeOwner) { + return treeOwner->QueryInterface(aIID, aSink); + } + } else if (aIID.Equals(NS_GET_IID(nsITabChild))) { + *aSink = GetTabChild().take(); + return *aSink ? NS_OK : NS_ERROR_FAILURE; + } else if (aIID.Equals(NS_GET_IID(nsIContentFrameMessageManager))) { + nsCOMPtr<nsITabChild> tabChild = + do_GetInterface(static_cast<nsIDocShell*>(this)); + nsCOMPtr<nsIContentFrameMessageManager> mm; + if (tabChild) { + tabChild->GetMessageManager(getter_AddRefs(mm)); + } else { + if (nsPIDOMWindowOuter* win = GetWindow()) { + mm = do_QueryInterface(win->GetParentTarget()); + } + } + *aSink = mm.get(); + } else { + return nsDocLoader::GetInterface(aIID, aSink); + } + + NS_IF_ADDREF(((nsISupports*)*aSink)); + return *aSink ? NS_OK : NS_NOINTERFACE; +} + +uint32_t +nsDocShell::ConvertDocShellLoadInfoToLoadType( + nsDocShellInfoLoadType aDocShellLoadType) +{ + uint32_t loadType = LOAD_NORMAL; + + switch (aDocShellLoadType) { + case nsIDocShellLoadInfo::loadNormal: + loadType = LOAD_NORMAL; + break; + case nsIDocShellLoadInfo::loadNormalReplace: + loadType = LOAD_NORMAL_REPLACE; + break; + case nsIDocShellLoadInfo::loadNormalExternal: + loadType = LOAD_NORMAL_EXTERNAL; + break; + case nsIDocShellLoadInfo::loadHistory: + loadType = LOAD_HISTORY; + break; + case nsIDocShellLoadInfo::loadNormalBypassCache: + loadType = LOAD_NORMAL_BYPASS_CACHE; + break; + case nsIDocShellLoadInfo::loadNormalBypassProxy: + loadType = LOAD_NORMAL_BYPASS_PROXY; + break; + case nsIDocShellLoadInfo::loadNormalBypassProxyAndCache: + loadType = LOAD_NORMAL_BYPASS_PROXY_AND_CACHE; + break; + case nsIDocShellLoadInfo::loadNormalAllowMixedContent: + loadType = LOAD_NORMAL_ALLOW_MIXED_CONTENT; + break; + case nsIDocShellLoadInfo::loadReloadNormal: + loadType = LOAD_RELOAD_NORMAL; + break; + case nsIDocShellLoadInfo::loadReloadCharsetChange: + loadType = LOAD_RELOAD_CHARSET_CHANGE; + break; + case nsIDocShellLoadInfo::loadReloadBypassCache: + loadType = LOAD_RELOAD_BYPASS_CACHE; + break; + case nsIDocShellLoadInfo::loadReloadBypassProxy: + loadType = LOAD_RELOAD_BYPASS_PROXY; + break; + case nsIDocShellLoadInfo::loadReloadBypassProxyAndCache: + loadType = LOAD_RELOAD_BYPASS_PROXY_AND_CACHE; + break; + case nsIDocShellLoadInfo::loadLink: + loadType = LOAD_LINK; + break; + case nsIDocShellLoadInfo::loadRefresh: + loadType = LOAD_REFRESH; + break; + case nsIDocShellLoadInfo::loadBypassHistory: + loadType = LOAD_BYPASS_HISTORY; + break; + case nsIDocShellLoadInfo::loadStopContent: + loadType = LOAD_STOP_CONTENT; + break; + case nsIDocShellLoadInfo::loadStopContentAndReplace: + loadType = LOAD_STOP_CONTENT_AND_REPLACE; + break; + case nsIDocShellLoadInfo::loadPushState: + loadType = LOAD_PUSHSTATE; + break; + case nsIDocShellLoadInfo::loadReplaceBypassCache: + loadType = LOAD_REPLACE_BYPASS_CACHE; + break; + case nsIDocShellLoadInfo::loadReloadMixedContent: + loadType = LOAD_RELOAD_ALLOW_MIXED_CONTENT; + break; + default: + NS_NOTREACHED("Unexpected nsDocShellInfoLoadType value"); + } + + return loadType; +} + +nsDocShellInfoLoadType +nsDocShell::ConvertLoadTypeToDocShellLoadInfo(uint32_t aLoadType) +{ + nsDocShellInfoLoadType docShellLoadType = nsIDocShellLoadInfo::loadNormal; + switch (aLoadType) { + case LOAD_NORMAL: + docShellLoadType = nsIDocShellLoadInfo::loadNormal; + break; + case LOAD_NORMAL_REPLACE: + docShellLoadType = nsIDocShellLoadInfo::loadNormalReplace; + break; + case LOAD_NORMAL_EXTERNAL: + docShellLoadType = nsIDocShellLoadInfo::loadNormalExternal; + break; + case LOAD_NORMAL_BYPASS_CACHE: + docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassCache; + break; + case LOAD_NORMAL_BYPASS_PROXY: + docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassProxy; + break; + case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE: + docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassProxyAndCache; + break; + case LOAD_NORMAL_ALLOW_MIXED_CONTENT: + docShellLoadType = nsIDocShellLoadInfo::loadNormalAllowMixedContent; + break; + case LOAD_HISTORY: + docShellLoadType = nsIDocShellLoadInfo::loadHistory; + break; + case LOAD_RELOAD_NORMAL: + docShellLoadType = nsIDocShellLoadInfo::loadReloadNormal; + break; + case LOAD_RELOAD_CHARSET_CHANGE: + docShellLoadType = nsIDocShellLoadInfo::loadReloadCharsetChange; + break; + case LOAD_RELOAD_BYPASS_CACHE: + docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassCache; + break; + case LOAD_RELOAD_BYPASS_PROXY: + docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassProxy; + break; + case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: + docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassProxyAndCache; + break; + case LOAD_LINK: + docShellLoadType = nsIDocShellLoadInfo::loadLink; + break; + case LOAD_REFRESH: + docShellLoadType = nsIDocShellLoadInfo::loadRefresh; + break; + case LOAD_BYPASS_HISTORY: + case LOAD_ERROR_PAGE: + docShellLoadType = nsIDocShellLoadInfo::loadBypassHistory; + break; + case LOAD_STOP_CONTENT: + docShellLoadType = nsIDocShellLoadInfo::loadStopContent; + break; + case LOAD_STOP_CONTENT_AND_REPLACE: + docShellLoadType = nsIDocShellLoadInfo::loadStopContentAndReplace; + break; + case LOAD_PUSHSTATE: + docShellLoadType = nsIDocShellLoadInfo::loadPushState; + break; + case LOAD_REPLACE_BYPASS_CACHE: + docShellLoadType = nsIDocShellLoadInfo::loadReplaceBypassCache; + break; + case LOAD_RELOAD_ALLOW_MIXED_CONTENT: + docShellLoadType = nsIDocShellLoadInfo::loadReloadMixedContent; + break; + default: + NS_NOTREACHED("Unexpected load type value"); + } + + return docShellLoadType; +} + +NS_IMETHODIMP +nsDocShell::LoadURI(nsIURI* aURI, + nsIDocShellLoadInfo* aLoadInfo, + uint32_t aLoadFlags, + bool aFirstParty) +{ + NS_PRECONDITION(aLoadInfo || (aLoadFlags & EXTRA_LOAD_FLAGS) == 0, + "Unexpected flags"); + NS_PRECONDITION((aLoadFlags & 0xf) == 0, "Should not have these flags set"); + + // Note: we allow loads to get through here even if mFiredUnloadEvent is + // true; that case will get handled in LoadInternal or LoadHistoryEntry, + // so we pass false as the second parameter to IsNavigationAllowed. + // However, we don't allow the page to change location *in the middle of* + // firing beforeunload, so we do need to check if *beforeunload* is currently + // firing, so we call IsNavigationAllowed rather than just IsPrintingOrPP. + if (!IsNavigationAllowed(true, false)) { + return NS_OK; // JS may not handle returning of an error code + } + + nsCOMPtr<nsIURI> referrer; + nsCOMPtr<nsIURI> originalURI; + bool loadReplace = false; + nsCOMPtr<nsIInputStream> postStream; + nsCOMPtr<nsIInputStream> headersStream; + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + bool inheritPrincipal = false; + bool principalIsExplicit = false; + bool sendReferrer = true; + uint32_t referrerPolicy = mozilla::net::RP_Default; + bool isSrcdoc = false; + nsCOMPtr<nsISHEntry> shEntry; + nsXPIDLString target; + nsAutoString srcdoc; + nsCOMPtr<nsIDocShell> sourceDocShell; + nsCOMPtr<nsIURI> baseURI; + + uint32_t loadType = MAKE_LOAD_TYPE(LOAD_NORMAL, aLoadFlags); + + NS_ENSURE_ARG(aURI); + + if (!StartupTimeline::HasRecord(StartupTimeline::FIRST_LOAD_URI) && + mItemType == typeContent && !NS_IsAboutBlank(aURI)) { + StartupTimeline::RecordOnce(StartupTimeline::FIRST_LOAD_URI); + } + + // Extract the info from the DocShellLoadInfo struct... + if (aLoadInfo) { + aLoadInfo->GetReferrer(getter_AddRefs(referrer)); + aLoadInfo->GetOriginalURI(getter_AddRefs(originalURI)); + aLoadInfo->GetLoadReplace(&loadReplace); + nsDocShellInfoLoadType lt = nsIDocShellLoadInfo::loadNormal; + aLoadInfo->GetLoadType(<); + // Get the appropriate loadType from nsIDocShellLoadInfo type + loadType = ConvertDocShellLoadInfoToLoadType(lt); + + aLoadInfo->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal)); + aLoadInfo->GetInheritPrincipal(&inheritPrincipal); + aLoadInfo->GetPrincipalIsExplicit(&principalIsExplicit); + aLoadInfo->GetSHEntry(getter_AddRefs(shEntry)); + aLoadInfo->GetTarget(getter_Copies(target)); + aLoadInfo->GetPostDataStream(getter_AddRefs(postStream)); + aLoadInfo->GetHeadersStream(getter_AddRefs(headersStream)); + aLoadInfo->GetSendReferrer(&sendReferrer); + aLoadInfo->GetReferrerPolicy(&referrerPolicy); + aLoadInfo->GetIsSrcdocLoad(&isSrcdoc); + aLoadInfo->GetSrcdocData(srcdoc); + aLoadInfo->GetSourceDocShell(getter_AddRefs(sourceDocShell)); + aLoadInfo->GetBaseURI(getter_AddRefs(baseURI)); + } + +#if defined(DEBUG) + if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) { + nsAutoCString uristr; + aURI->GetAsciiSpec(uristr); + MOZ_LOG(gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]: loading %s with flags 0x%08x", + this, uristr.get(), aLoadFlags)); + } +#endif + + if (!shEntry && + !LOAD_TYPE_HAS_FLAGS(loadType, LOAD_FLAGS_REPLACE_HISTORY)) { + // First verify if this is a subframe. + nsCOMPtr<nsIDocShellTreeItem> parentAsItem; + GetSameTypeParent(getter_AddRefs(parentAsItem)); + nsCOMPtr<nsIDocShell> parentDS(do_QueryInterface(parentAsItem)); + uint32_t parentLoadType; + + if (parentDS && parentDS != static_cast<nsIDocShell*>(this)) { + /* OK. It is a subframe. Checkout the + * parent's loadtype. If the parent was loaded thro' a history + * mechanism, then get the SH entry for the child from the parent. + * This is done to restore frameset navigation while going back/forward. + * If the parent was loaded through any other loadType, set the + * child's loadType too accordingly, so that session history does not + * get confused. + */ + + // Get the parent's load type + parentDS->GetLoadType(&parentLoadType); + + // Get the ShEntry for the child from the parent + nsCOMPtr<nsISHEntry> currentSH; + bool oshe = false; + parentDS->GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe); + bool dynamicallyAddedChild = mDynamicallyCreated; + if (!dynamicallyAddedChild && !oshe && currentSH) { + currentSH->HasDynamicallyAddedChild(&dynamicallyAddedChild); + } + if (!dynamicallyAddedChild) { + // Only use the old SHEntry, if we're sure enough that + // it wasn't originally for some other frame. + parentDS->GetChildSHEntry(mChildOffset, getter_AddRefs(shEntry)); + } + + // Make some decisions on the child frame's loadType based on the + // parent's loadType. + if (!mCurrentURI) { + // This is a newly created frame. Check for exception cases first. + // By default the subframe will inherit the parent's loadType. + if (shEntry && (parentLoadType == LOAD_NORMAL || + parentLoadType == LOAD_LINK || + parentLoadType == LOAD_NORMAL_EXTERNAL)) { + // 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 load type to + // LOAD_BYPASS_HISTORY. + bool inOnLoadHandler = false; + parentDS->GetIsExecutingOnLoadHandler(&inOnLoadHandler); + if (inOnLoadHandler) { + loadType = LOAD_NORMAL_REPLACE; + shEntry = nullptr; + } + } else if (parentLoadType == LOAD_REFRESH) { + // Clear shEntry. For refresh loads, we have to load + // what comes thro' the pipe, not what's in history. + shEntry = nullptr; + } else if ((parentLoadType == LOAD_BYPASS_HISTORY) || + (shEntry && + ((parentLoadType & LOAD_CMD_HISTORY) || + (parentLoadType == LOAD_RELOAD_NORMAL) || + (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE)))) { + // If the parent url, bypassed history or was loaded from + // history, pass on the parent's loadType to the new child + // frame too, so that the child frame will also + // avoid getting into history. + loadType = parentLoadType; + } else if (parentLoadType == LOAD_ERROR_PAGE) { + // If the parent document is an error page, we don't + // want to update global/session history. However, + // this child frame is not an error page. + loadType = LOAD_BYPASS_HISTORY; + } else if ((parentLoadType == LOAD_RELOAD_BYPASS_CACHE) || + (parentLoadType == LOAD_RELOAD_BYPASS_PROXY) || + (parentLoadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE)) { + // the new frame should inherit the parent's load type so that it also + // bypasses the cache and/or proxy + loadType = parentLoadType; + } + } else { + // This is a pre-existing subframe. If the load was not originally + // initiated by session history, (if (!shEntry) condition succeeded) and + // mCurrentURI is not null, it is possible that a parent's onLoadHandler + // or even self's onLoadHandler is loading a new page in this child. + // Check parent's and self's busy flag and if it is set, we don't want + // this onLoadHandler load to get in to session history. + uint32_t parentBusy = BUSY_FLAGS_NONE; + uint32_t selfBusy = BUSY_FLAGS_NONE; + parentDS->GetBusyFlags(&parentBusy); + GetBusyFlags(&selfBusy); + if (parentBusy & BUSY_FLAGS_BUSY || + selfBusy & BUSY_FLAGS_BUSY) { + loadType = LOAD_NORMAL_REPLACE; + shEntry = nullptr; + } + } + } // parentDS + else { + // This is the root docshell. If we got here while + // executing an onLoad Handler,this load will not go + // into session history. + bool inOnLoadHandler = false; + GetIsExecutingOnLoadHandler(&inOnLoadHandler); + if (inOnLoadHandler) { + loadType = LOAD_NORMAL_REPLACE; + } + } + } // !shEntry + + if (shEntry) { +#ifdef DEBUG + MOZ_LOG(gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]: loading from session history", this)); +#endif + + return LoadHistoryEntry(shEntry, loadType); + } + + // On history navigation via Back/Forward buttons, don't execute + // automatic JavaScript redirection such as |location.href = ...| or + // |window.open()| + // + // LOAD_NORMAL: window.open(...) etc. + // LOAD_STOP_CONTENT: location.href = ..., location.assign(...) + if ((loadType == LOAD_NORMAL || loadType == LOAD_STOP_CONTENT) && + ShouldBlockLoadingForBackButton()) { + return NS_OK; + } + + // Perform the load... + + // We need a principalToInherit. + // + // If principalIsExplicit is not set there are 4 possibilities: + // (1) If the system principal or an expanded principal was passed + // in and we're a typeContent docshell, inherit the principal + // from the current document instead. + // (2) In all other cases when the principal passed in is not null, + // use that principal. + // (3) If the caller has allowed inheriting from the current document, + // or if we're being called from system code (eg chrome JS or pure + // C++) then inheritPrincipal should be true and InternalLoad will get + // a principal from the current document. If none of these things are + // true, then + // (4) we don't pass a principal into the channel, and a principal will be + // created later from the channel's internal data. + // + // If principalIsExplicit *is* set, there are 4 possibilities + // (1) If the system principal or an expanded principal was passed in + // and we're a typeContent docshell, return an error. + // (2) In all other cases when the principal passed in is not null, + // use that principal. + // (3) If the caller has allowed inheriting from the current document, + // then inheritPrincipal should be true and InternalLoad will get + // a principal from the current document. If none of these things are + // true, then + // (4) we dont' pass a principal into the channel, and a principal will be + // created later from the channel's internal data. + nsCOMPtr<nsIPrincipal> principalToInherit = triggeringPrincipal; + if (principalToInherit && mItemType != typeChrome) { + if (nsContentUtils::IsSystemPrincipal(principalToInherit)) { + if (principalIsExplicit) { + return NS_ERROR_DOM_SECURITY_ERR; + } + principalToInherit = nullptr; + inheritPrincipal = true; + } else if (nsContentUtils::IsExpandedPrincipal(principalToInherit)) { + if (principalIsExplicit) { + return NS_ERROR_DOM_SECURITY_ERR; + } + // Don't inherit from the current page. Just do the safe thing + // and pretend that we were loaded by a nullprincipal. + // + // We didn't inherit OriginAttributes here as ExpandedPrincipal doesn't + // have origin attributes. + principalToInherit = nsNullPrincipal::CreateWithInheritedAttributes(this); + inheritPrincipal = false; + } + } + if (!principalToInherit && !inheritPrincipal && !principalIsExplicit) { + // See if there's system or chrome JS code running + inheritPrincipal = nsContentUtils::LegacyIsCallerChromeOrNativeCode(); + } + + if (aLoadFlags & LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL) { + inheritPrincipal = false; + principalToInherit = nsNullPrincipal::CreateWithInheritedAttributes(this); + } + + // If the triggeringPrincipal is not passed explicitly, we first try to create + // a principal from the referrer, since the referrer URI reflects the web origin + // that triggered the load. If there is no referrer URI, we fall back to using + // the SystemPrincipal. It's safe to assume that no provided triggeringPrincipal + // and no referrer simulate a load that was triggered by the system. + // It's important to note that this block of code needs to appear *after* the block + // where we munge the principalToInherit, because otherwise we would never enter + // code blocks checking if the principalToInherit is null and we will end up with + // a wrong inheritPrincipal flag. + if (!triggeringPrincipal) { + if (referrer) { + nsresult rv = CreatePrincipalFromReferrer(referrer, + getter_AddRefs(triggeringPrincipal)); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + triggeringPrincipal = nsContentUtils::GetSystemPrincipal(); + } + } + + uint32_t flags = 0; + + if (inheritPrincipal) { + flags |= INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL; + } + + if (!sendReferrer) { + flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER; + } + + if (aLoadFlags & LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) { + flags |= INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; + } + + if (aLoadFlags & LOAD_FLAGS_FIRST_LOAD) { + flags |= INTERNAL_LOAD_FLAGS_FIRST_LOAD; + } + + if (aLoadFlags & LOAD_FLAGS_BYPASS_CLASSIFIER) { + flags |= INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER; + } + + if (aLoadFlags & LOAD_FLAGS_FORCE_ALLOW_COOKIES) { + flags |= INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES; + } + + if (isSrcdoc) { + flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC; + } + + return InternalLoad(aURI, + originalURI, + loadReplace, + referrer, + referrerPolicy, + triggeringPrincipal, + principalToInherit, + flags, + target, + nullptr, // No type hint + NullString(), // No forced download + postStream, + headersStream, + loadType, + nullptr, // No SHEntry + aFirstParty, + srcdoc, + sourceDocShell, + baseURI, + nullptr, // No nsIDocShell + nullptr); // No nsIRequest +} + +NS_IMETHODIMP +nsDocShell::LoadStream(nsIInputStream* aStream, nsIURI* aURI, + const nsACString& aContentType, + const nsACString& aContentCharset, + nsIDocShellLoadInfo* aLoadInfo) +{ + NS_ENSURE_ARG(aStream); + + mAllowKeywordFixup = false; + + // if the caller doesn't pass in a URI we need to create a dummy URI. necko + // currently requires a URI in various places during the load. Some consumers + // do as well. + nsCOMPtr<nsIURI> uri = aURI; + if (!uri) { + // HACK ALERT + nsresult rv = NS_OK; + uri = do_CreateInstance(NS_SIMPLEURI_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + // Make sure that the URI spec "looks" like a protocol and path... + // For now, just use a bogus protocol called "internal" + rv = uri->SetSpec(NS_LITERAL_CSTRING("internal:load-stream")); + if (NS_FAILED(rv)) { + return rv; + } + } + + uint32_t loadType = LOAD_NORMAL; + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + if (aLoadInfo) { + nsDocShellInfoLoadType lt = nsIDocShellLoadInfo::loadNormal; + (void)aLoadInfo->GetLoadType(<); + // Get the appropriate LoadType from nsIDocShellLoadInfo type + loadType = ConvertDocShellLoadInfoToLoadType(lt); + aLoadInfo->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal)); + } + + NS_ENSURE_SUCCESS(Stop(nsIWebNavigation::STOP_NETWORK), NS_ERROR_FAILURE); + + mLoadType = loadType; + + if (!triggeringPrincipal) { + triggeringPrincipal = nsContentUtils::GetSystemPrincipal(); + } + + // build up a channel for this stream. + nsCOMPtr<nsIChannel> channel; + nsresult rv = NS_NewInputStreamChannel(getter_AddRefs(channel), + uri, + aStream, + triggeringPrincipal, + nsILoadInfo::SEC_NORMAL, + nsIContentPolicy::TYPE_OTHER, + aContentType, + aContentCharset); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + nsCOMPtr<nsIURILoader> uriLoader(do_GetService(NS_URI_LOADER_CONTRACTID)); + NS_ENSURE_TRUE(uriLoader, NS_ERROR_FAILURE); + + NS_ENSURE_SUCCESS(DoChannelLoad(channel, uriLoader, false), + NS_ERROR_FAILURE); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::CreateLoadInfo(nsIDocShellLoadInfo** aLoadInfo) +{ + nsDocShellLoadInfo* loadInfo = new nsDocShellLoadInfo(); + nsCOMPtr<nsIDocShellLoadInfo> localRef(loadInfo); + + localRef.forget(aLoadInfo); + return NS_OK; +} + +/* + * Reset state to a new content model within the current document and the + * document viewer. Called by the document before initiating an out of band + * document.write(). + */ +NS_IMETHODIMP +nsDocShell::PrepareForNewContentModel() +{ + mEODForCurrentDocument = false; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::FirePageHideNotification(bool aIsUnload) +{ + if (mContentViewer && !mFiredUnloadEvent) { + // Keep an explicit reference since calling PageHide could release + // mContentViewer + nsCOMPtr<nsIContentViewer> contentViewer(mContentViewer); + mFiredUnloadEvent = true; + + if (mTiming) { + mTiming->NotifyUnloadEventStart(); + } + + contentViewer->PageHide(aIsUnload); + + if (mTiming) { + mTiming->NotifyUnloadEventEnd(); + } + + AutoTArray<nsCOMPtr<nsIDocShell>, 8> kids; + uint32_t n = mChildList.Length(); + kids.SetCapacity(n); + for (uint32_t i = 0; i < n; i++) { + kids.AppendElement(do_QueryInterface(ChildAt(i))); + } + + n = kids.Length(); + for (uint32_t i = 0; i < n; ++i) { + if (kids[i]) { + kids[i]->FirePageHideNotification(aIsUnload); + } + } + // Now make sure our editor, if any, is detached before we go + // any farther. + DetachEditorFromWindow(); + } + + return NS_OK; +} + +void +nsDocShell::MaybeInitTiming() +{ + if (mTiming && !mBlankTiming) { + return; + } + + if (mScriptGlobal && mBlankTiming) { + nsPIDOMWindowInner* innerWin = + mScriptGlobal->AsOuter()->GetCurrentInnerWindow(); + if (innerWin && innerWin->GetPerformance()) { + mTiming = innerWin->GetPerformance()->GetDOMTiming(); + mBlankTiming = false; + } + } + + if (!mTiming) { + mTiming = new nsDOMNavigationTiming(); + } + + mTiming->NotifyNavigationStart( + mIsActive ? nsDOMNavigationTiming::DocShellState::eActive + : nsDOMNavigationTiming::DocShellState::eInactive); +} + +// +// Bug 13871: Prevent frameset spoofing +// +// This routine answers: 'Is origin's document from same domain as +// target's document?' +// +// file: uris are considered the same domain for the purpose of +// frame navigation regardless of script accessibility (bug 420425) +// +/* static */ bool +nsDocShell::ValidateOrigin(nsIDocShellTreeItem* aOriginTreeItem, + nsIDocShellTreeItem* aTargetTreeItem) +{ + // We want to bypass this check for chrome callers, but only if there's + // JS on the stack. System callers still need to do it. + if (nsContentUtils::GetCurrentJSContext() && + nsContentUtils::IsCallerChrome()) { + return true; + } + + MOZ_ASSERT(aOriginTreeItem && aTargetTreeItem, "need two docshells"); + + // Get origin document principal + nsCOMPtr<nsIDocument> originDocument = aOriginTreeItem->GetDocument(); + NS_ENSURE_TRUE(originDocument, false); + + // Get target principal + nsCOMPtr<nsIDocument> targetDocument = aTargetTreeItem->GetDocument(); + NS_ENSURE_TRUE(targetDocument, false); + + bool equal; + nsresult rv = originDocument->NodePrincipal()->Equals( + targetDocument->NodePrincipal(), &equal); + if (NS_SUCCEEDED(rv) && equal) { + return true; + } + + // Not strictly equal, special case if both are file: uris + bool originIsFile = false; + bool targetIsFile = false; + nsCOMPtr<nsIURI> originURI; + nsCOMPtr<nsIURI> targetURI; + nsCOMPtr<nsIURI> innerOriginURI; + nsCOMPtr<nsIURI> innerTargetURI; + + rv = originDocument->NodePrincipal()->GetURI(getter_AddRefs(originURI)); + if (NS_SUCCEEDED(rv) && originURI) { + innerOriginURI = NS_GetInnermostURI(originURI); + } + + rv = targetDocument->NodePrincipal()->GetURI(getter_AddRefs(targetURI)); + if (NS_SUCCEEDED(rv) && targetURI) { + innerTargetURI = NS_GetInnermostURI(targetURI); + } + + return innerOriginURI && innerTargetURI && + NS_SUCCEEDED(innerOriginURI->SchemeIs("file", &originIsFile)) && + NS_SUCCEEDED(innerTargetURI->SchemeIs("file", &targetIsFile)) && + originIsFile && targetIsFile; +} + +nsresult +nsDocShell::GetEldestPresContext(nsPresContext** aPresContext) +{ + NS_ENSURE_ARG_POINTER(aPresContext); + *aPresContext = nullptr; + + nsCOMPtr<nsIContentViewer> viewer = mContentViewer; + while (viewer) { + nsCOMPtr<nsIContentViewer> prevViewer; + viewer->GetPreviousViewer(getter_AddRefs(prevViewer)); + if (!prevViewer) { + return viewer->GetPresContext(aPresContext); + } + viewer = prevViewer; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetPresContext(nsPresContext** aPresContext) +{ + NS_ENSURE_ARG_POINTER(aPresContext); + *aPresContext = nullptr; + + if (!mContentViewer) { + return NS_OK; + } + + return mContentViewer->GetPresContext(aPresContext); +} + +NS_IMETHODIMP_(nsIPresShell*) +nsDocShell::GetPresShell() +{ + RefPtr<nsPresContext> presContext; + (void)GetPresContext(getter_AddRefs(presContext)); + return presContext ? presContext->GetPresShell() : nullptr; +} + +NS_IMETHODIMP +nsDocShell::GetEldestPresShell(nsIPresShell** aPresShell) +{ + nsresult rv = NS_OK; + + NS_ENSURE_ARG_POINTER(aPresShell); + *aPresShell = nullptr; + + RefPtr<nsPresContext> presContext; + (void)GetEldestPresContext(getter_AddRefs(presContext)); + + if (presContext) { + NS_IF_ADDREF(*aPresShell = presContext->GetPresShell()); + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::GetContentViewer(nsIContentViewer** aContentViewer) +{ + NS_ENSURE_ARG_POINTER(aContentViewer); + + *aContentViewer = mContentViewer; + NS_IF_ADDREF(*aContentViewer); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetChromeEventHandler(nsIDOMEventTarget* aChromeEventHandler) +{ + // Weak reference. Don't addref. + nsCOMPtr<EventTarget> handler = do_QueryInterface(aChromeEventHandler); + mChromeEventHandler = handler.get(); + + if (mScriptGlobal) { + mScriptGlobal->SetChromeEventHandler(mChromeEventHandler); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetChromeEventHandler(nsIDOMEventTarget** aChromeEventHandler) +{ + NS_ENSURE_ARG_POINTER(aChromeEventHandler); + nsCOMPtr<EventTarget> handler = mChromeEventHandler; + handler.forget(aChromeEventHandler); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCurrentURI(nsIURI* aURI) +{ + // Note that securityUI will set STATE_IS_INSECURE, even if + // the scheme of |aURI| is "https". + SetCurrentURI(aURI, nullptr, true, 0); + return NS_OK; +} + +bool +nsDocShell::SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest, + bool aFireOnLocationChange, uint32_t aLocationFlags) +{ + if (gDocShellLeakLog && MOZ_LOG_TEST(gDocShellLeakLog, LogLevel::Debug)) { + PR_LogPrint("DOCSHELL %p SetCurrentURI %s\n", + this, aURI ? aURI->GetSpecOrDefault().get() : ""); + } + + // We don't want to send a location change when we're displaying an error + // page, and we don't want to change our idea of "current URI" either + if (mLoadType == LOAD_ERROR_PAGE) { + return false; + } + + mCurrentURI = NS_TryToMakeImmutable(aURI); + + if (!NS_IsAboutBlank(mCurrentURI)) { + mHasLoadedNonBlankURI = true; + } + + bool isRoot = false; // Is this the root docshell + bool isSubFrame = false; // Is this a subframe navigation? + + nsCOMPtr<nsIDocShellTreeItem> root; + + GetSameTypeRootTreeItem(getter_AddRefs(root)); + if (root.get() == static_cast<nsIDocShellTreeItem*>(this)) { + // This is the root docshell + isRoot = true; + } + if (mLSHE) { + mLSHE->GetIsSubFrame(&isSubFrame); + } + + if (!isSubFrame && !isRoot) { + /* + * We don't want to send OnLocationChange notifications when + * a subframe is being loaded for the first time, while + * visiting a frameset page + */ + return false; + } + + if (aFireOnLocationChange) { + FireOnLocationChange(this, aRequest, aURI, aLocationFlags); + } + return !aFireOnLocationChange; +} + +NS_IMETHODIMP +nsDocShell::GetCharset(nsACString& aCharset) +{ + aCharset.Truncate(); + + nsIPresShell* presShell = GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + nsIDocument* doc = presShell->GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + aCharset = doc->GetDocumentCharacterSet(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GatherCharsetMenuTelemetry() +{ + nsCOMPtr<nsIContentViewer> viewer; + GetContentViewer(getter_AddRefs(viewer)); + if (!viewer) { + return NS_OK; + } + + nsIDocument* doc = viewer->GetDocument(); + if (!doc || doc->WillIgnoreCharsetOverride()) { + return NS_OK; + } + + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_USED, true); + + bool isFileURL = false; + nsIURI* url = doc->GetOriginalURI(); + if (url) { + url->SchemeIs("file", &isFileURL); + } + + int32_t charsetSource = doc->GetDocumentCharacterSetSource(); + switch (charsetSource) { + case kCharsetFromTopLevelDomain: + // Unlabeled doc on a domain that we map to a fallback encoding + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 7); + break; + case kCharsetFromFallback: + case kCharsetFromDocTypeDefault: + case kCharsetFromCache: + case kCharsetFromParentFrame: + case kCharsetFromHintPrevDoc: + // Changing charset on an unlabeled doc. + if (isFileURL) { + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 0); + } else { + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 1); + } + break; + case kCharsetFromAutoDetection: + // Changing charset on unlabeled doc where chardet fired + if (isFileURL) { + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 2); + } else { + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 3); + } + break; + case kCharsetFromMetaPrescan: + case kCharsetFromMetaTag: + case kCharsetFromChannel: + // Changing charset on a doc that had a charset label. + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 4); + break; + case kCharsetFromParentForced: + case kCharsetFromUserForced: + // Changing charset on a document that already had an override. + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 5); + break; + case kCharsetFromIrreversibleAutoDetection: + case kCharsetFromOtherComponent: + case kCharsetFromByteOrderMark: + case kCharsetUninitialized: + default: + // Bug. This isn't supposed to happen. + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 6); + break; + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCharset(const nsACString& aCharset) +{ + // set the charset override + return SetForcedCharset(aCharset); +} + +NS_IMETHODIMP +nsDocShell::SetForcedCharset(const nsACString& aCharset) +{ + if (aCharset.IsEmpty()) { + mForcedCharset.Truncate(); + return NS_OK; + } + nsAutoCString encoding; + if (!EncodingUtils::FindEncodingForLabel(aCharset, encoding)) { + // Reject unknown labels + return NS_ERROR_INVALID_ARG; + } + if (!EncodingUtils::IsAsciiCompatible(encoding)) { + // Reject XSS hazards + return NS_ERROR_INVALID_ARG; + } + mForcedCharset = encoding; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetForcedCharset(nsACString& aResult) +{ + aResult = mForcedCharset; + return NS_OK; +} + +void +nsDocShell::SetParentCharset(const nsACString& aCharset, + int32_t aCharsetSource, + nsIPrincipal* aPrincipal) +{ + mParentCharset = aCharset; + mParentCharsetSource = aCharsetSource; + mParentCharsetPrincipal = aPrincipal; +} + +void +nsDocShell::GetParentCharset(nsACString& aCharset, + int32_t* aCharsetSource, + nsIPrincipal** aPrincipal) +{ + aCharset = mParentCharset; + *aCharsetSource = mParentCharsetSource; + NS_IF_ADDREF(*aPrincipal = mParentCharsetPrincipal); +} + +NS_IMETHODIMP +nsDocShell::GetChannelIsUnsafe(bool* aUnsafe) +{ + *aUnsafe = false; + + nsIChannel* channel = GetCurrentDocChannel(); + if (!channel) { + return NS_OK; + } + + nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(channel); + if (!jarChannel) { + return NS_OK; + } + + return jarChannel->GetIsUnsafe(aUnsafe); +} + +NS_IMETHODIMP +nsDocShell::GetHasMixedActiveContentLoaded(bool* aHasMixedActiveContentLoaded) +{ + nsCOMPtr<nsIDocument> doc(GetDocument()); + *aHasMixedActiveContentLoaded = doc && doc->GetHasMixedActiveContentLoaded(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHasMixedActiveContentBlocked(bool* aHasMixedActiveContentBlocked) +{ + nsCOMPtr<nsIDocument> doc(GetDocument()); + *aHasMixedActiveContentBlocked = + doc && doc->GetHasMixedActiveContentBlocked(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHasMixedDisplayContentLoaded(bool* aHasMixedDisplayContentLoaded) +{ + nsCOMPtr<nsIDocument> doc(GetDocument()); + *aHasMixedDisplayContentLoaded = + doc && doc->GetHasMixedDisplayContentLoaded(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHasMixedDisplayContentBlocked( + bool* aHasMixedDisplayContentBlocked) +{ + nsCOMPtr<nsIDocument> doc(GetDocument()); + *aHasMixedDisplayContentBlocked = + doc && doc->GetHasMixedDisplayContentBlocked(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHasTrackingContentBlocked(bool* aHasTrackingContentBlocked) +{ + nsCOMPtr<nsIDocument> doc(GetDocument()); + *aHasTrackingContentBlocked = doc && doc->GetHasTrackingContentBlocked(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHasTrackingContentLoaded(bool* aHasTrackingContentLoaded) +{ + nsCOMPtr<nsIDocument> doc(GetDocument()); + *aHasTrackingContentLoaded = doc && doc->GetHasTrackingContentLoaded(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowPlugins(bool* aAllowPlugins) +{ + NS_ENSURE_ARG_POINTER(aAllowPlugins); + + *aAllowPlugins = mAllowPlugins; + if (!mAllowPlugins) { + return NS_OK; + } + + bool unsafe; + *aAllowPlugins = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowPlugins(bool aAllowPlugins) +{ + mAllowPlugins = aAllowPlugins; + // XXX should enable or disable a plugin host + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowJavascript(bool* aAllowJavascript) +{ + NS_ENSURE_ARG_POINTER(aAllowJavascript); + + *aAllowJavascript = mAllowJavascript; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowJavascript(bool aAllowJavascript) +{ + mAllowJavascript = aAllowJavascript; + RecomputeCanExecuteScripts(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) +{ + NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing); + AssertOriginAttributesMatchPrivateBrowsing(); + *aUsePrivateBrowsing = mPrivateBrowsingId > 0; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) +{ + nsContentUtils::ReportToConsoleNonLocalized( + NS_LITERAL_STRING("Only internal code is allowed to set the usePrivateBrowsing attribute"), + nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("Internal API Used"), + mContentViewer ? mContentViewer->GetDocument() : nullptr); + + if (!CanSetOriginAttributes()) { + bool changed = aUsePrivateBrowsing != (mPrivateBrowsingId > 0); + + return changed ? NS_ERROR_FAILURE : NS_OK; + } + + return SetPrivateBrowsing(aUsePrivateBrowsing); +} + +NS_IMETHODIMP +nsDocShell::SetPrivateBrowsing(bool aUsePrivateBrowsing) +{ + bool changed = aUsePrivateBrowsing != (mPrivateBrowsingId > 0); + if (changed) { + mPrivateBrowsingId = aUsePrivateBrowsing ? 1 : 0; + + if (mItemType != typeChrome) { + mOriginAttributes.SyncAttributesWithPrivateBrowsing(aUsePrivateBrowsing); + } + + if (mAffectPrivateSessionLifetime) { + if (aUsePrivateBrowsing) { + IncreasePrivateDocShellCount(); + } else { + DecreasePrivateDocShellCount(); + } + } + } + + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsILoadContext> shell = do_QueryObject(iter.GetNext()); + if (shell) { + shell->SetPrivateBrowsing(aUsePrivateBrowsing); + } + } + + if (changed) { + nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mPrivacyObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr<nsIPrivacyTransitionObserver> obs = do_QueryReferent(ref); + if (!obs) { + mPrivacyObservers.RemoveElement(ref); + } else { + obs->PrivateModeChanged(aUsePrivateBrowsing); + } + } + } + + AssertOriginAttributesMatchPrivateBrowsing(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHasLoadedNonBlankURI(bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = mHasLoadedNonBlankURI; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetUseRemoteTabs(bool* aUseRemoteTabs) +{ + NS_ENSURE_ARG_POINTER(aUseRemoteTabs); + + *aUseRemoteTabs = mUseRemoteTabs; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetRemoteTabs(bool aUseRemoteTabs) +{ +#ifdef MOZ_CRASHREPORTER + if (aUseRemoteTabs) { + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("DOMIPCEnabled"), + NS_LITERAL_CSTRING("1")); + } +#endif + + mUseRemoteTabs = aUseRemoteTabs; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAffectPrivateSessionLifetime(bool aAffectLifetime) +{ + bool change = aAffectLifetime != mAffectPrivateSessionLifetime; + if (change && UsePrivateBrowsing()) { + AssertOriginAttributesMatchPrivateBrowsing(); + if (aAffectLifetime) { + IncreasePrivateDocShellCount(); + } else { + DecreasePrivateDocShellCount(); + } + } + mAffectPrivateSessionLifetime = aAffectLifetime; + + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext()); + if (shell) { + shell->SetAffectPrivateSessionLifetime(aAffectLifetime); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAffectPrivateSessionLifetime(bool* aAffectLifetime) +{ + *aAffectLifetime = mAffectPrivateSessionLifetime; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::AddWeakPrivacyTransitionObserver( + nsIPrivacyTransitionObserver* aObserver) +{ + nsWeakPtr weakObs = do_GetWeakReference(aObserver); + if (!weakObs) { + return NS_ERROR_NOT_AVAILABLE; + } + return mPrivacyObservers.AppendElement(weakObs) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::AddWeakReflowObserver(nsIReflowObserver* aObserver) +{ + nsWeakPtr weakObs = do_GetWeakReference(aObserver); + if (!weakObs) { + return NS_ERROR_FAILURE; + } + return mReflowObservers.AppendElement(weakObs) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::RemoveWeakReflowObserver(nsIReflowObserver* aObserver) +{ + nsWeakPtr obs = do_GetWeakReference(aObserver); + return mReflowObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::NotifyReflowObservers(bool aInterruptible, + DOMHighResTimeStamp aStart, + DOMHighResTimeStamp aEnd) +{ + nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mReflowObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr<nsIReflowObserver> obs = do_QueryReferent(ref); + if (!obs) { + mReflowObservers.RemoveElement(ref); + } else if (aInterruptible) { + obs->ReflowInterruptible(aStart, aEnd); + } else { + obs->Reflow(aStart, aEnd); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowMetaRedirects(bool* aReturn) +{ + NS_ENSURE_ARG_POINTER(aReturn); + + *aReturn = mAllowMetaRedirects; + if (!mAllowMetaRedirects) { + return NS_OK; + } + + bool unsafe; + *aReturn = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowMetaRedirects(bool aValue) +{ + mAllowMetaRedirects = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowSubframes(bool* aAllowSubframes) +{ + NS_ENSURE_ARG_POINTER(aAllowSubframes); + + *aAllowSubframes = mAllowSubframes; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowSubframes(bool aAllowSubframes) +{ + mAllowSubframes = aAllowSubframes; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowImages(bool* aAllowImages) +{ + NS_ENSURE_ARG_POINTER(aAllowImages); + + *aAllowImages = mAllowImages; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowImages(bool aAllowImages) +{ + mAllowImages = aAllowImages; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowMedia(bool* aAllowMedia) +{ + *aAllowMedia = mAllowMedia; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowMedia(bool aAllowMedia) +{ + mAllowMedia = aAllowMedia; + + // Mute or unmute audio contexts attached to the inner window. + if (mScriptGlobal) { + if (nsPIDOMWindowInner* innerWin = + mScriptGlobal->AsOuter()->GetCurrentInnerWindow()) { + if (aAllowMedia) { + innerWin->UnmuteAudioContexts(); + } else { + innerWin->MuteAudioContexts(); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowDNSPrefetch(bool* aAllowDNSPrefetch) +{ + *aAllowDNSPrefetch = mAllowDNSPrefetch; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowDNSPrefetch(bool aAllowDNSPrefetch) +{ + mAllowDNSPrefetch = aAllowDNSPrefetch; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowWindowControl(bool* aAllowWindowControl) +{ + *aAllowWindowControl = mAllowWindowControl; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowWindowControl(bool aAllowWindowControl) +{ + mAllowWindowControl = aAllowWindowControl; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowContentRetargeting(bool* aAllowContentRetargeting) +{ + *aAllowContentRetargeting = mAllowContentRetargeting; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowContentRetargeting(bool aAllowContentRetargeting) +{ + mAllowContentRetargetingOnChildren = aAllowContentRetargeting; + mAllowContentRetargeting = aAllowContentRetargeting; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowContentRetargetingOnChildren( + bool* aAllowContentRetargetingOnChildren) +{ + *aAllowContentRetargetingOnChildren = mAllowContentRetargetingOnChildren; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowContentRetargetingOnChildren( + bool aAllowContentRetargetingOnChildren) +{ + mAllowContentRetargetingOnChildren = aAllowContentRetargetingOnChildren; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetInheritPrivateBrowsingId(bool* aInheritPrivateBrowsingId) +{ + *aInheritPrivateBrowsingId = mInheritPrivateBrowsingId; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetInheritPrivateBrowsingId(bool aInheritPrivateBrowsingId) +{ + mInheritPrivateBrowsingId = aInheritPrivateBrowsingId; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetFullscreenAllowed(bool* aFullscreenAllowed) +{ + NS_ENSURE_ARG_POINTER(aFullscreenAllowed); + + // Browsers and apps have their mFullscreenAllowed retrieved from their + // corresponding iframe in their parent upon creation. + if (mFullscreenAllowed != CHECK_ATTRIBUTES) { + *aFullscreenAllowed = (mFullscreenAllowed == PARENT_ALLOWS); + return NS_OK; + } + + // Assume false until we determine otherwise... + *aFullscreenAllowed = false; + + nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow(); + if (!win) { + return NS_OK; + } + if (nsCOMPtr<Element> frameElement = win->GetFrameElementInternal()) { + if (frameElement->IsXULElement()) { + if (frameElement->HasAttr(kNameSpaceID_None, + nsGkAtoms::disablefullscreen)) { + // Document inside this frame is explicitly disabled. + return NS_OK; + } + } else { + // We do not allow document inside any containing element other + // than iframe to enter fullscreen. + if (frameElement->IsHTMLElement(nsGkAtoms::iframe)) { + // If any ancestor iframe does not have allowfullscreen attribute + // set, then fullscreen is not allowed. + if (!frameElement->HasAttr(kNameSpaceID_None, + nsGkAtoms::allowfullscreen) && + !frameElement->HasAttr(kNameSpaceID_None, + nsGkAtoms::mozallowfullscreen)) { + return NS_OK; + } + } else if (frameElement->IsHTMLElement(nsGkAtoms::embed)) { + // Respect allowfullscreen only if this is a rewritten YouTube embed. + nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent = + do_QueryInterface(frameElement); + if (!objectLoadingContent) { + return NS_OK; + } + nsObjectLoadingContent* olc = + static_cast<nsObjectLoadingContent*>(objectLoadingContent.get()); + if (!olc->IsRewrittenYoutubeEmbed()) { + return NS_OK; + } + // We don't have to check prefixed attributes because Flash does not + // support them. + if (!frameElement->HasAttr(kNameSpaceID_None, + nsGkAtoms::allowfullscreen)) { + return NS_OK; + } + } else { + // neither iframe nor embed + return NS_OK; + } + } + } + + // If we have no parent then we're the root docshell; no ancestor of the + // original docshell doesn't have a allowfullscreen attribute, so + // report fullscreen as allowed. + RefPtr<nsDocShell> parent = GetParentDocshell(); + if (!parent) { + *aFullscreenAllowed = true; + return NS_OK; + } + + // Otherwise, we have a parent, continue the checking for + // mozFullscreenAllowed in the parent docshell's ancestors. + return parent->GetFullscreenAllowed(aFullscreenAllowed); +} + +NS_IMETHODIMP +nsDocShell::SetFullscreenAllowed(bool aFullscreenAllowed) +{ + if (!nsIDocShell::GetIsMozBrowserOrApp()) { + // Only allow setting of fullscreenAllowed on content/process boundaries. + // At non-boundaries the fullscreenAllowed attribute is calculated based on + // whether all enclosing frames have the "mozFullscreenAllowed" attribute + // set to "true". fullscreenAllowed is set at the process boundaries to + // propagate the value of the parent's "mozFullscreenAllowed" attribute + // across process boundaries. + return NS_ERROR_UNEXPECTED; + } + mFullscreenAllowed = (aFullscreenAllowed ? PARENT_ALLOWS : PARENT_PROHIBITS); + return NS_OK; +} + +ScreenOrientationInternal +nsDocShell::OrientationLock() +{ + return mOrientationLock; +} + +void +nsDocShell::SetOrientationLock(ScreenOrientationInternal aOrientationLock) +{ + mOrientationLock = aOrientationLock; +} + +NS_IMETHODIMP +nsDocShell::GetMayEnableCharacterEncodingMenu( + bool* aMayEnableCharacterEncodingMenu) +{ + *aMayEnableCharacterEncodingMenu = false; + if (!mContentViewer) { + return NS_OK; + } + nsIDocument* doc = mContentViewer->GetDocument(); + if (!doc) { + return NS_OK; + } + if (doc->WillIgnoreCharsetOverride()) { + return NS_OK; + } + + *aMayEnableCharacterEncodingMenu = true; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDocShellEnumerator(int32_t aItemType, int32_t aDirection, + nsISimpleEnumerator** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + RefPtr<nsDocShellEnumerator> docShellEnum; + if (aDirection == ENUMERATE_FORWARDS) { + docShellEnum = new nsDocShellForwardsEnumerator; + } else { + docShellEnum = new nsDocShellBackwardsEnumerator; + } + + nsresult rv = docShellEnum->SetEnumDocShellType(aItemType); + if (NS_FAILED(rv)) { + return rv; + } + + rv = docShellEnum->SetEnumerationRootItem((nsIDocShellTreeItem*)this); + if (NS_FAILED(rv)) { + return rv; + } + + rv = docShellEnum->First(); + if (NS_FAILED(rv)) { + return rv; + } + + rv = docShellEnum->QueryInterface(NS_GET_IID(nsISimpleEnumerator), + (void**)aResult); + + return rv; +} + +NS_IMETHODIMP +nsDocShell::GetAppType(uint32_t* aAppType) +{ + *aAppType = mAppType; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAppType(uint32_t aAppType) +{ + mAppType = aAppType; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowAuth(bool* aAllowAuth) +{ + *aAllowAuth = mAllowAuth; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowAuth(bool aAllowAuth) +{ + mAllowAuth = aAllowAuth; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetZoom(float* aZoom) +{ + NS_ENSURE_ARG_POINTER(aZoom); + *aZoom = 1.0f; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetZoom(float aZoom) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShell::GetMarginWidth(int32_t* aWidth) +{ + NS_ENSURE_ARG_POINTER(aWidth); + + *aWidth = mMarginWidth; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetMarginWidth(int32_t aWidth) +{ + mMarginWidth = aWidth; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetMarginHeight(int32_t* aHeight) +{ + NS_ENSURE_ARG_POINTER(aHeight); + + *aHeight = mMarginHeight; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetMarginHeight(int32_t aHeight) +{ + mMarginHeight = aHeight; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetBusyFlags(uint32_t* aBusyFlags) +{ + NS_ENSURE_ARG_POINTER(aBusyFlags); + + *aBusyFlags = mBusyFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::TabToTreeOwner(bool aForward, bool aForDocumentNavigation, bool* aTookFocus) +{ + NS_ENSURE_ARG_POINTER(aTookFocus); + + nsCOMPtr<nsIWebBrowserChromeFocus> chromeFocus = do_GetInterface(mTreeOwner); + if (chromeFocus) { + if (aForward) { + *aTookFocus = NS_SUCCEEDED(chromeFocus->FocusNextElement(aForDocumentNavigation)); + } else { + *aTookFocus = NS_SUCCEEDED(chromeFocus->FocusPrevElement(aForDocumentNavigation)); + } + } else { + *aTookFocus = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSecurityUI(nsISecureBrowserUI** aSecurityUI) +{ + NS_IF_ADDREF(*aSecurityUI = mSecurityUI); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetSecurityUI(nsISecureBrowserUI* aSecurityUI) +{ + mSecurityUI = aSecurityUI; + mSecurityUI->SetDocShell(this); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetUseErrorPages(bool* aUseErrorPages) +{ + *aUseErrorPages = UseErrorPages(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetUseErrorPages(bool aUseErrorPages) +{ + // If mUseErrorPages is set explicitly, stop using sUseErrorPages. + if (mObserveErrorPages) { + mObserveErrorPages = false; + } + mUseErrorPages = aUseErrorPages; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetPreviousTransIndex(int32_t* aPreviousTransIndex) +{ + *aPreviousTransIndex = mPreviousTransIndex; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetLoadedTransIndex(int32_t* aLoadedTransIndex) +{ + *aLoadedTransIndex = mLoadedTransIndex; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::HistoryPurged(int32_t aNumEntries) +{ + // These indices are used for fastback cache eviction, to determine + // which session history entries are candidates for content viewer + // eviction. We need to adjust by the number of entries that we + // just purged from history, so that we look at the right session history + // entries during eviction. + mPreviousTransIndex = std::max(-1, mPreviousTransIndex - aNumEntries); + mLoadedTransIndex = std::max(0, mLoadedTransIndex - aNumEntries); + + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext()); + if (shell) { + shell->HistoryPurged(aNumEntries); + } + } + + return NS_OK; +} + +nsresult +nsDocShell::HistoryTransactionRemoved(int32_t aIndex) +{ + // These indices are used for fastback cache eviction, to determine + // which session history entries are candidates for content viewer + // eviction. We need to adjust by the number of entries that we + // just purged from history, so that we look at the right session history + // entries during eviction. + if (aIndex == mPreviousTransIndex) { + mPreviousTransIndex = -1; + } else if (aIndex < mPreviousTransIndex) { + --mPreviousTransIndex; + } + if (mLoadedTransIndex == aIndex) { + mLoadedTransIndex = 0; + } else if (aIndex < mLoadedTransIndex) { + --mLoadedTransIndex; + } + + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext()); + if (shell) { + static_cast<nsDocShell*>(shell.get())->HistoryTransactionRemoved(aIndex); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetRecordProfileTimelineMarkers(bool aValue) +{ + bool currentValue = nsIDocShell::GetRecordProfileTimelineMarkers(); + if (currentValue == aValue) { + return NS_OK; + } + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines) { + return NS_OK; + } + + if (aValue) { + MOZ_ASSERT(!timelines->HasConsumer(this)); + timelines->AddConsumer(this); + MOZ_ASSERT(timelines->HasConsumer(this)); + UseEntryScriptProfiling(); + } else { + MOZ_ASSERT(timelines->HasConsumer(this)); + timelines->RemoveConsumer(this); + MOZ_ASSERT(!timelines->HasConsumer(this)); + UnuseEntryScriptProfiling(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetRecordProfileTimelineMarkers(bool* aValue) +{ + *aValue = !!mObserved; + return NS_OK; +} + +nsresult +nsDocShell::PopProfileTimelineMarkers( + JSContext* aCx, + JS::MutableHandle<JS::Value> aOut) +{ + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines) { + return NS_OK; + } + + nsTArray<dom::ProfileTimelineMarker> store; + SequenceRooter<dom::ProfileTimelineMarker> rooter(aCx, &store); + + timelines->PopMarkers(this, aCx, store); + + if (!ToJSValue(aCx, store, aOut)) { + JS_ClearPendingException(aCx); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +nsresult +nsDocShell::Now(DOMHighResTimeStamp* aWhen) +{ + bool ignore; + *aWhen = + (TimeStamp::Now() - TimeStamp::ProcessCreation(ignore)).ToMilliseconds(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetWindowDraggingAllowed(bool aValue) +{ + RefPtr<nsDocShell> parent = GetParentDocshell(); + if (!aValue && mItemType == typeChrome && !parent) { + // Window dragging is always allowed for top level + // chrome docshells. + return NS_ERROR_FAILURE; + } + mWindowDraggingAllowed = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetWindowDraggingAllowed(bool* aValue) +{ + // window dragging regions in CSS (-moz-window-drag:drag) + // can be slow. Default behavior is to only allow it for + // chrome top level windows. + RefPtr<nsDocShell> parent = GetParentDocshell(); + if (mItemType == typeChrome && !parent) { + // Top level chrome window + *aValue = true; + } else { + *aValue = mWindowDraggingAllowed; + } + return NS_OK; +} + +nsIDOMStorageManager* +nsDocShell::TopSessionStorageManager() +{ + nsresult rv; + + nsCOMPtr<nsIDocShellTreeItem> topItem; + rv = GetSameTypeRootTreeItem(getter_AddRefs(topItem)); + if (NS_FAILED(rv)) { + return nullptr; + } + + if (!topItem) { + return nullptr; + } + + nsDocShell* topDocShell = static_cast<nsDocShell*>(topItem.get()); + if (topDocShell != this) { + return topDocShell->TopSessionStorageManager(); + } + + if (!mSessionStorageManager) { + mSessionStorageManager = + do_CreateInstance("@mozilla.org/dom/sessionStorage-manager;1"); + } + + return mSessionStorageManager; +} + +NS_IMETHODIMP +nsDocShell::GetSessionStorageForPrincipal(nsIPrincipal* aPrincipal, + const nsAString& aDocumentURI, + bool aCreate, + nsIDOMStorage** aStorage) +{ + nsCOMPtr<nsIDOMStorageManager> manager = TopSessionStorageManager(); + if (!manager) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsPIDOMWindowOuter> domWin = GetWindow(); + + AssertOriginAttributesMatchPrivateBrowsing(); + if (aCreate) { + return manager->CreateStorage(domWin->GetCurrentInnerWindow(), aPrincipal, + aDocumentURI, UsePrivateBrowsing(), aStorage); + } + + return manager->GetStorage(domWin->GetCurrentInnerWindow(), aPrincipal, + UsePrivateBrowsing(), aStorage); +} + +nsresult +nsDocShell::AddSessionStorage(nsIPrincipal* aPrincipal, nsIDOMStorage* aStorage) +{ + RefPtr<DOMStorage> storage = static_cast<DOMStorage*>(aStorage); + if (!storage) { + return NS_ERROR_UNEXPECTED; + } + + nsIPrincipal* storagePrincipal = storage->GetPrincipal(); + if (storagePrincipal != aPrincipal) { + NS_ERROR("Wanting to add a sessionStorage for different principal"); + return NS_ERROR_DOM_SECURITY_ERR; + } + + nsCOMPtr<nsIDOMStorageManager> manager = TopSessionStorageManager(); + if (!manager) { + return NS_ERROR_UNEXPECTED; + } + + return manager->CloneStorage(aStorage); +} + +NS_IMETHODIMP +nsDocShell::GetCurrentDocumentChannel(nsIChannel** aResult) +{ + NS_IF_ADDREF(*aResult = GetCurrentDocChannel()); + return NS_OK; +} + +nsIChannel* +nsDocShell::GetCurrentDocChannel() +{ + if (mContentViewer) { + nsIDocument* doc = mContentViewer->GetDocument(); + if (doc) { + return doc->GetChannel(); + } + } + return nullptr; +} + +NS_IMETHODIMP +nsDocShell::AddWeakScrollObserver(nsIScrollObserver* aObserver) +{ + nsWeakPtr weakObs = do_GetWeakReference(aObserver); + if (!weakObs) { + return NS_ERROR_FAILURE; + } + return mScrollObservers.AppendElement(weakObs) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::RemoveWeakScrollObserver(nsIScrollObserver* aObserver) +{ + nsWeakPtr obs = do_GetWeakReference(aObserver); + return mScrollObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE; +} + +void +nsDocShell::NotifyAsyncPanZoomStarted() +{ + nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref); + if (obs) { + obs->AsyncPanZoomStarted(); + } else { + mScrollObservers.RemoveElement(ref); + } + } +} + +void +nsDocShell::NotifyAsyncPanZoomStopped() +{ + nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref); + if (obs) { + obs->AsyncPanZoomStopped(); + } else { + mScrollObservers.RemoveElement(ref); + } + } +} + +NS_IMETHODIMP +nsDocShell::NotifyScrollObservers() +{ + nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref); + if (obs) { + obs->ScrollPositionChanged(); + } else { + mScrollObservers.RemoveElement(ref); + } + } + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIDocShellTreeItem +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::GetName(nsAString& aName) +{ + aName = mName; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetName(const nsAString& aName) +{ + mName = aName; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::NameEquals(const nsAString& aName, bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mName.Equals(aName); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCustomUserAgent(nsAString& aCustomUserAgent) +{ + aCustomUserAgent = mCustomUserAgent; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCustomUserAgent(const nsAString& aCustomUserAgent) +{ + mCustomUserAgent = aCustomUserAgent; + RefPtr<nsGlobalWindow> win = mScriptGlobal ? + mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + if (win) { + ErrorResult ignored; + Navigator* navigator = win->GetNavigator(ignored); + ignored.SuppressException(); + if (navigator) { + navigator->ClearUserAgentCache(); + } + } + + uint32_t childCount = mChildList.Length(); + for (uint32_t i = 0; i < childCount; ++i) { + nsCOMPtr<nsIDocShell> childShell = do_QueryInterface(ChildAt(i)); + if (childShell) { + childShell->SetCustomUserAgent(aCustomUserAgent); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetTouchEventsOverride(uint32_t* aTouchEventsOverride) +{ + NS_ENSURE_ARG_POINTER(aTouchEventsOverride); + + *aTouchEventsOverride = mTouchEventsOverride; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetTouchEventsOverride(uint32_t aTouchEventsOverride) +{ + if (!(aTouchEventsOverride == nsIDocShell::TOUCHEVENTS_OVERRIDE_NONE || + aTouchEventsOverride == nsIDocShell::TOUCHEVENTS_OVERRIDE_ENABLED || + aTouchEventsOverride == nsIDocShell::TOUCHEVENTS_OVERRIDE_DISABLED)) { + return NS_ERROR_INVALID_ARG; + } + + mTouchEventsOverride = aTouchEventsOverride; + + uint32_t childCount = mChildList.Length(); + for (uint32_t i = 0; i < childCount; ++i) { + nsCOMPtr<nsIDocShell> childShell = do_QueryInterface(ChildAt(i)); + if (childShell) { + childShell->SetTouchEventsOverride(aTouchEventsOverride); + } + } + return NS_OK; +} + +/* virtual */ int32_t +nsDocShell::ItemType() +{ + return mItemType; +} + +NS_IMETHODIMP +nsDocShell::GetItemType(int32_t* aItemType) +{ + NS_ENSURE_ARG_POINTER(aItemType); + + *aItemType = ItemType(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetItemType(int32_t aItemType) +{ + NS_ENSURE_ARG((aItemType == typeChrome) || (typeContent == aItemType)); + + // Only allow setting the type on root docshells. Those would be the ones + // that have the docloader service as mParent or have no mParent at all. + nsCOMPtr<nsIDocumentLoader> docLoaderService = + do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(docLoaderService, NS_ERROR_UNEXPECTED); + + NS_ENSURE_STATE(!mParent || mParent == docLoaderService); + + mItemType = aItemType; + + // disable auth prompting for anything but content + mAllowAuth = mItemType == typeContent; + + RefPtr<nsPresContext> presContext = nullptr; + GetPresContext(getter_AddRefs(presContext)); + if (presContext) { + presContext->UpdateIsChrome(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetParent(nsIDocShellTreeItem** aParent) +{ + if (!mParent) { + *aParent = nullptr; + } else { + CallQueryInterface(mParent, aParent); + } + // Note that in the case when the parent is not an nsIDocShellTreeItem we + // don't want to throw; we just want to return null. + return NS_OK; +} + +already_AddRefed<nsDocShell> +nsDocShell::GetParentDocshell() +{ + nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(GetAsSupports(mParent)); + return docshell.forget().downcast<nsDocShell>(); +} + +void +nsDocShell::RecomputeCanExecuteScripts() +{ + bool old = mCanExecuteScripts; + RefPtr<nsDocShell> parent = GetParentDocshell(); + + // If we have no tree owner, that means that we've been detached from the + // docshell tree (this is distinct from having no parent dochshell, which + // is the case for root docshells). It would be nice to simply disallow + // script in detached docshells, but bug 986542 demonstrates that this + // behavior breaks at least one website. + // + // So instead, we use our previous value, unless mAllowJavascript has been + // explicitly set to false. + if (!mTreeOwner) { + mCanExecuteScripts = mCanExecuteScripts && mAllowJavascript; + // If scripting has been explicitly disabled on our docshell, we're done. + } else if (!mAllowJavascript) { + mCanExecuteScripts = false; + // If we have a parent, inherit. + } else if (parent) { + mCanExecuteScripts = parent->mCanExecuteScripts; + // Otherwise, we're the root of the tree, and we haven't explicitly disabled + // script. Allow. + } else { + mCanExecuteScripts = true; + } + + // Inform our active DOM window. + // + // This will pass the outer, which will be in the scope of the active inner. + if (mScriptGlobal && mScriptGlobal->GetGlobalJSObject()) { + xpc::Scriptability& scriptability = + xpc::Scriptability::Get(mScriptGlobal->GetGlobalJSObject()); + scriptability.SetDocShellAllowsScript(mCanExecuteScripts); + } + + // If our value has changed, our children might be affected. Recompute their + // value as well. + if (old != mCanExecuteScripts) { + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + static_cast<nsDocShell*>(iter.GetNext())->RecomputeCanExecuteScripts(); + } + } +} + +nsresult +nsDocShell::SetDocLoaderParent(nsDocLoader* aParent) +{ + bool wasFrame = IsFrame(); +#ifdef DEBUG + bool wasPrivate = UsePrivateBrowsing(); +#endif + + nsresult rv = nsDocLoader::SetDocLoaderParent(aParent); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsPriority> priorityGroup = do_QueryInterface(mLoadGroup); + if (wasFrame != IsFrame() && priorityGroup) { + priorityGroup->AdjustPriority(wasFrame ? -1 : 1); + } + + // Curse ambiguous nsISupports inheritance! + nsISupports* parent = GetAsSupports(aParent); + + // If parent is another docshell, we inherit all their flags for + // allowing plugins, scripting etc. + bool value; + nsString customUserAgent; + nsCOMPtr<nsIDocShell> parentAsDocShell(do_QueryInterface(parent)); + if (parentAsDocShell) { + if (mAllowPlugins && NS_SUCCEEDED(parentAsDocShell->GetAllowPlugins(&value))) { + SetAllowPlugins(value); + } + if (mAllowJavascript && NS_SUCCEEDED(parentAsDocShell->GetAllowJavascript(&value))) { + SetAllowJavascript(value); + } + if (mAllowMetaRedirects && NS_SUCCEEDED(parentAsDocShell->GetAllowMetaRedirects(&value))) { + SetAllowMetaRedirects(value); + } + if (mAllowSubframes && NS_SUCCEEDED(parentAsDocShell->GetAllowSubframes(&value))) { + SetAllowSubframes(value); + } + if (mAllowImages && NS_SUCCEEDED(parentAsDocShell->GetAllowImages(&value))) { + SetAllowImages(value); + } + SetAllowMedia(parentAsDocShell->GetAllowMedia() && mAllowMedia); + if (mAllowWindowControl && NS_SUCCEEDED(parentAsDocShell->GetAllowWindowControl(&value))) { + SetAllowWindowControl(value); + } + SetAllowContentRetargeting(mAllowContentRetargeting && + parentAsDocShell->GetAllowContentRetargetingOnChildren()); + if (parentAsDocShell->GetIsPrerendered()) { + SetIsPrerendered(); + } + if (NS_SUCCEEDED(parentAsDocShell->GetIsActive(&value))) { + // a prerendered docshell is not active yet + SetIsActive(value && !mIsPrerendered); + } + if (NS_SUCCEEDED(parentAsDocShell->GetCustomUserAgent(customUserAgent)) && + !customUserAgent.IsEmpty()) { + SetCustomUserAgent(customUserAgent); + } + if (NS_FAILED(parentAsDocShell->GetAllowDNSPrefetch(&value))) { + value = false; + } + SetAllowDNSPrefetch(mAllowDNSPrefetch && value); + if (mInheritPrivateBrowsingId) { + value = parentAsDocShell->GetAffectPrivateSessionLifetime(); + SetAffectPrivateSessionLifetime(value); + } + uint32_t flags; + if (NS_SUCCEEDED(parentAsDocShell->GetDefaultLoadFlags(&flags))) { + SetDefaultLoadFlags(flags); + } + uint32_t touchEventsOverride; + if (NS_SUCCEEDED(parentAsDocShell->GetTouchEventsOverride(&touchEventsOverride))) { + SetTouchEventsOverride(touchEventsOverride); + } + } + + nsCOMPtr<nsILoadContext> parentAsLoadContext(do_QueryInterface(parent)); + if (parentAsLoadContext && mInheritPrivateBrowsingId && + NS_SUCCEEDED(parentAsLoadContext->GetUsePrivateBrowsing(&value))) { + SetPrivateBrowsing(value); + } + + nsCOMPtr<nsIURIContentListener> parentURIListener(do_GetInterface(parent)); + if (parentURIListener) { + mContentListener->SetParentContentListener(parentURIListener); + } + + // Our parent has changed. Recompute scriptability. + RecomputeCanExecuteScripts(); + + NS_ASSERTION(mInheritPrivateBrowsingId || wasPrivate == UsePrivateBrowsing(), + "Private browsing state changed while inheritance was disabled"); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSameTypeParent(nsIDocShellTreeItem** aParent) +{ + NS_ENSURE_ARG_POINTER(aParent); + *aParent = nullptr; + + if (nsIDocShell::GetIsMozBrowserOrApp()) { + return NS_OK; + } + + nsCOMPtr<nsIDocShellTreeItem> parent = + do_QueryInterface(GetAsSupports(mParent)); + if (!parent) { + return NS_OK; + } + + if (parent->ItemType() == mItemType) { + parent.swap(*aParent); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSameTypeParentIgnoreBrowserAndAppBoundaries(nsIDocShell** aParent) +{ + NS_ENSURE_ARG_POINTER(aParent); + *aParent = nullptr; + + nsCOMPtr<nsIDocShellTreeItem> parent = + do_QueryInterface(GetAsSupports(mParent)); + if (!parent) { + return NS_OK; + } + + if (parent->ItemType() == mItemType) { + nsCOMPtr<nsIDocShell> parentDS = do_QueryInterface(parent); + parentDS.forget(aParent); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetRootTreeItem(nsIDocShellTreeItem** aRootTreeItem) +{ + NS_ENSURE_ARG_POINTER(aRootTreeItem); + + RefPtr<nsDocShell> root = this; + RefPtr<nsDocShell> parent = root->GetParentDocshell(); + while (parent) { + root = parent; + parent = root->GetParentDocshell(); + } + + root.forget(aRootTreeItem); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSameTypeRootTreeItem(nsIDocShellTreeItem** aRootTreeItem) +{ + NS_ENSURE_ARG_POINTER(aRootTreeItem); + *aRootTreeItem = static_cast<nsIDocShellTreeItem*>(this); + + nsCOMPtr<nsIDocShellTreeItem> parent; + NS_ENSURE_SUCCESS(GetSameTypeParent(getter_AddRefs(parent)), + NS_ERROR_FAILURE); + while (parent) { + *aRootTreeItem = parent; + NS_ENSURE_SUCCESS( + (*aRootTreeItem)->GetSameTypeParent(getter_AddRefs(parent)), + NS_ERROR_FAILURE); + } + NS_ADDREF(*aRootTreeItem); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSameTypeRootTreeItemIgnoreBrowserAndAppBoundaries(nsIDocShell ** aRootTreeItem) +{ + NS_ENSURE_ARG_POINTER(aRootTreeItem); + *aRootTreeItem = static_cast<nsIDocShell *>(this); + + nsCOMPtr<nsIDocShell> parent; + NS_ENSURE_SUCCESS(GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent)), + NS_ERROR_FAILURE); + while (parent) { + *aRootTreeItem = parent; + NS_ENSURE_SUCCESS((*aRootTreeItem)-> + GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent)), + NS_ERROR_FAILURE); + } + NS_ADDREF(*aRootTreeItem); + return NS_OK; +} + +/* static */ +bool +nsDocShell::CanAccessItem(nsIDocShellTreeItem* aTargetItem, + nsIDocShellTreeItem* aAccessingItem, + bool aConsiderOpener) +{ + NS_PRECONDITION(aTargetItem, "Must have target item!"); + + if (!gValidateOrigin || !aAccessingItem) { + // Good to go + return true; + } + + // XXXbz should we care if aAccessingItem or the document therein is + // chrome? Should those get extra privileges? + + // For historical context, see: + // + // Bug 13871: Prevent frameset spoofing + // Bug 103638: Targets with same name in different windows open in wrong + // window with javascript + // Bug 408052: Adopt "ancestor" frame navigation policy + + // Now do a security check. + // + // Disallow navigation if the two frames are not part of the same app, or if + // they have different is-in-browser-element states. + // + // Allow navigation if + // 1) aAccessingItem can script aTargetItem or one of its ancestors in + // the frame hierarchy or + // 2) aTargetItem is a top-level frame and aAccessingItem is its descendant + // 3) aTargetItem is a top-level frame and aAccessingItem can target + // its opener per rule (1) or (2). + + if (aTargetItem == aAccessingItem) { + // A frame is allowed to navigate itself. + return true; + } + + nsCOMPtr<nsIDocShell> targetDS = do_QueryInterface(aTargetItem); + nsCOMPtr<nsIDocShell> accessingDS = do_QueryInterface(aAccessingItem); + if (!targetDS || !accessingDS) { + // We must be able to convert both to nsIDocShell. + return false; + } + + if (targetDS->GetIsInIsolatedMozBrowserElement() != + accessingDS->GetIsInIsolatedMozBrowserElement() || + targetDS->GetAppId() != accessingDS->GetAppId()) { + return false; + } + + nsCOMPtr<nsIDocShellTreeItem> accessingRoot; + aAccessingItem->GetSameTypeRootTreeItem(getter_AddRefs(accessingRoot)); + nsCOMPtr<nsIDocShell> accessingRootDS = do_QueryInterface(accessingRoot); + + nsCOMPtr<nsIDocShellTreeItem> targetRoot; + aTargetItem->GetSameTypeRootTreeItem(getter_AddRefs(targetRoot)); + nsCOMPtr<nsIDocShell> targetRootDS = do_QueryInterface(targetRoot); + + DocShellOriginAttributes targetOA = + static_cast<nsDocShell*>(targetDS.get())->GetOriginAttributes(); + DocShellOriginAttributes accessingOA = + static_cast<nsDocShell*>(accessingDS.get())->GetOriginAttributes(); + + // When the first party isolation is on, the top-level docShell may not have + // the firstPartyDomain in its originAttributes, but its document will have + // it. So we get the firstPartyDomain from the nodePrincipal of the document + // before we compare the originAttributes. + if (OriginAttributes::IsFirstPartyEnabled()) { + if (accessingDS == accessingRootDS && + aAccessingItem->ItemType() == nsIDocShellTreeItem::typeContent && + !accessingDS->GetIsMozBrowserOrApp()) { + + nsCOMPtr<nsIDocument> accessingDoc = aAccessingItem->GetDocument(); + + if (accessingDoc) { + nsCOMPtr<nsIPrincipal> accessingPrincipal = accessingDoc->NodePrincipal(); + + accessingOA.mFirstPartyDomain = + BasePrincipal::Cast(accessingPrincipal)->OriginAttributesRef().mFirstPartyDomain; + } + } + + if (targetDS == targetRootDS && + aTargetItem->ItemType() == nsIDocShellTreeItem::typeContent && + !targetDS->GetIsMozBrowserOrApp()) { + + nsCOMPtr<nsIDocument> targetDoc = aAccessingItem->GetDocument(); + + if (targetDoc) { + nsCOMPtr<nsIPrincipal> targetPrincipal = targetDoc->NodePrincipal(); + + targetOA.mFirstPartyDomain = + BasePrincipal::Cast(targetPrincipal)->OriginAttributesRef().mFirstPartyDomain; + } + } + } + + if (targetOA != accessingOA) { + return false; + } + + // A private document can't access a non-private one, and vice versa. + if (static_cast<nsDocShell*>(targetDS.get())->UsePrivateBrowsing() != + static_cast<nsDocShell*>(accessingDS.get())->UsePrivateBrowsing()) { + return false; + } + + if (aTargetItem == accessingRoot) { + // A frame can navigate its root. + return true; + } + + // Check if aAccessingItem can navigate one of aTargetItem's ancestors. + nsCOMPtr<nsIDocShellTreeItem> target = aTargetItem; + do { + if (ValidateOrigin(aAccessingItem, target)) { + return true; + } + + nsCOMPtr<nsIDocShellTreeItem> parent; + target->GetSameTypeParent(getter_AddRefs(parent)); + parent.swap(target); + } while (target); + + if (aTargetItem != targetRoot) { + // target is a subframe, not in accessor's frame hierarchy, and all its + // ancestors have origins different from that of the accessor. Don't + // allow access. + return false; + } + + if (!aConsiderOpener) { + // All done here + return false; + } + + nsCOMPtr<nsPIDOMWindowOuter> targetWindow = aTargetItem->GetWindow(); + if (!targetWindow) { + NS_ERROR("This should not happen, really"); + return false; + } + + nsCOMPtr<mozIDOMWindowProxy> targetOpener = targetWindow->GetOpener(); + nsCOMPtr<nsIWebNavigation> openerWebNav(do_GetInterface(targetOpener)); + nsCOMPtr<nsIDocShellTreeItem> openerItem(do_QueryInterface(openerWebNav)); + + if (!openerItem) { + return false; + } + + return CanAccessItem(openerItem, aAccessingItem, false); +} + +static bool +ItemIsActive(nsIDocShellTreeItem* aItem) +{ + if (nsCOMPtr<nsPIDOMWindowOuter> window = aItem->GetWindow()) { + auto* win = nsGlobalWindow::Cast(window); + MOZ_ASSERT(win->IsOuterWindow()); + if (!win->GetClosedOuter()) { + return true; + } + } + + return false; +} + +NS_IMETHODIMP +nsDocShell::FindItemWithName(const nsAString& aName, + nsISupports* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + nsIDocShellTreeItem** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + // If we don't find one, we return NS_OK and a null result + *aResult = nullptr; + + if (aName.IsEmpty()) { + return NS_OK; + } + + if (aRequestor) { + // If aRequestor is not null we don't need to check special names, so + // just hand straight off to the search by actual name function. + return DoFindItemWithName(aName, aRequestor, aOriginalRequestor, aResult); + } else { + // This is the entry point into the target-finding algorithm. Check + // for special names. This should only be done once, hence the check + // for a null aRequestor. + + nsCOMPtr<nsIDocShellTreeItem> foundItem; + if (aName.LowerCaseEqualsLiteral("_self")) { + foundItem = this; + } else if (aName.LowerCaseEqualsLiteral("_blank")) { + // Just return null. Caller must handle creating a new window with + // a blank name himself. + return NS_OK; + } else if (aName.LowerCaseEqualsLiteral("_parent")) { + GetSameTypeParent(getter_AddRefs(foundItem)); + if (!foundItem) { + foundItem = this; + } + } else if (aName.LowerCaseEqualsLiteral("_top")) { + GetSameTypeRootTreeItem(getter_AddRefs(foundItem)); + NS_ASSERTION(foundItem, "Must have this; worst case it's us!"); + } else { + // Do the search for item by an actual name. + DoFindItemWithName(aName, aRequestor, aOriginalRequestor, + getter_AddRefs(foundItem)); + } + + if (foundItem && !CanAccessItem(foundItem, aOriginalRequestor)) { + foundItem = nullptr; + } + + // DoFindItemWithName only returns active items and we don't check if + // the item is active for the special cases. + if (foundItem) { + foundItem.swap(*aResult); + } + return NS_OK; + } +} + +void +nsDocShell::AssertOriginAttributesMatchPrivateBrowsing() { + // Chrome docshells must not have a private browsing OriginAttribute + // Content docshells must maintain the equality: + // mOriginAttributes.mPrivateBrowsingId == mPrivateBrowsingId + if (mItemType == typeChrome) { + MOZ_DIAGNOSTIC_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0); + } else { + MOZ_DIAGNOSTIC_ASSERT(mOriginAttributes.mPrivateBrowsingId == mPrivateBrowsingId); + } +} + +nsresult +nsDocShell::DoFindItemWithName(const nsAString& aName, + nsISupports* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + nsIDocShellTreeItem** aResult) +{ + // First we check our name. + if (mName.Equals(aName) && ItemIsActive(this) && + CanAccessItem(this, aOriginalRequestor)) { + NS_ADDREF(*aResult = this); + return NS_OK; + } + + // This QI may fail, but the places where we want to compare, comparing + // against nullptr serves the same purpose. + nsCOMPtr<nsIDocShellTreeItem> reqAsTreeItem(do_QueryInterface(aRequestor)); + + // Second we check our children making sure not to ask a child if + // it is the aRequestor. +#ifdef DEBUG + nsresult rv = +#endif + FindChildWithName(aName, true, true, reqAsTreeItem, aOriginalRequestor, + aResult); + NS_ASSERTION(NS_SUCCEEDED(rv), + "FindChildWithName should not be failing here."); + if (*aResult) { + return NS_OK; + } + + // Third if we have a parent and it isn't the requestor then we + // should ask it to do the search. If it is the requestor we + // should just stop here and let the parent do the rest. If we + // don't have a parent, then we should ask the + // docShellTreeOwner to do the search. + nsCOMPtr<nsIDocShellTreeItem> parentAsTreeItem = + do_QueryInterface(GetAsSupports(mParent)); + if (parentAsTreeItem) { + if (parentAsTreeItem == reqAsTreeItem) { + return NS_OK; + } + + // If we have a same-type parent, respecting browser and app boundaries. + // NOTE: Could use GetSameTypeParent if the issues described in bug 1310344 are fixed. + if (!GetIsMozBrowserOrApp() && parentAsTreeItem->ItemType() == mItemType) { + return parentAsTreeItem->FindItemWithName( + aName, + static_cast<nsIDocShellTreeItem*>(this), + aOriginalRequestor, + aResult); + } + } + + // If we have a null parent or the parent is not of the same type, we need to + // give up on finding it in our tree, and start looking in our TabGroup. + nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow(); + if (window) { + RefPtr<mozilla::dom::TabGroup> tabGroup = window->TabGroup(); + // We don't want to make the request to our TabGroup if they are the ones + // which made a request to us. + if (tabGroup != aRequestor) { + tabGroup->FindItemWithName(aName, this, aOriginalRequestor, aResult); + } + } + + return NS_OK; +} + +bool +nsDocShell::IsSandboxedFrom(nsIDocShell* aTargetDocShell) +{ + // If no target then not sandboxed. + if (!aTargetDocShell) { + return false; + } + + // We cannot be sandboxed from ourselves. + if (aTargetDocShell == this) { + return false; + } + + // Default the sandbox flags to our flags, so that if we can't retrieve the + // active document, we will still enforce our own. + uint32_t sandboxFlags = mSandboxFlags; + if (mContentViewer) { + nsCOMPtr<nsIDocument> doc = mContentViewer->GetDocument(); + if (doc) { + sandboxFlags = doc->GetSandboxFlags(); + } + } + + // If no flags, we are not sandboxed at all. + if (!sandboxFlags) { + return false; + } + + // If aTargetDocShell has an ancestor, it is not top level. + nsCOMPtr<nsIDocShellTreeItem> ancestorOfTarget; + aTargetDocShell->GetSameTypeParent(getter_AddRefs(ancestorOfTarget)); + if (ancestorOfTarget) { + do { + // We are not sandboxed if we are an ancestor of target. + if (ancestorOfTarget == this) { + return false; + } + nsCOMPtr<nsIDocShellTreeItem> tempTreeItem; + ancestorOfTarget->GetSameTypeParent(getter_AddRefs(tempTreeItem)); + tempTreeItem.swap(ancestorOfTarget); + } while (ancestorOfTarget); + + // Otherwise, we are sandboxed from aTargetDocShell. + return true; + } + + // aTargetDocShell is top level, are we the "one permitted sandboxed + // navigator", i.e. did we open aTargetDocShell? + nsCOMPtr<nsIDocShell> permittedNavigator; + aTargetDocShell->GetOnePermittedSandboxedNavigator( + getter_AddRefs(permittedNavigator)); + if (permittedNavigator == this) { + return false; + } + + // If SANDBOXED_TOPLEVEL_NAVIGATION flag is not on, we are not sandboxed + // from our top. + if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION)) { + nsCOMPtr<nsIDocShellTreeItem> rootTreeItem; + GetSameTypeRootTreeItem(getter_AddRefs(rootTreeItem)); + if (SameCOMIdentity(aTargetDocShell, rootTreeItem)) { + return false; + } + } + + // Otherwise, we are sandboxed from aTargetDocShell. + return true; +} + +NS_IMETHODIMP +nsDocShell::GetTreeOwner(nsIDocShellTreeOwner** aTreeOwner) +{ + NS_ENSURE_ARG_POINTER(aTreeOwner); + + *aTreeOwner = mTreeOwner; + NS_IF_ADDREF(*aTreeOwner); + return NS_OK; +} + +#ifdef DEBUG_DOCSHELL_FOCUS +static void +PrintDocTree(nsIDocShellTreeItem* aParentNode, int aLevel) +{ + for (int32_t i = 0; i < aLevel; i++) { + printf(" "); + } + + int32_t childWebshellCount; + aParentNode->GetChildCount(&childWebshellCount); + nsCOMPtr<nsIDocShell> parentAsDocShell(do_QueryInterface(aParentNode)); + int32_t type = aParentNode->ItemType(); + nsCOMPtr<nsIPresShell> presShell = parentAsDocShell->GetPresShell(); + RefPtr<nsPresContext> presContext; + parentAsDocShell->GetPresContext(getter_AddRefs(presContext)); + nsIDocument* doc = presShell->GetDocument(); + + nsCOMPtr<nsPIDOMWindowOuter> domwin(doc->GetWindow()); + + nsCOMPtr<nsIWidget> widget; + nsViewManager* vm = presShell->GetViewManager(); + if (vm) { + vm->GetWidget(getter_AddRefs(widget)); + } + dom::Element* rootElement = doc->GetRootElement(); + + printf("DS %p Ty %s Doc %p DW %p EM %p CN %p\n", + (void*)parentAsDocShell.get(), + type == nsIDocShellTreeItem::typeChrome ? "Chr" : "Con", + (void*)doc, (void*)domwin.get(), + (void*)presContext->EventStateManager(), (void*)rootElement); + + if (childWebshellCount > 0) { + for (int32_t i = 0; i < childWebshellCount; i++) { + nsCOMPtr<nsIDocShellTreeItem> child; + aParentNode->GetChildAt(i, getter_AddRefs(child)); + PrintDocTree(child, aLevel + 1); + } + } +} + +static void +PrintDocTree(nsIDocShellTreeItem* aParentNode) +{ + NS_ASSERTION(aParentNode, "Pointer is null!"); + + nsCOMPtr<nsIDocShellTreeItem> parentItem; + aParentNode->GetParent(getter_AddRefs(parentItem)); + while (parentItem) { + nsCOMPtr<nsIDocShellTreeItem> tmp; + parentItem->GetParent(getter_AddRefs(tmp)); + if (!tmp) { + break; + } + parentItem = tmp; + } + + if (!parentItem) { + parentItem = aParentNode; + } + + PrintDocTree(parentItem, 0); +} +#endif + +NS_IMETHODIMP +nsDocShell::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner) +{ +#ifdef DEBUG_DOCSHELL_FOCUS + nsCOMPtr<nsIDocShellTreeItem> item(do_QueryInterface(aTreeOwner)); + if (item) { + PrintDocTree(item); + } +#endif + + // Don't automatically set the progress based on the tree owner for frames + if (!IsFrame()) { + nsCOMPtr<nsIWebProgress> webProgress = + do_QueryInterface(GetAsSupports(this)); + + if (webProgress) { + nsCOMPtr<nsIWebProgressListener> oldListener = + do_QueryInterface(mTreeOwner); + nsCOMPtr<nsIWebProgressListener> newListener = + do_QueryInterface(aTreeOwner); + + if (oldListener) { + webProgress->RemoveProgressListener(oldListener); + } + + if (newListener) { + webProgress->AddProgressListener(newListener, + nsIWebProgress::NOTIFY_ALL); + } + } + } + + mTreeOwner = aTreeOwner; // Weak reference per API + + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShellTreeItem> child = do_QueryObject(iter.GetNext()); + NS_ENSURE_TRUE(child, NS_ERROR_FAILURE); + + if (child->ItemType() == mItemType) { + child->SetTreeOwner(aTreeOwner); + } + } + + // Our tree owner has changed. Recompute scriptability. + // + // Note that this is near-redundant with the recomputation in + // SetDocLoaderParent(), but not so for the root DocShell, where the call to + // SetTreeOwner() happens after the initial AddDocLoaderAsChildOfRoot(), + // and we never set another parent. Given that this is neither expensive nor + // performance-critical, let's be safe and unconditionally recompute this + // state whenever dependent state changes. + RecomputeCanExecuteScripts(); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetChildOffset(uint32_t aChildOffset) +{ + mChildOffset = aChildOffset; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHistoryID(uint64_t* aID) +{ + *aID = mHistoryID; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsInUnload(bool* aIsInUnload) +{ + *aIsInUnload = mFiredUnloadEvent; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetChildCount(int32_t* aChildCount) +{ + NS_ENSURE_ARG_POINTER(aChildCount); + *aChildCount = mChildList.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::AddChild(nsIDocShellTreeItem* aChild) +{ + NS_ENSURE_ARG_POINTER(aChild); + + RefPtr<nsDocLoader> childAsDocLoader = GetAsDocLoader(aChild); + NS_ENSURE_TRUE(childAsDocLoader, NS_ERROR_UNEXPECTED); + + // Make sure we're not creating a loop in the docshell tree + nsDocLoader* ancestor = this; + do { + if (childAsDocLoader == ancestor) { + return NS_ERROR_ILLEGAL_VALUE; + } + ancestor = ancestor->GetParent(); + } while (ancestor); + + // Make sure to remove the child from its current parent. + nsDocLoader* childsParent = childAsDocLoader->GetParent(); + if (childsParent) { + nsresult rv = childsParent->RemoveChildLoader(childAsDocLoader); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Make sure to clear the treeowner in case this child is a different type + // from us. + aChild->SetTreeOwner(nullptr); + + nsresult res = AddChildLoader(childAsDocLoader); + NS_ENSURE_SUCCESS(res, res); + NS_ASSERTION(!mChildList.IsEmpty(), + "child list must not be empty after a successful add"); + + nsCOMPtr<nsIDocShell> childDocShell = do_QueryInterface(aChild); + bool dynamic = false; + childDocShell->GetCreatedDynamically(&dynamic); + if (!dynamic) { + nsCOMPtr<nsISHEntry> currentSH; + bool oshe = false; + GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe); + if (currentSH) { + currentSH->HasDynamicallyAddedChild(&dynamic); + } + } + childDocShell->SetChildOffset(dynamic ? -1 : mChildList.Length() - 1); + + /* Set the child's global history if the parent has one */ + if (mUseGlobalHistory) { + childDocShell->SetUseGlobalHistory(true); + } + + if (aChild->ItemType() != mItemType) { + return NS_OK; + } + + aChild->SetTreeOwner(mTreeOwner); + + nsCOMPtr<nsIDocShell> childAsDocShell(do_QueryInterface(aChild)); + if (!childAsDocShell) { + return NS_OK; + } + + // charset, style-disabling, and zoom will be inherited in SetupNewViewer() + + // Now take this document's charset and set the child's parentCharset field + // to it. We'll later use that field, in the loading process, for the + // charset choosing algorithm. + // If we fail, at any point, we just return NS_OK. + // This code has some performance impact. But this will be reduced when + // the current charset will finally be stored as an Atom, avoiding the + // alias resolution extra look-up. + + // we are NOT going to propagate the charset is this Chrome's docshell + if (mItemType == nsIDocShellTreeItem::typeChrome) { + return NS_OK; + } + + // get the parent's current charset + if (!mContentViewer) { + return NS_OK; + } + nsIDocument* doc = mContentViewer->GetDocument(); + if (!doc) { + return NS_OK; + } + + bool isWyciwyg = false; + + if (mCurrentURI) { + // Check if the url is wyciwyg + mCurrentURI->SchemeIs("wyciwyg", &isWyciwyg); + } + + if (!isWyciwyg) { + // If this docshell is loaded from a wyciwyg: URI, don't + // advertise our charset since it does not in any way reflect + // the actual source charset, which is what we're trying to + // expose here. + + const nsACString& parentCS = doc->GetDocumentCharacterSet(); + int32_t charsetSource = doc->GetDocumentCharacterSetSource(); + // set the child's parentCharset + childAsDocShell->SetParentCharset(parentCS, + charsetSource, + doc->NodePrincipal()); + } + + // printf("### 1 >>> Adding child. Parent CS = %s. ItemType = %d.\n", + // NS_LossyConvertUTF16toASCII(parentCS).get(), mItemType); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RemoveChild(nsIDocShellTreeItem* aChild) +{ + NS_ENSURE_ARG_POINTER(aChild); + + RefPtr<nsDocLoader> childAsDocLoader = GetAsDocLoader(aChild); + NS_ENSURE_TRUE(childAsDocLoader, NS_ERROR_UNEXPECTED); + + nsresult rv = RemoveChildLoader(childAsDocLoader); + NS_ENSURE_SUCCESS(rv, rv); + + aChild->SetTreeOwner(nullptr); + + return nsDocLoader::AddDocLoaderAsChildOfRoot(childAsDocLoader); +} + +NS_IMETHODIMP +nsDocShell::GetChildAt(int32_t aIndex, nsIDocShellTreeItem** aChild) +{ + NS_ENSURE_ARG_POINTER(aChild); + +#ifdef DEBUG + if (aIndex < 0) { + NS_WARNING("Negative index passed to GetChildAt"); + } else if (static_cast<uint32_t>(aIndex) >= mChildList.Length()) { + NS_WARNING("Too large an index passed to GetChildAt"); + } +#endif + + nsIDocumentLoader* child = ChildAt(aIndex); + NS_ENSURE_TRUE(child, NS_ERROR_UNEXPECTED); + + return CallQueryInterface(child, aChild); +} + +NS_IMETHODIMP +nsDocShell::FindChildWithName(const nsAString& aName, + bool aRecurse, bool aSameType, + nsIDocShellTreeItem* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + nsIDocShellTreeItem** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + // if we don't find one, we return NS_OK and a null result + *aResult = nullptr; + + if (aName.IsEmpty()) { + return NS_OK; + } + + nsXPIDLString childName; + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShellTreeItem> child = do_QueryObject(iter.GetNext()); + NS_ENSURE_TRUE(child, NS_ERROR_FAILURE); + int32_t childType = child->ItemType(); + + if (aSameType && (childType != mItemType)) { + continue; + } + + bool childNameEquals = false; + child->NameEquals(aName, &childNameEquals); + if (childNameEquals && ItemIsActive(child) && + CanAccessItem(child, aOriginalRequestor)) { + child.swap(*aResult); + break; + } + + // Only ask it to check children if it is same type + if (childType != mItemType) { + continue; + } + + // Only ask the child if it isn't the requestor + if (aRecurse && (aRequestor != child)) { + // See if child contains the shell with the given name +#ifdef DEBUG + nsresult rv = +#endif + child->FindChildWithName(aName, true, aSameType, + static_cast<nsIDocShellTreeItem*>(this), + aOriginalRequestor, aResult); + NS_ASSERTION(NS_SUCCEEDED(rv), "FindChildWithName should not fail here"); + if (*aResult) { + // found it + return NS_OK; + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetChildSHEntry(int32_t aChildOffset, nsISHEntry** aResult) +{ + nsresult rv = NS_OK; + + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + // A nsISHEntry for a child is *only* available when the parent is in + // the progress of loading a document too... + + if (mLSHE) { + /* Before looking for the subframe's url, check + * the expiration status of the parent. If the parent + * has expired from cache, then subframes will not be + * loaded from history in certain situations. + */ + bool parentExpired = false; + mLSHE->GetExpirationStatus(&parentExpired); + + /* Get the parent's Load Type so that it can be set on the child too. + * By default give a loadHistory value + */ + uint32_t loadType = nsIDocShellLoadInfo::loadHistory; + mLSHE->GetLoadType(&loadType); + // If the user did a shift-reload on this frameset page, + // we don't want to load the subframes from history. + if (loadType == nsIDocShellLoadInfo::loadReloadBypassCache || + loadType == nsIDocShellLoadInfo::loadReloadBypassProxy || + loadType == nsIDocShellLoadInfo::loadReloadBypassProxyAndCache || + loadType == nsIDocShellLoadInfo::loadRefresh) { + return rv; + } + + /* If the user pressed reload and the parent frame has expired + * from cache, we do not want to load the child frame from history. + */ + if (parentExpired && (loadType == nsIDocShellLoadInfo::loadReloadNormal)) { + // The parent has expired. Return null. + *aResult = nullptr; + return rv; + } + + nsCOMPtr<nsISHContainer> container(do_QueryInterface(mLSHE)); + if (container) { + // Get the child subframe from session history. + rv = container->GetChildAt(aChildOffset, aResult); + if (*aResult) { + (*aResult)->SetLoadType(loadType); + } + } + } + return rv; +} + +NS_IMETHODIMP +nsDocShell::AddChildSHEntry(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry, + int32_t aChildOffset, uint32_t aLoadType, + bool aCloneChildren) +{ + nsresult rv = NS_OK; + + if (mLSHE && aLoadType != LOAD_PUSHSTATE) { + /* You get here if you are currently building a + * hierarchy ie.,you just visited a frameset page + */ + nsCOMPtr<nsISHContainer> container(do_QueryInterface(mLSHE, &rv)); + if (container) { + if (NS_FAILED(container->ReplaceChild(aNewEntry))) { + rv = container->AddChild(aNewEntry, aChildOffset); + } + } + } else if (!aCloneRef) { + /* This is an initial load in some subframe. Just append it if we can */ + nsCOMPtr<nsISHContainer> container(do_QueryInterface(mOSHE, &rv)); + if (container) { + rv = container->AddChild(aNewEntry, aChildOffset); + } + } else { + rv = AddChildSHEntryInternal(aCloneRef, aNewEntry, aChildOffset, + aLoadType, aCloneChildren); + } + return rv; +} + +nsresult +nsDocShell::AddChildSHEntryInternal(nsISHEntry* aCloneRef, + nsISHEntry* aNewEntry, + int32_t aChildOffset, + uint32_t aLoadType, + bool aCloneChildren) +{ + nsresult rv = NS_OK; + if (mSessionHistory) { + /* You are currently in the rootDocShell. + * You will get here when a subframe has a new url + * to load and you have walked up the tree all the + * way to the top to clone the current SHEntry hierarchy + * and replace the subframe where a new url was loaded with + * a new entry. + */ + int32_t index = -1; + nsCOMPtr<nsISHEntry> currentHE; + mSessionHistory->GetIndex(&index); + if (index < 0) { + return NS_ERROR_FAILURE; + } + + rv = mSessionHistory->GetEntryAtIndex(index, false, + getter_AddRefs(currentHE)); + NS_ENSURE_TRUE(currentHE, NS_ERROR_FAILURE); + + nsCOMPtr<nsISHEntry> currentEntry(do_QueryInterface(currentHE)); + if (currentEntry) { + uint32_t cloneID = 0; + nsCOMPtr<nsISHEntry> nextEntry; + aCloneRef->GetID(&cloneID); + rv = CloneAndReplace(currentEntry, this, cloneID, aNewEntry, + aCloneChildren, getter_AddRefs(nextEntry)); + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsISHistoryInternal> shPrivate = + do_QueryInterface(mSessionHistory); + NS_ENSURE_TRUE(shPrivate, NS_ERROR_FAILURE); + rv = shPrivate->AddEntry(nextEntry, true); + } + } + } else { + /* Just pass this along */ + nsCOMPtr<nsIDocShell> parent = + do_QueryInterface(GetAsSupports(mParent), &rv); + if (parent) { + rv = static_cast<nsDocShell*>(parent.get())->AddChildSHEntryInternal( + aCloneRef, aNewEntry, aChildOffset, aLoadType, aCloneChildren); + } + } + return rv; +} + +nsresult +nsDocShell::AddChildSHEntryToParent(nsISHEntry* aNewEntry, int32_t aChildOffset, + bool aCloneChildren) +{ + /* You will get here when you are in a subframe and + * a new url has been loaded on you. + * The mOSHE in this subframe will be the previous url's + * mOSHE. This mOSHE will be used as the identification + * for this subframe in the CloneAndReplace function. + */ + + // In this case, we will end up calling AddEntry, which increases the + // current index by 1 + nsCOMPtr<nsISHistory> rootSH; + GetRootSessionHistory(getter_AddRefs(rootSH)); + if (rootSH) { + rootSH->GetIndex(&mPreviousTransIndex); + } + + nsresult rv; + nsCOMPtr<nsIDocShell> parent = do_QueryInterface(GetAsSupports(mParent), &rv); + if (parent) { + rv = parent->AddChildSHEntry(mOSHE, aNewEntry, aChildOffset, mLoadType, + aCloneChildren); + } + + if (rootSH) { + rootSH->GetIndex(&mLoadedTransIndex); +#ifdef DEBUG_PAGE_CACHE + printf("Previous index: %d, Loaded index: %d\n\n", mPreviousTransIndex, + mLoadedTransIndex); +#endif + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::SetUseGlobalHistory(bool aUseGlobalHistory) +{ + nsresult rv; + + mUseGlobalHistory = aUseGlobalHistory; + + if (!aUseGlobalHistory) { + mGlobalHistory = nullptr; + return NS_OK; + } + + // No need to initialize mGlobalHistory if IHistory is available. + nsCOMPtr<IHistory> history = services::GetHistoryService(); + if (history) { + return NS_OK; + } + + if (mGlobalHistory) { + return NS_OK; + } + + mGlobalHistory = do_GetService(NS_GLOBALHISTORY2_CONTRACTID, &rv); + return rv; +} + +NS_IMETHODIMP +nsDocShell::GetUseGlobalHistory(bool* aUseGlobalHistory) +{ + *aUseGlobalHistory = mUseGlobalHistory; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RemoveFromSessionHistory() +{ + nsCOMPtr<nsISHistoryInternal> internalHistory; + nsCOMPtr<nsISHistory> sessionHistory; + nsCOMPtr<nsIDocShellTreeItem> root; + GetSameTypeRootTreeItem(getter_AddRefs(root)); + if (root) { + nsCOMPtr<nsIWebNavigation> rootAsWebnav = do_QueryInterface(root); + if (rootAsWebnav) { + rootAsWebnav->GetSessionHistory(getter_AddRefs(sessionHistory)); + internalHistory = do_QueryInterface(sessionHistory); + } + } + if (!internalHistory) { + return NS_OK; + } + + int32_t index = 0; + sessionHistory->GetIndex(&index); + AutoTArray<uint64_t, 16> ids({mHistoryID}); + internalHistory->RemoveEntries(ids, index); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCreatedDynamically(bool aDynamic) +{ + mDynamicallyCreated = aDynamic; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCreatedDynamically(bool* aDynamic) +{ + *aDynamic = mDynamicallyCreated; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCurrentSHEntry(nsISHEntry** aEntry, bool* aOSHE) +{ + *aOSHE = false; + *aEntry = nullptr; + if (mLSHE) { + NS_ADDREF(*aEntry = mLSHE); + } else if (mOSHE) { + NS_ADDREF(*aEntry = mOSHE); + *aOSHE = true; + } + return NS_OK; +} + +nsIScriptGlobalObject* +nsDocShell::GetScriptGlobalObject() +{ + NS_ENSURE_SUCCESS(EnsureScriptEnvironment(), nullptr); + return mScriptGlobal; +} + +nsIDocument* +nsDocShell::GetDocument() +{ + NS_ENSURE_SUCCESS(EnsureContentViewer(), nullptr); + return mContentViewer->GetDocument(); +} + +nsPIDOMWindowOuter* +nsDocShell::GetWindow() +{ + if (NS_FAILED(EnsureScriptEnvironment())) { + return nullptr; + } + return mScriptGlobal->AsOuter(); +} + +NS_IMETHODIMP +nsDocShell::SetDeviceSizeIsPageSize(bool aValue) +{ + if (mDeviceSizeIsPageSize != aValue) { + mDeviceSizeIsPageSize = aValue; + RefPtr<nsPresContext> presContext; + GetPresContext(getter_AddRefs(presContext)); + if (presContext) { + presContext->MediaFeatureValuesChanged(nsRestyleHint(0)); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDeviceSizeIsPageSize(bool* aValue) +{ + *aValue = mDeviceSizeIsPageSize; + return NS_OK; +} + +void +nsDocShell::ClearFrameHistory(nsISHEntry* aEntry) +{ + nsCOMPtr<nsISHContainer> shcontainer = do_QueryInterface(aEntry); + nsCOMPtr<nsISHistory> rootSH; + GetRootSessionHistory(getter_AddRefs(rootSH)); + nsCOMPtr<nsISHistoryInternal> history = do_QueryInterface(rootSH); + if (!history || !shcontainer) { + return; + } + + int32_t count = 0; + shcontainer->GetChildCount(&count); + AutoTArray<uint64_t, 16> ids; + for (int32_t i = 0; i < count; ++i) { + nsCOMPtr<nsISHEntry> child; + shcontainer->GetChildAt(i, getter_AddRefs(child)); + if (child) { + uint64_t id = 0; + child->GetDocshellID(&id); + ids.AppendElement(id); + } + } + int32_t index = 0; + rootSH->GetIndex(&index); + history->RemoveEntries(ids, index); +} + +//------------------------------------- +//-- Helper Method for Print discovery +//------------------------------------- +bool +nsDocShell::IsPrintingOrPP(bool aDisplayErrorDialog) +{ + if (mIsPrintingOrPP && aDisplayErrorDialog) { + DisplayLoadError(NS_ERROR_DOCUMENT_IS_PRINTMODE, nullptr, nullptr, nullptr); + } + + return mIsPrintingOrPP; +} + +bool +nsDocShell::IsNavigationAllowed(bool aDisplayPrintErrorDialog, + bool aCheckIfUnloadFired) +{ + bool isAllowed = !IsPrintingOrPP(aDisplayPrintErrorDialog) && + (!aCheckIfUnloadFired || !mFiredUnloadEvent); + if (!isAllowed) { + return false; + } + if (!mContentViewer) { + return true; + } + bool firingBeforeUnload; + mContentViewer->GetBeforeUnloadFiring(&firingBeforeUnload); + return !firingBeforeUnload; +} + +//***************************************************************************** +// nsDocShell::nsIWebNavigation +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::GetCanGoBack(bool* aCanGoBack) +{ + if (!IsNavigationAllowed(false)) { + *aCanGoBack = false; + return NS_OK; // JS may not handle returning of an error code + } + nsresult rv; + nsCOMPtr<nsISHistory> rootSH; + rv = GetRootSessionHistory(getter_AddRefs(rootSH)); + nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(rootSH)); + NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE); + rv = webnav->GetCanGoBack(aCanGoBack); + return rv; +} + +NS_IMETHODIMP +nsDocShell::GetCanGoForward(bool* aCanGoForward) +{ + if (!IsNavigationAllowed(false)) { + *aCanGoForward = false; + return NS_OK; // JS may not handle returning of an error code + } + nsresult rv; + nsCOMPtr<nsISHistory> rootSH; + rv = GetRootSessionHistory(getter_AddRefs(rootSH)); + nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(rootSH)); + NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE); + rv = webnav->GetCanGoForward(aCanGoForward); + return rv; +} + +NS_IMETHODIMP +nsDocShell::GoBack() +{ + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + nsresult rv; + nsCOMPtr<nsISHistory> rootSH; + rv = GetRootSessionHistory(getter_AddRefs(rootSH)); + nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(rootSH)); + NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE); + rv = webnav->GoBack(); + return rv; +} + +NS_IMETHODIMP +nsDocShell::GoForward() +{ + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + nsresult rv; + nsCOMPtr<nsISHistory> rootSH; + rv = GetRootSessionHistory(getter_AddRefs(rootSH)); + nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(rootSH)); + NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE); + rv = webnav->GoForward(); + return rv; +} + +NS_IMETHODIMP +nsDocShell::GotoIndex(int32_t aIndex) +{ + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + nsresult rv; + nsCOMPtr<nsISHistory> rootSH; + rv = GetRootSessionHistory(getter_AddRefs(rootSH)); + nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(rootSH)); + NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE); + rv = webnav->GotoIndex(aIndex); + return rv; +} + +NS_IMETHODIMP +nsDocShell::LoadURI(const char16_t* aURI, + uint32_t aLoadFlags, + nsIURI* aReferringURI, + nsIInputStream* aPostStream, + nsIInputStream* aHeaderStream) +{ + return LoadURIWithOptions(aURI, aLoadFlags, aReferringURI, + mozilla::net::RP_Default, aPostStream, + aHeaderStream, nullptr); +} + +NS_IMETHODIMP +nsDocShell::LoadURIWithOptions(const char16_t* aURI, + uint32_t aLoadFlags, + nsIURI* aReferringURI, + uint32_t aReferrerPolicy, + nsIInputStream* aPostStream, + nsIInputStream* aHeaderStream, + nsIURI* aBaseURI) +{ + NS_ASSERTION((aLoadFlags & 0xf) == 0, "Unexpected flags"); + + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIInputStream> postStream(aPostStream); + nsresult rv = NS_OK; + + // Create a URI from our string; if that succeeds, we want to + // change aLoadFlags to not include the ALLOW_THIRD_PARTY_FIXUP + // flag. + + NS_ConvertUTF16toUTF8 uriString(aURI); + // Cleanup the empty spaces that might be on each end. + uriString.Trim(" "); + // Eliminate embedded newlines, which single-line text fields now allow: + uriString.StripChars("\r\n"); + NS_ENSURE_TRUE(!uriString.IsEmpty(), NS_ERROR_FAILURE); + + rv = NS_NewURI(getter_AddRefs(uri), uriString); + if (uri) { + aLoadFlags &= ~LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; + } + + nsCOMPtr<nsIURIFixupInfo> fixupInfo; + if (sURIFixup) { + // Call the fixup object. This will clobber the rv from NS_NewURI + // above, but that's fine with us. Note that we need to do this even + // if NS_NewURI returned a URI, because fixup handles nested URIs, etc + // (things like view-source:mozilla.org for example). + uint32_t fixupFlags = 0; + if (aLoadFlags & LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) { + fixupFlags |= nsIURIFixup::FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; + } + if (aLoadFlags & LOAD_FLAGS_FIXUP_SCHEME_TYPOS) { + fixupFlags |= nsIURIFixup::FIXUP_FLAG_FIX_SCHEME_TYPOS; + } + nsCOMPtr<nsIInputStream> fixupStream; + rv = sURIFixup->GetFixupURIInfo(uriString, fixupFlags, + getter_AddRefs(fixupStream), + getter_AddRefs(fixupInfo)); + + if (NS_SUCCEEDED(rv)) { + fixupInfo->GetPreferredURI(getter_AddRefs(uri)); + fixupInfo->SetConsumer(GetAsSupports(this)); + } + + if (fixupStream) { + // GetFixupURIInfo only returns a post data stream if it succeeded + // and changed the URI, in which case we should override the + // passed-in post data. + postStream = fixupStream; + } + + if (aLoadFlags & LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) { + nsCOMPtr<nsIObserverService> serv = services::GetObserverService(); + if (serv) { + serv->NotifyObservers(fixupInfo, "keyword-uri-fixup", aURI); + } + } + } + // else no fixup service so just use the URI we created and see + // what happens + + if (NS_ERROR_MALFORMED_URI == rv) { + if (DisplayLoadError(rv, uri, aURI, nullptr) && + (aLoadFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) { + return NS_ERROR_LOAD_SHOWED_ERRORPAGE; + } + } + + if (NS_FAILED(rv) || !uri) { + return NS_ERROR_FAILURE; + } + + PopupControlState popupState; + if (aLoadFlags & LOAD_FLAGS_ALLOW_POPUPS) { + popupState = openAllowed; + aLoadFlags &= ~LOAD_FLAGS_ALLOW_POPUPS; + } else { + popupState = openOverridden; + } + nsAutoPopupStatePusher statePusher(popupState); + + // Don't pass certain flags that aren't needed and end up confusing + // ConvertLoadTypeToDocShellLoadInfo. We do need to ensure that they are + // passed to LoadURI though, since it uses them. + uint32_t extraFlags = (aLoadFlags & EXTRA_LOAD_FLAGS); + aLoadFlags &= ~EXTRA_LOAD_FLAGS; + + nsCOMPtr<nsIDocShellLoadInfo> loadInfo; + rv = CreateLoadInfo(getter_AddRefs(loadInfo)); + if (NS_FAILED(rv)) { + return rv; + } + + /* + * If the user "Disables Protection on This Page", we have to make sure to + * remember the users decision when opening links in child tabs [Bug 906190] + */ + uint32_t loadType; + if (aLoadFlags & LOAD_FLAGS_ALLOW_MIXED_CONTENT) { + loadType = MAKE_LOAD_TYPE(LOAD_NORMAL_ALLOW_MIXED_CONTENT, aLoadFlags); + } else { + loadType = MAKE_LOAD_TYPE(LOAD_NORMAL, aLoadFlags); + } + + loadInfo->SetLoadType(ConvertLoadTypeToDocShellLoadInfo(loadType)); + loadInfo->SetPostDataStream(postStream); + loadInfo->SetReferrer(aReferringURI); + loadInfo->SetReferrerPolicy(aReferrerPolicy); + loadInfo->SetHeadersStream(aHeaderStream); + loadInfo->SetBaseURI(aBaseURI); + + if (fixupInfo) { + nsAutoString searchProvider, keyword; + fixupInfo->GetKeywordProviderName(searchProvider); + fixupInfo->GetKeywordAsSent(keyword); + MaybeNotifyKeywordSearchLoading(searchProvider, keyword); + } + + rv = LoadURI(uri, loadInfo, extraFlags, true); + + // Save URI string in case it's needed later when + // sending to search engine service in EndPageLoad() + mOriginalUriString = uriString; + + return rv; +} + +NS_IMETHODIMP +nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, + const char16_t* aURL, + nsIChannel* aFailedChannel, + bool* aDisplayedErrorPage) +{ + *aDisplayedErrorPage = false; + // Get prompt and string bundle servcies + nsCOMPtr<nsIPrompt> prompter; + nsCOMPtr<nsIStringBundle> stringBundle; + GetPromptAndStringBundle(getter_AddRefs(prompter), + getter_AddRefs(stringBundle)); + + NS_ENSURE_TRUE(stringBundle, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(prompter, NS_ERROR_FAILURE); + + nsAutoString error; + const uint32_t kMaxFormatStrArgs = 3; + nsAutoString formatStrs[kMaxFormatStrArgs]; + uint32_t formatStrCount = 0; + bool addHostPort = false; + nsresult rv = NS_OK; + nsAutoString messageStr; + nsAutoCString cssClass; + nsAutoCString errorPage; + + errorPage.AssignLiteral("neterror"); + + // Turn the error code into a human readable error message. + if (NS_ERROR_UNKNOWN_PROTOCOL == aError) { + NS_ENSURE_ARG_POINTER(aURI); + + // Extract the schemes into a comma delimited list. + nsAutoCString scheme; + aURI->GetScheme(scheme); + CopyASCIItoUTF16(scheme, formatStrs[0]); + nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(aURI); + while (nestedURI) { + nsCOMPtr<nsIURI> tempURI; + nsresult rv2; + rv2 = nestedURI->GetInnerURI(getter_AddRefs(tempURI)); + if (NS_SUCCEEDED(rv2) && tempURI) { + tempURI->GetScheme(scheme); + formatStrs[0].AppendLiteral(", "); + AppendASCIItoUTF16(scheme, formatStrs[0]); + } + nestedURI = do_QueryInterface(tempURI); + } + formatStrCount = 1; + error.AssignLiteral("unknownProtocolFound"); + } else if (NS_ERROR_FILE_NOT_FOUND == aError) { + NS_ENSURE_ARG_POINTER(aURI); + error.AssignLiteral("fileNotFound"); + } else if (NS_ERROR_FILE_ACCESS_DENIED == aError) { + NS_ENSURE_ARG_POINTER(aURI); + error.AssignLiteral("fileAccessDenied"); + } else if (NS_ERROR_UNKNOWN_HOST == aError) { + NS_ENSURE_ARG_POINTER(aURI); + // Get the host + nsAutoCString host; + nsCOMPtr<nsIURI> innermostURI = NS_GetInnermostURI(aURI); + innermostURI->GetHost(host); + CopyUTF8toUTF16(host, formatStrs[0]); + formatStrCount = 1; + error.AssignLiteral("dnsNotFound"); + } else if (NS_ERROR_CONNECTION_REFUSED == aError) { + NS_ENSURE_ARG_POINTER(aURI); + addHostPort = true; + error.AssignLiteral("connectionFailure"); + } else if (NS_ERROR_NET_INTERRUPT == aError) { + NS_ENSURE_ARG_POINTER(aURI); + addHostPort = true; + error.AssignLiteral("netInterrupt"); + } else if (NS_ERROR_NET_TIMEOUT == aError) { + NS_ENSURE_ARG_POINTER(aURI); + // Get the host + nsAutoCString host; + aURI->GetHost(host); + CopyUTF8toUTF16(host, formatStrs[0]); + formatStrCount = 1; + error.AssignLiteral("netTimeout"); + } else if (NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION == aError || + NS_ERROR_CSP_FORM_ACTION_VIOLATION == aError) { + // CSP error + cssClass.AssignLiteral("neterror"); + error.AssignLiteral("cspBlocked"); + } else if (NS_ERROR_GET_MODULE(aError) == NS_ERROR_MODULE_SECURITY) { + nsCOMPtr<nsINSSErrorsService> nsserr = + do_GetService(NS_NSS_ERRORS_SERVICE_CONTRACTID); + + uint32_t errorClass; + if (!nsserr || NS_FAILED(nsserr->GetErrorClass(aError, &errorClass))) { + errorClass = nsINSSErrorsService::ERROR_CLASS_SSL_PROTOCOL; + } + + nsCOMPtr<nsISupports> securityInfo; + nsCOMPtr<nsITransportSecurityInfo> tsi; + if (aFailedChannel) { + aFailedChannel->GetSecurityInfo(getter_AddRefs(securityInfo)); + } + tsi = do_QueryInterface(securityInfo); + if (tsi) { + uint32_t securityState; + tsi->GetSecurityState(&securityState); + if (securityState & nsIWebProgressListener::STATE_USES_SSL_3) { + error.AssignLiteral("sslv3Used"); + addHostPort = true; + } else if (securityState & nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) { + error.AssignLiteral("weakCryptoUsed"); + addHostPort = true; + } else { + // Usually we should have aFailedChannel and get a detailed message + tsi->GetErrorMessage(getter_Copies(messageStr)); + } + } else { + // No channel, let's obtain the generic error message + if (nsserr) { + nsserr->GetErrorMessage(aError, messageStr); + } + } + if (!messageStr.IsEmpty()) { + if (errorClass == nsINSSErrorsService::ERROR_CLASS_BAD_CERT) { + error.AssignLiteral("nssBadCert"); + + // If this is an HTTP Strict Transport Security host or a pinned host + // and the certificate is bad, don't allow overrides (RFC 6797 section + // 12.1, HPKP draft spec section 2.6). + uint32_t flags = + UsePrivateBrowsing() ? nsISocketProvider::NO_PERMANENT_STORAGE : 0; + bool isStsHost = false; + bool isPinnedHost = false; + if (XRE_IsParentProcess()) { + nsCOMPtr<nsISiteSecurityService> sss = + do_GetService(NS_SSSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, + flags, nullptr, &isStsHost); + NS_ENSURE_SUCCESS(rv, rv); + rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HPKP, aURI, + flags, nullptr, &isPinnedHost); + NS_ENSURE_SUCCESS(rv, rv); + } else { + mozilla::dom::ContentChild* cc = + mozilla::dom::ContentChild::GetSingleton(); + mozilla::ipc::URIParams uri; + SerializeURI(aURI, uri); + cc->SendIsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, flags, + &isStsHost); + cc->SendIsSecureURI(nsISiteSecurityService::HEADER_HPKP, uri, flags, + &isPinnedHost); + } + + if (Preferences::GetBool( + "browser.xul.error_pages.expert_bad_cert", false)) { + cssClass.AssignLiteral("expertBadCert"); + } + + // HSTS/pinning takes precedence over the expert bad cert pref. We + // never want to show the "Add Exception" button for these sites. + // In the future we should differentiate between an HSTS host and a + // pinned host and display a more informative message to the user. + if (isStsHost || isPinnedHost) { + cssClass.AssignLiteral("badStsCert"); + } + + uint32_t bucketId; + if (isStsHost) { + // measuring STS separately allows us to measure click through + // rates easily + bucketId = nsISecurityUITelemetry::WARNING_BAD_CERT_TOP_STS; + } else { + bucketId = nsISecurityUITelemetry::WARNING_BAD_CERT_TOP; + } + + // See if an alternate cert error page is registered + nsAdoptingCString alternateErrorPage = + Preferences::GetCString("security.alternate_certificate_error_page"); + if (alternateErrorPage) { + errorPage.Assign(alternateErrorPage); + } + + if (!IsFrame() && errorPage.EqualsIgnoreCase("certerror")) { + Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI, bucketId); + } + + } else { + error.AssignLiteral("nssFailure2"); + } + } + } else if (NS_ERROR_PHISHING_URI == aError || + NS_ERROR_MALWARE_URI == aError || + NS_ERROR_UNWANTED_URI == aError) { + nsAutoCString host; + aURI->GetHost(host); + CopyUTF8toUTF16(host, formatStrs[0]); + formatStrCount = 1; + + // Malware and phishing detectors may want to use an alternate error + // page, but if the pref's not set, we'll fall back on the standard page + nsAdoptingCString alternateErrorPage = + Preferences::GetCString("urlclassifier.alternate_error_page"); + if (alternateErrorPage) { + errorPage.Assign(alternateErrorPage); + } + + uint32_t bucketId; + bool sendTelemetry = false; + if (NS_ERROR_PHISHING_URI == aError) { + sendTelemetry = true; + error.AssignLiteral("deceptiveBlocked"); + bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_PHISHING_PAGE_FRAME + : nsISecurityUITelemetry::WARNING_PHISHING_PAGE_TOP; + } else if (NS_ERROR_MALWARE_URI == aError) { + sendTelemetry = true; + error.AssignLiteral("malwareBlocked"); + bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_MALWARE_PAGE_FRAME + : nsISecurityUITelemetry::WARNING_MALWARE_PAGE_TOP; + } else if (NS_ERROR_UNWANTED_URI == aError) { + sendTelemetry = true; + error.AssignLiteral("unwantedBlocked"); + bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_UNWANTED_PAGE_FRAME + : nsISecurityUITelemetry::WARNING_UNWANTED_PAGE_TOP; + } + + if (sendTelemetry && errorPage.EqualsIgnoreCase("blocked")) { + Telemetry::Accumulate(Telemetry::SECURITY_UI, bucketId); + } + + cssClass.AssignLiteral("blacklist"); + } else if (NS_ERROR_CONTENT_CRASHED == aError) { + errorPage.AssignLiteral("tabcrashed"); + error.AssignLiteral("tabcrashed"); + + nsCOMPtr<EventTarget> handler = mChromeEventHandler; + if (handler) { + nsCOMPtr<Element> element = do_QueryInterface(handler); + element->GetAttribute(NS_LITERAL_STRING("crashedPageTitle"), messageStr); + } + + // DisplayLoadError requires a non-empty messageStr to proceed and call + // LoadErrorPage. If the page doesn't have a title, we will use a blank + // space which will be trimmed and thus treated as empty by the front-end. + if (messageStr.IsEmpty()) { + messageStr.AssignLiteral(u" "); + } + } else { + // Errors requiring simple formatting + switch (aError) { + case NS_ERROR_MALFORMED_URI: + // URI is malformed + error.AssignLiteral("malformedURI"); + break; + case NS_ERROR_REDIRECT_LOOP: + // Doc failed to load because the server generated too many redirects + error.AssignLiteral("redirectLoop"); + break; + case NS_ERROR_UNKNOWN_SOCKET_TYPE: + // Doc failed to load because PSM is not installed + error.AssignLiteral("unknownSocketType"); + break; + case NS_ERROR_NET_RESET: + // Doc failed to load because the server kept reseting the connection + // before we could read any data from it + error.AssignLiteral("netReset"); + break; + case NS_ERROR_DOCUMENT_NOT_CACHED: + // Doc failed to load because the cache does not contain a copy of + // the document. + error.AssignLiteral("notCached"); + break; + case NS_ERROR_OFFLINE: + // Doc failed to load because we are offline. + error.AssignLiteral("netOffline"); + break; + case NS_ERROR_DOCUMENT_IS_PRINTMODE: + // Doc navigation attempted while Printing or Print Preview + error.AssignLiteral("isprinting"); + break; + case NS_ERROR_PORT_ACCESS_NOT_ALLOWED: + // Port blocked for security reasons + addHostPort = true; + error.AssignLiteral("deniedPortAccess"); + break; + case NS_ERROR_UNKNOWN_PROXY_HOST: + // Proxy hostname could not be resolved. + error.AssignLiteral("proxyResolveFailure"); + break; + case NS_ERROR_PROXY_CONNECTION_REFUSED: + // Proxy connection was refused. + error.AssignLiteral("proxyConnectFailure"); + break; + case NS_ERROR_INVALID_CONTENT_ENCODING: + // Bad Content Encoding. + error.AssignLiteral("contentEncodingError"); + break; + case NS_ERROR_REMOTE_XUL: + error.AssignLiteral("remoteXUL"); + break; + case NS_ERROR_UNSAFE_CONTENT_TYPE: + // Channel refused to load from an unrecognized content type. + error.AssignLiteral("unsafeContentType"); + break; + case NS_ERROR_CORRUPTED_CONTENT: + // Broken Content Detected. e.g. Content-MD5 check failure. + error.AssignLiteral("corruptedContentErrorv2"); + break; + case NS_ERROR_INTERCEPTION_FAILED: + // ServiceWorker intercepted request, but something went wrong. + error.AssignLiteral("corruptedContentErrorv2"); + break; + case NS_ERROR_NET_INADEQUATE_SECURITY: + // Server negotiated bad TLS for HTTP/2. + error.AssignLiteral("inadequateSecurityError"); + addHostPort = true; + break; + default: + break; + } + } + + // Test if the error should be displayed + if (error.IsEmpty()) { + return NS_OK; + } + + // Test if the error needs to be formatted + if (!messageStr.IsEmpty()) { + // already obtained message + } else { + if (addHostPort) { + // Build up the host:port string. + nsAutoCString hostport; + if (aURI) { + aURI->GetHostPort(hostport); + } else { + hostport.Assign('?'); + } + CopyUTF8toUTF16(hostport, formatStrs[formatStrCount++]); + } + + nsAutoCString spec; + rv = NS_ERROR_NOT_AVAILABLE; + if (aURI) { + // displaying "file://" is aesthetically unpleasing and could even be + // confusing to the user + bool isFileURI = false; + rv = aURI->SchemeIs("file", &isFileURI); + if (NS_SUCCEEDED(rv) && isFileURI) { + aURI->GetPath(spec); + } else { + aURI->GetSpec(spec); + } + + nsAutoCString charset; + // unescape and convert from origin charset + aURI->GetOriginCharset(charset); + nsCOMPtr<nsITextToSubURI> textToSubURI( + do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = textToSubURI->UnEscapeURIForUI(charset, spec, + formatStrs[formatStrCount]); + } + } else { + spec.Assign('?'); + } + if (NS_FAILED(rv)) { + CopyUTF8toUTF16(spec, formatStrs[formatStrCount]); + } + rv = NS_OK; + ++formatStrCount; + + const char16_t* strs[kMaxFormatStrArgs]; + for (uint32_t i = 0; i < formatStrCount; i++) { + strs[i] = formatStrs[i].get(); + } + nsXPIDLString str; + rv = stringBundle->FormatStringFromName(error.get(), strs, formatStrCount, + getter_Copies(str)); + NS_ENSURE_SUCCESS(rv, rv); + messageStr.Assign(str.get()); + } + + // Display the error as a page or an alert prompt + NS_ENSURE_FALSE(messageStr.IsEmpty(), NS_ERROR_FAILURE); + + if (NS_ERROR_NET_INTERRUPT == aError || NS_ERROR_NET_RESET == aError) { + bool isSecureURI = false; + rv = aURI->SchemeIs("https", &isSecureURI); + if (NS_SUCCEEDED(rv) && isSecureURI) { + // Maybe TLS intolerant. Treat this as an SSL error. + error.AssignLiteral("nssFailure2"); + } + } + + if (UseErrorPages()) { + // Display an error page + nsresult loadedPage = LoadErrorPage(aURI, aURL, errorPage.get(), + error.get(), messageStr.get(), + cssClass.get(), aFailedChannel); + *aDisplayedErrorPage = NS_SUCCEEDED(loadedPage); + } else { + // The prompter reqires that our private window has a document (or it + // asserts). Satisfy that assertion now since GetDoc will force + // creation of one if it hasn't already been created. + if (mScriptGlobal) { + Unused << mScriptGlobal->GetDoc(); + } + + // Display a message box + prompter->Alert(nullptr, messageStr.get()); + } + + return NS_OK; +} + +#define PREF_SAFEBROWSING_ALLOWOVERRIDE "browser.safebrowsing.allowOverride" + +NS_IMETHODIMP +nsDocShell::LoadErrorPage(nsIURI* aURI, const char16_t* aURL, + const char* aErrorPage, + const char16_t* aErrorType, + const char16_t* aDescription, + const char* aCSSClass, + nsIChannel* aFailedChannel) +{ +#if defined(DEBUG) + if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) { + nsAutoCString chanName; + if (aFailedChannel) { + aFailedChannel->GetName(chanName); + } else { + chanName.AssignLiteral("<no channel>"); + } + + MOZ_LOG(gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]::LoadErrorPage(\"%s\", \"%s\", {...}, [%s])\n", this, + aURI->GetSpecOrDefault().get(), NS_ConvertUTF16toUTF8(aURL).get(), + chanName.get())); + } +#endif + mFailedChannel = aFailedChannel; + mFailedURI = aURI; + mFailedLoadType = mLoadType; + + if (mLSHE) { + // Abandon mLSHE's BFCache entry and create a new one. This way, if + // we go back or forward to another SHEntry with the same doc + // identifier, the error page won't persist. + mLSHE->AbandonBFCacheEntry(); + } + + nsAutoCString url; + nsAutoCString charset; + if (aURI) { + nsresult rv = aURI->GetSpec(url); + NS_ENSURE_SUCCESS(rv, rv); + rv = aURI->GetOriginCharset(charset); + NS_ENSURE_SUCCESS(rv, rv); + } else if (aURL) { + CopyUTF16toUTF8(aURL, url); + } else { + return NS_ERROR_INVALID_POINTER; + } + + // Create a URL to pass all the error information through to the page. + +#undef SAFE_ESCAPE +#define SAFE_ESCAPE(output, input, params) \ + if (NS_WARN_IF(!NS_Escape(input, output, params))) { \ + return NS_ERROR_OUT_OF_MEMORY; \ + } + + nsCString escapedUrl, escapedCharset, escapedError, escapedDescription, + escapedCSSClass; + SAFE_ESCAPE(escapedUrl, url, url_Path); + SAFE_ESCAPE(escapedCharset, charset, url_Path); + SAFE_ESCAPE(escapedError, NS_ConvertUTF16toUTF8(aErrorType), url_Path); + SAFE_ESCAPE(escapedDescription, + NS_ConvertUTF16toUTF8(aDescription), url_Path); + if (aCSSClass) { + nsCString cssClass(aCSSClass); + SAFE_ESCAPE(escapedCSSClass, cssClass, url_Path); + } + nsCString errorPageUrl("about:"); + errorPageUrl.AppendASCII(aErrorPage); + errorPageUrl.AppendLiteral("?e="); + + errorPageUrl.AppendASCII(escapedError.get()); + errorPageUrl.AppendLiteral("&u="); + errorPageUrl.AppendASCII(escapedUrl.get()); + if ((strcmp(aErrorPage, "blocked") == 0) && + Preferences::GetBool(PREF_SAFEBROWSING_ALLOWOVERRIDE, true)) { + errorPageUrl.AppendLiteral("&o=1"); + } + if (!escapedCSSClass.IsEmpty()) { + errorPageUrl.AppendLiteral("&s="); + errorPageUrl.AppendASCII(escapedCSSClass.get()); + } + errorPageUrl.AppendLiteral("&c="); + errorPageUrl.AppendASCII(escapedCharset.get()); + + nsAutoCString frameType(FrameTypeToString(mFrameType)); + errorPageUrl.AppendLiteral("&f="); + errorPageUrl.AppendASCII(frameType.get()); + + // Append the manifest URL if the error comes from an app. + nsString manifestURL; + nsresult rv = GetAppManifestURL(manifestURL); + if (manifestURL.Length() > 0) { + nsCString manifestParam; + SAFE_ESCAPE(manifestParam, NS_ConvertUTF16toUTF8(manifestURL), url_Path); + errorPageUrl.AppendLiteral("&m="); + errorPageUrl.AppendASCII(manifestParam.get()); + } + + nsCOMPtr<nsICaptivePortalService> cps = do_GetService(NS_CAPTIVEPORTAL_CID); + int32_t cpsState; + if (cps && NS_SUCCEEDED(cps->GetState(&cpsState)) && + cpsState == nsICaptivePortalService::LOCKED_PORTAL) { + errorPageUrl.AppendLiteral("&captive=true"); + } + + // netError.xhtml's getDescription only handles the "d" parameter at the + // end of the URL, so append it last. + errorPageUrl.AppendLiteral("&d="); + errorPageUrl.AppendASCII(escapedDescription.get()); + + nsCOMPtr<nsIURI> errorPageURI; + rv = NS_NewURI(getter_AddRefs(errorPageURI), errorPageUrl); + NS_ENSURE_SUCCESS(rv, rv); + + return InternalLoad(errorPageURI, nullptr, false, nullptr, + mozilla::net::RP_Default, + nsContentUtils::GetSystemPrincipal(), nullptr, + INTERNAL_LOAD_FLAGS_NONE, EmptyString(), + nullptr, NullString(), nullptr, nullptr, LOAD_ERROR_PAGE, + nullptr, true, NullString(), this, nullptr, nullptr, + nullptr); +} + +NS_IMETHODIMP +nsDocShell::Reload(uint32_t aReloadFlags) +{ + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + nsresult rv; + NS_ASSERTION(((aReloadFlags & 0xf) == 0), + "Reload command not updated to use load flags!"); + NS_ASSERTION((aReloadFlags & EXTRA_LOAD_FLAGS) == 0, + "Don't pass these flags to Reload"); + + uint32_t loadType = MAKE_LOAD_TYPE(LOAD_RELOAD_NORMAL, aReloadFlags); + NS_ENSURE_TRUE(IsValidLoadType(loadType), NS_ERROR_INVALID_ARG); + + // Send notifications to the HistoryListener if any, about the impending + // reload + nsCOMPtr<nsISHistory> rootSH; + rv = GetRootSessionHistory(getter_AddRefs(rootSH)); + nsCOMPtr<nsISHistoryInternal> shistInt(do_QueryInterface(rootSH)); + bool canReload = true; + if (rootSH) { + shistInt->NotifyOnHistoryReload(mCurrentURI, aReloadFlags, &canReload); + } + + if (!canReload) { + return NS_OK; + } + + /* If you change this part of code, make sure bug 45297 does not re-occur */ + if (mOSHE) { + rv = LoadHistoryEntry(mOSHE, loadType); + } else if (mLSHE) { // In case a reload happened before the current load is done + rv = LoadHistoryEntry(mLSHE, loadType); + } else { + nsCOMPtr<nsIDocument> doc(GetDocument()); + + if (!doc) { + return NS_OK; + } + + // Do not inherit owner from document + uint32_t flags = INTERNAL_LOAD_FLAGS_NONE; + nsAutoString srcdoc; + nsCOMPtr<nsIURI> baseURI; + nsCOMPtr<nsIURI> originalURI; + bool loadReplace = false; + + nsIPrincipal* triggeringPrincipal = doc->NodePrincipal(); + nsAutoString contentTypeHint; + doc->GetContentType(contentTypeHint); + + if (doc->IsSrcdocDocument()) { + doc->GetSrcdocData(srcdoc); + flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC; + baseURI = doc->GetBaseURI(); + } + nsCOMPtr<nsIChannel> chan = doc->GetChannel(); + if (chan) { + uint32_t loadFlags; + chan->GetLoadFlags(&loadFlags); + loadReplace = loadFlags & nsIChannel::LOAD_REPLACE; + nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(chan)); + if (httpChan) { + httpChan->GetOriginalURI(getter_AddRefs(originalURI)); + } + } + + MOZ_ASSERT(triggeringPrincipal, "Need a valid triggeringPrincipal"); + + // Stack variables to ensure changes to the member variables don't affect to + // the call. + nsCOMPtr<nsIURI> currentURI = mCurrentURI; + nsCOMPtr<nsIURI> referrerURI = mReferrerURI; + uint32_t referrerPolicy = mReferrerPolicy; + rv = InternalLoad(currentURI, + originalURI, + loadReplace, + referrerURI, + referrerPolicy, + triggeringPrincipal, + triggeringPrincipal, + flags, + EmptyString(), // No window target + NS_LossyConvertUTF16toASCII(contentTypeHint).get(), + NullString(), // No forced download + nullptr, // No post data + nullptr, // No headers data + loadType, // Load type + nullptr, // No SHEntry + true, + srcdoc, // srcdoc argument for iframe + this, // For reloads we are the source + baseURI, + nullptr, // No nsIDocShell + nullptr); // No nsIRequest + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::Stop(uint32_t aStopFlags) +{ + // Revoke any pending event related to content viewer restoration + mRestorePresentationEvent.Revoke(); + + if (mLoadType == LOAD_ERROR_PAGE) { + if (mLSHE) { + // Since error page loads never unset mLSHE, do so now + SetHistoryEntry(&mOSHE, mLSHE); + SetHistoryEntry(&mLSHE, nullptr); + } + + mFailedChannel = nullptr; + mFailedURI = nullptr; + } + + if (nsIWebNavigation::STOP_CONTENT & aStopFlags) { + // Stop the document loading + if (mContentViewer) { + nsCOMPtr<nsIContentViewer> cv = mContentViewer; + cv->Stop(); + } + } + + if (nsIWebNavigation::STOP_NETWORK & aStopFlags) { + // Suspend any timers that were set for this loader. We'll clear + // them out for good in CreateContentViewer. + if (mRefreshURIList) { + SuspendRefreshURIs(); + mSavedRefreshURIList.swap(mRefreshURIList); + mRefreshURIList = nullptr; + } + + // XXXbz We could also pass |this| to nsIURILoader::Stop. That will + // just call Stop() on us as an nsIDocumentLoader... We need fewer + // redundant apis! + Stop(); + } + + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIWebNavigation> shellAsNav(do_QueryObject(iter.GetNext())); + if (shellAsNav) { + shellAsNav->Stop(aStopFlags); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDocument(nsIDOMDocument** aDocument) +{ + NS_ENSURE_ARG_POINTER(aDocument); + NS_ENSURE_SUCCESS(EnsureContentViewer(), NS_ERROR_FAILURE); + + return mContentViewer->GetDOMDocument(aDocument); +} + +NS_IMETHODIMP +nsDocShell::GetCurrentURI(nsIURI** aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + + if (mCurrentURI) { + return NS_EnsureSafeToReturn(mCurrentURI, aURI); + } + + *aURI = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetReferringURI(nsIURI** aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + + *aURI = mReferrerURI; + NS_IF_ADDREF(*aURI); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetSessionHistory(nsISHistory* aSessionHistory) +{ + NS_ENSURE_TRUE(aSessionHistory, NS_ERROR_FAILURE); + // make sure that we are the root docshell and + // set a handle to root docshell in SH. + + nsCOMPtr<nsIDocShellTreeItem> root; + /* Get the root docshell. If *this* is the root docshell + * then save a handle to *this* in SH. SH needs it to do + * traversions thro' its entries + */ + GetSameTypeRootTreeItem(getter_AddRefs(root)); + NS_ENSURE_TRUE(root, NS_ERROR_FAILURE); + if (root.get() == static_cast<nsIDocShellTreeItem*>(this)) { + mSessionHistory = aSessionHistory; + nsCOMPtr<nsISHistoryInternal> shPrivate = + do_QueryInterface(mSessionHistory); + NS_ENSURE_TRUE(shPrivate, NS_ERROR_FAILURE); + shPrivate->SetRootDocShell(this); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::GetSessionHistory(nsISHistory** aSessionHistory) +{ + NS_ENSURE_ARG_POINTER(aSessionHistory); + *aSessionHistory = mSessionHistory; + NS_IF_ADDREF(*aSessionHistory); + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIWebPageDescriptor +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::LoadPage(nsISupports* aPageDescriptor, uint32_t aDisplayType) +{ + nsCOMPtr<nsISHEntry> shEntryIn(do_QueryInterface(aPageDescriptor)); + + // Currently, the opaque 'page descriptor' is an nsISHEntry... + if (!shEntryIn) { + return NS_ERROR_INVALID_POINTER; + } + + // Now clone shEntryIn, since we might end up modifying it later on, and we + // want a page descriptor to be reusable. + nsCOMPtr<nsISHEntry> shEntry; + nsresult rv = shEntryIn->Clone(getter_AddRefs(shEntry)); + NS_ENSURE_SUCCESS(rv, rv); + + // Give our cloned shEntry a new bfcache entry so this load is independent + // of all other loads. (This is important, in particular, for bugs 582795 + // and 585298.) + rv = shEntry->AbandonBFCacheEntry(); + NS_ENSURE_SUCCESS(rv, rv); + + // + // load the page as view-source + // + if (nsIWebPageDescriptor::DISPLAY_AS_SOURCE == aDisplayType) { + nsCOMPtr<nsIURI> oldUri, newUri; + nsCString spec, newSpec; + + // Create a new view-source URI and replace the original. + rv = shEntry->GetURI(getter_AddRefs(oldUri)); + if (NS_FAILED(rv)) { + return rv; + } + + oldUri->GetSpec(spec); + newSpec.AppendLiteral("view-source:"); + newSpec.Append(spec); + + rv = NS_NewURI(getter_AddRefs(newUri), newSpec); + if (NS_FAILED(rv)) { + return rv; + } + shEntry->SetURI(newUri); + shEntry->SetOriginalURI(nullptr); + } + + rv = LoadHistoryEntry(shEntry, LOAD_HISTORY); + return rv; +} + +NS_IMETHODIMP +nsDocShell::GetCurrentDescriptor(nsISupports** aPageDescriptor) +{ + NS_PRECONDITION(aPageDescriptor, "Null out param?"); + + *aPageDescriptor = nullptr; + + nsISHEntry* src = mOSHE ? mOSHE : mLSHE; + if (src) { + nsCOMPtr<nsISHEntry> dest; + + nsresult rv = src->Clone(getter_AddRefs(dest)); + if (NS_FAILED(rv)) { + return rv; + } + + // null out inappropriate cloned attributes... + dest->SetParent(nullptr); + dest->SetIsSubFrame(false); + + return CallQueryInterface(dest, aPageDescriptor); + } + + return NS_ERROR_NOT_AVAILABLE; +} + +//***************************************************************************** +// nsDocShell::nsIBaseWindow +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::InitWindow(nativeWindow aParentNativeWindow, + nsIWidget* aParentWidget, int32_t aX, int32_t aY, + int32_t aWidth, int32_t aHeight) +{ + SetParentWidget(aParentWidget); + SetPositionAndSize(aX, aY, aWidth, aHeight, 0); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::Create() +{ + if (mCreated) { + // We've already been created + return NS_OK; + } + + NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome, + "Unexpected item type in docshell"); + + NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE); + mCreated = true; + + if (gValidateOrigin == 0xffffffff) { + // Check pref to see if we should prevent frameset spoofing + gValidateOrigin = + Preferences::GetBool("browser.frame.validate_origin", true); + } + + // Should we use XUL error pages instead of alerts if possible? + mUseErrorPages = + Preferences::GetBool("browser.xul.error_pages.enabled", mUseErrorPages); + + if (!gAddedPreferencesVarCache) { + Preferences::AddBoolVarCache(&sUseErrorPages, + "browser.xul.error_pages.enabled", + mUseErrorPages); + gAddedPreferencesVarCache = true; + } + + mDisableMetaRefreshWhenInactive = + Preferences::GetBool("browser.meta_refresh_when_inactive.disabled", + mDisableMetaRefreshWhenInactive); + + mDeviceSizeIsPageSize = + Preferences::GetBool("docshell.device_size_is_page_size", + mDeviceSizeIsPageSize); + + nsCOMPtr<nsIObserverService> serv = services::GetObserverService(); + if (serv) { + const char* msg = mItemType == typeContent ? + NS_WEBNAVIGATION_CREATE : NS_CHROME_WEBNAVIGATION_CREATE; + serv->NotifyObservers(GetAsSupports(this), msg, nullptr); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::Destroy() +{ + NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome, + "Unexpected item type in docshell"); + + if (!mIsBeingDestroyed) { + nsCOMPtr<nsIObserverService> serv = services::GetObserverService(); + if (serv) { + const char* msg = mItemType == typeContent ? + NS_WEBNAVIGATION_DESTROY : NS_CHROME_WEBNAVIGATION_DESTROY; + serv->NotifyObservers(GetAsSupports(this), msg, nullptr); + } + } + + mIsBeingDestroyed = true; + + // Make sure we don't record profile timeline markers anymore + SetRecordProfileTimelineMarkers(false); + + // Remove our pref observers + if (mObserveErrorPages) { + mObserveErrorPages = false; + } + + // Make sure to blow away our mLoadingURI just in case. No loads + // from inside this pagehide. + mLoadingURI = nullptr; + + // Fire unload event before we blow anything away. + (void)FirePageHideNotification(true); + + // Clear pointers to any detached nsEditorData that's lying + // around in shistory entries. Breaks cycle. See bug 430921. + if (mOSHE) { + mOSHE->SetEditorData(nullptr); + } + if (mLSHE) { + mLSHE->SetEditorData(nullptr); + } + + // Note: mContentListener can be null if Init() failed and we're being + // called from the destructor. + if (mContentListener) { + mContentListener->DropDocShellReference(); + mContentListener->SetParentContentListener(nullptr); + // Note that we do NOT set mContentListener to null here; that + // way if someone tries to do a load in us after this point + // the nsDSURIContentListener will block it. All of which + // means that we should do this before calling Stop(), of + // course. + } + + // Stop any URLs that are currently being loaded... + Stop(nsIWebNavigation::STOP_ALL); + + mEditorData = nullptr; + + mTransferableHookData = nullptr; + + // Save the state of the current document, before destroying the window. + // This is needed to capture the state of a frameset when the new document + // causes the frameset to be destroyed... + PersistLayoutHistoryState(); + + // Remove this docshell from its parent's child list + nsCOMPtr<nsIDocShellTreeItem> docShellParentAsItem = + do_QueryInterface(GetAsSupports(mParent)); + if (docShellParentAsItem) { + docShellParentAsItem->RemoveChild(this); + } + + if (mContentViewer) { + mContentViewer->Close(nullptr); + mContentViewer->Destroy(); + mContentViewer = nullptr; + } + + nsDocLoader::Destroy(); + + mParentWidget = nullptr; + mCurrentURI = nullptr; + + if (mScriptGlobal) { + mScriptGlobal->DetachFromDocShell(); + mScriptGlobal = nullptr; + } + + if (mSessionHistory) { + // We want to destroy these content viewers now rather than + // letting their destruction wait for the session history + // entries to get garbage collected. (Bug 488394) + nsCOMPtr<nsISHistoryInternal> shPrivate = + do_QueryInterface(mSessionHistory); + if (shPrivate) { + shPrivate->EvictAllContentViewers(); + } + mSessionHistory = nullptr; + } + + SetTreeOwner(nullptr); + + mOnePermittedSandboxedNavigator = nullptr; + + // required to break ref cycle + mSecurityUI = nullptr; + + // Cancel any timers that were set for this docshell; this is needed + // to break the cycle between us and the timers. + CancelRefreshURITimers(); + + if (UsePrivateBrowsing()) { + mPrivateBrowsingId = 0; + mOriginAttributes.SyncAttributesWithPrivateBrowsing(false); + if (mAffectPrivateSessionLifetime) { + DecreasePrivateDocShellCount(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetUnscaledDevicePixelsPerCSSPixel(double* aScale) +{ + if (mParentWidget) { + *aScale = mParentWidget->GetDefaultScale().scale; + return NS_OK; + } + + nsCOMPtr<nsIBaseWindow> ownerWindow(do_QueryInterface(mTreeOwner)); + if (ownerWindow) { + return ownerWindow->GetUnscaledDevicePixelsPerCSSPixel(aScale); + } + + *aScale = 1.0; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDevicePixelsPerDesktopPixel(double* aScale) +{ + if (mParentWidget) { + *aScale = mParentWidget->GetDesktopToDeviceScale().scale; + return NS_OK; + } + + nsCOMPtr<nsIBaseWindow> ownerWindow(do_QueryInterface(mTreeOwner)); + if (ownerWindow) { + return ownerWindow->GetDevicePixelsPerDesktopPixel(aScale); + } + + *aScale = 1.0; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetPosition(int32_t aX, int32_t aY) +{ + mBounds.x = aX; + mBounds.y = aY; + + if (mContentViewer) { + NS_ENSURE_SUCCESS(mContentViewer->Move(aX, aY), NS_ERROR_FAILURE); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetPositionDesktopPix(int32_t aX, int32_t aY) +{ + nsCOMPtr<nsIBaseWindow> ownerWindow(do_QueryInterface(mTreeOwner)); + if (ownerWindow) { + return ownerWindow->SetPositionDesktopPix(aX, aY); + } + + double scale = 1.0; + GetDevicePixelsPerDesktopPixel(&scale); + return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale)); +} + +NS_IMETHODIMP +nsDocShell::GetPosition(int32_t* aX, int32_t* aY) +{ + return GetPositionAndSize(aX, aY, nullptr, nullptr); +} + +NS_IMETHODIMP +nsDocShell::SetSize(int32_t aWidth, int32_t aHeight, bool aRepaint) +{ + int32_t x = 0, y = 0; + GetPosition(&x, &y); + return SetPositionAndSize(x, y, aWidth, aHeight, + aRepaint ? nsIBaseWindow::eRepaint : 0); +} + +NS_IMETHODIMP +nsDocShell::GetSize(int32_t* aWidth, int32_t* aHeight) +{ + return GetPositionAndSize(nullptr, nullptr, aWidth, aHeight); +} + +NS_IMETHODIMP +nsDocShell::SetPositionAndSize(int32_t aX, int32_t aY, int32_t aWidth, + int32_t aHeight, uint32_t aFlags) +{ + mBounds.x = aX; + mBounds.y = aY; + mBounds.width = aWidth; + mBounds.height = aHeight; + + // Hold strong ref, since SetBounds can make us null out mContentViewer + nsCOMPtr<nsIContentViewer> viewer = mContentViewer; + if (viewer) { + uint32_t cvflags = (aFlags & nsIBaseWindow::eDelayResize) ? + nsIContentViewer::eDelayResize : 0; + // XXX Border figured in here or is that handled elsewhere? + nsresult rv = viewer->SetBoundsWithFlags(mBounds, cvflags); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth, + int32_t* aHeight) +{ + if (mParentWidget) { + // ensure size is up-to-date if window has changed resolution + LayoutDeviceIntRect r = mParentWidget->GetClientBounds(); + SetPositionAndSize(mBounds.x, mBounds.y, r.width, r.height, 0); + } + + // We should really consider just getting this information from + // our window instead of duplicating the storage and code... + if (aWidth || aHeight) { + // Caller wants to know our size; make sure to give them up to + // date information. + nsCOMPtr<nsIDocument> doc(do_GetInterface(GetAsSupports(mParent))); + if (doc) { + doc->FlushPendingNotifications(Flush_Layout); + } + } + + DoGetPositionAndSize(aX, aY, aWidth, aHeight); + return NS_OK; +} + +void +nsDocShell::DoGetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth, + int32_t* aHeight) +{ + if (aX) { + *aX = mBounds.x; + } + if (aY) { + *aY = mBounds.y; + } + if (aWidth) { + *aWidth = mBounds.width; + } + if (aHeight) { + *aHeight = mBounds.height; + } +} + +NS_IMETHODIMP +nsDocShell::Repaint(bool aForce) +{ + nsCOMPtr<nsIPresShell> presShell = GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + + nsViewManager* viewManager = presShell->GetViewManager(); + NS_ENSURE_TRUE(viewManager, NS_ERROR_FAILURE); + + viewManager->InvalidateAllViews(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetParentWidget(nsIWidget** aParentWidget) +{ + NS_ENSURE_ARG_POINTER(aParentWidget); + + *aParentWidget = mParentWidget; + NS_IF_ADDREF(*aParentWidget); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetParentWidget(nsIWidget* aParentWidget) +{ + mParentWidget = aParentWidget; + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetParentNativeWindow(nativeWindow* aParentNativeWindow) +{ + NS_ENSURE_ARG_POINTER(aParentNativeWindow); + + if (mParentWidget) { + *aParentNativeWindow = mParentWidget->GetNativeData(NS_NATIVE_WIDGET); + } else { + *aParentNativeWindow = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetParentNativeWindow(nativeWindow aParentNativeWindow) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShell::GetNativeHandle(nsAString& aNativeHandle) +{ + // the nativeHandle should be accessed from nsIXULWindow + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShell::GetVisibility(bool* aVisibility) +{ + NS_ENSURE_ARG_POINTER(aVisibility); + + *aVisibility = false; + + if (!mContentViewer) { + return NS_OK; + } + + nsCOMPtr<nsIPresShell> presShell = GetPresShell(); + if (!presShell) { + return NS_OK; + } + + // get the view manager + nsViewManager* vm = presShell->GetViewManager(); + NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE); + + // get the root view + nsView* view = vm->GetRootView(); // views are not ref counted + NS_ENSURE_TRUE(view, NS_ERROR_FAILURE); + + // if our root view is hidden, we are not visible + if (view->GetVisibility() == nsViewVisibility_kHide) { + return NS_OK; + } + + // otherwise, we must walk up the document and view trees checking + // for a hidden view, unless we're an off screen browser, which + // would make this test meaningless. + + RefPtr<nsDocShell> docShell = this; + RefPtr<nsDocShell> parentItem = docShell->GetParentDocshell(); + while (parentItem) { + presShell = docShell->GetPresShell(); + + nsCOMPtr<nsIPresShell> pPresShell = parentItem->GetPresShell(); + + // Null-check for crash in bug 267804 + if (!pPresShell) { + NS_NOTREACHED("parent docshell has null pres shell"); + return NS_OK; + } + + vm = presShell->GetViewManager(); + if (vm) { + view = vm->GetRootView(); + } + + if (view) { + view = view->GetParent(); // anonymous inner view + if (view) { + view = view->GetParent(); // subdocumentframe's view + } + } + + nsIFrame* frame = view ? view->GetFrame() : nullptr; + bool isDocShellOffScreen = false; + docShell->GetIsOffScreenBrowser(&isDocShellOffScreen); + if (frame && + !frame->IsVisibleConsideringAncestors( + nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) && + !isDocShellOffScreen) { + return NS_OK; + } + + docShell = parentItem; + parentItem = docShell->GetParentDocshell(); + } + + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin(do_QueryInterface(mTreeOwner)); + if (!treeOwnerAsWin) { + *aVisibility = true; + return NS_OK; + } + + // Check with the tree owner as well to give embedders a chance to + // expose visibility as well. + return treeOwnerAsWin->GetVisibility(aVisibility); +} + +NS_IMETHODIMP +nsDocShell::SetIsOffScreenBrowser(bool aIsOffScreen) +{ + mIsOffScreenBrowser = aIsOffScreen; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsOffScreenBrowser(bool* aIsOffScreen) +{ + *aIsOffScreen = mIsOffScreenBrowser; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetIsActive(bool aIsActive) +{ + // We disallow setting active on chrome docshells. + if (mItemType == nsIDocShellTreeItem::typeChrome) { + return NS_ERROR_INVALID_ARG; + } + + // Keep track ourselves. + mIsActive = aIsActive; + + // Clear prerender flag if necessary. + mIsPrerendered &= !aIsActive; + + // Tell the PresShell about it. + nsCOMPtr<nsIPresShell> pshell = GetPresShell(); + if (pshell) { + pshell->SetIsActive(aIsActive); + } + + // Tell the window about it + if (mScriptGlobal) { + mScriptGlobal->SetIsBackground(!aIsActive); + if (nsCOMPtr<nsIDocument> doc = mScriptGlobal->GetExtantDoc()) { + // Update orientation when the top-level browsing context becomes active. + // We make an exception for apps because they currently rely on + // orientation locks persisting across browsing contexts. + if (aIsActive && !GetIsApp()) { + nsCOMPtr<nsIDocShellTreeItem> parent; + GetSameTypeParent(getter_AddRefs(parent)); + if (!parent) { + // We only care about the top-level browsing context. + uint16_t orientation = OrientationLock(); + ScreenOrientation::UpdateActiveOrientationLock(orientation); + } + } + + doc->PostVisibilityUpdateEvent(); + } + } + + // Tell the nsDOMNavigationTiming about it + RefPtr<nsDOMNavigationTiming> timing = mTiming; + if (!timing && mContentViewer) { + nsIDocument* doc = mContentViewer->GetDocument(); + if (doc) { + timing = doc->GetNavigationTiming(); + } + } + if (timing) { + timing->NotifyDocShellStateChanged( + aIsActive ? nsDOMNavigationTiming::DocShellState::eActive + : nsDOMNavigationTiming::DocShellState::eInactive); + } + + // Recursively tell all of our children, but don't tell <iframe mozbrowser> + // children; they handle their state separately. + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> docshell = do_QueryObject(iter.GetNext()); + if (!docshell) { + continue; + } + + if (!docshell->GetIsMozBrowserOrApp()) { + docshell->SetIsActive(aIsActive); + } + } + + // Restart or stop meta refresh timers if necessary + if (mDisableMetaRefreshWhenInactive) { + if (mIsActive) { + ResumeRefreshURIs(); + } else { + SuspendRefreshURIs(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsActive(bool* aIsActive) +{ + *aIsActive = mIsActive; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetIsPrerendered() +{ + MOZ_ASSERT(!mIsPrerendered, + "SetIsPrerendered() called on already prerendered docshell"); + SetIsActive(false); + mIsPrerendered = true; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsPrerendered(bool* aIsPrerendered) +{ + *aIsPrerendered = mIsPrerendered; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetIsAppTab(bool aIsAppTab) +{ + mIsAppTab = aIsAppTab; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsAppTab(bool* aIsAppTab) +{ + *aIsAppTab = mIsAppTab; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetSandboxFlags(uint32_t aSandboxFlags) +{ + mSandboxFlags = aSandboxFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSandboxFlags(uint32_t* aSandboxFlags) +{ + *aSandboxFlags = mSandboxFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetOnePermittedSandboxedNavigator(nsIDocShell* aSandboxedNavigator) +{ + if (mOnePermittedSandboxedNavigator) { + NS_ERROR("One Permitted Sandboxed Navigator should only be set once."); + return NS_OK; + } + + mOnePermittedSandboxedNavigator = do_GetWeakReference(aSandboxedNavigator); + NS_ASSERTION(mOnePermittedSandboxedNavigator, + "One Permitted Sandboxed Navigator must support weak references."); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetOnePermittedSandboxedNavigator(nsIDocShell** aSandboxedNavigator) +{ + NS_ENSURE_ARG_POINTER(aSandboxedNavigator); + nsCOMPtr<nsIDocShell> permittedNavigator = + do_QueryReferent(mOnePermittedSandboxedNavigator); + permittedNavigator.forget(aSandboxedNavigator); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetDefaultLoadFlags(uint32_t aDefaultLoadFlags) +{ + mDefaultLoadFlags = aDefaultLoadFlags; + + // Tell the load group to set these flags all requests in the group + if (mLoadGroup) { + mLoadGroup->SetDefaultLoadFlags(aDefaultLoadFlags); + } else { + NS_WARNING("nsDocShell::SetDefaultLoadFlags has no loadGroup to propagate the flags to"); + } + + // Recursively tell all of our children. We *do not* skip + // <iframe mozbrowser> children - if someone sticks custom flags in this + // docShell then they too get the same flags. + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> docshell = do_QueryObject(iter.GetNext()); + if (!docshell) { + continue; + } + docshell->SetDefaultLoadFlags(aDefaultLoadFlags); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDefaultLoadFlags(uint32_t* aDefaultLoadFlags) +{ + *aDefaultLoadFlags = mDefaultLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetMixedContentChannel(nsIChannel* aMixedContentChannel) +{ +#ifdef DEBUG + // if the channel is non-null + if (aMixedContentChannel) { + // Get the root docshell. + nsCOMPtr<nsIDocShellTreeItem> root; + GetSameTypeRootTreeItem(getter_AddRefs(root)); + NS_WARNING_ASSERTION(root.get() == static_cast<nsIDocShellTreeItem*>(this), + "Setting mMixedContentChannel on a docshell that is " + "not the root docshell"); + } +#endif + mMixedContentChannel = aMixedContentChannel; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetFailedChannel(nsIChannel** aFailedChannel) +{ + NS_ENSURE_ARG_POINTER(aFailedChannel); + nsIDocument* doc = GetDocument(); + if (!doc) { + *aFailedChannel = nullptr; + return NS_OK; + } + NS_IF_ADDREF(*aFailedChannel = doc->GetFailedChannel()); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetMixedContentChannel(nsIChannel** aMixedContentChannel) +{ + NS_ENSURE_ARG_POINTER(aMixedContentChannel); + NS_IF_ADDREF(*aMixedContentChannel = mMixedContentChannel); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowMixedContentAndConnectionData(bool* aRootHasSecureConnection, + bool* aAllowMixedContent, + bool* aIsRootDocShell) +{ + *aRootHasSecureConnection = true; + *aAllowMixedContent = false; + *aIsRootDocShell = false; + + nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot; + GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot)); + NS_ASSERTION(sameTypeRoot, + "No document shell root tree item from document shell tree item!"); + *aIsRootDocShell = + sameTypeRoot.get() == static_cast<nsIDocShellTreeItem*>(this); + + // now get the document from sameTypeRoot + nsCOMPtr<nsIDocument> rootDoc = sameTypeRoot->GetDocument(); + if (rootDoc) { + nsCOMPtr<nsIPrincipal> rootPrincipal = rootDoc->NodePrincipal(); + + // For things with system principal (e.g. scratchpad) there is no uri + // aRootHasSecureConnection should be false. + nsCOMPtr<nsIURI> rootUri; + if (nsContentUtils::IsSystemPrincipal(rootPrincipal) || + NS_FAILED(rootPrincipal->GetURI(getter_AddRefs(rootUri))) || !rootUri || + NS_FAILED(rootUri->SchemeIs("https", aRootHasSecureConnection))) { + *aRootHasSecureConnection = false; + } + + // Check the root doc's channel against the root docShell's + // mMixedContentChannel to see if they are the same. If they are the same, + // the user has overriden the block. + nsCOMPtr<nsIDocShell> rootDocShell = do_QueryInterface(sameTypeRoot); + nsCOMPtr<nsIChannel> mixedChannel; + rootDocShell->GetMixedContentChannel(getter_AddRefs(mixedChannel)); + *aAllowMixedContent = + mixedChannel && (mixedChannel == rootDoc->GetChannel()); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetVisibility(bool aVisibility) +{ + // Show()/Hide() may change mContentViewer. + nsCOMPtr<nsIContentViewer> cv = mContentViewer; + if (!cv) { + return NS_OK; + } + if (aVisibility) { + cv->Show(); + } else { + cv->Hide(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetEnabled(bool* aEnabled) +{ + NS_ENSURE_ARG_POINTER(aEnabled); + *aEnabled = true; + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShell::SetEnabled(bool aEnabled) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShell::SetFocus() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetMainWidget(nsIWidget** aMainWidget) +{ + // We don't create our own widget, so simply return the parent one. + return GetParentWidget(aMainWidget); +} + +NS_IMETHODIMP +nsDocShell::GetTitle(char16_t** aTitle) +{ + NS_ENSURE_ARG_POINTER(aTitle); + + *aTitle = ToNewUnicode(mTitle); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetTitle(const char16_t* aTitle) +{ + // Store local title + mTitle = aTitle; + + nsCOMPtr<nsIDocShellTreeItem> parent; + GetSameTypeParent(getter_AddRefs(parent)); + + // When title is set on the top object it should then be passed to the + // tree owner. + if (!parent) { + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin(do_QueryInterface(mTreeOwner)); + if (treeOwnerAsWin) { + treeOwnerAsWin->SetTitle(aTitle); + } + } + + AssertOriginAttributesMatchPrivateBrowsing(); + if (mCurrentURI && mLoadType != LOAD_ERROR_PAGE && mUseGlobalHistory && + !UsePrivateBrowsing()) { + nsCOMPtr<IHistory> history = services::GetHistoryService(); + if (history) { + history->SetURITitle(mCurrentURI, mTitle); + } else if (mGlobalHistory) { + mGlobalHistory->SetPageTitle(mCurrentURI, nsString(mTitle)); + } + } + + // Update SessionHistory with the document's title. + if (mOSHE && mLoadType != LOAD_BYPASS_HISTORY && + mLoadType != LOAD_ERROR_PAGE) { + mOSHE->SetTitle(mTitle); + } + + return NS_OK; +} + +nsresult +nsDocShell::GetCurScrollPos(int32_t aScrollOrientation, int32_t* aCurPos) +{ + NS_ENSURE_ARG_POINTER(aCurPos); + + nsIScrollableFrame* sf = GetRootScrollFrame(); + if (!sf) { + return NS_ERROR_FAILURE; + } + + nsPoint pt = sf->GetScrollPosition(); + + switch (aScrollOrientation) { + case ScrollOrientation_X: + *aCurPos = pt.x; + return NS_OK; + + case ScrollOrientation_Y: + *aCurPos = pt.y; + return NS_OK; + + default: + NS_ENSURE_TRUE(false, NS_ERROR_INVALID_ARG); + } +} + +nsresult +nsDocShell::SetCurScrollPosEx(int32_t aCurHorizontalPos, + int32_t aCurVerticalPos) +{ + nsIScrollableFrame* sf = GetRootScrollFrame(); + NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); + + sf->ScrollTo(nsPoint(aCurHorizontalPos, aCurVerticalPos), + nsIScrollableFrame::INSTANT); + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIScrollable +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::GetDefaultScrollbarPreferences(int32_t aScrollOrientation, + int32_t* aScrollbarPref) +{ + NS_ENSURE_ARG_POINTER(aScrollbarPref); + switch (aScrollOrientation) { + case ScrollOrientation_X: + *aScrollbarPref = mDefaultScrollbarPref.x; + return NS_OK; + + case ScrollOrientation_Y: + *aScrollbarPref = mDefaultScrollbarPref.y; + return NS_OK; + + default: + NS_ENSURE_TRUE(false, NS_ERROR_INVALID_ARG); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::SetDefaultScrollbarPreferences(int32_t aScrollOrientation, + int32_t aScrollbarPref) +{ + switch (aScrollOrientation) { + case ScrollOrientation_X: + mDefaultScrollbarPref.x = aScrollbarPref; + return NS_OK; + + case ScrollOrientation_Y: + mDefaultScrollbarPref.y = aScrollbarPref; + return NS_OK; + + default: + NS_ENSURE_TRUE(false, NS_ERROR_INVALID_ARG); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::GetScrollbarVisibility(bool* aVerticalVisible, + bool* aHorizontalVisible) +{ + nsIScrollableFrame* sf = GetRootScrollFrame(); + NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); + + uint32_t scrollbarVisibility = sf->GetScrollbarVisibility(); + if (aVerticalVisible) { + *aVerticalVisible = + (scrollbarVisibility & nsIScrollableFrame::VERTICAL) != 0; + } + if (aHorizontalVisible) { + *aHorizontalVisible = + (scrollbarVisibility & nsIScrollableFrame::HORIZONTAL) != 0; + } + + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsITextScroll +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::ScrollByLines(int32_t aNumLines) +{ + nsIScrollableFrame* sf = GetRootScrollFrame(); + NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); + + sf->ScrollBy(nsIntPoint(0, aNumLines), nsIScrollableFrame::LINES, + nsIScrollableFrame::SMOOTH); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::ScrollByPages(int32_t aNumPages) +{ + nsIScrollableFrame* sf = GetRootScrollFrame(); + NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); + + sf->ScrollBy(nsIntPoint(0, aNumPages), nsIScrollableFrame::PAGES, + nsIScrollableFrame::SMOOTH); + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIRefreshURI +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::RefreshURI(nsIURI* aURI, + int32_t aDelay, bool aRepeat, + bool aMetaRefresh, + nsIPrincipal* aPrincipal) +{ + NS_ENSURE_ARG(aURI); + + /* Check if Meta refresh/redirects are permitted. Some + * embedded applications may not want to do this. + * Must do this before sending out NOTIFY_REFRESH events + * because listeners may have side effects (e.g. displaying a + * button to manually trigger the refresh later). + */ + bool allowRedirects = true; + GetAllowMetaRedirects(&allowRedirects); + if (!allowRedirects) { + return NS_OK; + } + + // If any web progress listeners are listening for NOTIFY_REFRESH events, + // give them a chance to block this refresh. + bool sameURI; + nsresult rv = aURI->Equals(mCurrentURI, &sameURI); + if (NS_FAILED(rv)) { + sameURI = false; + } + if (!RefreshAttempted(this, aURI, aDelay, sameURI)) { + return NS_OK; + } + + nsRefreshTimer* refreshTimer = new nsRefreshTimer(); + uint32_t busyFlags = 0; + GetBusyFlags(&busyFlags); + + nsCOMPtr<nsISupports> dataRef = refreshTimer; // Get the ref count to 1 + + refreshTimer->mDocShell = this; + refreshTimer->mPrincipal = aPrincipal; + refreshTimer->mURI = aURI; + refreshTimer->mDelay = aDelay; + refreshTimer->mRepeat = aRepeat; + refreshTimer->mMetaRefresh = aMetaRefresh; + + if (!mRefreshURIList) { + mRefreshURIList = nsArray::Create(); + } + + if (busyFlags & BUSY_FLAGS_BUSY || (!mIsActive && mDisableMetaRefreshWhenInactive)) { + // We don't want to create the timer right now. Instead queue up the request + // and trigger the timer in EndPageLoad() or whenever we become active. + mRefreshURIList->AppendElement(refreshTimer, /*weak =*/ false); + } else { + // There is no page loading going on right now. Create the + // timer and fire it right away. + nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1"); + NS_ENSURE_TRUE(timer, NS_ERROR_FAILURE); + + mRefreshURIList->AppendElement(timer, /*weak =*/ false); // owning timer ref + timer->InitWithCallback(refreshTimer, aDelay, nsITimer::TYPE_ONE_SHOT); + } + return NS_OK; +} + +nsresult +nsDocShell::ForceRefreshURIFromTimer(nsIURI* aURI, + int32_t aDelay, + bool aMetaRefresh, + nsITimer* aTimer, + nsIPrincipal* aPrincipal) +{ + NS_PRECONDITION(aTimer, "Must have a timer here"); + + // Remove aTimer from mRefreshURIList if needed + if (mRefreshURIList) { + uint32_t n = 0; + mRefreshURIList->GetLength(&n); + + for (uint32_t i = 0; i < n; ++i) { + nsCOMPtr<nsITimer> timer = do_QueryElementAt(mRefreshURIList, i); + if (timer == aTimer) { + mRefreshURIList->RemoveElementAt(i); + break; + } + } + } + + return ForceRefreshURI(aURI, aDelay, aMetaRefresh, aPrincipal); +} + +NS_IMETHODIMP +nsDocShell::ForceRefreshURI(nsIURI* aURI, int32_t aDelay, bool aMetaRefresh, nsIPrincipal* aPrincipal) +{ + NS_ENSURE_ARG(aURI); + + nsCOMPtr<nsIDocShellLoadInfo> loadInfo; + CreateLoadInfo(getter_AddRefs(loadInfo)); + NS_ENSURE_TRUE(loadInfo, NS_ERROR_OUT_OF_MEMORY); + + /* We do need to pass in a referrer, but we don't want it to + * be sent to the server. + */ + loadInfo->SetSendReferrer(false); + + /* for most refreshes the current URI is an appropriate + * internal referrer + */ + loadInfo->SetReferrer(mCurrentURI); + + /* Don't ever "guess" on which principal to use to avoid picking + * the current principal. + */ + loadInfo->SetPrincipalIsExplicit(true); + + /* Check if this META refresh causes a redirection + * to another site. + */ + bool equalUri = false; + nsresult rv = aURI->Equals(mCurrentURI, &equalUri); + if (NS_SUCCEEDED(rv) && (!equalUri) && aMetaRefresh && + aDelay <= REFRESH_REDIRECT_TIMER) { + /* It is a META refresh based redirection within the threshold time + * we have in mind (15000 ms as defined by REFRESH_REDIRECT_TIMER). + * Pass a REPLACE flag to LoadURI(). + */ + loadInfo->SetLoadType(nsIDocShellLoadInfo::loadNormalReplace); + + /* for redirects we mimic HTTP, which passes the + * original referrer + */ + nsCOMPtr<nsIURI> internalReferrer; + GetReferringURI(getter_AddRefs(internalReferrer)); + if (internalReferrer) { + loadInfo->SetReferrer(internalReferrer); + } + } else { + loadInfo->SetLoadType(nsIDocShellLoadInfo::loadRefresh); + } + + // If the principal is null, the refresh will have a triggeringPrincipal + // derived from the referrer URI, or will be set to the system principal + // if there is no refererrer. See LoadURI() + if (aPrincipal) { + loadInfo->SetTriggeringPrincipal(aPrincipal); + } + + /* + * LoadURI(...) will cancel all refresh timers... This causes the + * Timer and its refreshData instance to be released... + */ + LoadURI(aURI, loadInfo, nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL, true); + + return NS_OK; +} + +nsresult +nsDocShell::SetupRefreshURIFromHeader(nsIURI* aBaseURI, + nsIPrincipal* aPrincipal, + const nsACString& aHeader) +{ + // Refresh headers are parsed with the following format in mind + // <META HTTP-EQUIV=REFRESH CONTENT="5; URL=http://uri"> + // By the time we are here, the following is true: + // header = "REFRESH" + // content = "5; URL=http://uri" // note the URL attribute is + // optional, if it is absent, the currently loaded url is used. + // Also note that the seconds and URL separator can be either + // a ';' or a ','. The ',' separator should be illegal but CNN + // is using it. + // + // We need to handle the following strings, where + // - X is a set of digits + // - URI is either a relative or absolute URI + // + // Note that URI should start with "url=" but we allow omission + // + // "" || ";" || "," + // empty string. use the currently loaded URI + // and refresh immediately. + // "X" || "X;" || "X," + // Refresh the currently loaded URI in X seconds. + // "X; URI" || "X, URI" + // Refresh using URI as the destination in X seconds. + // "URI" || "; URI" || ", URI" + // Refresh immediately using URI as the destination. + // + // Currently, anything immediately following the URI, if + // separated by any char in the set "'\"\t\r\n " will be + // ignored. So "10; url=go.html ; foo=bar" will work, + // and so will "10; url='go.html'; foo=bar". However, + // "10; url=go.html; foo=bar" will result in the uri + // "go.html;" since ';' and ',' are valid uri characters. + // + // Note that we need to remove any tokens wrapping the URI. + // These tokens currently include spaces, double and single + // quotes. + + // when done, seconds is 0 or the given number of seconds + // uriAttrib is empty or the URI specified + MOZ_ASSERT(aPrincipal); + + nsAutoCString uriAttrib; + int32_t seconds = 0; + bool specifiesSeconds = false; + + nsACString::const_iterator iter, tokenStart, doneIterating; + + aHeader.BeginReading(iter); + aHeader.EndReading(doneIterating); + + // skip leading whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + tokenStart = iter; + + // skip leading + and - + if (iter != doneIterating && (*iter == '-' || *iter == '+')) { + ++iter; + } + + // parse number + while (iter != doneIterating && (*iter >= '0' && *iter <= '9')) { + seconds = seconds * 10 + (*iter - '0'); + specifiesSeconds = true; + ++iter; + } + + if (iter != doneIterating) { + // if we started with a '-', number is negative + if (*tokenStart == '-') { + seconds = -seconds; + } + + // skip to next ';' or ',' + nsACString::const_iterator iterAfterDigit = iter; + while (iter != doneIterating && !(*iter == ';' || *iter == ',')) { + if (specifiesSeconds) { + // Non-whitespace characters here mean that the string is + // malformed but tolerate sites that specify a decimal point, + // even though meta refresh only works on whole seconds. + if (iter == iterAfterDigit && + !nsCRT::IsAsciiSpace(*iter) && *iter != '.') { + // The characters between the seconds and the next + // section are just garbage! + // e.g. content="2a0z+,URL=http://www.mozilla.org/" + // Just ignore this redirect. + return NS_ERROR_FAILURE; + } else if (nsCRT::IsAsciiSpace(*iter)) { + // We've had at least one whitespace so tolerate the mistake + // and drop through. + // e.g. content="10 foo" + ++iter; + break; + } + } + ++iter; + } + + // skip any remaining whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + // skip ';' or ',' + if (iter != doneIterating && (*iter == ';' || *iter == ',')) { + ++iter; + } + + // skip whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + } + + // possible start of URI + tokenStart = iter; + + // skip "url = " to real start of URI + if (iter != doneIterating && (*iter == 'u' || *iter == 'U')) { + ++iter; + if (iter != doneIterating && (*iter == 'r' || *iter == 'R')) { + ++iter; + if (iter != doneIterating && (*iter == 'l' || *iter == 'L')) { + ++iter; + + // skip whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + if (iter != doneIterating && *iter == '=') { + ++iter; + + // skip whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + // found real start of URI + tokenStart = iter; + } + } + } + } + + // skip a leading '"' or '\''. + + bool isQuotedURI = false; + if (tokenStart != doneIterating && + (*tokenStart == '"' || *tokenStart == '\'')) { + isQuotedURI = true; + ++tokenStart; + } + + // set iter to start of URI + iter = tokenStart; + + // tokenStart here points to the beginning of URI + + // grab the rest of the URI + while (iter != doneIterating) { + if (isQuotedURI && (*iter == '"' || *iter == '\'')) { + break; + } + ++iter; + } + + // move iter one back if the last character is a '"' or '\'' + if (iter != tokenStart && isQuotedURI) { + --iter; + if (!(*iter == '"' || *iter == '\'')) { + ++iter; + } + } + + // URI is whatever's contained from tokenStart to iter. + // note: if tokenStart == doneIterating, so is iter. + + nsresult rv = NS_OK; + + nsCOMPtr<nsIURI> uri; + bool specifiesURI = false; + if (tokenStart == iter) { + uri = aBaseURI; + } else { + uriAttrib = Substring(tokenStart, iter); + // NS_NewURI takes care of any whitespace surrounding the URL + rv = NS_NewURI(getter_AddRefs(uri), uriAttrib, nullptr, aBaseURI); + specifiesURI = true; + } + + // No URI or seconds were specified + if (!specifiesSeconds && !specifiesURI) { + // Do nothing because the alternative is to spin around in a refresh + // loop forever! + return NS_ERROR_FAILURE; + } + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIScriptSecurityManager> securityManager( + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = securityManager->CheckLoadURIWithPrincipal( + aPrincipal, uri, + nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT); + + if (NS_SUCCEEDED(rv)) { + bool isjs = true; + rv = NS_URIChainHasFlags( + uri, nsIProtocolHandler::URI_OPENING_EXECUTES_SCRIPT, &isjs); + NS_ENSURE_SUCCESS(rv, rv); + + if (isjs) { + return NS_ERROR_FAILURE; + } + } + + if (NS_SUCCEEDED(rv)) { + // Since we can't travel back in time yet, just pretend + // negative numbers do nothing at all. + if (seconds < 0) { + return NS_ERROR_FAILURE; + } + + rv = RefreshURI(uri, seconds * 1000, false, true, aPrincipal); + } + } + } + return rv; +} + +NS_IMETHODIMP +nsDocShell::SetupRefreshURI(nsIChannel* aChannel) +{ + nsresult rv; + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel, &rv)); + if (NS_SUCCEEDED(rv)) { + nsAutoCString refreshHeader; + rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("refresh"), + refreshHeader); + + if (!refreshHeader.IsEmpty()) { + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrincipal> principal; + rv = secMan->GetChannelResultPrincipal(aChannel, + getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + + SetupReferrerFromChannel(aChannel); + rv = SetupRefreshURIFromHeader(mCurrentURI, principal, refreshHeader); + if (NS_SUCCEEDED(rv)) { + return NS_REFRESHURI_HEADER_FOUND; + } + } + } + return rv; +} + +static void +DoCancelRefreshURITimers(nsIMutableArray* aTimerList) +{ + if (!aTimerList) { + return; + } + + uint32_t n = 0; + aTimerList->GetLength(&n); + + while (n) { + nsCOMPtr<nsITimer> timer(do_QueryElementAt(aTimerList, --n)); + + aTimerList->RemoveElementAt(n); // bye bye owning timer ref + + if (timer) { + timer->Cancel(); + } + } +} + +NS_IMETHODIMP +nsDocShell::CancelRefreshURITimers() +{ + DoCancelRefreshURITimers(mRefreshURIList); + DoCancelRefreshURITimers(mSavedRefreshURIList); + mRefreshURIList = nullptr; + mSavedRefreshURIList = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetRefreshPending(bool* aResult) +{ + if (!mRefreshURIList) { + *aResult = false; + return NS_OK; + } + + uint32_t count; + nsresult rv = mRefreshURIList->GetLength(&count); + if (NS_SUCCEEDED(rv)) { + *aResult = (count != 0); + } + return rv; +} + +NS_IMETHODIMP +nsDocShell::SuspendRefreshURIs() +{ + if (mRefreshURIList) { + uint32_t n = 0; + mRefreshURIList->GetLength(&n); + + for (uint32_t i = 0; i < n; ++i) { + nsCOMPtr<nsITimer> timer = do_QueryElementAt(mRefreshURIList, i); + if (!timer) { + continue; // this must be a nsRefreshURI already + } + + // Replace this timer object with a nsRefreshTimer object. + nsCOMPtr<nsITimerCallback> callback; + timer->GetCallback(getter_AddRefs(callback)); + + timer->Cancel(); + + nsCOMPtr<nsITimerCallback> rt = do_QueryInterface(callback); + NS_ASSERTION(rt, + "RefreshURIList timer callbacks should only be RefreshTimer objects"); + + mRefreshURIList->ReplaceElementAt(rt, i, /*weak =*/ false); + } + } + + // Suspend refresh URIs for our child shells as well. + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext()); + if (shell) { + shell->SuspendRefreshURIs(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::ResumeRefreshURIs() +{ + RefreshURIFromQueue(); + + // Resume refresh URIs for our child shells as well. + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext()); + if (shell) { + shell->ResumeRefreshURIs(); + } + } + + return NS_OK; +} + +nsresult +nsDocShell::RefreshURIFromQueue() +{ + if (!mRefreshURIList) { + return NS_OK; + } + uint32_t n = 0; + mRefreshURIList->GetLength(&n); + + while (n) { + nsCOMPtr<nsITimerCallback> refreshInfo = + do_QueryElementAt(mRefreshURIList, --n); + + if (refreshInfo) { + // This is the nsRefreshTimer object, waiting to be + // setup in a timer object and fired. + // Create the timer and trigger it. + uint32_t delay = + static_cast<nsRefreshTimer*>( + static_cast<nsITimerCallback*>(refreshInfo))->GetDelay(); + nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1"); + if (timer) { + // Replace the nsRefreshTimer element in the queue with + // its corresponding timer object, so that in case another + // load comes through before the timer can go off, the timer will + // get cancelled in CancelRefreshURITimer() + mRefreshURIList->ReplaceElementAt(timer, n, /*weak =*/ false); + timer->InitWithCallback(refreshInfo, delay, nsITimer::TYPE_ONE_SHOT); + } + } + } + + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIContentViewerContainer +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::Embed(nsIContentViewer* aContentViewer, + const char* aCommand, nsISupports* aExtraInfo) +{ + // Save the LayoutHistoryState of the previous document, before + // setting up new document + PersistLayoutHistoryState(); + + nsresult rv = SetupNewViewer(aContentViewer); + NS_ENSURE_SUCCESS(rv, rv); + + // If we are loading a wyciwyg url from history, change the base URI for + // the document to the original http url that created the document.write(). + // This makes sure that all relative urls in a document.written page loaded + // via history work properly. + if (mCurrentURI && + (mLoadType & LOAD_CMD_HISTORY || + mLoadType == LOAD_RELOAD_NORMAL || + mLoadType == LOAD_RELOAD_CHARSET_CHANGE)) { + bool isWyciwyg = false; + // Check if the url is wyciwyg + rv = mCurrentURI->SchemeIs("wyciwyg", &isWyciwyg); + if (isWyciwyg && NS_SUCCEEDED(rv)) { + SetBaseUrlForWyciwyg(aContentViewer); + } + } + // XXX What if SetupNewViewer fails? + if (mLSHE) { + // Restore the editing state, if it's stored in session history. + if (mLSHE->HasDetachedEditor()) { + ReattachEditorToWindow(mLSHE); + } + // Set history.state + SetDocCurrentStateObj(mLSHE); + + SetHistoryEntry(&mOSHE, mLSHE); + } + + bool updateHistory = true; + + // Determine if this type of load should update history + switch (mLoadType) { + case LOAD_NORMAL_REPLACE: + case LOAD_STOP_CONTENT_AND_REPLACE: + case LOAD_RELOAD_BYPASS_CACHE: + case LOAD_RELOAD_BYPASS_PROXY: + case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: + case LOAD_REPLACE_BYPASS_CACHE: + updateHistory = false; + break; + default: + break; + } + + if (!updateHistory) { + SetLayoutHistoryState(nullptr); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetIsPrinting(bool aIsPrinting) +{ + mIsPrintingOrPP = aIsPrinting; + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIWebProgressListener +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::OnProgressChange(nsIWebProgress* aProgress, + nsIRequest* aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::OnStateChange(nsIWebProgress* aProgress, nsIRequest* aRequest, + uint32_t aStateFlags, nsresult aStatus) +{ + if ((~aStateFlags & (STATE_START | STATE_IS_NETWORK)) == 0) { + // Save timing statistics. + nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + nsAutoCString aURI; + uri->GetAsciiSpec(aURI); + + nsCOMPtr<nsIWyciwygChannel> wcwgChannel(do_QueryInterface(aRequest)); + nsCOMPtr<nsIWebProgress> webProgress = + do_QueryInterface(GetAsSupports(this)); + + // We don't update navigation timing for wyciwyg channels + if (this == aProgress && !wcwgChannel) { + MaybeInitTiming(); + mTiming->NotifyFetchStart(uri, + ConvertLoadTypeToNavigationType(mLoadType)); + } + + // Was the wyciwyg document loaded on this docshell? + if (wcwgChannel && !mLSHE && (mItemType == typeContent) && + aProgress == webProgress.get()) { + bool equalUri = true; + // Store the wyciwyg url in session history, only if it is + // being loaded fresh for the first time. We don't want + // multiple entries for successive loads + if (mCurrentURI && + NS_SUCCEEDED(uri->Equals(mCurrentURI, &equalUri)) && + !equalUri) { + nsCOMPtr<nsIDocShellTreeItem> parentAsItem; + GetSameTypeParent(getter_AddRefs(parentAsItem)); + nsCOMPtr<nsIDocShell> parentDS(do_QueryInterface(parentAsItem)); + bool inOnLoadHandler = false; + if (parentDS) { + parentDS->GetIsExecutingOnLoadHandler(&inOnLoadHandler); + } + if (inOnLoadHandler) { + // We're handling parent's load event listener, which causes + // document.write in a subdocument. + // Need to clear the session history for all child + // docshells so that we can handle them like they would + // all be added dynamically. + nsCOMPtr<nsIDocShell> parent = do_QueryInterface(parentAsItem); + if (parent) { + bool oshe = false; + nsCOMPtr<nsISHEntry> entry; + parent->GetCurrentSHEntry(getter_AddRefs(entry), &oshe); + static_cast<nsDocShell*>(parent.get())->ClearFrameHistory(entry); + } + } + + // This is a document.write(). Get the made-up url + // from the channel and store it in session history. + // Pass false for aCloneChildren, since we're creating + // a new DOM here. + AddToSessionHistory(uri, wcwgChannel, nullptr, nullptr, false, + getter_AddRefs(mLSHE)); + SetCurrentURI(uri, aRequest, true, 0); + // Save history state of the previous page + PersistLayoutHistoryState(); + // We'll never get an Embed() for this load, so just go ahead + // and SetHistoryEntry now. + SetHistoryEntry(&mOSHE, mLSHE); + } + } + // Page has begun to load + mBusyFlags = BUSY_FLAGS_BUSY | BUSY_FLAGS_BEFORE_PAGE_LOAD; + + if ((aStateFlags & STATE_RESTORING) == 0) { + // Show the progress cursor if the pref is set + if (nsContentUtils::UseActivityCursor()) { + nsCOMPtr<nsIWidget> mainWidget; + GetMainWidget(getter_AddRefs(mainWidget)); + if (mainWidget) { + mainWidget->SetCursor(eCursor_spinning); + } + } + } + } else if ((~aStateFlags & (STATE_TRANSFERRING | STATE_IS_DOCUMENT)) == 0) { + // Page is loading + mBusyFlags = BUSY_FLAGS_BUSY | BUSY_FLAGS_PAGE_LOADING; + } else if ((aStateFlags & STATE_STOP) && (aStateFlags & STATE_IS_NETWORK)) { + // Page has finished loading + mBusyFlags = BUSY_FLAGS_NONE; + + // Hide the progress cursor if the pref is set + if (nsContentUtils::UseActivityCursor()) { + nsCOMPtr<nsIWidget> mainWidget; + GetMainWidget(getter_AddRefs(mainWidget)); + if (mainWidget) { + mainWidget->SetCursor(eCursor_standard); + } + } + } + if ((~aStateFlags & (STATE_IS_DOCUMENT | STATE_STOP)) == 0) { + nsCOMPtr<nsIWebProgress> webProgress = + do_QueryInterface(GetAsSupports(this)); + // Is the document stop notification for this document? + if (aProgress == webProgress.get()) { + nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest)); + EndPageLoad(aProgress, channel, aStatus); + } + } + // note that redirect state changes will go through here as well, but it + // is better to handle those in OnRedirectStateChange where more + // information is available. + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::OnLocationChange(nsIWebProgress* aProgress, nsIRequest* aRequest, + nsIURI* aURI, uint32_t aFlags) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +void +nsDocShell::OnRedirectStateChange(nsIChannel* aOldChannel, + nsIChannel* aNewChannel, + uint32_t aRedirectFlags, + uint32_t aStateFlags) +{ + NS_ASSERTION(aStateFlags & STATE_REDIRECTING, + "Calling OnRedirectStateChange when there is no redirect"); + + // If mixed content is allowed for the old channel, we forward + // the permission to the new channel if it has the same origin + // as the old one. + if (mMixedContentChannel && mMixedContentChannel == aOldChannel) { + nsresult rv = nsContentUtils::CheckSameOrigin(mMixedContentChannel, aNewChannel); + if (NS_SUCCEEDED(rv)) { + SetMixedContentChannel(aNewChannel); // Same origin: forward permission. + } else { + SetMixedContentChannel(nullptr); // Different origin: clear mMixedContentChannel. + } + } + + if (!(aStateFlags & STATE_IS_DOCUMENT)) { + return; // not a toplevel document + } + + nsCOMPtr<nsIURI> oldURI, newURI; + aOldChannel->GetURI(getter_AddRefs(oldURI)); + aNewChannel->GetURI(getter_AddRefs(newURI)); + if (!oldURI || !newURI) { + return; + } + + // Below a URI visit is saved (see AddURIVisit method doc). + // The visit chain looks something like: + // ... + // Site N - 1 + // => Site N + // (redirect to =>) Site N + 1 (we are here!) + + // Get N - 1 and transition type + nsCOMPtr<nsIURI> previousURI; + uint32_t previousFlags = 0; + ExtractLastVisit(aOldChannel, getter_AddRefs(previousURI), &previousFlags); + + if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL || + ChannelIsPost(aOldChannel)) { + // 1. Internal redirects are ignored because they are specific to the + // channel implementation. + // 2. POSTs are not saved by global history. + // + // Regardless, we need to propagate the previous visit to the new + // channel. + SaveLastVisit(aNewChannel, previousURI, previousFlags); + } else { + nsCOMPtr<nsIURI> referrer; + // Treat referrer as null if there is an error getting it. + (void)NS_GetReferrerFromChannel(aOldChannel, getter_AddRefs(referrer)); + + // Get the HTTP response code, if available. + uint32_t responseStatus = 0; + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aOldChannel); + if (httpChannel) { + (void)httpChannel->GetResponseStatus(&responseStatus); + } + + // Add visit N -1 => N + AddURIVisit(oldURI, referrer, previousURI, previousFlags, responseStatus); + + // Since N + 1 could be the final destination, we will not save N => N + 1 + // here. OnNewURI will do that, so we will cache it. + SaveLastVisit(aNewChannel, oldURI, aRedirectFlags); + } + + // check if the new load should go through the application cache. + nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel = + do_QueryInterface(aNewChannel); + if (appCacheChannel) { + if (GeckoProcessType_Default != XRE_GetProcessType()) { + // Permission will be checked in the parent process. + appCacheChannel->SetChooseApplicationCache(true); + } else { + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + + if (secMan) { + nsCOMPtr<nsIPrincipal> principal; + secMan->GetDocShellCodebasePrincipal(newURI, this, + getter_AddRefs(principal)); + appCacheChannel->SetChooseApplicationCache( + NS_ShouldCheckAppCache(principal, UsePrivateBrowsing())); + } + } + } + + if (!(aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) && + mLoadType & (LOAD_CMD_RELOAD | LOAD_CMD_HISTORY)) { + mLoadType = LOAD_NORMAL_REPLACE; + SetHistoryEntry(&mLSHE, nullptr); + } +} + +NS_IMETHODIMP +nsDocShell::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, const char16_t* aMessage) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, uint32_t aState) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +nsresult +nsDocShell::EndPageLoad(nsIWebProgress* aProgress, + nsIChannel* aChannel, nsresult aStatus) +{ + if (!aChannel) { + return NS_ERROR_NULL_POINTER; + } + + nsCOMPtr<nsIConsoleReportCollector> reporter = do_QueryInterface(aChannel); + if (reporter) { + reporter->FlushConsoleReports(GetDocument()); + } + + nsCOMPtr<nsIURI> url; + nsresult rv = aChannel->GetURI(getter_AddRefs(url)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsITimedChannel> timingChannel = do_QueryInterface(aChannel); + if (timingChannel) { + TimeStamp channelCreationTime; + rv = timingChannel->GetChannelCreation(&channelCreationTime); + if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) { + Telemetry::AccumulateTimeDelta(Telemetry::TOTAL_CONTENT_PAGE_LOAD_TIME, + channelCreationTime); + nsCOMPtr<nsPILoadGroupInternal> internalLoadGroup = + do_QueryInterface(mLoadGroup); + if (internalLoadGroup) { + internalLoadGroup->OnEndPageLoad(aChannel); + } + } + } + + // Timing is picked up by the window, we don't need it anymore + mTiming = nullptr; + + // clean up reload state for meta charset + if (eCharsetReloadRequested == mCharsetReloadState) { + mCharsetReloadState = eCharsetReloadStopOrigional; + } else { + mCharsetReloadState = eCharsetReloadInit; + } + + // Save a pointer to the currently-loading history entry. + // nsDocShell::EndPageLoad will clear mLSHE, but we may need this history + // entry further down in this method. + nsCOMPtr<nsISHEntry> loadingSHE = mLSHE; + mozilla::Unused << loadingSHE; // XXX: Not sure if we need this anymore + + // + // one of many safeguards that prevent death and destruction if + // someone is so very very rude as to bring this window down + // during this load handler. + // + nsCOMPtr<nsIDocShell> kungFuDeathGrip(this); + + // Notify the ContentViewer that the Document has finished loading. This + // will cause any OnLoad(...) and PopState(...) handlers to fire. + if (!mEODForCurrentDocument && mContentViewer) { + mIsExecutingOnLoadHandler = true; + mContentViewer->LoadComplete(aStatus); + mIsExecutingOnLoadHandler = false; + + mEODForCurrentDocument = true; + + // If all documents have completed their loading + // favor native event dispatch priorities + // over performance + if (--gNumberOfDocumentsLoading == 0) { + // Hint to use normal native event dispatch priorities + FavorPerformanceHint(false); + } + } + /* Check if the httpChannel has any cache-control related response headers, + * like no-store, no-cache. If so, update SHEntry so that + * when a user goes back/forward to this page, we appropriately do + * form value restoration or load from server. + */ + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); + if (!httpChannel) { + // HttpChannel could be hiding underneath a Multipart channel. + GetHttpChannel(aChannel, getter_AddRefs(httpChannel)); + } + + if (httpChannel) { + // figure out if SH should be saving layout state. + bool discardLayoutState = ShouldDiscardLayoutState(httpChannel); + if (mLSHE && discardLayoutState && (mLoadType & LOAD_CMD_NORMAL) && + (mLoadType != LOAD_BYPASS_HISTORY) && (mLoadType != LOAD_ERROR_PAGE)) { + mLSHE->SetSaveLayoutStateFlag(false); + } + } + + // Clear mLSHE after calling the onLoadHandlers. This way, if the + // onLoadHandler tries to load something different in + // itself or one of its children, we can deal with it appropriately. + if (mLSHE) { + mLSHE->SetLoadType(nsIDocShellLoadInfo::loadHistory); + + // Clear the mLSHE reference to indicate document loading is done one + // way or another. + SetHistoryEntry(&mLSHE, nullptr); + } + // if there's a refresh header in the channel, this method + // will set it up for us. + if (mIsActive || !mDisableMetaRefreshWhenInactive) + RefreshURIFromQueue(); + + // Test whether this is the top frame or a subframe + bool isTopFrame = true; + nsCOMPtr<nsIDocShellTreeItem> targetParentTreeItem; + rv = GetSameTypeParent(getter_AddRefs(targetParentTreeItem)); + if (NS_SUCCEEDED(rv) && targetParentTreeItem) { + isTopFrame = false; + } + + // + // If the page load failed, then deal with the error condition... + // Errors are handled as follows: + // 1. Check to see if it's a file not found error or bad content + // encoding error. + // 2. Send the URI to a keyword server (if enabled) + // 3. If the error was DNS failure, then add www and .com to the URI + // (if appropriate). + // 4. Throw an error dialog box... + // + if (url && NS_FAILED(aStatus)) { + if (aStatus == NS_ERROR_FILE_NOT_FOUND || + aStatus == NS_ERROR_FILE_ACCESS_DENIED || + aStatus == NS_ERROR_CORRUPTED_CONTENT || + aStatus == NS_ERROR_INVALID_CONTENT_ENCODING) { + DisplayLoadError(aStatus, url, nullptr, aChannel); + return NS_OK; + } else if (aStatus == NS_ERROR_INVALID_SIGNATURE) { + // NS_ERROR_INVALID_SIGNATURE indicates a content-signature error. + // This currently only happens in case a remote about page fails. + // We have to load a fallback in this case. + // XXX: We always load about blank here, firefox has to overwrite this if + // it wants to display something else. + return LoadURI(u"about:blank", // URI string + nsIChannel::LOAD_NORMAL, // Load flags + nullptr, // Referring URI + nullptr, // Post data stream + nullptr); // Headers stream + } + + // Handle iframe document not loading error because source was + // a tracking URL. We make a note of this iframe node by including + // it in a dedicated array of blocked tracking nodes under its parent + // document. (document of parent window of blocked document) + if (isTopFrame == false && aStatus == NS_ERROR_TRACKING_URI) { + // frameElement is our nsIContent to be annotated + nsCOMPtr<nsIDOMElement> frameElement; + nsPIDOMWindowOuter* thisWindow = GetWindow(); + if (!thisWindow) { + return NS_OK; + } + + frameElement = thisWindow->GetFrameElement(); + if (!frameElement) { + return NS_OK; + } + + // Parent window + nsCOMPtr<nsIDocShellTreeItem> parentItem; + GetSameTypeParent(getter_AddRefs(parentItem)); + if (!parentItem) { + return NS_OK; + } + + nsCOMPtr<nsIDocument> parentDoc; + parentDoc = parentItem->GetDocument(); + if (!parentDoc) { + return NS_OK; + } + + nsCOMPtr<nsIContent> cont = do_QueryInterface(frameElement); + parentDoc->AddBlockedTrackingNode(cont); + + return NS_OK; + } + + if (sURIFixup) { + // + // Try and make an alternative URI from the old one + // + nsCOMPtr<nsIURI> newURI; + nsCOMPtr<nsIInputStream> newPostData; + + nsAutoCString oldSpec; + url->GetSpec(oldSpec); + + // + // First try keyword fixup + // + nsAutoString keywordProviderName, keywordAsSent; + if (aStatus == NS_ERROR_UNKNOWN_HOST && mAllowKeywordFixup) { + bool keywordsEnabled = Preferences::GetBool("keyword.enabled", false); + + nsAutoCString host; + url->GetHost(host); + + nsAutoCString scheme; + url->GetScheme(scheme); + + int32_t dotLoc = host.FindChar('.'); + + // we should only perform a keyword search under the following + // conditions: + // (0) Pref keyword.enabled is true + // (1) the url scheme is http (or https) + // (2) the url does not have a protocol scheme + // If we don't enforce such a policy, then we end up doing + // keyword searchs on urls we don't intend like imap, file, + // mailbox, etc. This could lead to a security problem where we + // send data to the keyword server that we shouldn't be. + // Someone needs to clean up keywords in general so we can + // determine on a per url basis if we want keywords + // enabled...this is just a bandaid... + if (keywordsEnabled && !scheme.IsEmpty() && + (scheme.Find("http") != 0)) { + keywordsEnabled = false; + } + + if (keywordsEnabled && (kNotFound == dotLoc)) { + nsCOMPtr<nsIURIFixupInfo> info; + // only send non-qualified hosts to the keyword server + if (!mOriginalUriString.IsEmpty()) { + sURIFixup->KeywordToURI(mOriginalUriString, + getter_AddRefs(newPostData), + getter_AddRefs(info)); + } else { + // + // If this string was passed through nsStandardURL by + // chance, then it may have been converted from UTF-8 to + // ACE, which would result in a completely bogus keyword + // query. Here we try to recover the original Unicode + // value, but this is not 100% correct since the value may + // have been normalized per the IDN normalization rules. + // + // Since we don't have access to the exact original string + // that was entered by the user, this will just have to do. + bool isACE; + nsAutoCString utf8Host; + nsCOMPtr<nsIIDNService> idnSrv = + do_GetService(NS_IDNSERVICE_CONTRACTID); + if (idnSrv && + NS_SUCCEEDED(idnSrv->IsACE(host, &isACE)) && isACE && + NS_SUCCEEDED(idnSrv->ConvertACEtoUTF8(host, utf8Host))) { + sURIFixup->KeywordToURI(utf8Host, + getter_AddRefs(newPostData), + getter_AddRefs(info)); + } else { + sURIFixup->KeywordToURI(host, + getter_AddRefs(newPostData), + getter_AddRefs(info)); + } + } + + info->GetPreferredURI(getter_AddRefs(newURI)); + if (newURI) { + info->GetKeywordAsSent(keywordAsSent); + info->GetKeywordProviderName(keywordProviderName); + } + } // end keywordsEnabled + } + + // + // Now try change the address, e.g. turn http://foo into + // http://www.foo.com + // + if (aStatus == NS_ERROR_UNKNOWN_HOST || + aStatus == NS_ERROR_NET_RESET) { + bool doCreateAlternate = true; + + // Skip fixup for anything except a normal document load + // operation on the topframe. + + if (mLoadType != LOAD_NORMAL || !isTopFrame) { + doCreateAlternate = false; + } else { + // Test if keyword lookup produced a new URI or not + if (newURI) { + bool sameURI = false; + url->Equals(newURI, &sameURI); + if (!sameURI) { + // Keyword lookup made a new URI so no need to try + // an alternate one. + doCreateAlternate = false; + } + } + + if (doCreateAlternate) { + // Skip doing this if our channel was redirected, because we + // shouldn't be guessing things about the post-redirect URI. + nsLoadFlags loadFlags = 0; + if (NS_FAILED(aChannel->GetLoadFlags(&loadFlags)) || + (loadFlags & nsIChannel::LOAD_REPLACE)) { + doCreateAlternate = false; + } + } + } + if (doCreateAlternate) { + newURI = nullptr; + newPostData = nullptr; + keywordProviderName.Truncate(); + keywordAsSent.Truncate(); + sURIFixup->CreateFixupURI(oldSpec, + nsIURIFixup::FIXUP_FLAGS_MAKE_ALTERNATE_URI, + getter_AddRefs(newPostData), + getter_AddRefs(newURI)); + } + } + + // Did we make a new URI that is different to the old one? If so + // load it. + // + if (newURI) { + // Make sure the new URI is different from the old one, + // otherwise there's little point trying to load it again. + bool sameURI = false; + url->Equals(newURI, &sameURI); + if (!sameURI) { + nsAutoCString newSpec; + newURI->GetSpec(newSpec); + NS_ConvertUTF8toUTF16 newSpecW(newSpec); + + // This notification is meant for Firefox Health Report so it + // can increment counts from the search engine + MaybeNotifyKeywordSearchLoading(keywordProviderName, keywordAsSent); + + return LoadURI(newSpecW.get(), // URI string + LOAD_FLAGS_NONE, // Load flags + nullptr, // Referring URI + newPostData, // Post data stream + nullptr); // Headers stream + } + } + } + + // Well, fixup didn't work :-( + // It is time to throw an error dialog box, and be done with it... + + // Errors to be shown only on top-level frames + if ((aStatus == NS_ERROR_UNKNOWN_HOST || + aStatus == NS_ERROR_CONNECTION_REFUSED || + aStatus == NS_ERROR_UNKNOWN_PROXY_HOST || + aStatus == NS_ERROR_PROXY_CONNECTION_REFUSED) && + (isTopFrame || UseErrorPages())) { + DisplayLoadError(aStatus, url, nullptr, aChannel); + } else if (aStatus == NS_ERROR_NET_TIMEOUT || + aStatus == NS_ERROR_REDIRECT_LOOP || + aStatus == NS_ERROR_UNKNOWN_SOCKET_TYPE || + aStatus == NS_ERROR_NET_INTERRUPT || + aStatus == NS_ERROR_NET_RESET || + aStatus == NS_ERROR_OFFLINE || + aStatus == NS_ERROR_MALWARE_URI || + aStatus == NS_ERROR_PHISHING_URI || + aStatus == NS_ERROR_UNWANTED_URI || + aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE || + aStatus == NS_ERROR_REMOTE_XUL || + aStatus == NS_ERROR_INTERCEPTION_FAILED || + aStatus == NS_ERROR_NET_INADEQUATE_SECURITY || + NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) { + // Errors to be shown for any frame + DisplayLoadError(aStatus, url, nullptr, aChannel); + } else if (aStatus == NS_ERROR_DOCUMENT_NOT_CACHED) { + // Non-caching channels will simply return NS_ERROR_OFFLINE. + // Caching channels would have to look at their flags to work + // out which error to return. Or we can fix up the error here. + if (!(mLoadType & LOAD_CMD_HISTORY)) { + aStatus = NS_ERROR_OFFLINE; + } + DisplayLoadError(aStatus, url, nullptr, aChannel); + } + } else if (url && NS_SUCCEEDED(aStatus)) { + // If we have a host + mozilla::net::PredictorLearnRedirect(url, aChannel, this); + } + + return NS_OK; +} + +//***************************************************************************** +// nsDocShell: Content Viewer Management +//***************************************************************************** + +nsresult +nsDocShell::EnsureContentViewer() +{ + if (mContentViewer) { + return NS_OK; + } + if (mIsBeingDestroyed) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIURI> baseURI; + nsIPrincipal* principal = GetInheritedPrincipal(false); + nsCOMPtr<nsIDocShellTreeItem> parentItem; + GetSameTypeParent(getter_AddRefs(parentItem)); + if (parentItem) { + if (nsCOMPtr<nsPIDOMWindowOuter> domWin = GetWindow()) { + nsCOMPtr<Element> parentElement = domWin->GetFrameElementInternal(); + if (parentElement) { + baseURI = parentElement->GetBaseURI(); + } + } + } + + nsresult rv = CreateAboutBlankContentViewer(principal, baseURI); + + NS_ENSURE_STATE(mContentViewer); + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIDocument> doc(GetDocument()); + NS_ASSERTION(doc, + "Should have doc if CreateAboutBlankContentViewer " + "succeeded!"); + + doc->SetIsInitialDocument(true); + } + + return rv; +} + +nsresult +nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal, + nsIURI* aBaseURI, + bool aTryToSaveOldPresentation) +{ + nsCOMPtr<nsIDocument> blankDoc; + nsCOMPtr<nsIContentViewer> viewer; + nsresult rv = NS_ERROR_FAILURE; + + /* mCreatingDocument should never be true at this point. However, it's + a theoretical possibility. We want to know about it and make it stop, + and this sounds like a job for an assertion. */ + NS_ASSERTION(!mCreatingDocument, + "infinite(?) loop creating document averted"); + if (mCreatingDocument) { + return NS_ERROR_FAILURE; + } + + // mContentViewer->PermitUnload may release |this| docshell. + nsCOMPtr<nsIDocShell> kungFuDeathGrip(this); + + AutoRestore<bool> creatingDocument(mCreatingDocument); + mCreatingDocument = true; + + if (aPrincipal && !nsContentUtils::IsSystemPrincipal(aPrincipal) && + mItemType != typeChrome) { + MOZ_ASSERT(ChromeUtils::IsOriginAttributesEqualIgnoringAddonId( + BasePrincipal::Cast(aPrincipal)->OriginAttributesRef(), + mOriginAttributes)); + } + + // Make sure timing is created. But first record whether we had it + // already, so we don't clobber the timing for an in-progress load. + bool hadTiming = mTiming; + MaybeInitTiming(); + if (mContentViewer) { + // We've got a content viewer already. Make sure the user + // permits us to discard the current document and replace it + // with about:blank. And also ensure we fire the unload events + // in the current document. + + // Unload gets fired first for + // document loaded from the session history. + mTiming->NotifyBeforeUnload(); + + bool okToUnload; + rv = mContentViewer->PermitUnload(&okToUnload); + + if (NS_SUCCEEDED(rv) && !okToUnload) { + // The user chose not to unload the page, interrupt the load. + return NS_ERROR_FAILURE; + } + + mSavingOldViewer = aTryToSaveOldPresentation && + CanSavePresentation(LOAD_NORMAL, nullptr, nullptr); + + if (mTiming) { + mTiming->NotifyUnloadAccepted(mCurrentURI); + } + + // Make sure to blow away our mLoadingURI just in case. No loads + // from inside this pagehide. + mLoadingURI = nullptr; + + // Stop any in-progress loading, so that we don't accidentally trigger any + // PageShow notifications from Embed() interrupting our loading below. + Stop(); + + // Notify the current document that it is about to be unloaded!! + // + // It is important to fire the unload() notification *before* any state + // is changed within the DocShell - otherwise, javascript will get the + // wrong information :-( + // + (void)FirePageHideNotification(!mSavingOldViewer); + } + + // Now make sure we don't think we're in the middle of firing unload after + // this point. This will make us fire unload when the about:blank document + // unloads... but that's ok, more or less. Would be nice if it fired load + // too, of course. + mFiredUnloadEvent = false; + + nsCOMPtr<nsIDocumentLoaderFactory> docFactory = + nsContentUtils::FindInternalContentViewer(NS_LITERAL_CSTRING("text/html")); + + if (docFactory) { + nsCOMPtr<nsIPrincipal> principal; + if (mSandboxFlags & SANDBOXED_ORIGIN) { + if (aPrincipal) { + principal = nsNullPrincipal::CreateWithInheritedAttributes(aPrincipal); + } else { + principal = nsNullPrincipal::CreateWithInheritedAttributes(this); + } + } else { + principal = aPrincipal; + } + // generate (about:blank) document to load + docFactory->CreateBlankDocument(mLoadGroup, principal, + getter_AddRefs(blankDoc)); + if (blankDoc) { + // Hack: set the base URI manually, since this document never + // got Reset() with a channel. + blankDoc->SetBaseURI(aBaseURI); + + blankDoc->SetContainer(this); + + // Copy our sandbox flags to the document. These are immutable + // after being set here. + blankDoc->SetSandboxFlags(mSandboxFlags); + + // create a content viewer for us and the new document + docFactory->CreateInstanceForDocument( + NS_ISUPPORTS_CAST(nsIDocShell*, this), blankDoc, "view", + getter_AddRefs(viewer)); + + // hook 'em up + if (viewer) { + viewer->SetContainer(this); + rv = Embed(viewer, "", 0); + NS_ENSURE_SUCCESS(rv, rv); + + SetCurrentURI(blankDoc->GetDocumentURI(), nullptr, true, 0); + rv = mIsBeingDestroyed ? NS_ERROR_NOT_AVAILABLE : NS_OK; + } + } + } + + // The transient about:blank viewer doesn't have a session history entry. + SetHistoryEntry(&mOSHE, nullptr); + + // Clear out our mTiming like we would in EndPageLoad, if we didn't + // have one before entering this function. + if (!hadTiming) { + mTiming = nullptr; + mBlankTiming = true; + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal) +{ + return CreateAboutBlankContentViewer(aPrincipal, nullptr); +} + +bool +nsDocShell::CanSavePresentation(uint32_t aLoadType, + nsIRequest* aNewRequest, + nsIDocument* aNewDocument) +{ + if (!mOSHE) { + return false; // no entry to save into + } + + nsCOMPtr<nsIContentViewer> viewer; + mOSHE->GetContentViewer(getter_AddRefs(viewer)); + if (viewer) { + NS_WARNING("mOSHE already has a content viewer!"); + return false; + } + + // Only save presentation for "normal" loads and link loads. Anything else + // probably wants to refetch the page, so caching the old presentation + // would be incorrect. + if (aLoadType != LOAD_NORMAL && + aLoadType != LOAD_HISTORY && + aLoadType != LOAD_LINK && + aLoadType != LOAD_STOP_CONTENT && + aLoadType != LOAD_STOP_CONTENT_AND_REPLACE && + aLoadType != LOAD_ERROR_PAGE) { + return false; + } + + // If the session history entry has the saveLayoutState flag set to false, + // then we should not cache the presentation. + bool canSaveState; + mOSHE->GetSaveLayoutStateFlag(&canSaveState); + if (!canSaveState) { + return false; + } + + // If the document is not done loading, don't cache it. + if (!mScriptGlobal || mScriptGlobal->IsLoading()) { + return false; + } + + if (mScriptGlobal->WouldReuseInnerWindow(aNewDocument)) { + return false; + } + + // Avoid doing the work of saving the presentation state in the case where + // the content viewer cache is disabled. + if (nsSHistory::GetMaxTotalViewers() == 0) { + return false; + } + + // Don't cache the content viewer if we're in a subframe and the subframe + // pref is disabled. + bool cacheFrames = + Preferences::GetBool("browser.sessionhistory.cache_subframes", false); + if (!cacheFrames) { + nsCOMPtr<nsIDocShellTreeItem> root; + GetSameTypeParent(getter_AddRefs(root)); + if (root && root != this) { + return false; // this is a subframe load + } + } + + // If the document does not want its presentation cached, then don't. + nsCOMPtr<nsIDocument> doc = mScriptGlobal->GetExtantDoc(); + return doc && doc->CanSavePresentation(aNewRequest); +} + +void +nsDocShell::ReattachEditorToWindow(nsISHEntry* aSHEntry) +{ + NS_ASSERTION(!mEditorData, + "Why reattach an editor when we already have one?"); + NS_ASSERTION(aSHEntry && aSHEntry->HasDetachedEditor(), + "Reattaching when there's not a detached editor."); + + if (mEditorData || !aSHEntry) { + return; + } + + mEditorData = aSHEntry->ForgetEditorData(); + if (mEditorData) { +#ifdef DEBUG + nsresult rv = +#endif + mEditorData->ReattachToWindow(this); + NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to reattach editing session"); + } +} + +void +nsDocShell::DetachEditorFromWindow() +{ + if (!mEditorData || mEditorData->WaitingForLoad()) { + // If there's nothing to detach, or if the editor data is actually set + // up for the _new_ page that's coming in, don't detach. + return; + } + + NS_ASSERTION(!mOSHE || !mOSHE->HasDetachedEditor(), + "Detaching editor when it's already detached."); + + nsresult res = mEditorData->DetachFromWindow(); + NS_ASSERTION(NS_SUCCEEDED(res), "Failed to detach editor"); + + if (NS_SUCCEEDED(res)) { + // Make mOSHE hold the owning ref to the editor data. + if (mOSHE) { + mOSHE->SetEditorData(mEditorData.forget()); + } else { + mEditorData = nullptr; + } + } + +#ifdef DEBUG + { + bool isEditable; + GetEditable(&isEditable); + NS_ASSERTION(!isEditable, + "Window is still editable after detaching editor."); + } +#endif // DEBUG +} + +nsresult +nsDocShell::CaptureState() +{ + if (!mOSHE || mOSHE == mLSHE) { + // No entry to save into, or we're replacing the existing entry. + return NS_ERROR_FAILURE; + } + + if (!mScriptGlobal) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsISupports> windowState = mScriptGlobal->SaveWindowState(); + NS_ENSURE_TRUE(windowState, NS_ERROR_FAILURE); + +#ifdef DEBUG_PAGE_CACHE + nsCOMPtr<nsIURI> uri; + mOSHE->GetURI(getter_AddRefs(uri)); + nsAutoCString spec; + if (uri) { + uri->GetSpec(spec); + } + printf("Saving presentation into session history\n"); + printf(" SH URI: %s\n", spec.get()); +#endif + + nsresult rv = mOSHE->SetWindowState(windowState); + NS_ENSURE_SUCCESS(rv, rv); + + // Suspend refresh URIs and save off the timer queue + rv = mOSHE->SetRefreshURIList(mSavedRefreshURIList); + NS_ENSURE_SUCCESS(rv, rv); + + // Capture the current content viewer bounds. + if (mContentViewer) { + nsIntRect bounds; + mContentViewer->GetBounds(bounds); + rv = mOSHE->SetViewerBounds(bounds); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Capture the docshell hierarchy. + mOSHE->ClearChildShells(); + + uint32_t childCount = mChildList.Length(); + for (uint32_t i = 0; i < childCount; ++i) { + nsCOMPtr<nsIDocShellTreeItem> childShell = do_QueryInterface(ChildAt(i)); + NS_ASSERTION(childShell, "null child shell"); + + mOSHE->AddChildShell(childShell); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RestorePresentationEvent::Run() +{ + if (mDocShell && NS_FAILED(mDocShell->RestoreFromHistory())) { + NS_WARNING("RestoreFromHistory failed"); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::BeginRestore(nsIContentViewer* aContentViewer, bool aTop) +{ + nsresult rv; + if (!aContentViewer) { + rv = EnsureContentViewer(); + NS_ENSURE_SUCCESS(rv, rv); + + aContentViewer = mContentViewer; + } + + // Dispatch events for restoring the presentation. We try to simulate + // the progress notifications loading the document would cause, so we add + // the document's channel to the loadgroup to initiate stateChange + // notifications. + + nsCOMPtr<nsIDOMDocument> domDoc; + aContentViewer->GetDOMDocument(getter_AddRefs(domDoc)); + nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc); + if (doc) { + nsIChannel* channel = doc->GetChannel(); + if (channel) { + mEODForCurrentDocument = false; + mIsRestoringDocument = true; + mLoadGroup->AddRequest(channel, nullptr); + mIsRestoringDocument = false; + } + } + + if (!aTop) { + // This point corresponds to us having gotten OnStartRequest or + // STATE_START, so do the same thing that CreateContentViewer does at + // this point to ensure that unload/pagehide events for this document + // will fire when it's unloaded again. + mFiredUnloadEvent = false; + + // For non-top frames, there is no notion of making sure that the + // previous document is in the domwindow when STATE_START notifications + // happen. We can just call BeginRestore for all of the child shells + // now. + rv = BeginRestoreChildren(); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult +nsDocShell::BeginRestoreChildren() +{ + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> child = do_QueryObject(iter.GetNext()); + if (child) { + nsresult rv = child->BeginRestore(nullptr, false); + NS_ENSURE_SUCCESS(rv, rv); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::FinishRestore() +{ + // First we call finishRestore() on our children. In the simulated load, + // all of the child frames finish loading before the main document. + + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> child = do_QueryObject(iter.GetNext()); + if (child) { + child->FinishRestore(); + } + } + + if (mOSHE && mOSHE->HasDetachedEditor()) { + ReattachEditorToWindow(mOSHE); + } + + nsCOMPtr<nsIDocument> doc = GetDocument(); + if (doc) { + // Finally, we remove the request from the loadgroup. This will + // cause onStateChange(STATE_STOP) to fire, which will fire the + // pageshow event to the chrome. + + nsIChannel* channel = doc->GetChannel(); + if (channel) { + mIsRestoringDocument = true; + mLoadGroup->RemoveRequest(channel, nullptr, NS_OK); + mIsRestoringDocument = false; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetRestoringDocument(bool* aRestoring) +{ + *aRestoring = mIsRestoringDocument; + return NS_OK; +} + +nsresult +nsDocShell::RestorePresentation(nsISHEntry* aSHEntry, bool* aRestoring) +{ + NS_ASSERTION(mLoadType & LOAD_CMD_HISTORY, + "RestorePresentation should only be called for history loads"); + + nsCOMPtr<nsIContentViewer> viewer; + aSHEntry->GetContentViewer(getter_AddRefs(viewer)); + +#ifdef DEBUG_PAGE_CACHE + nsCOMPtr<nsIURI> uri; + aSHEntry->GetURI(getter_AddRefs(uri)); + + nsAutoCString spec; + if (uri) { + uri->GetSpec(spec); + } +#endif + + *aRestoring = false; + + if (!viewer) { +#ifdef DEBUG_PAGE_CACHE + printf("no saved presentation for uri: %s\n", spec.get()); +#endif + return NS_OK; + } + + // We need to make sure the content viewer's container is this docshell. + // In subframe navigation, it's possible for the docshell that the + // content viewer was originally loaded into to be replaced with a + // different one. We don't currently support restoring the presentation + // in that case. + + nsCOMPtr<nsIDocShell> container; + viewer->GetContainer(getter_AddRefs(container)); + if (!::SameCOMIdentity(container, GetAsSupports(this))) { +#ifdef DEBUG_PAGE_CACHE + printf("No valid container, clearing presentation\n"); +#endif + aSHEntry->SetContentViewer(nullptr); + return NS_ERROR_FAILURE; + } + + NS_ASSERTION(mContentViewer != viewer, "Restoring existing presentation"); + +#ifdef DEBUG_PAGE_CACHE + printf("restoring presentation from session history: %s\n", spec.get()); +#endif + + SetHistoryEntry(&mLSHE, aSHEntry); + + // Post an event that will remove the request after we've returned + // to the event loop. This mimics the way it is called by nsIChannel + // implementations. + + // Revoke any pending restore (just in case) + NS_ASSERTION(!mRestorePresentationEvent.IsPending(), + "should only have one RestorePresentationEvent"); + mRestorePresentationEvent.Revoke(); + + RefPtr<RestorePresentationEvent> evt = new RestorePresentationEvent(this); + nsresult rv = NS_DispatchToCurrentThread(evt); + if (NS_SUCCEEDED(rv)) { + mRestorePresentationEvent = evt.get(); + // The rest of the restore processing will happen on our event + // callback. + *aRestoring = true; + } + + return rv; +} + +namespace { +class MOZ_STACK_CLASS PresentationEventForgetter +{ +public: + explicit PresentationEventForgetter( + nsRevocableEventPtr<nsDocShell::RestorePresentationEvent>& + aRestorePresentationEvent) + : mRestorePresentationEvent(aRestorePresentationEvent) + , mEvent(aRestorePresentationEvent.get()) + { + } + + ~PresentationEventForgetter() + { + Forget(); + } + + void Forget() + { + if (mRestorePresentationEvent.get() == mEvent) { + mRestorePresentationEvent.Forget(); + mEvent = nullptr; + } + } + +private: + nsRevocableEventPtr<nsDocShell::RestorePresentationEvent>& + mRestorePresentationEvent; + RefPtr<nsDocShell::RestorePresentationEvent> mEvent; +}; + +} // namespace + +nsresult +nsDocShell::RestoreFromHistory() +{ + MOZ_ASSERT(mRestorePresentationEvent.IsPending()); + PresentationEventForgetter forgetter(mRestorePresentationEvent); + + // This section of code follows the same ordering as CreateContentViewer. + if (!mLSHE) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIContentViewer> viewer; + mLSHE->GetContentViewer(getter_AddRefs(viewer)); + if (!viewer) { + return NS_ERROR_FAILURE; + } + + if (mSavingOldViewer) { + // We determined that it was safe to cache the document presentation + // at the time we initiated the new load. We need to check whether + // it's still safe to do so, since there may have been DOM mutations + // or new requests initiated. + nsCOMPtr<nsIDOMDocument> domDoc; + viewer->GetDOMDocument(getter_AddRefs(domDoc)); + nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc); + nsIRequest* request = nullptr; + if (doc) { + request = doc->GetChannel(); + } + mSavingOldViewer = CanSavePresentation(mLoadType, request, doc); + } + + nsCOMPtr<nsIContentViewer> oldCv(mContentViewer); + nsCOMPtr<nsIContentViewer> newCv(viewer); + int32_t minFontSize = 0; + float textZoom = 1.0f; + float pageZoom = 1.0f; + float overrideDPPX = 0.0f; + + bool styleDisabled = false; + if (oldCv && newCv) { + oldCv->GetMinFontSize(&minFontSize); + oldCv->GetTextZoom(&textZoom); + oldCv->GetFullZoom(&pageZoom); + oldCv->GetOverrideDPPX(&overrideDPPX); + oldCv->GetAuthorStyleDisabled(&styleDisabled); + } + + // Protect against mLSHE going away via a load triggered from + // pagehide or unload. + nsCOMPtr<nsISHEntry> origLSHE = mLSHE; + + // Make sure to blow away our mLoadingURI just in case. No loads + // from inside this pagehide. + mLoadingURI = nullptr; + + // Notify the old content viewer that it's being hidden. + FirePageHideNotification(!mSavingOldViewer); + + // If mLSHE was changed as a result of the pagehide event, then + // something else was loaded. Don't finish restoring. + if (mLSHE != origLSHE) { + return NS_OK; + } + + // Add the request to our load group. We do this before swapping out + // the content viewers so that consumers of STATE_START can access + // the old document. We only deal with the toplevel load at this time -- + // to be consistent with normal document loading, subframes cannot start + // loading until after data arrives, which is after STATE_START completes. + + RefPtr<RestorePresentationEvent> currentPresentationRestoration = + mRestorePresentationEvent.get(); + Stop(); + // Make sure we're still restoring the same presentation. + // If we aren't, docshell is in process doing another load already. + NS_ENSURE_STATE(currentPresentationRestoration == + mRestorePresentationEvent.get()); + BeginRestore(viewer, true); + NS_ENSURE_STATE(currentPresentationRestoration == + mRestorePresentationEvent.get()); + forgetter.Forget(); + + // Set mFiredUnloadEvent = false so that the unload handler for the + // *new* document will fire. + mFiredUnloadEvent = false; + + mURIResultedInDocument = true; + nsCOMPtr<nsISHistory> rootSH; + GetRootSessionHistory(getter_AddRefs(rootSH)); + if (rootSH) { + nsCOMPtr<nsISHistoryInternal> hist = do_QueryInterface(rootSH); + rootSH->GetIndex(&mPreviousTransIndex); + hist->UpdateIndex(); + rootSH->GetIndex(&mLoadedTransIndex); +#ifdef DEBUG_PAGE_CACHE + printf("Previous index: %d, Loaded index: %d\n\n", mPreviousTransIndex, + mLoadedTransIndex); +#endif + } + + // Rather than call Embed(), we will retrieve the viewer from the session + // history entry and swap it in. + // XXX can we refactor this so that we can just call Embed()? + PersistLayoutHistoryState(); + nsresult rv; + if (mContentViewer) { + if (mSavingOldViewer && NS_FAILED(CaptureState())) { + if (mOSHE) { + mOSHE->SyncPresentationState(); + } + mSavingOldViewer = false; + } + } + + mSavedRefreshURIList = nullptr; + + // In cases where we use a transient about:blank viewer between loads, + // we never show the transient viewer, so _its_ previous viewer is never + // unhooked from the view hierarchy. Destroy any such previous viewer now, + // before we grab the root view sibling, so that we don't grab a view + // that's about to go away. + + if (mContentViewer) { + nsCOMPtr<nsIContentViewer> previousViewer; + mContentViewer->GetPreviousViewer(getter_AddRefs(previousViewer)); + if (previousViewer) { + mContentViewer->SetPreviousViewer(nullptr); + previousViewer->Destroy(); + } + } + + // Save off the root view's parent and sibling so that we can insert the + // new content viewer's root view at the same position. Also save the + // bounds of the root view's widget. + + nsView* rootViewSibling = nullptr; + nsView* rootViewParent = nullptr; + nsIntRect newBounds(0, 0, 0, 0); + + nsCOMPtr<nsIPresShell> oldPresShell = GetPresShell(); + if (oldPresShell) { + nsViewManager* vm = oldPresShell->GetViewManager(); + if (vm) { + nsView* oldRootView = vm->GetRootView(); + + if (oldRootView) { + rootViewSibling = oldRootView->GetNextSibling(); + rootViewParent = oldRootView->GetParent(); + + mContentViewer->GetBounds(newBounds); + } + } + } + + nsCOMPtr<nsIContent> container; + nsCOMPtr<nsIDocument> sibling; + if (rootViewParent && rootViewParent->GetParent()) { + nsIFrame* frame = rootViewParent->GetParent()->GetFrame(); + container = frame ? frame->GetContent() : nullptr; + } + if (rootViewSibling) { + nsIFrame* frame = rootViewSibling->GetFrame(); + sibling = + frame ? frame->PresContext()->PresShell()->GetDocument() : nullptr; + } + + // Transfer ownership to mContentViewer. By ensuring that either the + // docshell or the session history, but not both, have references to the + // content viewer, we prevent the viewer from being torn down after + // Destroy() is called. + + if (mContentViewer) { + mContentViewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr); + viewer->SetPreviousViewer(mContentViewer); + } + if (mOSHE && (!mContentViewer || !mSavingOldViewer)) { + // We don't plan to save a viewer in mOSHE; tell it to drop + // any other state it's holding. + mOSHE->SyncPresentationState(); + } + + // Order the mContentViewer setup just like Embed does. + mContentViewer = nullptr; + + // Now that we're about to switch documents, forget all of our children. + // Note that we cached them as needed up in CaptureState above. + DestroyChildren(); + + mContentViewer.swap(viewer); + + // Grab all of the related presentation from the SHEntry now. + // Clearing the viewer from the SHEntry will clear all of this state. + nsCOMPtr<nsISupports> windowState; + mLSHE->GetWindowState(getter_AddRefs(windowState)); + mLSHE->SetWindowState(nullptr); + + bool sticky; + mLSHE->GetSticky(&sticky); + + nsCOMPtr<nsIDOMDocument> domDoc; + mContentViewer->GetDOMDocument(getter_AddRefs(domDoc)); + + nsCOMArray<nsIDocShellTreeItem> childShells; + int32_t i = 0; + nsCOMPtr<nsIDocShellTreeItem> child; + while (NS_SUCCEEDED(mLSHE->ChildShellAt(i++, getter_AddRefs(child))) && + child) { + childShells.AppendObject(child); + } + + // get the previous content viewer size + nsIntRect oldBounds(0, 0, 0, 0); + mLSHE->GetViewerBounds(oldBounds); + + // Restore the refresh URI list. The refresh timers will be restarted + // when EndPageLoad() is called. + nsCOMPtr<nsIMutableArray> refreshURIList; + mLSHE->GetRefreshURIList(getter_AddRefs(refreshURIList)); + + // Reattach to the window object. + mIsRestoringDocument = true; // for MediaDocument::BecomeInteractive + rv = mContentViewer->Open(windowState, mLSHE); + mIsRestoringDocument = false; + + // Hack to keep nsDocShellEditorData alive across the + // SetContentViewer(nullptr) call below. + nsAutoPtr<nsDocShellEditorData> data(mLSHE->ForgetEditorData()); + + // Now remove it from the cached presentation. + mLSHE->SetContentViewer(nullptr); + mEODForCurrentDocument = false; + + mLSHE->SetEditorData(data.forget()); + +#ifdef DEBUG + { + nsCOMPtr<nsIMutableArray> refreshURIs; + mLSHE->GetRefreshURIList(getter_AddRefs(refreshURIs)); + nsCOMPtr<nsIDocShellTreeItem> childShell; + mLSHE->ChildShellAt(0, getter_AddRefs(childShell)); + NS_ASSERTION(!refreshURIs && !childShell, + "SHEntry should have cleared presentation state"); + } +#endif + + // Restore the sticky state of the viewer. The viewer has set this state + // on the history entry in Destroy() just before marking itself non-sticky, + // to avoid teardown of the presentation. + mContentViewer->SetSticky(sticky); + + NS_ENSURE_SUCCESS(rv, rv); + + // mLSHE is now our currently-loaded document. + SetHistoryEntry(&mOSHE, mLSHE); + + // XXX special wyciwyg handling in Embed()? + + // We aren't going to restore any items from the LayoutHistoryState, + // but we don't want them to stay around in case the page is reloaded. + SetLayoutHistoryState(nullptr); + + // This is the end of our Embed() replacement + + mSavingOldViewer = false; + mEODForCurrentDocument = false; + + // Tell the event loop to favor plevents over user events, see comments + // in CreateContentViewer. + if (++gNumberOfDocumentsLoading == 1) { + FavorPerformanceHint(true); + } + + if (oldCv && newCv) { + newCv->SetMinFontSize(minFontSize); + newCv->SetTextZoom(textZoom); + newCv->SetFullZoom(pageZoom); + newCv->SetOverrideDPPX(overrideDPPX); + newCv->SetAuthorStyleDisabled(styleDisabled); + } + + nsCOMPtr<nsIDocument> document = do_QueryInterface(domDoc); + if (document) { + RefPtr<nsDocShell> parent = GetParentDocshell(); + if (parent) { + nsCOMPtr<nsIDocument> d = parent->GetDocument(); + if (d) { + if (d->EventHandlingSuppressed()) { + document->SuppressEventHandling(nsIDocument::eEvents, + d->EventHandlingSuppressed()); + } + + // Ick, it'd be nicer to not rewalk all of the subdocs here. + if (d->AnimationsPaused()) { + document->SuppressEventHandling(nsIDocument::eAnimationsOnly, + d->AnimationsPaused()); + } + } + } + + // Use the uri from the mLSHE we had when we entered this function + // (which need not match the document's URI if anchors are involved), + // since that's the history entry we're loading. Note that if we use + // origLSHE we don't have to worry about whether the entry in question + // is still mLSHE or whether it's now mOSHE. + nsCOMPtr<nsIURI> uri; + origLSHE->GetURI(getter_AddRefs(uri)); + SetCurrentURI(uri, document->GetChannel(), true, 0); + } + + // This is the end of our CreateContentViewer() replacement. + // Now we simulate a load. First, we restore the state of the javascript + // window object. + nsCOMPtr<nsPIDOMWindowOuter> privWin = GetWindow(); + NS_ASSERTION(privWin, "could not get nsPIDOMWindow interface"); + + // Now, dispatch a title change event which would happen as the + // <head> is parsed. + document->NotifyPossibleTitleChange(false); + + // Now we simulate appending child docshells for subframes. + for (i = 0; i < childShells.Count(); ++i) { + nsIDocShellTreeItem* childItem = childShells.ObjectAt(i); + nsCOMPtr<nsIDocShell> childShell = do_QueryInterface(childItem); + + // Make sure to not clobber the state of the child. Since AddChild + // always clobbers it, save it off first. + bool allowPlugins; + childShell->GetAllowPlugins(&allowPlugins); + + bool allowJavascript; + childShell->GetAllowJavascript(&allowJavascript); + + bool allowRedirects; + childShell->GetAllowMetaRedirects(&allowRedirects); + + bool allowSubframes; + childShell->GetAllowSubframes(&allowSubframes); + + bool allowImages; + childShell->GetAllowImages(&allowImages); + + bool allowMedia = childShell->GetAllowMedia(); + + bool allowDNSPrefetch; + childShell->GetAllowDNSPrefetch(&allowDNSPrefetch); + + bool allowContentRetargeting = childShell->GetAllowContentRetargeting(); + bool allowContentRetargetingOnChildren = + childShell->GetAllowContentRetargetingOnChildren(); + + uint32_t defaultLoadFlags; + childShell->GetDefaultLoadFlags(&defaultLoadFlags); + + // this.AddChild(child) calls child.SetDocLoaderParent(this), meaning that + // the child inherits our state. Among other things, this means that the + // child inherits our mIsActive, mIsPrerendered and mPrivateBrowsingId, + // which is what we want. + AddChild(childItem); + + childShell->SetAllowPlugins(allowPlugins); + childShell->SetAllowJavascript(allowJavascript); + childShell->SetAllowMetaRedirects(allowRedirects); + childShell->SetAllowSubframes(allowSubframes); + childShell->SetAllowImages(allowImages); + childShell->SetAllowMedia(allowMedia); + childShell->SetAllowDNSPrefetch(allowDNSPrefetch); + childShell->SetAllowContentRetargeting(allowContentRetargeting); + childShell->SetAllowContentRetargetingOnChildren( + allowContentRetargetingOnChildren); + childShell->SetDefaultLoadFlags(defaultLoadFlags); + + rv = childShell->BeginRestore(nullptr, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Make sure to restore the window state after adding the child shells back + // to the tree. This is necessary for Thaw() and Resume() to propagate + // properly. + rv = privWin->RestoreWindowState(windowState); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPresShell> shell = GetPresShell(); + + // We may be displayed on a different monitor (or in a different + // HiDPI mode) than when we got into the history list. So we need + // to check if this has happened. See bug 838239. + + // Because the prescontext normally handles resolution changes via + // a runnable (see nsPresContext::UIResolutionChanged), its device + // context won't be -immediately- updated as a result of calling + // shell->BackingScaleFactorChanged(). + + // But we depend on that device context when adjusting the view size + // via mContentViewer->SetBounds(newBounds) below. So we need to + // explicitly tell it to check for changed resolution here. + if (shell && shell->GetPresContext()->DeviceContext()->CheckDPIChange()) { + shell->BackingScaleFactorChanged(); + } + + nsViewManager* newVM = shell ? shell->GetViewManager() : nullptr; + nsView* newRootView = newVM ? newVM->GetRootView() : nullptr; + + // Insert the new root view at the correct location in the view tree. + if (container) { + nsSubDocumentFrame* subDocFrame = + do_QueryFrame(container->GetPrimaryFrame()); + rootViewParent = subDocFrame ? subDocFrame->EnsureInnerView() : nullptr; + } else { + rootViewParent = nullptr; + } + if (sibling && + sibling->GetShell() && + sibling->GetShell()->GetViewManager()) { + rootViewSibling = sibling->GetShell()->GetViewManager()->GetRootView(); + } else { + rootViewSibling = nullptr; + } + if (rootViewParent && newRootView && + newRootView->GetParent() != rootViewParent) { + nsViewManager* parentVM = rootViewParent->GetViewManager(); + if (parentVM) { + // InsertChild(parent, child, sib, true) inserts the child after + // sib in content order, which is before sib in view order. BUT + // when sib is null it inserts at the end of the the document + // order, i.e., first in view order. But when oldRootSibling is + // null, the old root as at the end of the view list --- last in + // content order --- and we want to call InsertChild(parent, child, + // nullptr, false) in that case. + parentVM->InsertChild(rootViewParent, newRootView, + rootViewSibling, + rootViewSibling ? true : false); + + NS_ASSERTION(newRootView->GetNextSibling() == rootViewSibling, + "error in InsertChild"); + } + } + + nsCOMPtr<nsPIDOMWindowInner> privWinInner = privWin->GetCurrentInnerWindow(); + + // If parent is suspended, increase suspension count. + // This can't be done as early as event suppression since this + // depends on docshell tree. + privWinInner->SyncStateFromParentWindow(); + + // Now that all of the child docshells have been put into place, we can + // restart the timers for the window and all of the child frames. + privWinInner->Resume(); + + // Restore the refresh URI list. The refresh timers will be restarted + // when EndPageLoad() is called. + mRefreshURIList = refreshURIList; + + // Meta-refresh timers have been restarted for this shell, but not + // for our children. Walk the child shells and restart their timers. + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> child = do_QueryObject(iter.GetNext()); + if (child) { + child->ResumeRefreshURIs(); + } + } + + // Make sure this presentation is the same size as the previous + // presentation. If this is not the same size we showed it at last time, + // then we need to resize the widget. + + // XXXbryner This interacts poorly with Firefox's infobar. If the old + // presentation had the infobar visible, then we will resize the new + // presentation to that smaller size. However, firing the locationchanged + // event will hide the infobar, which will immediately resize the window + // back to the larger size. A future optimization might be to restore + // the presentation at the "wrong" size, then fire the locationchanged + // event and check whether the docshell's new size is the same as the + // cached viewer size (skipping the resize if they are equal). + + if (newRootView) { + if (!newBounds.IsEmpty() && !newBounds.IsEqualEdges(oldBounds)) { +#ifdef DEBUG_PAGE_CACHE + printf("resize widget(%d, %d, %d, %d)\n", newBounds.x, + newBounds.y, newBounds.width, newBounds.height); +#endif + mContentViewer->SetBounds(newBounds); + } else { + nsIScrollableFrame* rootScrollFrame = + shell->GetRootScrollFrameAsScrollableExternal(); + if (rootScrollFrame) { + rootScrollFrame->PostScrolledAreaEventForCurrentArea(); + } + } + } + + // The FinishRestore call below can kill these, null them out so we don't + // have invalid pointer lying around. + newRootView = rootViewSibling = rootViewParent = nullptr; + newVM = nullptr; + + // Simulate the completion of the load. + nsDocShell::FinishRestore(); + + // Restart plugins, and paint the content. + if (shell) { + shell->Thaw(); + } + + return privWin->FireDelayedDOMEvents(); +} + +nsresult +nsDocShell::CreateContentViewer(const nsACString& aContentType, + nsIRequest* aRequest, + nsIStreamListener** aContentHandler) +{ + *aContentHandler = nullptr; + + // Can we check the content type of the current content viewer + // and reuse it without destroying it and re-creating it? + + NS_ASSERTION(mLoadGroup, "Someone ignored return from Init()?"); + + // Instantiate the content viewer object + nsCOMPtr<nsIContentViewer> viewer; + nsresult rv = NewContentViewerObj(aContentType, aRequest, mLoadGroup, + aContentHandler, getter_AddRefs(viewer)); + + if (NS_FAILED(rv)) { + return rv; + } + + // Notify the current document that it is about to be unloaded!! + // + // It is important to fire the unload() notification *before* any state + // is changed within the DocShell - otherwise, javascript will get the + // wrong information :-( + // + + if (mSavingOldViewer) { + // We determined that it was safe to cache the document presentation + // at the time we initiated the new load. We need to check whether + // it's still safe to do so, since there may have been DOM mutations + // or new requests initiated. + nsCOMPtr<nsIDOMDocument> domDoc; + viewer->GetDOMDocument(getter_AddRefs(domDoc)); + nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc); + mSavingOldViewer = CanSavePresentation(mLoadType, aRequest, doc); + } + + NS_ASSERTION(!mLoadingURI, "Re-entering unload?"); + + nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest); + if (aOpenedChannel) { + aOpenedChannel->GetURI(getter_AddRefs(mLoadingURI)); + } + FirePageHideNotification(!mSavingOldViewer); + mLoadingURI = nullptr; + + // Set mFiredUnloadEvent = false so that the unload handler for the + // *new* document will fire. + mFiredUnloadEvent = false; + + // we've created a new document so go ahead and call + // OnLoadingSite(), but don't fire OnLocationChange() + // notifications before we've called Embed(). See bug 284993. + mURIResultedInDocument = true; + + if (mLoadType == LOAD_ERROR_PAGE) { + // We need to set the SH entry and our current URI here and not + // at the moment we load the page. We want the same behavior + // of Stop() as for a normal page load. See bug 514232 for details. + + // Revert mLoadType to load type to state the page load failed, + // following function calls need it. + mLoadType = mFailedLoadType; + + nsCOMPtr<nsIChannel> failedChannel = mFailedChannel; + + nsIDocument* doc = viewer->GetDocument(); + if (doc) { + doc->SetFailedChannel(failedChannel); + } + + // Make sure we have a URI to set currentURI. + nsCOMPtr<nsIURI> failedURI; + if (failedChannel) { + NS_GetFinalChannelURI(failedChannel, getter_AddRefs(failedURI)); + } + + if (!failedURI) { + failedURI = mFailedURI; + } + if (!failedURI) { + // We need a URI object to store a session history entry, so make up a URI + NS_NewURI(getter_AddRefs(failedURI), "about:blank"); + } + + // When we don't have failedURI, something wrong will happen. See + // bug 291876. + MOZ_ASSERT(failedURI, "We don't have a URI for history APIs."); + + mFailedChannel = nullptr; + mFailedURI = nullptr; + + // Create an shistory entry for the old load. + if (failedURI) { + bool errorOnLocationChangeNeeded = OnNewURI( + failedURI, failedChannel, nullptr, nullptr, mLoadType, false, false, false); + + if (errorOnLocationChangeNeeded) { + FireOnLocationChange(this, failedChannel, failedURI, + LOCATION_CHANGE_ERROR_PAGE); + } + } + + // Be sure to have a correct mLSHE, it may have been cleared by + // EndPageLoad. See bug 302115. + if (mSessionHistory && !mLSHE) { + int32_t idx; + mSessionHistory->GetRequestedIndex(&idx); + if (idx == -1) { + mSessionHistory->GetIndex(&idx); + } + mSessionHistory->GetEntryAtIndex(idx, false, getter_AddRefs(mLSHE)); + } + + mLoadType = LOAD_ERROR_PAGE; + } + + bool onLocationChangeNeeded = OnLoadingSite(aOpenedChannel, false); + + // let's try resetting the load group if we need to... + nsCOMPtr<nsILoadGroup> currentLoadGroup; + NS_ENSURE_SUCCESS( + aOpenedChannel->GetLoadGroup(getter_AddRefs(currentLoadGroup)), + NS_ERROR_FAILURE); + + if (currentLoadGroup != mLoadGroup) { + nsLoadFlags loadFlags = 0; + + // Cancel any URIs that are currently loading... + // XXX: Need to do this eventually Stop(); + // + // Retarget the document to this loadgroup... + // + /* First attach the channel to the right loadgroup + * and then remove from the old loadgroup. This + * puts the notifications in the right order and + * we don't null-out mLSHE in OnStateChange() for + * all redirected urls + */ + aOpenedChannel->SetLoadGroup(mLoadGroup); + + // Mark the channel as being a document URI... + aOpenedChannel->GetLoadFlags(&loadFlags); + loadFlags |= nsIChannel::LOAD_DOCUMENT_URI; + + aOpenedChannel->SetLoadFlags(loadFlags); + + mLoadGroup->AddRequest(aRequest, nullptr); + if (currentLoadGroup) { + currentLoadGroup->RemoveRequest(aRequest, nullptr, NS_BINDING_RETARGETED); + } + + // Update the notification callbacks, so that progress and + // status information are sent to the right docshell... + aOpenedChannel->SetNotificationCallbacks(this); + } + + NS_ENSURE_SUCCESS(Embed(viewer, "", nullptr), NS_ERROR_FAILURE); + + mSavedRefreshURIList = nullptr; + mSavingOldViewer = false; + mEODForCurrentDocument = false; + + // if this document is part of a multipart document, + // the ID can be used to distinguish it from the other parts. + nsCOMPtr<nsIMultiPartChannel> multiPartChannel(do_QueryInterface(aRequest)); + if (multiPartChannel) { + nsCOMPtr<nsIPresShell> shell = GetPresShell(); + if (NS_SUCCEEDED(rv) && shell) { + nsIDocument* doc = shell->GetDocument(); + if (doc) { + uint32_t partID; + multiPartChannel->GetPartID(&partID); + doc->SetPartID(partID); + } + } + } + + // Give hint to native plevent dispatch mechanism. If a document + // is loading the native plevent dispatch mechanism should favor + // performance over normal native event dispatch priorities. + if (++gNumberOfDocumentsLoading == 1) { + // Hint to favor performance for the plevent notification mechanism. + // We want the pages to load as fast as possible even if its means + // native messages might be starved. + FavorPerformanceHint(true); + } + + if (onLocationChangeNeeded) { + FireOnLocationChange(this, aRequest, mCurrentURI, 0); + } + + return NS_OK; +} + +nsresult +nsDocShell::NewContentViewerObj(const nsACString& aContentType, + nsIRequest* aRequest, nsILoadGroup* aLoadGroup, + nsIStreamListener** aContentHandler, + nsIContentViewer** aViewer) +{ + nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest); + + nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory = + nsContentUtils::FindInternalContentViewer(aContentType); + if (!docLoaderFactory) { + return NS_ERROR_FAILURE; + } + + // Now create an instance of the content viewer nsLayoutDLF makes the + // determination if it should be a "view-source" instead of "view" + nsresult rv = docLoaderFactory->CreateInstance("view", + aOpenedChannel, + aLoadGroup, aContentType, + this, + nullptr, + aContentHandler, + aViewer); + NS_ENSURE_SUCCESS(rv, rv); + + (*aViewer)->SetContainer(this); + return NS_OK; +} + +nsresult +nsDocShell::SetupNewViewer(nsIContentViewer* aNewViewer) +{ + // + // Copy content viewer state from previous or parent content viewer. + // + // The following logic is mirrored in nsHTMLDocument::StartDocumentLoad! + // + // Do NOT to maintain a reference to the old content viewer outside + // of this "copying" block, or it will not be destroyed until the end of + // this routine and all <SCRIPT>s and event handlers fail! (bug 20315) + // + // In this block of code, if we get an error result, we return it + // but if we get a null pointer, that's perfectly legal for parent + // and parentContentViewer. + // + + int32_t x = 0; + int32_t y = 0; + int32_t cx = 0; + int32_t cy = 0; + + // This will get the size from the current content viewer or from the + // Init settings + DoGetPositionAndSize(&x, &y, &cx, &cy); + + nsCOMPtr<nsIDocShellTreeItem> parentAsItem; + NS_ENSURE_SUCCESS(GetSameTypeParent(getter_AddRefs(parentAsItem)), + NS_ERROR_FAILURE); + nsCOMPtr<nsIDocShell> parent(do_QueryInterface(parentAsItem)); + + nsAutoCString forceCharset; + nsAutoCString hintCharset; + int32_t hintCharsetSource; + int32_t minFontSize; + float textZoom; + float pageZoom; + float overrideDPPX; + bool styleDisabled; + // |newMUDV| also serves as a flag to set the data from the above vars + nsCOMPtr<nsIContentViewer> newCv; + + if (mContentViewer || parent) { + nsCOMPtr<nsIContentViewer> oldCv; + if (mContentViewer) { + // Get any interesting state from old content viewer + // XXX: it would be far better to just reuse the document viewer , + // since we know we're just displaying the same document as before + oldCv = mContentViewer; + + // Tell the old content viewer to hibernate in session history when + // it is destroyed. + + if (mSavingOldViewer && NS_FAILED(CaptureState())) { + if (mOSHE) { + mOSHE->SyncPresentationState(); + } + mSavingOldViewer = false; + } + } else { + // No old content viewer, so get state from parent's content viewer + parent->GetContentViewer(getter_AddRefs(oldCv)); + } + + if (oldCv) { + newCv = aNewViewer; + if (newCv) { + NS_ENSURE_SUCCESS(oldCv->GetForceCharacterSet(forceCharset), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(oldCv->GetHintCharacterSet(hintCharset), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(oldCv->GetHintCharacterSetSource(&hintCharsetSource), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(oldCv->GetMinFontSize(&minFontSize), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(oldCv->GetTextZoom(&textZoom), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(oldCv->GetFullZoom(&pageZoom), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(oldCv->GetOverrideDPPX(&overrideDPPX), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(oldCv->GetAuthorStyleDisabled(&styleDisabled), + NS_ERROR_FAILURE); + } + } + } + + nscolor bgcolor = NS_RGBA(0, 0, 0, 0); + // Ensure that the content viewer is destroyed *after* the GC - bug 71515 + nsCOMPtr<nsIContentViewer> contentViewer = mContentViewer; + if (contentViewer) { + // Stop any activity that may be happening in the old document before + // releasing it... + contentViewer->Stop(); + + // Try to extract the canvas background color from the old + // presentation shell, so we can use it for the next document. + nsCOMPtr<nsIPresShell> shell; + contentViewer->GetPresShell(getter_AddRefs(shell)); + + if (shell) { + bgcolor = shell->GetCanvasBackground(); + } + + contentViewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr); + aNewViewer->SetPreviousViewer(contentViewer); + } + if (mOSHE && (!mContentViewer || !mSavingOldViewer)) { + // We don't plan to save a viewer in mOSHE; tell it to drop + // any other state it's holding. + mOSHE->SyncPresentationState(); + } + + mContentViewer = nullptr; + + // Now that we're about to switch documents, forget all of our children. + // Note that we cached them as needed up in CaptureState above. + DestroyChildren(); + + mContentViewer = aNewViewer; + + nsCOMPtr<nsIWidget> widget; + NS_ENSURE_SUCCESS(GetMainWidget(getter_AddRefs(widget)), NS_ERROR_FAILURE); + + nsIntRect bounds(x, y, cx, cy); + + mContentViewer->SetNavigationTiming(mTiming); + + if (NS_FAILED(mContentViewer->Init(widget, bounds))) { + mContentViewer = nullptr; + NS_WARNING("ContentViewer Initialization failed"); + return NS_ERROR_FAILURE; + } + + // If we have old state to copy, set the old state onto the new content + // viewer + if (newCv) { + NS_ENSURE_SUCCESS(newCv->SetForceCharacterSet(forceCharset), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newCv->SetHintCharacterSet(hintCharset), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newCv->SetHintCharacterSetSource(hintCharsetSource), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newCv->SetMinFontSize(minFontSize), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newCv->SetTextZoom(textZoom), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newCv->SetFullZoom(pageZoom), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newCv->SetOverrideDPPX(overrideDPPX), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newCv->SetAuthorStyleDisabled(styleDisabled), + NS_ERROR_FAILURE); + } + + // Stuff the bgcolor from the old pres shell into the new + // pres shell. This improves page load continuity. + nsCOMPtr<nsIPresShell> shell; + mContentViewer->GetPresShell(getter_AddRefs(shell)); + + if (shell) { + shell->SetCanvasBackground(bgcolor); + } + + // XXX: It looks like the LayoutState gets restored again in Embed() + // right after the call to SetupNewViewer(...) + + // We don't show the mContentViewer yet, since we want to draw the old page + // until we have enough of the new page to show. Just return with the new + // viewer still set to hidden. + + return NS_OK; +} + +nsresult +nsDocShell::SetDocCurrentStateObj(nsISHEntry* aShEntry) +{ + NS_ENSURE_STATE(mContentViewer); + nsCOMPtr<nsIDocument> document = GetDocument(); + NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); + + nsCOMPtr<nsIStructuredCloneContainer> scContainer; + if (aShEntry) { + nsresult rv = aShEntry->GetStateData(getter_AddRefs(scContainer)); + NS_ENSURE_SUCCESS(rv, rv); + + // If aShEntry is null, just set the document's state object to null. + } + + // It's OK for scContainer too be null here; that just means there's no + // state data associated with this history entry. + document->SetStateObject(scContainer); + + return NS_OK; +} + +nsresult +nsDocShell::CheckLoadingPermissions() +{ + // This method checks whether the caller may load content into + // this docshell. Even though we've done our best to hide windows + // from code that doesn't have the right to access them, it's + // still possible for an evil site to open a window and access + // frames in the new window through window.frames[] (which is + // allAccess for historic reasons), so we still need to do this + // check on load. + nsresult rv = NS_OK; + + if (!gValidateOrigin || !IsFrame()) { + // Origin validation was turned off, or we're not a frame. + // Permit all loads. + + return rv; + } + + // Note - The check for a current JSContext here isn't necessarily sensical. + // It's just designed to preserve the old semantics during a mass-conversion + // patch. + if (!nsContentUtils::GetCurrentJSContext()) { + return NS_OK; + } + + // Check if the caller is from the same origin as this docshell, + // or any of its ancestors. + nsCOMPtr<nsIDocShellTreeItem> item(this); + do { + nsCOMPtr<nsIScriptGlobalObject> sgo = do_GetInterface(item); + nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(sgo)); + + nsIPrincipal* p; + if (!sop || !(p = sop->GetPrincipal())) { + return NS_ERROR_UNEXPECTED; + } + + if (nsContentUtils::SubjectPrincipal()->Subsumes(p)) { + // Same origin, permit load + return NS_OK; + } + + nsCOMPtr<nsIDocShellTreeItem> tmp; + item->GetSameTypeParent(getter_AddRefs(tmp)); + item.swap(tmp); + } while (item); + + return NS_ERROR_DOM_PROP_ACCESS_DENIED; +} + +//***************************************************************************** +// nsDocShell: Site Loading +//***************************************************************************** + +namespace { + +#ifdef MOZ_PLACES +// Callback used by CopyFavicon to inform the favicon service that one URI +// (mNewURI) has the same favicon URI (OnComplete's aFaviconURI) as another. +class nsCopyFaviconCallback final : public nsIFaviconDataCallback +{ +public: + NS_DECL_ISUPPORTS + + nsCopyFaviconCallback(mozIAsyncFavicons* aSvc, + nsIURI* aNewURI, + nsIPrincipal* aLoadingPrincipal, + bool aInPrivateBrowsing) + : mSvc(aSvc) + , mNewURI(aNewURI) + , mLoadingPrincipal(aLoadingPrincipal) + , mInPrivateBrowsing(aInPrivateBrowsing) + { + } + + NS_IMETHOD + OnComplete(nsIURI* aFaviconURI, uint32_t aDataLen, + const uint8_t* aData, const nsACString& aMimeType) override + { + // Continue only if there is an associated favicon. + if (!aFaviconURI) { + return NS_OK; + } + + MOZ_ASSERT(aDataLen == 0, + "We weren't expecting the callback to deliver data."); + + nsCOMPtr<mozIPlacesPendingOperation> po; + return mSvc->SetAndFetchFaviconForPage( + mNewURI, aFaviconURI, false, + mInPrivateBrowsing ? nsIFaviconService::FAVICON_LOAD_PRIVATE : + nsIFaviconService::FAVICON_LOAD_NON_PRIVATE, + nullptr, mLoadingPrincipal, getter_AddRefs(po)); + } + +private: + ~nsCopyFaviconCallback() {} + + nsCOMPtr<mozIAsyncFavicons> mSvc; + nsCOMPtr<nsIURI> mNewURI; + nsCOMPtr<nsIPrincipal> mLoadingPrincipal; + bool mInPrivateBrowsing; +}; + +NS_IMPL_ISUPPORTS(nsCopyFaviconCallback, nsIFaviconDataCallback) +#endif + +} // namespace + +void +nsDocShell::CopyFavicon(nsIURI* aOldURI, + nsIURI* aNewURI, + nsIPrincipal* aLoadingPrincipal, + bool aInPrivateBrowsing) +{ + if (XRE_IsContentProcess()) { + dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); + if (contentChild) { + mozilla::ipc::URIParams oldURI, newURI; + SerializeURI(aOldURI, oldURI); + SerializeURI(aNewURI, newURI); + contentChild->SendCopyFavicon(oldURI, newURI, + IPC::Principal(aLoadingPrincipal), + aInPrivateBrowsing); + } + return; + } + +#ifdef MOZ_PLACES + nsCOMPtr<mozIAsyncFavicons> favSvc = + do_GetService("@mozilla.org/browser/favicon-service;1"); + if (favSvc) { + nsCOMPtr<nsIFaviconDataCallback> callback = + new nsCopyFaviconCallback(favSvc, aNewURI, + aLoadingPrincipal, + aInPrivateBrowsing); + favSvc->GetFaviconURLForPage(aOldURI, callback); + } +#endif +} + +class InternalLoadEvent : public Runnable +{ +public: + InternalLoadEvent(nsDocShell* aDocShell, nsIURI* aURI, + nsIURI* aOriginalURI, bool aLoadReplace, + nsIURI* aReferrer, uint32_t aReferrerPolicy, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, uint32_t aFlags, + const char* aTypeHint, nsIInputStream* aPostData, + nsIInputStream* aHeadersData, uint32_t aLoadType, + nsISHEntry* aSHEntry, bool aFirstParty, + const nsAString& aSrcdoc, nsIDocShell* aSourceDocShell, + nsIURI* aBaseURI) + : mSrcdoc(aSrcdoc) + , mDocShell(aDocShell) + , mURI(aURI) + , mOriginalURI(aOriginalURI) + , mLoadReplace(aLoadReplace) + , mReferrer(aReferrer) + , mReferrerPolicy(aReferrerPolicy) + , mTriggeringPrincipal(aTriggeringPrincipal) + , mPrincipalToInherit(aPrincipalToInherit) + , mPostData(aPostData) + , mHeadersData(aHeadersData) + , mSHEntry(aSHEntry) + , mFlags(aFlags) + , mLoadType(aLoadType) + , mFirstParty(aFirstParty) + , mSourceDocShell(aSourceDocShell) + , mBaseURI(aBaseURI) + { + // Make sure to keep null things null as needed + if (aTypeHint) { + mTypeHint = aTypeHint; + } + } + + NS_IMETHOD + Run() override + { + return mDocShell->InternalLoad(mURI, mOriginalURI, + mLoadReplace, + mReferrer, + mReferrerPolicy, + mTriggeringPrincipal, mPrincipalToInherit, + mFlags, EmptyString(), mTypeHint.get(), + NullString(), mPostData, mHeadersData, + mLoadType, mSHEntry, mFirstParty, + mSrcdoc, mSourceDocShell, mBaseURI, + nullptr, nullptr); + } + +private: + // Use IDL strings so .get() returns null by default + nsXPIDLString mWindowTarget; + nsXPIDLCString mTypeHint; + nsString mSrcdoc; + + RefPtr<nsDocShell> mDocShell; + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsIURI> mOriginalURI; + bool mLoadReplace; + nsCOMPtr<nsIURI> mReferrer; + uint32_t mReferrerPolicy; + nsCOMPtr<nsIPrincipal> mTriggeringPrincipal; + nsCOMPtr<nsIPrincipal> mPrincipalToInherit; + nsCOMPtr<nsIInputStream> mPostData; + nsCOMPtr<nsIInputStream> mHeadersData; + nsCOMPtr<nsISHEntry> mSHEntry; + uint32_t mFlags; + uint32_t mLoadType; + bool mFirstParty; + nsCOMPtr<nsIDocShell> mSourceDocShell; + nsCOMPtr<nsIURI> mBaseURI; +}; + +/** + * Returns true if we started an asynchronous load (i.e., from the network), but + * the document we're loading there hasn't yet become this docshell's active + * document. + * + * When JustStartedNetworkLoad is true, you should be careful about modifying + * mLoadType and mLSHE. These are both set when the asynchronous load first + * starts, and the load expects that, when it eventually runs InternalLoad, + * mLoadType and mLSHE will have their original values. + */ +bool +nsDocShell::JustStartedNetworkLoad() +{ + return mDocumentRequest && mDocumentRequest != GetCurrentDocChannel(); +} + +nsresult +nsDocShell::CreatePrincipalFromReferrer(nsIURI* aReferrer, + nsIPrincipal** aResult) +{ + PrincipalOriginAttributes attrs; + attrs.InheritFromDocShellToDoc(mOriginAttributes, aReferrer); + nsCOMPtr<nsIPrincipal> prin = + BasePrincipal::CreateCodebasePrincipal(aReferrer, attrs); + prin.forget(aResult); + + return *aResult ? NS_OK : NS_ERROR_FAILURE; +} + +bool +nsDocShell::IsAboutNewtab(nsIURI* aURI) +{ + if (!aURI) { + return false; + } + bool isAbout; + if (NS_WARN_IF(NS_FAILED(aURI->SchemeIs("about", &isAbout)))) { + return false; + } + if (!isAbout) { + return false; + } + + nsAutoCString module; + if (NS_WARN_IF(NS_FAILED(NS_GetAboutModuleName(aURI, module)))) { + return false; + } + return module.Equals("newtab"); +} + +NS_IMETHODIMP +nsDocShell::InternalLoad(nsIURI* aURI, + nsIURI* aOriginalURI, + bool aLoadReplace, + nsIURI* aReferrer, + uint32_t aReferrerPolicy, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + uint32_t aFlags, + const nsAString& aWindowTarget, + const char* aTypeHint, + const nsAString& aFileName, + nsIInputStream* aPostData, + nsIInputStream* aHeadersData, + uint32_t aLoadType, + nsISHEntry* aSHEntry, + bool aFirstParty, + const nsAString& aSrcdoc, + nsIDocShell* aSourceDocShell, + nsIURI* aBaseURI, + nsIDocShell** aDocShell, + nsIRequest** aRequest) +{ + MOZ_ASSERT(aTriggeringPrincipal, "need a valid TriggeringPrincipal"); + + nsresult rv = NS_OK; + mOriginalUriString.Truncate(); + + if (gDocShellLeakLog && MOZ_LOG_TEST(gDocShellLeakLog, LogLevel::Debug)) { + PR_LogPrint("DOCSHELL %p InternalLoad %s\n", + this, aURI ? aURI->GetSpecOrDefault().get() : ""); + } + // Initialize aDocShell/aRequest + if (aDocShell) { + *aDocShell = nullptr; + } + if (aRequest) { + *aRequest = nullptr; + } + + if (!aURI) { + return NS_ERROR_NULL_POINTER; + } + + NS_ENSURE_TRUE(IsValidLoadType(aLoadType), NS_ERROR_INVALID_ARG); + + NS_ENSURE_TRUE(!mIsBeingDestroyed, NS_ERROR_NOT_AVAILABLE); + + rv = EnsureScriptEnvironment(); + if (NS_FAILED(rv)) { + return rv; + } + + // wyciwyg urls can only be loaded through history. Any normal load of + // wyciwyg through docshell is illegal. Disallow such loads. + if (aLoadType & LOAD_CMD_NORMAL) { + bool isWyciwyg = false; + rv = aURI->SchemeIs("wyciwyg", &isWyciwyg); + if ((isWyciwyg && NS_SUCCEEDED(rv)) || NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + } + + bool isJavaScript = false; + if (NS_FAILED(aURI->SchemeIs("javascript", &isJavaScript))) { + isJavaScript = false; + } + + bool isTargetTopLevelDocShell = false; + nsCOMPtr<nsIDocShell> targetDocShell; + if (!aWindowTarget.IsEmpty()) { + // Locate the target DocShell. + nsCOMPtr<nsIDocShellTreeItem> targetItem; + // Only _self, _parent, and _top are supported in noopener case. But we + // have to be careful to not apply that to the noreferrer case. See bug + // 1358469. + bool allowNamedTarget = !(aFlags & INTERNAL_LOAD_FLAGS_NO_OPENER) || + (aFlags & INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER); + if (allowNamedTarget || + aWindowTarget.LowerCaseEqualsLiteral("_self") || + aWindowTarget.LowerCaseEqualsLiteral("_parent") || + aWindowTarget.LowerCaseEqualsLiteral("_top")) { + rv = FindItemWithName(aWindowTarget, nullptr, this, + getter_AddRefs(targetItem)); + NS_ENSURE_SUCCESS(rv, rv); + } + + targetDocShell = do_QueryInterface(targetItem); + if (targetDocShell) { + // If the targetDocShell and the rootDocShell are the same, then the + // targetDocShell is the top level document and hence we should + // consider this TYPE_DOCUMENT + // + // For example: + // 1. target="_top" + // 2. target="_parent", where this docshell is in the 2nd level of + // docshell tree. + nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot; + targetDocShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot)); + NS_ASSERTION(sameTypeRoot, + "No document shell root tree item from targetDocShell!"); + nsCOMPtr<nsIDocShell> rootShell = do_QueryInterface(sameTypeRoot); + NS_ASSERTION(rootShell, + "No root docshell from document shell root tree item."); + isTargetTopLevelDocShell = targetDocShell == rootShell; + } else { + // If the targetDocShell doesn't exist, then this is a new docShell + // and we should consider this a TYPE_DOCUMENT load + // + // For example, when target="_blank" + isTargetTopLevelDocShell = true; + } + } + + // The contentType will be INTERNAL_(I)FRAME if: + // 1. This docshell is for iframe. + // 2. AND aWindowTarget is not a new window, nor a top-level window. + // + // This variable will be used when we call NS_CheckContentLoadPolicy, and + // later when we call DoURILoad. + uint32_t contentType; + if (IsFrame() && !isTargetTopLevelDocShell) { + nsCOMPtr<Element> requestingElement = + mScriptGlobal->AsOuter()->GetFrameElementInternal(); + if (requestingElement) { + contentType = requestingElement->IsHTMLElement(nsGkAtoms::iframe) ? + nsIContentPolicy::TYPE_INTERNAL_IFRAME : nsIContentPolicy::TYPE_INTERNAL_FRAME; + } else { + // If we have lost our frame element by now, just assume we're + // an iframe since that's more common. + contentType = nsIContentPolicy::TYPE_INTERNAL_IFRAME; + } + } else { + contentType = nsIContentPolicy::TYPE_DOCUMENT; + } + + // If there's no targetDocShell, that means we are about to create a new window, + // perform a content policy check before creating the window. + if (!targetDocShell) { + nsCOMPtr<Element> requestingElement; + nsISupports* requestingContext = nullptr; + + if (contentType == nsIContentPolicy::TYPE_DOCUMENT) { + if (XRE_IsContentProcess()) { + // In e10s the child process doesn't have access to the element that + // contains the browsing context (because that element is in the chrome + // process). So we just pass mScriptGlobal. + requestingContext = ToSupports(mScriptGlobal); + } else { + // This is for loading non-e10s tabs and toplevel windows of various + // sorts. + // For the toplevel window cases, requestingElement will be null. + requestingElement = mScriptGlobal->AsOuter()->GetFrameElementInternal(); + requestingContext = requestingElement; + } + } else { + requestingElement = mScriptGlobal->AsOuter()->GetFrameElementInternal(); + requestingContext = requestingElement; + +#ifdef DEBUG + if (requestingElement) { + // Get the docshell type for requestingElement. + nsCOMPtr<nsIDocument> requestingDoc = requestingElement->OwnerDoc(); + nsCOMPtr<nsIDocShell> elementDocShell = requestingDoc->GetDocShell(); + + // requestingElement docshell type = current docshell type. + MOZ_ASSERT(mItemType == elementDocShell->ItemType(), + "subframes should have the same docshell type as their parent"); + } +#endif + } + + int16_t shouldLoad = nsIContentPolicy::ACCEPT; + rv = NS_CheckContentLoadPolicy(contentType, + aURI, + aTriggeringPrincipal, + requestingContext, + EmptyCString(), // mime guess + nullptr, // extra + &shouldLoad); + + if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { + if (NS_SUCCEEDED(rv) && shouldLoad == nsIContentPolicy::REJECT_TYPE) { + return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT; + } + + return NS_ERROR_CONTENT_BLOCKED; + } + + // If HSTS priming was set by nsMixedContentBlocker::ShouldLoad, and we + // would block due to mixed content, go ahead and block here. If we try to + // proceed with priming, we will error out later on. + nsCOMPtr<nsIDocShell> docShell = NS_CP_GetDocShellFromContext(requestingContext); + // When loading toplevel windows, requestingContext can be null. We don't + // really care about HSTS in that situation, though; loads in toplevel + // windows should all be browser UI. + if (docShell) { + nsIDocument* document = docShell->GetDocument(); + NS_ENSURE_TRUE(document, NS_OK); + + HSTSPrimingState state = document->GetHSTSPrimingStateForLocation(aURI); + if (state == HSTSPrimingState::eHSTS_PRIMING_BLOCK) { + // HSTS Priming currently disabled for InternalLoad, so we need to clear + // the location that was added by nsMixedContentBlocker::ShouldLoad + // Bug 1269815 will address images loaded via InternalLoad + document->ClearHSTSPrimingLocation(aURI); + return NS_ERROR_CONTENT_BLOCKED; + } + } + } + + nsCOMPtr<nsIPrincipal> principalToInherit = aPrincipalToInherit; + // + // Get a principal from the current document if necessary. Note that we only + // do this for URIs that inherit a security context and local file URIs; + // in particular we do NOT do this for about:blank. This way, random + // about:blank loads that have no principal (which basically means they were + // done by someone from chrome manually messing with our nsIWebNavigation + // or by C++ setting document.location) don't get a funky principal. If + // callers want something interesting to happen with the about:blank + // principal in this case, they should pass aPrincipalToInherit in. + // + { + bool inherits; + // One more twist: Don't inherit the principal for external loads. + if (aLoadType != LOAD_NORMAL_EXTERNAL && !principalToInherit && + (aFlags & INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL) && + NS_SUCCEEDED(nsContentUtils::URIInheritsSecurityContext(aURI, + &inherits)) && + inherits) { + principalToInherit = GetInheritedPrincipal(true); + } + } + + // Don't allow loads that would inherit our security context + // if this document came from an unsafe channel. + { + bool willInherit; + // This condition needs to match the one in + // nsContentUtils::ChannelShouldInheritPrincipal. + // Except we reverse the rv check to be safe in case + // nsContentUtils::URIInheritsSecurityContext fails here and + // succeeds there. + rv = nsContentUtils::URIInheritsSecurityContext(aURI, &willInherit); + if (NS_FAILED(rv) || willInherit || NS_IsAboutBlank(aURI)) { + nsCOMPtr<nsIDocShellTreeItem> treeItem = this; + do { + nsCOMPtr<nsIDocShell> itemDocShell = do_QueryInterface(treeItem); + bool isUnsafe; + if (itemDocShell && + NS_SUCCEEDED(itemDocShell->GetChannelIsUnsafe(&isUnsafe)) && + isUnsafe) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + nsCOMPtr<nsIDocShellTreeItem> parent; + treeItem->GetSameTypeParent(getter_AddRefs(parent)); + parent.swap(treeItem); + } while (treeItem); + } + } + + // + // Resolve the window target before going any further... + // If the load has been targeted to another DocShell, then transfer the + // load to it... + // + if (!aWindowTarget.IsEmpty()) { + // We've already done our owner-inheriting. Mask out that bit, so we + // don't try inheriting an owner from the target window if we came up + // with a null owner above. + aFlags = aFlags & ~INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL; + + bool isNewWindow = false; + if (!targetDocShell) { + // If the docshell's document is sandboxed, only open a new window + // if the document's SANDBOXED_AUXILLARY_NAVIGATION flag is not set. + // (i.e. if allow-popups is specified) + NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE); + nsIDocument* doc = mContentViewer->GetDocument(); + uint32_t sandboxFlags = 0; + + if (doc) { + sandboxFlags = doc->GetSandboxFlags(); + if (sandboxFlags & SANDBOXED_AUXILIARY_NAVIGATION) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } + } + + nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow(); + NS_ENSURE_TRUE(win, NS_ERROR_NOT_AVAILABLE); + + nsCOMPtr<nsPIDOMWindowOuter> newWin; + nsAutoCString spec; + if (aURI) { + aURI->GetSpec(spec); + } + // If we are a noopener load, we just hand the whole thing over to our + // window. + if (aFlags & INTERNAL_LOAD_FLAGS_NO_OPENER) { + // Various asserts that we know to hold because NO_OPENER loads can only + // happen for links. + MOZ_ASSERT(!aLoadReplace); + MOZ_ASSERT(aPrincipalToInherit == aTriggeringPrincipal); + MOZ_ASSERT(aFlags == INTERNAL_LOAD_FLAGS_NO_OPENER || + aFlags == (INTERNAL_LOAD_FLAGS_NO_OPENER | + INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER)); + MOZ_ASSERT(!aPostData); + MOZ_ASSERT(!aHeadersData); + MOZ_ASSERT(aLoadType == LOAD_LINK); + MOZ_ASSERT(!aSHEntry); + MOZ_ASSERT(aFirstParty); // Windowwatcher will assume this. + + nsCOMPtr<nsIDocShellLoadInfo> loadInfo; + rv = CreateLoadInfo(getter_AddRefs(loadInfo)); + if (NS_FAILED(rv)) { + return rv; + } + + // Set up our loadinfo so it will do the load as much like we would have + // as possible. + loadInfo->SetReferrer(aReferrer); + loadInfo->SetReferrerPolicy(aReferrerPolicy); + loadInfo->SetSendReferrer(!(aFlags & + INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER)); + loadInfo->SetOriginalURI(aOriginalURI); + loadInfo->SetLoadReplace(aLoadReplace); + loadInfo->SetTriggeringPrincipal(aTriggeringPrincipal); + loadInfo->SetInheritPrincipal( + aFlags & INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL); + // Explicit principal because we do not want any guesses as to what the + // principal to inherit is: it should be aTriggeringPrincipal. + loadInfo->SetPrincipalIsExplicit(true); + loadInfo->SetLoadType(ConvertLoadTypeToDocShellLoadInfo(LOAD_LINK)); + + rv = win->Open(NS_ConvertUTF8toUTF16(spec), + aWindowTarget, // window name + EmptyString(), // Features + loadInfo, + true, // aForceNoOpener + getter_AddRefs(newWin)); + MOZ_ASSERT(!newWin); + return rv; + } + + rv = win->OpenNoNavigate(NS_ConvertUTF8toUTF16(spec), + aWindowTarget, // window name + EmptyString(), // Features + getter_AddRefs(newWin)); + + // In some cases the Open call doesn't actually result in a new + // window being opened. We can detect these cases by examining the + // document in |newWin|, if any. + nsCOMPtr<nsPIDOMWindowOuter> piNewWin = do_QueryInterface(newWin); + if (piNewWin) { + nsCOMPtr<nsIDocument> newDoc = piNewWin->GetExtantDoc(); + if (!newDoc || newDoc->IsInitialDocument()) { + isNewWindow = true; + aFlags |= INTERNAL_LOAD_FLAGS_FIRST_LOAD; + } + } + + nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(newWin); + targetDocShell = do_QueryInterface(webNav); + } + + // + // Transfer the load to the target DocShell... Pass nullptr as the + // window target name from to prevent recursive retargeting! + // + if (NS_SUCCEEDED(rv) && targetDocShell) { + rv = targetDocShell->InternalLoad(aURI, + aOriginalURI, + aLoadReplace, + aReferrer, + aReferrerPolicy, + aTriggeringPrincipal, + principalToInherit, + aFlags, + EmptyString(), // No window target + aTypeHint, + NullString(), // No forced download + aPostData, + aHeadersData, + aLoadType, + aSHEntry, + aFirstParty, + aSrcdoc, + aSourceDocShell, + aBaseURI, + aDocShell, + aRequest); + if (rv == NS_ERROR_NO_CONTENT) { + // XXXbz except we never reach this code! + if (isNewWindow) { + // + // At this point, a new window has been created, but the + // URI did not have any data associated with it... + // + // So, the best we can do, is to tear down the new window + // that was just created! + // + if (nsCOMPtr<nsPIDOMWindowOuter> domWin = targetDocShell->GetWindow()) { + domWin->Close(); + } + } + // + // NS_ERROR_NO_CONTENT should not be returned to the + // caller... This is an internal error code indicating that + // the URI had no data associated with it - probably a + // helper-app style protocol (ie. mailto://) + // + rv = NS_OK; + } else if (isNewWindow) { + // XXX: Once new windows are created hidden, the new + // window will need to be made visible... For now, + // do nothing. + } + } + + // Else we ran out of memory, or were a popup and got blocked, + // or something. + + return rv; + } + + // + // Load is being targetted at this docshell so return an error if the + // docshell is in the process of being destroyed. + // + if (mIsBeingDestroyed) { + return NS_ERROR_FAILURE; + } + + NS_ENSURE_STATE(!HasUnloadedParent()); + + rv = CheckLoadingPermissions(); + if (NS_FAILED(rv)) { + return rv; + } + + if (mFiredUnloadEvent) { + if (IsOKToLoadURI(aURI)) { + NS_PRECONDITION(aWindowTarget.IsEmpty(), + "Shouldn't have a window target here!"); + + // If this is a replace load, make whatever load triggered + // the unload event also a replace load, so we don't + // create extra history entries. + if (LOAD_TYPE_HAS_FLAGS(aLoadType, LOAD_FLAGS_REPLACE_HISTORY)) { + mLoadType = LOAD_NORMAL_REPLACE; + } + + // Do this asynchronously + nsCOMPtr<nsIRunnable> ev = + new InternalLoadEvent(this, aURI, aOriginalURI, aLoadReplace, + aReferrer, aReferrerPolicy, + aTriggeringPrincipal, principalToInherit, + aFlags, aTypeHint, aPostData, aHeadersData, + aLoadType, aSHEntry, aFirstParty, aSrcdoc, + aSourceDocShell, aBaseURI); + return NS_DispatchToCurrentThread(ev); + } + + // Just ignore this load attempt + return NS_OK; + } + + // If a source docshell has been passed, check to see if we are sandboxed + // from it as the result of an iframe or CSP sandbox. + if (aSourceDocShell && aSourceDocShell->IsSandboxedFrom(this)) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } + + // If this docshell is owned by a frameloader, make sure to cancel + // possible frameloader initialization before loading a new page. + nsCOMPtr<nsIDocShellTreeItem> parent = GetParentDocshell(); + if (parent) { + nsCOMPtr<nsIDocument> doc = parent->GetDocument(); + if (doc) { + doc->TryCancelFrameLoaderInitialization(this); + } + } + + // Before going any further vet loads initiated by external programs. + if (aLoadType == LOAD_NORMAL_EXTERNAL) { + // Disallow external chrome: loads targetted at content windows + bool isChrome = false; + if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) && isChrome) { + NS_WARNING("blocked external chrome: url -- use '--chrome' option"); + return NS_ERROR_FAILURE; + } + + // clear the decks to prevent context bleed-through (bug 298255) + rv = CreateAboutBlankContentViewer(nullptr, nullptr); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + // reset loadType so we don't have to add lots of tests for + // LOAD_NORMAL_EXTERNAL after this point + aLoadType = LOAD_NORMAL; + } + + mAllowKeywordFixup = + (aFlags & INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) != 0; + mURIResultedInDocument = false; // reset the clock... + + if (aLoadType == LOAD_NORMAL || + aLoadType == LOAD_STOP_CONTENT || + LOAD_TYPE_HAS_FLAGS(aLoadType, LOAD_FLAGS_REPLACE_HISTORY) || + aLoadType == LOAD_HISTORY || + aLoadType == LOAD_LINK) { + nsCOMPtr<nsIURI> currentURI = mCurrentURI; + + nsAutoCString curHash, newHash; + bool curURIHasRef = false, newURIHasRef = false; + + nsresult rvURINew = aURI->GetRef(newHash); + if (NS_SUCCEEDED(rvURINew)) { + rvURINew = aURI->GetHasRef(&newURIHasRef); + } + + bool sameExceptHashes = false; + if (currentURI && NS_SUCCEEDED(rvURINew)) { + nsresult rvURIOld = currentURI->GetRef(curHash); + if (NS_SUCCEEDED(rvURIOld)) { + rvURIOld = currentURI->GetHasRef(&curURIHasRef); + } + if (NS_SUCCEEDED(rvURIOld)) { + if (NS_FAILED(currentURI->EqualsExceptRef(aURI, &sameExceptHashes))) { + sameExceptHashes = false; + } + } + } + + if (!sameExceptHashes && sURIFixup && currentURI && + NS_SUCCEEDED(rvURINew)) { + // Maybe aURI came from the exposable form of currentURI? + nsCOMPtr<nsIURI> currentExposableURI; + rv = sURIFixup->CreateExposableURI(currentURI, + getter_AddRefs(currentExposableURI)); + NS_ENSURE_SUCCESS(rv, rv); + nsresult rvURIOld = currentExposableURI->GetRef(curHash); + if (NS_SUCCEEDED(rvURIOld)) { + rvURIOld = currentExposableURI->GetHasRef(&curURIHasRef); + } + if (NS_SUCCEEDED(rvURIOld)) { + if (NS_FAILED(currentExposableURI->EqualsExceptRef(aURI, &sameExceptHashes))) { + sameExceptHashes = false; + } + } + } + + bool historyNavBetweenSameDoc = false; + if (mOSHE && aSHEntry) { + // We're doing a history load. + + mOSHE->SharesDocumentWith(aSHEntry, &historyNavBetweenSameDoc); + +#ifdef DEBUG + if (historyNavBetweenSameDoc) { + nsCOMPtr<nsIInputStream> currentPostData; + mOSHE->GetPostData(getter_AddRefs(currentPostData)); + NS_ASSERTION(currentPostData == aPostData, + "Different POST data for entries for the same page?"); + } +#endif + } + + // A short-circuited load happens when we navigate between two SHEntries + // for the same document. We do a short-circuited load under two + // circumstances. Either + // + // a) we're navigating between two different SHEntries which share a + // document, or + // + // b) we're navigating to a new shentry whose URI differs from the + // current URI only in its hash, the new hash is non-empty, and + // we're not doing a POST. + // + // The restriction tha the SHEntries in (a) must be different ensures + // that history.go(0) and the like trigger full refreshes, rather than + // short-circuited loads. + bool doShortCircuitedLoad = + (historyNavBetweenSameDoc && mOSHE != aSHEntry) || + (!aSHEntry && !aPostData && + sameExceptHashes && newURIHasRef); + + if (doShortCircuitedLoad) { + // Save the position of the scrollers. + nscoord cx = 0, cy = 0; + GetCurScrollPos(ScrollOrientation_X, &cx); + GetCurScrollPos(ScrollOrientation_Y, &cy); + + // Reset mLoadType to its original value once we exit this block, + // because this short-circuited load might have started after a + // normal, network load, and we don't want to clobber its load type. + // See bug 737307. + AutoRestore<uint32_t> loadTypeResetter(mLoadType); + + // If a non-short-circuit load (i.e., a network load) is pending, + // make this a replacement load, so that we don't add a SHEntry here + // and the network load goes into the SHEntry it expects to. + if (JustStartedNetworkLoad() && (aLoadType & LOAD_CMD_NORMAL)) { + mLoadType = LOAD_NORMAL_REPLACE; + } else { + mLoadType = aLoadType; + } + + mURIResultedInDocument = true; + + nsCOMPtr<nsISHEntry> oldLSHE = mLSHE; + + /* 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. + */ + SetHistoryEntry(&mLSHE, aSHEntry); + + // Set the doc's URI according to the new history entry's URI. + nsCOMPtr<nsIDocument> doc = GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + doc->SetDocumentURI(aURI); + + /* This is a anchor traversal with in the same page. + * call OnNewURI() so that, this traversal will be + * recorded in session and global history. + */ + nsCOMPtr<nsIPrincipal> triggeringPrincipal, principalToInherit; + if (mOSHE) { + mOSHE->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal)); + mOSHE->GetPrincipalToInherit(getter_AddRefs(principalToInherit)); + } + // Pass true for aCloneSHChildren, since we're not + // changing documents here, so all of our subframes are + // still relevant to the new session history entry. + // + // It also makes OnNewURI(...) set LOCATION_CHANGE_SAME_DOCUMENT + // flag on firing onLocationChange(...). + // Anyway, aCloneSHChildren param is simply reflecting + // doShortCircuitedLoad in this scope. + OnNewURI(aURI, nullptr, triggeringPrincipal, principalToInherit, + mLoadType, true, true, true); + + nsCOMPtr<nsIInputStream> postData; + nsCOMPtr<nsISupports> cacheKey; + + bool scrollRestorationIsManual = false; + if (mOSHE) { + /* save current position of scroller(s) (bug 59774) */ + mOSHE->SetScrollPosition(cx, cy); + mOSHE->GetScrollRestorationIsManual(&scrollRestorationIsManual); + // Get the postdata and page ident from the current page, if + // the new load is being done via normal means. Note that + // "normal means" can be checked for just by checking for + // LOAD_CMD_NORMAL, given the loadType and allowScroll check + // above -- it filters out some LOAD_CMD_NORMAL cases that we + // wouldn't want here. + if (aLoadType & LOAD_CMD_NORMAL) { + mOSHE->GetPostData(getter_AddRefs(postData)); + mOSHE->GetCacheKey(getter_AddRefs(cacheKey)); + + // Link our new SHEntry to the old SHEntry's back/forward + // cache data, since the two SHEntries correspond to the + // same document. + if (mLSHE) { + if (!aSHEntry) { + // If we're not doing a history load, scroll restoration + // should be inherited from the previous session history entry. + mLSHE->SetScrollRestorationIsManual(scrollRestorationIsManual); + } + mLSHE->AdoptBFCacheEntry(mOSHE); + } + } + } + + // If we're doing a history load, use its scroll restoration state. + if (aSHEntry) { + aSHEntry->GetScrollRestorationIsManual(&scrollRestorationIsManual); + } + + /* Assign mOSHE to mLSHE. This will either be a new entry created + * by OnNewURI() for normal loads or aSHEntry for history loads. + */ + if (mLSHE) { + SetHistoryEntry(&mOSHE, mLSHE); + // Save the postData obtained from the previous page + // in to the session history entry created for the + // anchor page, so that any history load of the anchor + // page will restore the appropriate postData. + if (postData) { + mOSHE->SetPostData(postData); + } + + // Make sure we won't just repost without hitting the + // cache first + if (cacheKey) { + mOSHE->SetCacheKey(cacheKey); + } + } + + /* Restore the original LSHE if we were loading something + * while short-circuited load was initiated. + */ + SetHistoryEntry(&mLSHE, oldLSHE); + /* Set the title for the SH entry for this target url. so that + * SH menus in go/back/forward buttons won't be empty for this. + */ + if (mSessionHistory) { + int32_t index = -1; + mSessionHistory->GetIndex(&index); + nsCOMPtr<nsISHEntry> shEntry; + mSessionHistory->GetEntryAtIndex(index, false, getter_AddRefs(shEntry)); + NS_ENSURE_TRUE(shEntry, NS_ERROR_FAILURE); + shEntry->SetTitle(mTitle); + } + + /* Set the title for the Global History entry for this anchor url. + */ + if (mUseGlobalHistory && !UsePrivateBrowsing()) { + nsCOMPtr<IHistory> history = services::GetHistoryService(); + if (history) { + history->SetURITitle(aURI, mTitle); + } else if (mGlobalHistory) { + mGlobalHistory->SetPageTitle(aURI, mTitle); + } + } + + SetDocCurrentStateObj(mOSHE); + + // Inform the favicon service that the favicon for oldURI also + // applies to aURI. + CopyFavicon(currentURI, aURI, doc->NodePrincipal(), UsePrivateBrowsing()); + + RefPtr<nsGlobalWindow> scriptGlobal = mScriptGlobal; + RefPtr<nsGlobalWindow> win = scriptGlobal ? + scriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + + // ScrollToAnchor doesn't necessarily cause us to scroll the window; + // the function decides whether a scroll is appropriate based on the + // arguments it receives. But even if we don't end up scrolling, + // ScrollToAnchor performs other important tasks, such as informing + // the presShell that we have a new hash. See bug 680257. + rv = ScrollToAnchor(curURIHasRef, newURIHasRef, newHash, aLoadType); + NS_ENSURE_SUCCESS(rv, rv); + + /* restore previous position of scroller(s), if we're moving + * back in history (bug 59774) + */ + nscoord bx = 0; + nscoord by = 0; + bool needsScrollPosUpdate = false; + if (mOSHE && (aLoadType == LOAD_HISTORY || + aLoadType == LOAD_RELOAD_NORMAL) && + !scrollRestorationIsManual) { + needsScrollPosUpdate = true; + mOSHE->GetScrollPosition(&bx, &by); + } + + // Dispatch the popstate and hashchange events, as appropriate. + // + // The event dispatch below can cause us to re-enter script and + // destroy the docshell, nulling out mScriptGlobal. Hold a stack + // reference to avoid null derefs. See bug 914521. + if (win) { + // Fire a hashchange event URIs differ, and only in their hashes. + bool doHashchange = sameExceptHashes && + (curURIHasRef != newURIHasRef || !curHash.Equals(newHash)); + + if (historyNavBetweenSameDoc || doHashchange) { + win->DispatchSyncPopState(); + } + + if (needsScrollPosUpdate && win->AsInner()->HasActiveDocument()) { + SetCurScrollPosEx(bx, by); + } + + if (doHashchange) { + // Note that currentURI hasn't changed because it's on the + // stack, so we can just use it directly as the old URI. + win->DispatchAsyncHashchange(currentURI, aURI); + } + } + + return NS_OK; + } + } + + // mContentViewer->PermitUnload can destroy |this| docShell, which + // causes the next call of CanSavePresentation to crash. + // Hold onto |this| until we return, to prevent a crash from happening. + // (bug#331040) + nsCOMPtr<nsIDocShell> kungFuDeathGrip(this); + + // Don't init timing for javascript:, since it generally doesn't + // actually start a load or anything. If it does, we'll init + // timing then, from OnStateChange. + + // XXXbz mTiming should know what channel it's for, so we don't + // need this hackery. Note that this is still broken in cases + // when we're loading something that's not javascript: and the + // beforeunload handler denies the load. That will screw up + // timing for the next load! + if (!isJavaScript) { + MaybeInitTiming(); + } + bool timeBeforeUnload = aFileName.IsVoid(); + if (mTiming && timeBeforeUnload) { + mTiming->NotifyBeforeUnload(); + } + // Check if the page doesn't want to be unloaded. The javascript: + // protocol handler deals with this for javascript: URLs. + if (!isJavaScript && aFileName.IsVoid() && mContentViewer) { + bool okToUnload; + rv = mContentViewer->PermitUnload(&okToUnload); + + if (NS_SUCCEEDED(rv) && !okToUnload) { + // The user chose not to unload the page, interrupt the + // load. + return NS_OK; + } + } + + if (mTiming && timeBeforeUnload) { + mTiming->NotifyUnloadAccepted(mCurrentURI); + } + + // Check if the webbrowser chrome wants the load to proceed; this can be + // used to cancel attempts to load URIs in the wrong process. + nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner); + if (browserChrome3) { + // In case this is a remote newtab load, set aURI to aOriginalURI (newtab). + // This ensures that the verifySignedContent flag is set on loadInfo in + // DoURILoad. + nsIURI* uriForShouldLoadCheck = aURI; + if (IsAboutNewtab(aOriginalURI)) { + uriForShouldLoadCheck = aOriginalURI; + } + bool shouldLoad; + rv = browserChrome3->ShouldLoadURI(this, uriForShouldLoadCheck, aReferrer, + &shouldLoad); + if (NS_SUCCEEDED(rv) && !shouldLoad) { + return NS_OK; + } + } + + // Whenever a top-level browsing context is navigated, the user agent MUST + // lock the orientation of the document to the document's default + // orientation. We don't explicitly check for a top-level browsing context + // here because orientation is only set on top-level browsing contexts. + // We make an exception for apps because they currently rely on + // orientation locks persisting across browsing contexts. + if (OrientationLock() != eScreenOrientation_None && !GetIsApp()) { +#ifdef DEBUG + nsCOMPtr<nsIDocShellTreeItem> parent; + GetSameTypeParent(getter_AddRefs(parent)); + MOZ_ASSERT(!parent); +#endif + SetOrientationLock(eScreenOrientation_None); + if (mIsActive) { + ScreenOrientation::UpdateActiveOrientationLock(eScreenOrientation_None); + } + } + + // Check for saving the presentation here, before calling Stop(). + // This is necessary so that we can catch any pending requests. + // Since the new request has not been created yet, we pass null for the + // new request parameter. + // Also pass nullptr for the document, since it doesn't affect the return + // value for our purposes here. + bool savePresentation = CanSavePresentation(aLoadType, nullptr, nullptr); + + // Don't stop current network activity for javascript: URL's since + // they might not result in any data, and thus nothing should be + // stopped in those cases. In the case where they do result in + // data, the javascript: URL channel takes care of stopping + // current network activity. + if (!isJavaScript && aFileName.IsVoid()) { + // Stop any current network activity. + // Also stop content if this is a zombie doc. otherwise + // the onload will be delayed by other loads initiated in the + // background by the first document that + // didn't fully load before the next load was initiated. + // If not a zombie, don't stop content until data + // starts arriving from the new URI... + + nsCOMPtr<nsIContentViewer> zombieViewer; + if (mContentViewer) { + mContentViewer->GetPreviousViewer(getter_AddRefs(zombieViewer)); + } + + if (zombieViewer || + LOAD_TYPE_HAS_FLAGS(aLoadType, LOAD_FLAGS_STOP_CONTENT)) { + rv = Stop(nsIWebNavigation::STOP_ALL); + } else { + rv = Stop(nsIWebNavigation::STOP_NETWORK); + } + + if (NS_FAILED(rv)) { + return rv; + } + } + + mLoadType = aLoadType; + + // mLSHE should be assigned to aSHEntry, only after Stop() has + // been called. But when loading an error page, do not clear the + // mLSHE for the real page. + if (mLoadType != LOAD_ERROR_PAGE) { + SetHistoryEntry(&mLSHE, aSHEntry); + } + + mSavingOldViewer = savePresentation; + + // If we have a saved content viewer in history, restore and show it now. + if (aSHEntry && (mLoadType & LOAD_CMD_HISTORY)) { + // Make sure our history ID points to the same ID as + // SHEntry's docshell ID. + aSHEntry->GetDocshellID(&mHistoryID); + + // It's possible that the previous viewer of mContentViewer is the + // viewer that will end up in aSHEntry when it gets closed. If that's + // the case, we need to go ahead and force it into its shentry so we + // can restore it. + if (mContentViewer) { + nsCOMPtr<nsIContentViewer> prevViewer; + mContentViewer->GetPreviousViewer(getter_AddRefs(prevViewer)); + if (prevViewer) { +#ifdef DEBUG + nsCOMPtr<nsIContentViewer> prevPrevViewer; + prevViewer->GetPreviousViewer(getter_AddRefs(prevPrevViewer)); + NS_ASSERTION(!prevPrevViewer, "Should never have viewer chain here"); +#endif + nsCOMPtr<nsISHEntry> viewerEntry; + prevViewer->GetHistoryEntry(getter_AddRefs(viewerEntry)); + if (viewerEntry == aSHEntry) { + // Make sure this viewer ends up in the right place + mContentViewer->SetPreviousViewer(nullptr); + prevViewer->Destroy(); + } + } + } + nsCOMPtr<nsISHEntry> oldEntry = mOSHE; + bool restoring; + rv = RestorePresentation(aSHEntry, &restoring); + if (restoring) { + return rv; + } + + // We failed to restore the presentation, so clean up. + // Both the old and new history entries could potentially be in + // an inconsistent state. + if (NS_FAILED(rv)) { + if (oldEntry) { + oldEntry->SyncPresentationState(); + } + + aSHEntry->SyncPresentationState(); + } + } + + nsAutoString srcdoc; + if (aFlags & INTERNAL_LOAD_FLAGS_IS_SRCDOC) { + srcdoc = aSrcdoc; + } else { + srcdoc = NullString(); + } + + net::PredictorLearn(aURI, nullptr, + nsINetworkPredictor::LEARN_LOAD_TOPLEVEL, this); + net::PredictorPredict(aURI, nullptr, + nsINetworkPredictor::PREDICT_LOAD, this, nullptr); + + nsCOMPtr<nsIRequest> req; + rv = DoURILoad(aURI, aOriginalURI, aLoadReplace, aReferrer, + !(aFlags & INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER), + aReferrerPolicy, + aTriggeringPrincipal, principalToInherit, aTypeHint, + aFileName, aPostData, aHeadersData, + aFirstParty, aDocShell, getter_AddRefs(req), + (aFlags & INTERNAL_LOAD_FLAGS_FIRST_LOAD) != 0, + (aFlags & INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER) != 0, + (aFlags & INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES) != 0, + srcdoc, aBaseURI, contentType); + if (req && aRequest) { + NS_ADDREF(*aRequest = req); + } + + if (NS_FAILED(rv)) { + nsCOMPtr<nsIChannel> chan(do_QueryInterface(req)); + if (DisplayLoadError(rv, aURI, nullptr, chan) && + (aFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) { + return NS_ERROR_LOAD_SHOWED_ERRORPAGE; + } + } + + return rv; +} + +nsIPrincipal* +nsDocShell::GetInheritedPrincipal(bool aConsiderCurrentDocument) +{ + nsCOMPtr<nsIDocument> document; + bool inheritedFromCurrent = false; + + if (aConsiderCurrentDocument && mContentViewer) { + document = mContentViewer->GetDocument(); + inheritedFromCurrent = true; + } + + if (!document) { + nsCOMPtr<nsIDocShellTreeItem> parentItem; + GetSameTypeParent(getter_AddRefs(parentItem)); + if (parentItem) { + document = parentItem->GetDocument(); + } + } + + if (!document) { + if (!aConsiderCurrentDocument) { + return nullptr; + } + + // Make sure we end up with _something_ as the principal no matter + // what.If this fails, we'll just get a null docViewer and bail. + EnsureContentViewer(); + if (!mContentViewer) { + return nullptr; + } + document = mContentViewer->GetDocument(); + } + + //-- Get the document's principal + if (document) { + nsIPrincipal* docPrincipal = document->NodePrincipal(); + + // Don't allow loads in typeContent docShells to inherit the system + // principal from existing documents. + if (inheritedFromCurrent && + mItemType == typeContent && + nsContentUtils::IsSystemPrincipal(docPrincipal)) { + return nullptr; + } + + return docPrincipal; + } + + return nullptr; +} + +nsresult +nsDocShell::DoURILoad(nsIURI* aURI, + nsIURI* aOriginalURI, + bool aLoadReplace, + nsIURI* aReferrerURI, + bool aSendReferrer, + uint32_t aReferrerPolicy, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + const char* aTypeHint, + const nsAString& aFileName, + nsIInputStream* aPostData, + nsIInputStream* aHeadersData, + bool aFirstParty, + nsIDocShell** aDocShell, + nsIRequest** aRequest, + bool aIsNewWindowTarget, + bool aBypassClassifier, + bool aForceAllowCookies, + const nsAString& aSrcdoc, + nsIURI* aBaseURI, + nsContentPolicyType aContentPolicyType) +{ + // Double-check that we're still around to load this URI. + if (mIsBeingDestroyed) { + // Return NS_OK despite not doing anything to avoid throwing exceptions from + // nsLocation::SetHref if the unload handler of the existing page tears us + // down. + return NS_OK; + } + + nsresult rv; + nsCOMPtr<nsIURILoader> uriLoader = do_GetService(NS_URI_LOADER_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + if (IsFrame()) { + + MOZ_ASSERT(aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IFRAME || + aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_FRAME, + "DoURILoad thinks this is a frame and InternalLoad does not"); + + // Only allow view-source scheme in top-level docshells. view-source is + // the only scheme to which this applies at the moment due to potential + // timing attacks to read data from cross-origin iframes. If this widens + // we should add a protocol flag for whether the scheme is allowed in + // frames and use something like nsNetUtil::NS_URIChainHasFlags. + nsCOMPtr<nsIURI> tempURI = aURI; + nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(tempURI); + while (nestedURI) { + // view-source should always be an nsINestedURI, loop and check the + // scheme on this and all inner URIs that are also nested URIs. + bool isViewSource = false; + rv = tempURI->SchemeIs("view-source", &isViewSource); + if (NS_FAILED(rv) || isViewSource) { + return NS_ERROR_UNKNOWN_PROTOCOL; + } + nestedURI->GetInnerURI(getter_AddRefs(tempURI)); + nestedURI = do_QueryInterface(tempURI); + } + } else { + MOZ_ASSERT(aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT, + "DoURILoad thinks this is a document and InternalLoad does not"); + } + + // open a channel for the url + nsCOMPtr<nsIChannel> channel; + + bool isSrcdoc = !aSrcdoc.IsVoid(); + + // There are two cases we care about: + // * Top-level load: In this case, loadingNode is null, but loadingWindow + // is our mScriptGlobal. We pass null for loadingPrincipal in this case. + // * Subframe load: loadingWindow is null, but loadingNode is the frame + // element for the load. loadingPrincipal is the NodePrincipal of the frame + // element. + nsCOMPtr<nsINode> loadingNode; + nsCOMPtr<nsPIDOMWindowOuter> loadingWindow; + nsCOMPtr<nsIPrincipal> loadingPrincipal; + + if (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) { + loadingNode = nullptr; + loadingPrincipal = nullptr; + loadingWindow = mScriptGlobal->AsOuter(); + } else { + loadingWindow = nullptr; + loadingNode = mScriptGlobal->AsOuter()->GetFrameElementInternal(); + if (loadingNode) { + // If we have a loading node, then use that as our loadingPrincipal. + loadingPrincipal = loadingNode->NodePrincipal(); + } else { + // If this isn't a top-level load and mScriptGlobal's frame element is + // null, then the element got removed from the DOM while we were trying + // to load this resource. This docshell is scheduled for destruction + // already, so bail out here. + return NS_OK; + } + } + + // Getting the right triggeringPrincipal needs to be updated and is only + // ready for use once bug 1182569 landed. Until then, we cannot rely on + // the triggeringPrincipal for TYPE_DOCUMENT loads. + MOZ_ASSERT(aTriggeringPrincipal, "Need a valid triggeringPrincipal"); + + bool isSandBoxed = mSandboxFlags & SANDBOXED_ORIGIN; + // only inherit if we have a aPrincipalToInherit + bool inherit = false; + + if (aPrincipalToInherit) { + inherit = nsContentUtils::ChannelShouldInheritPrincipal( + aPrincipalToInherit, + aURI, + true, // aInheritForAboutBlank + isSrcdoc); + } + + nsLoadFlags loadFlags = mDefaultLoadFlags; + nsSecurityFlags securityFlags = nsILoadInfo::SEC_NORMAL; + + if (aFirstParty) { + // tag first party URL loads + loadFlags |= nsIChannel::LOAD_INITIAL_DOCUMENT_URI; + } + + if (mLoadType == LOAD_ERROR_PAGE) { + // Error pages are LOAD_BACKGROUND + loadFlags |= nsIChannel::LOAD_BACKGROUND; + securityFlags |= nsILoadInfo::SEC_LOAD_ERROR_PAGE; + } + + if (inherit) { + securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + } + if (isSandBoxed) { + securityFlags |= nsILoadInfo::SEC_SANDBOXED; + } + + nsCOMPtr<nsILoadInfo> loadInfo = + (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) ? + new LoadInfo(loadingWindow, aTriggeringPrincipal, + securityFlags) : + new LoadInfo(loadingPrincipal, aTriggeringPrincipal, loadingNode, + securityFlags, aContentPolicyType); + + if (aPrincipalToInherit) { + loadInfo->SetPrincipalToInherit(aPrincipalToInherit); + } + + // We have to do this in case our OriginAttributes are different from the + // OriginAttributes of the parent document. Or in case there isn't a + // parent document. + NeckoOriginAttributes neckoAttrs; + bool isTopLevelDoc = aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT && + mItemType == typeContent && + !GetIsMozBrowserOrApp(); + neckoAttrs.InheritFromDocShellToNecko(GetOriginAttributes(), isTopLevelDoc, aURI); + rv = loadInfo->SetOriginAttributes(neckoAttrs); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!isSrcdoc) { + rv = NS_NewChannelInternal(getter_AddRefs(channel), + aURI, + loadInfo, + nullptr, // loadGroup + static_cast<nsIInterfaceRequestor*>(this), + loadFlags); + + if (NS_FAILED(rv)) { + if (rv == NS_ERROR_UNKNOWN_PROTOCOL) { + // This is a uri with a protocol scheme we don't know how + // to handle. Embedders might still be interested in + // handling the load, though, so we fire a notification + // before throwing the load away. + bool abort = false; + nsresult rv2 = mContentListener->OnStartURIOpen(aURI, &abort); + if (NS_SUCCEEDED(rv2) && abort) { + // Hey, they're handling the load for us! How convenient! + return NS_OK; + } + } + return rv; + } + + if (aBaseURI) { + nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(channel); + if (vsc) { + vsc->SetBaseURI(aBaseURI); + } + } + } else { + nsAutoCString scheme; + rv = aURI->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); + bool isViewSource; + aURI->SchemeIs("view-source", &isViewSource); + + if (isViewSource) { + nsViewSourceHandler* vsh = nsViewSourceHandler::GetInstance(); + NS_ENSURE_TRUE(vsh, NS_ERROR_FAILURE); + + rv = vsh->NewSrcdocChannel(aURI, aBaseURI, aSrcdoc, + loadInfo, getter_AddRefs(channel)); + } else { + rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel), + aURI, + aSrcdoc, + NS_LITERAL_CSTRING("text/html"), + loadInfo, + true); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIInputStreamChannel> isc = do_QueryInterface(channel); + MOZ_ASSERT(isc); + isc->SetBaseURI(aBaseURI); + } + } + + nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel = + do_QueryInterface(channel); + if (appCacheChannel) { + // Any document load should not inherit application cache. + appCacheChannel->SetInheritApplicationCache(false); + + // Loads with the correct permissions should check for a matching + // application cache. + if (GeckoProcessType_Default != XRE_GetProcessType()) { + // Permission will be checked in the parent process + appCacheChannel->SetChooseApplicationCache(true); + } else { + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + + if (secMan) { + nsCOMPtr<nsIPrincipal> principal; + secMan->GetDocShellCodebasePrincipal(aURI, this, + getter_AddRefs(principal)); + appCacheChannel->SetChooseApplicationCache( + NS_ShouldCheckAppCache(principal, UsePrivateBrowsing())); + } + } + } + + // Make sure to give the caller a channel if we managed to create one + // This is important for correct error page/session history interaction + if (aRequest) { + NS_ADDREF(*aRequest = channel); + } + + if (aOriginalURI) { + channel->SetOriginalURI(aOriginalURI); + if (aLoadReplace) { + uint32_t loadFlags; + channel->GetLoadFlags(&loadFlags); + NS_ENSURE_SUCCESS(rv, rv); + channel->SetLoadFlags(loadFlags | nsIChannel::LOAD_REPLACE); + } + } else { + channel->SetOriginalURI(aURI); + } + + if (aTypeHint && *aTypeHint) { + channel->SetContentType(nsDependentCString(aTypeHint)); + mContentTypeHint = aTypeHint; + } else { + mContentTypeHint.Truncate(); + } + + if (!aFileName.IsVoid()) { + rv = channel->SetContentDisposition(nsIChannel::DISPOSITION_ATTACHMENT); + NS_ENSURE_SUCCESS(rv, rv); + if (!aFileName.IsEmpty()) { + rv = channel->SetContentDispositionFilename(aFileName); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + if (mLoadType == LOAD_NORMAL_ALLOW_MIXED_CONTENT || + mLoadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) { + rv = SetMixedContentChannel(channel); + NS_ENSURE_SUCCESS(rv, rv); + } else if (mMixedContentChannel) { + /* + * If the user "Disables Protection on This Page", we call + * SetMixedContentChannel for the first time, otherwise + * mMixedContentChannel is still null. + * Later, if the new channel passes a same orign check, we remember the + * users decision by calling SetMixedContentChannel using the new channel. + * This way, the user does not have to click the disable protection button + * over and over for browsing the same site. + */ + rv = nsContentUtils::CheckSameOrigin(mMixedContentChannel, channel); + if (NS_FAILED(rv) || NS_FAILED(SetMixedContentChannel(channel))) { + SetMixedContentChannel(nullptr); + } + } + + // hack + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); + nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal( + do_QueryInterface(channel)); + if (httpChannelInternal) { + if (aForceAllowCookies) { + httpChannelInternal->SetThirdPartyFlags( + nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW); + } + if (aFirstParty) { + httpChannelInternal->SetDocumentURI(aURI); + } else { + httpChannelInternal->SetDocumentURI(aReferrerURI); + } + httpChannelInternal->SetRedirectMode( + nsIHttpChannelInternal::REDIRECT_MODE_MANUAL); + } + + nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(channel)); + if (props) { + // save true referrer for those who need it (e.g. xpinstall whitelisting) + // Currently only http and ftp channels support this. + props->SetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"), + aReferrerURI); + } + + nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(channel)); + /* Get the cache Key from SH */ + nsCOMPtr<nsISupports> cacheKey; + if (cacheChannel) { + if (mLSHE) { + mLSHE->GetCacheKey(getter_AddRefs(cacheKey)); + } else if (mOSHE) { // for reload cases + mOSHE->GetCacheKey(getter_AddRefs(cacheKey)); + } + } + + // figure out if we need to set the post data stream on the channel... + if (aPostData) { + nsCOMPtr<nsIFormPOSTActionChannel> postChannel(do_QueryInterface(channel)); + if (postChannel) { + // XXX it's a bit of a hack to rewind the postdata stream here but + // it has to be done in case the post data is being reused multiple + // times. + nsCOMPtr<nsISeekableStream> postDataSeekable = + do_QueryInterface(aPostData); + if (postDataSeekable) { + rv = postDataSeekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); + NS_ENSURE_SUCCESS(rv, rv); + } + + // we really need to have a content type associated with this stream!! + postChannel->SetUploadStream(aPostData, EmptyCString(), -1); + } + + /* If there is a valid postdata *and* it is a History Load, + * set up the cache key on the channel, to retrieve the + * data *only* from the cache. If it is a normal reload, the + * cache is free to go to the server for updated postdata. + */ + if (cacheChannel && cacheKey) { + if (mLoadType == LOAD_HISTORY || + mLoadType == LOAD_RELOAD_CHARSET_CHANGE) { + cacheChannel->SetCacheKey(cacheKey); + uint32_t loadFlags; + if (NS_SUCCEEDED(channel->GetLoadFlags(&loadFlags))) { + channel->SetLoadFlags( + loadFlags | nsICachingChannel::LOAD_ONLY_FROM_CACHE); + } + } else if (mLoadType == LOAD_RELOAD_NORMAL) { + cacheChannel->SetCacheKey(cacheKey); + } + } + } else { + /* If there is no postdata, set the cache key on the channel, and + * do not set the LOAD_ONLY_FROM_CACHE flag, so that the channel + * will be free to get it from net if it is not found in cache. + * New cache may use it creatively on CGI pages with GET + * method and even on those that say "no-cache" + */ + if (mLoadType == LOAD_HISTORY || + mLoadType == LOAD_RELOAD_NORMAL || + mLoadType == LOAD_RELOAD_CHARSET_CHANGE) { + if (cacheChannel && cacheKey) { + cacheChannel->SetCacheKey(cacheKey); + } + } + } + + if (httpChannel) { + if (aHeadersData) { + rv = AddHeadersToChannel(aHeadersData, httpChannel); + } + // Set the referrer explicitly + if (aReferrerURI && aSendReferrer) { + // Referrer is currenly only set for link clicks here. + httpChannel->SetReferrerWithPolicy(aReferrerURI, aReferrerPolicy); + } + // set Content-Signature enforcing bit if aOriginalURI == about:newtab + if (aOriginalURI && httpChannel) { + if (IsAboutNewtab(aOriginalURI)) { + nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->GetLoadInfo(); + if (loadInfo) { + loadInfo->SetVerifySignedContent(true); + } + } + } + } + + nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(channel); + if (scriptChannel) { + // Allow execution against our context if the principals match + scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL); + } + + if (aIsNewWindowTarget) { + nsCOMPtr<nsIWritablePropertyBag2> props = do_QueryInterface(channel); + if (props) { + props->SetPropertyAsBool(NS_LITERAL_STRING("docshell.newWindowTarget"), + true); + } + } + + nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(channel)); + if (timedChannel) { + timedChannel->SetTimingEnabled(true); + + nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow(); + if (IsFrame() && win) { + nsCOMPtr<Element> frameElement = win->GetFrameElementInternal(); + if (frameElement) { + timedChannel->SetInitiatorType(frameElement->LocalName()); + } + } + } + + rv = DoChannelLoad(channel, uriLoader, aBypassClassifier); + + // + // If the channel load failed, we failed and nsIWebProgress just ain't + // gonna happen. + // + if (NS_SUCCEEDED(rv)) { + if (aDocShell) { + *aDocShell = this; + NS_ADDREF(*aDocShell); + } + } + + return rv; +} + +static nsresult +AppendSegmentToString(nsIInputStream* aIn, + void* aClosure, + const char* aFromRawSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t* aWriteCount) +{ + // aFromSegment now contains aCount bytes of data. + + nsAutoCString* buf = static_cast<nsAutoCString*>(aClosure); + buf->Append(aFromRawSegment, aCount); + + // Indicate that we have consumed all of aFromSegment + *aWriteCount = aCount; + return NS_OK; +} + +nsresult +nsDocShell::AddHeadersToChannel(nsIInputStream* aHeadersData, + nsIChannel* aGenericChannel) +{ + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aGenericChannel); + NS_ENSURE_STATE(httpChannel); + + uint32_t numRead; + nsAutoCString headersString; + nsresult rv = aHeadersData->ReadSegments(AppendSegmentToString, + &headersString, + UINT32_MAX, + &numRead); + NS_ENSURE_SUCCESS(rv, rv); + + // used during the manipulation of the String from the InputStream + nsAutoCString headerName; + nsAutoCString headerValue; + int32_t crlf; + int32_t colon; + + // + // Iterate over the headersString: for each "\r\n" delimited chunk, + // add the value as a header to the nsIHttpChannel + // + + static const char kWhitespace[] = "\b\t\r\n "; + while (true) { + crlf = headersString.Find("\r\n"); + if (crlf == kNotFound) { + return NS_OK; + } + + const nsCSubstring& oneHeader = StringHead(headersString, crlf); + + colon = oneHeader.FindChar(':'); + if (colon == kNotFound) { + return NS_ERROR_UNEXPECTED; + } + + headerName = StringHead(oneHeader, colon); + headerValue = Substring(oneHeader, colon + 1); + + headerName.Trim(kWhitespace); + headerValue.Trim(kWhitespace); + + headersString.Cut(0, crlf + 2); + + // + // FINALLY: we can set the header! + // + + rv = httpChannel->SetRequestHeader(headerName, headerValue, true); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_NOTREACHED("oops"); + return NS_ERROR_UNEXPECTED; +} + +nsresult +nsDocShell::DoChannelLoad(nsIChannel* aChannel, + nsIURILoader* aURILoader, + bool aBypassClassifier) +{ + nsresult rv; + // Mark the channel as being a document URI and allow content sniffing... + nsLoadFlags loadFlags = 0; + (void)aChannel->GetLoadFlags(&loadFlags); + loadFlags |= nsIChannel::LOAD_DOCUMENT_URI | + nsIChannel::LOAD_CALL_CONTENT_SNIFFERS; + + // Load attributes depend on load type... + switch (mLoadType) { + case LOAD_HISTORY: { + // Only send VALIDATE_NEVER if mLSHE's URI was never changed via + // push/replaceState (bug 669671). + bool uriModified = false; + if (mLSHE) { + mLSHE->GetURIWasModified(&uriModified); + } + + if (!uriModified) { + loadFlags |= nsIRequest::VALIDATE_NEVER; + } + break; + } + + case LOAD_RELOAD_CHARSET_CHANGE: { + // Use SetAllowStaleCacheContent (not LOAD_FROM_CACHE flag) since we only want + // to force cache load for this channel, not the whole loadGroup. + nsCOMPtr<nsICacheInfoChannel> cachingChannel = do_QueryInterface(aChannel); + if (cachingChannel) { + cachingChannel->SetAllowStaleCacheContent(true); + } + break; + } + + case LOAD_RELOAD_NORMAL: + case LOAD_REFRESH: + loadFlags |= nsIRequest::VALIDATE_ALWAYS; + break; + + case LOAD_NORMAL_BYPASS_CACHE: + case LOAD_NORMAL_BYPASS_PROXY: + case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE: + case LOAD_NORMAL_ALLOW_MIXED_CONTENT: + case LOAD_RELOAD_BYPASS_CACHE: + case LOAD_RELOAD_BYPASS_PROXY: + case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: + case LOAD_RELOAD_ALLOW_MIXED_CONTENT: + case LOAD_REPLACE_BYPASS_CACHE: + loadFlags |= nsIRequest::LOAD_BYPASS_CACHE | + nsIRequest::LOAD_FRESH_CONNECTION; + break; + + case LOAD_NORMAL: + case LOAD_LINK: + // Set cache checking flags + switch (Preferences::GetInt("browser.cache.check_doc_frequency", -1)) { + case 0: + loadFlags |= nsIRequest::VALIDATE_ONCE_PER_SESSION; + break; + case 1: + loadFlags |= nsIRequest::VALIDATE_ALWAYS; + break; + case 2: + loadFlags |= nsIRequest::VALIDATE_NEVER; + break; + } + break; + } + + if (!aBypassClassifier) { + loadFlags |= nsIChannel::LOAD_CLASSIFY_URI; + } + + // If the user pressed shift-reload, then do not allow ServiceWorker + // interception to occur. See step 12.1 of the SW HandleFetch algorithm. + if (IsForceReloadType(mLoadType)) { + loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER; + } + + (void)aChannel->SetLoadFlags(loadFlags); + + uint32_t openFlags = 0; + if (mLoadType == LOAD_LINK) { + openFlags |= nsIURILoader::IS_CONTENT_PREFERRED; + } + if (!mAllowContentRetargeting) { + openFlags |= nsIURILoader::DONT_RETARGET; + } + rv = aURILoader->OpenURI(aChannel, openFlags, this); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef, + nsACString& aNewHash, uint32_t aLoadType) +{ + if (!mCurrentURI) { + return NS_OK; + } + + nsCOMPtr<nsIPresShell> shell = GetPresShell(); + if (!shell) { + // If we failed to get the shell, or if there is no shell, + // nothing left to do here. + return NS_OK; + } + + nsIScrollableFrame* rootScroll = shell->GetRootScrollFrameAsScrollable(); + if (rootScroll) { + rootScroll->ClearDidHistoryRestore(); + } + + // If we have no new anchor, we do not want to scroll, unless there is a + // current anchor and we are doing a history load. So return if we have no + // new anchor, and there is no current anchor or the load is not a history + // load. + if ((!aCurHasRef || aLoadType != LOAD_HISTORY) && !aNewHasRef) { + return NS_OK; + } + + // Both the new and current URIs refer to the same page. We can now + // browse to the hash stored in the new URI. + + if (!aNewHash.IsEmpty()) { + // anchor is there, but if it's a load from history, + // we don't have any anchor jumping to do + bool scroll = aLoadType != LOAD_HISTORY && + aLoadType != LOAD_RELOAD_NORMAL; + + char* str = ToNewCString(aNewHash); + if (!str) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // nsUnescape modifies the string that is passed into it. + nsUnescape(str); + + // We assume that the bytes are in UTF-8, as it says in the + // spec: + // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1 + + // We try the UTF-8 string first, and then try the document's + // charset (see below). If the string is not UTF-8, + // conversion will fail and give us an empty Unicode string. + // In that case, we should just fall through to using the + // page's charset. + nsresult rv = NS_ERROR_FAILURE; + NS_ConvertUTF8toUTF16 uStr(str); + if (!uStr.IsEmpty()) { + rv = shell->GoToAnchor(NS_ConvertUTF8toUTF16(str), scroll, + nsIPresShell::SCROLL_SMOOTH_AUTO); + } + free(str); + + // Above will fail if the anchor name is not UTF-8. Need to + // convert from document charset to unicode. + if (NS_FAILED(rv)) { + // Get a document charset + NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE); + nsIDocument* doc = mContentViewer->GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + const nsACString& aCharset = doc->GetDocumentCharacterSet(); + + nsCOMPtr<nsITextToSubURI> textToSubURI = + do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Unescape and convert to unicode + nsXPIDLString uStr; + + rv = textToSubURI->UnEscapeAndConvert(PromiseFlatCString(aCharset).get(), + PromiseFlatCString(aNewHash).get(), + getter_Copies(uStr)); + NS_ENSURE_SUCCESS(rv, rv); + + // Ignore return value of GoToAnchor, since it will return an error + // if there is no such anchor in the document, which is actually a + // success condition for us (we want to update the session history + // with the new URI no matter whether we actually scrolled + // somewhere). + // + // When aNewHash contains "%00", unescaped string may be empty. + // And GoToAnchor asserts if we ask it to scroll to an empty ref. + shell->GoToAnchor(uStr, scroll && !uStr.IsEmpty(), + nsIPresShell::SCROLL_SMOOTH_AUTO); + } + } else { + // Tell the shell it's at an anchor, without scrolling. + shell->GoToAnchor(EmptyString(), false); + + // An empty anchor was found, but if it's a load from history, + // we don't have to jump to the top of the page. Scrollbar + // position will be restored by the caller, based on positions + // stored in session history. + if (aLoadType == LOAD_HISTORY || aLoadType == LOAD_RELOAD_NORMAL) { + return NS_OK; + } + // An empty anchor. Scroll to the top of the page. Ignore the + // return value; failure to scroll here (e.g. if there is no + // root scrollframe) is not grounds for canceling the load! + SetCurScrollPosEx(0, 0); + } + + return NS_OK; +} + +void +nsDocShell::SetupReferrerFromChannel(nsIChannel* aChannel) +{ + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); + if (httpChannel) { + nsCOMPtr<nsIURI> referrer; + nsresult rv = httpChannel->GetReferrer(getter_AddRefs(referrer)); + if (NS_SUCCEEDED(rv)) { + SetReferrerURI(referrer); + } + uint32_t referrerPolicy; + rv = httpChannel->GetReferrerPolicy(&referrerPolicy); + if (NS_SUCCEEDED(rv)) { + SetReferrerPolicy(referrerPolicy); + } + } +} + +bool +nsDocShell::OnNewURI(nsIURI* aURI, nsIChannel* aChannel, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + uint32_t aLoadType, bool aFireOnLocationChange, + bool aAddToGlobalHistory, bool aCloneSHChildren) +{ + NS_PRECONDITION(aURI, "uri is null"); + NS_PRECONDITION(!aChannel || !aTriggeringPrincipal, "Shouldn't have both set"); + + MOZ_ASSERT(!aPrincipalToInherit || (aPrincipalToInherit && aTriggeringPrincipal)); + +#if defined(DEBUG) + if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) { + nsAutoCString chanName; + if (aChannel) { + aChannel->GetName(chanName); + } else { + chanName.AssignLiteral("<no channel>"); + } + + MOZ_LOG(gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]::OnNewURI(\"%s\", [%s], 0x%x)\n", + this, aURI->GetSpecOrDefault().get(), chanName.get(), aLoadType)); + } +#endif + + bool equalUri = false; + + // Get the post data and the HTTP response code from the channel. + uint32_t responseStatus = 0; + nsCOMPtr<nsIInputStream> inputStream; + if (aChannel) { + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); + + // Check if the HTTPChannel is hiding under a multiPartChannel + if (!httpChannel) { + GetHttpChannel(aChannel, getter_AddRefs(httpChannel)); + } + + if (httpChannel) { + nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel)); + if (uploadChannel) { + uploadChannel->GetUploadStream(getter_AddRefs(inputStream)); + } + + // If the response status indicates an error, unlink this session + // history entry from any entries sharing its document. + nsresult rv = httpChannel->GetResponseStatus(&responseStatus); + if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) { + mLSHE->AbandonBFCacheEntry(); + } + } + } + + // Determine if this type of load should update history. + bool updateGHistory = !(aLoadType == LOAD_BYPASS_HISTORY || + aLoadType == LOAD_ERROR_PAGE || + aLoadType & LOAD_CMD_HISTORY); + + // We don't update session history on reload unless we're loading + // an iframe in shift-reload case. + bool updateSHistory = updateGHistory && + (!(aLoadType & LOAD_CMD_RELOAD) || + (IsForceReloadType(aLoadType) && IsFrame())); + + // Create SH Entry (mLSHE) only if there is a SessionHistory object in the + // current frame or in the root docshell. + nsCOMPtr<nsISHistory> rootSH = mSessionHistory; + if (!rootSH) { + // Get the handle to SH from the root docshell + GetRootSessionHistory(getter_AddRefs(rootSH)); + if (!rootSH) { + updateSHistory = false; + updateGHistory = false; // XXX Why global history too? + } + } + + // Check if the url to be loaded is the same as the one already loaded. + if (mCurrentURI) { + aURI->Equals(mCurrentURI, &equalUri); + } + +#ifdef DEBUG + bool shAvailable = (rootSH != nullptr); + + // XXX This log message is almost useless because |updateSHistory| + // and |updateGHistory| are not correct at this point. + + MOZ_LOG(gDocShellLog, LogLevel::Debug, + (" shAvailable=%i updateSHistory=%i updateGHistory=%i" + " equalURI=%i\n", + shAvailable, updateSHistory, updateGHistory, equalUri)); + + if (shAvailable && mCurrentURI && !mOSHE && aLoadType != LOAD_ERROR_PAGE) { + NS_ASSERTION(NS_IsAboutBlank(mCurrentURI), + "no SHEntry for a non-transient viewer?"); + } +#endif + + /* If the url to be loaded is the same as the one already there, + * and the original loadType is LOAD_NORMAL, LOAD_LINK, or + * LOAD_STOP_CONTENT, set loadType to LOAD_NORMAL_REPLACE so that + * AddToSessionHistory() won't mess with the current SHEntry and + * if this page has any frame children, it also will be handled + * properly. see bug 83684 + * + * NB: If mOSHE is null but we have a current URI, then it means + * that we must be at the transient about:blank content viewer + * (asserted above) and we should let the normal load continue, + * since there's nothing to replace. + * + * XXX Hopefully changing the loadType at this time will not hurt + * anywhere. The other way to take care of sequentially repeating + * frameset pages is to add new methods to nsIDocShellTreeItem. + * Hopefully I don't have to do that. + */ + if (equalUri && + mOSHE && + (mLoadType == LOAD_NORMAL || + mLoadType == LOAD_LINK || + mLoadType == LOAD_STOP_CONTENT) && + !inputStream) { + mLoadType = LOAD_NORMAL_REPLACE; + } + + // If this is a refresh to the currently loaded url, we don't + // have to update session or global history. + if (mLoadType == LOAD_REFRESH && !inputStream && equalUri) { + 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 + */ + if (aChannel && IsForceReloadType(aLoadType)) { + MOZ_ASSERT(!updateSHistory || IsFrame(), + "We shouldn't be updating session history for forced" + " reloads unless we're in a newly created iframe!"); + + nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(aChannel)); + nsCOMPtr<nsISupports> cacheKey; + // Get the Cache Key and store it in SH. + if (cacheChannel) { + cacheChannel->GetCacheKey(getter_AddRefs(cacheKey)); + } + // If we already have a loading history entry, store the new cache key + // in it. Otherwise, since we're doing a reload and won't be updating + // our history entry, store the cache key in our current history entry. + if (mLSHE) { + mLSHE->SetCacheKey(cacheKey); + } else if (mOSHE) { + mOSHE->SetCacheKey(cacheKey); + } + + // Since we're force-reloading, clear all the sub frame history. + ClearFrameHistory(mLSHE); + ClearFrameHistory(mOSHE); + } + + if (aLoadType == LOAD_RELOAD_NORMAL) { + nsCOMPtr<nsISHEntry> currentSH; + bool oshe = false; + GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe); + bool dynamicallyAddedChild = false; + if (currentSH) { + currentSH->HasDynamicallyAddedChild(&dynamicallyAddedChild); + } + if (dynamicallyAddedChild) { + ClearFrameHistory(currentSH); + } + } + + if (aLoadType == LOAD_REFRESH) { + ClearFrameHistory(mLSHE); + ClearFrameHistory(mOSHE); + } + + if (updateSHistory) { + // Update session history if necessary... + if (!mLSHE && (mItemType == typeContent) && mURIResultedInDocument) { + /* This is a fresh page getting loaded for the first time + *.Create a Entry for it and add it to SH, if this is the + * rootDocShell + */ + (void)AddToSessionHistory(aURI, aChannel, aTriggeringPrincipal, + aPrincipalToInherit, aCloneSHChildren, + getter_AddRefs(mLSHE)); + } + } else if (mSessionHistory && mLSHE && mURIResultedInDocument) { + // Even if we don't add anything to SHistory, ensure the current index + // points to the same SHEntry as our mLSHE. + int32_t index = 0; + mSessionHistory->GetRequestedIndex(&index); + if (index == -1) { + mSessionHistory->GetIndex(&index); + } + nsCOMPtr<nsISHEntry> currentSH; + mSessionHistory->GetEntryAtIndex(index, false, getter_AddRefs(currentSH)); + if (currentSH != mLSHE) { + nsCOMPtr<nsISHistoryInternal> shPrivate = + do_QueryInterface(mSessionHistory); + shPrivate->ReplaceEntry(index, mLSHE); + } + } + + // If this is a POST request, we do not want to include this in global + // history. + if (updateGHistory && aAddToGlobalHistory && !ChannelIsPost(aChannel)) { + nsCOMPtr<nsIURI> previousURI; + uint32_t previousFlags = 0; + + if (aLoadType & LOAD_CMD_RELOAD) { + // On a reload request, we don't set redirecting flags. + previousURI = aURI; + } else { + ExtractLastVisit(aChannel, getter_AddRefs(previousURI), &previousFlags); + } + + // Note: We don't use |referrer| when our global history is + // based on IHistory. + nsCOMPtr<nsIURI> referrer; + // Treat referrer as null if there is an error getting it. + (void)NS_GetReferrerFromChannel(aChannel, getter_AddRefs(referrer)); + + AddURIVisit(aURI, referrer, previousURI, previousFlags, responseStatus); + } + + // If this was a history load or a refresh, or it was a history load but + // later changed to LOAD_NORMAL_REPLACE due to redirection, update the index + // in session history. + if (rootSH && + ((mLoadType & (LOAD_CMD_HISTORY | LOAD_CMD_RELOAD)) || + mLoadType == LOAD_NORMAL_REPLACE)) { + nsCOMPtr<nsISHistoryInternal> shInternal(do_QueryInterface(rootSH)); + if (shInternal) { + rootSH->GetIndex(&mPreviousTransIndex); + shInternal->UpdateIndex(); + rootSH->GetIndex(&mLoadedTransIndex); +#ifdef DEBUG_PAGE_CACHE + printf("Previous index: %d, Loaded index: %d\n\n", + mPreviousTransIndex, mLoadedTransIndex); +#endif + } + } + + // aCloneSHChildren exactly means "we are not loading a new document". + uint32_t locationFlags = + aCloneSHChildren ? uint32_t(LOCATION_CHANGE_SAME_DOCUMENT) : 0; + + bool onLocationChangeNeeded = SetCurrentURI(aURI, aChannel, + aFireOnLocationChange, + locationFlags); + // Make sure to store the referrer from the channel, if any + SetupReferrerFromChannel(aChannel); + return onLocationChangeNeeded; +} + +bool +nsDocShell::OnLoadingSite(nsIChannel* aChannel, bool aFireOnLocationChange, + bool aAddToGlobalHistory) +{ + nsCOMPtr<nsIURI> uri; + // If this a redirect, use the final url (uri) + // else use the original url + // + // Note that this should match what documents do (see nsDocument::Reset). + NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); + NS_ENSURE_TRUE(uri, false); + + // Pass false for aCloneSHChildren, since we're loading a new page here. + return OnNewURI(uri, aChannel, nullptr, nullptr, mLoadType, aFireOnLocationChange, + aAddToGlobalHistory, false); +} + +void +nsDocShell::SetReferrerURI(nsIURI* aURI) +{ + mReferrerURI = aURI; // This assigment addrefs +} + +void +nsDocShell::SetReferrerPolicy(uint32_t aReferrerPolicy) +{ + mReferrerPolicy = aReferrerPolicy; +} + +//***************************************************************************** +// nsDocShell: Session History +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle, + const nsAString& aURL, bool aReplace, JSContext* aCx) +{ + // Implements History.pushState and History.replaceState + + // Here's what we do, roughly in the order specified by HTML5: + // 1. Serialize aData using structured clone. + // 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, and save the current + // entry's scroll position (bug 590573). + // 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. if the third argument was present, the absolute URL found in + // step 2 + // Also clear the new history entry's POST data (see bug 580069). + // 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 EvictOutOfRangeContentViewers at the end of + // step 5 might run script, we can't just put a script blocker around the + // critical section. + // + // Note that we completely ignore the aTitle parameter. + + nsresult rv; + + // Don't clobber the load type of an existing network load. + AutoRestore<uint32_t> loadTypeResetter(mLoadType); + + // pushState effectively becomes replaceState when we've started a network + // load but haven't adopted its document yet. This mirrors what we do with + // changes to the hash at this stage of the game. + if (JustStartedNetworkLoad()) { + aReplace = true; + } + + nsCOMPtr<nsIDocument> document = GetDocument(); + NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); + + // Step 1: Serialize aData using structured clone. + nsCOMPtr<nsIStructuredCloneContainer> scContainer; + + // scContainer->Init might cause arbitrary JS to run, and this code might + // navigate the page we're on, potentially to a different origin! (bug + // 634834) To protect against this, we abort if our principal changes due + // to the InitFromJSVal() call. + { + nsCOMPtr<nsIDocument> origDocument = GetDocument(); + if (!origDocument) { + return NS_ERROR_DOM_SECURITY_ERR; + } + nsCOMPtr<nsIPrincipal> origPrincipal = origDocument->NodePrincipal(); + + scContainer = new nsStructuredCloneContainer(); + rv = scContainer->InitFromJSVal(aData, aCx); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocument> newDocument = GetDocument(); + if (!newDocument) { + return NS_ERROR_DOM_SECURITY_ERR; + } + nsCOMPtr<nsIPrincipal> newPrincipal = newDocument->NodePrincipal(); + + bool principalsEqual = false; + origPrincipal->Equals(newPrincipal, &principalsEqual); + NS_ENSURE_TRUE(principalsEqual, NS_ERROR_DOM_SECURITY_ERR); + } + + // Check that the state object isn't too long. + // Default max length: 640k bytes. + int32_t maxStateObjSize = + Preferences::GetInt("browser.history.maxStateObjectSize", 0xA0000); + if (maxStateObjSize < 0) { + maxStateObjSize = 0; + } + + uint64_t scSize; + rv = scContainer->GetSerializedNBytes(&scSize); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(scSize <= (uint32_t)maxStateObjSize, NS_ERROR_ILLEGAL_VALUE); + + // Step 2: Resolve aURL + bool equalURIs = true; + nsCOMPtr<nsIURI> currentURI; + if (sURIFixup && mCurrentURI) { + rv = sURIFixup->CreateExposableURI(mCurrentURI, getter_AddRefs(currentURI)); + NS_ENSURE_SUCCESS(rv, rv); + } else { + currentURI = mCurrentURI; + } + nsCOMPtr<nsIURI> oldURI = currentURI; + nsCOMPtr<nsIURI> newURI; + if (aURL.Length() == 0) { + newURI = currentURI; + } else { + // 2a: Resolve aURL relative to mURI + + nsIURI* docBaseURI = document->GetDocBaseURI(); + if (!docBaseURI) { + return NS_ERROR_FAILURE; + } + + nsAutoCString spec; + docBaseURI->GetSpec(spec); + + nsAutoCString 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 (!nsContentUtils::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.) currentURI + // 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 currentURI, not docBaseURI, because a page can + // set docBaseURI arbitrarily to any domain. + nsAutoCString currentUserPass, newUserPass; + NS_ENSURE_SUCCESS(currentURI->GetUserPass(currentUserPass), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newURI->GetUserPass(newUserPass), NS_ERROR_FAILURE); + if (NS_FAILED(secMan->CheckSameOriginURI(currentURI, newURI, 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, true, false))) { + return NS_ERROR_DOM_SECURITY_ERR; + } + } + + if (currentURI) { + currentURI->Equals(newURI, &equalURIs); + } else { + equalURIs = false; + } + + } // end of same-origin check + + // 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; + + mLoadType = LOAD_PUSHSTATE; + + nsCOMPtr<nsISHEntry> newSHEntry; + if (!aReplace) { + // Save the current scroll position (bug 590573). + nscoord cx = 0, cy = 0; + GetCurScrollPos(ScrollOrientation_X, &cx); + GetCurScrollPos(ScrollOrientation_Y, &cy); + mOSHE->SetScrollPosition(cx, cy); + + bool scrollRestorationIsManual = false; + mOSHE->GetScrollRestorationIsManual(&scrollRestorationIsManual); + + // Since we're not changing which page we have loaded, pass + // true for aCloneChildren. + rv = AddToSessionHistory(newURI, nullptr, nullptr, nullptr, true, + getter_AddRefs(newSHEntry)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE); + + // Session history entries created by pushState inherit scroll restoration + // mode from the current entry. + newSHEntry->SetScrollRestorationIsManual(scrollRestorationIsManual); + + // Link the new SHEntry to the old SHEntry's BFCache entry, since the + // two entries correspond to the same document. + NS_ENSURE_SUCCESS(newSHEntry->AdoptBFCacheEntry(oldOSHE), NS_ERROR_FAILURE); + + // Set the new SHEntry's title (bug 655273). + nsString title; + mOSHE->GetTitle(getter_Copies(title)); + newSHEntry->SetTitle(title); + + // AddToSessionHistory may not modify mOSHE. In case it doesn't, + // we'll just set mOSHE here. + mOSHE = newSHEntry; + + } else { + newSHEntry = mOSHE; + newSHEntry->SetURI(newURI); + newSHEntry->SetOriginalURI(newURI); + newSHEntry->SetLoadReplace(false); + } + + // Step 4: Modify new/original session history entry and clear its POST + // data, if there is any. + newSHEntry->SetStateData(scContainer); + newSHEntry->SetPostData(nullptr); + + // If this push/replaceState changed the document's current URI and the new + // URI differs from the old URI in more than the hash, or if the old + // SHEntry's URI was modified in this way by a push/replaceState call + // set URIWasModified to true for the current SHEntry (bug 669671). + bool sameExceptHashes = true, oldURIWasModified = false; + newURI->EqualsExceptRef(currentURI, &sameExceptHashes); + oldOSHE->GetURIWasModified(&oldURIWasModified); + newSHEntry->SetURIWasModified(!sameExceptHashes || oldURIWasModified); + + // 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. Otherwise + // call ReplaceEntry so that we notify nsIHistoryListeners that an entry + // was replaced. + 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); + + if (!aReplace) { + int32_t curIndex = -1; + rv = rootSH->GetIndex(&curIndex); + if (NS_SUCCEEDED(rv) && curIndex > -1) { + internalSH->EvictOutOfRangeContentViewers(curIndex); + } + } else { + nsCOMPtr<nsISHEntry> rootSHEntry = GetRootSHEntry(newSHEntry); + + int32_t index = -1; + rv = rootSH->GetIndexOfEntry(rootSHEntry, &index); + if (NS_SUCCEEDED(rv) && index > -1) { + internalSH->ReplaceEntry(index, rootSHEntry); + } + } + + // Step 6: If the document's URI changed, update document's URI and update + // global history. + // + // We need to call FireOnLocationChange so that the browser's address bar + // gets updated and the back button is enabled, but we only need to + // explicitly call FireOnLocationChange if we're not calling SetCurrentURI, + // since SetCurrentURI will call FireOnLocationChange for us. + // + // Both SetCurrentURI(...) and FireDummyOnLocationChange() pass + // nullptr for aRequest param to FireOnLocationChange(...). Such an update + // notification is allowed only when we know docshell is not loading a new + // document and it requires LOCATION_CHANGE_SAME_DOCUMENT flag. Otherwise, + // FireOnLocationChange(...) breaks security UI. + if (!equalURIs) { + document->SetDocumentURI(newURI); + // We can't trust SetCurrentURI to do always fire locationchange events + // when we expect it to, so we hack around that by doing it ourselves... + SetCurrentURI(newURI, nullptr, false, LOCATION_CHANGE_SAME_DOCUMENT); + if (mLoadType != LOAD_ERROR_PAGE) { + FireDummyOnLocationChange(); + } + + AddURIVisit(newURI, oldURI, oldURI, 0); + + // AddURIVisit doesn't set the title for the new URI in global history, + // so do that here. + if (mUseGlobalHistory && !UsePrivateBrowsing()) { + nsCOMPtr<IHistory> history = services::GetHistoryService(); + if (history) { + history->SetURITitle(newURI, mTitle); + } else if (mGlobalHistory) { + mGlobalHistory->SetPageTitle(newURI, mTitle); + } + } + + // Inform the favicon service that our old favicon applies to this new + // URI. + CopyFavicon(oldURI, newURI, document->NodePrincipal(), UsePrivateBrowsing()); + } else { + FireDummyOnLocationChange(); + } + document->SetStateObject(scContainer); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCurrentScrollRestorationIsManual(bool* aIsManual) +{ + *aIsManual = false; + if (mOSHE) { + mOSHE->GetScrollRestorationIsManual(aIsManual); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCurrentScrollRestorationIsManual(bool aIsManual) +{ + if (mOSHE) { + mOSHE->SetScrollRestorationIsManual(aIsManual); + } + + return NS_OK; +} + +bool +nsDocShell::ShouldAddToSessionHistory(nsIURI* aURI) +{ + // I believe none of the about: urls should go in the history. But then + // that could just be me... If the intent is only deny about:blank then we + // should just do a spec compare, rather than two gets of the scheme and + // then the path. -Gagan + nsresult rv; + nsAutoCString buf; + + rv = aURI->GetScheme(buf); + if (NS_FAILED(rv)) { + return false; + } + + if (buf.EqualsLiteral("about")) { + rv = aURI->GetPath(buf); + if (NS_FAILED(rv)) { + return false; + } + + if (buf.EqualsLiteral("blank") || buf.EqualsLiteral("newtab")) { + return false; + } + } + + return true; +} + +nsresult +nsDocShell::AddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + bool aCloneChildren, + nsISHEntry** aNewEntry) +{ + NS_PRECONDITION(aURI, "uri is null"); + NS_PRECONDITION(!aChannel || !aTriggeringPrincipal, "Shouldn't have both set"); + +#if defined(DEBUG) + if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) { + nsAutoCString chanName; + if (aChannel) { + aChannel->GetName(chanName); + } else { + chanName.AssignLiteral("<no channel>"); + } + + MOZ_LOG(gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]::AddToSessionHistory(\"%s\", [%s])\n", + this, aURI->GetSpecOrDefault().get(), chanName.get())); + } +#endif + + nsresult rv = NS_OK; + nsCOMPtr<nsISHEntry> entry; + bool shouldPersist; + + shouldPersist = ShouldAddToSessionHistory(aURI); + + // Get a handle to the root docshell + nsCOMPtr<nsIDocShellTreeItem> root; + GetSameTypeRootTreeItem(getter_AddRefs(root)); + /* + * If this is a LOAD_FLAGS_REPLACE_HISTORY in a subframe, we use + * the existing SH entry in the page and replace the url and + * other vitalities. + */ + if (LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY) && + root != static_cast<nsIDocShellTreeItem*>(this)) { + // This is a subframe + entry = mOSHE; + nsCOMPtr<nsISHContainer> shContainer(do_QueryInterface(entry)); + if (shContainer) { + int32_t childCount = 0; + shContainer->GetChildCount(&childCount); + // Remove all children of this entry + for (int32_t i = childCount - 1; i >= 0; i--) { + nsCOMPtr<nsISHEntry> child; + shContainer->GetChildAt(i, getter_AddRefs(child)); + shContainer->RemoveChild(child); + } + entry->AbandonBFCacheEntry(); + } + } + + // Create a new entry if necessary. + if (!entry) { + entry = do_CreateInstance(NS_SHENTRY_CONTRACTID); + + if (!entry) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + // Get the post data & referrer + nsCOMPtr<nsIInputStream> inputStream; + nsCOMPtr<nsIURI> originalURI; + bool loadReplace = false; + nsCOMPtr<nsIURI> referrerURI; + uint32_t referrerPolicy = mozilla::net::RP_Default; + nsCOMPtr<nsISupports> cacheKey; + nsCOMPtr<nsIPrincipal> triggeringPrincipal = aTriggeringPrincipal; + nsCOMPtr<nsIPrincipal> principalToInherit = aPrincipalToInherit; + bool expired = false; + bool discardLayoutState = false; + nsCOMPtr<nsICacheInfoChannel> cacheChannel; + if (aChannel) { + cacheChannel = do_QueryInterface(aChannel); + + /* If there is a caching channel, get the Cache Key and store it + * in SH. + */ + if (cacheChannel) { + cacheChannel->GetCacheKey(getter_AddRefs(cacheKey)); + } + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); + + // Check if the httpChannel is hiding under a multipartChannel + if (!httpChannel) { + GetHttpChannel(aChannel, getter_AddRefs(httpChannel)); + } + if (httpChannel) { + nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel)); + if (uploadChannel) { + uploadChannel->GetUploadStream(getter_AddRefs(inputStream)); + } + httpChannel->GetOriginalURI(getter_AddRefs(originalURI)); + uint32_t loadFlags; + aChannel->GetLoadFlags(&loadFlags); + loadReplace = loadFlags & nsIChannel::LOAD_REPLACE; + httpChannel->GetReferrer(getter_AddRefs(referrerURI)); + httpChannel->GetReferrerPolicy(&referrerPolicy); + + discardLayoutState = ShouldDiscardLayoutState(httpChannel); + } + + // XXX Bug 1286838: Replace channel owner with loadInfo triggeringPrincipal + nsCOMPtr<nsISupports> owner; + aChannel->GetOwner(getter_AddRefs(owner)); + triggeringPrincipal = do_QueryInterface(owner); + + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); + if (loadInfo) { + if (!triggeringPrincipal) { + triggeringPrincipal = loadInfo->TriggeringPrincipal(); + } + + // For now keep storing just the principal in the SHEntry. + if (!principalToInherit) { + if (loadInfo->GetLoadingSandboxed()) { + if (loadInfo->LoadingPrincipal()) { + principalToInherit = nsNullPrincipal::CreateWithInheritedAttributes( + loadInfo->LoadingPrincipal()); + } else { + // get the OriginAttributes + NeckoOriginAttributes nAttrs; + loadInfo->GetOriginAttributes(&nAttrs); + PrincipalOriginAttributes pAttrs; + pAttrs.InheritFromNecko(nAttrs); + principalToInherit = nsNullPrincipal::Create(pAttrs); + } + } else { + principalToInherit = loadInfo->PrincipalToInherit(); + } + } + } + } + + // Title is set in nsDocShell::SetTitle() + entry->Create(aURI, // uri + EmptyString(), // Title + inputStream, // Post data stream + nullptr, // LayoutHistory state + cacheKey, // CacheKey + mContentTypeHint, // Content-type + triggeringPrincipal, // Channel or provided principal + principalToInherit, + mHistoryID, + mDynamicallyCreated); + + entry->SetOriginalURI(originalURI); + entry->SetLoadReplace(loadReplace); + entry->SetReferrerURI(referrerURI); + entry->SetReferrerPolicy(referrerPolicy); + nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(aChannel); + if (inStrmChan) { + bool isSrcdocChannel; + inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel); + if (isSrcdocChannel) { + nsAutoString srcdoc; + inStrmChan->GetSrcdocData(srcdoc); + entry->SetSrcdocData(srcdoc); + nsCOMPtr<nsIURI> baseURI; + inStrmChan->GetBaseURI(getter_AddRefs(baseURI)); + entry->SetBaseURI(baseURI); + } + } + /* If cache got a 'no-store', ask SH not to store + * HistoryLayoutState. By default, SH will set this + * flag to true and save HistoryLayoutState. + */ + if (discardLayoutState) { + entry->SetSaveLayoutStateFlag(false); + } + if (cacheChannel) { + // Check if the page has expired from cache + uint32_t expTime = 0; + cacheChannel->GetCacheTokenExpirationTime(&expTime); + uint32_t now = PRTimeToSeconds(PR_Now()); + if (expTime <= now) { + expired = true; + } + } + if (expired) { + entry->SetExpirationStatus(true); + } + + if (root == static_cast<nsIDocShellTreeItem*>(this) && mSessionHistory) { + // If we need to clone our children onto the new session + // history entry, do so now. + if (aCloneChildren && mOSHE) { + uint32_t cloneID; + mOSHE->GetID(&cloneID); + nsCOMPtr<nsISHEntry> newEntry; + CloneAndReplace(mOSHE, this, cloneID, entry, true, + getter_AddRefs(newEntry)); + NS_ASSERTION(entry == newEntry, + "The new session history should be in the new entry"); + } + + // This is the root docshell + bool addToSHistory = !LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY); + if (!addToSHistory) { + // Replace current entry in session history; If the requested index is + // valid, it indicates the loading was triggered by a history load, and + // we should replace the entry at requested index instead. + int32_t index = 0; + mSessionHistory->GetRequestedIndex(&index); + if (index == -1) { + mSessionHistory->GetIndex(&index); + } + nsCOMPtr<nsISHistoryInternal> shPrivate = + do_QueryInterface(mSessionHistory); + // Replace the current entry with the new entry + if (index >= 0) { + if (shPrivate) { + rv = shPrivate->ReplaceEntry(index, entry); + } + } else { + // If we're trying to replace an inexistant shistory entry, append. + addToSHistory = true; + } + } + + if (addToSHistory) { + // Add to session history + nsCOMPtr<nsISHistoryInternal> shPrivate = + do_QueryInterface(mSessionHistory); + NS_ENSURE_TRUE(shPrivate, NS_ERROR_FAILURE); + mSessionHistory->GetIndex(&mPreviousTransIndex); + rv = shPrivate->AddEntry(entry, shouldPersist); + mSessionHistory->GetIndex(&mLoadedTransIndex); +#ifdef DEBUG_PAGE_CACHE + printf("Previous index: %d, Loaded index: %d\n\n", + mPreviousTransIndex, mLoadedTransIndex); +#endif + } + } else { + // This is a subframe. + if (!mOSHE || !LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY)) { + rv = AddChildSHEntryToParent(entry, mChildOffset, aCloneChildren); + } + } + + // Return the new SH entry... + if (aNewEntry) { + *aNewEntry = nullptr; + if (NS_SUCCEEDED(rv)) { + entry.forget(aNewEntry); + } + } + + return rv; +} + +nsresult +nsDocShell::LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType) +{ + if (!IsNavigationAllowed()) { + return NS_OK; + } + + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIURI> originalURI; + bool loadReplace = false; + nsCOMPtr<nsIInputStream> postData; + nsCOMPtr<nsIURI> referrerURI; + uint32_t referrerPolicy; + nsAutoCString contentType; + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + nsCOMPtr<nsIPrincipal> principalToInherit; + + NS_ENSURE_TRUE(aEntry, NS_ERROR_FAILURE); + + NS_ENSURE_SUCCESS(aEntry->GetURI(getter_AddRefs(uri)), NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(aEntry->GetOriginalURI(getter_AddRefs(originalURI)), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(aEntry->GetLoadReplace(&loadReplace), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(aEntry->GetReferrerURI(getter_AddRefs(referrerURI)), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(aEntry->GetReferrerPolicy(&referrerPolicy), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(aEntry->GetPostData(getter_AddRefs(postData)), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(aEntry->GetContentType(contentType), NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(aEntry->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal)), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(aEntry->GetPrincipalToInherit(getter_AddRefs(principalToInherit)), + NS_ERROR_FAILURE); + + // Calling CreateAboutBlankContentViewer can set mOSHE to null, and if + // that's the only thing holding a ref to aEntry that will cause aEntry to + // die while we're loading it. So hold a strong ref to aEntry here, just + // in case. + nsCOMPtr<nsISHEntry> kungFuDeathGrip(aEntry); + bool isJS; + nsresult rv = uri->SchemeIs("javascript", &isJS); + if (NS_FAILED(rv) || isJS) { + // We're loading a URL that will execute script from inside asyncOpen. + // Replace the current document with about:blank now to prevent + // anything from the current document from leaking into any JavaScript + // code in the URL. + // Don't cache the presentation if we're going to just reload the + // current entry. Caching would lead to trying to save the different + // content viewers in the same nsISHEntry object. + rv = CreateAboutBlankContentViewer(principalToInherit, nullptr, + aEntry != mOSHE); + + if (NS_FAILED(rv)) { + // The creation of the intermittent about:blank content + // viewer failed for some reason (potentially because the + // user prevented it). Interrupt the history load. + return NS_OK; + } + + if (!triggeringPrincipal) { + // Ensure that we have a triggeringPrincipal. Otherwise javascript: + // URIs will pick it up from the about:blank page we just loaded, + // and we don't really want even that in this case. + triggeringPrincipal = nsNullPrincipal::CreateWithInheritedAttributes(this); + } + } + + /* If there is a valid postdata *and* the user pressed + * reload or shift-reload, take user's permission before we + * repost the data to the server. + */ + if ((aLoadType & LOAD_CMD_RELOAD) && postData) { + bool repost; + rv = ConfirmRepost(&repost); + if (NS_FAILED(rv)) { + return rv; + } + + // If the user pressed cancel in the dialog, return. We're done here. + if (!repost) { + return NS_BINDING_ABORTED; + } + } + + // Do not inherit principal from document (security-critical!); + uint32_t flags = INTERNAL_LOAD_FLAGS_NONE; + + nsAutoString srcdoc; + bool isSrcdoc; + nsCOMPtr<nsIURI> baseURI; + aEntry->GetIsSrcdocEntry(&isSrcdoc); + if (isSrcdoc) { + aEntry->GetSrcdocData(srcdoc); + aEntry->GetBaseURI(getter_AddRefs(baseURI)); + flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC; + } else { + srcdoc = NullString(); + } + + // If there is no triggeringPrincipal we can fall back to using the + // SystemPrincipal as the triggeringPrincipal for loading the history + // entry, since the history entry can only end up in history if security + // checks passed in the initial loading phase. + if (!triggeringPrincipal) { + triggeringPrincipal = nsContentUtils::GetSystemPrincipal(); + } + + // Passing nullptr as aSourceDocShell gives the same behaviour as before + // aSourceDocShell was introduced. According to spec we should be passing + // the source browsing context that was used when the history entry was + // first created. bug 947716 has been created to address this issue. + rv = InternalLoad(uri, + originalURI, + loadReplace, + referrerURI, + referrerPolicy, + triggeringPrincipal, + principalToInherit, + flags, + EmptyString(), // No window target + contentType.get(), // Type hint + NullString(), // No forced file download + postData, // Post data stream + nullptr, // No headers stream + aLoadType, // Load type + aEntry, // SHEntry + true, + srcdoc, + nullptr, // Source docshell, see comment above + baseURI, + nullptr, // No nsIDocShell + nullptr); // No nsIRequest + return rv; +} + +NS_IMETHODIMP +nsDocShell::GetShouldSaveLayoutState(bool* aShould) +{ + *aShould = false; + if (mOSHE) { + // Don't capture historystate and save it in history + // if the page asked not to do so. + mOSHE->GetSaveLayoutStateFlag(aShould); + } + + return NS_OK; +} + +nsresult +nsDocShell::PersistLayoutHistoryState() +{ + nsresult rv = NS_OK; + + if (mOSHE) { + bool scrollRestorationIsManual = false; + mOSHE->GetScrollRestorationIsManual(&scrollRestorationIsManual); + + nsCOMPtr<nsIPresShell> shell = GetPresShell(); + nsCOMPtr<nsILayoutHistoryState> layoutState; + if (shell) { + rv = shell->CaptureHistoryState(getter_AddRefs(layoutState)); + } else if (scrollRestorationIsManual) { + // Even if we don't have layout anymore, we may want to reset the current + // scroll state in layout history. + GetLayoutHistoryState(getter_AddRefs(layoutState)); + } + + if (scrollRestorationIsManual && layoutState) { + layoutState->ResetScrollState(); + } + } + + return rv; +} + +/* static */ nsresult +nsDocShell::WalkHistoryEntries(nsISHEntry* aRootEntry, + nsDocShell* aRootShell, + WalkHistoryEntriesFunc aCallback, + void* aData) +{ + NS_ENSURE_TRUE(aRootEntry, NS_ERROR_FAILURE); + + nsCOMPtr<nsISHContainer> container(do_QueryInterface(aRootEntry)); + if (!container) { + return NS_ERROR_FAILURE; + } + + int32_t childCount; + container->GetChildCount(&childCount); + for (int32_t i = 0; i < childCount; i++) { + nsCOMPtr<nsISHEntry> childEntry; + container->GetChildAt(i, getter_AddRefs(childEntry)); + if (!childEntry) { + // childEntry can be null for valid reasons, for example if the + // docshell at index i never loaded anything useful. + // Remember to clone also nulls in the child array (bug 464064). + aCallback(nullptr, nullptr, i, aData); + continue; + } + + nsDocShell* childShell = nullptr; + if (aRootShell) { + // Walk the children of aRootShell and see if one of them + // has srcChild as a SHEntry. + nsTObserverArray<nsDocLoader*>::ForwardIterator iter( + aRootShell->mChildList); + while (iter.HasMore()) { + nsDocShell* child = static_cast<nsDocShell*>(iter.GetNext()); + + if (child->HasHistoryEntry(childEntry)) { + childShell = child; + break; + } + } + } + nsresult rv = aCallback(childEntry, childShell, i, aData); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +// callback data for WalkHistoryEntries +struct MOZ_STACK_CLASS CloneAndReplaceData +{ + CloneAndReplaceData(uint32_t aCloneID, nsISHEntry* aReplaceEntry, + bool aCloneChildren, nsISHEntry* aDestTreeParent) + : cloneID(aCloneID) + , cloneChildren(aCloneChildren) + , replaceEntry(aReplaceEntry) + , destTreeParent(aDestTreeParent) + { + } + + uint32_t cloneID; + bool cloneChildren; + nsISHEntry* replaceEntry; + nsISHEntry* destTreeParent; + nsCOMPtr<nsISHEntry> resultEntry; +}; + +/* static */ nsresult +nsDocShell::CloneAndReplaceChild(nsISHEntry* aEntry, nsDocShell* aShell, + int32_t aEntryIndex, void* aData) +{ + nsCOMPtr<nsISHEntry> dest; + + CloneAndReplaceData* data = static_cast<CloneAndReplaceData*>(aData); + uint32_t cloneID = data->cloneID; + nsISHEntry* replaceEntry = data->replaceEntry; + + nsCOMPtr<nsISHContainer> container = do_QueryInterface(data->destTreeParent); + if (!aEntry) { + if (container) { + container->AddChild(nullptr, aEntryIndex); + } + return NS_OK; + } + + uint32_t srcID; + aEntry->GetID(&srcID); + + nsresult rv = NS_OK; + if (srcID == cloneID) { + // Replace the entry + dest = replaceEntry; + } else { + // Clone the SHEntry... + rv = aEntry->Clone(getter_AddRefs(dest)); + NS_ENSURE_SUCCESS(rv, rv); + } + dest->SetIsSubFrame(true); + + if (srcID != cloneID || data->cloneChildren) { + // Walk the children + CloneAndReplaceData childData(cloneID, replaceEntry, + data->cloneChildren, dest); + rv = WalkHistoryEntries(aEntry, aShell, + CloneAndReplaceChild, &childData); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (srcID != cloneID && aShell) { + aShell->SwapHistoryEntries(aEntry, dest); + } + + if (container) { + container->AddChild(dest, aEntryIndex); + } + + data->resultEntry = dest; + return rv; +} + +/* static */ nsresult +nsDocShell::CloneAndReplace(nsISHEntry* aSrcEntry, + nsDocShell* aSrcShell, + uint32_t aCloneID, + nsISHEntry* aReplaceEntry, + bool aCloneChildren, + nsISHEntry** aResultEntry) +{ + NS_ENSURE_ARG_POINTER(aResultEntry); + NS_ENSURE_TRUE(aReplaceEntry, NS_ERROR_FAILURE); + + CloneAndReplaceData data(aCloneID, aReplaceEntry, aCloneChildren, nullptr); + nsresult rv = CloneAndReplaceChild(aSrcEntry, aSrcShell, 0, &data); + + data.resultEntry.swap(*aResultEntry); + return rv; +} + +void +nsDocShell::SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry) +{ + if (aOldEntry == mOSHE) { + mOSHE = aNewEntry; + } + + if (aOldEntry == mLSHE) { + mLSHE = aNewEntry; + } +} + +struct SwapEntriesData +{ + nsDocShell* ignoreShell; // constant; the shell to ignore + nsISHEntry* destTreeRoot; // constant; the root of the dest tree + nsISHEntry* destTreeParent; // constant; the node under destTreeRoot + // whose children will correspond to aEntry +}; + +nsresult +nsDocShell::SetChildHistoryEntry(nsISHEntry* aEntry, nsDocShell* aShell, + int32_t aEntryIndex, void* aData) +{ + SwapEntriesData* data = static_cast<SwapEntriesData*>(aData); + nsDocShell* ignoreShell = data->ignoreShell; + + if (!aShell || aShell == ignoreShell) { + return NS_OK; + } + + nsISHEntry* destTreeRoot = data->destTreeRoot; + + nsCOMPtr<nsISHEntry> destEntry; + nsCOMPtr<nsISHContainer> container = do_QueryInterface(data->destTreeParent); + + if (container) { + // aEntry is a clone of some child of destTreeParent, but since the + // trees aren't necessarily in sync, we'll have to locate it. + // Note that we could set aShell's entry to null if we don't find a + // corresponding entry under destTreeParent. + + uint32_t targetID, id; + aEntry->GetID(&targetID); + + // First look at the given index, since this is the common case. + nsCOMPtr<nsISHEntry> entry; + container->GetChildAt(aEntryIndex, getter_AddRefs(entry)); + if (entry && NS_SUCCEEDED(entry->GetID(&id)) && id == targetID) { + destEntry.swap(entry); + } else { + int32_t childCount; + container->GetChildCount(&childCount); + for (int32_t i = 0; i < childCount; ++i) { + container->GetChildAt(i, getter_AddRefs(entry)); + if (!entry) { + continue; + } + + entry->GetID(&id); + if (id == targetID) { + destEntry.swap(entry); + break; + } + } + } + } else { + destEntry = destTreeRoot; + } + + aShell->SwapHistoryEntries(aEntry, destEntry); + + // Now handle the children of aEntry. + SwapEntriesData childData = { ignoreShell, destTreeRoot, destEntry }; + return WalkHistoryEntries(aEntry, aShell, SetChildHistoryEntry, &childData); +} + +static nsISHEntry* +GetRootSHEntry(nsISHEntry* aEntry) +{ + nsCOMPtr<nsISHEntry> rootEntry = aEntry; + nsISHEntry* result = nullptr; + while (rootEntry) { + result = rootEntry; + result->GetParent(getter_AddRefs(rootEntry)); + } + + return result; +} + +void +nsDocShell::SetHistoryEntry(nsCOMPtr<nsISHEntry>* aPtr, nsISHEntry* aEntry) +{ + // We need to sync up the docshell and session history trees for + // subframe navigation. If the load was in a subframe, we forward up to + // the root docshell, which will then recursively sync up all docshells + // to their corresponding entries in the new session history tree. + // If we don't do this, then we can cache a content viewer on the wrong + // cloned entry, and subsequently restore it at the wrong time. + + nsISHEntry* newRootEntry = GetRootSHEntry(aEntry); + if (newRootEntry) { + // newRootEntry is now the new root entry. + // Find the old root entry as well. + + // Need a strong ref. on |oldRootEntry| so it isn't destroyed when + // SetChildHistoryEntry() does SwapHistoryEntries() (bug 304639). + nsCOMPtr<nsISHEntry> oldRootEntry = GetRootSHEntry(*aPtr); + if (oldRootEntry) { + nsCOMPtr<nsIDocShellTreeItem> rootAsItem; + GetSameTypeRootTreeItem(getter_AddRefs(rootAsItem)); + nsCOMPtr<nsIDocShell> rootShell = do_QueryInterface(rootAsItem); + if (rootShell) { // if we're the root just set it, nothing to swap + SwapEntriesData data = { this, newRootEntry }; + nsIDocShell* rootIDocShell = static_cast<nsIDocShell*>(rootShell); + nsDocShell* rootDocShell = static_cast<nsDocShell*>(rootIDocShell); + +#ifdef DEBUG + nsresult rv = +#endif + SetChildHistoryEntry(oldRootEntry, rootDocShell, 0, &data); + NS_ASSERTION(NS_SUCCEEDED(rv), "SetChildHistoryEntry failed"); + } + } + } + + *aPtr = aEntry; +} + +nsresult +nsDocShell::GetRootSessionHistory(nsISHistory** aReturn) +{ + nsresult rv; + + nsCOMPtr<nsIDocShellTreeItem> root; + // Get the root docshell + rv = GetSameTypeRootTreeItem(getter_AddRefs(root)); + // QI to nsIWebNavigation + nsCOMPtr<nsIWebNavigation> rootAsWebnav(do_QueryInterface(root)); + if (rootAsWebnav) { + // Get the handle to SH from the root docshell + rv = rootAsWebnav->GetSessionHistory(aReturn); + } + return rv; +} + +nsresult +nsDocShell::GetHttpChannel(nsIChannel* aChannel, nsIHttpChannel** aReturn) +{ + NS_ENSURE_ARG_POINTER(aReturn); + if (!aChannel) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIMultiPartChannel> multiPartChannel(do_QueryInterface(aChannel)); + if (multiPartChannel) { + nsCOMPtr<nsIChannel> baseChannel; + multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel)); + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(baseChannel)); + *aReturn = httpChannel; + NS_IF_ADDREF(*aReturn); + } + return NS_OK; +} + +bool +nsDocShell::ShouldDiscardLayoutState(nsIHttpChannel* aChannel) +{ + // By default layout State will be saved. + if (!aChannel) { + return false; + } + + // figure out if SH should be saving layout state + bool noStore = false; + aChannel->IsNoStoreResponse(&noStore); + return noStore; +} + +NS_IMETHODIMP +nsDocShell::GetEditor(nsIEditor** aEditor) +{ + NS_ENSURE_ARG_POINTER(aEditor); + + if (!mEditorData) { + *aEditor = nullptr; + return NS_OK; + } + + return mEditorData->GetEditor(aEditor); +} + +NS_IMETHODIMP +nsDocShell::SetEditor(nsIEditor* aEditor) +{ + nsresult rv = EnsureEditorData(); + if (NS_FAILED(rv)) { + return rv; + } + + return mEditorData->SetEditor(aEditor); +} + +NS_IMETHODIMP +nsDocShell::GetEditable(bool* aEditable) +{ + NS_ENSURE_ARG_POINTER(aEditable); + *aEditable = mEditorData && mEditorData->GetEditable(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHasEditingSession(bool* aHasEditingSession) +{ + NS_ENSURE_ARG_POINTER(aHasEditingSession); + + if (mEditorData) { + nsCOMPtr<nsIEditingSession> editingSession; + mEditorData->GetEditingSession(getter_AddRefs(editingSession)); + *aHasEditingSession = (editingSession.get() != nullptr); + } else { + *aHasEditingSession = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::MakeEditable(bool aInWaitForUriLoad) +{ + nsresult rv = EnsureEditorData(); + if (NS_FAILED(rv)) { + return rv; + } + + return mEditorData->MakeEditable(aInWaitForUriLoad); +} + +bool +nsDocShell::ChannelIsPost(nsIChannel* aChannel) +{ + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); + if (!httpChannel) { + return false; + } + + nsAutoCString method; + httpChannel->GetRequestMethod(method); + return method.EqualsLiteral("POST"); +} + +void +nsDocShell::ExtractLastVisit(nsIChannel* aChannel, + nsIURI** aURI, + uint32_t* aChannelRedirectFlags) +{ + nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel)); + if (!props) { + return; + } + + nsresult rv = props->GetPropertyAsInterface( + NS_LITERAL_STRING("docshell.previousURI"), + NS_GET_IID(nsIURI), + reinterpret_cast<void**>(aURI)); + + if (NS_FAILED(rv)) { + // There is no last visit for this channel, so this must be the first + // link. Link the visit to the referrer of this request, if any. + // Treat referrer as null if there is an error getting it. + (void)NS_GetReferrerFromChannel(aChannel, aURI); + } else { + rv = props->GetPropertyAsUint32(NS_LITERAL_STRING("docshell.previousFlags"), + aChannelRedirectFlags); + + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "Could not fetch previous flags, URI will be treated like referrer"); + } +} + +void +nsDocShell::SaveLastVisit(nsIChannel* aChannel, + nsIURI* aURI, + uint32_t aChannelRedirectFlags) +{ + nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel)); + if (!props || !aURI) { + return; + } + + props->SetPropertyAsInterface(NS_LITERAL_STRING("docshell.previousURI"), + aURI); + props->SetPropertyAsUint32(NS_LITERAL_STRING("docshell.previousFlags"), + aChannelRedirectFlags); +} + +void +nsDocShell::AddURIVisit(nsIURI* aURI, + nsIURI* aReferrerURI, + nsIURI* aPreviousURI, + uint32_t aChannelRedirectFlags, + uint32_t aResponseStatus) +{ + MOZ_ASSERT(aURI, "Visited URI is null!"); + MOZ_ASSERT(mLoadType != LOAD_ERROR_PAGE && + mLoadType != LOAD_BYPASS_HISTORY, + "Do not add error or bypass pages to global history"); + + // Only content-type docshells save URI visits. Also don't do + // anything here if we're not supposed to use global history. + if (mItemType != typeContent || !mUseGlobalHistory || UsePrivateBrowsing()) { + return; + } + + nsCOMPtr<IHistory> history = services::GetHistoryService(); + + if (history) { + uint32_t visitURIFlags = 0; + + if (!IsFrame()) { + visitURIFlags |= IHistory::TOP_LEVEL; + } + + if (aChannelRedirectFlags & nsIChannelEventSink::REDIRECT_TEMPORARY) { + visitURIFlags |= IHistory::REDIRECT_TEMPORARY; + } else if (aChannelRedirectFlags & nsIChannelEventSink::REDIRECT_PERMANENT) { + visitURIFlags |= IHistory::REDIRECT_PERMANENT; + } + + if (aResponseStatus >= 300 && aResponseStatus < 400) { + visitURIFlags |= IHistory::REDIRECT_SOURCE; + } + // Errors 400-501 and 505 are considered unrecoverable, in the sense a + // simple retry attempt by the user is unlikely to solve them. + // 408 is special cased, since may actually indicate a temporary + // connection problem. + else if (aResponseStatus != 408 && + ((aResponseStatus >= 400 && aResponseStatus <= 501) || + aResponseStatus == 505)) { + visitURIFlags |= IHistory::UNRECOVERABLE_ERROR; + } + + (void)history->VisitURI(aURI, aPreviousURI, visitURIFlags); + } else if (mGlobalHistory) { + // Falls back to sync global history interface. + (void)mGlobalHistory->AddURI(aURI, + !!aChannelRedirectFlags, + !IsFrame(), + aReferrerURI); + } +} + +//***************************************************************************** +// nsDocShell: Helper Routines +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::SetLoadType(uint32_t aLoadType) +{ + mLoadType = aLoadType; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetLoadType(uint32_t* aLoadType) +{ + *aLoadType = mLoadType; + return NS_OK; +} + +nsresult +nsDocShell::ConfirmRepost(bool* aRepost) +{ + nsCOMPtr<nsIPrompt> prompter; + CallGetInterface(this, static_cast<nsIPrompt**>(getter_AddRefs(prompter))); + if (!prompter) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr<nsIStringBundleService> stringBundleService = + mozilla::services::GetStringBundleService(); + if (!stringBundleService) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIStringBundle> appBundle; + nsresult rv = stringBundleService->CreateBundle(kAppstringsBundleURL, + getter_AddRefs(appBundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIStringBundle> brandBundle; + rv = stringBundleService->CreateBundle(kBrandBundleURL, + getter_AddRefs(brandBundle)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(prompter && brandBundle && appBundle, + "Unable to set up repost prompter."); + + nsXPIDLString brandName; + rv = brandBundle->GetStringFromName(u"brandShortName", + getter_Copies(brandName)); + + nsXPIDLString msgString, button0Title; + if (NS_FAILED(rv)) { // No brand, use the generic version. + rv = appBundle->GetStringFromName(u"confirmRepostPrompt", + getter_Copies(msgString)); + } else { + // Brand available - if the app has an override file with formatting, the + // app name will be included. Without an override, the prompt will look + // like the generic version. + const char16_t* formatStrings[] = { brandName.get() }; + rv = appBundle->FormatStringFromName(u"confirmRepostPrompt", + formatStrings, + ArrayLength(formatStrings), + getter_Copies(msgString)); + } + if (NS_FAILED(rv)) { + return rv; + } + + rv = appBundle->GetStringFromName(u"resendButton.label", + getter_Copies(button0Title)); + if (NS_FAILED(rv)) { + return rv; + } + + int32_t buttonPressed; + // The actual value here is irrelevant, but we can't pass an invalid + // bool through XPConnect. + bool checkState = false; + rv = prompter->ConfirmEx( + nullptr, msgString.get(), + (nsIPrompt::BUTTON_POS_0 * nsIPrompt::BUTTON_TITLE_IS_STRING) + + (nsIPrompt::BUTTON_POS_1 * nsIPrompt::BUTTON_TITLE_CANCEL), + button0Title.get(), nullptr, nullptr, nullptr, &checkState, &buttonPressed); + if (NS_FAILED(rv)) { + return rv; + } + + *aRepost = (buttonPressed == 0); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetPromptAndStringBundle(nsIPrompt** aPrompt, + nsIStringBundle** aStringBundle) +{ + NS_ENSURE_SUCCESS(GetInterface(NS_GET_IID(nsIPrompt), (void**)aPrompt), + NS_ERROR_FAILURE); + + nsCOMPtr<nsIStringBundleService> stringBundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(stringBundleService, NS_ERROR_FAILURE); + + NS_ENSURE_SUCCESS( + stringBundleService->CreateBundle(kAppstringsBundleURL, aStringBundle), + NS_ERROR_FAILURE); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetChildOffset(nsIDOMNode* aChild, nsIDOMNode* aParent, + int32_t* aOffset) +{ + NS_ENSURE_ARG_POINTER(aChild || aParent); + + nsCOMPtr<nsIDOMNodeList> childNodes; + NS_ENSURE_SUCCESS(aParent->GetChildNodes(getter_AddRefs(childNodes)), + NS_ERROR_FAILURE); + NS_ENSURE_TRUE(childNodes, NS_ERROR_FAILURE); + + int32_t i = 0; + + for (; true; i++) { + nsCOMPtr<nsIDOMNode> childNode; + NS_ENSURE_SUCCESS(childNodes->Item(i, getter_AddRefs(childNode)), + NS_ERROR_FAILURE); + NS_ENSURE_TRUE(childNode, NS_ERROR_FAILURE); + + if (childNode.get() == aChild) { + *aOffset = i; + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} + +nsIScrollableFrame* +nsDocShell::GetRootScrollFrame() +{ + nsCOMPtr<nsIPresShell> shell = GetPresShell(); + NS_ENSURE_TRUE(shell, nullptr); + + return shell->GetRootScrollFrameAsScrollableExternal(); +} + +NS_IMETHODIMP +nsDocShell::EnsureScriptEnvironment() +{ + if (mScriptGlobal) { + return NS_OK; + } + + if (mIsBeingDestroyed) { + return NS_ERROR_NOT_AVAILABLE; + } + +#ifdef DEBUG + NS_ASSERTION(!mInEnsureScriptEnv, + "Infinite loop! Calling EnsureScriptEnvironment() from " + "within EnsureScriptEnvironment()!"); + + // Yeah, this isn't re-entrant safe, but that's ok since if we + // re-enter this method, we'll infinitely loop... + AutoRestore<bool> boolSetter(mInEnsureScriptEnv); + mInEnsureScriptEnv = true; +#endif + + nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(mTreeOwner)); + NS_ENSURE_TRUE(browserChrome, NS_ERROR_NOT_AVAILABLE); + + uint32_t chromeFlags; + browserChrome->GetChromeFlags(&chromeFlags); + + bool isModalContentWindow = + (mItemType == typeContent) && + (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL_CONTENT_WINDOW); + // There can be various other content docshells associated with the + // top-level window, like sidebars. Make sure that we only create an + // nsGlobalModalWindow for the primary content shell. + if (isModalContentWindow) { + nsCOMPtr<nsIDocShellTreeItem> primaryItem; + nsresult rv = + mTreeOwner->GetPrimaryContentShell(getter_AddRefs(primaryItem)); + NS_ENSURE_SUCCESS(rv, rv); + isModalContentWindow = (primaryItem == this); + } + + // If our window is modal and we're not opened as chrome, make + // this window a modal content window. + mScriptGlobal = + NS_NewScriptGlobalObject(mItemType == typeChrome, isModalContentWindow); + MOZ_ASSERT(mScriptGlobal); + + mScriptGlobal->SetDocShell(this); + + // Ensure the script object is set up to run script. + return mScriptGlobal->EnsureScriptEnvironment(); +} + +NS_IMETHODIMP +nsDocShell::EnsureEditorData() +{ + bool openDocHasDetachedEditor = mOSHE && mOSHE->HasDetachedEditor(); + if (!mEditorData && !mIsBeingDestroyed && !openDocHasDetachedEditor) { + // We shouldn't recreate the editor data if it already exists, or + // we're shutting down, or we already have a detached editor data + // stored in the session history. We should only have one editordata + // per docshell. + mEditorData = new nsDocShellEditorData(this); + } + + return mEditorData ? NS_OK : NS_ERROR_NOT_AVAILABLE; +} + +nsresult +nsDocShell::EnsureTransferableHookData() +{ + if (!mTransferableHookData) { + mTransferableHookData = new nsTransferableHookData(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::EnsureFind() +{ + nsresult rv; + if (!mFind) { + mFind = do_CreateInstance("@mozilla.org/embedcomp/find;1", &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + // we promise that the nsIWebBrowserFind that we return has been set + // up to point to the focused, or content window, so we have to + // set that up each time. + + nsIScriptGlobalObject* scriptGO = GetScriptGlobalObject(); + NS_ENSURE_TRUE(scriptGO, NS_ERROR_UNEXPECTED); + + // default to our window + nsCOMPtr<nsPIDOMWindowOuter> ourWindow = do_QueryInterface(scriptGO); + nsCOMPtr<nsPIDOMWindowOuter> windowToSearch; + nsFocusManager::GetFocusedDescendant(ourWindow, true, + getter_AddRefs(windowToSearch)); + + nsCOMPtr<nsIWebBrowserFindInFrames> findInFrames = do_QueryInterface(mFind); + if (!findInFrames) { + return NS_ERROR_NO_INTERFACE; + } + + rv = findInFrames->SetRootSearchFrame(ourWindow); + if (NS_FAILED(rv)) { + return rv; + } + rv = findInFrames->SetCurrentSearchFrame(windowToSearch); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +bool +nsDocShell::IsFrame() +{ + nsCOMPtr<nsIDocShellTreeItem> parent; + GetSameTypeParent(getter_AddRefs(parent)); + return !!parent; +} + +NS_IMETHODIMP +nsDocShell::IsBeingDestroyed(bool* aDoomed) +{ + NS_ENSURE_ARG(aDoomed); + *aDoomed = mIsBeingDestroyed; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsExecutingOnLoadHandler(bool* aResult) +{ + NS_ENSURE_ARG(aResult); + *aResult = mIsExecutingOnLoadHandler; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetLayoutHistoryState(nsILayoutHistoryState** aLayoutHistoryState) +{ + if (mOSHE) { + mOSHE->GetLayoutHistoryState(aLayoutHistoryState); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetLayoutHistoryState(nsILayoutHistoryState* aLayoutHistoryState) +{ + if (mOSHE) { + mOSHE->SetLayoutHistoryState(aLayoutHistoryState); + } + return NS_OK; +} + +nsRefreshTimer::nsRefreshTimer() + : mDelay(0), mRepeat(false), mMetaRefresh(false) +{ +} + +nsRefreshTimer::~nsRefreshTimer() +{ +} + +NS_IMPL_ADDREF(nsRefreshTimer) +NS_IMPL_RELEASE(nsRefreshTimer) + +NS_INTERFACE_MAP_BEGIN(nsRefreshTimer) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback) + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) +NS_INTERFACE_MAP_END_THREADSAFE + +NS_IMETHODIMP +nsRefreshTimer::Notify(nsITimer* aTimer) +{ + NS_ASSERTION(mDocShell, "DocShell is somehow null"); + + if (mDocShell && aTimer) { + // Get the delay count to determine load type + uint32_t delay = 0; + aTimer->GetDelay(&delay); + mDocShell->ForceRefreshURIFromTimer(mURI, delay, mMetaRefresh, aTimer, mPrincipal); + } + return NS_OK; +} + +nsDocShell::InterfaceRequestorProxy::InterfaceRequestorProxy( + nsIInterfaceRequestor* aRequestor) +{ + if (aRequestor) { + mWeakPtr = do_GetWeakReference(aRequestor); + } +} + +nsDocShell::InterfaceRequestorProxy::~InterfaceRequestorProxy() +{ + mWeakPtr = nullptr; +} + +NS_IMPL_ISUPPORTS(nsDocShell::InterfaceRequestorProxy, nsIInterfaceRequestor) + +NS_IMETHODIMP +nsDocShell::InterfaceRequestorProxy::GetInterface(const nsIID& aIID, + void** aSink) +{ + NS_ENSURE_ARG_POINTER(aSink); + nsCOMPtr<nsIInterfaceRequestor> ifReq = do_QueryReferent(mWeakPtr); + if (ifReq) { + return ifReq->GetInterface(aIID, aSink); + } + *aSink = nullptr; + return NS_NOINTERFACE; +} + +nsresult +nsDocShell::SetBaseUrlForWyciwyg(nsIContentViewer* aContentViewer) +{ + if (!aContentViewer) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIURI> baseURI; + nsresult rv = NS_ERROR_NOT_AVAILABLE; + + if (sURIFixup) { + rv = sURIFixup->CreateExposableURI(mCurrentURI, getter_AddRefs(baseURI)); + } + + // Get the current document and set the base uri + if (baseURI) { + nsIDocument* document = aContentViewer->GetDocument(); + if (document) { + document->SetBaseURI(baseURI); + } + } + return rv; +} + +//***************************************************************************** +// nsDocShell::nsIAuthPromptProvider +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::GetAuthPrompt(uint32_t aPromptReason, const nsIID& aIID, + void** aResult) +{ + // a priority prompt request will override a false mAllowAuth setting + bool priorityPrompt = (aPromptReason == PROMPT_PROXY); + + if (!mAllowAuth && !priorityPrompt) { + return NS_ERROR_NOT_AVAILABLE; + } + + // we're either allowing auth, or it's a proxy request + nsresult rv; + nsCOMPtr<nsIPromptFactory> wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnsureScriptEnvironment(); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the an auth prompter for our window so that the parenting + // of the dialogs works as it should when using tabs. + + return wwatch->GetPrompt(mScriptGlobal->AsOuter(), aIID, + reinterpret_cast<void**>(aResult)); +} + +//***************************************************************************** +// nsDocShell::nsILoadContext +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::GetAssociatedWindow(mozIDOMWindowProxy** aWindow) +{ + CallGetInterface(this, aWindow); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetTopWindow(mozIDOMWindowProxy** aWindow) +{ + nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow(); + if (win) { + win = win->GetTop(); + } + win.forget(aWindow); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetTopFrameElement(nsIDOMElement** aElement) +{ + *aElement = nullptr; + nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow(); + if (!win) { + return NS_OK; + } + + nsCOMPtr<nsPIDOMWindowOuter> top = win->GetScriptableTop(); + NS_ENSURE_TRUE(top, NS_ERROR_FAILURE); + + // GetFrameElementInternal, /not/ GetScriptableFrameElement -- if |top| is + // inside <iframe mozbrowser>, we want to return the iframe, not null. + // And we want to cross the content/chrome boundary. + nsCOMPtr<nsIDOMElement> elt = + do_QueryInterface(top->GetFrameElementInternal()); + elt.forget(aElement); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetNestedFrameId(uint64_t* aId) +{ + *aId = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::IsTrackingProtectionOn(bool* aIsTrackingProtectionOn) +{ + if (Preferences::GetBool("privacy.trackingprotection.enabled", false)) { + *aIsTrackingProtectionOn = true; + } else if (UsePrivateBrowsing() && + Preferences::GetBool("privacy.trackingprotection.pbmode.enabled", false)) { + *aIsTrackingProtectionOn = true; + } else { + *aIsTrackingProtectionOn = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsContent(bool* aIsContent) +{ + *aIsContent = (mItemType == typeContent); + return NS_OK; +} + +bool +nsDocShell::IsOKToLoadURI(nsIURI* aURI) +{ + NS_PRECONDITION(aURI, "Must have a URI!"); + + if (!mFiredUnloadEvent) { + return true; + } + + if (!mLoadingURI) { + return false; + } + + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + return secMan && + NS_SUCCEEDED(secMan->CheckSameOriginURI(aURI, mLoadingURI, false)); +} + +// +// Routines for selection and clipboard +// +nsresult +nsDocShell::GetControllerForCommand(const char* aCommand, + nsIController** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + NS_ENSURE_TRUE(mScriptGlobal, NS_ERROR_FAILURE); + + nsCOMPtr<nsPIWindowRoot> root = mScriptGlobal->GetTopWindowRoot(); + NS_ENSURE_TRUE(root, NS_ERROR_FAILURE); + + return root->GetControllerForCommand(aCommand, aResult); +} + +NS_IMETHODIMP +nsDocShell::IsCommandEnabled(const char* aCommand, bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + + nsresult rv = NS_ERROR_FAILURE; + + nsCOMPtr<nsIController> controller; + rv = GetControllerForCommand(aCommand, getter_AddRefs(controller)); + if (controller) { + rv = controller->IsCommandEnabled(aCommand, aResult); + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::DoCommand(const char* aCommand) +{ + nsresult rv = NS_ERROR_FAILURE; + + nsCOMPtr<nsIController> controller; + rv = GetControllerForCommand(aCommand, getter_AddRefs(controller)); + if (controller) { + rv = controller->DoCommand(aCommand); + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::DoCommandWithParams(const char* aCommand, nsICommandParams* aParams) +{ + nsCOMPtr<nsIController> controller; + nsresult rv = GetControllerForCommand(aCommand, getter_AddRefs(controller)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsICommandController> commandController = + do_QueryInterface(controller, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return commandController->DoCommandWithParams(aCommand, aParams); +} + +nsresult +nsDocShell::EnsureCommandHandler() +{ + if (!mCommandManager) { + nsCOMPtr<nsPICommandUpdater> commandUpdater = + do_CreateInstance("@mozilla.org/embedcomp/command-manager;1"); + if (!commandUpdater) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCOMPtr<nsPIDOMWindowOuter> domWindow = GetWindow(); + nsresult rv = commandUpdater->Init(domWindow); + if (NS_SUCCEEDED(rv)) { + mCommandManager = do_QueryInterface(commandUpdater); + } + } + + return mCommandManager ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::CanCutSelection(bool* aResult) +{ + return IsCommandEnabled("cmd_cut", aResult); +} + +NS_IMETHODIMP +nsDocShell::CanCopySelection(bool* aResult) +{ + return IsCommandEnabled("cmd_copy", aResult); +} + +NS_IMETHODIMP +nsDocShell::CanCopyLinkLocation(bool* aResult) +{ + return IsCommandEnabled("cmd_copyLink", aResult); +} + +NS_IMETHODIMP +nsDocShell::CanCopyImageLocation(bool* aResult) +{ + return IsCommandEnabled("cmd_copyImageLocation", aResult); +} + +NS_IMETHODIMP +nsDocShell::CanCopyImageContents(bool* aResult) +{ + return IsCommandEnabled("cmd_copyImageContents", aResult); +} + +NS_IMETHODIMP +nsDocShell::CanPaste(bool* aResult) +{ + return IsCommandEnabled("cmd_paste", aResult); +} + +NS_IMETHODIMP +nsDocShell::CutSelection(void) +{ + return DoCommand("cmd_cut"); +} + +NS_IMETHODIMP +nsDocShell::CopySelection(void) +{ + return DoCommand("cmd_copy"); +} + +NS_IMETHODIMP +nsDocShell::CopyLinkLocation(void) +{ + return DoCommand("cmd_copyLink"); +} + +NS_IMETHODIMP +nsDocShell::CopyImageLocation(void) +{ + return DoCommand("cmd_copyImageLocation"); +} + +NS_IMETHODIMP +nsDocShell::CopyImageContents(void) +{ + return DoCommand("cmd_copyImageContents"); +} + +NS_IMETHODIMP +nsDocShell::Paste(void) +{ + return DoCommand("cmd_paste"); +} + +NS_IMETHODIMP +nsDocShell::SelectAll(void) +{ + return DoCommand("cmd_selectAll"); +} + +// +// SelectNone +// +// Collapses the current selection, insertion point ends up at beginning +// of previous selection. +// +NS_IMETHODIMP +nsDocShell::SelectNone(void) +{ + return DoCommand("cmd_selectNone"); +} + +// link handling + +class OnLinkClickEvent : public Runnable +{ +public: + OnLinkClickEvent(nsDocShell* aHandler, nsIContent* aContent, + nsIURI* aURI, + const char16_t* aTargetSpec, + const nsAString& aFileName, + nsIInputStream* aPostDataStream, + nsIInputStream* aHeadersDataStream, + bool aIsTrusted); + + NS_IMETHOD Run() override + { + nsAutoPopupStatePusher popupStatePusher(mPopupState); + + // We need to set up an AutoJSAPI here for the following reason: When we do + // OnLinkClickSync we'll eventually end up in nsGlobalWindow::OpenInternal + // which only does popup blocking if !LegacyIsCallerChromeOrNativeCode(). + // So we need to fake things so that we don't look like native code as far + // as LegacyIsCallerNativeCode() is concerned. + AutoJSAPI jsapi; + if (mIsTrusted || jsapi.Init(mContent->OwnerDoc()->GetScopeObject())) { + mHandler->OnLinkClickSync(mContent, mURI, + mTargetSpec.get(), mFileName, + mPostDataStream, mHeadersDataStream, + nullptr, nullptr); + } + return NS_OK; + } + +private: + RefPtr<nsDocShell> mHandler; + nsCOMPtr<nsIURI> mURI; + nsString mTargetSpec; + nsString mFileName; + nsCOMPtr<nsIInputStream> mPostDataStream; + nsCOMPtr<nsIInputStream> mHeadersDataStream; + nsCOMPtr<nsIContent> mContent; + PopupControlState mPopupState; + bool mIsTrusted; +}; + +OnLinkClickEvent::OnLinkClickEvent(nsDocShell* aHandler, + nsIContent* aContent, + nsIURI* aURI, + const char16_t* aTargetSpec, + const nsAString& aFileName, + nsIInputStream* aPostDataStream, + nsIInputStream* aHeadersDataStream, + bool aIsTrusted) + : mHandler(aHandler) + , mURI(aURI) + , mTargetSpec(aTargetSpec) + , mFileName(aFileName) + , mPostDataStream(aPostDataStream) + , mHeadersDataStream(aHeadersDataStream) + , mContent(aContent) + , mPopupState(mHandler->mScriptGlobal->GetPopupControlState()) + , mIsTrusted(aIsTrusted) +{ +} + +NS_IMETHODIMP +nsDocShell::OnLinkClick(nsIContent* aContent, + nsIURI* aURI, + const char16_t* aTargetSpec, + const nsAString& aFileName, + nsIInputStream* aPostDataStream, + nsIInputStream* aHeadersDataStream, + bool aIsTrusted) +{ + NS_ASSERTION(NS_IsMainThread(), "wrong thread"); + + if (!IsNavigationAllowed() || !IsOKToLoadURI(aURI)) { + return NS_OK; + } + + // On history navigation through Back/Forward buttons, don't execute + // automatic JavaScript redirection such as |anchorElement.click()| or + // |formElement.submit()|. + // + // XXX |formElement.submit()| bypasses this checkpoint because it calls + // nsDocShell::OnLinkClickSync(...) instead. + if (ShouldBlockLoadingForBackButton()) { + return NS_OK; + } + + if (aContent->IsEditable()) { + return NS_OK; + } + + nsresult rv = NS_ERROR_FAILURE; + nsAutoString target; + + nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner); + if (browserChrome3) { + nsCOMPtr<nsIDOMNode> linkNode = do_QueryInterface(aContent); + nsAutoString oldTarget(aTargetSpec); + rv = browserChrome3->OnBeforeLinkTraversal(oldTarget, aURI, + linkNode, mIsAppTab, target); + } + + if (NS_FAILED(rv)) { + target = aTargetSpec; + } + + nsCOMPtr<nsIRunnable> ev = + new OnLinkClickEvent(this, aContent, aURI, target.get(), aFileName, + aPostDataStream, aHeadersDataStream, aIsTrusted); + return NS_DispatchToCurrentThread(ev); +} + +NS_IMETHODIMP +nsDocShell::OnLinkClickSync(nsIContent* aContent, + nsIURI* aURI, + const char16_t* aTargetSpec, + const nsAString& aFileName, + nsIInputStream* aPostDataStream, + nsIInputStream* aHeadersDataStream, + nsIDocShell** aDocShell, + nsIRequest** aRequest) +{ + // Initialize the DocShell / Request + if (aDocShell) { + *aDocShell = nullptr; + } + if (aRequest) { + *aRequest = nullptr; + } + + if (!IsNavigationAllowed() || !IsOKToLoadURI(aURI)) { + return NS_OK; + } + + // XXX When the linking node was HTMLFormElement, it is synchronous event. + // That is, the caller of this method is not |OnLinkClickEvent::Run()| + // but |HTMLFormElement::SubmitSubmission(...)|. + if (aContent->IsHTMLElement(nsGkAtoms::form) && + ShouldBlockLoadingForBackButton()) { + return NS_OK; + } + + if (aContent->IsEditable()) { + return NS_OK; + } + + { + // defer to an external protocol handler if necessary... + nsCOMPtr<nsIExternalProtocolService> extProtService = + do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID); + if (extProtService) { + nsAutoCString scheme; + aURI->GetScheme(scheme); + if (!scheme.IsEmpty()) { + // if the URL scheme does not correspond to an exposed protocol, then we + // need to hand this link click over to the external protocol handler. + bool isExposed; + nsresult rv = + extProtService->IsExposedProtocol(scheme.get(), &isExposed); + if (NS_SUCCEEDED(rv) && !isExposed) { + return extProtService->LoadURI(aURI, this); + } + } + } + } + + uint32_t flags = INTERNAL_LOAD_FLAGS_NONE; + if (IsElementAnchor(aContent)) { + MOZ_ASSERT(aContent->IsHTMLElement()); + nsAutoString referrer; + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, referrer); + nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok(referrer); + while (tok.hasMoreTokens()) { + const nsAString& token = tok.nextToken(); + if (token.LowerCaseEqualsLiteral("noreferrer")) { + flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER | + INTERNAL_LOAD_FLAGS_NO_OPENER; + // We now have all the flags we could possibly have, so just stop. + break; + } + if (token.LowerCaseEqualsLiteral("noopener")) { + flags |= INTERNAL_LOAD_FLAGS_NO_OPENER; + } + } + } + + // Get the owner document of the link that was clicked, this will be + // the document that the link is in, or the last document that the + // link was in. From that document, we'll get the URI to use as the + // referer, since the current URI in this docshell may be a + // new document that we're in the process of loading. + nsCOMPtr<nsIDocument> refererDoc = aContent->OwnerDoc(); + NS_ENSURE_TRUE(refererDoc, NS_ERROR_UNEXPECTED); + + // Now check that the refererDoc's inner window is the current inner + // window for mScriptGlobal. If it's not, then we don't want to + // follow this link. + nsPIDOMWindowInner* refererInner = refererDoc->GetInnerWindow(); + NS_ENSURE_TRUE(refererInner, NS_ERROR_UNEXPECTED); + if (!mScriptGlobal || + mScriptGlobal->AsOuter()->GetCurrentInnerWindow() != refererInner) { + // We're no longer the current inner window + return NS_OK; + } + + nsCOMPtr<nsIURI> referer = refererDoc->GetDocumentURI(); + uint32_t refererPolicy = refererDoc->GetReferrerPolicy(); + + // get referrer attribute from clicked link and parse it + // if per element referrer is enabled, the element referrer overrules + // the document wide referrer + if (IsElementAnchor(aContent)) { + net::ReferrerPolicy refPolEnum = aContent->AsElement()->GetReferrerPolicyAsEnum(); + if (refPolEnum != net::RP_Unset) { + refererPolicy = refPolEnum; + } + } + + // referer could be null here in some odd cases, but that's ok, + // we'll just load the link w/o sending a referer in those cases. + + nsAutoString target(aTargetSpec); + + // If this is an anchor element, grab its type property to use as a hint + nsAutoString typeHint; + nsCOMPtr<nsIDOMHTMLAnchorElement> anchor(do_QueryInterface(aContent)); + if (anchor) { + anchor->GetType(typeHint); + NS_ConvertUTF16toUTF8 utf8Hint(typeHint); + nsAutoCString type, dummy; + NS_ParseRequestContentType(utf8Hint, type, dummy); + CopyUTF8toUTF16(type, typeHint); + } + + // Clone the URI now, in case a content policy or something messes + // with it under InternalLoad; we do _not_ want to change the URI + // our caller passed in. + nsCOMPtr<nsIURI> clonedURI; + aURI->Clone(getter_AddRefs(clonedURI)); + if (!clonedURI) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = InternalLoad(clonedURI, // New URI + nullptr, // Original URI + false, // LoadReplace + referer, // Referer URI + refererPolicy, // Referer policy + aContent->NodePrincipal(), // Triggering is our node's + // principal + aContent->NodePrincipal(), + flags, + target, // Window target + NS_LossyConvertUTF16toASCII(typeHint).get(), + aFileName, // Download as file + aPostDataStream, // Post data stream + aHeadersDataStream, // Headers stream + LOAD_LINK, // Load type + nullptr, // No SHEntry + true, // first party site + NullString(), // No srcdoc + this, // We are the source + nullptr, // baseURI not needed + aDocShell, // DocShell out-param + aRequest); // Request out-param + if (NS_SUCCEEDED(rv)) { + DispatchPings(this, aContent, aURI, referer, refererPolicy); + } + return rv; +} + +NS_IMETHODIMP +nsDocShell::OnOverLink(nsIContent* aContent, + nsIURI* aURI, + const char16_t* aTargetSpec) +{ + if (aContent->IsEditable()) { + return NS_OK; + } + + nsCOMPtr<nsIWebBrowserChrome2> browserChrome2 = do_GetInterface(mTreeOwner); + nsresult rv = NS_ERROR_FAILURE; + + nsCOMPtr<nsIWebBrowserChrome> browserChrome; + if (!browserChrome2) { + browserChrome = do_GetInterface(mTreeOwner); + if (!browserChrome) { + return rv; + } + } + + nsCOMPtr<nsITextToSubURI> textToSubURI = + do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + // use url origin charset to unescape the URL + nsAutoCString charset; + rv = aURI->GetOriginCharset(charset); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString spec; + rv = aURI->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString uStr; + rv = textToSubURI->UnEscapeURIForUI(charset, spec, uStr); + NS_ENSURE_SUCCESS(rv, rv); + + mozilla::net::PredictorPredict(aURI, mCurrentURI, + nsINetworkPredictor::PREDICT_LINK, + this, nullptr); + + if (browserChrome2) { + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aContent); + rv = browserChrome2->SetStatusWithContext(nsIWebBrowserChrome::STATUS_LINK, + uStr, element); + } else { + rv = browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_LINK, uStr.get()); + } + return rv; +} + +NS_IMETHODIMP +nsDocShell::OnLeaveLink() +{ + nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(mTreeOwner)); + nsresult rv = NS_ERROR_FAILURE; + + if (browserChrome) { + rv = browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_LINK, + EmptyString().get()); + } + return rv; +} + +bool +nsDocShell::ShouldBlockLoadingForBackButton() +{ + if (!(mLoadType & LOAD_CMD_HISTORY) || + EventStateManager::IsHandlingUserInput() || + !Preferences::GetBool("accessibility.blockjsredirection")) { + return false; + } + + bool canGoForward = false; + GetCanGoForward(&canGoForward); + return canGoForward; +} + +bool +nsDocShell::PluginsAllowedInCurrentDoc() +{ + bool pluginsAllowed = false; + + if (!mContentViewer) { + return false; + } + + nsIDocument* doc = mContentViewer->GetDocument(); + if (!doc) { + return false; + } + + doc->GetAllowPlugins(&pluginsAllowed); + return pluginsAllowed; +} + +//---------------------------------------------------------------------- +// Web Shell Services API + +// This functions is only called when a new charset is detected in loading a +// document. Its name should be changed to "CharsetReloadDocument" +NS_IMETHODIMP +nsDocShell::ReloadDocument(const char* aCharset, int32_t aSource) +{ + // XXX hack. keep the aCharset and aSource wait to pick it up + nsCOMPtr<nsIContentViewer> cv; + NS_ENSURE_SUCCESS(GetContentViewer(getter_AddRefs(cv)), NS_ERROR_FAILURE); + if (cv) { + int32_t hint; + cv->GetHintCharacterSetSource(&hint); + if (aSource > hint) { + nsCString charset(aCharset); + cv->SetHintCharacterSet(charset); + cv->SetHintCharacterSetSource(aSource); + if (eCharsetReloadRequested != mCharsetReloadState) { + mCharsetReloadState = eCharsetReloadRequested; + return Reload(LOAD_FLAGS_CHARSET_CHANGE); + } + } + } + // return failure if this request is not accepted due to mCharsetReloadState + return NS_ERROR_DOCSHELL_REQUEST_REJECTED; +} + +NS_IMETHODIMP +nsDocShell::StopDocumentLoad(void) +{ + if (eCharsetReloadRequested != mCharsetReloadState) { + Stop(nsIWebNavigation::STOP_ALL); + return NS_OK; + } + // return failer if this request is not accepted due to mCharsetReloadState + return NS_ERROR_DOCSHELL_REQUEST_REJECTED; +} + +NS_IMETHODIMP +nsDocShell::GetPrintPreview(nsIWebBrowserPrint** aPrintPreview) +{ + *aPrintPreview = nullptr; +#if NS_PRINT_PREVIEW + nsCOMPtr<nsIDocumentViewerPrint> print = do_QueryInterface(mContentViewer); + if (!print || !print->IsInitializedForPrintPreview()) { + Stop(nsIWebNavigation::STOP_ALL); + nsCOMPtr<nsIPrincipal> principal = nsNullPrincipal::CreateWithInheritedAttributes(this); + nsresult rv = CreateAboutBlankContentViewer(principal, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + print = do_QueryInterface(mContentViewer); + NS_ENSURE_STATE(print); + print->InitializeForPrintPreview(); + } + nsCOMPtr<nsIWebBrowserPrint> result = do_QueryInterface(print); + result.forget(aPrintPreview); + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +#ifdef DEBUG +unsigned long nsDocShell::gNumberOfDocShells = 0; +#endif + +NS_IMETHODIMP +nsDocShell::GetCanExecuteScripts(bool* aResult) +{ + *aResult = mCanExecuteScripts; + return NS_OK; +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::SetFrameType(uint32_t aFrameType) +{ + mFrameType = aFrameType; + return NS_OK; +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::GetFrameType(uint32_t* aFrameType) +{ + *aFrameType = mFrameType; + return NS_OK; +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::GetIsApp(bool* aIsApp) +{ + *aIsApp = (mFrameType == FRAME_TYPE_APP); + return NS_OK; +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::GetIsMozBrowserOrApp(bool* aIsMozBrowserOrApp) +{ + *aIsMozBrowserOrApp = (mFrameType != FRAME_TYPE_REGULAR); + return NS_OK; +} + +uint32_t +nsDocShell::GetInheritedFrameType() +{ + if (mFrameType != FRAME_TYPE_REGULAR) { + return mFrameType; + } + + nsCOMPtr<nsIDocShellTreeItem> parentAsItem; + GetSameTypeParent(getter_AddRefs(parentAsItem)); + + nsCOMPtr<nsIDocShell> parent = do_QueryInterface(parentAsItem); + if (!parent) { + return FRAME_TYPE_REGULAR; + } + + return static_cast<nsDocShell*>(parent.get())->GetInheritedFrameType(); +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::GetIsIsolatedMozBrowserElement(bool* aIsIsolatedMozBrowserElement) +{ + bool result = mFrameType == FRAME_TYPE_BROWSER && + mOriginAttributes.mInIsolatedMozBrowser; + *aIsIsolatedMozBrowserElement = result; + return NS_OK; +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::GetIsInIsolatedMozBrowserElement(bool* aIsInIsolatedMozBrowserElement) +{ + MOZ_ASSERT(!mOriginAttributes.mInIsolatedMozBrowser || + (GetInheritedFrameType() == FRAME_TYPE_BROWSER), + "Isolated mozbrowser should only be true inside browser frames"); + bool result = (GetInheritedFrameType() == FRAME_TYPE_BROWSER) && + mOriginAttributes.mInIsolatedMozBrowser; + *aIsInIsolatedMozBrowserElement = result; + return NS_OK; +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::GetIsInMozBrowserOrApp(bool* aIsInMozBrowserOrApp) +{ + *aIsInMozBrowserOrApp = (GetInheritedFrameType() != FRAME_TYPE_REGULAR); + return NS_OK; +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::GetIsTopLevelContentDocShell(bool* aIsTopLevelContentDocShell) +{ + *aIsTopLevelContentDocShell = false; + + if (mItemType == typeContent) { + nsCOMPtr<nsIDocShellTreeItem> root; + GetSameTypeRootTreeItem(getter_AddRefs(root)); + *aIsTopLevelContentDocShell = root.get() == static_cast<nsIDocShellTreeItem*>(this); + } + + return NS_OK; +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::GetAppId(uint32_t* aAppId) +{ + if (mOriginAttributes.mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID) { + *aAppId = mOriginAttributes.mAppId; + return NS_OK; + } + + nsCOMPtr<nsIDocShell> parent; + GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent)); + + if (!parent) { + *aAppId = nsIScriptSecurityManager::NO_APP_ID; + return NS_OK; + } + + return parent->GetAppId(aAppId); +} + +// Implements nsILoadContext.originAttributes +NS_IMETHODIMP +nsDocShell::GetOriginAttributes(JS::MutableHandle<JS::Value> aVal) +{ + JSContext* cx = nsContentUtils::GetCurrentJSContext(); + MOZ_ASSERT(cx); + + return GetOriginAttributes(cx, aVal); +} + +// Implements nsIDocShell.GetOriginAttributes() +NS_IMETHODIMP +nsDocShell::GetOriginAttributes(JSContext* aCx, + JS::MutableHandle<JS::Value> aVal) +{ + bool ok = ToJSValue(aCx, mOriginAttributes, aVal); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + return NS_OK; +} + +bool +nsDocShell::CanSetOriginAttributes() +{ + MOZ_ASSERT(mChildList.IsEmpty()); + if (!mChildList.IsEmpty()) { + return false; + } + + // TODO: Bug 1273058 - mContentViewer should be null when setting origin + // attributes. + if (mContentViewer) { + nsIDocument* doc = mContentViewer->GetDocument(); + if (doc) { + nsIURI* uri = doc->GetDocumentURI(); + if (!uri) { + return false; + } + nsCString uriSpec = uri->GetSpecOrDefault(); + MOZ_ASSERT(uriSpec.EqualsLiteral("about:blank")); + if (!uriSpec.EqualsLiteral("about:blank")) { + return false; + } + } + } + + return true; +} + +nsresult +nsDocShell::SetOriginAttributes(const DocShellOriginAttributes& aAttrs) +{ + if (!CanSetOriginAttributes()) { + return NS_ERROR_FAILURE; + } + + AssertOriginAttributesMatchPrivateBrowsing(); + mOriginAttributes = aAttrs; + + bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0; + // Chrome docshell can not contain OriginAttributes.mPrivateBrowsingId + if (mItemType == typeChrome && isPrivate) { + mOriginAttributes.mPrivateBrowsingId = 0; + } + + SetPrivateBrowsing(isPrivate); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetOriginAttributesBeforeLoading(JS::Handle<JS::Value> aOriginAttributes) +{ + if (!aOriginAttributes.isObject()) { + return NS_ERROR_INVALID_ARG; + } + + AutoJSAPI jsapi; + if (!jsapi.Init(&aOriginAttributes.toObject())) { + return NS_ERROR_UNEXPECTED; + } + + JSContext* cx = jsapi.cx(); + if (NS_WARN_IF(!cx)) { + return NS_ERROR_FAILURE; + } + + DocShellOriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(cx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + return SetOriginAttributes(attrs); +} + +NS_IMETHODIMP +nsDocShell::SetOriginAttributes(JS::Handle<JS::Value> aOriginAttributes, + JSContext* aCx) +{ + DocShellOriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + return SetOriginAttributes(attrs); +} + +NS_IMETHODIMP +nsDocShell::GetAppManifestURL(nsAString& aAppManifestURL) +{ + uint32_t appId = nsIDocShell::GetAppId(); + if (appId != nsIScriptSecurityManager::NO_APP_ID && + appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) { + nsCOMPtr<nsIAppsService> appsService = + do_GetService(APPS_SERVICE_CONTRACTID); + NS_ASSERTION(appsService, "No AppsService available"); + appsService->GetManifestURLByLocalId(appId, aAppManifestURL); + } else { + aAppManifestURL.SetLength(0); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAsyncPanZoomEnabled(bool* aOut) +{ + if (nsIPresShell* presShell = GetPresShell()) { + *aOut = presShell->AsyncPanZoomEnabled(); + return NS_OK; + } + + // If we don't have a presShell, fall back to the default platform value of + // whether or not APZ is enabled. + *aOut = gfxPlatform::AsyncPanZoomEnabled(); + return NS_OK; +} + +bool +nsDocShell::HasUnloadedParent() +{ + RefPtr<nsDocShell> parent = GetParentDocshell(); + while (parent) { + bool inUnload = false; + parent->GetIsInUnload(&inUnload); + if (inUnload) { + return true; + } + parent = parent->GetParentDocshell(); + } + return false; +} + +bool +nsDocShell::IsInvisible() +{ + return mInvisible; +} + +void +nsDocShell::SetInvisible(bool aInvisible) +{ + mInvisible = aInvisible; +} + +void +nsDocShell::SetOpener(nsITabParent* aOpener) +{ + mOpener = do_GetWeakReference(aOpener); +} + +nsITabParent* +nsDocShell::GetOpener() +{ + nsCOMPtr<nsITabParent> opener(do_QueryReferent(mOpener)); + return opener; +} + +// The caller owns |aAsyncCause| here. +void +nsDocShell::NotifyJSRunToCompletionStart(const char* aReason, + const char16_t* aFunctionName, + const char16_t* aFilename, + const uint32_t aLineNumber, + JS::Handle<JS::Value> aAsyncStack, + const char* aAsyncCause) +{ + // If first start, mark interval start. + if (mJSRunToCompletionDepth == 0) { + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (timelines && timelines->HasConsumer(this)) { + timelines->AddMarkerForDocShell(this, Move( + mozilla::MakeUnique<JavascriptTimelineMarker>( + aReason, aFunctionName, aFilename, aLineNumber, MarkerTracingType::START, + aAsyncStack, aAsyncCause))); + } + } + + mJSRunToCompletionDepth++; +} + +void +nsDocShell::NotifyJSRunToCompletionStop() +{ + mJSRunToCompletionDepth--; + + // If last stop, mark interval end. + if (mJSRunToCompletionDepth == 0) { + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (timelines && timelines->HasConsumer(this)) { + timelines->AddMarkerForDocShell(this, "Javascript", MarkerTracingType::END); + } + } +} + +void +nsDocShell::MaybeNotifyKeywordSearchLoading(const nsString& aProvider, + const nsString& aKeyword) +{ + if (aProvider.IsEmpty()) { + return; + } + + if (XRE_IsContentProcess()) { + dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); + if (contentChild) { + contentChild->SendNotifyKeywordSearchLoading(aProvider, aKeyword); + } + return; + } + +#ifdef MOZ_TOOLKIT_SEARCH + nsCOMPtr<nsIBrowserSearchService> searchSvc = + do_GetService("@mozilla.org/browser/search-service;1"); + if (searchSvc) { + nsCOMPtr<nsISearchEngine> searchEngine; + searchSvc->GetEngineByName(aProvider, getter_AddRefs(searchEngine)); + if (searchEngine) { + nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService(); + if (obsSvc) { + // Note that "keyword-search" refers to a search via the url + // bar, not a bookmarks keyword search. + obsSvc->NotifyObservers(searchEngine, "keyword-search", aKeyword.get()); + } + } + } +#endif +} + +NS_IMETHODIMP +nsDocShell::ShouldPrepareForIntercept(nsIURI* aURI, bool aIsNonSubresourceRequest, + bool* aShouldIntercept) +{ + *aShouldIntercept = false; + // No in private browsing + if (UsePrivateBrowsing()) { + return NS_OK; + } + + if (mSandboxFlags) { + // If we're sandboxed, don't intercept. + return NS_OK; + } + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (!swm) { + return NS_OK; + } + + nsresult result; + nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = + do_GetService(THIRDPARTYUTIL_CONTRACTID, &result); + NS_ENSURE_SUCCESS(result, result); + + if (mCurrentURI && + nsContentUtils::CookiesBehavior() == nsICookieService::BEHAVIOR_REJECT_FOREIGN) { + nsAutoCString uriSpec; + if (!(mCurrentURI->GetSpecOrDefault().EqualsLiteral("about:blank"))) { + // Reject the interception of third-party iframes if the cookie behaviour + // is set to reject all third-party cookies (1). In case that this pref + // is not set or can't be read, we default to allow all cookies (0) as + // this is the default value in all.js. + bool isThirdPartyURI = true; + result = thirdPartyUtil->IsThirdPartyURI(mCurrentURI, aURI, + &isThirdPartyURI); + if (NS_FAILED(result)) { + return result; + } + + if (isThirdPartyURI) { + return NS_OK; + } + } + } + + if (aIsNonSubresourceRequest) { + PrincipalOriginAttributes attrs; + attrs.InheritFromDocShellToDoc(mOriginAttributes, aURI); + nsCOMPtr<nsIPrincipal> principal = + BasePrincipal::CreateCodebasePrincipal(aURI, attrs); + *aShouldIntercept = swm->IsAvailable(principal, aURI); + return NS_OK; + } + + nsCOMPtr<nsIDocument> doc = GetDocument(); + if (!doc) { + return NS_ERROR_NOT_AVAILABLE; + } + + ErrorResult rv; + *aShouldIntercept = swm->IsControlled(doc, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::ChannelIntercepted(nsIInterceptedChannel* aChannel) +{ + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (!swm) { + aChannel->Cancel(NS_ERROR_INTERCEPTION_FAILED); + return NS_OK; + } + + nsCOMPtr<nsIChannel> channel; + nsresult rv = aChannel->GetChannel(getter_AddRefs(channel)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocument> doc; + + bool isSubresourceLoad = !nsContentUtils::IsNonSubresourceRequest(channel); + if (isSubresourceLoad) { + doc = GetDocument(); + if (!doc) { + return NS_ERROR_NOT_AVAILABLE; + } + } else { + // For top-level navigations, save a document ID which will be passed to + // the FetchEvent as the clientId later on. + rv = nsIDocument::GenerateDocumentId(mInterceptedDocumentId); + NS_ENSURE_SUCCESS(rv, rv); + } + + bool isReload = mLoadType & LOAD_CMD_RELOAD; + + nsCOMPtr<nsIURI> uri; + rv = channel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + PrincipalOriginAttributes attrs; + attrs.InheritFromDocShellToDoc(mOriginAttributes, uri); + + ErrorResult error; + swm->DispatchFetchEvent(attrs, doc, mInterceptedDocumentId, aChannel, + isReload, isSubresourceLoad, error); + if (NS_WARN_IF(error.Failed())) { + return error.StealNSResult(); + } + + return NS_OK; +} + +bool +nsDocShell::InFrameSwap() +{ + RefPtr<nsDocShell> shell = this; + do { + if (shell->mInFrameSwap) { + return true; + } + shell = shell->GetParentDocshell(); + } while (shell); + return false; +} + +NS_IMETHODIMP +nsDocShell::IssueWarning(uint32_t aWarning, bool aAsError) +{ + if (mContentViewer) { + nsCOMPtr<nsIDocument> doc = mContentViewer->GetDocument(); + if (doc) { + doc->WarnOnceAbout(nsIDocument::DeprecatedOperations(aWarning), aAsError); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetEditingSession(nsIEditingSession** aEditSession) +{ + if (!NS_SUCCEEDED(EnsureEditorData())) { + return NS_ERROR_FAILURE; + } + + mEditorData->GetEditingSession(aEditSession); + return *aEditSession ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::GetScriptableTabChild(nsITabChild** aTabChild) +{ + *aTabChild = GetTabChild().take(); + return *aTabChild ? NS_OK : NS_ERROR_FAILURE; +} + +already_AddRefed<nsITabChild> +nsDocShell::GetTabChild() +{ + nsCOMPtr<nsIDocShellTreeOwner> owner(mTreeOwner); + nsCOMPtr<nsITabChild> tc = do_GetInterface(owner); + return tc.forget(); +} + +nsICommandManager* +nsDocShell::GetCommandManager() +{ + NS_ENSURE_SUCCESS(EnsureCommandHandler(), nullptr); + return mCommandManager; +} + +NS_IMETHODIMP +nsDocShell::GetProcessLockReason(uint32_t* aReason) +{ + MOZ_ASSERT(aReason); + + nsPIDOMWindowOuter* outer = GetWindow(); + MOZ_ASSERT(outer); + + // Check if we are a toplevel window + if (outer->GetScriptableParentOrNull()) { + *aReason = PROCESS_LOCK_IFRAME; + return NS_OK; + } + + // If we have any other toplevel windows in our tab group, then we cannot + // perform the navigation. + nsTArray<nsPIDOMWindowOuter*> toplevelWindows = + outer->TabGroup()->GetTopLevelWindows(); + if (toplevelWindows.Length() > 1) { + *aReason = PROCESS_LOCK_RELATED_CONTEXTS; + return NS_OK; + } + MOZ_ASSERT(toplevelWindows.Length() == 1); + MOZ_ASSERT(toplevelWindows[0] == outer); + + // If we aren't in a content process, we cannot perform a cross-process load. + if (!XRE_IsContentProcess()) { + *aReason = PROCESS_LOCK_NON_CONTENT; + return NS_OK; + } + + *aReason = PROCESS_LOCK_NONE; + return NS_OK; +} diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h new file mode 100644 index 000000000..3ca9e0b34 --- /dev/null +++ b/docshell/base/nsDocShell.h @@ -0,0 +1,1083 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDocShell_h__ +#define nsDocShell_h__ + +#include "nsITimer.h" +#include "nsContentPolicyUtils.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeItem.h" +#include "nsIBaseWindow.h" +#include "nsINetworkInterceptController.h" +#include "nsIScrollable.h" +#include "nsITextScroll.h" +#include "nsIContentViewerContainer.h" +#include "nsIDOMStorageManager.h" +#include "nsDocLoader.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/WeakPtr.h" +#include "mozilla/TimeStamp.h" +#include "GeckoProfiler.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "mozilla/LinkedList.h" +#include "jsapi.h" + +// Helper Classes +#include "nsCOMPtr.h" +#include "nsPoint.h" // mCurrent/mDefaultScrollbarPreferences +#include "nsString.h" +#include "nsAutoPtr.h" +#include "nsThreadUtils.h" +#include "nsContentUtils.h" +#include "timeline/ObservedDocShell.h" +#include "timeline/TimelineConsumers.h" +#include "timeline/TimelineMarker.h" + +// Threshold value in ms for META refresh based redirects +#define REFRESH_REDIRECT_TIMER 15000 + +// Interfaces Needed +#include "nsIDocCharset.h" +#include "nsIInterfaceRequestor.h" +#include "nsIRefreshURI.h" +#include "nsIWebNavigation.h" +#include "nsIWebPageDescriptor.h" +#include "nsIWebProgressListener.h" +#include "nsIDocShellLoadInfo.h" +#include "nsIAuthPromptProvider.h" +#include "nsILoadContext.h" +#include "nsIWebShellServices.h" +#include "nsILinkHandler.h" +#include "nsIClipboardCommands.h" +#include "nsITabParent.h" +#include "nsCRT.h" +#include "prtime.h" +#include "nsRect.h" +#include "Units.h" +#include "nsIDeprecationWarner.h" + +namespace mozilla { +namespace dom { +class EventTarget; +typedef uint32_t ScreenOrientationInternal; +} // namespace dom +} // namespace mozilla + +class nsDocShell; +class nsDOMNavigationTiming; +class nsGlobalWindow; +class nsIController; +class nsIScrollableFrame; +class OnLinkClickEvent; +class nsDSURIContentListener; +class nsDocShellEditorData; +class nsIClipboardDragDropHookList; +class nsICommandManager; +class nsIContentViewer; +class nsIDocument; +class nsIDOMNode; +class nsIDocShellTreeOwner; +class nsIGlobalHistory2; +class nsIHttpChannel; +class nsIMutableArray; +class nsIPrompt; +class nsISHistory; +class nsISecureBrowserUI; +class nsIStringBundle; +class nsIURIFixup; +class nsIURILoader; +class nsIWebBrowserFind; +class nsIWidget; + +/* internally used ViewMode types */ +enum ViewMode +{ + viewNormal = 0x0, + viewSource = 0x1 +}; + +class nsRefreshTimer : public nsITimerCallback +{ +public: + nsRefreshTimer(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + + int32_t GetDelay() { return mDelay ;} + + RefPtr<nsDocShell> mDocShell; + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsIPrincipal> mPrincipal; + int32_t mDelay; + bool mRepeat; + bool mMetaRefresh; + +protected: + virtual ~nsRefreshTimer(); +}; + +enum eCharsetReloadState +{ + eCharsetReloadInit, + eCharsetReloadRequested, + eCharsetReloadStopOrigional +}; + +class nsDocShell final + : public nsDocLoader + , public nsIDocShell + , public nsIWebNavigation + , public nsIBaseWindow + , public nsIScrollable + , public nsITextScroll + , public nsIDocCharset + , public nsIContentViewerContainer + , public nsIRefreshURI + , public nsIWebProgressListener + , public nsIWebPageDescriptor + , public nsIAuthPromptProvider + , public nsILoadContext + , public nsIWebShellServices + , public nsILinkHandler + , public nsIClipboardCommands + , public nsIDOMStorageManager + , public nsINetworkInterceptController + , public nsIDeprecationWarner + , public mozilla::SupportsWeakPtr<nsDocShell> +{ + friend class nsDSURIContentListener; + +public: + MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsDocShell) + + nsDocShell(); + + NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW + + virtual nsresult Init() override; + + NS_DECL_ISUPPORTS_INHERITED + + NS_DECL_NSIDOCSHELL + NS_DECL_NSIDOCSHELLTREEITEM + NS_DECL_NSIWEBNAVIGATION + NS_DECL_NSIBASEWINDOW + NS_DECL_NSISCROLLABLE + NS_DECL_NSITEXTSCROLL + NS_DECL_NSIDOCCHARSET + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_NSIREFRESHURI + NS_DECL_NSICONTENTVIEWERCONTAINER + NS_DECL_NSIWEBPAGEDESCRIPTOR + NS_DECL_NSIAUTHPROMPTPROVIDER + NS_DECL_NSICLIPBOARDCOMMANDS + NS_DECL_NSIWEBSHELLSERVICES + NS_DECL_NSINETWORKINTERCEPTCONTROLLER + NS_DECL_NSIDEPRECATIONWARNER + NS_FORWARD_SAFE_NSIDOMSTORAGEMANAGER(TopSessionStorageManager()) + + NS_IMETHOD Stop() override + { + // Need this here because otherwise nsIWebNavigation::Stop + // overrides the docloader's Stop() + return nsDocLoader::Stop(); + } + + // Need to implement (and forward) nsISecurityEventSink, because + // nsIWebProgressListener has methods with identical names... + NS_FORWARD_NSISECURITYEVENTSINK(nsDocLoader::) + + // nsILinkHandler + NS_IMETHOD OnLinkClick(nsIContent* aContent, + nsIURI* aURI, + const char16_t* aTargetSpec, + const nsAString& aFileName, + nsIInputStream* aPostDataStream, + nsIInputStream* aHeadersDataStream, + bool aIsTrusted) override; + NS_IMETHOD OnLinkClickSync(nsIContent* aContent, + nsIURI* aURI, + const char16_t* aTargetSpec, + const nsAString& aFileName, + nsIInputStream* aPostDataStream = 0, + nsIInputStream* aHeadersDataStream = 0, + nsIDocShell** aDocShell = 0, + nsIRequest** aRequest = 0) override; + NS_IMETHOD OnOverLink(nsIContent* aContent, + nsIURI* aURI, + const char16_t* aTargetSpec) override; + NS_IMETHOD OnLeaveLink() override; + + nsDocShellInfoLoadType ConvertLoadTypeToDocShellLoadInfo(uint32_t aLoadType); + uint32_t ConvertDocShellLoadInfoToLoadType( + nsDocShellInfoLoadType aDocShellLoadType); + + // Don't use NS_DECL_NSILOADCONTEXT because some of nsILoadContext's methods + // are shared with nsIDocShell (appID, etc.) and can't be declared twice. + NS_IMETHOD GetAssociatedWindow(mozIDOMWindowProxy**) override; + NS_IMETHOD GetTopWindow(mozIDOMWindowProxy**) override; + NS_IMETHOD GetTopFrameElement(nsIDOMElement**) override; + NS_IMETHOD GetNestedFrameId(uint64_t*) override; + NS_IMETHOD GetIsContent(bool*) override; + NS_IMETHOD GetUsePrivateBrowsing(bool*) override; + NS_IMETHOD SetUsePrivateBrowsing(bool) override; + NS_IMETHOD SetPrivateBrowsing(bool) override; + NS_IMETHOD GetUseRemoteTabs(bool*) override; + NS_IMETHOD SetRemoteTabs(bool) override; + NS_IMETHOD GetOriginAttributes(JS::MutableHandle<JS::Value>) override; + NS_IMETHOD IsTrackingProtectionOn(bool*) override; + + // Restores a cached presentation from history (mLSHE). + // This method swaps out the content viewer and simulates loads for + // subframes. It then simulates the completion of the toplevel load. + nsresult RestoreFromHistory(); + + // Perform a URI load from a refresh timer. This is just like the + // ForceRefreshURI method on nsIRefreshURI, but makes sure to take + // the timer involved out of mRefreshURIList if it's there. + // aTimer must not be null. + nsresult ForceRefreshURIFromTimer(nsIURI* aURI, int32_t aDelay, + bool aMetaRefresh, nsITimer* aTimer, + nsIPrincipal* aPrincipal); + + friend class OnLinkClickEvent; + + // We need dummy OnLocationChange in some cases to update the UI without + // updating security info. + void FireDummyOnLocationChange() + { + FireOnLocationChange(this, nullptr, mCurrentURI, + LOCATION_CHANGE_SAME_DOCUMENT); + } + + nsresult HistoryTransactionRemoved(int32_t aIndex); + + // Notify Scroll observers when an async panning/zooming transform + // has started being applied + void NotifyAsyncPanZoomStarted(); + // Notify Scroll observers when an async panning/zooming transform + // is no longer applied + void NotifyAsyncPanZoomStopped(); + + void SetInFrameSwap(bool aInSwap) + { + mInFrameSwap = aInSwap; + } + bool InFrameSwap(); + +private: + bool CanSetOriginAttributes(); + +public: + const mozilla::DocShellOriginAttributes& + GetOriginAttributes() + { + return mOriginAttributes; + } + + nsresult SetOriginAttributes(const mozilla::DocShellOriginAttributes& aAttrs); + + void GetInterceptedDocumentId(nsAString& aId) + { + aId = mInterceptedDocumentId; + } + +private: + // An observed docshell wrapper is created when recording markers is enabled. + mozilla::UniquePtr<mozilla::ObservedDocShell> mObserved; + + // It is necessary to allow adding a timeline marker wherever a docshell + // instance is available. This operation happens frequently and needs to + // be very fast, so instead of using a Map or having to search for some + // docshell-specific markers storage, a pointer to an `ObservedDocShell` is + // is stored on docshells directly. + friend void mozilla::TimelineConsumers::AddConsumer(nsDocShell*); + friend void mozilla::TimelineConsumers::RemoveConsumer(nsDocShell*); + friend void mozilla::TimelineConsumers::AddMarkerForDocShell( + nsDocShell*, const char*, MarkerTracingType, MarkerStackRequest); + friend void mozilla::TimelineConsumers::AddMarkerForDocShell( + nsDocShell*, const char*, const TimeStamp&, MarkerTracingType, + MarkerStackRequest); + friend void mozilla::TimelineConsumers::AddMarkerForDocShell( + nsDocShell*, UniquePtr<AbstractTimelineMarker>&&); + friend void mozilla::TimelineConsumers::PopMarkers(nsDocShell*, + JSContext*, nsTArray<dom::ProfileTimelineMarker>&); + +public: + // Tell the favicon service that aNewURI has the same favicon as aOldURI. + static void CopyFavicon(nsIURI* aOldURI, + nsIURI* aNewURI, + nsIPrincipal* aLoadingPrincipal, + bool aInPrivateBrowsing); + + static nsDocShell* Cast(nsIDocShell* aDocShell) + { + return static_cast<nsDocShell*>(aDocShell); + } + +protected: + virtual ~nsDocShell(); + virtual void DestroyChildren() override; + + // Content Viewer Management + nsresult EnsureContentViewer(); + // aPrincipal can be passed in if the caller wants. If null is + // passed in, the about:blank principal will end up being used. + nsresult CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal, + nsIURI* aBaseURI, + bool aTryToSaveOldPresentation = true); + nsresult CreateContentViewer(const nsACString& aContentType, + nsIRequest* aRequest, + nsIStreamListener** aContentHandler); + nsresult NewContentViewerObj(const nsACString& aContentType, + nsIRequest* aRequest, nsILoadGroup* aLoadGroup, + nsIStreamListener** aContentHandler, + nsIContentViewer** aViewer); + nsresult SetupNewViewer(nsIContentViewer* aNewViewer); + + void SetupReferrerFromChannel(nsIChannel* aChannel); + + nsresult GetEldestPresContext(nsPresContext** aPresContext); + + // Get the principal that we'll set on the channel if we're inheriting. If + // aConsiderCurrentDocument is true, we try to use the current document if + // at all possible. If that fails, we fall back on the parent document. + // If that fails too, we force creation of a content viewer and use the + // resulting principal. If aConsiderCurrentDocument is false, we just look + // at the parent. + nsIPrincipal* GetInheritedPrincipal(bool aConsiderCurrentDocument); + + // Actually open a channel and perform a URI load. Callers need to pass a + // non-null aTriggeringPrincipal which initiated the URI load. Please note + // that aTriggeringPrincipal will be used for performing security checks. + // If the argument aURI is provided by the web, then please do not pass a + // SystemPrincipal as the triggeringPrincipal. If principalToInherit is + // null, then no inheritance of any sort will happen and the load will + // get a principal based on the URI being loaded. + // If aSrcdoc is not void, the load will be considered as a srcdoc load, + // and the contents of aSrcdoc will be loaded instead of aURI. + // aOriginalURI will be set as the originalURI on the channel that does the + // load. If aOriginalURI is null, aURI will be set as the originalURI. + // If aLoadReplace is true, LOAD_REPLACE flag will be set to the nsIChannel. + nsresult DoURILoad(nsIURI* aURI, + nsIURI* aOriginalURI, + bool aLoadReplace, + nsIURI* aReferrer, + bool aSendReferrer, + uint32_t aReferrerPolicy, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + const char* aTypeHint, + const nsAString& aFileName, + nsIInputStream* aPostData, + nsIInputStream* aHeadersData, + bool aFirstParty, + nsIDocShell** aDocShell, + nsIRequest** aRequest, + bool aIsNewWindowTarget, + bool aBypassClassifier, + bool aForceAllowCookies, + const nsAString& aSrcdoc, + nsIURI* aBaseURI, + nsContentPolicyType aContentPolicyType); + nsresult AddHeadersToChannel(nsIInputStream* aHeadersData, + nsIChannel* aChannel); + nsresult DoChannelLoad(nsIChannel* aChannel, + nsIURILoader* aURILoader, + bool aBypassClassifier); + + nsresult ScrollToAnchor(bool aCurHasRef, + bool aNewHasRef, + nsACString& aNewHash, + uint32_t aLoadType); + + // Returns 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 + // FireOnLocationChange is called. + // In all other cases false is returned. + bool OnLoadingSite(nsIChannel* aChannel, + bool aFireOnLocationChange, + bool aAddToGlobalHistory = true); + + // Returns 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 + // FireOnLocationChange is called. + // In all other cases false is returned. + // Either aChannel or aTriggeringPrincipal must be null. If aChannel is + // present, the owner should be gotten from it. + // If OnNewURI calls AddToSessionHistory, it will pass its + // aCloneSHChildren argument as aCloneChildren. + bool OnNewURI(nsIURI* aURI, nsIChannel* aChannel, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + uint32_t aLoadType, + bool aFireOnLocationChange, + bool aAddToGlobalHistory, + bool aCloneSHChildren); + + void SetReferrerURI(nsIURI* aURI); + void SetReferrerPolicy(uint32_t aReferrerPolicy); + + // Session History + bool ShouldAddToSessionHistory(nsIURI* aURI); + // Either aChannel or aOwner must be null. If aChannel is + // present, the owner should be gotten from it. + // If aCloneChildren is true, then our current session history's + // children will be cloned onto the new entry. This should be + // used when we aren't actually changing the document while adding + // the new session history entry. + nsresult AddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + bool aCloneChildren, + nsISHEntry** aNewEntry); + nsresult AddChildSHEntryToParent(nsISHEntry* aNewEntry, int32_t aChildOffset, + bool aCloneChildren); + + nsresult AddChildSHEntryInternal(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry, + int32_t aChildOffset, uint32_t aLoadType, + bool aCloneChildren); + + nsresult LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType); + nsresult PersistLayoutHistoryState(); + + // Clone a session history tree for subframe navigation. + // The tree rooted at |aSrcEntry| will be cloned into |aDestEntry|, except + // for the entry with id |aCloneID|, which will be replaced with + // |aReplaceEntry|. |aSrcShell| is a (possibly null) docshell which + // corresponds to |aSrcEntry| via its mLSHE or mOHE pointers, and will + // have that pointer updated to point to the cloned history entry. + // If aCloneChildren is true then the children of the entry with id + // |aCloneID| will be cloned into |aReplaceEntry|. + static nsresult CloneAndReplace(nsISHEntry* aSrcEntry, + nsDocShell* aSrcShell, + uint32_t aCloneID, + nsISHEntry* aReplaceEntry, + bool aCloneChildren, + nsISHEntry** aDestEntry); + + // Child-walking callback for CloneAndReplace + static nsresult CloneAndReplaceChild(nsISHEntry* aEntry, nsDocShell* aShell, + int32_t aChildIndex, void* aData); + + nsresult GetRootSessionHistory(nsISHistory** aReturn); + nsresult GetHttpChannel(nsIChannel* aChannel, nsIHttpChannel** aReturn); + bool ShouldDiscardLayoutState(nsIHttpChannel* aChannel); + + // Determine whether this docshell corresponds to the given history entry, + // via having a pointer to it in mOSHE or mLSHE. + bool HasHistoryEntry(nsISHEntry* aEntry) const + { + return aEntry && (aEntry == mOSHE || aEntry == mLSHE); + } + + // Update any pointers (mOSHE or mLSHE) to aOldEntry to point to aNewEntry + void SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry); + + // Call this method to swap in a new history entry to m[OL]SHE, rather than + // setting it directly. This completes the navigation in all docshells + // in the case of a subframe navigation. + void SetHistoryEntry(nsCOMPtr<nsISHEntry>* aPtr, nsISHEntry* aEntry); + + // Child-walking callback for SetHistoryEntry + static nsresult SetChildHistoryEntry(nsISHEntry* aEntry, nsDocShell* aShell, + int32_t aEntryIndex, void* aData); + + // Callback prototype for WalkHistoryEntries. + // aEntry is the child history entry, aShell is its corresponding docshell, + // aChildIndex is the child's index in its parent entry, and aData is + // the opaque pointer passed to WalkHistoryEntries. + typedef nsresult(*WalkHistoryEntriesFunc)(nsISHEntry* aEntry, + nsDocShell* aShell, + int32_t aChildIndex, + void* aData); + + // For each child of aRootEntry, find the corresponding docshell which is + // a child of aRootShell, and call aCallback. The opaque pointer aData + // is passed to the callback. + static nsresult WalkHistoryEntries(nsISHEntry* aRootEntry, + nsDocShell* aRootShell, + WalkHistoryEntriesFunc aCallback, + void* aData); + + // overridden from nsDocLoader, this provides more information than the + // normal OnStateChange with flags STATE_REDIRECTING + virtual void OnRedirectStateChange(nsIChannel* aOldChannel, + nsIChannel* aNewChannel, + uint32_t aRedirectFlags, + uint32_t aStateFlags) override; + + /** + * Helper function that determines if channel is an HTTP POST. + * + * @param aChannel + * The channel to test + * + * @return True iff channel is an HTTP post. + */ + bool ChannelIsPost(nsIChannel* aChannel); + + /** + * Helper function that finds the last URI and its transition flags for a + * channel. + * + * This method first checks the channel's property bag to see if previous + * info has been saved. If not, it gives back the referrer of the channel. + * + * @param aChannel + * The channel we are transitioning to + * @param aURI + * Output parameter with the previous URI, not addref'd + * @param aChannelRedirectFlags + * If a redirect, output parameter with the previous redirect flags + * from nsIChannelEventSink + */ + void ExtractLastVisit(nsIChannel* aChannel, + nsIURI** aURI, + uint32_t* aChannelRedirectFlags); + + /** + * Helper function that caches a URI and a transition for saving later. + * + * @param aChannel + * Channel that will have these properties saved + * @param aURI + * The URI to save for later + * @param aChannelRedirectFlags + * The nsIChannelEventSink redirect flags to save for later + */ + void SaveLastVisit(nsIChannel* aChannel, + nsIURI* aURI, + uint32_t aChannelRedirectFlags); + + /** + * Helper function for adding a URI visit using IHistory. If IHistory is + * not available, the method tries nsIGlobalHistory2. + * + * The IHistory API maintains chains of visits, tracking both HTTP referrers + * and redirects for a user session. VisitURI requires the current URI and + * the previous URI in the chain. + * + * Visits can be saved either during a redirect or when the request has + * reached its final destination. The previous URI in the visit may be + * from another redirect or it may be the referrer. + * + * @pre aURI is not null. + * + * @param aURI + * The URI that was just visited + * @param aReferrerURI + * The referrer URI of this request + * @param aPreviousURI + * The previous URI of this visit (may be the same as aReferrerURI) + * @param aChannelRedirectFlags + * For redirects, the redirect flags from nsIChannelEventSink + * (0 otherwise) + * @param aResponseStatus + * For HTTP channels, the response code (0 otherwise). + */ + void AddURIVisit(nsIURI* aURI, + nsIURI* aReferrerURI, + nsIURI* aPreviousURI, + uint32_t aChannelRedirectFlags, + uint32_t aResponseStatus = 0); + + // Helper Routines + nsresult ConfirmRepost(bool* aRepost); + NS_IMETHOD GetPromptAndStringBundle(nsIPrompt** aPrompt, + nsIStringBundle** aStringBundle); + NS_IMETHOD GetChildOffset(nsIDOMNode* aChild, nsIDOMNode* aParent, + int32_t* aOffset); + nsIScrollableFrame* GetRootScrollFrame(); + NS_IMETHOD EnsureScriptEnvironment(); + NS_IMETHOD EnsureEditorData(); + nsresult EnsureTransferableHookData(); + NS_IMETHOD EnsureFind(); + nsresult RefreshURIFromQueue(); + NS_IMETHOD LoadErrorPage(nsIURI* aURI, const char16_t* aURL, + const char* aErrorPage, + const char16_t* aErrorType, + const char16_t* aDescription, + const char* aCSSClass, + nsIChannel* aFailedChannel); + bool IsPrintingOrPP(bool aDisplayErrorDialog = true); + bool IsNavigationAllowed(bool aDisplayPrintErrorDialog = true, + bool aCheckIfUnloadFired = true); + + nsresult SetBaseUrlForWyciwyg(nsIContentViewer* aContentViewer); + + static inline uint32_t PRTimeToSeconds(PRTime aTimeUsec) + { + PRTime usecPerSec = PR_USEC_PER_SEC; + return uint32_t(aTimeUsec /= usecPerSec); + } + + inline bool UseErrorPages() + { + return (mObserveErrorPages ? sUseErrorPages : mUseErrorPages); + } + + bool IsFrame(); + + // + // Helper method that is called when a new document (including any + // sub-documents - ie. frames) has been completely loaded. + // + virtual nsresult EndPageLoad(nsIWebProgress* aProgress, + nsIChannel* aChannel, + nsresult aResult); + + // Sets the current document's current state object to the given SHEntry's + // state object. The current state object is eventually given to the page + // in the PopState event. + nsresult SetDocCurrentStateObj(nsISHEntry* aShEntry); + + nsresult CheckLoadingPermissions(); + + // Security checks to prevent frameset spoofing. See comments at + // implementation sites. + static bool CanAccessItem(nsIDocShellTreeItem* aTargetItem, + nsIDocShellTreeItem* aAccessingItem, + bool aConsiderOpener = true); + static bool ValidateOrigin(nsIDocShellTreeItem* aOriginTreeItem, + nsIDocShellTreeItem* aTargetTreeItem); + + // Returns 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 + // FireOnLocationChange is called. + // In all other cases false is returned. + bool SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest, + bool aFireOnLocationChange, + uint32_t aLocationFlags); + + // The following methods deal with saving and restoring content viewers + // in session history. + + // mContentViewer points to the current content viewer associated with + // this docshell. When loading a new document, the content viewer is + // either destroyed or stored into a session history entry. To make sure + // that destruction happens in a controlled fashion, a given content viewer + // is always owned in exactly one of these ways: + // 1) The content viewer is active and owned by a docshell's + // mContentViewer. + // 2) The content viewer is still being displayed while we begin loading + // a new document. The content viewer is owned by the _new_ + // content viewer's mPreviousViewer, and has a pointer to the + // nsISHEntry where it will eventually be stored. The content viewer + // has been close()d by the docshell, which detaches the document from + // the window object. + // 3) The content viewer is cached in session history. The nsISHEntry + // has the only owning reference to the content viewer. The viewer + // has released its nsISHEntry pointer to prevent circular ownership. + // + // When restoring a content viewer from session history, open() is called + // to reattach the document to the window object. The content viewer is + // then placed into mContentViewer and removed from the history entry. + // (mContentViewer is put into session history as described above, if + // applicable). + + // Determines whether we can safely cache the current mContentViewer in + // session history. This checks a number of factors such as cache policy, + // pending requests, and unload handlers. + // |aLoadType| should be the load type that will replace the current + // presentation. |aNewRequest| should be the request for the document to + // be loaded in place of the current document, or null if such a request + // has not been created yet. |aNewDocument| should be the document that will + // replace the current document. + bool CanSavePresentation(uint32_t aLoadType, + nsIRequest* aNewRequest, + nsIDocument* aNewDocument); + + // Captures the state of the supporting elements of the presentation + // (the "window" object, docshell tree, meta-refresh loads, and security + // state) and stores them on |mOSHE|. + nsresult CaptureState(); + + // Begin the toplevel restore process for |aSHEntry|. + // This simulates a channel open, and defers the real work until + // RestoreFromHistory is called from a PLEvent. + nsresult RestorePresentation(nsISHEntry* aSHEntry, bool* aRestoring); + + // Call BeginRestore(nullptr, false) for each child of this shell. + nsresult BeginRestoreChildren(); + + // Method to get our current position and size without flushing + void DoGetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth, + int32_t* aHeight); + + // Call this when a URI load is handed to us (via OnLinkClick or + // InternalLoad). This makes sure that we're not inside unload, or that if + // we are it's still OK to load this URI. + bool IsOKToLoadURI(nsIURI* aURI); + + void ReattachEditorToWindow(nsISHEntry* aSHEntry); + + nsCOMPtr<nsIDOMStorageManager> mSessionStorageManager; + nsIDOMStorageManager* TopSessionStorageManager(); + + // helpers for executing commands + nsresult GetControllerForCommand(const char* aCommand, + nsIController** aResult); + nsresult EnsureCommandHandler(); + + nsIChannel* GetCurrentDocChannel(); + + bool ShouldBlockLoadingForBackButton(); + + // Convenience method for getting our parent docshell. Can return null + already_AddRefed<nsDocShell> GetParentDocshell(); + + // Check if aURI is about:newtab. + bool IsAboutNewtab(nsIURI* aURI); + +protected: + nsresult GetCurScrollPos(int32_t aScrollOrientation, int32_t* aCurPos); + nsresult SetCurScrollPosEx(int32_t aCurHorizontalPos, + int32_t aCurVerticalPos); + + // Override the parent setter from nsDocLoader + virtual nsresult SetDocLoaderParent(nsDocLoader* aLoader) override; + + void ClearFrameHistory(nsISHEntry* aEntry); + + /** + * Initializes mTiming if it isn't yet. + * After calling this, mTiming is non-null. + */ + void MaybeInitTiming(); + + bool DisplayLoadError(nsresult aError, nsIURI* aURI, const char16_t* aURL, + nsIChannel* aFailedChannel) + { + bool didDisplayLoadError = false; + DisplayLoadError(aError, aURI, aURL, aFailedChannel, &didDisplayLoadError); + return didDisplayLoadError; + } + +public: + // Event type dispatched by RestorePresentation + class RestorePresentationEvent : public mozilla::Runnable + { + public: + NS_DECL_NSIRUNNABLE + explicit RestorePresentationEvent(nsDocShell* aDs) : mDocShell(aDs) {} + void Revoke() { mDocShell = nullptr; } + private: + RefPtr<nsDocShell> mDocShell; + }; + +protected: + bool JustStartedNetworkLoad(); + + nsresult CreatePrincipalFromReferrer(nsIURI* aReferrer, + nsIPrincipal** aResult); + + static const nsCString FrameTypeToString(uint32_t aFrameType) + { + switch (aFrameType) { + case FRAME_TYPE_APP: + return NS_LITERAL_CSTRING("app"); + case FRAME_TYPE_BROWSER: + return NS_LITERAL_CSTRING("browser"); + case FRAME_TYPE_REGULAR: + return NS_LITERAL_CSTRING("regular"); + default: + NS_ERROR("Unknown frame type"); + return EmptyCString(); + } + } + + uint32_t GetInheritedFrameType(); + + bool HasUnloadedParent(); + + // Dimensions of the docshell + nsIntRect mBounds; + nsString mName; + nsString mTitle; + nsString mCustomUserAgent; + + /** + * Content-Type Hint of the most-recently initiated load. Used for + * session history entries. + */ + nsCString mContentTypeHint; + nsIntPoint mDefaultScrollbarPref; // persistent across doc loads + + nsCOMPtr<nsIMutableArray> mRefreshURIList; + nsCOMPtr<nsIMutableArray> mSavedRefreshURIList; + RefPtr<nsDSURIContentListener> mContentListener; + nsCOMPtr<nsIContentViewer> mContentViewer; + nsCOMPtr<nsIWidget> mParentWidget; + + // mCurrentURI should be marked immutable on set if possible. + nsCOMPtr<nsIURI> mCurrentURI; + nsCOMPtr<nsIURI> mReferrerURI; + uint32_t mReferrerPolicy; + RefPtr<nsGlobalWindow> mScriptGlobal; + nsCOMPtr<nsISHistory> mSessionHistory; + nsCOMPtr<nsIGlobalHistory2> mGlobalHistory; + nsCOMPtr<nsIWebBrowserFind> mFind; + nsCOMPtr<nsICommandManager> mCommandManager; + // Reference to the SHEntry for this docshell until the page is destroyed. + // Somebody give me better name + nsCOMPtr<nsISHEntry> mOSHE; + // Reference to the SHEntry for this docshell until the page is loaded + // Somebody give me better name. + // If mLSHE is non-null, non-pushState subframe loads don't create separate + // root history entries. That is, frames loaded during the parent page + // load don't generate history entries the way frame navigation after the + // parent has loaded does. (This isn't the only purpose of mLSHE.) + nsCOMPtr<nsISHEntry> mLSHE; + + // Holds a weak pointer to a RestorePresentationEvent object if any that + // holds a weak pointer back to us. We use this pointer to possibly revoke + // the event whenever necessary. + nsRevocableEventPtr<RestorePresentationEvent> mRestorePresentationEvent; + + // Editor data, if this document is designMode or contentEditable. + nsAutoPtr<nsDocShellEditorData> mEditorData; + + // Transferable hooks/callbacks + nsCOMPtr<nsIClipboardDragDropHookList> mTransferableHookData; + + // Secure browser UI object + nsCOMPtr<nsISecureBrowserUI> mSecurityUI; + + // The URI we're currently loading. This is only relevant during the + // firing of a pagehide/unload. The caller of FirePageHideNotification() + // is responsible for setting it and unsetting it. It may be null if the + // pagehide/unload is happening for some reason other than just loading a + // new URI. + nsCOMPtr<nsIURI> mLoadingURI; + + // Set in LoadErrorPage from the method argument and used later + // in CreateContentViewer. We have to delay an shistory entry creation + // for which these objects are needed. + nsCOMPtr<nsIURI> mFailedURI; + nsCOMPtr<nsIChannel> mFailedChannel; + uint32_t mFailedLoadType; + + // Set in DoURILoad when either the LOAD_RELOAD_ALLOW_MIXED_CONTENT flag or + // the LOAD_NORMAL_ALLOW_MIXED_CONTENT flag is set. + // Checked in nsMixedContentBlocker, to see if the channels match. + nsCOMPtr<nsIChannel> mMixedContentChannel; + + // WEAK REFERENCES BELOW HERE. + // Note these are intentionally not addrefd. Doing so will create a cycle. + // For that reasons don't use nsCOMPtr. + + nsIDocShellTreeOwner* mTreeOwner; // Weak Reference + mozilla::dom::EventTarget* mChromeEventHandler; // Weak Reference + + eCharsetReloadState mCharsetReloadState; + + // Offset in the parent's child list. + // -1 if the docshell is added dynamically to the parent shell. + uint32_t mChildOffset; + uint32_t mBusyFlags; + uint32_t mAppType; + uint32_t mLoadType; + + int32_t mMarginWidth; + int32_t mMarginHeight; + + // This can either be a content docshell or a chrome docshell. After + // Create() is called, the type is not expected to change. + int32_t mItemType; + + // Index into the SHTransaction list, indicating the previous and current + // transaction at the time that this DocShell begins to load + int32_t mPreviousTransIndex; + int32_t mLoadedTransIndex; + + uint32_t mSandboxFlags; + nsWeakPtr mOnePermittedSandboxedNavigator; + + // The orientation lock as described by + // https://w3c.github.io/screen-orientation/ + mozilla::dom::ScreenOrientationInternal mOrientationLock; + + // mFullscreenAllowed stores how we determine whether fullscreen is allowed + // when GetFullscreenAllowed() is called. Fullscreen is allowed in a + // docshell when all containing iframes have the allowfullscreen + // attribute set to true. When mFullscreenAllowed is CHECK_ATTRIBUTES + // we check this docshell's containing frame for the allowfullscreen + // attribute, and recurse onto the parent docshell to ensure all containing + // frames also have the allowfullscreen attribute. If we find an ancestor + // docshell with mFullscreenAllowed not equal to CHECK_ATTRIBUTES, we've + // reached a content boundary, and mFullscreenAllowed denotes whether the + // parent across the content boundary has allowfullscreen=true in all its + // containing iframes. mFullscreenAllowed defaults to CHECK_ATTRIBUTES and + // is set otherwise when docshells which are content boundaries are created. + enum FullscreenAllowedState : uint8_t + { + CHECK_ATTRIBUTES, + PARENT_ALLOWS, + PARENT_PROHIBITS + }; + FullscreenAllowedState mFullscreenAllowed; + + // Cached value of the "browser.xul.error_pages.enabled" preference. + static bool sUseErrorPages; + + bool mCreated : 1; + bool mAllowSubframes : 1; + bool mAllowPlugins : 1; + bool mAllowJavascript : 1; + bool mAllowMetaRedirects : 1; + bool mAllowImages : 1; + bool mAllowMedia : 1; + bool mAllowDNSPrefetch : 1; + bool mAllowWindowControl : 1; + bool mAllowContentRetargeting : 1; + bool mAllowContentRetargetingOnChildren : 1; + bool mUseErrorPages : 1; + bool mObserveErrorPages : 1; + bool mAllowAuth : 1; + bool mAllowKeywordFixup : 1; + bool mIsOffScreenBrowser : 1; + bool mIsActive : 1; + bool mDisableMetaRefreshWhenInactive : 1; + bool mIsPrerendered : 1; + bool mIsAppTab : 1; + bool mUseGlobalHistory : 1; + bool mUseRemoteTabs : 1; + bool mDeviceSizeIsPageSize : 1; + bool mWindowDraggingAllowed : 1; + bool mInFrameSwap : 1; + bool mInheritPrivateBrowsingId : 1; + + // Because scriptability depends on the mAllowJavascript values of our + // ancestors, we cache the effective scriptability and recompute it when + // it might have changed; + bool mCanExecuteScripts : 1; + void RecomputeCanExecuteScripts(); + + // This boolean is set to true right before we fire pagehide and generally + // unset when we embed a new content viewer. While it's true no navigation + // is allowed in this docshell. + bool mFiredUnloadEvent : 1; + + // this flag is for bug #21358. a docshell may load many urls + // which don't result in new documents being created (i.e. a new + // content viewer) we want to make sure we don't call a on load + // event more than once for a given content viewer. + bool mEODForCurrentDocument : 1; + bool mURIResultedInDocument : 1; + + bool mIsBeingDestroyed : 1; + + bool mIsExecutingOnLoadHandler : 1; + + // Indicates that a DocShell in this "docshell tree" is printing + bool mIsPrintingOrPP : 1; + + // Indicates to CreateContentViewer() that it is safe to cache the old + // presentation of the page, and to SetupNewViewer() that the old viewer + // should be passed a SHEntry to save itself into. + bool mSavingOldViewer : 1; + + // @see nsIDocShellHistory::createdDynamically + bool mDynamicallyCreated : 1; + bool mAffectPrivateSessionLifetime : 1; + bool mInvisible : 1; + bool mHasLoadedNonBlankURI : 1; + + // This flag means that mTiming has been initialized but nulled out. + // We will check the innerWin's timing before creating a new one + // in MaybeInitTiming() + bool mBlankTiming : 1; + + // The following two fields cannot be declared as bit fields + // because of uses with AutoRestore. + bool mCreatingDocument; // (should be) debugging only +#ifdef DEBUG + bool mInEnsureScriptEnv; +#endif + + uint64_t mHistoryID; + uint32_t mDefaultLoadFlags; + + static nsIURIFixup* sURIFixup; + + RefPtr<nsDOMNavigationTiming> mTiming; + + // Are we a regular frame, a browser frame, or an app frame? + uint32_t mFrameType; + + // This represents the state of private browsing in the docshell. + // Currently treated as a binary value: 1 - in private mode, 0 - not private mode + // On content docshells mPrivateBrowsingId == mOriginAttributes.mPrivateBrowsingId + // On chrome docshells this value will be set, but not have the corresponding + // origin attribute set. + uint32_t mPrivateBrowsingId; + + nsString mInterceptedDocumentId; + +private: + nsCString mForcedCharset; + nsCString mParentCharset; + int32_t mParentCharsetSource; + nsCOMPtr<nsIPrincipal> mParentCharsetPrincipal; + nsTObserverArray<nsWeakPtr> mPrivacyObservers; + nsTObserverArray<nsWeakPtr> mReflowObservers; + nsTObserverArray<nsWeakPtr> mScrollObservers; + nsCString mOriginalUriString; + nsWeakPtr mOpener; + mozilla::DocShellOriginAttributes mOriginAttributes; + + // A depth count of how many times NotifyRunToCompletionStart + // has been called without a matching NotifyRunToCompletionStop. + uint32_t mJSRunToCompletionDepth; + + // Whether or not touch events are overridden. Possible values are defined + // as constants in the nsIDocShell.idl file. + uint32_t mTouchEventsOverride; + + // Separate function to do the actual name (i.e. not _top, _self etc.) + // searching for FindItemWithName. + nsresult DoFindItemWithName(const nsAString& aName, + nsISupports* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + nsIDocShellTreeItem** aResult); + + // Helper assertion to enforce that mInPrivateBrowsing is in sync with + // OriginAttributes.mPrivateBrowsingId + void AssertOriginAttributesMatchPrivateBrowsing(); + + // Notify consumers of a search being loaded through the observer service: + void MaybeNotifyKeywordSearchLoading(const nsString& aProvider, + const nsString& aKeyword); + +#ifdef DEBUG + // We're counting the number of |nsDocShells| to help find leaks + static unsigned long gNumberOfDocShells; +#endif /* DEBUG */ + +public: + class InterfaceRequestorProxy : public nsIInterfaceRequestor + { + public: + explicit InterfaceRequestorProxy(nsIInterfaceRequestor* aRequestor); + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINTERFACEREQUESTOR + + protected: + virtual ~InterfaceRequestorProxy(); + InterfaceRequestorProxy() {} + nsWeakPtr mWeakPtr; + }; +}; + +#endif /* nsDocShell_h__ */ diff --git a/docshell/base/nsDocShellEditorData.cpp b/docshell/base/nsDocShellEditorData.cpp new file mode 100644 index 000000000..7e4068eb9 --- /dev/null +++ b/docshell/base/nsDocShellEditorData.cpp @@ -0,0 +1,192 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDocShellEditorData.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsPIDOMWindow.h" +#include "nsIDOMDocument.h" +#include "nsIEditor.h" +#include "nsIEditingSession.h" +#include "nsIDocShell.h" + +nsDocShellEditorData::nsDocShellEditorData(nsIDocShell* aOwningDocShell) + : mDocShell(aOwningDocShell) + , mMakeEditable(false) + , mIsDetached(false) + , mDetachedMakeEditable(false) + , mDetachedEditingState(nsIHTMLDocument::eOff) +{ + NS_ASSERTION(mDocShell, "Where is my docShell?"); +} + +nsDocShellEditorData::~nsDocShellEditorData() +{ + TearDownEditor(); +} + +void +nsDocShellEditorData::TearDownEditor() +{ + if (mEditor) { + mEditor->PreDestroy(false); + mEditor = nullptr; + } + mEditingSession = nullptr; + mIsDetached = false; +} + +nsresult +nsDocShellEditorData::MakeEditable(bool aInWaitForUriLoad) +{ + if (mMakeEditable) { + return NS_OK; + } + + // if we are already editable, and are getting turned off, + // nuke the editor. + if (mEditor) { + NS_WARNING("Destroying existing editor on frame"); + + mEditor->PreDestroy(false); + mEditor = nullptr; + } + + if (aInWaitForUriLoad) { + mMakeEditable = true; + } + return NS_OK; +} + +bool +nsDocShellEditorData::GetEditable() +{ + return mMakeEditable || (mEditor != nullptr); +} + +nsresult +nsDocShellEditorData::CreateEditor() +{ + nsCOMPtr<nsIEditingSession> editingSession; + nsresult rv = GetEditingSession(getter_AddRefs(editingSession)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsPIDOMWindowOuter> domWindow = + mDocShell ? mDocShell->GetWindow() : nullptr; + rv = editingSession->SetupEditorOnWindow(domWindow); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +nsresult +nsDocShellEditorData::GetEditingSession(nsIEditingSession** aResult) +{ + nsresult rv = EnsureEditingSession(); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ADDREF(*aResult = mEditingSession); + + return NS_OK; +} + +nsresult +nsDocShellEditorData::GetEditor(nsIEditor** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + NS_IF_ADDREF(*aResult = mEditor); + return NS_OK; +} + +nsresult +nsDocShellEditorData::SetEditor(nsIEditor* aEditor) +{ + // destroy any editor that we have. Checks for equality are + // necessary to ensure that assigment into the nsCOMPtr does + // not temporarily reduce the refCount of the editor to zero + if (mEditor.get() != aEditor) { + if (mEditor) { + mEditor->PreDestroy(false); + mEditor = nullptr; + } + + mEditor = aEditor; // owning addref + if (!mEditor) { + mMakeEditable = false; + } + } + + return NS_OK; +} + +// This creates the editing session on the content docShell that owns 'this'. +nsresult +nsDocShellEditorData::EnsureEditingSession() +{ + NS_ASSERTION(mDocShell, "Should have docShell here"); + NS_ASSERTION(!mIsDetached, "This will stomp editing session!"); + + nsresult rv = NS_OK; + + if (!mEditingSession) { + mEditingSession = + do_CreateInstance("@mozilla.org/editor/editingsession;1", &rv); + } + + return rv; +} + +nsresult +nsDocShellEditorData::DetachFromWindow() +{ + NS_ASSERTION(mEditingSession, + "Can't detach when we don't have a session to detach!"); + + nsCOMPtr<nsPIDOMWindowOuter> domWindow = + mDocShell ? mDocShell->GetWindow() : nullptr; + nsresult rv = mEditingSession->DetachFromWindow(domWindow); + NS_ENSURE_SUCCESS(rv, rv); + + mIsDetached = true; + mDetachedMakeEditable = mMakeEditable; + mMakeEditable = false; + + nsCOMPtr<nsIDocument> doc = domWindow->GetDoc(); + nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc); + if (htmlDoc) { + mDetachedEditingState = htmlDoc->GetEditingState(); + } + + mDocShell = nullptr; + + return NS_OK; +} + +nsresult +nsDocShellEditorData::ReattachToWindow(nsIDocShell* aDocShell) +{ + mDocShell = aDocShell; + + nsCOMPtr<nsPIDOMWindowOuter> domWindow = + mDocShell ? mDocShell->GetWindow() : nullptr; + nsresult rv = mEditingSession->ReattachToWindow(domWindow); + NS_ENSURE_SUCCESS(rv, rv); + + mIsDetached = false; + mMakeEditable = mDetachedMakeEditable; + + nsCOMPtr<nsIDocument> doc = domWindow->GetDoc(); + nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc); + if (htmlDoc) { + htmlDoc->SetEditingState(mDetachedEditingState); + } + + return NS_OK; +} diff --git a/docshell/base/nsDocShellEditorData.h b/docshell/base/nsDocShellEditorData.h new file mode 100644 index 000000000..272190503 --- /dev/null +++ b/docshell/base/nsDocShellEditorData.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsDocShellEditorData_h__ +#define nsDocShellEditorData_h__ + +#ifndef nsCOMPtr_h___ +#include "nsCOMPtr.h" +#endif + +#include "nsIHTMLDocument.h" + +class nsIDocShell; +class nsIEditingSession; +class nsIEditor; + +class nsDocShellEditorData +{ +public: + explicit nsDocShellEditorData(nsIDocShell* aOwningDocShell); + ~nsDocShellEditorData(); + + nsresult MakeEditable(bool aWaitForUriLoad); + bool GetEditable(); + nsresult CreateEditor(); + nsresult GetEditingSession(nsIEditingSession** aResult); + nsresult GetEditor(nsIEditor** aResult); + nsresult SetEditor(nsIEditor* aEditor); + void TearDownEditor(); + nsresult DetachFromWindow(); + nsresult ReattachToWindow(nsIDocShell* aDocShell); + bool WaitingForLoad() const { return mMakeEditable; } + +protected: + nsresult EnsureEditingSession(); + + // The doc shell that owns us. Weak ref, since it always outlives us. + nsIDocShell* mDocShell; + + // Only present for the content root docShell. Session is owned here. + nsCOMPtr<nsIEditingSession> mEditingSession; + + // Indicates whether to make an editor after a url load. + bool mMakeEditable; + + // If this frame is editable, store editor here. Editor is owned here. + nsCOMPtr<nsIEditor> mEditor; + + // Denotes if the editor is detached from its window. The editor is detached + // while it's stored in the session history bfcache. + bool mIsDetached; + + // Backup for mMakeEditable while the editor is detached. + bool mDetachedMakeEditable; + + // Backup for the corresponding nsIHTMLDocument's editing state while + // the editor is detached. + nsIHTMLDocument::EditingState mDetachedEditingState; +}; + +#endif // nsDocShellEditorData_h__ diff --git a/docshell/base/nsDocShellEnumerator.cpp b/docshell/base/nsDocShellEnumerator.cpp new file mode 100644 index 000000000..1f870cd01 --- /dev/null +++ b/docshell/base/nsDocShellEnumerator.cpp @@ -0,0 +1,205 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDocShellEnumerator.h" + +#include "nsIDocShellTreeItem.h" + +nsDocShellEnumerator::nsDocShellEnumerator(int32_t aEnumerationDirection) + : mRootItem(nullptr) + , mCurIndex(0) + , mDocShellType(nsIDocShellTreeItem::typeAll) + , mArrayValid(false) + , mEnumerationDirection(aEnumerationDirection) +{ +} + +nsDocShellEnumerator::~nsDocShellEnumerator() +{ +} + +NS_IMPL_ISUPPORTS(nsDocShellEnumerator, nsISimpleEnumerator) + +NS_IMETHODIMP +nsDocShellEnumerator::GetNext(nsISupports** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + nsresult rv = EnsureDocShellArray(); + if (NS_FAILED(rv)) { + return rv; + } + + if (mCurIndex >= mItemArray.Length()) { + return NS_ERROR_FAILURE; + } + + // post-increment is important here + nsCOMPtr<nsISupports> item = do_QueryReferent(mItemArray[mCurIndex++], &rv); + item.forget(aResult); + return rv; +} + +NS_IMETHODIMP +nsDocShellEnumerator::HasMoreElements(bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + + nsresult rv = EnsureDocShellArray(); + if (NS_FAILED(rv)) { + return rv; + } + + *aResult = (mCurIndex < mItemArray.Length()); + return NS_OK; +} + +nsresult +nsDocShellEnumerator::GetEnumerationRootItem( + nsIDocShellTreeItem** aEnumerationRootItem) +{ + NS_ENSURE_ARG_POINTER(aEnumerationRootItem); + nsCOMPtr<nsIDocShellTreeItem> item = do_QueryReferent(mRootItem); + item.forget(aEnumerationRootItem); + return NS_OK; +} + +nsresult +nsDocShellEnumerator::SetEnumerationRootItem( + nsIDocShellTreeItem* aEnumerationRootItem) +{ + mRootItem = do_GetWeakReference(aEnumerationRootItem); + ClearState(); + return NS_OK; +} + +nsresult +nsDocShellEnumerator::GetEnumDocShellType(int32_t* aEnumerationItemType) +{ + NS_ENSURE_ARG_POINTER(aEnumerationItemType); + *aEnumerationItemType = mDocShellType; + return NS_OK; +} + +nsresult +nsDocShellEnumerator::SetEnumDocShellType(int32_t aEnumerationItemType) +{ + mDocShellType = aEnumerationItemType; + ClearState(); + return NS_OK; +} + +nsresult +nsDocShellEnumerator::First() +{ + mCurIndex = 0; + return EnsureDocShellArray(); +} + +nsresult +nsDocShellEnumerator::EnsureDocShellArray() +{ + if (!mArrayValid) { + mArrayValid = true; + return BuildDocShellArray(mItemArray); + } + + return NS_OK; +} + +nsresult +nsDocShellEnumerator::ClearState() +{ + mItemArray.Clear(); + mArrayValid = false; + mCurIndex = 0; + return NS_OK; +} + +nsresult +nsDocShellEnumerator::BuildDocShellArray(nsTArray<nsWeakPtr>& aItemArray) +{ + NS_ENSURE_TRUE(mRootItem, NS_ERROR_NOT_INITIALIZED); + aItemArray.Clear(); + nsCOMPtr<nsIDocShellTreeItem> item = do_QueryReferent(mRootItem); + return BuildArrayRecursive(item, aItemArray); +} + +nsresult +nsDocShellForwardsEnumerator::BuildArrayRecursive( + nsIDocShellTreeItem* aItem, + nsTArray<nsWeakPtr>& aItemArray) +{ + nsresult rv; + + // add this item to the array + if (mDocShellType == nsIDocShellTreeItem::typeAll || + aItem->ItemType() == mDocShellType) { + if (!aItemArray.AppendElement(do_GetWeakReference(aItem))) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + int32_t numChildren; + rv = aItem->GetChildCount(&numChildren); + if (NS_FAILED(rv)) { + return rv; + } + + for (int32_t i = 0; i < numChildren; ++i) { + nsCOMPtr<nsIDocShellTreeItem> curChild; + rv = aItem->GetChildAt(i, getter_AddRefs(curChild)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = BuildArrayRecursive(curChild, aItemArray); + if (NS_FAILED(rv)) { + return rv; + } + } + + return NS_OK; +} + +nsresult +nsDocShellBackwardsEnumerator::BuildArrayRecursive( + nsIDocShellTreeItem* aItem, + nsTArray<nsWeakPtr>& aItemArray) +{ + nsresult rv; + + int32_t numChildren; + rv = aItem->GetChildCount(&numChildren); + if (NS_FAILED(rv)) { + return rv; + } + + for (int32_t i = numChildren - 1; i >= 0; --i) { + nsCOMPtr<nsIDocShellTreeItem> curChild; + rv = aItem->GetChildAt(i, getter_AddRefs(curChild)); + if (NS_FAILED(rv)) { + return rv; + } + + rv = BuildArrayRecursive(curChild, aItemArray); + if (NS_FAILED(rv)) { + return rv; + } + } + + // add this item to the array + if (mDocShellType == nsIDocShellTreeItem::typeAll || + aItem->ItemType() == mDocShellType) { + if (!aItemArray.AppendElement(do_GetWeakReference(aItem))) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + return NS_OK; +} diff --git a/docshell/base/nsDocShellEnumerator.h b/docshell/base/nsDocShellEnumerator.h new file mode 100644 index 000000000..87de74f0f --- /dev/null +++ b/docshell/base/nsDocShellEnumerator.h @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDocShellEnumerator_h___ +#define nsDocShellEnumerator_h___ + +#include "nsISimpleEnumerator.h" +#include "nsTArray.h" +#include "nsIWeakReferenceUtils.h" + +class nsIDocShellTreeItem; + +/* +// {13cbc281-35ae-11d5-be5b-bde0edece43c} +#define NS_DOCSHELL_FORWARDS_ENUMERATOR_CID \ +{ 0x13cbc281, 0x35ae, 0x11d5, { 0xbe, 0x5b, 0xbd, 0xe0, 0xed, 0xec, 0xe4, 0x3c } } + +#define NS_DOCSHELL_FORWARDS_ENUMERATOR_CONTRACTID \ +"@mozilla.org/docshell/enumerator-forwards;1" + +// {13cbc282-35ae-11d5-be5b-bde0edece43c} +#define NS_DOCSHELL_BACKWARDS_ENUMERATOR_CID \ +{ 0x13cbc282, 0x35ae, 0x11d5, { 0xbe, 0x5b, 0xbd, 0xe0, 0xed, 0xec, 0xe4, 0x3c } } + +#define NS_DOCSHELL_BACKWARDS_ENUMERATOR_CONTRACTID \ +"@mozilla.org/docshell/enumerator-backwards;1" +*/ + +class nsDocShellEnumerator : public nsISimpleEnumerator +{ +protected: + enum + { + enumerateForwards, + enumerateBackwards + }; + + virtual ~nsDocShellEnumerator(); + +public: + explicit nsDocShellEnumerator(int32_t aEnumerationDirection); + + // nsISupports + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator + NS_DECL_NSISIMPLEENUMERATOR + +public: + nsresult GetEnumerationRootItem(nsIDocShellTreeItem** aEnumerationRootItem); + nsresult SetEnumerationRootItem(nsIDocShellTreeItem* aEnumerationRootItem); + + nsresult GetEnumDocShellType(int32_t* aEnumerationItemType); + nsresult SetEnumDocShellType(int32_t aEnumerationItemType); + + nsresult First(); + +protected: + nsresult EnsureDocShellArray(); + nsresult ClearState(); + + nsresult BuildDocShellArray(nsTArray<nsWeakPtr>& aItemArray); + virtual nsresult BuildArrayRecursive(nsIDocShellTreeItem* aItem, + nsTArray<nsWeakPtr>& aItemArray) = 0; + +protected: + nsWeakPtr mRootItem; // weak ref! + + nsTArray<nsWeakPtr> mItemArray; // flattened list of items with matching type + uint32_t mCurIndex; + + int32_t mDocShellType; // only want shells of this type + bool mArrayValid; // is mItemArray up to date? + + const int8_t mEnumerationDirection; +}; + +class nsDocShellForwardsEnumerator : public nsDocShellEnumerator +{ +public: + nsDocShellForwardsEnumerator() + : nsDocShellEnumerator(enumerateForwards) + { + } + +protected: + virtual nsresult BuildArrayRecursive(nsIDocShellTreeItem* aItem, + nsTArray<nsWeakPtr>& aItemArray); +}; + +class nsDocShellBackwardsEnumerator : public nsDocShellEnumerator +{ +public: + nsDocShellBackwardsEnumerator() + : nsDocShellEnumerator(enumerateBackwards) + { + } + +protected: + virtual nsresult BuildArrayRecursive(nsIDocShellTreeItem* aItem, + nsTArray<nsWeakPtr>& aItemArray); +}; + +#endif // nsDocShellEnumerator_h___ diff --git a/docshell/base/nsDocShellLoadInfo.cpp b/docshell/base/nsDocShellLoadInfo.cpp new file mode 100644 index 000000000..7d0034b04 --- /dev/null +++ b/docshell/base/nsDocShellLoadInfo.cpp @@ -0,0 +1,297 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDocShellLoadInfo.h" +#include "nsISHEntry.h" +#include "nsIInputStream.h" +#include "nsIURI.h" +#include "nsIDocShell.h" +#include "mozilla/net/ReferrerPolicy.h" + +nsDocShellLoadInfo::nsDocShellLoadInfo() + : mLoadReplace(false) + , mInheritPrincipal(false) + , mPrincipalIsExplicit(false) + , mSendReferrer(true) + , mReferrerPolicy(mozilla::net::RP_Default) + , mLoadType(nsIDocShellLoadInfo::loadNormal) + , mIsSrcdocLoad(false) +{ +} + +nsDocShellLoadInfo::~nsDocShellLoadInfo() +{ +} + +NS_IMPL_ADDREF(nsDocShellLoadInfo) +NS_IMPL_RELEASE(nsDocShellLoadInfo) + +NS_INTERFACE_MAP_BEGIN(nsDocShellLoadInfo) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocShellLoadInfo) + NS_INTERFACE_MAP_ENTRY(nsIDocShellLoadInfo) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +nsDocShellLoadInfo::GetReferrer(nsIURI** aReferrer) +{ + NS_ENSURE_ARG_POINTER(aReferrer); + + *aReferrer = mReferrer; + NS_IF_ADDREF(*aReferrer); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::SetReferrer(nsIURI* aReferrer) +{ + mReferrer = aReferrer; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::GetOriginalURI(nsIURI** aOriginalURI) +{ + NS_ENSURE_ARG_POINTER(aOriginalURI); + + *aOriginalURI = mOriginalURI; + NS_IF_ADDREF(*aOriginalURI); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::SetOriginalURI(nsIURI* aOriginalURI) +{ + mOriginalURI = aOriginalURI; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::GetLoadReplace(bool* aLoadReplace) +{ + *aLoadReplace = mLoadReplace; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::SetLoadReplace(bool aLoadReplace) +{ + mLoadReplace = aLoadReplace; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal) +{ + NS_ENSURE_ARG_POINTER(aTriggeringPrincipal); + NS_IF_ADDREF(*aTriggeringPrincipal = mTriggeringPrincipal); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::SetTriggeringPrincipal(nsIPrincipal* aTriggeringPrincipal) +{ + mTriggeringPrincipal = aTriggeringPrincipal; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::GetInheritPrincipal(bool* aInheritPrincipal) +{ + NS_ENSURE_ARG_POINTER(aInheritPrincipal); + *aInheritPrincipal = mInheritPrincipal; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::SetInheritPrincipal(bool aInheritPrincipal) +{ + mInheritPrincipal = aInheritPrincipal; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::GetPrincipalIsExplicit(bool* aPrincipalIsExplicit) +{ + *aPrincipalIsExplicit = mPrincipalIsExplicit; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::SetPrincipalIsExplicit(bool aPrincipalIsExplicit) +{ + mPrincipalIsExplicit = aPrincipalIsExplicit; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::GetLoadType(nsDocShellInfoLoadType* aLoadType) +{ + NS_ENSURE_ARG_POINTER(aLoadType); + + *aLoadType = mLoadType; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::SetLoadType(nsDocShellInfoLoadType aLoadType) +{ + mLoadType = aLoadType; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::GetSHEntry(nsISHEntry** aSHEntry) +{ + NS_ENSURE_ARG_POINTER(aSHEntry); + + *aSHEntry = mSHEntry; + NS_IF_ADDREF(*aSHEntry); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::SetSHEntry(nsISHEntry* aSHEntry) +{ + mSHEntry = aSHEntry; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::GetTarget(char16_t** aTarget) +{ + NS_ENSURE_ARG_POINTER(aTarget); + + *aTarget = ToNewUnicode(mTarget); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::SetTarget(const char16_t* aTarget) +{ + mTarget.Assign(aTarget); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::GetPostDataStream(nsIInputStream** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = mPostDataStream; + + NS_IF_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::SetPostDataStream(nsIInputStream* aStream) +{ + mPostDataStream = aStream; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::GetHeadersStream(nsIInputStream** aHeadersStream) +{ + NS_ENSURE_ARG_POINTER(aHeadersStream); + *aHeadersStream = mHeadersStream; + NS_IF_ADDREF(*aHeadersStream); + return NS_OK; +} +NS_IMETHODIMP +nsDocShellLoadInfo::SetHeadersStream(nsIInputStream* aHeadersStream) +{ + mHeadersStream = aHeadersStream; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::GetSendReferrer(bool* aSendReferrer) +{ + NS_ENSURE_ARG_POINTER(aSendReferrer); + + *aSendReferrer = mSendReferrer; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::SetSendReferrer(bool aSendReferrer) +{ + mSendReferrer = aSendReferrer; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::GetReferrerPolicy( + nsDocShellInfoReferrerPolicy* aReferrerPolicy) +{ + *aReferrerPolicy = mReferrerPolicy; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::SetReferrerPolicy( + nsDocShellInfoReferrerPolicy aReferrerPolicy) +{ + mReferrerPolicy = aReferrerPolicy; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::GetIsSrcdocLoad(bool* aIsSrcdocLoad) +{ + *aIsSrcdocLoad = mIsSrcdocLoad; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::GetSrcdocData(nsAString& aSrcdocData) +{ + aSrcdocData = mSrcdocData; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::SetSrcdocData(const nsAString& aSrcdocData) +{ + mSrcdocData = aSrcdocData; + mIsSrcdocLoad = true; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::GetSourceDocShell(nsIDocShell** aSourceDocShell) +{ + MOZ_ASSERT(aSourceDocShell); + nsCOMPtr<nsIDocShell> result = mSourceDocShell; + result.forget(aSourceDocShell); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::SetSourceDocShell(nsIDocShell* aSourceDocShell) +{ + mSourceDocShell = aSourceDocShell; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::GetBaseURI(nsIURI** aBaseURI) +{ + NS_ENSURE_ARG_POINTER(aBaseURI); + + *aBaseURI = mBaseURI; + NS_IF_ADDREF(*aBaseURI); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellLoadInfo::SetBaseURI(nsIURI* aBaseURI) +{ + mBaseURI = aBaseURI; + return NS_OK; +} diff --git a/docshell/base/nsDocShellLoadInfo.h b/docshell/base/nsDocShellLoadInfo.h new file mode 100644 index 000000000..b7eaed832 --- /dev/null +++ b/docshell/base/nsDocShellLoadInfo.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDocShellLoadInfo_h__ +#define nsDocShellLoadInfo_h__ + +// Helper Classes +#include "nsCOMPtr.h" +#include "nsString.h" + +// Interfaces Needed +#include "nsIDocShellLoadInfo.h" + +class nsIInputStream; +class nsISHEntry; +class nsIURI; +class nsIDocShell; + +class nsDocShellLoadInfo : public nsIDocShellLoadInfo +{ +public: + nsDocShellLoadInfo(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOCSHELLLOADINFO + +protected: + virtual ~nsDocShellLoadInfo(); + +protected: + nsCOMPtr<nsIURI> mReferrer; + nsCOMPtr<nsIURI> mOriginalURI; + nsCOMPtr<nsIPrincipal> mTriggeringPrincipal; + bool mLoadReplace; + bool mInheritPrincipal; + bool mPrincipalIsExplicit; + bool mSendReferrer; + nsDocShellInfoReferrerPolicy mReferrerPolicy; + nsDocShellInfoLoadType mLoadType; + nsCOMPtr<nsISHEntry> mSHEntry; + nsString mTarget; + nsCOMPtr<nsIInputStream> mPostDataStream; + nsCOMPtr<nsIInputStream> mHeadersStream; + bool mIsSrcdocLoad; + nsString mSrcdocData; + nsCOMPtr<nsIDocShell> mSourceDocShell; + nsCOMPtr<nsIURI> mBaseURI; +}; + +#endif /* nsDocShellLoadInfo_h__ */ diff --git a/docshell/base/nsDocShellLoadTypes.h b/docshell/base/nsDocShellLoadTypes.h new file mode 100644 index 000000000..67304476d --- /dev/null +++ b/docshell/base/nsDocShellLoadTypes.h @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDocShellLoadTypes_h_ +#define nsDocShellLoadTypes_h_ + +#ifdef MOZILLA_INTERNAL_API + +#include "nsIDocShell.h" +#include "nsIWebNavigation.h" + +/** + * Load flag for error pages. This uses one of the reserved flag + * values from nsIWebNavigation. + */ +#define LOAD_FLAGS_ERROR_PAGE 0x0001U + +#define MAKE_LOAD_TYPE(type, flags) ((type) | ((flags) << 16)) +#define LOAD_TYPE_HAS_FLAGS(type, flags) ((type) & ((flags) << 16)) + +/** + * These are flags that confuse ConvertLoadTypeToDocShellLoadInfo and should + * not be passed to MAKE_LOAD_TYPE. In particular this includes all flags + * above 0xffff (e.g. LOAD_FLAGS_BYPASS_CLASSIFIER), since MAKE_LOAD_TYPE would + * just shift them out anyway. + */ +#define EXTRA_LOAD_FLAGS (LOAD_FLAGS_FIRST_LOAD | \ + LOAD_FLAGS_ALLOW_POPUPS | \ + 0xffff0000) + +/* load types are legal combinations of load commands and flags + * + * NOTE: + * Remember to update the IsValidLoadType function below if you change this + * enum to ensure bad flag combinations will be rejected. + */ +enum LoadType +{ + LOAD_NORMAL = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_NONE), + LOAD_NORMAL_REPLACE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY), + LOAD_NORMAL_EXTERNAL = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_FROM_EXTERNAL), + LOAD_HISTORY = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_HISTORY, nsIWebNavigation::LOAD_FLAGS_NONE), + LOAD_NORMAL_BYPASS_CACHE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE), + LOAD_NORMAL_BYPASS_PROXY = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY), + LOAD_NORMAL_BYPASS_PROXY_AND_CACHE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE | nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY), + LOAD_NORMAL_ALLOW_MIXED_CONTENT = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT | nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE), + LOAD_RELOAD_NORMAL = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD, nsIWebNavigation::LOAD_FLAGS_NONE), + LOAD_RELOAD_BYPASS_CACHE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD, nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE), + LOAD_RELOAD_BYPASS_PROXY = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD, nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY), + LOAD_RELOAD_ALLOW_MIXED_CONTENT = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD, nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT | nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE), + LOAD_RELOAD_BYPASS_PROXY_AND_CACHE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD, nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE | nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY), + LOAD_LINK = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_IS_LINK), + LOAD_REFRESH = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_IS_REFRESH), + LOAD_RELOAD_CHARSET_CHANGE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD, nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE), + 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_REPLACE_BYPASS_CACHE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY | nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE), + /** + * Load type for an error page. These loads are never triggered by users of + * Docshell. Instead, Docshell triggers the load itself when a + * consumer-triggered load failed. + */ + LOAD_ERROR_PAGE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, + LOAD_FLAGS_ERROR_PAGE) + + // NOTE: Adding a new value? Remember to update IsValidLoadType! +}; +static inline bool +IsValidLoadType(uint32_t aLoadType) +{ + switch (aLoadType) { + case LOAD_NORMAL: + case LOAD_NORMAL_REPLACE: + case LOAD_NORMAL_EXTERNAL: + case LOAD_NORMAL_BYPASS_CACHE: + case LOAD_NORMAL_BYPASS_PROXY: + case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE: + case LOAD_NORMAL_ALLOW_MIXED_CONTENT: + case LOAD_HISTORY: + case LOAD_RELOAD_NORMAL: + case LOAD_RELOAD_BYPASS_CACHE: + case LOAD_RELOAD_BYPASS_PROXY: + case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: + case LOAD_RELOAD_ALLOW_MIXED_CONTENT: + case LOAD_LINK: + case LOAD_REFRESH: + case LOAD_RELOAD_CHARSET_CHANGE: + case LOAD_BYPASS_HISTORY: + case LOAD_STOP_CONTENT: + case LOAD_STOP_CONTENT_AND_REPLACE: + case LOAD_PUSHSTATE: + case LOAD_REPLACE_BYPASS_CACHE: + case LOAD_ERROR_PAGE: + return true; + } + return false; +} + +static inline bool +IsForceReloadType(uint32_t aLoadType) { + switch (aLoadType) { + case LOAD_RELOAD_BYPASS_CACHE: + case LOAD_RELOAD_BYPASS_PROXY: + case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: + case LOAD_RELOAD_ALLOW_MIXED_CONTENT: + return true; + } + return false; +} + +#endif // MOZILLA_INTERNAL_API +#endif diff --git a/docshell/base/nsDocShellTransferableHooks.cpp b/docshell/base/nsDocShellTransferableHooks.cpp new file mode 100644 index 000000000..441ade128 --- /dev/null +++ b/docshell/base/nsDocShellTransferableHooks.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDocShellTransferableHooks.h" +#include "nsIClipboardDragDropHooks.h" +#include "nsIClipboardDragDropHookList.h" +#include "nsArrayEnumerator.h" + +nsTransferableHookData::nsTransferableHookData() +{ +} + +nsTransferableHookData::~nsTransferableHookData() +{ +} + +NS_IMPL_ISUPPORTS(nsTransferableHookData, nsIClipboardDragDropHookList) + +NS_IMETHODIMP +nsTransferableHookData::AddClipboardDragDropHooks( + nsIClipboardDragDropHooks* aOverrides) +{ + NS_ENSURE_ARG(aOverrides); + + // don't let a hook be added more than once + if (mHookList.IndexOfObject(aOverrides) == -1) { + if (!mHookList.AppendObject(aOverrides)) { + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTransferableHookData::RemoveClipboardDragDropHooks( + nsIClipboardDragDropHooks* aOverrides) +{ + NS_ENSURE_ARG(aOverrides); + if (!mHookList.RemoveObject(aOverrides)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsTransferableHookData::GetHookEnumerator(nsISimpleEnumerator** aResult) +{ + return NS_NewArrayEnumerator(aResult, mHookList); +} diff --git a/docshell/base/nsDocShellTransferableHooks.h b/docshell/base/nsDocShellTransferableHooks.h new file mode 100644 index 000000000..58d764529 --- /dev/null +++ b/docshell/base/nsDocShellTransferableHooks.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDocShellTransferableHooks_h__ +#define nsDocShellTransferableHooks_h__ + +#include "nsIClipboardDragDropHookList.h" +#include "nsCOMArray.h" + +class nsIClipboardDragDropHooks; + +class nsTransferableHookData : public nsIClipboardDragDropHookList +{ +public: + nsTransferableHookData(); + NS_DECL_ISUPPORTS + NS_DECL_NSICLIPBOARDDRAGDROPHOOKLIST + +protected: + virtual ~nsTransferableHookData(); + + nsCOMArray<nsIClipboardDragDropHooks> mHookList; +}; + +#endif // nsDocShellTransferableHooks_h__ diff --git a/docshell/base/nsDownloadHistory.cpp b/docshell/base/nsDownloadHistory.cpp new file mode 100644 index 000000000..32513ef00 --- /dev/null +++ b/docshell/base/nsDownloadHistory.cpp @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDownloadHistory.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" +#include "nsIGlobalHistory2.h" +#include "nsIObserverService.h" +#include "nsIURI.h" +#include "mozilla/Services.h" + +NS_IMPL_ISUPPORTS(nsDownloadHistory, nsIDownloadHistory) + +NS_IMETHODIMP +nsDownloadHistory::AddDownload(nsIURI* aSource, + nsIURI* aReferrer, + PRTime aStartTime, + nsIURI* aDestination) +{ + NS_ENSURE_ARG_POINTER(aSource); + + nsCOMPtr<nsIGlobalHistory2> history = + do_GetService("@mozilla.org/browser/global-history;2"); + if (!history) { + return NS_ERROR_NOT_AVAILABLE; + } + + bool visited; + nsresult rv = history->IsVisited(aSource, &visited); + NS_ENSURE_SUCCESS(rv, rv); + + rv = history->AddURI(aSource, false, true, aReferrer); + NS_ENSURE_SUCCESS(rv, rv); + + if (!visited) { + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->NotifyObservers(aSource, NS_LINK_VISITED_EVENT_TOPIC, nullptr); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDownloadHistory::RemoveAllDownloads() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/docshell/base/nsDownloadHistory.h b/docshell/base/nsDownloadHistory.h new file mode 100644 index 000000000..4a4c20ddf --- /dev/null +++ b/docshell/base/nsDownloadHistory.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsDownloadHistory_h__ +#define __nsDownloadHistory_h__ + +#include "nsIDownloadHistory.h" +#include "mozilla/Attributes.h" + +#define NS_DOWNLOADHISTORY_CID \ + {0x2ee83680, 0x2af0, 0x4bcb, {0xbf, 0xa0, 0xc9, 0x70, 0x5f, 0x65, 0x54, 0xf1}} + +class nsDownloadHistory final : public nsIDownloadHistory +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIDOWNLOADHISTORY + + NS_DEFINE_STATIC_CID_ACCESSOR(NS_DOWNLOADHISTORY_CID) + +private: + ~nsDownloadHistory() {} +}; + +#endif // __nsDownloadHistory_h__ diff --git a/docshell/base/nsIClipboardCommands.idl b/docshell/base/nsIClipboardCommands.idl new file mode 100644 index 000000000..9dcb1401f --- /dev/null +++ b/docshell/base/nsIClipboardCommands.idl @@ -0,0 +1,111 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * An interface for embedding clients who wish to interact with + * the system-wide OS clipboard. Mozilla does not use a private + * clipboard, instead it places its data directly onto the system + * clipboard. The webshell implements this interface. + */ + +[scriptable, uuid(b8100c90-73be-11d2-92a5-00105a1b0d64)] +interface nsIClipboardCommands : nsISupports { + + /** + * Returns whether there is a selection and it is not read-only. + * + * @return <code>true</code> if the current selection can be cut, + * <code>false</code> otherwise. + */ + boolean canCutSelection(); + + /** + * Returns whether there is a selection and it is copyable. + * + * @return <code>true</code> if there is a selection, + * <code>false</code> otherwise. + */ + boolean canCopySelection(); + + /** + * Returns whether we can copy a link location. + * + * @return <code>true</code> if a link is selected, + * <code>false</code> otherwise. + */ + boolean canCopyLinkLocation(); + + /** + * Returns whether we can copy an image location. + * + * @return <code>true</code> if an image is selected, + <code>false</code> otherwise. + */ + boolean canCopyImageLocation(); + + /** + * Returns whether we can copy an image's contents. + * + * @return <code>true</code> if an image is selected, + * <code>false</code> otherwise + */ + boolean canCopyImageContents(); + + /** + * Returns whether the current contents of the clipboard can be + * pasted and if the current selection is not read-only. + * + * @return <code>true</code> there is data to paste on the clipboard + * and the current selection is not read-only, + * <code>false</code> otherwise + */ + boolean canPaste(); + + /** + * Cut the current selection onto the clipboard. + */ + void cutSelection(); + + /** + * Copy the current selection onto the clipboard. + */ + void copySelection(); + + /** + * Copy the link location of the current selection (e.g., + * the |href| attribute of a selected |a| tag). + */ + void copyLinkLocation(); + + /** + * Copy the location of the selected image. + */ + void copyImageLocation(); + + /** + * Copy the contents of the selected image. + */ + void copyImageContents(); + + /** + * Paste the contents of the clipboard into the current selection. + */ + void paste(); + + /** + * Select the entire contents. + */ + void selectAll(); + + /** + * Clear the current selection (if any). Insertion point ends up + * at beginning of current selection. + */ + void selectNone(); + +}; diff --git a/docshell/base/nsIContentViewer.idl b/docshell/base/nsIContentViewer.idl new file mode 100644 index 000000000..d7d58ad69 --- /dev/null +++ b/docshell/base/nsIContentViewer.idl @@ -0,0 +1,280 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDocShell; +interface nsIDocument; +interface nsIDOMDocument; +interface nsIDOMNode; +interface nsISHEntry; +interface nsIPrintSettings; + + +%{ C++ +#include "nsTArray.h" +#include "nsRect.h" + +class nsIWidget; +class nsIPresShell; +class nsPresContext; +class nsView; +class nsDOMNavigationTiming; +%} + +[ptr] native nsIWidgetPtr(nsIWidget); +[ref] native nsIntRectRef(nsIntRect); +[ptr] native nsIPresShellPtr(nsIPresShell); +[ptr] native nsPresContextPtr(nsPresContext); +[ptr] native nsViewPtr(nsView); +[ptr] native nsDOMNavigationTimingPtr(nsDOMNavigationTiming); +[ref] native nsIContentViewerTArray(nsTArray<nsCOMPtr<nsIContentViewer> >); + +[scriptable, builtinclass, uuid(2da17016-7851-4a45-a7a8-00b360e01595)] +interface nsIContentViewer : nsISupports +{ + [noscript] void init(in nsIWidgetPtr aParentWidget, + [const] in nsIntRectRef aBounds); + + attribute nsIDocShell container; + + [noscript,notxpcom,nostdcall] void loadStart(in nsIDocument aDoc); + void loadComplete(in nsresult aStatus); + [noscript] readonly attribute boolean loadCompleted; + + /** + * Checks if the document wants to prevent unloading by firing beforeunload on + * the document, and if it does, prompts the user. The result is returned. + */ + boolean permitUnload(); + + /** + * Exposes whether we're blocked in a call to permitUnload. + */ + readonly attribute boolean inPermitUnload; + + /** + * As above, but this passes around the aShouldPrompt argument to keep + * track of whether the user has responded to a prompt. + * Used internally by the scriptable version to ensure we only prompt once. + */ + [noscript,nostdcall] boolean permitUnloadInternal(inout boolean aShouldPrompt); + + /** + * Exposes whether we're in the process of firing the beforeunload event. + * In this case, the corresponding docshell will not allow navigation. + */ + readonly attribute boolean beforeUnloadFiring; + + void pageHide(in boolean isUnload); + + /** + * All users of a content viewer are responsible for calling both + * close() and destroy(), in that order. + * + * close() should be called when the load of a new page for the next + * content viewer begins, and destroy() should be called when the next + * content viewer replaces this one. + * + * |historyEntry| sets the session history entry for the content viewer. If + * this is null, then Destroy() will be called on the document by close(). + * If it is non-null, the document will not be destroyed, and the following + * actions will happen when destroy() is called (*): + * - Sanitize() will be called on the viewer's document + * - The content viewer will set the contentViewer property on the + * history entry, and release its reference (ownership reversal). + * - hide() will be called, and no further destruction will happen. + * + * (*) unless the document is currently being printed, in which case + * it will never be saved in session history. + * + */ + void close(in nsISHEntry historyEntry); + void destroy(); + + void stop(); + + attribute nsIDOMDocument DOMDocument; + + /** + * Returns DOMDocument as nsIDocument and without addrefing. + */ + [noscript,notxpcom] nsIDocument getDocument(); + + [noscript] void getBounds(in nsIntRectRef aBounds); + [noscript] void setBounds([const] in nsIntRectRef aBounds); + /** + * The 'aFlags' argument to setBoundsWithFlags is a set of these bits. + */ + const unsigned long eDelayResize = 1; + [noscript] void setBoundsWithFlags([const] in nsIntRectRef aBounds, + in unsigned long aFlags); + + /** + * The previous content viewer, which has been |close|d but not + * |destroy|ed. + */ + [noscript] attribute nsIContentViewer previousViewer; + + void move(in long aX, in long aY); + + void show(); + void hide(); + + attribute boolean sticky; + + /* + * This is called when the DOM window wants to be closed. Returns true + * if the window can close immediately. Otherwise, returns false and will + * close the DOM window as soon as practical. + */ + + boolean requestWindowClose(); + + /** + * Attach the content viewer to its DOM window and docshell. + * @param aState A state object that might be useful in attaching the DOM + * window. + * @param aSHEntry The history entry that the content viewer was stored in. + * The entry must have the docshells for all of the child + * documents stored in its child shell list. + */ + void open(in nsISupports aState, in nsISHEntry aSHEntry); + + /** + * Clears the current history entry. This is used if we need to clear out + * the saved presentation state. + */ + void clearHistoryEntry(); + + /** + * Change the layout to view the document with page layout (like print preview), but + * dynamic and editable (like Galley layout). + */ + void setPageMode(in boolean aPageMode, in nsIPrintSettings aPrintSettings); + + /** + * Get the history entry that this viewer will save itself into when + * destroyed. Can return null + */ + readonly attribute nsISHEntry historyEntry; + + /** + * Indicates when we're in a state where content shouldn't be allowed to + * trigger a tab-modal prompt (as opposed to a window-modal prompt) because + * we're part way through some operation (eg beforeunload) that shouldn't be + * rentrant if the user closes the tab while the prompt is showing. + * See bug 613800. + */ + readonly attribute boolean isTabModalPromptAllowed; + + /** + * Returns whether this content viewer is in a hidden state. + * + * @note Only Gecko internal code should set the attribute! + */ + attribute boolean isHidden; + + [noscript] readonly attribute nsIPresShellPtr presShell; + [noscript] readonly attribute nsPresContextPtr presContext; + // aDocument must not be null. + [noscript] void setDocumentInternal(in nsIDocument aDocument, + in boolean aForceReuseInnerWindow); + /** + * Find the view to use as the container view for MakeWindow. Returns + * null if this will be the root of a view manager hierarchy. In that + * case, if mParentWidget is null then this document should not even + * be displayed. + */ + [noscript,notxpcom,nostdcall] nsViewPtr findContainerView(); + /** + * Set collector for navigation timing data (load, unload events). + */ + [noscript,notxpcom,nostdcall] void setNavigationTiming(in nsDOMNavigationTimingPtr aTiming); + /* + Scrolls to a given DOM content node. + */ + void scrollToNode(in nsIDOMNode node); + + /** The amount by which to scale all text. Default is 1.0. */ + attribute float textZoom; + + /** The amount by which to scale all lengths. Default is 1.0. */ + attribute float fullZoom; + + /** + * The value used to override devicePixelRatio and media queries dppx. + * Default is 0.0, that means no overriding is done (only a positive value + * is applied). + */ + attribute float overrideDPPX; + + /** Disable entire author style level (including HTML presentation hints) */ + attribute boolean authorStyleDisabled; + + /** + * XXX comm-central only: bug 829543. Not the Character Encoding menu in + * browser! + */ + attribute ACString forceCharacterSet; + + /** + * XXX comm-central only: bug 829543. + */ + attribute ACString hintCharacterSet; + + /** + * XXX comm-central only: bug 829543. + */ + attribute int32_t hintCharacterSetSource; + + /** + * Requests the size of the content to the container. + */ + void getContentSize(out long width, out long height); + + /** + * Returns the preferred width and height of the content, constrained to the + * given maximum values. If either maxWidth or maxHeight is less than zero, + * that dimension is not constrained. + * + * All input and output values are in device pixels, rather than CSS pixels. + */ + void getContentSizeConstrained(in long maxWidth, in long maxHeight, + out long width, out long height); + + /** The minimum font size */ + attribute long minFontSize; + + /** + * Append |this| and all of its descendants to the given array, + * in depth-first pre-order traversal. + */ + [noscript] void appendSubtree(in nsIContentViewerTArray array); + + /** + * Instruct the refresh driver to discontinue painting until further + * notice. + */ + void pausePainting(); + + /** + * Instruct the refresh driver to resume painting after a previous call to + * pausePainting(). + */ + void resumePainting(); + + /* + * Render the document as if being viewed on a device with the specified + * media type. This will cause a reflow. + * + * @param mediaType The media type to be emulated + */ + void emulateMedium(in AString aMediaType); + + /* + * Restore the viewer's natural media type + */ + void stopEmulatingMedium(); +}; diff --git a/docshell/base/nsIContentViewerContainer.idl b/docshell/base/nsIContentViewerContainer.idl new file mode 100644 index 000000000..c94b37ea7 --- /dev/null +++ b/docshell/base/nsIContentViewerContainer.idl @@ -0,0 +1,20 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIContentViewer; + +[scriptable, uuid(ea2ce7a0-5c3d-11d4-90c2-0050041caf44)] +interface nsIContentViewerContainer : nsISupports { + void embed(in nsIContentViewer aDocViewer, in string aCommand, in nsISupports aExtraInfo); + + /** + * Allows the PrintEngine to make this call on + * an internal interface to the DocShell + */ + void setIsPrinting(in boolean aIsPrinting); +}; diff --git a/docshell/base/nsIContentViewerEdit.idl b/docshell/base/nsIContentViewerEdit.idl new file mode 100644 index 000000000..c8b6b8a02 --- /dev/null +++ b/docshell/base/nsIContentViewerEdit.idl @@ -0,0 +1,36 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDOMNode; + +[scriptable, uuid(35BE2D7E-F29B-48EC-BF7E-80A30A724DE3)] +interface nsIContentViewerEdit : nsISupports +{ + void clearSelection(); + void selectAll(); + + void copySelection(); + readonly attribute boolean copyable; + + void copyLinkLocation(); + readonly attribute boolean inLink; + + const long COPY_IMAGE_TEXT = 0x0001; + const long COPY_IMAGE_HTML = 0x0002; + const long COPY_IMAGE_DATA = 0x0004; + const long COPY_IMAGE_ALL = -1; + void copyImage(in long aCopyFlags); + readonly attribute boolean inImage; + + AString getContents(in string aMimeType, in boolean aSelectionOnly); + readonly attribute boolean canGetContents; + + // Set the node that will be the subject of the editing commands above. + // Usually this will be the node that was context-clicked. + void setCommandNode(in nsIDOMNode aNode); +}; diff --git a/docshell/base/nsIContentViewerFile.idl b/docshell/base/nsIContentViewerFile.idl new file mode 100644 index 000000000..dd2adbf30 --- /dev/null +++ b/docshell/base/nsIContentViewerFile.idl @@ -0,0 +1,31 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDOMWindow; +interface nsIPrintSettings; +interface nsIWebProgressListener; + +%{ C++ +#include <stdio.h> +%} + +[ptr] native FILE(FILE); + +/** + * The nsIDocShellFile + */ + +[scriptable, uuid(564a3276-6228-401e-9b5c-d82cb382a60f)] +interface nsIContentViewerFile : nsISupports +{ + readonly attribute boolean printable; + + [noscript] void print(in boolean aSilent, + in FILE aDebugFile, + in nsIPrintSettings aPrintSettings); +}; diff --git a/docshell/base/nsIDocCharset.idl b/docshell/base/nsIDocCharset.idl new file mode 100644 index 000000000..5b1b5a8dc --- /dev/null +++ b/docshell/base/nsIDocCharset.idl @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * The functionality of the nsIDocCharset interface has been incorporated into + * nsIDocShell. + * + * This is an empty interface for backwards compatibility that will go away at + * some point in the future + * + */ + +[scriptable, uuid(c3faaf6e-40f0-11e1-95fc-6c626d69675c)] +interface nsIDocCharset : nsISupports +{}; diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl new file mode 100644 index 000000000..8261c45dc --- /dev/null +++ b/docshell/base/nsIDocShell.idl @@ -0,0 +1,1186 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "domstubs.idl" +#include "nsIDocShellTreeItem.idl" +#include "nsIRequest.idl" + +%{ C++ +#include "js/TypeDecls.h" +class nsPresContext; +class nsIPresShell; +%} + +/** + * The nsIDocShell interface. + */ + +[ptr] native nsPresContext(nsPresContext); +[ptr] native nsIPresShell(nsIPresShell); + +interface nsIURI; +interface nsIChannel; +interface nsIContentViewer; +interface nsIDOMEventTarget; +interface nsIDocShellLoadInfo; +interface nsIEditor; +interface nsIEditingSession; +interface nsISimpleEnumerator; +interface nsIInputStream; +interface nsIRequest; +interface nsISHEntry; +interface nsILayoutHistoryState; +interface nsISecureBrowserUI; +interface nsIScriptGlobalObject; +interface nsIDOMStorage; +interface nsIPrincipal; +interface nsIWebBrowserPrint; +interface nsIPrivacyTransitionObserver; +interface nsIReflowObserver; +interface nsIScrollObserver; +interface nsITabParent; +interface nsITabChild; +interface nsICommandManager; +interface nsICommandParams; +native TabChildRef(already_AddRefed<nsITabChild>); + +[scriptable, builtinclass, uuid(049234fe-da10-478b-bc5d-bc6f9a1ba63d)] +interface nsIDocShell : nsIDocShellTreeItem +{ + /** + * Loads a given URI. This will give priority to loading the requested URI + * in the object implementing this interface. If it can't be loaded here + * however, the URL dispatcher will go through its normal process of content + * loading. + * + * @param uri - The URI to load. + * @param loadInfo - This is the extended load info for this load. This + * most often will be null, but if you need to do + * additional setup for this load you can get a loadInfo + * object by calling createLoadInfo. Once you have this + * object you can set the needed properties on it and + * then pass it to loadURI. + * @param aLoadFlags - Flags to modify load behaviour. Flags are defined in + * nsIWebNavigation. Note that using flags outside + * LOAD_FLAGS_MASK is only allowed if passing in a + * non-null loadInfo. And even some of those might not + * be allowed. Use at your own risk. + */ + [noscript]void loadURI(in nsIURI uri, + in nsIDocShellLoadInfo loadInfo, + in unsigned long aLoadFlags, + in boolean firstParty); + + /** + * Loads a given stream. This will give priority to loading the requested + * stream in the object implementing this interface. If it can't be loaded + * here however, the URL dispatched will go through its normal process of + * content loading. + * + * @param aStream - The input stream that provides access to the data + * to be loaded. This must be a blocking, threadsafe + * stream implementation. + * @param aURI - The URI representing the stream, or null. + * @param aContentType - The type (MIME) of data being loaded (empty if unknown). + * @param aContentCharset - The charset of the data being loaded (empty if unknown). + * @param aLoadInfo - This is the extended load info for this load. This + * most often will be null, but if you need to do + * additional setup for this load you can get a + * loadInfo object by calling createLoadInfo. Once + * you have this object you can set the needed + * properties on it and then pass it to loadStream. + */ + [noscript]void loadStream(in nsIInputStream aStream, + in nsIURI aURI, + in ACString aContentType, + in ACString aContentCharset, + in nsIDocShellLoadInfo aLoadInfo); + + const long INTERNAL_LOAD_FLAGS_NONE = 0x0; + const long INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL = 0x1; + const long INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER = 0x2; + const long INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP = 0x4; + + // This flag marks the first load in this object + // @see nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD + const long INTERNAL_LOAD_FLAGS_FIRST_LOAD = 0x8; + + const long INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER = 0x10; + const long INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES = 0x20; + + // Whether the load should be treated as srcdoc load, rather than a URI one. + const long INTERNAL_LOAD_FLAGS_IS_SRCDOC = 0x40; + + const long INTERNAL_LOAD_FLAGS_NO_OPENER = 0x100; + + // NB: 0x80 is available. + + /** + * Loads the given URI. This method is identical to loadURI(...) except + * that its parameter list is broken out instead of being packaged inside + * of an nsIDocShellLoadInfo object... + * + * @param aURI - The URI to load. + * @param aOriginalURI - The URI to set as the originalURI on the channel + * that does the load. If null, aURI will be set as + * the originalURI. + * @param aLoadReplace - If set LOAD_REPLACE flag will be set on the + * channel. aOriginalURI is null, this argument is + * ignored. + * @param aReferrer - Referring URI + * @param aReferrerPolicy - Referrer policy + * @param aTriggeringPrincipal - A non-null principal that initiated that load. + * Please note that this is the principal that is + * used for security checks. If the argument aURI + * is provided by the web, then please do not pass + * a SystemPrincipal as the triggeringPrincipal. + * @param aPrincipalToInherit - Principal to be inherited for that load. If this + * argument is null then principalToInherit is + * computed as follows: + * a) If INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL, and + * aLoadType is not LOAD_NORMAL_EXTERNAL, and the + * URI would normally inherit a principal, then + * principalToInherit is set to the current + * document's principal, or parent document if + * there is not a current document. + * b) If principalToInherit is still null (e.g. if + * some of the conditions of (a) were not satisfied), + * then no inheritance of any sort will happen: the + * load will just get a principal based on the URI + * being loaded. + * @param aFlags - Any of the load flags defined within above. + * @param aStopActiveDoc - Flag indicating whether loading the current + * document should be stopped. + * @param aWindowTarget - Window target for the load. + * @param aTypeHint - A hint as to the content-type of the resulting + * data. May be null or empty if no hint. + * @param aFileName - Non-null when the link should be downloaded as + the given filename. + * @param aPostDataStream - Post data stream (if POSTing) + * @param aHeadersStream - Stream containing "extra" request headers... + * @param aLoadFlags - Flags to modify load behaviour. Flags are defined + * in nsIWebNavigation. + * @param aSHEntry - Active Session History entry (if loading from SH) + * @param aSrcdoc When INTERNAL_LOAD_FLAGS_IS_SRCDOC is set, the + * contents of this parameter will be loaded instead + * of aURI. + * @param aSourceDocShell - The source browsing context for the navigation. + * @param aBaseURI - The base URI to be used for the load. Set in + * srcdoc loads as it cannot otherwise be inferred + * in certain situations such as view-source. + */ + [noscript]void internalLoad(in nsIURI aURI, + in nsIURI aOriginalURI, + in boolean aLoadReplace, + in nsIURI aReferrer, + in unsigned long aReferrerPolicy, + in nsIPrincipal aTriggeringPrincipal, + in nsIPrincipal aPrincipalToInherit, + in uint32_t aFlags, + in AString aWindowTarget, + in string aTypeHint, + in AString aFileName, + in nsIInputStream aPostDataStream, + in nsIInputStream aHeadersStream, + in unsigned long aLoadFlags, + in nsISHEntry aSHEntry, + in boolean firstParty, + in AString aSrcdoc, + in nsIDocShell aSourceDocShell, + in nsIURI aBaseURI, + out nsIDocShell aDocShell, + out nsIRequest aRequest); + + /** + * Do either a history.pushState() or history.replaceState() operation, + * depending on the value of aReplace. + */ + [implicit_jscontext] + void addState(in jsval aData, in DOMString aTitle, + in DOMString aURL, in boolean aReplace); + + /** + * Creates a DocShellLoadInfo object that you can manipulate and then pass + * to loadURI. + */ + void createLoadInfo(out nsIDocShellLoadInfo loadInfo); + + /** + * Reset state to a new content model within the current document and the document + * viewer. Called by the document before initiating an out of band document.write(). + */ + void prepareForNewContentModel(); + + /** + * For editors and suchlike who wish to change the URI associated with the + * document. Note if you want to get the current URI, use the read-only + * property on nsIWebNavigation. + */ + void setCurrentURI(in nsIURI aURI); + + /** + * Notify the associated content viewer and all child docshells that they are + * about to be hidden. If |isUnload| is true, then the document is being + * unloaded as well. + * + * @param isUnload if true, fire the unload event in addition to the pagehide + * event. + */ + [noscript] void firePageHideNotification(in boolean isUnload); + + /** + * Presentation context for the currently loaded document. This may be null. + */ + [noscript] readonly attribute nsPresContext presContext; + + /** + * Presentation shell for the currently loaded document. This may be null. + */ + [noscript,notxpcom] nsIPresShell GetPresShell(); + + /** + * Presentation shell for the oldest document, if this docshell is + * currently transitioning between documents. + */ + [noscript] readonly attribute nsIPresShell eldestPresShell; + + /** + * Content Viewer that is currently loaded for this DocShell. This may + * change as the underlying content changes. + */ + readonly attribute nsIContentViewer contentViewer; + + /** + * This attribute allows chrome to tie in to handle DOM events that may + * be of interest to chrome. + */ + attribute nsIDOMEventTarget chromeEventHandler; + + /** + * This allows chrome to set a custom User agent on a specific docshell + */ + attribute DOMString customUserAgent; + + /** + * Whether to allow plugin execution + */ + attribute boolean allowPlugins; + + /** + * Whether to allow Javascript execution + */ + attribute boolean allowJavascript; + + /** + * Attribute stating if refresh based redirects can be allowed + */ + attribute boolean allowMetaRedirects; + + /** + * Attribute stating if it should allow subframes (framesets/iframes) or not + */ + attribute boolean allowSubframes; + + /** + * Attribute stating whether or not images should be loaded. + */ + attribute boolean allowImages; + + /** + * Attribute stating whether or not media (audio/video) should be loaded. + */ + [infallible] attribute boolean allowMedia; + + /** + * Attribute that determines whether DNS prefetch is allowed for this subtree + * of the docshell tree. Defaults to true. Setting this will make it take + * effect starting with the next document loaded in the docshell. + */ + attribute boolean allowDNSPrefetch; + + /** + * Attribute that determines whether window control (move/resize) is allowed. + */ + attribute boolean allowWindowControl; + + /** + * True if the docshell allows its content to be handled by a content listener + * other than the docshell itself, including the external helper app service, + * and false otherwise. Defaults to true. + */ + [infallible] attribute boolean allowContentRetargeting; + + /** + * True if new child docshells should allow content retargeting. + * Setting allowContentRetargeting also overwrites this value. + */ + [infallible] attribute boolean allowContentRetargetingOnChildren; + + /** + * True if this docShell should inherit the private browsing ID from + * its parent when reparented. + * + * NOTE: This should *not* be set false in new code, or for docShells + * inserted anywhere other than as children of panels. + */ + [infallible] attribute boolean inheritPrivateBrowsingId; + + /** + * Get an enumerator over this docShell and its children. + * + * @param aItemType - Only include docShells of this type, or if typeAll, + * include all child shells. + * Uses types from nsIDocShellTreeItem. + * @param aDirection - Whether to enumerate forwards or backwards. + */ + + const long ENUMERATE_FORWARDS = 0; + const long ENUMERATE_BACKWARDS = 1; + + nsISimpleEnumerator getDocShellEnumerator(in long aItemType, + in long aDirection); + + /** + * The type of application that created this window + */ + const unsigned long APP_TYPE_UNKNOWN = 0; + const unsigned long APP_TYPE_MAIL = 1; + const unsigned long APP_TYPE_EDITOR = 2; + + attribute unsigned long appType; + + /** + * certain dochshells (like the message pane) + * should not throw up auth dialogs + * because it can act as a password trojan + */ + attribute boolean allowAuth; + + /** + * Set/Get the document scale factor. When setting this attribute, a + * NS_ERROR_NOT_IMPLEMENTED error may be returned by implementations + * not supporting zoom. Implementations not supporting zoom should return + * 1.0 all the time for the Get operation. 1.0 by the way is the default + * of zoom. This means 100% of normal scaling or in other words normal size + * no zoom. + */ + attribute float zoom; + + /* + * The size, in CSS pixels, of the horizontal margins for the <body> of an + * HTML document in this docshel; used to implement the marginwidth attribute + * on HTML <frame>/<iframe> elements. A value smaller than zero indicates + * that the attribute was not set. + */ + attribute long marginWidth; + + /* + * The size, in CSS pixels, of the vertical margins for the <body> of an HTML + * document in this docshel; used to implement the marginheight attribute on + * HTML <frame>/<iframe> elements. A value smaller than zero indicates that + * the attribute was not set. + */ + attribute long marginHeight; + + /* + * Tells the docshell to offer focus to its tree owner. + * This is currently only necessary for embedding chrome. + * If forDocumentNavigation is true, then document navigation should be + * performed, where only the root of documents are selected. Otherwise, the + * next element in the parent should be returned. Returns true if focus was + * successfully taken by the tree owner. + */ + bool tabToTreeOwner(in boolean forward, in boolean forDocumentNavigation); + + /** + * Current busy state for DocShell + */ + const unsigned long BUSY_FLAGS_NONE = 0; + const unsigned long BUSY_FLAGS_BUSY = 1; + const unsigned long BUSY_FLAGS_BEFORE_PAGE_LOAD = 2; + const unsigned long BUSY_FLAGS_PAGE_LOADING = 4; + + /** + * 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_PUSHSTATE = 0x8; // History.pushState() + + readonly attribute unsigned long busyFlags; + + /* + * attribute to access the loadtype for the document + */ + attribute unsigned long loadType; + + /* + * Default load flags (as defined in nsIRequest) that will be set on all + * requests made by this docShell and propagated to all child docShells and + * to nsILoadGroup::defaultLoadFlags for the docShell's loadGroup. + * Default is no flags. Once set, only future requests initiated by the + * docShell are affected, so in general, these flags should be set before + * the docShell loads any content. + */ + attribute nsLoadFlags defaultLoadFlags; + + /* + * returns true if the docshell is being destroyed, false otherwise + */ + boolean isBeingDestroyed(); + + /* + * Returns true if the docshell is currently executing the onLoad Handler + */ + readonly attribute boolean isExecutingOnLoadHandler; + + attribute nsILayoutHistoryState layoutHistoryState; + + readonly attribute boolean shouldSaveLayoutState; + + /** + * The SecureBrowserUI object for this docshell. This is set by XUL + * <browser> or nsWebBrowser for their root docshell. + */ + attribute nsISecureBrowserUI securityUI; + + /** + * Cancel the XPCOM timers for each meta-refresh URI in this docshell, + * and this docshell's children, recursively. The meta-refresh timers can be + * restarted using resumeRefreshURIs(). If the timers are already suspended, + * this has no effect. + */ + void suspendRefreshURIs(); + + /** + * Restart the XPCOM timers for each meta-refresh URI in this docshell, + * and this docshell's children, recursively. If the timers are already + * running, this has no effect. + */ + void resumeRefreshURIs(); + + /** + * Begin firing WebProgressListener notifications for restoring a page + * presentation. |viewer| is the content viewer whose document we are + * starting to load. If null, it defaults to the docshell's current content + * viewer, creating one if necessary. |top| should be true for the toplevel + * docshell that is being restored; it will be set to false when this method + * is called for child docshells. This method will post an event to + * complete the simulated load after returning to the event loop. + */ + void beginRestore(in nsIContentViewer viewer, in boolean top); + + /** + * Finish firing WebProgressListener notifications and DOM events for + * restoring a page presentation. This should only be called via + * beginRestore(). + */ + void finishRestore(); + + /* Track whether we're currently restoring a document presentation. */ + readonly attribute boolean restoringDocument; + + /* attribute to access whether error pages are enabled */ + attribute boolean useErrorPages; + + /** + * Display a load error in a frame while keeping that frame's currentURI + * pointing correctly to the page where the error ocurred, rather than to + * the error document page. You must provide either the aURI or aURL parameter. + * + * @param aError The error code to be displayed + * @param aURI nsIURI of the page where the error happened + * @param aURL wstring of the page where the error happened + * @param aFailedChannel The channel related to this error + * + * Returns whether or not we displayed an error page (note: will always + * return false if in-content error pages are disabled!) + */ + boolean displayLoadError(in nsresult aError, + in nsIURI aURI, + in wstring aURL, + [optional] in nsIChannel aFailedChannel); + + /** + * The channel that failed to load and resulted in an error page. + * May be null. Relevant only to error pages. + */ + readonly attribute nsIChannel failedChannel; + + /** + * Keeps track of the previous SHTransaction index and the current + * SHTransaction index at the time that the doc shell begins to load. + * Used for ContentViewer eviction. + */ + readonly attribute long previousTransIndex; + readonly attribute long loadedTransIndex; + + /** + * Notification that entries have been removed from the beginning of a + * nsSHistory which has this as its rootDocShell. + * + * @param numEntries - The number of entries removed + */ + void historyPurged(in long numEntries); + + /* + * @deprecated, use nsIDocShell.QueryInterface(nsIDOMStorageManager) instead. + * + * Retrieves the WebApps session storage object for the supplied principal. + * + * @param principal returns a storage for this principal + * @param documentURI new storage will be created with reference to this + * document.documentURI that will appear in storage event + * @param create If true and a session storage object doesn't + * already exist, a new one will be created. + */ + nsIDOMStorage getSessionStorageForPrincipal(in nsIPrincipal principal, + in DOMString documentURI, + in boolean create); + + /* + * @deprecated, use nsIDocShell.QueryInterface(nsIDOMStorageManager) instead. + * + * Add a WebApps session storage object to the docshell. + * + * @param principal the principal the storage object is associated with + * @param storage the storage object to add + */ + void addSessionStorage(in nsIPrincipal principal, in nsIDOMStorage storage); + + /** + * Gets the channel for the currently loaded document, if any. + * For a new document load, this will be the channel of the previous document + * until after OnLocationChange fires. + */ + readonly attribute nsIChannel currentDocumentChannel; + + /** + * Set the offset of this child in its container. + */ + [noscript] void setChildOffset(in unsigned long offset); + + /** + * Find out whether the docshell is currently in the middle of a page + * transition. This is set just before the pagehide/unload events fire. + */ + readonly attribute boolean isInUnload; + + /** + * Find out if the currently loaded document came from a suspicious channel + * (such as a JAR channel where the server-returned content type isn't a + * known JAR type). + */ + readonly attribute boolean channelIsUnsafe; + + /** + * This attribute determines whether Mixed Active Content is loaded on the + * document. When it is true, mixed active content was not blocked and has + * loaded (or is about to load) on the page. When it is false, mixed active content + * has not loaded on the page, either because there was no mixed active content + * requests on the page or such requests were blocked by nsMixedContentBlocker. + * This boolean is set to true in nsMixedContentBlocker if Mixed Active Content + * is allowed (either explicitly on the page by the user or when the about:config + * setting security.mixed_content.block_active_content is set to false). + */ + [infallible] readonly attribute boolean hasMixedActiveContentLoaded; + + /** + * This attribute determines whether a document has Mixed Active Content + * that has been blocked from loading. When it is true, there is definitely + * mixed active content on a page that has been blocked by + * nsMixedContentBlocker. When it is false, there may or may not be mixed + * active content on a page, but if there is, it will load. Note that if the + * about:config setting security.mixed_content.block_active_content is set + * false, this boolean will be false, since blocking active content has been + * disabled. + */ + [infallible] readonly attribute boolean hasMixedActiveContentBlocked; + + /** + * This attribute determines whether Mixed Display Content is loaded on the + * document. When it is true, mixed display content was not blocked and has + * loaded (or is about to load) on the page. Similar behavior to + * hasMixedActiveContentLoaded. + */ + [infallible] readonly attribute boolean hasMixedDisplayContentLoaded; + + /** + * This attribute determines whether a document has Mixed Display Content + * that has been blocked from loading. Similar behavior to + * hasMixedActiveContentBlocked. + */ + [infallible] readonly attribute boolean hasMixedDisplayContentBlocked; + + /** + * This attribute determines whether a document has Tracking Content + * that has been blocked from loading. + */ + [infallible] readonly attribute boolean hasTrackingContentBlocked; + + /** + * This attribute determines whether Tracking Content is loaded on the + * document. When it is true, tracking content was not blocked and has + * loaded (or is about to load) on the page. + */ + [infallible] readonly attribute boolean hasTrackingContentLoaded; + + /** + * Disconnects this docshell's editor from its window, and stores the + * editor data in the open document's session history entry. This + * should be called only during page transitions. + */ + [noscript, notxpcom] void DetachEditorFromWindow(); + + /** + * If true, this browser is not visible in the traditional sense, but + * is actively being rendered to the screen (ex. painted on a canvas) + * and should be treated accordingly. + **/ + attribute boolean isOffScreenBrowser; + + /** + * If the current content viewer isn't initialized for print preview, + * it is replaced with one which is and to which an about:blank document + * is loaded. + */ + readonly attribute nsIWebBrowserPrint printPreview; + + /** + * Whether this docshell can execute scripts based on its hierarchy. + * The rule of thumb here is that we disable js if this docshell or any + * of its parents disallow scripting. + */ + [infallible] readonly attribute boolean canExecuteScripts; + + /** + * Sets whether a docshell is active. An active docshell is one that is + * visible, and thus is not a good candidate for certain optimizations + * like image frame discarding. Docshells are active unless told otherwise. + */ + attribute boolean isActive; + + /** + * Puts the docshell in prerendering mode. noscript because we want only + * native code to be able to put a docshell in prerendering. + */ + [noscript] void SetIsPrerendered(); + + /** + * Whether this docshell is in prerender mode. + */ + [infallible] readonly attribute boolean isPrerendered; + + /** + * The ID of the docshell in the session history. + */ + readonly attribute unsigned long long historyID; + + /** + * Sets whether a docshell is an app tab. An app tab docshell may behave + * differently than a non-app tab docshell in some cases, such as when + * handling link clicks. Docshells are not app tabs unless told otherwise. + */ + attribute boolean isAppTab; + + /** + * Create a new about:blank document and content viewer. + * @param aPrincipal the principal to use for the new document. + */ + void createAboutBlankContentViewer(in nsIPrincipal aPrincipal); + + /** + * Upon getting, returns the canonical encoding label of the document + * currently loaded into this docshell. + * + * Upon setting, sets forcedCharset for compatibility with legacy callers. + */ + attribute ACString charset; + + /** + * Called when the user chose an encoding override from the character + * encoding menu. Separate from the setter for the charset property to avoid + * extensions adding noise to the data. + */ + void gatherCharsetMenuTelemetry(); + + /** + * The charset forced by the user. + */ + attribute ACString forcedCharset; + + /** + * In a child docshell, this is the charset of the parent docshell + */ + [noscript, notxpcom, nostdcall] void setParentCharset( + in ACString parentCharset, + in int32_t parentCharsetSource, + in nsIPrincipal parentCharsetPrincipal); + [noscript, notxpcom, nostdcall] void getParentCharset( + out ACString parentCharset, + out int32_t parentCharsetSource, + out nsIPrincipal parentCharsetPrincipal); + + /** + * Whether the docShell records profile timeline markers at the moment + */ + [infallible] attribute boolean recordProfileTimelineMarkers; + + /** + * Return a DOMHighResTimeStamp representing the number of + * milliseconds from an arbitrary point in time. The reference + * point is shared by all DocShells and is also used by timestamps + * on markers. + */ + DOMHighResTimeStamp now(); + + /** + * Returns and flushes the profile timeline markers gathered by the docShell + */ + [implicit_jscontext] + jsval popProfileTimelineMarkers(); + + /** + * Add an observer to the list of parties to be notified when this docshell's + * private browsing status is changed. |obs| must support weak references. + */ + void addWeakPrivacyTransitionObserver(in nsIPrivacyTransitionObserver obs); + + /** + * Add an observer to the list of parties to be notified when reflows are + * occurring. |obs| must support weak references. + */ + void addWeakReflowObserver(in nsIReflowObserver obs); + + /** + * Remove an observer from the list of parties to be notified about reflows. + */ + void removeWeakReflowObserver(in nsIReflowObserver obs); + + /** + * Notify all attached observers that a reflow has just occurred. + * + * @param interruptible if true, the reflow was interruptible. + * @param start timestamp when reflow started, in milliseconds since + * navigationStart (accurate to 1/1000 of a ms) + * @param end timestamp when reflow ended, in milliseconds since + * navigationStart (accurate to 1/1000 of a ms) + */ + [noscript] void notifyReflowObservers(in bool interruptible, + in DOMHighResTimeStamp start, + in DOMHighResTimeStamp end); + + /** + * Add an observer to the list of parties to be notified when scroll position + * of some elements is changed. + */ + [noscript] void addWeakScrollObserver(in nsIScrollObserver obs); + + /** + * Add an observer to the list of parties to be notified when scroll position + * of some elements is changed. + */ + [noscript] void removeWeakScrollObserver(in nsIScrollObserver obs); + + /** + * Notify all attached observers that the scroll position of some element + * has changed. + */ + [noscript] void notifyScrollObservers(); + + /** + * Returns true iff the docshell corresponds to an <iframe mozapp>. + */ + [infallible] readonly attribute boolean isApp; + + /** + * The type of iframe that this docshell lives. + */ + const unsigned long FRAME_TYPE_REGULAR = 0; + const unsigned long FRAME_TYPE_BROWSER = 1; + const unsigned long FRAME_TYPE_APP = 2; + + [infallible] attribute unsigned long frameType; + + /** + * Returns true if this docshell corresponds to an <iframe mozbrowser> or + * <iframe mozapp>. <xul:browser> returns false here. + */ + [infallible] readonly attribute boolean isMozBrowserOrApp; + + /** + * Returns true if this docshell corresponds to an isolated <iframe + * mozbrowser>. + * + * <iframe mozbrowser mozapp> and <xul:browser> are not considered to be + * mozbrowser elements. <iframe mozbrowser noisolation> does not count as + * isolated since isolation is disabled. Isolation can only be disabled if + * the containing document is chrome. + */ + [infallible] readonly attribute boolean isIsolatedMozBrowserElement; + + /** + * Returns true if this docshell corresponds to an isolated <iframe + * mozbrowser> or if the docshell is contained in an isolated <iframe + * mozbrowser>. + * + * <iframe mozbrowser mozapp> and <xul:browser> are not considered to be + * mozbrowser elements. <iframe mozbrowser noisolation> does not count as + * isolated since isolation is disabled. Isolation can only be disabled if + * the containing document is chrome. + * + * Our notion here of "contained in" means: Walk up the docshell hierarchy in + * this process until we hit an <iframe mozapp> or <iframe mozbrowser> (or + * until the hierarchy ends). Return true iff the docshell we stopped on has + * isIsolatedMozBrowserElement == true. + */ + [infallible] readonly attribute boolean isInIsolatedMozBrowserElement; + + /** + * Returns true if this docshell corresponds to an <iframe mozbrowser> or + * <iframe mozapp>, or if this docshell is contained in an <iframe mozbrowser> + * or <iframe mozapp>. <xul:browser> returns false here. + * + * To compute this value, we walk up the docshell hierarchy. If we encounter + * a docshell with isMozBrowserOrApp before we hit the end of the hierarchy, + * we return true. Otherwise, we return false. + */ + [infallible] readonly attribute boolean isInMozBrowserOrApp; + + /** + * Returns true if this docshell is the top level content docshell. + */ + [infallible] readonly attribute boolean isTopLevelContentDocShell; + + /** + * Returns the id of the app associated with this docshell. If this docshell + * is an <iframe mozbrowser> inside an <iframe mozapp>, we return the app's + * appId. + * + * We compute this value by walking up the docshell hierarchy until we find a + * docshell on which origin attributes was set. (ignoring those docshells + * where x == UNKNOWN_APP_ID). We return the app id x. + * + * If we don't find a docshell with an associated app id in our hierarchy, we + * return NO_APP_ID. We never return UNKNOWN_APP_ID. + * + * Notice that a docshell may have an associated app even if it returns true + * for isBrowserElement! + */ + [infallible] readonly attribute unsigned long appId; + + /** + * Return the manifest URL of the app associated with this docshell. + * + * If there is no associated app in our hierarchy, we return empty string. + */ + readonly attribute DOMString appManifestURL; + + /** + * Like nsIDocShellTreeItem::GetSameTypeParent, except this ignores <iframe + * mozbrowser> and <iframe mozapp> boundaries. + */ + nsIDocShell getSameTypeParentIgnoreBrowserAndAppBoundaries(); + + /** + * Like nsIDocShellTreeItem::GetSameTypeRootTreeItem, except this ignores + * <iframe mozbrowser> and <iframe mozapp> boundaries. + */ + nsIDocShell getSameTypeRootTreeItemIgnoreBrowserAndAppBoundaries(); + + /** + * True iff asynchronous panning and zooming is enabled for this + * docshell. + */ + readonly attribute bool asyncPanZoomEnabled; + + /** + * The sandbox flags on the docshell. These reflect the value of the sandbox + * attribute of the associated IFRAME or CSP-protectable content, if + * existent. See the HTML5 spec for more details. + * These flags on the docshell reflect the current state of the sandbox + * attribute, which is modifiable. They are only used when loading new + * content, sandbox flags are also immutably set on the document when it is + * loaded. + * The sandbox flags of a document depend on the sandbox flags on its + * docshell and of its parent document, if any. + * See nsSandboxFlags.h for the possible flags. + */ + attribute unsigned long sandboxFlags; + + /** + * When a new browsing context is opened by a sandboxed document, it needs to + * keep track of the browsing context that opened it, so that it can be + * navigated by it. This is the "one permitted sandboxed navigator". + */ + attribute nsIDocShell onePermittedSandboxedNavigator; + + /** + * Returns true if we are sandboxed from aTargetDocShell. + * aTargetDocShell - the browsing context we are attempting to navigate. + */ + [noscript,notxpcom,nostdcall] bool isSandboxedFrom(in nsIDocShell aTargetDocShell); + + /** + * This member variable determines whether a document has Mixed Active Content that + * was initially blocked from loading, but the user has choosen to override the + * block and allow the content to load. mMixedContentChannel is set to the document's + * channel when the user allows mixed content. The nsMixedContentBlocker content policy + * checks if the document's root channel matches the mMixedContentChannel. If it matches, + * then Mixed Content is loaded. If it does match, mixed content is blocked. + * + * A match implies that there is definitely mixed active content on a page that was + * initially blocked by nsMixedContentBlocker and then allowed and loaded by the user. + * A miss imples that IF there is mixed active content on the page AND it was + * blocked by nsMixedContentBlocker.cpp, the user has not choosen to override + * the block. Note that if the about:config setting + * security.mixed_content.block_active_content is set to false, this boolean + * will be false, mMixedContentChannel will remain null since blocking active content has + * been disabled and hence mMixedContentChannel will never be set. + */ + attribute nsIChannel mixedContentChannel; + + /** + * Checks whether the channel associated with the root docShell is equal to + * mMixedContentChannel. If they are the same, allowMixedContent is set to true. + * Checks if the root document has a secure connection. If it is, sets + * rootHasSecureConnection to true. If the docShell is the root doc shell, + * isRootDocShell is set to true. + */ + void GetAllowMixedContentAndConnectionData(out boolean rootHasSecureConnection, out boolean allowMixedContent, out boolean isRootDocShell); + + + /** + * Are plugins allowed in the current document loaded in this docshell ? + * (if there is one). This depends on whether plugins are allowed by this + * docshell itself or if the document is sandboxed and hence plugins should + * not be allowed. + */ + [noscript, notxpcom] bool pluginsAllowedInCurrentDoc(); + + + /** + * Attribute that determines whether fullscreen is allowed to be entered for + * this subtree of the docshell tree. This is true when all iframes containing + * this docshell have their "allowfullscreen" attribute set to "true". + * fullscreenAllowed is only writable at content boundaries, where it is used + * to propagate the value of the cross process parent's iframe's + * "allowfullscreen" attribute to the child process. Setting + * fullscreenAllowed on docshells which aren't content boundaries throws an + * exception. + */ + [infallible] readonly attribute boolean fullscreenAllowed; + + void setFullscreenAllowed(in boolean allowed); + + [notxpcom] uint32_t orientationLock(); + [notxpcom] void setOrientationLock(in uint32_t orientationLock); + + [noscript, infallible] attribute boolean affectPrivateSessionLifetime; + + /** + * Indicates whether the UI may enable the character encoding menu. The UI + * must disable the menu when this property is false. + */ + [infallible] readonly attribute boolean mayEnableCharacterEncodingMenu; + + attribute nsIEditor editor; + readonly attribute boolean editable; /* this docShell is editable */ + readonly attribute boolean hasEditingSession; /* this docShell has an editing session */ + + /** + * Make this docShell editable, setting a flag that causes + * an editor to get created, either immediately, or after + * a url has been loaded. + * @param inWaitForUriLoad true to wait for a URI before + * creating the editor. + */ + void makeEditable(in boolean inWaitForUriLoad); + + /** + * Get the SHEntry associated with a child docshell + */ + nsISHEntry getChildSHEntry(in long aChildOffset); + + /** + * Add a Child SHEntry for a frameset page, given the child's loadtype. + * If aCloneChildren is true, then aCloneReference's children will be + * cloned onto aHistoryEntry. + */ + void addChildSHEntry(in nsISHEntry aCloneReference, + in nsISHEntry aHistoryEntry, + in long aChildOffset, + in unsigned long aLoadType, + in boolean aCloneChilden); + + /** + * Whether this docshell should save entries in global history. + */ + attribute boolean useGlobalHistory; + + /** + * Removes nsISHEntry objects related to this docshell from session history. + * Use this only with subdocuments, like iframes. + */ + void removeFromSessionHistory(); + + /** + * Set when an iframe/frame is added dynamically. + */ + attribute boolean createdDynamically; + + /** + * Returns false for mLSHE, true for mOSHE + */ + boolean getCurrentSHEntry(out nsISHEntry aEntry); + + /** + * Cherry picked parts of nsIController. + * They are here, because we want to call these functions + * from JS. + */ + boolean isCommandEnabled(in string command); + void doCommand(in string command); + void doCommandWithParams(in string command, in nsICommandParams aParams); + + /** + * Invisible DocShell are dummy construct to simulate DOM windows + * without any actual visual representation. They have to be marked + * at construction time, to avoid any painting activity. + */ + [noscript, notxpcom] bool IsInvisible(); + [noscript, notxpcom] void SetInvisible(in bool aIsInvisibleDochsell); + +/** + * Get the script global for the document in this docshell. +*/ + [noscript,notxpcom,nostdcall] nsIScriptGlobalObject GetScriptGlobalObject(); + + /** + * If deviceSizeIsPageSize is set to true, device-width/height media queries + * will be calculated from the page size, not the device size. + * + * Used by the Responsive Design Mode and B2G Simulator. + * + * Default is False. + * Default value can be overriden with + * docshell.device_size_is_page_size pref. + */ + [infallible] attribute boolean deviceSizeIsPageSize; + + /** + * Regarding setOpener / getOpener - We can't use XPIDL's "attribute" + * for notxpcom, so we're relegated to using explicit gets / sets. This + * should be fine, considering that these methods should only ever be + * called from native code. + */ + [noscript,notxpcom,nostdcall] void setOpener(in nsITabParent aOpener); + [noscript,notxpcom,nostdcall] nsITabParent getOpener(); + + /** + * Notify DocShell when the browser is about to start executing JS, and after + * that execution has stopped. This only occurs when the Timeline devtool + * is collecting information. + */ + [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStart(in string aReason, + in wstring functionName, + in wstring fileName, + in unsigned long lineNumber, + in jsval asyncStack, + in string asyncCause); + [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStop(); + + /** + * This attribute determines whether a document which is not about:blank has + * already be loaded by this docShell. + */ + [infallible] readonly attribute boolean hasLoadedNonBlankURI; + + /** + * Allow usage of -moz-window-dragging:drag for content docshells. + * True for top level chrome docshells. Throws if set to false with + * top level chrome docshell. + */ + attribute boolean windowDraggingAllowed; + + /** + * Sets/gets the current scroll restoration mode. + * @see https://html.spec.whatwg.org/#dom-history-scroll-restoration + */ + attribute boolean currentScrollRestorationIsManual; + + /** + * Setter and getter for the origin attributes living on this docshell. + */ + [implicit_jscontext] + jsval getOriginAttributes(); + + [implicit_jscontext] + void setOriginAttributes(in jsval aAttrs); + + /** + * The editing session for this docshell. + */ + readonly attribute nsIEditingSession editingSession; + + /** + * The tab child for this docshell. + */ + [binaryname(ScriptableTabChild)] readonly attribute nsITabChild tabChild; + [noscript,notxpcom,nostdcall] TabChildRef GetTabChild(); + + [noscript,nostdcall,notxpcom] nsICommandManager GetCommandManager(); + + /** + * This allows chrome to override the default choice of whether touch events + * are available on a specific docshell. Possible values are listed below. + */ + attribute unsigned long touchEventsOverride; + /** + * Override platform/pref default behaviour and force-disable touch events. + */ + const unsigned long TOUCHEVENTS_OVERRIDE_DISABLED = 0; + /** + * Override platform/pref default behaviour and force-enable touch events. + */ + const unsigned long TOUCHEVENTS_OVERRIDE_ENABLED = 1; + /** + * Don't override the platform/pref default behaviour for touch events. + */ + const unsigned long TOUCHEVENTS_OVERRIDE_NONE = 2; + + /** + * A DocShell is locked to the current process if it would be + * content-observable for a process switch to occur before performing a + * navigation load. It is important to ensure that a DocShell is not process + * locked before perfoming process changing loads. + */ + [infallible] readonly attribute unsigned long processLockReason; + /** + * The DocShell is not locked to the current process, and a navigation may + * proceed in a new process. + */ + const unsigned long PROCESS_LOCK_NONE = 0; + /** + * The DocShell is locked to the current process because it is not a + * toplevel browsing context. + */ + const unsigned long PROCESS_LOCK_IFRAME = 1; + /** + * The DocShell is locked to the current process because there exist other + * related browsing contexts which may be holding a reference. + */ + const unsigned long PROCESS_LOCK_RELATED_CONTEXTS = 2; + /** + * The DocShell is locked to the current process because the current + * process is not a content process. + * + * NOTE: Some loads may not consider this a hard process lock, and may wish to + * ignore this reason. + */ + const unsigned long PROCESS_LOCK_NON_CONTENT = 3; +}; diff --git a/docshell/base/nsIDocShellLoadInfo.idl b/docshell/base/nsIDocShellLoadInfo.idl new file mode 100644 index 000000000..113c0a4c1 --- /dev/null +++ b/docshell/base/nsIDocShellLoadInfo.idl @@ -0,0 +1,125 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * The nsIDocShellLoadInfo interface defines an interface for specifying + * setup information used in a nsIDocShell::loadURI call. + */ + +interface nsIURI; +interface nsIInputStream; +interface nsISHEntry; +interface nsIDocShell; +interface nsIPrincipal; + +typedef long nsDocShellInfoLoadType; +typedef unsigned long nsDocShellInfoReferrerPolicy; + +[scriptable, uuid(e7570e5a-f1d6-452d-b4f8-b35fdc63aa03)] +interface nsIDocShellLoadInfo : nsISupports +{ + /** This is the referrer for the load. */ + attribute nsIURI referrer; + + /** + * The originalURI to be passed to nsIDocShell.internalLoad. May be null. + */ + attribute nsIURI originalURI; + + /** + * loadReplace flag to be passed to nsIDocShell.internalLoad. + */ + attribute boolean loadReplace; + + /** The principal of the load, that is, the entity responsible for + * causing the load to occur. In most cases the referrer and + * the triggeringPrincipal's URI will be identical. + */ + attribute nsIPrincipal triggeringPrincipal; + + /** If this attribute is true and no triggeringPrincipal is specified, + * copy the principal from the referring document. + */ + attribute boolean inheritPrincipal; + + /** If this attribute is true only ever use the principal specified + * by the triggeringPrincipal and inheritPrincipal attributes. + * If there are security reasons for why this is unsafe, such + * as trying to use a systemprincipal as the triggeringPrincipal + * for a content docshell the load fails. + */ + attribute boolean principalIsExplicit; + + /* these are load type enums... */ + const long loadNormal = 0; // Normal Load + const long loadNormalReplace = 1; // Normal Load but replaces current history slot + const long loadHistory = 2; // Load from history + const long loadReloadNormal = 3; // Reload + const long loadReloadBypassCache = 4; + const long loadReloadBypassProxy = 5; + const long loadReloadBypassProxyAndCache = 6; + const long loadLink = 7; + const long loadRefresh = 8; + const long loadReloadCharsetChange = 9; + const long loadBypassHistory = 10; + const long loadStopContent = 11; + const long loadStopContentAndReplace = 12; + const long loadNormalExternal = 13; + const long loadNormalBypassCache = 14; + const long loadNormalBypassProxy = 15; + const long loadNormalBypassProxyAndCache = 16; + const long loadPushState = 17; // history.pushState or replaceState + const long loadReplaceBypassCache = 18; + const long loadReloadMixedContent = 19; + const long loadNormalAllowMixedContent = 20; + + /** Contains a load type as specified by the load* constants */ + attribute nsDocShellInfoLoadType loadType; + + /** SHEntry for this page */ + attribute nsISHEntry SHEntry; + + /** Target for load, like _content, _blank etc. */ + attribute wstring target; + + /** Post data */ + attribute nsIInputStream postDataStream; + + /** Additional headers */ + attribute nsIInputStream headersStream; + + /** True if the referrer should be sent, false if it shouldn't be + * sent, even if it's available. This attribute defaults to true. + */ + attribute boolean sendReferrer; + + /** Referrer policy for the load. This attribute holds one of + * the values (REFERRER_POLICY_*) defined in nsIHttpChannel. + */ + attribute nsDocShellInfoReferrerPolicy referrerPolicy; + + /** True if the docshell has been created to load an iframe where the + * srcdoc attribute has been set. Set when srcdocData is specified. + */ + readonly attribute boolean isSrcdocLoad; + + /** When set, the load will be interpreted as a srcdoc load, where contents + * of this string will be loaded instead of the URI. Setting srcdocData + * sets isSrcdocLoad to true + */ + attribute AString srcdocData; + + /** When set, this is the Source Browsing Context for the navigation. */ + attribute nsIDocShell sourceDocShell; + + /** + * Used for srcdoc loads to give view-source knowledge of the load's base + * URI as this information isn't embedded in the load's URI. + */ + attribute nsIURI baseURI; +}; diff --git a/docshell/base/nsIDocShellTreeItem.idl b/docshell/base/nsIDocShellTreeItem.idl new file mode 100644 index 000000000..431b16c79 --- /dev/null +++ b/docshell/base/nsIDocShellTreeItem.idl @@ -0,0 +1,184 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDocShellTreeOwner; +interface nsIDocument; +interface nsPIDOMWindowOuter; + + +/** + * The nsIDocShellTreeItem supplies the methods that are required of any item + * that wishes to be able to live within the docshell tree either as a middle + * node or a leaf. + */ + +[scriptable, uuid(9b7c586f-9214-480c-a2c4-49b526fff1a6)] +interface nsIDocShellTreeItem : nsISupports +{ + /* + name of the DocShellTreeItem + */ + attribute AString name; + + /** + * Compares the provided name against the item's name and + * returns the appropriate result. + * + * @return <CODE>PR_TRUE</CODE> if names match; + * <CODE>PR_FALSE</CODE> otherwise. + */ + boolean nameEquals(in AString name); + + /* + Definitions for the item types. + */ + const long typeChrome=0; // typeChrome must equal 0 + const long typeContent=1; // typeContent must equal 1 + const long typeContentWrapper=2; // typeContentWrapper must equal 2 + const long typeChromeWrapper=3; // typeChromeWrapper must equal 3 + + const long typeAll=0x7FFFFFFF; + + /* + The type this item is. + */ + attribute long itemType; + [noscript,notxpcom,nostdcall] long ItemType(); + + /* + Parent DocShell. + */ + readonly attribute nsIDocShellTreeItem parent; + + /* + This getter returns the same thing parent does however if the parent + is of a different itemType, or if the parent is an <iframe mozbrowser> + or <iframe mozapp>, it will instead return nullptr. This call is a + convience function for those wishing to not cross the boundaries at + which item types change. + */ + readonly attribute nsIDocShellTreeItem sameTypeParent; + + /* + Returns the root DocShellTreeItem. This is a convience equivalent to + getting the parent and its parent until there isn't a parent. + */ + readonly attribute nsIDocShellTreeItem rootTreeItem; + + /* + Returns the root DocShellTreeItem of the same type. This is a convience + equivalent to getting the parent of the same type and its parent until + there isn't a parent. + */ + readonly attribute nsIDocShellTreeItem sameTypeRootTreeItem; + + /* + Returns the docShellTreeItem with the specified name. Search order is as + follows... + 1.) Check name of self, if it matches return it. + 2.) For each immediate child. + a.) Check name of child and if it matches return it. + b.) Ask the child to perform the check + i.) Do not ask a child if it is the aRequestor + ii.) Do not ask a child if it is of a different item type. + 3.) If there is a parent of the same item type ask parent to perform the check + a.) Do not ask parent if it is the aRequestor + 4.) If there is a tree owner ask the tree owner to perform the check + a.) Do not ask the tree owner if it is the aRequestor + b.) This should only be done if there is no parent of the same type. + + Return the child DocShellTreeItem with the specified name. + name - This is the name of the item that is trying to be found. + aRequestor - This is the object that is requesting the find. This + parameter is used to identify when the child is asking its parent to find + a child with the specific name. The parent uses this parameter to ensure + a resursive state does not occur by not again asking the requestor to find + a shell by the specified name. Inversely the child uses it to ensure it + does not ask its parent to do the search if its parent is the one that + asked it to search. Children also use this to test against the treeOwner; + aOriginalRequestor - The original treeitem that made the request, if any. + This is used to ensure that we don't run into cross-site issues. + */ + nsIDocShellTreeItem findItemWithName(in AString name, + in nsISupports aRequestor, + in nsIDocShellTreeItem aOriginalRequestor); + + /* + The owner of the DocShell Tree. This interface will be called upon when + the docshell has things it needs to tell to the owner of the docshell. + Note that docShell tree ownership does not cross tree types. Meaning + setting ownership on a chrome tree does not set ownership on the content + sub-trees. A given tree's boundaries are identified by the type changes. + Trees of different types may be connected, but should not be traversed + for things such as ownership. + + Note implementers of this interface should NOT effect the lifetime of the + parent DocShell by holding this reference as it creates a cycle. Owners + when releasing this interface should set the treeOwner to nullptr. + Implementers of this interface are guaranteed that when treeOwner is + set that the poitner is valid without having to addref. + + Further note however when others try to get the interface it should be + addref'd before handing it to them. + */ + readonly attribute nsIDocShellTreeOwner treeOwner; + [noscript] void setTreeOwner(in nsIDocShellTreeOwner treeOwner); + + /* + The current number of DocShells which are immediate children of the + this object. + */ + readonly attribute long childCount; + + /* + Add a new child DocShellTreeItem. Adds to the end of the list. + Note that this does NOT take a reference to the child. The child stays + alive only as long as it's referenced from outside the docshell tree. + @throws NS_ERROR_ILLEGAL_VALUE if child corresponds to the same + object as this treenode or an ancestor of this treenode + @throws NS_ERROR_UNEXPECTED if this node is a leaf in the tree. + */ + void addChild(in nsIDocShellTreeItem child); + + /* + Removes a child DocShellTreeItem. + @throws NS_ERROR_UNEXPECTED if this node is a leaf in the tree. + */ + void removeChild(in nsIDocShellTreeItem child); + + /** + * Return the child at the index requested. This is 0-based. + * + * @throws NS_ERROR_UNEXPECTED if the index is out of range + */ + nsIDocShellTreeItem getChildAt(in long index); + + /* + Return the child DocShellTreeItem with the specified name. + aName - This is the name of the item that is trying to be found. + aRecurse - Is used to tell the function to recurse through children. + Note, recursion will only happen through items of the same type. + aSameType - If this is set only children of the same type will be returned. + aRequestor - This is the docshellTreeItem that is requesting the find. This + parameter is used when recursion is being used to avoid searching the same + tree again when a child has asked a parent to search for children. + aOriginalRequestor - The original treeitem that made the request, if any. + This is used to ensure that we don't run into cross-site issues. + + Note the search is depth first when recursing. + */ + nsIDocShellTreeItem findChildWithName(in AString aName, + in boolean aRecurse, + in boolean aSameType, + in nsIDocShellTreeItem aRequestor, + in nsIDocShellTreeItem aOriginalRequestor); + + [noscript,nostdcall,notxpcom] nsIDocument getDocument(); + [noscript,nostdcall,notxpcom] nsPIDOMWindowOuter getWindow(); +}; + diff --git a/docshell/base/nsIDocShellTreeOwner.idl b/docshell/base/nsIDocShellTreeOwner.idl new file mode 100644 index 000000000..3610bd969 --- /dev/null +++ b/docshell/base/nsIDocShellTreeOwner.idl @@ -0,0 +1,109 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * The nsIDocShellTreeOwner + */ + +interface nsIDocShellTreeItem; +interface nsITabParent; + +[scriptable, uuid(0e3dc4b1-4cea-4a37-af71-79f0afd07574)] +interface nsIDocShellTreeOwner : nsISupports +{ + /** + * Called when a content shell is added to the docshell tree. This is + * _only_ called for "root" content shells (that is, ones whose parent is a + * chrome shell). + * + * @param aContentShell the shell being added. + * @param aPrimary whether the shell is primary. + * @param aTargetable whether the shell can be a target for named window + * targeting. + * @param aID the "id" of the shell. What this actually means is + * undefined. Don't rely on this for anything. + */ + void contentShellAdded(in nsIDocShellTreeItem aContentShell, + in boolean aPrimary, in boolean aTargetable, + in AString aID); + + /** + * Called when a content shell is removed from the docshell tree. This is + * _only_ called for "root" content shells (that is, ones whose parent is a + * chrome shell). Note that if aContentShell was never added, + * contentShellRemoved should just do nothing. + * + * @param aContentShell the shell being removed. + */ + void contentShellRemoved(in nsIDocShellTreeItem aContentShell); + + /* + Returns the Primary Content Shell + */ + readonly attribute nsIDocShellTreeItem primaryContentShell; + + void tabParentAdded(in nsITabParent aTab, in boolean aPrimary); + void tabParentRemoved(in nsITabParent aTab); + + /* + In multiprocess case we may not have primaryContentShell but + primaryTabParent. + */ + readonly attribute nsITabParent primaryTabParent; + + /* + Tells the tree owner to size its window or parent window in such a way + that the shell passed along will be the size specified. + */ + void sizeShellTo(in nsIDocShellTreeItem shell, in long cx, in long cy); + + /* + Gets the size of the primary content area in CSS pixels. This should work + for both in-process and out-of-process content areas. + */ + void getPrimaryContentSize(out long width, out long height); + /* + Sets the size of the primary content area in CSS pixels. This should work + for both in-process and out-of-process content areas. + */ + void setPrimaryContentSize(in long width, in long height); + + /* + Gets the size of the root docshell in CSS pixels. + */ + void getRootShellSize(out long width, out long height); + /* + Sets the size of the root docshell in CSS pixels. + */ + void setRootShellSize(in long width, in long height); + + /* + Sets the persistence of different attributes of the window. + */ + void setPersistence(in boolean aPersistPosition, + in boolean aPersistSize, + in boolean aPersistSizeMode); + + /* + Gets the current persistence states of the window. + */ + void getPersistence(out boolean aPersistPosition, + out boolean aPersistSize, + out boolean aPersistSizeMode); + + /* + Gets the number of targettable docshells. + */ + readonly attribute unsigned long targetableShellCount; + + /* + Returns true if there is a primary content shell or a primary + tab parent. + */ + readonly attribute bool hasPrimaryContent; +}; diff --git a/docshell/base/nsIDocumentLoaderFactory.idl b/docshell/base/nsIDocumentLoaderFactory.idl new file mode 100644 index 000000000..7e0df1fd7 --- /dev/null +++ b/docshell/base/nsIDocumentLoaderFactory.idl @@ -0,0 +1,46 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIChannel; +interface nsIContentViewer; +interface nsIStreamListener; +interface nsIDocShell; +interface nsIDocument; +interface nsILoadGroup; +interface nsIPrincipal; + +/** + * To get a component that implements nsIDocumentLoaderFactory + * for a given mimetype, use nsICategoryManager to find an entry + * with the mimetype as its name in the category "Gecko-Content-Viewers". + * The value of the entry is the contractid of the component. + * The component is a service, so use GetService, not CreateInstance to get it. + */ + +[scriptable, uuid(e795239e-9d3c-47c4-b063-9e600fb3b287)] +interface nsIDocumentLoaderFactory : nsISupports { + nsIContentViewer createInstance(in string aCommand, + in nsIChannel aChannel, + in nsILoadGroup aLoadGroup, + in ACString aContentType, + in nsIDocShell aContainer, + in nsISupports aExtraInfo, + out nsIStreamListener aDocListenerResult); + + nsIContentViewer createInstanceForDocument(in nsISupports aContainer, + in nsIDocument aDocument, + in string aCommand); + + /** + * Create a blank document using the given loadgroup and given + * principal. aPrincipal is allowed to be null, in which case the + * new document will get the about:blank codebase principal. + */ + nsIDocument createBlankDocument(in nsILoadGroup aLoadGroup, + in nsIPrincipal aPrincipal); +}; diff --git a/docshell/base/nsIDownloadHistory.idl b/docshell/base/nsIDownloadHistory.idl new file mode 100644 index 000000000..ed2a19deb --- /dev/null +++ b/docshell/base/nsIDownloadHistory.idl @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 sts=2 + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; + +/** + * This interface can be used to add a download to history. There is a separate + * interface specifically for downloads in case embedders choose to track + * downloads differently from other types of history. + */ +[scriptable, uuid(4dcd6a12-a091-4f38-8360-022929635746)] +interface nsIDownloadHistory : nsISupports { + /** + * Adds a download to history. This will also notify observers that the + * URI aSource is visited with the topic NS_LINK_VISITED_EVENT_TOPIC if + * aSource has not yet been visited. + * + * @param aSource + * The source of the download we are adding to history. This cannot be + * null. + * @param aReferrer + * [optional] The referrer of source URI. + * @param aStartTime + * [optional] The time the download was started. If the start time + * is not given, the current time is used. + * @param aDestination + * [optional] The target where the download is to be saved on the local + * filesystem. + * @throws NS_ERROR_NOT_AVAILABLE + * In a situation where a history implementation is not available, + * where 'history implementation' refers to something like + * nsIGlobalHistory and friends. + * @note This addition is not guaranteed to be synchronous, since it delegates + * the actual addition to the underlying history implementation. If you + * need to observe the completion of the addition, use the underlying + * history implementation's notifications system (e.g. nsINavHistoryObserver + * for toolkit's implementation of this interface). + */ + void addDownload(in nsIURI aSource, [optional] in nsIURI aReferrer, + [optional] in PRTime aStartTime, + [optional] in nsIURI aDestination); + + /** + * Remove all downloads from history. + * + * @note This removal is not guaranteed to be synchronous, since it delegates + * the actual removal to the underlying history implementation. If you + * need to observe the completion of the removal, use the underlying + * history implementation's notifications system (e.g. nsINavHistoryObserver + * for toolkit's implementation of this interface). + */ + void removeAllDownloads(); +}; diff --git a/docshell/base/nsIGlobalHistory2.idl b/docshell/base/nsIGlobalHistory2.idl new file mode 100644 index 000000000..aa8b44620 --- /dev/null +++ b/docshell/base/nsIGlobalHistory2.idl @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Provides information about global history to gecko. + * + * @note This interface replaces and deprecates nsIGlobalHistory. + */ + +#include "nsISupports.idl" +interface nsIURI; + +%{ C++ + +// nsIObserver topic to fire when you add new visited URIs to the history; +// the nsIURI is the subject +#define NS_LINK_VISITED_EVENT_TOPIC "link-visited" + +%} + +[scriptable, uuid(cf777d42-1270-4b34-be7b-2931c93feda5)] +interface nsIGlobalHistory2 : nsISupports +{ + /** + * Add a URI to global history + * + * @param aURI the URI of the page + * @param aRedirect whether the URI was redirected to another location; + * this is 'true' for the original URI which is + * redirected. + * @param aToplevel whether the URI is loaded in a top-level window + * @param aReferrer the URI of the referring page + * + * @note Docshell will not filter out URI schemes like chrome: data: + * about: and view-source:. Embedders should consider filtering out + * these schemes and others, e.g. mailbox: for the main URI and the + * referrer. + */ + void addURI(in nsIURI aURI, in boolean aRedirect, in boolean aToplevel, in nsIURI aReferrer); + + /** + * Checks to see whether the given URI is in history. + * + * @param aURI the uri to the page + * @return true if a URI has been visited + */ + boolean isVisited(in nsIURI aURI); + + /** + * Set the page title for the given uri. URIs that are not already in + * global history will not be added. + * + * @param aURI the URI for which to set to the title + * @param aTitle the page title + */ + void setPageTitle(in nsIURI aURI, in AString aTitle); +}; diff --git a/docshell/base/nsILinkHandler.h b/docshell/base/nsILinkHandler.h new file mode 100644 index 000000000..7cdcd566d --- /dev/null +++ b/docshell/base/nsILinkHandler.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsILinkHandler_h___ +#define nsILinkHandler_h___ + +#include "nsISupports.h" +#include "mozilla/EventForwards.h" + +class nsIContent; +class nsIDocShell; +class nsIInputStream; +class nsIRequest; + +#define NS_ILINKHANDLER_IID \ + { 0xceb9aade, 0x43da, 0x4f1a, \ + { 0xac, 0x8a, 0xc7, 0x09, 0xfb, 0x22, 0x46, 0x64 } } + +/** + * Interface used for handling clicks on links + */ +class nsILinkHandler : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ILINKHANDLER_IID) + + /** + * Process a click on a link. + * + * @param aContent the content for the frame that generated the trigger + * @param aURI a URI object that defines the destination for the link + * @param aTargetSpec indicates where the link is targeted (may be an empty + * string) + * @param aPostDataStream the POST data to send + * @param aFileName non-null when the link should be downloaded as the given file + * @param aHeadersDataStream ??? + * @param aIsTrusted false if the triggerer is an untrusted DOM event. + */ + NS_IMETHOD OnLinkClick(nsIContent* aContent, + nsIURI* aURI, + const char16_t* aTargetSpec, + const nsAString& aFileName, + nsIInputStream* aPostDataStream, + nsIInputStream* aHeadersDataStream, + bool aIsTrusted) = 0; + + /** + * Process a click on a link. + * + * Works the same as OnLinkClick() except it happens immediately rather than + * through an event. + * + * @param aContent the content for the frame that generated the trigger + * @param aURI a URI obect that defines the destination for the link + * @param aTargetSpec indicates where the link is targeted (may be an empty + * string) + * @param aFileName non-null when the link should be downloaded as the given file + * @param aPostDataStream the POST data to send + * @param aHeadersDataStream ??? + * @param aDocShell (out-param) the DocShell that the request was opened on + * @param aRequest the request that was opened + */ + NS_IMETHOD OnLinkClickSync(nsIContent* aContent, + nsIURI* aURI, + const char16_t* aTargetSpec, + const nsAString& aFileName, + nsIInputStream* aPostDataStream = 0, + nsIInputStream* aHeadersDataStream = 0, + nsIDocShell** aDocShell = 0, + nsIRequest** aRequest = 0) = 0; + + /** + * Process a mouse-over a link. + * + * @param aContent the linked content. + * @param aURI an URI object that defines the destination for the link + * @param aTargetSpec indicates where the link is targeted (it may be an empty + * string) + */ + NS_IMETHOD OnOverLink(nsIContent* aContent, + nsIURI* aURLSpec, + const char16_t* aTargetSpec) = 0; + + /** + * Process the mouse leaving a link. + */ + NS_IMETHOD OnLeaveLink() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsILinkHandler, NS_ILINKHANDLER_IID) + +#endif /* nsILinkHandler_h___ */ diff --git a/docshell/base/nsILoadContext.idl b/docshell/base/nsILoadContext.idl new file mode 100644 index 000000000..b0da7597b --- /dev/null +++ b/docshell/base/nsILoadContext.idl @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: ft=cpp tw=78 sw=2 et ts=2 sts=2 cin + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface mozIDOMWindowProxy; +interface nsIDOMElement; + +%{C++ +#ifdef MOZILLA_INTERNAL_API +#include "mozilla/BasePrincipal.h" // for DocShellOriginAttributes +#endif +%} + +/** + * An nsILoadContext represents the context of a load. This interface + * can be queried for various information about where the load is + * happening. + */ +[scriptable, uuid(2813a7a3-d084-4d00-acd0-f76620315c02)] +interface nsILoadContext : nsISupports +{ + /** + * associatedWindow is the window with which the load is associated, if any. + * Note that the load may be triggered by a document which is different from + * the document in associatedWindow, and in fact the source of the load need + * not be same-origin with the document in associatedWindow. This attribute + * may be null if there is no associated window. + */ + readonly attribute mozIDOMWindowProxy associatedWindow; + + /** + * topWindow is the top window which is of same type as associatedWindow. + * This is equivalent to associatedWindow.top, but is provided here as a + * convenience. All the same caveats as associatedWindow of apply, of + * course. This attribute may be null if there is no associated window. + */ + readonly attribute mozIDOMWindowProxy topWindow; + + /** + * topFrameElement is the <iframe>, <frame>, or <browser> element which + * contains the topWindow with which the load is associated. + * + * Note that we may have a topFrameElement even when we don't have an + * associatedWindow, if the topFrameElement's content lives out of process. + * topFrameElement is available in single-process and multiprocess contexts. + * Note that topFrameElement may be in chrome even when the nsILoadContext is + * associated with content. + */ + readonly attribute nsIDOMElement topFrameElement; + + /** + * If this LoadContext corresponds to a nested remote iframe, we don't have + * access to the topFrameElement. Instead, we must use this id to send + * messages. A return value of 0 signifies that this load context is not for + * a nested frame. + */ + readonly attribute unsigned long long nestedFrameId; + + /** + * True if the load context is content (as opposed to chrome). This is + * determined based on the type of window the load is performed in, NOT based + * on any URIs that might be around. + */ + readonly attribute boolean isContent; + + /* + * Attribute that determines if private browsing should be used. May not be + * changed after a document has been loaded in this context. + */ + attribute boolean usePrivateBrowsing; + + /** + * Attribute that determines if remote (out-of-process) tabs should be used. + */ + readonly attribute boolean useRemoteTabs; + +%{C++ + /** + * De-XPCOMed getter to make call-sites cleaner. + */ + bool UsePrivateBrowsing() { + bool usingPB; + GetUsePrivateBrowsing(&usingPB); + return usingPB; + } + + bool UseRemoteTabs() { + bool usingRT; + GetUseRemoteTabs(&usingRT); + return usingRT; + } +%} + + /** + * Set the private browsing state of the load context, meant to be used internally. + */ + [noscript] void SetPrivateBrowsing(in boolean aInPrivateBrowsing); + + /** + * Set the remote tabs state of the load context, meant to be used internally. + */ + [noscript] void SetRemoteTabs(in boolean aUseRemoteTabs); + + /** + * Returns true iff the load is occurring inside an isolated mozbrowser + * element. <iframe mozbrowser mozapp> and <xul:browser> are not considered to + * be mozbrowser elements. <iframe mozbrowser noisolation> does not count as + * isolated since isolation is disabled. Isolation can only be disabled if + * the containing document is chrome. + */ + readonly attribute boolean isInIsolatedMozBrowserElement; + + /** + * Returns the app id of the app the load is occurring is in. Returns + * nsIScriptSecurityManager::NO_APP_ID if the load is not part of an app. + */ + readonly attribute unsigned long appId; + + /** + * A dictionary of the non-default origin attributes associated with this + * nsILoadContext. + */ + readonly attribute jsval originAttributes; + +%{C++ +#ifdef MOZILLA_INTERNAL_API + /** + * The C++ getter for origin attributes. + * + * Defined in LoadContext.cpp + */ + bool GetOriginAttributes(mozilla::DocShellOriginAttributes& aAttrs); +#endif +%} + + /** + * Returns true if tracking protection is enabled for the load context. + */ + boolean IsTrackingProtectionOn(); + +%{C++ + /** + * De-XPCOMed getter to make call-sites cleaner. + */ + bool UseTrackingProtection() { + bool usingTP; + IsTrackingProtectionOn(&usingTP); + return usingTP; + } +%} +}; diff --git a/docshell/base/nsIPrivacyTransitionObserver.idl b/docshell/base/nsIPrivacyTransitionObserver.idl new file mode 100644 index 000000000..c85d468d3 --- /dev/null +++ b/docshell/base/nsIPrivacyTransitionObserver.idl @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, function, uuid(b4b1449d-0ef0-47f5-b62e-adc57fd49702)] +interface nsIPrivacyTransitionObserver : nsISupports +{ + void privateModeChanged(in bool enabled); +}; diff --git a/docshell/base/nsIReflowObserver.idl b/docshell/base/nsIReflowObserver.idl new file mode 100644 index 000000000..fb602e260 --- /dev/null +++ b/docshell/base/nsIReflowObserver.idl @@ -0,0 +1,31 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "domstubs.idl" + +[scriptable, uuid(832e692c-c4a6-11e2-8fd1-dce678957a39)] +interface nsIReflowObserver : nsISupports +{ + /** + * Called when an uninterruptible reflow has occurred. + * + * @param start timestamp when reflow ended, in milliseconds since + * navigationStart (accurate to 1/1000 of a ms) + * @param end timestamp when reflow ended, in milliseconds since + * navigationStart (accurate to 1/1000 of a ms) + */ + void reflow(in DOMHighResTimeStamp start, + in DOMHighResTimeStamp end); + + /** + * Called when an interruptible reflow has occurred. + * + * @param start timestamp when reflow ended, in milliseconds since + * navigationStart (accurate to 1/1000 of a ms) + * @param end timestamp when reflow ended, in milliseconds since + * navigationStart (accurate to 1/1000 of a ms) + */ + void reflowInterruptible(in DOMHighResTimeStamp start, + in DOMHighResTimeStamp end); +}; diff --git a/docshell/base/nsIRefreshURI.idl b/docshell/base/nsIRefreshURI.idl new file mode 100644 index 000000000..5abd829da --- /dev/null +++ b/docshell/base/nsIRefreshURI.idl @@ -0,0 +1,91 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIChannel; +interface nsIPrincipal; +interface nsIURI; + +[scriptable, uuid(a5e61a3c-51bd-45be-ac0c-e87b71860656)] +interface nsIRefreshURI : nsISupports { + /** + * Load a uri after waiting for aMillis milliseconds. If the docshell + * is busy loading a page currently, the refresh request will be + * queued and executed when the current load finishes. + * + * @param aUri The uri to refresh. + * @param aPrincipal The triggeringPrincipal for the refresh load + * May be null, in which case a principal will be built based on the + * referrer URI of the previous docshell load, or will use the system + * principal when there is no referrer. + * @param aMillis The number of milliseconds to wait. + * @param aRepeat Flag to indicate if the uri is to be + * repeatedly refreshed every aMillis milliseconds. + * @param aMetaRefresh Flag to indicate if this is a Meta refresh. + */ + void refreshURI(in nsIURI aURI, + in long aMillis, in boolean aRepeat, + in boolean aMetaRefresh, + [optional] in nsIPrincipal aPrincipal); + + /** + * Loads a URI immediately as if it were a refresh. + * + * @param aURI The URI to refresh. + * @param aPrincipal The triggeringPrincipal for the refresh load + * May be null, in which case a principal will be built based on the + * referrer URI of the previous docshell load, or will use the system + * principal when there is no referrer. + * @param aMillis The number of milliseconds by which this refresh would + * be delayed if it were not being forced. + * @param aMetaRefresh Flag to indicate if this is a meta refresh. + */ + void forceRefreshURI(in nsIURI aURI, + in long aMillis, in boolean aMetaRefresh, + [optional] in nsIPrincipal aPrincipal); + + /** + * Checks the passed in channel to see if there is a refresh header, + * if there is, will setup a timer to refresh the uri found + * in the header. If docshell is busy loading a page currently, the + * request will be queued and executed when the current page + * finishes loading. + * + * Returns the NS_REFRESHURI_HEADER_FOUND success code if a refresh + * header was found and successfully setup. + * + * @param aChannel The channel to be parsed. + */ + void setupRefreshURI(in nsIChannel aChannel); + + /** + * Parses the passed in header string and sets up a refreshURI if + * a "refresh" header is found. If docshell is busy loading a page + * currently, the request will be queued and executed when + * the current page finishes loading. + * + * @param aBaseURI base URI to resolve refresh uri with. + * @param aPrincipal The triggeringPrincipal for the refresh load + * May be null, in which case a principal will be built based on the + * referrer URI of the previous docshell load, or will use the system + * principal when there is no referrer. + * @param aHeader The meta refresh header string. + */ + void setupRefreshURIFromHeader(in nsIURI aBaseURI, + in nsIPrincipal principal, + in ACString aHeader); + + /** + * Cancels all timer loads. + */ + void cancelRefreshURITimers(); + + /** + * True when there are pending refreshes, false otherwise. + */ + readonly attribute boolean refreshPending; +}; diff --git a/docshell/base/nsIScrollObserver.h b/docshell/base/nsIScrollObserver.h new file mode 100644 index 000000000..82c7cabe9 --- /dev/null +++ b/docshell/base/nsIScrollObserver.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsIScrollObserver_h___ +#define nsIScrollObserver_h___ + +#include "nsISupports.h" +#include "Units.h" + +#define NS_ISCROLLOBSERVER_IID \ + { 0xaa5026eb, 0x2f88, 0x4026, \ + { 0xa4, 0x6b, 0xf4, 0x59, 0x6b, 0x4e, 0xdf, 0x00 } } + +class nsIScrollObserver : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCROLLOBSERVER_IID) + + /** + * Called when the scroll position of some element has changed. + */ + virtual void ScrollPositionChanged() = 0; + + /** + * Called when an async panning/zooming transform has started being applied + * and passed the scroll offset + */ + virtual void AsyncPanZoomStarted() {}; + + /** + * Called when an async panning/zooming transform is no longer applied + * and passed the scroll offset + */ + virtual void AsyncPanZoomStopped() {}; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIScrollObserver, NS_ISCROLLOBSERVER_IID) + +#endif /* nsIScrollObserver_h___ */ diff --git a/docshell/base/nsIScrollable.idl b/docshell/base/nsIScrollable.idl new file mode 100644 index 000000000..931469528 --- /dev/null +++ b/docshell/base/nsIScrollable.idl @@ -0,0 +1,55 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=2 sw=2 et tw=78: + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * The nsIScrollable is an interface that can be implemented by a control that + * supports scrolling. This is a generic interface without concern for the + * type of content that may be inside. + */ +[scriptable, uuid(3507fc93-313e-4a4c-8ca8-4d0ea0f97315)] +interface nsIScrollable : nsISupports +{ + /** + * Constants declaring the two scroll orientations a scrollbar can be in. + * ScrollOrientation_X - Horizontal scrolling. When passing this + * in to a method you are requesting or setting data for the + * horizontal scrollbar. + * ScrollOrientation_Y - Vertical scrolling. When passing this + * in to a method you are requesting or setting data for the + * vertical scrollbar. + */ + const long ScrollOrientation_X = 1; + const long ScrollOrientation_Y = 2; + + /** + * Constants declaring the states of the scrollbars. + * ScrollPref_Auto - bars visible only when needed. + * ScrollPref_Never - bars never visible, even when scrolling still possible. + * ScrollPref_Always - bars always visible, even when scrolling is not possible + */ + const long Scrollbar_Auto = 1; + const long Scrollbar_Never = 2; + const long Scrollbar_Always = 3; + + /** + * Get or set the default scrollbar state for all documents in + * this shell. + */ + long getDefaultScrollbarPreferences(in long scrollOrientation); + void setDefaultScrollbarPreferences(in long scrollOrientation, + in long scrollbarPref); + + /** + * Get information about whether the vertical and horizontal scrollbars are + * currently visible. If you are only interested in one of the visibility + * settings pass nullptr in for the one you aren't interested in. + */ + void getScrollbarVisibility(out boolean verticalVisible, + out boolean horizontalVisible); +}; diff --git a/docshell/base/nsITextScroll.idl b/docshell/base/nsITextScroll.idl new file mode 100644 index 000000000..87338c491 --- /dev/null +++ b/docshell/base/nsITextScroll.idl @@ -0,0 +1,33 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * The nsITextScroll is an interface that can be implemented by a control that + * supports text scrolling. + */ + +[scriptable, uuid(067B28A0-877F-11d3-AF7E-00A024FFC08C)] +interface nsITextScroll : nsISupports +{ + /** + * Scroll the view up or down by aNumLines lines. positive + * values move down in the view. Prevents scrolling off the + * end of the view. + * @param numLines number of lines to scroll the view by + */ + void scrollByLines(in long numLines); + + /** + * Scroll the view up or down by numPages pages. a page + * is considered to be the amount displayed by the clip view. + * positive values move down in the view. Prevents scrolling + * off the end of the view. + * @param numPages number of pages to scroll the view by + */ + void scrollByPages(in long numPages); +};
\ No newline at end of file diff --git a/docshell/base/nsIURIFixup.idl b/docshell/base/nsIURIFixup.idl new file mode 100644 index 000000000..0145aca00 --- /dev/null +++ b/docshell/base/nsIURIFixup.idl @@ -0,0 +1,166 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; +interface nsIInputStream; + +/** + * Interface indicating what we found/corrected when fixing up a URI + */ +[scriptable, uuid(4819f183-b532-4932-ac09-b309cd853be7)] +interface nsIURIFixupInfo : nsISupports +{ + /** + * Consumer that asked for fixed up URI. + */ + attribute nsISupports consumer; + + /** + * Our best guess as to what URI the consumer will want. Might + * be null if we couldn't salvage anything (for instance, because + * the input was invalid as a URI and FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP + * was not passed) + */ + readonly attribute nsIURI preferredURI; + + /** + * The fixed-up original input, *never* using a keyword search. + * (might be null if the original input was not recoverable as + * a URL, e.g. "foo bar"!) + */ + readonly attribute nsIURI fixedURI; + + /** + * The name of the keyword search provider used to provide a keyword search; + * empty string if no keyword search was done. + */ + readonly attribute AString keywordProviderName; + + /** + * The keyword as used for the search (post trimming etc.) + * empty string if no keyword search was done. + */ + readonly attribute AString keywordAsSent; + + /** + * Whether we changed the protocol instead of using one from the input as-is. + */ + readonly attribute boolean fixupChangedProtocol; + + /** + * Whether we created an alternative URI. We might have added a prefix and/or + * suffix, the contents of which are controlled by the + * browser.fixup.alternate.prefix and .suffix prefs, with the defaults being + * "www." and ".com", respectively. + */ + readonly attribute boolean fixupCreatedAlternateURI; + + /** + * The original input + */ + readonly attribute AUTF8String originalInput; +}; + + +/** + * Interface implemented by objects capable of fixing up strings into URIs + */ +[scriptable, uuid(1da7e9d4-620b-4949-849a-1cd6077b1b2d)] +interface nsIURIFixup : nsISupports +{ + /** No fixup flags. */ + const unsigned long FIXUP_FLAG_NONE = 0; + + /** + * Allow the fixup to use a keyword lookup service to complete the URI. + * The fixup object implementer should honour this flag and only perform + * any lengthy keyword (or search) operation if it is set. + */ + const unsigned long FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP = 1; + + /** + * Tell the fixup to make an alternate URI from the input URI, for example + * to turn foo into www.foo.com. + */ + const unsigned long FIXUP_FLAGS_MAKE_ALTERNATE_URI = 2; + + /* + * Fix common scheme typos. + */ + const unsigned long FIXUP_FLAG_FIX_SCHEME_TYPOS = 8; + + /* NB: If adding an extra flag, 4 is free (again) */ + + /** + * Converts an internal URI (e.g. a wyciwyg URI) into one which we can + * expose to the user, for example on the URL bar. + * + * @param aURI The URI to be converted + * @return nsIURI The converted, exposable URI + * @throws NS_ERROR_MALFORMED_URI when the exposable portion of aURI is malformed + * @throws NS_ERROR_UNKNOWN_PROTOCOL when we can't get a protocol handler service + * for the URI scheme. + */ + nsIURI createExposableURI(in nsIURI aURI); + + /** + * Converts the specified string into a URI, first attempting + * to correct any errors in the syntax or other vagaries. Returns + * a wellformed URI or nullptr if it can't. + * + * @param aURIText Candidate URI. + * @param aFixupFlags Flags that govern ways the URI may be fixed up. + * @param aPostData The POST data to submit with the returned + * URI (see nsISearchSubmission). + */ + nsIURI createFixupURI(in AUTF8String aURIText, in unsigned long aFixupFlags, + [optional] out nsIInputStream aPostData); + + /** + * Same as createFixupURI, but returns information about what it corrected + * (e.g. whether we could rescue the URI or "just" generated a keyword + * search URI instead). + * + * @param aURIText Candidate URI. + * @param aFixupFlags Flags that govern ways the URI may be fixed up. + * @param aPostData The POST data to submit with the returned + * URI (see nsISearchSubmission). + */ + nsIURIFixupInfo getFixupURIInfo(in AUTF8String aURIText, + in unsigned long aFixupFlags, + [optional] out nsIInputStream aPostData); + + /** + * Converts the specified keyword string into a URI. Note that it's the + * caller's responsibility to check whether keywords are enabled and + * whether aKeyword is a sensible keyword. + * + * @param aKeyword The keyword string to convert into a URI + * @param aPostData The POST data to submit to the returned URI + * (see nsISearchSubmission). + * + * @throws NS_ERROR_FAILURE if the resulting URI requires submission of POST + * data and aPostData is null. + */ + nsIURIFixupInfo keywordToURI(in AUTF8String aKeyword, + [optional] out nsIInputStream aPostData); + + /** + * Returns true if the specified domain is whitelisted and false otherwise. + * A whitelisted domain is relevant when we have a single word and can't be + * sure whether to treat the word as a host name or should instead be + * treated as a search term. + * + * @param aDomain A domain name to query. + * @param aDotPos The position of the first '.' character in aDomain, or + * -1 if no '.' character exists. + */ + bool isDomainWhitelisted(in AUTF8String aDomain, + in uint32_t aDotPos); +}; + diff --git a/docshell/base/nsIWebNavigation.idl b/docshell/base/nsIWebNavigation.idl new file mode 100644 index 000000000..042b1c547 --- /dev/null +++ b/docshell/base/nsIWebNavigation.idl @@ -0,0 +1,367 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDOMDocument; +interface nsIInputStream; +interface nsISHistory; +interface nsIURI; + +/** + * The nsIWebNavigation interface defines an interface for navigating the web. + * It provides methods and attributes to direct an object to navigate to a new + * location, stop or restart an in process load, or determine where the object + * has previously gone. + */ +[scriptable, uuid(3ade79d4-8cb9-4952-b18d-4f9b63ca0d31)] +interface nsIWebNavigation : nsISupports +{ + /** + * Indicates if the object can go back. If true this indicates that + * there is back session history available for navigation. + */ + readonly attribute boolean canGoBack; + + /** + * Indicates if the object can go forward. If true this indicates that + * there is forward session history available for navigation + */ + readonly attribute boolean canGoForward; + + /** + * Tells the object to navigate to the previous session history item. When a + * page is loaded from session history, all content is loaded from the cache + * (if available) and page state (such as form values and scroll position) is + * restored. + * + * @throw NS_ERROR_UNEXPECTED + * Indicates that the call was unexpected at this time, which implies + * that canGoBack is false. + */ + void goBack(); + + /** + * Tells the object to navigate to the next session history item. When a + * page is loaded from session history, all content is loaded from the cache + * (if available) and page state (such as form values and scroll position) is + * restored. + * + * @throw NS_ERROR_UNEXPECTED + * Indicates that the call was unexpected at this time, which implies + * that canGoForward is false. + */ + void goForward(); + + /** + * Tells the object to navigate to the session history item at a given index. + * + * @throw NS_ERROR_UNEXPECTED + * Indicates that the call was unexpected at this time, which implies + * that session history entry at the given index does not exist. + */ + void gotoIndex(in long index); + + /**************************************************************************** + * The following flags may be bitwise combined to form the load flags + * parameter passed to either the loadURI or reload method. Some of these + * flags are only applicable to loadURI. + */ + + /** + * This flags defines the range of bits that may be specified. Flags + * outside this range may be used, but may not be passed to Reload(). + */ + const unsigned long LOAD_FLAGS_MASK = 0xffff; + + /** + * This is the default value for the load flags parameter. + */ + const unsigned long LOAD_FLAGS_NONE = 0x0000; + + /** + * Flags 0x1, 0x2, 0x4, 0x8 are reserved for internal use by + * nsIWebNavigation implementations for now. + */ + + /** + * This flag specifies that the load should have the semantics of an HTML + * Meta-refresh tag (i.e., that the cache should be bypassed). This flag + * is only applicable to loadURI. + * XXX the meaning of this flag is poorly defined. + * XXX no one uses this, so we should probably deprecate and remove it. + */ + const unsigned long LOAD_FLAGS_IS_REFRESH = 0x0010; + + /** + * This flag specifies that the load should have the semantics of a link + * click. This flag is only applicable to loadURI. + * XXX the meaning of this flag is poorly defined. + */ + const unsigned long LOAD_FLAGS_IS_LINK = 0x0020; + + /** + * This flag specifies that history should not be updated. This flag is only + * applicable to loadURI. + */ + const unsigned long LOAD_FLAGS_BYPASS_HISTORY = 0x0040; + + /** + * This flag specifies that any existing history entry should be replaced. + * This flag is only applicable to loadURI. + */ + const unsigned long LOAD_FLAGS_REPLACE_HISTORY = 0x0080; + + /** + * This flag specifies that the local web cache should be bypassed, but an + * intermediate proxy cache could still be used to satisfy the load. + */ + const unsigned long LOAD_FLAGS_BYPASS_CACHE = 0x0100; + + /** + * This flag specifies that any intermediate proxy caches should be bypassed + * (i.e., that the content should be loaded from the origin server). + */ + const unsigned long LOAD_FLAGS_BYPASS_PROXY = 0x0200; + + /** + * This flag specifies that a reload was triggered as a result of detecting + * an incorrect character encoding while parsing a previously loaded + * document. + */ + const unsigned long LOAD_FLAGS_CHARSET_CHANGE = 0x0400; + + /** + * If this flag is set, Stop() will be called before the load starts + * and will stop both content and network activity (the default is to + * only stop network activity). Effectively, this passes the + * STOP_CONTENT flag to Stop(), in addition to the STOP_NETWORK flag. + */ + const unsigned long LOAD_FLAGS_STOP_CONTENT = 0x0800; + + /** + * A hint this load was prompted by an external program: take care! + */ + const unsigned long LOAD_FLAGS_FROM_EXTERNAL = 0x1000; + + /** + This flag is set when a user explicitly disables the Mixed Content + Blocker, and allows Mixed Content to load on an https page. + */ + const unsigned long LOAD_FLAGS_ALLOW_MIXED_CONTENT = 0x2000; + + /** + * This flag specifies that this is the first load in this object. + * Set with care, since setting incorrectly can cause us to assume that + * nothing was actually loaded in this object if the load ends up being + * handled by an external application. This flag must not be passed to + * Reload. + */ + const unsigned long LOAD_FLAGS_FIRST_LOAD = 0x4000; + + /** + * This flag specifies that the load should not be subject to popup + * blocking checks. This flag must not be passed to Reload. + */ + const unsigned long LOAD_FLAGS_ALLOW_POPUPS = 0x8000; + + /** + * This flag specifies that the URI classifier should not be checked for + * this load. This flag must not be passed to Reload. + */ + const unsigned long LOAD_FLAGS_BYPASS_CLASSIFIER = 0x10000; + + /** + * Force relevant cookies to be sent with this load even if normally they + * wouldn't be. + */ + const unsigned long LOAD_FLAGS_FORCE_ALLOW_COOKIES = 0x20000; + + /** + * Prevent the owner principal from being inherited for this load. + * Note: Within Gecko we use the term principal rather than owners + * but some legacy addons might still rely on the outdated term. + */ + const unsigned long LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL = 0x40000; + const unsigned long LOAD_FLAGS_DISALLOW_INHERIT_OWNER = 0x40000; + + /** + * Overwrite the returned error code with a specific result code + * when an error page is displayed. + */ + const unsigned long LOAD_FLAGS_ERROR_LOAD_CHANGES_RV = 0x80000; + + /** + * This flag specifies that the URI may be submitted to a third-party + * server for correction. This should only be applied to non-sensitive + * URIs entered by users. This flag must not be passed to Reload. + */ + const unsigned long LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP = 0x100000; + + /** + * This flag specifies that common scheme typos should be corrected. + */ + const unsigned long LOAD_FLAGS_FIXUP_SCHEME_TYPOS = 0x200000; + + /** + * Loads a given URI. This will give priority to loading the requested URI + * in the object implementing this interface. If it can't be loaded here + * however, the URI dispatcher will go through its normal process of content + * loading. + * + * @param aURI + * The URI string to load. For HTTP and FTP URLs and possibly others, + * characters above U+007F will be converted to UTF-8 and then URL- + * escaped per the rules of RFC 2396. + * @param aLoadFlags + * Flags modifying load behaviour. This parameter is a bitwise + * combination of the load flags defined above. (Undefined bits are + * reserved for future use.) Generally you will pass LOAD_FLAGS_NONE + * for this parameter. + * @param aReferrer + * The referring URI. If this argument is null, then the referring + * URI will be inferred internally. + * @param aPostData + * If the URI corresponds to a HTTP request, then this stream is + * appended directly to the HTTP request headers. It may be prefixed + * with additional HTTP headers. This stream must contain a "\r\n" + * sequence separating any HTTP headers from the HTTP request body. + * This parameter is optional and may be null. + * @param aHeaders + * If the URI corresponds to a HTTP request, then any HTTP headers + * contained in this stream are set on the HTTP request. The HTTP + * header stream is formatted as: + * ( HEADER "\r\n" )* + * This parameter is optional and may be null. + */ + void loadURI(in wstring aURI, + in unsigned long aLoadFlags, + in nsIURI aReferrer, + in nsIInputStream aPostData, + in nsIInputStream aHeaders); + + /** + * Loads a given URI. This will give priority to loading the requested URI + * in the object implementing this interface. If it can't be loaded here + * however, the URI dispatcher will go through its normal process of content + * loading. + * + * Behaves like loadURI, but allows passing of additional parameters. + * + * @param aURI + * The URI string to load. For HTTP and FTP URLs and possibly others, + * characters above U+007F will be converted to UTF-8 and then URL- + * escaped per the rules of RFC 2396. + * @param aLoadFlags + * Flags modifying load behaviour. This parameter is a bitwise + * combination of the load flags defined above. (Undefined bits are + * reserved for future use.) Generally you will pass LOAD_FLAGS_NONE + * for this parameter. + * @param aReferrer + * The referring URI. If this argument is null, then the referring + * URI will be inferred internally. + * @param aReferrerPolicy + * One of the REFERRER_POLICY_* constants from nsIHttpChannel. + * Normal case is REFERRER_POLICY_DEFAULT. + * @param aPostData + * If the URI corresponds to a HTTP request, then this stream is + * appended directly to the HTTP request headers. It may be prefixed + * with additional HTTP headers. This stream must contain a "\r\n" + * sequence separating any HTTP headers from the HTTP request body. + * This parameter is optional and may be null. + * @param aHeaders + * If the URI corresponds to a HTTP request, then any HTTP headers + * contained in this stream are set on the HTTP request. The HTTP + * header stream is formatted as: + * ( HEADER "\r\n" )* + * This parameter is optional and may be null. + * @param aBaseURI + * Set to indicate a base URI to be associated with the load. Note + * that at present this argument is only used with view-source aURIs + * and cannot be used to resolve aURI. + * This parameter is optional and may be null. + */ + void loadURIWithOptions(in wstring aURI, + in unsigned long aLoadFlags, + in nsIURI aReferrer, + in unsigned long aReferrerPolicy, + in nsIInputStream aPostData, + in nsIInputStream aHeaders, + in nsIURI aBaseURI); + + /** + * Tells the Object to reload the current page. There may be cases where the + * user will be asked to confirm the reload (for example, when it is + * determined that the request is non-idempotent). + * + * @param aReloadFlags + * Flags modifying load behaviour. This parameter is a bitwise + * combination of the Load Flags defined above. (Undefined bits are + * reserved for future use.) Generally you will pass LOAD_FLAGS_NONE + * for this parameter. + * + * @throw NS_BINDING_ABORTED + * Indicating that the user canceled the reload. + */ + void reload(in unsigned long aReloadFlags); + + /**************************************************************************** + * The following flags may be passed as the stop flags parameter to the stop + * method defined on this interface. + */ + + /** + * This flag specifies that all network activity should be stopped. This + * includes both active network loads and pending META-refreshes. + */ + const unsigned long STOP_NETWORK = 0x01; + + /** + * This flag specifies that all content activity should be stopped. This + * includes animated images, plugins and pending Javascript timeouts. + */ + const unsigned long STOP_CONTENT = 0x02; + + /** + * This flag specifies that all activity should be stopped. + */ + const unsigned long STOP_ALL = 0x03; + + /** + * Stops a load of a URI. + * + * @param aStopFlags + * This parameter is one of the stop flags defined above. + */ + void stop(in unsigned long aStopFlags); + + /** + * Retrieves the current DOM document for the frame, or lazily creates a + * blank document if there is none. This attribute never returns null except + * for unexpected error situations. + */ + readonly attribute nsIDOMDocument document; + + /** + * The currently loaded URI or null. + */ + readonly attribute nsIURI currentURI; + + /** + * The referring URI for the currently loaded URI or null. + */ + readonly attribute nsIURI referringURI; + + /** + * The session history object used by this web navigation instance. + */ + attribute nsISHistory sessionHistory; + + /** + * Set an OriginAttributes dictionary in the docShell. This can be done only + * before loading any content. + */ + void setOriginAttributesBeforeLoading(in jsval originAttributes); +}; diff --git a/docshell/base/nsIWebNavigationInfo.idl b/docshell/base/nsIWebNavigationInfo.idl new file mode 100644 index 000000000..cb6035650 --- /dev/null +++ b/docshell/base/nsIWebNavigationInfo.idl @@ -0,0 +1,63 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIWebNavigation; + +/** + * The nsIWebNavigationInfo interface exposes a way to get information + * on the capabilities of Gecko webnavigation objects. + */ +[scriptable, uuid(62a93afb-93a1-465c-84c8-0432264229de)] +interface nsIWebNavigationInfo : nsISupports +{ + /** + * Returned by isTypeSupported to indicate lack of support for a type. + * @note this is guaranteed not to change, so that boolean tests can be done + * on the return value if isTypeSupported to detect whether a type is + * supported at all. + */ + const unsigned long UNSUPPORTED = 0; + + /** + * Returned by isTypeSupported to indicate that a type is supported as an + * image. + */ + const unsigned long IMAGE = 1; + + /** + * Returned by isTypeSupported to indicate that a type is supported via an + * NPAPI ("Netscape 4 API") plug-in. This is not the value returned for + * "XPCOM plug-ins". + */ + const unsigned long PLUGIN = 2; + + /** + * @note Other return types may be added here in the future as they become + * relevant. + */ + + /** + * Returned by isTypeSupported to indicate that a type is supported via some + * other means. + */ + const unsigned long OTHER = 1 << 15; + + /** + * Query whether aType is supported. + * @param aType the MIME type in question. + * @param aWebNav the nsIWebNavigation object for which the request + * is being made. This is allowed to be null. If it is non-null, + * the return value of this method may depend on the exact state of + * aWebNav and the values set through nsIWebBrowserSetup; otherwise + * the method will assume that the caller is interested in information + * about nsIWebNavigation objects in their default state. + * @return an enum value indicating whether and how aType is supported. + * @note This method may rescan plugins to ensure that they're properly + * registered for the types they support. + */ + unsigned long isTypeSupported(in ACString aType, in nsIWebNavigation aWebNav); +}; diff --git a/docshell/base/nsIWebPageDescriptor.idl b/docshell/base/nsIWebPageDescriptor.idl new file mode 100644 index 000000000..96db7f34a --- /dev/null +++ b/docshell/base/nsIWebPageDescriptor.idl @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsISupports.idl" + +/** + * The nsIWebPageDescriptor interface allows content being displayed in one + * window to be loaded into another window without refetching it from the + * network. + */ + +[scriptable, uuid(6f30b676-3710-4c2c-80b1-0395fb26516e)] +interface nsIWebPageDescriptor : nsISupports +{ + const unsigned long DISPLAY_AS_SOURCE = 0x0001; + const unsigned long DISPLAY_NORMAL = 0x0002; + + /** + * Tells the object to load the page specified by the page descriptor + * + * @throws NS_ERROR_FAILURE - + */ + void loadPage(in nsISupports aPageDescriptor, in unsigned long aDisplayType); + + + /** + * Retrieves the page descriptor for the curent document. + */ + readonly attribute nsISupports currentDescriptor; +}; diff --git a/docshell/base/nsIWebShellServices.h b/docshell/base/nsIWebShellServices.h new file mode 100644 index 000000000..c67de0ff7 --- /dev/null +++ b/docshell/base/nsIWebShellServices.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsIWebShellServices_h___ +#define nsIWebShellServices_h___ + +#include "nsISupports.h" +#include "nsCharsetSource.h" + +/* 0c628af0-5638-4703-8f99-ed6134c9de18 */ +#define NS_IWEB_SHELL_SERVICES_IID \ +{ 0x0c628af0, 0x5638, 0x4703, {0x8f, 0x99, 0xed, 0x61, 0x34, 0xc9, 0xde, 0x18} } + +class nsIWebShellServices : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IWEB_SHELL_SERVICES_IID) + + NS_IMETHOD ReloadDocument(const char* aCharset = nullptr, + int32_t aSource = kCharsetUninitialized) = 0; + NS_IMETHOD StopDocumentLoad(void) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIWebShellServices, NS_IWEB_SHELL_SERVICES_IID) + +/* Use this macro when declaring classes that implement this interface. */ +#define NS_DECL_NSIWEBSHELLSERVICES \ + NS_IMETHOD ReloadDocument(const char* aCharset = nullptr, \ + int32_t aSource = kCharsetUninitialized) override; \ + NS_IMETHOD StopDocumentLoad(void) override; + +#endif /* nsIWebShellServices_h___ */ diff --git a/docshell/base/nsWebNavigationInfo.cpp b/docshell/base/nsWebNavigationInfo.cpp new file mode 100644 index 000000000..2d1e19665 --- /dev/null +++ b/docshell/base/nsWebNavigationInfo.cpp @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsWebNavigationInfo.h" +#include "nsIWebNavigation.h" +#include "nsServiceManagerUtils.h" +#include "nsIDocumentLoaderFactory.h" +#include "nsIPluginHost.h" +#include "nsIDocShell.h" +#include "nsContentUtils.h" +#include "imgLoader.h" + +NS_IMPL_ISUPPORTS(nsWebNavigationInfo, nsIWebNavigationInfo) + +#define CONTENT_DLF_CONTRACT "@mozilla.org/content/document-loader-factory;1" +#define PLUGIN_DLF_CONTRACT \ + "@mozilla.org/content/plugin/document-loader-factory;1" + +nsresult +nsWebNavigationInfo::Init() +{ + nsresult rv; + mCategoryManager = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsWebNavigationInfo::IsTypeSupported(const nsACString& aType, + nsIWebNavigation* aWebNav, + uint32_t* aIsTypeSupported) +{ + NS_PRECONDITION(aIsTypeSupported, "null out param?"); + + // Note to self: aWebNav could be an nsWebBrowser or an nsDocShell here (or + // an nsSHistory, but not much we can do with that). So if we start using + // it here, we need to be careful to get to the docshell correctly. + + // For now just report what the Gecko-Content-Viewers category has + // to say for itself. + *aIsTypeSupported = nsIWebNavigationInfo::UNSUPPORTED; + + // We want to claim that the type for PDF documents is unsupported, + // so that the internal PDF viewer's stream converted will get used. + if (aType.LowerCaseEqualsLiteral("application/pdf") && + nsContentUtils::IsPDFJSEnabled()) { + return NS_OK; + } + + // We want to claim that the type for SWF movies is unsupported, + // so that the internal SWF player's stream converter will get used. + if (aType.LowerCaseEqualsLiteral("application/x-shockwave-flash") && + nsContentUtils::IsSWFPlayerEnabled()) { + return NS_OK; + } + + const nsCString& flatType = PromiseFlatCString(aType); + nsresult rv = IsTypeSupportedInternal(flatType, aIsTypeSupported); + NS_ENSURE_SUCCESS(rv, rv); + + if (*aIsTypeSupported) { + return rv; + } + + // If this request is for a docShell that isn't going to allow plugins, + // there's no need to try and find a plugin to handle it. + nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aWebNav)); + bool allowed; + if (docShell && + NS_SUCCEEDED(docShell->GetAllowPlugins(&allowed)) && !allowed) { + return NS_OK; + } + + // Try reloading plugins in case they've changed. + nsCOMPtr<nsIPluginHost> pluginHost = + do_GetService(MOZ_PLUGIN_HOST_CONTRACTID); + if (pluginHost) { + // false will ensure that currently running plugins will not + // be shut down + rv = pluginHost->ReloadPlugins(); + if (NS_SUCCEEDED(rv)) { + // OK, we reloaded plugins and there were new ones + // (otherwise NS_ERROR_PLUGINS_PLUGINSNOTCHANGED would have + // been returned). Try checking whether we can handle the + // content now. + return IsTypeSupportedInternal(flatType, aIsTypeSupported); + } + } + + return NS_OK; +} + +nsresult +nsWebNavigationInfo::IsTypeSupportedInternal(const nsCString& aType, + uint32_t* aIsSupported) +{ + NS_PRECONDITION(aIsSupported, "Null out param?"); + + nsContentUtils::ContentViewerType vtype = nsContentUtils::TYPE_UNSUPPORTED; + + nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory = + nsContentUtils::FindInternalContentViewer(aType, &vtype); + + switch (vtype) { + case nsContentUtils::TYPE_UNSUPPORTED: + *aIsSupported = nsIWebNavigationInfo::UNSUPPORTED; + break; + + case nsContentUtils::TYPE_PLUGIN: + *aIsSupported = nsIWebNavigationInfo::PLUGIN; + break; + + case nsContentUtils::TYPE_UNKNOWN: + *aIsSupported = nsIWebNavigationInfo::OTHER; + break; + + case nsContentUtils::TYPE_CONTENT: + // XXXbz we only need this because images register for the same + // contractid as documents, so we can't tell them apart based on + // contractid. + if (imgLoader::SupportImageWithMimeType(aType.get())) { + *aIsSupported = nsIWebNavigationInfo::IMAGE; + } else { + *aIsSupported = nsIWebNavigationInfo::OTHER; + } + break; + } + + return NS_OK; +} diff --git a/docshell/base/nsWebNavigationInfo.h b/docshell/base/nsWebNavigationInfo.h new file mode 100644 index 000000000..2b54156f7 --- /dev/null +++ b/docshell/base/nsWebNavigationInfo.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsWebNavigationInfo_h__ +#define nsWebNavigationInfo_h__ + +#include "nsIWebNavigationInfo.h" +#include "nsCOMPtr.h" +#include "nsICategoryManager.h" +#include "mozilla/Attributes.h" + +class nsCString; + +#define NS_WEBNAVIGATION_INFO_CID \ + { 0xf30bc0a2, 0x958b, 0x4287,{0xbf, 0x62, 0xce, 0x38, 0xba, 0x0c, 0x81, 0x1e}} + +class nsWebNavigationInfo final : public nsIWebNavigationInfo +{ +public: + nsWebNavigationInfo() {} + + NS_DECL_ISUPPORTS + + NS_DECL_NSIWEBNAVIGATIONINFO + + nsresult Init(); + +private: + ~nsWebNavigationInfo() {} + + // Check whether aType is supported. If this method throws, the + // value of aIsSupported is not changed. + nsresult IsTypeSupportedInternal(const nsCString& aType, + uint32_t* aIsSupported); + + nsCOMPtr<nsICategoryManager> mCategoryManager; +}; + +#endif // nsWebNavigationInfo_h__ diff --git a/docshell/base/timeline/AbstractTimelineMarker.cpp b/docshell/base/timeline/AbstractTimelineMarker.cpp new file mode 100644 index 000000000..aeeab8207 --- /dev/null +++ b/docshell/base/timeline/AbstractTimelineMarker.cpp @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AbstractTimelineMarker.h" + +#include "mozilla/TimeStamp.h" +#include "MainThreadUtils.h" +#include "nsAppRunner.h" + +namespace mozilla { + +AbstractTimelineMarker::AbstractTimelineMarker(const char* aName, + MarkerTracingType aTracingType) + : mName(aName) + , mTracingType(aTracingType) + , mProcessType(XRE_GetProcessType()) + , mIsOffMainThread(!NS_IsMainThread()) +{ + MOZ_COUNT_CTOR(AbstractTimelineMarker); + SetCurrentTime(); +} + +AbstractTimelineMarker::AbstractTimelineMarker(const char* aName, + const TimeStamp& aTime, + MarkerTracingType aTracingType) + : mName(aName) + , mTracingType(aTracingType) + , mProcessType(XRE_GetProcessType()) + , mIsOffMainThread(!NS_IsMainThread()) +{ + MOZ_COUNT_CTOR(AbstractTimelineMarker); + SetCustomTime(aTime); +} + +UniquePtr<AbstractTimelineMarker> +AbstractTimelineMarker::Clone() +{ + MOZ_ASSERT(false, "Clone method not yet implemented on this marker type."); + return nullptr; +} + +bool +AbstractTimelineMarker::Equals(const AbstractTimelineMarker& aOther) +{ + // Check whether two markers should be considered the same, for the purpose + // of pairing start and end markers. Normally this definition suffices. + return strcmp(mName, aOther.mName) == 0; +} + +AbstractTimelineMarker::~AbstractTimelineMarker() +{ + MOZ_COUNT_DTOR(AbstractTimelineMarker); +} + +void +AbstractTimelineMarker::SetCurrentTime() +{ + TimeStamp now = TimeStamp::Now(); + SetCustomTime(now); +} + +void +AbstractTimelineMarker::SetCustomTime(const TimeStamp& aTime) +{ + bool isInconsistent = false; + mTime = (aTime - TimeStamp::ProcessCreation(isInconsistent)).ToMilliseconds(); +} + +void +AbstractTimelineMarker::SetCustomTime(DOMHighResTimeStamp aTime) +{ + mTime = aTime; +} + +void +AbstractTimelineMarker::SetProcessType(GeckoProcessType aProcessType) +{ + mProcessType = aProcessType; +} + +void +AbstractTimelineMarker::SetOffMainThread(bool aIsOffMainThread) +{ + mIsOffMainThread = aIsOffMainThread; +} + +} // namespace mozilla diff --git a/docshell/base/timeline/AbstractTimelineMarker.h b/docshell/base/timeline/AbstractTimelineMarker.h new file mode 100644 index 000000000..516b44eb0 --- /dev/null +++ b/docshell/base/timeline/AbstractTimelineMarker.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_AbstractTimelineMarker_h_ +#define mozilla_AbstractTimelineMarker_h_ + +#include "TimelineMarkerEnums.h" // for MarkerTracingType +#include "nsDOMNavigationTiming.h" // for DOMHighResTimeStamp +#include "nsXULAppAPI.h" // for GeckoProcessType +#include "mozilla/UniquePtr.h" + +struct JSContext; +class JSObject; + +namespace mozilla { +class TimeStamp; + +namespace dom { +struct ProfileTimelineMarker; +} + +class AbstractTimelineMarker +{ +private: + AbstractTimelineMarker() = delete; + AbstractTimelineMarker(const AbstractTimelineMarker& aOther) = delete; + void operator=(const AbstractTimelineMarker& aOther) = delete; + +public: + AbstractTimelineMarker(const char* aName, + MarkerTracingType aTracingType); + + AbstractTimelineMarker(const char* aName, + const TimeStamp& aTime, + MarkerTracingType aTracingType); + + virtual ~AbstractTimelineMarker(); + + virtual UniquePtr<AbstractTimelineMarker> Clone(); + virtual bool Equals(const AbstractTimelineMarker& aOther); + + virtual void AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker) = 0; + virtual JSObject* GetStack() = 0; + + const char* GetName() const { return mName; } + DOMHighResTimeStamp GetTime() const { return mTime; } + MarkerTracingType GetTracingType() const { return mTracingType; } + + uint8_t GetProcessType() const { return mProcessType; }; + bool IsOffMainThread() const { return mIsOffMainThread; }; + +private: + const char* mName; + DOMHighResTimeStamp mTime; + MarkerTracingType mTracingType; + + uint8_t mProcessType; // @see `enum GeckoProcessType`. + bool mIsOffMainThread; + +protected: + void SetCurrentTime(); + void SetCustomTime(const TimeStamp& aTime); + void SetCustomTime(DOMHighResTimeStamp aTime); + void SetProcessType(GeckoProcessType aProcessType); + void SetOffMainThread(bool aIsOffMainThread); +}; + +} // namespace mozilla + +#endif /* mozilla_AbstractTimelineMarker_h_ */ diff --git a/docshell/base/timeline/AutoGlobalTimelineMarker.cpp b/docshell/base/timeline/AutoGlobalTimelineMarker.cpp new file mode 100644 index 000000000..44b7c750b --- /dev/null +++ b/docshell/base/timeline/AutoGlobalTimelineMarker.cpp @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AutoGlobalTimelineMarker.h" + +#include "TimelineConsumers.h" +#include "MainThreadUtils.h" + +namespace mozilla { + +AutoGlobalTimelineMarker::AutoGlobalTimelineMarker(const char* aName, + MarkerStackRequest aStackRequest /* = STACK */ + MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) + : mName(aName) + , mStackRequest(aStackRequest) +{ + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines || timelines->IsEmpty()) { + return; + } + + timelines->AddMarkerForAllObservedDocShells(mName, MarkerTracingType::START, mStackRequest); +} + +AutoGlobalTimelineMarker::~AutoGlobalTimelineMarker() +{ + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines || timelines->IsEmpty()) { + return; + } + + timelines->AddMarkerForAllObservedDocShells(mName, MarkerTracingType::END, mStackRequest); +} + +} // namespace mozilla diff --git a/docshell/base/timeline/AutoGlobalTimelineMarker.h b/docshell/base/timeline/AutoGlobalTimelineMarker.h new file mode 100644 index 000000000..2937cf3d7 --- /dev/null +++ b/docshell/base/timeline/AutoGlobalTimelineMarker.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_AutoGlobalTimelineMarker_h_ +#define mozilla_AutoGlobalTimelineMarker_h_ + +#include "mozilla/GuardObjects.h" +#include "TimelineMarkerEnums.h" + +namespace mozilla { + +// # AutoGlobalTimelineMarker +// +// Similar to `AutoTimelineMarker`, but adds its traced marker to all docshells, +// not a single particular one. This is useful for operations that aren't +// associated with any one particular doc shell, or when it isn't clear which +// docshell triggered the operation. +// +// Example usage: +// +// { +// AutoGlobalTimelineMarker marker("Cycle Collection"); +// nsCycleCollector* cc = GetCycleCollector(); +// cc->Collect(); +// ... +// } +class MOZ_RAII AutoGlobalTimelineMarker +{ + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER; + + // The name of the marker we are adding. + const char* mName; + // Whether to capture the JS stack or not. + MarkerStackRequest mStackRequest; + +public: + explicit AutoGlobalTimelineMarker(const char* aName, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK + MOZ_GUARD_OBJECT_NOTIFIER_PARAM); + ~AutoGlobalTimelineMarker(); + + AutoGlobalTimelineMarker(const AutoGlobalTimelineMarker& aOther) = delete; + void operator=(const AutoGlobalTimelineMarker& aOther) = delete; +}; + +} // namespace mozilla + +#endif /* mozilla_AutoGlobalTimelineMarker_h_ */ diff --git a/docshell/base/timeline/AutoTimelineMarker.cpp b/docshell/base/timeline/AutoTimelineMarker.cpp new file mode 100644 index 000000000..dbf4bd596 --- /dev/null +++ b/docshell/base/timeline/AutoTimelineMarker.cpp @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AutoTimelineMarker.h" + +#include "TimelineConsumers.h" +#include "MainThreadUtils.h" + +namespace mozilla { + +AutoTimelineMarker::AutoTimelineMarker(nsIDocShell* aDocShell, const char* aName + MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) + : mName(aName) + , mDocShell(nullptr) +{ + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + MOZ_ASSERT(NS_IsMainThread()); + + if (!aDocShell) { + return; + } + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines || !timelines->HasConsumer(aDocShell)) { + return; + } + + mDocShell = aDocShell; + timelines->AddMarkerForDocShell(mDocShell, mName, MarkerTracingType::START); +} + +AutoTimelineMarker::~AutoTimelineMarker() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!mDocShell) { + return; + } + + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (!timelines || !timelines->HasConsumer(mDocShell)) { + return; + } + + timelines->AddMarkerForDocShell(mDocShell, mName, MarkerTracingType::END); +} + +} // namespace mozilla diff --git a/docshell/base/timeline/AutoTimelineMarker.h b/docshell/base/timeline/AutoTimelineMarker.h new file mode 100644 index 000000000..47bb25046 --- /dev/null +++ b/docshell/base/timeline/AutoTimelineMarker.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_AutoTimelineMarker_h_ +#define mozilla_AutoTimelineMarker_h_ + +#include "mozilla/GuardObjects.h" +#include "mozilla/RefPtr.h" + +class nsIDocShell; + +namespace mozilla { + +// # AutoTimelineMarker +// +// An RAII class to trace some task in the platform by adding a start and end +// timeline marker pair. These markers are then rendered in the devtools' +// performance tool's waterfall graph. +// +// Example usage: +// +// { +// AutoTimelineMarker marker(mDocShell, "Parse CSS"); +// nsresult rv = ParseTheCSSFile(mFile); +// ... +// } +class MOZ_RAII AutoTimelineMarker +{ + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER; + + // The name of the marker we are adding. + const char* mName; + + // The docshell that is associated with this marker. + RefPtr<nsIDocShell> mDocShell; + +public: + AutoTimelineMarker(nsIDocShell* aDocShell, + const char* aName MOZ_GUARD_OBJECT_NOTIFIER_PARAM); + ~AutoTimelineMarker(); + + AutoTimelineMarker(const AutoTimelineMarker& aOther) = delete; + void operator=(const AutoTimelineMarker& aOther) = delete; +}; + +} // namespace mozilla + +#endif /* mozilla_AutoTimelineMarker_h_ */ diff --git a/docshell/base/timeline/CompositeTimelineMarker.h b/docshell/base/timeline/CompositeTimelineMarker.h new file mode 100644 index 000000000..45faefbd6 --- /dev/null +++ b/docshell/base/timeline/CompositeTimelineMarker.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_CompositeTimelineMarker_h_ +#define mozilla_CompositeTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class CompositeTimelineMarker : public TimelineMarker +{ +public: + CompositeTimelineMarker(const TimeStamp& aTime, + MarkerTracingType aTracingType) + : TimelineMarker("Composite", aTime, aTracingType) + { + // Even though these markers end up being created on the main thread in the + // content or chrome processes, they actually trace down code in the + // compositor parent process. All the information for creating these markers + // is sent along via IPC to an nsView when a composite finishes. + // Mark this as 'off the main thread' to style it differently in frontends. + SetOffMainThread(true); + } +}; + +} // namespace mozilla + +#endif // mozilla_CompositeTimelineMarker_h_ diff --git a/docshell/base/timeline/ConsoleTimelineMarker.h b/docshell/base/timeline/ConsoleTimelineMarker.h new file mode 100644 index 000000000..f397830c2 --- /dev/null +++ b/docshell/base/timeline/ConsoleTimelineMarker.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ConsoleTimelineMarker_h_ +#define mozilla_ConsoleTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class ConsoleTimelineMarker : public TimelineMarker +{ +public: + ConsoleTimelineMarker(const nsAString& aCause, + MarkerTracingType aTracingType) + : TimelineMarker("ConsoleTime", aTracingType) + , mCause(aCause) + { + // Stack is captured by default on the "start" marker. Explicitly also + // capture stack on the "end" marker. + if (aTracingType == MarkerTracingType::END) { + CaptureStack(); + } + } + + virtual bool Equals(const AbstractTimelineMarker& aOther) override + { + if (!TimelineMarker::Equals(aOther)) { + return false; + } + // Console markers must have matching causes as well. It is safe to perform + // a static_cast here as the previous equality check ensures that this is + // a console marker instance. + return mCause == static_cast<const ConsoleTimelineMarker*>(&aOther)->mCause; + } + + virtual void AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker) override + { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mCauseName.Construct(mCause); + } else { + aMarker.mEndStack = GetStack(); + } + } + +private: + nsString mCause; +}; + +} // namespace mozilla + +#endif // mozilla_ConsoleTimelineMarker_h_ diff --git a/docshell/base/timeline/DocLoadingTimelineMarker.h b/docshell/base/timeline/DocLoadingTimelineMarker.h new file mode 100644 index 000000000..af2ac17fd --- /dev/null +++ b/docshell/base/timeline/DocLoadingTimelineMarker.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_DocLoadingTimelineMarker_h_ +#define mozilla_DocLoadingTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class DocLoadingTimelineMarker : public TimelineMarker +{ +public: + explicit DocLoadingTimelineMarker(const char* aName) + : TimelineMarker(aName, MarkerTracingType::TIMESTAMP) + , mUnixTime(PR_Now()) + {} + + virtual void AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker) override + { + TimelineMarker::AddDetails(aCx, aMarker); + aMarker.mUnixTime.Construct(mUnixTime); + } + +private: + // Certain consumers might use Date.now() or similar for tracing time. + // However, TimelineMarkers use process creation as an epoch, which provides + // more precision. To allow syncing, attach an additional unix timestamp. + // Using this instead of `AbstractTimelineMarker::GetTime()'s` timestamp + // is strongly discouraged. + PRTime mUnixTime; +}; + +} // namespace mozilla + +#endif // mozilla_DocLoadingTimelineMarker_h_ diff --git a/docshell/base/timeline/EventTimelineMarker.h b/docshell/base/timeline/EventTimelineMarker.h new file mode 100644 index 000000000..095bc528e --- /dev/null +++ b/docshell/base/timeline/EventTimelineMarker.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_EventTimelineMarker_h_ +#define mozilla_EventTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class EventTimelineMarker : public TimelineMarker +{ +public: + EventTimelineMarker(const nsAString& aType, + uint16_t aPhase, + MarkerTracingType aTracingType) + : TimelineMarker("DOMEvent", aTracingType) + , mType(aType) + , mPhase(aPhase) + {} + + virtual void AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker) override + { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mType.Construct(mType); + aMarker.mEventPhase.Construct(mPhase); + } + } + +private: + nsString mType; + uint16_t mPhase; +}; + +} // namespace mozilla + +#endif // mozilla_EventTimelineMarker_h_ diff --git a/docshell/base/timeline/JavascriptTimelineMarker.h b/docshell/base/timeline/JavascriptTimelineMarker.h new file mode 100644 index 000000000..5a9924283 --- /dev/null +++ b/docshell/base/timeline/JavascriptTimelineMarker.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_JavascriptTimelineMarker_h_ +#define mozilla_JavascriptTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/dom/ToJSValue.h" + +namespace mozilla { + +class JavascriptTimelineMarker : public TimelineMarker +{ +public: + // The caller owns |aAsyncCause| here, so we must copy it into a separate + // string for use later on. + JavascriptTimelineMarker(const char* aReason, + const char16_t* aFunctionName, + const char16_t* aFileName, + uint32_t aLineNumber, + MarkerTracingType aTracingType, + JS::Handle<JS::Value> aAsyncStack, + const char* aAsyncCause) + : TimelineMarker("Javascript", aTracingType, MarkerStackRequest::NO_STACK) + , mCause(NS_ConvertUTF8toUTF16(aReason)) + , mFunctionName(aFunctionName) + , mFileName(aFileName) + , mLineNumber(aLineNumber) + , mAsyncCause(aAsyncCause) + { + JSContext* ctx = nsContentUtils::GetCurrentJSContext(); + if (ctx) { + mAsyncStack.init(ctx, aAsyncStack); + } + } + + virtual void AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker) override + { + TimelineMarker::AddDetails(aCx, aMarker); + + aMarker.mCauseName.Construct(mCause); + + if (!mFunctionName.IsEmpty() || !mFileName.IsEmpty()) { + dom::RootedDictionary<dom::ProfileTimelineStackFrame> stackFrame(aCx); + stackFrame.mLine.Construct(mLineNumber); + stackFrame.mSource.Construct(mFileName); + stackFrame.mFunctionDisplayName.Construct(mFunctionName); + + if (mAsyncStack.isObject() && !mAsyncStack.isNullOrUndefined() && + !mAsyncCause.IsEmpty()) { + JS::Rooted<JSObject*> asyncStack(aCx, mAsyncStack.toObjectOrNull()); + JS::Rooted<JSObject*> parentFrame(aCx); + JS::Rooted<JSString*> asyncCause(aCx, JS_NewUCStringCopyN(aCx, mAsyncCause.BeginReading(), + mAsyncCause.Length())); + if (!asyncCause) { + JS_ClearPendingException(aCx); + return; + } + + if (JS::IsSavedFrame(asyncStack) && + !JS::CopyAsyncStack(aCx, asyncStack, asyncCause, &parentFrame, 0)) { + JS_ClearPendingException(aCx); + } else { + stackFrame.mAsyncParent = parentFrame; + } + } + + JS::Rooted<JS::Value> newStack(aCx); + if (ToJSValue(aCx, stackFrame, &newStack)) { + if (newStack.isObject()) { + aMarker.mStack = &newStack.toObject(); + } + } else { + JS_ClearPendingException(aCx); + } + } + } + +private: + nsString mCause; + nsString mFunctionName; + nsString mFileName; + uint32_t mLineNumber; + JS::PersistentRooted<JS::Value> mAsyncStack; + NS_ConvertUTF8toUTF16 mAsyncCause; +}; + +} // namespace mozilla + +#endif // mozilla_JavascriptTimelineMarker_h_ diff --git a/docshell/base/timeline/LayerTimelineMarker.h b/docshell/base/timeline/LayerTimelineMarker.h new file mode 100644 index 000000000..8293f3558 --- /dev/null +++ b/docshell/base/timeline/LayerTimelineMarker.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_LayerTimelineMarker_h_ +#define mozilla_LayerTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "nsRegion.h" + +namespace mozilla { + +class LayerTimelineMarker : public TimelineMarker +{ +public: + explicit LayerTimelineMarker(const nsIntRegion& aRegion) + : TimelineMarker("Layer", MarkerTracingType::HELPER_EVENT) + , mRegion(aRegion) + {} + + void AddLayerRectangles(dom::Sequence<dom::ProfileTimelineLayerRect>& aRectangles) + { + for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) { + const nsIntRect& iterRect = iter.Get(); + dom::ProfileTimelineLayerRect rect; + rect.mX = iterRect.X(); + rect.mY = iterRect.Y(); + rect.mWidth = iterRect.Width(); + rect.mHeight = iterRect.Height(); + aRectangles.AppendElement(rect, fallible); + } + } + +private: + nsIntRegion mRegion; +}; + +} // namespace mozilla + +#endif // mozilla_LayerTimelineMarker_h_ diff --git a/docshell/base/timeline/MarkersStorage.cpp b/docshell/base/timeline/MarkersStorage.cpp new file mode 100644 index 000000000..cc56998fb --- /dev/null +++ b/docshell/base/timeline/MarkersStorage.cpp @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "MarkersStorage.h" +#include "MainThreadUtils.h" + +namespace mozilla { + +MarkersStorage::MarkersStorage(const char* aMutexName) + : mLock(aMutexName) +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +MarkersStorage::~MarkersStorage() +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +Mutex& +MarkersStorage::GetLock() +{ + return mLock; +} + +} // namespace mozilla diff --git a/docshell/base/timeline/MarkersStorage.h b/docshell/base/timeline/MarkersStorage.h new file mode 100644 index 000000000..8728ea586 --- /dev/null +++ b/docshell/base/timeline/MarkersStorage.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_MarkersStorage_h_ +#define mozilla_MarkersStorage_h_ + +#include "TimelineMarkerEnums.h" // for MarkerReleaseRequest +#include "mozilla/Mutex.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/LinkedList.h" +#include "nsTArray.h" + +namespace mozilla { +class AbstractTimelineMarker; + +namespace dom { +struct ProfileTimelineMarker; +} + +class MarkersStorage : public LinkedListElement<MarkersStorage> +{ +private: + MarkersStorage() = delete; + MarkersStorage(const MarkersStorage& aOther) = delete; + void operator=(const MarkersStorage& aOther) = delete; + +public: + explicit MarkersStorage(const char* aMutexName); + virtual ~MarkersStorage(); + + virtual void AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) = 0; + virtual void AddOTMTMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) = 0; + virtual void ClearMarkers() = 0; + virtual void PopMarkers(JSContext* aCx, nsTArray<dom::ProfileTimelineMarker>& aStore) = 0; + +protected: + Mutex& GetLock(); + +private: + Mutex mLock; +}; + +} // namespace mozilla + +#endif /* mozilla_MarkersStorage_h_ */ diff --git a/docshell/base/timeline/MessagePortTimelineMarker.h b/docshell/base/timeline/MessagePortTimelineMarker.h new file mode 100644 index 000000000..f578cf128 --- /dev/null +++ b/docshell/base/timeline/MessagePortTimelineMarker.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_MessagePortTimelineMarker_h_ +#define mozilla_MessagePortTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class MessagePortTimelineMarker : public TimelineMarker +{ +public: + MessagePortTimelineMarker(dom::ProfileTimelineMessagePortOperationType aOperationType, + MarkerTracingType aTracingType) + : TimelineMarker("MessagePort", aTracingType, MarkerStackRequest::NO_STACK) + , mOperationType(aOperationType) + {} + + virtual UniquePtr<AbstractTimelineMarker> Clone() override + { + MessagePortTimelineMarker* clone = + new MessagePortTimelineMarker(mOperationType, GetTracingType()); + clone->SetCustomTime(GetTime()); + return UniquePtr<AbstractTimelineMarker>(clone); + } + + virtual void AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker) override + { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mMessagePortOperation.Construct(mOperationType); + } + } + +private: + dom::ProfileTimelineMessagePortOperationType mOperationType; +}; + +} // namespace mozilla + +#endif /* mozilla_MessagePortTimelineMarker_h_ */ diff --git a/docshell/base/timeline/ObservedDocShell.cpp b/docshell/base/timeline/ObservedDocShell.cpp new file mode 100644 index 000000000..b394f37b3 --- /dev/null +++ b/docshell/base/timeline/ObservedDocShell.cpp @@ -0,0 +1,171 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ObservedDocShell.h" + +#include "AbstractTimelineMarker.h" +#include "LayerTimelineMarker.h" +#include "MainThreadUtils.h" +#include "mozilla/Move.h" +#include "mozilla/AutoRestore.h" + +namespace mozilla { + +ObservedDocShell::ObservedDocShell(nsIDocShell* aDocShell) + : MarkersStorage("ObservedDocShellMutex") + , mDocShell(aDocShell) + , mPopping(false) +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +void +ObservedDocShell::AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) +{ + // Only allow main thread markers to go into this list. No need to lock + // here since `mTimelineMarkers` will only be accessed or modified on the + // main thread only. + MOZ_ASSERT(NS_IsMainThread()); + // Don't accept any markers generated by the process of popping + // markers. + if (!mPopping) { + mTimelineMarkers.AppendElement(Move(aMarker)); + } +} + +void +ObservedDocShell::AddOTMTMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) +{ + // Only allow off the main thread markers to go into this list. Since most + // of our markers come from the main thread, be a little more efficient and + // avoid dealing with multithreading scenarios until all the markers are + // actually cleared or popped in `ClearMarkers` or `PopMarkers`. + MOZ_ASSERT(!NS_IsMainThread()); + MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`. + mOffTheMainThreadTimelineMarkers.AppendElement(Move(aMarker)); +} + +void +ObservedDocShell::ClearMarkers() +{ + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`. + mTimelineMarkers.Clear(); + mOffTheMainThreadTimelineMarkers.Clear(); +} + +void +ObservedDocShell::PopMarkers(JSContext* aCx, + nsTArray<dom::ProfileTimelineMarker>& aStore) +{ + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(GetLock()); // for `mOffTheMainThreadTimelineMarkers`. + + MOZ_RELEASE_ASSERT(!mPopping); + AutoRestore<bool> resetPopping(mPopping); + mPopping = true; + + // First, move all of our markers into a single array. We'll chose + // the `mTimelineMarkers` store because that's where we expect most of + // our markers to be. + mTimelineMarkers.AppendElements(Move(mOffTheMainThreadTimelineMarkers)); + + // If we see an unpaired START, we keep it around for the next call + // to ObservedDocShell::PopMarkers. We store the kept START objects here. + nsTArray<UniquePtr<AbstractTimelineMarker>> keptStartMarkers; + + for (uint32_t i = 0; i < mTimelineMarkers.Length(); ++i) { + UniquePtr<AbstractTimelineMarker>& startPayload = mTimelineMarkers.ElementAt(i); + + // If this is a TIMESTAMP marker, there's no corresponding END, + // as it's a single unit of time, not a duration. + if (startPayload->GetTracingType() == MarkerTracingType::TIMESTAMP) { + dom::ProfileTimelineMarker* marker = aStore.AppendElement(); + marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName()); + marker->mStart = startPayload->GetTime(); + marker->mEnd = startPayload->GetTime(); + marker->mStack = startPayload->GetStack(); + startPayload->AddDetails(aCx, *marker); + continue; + } + + // Whenever a START marker is found, look for the corresponding END + // and build a {name,start,end} JS object. + if (startPayload->GetTracingType() == MarkerTracingType::START) { + bool hasSeenEnd = false; + + // "Paint" markers are different because painting is handled at root + // docshell level. The information that a paint was done is stored at + // sub-docshell level, but we can only be sure that a paint did actually + // happen in if a "Layer" marker was recorded too. + bool startIsPaintType = strcmp(startPayload->GetName(), "Paint") == 0; + bool hasSeenLayerType = false; + + // If we are processing a "Paint" marker, we append information from + // all the embedded "Layer" markers to this array. + dom::Sequence<dom::ProfileTimelineLayerRect> layerRectangles; + + // DOM events can be nested, so we must take care when searching + // for the matching end. It doesn't hurt to apply this logic to + // all event types. + uint32_t markerDepth = 0; + + // The assumption is that the devtools timeline flushes markers frequently + // enough for the amount of markers to always be small enough that the + // nested for loop isn't going to be a performance problem. + for (uint32_t j = i + 1; j < mTimelineMarkers.Length(); ++j) { + UniquePtr<AbstractTimelineMarker>& endPayload = mTimelineMarkers.ElementAt(j); + bool endIsLayerType = strcmp(endPayload->GetName(), "Layer") == 0; + + // Look for "Layer" markers to stream out "Paint" markers. + if (startIsPaintType && endIsLayerType) { + AbstractTimelineMarker* raw = endPayload.get(); + LayerTimelineMarker* layerPayload = static_cast<LayerTimelineMarker*>(raw); + layerPayload->AddLayerRectangles(layerRectangles); + hasSeenLayerType = true; + } + if (!startPayload->Equals(*endPayload)) { + continue; + } + if (endPayload->GetTracingType() == MarkerTracingType::START) { + ++markerDepth; + continue; + } + if (endPayload->GetTracingType() == MarkerTracingType::END) { + if (markerDepth > 0) { + --markerDepth; + continue; + } + if (!startIsPaintType || (startIsPaintType && hasSeenLayerType)) { + dom::ProfileTimelineMarker* marker = aStore.AppendElement(); + marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName()); + marker->mStart = startPayload->GetTime(); + marker->mEnd = endPayload->GetTime(); + marker->mStack = startPayload->GetStack(); + if (hasSeenLayerType) { + marker->mRectangles.Construct(layerRectangles); + } + startPayload->AddDetails(aCx, *marker); + endPayload->AddDetails(aCx, *marker); + } + hasSeenEnd = true; + break; + } + } + + // If we did not see the corresponding END, keep the START. + if (!hasSeenEnd) { + keptStartMarkers.AppendElement(Move(mTimelineMarkers.ElementAt(i))); + mTimelineMarkers.RemoveElementAt(i); + --i; + } + } + } + + mTimelineMarkers.SwapElements(keptStartMarkers); +} + +} // namespace mozilla diff --git a/docshell/base/timeline/ObservedDocShell.h b/docshell/base/timeline/ObservedDocShell.h new file mode 100644 index 000000000..05eafbd0c --- /dev/null +++ b/docshell/base/timeline/ObservedDocShell.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ObservedDocShell_h_ +#define mozilla_ObservedDocShell_h_ + +#include "MarkersStorage.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + +class nsIDocShell; + +namespace mozilla { +class AbstractTimelineMarker; + +namespace dom { +struct ProfileTimelineMarker; +} + +// # ObservedDocShell +// +// A wrapper around a docshell for which docshell-specific markers are +// allowed to exist. See TimelineConsumers for register/unregister logic. +class ObservedDocShell : public MarkersStorage +{ +private: + RefPtr<nsIDocShell> mDocShell; + + // Main thread only. + nsTArray<UniquePtr<AbstractTimelineMarker>> mTimelineMarkers; + bool mPopping; + + // Off the main thread only. + nsTArray<UniquePtr<AbstractTimelineMarker>> mOffTheMainThreadTimelineMarkers; + +public: + explicit ObservedDocShell(nsIDocShell* aDocShell); + nsIDocShell* operator*() const { return mDocShell.get(); } + + void AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) override; + void AddOTMTMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) override; + void ClearMarkers() override; + void PopMarkers(JSContext* aCx, nsTArray<dom::ProfileTimelineMarker>& aStore) override; +}; + +} // namespace mozilla + +#endif /* mozilla_ObservedDocShell_h_ */ diff --git a/docshell/base/timeline/RestyleTimelineMarker.h b/docshell/base/timeline/RestyleTimelineMarker.h new file mode 100644 index 000000000..71055b08e --- /dev/null +++ b/docshell/base/timeline/RestyleTimelineMarker.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_RestyleTimelineMarker_h_ +#define mozilla_RestyleTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class RestyleTimelineMarker : public TimelineMarker +{ +public: + RestyleTimelineMarker(nsRestyleHint aRestyleHint, + MarkerTracingType aTracingType) + : TimelineMarker("Styles", aTracingType) + { + if (aRestyleHint) { + mRestyleHint.AssignWithConversion(RestyleManager::RestyleHintToString(aRestyleHint)); + } + } + + virtual void AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker) override + { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mRestyleHint.Construct(mRestyleHint); + } + } + +private: + nsString mRestyleHint; +}; + +} // namespace mozilla + +#endif // mozilla_RestyleTimelineMarker_h_ diff --git a/docshell/base/timeline/TimelineConsumers.cpp b/docshell/base/timeline/TimelineConsumers.cpp new file mode 100644 index 000000000..2668cdc9b --- /dev/null +++ b/docshell/base/timeline/TimelineConsumers.cpp @@ -0,0 +1,312 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TimelineConsumers.h" + +#include "mozilla/ClearOnShutdown.h" +#include "nsAppRunner.h" // for XRE_IsContentProcess, XRE_IsParentProcess +#include "nsDocShell.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(TimelineConsumers, nsIObserver); + +StaticMutex TimelineConsumers::sMutex; + +// Manually manage this singleton's lifetime and destroy it before shutdown. +// This avoids the leakchecker detecting false-positive memory leaks when +// using automatic memory management (i.e. statically instantiating this +// singleton inside the `Get` method), which would automatically destroy it on +// application shutdown, but too late for the leakchecker. Sigh... +StaticRefPtr<TimelineConsumers> TimelineConsumers::sInstance; + +// This flag makes sure the singleton never gets instantiated while a shutdown +// is in progress. This can actually happen, and `ClearOnShutdown` doesn't work +// in these cases. +bool TimelineConsumers::sInShutdown = false; + +already_AddRefed<TimelineConsumers> +TimelineConsumers::Get() +{ + // Using this class is not supported yet for other processes other than + // parent or content. To avoid accidental checks to methods like `IsEmpty`, + // which would probably always be true in those cases, assert here. + // Remember, there will be different singletons available to each process. + MOZ_ASSERT(XRE_IsContentProcess() || XRE_IsParentProcess()); + + // If we are shutting down, don't bother doing anything. Note: we can only + // know whether or not we're in shutdown if we're instantiated. + if (sInShutdown) { + return nullptr; + } + + // Note: We don't simply check `sInstance` for null-ness here, since otherwise + // this can resurrect the TimelineConsumers pretty late during shutdown. + // We won't know if we're in shutdown or not though, because the singleton + // could have been destroyed or just never instantiated, so in the previous + // conditional `sInShutdown` would be false. + static bool firstTime = true; + if (firstTime) { + firstTime = false; + + StaticMutexAutoLock lock(sMutex); + sInstance = new TimelineConsumers(); + + // Make sure the initialization actually suceeds, otherwise don't allow + // access by destroying the instance immediately. + if (sInstance->Init()) { + ClearOnShutdown(&sInstance); + } else { + sInstance->RemoveObservers(); + sInstance = nullptr; + } + } + + RefPtr<TimelineConsumers> copy = sInstance.get(); + return copy.forget(); +} + +bool +TimelineConsumers::Init() +{ + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (!obs) { + return false; + } + if (NS_WARN_IF(NS_FAILED( + obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false)))) { + return false; + } + return true; +} + +bool +TimelineConsumers::RemoveObservers() +{ + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (!obs) { + return false; + } + if (NS_WARN_IF(NS_FAILED( + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)))) { + return false; + } + return true; +} + +nsresult +TimelineConsumers::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + sInShutdown = true; + RemoveObservers(); + return NS_OK; + } + + MOZ_ASSERT(false, "TimelineConsumers got unexpected topic!"); + return NS_ERROR_UNEXPECTED; +} + +TimelineConsumers::TimelineConsumers() + : mActiveConsumers(0) +{ +} + +void +TimelineConsumers::AddConsumer(nsDocShell* aDocShell) +{ + MOZ_ASSERT(NS_IsMainThread()); + StaticMutexAutoLock lock(sMutex); // for `mActiveConsumers` and `mMarkersStores`. + + UniquePtr<ObservedDocShell>& observed = aDocShell->mObserved; + MOZ_ASSERT(!observed); + + mActiveConsumers++; + + ObservedDocShell* obsDocShell = new ObservedDocShell(aDocShell); + MarkersStorage* storage = static_cast<MarkersStorage*>(obsDocShell); + + observed.reset(obsDocShell); + mMarkersStores.insertFront(storage); +} + +void +TimelineConsumers::RemoveConsumer(nsDocShell* aDocShell) +{ + MOZ_ASSERT(NS_IsMainThread()); + StaticMutexAutoLock lock(sMutex); // for `mActiveConsumers` and `mMarkersStores`. + + UniquePtr<ObservedDocShell>& observed = aDocShell->mObserved; + MOZ_ASSERT(observed); + + mActiveConsumers--; + + // Clear all markers from the `mTimelineMarkers` store. + observed.get()->ClearMarkers(); + // Remove self from the `mMarkersStores` store. + observed.get()->remove(); + // Prepare for becoming a consumer later. + observed.reset(nullptr); +} + +bool +TimelineConsumers::HasConsumer(nsIDocShell* aDocShell) +{ + MOZ_ASSERT(NS_IsMainThread()); + return aDocShell + ? aDocShell->GetRecordProfileTimelineMarkers() + : false; +} + +bool +TimelineConsumers::IsEmpty() +{ + StaticMutexAutoLock lock(sMutex); // for `mActiveConsumers`. + return mActiveConsumers == 0; +} + +void +TimelineConsumers::AddMarkerForDocShell(nsDocShell* aDocShell, + const char* aName, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (HasConsumer(aDocShell)) { + aDocShell->mObserved->AddMarker(Move(MakeUnique<TimelineMarker>(aName, aTracingType, aStackRequest))); + } +} + +void +TimelineConsumers::AddMarkerForDocShell(nsDocShell* aDocShell, + const char* aName, + const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (HasConsumer(aDocShell)) { + aDocShell->mObserved->AddMarker(Move(MakeUnique<TimelineMarker>(aName, aTime, aTracingType, aStackRequest))); + } +} + +void +TimelineConsumers::AddMarkerForDocShell(nsDocShell* aDocShell, + UniquePtr<AbstractTimelineMarker>&& aMarker) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (HasConsumer(aDocShell)) { + aDocShell->mObserved->AddMarker(Move(aMarker)); + } +} + +void +TimelineConsumers::AddMarkerForDocShell(nsIDocShell* aDocShell, + const char* aName, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) +{ + MOZ_ASSERT(NS_IsMainThread()); + AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), aName, aTracingType, aStackRequest); +} + +void +TimelineConsumers::AddMarkerForDocShell(nsIDocShell* aDocShell, + const char* aName, + const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) +{ + MOZ_ASSERT(NS_IsMainThread()); + AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), aName, aTime, aTracingType, aStackRequest); +} + +void +TimelineConsumers::AddMarkerForDocShell(nsIDocShell* aDocShell, + UniquePtr<AbstractTimelineMarker>&& aMarker) +{ + MOZ_ASSERT(NS_IsMainThread()); + AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), Move(aMarker)); +} + +void +TimelineConsumers::AddMarkerForAllObservedDocShells(const char* aName, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest /* = STACK */) +{ + bool isMainThread = NS_IsMainThread(); + StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`. + + for (MarkersStorage* storage = mMarkersStores.getFirst(); + storage != nullptr; + storage = storage->getNext()) { + UniquePtr<AbstractTimelineMarker> marker = + MakeUnique<TimelineMarker>(aName, aTracingType, aStackRequest); + if (isMainThread) { + storage->AddMarker(Move(marker)); + } else { + storage->AddOTMTMarker(Move(marker)); + } + } +} + +void +TimelineConsumers::AddMarkerForAllObservedDocShells(const char* aName, + const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest /* = STACK */) +{ + bool isMainThread = NS_IsMainThread(); + StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`. + + for (MarkersStorage* storage = mMarkersStores.getFirst(); + storage != nullptr; + storage = storage->getNext()) { + UniquePtr<AbstractTimelineMarker> marker = + MakeUnique<TimelineMarker>(aName, aTime, aTracingType, aStackRequest); + if (isMainThread) { + storage->AddMarker(Move(marker)); + } else { + storage->AddOTMTMarker(Move(marker)); + } + } +} + +void +TimelineConsumers::AddMarkerForAllObservedDocShells(UniquePtr<AbstractTimelineMarker>& aMarker) +{ + bool isMainThread = NS_IsMainThread(); + StaticMutexAutoLock lock(sMutex); // for `mMarkersStores`. + + for (MarkersStorage* storage = mMarkersStores.getFirst(); + storage != nullptr; + storage = storage->getNext()) { + UniquePtr<AbstractTimelineMarker> clone = aMarker->Clone(); + if (isMainThread) { + storage->AddMarker(Move(clone)); + } else { + storage->AddOTMTMarker(Move(clone)); + } + } +} + +void +TimelineConsumers::PopMarkers(nsDocShell* aDocShell, + JSContext* aCx, + nsTArray<dom::ProfileTimelineMarker>& aStore) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!aDocShell || !aDocShell->mObserved) { + return; + } + + aDocShell->mObserved->PopMarkers(aCx, aStore); +} + +} // namespace mozilla diff --git a/docshell/base/timeline/TimelineConsumers.h b/docshell/base/timeline/TimelineConsumers.h new file mode 100644 index 000000000..04099e1b9 --- /dev/null +++ b/docshell/base/timeline/TimelineConsumers.h @@ -0,0 +1,135 @@ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_TimelineConsumers_h_ +#define mozilla_TimelineConsumers_h_ + +#include "nsIObserver.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/LinkedList.h" +#include "mozilla/StaticMutex.h" +#include "TimelineMarkerEnums.h" // for MarkerTracingType + +class nsDocShell; +class nsIDocShell; +struct JSContext; + +namespace mozilla { +class TimeStamp; +class MarkersStorage; +class AbstractTimelineMarker; + +namespace dom { +struct ProfileTimelineMarker; +} + +class TimelineConsumers : public nsIObserver +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + +private: + TimelineConsumers(); + TimelineConsumers(const TimelineConsumers& aOther) = delete; + void operator=(const TimelineConsumers& aOther) = delete; + virtual ~TimelineConsumers() = default; + + bool Init(); + bool RemoveObservers(); + +public: + static already_AddRefed<TimelineConsumers> Get(); + + // Methods for registering interested consumers (i.e. "devtools toolboxes"). + // Each consumer should be directly focused on a particular docshell, but + // timeline markers don't necessarily have to be tied to that docshell. + // See the public `AddMarker*` methods below. + // Main thread only. + void AddConsumer(nsDocShell* aDocShell); + void RemoveConsumer(nsDocShell* aDocShell); + + bool HasConsumer(nsIDocShell* aDocShell); + + // Checks if there's any existing interested consumer. + // May be called from any thread. + bool IsEmpty(); + + // Methods for adding markers relevant for particular docshells, or generic + // (meaning that they either can't be tied to a particular docshell, or one + // wasn't accessible in the part of the codebase where they're instantiated). + // These will only add markers if at least one docshell is currently being + // observed by a timeline. Markers tied to a particular docshell won't be + // created unless that docshell is specifically being currently observed. + // See nsIDocShell::recordProfileTimelineMarkers + + // These methods create a basic TimelineMarker from a name and some metadata, + // relevant for a specific docshell. + // Main thread only. + void AddMarkerForDocShell(nsDocShell* aDocShell, + const char* aName, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + void AddMarkerForDocShell(nsIDocShell* aDocShell, + const char* aName, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + + void AddMarkerForDocShell(nsDocShell* aDocShell, + const char* aName, + const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + void AddMarkerForDocShell(nsIDocShell* aDocShell, + const char* aName, + const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + + // These methods register and receive ownership of an already created marker, + // relevant for a specific docshell. + // Main thread only. + void AddMarkerForDocShell(nsDocShell* aDocShell, + UniquePtr<AbstractTimelineMarker>&& aMarker); + void AddMarkerForDocShell(nsIDocShell* aDocShell, + UniquePtr<AbstractTimelineMarker>&& aMarker); + + // These methods create a basic marker from a name and some metadata, + // which doesn't have to be relevant to a specific docshell. + // May be called from any thread. + void AddMarkerForAllObservedDocShells(const char* aName, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + void AddMarkerForAllObservedDocShells(const char* aName, + const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + + // This method clones and registers an already instantiated marker, + // which doesn't have to be relevant to a specific docshell. + // May be called from any thread. + void AddMarkerForAllObservedDocShells(UniquePtr<AbstractTimelineMarker>& aMarker); + + void PopMarkers(nsDocShell* aDocShell, + JSContext* aCx, + nsTArray<dom::ProfileTimelineMarker>& aStore); + +private: + static StaticRefPtr<TimelineConsumers> sInstance; + static bool sInShutdown; + + // Counter for how many timelines are currently interested in markers, + // and a list of the MarkersStorage interfaces representing them. + unsigned long mActiveConsumers; + LinkedList<MarkersStorage> mMarkersStores; + + // Protects this class's data structures. + static StaticMutex sMutex; +}; + +} // namespace mozilla + +#endif /* mozilla_TimelineConsumers_h_ */ diff --git a/docshell/base/timeline/TimelineMarker.cpp b/docshell/base/timeline/TimelineMarker.cpp new file mode 100644 index 000000000..b83e9ceb4 --- /dev/null +++ b/docshell/base/timeline/TimelineMarker.cpp @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TimelineMarker.h" + +namespace mozilla { + +TimelineMarker::TimelineMarker(const char* aName, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) + : AbstractTimelineMarker(aName, aTracingType) +{ + CaptureStackIfNecessary(aTracingType, aStackRequest); +} + +TimelineMarker::TimelineMarker(const char* aName, + const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) + : AbstractTimelineMarker(aName, aTime, aTracingType) +{ + CaptureStackIfNecessary(aTracingType, aStackRequest); +} + +void +TimelineMarker::AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker) +{ + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mProcessType.Construct(GetProcessType()); + aMarker.mIsOffMainThread.Construct(IsOffMainThread()); + } +} + +JSObject* +TimelineMarker::GetStack() +{ + if (mStackTrace.initialized()) { + return mStackTrace; + } + return nullptr; +} + +void +TimelineMarker::CaptureStack() +{ + JSContext* ctx = nsContentUtils::GetCurrentJSContext(); + if (ctx) { + JS::RootedObject stack(ctx); + if (JS::CaptureCurrentStack(ctx, &stack)) { + mStackTrace.init(ctx, stack.get()); + } else { + JS_ClearPendingException(ctx); + } + } +} + +void +TimelineMarker::CaptureStackIfNecessary(MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest) +{ + if ((aTracingType == MarkerTracingType::START || + aTracingType == MarkerTracingType::TIMESTAMP) && + aStackRequest != MarkerStackRequest::NO_STACK) { + CaptureStack(); + } +} + +} // namespace mozilla diff --git a/docshell/base/timeline/TimelineMarker.h b/docshell/base/timeline/TimelineMarker.h new file mode 100644 index 000000000..2e0b4dd02 --- /dev/null +++ b/docshell/base/timeline/TimelineMarker.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_TimelineMarker_h_ +#define mozilla_TimelineMarker_h_ + +#include "AbstractTimelineMarker.h" +#include "js/RootingAPI.h" + +namespace mozilla { + +// Objects of this type can be added to the timeline if there is an interested +// consumer. The class can also be subclassed to let a given marker creator +// provide custom details. +class TimelineMarker : public AbstractTimelineMarker +{ +public: + TimelineMarker(const char* aName, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + + TimelineMarker(const char* aName, + const TimeStamp& aTime, + MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest = MarkerStackRequest::STACK); + + virtual void AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker) override; + virtual JSObject* GetStack() override; + +protected: + void CaptureStack(); + +private: + // While normally it is not a good idea to make a persistent root, + // in this case changing nsDocShell to participate in cycle + // collection was deemed too invasive, and the markers are only held + // here temporarily to boot. + JS::PersistentRooted<JSObject*> mStackTrace; + + void CaptureStackIfNecessary(MarkerTracingType aTracingType, + MarkerStackRequest aStackRequest); +}; + +} // namespace mozilla + +#endif /* mozilla_TimelineMarker_h_ */ diff --git a/docshell/base/timeline/TimelineMarkerEnums.h b/docshell/base/timeline/TimelineMarkerEnums.h new file mode 100644 index 000000000..779f8ba72 --- /dev/null +++ b/docshell/base/timeline/TimelineMarkerEnums.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_TimelineMarkerEnums_h_ +#define mozilla_TimelineMarkerEnums_h_ + +namespace mozilla { + +enum class MarkerTracingType { + START, + END, + TIMESTAMP, + HELPER_EVENT +}; + +enum class MarkerStackRequest { + STACK, + NO_STACK +}; + +} // namespace mozilla + +#endif // mozilla_TimelineMarkerEnums_h_ diff --git a/docshell/base/timeline/TimestampTimelineMarker.h b/docshell/base/timeline/TimestampTimelineMarker.h new file mode 100644 index 000000000..4e588b576 --- /dev/null +++ b/docshell/base/timeline/TimestampTimelineMarker.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_TimestampTimelineMarker_h_ +#define mozilla_TimestampTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class TimestampTimelineMarker : public TimelineMarker +{ +public: + explicit TimestampTimelineMarker(const nsAString& aCause) + : TimelineMarker("TimeStamp", MarkerTracingType::TIMESTAMP) + , mCause(aCause) + {} + + virtual void AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker) override + { + TimelineMarker::AddDetails(aCx, aMarker); + + if (!mCause.IsEmpty()) { + aMarker.mCauseName.Construct(mCause); + } + } + +private: + nsString mCause; +}; + +} // namespace mozilla + +#endif // mozilla_TimestampTimelineMarker_h_ diff --git a/docshell/base/timeline/WorkerTimelineMarker.h b/docshell/base/timeline/WorkerTimelineMarker.h new file mode 100644 index 000000000..b6b369270 --- /dev/null +++ b/docshell/base/timeline/WorkerTimelineMarker.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_WorkerTimelineMarker_h_ +#define mozilla_WorkerTimelineMarker_h_ + +#include "TimelineMarker.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" + +namespace mozilla { + +class WorkerTimelineMarker : public TimelineMarker +{ +public: + WorkerTimelineMarker(dom::ProfileTimelineWorkerOperationType aOperationType, + MarkerTracingType aTracingType) + : TimelineMarker("Worker", aTracingType, MarkerStackRequest::NO_STACK) + , mOperationType(aOperationType) + {} + + virtual UniquePtr<AbstractTimelineMarker> Clone() override + { + WorkerTimelineMarker* clone = new WorkerTimelineMarker(mOperationType, GetTracingType()); + clone->SetCustomTime(GetTime()); + return UniquePtr<AbstractTimelineMarker>(clone); + } + + virtual void AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker) override + { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + aMarker.mWorkerOperation.Construct(mOperationType); + } + } + +private: + dom::ProfileTimelineWorkerOperationType mOperationType; +}; + +} // namespace mozilla + +#endif /* mozilla_WorkerTimelineMarker_h_ */ diff --git a/docshell/base/timeline/moz.build b/docshell/base/timeline/moz.build new file mode 100644 index 000000000..824e37466 --- /dev/null +++ b/docshell/base/timeline/moz.build @@ -0,0 +1,42 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS.mozilla += [ + 'AbstractTimelineMarker.h', + 'AutoGlobalTimelineMarker.h', + 'AutoTimelineMarker.h', + 'CompositeTimelineMarker.h', + 'ConsoleTimelineMarker.h', + 'DocLoadingTimelineMarker.h', + 'EventTimelineMarker.h', + 'JavascriptTimelineMarker.h', + 'LayerTimelineMarker.h', + 'MarkersStorage.h', + 'MessagePortTimelineMarker.h', + 'ObservedDocShell.h', + 'RestyleTimelineMarker.h', + 'TimelineConsumers.h', + 'TimelineMarker.h', + 'TimelineMarkerEnums.h', + 'TimestampTimelineMarker.h', + 'WorkerTimelineMarker.h', +] + +UNIFIED_SOURCES += [ + 'AbstractTimelineMarker.cpp', + 'AutoGlobalTimelineMarker.cpp', + 'AutoTimelineMarker.cpp', + 'MarkersStorage.cpp', + 'ObservedDocShell.cpp', + 'TimelineConsumers.cpp', + 'TimelineMarker.cpp', +] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '/docshell/base' +] diff --git a/docshell/base/timeline/readme.md b/docshell/base/timeline/readme.md new file mode 100644 index 000000000..2af9ed3a2 --- /dev/null +++ b/docshell/base/timeline/readme.md @@ -0,0 +1,97 @@ + +#Timeline + +The files in this directory are concerned with providing the backend platform features required for the developer tools interested in tracking down operations done in Gecko. The mechanism we use to define these operations are `markers`. + +Examples of traced operations include: + +* Style Recalculation +* Layout +* Painting +* JavaScript run-to-completion +* HTML parsing +* etc. + +The traced operations are displayed in the DevTools Performance tool's timeline. + +This is an overview of how everything works and can be extended. + +##MarkersStorage +A `MarkersStorage` is an abstract class defining a place where timeline markers may be held. It defines an interface with pure virtual functions to highlight how this storage can be interacted with: + +- `AddMarker`: adding a marker, from the main thread only +- `AddOTMTMarker`: adding a marker off the main thread only +- `ClearMarkers`: clearing all accumulated markers (both from the main thread and off it) +- `PopMarkers`: popping all accumulated markers (both from the main thread and off it). + +Note on why we handle on/off the main thread markers separately: since most of our markers will come from the main thread, we can be a little more efficient and avoid dealing with multithreading scenarios until all the markers are actually cleared or popped in `ClearMarkers` or `PopMarkers`. Main thread markers may only be added via `AddMarker`, while off the main thread markers may only be added via `AddOTMTMarker`. Clearing and popping markers will yield until all operations involving off the main thread markers finish. When popping, the markers accumulated off the main thread will be moved over. We expect popping to be fairly infrequent (every few hundred milliseconds, currently we schedule this to happen every 200ms). + +##ObservedDocShell +The only implementation of a MarkersStorage we have right now is an `ObservedDocShell`. + +Instances of `ObservedDocShell` accumulate markers that are *mostly* about a particular docshell. At a high level, for example, an `ObservedDocshell` would be created when a timeline tool is opened on a page. It is reasonable to assume that most operations which are interesting for that particular page happen on the main thread. However certain operations may happen outside of it, yet are interesting for its developers, for which markers can be created as well (e.g. web audio stuff, service workers etc.). It is also reasonable to assume that a docshell may sometimes not be easily accessible from certain parts of the platform code, but for which markers still need to be created. + +Therefore, the following scenarios arise: + +- a). creating a marker on the main thread about a particular dochsell + +- b). creating a marker on the main thread without pinpointing to an affected docshell (unlikely, but allowed; in this case such a marker would have to be stored in all currently existing `ObservedDocShell` instances) + +- c). creating a marker off the main thread about a particular dochsell (impossible; docshells can't be referenced outside the main thread, in which case some other type of identification mechanism needs to be put in place). + +- d). creating a marker off the main thread without pinpointing to a particular docshell (same path as c. here, such a marker would have to be stored in all currently existing `ObservedDocShell` instances). + +An observed docshell (in other words, "a docshell for which a timeline tool was opened") can thus receive both main thread and off the main thread markers. + +Cross-process markers are unnecessary at the moment, but tracked in bug 1200120. + +##TimelineConsumers +A `TimelineConsumer` is a singleton that facilitates access to `ObservedDocShell` instances. This is where a docshell can register/unregister itself as being observed via the `AddConsumer` and `RemoveConsumer` methods. + +All markers may only be stored via this singleton. Certain helper methods are available: + +* Main thread only +`AddMarkerForDocShell(nsDocShell*, const char*, MarkerTracingType)` +`AddMarkerForDocShell(nsDocShell*, const char*, const TimeStamp&, MarkerTracingType)` +`AddMarkerForDocShell(nsDocShell*, UniquePtr<AbstractTimelineMarker>&&)` + +* Any thread +`AddMarkerForAllObservedDocShells(const char*, MarkerTracingType)` +`AddMarkerForAllObservedDocShells(const char*, const TimeStamp&, MarkerTracingType)` +`AddMarkerForAllObservedDocShells(UniquePtr<AbstractTimelineMarker>&)` + +The "main thread only" methods deal with point a). described above. The "any thread" methods deal with points b). and d). + +##AbstractTimelineMarker + +All markers inherit from this abstract class, providing a simple thread-safe extendable blueprint. + +Markers are readonly after instantiation, and will always be identified by a name, a timestamp and their tracing type (`START`, `END`, `TIMESTAMP`). It *should not* make sense to modify their data after their creation. + +There are only two accessible constructors: +`AbstractTimelineMarker(const char*, MarkerTracingType)` +`AbstractTimelineMarker(const char*, const TimeStamp&, MarkerTracingType)` +which create a marker with a name and a tracing type. If unspecified, the corresponding timestamp will be the current instantiation time. Instantiating a marker *much later* after a particular operation is possible, but be careful providing the correct timestamp. + +The `AddDetails` virtual method should be implemented by subclasses when creating WebIDL versions of these markers, which will be sent over to a JavaScript frontend. + +##TimelineMarker +A `TimelineMarker` is the main `AbstractTimelineMarker` implementation. They allow attaching a JavaScript stack on `START` and `TIMESTAMP` markers. + +These markers will be created when using the `TimelineConsumers` helper methods which take in a string, a tracing type and (optionally) a timestamp. For more complex markers, subclasses are encouraged. See `EventTimelineMarker` or `ConsoleTimelineMarker` for some examples. + +##RAII + +### mozilla::AutoTimelineMarker + +The easiest way to trace Gecko events/tasks with start and end timeline markers is to use the `mozilla::AutoTimelineMarker` RAII class. It automatically adds the start marker on construction, and adds the end marker on destruction. Don't worry too much about potential performance impact! It only actually adds the markers when the given docshell is being observed by a timeline consumer, so essentially nothing will happen if a tool to inspect those markers isn't specifically open. + +This class may only be used on the main thread, and pointer to a docshell is necessary. If the docshell is a nullptr, nothing happens and this operation fails silently. + +Example: `AutoTimelineMarker marker(aTargetNode->OwnerDoc()->GetDocShell(), "Parse HTML");` + +### mozilla::AutoGlobalTimelineMarker` + +Similar to the previous RAII class, but doesn't expect a specific docshell, and the marker will be visible in all timeline consumers. This is useful for generic operations that don't involve a particular dochsell, or where a docshell isn't accessible. May also only be used on the main thread. + +Example: `AutoGlobalTimelineMarker marker("Some global operation");` diff --git a/docshell/build/moz.build b/docshell/build/moz.build new file mode 100644 index 000000000..130ec8736 --- /dev/null +++ b/docshell/build/moz.build @@ -0,0 +1,28 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'nsDocShellCID.h', +] + +SOURCES += [ + 'nsDocShellModule.cpp', +] + +LOCAL_INCLUDES += [ + '/docshell/base', + '/docshell/shistory', + '/uriloader/base', + '/uriloader/exthandler', + '/uriloader/prefetch', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +if CONFIG['MOZ_DEVTOOLS'] == 'all': + DEFINES['MOZ_DEVTOOLS_ALL'] = True diff --git a/docshell/build/nsDocShellCID.h b/docshell/build/nsDocShellCID.h new file mode 100644 index 000000000..85080c3cb --- /dev/null +++ b/docshell/build/nsDocShellCID.h @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsDocShellCID_h__ +#define nsDocShellCID_h__ + +#define NS_GLOBALHISTORY2_CONTRACTID "@mozilla.org/browser/global-history;2" + +/** + * A contract for a service that will track download history. This can be + * overridden by embedders if they would like to track additional information + * about downloads. + * + * @implements nsIDownloadHistory + */ +#define NS_DOWNLOADHISTORY_CONTRACTID "@mozilla.org/browser/download-history;1" + +/** + * A contract that can be used to get a service that provides + * meta-information about nsIWebNavigation objects' capabilities. + * @implements nsIWebNavigationInfo + */ +#define NS_WEBNAVIGATION_INFO_CONTRACTID "@mozilla.org/webnavigation-info;1" + +/** + * Class and contract ID for the docshell. This is the container for a web + * navigation context. It implements too many interfaces to count, and the + * exact ones keep changing; if they stabilize somewhat that will get + * documented. + */ +#define NS_DOCSHELL_CID \ + { 0xf1eac762, 0x87e9, 0x11d3, \ + { 0xaf, 0x80, 0x00, 0xa0, 0x24, 0xff, 0xc0, 0x8c } } +#define NS_DOCSHELL_CONTRACTID "@mozilla.org/docshell/html;1" + +/** + * Contract ID to obtain the IHistory interface. This is a non-scriptable + * interface used to interact with history in an asynchronous manner. + */ +#define NS_IHISTORY_CONTRACTID "@mozilla.org/browser/history;1" + +/** + * A contract for a service that is used for finding + * platform-specific applications for handling particular URLs. + * + * @implements nsIExternalURLHandlerService + */ +#define NS_EXTERNALURLHANDLERSERVICE_CONTRACTID \ + "@mozilla.org/uriloader/external-url-handler-service;1" + +/** + * An observer service topic that can be listened to to catch creation + * of content browsing areas (both toplevel ones and subframes). The + * subject of the notification will be the nsIWebNavigation being + * created. At this time the additional data wstring is not defined + * to be anything in particular. + */ +#define NS_WEBNAVIGATION_CREATE "webnavigation-create" + +/** + * An observer service topic that can be listened to to catch creation + * of chrome browsing areas (both toplevel ones and subframes). The + * subject of the notification will be the nsIWebNavigation being + * created. At this time the additional data wstring is not defined + * to be anything in particular. + */ +#define NS_CHROME_WEBNAVIGATION_CREATE "chrome-webnavigation-create" + +/** + * An observer service topic that can be listened to to catch destruction + * of content browsing areas (both toplevel ones and subframes). The + * subject of the notification will be the nsIWebNavigation being + * destroyed. At this time the additional data wstring is not defined + * to be anything in particular. + */ +#define NS_WEBNAVIGATION_DESTROY "webnavigation-destroy" + +/** + * An observer service topic that can be listened to to catch destruction + * of chrome browsing areas (both toplevel ones and subframes). The + * subject of the notification will be the nsIWebNavigation being + * destroyed. At this time the additional data wstring is not defined + * to be anything in particular. + */ +#define NS_CHROME_WEBNAVIGATION_DESTROY "chrome-webnavigation-destroy" + +#endif // nsDocShellCID_h__ diff --git a/docshell/build/nsDocShellModule.cpp b/docshell/build/nsDocShellModule.cpp new file mode 100644 index 000000000..1e62e1479 --- /dev/null +++ b/docshell/build/nsDocShellModule.cpp @@ -0,0 +1,228 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ModuleUtils.h" +#include "nsDocShellCID.h" + +#include "nsDocShell.h" +#include "nsDefaultURIFixup.h" +#include "nsWebNavigationInfo.h" +#include "nsAboutRedirector.h" +#include "nsCDefaultURIFixup.h" + +// uriloader +#include "nsURILoader.h" +#include "nsDocLoader.h" +#include "nsOSHelperAppService.h" +#include "nsExternalProtocolHandler.h" +#include "nsPrefetchService.h" +#include "nsOfflineCacheUpdate.h" +#include "nsLocalHandlerApp.h" +#include "ContentHandlerService.h" +#ifdef MOZ_ENABLE_DBUS +#include "nsDBusHandlerApp.h" +#endif +#if defined(MOZ_WIDGET_ANDROID) +#include "nsExternalSharingAppService.h" +#include "nsExternalURLHandlerService.h" +#endif + +// session history +#include "nsSHEntry.h" +#include "nsSHEntryShared.h" +#include "nsSHistory.h" +#include "nsSHTransaction.h" + +// download history +#include "nsDownloadHistory.h" + +using mozilla::dom::ContentHandlerService; + +static bool gInitialized = false; + +// The one time initialization for this module +static nsresult +Initialize() +{ + NS_PRECONDITION(!gInitialized, "docshell module already initialized"); + if (gInitialized) { + return NS_OK; + } + gInitialized = true; + + nsresult rv = nsSHistory::Startup(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +static void +Shutdown() +{ + nsSHistory::Shutdown(); + nsSHEntryShared::Shutdown(); + gInitialized = false; +} + +// docshell +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDocShell, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsDefaultURIFixup) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWebNavigationInfo, Init) + +// uriloader +NS_GENERIC_FACTORY_CONSTRUCTOR(nsURILoader) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDocLoader, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsOSHelperAppService, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsExternalProtocolHandler) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrefetchService, Init) +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsOfflineCacheUpdateService, + nsOfflineCacheUpdateService::GetInstance) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsOfflineCacheUpdate) +NS_GENERIC_FACTORY_CONSTRUCTOR(PlatformLocalHandlerApp_t) +#ifdef MOZ_ENABLE_DBUS +NS_GENERIC_FACTORY_CONSTRUCTOR(nsDBusHandlerApp) +#endif +#if defined(MOZ_WIDGET_ANDROID) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsExternalSharingAppService) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsExternalURLHandlerService) +#endif +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(ContentHandlerService, Init) + +// session history +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHEntry) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHTransaction) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSHistory) + +// download history +NS_GENERIC_FACTORY_CONSTRUCTOR(nsDownloadHistory) + +NS_DEFINE_NAMED_CID(NS_DOCSHELL_CID); +NS_DEFINE_NAMED_CID(NS_DEFAULTURIFIXUP_CID); +NS_DEFINE_NAMED_CID(NS_WEBNAVIGATION_INFO_CID); +NS_DEFINE_NAMED_CID(NS_ABOUT_REDIRECTOR_MODULE_CID); +NS_DEFINE_NAMED_CID(NS_URI_LOADER_CID); +NS_DEFINE_NAMED_CID(NS_DOCUMENTLOADER_SERVICE_CID); +NS_DEFINE_NAMED_CID(NS_EXTERNALHELPERAPPSERVICE_CID); +NS_DEFINE_NAMED_CID(NS_EXTERNALPROTOCOLHANDLER_CID); +NS_DEFINE_NAMED_CID(NS_PREFETCHSERVICE_CID); +NS_DEFINE_NAMED_CID(NS_OFFLINECACHEUPDATESERVICE_CID); +NS_DEFINE_NAMED_CID(NS_OFFLINECACHEUPDATE_CID); +NS_DEFINE_NAMED_CID(NS_LOCALHANDLERAPP_CID); +#ifdef MOZ_ENABLE_DBUS +NS_DEFINE_NAMED_CID(NS_DBUSHANDLERAPP_CID); +#endif +#if defined(MOZ_WIDGET_ANDROID) +NS_DEFINE_NAMED_CID(NS_EXTERNALSHARINGAPPSERVICE_CID); +NS_DEFINE_NAMED_CID(NS_EXTERNALURLHANDLERSERVICE_CID); +#endif +NS_DEFINE_NAMED_CID(NS_SHENTRY_CID); +NS_DEFINE_NAMED_CID(NS_SHTRANSACTION_CID); +NS_DEFINE_NAMED_CID(NS_SHISTORY_CID); +NS_DEFINE_NAMED_CID(NS_SHISTORY_INTERNAL_CID); +NS_DEFINE_NAMED_CID(NS_DOWNLOADHISTORY_CID); +NS_DEFINE_NAMED_CID(NS_CONTENTHANDLERSERVICE_CID); + +const mozilla::Module::CIDEntry kDocShellCIDs[] = { + { &kNS_DOCSHELL_CID, false, nullptr, nsDocShellConstructor }, + { &kNS_DEFAULTURIFIXUP_CID, false, nullptr, nsDefaultURIFixupConstructor }, + { &kNS_WEBNAVIGATION_INFO_CID, false, nullptr, nsWebNavigationInfoConstructor }, + { &kNS_ABOUT_REDIRECTOR_MODULE_CID, false, nullptr, nsAboutRedirector::Create }, + { &kNS_URI_LOADER_CID, false, nullptr, nsURILoaderConstructor }, + { &kNS_DOCUMENTLOADER_SERVICE_CID, false, nullptr, nsDocLoaderConstructor }, + { &kNS_EXTERNALHELPERAPPSERVICE_CID, false, nullptr, nsOSHelperAppServiceConstructor }, + { &kNS_CONTENTHANDLERSERVICE_CID, false, nullptr, ContentHandlerServiceConstructor, + mozilla::Module::CONTENT_PROCESS_ONLY }, + { &kNS_EXTERNALPROTOCOLHANDLER_CID, false, nullptr, nsExternalProtocolHandlerConstructor }, + { &kNS_PREFETCHSERVICE_CID, false, nullptr, nsPrefetchServiceConstructor }, + { &kNS_OFFLINECACHEUPDATESERVICE_CID, false, nullptr, nsOfflineCacheUpdateServiceConstructor }, + { &kNS_OFFLINECACHEUPDATE_CID, false, nullptr, nsOfflineCacheUpdateConstructor }, + { &kNS_LOCALHANDLERAPP_CID, false, nullptr, PlatformLocalHandlerApp_tConstructor }, +#ifdef MOZ_ENABLE_DBUS + { &kNS_DBUSHANDLERAPP_CID, false, nullptr, nsDBusHandlerAppConstructor }, +#endif +#if defined(MOZ_WIDGET_ANDROID) + { &kNS_EXTERNALSHARINGAPPSERVICE_CID, false, nullptr, nsExternalSharingAppServiceConstructor }, + { &kNS_EXTERNALURLHANDLERSERVICE_CID, false, nullptr, nsExternalURLHandlerServiceConstructor }, +#endif + { &kNS_SHENTRY_CID, false, nullptr, nsSHEntryConstructor }, + { &kNS_SHTRANSACTION_CID, false, nullptr, nsSHTransactionConstructor }, + { &kNS_SHISTORY_CID, false, nullptr, nsSHistoryConstructor }, + { &kNS_SHISTORY_INTERNAL_CID, false, nullptr, nsSHistoryConstructor }, + { &kNS_DOWNLOADHISTORY_CID, false, nullptr, nsDownloadHistoryConstructor }, + { nullptr } +}; + +const mozilla::Module::ContractIDEntry kDocShellContracts[] = { + { "@mozilla.org/docshell;1", &kNS_DOCSHELL_CID }, + { NS_URIFIXUP_CONTRACTID, &kNS_DEFAULTURIFIXUP_CID }, + { NS_WEBNAVIGATION_INFO_CONTRACTID, &kNS_WEBNAVIGATION_INFO_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "about", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "addons", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "buildconfig", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "checkerboard", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "config", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, +#ifdef MOZ_CRASHREPORTER + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "crashes", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, +#endif + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "credits", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, +#ifdef MOZ_DEVTOOLS_ALL + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "debugging", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, +#endif + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "license", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "logo", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "memory", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "mozilla", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "neterror", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "networking", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "newaddon", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "performance", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "plugins", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "serviceworkers", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, +#ifndef ANDROID + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "profiles", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, +#endif + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "srcdoc", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "support", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "telemetry", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_ABOUT_MODULE_CONTRACTID_PREFIX "webrtc", &kNS_ABOUT_REDIRECTOR_MODULE_CID }, + { NS_URI_LOADER_CONTRACTID, &kNS_URI_LOADER_CID }, + { NS_DOCUMENTLOADER_SERVICE_CONTRACTID, &kNS_DOCUMENTLOADER_SERVICE_CID }, + { NS_HANDLERSERVICE_CONTRACTID, &kNS_CONTENTHANDLERSERVICE_CID, mozilla::Module::CONTENT_PROCESS_ONLY }, + { NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &kNS_EXTERNALHELPERAPPSERVICE_CID }, + { NS_EXTERNALPROTOCOLSERVICE_CONTRACTID, &kNS_EXTERNALHELPERAPPSERVICE_CID }, + { NS_MIMESERVICE_CONTRACTID, &kNS_EXTERNALHELPERAPPSERVICE_CID }, + { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX"default", &kNS_EXTERNALPROTOCOLHANDLER_CID }, + { NS_PREFETCHSERVICE_CONTRACTID, &kNS_PREFETCHSERVICE_CID }, + { NS_OFFLINECACHEUPDATESERVICE_CONTRACTID, &kNS_OFFLINECACHEUPDATESERVICE_CID }, + { NS_OFFLINECACHEUPDATE_CONTRACTID, &kNS_OFFLINECACHEUPDATE_CID }, + { NS_LOCALHANDLERAPP_CONTRACTID, &kNS_LOCALHANDLERAPP_CID }, +#ifdef MOZ_ENABLE_DBUS + { NS_DBUSHANDLERAPP_CONTRACTID, &kNS_DBUSHANDLERAPP_CID }, +#endif +#if defined(MOZ_WIDGET_ANDROID) + { NS_EXTERNALSHARINGAPPSERVICE_CONTRACTID, &kNS_EXTERNALSHARINGAPPSERVICE_CID }, + { NS_EXTERNALURLHANDLERSERVICE_CONTRACTID, &kNS_EXTERNALURLHANDLERSERVICE_CID }, +#endif + { NS_SHENTRY_CONTRACTID, &kNS_SHENTRY_CID }, + { NS_SHTRANSACTION_CONTRACTID, &kNS_SHTRANSACTION_CID }, + { NS_SHISTORY_CONTRACTID, &kNS_SHISTORY_CID }, + { NS_SHISTORY_INTERNAL_CONTRACTID, &kNS_SHISTORY_INTERNAL_CID }, + { NS_DOWNLOADHISTORY_CONTRACTID, &kNS_DOWNLOADHISTORY_CID }, + { nullptr } +}; + +static const mozilla::Module kDocShellModule = { + mozilla::Module::kVersion, + kDocShellCIDs, + kDocShellContracts, + nullptr, + nullptr, + Initialize, + Shutdown +}; + +NSMODULE_DEFN(docshell_provider) = &kDocShellModule; diff --git a/docshell/moz.build b/docshell/moz.build new file mode 100644 index 000000000..c89c58e0d --- /dev/null +++ b/docshell/moz.build @@ -0,0 +1,50 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += [ + 'base', + 'shistory', + 'build', + 'resources/content', +] + +XPCSHELL_TESTS_MANIFESTS += [ + 'test/unit/xpcshell.ini', + 'test/unit_ipc/xpcshell.ini', +] + +MOCHITEST_MANIFESTS += [ + 'test/iframesandbox/mochitest.ini', + 'test/mochitest.ini', + 'test/navigation/mochitest.ini', +] + +MOCHITEST_CHROME_MANIFESTS += [ + 'test/chrome/chrome.ini', +] + +BROWSER_CHROME_MANIFESTS += [ + 'test/browser/browser.ini', + 'test/navigation/browser.ini', +] + +TEST_HARNESS_FILES.testing.mochitest.tests.docshell.test.chrome += [ + 'test/chrome/112564_nocache.html', + 'test/chrome/112564_nocache.html^headers^', + 'test/chrome/215405_nocache.html', + 'test/chrome/215405_nocache.html^headers^', + 'test/chrome/215405_nostore.html', + 'test/chrome/215405_nostore.html^headers^', + 'test/chrome/582176_dummy.html', + 'test/chrome/582176_xml.xml', + 'test/chrome/582176_xslt.xsl', + 'test/chrome/92598_nostore.html', + 'test/chrome/92598_nostore.html^headers^', + 'test/chrome/allowContentRetargeting.sjs', + 'test/chrome/blue.png', + 'test/chrome/bug89419.sjs', + 'test/chrome/red.png', +] diff --git a/docshell/resources/content/jar.mn b/docshell/resources/content/jar.mn new file mode 100644 index 000000000..49c26aaba --- /dev/null +++ b/docshell/resources/content/jar.mn @@ -0,0 +1,6 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +toolkit.jar: + content/global/netError.xhtml diff --git a/docshell/resources/content/moz.build b/docshell/resources/content/moz.build new file mode 100644 index 000000000..eb4454d28 --- /dev/null +++ b/docshell/resources/content/moz.build @@ -0,0 +1,7 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file diff --git a/docshell/resources/content/netError.xhtml b/docshell/resources/content/netError.xhtml new file mode 100644 index 000000000..1db7131d3 --- /dev/null +++ b/docshell/resources/content/netError.xhtml @@ -0,0 +1,397 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!DOCTYPE html [ + <!ENTITY % htmlDTD + PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "DTD/xhtml1-strict.dtd"> + %htmlDTD; + <!ENTITY % netErrorAppDTD + SYSTEM "chrome://global/locale/netErrorApp.dtd"> + %netErrorAppDTD; + <!ENTITY % netErrorDTD + SYSTEM "chrome://global/locale/netError.dtd"> + %netErrorDTD; + <!ENTITY % globalDTD + SYSTEM "chrome://global/locale/global.dtd"> + %globalDTD; +]> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>&loadError.label;</title> + <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" /> + <!-- If the location of the favicon is changed here, the FAVICON_ERRORPAGE_URL symbol in + toolkit/components/places/src/nsFaviconService.h should be updated. --> + <link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/warning-16.png"/> + + <script type="application/javascript"><![CDATA[ + // Error url MUST be formatted like this: + // moz-neterror:page?e=error&u=url&d=desc + // + // or optionally, to specify an alternate CSS class to allow for + // custom styling and favicon: + // + // moz-neterror:page?e=error&u=url&s=classname&d=desc + + // Note that this file uses document.documentURI to get + // the URL (with the format from above). This is because + // document.location.href gets the current URI off the docshell, + // which is the URL displayed in the location bar, i.e. + // the URI that the user attempted to load. + + function getErrorCode() + { + var url = document.documentURI; + var error = url.search(/e\=/); + var duffUrl = url.search(/\&u\=/); + return decodeURIComponent(url.slice(error + 2, duffUrl)); + } + + function getCSSClass() + { + var url = document.documentURI; + var matches = url.match(/s\=([^&]+)\&/); + // s is optional, if no match just return nothing + if (!matches || matches.length < 2) + return ""; + + // parenthetical match is the second entry + return decodeURIComponent(matches[1]); + } + + function getDescription() + { + var url = document.documentURI; + var desc = url.search(/d\=/); + + // desc == -1 if not found; if so, return an empty string + // instead of what would turn out to be portions of the URI + if (desc == -1) + return ""; + + return decodeURIComponent(url.slice(desc + 2)); + } + + function retryThis(buttonEl) + { + // Note: The application may wish to handle switching off "offline mode" + // before this event handler runs, but using a capturing event handler. + + // Session history has the URL of the page that failed + // to load, not the one of the error page. So, just call + // reload(), which will also repost POST data correctly. + try { + location.reload(); + } catch (e) { + // We probably tried to reload a URI that caused an exception to + // occur; e.g. a nonexistent file. + } + + buttonEl.disabled = true; + } + + function initPage() + { + var err = getErrorCode(); + + // if it's an unknown error or there's no title or description + // defined, get the generic message + var errTitle = document.getElementById("et_" + err); + var errDesc = document.getElementById("ed_" + err); + if (!errTitle || !errDesc) + { + errTitle = document.getElementById("et_generic"); + errDesc = document.getElementById("ed_generic"); + } + + var title = document.getElementById("errorTitleText"); + if (title) + { + title.parentNode.replaceChild(errTitle, title); + // change id to the replaced child's id so styling works + errTitle.id = "errorTitleText"; + } + + var sd = document.getElementById("errorShortDescText"); + if (sd) + sd.textContent = getDescription(); + + var ld = document.getElementById("errorLongDesc"); + if (ld) + { + ld.parentNode.replaceChild(errDesc, ld); + // change id to the replaced child's id so styling works + errDesc.id = "errorLongDesc"; + } + + // remove undisplayed errors to avoid bug 39098 + var errContainer = document.getElementById("errorContainer"); + errContainer.parentNode.removeChild(errContainer); + + var className = getCSSClass(); + if (className && className != "expertBadCert") { + // Associate a CSS class with the root of the page, if one was passed in, + // to allow custom styling. + // Not "expertBadCert" though, don't want to deal with the favicon + document.documentElement.className = className; + + // Also, if they specified a CSS class, they must supply their own + // favicon. In order to trigger the browser to repaint though, we + // need to remove/add the link element. + var favicon = document.getElementById("favicon"); + var faviconParent = favicon.parentNode; + faviconParent.removeChild(favicon); + favicon.setAttribute("href", "chrome://global/skin/icons/" + className + "_favicon.png"); + faviconParent.appendChild(favicon); + } + if (className == "expertBadCert") { + showSecuritySection(); + } + + if (err == "remoteXUL") { + // Remove the "Try again" button for remote XUL errors given that + // it is useless. + document.getElementById("errorTryAgain").style.display = "none"; + } + + if (err == "cspBlocked") { + // Remove the "Try again" button for CSP violations, since it's + // almost certainly useless. (Bug 553180) + document.getElementById("errorTryAgain").style.display = "none"; + } + + if (err == "nssBadCert") { + // Remove the "Try again" button for security exceptions, since it's + // almost certainly useless. + document.getElementById("errorTryAgain").style.display = "none"; + document.getElementById("errorPageContainer").setAttribute("class", "certerror"); + addDomainErrorLink(); + } + else { + // Remove the override block for non-certificate errors. CSS-hiding + // isn't good enough here, because of bug 39098 + var secOverride = document.getElementById("securityOverrideDiv"); + secOverride.parentNode.removeChild(secOverride); + } + + if (err == "inadequateSecurityError") { + // Remove the "Try again" button for HTTP/2 inadequate security as it + // is useless. + document.getElementById("errorTryAgain").style.display = "none"; + + var container = document.getElementById("errorLongDesc"); + for (var span of container.querySelectorAll("span.hostname")) { + span.textContent = document.location.hostname; + } + } + } + + function showSecuritySection() { + // Swap link out, content in + document.getElementById('securityOverrideContent').style.display = ''; + document.getElementById('securityOverrideLink').style.display = 'none'; + } + + /* In the case of SSL error pages about domain mismatch, see if + we can hyperlink the user to the correct site. We don't want + to do this generically since it allows MitM attacks to redirect + users to a site under attacker control, but in certain cases + it is safe (and helpful!) to do so. Bug 402210 + */ + function addDomainErrorLink() { + // Rather than textContent, we need to treat description as HTML + var sd = document.getElementById("errorShortDescText"); + if (sd) { + var desc = getDescription(); + + // sanitize description text - see bug 441169 + + // First, find the index of the <a> tag we care about, being careful not to + // use an over-greedy regex + var re = /<a id="cert_domain_link" title="([^"]+)">/; + var result = re.exec(desc); + if(!result) + return; + + // Remove sd's existing children + sd.textContent = ""; + + // Everything up to the link should be text content + sd.appendChild(document.createTextNode(desc.slice(0, result.index))); + + // Now create the link itself + var anchorEl = document.createElement("a"); + anchorEl.setAttribute("id", "cert_domain_link"); + anchorEl.setAttribute("title", result[1]); + anchorEl.appendChild(document.createTextNode(result[1])); + sd.appendChild(anchorEl); + + // Finally, append text for anything after the closing </a> + sd.appendChild(document.createTextNode(desc.slice(desc.indexOf("</a>") + "</a>".length))); + } + + var link = document.getElementById('cert_domain_link'); + if (!link) + return; + + var okHost = link.getAttribute("title"); + var thisHost = document.location.hostname; + var proto = document.location.protocol; + + // If okHost is a wildcard domain ("*.example.com") let's + // use "www" instead. "*.example.com" isn't going to + // get anyone anywhere useful. bug 432491 + okHost = okHost.replace(/^\*\./, "www."); + + /* case #1: + * example.com uses an invalid security certificate. + * + * The certificate is only valid for www.example.com + * + * Make sure to include the "." ahead of thisHost so that + * a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com" + * + * We'd normally just use a RegExp here except that we lack a + * library function to escape them properly (bug 248062), and + * domain names are famous for having '.' characters in them, + * which would allow spurious and possibly hostile matches. + */ + if (endsWith(okHost, "." + thisHost)) + link.href = proto + okHost; + + /* case #2: + * browser.garage.maemo.org uses an invalid security certificate. + * + * The certificate is only valid for garage.maemo.org + */ + if (endsWith(thisHost, "." + okHost)) + link.href = proto + okHost; + } + + function endsWith(haystack, needle) { + return haystack.slice(-needle.length) == needle; + } + + ]]></script> + </head> + + <body dir="&locale.dir;"> + + <!-- ERROR ITEM CONTAINER (removed during loading to avoid bug 39098) --> + <div id="errorContainer"> + <div id="errorTitlesContainer"> + <h1 id="et_generic">&generic.title;</h1> + <h1 id="et_dnsNotFound">&dnsNotFound.title;</h1> + <h1 id="et_fileNotFound">&fileNotFound.title;</h1> + <h1 id="et_fileAccessDenied">&fileAccessDenied.title;</h1> + <h1 id="et_malformedURI">&malformedURI.title;</h1> + <h1 id="et_unknownProtocolFound">&unknownProtocolFound.title;</h1> + <h1 id="et_connectionFailure">&connectionFailure.title;</h1> + <h1 id="et_netTimeout">&netTimeout.title;</h1> + <h1 id="et_redirectLoop">&redirectLoop.title;</h1> + <h1 id="et_unknownSocketType">&unknownSocketType.title;</h1> + <h1 id="et_netReset">&netReset.title;</h1> + <h1 id="et_notCached">¬Cached.title;</h1> + <h1 id="et_netOffline">&netOffline.title;</h1> + <h1 id="et_netInterrupt">&netInterrupt.title;</h1> + <h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1> + <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1> + <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1> + <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1> + <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1> + <h1 id="et_nssFailure2">&nssFailure2.title;</h1> + <h1 id="et_nssBadCert">&nssBadCert.title;</h1> + <h1 id="et_cspBlocked">&cspBlocked.title;</h1> + <h1 id="et_remoteXUL">&remoteXUL.title;</h1> + <h1 id="et_corruptedContentErrorv2">&corruptedContentErrorv2.title;</h1> + <h1 id="et_inadequateSecurityError">&inadequateSecurityError.title;</h1> + </div> + <div id="errorDescriptionsContainer"> + <div id="ed_generic">&generic.longDesc;</div> + <div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div> + <div id="ed_fileNotFound">&fileNotFound.longDesc;</div> + <div id="ed_fileAccessDenied">&fileAccessDenied.longDesc;</div> + <div id="ed_malformedURI">&malformedURI.longDesc;</div> + <div id="ed_unknownProtocolFound">&unknownProtocolFound.longDesc;</div> + <div id="ed_connectionFailure">&connectionFailure.longDesc;</div> + <div id="ed_netTimeout">&netTimeout.longDesc;</div> + <div id="ed_redirectLoop">&redirectLoop.longDesc;</div> + <div id="ed_unknownSocketType">&unknownSocketType.longDesc;</div> + <div id="ed_netReset">&netReset.longDesc;</div> + <div id="ed_notCached">¬Cached.longDesc;</div> + <div id="ed_netOffline">&netOffline.longDesc2;</div> + <div id="ed_netInterrupt">&netInterrupt.longDesc;</div> + <div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div> + <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div> + <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div> + <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div> + <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div> + <div id="ed_nssFailure2">&nssFailure2.longDesc2;</div> + <div id="ed_nssBadCert">&nssBadCert.longDesc2;</div> + <div id="ed_cspBlocked">&cspBlocked.longDesc;</div> + <div id="ed_remoteXUL">&remoteXUL.longDesc;</div> + <div id="ed_corruptedContentErrorv2">&corruptedContentErrorv2.longDesc;</div> + <div id="ed_inadequateSecurityError">&inadequateSecurityError.longDesc;</div> + </div> + </div> + + <!-- PAGE CONTAINER (for styling purposes only) --> + <div id="errorPageContainer"> + + <!-- Error Title --> + <div id="errorTitle"> + <h1 id="errorTitleText" /> + </div> + + <!-- LONG CONTENT (the section most likely to require scrolling) --> + <div id="errorLongContent"> + + <!-- Short Description --> + <div id="errorShortDesc"> + <p id="errorShortDescText" /> + </div> + + <!-- Long Description (Note: See netError.dtd for used XHTML tags) --> + <div id="errorLongDesc" /> + + <!-- Override section - For ssl errors only. Removed on init for other + error types. --> + <div id="securityOverrideDiv"> + <a id="securityOverrideLink" href="javascript:showSecuritySection();" >&securityOverride.linkText;</a> + <div id="securityOverrideContent" style="display: none;">&securityOverride.warningContent;</div> + </div> + </div> + + <!-- Retry Button --> + <button id="errorTryAgain" autocomplete="off" onclick="retryThis(this);">&retry.label;</button> + <script> + // Only do autofocus if we're the toplevel frame; otherwise we + // don't want to call attention to ourselves! The key part is + // that autofocus happens on insertion into the tree, so we + // can remove the button, add @autofocus, and reinsert the + // button. + if (window.top == window) { + var button = document.getElementById("errorTryAgain"); + var nextSibling = button.nextSibling; + var parent = button.parentNode; + parent.removeChild(button); + button.setAttribute("autofocus", "true"); + parent.insertBefore(button, nextSibling); + } + </script> + + </div> + + <!-- + - Note: It is important to run the script this way, instead of using + - an onload handler. This is because error pages are loaded as + - LOAD_BACKGROUND, which means that onload handlers will not be executed. + --> + <script type="application/javascript">initPage();</script> + + </body> +</html> diff --git a/docshell/shistory/moz.build b/docshell/shistory/moz.build new file mode 100644 index 000000000..c85c3a3cb --- /dev/null +++ b/docshell/shistory/moz.build @@ -0,0 +1,40 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + 'nsIBFCacheEntry.idl', + 'nsIGroupedSHistory.idl', + 'nsIPartialSHistory.idl', + 'nsIPartialSHistoryListener.idl', + 'nsISHContainer.idl', + 'nsISHEntry.idl', + 'nsISHistory.idl', + 'nsISHistoryInternal.idl', + 'nsISHistoryListener.idl', + 'nsISHTransaction.idl', +] + +XPIDL_MODULE = 'shistory' + +EXPORTS += [ + 'nsSHEntryShared.h', +] + +UNIFIED_SOURCES += [ + 'nsSHEntry.cpp', + 'nsSHEntryShared.cpp', + 'nsSHistory.cpp', + 'nsSHTransaction.cpp', +] + +LOCAL_INCLUDES += [ + '/docshell/base', +] + +FINAL_LIBRARY = 'xul' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/docshell/shistory/nsIBFCacheEntry.idl b/docshell/shistory/nsIBFCacheEntry.idl new file mode 100644 index 000000000..e16f1a302 --- /dev/null +++ b/docshell/shistory/nsIBFCacheEntry.idl @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * This interface lets you evict a document from the back/forward cache. + */ +[scriptable, uuid(a576060e-c7df-4d81-aa8c-5b52bd6ad25d)] +interface nsIBFCacheEntry : nsISupports +{ + void RemoveFromBFCacheSync(); + void RemoveFromBFCacheAsync(); + + readonly attribute unsigned long long ID; +}; diff --git a/docshell/shistory/nsIGroupedSHistory.idl b/docshell/shistory/nsIGroupedSHistory.idl new file mode 100644 index 000000000..4001dcc34 --- /dev/null +++ b/docshell/shistory/nsIGroupedSHistory.idl @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIFrameLoader; +interface nsIPartialSHistory; + +/** + * nsIGroupedSHistory represent a combined session history across multiple + * root docshells (usually browser tabs). The participating nsISHistory can + * either be in chrome process or in content process, but nsIGroupedSHistory + * itself lives in chrome process. The communication is proxyed through + * nsIPartialSHistory. + */ +[scriptable, builtinclass, uuid(813e498d-73a8-449a-be09-6187e62c5352)] +interface nsIGroupedSHistory : nsISupports +{ + // The total number of entries of all its partial session histories. + [infallible] readonly attribute unsigned long count; + + /** + * Remove all partial histories after currently active one (if any) and then + * append the given partial session history to the end of the list. + */ + void appendPartialSessionHistory(in nsIPartialSHistory aPartialHistory); + + /** + * Notify the grouped session history that the active partial session history + * has been modified. All partial session histories after the active one + * will be removed and destroy. + */ + void onPartialSessionHistoryChange(in nsIPartialSHistory aPartialHistory); + + /** + * Find the proper partial session history and navigate to the entry + * corresponding to the given global index. Note it doesn't swap frameloaders, + * but rather return the target loader for the caller to swap. + * + * @param aGlobalIndex The global index to navigate to. + * @param aTargetLoaderToSwap The owner frameloader of the to-be-navigate + * partial session history. + */ + void gotoIndex(in unsigned long aGlobalIndex, out nsIFrameLoader aTargetLoaderToSwap); +}; diff --git a/docshell/shistory/nsIPartialSHistory.idl b/docshell/shistory/nsIPartialSHistory.idl new file mode 100644 index 000000000..99359794e --- /dev/null +++ b/docshell/shistory/nsIPartialSHistory.idl @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIFrameLoader; + +/** + * nsIPartialSHistory represents a part of nsIGroupedSHistory. It associates to + * a "partial" nsISHistory in either local or remote process. + */ +[scriptable, builtinclass, uuid(5cd75e28-838c-4a0a-972e-6005f736ef7a)] +interface nsIPartialSHistory : nsISupports +{ + // The number of entries of its corresponding nsISHistory. + [infallible] readonly attribute unsigned long count; + + // If it's part of a grouped session history, globalIndexOffset denotes the + // number of entries ahead. + [infallible] readonly attribute unsigned long globalIndexOffset; + + // The frameloader which owns this partial session history. + readonly attribute nsIFrameLoader ownerFrameLoader; + + /** + * Notify that it's been added to a grouped session history. It also implies + * it's becoming the active partial history of the group. + * + * @param aOffset The number of entries in preceding partial + * session histories. + */ + void onAttachGroupedSessionHistory(in unsigned long aOffset); + + /** + * Notify that one or more entries in its associated nsISHistory object + * have been changed (i.e. add / remove / replace). It's mainly used for + * cross-process case, since in the in-process case we can just register an + * nsISHistoryListener instead. + * + * @param aCount The number of entries in the associated session history. + * It can be the same as the old value if entries were replaced. + */ + void onSessionHistoryChange(in unsigned long aCount); + + /** + * Notify that the partial session history has been swapped in as the active + * session history. Only an active session history can possibly add / remove / + * replace its history entries. + * + * @param aGlobalLength The up-to-date global length. + * @param aTargetLocalIndex The local index to navigate to. + */ + void onActive(in unsigned long aGlobalLength, in unsigned long aTargetLocalIndex); + + /** + * Notify that the partial session history has been swapped out and is no + * longer active. + */ + void onDeactive(); +}; diff --git a/docshell/shistory/nsIPartialSHistoryListener.idl b/docshell/shistory/nsIPartialSHistoryListener.idl new file mode 100644 index 000000000..f0cdfd7e4 --- /dev/null +++ b/docshell/shistory/nsIPartialSHistoryListener.idl @@ -0,0 +1,24 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include "nsISupports.idl" + +/** + * Listener to handle cross partial nsISHistory navigation requests. + */ +[scriptable, uuid(be0cd2b6-6f03-4366-9fe2-184c914ff3df)] +interface nsIPartialSHistoryListener : nsISupports +{ + /** + * Called when the navigation target belongs to another nsISHistory within + * the same nsIGroupedSHistory, and it needs to initiate cross nsISHistory + * navigation. + * + * @param aIndex The index of complete history to navigate to. + */ + void onRequestCrossBrowserNavigation(in unsigned long aIndex); +}; diff --git a/docshell/shistory/nsISHContainer.idl b/docshell/shistory/nsISHContainer.idl new file mode 100644 index 000000000..942603d87 --- /dev/null +++ b/docshell/shistory/nsISHContainer.idl @@ -0,0 +1,49 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsISHEntry; + +/** + * The nsISHEntryContainer. The interface to access child entries + * of an nsISHEntry. + * + */ + +[scriptable, uuid(67dd0357-8372-4122-bff6-217435e8b7e4)] +interface nsISHContainer : nsISupports +{ + /** + * The current number of nsISHEntries which are immediate children of the + * current SHEntry + */ + readonly attribute long childCount; + + /** + * Add a new child SHEntry. If offset is -1 adds to the end of the list. + */ + void AddChild(in nsISHEntry child, in long offset); + + /** + * Removes a child SHEntry + */ + void RemoveChild(in nsISHEntry child); + + /** + * Get child at an index + */ + nsISHEntry GetChildAt(in long index); + + /** + * Replaces a child which is for the same docshell as aNewChild + * with aNewChild. + * @throw if nothing was replaced. + */ + void ReplaceChild(in nsISHEntry aNewChild); + +}; + diff --git a/docshell/shistory/nsISHEntry.idl b/docshell/shistory/nsISHEntry.idl new file mode 100644 index 000000000..05dbf7642 --- /dev/null +++ b/docshell/shistory/nsISHEntry.idl @@ -0,0 +1,352 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * The interface to nsISHentry. Each document or subframe in + * Session History will have a nsISHEntry associated with it which will + * hold all information required to recreate the document from history + * + */ + +#include "nsISupports.idl" + +interface nsIMutableArray; +interface nsILayoutHistoryState; +interface nsIContentViewer; +interface nsIURI; +interface nsIInputStream; +interface nsIDocShellTreeItem; +interface nsIStructuredCloneContainer; +interface nsIBFCacheEntry; +interface nsIPrincipal; + +%{C++ +#include "nsRect.h" +class nsDocShellEditorData; +class nsSHEntryShared; +%} +[ref] native nsIntRect(nsIntRect); +[ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData); +[ptr] native nsSHEntryShared(nsSHEntryShared); + +[scriptable, uuid(0dad26b8-a259-42c7-93f1-2fa7fc076e45)] +interface nsISHEntry : nsISupports +{ + /** + * A readonly property that returns the URI + * of the current entry. The object returned is + * of type nsIURI + */ + readonly attribute nsIURI URI; + + /** + * A readonly property that returns the original URI of the current entry. + * If an entry is the result of a redirect this attribute holds original + * URI. The object returned is of type nsIURI + */ + attribute nsIURI originalURI; + + /** + * This flag remembers whether channel has LOAD_REPLACE set. + */ + attribute boolean loadReplace; + + /** + * A readonly property that returns the title + * of the current entry. The object returned + * is a encoded string + */ + readonly attribute wstring title; + + /** + * A readonly property that returns a boolean + * flag which indicates if the entry was created as a + * result of a subframe navigation. This flag will be + * 'false' when a frameset page is visited for + * the first time. This flag will be 'true' for all + * history entries created as a result of a subframe + * navigation. + */ + readonly attribute boolean isSubFrame; + + /** URI for the document */ + void setURI(in nsIURI aURI); + + /** Referrer URI */ + attribute nsIURI referrerURI; + + /** Referrer policy, holding one of the values (REFERRER_POLICY_*) + * defined in nsIHttpChannel. + */ + attribute unsigned long referrerPolicy; + + /** Content viewer, for fast restoration of presentation */ + attribute nsIContentViewer contentViewer; + + /** Whether the content viewer is marked "sticky" */ + attribute boolean sticky; + + /** Saved state of the global window object */ + attribute nsISupports windowState; + + /** + * Saved position and dimensions of the content viewer; we must adjust the + * root view's widget accordingly if this has changed when the presentation + * is restored. + */ + [noscript] void getViewerBounds(in nsIntRect bounds); + [noscript] void setViewerBounds([const] in nsIntRect bounds); + + /** + * Saved child docshells corresponding to contentViewer. The child shells + * are restored as children of the parent docshell, in this order, when the + * parent docshell restores a saved presentation. + */ + + /** Append a child shell to the end of our list. */ + void addChildShell(in nsIDocShellTreeItem shell); + + /** + * Get the child shell at |index|; returns null if |index| is out of bounds. + */ + nsIDocShellTreeItem childShellAt(in long index); + + /** + * Clear the child shell list. + */ + void clearChildShells(); + + /** Saved refresh URI list for the content viewer */ + attribute nsIMutableArray refreshURIList; + + /** + * Ensure that the cached presentation members are self-consistent. + * If either |contentViewer| or |windowState| are null, then all of the + * following members are cleared/reset: + * contentViewer, sticky, windowState, viewerBounds, childShells, + * refreshURIList. + */ + void syncPresentationState(); + + /** Title for the document */ + void setTitle(in AString aTitle); + + /** Post Data for the document */ + attribute nsIInputStream postData; + + /** LayoutHistoryState for scroll position and form values */ + attribute nsILayoutHistoryState layoutHistoryState; + + /** parent of this entry */ + attribute nsISHEntry parent; + + /** + * The loadType for this entry. This is typically loadHistory except + * when reload is pressed, it has the appropriate reload flag + */ + attribute unsigned long loadType; + + /** + * An ID to help identify this entry from others during + * subframe navigation + */ + attribute unsigned long ID; + + /** attribute to set and get the cache key for the entry */ + attribute nsISupports cacheKey; + + /** attribute to indicate whether layoutHistoryState should be saved */ + attribute boolean saveLayoutStateFlag; + + /** attribute to indicate whether the page is already expired in cache */ + attribute boolean expirationStatus; + + /** + * attribute to indicate the content-type of the document that this + * is a session history entry for + */ + attribute ACString contentType; + + /** + * If we created this SHEntry via history.pushState or modified it via + * history.replaceState, and if we changed the SHEntry's URI via the + * push/replaceState call, and if the SHEntry's new URI differs from its + * old URI by more than just the hash, then we set this field to true. + * + * Additionally, if this SHEntry was created by calling pushState from a + * SHEntry whose URI was modified, this SHEntry's URIWasModified field is + * true. + * + */ + attribute boolean URIWasModified; + + /** Set/Get scrollers' positon in anchored pages */ + void setScrollPosition(in long x, in long y); + void getScrollPosition(out long x, out long y); + + /** Additional ways to create an entry */ + [noscript] void create(in nsIURI URI, in AString title, + in nsIInputStream inputStream, + in nsILayoutHistoryState layoutHistoryState, + in nsISupports cacheKey, in ACString contentType, + in nsIPrincipal triggeringPrincipal, + in nsIPrincipal principalToInherit, + in unsigned long long docshellID, + in boolean dynamicCreation); + + nsISHEntry clone(); + + /** Attribute that indicates if this entry is for a subframe navigation */ + void setIsSubFrame(in boolean aFlag); + + /** Return any content viewer present in or below this node in the + nsSHEntry tree. This will differ from contentViewer in the case + where a child nsSHEntry has the content viewer for this tree. */ + nsIContentViewer getAnyContentViewer(out nsISHEntry ownerEntry); + + /** + * Get the principal, if any, that was associated with the channel + * that the document that was loaded to create this history entry + * came from. + */ + attribute nsIPrincipal triggeringPrincipal; + + /** + * Get the principal, if any, that is used when the inherit flag + * is set. + */ + attribute nsIPrincipal principalToInherit; + + /** + * Get/set data associated with this history state via a pushState() call, + * serialized using structured clone. + **/ + attribute nsIStructuredCloneContainer stateData; + + /** + * Gets the owning pointer to the editor data assosicated with + * this shistory entry. This forgets its pointer, so free it when + * you're done. + */ + [noscript, notxpcom] nsDocShellEditorDataPtr forgetEditorData(); + + /** + * Sets the owning pointer to the editor data assosicated with + * this shistory entry. Unless forgetEditorData() is called, this + * shentry will destroy the editor data when it's destroyed. + */ + [noscript, notxpcom] void setEditorData(in nsDocShellEditorDataPtr aData); + + /** Returns true if this shistory entry is storing a detached editor. */ + [noscript, notxpcom] boolean hasDetachedEditor(); + + /** + * Returns true if the related docshell was added because of + * dynamic addition of an iframe/frame. + */ + boolean isDynamicallyAdded(); + + /** + * Returns true if any of the child entries returns true + * when isDynamicallyAdded is called on it. + */ + boolean hasDynamicallyAddedChild(); + + /** + * The history ID of the docshell. + */ + attribute unsigned long long docshellID; + + readonly attribute nsIBFCacheEntry BFCacheEntry; + + /** + * Does this SHEntry point to the given BFCache entry? If so, evicting + * the BFCache entry will evict the SHEntry, since the two entries + * correspond to the same document. + */ + [notxpcom, noscript] + boolean hasBFCacheEntry(in nsIBFCacheEntry aEntry); + + /** + * Adopt aEntry's BFCacheEntry, so now both this and aEntry point to + * aEntry's BFCacheEntry. + */ + void adoptBFCacheEntry(in nsISHEntry aEntry); + + /** + * Create a new BFCache entry and drop our reference to our old one. This + * call unlinks this SHEntry from any other SHEntries for its document. + */ + void abandonBFCacheEntry(); + + /** + * Does this SHEntry correspond to the same document as aEntry? This is + * true iff the two SHEntries have the same BFCacheEntry. So in + * particular, sharesDocumentWith(aEntry) is guaranteed to return true if + * it's preceeded by a call to adoptBFCacheEntry(aEntry). + */ + boolean sharesDocumentWith(in nsISHEntry aEntry); + + /** + * True if this SHEntry corresponds to a document created by a srcdoc iframe. + * Set when a value is assigned to srcdocData. + */ + readonly attribute boolean isSrcdocEntry; + + /** + * Contents of the srcdoc attribute in a srcdoc iframe to be loaded instead + * of the URI. Similar to a Data URI, this information is needed to + * recreate the document at a later stage. + * Setting this sets isSrcdocEntry to true + */ + attribute AString srcdocData; + + /** + * When isSrcdocEntry is true, this contains the baseURI of the srcdoc + * document for use in situations where it cannot otherwise be determined, + * for example with view-source. + */ + attribute nsIURI baseURI; + + /** + * Sets/gets the current scroll restoration state, + * if true == "manual", false == "auto". + */ + attribute boolean scrollRestorationIsManual; +}; + +[scriptable, uuid(bb66ac35-253b-471f-a317-3ece940f04c5)] +interface nsISHEntryInternal : nsISupports +{ + [notxpcom] void RemoveFromBFCacheAsync(); + [notxpcom] void RemoveFromBFCacheSync(); + + /** + * A number that is assigned by the sHistory when the entry is activated + */ + attribute unsigned long lastTouched; + + /** + * Some state, particularly that related to the back/forward cache, is + * shared between SHEntries which correspond to the same document. This + * method gets a pointer to that shared state. + * + * This shared state is the SHEntry's BFCacheEntry. So + * hasBFCacheEntry(getSharedState()) is guaranteed to return true. + */ + [noscript, notxpcom] + nsSHEntryShared getSharedState(); +}; + +%{ C++ +// {BFD1A791-AD9F-11d3-BDC7-0050040A9B44} +#define NS_SHENTRY_CID \ +{0xbfd1a791, 0xad9f, 0x11d3, {0xbd, 0xc7, 0x0, 0x50, 0x4, 0xa, 0x9b, 0x44}} + +#define NS_SHENTRY_CONTRACTID \ + "@mozilla.org/browser/session-history-entry;1" + +%} + diff --git a/docshell/shistory/nsISHTransaction.idl b/docshell/shistory/nsISHTransaction.idl new file mode 100644 index 000000000..f5467cfb9 --- /dev/null +++ b/docshell/shistory/nsISHTransaction.idl @@ -0,0 +1,56 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsISHEntry; + +/** + * The nsISHTransaction. + */ + + +[scriptable, uuid(2EDF705F-D252-4971-9F09-71DD0F760DC6)] +interface nsISHTransaction : nsISupports +{ + /** + * The nsISHEntry for the current transaction + */ + attribute nsISHEntry sHEntry; + + /** + * The parent of this transaction + */ + attribute nsISHTransaction prev; + + /** + * The legitimate child of this transaction + */ + attribute nsISHTransaction next; + + /** + * Specifies if this transaction should persist. If not it will be replaced + * by new additions to the list. + */ + attribute boolean persist; + + /** + * Create a transaction with parent and History Entry + */ + void create(in nsISHEntry aSHEntry, in nsISHTransaction aPrev); +}; + +%{ C++ +// {BFD1A792-AD9F-11d3-BDC7-0050040A9B44} + + +#define NS_SHTRANSACTION_CID \ +{0xbfd1a792, 0xad9f, 0x11d3, {0xbd, 0xc7, 0x0, 0x50, 0x4, 0xa, 0x9b, 0x44}} + +#define NS_SHTRANSACTION_CONTRACTID \ + "@mozilla.org/browser/session-history-transaction;1" + +%} diff --git a/docshell/shistory/nsISHistory.idl b/docshell/shistory/nsISHistory.idl new file mode 100644 index 000000000..71da2e1ab --- /dev/null +++ b/docshell/shistory/nsISHistory.idl @@ -0,0 +1,221 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsISHEntry; +interface nsISHistoryListener; +interface nsISimpleEnumerator; +interface nsIPartialSHistoryListener; + +/** + * An interface to the primary properties of the Session History + * component. In an embedded browser environment, the nsIWebBrowser + * object creates an instance of session history for each open window. + * A handle to the session history object can be obtained from + * nsIWebNavigation. In a non-embedded situation, the owner of the + * session history component must create a instance of it and set + * it in the nsIWebNavigation object. + * This interface is accessible from javascript. + */ + + +%{C++ +#define NS_SHISTORY_CID \ +{0x7b807041, 0xe60a, 0x4384, {0x93, 0x5f, 0xaf, 0x30, 0x61, 0xd8, 0xb8, 0x15}} + +#define NS_SHISTORY_CONTRACTID "@mozilla.org/browser/shistory;1" +%} + +[scriptable, uuid(7b807041-e60a-4384-935f-af3061d8b815)] +interface nsISHistory: nsISupports +{ + /** + * An attribute denoting whether the nsISHistory is associated to a grouped + * session history. + * + * The abstraction of grouped session history is implemented at + * nsIWebNavigation level, so those canGoBack / canGoForward / gotoIndex + * functions work transparently; + * + * On the other hand, nsISHistory works on partial session history directly. + * Unless otherwise specified, count / index attributes and parameters all + * indicate local count / index, so we won't mess up docshell. + */ + readonly attribute bool isPartial; + + /** + * A readonly property of the interface that returns + * the number of toplevel documents currently available + * in session history. + */ + readonly attribute long count; + + /** + * If isPartial, globalCount denotes the total number of entries in the + * grouped session history; Otherwise it has the same value as count. + */ + readonly attribute long globalCount; + + /** + * A readonly property which represents the difference between global indices + * of grouped session history and local indices of this particular session + * history object. + */ + readonly attribute long globalIndexOffset; + + /** + * A readonly property of the interface that returns + * the index of the current document in session history. + */ + readonly attribute long index; + + /** + * A readonly property of the interface that returns + * the index of the last document that started to load and + * didn't finished yet. When document finishes the loading + * value -1 is returned. + */ + readonly attribute long requestedIndex; + + /** + * A read/write property of the interface, used to Get/Set + * the maximum number of toplevel documents, session history + * can hold for each instance. + */ + attribute long maxLength; + + /** + * Called to obtain handle to the history entry at a + * given index. + * + * @param index The index value whose entry is requested. + * The oldest entry is located at index == 0. + * @param modifyIndex A boolean flag that indicates if the current + * index of session history should be modified + * to the parameter index. + * + * @return <code>NS_OK</code> history entry for + * the index is obtained successfully. + * <code>NS_ERROR_FAILURE</code> Error in obtaining + * history entry for the given index. + */ + nsISHEntry getEntryAtIndex(in long index, in boolean modifyIndex); + + + /** + * Called to purge older documents from history. + * Documents can be removed from session history for various + * reasons. For example to control memory usage of the browser, to + * prevent users from loading documents from history, to erase evidence of + * prior page loads etc... + * + * @param numEntries The number of toplevel documents to be + * purged from history. During purge operation, + * the latest documents are maintained and older + * 'numEntries' documents are removed from history. + * @throws <code>NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA</code> Purge was vetod. + * @throws <code>NS_ERROR_FAILURE</code> numEntries is + * invalid or out of bounds with the size of history. + * + */ + void PurgeHistory(in long numEntries); + + /** + * Called to register a listener for the session history component. + * Listeners are notified when pages are loaded or purged from history. + * + * @param aListener Listener object to be notified for all + * page loads that initiate in session history. + * + * @note A listener object must implement + * nsISHistoryListener and nsSupportsWeakReference + * + * @see nsISHistoryListener + * @see nsSupportsWeakReference + */ + void addSHistoryListener(in nsISHistoryListener aListener); + + /** + * Called to remove a listener for the session history component. + * Listeners are notified when pages are loaded from history. + * + * @param aListener Listener object to be removed from + * session history. + * + * @note A listener object must implement + * nsISHistoryListener and nsSupportsWeakReference + * @see nsISHistoryListener + * @see nsSupportsWeakReference + */ + void removeSHistoryListener(in nsISHistoryListener aListener); + + /** + * Set the listener to handle cross nsISHistory navigation when it works + * in "partial" mode. + */ + void setPartialSHistoryListener(in nsIPartialSHistoryListener aListener); + + /** + * Called to obtain a enumerator for all the documents stored in + * session history. The enumerator object thus returned by this method + * can be traversed using nsISimpleEnumerator. + * + * @note To access individual history entries of the enumerator, perform the + * following steps: + * 1) Call nsISHistory->GetSHistoryEnumerator() to obtain handle + * the nsISimpleEnumerator object. + * 2) Use nsISimpleEnumerator->GetNext() on the object returned + * by step #1 to obtain handle to the next object in the list. + * The object returned by this step is of type nsISupports. + * 3) Perform a QueryInterface on the object returned by step #2 + * to nsISHEntry. + * 4) Use nsISHEntry to access properties of each history entry. + * + * @see nsISimpleEnumerator + * @see nsISHEntry + * @see QueryInterface() + * @see do_QueryInterface() + */ + readonly attribute nsISimpleEnumerator SHistoryEnumerator; + + void reloadCurrentEntry(); + + /** + * Called to obtain the index to a given history entry. + * + * @param aEntry The entry to obtain the index of. + * + * @return <code>NS_OK</code> index for the history entry + * is obtained successfully. + * <code>NS_ERROR_FAILURE</code> Error in obtaining + * index for the given history entry. + */ + long getIndexOfEntry(in nsISHEntry aEntry); + + /** + * Called when this nsISHistory has became the active history of a grouped + * session history. + * + * @param globalLength The up to date number of entries in the grouped + * session history. + * @param targetIndex The local index to navigate to. + */ + void onPartialSessionHistoryActive(in long globalLength, in long targetIndex); + + /** + * Called when this nsISHistory has became inactive history of a grouped + * session history. + */ + void onPartialSessionHistoryDeactive(); + + /** + * Called when it's attached to a nsIGroupedSHistory instance. + * + * @param offset The number of entries in the grouped session + * history before this session history object. + */ + void onAttachGroupedSessionHistory(in long offset); +}; diff --git a/docshell/shistory/nsISHistoryInternal.idl b/docshell/shistory/nsISHistoryInternal.idl new file mode 100644 index 000000000..b7b900547 --- /dev/null +++ b/docshell/shistory/nsISHistoryInternal.idl @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIBFCacheEntry; +interface nsISHEntry; +interface nsISHistoryListener; +interface nsISHTransaction; +interface nsIDocShell; +interface nsIURI; + +%{C++ +#define NS_SHISTORY_INTERNAL_CID \ +{ 0x3dfb2f54, 0x378d, 0x4d3c, \ + { 0xa9, 0xf9, 0x95, 0xdd, 0x26, 0x73, 0x24, 0x8c } } + +#define NS_SHISTORY_INTERNAL_CONTRACTID "@mozilla.org/browser/shistory-internal;1" + +#include "nsTArrayForwardDeclare.h" +%} + +[ref] native nsDocshellIDArray(nsTArray<uint64_t>); + +[scriptable, uuid(3dfb2f54-378d-4d3c-a9f9-95dd2673248c)] +interface nsISHistoryInternal: nsISupports +{ + /** + * Add a new Entry to the History List + * @param aEntry - The entry to add + * @param aPersist - If true this specifies that the entry should persist + * in the list. If false, this means that when new entries are added + * this element will not appear in the session history list. + */ + void addEntry(in nsISHEntry aEntry, in boolean aPersist); + + /** + * Get the root transaction + */ + readonly attribute nsISHTransaction rootTransaction; + + /** + * Sets the toplevel docshell object to which this SHistory object belongs to. + */ + void setRootDocShell(in nsIDocShell rootDocShell); + + /** + * Update the index maintained by sessionHistory + */ + void updateIndex(); + + /** + * Replace the nsISHEntry at a particular index + * @param aIndex - The index at which the entry should be replaced + * @param aReplaceEntry - The replacement entry for the index. + */ + void replaceEntry(in long aIndex, in nsISHEntry aReplaceEntry); + + /** + * Notifies all registered session history listeners about an impending + * reload. + * + * @param aReloadURI The URI of the document to be reloaded. + * @param aReloadFlags Flags that indicate how the document is to be + * refreshed. See constants on the nsIWebNavigation + * interface. + * @return Whether the operation can proceed. + */ + boolean notifyOnHistoryReload(in nsIURI aReloadURI, in unsigned long aReloadFlags); + + /** + * Evict content viewers which don't lie in the "safe" range around aIndex. + * In practice, this should leave us with no more than gHistoryMaxViewers + * viewers associated with this SHistory object. + * + * Also make sure that the total number of content viewers in all windows is + * not greater than our global max; if it is, evict viewers as appropriate. + * + * @param aIndex - The index around which the "safe" range is centered. In + * general, if you just navigated the history, aIndex should be the index + * history was navigated to. + */ + void evictOutOfRangeContentViewers(in long aIndex); + + /** + * Evict the content viewer associated with a bfcache entry + * that has timed out. + */ + void evictExpiredContentViewerForEntry(in nsIBFCacheEntry aEntry); + + /** + * Evict all the content viewers in this session history + */ + void evictAllContentViewers(); + + /** + * Removes entries from the history if their docshellID is in + * aIDs array. + */ + [noscript, notxpcom] void RemoveEntries(in nsDocshellIDArray aIDs, + in long aStartIndex); +}; diff --git a/docshell/shistory/nsISHistoryListener.idl b/docshell/shistory/nsISHistoryListener.idl new file mode 100644 index 000000000..38552d60e --- /dev/null +++ b/docshell/shistory/nsISHistoryListener.idl @@ -0,0 +1,108 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIURI; + +/** + * nsISHistoryListener defines the interface one can implement to receive + * notifications about activities in session history and to be able to + * cancel them. + * + * A session history listener will be notified when pages are added, removed + * and loaded from session history. It can prevent any action (except adding + * a new session history entry) from happening by returning false from the + * corresponding callback method. + * + * A session history listener can be registered on a particular nsISHistory + * instance via the nsISHistory::addSHistoryListener() method. + */ +[scriptable, uuid(125c0833-746a-400e-9b89-d2d18545c08a)] +interface nsISHistoryListener : nsISupports +{ + /** + * Called when a new document is added to session history. New documents are + * added to session history by docshell when new pages are loaded in a frame + * or content area, for example via nsIWebNavigation::loadURI() + * + * @param aNewURI The URI of the document to be added to session history. + * @param aOldIndex The index of the current history item before the operation. + */ + void OnHistoryNewEntry(in nsIURI aNewURI, in long aOldIndex); + + /** + * Called when navigating to a previous session history entry, for example + * due to a nsIWebNavigation::goBack() call. + * + * @param aBackURI The URI of the session history entry being navigated to. + * @return Whether the operation can proceed. + */ + boolean OnHistoryGoBack(in nsIURI aBackURI); + + /** + * Called when navigating to a next session history entry, for example + * due to a nsIWebNavigation::goForward() call. + * + * @param aForwardURI The URI of the session history entry being navigated to. + * @return Whether the operation can proceed. + */ + boolean OnHistoryGoForward(in nsIURI aForwardURI); + + /** + * Called when the current document is reloaded, for example due to a + * nsIWebNavigation::reload() call. + * + * @param aReloadURI The URI of the document to be reloaded. + * @param aReloadFlags Flags that indicate how the document is to be + * refreshed. See constants on the nsIWebNavigation + * interface. + * @return Whether the operation can proceed. + * + * @see nsIWebNavigation + */ + boolean OnHistoryReload(in nsIURI aReloadURI, in unsigned long aReloadFlags); + + /** + * Called when navigating to a session history entry by index, for example, + * when nsIWebNavigation::gotoIndex() is called. + * + * @param aIndex The index in session history of the entry to be loaded. + * @param aGotoURI The URI of the session history entry to be loaded. + * @return Whether the operation can proceed. + */ + boolean OnHistoryGotoIndex(in long aIndex, in nsIURI aGotoURI); + + /** + * Called when entries are removed from session history. Entries can be + * removed from session history for various reasons, for example to control + * the memory usage of the browser, to prevent users from loading documents + * from history, to erase evidence of prior page loads, etc. + * + * To purge documents from session history call nsISHistory::PurgeHistory() + * + * @param aNumEntries The number of entries to be removed from session history. + * @return Whether the operation can proceed. + */ + boolean OnHistoryPurge(in long aNumEntries); + + /** + * Called when an entry is replaced in the session history. Entries are + * replaced when navigating away from non-persistent history entries (such as + * about pages) and when history.replaceState is called. + * + * @param aIndex The index in session history of the entry being + * replaced + */ + void OnHistoryReplaceEntry(in long aIndex); + + /** + * Called when nsISHistory::count has been updated. Unlike OnHistoryNewEntry + * and OnHistoryPurge which happen before the modifications are actually done + * and maybe cancellable, this function is called after these modifications. + */ + void OnLengthChange(in long aCount); +}; diff --git a/docshell/shistory/nsSHEntry.cpp b/docshell/shistory/nsSHEntry.cpp new file mode 100644 index 000000000..9d972136f --- /dev/null +++ b/docshell/shistory/nsSHEntry.cpp @@ -0,0 +1,944 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSHEntry.h" +#include "nsIDocShellLoadInfo.h" +#include "nsIDocShellTreeItem.h" +#include "nsDocShellEditorData.h" +#include "nsSHEntryShared.h" +#include "nsILayoutHistoryState.h" +#include "nsIContentViewer.h" +#include "nsIStructuredCloneContainer.h" +#include "nsIInputStream.h" +#include "nsIURI.h" +#include "mozilla/net/ReferrerPolicy.h" +#include <algorithm> + +namespace dom = mozilla::dom; + +static uint32_t gEntryID = 0; + +nsSHEntry::nsSHEntry() + : mShared(new nsSHEntryShared()) + , mLoadReplace(false) + , mReferrerPolicy(mozilla::net::RP_Default) + , mLoadType(0) + , mID(gEntryID++) + , mScrollPositionX(0) + , mScrollPositionY(0) + , mParent(nullptr) + , mURIWasModified(false) + , mIsSrcdocEntry(false) + , mScrollRestorationIsManual(false) +{ +} + +nsSHEntry::nsSHEntry(const nsSHEntry& aOther) + : mShared(aOther.mShared) + , mURI(aOther.mURI) + , mOriginalURI(aOther.mOriginalURI) + , mLoadReplace(aOther.mLoadReplace) + , mReferrerURI(aOther.mReferrerURI) + , mReferrerPolicy(aOther.mReferrerPolicy) + , mTitle(aOther.mTitle) + , mPostData(aOther.mPostData) + , mLoadType(0) // XXX why not copy? + , mID(aOther.mID) + , mScrollPositionX(0) // XXX why not copy? + , mScrollPositionY(0) // XXX why not copy? + , mParent(aOther.mParent) + , mURIWasModified(aOther.mURIWasModified) + , mStateData(aOther.mStateData) + , mIsSrcdocEntry(aOther.mIsSrcdocEntry) + , mScrollRestorationIsManual(false) + , mSrcdocData(aOther.mSrcdocData) + , mBaseURI(aOther.mBaseURI) +{ +} + +static bool +ClearParentPtr(nsISHEntry* aEntry, void* /* aData */) +{ + if (aEntry) { + aEntry->SetParent(nullptr); + } + return true; +} + +nsSHEntry::~nsSHEntry() +{ + // Null out the mParent pointers on all our kids. + mChildren.EnumerateForwards(ClearParentPtr, nullptr); +} + +NS_IMPL_ISUPPORTS(nsSHEntry, nsISHContainer, nsISHEntry, nsISHEntryInternal) + +NS_IMETHODIMP +nsSHEntry::SetScrollPosition(int32_t aX, int32_t aY) +{ + mScrollPositionX = aX; + mScrollPositionY = aY; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetScrollPosition(int32_t* aX, int32_t* aY) +{ + *aX = mScrollPositionX; + *aY = mScrollPositionY; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetURIWasModified(bool* aOut) +{ + *aOut = mURIWasModified; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetURIWasModified(bool aIn) +{ + mURIWasModified = aIn; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetURI(nsIURI** aURI) +{ + *aURI = mURI; + NS_IF_ADDREF(*aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetURI(nsIURI* aURI) +{ + mURI = aURI; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetOriginalURI(nsIURI** aOriginalURI) +{ + *aOriginalURI = mOriginalURI; + NS_IF_ADDREF(*aOriginalURI); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetOriginalURI(nsIURI* aOriginalURI) +{ + mOriginalURI = aOriginalURI; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetLoadReplace(bool* aLoadReplace) +{ + *aLoadReplace = mLoadReplace; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetLoadReplace(bool aLoadReplace) +{ + mLoadReplace = aLoadReplace; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetReferrerURI(nsIURI** aReferrerURI) +{ + *aReferrerURI = mReferrerURI; + NS_IF_ADDREF(*aReferrerURI); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetReferrerURI(nsIURI* aReferrerURI) +{ + mReferrerURI = aReferrerURI; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetReferrerPolicy(uint32_t* aReferrerPolicy) +{ + *aReferrerPolicy = mReferrerPolicy; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetReferrerPolicy(uint32_t aReferrerPolicy) +{ + mReferrerPolicy = aReferrerPolicy; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetContentViewer(nsIContentViewer* aViewer) +{ + return mShared->SetContentViewer(aViewer); +} + +NS_IMETHODIMP +nsSHEntry::GetContentViewer(nsIContentViewer** aResult) +{ + *aResult = mShared->mContentViewer; + NS_IF_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetAnyContentViewer(nsISHEntry** aOwnerEntry, + nsIContentViewer** aResult) +{ + // Find a content viewer in the root node or any of its children, + // assuming that there is only one content viewer total in any one + // nsSHEntry tree + GetContentViewer(aResult); + if (*aResult) { +#ifdef DEBUG_PAGE_CACHE + printf("Found content viewer\n"); +#endif + *aOwnerEntry = this; + NS_ADDREF(*aOwnerEntry); + return NS_OK; + } + // The root SHEntry doesn't have a ContentViewer, so check child nodes + for (int32_t i = 0; i < mChildren.Count(); i++) { + nsISHEntry* child = mChildren[i]; + if (child) { +#ifdef DEBUG_PAGE_CACHE + printf("Evaluating SHEntry child %d\n", i); +#endif + child->GetAnyContentViewer(aOwnerEntry, aResult); + if (*aResult) { + return NS_OK; + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetSticky(bool aSticky) +{ + mShared->mSticky = aSticky; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetSticky(bool* aSticky) +{ + *aSticky = mShared->mSticky; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetTitle(char16_t** aTitle) +{ + // Check for empty title... + if (mTitle.IsEmpty() && mURI) { + // Default title is the URL. + nsAutoCString spec; + if (NS_SUCCEEDED(mURI->GetSpec(spec))) { + AppendUTF8toUTF16(spec, mTitle); + } + } + + *aTitle = ToNewUnicode(mTitle); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetTitle(const nsAString& aTitle) +{ + mTitle = aTitle; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetPostData(nsIInputStream** aResult) +{ + *aResult = mPostData; + NS_IF_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetPostData(nsIInputStream* aPostData) +{ + mPostData = aPostData; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetLayoutHistoryState(nsILayoutHistoryState** aResult) +{ + *aResult = mShared->mLayoutHistoryState; + NS_IF_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetLayoutHistoryState(nsILayoutHistoryState* aState) +{ + mShared->mLayoutHistoryState = aState; + if (mShared->mLayoutHistoryState) { + mShared->mLayoutHistoryState->SetScrollPositionOnly( + !mShared->mSaveLayoutState); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetLoadType(uint32_t* aResult) +{ + *aResult = mLoadType; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetLoadType(uint32_t aLoadType) +{ + mLoadType = aLoadType; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetID(uint32_t* aResult) +{ + *aResult = mID; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetID(uint32_t aID) +{ + mID = aID; + return NS_OK; +} + +nsSHEntryShared* +nsSHEntry::GetSharedState() +{ + return mShared; +} + +NS_IMETHODIMP +nsSHEntry::GetIsSubFrame(bool* aFlag) +{ + *aFlag = mShared->mIsFrameNavigation; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetIsSubFrame(bool aFlag) +{ + mShared->mIsFrameNavigation = aFlag; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetCacheKey(nsISupports** aResult) +{ + *aResult = mShared->mCacheKey; + NS_IF_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetCacheKey(nsISupports* aCacheKey) +{ + mShared->mCacheKey = aCacheKey; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetSaveLayoutStateFlag(bool* aFlag) +{ + *aFlag = mShared->mSaveLayoutState; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetSaveLayoutStateFlag(bool aFlag) +{ + mShared->mSaveLayoutState = aFlag; + if (mShared->mLayoutHistoryState) { + mShared->mLayoutHistoryState->SetScrollPositionOnly(!aFlag); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetExpirationStatus(bool* aFlag) +{ + *aFlag = mShared->mExpired; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetExpirationStatus(bool aFlag) +{ + mShared->mExpired = aFlag; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetContentType(nsACString& aContentType) +{ + aContentType = mShared->mContentType; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetContentType(const nsACString& aContentType) +{ + mShared->mContentType = aContentType; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::Create(nsIURI* aURI, const nsAString& aTitle, + nsIInputStream* aInputStream, + nsILayoutHistoryState* aLayoutHistoryState, + nsISupports* aCacheKey, const nsACString& aContentType, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + uint64_t aDocShellID, + bool aDynamicCreation) +{ + mURI = aURI; + mTitle = aTitle; + mPostData = aInputStream; + + // Set the LoadType by default to loadHistory during creation + mLoadType = (uint32_t)nsIDocShellLoadInfo::loadHistory; + + mShared->mCacheKey = aCacheKey; + mShared->mContentType = aContentType; + mShared->mTriggeringPrincipal = aTriggeringPrincipal; + mShared->mPrincipalToInherit = aPrincipalToInherit; + mShared->mDocShellID = aDocShellID; + mShared->mDynamicallyCreated = aDynamicCreation; + + // By default all entries are set false for subframe flag. + // nsDocShell::CloneAndReplace() which creates entries for + // all subframe navigations, sets the flag to true. + mShared->mIsFrameNavigation = false; + + // By default we save LayoutHistoryState + mShared->mSaveLayoutState = true; + mShared->mLayoutHistoryState = aLayoutHistoryState; + + // By default the page is not expired + mShared->mExpired = false; + + mIsSrcdocEntry = false; + mSrcdocData = NullString(); + + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::Clone(nsISHEntry** aResult) +{ + *aResult = new nsSHEntry(*this); + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetParent(nsISHEntry** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mParent; + NS_IF_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetParent(nsISHEntry* aParent) +{ + /* parent not Addrefed on purpose to avoid cyclic reference + * Null parent is OK + * + * XXX this method should not be scriptable if this is the case!! + */ + mParent = aParent; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetWindowState(nsISupports* aState) +{ + mShared->mWindowState = aState; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetWindowState(nsISupports** aState) +{ + NS_IF_ADDREF(*aState = mShared->mWindowState); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetViewerBounds(const nsIntRect& aBounds) +{ + mShared->mViewerBounds = aBounds; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetViewerBounds(nsIntRect& aBounds) +{ + aBounds = mShared->mViewerBounds; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal) +{ + NS_IF_ADDREF(*aTriggeringPrincipal = mShared->mTriggeringPrincipal); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetTriggeringPrincipal(nsIPrincipal* aTriggeringPrincipal) +{ + mShared->mTriggeringPrincipal = aTriggeringPrincipal; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) +{ + NS_IF_ADDREF(*aPrincipalToInherit = mShared->mPrincipalToInherit); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) +{ + mShared->mPrincipalToInherit = aPrincipalToInherit; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetBFCacheEntry(nsIBFCacheEntry** aEntry) +{ + NS_ENSURE_ARG_POINTER(aEntry); + NS_IF_ADDREF(*aEntry = mShared); + return NS_OK; +} + +bool +nsSHEntry::HasBFCacheEntry(nsIBFCacheEntry* aEntry) +{ + return static_cast<nsIBFCacheEntry*>(mShared) == aEntry; +} + +NS_IMETHODIMP +nsSHEntry::AdoptBFCacheEntry(nsISHEntry* aEntry) +{ + nsCOMPtr<nsISHEntryInternal> shEntry = do_QueryInterface(aEntry); + NS_ENSURE_STATE(shEntry); + + nsSHEntryShared* shared = shEntry->GetSharedState(); + NS_ENSURE_STATE(shared); + + mShared = shared; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SharesDocumentWith(nsISHEntry* aEntry, bool* aOut) +{ + NS_ENSURE_ARG_POINTER(aOut); + + nsCOMPtr<nsISHEntryInternal> internal = do_QueryInterface(aEntry); + NS_ENSURE_STATE(internal); + + *aOut = mShared == internal->GetSharedState(); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::AbandonBFCacheEntry() +{ + mShared = nsSHEntryShared::Duplicate(mShared); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetIsSrcdocEntry(bool* aIsSrcdocEntry) +{ + *aIsSrcdocEntry = mIsSrcdocEntry; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetSrcdocData(nsAString& aSrcdocData) +{ + aSrcdocData = mSrcdocData; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetSrcdocData(const nsAString& aSrcdocData) +{ + mSrcdocData = aSrcdocData; + mIsSrcdocEntry = true; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetBaseURI(nsIURI** aBaseURI) +{ + *aBaseURI = mBaseURI; + NS_IF_ADDREF(*aBaseURI); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetBaseURI(nsIURI* aBaseURI) +{ + mBaseURI = aBaseURI; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetScrollRestorationIsManual(bool* aIsManual) +{ + *aIsManual = mScrollRestorationIsManual; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetScrollRestorationIsManual(bool aIsManual) +{ + mScrollRestorationIsManual = aIsManual; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetChildCount(int32_t* aCount) +{ + *aCount = mChildren.Count(); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::AddChild(nsISHEntry* aChild, int32_t aOffset) +{ + if (aChild) { + NS_ENSURE_SUCCESS(aChild->SetParent(this), NS_ERROR_FAILURE); + } + + if (aOffset < 0) { + mChildren.AppendObject(aChild); + return NS_OK; + } + + // + // Bug 52670: Ensure children are added in order. + // + // Later frames in the child list may load faster and get appended + // before earlier frames, causing session history to be scrambled. + // By growing the list here, they are added to the right position. + // + // Assert that aOffset will not be so high as to grow us a lot. + // + NS_ASSERTION(aOffset < (mChildren.Count() + 1023), "Large frames array!\n"); + + bool newChildIsDyn = false; + if (aChild) { + aChild->IsDynamicallyAdded(&newChildIsDyn); + } + + // If the new child is dynamically added, try to add it to aOffset, but if + // there are non-dynamically added children, the child must be after those. + if (newChildIsDyn) { + int32_t lastNonDyn = aOffset - 1; + for (int32_t i = aOffset; i < mChildren.Count(); ++i) { + nsISHEntry* entry = mChildren[i]; + if (entry) { + bool dyn = false; + entry->IsDynamicallyAdded(&dyn); + if (dyn) { + break; + } else { + lastNonDyn = i; + } + } + } + // InsertObjectAt allows only appending one object. + // If aOffset is larger than Count(), we must first manually + // set the capacity. + if (aOffset > mChildren.Count()) { + mChildren.SetCount(aOffset); + } + if (!mChildren.InsertObjectAt(aChild, lastNonDyn + 1)) { + NS_WARNING("Adding a child failed!"); + aChild->SetParent(nullptr); + return NS_ERROR_FAILURE; + } + } else { + // If the new child isn't dynamically added, it should be set to aOffset. + // If there are dynamically added children before that, those must be + // moved to be after aOffset. + if (mChildren.Count() > 0) { + int32_t start = std::min(mChildren.Count() - 1, aOffset); + int32_t dynEntryIndex = -1; + nsISHEntry* dynEntry = nullptr; + for (int32_t i = start; i >= 0; --i) { + nsISHEntry* entry = mChildren[i]; + if (entry) { + bool dyn = false; + entry->IsDynamicallyAdded(&dyn); + if (dyn) { + dynEntryIndex = i; + dynEntry = entry; + } else { + break; + } + } + } + + if (dynEntry) { + nsCOMArray<nsISHEntry> tmp; + tmp.SetCount(aOffset - dynEntryIndex + 1); + mChildren.InsertObjectsAt(tmp, dynEntryIndex); + NS_ASSERTION(mChildren[aOffset + 1] == dynEntry, "Whaat?"); + } + } + + // Make sure there isn't anything at aOffset. + if (aOffset < mChildren.Count()) { + nsISHEntry* oldChild = mChildren[aOffset]; + if (oldChild && oldChild != aChild) { + NS_ERROR("Adding a child where we already have a child? This may misbehave"); + oldChild->SetParent(nullptr); + } + } + + mChildren.ReplaceObjectAt(aChild, aOffset); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::RemoveChild(nsISHEntry* aChild) +{ + NS_ENSURE_TRUE(aChild, NS_ERROR_FAILURE); + bool childRemoved = false; + bool dynamic = false; + aChild->IsDynamicallyAdded(&dynamic); + if (dynamic) { + childRemoved = mChildren.RemoveObject(aChild); + } else { + int32_t index = mChildren.IndexOfObject(aChild); + if (index >= 0) { + mChildren.ReplaceObjectAt(nullptr, index); + childRemoved = true; + } + } + if (childRemoved) { + aChild->SetParent(nullptr); + + // reduce the child count, i.e. remove empty children at the end + for (int32_t i = mChildren.Count() - 1; i >= 0 && !mChildren[i]; --i) { + if (!mChildren.RemoveObjectAt(i)) { + break; + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetChildAt(int32_t aIndex, nsISHEntry** aResult) +{ + if (aIndex >= 0 && aIndex < mChildren.Count()) { + *aResult = mChildren[aIndex]; + // yes, mChildren can have holes in it. AddChild's offset parameter makes + // that possible. + NS_IF_ADDREF(*aResult); + } else { + *aResult = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::ReplaceChild(nsISHEntry* aNewEntry) +{ + NS_ENSURE_STATE(aNewEntry); + + uint64_t docshellID; + aNewEntry->GetDocshellID(&docshellID); + + uint64_t otherID; + for (int32_t i = 0; i < mChildren.Count(); ++i) { + if (mChildren[i] && NS_SUCCEEDED(mChildren[i]->GetDocshellID(&otherID)) && + docshellID == otherID) { + mChildren[i]->SetParent(nullptr); + mChildren.ReplaceObjectAt(aNewEntry, i); + return aNewEntry->SetParent(this); + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSHEntry::AddChildShell(nsIDocShellTreeItem* aShell) +{ + NS_ASSERTION(aShell, "Null child shell added to history entry"); + mShared->mChildShells.AppendObject(aShell); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::ChildShellAt(int32_t aIndex, nsIDocShellTreeItem** aShell) +{ + NS_IF_ADDREF(*aShell = mShared->mChildShells.SafeObjectAt(aIndex)); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::ClearChildShells() +{ + mShared->mChildShells.Clear(); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetRefreshURIList(nsIMutableArray** aList) +{ + NS_IF_ADDREF(*aList = mShared->mRefreshURIList); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetRefreshURIList(nsIMutableArray* aList) +{ + mShared->mRefreshURIList = aList; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SyncPresentationState() +{ + return mShared->SyncPresentationState(); +} + +void +nsSHEntry::RemoveFromBFCacheSync() +{ + mShared->RemoveFromBFCacheSync(); +} + +void +nsSHEntry::RemoveFromBFCacheAsync() +{ + mShared->RemoveFromBFCacheAsync(); +} + +nsDocShellEditorData* +nsSHEntry::ForgetEditorData() +{ + // XXX jlebar Check how this is used. + return mShared->mEditorData.forget(); +} + +void +nsSHEntry::SetEditorData(nsDocShellEditorData* aData) +{ + NS_ASSERTION(!(aData && mShared->mEditorData), + "We're going to overwrite an owning ref!"); + if (mShared->mEditorData != aData) { + mShared->mEditorData = aData; + } +} + +bool +nsSHEntry::HasDetachedEditor() +{ + return mShared->mEditorData != nullptr; +} + +NS_IMETHODIMP +nsSHEntry::GetStateData(nsIStructuredCloneContainer** aContainer) +{ + NS_ENSURE_ARG_POINTER(aContainer); + NS_IF_ADDREF(*aContainer = mStateData); + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetStateData(nsIStructuredCloneContainer* aContainer) +{ + mStateData = aContainer; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::IsDynamicallyAdded(bool* aAdded) +{ + *aAdded = mShared->mDynamicallyCreated; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::HasDynamicallyAddedChild(bool* aAdded) +{ + *aAdded = false; + for (int32_t i = 0; i < mChildren.Count(); ++i) { + nsISHEntry* entry = mChildren[i]; + if (entry) { + entry->IsDynamicallyAdded(aAdded); + if (*aAdded) { + break; + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetDocshellID(uint64_t* aID) +{ + *aID = mShared->mDocShellID; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetDocshellID(uint64_t aID) +{ + mShared->mDocShellID = aID; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::GetLastTouched(uint32_t* aLastTouched) +{ + *aLastTouched = mShared->mLastTouched; + return NS_OK; +} + +NS_IMETHODIMP +nsSHEntry::SetLastTouched(uint32_t aLastTouched) +{ + mShared->mLastTouched = aLastTouched; + return NS_OK; +} diff --git a/docshell/shistory/nsSHEntry.h b/docshell/shistory/nsSHEntry.h new file mode 100644 index 000000000..2383e38c4 --- /dev/null +++ b/docshell/shistory/nsSHEntry.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSHEntry_h +#define nsSHEntry_h + +// Helper Classes +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsString.h" +#include "mozilla/Attributes.h" + +// Interfaces needed +#include "nsISHEntry.h" +#include "nsISHContainer.h" + +class nsSHEntryShared; +class nsIInputStream; +class nsIURI; + +class nsSHEntry final : public nsISHEntry, + public nsISHContainer, + public nsISHEntryInternal +{ +public: + nsSHEntry(); + nsSHEntry(const nsSHEntry& aOther); + + NS_DECL_ISUPPORTS + NS_DECL_NSISHENTRY + NS_DECL_NSISHENTRYINTERNAL + NS_DECL_NSISHCONTAINER + + void DropPresentationState(); + + static nsresult Startup(); + static void Shutdown(); + +private: + ~nsSHEntry(); + + // We share the state in here with other SHEntries which correspond to the + // same document. + RefPtr<nsSHEntryShared> mShared; + + // See nsSHEntry.idl for comments on these members. + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsIURI> mOriginalURI; + bool mLoadReplace; + nsCOMPtr<nsIURI> mReferrerURI; + uint32_t mReferrerPolicy; + nsString mTitle; + nsCOMPtr<nsIInputStream> mPostData; + uint32_t mLoadType; + uint32_t mID; + int32_t mScrollPositionX; + int32_t mScrollPositionY; + nsISHEntry* mParent; + nsCOMArray<nsISHEntry> mChildren; + bool mURIWasModified; + nsCOMPtr<nsIStructuredCloneContainer> mStateData; + bool mIsSrcdocEntry; + bool mScrollRestorationIsManual; + nsString mSrcdocData; + nsCOMPtr<nsIURI> mBaseURI; +}; + +#endif /* nsSHEntry_h */ diff --git a/docshell/shistory/nsSHEntryShared.cpp b/docshell/shistory/nsSHEntryShared.cpp new file mode 100644 index 000000000..645294408 --- /dev/null +++ b/docshell/shistory/nsSHEntryShared.cpp @@ -0,0 +1,385 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSHEntryShared.h" + +#include "nsIDOMDocument.h" +#include "nsISHistory.h" +#include "nsISHistoryInternal.h" +#include "nsIDocument.h" +#include "nsIWebNavigation.h" +#include "nsIContentViewer.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeItem.h" +#include "nsDocShellEditorData.h" +#include "nsThreadUtils.h" +#include "nsILayoutHistoryState.h" +#include "mozilla/Attributes.h" +#include "mozilla/Preferences.h" +#include "nsArray.h" + +namespace dom = mozilla::dom; + +namespace { + +uint64_t gSHEntrySharedID = 0; + +} // namespace + +#define CONTENT_VIEWER_TIMEOUT_SECONDS "browser.sessionhistory.contentViewerTimeout" +// Default this to time out unused content viewers after 30 minutes +#define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60) + +typedef nsExpirationTracker<nsSHEntryShared, 3> HistoryTrackerBase; +class HistoryTracker final : public HistoryTrackerBase +{ +public: + explicit HistoryTracker(uint32_t aTimeout) + : HistoryTrackerBase(1000 * aTimeout / 2, "HistoryTracker") + { + } + +protected: + virtual void NotifyExpired(nsSHEntryShared* aObj) + { + RemoveObject(aObj); + aObj->Expire(); + } +}; + +static HistoryTracker* gHistoryTracker = nullptr; + +void +nsSHEntryShared::EnsureHistoryTracker() +{ + if (!gHistoryTracker) { + // nsExpirationTracker doesn't allow one to change the timer period, + // so just set it once when the history tracker is used for the first time. + gHistoryTracker = new HistoryTracker( + mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS, + CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT)); + } +} + +void +nsSHEntryShared::Shutdown() +{ + delete gHistoryTracker; + gHistoryTracker = nullptr; +} + +nsSHEntryShared::nsSHEntryShared() + : mDocShellID(0) + , mIsFrameNavigation(false) + , mSaveLayoutState(true) + , mSticky(true) + , mDynamicallyCreated(false) + , mLastTouched(0) + , mID(gSHEntrySharedID++) + , mExpired(false) + , mViewerBounds(0, 0, 0, 0) +{ +} + +nsSHEntryShared::~nsSHEntryShared() +{ + RemoveFromExpirationTracker(); + +#ifdef DEBUG + if (gHistoryTracker) { + // Check that we're not still on track to expire. We shouldn't be, because + // we just removed ourselves! + nsExpirationTracker<nsSHEntryShared, 3>::Iterator iterator(gHistoryTracker); + + nsSHEntryShared* elem; + while ((elem = iterator.Next()) != nullptr) { + NS_ASSERTION(elem != this, "Found dead entry still in the tracker!"); + } + } +#endif + + if (mContentViewer) { + RemoveFromBFCacheSync(); + } +} + +NS_IMPL_ISUPPORTS(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver) + +already_AddRefed<nsSHEntryShared> +nsSHEntryShared::Duplicate(nsSHEntryShared* aEntry) +{ + RefPtr<nsSHEntryShared> newEntry = new nsSHEntryShared(); + + newEntry->mDocShellID = aEntry->mDocShellID; + newEntry->mChildShells.AppendObjects(aEntry->mChildShells); + newEntry->mTriggeringPrincipal = aEntry->mTriggeringPrincipal; + newEntry->mPrincipalToInherit = aEntry->mPrincipalToInherit; + newEntry->mContentType.Assign(aEntry->mContentType); + newEntry->mIsFrameNavigation = aEntry->mIsFrameNavigation; + newEntry->mSaveLayoutState = aEntry->mSaveLayoutState; + newEntry->mSticky = aEntry->mSticky; + newEntry->mDynamicallyCreated = aEntry->mDynamicallyCreated; + newEntry->mCacheKey = aEntry->mCacheKey; + newEntry->mLastTouched = aEntry->mLastTouched; + + return newEntry.forget(); +} + +void +nsSHEntryShared::RemoveFromExpirationTracker() +{ + if (gHistoryTracker && GetExpirationState()->IsTracked()) { + gHistoryTracker->RemoveObject(this); + } +} + +nsresult +nsSHEntryShared::SyncPresentationState() +{ + if (mContentViewer && mWindowState) { + // If we have a content viewer and a window state, we should be ok. + return NS_OK; + } + + DropPresentationState(); + + return NS_OK; +} + +void +nsSHEntryShared::DropPresentationState() +{ + RefPtr<nsSHEntryShared> kungFuDeathGrip = this; + + if (mDocument) { + mDocument->SetBFCacheEntry(nullptr); + mDocument->RemoveMutationObserver(this); + mDocument = nullptr; + } + if (mContentViewer) { + mContentViewer->ClearHistoryEntry(); + } + + RemoveFromExpirationTracker(); + mContentViewer = nullptr; + mSticky = true; + mWindowState = nullptr; + mViewerBounds.SetRect(0, 0, 0, 0); + mChildShells.Clear(); + mRefreshURIList = nullptr; + mEditorData = nullptr; +} + +void +nsSHEntryShared::Expire() +{ + // This entry has timed out. If we still have a content viewer, we need to + // evict it. + if (!mContentViewer) { + return; + } + nsCOMPtr<nsIDocShell> container; + mContentViewer->GetContainer(getter_AddRefs(container)); + nsCOMPtr<nsIDocShellTreeItem> treeItem = do_QueryInterface(container); + if (!treeItem) { + return; + } + // We need to find the root DocShell since only that object has an + // SHistory and we need the SHistory to evict content viewers + nsCOMPtr<nsIDocShellTreeItem> root; + treeItem->GetSameTypeRootTreeItem(getter_AddRefs(root)); + nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(root); + nsCOMPtr<nsISHistory> history; + webNav->GetSessionHistory(getter_AddRefs(history)); + nsCOMPtr<nsISHistoryInternal> historyInt = do_QueryInterface(history); + if (!historyInt) { + return; + } + historyInt->EvictExpiredContentViewerForEntry(this); +} + +nsresult +nsSHEntryShared::SetContentViewer(nsIContentViewer* aViewer) +{ + NS_PRECONDITION(!aViewer || !mContentViewer, + "SHEntryShared already contains viewer"); + + if (mContentViewer || !aViewer) { + DropPresentationState(); + } + + mContentViewer = aViewer; + + if (mContentViewer) { + EnsureHistoryTracker(); + gHistoryTracker->AddObject(this); + + nsCOMPtr<nsIDOMDocument> domDoc; + mContentViewer->GetDOMDocument(getter_AddRefs(domDoc)); + // Store observed document in strong pointer in case it is removed from + // the contentviewer + mDocument = do_QueryInterface(domDoc); + if (mDocument) { + mDocument->SetBFCacheEntry(this); + mDocument->AddMutationObserver(this); + } + } + + return NS_OK; +} + +nsresult +nsSHEntryShared::RemoveFromBFCacheSync() +{ + NS_ASSERTION(mContentViewer && mDocument, "we're not in the bfcache!"); + + nsCOMPtr<nsIContentViewer> viewer = mContentViewer; + DropPresentationState(); + + // Warning! The call to DropPresentationState could have dropped the last + // reference to this object, so don't access members beyond here. + + if (viewer) { + viewer->Destroy(); + } + + return NS_OK; +} + +class DestroyViewerEvent : public mozilla::Runnable +{ +public: + DestroyViewerEvent(nsIContentViewer* aViewer, nsIDocument* aDocument) + : mViewer(aViewer) + , mDocument(aDocument) + { + } + + NS_IMETHOD Run() override + { + if (mViewer) { + mViewer->Destroy(); + } + return NS_OK; + } + + nsCOMPtr<nsIContentViewer> mViewer; + nsCOMPtr<nsIDocument> mDocument; +}; + +nsresult +nsSHEntryShared::RemoveFromBFCacheAsync() +{ + NS_ASSERTION(mContentViewer && mDocument, "we're not in the bfcache!"); + + // Release the reference to the contentviewer asynchronously so that the + // document doesn't get nuked mid-mutation. + + nsCOMPtr<nsIRunnable> evt = new DestroyViewerEvent(mContentViewer, mDocument); + nsresult rv = NS_DispatchToCurrentThread(evt); + if (NS_FAILED(rv)) { + NS_WARNING("failed to dispatch DestroyViewerEvent"); + } else { + // Drop presentation. Only do this if we succeeded in posting the event + // since otherwise the document could be torn down mid-mutation, causing + // crashes. + DropPresentationState(); + } + + // Careful! The call to DropPresentationState could have dropped the last + // reference to this nsSHEntryShared, so don't access members beyond here. + + return NS_OK; +} + +nsresult +nsSHEntryShared::GetID(uint64_t* aID) +{ + *aID = mID; + return NS_OK; +} + +void +nsSHEntryShared::NodeWillBeDestroyed(const nsINode* aNode) +{ + NS_NOTREACHED("Document destroyed while we're holding a strong ref to it"); +} + +void +nsSHEntryShared::CharacterDataWillChange(nsIDocument* aDocument, + nsIContent* aContent, + CharacterDataChangeInfo* aInfo) +{ +} + +void +nsSHEntryShared::CharacterDataChanged(nsIDocument* aDocument, + nsIContent* aContent, + CharacterDataChangeInfo* aInfo) +{ + RemoveFromBFCacheAsync(); +} + +void +nsSHEntryShared::AttributeWillChange(nsIDocument* aDocument, + dom::Element* aContent, + int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aNewValue) +{ +} + +void +nsSHEntryShared::NativeAnonymousChildListChange(nsIDocument* aDocument, + nsIContent* aContent, + bool aIsRemove) +{ +} + +void +nsSHEntryShared::AttributeChanged(nsIDocument* aDocument, + dom::Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue) +{ + RemoveFromBFCacheAsync(); +} + +void +nsSHEntryShared::ContentAppended(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aFirstNewContent, + int32_t /* unused */) +{ + RemoveFromBFCacheAsync(); +} + +void +nsSHEntryShared::ContentInserted(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t /* unused */) +{ + RemoveFromBFCacheAsync(); +} + +void +nsSHEntryShared::ContentRemoved(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer, + nsIContent* aPreviousSibling) +{ + RemoveFromBFCacheAsync(); +} + +void +nsSHEntryShared::ParentChainChanged(nsIContent* aContent) +{ +} diff --git a/docshell/shistory/nsSHEntryShared.h b/docshell/shistory/nsSHEntryShared.h new file mode 100644 index 000000000..0a40691ff --- /dev/null +++ b/docshell/shistory/nsSHEntryShared.h @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSHEntryShared_h__ +#define nsSHEntryShared_h__ + +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsCOMArray.h" +#include "nsIBFCacheEntry.h" +#include "nsIMutationObserver.h" +#include "nsExpirationTracker.h" +#include "nsRect.h" +#include "nsString.h" +#include "mozilla/Attributes.h" + +class nsSHEntry; +class nsISHEntry; +class nsIDocument; +class nsIContentViewer; +class nsIDocShellTreeItem; +class nsILayoutHistoryState; +class nsDocShellEditorData; +class nsIMutableArray; + +// A document may have multiple SHEntries, either due to hash navigations or +// calls to history.pushState. SHEntries corresponding to the same document +// share many members; in particular, they share state related to the +// back/forward cache. +// +// nsSHEntryShared is the vehicle for this sharing. +class nsSHEntryShared final + : public nsIBFCacheEntry + , public nsIMutationObserver +{ +public: + static void EnsureHistoryTracker(); + static void Shutdown(); + + nsSHEntryShared(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMUTATIONOBSERVER + NS_DECL_NSIBFCACHEENTRY + + nsExpirationState *GetExpirationState() { return &mExpirationState; } + +private: + ~nsSHEntryShared(); + + friend class nsSHEntry; + + friend class HistoryTracker; + + static already_AddRefed<nsSHEntryShared> Duplicate(nsSHEntryShared* aEntry); + + void RemoveFromExpirationTracker(); + void Expire(); + nsresult SyncPresentationState(); + void DropPresentationState(); + + nsresult SetContentViewer(nsIContentViewer* aViewer); + + // See nsISHEntry.idl for an explanation of these members. + + // These members are copied by nsSHEntryShared::Duplicate(). If you add a + // member here, be sure to update the Duplicate() implementation. + uint64_t mDocShellID; + nsCOMArray<nsIDocShellTreeItem> mChildShells; + nsCOMPtr<nsIPrincipal> mTriggeringPrincipal; + nsCOMPtr<nsIPrincipal> mPrincipalToInherit; + nsCString mContentType; + bool mIsFrameNavigation; + bool mSaveLayoutState; + bool mSticky; + bool mDynamicallyCreated; + nsCOMPtr<nsISupports> mCacheKey; + uint32_t mLastTouched; + + // These members aren't copied by nsSHEntryShared::Duplicate() because + // they're specific to a particular content viewer. + uint64_t mID; + nsCOMPtr<nsIContentViewer> mContentViewer; + nsCOMPtr<nsIDocument> mDocument; + nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState; + bool mExpired; + nsCOMPtr<nsISupports> mWindowState; + nsIntRect mViewerBounds; + nsCOMPtr<nsIMutableArray> mRefreshURIList; + nsExpirationState mExpirationState; + nsAutoPtr<nsDocShellEditorData> mEditorData; +}; + +#endif diff --git a/docshell/shistory/nsSHTransaction.cpp b/docshell/shistory/nsSHTransaction.cpp new file mode 100644 index 000000000..061454d35 --- /dev/null +++ b/docshell/shistory/nsSHTransaction.cpp @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSHTransaction.h" +#include "nsISHEntry.h" + +nsSHTransaction::nsSHTransaction() + : mPersist(true) + , mPrev(nullptr) +{ +} + +nsSHTransaction::~nsSHTransaction() +{ +} + +NS_IMPL_ADDREF(nsSHTransaction) +NS_IMPL_RELEASE(nsSHTransaction) + +NS_INTERFACE_MAP_BEGIN(nsSHTransaction) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISHTransaction) + NS_INTERFACE_MAP_ENTRY(nsISHTransaction) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +nsSHTransaction::Create(nsISHEntry* aSHEntry, nsISHTransaction* aPrev) +{ + SetSHEntry(aSHEntry); + if (aPrev) { + aPrev->SetNext(this); + } + + SetPrev(aPrev); + return NS_OK; +} + +NS_IMETHODIMP +nsSHTransaction::GetSHEntry(nsISHEntry** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mSHEntry; + NS_IF_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsSHTransaction::SetSHEntry(nsISHEntry* aSHEntry) +{ + mSHEntry = aSHEntry; + return NS_OK; +} + +NS_IMETHODIMP +nsSHTransaction::GetNext(nsISHTransaction** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mNext; + NS_IF_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsSHTransaction::SetNext(nsISHTransaction* aNext) +{ + if (aNext) { + NS_ENSURE_SUCCESS(aNext->SetPrev(this), NS_ERROR_FAILURE); + } + + mNext = aNext; + return NS_OK; +} + +NS_IMETHODIMP +nsSHTransaction::SetPrev(nsISHTransaction* aPrev) +{ + /* This is weak reference to parent. Do not Addref it */ + mPrev = aPrev; + return NS_OK; +} + +nsresult +nsSHTransaction::GetPrev(nsISHTransaction** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mPrev; + NS_IF_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsSHTransaction::SetPersist(bool aPersist) +{ + mPersist = aPersist; + return NS_OK; +} + +NS_IMETHODIMP +nsSHTransaction::GetPersist(bool* aPersist) +{ + NS_ENSURE_ARG_POINTER(aPersist); + + *aPersist = mPersist; + return NS_OK; +} diff --git a/docshell/shistory/nsSHTransaction.h b/docshell/shistory/nsSHTransaction.h new file mode 100644 index 000000000..334aa3074 --- /dev/null +++ b/docshell/shistory/nsSHTransaction.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSHTransaction_h +#define nsSHTransaction_h + +// Helper Classes +#include "nsCOMPtr.h" + +// Needed interfaces +#include "nsISHTransaction.h" + +class nsISHEntry; + +class nsSHTransaction : public nsISHTransaction +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISHTRANSACTION + + nsSHTransaction(); + +protected: + virtual ~nsSHTransaction(); + +protected: + bool mPersist; + + nsISHTransaction* mPrev; // Weak Reference + nsCOMPtr<nsISHTransaction> mNext; + nsCOMPtr<nsISHEntry> mSHEntry; +}; + +#endif /* nsSHTransaction_h */ diff --git a/docshell/shistory/nsSHistory.cpp b/docshell/shistory/nsSHistory.cpp new file mode 100644 index 000000000..7c148ffcc --- /dev/null +++ b/docshell/shistory/nsSHistory.cpp @@ -0,0 +1,1932 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSHistory.h" +#include <algorithm> + +// Helper Classes +#include "mozilla/Preferences.h" +#include "mozilla/StaticPtr.h" + +// Interfaces Needed +#include "nsILayoutHistoryState.h" +#include "nsIDocShell.h" +#include "nsIDocShellLoadInfo.h" +#include "nsISHContainer.h" +#include "nsIDocShellTreeItem.h" +#include "nsIURI.h" +#include "nsIContentViewer.h" +#include "nsIObserverService.h" +#include "prclist.h" +#include "mozilla/Services.h" +#include "nsTArray.h" +#include "nsCOMArray.h" +#include "nsDocShell.h" +#include "mozilla/Attributes.h" +#include "nsISHEntry.h" +#include "nsISHTransaction.h" +#include "nsISHistoryListener.h" +#include "nsComponentManagerUtils.h" + +// For calculating max history entries and max cachable contentviewers +#include "prsystem.h" +#include "mozilla/MathAlgorithms.h" + +using namespace mozilla; + +#define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries" +#define PREF_SHISTORY_MAX_TOTAL_VIEWERS "browser.sessionhistory.max_total_viewers" + +static const char* kObservedPrefs[] = { + PREF_SHISTORY_SIZE, + PREF_SHISTORY_MAX_TOTAL_VIEWERS, + nullptr +}; + +static int32_t gHistoryMaxSize = 50; +// Max viewers allowed per SHistory objects +static const int32_t gHistoryMaxViewers = 3; +// List of all SHistory objects, used for content viewer cache eviction +static PRCList gSHistoryList; +// Max viewers allowed total, across all SHistory objects - negative default +// means we will calculate how many viewers to cache based on total memory +int32_t nsSHistory::sHistoryMaxTotalViewers = -1; + +// A counter that is used to be able to know the order in which +// entries were touched, so that we can evict older entries first. +static uint32_t gTouchCounter = 0; + +static LazyLogModule gSHistoryLog("nsSHistory"); + +#define LOG(format) MOZ_LOG(gSHistoryLog, mozilla::LogLevel::Debug, format) + +// This macro makes it easier to print a log message which includes a URI's +// spec. Example use: +// +// nsIURI *uri = [...]; +// LOG_SPEC(("The URI is %s.", _spec), uri); +// +#define LOG_SPEC(format, uri) \ + PR_BEGIN_MACRO \ + if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \ + nsAutoCString _specStr(NS_LITERAL_CSTRING("(null)"));\ + if (uri) { \ + _specStr = uri->GetSpecOrDefault(); \ + } \ + const char* _spec = _specStr.get(); \ + LOG(format); \ + } \ + PR_END_MACRO + +// This macro makes it easy to log a message including an SHEntry's URI. +// For example: +// +// nsCOMPtr<nsISHEntry> shentry = [...]; +// LOG_SHENTRY_SPEC(("shentry %p has uri %s.", shentry.get(), _spec), shentry); +// +#define LOG_SHENTRY_SPEC(format, shentry) \ + PR_BEGIN_MACRO \ + if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \ + nsCOMPtr<nsIURI> uri; \ + shentry->GetURI(getter_AddRefs(uri)); \ + LOG_SPEC(format, uri); \ + } \ + PR_END_MACRO + +// Iterates over all registered session history listeners. +#define ITERATE_LISTENERS(body) \ + PR_BEGIN_MACRO \ + { \ + nsAutoTObserverArray<nsWeakPtr, 2>::EndLimitedIterator \ + iter(mListeners); \ + while (iter.HasMore()) { \ + nsCOMPtr<nsISHistoryListener> listener = \ + do_QueryReferent(iter.GetNext()); \ + if (listener) { \ + body \ + } \ + } \ + } \ + PR_END_MACRO + +// Calls a given method on all registered session history listeners. +#define NOTIFY_LISTENERS(method, args) \ + ITERATE_LISTENERS( \ + listener->method args; \ + ); + +// Calls a given method on all registered session history listeners. +// Listeners may return 'false' to cancel an action so make sure that we +// set the return value to 'false' if one of the listeners wants to cancel. +#define NOTIFY_LISTENERS_CANCELABLE(method, retval, args) \ + PR_BEGIN_MACRO \ + { \ + bool canceled = false; \ + retval = true; \ + ITERATE_LISTENERS( \ + listener->method args; \ + if (!retval) { \ + canceled = true; \ + } \ + ); \ + if (canceled) { \ + retval = false; \ + } \ + } \ + PR_END_MACRO + +enum HistCmd +{ + HIST_CMD_BACK, + HIST_CMD_FORWARD, + HIST_CMD_GOTOINDEX, + HIST_CMD_RELOAD +}; + +class nsSHistoryObserver final : public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + nsSHistoryObserver() {} + +protected: + ~nsSHistoryObserver() {} +}; + +StaticRefPtr<nsSHistoryObserver> gObserver; + +NS_IMPL_ISUPPORTS(nsSHistoryObserver, nsIObserver) + +NS_IMETHODIMP +nsSHistoryObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + nsSHistory::UpdatePrefs(); + nsSHistory::GloballyEvictContentViewers(); + } else if (!strcmp(aTopic, "cacheservice:empty-cache") || + !strcmp(aTopic, "memory-pressure")) { + nsSHistory::GloballyEvictAllContentViewers(); + } + + return NS_OK; +} + +namespace { + +already_AddRefed<nsIContentViewer> +GetContentViewerForTransaction(nsISHTransaction* aTrans) +{ + nsCOMPtr<nsISHEntry> entry; + aTrans->GetSHEntry(getter_AddRefs(entry)); + if (!entry) { + return nullptr; + } + + nsCOMPtr<nsISHEntry> ownerEntry; + nsCOMPtr<nsIContentViewer> viewer; + entry->GetAnyContentViewer(getter_AddRefs(ownerEntry), + getter_AddRefs(viewer)); + return viewer.forget(); +} + +void +EvictContentViewerForTransaction(nsISHTransaction* aTrans) +{ + nsCOMPtr<nsISHEntry> entry; + aTrans->GetSHEntry(getter_AddRefs(entry)); + nsCOMPtr<nsIContentViewer> viewer; + nsCOMPtr<nsISHEntry> ownerEntry; + entry->GetAnyContentViewer(getter_AddRefs(ownerEntry), + getter_AddRefs(viewer)); + if (viewer) { + NS_ASSERTION(ownerEntry, "Content viewer exists but its SHEntry is null"); + + LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for " + "owning SHEntry 0x%p at %s.", + viewer.get(), ownerEntry.get(), _spec), + ownerEntry); + + // Drop the presentation state before destroying the viewer, so that + // document teardown is able to correctly persist the state. + ownerEntry->SetContentViewer(nullptr); + ownerEntry->SyncPresentationState(); + viewer->Destroy(); + } +} + +} // namespace + +nsSHistory::nsSHistory() + : mIndex(-1) + , mLength(0) + , mRequestedIndex(-1) + , mIsPartial(false) + , mGlobalIndexOffset(0) + , mEntriesInFollowingPartialHistories(0) + , mRootDocShell(nullptr) +{ + // Add this new SHistory object to the list + PR_APPEND_LINK(this, &gSHistoryList); +} + +nsSHistory::~nsSHistory() +{ + // Remove this SHistory object from the list + PR_REMOVE_LINK(this); +} + +NS_IMPL_ADDREF(nsSHistory) +NS_IMPL_RELEASE(nsSHistory) + +NS_INTERFACE_MAP_BEGIN(nsSHistory) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISHistory) + NS_INTERFACE_MAP_ENTRY(nsISHistory) + NS_INTERFACE_MAP_ENTRY(nsIWebNavigation) + NS_INTERFACE_MAP_ENTRY(nsISHistoryInternal) +NS_INTERFACE_MAP_END + +// static +uint32_t +nsSHistory::CalcMaxTotalViewers() +{ + // Calculate an estimate of how many ContentViewers we should cache based + // on RAM. This assumes that the average ContentViewer is 4MB (conservative) + // and caps the max at 8 ContentViewers + // + // TODO: Should we split the cache memory betw. ContentViewer caching and + // nsCacheService? + // + // RAM ContentViewers + // ----------------------- + // 32 Mb 0 + // 64 Mb 1 + // 128 Mb 2 + // 256 Mb 3 + // 512 Mb 5 + // 1024 Mb 8 + // 2048 Mb 8 + // 4096 Mb 8 + uint64_t bytes = PR_GetPhysicalMemorySize(); + + if (bytes == 0) { + return 0; + } + + // Conversion from unsigned int64_t to double doesn't work on all platforms. + // We need to truncate the value at INT64_MAX to make sure we don't + // overflow. + if (bytes > INT64_MAX) { + bytes = INT64_MAX; + } + + double kBytesD = (double)(bytes >> 10); + + // This is essentially the same calculation as for nsCacheService, + // except that we divide the final memory calculation by 4, since + // we assume each ContentViewer takes on average 4MB + uint32_t viewers = 0; + double x = std::log(kBytesD) / std::log(2.0) - 14; + if (x > 0) { + viewers = (uint32_t)(x * x - x + 2.001); // add .001 for rounding + viewers /= 4; + } + + // Cap it off at 8 max + if (viewers > 8) { + viewers = 8; + } + return viewers; +} + +// static +void +nsSHistory::UpdatePrefs() +{ + Preferences::GetInt(PREF_SHISTORY_SIZE, &gHistoryMaxSize); + Preferences::GetInt(PREF_SHISTORY_MAX_TOTAL_VIEWERS, + &sHistoryMaxTotalViewers); + // If the pref is negative, that means we calculate how many viewers + // we think we should cache, based on total memory + if (sHistoryMaxTotalViewers < 0) { + sHistoryMaxTotalViewers = CalcMaxTotalViewers(); + } +} + +// static +nsresult +nsSHistory::Startup() +{ + UpdatePrefs(); + + // The goal of this is to unbreak users who have inadvertently set their + // session history size to less than the default value. + int32_t defaultHistoryMaxSize = + Preferences::GetDefaultInt(PREF_SHISTORY_SIZE, 50); + if (gHistoryMaxSize < defaultHistoryMaxSize) { + gHistoryMaxSize = defaultHistoryMaxSize; + } + + // Allow the user to override the max total number of cached viewers, + // but keep the per SHistory cached viewer limit constant + if (!gObserver) { + gObserver = new nsSHistoryObserver(); + Preferences::AddStrongObservers(gObserver, kObservedPrefs); + + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + if (obsSvc) { + // Observe empty-cache notifications so tahat clearing the disk/memory + // cache will also evict all content viewers. + obsSvc->AddObserver(gObserver, "cacheservice:empty-cache", false); + + // Same for memory-pressure notifications + obsSvc->AddObserver(gObserver, "memory-pressure", false); + } + } + + // Initialize the global list of all SHistory objects + PR_INIT_CLIST(&gSHistoryList); + return NS_OK; +} + +// static +void +nsSHistory::Shutdown() +{ + if (gObserver) { + Preferences::RemoveObservers(gObserver, kObservedPrefs); + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + if (obsSvc) { + obsSvc->RemoveObserver(gObserver, "cacheservice:empty-cache"); + obsSvc->RemoveObserver(gObserver, "memory-pressure"); + } + gObserver = nullptr; + } +} + +/* Add an entry to the History list at mIndex and + * increment the index to point to the new entry + */ +NS_IMETHODIMP +nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist) +{ + NS_ENSURE_ARG(aSHEntry); + + nsCOMPtr<nsISHTransaction> currentTxn; + + if (mListRoot) { + GetTransactionAtIndex(mIndex, getter_AddRefs(currentTxn)); + } + + bool currentPersist = true; + if (currentTxn) { + currentTxn->GetPersist(¤tPersist); + } + + int32_t currentIndex = mIndex; + + if (!currentPersist) { + NOTIFY_LISTENERS(OnHistoryReplaceEntry, (currentIndex)); + NS_ENSURE_SUCCESS(currentTxn->SetSHEntry(aSHEntry), NS_ERROR_FAILURE); + currentTxn->SetPersist(aPersist); + return NS_OK; + } + + nsCOMPtr<nsISHTransaction> txn( + do_CreateInstance(NS_SHTRANSACTION_CONTRACTID)); + NS_ENSURE_TRUE(txn, NS_ERROR_FAILURE); + + nsCOMPtr<nsIURI> uri; + aSHEntry->GetURI(getter_AddRefs(uri)); + NOTIFY_LISTENERS(OnHistoryNewEntry, (uri, currentIndex)); + + // If a listener has changed mIndex, we need to get currentTxn again, + // otherwise we'll be left at an inconsistent state (see bug 320742) + if (currentIndex != mIndex) { + GetTransactionAtIndex(mIndex, getter_AddRefs(currentTxn)); + } + + // Set the ShEntry and parent for the transaction. setting the + // parent will properly set the parent child relationship + txn->SetPersist(aPersist); + NS_ENSURE_SUCCESS(txn->Create(aSHEntry, currentTxn), NS_ERROR_FAILURE); + + // A little tricky math here... Basically when adding an object regardless of + // what the length was before, it should always be set back to the current and + // lop off the forward. + mLength = (++mIndex + 1); + NOTIFY_LISTENERS(OnLengthChange, (mLength)); + + // Much like how mLength works above, when changing our entries, all following + // partial histories should be purged, so we just reset the number to zero. + mEntriesInFollowingPartialHistories = 0; + + // If this is the very first transaction, initialize the list + if (!mListRoot) { + mListRoot = txn; + } + + // Purge History list if it is too long + if (gHistoryMaxSize >= 0 && mLength > gHistoryMaxSize) { + PurgeHistory(mLength - gHistoryMaxSize); + } + + RemoveDynEntries(mIndex - 1, mIndex); + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::GetIsPartial(bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mIsPartial; + return NS_OK; +} + +/* Get size of the history list */ +NS_IMETHODIMP +nsSHistory::GetCount(int32_t* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mLength; + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::GetGlobalCount(int32_t* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mGlobalIndexOffset + mLength + mEntriesInFollowingPartialHistories; + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::GetGlobalIndexOffset(int32_t* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mGlobalIndexOffset; + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::OnPartialSessionHistoryActive(int32_t aGlobalLength, int32_t aTargetIndex) +{ + NS_ENSURE_TRUE(mIsPartial, NS_ERROR_UNEXPECTED); + + int32_t extraLength = aGlobalLength - mLength - mGlobalIndexOffset; + NS_ENSURE_TRUE(extraLength >= 0, NS_ERROR_UNEXPECTED); + + if (extraLength != mEntriesInFollowingPartialHistories) { + mEntriesInFollowingPartialHistories = extraLength; + } + + if (mIndex == aTargetIndex) { + // TODO When we finish OnPartialSessionHistoryDeactive, we'll need to active + // the suspended document here. + + // Fire location change to update canGoBack / canGoForward. + NS_DispatchToCurrentThread(NewRunnableMethod(static_cast<nsDocShell*>(mRootDocShell), + &nsDocShell::FireDummyOnLocationChange)); + return NS_OK; + } + + return LoadEntry(aTargetIndex, nsIDocShellLoadInfo::loadHistory, + HIST_CMD_GOTOINDEX); +} + +NS_IMETHODIMP +nsSHistory::OnPartialSessionHistoryDeactive() +{ + NS_ENSURE_TRUE(mIsPartial, NS_ERROR_UNEXPECTED); + + // TODO We need to suspend current document first. Much like what happens when + // loading a new page. Move the ownership of the document to nsISHEntry or so. + return NS_OK; +} + +/* Get index of the history list */ +NS_IMETHODIMP +nsSHistory::GetIndex(int32_t* aResult) +{ + NS_PRECONDITION(aResult, "null out param?"); + *aResult = mIndex; + return NS_OK; +} + +/* Get the requestedIndex */ +NS_IMETHODIMP +nsSHistory::GetRequestedIndex(int32_t* aResult) +{ + NS_PRECONDITION(aResult, "null out param?"); + *aResult = mRequestedIndex; + return NS_OK; +} + +/* Get the entry at a given index */ +NS_IMETHODIMP +nsSHistory::GetEntryAtIndex(int32_t aIndex, bool aModifyIndex, + nsISHEntry** aResult) +{ + nsresult rv; + nsCOMPtr<nsISHTransaction> txn; + + /* GetTransactionAtIndex ensures aResult is valid and validates aIndex */ + rv = GetTransactionAtIndex(aIndex, getter_AddRefs(txn)); + if (NS_SUCCEEDED(rv) && txn) { + // Get the Entry from the transaction + rv = txn->GetSHEntry(aResult); + if (NS_SUCCEEDED(rv) && (*aResult)) { + // Set mIndex to the requested index, if asked to do so.. + if (aModifyIndex) { + mIndex = aIndex; + } + } + } + return rv; +} + +/* Get the transaction at a given index */ +NS_IMETHODIMP +nsSHistory::GetTransactionAtIndex(int32_t aIndex, nsISHTransaction** aResult) +{ + nsresult rv; + NS_ENSURE_ARG_POINTER(aResult); + + if (mLength <= 0 || aIndex < 0 || aIndex >= mLength) { + return NS_ERROR_FAILURE; + } + + if (!mListRoot) { + return NS_ERROR_FAILURE; + } + + if (aIndex == 0) { + *aResult = mListRoot; + NS_ADDREF(*aResult); + return NS_OK; + } + + int32_t cnt = 0; + nsCOMPtr<nsISHTransaction> tempPtr; + rv = GetRootTransaction(getter_AddRefs(tempPtr)); + if (NS_FAILED(rv) || !tempPtr) { + return NS_ERROR_FAILURE; + } + + while (true) { + nsCOMPtr<nsISHTransaction> ptr; + rv = tempPtr->GetNext(getter_AddRefs(ptr)); + if (NS_SUCCEEDED(rv) && ptr) { + cnt++; + if (cnt == aIndex) { + ptr.forget(aResult); + break; + } else { + tempPtr = ptr; + continue; + } + } else { + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +/* Get the index of a given entry */ +NS_IMETHODIMP +nsSHistory::GetIndexOfEntry(nsISHEntry* aSHEntry, int32_t* aResult) +{ + NS_ENSURE_ARG(aSHEntry); + NS_ENSURE_ARG_POINTER(aResult); + *aResult = -1; + + if (mLength <= 0) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsISHTransaction> currentTxn; + int32_t cnt = 0; + + nsresult rv = GetRootTransaction(getter_AddRefs(currentTxn)); + if (NS_FAILED(rv) || !currentTxn) { + return NS_ERROR_FAILURE; + } + + while (true) { + nsCOMPtr<nsISHEntry> entry; + rv = currentTxn->GetSHEntry(getter_AddRefs(entry)); + if (NS_FAILED(rv) || !entry) { + return NS_ERROR_FAILURE; + } + + if (aSHEntry == entry) { + *aResult = cnt; + break; + } + + rv = currentTxn->GetNext(getter_AddRefs(currentTxn)); + if (NS_FAILED(rv) || !currentTxn) { + return NS_ERROR_FAILURE; + } + + cnt++; + } + + return NS_OK; +} + +#ifdef DEBUG +nsresult +nsSHistory::PrintHistory() +{ + nsCOMPtr<nsISHTransaction> txn; + int32_t index = 0; + nsresult rv; + + if (!mListRoot) { + return NS_ERROR_FAILURE; + } + + txn = mListRoot; + + while (1) { + if (!txn) { + break; + } + nsCOMPtr<nsISHEntry> entry; + rv = txn->GetSHEntry(getter_AddRefs(entry)); + if (NS_FAILED(rv) && !entry) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsILayoutHistoryState> layoutHistoryState; + nsCOMPtr<nsIURI> uri; + nsXPIDLString title; + + entry->GetLayoutHistoryState(getter_AddRefs(layoutHistoryState)); + entry->GetURI(getter_AddRefs(uri)); + entry->GetTitle(getter_Copies(title)); + +#if 0 + nsAutoCString url; + if (uri) { + uri->GetSpec(url); + } + + printf("**** SH Transaction #%d, Entry = %x\n", index, entry.get()); + 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.get()); +#endif + + nsCOMPtr<nsISHTransaction> next; + rv = txn->GetNext(getter_AddRefs(next)); + if (NS_SUCCEEDED(rv) && next) { + txn = next; + index++; + continue; + } else { + break; + } + } + + return NS_OK; +} +#endif + +NS_IMETHODIMP +nsSHistory::GetRootTransaction(nsISHTransaction** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mListRoot; + NS_IF_ADDREF(*aResult); + return NS_OK; +} + +/* Get the max size of the history list */ +NS_IMETHODIMP +nsSHistory::GetMaxLength(int32_t* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = gHistoryMaxSize; + return NS_OK; +} + +/* Set the max size of the history list */ +NS_IMETHODIMP +nsSHistory::SetMaxLength(int32_t aMaxSize) +{ + if (aMaxSize < 0) { + return NS_ERROR_ILLEGAL_VALUE; + } + + gHistoryMaxSize = aMaxSize; + if (mLength > aMaxSize) { + PurgeHistory(mLength - aMaxSize); + } + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::PurgeHistory(int32_t aEntries) +{ + if (mLength <= 0 || aEntries <= 0) { + return NS_ERROR_FAILURE; + } + + aEntries = std::min(aEntries, mLength); + + bool purgeHistory = true; + NOTIFY_LISTENERS_CANCELABLE(OnHistoryPurge, purgeHistory, + (aEntries, &purgeHistory)); + + if (!purgeHistory) { + // Listener asked us not to purge + return NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA; + } + + int32_t cnt = 0; + while (cnt < aEntries) { + nsCOMPtr<nsISHTransaction> nextTxn; + if (mListRoot) { + mListRoot->GetNext(getter_AddRefs(nextTxn)); + mListRoot->SetNext(nullptr); + } + mListRoot = nextTxn; + if (mListRoot) { + mListRoot->SetPrev(nullptr); + } + cnt++; + } + mLength -= cnt; + mIndex -= cnt; + NOTIFY_LISTENERS(OnLengthChange, (mLength)); + + // All following partial histories will be deleted in this case. + mEntriesInFollowingPartialHistories = 0; + + // Now if we were not at the end of the history, mIndex could have + // become far too negative. If so, just set it to -1. + if (mIndex < -1) { + mIndex = -1; + } + + if (mRootDocShell) { + mRootDocShell->HistoryPurged(cnt); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::AddSHistoryListener(nsISHistoryListener* aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + + // Check if the listener supports Weak Reference. This is a must. + // This listener functionality is used by embedders and we want to + // have the right ownership with who ever listens to SHistory + nsWeakPtr listener = do_GetWeakReference(aListener); + if (!listener) { + return NS_ERROR_FAILURE; + } + + return mListeners.AppendElementUnlessExists(listener) ? + NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsSHistory::RemoveSHistoryListener(nsISHistoryListener* aListener) +{ + // Make sure the listener that wants to be removed is the + // one we have in store. + nsWeakPtr listener = do_GetWeakReference(aListener); + mListeners.RemoveElement(listener); + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::SetPartialSHistoryListener(nsIPartialSHistoryListener* aListener) +{ + mPartialHistoryListener = do_GetWeakReference(aListener); + return NS_OK; +} + +/* Replace an entry in the History list at a particular index. + * Do not update index or count. + */ +NS_IMETHODIMP +nsSHistory::ReplaceEntry(int32_t aIndex, nsISHEntry* aReplaceEntry) +{ + NS_ENSURE_ARG(aReplaceEntry); + nsresult rv; + nsCOMPtr<nsISHTransaction> currentTxn; + + if (!mListRoot) { + // Session History is not initialised. + return NS_ERROR_FAILURE; + } + + rv = GetTransactionAtIndex(aIndex, getter_AddRefs(currentTxn)); + + if (currentTxn) { + NOTIFY_LISTENERS(OnHistoryReplaceEntry, (aIndex)); + + // Set the replacement entry in the transaction + rv = currentTxn->SetSHEntry(aReplaceEntry); + rv = currentTxn->SetPersist(true); + } + return rv; +} + +NS_IMETHODIMP +nsSHistory::NotifyOnHistoryReload(nsIURI* aReloadURI, uint32_t aReloadFlags, + bool* aCanReload) +{ + NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, *aCanReload, + (aReloadURI, aReloadFlags, aCanReload)); + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::EvictOutOfRangeContentViewers(int32_t aIndex) +{ + // Check our per SHistory object limit in the currently navigated SHistory + EvictOutOfRangeWindowContentViewers(aIndex); + // Check our total limit across all SHistory objects + GloballyEvictContentViewers(); + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::EvictAllContentViewers() +{ + // XXXbz we don't actually do a good job of evicting things as we should, so + // we might have viewers quite far from mIndex. So just evict everything. + nsCOMPtr<nsISHTransaction> trans = mListRoot; + while (trans) { + EvictContentViewerForTransaction(trans); + + nsCOMPtr<nsISHTransaction> temp = trans; + temp->GetNext(getter_AddRefs(trans)); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::GetCanGoBack(bool* aCanGoBack) +{ + NS_ENSURE_ARG_POINTER(aCanGoBack); + + if (mGlobalIndexOffset) { + *aCanGoBack = true; + return NS_OK; + } + + int32_t index = -1; + NS_ENSURE_SUCCESS(GetIndex(&index), NS_ERROR_FAILURE); + if (index > 0) { + *aCanGoBack = true; + return NS_OK; + } + + *aCanGoBack = false; + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::GetCanGoForward(bool* aCanGoForward) +{ + NS_ENSURE_ARG_POINTER(aCanGoForward); + + if (mEntriesInFollowingPartialHistories) { + *aCanGoForward = true; + return NS_OK; + } + + int32_t index = -1; + int32_t count = -1; + NS_ENSURE_SUCCESS(GetIndex(&index), NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(GetCount(&count), NS_ERROR_FAILURE); + if (index >= 0 && index < (count - 1)) { + *aCanGoForward = true; + return NS_OK; + } + + *aCanGoForward = false; + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::GoBack() +{ + bool canGoBack = false; + + GetCanGoBack(&canGoBack); + if (!canGoBack) { + return NS_ERROR_UNEXPECTED; + } + return LoadEntry(mIndex - 1, nsIDocShellLoadInfo::loadHistory, HIST_CMD_BACK); +} + +NS_IMETHODIMP +nsSHistory::GoForward() +{ + bool canGoForward = false; + + GetCanGoForward(&canGoForward); + if (!canGoForward) { + return NS_ERROR_UNEXPECTED; + } + return LoadEntry(mIndex + 1, nsIDocShellLoadInfo::loadHistory, + HIST_CMD_FORWARD); +} + +NS_IMETHODIMP +nsSHistory::Reload(uint32_t aReloadFlags) +{ + nsDocShellInfoLoadType loadType; + if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY && + aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) { + loadType = nsIDocShellLoadInfo::loadReloadBypassProxyAndCache; + } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY) { + loadType = nsIDocShellLoadInfo::loadReloadBypassProxy; + } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) { + loadType = nsIDocShellLoadInfo::loadReloadBypassCache; + } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE) { + loadType = nsIDocShellLoadInfo::loadReloadCharsetChange; + } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT) { + loadType = nsIDocShellLoadInfo::loadReloadMixedContent; + } else { + loadType = nsIDocShellLoadInfo::loadReloadNormal; + } + + // We are reloading. Send Reload notifications. + // nsDocShellLoadFlagType is not public, where as nsIWebNavigation + // is public. So send the reload notifications with the + // nsIWebNavigation flags. + bool canNavigate = true; + nsCOMPtr<nsIURI> currentURI; + GetCurrentURI(getter_AddRefs(currentURI)); + NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload, canNavigate, + (currentURI, aReloadFlags, &canNavigate)); + if (!canNavigate) { + return NS_OK; + } + + return LoadEntry(mIndex, loadType, HIST_CMD_RELOAD); +} + +NS_IMETHODIMP +nsSHistory::ReloadCurrentEntry() +{ + // Notify listeners + bool canNavigate = true; + nsCOMPtr<nsIURI> currentURI; + GetCurrentURI(getter_AddRefs(currentURI)); + NOTIFY_LISTENERS_CANCELABLE(OnHistoryGotoIndex, canNavigate, + (mIndex, currentURI, &canNavigate)); + if (!canNavigate) { + return NS_OK; + } + + return LoadEntry(mIndex, nsIDocShellLoadInfo::loadHistory, HIST_CMD_RELOAD); +} + +void +nsSHistory::EvictOutOfRangeWindowContentViewers(int32_t aIndex) +{ + // XXX rename method to EvictContentViewersExceptAroundIndex, or something. + + // We need to release all content viewers that are no longer in the range + // + // aIndex - gHistoryMaxViewers to aIndex + gHistoryMaxViewers + // + // to ensure that this SHistory object isn't responsible for more than + // gHistoryMaxViewers content viewers. But our job is complicated by the + // fact that two transactions which are related by either hash navigations or + // history.pushState will have the same content viewer. + // + // To illustrate the issue, suppose gHistoryMaxViewers = 3 and we have four + // linked transactions in our history. Suppose we then add a new content + // viewer and call into this function. So the history looks like: + // + // A A A A B + // + * + // + // where the letters are content viewers and + and * denote the beginning and + // end of the range aIndex +/- gHistoryMaxViewers. + // + // Although one copy of the content viewer A exists outside the range, we + // don't want to evict A, because it has other copies in range! + // + // We therefore adjust our eviction strategy to read: + // + // Evict each content viewer outside the range aIndex -/+ + // gHistoryMaxViewers, unless that content viewer also appears within the + // range. + // + // (Note that it's entirely legal to have two copies of one content viewer + // separated by a different content viewer -- call pushState twice, go back + // once, and refresh -- so we can't rely on identical viewers only appearing + // adjacent to one another.) + + if (aIndex < 0) { + return; + } + NS_ENSURE_TRUE_VOID(aIndex < mLength); + + // Calculate the range that's safe from eviction. + int32_t startSafeIndex = std::max(0, aIndex - gHistoryMaxViewers); + int32_t endSafeIndex = std::min(mLength, aIndex + gHistoryMaxViewers); + + LOG(("EvictOutOfRangeWindowContentViewers(index=%d), " + "mLength=%d. Safe range [%d, %d]", + aIndex, mLength, startSafeIndex, endSafeIndex)); + + // The content viewers in range aIndex -/+ gHistoryMaxViewers will not be + // evicted. Collect a set of them so we don't accidentally evict one of them + // if it appears outside this range. + nsCOMArray<nsIContentViewer> safeViewers; + nsCOMPtr<nsISHTransaction> trans; + GetTransactionAtIndex(startSafeIndex, getter_AddRefs(trans)); + for (int32_t i = startSafeIndex; trans && i <= endSafeIndex; i++) { + nsCOMPtr<nsIContentViewer> viewer = GetContentViewerForTransaction(trans); + safeViewers.AppendObject(viewer); + nsCOMPtr<nsISHTransaction> temp = trans; + temp->GetNext(getter_AddRefs(trans)); + } + + // Walk the SHistory list and evict any content viewers that aren't safe. + GetTransactionAtIndex(0, getter_AddRefs(trans)); + while (trans) { + nsCOMPtr<nsIContentViewer> viewer = GetContentViewerForTransaction(trans); + if (safeViewers.IndexOf(viewer) == -1) { + EvictContentViewerForTransaction(trans); + } + + nsCOMPtr<nsISHTransaction> temp = trans; + temp->GetNext(getter_AddRefs(trans)); + } +} + +namespace { + +class TransactionAndDistance +{ +public: + TransactionAndDistance(nsISHTransaction* aTrans, uint32_t aDist) + : mTransaction(aTrans) + , mLastTouched(0) + , mDistance(aDist) + { + mViewer = GetContentViewerForTransaction(aTrans); + NS_ASSERTION(mViewer, "Transaction should have a content viewer"); + + nsCOMPtr<nsISHEntry> shentry; + mTransaction->GetSHEntry(getter_AddRefs(shentry)); + + nsCOMPtr<nsISHEntryInternal> shentryInternal = do_QueryInterface(shentry); + if (shentryInternal) { + shentryInternal->GetLastTouched(&mLastTouched); + } else { + NS_WARNING("Can't cast to nsISHEntryInternal?"); + } + } + + bool operator<(const TransactionAndDistance& aOther) const + { + // Compare distances first, and fall back to last-accessed times. + if (aOther.mDistance != this->mDistance) { + return this->mDistance < aOther.mDistance; + } + + return this->mLastTouched < aOther.mLastTouched; + } + + bool operator==(const TransactionAndDistance& aOther) const + { + // This is a little silly; we need == so the default comaprator can be + // instantiated, but this function is never actually called when we sort + // the list of TransactionAndDistance objects. + return aOther.mDistance == this->mDistance && + aOther.mLastTouched == this->mLastTouched; + } + + nsCOMPtr<nsISHTransaction> mTransaction; + nsCOMPtr<nsIContentViewer> mViewer; + uint32_t mLastTouched; + int32_t mDistance; +}; + +} // namespace + +// static +void +nsSHistory::GloballyEvictContentViewers() +{ + // First, collect from each SHistory object the transactions which have a + // cached content viewer. Associate with each transaction its distance from + // its SHistory's current index. + + nsTArray<TransactionAndDistance> transactions; + + PRCList* listEntry = PR_LIST_HEAD(&gSHistoryList); + while (listEntry != &gSHistoryList) { + nsSHistory* shist = static_cast<nsSHistory*>(listEntry); + + // Maintain a list of the transactions which have viewers and belong to + // this particular shist object. We'll add this list to the global list, + // |transactions|, eventually. + nsTArray<TransactionAndDistance> shTransactions; + + // Content viewers are likely to exist only within shist->mIndex -/+ + // gHistoryMaxViewers, so only search within that range. + // + // A content viewer might exist outside that range due to either: + // + // * history.pushState or hash navigations, in which case a copy of the + // content viewer should exist within the range, or + // + // * bugs which cause us not to call nsSHistory::EvictContentViewers() + // often enough. Once we do call EvictContentViewers() for the + // SHistory object in question, we'll do a full search of its history + // and evict the out-of-range content viewers, so we don't bother here. + // + int32_t startIndex = std::max(0, shist->mIndex - gHistoryMaxViewers); + int32_t endIndex = std::min(shist->mLength - 1, + shist->mIndex + gHistoryMaxViewers); + nsCOMPtr<nsISHTransaction> trans; + shist->GetTransactionAtIndex(startIndex, getter_AddRefs(trans)); + for (int32_t i = startIndex; trans && i <= endIndex; i++) { + nsCOMPtr<nsIContentViewer> contentViewer = + GetContentViewerForTransaction(trans); + + if (contentViewer) { + // Because one content viewer might belong to multiple SHEntries, we + // have to search through shTransactions to see if we already know + // about this content viewer. If we find the viewer, update its + // distance from the SHistory's index and continue. + bool found = false; + for (uint32_t j = 0; j < shTransactions.Length(); j++) { + TransactionAndDistance& container = shTransactions[j]; + if (container.mViewer == contentViewer) { + container.mDistance = std::min(container.mDistance, + DeprecatedAbs(i - shist->mIndex)); + found = true; + break; + } + } + + // If we didn't find a TransactionAndDistance for this content viewer, + // make a new one. + if (!found) { + TransactionAndDistance container(trans, + DeprecatedAbs(i - shist->mIndex)); + shTransactions.AppendElement(container); + } + } + + nsCOMPtr<nsISHTransaction> temp = trans; + temp->GetNext(getter_AddRefs(trans)); + } + + // We've found all the transactions belonging to shist which have viewers. + // Add those transactions to our global list and move on. + transactions.AppendElements(shTransactions); + listEntry = PR_NEXT_LINK(shist); + } + + // We now have collected all cached content viewers. First check that we + // have enough that we actually need to evict some. + if ((int32_t)transactions.Length() <= sHistoryMaxTotalViewers) { + return; + } + + // If we need to evict, sort our list of transactions and evict the largest + // ones. (We could of course get better algorithmic complexity here by using + // a heap or something more clever. But sHistoryMaxTotalViewers isn't large, + // so let's not worry about it.) + transactions.Sort(); + + for (int32_t i = transactions.Length() - 1; i >= sHistoryMaxTotalViewers; + --i) { + EvictContentViewerForTransaction(transactions[i].mTransaction); + } +} + +nsresult +nsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry* aEntry) +{ + int32_t startIndex = std::max(0, mIndex - gHistoryMaxViewers); + int32_t endIndex = std::min(mLength - 1, mIndex + gHistoryMaxViewers); + nsCOMPtr<nsISHTransaction> trans; + GetTransactionAtIndex(startIndex, getter_AddRefs(trans)); + + int32_t i; + for (i = startIndex; trans && i <= endIndex; ++i) { + nsCOMPtr<nsISHEntry> entry; + trans->GetSHEntry(getter_AddRefs(entry)); + + // Does entry have the same BFCacheEntry as the argument to this method? + if (entry->HasBFCacheEntry(aEntry)) { + break; + } + + nsCOMPtr<nsISHTransaction> temp = trans; + temp->GetNext(getter_AddRefs(trans)); + } + if (i > endIndex) { + return NS_OK; + } + + if (i == mIndex) { + NS_WARNING("How did the current SHEntry expire?"); + return NS_OK; + } + + EvictContentViewerForTransaction(trans); + + return NS_OK; +} + +// Evicts all content viewers in all history objects. This is very +// inefficient, because it requires a linear search through all SHistory +// objects for each viewer to be evicted. However, this method is called +// infrequently -- only when the disk or memory cache is cleared. + +// static +void +nsSHistory::GloballyEvictAllContentViewers() +{ + int32_t maxViewers = sHistoryMaxTotalViewers; + sHistoryMaxTotalViewers = 0; + GloballyEvictContentViewers(); + sHistoryMaxTotalViewers = maxViewers; +} + +void +GetDynamicChildren(nsISHContainer* aContainer, + nsTArray<uint64_t>& aDocshellIDs, + bool aOnlyTopLevelDynamic) +{ + int32_t count = 0; + aContainer->GetChildCount(&count); + for (int32_t i = 0; i < count; ++i) { + nsCOMPtr<nsISHEntry> child; + aContainer->GetChildAt(i, getter_AddRefs(child)); + if (child) { + bool dynAdded = false; + child->IsDynamicallyAdded(&dynAdded); + if (dynAdded) { + uint64_t docshellID = 0; + child->GetDocshellID(&docshellID); + aDocshellIDs.AppendElement(docshellID); + } + if (!dynAdded || !aOnlyTopLevelDynamic) { + nsCOMPtr<nsISHContainer> childAsContainer = do_QueryInterface(child); + if (childAsContainer) { + GetDynamicChildren(childAsContainer, aDocshellIDs, + aOnlyTopLevelDynamic); + } + } + } + } +} + +bool +RemoveFromSessionHistoryContainer(nsISHContainer* aContainer, + nsTArray<uint64_t>& aDocshellIDs) +{ + nsCOMPtr<nsISHEntry> root = do_QueryInterface(aContainer); + NS_ENSURE_TRUE(root, false); + + bool didRemove = false; + int32_t childCount = 0; + aContainer->GetChildCount(&childCount); + for (int32_t i = childCount - 1; i >= 0; --i) { + nsCOMPtr<nsISHEntry> child; + aContainer->GetChildAt(i, getter_AddRefs(child)); + if (child) { + uint64_t docshelldID = 0; + child->GetDocshellID(&docshelldID); + if (aDocshellIDs.Contains(docshelldID)) { + didRemove = true; + aContainer->RemoveChild(child); + } else { + nsCOMPtr<nsISHContainer> container = do_QueryInterface(child); + if (container) { + bool childRemoved = + RemoveFromSessionHistoryContainer(container, aDocshellIDs); + if (childRemoved) { + didRemove = true; + } + } + } + } + } + return didRemove; +} + +bool +RemoveChildEntries(nsISHistory* aHistory, int32_t aIndex, + nsTArray<uint64_t>& aEntryIDs) +{ + nsCOMPtr<nsISHEntry> rootHE; + aHistory->GetEntryAtIndex(aIndex, false, getter_AddRefs(rootHE)); + nsCOMPtr<nsISHContainer> root = do_QueryInterface(rootHE); + return root ? RemoveFromSessionHistoryContainer(root, aEntryIDs) : false; +} + +bool +IsSameTree(nsISHEntry* aEntry1, nsISHEntry* aEntry2) +{ + if (!aEntry1 && !aEntry2) { + return true; + } + if ((!aEntry1 && aEntry2) || (aEntry1 && !aEntry2)) { + return false; + } + uint32_t id1, id2; + aEntry1->GetID(&id1); + aEntry2->GetID(&id2); + if (id1 != id2) { + return false; + } + + nsCOMPtr<nsISHContainer> container1 = do_QueryInterface(aEntry1); + nsCOMPtr<nsISHContainer> container2 = do_QueryInterface(aEntry2); + int32_t count1, count2; + container1->GetChildCount(&count1); + container2->GetChildCount(&count2); + // We allow null entries in the end of the child list. + int32_t count = std::max(count1, count2); + for (int32_t i = 0; i < count; ++i) { + nsCOMPtr<nsISHEntry> child1, child2; + container1->GetChildAt(i, getter_AddRefs(child1)); + container2->GetChildAt(i, getter_AddRefs(child2)); + if (!IsSameTree(child1, child2)) { + return false; + } + } + + return true; +} + +bool +nsSHistory::RemoveDuplicate(int32_t aIndex, bool aKeepNext) +{ + NS_ASSERTION(aIndex >= 0, "aIndex must be >= 0!"); + NS_ASSERTION(aIndex != 0 || aKeepNext, + "If we're removing index 0 we must be keeping the next"); + NS_ASSERTION(aIndex != mIndex, "Shouldn't remove mIndex!"); + int32_t compareIndex = aKeepNext ? aIndex + 1 : aIndex - 1; + nsCOMPtr<nsISHEntry> root1, root2; + GetEntryAtIndex(aIndex, false, getter_AddRefs(root1)); + GetEntryAtIndex(compareIndex, false, getter_AddRefs(root2)); + if (IsSameTree(root1, root2)) { + nsCOMPtr<nsISHTransaction> txToRemove, txToKeep, txNext, txPrev; + GetTransactionAtIndex(aIndex, getter_AddRefs(txToRemove)); + GetTransactionAtIndex(compareIndex, getter_AddRefs(txToKeep)); + if (!txToRemove) { + return false; + } + NS_ENSURE_TRUE(txToKeep, false); + txToRemove->GetNext(getter_AddRefs(txNext)); + txToRemove->GetPrev(getter_AddRefs(txPrev)); + txToRemove->SetNext(nullptr); + txToRemove->SetPrev(nullptr); + if (aKeepNext) { + if (txPrev) { + txPrev->SetNext(txToKeep); + } else { + txToKeep->SetPrev(nullptr); + } + } else { + txToKeep->SetNext(txNext); + } + + if (aIndex == 0 && aKeepNext) { + NS_ASSERTION(txToRemove == mListRoot, + "Transaction at index 0 should be mListRoot!"); + // We're removing the very first session history transaction! + mListRoot = txToKeep; + } + if (mRootDocShell) { + static_cast<nsDocShell*>(mRootDocShell)->HistoryTransactionRemoved(aIndex); + } + + // Adjust our indices to reflect the removed transaction + if (mIndex > aIndex) { + mIndex = mIndex - 1; + } + + // NB: If the transaction we are removing is the transaction currently + // being navigated to (mRequestedIndex) then we adjust the index + // only if we're not keeping the next entry (because if we are keeping + // the next entry (because the current is a duplicate of the next), then + // that entry slides into the spot that we're currently pointing to. + // We don't do this adjustment for mIndex because mIndex cannot equal + // aIndex. + + // NB: We don't need to guard on mRequestedIndex being nonzero here, + // because either they're strictly greater than aIndex which is at least + // zero, or they are equal to aIndex in which case aKeepNext must be true + // if aIndex is zero. + if (mRequestedIndex > aIndex || (mRequestedIndex == aIndex && !aKeepNext)) { + mRequestedIndex = mRequestedIndex - 1; + } + --mLength; + mEntriesInFollowingPartialHistories = 0; + NOTIFY_LISTENERS(OnLengthChange, (mLength)); + return true; + } + return false; +} + +NS_IMETHODIMP_(void) +nsSHistory::RemoveEntries(nsTArray<uint64_t>& aIDs, int32_t aStartIndex) +{ + int32_t index = aStartIndex; + while (index >= 0 && RemoveChildEntries(this, --index, aIDs)) { + } + int32_t minIndex = index; + index = aStartIndex; + while (index >= 0 && RemoveChildEntries(this, index++, aIDs)) { + } + + // We need to remove duplicate nsSHEntry trees. + bool didRemove = false; + while (index > minIndex) { + if (index != mIndex) { + didRemove = RemoveDuplicate(index, index < mIndex) || didRemove; + } + --index; + } + if (didRemove && mRootDocShell) { + NS_DispatchToCurrentThread(NewRunnableMethod(static_cast<nsDocShell*>(mRootDocShell), + &nsDocShell::FireDummyOnLocationChange)); + } +} + +void +nsSHistory::RemoveDynEntries(int32_t aOldIndex, int32_t aNewIndex) +{ + // Search for the entries which are in the current index, + // but not in the new one. + nsCOMPtr<nsISHEntry> originalSH; + GetEntryAtIndex(aOldIndex, false, getter_AddRefs(originalSH)); + nsCOMPtr<nsISHContainer> originalContainer = do_QueryInterface(originalSH); + AutoTArray<uint64_t, 16> toBeRemovedEntries; + if (originalContainer) { + nsTArray<uint64_t> originalDynDocShellIDs; + GetDynamicChildren(originalContainer, originalDynDocShellIDs, true); + if (originalDynDocShellIDs.Length()) { + nsCOMPtr<nsISHEntry> currentSH; + GetEntryAtIndex(aNewIndex, false, getter_AddRefs(currentSH)); + nsCOMPtr<nsISHContainer> newContainer = do_QueryInterface(currentSH); + if (newContainer) { + nsTArray<uint64_t> newDynDocShellIDs; + GetDynamicChildren(newContainer, newDynDocShellIDs, false); + for (uint32_t i = 0; i < originalDynDocShellIDs.Length(); ++i) { + if (!newDynDocShellIDs.Contains(originalDynDocShellIDs[i])) { + toBeRemovedEntries.AppendElement(originalDynDocShellIDs[i]); + } + } + } + } + } + if (toBeRemovedEntries.Length()) { + RemoveEntries(toBeRemovedEntries, aOldIndex); + } +} + +NS_IMETHODIMP +nsSHistory::UpdateIndex() +{ + // Update the actual index with the right value. + if (mIndex != mRequestedIndex && mRequestedIndex != -1) { + RemoveDynEntries(mIndex, mRequestedIndex); + mIndex = mRequestedIndex; + } + + mRequestedIndex = -1; + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::Stop(uint32_t aStopFlags) +{ + // Not implemented + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::GetDocument(nsIDOMDocument** aDocument) +{ + // Not implemented + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::GetCurrentURI(nsIURI** aResultURI) +{ + NS_ENSURE_ARG_POINTER(aResultURI); + nsresult rv; + + nsCOMPtr<nsISHEntry> currentEntry; + rv = GetEntryAtIndex(mIndex, false, getter_AddRefs(currentEntry)); + if (NS_FAILED(rv) && !currentEntry) { + return rv; + } + rv = currentEntry->GetURI(aResultURI); + return rv; +} + +NS_IMETHODIMP +nsSHistory::GetReferringURI(nsIURI** aURI) +{ + *aURI = nullptr; + // Not implemented + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::SetSessionHistory(nsISHistory* aSessionHistory) +{ + // Not implemented + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::GetSessionHistory(nsISHistory** aSessionHistory) +{ + // Not implemented + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::LoadURIWithOptions(const char16_t* aURI, + uint32_t aLoadFlags, + nsIURI* aReferringURI, + uint32_t aReferrerPolicy, + nsIInputStream* aPostStream, + nsIInputStream* aExtraHeaderStream, + nsIURI* aBaseURI) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::SetOriginAttributesBeforeLoading(JS::HandleValue aOriginAttributes) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::LoadURI(const char16_t* aURI, + uint32_t aLoadFlags, + nsIURI* aReferringURI, + nsIInputStream* aPostStream, + nsIInputStream* aExtraHeaderStream) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::GotoIndex(int32_t aGlobalIndex) +{ + // We provide abstraction of grouped session history for nsIWebNavigation + // functions, so the index passed in here is global index. + return LoadEntry(aGlobalIndex - mGlobalIndexOffset, nsIDocShellLoadInfo::loadHistory, + HIST_CMD_GOTOINDEX); +} + +nsresult +nsSHistory::LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType, + uint32_t aHistCmd) +{ + mRequestedIndex = -1; + if (aNewIndex < mIndex) { + return LoadEntry(aNewIndex - 1, aLoadType, aHistCmd); + } + if (aNewIndex > mIndex) { + return LoadEntry(aNewIndex + 1, aLoadType, aHistCmd); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsSHistory::LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd) +{ + if (!mRootDocShell) { + return NS_ERROR_FAILURE; + } + + if (aIndex < 0 || aIndex >= mLength) { + if (aIndex + mGlobalIndexOffset < 0) { + // The global index is negative. + return NS_ERROR_FAILURE; + } + + if (aIndex - mLength >= mEntriesInFollowingPartialHistories) { + // The global index exceeds max possible value. + return NS_ERROR_FAILURE; + } + + // The global index is valid. trigger cross browser navigation. + nsCOMPtr<nsIPartialSHistoryListener> listener = + do_QueryReferent(mPartialHistoryListener); + if (!listener) { + return NS_ERROR_FAILURE; + } + return listener->OnRequestCrossBrowserNavigation(aIndex + mGlobalIndexOffset); + } + + // Keep note of requested history index in mRequestedIndex. + mRequestedIndex = aIndex; + + nsCOMPtr<nsISHEntry> prevEntry; + GetEntryAtIndex(mIndex, false, getter_AddRefs(prevEntry)); + + nsCOMPtr<nsISHEntry> nextEntry; + GetEntryAtIndex(mRequestedIndex, false, getter_AddRefs(nextEntry)); + if (!nextEntry || !prevEntry) { + mRequestedIndex = -1; + return NS_ERROR_FAILURE; + } + + // Remember that this entry is getting loaded at this point in the sequence + nsCOMPtr<nsISHEntryInternal> entryInternal = do_QueryInterface(nextEntry); + + if (entryInternal) { + entryInternal->SetLastTouched(++gTouchCounter); + } + + // Send appropriate listener notifications + bool canNavigate = true; + // Get the uri for the entry we are about to visit + nsCOMPtr<nsIURI> nextURI; + nextEntry->GetURI(getter_AddRefs(nextURI)); + + if (aHistCmd == HIST_CMD_BACK) { + // We are going back one entry. Send GoBack notifications + NOTIFY_LISTENERS_CANCELABLE(OnHistoryGoBack, canNavigate, + (nextURI, &canNavigate)); + } else if (aHistCmd == HIST_CMD_FORWARD) { + // We are going forward. Send GoForward notification + NOTIFY_LISTENERS_CANCELABLE(OnHistoryGoForward, canNavigate, + (nextURI, &canNavigate)); + } else if (aHistCmd == HIST_CMD_GOTOINDEX) { + // We are going somewhere else. This is not reload either + NOTIFY_LISTENERS_CANCELABLE(OnHistoryGotoIndex, canNavigate, + (aIndex, nextURI, &canNavigate)); + } + + if (!canNavigate) { + // If the listener asked us not to proceed with + // the operation, simply return. + mRequestedIndex = -1; + return NS_OK; // XXX Maybe I can return some other error code? + } + + if (mRequestedIndex == mIndex) { + // Possibly a reload case + return InitiateLoad(nextEntry, mRootDocShell, aLoadType); + } + + // Going back or forward. + bool differenceFound = false; + nsresult rv = LoadDifferingEntries(prevEntry, nextEntry, mRootDocShell, + aLoadType, differenceFound); + if (!differenceFound) { + // We did not find any differences. Go further in the history. + return LoadNextPossibleEntry(aIndex, aLoadType, aHistCmd); + } + + return rv; +} + +nsresult +nsSHistory::LoadDifferingEntries(nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry, + nsIDocShell* aParent, long aLoadType, + bool& aDifferenceFound) +{ + if (!aPrevEntry || !aNextEntry || !aParent) { + return NS_ERROR_FAILURE; + } + + nsresult result = NS_OK; + uint32_t prevID, nextID; + + aPrevEntry->GetID(&prevID); + aNextEntry->GetID(&nextID); + + // Check the IDs to verify if the pages are different. + if (prevID != nextID) { + aDifferenceFound = true; + + // Set the Subframe flag if not navigating the root docshell. + aNextEntry->SetIsSubFrame(aParent != mRootDocShell); + return InitiateLoad(aNextEntry, aParent, aLoadType); + } + + // The entries are the same, so compare any child frames + int32_t pcnt = 0; + int32_t ncnt = 0; + int32_t dsCount = 0; + nsCOMPtr<nsISHContainer> prevContainer(do_QueryInterface(aPrevEntry)); + nsCOMPtr<nsISHContainer> nextContainer(do_QueryInterface(aNextEntry)); + + if (!prevContainer || !nextContainer) { + return NS_ERROR_FAILURE; + } + + prevContainer->GetChildCount(&pcnt); + nextContainer->GetChildCount(&ncnt); + aParent->GetChildCount(&dsCount); + + // Create an array for child docshells. + nsCOMArray<nsIDocShell> docshells; + for (int32_t i = 0; i < dsCount; ++i) { + nsCOMPtr<nsIDocShellTreeItem> treeItem; + aParent->GetChildAt(i, getter_AddRefs(treeItem)); + nsCOMPtr<nsIDocShell> shell = do_QueryInterface(treeItem); + if (shell) { + docshells.AppendElement(shell.forget()); + } + } + + // Search for something to load next. + for (int32_t i = 0; i < ncnt; ++i) { + // First get an entry which may cause a new page to be loaded. + nsCOMPtr<nsISHEntry> nChild; + nextContainer->GetChildAt(i, getter_AddRefs(nChild)); + if (!nChild) { + continue; + } + uint64_t docshellID = 0; + nChild->GetDocshellID(&docshellID); + + // Then find the associated docshell. + nsIDocShell* dsChild = nullptr; + int32_t count = docshells.Count(); + for (int32_t j = 0; j < count; ++j) { + uint64_t shellID = 0; + nsIDocShell* shell = docshells[j]; + shell->GetHistoryID(&shellID); + if (shellID == docshellID) { + dsChild = shell; + break; + } + } + if (!dsChild) { + continue; + } + + // Then look at the previous entries to see if there was + // an entry for the docshell. + nsCOMPtr<nsISHEntry> pChild; + for (int32_t k = 0; k < pcnt; ++k) { + nsCOMPtr<nsISHEntry> child; + prevContainer->GetChildAt(k, getter_AddRefs(child)); + if (child) { + uint64_t dID = 0; + child->GetDocshellID(&dID); + if (dID == docshellID) { + pChild = child; + break; + } + } + } + + // Finally recursively call this method. + // This will either load a new page to shell or some subshell or + // do nothing. + LoadDifferingEntries(pChild, nChild, dsChild, aLoadType, aDifferenceFound); + } + return result; +} + +nsresult +nsSHistory::InitiateLoad(nsISHEntry* aFrameEntry, nsIDocShell* aFrameDS, + long aLoadType) +{ + NS_ENSURE_STATE(aFrameDS && aFrameEntry); + + nsCOMPtr<nsIDocShellLoadInfo> loadInfo; + + /* Set the loadType in the SHEntry too to what was passed on. + * This will be passed on to child subframes later in nsDocShell, + * so that proper loadType is maintained through out a frameset + */ + aFrameEntry->SetLoadType(aLoadType); + aFrameDS->CreateLoadInfo(getter_AddRefs(loadInfo)); + + loadInfo->SetLoadType(aLoadType); + loadInfo->SetSHEntry(aFrameEntry); + + nsCOMPtr<nsIURI> originalURI; + aFrameEntry->GetOriginalURI(getter_AddRefs(originalURI)); + loadInfo->SetOriginalURI(originalURI); + + bool loadReplace; + aFrameEntry->GetLoadReplace(&loadReplace); + loadInfo->SetLoadReplace(loadReplace); + + nsCOMPtr<nsIURI> nextURI; + aFrameEntry->GetURI(getter_AddRefs(nextURI)); + // Time to initiate a document load + return aFrameDS->LoadURI(nextURI, loadInfo, + nsIWebNavigation::LOAD_FLAGS_NONE, false); + +} + +NS_IMETHODIMP +nsSHistory::SetRootDocShell(nsIDocShell* aDocShell) +{ + mRootDocShell = aDocShell; + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::GetSHistoryEnumerator(nsISimpleEnumerator** aEnumerator) +{ + NS_ENSURE_ARG_POINTER(aEnumerator); + RefPtr<nsSHEnumerator> iterator = new nsSHEnumerator(this); + iterator.forget(aEnumerator); + return NS_OK; +} + +NS_IMETHODIMP +nsSHistory::OnAttachGroupedSessionHistory(int32_t aOffset) +{ + NS_ENSURE_TRUE(!mIsPartial, NS_ERROR_UNEXPECTED); + NS_ENSURE_TRUE(aOffset >= 0, NS_ERROR_ILLEGAL_VALUE); + + mIsPartial = true; + mGlobalIndexOffset = aOffset; + + // The last attached history is always at the end of the group. + mEntriesInFollowingPartialHistories = 0; + + // Setting grouped history info may change canGoBack / canGoForward. + // Send a location change to update these values. + NS_DispatchToCurrentThread(NewRunnableMethod(static_cast<nsDocShell*>(mRootDocShell), + &nsDocShell::FireDummyOnLocationChange)); + return NS_OK; + +} + +nsSHEnumerator::nsSHEnumerator(nsSHistory* aSHistory) : mIndex(-1) +{ + mSHistory = aSHistory; +} + +nsSHEnumerator::~nsSHEnumerator() +{ + mSHistory = nullptr; +} + +NS_IMPL_ISUPPORTS(nsSHEnumerator, nsISimpleEnumerator) + +NS_IMETHODIMP +nsSHEnumerator::HasMoreElements(bool* aReturn) +{ + int32_t cnt; + *aReturn = false; + mSHistory->GetCount(&cnt); + if (mIndex >= -1 && mIndex < (cnt - 1)) { + *aReturn = true; + } + return NS_OK; +} + +NS_IMETHODIMP +nsSHEnumerator::GetNext(nsISupports** aItem) +{ + NS_ENSURE_ARG_POINTER(aItem); + int32_t cnt = 0; + + nsresult result = NS_ERROR_FAILURE; + mSHistory->GetCount(&cnt); + if (mIndex < (cnt - 1)) { + mIndex++; + nsCOMPtr<nsISHEntry> hEntry; + result = mSHistory->GetEntryAtIndex(mIndex, false, getter_AddRefs(hEntry)); + if (hEntry) { + result = CallQueryInterface(hEntry, aItem); + } + } + return result; +} diff --git a/docshell/shistory/nsSHistory.h b/docshell/shistory/nsSHistory.h new file mode 100644 index 000000000..cddc27269 --- /dev/null +++ b/docshell/shistory/nsSHistory.h @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSHistory_h +#define nsSHistory_h + +#include "nsCOMPtr.h" +#include "nsISHistory.h" +#include "nsISHistoryInternal.h" +#include "nsIWebNavigation.h" +#include "nsISimpleEnumerator.h" +#include "nsTObserverArray.h" +#include "nsWeakPtr.h" +#include "nsIPartialSHistoryListener.h" + +#include "prclist.h" + +class nsIDocShell; +class nsSHEnumerator; +class nsSHistoryObserver; +class nsISHEntry; +class nsISHTransaction; + +class nsSHistory final : public PRCList, + public nsISHistory, + public nsISHistoryInternal, + public nsIWebNavigation +{ +public: + nsSHistory(); + NS_DECL_ISUPPORTS + NS_DECL_NSISHISTORY + NS_DECL_NSISHISTORYINTERNAL + NS_DECL_NSIWEBNAVIGATION + + // One time initialization method called upon docshell module construction + static nsresult Startup(); + static void Shutdown(); + static void UpdatePrefs(); + + // Max number of total cached content viewers. If the pref + // browser.sessionhistory.max_total_viewers is negative, then + // this value is calculated based on the total amount of memory. + // Otherwise, it comes straight from the pref. + static uint32_t GetMaxTotalViewers() { return sHistoryMaxTotalViewers; } + +private: + virtual ~nsSHistory(); + friend class nsSHEnumerator; + friend class nsSHistoryObserver; + + // Could become part of nsIWebNavigation + NS_IMETHOD GetTransactionAtIndex(int32_t aIndex, nsISHTransaction** aResult); + nsresult LoadDifferingEntries(nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry, + nsIDocShell* aRootDocShell, long aLoadType, + bool& aDifferenceFound); + nsresult InitiateLoad(nsISHEntry* aFrameEntry, nsIDocShell* aFrameDS, + long aLoadType); + + NS_IMETHOD LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd); + +#ifdef DEBUG + nsresult PrintHistory(); +#endif + + // Evict content viewers in this window which don't lie in the "safe" range + // around aIndex. + void EvictOutOfRangeWindowContentViewers(int32_t aIndex); + static void GloballyEvictContentViewers(); + static void GloballyEvictAllContentViewers(); + + // Calculates a max number of total + // content viewers to cache, based on amount of total memory + static uint32_t CalcMaxTotalViewers(); + + void RemoveDynEntries(int32_t aOldIndex, int32_t aNewIndex); + + nsresult LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType, + uint32_t aHistCmd); + + // aIndex is the index of the transaction which may be removed. + // If aKeepNext is true, aIndex is compared to aIndex + 1, + // otherwise comparison is done to aIndex - 1. + bool RemoveDuplicate(int32_t aIndex, bool aKeepNext); + + nsCOMPtr<nsISHTransaction> mListRoot; + int32_t mIndex; + int32_t mLength; + int32_t mRequestedIndex; + + // Set to true if attached to a grouped session history. + bool mIsPartial; + + // The number of entries before this session history object. + int32_t mGlobalIndexOffset; + + // The number of entries after this session history object. + int32_t mEntriesInFollowingPartialHistories; + + // Session History listeners + nsAutoTObserverArray<nsWeakPtr, 2> mListeners; + + // Partial session history listener + nsWeakPtr mPartialHistoryListener; + + // Weak reference. Do not refcount this. + nsIDocShell* mRootDocShell; + + // Max viewers allowed total, across all SHistory objects + static int32_t sHistoryMaxTotalViewers; +}; + +class nsSHEnumerator : public nsISimpleEnumerator +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATOR + + explicit nsSHEnumerator(nsSHistory* aHistory); + +protected: + friend class nsSHistory; + virtual ~nsSHEnumerator(); + +private: + int32_t mIndex; + nsSHistory* mSHistory; +}; + +#endif /* nsSHistory */ diff --git a/docshell/test/browser/browser.ini b/docshell/test/browser/browser.ini new file mode 100644 index 000000000..9211092a4 --- /dev/null +++ b/docshell/test/browser/browser.ini @@ -0,0 +1,93 @@ +[DEFAULT] +support-files = + favicon_bug655270.ico + file_bug234628-1-child.html + file_bug234628-1.html + file_bug234628-10-child.xhtml + file_bug234628-10.html + file_bug234628-11-child.xhtml + file_bug234628-11-child.xhtml^headers^ + file_bug234628-11.html + file_bug234628-2-child.html + file_bug234628-2.html + file_bug234628-3-child.html + file_bug234628-3.html + file_bug234628-4-child.html + file_bug234628-4.html + file_bug234628-5-child.html + file_bug234628-5.html + file_bug234628-6-child.html + file_bug234628-6-child.html^headers^ + file_bug234628-6.html + file_bug234628-7-child.html + file_bug234628-7-child.html^headers^ + file_bug234628-7.html + file_bug234628-8-child.html + file_bug234628-8.html + file_bug234628-9-child.html + file_bug234628-9.html + file_bug420605.html + file_bug422543_script.js + file_bug503832.html + file_bug655270.html + file_bug670318.html + file_bug852909.pdf + file_bug852909.png + file_bug1046022.html + file_bug1206879.html + file_multiple_pushState.html + print_postdata.sjs + test-form_sjis.html + timelineMarkers-04.html + browser_timelineMarkers-frame-02.js + browser_timelineMarkers-frame-03.js + browser_timelineMarkers-frame-04.js + browser_timelineMarkers-frame-05.js + head.js + frame-head.js + +[browser_bug1206879.js] +[browser_bug1309900_crossProcessHistoryNavigation.js] +[browser_bug134911.js] +[browser_bug234628-1.js] +[browser_bug234628-10.js] +[browser_bug234628-11.js] +[browser_bug234628-2.js] +[browser_bug234628-3.js] +[browser_bug234628-4.js] +[browser_bug234628-5.js] +[browser_bug234628-6.js] +[browser_bug234628-7.js] +[browser_bug234628-8.js] +[browser_bug234628-9.js] +[browser_bug349769.js] +[browser_bug388121-1.js] +[browser_bug388121-2.js] +[browser_bug420605.js] +[browser_bug422543.js] +[browser_bug441169.js] +[browser_bug503832.js] +[browser_bug554155.js] +[browser_bug655270.js] +[browser_bug655273.js] +[browser_bug670318.js] +[browser_bug673467.js] +[browser_bug852909.js] +[browser_bug92473.js] +[browser_uriFixupIntegration.js] +[browser_uriFixupAlternateRedirects.js] +support-files = + redirect_to_example.sjs +[browser_loadDisallowInherit.js] +[browser_loadURI.js] +[browser_multiple_pushState.js] +[browser_onbeforeunload_navigation.js] +[browser_search_notification.js] +[browser_tab_touch_events.js] +[browser_timelineMarkers-01.js] +[browser_timelineMarkers-02.js] +skip-if = true # Bug 1220415 +[browser_timelineMarkers-03.js] +[browser_timelineMarkers-04.js] +[browser_timelineMarkers-05.js] +[browser_ua_emulation.js] diff --git a/docshell/test/browser/browser_bug1206879.js b/docshell/test/browser/browser_bug1206879.js new file mode 100644 index 000000000..1276f6428 --- /dev/null +++ b/docshell/test/browser/browser_bug1206879.js @@ -0,0 +1,31 @@ +add_task(function*() { + let url = getRootDirectory(gTestPath) + "file_bug1206879.html"; + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url, true); + + let numLocationChanges = 0; + + let listener = { + onLocationChange: function(browser, wp, request, uri, flags) { + if (browser != tab.linkedBrowser) { + return; + } + info("onLocationChange: " + uri.spec); + numLocationChanges++; + this.resolve(); + } + }; + let locationPromise = new Promise((resolve, reject) => { + listener.resolve = resolve; + gBrowser.addTabsProgressListener(listener); + }); + yield ContentTask.spawn(tab.linkedBrowser, {}, function() { + content.frames[0].history.pushState(null, null, "foo"); + }); + + yield locationPromise; + + gBrowser.removeTab(tab); + gBrowser.removeTabsProgressListener(listener); + is(numLocationChanges, 1, + "pushState with a different URI should cause a LocationChange event."); +}); diff --git a/docshell/test/browser/browser_bug1309900_crossProcessHistoryNavigation.js b/docshell/test/browser/browser_bug1309900_crossProcessHistoryNavigation.js new file mode 100644 index 000000000..a4b4645e8 --- /dev/null +++ b/docshell/test/browser/browser_bug1309900_crossProcessHistoryNavigation.js @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(function* runTests() { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:about"); + + registerCleanupFunction(function* () { + gBrowser.removeTab(tab); + }); + + let browser = tab.linkedBrowser; + + browser.loadURI("about:accounts"); + let href = yield BrowserTestUtils.browserLoaded(browser); + is(href, "about:accounts", "Check about:accounts loaded"); + + // Using a dummy onunload listener to disable the bfcache as that can prevent + // the test browser load detection mechanism from working. + browser.loadURI("data:text/html,<body%20onunload=''><iframe></iframe></body>"); + href = yield BrowserTestUtils.browserLoaded(browser); + is(href, "data:text/html,<body%20onunload=''><iframe></iframe></body>", + "Check data URL loaded"); + + browser.goBack(); + href = yield BrowserTestUtils.browserLoaded(browser); + is(href, "about:accounts", "Check we've gone back to about:accounts"); + + browser.goForward(); + href = yield BrowserTestUtils.browserLoaded(browser); + is(href, "data:text/html,<body%20onunload=''><iframe></iframe></body>", + "Check we've gone forward to data URL"); +}); diff --git a/docshell/test/browser/browser_bug134911.js b/docshell/test/browser/browser_bug134911.js new file mode 100644 index 000000000..aa54cfd1a --- /dev/null +++ b/docshell/test/browser/browser_bug134911.js @@ -0,0 +1,41 @@ +const TEXT = { + /* The test text decoded correctly as Shift_JIS */ + rightText: "\u30E6\u30CB\u30B3\u30FC\u30C9\u306F\u3001\u3059\u3079\u3066\u306E\u6587\u5B57\u306B\u56FA\u6709\u306E\u756A\u53F7\u3092\u4ED8\u4E0E\u3057\u307E\u3059", + + enteredText1: "The quick brown fox jumps over the lazy dog", + enteredText2: "\u03BE\u03B5\u03C3\u03BA\u03B5\u03C0\u03AC\u03B6\u03C9\u0020\u03C4\u1F74\u03BD\u0020\u03C8\u03C5\u03C7\u03BF\u03C6\u03B8\u03CC\u03C1\u03B1\u0020\u03B2\u03B4\u03B5\u03BB\u03C5\u03B3\u03BC\u03AF\u03B1", +}; + +function test() { + waitForExplicitFinish(); + + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + gBrowser.selectedTab = gBrowser.addTab(rootDir + "test-form_sjis.html"); + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(afterOpen); +} + +function afterOpen() { + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(afterChangeCharset); + + ContentTask.spawn(gBrowser.selectedBrowser, TEXT, function(TEXT) { + content.document.getElementById("testtextarea").value = TEXT.enteredText1; + content.document.getElementById("testinput").value = TEXT.enteredText2; + }).then(() => { + /* Force the page encoding to Shift_JIS */ + BrowserSetForcedCharacterSet("Shift_JIS"); + }); +} + +function afterChangeCharset() { + ContentTask.spawn(gBrowser.selectedBrowser, TEXT, function(TEXT) { + is(content.document.getElementById("testpar").innerHTML, TEXT.rightText, + "encoding successfully changed"); + is(content.document.getElementById("testtextarea").value, TEXT.enteredText1, + "text preserved in <textarea>"); + is(content.document.getElementById("testinput").value, TEXT.enteredText2, + "text preserved in <input>"); + }).then(() => { + gBrowser.removeCurrentTab(); + finish(); + }); +} diff --git a/docshell/test/browser/browser_bug234628-1.js b/docshell/test/browser/browser_bug234628-1.js new file mode 100644 index 000000000..bfb4d3708 --- /dev/null +++ b/docshell/test/browser/browser_bug234628-1.js @@ -0,0 +1,18 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest(rootDir + "file_bug234628-1.html", afterOpen, "windows-1251", afterChangeCharset); +} + +function afterOpen() { + is(content.document.documentElement.textContent.indexOf('\u20AC'), 129, "Parent doc should be windows-1252 initially"); + + is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 85, "Child doc should be windows-1252 initially"); +} + +function afterChangeCharset() { + is(content.document.documentElement.textContent.indexOf('\u0402'), 129, "Parent doc should decode as windows-1251 subsequently"); + is(content.frames[0].document.documentElement.textContent.indexOf('\u0402'), 85, "Child doc should decode as windows-1251 subsequently"); + + is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently"); + is(content.frames[0].document.characterSet, "windows-1251", "Child doc should report windows-1251 subsequently"); +} diff --git a/docshell/test/browser/browser_bug234628-10.js b/docshell/test/browser/browser_bug234628-10.js new file mode 100644 index 000000000..7dbe805c4 --- /dev/null +++ b/docshell/test/browser/browser_bug234628-10.js @@ -0,0 +1,18 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest(rootDir + "file_bug234628-10.html", afterOpen, "windows-1251", afterChangeCharset); +} + +function afterOpen() { + is(content.document.documentElement.textContent.indexOf('\u20AC'), 151, "Parent doc should be windows-1252 initially"); + + is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 71, "Child doc should be utf-8 initially"); +} + +function afterChangeCharset() { + is(content.document.documentElement.textContent.indexOf('\u0402'), 151, "Parent doc should decode as windows-1251 subsequently"); + is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 71, "Child doc should decode as utf-8 subsequently"); + + is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently"); + is(content.frames[0].document.characterSet, "UTF-8", "Child doc should report UTF-8 subsequently"); +} diff --git a/docshell/test/browser/browser_bug234628-11.js b/docshell/test/browser/browser_bug234628-11.js new file mode 100644 index 000000000..fc597f5c0 --- /dev/null +++ b/docshell/test/browser/browser_bug234628-11.js @@ -0,0 +1,18 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest(rootDir + "file_bug234628-11.html", afterOpen, "windows-1251", afterChangeCharset); +} + +function afterOpen() { + is(content.document.documentElement.textContent.indexOf('\u20AC'), 193, "Parent doc should be windows-1252 initially"); + + is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 107, "Child doc should be utf-8 initially"); +} + +function afterChangeCharset() { + is(content.document.documentElement.textContent.indexOf('\u0402'), 193, "Parent doc should decode as windows-1251 subsequently"); + is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 107, "Child doc should decode as utf-8 subsequently"); + + is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently"); + is(content.frames[0].document.characterSet, "UTF-8", "Child doc should report UTF-8 subsequently"); +} diff --git a/docshell/test/browser/browser_bug234628-2.js b/docshell/test/browser/browser_bug234628-2.js new file mode 100644 index 000000000..638e223c0 --- /dev/null +++ b/docshell/test/browser/browser_bug234628-2.js @@ -0,0 +1,18 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest(rootDir + "file_bug234628-2.html", afterOpen, "windows-1251", afterChangeCharset); +} + +function afterOpen() { + is(content.document.documentElement.textContent.indexOf('\u20AC'), 129, "Parent doc should be windows-1252 initially"); + + is(content.frames[0].document.documentElement.textContent.indexOf('\u00E2\u201A\u00AC'), 78, "Child doc should be windows-1252 initially"); +} + +function afterChangeCharset() { + is(content.document.documentElement.textContent.indexOf('\u0402'), 129, "Parent doc should decode as windows-1251 subsequently"); + is(content.frames[0].document.documentElement.textContent.indexOf('\u0432\u201A\u00AC'), 78, "Child doc should decode as windows-1251 subsequently"); + + is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently"); + is(content.frames[0].document.characterSet, "windows-1251", "Child doc should report windows-1251 subsequently"); +} diff --git a/docshell/test/browser/browser_bug234628-3.js b/docshell/test/browser/browser_bug234628-3.js new file mode 100644 index 000000000..51aca7ec8 --- /dev/null +++ b/docshell/test/browser/browser_bug234628-3.js @@ -0,0 +1,18 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest(rootDir + "file_bug234628-3.html", afterOpen, "windows-1251", afterChangeCharset); +} + +function afterOpen() { + is(content.document.documentElement.textContent.indexOf('\u20AC'), 118, "Parent doc should be windows-1252 initially"); + + is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 73, "Child doc should be utf-8 initially"); +} + +function afterChangeCharset() { + is(content.document.documentElement.textContent.indexOf('\u0402'), 118, "Parent doc should decode as windows-1251 subsequently"); + is(content.frames[0].document.documentElement.textContent.indexOf('\u0432\u201A\u00AC'), 73, "Child doc should decode as windows-1251 subsequently"); + + is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently"); + is(content.frames[0].document.characterSet, "windows-1251", "Child doc should report windows-1251 subsequently"); +} diff --git a/docshell/test/browser/browser_bug234628-4.js b/docshell/test/browser/browser_bug234628-4.js new file mode 100644 index 000000000..fe7f1545f --- /dev/null +++ b/docshell/test/browser/browser_bug234628-4.js @@ -0,0 +1,18 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest(rootDir + "file_bug234628-4.html", afterOpen, "windows-1251", afterChangeCharset); +} + +function afterOpen() { + is(content.document.documentElement.textContent.indexOf('\u20AC'), 132, "Parent doc should be windows-1252 initially"); + + is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 79, "Child doc should be utf-8 initially"); +} + +function afterChangeCharset() { + is(content.document.documentElement.textContent.indexOf('\u0402'), 132, "Parent doc should decode as windows-1251 subsequently"); + is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 79, "Child doc should decode as utf-8 subsequently"); + + is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently"); + is(content.frames[0].document.characterSet, "UTF-8", "Child doc should report UTF-8 subsequently"); +} diff --git a/docshell/test/browser/browser_bug234628-5.js b/docshell/test/browser/browser_bug234628-5.js new file mode 100644 index 000000000..a37f14b8d --- /dev/null +++ b/docshell/test/browser/browser_bug234628-5.js @@ -0,0 +1,18 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest(rootDir + "file_bug234628-5.html", afterOpen, "windows-1251", afterChangeCharset); +} + +function afterOpen() { + is(content.document.documentElement.textContent.indexOf('\u20AC'), 146, "Parent doc should be windows-1252 initially"); + + is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 87, "Child doc should be utf-16 initially"); +} + +function afterChangeCharset() { + is(content.document.documentElement.textContent.indexOf('\u0402'), 146, "Parent doc should decode as windows-1251 subsequently"); + is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 87, "Child doc should decode as utf-16 subsequently"); + + is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently"); + is(content.frames[0].document.characterSet, "UTF-16LE", "Child doc should report UTF-16LE subsequently"); +} diff --git a/docshell/test/browser/browser_bug234628-6.js b/docshell/test/browser/browser_bug234628-6.js new file mode 100644 index 000000000..7d2d92531 --- /dev/null +++ b/docshell/test/browser/browser_bug234628-6.js @@ -0,0 +1,18 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest(rootDir + "file_bug234628-6.html", afterOpen, "windows-1251", afterChangeCharset); +} + +function afterOpen() { + is(content.document.documentElement.textContent.indexOf('\u20AC'), 190, "Parent doc should be windows-1252 initially"); + + is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 109, "Child doc should be utf-16 initially"); +} + +function afterChangeCharset() { + is(content.document.documentElement.textContent.indexOf('\u0402'), 190, "Parent doc should decode as windows-1251 subsequently"); + is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 109, "Child doc should decode as utf-16 subsequently"); + + is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently"); + is(content.frames[0].document.characterSet, "UTF-16BE", "Child doc should report UTF-16 subsequently"); +} diff --git a/docshell/test/browser/browser_bug234628-7.js b/docshell/test/browser/browser_bug234628-7.js new file mode 100644 index 000000000..0bafd67e7 --- /dev/null +++ b/docshell/test/browser/browser_bug234628-7.js @@ -0,0 +1,18 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest(rootDir + "file_bug234628-7.html", afterOpen, "windows-1251", afterChangeCharset); +} + +function afterOpen() { + is(content.document.documentElement.textContent.indexOf('\u20AC'), 188, "Parent doc should be windows-1252 initially"); + + is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 107, "Child doc should be utf-8 initially"); +} + +function afterChangeCharset() { + is(content.document.documentElement.textContent.indexOf('\u0402'), 188, "Parent doc should decode as windows-1251 subsequently"); + is(content.frames[0].document.documentElement.textContent.indexOf('\u0432\u201A\u00AC'), 107, "Child doc should decode as windows-1251 subsequently"); + + is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently"); + is(content.frames[0].document.characterSet, "windows-1251", "Child doc should report windows-1251 subsequently"); +} diff --git a/docshell/test/browser/browser_bug234628-8.js b/docshell/test/browser/browser_bug234628-8.js new file mode 100644 index 000000000..40a0c6946 --- /dev/null +++ b/docshell/test/browser/browser_bug234628-8.js @@ -0,0 +1,11 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest(rootDir + "file_bug234628-8.html", afterOpen); +} + +function afterOpen() { + is(content.document.documentElement.textContent.indexOf('\u0402'), 156, "Parent doc should be windows-1251"); + + is(content.frames[0].document.documentElement.textContent.indexOf('\u0402'), 99, "Child doc should be windows-1251"); +} + diff --git a/docshell/test/browser/browser_bug234628-9.js b/docshell/test/browser/browser_bug234628-9.js new file mode 100644 index 000000000..72da65e80 --- /dev/null +++ b/docshell/test/browser/browser_bug234628-9.js @@ -0,0 +1,11 @@ +function test() { + var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + runCharsetTest(rootDir + "file_bug234628-9.html", afterOpen); +} + +function afterOpen() { + is(content.document.documentElement.textContent.indexOf('\u20AC'), 145, "Parent doc should be UTF-16"); + + is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 96, "Child doc should be windows-1252"); +} + diff --git a/docshell/test/browser/browser_bug349769.js b/docshell/test/browser/browser_bug349769.js new file mode 100644 index 000000000..76d4e0f28 --- /dev/null +++ b/docshell/test/browser/browser_bug349769.js @@ -0,0 +1,47 @@ +add_task(function* test() { + const secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager); + const uris = [undefined, "about:blank"]; + + function checkContentProcess(uri) { + yield ContentTask.spawn(newBrowser, uri, function* (uri) { + var prin = content.document.nodePrincipal; + Assert.notEqual(prin, null, "Loaded principal must not be null when adding " + uri); + Assert.notEqual(prin, undefined, "Loaded principal must not be undefined when loading " + uri); + + const secMan = Cc["@mozilla.org/scriptsecuritymanager;1"] + .getService(Ci.nsIScriptSecurityManager); + Assert.equal(secMan.isSystemPrincipal(prin), false, + "Loaded principal must not be system when loading " + uri); + }); + } + + for (var uri of uris) { + yield BrowserTestUtils.withNewTab({ gBrowser }, function* (newBrowser) { + yield BrowserTestUtils.loadURI(newBrowser, uri); + + var prin = newBrowser.contentPrincipal; + isnot(prin, null, "Forced principal must not be null when loading " + uri); + isnot(prin, undefined, + "Forced principal must not be undefined when loading " + uri); + is(secMan.isSystemPrincipal(prin), false, + "Forced principal must not be system when loading " + uri); + + // Belt-and-suspenders e10s check: make sure that the same checks hold + // true in the content process. + checkContentProcess(uri); + + yield BrowserTestUtils.browserLoaded(newBrowser); + + prin = newBrowser.contentPrincipal; + isnot(prin, null, "Loaded principal must not be null when adding " + uri); + isnot(prin, undefined, "Loaded principal must not be undefined when loading " + uri); + is(secMan.isSystemPrincipal(prin), false, + "Loaded principal must not be system when loading " + uri); + + // Belt-and-suspenders e10s check: make sure that the same checks hold + // true in the content process. + checkContentProcess(uri); + }); + } +}); + diff --git a/docshell/test/browser/browser_bug388121-1.js b/docshell/test/browser/browser_bug388121-1.js new file mode 100644 index 000000000..7da617594 --- /dev/null +++ b/docshell/test/browser/browser_bug388121-1.js @@ -0,0 +1,15 @@ +add_task(function* test() { + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* (newBrowser) { + yield ContentTask.spawn(newBrowser, null, function* () { + var prin = content.document.nodePrincipal; + Assert.notEqual(prin, null, "Loaded principal must not be null"); + Assert.notEqual(prin, undefined, "Loaded principal must not be undefined"); + + const secMan = Cc["@mozilla.org/scriptsecuritymanager;1"] + .getService(Ci.nsIScriptSecurityManager); + Assert.equal(secMan.isSystemPrincipal(prin), false, + "Loaded principal must not be system"); + }); + }); +}); + diff --git a/docshell/test/browser/browser_bug388121-2.js b/docshell/test/browser/browser_bug388121-2.js new file mode 100644 index 000000000..6c050d971 --- /dev/null +++ b/docshell/test/browser/browser_bug388121-2.js @@ -0,0 +1,58 @@ +function test() { + waitForExplicitFinish(); + + var w; + const secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager); + var iteration = 1; + const uris = ["", "about:blank"]; + var uri; + var origDoc; + + function testLoad() { + if (w.document == origDoc) { + // Go back to polling + setTimeout(testLoad, 10); + return; + } + var prin = w.document.nodePrincipal; + isnot(prin, null, "Loaded principal must not be null when adding " + uri); + isnot(prin, undefined, "Loaded principal must not be undefined when loading " + uri); + is(secMan.isSystemPrincipal(prin), false, + "Loaded principal must not be system when loading " + uri); + w.close(); + + if (iteration == uris.length) { + finish(); + } else { + ++iteration; + doTest(); + } + } + + function doTest() { + uri = uris[iteration - 1]; + w = window.open(uri, "_blank", "width=10,height=10"); + var prin = w.document.nodePrincipal; + if (!uri) { + uri = undefined; + } + isnot(prin, null, "Forced principal must not be null when loading " + uri); + isnot(prin, undefined, + "Forced principal must not be undefined when loading " + uri); + is(secMan.isSystemPrincipal(prin), false, + "Forced principal must not be system when loading " + uri); + if (uri == undefined) { + // No actual load here, so just move along. + w.close(); + ++iteration; + doTest(); + } else { + origDoc = w.document; + // Need to poll, because load listeners on the content window won't + // survive the load. + setTimeout(testLoad, 10); + } + } + + doTest(); +} diff --git a/docshell/test/browser/browser_bug420605.js b/docshell/test/browser/browser_bug420605.js new file mode 100644 index 000000000..f07d45e93 --- /dev/null +++ b/docshell/test/browser/browser_bug420605.js @@ -0,0 +1,122 @@ +/* Test for Bug 420605 + * https://bugzilla.mozilla.org/show_bug.cgi?id=420605 + */ + +function test() { + waitForExplicitFinish(); + + var pageurl = "http://mochi.test:8888/browser/docshell/test/browser/file_bug420605.html"; + var fragmenturl = "http://mochi.test:8888/browser/docshell/test/browser/file_bug420605.html#firefox"; + + var historyService = Cc["@mozilla.org/browser/nav-history-service;1"] + .getService(Ci.nsINavHistoryService); + + /* Queries nsINavHistoryService and returns a single history entry + * for a given URI */ + function getNavHistoryEntry(aURI) { + var options = historyService.getNewQueryOptions(); + options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY; + options.maxResults = 1; + + var query = historyService.getNewQuery(); + query.uri = aURI; + + var result = historyService.executeQuery(query, options); + result.root.containerOpen = true; + + if (!result.root.childCount) { + return null; + } + return result.root.getChild(0); + } + + // We'll save the favicon URL of the orignal page here and check that the + // page with a hash has the same favicon. + var originalFavicon; + + // Control flow in this test is a bit complicated. + // + // When the page loads, onPageLoad (the DOMContentLoaded handler) and + // historyObserver::onPageChanged are both called, in some order. Once + // they've both run, we click a fragment link in the content page + // (clickLinkIfReady), which should trigger another onPageChanged event, + // this time for the fragment's URL. + + var _clickLinkTimes = 0; + function clickLinkIfReady() { + _clickLinkTimes++; + if (_clickLinkTimes == 2) { + BrowserTestUtils.synthesizeMouseAtCenter('#firefox-link', {}, + gBrowser.selectedBrowser); + } + } + + /* Global history observer that triggers for the two test URLs above. */ + var historyObserver = { + onBeginUpdateBatch: function() {}, + onEndUpdateBatch: function() {}, + onVisit: function(aURI, aVisitID, aTime, aSessionId, aReferringId, + aTransitionType, _added) {}, + onTitleChanged: function(aURI, aPageTitle) {}, + onDeleteURI: function(aURI) {}, + onClearHistory: function() {}, + onPageChanged: function(aURI, aWhat, aValue) { + if (aWhat != Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) { + return; + } + aURI = aURI.spec; + switch (aURI) { + case pageurl: + ok(aValue, "Favicon value is not null for page without fragment."); + originalFavicon = aValue; + + // Now that the favicon has loaded, click on fragment link. + // This should trigger the |case fragmenturl| below. + clickLinkIfReady(); + + return; + case fragmenturl: + // If the fragment URL's favicon isn't set, this branch won't + // be called and the test will time out. + + is(aValue, originalFavicon, "New favicon should be same as original favicon."); + + // Let's explicitly check that we can get the favicon + // from nsINavHistoryService now. + let info = getNavHistoryEntry(makeURI(aURI)); + ok(info, "There must be a history entry for the fragment."); + ok(info.icon, "The history entry must have an associated favicon."); + historyService.removeObserver(historyObserver, false); + gBrowser.removeCurrentTab(); + finish(); + } + }, + onPageExpired: function(aURI, aVisitTime, aWholeEntry) {}, + QueryInterface: function(iid) { + if (iid.equals(Ci.nsINavHistoryObserver) || + iid.equals(Ci.nsISupports)) { + return this; + } + throw Cr.NS_ERROR_NO_INTERFACE; + } + }; + historyService.addObserver(historyObserver, false); + + function onPageLoad() { + gBrowser.selectedBrowser + .removeEventListener("DOMContentLoaded", arguments.callee, true); + clickLinkIfReady(); + } + + // Make sure neither of the test pages haven't been loaded before. + var info = getNavHistoryEntry(makeURI(pageurl)); + ok(!info, "The test page must not have been visited already."); + info = getNavHistoryEntry(makeURI(fragmenturl)); + ok(!info, "The fragment test page must not have been visited already."); + + // Now open the test page in a new tab. + gBrowser.selectedTab = gBrowser.addTab(); + gBrowser.selectedBrowser.addEventListener( + "DOMContentLoaded", onPageLoad, true); + content.location = pageurl; +} diff --git a/docshell/test/browser/browser_bug422543.js b/docshell/test/browser/browser_bug422543.js new file mode 100644 index 000000000..0655a17b7 --- /dev/null +++ b/docshell/test/browser/browser_bug422543.js @@ -0,0 +1,116 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(function* runTests() { + yield setup(); + let browser = gBrowser.selectedBrowser; + // Now that we're set up, initialize our frame script. + yield checkListeners("initial", "listeners initialized"); + + // Check if all history listeners are always notified. + info("# part 1"); + yield whenPageShown(browser, () => browser.loadURI("http://www.example.com/")); + yield checkListeners("newentry", "shistory has a new entry"); + ok(browser.canGoBack, "we can go back"); + + yield whenPageShown(browser, () => browser.goBack()); + yield checkListeners("goback", "back to the first shentry"); + ok(browser.canGoForward, "we can go forward"); + + yield whenPageShown(browser, () => browser.goForward()); + yield checkListeners("goforward", "forward to the second shentry"); + + yield whenPageShown(browser, () => browser.reload()); + yield checkListeners("reload", "current shentry reloaded"); + + yield whenPageShown(browser, () => browser.gotoIndex(0)); + yield checkListeners("gotoindex", "back to the first index"); + + // Check nsISHistoryInternal.notifyOnHistoryReload + info("# part 2"); + ok((yield notifyReload()), "reloading has not been canceled"); + yield checkListeners("reload", "saw the reload notification"); + + // Let the first listener cancel the reload action. + info("# part 3"); + yield resetListeners(); + yield setListenerRetval(0, false); + ok(!(yield notifyReload()), "reloading has been canceled"); + yield checkListeners("reload", "saw the reload notification"); + + // Let both listeners cancel the reload action. + info("# part 4"); + yield resetListeners(); + yield setListenerRetval(1, false); + ok(!(yield notifyReload()), "reloading has been canceled"); + yield checkListeners("reload", "saw the reload notification"); + + // Let the second listener cancel the reload action. + info("# part 5"); + yield resetListeners(); + yield setListenerRetval(0, true); + ok(!(yield notifyReload()), "reloading has been canceled"); + yield checkListeners("reload", "saw the reload notification"); +}); + +function listenOnce(message, arg = {}) { + return new Promise(resolve => { + let mm = gBrowser.selectedBrowser.messageManager; + mm.addMessageListener(message + ":return", function listener(msg) { + mm.removeMessageListener(message + ":return", listener); + resolve(msg.data); + }); + + mm.sendAsyncMessage(message, arg); + }); +} + +function checkListeners(aLast, aMessage) { + return listenOnce("bug422543:getListenerStatus").then((listenerStatuses) => { + is(listenerStatuses[0], aLast, aMessage); + is(listenerStatuses[1], aLast, aMessage); + }); +} + +function resetListeners() { + return listenOnce("bug422543:resetListeners"); +} + +function notifyReload() { + return listenOnce("bug422543:notifyReload").then(({ rval }) => { + return rval; + }); +} + +function setListenerRetval(num, val) { + return listenOnce("bug422543:setRetval", { num, val }); +} + +function setup() { + return BrowserTestUtils.openNewForegroundTab(gBrowser, + "http://mochi.test:8888") + .then(function (tab) { + let browser = tab.linkedBrowser; + registerCleanupFunction(function* () { + yield listenOnce("bug422543:cleanup"); + gBrowser.removeTab(tab); + }); + + browser.messageManager + .loadFrameScript(getRootDirectory(gTestPath) + "file_bug422543_script.js", false); + }); +} + +function whenPageShown(aBrowser, aNavigation) { + let listener = ContentTask.spawn(aBrowser, null, function () { + return new Promise(resolve => { + addEventListener("pageshow", function onLoad() { + removeEventListener("pageshow", onLoad, true); + resolve(); + }, true); + }); + }); + + aNavigation(); + return listener; +} diff --git a/docshell/test/browser/browser_bug441169.js b/docshell/test/browser/browser_bug441169.js new file mode 100644 index 000000000..015115793 --- /dev/null +++ b/docshell/test/browser/browser_bug441169.js @@ -0,0 +1,37 @@ +/* Make sure that netError won't allow HTML injection through badcert parameters. See bug 441169. */ +var newBrowser + +function task() { + let resolve; + let promise = new Promise(r => { resolve = r; }); + + addEventListener("DOMContentLoaded", checkPage, false); + + function checkPage(event) { + if (event.target != content.document) { + return; + } + removeEventListener("DOMContentLoaded", checkPage, false); + + is(content.document.getElementById("test_span"), null, "Error message should not be parsed as HTML, and hence shouldn't include the 'test_span' element."); + resolve(); + } + + var chromeURL = "about:neterror?e=nssBadCert&u=https%3A//test.kuix.de/&c=UTF-8&d=This%20sentence%20should%20not%20be%20parsed%20to%20include%20a%20%3Cspan%20id=%22test_span%22%3Enamed%3C/span%3E%20span%20tag.%0A%0AThe%20certificate%20is%20only%20valid%20for%20%3Ca%20id=%22cert_domain_link%22%20title=%22kuix.de%22%3Ekuix.de%3C/a%3E%0A%0A(Error%20code%3A%20ssl_error_bad_cert_domain)"; + content.location = chromeURL; + + return promise; +} + +function test() { + waitForExplicitFinish(); + + var newTab = gBrowser.addTab(); + gBrowser.selectedTab = newTab; + newBrowser = gBrowser.getBrowserForTab(newTab); + + ContentTask.spawn(newBrowser, null, task).then(() => { + gBrowser.removeCurrentTab(); + finish(); + }); +} diff --git a/docshell/test/browser/browser_bug503832.js b/docshell/test/browser/browser_bug503832.js new file mode 100644 index 000000000..66904cbae --- /dev/null +++ b/docshell/test/browser/browser_bug503832.js @@ -0,0 +1,87 @@ +/* Test for Bug 503832 + * https://bugzilla.mozilla.org/show_bug.cgi?id=503832 + */ + +add_task(function* () { + var pagetitle = "Page Title for Bug 503832"; + var pageurl = "http://mochi.test:8888/browser/docshell/test/browser/file_bug503832.html"; + var fragmenturl = "http://mochi.test:8888/browser/docshell/test/browser/file_bug503832.html#firefox"; + + var historyService = Cc["@mozilla.org/browser/nav-history-service;1"] + .getService(Ci.nsINavHistoryService); + + let fragmentPromise = new Promise(resolve => { + /* Global history observer that triggers for the two test URLs above. */ + var historyObserver = { + onBeginUpdateBatch: function() {}, + onEndUpdateBatch: function() {}, + onVisit: function(aURI, aVisitID, aTime, aSessionId, aReferringId, + aTransitionType, _added) {}, + onTitleChanged: function(aURI, aPageTitle) { + aURI = aURI.spec; + switch (aURI) { + case pageurl: + is(aPageTitle, pagetitle, "Correct page title for " + aURI); + return; + case fragmenturl: + is(aPageTitle, pagetitle, "Correct page title for " + aURI); + // If titles for fragment URLs aren't set, this code + // branch won't be called and the test will timeout, + // resulting in a failure + historyService.removeObserver(historyObserver, false); + resolve(); + } + }, + onDeleteURI: function(aURI) {}, + onClearHistory: function() {}, + onPageChanged: function(aURI, aWhat, aValue) {}, + onDeleteVisits: function () {}, + QueryInterface: function(iid) { + if (iid.equals(Ci.nsINavHistoryObserver) || + iid.equals(Ci.nsISupports)) { + return this; + } + throw Cr.NS_ERROR_NO_INTERFACE; + } + }; + + historyService.addObserver(historyObserver, false); + }); + + /* Queries nsINavHistoryService and returns a single history entry + * for a given URI */ + function getNavHistoryEntry(aURI) { + var options = historyService.getNewQueryOptions(); + options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY; + options.maxResults = 1; + + var query = historyService.getNewQuery(); + query.uri = aURI; + + var result = historyService.executeQuery(query, options); + result.root.containerOpen = true; + + if (!result.root.childCount) { + return null; + } + var node = result.root.getChild(0); + result.root.containerOpen = false; + return node; + } + + // Make sure neither of the test pages haven't been loaded before. + var info = getNavHistoryEntry(makeURI(pageurl)); + ok(!info, "The test page must not have been visited already."); + info = getNavHistoryEntry(makeURI(fragmenturl)); + ok(!info, "The fragment test page must not have been visited already."); + + // Now open the test page in a new tab + yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageurl); + + // Now that the page is loaded, click on fragment link + yield BrowserTestUtils.synthesizeMouseAtCenter("#firefox-link", {}, + gBrowser.selectedBrowser); + yield fragmentPromise; + + gBrowser.removeCurrentTab(); +}); diff --git a/docshell/test/browser/browser_bug554155.js b/docshell/test/browser/browser_bug554155.js new file mode 100644 index 000000000..efc74a1c1 --- /dev/null +++ b/docshell/test/browser/browser_bug554155.js @@ -0,0 +1,26 @@ +add_task(function* test() { + yield BrowserTestUtils.withNewTab({ gBrowser, url: "http://example.com" }, function* (browser) { + let numLocationChanges = 0; + + let listener = { + onLocationChange: function(browser, webProgress, request, uri, flags) { + info("location change: " + (uri && uri.spec)); + numLocationChanges++; + } + }; + + gBrowser.addTabsProgressListener(listener); + + yield ContentTask.spawn(browser, null, function() { + // pushState to a new URL (http://example.com/foo"). This should trigger + // exactly one LocationChange event. + content.history.pushState(null, null, "foo"); + }); + + yield Promise.resolve(); + + gBrowser.removeTabsProgressListener(listener); + is(numLocationChanges, 1, + "pushState should cause exactly one LocationChange event."); + }); +}); diff --git a/docshell/test/browser/browser_bug655270.js b/docshell/test/browser/browser_bug655270.js new file mode 100644 index 000000000..275245df8 --- /dev/null +++ b/docshell/test/browser/browser_bug655270.js @@ -0,0 +1,60 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test for Bug 655273 + * + * Call pushState and then make sure that the favicon service associates our + * old favicon with the new URI. + */ + +function test() { + const testDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + const origURL = testDir + "file_bug655270.html"; + const newURL = origURL + '?new_page'; + + const faviconURL = testDir + "favicon_bug655270.ico"; + + waitForExplicitFinish(); + + let tab = gBrowser.addTab(origURL); + + // The page at origURL has a <link rel='icon'>, so we should get a call into + // our observer below when it loads. Once we verify that we have the right + // favicon URI, we call pushState, which should trigger another onPageChange + // event, this time for the URI after pushState. + + let observer = { + onPageChanged: function(aURI, aWhat, aValue) { + if (aWhat != Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) + return; + + if (aURI.spec == origURL) { + is(aValue, faviconURL, 'FaviconURL for original URI'); + // Ignore the promise returned here and wait for the next + // onPageChanged notification. + ContentTask.spawn(tab.linkedBrowser, null, function() { + content.history.pushState('', '', '?new_page'); + }); + } + + if (aURI.spec == newURL) { + is(aValue, faviconURL, 'FaviconURL for new URI'); + gBrowser.removeTab(tab); + PlacesUtils.history.removeObserver(this); + finish(); + } + }, + + onBeginUpdateBatch: function() { }, + onEndUpdateBatch: function() { }, + onVisit: function() { }, + onTitleChanged: function() { }, + onDeleteURI: function() { }, + onClearHistory: function() { }, + onDeleteVisits: function() { }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsINavHistoryObserver]) + }; + + PlacesUtils.history.addObserver(observer, false); +} diff --git a/docshell/test/browser/browser_bug655273.js b/docshell/test/browser/browser_bug655273.js new file mode 100644 index 000000000..3acb6ddd5 --- /dev/null +++ b/docshell/test/browser/browser_bug655273.js @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test for Bug 655273. Make sure that after changing the URI via + * history.pushState, the resulting SHEntry has the same title as our old + * SHEntry. + **/ + +add_task(function* test() { + waitForExplicitFinish(); + + yield BrowserTestUtils.withNewTab({ gBrowser, url: "http://example.com" }, + function* (browser) { + yield ContentTask.spawn(browser, null, function* () { + let cw = content; + let oldTitle = cw.document.title; + ok(oldTitle, 'Content window should initially have a title.'); + cw.history.pushState('', '', 'new_page'); + + let shistory = cw.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .sessionHistory; + + is(shistory.getEntryAtIndex(shistory.index, false).title, + oldTitle, 'SHEntry title after pushstate.'); + }); + }); +}); diff --git a/docshell/test/browser/browser_bug670318.js b/docshell/test/browser/browser_bug670318.js new file mode 100644 index 000000000..a73639cab --- /dev/null +++ b/docshell/test/browser/browser_bug670318.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test for Bug 670318 + * + * When LoadEntry() is called on a browser that has multiple duplicate history + * entries, history.index can end up out of range (>= history.count). + */ + +const URL = "http://mochi.test:8888/browser/docshell/test/browser/file_bug670318.html"; + +add_task(function* test() { + yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, + function* (browser) { + yield ContentTask.spawn(browser, URL, function* (URL) { + let history = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory; + let count = 0; + + let testDone = {}; + testDone.promise = new Promise(resolve => { testDone.resolve = resolve; }); + + let listener = { + OnHistoryNewEntry: function (aNewURI) { + if (aNewURI.spec == URL && 5 == ++count) { + addEventListener("load", function onLoad() { + removeEventListener("load", onLoad, true); + + Assert.ok(history.index < history.count, "history.index is valid"); + testDone.resolve(); + }, true); + + history.removeSHistoryListener(listener); + delete content._testListener; + content.setTimeout(() => { content.location.reload(); }, 0); + } + + return true; + }, + + OnHistoryReload: () => true, + OnHistoryGoBack: () => true, + OnHistoryGoForward: () => true, + OnHistoryGotoIndex: () => true, + OnHistoryPurge: () => true, + OnHistoryReplaceEntry: () => { + // The initial load of about:blank causes a transient entry to be + // created, so our first navigation to a real page is a replace + // instead of a new entry. + ++count; + return true; + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsISHistoryListener, + Ci.nsISupportsWeakReference]) + }; + + history.addSHistoryListener(listener); + // Since listener implements nsISupportsWeakReference, we are + // responsible for keeping it alive so that the GC doesn't clear + // it before the test completes. We do this by anchoring the listener + // to the content global window, and clearing it just before the test + // completes. + content._testListener = listener; + content.location = URL; + + yield testDone.promise; + }); + }); +}); diff --git a/docshell/test/browser/browser_bug673467.js b/docshell/test/browser/browser_bug673467.js new file mode 100644 index 000000000..10139a48c --- /dev/null +++ b/docshell/test/browser/browser_bug673467.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test for bug 673467. In a new tab, load a page which inserts a new iframe +// before the load and then sets its location during the load. This should +// create just one SHEntry. + +var doc = "data:text/html,<html><body onload='load()'>" + + "<script>" + + " var iframe = document.createElement('iframe');" + + " iframe.id = 'iframe';" + + " document.documentElement.appendChild(iframe);" + + " function load() {" + + " iframe.src = 'data:text/html,Hello!';" + + " }" + + "</script>" + + "</body></html>" + +function test() { + waitForExplicitFinish(); + + let tab = gBrowser.addTab(doc); + let tabBrowser = tab.linkedBrowser; + + BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => { + return ContentTask.spawn(tab.linkedBrowser, null, () => { + return new Promise(resolve => { + // The main page has loaded. Now wait for the iframe to load. + let iframe = content.document.getElementById('iframe'); + iframe.addEventListener('load', function listener(aEvent) { + + // Wait for the iframe to load the new document, not about:blank. + if (!iframe.src) + return; + + iframe.removeEventListener('load', listener, true); + let shistory = content + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .sessionHistory; + + Assert.equal(shistory.count, 1, "shistory count should be 1."); + resolve(); + }, true); + }); + }); + }).then(() => { + gBrowser.removeTab(tab); + finish(); + }); +} diff --git a/docshell/test/browser/browser_bug852909.js b/docshell/test/browser/browser_bug852909.js new file mode 100644 index 000000000..5e2374c43 --- /dev/null +++ b/docshell/test/browser/browser_bug852909.js @@ -0,0 +1,23 @@ +var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/"; + +function test() { + waitForExplicitFinish(); + + gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug852909.png"); + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(image); +} + +function image(event) { + ok(!gBrowser.selectedTab.mayEnableCharacterEncodingMenu, "Docshell should say the menu should be disabled for images."); + + gBrowser.removeCurrentTab(); + gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug852909.pdf"); + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(pdf); +} + +function pdf(event) { + ok(!gBrowser.selectedTab.mayEnableCharacterEncodingMenu, "Docshell should say the menu should be disabled for PDF.js."); + + gBrowser.removeCurrentTab(); + finish(); +} diff --git a/docshell/test/browser/browser_bug92473.js b/docshell/test/browser/browser_bug92473.js new file mode 100644 index 000000000..d1c4aebdb --- /dev/null +++ b/docshell/test/browser/browser_bug92473.js @@ -0,0 +1,54 @@ +/* The test text as octets for reference + * %83%86%83%6a%83%52%81%5b%83%68%82%cd%81%41%82%b7%82%d7%82%c4%82%cc%95%b6%8e%9a%82%c9%8c%c5%97%4c%82%cc%94%d4%8d%86%82%f0%95%74%97%5e%82%b5%82%dc%82%b7 + */ + +function testContent(text) { + return ContentTask.spawn(gBrowser.selectedBrowser, text, text => { + Assert.equal(content.document.getElementById("testpar").innerHTML, text, + "<p> contains expected text"); + Assert.equal(content.document.getElementById("testtextarea").innerHTML, text, + "<textarea> contains expected text"); + Assert.equal(content.document.getElementById("testinput").value, text, + "<input> contains expected text"); + }); +} + +function afterOpen() { + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(afterChangeCharset); + + /* The test text decoded incorrectly as Windows-1251. This is the "right" wrong + text; anything else is unexpected. */ + const wrongText="\u0453\u2020\u0453\u006A\u0453\u0052\u0403\u005B\u0453\u0068\u201A\u041D\u0403\u0041\u201A\u00B7\u201A\u0427\u201A\u0414\u201A\u041C\u2022\u00B6\u040B\u0459\u201A\u0419\u040A\u0415\u2014\u004C\u201A\u041C\u201D\u0424\u040C\u2020\u201A\u0440\u2022\u0074\u2014\u005E\u201A\u00B5\u201A\u042C\u201A\u00B7"; + + /* Test that the content on load is the expected wrong decoding */ + testContent(wrongText).then(() => { + BrowserSetForcedCharacterSet("Shift_JIS"); + }); +} + +function afterChangeCharset() { + /* The test text decoded correctly as Shift_JIS */ + const rightText="\u30E6\u30CB\u30B3\u30FC\u30C9\u306F\u3001\u3059\u3079\u3066\u306E\u6587\u5B57\u306B\u56FA\u6709\u306E\u756A\u53F7\u3092\u4ED8\u4E0E\u3057\u307E\u3059"; + + /* test that the content is decoded correctly */ + testContent(rightText).then(() => { + gBrowser.removeCurrentTab(); + finish(); + }); +} + +function test() { + waitForExplicitFinish(); + + // Get the local directory. This needs to be a file: URI because chrome: URIs + // are always UTF-8 (bug 617339) and we are testing decoding from other + // charsets. + var jar = getJar(getRootDirectory(gTestPath)); + var dir = jar ? + extractJarToTmp(jar) : + getChromeDir(getResolvedURI(gTestPath)); + var rootDir = Services.io.newFileURI(dir).spec; + + gBrowser.selectedTab = gBrowser.addTab(rootDir + "test-form_sjis.html"); + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(afterOpen); +} diff --git a/docshell/test/browser/browser_loadDisallowInherit.js b/docshell/test/browser/browser_loadDisallowInherit.js new file mode 100644 index 000000000..71789890f --- /dev/null +++ b/docshell/test/browser/browser_loadDisallowInherit.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() { + waitForExplicitFinish(); + + let tab = gBrowser.selectedTab = gBrowser.addTab(); + registerCleanupFunction(function () { + gBrowser.removeTab(tab); + }); + + let browser = gBrowser.getBrowserForTab(tab); + + function loadURL(url, flags, func) { + browser.addEventListener("load", function loadListener(e) { + if (browser.currentURI.spec != url) + return; + browser.removeEventListener(e.type, loadListener, true); + func(); + }, true); + browser.loadURIWithFlags(url, flags, null, null, null); + } + + // Load a normal http URL + function testURL(url, func) { + loadURL("http://example.com/", 0, function () { + let pagePrincipal = browser.contentPrincipal; + ok(pagePrincipal, "got principal for http:// page"); + + // Now load the URL normally + loadURL(url, 0, function () { + ok(browser.contentPrincipal.equals(pagePrincipal), url + " should inherit principal"); + + // Now load the URL and disallow inheriting the principal + let webNav = Components.interfaces.nsIWebNavigation; + loadURL(url, webNav.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL, function () { + let newPrincipal = browser.contentPrincipal; + ok(newPrincipal, "got inner principal"); + ok(!newPrincipal.equals(pagePrincipal), + url + " should not inherit principal when loaded with DISALLOW_INHERIT_OWNER"); + + func(); + }); + }); + }); + } + + let urls = [ + "data:text/html,<body>hi", + // We used to test javascript: here as well, but now that we no longer run + // javascript: in a sandbox, we end up not running it at all in the + // DISALLOW_INHERIT_OWNER case, so never actually do a load for it at all. + ]; + + function nextTest() { + let url = urls.shift(); + if (url) + testURL(url, nextTest); + else + finish(); + } + + nextTest(); +} + diff --git a/docshell/test/browser/browser_loadURI.js b/docshell/test/browser/browser_loadURI.js new file mode 100644 index 000000000..a68e1c2de --- /dev/null +++ b/docshell/test/browser/browser_loadURI.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +var Cc = Components.classes; +var Ci = Components.interfaces; + +const gPostData = "postdata=true"; + +function test() { + waitForExplicitFinish(); + + let tab = gBrowser.selectedTab = gBrowser.addTab(); + registerCleanupFunction(function () { + gBrowser.removeTab(tab); + }); + + var dataStream = Cc["@mozilla.org/io/string-input-stream;1"]. + createInstance(Ci.nsIStringInputStream); + dataStream.data = gPostData; + + var postStream = Cc["@mozilla.org/network/mime-input-stream;1"]. + createInstance(Ci.nsIMIMEInputStream); + postStream.addHeader("Content-Type", "application/x-www-form-urlencoded"); + postStream.addContentLength = true; + postStream.setData(dataStream); + + tab.linkedBrowser.loadURIWithFlags("http://mochi.test:8888/browser/docshell/test/browser/print_postdata.sjs", 0, null, null, postStream); + BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => { + ContentTask.spawn(tab.linkedBrowser, gPostData, function(postData) { + var bodyText = content.document.body.textContent; + is(bodyText, postData, "post data was submitted correctly"); + }).then(() => { finish(); }); + }); +} diff --git a/docshell/test/browser/browser_multiple_pushState.js b/docshell/test/browser/browser_multiple_pushState.js new file mode 100644 index 000000000..5c2f5aef4 --- /dev/null +++ b/docshell/test/browser/browser_multiple_pushState.js @@ -0,0 +1,15 @@ +add_task(function* test_multiple_pushState() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: "http://example.org/browser/docshell/test/browser/file_multiple_pushState.html", + }, function* (browser) { + const kExpected = "http://example.org/bar/ABC/DEF?key=baz"; + + let contentLocation = yield ContentTask.spawn(browser, null, function* () { + return content.document.location.href; + }); + + is(contentLocation, kExpected); + is(browser.documentURI.spec, kExpected); + }); +}); diff --git a/docshell/test/browser/browser_onbeforeunload_navigation.js b/docshell/test/browser/browser_onbeforeunload_navigation.js new file mode 100644 index 000000000..d2feb69d4 --- /dev/null +++ b/docshell/test/browser/browser_onbeforeunload_navigation.js @@ -0,0 +1,176 @@ +var contentWindow; +var originalLocation; +var currentTest = -1; +var stayingOnPage = true; + +var TEST_PAGE = "http://mochi.test:8888/browser/docshell/test/browser/file_bug1046022.html"; +var TARGETED_PAGE = "data:text/html," + encodeURIComponent("<body>Shouldn't be seeing this</body>"); + +SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]}); + +var loadExpected = TEST_PAGE; +var testTab; +var testsLength; + +var loadStarted = false; +var tabStateListener = { + onStateChange: function(webprogress, request, stateFlags, status) { + let startDocumentFlags = Ci.nsIWebProgressListener.STATE_START | + Ci.nsIWebProgressListener.STATE_IS_DOCUMENT; + if ((stateFlags & startDocumentFlags) == startDocumentFlags) { + loadStarted = true; + } + }, + onStatusChange: () => {}, + onLocationChange: () => {}, + onSecurityChange: () => {}, + onProgressChange: () => {}, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener]) +}; + +function onTabLoaded(event) { + info("A document loaded in a tab!"); + let loadedPage = event.target.location.href; + if (loadedPage == "about:blank" || + event.originalTarget != testTab.linkedBrowser.contentDocument) { + return; + } + + if (!loadExpected) { + ok(false, "Expected no page loads, but loaded " + loadedPage + " instead!"); + return; + } + + if (!testsLength) { + testsLength = testTab.linkedBrowser.contentWindow.wrappedJSObject.testFns.length; + } + + is(loadedPage, loadExpected, "Loaded the expected page"); + if (contentWindow) { + is(contentWindow.document, event.target, "Same doc"); + } + if (onAfterPageLoad) { + onAfterPageLoad(); + } +} + +function onAfterTargetedPageLoad() { + ok(!stayingOnPage, "We should only fire if we're expecting to let the onbeforeunload dialog proceed to the new location"); + is(testTab.linkedBrowser.currentURI.spec, TARGETED_PAGE, "Should have loaded the expected new page"); + + runNextTest(); +} + +function onTabModalDialogLoaded(node) { + let content = testTab.linkedBrowser.contentWindow; + ok(!loadStarted, "No load should be started."); + info(content.location.href); + is(content, contentWindow, "Window should be the same still."); + is(content.location.href, originalLocation, "Page should not have changed."); + is(content.mySuperSpecialMark, 42, "Page should not have refreshed."); + + ok(!content.dialogWasInvoked, "Dialog should only be invoked once per test."); + content.dialogWasInvoked = true; + + + // Now listen for the dialog going away again... + let observer = new MutationObserver(function(muts) { + if (!node.parentNode) { + info("Dialog is gone"); + observer.disconnect(); + observer = null; + // If we're staying on the page, run the next test from here + if (stayingOnPage) { + // Evil, but necessary: without this delay, we manage to still break our + // own onbeforeunload code, because we'll basically cause a new load to be + // started while processing the destruction of the dialog for the old one. + executeSoon(runNextTest); + } + // if we accepted a page load in the dialog, the next test will get started + // by the load handler for that page loading + } + }); + observer.observe(node.parentNode, {childList: true}); + + // If we're going to let the page load, set us up to listen for that happening: + if (!stayingOnPage) { + loadExpected = TARGETED_PAGE; + onAfterPageLoad = onAfterTargetedPageLoad; + } + + let button = stayingOnPage ? node.ui.button1 : node.ui.button0; + // ... and then actually make the dialog go away + info("Clicking button: " + button.label); + EventUtils.synthesizeMouseAtCenter(button, {}); +} + +// Listen for the dialog being created +Services.obs.addObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded", false); + +function runNextTest() { + currentTest++; + if (currentTest >= testsLength) { + if (!stayingOnPage) { + finish(); + return; + } + // Run the same tests again, but this time let the navigation happen: + stayingOnPage = false; + // Remove onbeforeunload handler, or this load will trigger the dialog... + contentWindow.onbeforeunload = null; + currentTest = 0; + } + + + if (!stayingOnPage) { + // Right now we're on the data: page. Null contentWindow out to + // avoid CPOW errors when contentWindow is no longer the correct + // outer window proxy object. + contentWindow = null; + + onAfterPageLoad = runCurrentTest; + loadExpected = TEST_PAGE; + testTab.linkedBrowser.loadURI(TEST_PAGE); + } else { + runCurrentTest(); + } +} + +function runCurrentTest() { + // Reset things so we're sure the previous tests failings don't influence this one: + contentWindow = testTab.linkedBrowser.contentWindow; + contentWindow.mySuperSpecialMark = 42; + contentWindow.dialogWasInvoked = false; + originalLocation = contentWindow.location.href; + // And run this test: + info("Running test with onbeforeunload " + contentWindow.wrappedJSObject.testFns[currentTest].toSource()); + contentWindow.onbeforeunload = contentWindow.wrappedJSObject.testFns[currentTest]; + loadStarted = false; + testTab.linkedBrowser.loadURI(TARGETED_PAGE); +} + +var onAfterPageLoad = runNextTest; + +function test() { + waitForExplicitFinish(); + gBrowser.addProgressListener(tabStateListener); + + testTab = gBrowser.selectedTab = gBrowser.addTab(); + testTab.linkedBrowser.addEventListener("load", onTabLoaded, true); + testTab.linkedBrowser.loadURI(TEST_PAGE); +} + +registerCleanupFunction(function() { + // Remove the handler, or closing this tab will prove tricky: + if (contentWindow) { + try { + contentWindow.onbeforeunload = null; + } catch (ex) {} + } + contentWindow = null; + testTab.linkedBrowser.removeEventListener("load", onTabLoaded, true); + Services.obs.removeObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded"); + gBrowser.removeProgressListener(tabStateListener); + gBrowser.removeTab(testTab); +}); + diff --git a/docshell/test/browser/browser_search_notification.js b/docshell/test/browser/browser_search_notification.js new file mode 100644 index 000000000..b0a4571e5 --- /dev/null +++ b/docshell/test/browser/browser_search_notification.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() { + waitForExplicitFinish(); + + const kSearchEngineID = "test_urifixup_search_engine"; + const kSearchEngineURL = "http://localhost/?search={searchTerms}"; + Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get", + kSearchEngineURL); + + let oldDefaultEngine = Services.search.defaultEngine; + Services.search.defaultEngine = Services.search.getEngineByName(kSearchEngineID); + + let selectedName = Services.search.defaultEngine.name; + is(selectedName, kSearchEngineID, "Check fake search engine is selected"); + + registerCleanupFunction(function() { + if (oldDefaultEngine) { + Services.search.defaultEngine = oldDefaultEngine; + } + let engine = Services.search.getEngineByName(kSearchEngineID); + if (engine) { + Services.search.removeEngine(engine); + } + }); + + let tab = gBrowser.addTab(); + gBrowser.selectedTab = tab; + + function observer(subject, topic, data) { + Services.obs.removeObserver(observer, "keyword-search"); + is(topic, "keyword-search", "Got keyword-search notification"); + + let engine = Services.search.defaultEngine; + ok(engine, "Have default search engine."); + is(engine, subject, "Notification subject is engine."); + is("firefox health report", data, "Notification data is search term."); + + executeSoon(function cleanup() { + gBrowser.removeTab(tab); + finish(); + }); + } + + Services.obs.addObserver(observer, "keyword-search", false); + + gURLBar.value = "firefox health report"; + gURLBar.handleCommand(); +} + diff --git a/docshell/test/browser/browser_tab_touch_events.js b/docshell/test/browser/browser_tab_touch_events.js new file mode 100644 index 000000000..615983f61 --- /dev/null +++ b/docshell/test/browser/browser_tab_touch_events.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(function*() { + const URI = "data:text/html;charset=utf-8,<iframe id='test-iframe'></iframe>"; + + yield BrowserTestUtils.withNewTab({ gBrowser, url: URI }, function* (browser) { + yield ContentTask.spawn(browser, null, test_body); + }); +}); + +function* test_body() { + let docshell = docShell; + + is(docshell.touchEventsOverride, Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_NONE, + "touchEventsOverride flag should be initially set to NONE"); + + docshell.touchEventsOverride = Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_DISABLED; + is(docshell.touchEventsOverride, Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_DISABLED, + "touchEventsOverride flag should be changed to DISABLED"); + + let frameWin = content.document.querySelector("#test-iframe").contentWindow; + docshell = frameWin.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + is(docshell.touchEventsOverride, Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_DISABLED, + "touchEventsOverride flag should be passed on to frames."); + + let newFrame = content.document.createElement("iframe"); + content.document.body.appendChild(newFrame); + + let newFrameWin = newFrame.contentWindow; + docshell = newFrameWin.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + is(docshell.touchEventsOverride, Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_DISABLED, + "Newly created frames should use the new touchEventsOverride flag"); + + newFrameWin.location.reload(); + yield ContentTaskUtils.waitForEvent(newFrameWin, "load"); + + docshell = newFrameWin.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + is(docshell.touchEventsOverride, Ci.nsIDocShell.TOUCHEVENTS_OVERRIDE_DISABLED, + "New touchEventsOverride flag should persist across reloads"); +} diff --git a/docshell/test/browser/browser_timelineMarkers-01.js b/docshell/test/browser/browser_timelineMarkers-01.js new file mode 100644 index 000000000..59eeb57db --- /dev/null +++ b/docshell/test/browser/browser_timelineMarkers-01.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the docShell has the right profile timeline API + +const URL = "data:text/html;charset=utf-8,Test page"; + +add_task(function* () { + yield BrowserTestUtils.withNewTab({ gBrowser, url: URL }, + function* (browser) { + yield ContentTask.spawn(browser, null, function() { + ok("recordProfileTimelineMarkers" in docShell, + "The recordProfileTimelineMarkers attribute exists"); + ok("popProfileTimelineMarkers" in docShell, + "The popProfileTimelineMarkers function exists"); + ok(docShell.recordProfileTimelineMarkers === false, + "recordProfileTimelineMarkers is false by default"); + ok(docShell.popProfileTimelineMarkers().length === 0, + "There are no markers by default"); + + docShell.recordProfileTimelineMarkers = true; + ok(docShell.recordProfileTimelineMarkers === true, + "recordProfileTimelineMarkers can be set to true"); + + docShell.recordProfileTimelineMarkers = false; + ok(docShell.recordProfileTimelineMarkers === false, + "recordProfileTimelineMarkers can be set to false"); + }); + }); +}); diff --git a/docshell/test/browser/browser_timelineMarkers-02.js b/docshell/test/browser/browser_timelineMarkers-02.js new file mode 100644 index 000000000..34142760e --- /dev/null +++ b/docshell/test/browser/browser_timelineMarkers-02.js @@ -0,0 +1,15 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var URL = '<!DOCTYPE html><style>' + + 'body {margin:0; padding: 0;} ' + + 'div {width:100px;height:100px;background:red;} ' + + '.resize-change-color {width:50px;height:50px;background:blue;} ' + + '.change-color {width:50px;height:50px;background:yellow;} ' + + '.add-class {}' + + '</style><div></div>'; +URL = "data:text/html;charset=utf8," + encodeURIComponent(URL); + +var test = makeTimelineTest("browser_timelineMarkers-frame-02.js", URL); diff --git a/docshell/test/browser/browser_timelineMarkers-03.js b/docshell/test/browser/browser_timelineMarkers-03.js new file mode 100644 index 000000000..b104367c1 --- /dev/null +++ b/docshell/test/browser/browser_timelineMarkers-03.js @@ -0,0 +1,8 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var URL = "data:text/html;charset=utf-8,<p>Test page</p>"; + +var test = makeTimelineTest("browser_timelineMarkers-frame-03.js", URL); diff --git a/docshell/test/browser/browser_timelineMarkers-04.js b/docshell/test/browser/browser_timelineMarkers-04.js new file mode 100644 index 000000000..fdee867e7 --- /dev/null +++ b/docshell/test/browser/browser_timelineMarkers-04.js @@ -0,0 +1,8 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const URL = "http://mochi.test:8888/browser/docshell/test/browser/timelineMarkers-04.html"; + +var test = makeTimelineTest("browser_timelineMarkers-frame-04.js", URL); diff --git a/docshell/test/browser/browser_timelineMarkers-05.js b/docshell/test/browser/browser_timelineMarkers-05.js new file mode 100644 index 000000000..0130c58d9 --- /dev/null +++ b/docshell/test/browser/browser_timelineMarkers-05.js @@ -0,0 +1,15 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var URL = '<!DOCTYPE html><style>' + + 'body {margin:0; padding: 0;} ' + + 'div {width:100px;height:100px;background:red;} ' + + '.resize-change-color {width:50px;height:50px;background:blue;} ' + + '.change-color {width:50px;height:50px;background:yellow;} ' + + '.add-class {}' + + '</style><div></div>'; +URL = "data:text/html;charset=utf8," + encodeURIComponent(URL); + +var test = makeTimelineTest("browser_timelineMarkers-frame-05.js", URL); diff --git a/docshell/test/browser/browser_timelineMarkers-frame-02.js b/docshell/test/browser/browser_timelineMarkers-frame-02.js new file mode 100644 index 000000000..83750395d --- /dev/null +++ b/docshell/test/browser/browser_timelineMarkers-frame-02.js @@ -0,0 +1,133 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the docShell profile timeline API returns the right markers when +// restyles, reflows and paints occur + +function rectangleContains(rect, x, y, width, height) { + return rect.x <= x && rect.y <= y && rect.width >= width && + rect.height >= height; +} + +function sanitizeMarkers(list) { + // These markers are currently gathered from all docshells, which may + // interfere with this test. + return list.filter(e => e.name != "Worker" && e.name != "MinorGC"); +} + +var TESTS = [{ + desc: "Changing the width of the test element", + searchFor: "Paint", + setup: function(docShell) { + let div = content.document.querySelector("div"); + div.setAttribute("class", "resize-change-color"); + }, + check: function(markers) { + markers = sanitizeMarkers(markers); + ok(markers.length > 0, "markers were returned"); + console.log(markers); + info(JSON.stringify(markers.filter(m => m.name == "Paint"))); + ok(markers.some(m => m.name == "Reflow"), "markers includes Reflow"); + ok(markers.some(m => m.name == "Paint"), "markers includes Paint"); + for (let marker of markers.filter(m => m.name == "Paint")) { + // This change should generate at least one rectangle. + ok(marker.rectangles.length >= 1, "marker has one rectangle"); + // One of the rectangles should contain the div. + ok(marker.rectangles.some(r => rectangleContains(r, 0, 0, 100, 100))); + } + ok(markers.some(m => m.name == "Styles"), "markers includes Restyle"); + } +}, { + desc: "Changing the test element's background color", + searchFor: "Paint", + setup: function(docShell) { + let div = content.document.querySelector("div"); + div.setAttribute("class", "change-color"); + }, + check: function(markers) { + markers = sanitizeMarkers(markers); + ok(markers.length > 0, "markers were returned"); + ok(!markers.some(m => m.name == "Reflow"), "markers doesn't include Reflow"); + ok(markers.some(m => m.name == "Paint"), "markers includes Paint"); + for (let marker of markers.filter(m => m.name == "Paint")) { + // This change should generate at least one rectangle. + ok(marker.rectangles.length >= 1, "marker has one rectangle"); + // One of the rectangles should contain the div. + ok(marker.rectangles.some(r => rectangleContains(r, 0, 0, 50, 50))); + } + ok(markers.some(m => m.name == "Styles"), "markers includes Restyle"); + } +}, { + desc: "Changing the test element's classname", + searchFor: "Paint", + setup: function(docShell) { + let div = content.document.querySelector("div"); + div.setAttribute("class", "change-color add-class"); + }, + check: function(markers) { + markers = sanitizeMarkers(markers); + ok(markers.length > 0, "markers were returned"); + ok(!markers.some(m => m.name == "Reflow"), "markers doesn't include Reflow"); + ok(!markers.some(m => m.name == "Paint"), "markers doesn't include Paint"); + ok(markers.some(m => m.name == "Styles"), "markers includes Restyle"); + } +}, { + desc: "sync console.time/timeEnd", + searchFor: "ConsoleTime", + setup: function(docShell) { + content.console.time("FOOBAR"); + content.console.timeEnd("FOOBAR"); + let markers = docShell.popProfileTimelineMarkers(); + is(markers.length, 1, "Got one marker"); + is(markers[0].name, "ConsoleTime", "Got ConsoleTime marker"); + is(markers[0].causeName, "FOOBAR", "Got ConsoleTime FOOBAR detail"); + content.console.time("FOO"); + content.setTimeout(() => { + content.console.time("BAR"); + content.setTimeout(() => { + content.console.timeEnd("FOO"); + content.console.timeEnd("BAR"); + }, 100); + }, 100); + }, + check: function(markers) { + markers = sanitizeMarkers(markers); + is(markers.length, 2, "Got 2 markers"); + is(markers[0].name, "ConsoleTime", "Got first ConsoleTime marker"); + is(markers[0].causeName, "FOO", "Got ConsoleTime FOO detail"); + is(markers[1].name, "ConsoleTime", "Got second ConsoleTime marker"); + is(markers[1].causeName, "BAR", "Got ConsoleTime BAR detail"); + } +}, { + desc: "Timestamps created by console.timeStamp()", + searchFor: "Timestamp", + setup: function(docshell) { + content.console.timeStamp("rock"); + let markers = docShell.popProfileTimelineMarkers(); + is(markers.length, 1, "Got one marker"); + is(markers[0].name, "TimeStamp", "Got Timestamp marker"); + is(markers[0].causeName, "rock", "Got Timestamp label value"); + content.console.timeStamp("paper"); + content.console.timeStamp("scissors"); + content.console.timeStamp(); + content.console.timeStamp(undefined); + }, + check: function (markers) { + markers = sanitizeMarkers(markers); + is(markers.length, 4, "Got 4 markers"); + is(markers[0].name, "TimeStamp", "Got Timestamp marker"); + is(markers[0].causeName, "paper", "Got Timestamp label value"); + is(markers[1].name, "TimeStamp", "Got Timestamp marker"); + is(markers[1].causeName, "scissors", "Got Timestamp label value"); + is(markers[2].name, "TimeStamp", "Got empty Timestamp marker when no argument given"); + is(markers[2].causeName, void 0, "Got empty Timestamp label value"); + is(markers[3].name, "TimeStamp", "Got empty Timestamp marker when argument is undefined"); + is(markers[3].causeName, void 0, "Got empty Timestamp label value"); + markers.forEach(m => is(m.end, m.start, + "All Timestamp markers should have identical start/end times")); + } +}]; + +timelineContentTest(TESTS); diff --git a/docshell/test/browser/browser_timelineMarkers-frame-03.js b/docshell/test/browser/browser_timelineMarkers-frame-03.js new file mode 100644 index 000000000..cf5f150ad --- /dev/null +++ b/docshell/test/browser/browser_timelineMarkers-frame-03.js @@ -0,0 +1,91 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the docShell profile timeline API returns the right +// markers for DOM events. + +var TESTS = [{ + desc: "Event dispatch with single handler", + searchFor: 'DOMEvent', + setup: function(docShell) { + content.document.body.addEventListener("dog", + function(e) { console.log("hi"); }, + true); + content.document.body.dispatchEvent(new content.Event("dog")); + }, + check: function(markers) { + markers = markers.filter(m => m.name == 'DOMEvent'); + is(markers.length, 1, "Got 1 marker"); + is(markers[0].type, "dog", "Got dog event name"); + is(markers[0].eventPhase, 2, "Got phase 2"); + } +}, { + desc: "Event dispatch with a second handler", + searchFor: function(markers) { + return markers.filter(m => m.name == 'DOMEvent').length >= 2; + }, + setup: function(docShell) { + content.document.body.addEventListener("dog", + function(e) { console.log("hi"); }, + false); + content.document.body.dispatchEvent(new content.Event("dog")); + }, + check: function(markers) { + markers = markers.filter(m => m.name == 'DOMEvent'); + is(markers.length, 2, "Got 2 markers"); + } +}, { + desc: "Event targeted at child", + searchFor: function(markers) { + return markers.filter(m => m.name == 'DOMEvent').length >= 2; + }, + setup: function(docShell) { + let child = content.document.body.firstElementChild; + child.addEventListener("dog", function(e) { }); + child.dispatchEvent(new content.Event("dog")); + }, + check: function(markers) { + markers = markers.filter(m => m.name == 'DOMEvent'); + is(markers.length, 2, "Got 2 markers"); + is(markers[0].eventPhase, 1, "Got phase 1 marker"); + is(markers[1].eventPhase, 2, "Got phase 2 marker"); + } +}, { + desc: "Event dispatch on a new document", + searchFor: function(markers) { + return markers.filter(m => m.name == 'DOMEvent').length >= 2; + }, + setup: function(docShell) { + let doc = content.document.implementation.createHTMLDocument("doc"); + let p = doc.createElement("p"); + p.innerHTML = "inside"; + doc.body.appendChild(p); + + p.addEventListener("zebra", function(e) {console.log("hi");}); + p.dispatchEvent(new content.Event("zebra")); + }, + check: function(markers) { + markers = markers.filter(m => m.name == 'DOMEvent'); + is(markers.length, 1, "Got 1 marker"); + } +}, { + desc: "Event dispatch on window", + searchFor: function(markers) { + return markers.filter(m => m.name == 'DOMEvent').length >= 2; + }, + setup: function(docShell) { + let doc = content.window.addEventListener("aardvark", function(e) { + console.log("I like ants!"); + }); + + content.window.dispatchEvent(new content.Event("aardvark")); + }, + check: function(markers) { + markers = markers.filter(m => m.name == 'DOMEvent'); + is(markers.length, 1, "Got 1 marker"); + } +}]; + +timelineContentTest(TESTS); diff --git a/docshell/test/browser/browser_timelineMarkers-frame-04.js b/docshell/test/browser/browser_timelineMarkers-frame-04.js new file mode 100644 index 000000000..427312993 --- /dev/null +++ b/docshell/test/browser/browser_timelineMarkers-frame-04.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the docShell profile timeline API returns the right +// markers for XMLHttpRequest events. + +var TESTS = [{ + desc: "Event dispatch from XMLHttpRequest", + searchFor: function(markers) { + return markers.filter(m => m.name == "DOMEvent").length >= 5; + }, + setup: function(docShell) { + content.dispatchEvent(new content.Event("dog")); + }, + check: function(markers) { + let domMarkers = markers.filter(m => m.name == "DOMEvent"); + // One subtlety here is that we have five events: the event we + // inject in "setup", plus the four state transition events. The + // first state transition is reported synchronously and so should + // show up as a nested marker. + is(domMarkers.length, 5, "Got 5 markers"); + + // We should see some Javascript markers, and they should have a + // cause. + let jsMarkers = markers.filter(m => m.name == "Javascript" && m.causeName); + ok(jsMarkers.length > 0, "Got some Javascript markers"); + is(jsMarkers[0].stack.functionDisplayName, "do_xhr", + "Javascript marker has entry point name"); + } +}]; + +if (Services.prefs.getBoolPref("javascript.options.asyncstack")) { + TESTS.push({ + desc: "Async stack trace on Javascript marker", + searchFor: (markers) => { + return markers.some(m => (m.name == "Javascript" && + m.causeName == "promise callback")); + }, + setup: function(docShell) { + content.dispatchEvent(new content.Event("promisetest")); + }, + check: function(markers) { + markers = markers.filter(m => (m.name == "Javascript" && + m.causeName == "promise callback")); + ok(markers.length > 0, "Found a Javascript marker"); + + let frame = markers[0].stack; + ok(frame.asyncParent !== null, "Parent frame has async parent"); + is(frame.asyncParent.asyncCause, "promise callback", + "Async parent has correct cause"); + let asyncFrame = frame.asyncParent; + // Skip over self-hosted parts of our Promise implementation. + while (asyncFrame.source === 'self-hosted') + asyncFrame = asyncFrame.parent; + is(asyncFrame.functionDisplayName, "do_promise", + "Async parent has correct function name"); + } + }, { + desc: "Async stack trace on Javascript marker with script", + searchFor: (markers) => { + return markers.some(m => (m.name == "Javascript" && + m.causeName == "promise callback")); + }, + setup: function(docShell) { + content.dispatchEvent(new content.Event("promisescript")); + }, + check: function(markers) { + markers = markers.filter(m => (m.name == "Javascript" && + m.causeName == "promise callback")); + ok(markers.length > 0, "Found a Javascript marker"); + + let frame = markers[0].stack; + ok(frame.asyncParent !== null, "Parent frame has async parent"); + is(frame.asyncParent.asyncCause, "promise callback", + "Async parent has correct cause"); + let asyncFrame = frame.asyncParent; + // Skip over self-hosted parts of our Promise implementation. + while (asyncFrame.source === 'self-hosted') + asyncFrame = asyncFrame.parent; + is(asyncFrame.functionDisplayName, "do_promise_script", + "Async parent has correct function name"); + } + }); +} + +timelineContentTest(TESTS); diff --git a/docshell/test/browser/browser_timelineMarkers-frame-05.js b/docshell/test/browser/browser_timelineMarkers-frame-05.js new file mode 100644 index 000000000..713f0e560 --- /dev/null +++ b/docshell/test/browser/browser_timelineMarkers-frame-05.js @@ -0,0 +1,117 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +function forceSyncReflow(div) { + div.setAttribute('class', 'resize-change-color'); + // Force a reflow. + return div.offsetWidth; +} + +function testSendingEvent() { + content.document.body.dispatchEvent(new content.Event("dog")); +} + +function testConsoleTime() { + content.console.time("cats"); +} + +function testConsoleTimeEnd() { + content.console.timeEnd("cats"); +} + +function makePromise() { + let resolver; + new Promise(function(resolve, reject) { + testConsoleTime(); + resolver = resolve; + }).then(function(val) { + testConsoleTimeEnd(); + }); + return resolver; +} + +function resolvePromise(resolver) { + resolver(23); +} + +var TESTS = [{ + desc: "Stack trace on sync reflow", + searchFor: "Reflow", + setup: function(docShell) { + let div = content.document.querySelector("div"); + forceSyncReflow(div); + }, + check: function(markers) { + markers = markers.filter(m => m.name == "Reflow"); + ok(markers.length > 0, "Reflow marker includes stack"); + ok(markers[0].stack.functionDisplayName == "forceSyncReflow"); + } +}, { + desc: "Stack trace on DOM event", + searchFor: "DOMEvent", + setup: function(docShell) { + content.document.body.addEventListener("dog", + function(e) { console.log("hi"); }, + true); + testSendingEvent(); + }, + check: function(markers) { + markers = markers.filter(m => m.name == "DOMEvent"); + ok(markers.length > 0, "DOMEvent marker includes stack"); + ok(markers[0].stack.functionDisplayName == "testSendingEvent", + "testSendingEvent is on the stack"); + } +}, { + desc: "Stack trace on console event", + searchFor: "ConsoleTime", + setup: function(docShell) { + testConsoleTime(); + testConsoleTimeEnd(); + }, + check: function(markers) { + markers = markers.filter(m => m.name == "ConsoleTime"); + ok(markers.length > 0, "ConsoleTime marker includes stack"); + ok(markers[0].stack.functionDisplayName == "testConsoleTime", + "testConsoleTime is on the stack"); + ok(markers[0].endStack.functionDisplayName == "testConsoleTimeEnd", + "testConsoleTimeEnd is on the stack"); + } +}]; + +if (Services.prefs.getBoolPref("javascript.options.asyncstack")) { + TESTS.push({ + desc: "Async stack trace on Promise", + searchFor: "ConsoleTime", + setup: function(docShell) { + let resolver = makePromise(); + resolvePromise(resolver); + }, + check: function(markers) { + markers = markers.filter(m => m.name == "ConsoleTime"); + ok(markers.length > 0, "Promise marker includes stack"); + ok(markers[0].stack.functionDisplayName == "testConsoleTime", + "testConsoleTime is on the stack"); + let frame = markers[0].endStack; + ok(frame.functionDisplayName == "testConsoleTimeEnd", + "testConsoleTimeEnd is on the stack"); + + frame = frame.parent; + ok(frame.functionDisplayName == "makePromise/<", + "makePromise/< is on the stack"); + let asyncFrame = frame.asyncParent; + ok(asyncFrame !== null, "Frame has async parent"); + is(asyncFrame.asyncCause, "promise callback", + "Async parent has correct cause"); + // Skip over self-hosted parts of our Promise implementation. + while (asyncFrame.source === 'self-hosted') { + asyncFrame = asyncFrame.parent; + } + is(asyncFrame.functionDisplayName, "makePromise", + "Async parent has correct function name"); + } + }); +} + +timelineContentTest(TESTS); diff --git a/docshell/test/browser/browser_ua_emulation.js b/docshell/test/browser/browser_ua_emulation.js new file mode 100644 index 000000000..94a602d4b --- /dev/null +++ b/docshell/test/browser/browser_ua_emulation.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const URL = "data:text/html;charset=utf-8,<iframe id='test-iframe'></iframe>"; + +// Test that the docShell UA emulation works +function* contentTask() { + let docshell = docShell; + is(docshell.customUserAgent, "", "There should initially be no customUserAgent"); + + docshell.customUserAgent = "foo"; + is(content.navigator.userAgent, "foo", "The user agent should be changed to foo"); + + let frameWin = content.document.querySelector("#test-iframe").contentWindow; + is(frameWin.navigator.userAgent, "foo", "The UA should be passed on to frames."); + + let newFrame = content.document.createElement("iframe"); + content.document.body.appendChild(newFrame); + + let newFrameWin = newFrame.contentWindow; + is(newFrameWin.navigator.userAgent, "foo", "Newly created frames should use the new UA"); + + newFrameWin.location.reload(); + yield ContentTaskUtils.waitForEvent(newFrameWin, "load"); + + is(newFrameWin.navigator.userAgent, "foo", "New UA should persist across reloads"); +} + +add_task(function* () { + yield BrowserTestUtils.withNewTab({ gBrowser, url: URL }, + function* (browser) { + yield ContentTask.spawn(browser, null, contentTask); + }); +}); diff --git a/docshell/test/browser/browser_uriFixupAlternateRedirects.js b/docshell/test/browser/browser_uriFixupAlternateRedirects.js new file mode 100644 index 000000000..f5a00a2df --- /dev/null +++ b/docshell/test/browser/browser_uriFixupAlternateRedirects.js @@ -0,0 +1,24 @@ +"use strict"; + +const REDIRECTURL = "http://www.example.com/browser/docshell/test/browser/redirect_to_example.sjs" + +add_task(function* () { + let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank"); + gURLBar.value = REDIRECTURL; + gURLBar.select(); + let errorPageLoaded = BrowserTestUtils.waitForErrorPage(tab.linkedBrowser); + EventUtils.sendKey("return"); + yield errorPageLoaded; + let [contentURL, originalURL] = yield ContentTask.spawn(tab.linkedBrowser, null, () => { + return [ + content.document.documentURI, + content.document.mozDocumentURIIfNotForErrorPages.spec, + ]; + }); + info("Page that loaded: " + contentURL); + ok(contentURL.startsWith("about:neterror?"), "Should be on an error page"); + originalURL = new URL(originalURL); + is(originalURL.host, "example", "Should be an error for http://example, not http://www.example.com/"); + + yield BrowserTestUtils.removeTab(tab); +}); diff --git a/docshell/test/browser/browser_uriFixupIntegration.js b/docshell/test/browser/browser_uriFixupIntegration.js new file mode 100644 index 000000000..e041e1f67 --- /dev/null +++ b/docshell/test/browser/browser_uriFixupIntegration.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const kSearchEngineID = "browser_urifixup_search_engine"; +const kSearchEngineURL = "http://example.com/?search={searchTerms}"; + +add_task(function* setup() { + // Add a new fake search engine. + Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get", + kSearchEngineURL); + + let oldDefaultEngine = Services.search.defaultEngine; + Services.search.defaultEngine = Services.search.getEngineByName(kSearchEngineID); + + // Remove the fake engine when done. + registerCleanupFunction(() => { + if (oldDefaultEngine) { + Services.search.defaultEngine = oldDefaultEngine; + } + + let engine = Services.search.getEngineByName(kSearchEngineID); + if (engine) { + Services.search.removeEngine(engine); + } + }); +}); + +add_task(function* test() { + for (let searchParams of ["foo bar", "brokenprotocol:somethingelse"]) { + // Add a new blank tab. + gBrowser.selectedTab = gBrowser.addTab("about:blank"); + yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + + // Enter search terms and start a search. + gURLBar.value = searchParams; + gURLBar.focus(); + EventUtils.synthesizeKey("VK_RETURN", {}); + yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + + // Check that we arrived at the correct URL. + let escapedParams = encodeURIComponent(searchParams).replace("%20", "+"); + let expectedURL = kSearchEngineURL.replace("{searchTerms}", escapedParams); + is(gBrowser.selectedBrowser.currentURI.spec, expectedURL, + "New tab should have loaded with expected url."); + + // Cleanup. + gBrowser.removeCurrentTab(); + } +}); diff --git a/docshell/test/browser/favicon_bug655270.ico b/docshell/test/browser/favicon_bug655270.ico Binary files differnew file mode 100644 index 000000000..d44438903 --- /dev/null +++ b/docshell/test/browser/favicon_bug655270.ico diff --git a/docshell/test/browser/file_bug1046022.html b/docshell/test/browser/file_bug1046022.html new file mode 100644 index 000000000..2de68cb43 --- /dev/null +++ b/docshell/test/browser/file_bug1046022.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>Bug 1046022 - test navigating inside onbeforeunload</title> + </head> + <body> + Waiting for onbeforeunload to hit... + </body> + + <script> +var testFns = [ + function(e) { + e.target.location.href = 'otherpage-href-set.html'; + return "stop"; + }, + function(e) { + e.target.location.reload(); + return "stop"; + }, + function(e) { + e.target.location.replace('otherpage-location-replaced.html'); + return "stop"; + }, + function(e) { + var link = e.target.createElement('a'); + link.href = "otherpage.html"; + e.target.body.appendChild(link); + link.click(); + return "stop"; + }, + function(e) { + var link = e.target.createElement('a'); + link.href = "otherpage.html"; + link.setAttribute("target", "_blank"); + e.target.body.appendChild(link); + link.click(); + return "stop"; + }, + function(e) { + var link = e.target.createElement('a'); + link.href = e.target.location.href; + e.target.body.appendChild(link); + link.setAttribute("target", "somearbitrarywindow"); + link.click(); + return "stop"; + }, +]; + </script> +</html> diff --git a/docshell/test/browser/file_bug1206879.html b/docshell/test/browser/file_bug1206879.html new file mode 100644 index 000000000..5313902a9 --- /dev/null +++ b/docshell/test/browser/file_bug1206879.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <head> + <title>Test page for bug 1206879</title> + </head> + <body> + <iframe src="http://example.com/"></iframe> + </body> +</html> diff --git a/docshell/test/browser/file_bug234628-1-child.html b/docshell/test/browser/file_bug234628-1-child.html new file mode 100644 index 000000000..c36197ac4 --- /dev/null +++ b/docshell/test/browser/file_bug234628-1-child.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>No encoding declaration in parent or child</title> +</head> +<body> +<p>Euro sign if decoded as Windows-1252: </p> +<p>a with diaeresis if decoded as Windows-1252: </p> +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-1.html b/docshell/test/browser/file_bug234628-1.html new file mode 100644 index 000000000..11c523ccd --- /dev/null +++ b/docshell/test/browser/file_bug234628-1.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>No encoding declaration in parent or child</title> +</head> +<body> +<h1>No encoding declaration in parent or child</h1> + +<p>Euro sign if decoded as Windows-1252: </p> +<p>a with diaeresis if decoded as Windows-1252: </p> + +<iframe src="file_bug234628-1-child.html"></iframe> + +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-10-child.xhtml b/docshell/test/browser/file_bug234628-10-child.xhtml new file mode 100644 index 000000000..cccf6f2bc --- /dev/null +++ b/docshell/test/browser/file_bug234628-10-child.xhtml @@ -0,0 +1,4 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head><title>XML child with no encoding declaration</title></head> +<body><p>Euro sign if decoded as UTF-8: €</p></body> +</html> diff --git a/docshell/test/browser/file_bug234628-10.html b/docshell/test/browser/file_bug234628-10.html new file mode 100644 index 000000000..78b8f0035 --- /dev/null +++ b/docshell/test/browser/file_bug234628-10.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>No encoding declaration in HTML parent or XHTML child</title> +</head> +<body> +<h1>No encoding declaration in HTML parent or XHTML child</h1> + +<p>Euro sign if decoded as Windows-1252: </p> +<p>a with diaeresis if decoded as Windows-1252: </p> + +<iframe src="file_bug234628-10-child.xhtml"></iframe> + +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-11-child.xhtml b/docshell/test/browser/file_bug234628-11-child.xhtml new file mode 100644 index 000000000..11ef668b0 --- /dev/null +++ b/docshell/test/browser/file_bug234628-11-child.xhtml @@ -0,0 +1,4 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head><title>No encoding declaration in HTML parent and HTTP declaration in XHTML child</title></head> +<body><p>Euro sign if decoded as UTF-8: €</p></body> +</html> diff --git a/docshell/test/browser/file_bug234628-11-child.xhtml^headers^ b/docshell/test/browser/file_bug234628-11-child.xhtml^headers^ new file mode 100644 index 000000000..30fb30405 --- /dev/null +++ b/docshell/test/browser/file_bug234628-11-child.xhtml^headers^ @@ -0,0 +1 @@ +Content-Type: application/xhtml+xml; charset=utf-8 diff --git a/docshell/test/browser/file_bug234628-11.html b/docshell/test/browser/file_bug234628-11.html new file mode 100644 index 000000000..21c5b733e --- /dev/null +++ b/docshell/test/browser/file_bug234628-11.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>No encoding declaration in HTML parent and HTTP declaration in XHTML child</title> +</head> +<body> +<h1>No encoding declaration in HTML parent and HTTP declaration in XHTML child</h1> + +<p>Euro sign if decoded as Windows-1252: </p> +<p>a with diaeresis if decoded as Windows-1252: </p> + +<iframe src="file_bug234628-11-child.xhtml"></iframe> + +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-2-child.html b/docshell/test/browser/file_bug234628-2-child.html new file mode 100644 index 000000000..0acd2e0b2 --- /dev/null +++ b/docshell/test/browser/file_bug234628-2-child.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>No encoding declaration in parent or child</title> +</head> +<body> +<p>Euro sign if decoded as UTF-8: €</p> +<p>a with diaeresis if decoded as UTF-8: ä</p> +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-2.html b/docshell/test/browser/file_bug234628-2.html new file mode 100644 index 000000000..a87d29e12 --- /dev/null +++ b/docshell/test/browser/file_bug234628-2.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>No encoding declaration in parent or child</title> +</head> +<body> +<h1>No encoding declaration in parent or child</h1> + +<p>Euro sign if decoded as Windows-1252: </p> +<p>a with diaeresis if decoded as Windows-1252: </p> + +<iframe src="file_bug234628-2-child.html"></iframe> + +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-3-child.html b/docshell/test/browser/file_bug234628-3-child.html new file mode 100644 index 000000000..a6ad83231 --- /dev/null +++ b/docshell/test/browser/file_bug234628-3-child.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="utf-8"> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>meta declaration in parent and child</title> +</head> +<body> +<p>Euro sign if decoded as UTF-8: €</p> +<p>a with diaeresis if decoded as UTF-8: ä</p> +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-3.html b/docshell/test/browser/file_bug234628-3.html new file mode 100644 index 000000000..8caab6040 --- /dev/null +++ b/docshell/test/browser/file_bug234628-3.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="windows-1252"> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>meta declaration in parent and child</title> +</head> +<body> +<h1>meta declaration in parent and child</h1> + +<p>Euro sign if decoded as Windows-1252: </p> +<p>a with diaeresis if decoded as Windows-1252: </p> + +<iframe src="file_bug234628-3-child.html"></iframe> + +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-4-child.html b/docshell/test/browser/file_bug234628-4-child.html new file mode 100644 index 000000000..f0e7c2c05 --- /dev/null +++ b/docshell/test/browser/file_bug234628-4-child.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>meta declaration in parent and BOM in child</title> +</head> +<body> +<p>Euro sign if decoded as UTF-8: €</p> +<p>a with diaeresis if decoded as UTF-8: ä</p> +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-4.html b/docshell/test/browser/file_bug234628-4.html new file mode 100644 index 000000000..013757901 --- /dev/null +++ b/docshell/test/browser/file_bug234628-4.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="windows-1252"> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>meta declaration in parent and BOM in child</title> +</head> +<body> +<h1>meta declaration in parent and BOM in child</h1> + +<p>Euro sign if decoded as Windows-1252: </p> +<p>a with diaeresis if decoded as Windows-1252: </p> + +<iframe src="file_bug234628-4-child.html"></iframe> + +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-5-child.html b/docshell/test/browser/file_bug234628-5-child.html Binary files differnew file mode 100644 index 000000000..a650552f6 --- /dev/null +++ b/docshell/test/browser/file_bug234628-5-child.html diff --git a/docshell/test/browser/file_bug234628-5.html b/docshell/test/browser/file_bug234628-5.html new file mode 100644 index 000000000..987e6420b --- /dev/null +++ b/docshell/test/browser/file_bug234628-5.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="windows-1252"> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>meta declaration in parent and UTF-16 BOM in child</title> +</head> +<body> +<h1>meta declaration in parent and UTF-16 BOM in child</h1> + +<p>Euro sign if decoded as Windows-1252: </p> +<p>a with diaeresis if decoded as Windows-1252: </p> + +<iframe src="file_bug234628-5-child.html"></iframe> + +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-6-child.html b/docshell/test/browser/file_bug234628-6-child.html Binary files differnew file mode 100644 index 000000000..52c37f259 --- /dev/null +++ b/docshell/test/browser/file_bug234628-6-child.html diff --git a/docshell/test/browser/file_bug234628-6-child.html^headers^ b/docshell/test/browser/file_bug234628-6-child.html^headers^ new file mode 100644 index 000000000..bfdcf487f --- /dev/null +++ b/docshell/test/browser/file_bug234628-6-child.html^headers^ @@ -0,0 +1 @@ +Content-Type: text/html; charset=utf-16be diff --git a/docshell/test/browser/file_bug234628-6.html b/docshell/test/browser/file_bug234628-6.html new file mode 100644 index 000000000..9d7fc580c --- /dev/null +++ b/docshell/test/browser/file_bug234628-6.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="windows-1252"> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>meta declaration in parent and BOMless UTF-16 with HTTP charset in child</title> +</head> +<body> +<h1>meta declaration in parent and BOMless UTF-16 with HTTP charset in child</h1> + +<p>Euro sign if decoded as Windows-1252: </p> +<p>a with diaeresis if decoded as Windows-1252: </p> + +<iframe src="file_bug234628-6-child.html"></iframe> + +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-7-child.html b/docshell/test/browser/file_bug234628-7-child.html new file mode 100644 index 000000000..c761ace10 --- /dev/null +++ b/docshell/test/browser/file_bug234628-7-child.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>meta declaration in parent and BOMless UTF-8 with HTTP charset in child</title> +</head> +<body> +<p>Euro sign if decoded as UTF-8: €</p> +<p>a with diaeresis if decoded as UTF-8: ä</p> +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-7-child.html^headers^ b/docshell/test/browser/file_bug234628-7-child.html^headers^ new file mode 100644 index 000000000..2d1c08b9e --- /dev/null +++ b/docshell/test/browser/file_bug234628-7-child.html^headers^ @@ -0,0 +1 @@ +Content-Type: text/html; charset=utf-8 diff --git a/docshell/test/browser/file_bug234628-7.html b/docshell/test/browser/file_bug234628-7.html new file mode 100644 index 000000000..7cb506096 --- /dev/null +++ b/docshell/test/browser/file_bug234628-7.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="windows-1252"> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>meta declaration in parent and BOMless UTF-8 with HTTP charset in child</title> +</head> +<body> +<h1>meta declaration in parent and BOMless UTF-8 with HTTP charset in child</h1> + +<p>Euro sign if decoded as Windows-1252: </p> +<p>a with diaeresis if decoded as Windows-1252: </p> + +<iframe src="file_bug234628-7-child.html"></iframe> + +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-8-child.html b/docshell/test/browser/file_bug234628-8-child.html new file mode 100644 index 000000000..254e0fb2b --- /dev/null +++ b/docshell/test/browser/file_bug234628-8-child.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>meta declaration in parent and no declaration in child</title> +</head> +<body> +<p>Capital dje if decoded as Windows-1251: </p> + +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-8.html b/docshell/test/browser/file_bug234628-8.html new file mode 100644 index 000000000..b44e91801 --- /dev/null +++ b/docshell/test/browser/file_bug234628-8.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="windows-1251"> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>meta declaration in parent and no declaration in child</title> +</head> +<body> +<h1>meta declaration in parent and no declaration in child</h1> + +<p>Capital dje if decoded as Windows-1251: </p> + +<iframe src="file_bug234628-8-child.html"></iframe> + +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-9-child.html b/docshell/test/browser/file_bug234628-9-child.html new file mode 100644 index 000000000..a86b14d7e --- /dev/null +++ b/docshell/test/browser/file_bug234628-9-child.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html lang="en"> +<head> +<meta content="width=device-width, initial-scale=1" name="viewport"> +<title>UTF-16 with BOM in parent and no declaration in child</title> +</head> +<body> +<p>Euro sign if decoded as Windows-1251: </p> + +</body> +</html> + diff --git a/docshell/test/browser/file_bug234628-9.html b/docshell/test/browser/file_bug234628-9.html Binary files differnew file mode 100644 index 000000000..8a469da3a --- /dev/null +++ b/docshell/test/browser/file_bug234628-9.html diff --git a/docshell/test/browser/file_bug420605.html b/docshell/test/browser/file_bug420605.html new file mode 100644 index 000000000..8424b92f8 --- /dev/null +++ b/docshell/test/browser/file_bug420605.html @@ -0,0 +1,31 @@ +<head> +<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="/> + <title>Page Title for Bug 420605</title> +</head> +<body> + <h1>Fragment links</h1> + + <p>This page has a bunch of fragment links to sections below:</p> + + <ul> + <li><a id="firefox-link" href="#firefox">Firefox</a></li> + <li><a id="thunderbird-link" href="#thunderbird">Thunderbird</a></li> + <li><a id="seamonkey-link" href="#seamonkey">Seamonkey</a></li> + </ul> + + <p>And here are the sections:</p> + + <h2 id="firefox">Firefox</h2> + + <p>Firefox is a browser.</p> + + <h2 id="thunderbird">Thunderbird</h2> + + <p>Thunderbird is an email client</p> + + <h2 id="seamonkey">Seamonkey</h2> + + <p>Seamonkey is the all-in-one application.</p> + +</body> +</html> diff --git a/docshell/test/browser/file_bug422543_script.js b/docshell/test/browser/file_bug422543_script.js new file mode 100644 index 000000000..cd69df1ec --- /dev/null +++ b/docshell/test/browser/file_bug422543_script.js @@ -0,0 +1,98 @@ +const { utils: Cu, interfaces: Ci } = Components; +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +function SHistoryListener() { +} + +SHistoryListener.prototype = { + retval: true, + last: "initial", + + OnHistoryNewEntry: function (aNewURI) { + this.last = "newentry"; + }, + + OnHistoryGoBack: function (aBackURI) { + this.last = "goback"; + return this.retval; + }, + + OnHistoryGoForward: function (aForwardURI) { + this.last = "goforward"; + return this.retval; + }, + + OnHistoryGotoIndex: function (aIndex, aGotoURI) { + this.last = "gotoindex"; + return this.retval; + }, + + OnHistoryPurge: function (aNumEntries) { + this.last = "purge"; + return this.retval; + }, + + OnHistoryReload: function (aReloadURI, aReloadFlags) { + this.last = "reload"; + return this.retval; + }, + + OnHistoryReplaceEntry: function (aIndex) {}, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsISHistoryListener, + Ci.nsISupportsWeakReference]) +}; + +let testAPI = { + shistory: null, + listeners: [ new SHistoryListener(), + new SHistoryListener() ], + + init() { + this.shistory = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory; + for (let listener of this.listeners) { + this.shistory.addSHistoryListener(listener); + } + }, + + cleanup() { + for (let listener of this.listeners) { + this.shistory.removeSHistoryListener(listener); + } + this.shistory = null; + sendAsyncMessage("bug422543:cleanup:return", {}); + }, + + getListenerStatus() { + sendAsyncMessage("bug422543:getListenerStatus:return", + this.listeners.map(l => l.last)); + }, + + resetListeners() { + for (let listener of this.listeners) { + listener.last = "initial"; + } + + sendAsyncMessage("bug422543:resetListeners:return", {}); + }, + + notifyReload() { + let internal = this.shistory.QueryInterface(Ci.nsISHistoryInternal); + let rval = + internal.notifyOnHistoryReload(content.document.documentURIObject, 0); + sendAsyncMessage("bug422543:notifyReload:return", { rval }); + }, + + setRetval({ num, val }) { + this.listeners[num].retval = val; + sendAsyncMessage("bug422543:setRetval:return", {}); + }, +}; + +addMessageListener("bug422543:cleanup", () => { testAPI.cleanup(); }); +addMessageListener("bug422543:getListenerStatus", () => { testAPI.getListenerStatus(); }); +addMessageListener("bug422543:notifyReload", () => { testAPI.notifyReload(); }); +addMessageListener("bug422543:resetListeners", () => { testAPI.resetListeners(); }); +addMessageListener("bug422543:setRetval", (msg) => { testAPI.setRetval(msg.data); }); + +testAPI.init(); diff --git a/docshell/test/browser/file_bug503832.html b/docshell/test/browser/file_bug503832.html new file mode 100644 index 000000000..338631c8a --- /dev/null +++ b/docshell/test/browser/file_bug503832.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html> +<!-- +Test page for https://bugzilla.mozilla.org/show_bug.cgi?id=503832 +--> +<head> + <title>Page Title for Bug 503832</title> +</head> +<body> + <h1>Fragment links</h1> + + <p>This page has a bunch of fragment links to sections below:</p> + + <ul> + <li><a id="firefox-link" href="#firefox">Firefox</a></li> + <li><a id="thunderbird-link" href="#thunderbird">Thunderbird</a></li> + <li><a id="seamonkey-link" href="#seamonkey">Seamonkey</a></li> + </ul> + + <p>And here are the sections:</p> + + <h2 id="firefox">Firefox</h2> + + <p>Firefox is a browser.</p> + + <h2 id="thunderbird">Thunderbird</h2> + + <p>Thunderbird is an email client</p> + + <h2 id="seamonkey">Seamonkey</h2> + + <p>Seamonkey is the all-in-one application.</p> + +</body> +</html> diff --git a/docshell/test/browser/file_bug655270.html b/docshell/test/browser/file_bug655270.html new file mode 100644 index 000000000..0c08d982b --- /dev/null +++ b/docshell/test/browser/file_bug655270.html @@ -0,0 +1,11 @@ +<html> + +<head> + <link rel='icon' href='favicon_bug655270.ico'> +</head> + +<body> +Nothing to see here... +</body> + +</html> diff --git a/docshell/test/browser/file_bug670318.html b/docshell/test/browser/file_bug670318.html new file mode 100644 index 000000000..4cd9b454a --- /dev/null +++ b/docshell/test/browser/file_bug670318.html @@ -0,0 +1,23 @@ +<html><head> +<meta http-equiv="content-type" content="text/html; charset=UTF-8"> +<script> +function load() { + function next() { + if (count < 5) + iframe.src = 'data:text/html;charset=utf-8,iframe ' + (++count); + } + + var count = 0; + var iframe = document.createElement('iframe'); + iframe.onload = function () { setTimeout(next, 0) }; + document.body.appendChild(iframe); + + setTimeout(next, 0); +} +</script> +</head> + +<body onload="load()"> +Testcase +</body> +</html> diff --git a/docshell/test/browser/file_bug852909.pdf b/docshell/test/browser/file_bug852909.pdf Binary files differnew file mode 100644 index 000000000..89066463f --- /dev/null +++ b/docshell/test/browser/file_bug852909.pdf diff --git a/docshell/test/browser/file_bug852909.png b/docshell/test/browser/file_bug852909.png Binary files differnew file mode 100644 index 000000000..c7510d388 --- /dev/null +++ b/docshell/test/browser/file_bug852909.png diff --git a/docshell/test/browser/file_multiple_pushState.html b/docshell/test/browser/file_multiple_pushState.html new file mode 100644 index 000000000..c58a1a71b --- /dev/null +++ b/docshell/test/browser/file_multiple_pushState.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>Test multiple calls to history.pushState</title> + </head> + <body> + <h1>Ohai</h1> + </body> + <script type="text/javascript"> + window.history.pushState({}, "", "/bar/ABC?key=baz"); + window.history.pushState({}, "", "/bar/ABC/DEF?key=baz"); + </script> +</html> diff --git a/docshell/test/browser/frame-head.js b/docshell/test/browser/frame-head.js new file mode 100644 index 000000000..5cb3b1513 --- /dev/null +++ b/docshell/test/browser/frame-head.js @@ -0,0 +1,113 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Functions that are automatically loaded as frame scripts for +// timeline tests. + +var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; +var { Task } = Cu.import("resource://gre/modules/Task.jsm", {}); +var { Promise } = Cu.import('resource://gre/modules/Promise.jsm', {}); + +Cu.import("resource://gre/modules/Timer.jsm"); + +// Functions that look like mochitest functions but forward to the +// browser process. + +this.ok = function(value, message) { + sendAsyncMessage("browser:test:ok", { + value: !!value, + message: message}); +} + +this.is = function(v1, v2, message) { + ok(v1 == v2, message); +} + +this.info = function(message) { + sendAsyncMessage("browser:test:info", {message: message}); +} + +this.finish = function() { + sendAsyncMessage("browser:test:finish"); +} + +/* Start a task that runs some timeline tests in the ordinary way. + * + * @param array tests + * The tests to run. This is an array where each element + * is of the form { desc, searchFor, setup, check }. + * + * desc is the test description, a string. + * searchFor is a string or a function + * If a string, then when a marker with this name is + * found, marker-reading is stopped. + * If a function, then the accumulated marker array is + * passed to it, and marker reading stops when it returns + * true. + * setup is a function that takes the docshell as an argument. + * It should start the test. + * check is a function that takes an array of markers + * as an argument and checks the results of the test. + */ +this.timelineContentTest = function(tests) { + Task.spawn(function*() { + let docShell = content.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + + info("Start recording"); + docShell.recordProfileTimelineMarkers = true; + + for (let {desc, searchFor, setup, check} of tests) { + + info("Running test: " + desc); + + info("Flushing the previous markers if any"); + docShell.popProfileTimelineMarkers(); + + info("Running the test setup function"); + let onMarkers = timelineWaitForMarkers(docShell, searchFor); + setup(docShell); + info("Waiting for new markers on the docShell"); + let markers = yield onMarkers; + + // Cycle collection markers are non-deterministic, and none of these tests + // expect them to show up. + markers = markers.filter(m => m.name.indexOf("nsCycleCollector") === -1); + + info("Running the test check function"); + check(markers); + } + + info("Stop recording"); + docShell.recordProfileTimelineMarkers = false; + finish(); + }); +} + +function timelineWaitForMarkers(docshell, searchFor) { + if (typeof(searchFor) == "string") { + let searchForString = searchFor; + let f = function (markers) { + return markers.some(m => m.name == searchForString); + }; + searchFor = f; + } + + return new Promise(function(resolve, reject) { + let waitIterationCount = 0; + let maxWaitIterationCount = 10; // Wait for 2sec maximum + let markers = []; + + setTimeout(function timeoutHandler() { + let newMarkers = docshell.popProfileTimelineMarkers(); + markers = [...markers, ...newMarkers]; + if (searchFor(markers) || waitIterationCount > maxWaitIterationCount) { + resolve(markers); + } else { + setTimeout(timeoutHandler, 200); + waitIterationCount++; + } + }, 200); + }); +} diff --git a/docshell/test/browser/head.js b/docshell/test/browser/head.js new file mode 100644 index 000000000..24ca8f45b --- /dev/null +++ b/docshell/test/browser/head.js @@ -0,0 +1,99 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* Helper function for timeline tests. Returns an async task that is + * suitable for use as a particular timeline test. + * @param string frameScriptName + * Base name of the frame script file. + * @param string url + * URL to load. + */ +function makeTimelineTest(frameScriptName, url) { + info("in timelineTest"); + return Task.async(function*() { + info("in in timelineTest"); + waitForExplicitFinish(); + + yield timelineTestOpenUrl(url); + + const here = "chrome://mochitests/content/browser/docshell/test/browser/"; + + let mm = gBrowser.selectedBrowser.messageManager; + mm.loadFrameScript(here + "frame-head.js", false); + mm.loadFrameScript(here + frameScriptName, false); + + // Set up some listeners so that timeline tests running in the + // content process can forward their results to the main process. + mm.addMessageListener("browser:test:ok", function(message) { + ok(message.data.value, message.data.message); + }); + mm.addMessageListener("browser:test:info", function(message) { + info(message.data.message); + }); + mm.addMessageListener("browser:test:finish", function(ignore) { + gBrowser.removeCurrentTab(); + finish(); + }); + }); +} + +/* Open a URL for a timeline test. */ +function timelineTestOpenUrl(url) { + window.focus(); + + let tabSwitchPromise = new Promise((resolve, reject) => { + window.gBrowser.addEventListener("TabSwitchDone", function listener() { + window.gBrowser.removeEventListener("TabSwitchDone", listener); + resolve(); + }); + }); + + let loadPromise = new Promise(function(resolve, reject) { + let tab = window.gBrowser.selectedTab = window.gBrowser.addTab(url); + let linkedBrowser = tab.linkedBrowser; + + linkedBrowser.addEventListener("load", function onload() { + linkedBrowser.removeEventListener("load", onload, true); + resolve(tab); + }, true); + }); + + return Promise.all([tabSwitchPromise, loadPromise]).then(([_, tab]) => tab); +} + +/** + * Helper function for charset tests. It loads |url| in a new tab, + * runs |check1| in a ContentTask when the page is ready, switches the + * charset to |charset|, and then runs |check2| in a ContentTask when + * the page has finished reloading. + * + * |charset| and |check2| can be omitted, in which case the test + * finishes when |check1| completes. + */ +function runCharsetTest(url, check1, charset, check2) { + waitForExplicitFinish(); + + BrowserTestUtils.openNewForegroundTab(gBrowser, url, true).then(afterOpen); + + function afterOpen() { + if (charset) { + BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(afterChangeCharset); + + ContentTask.spawn(gBrowser.selectedBrowser, null, check1).then(() => { + BrowserSetForcedCharacterSet(charset); + }); + } else { + ContentTask.spawn(gBrowser.selectedBrowser, null, check1).then(() => { + gBrowser.removeCurrentTab(); + finish(); + }); + } + } + + function afterChangeCharset() { + ContentTask.spawn(gBrowser.selectedBrowser, null, check2).then(() => { + gBrowser.removeCurrentTab(); + finish(); + }); + } +} diff --git a/docshell/test/browser/print_postdata.sjs b/docshell/test/browser/print_postdata.sjs new file mode 100644 index 000000000..4175a2480 --- /dev/null +++ b/docshell/test/browser/print_postdata.sjs @@ -0,0 +1,22 @@ +const CC = Components.Constructor; +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream"); + +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/plain", false); + if (request.method == "GET") { + response.write(request.queryString); + } else { + var body = new BinaryInputStream(request.bodyInputStream); + + var avail; + var bytes = []; + + while ((avail = body.available()) > 0) + Array.prototype.push.apply(bytes, body.readByteArray(avail)); + + var data = String.fromCharCode.apply(null, bytes); + response.bodyOutputStream.write(data, data.length); + } +} diff --git a/docshell/test/browser/redirect_to_example.sjs b/docshell/test/browser/redirect_to_example.sjs new file mode 100644 index 000000000..eef5e49f4 --- /dev/null +++ b/docshell/test/browser/redirect_to_example.sjs @@ -0,0 +1,4 @@ +function handleRequest(request, response) { + response.setStatusLine(request.httpVersion, 302, "Moved Permanently"); + response.setHeader("Location", "http://example"); +} diff --git a/docshell/test/browser/test-form_sjis.html b/docshell/test/browser/test-form_sjis.html new file mode 100644 index 000000000..91c375dee --- /dev/null +++ b/docshell/test/browser/test-form_sjis.html @@ -0,0 +1,24 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/REC-html401-19991224/strict.dtd"> +<html> + <head> + <meta http-equiv="content-type" content="text/html; charset=windows-1251"> + <title>Shift_JIS in body and text area</title> + </head> + <body> + <h1>Incorrect meta charset</h1> + <h2>This page is encoded in Shift_JIS, but has an incorrect meta charset + claiming that it is windows-1251</h2> + <p id="testpar">jR[h́AׂĂ̕ɌŗL̔ԍt^܂</p> + <form> + <p> + <textarea id="testtextarea" rows=6 cols=60>jR[h́AׂĂ̕ɌŗL̔ԍt^܂</textarea> + <input id="testinput" type="text" size=60 value="jR[h́AׂĂ̕ɌŗL̔ԍt^܂"> + </p> + </form> + <h2>Expected text on load:</h2> + <p>ѓ†ѓjѓRЃ[ѓh‚НЃA‚·‚Ч‚Д‚М•¶Ћљ‚ЙЊЕ—L‚М”ФЌ†‚р•t—^‚µ‚Ь‚·</p> + <h2>Expected text on resetting the encoding to Shift_JIS:</h2> + <p>ユニコードは、すべての文字に固有の番号を付与します</p> + </body> +</html> diff --git a/docshell/test/browser/timelineMarkers-04.html b/docshell/test/browser/timelineMarkers-04.html new file mode 100644 index 000000000..829998387 --- /dev/null +++ b/docshell/test/browser/timelineMarkers-04.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="utf-8"></meta> + <title>markers test</title> +</head> +<body> + + <p>Test page</p> + + <script> + function do_xhr() { + const theURL = "timelineMarkers-04.html"; + + xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + dump("ReadyState = " + xhr.readyState + "\n"); + }; + xhr.open("get", theURL, true); + xhr.send(); + } + + window.addEventListener("dog", do_xhr, true); + + function do_promise() { + new Promise(function(resolve, reject) { + console.time("Bob"); + window.setTimeout(function() { + resolve(23); + }, 10); + }).then(function (val) { + console.timeEnd("Bob"); + }); + } + + window.addEventListener("promisetest", do_promise, true); + + var globalResolver; + function do_promise_script() { + new Promise(function(resolve, reject) { + console.time("Bob"); + globalResolver = resolve; + window.setTimeout("globalResolver(23);", 10); + }).then(function (val) { + console.timeEnd("Bob"); + }); + } + + window.addEventListener("promisescript", do_promise_script, true); + + </script> + +</body> +</html> + diff --git a/docshell/test/bug123696-subframe.html b/docshell/test/bug123696-subframe.html new file mode 100644 index 000000000..136d0a184 --- /dev/null +++ b/docshell/test/bug123696-subframe.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> + <head> + <script> + function change() { + return "<html><body>change</body></html>"; + } + function change2() { + return "<html><body>change2</body></html>"; + } + </script> + </head> + <body> + <iframe src="javascript:parent.change()"></iframe> + </body> +</html> diff --git a/docshell/test/bug369814.jar b/docshell/test/bug369814.jar Binary files differnew file mode 100644 index 000000000..86b9c8c09 --- /dev/null +++ b/docshell/test/bug369814.jar diff --git a/docshell/test/bug369814.zip b/docshell/test/bug369814.zip Binary files differnew file mode 100644 index 000000000..ed8234dbc --- /dev/null +++ b/docshell/test/bug369814.zip diff --git a/docshell/test/bug404548-subframe.html b/docshell/test/bug404548-subframe.html new file mode 100644 index 000000000..0afd17544 --- /dev/null +++ b/docshell/test/bug404548-subframe.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<html> +<body onload="setTimeout(function() { window.location = "data:text/html,<body onload='window.opener.finishTest()'>" }, 10)"> +<iframe src="data:text/html,<body onpagehide='var p = window.parent.opener; var e = window.frameElement; e.parentNode.removeChild(e); if (e.parentNode == null && e.contentWindow == null) { p.firstRemoved = true; }'>"> +</iframe> +<iframe src="data:text/html,<body onpagehide='window.parent.opener.secondHidden = true;'>"> +</iframe> diff --git a/docshell/test/bug413310-post.sjs b/docshell/test/bug413310-post.sjs new file mode 100644 index 000000000..b8d85dd3c --- /dev/null +++ b/docshell/test/bug413310-post.sjs @@ -0,0 +1,7 @@ +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/html"); + response.write("<body onload='window.parent.onloadCount++'>" + + request.method + " " + + Date.now() + + "</body>"); +} diff --git a/docshell/test/bug413310-subframe.html b/docshell/test/bug413310-subframe.html new file mode 100644 index 000000000..bcff1886f --- /dev/null +++ b/docshell/test/bug413310-subframe.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<html> + <body onload="window.parent.onloadCount++"> + <form action="bug413310-post.sjs" method="POST"> + </form> + </body> +</html> diff --git a/docshell/test/bug529119-window.html b/docshell/test/bug529119-window.html new file mode 100644 index 000000000..f1908835a --- /dev/null +++ b/docshell/test/bug529119-window.html @@ -0,0 +1,7 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Test bug 529119, sub-window</title> +<body onload="window.opener.windowLoaded();"> +</body> +</html> diff --git a/docshell/test/bug530396-noref.sjs b/docshell/test/bug530396-noref.sjs new file mode 100644 index 000000000..1ed5f7aa2 --- /dev/null +++ b/docshell/test/bug530396-noref.sjs @@ -0,0 +1,20 @@ +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/html"); + response.setHeader("Cache-Control", "no-cache"); + response.write("<body onload='"); + + if (!request.hasHeader('Referer')) { + response.write("window.parent.onloadCount++;"); + } + + if (request.queryString == "newwindow") { + response.write("if (window.opener) { window.opener.parent.onloadCount++; window.opener.parent.doNextStep(); }"); + response.write("if (!window.opener) window.close();"); + response.write("'>"); + } else { + response.write("window.parent.doNextStep();'>"); + } + + response.write(request.method + " " + Date.now()); + response.write("</body>"); +} diff --git a/docshell/test/bug530396-subframe.html b/docshell/test/bug530396-subframe.html new file mode 100644 index 000000000..be81b9f14 --- /dev/null +++ b/docshell/test/bug530396-subframe.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<html> + <body onload="window.parent.onloadCount++"> + <a href="bug530396-noref.sjs" rel="noreferrer foo" id="target1">bug530396-noref.sjs</a> + <a href="bug530396-noref.sjs?newwindow" rel="nofollow noreferrer" id="target2" target="newwindow">bug530396-noref.sjs with new window</a> + </body> +</html> diff --git a/docshell/test/bug570341_recordevents.html b/docshell/test/bug570341_recordevents.html new file mode 100644 index 000000000..51fc1cd76 --- /dev/null +++ b/docshell/test/bug570341_recordevents.html @@ -0,0 +1,21 @@ +<html> +<head> +<script> + var start = Date.now(); + window._testing_js_start = Date.now(); + window['_testing_js_after_' + document.readyState] = start; + document.addEventListener('DOMContentLoaded', + function () { + window._testing_evt_DOMContentLoaded = Date.now(); + }, true); + document.addEventListener('readystatechange', function(){ + window['_testing_evt_DOM_' + document.readyState] = Date.now(); + }, true); + function recordLoad() { + window._testing_evt_load = Date.now(); + } +</script> +</head> +<body onload="recordLoad()">This document collects time +for events related to the page load progress.</body> +</html> diff --git a/docshell/test/bug668513_redirect.html b/docshell/test/bug668513_redirect.html new file mode 100644 index 000000000..1b8f66c63 --- /dev/null +++ b/docshell/test/bug668513_redirect.html @@ -0,0 +1 @@ +<html><body>This document is redirected to a blank document.</body></html> diff --git a/docshell/test/bug668513_redirect.html^headers^ b/docshell/test/bug668513_redirect.html^headers^ new file mode 100644 index 000000000..0e785833c --- /dev/null +++ b/docshell/test/bug668513_redirect.html^headers^ @@ -0,0 +1,2 @@ +HTTP 302 Moved Temporarily +Location: navigation/blank.html diff --git a/docshell/test/bug691547_frame.html b/docshell/test/bug691547_frame.html new file mode 100644 index 000000000..00172f711 --- /dev/null +++ b/docshell/test/bug691547_frame.html @@ -0,0 +1,12 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=691547 +--> +<head> + <title>Test for Bug 691547</title> +</head> +<body> +<iframe style="width:95%"></iframe> +</body> +</html> diff --git a/docshell/test/chrome/112564_nocache.html b/docshell/test/chrome/112564_nocache.html new file mode 100644 index 000000000..29fb990b8 --- /dev/null +++ b/docshell/test/chrome/112564_nocache.html @@ -0,0 +1,10 @@ +<html> +<head> +<title>test1</title> +</head> +<body> +<p> +This document will be sent with a no-cache cache-control header. When sent over a secure connection, it should not be stored in bfcache. +</p> +</body> +</html> diff --git a/docshell/test/chrome/112564_nocache.html^headers^ b/docshell/test/chrome/112564_nocache.html^headers^ new file mode 100644 index 000000000..c829a41ae --- /dev/null +++ b/docshell/test/chrome/112564_nocache.html^headers^ @@ -0,0 +1 @@ +Cache-control: no-cache diff --git a/docshell/test/chrome/215405_nocache.html b/docshell/test/chrome/215405_nocache.html new file mode 100644 index 000000000..c7d48c4eb --- /dev/null +++ b/docshell/test/chrome/215405_nocache.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html style="height: 100%"> +<head> + <title>test1</title> +</head> +<body style="height: 100%"> + <input type="text" id="inp" value=""> + </input> + <div style="height: 50%">Some text</div> + <div style="height: 50%">Some text</div> + <div style="height: 50%">Some text</div> + <div style="height: 50%; width: 300%">Some more text</div> +</body> +</html> diff --git a/docshell/test/chrome/215405_nocache.html^headers^ b/docshell/test/chrome/215405_nocache.html^headers^ new file mode 100644 index 000000000..c829a41ae --- /dev/null +++ b/docshell/test/chrome/215405_nocache.html^headers^ @@ -0,0 +1 @@ +Cache-control: no-cache diff --git a/docshell/test/chrome/215405_nostore.html b/docshell/test/chrome/215405_nostore.html new file mode 100644 index 000000000..4f5bd0f4f --- /dev/null +++ b/docshell/test/chrome/215405_nostore.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html style="height: 100%"> +<head> + <title>test1</title> +</head> +<body style="height: 100%"> + <input type="text" id="inp" value=""> + </input> + <div style="height: 50%">Some text</div> + <div style="height: 50%">Some text</div> + <div style="height: 50%">Some text</div> + <div style="height: 50%; width: 350%">Some more text</div> +</body> +</html> diff --git a/docshell/test/chrome/215405_nostore.html^headers^ b/docshell/test/chrome/215405_nostore.html^headers^ new file mode 100644 index 000000000..59ba29610 --- /dev/null +++ b/docshell/test/chrome/215405_nostore.html^headers^ @@ -0,0 +1 @@ +Cache-control: no-store diff --git a/docshell/test/chrome/582176_dummy.html b/docshell/test/chrome/582176_dummy.html new file mode 100644 index 000000000..3b18e512d --- /dev/null +++ b/docshell/test/chrome/582176_dummy.html @@ -0,0 +1 @@ +hello world diff --git a/docshell/test/chrome/582176_xml.xml b/docshell/test/chrome/582176_xml.xml new file mode 100644 index 000000000..d3dd576df --- /dev/null +++ b/docshell/test/chrome/582176_xml.xml @@ -0,0 +1,2 @@ +<?xml-stylesheet type="text/xsl" href="582176_xslt.xsl"?> +<out/> diff --git a/docshell/test/chrome/582176_xslt.xsl b/docshell/test/chrome/582176_xslt.xsl new file mode 100644 index 000000000..595741689 --- /dev/null +++ b/docshell/test/chrome/582176_xslt.xsl @@ -0,0 +1,8 @@ +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + <xsl:template match="out"> + <html> + <head><title>XSLT result doc</title></head> + <body><p>xslt result</p></body> + </html> + </xsl:template> +</xsl:stylesheet> diff --git a/docshell/test/chrome/662200a.html b/docshell/test/chrome/662200a.html new file mode 100644 index 000000000..0b9ead6f3 --- /dev/null +++ b/docshell/test/chrome/662200a.html @@ -0,0 +1,8 @@ +<html> + <head> + <title>A</title> + </head> + <body> + <a id="link" href="662200b.html">Next</a> + </body> +</html> diff --git a/docshell/test/chrome/662200b.html b/docshell/test/chrome/662200b.html new file mode 100644 index 000000000..91e6b971d --- /dev/null +++ b/docshell/test/chrome/662200b.html @@ -0,0 +1,8 @@ +<html> + <head> + <title>B</title> + </head> + <body> + <a id="link" href="662200c.html">Next</a> + </body> +</html> diff --git a/docshell/test/chrome/662200c.html b/docshell/test/chrome/662200c.html new file mode 100644 index 000000000..bc00e6b14 --- /dev/null +++ b/docshell/test/chrome/662200c.html @@ -0,0 +1,7 @@ +<html> + <head> + <title>C</title> + </head> + <body> + </body> +</html> diff --git a/docshell/test/chrome/89419.html b/docshell/test/chrome/89419.html new file mode 100644 index 000000000..b36b8d788 --- /dev/null +++ b/docshell/test/chrome/89419.html @@ -0,0 +1,7 @@ +<html> +<head> +<title>Bug 89419</title> +</head> +<body> +<img src="http://mochi.test:8888/tests/docshell/test/chrome/bug89419.sjs"> +</body> diff --git a/docshell/test/chrome/92598_nostore.html b/docshell/test/chrome/92598_nostore.html new file mode 100644 index 000000000..47bb90441 --- /dev/null +++ b/docshell/test/chrome/92598_nostore.html @@ -0,0 +1,10 @@ +<html> +<head> +<title>test1</title> +</head> +<body> +<p> +This document will be sent with a no-store cache-control header. It should not be stored in bfcache. +</p> +</body> +</html> diff --git a/docshell/test/chrome/92598_nostore.html^headers^ b/docshell/test/chrome/92598_nostore.html^headers^ new file mode 100644 index 000000000..59ba29610 --- /dev/null +++ b/docshell/test/chrome/92598_nostore.html^headers^ @@ -0,0 +1 @@ +Cache-control: no-store diff --git a/docshell/test/chrome/allowContentRetargeting.sjs b/docshell/test/chrome/allowContentRetargeting.sjs new file mode 100644 index 000000000..96e467ef6 --- /dev/null +++ b/docshell/test/chrome/allowContentRetargeting.sjs @@ -0,0 +1,7 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function handleRequest(req, resp) { + resp.setHeader("Content-Type", "application/octet-stream", false); + resp.write("hi"); +} diff --git a/docshell/test/chrome/blue.png b/docshell/test/chrome/blue.png Binary files differnew file mode 100644 index 000000000..8df58f3a5 --- /dev/null +++ b/docshell/test/chrome/blue.png diff --git a/docshell/test/chrome/bug112564_window.xul b/docshell/test/chrome/bug112564_window.xul new file mode 100644 index 000000000..e9c05ca9f --- /dev/null +++ b/docshell/test/chrome/bug112564_window.xul @@ -0,0 +1,117 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="112564Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="onLoad();" + title="112564 test"> + + <script type="application/javascript"><![CDATA[ + const LISTEN_EVENTS = ["load", "unload", "pageshow", "pagehide"]; + + var gBrowser; + var gTestsIterator; + var gExpected = []; + + function ok(condition, message) { + window.opener.wrappedJSObject.SimpleTest.ok(condition, message); + } + + function is(a, b, message) { + window.opener.wrappedJSObject.SimpleTest.is(a, b, message); + } + + function finish() { + for (let eventType of LISTEN_EVENTS) { + gBrowser.removeEventListener(eventType, eventListener, true); + } + + // Work around bug 467960 + var history = gBrowser.webNavigation.sessionHistory; + history.PurgeHistory(history.count); + + window.close(); + window.opener.wrappedJSObject.SimpleTest.finish(); + } + + function onLoad() { + gBrowser = document.getElementById("content"); + for (let eventType of LISTEN_EVENTS) { + gBrowser.addEventListener(eventType, eventListener, true); + } + + gTestsIterator = testsIterator(); + nextTest(); + } + + function eventListener(event) { + ok(gExpected.length >= 1, "Unexpected event " + event.type); + if (gExpected.length == 0) { + // in case of unexpected event, try to continue anyway + setTimeout(nextTest, 0); + return; + } + + var exp = gExpected.shift(); + is(event.type, exp.type, "Invalid event received"); + if (typeof(exp.persisted) != "undefined") { + is(event.persisted, exp.persisted, "Invalid persisted state"); + } + if (exp.title) { + ok(event.originalTarget instanceof HTMLDocument, + "originalTarget not a HTMLDocument"); + is(event.originalTarget.title, exp.title, "titles don't match"); + } + + if (gExpected.length == 0) { + setTimeout(nextTest, 0); + } + } + + function nextTest() { + try { + gTestsIterator.next(); + } catch (err if err instanceof StopIteration) { + finish(); + } + } + + function testsIterator() { + // Load a secure page with a no-cache header, followed by a simple page. + // no-cache should not interfere with the bfcache in the way no-store + // does. + var test1DocURI = "https://example.com:443/tests/docshell/test/chrome/112564_nocache.html"; + + gExpected = [{type: "pagehide", persisted: true}, + {type: "load", title: "test1"}, + {type: "pageshow", title: "test1", persisted: false}]; + gBrowser.loadURI(test1DocURI); + yield undefined; + + var test2Doc = "data:text/html,<html><head><title>test2</title></head>" + + "<body>test2</body></html>"; + + gExpected = [{type: "pagehide", title: "test1", persisted: true}, + {type: "load", title: "test2"}, + {type: "pageshow", title: "test2", persisted: false}]; + gBrowser.loadURI(test2Doc); + yield undefined; + + // Now go back in history. First page has been cached. + // Check persisted property to confirm + gExpected = [{type: "pagehide", title: "test2", persisted: true}, + {type: "pageshow", title: "test1", persisted: true}]; + gBrowser.goBack(); + yield undefined; + } + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/bug113934_window.xul b/docshell/test/chrome/bug113934_window.xul new file mode 100644 index 000000000..9dbfc3b8f --- /dev/null +++ b/docshell/test/chrome/bug113934_window.xul @@ -0,0 +1,161 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<window title="Mozilla Bug 113934" onload="doTheTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <hbox> + <vbox id="box1"> + </vbox> + <vbox id="box2"> + </vbox> + <spacer flex="1"/> + </hbox> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + var imports = [ "SimpleTest", "is", "isnot", "ok", "snapshotWindow", + "compareSnapshots", "onerror" ]; + for (var name of imports) { + window[name] = window.opener.wrappedJSObject[name]; + } + + function $(id) { + return document.getElementById(id); + } + + function addBrowser(parent, id, width, height) { + var b = + document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "browser"); + var type = window.location.search.slice(1); + is(type == "chrome" || type == "content", true, "Unexpected type"); + b.setAttribute("type", type); + b.setAttribute("id", id); + b.setAttribute("width", width); + b.setAttribute("height", height); + $(parent).appendChild(b); + } + addBrowser("box1", "f1", 300, 200); + addBrowser("box1", "f2", 300, 200); + addBrowser("box2", "f3", 30, 200); + + /** Test for Bug 113934 **/ + var doc1 = + "data:text/html,<html><body onbeforeunload='document.documentElement.textContent = \"\"' onunload='document.documentElement.textContent = \"\"' onpagehide='document.documentElement.textContent = \"\"'>This is a test</body></html>"; + var doc2 = "data:text/html,<html><head></head><body>This is a second test</body></html>"; + + + $("f1").setAttribute("src", doc1); + $("f2").setAttribute("src", doc2); + $("f3").setAttribute("src", doc2); + + function doTheTest() { + var s1 = snapshotWindow($("f1").contentWindow); + var s2 = snapshotWindow($("f2").contentWindow); + var s3 = snapshotWindow($("f3").contentWindow); + + // This test is broken - see bug 1090274 + //ok(!compareSnapshots(s2, s3, true)[0], + // "Should look different due to different sizing"); + + function getDOM(id) { + return $(id).contentDocument.documentElement.innerHTML; + } + + var dom1 = getDOM("f1"); + + var dom2 = getDOM("f2"); + $("f2").contentDocument.body.textContent = "Modified the text"; + var dom2star = getDOM("f2"); + isnot(dom2, dom2star, "We changed the DOM!"); + + $("f1").swapDocShells($("f2")); + // now we have doms 2*, 1, 2 in the frames + + is(getDOM("f1"), dom2star, "Shouldn't have changed the DOM on swap"); + is(getDOM("f2"), dom1, "Shouldn't have fired event handlers"); + + // Test for bug 480149 + // The DOMLink* events are dispatched asynchronously, thus I cannot + // just include the <link> element in the initial DOM and swap the + // docshells. Instead, the link element is added now. Then, when the + // first DOMLinkAdded event (which is a result of the actual addition) + // is dispatched, the docshells are swapped and the pageshow and pagehide + // events are tested. Only then, we wait for the DOMLink* events, + // which are a result of swapping the docshells. + var DOMLinkListener = { + _afterFirst: false, + _removedDispatched: false, + _addedDispatched: false, + handleEvent: function(aEvent) { + if (!this._afterFirst) { + is(aEvent.type, "DOMLinkAdded"); + + var strs = { "f1": "", "f3" : "" }; + function attachListener(node, type) { + var listener = function(e) { + if (strs[node.id]) strs[node.id] += " "; + strs[node.id] += node.id + ".page" + type; + } + node.addEventListener("page" + type, listener, false); + + listener.detach = function() { + node.removeEventListener("page" + type, listener, false); + } + return listener; + } + + var l1 = attachListener($("f1"), "show"); + var l2 = attachListener($("f1"), "hide"); + var l3 = attachListener($("f3"), "show"); + var l4 = attachListener($("f3"), "hide"); + + $("f1").swapDocShells($("f3")); + // now we have DOMs 2, 1, 2* in the frames + + l1.detach(); + l2.detach(); + l3.detach(); + l4.detach(); + + var s1_new = snapshotWindow($("f1").contentWindow); + var [same, first, second] = compareSnapshots(s1_new, s2, true); + ok(same, "Should reflow on swap", "Expected " + second + " but got " + first); + + is(strs["f1"], "f1.pagehide f1.pageshow"); + is(strs["f3"], "f3.pagehide f3.pageshow"); + this._afterFirst = true; + return; + } + if (aEvent.type == "DOMLinkAdded") { + is(this._addedDispatched, false); + this._addedDispatched = true; + } + else { + is(this._removedDispatched, false); + this._removedDispatched = true; + } + + if (this._addedDispatched && this._removedDispatched) { + $("f1").removeEventListener("DOMLinkAdded", this, false); + $("f1").removeEventListener("DOMLinkRemoved", this, false); + $("f3").removeEventListener("DOMLinkAdded", this, false); + $("f3").removeEventListener("DOMLinkRemoved", this, false); + window.close(); + SimpleTest.finish(); + } + } + }; + + $("f1").addEventListener("DOMLinkAdded", DOMLinkListener, false); + $("f1").addEventListener("DOMLinkRemoved", DOMLinkListener, false); + $("f3").addEventListener("DOMLinkAdded", DOMLinkListener, false); + $("f3").addEventListener("DOMLinkRemoved", DOMLinkListener, false); + + var linkElement = $("f1").contentDocument.createElement("link"); + linkElement.setAttribute("rel", "alternate"); + linkElement.setAttribute("href", "about:blank"); + $("f1").contentDocument.documentElement.firstChild.appendChild(linkElement); + } + + ]]></script> +</window> diff --git a/docshell/test/chrome/bug215405_window.xul b/docshell/test/chrome/bug215405_window.xul new file mode 100644 index 000000000..1935eddee --- /dev/null +++ b/docshell/test/chrome/bug215405_window.xul @@ -0,0 +1,167 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="215405Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="onLoad();" + title="215405 test"> + + <script type="application/javascript"><![CDATA[ + var imports = [ "SimpleTest", "is", "isnot", "ok"]; + for (var name of imports) { + window[name] = window.opener.wrappedJSObject[name]; + } + + const text="MOZILLA"; + const nostoreURI = "http://mochi.test:8888/tests/docshell/test/chrome/" + + "215405_nostore.html"; + const nocacheURI = "https://example.com:443/tests/docshell/test/chrome/" + + "215405_nocache.html"; + + var gBrowser; + var gTestsIterator; + var scrollX = 0; + var scrollY = 0; + + function finish() { + gBrowser.removeEventListener("pageshow", eventListener, true); + // Work around bug 467960 + var history = gBrowser.webNavigation.sessionHistory; + history.PurgeHistory(history.count); + + window.close(); + window.opener.wrappedJSObject.SimpleTest.finish(); + } + + function onLoad(e) { + gBrowser = document.getElementById("content"); + gBrowser.addEventListener("pageshow", eventListener, true); + + gTestsIterator = testsIterator(); + nextTest(); + } + + function eventListener(event) { + setTimeout(nextTest, 0); + } + + function nextTest() { + try { + gTestsIterator.next(); + } catch (err if err instanceof StopIteration) { + finish(); + } + } + + function testsIterator() { + // No-store tests + var testName = "[nostore]"; + + // Load a page with a no-store header + gBrowser.loadURI(nostoreURI); + yield undefined; + + + // Now that the page has loaded, amend the form contents + var form = gBrowser.contentDocument.getElementById("inp"); + form.value = text; + + // Attempt to scroll the page + var originalXPosition = gBrowser.contentWindow.scrollX; + var originalYPosition = gBrowser.contentWindow.scrollY; + var scrollToX = gBrowser.contentWindow.scrollMaxX; + var scrollToY = gBrowser.contentWindow.scrollMaxY; + gBrowser.contentWindow.scrollBy(scrollToX, scrollToY); + + // Save the scroll position for future comparison + scrollX = gBrowser.contentWindow.scrollX; + scrollY = gBrowser.contentWindow.scrollY; + isnot(scrollX, originalXPosition, + testName + " failed to scroll window horizontally"); + isnot(scrollY, originalYPosition, + testName + " failed to scroll window vertically"); + + // Load a new document into the browser + var simple = "data:text/html,<html><head><title>test2</title></head>" + + "<body>test2</body></html>"; + gBrowser.loadURI(simple); + yield undefined; + + + // Now go back in history. First page should not have been cached. + gBrowser.goBack(); + yield undefined; + + + // First uncacheable page will now be reloaded. Check scroll position + // restored, and form contents not + is(gBrowser.contentWindow.scrollX, scrollX, testName + + " horizontal axis scroll position not correctly restored"); + is(gBrowser.contentWindow.scrollY, scrollY, testName + + " vertical axis scroll position not correctly restored"); + var formValue = gBrowser.contentDocument.getElementById("inp").value; + isnot(formValue, text, testName + " form value incorrectly restored"); + + + // https no-cache + testName = "[nocache]"; + + // Load a page with a no-cache header. This should not be + // restricted like no-store (bug 567365) + gBrowser.loadURI(nocacheURI); + yield undefined; + + + // Now that the page has loaded, amend the form contents + form = gBrowser.contentDocument.getElementById("inp"); + form.value = text; + + // Attempt to scroll the page + originalXPosition = gBrowser.contentWindow.scrollX; + originalYPosition = gBrowser.contentWindow.scrollY; + scrollToX = gBrowser.contentWindow.scrollMaxX; + scrollToY = gBrowser.contentWindow.scrollMaxY; + gBrowser.contentWindow.scrollBy(scrollToX, scrollToY); + + // Save the scroll position for future comparison + scrollX = gBrowser.contentWindow.scrollX; + scrollY = gBrowser.contentWindow.scrollY; + isnot(scrollX, originalXPosition, + testName + " failed to scroll window horizontally"); + isnot(scrollY, originalYPosition, + testName + " failed to scroll window vertically"); + + gBrowser.loadURI(simple); + yield undefined; + + + // Now go back in history to the cached page. + gBrowser.goBack(); + yield undefined; + + + // First page will now be reloaded. Check scroll position + // and form contents are restored + is(gBrowser.contentWindow.scrollX, scrollX, testName + + " horizontal axis scroll position not correctly restored"); + is(gBrowser.contentWindow.scrollY, scrollY, testName + + " vertical axis scroll position not correctly restored"); + var formValue = gBrowser.contentDocument.getElementById("inp").value; + is(formValue, text, testName + " form value not correctly restored"); + + // nextTest has to be called from here, as no events are fired in this + // step + setTimeout(nextTest, 0); + yield undefined; + } + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/bug293235.html b/docshell/test/chrome/bug293235.html new file mode 100644 index 000000000..458f88431 --- /dev/null +++ b/docshell/test/chrome/bug293235.html @@ -0,0 +1,13 @@ +<html> + <head> + <title>Bug 293235 page1</title> + <style type="text/css"> + a:visited, a.forcevisited.forcevisited { color: rgb(128, 0, 128); } + a:link, a.forcelink.forcelink { color: rgb(0, 0, 128); } + a:focus { color: rgb(128, 0, 0); } + </style> + </head> + <body> + <a id="link1" href="bug293235_p2.html">This is a test link.</a> + </body> +</html> diff --git a/docshell/test/chrome/bug293235_p2.html b/docshell/test/chrome/bug293235_p2.html new file mode 100644 index 000000000..2de067b80 --- /dev/null +++ b/docshell/test/chrome/bug293235_p2.html @@ -0,0 +1,8 @@ +<html> + <head> + <title>Bug 293235 page2</title> + </head> + <body> + Nothing to see here, move along. + </body> +</html> diff --git a/docshell/test/chrome/bug293235_window.xul b/docshell/test/chrome/bug293235_window.xul new file mode 100644 index 000000000..b5b359d48 --- /dev/null +++ b/docshell/test/chrome/bug293235_window.xul @@ -0,0 +1,162 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="293235Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="setTimeout(nextTest,0);" + title="bug 293235 test"> + + <script type="text/javascript" + src="chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js"/> + <script type="text/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SpecialPowersObserverAPI.js"/> + <script type="text/javascript" + src="chrome://mochikit/content/tests/SimpleTest/ChromePowers.js"/> + <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" /> + <script type="application/javascript" src="docshell_helpers.js" /> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script> + + <script type="application/javascript"><![CDATA[ + var Ci = Components.interfaces; + var Cc = Components.classes; + Components.utils.import("resource://gre/modules/NetUtil.jsm"); + + // Define the generator-iterator for the tests. + var tests = testIterator(); + + //// + // Execute the next test in the generator function. + // + function nextTest() { + tests.next(); + } + + // Return the Element object for the specified element id + function $(id) { return TestWindow.getDocument().getElementById(id); } + + //// + // Generator function for test steps for bug 293235: + // A visited link should have the :visited style applied + // to it when displayed on a page which was fetched from + // the bfcache. + // + function testIterator() + { + // Register our observer to know when the link lookup is complete. + let testURI = NetUtil.newURI(getHttpUrl("bug293235_p2.html")); + let os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + const URI_VISITED_RESOLUTION_TOPIC = "visited-status-resolution"; + let observer = { + notified: false, + observe: function(aSubject, aTopic, aData) + { + if (!testURI.equals(aSubject.QueryInterface(Ci.nsIURI))) { + return; + } + is(aTopic, URI_VISITED_RESOLUTION_TOPIC, "Unexpected topic"); + this.notified = true; + + // Cleanup after ourselves... + os.removeObserver(this, URI_VISITED_RESOLUTION_TOPIC); + }, + }; + os.addObserver(observer, URI_VISITED_RESOLUTION_TOPIC, false); + function notified() { + return observer.notified; + } + + // Load a test page containing a link that should be initially + // blue, per the :link style. + doPageNavigation({ + uri: getHttpUrl("bug293235.html"), + onNavComplete: nextTest + }); + yield undefined; + + // Before we go any further, make sure our link has been notified. + waitForTrue(notified, nextTest); + yield undefined; + + // Now that we've been notified, we can check our link color. + // Since we can't use getComputedStyle() for this because + // getComputedStyle lies about styles that result from :visited, + // we have to take snapshots. + // First, take two reference snapshots. + var link1 = $("link1"); + link1.className = "forcelink"; + var refLink = snapshotWindow(TestWindow.getWindow()); + link1.className = "forcevisited"; + var refVisited = snapshotWindow(TestWindow.getWindow()); + link1.className = ""; + function snapshotsEqual(snap1, snap2) { + return compareSnapshots(snap1, snap2, true)[0]; + } + ok(!snapshotsEqual(refLink, refVisited), "references should not match"); + ok(snapshotsEqual(refLink, snapshotWindow(TestWindow.getWindow())), + "link should initially be blue"); + + let observedVisit = false, observedPageShow = false; + function maybeRunNextTest() { + ok(true, "maybe run next test? visited: " + observedVisit + " pageShow: " + observedPageShow); + if (observedVisit && observedPageShow) + nextTest(); + } + + // Because adding visits is async, we will not be notified imemdiately. + let visitObserver = { + observe: function(aSubject, aTopic, aData) + { + if (!testURI.equals(aSubject.QueryInterface(Ci.nsIURI))) { + return; + } + os.removeObserver(this, aTopic); + observedVisit = true; + maybeRunNextTest(); + }, + }; + os.addObserver(visitObserver, "uri-visit-saved", false); + // Load the page that the link on the previous page points to. + doPageNavigation({ + uri: getHttpUrl("bug293235_p2.html"), + onNavComplete: function() { + observedPageShow = true; + maybeRunNextTest(); + } + }); + yield undefined; + + // And the nodes get notified after the "link-visited" topic, so + // we need to execute soon... + SimpleTest.executeSoon(nextTest); + yield undefined; + + // Go back, verify the original page was loaded from the bfcache, + // and verify that the link is now purple, per the + // :visited style. + doPageNavigation({ + back: true, + eventsToListenFor: ["pageshow"], + expectedEvents: [ { type: "pageshow", + persisted: true, + title: "Bug 293235 page1" } ], + onNavComplete: nextTest + }); + yield undefined; + + // Now we can test the link color. + ok(snapshotsEqual(refVisited, snapshotWindow(TestWindow.getWindow())), + "visited link should be purple"); + + // Tell the framework the test is finished. Include the final 'yield' + // statement to prevent a StopIteration exception from being thrown. + finish(); + yield undefined; + } + + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/bug294258_testcase.html b/docshell/test/chrome/bug294258_testcase.html new file mode 100644 index 000000000..cd80fefd0 --- /dev/null +++ b/docshell/test/chrome/bug294258_testcase.html @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" +"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title>Bug 294258 Testcase</title> + <meta http-equiv="Content-Type" content="application/xhtml+xml"/> + <style type="text/css"> + * { + font-family: monospace; + } + </style> + </head> + <body> + <div> + <p> + input type="text": <input id="text" type="text"/> + </p> + <p> + input type="checkbox": <input id="checkbox" type="checkbox"/> + </p> + <p> + input type="file": <input id="file" type="file"/> + </p> + <p> + input type="radio": + <input type="radio" id="radio1" name="radio" value="radio1"/> + <input id="radio2" type="radio" name="radio" value="radio2"/> + </p> + <p> + textarea: <textarea id="textarea" rows="4" cols="80"></textarea> + </p> + <p> + select -> option: <select id="select"> + <option>1</option> + <option>2</option> + <option>3</option> + <option>4</option> + </select> + </p> + </div> + </body> +</html> diff --git a/docshell/test/chrome/bug294258_window.xul b/docshell/test/chrome/bug294258_window.xul new file mode 100644 index 000000000..f25097eab --- /dev/null +++ b/docshell/test/chrome/bug294258_window.xul @@ -0,0 +1,76 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="294258Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="setTimeout(nextTest,0);" + title="bug 294258 test"> + + <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" /> + <script type="application/javascript" src="docshell_helpers.js" /> + <script type="application/javascript"><![CDATA[ + + // Define the generator-iterator for the tests. + var tests = testIterator(); + + //// + // Execute the next test in the generator function. + // + function nextTest() { + tests.next(); + } + + function $(id) { return TestWindow.getDocument().getElementById(id); } + + //// + // Generator function for test steps for bug 294258: + // Form values should be preserved on reload. + // + function testIterator() + { + // Load a page containing a form. + doPageNavigation( { + uri: getHttpUrl("bug294258_testcase.html"), + onNavComplete: nextTest + } ); + yield undefined; + + // Change the data for each of the form fields, and reload. + $("text").value = "text value"; + $("checkbox").checked = true; + var file = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("TmpD", Components.interfaces.nsILocalFile); + file.append("294258_test.file"); + file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666); + filePath = file.path; + $("file").value = filePath; + $("textarea").value = "textarea value"; + $("radio1").checked = true; + $("select").selectedIndex = 2; + doPageNavigation( { + reload: true, + onNavComplete: nextTest + } ); + yield undefined; + + // Verify that none of the form data has changed. + is($("text").value, "text value", "Text value changed"); + is($("checkbox").checked, true, "Checkbox value changed"); + is($("file").value, filePath, "File value changed"); + is($("textarea").value, "textarea value", "Textarea value changed"); + is($("radio1").checked, true, "Radio value changed"); + is($("select").selectedIndex, 2, "Select value changed"); + + // Tell the framework the test is finished. Include the final 'yield' + // statement to prevent a StopIteration exception from being thrown. + finish(); + yield undefined; + } + + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/bug298622_window.xul b/docshell/test/chrome/bug298622_window.xul new file mode 100755 index 000000000..2773f3420 --- /dev/null +++ b/docshell/test/chrome/bug298622_window.xul @@ -0,0 +1,148 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="298622Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="setTimeout(nextTest,0);" + title="bug 298622 test"> + + <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" /> + <script type="application/javascript" src= "docshell_helpers.js" /> + <script type="application/javascript"><![CDATA[ + // Global variable that holds a reference to the find bar. + var gFindBar; + + // Define the generator-iterator for the tests. + var tests = testIterator(); + + //// + // Execute the next test in the generator function. + // + function nextTest() { + tests.next(); + } + + //// + // Generator function for test steps for bug 298622: + // Find should work correctly on a page loaded from the + // bfcache. + // + function testIterator() + { + // Make sure bfcache is on. + enableBFCache(true); + + // Load a test page which contains some text to be found. + doPageNavigation({ + uri: "data:text/html,<html><head><title>test1</title></head>" + + "<body>find this!</body></html>", + onNavComplete: nextTest + }); + yield undefined; + + // Load a second, dummy page, verifying that the original + // page gets stored in the bfcache. + doPageNavigation({ + uri: getHttpUrl("generic.html"), + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ { type: "pagehide", + title: "test1", + persisted: true }, + { type: "pageshow", + title: "generic page" } ], + onNavComplete: nextTest + }); + yield undefined; + + // Make sure we unsuppress painting before continuing + SimpleTest.executeSoon(nextTest); + yield undefined; + + // Search for some text that's on the second page (but not on + // the first page), and verify that it can be found. + gFindBar = document.getElementById("FindToolbar"); + document.getElementById("cmd_find").doCommand(); + ok(!gFindBar.hidden, "failed to open findbar"); + gFindBar._findField.value = "A generic page"; + gFindBar._find(); + SimpleTest.executeSoon(nextTest); + yield undefined; + + // Make sure Find bar's internal status is not 'notfound' + isnot(gFindBar._findField.getAttribute("status"), "notfound", + "Findfield status attribute should not have been 'notfound'" + + " after Find"); + + // Make sure the key events above have time to be processed + // before continuing + waitForTrue(function() { + return ( + TestWindow.getWindow().getSelection().toString().toLowerCase() == + "a generic page"); + }, nextTest, 20); + yield undefined; + + is(gFindBar._findField.inputField.value, "A generic page", + "expected text not present in find input field"); + is(TestWindow.getWindow().getSelection().toString().toLowerCase(), + "a generic page", + "find failed on second page loaded"); + + // Go back to the original page and verify it's loaded from the + // bfcache. + doPageNavigation({ + back: true, + eventsToListenFor: ["pageshow"], + expectedEvents: [ { type: "pageshow", + title: "test1", + persisted: true } ], + onNavComplete: nextTest + }); + yield undefined; + + // Search for some text that's on the original page (but not + // the dummy page loaded above), and verify that it can + // be found. + gFindBar = document.getElementById("FindToolbar"); + document.getElementById("cmd_find").doCommand(); + ok(!gFindBar.hidden, "failed to open findbar"); + gFindBar._findField.value = "find this"; + gFindBar._find(); + SimpleTest.executeSoon(nextTest); + yield undefined; + + // Make sure Find bar's internal status is not 'notfound' + isnot(gFindBar._findField.getAttribute("status"), "notfound", + "Findfield status attribute should not have been 'notfound'" + + " after Find"); + + // Make sure the key events above have time to be processed + // before continuing + waitForTrue(function() { + return ( + TestWindow.getWindow().getSelection().toString().toLowerCase() == + "find this"); + }, nextTest, 20); + yield undefined; + + is(TestWindow.getWindow().getSelection().toString().toLowerCase(), + "find this", + "find failed on page loaded from bfcache"); + + // Tell the framework the test is finished. Include the final 'yield' + // statement to prevent a StopIteration exception from being thrown. + finish(); + yield undefined; + } + + ]]></script> + + <commandset> + <command id="cmd_find" + oncommand="document.getElementById('FindToolbar').onFindCommand();"/> + </commandset> + <browser type="content-primary" flex="1" id="content" src="about:blank"/> + <findbar id="FindToolbar" browserid="content"/> +</window> diff --git a/docshell/test/chrome/bug301397_1.html b/docshell/test/chrome/bug301397_1.html new file mode 100644 index 000000000..9943c2efe --- /dev/null +++ b/docshell/test/chrome/bug301397_1.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> + <title>iframe parent</title> + </head> +<body> + <iframe id="iframe" src="bug301397_2.html"/> + </body> +</html> diff --git a/docshell/test/chrome/bug301397_2.html b/docshell/test/chrome/bug301397_2.html new file mode 100644 index 000000000..423710706 --- /dev/null +++ b/docshell/test/chrome/bug301397_2.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> + <title>iframe content #1</title> + </head> +<body> + iframe page 1<br/> + <a id="link" href="bug301397_3.html">go to next page</a> + </body> +</html> diff --git a/docshell/test/chrome/bug301397_3.html b/docshell/test/chrome/bug301397_3.html new file mode 100644 index 000000000..8d36e9246 --- /dev/null +++ b/docshell/test/chrome/bug301397_3.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> + <title>iframe content #2</title> + </head> +<body> + iframe page 2<br/> + You made it! + </body> +</html> diff --git a/docshell/test/chrome/bug301397_4.html b/docshell/test/chrome/bug301397_4.html new file mode 100644 index 000000000..5584a4554 --- /dev/null +++ b/docshell/test/chrome/bug301397_4.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> + <title>dummy page, no iframe</title> + </head> +<body> + Just a boring test page, nothing special. + </body> +</html> diff --git a/docshell/test/chrome/bug301397_window.xul b/docshell/test/chrome/bug301397_window.xul new file mode 100644 index 000000000..5df33c5c9 --- /dev/null +++ b/docshell/test/chrome/bug301397_window.xul @@ -0,0 +1,250 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="301397Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="setTimeout(nextTest,0);" + title="bug 301397 test"> + + <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" /> + <script type="application/javascript" src="docshell_helpers.js" /> + <script type="application/javascript"><![CDATA[ + + // Define the generator-iterator for the tests. + var tests = testIterator(); + + //// + // Execute the next test in the generator function. + // + function nextTest() { + tests.next(); + } + + //// + // Return the document element with the specified id. + // + function $(id) { return TestWindow.getDocument().getElementById(id); } + + //// + // Verifies that the given string exists in the innerHTML of the iframe + // content. + // + function verifyIframeInnerHtml(string) { + var iframeInnerHtml = $("iframe").contentDocument.body.innerHTML; + ok(iframeInnerHtml.indexOf(string) != -1, + "iframe contains wrong document: " + iframeInnerHtml); + } + + //// + // Generator function for test steps for bug 301397: + // The correct page should be displayed in an iframe when + // navigating back and forwards, when the parent page + // occupies multiple spots in the session history. + // + function testIterator() + { + // Make sure the bfcache is enabled. + enableBFCache(8); + + // Load a dummy page. + doPageNavigation({ + uri: getHttpUrl("generic.html"), + onNavComplete: nextTest + }); + yield undefined; + + // Load a page containing an iframe. + doPageNavigation({ + uri: getHttpUrl("bug301397_1.html"), + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ { type: "pagehide", + title: "generic page", + persisted: true }, + { type: "pageshow", + title: "iframe content #1", + persisted: false }, // false on initial load + { type: "pageshow", + title: "iframe parent", + persisted: false } ], // false on initial load + onNavComplete: nextTest + }); + yield undefined; + + // Click a link in the iframe to cause the iframe to navigate + // to a new page, and wait until the related pagehide/pageshow + // events have occurred. + waitForPageEvents({ + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ { type: "pagehide", + title: "iframe content #1", + persisted: false }, // false, subframe nav + { type: "pageshow", + title: "iframe content #2", + persisted: false } ], // false on initial load + onNavComplete: nextTest + }); + var link = $("iframe").contentDocument.getElementById("link"); + var event = $("iframe").contentDocument.createEvent("MouseEvents"); + event.initMouseEvent("click", true, true, $("iframe").contentWindow, + 0, 0, 0, 0, 0, + false, false, false, false, + 0, null); + link.dispatchEvent(event); + yield undefined; + + // Load another dummy page. Verify that both the outgoing parent and + // iframe pages are stored in the bfcache. + doPageNavigation({ + uri: getHttpUrl("bug301397_4.html"), + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ { type: "pagehide", + title: "iframe parent", + persisted: true }, + { type: "pagehide", + title: "iframe content #2", + persisted: true }, + { type: "pageshow", + title: "dummy page, no iframe", + persisted: false } ], // false on initial load + onNavComplete: nextTest + }); + yield undefined; + + // Go back. The iframe should show the second page loaded in it. + // Both the parent and the iframe pages should be loaded from + // the bfcache. + doPageNavigation({ + back: true, + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ { type: "pagehide", + title: "dummy page, no iframe", + persisted: true }, + { type: "pageshow", + persisted: true, + title: "iframe content #2" }, + { type: "pageshow", + persisted: true, + title: "iframe parent" } ], + onNavComplete: nextTest + }); + yield undefined; + + verifyIframeInnerHtml("You made it"); + + // Go gack again. The iframe should show the first page loaded in it. + doPageNavigation({ + back: true, + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ { type: "pagehide", + title: "iframe content #2", + persisted: false }, // false, subframe nav + { type: "pageshow", + title: "iframe content #1", + // false since this page was never stored + // in the bfcache in the first place + persisted: false } ], + onNavComplete: nextTest + }); + yield undefined; + + verifyIframeInnerHtml("go to next page"); + + // Go back to the generic page. Now go forward to the last page, + // again verifying that the iframe shows the first and second + // pages in order. + doPageNavigation({ + back: true, + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ { type: "pagehide", + title: "iframe parent", + persisted: true }, + { type: "pagehide", + title: "iframe content #1", + persisted: true }, + { type: "pageshow", + title: "generic page", + persisted: true } ], + onNavComplete: nextTest + }); + yield undefined; + + doPageNavigation({ + forward: true, + eventsToListenFor: ["pageshow"], + expectedEvents: [ {type: "pageshow", + title: "iframe content #1", + persisted: true}, + {type: "pageshow", + title: "iframe parent", + persisted: true} ], + onNavComplete: nextTest + }); + yield undefined; + + verifyIframeInnerHtml("go to next page"); + + doPageNavigation({ + forward: true, + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ { type: "pagehide", + title: "iframe content #1", + persisted: false }, // false, subframe nav + { type: "pageshow", + title: "iframe content #2", + // false because the page wasn't stored in + // bfcache last time it was unloaded + persisted: false } ], + onNavComplete: nextTest + }); + yield undefined; + + verifyIframeInnerHtml("You made it"); + + doPageNavigation({ + forward: true, + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ { type: "pagehide", + title: "iframe parent", + persisted: true }, + { type: "pagehide", + title: "iframe content #2", + persisted: true }, + { type: "pageshow", + title: "dummy page, no iframe", + persisted: true } ], + onNavComplete: nextTest + }); + yield undefined; + + // Go back once more, and again verify that the iframe shows the + // second page loaded in it. + doPageNavigation({ + back: true, + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ { type: "pagehide", + title: "dummy page, no iframe", + persisted: true }, + { type: "pageshow", + persisted: true, + title: "iframe content #2" }, + { type: "pageshow", + persisted: true, + title: "iframe parent" } ], + onNavComplete: nextTest + }); + yield undefined; + + verifyIframeInnerHtml("You made it"); + + // Tell the framework the test is finished. Include the final 'yield' + // statement to prevent a StopIteration exception from being thrown. + finish(); + yield undefined; + } + + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/bug303267.html b/docshell/test/chrome/bug303267.html new file mode 100644 index 000000000..32b575eee --- /dev/null +++ b/docshell/test/chrome/bug303267.html @@ -0,0 +1,25 @@ +<html> +<head> + <title> + bug303267.html + </title> + </head> +<body onpageshow="showpageshowcount()"> +<script> +var pageshowcount = 0; +function showpageshowcount() +{ + pageshowcount++; + var div1 = document.getElementById("div1"); + while (div1.firstChild) + { + div1.removeChild(div1.firstChild); + } + div1.appendChild(document.createTextNode( + "pageshowcount: " + pageshowcount)); +} +</script> +<div id="div1"> + </div> +</body> +</html> diff --git a/docshell/test/chrome/bug303267_window.xul b/docshell/test/chrome/bug303267_window.xul new file mode 100755 index 000000000..46a193712 --- /dev/null +++ b/docshell/test/chrome/bug303267_window.xul @@ -0,0 +1,101 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="303267Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="setTimeout(nextTest,0);" + title="bug 303267 test"> + + <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" /> + <script type="application/javascript" src="docshell_helpers.js" /> + <script type="application/javascript"><![CDATA[ + + // Define the generator-iterator for the tests. + var tests = testIterator(); + + //// + // Execute the next test in the generator function. + // + function nextTest() { + tests.next(); + } + + //// + // Generator function for test steps for bug 303267: When a page is + // displayed from the bfcache, the script globals should + // remain intact from the page's initial load. + // + function testIterator() + { + // Load an initial test page which should be saved in the bfcache. + var navData = { + uri: getHttpUrl("bug303267.html"), + eventsToListenFor: ["pageshow"], + expectedEvents: [ {type: "pageshow", title: "bug303267.html"} ], + onNavComplete: nextTest + }; + doPageNavigation(navData); + yield undefined; + + // Save the HTML of the test page for later comparison. + var originalHTML = getInnerHTMLById("div1"); + + // Load a second test page. The first test page's pagehide event should + // have the .persisted property set to true, indicating that it was + // stored in the bfcache. + navData = { + uri: "data:text/html,<html><head><title>page2</title></head>" + + "<body>bug303267, page2</body></html>", + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ {type: "pagehide", + title: "bug303267.html", + persisted: true}, + {type: "pageshow", + title: "page2"} ], + onNavComplete: nextTest + }; + doPageNavigation(navData); + yield undefined; + + // Go back. Verify that the pageshow event for the original test page + // had a .persisted property of true, indicating that it came from the + // bfcache. + navData = { + back: true, + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ {type: "pagehide", + title: "page2"}, + {type: "pageshow", + title: "bug303267.html", + persisted: true} ], + onNavComplete: nextTest + }; + doPageNavigation(navData); + yield undefined; + + // After going back, if showpagecount() could access a global variable + // and change the test div's innerHTML, then we pass. Otherwise, it + // threw an exception and the following test will fail. + var newHTML = getInnerHTMLById("div1"); + isnot(originalHTML, + newHTML, "HTML not updated on pageshow; javascript broken?"); + + // Tell the framework the test is finished. Include the final 'yield' + // statement to prevent a StopIteration exception from being thrown. + finish(); + yield undefined; + } + + //// + // Return the innerHTML of a particular element in the content document. + // + function getInnerHTMLById(id) { + return TestWindow.getDocument().getElementById(id).innerHTML; + } + + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/bug311007_window.xul b/docshell/test/chrome/bug311007_window.xul new file mode 100644 index 000000000..730580cc2 --- /dev/null +++ b/docshell/test/chrome/bug311007_window.xul @@ -0,0 +1,199 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="311007Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="startup();" + title="bug 311007 test"> + + <script type="application/javascript" src="docshell_helpers.js"></script> + <script type="application/javascript"><![CDATA[ +/* + Regression test for bug 283733 and bug 307027. + + Bug 283733 + "accessing a relative anchor in a secure page removes the + locked icon and yellow background UI" + + Bug 307027 + "Going back from secure page to error page does not clear yellow bar" + + And enhancements: + + Bug 478927 + onLocationChange should notify whether or not loading an error page. + + */ + +const kDNSErrorURI = "https://example/err.html"; +const kSecureURI = + "https://example.com/tests/docshell/test/navigation/blank.html"; + +/* + Step 1: load a network error page. <err.html> Not Secure + Step 2: load a secure page. <blank.html> Secure + Step 3: a secure page + hashchange. <blank.html#foo> Secure (bug 283733) + Step 4: go back to the error page. <err.html> Not Secure (bug 307027) + */ + +var gListener = null; + +function WebProgressListener() { + this._callback = null; +} + +WebProgressListener.prototype = { + QueryInterface: function(aIID) { + if (aIID.equals(Components.interfaces.nsIWebProgressListener) || + aIID.equals(Components.interfaces.nsISupportsWeakReference) || + aIID.equals(Components.interfaces.nsISupports)) + return this; + throw Components.results.NS_NOINTERFACE; + }, + + onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) { + setTimeout(this._callback, 0, aWebProgress, aRequest, aLocation, aFlags); + }, + + set callback(aVal) { + this._callback = aVal; + } +}; + +function startup() { + gListener = new WebProgressListener(); + + document.getElementById("content") + .webProgress + .addProgressListener(gListener, + Components.interfaces.nsIWebProgress + .NOTIFY_LOCATION); + + setTimeout(step1A, 0); +} + +/****************************************************************************** + * Step 1: Load an error page, and confirm UA knows it's insecure. + ******************************************************************************/ + +function step1A() { + gListener.callback = step1B; + content.location = kDNSErrorURI; +} + +function step1B(aWebProgress, aRequest, aLocation, aFlags) { + is(aLocation.spec, kDNSErrorURI, "Error page's URI (1)"); + + ok(!(aFlags & Components.interfaces.nsIWebProgressListener + .LOCATION_CHANGE_SAME_DOCUMENT), + "DocShell loaded a document (1)"); + + ok((aFlags & Components.interfaces.nsIWebProgressListener + .LOCATION_CHANGE_ERROR_PAGE), + "This page is an error page."); + + ok(!(aWebProgress.QueryInterface(Components.interfaces.nsIDocShell) + .securityUI.state & + Components.interfaces.nsIWebProgressListener.STATE_IS_SECURE), + "This is not a secure page (1)"); + + /* Go to step 2. */ + setTimeout(step2A, 0); +} + +/****************************************************************************** + * Step 2: Load a HTTPS page, and confirm it's secure. + ******************************************************************************/ + +function step2A() { + gListener.callback = step2B; + content.location = kSecureURI; +} + +function step2B(aWebProgress, aRequest, aLocation, aFlags) { + is(aLocation.spec, kSecureURI, "A URI on HTTPS (2)"); + + ok(!(aFlags & Components.interfaces.nsIWebProgressListener + .LOCATION_CHANGE_SAME_DOCUMENT), + "DocShell loaded a document (2)"); + + ok(!(aFlags & Components.interfaces.nsIWebProgressListener + .LOCATION_CHANGE_ERROR_PAGE), + "This page is not an error page."); + + ok((aWebProgress.QueryInterface(Components.interfaces.nsIDocShell) + .securityUI.state & + Components.interfaces.nsIWebProgressListener.STATE_IS_SECURE), + "This is a secure page (2)"); + + /* Go to step 3. */ + setTimeout(step3A, 0); +} + +/***************************************************************************** + * Step 3: Trigger hashchange within a secure page, and confirm UA knows + * it's secure. (Bug 283733) + *****************************************************************************/ + +function step3A() { + gListener.callback = step3B; + content.location += "#foo"; +} + +function step3B(aWebProgress, aRequest, aLocation, aFlags) { + is(aLocation.spec, kSecureURI + "#foo", "hashchange on HTTPS (3)"); + + ok((aFlags & Components.interfaces.nsIWebProgressListener + .LOCATION_CHANGE_SAME_DOCUMENT), + "We are in the same document as before (3)"); + + ok(!(aFlags & Components.interfaces.nsIWebProgressListener + .LOCATION_CHANGE_ERROR_PAGE), + "This page is not an error page."); + + ok((aWebProgress.QueryInterface(Components.interfaces.nsIDocShell) + .securityUI.state & + Components.interfaces.nsIWebProgressListener.STATE_IS_SECURE), + "This is a secure page (3)"); + + /* Go to step 4. */ + setTimeout(step4A, 0); +} + +/***************************************************************************** + * Step 4: Go back from a secure page to an error page, and confirm UA knows + * it's not secure. (Bug 307027) + *****************************************************************************/ + +function step4A() { + gListener.callback = step4B; + content.history.go(-2); +} + +function step4B(aWebProgress, aRequest, aLocation, aFlags) { + is(aLocation.spec, kDNSErrorURI, "Go back to the error URI (4)"); + + ok(!(aFlags & Components.interfaces.nsIWebProgressListener + .LOCATION_CHANGE_SAME_DOCUMENT), + "DocShell loaded a document (4)"); + + ok((aFlags & Components.interfaces.nsIWebProgressListener + .LOCATION_CHANGE_ERROR_PAGE), + "This page is an error page."); + + ok(!(aWebProgress.QueryInterface(Components.interfaces.nsIDocShell) + .securityUI.state & + Components.interfaces.nsIWebProgressListener.STATE_IS_SECURE), + "This is not a secure page (4)"); + + /* End. */ + aWebProgress.removeProgressListener(gListener); + delete(gListener); + finish(); +} + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/bug321671_window.xul b/docshell/test/chrome/bug321671_window.xul new file mode 100755 index 000000000..addd04570 --- /dev/null +++ b/docshell/test/chrome/bug321671_window.xul @@ -0,0 +1,133 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="321671Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="setTimeout(nextTest,0);" + title="bug 321671 test"> + + <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" /> + <script type="application/javascript" src="docshell_helpers.js" /> + <script type="application/javascript"><![CDATA[ + + // Define the generator-iterator for the tests. + var tests = testIterator(); + + // Maximum number of entries in the bfcache for this session history. + // This number is hardcoded in docshell code. In the test, we'll + // navigate through enough pages so that we hit one that's been + // evicted from the bfcache because it's farther from the current + // page than this number. + const MAX_BFCACHE_PAGES = 3; + + //// + // Execute the next test in the generator function. + // + function nextTest() { + tests.next(); + } + + //// + // Generator function for test steps for bug 321671: Scroll position + // should be retained when moving backwards and forwards through pages + // when bfcache is enabled. + // + function testIterator() + { + // Variable to hold the scroll positions of the test pages. + var scrollPositions = []; + + // Make sure bfcache is on. + enableBFCache(true); + + // Load enough test pages that so the first one is evicted from the + // bfcache, scroll down on each page, and save the + // current scroll position before continuing. Verify that each + // page we're navigating away from is initially put into the bfcache. + for (var i = 0; i <= MAX_BFCACHE_PAGES + 1; i++) { + doPageNavigation( { + uri: "data:text/html,<html><head><title>bug321671 page" + (i + 1) + + "</title></head>" + + "<body><table border='1' width='300' height='1000'>" + + "<tbody><tr><td>" + + " page " + (i + 1) + ": foobar foobar foobar foobar " + + "</td></tr></tbody></table> " + + "</body></html>", + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ { type: "pagehide", + persisted: true, + title: i == 0 ? + undefined : "bug321671 page" + i }, + { type: "pageshow", + title: "bug321671 page" + (i + 1) } ], + onNavComplete: nextTest + } ); + yield undefined; + + is(TestWindow.getWindow().scrollY, 0, + "Page initially has non-zero scrollY position"); + TestWindow.getWindow().scrollByLines(10 + (2*i)); + ok(TestWindow.getWindow().scrollY > 0, + "Page has zero scrollY position after scrolling"); + scrollPositions[i] = TestWindow.getWindow().scrollY; + } + + // Go back to the first page, one page at a time. For each 'back' + // action, verify that its vertical scroll position is restored + // correctly. Verify that the last page in the sequence + // does not come from the bfcache. Again verify that all pages + // that we navigate away from are initially + // stored in the bfcache. + for (i = MAX_BFCACHE_PAGES + 1; i > 0; i--) { + doPageNavigation( { + back: true, + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ { type: "pagehide", + title: "bug321671 page" + (i+1), + persisted: true }, + { type: "pageshow", + title: "bug321671 page" + i, + persisted: i > 1 } ], + onNavComplete: nextTest + } ); + yield undefined; + + is(TestWindow.getWindow().scrollY, scrollPositions[i-1], + "Scroll position not restored while going back!"); + } + + // Traverse history forward now, and verify scroll position is still + // restored. Similar to the backward traversal, verify that all + // but the last page in the sequence comes from the bfcache. Also + // verify that all of the pages get stored in the bfcache when we + // navigate away from them. + for (i = 1; i <= MAX_BFCACHE_PAGES + 1; i++) { + doPageNavigation( { + forward: true, + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ { type: "pagehide", + persisted: true, + title: "bug321671 page" + i }, + { type: "pageshow", + persisted: i < MAX_BFCACHE_PAGES + 1, + title: "bug321671 page" + (i + 1) } ], + onNavComplete: nextTest + } ); + yield undefined; + + is(TestWindow.getWindow().scrollY, scrollPositions[i], + "Scroll position not restored while going forward!"); + } + + // Tell the framework the test is finished. Include the final 'yield' + // statement to prevent a StopIteration exception from being thrown. + finish(); + yield undefined; + } + + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/bug360511_case1.html b/docshell/test/chrome/bug360511_case1.html new file mode 100644 index 000000000..cca043bb6 --- /dev/null +++ b/docshell/test/chrome/bug360511_case1.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html style="height: 100%"> +<head> + <title> + bug360511 case 1 + </title> + </head> +<body style="height: 100%"> +<a id="link1" href="#bottom">jump to bottom</a> +<div id="div1" style="height: 200%; border: thin solid black;"> + hello large div + </div> + <a name="bottom">here's the bottom of the page</a> +</body> +</html> diff --git a/docshell/test/chrome/bug360511_case2.html b/docshell/test/chrome/bug360511_case2.html new file mode 100644 index 000000000..217f47724 --- /dev/null +++ b/docshell/test/chrome/bug360511_case2.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html style="height: 100%"> +<head> + <title> + bug360511 case 2 + </title> + </head> +<body style="height: 100%"> +<a id="link1" href="#bottom">jump to bottom</a> +<div id="div1" style="height: 200%; border: thin solid black;"> + hello large div + </div> + <a name="bottom">here's the bottom of the page</a> +</body> +</html> diff --git a/docshell/test/chrome/bug360511_window.xul b/docshell/test/chrome/bug360511_window.xul new file mode 100755 index 000000000..9d0cde7f1 --- /dev/null +++ b/docshell/test/chrome/bug360511_window.xul @@ -0,0 +1,134 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="360511Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="setTimeout(nextTest,0);" + title="bug 360511 test"> + + <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" /> + <script type="application/javascript" src="docshell_helpers.js" /> + <script type="application/javascript"><![CDATA[ + + // Define the generator-iterator for the tests. + var tests = testIterator(); + + //// + // Execute the next test in the generator function. + // + function nextTest() { + tests.next(); + } + + //// + // Generator function for test steps for bug 360511: + // Fragment uri's in session history should be restored correctly + // upon back navigation. + // + function testIterator() + { + // Case 1: load a page containing a fragment link; the page should be + // stored in the bfcache. + // Case 2: load a page containing a fragment link; the page should NOT + // be stored in the bfcache. + for (var i = 1; i < 3; i++) + { + var url = "bug360511_case" + i + ".html"; + doPageNavigation( { + uri: getHttpUrl(url), + onNavComplete: nextTest, + preventBFCache: i != 1 + } ); + yield undefined; + + // Store the original url for later comparison. + var originalUrl = TestWindow.getBrowser().currentURI.spec; + var originalDocLocation = TestWindow.getDocument().location.href; + + // Verify we're at the top of the page. + is(TestWindow.getWindow().scrollY, 0, + "Page initially has a non-zero scrollY property"); + + // Click the on the fragment link in the browser, and use setTimeout + // to give the event a chance to be processed. + var event = TestWindow.getDocument().createEvent('MouseEvent'); + event.initMouseEvent("click", true, true, TestWindow.getWindow(), 0, + 0, 0, 0, 0, + false, false, false, false, 0, null); + TestWindow.getDocument().getElementById("link1").dispatchEvent(event); + setTimeout(nextTest, 0); + yield undefined; + + // Store the fragment url for later comparison. + var fragmentUrl = TestWindow.getBrowser().currentURI.spec; + var fragDocLocation = TestWindow.getDocument().location.href; + + // Verify we're no longer at the top of the page. + ok(TestWindow.getWindow().scrollY > 0, + "We're at the top of the page but we should be at the bottom"); + + // Now navigate to any other page + var expectedPageTitle = "bug360511 case " + i; + doPageNavigation( { + uri: getHttpUrl("generic.html"), + eventsToListenFor: ["pagehide", "pageshow"], + expectedEvents: [ {type: "pagehide", title: expectedPageTitle, + persisted: i == 1}, + {type: "pageshow"} ], + onNavComplete: nextTest + } ); + yield undefined; + + // Go back + doPageNavigation( { + back: true, + eventsToListenFor: ["pageshow"], + expectedEvents: [ {type: "pageshow", title: expectedPageTitle, + persisted: i == 1} ], + onNavComplete: nextTest + } ); + yield undefined; + + // Verify the current url is the fragment url + is(TestWindow.getBrowser().currentURI.spec, fragmentUrl, + "current url is not the previous fragment url"); + is(TestWindow.getDocument().location.href, fragDocLocation, + "document.location is not the previous fragment url"); + + // Go back again. Since we're just going from a fragment url to + // parent url, no pageshow event is fired, so don't wait for any + // events. Rather, just wait for the page's scrollY property to + // change. + var originalScrollY = TestWindow.getWindow().scrollY; + doPageNavigation( { + back: true, + eventsToListenFor: [] + } ); + waitForTrue( + function() { + return (TestWindow.getWindow().scrollY != originalScrollY); + }, + function() { + setTimeout(nextTest, 0); + }, 20); + yield undefined; + + // Verify the current url is the original url without fragment + is(TestWindow.getBrowser().currentURI.spec, originalUrl, + "current url is not the original url"); + is(TestWindow.getDocument().location.href, originalDocLocation, + "document.location is not the original url"); + } + + // Tell the framework the test is finished. Include the final 'yield' + // statement to prevent a StopIteration exception from being thrown. + finish(); + yield undefined; + } + + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/bug364461_window.xul b/docshell/test/chrome/bug364461_window.xul new file mode 100644 index 000000000..a05a33b3f --- /dev/null +++ b/docshell/test/chrome/bug364461_window.xul @@ -0,0 +1,277 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="364461Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="onLoad();" + title="364461 test"> + + <script type="application/javascript"><![CDATA[ + + const LISTEN_EVENTS = ["load", "unload", "pageshow", "pagehide"]; + + var gBrowser; + var gTestsIterator; + var gExpected = []; + + function ok(condition, message) { + window.opener.wrappedJSObject.SimpleTest.ok(condition, message); + } + function is(a, b, message) { + window.opener.wrappedJSObject.SimpleTest.is(a, b, message); + } + function finish() { + for (let eventType of LISTEN_EVENTS) { + gBrowser.removeEventListener(eventType, eventListener, true); + } + + window.close(); + window.opener.wrappedJSObject.SimpleTest.finish(); + } + + function onLoad() { + gBrowser = document.getElementById("content"); + + for (let eventType of LISTEN_EVENTS) { + gBrowser.addEventListener(eventType, eventListener, true); + } + + gTestsIterator = testsIterator(); + nextTest(); + } + + function eventListener(event) { + ok(gExpected.length >= 1, "Unexpected event " + event.type); + if (gExpected.length == 0) { + // in case of unexpected event, try to continue anyway + setTimeout(nextTest, 0); + return; + } + + var exp = gExpected.shift(); + is(event.type, exp.type, "Invalid event received"); + if (typeof(exp.persisted) != "undefined") { + is(event.persisted, exp.persisted, "Invalid persisted state"); + } + if (exp.title) { + ok(event.originalTarget instanceof HTMLDocument, + "originalTarget not a HTMLDocument"); + is(event.originalTarget.title, exp.title, "titles don't match"); + } + + if (gExpected.length == 0) { + setTimeout(nextTest, 0); + } + } + + function nextTest() { + try { + gTestsIterator.next(); + } catch (err if err instanceof StopIteration) { + finish(); + } + } + + function testsIterator() { + + // Tests 1 + 2: + // Back/forward between two simple documents. Bfcache will be used. + + var test1Doc = "data:text/html,<html><head><title>test1</title></head>" + + "<body>test1</body></html>"; + + gExpected = [{type: "pagehide", persisted: true}, + + {type: "load", title: "test1"}, + {type: "pageshow", title: "test1", persisted: false}]; + gBrowser.loadURI(test1Doc); + yield undefined; + + var test2Doc = "data:text/html,<html><head><title>test2</title></head>" + + "<body>test2</body></html>"; + + gExpected = [{type: "pagehide", title: "test1", persisted: true}, + {type: "load", title: "test2"}, + {type: "pageshow", title: "test2", persisted: false}]; + gBrowser.loadURI(test2Doc); + yield undefined; + + gExpected = [{type: "pagehide", title: "test2", persisted: true}, + {type: "pageshow", title: "test1", persisted: true}]; + gBrowser.goBack(); + yield undefined; + + gExpected = [{type: "pagehide", title: "test1", persisted: true}, + {type: "pageshow", title: "test2", persisted: true}]; + gBrowser.goForward(); + yield undefined; + + // Tests 3 + 4: + // Back/forward between a two-level deep iframed document and a simple + // document. Bfcache will be used and events should be dispatched to + // all frames. + + var test3Doc = "data:text/html,<html><head><title>test3</title>" + + "</head><body>" + + "<iframe src='data:text/html," + + "<html><head><title>test3-nested1</title></head>" + + "<body>test3-nested1" + + "<iframe src=\"data:text/html," + + "<html><head><title>test3-nested2</title></head>" + + "<body>test3-nested2</body></html>\">" + + "</iframe>" + + "</body></html>'>" + + "</iframe>" + + "</body></html>"; + + gExpected = [{type: "pagehide", title: "test2", persisted: true}, + {type: "load", title: "test3-nested2"}, + {type: "pageshow", title: "test3-nested2", persisted: false}, + {type: "load", title: "test3-nested1"}, + {type: "pageshow", title: "test3-nested1", persisted: false}, + {type: "load", title: "test3"}, + {type: "pageshow", title: "test3", persisted: false}]; + gBrowser.loadURI(test3Doc); + yield undefined; + + var test4Doc = "data:text/html,<html><head><title>test4</title></head>" + + "<body>test4</body></html>"; + + gExpected = [{type: "pagehide", title: "test3", persisted: true}, + {type: "pagehide", title: "test3-nested1", persisted: true}, + {type: "pagehide", title: "test3-nested2", persisted: true}, + {type: "load", title: "test4"}, + {type: "pageshow", title: "test4", persisted: false}]; + gBrowser.loadURI(test4Doc); + yield undefined; + + gExpected = [{type: "pagehide", title: "test4", persisted: true}, + {type: "pageshow", title: "test3-nested2", persisted: true}, + {type: "pageshow", title: "test3-nested1", persisted: true}, + {type: "pageshow", title: "test3", persisted: true}]; + gBrowser.goBack(); + yield undefined; + + // This is where the two nested pagehide are not dispatched in bug 364461 + gExpected = [{type: "pagehide", title: "test3", persisted: true}, + {type: "pagehide", title: "test3-nested1", persisted: true}, + {type: "pagehide", title: "test3-nested2", persisted: true}, + {type: "pageshow", title: "test4", persisted: true}]; + gBrowser.goForward(); + yield undefined; + + // Tests 5 + 6: + // Back/forward between a document containing an unload handler and a + // a simple document. Bfcache won't be used for the first one (see + // http://developer.mozilla.org/en/docs/Using_Firefox_1.5_caching). + + var test5Doc = "data:text/html,<html><head><title>test5</title></head>" + + "<body onunload='while(false) { /* nop */ }'>" + + "test5</body></html>"; + + gExpected = [{type: "pagehide", title: "test4", persisted: true}, + {type: "load", title: "test5"}, + {type: "pageshow", title: "test5", persisted: false}]; + gBrowser.loadURI(test5Doc); + yield undefined; + + var test6Doc = "data:text/html,<html><head><title>test6</title></head>" + + "<body>test6</body></html>"; + + gExpected = [{type: "pagehide", title: "test5", persisted: false}, + {type: "unload", title: "test5"}, + {type: "load", title: "test6"}, + {type: "pageshow", title: "test6", persisted: false}]; + gBrowser.loadURI(test6Doc); + yield undefined; + + gExpected = [{type: "pagehide", title: "test6", persisted: true}, + {type: "load", title: "test5"}, + {type: "pageshow", title: "test5", persisted: false}]; + gBrowser.goBack(); + yield undefined; + + gExpected = [{type: "pagehide", title: "test5", persisted: false}, + {type: "unload", title: "test5"}, + {type: "pageshow", title: "test6", persisted: true}]; + gBrowser.goForward(); + yield undefined; + + // Test 7: + // Testcase from https://bugzilla.mozilla.org/show_bug.cgi?id=384977#c10 + // Check that navigation is not blocked after a document is restored + // from bfcache + + var test7Doc = "data:text/html,<html><head><title>test7</title>" + + "</head><body>" + + "<iframe src='data:text/html," + + "<html><head><title>test7-nested1</title></head>" + + "<body>test7-nested1<br/>" + + "<a href=\"data:text/plain,aaa\" target=\"_top\">" + + "Click me, hit back, click me again</a>" + + "</body></html>'>" + + "</iframe>" + + "</body></html>"; + + gExpected = [{type: "pagehide", title: "test6", persisted: true}, + {type: "load", title: "test7-nested1"}, + {type: "pageshow", title: "test7-nested1", persisted: false}, + {type: "load", title: "test7"}, + {type: "pageshow", title: "test7", persisted: false}]; + gBrowser.loadURI(test7Doc); + yield undefined; + + // Simulates a click on the link inside the iframe + function clickIframeLink() { + var iframe = gBrowser.contentDocument.getElementsByTagName("iframe")[0]; + var w = iframe.contentWindow; + var d = iframe.contentDocument; + + var evt = d.createEvent("MouseEvents"); + evt.initMouseEvent("click", true, true, w, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + d.getElementsByTagName("a")[0].dispatchEvent(evt); + } + + gExpected = [{type: "pagehide", title: "test7", persisted: true}, + {type: "pagehide", title: "test7-nested1", persisted: true}, + {type: "load"}, + {type: "pageshow", persisted: false}]; + clickIframeLink(); + yield undefined; + + is(gBrowser.currentURI.spec, "data:text/plain,aaa", + "Navigation is blocked when clicking link"); + + gExpected = [{type: "pagehide", persisted: true}, + {type: "pageshow", title: "test7-nested1", persisted: true}, + {type: "pageshow", title: "test7", persisted: true}]; + gBrowser.goBack(); + yield undefined; + + gExpected = [{type: "pagehide", title: "test7", persisted: true}, + {type: "pagehide", title: "test7-nested1", persisted: true}, + {type: "load"}, + {type: "pageshow", persisted: false}]; + clickIframeLink(); + yield undefined; + + is(gBrowser.currentURI.spec, "data:text/plain,aaa", + "Navigation is blocked when clicking link"); + + // nextTest has to be called from here, as no events are fired in this + // step + setTimeout(nextTest, 0); + yield undefined; + } + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/bug396519_window.xul b/docshell/test/chrome/bug396519_window.xul new file mode 100644 index 000000000..32497b163 --- /dev/null +++ b/docshell/test/chrome/bug396519_window.xul @@ -0,0 +1,169 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="396519Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="onLoad();" + title="396519 test"> + + <script type="application/javascript"><![CDATA[ + + const LISTEN_EVENTS = ["pageshow"]; + + const Cc = Components.classes; + const Ci = Components.interfaces; + + var gBrowser; + var gTestCount = 0; + var gTestsIterator; + var gExpected = []; + + function ok(condition, message) { + window.opener.wrappedJSObject.SimpleTest.ok(condition, message); + } + function is(a, b, message) { + window.opener.wrappedJSObject.SimpleTest.is(a, b, message); + } + function finish() { + for (let eventType of LISTEN_EVENTS) { + gBrowser.removeEventListener(eventType, eventListener, true); + } + + window.close(); + window.opener.wrappedJSObject.SimpleTest.finish(); + } + + function onLoad() { + gBrowser = document.getElementById("content"); + + for (let eventType of LISTEN_EVENTS) { + gBrowser.addEventListener(eventType, eventListener, true); + } + + gTestsIterator = testsIterator(); + nextTest(); + } + + function eventListener(event) { + // we're in pageshow, but we need to let that finish + // content eviction and saving happen during pageshow, so when doTest + // runs, we should should be in a testable state + setTimeout(doTest, 0); + } + + function doTest() { + var history = gBrowser.webNavigation.sessionHistory; + if (history.count == gExpected.length) { + for (var i=0; i<history.count; i++) { + var shEntry = history.getEntryAtIndex(i,false). + QueryInterface(Components.interfaces.nsISHEntry); + is(!!shEntry.contentViewer, gExpected[i], "content viewer "+i+", test "+gTestCount); + } + + // Make sure none of the SHEntries share bfcache entries with one + // another. + for (var i = 0; i < history.count; i++) { + for (var j = 0; j < history.count; j++) { + if (j == i) + continue; + + let shentry1 = history.getEntryAtIndex(i, false) + .QueryInterface(Ci.nsISHEntry); + let shentry2 = history.getEntryAtIndex(j, false) + .QueryInterface(Ci.nsISHEntry); + ok(!shentry1.sharesDocumentWith(shentry2), + 'Test ' + gTestCount + ': shentry[' + i + "] shouldn't " + + "share document with shentry[" + j + ']'); + } + } + } + else { + is(history.count, gExpected.length, "Wrong history length in test "+gTestCount); + } + + setTimeout(nextTest, 0); + } + + function nextTest() { + try { + gTestsIterator.next(); + } catch (err if err instanceof StopIteration) { + finish(); + } + } + + function testsIterator() { + + // Tests 1 + 2: + // Back/forward between two simple documents. Bfcache will be used. + + var test1Doc = "data:text/html,<html><head><title>test1</title></head>" + + "<body>test1</body></html>"; + + gTestCount++; + gExpected = [false]; + gBrowser.loadURI(test1Doc); + yield undefined; + + gTestCount++; + gExpected = [true, false]; + var test2Doc = test1Doc.replace(/1/,"2"); + gBrowser.loadURI(test2Doc); + yield undefined; + + gTestCount++; + gExpected = [true, true, false]; + gBrowser.loadURI(test1Doc); + yield undefined; + + gTestCount++; + gExpected = [true, true, true, false]; + gBrowser.loadURI(test2Doc); + yield undefined; + + gTestCount++; + gExpected = [false, true, true, true, false]; + gBrowser.loadURI(test1Doc); + yield undefined; + + gTestCount++; + gExpected = [false, false, true, true, true, false]; + gBrowser.loadURI(test2Doc); + yield undefined; + + gTestCount++; + gExpected = [false, false, true, true, false, true]; + gBrowser.goBack(); + yield undefined; + + gTestCount++; + gExpected = [false, false, true, true, true, false]; + gBrowser.goForward(); + yield undefined; + + gTestCount++; + gExpected = [false, false, true, true, true, false]; + gBrowser.gotoIndex(1); + yield undefined; + + gTestCount++; + gExpected = [false, true, true, true, false, false]; + gBrowser.goBack(); + yield undefined; + + gTestCount++; + gExpected = [false, false, true, true, false, false]; + gBrowser.gotoIndex(5); + yield undefined; + } + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/bug396649_window.xul b/docshell/test/chrome/bug396649_window.xul new file mode 100755 index 000000000..c94d8b78e --- /dev/null +++ b/docshell/test/chrome/bug396649_window.xul @@ -0,0 +1,122 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="396649Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="setTimeout(nextTest,0);" + title="bug 396649 test"> + + <script type="text/javascript" + src="chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js"/> + <script type="text/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SpecialPowersObserverAPI.js"/> + <script type="text/javascript" + src="chrome://mochikit/content/tests/SimpleTest/ChromePowers.js"/> + <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" /> + <script type="application/javascript" src="docshell_helpers.js" /> + <script type="application/javascript"><![CDATA[ + + // Define the generator-iterator for the tests. + var tests = testIterator(); + + // Maximum number of entries in the bfcache for this session history. + // This number is hardcoded in docshell code. In the test, we'll + // navigate through enough pages so that we hit one that's been + // evicted from the bfcache because it's farther from the current + // page than this number. + const MAX_BFCACHE_PAGES = 3; + + //// + // Execute the next test in the generator function. + // + function nextTest() { + tests.next(); + } + + //// + // Generator function for test steps for bug 396649: Content + // viewers should be evicted from bfcache when going back if more + // than MAX_BFCACHE_PAGES from the current index. + // + function testIterator() + { + // Make sure bfcache is on. + enableBFCache(true); + + // Load enough pages so that the first loaded is eviced from + // the bfcache, since it is greater the MAX_BFCACHE_PAGES from + // the current position in the session history. Verify all + // of the pages are initially stored in the bfcache when + // they're unloaded. + for (var i = 0; i <= MAX_BFCACHE_PAGES + 1; i++) { + doPageNavigation( { + uri: "data:text/html,<!DOCTYPE html><html>" + + "<head><title>bug396649 page" + i + + "</title></head>" + + "<body>" + + "test page " + i + + "</body></html>", + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ { type: "pagehide", + title: i == 0 ? + undefined : "bug396649 page" + (i-1), + persisted: true }, + { type: "pageshow", + title: "bug396649 page" + i } ], + onNavComplete: nextTest + } ); + yield undefined; + } + + // Go back to the first page, one page at a time. The first + // MAX_BFCACHE_PAGES pages loaded via back should come from the bfcache, + // the last should not, since it should have been evicted during the + // previous navigation sequence. Verify all pages are initially stored + // in the bfcache when they're unloaded. + for (i = MAX_BFCACHE_PAGES + 1; i > 0; i--) { + doPageNavigation( { + back: true, + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ { type: "pagehide", + title: "bug396649 page" + i, + persisted: true }, + { type: "pageshow", + title: "bug396649 page" + (i - 1), + persisted: i > 1 } ], + onNavComplete: nextTest + } ); + yield undefined; + } + + // Traverse history forward now. Again, the first MAX_BFCACHE_PAGES + // pages should come from the bfcache, the last should not, + // since it should have been evicted during the backwards + // traversal above. Verify all pages are initially stored + // in the bfcache when they're unloaded. + for (i = 1; i <= MAX_BFCACHE_PAGES + 1; i++) { + doPageNavigation( { + forward: true, + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ { type: "pagehide", + title: "bug396649 page" + (i-1), + persisted: true }, + { type: "pageshow", + title: "bug396649 page" + i, + persisted: i < MAX_BFCACHE_PAGES + 1 } ], + onNavComplete: nextTest + } ); + yield undefined; + } + + // Tell the framework the test is finished. Include the final 'yield' + // statement to prevent a StopIteration exception from being thrown. + finish(); + yield undefined; + } + + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/bug449778_window.xul b/docshell/test/chrome/bug449778_window.xul new file mode 100644 index 000000000..b4fc5e2ba --- /dev/null +++ b/docshell/test/chrome/bug449778_window.xul @@ -0,0 +1,106 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<window title="Mozilla Bug 449778" onload="doTheTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <hbox id="parent"> + </hbox> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + var imports = [ "SimpleTest", "is", "isnot", "ok", "onerror" ]; + for (var name of imports) { + window[name] = window.opener.wrappedJSObject[name]; + } + + function $(id) { + return document.getElementById(id); + } + + function addBrowser(parent, id, width, height) { + var b = + document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "browser"); + b.setAttribute("type", "content"); + b.setAttribute("id", id); + b.setAttribute("width", width); + b.setAttribute("height", height); + $(parent).appendChild(b); + } + addBrowser("parent", "f1", 300, 200); + addBrowser("parent", "f2", 300, 200); + + /** Test for Bug 449778 **/ + var doc1 = "data:text/html,<html><body>This is a test</body></html>"; + var doc2 = "data:text/html,<html><body>This is a second test</body></html>"; + var doc3 = "data:text/html,<html><body>This is a <script>var evt = document.createEvent('Events'); evt.initEvent('testEvt', true, true); document.dispatchEvent(evt);</script>third test</body></html>"; + + + $("f1").setAttribute("src", doc1); + $("f2").setAttribute("src", doc2); + + function doTheTest() { + var strs = { "f1": "", "f2" : "" }; + function attachListener(node, type) { + var listener = function(e) { + if (strs[node.id]) strs[node.id] += " "; + strs[node.id] += node.id + ".page" + type; + } + node.addEventListener("page" + type, listener, false); + + listener.detach = function() { + node.removeEventListener("page" + type, listener, false); + } + return listener; + } + + var l1 = attachListener($("f1"), "show"); + var l2 = attachListener($("f1"), "hide"); + var l3 = attachListener($("f2"), "show"); + var l4 = attachListener($("f2"), "hide"); + + $("f1").swapDocShells($("f2")); + + is(strs["f1"], "f1.pagehide f1.pageshow", + "Expected hide then show on first loaded page"); + is(strs["f2"], "f2.pagehide f2.pageshow", + "Expected hide then show on second loaded page"); + + function listener2() { + $("f2").removeEventListener("testEvt", listener2, false); + + strs = { "f1": "", "f2" : "" }; + + $("f1").swapDocShells($("f2")); + is(strs["f1"], "f1.pagehide", + "Expected hide on already-loaded page, then nothing"); + is(strs["f2"], "f2.pageshow f2.pagehide f2.pageshow", + "Expected show on still-loading page, then hide on it, then show " + + "on already-loaded page"); + + strs = { "f1": "", "f2" : "" }; + + $("f1").addEventListener("pageshow", listener3, false); + } + + function listener3() { + $("f1").removeEventListener("pageshow", listener3, false); + + is(strs["f1"], "f1.pageshow", + "Expected show as our page finishes loading"); + is(strs["f2"], "", "Expected no more events here."); + + l1.detach(); + l2.detach(); + l3.detach(); + l4.detach(); + + window.close(); + SimpleTest.finish(); + } + + $("f2").addEventListener("testEvt", listener2, false, true); + $("f2").setAttribute("src", doc3); + } + + ]]></script> +</window> diff --git a/docshell/test/chrome/bug449780_window.xul b/docshell/test/chrome/bug449780_window.xul new file mode 100644 index 000000000..38633c00f --- /dev/null +++ b/docshell/test/chrome/bug449780_window.xul @@ -0,0 +1,77 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<window title="Mozilla Bug 449780" onload="doTheTest()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <hbox id="parent"> + </hbox> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + var imports = [ "SimpleTest", "is", "isnot", "ok", "onerror" ]; + for (var name of imports) { + window[name] = window.opener.wrappedJSObject[name]; + } + + function $(id) { + return document.getElementById(id); + } + + function addBrowser(parent, id, width, height) { + var b = + document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "browser"); + b.setAttribute("type", "content"); + b.setAttribute("id", id); + b.setAttribute("width", width); + b.setAttribute("height", height); + $(parent).appendChild(b); + } + addBrowser("parent", "f1", 300, 200); + addBrowser("parent", "f2", 300, 200); + + /** Test for Bug 449780 **/ + var doc1 = "data:text/html,<html><body>This is a test</body></html>"; + var doc2 = "data:text/html,<html><body>This is a second test</body></html>"; + + function getDOM(id) { + return $(id).contentDocument.documentElement.innerHTML; + } + + var tester = (function() { + var origDOM = getDOM("f1"); + $("f1").contentDocument.body.textContent = "Modified"; + var modifiedDOM = getDOM("f1"); + isnot(origDOM, modifiedDOM, "DOM should be different"); + $("f1").contentWindow.location.href = doc2; + yield undefined; + + $("f1").goBack(); + yield undefined; + + is(getDOM("f1"), modifiedDOM, "Should have been bfcached"); + $("f1").goForward(); + yield undefined; + + // Ignore the notifications during swap + $("f1").removeEventListener("pageshow", testDriver, false); + $("f1").swapDocShells($("f2")); + $("f2").addEventListener("pageshow", testDriver, false); + $("f2").goBack(); + yield undefined; + + is(getDOM("f2"), origDOM, "Should have not have been bfcached"); + window.close(); + SimpleTest.finish(); + yield undefined; + })(); + + function testDriver() { + setTimeout(function() { tester.next() }, 0); + } + + function doTheTest() { + $("f1").addEventListener("pageshow", testDriver, false); + $("f1").setAttribute("src", doc1); + } + ]]></script> +</window> diff --git a/docshell/test/chrome/bug454235-subframe.xul b/docshell/test/chrome/bug454235-subframe.xul new file mode 100644 index 000000000..a8b6178e6 --- /dev/null +++ b/docshell/test/chrome/bug454235-subframe.xul @@ -0,0 +1,7 @@ +<window title="Mozilla Bug 454235 SubFrame" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <deck flex="1"> + <browser id="topBrowser" src="about:mozilla"/> + <browser id="burriedBrowser" src="about:mozilla"/> + </deck> +</window> diff --git a/docshell/test/chrome/bug582176_window.xul b/docshell/test/chrome/bug582176_window.xul new file mode 100644 index 000000000..ad594ee0c --- /dev/null +++ b/docshell/test/chrome/bug582176_window.xul @@ -0,0 +1,88 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="303267Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="nextTestAsync();" + title="bug 582176 test"> + + <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" /> + <script type="application/javascript" src="docshell_helpers.js" /> + <script type="application/javascript"><![CDATA[ + + // Define the generator-iterator for the tests. + var tests = testIterator(); + + //// + // Execute the next test in the generator function. + // + function nextTestAsync() { + SimpleTest.executeSoon(tests.next.bind(tests)); + } + + //// + // Generator function for test steps for bug 582176: + // Description goes here. + // + function testIterator() + { + var browser = document.getElementById('content'); + browser.addEventListener("pageshow", nextTestAsync, true); + + enableBFCache(true); + + var notificationCount = 0; + var observer = { + observe: function(aSubject, aTopic, aData) { + is(aSubject, browser.contentWindow, + "correct subject"); + is(aTopic, "content-document-global-created", + "correct topic"); + is(aData, "http://mochi.test:8888", + "correct data"); + notificationCount++; + } + }; + + os = Components.classes["@mozilla.org/observer-service;1"]. + getService(Components.interfaces.nsIObserverService); + os.addObserver(observer, "content-document-global-created", false); + + browser.loadURI("http://mochi.test:8888/tests/docshell/test/chrome/582176_dummy.html"); + yield undefined; + is(browser.contentWindow.testVar, undefined, + "variable unexpectedly there already"); + browser.contentWindow.wrappedJSObject.testVar = 1; + is(notificationCount, 1, "Should notify on first navigation"); + + browser.loadURI("http://mochi.test:8888/tests/docshell/test/chrome/582176_dummy.html?2"); + yield undefined; + is(browser.contentWindow.wrappedJSObject.testVar, undefined, + "variable should no longer be there"); + is(notificationCount, 2, "Should notify on second navigation"); + + browser.goBack(); + yield undefined; + is(browser.contentWindow.wrappedJSObject.testVar, 1, + "variable should still be there"); + is(notificationCount, 2, "Should not notify on back navigation"); + + browser.loadURI("http://mochi.test:8888/tests/docshell/test/chrome/582176_xml.xml"); + yield undefined; + is(browser.contentDocument.body.textContent, "xslt result", + "Transform performed successfully"); + is(notificationCount, 3, "Should notify only once on XSLT navigation"); + + os.removeObserver(observer, "content-document-global-created") + + // Tell the framework the test is finished. Include the final 'yield' + // statement to prevent a StopIteration exception from being thrown. + finish(); + yield undefined; + } + + ]]></script> + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/bug608669.xul b/docshell/test/chrome/bug608669.xul new file mode 100644 index 000000000..1ab012c14 --- /dev/null +++ b/docshell/test/chrome/bug608669.xul @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<window title="Mozilla Bug 608669 - Blank page" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <description flex="1" value="This window is intentionally left blank"/> +</window> diff --git a/docshell/test/chrome/bug662200_window.xul b/docshell/test/chrome/bug662200_window.xul new file mode 100644 index 000000000..a73e2d296 --- /dev/null +++ b/docshell/test/chrome/bug662200_window.xul @@ -0,0 +1,129 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="303267Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="setTimeout(nextTest,0);" + title="bug 662200 test"> + + <script type="application/javascript" + src="docshell_helpers.js"> + </script> + <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" /> + <script type="application/javascript"><![CDATA[ + + // Define the generator-iterator for the tests. + var tests = testIterator(); + + //// + // Execute the next test in the generator function. + // + function nextTest() { + tests.next(); + } + + //// + // Generator function for test steps for bug 662200: + // Description goes here. + // + function testIterator() + { + // Load the first test page + var navData = { + uri: getHttpUrl("662200a.html"), + eventsToListenFor: ["pageshow"], + expectedEvents: [ {type: "pageshow", title: "A"} ], + onNavComplete: nextTest + }; + doPageNavigation(navData); + yield undefined; + + // Load the second test page. + navData = { + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ {type: "pagehide", + title: "A"}, + {type: "pageshow", + title: "B"} ], + onNavComplete: nextTest + } + waitForPageEvents(navData); + var link = TestWindow.getDocument().getElementById("link"); + var event = TestWindow.getDocument().createEvent("MouseEvents"); + event.initMouseEvent("click", true, true, TestWindow.getWindow(), + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + link.dispatchEvent(event); + yield undefined; + + // Load the third test page. + navData = { + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ {type: "pagehide", + title: "B"}, + {type: "pageshow", + title: "C"} ], + onNavComplete: nextTest + }; + waitForPageEvents(navData); + var link = TestWindow.getDocument().getElementById("link"); + var event = TestWindow.getDocument().createEvent("MouseEvents"); + event.initMouseEvent("click", true, true, TestWindow.getWindow(), + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + link.dispatchEvent(event); + yield undefined; + + // Go back. + navData = { + back: true, + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ {type: "pagehide", + title: "C"}, + {type: "pageshow", + title: "B"} ], + onNavComplete: nextTest + }; + doPageNavigation(navData); + yield undefined; + + var Ci = Components.interfaces; + var docshell = TestWindow.getWindow() + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + var shistory = docshell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISHistory) + .QueryInterface(Ci.nsIWebNavigation); + + // Reload. + navData = { + eventsToListenFor: ["pageshow", "pagehide"], + expectedEvents: [ {type: "pagehide", + title: "B"}, + {type: "pageshow", + title: "B"} ], + onNavComplete: nextTest + }; + // Asking the docshell harness to reload for us will call reload on + // nsDocShell which has different behavior than the reload on nsSHistory + // so we call reload explicitly here + waitForPageEvents(navData); + shistory.reload(0); + yield undefined; + + // After this sequence of events, we should be able to go back and forward + is(TestWindow.getBrowser().canGoBack, true, "Should be able to go back!"); + is(TestWindow.getBrowser().canGoForward, true, "Should be able to go forward!"); + is(shistory.requestedIndex, -1, "Requested index should be cleared!"); + + // Tell the framework the test is finished. Include the final 'yield' + // statement to prevent a StopIteration exception from being thrown. + finish(); + yield undefined; + } + + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/bug690056_window.xul b/docshell/test/chrome/bug690056_window.xul new file mode 100644 index 000000000..ce5700e39 --- /dev/null +++ b/docshell/test/chrome/bug690056_window.xul @@ -0,0 +1,176 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="690056Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="setTimeout(nextTest,0);" + title="bug 6500056 test"> + + <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" /> + <script type="application/javascript" src="docshell_helpers.js" /> + <script type="application/javascript"><![CDATA[ + var tests = testIterator(); + + function nextTest() { + tests.next(); + } + + // Makes sure that we fire the visibilitychange events + function testIterator() { + // Enable bfcache + enableBFCache(8); + + // Load something for a start + doPageNavigation({ + uri: 'data:text/html,<title>initial load</title>', + onNavComplete: nextTest + }); + yield undefined; + + // Now load a new page + doPageNavigation({ + uri: 'data:text/html,<title>new load</title>', + eventsToListenFor: [ "pageshow", "pagehide", "visibilitychange" ], + expectedEvents: [ { type: "pagehide", + title: "initial load", + persisted: true }, + { type: "visibilitychange", + title: "initial load", + visibilityState: "hidden", + hidden: true }, + // No visibilitychange events fired for initial pageload + { type: "pageshow", + title: "new load", + persisted: false }, // false on initial load + ], + onNavComplete: nextTest + }); + yield undefined; + + // Now go back + doPageNavigation({ + back: true, + eventsToListenFor: [ "pageshow", "pagehide", "visibilitychange" ], + expectedEvents: [ { type: "pagehide", + title: "new load", + persisted: true }, + { type: "visibilitychange", + title: "new load", + visibilityState: "hidden", + hidden: true }, + { type: "visibilitychange", + title: "initial load", + visibilityState: "visible", + hidden: false }, + { type: "pageshow", + title: "initial load", + persisted: true }, + ], + onNavComplete: nextTest + }); + yield undefined; + + // And forward + doPageNavigation({ + forward: true, + eventsToListenFor: [ "pageshow", "pagehide", "visibilitychange" ], + expectedEvents: [ { type: "pagehide", + title: "initial load", + persisted: true }, + { type: "visibilitychange", + title: "initial load", + visibilityState: "hidden", + hidden: true }, + { type: "visibilitychange", + title: "new load", + visibilityState: "visible", + hidden: false }, + { type: "pageshow", + title: "new load", + persisted: true }, + ], + onNavComplete: nextTest + }); + yield undefined; + + function generateDetector(state, hidden, title, name) { + var detector = function (event) { + is(event.target.hidden, hidden, + name + " hidden value does not match"); + is(event.target.visibilityState, state, + name + " state value does not match"); + is(event.target.title, title, + name + " title value does not match"); + document.getElementById("content") + .removeEventListener("visibilitychange", + detector, + true); + nextTest(); + } + + document.getElementById("content") + .addEventListener("visibilitychange", detector, true); + } + + generateDetector("hidden", true, "new load", "Going hidden"); + + // Now flip our docshell to not active + document.getElementById("content").docShellIsActive = false; + yield undefined; + + // And navigate back; there should be no visibility state transitions + doPageNavigation({ + back: true, + eventsToListenFor: [ "pageshow", "pagehide", "visibilitychange" ], + expectedEvents: [ { type: "pagehide", + title: "new load", + persisted: true }, + { type: "pageshow", + title: "initial load", + persisted: true }, + ], + unexpectedEvents: [ "visibilitychange" ], + onNavComplete: nextTest + }); + yield undefined; + + generateDetector("visible", false, "initial load", "Going visible"); + + // Now set the docshell active again + document.getElementById("content").docShellIsActive = true; + yield undefined; + + // And forward + doPageNavigation({ + forward: true, + eventsToListenFor: [ "pageshow", "pagehide", "visibilitychange" ], + expectedEvents: [ { type: "pagehide", + title: "initial load", + persisted: true }, + { type: "visibilitychange", + title: "initial load", + visibilityState: "hidden", + hidden: true }, + { type: "visibilitychange", + title: "new load", + visibilityState: "visible", + hidden: false }, + { type: "pageshow", + title: "new load", + persisted: true }, + ], + onNavComplete: nextTest + }); + yield undefined; + + // Tell the framework the test is finished. Include the final 'yield' + // statement to prevent a StopIteration exception from being thrown. + finish(); + yield undefined; + } + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/bug846906.html b/docshell/test/chrome/bug846906.html new file mode 100644 index 000000000..a289417ea --- /dev/null +++ b/docshell/test/chrome/bug846906.html @@ -0,0 +1,10 @@ +<html> + <head> + <title> + </title> + </head> + <body> + <div id="div1" style="width:1024px; height:768px; border:none;"> + </div> + </body> +</html> diff --git a/docshell/test/chrome/bug89419.sjs b/docshell/test/chrome/bug89419.sjs new file mode 100644 index 000000000..c075b1e50 --- /dev/null +++ b/docshell/test/chrome/bug89419.sjs @@ -0,0 +1,13 @@ +function handleRequest(request, response) +{ + var redirectstate = "/docshell/test/chrome/bug89419.sjs"; + response.setStatusLine("1.1", 302, "Found"); + if (getState(redirectstate) == "") { + response.setHeader("Location", "red.png", false); + setState(redirectstate, "red"); + } else { + response.setHeader("Location", "blue.png", false); + setState(redirectstate, ""); + } + response.setHeader("Cache-Control", "no-cache", false); +} diff --git a/docshell/test/chrome/bug89419_window.xul b/docshell/test/chrome/bug89419_window.xul new file mode 100644 index 000000000..923378ea0 --- /dev/null +++ b/docshell/test/chrome/bug89419_window.xul @@ -0,0 +1,82 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="89419Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="setTimeout(nextTest,0);" + title="bug 89419 test"> + + <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" /> + <script type="text/javascript" + src="chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js"/> + <script type="text/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SpecialPowersObserverAPI.js"/> + <script type="text/javascript" + src="chrome://mochikit/content/tests/SimpleTest/specialpowers.js"/> + <script type="application/javascript" src="docshell_helpers.js" /> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script> + + <script type="application/javascript"><![CDATA[ + // Define the generator-iterator for the tests. + var tests = testIterator(); + + //// + // Execute the next test in the generator function. + // + function nextTest() { + tests.next(); + } + + //// + // Generator function for test steps for bug 89419: + // A visited link should have the :visited style applied + // to it when displayed on a page which was fetched from + // the bfcache. + // + function testIterator() + { + // Load a test page containing an image referring to the sjs that returns + // a different redirect every time it's loaded. + doPageNavigation({ + uri: getHttpUrl("89419.html"), + onNavComplete: nextTest, + preventBFCache: true + }); + yield undefined; + + var first = snapshotWindow(TestWindow.getWindow()); + + doPageNavigation({ + uri: "about:blank", + onNavComplete: nextTest + }); + yield undefined; + + var second = snapshotWindow(TestWindow.getWindow()); + function snapshotsEqual(snap1, snap2) { + return compareSnapshots(snap1, snap2, true)[0]; + } + ok(!snapshotsEqual(first, second), "about:blank should not be the same as the image web page"); + + doPageNavigation({ + back: true, + onNavComplete: nextTest + }); + yield undefined; + + var third = snapshotWindow(TestWindow.getWindow()); + ok(!snapshotsEqual(third, second), "going back should not be the same as about:blank"); + ok(snapshotsEqual(first, third), "going back should be the same as the initial load"); + + // Tell the framework the test is finished. Include the final 'yield' + // statement to prevent a StopIteration exception from being thrown. + finish(); + yield undefined; + } + + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/bug909218.html b/docshell/test/chrome/bug909218.html new file mode 100644 index 000000000..a11fa6000 --- /dev/null +++ b/docshell/test/chrome/bug909218.html @@ -0,0 +1,11 @@ +<html>
+<head>
+ <link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/SimpleTest/test.css">
+ <script src="bug909218.js"></script>
+</head>
+<body>
+ <img src="http://mochi.test:8888/tests/docshell/test/chrome/red.png">
+ <!-- an iframe so we can check these too get the correct flags -->
+ <iframe src="generic.html"/>
+</body>
+</html>
diff --git a/docshell/test/chrome/bug909218.js b/docshell/test/chrome/bug909218.js new file mode 100644 index 000000000..c360616f9 --- /dev/null +++ b/docshell/test/chrome/bug909218.js @@ -0,0 +1,2 @@ +// This file exists just to ensure that we load it with the correct flags.
+dump("bug909218.js loaded\n");
diff --git a/docshell/test/chrome/bug92598_window.xul b/docshell/test/chrome/bug92598_window.xul new file mode 100644 index 000000000..ed877b383 --- /dev/null +++ b/docshell/test/chrome/bug92598_window.xul @@ -0,0 +1,118 @@ +<?xml version="1.0"?> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="92598Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="onLoad();" + title="92598 test"> + + <script type="application/javascript"><![CDATA[ + const LISTEN_EVENTS = ["load", "unload", "pageshow", "pagehide"]; + + var gBrowser; + var gTestsIterator; + var gExpected = []; + + function ok(condition, message) { + window.opener.wrappedJSObject.SimpleTest.ok(condition, message); + } + + function is(a, b, message) { + window.opener.wrappedJSObject.SimpleTest.is(a, b, message); + } + + function finish() { + for (let eventType of LISTEN_EVENTS) { + gBrowser.removeEventListener(eventType, eventListener, true); + } + + // Work around bug 467960 + var history = gBrowser.webNavigation.sessionHistory; + history.PurgeHistory(history.count); + + window.close(); + window.opener.wrappedJSObject.SimpleTest.finish(); + } + + function onLoad() { + gBrowser = document.getElementById("content"); + for (let eventType of LISTEN_EVENTS) { + gBrowser.addEventListener(eventType, eventListener, true); + } + + gTestsIterator = testsIterator(); + nextTest(); + } + + function eventListener(event) { + ok(gExpected.length >= 1, "Unexpected event " + event.type); + if (gExpected.length == 0) { + // in case of unexpected event, try to continue anyway + setTimeout(nextTest, 0); + return; + } + + var exp = gExpected.shift(); + is(event.type, exp.type, "Invalid event received"); + if (typeof(exp.persisted) != "undefined") { + is(event.persisted, exp.persisted, "Invalid persisted state"); + } + if (exp.title) { + ok(event.originalTarget instanceof HTMLDocument, + "originalTarget not a HTMLDocument"); + is(event.originalTarget.title, exp.title, "titles don't match"); + } + + if (gExpected.length == 0) { + setTimeout(nextTest, 0); + } + } + + function nextTest() { + try { + gTestsIterator.next(); + } catch (err if err instanceof StopIteration) { + finish(); + } + } + + function testsIterator() { + // Load a page with a no-cache header, followed by a simple page + // On pagehide, first page should report it is not being persisted + var test1DocURI = "http://mochi.test:8888/tests/docshell/test/chrome/92598_nostore.html"; + + gExpected = [{type: "pagehide", persisted: true}, + {type: "load", title: "test1"}, + {type: "pageshow", title: "test1", persisted: false}]; + gBrowser.loadURI(test1DocURI); + yield undefined; + + var test2Doc = "data:text/html,<html><head><title>test2</title></head>" + + "<body>test2</body></html>"; + + gExpected = [{type: "pagehide", title: "test1", persisted: false}, + {type: "unload", title: "test1"}, + {type: "load", title: "test2"}, + {type: "pageshow", title: "test2", persisted: false}]; + gBrowser.loadURI(test2Doc); + yield undefined; + + // Now go back in history. First page should not have been cached. + // Check persisted property to confirm + gExpected = [{type: "pagehide", title: "test2", persisted: true}, + {type: "load", title: "test1"}, + {type: "pageshow", title: "test1", persisted: false}]; + gBrowser.goBack(); + yield undefined; + } + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/chrome/chrome.ini b/docshell/test/chrome/chrome.ini new file mode 100644 index 000000000..c3c5b9f80 --- /dev/null +++ b/docshell/test/chrome/chrome.ini @@ -0,0 +1,88 @@ +[DEFAULT] +skip-if = os == 'android' +support-files = + 662200a.html + 662200b.html + 662200c.html + 89419.html + 92598_nostore.html + bug112564_window.xul + bug113934_window.xul + bug215405_window.xul + bug293235.html + bug293235_p2.html + bug293235_window.xul + bug294258_testcase.html + bug294258_window.xul + bug298622_window.xul + bug301397_1.html + bug301397_2.html + bug301397_3.html + bug301397_4.html + bug301397_window.xul + bug303267.html + bug303267_window.xul + bug311007_window.xul + bug321671_window.xul + bug360511_case1.html + bug360511_case2.html + bug360511_window.xul + bug364461_window.xul + bug396519_window.xul + bug396649_window.xul + bug449778_window.xul + bug449780_window.xul + bug454235-subframe.xul + bug582176_window.xul + bug608669.xul + bug662200_window.xul + bug690056_window.xul + bug846906.html + bug89419_window.xul + bug909218.html + bug909218.js + bug92598_window.xul + docshell_helpers.js + file_viewsource_forbidden_in_iframe.html + generic.html + mozFrameType_window.xul + +[test_allowContentRetargeting.html] +[test_bug112564.xul] +[test_bug113934.xul] +[test_bug215405.xul] +[test_bug293235.xul] +[test_bug294258.xul] +[test_bug298622.xul] +[test_bug301397.xul] +[test_bug303267.xul] +[test_bug311007.xul] +[test_bug321671.xul] +[test_bug360511.xul] +[test_bug364461.xul] +[test_bug396519.xul] +[test_bug396649.xul] +[test_bug428288.html] +[test_bug449778.xul] +[test_bug449780.xul] +[test_bug453650.xul] +[test_bug454235.xul] +# bug 684176 +skip-if = toolkit == "gtk2" +[test_bug456980.xul] +[test_bug565388.xul] +skip-if = os == 'linux' || os == 'mac' # Bug 1026815 +[test_bug582176.xul] +[test_bug608669.xul] +[test_bug662200.xul] +[test_bug690056.xul] +[test_bug789773.xul] +[test_bug846906.xul] +[test_bug89419.xul] +[test_bug909218.html] +[test_bug92598.xul] +[test_mozFrameType.xul] +[test_principalInherit.xul] +[test_private_hidden_window.html] +[test_viewsource_forbidden_in_iframe.xul] +skip-if = true # bug 1019315 diff --git a/docshell/test/chrome/docshell_helpers.js b/docshell/test/chrome/docshell_helpers.js new file mode 100755 index 000000000..2d55e5a6e --- /dev/null +++ b/docshell/test/chrome/docshell_helpers.js @@ -0,0 +1,496 @@ +/** + * Import common SimpleTest methods so that they're usable in this window. + */ +var imports = [ "SimpleTest", "is", "isnot", "ok", "onerror", "todo", + "todo_is", "todo_isnot" ]; +for (var name of imports) { + window[name] = window.opener.wrappedJSObject[name]; +} + +/** + * Define global constants and variables. + */ +const NAV_NONE = 0; +const NAV_BACK = 1; +const NAV_FORWARD = 2; +const NAV_URI = 3; +const NAV_RELOAD = 4; + +var gExpectedEvents; // an array of events which are expected to + // be triggered by this navigation +var gUnexpectedEvents; // an array of event names which are NOT expected + // to be triggered by this navigation +var gFinalEvent; // true if the last expected event has fired +var gUrisNotInBFCache = []; // an array of uri's which shouldn't be stored + // in the bfcache +var gNavType = NAV_NONE; // defines the most recent navigation type + // executed by doPageNavigation +var gOrigMaxTotalViewers = // original value of max_total_viewers, + undefined; // to be restored at end of test + +var gExtractedPath = null; //used to cache file path for extracting files from a .jar file + +/** + * The doPageNavigation() function performs page navigations asynchronously, + * listens for specified events, and compares actual events with a list of + * expected events. When all expected events have occurred, an optional + * callback can be notified. The parameter passed to this function is an + * object with the following properties: + * + * uri: if !undefined, the browser will navigate to this uri + * + * back: if true, the browser will execute goBack() + * + * forward: if true, the browser will execute goForward() + * + * reload: if true, the browser will execute reload() + * + * eventsToListenFor: an array containing one or more of the following event + * types to listen for: "pageshow", "pagehide", "onload", + * "onunload". If this property is undefined, only a + * single "pageshow" events will be listened for. If this + * property is explicitly empty, [], then no events will + * be listened for. + * + * expectedEvents: an array of one or more expectedEvent objects, + * corresponding to the events which are expected to be + * fired for this navigation. Each object has the + * following properties: + * + * type: one of the event type strings + * title (optional): the title of the window the + * event belongs to + * persisted (optional): the event's expected + * .persisted attribute + * + * This function will verify that events with the + * specified properties are fired in the same order as + * specified in the array. If .title or .persisted + * properties for an expectedEvent are undefined, those + * properties will not be verified for that particular + * event. + * + * This property is ignored if eventsToListenFor is + * undefined or []. + * + * preventBFCache: if true, an unload handler will be added to the loaded + * page to prevent it from being bfcached. This property + * has no effect when eventsToListenFor is []. + * + * onNavComplete: a callback which is notified after all expected events + * have occurred, or after a timeout has elapsed. This + * callback is not notified if eventsToListenFor is []. + * + * There must be an expectedEvent object for each event of the types in + * eventsToListenFor which is triggered by this navigation. For example, if + * eventsToListenFor = [ "pagehide", "pageshow" ], then expectedEvents + * must contain an object for each pagehide and pageshow event which occurs as + * a result of this navigation. + */ +function doPageNavigation(params) { + // Parse the parameters. + let back = params.back ? params.back : false; + let forward = params.forward ? params.forward : false; + let reload = params.reload ? params.reload : false; + let uri = params.uri ? params.uri : false; + let eventsToListenFor = typeof(params.eventsToListenFor) != "undefined" ? + params.eventsToListenFor : ["pageshow"]; + gExpectedEvents = typeof(params.eventsToListenFor) == "undefined" || + eventsToListenFor.length == 0 ? undefined : params.expectedEvents; + gUnexpectedEvents = typeof(params.eventsToListenFor) == "undefined" || + eventsToListenFor.length == 0 ? undefined : params.unexpectedEvents; + let preventBFCache = (typeof[params.preventBFCache] == "undefined") ? + false : params.preventBFCache; + let waitOnly = (typeof(params.waitForEventsOnly) == "boolean" + && params.waitForEventsOnly); + + // Do some sanity checking on arguments. + if (back && forward) + throw "Can't specify both back and forward"; + if (back && uri) + throw "Can't specify both back and a uri"; + if (forward && uri) + throw "Can't specify both forward and a uri"; + if (reload && (forward || back || uri)) + throw "Can't specify reload and another navigation type"; + if (!back && !forward && !uri && !reload && !waitOnly) + throw "Must specify back or foward or reload or uri"; + if (params.onNavComplete && eventsToListenFor.length == 0) + throw "Can't use onNavComplete when eventsToListenFor == []"; + if (params.preventBFCache && eventsToListenFor.length == 0) + throw "Can't use preventBFCache when eventsToListenFor == []"; + if (params.preventBFCache && waitOnly) + throw "Can't prevent bfcaching when only waiting for events"; + if (waitOnly && typeof(params.onNavComplete) == "undefined") + throw "Must specify onNavComplete when specifying waitForEventsOnly"; + if (waitOnly && (back || forward || reload || uri)) + throw "Can't specify a navigation type when using waitForEventsOnly"; + for (let anEventType of eventsToListenFor) { + let eventFound = false; + if ( (anEventType == "pageshow") && (!gExpectedEvents) ) + eventFound = true; + if (gExpectedEvents) { + for (let anExpectedEvent of gExpectedEvents) { + if (anExpectedEvent.type == anEventType) + eventFound = true; + } + } + if (gUnexpectedEvents) { + for (let anExpectedEventType of gUnexpectedEvents) { + if (anExpectedEventType == anEventType) + eventFound = true; + } + } + if (!eventFound) + throw "Event type " + anEventType + " is specified in " + + "eventsToListenFor, but not in expectedEvents"; + } + + // If the test explicitly sets .eventsToListenFor to [], don't wait for any + // events. + gFinalEvent = eventsToListenFor.length == 0 ? true : false; + + // Add an event listener for each type of event in the .eventsToListenFor + // property of the input parameters. + for (let eventType of eventsToListenFor) { + dump("TEST: registering a listener for " + eventType + " events\n"); + TestWindow.getBrowser().addEventListener(eventType, pageEventListener, + true); + } + + // Perform the specified navigation. + if (back) { + gNavType = NAV_BACK; + TestWindow.getBrowser().goBack(); + } + else if (forward) { + gNavType = NAV_FORWARD; + TestWindow.getBrowser().goForward(); + } + else if (uri) { + gNavType = NAV_URI; + TestWindow.getBrowser().loadURI(uri); + } + else if (reload) { + gNavType = NAV_RELOAD; + TestWindow.getBrowser().reload(); + } + else if (waitOnly) { + gNavType = NAV_NONE; + } + else { + throw "No valid navigation type passed to doPageNavigation!"; + } + + // If we're listening for events and there is an .onNavComplete callback, + // wait for all events to occur, and then call doPageNavigation_complete(). + if (eventsToListenFor.length > 0 && params.onNavComplete) + { + waitForTrue( + function() { return gFinalEvent; }, + function() { + doPageNavigation_complete(eventsToListenFor, params.onNavComplete, + preventBFCache); + } ); + } +} + +/** + * Finish doPageNavigation(), by removing event listeners, adding an unload + * handler if appropriate, and calling the onNavComplete callback. This + * function is called after all the expected events for this navigation have + * occurred. + */ +function doPageNavigation_complete(eventsToListenFor, onNavComplete, + preventBFCache) { + // Unregister our event listeners. + dump("TEST: removing event listeners\n"); + for (let eventType of eventsToListenFor) { + TestWindow.getBrowser().removeEventListener(eventType, pageEventListener, + true); + } + + // If the .preventBFCache property was set, add an empty unload handler to + // prevent the page from being bfcached. + let uri = TestWindow.getBrowser().currentURI.spec; + if (preventBFCache) { + TestWindow.getWindow().addEventListener("unload", function() { + dump("TEST: Called dummy unload function to prevent page from " + + "being bfcached.\n"); + }, true); + + // Save the current uri in an array of uri's which shouldn't be + // stored in the bfcache, for later verification. + if (!(uri in gUrisNotInBFCache)) { + gUrisNotInBFCache.push(uri); + } + } else if (gNavType == NAV_URI) { + // If we're navigating to a uri and .preventBFCache was not + // specified, splice it out of gUrisNotInBFCache if it's there. + gUrisNotInBFCache.forEach( + function(element, index, array) { + if (element == uri) { + array.splice(index, 1); + } + }, this); + } + + // Notify the callback now that we're done. + onNavComplete.call(); +} + +/** + * Allows a test to wait for page navigation events, and notify a + * callback when they've all been received. This works exactly the + * same as doPageNavigation(), except that no navigation is initiated. + */ +function waitForPageEvents(params) { + params.waitForEventsOnly = true; + doPageNavigation(params); +} + +/** + * The event listener which listens for expectedEvents. + */ +function pageEventListener(event) { + try { + dump("TEST: eventListener received a " + event.type + " event for page " + + event.originalTarget.title + ", persisted=" + event.persisted + "\n"); + } catch(e) { + // Ignore any exception. + } + + // If this page shouldn't be in the bfcache because it was previously + // loaded with .preventBFCache, make sure that its pageshow event + // has .persisted = false, even if the test doesn't explicitly test + // for .persisted. + if ( (event.type == "pageshow") && + (gNavType == NAV_BACK || gNavType == NAV_FORWARD) ) { + let uri = TestWindow.getBrowser().currentURI.spec; + if (uri in gUrisNotInBFCache) { + ok(!event.persisted, "pageshow event has .persisted = false, even " + + "though it was loaded with .preventBFCache previously\n"); + } + } + + if (typeof(gUnexpectedEvents) != "undefined") { + is(gUnexpectedEvents.indexOf(event.type), -1, + "Should not get unexpected event " + event.type); + } + + // If no expected events were specified, mark the final event as having been + // triggered when a pageshow event is fired; this will allow + // doPageNavigation() to return. + if ((typeof(gExpectedEvents) == "undefined") && event.type == "pageshow") + { + setTimeout(function() { gFinalEvent = true; }, 0); + return; + } + + // If there are explicitly no expected events, but we receive one, it's an + // error. + if (gExpectedEvents.length == 0) { + ok(false, "Unexpected event (" + event.type + ") occurred"); + return; + } + + // Grab the next expected event, and compare its attributes against the + // actual event. + let expected = gExpectedEvents.shift(); + + is(event.type, expected.type, + "A " + expected.type + " event was expected, but a " + + event.type + " event occurred"); + + if (typeof(expected.title) != "undefined") { + ok(event.originalTarget instanceof HTMLDocument, + "originalTarget for last " + event.type + + " event not an HTMLDocument"); + is(event.originalTarget.title, expected.title, + "A " + event.type + " event was expected for page " + + expected.title + ", but was fired for page " + + event.originalTarget.title); + } + + if (typeof(expected.persisted) != "undefined") { + is(event.persisted, expected.persisted, + "The persisted property of the " + event.type + " event on page " + + event.originalTarget.location + " had an unexpected value"); + } + + if ("visibilityState" in expected) { + is(event.originalTarget.visibilityState, expected.visibilityState, + "The visibilityState property of the document on page " + + event.originalTarget.location + " had an unexpected value"); + } + + if ("hidden" in expected) { + is(event.originalTarget.hidden, expected.hidden, + "The hidden property of the document on page " + + event.originalTarget.location + " had an unexpected value"); + } + + // If we're out of expected events, let doPageNavigation() return. + if (gExpectedEvents.length == 0) + setTimeout(function() { gFinalEvent = true; }, 0); +} + +/** + * End a test. + */ +function finish() { + // Work around bug 467960. + var history = TestWindow.getBrowser().webNavigation.sessionHistory; + history.PurgeHistory(history.count); + + // If the test changed the value of max_total_viewers via a call to + // enableBFCache(), then restore it now. + if (typeof(gOrigMaxTotalViewers) != "undefined") { + var prefs = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + prefs.setIntPref("browser.sessionhistory.max_total_viewers", + gOrigMaxTotalViewers); + } + + // Close the test window and signal the framework that the test is done. + let opener = window.opener; + let SimpleTest = opener.wrappedJSObject.SimpleTest; + + // Wait for the window to be closed before finishing the test + let ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] + .getService(Components.interfaces.nsIWindowWatcher); + ww.registerNotification(function(subject, topic, data) { + if (topic == "domwindowclosed") { + ww.unregisterNotification(arguments.callee); + SimpleTest.waitForFocus(SimpleTest.finish, opener); + } + }); + + window.close(); +} + +/** + * Helper function which waits until another function returns true, or until a + * timeout occurs, and then notifies a callback. + * + * Parameters: + * + * fn: a function which is evaluated repeatedly, and when it turns true, + * the onWaitComplete callback is notified. + * + * onWaitComplete: a callback which will be notified when fn() returns + * true, or when a timeout occurs. + * + * timeout: a timeout, in seconds or ms, after which waitForTrue() will + * fail an assertion and then return, even if the fn function never + * returns true. If timeout is undefined, waitForTrue() will never + * time out. + */ +function waitForTrue(fn, onWaitComplete, timeout) { + var start = new Date().valueOf(); + if (typeof(timeout) != "undefined") { + // If timeoutWait is less than 500, assume it represents seconds, and + // convert to ms. + if (timeout < 500) + timeout *= 1000; + } + + // Loop until the test function returns true, or until a timeout occurs, + // if a timeout is defined. + var intervalid; + intervalid = + setInterval( + function() { + var timeoutHit = false; + if (typeof(timeout) != "undefined") { + timeoutHit = new Date().valueOf() - start >= + timeout ? true : false; + if (timeoutHit) { + ok(false, "Timed out waiting for condition"); + } + } + if (timeoutHit || fn.call()) { + // Stop calling the test function and notify the callback. + clearInterval(intervalid); + onWaitComplete.call(); + } + }, 20); +} + +/** + * Enable or disable the bfcache. + * + * Parameters: + * + * enable: if true, set max_total_viewers to -1 (the default); if false, set + * to 0 (disabled), if a number, set it to that specific number + */ +function enableBFCache(enable) { + var prefs = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + + // If this is the first time the test called enableBFCache(), + // store the original value of max_total_viewers, so it can + // be restored at the end of the test. + if (typeof(gOrigMaxTotalViewers) == "undefined") { + gOrigMaxTotalViewers = + prefs.getIntPref("browser.sessionhistory.max_total_viewers"); + } + + if (typeof(enable) == "boolean") { + if (enable) + prefs.setIntPref("browser.sessionhistory.max_total_viewers", -1); + else + prefs.setIntPref("browser.sessionhistory.max_total_viewers", 0); + } + else if (typeof(enable) == "number") { + prefs.setIntPref("browser.sessionhistory.max_total_viewers", enable); + } +} + +/* + * get http root for local tests. Use a single extractJarToTmp instead of + * extracting for each test. + * Returns a file://path if we have a .jar file + */ +function getHttpRoot() { + var location = window.location.href; + location = getRootDirectory(location); + var jar = getJar(location); + if (jar != null) { + if (gExtractedPath == null) { + var resolved = extractJarToTmp(jar); + gExtractedPath = resolved.path; + } + } else { + return null; + } + return "file://" + gExtractedPath + '/'; +} + +/** + * Returns the full HTTP url for a file in the mochitest docshell test + * directory. + */ +function getHttpUrl(filename) { + var root = getHttpRoot(); + if (root == null) { + root = "http://mochi.test:8888/chrome/docshell/test/chrome/"; + } + return root + filename; +} + +/** + * A convenience object with methods that return the current test window, + * browser, and document. + */ +var TestWindow = {}; +TestWindow.getWindow = function () { + return document.getElementById("content").contentWindow; +} +TestWindow.getBrowser = function () { + return document.getElementById("content"); +} +TestWindow.getDocument = function () { + return document.getElementById("content").contentDocument; +} diff --git a/docshell/test/chrome/file_viewsource_forbidden_in_iframe.html b/docshell/test/chrome/file_viewsource_forbidden_in_iframe.html new file mode 100644 index 000000000..fdecbbdfe --- /dev/null +++ b/docshell/test/chrome/file_viewsource_forbidden_in_iframe.html @@ -0,0 +1,11 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Test ifranes for view-source forbidden in iframe tests</title> +</head> +<body> + <iframe id="testIframe"></iframe> + <iframe id="refIframe"></iframe> +</body> +</html> diff --git a/docshell/test/chrome/gen_template.pl b/docshell/test/chrome/gen_template.pl new file mode 100644 index 000000000..5fe9e9ea4 --- /dev/null +++ b/docshell/test/chrome/gen_template.pl @@ -0,0 +1,39 @@ +#!/usr/bin/perl + +# This script makes docshell test case templates. It takes one argument: +# +# -b: a bugnumber +# +# For example, this command: +# +# perl gen_template.pl -b 303267 +# +# Writes test case template files test_bug303267.xul and bug303267_window.xul +# to the current directory. + +use FindBin; +use Getopt::Long; +GetOptions("b=i"=> \$bug_number); + +$template = "$FindBin::RealBin/test.template.txt"; + +open(IN,$template) or die("Failed to open input file for reading."); +open(OUT, ">>test_bug" . $bug_number . ".xul") or die("Failed to open output file for appending."); +while((defined(IN)) && ($line = <IN>)) { + $line =~ s/{BUGNUMBER}/$bug_number/g; + print OUT $line; +} +close(IN); +close(OUT); + +$template = "$FindBin::RealBin/window.template.txt"; + +open(IN,$template) or die("Failed to open input file for reading."); +open(OUT, ">>bug" . $bug_number . "_window.xul") or die("Failed to open output file for appending."); +while((defined(IN)) && ($line = <IN>)) { + $line =~ s/{BUGNUMBER}/$bug_number/g; + print OUT $line; +} +close(IN); +close(OUT); + diff --git a/docshell/test/chrome/generic.html b/docshell/test/chrome/generic.html new file mode 100644 index 000000000..569a78c05 --- /dev/null +++ b/docshell/test/chrome/generic.html @@ -0,0 +1,12 @@ +<html> +<head> + <title> + generic page + </title> + </head> +<body> +<div id="div1" style="height: 1000px; border: thin solid black;"> + A generic page which can be used any time a test needs to load an arbitrary page via http. + </div> +</body> +</html> diff --git a/docshell/test/chrome/mozFrameType_window.xul b/docshell/test/chrome/mozFrameType_window.xul new file mode 100644 index 000000000..aa811c333 --- /dev/null +++ b/docshell/test/chrome/mozFrameType_window.xul @@ -0,0 +1,57 @@ +<?xml version="1.0"?> +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<window title="Test mozFrameType attribute" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="runTests();"> + + <html:iframe id="normalFrame"/> + <html:iframe id="typeContentFrame" mozframetype="content"/> + + <script type="application/javascript" src="docshell_helpers.js" /> + <script type="application/javascript"><![CDATA[ + function runTests() { + let opener = window.opener; + let SimpleTest = opener.wrappedJSObject.SimpleTest; + + let Ci = Components.interfaces; + + function getDocShellType(frame) { + return frame.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIDocShellTreeItem) + .itemType; + } + + var normalFrame = document.getElementById("normalFrame"); + var typeContentFrame = document.getElementById("typeContentFrame"); + + SimpleTest.is(getDocShellType(normalFrame), Ci.nsIDocShellTreeItem.typeChrome, + "normal iframe in chrome document is typeChrome"); + SimpleTest.is(getDocShellType(typeContentFrame), Ci.nsIDocShellTreeItem.typeContent, + "iframe with mozFrameType='content' in chrome document is typeContent"); + + SimpleTest.executeSoon(function () { + // First focus the parent window and then close this one. + SimpleTest.waitForFocus(function() { + let ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] + .getService(Components.interfaces.nsIWindowWatcher); + ww.registerNotification(function windowObs(subject, topic, data) { + if (topic == "domwindowclosed") { + ww.unregisterNotification(windowObs); + + // Don't start the next test synchronously! + SimpleTest.executeSoon(function() { + SimpleTest.finish(); + }); + } + }); + + window.close(); + }, opener); + }); + } + ]]></script> +</window> + diff --git a/docshell/test/chrome/red.png b/docshell/test/chrome/red.png Binary files differnew file mode 100644 index 000000000..aa9ce2526 --- /dev/null +++ b/docshell/test/chrome/red.png diff --git a/docshell/test/chrome/test.template.txt b/docshell/test/chrome/test.template.txt new file mode 100644 index 000000000..b7dd5e5c2 --- /dev/null +++ b/docshell/test/chrome/test.template.txt @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id={BUGNUMBER}.xul +--> +<window title="Mozilla Bug {BUGNUMBER}" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <title>Test for Bug {BUGNUMBER}</title> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id={BUGNUMBER}"> + Mozilla Bug {BUGNUMBER}</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug {BUGNUMBER} **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug{BUGNUMBER}_window.xul", "bug{BUGNUMBER}", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_allowContentRetargeting.html b/docshell/test/chrome/test_allowContentRetargeting.html new file mode 100644 index 000000000..58e838ca0 --- /dev/null +++ b/docshell/test/chrome/test_allowContentRetargeting.html @@ -0,0 +1,94 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(runNextTest); + +var TEST_URL = "http://mochi.test:8888/tests/docshell/test/chrome/allowContentRetargeting.sjs"; + +var Ci = Components.interfaces; + +function runNextTest() { + var test = tests.shift(); + if (!test) { + SimpleTest.finish(); + return; + } + test(); +} + +var tests = [ + + // Set allowContentRetargeting = false, load a downloadable URL, verify the + // downloadable stops loading. + function basic() { + var iframe = insertIframe(); + docshellForWindow(iframe.contentWindow).allowContentRetargeting = false; + loadIframe(iframe); + }, + + // Set allowContentRetargeting = false on parent docshell, load a downloadable + // URL, verify the downloadable stops loading. + function inherit() { + var docshell = docshellForWindow(window); + docshell.allowContentRetargeting = false; + loadIframe(insertIframe()); + }, +]; + +function docshellForWindow(win) { + return win. + QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIWebNavigation). + QueryInterface(Ci.nsIDocShell); +} + +function insertIframe() { + var iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + return iframe; +} + +function loadIframe(iframe) { + iframe.setAttribute("src", TEST_URL); + docshellForWindow(iframe.contentWindow). + QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIWebProgress). + addProgressListener(progressListener, + Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT); +} + +var progressListener = { + onStateChange: function (webProgress, req, flags, status) { + if (!(flags & Ci.nsIWebProgressListener.STATE_STOP)) + return; + is(Components.isSuccessCode(status), false, + "Downloadable should have failed to load"); + document.querySelector("iframe").remove(); + runNextTest(); + }, + + QueryInterface: function (iid) { + var iids = [ + Ci.nsIWebProgressListener, + Ci.nsISupportsWeakReference, + Ci.nsISupports, + ]; + if (iids.some(function (i) { return iid.equals(i); })) + return this; + throw Components.results.NS_ERROR_NO_INTERFACE; + }, +}; + + </script> +</head> +<body> +<p id="display"> +</p> +</body> +</html> diff --git a/docshell/test/chrome/test_bug112564.xul b/docshell/test/chrome/test_bug112564.xul new file mode 100644 index 000000000..0706595d0 --- /dev/null +++ b/docshell/test/chrome/test_bug112564.xul @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=112564 +--> +<window title="Mozilla Bug 112564" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=112564">Mozilla Bug 112564</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 112564 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug112564_window.xul", "bug112564", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_bug113934.xul b/docshell/test/chrome/test_bug113934.xul new file mode 100644 index 000000000..ba297e035 --- /dev/null +++ b/docshell/test/chrome/test_bug113934.xul @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=113934 +--> +<window title="Mozilla Bug 113934" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=113934" + target="_blank">Mozilla Bug 396519</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + addLoadEvent(function() { + window.open("bug113934_window.xul?content", "bug113934", + "chrome,width=800,height=800"); + }); + + ]]></script> +</window> diff --git a/docshell/test/chrome/test_bug215405.xul b/docshell/test/chrome/test_bug215405.xul new file mode 100644 index 000000000..e934d76ca --- /dev/null +++ b/docshell/test/chrome/test_bug215405.xul @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=215405 +--> +<window title="Mozilla Bug 215405" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=215405">Mozilla Bug 215405</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 215405 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug215405_window.xul", "bug215405", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_bug293235.xul b/docshell/test/chrome/test_bug293235.xul new file mode 100644 index 000000000..8a42c0a6e --- /dev/null +++ b/docshell/test/chrome/test_bug293235.xul @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=293235.xul +--> +<window title="Mozilla Bug 293235" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=293235"> + Mozilla Bug 293235</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 293235 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug293235_window.xul", "bug293235", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_bug294258.xul b/docshell/test/chrome/test_bug294258.xul new file mode 100644 index 000000000..4658a82de --- /dev/null +++ b/docshell/test/chrome/test_bug294258.xul @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=294258.xul +--> +<window title="Mozilla Bug 294258" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=294258"> + Mozilla Bug 294258</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 294258 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug294258_window.xul", "bug294258", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_bug298622.xul b/docshell/test/chrome/test_bug298622.xul new file mode 100644 index 000000000..c3217c7e5 --- /dev/null +++ b/docshell/test/chrome/test_bug298622.xul @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=298622.xul +--> +<window title="Mozilla Bug 298622" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=298622"> + Mozilla Bug 298622</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 298622 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug298622_window.xul", "bug298622", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_bug301397.xul b/docshell/test/chrome/test_bug301397.xul new file mode 100644 index 000000000..37fe47993 --- /dev/null +++ b/docshell/test/chrome/test_bug301397.xul @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=301397.xul +--> +<window title="Mozilla Bug 301397" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=301397"> + Mozilla Bug 301397</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 301397 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug301397_window.xul", "bug301397", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_bug303267.xul b/docshell/test/chrome/test_bug303267.xul new file mode 100644 index 000000000..f68dbdcd2 --- /dev/null +++ b/docshell/test/chrome/test_bug303267.xul @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=303267.xul +--> +<window title="Mozilla Bug 303267" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=303267">Mozilla Bug 303267</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +SimpleTest.expectAssertions(0, 1); + +/** Test for Bug 303267 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug303267_window.xul", "bug303267", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_bug311007.xul b/docshell/test/chrome/test_bug311007.xul new file mode 100644 index 000000000..32f1085f0 --- /dev/null +++ b/docshell/test/chrome/test_bug311007.xul @@ -0,0 +1,45 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=311007.xul +--> +<window title="Mozilla Bug 311007" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/MochiKit/packed.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=311007"> + Mozilla Bug 311007</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +if (navigator.platform.startsWith("Win")) { + SimpleTest.expectAssertions(0, 1); +} + +/** Test for Bug 311007 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug311007_window.xul", "bug311007", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_bug321671.xul b/docshell/test/chrome/test_bug321671.xul new file mode 100644 index 000000000..46164018b --- /dev/null +++ b/docshell/test/chrome/test_bug321671.xul @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=321671.xul +--> +<window title="Mozilla Bug 321671" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=321671"> + Mozilla Bug 321671</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 321671 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug321671_window.xul", "bug321671", + "chrome,width=600,height=600,scrollbars"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_bug360511.xul b/docshell/test/chrome/test_bug360511.xul new file mode 100644 index 000000000..ffa47fa27 --- /dev/null +++ b/docshell/test/chrome/test_bug360511.xul @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=360511.xul +--> +<window title="Mozilla Bug 360511" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"> + </script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=360511"> + Mozilla Bug 360511</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 360511 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug360511_window.xul", "bug360511", + "chrome,scrollbars,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_bug364461.xul b/docshell/test/chrome/test_bug364461.xul new file mode 100644 index 000000000..85154f9d7 --- /dev/null +++ b/docshell/test/chrome/test_bug364461.xul @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=364461 +--> +<window title="Mozilla Bug 364461" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=364461">Mozilla Bug 364461</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 364461 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug364461_window.xul", "bug364461", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_bug396519.xul b/docshell/test/chrome/test_bug396519.xul new file mode 100644 index 000000000..be1d148b7 --- /dev/null +++ b/docshell/test/chrome/test_bug396519.xul @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=396519 +--> +<window title="Mozilla Bug 396519" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=396519" + target="_blank">Mozilla Bug 396519</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 396519 **/ + + SimpleTest.waitForExplicitFinish(); + window.open("bug396519_window.xul", "bug396519", + "chrome,width=600,height=600"); + + ]]></script> +</window> diff --git a/docshell/test/chrome/test_bug396649.xul b/docshell/test/chrome/test_bug396649.xul new file mode 100644 index 000000000..3554c2f98 --- /dev/null +++ b/docshell/test/chrome/test_bug396649.xul @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=396649.xul +--> +<window title="Mozilla Bug 396649" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src= + "chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"> + </script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=396649"> + Mozilla Bug 396649</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 396649 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug396649_window.xul", "bug396649", + "chrome,width=600,height=600,scrollbars"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_bug428288.html b/docshell/test/chrome/test_bug428288.html new file mode 100644 index 000000000..83fcd20c6 --- /dev/null +++ b/docshell/test/chrome/test_bug428288.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=428288 +--> +<head> + <title>Test for Bug 428288</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=428288">Mozilla Bug 428288</a> +<p id="display"></p> +<div id="content" style="display: none"> + <iframe name="target"></iframe> + <a id="crashy" target="target" href="about:blank">crash me</a> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 428288 **/ + +function makeClick() { + var event = document.createEvent("MouseEvents"); + event.initMouseEvent("click", true, true, window, 0, 0,0,0,0, + false, false, false, false, 0, null); + document.getElementById("crashy").dispatchEvent(event); + return true; +} + +ok(makeClick(), "Crashes if bug 428288 is present"); + +</script> +</pre> +</body> +</html> + diff --git a/docshell/test/chrome/test_bug449778.xul b/docshell/test/chrome/test_bug449778.xul new file mode 100644 index 000000000..48c76c2e2 --- /dev/null +++ b/docshell/test/chrome/test_bug449778.xul @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=449778 +--> +<window title="Mozilla Bug 449778" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=449778" + target="_blank">Mozilla Bug 396519</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + addLoadEvent(function() { + window.open("bug449778_window.xul", "bug449778", + "chrome,width=800,height=800"); + }); + + ]]></script> +</window> diff --git a/docshell/test/chrome/test_bug449780.xul b/docshell/test/chrome/test_bug449780.xul new file mode 100644 index 000000000..fc5231d6b --- /dev/null +++ b/docshell/test/chrome/test_bug449780.xul @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=449780 +--> +<window title="Mozilla Bug 449780" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=449780" + target="_blank">Mozilla Bug 396519</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + addLoadEvent(function() { + window.open("bug449780_window.xul", "bug449780", + "chrome,width=800,height=800"); + }); + + ]]></script> +</window> diff --git a/docshell/test/chrome/test_bug453650.xul b/docshell/test/chrome/test_bug453650.xul new file mode 100644 index 000000000..2837e56d8 --- /dev/null +++ b/docshell/test/chrome/test_bug453650.xul @@ -0,0 +1,115 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=453650 +--> +<window title="Mozilla Bug 453650" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 453650 **/ + SimpleTest.waitForExplicitFinish(); + + var Ci = Components.interfaces; + var Cr = Components.results; + + var iter = runTests(); + nextTest(); + + function runTests() { + var iframe = document.createElement("iframe"); + iframe.style.width = "300px"; + iframe.style.height = "300px"; + iframe.setAttribute("src", "data:text/html,<h1 id='h'>hello</h1>"); + + document.documentElement.appendChild(iframe); + yield whenLoaded(iframe); + info("iframe loaded"); + + var h1 = iframe.contentDocument.getElementById("h"); + h1.style.width = "400px"; + yield waitForInterruptibleReflow(iframe.docShell); + + h1.style.width = "300px"; + waitForReflow(iframe.docShell); + yield is(300, h1.offsetWidth, "h1 has correct width"); + } + + function waitForInterruptibleReflow(docShell) { + waitForReflow(docShell, true); + } + + function waitForReflow(docShell, interruptible = false) { + function done() { + docShell.removeWeakReflowObserver(observer); + SimpleTest.executeSoon(nextTest); + } + + var observer = { + reflow: function (start, end) { + if (interruptible) { + ok(false, "expected interruptible reflow"); + } else { + ok(true, "observed uninterruptible reflow"); + } + + info("times: " + start + ", " + end); + ok(start < end, "reflow start time lower than end time"); + done(); + }, + + reflowInterruptible: function (start, end) { + if (!interruptible) { + ok(false, "expected uninterruptible reflow"); + } else { + ok(true, "observed interruptible reflow"); + } + + info("times: " + start + ", " + end); + ok(start < end, "reflow start time lower than end time"); + done(); + }, + + QueryInterface: function (iid) { + if (Ci.nsIReflowObserver.equals(iid) || + Ci.nsISupportsWeakReference.equals(iid) || + Ci.nsISupports.equals(iid)) + return this; + throw Cr.NS_ERROR_NO_INTERFACE; + }, + }; + + docShell.addWeakReflowObserver(observer); + } + + function whenLoaded(iframe) { + iframe.addEventListener("load", function onLoad() { + iframe.removeEventListener("load", onLoad); + SimpleTest.executeSoon(nextTest); + }); + } + + function nextTest() { + try { + iter.next(); + } catch (e if e instanceof StopIteration) { + SimpleTest.finish(); + } + } + + ]]> + </script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=453650" + target="_blank">Mozilla Bug 453650</a> + </body> +</window> diff --git a/docshell/test/chrome/test_bug454235.xul b/docshell/test/chrome/test_bug454235.xul new file mode 100644 index 000000000..f9107a186 --- /dev/null +++ b/docshell/test/chrome/test_bug454235.xul @@ -0,0 +1,56 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=454235 +--> +<window title="Mozilla Bug 454235" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=454235" + target="_blank">Mozilla Bug 454235</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + + /** Test for Bug 454235 **/ +SimpleTest.waitForExplicitFinish(); + +SimpleTest.waitForFocus(doTest); + +function doTest() { + var shownBrowser = document.getElementById("shownBrowser"); + var hiddenBrowser = document.getElementById("hiddenBrowser"); + var offScreenBrowser = document.getElementById("offScreenBrowser"); + var offScreenSubBrowser = offScreenBrowser.contentDocument.getElementById("topBrowser"); + var offScreenBurriedBrowser = offScreenBrowser.contentDocument.getElementById("burriedBrowser"); + + hiddenBrowser.contentWindow.focus(); + ok(!hiddenBrowser.contentDocument.hasFocus(),"hidden browser is visible"); + + offScreenBrowser.docShell.isOffScreenBrowser = true; + offScreenBrowser.contentWindow.focus(); + ok(offScreenBrowser.contentDocument.hasFocus(),"offscreen browser is not visible"); + + offScreenSubBrowser.contentWindow.focus(); + todo(offScreenSubBrowser.contentDocument.hasFocus(),"visible browser in offscreen browser is not visible"); + + offScreenBurriedBrowser.contentWindow.focus(); + ok(!offScreenBurriedBrowser.contentDocument.hasFocus(),"hidden browser in offscreen browser is visible"); + + SimpleTest.finish(); +} + + + + ]]></script> + <box flex="1" style="visibility: hidden; border:5px black solid"> + <browser style="border:5px blue solid" id="hiddenBrowser" src="bug454235-subframe.xul"/> + <browser style="border:5px yellow solid" id="offScreenBrowser" src="bug454235-subframe.xul"/> + </box> +</window> diff --git a/docshell/test/chrome/test_bug456980.xul b/docshell/test/chrome/test_bug456980.xul new file mode 100644 index 000000000..9f4f97209 --- /dev/null +++ b/docshell/test/chrome/test_bug456980.xul @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=456980 +--> +<window title="Mozilla Bug 456980" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=456980" + target="_blank">Mozilla Bug 396519</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + SimpleTest.waitForExplicitFinish(); + + addLoadEvent(function() { + window.open("bug113934_window.xul?chrome", "bug456980", + "chrome,width=800,height=800"); + }); + + ]]></script> +</window> diff --git a/docshell/test/chrome/test_bug565388.xul b/docshell/test/chrome/test_bug565388.xul new file mode 100644 index 000000000..47b5c07e4 --- /dev/null +++ b/docshell/test/chrome/test_bug565388.xul @@ -0,0 +1,83 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=565388 +--> +<window title="Mozilla Bug 565388" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 565388 **/ + SimpleTest.waitForExplicitFinish(); + + var Ci = Components.interfaces; + var Cc = Components.classes; + + var progressListener = { + add: function(docShell, callback) { + this.callback = callback; + this.docShell = docShell; + docShell. + QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIWebProgress). + addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW); + }, + + finish: function() { + this.docShell. + QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIWebProgress). + removeProgressListener(this); + this.callback(); + }, + + onStateChange: function (webProgress, req, flags, status) { + if (req.name.startsWith("data:application/vnd.mozilla.xul")) { + if (flags & Ci.nsIWebProgressListener.STATE_STOP) + this.finish(); + } + }, + + QueryInterface: function(iid) { + if (iid.equals(Components.interfaces.nsIWebProgressListener) || + iid.equals(Components.interfaces.nsISupportsWeakReference)) + return this; + throw Components.results.NS_ERROR_NO_INTERFACE; + } + } + + var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]. + createInstance(Ci.nsIPrincipal); + var webNav = Cc["@mozilla.org/appshell/appShellService;1"]. + getService(Ci.nsIAppShellService). + createWindowlessBrowser(true); + var docShell = webNav. + QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIDocShell); + docShell.createAboutBlankContentViewer(systemPrincipal); + var win = docShell.contentViewer.DOMDocument.defaultView; + + progressListener.add(docShell, function(){ + is(win.document.documentURI, "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window/>"); + webNav.close(); + SimpleTest.finish(); + }); + + win.location = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window/>"; + + ]]> + </script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=565388" + target="_blank">Mozilla Bug 565388</a> + </body> +</window> diff --git a/docshell/test/chrome/test_bug582176.xul b/docshell/test/chrome/test_bug582176.xul new file mode 100644 index 000000000..98053b009 --- /dev/null +++ b/docshell/test/chrome/test_bug582176.xul @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=582176.xul +--> +<window title="Mozilla Bug 582176" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=582176"> + Mozilla Bug 582176</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 582176 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug582176_window.xul", "bug582176", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_bug608669.xul b/docshell/test/chrome/test_bug608669.xul new file mode 100644 index 000000000..51d6a3b7d --- /dev/null +++ b/docshell/test/chrome/test_bug608669.xul @@ -0,0 +1,117 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=608669 +--> +<window title="Mozilla Bug 608669" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=608669" + target="_blank">Mozilla Bug 608669</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"><![CDATA[ + +var gOrigMaxTotalViewers = undefined; +function setCachePref(enabled) { + var prefBranch = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + if (enabled) { + is(typeof gOrigMaxTotalViewers, "undefined", "don't double-enable bfcache"); + prefBranch.setBoolPref("browser.sessionhistory.cache_subframes", true); + gOrigMaxTotalViewers = prefBranch.getIntPref("browser.sessionhistory.max_total_viewers"); + prefBranch.setIntPref("browser.sessionhistory.max_total_viewers", 10); + } + else { + is(typeof gOrigMaxTotalViewers, "number", "don't double-disable bfcache"); + prefBranch.setIntPref("browser.sessionhistory.max_total_viewers", gOrigMaxTotalViewers); + gOrigMaxTotalViewers = undefined; + try { + prefBranch.clearUserPref("browser.sessionhistory.cache_subframes"); + } catch (e) { /* Pref didn't exist, meh */ } + } +} + + +/** Test for Bug 608669 **/ +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(nextTest); + +gen = doTest(); + +function nextTest() { + gen.next(); +} + +function doTest() { + var container = document.getElementById('container'); + + setCachePref(true); + + var notificationCount = 0; + var observer = { + observe: function(aSubject, aTopic, aData) { + is(aTopic, "chrome-document-global-created", + "correct topic"); + is(aData, "null", + "correct data"); + notificationCount++; + } + }; + + var os = Components.classes["@mozilla.org/observer-service;1"]. + getService(Components.interfaces.nsIObserverService); + os.addObserver(observer, "chrome-document-global-created", false); + os.addObserver(observer, "content-document-global-created", false); + + is(notificationCount, 0, "initial count"); + + // create a new iframe + var iframe = document.createElement("iframe"); + container.appendChild(iframe); + iframe.contentWindow.x = "y"; + is(notificationCount, 1, "after created iframe"); + + // Try loading in an iframe + iframe.setAttribute("src", "bug608669.xul"); + iframe.onload = nextTest; + yield undefined; + is(notificationCount, 1, "after first load"); + is(iframe.contentWindow.x, "y", "reused window"); + + // Try loading again in an iframe + iframe.setAttribute("src", "bug608669.xul?x"); + iframe.onload = nextTest; + yield undefined; + is(notificationCount, 2, "after second load"); + is("x" in iframe.contentWindow, false, "didn't reuse window"); + + // Open a new window using window.open + popup = window.open("bug608669.xul", "bug 608669", + "chrome,width=600,height=600"); + popup.onload = nextTest; + yield undefined; + is(notificationCount, 3, "after window.open load"); + popup.close(); + + setCachePref(false); + os.removeObserver(observer, "chrome-document-global-created"); + os.removeObserver(observer, "content-document-global-created"); + SimpleTest.finish(); + yield undefined; +} + + + + ]]></script> + <vbox id="container" flex="1"> + <description>Below will an iframe be added</description> + </vbox> +</window> diff --git a/docshell/test/chrome/test_bug662200.xul b/docshell/test/chrome/test_bug662200.xul new file mode 100644 index 000000000..8b864c8af --- /dev/null +++ b/docshell/test/chrome/test_bug662200.xul @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=662200.xul +--> +<window title="Mozilla Bug 662200" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=662200"> + Mozilla Bug 662200</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 662200 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug662200_window.xul", "bug662200", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_bug690056.xul b/docshell/test/chrome/test_bug690056.xul new file mode 100644 index 000000000..caeed09d3 --- /dev/null +++ b/docshell/test/chrome/test_bug690056.xul @@ -0,0 +1,26 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=690056 +--> +<window title="Mozilla Bug 690056" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=690056" + target="_blank">Mozilla Bug 690056</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + /** Test for Bug 690056 **/ +SimpleTest.waitForExplicitFinish(); +window.open("bug690056_window.xul", "bug690056", + "chrome,width=600,height=600"); + ]]> + </script> +</window> diff --git a/docshell/test/chrome/test_bug789773.xul b/docshell/test/chrome/test_bug789773.xul new file mode 100644 index 000000000..b7a2b3d1c --- /dev/null +++ b/docshell/test/chrome/test_bug789773.xul @@ -0,0 +1,71 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=789773 +--> +<window title="Mozilla Bug 789773" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=789773" + target="_blank">Mozilla Bug 789773</a> + </body> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + const Cc = Components.classes; + const Ci = Components.interfaces; + const Cr = Components.results; + const Cu = Components.utils; + + /* Test for Bug 789773. + * + * See comment 50 for the situation we're testing against here. + * + * Note that the failure mode of this test is to hang, and hang the browser on quit. + * This is an unfortunate occurance, but that's why we're testing it. + */ + SimpleTest.waitForExplicitFinish(); + + var calledListenerForBrowserXUL = false; + var testProgressListener = { + START_DOC: Ci.nsIWebProgressListener.STATE_START | Ci.nsIWebProgressListener.STATE_IS_DOCUMENT, + onStateChange: function(wp, req, stateFlags, status) { + if (/browser.xul/.test(req.name)) { + wp.DOMWindow; // Force the lazy creation of a DOM window. + calledListenerForBrowserXUL = true; + } + if (/mozilla.xhtml/.test(req.name) && (stateFlags & Ci.nsIWebProgressListener.STATE_STOP)) + finishTest(); + }, + QueryInterface: function(iid) { + if (iid.equals(Ci.nsISupportsWeakReference) || + iid.equals(Ci.nsIWebProgressListener)) + return this; + throw Cr.NS_ERROR_NO_INTERFACE; + } + } + + // Add our progress listener + var webProgress = Cc['@mozilla.org/docloaderservice;1'].getService(Ci.nsIWebProgress); + webProgress.addProgressListener(testProgressListener, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST); + + // Open the window. + var popup = window.open("about:mozilla", "_blank", "width=640,height=400"); + + // Wait for the window to load. + function finishTest() { + webProgress.removeProgressListener(testProgressListener); + ok(true, "Loaded the popup window without spinning forever in the event loop!"); + ok(calledListenerForBrowserXUL, "Should have called the progress listener for browser.xul"); + popup.close(); + SimpleTest.finish(); + } + + ]]> + </script> +</window> diff --git a/docshell/test/chrome/test_bug846906.xul b/docshell/test/chrome/test_bug846906.xul new file mode 100644 index 000000000..2003384f0 --- /dev/null +++ b/docshell/test/chrome/test_bug846906.xul @@ -0,0 +1,96 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> +<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=846906 +--> +<window title="Mozilla Bug 846906" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + + <!-- test code goes here --> + <script type="application/javascript"> + <![CDATA[ + + /** Test for Bug 846906 **/ + SimpleTest.waitForExplicitFinish(); + + var appShellService = Components.classes["@mozilla.org/appshell/appShellService;1"] + .getService(Components.interfaces.nsIAppShellService); + ok(appShellService, "Should be able to get app shell service"); + + var windowlessBrowser = appShellService.createWindowlessBrowser(); + ok(windowlessBrowser, "Should be able to create windowless browser"); + + ok(windowlessBrowser instanceof Components.interfaces.nsIWindowlessBrowser, + "Windowless browser should implement nsIWindowlessBrowser"); + + var webNavigation = windowlessBrowser.QueryInterface(Components.interfaces.nsIWebNavigation); + ok(webNavigation, "Windowless browser should implement nsIWebNavigation"); + + var interfaceRequestor = windowlessBrowser.QueryInterface(Components.interfaces.nsIInterfaceRequestor); + ok(interfaceRequestor, "Should be able to query interface requestor interface"); + + var docShell = interfaceRequestor.getInterface(Components.interfaces.nsIDocShell); + ok(docShell, "Should be able to get doc shell interface"); + + var document = webNavigation.document; + ok(document, "Should be able to get document"); + + var iframe = document.createElement("iframe"); + ok(iframe, "Should be able to create iframe"); + + iframe.onload = function () { + ok(true, "Should receive initial onload event"); + + iframe.onload = function () { + ok(true, "Should receive onload event"); + + var contentDocument = iframe.contentDocument; + ok(contentDocument, "Should be able to get content document"); + + var div = contentDocument.getElementById("div1"); + ok(div, "Should be able to get element by id"); + + var rect = div.getBoundingClientRect(); + ok(rect, "Should be able to get bounding client rect"); + + // xxx: can we do better than hardcoding these values here? + is(rect.width, 1024); + is(rect.height, 768); + + windowlessBrowser.close(); + + // Once the browser is closed, nsIWebNavigation and + // nsIInterfaceRequestor methods should no longer be accessible. + try { + windowlessBrowser.getInterface(Components.interfaces.nsIDocShell); + ok(false); + } catch (e) { + is(e.result, Components.results.NS_ERROR_NULL_POINTER); + } + + try { + windowlessBrowser.document; + ok(false); + } catch (e) { + is(e.result, Components.results.NS_ERROR_NULL_POINTER); + } + + SimpleTest.finish(); + }; + iframe.setAttribute("src", "http://mochi.test:8888/chrome/docshell/test/chrome/bug846906.html"); + }; + document.documentElement.appendChild(iframe); + + ]]> + </script> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=846906" + target="_blank">Mozilla Bug 846906</a> + </body> +</window> diff --git a/docshell/test/chrome/test_bug89419.xul b/docshell/test/chrome/test_bug89419.xul new file mode 100644 index 000000000..26194c49b --- /dev/null +++ b/docshell/test/chrome/test_bug89419.xul @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=89419.xul +--> +<window title="Mozilla Bug 89419" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=89419"> + Mozilla Bug 89419</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 89419 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug89419_window.xul", "bug89419", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_bug909218.html b/docshell/test/chrome/test_bug909218.html new file mode 100644 index 000000000..e635fdb11 --- /dev/null +++ b/docshell/test/chrome/test_bug909218.html @@ -0,0 +1,127 @@ +<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(test);
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+
+// The default flags we will stick on the docShell - every request made by the
+// docShell should include those flags.
+const TEST_FLAGS = Ci.nsIRequest.LOAD_ANONYMOUS |
+ Ci.nsIRequest.LOAD_BYPASS_CACHE |
+ Ci.nsIRequest.INHIBIT_CACHING |
+ Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY;
+
+var TEST_URL = "http://mochi.test:8888/chrome/docshell/test/chrome/bug909218.html";
+
+// These are the requests we expect to see loading TEST_URL into our iframe.
+
+// The test entry-point. The basic outline is:
+// * Create an iframe and set defaultLoadFlags on its docShell.
+// * Add a web progress listener to observe each request as the iframe is
+// loaded, and check that each request has the flags we specified.
+// * Load our test URL into the iframe and wait for the load to complete.
+function test() {
+ var iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ var docShell = docshellForWindow(iframe.contentWindow);
+ // Add our progress listener - when it notices the top-level document is
+ // complete, the test will end.
+ RequestWatcher.init(docShell, SimpleTest.finish);
+ // Set the flags we care about, then load our test URL.
+ docShell.defaultLoadFlags = TEST_FLAGS;
+ iframe.setAttribute("src", TEST_URL);
+}
+
+// an nsIWebProgressListener that checks all requests made by the docShell
+// have the flags we expect.
+RequestWatcher = {
+ init: function(docShell, callback) {
+ this.callback = callback;
+ this.docShell = docShell;
+ docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
+ Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
+ // These are the requests we expect to see - initialize each to have a
+ // count of zero.
+ this.requestCounts = {};
+ for (var url of [
+ TEST_URL,
+ // content loaded by the above test html.
+ "http://mochi.test:8888/chrome/docshell/test/chrome/bug909218.js",
+ "http://mochi.test:8888/tests/SimpleTest/test.css",
+ "http://mochi.test:8888/tests/docshell/test/chrome/red.png",
+ // the content of an iframe in the test html.
+ "http://mochi.test:8888/chrome/docshell/test/chrome/generic.html"
+ ]) {
+ this.requestCounts[url] = 0;
+ }
+ },
+
+ // Finalize the test after we detect a completed load. We check we saw the
+ // correct requests and make a callback to exit.
+ finalize: function() {
+ ok(Object.keys(this.requestCounts).length, "we expected some requests");
+ for (var url in this.requestCounts) {
+ var count = this.requestCounts[url];
+ // As we are looking at all request states, we expect more than 1 for
+ // each URL - 0 or 1 would imply something went wrong - >1 just means
+ // multiple states for each request were recorded, which we don't care
+ // about (we do care they all have the correct flags though - but we
+ // do that in onStateChange)
+ ok(count > 1, url + " saw " + count + " requests");
+ }
+ this.docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ removeProgressListener(this);
+ this.callback();
+ },
+
+ onStateChange: function (webProgress, req, flags, status) {
+ // We are checking requests - if there isn't one, ignore it.
+ if (!req) {
+ return;
+ }
+ // We will usually see requests for 'about:document-onload-blocker' not
+ // have the flag, so we just ignore them.
+ // We also see, eg, resource://gre-resources/loading-image.png, so
+ // skip resource:// URLs too.
+ if (req.name.startsWith("about:") || req.name.startsWith("resource:")) {
+ return;
+ }
+ is(req.loadFlags & TEST_FLAGS, TEST_FLAGS, "request " + req.name + " has the expected flags");
+ this.requestCounts[req.name] += 1;
+ var stopFlags = Ci.nsIWebProgressListener.STATE_STOP |
+ Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
+ if (req.name == TEST_URL && (flags & stopFlags) == stopFlags) {
+ this.finalize();
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference,
+ ])
+}
+
+function docshellForWindow(win) {
+ return win.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebNavigation).
+ QueryInterface(Ci.nsIDocShell);
+}
+
+</script>
+</head>
+</html>
diff --git a/docshell/test/chrome/test_bug92598.xul b/docshell/test/chrome/test_bug92598.xul new file mode 100644 index 000000000..8e81463be --- /dev/null +++ b/docshell/test/chrome/test_bug92598.xul @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=92598 +--> +<window title="Mozilla Bug 92598" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=92598">Mozilla Bug 92598</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 92598 **/ + +SimpleTest.waitForExplicitFinish(); +window.open("bug92598_window.xul", "bug92598", + "chrome,width=600,height=600"); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_mozFrameType.xul b/docshell/test/chrome/test_mozFrameType.xul new file mode 100644 index 000000000..225cd16cc --- /dev/null +++ b/docshell/test/chrome/test_mozFrameType.xul @@ -0,0 +1,43 @@ +<?xml version="1.0"?> +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=769771 +--> +<window title="Test mozFrameType attribute" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +if (navigator.platform.startsWith("Win")) { + SimpleTest.expectAssertions(0, 1); +} + +/** Test for Bug 769771 **/ +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function () { + window.open("mozFrameType_window.xul", "mozFrameType", + "chrome,width=600,height=600"); +}); + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_principalInherit.xul b/docshell/test/chrome/test_principalInherit.xul new file mode 100644 index 000000000..87056c63d --- /dev/null +++ b/docshell/test/chrome/test_principalInherit.xul @@ -0,0 +1,108 @@ +<?xml version="1.0"?> +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet + href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=719994 +--> +<window title="Test principal inheriting" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +/** Test for Bug 719994 **/ + +SimpleTest.waitForExplicitFinish(); + +var gFrame; + +// This test file is loaded in a type=content docshell whose principal is +// the system principal. + +// Using data: URIs here instead of javascript: URIs, since javascript: URIs +// fail to load when there's no principal to load them against. This only +// matters when these tests fail (produces better error messages). +var tests = [ + function testInheritFromParent(cb) { + gFrame = document.createElement("iframe"); + loadListener(gFrame, function () { + is(window.inheritedFromParent, true, "load in type=content iframe inherited principal of same type parent"); + cb(); + }); + gFrame.setAttribute("type", "content"); + gFrame.setAttribute("src", "data:text/html,<script>parent.inheritedFromParent = true;</script>"); + document.documentElement.appendChild(gFrame); + }, + function testInheritFromCurrent_system(cb) { + loadListener(gFrame, function () { + is(window.inheritedSystem, undefined, "load in type=content iframe shouldn't inherit system principal from current document"); + cb(); + }, true); + gFrame.setAttribute("src", "data:text/html,<script>parent.inheritedSystem = true;</script>"); + }, + function testInheritFromCreated(cb) { + // Open a new chrome window with a type="content" iframe, so that it has no + // same-type parent. + // Load a javascript: URI in it to ensure that GetInheritedPrincipal will + // force creation of a content viewer. + let xulWinURL = 'data:application/vnd.mozilla.xul+xml,<?xml version="1.0"?>' + + '<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>'; + let newWin = window.openDialog(xulWinURL, "chrome_window", "chrome"); + loadListener(newWin, function () { + let frame = newWin.document.createElement("iframe"); + frame.setAttribute("type", "content"); + frame.setAttribute("src", "javascript:'1';"); + loadListener(frame, function () { + is(frame.contentWindow.document.body.textContent, "1", "content viewer was created"); + SimpleTest.executeSoon(function () { + newWin.close(); + cb(); + }) + }); + newWin.document.documentElement.appendChild(frame); + }); + } +]; + +addLoadEvent(function onLoad() { + ok(Components.stack, "this test must be run with the system principal"); + SimpleTest.executeSoon(nextTest); +}); + +function loadListener(target, func) { + target.addEventListener("load", function lis() { + target.removeEventListener("load", lis, true); + func(); + }, true); +} + +function nextTest() { + if (tests.length) { + let test = tests.shift(); + SimpleTest.executeSoon(function () { + info("running " + test.name); + test(nextTest); + }); + } else + SimpleTest.executeSoon(SimpleTest.finish); +} + +]]> +</script> + +</window> diff --git a/docshell/test/chrome/test_private_hidden_window.html b/docshell/test/chrome/test_private_hidden_window.html new file mode 100644 index 000000000..3907e5de8 --- /dev/null +++ b/docshell/test/chrome/test_private_hidden_window.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=829383 +--> +<head> + <title>Test for Bug 829383</title> + <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=829383">Mozilla Bug 829383</a> +<p id="display"></p> +<div id="content" style="display: none"> + <iframe name="target"></iframe> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +const Ci = Components.interfaces; +var mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + +// We need to wait for the hidden window to load, but can't access +// an event target for a regular event listener. +var hidden = mainWindow.Services.appShell.hiddenPrivateDOMWindow; +mainWindow.PrivateBrowsingUtils.whenHiddenPrivateWindowReady(function(hidden) { + var iframe = hidden.document.createElement('iframe'); + iframe.src = 'generic.html'; + hidden.document.body.appendChild(iframe); + + var win = mainWindow.OpenBrowserWindow({private: true}); + win.addEventListener("load", function onLoad() { + win.removeEventListener("load", onLoad, false); + win.close(); + win = null; + }, false); +}); + +function observer(aSubject, aTopic, aData) { + is(aTopic, "last-pb-context-exited", "Unexpected observer topic"); + mainWindow.Services.obs.removeObserver(observer, "last-pb-context-exited"); + SimpleTest.finish(); +} +mainWindow.Services.obs.addObserver(observer, "last-pb-context-exited", false); + +</script> +</pre> +</body> +</html> + diff --git a/docshell/test/chrome/test_viewsource_forbidden_in_iframe.xul b/docshell/test/chrome/test_viewsource_forbidden_in_iframe.xul new file mode 100644 index 000000000..536ca4d03 --- /dev/null +++ b/docshell/test/chrome/test_viewsource_forbidden_in_iframe.xul @@ -0,0 +1,180 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/css" href="chrome://global/skin/"?> +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=624883 +--> +<window title="Mozilla Bug 624883" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + + <!-- test results are displayed in the html:body --> + <body xmlns="http://www.w3.org/1999/xhtml"> + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=624883" + target="_blank">Mozilla Bug 624883</a> + </body> + + <!-- test code goes here --> + <iframe type="content" onload="startTest()" src="file_viewsource_forbidden_in_iframe.html"></iframe> + + <script type="application/javascript"> + <![CDATA[ + + const Ci = Components.interfaces; + const Cu = Components.utils; + + Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + + SimpleTest.waitForExplicitFinish(); + + // We create a promise that will resolve with the error message + // on a network error page load and reject on any other load. + function createNetworkErrorMessagePromise(frame) { + return new Promise(function(resolve, reject) { + + // Error pages do not fire "load" events, so use a progressListener. + var originalDocumentURI = frame.contentDocument.documentURI; + var progressListener = { + onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) { + // Make sure nothing other than an error page is loaded. + if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE)) { + reject("location change was not to an error page"); + } + }, + + onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) { + // Wait until the documentURI changes (from about:blank) this should + // be the error page URI. + var documentURI = frame.contentDocument.documentURI; + if (documentURI == originalDocumentURI) { + return; + } + + aWebProgress.removeProgressListener(progressListener, + Ci.nsIWebProgress.NOTIFY_ALL); + var matchArray = /about:neterror\?.*&d=([^&]*)/.exec(documentURI); + if (!matchArray) { + reject("no network error message found in URI") + return; + } + + var errorMsg = matchArray[1]; + resolve(decodeURIComponent(errorMsg)); + }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, + Ci.nsISupportsWeakReference]) + }; + + frame.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebProgress) + .addProgressListener(progressListener, + Ci.nsIWebProgress.NOTIFY_LOCATION | + Ci.nsIWebProgress.NOTIFY_STATE_REQUEST); + }); + } + + function startTest() { + // Get a reference message that we know will be an unknown protocol message, + // so we can use it for comparisons in the test cases. + var refIframe = window[0].document.getElementById("refIframe"); + var refErrorPromise = createNetworkErrorMessagePromise(refIframe); + + refErrorPromise.then( + function(msg) { + window.refErrorMsg = msg; + var testIframe = window[0].document.getElementById("testIframe"); + + // Run test cases on load of "about:blank", so that the URI always changes + // and we can detect this in our Promise. + testIframe.onload = runNextTestCase; + testIframe.src = "about:blank"; + }, + function(reason) { + ok(false, "Could not get reference error message", reason); + SimpleTest.finish(); + }) + .catch(function(e) { + ok(false, "Unexpected exception thrown getting reference error message", exception); + }); + + refIframe.src = "wibble://example.com"; + } + + function runTestCase(testCase) { + var testIframe = window[0].document.getElementById("testIframe"); + var expectedErrorMsg = window.refErrorMsg.replace("wibble", testCase.expectedProtocolList); + + var testErrorPromise = createNetworkErrorMessagePromise(testIframe); + testErrorPromise.then( + function(actualErrorMsg) { + is(actualErrorMsg, expectedErrorMsg, testCase.desc); + testIframe.src = "about:blank"; + }, + function(reason) { + ok(false, testCase.desc, reason); + testIframe.src = "about:blank"; + }) + .catch(function(e) { + ok(false, testCase.desc + " - unexpected exception thrown", exception); + }); + + testIframe.src = testCase.protocols + "://example.com/!/"; + } + + var testCaseIndex = -1; + testCases = [ + { + desc: "Test 1: view-source should not be allowed in an iframe", + protocols: "view-source:http", + expectedProtocolList: "view-source, http" + }, + { + desc: "Test 2: feed:view-source should not be allowed in an iframe", + protocols: "feed:view-source:http", + expectedProtocolList: "feed, view-source, http" + }, + { + desc: "Test 3: jar:view-source should not be allowed in an iframe", + protocols: "jar:view-source:http", + expectedProtocolList: "jar, view-source, http" + }, + { + desc: "Test 4: pcast:view-source should not be allowed in an iframe", + protocols: "pcast:view-source:http", + expectedProtocolList: "pcast, view-source, http" + }, + { + desc: "Test 5: pcast:feed:view-source should not be allowed in an iframe", + protocols: "pcast:feed:view-source:http", + expectedProtocolList: "pcast, feed, view-source, http" + }, + { + desc: "Test 6: if invalid protocol first should report before view-source", + protocols: "wibble:view-source:http", + // Nothing after the invalid protocol gets set as a proper nested URI, + // so the list stops there. + expectedProtocolList: "wibble" + }, + { + desc: "Test 7: if view-source first should report before invalid protocol", + protocols: "view-source:wibble:http", + expectedProtocolList: "view-source, wibble" + } + ]; + + function runNextTestCase() { + ++testCaseIndex; + if (testCaseIndex == testCases.length) { + SimpleTest.finish(); + return; + } + + runTestCase(testCases[testCaseIndex]); + } + + ]]> + </script> +</window> diff --git a/docshell/test/chrome/window.template.txt b/docshell/test/chrome/window.template.txt new file mode 100644 index 000000000..a7fb0fac7 --- /dev/null +++ b/docshell/test/chrome/window.template.txt @@ -0,0 +1,44 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window id="{BUGNUMBER}Test" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + width="600" + height="600" + onload="setTimeout(nextTest,0);" + title="bug {BUGNUMBER} test"> + + <script type="application/javascript" + src="docshell_helpers.js"> + </script> + + <script type="application/javascript"><![CDATA[ + + // Define the generator-iterator for the tests. + var tests = testIterator(); + + //// + // Execute the next test in the generator function. + // + function nextTest() { + tests.next(); + } + + //// + // Generator function for test steps for bug {BUGNUMBER}: + // Description goes here. + // + function testIterator() + { + // Test steps go here. See bug303267_window.xul for an example. + + // Tell the framework the test is finished. Include the final 'yield' + // statement to prevent a StopIteration exception from being thrown. + finish(); + yield undefined; + } + + ]]></script> + + <browser type="content-primary" flex="1" id="content" src="about:blank"/> +</window> diff --git a/docshell/test/file_anchor_scroll_after_document_open.html b/docshell/test/file_anchor_scroll_after_document_open.html new file mode 100644 index 000000000..7903380ea --- /dev/null +++ b/docshell/test/file_anchor_scroll_after_document_open.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<script> + if (location.hash == "#target") { + parent.postMessage("haveHash", "*"); + } else { + document.addEventListener("DOMContentLoaded", function() { + document.open(); + document.write("<!DOCTYPE html><html style='height: 100%'><body style='height: 100%'><div style='height: 200%'></div><div id='target'></div></body></html>"); + document.close(); + // Notify parent via postMessage, since otherwise exceptions will not get + // caught by its onerror handler. + parent.postMessage("doTest", "*"); + }); + } +</script> diff --git a/docshell/test/file_bug385434_1.html b/docshell/test/file_bug385434_1.html new file mode 100644 index 000000000..5c951f1fa --- /dev/null +++ b/docshell/test/file_bug385434_1.html @@ -0,0 +1,29 @@ +<!-- +Inner frame for test of bug 385434. +https://bugzilla.mozilla.org/show_bug.cgi?id=385434 +--> +<html> +<head> + <script type="application/javascript"> + function hashchange() { + parent.onIframeHashchange(); + } + + function load() { + parent.onIframeLoad(); + } + + function scroll() { + parent.onIframeScroll(); + } + </script> +</head> + +<body onscroll="scroll()" onload="load()" onhashchange="hashchange()"> +<a href="#link1" id="link1">link1</a> +<!-- Our parent loads us in an iframe with height 100px, so this spacer ensures + that switching between #link1 and #link2 causes us to scroll --> +<div style="height:200px;"></div> +<a href="#link2" id="link2">link2</a> +</body> +</html> diff --git a/docshell/test/file_bug385434_2.html b/docshell/test/file_bug385434_2.html new file mode 100644 index 000000000..4aa5ef82b --- /dev/null +++ b/docshell/test/file_bug385434_2.html @@ -0,0 +1,26 @@ +<!-- +Inner frame for test of bug 385434. +https://bugzilla.mozilla.org/show_bug.cgi?id=385434 +--> +<html> +<head> + <script type="application/javascript"> + function hashchange(e) { + // pass the event back to the parent so it can check its properties. + parent.gSampleEvent = e; + + parent.statusMsg("Hashchange in 2."); + parent.onIframeHashchange(); + } + + function load() { + parent.statusMsg("Loading 2."); + parent.onIframeLoad(); + } + </script> +</head> + +<frameset onload="load()" onhashchange="hashchange(event)"> + <frame src="about:blank" /> +</frameset> +</html> diff --git a/docshell/test/file_bug385434_3.html b/docshell/test/file_bug385434_3.html new file mode 100644 index 000000000..984b85f1f --- /dev/null +++ b/docshell/test/file_bug385434_3.html @@ -0,0 +1,22 @@ +<!-- +Inner frame for test of bug 385434. +https://bugzilla.mozilla.org/show_bug.cgi?id=385434 +--> +<html> +<head> + <script type="application/javascript"> + // Notify our parent if we have a hashchange and once we're done loading. + window.addEventListener("hashchange", parent.onIframeHashchange, false); + + window.addEventListener("DOMContentLoaded", function() { + // This also should trigger a hashchange, becuase the readystate is + // "interactive", not "complete" during DOMContentLoaded. + window.location.hash = "2"; + }, false); + + </script> +</head> + +<body> +</body> +</html> diff --git a/docshell/test/file_bug475636.sjs b/docshell/test/file_bug475636.sjs new file mode 100644 index 000000000..38da6a6a5 --- /dev/null +++ b/docshell/test/file_bug475636.sjs @@ -0,0 +1,90 @@ +jsURL = "javascript:" + escape('window.parent.postMessage("JS uri ran", "*");\ +return \'\ +<script>\ +window.parent.postMessage("Able to access private: " +\ + window.parent.private, "*");\ +</script>\''); +dataURL = "data:text/html," + escape('<!DOCTYPE HTML>\ +<script>\ +try {\ + window.parent.postMessage("Able to access private: " +\ + window.parent.private, "*");\ +}\ +catch (e) {\ + window.parent.postMessage("pass", "*");\ +}\ +</script>'); + +tests = [ +// Plain document should work as normal +'<!DOCTYPE HTML>\ +<script>\ +try {\ + window.parent.private;\ + window.parent.postMessage("pass", "*");\ +}\ +catch (e) {\ + window.parent.postMessage("Unble to access private", "*");\ +}\ +</script>', + +// refresh to plain doc +{ refresh: "file_bug475636.sjs?1", + doc: '<!DOCTYPE HTML>' }, + +// meta-refresh to plain doc +'<!DOCTYPE HTML>\ +<head>\ + <meta http-equiv="refresh" content="0; url=file_bug475636.sjs?1">\ +</head>', + +// refresh to data url +{ refresh: dataURL, + doc: '<!DOCTYPE HTML>' }, + +// meta-refresh to data url +'<!DOCTYPE HTML>\ +<head>\ + <meta http-equiv="refresh" content="0; url=' + dataURL + '">\ +</head>', + +// refresh to js url should not be followed +{ refresh: jsURL, + doc: +'<!DOCTYPE HTML>\ +<script>\ +setTimeout(function() {\ + window.parent.postMessage("pass", "*");\ +}, 2000);\ +</script>' }, + +// meta refresh to js url should not be followed +'<!DOCTYPE HTML>\ +<head>\ + <meta http-equiv="refresh" content="0; url=' + jsURL + '">\ +</head>\ +<script>\ +setTimeout(function() {\ + window.parent.postMessage("pass", "*");\ +}, 2000);\ +</script>' +]; + + +function handleRequest(request, response) +{ + dump("@@@@@@@@@hi there: " + request.queryString + "\n"); + test = tests[parseInt(request.queryString, 10) - 1]; + response.setHeader("Content-Type", "text/html"); + + if (!test) { + response.write('<script>parent.postMessage("done", "*");</script>'); + } + else if (typeof test == "string") { + response.write(test); + } + else if (test.refresh) { + response.setHeader("Refresh", "0; url=" + test.refresh); + response.write(test.doc); + } +} diff --git a/docshell/test/file_bug509055.html b/docshell/test/file_bug509055.html new file mode 100644 index 000000000..ac30876bb --- /dev/null +++ b/docshell/test/file_bug509055.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test inner frame for bug 509055</title> +</head> +<body onhashchange="hashchangeCallback()"> + file_bug509055.html +</body> +</html> diff --git a/docshell/test/file_bug511449.html b/docshell/test/file_bug511449.html new file mode 100644 index 000000000..637732dbb --- /dev/null +++ b/docshell/test/file_bug511449.html @@ -0,0 +1,6 @@ +<!DOCTYPE HTML> +<title>Used in test for bug 511449</title> +<input type="text" id="input"> +<script type="text/javascript"> + document.getElementById("input").focus(); +</script> diff --git a/docshell/test/file_bug540462.html b/docshell/test/file_bug540462.html new file mode 100644 index 000000000..52d1580c7 --- /dev/null +++ b/docshell/test/file_bug540462.html @@ -0,0 +1,16 @@ +<html> + <head> + <script> + //<!-- + function test() { + document.open(); + document.write("<html><body onload='opener.documentWriteLoad(); rel();'><a href='foo.html'>foo</a><script>function rel() { setTimeout('location.reload()', 0); }</script></body></html>"); + document.close(); + } + //--> + </script> + </head> + <body onload="setTimeout('test()', 0)"> + Test for bug 540462 + </body> +</html> diff --git a/docshell/test/file_bug580069_1.html b/docshell/test/file_bug580069_1.html new file mode 100644 index 000000000..7ab461033 --- /dev/null +++ b/docshell/test/file_bug580069_1.html @@ -0,0 +1,8 @@ +<html> +<body onload='parent.page1Load();'> +file_bug580069_1.html + +<form id='form' action='file_bug580069_2.sjs' method='POST'></form> + +</body> +</html> diff --git a/docshell/test/file_bug580069_2.sjs b/docshell/test/file_bug580069_2.sjs new file mode 100644 index 000000000..0c76c1f16 --- /dev/null +++ b/docshell/test/file_bug580069_2.sjs @@ -0,0 +1,5 @@ +function handleRequest(request, response) +{ + response.setHeader("Content-Type", "text/html", false); + response.write('<html><body onload=\'parent.page2Load("' + request.method + '")\'>file_bug580069_2.sjs</body></html>'); +} diff --git a/docshell/test/file_bug590573_1.html b/docshell/test/file_bug590573_1.html new file mode 100644 index 000000000..850d418bd --- /dev/null +++ b/docshell/test/file_bug590573_1.html @@ -0,0 +1,8 @@ +<html> +<body onpopstate='opener.page1Popstate();' onload='opener.page1Load();' + onpageshow='opener.page1PageShow();'> + +<div style='height:10000px' id='div1'>This is a very tall div.</div> + +</body> +</html> diff --git a/docshell/test/file_bug590573_2.html b/docshell/test/file_bug590573_2.html new file mode 100644 index 000000000..5f9ca22be --- /dev/null +++ b/docshell/test/file_bug590573_2.html @@ -0,0 +1,8 @@ +<html> +<body onpopstate='opener.page2Popstate();' onload='opener.page2Load();' + onpageshow='opener.page2PageShow();'> + +<div style='height:300%' id='div2'>The second page also has a big div.</div> + +</body> +</html> diff --git a/docshell/test/file_bug634834.html b/docshell/test/file_bug634834.html new file mode 100644 index 000000000..3ff089745 --- /dev/null +++ b/docshell/test/file_bug634834.html @@ -0,0 +1,5 @@ +<html> +<body> +Nothing to see here; just an empty page. +</body> +</html> diff --git a/docshell/test/file_bug640387.html b/docshell/test/file_bug640387.html new file mode 100644 index 000000000..3a939fb41 --- /dev/null +++ b/docshell/test/file_bug640387.html @@ -0,0 +1,26 @@ +<html> +<body onhashchange='hashchange()' onload='load()' onpopstate='popstate()'> + +<script> +function hashchange() { + var f = (opener || parent).childHashchange; + if (f) + f(); +} + +function load() { + var f = (opener || parent).childLoad; + if (f) + f(); +} + +function popstate() { + var f = (opener || parent).childPopstate; + if (f) + f(); +} +</script> + +Not much to see here... +</body> +</html> diff --git a/docshell/test/file_bug653741.html b/docshell/test/file_bug653741.html new file mode 100644 index 000000000..3202b5257 --- /dev/null +++ b/docshell/test/file_bug653741.html @@ -0,0 +1,13 @@ +<html> +<body onload='(parent || opener).childLoad()'> + +<div style='height:500px; background:yellow'> +<a id='#top'>Top of the page</a> +</div> + +<div id='bottom'> +<a id='#bottom'>Bottom of the page</a> +</div> + +</body> +</html> diff --git a/docshell/test/file_bug660404 b/docshell/test/file_bug660404 new file mode 100644 index 000000000..0737a5c4d --- /dev/null +++ b/docshell/test/file_bug660404 @@ -0,0 +1,5 @@ +--testingtesting +Content-Type: text/html + +<script>opener.finishTest();</script> +--testingtesting-- diff --git a/docshell/test/file_bug660404^headers^ b/docshell/test/file_bug660404^headers^ new file mode 100644 index 000000000..5c821f3f4 --- /dev/null +++ b/docshell/test/file_bug660404^headers^ @@ -0,0 +1 @@ +Content-Type: multipart/x-mixed-replace; boundary="testingtesting" diff --git a/docshell/test/file_bug662170.html b/docshell/test/file_bug662170.html new file mode 100644 index 000000000..3202b5257 --- /dev/null +++ b/docshell/test/file_bug662170.html @@ -0,0 +1,13 @@ +<html> +<body onload='(parent || opener).childLoad()'> + +<div style='height:500px; background:yellow'> +<a id='#top'>Top of the page</a> +</div> + +<div id='bottom'> +<a id='#bottom'>Bottom of the page</a> +</div> + +</body> +</html> diff --git a/docshell/test/file_bug668513.html b/docshell/test/file_bug668513.html new file mode 100644 index 000000000..1e43a51e0 --- /dev/null +++ b/docshell/test/file_bug668513.html @@ -0,0 +1,107 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test file for Bug 668513</title> +<script> + var SimpleTest = opener.SimpleTest; + var ok = opener.ok; + var is = opener.is; + + function finish() { + SimpleTest.finish(); + close(); + } + + function onload_test() + { + var win = frames[0]; + ok(win.performance, 'Window.performance should be defined'); + ok(win.performance.navigation, 'Window.performance.navigation should be defined'); + var navigation = win.performance && win.performance.navigation; + if (navigation === undefined) + { + // avoid script errors + finish(); + return; + } + + // do this with a timeout to see the visuals of the navigations. + setTimeout("nav_frame();", 100); + } + + var step = 1; + function nav_frame() + { + var navigation_frame = frames[0]; + var navigation = navigation_frame.performance.navigation; + switch (step) + { + case 1: + { + navigation_frame.location.href = 'bug570341_recordevents.html'; + step++; + break; + } + case 2: + { + is(navigation.type, navigation.TYPE_NAVIGATE, + 'Expected window.performance.navigation.type == TYPE_NAVIGATE'); + navigation_frame.history.back(); + step++; + break; + } + case 3: + { + is(navigation.type, navigation.TYPE_BACK_FORWARD, + 'Expected window.performance.navigation.type == TYPE_BACK_FORWARD'); + step++; + navigation_frame.history.forward(); + break; + } + case 4: + { + is(navigation.type, navigation.TYPE_BACK_FORWARD, + 'Expected window.performance.navigation.type == TYPE_BACK_FORWARD'); + navigation_frame.location.href = 'bug668513_redirect.html'; + step++; + break; + } + case 5: + { + is(navigation.type, navigation.TYPE_NAVIGATE, + 'Expected timing.navigation.type as TYPE_NAVIGATE'); + is(navigation.redirectCount, 1, + 'Expected navigation.redirectCount == 1 on an server redirected navigation'); + + var timing = navigation_frame.performance && navigation_frame.performance.timing; + if (timing === undefined) + { + // avoid script errors + finish(); + break; + } + ok(timing.navigationStart > 0, 'navigationStart should be > 0'); + sequence = ['navigationStart', 'redirectStart', 'redirectEnd', 'fetchStart']; + for (var j = 1; j < sequence.length; ++j) { + var prop = sequence[j]; + var prevProp = sequence[j-1]; + ok(timing[prevProp] <= timing[prop], + ['Expected ', prevProp, ' to happen before ', prop, + ', got ', prevProp, ' = ', timing[prevProp], + ', ', prop, ' = ', timing[prop]].join('')); + } + step++; + finish(); + } + default: + break; + } + } +</script> +</head> +<body> +<div id="frames"> +<iframe name="child0" onload="onload_test();" src="navigation/blank.html"></iframe> +</div> +</body> +</html> diff --git a/docshell/test/file_bug669671.sjs b/docshell/test/file_bug669671.sjs new file mode 100644 index 000000000..b6fd1ec7c --- /dev/null +++ b/docshell/test/file_bug669671.sjs @@ -0,0 +1,14 @@ +function handleRequest(request, response) +{ + var count = parseInt(getState('count')); + if (!count || request.queryString == 'countreset') + count = 0; + + setState('count', count + 1 + ''); + + response.setHeader('Content-Type', 'text/html', false); + response.setHeader('Cache-Control', 'max-age=0'); + response.write('<html><body onload="opener.onChildLoad()" ' + + 'onunload="parseInt(\'0\')">' + + count + '</body></html>'); +} diff --git a/docshell/test/file_bug680257.html b/docshell/test/file_bug680257.html new file mode 100644 index 000000000..ff480e96a --- /dev/null +++ b/docshell/test/file_bug680257.html @@ -0,0 +1,16 @@ +<!DOCTYPE HTML> +<html> +<head> + <style type='text/css'> + a { color: black; } + a:target { color: red; } + </style> +</head> + +<body onload='(opener || parent).popupLoaded()'> + +<a id='a' href='#a'>link</a> +<a id='b' href='#b'>link2</a> + +</body> +</html> diff --git a/docshell/test/file_bug703855.html b/docshell/test/file_bug703855.html new file mode 100644 index 000000000..fe15b6e3d --- /dev/null +++ b/docshell/test/file_bug703855.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<!-- Just need an empty file here, as long as it's served over HTTP --> diff --git a/docshell/test/file_bug728939.html b/docshell/test/file_bug728939.html new file mode 100644 index 000000000..1cd52a44e --- /dev/null +++ b/docshell/test/file_bug728939.html @@ -0,0 +1,3 @@ +<html> +<body onload="opener.popupLoaded()">file_bug728939</body> +</html> diff --git a/docshell/test/file_framedhistoryframes.html b/docshell/test/file_framedhistoryframes.html new file mode 100644 index 000000000..314f9c72d --- /dev/null +++ b/docshell/test/file_framedhistoryframes.html @@ -0,0 +1,16 @@ +<!DOCTYPE HTML> +<html> +<body> +<iframe id="iframe" src="historyframes.html"></iframe> +<script type="application/javascript"> + +var SimpleTest = window.opener.SimpleTest; +var is = window.opener.is; + +function done() { + window.opener.done(); +} + +</script> +</body> +</html> diff --git a/docshell/test/file_pushState_after_document_open.html b/docshell/test/file_pushState_after_document_open.html new file mode 100644 index 000000000..97a6954f2 --- /dev/null +++ b/docshell/test/file_pushState_after_document_open.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<script> + document.addEventListener("DOMContentLoaded", function() { + document.open(); + document.write("<!DOCTYPE html>New Document here"); + document.close(); + // Notify parent via postMessage, since otherwise exceptions will not get + // caught by its onerror handler. + parent.postMessage("doTest", "*"); + }); +</script> diff --git a/docshell/test/historyframes.html b/docshell/test/historyframes.html new file mode 100644 index 000000000..30fca2c0a --- /dev/null +++ b/docshell/test/historyframes.html @@ -0,0 +1,150 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=602256 +--> +<head> + <title>Test for Bug 602256</title> +</head> +<body onload="SimpleTest.executeSoon(run_test)"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602256">Mozilla Bug 602256</a> +<div id="content"> + <iframe id="iframe" src="data:text/html,<p%20id='text'>Start</p>"></iframe> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 602256 **/ + +var testWin = window.opener ? window.opener : window.parent; + +var SimpleTest = testWin.SimpleTest; +function is() { testWin.is.apply(testWin, arguments); } + +var gFrame = null; + +function gState() { + return location.hash.replace(/^#/, ""); +} + +function waitForLoad(aCallback) { + function listener() { + gFrame.removeEventListener("load", listener, false); + SimpleTest.executeSoon(aCallback); + } + + gFrame.addEventListener("load", listener, false); +} + +function loadContent(aURL, aCallback) { + waitForLoad(aCallback); + + gFrame.src = aURL; +} + +function getURL() { + return gFrame.contentDocument.documentURI; +} + +function getContent() { + return gFrame.contentDocument.getElementById("text").textContent; +} + +var START = "data:text/html,<p%20id='text'>Start</p>"; +var URL1 = "data:text/html,<p%20id='text'>Test1</p>"; +var URL2 = "data:text/html,<p%20id='text'>Test2</p>"; + +function run_test() { + window.location.hash = "START"; + + gFrame = document.getElementById("iframe"); + + test_basic_inner_navigation(); +} + +function end_test() { + testWin.done(); +} + +function test_basic_inner_navigation() { + // Navigate the inner frame a few times + loadContent(URL1, function() { + is(getURL(), URL1, "URL should be correct"); + is(getContent(), "Test1", "Page should be correct"); + + loadContent(URL2, function() { + is(getURL(), URL2, "URL should be correct"); + is(getContent(), "Test2", "Page should be correct"); + + // Test that history is working + waitForLoad(function() { + is(getURL(), URL1, "URL should be correct"); + is(getContent(), "Test1", "Page should be correct"); + + waitForLoad(function() { + is(getURL(), URL2, "URL should be correct"); + is(getContent(), "Test2", "Page should be correct"); + + test_state_navigation(); + }); + window.history.forward(); + }); + window.history.back(); + }); + }); +} + +function test_state_navigation() { + window.location.hash = "STATE1"; + + is(getURL(), URL2, "URL should be correct"); + is(getContent(), "Test2", "Page should be correct"); + + window.location.hash = "STATE2"; + + is(getURL(), URL2, "URL should be correct"); + is(getContent(), "Test2", "Page should be correct"); + + window.history.back(); + + is(gState(), "STATE1", "State should be correct after going back"); + is(getURL(), URL2, "URL should be correct"); + is(getContent(), "Test2", "Page should be correct"); + + window.history.forward(); + + is(gState(), "STATE2", "State should be correct after going forward"); + is(getURL(), URL2, "URL should be correct"); + is(getContent(), "Test2", "Page should be correct"); + + window.history.back(); + window.history.back(); + + is(gState(), "START", "State should be correct"); + is(getURL(), URL2, "URL should be correct"); + is(getContent(), "Test2", "Page should be correct"); + + waitForLoad(function() { + is(getURL(), URL1, "URL should be correct"); + is(getContent(), "Test1", "Page should be correct"); + + waitForLoad(function() { + is(gState(), "START", "State should be correct"); + is(getURL(), START, "URL should be correct"); + is(getContent(), "Start", "Page should be correct"); + + end_test(); + }); + + window.history.back(); + + is(gState(), "START", "State should be correct after going back twice"); + }); + + window.history.back(); + is(gState(), "START", "State should be correct"); +} +</script> +</pre> +</body> +</html> diff --git a/docshell/test/iframesandbox/file_marquee_event_handlers.html b/docshell/test/iframesandbox/file_marquee_event_handlers.html new file mode 100644 index 000000000..13ee31ddb --- /dev/null +++ b/docshell/test/iframesandbox/file_marquee_event_handlers.html @@ -0,0 +1,17 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Test marquee attribute event handlers in iframe sandbox</title> +</head> +<body> + <!-- Note that the width here is slightly longer than the contents, to make + sure we bounce and finish very quickly. --> + <marquee loop="2" width="145" behavior="alternate" truespeed scrolldelay="1" + onstart="parent.postMessage(window.name + ' marquee onstart', '*');" + onbounce="parent.postMessage(window.name + ' marquee onbounce', '*');" + onfinish="parent.postMessage(window.name + ' marquee onfinish', '*');"> + Will bounce and finish + </marquee> +</body> +</html> diff --git a/docshell/test/iframesandbox/file_other_auxiliary_navigation_by_location.html b/docshell/test/iframesandbox/file_other_auxiliary_navigation_by_location.html new file mode 100644 index 000000000..ad24c0f24 --- /dev/null +++ b/docshell/test/iframesandbox/file_other_auxiliary_navigation_by_location.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Test window for other auxiliary navigation by location tests</title> +<script> + function onNav() { + opener.postMessage(window.name, "*"); + } + + window.onload = onNav; + window.onhashchange = onNav; +</script> +</head> +</html> diff --git a/docshell/test/iframesandbox/file_our_auxiliary_navigation_by_location.html b/docshell/test/iframesandbox/file_our_auxiliary_navigation_by_location.html new file mode 100644 index 000000000..978980df2 --- /dev/null +++ b/docshell/test/iframesandbox/file_our_auxiliary_navigation_by_location.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Test window for our auxiliary navigation by location tests</title> +<script> + function onNav() { + opener.parent.postMessage(window.name, "*"); + } + + window.onload = onNav; + window.onhashchange = onNav; +</script> +</head> +</html> diff --git a/docshell/test/iframesandbox/file_parent_navigation_by_location.html b/docshell/test/iframesandbox/file_parent_navigation_by_location.html new file mode 100644 index 000000000..9a2e95fad --- /dev/null +++ b/docshell/test/iframesandbox/file_parent_navigation_by_location.html @@ -0,0 +1,18 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Test window for parent navigation by location tests</title> +<script> + function onNav() { + parent.postMessage(window.name, "*"); + } + + window.onload = onNav; + window.onhashchange = onNav; +</script> +</head> +<body> + <iframe name="childIframe" sandbox="allow-scripts allow-same-origin allow-top-navigation"></iframe> +</body> +</html> diff --git a/docshell/test/iframesandbox/file_sibling_navigation_by_location.html b/docshell/test/iframesandbox/file_sibling_navigation_by_location.html new file mode 100644 index 000000000..51a52bb8e --- /dev/null +++ b/docshell/test/iframesandbox/file_sibling_navigation_by_location.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Test window for sibling navigation by location tests</title> +<script> + function onNav() { + parent.postMessage(window.name, "*"); + } + + window.onload = onNav; + window.onhashchange = onNav; +</script> +</head> +</html> diff --git a/docshell/test/iframesandbox/file_top_navigation_by_location.html b/docshell/test/iframesandbox/file_top_navigation_by_location.html new file mode 100644 index 000000000..194430f38 --- /dev/null +++ b/docshell/test/iframesandbox/file_top_navigation_by_location.html @@ -0,0 +1,20 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Test window for top navigation by location tests</title> +<script> + function onNav() { + opener.postMessage(window.name, "*"); + } + + window.onload = onNav; + window.onhashchange = onNav; +</script> +</head> +<body> + <iframe name="if1" sandbox="allow-scripts allow-same-origin"></iframe> + <iframe name="if2" sandbox="allow-scripts allow-same-origin allow-top-navigation"></iframe> + <iframe name="if3" sandbox="allow-scripts allow-top-navigation"></iframe> +</body> +</html> diff --git a/docshell/test/iframesandbox/file_top_navigation_by_location_exotic.html b/docshell/test/iframesandbox/file_top_navigation_by_location_exotic.html new file mode 100644 index 000000000..1c7c6ce29 --- /dev/null +++ b/docshell/test/iframesandbox/file_top_navigation_by_location_exotic.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Test window for top navigation by location tests</title> +<script> + function onBlock() { + opener.postMessage({ name: window.name, blocked: true }, '*'); + } + + function onNav() { + opener.postMessage({ name: window.name, blocked: false }, '*'); + } + + function setOwnHref() { + location.href = location.href; + } + + window.onload = onNav; +</script> +</head> +<body> + <iframe name="if1" sandbox="allow-scripts allow-same-origin"></iframe> + <iframe name="if2" sandbox="allow-scripts allow-same-origin allow-top-navigation"></iframe> +</body> +</html> diff --git a/docshell/test/iframesandbox/mochitest.ini b/docshell/test/iframesandbox/mochitest.ini new file mode 100644 index 000000000..5f6b570e9 --- /dev/null +++ b/docshell/test/iframesandbox/mochitest.ini @@ -0,0 +1,24 @@ +[DEFAULT] +support-files = + file_marquee_event_handlers.html + file_other_auxiliary_navigation_by_location.html + file_our_auxiliary_navigation_by_location.html + file_parent_navigation_by_location.html + file_sibling_navigation_by_location.html + file_top_navigation_by_location.html + file_top_navigation_by_location_exotic.html + +[test_child_navigation_by_location.html] +[test_marquee_event_handlers.html] +[test_other_auxiliary_navigation_by_location.html] +tags = openwindow +[test_our_auxiliary_navigation_by_location.html] +tags = openwindow +[test_parent_navigation_by_location.html] +tags = openwindow +[test_sibling_navigation_by_location.html] +tags = openwindow +[test_top_navigation_by_location_exotic.html] +skip-if = android_version == '18' #bug 948948, NS_ERROR_FAILURE from nsWindowWatcher::GetPrompt +[test_top_navigation_by_location.html] +skip-if = android_version == '18' #bug 948948, NS_ERROR_FAILURE from nsWindowWatcher::GetPrompt diff --git a/docshell/test/iframesandbox/test_child_navigation_by_location.html b/docshell/test/iframesandbox/test_child_navigation_by_location.html new file mode 100644 index 000000000..06605e3cd --- /dev/null +++ b/docshell/test/iframesandbox/test_child_navigation_by_location.html @@ -0,0 +1,92 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=785310 +html5 sandboxed iframe should not be able to perform top navigation with scripts allowed +--> +<head> +<meta charset="utf-8"> +<title>Test for Bug 785310 - iframe sandbox child navigation by location tests</title> +<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + +<script> + SimpleTest.waitForExplicitFinish(); + + var testHtml = "<script>function onNav() { parent.parent.postMessage('childIframe', '*'); } window.onload = onNav; window.onhashchange = onNav;<\/script>"; + var testDataUri = "data:text/html," + testHtml; + + function runScriptNavigationTest(testCase) { + window.onmessage = function(event) { + if (event.data != 'childIframe') { + ok(false, "event.data: got '" + event.data + "', expected 'childIframe'"); + } + ok(!testCase.shouldBeBlocked, testCase.desc, "child navigation was NOT blocked"); + runNextTest(); + }; + try { + window["parentIframe"].eval(testCase.script); + } catch(e) { + ok(testCase.shouldBeBlocked, testCase.desc, e.message); + runNextTest(); + } + } + + var testCaseIndex = -1; + testCases = [ + { + desc: "Test 1: cross origin child location.replace should NOT be blocked", + script: "window['crossOriginChildIframe'].location.replace(\"" + testDataUri + "\")", + shouldBeBlocked: false + }, + { + desc: "Test 2: cross origin child location.assign should be blocked", + script: "window['crossOriginChildIframe'].location.assign(\"" + testDataUri + "\")", + shouldBeBlocked: true + }, + { + desc: "Test 3: same origin child location.assign should NOT be blocked", + script: "window['sameOriginChildIframe'].location.assign(\"" + testDataUri + "\")", + shouldBeBlocked: false + }, + { + desc: "Test 4: cross origin child location.href should NOT be blocked", + script: "window['crossOriginChildIframe'].location.href = \"" + testDataUri + "\"", + shouldBeBlocked: false + }, + { + desc: "Test 5: cross origin child location.hash should be blocked", + script: "window['crossOriginChildIframe'].location.hash = 'wibble'", + shouldBeBlocked: true + }, + { + desc: "Test 6: same origin child location.hash should NOT be blocked", + script: "window['sameOriginChildIframe'].location.hash = 'wibble'", + shouldBeBlocked: false + } + ]; + + function runNextTest() { + ++testCaseIndex; + if (testCaseIndex == testCases.length) { + SimpleTest.finish(); + return; + } + + runScriptNavigationTest(testCases[testCaseIndex]); + } + + addLoadEvent(runNextTest); +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a> +<p id="display"></p> +<div id="content"> +Tests for Bug 785310 +</div> + +<iframe name="parentIframe" sandbox="allow-scripts allow-same-origin" src="data:text/html,<iframe name='sameOriginChildIframe'></iframe><iframe name='crossOriginChildIframe' sandbox='allow-scripts'></iframe>"</iframe> + +</body> +</html> diff --git a/docshell/test/iframesandbox/test_marquee_event_handlers.html b/docshell/test/iframesandbox/test_marquee_event_handlers.html new file mode 100644 index 000000000..f243e4834 --- /dev/null +++ b/docshell/test/iframesandbox/test_marquee_event_handlers.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1277475 +html5 sandboxed iframe should not run marquee attribute event handlers without allow-scripts +--> +<head> +<meta charset="utf-8"> +<title>Test for Bug 1277475 - html5 sandboxed iframe should not run marquee attribute event handlers without allow-scripts</title> +<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=1277475">Mozilla Bug 1277475</a> +<p id="display"></p> +<div id="content">Tests for Bug 1277475</div> + +<iframe id="if1" name="if1" src="file_marquee_event_handlers.html" + sandbox="allow-same-origin allow-forms allow-top-navigation allow-pointer-lock allow-orientation-lock allow-popups allow-modals allow-popups-to-escape-sandbox"> +</iframe> + +<iframe id="if2" name="if2" src="file_marquee_event_handlers.html" + sandbox="allow-scripts"></iframe> + +<script> + SimpleTest.waitForExplicitFinish(); + + var expectedMessages = new Set(); + var numberOfMessagesExpected = 4; + var unexpectedMessages = new Set(); + + window.onmessage = function (event) { + info(event.data + " message received"); + if (event.data.startsWith("if2") || event.data == "if1 chaser") { + expectedMessages.add(event.data); + if (expectedMessages.size == numberOfMessagesExpected) { + checkRecievedMessages(); + } + } else { + unexpectedMessages.add(event.data); + } + } + + function checkRecievedMessages() { + // Check the expected messages explicitly as a cross-check. + ok(expectedMessages.has("if1 chaser"), + "if1 chaser message should have been received"); + ok(expectedMessages.has("if2 marquee onstart"), + "if2 marquee onstart should have run in iframe sandbox with allow-scripts"); + ok(expectedMessages.has("if2 marquee onbounce"), + "if2 marquee onbounce should have run in iframe sandbox with allow-scripts"); + ok(expectedMessages.has("if2 marquee onfinish"), + "if2 marquee onfinish should have run in iframe sandbox with allow-scripts"); + + unexpectedMessages.forEach( + (v) => { + ok(false, v + " should NOT have run in iframe sandbox without allow-scripts") + } + ); + + SimpleTest.finish(); + } + + // If things are working properly the attribute event handlers won't run on + // the marquee in if1, so add our own capturing listeners on its window, so we + // know when they have fired. (These will run as we are not sandboxed.) + var if1FiredEvents = new Set(); + var if1NumberOfEventsExpected = 3; + var if1Win = document.getElementById("if1").contentWindow; + if1Win.addEventListener("start", () => { checkMarqueeEvent("start"); }, true); + if1Win.addEventListener("bounce", () => { checkMarqueeEvent("bounce"); }, true); + if1Win.addEventListener("finish", () => { checkMarqueeEvent("finish"); }, true); + + function checkMarqueeEvent(eventType) { + info("if1 event " + eventType + " fired"); + if1FiredEvents.add(eventType); + if (if1FiredEvents.size == if1NumberOfEventsExpected) { + // Only send the chasing message after a tick of the event loop to allow + // event handlers on the marquee to process. + SimpleTest.executeSoon(sendChasingMessage); + } + } + + function sendChasingMessage() { + // Add our own message listener to if1's window and echo back a chasing + // message to make sure that any messages from incorrectly run marquee + // attribute event handlers should have arrived before it. + if1Win.addEventListener("message", + (e) => { if1Win.parent.postMessage(e.data, "*"); }); + if1Win.postMessage("if1 chaser", "*"); + info("if1 chaser message sent"); + } +</script> +</body> +</html> diff --git a/docshell/test/iframesandbox/test_other_auxiliary_navigation_by_location.html b/docshell/test/iframesandbox/test_other_auxiliary_navigation_by_location.html new file mode 100644 index 000000000..05c86f0cc --- /dev/null +++ b/docshell/test/iframesandbox/test_other_auxiliary_navigation_by_location.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=785310 +html5 sandboxed iframe should not be able to perform top navigation with scripts allowed +--> +<head> +<meta charset="utf-8"> +<title>Test for Bug 785310 - iframe sandbox other auxiliary navigation by location tests</title> +<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + +<script> + SimpleTest.waitForExplicitFinish(); + + function runScriptNavigationTest(testCase) { + window.onmessage = function(event) { + if (event.data != "otherWindow") { + ok(false, "event.data: got '" + event.data + "', expected 'otherWindow'"); + } + ok(false, testCase.desc, "auxiliary navigation was NOT blocked"); + runNextTest(); + }; + try { + window["testIframe"].eval(testCase.script); + } catch(e) { + ok(true, testCase.desc, e.message); + runNextTest(); + } + } + + var testCaseIndex = -1; + testCases = [ + { + desc: "Test 1: location.replace on auxiliary NOT opened by us should be blocked", + script: "parent.openedWindow.location.replace('file_other_auxiliary_navigation_by_location.html')" + }, + { + desc: "Test 2: location.assign on auxiliary NOT opened by us should be blocked", + script: "parent.openedWindow.location.assign('file_other_auxiliary_navigation_by_location.html')" + }, + { + desc: "Test 3: location.href on auxiliary NOT opened by us should be blocked", + script: "parent.openedWindow.location.href = 'file_other_auxiliary_navigation_by_location.html'" + }, + { + desc: "Test 4: location.hash on auxiliary NOT opened by us should be blocked", + script: "parent.openedWindow.location.hash = 'wibble'" + } + ]; + + function runNextTest() { + ++testCaseIndex; + if (testCaseIndex == testCases.length) { + openedWindow.close(); + SimpleTest.finish(); + return; + } + + runScriptNavigationTest(testCases[testCaseIndex]); + } + + window.onmessage = runNextTest; + + window.onload = function() { + window.openedWindow = window.open("file_other_auxiliary_navigation_by_location.html", "otherWindow"); + } +</script> + +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a> +<p id="display"></p> +<div id="content"> +Tests for Bug 785310 +</div> + +<iframe name="testIframe" sandbox="allow-scripts allow-same-origin allow-top-navigation allow-popups"></iframe> +</body> +</html> diff --git a/docshell/test/iframesandbox/test_our_auxiliary_navigation_by_location.html b/docshell/test/iframesandbox/test_our_auxiliary_navigation_by_location.html new file mode 100644 index 000000000..01b371545 --- /dev/null +++ b/docshell/test/iframesandbox/test_our_auxiliary_navigation_by_location.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=785310 +html5 sandboxed iframe should not be able to perform top navigation with scripts allowed +--> +<head> +<meta charset="utf-8"> +<title>Test for Bug 785310 - iframe sandbox our auxiliary navigation by location tests</title> +<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + +<script> + SimpleTest.waitForExplicitFinish(); + + function runScriptNavigationTest(testCase) { + window.onmessage = function(event) { + if (event.data != "ourWindow") { + ok(false, "event.data: got '" + event.data + "', expected 'ourWindow'"); + } + ok(!testCase.shouldBeBlocked, testCase.desc, "auxiliary navigation was NOT blocked"); + runNextTest(); + }; + try { + SpecialPowers.wrap(window["testIframe"]).eval(testCase.script); + } catch(e) { + ok(testCase.shouldBeBlocked, testCase.desc, SpecialPowers.wrap(e).message); + runNextTest(); + } + } + + var testCaseIndex = -1; + testCases = [ + { + desc: "Test 1: location.replace on auxiliary opened by us should NOT be blocked", + script: "openedWindow.location.replace('file_our_auxiliary_navigation_by_location.html')", + shouldBeBlocked: false + }, + { + desc: "Test 2: location.assign on auxiliary opened by us should be blocked without allow-same-origin", + script: "openedWindow.location.assign('file_our_auxiliary_navigation_by_location.html')", + shouldBeBlocked: true + }, + { + desc: "Test 3: location.href on auxiliary opened by us should NOT be blocked", + script: "openedWindow.location.href = 'file_our_auxiliary_navigation_by_location.html'", + shouldBeBlocked: false + }, + { + desc: "Test 4: location.hash on auxiliary opened by us should be blocked without allow-same-origin", + script: "openedWindow.location.hash = 'wibble'", + shouldBeBlocked: true + } + ]; + + function runNextTest() { + ++testCaseIndex; + if (testCaseIndex == testCases.length) { + SpecialPowers.wrap(window["testIframe"]).eval("openedWindow.close()"); + SimpleTest.finish(); + return; + } + + runScriptNavigationTest(testCases[testCaseIndex]); + } + + window.onmessage = runNextTest; + + window.onload = function() { + SpecialPowers.wrap(window["testIframe"]).eval("var openedWindow = window.open('file_our_auxiliary_navigation_by_location.html', 'ourWindow')"); + } +</script> + +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a> +<p id="display"></p> +<div id="content"> +Tests for Bug 785310 +</div> + +<iframe name="testIframe" sandbox="allow-scripts allow-popups"></iframe> +</body> +</html> diff --git a/docshell/test/iframesandbox/test_parent_navigation_by_location.html b/docshell/test/iframesandbox/test_parent_navigation_by_location.html new file mode 100644 index 000000000..06335cdec --- /dev/null +++ b/docshell/test/iframesandbox/test_parent_navigation_by_location.html @@ -0,0 +1,75 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=785310 +html5 sandboxed iframe should not be able to perform top navigation with scripts allowed +--> +<head> +<meta charset="utf-8"> +<title>Test for Bug 785310 - iframe sandbox parent navigation by location tests</title> +<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + +<script> + SimpleTest.waitForExplicitFinish(); + + function runScriptNavigationTest(testCase) { + window.onmessage = function(event) { + if (event.data != "parentIframe") { + ok(false, "event.data: got '" + event.data + "', expected 'parentIframe'"); + } + ok(false, testCase.desc, "parent navigation was NOT blocked"); + runNextTest(); + }; + try { + window["parentIframe"]["childIframe"].eval(testCase.script); + } catch(e) { + ok(true, testCase.desc, e.message); + runNextTest(); + } + } + + var testCaseIndex = -1; + testCases = [ + { + desc: "Test 1: parent.location.replace should be blocked even when sandboxed with allow-same-origin allow-top-navigation", + script: "parent.location.replace('file_parent_navigation_by_location.html')" + }, + { + desc: "Test 2: parent.location.assign should be blocked even when sandboxed with allow-same-origin allow-top-navigation", + script: "parent.location.assign('file_parent_navigation_by_location.html')" + }, + { + desc: "Test 3: parent.location.href should be blocked even when sandboxed with allow-same-origin allow-top-navigation", + script: "parent.location.href = 'file_parent_navigation_by_location.html'" + }, + { + desc: "Test 4: parent.location.hash should be blocked even when sandboxed with allow-same-origin allow-top-navigation", + script: "parent.location.hash = 'wibble'" + } + ]; + + function runNextTest() { + ++testCaseIndex; + if (testCaseIndex == testCases.length) { + SimpleTest.finish(); + return; + } + + runScriptNavigationTest(testCases[testCaseIndex]); + } + + window.onmessage = runNextTest; +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a> +<p id="display"></p> +<div id="content"> +Tests for Bug 785310 +</div> + +<iframe name="parentIframe" src="file_parent_navigation_by_location.html"></iframe> + +</body> +</html> diff --git a/docshell/test/iframesandbox/test_sibling_navigation_by_location.html b/docshell/test/iframesandbox/test_sibling_navigation_by_location.html new file mode 100644 index 000000000..a93c1ae0d --- /dev/null +++ b/docshell/test/iframesandbox/test_sibling_navigation_by_location.html @@ -0,0 +1,78 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=785310 +html5 sandboxed iframe should not be able to perform top navigation with scripts allowed +--> +<head> +<meta charset="utf-8"> +<title>Test for Bug 785310 - iframe sandbox sibling navigation by location tests</title> +<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + +<script> + SimpleTest.waitForExplicitFinish(); + + function runScriptNavigationTest(testCase) { + window.onmessage = function(event) { + if (event.data != "siblingIframe") { + ok(false, "event.data: got '" + event.data + "', expected 'siblingIframe'"); + } + + ok(false, testCase.desc, "sibling navigation was NOT blocked"); + runNextTest(); + }; + + try { + window["testIframe"].eval(testCase.script); + } catch(e) { + ok(true, testCase.desc, e.message); + runNextTest(); + } + } + + var testCaseIndex = -1; + testCases = [ + { + desc: "Test 1: sibling location.replace should be blocked even when sandboxed with allow-same-origin allow-top-navigation", + script: "parent['siblingIframe'].location.replace('file_sibling_navigation_by_location.html')" + }, + { + desc: "Test 2: sibling location.assign should be blocked even when sandboxed with allow-same-origin allow-top-navigation", + script: "parent['siblingIframe'].location.assign('file_sibling_navigation_by_location.html')" + }, + { + desc: "Test 3: sibling location.href should be blocked even when sandboxed with allow-same-origin allow-top-navigation", + script: "parent['siblingIframe'].location.href = 'file_sibling_navigation_by_location.html'" + }, + { + desc: "Test 4: sibling location.hash should be blocked even when sandboxed with allow-same-origin allow-top-navigation", + script: "parent['siblingIframe'].location.hash = 'wibble'" + } + ]; + + function runNextTest() { + ++testCaseIndex; + if (testCaseIndex == testCases.length) { + SimpleTest.finish(); + return; + } + + runScriptNavigationTest(testCases[testCaseIndex]); + } + + window.onmessage = runNextTest; +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a> +<p id="display"></p> +<div id="content"> +Tests for Bug 785310 +</div> + +<iframe name="testIframe" sandbox="allow-scripts allow-same-origin allow-top-navigation"></iframe> +<iframe name="siblingIframe" src="file_sibling_navigation_by_location.html"></iframe> + +</body> +</html> diff --git a/docshell/test/iframesandbox/test_top_navigation_by_location.html b/docshell/test/iframesandbox/test_top_navigation_by_location.html new file mode 100644 index 000000000..3191539a2 --- /dev/null +++ b/docshell/test/iframesandbox/test_top_navigation_by_location.html @@ -0,0 +1,167 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=785310 +html5 sandboxed iframe should not be able to perform top navigation with scripts allowed +--> +<head> +<meta charset="utf-8"> +<title>Test for Bug 785310 - iframe sandbox top navigation by location tests</title> +<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +<script> + SimpleTest.waitForExplicitFinish(); + + var testWin; + + function runScriptNavigationTest(testCase) { + window.onmessage = function(event) { + if (event.data != "newTop") { + ok(false, "event.data: got '" + event.data + "', expected 'newTop'"); + } + ok(!testCase.shouldBeBlocked, testCase.desc, "top navigation was NOT blocked"); + runNextTest(); + }; + try { + SpecialPowers.wrap(testWin[testCase.iframeName]).eval(testCase.script); + } catch(e) { + ok(testCase.shouldBeBlocked, testCase.desc, SpecialPowers.wrap(e).message); + runNextTest(); + } + } + + var testCaseIndex = -1; + testCases = [ + { + desc: "Test 1: top.location.replace should be blocked when sandboxed without allow-top-navigation", + script: "top.location.replace('file_top_navigation_by_location.html')", + iframeName: "if1", + shouldBeBlocked: true + }, + { + desc: "Test 2: top.location.assign should be blocked when sandboxed without allow-top-navigation", + script: "top.location.assign('file_top_navigation_by_location.html')", + iframeName: "if1", + shouldBeBlocked: true + }, + { + desc: "Test 3: top.location.href should be blocked when sandboxed without allow-top-navigation", + script: "top.location.href = 'file_top_navigation_by_location.html'", + iframeName: "if1", + shouldBeBlocked: true + }, + { + desc: "Test 4: top.location.pathname should be blocked when sandboxed without allow-top-navigation", + script: "top.location.pathname = top.location.pathname", + iframeName: "if1", + shouldBeBlocked: true + }, + { + desc: "Test 5: top.location should be blocked when sandboxed without allow-top-navigation", + script: "top.location = 'file_top_navigation_by_location.html'", + iframeName: "if1", + shouldBeBlocked: true + }, + { + desc: "Test 6: top.location.hash should be blocked when sandboxed without allow-top-navigation", + script: "top.location.hash = 'wibble'", + iframeName: "if1", + shouldBeBlocked: true + }, + { + desc: "Test 7: top.location.replace should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation", + script: "top.location.replace('file_top_navigation_by_location.html')", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 8: top.location.assign should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation", + script: "top.location.assign('file_top_navigation_by_location.html')", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 9: top.location.href should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation", + script: "top.location.href = 'file_top_navigation_by_location.html'", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 10: top.location.pathname should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation", + script: "top.location.pathname = top.location.pathname", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 11: top.location should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation", + script: "top.location = 'file_top_navigation_by_location.html'", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 12: top.location.hash should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation", + script: "top.location.hash = 'wibble'", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 13: top.location.replace should NOT be blocked when sandboxed with allow-top-navigation, but without allow-same-origin", + script: "top.location.replace('file_top_navigation_by_location.html')", + iframeName: "if3", + shouldBeBlocked: false + }, + { + desc: "Test 14: top.location.assign should be blocked when sandboxed with allow-top-navigation, but without allow-same-origin", + script: "top.location.assign('file_top_navigation_by_location.html')", + iframeName: "if3", + shouldBeBlocked: true + }, + { + desc: "Test 15: top.location.href should NOT be blocked when sandboxed with allow-top-navigation, but without allow-same-origin", + script: "top.location.href = 'file_top_navigation_by_location.html'", + iframeName: "if3", + shouldBeBlocked: false + }, + { + desc: "Test 16: top.location.pathname should be blocked when sandboxed with allow-top-navigation, but without allow-same-origin", + script: "top.location.pathname = top.location.pathname", + iframeName: "if3", + shouldBeBlocked: true + }, + { + desc: "Test 17: top.location should NOT be blocked when sandboxed with allow-top-navigation, but without allow-same-origin", + script: "top.location = 'file_top_navigation_by_location.html'", + iframeName: "if3", + shouldBeBlocked: false + }, + { + desc: "Test 18: top.location.hash should be blocked when sandboxed with allow-top-navigation, but without allow-same-origin", + script: "top.location.hash = 'wibble'", + iframeName: "if3", + shouldBeBlocked: true + } + ]; + + function runNextTest() { + ++testCaseIndex; + if (testCaseIndex == testCases.length) { + testWin.close(); + SimpleTest.finish(); + return; + } + + runScriptNavigationTest(testCases[testCaseIndex]); + } + + window.onmessage = runNextTest; + testWin = window.open("file_top_navigation_by_location.html", "newTop"); +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a> +<p id="display"></p> +<div id="content"> +Tests for Bug 785310 +</div> +</body> +</html> diff --git a/docshell/test/iframesandbox/test_top_navigation_by_location_exotic.html b/docshell/test/iframesandbox/test_top_navigation_by_location_exotic.html new file mode 100644 index 000000000..fcc1f8659 --- /dev/null +++ b/docshell/test/iframesandbox/test_top_navigation_by_location_exotic.html @@ -0,0 +1,204 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=785310 +html5 sandboxed iframe should not be able to perform top navigation with scripts allowed +--> +<head> +<meta charset="utf-8"> +<title>Test for Bug 785310 - iframe sandbox top navigation by location via exotic means tests</title> +<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +<script> + SimpleTest.waitForExplicitFinish(); + + var testWin; + + function runScriptNavigationTest(testCase) { + window.onmessage = function(event) { + if (event.data.name != 'newWindow') { + ok(false, "event.data.name: got '" + event.data.name + "', expected 'newWindow'"); + } + var diag = "top navigation was " + (event.data.blocked ? "" : "NOT ") + "blocked"; + ok((testCase.shouldBeBlocked == event.data.blocked), testCase.desc, diag); + runNextTest(); + }; + try { + testWin[testCase.iframeName].eval(testCase.script); + } catch(e) { + ok(testCase.shouldBeBlocked, testCase.desc, e.message); + runNextTest(); + } + } + + var testCaseIndex = -1; + testCases = [ + { + desc: "Test 1: location.replace.call(top.location, ...) should be blocked when sandboxed without allow-top-navigation", + script: "location.replace.call(top.location, 'file_top_navigation_by_location_exotic.html')", + iframeName: "if1", + shouldBeBlocked: true + }, + { + desc: "Test 2: location.replace.bind(top.location, ...) should be blocked when sandboxed without allow-top-navigation", + script: "location.replace.bind(top.location, 'file_top_navigation_by_location_exotic.html')()", + iframeName: "if1", + shouldBeBlocked: true + }, + { + desc: "Test 3: Function.bind.call(location.replace, top.location, ...) should be blocked when sandboxed without allow-top-navigation", + script: "Function.bind.call(location.replace, top.location, 'file_top_navigation_by_location_exotic.html')()", + iframeName: "if1", + shouldBeBlocked: true + }, + { + desc: "Test 4: location.replace.call(top.location, ...) should NOT be blocked when sandboxed with allow-top-navigation", + script: "location.replace.call(top.location, 'file_top_navigation_by_location_exotic.html')", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 5: location.replace.bind(top.location, ...) should NOT be blocked when sandboxed with allow-top-navigation", + script: "location.replace.bind(top.location, 'file_top_navigation_by_location_exotic.html')()", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 6: Function.bind.call(location.replace, top.location, ...) should NOT be blocked when sandboxed with allow-top-navigation", + script: "Function.bind.call(location.replace, top.location, 'file_top_navigation_by_location_exotic.html')()", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 7: top.location.href, via setTimeout, should be blocked when sandboxed without allow-top-navigation", + script: "setTimeout(function() { try { top.location.href = 'file_top_navigation_by_location_exotic.html' } catch (e) { top.onBlock() } }, 0)", + iframeName: "if1", + shouldBeBlocked: true + }, + { + desc: "Test 8: top.location.href, via setTimeout, should NOT be blocked when sandboxed with allow-top-navigation", + script: "setTimeout(function() { try { top.location.href = 'file_top_navigation_by_location_exotic.html' } catch(e) { top.onBlock() } }, 0)", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 9: top.location.href, via eval, should be blocked when sandboxed without allow-top-navigation", + script: "eval('top.location.href = \"file_top_navigation_by_location_exotic.html\"')", + iframeName: "if1", + shouldBeBlocked: true + }, + { + desc: "Test 10: top.location.href, via eval, should NOT be blocked when sandboxed with allow-top-navigation", + script: "eval('top.location.href = \"file_top_navigation_by_location_exotic.html\"')", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 11: top.location.href, via anonymous function, should be blocked when sandboxed without allow-top-navigation", + script: "(function() { top.location.href = 'file_top_navigation_by_location_exotic.html' })()", + iframeName: "if1", + shouldBeBlocked: true + }, + { + desc: "Test 12: top.location.href, via anonymous function, should NOT be blocked when sandboxed with allow-top-navigation", + script: "(function() { top.location.href = 'file_top_navigation_by_location_exotic.html' })()", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 13: top.location.href, via function inserted in top, should be blocked when sandboxed without allow-top-navigation", + script: "top.doTest = function() { top.location.href = 'file_top_navigation_by_location_exotic.html' }; top.doTest();", + iframeName: "if1", + shouldBeBlocked: true + }, + { + desc: "Test 14: top.location.href, via function inserted in top, should NOT be blocked when sandboxed with allow-top-navigation", + script: "top.doTest = function() { top.location.href = 'file_top_navigation_by_location_exotic.html' }; top.doTest();", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 15: top.location.href, via function inserted in us by top, should NOT be blocked when sandboxed without allow-top-navigation", + script: "top.eval('window[\"if1\"].doTest = function() { top.location.href = \"file_top_navigation_by_location_exotic.html\" };'), doTest();", + iframeName: "if1", + shouldBeBlocked: false + }, + { + desc: "Test 16: top.location.href, via function inserted in top, should NOT be blocked when sandboxed with allow-top-navigation", + script: "top.eval('window[\"if2\"].doTest = function() { top.location.href = \"file_top_navigation_by_location_exotic.html\" };'), doTest();", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 17: top.location.href, via function in top, should NOT be blocked when sandboxed without allow-top-navigation", + script: "top.setOwnHref()", + iframeName: "if1", + shouldBeBlocked: false + }, + { + desc: "Test 18: top.location.href, via function in top, should NOT be blocked when sandboxed with allow-top-navigation", + script: "top.setOwnHref()", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 19: top.location.href, via eval in top, should NOT be blocked when sandboxed without allow-top-navigation", + script: "top.eval('location.href = \"file_top_navigation_by_location_exotic.html\"')", + iframeName: "if1", + shouldBeBlocked: false + }, + { + desc: "Test 20: top.location.href, via eval in top, should NOT be blocked when sandboxed with allow-top-navigation", + script: "top.eval('location.href = \"file_top_navigation_by_location_exotic.html\"')", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 21: top.location.href, via eval in top calling us, should be blocked when sandboxed without allow-top-navigation", + script: "function doTest() { top.location.href = 'file_top_navigation_by_location_exotic.html' } top.eval('window[\"if1\"].doTest()');", + iframeName: "if1", + shouldBeBlocked: true + }, + { + desc: "Test 22: top.location.href, via eval in top calling us, should NOT be blocked when sandboxed with allow-top-navigation", + script: "function doTest() { top.location.href = 'file_top_navigation_by_location_exotic.html' } top.eval('window[\"if2\"].doTest()');", + iframeName: "if2", + shouldBeBlocked: false + }, + { + desc: "Test 23: top.location.href, via function bound to top, should be blocked when sandboxed without allow-top-navigation", + script: "(function() { top.location.href = 'file_top_navigation_by_location_exotic.html' }).bind(top)();", + iframeName: "if1", + shouldBeBlocked: true + }, + { + desc: "Test 24: top.location.href, via function bound to top, should NOT be blocked when sandboxed with allow-top-navigation", + script: "(function() { top.location.href = 'file_top_navigation_by_location_exotic.html' }).bind(top)();", + iframeName: "if2", + shouldBeBlocked: false + } + ]; + + function runNextTest() { + ++testCaseIndex; + if (testCaseIndex == testCases.length) { + testWin.close(); + SimpleTest.finish(); + return; + } + + runScriptNavigationTest(testCases[testCaseIndex]); + } + + window.onmessage = runNextTest; + testWin = window.open('file_top_navigation_by_location_exotic.html', "newWindow"); +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a> +<p id="display"></p> +<div id="content"> +Tests for Bug 785310 +</div> +</body> +</html> diff --git a/docshell/test/mochitest.ini b/docshell/test/mochitest.ini new file mode 100644 index 000000000..725486b77 --- /dev/null +++ b/docshell/test/mochitest.ini @@ -0,0 +1,94 @@ +[DEFAULT] +support-files = + bug123696-subframe.html + bug369814.jar + bug369814.zip + bug404548-subframe.html + bug413310-post.sjs + bug413310-subframe.html + bug529119-window.html + bug570341_recordevents.html + bug668513_redirect.html + bug668513_redirect.html^headers^ + bug691547_frame.html + file_anchor_scroll_after_document_open.html + file_bug385434_1.html + file_bug385434_2.html + file_bug385434_3.html + file_bug475636.sjs + file_bug509055.html + file_bug540462.html + file_bug580069_1.html + file_bug580069_2.sjs + file_bug590573_1.html + file_bug590573_2.html + file_bug634834.html + file_bug640387.html + file_bug653741.html + file_bug660404 + file_bug660404^headers^ + file_bug662170.html + file_bug669671.sjs + file_bug680257.html + file_bug703855.html + file_bug728939.html + file_pushState_after_document_open.html + historyframes.html + +[test_anchor_scroll_after_document_open.html] +[test_bfcache_plus_hash.html] +[test_bug123696.html] +[test_bug369814.html] +[test_bug384014.html] +[test_bug385434.html] +[test_bug387979.html] +[test_bug402210.html] +[test_bug404548.html] +[test_bug413310.html] +skip-if = true +# Disabled for too many intermittent failures (bug 719186) +[test_bug475636.html] +[test_bug509055.html] +[test_bug511449.html] +skip-if = toolkit != "cocoa" +support-files = file_bug511449.html +[test_bug529119-1.html] +[test_bug529119-2.html] +[test_bug530396.html] +support-files = bug530396-noref.sjs bug530396-subframe.html +[test_bug540462.html] +[test_bug551225.html] +[test_bug570341.html] +[test_bug580069.html] +[test_bug590573.html] +[test_bug598895.html] +skip-if = toolkit == 'android' +[test_bug634834.html] +[test_bug637644.html] +skip-if = toolkit == 'android' +[test_bug640387_1.html] +[test_bug640387_2.html] +[test_bug653741.html] +[test_bug660404.html] +[test_bug662170.html] +[test_bug668513.html] +skip-if = toolkit == 'android' +support-files = file_bug668513.html +[test_bug669671.html] +[test_bug675587.html] +[test_bug680257.html] +[test_bug691547.html] +[test_bug694612.html] +[test_bug703855.html] +[test_bug713825.html] +[test_bug728939.html] +[test_bug797909.html] +[test_bug1045096.html] +[test_bug1121701.html] +[test_bug1186774.html] +[test_forceinheritprincipal_overrule_owner.html] +[test_framedhistoryframes.html] +skip-if = toolkit == 'android' # bug 784321 +support-files = file_framedhistoryframes.html +[test_pushState_after_document_open.html] +[test_windowedhistoryframes.html] diff --git a/docshell/test/navigation/NavigationUtils.js b/docshell/test/navigation/NavigationUtils.js new file mode 100644 index 000000000..c90ea74d5 --- /dev/null +++ b/docshell/test/navigation/NavigationUtils.js @@ -0,0 +1,213 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/////////////////////////////////////////////////////////////////////////// +// +// Utilities for navigation tests +// +/////////////////////////////////////////////////////////////////////////// + +var body = "This frame was navigated."; +var target_url = "data:text/html,<html><body>" + body + "</body></html>"; + +var popup_body = "This is a popup"; +var target_popup_url = "data:text/html,<html><body>" + popup_body + "</body></html>"; + +/////////////////////////////////////////////////////////////////////////// +// Functions that navigate frames +/////////////////////////////////////////////////////////////////////////// + +function navigateByLocation(wnd) { + try { + wnd.location = target_url; + } catch(ex) { + // We need to keep our finished frames count consistent. + // Oddly, this ends up simulating the behavior of IE7. + window.open(target_url, "_blank", "width=10,height=10"); + } +} + +function navigateByOpen(name) { + window.open(target_url, name, "width=10,height=10"); +} + +function navigateByForm(name) { + var form = document.createElement("form"); + form.action = target_url; + form.method = "POST"; + form.target = name; document.body.appendChild(form); + form.submit(); +} + +var hyperlink_count = 0; + +function navigateByHyperlink(name) { + var link = document.createElement("a"); + link.href = target_url; + link.target = name; + link.id = "navigation_hyperlink_" + hyperlink_count++; + document.body.appendChild(link); + sendMouseEvent({type:"click"}, link.id); +} + +/////////////////////////////////////////////////////////////////////////// +// Functions that call into Mochitest framework +/////////////////////////////////////////////////////////////////////////// + +function isNavigated(wnd, message) { + var result = null; + try { + result = SpecialPowers.wrap(wnd).document.body.innerHTML; + } catch(ex) { + result = ex; + } + is(result, body, message); +} + +function isBlank(wnd, message) { + var result = null; + try { + result = wnd.document.body.innerHTML; + } catch(ex) { + result = ex; + } + is(result, "This is a blank document.", message); +} + +function isAccessible(wnd, message) { + try { + wnd.document.body.innerHTML; + ok(true, message); + } catch(ex) { + ok(false, message); + } +} + +function isInaccessible(wnd, message) { + try { + wnd.document.body.innerHTML; + ok(false, message); + } catch(ex) { + ok(true, message); + } +} + +/////////////////////////////////////////////////////////////////////////// +// Functions that require UniversalXPConnect privilege +/////////////////////////////////////////////////////////////////////////// + +function xpcEnumerateContentWindows(callback) { + + var Ci = SpecialPowers.Ci; + var ww = SpecialPowers.Cc["@mozilla.org/embedcomp/window-watcher;1"] + .getService(Ci.nsIWindowWatcher); + var enumerator = ww.getWindowEnumerator(); + + var contentWindows = []; + + while (enumerator.hasMoreElements()) { + var win = enumerator.getNext(); + if (/ChromeWindow/.exec(win)) { + var docshellTreeNode = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem); + var childCount = docshellTreeNode.childCount; + for (var i = 0; i < childCount; ++i) { + var childTreeNode = docshellTreeNode.getChildAt(i); + + // we're only interested in content docshells + if (SpecialPowers.unwrap(childTreeNode.itemType) != Ci.nsIDocShellTreeItem.typeContent) + continue; + + var webNav = childTreeNode.QueryInterface(Ci.nsIWebNavigation); + contentWindows.push(webNav.document.defaultView); + } + } else { + contentWindows.push(win); + } + } + + while (contentWindows.length > 0) + callback(contentWindows.pop()); +} + +// Note: This only searches for top-level frames with this name. +function xpcGetFramesByName(name) { + var results = []; + + xpcEnumerateContentWindows(function(win) { + if (win.name == name) + results.push(win); + }); + + return results; +} + +function xpcCleanupWindows() { + xpcEnumerateContentWindows(function(win) { + if (win.location && win.location.protocol == "data:") + win.close(); + }); +} + +function xpcWaitForFinishedFrames(callback, numFrames) { + var finishedFrameCount = 0; + function frameFinished() { + finishedFrameCount++; + + if (finishedFrameCount == numFrames) { + clearInterval(frameWaitInterval); + setTimeout(callback, 0); + return; + } + + if (finishedFrameCount > numFrames) + throw "Too many frames loaded."; + } + + var finishedWindows = []; + + function contains(obj, arr) { + for (var i = 0; i < arr.length; i++) { + if (obj === arr[i]) + return true; + } + return false; + } + + function searchForFinishedFrames(win) { + if ((escape(unescape(win.location)) == escape(target_url) || + escape(unescape(win.location)) == escape(target_popup_url)) && + win.document && + win.document.body && + (win.document.body.textContent == body || + win.document.body.textContent == popup_body) && + win.document.readyState == "complete") { + + var util = win.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIDOMWindowUtils); + var windowId = util.outerWindowID; + if (!contains(windowId, finishedWindows)) { + finishedWindows.push(windowId); + frameFinished(); + } + } + for (var i = 0; i < win.frames.length; i++) + searchForFinishedFrames(win.frames[i]); + } + + function poll() { + try { + // This only gives us UniversalXPConnect for the current stack frame + // We're using setInterval, so the main page's privileges are still normal + xpcEnumerateContentWindows(searchForFinishedFrames); + } catch(ex) { + // We might be accessing windows before they are fully constructed, + // which can throw. We'll find those frames on our next poll(). + } + } + + var frameWaitInterval = setInterval(poll, 500); +} + diff --git a/docshell/test/navigation/blank.html b/docshell/test/navigation/blank.html new file mode 100644 index 000000000..5360333f1 --- /dev/null +++ b/docshell/test/navigation/blank.html @@ -0,0 +1 @@ +<html><body>This is a blank document.</body></html>
\ No newline at end of file diff --git a/docshell/test/navigation/browser.ini b/docshell/test/navigation/browser.ini new file mode 100644 index 000000000..f25853f89 --- /dev/null +++ b/docshell/test/navigation/browser.ini @@ -0,0 +1,12 @@ +[DEFAULT] +support-files = + bug343515_pg1.html + bug343515_pg2.html + bug343515_pg3.html + bug343515_pg3_1.html + bug343515_pg3_1_1.html + bug343515_pg3_2.html + +[browser_bug343515.js] +[browser_test-content-chromeflags.js] +tags = openwindow
\ No newline at end of file diff --git a/docshell/test/navigation/browser_bug343515.js b/docshell/test/navigation/browser_bug343515.js new file mode 100644 index 000000000..74152b493 --- /dev/null +++ b/docshell/test/navigation/browser_bug343515.js @@ -0,0 +1,262 @@ +// Test for bug 343515 - Need API for tabbrowsers to tell docshells they're visible/hidden + +// Globals +var testPath = "http://mochi.test:8888/browser/docshell/test/navigation/"; +var ctx = {}; + +// We need to wait until the page from each testcase is fully loaded, +// including all of its descendant iframes. To do that we manually count +// how many load events should happen on that page (one for the toplevel doc +// and one for each subframe) and wait until we receive the expected number +// of events. +function nShotsListener(aElem, aType, aCallback, aCount) { + let count = aCount; + aElem.addEventListener(aType, function listenerCallback() { + if (--count == 0) { + aElem.removeEventListener(aType, listenerCallback, true); + + // aCallback is executed asynchronously, which is handy because load + // events fire before mIsDocumentLoaded is actually set to true. :( + executeSoon(aCallback); + } + }, true); +} + +function oneShotListener(aElem, aType, aCallback) { + nShotsListener(aElem, aType, aCallback, 1); +} + +function waitForPageshow(aBrowser, callback) { + return ContentTask.spawn(aBrowser, null, function* () { + yield ContentTaskUtils.waitForEvent(this, "pageshow"); + }).then(callback); +} + +// Entry point from Mochikit +function test() { + + // Lots of callbacks going on here + waitForExplicitFinish(); + + // Begin the test + step1(); +} + +function step1() { + + // Get a handle on the initial tab + ctx.tab0 = gBrowser.selectedTab; + ctx.tab0Browser = gBrowser.getBrowserForTab(ctx.tab0); + + // Our current tab should be active + ok(ctx.tab0Browser.docShellIsActive, "Tab 0 should be active at test start"); + + // Open a New Tab + ctx.tab1 = gBrowser.addTab(testPath + "bug343515_pg1.html"); + ctx.tab1Browser = gBrowser.getBrowserForTab(ctx.tab1); + oneShotListener(ctx.tab1Browser, "load", step2); +} + +function step2() { + is(testPath + "bug343515_pg1.html", ctx.tab1Browser.currentURI.spec, + "Got expected tab 1 url in step 2"); + + // Our current tab should still be active + ok(ctx.tab0Browser.docShellIsActive, "Tab 0 should still be active"); + ok(!ctx.tab1Browser.docShellIsActive, "Tab 1 should not be active"); + + // Switch to tab 1 + BrowserTestUtils.switchTab(gBrowser, ctx.tab1).then(() => { + // Tab 1 should now be active + ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive"); + ok(ctx.tab1Browser.docShellIsActive, "Tab 1 should be active"); + + // Open another tab + ctx.tab2 = gBrowser.addTab(testPath + "bug343515_pg2.html"); + ctx.tab2Browser = gBrowser.getBrowserForTab(ctx.tab2); + + // bug343515_pg2.html consists of a page with two iframes, + // which will therefore generate 3 load events. + nShotsListener(ctx.tab2Browser, "load", step3, 3); + }); +} + +function step3() { + is(testPath + "bug343515_pg2.html", ctx.tab2Browser.currentURI.spec, + "Got expected tab 2 url in step 3"); + + // Tab 0 should be inactive, Tab 1 should be active + ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive"); + ok(ctx.tab1Browser.docShellIsActive, "Tab 1 should be active"); + + // Tab 2's window _and_ its iframes should be inactive + ok(!ctx.tab2Browser.docShellIsActive, "Tab 2 should be inactive"); + ContentTask.spawn(ctx.tab2Browser, null, function* () { + Assert.equal(content.frames.length, 2, "Tab 2 should have 2 iframes"); + for (var i = 0; i < content.frames.length; i++) { + info("step 3, frame " + i + " info: " + content.frames[i].location); + let docshell = content.frames[i].QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + + Assert.ok(!docShell.isActive, `Tab2 iframe ${i} should be inactive`); + } + }).then(() => { + // Navigate tab 2 to a different page + ctx.tab2Browser.loadURI(testPath + "bug343515_pg3.html"); + + // bug343515_pg3.html consists of a page with two iframes, one of which + // contains another iframe, so there'll be a total of 4 load events + nShotsListener(ctx.tab2Browser, "load", step4, 4); + }); +} + +function step4() { + function checkTab2Active(expected) { + return ContentTask.spawn(ctx.tab2Browser, expected, function* (expected) { + function isActive(aWindow) { + var docshell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + return docshell.isActive; + } + + let active = expected ? "active" : "inactive"; + Assert.equal(content.frames.length, 2, "Tab 2 should have 2 iframes"); + for (var i = 0; i < content.frames.length; i++) + info("step 4, frame " + i + " info: " + content.frames[i].location); + Assert.equal(content.frames[0].frames.length, 1, "Tab 2 iframe 0 should have 1 iframes"); + Assert.equal(isActive(content.frames[0]), expected, `Tab2 iframe 0 should be ${active}`); + Assert.equal(isActive(content.frames[0].frames[0]), expected, + `Tab2 iframe 0 subiframe 0 should be ${active}`); + Assert.equal(isActive(content.frames[1]), expected, `Tab2 iframe 1 should be ${active}`); + }); + } + + is(testPath + "bug343515_pg3.html", ctx.tab2Browser.currentURI.spec, + "Got expected tab 2 url in step 4"); + + // Tab 0 should be inactive, Tab 1 should be active + ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive"); + ok(ctx.tab1Browser.docShellIsActive, "Tab 1 should be active"); + + // Tab2 and all descendants should be inactive + checkTab2Active(false).then(() => { + // Switch to Tab 2 + return BrowserTestUtils.switchTab(gBrowser, ctx.tab2); + }).then(() => { + // Check everything + ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive"); + ok(!ctx.tab1Browser.docShellIsActive, "Tab 1 should be inactive"); + ok(ctx.tab2Browser.docShellIsActive, "Tab 2 should be active"); + + return checkTab2Active(true); + }).then(() => { + // Go back + waitForPageshow(ctx.tab2Browser, step5); + ctx.tab2Browser.goBack(); + }); +} + +function step5() { + // Check everything + ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive"); + ok(!ctx.tab1Browser.docShellIsActive, "Tab 1 should be inactive"); + ok(ctx.tab2Browser.docShellIsActive, "Tab 2 should be active"); + ContentTask.spawn(ctx.tab2Browser, null, function* () { + for (var i = 0; i < content.frames.length; i++) { + let docshell = content.frames[i].QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + + Assert.ok(docShell.isActive, `Tab2 iframe ${i} should be active`); + } + }).then(() => { + // Switch to tab 1 + return BrowserTestUtils.switchTab(gBrowser, ctx.tab1); + }).then(() => { + // Navigate to page 3 + ctx.tab1Browser.loadURI(testPath + "bug343515_pg3.html"); + + // bug343515_pg3.html consists of a page with two iframes, one of which + // contains another iframe, so there'll be a total of 4 load events + nShotsListener(ctx.tab1Browser, "load", step6, 4); + }); +} + +function step6() { + + // Check everything + ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive"); + ok(ctx.tab1Browser.docShellIsActive, "Tab 1 should be active"); + ContentTask.spawn(ctx.tab1Browser, null, function* () { + function isActive(aWindow) { + var docshell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + return docshell.isActive; + } + + Assert.ok(isActive(content.frames[0]), "Tab1 iframe 0 should be active"); + Assert.ok(isActive(content.frames[0].frames[0]), "Tab1 iframe 0 subiframe 0 should be active"); + Assert.ok(isActive(content.frames[1]), "Tab1 iframe 1 should be active"); + }).then(() => { + ok(!ctx.tab2Browser.docShellIsActive, "Tab 2 should be inactive"); + return ContentTask.spawn(ctx.tab2Browser, null, function* () { + for (var i = 0; i < content.frames.length; i++) { + let docshell = content.frames[i].QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + + Assert.ok(!docShell.isActive, `Tab2 iframe ${i} should be inactive`); + } + }); + }).then(() => { + // Go forward on tab 2 + waitForPageshow(ctx.tab2Browser, step7); + ctx.tab2Browser.goForward(); + }); +} + +function step7() { + function checkBrowser(browser, tabNum, active) { + return ContentTask.spawn(browser, { tabNum, active }, + function* ({ tabNum, active }) { + function isActive(aWindow) { + var docshell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + return docshell.isActive; + } + + let activestr = active ? "active" : "inactive"; + Assert.equal(isActive(content.frames[0]), active, + `Tab${tabNum} iframe 0 should be ${activestr}`); + Assert.equal(isActive(content.frames[0].frames[0]), active, + `Tab${tabNum} iframe 0 subiframe 0 should be ${activestr}`); + Assert.equal(isActive(content.frames[1]), active, + `Tab${tabNum} iframe 1 should be ${activestr}`); + }); + } + + // Check everything + ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive"); + ok(ctx.tab1Browser.docShellIsActive, "Tab 1 should be active"); + checkBrowser(ctx.tab1Browser, 1, true).then(() => { + ok(!ctx.tab2Browser.docShellIsActive, "Tab 2 should be inactive"); + return checkBrowser(ctx.tab2Browser, 2, false); + }).then(() => { + // That's probably enough + allDone(); + }); +} + +function allDone() { + + // Close the tabs we made + gBrowser.removeTab(ctx.tab1); + gBrowser.removeTab(ctx.tab2); + + // Tell the framework we're done + finish(); +} diff --git a/docshell/test/navigation/browser_test-content-chromeflags.js b/docshell/test/navigation/browser_test-content-chromeflags.js new file mode 100644 index 000000000..64559897c --- /dev/null +++ b/docshell/test/navigation/browser_test-content-chromeflags.js @@ -0,0 +1,45 @@ +const TEST_PAGE = `data:text/html,<html><body><a href="about:blank" target="_blank">Test</a></body></html>`; +const CHROME_ALL = Ci.nsIWebBrowserChrome.CHROME_ALL; +const CHROME_REMOTE_WINDOW = Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW; + +/** + * Tests that when we open new browser windows from content they + * get the full browser chrome. + */ +add_task(function* () { + // Make sure that the window.open call will open a new + // window instead of a new tab. + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({ + "set": [ + ["browser.link.open_newwindow", 2], + ] + }, resolve); + }); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: TEST_PAGE + }, function*(browser) { + let openedPromise = BrowserTestUtils.waitForNewWindow(); + BrowserTestUtils.synthesizeMouse("a", 0, 0, {}, browser); + let win = yield openedPromise; + + let chromeFlags = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .treeOwner + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIXULWindow) + .chromeFlags; + + // In the multi-process case, the new window will have the + // CHROME_REMOTE_WINDOW flag set. + const EXPECTED = gMultiProcessBrowser ? CHROME_ALL | CHROME_REMOTE_WINDOW + : CHROME_ALL; + + is(chromeFlags, EXPECTED, "Window should have opened with all chrome"); + + BrowserTestUtils.closeWindow(win); + }); +}); diff --git a/docshell/test/navigation/bug343515_pg1.html b/docshell/test/navigation/bug343515_pg1.html new file mode 100644 index 000000000..a8337c7f7 --- /dev/null +++ b/docshell/test/navigation/bug343515_pg1.html @@ -0,0 +1,5 @@ +<html> + <head><meta charset="UTF-8"/></head> + <body>Page 1 + </body> +</html> diff --git a/docshell/test/navigation/bug343515_pg2.html b/docshell/test/navigation/bug343515_pg2.html new file mode 100644 index 000000000..c5f5665de --- /dev/null +++ b/docshell/test/navigation/bug343515_pg2.html @@ -0,0 +1,7 @@ +<html> + <head><meta charset="UTF-8"/></head> + <body>Page 2 + <iframe src="data:text/html;charset=UTF8,<html><head></head><body>pg2 iframe 0</body></html>"></iframe> + <iframe src="data:text/html;charset=UTF8,<html><head></head><body>pg2 iframe 1</body></html>"></iframe> + </body> +</html> diff --git a/docshell/test/navigation/bug343515_pg3.html b/docshell/test/navigation/bug343515_pg3.html new file mode 100644 index 000000000..fdc79fbf7 --- /dev/null +++ b/docshell/test/navigation/bug343515_pg3.html @@ -0,0 +1,7 @@ +<html> + <head><meta charset="UTF-8"/></head> + <body>Page 3 + <iframe src="bug343515_pg3_1.html"></iframe> + <iframe src="bug343515_pg3_2.html"></iframe> + </body> +</html> diff --git a/docshell/test/navigation/bug343515_pg3_1.html b/docshell/test/navigation/bug343515_pg3_1.html new file mode 100644 index 000000000..254164c9f --- /dev/null +++ b/docshell/test/navigation/bug343515_pg3_1.html @@ -0,0 +1,6 @@ +<html> + <head><meta charset="UTF-8"/></head> + <body>pg3 - iframe 0 + <iframe src="bug343515_pg3_1_1.html"></iframe> + </body> +</html> diff --git a/docshell/test/navigation/bug343515_pg3_1_1.html b/docshell/test/navigation/bug343515_pg3_1_1.html new file mode 100644 index 000000000..be05b7488 --- /dev/null +++ b/docshell/test/navigation/bug343515_pg3_1_1.html @@ -0,0 +1 @@ +<html><head><meta charset="UTF-8"/></head><body>How far does the rabbit hole go?</body></html> diff --git a/docshell/test/navigation/bug343515_pg3_2.html b/docshell/test/navigation/bug343515_pg3_2.html new file mode 100644 index 000000000..7655eb526 --- /dev/null +++ b/docshell/test/navigation/bug343515_pg3_2.html @@ -0,0 +1 @@ +<html><head><meta charset="UTF-8"/></head><body>pg3 iframe 1</body></html> diff --git a/docshell/test/navigation/file_bug1300461.html b/docshell/test/navigation/file_bug1300461.html new file mode 100644 index 000000000..1ba935046 --- /dev/null +++ b/docshell/test/navigation/file_bug1300461.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <title>Bug 1300461</title> + </head> + <body onload="test();"> + <script> + /** + * Bug 1300461 identifies that if a history entry was not bfcached, and + * a http redirection happens when navigating to that entry, the history + * index would mess up. + * + * The test case emulates the circumstance by the following steps + * 1) Navigate to file_bug1300461_back.html which is not bf-cachable. + * 2) In file_bug1300461_back.html, replace its own history state to + * file_bug1300461_redirect.html. + * 3) Back, and then forward. Since the document is not in bfcache, it + * tries to load file_bug1300461_redirect.html directly. + * 4) file_bug1300461_redirect.html redirects UA to + * file_bug1300461_back.html through HTTP 301 header. + * + * We verify the history index, canGoBack, canGoForward, etc. keep correct + * in this process. + */ + let Ci = SpecialPowers.Ci; + let webNav = SpecialPowers.wrap(window) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation); + let shistory = webNav.sessionHistory; + let testSteps = [ + function() { + opener.is(shistory.count, 1, 'check history length'); + opener.is(shistory.index, 0, 'check history index'); + opener.ok(!webNav.canGoForward, 'check canGoForward'); + setTimeout(() => window.location = 'file_bug1300461_back.html', 0); + }, + function() { + opener.is(shistory.count, 2, 'check history length'); + opener.is(shistory.index, 0, 'check history index'); + opener.ok(webNav.canGoForward, 'check canGoForward'); + window.history.forward(); + opener.is(shistory.requestedIndex, 1, 'check requestedIndex'); + }, + function() { + opener.is(shistory.count, 2, 'check history length'); + opener.is(shistory.index, 0, 'check history index'); + opener.ok(webNav.canGoForward, 'check canGoForward'); + opener.info('file_bug1300461.html tests finished'); + opener.nextTest(); + window.close(); + } + ]; + + function test() { + if (opener) { + opener.info('file_bug1300461.html test ' + opener.testCount); + testSteps[opener.testCount++](); + } + } + </script> + </body> +</html> diff --git a/docshell/test/navigation/file_bug1300461_back.html b/docshell/test/navigation/file_bug1300461_back.html new file mode 100644 index 000000000..7b187742f --- /dev/null +++ b/docshell/test/navigation/file_bug1300461_back.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <title>Bug 1300461</title> + </head> + <!-- The empty unload handler is to prevent bfcache. --> + <body onload="test();" onunload=""> + <script> + let Ci = SpecialPowers.Ci; + let webNav = SpecialPowers.wrap(window) + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation); + let shistory = webNav.sessionHistory; + function test() { + if (opener) { + opener.info("file_bug1300461_back.html"); + opener.is(shistory.count, 2, 'check history length'); + opener.is(shistory.index, 1, 'check history index'); + opener.is(shistory.requestedIndex, -1, 'check requestedIndex'); + opener.ok(webNav.canGoBack, 'check canGoBack'); + if (opener.testCount == 1) { + opener.info('replaceState to redirect.html'); + window.history.replaceState({}, '', 'file_bug1300461_redirect.html'); + } + window.history.back(); + } + } + </script> + </body> +</html> diff --git a/docshell/test/navigation/file_bug1300461_redirect.html b/docshell/test/navigation/file_bug1300461_redirect.html new file mode 100644 index 000000000..979530c5c --- /dev/null +++ b/docshell/test/navigation/file_bug1300461_redirect.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta http-equiv="content-type" content="text/html; charset=utf-8"> + <title>Bug 1300461</title> + </head> + <body> + Redirect to file_bug1300461_back.html. + </body> +</html> diff --git a/docshell/test/navigation/file_bug1300461_redirect.html^headers^ b/docshell/test/navigation/file_bug1300461_redirect.html^headers^ new file mode 100644 index 000000000..241b89182 --- /dev/null +++ b/docshell/test/navigation/file_bug1300461_redirect.html^headers^ @@ -0,0 +1,2 @@ +HTTP 301 Moved Permanently +Location: file_bug1300461_back.html diff --git a/docshell/test/navigation/file_bug462076_1.html b/docshell/test/navigation/file_bug462076_1.html new file mode 100644 index 000000000..5b7bf2b11 --- /dev/null +++ b/docshell/test/navigation/file_bug462076_1.html @@ -0,0 +1,55 @@ +<html> + <head> + <title>Bug 462076</title> + <script> + var srcs = [ "frame0.html", + "frame1.html", + "frame2.html", + "frame3.html" ]; + + var checkCount = 0; + + function makeFrame(index) { + var ifr = document.createElement("iframe"); + ifr.src = srcs[index]; + ifr.onload = checkFrame; + document.getElementById("container" + index).appendChild(ifr); + } + + function runTest() { + var randomNumber = Math.floor(Math.random() * 4); + for (var i = randomNumber; i < 4; ++i) { + makeFrame(i); + } + for (var i = 0; i < randomNumber; ++i) { + makeFrame(i); + } + } + + function checkFrame(evt) { + var ifr = evt.target; + opener.ok(new String(ifr.contentWindow.location).indexOf(ifr.src) >= 0, + "Wrong document loaded (" + ifr.src + ", " + + ifr.contentWindow.location + ")!"); + + if (++checkCount == 4) { + if (++opener.testCount == 10) { + opener.nextTest(); + window.close(); + } else { + window.location.reload(); + } + } + } + </script> + </head> + <body> + <div id="container0"></div> + <div id="container1"></div> + <div id="container2"></div> + <div id="container3"></div> + <script> + runTest(); + </script> + </body> +</html> diff --git a/docshell/test/navigation/file_bug462076_2.html b/docshell/test/navigation/file_bug462076_2.html new file mode 100644 index 000000000..813a6d840 --- /dev/null +++ b/docshell/test/navigation/file_bug462076_2.html @@ -0,0 +1,52 @@ +<html> + <head> + <title>Bug 462076</title> + <script> + var srcs = [ "frame0.html", + "frame1.html", + "frame2.html", + "frame3.html" ]; + + var checkCount = 0; + + function makeFrame(index) { + var ifr = document.createElement("iframe"); + ifr.src = srcs[index]; + ifr.onload = checkFrame; + document.getElementById("container" + index).appendChild(ifr); + } + + function runTest() { + var randomNumber = Math.floor(Math.random() * 4); + for (var i = randomNumber; i < 4; ++i) { + makeFrame(i); + } + for (var i = 0; i < randomNumber; ++i) { + makeFrame(i); + } + } + + function checkFrame(evt) { + var ifr = evt.target; + opener.ok(new String(ifr.contentWindow.location).indexOf(ifr.src) >= 0, + "Wrong document loaded (" + ifr.src + ", " + + ifr.contentWindow.location + ")!"); + + if (++checkCount == 4) { + if (++opener.testCount == 10) { + opener.nextTest(); + window.close(); + } else { + window.location.reload(); + } + } + } + </script> + </head> + <body onload="runTest();"> + <div id="container0"></div> + <div id="container1"></div> + <div id="container2"></div> + <div id="container3"></div> + </body> +</html> diff --git a/docshell/test/navigation/file_bug462076_3.html b/docshell/test/navigation/file_bug462076_3.html new file mode 100644 index 000000000..0079b9cdf --- /dev/null +++ b/docshell/test/navigation/file_bug462076_3.html @@ -0,0 +1,52 @@ +<html> + <head> + <title>Bug 462076</title> + <script> + var srcs = [ "frame0.html", + "frame1.html", + "frame2.html", + "frame3.html" ]; + + var checkCount = 0; + + function makeFrame(index) { + var ifr = document.createElement("iframe"); + ifr.src = srcs[index]; + ifr.onload = checkFrame; + document.getElementById("container" + index).appendChild(ifr); + } + + function runTest() { + var randomNumber = Math.floor(Math.random() * 4); + for (var i = randomNumber; i < 4; ++i) { + makeFrame(i); + } + for (var i = 0; i < randomNumber; ++i) { + makeFrame(i); + } + } + + function checkFrame(evt) { + var ifr = evt.target; + opener.ok(new String(ifr.contentWindow.location).indexOf(ifr.src) >= 0, + "Wrong document loaded (" + ifr.src + ", " + + ifr.contentWindow.location + ")!"); + + if (++checkCount == 4) { + if (++opener.testCount == 10) { + opener.nextTest(); + window.close(); + } else { + window.location.reload(); + } + } + } + </script> + </head> + <body onload="setTimeout(runTest, 0);"> + <div id="container0"></div> + <div id="container1"></div> + <div id="container2"></div> + <div id="container3"></div> + </body> +</html> diff --git a/docshell/test/navigation/file_bug508537_1.html b/docshell/test/navigation/file_bug508537_1.html new file mode 100644 index 000000000..194165f43 --- /dev/null +++ b/docshell/test/navigation/file_bug508537_1.html @@ -0,0 +1,34 @@ +<html> + <head> + <script> + function dynFrameLoad() { + var ifrs = document.getElementsByTagName("iframe"); + opener.ok(new String(ifrs[0].contentWindow.location).indexOf(ifrs[0].src) >= 0, + "Wrong document loaded (1)\n"); + opener.ok(new String(ifrs[1].contentWindow.location).indexOf(ifrs[1].src) >= 0, + "Wrong document loaded (2)\n"); + if (opener && ++opener.testCount == 1) { + window.location = "goback.html"; + } else { + opener.nextTest(); + window.close(); + } + } + + window.addEventListener("load", + function () { + var container = document.getElementById("t1"); + container.addEventListener("load", dynFrameLoad, true); + container.appendChild(container.appendChild(document.getElementById("i1"))); + }, false); + </script> + </head> + <body> + <h5>Container:</h5> + <div id="t1"></div> + <h5>Original frames:</h5> + <iframe id="i1" src="frame0.html"></iframe> + <iframe src="frame1.html"></iframe> + </body> +</html> + diff --git a/docshell/test/navigation/file_bug534178.html b/docshell/test/navigation/file_bug534178.html new file mode 100644 index 000000000..8bcedb6fb --- /dev/null +++ b/docshell/test/navigation/file_bug534178.html @@ -0,0 +1,31 @@ +<html> + <head> + <script> + + function testDone() { + document.body.removeChild(document.body.firstChild); + var isOK = false; + try { + isOK = history.previous != location; + } catch(ex) { + // history.previous should throw if this is the first page in shistory. + isOK = true; + } + document.body.textContent = isOK ? "PASSED" : "FAILED"; + opener.ok(isOK, "Duplicate session history transactions should have been removed!"); + opener.nextTest(); + window.close(); + } + function ifrload() { + setTimeout(testDone, 0); + } + function test() { + var ifr = document.getElementsByTagName("iframe")[0]; + ifr.onload = ifrload; + ifr.src = "data:text/html,doc2"; + } + </script> + </head> + <body onload="setTimeout(test, 0)"><iframe src="data:text/html,doc1"></iframe> + </body> +</html> diff --git a/docshell/test/navigation/file_document_write_1.html b/docshell/test/navigation/file_document_write_1.html new file mode 100644 index 000000000..e0281f7cd --- /dev/null +++ b/docshell/test/navigation/file_document_write_1.html @@ -0,0 +1,30 @@ +<html> + <head> + <script> + function run() { + document.open(); + document.write("<h5 id='dynamic'>document.written content</h5>"); + document.close(); + window.history.go(-1); + } + + function start() { + if (++opener.testCount == 1) { + setTimeout(run, 0); + } + } + + window.addEventListener("pageshow", + function() { + ++opener.file_document_write_1_loadCount; + if (opener.file_document_write_1_loadCount == 2) { + opener.setTimeout("isTestDynamic()", 0); + } + opener.ok(opener.file_document_write_1_loadCount <= 2); + }); + </script> + </head> + <body onload="start();"> + <h5>static content</h5> + </body> +</html> diff --git a/docshell/test/navigation/file_fragment_handling_during_load.html b/docshell/test/navigation/file_fragment_handling_during_load.html new file mode 100644 index 000000000..fbfa0cb38 --- /dev/null +++ b/docshell/test/navigation/file_fragment_handling_during_load.html @@ -0,0 +1,24 @@ +<html> + <head> + <script> + var timerID = 0; + function testDone() { + clearTimeout(timerID); + var l = document.body.firstChild.contentWindow.location.href; + opener.is(l, "data:text/html,bar", "Should have loaded a new document"); + opener.nextTest(); + window.close(); + } + function test() { + var ifr = document.getElementsByTagName("iframe")[0]; + ifr.onload = testDone; + ifr.contentWindow.location.hash = "b"; + ifr.contentWindow.location.href = "data:text/html,bar"; + history.back(); + timerID = setTimeout(testDone, 2000); + } + </script> + </head> + <body onload="setTimeout(test, 0)"><iframe src="data:text/html,foo#a"></iframe> + </body> +</html> diff --git a/docshell/test/navigation/file_nested_frames.html b/docshell/test/navigation/file_nested_frames.html new file mode 100644 index 000000000..f65d8e01b --- /dev/null +++ b/docshell/test/navigation/file_nested_frames.html @@ -0,0 +1,28 @@ +<html> + <head> + <script> + function nestedIframeLoaded() { + var tf = document.getElementById("testframe"); + var innerf = tf.contentDocument.getElementsByTagName("iframe")[0]; + if (innerf.contentDocument.documentURI.indexOf("frame0") < 0) { + innerf.contentWindow.location.href = "http://mochi.test:8888/tests/docshell/test/navigation/frame0.html"; + return; + } + innerf.onload = null; + innerf.src = "about:blank"; + var d = innerf.contentDocument; + d.open(); + d.write("test"); + d.close(); + opener.is(window.history.length, 1, "Unexpected history length"); + opener.nextTest(); + window.close(); + } + </script> + </head> + <body> + <iframe id="testframe" src="data:text/html,<iframe onload='parent.nestedIframeLoaded();'></iframe>" onload="frameLoaded()"></iframe> + <script> + </script> + </body> +</html> diff --git a/docshell/test/navigation/file_scrollRestoration.html b/docshell/test/navigation/file_scrollRestoration.html new file mode 100644 index 000000000..5450c2724 --- /dev/null +++ b/docshell/test/navigation/file_scrollRestoration.html @@ -0,0 +1,137 @@ +<html> + <head> + <script> + var oldHistoryObject = null; + + function test(event) { + if (!opener.scrollRestorationTest) { + opener.scrollRestorationTest = 0; + } + ++opener.scrollRestorationTest; + + switch (opener.scrollRestorationTest) { + case 1: { + opener.is(event.persisted, false, "Shouldn't have persisted session history entry."); + opener.ok(history.scrollRestoration, "History object has scrollRestoration property."); + opener.ok(history.scrollRestoration, "auto", "history.scrollRestoration's default value should be 'auto'."); + history.scrollRestoration = "foobar"; + opener.ok(history.scrollRestoration, "auto", "Invalid enum value should not change the value of an attribute."); + history.scrollRestoration = "manual"; + opener.ok(history.scrollRestoration, "manual", "Valid enum value should change the value of an attribute."); + history.scrollRestoration = "auto"; + opener.ok(history.scrollRestoration, "auto", "Valid enum value should change the value of an attribute."); + document.getElementById("bottom").scrollIntoView(); + window.location.reload(false); + break; + } + case 2: { + opener.is(event.persisted, false, "Shouldn't have persisted session history entry."); + opener.isnot(window.scrollY, 0, "Should have restored scrolling."); + opener.is(history.scrollRestoration, "auto", "Should have the same scrollRestoration as before reload."); + history.scrollRestoration = "manual"; + window.onunload = function() {} // Disable bfcache. + window.location.reload(false); + break; + } + case 3: { + opener.is(event.persisted, false, "Shouldn't have persisted session history entry."); + opener.is(window.scrollY, 0, "Should not have restored scrolling."); + opener.is(history.scrollRestoration, "manual", "Should have the same scrollRestoration as before reload."); + document.getElementById("bottom").scrollIntoView(); + window.onunload = null; // Should get bfcache behavior. + opener.setTimeout("testWindow.history.back();", 250); + window.location.href = 'data:text/html,'; + break; + } + case 4: { + opener.is(event.persisted, true, "Should have persisted session history entry."); + opener.isnot(window.scrollY, 0, "Should have kept the old scroll position."); + opener.is(history.scrollRestoration, "manual", "Should have the same scrollRestoration as before reload."); + window.scrollTo(0, 0); + window.location.hash = "hash"; + requestAnimationFrame(test); + break; + } + case 5: { + opener.isnot(window.scrollY, 0, "Should have scrolled to #hash."); + opener.is(history.scrollRestoration, "manual", "Should have the same scrollRestoration mode as before fragment navigation."); + window.onunload = function() {} // Disable bfcache. + opener.setTimeout("is(testWindow.history.scrollRestoration, 'auto'); testWindow.history.back();", 250); + window.location.href = 'data:text/html,'; + break; + } + case 6: { + opener.is(event.persisted, false, "Shouldn't have persisted session history entry."); + opener.is(window.scrollY, 0, "Shouldn't have kept the old scroll position."); + opener.is(history.scrollRestoration, "manual", "Should have the same scrollRestoration mode as before fragment navigation."); + history.scrollRestoration = "auto"; + document.getElementById("bottom").scrollIntoView(); + history.pushState({ state: "state1" }, "state1"); + history.pushState({ state: "state2" }, "state2"); + window.scrollTo(0, 0); + history.back(); + opener.isnot(window.scrollY, 0, "Should have scrolled back to the state1's position"); + opener.is(history.state.state, "state1", "Unexpected state."); + + history.scrollRestoration = "manual"; + document.getElementById("bottom").scrollIntoView(); + history.pushState({ state: "state3" }, "state3"); + history.pushState({ state: "state4" }, "state4"); + window.scrollTo(0, 0); + history.back(); + opener.is(window.scrollY, 0, "Shouldn't have scrolled back to the state3's position"); + opener.is(history.state.state, "state3", "Unexpected state."); + + history.pushState({ state: "state5" }, "state5"); + history.scrollRestoration = "auto"; + document.getElementById("bottom").scrollIntoView(); + opener.isnot(window.scrollY, 0, "Should have scrolled to 'bottom'."); + history.back(); + window.scrollTo(0, 0); + history.forward(); + opener.isnot(window.scrollY, 0, "Should have scrolled back to the state5's position"); + + var ifr = document.createElement("iframe"); + ifr.src = "data:text/html,"; + document.body.appendChild(ifr); + ifr.onload = test; + break; + } + case 7: { + oldHistoryObject = event.target.contentWindow.history; + event.target.src = "about:blank"; + break; + } + case 8: { + try { + var sr = oldHistoryObject.scrollRestoration; + opener.ok(false, "Should have thrown an exception."); + } catch(ex) { + opener.isnot(ex, null, "Did get an exception"); + } + try { + oldHistoryObject.scrollRestoration = "auto"; + opener.ok(false, "Should have thrown an exception."); + } catch(ex) { + opener.isnot(ex, null, "Did get an exception"); + } + opener.nextTest(); + window.close(); + break; + } + } + } + + window.addEventListener("pageshow", + function(e) { + setTimeout(test, 0, e); + }); + </script> + </head> + <body> + <div style="border: 1px solid black; height: 5000px;"> + </div> + <div id="bottom">Hello world</div> + <a href="#hash" name="hash">hash</a> + </body> +</html> diff --git a/docshell/test/navigation/file_shiftReload_and_pushState.html b/docshell/test/navigation/file_shiftReload_and_pushState.html new file mode 100644 index 000000000..5a777894b --- /dev/null +++ b/docshell/test/navigation/file_shiftReload_and_pushState.html @@ -0,0 +1,29 @@ +<html> + <head> + <script> + function test() { + try { + frames[0].history.pushState({}, "state", "?pushed"); + } catch(ex) { + opener.ok(false, "history.pushState shouldn't throw"); + } + + if (!opener.shiftReloadPushStateFirstRound) { + opener.shiftReloadPushStateFirstRound = true; + window.location.reload(true); + } else { + opener.ok(true, "Did run history.push"); + opener.nextTest(); + window.close(); + } + } + + window.addEventListener("load", function() { setTimeout(test, 0); }); + </script> + </head> + <body> + <iframe src="frame0.html"></iframe> + <script> + </script> + </body> +</html> diff --git a/docshell/test/navigation/file_static_and_dynamic_1.html b/docshell/test/navigation/file_static_and_dynamic_1.html new file mode 100644 index 000000000..692a5a9ab --- /dev/null +++ b/docshell/test/navigation/file_static_and_dynamic_1.html @@ -0,0 +1,32 @@ +<html> + <head> + <script> + function test() { + var ifr = document.createElement("iframe"); + ifr.src = "frame0.html"; + document.getElementById("dynamic").appendChild(ifr); + var staticFrame = document.getElementById("staticframe"); + staticFrame.onload = window.location = "goback.html"; + staticFrame.contentWindow.location = "frame1.html"; + } + + function start() { + if (++opener.testCount == 1) { + test(); + } else { + var staticFrame = document.getElementById("staticframe"); + opener.ok(new String(staticFrame.contentWindow.location).indexOf(staticFrame.src) >= 0, + "Wrong document loaded!"); + opener.nextTest(); + window.close(); + } + } + </script> + </head> + <body onload="setTimeout('start()', 0)"> + <h5>Dynamic</h5> + <div id="dynamic"></div> + <h5>Static</h5> + <div id="static"><iframe id="staticframe" src="frame0.html"></iframe></div> + </body> +</html> diff --git a/docshell/test/navigation/file_triggeringprincipal_frame_1.html b/docshell/test/navigation/file_triggeringprincipal_frame_1.html new file mode 100644 index 000000000..1e21e6421 --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_frame_1.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<head><meta charset="utf-8"></head> +<body> +<b>Frame 1</b><br/> +<a href="#"" id="testlink" onclick="parent.frames[1].frames[0].location='http://test2.mochi.test:8888/tests/docshell/test/navigation/file_triggeringprincipal_subframe_nav.html'">click me</a> + +<script type="application/javascript"> + // make sure to set document.domain to the same domain as the subframe + window.onload = function() { + document.domain = 'mochi.test'; + }; + window.addEventListener('message', receiveMessage, false); + function receiveMessage(event) { + // make sure to get the right start command, otherwise + // let the parent know and fail the test + if (event.data.start !== 'startTest') { + window.removeEventListener("message", receiveMessage, false); + window.parent.postMessage({triggeringPrincipalURI: 'false'}, '*'); + } + // click the link to navigate the subframe + document.getElementById('testlink').click(); + } +</script> + +</body> +</html> diff --git a/docshell/test/navigation/file_triggeringprincipal_frame_2.html b/docshell/test/navigation/file_triggeringprincipal_frame_2.html new file mode 100644 index 000000000..ef7cdfc17 --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_frame_2.html @@ -0,0 +1,8 @@ +<!DOCTYPE HTML> +<html> +<head><meta charset="utf-8"></head> +<body> +<b>Frame 2</b><br/> +<iframe src="http://test2.mochi.test:8888/tests/docshell/test/navigation/file_triggeringprincipal_subframe.html"></iframe> +</body> +</html> diff --git a/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a.html b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a.html new file mode 100644 index 000000000..75b2933c1 --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> +<body> +Frame A +</body> +</html> diff --git a/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html new file mode 100644 index 000000000..0479f5e1e --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> +<body> +Frame A navigated by Frame B +</body> +</html> diff --git a/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_b.html b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_b.html new file mode 100644 index 000000000..e5d40b267 --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_b.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<body> +Frame B navigating Frame A + +<script type="text/javascript"> + +window.open("file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html", "framea"); + +</script> + +</body> +</html> + + diff --git a/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_base.html b/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_base.html new file mode 100644 index 000000000..caa6b275b --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_base.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> +<body> +base test frame +</body> +</html> diff --git a/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_nav.html b/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_nav.html new file mode 100644 index 000000000..f4a4d0e63 --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_nav.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> +<body> +navigated by window.open() +</body> +</html> diff --git a/docshell/test/navigation/file_triggeringprincipal_subframe.html b/docshell/test/navigation/file_triggeringprincipal_subframe.html new file mode 100644 index 000000000..0db1349f2 --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_subframe.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<head><meta charset='utf-8'></head> +<body> +<b>Sub Frame 2</b><br/> +<script type='application/javascript'> + // make sure to set document.domain to same domain as frame 1 + window.onload = function() { + document.domain = 'mochi.test'; + // let Frame 1 know that we are ready to run the test + window.parent.parent.frames[0].postMessage({start: 'startTest'}, '*'); + }; +</script> +</body> +</html> diff --git a/docshell/test/navigation/file_triggeringprincipal_subframe_nav.html b/docshell/test/navigation/file_triggeringprincipal_subframe_nav.html new file mode 100644 index 000000000..3bf7097a6 --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_subframe_nav.html @@ -0,0 +1,21 @@ +<!DOCTYPE HTML> +<html> +<head><meta charset="utf-8"></head> +<body onload="checkResults()"> +<b>Sub Frame 2 Navigated</b><br/> + +<script type='application/javascript'> + function checkResults() { + // query the uri of the loadingPrincipal and the TriggeringPrincipal and pass + // that information on to the parent for verification. + var channel = SpecialPowers.wrap(document).docShell.currentDocumentChannel; + var triggeringPrincipalURI = channel.loadInfo.triggeringPrincipal.URI.asciiSpec; + var loadingPrincipalURI = channel.loadInfo.loadingPrincipal.URI.asciiSpec; + var referrerURI = document.referrer; + window.parent.parent.postMessage({triggeringPrincipalURI, + loadingPrincipalURI, + referrerURI}, '*'); + } +</script> +</body> +</html> diff --git a/docshell/test/navigation/file_triggeringprincipal_window_open.html b/docshell/test/navigation/file_triggeringprincipal_window_open.html new file mode 100644 index 000000000..d0644a4d5 --- /dev/null +++ b/docshell/test/navigation/file_triggeringprincipal_window_open.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<html> +<body> +http +</body> +</html> diff --git a/docshell/test/navigation/frame0.html b/docshell/test/navigation/frame0.html new file mode 100644 index 000000000..93d1c9c82 --- /dev/null +++ b/docshell/test/navigation/frame0.html @@ -0,0 +1,3 @@ +<html> + <body>Frame 0</body> +</html> diff --git a/docshell/test/navigation/frame1.html b/docshell/test/navigation/frame1.html new file mode 100644 index 000000000..4d06c09d1 --- /dev/null +++ b/docshell/test/navigation/frame1.html @@ -0,0 +1,3 @@ +<html> + <body>Frame 1</body> +</html> diff --git a/docshell/test/navigation/frame2.html b/docshell/test/navigation/frame2.html new file mode 100644 index 000000000..7a3b5e0b9 --- /dev/null +++ b/docshell/test/navigation/frame2.html @@ -0,0 +1,3 @@ +<html> + <body>Frame 2</body> +</html> diff --git a/docshell/test/navigation/frame3.html b/docshell/test/navigation/frame3.html new file mode 100644 index 000000000..fd2429387 --- /dev/null +++ b/docshell/test/navigation/frame3.html @@ -0,0 +1,3 @@ +<html> + <body>Frame 3</body> +</html> diff --git a/docshell/test/navigation/goback.html b/docshell/test/navigation/goback.html new file mode 100644 index 000000000..ce2968374 --- /dev/null +++ b/docshell/test/navigation/goback.html @@ -0,0 +1,5 @@ +<html> + <body onload="setTimeout('window.history.go(-1)', 1000);"> + window.history.go(-1); + </body> +</html> diff --git a/docshell/test/navigation/iframe.html b/docshell/test/navigation/iframe.html new file mode 100644 index 000000000..4685fea7b --- /dev/null +++ b/docshell/test/navigation/iframe.html @@ -0,0 +1,8 @@ +<html> +<body> +<script> +var src = window.location.hash.substring(1); +document.write('<iframe src="' + src + '"></iframe>'); +</script> +</body> +</html> diff --git a/docshell/test/navigation/mochitest.ini b/docshell/test/navigation/mochitest.ini new file mode 100644 index 000000000..0c35cf352 --- /dev/null +++ b/docshell/test/navigation/mochitest.ini @@ -0,0 +1,64 @@ +[DEFAULT] +support-files = + NavigationUtils.js + blank.html + file_bug462076_1.html + file_bug462076_2.html + file_bug462076_3.html + file_bug508537_1.html + file_bug534178.html + file_document_write_1.html + file_fragment_handling_during_load.html + file_nested_frames.html + file_scrollRestoration.html + file_shiftReload_and_pushState.html + file_static_and_dynamic_1.html + frame0.html + frame1.html + frame2.html + frame3.html + goback.html + iframe.html + navigate.html + open.html + parent.html + file_triggeringprincipal_frame_1.html + file_triggeringprincipal_frame_2.html + file_triggeringprincipal_subframe.html + file_triggeringprincipal_subframe_nav.html + file_triggeringprincipal_window_open.html + file_triggeringprincipal_parent_iframe_window_open_base.html + file_triggeringprincipal_parent_iframe_window_open_nav.html + file_triggeringprincipal_iframe_iframe_window_open_frame_a.html + file_triggeringprincipal_iframe_iframe_window_open_frame_b.html + file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html + file_bug1300461.html + file_bug1300461_redirect.html + file_bug1300461_redirect.html^headers^ + file_bug1300461_back.html + +[test_bug13871.html] +[test_bug270414.html] +[test_bug278916.html] +[test_bug279495.html] +[test_bug344861.html] +skip-if = toolkit == "android" || toolkit == "windows" # disabled on Windows because of bug 1234520 +[test_bug386782.html] +[test_bug430624.html] +[test_bug430723.html] +skip-if = (toolkit == 'android') || (!debug && (os == 'mac' || os == 'win')) # Bug 874423 +[test_child.html] +[test_grandchild.html] +[test_not-opener.html] +[test_opener.html] +[test_popup-navigates-children.html] +[test_reserved.html] +skip-if = (toolkit == 'android') || (debug && e10s) #too slow on Android 4.3 aws only; bug 1030403; bug 1263213 for debug e10s +[test_sessionhistory.html] +skip-if = toolkit == 'android' #RANDOM +[test_sibling-matching-parent.html] +[test_sibling-off-domain.html] +[test_triggeringprincipal_frame_nav.html] +[test_triggeringprincipal_window_open.html] +[test_triggeringprincipal_parent_iframe_window_open.html] +[test_triggeringprincipal_iframe_iframe_window_open.html] diff --git a/docshell/test/navigation/navigate.html b/docshell/test/navigation/navigate.html new file mode 100644 index 000000000..e686da423 --- /dev/null +++ b/docshell/test/navigation/navigate.html @@ -0,0 +1,36 @@ +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script src="NavigationUtils.js"></script> + <script> + function navigate() { + var arguments = window.location.hash.substring(1).split(","); + var target = arguments[0]; + var mechanism = arguments[1]; + + switch(mechanism) { + case "location": + navigateByLocation(eval(target)); + break; + case "open": + navigateByOpen(target); + break; + case "form": + navigateByForm(target); + break; + case "hyperlink": + navigateByHyperlink(target); + break; + } + } + </script> +</head> +<body onload="navigate();"> +<script> +var arguments = window.location.hash.substring(1).split(","); +var target = arguments[0]; +var mechanism = arguments[1]; +document.write("target=" + target + " mechanism=" + mechanism); +</script> +</body> +</html> diff --git a/docshell/test/navigation/open.html b/docshell/test/navigation/open.html new file mode 100644 index 000000000..1bb70f865 --- /dev/null +++ b/docshell/test/navigation/open.html @@ -0,0 +1,9 @@ +<html> +<body> +<script> +var target = window.location.hash.substring(1); +document.write("target=" + target); +window.open("data:text/html,<html><body>This is a popup</body></html>", target, "width=10,height=10"); +</script> +</body> +</html> diff --git a/docshell/test/navigation/parent.html b/docshell/test/navigation/parent.html new file mode 100644 index 000000000..74722b8bd --- /dev/null +++ b/docshell/test/navigation/parent.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> +<body> +This document contains a frame. +<div><iframe src="blank.html"></iframe></div> +<script> +frames[0].name = window.name + "_child0"; +window.onload = function() { + opener.postMessage("ready", "*"); +}; +</script> +</body> +</html> + diff --git a/docshell/test/navigation/test_bug13871.html b/docshell/test/navigation/test_bug13871.html new file mode 100644 index 000000000..e0b563a4a --- /dev/null +++ b/docshell/test/navigation/test_bug13871.html @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="NavigationUtils.js"></script> + <style type="text/css"> + iframe { width: 90%; height: 50px; } + </style> +<script> +function runTest() { + navigateByLocation(window0.frames[0]); + navigateByOpen("window1_child0"); + navigateByForm("window2_child0"); + navigateByHyperlink("window3_child0"); + + xpcWaitForFinishedFrames(function() { + isInaccessible(window0.frames[0], "Should not be able to navigate off-domain frame by setting location."); + isInaccessible(window1.frames[0], "Should not be able to navigate off-domain frame by calling window.open."); + isInaccessible(window2.frames[0], "Should not be able to navigate off-domain frame by submitting form."); + isInaccessible(window3.frames[0], "Should not be able to navigate off-domain frame by targeted hyperlink."); + + window0.close(); + window1.close(); + window2.close(); + window3.close(); + + xpcCleanupWindows(); + SimpleTest.finish(); + }, 4); +} + +// Because our open()'d windows are cross-origin, we can't wait for onload. +// We instead wait for a postMessage from parent.html. +var windows = new Map(); +addEventListener("message", function windowLoaded(evt) { + // Because window.open spins the event loop in order to open new windows, + // we might receive the "ready" message before we call waitForLoad. + // In that case, windows won't contain evt.source and we just note that the + // window is ready. Otherwise, windows contains the "resolve" function for + // that window's promise and we just have to call it. + if (windows.has(evt.source)) { + windows.get(evt.source)(); + } else { + windows.set(evt.source, true); + } +}); + +var window0 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/parent.html", "window0", "width=10,height=10"); +var window1 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/parent.html", "window1", "width=10,height=10"); +var window2 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/parent.html", "window2", "width=10,height=10"); +var window3 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/parent.html", "window3", "width=10,height=10"); + +function waitForLoad(w) { + return new Promise(function(resolve, reject) { + // If we already got the "ready" message, resolve immediately. + if (windows.has(w)) { + resolve(); + } else { + windows.set(w, resolve); + } + }); +} + +Promise.all([ waitForLoad(window0), + waitForLoad(window1), + waitForLoad(window2), + waitForLoad(window3) ]) + .then(runTest); +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=13871">Mozilla Bug 13871</a> +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/navigation/test_bug270414.html b/docshell/test/navigation/test_bug270414.html new file mode 100644 index 000000000..98dcf42f8 --- /dev/null +++ b/docshell/test/navigation/test_bug270414.html @@ -0,0 +1,93 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="NavigationUtils.js"></script> + <style type="text/css"> + iframe { width: 90%; height: 50px; } + </style> +<script> +var headerHTML = "<html><head>" + + "<script src='/tests/SimpleTest/EventUtils.js'></scr" + "ipt>" + + "<script src='NavigationUtils.js'></scr" + "ipt>" + + "</head><body>"; +var footerHTML = "</body></html>"; + +function testChild0() { + if (!window.window0) { + window0 = window.open("", "window0", "width=10,height=10"); + window0.document.open(); + window0.document.write(headerHTML); + window0.document.write("<script>navigateByLocation(opener.frames[0])</scr" + "ipt>"); + window0.document.write(footerHTML); + window0.document.close(); + } +} + +function testChild1() { + if (!window.window1) { + window1 = window.open("", "window1", "width=10,height=10"); + window1.document.open(); + window1.document.write(headerHTML); + window1.document.write("<script>navigateByOpen('child1');</scr" + "ipt>"); + window1.document.write(footerHTML); + window1.document.close(); + } +} + +function testChild2() { + if (!window.window2) { + window2 = window.open("", "window2", "width=10,height=10"); + window2.document.open(); + window2.document.write(headerHTML); + window2.document.write("<script>navigateByForm('child2');</scr" + "ipt>"); + window2.document.write(footerHTML); + window2.document.close(); + } +} + +function testChild3() { + if (!window.window3) { + window3 = window.open("", "window3", "width=10,height=10"); + window3.document.open(); + window3.document.write(headerHTML); + window3.document.write("<script>navigateByHyperlink('child3');</scr" + "ipt>"); + window3.document.write(footerHTML); + window3.document.close(); + } +} + +xpcWaitForFinishedFrames(function() { + isNavigated(frames[0], "Should be able to navigate on-domain opener's children by setting location."); + isNavigated(frames[1], "Should be able to navigate on-domain opener's children by calling window.open."); + isNavigated(frames[2], "Should be able to navigate on-domain opener's children by submitting form."); + isNavigated(frames[3], "Should be able to navigate on-domain opener's children by targeted hyperlink."); + + window0.close(); + window1.close(); + window2.close(); + window3.close(); + + xpcCleanupWindows(); + SimpleTest.finish(); +}, 4); + +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=270414">Mozilla Bug 270414</a> +<div id="frames"> +<iframe onload="testChild0();" name="child0" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe> +<iframe onload="testChild1();" name="child1" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe> +<iframe onload="testChild2();" name="child2" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe> +<iframe onload="testChild3();" name="child3" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe> +</div> +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/navigation/test_bug278916.html b/docshell/test/navigation/test_bug278916.html new file mode 100644 index 000000000..3ad6a9666 --- /dev/null +++ b/docshell/test/navigation/test_bug278916.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="NavigationUtils.js"></script> +<script> +window.onload = function () { + document.getElementById("link0").href = target_url; + sendMouseEvent({type:"click"}, "link0"); + + xpcWaitForFinishedFrames(function() { + var array_of_frames = xpcGetFramesByName("window0"); + is(array_of_frames.length, 1, "Should only open one window using a fancy hyperlink."); + + for (var i=0; i < array_of_frames.length; ++i) + array_of_frames[i].close(); + + xpcCleanupWindows(); + SimpleTest.finish(); + }, 1); +} +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=278916">Mozilla Bug 278916</a> +<div id="links"> +<a id="link0" target="window0" onclick="window.open('', 'window0', 'width=10,height=10');">This is a fancy hyperlink</a> +</div> +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/navigation/test_bug279495.html b/docshell/test/navigation/test_bug279495.html new file mode 100644 index 000000000..79a169567 --- /dev/null +++ b/docshell/test/navigation/test_bug279495.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="NavigationUtils.js"></script> +<script> +window.onload = function () { + document.getElementById("link0").href = target_url; + document.getElementById("link1").href = target_url; + + sendMouseEvent({type:"click"}, "link0"); + sendMouseEvent({type:"click"}, "link1"); + + xpcWaitForFinishedFrames(function() { + countAndClose("window0", 1); + countAndClose("window1", 1); + + xpcCleanupWindows(); + SimpleTest.finish(); + }, 2); +} + +function countAndClose(name, expected_count) { + var array_of_frames = xpcGetFramesByName(name); + is(array_of_frames.length, expected_count, + "Should only open " + expected_count + + " window(s) with name " + name + " using a fancy hyperlink."); + + for (var i=0; i < array_of_frames.length; ++i) + array_of_frames[i].close(); +} +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=279495">Mozilla Bug 279495</a> +<div id="links"> +<a id="link0" target="window0" onclick="window.open('blank.html', 'window0', 'width=10,height=10');">This is a fancy hyperlink</a> +<a id="link1" target="window1" onclick="window.open('http://test1.example.org:80/tests/docshell/test/navigation/blank.html', 'window1', 'width=10,height=10');">This is a fancy hyperlink</a> +</div> +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/navigation/test_bug344861.html b/docshell/test/navigation/test_bug344861.html new file mode 100644 index 000000000..f9759751c --- /dev/null +++ b/docshell/test/navigation/test_bug344861.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=344861 +--> +<head> + <title>Test for Bug 344861</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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=344861">Mozilla Bug 344861</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 344861 **/ +SimpleTest.waitForExplicitFinish(); + +var newwindow = window.open("/", "testwindow", "width=200,height=200"); +newwindow.onload = function() { + is(newwindow.innerHeight, 200, "window.open has correct height dimensions"); + is(newwindow.innerWidth, 200, "window.open has correct width dimensions"); + SimpleTest.finish(); + newwindow.close(); +} +</script> +</pre> +</body> +</html> + + diff --git a/docshell/test/navigation/test_bug386782.html b/docshell/test/navigation/test_bug386782.html new file mode 100644 index 000000000..2434963f1 --- /dev/null +++ b/docshell/test/navigation/test_bug386782.html @@ -0,0 +1,137 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=386782 +--> +<head> + <title>Test for Bug 386782</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + + <script> + + // This tests if we can load a document whose root is in designMode, + // edit it, navigate to a new page, navigate back, still edit, and still + // undo/redo. Note that this is different from the case where the + // designMode document is in a frame inside the window, as this means + // the editable region is not in the root docshell (a less complicated case). + + var pageShowChecker = '<scr' + 'ipt>' + + 'window.addEventListener("pageshow", function(event) {' + + 'window.opener.postMessage({persisted:event.persisted}, "*");' + + '});</scr' + 'ipt>'; + + var gTests = [ + { + // <html><body><p>designModeDocument</p></body></html> + url: "data:text/html;charset=utf-8,<html><head>" + pageShowChecker + "</head><body><p>designModeDocument</p></body></html>", + name: 'designModeNavigate', + onload(doc) { doc.designMode = "on"; }, + expectedBodyBeforeEdit: '<p>designModeDocument</p>', + expectedBodyAfterEdit: '<p>EDITED designModeDocument</p>', + expectedBodyAfterSecondEdit: '<p>EDITED TWICE designModeDocument</p>', + }, + { + // <html><body contentEditable="true"><p>contentEditable</p></body></html> + url: "data:text/html;charset=utf-8,<html><head>" + pageShowChecker + "</head><body contentEditable=\"true\"><p>contentEditable</p></body></html>", + name: 'contentEditableNavigate', + expectedBodyBeforeEdit: '<p>contentEditable</p>', + expectedBodyAfterEdit: 'EDITED <br><p>contentEditable</p>', + expectedBodyAfterSecondEdit: 'EDITED TWICE <br><p>contentEditable</p>', + } + ]; + + var gTestNum = -1; + var gTest = null; + + window.onload = goNext(); + + function goNext() { + gTestNum++; + if (gTestNum >= gTests.length) { + SimpleTest.finish(); + return; + } + gTest = gTests[gTestNum]; + gTest.window = window.open(gTest.url, gTest.name, "width=500,height=500"); + window.onmessage = function(e) { + is(e.data.persisted, false, "Initial load cannot be persisted"); + window.onmessage = null; + if ("onload" in gTest) { + gTest.onload(gTest.window.document); + } + SimpleTest.waitForFocus(beginTest, gTest.window); + }; + } + + function beginTest() { + gTest.window.document.body.focus(); + + // WARNING: If the following test fails, give the setTimeout() in the onload() + // a bit longer; the doc hasn't had enough time to setup its editor. + is(gTest.window.document.body.innerHTML, gTest.expectedBodyBeforeEdit, "Is doc setup yet"); + sendString('EDITED ', gTest.window); + is(gTest.window.document.body.innerHTML, gTest.expectedBodyAfterEdit, "Editing failed."); + + gTest.window.location = 'data:text/html;charset=utf-8,SomeOtherDocument'; + SimpleTest.waitForFocus(goBack, gTest.window); + } + + function goBack() { + window.onmessage = function(e) { + window.onmessage = null; + // Skip the test if the page is not loaded from the bf-cache when going back. + if (e.data.persisted) { + checkStillEditable(); + } else { + gTest.window.close(); + goNext(); + } + }; + gTest.window.history.back(); + } + + function checkStillEditable() { + + // Check that the contents are correct. + is(gTest.window.document.body.innerHTML, gTest.expectedBodyAfterEdit, "Edited contents still correct?"); + + // Check that we can undo/redo and the contents are correct. + gTest.window.document.execCommand("undo", false, null); + is(gTest.window.document.body.innerHTML, gTest.expectedBodyBeforeEdit, "Can we undo?"); + + gTest.window.document.execCommand("redo", false, null); + is(gTest.window.document.body.innerHTML, gTest.expectedBodyAfterEdit, "Can we redo?"); + + // Check that we can still edit the page. + gTest.window.document.body.focus(); + sendString('TWICE ', gTest.window); + is(gTest.window.document.body.innerHTML, gTest.expectedBodyAfterSecondEdit, "Can we still edit?"); + + gTest.window.close(); + goNext(); + + } + + </script> + +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=386782">Mozilla Bug 386782</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 386782 **/ + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> + diff --git a/docshell/test/navigation/test_bug430624.html b/docshell/test/navigation/test_bug430624.html new file mode 100644 index 000000000..9fc92e4cd --- /dev/null +++ b/docshell/test/navigation/test_bug430624.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=430624 +--> +<head> + <title>Test for Bug 430624</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=430624">Mozilla Bug 430624</a> +<p id="display"></p> + + + +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 430624 **/ + +function onLoad() { + window.frames[0].frameElement.onload = onReload; + window.frames[0].location = window.frames[0].location; +} + +function onReload() { + var iframe = window.frames[0].frameElement; + SimpleTest.waitForFocus(doTest, iframe.contentWindow); + iframe.contentDocument.body.focus(); +} + +function doTest() { + var bodyElement = window.frames[0].frameElement.contentDocument.body; + bodyElement.focus(); + sendString('Still ', window.frames[0].frameElement.contentWindow); + + is(bodyElement.innerHTML, "Still contentEditable", "Check we're contentEditable after reload"); + + SimpleTest.finish(); +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> + +<iframe onload="onLoad()" src="data:text/html;charset=utf-8,<body contenteditable>contentEditable</body>"></iframe> + +</body> +</html> + diff --git a/docshell/test/navigation/test_bug430723.html b/docshell/test/navigation/test_bug430723.html new file mode 100644 index 000000000..eb53e0d22 --- /dev/null +++ b/docshell/test/navigation/test_bug430723.html @@ -0,0 +1,138 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=430723 +--> +<head> + <title>Test for Bug 430723</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/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=430723">Mozilla Bug 430723</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +//<![CDATA[ + +/** Test for Bug 430723 **/ + +var gTallRedBoxURI = "data:text/html;charset=utf-8;base64,PGh0bWw%2BPGhlYWQ%2BPHNjcmlwdD53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcigncGFnZXNob3cnLCBmdW5jdGlvbigpe29wZW5lci5uZXh0VGVzdCgpO30sIGZhbHNlKTs8L3NjcmlwdD48L2hlYWQ%2BPGJvZHk%2BPGRpdiBzdHlsZT0icG9zaXRpb246YWJzb2x1dGU7IGxlZnQ6MHB4OyB0b3A6MHB4OyB3aWR0aDo1MCU7IGhlaWdodDoxNTAlOyBiYWNrZ3JvdW5kLWNvbG9yOnJlZCI%2BPHA%2BVGhpcyBpcyBhIHZlcnkgdGFsbCByZWQgYm94LjwvcD48L2Rpdj48L2JvZHk%2BPC9odG1sPg%3D%3D"; +// <html><head> +// < script > window.addEventListener("pageshow", function(){opener.nextTest();}, false); < /script > +// </head><body> +// <div style="position:absolute; left:0px; top:0px; width:50%; height:150%; background-color:red"> +// <p>This is a very tall red box.</p> +// </div></body></html> + +var gTallBlueBoxURI = "data:text/html;charset=utf-8;base64,PGh0bWw%2BPGhlYWQ%2BPHNjcmlwdD53aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcigncGFnZXNob3cnLCBmdW5jdGlvbigpe29wZW5lci5uZXh0VGVzdCgpO30sIGZhbHNlKTs8L3NjcmlwdD48L2hlYWQ%2BPGJvZHk%2BPGRpdiBzdHlsZT0icG9zaXRpb246YWJzb2x1dGU7IGxlZnQ6MHB4OyB0b3A6MHB4OyB3aWR0aDo1MCU7IGhlaWdodDoxNTAlOyBiYWNrZ3JvdW5kLWNvbG9yOmJsdWUiPjxwPlRoaXMgaXMgYSB2ZXJ5IHRhbGwgYmx1ZSBib3guPC9wPjwvZGl2PjwvYm9keT48L2h0bWw%2B"; +// <html><head> +// < script > window.addEventListener("pageshow", function(){opener.nextTest();}, false); < /script > +// </head><body> +// <div style="position:absolute; left:0px; top:0px; width:50%; height:150%; background-color:blue"> +// <p>This is a very tall blue box.</p> +// </div></body></html> + +window.onload = runTest; + +var testWindow; +var testNum = 0; + +var smoothScrollPref = "general.smoothScroll"; +function runTest() { + SpecialPowers.pushPrefEnv({"set":[[smoothScrollPref, false]]}, function(){ + testWindow = window.open(gTallRedBoxURI, "testWindow", "width=300,height=300,location=yes,scrollbars=yes"); + }); +} + +var nextTest =function() { + testNum++; + switch (testNum) { + case 1: setTimeout(step1, 0); break; + case 2: setTimeout(step2, 0); break; + case 3: setTimeout(step3, 0); break; + }; +} + +var step1 =function() { + window.is(String(testWindow.location), gTallRedBoxURI, "Ensure red page loaded."); + + // Navigate down and up. + is(testWindow.document.body.scrollTop, 0, + "Page1: Ensure the scrollpane is at the top before we start scrolling."); + testWindow.addEventListener("scroll", function () { + testWindow.removeEventListener("scroll", arguments.callee, true); + isnot(testWindow.document.body.scrollTop, 0, + "Page1: Ensure we can scroll down."); + SimpleTest.executeSoon(step1_2); + }, true); + sendKey('DOWN', testWindow); + + function step1_2() { + testWindow.addEventListener("scroll", function () { + testWindow.removeEventListener("scroll", arguments.callee, true); + is(testWindow.document.body.scrollTop, 0, + "Page1: Ensure we can scroll up, back to the top."); + + // Nav to blue box page. This should fire step2. + testWindow.location = gTallBlueBoxURI; + }, true); + sendKey('UP', testWindow); + } +} + + +var step2 =function() { + window.is(String(testWindow.location), gTallBlueBoxURI, "Ensure blue page loaded."); + + // Scroll around a bit. + is(testWindow.document.body.scrollTop, 0, + "Page2: Ensure the scrollpane is at the top before we start scrolling."); + + var count = 0; + testWindow.addEventListener("scroll", function () { + if (++count < 2) { + SimpleTest.executeSoon(function () { sendKey('DOWN', testWindow); }); + } else { + testWindow.removeEventListener("scroll", arguments.callee, true); + + isnot(testWindow.document.body.scrollTop, 0, + "Page2: Ensure we could scroll."); + + // Navigate backwards. This should fire step3. + testWindow.history.back(); + } + }, true); + sendKey('DOWN', testWindow); +} + +var step3 =function() { + window.is(String(testWindow.location), gTallRedBoxURI, + "Ensure red page restored from history."); + + // Check we can still scroll with the keys. + is(testWindow.document.body.scrollTop, 0, + "Page1Again: Ensure scroll pane at top before we scroll."); + testWindow.addEventListener("scroll", function () { + testWindow.removeEventListener("scroll", arguments.callee, true); + + isnot(testWindow.document.body.scrollTop, 0, + "Page2Again: Ensure we can still scroll."); + + testWindow.close(); + window.SimpleTest.finish(); + }, true); + sendKey('DOWN', testWindow); +} + +SimpleTest.waitForExplicitFinish(); + +//]]> +</script> +</pre> +</body> +</html> diff --git a/docshell/test/navigation/test_child.html b/docshell/test/navigation/test_child.html new file mode 100644 index 000000000..c43e1a6cf --- /dev/null +++ b/docshell/test/navigation/test_child.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="NavigationUtils.js"></script> + <style type="text/css"> + iframe { width: 90%; height: 50px; } + </style> +<script> +if (!navigator.platform.startsWith("Win")) { + SimpleTest.expectAssertions(0, 1); +} + +window.onload = function() { + navigateByLocation(frames[0]); + navigateByOpen("child1"); + navigateByForm("child2"); + navigateByHyperlink("child3"); + + xpcWaitForFinishedFrames(function() { + isNavigated(frames[0], "Should be able to navigate off-domain child by setting location."); + isNavigated(frames[1], "Should be able to navigate off-domain child by calling window.open."); + isNavigated(frames[2], "Should be able to navigate off-domain child by submitting form."); + isNavigated(frames[3], "Should be able to navigate off-domain child by targeted hyperlink."); + + xpcCleanupWindows(); + SimpleTest.finish(); + }, 4); +} +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a> +<div id="frames"> +<iframe name="child0" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe> +<iframe name="child1" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe> +<iframe name="child2" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe> +<iframe name="child3" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe> +</div> +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/navigation/test_grandchild.html b/docshell/test/navigation/test_grandchild.html new file mode 100644 index 000000000..439145b3e --- /dev/null +++ b/docshell/test/navigation/test_grandchild.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="NavigationUtils.js"></script> + <style type="text/css"> + iframe { width: 90%; height: 200px; } + </style> +<script> +if (!navigator.platform.startsWith("Win")) { + SimpleTest.expectAssertions(0, 1); +} + +window.onload = function () { + navigateByLocation(frames[0].frames[0]); + navigateByOpen("child1_child0"); + navigateByForm("child2_child0"); + navigateByHyperlink("child3_child0"); + + xpcWaitForFinishedFrames(function() { + isNavigated(frames[0].frames[0], "Should be able to navigate off-domain grandchild by setting location."); + isNavigated(frames[1].frames[0], "Should be able to navigate off-domain grandchild by calling window.open."); + isNavigated(frames[2].frames[0], "Should be able to navigate off-domain grandchild by submitting form."); + isNavigated(frames[3].frames[0], "Should be able to navigate off-domain grandchild by targeted hyperlink."); + + xpcCleanupWindows(); + SimpleTest.finish(); + }, 4); +} +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a> +<div id="frames"> +<iframe name="child0" src="http://test1.example.org:80/tests/docshell/test/navigation/parent.html"></iframe> +<iframe name="child1" src="http://test1.example.org:80/tests/docshell/test/navigation/parent.html"></iframe> +<iframe name="child2" src="http://test1.example.org:80/tests/docshell/test/navigation/parent.html"></iframe> +<iframe name="child3" src="http://test1.example.org:80/tests/docshell/test/navigation/parent.html"></iframe> +</div> +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/navigation/test_not-opener.html b/docshell/test/navigation/test_not-opener.html new file mode 100644 index 000000000..d778f71d9 --- /dev/null +++ b/docshell/test/navigation/test_not-opener.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="NavigationUtils.js"></script> + <style type="text/css"> + iframe { width: 90%; height: 50px; } + </style> +<script> +if (!navigator.platform.startsWith("Win")) { + SimpleTest.expectAssertions(0, 1); +} + +window.onload = function () { + //navigateByLocation(window0); // Don't have a handle to the window. + navigateByOpen("window1"); + navigateByForm("window2"); + navigateByHyperlink("window3"); + + xpcWaitForFinishedFrames(function() { + is(xpcGetFramesByName("window1").length, 2, "Should not be able to navigate popup's popup by calling window.open."); + is(xpcGetFramesByName("window2").length, 2, "Should not be able to navigate popup's popup by submitting form."); + is(xpcGetFramesByName("window3").length, 2, "Should not be able to navigate popup's popup by targeted hyperlink."); + + //opener0.close(); + opener1.close(); + opener2.close(); + opener3.close(); + + xpcCleanupWindows(); + SimpleTest.finish(); + }, 6); +} + +//opener0 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/open.html#window0", "_blank", "width=10,height=10"); +opener1 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/open.html#window1", "_blank", "width=10,height=10"); +opener2 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/open.html#window2", "_blank", "width=10,height=10"); +opener3 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/open.html#window3", "_blank", "width=10,height=10"); +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a> +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/navigation/test_opener.html b/docshell/test/navigation/test_opener.html new file mode 100644 index 000000000..bfb1dde9f --- /dev/null +++ b/docshell/test/navigation/test_opener.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="NavigationUtils.js"></script> + <style type="text/css"> + iframe { width: 90%; height: 50px; } + </style> +<script> +if (navigator.platform.startsWith("Linux")) { + SimpleTest.expectAssertions(0, 1); +} + +window.onload = function () { + navigateByLocation(window0); + navigateByOpen("window1"); + navigateByForm("window2"); + navigateByHyperlink("window3"); + + xpcWaitForFinishedFrames(function() { + isNavigated(window0, "Should be able to navigate popup by setting location."); + isNavigated(window1, "Should be able to navigate popup by calling window.open."); + isNavigated(window2, "Should be able to navigate popup by submitting form."); + isNavigated(window3, "Should be able to navigate popup by targeted hyperlink."); + + window0.close(); + window1.close(); + window2.close(); + window3.close(); + + xpcCleanupWindows(); + SimpleTest.finish(); + }, 4); +} + +var window0 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/blank.html", "window0", "width=10,height=10"); +var window1 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/blank.html", "window1", "width=10,height=10"); +var window2 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/blank.html", "window2", "width=10,height=10"); +var window3 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/blank.html", "window3", "width=10,height=10"); +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a> +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/navigation/test_popup-navigates-children.html b/docshell/test/navigation/test_popup-navigates-children.html new file mode 100644 index 000000000..692f35713 --- /dev/null +++ b/docshell/test/navigation/test_popup-navigates-children.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="NavigationUtils.js"></script> + <style type="text/css"> + iframe { width: 90%; height: 50px; } + </style> +<script> +function testChild0() { + if (!window.window0) + window0 = window.open("navigate.html#opener.frames[0],location", "window0", "width=10,height=10"); +} + +function testChild1() { + if (!window.window1) + window1 = window.open("navigate.html#child1,open", "window1", "width=10,height=10"); +} + +function testChild2() { + if (!window.window2) + window2 = window.open("navigate.html#child2,form", "window2", "width=10,height=10"); +} + +function testChild3() { + if (!window.window3) + window3 = window.open("navigate.html#child3,hyperlink", "window3", "width=10,height=10"); +} + +xpcWaitForFinishedFrames(function() { + isNavigated(frames[0], "Should be able to navigate on-domain opener's children by setting location."); + isNavigated(frames[1], "Should be able to navigate on-domain opener's children by calling window.open."); + isNavigated(frames[2], "Should be able to navigate on-domain opener's children by submitting form."); + isNavigated(frames[3], "Should be able to navigate on-domain opener's children by targeted hyperlink."); + + window0.close(); + window1.close(); + window2.close(); + window3.close(); + + xpcCleanupWindows(); + SimpleTest.finish(); +}, 4); + +</script> +</head> +<body> +<div id="frames"> +<iframe onload="testChild0()" name="child0" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe> +<iframe onload="testChild1()" name="child1" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe> +<iframe onload="testChild2()" name="child2" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe> +<iframe onload="testChild3()" name="child3" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe> +</div> +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/navigation/test_reserved.html b/docshell/test/navigation/test_reserved.html new file mode 100644 index 000000000..b2389078c --- /dev/null +++ b/docshell/test/navigation/test_reserved.html @@ -0,0 +1,100 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="NavigationUtils.js"></script> + <style type="text/css"> + iframe { width: 90%; height: 200px; } + </style> +<script> +if (navigator.platform.startsWith("Mac")) { + SimpleTest.expectAssertions(0, 2); +} + +function testTop() { + window0 = window.open("iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#top,location", "_blank", "width=10,height=10"); + + xpcWaitForFinishedFrames(function() { + isInaccessible(window0, "Should be able to navigate off-domain top by setting location."); + window0.close(); + xpcCleanupWindows(); + + window1 = window.open("iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_top,open", "_blank", "width=10,height=10"); + + xpcWaitForFinishedFrames(function() { + isInaccessible(window1, "Should be able to navigate off-domain top by calling window.open."); + window1.close(); + xpcCleanupWindows(); + + window2 = window.open("iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_top,form", "_blank", "width=10,height=10"); + + xpcWaitForFinishedFrames(function() { + isInaccessible(window2, "Should be able to navigate off-domain top by submitting form."); + window2.close(); + xpcCleanupWindows(); + + window3 = window.open("iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_top,hyperlink", "_blank", "width=10,height=10"); + + xpcWaitForFinishedFrames(function() { + isInaccessible(window3, "Should be able to navigate off-domain top by targeted hyperlink."); + window3.close(); + xpcCleanupWindows(); + + testParent(); + }, 1); + }, 1); + }, 1); + }, 1); +} + +function testParent() { + document.getElementById("frames").innerHTML = '<iframe src="iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#parent,location"></iframe>'; + + xpcWaitForFinishedFrames(function() { + isAccessible(frames[0], "Should not be able to navigate off-domain parent by setting location."); + xpcCleanupWindows(); + + document.getElementById("frames").innerHTML = '<iframe src="iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_parent,open"></iframe>'; + + xpcWaitForFinishedFrames(function() { + isAccessible(frames[0], "Should not be able to navigate off-domain parent by calling window.open."); + xpcCleanupWindows(); + + document.getElementById("frames").innerHTML = '<iframe src="iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_parent,form"></iframe>'; + + xpcWaitForFinishedFrames(function() { + isAccessible(frames[0], "Should not be able to navigate off-domain parent by submitting form."); + xpcCleanupWindows(); + + document.getElementById("frames").innerHTML = '<iframe src="iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_parent,hyperlink"></iframe>'; + + xpcWaitForFinishedFrames(function() { + isAccessible(frames[0], "Should not be able to navigate off-domain parent by targeted hyperlink."); + xpcCleanupWindows(); + + document.getElementById("frames").innerHTML = ""; + SimpleTest.finish(); + }, 1); + }, 1); + }, 1); + }, 1); +} + +window.onload = function() { + testTop(); +} +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a> +<div id="frames"> +</div> +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/navigation/test_sessionhistory.html b/docshell/test/navigation/test_sessionhistory.html new file mode 100644 index 000000000..452271a41 --- /dev/null +++ b/docshell/test/navigation/test_sessionhistory.html @@ -0,0 +1,68 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id= +--> +<head> + <title>Test for Bug </title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="nextTest()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug **/ + +var testFiles = + [ "file_bug462076_1.html", // Dynamic frames before onload + "file_bug462076_2.html", // Dynamic frames when handling onload + "file_bug462076_3.html", // Dynamic frames after onload + "file_bug508537_1.html", // Dynamic frames and forward-back + "file_document_write_1.html", // Session history + document.write + //"file_static_and_dynamic_1.html",// Static and dynamic frames and forward-back + "file_bug534178.html", // Session history transaction clean-up. + "file_fragment_handling_during_load.html", + "file_nested_frames.html", + "file_shiftReload_and_pushState.html", + "file_scrollRestoration.html", + "file_bug1300461.html" + ]; +var testCount = 0; // Used by the test files. + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); + +var testWindow; +function nextTest_() { + if (testFiles.length) { + testCount = 0; + testWindow = window.open(testFiles.shift(), "", "width=300,height=300"); + testWindow.onunload = function () { } // to prevent bfcache + } else { + SimpleTest.finish(); + } +} + +// Needed by file_document_write_1.html +window.file_document_write_1_loadCount = 0; +function isTestDynamic() { + var dyn = testWindow.document.getElementById("dynamic"); + is(dyn, null, "Should have gone back to the static page!"); + nextTest(); + testWindow.close(); +} + +function nextTest() { + setTimeout(nextTest_, 0); +} + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/navigation/test_sibling-matching-parent.html b/docshell/test/navigation/test_sibling-matching-parent.html new file mode 100644 index 000000000..dc8ec2f90 --- /dev/null +++ b/docshell/test/navigation/test_sibling-matching-parent.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="NavigationUtils.js"></script> + <style type="text/css"> + iframe { width: 90%; height: 50px; } + </style> +<script> +window.onload = function () { + document.getElementById('active').innerHTML = + '<iframe src="navigate.html#parent.frames[0],location"></iframe>' + + '<iframe src="navigate.html#child1,open"></iframe>' + + '<iframe src="navigate.html#child2,form"></iframe>' + + '<iframe src="navigate.html#child3,hyperlink"></iframe>'; + + xpcWaitForFinishedFrames(function() { + isNavigated(frames[0], "Should be able to navigate sibling with on-domain parent by setting location."); + isNavigated(frames[1], "Should be able to navigate sibling with on-domain parent by calling window.open."); + isNavigated(frames[2], "Should be able to navigate sibling with on-domain parent by submitting form."); + isNavigated(frames[3], "Should be able to navigate sibling with on-domain parent by targeted hyperlink."); + + xpcCleanupWindows(); + SimpleTest.finish(); + }, 4); +} +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a> +<div id="frames"> +<iframe name="child0" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe> +<iframe name="child1" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe> +<iframe name="child2" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe> +<iframe name="child3" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe> +</div> +<div id="active"></div> +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/navigation/test_sibling-off-domain.html b/docshell/test/navigation/test_sibling-off-domain.html new file mode 100644 index 000000000..ba7942798 --- /dev/null +++ b/docshell/test/navigation/test_sibling-off-domain.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="NavigationUtils.js"></script> + <style type="text/css"> + iframe { width: 90%; height: 50px; } + </style> +<script> +window.onload = function () { + document.getElementById('active').innerHTML = + '<iframe src="http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#parent.frames[0],location"></iframe>' + + '<iframe src="http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#child1,open"></iframe>' + + '<iframe src="http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#child2,form"></iframe>' + + '<iframe src="http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#child3,hyperlink"></iframe>'; + + xpcWaitForFinishedFrames(function() { + isBlank(frames[0], "Should not be able to navigate off-domain sibling by setting location."); + isBlank(frames[1], "Should not be able to navigate off-domain sibling by calling window.open."); + isBlank(frames[2], "Should not be able to navigate off-domain sibling by submitting form."); + isBlank(frames[3], "Should not be able to navigate off-domain sibling by targeted hyperlink."); + + xpcCleanupWindows(); + SimpleTest.finish(); + }, 4); +} +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a> +<div id="frames"> +<iframe name="child0" src="blank.html"></iframe> +<iframe name="child1" src="blank.html"></iframe> +<iframe name="child2" src="blank.html"></iframe> +<iframe name="child3" src="blank.html"></iframe> +</div> +<div id="active"></div> +<pre id="test"> +<script type="text/javascript"> +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/navigation/test_triggeringprincipal_frame_nav.html b/docshell/test/navigation/test_triggeringprincipal_frame_nav.html new file mode 100644 index 000000000..f8f97c678 --- /dev/null +++ b/docshell/test/navigation/test_triggeringprincipal_frame_nav.html @@ -0,0 +1,69 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1181370 - Test triggeringPrincipal for iframe navigations</title> + <!-- Including SimpleTest.js so we can use waitForExplicitFinish !--> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<iframe style="width:100%;" id="testframe1"></iframe> +<iframe style="width:100%;" id="testframe2"></iframe> + +<script class="testbody" type="text/javascript"> + +/* Description of the test: + * + * +------------------------------------+ + * | +----------+ +--------------+ | + * | | Frame 1 | | Frame 2 | | + * | +----------+ | | | + * | | +----------+ | | + * | | | Subframe | | | + * | | +----------+ | | + * | +--------------+ | + * +------------------------------------+ + * + * Frame1: test1.mochi.test + * Frame2: test2.mochi.test + * Subframe: test2.mochi.test + * + * (*) Frame1 and Subframe set their document.domain to mochi.test + * (*) Frame1 navigates the Subframe + * (*) TriggeringPrincipal for the Subframe navigation should be + * ==> test1.mochi.test + * (*) LoadingPrincipal for the Subframe navigation should be + * ==> test2.mochi.test + */ + +const BASEURL1 = "http://test1.mochi.test:8888/tests/docshell/test/navigation/"; +const BASEURL2 = "http://test2.mochi.test:8888/tests/docshell/test/navigation/"; +const TRIGGERINGPRINCIPALURI = BASEURL1 + "file_triggeringprincipal_frame_1.html"; +const LOADINGPRINCIPALURI = BASEURL2 + "file_triggeringprincipal_frame_2.html"; + +SimpleTest.waitForExplicitFinish(); + +window.addEventListener("message", receiveMessage, false); + +function receiveMessage(event) { + is(event.data.triggeringPrincipalURI, TRIGGERINGPRINCIPALURI, + "TriggeringPrincipal should be the navigating iframe (Frame 1)"); + is(event.data.loadingPrincipalURI, LOADINGPRINCIPALURI, + "LoadingPrincipal should be the enclosing iframe (Frame 2)"); + is(event.data.referrerURI, TRIGGERINGPRINCIPALURI, + "Referrer and TriggeringPrincipal should be identical (Frame 1)"); + + window.removeEventListener("message", receiveMessage, false); + SimpleTest.finish(); +} + +var frame1 = document.getElementById("testframe1"); +frame1.src = BASEURL1 + "file_triggeringprincipal_frame_1.html"; + +var frame2 = document.getElementById("testframe2"); +frame2.src = BASEURL2 + "file_triggeringprincipal_frame_2.html"; + +</script> +</body> +</html> diff --git a/docshell/test/navigation/test_triggeringprincipal_iframe_iframe_window_open.html b/docshell/test/navigation/test_triggeringprincipal_iframe_iframe_window_open.html new file mode 100644 index 000000000..cd6a9c056 --- /dev/null +++ b/docshell/test/navigation/test_triggeringprincipal_iframe_iframe_window_open.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="NavigationUtils.js"></script> +</head> +<body> + +<iframe name="framea" id="framea" src="file_triggeringprincipal_iframe_iframe_window_open_frame_a.html"></iframe> +<iframe name="frameb" id="frameb"></iframe> + +<script type="text/javascript"> + +/* We load an iframe (Frame A) which then gets navigated by another iframe (Frame B) + * by calling window.open("http://", "Frame A") later in the test. We then verify the + * TriggeringPrincipal and LoadingPrincipal of the navigated iframe (Frame A). + * + * +---------------------------------------+ + * | Parent | + * | | + * | +----------------------------+ | + * | | Frame A | | + * | | | | + * | | | | + * | +----------------------------+ | + * | | + * | +----------------------------+ | + * | | Frame B | | + * | | | | + * | | win.open("http://", "A") | | + * | +----------------------------+ | + * | | + * +---------------------------------------+ + * + * Sequence of the test: + * [1] load Frame A + * [2] load Frame B which navigates A + * [3] load navigated Frame A and check triggeringPrincipal and loadingPrincipal + */ + +const TRIGGERING_PRINCIPAL_URI = + "http://mochi.test:8888/tests/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_b.html"; + +const LOADING_PRINCIPAL_URI = + "http://mochi.test:8888/tests/docshell/test/navigation/test_triggeringprincipal_iframe_iframe_window_open.html"; + +var frameA = document.getElementById("framea"); + +function checkResults() { + frameA.removeEventListener('load', checkResults, false); + + var channel = SpecialPowers.wrap(frameA.contentDocument).docShell.currentDocumentChannel; + var triggeringPrincipal = channel.loadInfo.triggeringPrincipal.URI.asciiSpec; + var loadingPrincipal = channel.loadInfo.loadingPrincipal.URI.asciiSpec; + + is(triggeringPrincipal, TRIGGERING_PRINCIPAL_URI, + "TriggeringPrincipal for targeted window.open() should be the iframe triggering the load"); + + is(frameA.contentDocument.referrer, TRIGGERING_PRINCIPAL_URI, + "Referrer for targeted window.open() should be the principal of the iframe triggering the load"); + + is(loadingPrincipal, LOADING_PRINCIPAL_URI, + "LoadingPrincipal for targeted window.open() should be the containing document"); + + SimpleTest.finish(); +} + +function performNavigation() { + frameA.removeEventListener('load', performNavigation, false); + frameA.addEventListener('load', checkResults, false); + + // load Frame B which then navigates Frame A + var frameB = document.getElementById("frameb"); + frameB.src = "file_triggeringprincipal_iframe_iframe_window_open_frame_b.html"; +} + +// start the test +SimpleTest.waitForExplicitFinish(); + +frameA.addEventListener('load', performNavigation, false); + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/navigation/test_triggeringprincipal_parent_iframe_window_open.html b/docshell/test/navigation/test_triggeringprincipal_parent_iframe_window_open.html new file mode 100644 index 000000000..7cc6ee97d --- /dev/null +++ b/docshell/test/navigation/test_triggeringprincipal_parent_iframe_window_open.html @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="NavigationUtils.js"></script> +</head> +<body> + +<iframe name="testframe" id="testframe" src="file_triggeringprincipal_iframe_iframe_window_open_base.html"></iframe> + +<script type="text/javascript"> + +/* We load an iframe which then gets navigated by the iframe's parent by calling + * window.open("http://", iframe) later in the test. We then verify the + * TriggeringPrincipal and LoadingPrincipal of the navigated iframe. + * + * +------------------------------------------+ + * | | + * | +------------------+ | + * | | testframe | | + * | +------------------+ | + * | | + * | window.open("http://", "testframe"); | + * | | + * +------------------------------------------+ + */ + +const TRIGGERING_PRINCIPAL_URI = + "http://mochi.test:8888/tests/docshell/test/navigation/test_triggeringprincipal_parent_iframe_window_open.html"; + +const LOADING_PRINCIPAL_URI = TRIGGERING_PRINCIPAL_URI; + +var testframe = document.getElementById("testframe"); + +function checkResults() { + testframe.removeEventListener('load', checkResults, false); + + var channel = SpecialPowers.wrap(testframe.contentDocument).docShell.currentDocumentChannel; + var triggeringPrincipal = channel.loadInfo.triggeringPrincipal.URI.asciiSpec; + var loadingPrincipal = channel.loadInfo.loadingPrincipal.URI.asciiSpec; + + is(triggeringPrincipal, TRIGGERING_PRINCIPAL_URI, + "TriggeringPrincipal for targeted window.open() should be the principal of the document"); + + is(testframe.contentDocument.referrer, TRIGGERING_PRINCIPAL_URI, + "Referrer for targeted window.open() should be the principal of the document"); + + is(loadingPrincipal, LOADING_PRINCIPAL_URI, + "LoadingPrincipal for targeted window.open() should be the <iframe>.ownerDocument"); + + SimpleTest.finish(); +} + +function performNavigation() { + testframe.removeEventListener('load', performNavigation, false); + testframe.addEventListener('load', checkResults, false); + win = window.open("file_triggeringprincipal_parent_iframe_window_open_nav.html", "testframe"); +} + +// start the test +SimpleTest.waitForExplicitFinish(); + +testframe.addEventListener('load', performNavigation, false); + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/navigation/test_triggeringprincipal_window_open.html b/docshell/test/navigation/test_triggeringprincipal_window_open.html new file mode 100644 index 000000000..d5d7f210b --- /dev/null +++ b/docshell/test/navigation/test_triggeringprincipal_window_open.html @@ -0,0 +1,101 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="NavigationUtils.js"></script> +</head> +<body> + +<script type="text/javascript"> + +/* We call window.open() using different URIs and make sure the triggeringPrincipal + * loadingPrincipal are correct. + * Test1: window.open(http:) + * Test2: window.open(data:) + * Test3: window.open(javascript:) + */ + +const TRIGGERING_PRINCIPAL_URI = + "http://mochi.test:8888/tests/docshell/test/navigation/test_triggeringprincipal_window_open.html"; + +SimpleTest.waitForExplicitFinish(); + +const NUM_TESTS = 3; +var test_counter = 0; + +function checkFinish() { + test_counter++; + if (test_counter === NUM_TESTS) { + SimpleTest.finish(); + } +} + +// ---------------------------------------------------------------------------- +// Test 1: window.open(http:) +var httpWin = window.open("file_triggeringprincipal_window_open.html", "_blank", "width=10,height=10"); +httpWin.onload = function() { + var httpChannel = SpecialPowers.wrap(httpWin.document).docShell.currentDocumentChannel; + var httpTriggeringPrincipal = httpChannel.loadInfo.triggeringPrincipal.URI.asciiSpec; + var httpLoadingPrincipal = httpChannel.loadInfo.loadingPrincipal; + + is(httpTriggeringPrincipal, TRIGGERING_PRINCIPAL_URI, + "TriggeringPrincipal for window.open(http:) should be the principal of the document"); + + is(httpWin.document.referrer, TRIGGERING_PRINCIPAL_URI, + "Referrer for window.open(http:) should be the principal of the document"); + + is(httpLoadingPrincipal, null, + "LoadingPrincipal for window.open(http:) should be null"); + + httpWin.close(); + checkFinish(); +} + +// ---------------------------------------------------------------------------- +// Test 2: window.open(data:) +var dataWin = window.open("data:text/html,<html><body>data</body></html>", "_blank", "width=10,height=10"); +dataWin.onload = function() { + var dataChannel = SpecialPowers.wrap(dataWin.document).docShell.currentDocumentChannel; + var dataTriggeringPrincipal = dataChannel.loadInfo.triggeringPrincipal.URI.asciiSpec; + var dataLoadingPrincipal = dataChannel.loadInfo.loadingPrincipal; + + is(dataTriggeringPrincipal, TRIGGERING_PRINCIPAL_URI, + "TriggeringPrincipal for window.open(data:) should be the principal of the document"); + + is(dataWin.document.referrer, "", + "Referrer for window.open(data:) should be empty"); + + is(dataLoadingPrincipal, null, + "LoadingPrincipal for window.open(data:) should be null"); + + dataWin.close(); + checkFinish(); +} + +// ---------------------------------------------------------------------------- +// Test 3: window.open(javascript:) +var jsWin = window.open("javascript:'<html><body>js</body></html>';", "_blank", "width=10,height=10"); +jsWin.onload = function() { + var jsChannel = SpecialPowers.wrap(jsWin.document).docShell.currentDocumentChannel; + var jsTriggeringPrincipal = jsChannel.loadInfo.triggeringPrincipal.URI.asciiSpec; + var jsLoadingPrincipal = jsChannel.loadInfo.loadingPrincipal; + + is(jsTriggeringPrincipal, TRIGGERING_PRINCIPAL_URI, + "TriggeringPrincipal for window.open(javascript:) should be the principal of the document"); + + is(jsWin.document.referrer, "", + "Referrer for window.open(javascript:) should be empty"); + + is(jsLoadingPrincipal, null, + "LoadingPrincipal for window.open(javascript:) should be null"); + + jsWin.close(); + checkFinish(); +} + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_anchor_scroll_after_document_open.html b/docshell/test/test_anchor_scroll_after_document_open.html new file mode 100644 index 000000000..93fa4615b --- /dev/null +++ b/docshell/test/test_anchor_scroll_after_document_open.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=881487 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 881487</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 881487 **/ + SimpleTest.waitForExplicitFinish(); + // Child needs to invoke us, otherwise our onload will fire before the child + // has done the write/close bit. + var gotOnload = false; + addLoadEvent(function() { + gotOnload = true; + }); + onmessage = function handleMessage(msg) { + if (msg.data == "doTest") { + if (!gotOnload) { + addLoadEvent(function() { handleMessage(msg); }); + return; + } + frames[0].onscroll = function() { + ok(true, "Got a scroll event"); + SimpleTest.finish(); + } + frames[0].location.hash = "#target"; + return; + } + if (msg.data == "haveHash") { + ok(false, "Child got reloaded"); + } else { + ok(false, "Unexpected message"); + } + SimpleTest.finish(); + } + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=881487">Mozilla Bug 881487</a> +<p id="display"> + <!-- iframe goes here so it can scroll --> +<iframe src="file_anchor_scroll_after_document_open.html"></iframe> +</p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/docshell/test/test_bfcache_plus_hash.html b/docshell/test/test_bfcache_plus_hash.html new file mode 100644 index 000000000..30c0b6b50 --- /dev/null +++ b/docshell/test/test_bfcache_plus_hash.html @@ -0,0 +1,120 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=646641 +--> +<head> + <title>Test for Bug 646641</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.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=646641">Mozilla Bug 646641</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> + +/** Test for Bug 646641 **/ + +/* + * In a popup (because navigating the main frame confuses Mochitest), do the + * following: + * + * * Call history.pushState(). + * * Navigate to a new page. + * * Go back two history entries. + * + * Check that we go back, we retrieve the document from bfcache. + */ + +SimpleTest.waitForExplicitFinish(); + +function debug(msg) { + // Wrap dump so we can turn debug messages on and off easily. + dump(msg + '\n'); +} + +var expectedLoadNum = -1; +function childLoad(n) { + if (n == expectedLoadNum) { + debug('Got load ' + n); + expectedLoadNum = -1; + + // Spin the event loop before calling gGen.next() so the generator runs + // outside the onload handler. This prevents us from encountering all + // sorts of docshell quirks. + // + // (I don't know why I need to wrap gGen.next() in a function, but it + // throws an error otherwise.) + setTimeout(function() { gGen.next() }, 0); + } + else { + debug('Got unexpected load ' + n); + ok(false, 'Got unexpected load ' + n); + } +} + +var expectedPageshowNum = -1; +function childPageshow(n) { + if (n == expectedPageshowNum) { + debug('Got expected pageshow ' + n); + expectedPageshowNum = -1; + ok(true, 'Got expected pageshow ' + n); + setTimeout(function() { gGen.next() }, 0); + } + else { + debug('Got pageshow ' + n); + } + + // Since a pageshow comes along with an onload, don't fail the test if we get + // an unexpected pageshow. +} + +function waitForLoad(n) { + debug('Waiting for load ' + n); + expectedLoadNum = n; +} + +function waitForShow(n) { + debug('Waiting for show ' + n); + expectedPageshowNum = n; +} + +function test() { + var popup = window.open('data:text/html,' + + '<html><body onload="opener.childLoad(1)" ' + + 'onpageshow="opener.childPageshow(1)">' + + 'Popup 1' + + '</body></html>'); + waitForLoad(1); + yield undefined; + + popup.history.pushState('', '', ''); + + popup.location = 'data:text/html,<html><body onload="opener.childLoad(2)">Popup 2</body></html>'; + waitForLoad(2); + yield undefined; + + // Now go back 2. The first page should be retrieved from bfcache. + popup.history.go(-2); + waitForShow(1); + yield undefined; + + popup.close(); + SimpleTest.finish(); + + // Yield once more so we don't throw a StopIteration exception. + yield undefined; +} + +var gGen = test(); +gGen.next(); + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug1045096.html b/docshell/test/test_bug1045096.html new file mode 100644 index 000000000..8a0b4a223 --- /dev/null +++ b/docshell/test/test_bug1045096.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1045096 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1045096</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=1045096">Mozilla Bug 1045096</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> + <script type="application/javascript"> + + /** Test for Bug 1045096 **/ + var i = document.createElement('iframe'); + i.src = "javascript:false"; // This is required! + $("content").appendChild(i); + ok(i.contentWindow.performance, "Should have a performance object"); + </script> +</body> +</html> diff --git a/docshell/test/test_bug1121701.html b/docshell/test/test_bug1121701.html new file mode 100644 index 000000000..8beb2d8d0 --- /dev/null +++ b/docshell/test/test_bug1121701.html @@ -0,0 +1,71 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1121701 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1121701</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1121701 **/ + + var testUrl1 = "data:text/html,<script>window.onpageshow = function(e) { opener.child1PageShow(e); } <" + "/script>"; + var testUrl2 = "data:text/html,<script>window.onpageshow = function(e) { opener.child2PageShow(e); } <" + "/script>"; + var testWin; + + var page1LoadCount = 0; + function child1PageShow(e) { + ++page1LoadCount; + if (page1LoadCount == 1) { + SimpleTest.executeSoon(function() { + is(e.persisted, false, "Initial page load shouldn't be persisted."); + testWin.document.body.innerHTML = "modified"; + testWin.onpagehide = function(e) { + testWin.onpagehide = null; + ok(e.persisted, "test page 1 should have been persisted"); + is(testWin.document.body.innerHTML, "modified"); + } + testWin.location.href = testUrl2; + }); + } else if (page1LoadCount == 2) { + is(e.persisted, true, "Page load from bfcache should be persisted."); + is(testWin.document.body.innerHTML, "modified"); + testWin.close(); + SimpleTest.finish(); + } + } + + function child2PageShow(e) { + testWin.document.body.innerHTML = "<img>"; + SimpleTest.executeSoon(function() { + testWin.onmessage = function() { + ok(true, "Got message"); + testWin.document.body.firstChild.src = location.href; + } + testWin.onbeforeunload = function() { + testWin.postMessage("foo", "*"); + } + testWin.history.back(); + }); + } + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(function() { + testWin = window.open(testUrl1); + }); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1121701">Mozilla Bug 1121701</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug1186774.html b/docshell/test/test_bug1186774.html new file mode 100644 index 000000000..52ef5f62c --- /dev/null +++ b/docshell/test/test_bug1186774.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1186774 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1186774</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1186774 **/ + +var child; + +function runTest() { + child = window.open("data:text/html,<div style='height: 9000px;'></div>", "", "width=100,height=100"); + child.onload = function() { + setTimeout(function() { + child.scrollTo(0, 0); + child.history.pushState({}, "initial"); + child.scrollTo(0, 3000); + child.history.pushState({}, "scrolled"); + child.scrollTo(0, 6000); + child.history.back(); + }); + } + + child.onpopstate = function() { + is(child.scrollY, 6000, "Shouldn't have scrolled before popstate"); + child.close(); + SimpleTest.finish(); + } +} + +SimpleTest.waitForExplicitFinish(); +addLoadEvent(runTest); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1186774">Mozilla Bug 1186774</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug123696.html b/docshell/test/test_bug123696.html new file mode 100644 index 000000000..11d14e807 --- /dev/null +++ b/docshell/test/test_bug123696.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=123696 +--> +<head> + <title>Test for Bug 123696</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=123696">Mozilla Bug 123696</a> +<p id="display"> + <iframe src="bug123696-subframe.html"></iframe> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 123696 **/ +SimpleTest.waitForExplicitFinish(); + +function finishTest() { + is(window.frames[0].frames[0].document.documentElement.textContent, + "change2", "Reload should have reloaded correctly!"); + SimpleTest.finish(); +} + +function doReload() { + window.frames[0].frameElement.onload = finishTest; + window.frames[0].location.reload(); +} + +addLoadEvent(function() { + window.frames[0].frames[0].frameElement.onload = doReload; + window.frames[0].frames[0].frameElement.src = "javascript:parent.change2()"; +}); + + + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug369814.html b/docshell/test/test_bug369814.html new file mode 100644 index 000000000..866771055 --- /dev/null +++ b/docshell/test/test_bug369814.html @@ -0,0 +1,211 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=369814 +--> +<head> + <title>Test for Bug 369814</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/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=384014">Mozilla Bug 369814</a> + +<p> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Tests for Bug 369814 **/ + +SimpleTest.waitForExplicitFinish(); + +// Because child scripts won't be able to run to tell us they're done, +// we need to just wait for them. Wait this many event loop spins before +// checking the results. +const gLoadEventLoopCount = 100; + +var gCurrentTest; +var gTargetWindow; +var gNumPokes; +var gTestFrame; + +/** + * Called by documents loaded from jar files to indicate that they can access + * this document. + */ +function poke(description) { + ok(false, gCurrentTest['name'] + ": got unexpected poke: " + description); + gNumPokes++; +} + +function loadEvent(window, callback) +{ + var fn = function() { + window.removeEventListener("load", fn, false); + callback(); + }; + window.addEventListener("load", fn, false); +} + +function loadTestTarget(callback) +{ + gTargetWindow = window.open("http://mochi.test:8888", "bug369814target"); + loadEvent(gTargetWindow, callback); +} + +function closeTestTarget() +{ + gTargetWindow.close(); + gTargetWindow = null; +} + +function loadErrorTest(test) +{ + // Give the frame a chance to fail at loading. + // How do detect failure to load? Error pages don't fire load + // events. But we can load another page before the error page and + // then use its unload handler to know when the error page is just + // about loaded; at that point a single trip through the event loop + // should do the trick. + loadEvent(gTestFrame, function() { + gTestFrame.src = test['url']; + }); + gTestFrame.unloading = function() { + gTestFrame.unloading = null; + // Go out to the event loop once so that unload processing finishes and + // the new document is set up. + setTimeout(function() { + // XXX: There doesn't seem to be a reliable check for "got an error," + // but reaching in to an error document will throw an exception + var errorPage; + try { + var item = gTestFrame.contentDocument.getElementById(gCurrentTest['data-iframe']); + errorPage = false; + } catch (e) { + errorPage = true; + } + ok(errorPage, gCurrentTest["name"] + ": should block a suspicious JAR load."); + + finishTest(); + }, 0); + } + var unloadDetector = "data:text/html,<script>window.onunload = function() { frameElement.unloading(); }</" + "script>"; + gTestFrame.src = unloadDetector; +} + +function iframeTest(test) { + gTestFrame.src = test['url']; + loadEvent(gTestFrame, function() { + finishTest(); + }); +} + + +function hitEventLoop(func, times) { + if (times > 0) { + SimpleTest.executeSoon(function() { hitEventLoop(func, times-1); }); + } else { + SimpleTest.executeSoon(func); + } +} + +function refreshTest(test) { + gTestFrame.src = test['url']; + loadEvent(gTestFrame, function() { + // Wait for the frame to try and refresh + // XXX: a "blocked redirect" signal would be needed to get rid of + // this timeout. + hitEventLoop(function() { + finishTest(); + }, gLoadEventLoopCount); + }); +} + +function anchorTest(test) { + loadTestTarget(function() { + gTestFrame.src = test['url']; + loadEvent(gTestFrame, function() { + sendMouseEvent({type:'click'}, 'target', gTestFrame.contentWindow); + sendMouseEvent({type:'click'}, 'notarget', gTestFrame.contentWindow); + + // Give the clicks a chance to load + hitEventLoop(function() { + closeTestTarget(); + finishTest(); + }, gLoadEventLoopCount); + }); + }); +} + +var gTests = [ + { "name" : "iframes.html loaded from non-jar type, pref disabled", + "url" : "jar:http://mochi.test:8888/tests/docshell/test/bug369814.zip!/iframes.html", + "pref" : false, + "pokes" : { }, + "func" : loadErrorTest, + }, + { "name" : "refresh.html loaded from non-jar type, pref enabled", + "url" : "jar:http://mochi.test:8888/tests/docshell/test/bug369814.zip!/refresh.html", + "pref" : true, + "pokes" : { }, + "func" : refreshTest, + }, + { "name" : "iframes.html loaded from non-jar type, pref enabled", + "url" : "jar:http://mochi.test:8888/tests/docshell/test/bug369814.zip!/iframes.html", + "pref" : true, + "pokes" : { }, + "func" : iframeTest, + }, + { "name" : "anchors.html loaded from non-jar type, pref enabled", + "url" : "jar:http://mochi.test:8888/tests/docshell/test/bug369814.zip!/anchors.html", + "pref" : true, + "pokes" : { }, + "func" : anchorTest, + }, +]; + +var gNextTest = 0; + +function runNextTest() +{ + if (gNextTest < gTests.length) { + gCurrentTest = gTests[gNextTest++]; + gNumPokes = 0; + + SpecialPowers.pushPrefEnv({"set": [["network.jar.block-remote-files", false], + ["network.jar.open-unsafe-types", gCurrentTest['pref']]]}, function() { + + // Create a new frame each time, so our restictions on loads in a + // jar:-loaded iframe don't interfere with the test. + if (gTestFrame) { + document.body.removeChild(gTestFrame); + } + gTestFrame = document.createElement("iframe"); + document.body.insertBefore(gTestFrame, $("test")); + + gCurrentTest['func'](gCurrentTest); + }); + } else { + SimpleTest.finish(); + } +} + +function finishTest() +{ + SpecialPowers.pushPrefEnv({"set": [["network.jar.open-unsafe-types", false]]}, function() { + if (gNumPokes == 0) { + ok(true, gCurrentTest["name"] + ": no unexpected pokes"); + } + + runNextTest(); + }); +} + +addLoadEvent(runNextTest); + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug384014.html b/docshell/test/test_bug384014.html new file mode 100644 index 000000000..1f115a6ec --- /dev/null +++ b/docshell/test/test_bug384014.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=384014 +--> +<head> + <title>Test for Bug 384014</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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=384014">Mozilla Bug 384014</a> +<p id="display"> +<iframe id="f" src="javascript:try { window.x = 'PASS'; s = 'PASS' } catch(e) { s = 'FAIL' } s;"></iframe> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 384014 **/ +SimpleTest.waitForExplicitFinish(); + +function runTest() { + $("f").onload = function () { + is($("f").contentDocument.documentElement.textContent, "PASS", + "We fail"); + SimpleTest.finish(); + } + + $("f").contentWindow.location.reload(); +} + +addLoadEvent(runTest); + +</script> +</pre> +</body> +</html> + diff --git a/docshell/test/test_bug385434.html b/docshell/test/test_bug385434.html new file mode 100644 index 000000000..660f522e8 --- /dev/null +++ b/docshell/test/test_bug385434.html @@ -0,0 +1,209 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=385434 +--> +<head> + <title>Test for Bug 385434</title> + <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=385434">Mozilla Bug 385434</a> +<p id="display"></p> +<div id="content"> + <iframe id="frame" style="height:100px; width:100px; border:0"></iframe> + <div id="status" style="display: none"></div> +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> + +/** Test for Bug 385434 **/ +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); + +var gNumHashchanges = 0; +var gCallbackOnIframeLoad = false; + +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); +} + +// onIframeHashchange, onIframeLoad, and onIframeScroll are all called by the +// content we load into our iframe in order to notify the parent frame of an +// event which was fired. +function onIframeHashchange() { + gNumHashchanges++; + gGen.next(); +} + +function onIframeLoad() { + if (gCallbackOnIframeLoad) { + gCallbackOnIframeLoad = false; + gGen.next(); + } +} + +function onIframeScroll() { + is(gNumHashchanges, 0, "onscroll should fire before onhashchange."); +} + +function enableIframeLoadCallback() { + gCallbackOnIframeLoad = true; +} + +function noEventExpected(msg) { + is(gNumHashchanges, 0, msg); + + // Even if there's an error, set gNumHashchanges to 0 so other tests don't + // fail. + gNumHashchanges = 0; +} + +function eventExpected(msg) { + is(gNumHashchanges, 1, msg); + + // Eat up this event, whether the test above was true or not + gNumHashchanges = 0; +} + +/* + * The hashchange event is dispatched asynchronously, so if we want to observe + * it, we have to yield within run_test(), transferring control back to the + * event loop. + * + * When we're expecting our iframe to observe a hashchange event after we poke + * it, we just yield and wait for onIframeHashchange() to call gGen.next() and + * wake us up. + * + * When we're testing to ensure that the iframe doesn't dispatch a hashchange + * event, we try to hook onto the iframe's load event. We call + * enableIframeLoadCallback(), which causes onIframeLoad() to call gGen.next() + * upon the next observed load. After we get our callback, we check that a + * hashchange didn't occur. + * + * We can't always just wait for page load in order to observe that a + * hashchange didn't happen. In these cases, we call longWait() and yield + * until either a hashchange occurs or longWait's callback is scheduled. This + * is something of a hack; it's entirely possible that longWait won't wait long + * enough, and we won't observe what should have been a failure of the test. + * But it shouldn't happen that good code will randomly *fail* this test. + */ +function run_test() { + /* + * TEST 1 tests that: + * <body onhashchange = ... > works, + * the event is (not) fired at the correct times + */ + var frame = document.getElementById("frame"); + var frameCw = frame.contentWindow; + + enableIframeLoadCallback(); + frameCw.document.location = "file_bug385434_1.html"; + // Wait for the iframe to load and for our callback to fire + yield undefined; + + noEventExpected("No hashchange expected initially."); + + sendMouseEvent({type: "click"}, "link1", frameCw); + yield undefined; + eventExpected("Clicking link1 should trigger a hashchange."); + + sendMouseEvent({type: "click"}, "link1", frameCw); + longWait(); + yield undefined; + // succeed if a hashchange event wasn't triggered while we were waiting + noEventExpected("Clicking link1 again should not trigger a hashchange."); + + sendMouseEvent({type: "click"}, "link2", frameCw); + yield undefined; + eventExpected("Clicking link2 should trigger a hashchange."); + + frameCw.history.go(-1); + yield undefined; + eventExpected("Going back should trigger a hashchange."); + + frameCw.history.go(1); + yield undefined; + eventExpected("Going forward should trigger a hashchange."); + + // window.location has a trailing '#' right now, so we append "link1", not + // "#link1". + frameCw.window.location = frameCw.window.location + "link1"; + yield undefined; + eventExpected("Assigning to window.location should trigger a hashchange."); + + // Set up history in the iframe which looks like: + // file_bug385434_1.html#link1 + // file_bug385434_2.html + // file_bug385434_1.html#foo <-- current page + enableIframeLoadCallback(); + frameCw.window.location = "file_bug385434_2.html"; + yield undefined; + + enableIframeLoadCallback(); + frameCw.window.location = "file_bug385434_1.html#foo"; + yield undefined; + + // Now when we do history.go(-2) on the frame, it *shouldn't* fire a + // hashchange. Although the URIs differ only by their hashes, they belong to + // two different Documents. + frameCw.history.go(-2); + longWait(); + yield undefined; + noEventExpected("Moving between different Documents shouldn't " + + "trigger a hashchange."); + + /* + * TEST 2 tests that: + * <frameset onhashchange = ... > works, + * the event is targeted at the window object + * the event's cancelable, bubbles settings are correct + */ + enableIframeLoadCallback(); + frameCw.document.location = "file_bug385434_2.html"; + yield undefined; + + frameCw.document.location = "file_bug385434_2.html#foo"; + yield undefined; + + eventExpected("frame onhashchange should fire events."); + // iframe should set gSampleEvent + is(gSampleEvent.target, frameCw, + "The hashchange event should be targeted to the window."); + is(gSampleEvent.type, "hashchange", + "Event type should be 'hashchange'."); + is(gSampleEvent.cancelable, false, + "The hashchange event shouldn't be cancelable."); + is(gSampleEvent.bubbles, true, + "The hashchange event should bubble."); + + /* + * TEST 3 tests that: + * hashchange is dispatched if the current document readyState is + * not "complete" (bug 504837). + */ + frameCw.document.location = "file_bug385434_3.html"; + yield undefined; + eventExpected("Hashchange should fire even if the document " + + "hasn't finished loading."); + + SimpleTest.finish(); + yield undefined; +} + +var gGen = run_test(); +gGen.next(); + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug387979.html b/docshell/test/test_bug387979.html new file mode 100644 index 000000000..9e337e2c2 --- /dev/null +++ b/docshell/test/test_bug387979.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=387979 +--> +<head> + <title>Test for Bug 387979</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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=387979">Mozilla Bug 387979</a> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 387979 **/ +function a(s) { + var r; + try { r = frames[0].document.body; } + catch (e) { r = e; } + is(r instanceof frames[0].HTMLBodyElement, true, + "Can't get body" + s); +} +var p = 0; +function b() { + switch (++p) { + case 1: + frames[0].location = "about:blank"; + break; + case 2: + a("before reload"); + frames[0].location.reload(); + break; + case 3: + a("after reload"); + SimpleTest.finish(); + break; + } +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +<p id="display"> + <iframe onload="b()"></iframe> + <pre id="p">-</pre> +</p> +</body> +</html> + diff --git a/docshell/test/test_bug402210.html b/docshell/test/test_bug402210.html new file mode 100644 index 000000000..78fa025e0 --- /dev/null +++ b/docshell/test/test_bug402210.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<!-- +While working on bug 402210, it came up that the code was doing + +a.href = proto + host + +which technically produces "https:host" instead of "https://host" and +that the code was relying on href's setting having fixup behaviour +for this kind of thing. + +If we rely on it, we might as well test for it, even if it isn't the +problem 402210 was meant to fix. + +https://bugzilla.mozilla.org/show_bug.cgi?id=402210 +--> +<head> + <title>Test for Bug 402210</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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=402210">Mozilla Bug 402210</a> +<p id="display"> + <a id="testlink">Test Link</a> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +function runTest() { + $("testlink").href = "https:example.com"; + is($("testlink").href, "https://example.com/", "Setting href on an anchor tag should fixup missing slashes after https protocol"); + + $("testlink").href = "ftp:example.com"; + is($("testlink").href, "ftp://example.com/", "Setting href on an anchor tag should fixup missing slashes after non-http protocol"); + + SimpleTest.finish(); +} + +addLoadEvent(runTest); +</script> +</pre> +</body> +</html> + diff --git a/docshell/test/test_bug404548.html b/docshell/test/test_bug404548.html new file mode 100644 index 000000000..edde15c9d --- /dev/null +++ b/docshell/test/test_bug404548.html @@ -0,0 +1,39 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=404548 +--> +<head> + <title>Test for Bug 404548</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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=404548">Mozilla Bug 404548</a> +<p id="display"> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 404548 **/ +var firstRemoved = false; +var secondHidden = false; + +SimpleTest.waitForExplicitFinish(); + +var w = window.open("bug404548-subframe.html", "", "width=10,height=10"); + +function finishTest() { + is(firstRemoved, true, "Should have removed iframe from the DOM") + is(secondHidden, true, "Should have fired pagehide on second kid"); + w.close(); + SimpleTest.finish(); +} +</script> +</pre> +</body> +</html> + diff --git a/docshell/test/test_bug413310.html b/docshell/test/test_bug413310.html new file mode 100644 index 000000000..e030c99d8 --- /dev/null +++ b/docshell/test/test_bug413310.html @@ -0,0 +1,106 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=413310 +--> +<head> + <title>Test for Bug 413310</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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=413310">Mozilla Bug 413310</a> +<p id="display"> +<script class="testbody" type="text/javascript"> + +if (navigator.platform.startsWith("Mac")) { + SimpleTest.expectAssertions(0, 2); +} else { + SimpleTest.expectAssertions(0, 1); +} + +/** Test for Bug 413310 **/ + +// NOTE: If we ever make subframes do bfcache stuff, this test will need to be +// modified accordingly! It assumes that subframes do NOT get bfcached. +var onloadCount = 0; + +var step = -1; // One increment will come from the initial subframe onload. + // Note that this script should come before the subframe, + // so that doNextStep is defined when its onload handler fires. + +var textContent; + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(doNextStep); + +function doNextStep() { + ++step; + switch (step) { + case 1: + is(onloadCount, 1, "Loaded initial page"); + is($("i").contentWindow.location.href, + location.href.replace(/test_bug413310.html/, + "bug413310-subframe.html"), + "Unexpected subframe location after initial load"); + $("i").contentDocument.forms[0].submit(); + break; + case 2: + is(onloadCount, 2, "Loaded POST result"); + + is($("i").contentWindow.location.href, + location.href.replace(/test_bug413310.html/, + "bug413310-post.sjs"), + "Unexpected subframe location after POST load"); + + textContent = $("i").contentDocument.body.textContent; + isDeeply(textContent.match(/^POST /), ["POST "], "Not a POST?"); + + $("i").contentWindow.location.hash = "foo"; + setTimeout(doNextStep, 0); + break; + case 3: + is(onloadCount, 2, "Anchor scroll should not fire onload"); + is($("i").contentWindow.location.href, + location.href.replace(/test_bug413310.html/, + "bug413310-post.sjs#foo"), + "Unexpected subframe location after anchor scroll"); + is(textContent, $("i").contentDocument.body.textContent, + "Did a load when scrolling?"); + $("i").contentWindow.location.href = "bug413310-subframe.html";; + break; + case 4: + is(onloadCount, 3, "Done new load"); + is($("i").contentWindow.location.href, + location.href.replace(/test_bug413310.html/, + "bug413310-subframe.html"), + "Unexpected subframe location after new load"); + history.back(); + break; + case 5: + is(onloadCount, 4, + "History traversal didn't fire onload: bfcache issues!"); + is($("i").contentWindow.location.href, + location.href.replace(/test_bug413310.html/, + "bug413310-post.sjs#foo"), + "Unexpected subframe location"); + is(textContent, $("i").contentDocument.body.textContent, + "Did a load when going back?"); + SimpleTest.finish(); + break; + } +} +</script> +<!-- Use a timeout in onload so that we don't do a load immediately inside onload --> +<iframe id="i" src="bug413310-subframe.html" onload="setTimeout(doNextStep, 20)"> +</iframe> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> + diff --git a/docshell/test/test_bug475636.html b/docshell/test/test_bug475636.html new file mode 100644 index 000000000..eaea3bcb8 --- /dev/null +++ b/docshell/test/test_bug475636.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=475636 +Test that refresh to data: URIs don't inherit the principal +--> +<head> + <title>Test for Bug 475636</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body onload="gen.next()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=475636">Mozilla Bug 475636</a> + +<div id="content" style="display: none"> + +</div> +<iframe id=loader></iframe> +<pre id="test"> +<script class="testbody" type="application/javascript;version=1.8"> + +SimpleTest.waitForExplicitFinish(); + +gen = runTests(); + +window.private = 42; + +window.addEventListener("message", function(e) { + gen.send(e.data); +}, false); + +var url = "file_bug475636.sjs?"; + +function runTests() { + var loader = document.getElementById('loader'); + for (var testNum = 1; ; ++testNum) { + loader.src = url + testNum; + let res = (yield); + if (res == "done") { + SimpleTest.finish(); + yield undefined; + } + is(res, "pass"); + } +} + + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug509055.html b/docshell/test/test_bug509055.html new file mode 100644 index 000000000..be163cafa --- /dev/null +++ b/docshell/test/test_bug509055.html @@ -0,0 +1,98 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=509055 +--> +<head> + <title>Test for Bug 509055</title> + <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=509055">Mozilla Bug 509055</a> +<p id="display"></p> +<div id="status"></div> +<div id="content"> +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> + +/** Test for Bug 509055 **/ + +SimpleTest.waitForExplicitFinish(); + +var gGen; + +function shortWait() { + setTimeout(function() { gGen.next(); }, 0, false); +} + +function onChildHashchange(e) { + // gGen might be undefined when we refresh the page, so we have to check here + dump("onChildHashchange() called.\n"); + if(gGen) + gGen.next(); +} + +function onChildLoad(e) { + if(gGen) + gGen.next(); +} + +function runTest() { + var popup = window.open("file_bug509055.html", "popup 0", + "height=200,width=200,location=yes," + + "menubar=yes,status=yes,toolbar=yes,dependent=yes"); + popup.hashchangeCallback = onChildHashchange; + popup.onload = onChildLoad; + dump('Waiting for initial load.\n'); + yield undefined; + + // Without this wait, the change to location.hash below doesn't create a + // SHEntry or enable the back button. + shortWait(); + dump('Got initial load. Spinning event loop.\n'); + yield undefined; + + popup.location.hash = "#1"; + dump('Waiting for hashchange.\n'); + yield undefined; + + popup.history.back(); + dump('Waiting for second hashchange.\n'); + yield undefined; // wait for hashchange + + popup.document.title = "Changed"; + + // Wait for listeners to be notified of the title change. + shortWait(); + dump('Got second hashchange. Spinning event loop.\n'); + yield undefined; + + var sh = SpecialPowers.wrap(popup) + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIWebNavigation) + .sessionHistory; + + // Get the title of the inner popup's current SHEntry + var sheTitle = sh.getEntryAtIndex(sh.index, false).title; + is(sheTitle, "Changed", "SHEntry's title should change when we change."); + + popup.close(); + + SimpleTest.executeSoon(SimpleTest.finish); + dump('Final yield.\n'); + yield undefined; +} + +window.addEventListener('load', function() { + gGen = runTest(); + gGen.next(); +}, false); + +</script> + +</body> +</html> + diff --git a/docshell/test/test_bug511449.html b/docshell/test/test_bug511449.html new file mode 100644 index 000000000..dcea4cf3d --- /dev/null +++ b/docshell/test/test_bug511449.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=511449 +--> +<head> + <title>Test for Bug 511449</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/NativeKeyCodes.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=511449">Mozilla Bug 511449</a> +<p id="display"></p> +<div id="status"></div> +<div id="content"> +</div> +<input type="text" id="input"> +<pre id="test"> +<script type="application/javascript;version=1.7"> + +/** Test for Bug 511449 **/ + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +window.addEventListener('load', runTest, false); + +var win = null; + +function runTest() { + document.getElementById("input").focus(); + win = window.open("file_bug511449.html", ""); + SimpleTest.waitForFocus(runNextTest, win); +} + +function runNextTest() { + var didClose = false; + win.onunload = function() { + didClose = true; + } + synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_W, {metaKey:1}, "w", "w"); + + setTimeout(function () { + ok(didClose, "Cmd+W should have closed the tab"); + if (!didClose) { + win.close(); + } + SimpleTest.finish(); + }, 1000); +} + +</script> + +</body> +</html> diff --git a/docshell/test/test_bug529119-1.html b/docshell/test/test_bug529119-1.html new file mode 100644 index 000000000..573885dc0 --- /dev/null +++ b/docshell/test/test_bug529119-1.html @@ -0,0 +1,95 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Test bug 529119</title> +<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); + +var workingURL = "http://mochi.test:8888/tests/docshell/test/bug529119-window.html"; +var faultyURL = "http://some-nonexistent-domain-27489274c892748217cn2384.com/"; + +var w = null; +var phase = 0; +var gotWrongPageOnTryAgainClick = false; + +function pollForPage(f, w) +{ + // Start with polling after a delay, we might mistakenly take the current page + // as an expected one. + window.setTimeout(function() { + var iterationsLeft = 200; + var int = window.setInterval(function() { + iterationsLeft--; + + var haveErrorPage = false; + try { + var title = w.document.title; + } + catch (ex) { + haveErrorPage = true; + } + + if (iterationsLeft == 0 || haveErrorPage) { + window.clearInterval(int); + f(iterationsLeft > 0); + } + }, 100); + }, 1000); +} + +function windowLoaded() +{ + switch (phase) + { + case 0: + /* 2. We have succeededfully loaded a page, now go to a faulty URL */ + window.setTimeout(function() { + w.location.href = faultyURL; + }, 0); + + phase = 1; + + pollForPage(function(succeeded) { + ok(succeeded, "Waiting for error page succeeded"); + + /* 3. now, while we are on the error page, try to reload it, actually + click the "Try Again" button */ + SpecialPowers.wrap(w).location.reload(); + + pollForPage(function(succeeded) { + ok(succeeded, "Waiting for error page succeeded"); + + /* 4-finish, check we are still on the error page */ + is(SpecialPowers.wrap(w).location.href, faultyURL, "Is on an error page"); + isnot(SpecialPowers.wrap(w).location.href, workingURL, "Is not on the previous page"); + is(gotWrongPageOnTryAgainClick, false, + "Must not get www.example.com page on reload of an error page"); + w.close(); + SimpleTest.finish(); + }, w); + }, w); + break; + + case 1: + /* 4-check, we must not get here! */ + gotWrongPageOnTryAgainClick = true; + break; + } +} + +function startTest() +{ + /* 1. load a URL that leads to an error page */ + w = window.open(workingURL); +} + +</script> +</head> +<body onload="startTest();"> +</body> +</html> diff --git a/docshell/test/test_bug529119-2.html b/docshell/test/test_bug529119-2.html new file mode 100644 index 000000000..fb3d19119 --- /dev/null +++ b/docshell/test/test_bug529119-2.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<head> +<title>Test bug 529119</title> +<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); + +var workingURL = "http://mochi.test:8888/tests/docshell/test/bug529119-window.html"; +var faultyURL = "http://some-nonexistent-domain-27489274c892748217cn2384.com/"; + +var w = null; +var phase = 0; +var isWindowLoaded = false; + +function pollForPage(expectErrorPage, f, w) +{ + // Start with polling after a delay, we might mistakenly take the current page + // as an expected one. + window.setTimeout(function() { + var iterationsLeft = 200; + var int = window.setInterval(function() { + iterationsLeft--; + + var haveErrorPage = false; + try { + var title = w.document.title; + } + catch (ex) { + haveErrorPage = true; + } + + if (iterationsLeft == 0 || expectErrorPage == haveErrorPage) { + window.clearInterval(int); + f(iterationsLeft > 0); + } + }, 100); + }, 1000); +} + +function windowLoaded() +{ + // The code under here should only be run once + // The test popup window workingURL was already opened + if (isWindowLoaded) + return; + isWindowLoaded = true; + + /* 2. We have successfully loaded a page, now go to a faulty URL */ + // XXX The test fails when we change the location synchronously + window.setTimeout(function() { + w.location.href = faultyURL; + }, 0); + + pollForPage(true, function(succeeded) { + ok(succeeded, "Waiting for error page succeeded"); + /* 3. now, while we are on the error page, navigate back */ + try { + SpecialPowers.wrap(w).back(); + } + catch(ex) { + ok(false, "w.back() threw " + ex); + } + + pollForPage(false, function(succeeded) { + ok(succeeded, "Waiting for original page succeeded"); + /* 4-finish, check we are back at the original page */ + isnot(SpecialPowers.wrap(w).location.href, faultyURL, "Is on an error page"); + is(SpecialPowers.wrap(w).location.href, workingURL, "Is not on the previous page"); + w.close(); + SimpleTest.finish(); + }, w); + }, w); +} + +function startTest() +{ + /* 1. load a URL that leads to an error page */ + w = window.open(workingURL); +} + +</script> +</head> +<body onload="startTest();"> +</body> +</html> diff --git a/docshell/test/test_bug530396.html b/docshell/test/test_bug530396.html new file mode 100644 index 000000000..f9fb79b4b --- /dev/null +++ b/docshell/test/test_bug530396.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=530396 +--> +<head> + <title>Test for Bug 530396</title> + <script type="application/javascript" src="/MochiKit/packed.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/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=530396">Mozilla Bug 530396</a> + +<p> + +<iframe id="testFrame" src="http://mochi.test:8888/tests/docshell/test/bug530396-subframe.html"></iframe> + +<pre id="test"> +<script class="testbody" type="text/javascript"> + +// NOTE: If we ever make subframes do bfcache stuff, this test will need to be +// modified accordingly! It assumes that subframes do NOT get bfcached. +var onloadCount = 0; + +var step = 0; + +var gTestFrame = document.getElementById('testFrame'); + +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +addLoadEvent(doNextStep); + +function doNextStep() { + ++step; + switch (step) { + case 1: + is(onloadCount, 1, "Loaded initial page"); + sendMouseEvent({type: "click"}, "target2", gTestFrame.contentWindow); + window.setTimeout(doNextStep, 1000); + break; + + case 2: + is(onloadCount, 1, "opener must be null"); + sendMouseEvent({type: "click"}, "target1", gTestFrame.contentWindow); + break; + + case 3: + is(onloadCount, 2, "don't send referrer with rel=referrer"); + SimpleTest.finish(); + break; + } +} +</script> +</pre> +</html> diff --git a/docshell/test/test_bug540462.html b/docshell/test/test_bug540462.html new file mode 100644 index 000000000..65447114f --- /dev/null +++ b/docshell/test/test_bug540462.html @@ -0,0 +1,44 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=540462 +--> +<head> + <title>Test for Bug 540462</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="runTest()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=540462">Mozilla Bug 540462</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 540462 **/ + +var win; +function runTest() { + win = window.open("file_bug540462.html", "", "width=100,height=100"); +} + +var dwlCount = 0; +var originalURL; +function documentWriteLoad() { + if (++dwlCount == 1) { + originalURL = win.document.body.firstChild.href; + } else if (dwlCount == 2) { + is(win.document.body.firstChild.href, originalURL, "Wrong href!"); + win.close(); + SimpleTest.finish(); + } +} + +SimpleTest.waitForExplicitFinish(); + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug551225.html b/docshell/test/test_bug551225.html new file mode 100644 index 000000000..00dc5e076 --- /dev/null +++ b/docshell/test/test_bug551225.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=551225 +--> +<head> + <title>Test for Bug 551225</title> + <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=551225">Mozilla Bug 551225</a> + +<script type="application/javascript;version=1.7"> + +/** Test for Bug 551225 **/ + +obj = { + a: new Date('1/1/2000'), + b: /^foo$/, + c: 'bar' +}; + +history.replaceState(obj, '', ''); +is(history.state.a.toString(), new Date('1/1/2000').toString(), 'Date object.'); +is(history.state.b.toString(), '/^foo$/', 'Regex'); +is(history.state.c, 'bar', 'Other state'); + +</script> +</body> +</html> diff --git a/docshell/test/test_bug570341.html b/docshell/test/test_bug570341.html new file mode 100644 index 000000000..d09afa4bd --- /dev/null +++ b/docshell/test/test_bug570341.html @@ -0,0 +1,142 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=570341 +--> +<head> + <title>Test for Bug 570341</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +<script> + var start = Date.now(); + var moments = {}; + + var unload = 0; + var wasEnabled = true; + + function collectMoments() { + var win = frames[0]; + var timing = (win.performance && win.performance.timing) || {}; + for (var p in timing) { + moments[p] = timing[p]; + } + for (var p in win) { + if (p.substring(0,9) == '_testing_') { + moments[p.substring(9)] = win[p]; + } + } + moments['evt_unload'] = unload; + return moments; + } + + function showSequence(node){ + while(node.firstChild) { + node.removeChild(node.firstChild); + } + var sequence = []; + for (var p in moments) { + sequence.push(p); + } + sequence.sort(function(a, b){ + return moments[a] - moments[b]; + }); + table = document.createElement('table'); + node.appendChild(table); + row = document.createElement('tr'); + table.appendChild(row); + cell = document.createElement('td'); + row.appendChild(cell); + cell.appendChild(document.createTextNode('start')); + cell = document.createElement('td'); + row.appendChild(cell); + cell.appendChild(document.createTextNode(start)); + for (var i = 0; i < sequence.length; ++i) { + var prop = sequence[i]; + row = document.createElement('tr'); + table.appendChild(row); + cell = document.createElement('td'); + row.appendChild(cell); + cell.appendChild(document.createTextNode(prop)); + cell = document.createElement('td'); + row.appendChild(cell); + cell.appendChild(document.createTextNode(moments[prop])); + } + } + + function checkValues(){ + var win = frames[0]; + ok(win.performance, + 'window.performance is missing or not accessible for frame'); + ok(!win.performance || win.performance.timing, + 'window.performance.timing is missing or not accessible for frame'); + collectMoments(); + + var sequences = [ + ['navigationStart', 'unloadEventStart', 'unloadEventEnd'], + ['navigationStart', 'fetchStart', 'domainLookupStart', 'domainLookupEnd', + 'connectStart', 'connectEnd', 'requestStart', 'responseStart', 'responseEnd'], + ['responseStart', 'domLoading', 'domInteractive', 'domComplete'], + ['domContentLoadedEventStart', 'domContentLoadedEventEnd', + 'loadEventStart', 'loadEventEnd'] + ] + + for (var i = 0; i < sequences.length; ++i) { + var seq = sequences[i]; + for (var j = 0; j < seq.length; ++j) { + var prop = seq[j]; + if (j > 0) { + var prevProp = seq[j-1]; + ok(moments[prevProp] <= moments[prop], + ['Expected ', prevProp, ' to happen before ', prop, + ', got ', prevProp, ' = ', moments[prevProp], + ', ', prop, ' = ', moments[prop]].join('')); + } + } + } + + SimpleTest.finish() + } + +window.onload = function() { + var win = frames[0]; + win.addEventListener('unload', function(){ + unload = Date.now(); + }, true); + var seenLoad = 0; + win.addEventListener('load', function (){ + seenLoad = Date.now(); + }, true); + frames[0].location = 'bug570341_recordevents.html' + var interval = setInterval(function () { + // time constants here are arbitrary, chosen to allow the test to pass + var stopPolling = (win.performance && win.performance.loadEventEnd) || + (seenLoad && Date.now() >= seenLoad + 3000) || + Date.now() >= start + 30000; + if (stopPolling) { + clearInterval(interval); + checkValues(); + } else if (win._testing_evt_load) { + seenLoad = Date.now(); + } + }, 100); +} +</script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=570341">Mozilla Bug 570341</a> +<div id="frames"> +<iframe name="child0" src="navigation/blank.html"></iframe> +</div> +<button type="button" onclick="showSequence(document.getElementById('display'))"> + Show Events</button> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> +SimpleTest.waitForExplicitFinish(); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug580069.html b/docshell/test/test_bug580069.html new file mode 100644 index 000000000..70d3b4a3e --- /dev/null +++ b/docshell/test/test_bug580069.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=580069 +--> +<head> + <title>Test for Bug 580069</title> + <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=580069">Mozilla Bug 580069</a> + +<iframe id='iframe' src='file_bug580069_1.html'></iframe> + +<script type="application/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var iframe = document.getElementById('iframe'); +var iframeCw = iframe.contentWindow; + +// Called when file_bug580069_1.html loads. +function page1Load() { + // This should cause us to load file 2. + dump('page1Load\n'); + iframeCw.document.getElementById('form').submit(); +} + +// Called when file_bug580069_2.html loads. +var page2Loads = 0; +function page2Load(method) { + + dump("iframe's location is: " + iframeCw.location + ", method is " + method + "\n"); + + if (page2Loads == 0) { + is(method, "POST", "Method for first load should be POST."); + iframeCw.history.replaceState('', '', '?replaced'); + + // This refresh shouldn't pop up the "are you sure you want to refresh a page + // with POST data?" dialog. If it does, this test will hang and fail, and + // we'll see 'Refreshing iframe...' at the end of the test log. + dump('Refreshing iframe...\n'); + iframeCw.location.reload(); + } + else if (page2Loads == 1) { + is(method, "GET", "Method for second load should be GET."); + is(iframeCw.location.search, "?replaced", "Wrong search on iframe after refresh."); + SimpleTest.finish(); + } + else { + ok(false, "page2Load should only be called twice."); + } + + page2Loads++; +} +</script> + +</body> +</html> diff --git a/docshell/test/test_bug590573.html b/docshell/test/test_bug590573.html new file mode 100644 index 000000000..aa6d3bd79 --- /dev/null +++ b/docshell/test/test_bug590573.html @@ -0,0 +1,232 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=590573 +--> +<head> + <title>Test for Bug 590573</title> + <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=590573">Mozilla Bug 590573</a> + +<script type='application/javascript;version=1.7'> +SimpleTest.waitForExplicitFinish(); + +// Listen to the first callback, since this indicates that the page loaded. +var page1LoadCallbackEnabled = true; +function page1Load() +{ + if (page1LoadCallbackEnabled) { + page1LoadCallbackEnabled = false; + dump('Got page1 load.\n'); + pageLoad(); + } + else { + dump('Ignoring page1 load.\n'); + } +} + +var page1PopstateCallbackEnabled = false; +function page1Popstate() +{ + if (page1PopstateCallbackEnabled) { + page1PopstateCallbackEnabled = false; + dump('Got page1 popstate.\n'); + pageLoad(); + } + else { + dump('Ignoring page1 popstate.\n'); + } +} + +var page1PageShowCallbackEnabled = false; +function page1PageShow() +{ + if (page1PageShowCallbackEnabled) { + page1PageShowCallbackEnabled = false; + dump('Got page1 pageshow.\n'); + pageLoad(); + } + else { + dump('Ignoring page1 pageshow.\n'); + } +} + +var page2LoadCallbackEnabled = false; +function page2Load() +{ + if (page2LoadCallbackEnabled) { + page2LoadCallbackEnabled = false; + dump('Got page2 popstate.\n'); + pageLoad(); + } + else { + dump('Ignoring page2 popstate.\n'); + } +} + +var page2PopstateCallbackEnabled = false; +function page2Popstate() +{ + if (page2PopstateCallbackEnabled) { + page2PopstateCallbackEnabled = false; + dump('Got page2 popstate.\n'); + pageLoad(); + } + else { + dump('Ignoring page2 popstate.\n'); + } +} + +var page2PageShowCallbackEnabled = false; +function page2PageShow() +{ + if (page2PageShowCallbackEnabled) { + page2PageShowCallbackEnabled = false; + dump('Got page2 pageshow.\n'); + pageLoad(); + } + else { + dump('Ignoring page2 pageshow.\n'); + } +} + +function dumpSHistory(theWindow) +{ + let sh = SpecialPowers.wrap(theWindow).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIWebNavigation) + .sessionHistory; + if (!sh) { + dump(" window has no shistory.\n"); + return; + } + + dump(" count: " + sh.count + "\n"); + dump(" index: " + sh.index + "\n"); + dump(" requestedIndex: " + sh.requestedIndex + "\n"); + + for (let i = 0; i < sh.count; i++) { + let shentry = sh.getEntryAtIndex(i, false); + dump(" " + i + ": " + shentry.URI.spec + '\n'); + shentry.QueryInterface(SpecialPowers.Ci.nsISHContainer); + for (let j = 0; j < shentry.childCount; j++) { + let child = shentry.GetChildAt(j); + dump(" child " + j + ": " + child.URI.spec + '\n'); + } + } + + return sh; +} + +var popup = window.open('file_bug590573_1.html'); + +var gTestContinuation = null; +var loads = 0; +function pageLoad() +{ + loads++; + dump('pageLoad(loads=' + loads + ', page location=' + popup.location + ')\n'); + + dumpSHistory(window); + + if (!gTestContinuation) { + gTestContinuation = testBody(); + } + var ret = gTestContinuation.next(); + if (ret.done) { + SimpleTest.finish(); + } +} + +function* testBody() +{ + is(popup.scrollY, 0, "test 1"); + popup.scroll(0, 100); + + popup.history.pushState('', '', '?pushed'); + is(popup.scrollY, 100, "test 2"); + popup.scroll(0, 200); // set state-2's position to 200 + + popup.history.back(); + is(popup.scrollY, 100, "test 3"); + popup.scroll(0, 150); // set original page's position to 150 + + popup.history.forward(); + is(popup.scrollY, 200, "test 4"); + + popup.history.back(); + is(popup.scrollY, 150, "test 5"); + + popup.history.forward(); + is(popup.scrollY, 200, "test 6"); + + // At this point, the history looks like: + // PATH POSITION + // file_bug590573_1.html 150 <-- oldest + // file_bug590573_1.html?pushed 200 <-- newest, current + + // Now test that the scroll position is persisted when we have real + // navigations involved. First, we need to spin the event loop so that the + // navigation doesn't replace our current history entry. + + setTimeout(pageLoad, 0); + yield; + + page2LoadCallbackEnabled = true; + popup.location = 'file_bug590573_2.html'; + yield; + + ok(popup.location.href.match('file_bug590573_2.html$'), + "Location was " + popup.location + + " but should end with file_bug590573_2.html"); + + is(popup.scrollY, 0, "test 7"); + popup.scroll(0, 300); + + // We need to spin the event loop again before we go back, otherwise the + // scroll positions don't get updated properly. + setTimeout(pageLoad, 0); + yield; + + page1PageShowCallbackEnabled = true; + popup.history.back(); + yield; + + // Spin the event loop again so that we get the right scroll positions. + setTimeout(pageLoad, 0); + yield; + + is(popup.location.search, "?pushed"); + ok(popup.document.getElementById('div1'), 'page should have div1.'); + + is(popup.scrollY, 200, "test 8"); + + popup.history.back(); + is(popup.scrollY, 150, "test 9"); + popup.history.forward(); + + is(popup.scrollY, 200, "test 10"); + + // Spin one last time... + setTimeout(pageLoad, 0); + yield; + + page2PageShowCallbackEnabled = true; + popup.history.forward(); + yield; + + // Bug 821821, on Android tegras we get 299 instead of 300 sometimes + if (popup.scrollY >= 299 && popup.scrollY <= 300) { + is(1, 1, "test 11"); + } else { + is(1, 0, "test 11, got " + popup.scrollY + " for popup.scrollY instead of 299|300"); + } + popup.close(); +} +</script> + +</body> +</html> diff --git a/docshell/test/test_bug598895.html b/docshell/test/test_bug598895.html new file mode 100644 index 000000000..52b9537be --- /dev/null +++ b/docshell/test/test_bug598895.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=598895 +--> +<head> + <title>Test for Bug 598895</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.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=598895">Mozilla Bug 598895</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 598895 **/ +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { +var win1 = window.open(); +win1.document.body.textContent = "Should show"; + +var windowsLoaded = 0; + +window.onmessage = function (ev) { + is(ev.data, "loaded", "Message should be 'loaded'"); + if (++windowsLoaded == 2) { + var one = snapshotWindow(win1); + var two = snapshotWindow(win2); + var three = snapshotWindow(win3); + win1.close(); + win2.close(); + win3.close(); + ok(compareSnapshots(one, two, true)[0], "Popups should look identical"); + ok(compareSnapshots(one, three, false)[0], "Popups should not look identical"); + + SimpleTest.finish(); + } +} + +var win2 = window.open("data:text/html,<script>window.onload = function() { opener.postMessage('loaded', '*'); }</" + "script><body>Should show</body>"); + +var win3 = window.open("data:text/html,<script>window.onload = function() { opener.postMessage('loaded', '*'); }</" + "script><body></body>"); +}); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug634834.html b/docshell/test/test_bug634834.html new file mode 100644 index 000000000..88f068518 --- /dev/null +++ b/docshell/test/test_bug634834.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=634834 +--> +<head> + <title>Test for Bug 634834</title> + <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=634834">Mozilla Bug 634834</a> + +<script type='application/javascript;version=1.7'> +SimpleTest.waitForExplicitFinish(); + +function iframe_loaded() { + var loadedAfterPushstate = false; + $('iframe').onload = function() { + loadedAfterPushstate = true; + } + + var obj = { name: 'name' }; + obj.__defineGetter__('a', function() { + $('iframe').contentWindow.location = 'http://example.com'; + + // Wait until we've loaded example.com. + do { + var r = new XMLHttpRequest(); + r.open("GET", location.href, false); + r.overrideMimeType("text/plain"); + try { r.send(null); } + catch (e) {} + } while (!loadedAfterPushstate); + }); + + try { + $('iframe').contentWindow.history.pushState(obj, ''); + ok(false, 'pushState should throw exception.'); + } + catch(e) { + ok(true, 'pushState threw an exception.'); + } + SimpleTest.finish(); +} + +</script> + +<iframe id='iframe' src='file_bug634834.html' onload='iframe_loaded()'></iframe> + +</body> +</html> diff --git a/docshell/test/test_bug637644.html b/docshell/test/test_bug637644.html new file mode 100644 index 000000000..d172ada4a --- /dev/null +++ b/docshell/test/test_bug637644.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=637644 +--> +<head> + <title>Test for Bug 637644</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.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=637644">Mozilla Bug 637644</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 637644 **/ +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { +var win1 = window.open("", "", "height=500,width=500"); +win1.document.body.textContent = "Should show"; + +var windowsLoaded = 0; + +window.onmessage = function (ev) { + is(ev.data, "loaded", "Message should be 'loaded'"); + if (++windowsLoaded == 2) { + var one = snapshotWindow(win1); + var two = snapshotWindow(win2); + var three = snapshotWindow(win3); + win1.close(); + win2.close(); + win3.close(); + ok(compareSnapshots(one, two, true)[0], "Popups should look identical"); + ok(compareSnapshots(one, three, false)[0], "Popups should not look identical"); + + SimpleTest.finish(); + } +} + +var win2 = window.open("data:text/html,<script>window.onload = function() { opener.postMessage('loaded', '*'); }</" + "script><body>Should show</body>", "", "height=500,width=500"); + +var win3 = window.open("data:text/html,<script>window.onload = function() { opener.postMessage('loaded', '*'); }</" + "script><body></body>", "", "height=500,width=500"); +}); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug640387_1.html b/docshell/test/test_bug640387_1.html new file mode 100644 index 000000000..e324787d4 --- /dev/null +++ b/docshell/test/test_bug640387_1.html @@ -0,0 +1,109 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=640387 +--> +<head> + <title>Test for Bug 640387</title> + <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=640387">Mozilla Bug 640387</a> + +<script type='application/javascript;version=1.7'> +SimpleTest.waitForExplicitFinish(); + +function test() { + /* Spin the event loop so we get out of the onload handler. */ + SimpleTest.executeSoon(function() { gGen.next() }); + yield undefined; + + popup.history.pushState('', '', '#hash1'); + popup.history.pushState('', '', '#hash2'); + + // Now the history looks like: + // file_bug640387.html + // file_bug640387.html#hash1 + // file_bug640387.html#hash2 <-- current + + // Going back should trigger a hashchange, which will wake us up from the + // yield. + popup.history.back(); + yield undefined; + ok(true, 'Got first hashchange.'); + + // Going back should wake us up again. + popup.history.back(); + yield undefined; + ok(true, 'Got second hashchange.'); + + // Now the history looks like: + // file_bug640387.html <-- current + // file_bug640387.html#hash1 + // file_bug640387.html#hash2 + + // Going forward should trigger a hashchange. + popup.history.forward(); + yield undefined; + ok(true, 'Got third hashchange.'); + + // Now modify the history so it looks like: + // file_bug640387.html + // file_bug640387.html#hash1 + // file_bug640387.html#hash1 <-- current + popup.history.pushState('', '', '#hash1'); + + // Now when we go back, we should not get a hashchange. Instead, wait for a + // popstate. We need to asynchronously go back because popstate is fired + // sync. + gHashchangeExpected = false; + gCallbackOnPopstate = true; + SimpleTest.executeSoon(function() { popup.history.back() }); + yield undefined; + ok(true, 'Got popstate.'); + gCallbackOnPopstate = false; + + // Spin the event loop so hashchange has a chance to fire, if it's going to. + SimpleTest.executeSoon(function() { gGen.next() }); + yield undefined; + + popup.close(); + SimpleTest.finish(); + yield undefined; +} + +gGen = null; +function childLoad() { + gGen = test(); + gGen.next(); +} + +gHashchangeExpected = true; +function childHashchange() { + if (gHashchangeExpected) { + gGen.next(); + } + else { + ok(false, "Got hashchange when we weren't expecting one."); + } +} + +gCallbackOnPopstate = false; +function childPopstate() { + if (gCallbackOnPopstate) { + gGen.next(); + } +} + +/* We need to run this test in a popup, because navigating an iframe + * back/forwards tends to cause intermittent orange. */ +popup = window.open('file_bug640387.html'); + +/* Control now flows up to childLoad(), called once the popup loads. */ + +</script> + +</body> +</html> diff --git a/docshell/test/test_bug640387_2.html b/docshell/test/test_bug640387_2.html new file mode 100644 index 000000000..00c27fdda --- /dev/null +++ b/docshell/test/test_bug640387_2.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=640387 +--> +<head> + <title>Test for Bug 640387</title> + <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=640387">Mozilla Bug 640387</a> + +<!-- Test that, when going from + + http://example.com/#foo + +to + + http://example.com/ + +via a non-history load, we do a true load, rather than a scroll. --> + +<script type='application/javascript;version=1.7'> +SimpleTest.waitForExplicitFinish(); + +callbackOnLoad = false; +function childLoad() { + if (callbackOnLoad) { + callbackOnLoad = false; + gGen.next(); + } +} + +errorOnHashchange = false; +callbackOnHashchange = false; +function childHashchange() { + if (errorOnHashchange) { + ok(false, 'Got unexpected hashchange.'); + } + if (callbackOnHashchange) { + callbackOnHashchange = false; + gGen.next(); + } +} + +function run_test() { + var iframe = $('iframe').contentWindow; + + ok(true, 'Got first load'); + + // Spin the event loop so we exit the onload handler. + SimpleTest.executeSoon(function() { gGen.next() }); + yield undefined; + + let origLocation = iframe.location + ''; + callbackOnHashchange = true; + iframe.location.hash = '#1'; + // Wait for a hashchange event. + yield undefined; + + ok(true, 'Got hashchange.'); + + iframe.location = origLocation; + // This should produce a load event and *not* a hashchange, because the + // result of the load is a different document than we had previously. + callbackOnLoad = true; + errorOnHashchange = true; + yield undefined; + + ok(true, 'Got final load.'); + + // Spin the event loop to give hashchange a chance to fire, if it's going to. + SimpleTest.executeSoon(function() { gGen.next() }); + yield undefined; + + SimpleTest.finish(); + yield undefined; +} + +callbackOnLoad = true; +gGen = run_test(); + +</script> + +<iframe id='iframe' src='file_bug640387.html'></iframe> + +</body> +</html> diff --git a/docshell/test/test_bug653741.html b/docshell/test/test_bug653741.html new file mode 100644 index 000000000..f4d4587b8 --- /dev/null +++ b/docshell/test/test_bug653741.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=653741 +--> +<head> + <title>Test for Bug 653741</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.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=653741">Mozilla Bug 653741</a> + +<script type="application/javascript;version=1.7"> + +/** Test for Bug 653741 **/ +SimpleTest.waitForExplicitFinish(); + +function childLoad() { + // Spin the event loop so we leave the onload handler. + SimpleTest.executeSoon(childLoad2); +} + +function childLoad2() { + let cw = $('iframe').contentWindow; + + // Save the Y offset. For sanity's sake, make sure it's not 0, because we + // should be at the bottom of the page! + let origYOffset = cw.pageYOffset; + ok(origYOffset != 0, 'Original Y offset is not 0.'); + + // Scroll the iframe to the top, then navigate to #bottom again. + cw.scrollTo(0, 0); + + // Our current location is #bottom, so this should scroll us down to the + // bottom again. + cw.location = cw.location + ''; + + is(cw.pageYOffset, origYOffset, 'Correct offset after reloading page.'); + SimpleTest.finish(); +} + +</script> + +<iframe height='100px' id='iframe' src='file_bug653741.html#bottom'></iframe> + +</body> +</html> diff --git a/docshell/test/test_bug660404.html b/docshell/test/test_bug660404.html new file mode 100644 index 000000000..df45793a9 --- /dev/null +++ b/docshell/test/test_bug660404.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=660404 +--> +<head> + <title>Test for Bug 660404</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=660404">Mozilla Bug 660404</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 660404 **/ +SimpleTest.waitForExplicitFinish(); + +var w; + +function continueTest() { + // Do this async so the load event can finish firing + SimpleTest.executeSoon(function() { + w.onpagehide = function(ev) { + is(ev.persisted, true, "Should be bfcached when navigating to multipart"); + } + w.location.href = "file_bug660404"; + }); +} + +function finishTest() { + is(w.document.documentElement.textContent, "opener.finishTest();"); + is(w.document.documentElement.innerHTML, "<head><script>opener.finishTest();</"+"script></head>"); + w.close(); + SimpleTest.finish(); +} + +// Have to open a new window, since there's no bfcache in subframes +w = window.open("data:text/html,<script>window.onload = function() { opener.continueTest(); }</"+"script>"); + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug662170.html b/docshell/test/test_bug662170.html new file mode 100644 index 000000000..514bb55b1 --- /dev/null +++ b/docshell/test/test_bug662170.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=662170 +--> +<head> + <title>Test for Bug 662170</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.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=662170">Mozilla Bug 662170</a> + +<script type="application/javascript;version=1.7"> + +/** Test for Bug 662170 **/ +SimpleTest.waitForExplicitFinish(); + +function childLoad() { + // Spin the event loop so we leave the onload handler. + SimpleTest.executeSoon(childLoad2); +} + +function childLoad2() { + let cw = $('iframe').contentWindow; + + // When we initially load the page, we should be at the top. + is(cw.pageYOffset, 0, 'Initial Y offset should be 0.'); + + // Scroll the iframe to the bottom. + cw.scrollTo(0, 300); + + // Did we actually scroll somewhere? + isnot(cw.pageYOffset, 0, 'Y offset should be non-zero after scrolling.'); + + // Now load file_bug662170.html#, which should take us to the top of the + // page. + cw.location = cw.location + '#'; + + is(cw.pageYOffset, 0, 'Correct Y offset after loading #.'); + SimpleTest.finish(); +} + +</script> + +<!-- When the iframe loads, it calls childLoad(). --> +<iframe height='100px' id='iframe' src='file_bug662170.html'></iframe> + +</body> +</html> diff --git a/docshell/test/test_bug668513.html b/docshell/test/test_bug668513.html new file mode 100644 index 000000000..78df4b135 --- /dev/null +++ b/docshell/test/test_bug668513.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=668513 +--> +<head> + <title>Test for Bug 668513</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=668513">Mozilla Bug 668513</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> +if (navigator.platform.startsWith("Linux")) { + SimpleTest.expectAssertions(0, 1); +} + +SimpleTest.waitForExplicitFinish(); +window.open("file_bug668513.html"); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug669671.html b/docshell/test/test_bug669671.html new file mode 100644 index 000000000..6070ecc82 --- /dev/null +++ b/docshell/test/test_bug669671.html @@ -0,0 +1,146 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=669671 +--> +<head> + <title>Test for Bug 669671</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=669671">Mozilla Bug 669671</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript;version=1.7"> + +/** + * Test for Bug 669671. + * + * This is a bit complicated. We have a script, file_bug669671.sjs, which counts + * how many times it's loaded and returns that count in the body of an HTML + * document. For brevity, call this page X. + * + * X is sent with Cache-Control: max-age=0 and can't be bfcached (it has an + * onunload handler). Our test does the following in a popup: + * + * 1) Load X?pushed, to prime the cache. + * 2) Navigate to X. + * 3) Call pushState and navigate from X to X?pushed. + * 4) Navigate to X?navigated. + * 5) Go back (to X?pushed). + * + * We do all this work so we can check that in step 5, we fetch X?pushed from + * the network -- we shouldn't use our cached copy, because of the + * cache-control header X sends. + * + * Then we go back and repeat the whole process but call history.replaceState + * instead of pushState. And for good measure, we test once more, this time + * modifying only the hash of the URI using replaceState. In this case, we + * *should* load from the cache. + * + **/ +SimpleTest.requestLongerTimeout(2); +SimpleTest.waitForExplicitFinish(); + +function onChildLoad() +{ + SimpleTest.executeSoon(function() { gGen.next() }); +} + +var _loadCount = 0; +function checkPopupLoadCount() +{ + is(popup.document.body.innerHTML, _loadCount + '', 'Load count'); + + // We normally want to increment _loadCount here. But if the test fails + // because we didn't do a load we should have, let's not cause a cascade of + // failures by incrementing _loadCount. + var origCount = _loadCount; + if (popup.document.body.innerHTML >= _loadCount + '') + _loadCount++; + return origCount; +} + +function test() +{ + // Step 0 - Make sure the count is reset to 0 in case of reload + popup.location = 'file_bug669671.sjs?countreset'; + yield; + is(popup.document.body.innerHTML, '0', + 'Load count should be reset to 0'); + + // Step 1 - The popup's body counts how many times we've requested the + // resource. This is the first time we've requested it, so it should be '0'. + checkPopupLoadCount(); + + // Step 2 - We'll get another onChildLoad when this finishes. + popup.location = 'file_bug669671.sjs'; + yield undefined; + + // Step 3 - Call pushState and change the URI back to ?pushed. + checkPopupLoadCount(); + popup.history.pushState('', '', '?pushed'); + + // Step 4 - Navigate away. This should trigger another onChildLoad. + popup.location = 'file_bug669671.sjs?navigated-1'; + yield undefined; + + // Step 5 - Go back. This should result in another onload (because the file is + // not in bfcache) and should be the fourth time we've requested the sjs file. + checkPopupLoadCount(); + SpecialPowers.wrap(popup).back(); + yield undefined; + + // This is the check which was failing before we fixed the bug. + checkPopupLoadCount(); + + popup.close(); + + // Do the whole thing again, but with replaceState. + popup = window.open('file_bug669671.sjs?replaced'); + yield undefined; + checkPopupLoadCount(); + popup.location = 'file_bug669671.sjs'; + yield undefined; + checkPopupLoadCount(); + popup.history.replaceState('', '', '?replaced'); + popup.location = 'file_bug669671.sjs?navigated-2'; + yield undefined; + checkPopupLoadCount(); + SpecialPowers.wrap(popup).back(); + yield undefined; + checkPopupLoadCount(); + popup.close(); + + // Once more, with feeling. Notice that we don't have to prime the cache + // with an extra load here, because X and X#hash share the same cache entry. + popup = window.open('file_bug669671.sjs?hash-test'); + yield undefined; + var initialCount = checkPopupLoadCount(); + popup.history.replaceState('', '', '#hash'); + popup.location = 'file_bug669671.sjs?navigated-3'; + yield undefined; + checkPopupLoadCount(); + SpecialPowers.wrap(popup).back(); + yield undefined; + is(popup.document.body.innerHTML, initialCount + '', + 'Load count (should be cached)'); + popup.close(); + + SimpleTest.finish(); + yield undefined; +} + +// This will call into onChildLoad once it loads. +var popup = window.open('file_bug669671.sjs?pushed'); + +var gGen = test(); + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug675587.html b/docshell/test/test_bug675587.html new file mode 100644 index 000000000..9a283d3ac --- /dev/null +++ b/docshell/test/test_bug675587.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=675587 +--> +<head> + <title>Test for Bug 675587</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=675587">Mozilla Bug 675587</a> +<p id="display"> + <iframe src="data:text/html,<script>location.hash='';</script>#hash"></iframe> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 675587 **/ +SimpleTest.waitForExplicitFinish(); +addLoadEvent(function() { + is(window.frames[0].location.href, + "data:text/html,<script>location.hash='';</" + "script>#", + "Should have the right href"); + SimpleTest.finish(); +}); + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug680257.html b/docshell/test/test_bug680257.html new file mode 100644 index 000000000..bf7c32d11 --- /dev/null +++ b/docshell/test/test_bug680257.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=680257 +--> +<head> + <title>Test for Bug 680257</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=680257">Mozilla Bug 680257</a> + +<script type="application/javascript;version=1.7"> + +SimpleTest.waitForExplicitFinish(); + +var popup = window.open('file_bug680257.html'); + +// The popup will call into popupLoaded() once it loads. +function popupLoaded() { + // runTests() needs to be called from outside popupLoaded's onload handler. + // Otherwise, the navigations we do in runTests won't create new SHEntries. + SimpleTest.executeSoon(runTests); +} + +function runTests() { + checkPopupLinkStyle(false, 'Initial'); + + popup.location.hash = 'a'; + checkPopupLinkStyle(true, 'After setting hash'); + + popup.history.back(); + checkPopupLinkStyle(false, 'After going back'); + + popup.history.forward(); + checkPopupLinkStyle(true, 'After going forward'); + + popup.close(); + SimpleTest.finish(); +} + +function checkPopupLinkStyle(isTarget, desc) { + var link = popup.document.getElementById('a'); + var style = popup.getComputedStyle(link); + var color = style.getPropertyValue('color'); + + // Color is red if isTarget, black otherwise. + if (isTarget) { + is(color, 'rgb(255, 0, 0)', desc); + } + else { + is(color, 'rgb(0, 0, 0)', desc); + } +} + +</script> +</body> +</html> diff --git a/docshell/test/test_bug691547.html b/docshell/test/test_bug691547.html new file mode 100644 index 000000000..9bf4f1004 --- /dev/null +++ b/docshell/test/test_bug691547.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=691547 +--> +<head> + <title>Test for Bug 691547</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + var navStart = 0; + var beforeReload = 0; + function onContentLoad() { + var frame = frames[0]; + if (!navStart) { + // First time we perform navigation in subframe. The bug is that + // load in subframe causes timing.navigationStart to be recorded + // as if it was a start of the next navigation. + var innerFrame = frame.frames[0]; + navStart = frame.performance.timing.navigationStart; + innerFrame.location = 'bug570341_recordevents.html'; + // Let's wait a bit so the difference is clear anough. + setTimeout(reload, 3000); + } + else { + // Content reloaded, time to check. We are allowing a huge time slack, + // in case clock is imprecise. If we have a bug, the difference is + // expected to be about the timeout value set above. + var diff = frame.performance.timing.navigationStart - beforeReload; + ok(diff >= -200, + 'navigationStart should be set after reload request. ' + + 'Measured difference: ' + diff + ' (should be positive)'); + SimpleTest.finish(); + } + } + function reload() { + var frame = frames[0]; + ok(navStart == frame.performance.timing.navigationStart, + 'navigationStart should not change when frame loads.'); + beforeReload = Date.now(); + frame.location.reload(); + } + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=570341">Mozilla Bug 570341</a> +<div id="frames"> +<iframe name="frame0" id="frame0" src="bug691547_frame.html" onload="onContentLoad()"></iframe> +</div> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> +SimpleTest.waitForExplicitFinish(); +SimpleTest.requestFlakyTimeout("untriaged"); +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug694612.html b/docshell/test/test_bug694612.html new file mode 100644 index 000000000..85c240146 --- /dev/null +++ b/docshell/test/test_bug694612.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=694612 +--> +<head> + <title>Test for Bug 694612</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=694612">Mozilla Bug 694612</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> +/** Test for Bug 694612 **/ +SimpleTest.waitForExplicitFinish(); +</script> +<object type="text/html" + data="data:text/html,<script>parent.isnot(performance, null, 'should have performance API in an <object>'); parent.SimpleTest.finish();</script>"> +</object> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug703855.html b/docshell/test/test_bug703855.html new file mode 100644 index 000000000..0804f70bc --- /dev/null +++ b/docshell/test/test_bug703855.html @@ -0,0 +1,79 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=703855 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 703855</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=703855">Mozilla Bug 703855</a> +<p id="display"></p> +<div id="content" style="display: none"> + <iframe id="f" src="file_bug703855.html"></iframe> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 703855 **/ + +SimpleTest.waitForExplicitFinish(); + +var timingAttributes = [ + 'connectEnd', + 'connectStart', + 'domComplete', + 'domContentLoadedEventEnd', + 'domContentLoadedEventStart', + 'domInteractive', + 'domLoading', + 'domainLookupEnd', + 'domainLookupStart', + 'fetchStart', + 'loadEventEnd', + 'loadEventStart', + 'navigationStart', + 'redirectEnd', + 'redirectStart', + 'requestStart', + 'responseEnd', + 'responseStart', + 'unloadEventEnd', + 'unloadEventStart' +]; +var originalTiming = {}; + +function runTest() { + var timing = $("f").contentWindow.performance.timing; + for (i in timingAttributes) { + originalTiming[timingAttributes[i]] = timing[timingAttributes[i]]; + } + + var doc = $("f").contentDocument; + doc.open(); + doc.write("<!DOCTYPE html>"); + doc.close(); + + SimpleTest.executeSoon(function() { + var newTiming = $("f").contentWindow.performance.timing; + for (var i in timingAttributes) { + is(timing[timingAttributes[i]], originalTiming[timingAttributes[i]], + "document.open should not affect value of " + timingAttributes[i]); + } + SimpleTest.finish(); + }); +} + +addLoadEvent(function() { + SimpleTest.executeSoon(runTest); +}); + + + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug713825.html b/docshell/test/test_bug713825.html new file mode 100644 index 000000000..5f30dd0b6 --- /dev/null +++ b/docshell/test/test_bug713825.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=713825 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 713825</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=713825">Mozilla Bug 713825</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** + * Test for Bug 713825 + * test that nsIDocCharset still works backward compatibly + */ +var Ci = SpecialPowers.Ci; +var docShell = SpecialPowers.wrap(window).QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIWebNavigation). + QueryInterface(Ci.nsIDocShell); + +var charset1 = docShell.charset; +var charset2 = docShell.QueryInterface(Ci.nsIDocCharset).charset; +var charset3 = SpecialPowers.wrap(window).QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIDocCharset).charset; + +/* if we get here without throwing and the three charsets are equal, all is OK */ +is(charset1, charset2, "QI'd nsIDocCharset.charset should equal nsIDocShell.charset"); +is(charset1, charset3, "getInterface'd nsIDocCharset.charset should equal nsIDocShell.charset"); + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_bug728939.html b/docshell/test/test_bug728939.html new file mode 100644 index 000000000..c29f18668 --- /dev/null +++ b/docshell/test/test_bug728939.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=728939 +--> +<head> + <title>Test for Bug 728939</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=728939">Mozilla Bug 728939</a> + +<script type="application/javascript;version=1.7"> + +SimpleTest.waitForExplicitFinish(); + +// Called when the popup finishes loading. +function popupLoaded() { + popup.location.hash = '#foo'; + is(popup.document.URL, popup.location.href, 'After hashchange.'); + + popup.history.pushState('', '', 'bar'); + is(popup.document.URL, popup.location.href, 'After pushState.'); + + popup.history.replaceState('', '', 'baz'); + is(popup.document.URL, popup.location.href, 'After replaceState.'); + + popup.close(); + SimpleTest.finish(); +} + +var popup = window.open('file_bug728939.html'); + +</script> +</body> +</html> diff --git a/docshell/test/test_bug797909.html b/docshell/test/test_bug797909.html new file mode 100644 index 000000000..a47aa0c6d --- /dev/null +++ b/docshell/test/test_bug797909.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=797909 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 797909</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="runTest()"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=797909">Mozilla Bug 797909</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + /** Test for Bug 797909 **/ + + SimpleTest.waitForExplicitFinish(); + + function runTest() { + iframe = document.getElementById("ifr"); + try { + var iframeDoc = iframe.contentWindow.document; + ok(false, "Should have thrown an exception"); + } catch (ex) { + ok(true, "Got an exception"); + } + + iframe = document.createElement("iframe"); + // set sandbox attribute + iframe.sandbox = "allow-scripts"; + // and then insert into the doc + document.body.appendChild(iframe); + + try { + var iframeDoc = iframe.contentWindow.document; + ok(false, "Should have thrown an exception"); + } catch (ex) { + ok(true, "Got an exception"); + } + + iframe = document.createElement("iframe"); + // set sandbox attribute + iframe.sandbox = "allow-same-origin"; + // and then insert into the doc + document.body.appendChild(iframe); + + try { + var iframeDoc = iframe.contentWindow.document; + ok(true, "Shouldn't have thrown an exception"); + } catch (ex) { + ok(false, "Got an unexpected exception"); + } + + SimpleTest.finish(); + } + +</script> +</pre> +<iframe id="ifr" sandbox = "allow-scripts"></iframe> +</body> +</html> diff --git a/docshell/test/test_forceinheritprincipal_overrule_owner.html b/docshell/test/test_forceinheritprincipal_overrule_owner.html new file mode 100644 index 000000000..b3a48d7ea --- /dev/null +++ b/docshell/test/test_forceinheritprincipal_overrule_owner.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html> +<head> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<script type="text/javascript"> + +var channel = SpecialPowers.wrap(document).docShell.currentDocumentChannel; +var loadInfo = channel.loadInfo; + +// 1) perform some sanity checks +var triggeringPrincipal = channel.loadInfo.triggeringPrincipal.URI.asciiSpec; +var loadingPrincipal = channel.loadInfo.loadingPrincipal.URI.asciiSpec; +var principalToInherit = channel.loadInfo.principalToInherit.URI.asciiSpec; + +ok(triggeringPrincipal.startsWith("http://mochi.test:8888/"), + "initial triggeringPrincipal correct"); +ok(loadingPrincipal.startsWith("http://mochi.test:8888/"), + "initial loadingPrincipal correct"); +ok(principalToInherit.startsWith("http://mochi.test:8888/"), + "initial principalToInherit correct"); + +// reset principals on the loadinfo +loadInfo.resetPrincipalsToNullPrincipal(); + +// 2) verify loadInfo contains the correct principals +var triggeringPrincipal = channel.loadInfo.triggeringPrincipal; +var loadingPrincipal = channel.loadInfo.loadingPrincipal; +var principalToInherit = channel.loadInfo.principalToInherit; + +ok(triggeringPrincipal.isNullPrincipal, + "triggeringPrincipal after resetting correct"); +ok(loadingPrincipal.isNullPrincipal, + "triggeringPrincipal after resetting correct"); +ok(triggeringPrincipal.isNullPrincipal, + "principalToInherit after resetting correct"); + +// 3) verify that getChannelResultPrincipal returns right principal +var resultPrincipal = SpecialPowers.Services.scriptSecurityManager + .getChannelResultPrincipal(channel); + +ok(resultPrincipal.isNullPrincipal, + "resultPrincipal after resetting correct"); + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_framedhistoryframes.html b/docshell/test/test_framedhistoryframes.html new file mode 100644 index 000000000..916a6229d --- /dev/null +++ b/docshell/test/test_framedhistoryframes.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=602256 +--> +<head> + <title>Test for Bug 602256</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=602256">Mozilla Bug 602256</a> +<p id="display"></p> +<div id="content"> +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 602256 **/ + +SimpleTest.waitForExplicitFinish(); +var win = window.open("file_framedhistoryframes.html"); + +function done() { + win.close(); + SimpleTest.finish(); +} + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/test_pushState_after_document_open.html b/docshell/test/test_pushState_after_document_open.html new file mode 100644 index 000000000..51ba1050c --- /dev/null +++ b/docshell/test/test_pushState_after_document_open.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=957479 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 957479</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 957479 **/ + SimpleTest.waitForExplicitFinish(); + // Child needs to invoke us, otherwise our onload will fire before the child + // has done the write/close bit. + onmessage = function doTest() { + is(frames[0].location.pathname, "/tests/docshell/test/file_pushState_after_document_open.html", "Should have the right path here"); + is(frames[0].location.hash, "", "Should have the right hash here"); + frames[0].history.pushState({}, '', frames[0].document.URL + "#foopy"); + is(frames[0].location.pathname, "/tests/docshell/test/file_pushState_after_document_open.html", "Pathname should not have changed"); + is(frames[0].location.hash, "#foopy", "Hash should have changed"); + SimpleTest.finish(); + } + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=957479">Mozilla Bug 957479</a> +<p id="display"></p> +<div id="content" style="display: none"> +<iframe src="file_pushState_after_document_open.html"></iframe> +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/docshell/test/test_windowedhistoryframes.html b/docshell/test/test_windowedhistoryframes.html new file mode 100644 index 000000000..d27987beb --- /dev/null +++ b/docshell/test/test_windowedhistoryframes.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=602256 +--> +<head> + <title>Test for Bug 602256</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.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=602256">Mozilla Bug 602256</a> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 602256 **/ + +SimpleTest.waitForExplicitFinish(); + +function done() { + subWin.close(); + SimpleTest.finish(); +} + +var subWin = window.open("historyframes.html", "_blank"); + +</script> +</pre> +</body> +</html> diff --git a/docshell/test/unit/head_docshell.js b/docshell/test/unit/head_docshell.js new file mode 100644 index 000000000..a5c7546c0 --- /dev/null +++ b/docshell/test/unit/head_docshell.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cr = Components.results; + +var dirSvc = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties); +var profileDir = do_get_profile(); diff --git a/docshell/test/unit/test_bug414201_jfif.js b/docshell/test/unit/test_bug414201_jfif.js new file mode 100644 index 000000000..6a689ec08 --- /dev/null +++ b/docshell/test/unit/test_bug414201_jfif.js @@ -0,0 +1,27 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Test for bug 414201 + */ + +function run_test() +{ + var ms = Components.classes["@mozilla.org/mime;1"].getService(Components.interfaces.nsIMIMEService); + + /* Test a few common image types to make sure that they get the right extension */ + var types = { + "image/jpeg": ["jpg", "jpeg"], /* accept either */ + "image/gif": ["gif"], + "image/png": ["png"] + }; + + /* Check whether the primary extension is what we'd expect */ + for (var mimetype in types) { + var exts = types[mimetype]; + var primary = ms.getFromTypeAndExtension(mimetype, null).primaryExtension.toLowerCase(); + + do_check_true (exts.indexOf(primary) != -1); + } +} diff --git a/docshell/test/unit/test_bug442584.js b/docshell/test/unit/test_bug442584.js new file mode 100644 index 000000000..174bdb7e3 --- /dev/null +++ b/docshell/test/unit/test_bug442584.js @@ -0,0 +1,30 @@ +var prefetch = Cc["@mozilla.org/prefetch-service;1"]. + getService(Ci.nsIPrefetchService); +var ios = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); +var prefs = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + +function run_test() { + // Fill up the queue + prefs.setBoolPref("network.prefetch-next", true); + for (var i = 0; i < 5; i++) { + var uri = ios.newURI("http://localhost/" + i, null, null); + prefetch.prefetchURI(uri, uri, null, true); + } + + // Make sure the queue has items in it... + do_check_true(prefetch.hasMoreElements()); + + // Now disable the pref to force the queue to empty... + prefs.setBoolPref("network.prefetch-next", false); + do_check_false(prefetch.hasMoreElements()); + + // Now reenable the pref, and add more items to the queue. + prefs.setBoolPref("network.prefetch-next", true); + for (var i = 0; i < 5; i++) { + var uri = ios.newURI("http://localhost/" + i, null, null); + prefetch.prefetchURI(uri, uri, null, true); + } + do_check_true(prefetch.hasMoreElements()); +} diff --git a/docshell/test/unit/test_nsDefaultURIFixup.js b/docshell/test/unit/test_nsDefaultURIFixup.js new file mode 100644 index 000000000..371bdea17 --- /dev/null +++ b/docshell/test/unit/test_nsDefaultURIFixup.js @@ -0,0 +1,93 @@ +var urifixup = Cc["@mozilla.org/docshell/urifixup;1"]. + getService(Ci.nsIURIFixup); +var prefs = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + +var pref = "browser.fixup.typo.scheme"; + +var data = [ + { + // ttp -> http. + wrong: 'ttp://www.example.com/', + fixed: 'http://www.example.com/', + }, + { + // ttps -> https. + wrong: 'ttps://www.example.com/', + fixed: 'https://www.example.com/', + }, + { + // tps -> https. + wrong: 'tps://www.example.com/', + fixed: 'https://www.example.com/', + }, + { + // ps -> https. + wrong: 'ps://www.example.com/', + fixed: 'https://www.example.com/', + }, + { + // ile -> file. + wrong: 'ile:///this/is/a/test.html', + fixed: 'file:///this/is/a/test.html', + }, + { + // le -> file. + wrong: 'le:///this/is/a/test.html', + fixed: 'file:///this/is/a/test.html', + }, + { + // Valid should not be changed. + wrong: 'https://example.com/this/is/a/test.html', + fixed: 'https://example.com/this/is/a/test.html', + }, + { + // Unmatched should not be changed. + wrong: 'whatever://this/is/a/test.html', + fixed: 'whatever://this/is/a/test.html', + }, +]; + +var len = data.length; + +function run_test() { + run_next_test(); +} + +// Make sure we fix what needs fixing when there is no pref set. +add_task(function test_unset_pref_fixes_typos() { + prefs.clearUserPref(pref); + for (let i = 0; i < len; ++i) { + let item = data[i]; + let result = + urifixup.createFixupURI(item.wrong, + urifixup.FIXUP_FLAG_FIX_SCHEME_TYPOS).spec; + do_check_eq(result, item.fixed); + } +}); + +// Make sure we don't do anything when the pref is explicitly +// set to false. +add_task(function test_false_pref_keeps_typos() { + prefs.setBoolPref(pref, false); + for (let i = 0; i < len; ++i) { + let item = data[i]; + let result = + urifixup.createFixupURI(item.wrong, + urifixup.FIXUP_FLAG_FIX_SCHEME_TYPOS).spec; + do_check_eq(result, item.wrong); + } +}); + +// Finally, make sure we still fix what needs fixing if the pref is +// explicitly set to true. +add_task(function test_true_pref_fixes_typos() { + prefs.setBoolPref(pref, true); + for (let i = 0; i < len; ++i) { + let item = data[i]; + let result = + urifixup.createFixupURI(item.wrong, + urifixup.FIXUP_FLAG_FIX_SCHEME_TYPOS).spec; + do_check_eq(result, item.fixed); + } +}); diff --git a/docshell/test/unit/test_nsDefaultURIFixup_info.js b/docshell/test/unit/test_nsDefaultURIFixup_info.js new file mode 100644 index 000000000..9e33ea484 --- /dev/null +++ b/docshell/test/unit/test_nsDefaultURIFixup_info.js @@ -0,0 +1,620 @@ +var urifixup = Cc["@mozilla.org/docshell/urifixup;1"]. + getService(Ci.nsIURIFixup); + +Components.utils.import("resource://gre/modules/Services.jsm"); + +var prefList = ["browser.fixup.typo.scheme", "keyword.enabled", + "browser.fixup.domainwhitelist.whitelisted"]; +for (let pref of prefList) { + Services.prefs.setBoolPref(pref, true); +} + +const kSearchEngineID = "test_urifixup_search_engine"; +const kSearchEngineURL = "http://www.example.org/?search={searchTerms}"; +Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get", + kSearchEngineURL); + +var oldDefaultEngine = Services.search.defaultEngine; +Services.search.defaultEngine = Services.search.getEngineByName(kSearchEngineID); + +var selectedName = Services.search.defaultEngine.name; +do_check_eq(selectedName, kSearchEngineID); + +const kForceHostLookup = "browser.fixup.dns_first_for_single_words"; +do_register_cleanup(function() { + if (oldDefaultEngine) { + Services.search.defaultEngine = oldDefaultEngine; + } + let engine = Services.search.getEngineByName(kSearchEngineID); + if (engine) { + Services.search.removeEngine(engine); + } + Services.prefs.clearUserPref("keyword.enabled"); + Services.prefs.clearUserPref("browser.fixup.typo.scheme"); + Services.prefs.clearUserPref(kForceHostLookup); +}); + +var flagInputs = [ + urifixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP, + urifixup.FIXUP_FLAGS_MAKE_ALTERNATE_URI, + urifixup.FIXUP_FLAG_FIX_SCHEME_TYPOS, +]; + +flagInputs.concat([ + flagInputs[0] | flagInputs[1], + flagInputs[1] | flagInputs[2], + flagInputs[0] | flagInputs[2], + flagInputs[0] | flagInputs[1] | flagInputs[2] +]); + +/* + The following properties are supported for these test cases: + { + input: "", // Input string, required + fixedURI: "", // Expected fixedURI + alternateURI: "", // Expected alternateURI + keywordLookup: false, // Whether a keyword lookup is expected + protocolChange: false, // Whether a protocol change is expected + inWhitelist: false, // Whether the input host is in the whitelist + affectedByDNSForSingleHosts: false, // Whether the input host could be a host, but is normally assumed to be a keyword query + } +*/ +var testcases = [ { + input: "http://www.mozilla.org", + fixedURI: "http://www.mozilla.org/", + }, { + input: "http://127.0.0.1/", + fixedURI: "http://127.0.0.1/", + }, { + input: "file:///foo/bar", + fixedURI: "file:///foo/bar", + }, { + input: "://www.mozilla.org", + fixedURI: "http://www.mozilla.org/", + protocolChange: true, + }, { + input: "www.mozilla.org", + fixedURI: "http://www.mozilla.org/", + protocolChange: true, + }, { + input: "http://mozilla/", + fixedURI: "http://mozilla/", + alternateURI: "http://www.mozilla.com/", + }, { + input: "http://test./", + fixedURI: "http://test./", + alternateURI: "http://www.test./", + }, { + input: "127.0.0.1", + fixedURI: "http://127.0.0.1/", + protocolChange: true, + }, { + input: "1.2.3.4/", + fixedURI: "http://1.2.3.4/", + protocolChange: true, + }, { + input: "1.2.3.4/foo", + fixedURI: "http://1.2.3.4/foo", + protocolChange: true, + }, { + input: "1.2.3.4:8000", + fixedURI: "http://1.2.3.4:8000/", + protocolChange: true, + }, { + input: "1.2.3.4:8000/", + fixedURI: "http://1.2.3.4:8000/", + protocolChange: true, + }, { + input: "1.2.3.4:8000/foo", + fixedURI: "http://1.2.3.4:8000/foo", + protocolChange: true, + }, { + input: "192.168.10.110", + fixedURI: "http://192.168.10.110/", + protocolChange: true, + }, { + input: "192.168.10.110/123", + fixedURI: "http://192.168.10.110/123", + protocolChange: true, + }, { + input: "192.168.10.110/123foo", + fixedURI: "http://192.168.10.110/123foo", + protocolChange: true, + }, { + input: "192.168.10.110:1234/123", + fixedURI: "http://192.168.10.110:1234/123", + protocolChange: true, + }, { + input: "192.168.10.110:1234/123foo", + fixedURI: "http://192.168.10.110:1234/123foo", + protocolChange: true, + }, { + input: "1.2.3", + fixedURI: "http://1.2.0.3/", + protocolChange: true, + }, { + input: "1.2.3/", + fixedURI: "http://1.2.0.3/", + protocolChange: true, + }, { + input: "1.2.3/foo", + fixedURI: "http://1.2.0.3/foo", + protocolChange: true, + }, { + input: "1.2.3/123", + fixedURI: "http://1.2.0.3/123", + protocolChange: true, + }, { + input: "1.2.3:8000", + fixedURI: "http://1.2.0.3:8000/", + protocolChange: true, + }, { + input: "1.2.3:8000/", + fixedURI: "http://1.2.0.3:8000/", + protocolChange: true, + }, { + input: "1.2.3:8000/foo", + fixedURI: "http://1.2.0.3:8000/foo", + protocolChange: true, + }, { + input: "1.2.3:8000/123", + fixedURI: "http://1.2.0.3:8000/123", + protocolChange: true, + }, { + input: "http://1.2.3", + fixedURI: "http://1.2.0.3/", + }, { + input: "http://1.2.3/", + fixedURI: "http://1.2.0.3/", + }, { + input: "http://1.2.3/foo", + fixedURI: "http://1.2.0.3/foo", + }, { + input: "[::1]", + fixedURI: "http://[::1]/", + alternateURI: "http://[::1]/", + protocolChange: true, + }, { + input: "[::1]/", + fixedURI: "http://[::1]/", + alternateURI: "http://[::1]/", + protocolChange: true, + }, { + input: "[::1]:8000", + fixedURI: "http://[::1]:8000/", + alternateURI: "http://[::1]:8000/", + protocolChange: true, + }, { + input: "[::1]:8000/", + fixedURI: "http://[::1]:8000/", + alternateURI: "http://[::1]:8000/", + protocolChange: true, + }, { + input: "[[::1]]/", + keywordLookup: true, + protocolChange: true, + }, { + input: "[fe80:cd00:0:cde:1257:0:211e:729c]", + fixedURI: "http://[fe80:cd00:0:cde:1257:0:211e:729c]/", + alternateURI: "http://[fe80:cd00:0:cde:1257:0:211e:729c]/", + protocolChange: true, + }, { + input: "[64:ff9b::8.8.8.8]", + fixedURI: "http://[64:ff9b::8.8.8.8]/", + protocolChange: true + }, { + input: "[64:ff9b::8.8.8.8]/~moz", + fixedURI: "http://[64:ff9b::8.8.8.8]/~moz", + protocolChange: true + }, { + input: "[::1][::1]", + keywordLookup: true, + protocolChange: true + }, { + input: "[::1][100", + fixedURI: null, + alternateURI: null, + keywordLookup: true, + protocolChange: true + }, { + input: "[::1]]", + keywordLookup: true, + protocolChange: true + }, { + input: "1234", + fixedURI: "http://0.0.4.210/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleHosts: true, + }, { + input: "host/foo.txt", + fixedURI: "http://host/foo.txt", + alternateURI: "http://www.host.com/foo.txt", + protocolChange: true, + }, { + input: "mozilla", + fixedURI: "http://mozilla/", + alternateURI: "http://www.mozilla.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleHosts: true, + }, { + input: "test.", + fixedURI: "http://test./", + alternateURI: "http://www.test./", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleHosts: true, + }, { + input: ".test", + fixedURI: "http://.test/", + alternateURI: "http://www..test/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleHosts: true, + }, { + input: "mozilla is amazing", + keywordLookup: true, + protocolChange: true, + }, { + input: "search ?mozilla", + keywordLookup: true, + protocolChange: true, + }, { + input: "mozilla .com", + keywordLookup: true, + protocolChange: true, + }, { + input: "what if firefox?", + keywordLookup: true, + protocolChange: true, + }, { + input: "london's map", + keywordLookup: true, + protocolChange: true, + }, { + input: "mozilla ", + fixedURI: "http://mozilla/", + alternateURI: "http://www.mozilla.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleHosts: true, + }, { + input: " mozilla ", + fixedURI: "http://mozilla/", + alternateURI: "http://www.mozilla.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleHosts: true, + }, { + input: "mozilla \\", + keywordLookup: true, + protocolChange: true, + }, { + input: "mozilla \\ foo.txt", + keywordLookup: true, + protocolChange: true, + }, { + input: "mozilla \\\r foo.txt", + keywordLookup: true, + protocolChange: true, + }, { + input: "mozilla\n", + fixedURI: "http://mozilla/", + alternateURI: "http://www.mozilla.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleHosts: true, + }, { + input: "mozilla \r\n", + fixedURI: "http://mozilla/", + alternateURI: "http://www.mozilla.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleHosts: true, + }, { + input: "moz\r\nfirefox\nos\r", + fixedURI: "http://mozfirefoxos/", + alternateURI: "http://www.mozfirefoxos.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleHosts: true, + }, { + input: "moz\r\n firefox\n", + keywordLookup: true, + protocolChange: true, + }, { + input: "", + keywordLookup: true, + protocolChange: true, + }, { + input: "[]", + keywordLookup: true, + protocolChange: true, + }, { + input: "http://whitelisted/", + fixedURI: "http://whitelisted/", + alternateURI: "http://www.whitelisted.com/", + inWhitelist: true, + }, { + input: "café.local", + fixedURI: "http://café.local/", + alternateURI: "http://www.café.local/", + protocolChange: true + }, { + input: "47.6182,-122.830", + fixedURI: "http://47.6182,-122.830/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleHosts: true, + }, { + input: "-47.6182,-23.51", + fixedURI: "http://-47.6182,-23.51/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleHosts: true, + }, { + input: "-22.14,23.51-", + fixedURI: "http://-22.14,23.51-/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleHosts: true, + }, { + input: "32.7", + fixedURI: "http://32.0.0.7/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleHosts: true, + }, { + input: "5+2", + fixedURI: "http://5+2/", + alternateURI: "http://www.5+2.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleHosts: true, + }, { + input: "5/2", + fixedURI: "http://0.0.0.5/2", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleHosts: true, + }, { + input: "moz ?.::%27", + keywordLookup: true, + protocolChange: true + }, { + input: "mozilla.com/?q=search", + fixedURI: "http://mozilla.com/?q=search", + alternateURI: "http://www.mozilla.com/?q=search", + protocolChange: true + }, { + input: "mozilla.com?q=search", + fixedURI: "http://mozilla.com/?q=search", + alternateURI: "http://www.mozilla.com/?q=search", + protocolChange: true + }, { + input: "mozilla.com ?q=search", + keywordLookup: true, + protocolChange: true + }, { + input: "mozilla.com.?q=search", + fixedURI: "http://mozilla.com./?q=search", + protocolChange: true + }, { + input: "mozilla.com'?q=search", + fixedURI: "http://mozilla.com'/?q=search", + alternateURI: "http://www.mozilla.com'/?q=search", + protocolChange: true + }, { + input: "mozilla.com':search", + keywordLookup: true, + protocolChange: true + }, { + input: "[mozilla]", + keywordLookup: true, + protocolChange: true + }, { + input: "':?", + fixedURI: "http://'/?", + alternateURI: "http://www.'.com/?", + keywordLookup: true, + protocolChange: true, + }, { + input: "a?.com", + fixedURI: "http://a/?.com", + alternateURI: "http://www.a.com/?.com", + protocolChange: true, + }, { + input: "?'.com", + fixedURI: "http:///?%27.com", + alternateURI: "http://www..com/?%27.com", + keywordLookup: true, + protocolChange: true, + }, { + input: "' ?.com", + keywordLookup: true, + protocolChange: true + }, { + input: "?mozilla", + fixedURI: "http:///?mozilla", + alternateURI: "http://www..com/?mozilla", + keywordLookup: true, + protocolChange: true, + }, { + input: "??mozilla", + fixedURI: "http:///??mozilla", + alternateURI: "http://www..com/??mozilla", + keywordLookup: true, + protocolChange: true, + }, { + input: "mozilla/", + fixedURI: "http://mozilla/", + alternateURI: "http://www.mozilla.com/", + protocolChange: true, + }, { + input: "mozilla", + fixedURI: "http://mozilla/", + alternateURI: "http://www.mozilla.com/", + protocolChange: true, + keywordLookup: true, + affectedByDNSForSingleHosts: true, + }, { + input: "mozilla5/2", + fixedURI: "http://mozilla5/2", + alternateURI: "http://www.mozilla5.com/2", + protocolChange: true, + }, { + input: "mozilla/foo", + fixedURI: "http://mozilla/foo", + alternateURI: "http://www.mozilla.com/foo", + protocolChange: true, + }, { + input: "mozilla\\", + fixedURI: "http://mozilla/", + alternateURI: "http://www.mozilla.com/", + keywordLookup: true, + protocolChange: true, + affectedByDNSForSingleHosts: true, + }]; + +if (Services.appinfo.OS.toLowerCase().startsWith("win")) { + testcases.push({ + input: "C:\\some\\file.txt", + fixedURI: "file:///C:/some/file.txt", + protocolChange: true, + }); + testcases.push({ + input: "//mozilla", + fixedURI: "http://mozilla/", + alternateURI: "http://www.mozilla.com/", + protocolChange: true, + }); +} else { + testcases.push({ + input: "/some/file.txt", + fixedURI: "file:///some/file.txt", + protocolChange: true, + }); + testcases.push({ + input: "//mozilla", + fixedURI: "file:////mozilla", + protocolChange: true, + }); +} + +function sanitize(input) { + return input.replace(/\r|\n/g, "").trim(); +} + + +var gSingleWordHostLookup = false; +function run_test() { + // Only keywordlookup things should be affected by requiring a DNS lookup for single-word hosts: + do_print("Check only keyword lookup testcases should be affected by requiring DNS for single hosts"); + let affectedTests = testcases.filter(t => !t.keywordLookup && t.affectedByDNSForSingleHosts); + if (affectedTests.length) { + for (let testcase of affectedTests) { + do_print("Affected: " + testcase.input); + } + } + do_check_eq(affectedTests.length, 0); + do_single_test_run(); + gSingleWordHostLookup = true; + do_single_test_run(); +} + +function do_single_test_run() { + Services.prefs.setBoolPref(kForceHostLookup, gSingleWordHostLookup); + + let relevantTests = gSingleWordHostLookup ? testcases.filter(t => t.keywordLookup) : + testcases; + + for (let { input: testInput, + fixedURI: expectedFixedURI, + alternateURI: alternativeURI, + keywordLookup: expectKeywordLookup, + protocolChange: expectProtocolChange, + inWhitelist: inWhitelist, + affectedByDNSForSingleHosts: affectedByDNSForSingleHosts, + } of relevantTests) { + + // Explicitly force these into a boolean + expectKeywordLookup = !!expectKeywordLookup; + expectProtocolChange = !!expectProtocolChange; + inWhitelist = !!inWhitelist; + affectedByDNSForSingleHosts = !!affectedByDNSForSingleHosts; + + expectKeywordLookup = expectKeywordLookup && (!affectedByDNSForSingleHosts || !gSingleWordHostLookup); + + for (let flags of flagInputs) { + let info; + let fixupURIOnly = null; + try { + fixupURIOnly = urifixup.createFixupURI(testInput, flags); + } catch (ex) { + do_print("Caught exception: " + ex); + do_check_eq(expectedFixedURI, null); + } + + try { + info = urifixup.getFixupURIInfo(testInput, flags); + } catch (ex) { + // Both APIs should return an error in the same cases. + do_print("Caught exception: " + ex); + do_check_eq(expectedFixedURI, null); + do_check_eq(fixupURIOnly, null); + continue; + } + + do_print("Checking \"" + testInput + "\" with flags " + flags + + " (host lookup for single words: " + (gSingleWordHostLookup ? "yes" : "no") + ")"); + + // Both APIs should then also be using the same spec. + do_check_eq(!!fixupURIOnly, !!info.preferredURI); + if (fixupURIOnly) + do_check_eq(fixupURIOnly.spec, info.preferredURI.spec); + + let isFileURL = expectedFixedURI && expectedFixedURI.startsWith("file"); + + // Check the fixedURI: + let makeAlternativeURI = flags & urifixup.FIXUP_FLAGS_MAKE_ALTERNATE_URI; + if (makeAlternativeURI && alternativeURI != null) { + do_check_eq(info.fixedURI.spec, alternativeURI); + } else { + do_check_eq(info.fixedURI && info.fixedURI.spec, expectedFixedURI); + } + + // Check booleans on input: + let couldDoKeywordLookup = flags & urifixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; + do_check_eq(!!info.keywordProviderName, couldDoKeywordLookup && expectKeywordLookup); + do_check_eq(info.fixupChangedProtocol, expectProtocolChange); + do_check_eq(info.fixupCreatedAlternateURI, makeAlternativeURI && alternativeURI != null); + + // Check the preferred URI + if (couldDoKeywordLookup) { + if (expectKeywordLookup) { + if (!inWhitelist) { + let urlparamInput = encodeURIComponent(sanitize(testInput)).replace(/%20/g, "+"); + // If the input starts with `?`, then info.preferredURI.spec will omit it + // In order to test this behaviour, remove `?` only if it is the first character + if (urlparamInput.startsWith("%3F")) { + urlparamInput = urlparamInput.replace("%3F", ""); + } + let searchURL = kSearchEngineURL.replace("{searchTerms}", urlparamInput); + let spec = info.preferredURI.spec.replace(/%27/g, "'"); + do_check_eq(spec, searchURL); + } else { + do_check_eq(info.preferredURI, null); + } + } else { + do_check_eq(info.preferredURI.spec, info.fixedURI.spec); + } + } else { + // In these cases, we should never be doing a keyword lookup and + // the fixed URI should be preferred: + do_check_eq(info.preferredURI.spec, info.fixedURI.spec); + } + do_check_eq(sanitize(testInput), info.originalInput); + } + } +} diff --git a/docshell/test/unit/test_nsDefaultURIFixup_search.js b/docshell/test/unit/test_nsDefaultURIFixup_search.js new file mode 100644 index 000000000..c00b6a85f --- /dev/null +++ b/docshell/test/unit/test_nsDefaultURIFixup_search.js @@ -0,0 +1,117 @@ +var urifixup = Cc["@mozilla.org/docshell/urifixup;1"]. + getService(Ci.nsIURIFixup); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/AppConstants.jsm"); + +Services.prefs.setBoolPref("keyword.enabled", true); + +const kSearchEngineID = "test_urifixup_search_engine"; +const kSearchEngineURL = "http://www.example.org/?search={searchTerms}"; +Services.search.addEngineWithDetails(kSearchEngineID, "", "", "", "get", + kSearchEngineURL); + +var oldDefaultEngine = Services.search.defaultEngine; +Services.search.defaultEngine = Services.search.getEngineByName(kSearchEngineID); + +var selectedName = Services.search.defaultEngine.name; +do_check_eq(selectedName, kSearchEngineID); + +do_register_cleanup(function() { + if (oldDefaultEngine) { + Services.search.defaultEngine = oldDefaultEngine; + } + let engine = Services.search.getEngineByName(kSearchEngineID); + if (engine) { + Services.search.removeEngine(engine); + } + Services.prefs.clearUserPref("keyword.enabled"); +}); + +var isWin = AppConstants.platform == "win"; + +var data = [ + { + // Valid should not be changed. + wrong: 'https://example.com/this/is/a/test.html', + fixed: 'https://example.com/this/is/a/test.html', + }, + { + // Unrecognized protocols should be changed. + wrong: 'whatever://this/is/a/test.html', + fixed: kSearchEngineURL.replace("{searchTerms}", encodeURIComponent('whatever://this/is/a/test.html')), + }, + + // The following tests check that when a user:password is present in the URL + // `user:` isn't treated as an unknown protocol thus leaking the user and + // password to the search engine. + { + wrong: 'user:pass@example.com/this/is/a/test.html', + fixed: 'http://user:pass@example.com/this/is/a/test.html', + }, + { + wrong: 'user@example.com:8080/this/is/a/test.html', + fixed: 'http://user@example.com:8080/this/is/a/test.html', + }, + { + wrong: 'https:pass@example.com/this/is/a/test.html', + fixed: 'https://pass@example.com/this/is/a/test.html', + }, + { + wrong: 'user:pass@example.com:8080/this/is/a/test.html', + fixed: 'http://user:pass@example.com:8080/this/is/a/test.html', + }, + { + wrong: 'http:user:pass@example.com:8080/this/is/a/test.html', + fixed: 'http://user:pass@example.com:8080/this/is/a/test.html', + }, + { + wrong: 'ttp:user:pass@example.com:8080/this/is/a/test.html', + fixed: 'http://user:pass@example.com:8080/this/is/a/test.html', + }, + { + wrong: 'gobbledygook:user:pass@example.com:8080/this/is/a/test.html', + fixed: 'http://gobbledygook:user%3Apass@example.com:8080/this/is/a/test.html', + }, + { + wrong: 'user:@example.com:8080/this/is/a/test.html', + fixed: 'http://user:@example.com:8080/this/is/a/test.html', + }, + { + wrong: '//user:pass@example.com:8080/this/is/a/test.html', + fixed: (isWin ? "http:" : "file://") + '//user:pass@example.com:8080/this/is/a/test.html', + }, + { + wrong: '://user:pass@example.com:8080/this/is/a/test.html', + fixed: 'http://user:pass@example.com:8080/this/is/a/test.html', + }, + { + wrong: 'whatever://this/is/a@b/test.html', + fixed: kSearchEngineURL.replace("{searchTerms}", encodeURIComponent('whatever://this/is/a@b/test.html')), + }, +]; + +var extProtocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"] + .getService(Ci.nsIExternalProtocolService); + +if (extProtocolSvc && extProtocolSvc.externalProtocolHandlerExists("mailto")) { + data.push({ + wrong: "mailto:foo@bar.com", + fixed: "mailto:foo@bar.com" + }); +} + +function run_test() { + run_next_test(); +} + +var len = data.length; +// Make sure we fix what needs fixing +add_task(function test_fix_unknown_schemes() { + for (let i = 0; i < len; ++i) { + let item = data[i]; + let result = + urifixup.createFixupURI(item.wrong, + urifixup.FIXUP_FLAG_FIX_SCHEME_TYPOS).spec; + do_check_eq(result, item.fixed); + } +}); diff --git a/docshell/test/unit/test_nsIDownloadHistory.js b/docshell/test/unit/test_nsIDownloadHistory.js new file mode 100644 index 000000000..2030f0702 --- /dev/null +++ b/docshell/test/unit/test_nsIDownloadHistory.js @@ -0,0 +1,68 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const NS_DOWNLOADHISTORY_CID = "{2ee83680-2af0-4bcb-bfa0-c9705f6554f1}"; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyGetter(this, "Services", function() { + Components.utils.import("resource://gre/modules/Services.jsm"); + return Services; +}); + +function testLinkVistedObserver() +{ + const NS_LINK_VISITED_EVENT_TOPIC = "link-visited"; + var ios = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + var testURI = ios.newURI("http://google.com/", null, null); + + var gh = Cc["@mozilla.org/browser/global-history;2"]. + getService(Ci.nsIGlobalHistory2); + do_check_false(gh.isVisited(testURI)); + + var topicReceived = false; + var obs = { + observe: function tlvo_observe(aSubject, aTopic, aData) + { + if (NS_LINK_VISITED_EVENT_TOPIC == aTopic) { + do_check_eq(testURI, aSubject); + topicReceived = true; + } + } + }; + + var os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + os.addObserver(obs, NS_LINK_VISITED_EVENT_TOPIC, false); + + var dh = Components.classesByID[NS_DOWNLOADHISTORY_CID]. + getService(Ci.nsIDownloadHistory); + dh.addDownload(testURI); + do_check_true(topicReceived); + do_check_true(gh.isVisited(testURI)); +} + +var tests = [testLinkVistedObserver]; + +function run_test() +{ + // Not everyone uses/defines an nsGlobalHistory* service. Especially if + // MOZ_PLACES is not defined. If getService fails, then abort gracefully. + try { + Cc["@mozilla.org/browser/global-history;2"]. + getService(Ci.nsIGlobalHistory2); + } + catch (ex) { + return; + } + + // Needed to properly setup and shutdown the profile. + do_get_profile(); + + for (var i = 0; i < tests.length; i++) + tests[i](); + + cleanup(); +} diff --git a/docshell/test/unit/test_pb_notification.js b/docshell/test/unit/test_pb_notification.js new file mode 100644 index 000000000..82018f585 --- /dev/null +++ b/docshell/test/unit/test_pb_notification.js @@ -0,0 +1,23 @@ +if (typeof Cc === "undefined") + Cc = Components.classes; +if (typeof Ci === "undefined") + Ci = Components.interfaces; + +function destroy_transient_docshell() { + var docshell = Cc["@mozilla.org/docshell;1"].createInstance(Ci.nsIDocShell); + docshell.setOriginAttributes({privateBrowsingId : 1}); + do_test_pending(); + do_timeout(0, Components.utils.forceGC); +} + +function run_test() { + var obs = { + observe: function(aSubject, aTopic, aData) { + do_check_eq(aTopic, "last-pb-context-exited"); + do_test_finished(); + } + }; + var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + os.addObserver(obs, "last-pb-context-exited", false); + destroy_transient_docshell(); +} diff --git a/docshell/test/unit/test_privacy_transition.js b/docshell/test/unit/test_privacy_transition.js new file mode 100644 index 000000000..829e25c3c --- /dev/null +++ b/docshell/test/unit/test_privacy_transition.js @@ -0,0 +1,23 @@ +var gNotifications = 0; + +var observer = { + QueryInterface: function(iid) { + if (Ci.nsIPrivacyTransitionObserver.equals(iid) || + Ci.nsISupportsWeakReference.equals(iid) || + Ci.nsISupports.equals(iid)) + return this; + throw Cr.NS_ERROR_NO_INTERFACE; + }, + + privateModeChanged: function(enabled) { + gNotifications++; + } +} + +function run_test() { + var docshell = Cc["@mozilla.org/docshell;1"].createInstance(Ci.nsIDocShell); + docshell.addWeakPrivacyTransitionObserver(observer); + docshell.setOriginAttributes({ privateBrowsingId : 1 }); + docshell.setOriginAttributes({ privateBrowsingId : 0 }); + do_check_eq(gNotifications, 2); +}
\ No newline at end of file diff --git a/docshell/test/unit/test_setUsePrivateBrowsing.js b/docshell/test/unit/test_setUsePrivateBrowsing.js new file mode 100644 index 000000000..739186895 --- /dev/null +++ b/docshell/test/unit/test_setUsePrivateBrowsing.js @@ -0,0 +1,65 @@ +"use strict"; + +const {utils: Cu} = Components; + +Cu.import("resource://gre/modules/AppConstants.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +add_task(function*() { + let webNav = Services.appShell.createWindowlessBrowser(false); + + let loadContext = webNav.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsILoadContext); + + let docShell = webNav.getInterface(Ci.nsIDocShell); + + equal(loadContext.usePrivateBrowsing, false, "Should start out in non-private mode"); + + loadContext.usePrivateBrowsing = true; + equal(loadContext.usePrivateBrowsing, true, + "Should be able to change to private mode prior to a document load"); + + loadContext.usePrivateBrowsing = false; + equal(loadContext.usePrivateBrowsing, false, + "Should be able to change to non-private mode prior to a document load"); + + let oa = docShell.getOriginAttributes(); + + oa.privateBrowsingId = 1; + docShell.setOriginAttributes(oa); + + equal(loadContext.usePrivateBrowsing, true, + "Should be able to change origin attributes prior to a document load"); + + oa.privateBrowsingId = 0; + docShell.setOriginAttributes(oa); + + equal(loadContext.usePrivateBrowsing, false, + "Should be able to change origin attributes prior to a document load"); + + webNav.loadURI("data:text/html,", webNav.LOAD_FLAGS_NONE, null, null, null); + + // Return to the event loop so the load can begin. + yield new Promise(do_execute_soon); + + // This causes a failed assertion rather than an exception on debug + // builds. + if (!AppConstants.DEBUG) { + Assert.throws(() => { loadContext.usePrivateBrowsing = true; }, + /NS_ERROR_FAILURE/, + "Should not be able to change private browsing state after initial load has started"); + + oa.privateBrowsingId = 1; + Assert.throws(() => { docShell.setOriginAttributes(oa); }, + /NS_ERROR_FAILURE/, + "Should not be able to change origin attributes after initial load has started"); + + equal(loadContext.usePrivateBrowsing, false, + "Should not be able to change private browsing state after initial load has started"); + + loadContext.usePrivateBrowsing = false; + ok(true, "Should be able to set usePrivateBrowsing to its current value even after initial load"); + } + + webNav.close(); +}); diff --git a/docshell/test/unit/xpcshell.ini b/docshell/test/unit/xpcshell.ini new file mode 100644 index 000000000..ce8ec8b63 --- /dev/null +++ b/docshell/test/unit/xpcshell.ini @@ -0,0 +1,17 @@ +[DEFAULT] +head = head_docshell.js +tail = + +[test_bug414201_jfif.js] +[test_bug442584.js] +[test_nsDefaultURIFixup.js] +[test_nsDefaultURIFixup_search.js] +skip-if = os == 'android' +[test_nsDefaultURIFixup_info.js] +skip-if = os == 'android' +[test_nsIDownloadHistory.js] +[test_pb_notification.js] +# Bug 751575: unrelated JS changes cause timeouts on random platforms +skip-if = true +[test_privacy_transition.js] +[test_setUsePrivateBrowsing.js] diff --git a/docshell/test/unit_ipc/test_pb_notification_ipc.js b/docshell/test/unit_ipc/test_pb_notification_ipc.js new file mode 100644 index 000000000..1517a479f --- /dev/null +++ b/docshell/test/unit_ipc/test_pb_notification_ipc.js @@ -0,0 +1,20 @@ +var Cc = Components.classes; +var Ci = Components.interfaces; + +function run_test() { + var notifications = 0; + var obs = { + observe: function(aSubject, aTopic, aData) { + do_check_eq(aTopic, "last-pb-context-exited"); + notifications++; + } + }; + var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); + os.addObserver(obs, "last-pb-context-exited", false); + + run_test_in_child("../unit/test_pb_notification.js", + function() { + do_check_eq(notifications, 1); + do_test_finished(); + }); +}
\ No newline at end of file diff --git a/docshell/test/unit_ipc/xpcshell.ini b/docshell/test/unit_ipc/xpcshell.ini new file mode 100644 index 000000000..7a2df27c0 --- /dev/null +++ b/docshell/test/unit_ipc/xpcshell.ini @@ -0,0 +1,8 @@ +[DEFAULT] +head = +tail = +skip-if = toolkit == 'android' + +[test_pb_notification_ipc.js] +# Bug 751575: Perma-fails with: command timed out: 1200 seconds without output +skip-if = true |