diff options
Diffstat (limited to 'toolkit/components/windowwatcher/nsWindowWatcher.cpp')
-rw-r--r-- | toolkit/components/windowwatcher/nsWindowWatcher.cpp | 2524 |
1 files changed, 2524 insertions, 0 deletions
diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.cpp b/toolkit/components/windowwatcher/nsWindowWatcher.cpp new file mode 100644 index 0000000000..c6da134e1c --- /dev/null +++ b/toolkit/components/windowwatcher/nsWindowWatcher.cpp @@ -0,0 +1,2524 @@ +/* -*- 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 "nsWindowWatcher.h" +#include "nsAutoWindowStateHelper.h" + +#include "nsCRT.h" +#include "nsNetUtil.h" +#include "nsIAuthPrompt.h" +#include "nsIAuthPrompt2.h" +#include "nsISimpleEnumerator.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsJSUtils.h" +#include "plstr.h" + +#include "nsDocShell.h" +#include "nsGlobalWindow.h" +#include "nsHashPropertyBag.h" +#include "nsIBaseWindow.h" +#include "nsIBrowserDOMWindow.h" +#include "nsIDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/BrowsingContextGroup.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/DocumentInlines.h" +#include "nsIDOMChromeWindow.h" +#include "nsIPrompt.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScreen.h" +#include "nsIScreenManager.h" +#include "nsIScriptContext.h" +#include "nsIObserverService.h" +#include "nsXPCOM.h" +#include "nsIURI.h" +#include "nsIWebBrowser.h" +#include "nsIWebBrowserChrome.h" +#include "nsIWebNavigation.h" +#include "nsIWindowCreator.h" +#include "nsIXULRuntime.h" +#include "nsPIDOMWindow.h" +#include "nsIWindowProvider.h" +#include "nsIMutableArray.h" +#include "nsIDOMStorageManager.h" +#include "nsIWidget.h" +#include "nsFocusManager.h" +#include "nsOpenWindowInfo.h" +#include "nsPresContext.h" +#include "nsContentUtils.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsSandboxFlags.h" +#include "nsSimpleEnumerator.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/Preferences.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/StaticPrefs_full_screen_api.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Storage.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/BrowserHost.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/SessionStorageManager.h" +#include "nsIAppWindow.h" +#include "nsIXULBrowserWindow.h" +#include "nsGlobalWindow.h" +#include "ReferrerInfo.h" + +using namespace mozilla; +using namespace mozilla::dom; + +/**************************************************************** + ******************** nsWatcherWindowEntry ********************** + ****************************************************************/ + +class nsWindowWatcher; + +struct nsWatcherWindowEntry { + nsWatcherWindowEntry(mozIDOMWindowProxy* aWindow, + nsIWebBrowserChrome* aChrome) + : mChrome(nullptr) { + mWindow = aWindow; + nsCOMPtr<nsISupportsWeakReference> supportsweak(do_QueryInterface(aChrome)); + if (supportsweak) { + supportsweak->GetWeakReference(getter_AddRefs(mChromeWeak)); + } else { + mChrome = aChrome; + mChromeWeak = nullptr; + } + ReferenceSelf(); + } + ~nsWatcherWindowEntry() = default; + + void InsertAfter(nsWatcherWindowEntry* aOlder); + void Unlink(); + void ReferenceSelf(); + + mozIDOMWindowProxy* mWindow; + nsIWebBrowserChrome* mChrome; + nsWeakPtr mChromeWeak; + // each struct is in a circular, doubly-linked list + nsWatcherWindowEntry* mYounger; // next younger in sequence + nsWatcherWindowEntry* mOlder; +}; + +void nsWatcherWindowEntry::InsertAfter(nsWatcherWindowEntry* aOlder) { + if (aOlder) { + mOlder = aOlder; + mYounger = aOlder->mYounger; + mOlder->mYounger = this; + if (mOlder->mOlder == mOlder) { + mOlder->mOlder = this; + } + mYounger->mOlder = this; + if (mYounger->mYounger == mYounger) { + mYounger->mYounger = this; + } + } +} + +void nsWatcherWindowEntry::Unlink() { + mOlder->mYounger = mYounger; + mYounger->mOlder = mOlder; + ReferenceSelf(); +} + +void nsWatcherWindowEntry::ReferenceSelf() { + mYounger = this; + mOlder = this; +} + +/**************************************************************** + ****************** nsWatcherWindowEnumerator ******************* + ****************************************************************/ + +class nsWatcherWindowEnumerator : public nsSimpleEnumerator { + public: + explicit nsWatcherWindowEnumerator(nsWindowWatcher* aWatcher); + NS_IMETHOD HasMoreElements(bool* aResult) override; + NS_IMETHOD GetNext(nsISupports** aResult) override; + + protected: + ~nsWatcherWindowEnumerator() override; + + private: + friend class nsWindowWatcher; + + nsWatcherWindowEntry* FindNext(); + void WindowRemoved(nsWatcherWindowEntry* aInfo); + + nsWindowWatcher* mWindowWatcher; + nsWatcherWindowEntry* mCurrentPosition; +}; + +nsWatcherWindowEnumerator::nsWatcherWindowEnumerator(nsWindowWatcher* aWatcher) + : mWindowWatcher(aWatcher), mCurrentPosition(aWatcher->mOldestWindow) { + mWindowWatcher->AddEnumerator(this); + mWindowWatcher->AddRef(); +} + +nsWatcherWindowEnumerator::~nsWatcherWindowEnumerator() { + mWindowWatcher->RemoveEnumerator(this); + mWindowWatcher->Release(); +} + +NS_IMETHODIMP +nsWatcherWindowEnumerator::HasMoreElements(bool* aResult) { + if (!aResult) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = !!mCurrentPosition; + return NS_OK; +} + +NS_IMETHODIMP +nsWatcherWindowEnumerator::GetNext(nsISupports** aResult) { + if (!aResult) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = nullptr; + + if (mCurrentPosition) { + CallQueryInterface(mCurrentPosition->mWindow, aResult); + mCurrentPosition = FindNext(); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +nsWatcherWindowEntry* nsWatcherWindowEnumerator::FindNext() { + nsWatcherWindowEntry* info; + + if (!mCurrentPosition) { + return 0; + } + + info = mCurrentPosition->mYounger; + return info == mWindowWatcher->mOldestWindow ? 0 : info; +} + +// if a window is being removed adjust the iterator's current position +void nsWatcherWindowEnumerator::WindowRemoved(nsWatcherWindowEntry* aInfo) { + if (mCurrentPosition == aInfo) { + mCurrentPosition = + mCurrentPosition != aInfo->mYounger ? aInfo->mYounger : 0; + } +} + +/**************************************************************** + *********************** nsWindowWatcher ************************ + ****************************************************************/ + +NS_IMPL_ADDREF(nsWindowWatcher) +NS_IMPL_RELEASE(nsWindowWatcher) +NS_IMPL_QUERY_INTERFACE(nsWindowWatcher, nsIWindowWatcher, nsIPromptFactory, + nsPIWindowWatcher) + +nsWindowWatcher::nsWindowWatcher() + : mEnumeratorList(), + mOldestWindow(0), + mListLock("nsWindowWatcher.mListLock") {} + +nsWindowWatcher::~nsWindowWatcher() { + // delete data + while (mOldestWindow) { + RemoveWindow(mOldestWindow); + } +} + +nsresult nsWindowWatcher::Init() { return NS_OK; } + +/** + * Convert aArguments into either an nsIArray or nullptr. + * + * - If aArguments is nullptr, return nullptr. + * - If aArguments is an nsArray, return nullptr if it's empty, or otherwise + * return the array. + * - If aArguments is an nsIArray, return nullptr if it's empty, or + * otherwise just return the array. + * - Otherwise, return an nsIArray with one element: aArguments. + */ +static already_AddRefed<nsIArray> ConvertArgsToArray(nsISupports* aArguments) { + if (!aArguments) { + return nullptr; + } + + nsCOMPtr<nsIArray> array = do_QueryInterface(aArguments); + if (array) { + uint32_t argc = 0; + array->GetLength(&argc); + if (argc == 0) { + return nullptr; + } + + return array.forget(); + } + + nsCOMPtr<nsIMutableArray> singletonArray = + do_CreateInstance(NS_ARRAY_CONTRACTID); + NS_ENSURE_TRUE(singletonArray, nullptr); + + nsresult rv = singletonArray->AppendElement(aArguments); + NS_ENSURE_SUCCESS(rv, nullptr); + + return singletonArray.forget(); +} + +NS_IMETHODIMP +nsWindowWatcher::OpenWindow(mozIDOMWindowProxy* aParent, const nsACString& aUrl, + const nsACString& aName, + const nsACString& aFeatures, + nsISupports* aArguments, + mozIDOMWindowProxy** aResult) { + nsCOMPtr<nsIArray> argv = ConvertArgsToArray(aArguments); + + uint32_t argc = 0; + if (argv) { + argv->GetLength(&argc); + } + bool dialog = (argc != 0); + + RefPtr<BrowsingContext> bc; + MOZ_TRY(OpenWindowInternal(aParent, aUrl, aName, aFeatures, + /* calledFromJS = */ false, dialog, + /* navigate = */ true, argv, + /* aIsPopupSpam = */ false, + /* aForceNoOpener = */ false, + /* aForceNoReferrer = */ false, PRINT_NONE, + /* aLoadState = */ nullptr, getter_AddRefs(bc))); + if (bc) { + nsCOMPtr<mozIDOMWindowProxy> win(bc->GetDOMWindow()); + win.forget(aResult); + } + return NS_OK; +} + +struct SizeSpec { + SizeSpec() + : mLeft(0), + mTop(0), + mOuterWidth(0), + mOuterHeight(0), + mInnerWidth(0), + mInnerHeight(0), + mLeftSpecified(false), + mTopSpecified(false), + mOuterWidthSpecified(false), + mOuterHeightSpecified(false), + mInnerWidthSpecified(false), + mInnerHeightSpecified(false), + mLockAspectRatio(false) {} + + int32_t mLeft; + int32_t mTop; + int32_t mOuterWidth; // Total window width + int32_t mOuterHeight; // Total window height + int32_t mInnerWidth; // Content area width + int32_t mInnerHeight; // Content area height + + bool mLeftSpecified; + bool mTopSpecified; + bool mOuterWidthSpecified; + bool mOuterHeightSpecified; + bool mInnerWidthSpecified; + bool mInnerHeightSpecified; + bool mLockAspectRatio; + + bool PositionSpecified() const { return mLeftSpecified || mTopSpecified; } + + bool SizeSpecified() const { return WidthSpecified() || HeightSpecified(); } + + bool WidthSpecified() const { + return mOuterWidthSpecified || mInnerWidthSpecified; + } + + bool HeightSpecified() const { + return mOuterHeightSpecified || mInnerHeightSpecified; + } +}; + +NS_IMETHODIMP +nsWindowWatcher::OpenWindow2(mozIDOMWindowProxy* aParent, + const nsACString& aUrl, const nsACString& aName, + const nsACString& aFeatures, + bool aCalledFromScript, bool aDialog, + bool aNavigate, nsISupports* aArguments, + bool aIsPopupSpam, bool aForceNoOpener, + bool aForceNoReferrer, PrintKind aPrintKind, + nsDocShellLoadState* aLoadState, + BrowsingContext** aResult) { + nsCOMPtr<nsIArray> argv = ConvertArgsToArray(aArguments); + + uint32_t argc = 0; + if (argv) { + argv->GetLength(&argc); + } + + // This is extremely messed up, but this behavior is necessary because + // callers lie about whether they're a dialog window and whether they're + // called from script. Fixing this is bug 779939. + bool dialog = aDialog; + if (!aCalledFromScript) { + dialog = argc > 0; + } + + return OpenWindowInternal(aParent, aUrl, aName, aFeatures, aCalledFromScript, + dialog, aNavigate, argv, aIsPopupSpam, + aForceNoOpener, aForceNoReferrer, aPrintKind, + aLoadState, aResult); +} + +// This static function checks if the aDocShell uses an UserContextId equal to +// the userContextId of subjectPrincipal, if not null. +static bool CheckUserContextCompatibility(nsIDocShell* aDocShell) { + MOZ_ASSERT(aDocShell); + + uint32_t userContextId = + static_cast<nsDocShell*>(aDocShell)->GetOriginAttributes().mUserContextId; + + nsCOMPtr<nsIPrincipal> subjectPrincipal = + nsContentUtils::GetCurrentJSContext() ? nsContentUtils::SubjectPrincipal() + : nullptr; + + // If we don't have a valid principal, probably we are in e10s mode, parent + // side. + if (!subjectPrincipal) { + return true; + } + + // DocShell can have UsercontextID set but loading a document with system + // principal. In this case, we consider everything ok. + if (subjectPrincipal->IsSystemPrincipal()) { + return true; + } + + return subjectPrincipal->GetUserContextId() == userContextId; +} + +nsresult nsWindowWatcher::CreateChromeWindow(nsIWebBrowserChrome* aParentChrome, + uint32_t aChromeFlags, + nsIOpenWindowInfo* aOpenWindowInfo, + nsIWebBrowserChrome** aResult) { + if (NS_WARN_IF(!mWindowCreator)) { + return NS_ERROR_UNEXPECTED; + } + + bool cancel = false; + nsCOMPtr<nsIWebBrowserChrome> newWindowChrome; + nsresult rv = mWindowCreator->CreateChromeWindow( + aParentChrome, aChromeFlags, aOpenWindowInfo, &cancel, + getter_AddRefs(newWindowChrome)); + + if (NS_SUCCEEDED(rv) && cancel) { + newWindowChrome = nullptr; + return NS_ERROR_ABORT; + } + + newWindowChrome.forget(aResult); + return NS_OK; +} + +/** + * Disable persistence of size/position in popups (determined by + * determining whether the features parameter specifies width or height + * in any way). We consider any overriding of the window's size or position + * in the open call as disabling persistence of those attributes. + * Popup windows (which should not persist size or position) generally set + * the size. + * + * @param aFeatures + * The features that was used to open the window. + * @param aTreeOwner + * The nsIDocShellTreeOwner of the newly opened window. If null, + * this function is a no-op. + */ +void nsWindowWatcher::MaybeDisablePersistence( + const SizeSpec& sizeSpec, nsIDocShellTreeOwner* aTreeOwner) { + if (!aTreeOwner) { + return; + } + + if (sizeSpec.SizeSpecified()) { + aTreeOwner->SetPersistence(false, false, false); + } +} + +NS_IMETHODIMP +nsWindowWatcher::OpenWindowWithRemoteTab(nsIRemoteTab* aRemoteTab, + const nsACString& aFeatures, + bool aCalledFromJS, + float aOpenerFullZoom, + nsIOpenWindowInfo* aOpenWindowInfo, + nsIRemoteTab** aResult) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(mWindowCreator); + + if (!nsContentUtils::IsSafeToRunScript()) { + nsContentUtils::WarnScriptWasIgnored(nullptr); + return NS_ERROR_FAILURE; + } + + if (NS_WARN_IF(!mWindowCreator)) { + return NS_ERROR_UNEXPECTED; + } + + bool isFissionWindow = FissionAutostart(); + bool isPrivateBrowsingWindow = + Preferences::GetBool("browser.privatebrowsing.autostart"); + + nsCOMPtr<nsPIDOMWindowOuter> parentWindowOuter; + RefPtr<BrowsingContext> parentBC = aOpenWindowInfo->GetParent(); + if (parentBC) { + RefPtr<Element> browserElement = parentBC->Top()->GetEmbedderElement(); + if (browserElement && browserElement->GetOwnerGlobal() && + browserElement->GetOwnerGlobal()->AsInnerWindow()) { + parentWindowOuter = + browserElement->GetOwnerGlobal()->AsInnerWindow()->GetOuterWindow(); + } + + isFissionWindow = parentBC->UseRemoteSubframes(); + isPrivateBrowsingWindow = + isPrivateBrowsingWindow || parentBC->UsePrivateBrowsing(); + } + + if (!parentWindowOuter) { + // We couldn't find a browser window for the opener, so either we + // never were passed aRemoteTab, the window is closed, + // or it's in the process of closing. Either way, we'll use + // the most recently opened browser window instead. + parentWindowOuter = nsContentUtils::GetMostRecentNonPBWindow(); + } + + if (NS_WARN_IF(!parentWindowOuter)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner = + parentWindowOuter->GetTreeOwner(); + if (NS_WARN_IF(!parentTreeOwner)) { + return NS_ERROR_UNEXPECTED; + } + + if (NS_WARN_IF(!mWindowCreator)) { + return NS_ERROR_UNEXPECTED; + } + + WindowFeatures features; + features.Tokenize(aFeatures); + + SizeSpec sizeSpec; + CalcSizeSpec(features, false, sizeSpec); + + uint32_t chromeFlags = CalculateChromeFlagsForContent(features, sizeSpec); + + if (isPrivateBrowsingWindow) { + chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW; + } + + // A content process has asked for a new window, which implies + // that the new window will need to be remote. + chromeFlags |= nsIWebBrowserChrome::CHROME_REMOTE_WINDOW; + + if (isFissionWindow) { + chromeFlags |= nsIWebBrowserChrome::CHROME_FISSION_WINDOW; + } + + nsCOMPtr<nsIWebBrowserChrome> parentChrome(do_GetInterface(parentTreeOwner)); + nsCOMPtr<nsIWebBrowserChrome> newWindowChrome; + + CreateChromeWindow(parentChrome, chromeFlags, aOpenWindowInfo, + getter_AddRefs(newWindowChrome)); + + if (NS_WARN_IF(!newWindowChrome)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIDocShellTreeItem> chromeTreeItem = + do_GetInterface(newWindowChrome); + if (NS_WARN_IF(!chromeTreeItem)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIDocShellTreeOwner> chromeTreeOwner; + chromeTreeItem->GetTreeOwner(getter_AddRefs(chromeTreeOwner)); + if (NS_WARN_IF(!chromeTreeOwner)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsILoadContext> chromeContext = do_QueryInterface(chromeTreeItem); + if (NS_WARN_IF(!chromeContext)) { + return NS_ERROR_UNEXPECTED; + } + + MOZ_ASSERT(chromeContext->UsePrivateBrowsing() == isPrivateBrowsingWindow); + MOZ_ASSERT(chromeContext->UseRemoteSubframes() == isFissionWindow); + + // Tabs opened from a content process can only open new windows + // that will also run with out-of-process tabs. + MOZ_ASSERT(chromeContext->UseRemoteTabs()); + + MaybeDisablePersistence(sizeSpec, chromeTreeOwner); + + SizeOpenedWindow(chromeTreeOwner, parentWindowOuter, false, sizeSpec, + Some(aOpenerFullZoom)); + + nsCOMPtr<nsIRemoteTab> newBrowserParent; + chromeTreeOwner->GetPrimaryRemoteTab(getter_AddRefs(newBrowserParent)); + if (NS_WARN_IF(!newBrowserParent)) { + return NS_ERROR_UNEXPECTED; + } + + newBrowserParent.forget(aResult); + return NS_OK; +} + +nsresult nsWindowWatcher::OpenWindowInternal( + mozIDOMWindowProxy* aParent, const nsACString& aUrl, + const nsACString& aName, const nsACString& aFeatures, bool aCalledFromJS, + bool aDialog, bool aNavigate, nsIArray* aArgv, bool aIsPopupSpam, + bool aForceNoOpener, bool aForceNoReferrer, PrintKind aPrintKind, + nsDocShellLoadState* aLoadState, BrowsingContext** aResult) { + MOZ_ASSERT_IF(aForceNoReferrer, aForceNoOpener); + + nsresult rv = NS_OK; + bool isNewToplevelWindow = false; + bool windowIsNew = false; + bool windowNeedsName = false; + bool windowIsModal = false; + bool uriToLoadIsChrome = false; + + uint32_t chromeFlags; + nsAutoString name; // string version of aName + nsCOMPtr<nsIURI> uriToLoad; // from aUrl, if any + nsCOMPtr<nsIDocShellTreeOwner> + parentTreeOwner; // from the parent window, if any + RefPtr<BrowsingContext> newBC; // from the new window + + nsCOMPtr<nsPIDOMWindowOuter> parentWindow = + aParent ? nsPIDOMWindowOuter::From(aParent) : nullptr; + + NS_ENSURE_ARG_POINTER(aResult); + *aResult = 0; + + if (!nsContentUtils::IsSafeToRunScript()) { + nsContentUtils::WarnScriptWasIgnored(nullptr); + return NS_ERROR_FAILURE; + } + + if (parentWindow) { + parentTreeOwner = parentWindow->GetTreeOwner(); + } + + // We expect BrowserParent to have provided us the absolute URI of the window + // we're to open, so there's no need to call URIfromURL (or more importantly, + // to check for a chrome URI, which cannot be opened from a remote tab). + if (!aUrl.IsVoid()) { + rv = URIfromURL(aUrl, aParent, getter_AddRefs(uriToLoad)); + if (NS_FAILED(rv)) { + return rv; + } + uriToLoadIsChrome = uriToLoad->SchemeIs("chrome"); + } + + bool nameSpecified = false; + if (!aName.IsEmpty()) { + CopyUTF8toUTF16(aName, name); + nameSpecified = true; + } else { + name.SetIsVoid(true); + } + + WindowFeatures features; + nsAutoCString featuresStr; + if (!aFeatures.IsEmpty()) { + featuresStr.Assign(aFeatures); + features.Tokenize(featuresStr); + } else { + featuresStr.SetIsVoid(true); + } + + RefPtr<BrowsingContext> parentBC( + parentWindow ? parentWindow->GetBrowsingContext() : nullptr); + nsCOMPtr<nsIDocShell> parentDocShell(parentBC ? parentBC->GetDocShell() + : nullptr); + + // Return null for any attempt to trigger a load from a discarded browsing + // context. The spec is non-normative, and doesn't specify what should happen + // when window.open is called on a window with a null browsing context, but it + // does give us broad discretion over when we can decide to ignore an open + // request and return null. + // + // Regardless, we cannot trigger a cross-process load from a discarded + // browsing context, and ideally we should behave consistently whether a load + // is same-process or cross-process. + if (parentBC && parentBC->IsDiscarded()) { + return NS_ERROR_ABORT; + } + + // try to find an extant browsing context with the given name + newBC = GetBrowsingContextByName(name, aForceNoOpener, parentBC); + + // Do sandbox checks here, instead of waiting until nsIDocShell::LoadURI. + // The state of the window can change before this call and if we are blocked + // because of sandboxing, we wouldn't want that to happen. + if (parentBC && parentBC->IsSandboxedFrom(newBC)) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } + + // If our target BrowsingContext is still pending initialization, ignore the + // navigation request targeting it. + if (newBC && NS_WARN_IF(newBC->GetPendingInitialization())) { + return NS_ERROR_ABORT; + } + + // no extant window? make a new one. + + // If no parent, consider it chrome when running in the parent process. + bool hasChromeParent = XRE_IsContentProcess() ? false : true; + if (aParent) { + // Check if the parent document has chrome privileges. + Document* doc = parentWindow->GetDoc(); + hasChromeParent = doc && nsContentUtils::IsChromeDoc(doc); + } + + bool isCallerChrome = nsContentUtils::LegacyIsCallerChromeOrNativeCode(); + + SizeSpec sizeSpec; + CalcSizeSpec(features, hasChromeParent, sizeSpec); + + // Make sure we calculate the chromeFlags *before* we push the + // callee context onto the context stack so that + // the calculation sees the actual caller when doing its + // security checks. + if (isCallerChrome && XRE_IsParentProcess()) { + chromeFlags = CalculateChromeFlagsForSystem( + features, sizeSpec, aDialog, uriToLoadIsChrome, hasChromeParent); + } else { + MOZ_DIAGNOSTIC_ASSERT(parentBC && parentBC->IsContent(), + "content caller must provide content parent"); + chromeFlags = CalculateChromeFlagsForContent(features, sizeSpec); + + if (aDialog) { + MOZ_ASSERT(XRE_IsParentProcess()); + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG; + } + } + + bool windowTypeIsChrome = + chromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_CHROME; + + if (parentBC && !aForceNoOpener) { + if (parentBC->IsChrome() && !windowTypeIsChrome) { + NS_WARNING( + "Content windows may never have chrome windows as their openers."); + return NS_ERROR_INVALID_ARG; + } + if (parentBC->IsContent() && windowTypeIsChrome) { + NS_WARNING( + "Chrome windows may never have content windows as their openers."); + return NS_ERROR_INVALID_ARG; + } + } + + // If we're opening a content window from a content window, we need to exactly + // inherit fission and e10s status flags from parentBC. Only new toplevel + // windows may change these options. + if (parentBC && parentBC->IsContent() && !windowTypeIsChrome) { + chromeFlags &= ~(nsIWebBrowserChrome::CHROME_REMOTE_WINDOW | + nsIWebBrowserChrome::CHROME_FISSION_WINDOW); + if (parentBC->UseRemoteTabs()) { + chromeFlags |= nsIWebBrowserChrome::CHROME_REMOTE_WINDOW; + } + if (parentBC->UseRemoteSubframes()) { + chromeFlags |= nsIWebBrowserChrome::CHROME_FISSION_WINDOW; + } + } + + // XXXbz Why is an AutoJSAPI good enough here? Wouldn't AutoEntryScript (so + // we affect the entry global) make more sense? Or do we just want to affect + // GetSubjectPrincipal()? + dom::AutoJSAPI jsapiChromeGuard; + + if (isCallerChrome && !hasChromeParent && !windowTypeIsChrome) { + // open() is called from chrome on a non-chrome window, initialize an + // AutoJSAPI with the callee to prevent the caller's privileges from leaking + // into code that runs while opening the new window. + // + // The reasoning for this is in bug 289204. Basically, chrome sometimes does + // someContentWindow.open(untrustedURL), and wants to be insulated from + // nasty javascript: URLs and such. But there are also cases where we create + // a window parented to a content window (such as a download dialog), + // usually directly with nsIWindowWatcher. In those cases, we want the + // principal of the initial about:blank document to be system, so that the + // subsequent XUL load can reuse the inner window and avoid blowing away + // expandos. As such, we decide whether to load with the principal of the + // caller or of the parent based on whether the docshell type is chrome or + // content. + + nsCOMPtr<nsIGlobalObject> parentGlobalObject = do_QueryInterface(aParent); + if (!aParent) { + jsapiChromeGuard.Init(); + } else if (NS_WARN_IF(!jsapiChromeGuard.Init(parentGlobalObject))) { + return NS_ERROR_UNEXPECTED; + } + } + + // Information used when opening new content windows. This object will be + // passed through to the inner nsFrameLoader. + RefPtr<nsOpenWindowInfo> openWindowInfo; + if (!newBC && !windowTypeIsChrome) { + openWindowInfo = new nsOpenWindowInfo(); + openWindowInfo->mForceNoOpener = aForceNoOpener; + openWindowInfo->mParent = parentBC; + openWindowInfo->mIsForPrinting = aPrintKind != PRINT_NONE; + openWindowInfo->mIsForWindowDotPrint = aPrintKind == PRINT_WINDOW_DOT_PRINT; + + // We're going to want the window to be immediately available, meaning we + // want it to match the current remoteness. + openWindowInfo->mIsRemote = XRE_IsContentProcess(); + + // If we have a non-system non-expanded subject principal, we can inherit + // our OriginAttributes from it. + nsCOMPtr<nsIPrincipal> subjectPrincipal = + nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller(); + if (subjectPrincipal && + !nsContentUtils::IsSystemOrExpandedPrincipal(subjectPrincipal)) { + openWindowInfo->mOriginAttributes = + subjectPrincipal->OriginAttributesRef(); + } else if (parentBC) { + openWindowInfo->mOriginAttributes = parentBC->OriginAttributesRef(); + } + + MOZ_DIAGNOSTIC_ASSERT( + !parentBC || openWindowInfo->mOriginAttributes.EqualsIgnoringFPD( + parentBC->OriginAttributesRef()), + "subject principal origin attributes doesn't match opener"); + } + + uint32_t activeDocsSandboxFlags = 0; + if (!newBC) { + // We're going to either open up a new window ourselves or ask a + // nsIWindowProvider for one. In either case, we'll want to set the right + // name on it. + windowNeedsName = true; + + // If the parent trying to open a new window is sandboxed + // without 'allow-popups', this is not allowed and we fail here. + if (aParent) { + if (Document* doc = parentWindow->GetDoc()) { + // Save sandbox flags for copying to new browsing context (docShell). + activeDocsSandboxFlags = doc->GetSandboxFlags(); + // Check to see if this frame is allowed to navigate, but don't check if + // we're printing, as that's not a real navigation. + if (aPrintKind == PRINT_NONE && + (activeDocsSandboxFlags & SANDBOXED_AUXILIARY_NAVIGATION)) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } + } + } + + // Now check whether it's ok to ask a window provider for a window. Don't + // do it if we're opening a dialog or if our parent is a chrome window or + // if we're opening something that has modal, dialog, or chrome flags set. + if (parentTreeOwner && !aDialog && parentBC->IsContent() && + !(chromeFlags & (nsIWebBrowserChrome::CHROME_MODAL | + nsIWebBrowserChrome::CHROME_OPENAS_DIALOG | + nsIWebBrowserChrome::CHROME_OPENAS_CHROME))) { + MOZ_ASSERT(openWindowInfo); + + nsCOMPtr<nsIWindowProvider> provider = do_GetInterface(parentTreeOwner); + if (provider) { + rv = provider->ProvideWindow(openWindowInfo, chromeFlags, aCalledFromJS, + sizeSpec.WidthSpecified(), uriToLoad, name, + featuresStr, aForceNoOpener, + aForceNoReferrer, aLoadState, &windowIsNew, + getter_AddRefs(newBC)); + + if (NS_SUCCEEDED(rv) && newBC) { + nsCOMPtr<nsIDocShell> newDocShell = newBC->GetDocShell(); + + // If this is a new window, but it's incompatible with the current + // userContextId, we ignore it and we pretend that nothing has been + // returned by ProvideWindow. + if (!windowIsNew && newDocShell) { + if (!CheckUserContextCompatibility(newDocShell)) { + newBC = nullptr; + windowIsNew = false; + } + } + + } else if (rv == NS_ERROR_ABORT) { + // NS_ERROR_ABORT means the window provider has flat-out rejected + // the open-window call and we should bail. Don't return an error + // here, because our caller may propagate that error, which might + // cause e.g. window.open to throw! Just return null for our out + // param. + return NS_OK; + } + } + } + } + + bool newWindowShouldBeModal = false; + bool parentIsModal = false; + if (!newBC) { + if (XRE_IsContentProcess()) { + // If our window provider failed to provide a window in the content + // process, we cannot recover. Reject the window open request and bail. + return NS_OK; + } + + windowIsNew = true; + isNewToplevelWindow = true; + + nsCOMPtr<nsIWebBrowserChrome> parentChrome( + do_GetInterface(parentTreeOwner)); + + // is the parent (if any) modal? if so, we must be, too. + bool weAreModal = (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL) != 0; + newWindowShouldBeModal = weAreModal; + if (!weAreModal && parentChrome) { + parentChrome->IsWindowModal(&weAreModal); + parentIsModal = weAreModal; + } + + if (weAreModal) { + windowIsModal = true; + // in case we added this because weAreModal + chromeFlags |= nsIWebBrowserChrome::CHROME_MODAL | + nsIWebBrowserChrome::CHROME_DEPENDENT; + } + + // Make sure to not create modal windows if our parent is invisible and + // isn't a chrome window. Otherwise we can end up in a bizarre situation + // where we can't shut down because an invisible window is open. If + // someone tries to do this, throw. + if (!hasChromeParent && (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL)) { + nsCOMPtr<nsIBaseWindow> parentWindow(do_GetInterface(parentTreeOwner)); + nsCOMPtr<nsIWidget> parentWidget; + if (parentWindow) { + parentWindow->GetMainWidget(getter_AddRefs(parentWidget)); + } + // NOTE: the logic for this visibility check is duplicated in + // nsIDOMWindowUtils::isParentWindowMainWidgetVisible - if we change + // how a window is determined "visible" in this context then we should + // also adjust that attribute and/or any consumers of it... + if (parentWidget && !parentWidget->IsVisible()) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + NS_ASSERTION(mWindowCreator, + "attempted to open a new window with no WindowCreator"); + rv = NS_ERROR_FAILURE; + if (mWindowCreator) { + nsCOMPtr<nsIWebBrowserChrome> newChrome; + + nsCOMPtr<nsPIDOMWindowInner> parentTopInnerWindow; + if (parentWindow) { + nsCOMPtr<nsPIDOMWindowOuter> parentTopWindow = + parentWindow->GetInProcessTop(); + if (parentTopWindow) { + parentTopInnerWindow = parentTopWindow->GetCurrentInnerWindow(); + } + } + + if (parentTopInnerWindow) { + parentTopInnerWindow->Suspend(); + } + + /* We can give the window creator some hints. The only hint at this time + is whether the opening window is in a situation that's likely to mean + this is an unrequested popup window we're creating. However we're not + completely honest: we clear that indicator if the opener is chrome, so + that the downstream consumer can treat the indicator to mean simply + that the new window is subject to popup control. */ + rv = CreateChromeWindow(parentChrome, chromeFlags, openWindowInfo, + getter_AddRefs(newChrome)); + if (parentTopInnerWindow) { + parentTopInnerWindow->Resume(); + } + + if (newChrome) { + /* It might be a chrome AppWindow, in which case it won't have + an nsIDOMWindow (primary content shell). But in that case, it'll + be able to hand over an nsIDocShellTreeItem directly. */ + nsCOMPtr<nsPIDOMWindowOuter> newWindow(do_GetInterface(newChrome)); + nsCOMPtr<nsIDocShellTreeItem> newDocShellItem; + if (newWindow) { + newDocShellItem = newWindow->GetDocShell(); + } + if (!newDocShellItem) { + newDocShellItem = do_GetInterface(newChrome); + } + if (!newDocShellItem) { + rv = NS_ERROR_FAILURE; + } + newBC = newDocShellItem->GetBrowsingContext(); + } + } + } + + // better have a window to use by this point + if (!newBC) { + return rv; + } + + // If our parent is sandboxed, set it as the one permitted sandboxed navigator + // on the new window we're opening. + if (activeDocsSandboxFlags && parentBC) { + MOZ_ALWAYS_SUCCEEDS(newBC->SetOnePermittedSandboxedNavigator(parentBC)); + } + + if (!aForceNoOpener && parentBC) { + // If we've created a new content window, its opener should have been set + // when its BrowsingContext was created, in order to ensure that the context + // is loaded within the correct BrowsingContextGroup. + if (windowIsNew && newBC->IsContent()) { + MOZ_RELEASE_ASSERT(newBC->GetOpenerId() == parentBC->Id()); + MOZ_RELEASE_ASSERT(!!parentBC == newBC->HadOriginalOpener()); + } else { + // Update the opener for an existing or chrome BC. + newBC->SetOpener(parentBC); + } + } + + RefPtr<nsDocShell> newDocShell(nsDocShell::Cast(newBC->GetDocShell())); + + // As required by spec, new windows always start out same-process, even if the + // URL being loaded will eventually load in a new process. + MOZ_DIAGNOSTIC_ASSERT(!windowIsNew || newDocShell); + // New top-level windows are only opened in the parent process and are, by + // definition, always in-process. + MOZ_DIAGNOSTIC_ASSERT(!isNewToplevelWindow || newDocShell); + + // Copy sandbox flags to the new window if activeDocsSandboxFlags says to do + // so. Note that it's only nonzero if the window is new, so clobbering + // sandbox flags on the window makes sense in that case. + if (activeDocsSandboxFlags & + SANDBOX_PROPAGATES_TO_AUXILIARY_BROWSING_CONTEXTS) { + MOZ_ASSERT(windowIsNew, "Should only get here for new windows"); + MOZ_ALWAYS_SUCCEEDS(newBC->SetSandboxFlags(activeDocsSandboxFlags)); + } + + RefPtr<nsGlobalWindowOuter> win( + nsGlobalWindowOuter::Cast(newBC->GetDOMWindow())); + if (win) { + if (windowIsNew) { +#ifdef DEBUG + // Assert that we're not loading things right now. If we are, when + // that load completes it will clobber whatever principals we set up + // on this new window! + nsCOMPtr<nsIChannel> chan; + newDocShell->GetDocumentChannel(getter_AddRefs(chan)); + MOZ_ASSERT(!chan, "Why is there a document channel?"); +#endif + + if (RefPtr<Document> doc = win->GetExtantDoc()) { + doc->SetIsInitialDocument(true); + } + } + } + + MOZ_ASSERT(win || !windowIsNew, "New windows are always created in-process"); + + *aResult = do_AddRef(newBC).take(); + + if (isNewToplevelWindow) { + nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner; + newDocShell->GetTreeOwner(getter_AddRefs(newTreeOwner)); + MaybeDisablePersistence(sizeSpec, newTreeOwner); + } + + if (aDialog && aArgv) { + MOZ_ASSERT(win); + NS_ENSURE_TRUE(win, NS_ERROR_UNEXPECTED); + + // Set the args on the new window. + MOZ_TRY(win->SetArguments(aArgv)); + } + + /* allow a window that we found by name to keep its name (important for cases + like _self where the given name is different (and invalid)). Also, _blank + is not a window name. */ + if (windowNeedsName) { + if (nameSpecified && !name.LowerCaseEqualsLiteral("_blank")) { + MOZ_ALWAYS_SUCCEEDS(newBC->SetName(name)); + } else { + MOZ_ALWAYS_SUCCEEDS(newBC->SetName(u""_ns)); + } + } + + // Now we have to set the right opener principal on the new window. Note + // that we have to do this _before_ starting any URI loads, thanks to the + // sync nature of javascript: loads. + // + // Note: The check for the current JSContext isn't necessarily sensical. + // It's just designed to preserve old semantics during a mass-conversion + // patch. + // Bug 1498605 verify usages of systemPrincipal here + JSContext* cx = nsContentUtils::GetCurrentJSContext(); + nsCOMPtr<nsIPrincipal> subjectPrincipal = + cx ? nsContentUtils::SubjectPrincipal() + : nsContentUtils::GetSystemPrincipal(); + + if (windowIsNew) { + if (subjectPrincipal && + !nsContentUtils::IsSystemOrExpandedPrincipal(subjectPrincipal) && + newBC->IsContent()) { + MOZ_DIAGNOSTIC_ASSERT( + subjectPrincipal->OriginAttributesRef().EqualsIgnoringFPD( + newBC->OriginAttributesRef())); + } + + bool autoPrivateBrowsing = + Preferences::GetBool("browser.privatebrowsing.autostart"); + + if (!autoPrivateBrowsing && + (chromeFlags & nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW)) { + if (newBC->IsChrome()) { + newBC->SetUsePrivateBrowsing(false); + } + MOZ_DIAGNOSTIC_ASSERT( + !newBC->UsePrivateBrowsing(), + "CHROME_NON_PRIVATE_WINDOW passed, but got private window"); + } else if (autoPrivateBrowsing || + (chromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW)) { + if (newBC->IsChrome()) { + newBC->SetUsePrivateBrowsing(true); + } + MOZ_DIAGNOSTIC_ASSERT( + newBC->UsePrivateBrowsing(), + "CHROME_PRIVATE_WINDOW passed, but got non-private window"); + } + + // Now set the opener principal on the new window. Note that we need to do + // this no matter whether we were opened from JS; if there is nothing on + // the JS stack, just use the principal of our parent window. In those + // cases we do _not_ set the parent window principal as the owner of the + // load--since we really don't know who the owner is, just leave it null. + NS_ASSERTION(win == newDocShell->GetWindow(), "Different windows??"); + + // The principal of the initial about:blank document gets set up in + // nsWindowWatcher::AddWindow. Make sure to call it. In the common case + // this call already happened when the window was created, but + // SetInitialPrincipalToSubject is safe to call multiple times. + if (win) { + nsCOMPtr<nsIContentSecurityPolicy> cspToInheritForAboutBlank; + Maybe<nsILoadInfo::CrossOriginEmbedderPolicy> coepToInheritForAboutBlank; + nsCOMPtr<mozIDOMWindowProxy> targetOpener = win->GetSameProcessOpener(); + nsCOMPtr<nsIDocShell> openerDocShell(do_GetInterface(targetOpener)); + if (openerDocShell) { + RefPtr<Document> openerDoc = + static_cast<nsDocShell*>(openerDocShell.get())->GetDocument(); + cspToInheritForAboutBlank = openerDoc ? openerDoc->GetCsp() : nullptr; + coepToInheritForAboutBlank = openerDoc->GetEmbedderPolicy(); + } + win->SetInitialPrincipalToSubject(cspToInheritForAboutBlank, + coepToInheritForAboutBlank); + + if (aIsPopupSpam) { + MOZ_ASSERT(!newBC->GetIsPopupSpam(), + "Who marked it as popup spam already???"); + // Make sure we don't mess up our counter even if the above assert + // fails. + if (!newBC->GetIsPopupSpam()) { + MOZ_ALWAYS_SUCCEEDS(newBC->SetIsPopupSpam(true)); + } + } + } + } + + // We rely on CalculateChromeFlags to decide whether remote (out-of-process) + // tabs should be used. + MOZ_DIAGNOSTIC_ASSERT( + newBC->UseRemoteTabs() == + !!(chromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW)); + MOZ_DIAGNOSTIC_ASSERT( + newBC->UseRemoteSubframes() == + !!(chromeFlags & nsIWebBrowserChrome::CHROME_FISSION_WINDOW)); + + nsCOMPtr<nsPIDOMWindowInner> pInnerWin = + parentWindow ? parentWindow->GetCurrentInnerWindow() : nullptr; + ; + RefPtr<nsDocShellLoadState> loadState = aLoadState; + if (uriToLoad && loadState) { + // If a URI was passed to this function, open that, not what was passed in + // the original LoadState. See Bug 1515433. + loadState->SetURI(uriToLoad); + } else if (uriToLoad && aNavigate && !loadState) { + RefPtr<WindowContext> context = + pInnerWin ? pInnerWin->GetWindowContext() : nullptr; + loadState = new nsDocShellLoadState(uriToLoad); + + loadState->SetSourceBrowsingContext(parentBC); + loadState->SetAllowFocusMove(true); + loadState->SetHasValidUserGestureActivation( + context && context->HasValidTransientUserGestureActivation()); + if (parentBC) { + loadState->SetTriggeringSandboxFlags(parentBC->GetSandboxFlags()); + } + + if (subjectPrincipal) { + loadState->SetTriggeringPrincipal(subjectPrincipal); + } +#ifndef ANDROID + MOZ_ASSERT(subjectPrincipal, + "nsWindowWatcher: triggeringPrincipal required"); +#endif + + if (!aForceNoReferrer) { + /* use the URL from the *extant* document, if any. The usual accessor + GetDocument will synchronously create an about:blank document if + it has no better answer, and we only care about a real document. + Also using GetDocument to force document creation seems to + screw up focus in the hidden window; see bug 36016. + */ + RefPtr<Document> doc = GetEntryDocument(); + if (!doc && parentWindow) { + doc = parentWindow->GetExtantDoc(); + } + if (doc) { + auto referrerInfo = MakeRefPtr<ReferrerInfo>(*doc); + loadState->SetReferrerInfo(referrerInfo); + } + } + } + + if (loadState && cx) { + nsGlobalWindowInner* win = xpc::CurrentWindowOrNull(cx); + if (win) { + nsCOMPtr<nsIContentSecurityPolicy> csp = win->GetCsp(); + loadState->SetCsp(csp); + } + } + + if (isNewToplevelWindow) { + // Notify observers that the window is open and ready. + // The window has not yet started to load a document. + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + if (obsSvc) { + obsSvc->NotifyObservers(ToSupports(win), "toplevel-window-ready", + nullptr); + } + } + + // Before loading the URI we want to be 100% sure that we use the correct + // userContextId. + MOZ_ASSERT_IF(newDocShell, CheckUserContextCompatibility(newDocShell)); + + // If this tab or window has been opened by a window.open call, we have to + // provide all the data needed to send a + // webNavigation.onCreatedNavigationTarget event. + if (parentDocShell && windowIsNew) { + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + + if (obsSvc) { + RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag(); + + if (uriToLoad) { + // The url notified in the webNavigation.onCreatedNavigationTarget + // event. + props->SetPropertyAsACString(u"url"_ns, uriToLoad->GetSpecOrDefault()); + } + + props->SetPropertyAsInterface(u"sourceTabDocShell"_ns, parentDocShell); + props->SetPropertyAsInterface(u"createdTabDocShell"_ns, + ToSupports(newDocShell)); + + obsSvc->NotifyObservers(static_cast<nsIPropertyBag2*>(props), + "webNavigation-createdNavigationTarget-from-js", + nullptr); + } + } + + if (uriToLoad && aNavigate) { + loadState->SetLoadFlags( + windowIsNew + ? static_cast<uint32_t>(nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD) + : static_cast<uint32_t>(nsIWebNavigation::LOAD_FLAGS_NONE)); + loadState->SetFirstParty(true); + + // Should this pay attention to errors returned by LoadURI? + newBC->LoadURI(loadState); + } + + // Copy the current session storage for the current domain. Don't perform the + // copy if we're forcing noopener, however. + if (!aForceNoOpener && subjectPrincipal && parentDocShell && newDocShell) { + const RefPtr<SessionStorageManager> parentStorageManager = + parentDocShell->GetBrowsingContext()->GetSessionStorageManager(); + const RefPtr<SessionStorageManager> newStorageManager = + newDocShell->GetBrowsingContext()->GetSessionStorageManager(); + + if (parentStorageManager && newStorageManager) { + RefPtr<Storage> storage; + parentStorageManager->GetStorage( + pInnerWin, subjectPrincipal, subjectPrincipal, + newBC->UsePrivateBrowsing(), getter_AddRefs(storage)); + if (storage) { + newStorageManager->CloneStorage(storage); + } + } + } + + if (isNewToplevelWindow) { + nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner; + newDocShell->GetTreeOwner(getter_AddRefs(newTreeOwner)); + SizeOpenedWindow(newTreeOwner, aParent, isCallerChrome, sizeSpec); + } + + if (windowIsModal) { + NS_ENSURE_TRUE(newDocShell, NS_ERROR_NOT_IMPLEMENTED); + + nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner; + newDocShell->GetTreeOwner(getter_AddRefs(newTreeOwner)); + nsCOMPtr<nsIWebBrowserChrome> newChrome(do_GetInterface(newTreeOwner)); + + // Throw an exception here if no web browser chrome is available, + // we need that to show a modal window. + NS_ENSURE_TRUE(newChrome, NS_ERROR_NOT_AVAILABLE); + + // Dispatch dialog events etc, but we only want to do that if + // we're opening a modal content window (the helper classes are + // no-ops if given no window), for chrome dialogs we don't want to + // do any of that (it's done elsewhere for us). + // Make sure we maintain the state on an outer window, because + // that's where it lives; inner windows assert if you try to + // maintain the state on them. + nsAutoWindowStateHelper windowStateHelper(parentWindow); + + if (!windowStateHelper.DefaultEnabled()) { + // Default to cancel not opening the modal window. + NS_RELEASE(*aResult); + + return NS_OK; + } + + bool isAppModal = false; + nsCOMPtr<nsIBaseWindow> parentWindow(do_GetInterface(newTreeOwner)); + nsCOMPtr<nsIWidget> parentWidget; + if (parentWindow) { + parentWindow->GetMainWidget(getter_AddRefs(parentWidget)); + if (parentWidget) { + isAppModal = parentWidget->IsRunningAppModal(); + } + } + if (parentWidget && + ((!newWindowShouldBeModal && parentIsModal) || isAppModal)) { + parentWidget->SetFakeModal(true); + } else { + // Reset popup state while opening a modal dialog, and firing + // events about the dialog, to prevent the current state from + // being active the whole time a modal dialog is open. + AutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused); + + newChrome->ShowAsModal(); + } + } + // If a website opens a popup exit DOM fullscreen + if (StaticPrefs::full_screen_api_exit_on_windowOpen() && windowIsNew && + aCalledFromJS && !hasChromeParent && !isCallerChrome && parentWindow) { + Document::AsyncExitFullscreen(parentWindow->GetDoc()); + } + + if (aForceNoOpener && windowIsNew) { + NS_RELEASE(*aResult); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::RegisterNotification(nsIObserver* aObserver) { + // just a convenience method; it delegates to nsIObserverService + + if (!aObserver) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (!os) { + return NS_ERROR_FAILURE; + } + + nsresult rv = os->AddObserver(aObserver, "domwindowopened", false); + if (NS_SUCCEEDED(rv)) { + rv = os->AddObserver(aObserver, "domwindowclosed", false); + } + + return rv; +} + +NS_IMETHODIMP +nsWindowWatcher::UnregisterNotification(nsIObserver* aObserver) { + // just a convenience method; it delegates to nsIObserverService + + if (!aObserver) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (!os) { + return NS_ERROR_FAILURE; + } + + os->RemoveObserver(aObserver, "domwindowopened"); + os->RemoveObserver(aObserver, "domwindowclosed"); + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::GetWindowEnumerator(nsISimpleEnumerator** aResult) { + if (!aResult) { + return NS_ERROR_INVALID_ARG; + } + + MutexAutoLock lock(mListLock); + RefPtr<nsWatcherWindowEnumerator> enumerator = + new nsWatcherWindowEnumerator(this); + enumerator.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::GetNewPrompter(mozIDOMWindowProxy* aParent, + nsIPrompt** aResult) { + // This is for backwards compat only. Callers should just use the prompt + // service directly. + nsresult rv; + nsCOMPtr<nsIPromptFactory> factory = + do_GetService("@mozilla.org/prompter;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + return factory->GetPrompt(aParent, NS_GET_IID(nsIPrompt), + reinterpret_cast<void**>(aResult)); +} + +NS_IMETHODIMP +nsWindowWatcher::GetNewAuthPrompter(mozIDOMWindowProxy* aParent, + nsIAuthPrompt** aResult) { + // This is for backwards compat only. Callers should just use the prompt + // service directly. + nsresult rv; + nsCOMPtr<nsIPromptFactory> factory = + do_GetService("@mozilla.org/prompter;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + return factory->GetPrompt(aParent, NS_GET_IID(nsIAuthPrompt), + reinterpret_cast<void**>(aResult)); +} + +NS_IMETHODIMP +nsWindowWatcher::GetPrompt(mozIDOMWindowProxy* aParent, const nsIID& aIID, + void** aResult) { + // This is for backwards compat only. Callers should just use the prompt + // service directly. + nsresult rv; + nsCOMPtr<nsIPromptFactory> factory = + do_GetService("@mozilla.org/prompter;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = factory->GetPrompt(aParent, aIID, aResult); + + // Allow for an embedding implementation to not support nsIAuthPrompt2. + if (rv == NS_NOINTERFACE && aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) { + nsCOMPtr<nsIAuthPrompt> oldPrompt; + rv = factory->GetPrompt(aParent, NS_GET_IID(nsIAuthPrompt), + getter_AddRefs(oldPrompt)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_WrapAuthPrompt(oldPrompt, reinterpret_cast<nsIAuthPrompt2**>(aResult)); + if (!*aResult) { + rv = NS_ERROR_NOT_AVAILABLE; + } + } + return rv; +} + +NS_IMETHODIMP +nsWindowWatcher::SetWindowCreator(nsIWindowCreator* aCreator) { + mWindowCreator = aCreator; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::HasWindowCreator(bool* aResult) { + *aResult = mWindowCreator; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::GetActiveWindow(mozIDOMWindowProxy** aActiveWindow) { + *aActiveWindow = nullptr; + nsFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + return fm->GetActiveWindow(aActiveWindow); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::AddWindow(mozIDOMWindowProxy* aWindow, + nsIWebBrowserChrome* aChrome) { + if (!aWindow) { + return NS_ERROR_INVALID_ARG; + } + + { + nsWatcherWindowEntry* info; + MutexAutoLock lock(mListLock); + + // if we already have an entry for this window, adjust + // its chrome mapping and return + info = FindWindowEntry(aWindow); + if (info) { + nsCOMPtr<nsISupportsWeakReference> supportsweak( + do_QueryInterface(aChrome)); + if (supportsweak) { + supportsweak->GetWeakReference(getter_AddRefs(info->mChromeWeak)); + } else { + info->mChrome = aChrome; + info->mChromeWeak = nullptr; + } + return NS_OK; + } + + // create a window info struct and add it to the list of windows + info = new nsWatcherWindowEntry(aWindow, aChrome); + if (!info) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (mOldestWindow) { + info->InsertAfter(mOldestWindow->mOlder); + } else { + mOldestWindow = info; + } + } // leave the mListLock + + // a window being added to us signifies a newly opened window. + // send notifications. + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (!os) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsISupports> domwin(do_QueryInterface(aWindow)); + return os->NotifyObservers(domwin, "domwindowopened", 0); +} + +NS_IMETHODIMP +nsWindowWatcher::RemoveWindow(mozIDOMWindowProxy* aWindow) { + // find the corresponding nsWatcherWindowEntry, remove it + + if (!aWindow) { + return NS_ERROR_INVALID_ARG; + } + + nsWatcherWindowEntry* info = FindWindowEntry(aWindow); + if (info) { + RemoveWindow(info); + return NS_OK; + } + NS_WARNING("requested removal of nonexistent window"); + return NS_ERROR_INVALID_ARG; +} + +nsWatcherWindowEntry* nsWindowWatcher::FindWindowEntry( + mozIDOMWindowProxy* aWindow) { + // find the corresponding nsWatcherWindowEntry + nsWatcherWindowEntry* info; + nsWatcherWindowEntry* listEnd; + + info = mOldestWindow; + listEnd = 0; + while (info != listEnd) { + if (info->mWindow == aWindow) { + return info; + } + info = info->mYounger; + listEnd = mOldestWindow; + } + return 0; +} + +nsresult nsWindowWatcher::RemoveWindow(nsWatcherWindowEntry* aInfo) { + uint32_t count = mEnumeratorList.Length(); + + { + // notify the enumerators + MutexAutoLock lock(mListLock); + for (uint32_t ctr = 0; ctr < count; ++ctr) { + mEnumeratorList[ctr]->WindowRemoved(aInfo); + } + + // remove the element from the list + if (aInfo == mOldestWindow) { + mOldestWindow = aInfo->mYounger == mOldestWindow ? 0 : aInfo->mYounger; + } + aInfo->Unlink(); + } + + // a window being removed from us signifies a newly closed window. + // send notifications. + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + nsCOMPtr<nsISupports> domwin(do_QueryInterface(aInfo->mWindow)); + os->NotifyObservers(domwin, "domwindowclosed", 0); + } + + delete aInfo; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::GetChromeForWindow(mozIDOMWindowProxy* aWindow, + nsIWebBrowserChrome** aResult) { + if (!aWindow || !aResult) { + return NS_ERROR_INVALID_ARG; + } + *aResult = 0; + + MutexAutoLock lock(mListLock); + nsWatcherWindowEntry* info = FindWindowEntry(aWindow); + if (info) { + if (info->mChromeWeak) { + return info->mChromeWeak->QueryReferent( + NS_GET_IID(nsIWebBrowserChrome), reinterpret_cast<void**>(aResult)); + } + *aResult = info->mChrome; + NS_IF_ADDREF(*aResult); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::GetWindowByName(const nsAString& aTargetName, + mozIDOMWindowProxy* aCurrentWindow, + mozIDOMWindowProxy** aResult) { + if (!aResult) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = nullptr; + + BrowsingContext* currentContext = + aCurrentWindow + ? nsPIDOMWindowOuter::From(aCurrentWindow)->GetBrowsingContext() + : nullptr; + + RefPtr<BrowsingContext> context = + GetBrowsingContextByName(aTargetName, false, currentContext); + + if (context) { + *aResult = do_AddRef(context->GetDOMWindow()).take(); + MOZ_ASSERT(*aResult); + } + + return NS_OK; +} + +bool nsWindowWatcher::AddEnumerator(nsWatcherWindowEnumerator* aEnumerator) { + // (requires a lock; assumes it's called by someone holding the lock) + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier, or change the return type to void. + mEnumeratorList.AppendElement(aEnumerator); + return true; +} + +bool nsWindowWatcher::RemoveEnumerator(nsWatcherWindowEnumerator* aEnumerator) { + // (requires a lock; assumes it's called by someone holding the lock) + return mEnumeratorList.RemoveElement(aEnumerator); +} + +nsresult nsWindowWatcher::URIfromURL(const nsACString& aURL, + mozIDOMWindowProxy* aParent, + nsIURI** aURI) { + // Build the URI relative to the entry global. + nsCOMPtr<nsPIDOMWindowInner> baseWindow = do_QueryInterface(GetEntryGlobal()); + + // failing that, build it relative to the parent window, if possible + if (!baseWindow && aParent) { + baseWindow = nsPIDOMWindowOuter::From(aParent)->GetCurrentInnerWindow(); + } + + // failing that, use the given URL unmodified. It had better not be relative. + + nsIURI* baseURI = nullptr; + + // get baseWindow's document URI + if (baseWindow) { + if (Document* doc = baseWindow->GetDoc()) { + baseURI = doc->GetDocBaseURI(); + } + } + + // build and return the absolute URI + return NS_NewURI(aURI, aURL, nullptr, baseURI); +} + +// static +uint32_t nsWindowWatcher::CalculateChromeFlagsHelper( + uint32_t aInitialFlags, const WindowFeatures& aFeatures, + const SizeSpec& aSizeSpec, bool* presenceFlag, bool aHasChromeParent) { + uint32_t chromeFlags = aInitialFlags; + + if (aFeatures.GetBoolWithDefault("titlebar", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_TITLEBAR; + } + if (aFeatures.GetBoolWithDefault("close", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_CLOSE; + } + if (aFeatures.GetBoolWithDefault("toolbar", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_TOOLBAR; + } + if (aFeatures.GetBoolWithDefault("location", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_LOCATIONBAR; + } + if (aFeatures.GetBoolWithDefault("personalbar", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR; + } + if (aFeatures.GetBoolWithDefault("status", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_STATUSBAR; + } + if (aFeatures.GetBoolWithDefault("menubar", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_MENUBAR; + } + if (aFeatures.GetBoolWithDefault("resizable", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_RESIZE; + } + if (aFeatures.GetBoolWithDefault("minimizable", false, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_MIN; + } + + if (aFeatures.GetBoolWithDefault("scrollbars", true, presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_SCROLLBARS; + } + + if (aHasChromeParent) { + return chromeFlags; + } + + // Web content isn't allowed to control UI visibility separately, but only + // whether to open a popup or not. + // + // The above code is still necessary to calculate `presenceFlag`. + // (`ShouldOpenPopup` early returns and doesn't check all feature) + + if (ShouldOpenPopup(aFeatures, aSizeSpec)) { + // Flags for opening a popup, that doesn't have the following: + // * nsIWebBrowserChrome::CHROME_TOOLBAR + // * nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR + // * nsIWebBrowserChrome::CHROME_MENUBAR + return aInitialFlags | nsIWebBrowserChrome::CHROME_TITLEBAR | + nsIWebBrowserChrome::CHROME_WINDOW_CLOSE | + nsIWebBrowserChrome::CHROME_LOCATIONBAR | + nsIWebBrowserChrome::CHROME_STATUSBAR | + nsIWebBrowserChrome::CHROME_WINDOW_RESIZE | + nsIWebBrowserChrome::CHROME_WINDOW_MIN | + nsIWebBrowserChrome::CHROME_SCROLLBARS; + } + + // Otherwise open the current/new tab in the current/new window + // (depends on browser.link.open_newwindow). + return aInitialFlags | nsIWebBrowserChrome::CHROME_ALL; +} + +// static +uint32_t nsWindowWatcher::EnsureFlagsSafeForContent(uint32_t aChromeFlags, + bool aChromeURL) { + aChromeFlags |= nsIWebBrowserChrome::CHROME_TITLEBAR; + aChromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_CLOSE; + aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_LOWERED; + aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_RAISED; + aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_POPUP; + /* Untrusted script is allowed to pose modal windows with a chrome + scheme. This check could stand to be better. But it effectively + prevents untrusted script from opening modal windows in general + while still allowing alerts and the like. */ + if (!aChromeURL) { + aChromeFlags &= ~(nsIWebBrowserChrome::CHROME_MODAL | + nsIWebBrowserChrome::CHROME_OPENAS_CHROME); + } + + if (!(aChromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_CHROME)) { + aChromeFlags &= ~nsIWebBrowserChrome::CHROME_DEPENDENT; + } + + return aChromeFlags; +} + +// static +bool nsWindowWatcher::ShouldOpenPopup(const WindowFeatures& aFeatures, + const SizeSpec& aSizeSpec) { + if (aFeatures.IsEmpty()) { + return false; + } + + // Follow Google Chrome's behavior that opens a popup depending on + // the following features. + if (!aFeatures.GetBoolWithDefault("location", false) && + !aFeatures.GetBoolWithDefault("toolbar", false)) { + return true; + } + + if (!aFeatures.GetBoolWithDefault("menubar", false)) { + return true; + } + + if (!aFeatures.GetBoolWithDefault("resizable", true)) { + return true; + } + + if (!aFeatures.GetBoolWithDefault("scrollbars", false)) { + return true; + } + + if (!aFeatures.GetBoolWithDefault("status", false)) { + return true; + } + + // Follow Safari's behavior that opens a popup when width is specified. + if (aSizeSpec.WidthSpecified()) { + return true; + } + + return false; +} + +/** + * Calculate the chrome bitmask from a string list of features requested + * from a child process. The feature string can only control whether to open a + * new tab or a new popup. + * @param aFeatures a string containing a list of named features + * @param aSizeSpec the result of CalcSizeSpec + * @return the chrome bitmask + */ +// static +uint32_t nsWindowWatcher::CalculateChromeFlagsForContent( + const WindowFeatures& aFeatures, const SizeSpec& aSizeSpec) { + if (aFeatures.IsEmpty()) { + return nsIWebBrowserChrome::CHROME_ALL; + } + + uint32_t chromeFlags = CalculateChromeFlagsHelper( + nsIWebBrowserChrome::CHROME_WINDOW_BORDERS, aFeatures, aSizeSpec); + + return EnsureFlagsSafeForContent(chromeFlags); +} + +/** + * Calculate the chrome bitmask from a string list of features for a new + * privileged window. + * @param aFeatures a string containing a list of named chrome features + * @param aDialog affects the assumptions made about unnamed features + * @param aChromeURL true if the window is being sent to a chrome:// URL + * @param aHasChromeParent true if the parent window is privileged + * @return the chrome bitmask + */ +// static +uint32_t nsWindowWatcher::CalculateChromeFlagsForSystem( + const WindowFeatures& aFeatures, const SizeSpec& aSizeSpec, bool aDialog, + bool aChromeURL, bool aHasChromeParent) { + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(nsContentUtils::LegacyIsCallerChromeOrNativeCode()); + + uint32_t chromeFlags = 0; + + // The features string is made void by OpenWindowInternal + // if nullptr was originally passed as the features string. + if (aFeatures.IsEmpty()) { + chromeFlags = nsIWebBrowserChrome::CHROME_ALL; + if (aDialog) { + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG | + nsIWebBrowserChrome::CHROME_OPENAS_CHROME; + } + } else { + chromeFlags = nsIWebBrowserChrome::CHROME_WINDOW_BORDERS; + } + + /* This function has become complicated since browser windows and + dialogs diverged. The difference is, browser windows assume all + chrome not explicitly mentioned is off, if the features string + is not null. Exceptions are some OS border chrome new with Mozilla. + Dialogs interpret a (mostly) empty features string to mean + "OS's choice," and also support an "all" flag explicitly disallowed + in the standards-compliant window.(normal)open. */ + + bool presenceFlag = false; + if (aDialog && aFeatures.GetBoolWithDefault("all", false, &presenceFlag)) { + chromeFlags = nsIWebBrowserChrome::CHROME_ALL; + } + + /* Next, allow explicitly named options to override the initial settings */ + chromeFlags = CalculateChromeFlagsHelper(chromeFlags, aFeatures, aSizeSpec, + &presenceFlag, aHasChromeParent); + + // Determine whether the window is a private browsing window + if (aFeatures.GetBoolWithDefault("private", false, &presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW; + } + if (aFeatures.GetBoolWithDefault("non-private", false, &presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW; + } + + // Determine whether the window should have remote tabs. + bool remote = BrowserTabsRemoteAutostart(); + + if (remote) { + remote = !aFeatures.GetBoolWithDefault("non-remote", false, &presenceFlag); + } else { + remote = aFeatures.GetBoolWithDefault("remote", false, &presenceFlag); + } + + if (remote) { + chromeFlags |= nsIWebBrowserChrome::CHROME_REMOTE_WINDOW; + } + + // Determine whether the window should have remote subframes + bool fission = FissionAutostart(); + + if (fission) { + fission = + !aFeatures.GetBoolWithDefault("non-fission", false, &presenceFlag); + } else { + fission = aFeatures.GetBoolWithDefault("fission", false, &presenceFlag); + } + + if (fission) { + chromeFlags |= nsIWebBrowserChrome::CHROME_FISSION_WINDOW; + } + + if (aFeatures.GetBoolWithDefault("popup", false, &presenceFlag)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_POPUP; + } + + /* OK. + Normal browser windows, in spite of a stated pattern of turning off + all chrome not mentioned explicitly, will want the new OS chrome (window + borders, titlebars, closebox) on, unless explicitly turned off. + Dialogs, on the other hand, take the absence of any explicit settings + to mean "OS' choice." */ + + // default titlebar and closebox to "on," if not mentioned at all + if (!(chromeFlags & nsIWebBrowserChrome::CHROME_WINDOW_POPUP)) { + if (!aFeatures.Exists("titlebar")) { + chromeFlags |= nsIWebBrowserChrome::CHROME_TITLEBAR; + } + if (!aFeatures.Exists("close")) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_CLOSE; + } + } + + if (aDialog && !aFeatures.IsEmpty() && !presenceFlag) { + chromeFlags = nsIWebBrowserChrome::CHROME_DEFAULT; + } + + /* Finally, once all the above normal chrome has been divined, deal + with the features that are more operating hints than appearance + instructions. (Note modality implies dependence.) */ + + if (aFeatures.GetBoolWithDefault("alwayslowered", false) || + aFeatures.GetBoolWithDefault("z-lock", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_LOWERED; + } else if (aFeatures.GetBoolWithDefault("alwaysraised", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_RAISED; + } + + if (aFeatures.GetBoolWithDefault("suppressanimation", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_SUPPRESS_ANIMATION; + } + if (aFeatures.GetBoolWithDefault("alwaysontop", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_ALWAYS_ON_TOP; + } + if (aFeatures.GetBoolWithDefault("chrome", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_CHROME; + } + if (aFeatures.GetBoolWithDefault("extrachrome", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_EXTRA; + } + if (aFeatures.GetBoolWithDefault("centerscreen", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_CENTER_SCREEN; + } + if (aFeatures.GetBoolWithDefault("dependent", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_DEPENDENT; + } + if (aFeatures.GetBoolWithDefault("modal", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_MODAL | + nsIWebBrowserChrome::CHROME_DEPENDENT; + } + + /* On mobile we want to ignore the dialog window feature, since the mobile UI + does not provide any affordance for dialog windows. This does not interfere + with dialog windows created through openDialog. */ + bool disableDialogFeature = false; + nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID); + + branch->GetBoolPref("dom.disable_window_open_dialog_feature", + &disableDialogFeature); + + if (!disableDialogFeature) { + if (aFeatures.GetBoolWithDefault("dialog", false)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG; + } + } + + /* and dialogs need to have the last word. assume dialogs are dialogs, + and opened as chrome, unless explicitly told otherwise. */ + if (aDialog) { + if (!aFeatures.Exists("dialog")) { + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG; + } + if (!aFeatures.Exists("chrome")) { + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_CHROME; + } + } + + /* missing + chromeFlags->copy_history + */ + + // Check security state for use in determing window dimensions + if (!aHasChromeParent) { + chromeFlags = EnsureFlagsSafeForContent(chromeFlags, aChromeURL); + } + + return chromeFlags; +} + +already_AddRefed<BrowsingContext> nsWindowWatcher::GetBrowsingContextByName( + const nsAString& aName, bool aForceNoOpener, + BrowsingContext* aCurrentContext) { + if (aName.IsEmpty()) { + return nullptr; + } + + if (aForceNoOpener && !nsContentUtils::IsSpecialName(aName)) { + // Ignore all other names in the noopener case. + return nullptr; + } + + RefPtr<BrowsingContext> foundContext; + if (aCurrentContext) { + foundContext = aCurrentContext->FindWithName(aName); + } else if (!nsContentUtils::IsSpecialName(aName)) { + // If we are looking for an item and we don't have a docshell we are + // checking on, let's just look in the chrome browsing context group! + for (RefPtr<BrowsingContext> toplevel : + BrowsingContextGroup::GetChromeGroup()->Toplevels()) { + foundContext = toplevel->FindWithNameInSubtree(aName, *toplevel); + if (foundContext) { + break; + } + } + } + + return foundContext.forget(); +} + +// static +void nsWindowWatcher::CalcSizeSpec(const WindowFeatures& aFeatures, + bool aHasChromeParent, SizeSpec& aResult) { + // https://drafts.csswg.org/cssom-view/#set-up-browsing-context-features + // To set up browsing context features for a browsing context `target` given + // a map `tokenizedFeatures`: + + // Step 1. Let `x` be null. + // (implicit) + + // Step 2. Let `y` be null. + // (implicit) + + // Step 3. Let `width` be null. + // (implicit) + + // Step 4. Let `height` be null. + // (implicit) + + // Step 5. If `tokenizedFeatures["left"]` exists: + if (aFeatures.Exists("left")) { + // Step 5.1. Set `x` to the result of invoking the rules for parsing + // integers on `tokenizedFeatures["left"]`. + // + // Step 5.2. If `x` is an error, set `x` to 0. + int32_t x = aFeatures.GetInt("left"); + + // Step 5.3. Optionally, clamp `x` in a user-agent-defined manner so that + // the window does not move outside the Web-exposed available screen area. + // (done later) + + // Step 5.4. Optionally, move `target`’s window such that the window’s + // left edge is at the horizontal coordinate `x` relative to the left edge + // of the Web-exposed screen area, measured in CSS pixels of target. + // The positive axis is rightward. + aResult.mLeft = x; + aResult.mLeftSpecified = true; + } + + // Step 6. If `tokenizedFeatures["top"]` exists: + if (aFeatures.Exists("top")) { + // Step 6.1. Set `y` to the result of invoking the rules for parsing + // integers on `tokenizedFeatures["top"]`. + // + // Step 6.2. If `y` is an error, set `y` to 0. + int32_t y = aFeatures.GetInt("top"); + + // Step 6.3. Optionally, clamp `y` in a user-agent-defined manner so that + // the window does not move outside the Web-exposed available screen area. + // (done later) + + // Step 6.4. Optionally, move `target`’s window such that the window’s top + // edge is at the vertical coordinate `y` relative to the top edge of the + // Web-exposed screen area, measured in CSS pixels of target. The positive + // axis is downward. + aResult.mTop = y; + aResult.mTopSpecified = true; + } + + // Non-standard extension. + // Not exposed to web content. + if (aHasChromeParent && aFeatures.Exists("outerwidth")) { + int32_t width = aFeatures.GetInt("outerwidth"); + if (width) { + aResult.mOuterWidth = width; + aResult.mOuterWidthSpecified = true; + } + } + + if (!aResult.mOuterWidthSpecified) { + // Step 7. If `tokenizedFeatures["width"]` exists: + if (aFeatures.Exists("width")) { + // Step 7.1. Set `width` to the result of invoking the rules for parsing + // integers on `tokenizedFeatures["width"]`. + // + // Step 7.2. If `width` is an error, set `width` to 0. + int32_t width = aFeatures.GetInt("width"); + + // Step 7.3. If `width` is not 0: + if (width) { + // Step 7.3.1. Optionally, clamp `width` in a user-agent-defined manner + // so that the window does not get too small or bigger than the + // Web-exposed available screen area. + // (done later) + + // Step 7.3.2. Optionally, size `target`’s window by moving its right + // edge such that the distance between the left and right edges of the + // viewport are `width` CSS pixels of target. + aResult.mInnerWidth = width; + aResult.mInnerWidthSpecified = true; + + // Step 7.3.3. Optionally, move target’s window in a user-agent-defined + // manner so that it does not grow outside the Web-exposed available + // screen area. + // (done later) + } + } + } + + // Non-standard extension. + // Not exposed to web content. + if (aHasChromeParent && aFeatures.Exists("outerheight")) { + int32_t height = aFeatures.GetInt("outerheight"); + if (height) { + aResult.mOuterHeight = height; + aResult.mOuterHeightSpecified = true; + } + } + + if (!aResult.mOuterHeightSpecified) { + // Step 8. If `tokenizedFeatures["height"]` exists: + if (aFeatures.Exists("height")) { + // Step 8.1. Set `height` to the result of invoking the rules for parsing + // integers on `tokenizedFeatures["height"]`. + // + // Step 8.2. If `height` is an error, set `height` to 0. + int32_t height = aFeatures.GetInt("height"); + + // Step 8.3. If `height` is not 0: + if (height) { + // Step 8.3.1. Optionally, clamp `height` in a user-agent-defined manner + // so that the window does not get too small or bigger than the + // Web-exposed available screen area. + // (done later) + + // Step 8.3.2. Optionally, size `target`’s window by moving its bottom + // edge such that the distance between the top and bottom edges of the + // viewport are `height` CSS pixels of target. + aResult.mInnerHeight = height; + aResult.mInnerHeightSpecified = true; + + // Step 8.3.3. Optionally, move target’s window in a user-agent-defined + // manner so that it does not grow outside the Web-exposed available + // screen area. + // (done later) + } + } + } + + // NOTE: The value is handled only on chrome-priv code. + // See nsWindowWatcher::SizeOpenedWindow. + aResult.mLockAspectRatio = + aFeatures.GetBoolWithDefault("lockaspectratio", false); +} + +/* Size and position a new window according to aSizeSpec. This method + is assumed to be called after the window has already been given + a default position and size; thus its current position and size are + accurate defaults. The new window is made visible at method end. + @param aTreeOwner + The top-level nsIDocShellTreeOwner of the newly opened window. + @param aParent (optional) + The parent window from which to inherit zoom factors from if + aOpenerFullZoom is none. + @param aIsCallerChrome + True if the code requesting the new window is privileged. + @param aSizeSpec + The size that the new window should be. + @param aOpenerFullZoom + If not nothing, a zoom factor to scale the content to. +*/ +void nsWindowWatcher::SizeOpenedWindow(nsIDocShellTreeOwner* aTreeOwner, + mozIDOMWindowProxy* aParent, + bool aIsCallerChrome, + const SizeSpec& aSizeSpec, + const Maybe<float>& aOpenerFullZoom) { + // We should only be sizing top-level windows if we're in the parent + // process. + MOZ_ASSERT(XRE_IsParentProcess()); + + // position and size of window + int32_t left = 0, top = 0, width = 100, height = 100; + // difference between chrome and content size + int32_t chromeWidth = 0, chromeHeight = 0; + // whether the window size spec refers to chrome or content + bool sizeChromeWidth = true, sizeChromeHeight = true; + + // get various interfaces for aDocShellItem, used throughout this method + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin(do_QueryInterface(aTreeOwner)); + if (!treeOwnerAsWin) { // we'll need this to actually size the docshell + return; + } + + double openerZoom = aOpenerFullZoom.valueOr(1.0); + if (aParent && aOpenerFullZoom.isNothing()) { + nsCOMPtr<nsPIDOMWindowOuter> piWindow = nsPIDOMWindowOuter::From(aParent); + if (Document* doc = piWindow->GetDoc()) { + if (nsPresContext* presContext = doc->GetPresContext()) { + openerZoom = presContext->GetFullZoom(); + } + } + } + + double scale = 1.0; + treeOwnerAsWin->GetUnscaledDevicePixelsPerCSSPixel(&scale); + + /* The current position and size will be unchanged if not specified + (and they fit entirely onscreen). Also, calculate the difference + between chrome and content sizes on aDocShellItem's window. + This latter point becomes important if chrome and content + specifications are mixed in aFeatures, and when bringing the window + back from too far off the right or bottom edges of the screen. */ + + treeOwnerAsWin->GetPositionAndSize(&left, &top, &width, &height); + left = NSToIntRound(left / scale); + top = NSToIntRound(top / scale); + width = NSToIntRound(width / scale); + height = NSToIntRound(height / scale); + { + int32_t contentWidth, contentHeight; + bool hasPrimaryContent = false; + aTreeOwner->GetHasPrimaryContent(&hasPrimaryContent); + if (hasPrimaryContent) { + aTreeOwner->GetPrimaryContentSize(&contentWidth, &contentHeight); + } else { + aTreeOwner->GetRootShellSize(&contentWidth, &contentHeight); + } + chromeWidth = width - contentWidth; + chromeHeight = height - contentHeight; + } + + // Set up left/top + if (aSizeSpec.mLeftSpecified) { + left = NSToIntRound(aSizeSpec.mLeft * openerZoom); + } + + if (aSizeSpec.mTopSpecified) { + top = NSToIntRound(aSizeSpec.mTop * openerZoom); + } + + // Set up width + if (aSizeSpec.mOuterWidthSpecified) { + width = NSToIntRound(aSizeSpec.mOuterWidth * openerZoom); + } else if (aSizeSpec.mInnerWidthSpecified) { + sizeChromeWidth = false; + width = NSToIntRound(aSizeSpec.mInnerWidth * openerZoom); + } + + // Set up height + if (aSizeSpec.mOuterHeightSpecified) { + height = NSToIntRound(aSizeSpec.mOuterHeight * openerZoom); + } else if (aSizeSpec.mInnerHeightSpecified) { + sizeChromeHeight = false; + height = NSToIntRound(aSizeSpec.mInnerHeight * openerZoom); + } + + bool positionSpecified = aSizeSpec.PositionSpecified(); + + // Check security state for use in determing window dimensions + bool enabled = false; + if (aIsCallerChrome) { + // Only enable special priveleges for chrome when chrome calls + // open() on a chrome window + nsCOMPtr<nsIDOMChromeWindow> chromeWin(do_QueryInterface(aParent)); + enabled = !aParent || chromeWin; + } + + if (!enabled) { + // Security check failed. Ensure all args meet minimum reqs. + + int32_t oldTop = top, oldLeft = left; + + // We'll also need the screen dimensions + nsCOMPtr<nsIScreen> screen; + nsCOMPtr<nsIScreenManager> screenMgr( + do_GetService("@mozilla.org/gfx/screenmanager;1")); + if (screenMgr) + screenMgr->ScreenForRect(left, top, width, height, + getter_AddRefs(screen)); + if (screen) { + int32_t screenLeft, screenTop, screenWidth, screenHeight; + int32_t winWidth = width + (sizeChromeWidth ? 0 : chromeWidth), + winHeight = height + (sizeChromeHeight ? 0 : chromeHeight); + + // Get screen dimensions (in device pixels) + screen->GetAvailRect(&screenLeft, &screenTop, &screenWidth, + &screenHeight); + // Convert them to CSS pixels + screenLeft = NSToIntRound(screenLeft / scale); + screenTop = NSToIntRound(screenTop / scale); + screenWidth = NSToIntRound(screenWidth / scale); + screenHeight = NSToIntRound(screenHeight / scale); + + if (aSizeSpec.SizeSpecified()) { + if (!nsContentUtils::ShouldResistFingerprinting()) { + /* Unlike position, force size out-of-bounds check only if + size actually was specified. Otherwise, intrinsically sized + windows are broken. */ + if (height < 100) { + height = 100; + winHeight = height + (sizeChromeHeight ? 0 : chromeHeight); + } + if (winHeight > screenHeight) { + height = screenHeight - (sizeChromeHeight ? 0 : chromeHeight); + } + if (width < 100) { + width = 100; + winWidth = width + (sizeChromeWidth ? 0 : chromeWidth); + } + if (winWidth > screenWidth) { + width = screenWidth - (sizeChromeWidth ? 0 : chromeWidth); + } + } else { + int32_t targetContentWidth = 0; + int32_t targetContentHeight = 0; + + nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting( + chromeWidth, chromeHeight, screenWidth, screenHeight, width, + height, sizeChromeWidth, sizeChromeHeight, &targetContentWidth, + &targetContentHeight); + + if (aSizeSpec.mInnerWidthSpecified || + aSizeSpec.mOuterWidthSpecified) { + width = targetContentWidth; + winWidth = width + (sizeChromeWidth ? 0 : chromeWidth); + } + + if (aSizeSpec.mInnerHeightSpecified || + aSizeSpec.mOuterHeightSpecified) { + height = targetContentHeight; + winHeight = height + (sizeChromeHeight ? 0 : chromeHeight); + } + } + } + + CheckedInt<decltype(left)> leftPlusWinWidth = left; + leftPlusWinWidth += winWidth; + if (!leftPlusWinWidth.isValid() || + leftPlusWinWidth.value() > screenLeft + screenWidth) { + left = screenLeft + screenWidth - winWidth; + } + if (left < screenLeft) { + left = screenLeft; + } + + CheckedInt<decltype(top)> topPlusWinHeight = top; + topPlusWinHeight += winHeight; + if (!topPlusWinHeight.isValid() || + topPlusWinHeight.value() > screenTop + screenHeight) { + top = screenTop + screenHeight - winHeight; + } + if (top < screenTop) { + top = screenTop; + } + + if (top != oldTop || left != oldLeft) { + positionSpecified = true; + } + } + } + + // size and position the window + + if (positionSpecified) { + // Get the scale factor appropriate for the screen we're actually + // positioning on. + nsCOMPtr<nsIScreen> screen; + nsCOMPtr<nsIScreenManager> screenMgr( + do_GetService("@mozilla.org/gfx/screenmanager;1")); + if (screenMgr) { + screenMgr->ScreenForRect(left, top, 1, 1, getter_AddRefs(screen)); + } + if (screen) { + double cssToDevPixScale, desktopToDevPixScale; + screen->GetDefaultCSSScaleFactor(&cssToDevPixScale); + screen->GetContentsScaleFactor(&desktopToDevPixScale); + double cssToDesktopScale = cssToDevPixScale / desktopToDevPixScale; + int32_t screenLeft, screenTop, screenWd, screenHt; + screen->GetRectDisplayPix(&screenLeft, &screenTop, &screenWd, &screenHt); + // Adjust by desktop-pixel origin of the target screen when scaling + // to convert from per-screen CSS-px coords to global desktop coords. + treeOwnerAsWin->SetPositionDesktopPix( + (left - screenLeft) * cssToDesktopScale + screenLeft, + (top - screenTop) * cssToDesktopScale + screenTop); + } else { + // Couldn't find screen? This shouldn't happen. + treeOwnerAsWin->SetPosition(left * scale, top * scale); + } + // This shouldn't be necessary, given the screen check above, but in case + // moving the window didn't put it where we expected (e.g. due to issues + // at the widget level, or whatever), let's re-fetch the scale factor for + // wherever it really ended up + treeOwnerAsWin->GetUnscaledDevicePixelsPerCSSPixel(&scale); + } + if (aSizeSpec.SizeSpecified()) { + /* Prefer to trust the interfaces, which think in terms of pure + chrome or content sizes. If we have a mix, use the chrome size + adjusted by the chrome/content differences calculated earlier. */ + if (!sizeChromeWidth && !sizeChromeHeight) { + bool hasPrimaryContent = false; + aTreeOwner->GetHasPrimaryContent(&hasPrimaryContent); + if (hasPrimaryContent) { + aTreeOwner->SetPrimaryContentSize(width * scale, height * scale); + } else { + aTreeOwner->SetRootShellSize(width * scale, height * scale); + } + } else { + if (!sizeChromeWidth) { + width += chromeWidth; + } + if (!sizeChromeHeight) { + height += chromeHeight; + } + treeOwnerAsWin->SetSize(width * scale, height * scale, false); + } + } + + if (aIsCallerChrome) { + nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(treeOwnerAsWin); + if (appWin && aSizeSpec.mLockAspectRatio) { + appWin->LockAspectRatio(true); + } + } + + treeOwnerAsWin->SetVisibility(true); +} + +/* static */ +int32_t nsWindowWatcher::GetWindowOpenLocation(nsPIDOMWindowOuter* aParent, + uint32_t aChromeFlags, + bool aCalledFromJS, + bool aWidthSpecified, + bool aIsForPrinting) { + // These windows are not actually visible to the user, so we return the thing + // that we can always handle. + if (aIsForPrinting) { + return nsIBrowserDOMWindow::OPEN_PRINT_BROWSER; + } + + // Where should we open this? + int32_t containerPref; + if (NS_FAILED( + Preferences::GetInt("browser.link.open_newwindow", &containerPref))) { + // We couldn't read the user preference, so fall back on the default. + return nsIBrowserDOMWindow::OPEN_NEWTAB; + } + + bool isDisabledOpenNewWindow = + aParent->GetFullScreen() && + Preferences::GetBool( + "browser.link.open_newwindow.disabled_in_fullscreen"); + + if (isDisabledOpenNewWindow && + (containerPref == nsIBrowserDOMWindow::OPEN_NEWWINDOW)) { + containerPref = nsIBrowserDOMWindow::OPEN_NEWTAB; + } + + if (containerPref != nsIBrowserDOMWindow::OPEN_NEWTAB && + containerPref != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW) { + // Just open a window normally + return nsIBrowserDOMWindow::OPEN_NEWWINDOW; + } + + if (aCalledFromJS) { + /* Now check our restriction pref. The restriction pref is a power-user's + fine-tuning pref. values: + 0: no restrictions - divert everything + 1: don't divert window.open at all + 2: don't divert window.open with features + */ + int32_t restrictionPref = + Preferences::GetInt("browser.link.open_newwindow.restriction", 2); + if (restrictionPref < 0 || restrictionPref > 2) { + restrictionPref = 2; // Sane default behavior + } + + if (isDisabledOpenNewWindow) { + // In browser fullscreen, the window should be opened + // in the current window with no features (see bug 803675) + restrictionPref = 0; + } + + if (restrictionPref == 1) { + return nsIBrowserDOMWindow::OPEN_NEWWINDOW; + } + + if (restrictionPref == 2) { + // Only continue if there are no width feature and no special + // chrome flags - with the exception of the remoteness and private flags, + // which might have been automatically flipped by Gecko. + int32_t uiChromeFlags = aChromeFlags; + uiChromeFlags &= ~(nsIWebBrowserChrome::CHROME_REMOTE_WINDOW | + nsIWebBrowserChrome::CHROME_FISSION_WINDOW | + nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW | + nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW | + nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME); + if (uiChromeFlags != nsIWebBrowserChrome::CHROME_ALL || aWidthSpecified) { + return nsIBrowserDOMWindow::OPEN_NEWWINDOW; + } + } + } + + return containerPref; +} |