/* -*- 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 "nsDebug.h"
#include "nsNetUtil.h"
#include "nsIAuthPrompt.h"
#include "nsIAuthPrompt2.h"
#include "nsISimpleEnumerator.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsJSUtils.h"
#include "nsDocShell.h"
#include "nsGlobalWindowInner.h"
#include "nsGlobalWindowOuter.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 "mozilla/dom/UserActivation.h"
#include "nsIDragService.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_browser.h"
#include "mozilla/StaticPrefs_middlemouse.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/WindowGlobalChild.h"
#include "mozilla/dom/SessionStorageManager.h"
#include "nsIAppWindow.h"
#include "nsIXULBrowserWindow.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 nullptr;
}
info = mCurrentPosition->mYounger;
return info == mWindowWatcher->mOldestWindow ? nullptr : 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 : nullptr;
}
}
/****************************************************************
*********************** nsWindowWatcher ************************
****************************************************************/
NS_IMPL_ADDREF(nsWindowWatcher)
NS_IMPL_RELEASE(nsWindowWatcher)
NS_IMPL_QUERY_INTERFACE(nsWindowWatcher, nsIWindowWatcher, nsIPromptFactory,
nsPIWindowWatcher)
nsWindowWatcher::nsWindowWatcher()
: mOldestWindow(nullptr), 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,
mozilla::dom::UserActivation::Modifiers::None(),
/* 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() =
default;
// We store these in screen-independent pixels.
Maybe<DesktopIntCoord> mLeft;
Maybe<DesktopIntCoord> mTop;
Maybe<CSSIntCoord> mOuterWidth;
// Total window width
Maybe<CSSIntCoord> mOuterHeight;
// Total window height
Maybe<CSSIntCoord> mInnerWidth;
// Content area width
Maybe<CSSIntCoord> mInnerHeight;
// Content area height
bool mLockAspectRatio =
false;
bool PositionSpecified()
const {
return mLeft.isSome() || mTop.isSome(); }
bool SizeSpecified()
const {
return WidthSpecified() || HeightSpecified(); }
bool WidthSpecified()
const {
return mOuterWidth.isSome() || mInnerWidth.isSome();
}
bool HeightSpecified()
const {
return mOuterHeight.isSome() || mInnerHeight.isSome();
}
void ScaleBy(
float aOpenerZoom) {
if (aOpenerZoom == 1.0f) {
return;
}
auto Scale = [&aOpenerZoom](
auto& aValue) {
if (aValue) {
*aValue = NSToIntRound(*aValue * aOpenerZoom);
}
};
// Scaling the position is needed to make sure that the window position is
// what the caller expects.
Scale(mLeft);
Scale(mTop);
// Scaling these CSS sizes by the zoom factor might be a bit dubious, as the
// created window should not be zoomed, but we've done that historically...
Scale(mOuterWidth);
Scale(mOuterHeight);
Scale(mInnerWidth);
Scale(mInnerHeight);
}
};
static void SizeOpenedWindow(nsIDocShellTreeOwner* aTreeOwner,
mozIDOMWindowProxy* aParent,
bool aIsCallerChrome,
const SizeSpec&);
static SizeSpec CalcSizeSpec(
const WindowFeatures&,
bool aHasChromeParent,
CSSToDesktopScale);
NS_IMETHODIMP
nsWindowWatcher::OpenWindow2(
mozIDOMWindowProxy* aParent, nsIURI* aUri,
const nsACString& aName,
const nsACString& aFeatures,
const UserActivation::Modifiers& aModifiers,
bool aCalledFromScript,
bool aDialog,
bool aNavigate, nsIArray* 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, aUri, aName, aFeatures, aModifiers,
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;
if (aChromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_DIALOG) {
// If there are any drag and drop operations in flight, try to end them.
nsCOMPtr<nsIDragService> ds =
do_GetService(
"@mozilla.org/widget/dragservice;1");
if (ds) {
nsCOMPtr<nsIDragSession> session;
ds->GetCurrentSession(nullptr, getter_AddRefs(session));
if (session) {
session->EndDragSession(
true, 0);
}
}
}
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.
*/
static void MaybeDisablePersistence(
const SizeSpec& aSizeSpec,
nsIDocShellTreeOwner* aTreeOwner) {
if (!aTreeOwner) {
return;
}
if (aSizeSpec.SizeSpecified()) {
aTreeOwner->SetPersistence(
false,
false,
false);
}
}
NS_IMETHODIMP
nsWindowWatcher::OpenWindowWithRemoteTab(
nsIRemoteTab* aRemoteTab,
const WindowFeatures& aFeatures,
const UserActivation::Modifiers& aModifiers,
bool aCalledFromJS,
float aOpenerFullZoom, nsIOpenWindowInfo* aOpenWindowInfo,
nsIRemoteTab** aResult) {
#ifdef MOZ_GECKOVIEW
MOZ_ASSERT(
false,
"GeckoView should use nsIBrowserDOMWindow instead");
return NS_ERROR_NOT_IMPLEMENTED;
#else
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 =
StaticPrefs::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()->GetAsInnerWindow()) {
parentWindowOuter = browserElement->GetOwnerGlobal()
->GetAsInnerWindow()
->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;
}
// get various interfaces for aDocShellItem, used throughout this method
CSSToDesktopScale cssToDesktopScale(1.0f);
if (nsCOMPtr<nsIBaseWindow> win = do_QueryInterface(parentTreeOwner)) {
cssToDesktopScale = win->GetUnscaledCSSToDesktopScale();
}
SizeSpec sizeSpec = CalcSizeSpec(aFeatures,
false, cssToDesktopScale);
sizeSpec.ScaleBy(aOpenerFullZoom);
// This is not initiated by window.open call in content context, and we
// don't need to propagate isPopupRequested out-parameter to the resulting
// browsing context.
bool unused =
false;
uint32_t chromeFlags =
CalculateChromeFlagsForContent(aFeatures, aModifiers, &unused);
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);
nsCOMPtr<nsIRemoteTab> newBrowserParent;
chromeTreeOwner->GetPrimaryRemoteTab(getter_AddRefs(newBrowserParent));
if (NS_WARN_IF(!newBrowserParent)) {
return NS_ERROR_UNEXPECTED;
}
newBrowserParent.forget(aResult);
return NS_OK;
#endif
}
nsresult nsWindowWatcher::OpenWindowInternal(
mozIDOMWindowProxy* aParent,
const nsACString& aUrl,
const nsACString& aName,
const nsACString& aFeatures,
const mozilla::dom::UserActivation::Modifiers& aModifiers,
bool aCalledFromJS,
bool aDialog,
bool aNavigate, nsIArray* aArgv,
bool aIsPopupSpam,
bool aForceNoOpener,
bool aForceNoReferrer,
PrintKind aPrintKind, nsDocShellLoadState* aLoadState,
BrowsingContext** aResult) {
NS_ENSURE_ARG_POINTER(aResult);
*aResult = nullptr;
nsCOMPtr<nsIURI> uriToLoad;
if (!aUrl.IsVoid()) {
nsresult rv = URIfromURL(aUrl, aParent, getter_AddRefs(uriToLoad));
if (NS_FAILED(rv)) {
return rv;
}
}
RefPtr<nsDocShellLoadState> loadState = aLoadState;
if (!loadState && uriToLoad && aNavigate) {
loadState = CreateLoadState(
uriToLoad, aParent ? nsPIDOMWindowOuter::From(aParent) : nullptr);
}
return nsWindowWatcher::OpenWindowInternal(
aParent, uriToLoad, aName, aFeatures, aModifiers, aCalledFromJS, aDialog,
aNavigate, aArgv, aIsPopupSpam, aForceNoOpener, aForceNoReferrer,
aPrintKind, loadState, aResult);
}
nsresult nsWindowWatcher::OpenWindowInternal(
mozIDOMWindowProxy* aParent, nsIURI* aUri,
const nsACString& aName,
const nsACString& aFeatures,
const mozilla::dom::UserActivation::Modifiers& aModifiers,
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);
// XXXedgar aNavigate is now used for a sanity check only. We could consider
// removing it at some point.
MOZ_DIAGNOSTIC_ASSERT((aUri && aNavigate) == !!aLoadState);
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<nsIDocShellTreeOwner>
parentTreeOwner;
// from the parent window, if any
RefPtr<BrowsingContext> targetBC;
// from the new window
nsCOMPtr<nsPIDOMWindowOuter> parentOuterWin =
aParent ? nsPIDOMWindowOuter::From(aParent) : nullptr;
NS_ENSURE_ARG_POINTER(aResult);
*aResult = nullptr;
if (!nsContentUtils::IsSafeToRunScript()) {
nsContentUtils::WarnScriptWasIgnored(nullptr);
return NS_ERROR_FAILURE;
}
if (parentOuterWin) {
parentTreeOwner = parentOuterWin->GetTreeOwner();
}
if (aUri) {
uriToLoadIsChrome = aUri->SchemeIs(
"chrome");
if (aLoadState) {
bool equal =
false;
aUri->Equals(aLoadState->URI(), &equal);
MOZ_DIAGNOSTIC_ASSERT(
equal,
"aLoadState should contain the same URI passed to this function.");
}
}
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(
parentOuterWin ? parentOuterWin->GetBrowsingContext() : nullptr);
nsCOMPtr<nsIDocShell> parentDocShell(parentBC ? parentBC->GetDocShell()
: nullptr);
RefPtr<Document> parentDoc(parentOuterWin ? parentOuterWin->GetDoc()
: nullptr);
nsCOMPtr<nsPIDOMWindowInner> parentInnerWin(
parentOuterWin ? parentOuterWin->GetCurrentInnerWindow() : 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;
}
// If no parent, consider it chrome when running in the parent process.
bool hasChromeParent = !XRE_IsContentProcess();
if (aParent) {
// Check if the parent document has chrome privileges.
hasChromeParent = parentDoc && nsContentUtils::IsChromeDoc(parentDoc);
}
bool isCallerChrome = nsContentUtils::LegacyIsCallerChromeOrNativeCode();
// try to find an extant browsing context with the given name
if (!name.IsEmpty() &&
(!aForceNoOpener || nsContentUtils::IsSpecialName(name))) {
if (parentInnerWin && parentInnerWin->GetWindowGlobalChild()) {
// If we have a parent window, perform the look-up relative to the parent
// inner window.
targetBC =
parentInnerWin->GetWindowGlobalChild()->FindBrowsingContextWithName(
name);
}
else if (hasChromeParent && isCallerChrome &&
!nsContentUtils::IsSpecialName(name)) {
// Otherwise, if this call is from chrome, perform the lookup relative
// to the system group.
nsCOMPtr<mozIDOMWindowProxy> chromeWindow;
MOZ_ALWAYS_SUCCEEDS(GetWindowByName(name, getter_AddRefs(chromeWindow)));
if (chromeWindow) {
targetBC = nsPIDOMWindowOuter::From(chromeWindow)->GetBrowsingContext();
}
}
}
// 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(targetBC)) {
return NS_ERROR_DOM_INVALID_ACCESS_ERR;
}
// If our target BrowsingContext is still pending initialization, ignore the
// navigation request targeting it.
if (targetBC && NS_WARN_IF(targetBC->GetPendingInitialization())) {
return NS_ERROR_ABORT;
}
// no extant window? make a new one.
CSSToDesktopScale cssToDesktopScale(1.0);
if (nsCOMPtr<nsIBaseWindow> win = do_QueryInterface(parentDocShell)) {
cssToDesktopScale = win->GetUnscaledCSSToDesktopScale();
}
SizeSpec sizeSpec =
CalcSizeSpec(features, hasChromeParent, cssToDesktopScale);
sizeSpec.ScaleBy(parentBC ? parentBC->FullZoom() : 1.0f);
bool isPopupRequested =
false;
// 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 (hasChromeParent && isCallerChrome && XRE_IsParentProcess()) {
chromeFlags =
CalculateChromeFlagsForSystem(features, aDialog, uriToLoadIsChrome);
}
else {
MOZ_DIAGNOSTIC_ASSERT(parentBC && parentBC->IsContent(),
"content caller must provide content parent");
chromeFlags =
CalculateChromeFlagsForContent(features, aModifiers, &isPopupRequested);
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;
}
}
// Now that the jsapiChromeGuard has been set, fetch the system principal
// potentially configured by it. We want to make sure to respect any principal
// changes imposed by that guard throughout this function.
//
// 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();
MOZ_ASSERT(subjectPrincipal);
nsCOMPtr<nsIPrincipal> newWindowPrincipal;
if (!targetBC) {
if (windowTypeIsChrome) {
// If we are creating a chrome window, we must be called with a system
// principal, and should inherit that for the new chrome window.
MOZ_RELEASE_ASSERT(subjectPrincipal->IsSystemPrincipal(),
"Only system principals can create chrome windows");
newWindowPrincipal = subjectPrincipal;
}
else if (nsContentUtils::IsSystemOrExpandedPrincipal(subjectPrincipal)) {
// Don't allow initial about:blank documents to inherit a system or
// expanded principal, instead replace it with a null principal. We can't
// inherit origin attributes from the system principal, so use the parent
// BC if it's available.
if (parentBC) {
newWindowPrincipal =
NullPrincipal::Create(parentBC->OriginAttributesRef());
}
else {
newWindowPrincipal = NullPrincipal::CreateWithoutOriginAttributes();
}
}
else if (aForceNoOpener) {
// If we're opening a new window with noopener, create a new opaque
// principal for the new window, rather than re-using the existing
// principal.
newWindowPrincipal =
NullPrincipal::CreateWithInheritedAttributes(subjectPrincipal);
}
else {
// Finally, if there's an opener relationship and it's not a special
// principal, we should inherit that principal for the new window.
newWindowPrincipal = subjectPrincipal;
}
}
// Information used when opening new content windows. This object will be
// passed through to the inner nsFrameLoader.
RefPtr<nsOpenWindowInfo> openWindowInfo;
if (!targetBC && !windowTypeIsChrome) {
openWindowInfo =
new nsOpenWindowInfo();
openWindowInfo->mForceNoOpener = aForceNoOpener;
openWindowInfo->mParent = parentBC;
openWindowInfo->mIsForPrinting = aPrintKind != PRINT_NONE;
openWindowInfo->mIsForWindowDotPrint = aPrintKind == PRINT_WINDOW_DOT_PRINT;
openWindowInfo->mIsTopLevelCreatedByWebContent =
!nsContentUtils::IsSystemOrExpandedPrincipal(subjectPrincipal);
// We're going to want the window to be immediately available, meaning we
// want it to match the current remoteness.
openWindowInfo->mIsRemote = XRE_IsContentProcess();
// Inherit our OriginAttributes from the computed new window principal.
MOZ_ASSERT(
newWindowPrincipal &&
!nsContentUtils::IsSystemOrExpandedPrincipal(newWindowPrincipal));
openWindowInfo->mOriginAttributes =
newWindowPrincipal->OriginAttributesRef();
MOZ_DIAGNOSTIC_ASSERT(
!parentBC || openWindowInfo->mOriginAttributes.EqualsIgnoringFPD(
parentBC->OriginAttributesRef()),
"subject principal origin attributes doesn't match opener");
}
uint32_t activeDocsSandboxFlags = 0;
nsCOMPtr<nsIContentSecurityPolicy> cspToInheritForAboutBlank;
Maybe<nsILoadInfo::CrossOriginEmbedderPolicy> coepToInheritForAboutBlank;
if (!targetBC) {
// 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 (parentDoc) {
// Save sandbox flags for copying to new browsing context (docShell).
activeDocsSandboxFlags = parentDoc->GetSandboxFlags();
if (!aForceNoOpener) {
cspToInheritForAboutBlank = parentDoc->GetCsp();
coepToInheritForAboutBlank = parentDoc->GetEmbedderPolicy();
}
// 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, aUri, name, featuresStr,
aModifiers, aForceNoOpener, aForceNoReferrer, isPopupRequested,
aLoadState, &windowIsNew, getter_AddRefs(targetBC));
if (NS_SUCCEEDED(rv) && targetBC) {
nsCOMPtr<nsIDocShell> newDocShell = targetBC->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)) {
targetBC = 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 (!targetBC) {
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 (parentOuterWin) {
nsCOMPtr<nsPIDOMWindowOuter> parentTopWindow =
parentOuterWin->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;
}
targetBC = newDocShellItem->GetBrowsingContext();
}
}
}
// better have a window to use by this point
if (!targetBC) {
return rv;
}
MOZ_DIAGNOSTIC_ASSERT(
!windowIsNew || !targetBC->IsContent() ||
nsContentUtils::IsSystemOrExpandedPrincipal(subjectPrincipal) ||
targetBC->GetTopLevelCreatedByWebContent(),
"New BC not marked as created by web content, but it was");
// 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(targetBC->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 && targetBC->IsContent()) {
if (parentBC->IsDiscarded()) {
// If the parent BC was discarded in a nested event loop before we got
// to this point, we can't set it as the opener. Ideally we would still
// set `HadOriginalOpener()` in that case, but that's somewhat
// nontrivial, and not worth the effort given the nature of the corner
// case (see comment in `nsFrameLoader::CreateBrowsingContext`.
MOZ_RELEASE_ASSERT(targetBC->GetOpenerId() == parentBC->Id() ||
targetBC->GetOpenerId() == 0);
}
else {
MOZ_RELEASE_ASSERT(targetBC->GetOpenerId() == parentBC->Id());
MOZ_RELEASE_ASSERT(targetBC->HadOriginalOpener());
}
}
else {
// Update the opener for an existing or chrome BC.
targetBC->SetOpener(parentBC);
}
}
RefPtr<nsDocShell> targetDocShell(nsDocShell::Cast(targetBC->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 || targetDocShell);
// New top-level windows are only opened in the parent process and are, by
// definition, always in-process.
MOZ_DIAGNOSTIC_ASSERT(!isNewToplevelWindow || targetDocShell);
// 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(targetBC->SetSandboxFlags(activeDocsSandboxFlags));
MOZ_ALWAYS_SUCCEEDS(
targetBC->SetInitialSandboxFlags(targetBC->GetSandboxFlags()));
}
RefPtr<nsGlobalWindowOuter> targetOuterWin(
nsGlobalWindowOuter::Cast(targetBC->GetDOMWindow()));
#ifdef DEBUG
if (targetOuterWin && windowIsNew) {
// 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;
targetDocShell->GetDocumentChannel(getter_AddRefs(chan));
MOZ_ASSERT(!chan,
"Why is there a document channel?");
if (RefPtr<Document> doc = targetOuterWin->GetExtantDoc()) {
MOZ_ASSERT(doc->IsInitialDocument(),
"New window's document should be an initial document");
}
}
#endif
MOZ_ASSERT(targetOuterWin || !windowIsNew,
"New windows are always created in-process");
*aResult = do_AddRef(targetBC).take();
if (isNewToplevelWindow) {
nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner;
targetDocShell->GetTreeOwner(getter_AddRefs(newTreeOwner));
MaybeDisablePersistence(sizeSpec, newTreeOwner);
SizeOpenedWindow(newTreeOwner, aParent, isCallerChrome, sizeSpec);
}
if (aDialog && aArgv) {
MOZ_ASSERT(targetOuterWin);
NS_ENSURE_TRUE(targetOuterWin, NS_ERROR_UNEXPECTED);
// Set the args on the new window.
MOZ_TRY(targetOuterWin->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(targetBC->SetName(name));
}
else {
MOZ_ALWAYS_SUCCEEDS(targetBC->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.
if (windowIsNew) {
MOZ_DIAGNOSTIC_ASSERT(
!targetBC->IsContent() ||
newWindowPrincipal->OriginAttributesRef().EqualsIgnoringFPD(
targetBC->OriginAttributesRef()));
bool autoPrivateBrowsing = StaticPrefs::browser_privatebrowsing_autostart();
if (!autoPrivateBrowsing &&
(chromeFlags & nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW)) {
if (targetBC->IsChrome()) {
targetBC->SetUsePrivateBrowsing(
false);
}
MOZ_DIAGNOSTIC_ASSERT(
!targetBC->UsePrivateBrowsing(),
"CHROME_NON_PRIVATE_WINDOW passed, but got private window");
}
else if (autoPrivateBrowsing ||
(chromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW)) {
if (targetBC->IsChrome()) {
targetBC->SetUsePrivateBrowsing(
true);
}
MOZ_DIAGNOSTIC_ASSERT(
targetBC->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(targetOuterWin == targetDocShell->GetWindow(),
"Different windows??");
// Initialize the principal of the initial about:blank document. For
// toplevel windows, this call may have already happened when the window was
// created, but SetInitialPrincipal is safe to call multiple times.
if (targetOuterWin) {
MOZ_ASSERT(windowIsNew);
MOZ_ASSERT(!targetOuterWin->GetSameProcessOpener() ||
targetOuterWin->GetSameProcessOpener() == aParent);
targetOuterWin->SetInitialPrincipal(newWindowPrincipal,
cspToInheritForAboutBlank,
coepToInheritForAboutBlank);
if (aIsPopupSpam) {
MOZ_ASSERT(!targetBC->GetIsPopupSpam(),
"Who marked it as popup spam already???");
// Make sure we don't mess up our counter even if the above assert
// fails.
if (!targetBC->GetIsPopupSpam()) {
MOZ_ALWAYS_SUCCEEDS(targetBC->SetIsPopupSpam(
true));
}
}
}
// Copy the current session storage for the current domain. Don't perform
// the copy if we're forcing noopener, however.
if (!aForceNoOpener && subjectPrincipal && parentDocShell &&
targetDocShell) {
const RefPtr<SessionStorageManager> parentStorageManager =
parentDocShell->GetBrowsingContext()->GetSessionStorageManager();
const RefPtr<SessionStorageManager> newStorageManager =
targetDocShell->GetBrowsingContext()->GetSessionStorageManager();
if (parentStorageManager && newStorageManager) {
RefPtr<Storage> storage;
parentStorageManager->GetStorage(
parentInnerWin, subjectPrincipal, subjectPrincipal,
targetBC->UsePrivateBrowsing(), getter_AddRefs(storage));
if (storage) {
newStorageManager->CloneStorage(storage);
}
}
}
}
// We rely on CalculateChromeFlags to decide whether remote (out-of-process)
// tabs should be used.
MOZ_DIAGNOSTIC_ASSERT(
targetBC->UseRemoteTabs() ==
!!(chromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW));
MOZ_DIAGNOSTIC_ASSERT(
targetBC->UseRemoteSubframes() ==
!!(chromeFlags & nsIWebBrowserChrome::CHROME_FISSION_WINDOW));
if (aLoadState) {
// TriggeringPrincipal and ReferrerInfo are set up here because we
// rely on the `jsapiChromeGuard` set above to get proper value.
// Ideally, aLoadState should contain that value when passed in.
if (!aLoadState->TriggeringPrincipal()) {
aLoadState->SetTriggeringPrincipal(subjectPrincipal);
#ifndef ANDROID
MOZ_ASSERT(subjectPrincipal,
"nsWindowWatcher: triggeringPrincipal required");
#endif
}
if (!aLoadState->GetReferrerInfo() && !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) {
doc = parentDoc;
}
if (doc) {
auto referrerInfo = MakeRefPtr<ReferrerInfo>(*doc);
aLoadState->SetReferrerInfo(referrerInfo);
}
}
if (cx) {
nsGlobalWindowInner* win = xpc::CurrentWindowOrNull(cx);
if (win) {
nsCOMPtr<nsIContentSecurityPolicy> csp = win->GetCsp();
aLoadState->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(targetOuterWin),
"toplevel-window-ready", nullptr);
}
}
// Before loading the URI we want to be 100% sure that we use the correct
// userContextId.
MOZ_ASSERT_IF(targetDocShell, CheckUserContextCompatibility(targetDocShell));
// 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 (aUri) {
// The url notified in the webNavigation.onCreatedNavigationTarget
// event.
props->SetPropertyAsACString(u
"url"_ns, aUri->GetSpecOrDefault());
}
props->SetPropertyAsInterface(u
"sourceTabDocShell"_ns, parentDocShell);
props->SetPropertyAsInterface(u
"createdTabDocShell"_ns,
ToSupports(targetDocShell));
obsSvc->NotifyObservers(
static_cast<nsIPropertyBag2*>(props),
"webNavigation-createdNavigationTarget-from-js",
nullptr);
}
}
if (aLoadState) {
uint32_t loadFlags = nsIWebNavigation::LOAD_FLAGS_NONE;
if (windowIsNew) {
loadFlags |= nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD;
// Per spec, the explicit navigation to about:blank after the initial
// about:blank document in a new window does not occur, so there is no
// opportunity for it to inherit the source document's principal. This
// doesn't perfectly model this, as a noopener creation of `about:blank`
// will replace the global due to a principal mismatch, but it should be
// unobservable (bug 1694993).
// This isn't set for chrome windows, as an about:blank chrome document
// needs to inherit its principal from its opener.
if (aForceNoOpener && !windowTypeIsChrome) {
loadFlags |= nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
}
}
aLoadState->SetLoadFlags(loadFlags);
aLoadState->SetFirstParty(
true);
// Should this pay attention to errors returned by LoadURI?
targetBC->LoadURI(aLoadState);
}
if (windowIsModal) {
NS_ENSURE_TRUE(targetDocShell, NS_ERROR_NOT_IMPLEMENTED);
nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner;
targetDocShell->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(parentOuterWin);
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->SetModal(
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);
auto rv = newChrome->ShowAsModal();
NS_ENSURE_SUCCESS(rv, rv);
}
}
// If a website opens a popup exit DOM fullscreen
if (StaticPrefs::full_screen_api_exit_on_windowOpen() && aCalledFromJS &&
!hasChromeParent && !isCallerChrome && parentOuterWin) {
Document::AsyncExitFullscreen(parentOuterWin->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", nullptr);
}
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 = nullptr;
while (info != listEnd) {
if (info->mWindow == aWindow) {
return info;
}
info = info->mYounger;
listEnd = mOldestWindow;
}
return nullptr;
}
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 ? nullptr : 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", nullptr);
}
delete aInfo;
return NS_OK;
}
NS_IMETHODIMP
nsWindowWatcher::GetChromeForWindow(mozIDOMWindowProxy* aWindow,
nsIWebBrowserChrome** aResult) {
if (!aWindow || !aResult) {
return NS_ERROR_INVALID_ARG;
}
*aResult = nullptr;
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** aResult) {
if (!aResult) {
return NS_ERROR_INVALID_ARG;
}
*aResult = nullptr;
// We won't be able to find any windows with a special or empty name.
if (aTargetName.IsEmpty() || nsContentUtils::IsSpecialName(aTargetName)) {
return NS_OK;
}
// Search each toplevel in the chrome BrowsingContextGroup for a window with
// the given name.
for (
const RefPtr<BrowsingContext>& toplevel :
BrowsingContextGroup::GetChromeGroup()->Toplevels()) {
BrowsingContext* context =
toplevel->FindWithNameInSubtree(aTargetName, nullptr);
if (context) {
*aResult = do_AddRef(context->GetDOMWindow()).take();
MOZ_ASSERT(*aResult);
return NS_OK;
}
}
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.
// XXXedgar should we use the characterSet of the document to build the
// absolute URI?
return NS_NewURI(aURI, aURL, nullptr, baseURI);
}
// static
bool nsWindowWatcher::ShouldOpenPopup(
const WindowFeatures& aFeatures) {
if (aFeatures.IsEmpty()) {
return false;
}
// NOTE: This is different than chrome-only "popup" feature that is handled
// in nsWindowWatcher::CalculateChromeFlagsForSystem.
if (aFeatures.Exists(
"popup")) {
return aFeatures.GetBool(
"popup");
}
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;
}
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 aIsPopupRequested an out parameter that indicates whether a popup
* is requested by aFeatures
* @return the chrome bitmask
*/
--> --------------------
--> maximum size reached
--> --------------------