summaryrefslogtreecommitdiffstats
path: root/toolkit/components/windowwatcher
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /toolkit/components/windowwatcher
parentInitial commit. (diff)
downloadfirefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz
firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/windowwatcher')
-rw-r--r--toolkit/components/windowwatcher/moz.build45
-rw-r--r--toolkit/components/windowwatcher/nsAutoWindowStateHelper.cpp67
-rw-r--r--toolkit/components/windowwatcher/nsAutoWindowStateHelper.h34
-rw-r--r--toolkit/components/windowwatcher/nsDialogParamBlock.cpp88
-rw-r--r--toolkit/components/windowwatcher/nsDialogParamBlock.h46
-rw-r--r--toolkit/components/windowwatcher/nsIDialogParamBlock.idl41
-rw-r--r--toolkit/components/windowwatcher/nsIOpenWindowInfo.idl77
-rw-r--r--toolkit/components/windowwatcher/nsIPromptCollection.idl49
-rw-r--r--toolkit/components/windowwatcher/nsIPromptFactory.idl21
-rw-r--r--toolkit/components/windowwatcher/nsIPromptService.idl615
-rw-r--r--toolkit/components/windowwatcher/nsIWindowWatcher.idl161
-rw-r--r--toolkit/components/windowwatcher/nsOpenWindowInfo.cpp89
-rw-r--r--toolkit/components/windowwatcher/nsOpenWindowInfo.h50
-rw-r--r--toolkit/components/windowwatcher/nsPIPromptService.idl30
-rw-r--r--toolkit/components/windowwatcher/nsPIWindowWatcher.idl134
-rw-r--r--toolkit/components/windowwatcher/nsPromptUtils.h137
-rw-r--r--toolkit/components/windowwatcher/nsWindowWatcher.cpp2524
-rw-r--r--toolkit/components/windowwatcher/nsWindowWatcher.h111
-rw-r--r--toolkit/components/windowwatcher/test/browser.ini26
-rw-r--r--toolkit/components/windowwatcher/test/browser_new_content_window_chromeflags.js282
-rw-r--r--toolkit/components/windowwatcher/test/browser_new_content_window_from_chrome_principal.js44
-rw-r--r--toolkit/components/windowwatcher/test/browser_new_remote_window_flags.js85
-rw-r--r--toolkit/components/windowwatcher/test/browser_new_sized_window.js67
-rw-r--r--toolkit/components/windowwatcher/test/browser_non_popup_from_popup.js53
-rw-r--r--toolkit/components/windowwatcher/test/browser_popup_condition_current.js13
-rw-r--r--toolkit/components/windowwatcher/test/browser_popup_condition_tab.js13
-rw-r--r--toolkit/components/windowwatcher/test/browser_popup_condition_window.js13
-rw-r--r--toolkit/components/windowwatcher/test/chrome.ini9
-rw-r--r--toolkit/components/windowwatcher/test/file_named_window.html5
-rw-r--r--toolkit/components/windowwatcher/test/file_storage_copied.html13
-rw-r--r--toolkit/components/windowwatcher/test/file_test_dialog.html14
-rw-r--r--toolkit/components/windowwatcher/test/head.js207
-rw-r--r--toolkit/components/windowwatcher/test/mochitest.ini14
-rw-r--r--toolkit/components/windowwatcher/test/moz.build17
-rw-r--r--toolkit/components/windowwatcher/test/test_alwaysOnTop_windows.html82
-rw-r--r--toolkit/components/windowwatcher/test/test_blank_named_window.html40
-rw-r--r--toolkit/components/windowwatcher/test/test_dialog_arguments.html35
-rw-r--r--toolkit/components/windowwatcher/test/test_modal_windows.html50
-rw-r--r--toolkit/components/windowwatcher/test/test_named_window.html87
-rw-r--r--toolkit/components/windowwatcher/test/test_storage_copied.html43
40 files changed, 5531 insertions, 0 deletions
diff --git a/toolkit/components/windowwatcher/moz.build b/toolkit/components/windowwatcher/moz.build
new file mode 100644
index 0000000000..73479b38c9
--- /dev/null
+++ b/toolkit/components/windowwatcher/moz.build
@@ -0,0 +1,45 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Window Management")
+
+TEST_DIRS += ["test"]
+
+XPIDL_SOURCES += [
+ "nsIDialogParamBlock.idl",
+ "nsIOpenWindowInfo.idl",
+ "nsIPromptCollection.idl",
+ "nsIPromptFactory.idl",
+ "nsIPromptService.idl",
+ "nsIWindowWatcher.idl",
+ "nsPIPromptService.idl",
+ "nsPIWindowWatcher.idl",
+]
+
+XPIDL_MODULE = "windowwatcher"
+
+EXPORTS += [
+ "nsOpenWindowInfo.h",
+ "nsPromptUtils.h",
+ "nsWindowWatcher.h",
+]
+
+UNIFIED_SOURCES += [
+ "nsAutoWindowStateHelper.cpp",
+ "nsDialogParamBlock.cpp",
+ "nsOpenWindowInfo.cpp",
+ "nsWindowWatcher.cpp",
+]
+
+FINAL_LIBRARY = "xul"
+# For nsJSUtils
+LOCAL_INCLUDES += [
+ "/docshell/base",
+ "/dom/base",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/toolkit/components/windowwatcher/nsAutoWindowStateHelper.cpp b/toolkit/components/windowwatcher/nsAutoWindowStateHelper.cpp
new file mode 100644
index 0000000000..c735f51e49
--- /dev/null
+++ b/toolkit/components/windowwatcher/nsAutoWindowStateHelper.cpp
@@ -0,0 +1,67 @@
+/* -*- 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 "nsAutoWindowStateHelper.h"
+
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/Document.h"
+#include "nsPIDOMWindow.h"
+#include "nsString.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/****************************************************************
+ ****************** nsAutoWindowStateHelper *********************
+ ****************************************************************/
+
+nsAutoWindowStateHelper::nsAutoWindowStateHelper(nsPIDOMWindowOuter* aWindow)
+ : mWindow(aWindow),
+ mDefaultEnabled(DispatchEventToChrome("DOMWillOpenModalDialog")) {
+ if (mWindow) {
+ mWindow->EnterModalState();
+ }
+}
+
+nsAutoWindowStateHelper::~nsAutoWindowStateHelper() {
+ if (mWindow) {
+ mWindow->LeaveModalState();
+ }
+
+ if (mDefaultEnabled) {
+ DispatchEventToChrome("DOMModalDialogClosed");
+ }
+}
+
+bool nsAutoWindowStateHelper::DispatchEventToChrome(const char* aEventName) {
+ // XXXbz should we skip dispatching the event if the inner changed?
+ // That is, should we store both the inner and the outer?
+ if (!mWindow) {
+ return true;
+ }
+
+ // The functions of nsContentUtils do not provide the required behavior,
+ // so the following is inlined.
+ Document* doc = mWindow->GetExtantDoc();
+ if (!doc) {
+ return true;
+ }
+
+ ErrorResult rv;
+ RefPtr<Event> event = doc->CreateEvent(u"Events"_ns, CallerType::System, rv);
+ if (rv.Failed()) {
+ rv.SuppressException();
+ return false;
+ }
+ event->InitEvent(NS_ConvertASCIItoUTF16(aEventName), true, true);
+ event->SetTrusted(true);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+
+ nsCOMPtr<EventTarget> target = do_QueryInterface(mWindow);
+ bool defaultActionEnabled =
+ target->DispatchEvent(*event, CallerType::System, IgnoreErrors());
+ return defaultActionEnabled;
+}
diff --git a/toolkit/components/windowwatcher/nsAutoWindowStateHelper.h b/toolkit/components/windowwatcher/nsAutoWindowStateHelper.h
new file mode 100644
index 0000000000..b18bc4e5e2
--- /dev/null
+++ b/toolkit/components/windowwatcher/nsAutoWindowStateHelper.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsAutoWindowStateHelper_h
+#define __nsAutoWindowStateHelper_h
+
+#include "nsCOMPtr.h"
+#include "nsPIDOMWindow.h"
+
+/**
+ * Helper class for dealing with notifications around opening modal
+ * windows.
+ */
+
+class nsPIDOMWindowOuter;
+
+class nsAutoWindowStateHelper {
+ public:
+ explicit nsAutoWindowStateHelper(nsPIDOMWindowOuter* aWindow);
+ ~nsAutoWindowStateHelper();
+
+ bool DefaultEnabled() { return mDefaultEnabled; }
+
+ protected:
+ bool DispatchEventToChrome(const char* aEventName);
+
+ nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+ bool mDefaultEnabled;
+};
+
+#endif
diff --git a/toolkit/components/windowwatcher/nsDialogParamBlock.cpp b/toolkit/components/windowwatcher/nsDialogParamBlock.cpp
new file mode 100644
index 0000000000..7aab00f8e1
--- /dev/null
+++ b/toolkit/components/windowwatcher/nsDialogParamBlock.cpp
@@ -0,0 +1,88 @@
+/* -*- 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 "nsDialogParamBlock.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+
+NS_IMPL_ISUPPORTS(nsDialogParamBlock, nsIDialogParamBlock)
+
+nsDialogParamBlock::nsDialogParamBlock() : mNumStrings(0), mString(nullptr) {
+ for (int32_t i = 0; i < kNumInts; i++) {
+ mInt[i] = 0;
+ }
+}
+
+nsDialogParamBlock::~nsDialogParamBlock() { delete[] mString; }
+
+NS_IMETHODIMP
+nsDialogParamBlock::SetNumberStrings(int32_t aNumStrings) {
+ if (mString) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ mString = new nsString[aNumStrings];
+ if (!mString) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ mNumStrings = aNumStrings;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDialogParamBlock::GetInt(int32_t aIndex, int32_t* aResult) {
+ nsresult rv = InBounds(aIndex, kNumInts);
+ if (rv == NS_OK) {
+ *aResult = mInt[aIndex];
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDialogParamBlock::SetInt(int32_t aIndex, int32_t aInt) {
+ nsresult rv = InBounds(aIndex, kNumInts);
+ if (rv == NS_OK) {
+ mInt[aIndex] = aInt;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDialogParamBlock::GetString(int32_t aIndex, char16_t** aResult) {
+ if (mNumStrings == 0) {
+ SetNumberStrings(kNumStrings);
+ }
+ nsresult rv = InBounds(aIndex, mNumStrings);
+ if (rv == NS_OK) {
+ *aResult = ToNewUnicode(mString[aIndex]);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDialogParamBlock::SetString(int32_t aIndex, const char16_t* aString) {
+ if (mNumStrings == 0) {
+ SetNumberStrings(kNumStrings);
+ }
+ nsresult rv = InBounds(aIndex, mNumStrings);
+ if (rv == NS_OK) {
+ mString[aIndex] = aString;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDialogParamBlock::GetObjects(nsIMutableArray** aObjects) {
+ NS_ENSURE_ARG_POINTER(aObjects);
+ NS_IF_ADDREF(*aObjects = mObjects);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDialogParamBlock::SetObjects(nsIMutableArray* aObjects) {
+ mObjects = aObjects;
+ return NS_OK;
+}
diff --git a/toolkit/components/windowwatcher/nsDialogParamBlock.h b/toolkit/components/windowwatcher/nsDialogParamBlock.h
new file mode 100644
index 0000000000..e411d76eab
--- /dev/null
+++ b/toolkit/components/windowwatcher/nsDialogParamBlock.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsDialogParamBlock_h
+#define __nsDialogParamBlock_h
+
+#include "nsIDialogParamBlock.h"
+#include "nsIMutableArray.h"
+#include "nsCOMPtr.h"
+#include "nsStringFwd.h"
+
+// {4E4AAE11-8901-46cc-8217-DAD7C5415873}
+#define NS_DIALOGPARAMBLOCK_CID \
+ { \
+ 0x4e4aae11, 0x8901, 0x46cc, { \
+ 0x82, 0x17, 0xda, 0xd7, 0xc5, 0x41, 0x58, 0x73 \
+ } \
+ }
+
+class nsDialogParamBlock : public nsIDialogParamBlock {
+ public:
+ nsDialogParamBlock();
+
+ NS_DECL_NSIDIALOGPARAMBLOCK
+ NS_DECL_ISUPPORTS
+
+ protected:
+ virtual ~nsDialogParamBlock();
+
+ private:
+ enum { kNumInts = 8, kNumStrings = 16 };
+
+ nsresult InBounds(int32_t aIndex, int32_t aMax) {
+ return aIndex >= 0 && aIndex < aMax ? NS_OK : NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ int32_t mInt[kNumInts];
+ int32_t mNumStrings;
+ nsString* mString;
+ nsCOMPtr<nsIMutableArray> mObjects;
+};
+
+#endif
diff --git a/toolkit/components/windowwatcher/nsIDialogParamBlock.idl b/toolkit/components/windowwatcher/nsIDialogParamBlock.idl
new file mode 100644
index 0000000000..2b2b3aafd4
--- /dev/null
+++ b/toolkit/components/windowwatcher/nsIDialogParamBlock.idl
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+interface nsIMutableArray;
+
+/**
+ * An interface to pass strings, integers and nsISupports to a dialog
+ */
+
+[scriptable, uuid(f76c0901-437a-11d3-b7a0-e35db351b4bc)]
+interface nsIDialogParamBlock: nsISupports {
+
+ /** Get or set an integer to pass.
+ * Index must be in the range 0..7
+ */
+ int32_t GetInt( in int32_t inIndex );
+ void SetInt( in int32_t inIndex, in int32_t inInt );
+
+ /** Set the maximum number of strings to pass. Default is 16.
+ * Use before setting any string (If you want to change it from the default).
+ */
+ void SetNumberStrings( in int32_t inNumStrings );
+
+ /** Get or set an string to pass.
+ * Index starts at 0
+ */
+ wstring GetString( in int32_t inIndex );
+ void SetString( in int32_t inIndex, in wstring inString);
+
+ /**
+ * A place where you can store an nsIMutableArray to pass nsISupports
+ */
+ attribute nsIMutableArray objects;
+};
+
+%{C++
+#define NS_DIALOGPARAMBLOCK_CONTRACTID "@mozilla.org/embedcomp/dialogparam;1"
+%}
diff --git a/toolkit/components/windowwatcher/nsIOpenWindowInfo.idl b/toolkit/components/windowwatcher/nsIOpenWindowInfo.idl
new file mode 100644
index 0000000000..a8347c5a1a
--- /dev/null
+++ b/toolkit/components/windowwatcher/nsIOpenWindowInfo.idl
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+webidl BrowsingContext;
+
+%{ C++
+namespace mozilla {
+class OriginAttributes;
+namespace dom {
+class BrowserParent;
+} // namespace dom
+} // namespace mozilla
+%}
+[ref] native const_OriginAttributes(const mozilla::OriginAttributes);
+[ptr] native BrowserParent(mozilla::dom::BrowserParent);
+
+/*
+ * nsIBrowsingContextReadyCallback.browsingContextReady() is called within
+ * nsFrameLoader to indicate that the browsing context for a newly opened
+ * window/tab is ready.
+ */
+[uuid(0524ee06-7f4c-4cd3-ab80-084562745cad)]
+interface nsIBrowsingContextReadyCallback : nsISupports
+{
+ void browsingContextReady(in BrowsingContext bc);
+};
+
+/**
+ * nsIOpenWindowInfo is a helper type which contains details used when opening
+ * new content windows. This object is used to correctly create new initial
+ * content documents when creating a new window.
+ */
+[scriptable, builtinclass, uuid(30359edb-126c-4f65-ae80-07fb158697f9)]
+interface nsIOpenWindowInfo : nsISupports {
+ /** BrowsingContext which requested the creation of this new window */
+ [infallible]
+ readonly attribute BrowsingContext parent;
+
+ /** If `true`, the content document should be created initially-remote */
+ [infallible]
+ readonly attribute boolean isRemote;
+
+ /** Should |opener| be set on the newly-created content window? */
+ [infallible]
+ readonly attribute boolean forceNoOpener;
+
+ /** Whether this is a window opened for printing */
+ [infallible]
+ readonly attribute boolean isForPrinting;
+
+ /**
+ * Whether this is a window opened for window.print().
+ * When this is true, isForPrinting is necessarily true as well.
+ */
+ [infallible]
+ readonly attribute boolean isForWindowDotPrint;
+
+ /** BrowserParent instance to use in the new window */
+ [notxpcom, nostdcall]
+ BrowserParent getNextRemoteBrowser();
+
+ /** Origin Attributes for the to-be-created toplevel BrowsingContext */
+ [implicit_jscontext, binaryname(ScriptableOriginAttributes)]
+ readonly attribute jsval originAttributes;
+
+ [notxpcom, nostdcall, binaryname(GetOriginAttributes)]
+ const_OriginAttributes binaryGetOriginAttributes();
+
+ /* Callback to invoke when the browsing context for a new window is ready. */
+ [notxpcom, nostdcall]
+ nsIBrowsingContextReadyCallback browsingContextReadyCallback();
+};
diff --git a/toolkit/components/windowwatcher/nsIPromptCollection.idl b/toolkit/components/windowwatcher/nsIPromptCollection.idl
new file mode 100644
index 0000000000..3e3e104407
--- /dev/null
+++ b/toolkit/components/windowwatcher/nsIPromptCollection.idl
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+webidl BrowsingContext;
+
+/**
+ * This interface contains various specialized prompts that the app can
+ * implement.
+ */
+[scriptable, uuid(7913837c-9623-11ea-bb37-0242ac130002)]
+interface nsIPromptCollection : nsISupports
+{
+ /**
+ * Puts up a dialog for the before unload prompt.
+ *
+ * @param aBrowsingContext
+ * The browsing context the prompt should be opened for.
+ *
+ * @return Promise which resolves to true if the page should be allowed to
+ * navigate away
+ */
+ Promise asyncBeforeUnloadCheck(in BrowsingContext aBrowsingContext);
+
+ /**
+ * Puts up a dialog for the confirm repost prompt.
+ *
+ * @param aBrowsingContext
+ * The browsing context the prompt should be opened for.
+ *
+ * @return true if the page should be allowed to repost data.
+ */
+ boolean confirmRepost(in BrowsingContext aBrowsingContext);
+
+ /**
+ * Ask the user for confirmation to upload a selected folder.
+ *
+ * @param aBrowsingContext
+ * The browsing context the prompt should be opened for.
+ * @param aDirectoryName
+ * Name of the folder that will be uploaded.
+ *
+ * @return true if the user confirmed the upload, false otherwise.
+ */
+ boolean confirmFolderUpload(in BrowsingContext aBrowsingContext,
+ in AString aDirectoryName);
+};
diff --git a/toolkit/components/windowwatcher/nsIPromptFactory.idl b/toolkit/components/windowwatcher/nsIPromptFactory.idl
new file mode 100644
index 0000000000..940276ce8a
--- /dev/null
+++ b/toolkit/components/windowwatcher/nsIPromptFactory.idl
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+
+/**
+ * This interface allows creating various prompts that have a specific parent.
+ */
+[scriptable, uuid(2803541c-c96a-4ff1-bd7c-9cb566d46aeb)]
+interface nsIPromptFactory : nsISupports
+{
+ /**
+ * Returns an object implementing the specified interface that creates
+ * prompts parented to aParent.
+ */
+ void getPrompt(in mozIDOMWindowProxy aParent, in nsIIDRef iid,
+ [iid_is(iid),retval] out nsQIResult result);
+};
diff --git a/toolkit/components/windowwatcher/nsIPromptService.idl b/toolkit/components/windowwatcher/nsIPromptService.idl
new file mode 100644
index 0000000000..9d910e0bce
--- /dev/null
+++ b/toolkit/components/windowwatcher/nsIPromptService.idl
@@ -0,0 +1,615 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIAuthPromptCallback;
+interface nsIAuthInformation;
+interface nsICancelable;
+interface nsIChannel;
+
+webidl BrowsingContext;
+
+/**
+ * This is the interface to the embeddable prompt service; the service that
+ * implements nsIPrompt. Its interface is designed to be just nsIPrompt, each
+ * method modified to take a parent window parameter.
+ *
+ * Accesskeys can be attached to buttons and checkboxes by inserting an &
+ * before the accesskey character in the checkbox message or button title. For
+ * a real &, use && instead. (A "button title" generally refers to the text
+ * label of a button.)
+ *
+ * One note: in all cases, the parent window parameter can be null. However,
+ * these windows are all intended to have parents. So when no parent is
+ * specified, the implementation should try hard to find a suitable foster
+ * parent.
+ *
+ * Implementations are free to choose how they present the various button
+ * types. For example, while prompts that give the user a choice between OK
+ * and Cancel are required to return a boolean value indicating whether or not
+ * the user accepted the prompt (pressed OK) or rejected the prompt (pressed
+ * Cancel), the implementation of this interface could very well speak the
+ * prompt to the user instead of rendering any visual user-interface. The
+ * standard button types are merely idioms used to convey the nature of the
+ * choice the user is to make.
+ *
+ * Because implementations of this interface may loosely interpret the various
+ * button types, it is advised that text messages passed to these prompts do
+ * not refer to the button types by name. For example, it is inadvisable to
+ * tell the user to "Press OK to proceed." Instead, such a prompt might be
+ * rewritten to ask the user: "Would you like to proceed?"
+ */
+[scriptable, uuid(404ebfa2-d8f4-4c94-8416-e65a55f9df5a)]
+interface nsIPromptService : nsISupports
+{
+ /**
+ * Puts up an alert dialog with an OK button.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ */
+ void alert(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText);
+ /**
+ * Like alert, but with a BrowsingContext as parent.
+ *
+ * @param aBrowsingContext
+ * The browsing context the prompt should be opened for.
+ * @param modalType
+ * Whether the prompt should be window, tab or content modal.
+ */
+ void alertBC(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText);
+ /**
+ * Async version of alertBC
+ *
+ * @return A promise which resolves when the prompt is dismissed.
+ */
+ Promise asyncAlert(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText);
+
+ /**
+ * Puts up an alert dialog with an OK button and a labeled checkbox.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ * @param aCheckMsg
+ * Text to appear with the checkbox.
+ * @param aCheckState
+ * Contains the initial checked state of the checkbox when this method
+ * is called and the final checked state after this method returns.
+ */
+ void alertCheck(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in wstring aCheckMsg,
+ inout boolean aCheckState);
+ /**
+ * Like alertCheck, but with a BrowsingContext as parent.
+ *
+ * @param aBrowsingContext
+ * The browsing context the prompt should be opened for.
+ * @param modalType
+ * Whether the prompt should be window, tab or content modal.
+ */
+ void alertCheckBC(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in wstring aCheckMsg,
+ inout boolean aCheckState);
+ /**
+ * Async version of alertCheckBC
+ *
+ * @return A promise which resolves when the prompt is dismissed.
+ *
+ * @resolves nsIPropertyBag { checked: boolean }
+ */
+ Promise asyncAlertCheck(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in wstring aCheckMsg,
+ in boolean aCheckState);
+
+ /**
+ * Puts up a dialog with OK and Cancel buttons.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ *
+ * @return true for OK, false for Cancel
+ */
+ boolean confirm(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText);
+ /**
+ * Like confirm, but with a BrowsingContext as parent.
+ *
+ * @param aBrowsingContext
+ * The browsing context the prompt should be opened for.
+ * @param modalType
+ * Whether the prompt should be window, tab or content modal.
+ */
+ boolean confirmBC(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText);
+ /**
+ * Async version of confirmBC
+ *
+ * @return A promise which resolves when the prompt is dismissed.
+ *
+ * @resolves nsIPropertyBag { ok: boolean }
+ */
+ Promise asyncConfirm(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText);
+
+ /**
+ * Puts up a dialog with OK and Cancel buttons and a labeled checkbox.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ * @param aCheckMsg
+ * Text to appear with the checkbox.
+ * @param aCheckState
+ * Contains the initial checked state of the checkbox when this method
+ * is called and the final checked state after this method returns.
+ *
+ * @return true for OK, false for Cancel
+ */
+ boolean confirmCheck(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in wstring aCheckMsg,
+ inout boolean aCheckState);
+ /**
+ * Like confirmCheck, but with a BrowsingContext as parent.
+ *
+ * @param aBrowsingContext
+ * The browsing context the prompt should be opened for.
+ * @param modalType
+ * Whether the prompt should be window, tab or content modal.
+ */
+ boolean confirmCheckBC(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in wstring aCheckMsg,
+ inout boolean aCheckState);
+ /**
+ * Async version of confirmCheckBC
+ *
+ * @return A promise which resolves when the prompt is dismissed.
+ *
+ * @resolves nsIPropertyBag { ok: boolean, checked: boolean }
+ */
+ Promise asyncConfirmCheck(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in wstring aCheckMsg,
+ in boolean aCheckState);
+
+ /**
+ * Button Flags
+ *
+ * The following flags are combined to form the aButtonFlags parameter passed
+ * to confirmEx. See confirmEx for more information on how the flags may be
+ * combined.
+ */
+
+ /**
+ * Button Position Flags
+ */
+ const unsigned long BUTTON_POS_0 = 1;
+ const unsigned long BUTTON_POS_1 = 1 << 8;
+ const unsigned long BUTTON_POS_2 = 1 << 16;
+
+ /**
+ * Button Title Flags (used to set the labels of buttons in the prompt)
+ */
+ const unsigned long BUTTON_TITLE_OK = 1;
+ const unsigned long BUTTON_TITLE_CANCEL = 2;
+ const unsigned long BUTTON_TITLE_YES = 3;
+ const unsigned long BUTTON_TITLE_NO = 4;
+ const unsigned long BUTTON_TITLE_SAVE = 5;
+ const unsigned long BUTTON_TITLE_DONT_SAVE = 6;
+ const unsigned long BUTTON_TITLE_REVERT = 7;
+ const unsigned long BUTTON_TITLE_IS_STRING = 127;
+
+ /**
+ * Button Default Flags (used to select which button is the default one)
+ */
+ const unsigned long BUTTON_POS_0_DEFAULT = 0;
+ const unsigned long BUTTON_POS_1_DEFAULT = 1 << 24;
+ const unsigned long BUTTON_POS_2_DEFAULT = 1 << 25;
+
+ /**
+ * Causes the buttons to be initially disabled. They are enabled after a
+ * timeout expires. The implementation may interpret this loosely as the
+ * intent is to ensure that the user does not click through a security dialog
+ * too quickly. Strictly speaking, the implementation could choose to ignore
+ * this flag.
+ */
+ const unsigned long BUTTON_DELAY_ENABLE = 1 << 26;
+
+ /**
+ * Selects the standard set of OK/Cancel buttons.
+ */
+ const unsigned long STD_OK_CANCEL_BUTTONS = (BUTTON_TITLE_OK * BUTTON_POS_0) +
+ (BUTTON_TITLE_CANCEL * BUTTON_POS_1);
+
+ /**
+ * Selects the standard set of Yes/No buttons.
+ */
+ const unsigned long STD_YES_NO_BUTTONS = (BUTTON_TITLE_YES * BUTTON_POS_0) +
+ (BUTTON_TITLE_NO * BUTTON_POS_1);
+
+ // Indicates whether a prompt should be shown in-content, on tab level or as a separate window
+ const unsigned long MODAL_TYPE_CONTENT = 1;
+ const unsigned long MODAL_TYPE_TAB = 2;
+ const unsigned long MODAL_TYPE_WINDOW = 3;
+ // Like MODAL_TYPE_WINDOW, but shown inside a parent window (with similar
+ // styles as _TAB and _CONTENT types) rather than as a new window:
+ const unsigned long MODAL_TYPE_INTERNAL_WINDOW = 4;
+
+ /**
+ * Puts up a dialog with up to 3 buttons and an optional, labeled checkbox.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ * @param aButtonFlags
+ * A combination of Button Flags.
+ * @param aButton0Title
+ * Used when button 0 uses TITLE_IS_STRING
+ * @param aButton1Title
+ * Used when button 1 uses TITLE_IS_STRING
+ * @param aButton2Title
+ * Used when button 2 uses TITLE_IS_STRING
+ * @param aCheckMsg
+ * Text to appear with the checkbox. Null if no checkbox.
+ * @param aCheckState
+ * Contains the initial checked state of the checkbox when this method
+ * is called and the final checked state after this method returns.
+ *
+ * @return index of the button pressed.
+ *
+ * Buttons are numbered 0 - 2. The implementation can decide whether the
+ * sequence goes from right to left or left to right. Button 0 is the
+ * default button unless one of the Button Default Flags is specified.
+ *
+ * A button may use a predefined title, specified by one of the Button Title
+ * Flags values. Each title value can be multiplied by a position value to
+ * assign the title to a particular button. If BUTTON_TITLE_IS_STRING is
+ * used for a button, the string parameter for that button will be used. If
+ * the value for a button position is zero, the button will not be shown.
+ *
+ * In general, aButtonFlags is constructed per the following example:
+ *
+ * aButtonFlags = (BUTTON_POS_0) * (BUTTON_TITLE_AAA) +
+ * (BUTTON_POS_1) * (BUTTON_TITLE_BBB) +
+ * BUTTON_POS_1_DEFAULT;
+ *
+ * where "AAA" and "BBB" correspond to one of the button titles.
+ */
+ int32_t confirmEx(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in unsigned long aButtonFlags,
+ in wstring aButton0Title,
+ in wstring aButton1Title,
+ in wstring aButton2Title,
+ in wstring aCheckMsg,
+ inout boolean aCheckState);
+ /**
+ * Like confirmEx, but with a BrowsingContext as parent.
+ *
+ * @param aBrowsingContext
+ * The browsing context the prompt should be opened for.
+ * @param modalType
+ * Whether the prompt should be window, tab or content modal.
+ */
+ int32_t confirmExBC(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in unsigned long aButtonFlags,
+ in wstring aButton0Title,
+ in wstring aButton1Title,
+ in wstring aButton2Title,
+ in wstring aCheckMsg,
+ inout boolean aCheckState);
+ /**
+ * Async version of confirmExBC
+ *
+ * @return A promise which resolves when the prompt is dismissed.
+ *
+ * @resolves nsIPropertyBag { checked: boolean, buttonNumClicked: int }
+ */
+ Promise asyncConfirmEx(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in unsigned long aButtonFlags,
+ in wstring aButton0Title,
+ in wstring aButton1Title,
+ in wstring aButton2Title,
+ in wstring aCheckMsg,
+ in boolean aCheckState,
+ [optional] in jsval aExtraArgs);
+ /**
+ * Puts up a dialog with an edit field and an optional, labeled checkbox.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ * @param aValue
+ * Contains the default value for the dialog field when this method
+ * is called (null value is ok). Upon return, if the user pressed
+ * OK, then this parameter contains a newly allocated string value.
+ * Otherwise, the parameter's value is unmodified.
+ * @param aCheckMsg
+ * Text to appear with the checkbox. If null, check box will not be shown.
+ * @param aCheckState
+ * Contains the initial checked state of the checkbox when this method
+ * is called and the final checked state after this method returns.
+ *
+ * @return true for OK, false for Cancel.
+ */
+ boolean prompt(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText,
+ inout wstring aValue,
+ in wstring aCheckMsg,
+ inout boolean aCheckState);
+ /**
+ * Like prompt, but with a BrowsingContext as parent.
+ *
+ * @param aBrowsingContext
+ * The browsing context the prompt should be opened for.
+ * @param modalType
+ * Whether the prompt should be window, tab or content modal.
+ */
+ boolean promptBC(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText,
+ inout wstring aValue,
+ in wstring aCheckMsg,
+ inout boolean aCheckState);
+ /**
+ * Async version of promptBC
+ *
+ * @return A promise which resolves when the prompt is dismissed.
+ *
+ * @resolves nsIPropertyBag { checked: boolean, value: string, ok: boolean }
+ */
+ Promise asyncPrompt(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in wstring aValue,
+ in wstring aCheckMsg,
+ in boolean aCheckState);
+
+ /**
+ * Puts up a dialog with an edit field and a password field.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ * @param aUsername
+ * Contains the default value for the username field when this method
+ * is called (null value is ok). Upon return, if the user pressed OK,
+ * then this parameter contains a newly allocated string value.
+ * Otherwise, the parameter's value is unmodified.
+ * @param aPassword
+ * Contains the default value for the password field when this method
+ * is called (null value is ok). Upon return, if the user pressed OK,
+ * then this parameter contains a newly allocated string value.
+ * Otherwise, the parameter's value is unmodified.
+ *
+ * @return true for OK, false for Cancel.
+ */
+ boolean promptUsernameAndPassword(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText,
+ inout wstring aUsername,
+ inout wstring aPassword);
+ /**
+ * Like promptUsernameAndPassword, but with a BrowsingContext as parent.
+ *
+ * @param aBrowsingContext
+ * The browsing context the prompt should be opened for.
+ * @param modalType
+ * Whether the prompt should be window, tab or content modal.
+ */
+ boolean promptUsernameAndPasswordBC(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText,
+ inout wstring aUsername,
+ inout wstring aPassword);
+ /**
+ * Async version of promptUsernameAndPasswordBC
+ *
+ * @return A promise which resolves when the prompt is dismissed.
+ *
+ * @resolves nsIPropertyBag { user: string, pass: string, ok: boolean }
+ */
+ Promise asyncPromptUsernameAndPassword(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in wstring aUsername,
+ in wstring aPassword);
+
+ /**
+ * Puts up a dialog with a password field.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ * @param aPassword
+ * Contains the default value for the password field when this method
+ * is called (null value is ok). Upon return, if the user pressed OK,
+ * then this parameter contains a newly allocated string value.
+ * Otherwise, the parameter's value is unmodified.
+ *
+ * @return true for OK, false for Cancel.
+ */
+ boolean promptPassword(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText,
+ inout wstring aPassword);
+ /**
+ * Like promptPassword, but with a BrowsingContext as parent.
+ *
+ * @param aBrowsingContext
+ * The browsing context the prompt should be opened for.
+ * @param modalType
+ * Whether the prompt should be window, tab or content modal.
+ */
+ boolean promptPasswordBC(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText,
+ inout wstring aPassword);
+ /**
+ * Async version of promptPasswordBC
+ *
+ * @return A promise which resolves when the prompt is dismissed.
+ *
+ * @resolves nsIPropertyBag { pass: string, ok: boolean }
+ */
+ Promise asyncPromptPassword(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in wstring aPassword);
+ /**
+ * Puts up a dialog box which has a list box of strings from which the user
+ * may make a single selection.
+ *
+ * @param aParent
+ * The parent window or null.
+ * @param aDialogTitle
+ * Text to appear in the title of the dialog.
+ * @param aText
+ * Text to appear in the body of the dialog.
+ * @param aSelectList
+ * The list of strings to display.
+ * @param aOutSelection
+ * Contains the index of the selected item in the list when this
+ * method returns true.
+ *
+ * @return true for OK, false for Cancel.
+ */
+ boolean select(in mozIDOMWindowProxy aParent,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in Array<AString> aSelectList,
+ out long aOutSelection);
+ /**
+ * Like select, but with a BrowsingContext as parent.
+ *
+ * @param aBrowsingContext
+ * The browsing context the prompt should be opened for.
+ * @param modalType
+ * Whether the prompt should be window, tab or content modal.
+ */
+ boolean selectBC(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in Array<AString> aSelectList,
+ out long aOutSelection);
+ /**
+ * Async version of selectBC
+ *
+ * @return A promise which resolves when the prompt is dismissed.
+ *
+ * @resolves nsIPropertyBag { selected: int, ok: boolean }
+ */
+ Promise asyncSelect(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in wstring aDialogTitle,
+ in wstring aText,
+ in Array<AString> aSelectList);
+
+ // NOTE: These functions differ from their nsIAuthPrompt counterparts by
+ // having additional checkbox parameters
+ //
+ // See nsIAuthPrompt2 for documentation on the semantics of the other
+ // parameters.
+ boolean promptAuth(in mozIDOMWindowProxy aParent,
+ in nsIChannel aChannel,
+ in uint32_t level,
+ in nsIAuthInformation authInfo);
+ /**
+ * Like promptAuth, but with a BrowsingContext as parent.
+ *
+ * @param aBrowsingContext
+ * The browsing context the prompt should be opened for.
+ * @param modalType
+ * Whether the prompt should be window, tab or content modal.
+ */
+ boolean promptAuthBC(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in nsIChannel aChannel,
+ in uint32_t level,
+ in nsIAuthInformation authInfo);
+ /**
+ * Async version of promptAuthBC
+ *
+ * @return A promise which resolves when the prompt is dismissed.
+ *
+ * @resolves nsIPropertyBag { ok: boolean }
+ */
+ Promise asyncPromptAuth(in BrowsingContext aBrowsingContext,
+ in unsigned long modalType,
+ in nsIChannel aChannel,
+ in uint32_t level,
+ in nsIAuthInformation authInfo);
+};
diff --git a/toolkit/components/windowwatcher/nsIWindowWatcher.idl b/toolkit/components/windowwatcher/nsIWindowWatcher.idl
new file mode 100644
index 0000000000..85a40970d0
--- /dev/null
+++ b/toolkit/components/windowwatcher/nsIWindowWatcher.idl
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIObserver;
+interface nsIPrompt;
+interface nsIAuthPrompt;
+interface nsISimpleEnumerator;
+interface nsIWebBrowserChrome;
+interface nsIWindowCreator;
+
+
+/**
+ * nsIWindowWatcher is the keeper of Gecko/DOM Windows. It maintains
+ * a list of open top-level windows, and allows some operations on them.
+
+ * Usage notes:
+
+ * This component has an |activeWindow| property. Clients may expect
+ * this property to be always current, so to properly integrate this component
+ * the application will need to keep it current by setting the property
+ * as the active window changes.
+ * This component should not keep a (XPCOM) reference to any windows;
+ * the implementation will claim no ownership. Windows must notify
+ * this component when they are created or destroyed, so only a weak
+ * reference is kept. Note that there is no interface for such notifications
+ * (not a public one, anyway). This is taken care of both in Mozilla and
+ * by common embedding code. Embedding clients need do nothing special
+ * about that requirement.
+ * This component must be initialized at application startup by calling
+ * setWindowCreator.
+ */
+[scriptable, uuid(641fe945-6902-4b3f-87c2-0daef32499b3)]
+interface nsIWindowWatcher : nsISupports {
+
+ /** Create a new window. It will automatically be added to our list
+ (via addWindow()).
+ @param aParent parent window, if any. Null if no parent. If it is
+ impossible to get to an nsIWebBrowserChrome from aParent, this
+ method will effectively act as if aParent were null.
+ @param aURL url to which to open the new window. Must already be
+ escaped, if applicable. can be null.
+ @param aName window name from JS window.open. can be null. If a window
+ with this name already exists, the openWindow call may just load
+ aUrl in it (if aUrl is not null) and return it.
+ @param aFeatures window features from JS window.open. can be null.
+ @param aArguments extra argument(s) to the new window, to be attached
+ as the |arguments| property. An nsIArray will be
+ unwound into multiple arguments (but not recursively!).
+ can be null.
+ @return the new window
+
+ @note This method may examine the JS context stack for purposes of
+ determining the security context to use for the search for a given
+ window named aName.
+ @note This method should try to set the default charset for the new
+ window to the default charset of aParent. This is not guaranteed,
+ however.
+ @note This method may dispatch a "toplevel-window-ready" notification
+ via nsIObserverService if the window did not already exist.
+ */
+ mozIDOMWindowProxy openWindow(in mozIDOMWindowProxy aParent, in ACString aUrl,
+ in ACString aName, in ACString aFeatures,
+ in nsISupports aArguments);
+
+ /** Clients of this service can register themselves to be notified
+ when a window is opened or closed (added to or removed from this
+ service). This method adds an aObserver to the list of objects
+ to be notified.
+ @param aObserver the object to be notified when windows are
+ opened or closed. Its Observe method will be
+ called with the following parameters:
+
+ aObserver::Observe interprets its parameters so:
+ aSubject the window being opened or closed, sent as an nsISupports
+ which can be QIed to an nsIDOMWindow.
+ aTopic a wstring, either "domwindowopened" or "domwindowclosed".
+ someData not used.
+ */
+ void registerNotification(in nsIObserver aObserver);
+
+ /** Clients of this service can register themselves to be notified
+ when a window is opened or closed (added to or removed from this
+ service). This method removes an aObserver from the list of objects
+ to be notified.
+ @param aObserver the observer to be removed.
+ */
+ void unregisterNotification(in nsIObserver aObserver);
+
+ /** Get an iterator for currently open windows in the order they were opened,
+ guaranteeing that each will be visited exactly once.
+ @return an enumerator which will itself return nsISupports objects which
+ can be QIed to an nsIDOMWindow
+ */
+
+ nsISimpleEnumerator getWindowEnumerator();
+
+ /** Return a newly created nsIPrompt implementation.
+ @param aParent the parent window used for posing alerts. can be null.
+ @return a new nsIPrompt object
+ */
+
+ nsIPrompt getNewPrompter(in mozIDOMWindowProxy aParent);
+
+ /** Return a newly created nsIAuthPrompt implementation.
+ @param aParent the parent window used for posing alerts. can be null.
+ @return a new nsIAuthPrompt object
+ */
+
+ nsIAuthPrompt getNewAuthPrompter(in mozIDOMWindowProxy aParent);
+
+ /** Set the window creator callback. It must be filled in by the app.
+ openWindow will use it to create new windows.
+ @param creator the callback. if null, the callback will be cleared
+ and window creation capabilities lost.
+ */
+ void setWindowCreator(in nsIWindowCreator creator);
+
+ /** Returns true if a window creator callback has been set, false otherwise.
+ */
+ boolean hasWindowCreator();
+
+
+ /** Retrieve the chrome window mapped to the given DOM window. Window
+ Watcher keeps a list of all top-level DOM windows currently open,
+ along with their corresponding chrome interfaces. Since DOM Windows
+ lack a (public) means of retrieving their corresponding chrome,
+ this method will do that.
+ @param aWindow the DOM window whose chrome window the caller needs
+ @return the corresponding chrome window
+ */
+ nsIWebBrowserChrome getChromeForWindow(in mozIDOMWindowProxy aWindow);
+
+ /**
+ Retrieve an existing chrome window (or frame).
+ @param aTargetName the window name
+
+ Note: This method will not consider special names like "_blank", "_top",
+ "_self", or "_parent", as there is no reference window.
+
+ Note: This method will search all open windows for any window or
+ frame with the given window name. Make sure you understand the
+ security implications of this before using this method!
+ */
+ mozIDOMWindowProxy getWindowByName(in AString aTargetName);
+
+ /**
+ Retrieves the active window from the focus manager.
+ */
+ readonly attribute mozIDOMWindowProxy activeWindow;
+
+};
+
+%{C++
+#define NS_WINDOWWATCHER_CONTRACTID "@mozilla.org/embedcomp/window-watcher;1"
+%}
diff --git a/toolkit/components/windowwatcher/nsOpenWindowInfo.cpp b/toolkit/components/windowwatcher/nsOpenWindowInfo.cpp
new file mode 100644
index 0000000000..4374090075
--- /dev/null
+++ b/toolkit/components/windowwatcher/nsOpenWindowInfo.cpp
@@ -0,0 +1,89 @@
+/* -*- 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 "nsOpenWindowInfo.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/BrowserParent.h"
+
+NS_IMPL_ISUPPORTS(nsOpenWindowInfo, nsIOpenWindowInfo)
+
+NS_IMETHODIMP nsOpenWindowInfo::GetParent(
+ mozilla::dom::BrowsingContext** aParent) {
+ *aParent = do_AddRef(mParent).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOpenWindowInfo::GetIsRemote(bool* aIsRemote) {
+ *aIsRemote = mIsRemote;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOpenWindowInfo::GetIsForWindowDotPrint(bool* aResult) {
+ *aResult = mIsForWindowDotPrint;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOpenWindowInfo::GetIsForPrinting(bool* aIsForPrinting) {
+ *aIsForPrinting = mIsForPrinting;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOpenWindowInfo::GetForceNoOpener(bool* aForceNoOpener) {
+ *aForceNoOpener = mForceNoOpener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOpenWindowInfo::GetScriptableOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aAttrs) {
+ bool ok = ToJSValue(aCx, mOriginAttributes, aAttrs);
+ NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+const mozilla::OriginAttributes& nsOpenWindowInfo::GetOriginAttributes() {
+ return mOriginAttributes;
+}
+
+mozilla::dom::BrowserParent* nsOpenWindowInfo::GetNextRemoteBrowser() {
+ return mNextRemoteBrowser;
+}
+
+nsIBrowsingContextReadyCallback*
+nsOpenWindowInfo::BrowsingContextReadyCallback() {
+ return mBrowsingContextReadyCallback;
+}
+
+NS_IMPL_ISUPPORTS(nsBrowsingContextReadyCallback,
+ nsIBrowsingContextReadyCallback)
+
+nsBrowsingContextReadyCallback::nsBrowsingContextReadyCallback(
+ RefPtr<mozilla::dom::BrowsingContextCallbackReceivedPromise::Private>
+ aPromise)
+ : mPromise(std::move(aPromise)) {}
+
+nsBrowsingContextReadyCallback::~nsBrowsingContextReadyCallback() {
+ if (mPromise) {
+ mPromise->Reject(NS_ERROR_FAILURE, __func__);
+ }
+ mPromise = nullptr;
+}
+
+NS_IMETHODIMP nsBrowsingContextReadyCallback::BrowsingContextReady(
+ mozilla::dom::BrowsingContext* aBC) {
+ MOZ_DIAGNOSTIC_ASSERT(mPromise,
+ "The 'browsing context ready' callback is null");
+ if (!mPromise) {
+ return NS_OK;
+ }
+ if (aBC) {
+ mPromise->Resolve(aBC, __func__);
+ } else {
+ mPromise->Reject(NS_ERROR_FAILURE, __func__);
+ }
+ mPromise = nullptr;
+ return NS_OK;
+}
diff --git a/toolkit/components/windowwatcher/nsOpenWindowInfo.h b/toolkit/components/windowwatcher/nsOpenWindowInfo.h
new file mode 100644
index 0000000000..8a353dda6b
--- /dev/null
+++ b/toolkit/components/windowwatcher/nsOpenWindowInfo.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsOpenWindowInfo_h
+#define nsOpenWindowInfo_h
+
+#include "nsIOpenWindowInfo.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/ClientOpenWindowUtils.h"
+
+class nsOpenWindowInfo : public nsIOpenWindowInfo {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOPENWINDOWINFO
+
+ bool mForceNoOpener = false;
+ bool mIsRemote = false;
+ bool mIsForPrinting = false;
+ bool mIsForWindowDotPrint = false;
+ RefPtr<mozilla::dom::BrowserParent> mNextRemoteBrowser;
+ mozilla::OriginAttributes mOriginAttributes;
+ RefPtr<mozilla::dom::BrowsingContext> mParent;
+ RefPtr<nsIBrowsingContextReadyCallback> mBrowsingContextReadyCallback;
+
+ private:
+ virtual ~nsOpenWindowInfo() = default;
+};
+
+class nsBrowsingContextReadyCallback : public nsIBrowsingContextReadyCallback {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIBROWSINGCONTEXTREADYCALLBACK
+
+ explicit nsBrowsingContextReadyCallback(
+ RefPtr<mozilla::dom::BrowsingContextCallbackReceivedPromise::Private>
+ aPromise);
+
+ private:
+ virtual ~nsBrowsingContextReadyCallback();
+
+ RefPtr<mozilla::dom::BrowsingContextCallbackReceivedPromise::Private>
+ mPromise;
+};
+
+#endif // nsOpenWindowInfo_h
diff --git a/toolkit/components/windowwatcher/nsPIPromptService.idl b/toolkit/components/windowwatcher/nsPIPromptService.idl
new file mode 100644
index 0000000000..3bff27a993
--- /dev/null
+++ b/toolkit/components/windowwatcher/nsPIPromptService.idl
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* The general dialog posing function within nsPromptService, for
+ private consumption, only. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMWindow;
+interface nsIDialogParamBlock;
+
+[uuid(C60A1955-6CB3-4827-8EF8-4F5C668AF0B3)]
+interface nsPIPromptService : nsISupports
+{
+%{C++
+ // eOpeningSound is obsolete but we need to support it for the compatibility.
+ // The implementers should use eSoundEventId instead.
+ enum {eMsg=0, eCheckboxMsg=1, eIconClass=2, eTitleMessage=3, eEditfield1Msg=4,
+ eEditfield2Msg=5, eEditfield1Value=6, eEditfield2Value=7,
+ eButton0Text=8, eButton1Text=9, eButton2Text=10, eButton3Text=11,
+ eDialogTitle=12, eOpeningSound=13};
+ enum {eButtonPressed=0, eCheckboxState=1, eNumberButtons=2,
+ eNumberEditfields=3, eEditField1Password=4, eDefaultButton=5,
+ eDelayButtonEnable=6, eSoundEventId=7};
+%}
+
+ void doDialog(in nsIDOMWindow aParent, in nsIDialogParamBlock aParamBlock, in string aChromeURL);
+};
diff --git a/toolkit/components/windowwatcher/nsPIWindowWatcher.idl b/toolkit/components/windowwatcher/nsPIWindowWatcher.idl
new file mode 100644
index 0000000000..ff827895ed
--- /dev/null
+++ b/toolkit/components/windowwatcher/nsPIWindowWatcher.idl
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Private "control" methods on the Window Watcher. These are annoying
+ bookkeeping methods, not part of the public (embedding) interface.
+*/
+
+#include "nsISupports.idl"
+
+%{ C++
+class nsDocShellLoadState;
+namespace mozilla::dom {
+class WindowFeatures;
+}
+%}
+
+webidl BrowsingContext;
+interface mozIDOMWindowProxy;
+interface nsISimpleEnumerator;
+interface nsIWebBrowserChrome;
+interface nsIDocShellTreeItem;
+interface nsIArray;
+interface nsIRemoteTab;
+interface nsIOpenWindowInfo;
+native nsDocShellLoadStatePtr(nsDocShellLoadState*);
+[ref] native WindowFeaturesRef(const mozilla::dom::WindowFeatures);
+
+[uuid(d162f9c4-19d5-4723-931f-f1e51bfa9f68)]
+interface nsPIWindowWatcher : nsISupports
+{
+ /** A window has been created. Add it to our list.
+ @param aWindow the window to add
+ @param aChrome the corresponding chrome window. The DOM window
+ and chrome will be mapped together, and the corresponding
+ chrome can be retrieved using the (not private)
+ method getChromeForWindow. If null, any extant mapping
+ will be cleared.
+ */
+ void addWindow(in mozIDOMWindowProxy aWindow,
+ in nsIWebBrowserChrome aChrome);
+
+ /** A window has been closed. Remove it from our list.
+ @param aWindow the window to remove
+ */
+ void removeWindow(in mozIDOMWindowProxy aWindow);
+
+ cenum PrintKind : 8 {
+ PRINT_NONE,
+ PRINT_INTERNAL,
+ PRINT_WINDOW_DOT_PRINT,
+ };
+
+ /** Like the public interface's open(), but can handle openDialog-style
+ arguments and calls which shouldn't result in us navigating the window.
+
+ @param aParent parent window, if any. Null if no parent. If it is
+ impossible to get to an nsIWebBrowserChrome from aParent, this
+ method will effectively act as if aParent were null.
+ @param aURL url to which to open the new window. Must already be
+ escaped, if applicable. can be null.
+ @param aName window name from JS window.open. can be null. If a window
+ with this name already exists, the openWindow call may just load
+ aUrl in it (if aUrl is not null) and return it.
+ @param aFeatures window features from JS window.open. can be null.
+ @param aCalledFromScript true if we were called from script.
+ @param aDialog use dialog defaults (see nsGlobalWindowOuter::OpenInternal)
+ @param aNavigate true if we should navigate the new window to the
+ specified URL.
+ @param aArgs Window argument
+ @param aIsPopupSpam true if the window is a popup spam window; used for
+ popup blocker internals.
+ @param aForceNoOpener If true, force noopener behavior. This means not
+ looking for existing windows with the given name,
+ not setting an opener on the newly opened window,
+ and returning null from this method.
+ @param aLoadState if aNavigate is true, this allows the caller to pass in
+ an nsIDocShellLoadState to use for the navigation.
+ Callers can pass in null if they want the windowwatcher
+ to just construct a loadinfo itself. If aNavigate is
+ false, this argument is ignored.
+
+ @return the new window
+
+ @note This method may examine the JS context stack for purposes of
+ determining the security context to use for the search for a given
+ window named aName.
+ @note This method should try to set the default charset for the new
+ window to the default charset of the document in the calling window
+ (which is determined based on the JS stack and the value of
+ aParent). This is not guaranteed, however.
+ */
+ [noscript]
+ BrowsingContext openWindow2(in mozIDOMWindowProxy aParent, in ACString aUrl,
+ in ACString aName, in ACString aFeatures,
+ in boolean aCalledFromScript,
+ in boolean aDialog,
+ in boolean aNavigate,
+ in nsISupports aArgs,
+ in boolean aIsPopupSpam,
+ in boolean aForceNoOpener,
+ in boolean aForceNoReferrer,
+ in nsPIWindowWatcher_PrintKind aPrintKind,
+ in nsDocShellLoadStatePtr aLoadState);
+
+ /**
+ * Opens a new window so that the window that aOpeningTab belongs to
+ * is set as the parent window. The newly opened window will also
+ * inherit load context information from aOpeningTab.
+ *
+ * @param aOpeningTab
+ * The nsIRemoteTab that is requesting the new window be opened.
+ * @param aFeatures
+ * Window features if called with window.open or similar.
+ * @param aCalledFromJS
+ * True if called via window.open or similar.
+ * @param aOpenerFullZoom
+ * The current zoom multiplier for the opener tab. This is then
+ * applied to the newly opened window.
+ * @param aOpenWindowInfo
+ * Information used to create the initial content browser in the new
+ * window.
+ *
+ * @return the nsIRemoteTab of the initial browser for the newly opened
+ * window.
+ */
+ nsIRemoteTab openWindowWithRemoteTab(in nsIRemoteTab aOpeningTab,
+ in WindowFeaturesRef aFeatures,
+ in boolean aCalledFromJS,
+ in float aOpenerFullZoom,
+ in nsIOpenWindowInfo aOpenWindowInfo);
+};
diff --git a/toolkit/components/windowwatcher/nsPromptUtils.h b/toolkit/components/windowwatcher/nsPromptUtils.h
new file mode 100644
index 0000000000..4ff85b00af
--- /dev/null
+++ b/toolkit/components/windowwatcher/nsPromptUtils.h
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSPROMPTUTILS_H_
+#define NSPROMPTUTILS_H_
+
+#include "nsIHttpChannel.h"
+
+/**
+ * @file
+ * This file defines some helper functions that simplify interaction
+ * with authentication prompts.
+ */
+
+/**
+ * Given a username (possibly in DOMAIN\user form) and password, parses the
+ * domain out of the username if necessary and sets domain, username and
+ * password on the auth information object.
+ */
+inline void NS_SetAuthInfo(nsIAuthInformation* aAuthInfo, const nsString& aUser,
+ const nsString& aPassword) {
+ uint32_t flags;
+ aAuthInfo->GetFlags(&flags);
+ if (flags & nsIAuthInformation::NEED_DOMAIN) {
+ // Domain is separated from username by a backslash
+ int32_t idx = aUser.FindChar(char16_t('\\'));
+ if (idx == kNotFound) {
+ aAuthInfo->SetUsername(aUser);
+ } else {
+ aAuthInfo->SetDomain(Substring(aUser, 0, idx));
+ aAuthInfo->SetUsername(Substring(aUser, idx + 1));
+ }
+ } else {
+ aAuthInfo->SetUsername(aUser);
+ }
+ aAuthInfo->SetPassword(aPassword);
+}
+
+/**
+ * Gets the host and port from a channel and authentication info. This is the
+ * "logical" host and port for this authentication, i.e. for a proxy
+ * authentication it refers to the proxy, while for a host authentication it
+ * is the actual host.
+ *
+ * @param machineProcessing
+ * When this parameter is true, the host will be returned in ASCII
+ * (instead of UTF-8; this is relevant when IDN is used). In addition,
+ * the port will be returned as the real port even when it was not
+ * explicitly specified (when false, the port will be returned as -1 in
+ * this case)
+ */
+inline void NS_GetAuthHostPort(nsIChannel* aChannel,
+ nsIAuthInformation* aAuthInfo,
+ bool aMachineProcessing, nsCString& aHost,
+ int32_t* aPort) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // Have to distinguish proxy auth and host auth here...
+ uint32_t flags;
+ aAuthInfo->GetFlags(&flags);
+ if (flags & nsIAuthInformation::AUTH_PROXY) {
+ nsCOMPtr<nsIProxiedChannel> proxied(do_QueryInterface(aChannel));
+ NS_ASSERTION(proxied, "proxy auth needs nsIProxiedChannel");
+
+ nsCOMPtr<nsIProxyInfo> info;
+ proxied->GetProxyInfo(getter_AddRefs(info));
+ NS_ASSERTION(info, "proxy auth needs nsIProxyInfo");
+
+ nsAutoCString idnhost;
+ info->GetHost(idnhost);
+ info->GetPort(aPort);
+
+ if (aMachineProcessing) {
+ nsCOMPtr<nsIIDNService> idnService =
+ do_GetService(NS_IDNSERVICE_CONTRACTID);
+ if (idnService) {
+ idnService->ConvertUTF8toACE(idnhost, aHost);
+ } else {
+ // Not much we can do here...
+ aHost = idnhost;
+ }
+ } else {
+ aHost = idnhost;
+ }
+ } else {
+ if (aMachineProcessing) {
+ uri->GetAsciiHost(aHost);
+ *aPort = NS_GetRealPort(uri);
+ } else {
+ uri->GetHost(aHost);
+ uri->GetPort(aPort);
+ }
+ }
+}
+
+/**
+ * Creates the key for looking up passwords in the password manager. This
+ * function uses the same format that Gecko functions have always used, thus
+ * ensuring backwards compatibility.
+ */
+inline void NS_GetAuthKey(nsIChannel* aChannel, nsIAuthInformation* aAuthInfo,
+ nsCString& aKey) {
+ // HTTP does this differently from other protocols
+ nsCOMPtr<nsIHttpChannel> http(do_QueryInterface(aChannel));
+ if (!http) {
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+ uri->GetPrePath(aKey);
+ return;
+ }
+
+ // NOTE: For backwards-compatibility reasons, this must be the ASCII host.
+ nsCString host;
+ int32_t port = -1;
+
+ NS_GetAuthHostPort(aChannel, aAuthInfo, true, host, &port);
+
+ nsAutoString realm;
+ aAuthInfo->GetRealm(realm);
+
+ // Now assemble the key: host:port (realm)
+ aKey.Append(host);
+ aKey.Append(':');
+ aKey.AppendInt(port);
+ aKey.AppendLiteral(" (");
+ AppendUTF16toUTF8(realm, aKey);
+ aKey.Append(')');
+}
+
+#endif
diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.cpp b/toolkit/components/windowwatcher/nsWindowWatcher.cpp
new file mode 100644
index 0000000000..9496efd668
--- /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 "nsDebug.h"
+#include "nsNetUtil.h"
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsJSUtils.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 "nsIDragService.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_browser.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 "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 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()
+ : mEnumeratorList(),
+ 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,
+ /* 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,
+ 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;
+ 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) {
+ ds->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,
+ 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 =
+ 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()->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;
+ }
+
+ // 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, &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;
+}
+
+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> 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();
+ }
+
+ // 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(
+ 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, &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;
+
+ // 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, uriToLoad, name,
+ featuresStr, 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;
+ }
+
+ // 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);
+ }
+
+ 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));
+
+ 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 =
+ parentInnerWin ? parentInnerWin->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) {
+ doc = parentDoc;
+ }
+ 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(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 (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(targetDocShell));
+
+ obsSvc->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
+ "webNavigation-createdNavigationTarget-from-js",
+ nullptr);
+ }
+ }
+
+ if (uriToLoad && aNavigate) {
+ 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;
+ }
+ }
+ loadState->SetLoadFlags(loadFlags);
+ loadState->SetFirstParty(true);
+
+ // Should this pay attention to errors returned by LoadURI?
+ targetBC->LoadURI(loadState);
+ }
+
+ if (isNewToplevelWindow) {
+ nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner;
+ targetDocShell->GetTreeOwner(getter_AddRefs(newTreeOwner));
+ SizeOpenedWindow(newTreeOwner, aParent, isCallerChrome, sizeSpec);
+ }
+
+ 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->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);
+
+ 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
+ return NS_NewURI(aURI, aURL, nullptr, baseURI);
+}
+
+// static
+uint32_t nsWindowWatcher::CalculateChromeFlagsHelper(
+ uint32_t aInitialFlags, const WindowFeatures& aFeatures,
+ bool* presenceFlag) {
+ 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;
+ }
+
+ return chromeFlags;
+}
+
+// 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
+ */
+// static
+uint32_t nsWindowWatcher::CalculateChromeFlagsForContent(
+ const WindowFeatures& aFeatures, bool* aIsPopupRequested) {
+ if (aFeatures.IsEmpty() || !ShouldOpenPopup(aFeatures)) {
+ // Open the current/new tab in the current/new window
+ // (depends on browser.link.open_newwindow).
+ return nsIWebBrowserChrome::CHROME_ALL;
+ }
+
+ // Open a minimal popup.
+ *aIsPopupRequested = true;
+ return nsIWebBrowserChrome::CHROME_MINIMAL_POPUP;
+}
+
+/**
+ * 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
+ * @return the chrome bitmask
+ */
+// static
+uint32_t nsWindowWatcher::CalculateChromeFlagsForSystem(
+ const WindowFeatures& aFeatures, bool aDialog, bool aChromeURL) {
+ 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, &presenceFlag);
+
+ // 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
+ */
+
+ return chromeFlags;
+}
+
+// public static
+bool nsWindowWatcher::HaveSpecifiedSize(const WindowFeatures& features) {
+ return CalcSizeSpec(features, false, CSSToDesktopScale()).SizeSpecified();
+}
+
+// static
+SizeSpec CalcSizeSpec(const WindowFeatures& aFeatures, bool aHasChromeParent,
+ CSSToDesktopScale aCSSToDesktopScale) {
+ SizeSpec result;
+ // 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.
+ result.mLeft.emplace((CSSCoord(x) * aCSSToDesktopScale).Rounded());
+ }
+
+ // 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.
+ result.mTop.emplace((CSSCoord(y) * aCSSToDesktopScale).Rounded());
+ }
+
+ // Non-standard extension.
+ // Not exposed to web content.
+ if (aHasChromeParent && aFeatures.Exists("outerwidth")) {
+ int32_t width = aFeatures.GetInt("outerwidth");
+ if (width) {
+ result.mOuterWidth.emplace(width);
+ }
+ }
+
+ if (result.mOuterWidth.isNothing()) {
+ // 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.
+ result.mInnerWidth.emplace(width);
+
+ // 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) {
+ result.mOuterHeight.emplace(height);
+ }
+ }
+
+ if (result.mOuterHeight.isNothing()) {
+ // 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.
+ result.mInnerHeight.emplace(height);
+
+ // 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.
+ result.mLockAspectRatio =
+ aFeatures.GetBoolWithDefault("lockaspectratio", false);
+ return result;
+}
+
+/* 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
+ The parent window, used to do security checks.
+ @param aIsCallerChrome
+ True if the code requesting the new window is privileged.
+ @param aSizeSpec
+ The size that the new window should be.
+*/
+static void SizeOpenedWindow(nsIDocShellTreeOwner* aTreeOwner,
+ mozIDOMWindowProxy* aParent, bool aIsCallerChrome,
+ const SizeSpec& aSizeSpec) {
+ // We should only be sizing top-level windows if we're in the parent
+ // process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // 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;
+ }
+
+ // 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.
+ DesktopIntCoord left = 0;
+ DesktopIntCoord top = 0;
+ CSSIntCoord width = 0;
+ CSSIntCoord height = 0;
+ // difference between chrome and content size
+ CSSIntCoord chromeWidth = 0;
+ CSSIntCoord chromeHeight = 0;
+ // whether the window size spec refers to chrome or content
+ bool sizeChromeWidth = true;
+ bool sizeChromeHeight = true;
+
+ {
+ CSSToLayoutDeviceScale cssToDevScale =
+ treeOwnerAsWin->UnscaledDevicePixelsPerCSSPixel();
+ DesktopToLayoutDeviceScale devToDesktopScale =
+ treeOwnerAsWin->DevicePixelsPerDesktopPixel();
+
+ LayoutDeviceIntRect devPxRect = treeOwnerAsWin->GetPositionAndSize();
+ width = (LayoutDeviceCoord(devPxRect.width) / cssToDevScale).Rounded();
+ height = (LayoutDeviceCoord(devPxRect.height) / cssToDevScale).Rounded();
+ left = (LayoutDeviceCoord(devPxRect.x) / devToDesktopScale).Rounded();
+ top = (LayoutDeviceCoord(devPxRect.y) / devToDesktopScale).Rounded();
+
+ LayoutDeviceIntSize contentSize;
+ bool hasPrimaryContent = false;
+ aTreeOwner->GetHasPrimaryContent(&hasPrimaryContent);
+ if (hasPrimaryContent) {
+ aTreeOwner->GetPrimaryContentSize(&contentSize.width,
+ &contentSize.height);
+ } else {
+ aTreeOwner->GetRootShellSize(&contentSize.width, &contentSize.height);
+ }
+
+ CSSIntSize contentSizeCSS = RoundedToInt(contentSize / cssToDevScale);
+ chromeWidth = width - contentSizeCSS.width;
+ chromeHeight = height - contentSizeCSS.height;
+ }
+
+ // Set up left/top
+ if (aSizeSpec.mLeft) {
+ left = *aSizeSpec.mLeft;
+ }
+
+ if (aSizeSpec.mTop) {
+ top = *aSizeSpec.mTop;
+ }
+
+ // Set up width
+ if (aSizeSpec.mOuterWidth) {
+ width = *aSizeSpec.mOuterWidth;
+ } else if (aSizeSpec.mInnerWidth) {
+ sizeChromeWidth = false;
+ width = *aSizeSpec.mInnerWidth;
+ }
+
+ // Set up height
+ if (aSizeSpec.mOuterHeight) {
+ height = *aSizeSpec.mOuterHeight;
+ } else if (aSizeSpec.mInnerHeight) {
+ sizeChromeHeight = false;
+ height = *aSizeSpec.mInnerHeight;
+ }
+
+ bool positionSpecified = aSizeSpec.PositionSpecified();
+
+ // Check security state for use in determining window dimensions
+ bool enabled = false;
+ if (aIsCallerChrome) {
+ // Only enable special privileges for chrome when chrome calls
+ // open() on a chrome window
+ nsCOMPtr<nsIDOMChromeWindow> chromeWin(do_QueryInterface(aParent));
+ enabled = !aParent || chromeWin;
+ }
+
+ const CSSIntCoord extraWidth = sizeChromeWidth ? CSSIntCoord(0) : chromeWidth;
+ const CSSIntCoord extraHeight =
+ sizeChromeHeight ? CSSIntCoord(0) : chromeHeight;
+
+ 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) {
+ // XXX width, height are in CSS rather than desktop pixels here.
+ screenMgr->ScreenForRect(left, top, width, height,
+ getter_AddRefs(screen));
+ }
+ if (screen) {
+ CSSIntCoord winWidth = width + extraWidth;
+ CSSIntCoord winHeight = height + extraHeight;
+
+ auto screenCssToDesktopScale = screen->GetCSSToDesktopScale();
+
+ const DesktopIntRect screenDesktopRect = screen->GetAvailRectDisplayPix();
+ // Get screen dimensions (in CSS pixels)
+ const CSSSize screenCssSize =
+ screenDesktopRect.Size() / screenCssToDesktopScale;
+
+ 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 + extraHeight;
+ }
+ if (winHeight > screenCssSize.height) {
+ height = screenCssSize.height - extraHeight;
+ }
+ if (width < 100) {
+ width = 100;
+ winWidth = width + extraWidth;
+ }
+ if (winWidth > screenCssSize.width) {
+ width = screenCssSize.width - extraWidth;
+ }
+ } else {
+ int32_t targetContentWidth = 0;
+ int32_t targetContentHeight = 0;
+
+ nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting(
+ chromeWidth, chromeHeight, screenCssSize.width,
+ screenCssSize.height, width, height, sizeChromeWidth,
+ sizeChromeHeight, &targetContentWidth, &targetContentHeight);
+
+ if (aSizeSpec.mInnerWidth || aSizeSpec.mOuterWidth) {
+ width = targetContentWidth;
+ winWidth = width + extraWidth;
+ }
+
+ if (aSizeSpec.mInnerHeight || aSizeSpec.mOuterHeight) {
+ height = targetContentHeight;
+ winHeight = height + extraHeight;
+ }
+ }
+ }
+
+ const DesktopIntCoord desktopWinWidth =
+ (CSSCoord(winWidth) * screenCssToDesktopScale).Rounded();
+ const DesktopIntCoord desktopWinHeight =
+ (CSSCoord(winHeight) * screenCssToDesktopScale).Rounded();
+ CheckedInt<int32_t> leftPlusWinWidth = int32_t(left);
+ leftPlusWinWidth += int32_t(desktopWinWidth);
+ if (!leftPlusWinWidth.isValid() ||
+ leftPlusWinWidth.value() > screenDesktopRect.XMost()) {
+ left = screenDesktopRect.XMost() - desktopWinWidth;
+ }
+ if (left < screenDesktopRect.x) {
+ left = screenDesktopRect.x;
+ }
+
+ CheckedInt<int32_t> topPlusWinHeight = int32_t(top);
+ topPlusWinHeight += int32_t(desktopWinHeight);
+ if (!topPlusWinHeight.isValid() ||
+ topPlusWinHeight.value() > screenDesktopRect.YMost()) {
+ top = screenDesktopRect.YMost() - desktopWinHeight;
+ }
+ if (top < screenDesktopRect.y) {
+ top = screenDesktopRect.y;
+ }
+
+ if (top != oldTop || left != oldLeft) {
+ positionSpecified = true;
+ }
+ }
+ }
+
+ // size and position the window
+
+ if (positionSpecified) {
+ treeOwnerAsWin->SetPositionDesktopPix(left, top);
+ }
+
+ if (aSizeSpec.SizeSpecified()) {
+ const CSSToLayoutDeviceScale scale =
+ treeOwnerAsWin->UnscaledDevicePixelsPerCSSPixel();
+
+ /* 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) {
+ const LayoutDeviceIntCoord widthDevPx =
+ (CSSCoord(width) * scale).Rounded();
+ const LayoutDeviceIntCoord heightDevPx =
+ (CSSCoord(height) * scale).Rounded();
+ bool hasPrimaryContent = false;
+ aTreeOwner->GetHasPrimaryContent(&hasPrimaryContent);
+ if (hasPrimaryContent) {
+ aTreeOwner->SetPrimaryContentSize(widthDevPx, heightDevPx);
+ } else {
+ aTreeOwner->SetRootShellSize(widthDevPx, heightDevPx);
+ }
+ } else {
+ const LayoutDeviceIntCoord widthDevPx =
+ (CSSCoord(width + extraWidth) * scale).Rounded();
+ const LayoutDeviceIntCoord heightDevPx =
+ (CSSCoord(height + extraHeight) * scale).Rounded();
+ treeOwnerAsWin->SetSize(widthDevPx, heightDevPx, 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 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 is 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) {
+ return nsIBrowserDOMWindow::OPEN_NEWWINDOW;
+ }
+ }
+ }
+
+ return containerPref;
+}
diff --git a/toolkit/components/windowwatcher/nsWindowWatcher.h b/toolkit/components/windowwatcher/nsWindowWatcher.h
new file mode 100644
index 0000000000..e34865f510
--- /dev/null
+++ b/toolkit/components/windowwatcher/nsWindowWatcher.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsWindowWatcher_h__
+#define __nsWindowWatcher_h__
+
+// {a21bfa01-f349-4394-a84c-8de5cf0737d0}
+#define NS_WINDOWWATCHER_CID \
+ { \
+ 0xa21bfa01, 0xf349, 0x4394, { \
+ 0xa8, 0x4c, 0x8d, 0xe5, 0xcf, 0x7, 0x37, 0xd0 \
+ } \
+ }
+
+#include "nsCOMPtr.h"
+#include "Units.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Maybe.h"
+#include "nsIWindowCreator.h" // for stupid compilers
+#include "nsIWindowWatcher.h"
+#include "nsIOpenWindowInfo.h"
+#include "nsIPromptFactory.h"
+#include "nsIRemoteTab.h"
+#include "nsPIWindowWatcher.h"
+#include "nsTArray.h"
+#include "mozilla/dom/WindowFeatures.h" // mozilla::dom::WindowFeatures
+
+class nsIURI;
+class nsIDocShellTreeItem;
+class nsIDocShellTreeOwner;
+class nsPIDOMWindowOuter;
+class nsWatcherWindowEnumerator;
+class nsPromptService;
+struct nsWatcherWindowEntry;
+
+class nsWindowWatcher : public nsIWindowWatcher,
+ public nsPIWindowWatcher,
+ public nsIPromptFactory {
+ friend class nsWatcherWindowEnumerator;
+
+ public:
+ nsWindowWatcher();
+
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIWINDOWWATCHER
+ NS_DECL_NSPIWINDOWWATCHER
+ NS_DECL_NSIPROMPTFACTORY
+
+ static int32_t GetWindowOpenLocation(nsPIDOMWindowOuter* aParent,
+ uint32_t aChromeFlags,
+ bool aCalledFromJS, bool aIsForPrinting);
+
+ static bool HaveSpecifiedSize(const mozilla::dom::WindowFeatures& features);
+
+ protected:
+ virtual ~nsWindowWatcher();
+
+ friend class nsPromptService;
+ bool AddEnumerator(nsWatcherWindowEnumerator* aEnumerator);
+ bool RemoveEnumerator(nsWatcherWindowEnumerator* aEnumerator);
+
+ nsWatcherWindowEntry* FindWindowEntry(mozIDOMWindowProxy* aWindow);
+ nsresult RemoveWindow(nsWatcherWindowEntry* aInfo);
+
+ // Just like OpenWindowJS, but knows whether it got called via OpenWindowJS
+ // (which means called from script) or called via OpenWindow.
+ nsresult 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,
+ nsDocShellLoadState* aLoadState,
+ mozilla::dom::BrowsingContext** aResult);
+
+ static nsresult URIfromURL(const nsACString& aURL,
+ mozIDOMWindowProxy* aParent, nsIURI** aURI);
+
+ static bool ShouldOpenPopup(const mozilla::dom::WindowFeatures& aFeatures);
+
+ static uint32_t CalculateChromeFlagsForContent(
+ const mozilla::dom::WindowFeatures& aFeatures, bool* aIsPopupRequested);
+
+ static uint32_t CalculateChromeFlagsForSystem(
+ const mozilla::dom::WindowFeatures& aFeatures, bool aDialog,
+ bool aChromeURL);
+
+ private:
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult CreateChromeWindow(
+ nsIWebBrowserChrome* aParentChrome, uint32_t aChromeFlags,
+ nsIOpenWindowInfo* aOpenWindowInfo, nsIWebBrowserChrome** aResult);
+
+ static uint32_t CalculateChromeFlagsHelper(
+ uint32_t aInitialFlags, const mozilla::dom::WindowFeatures& aFeatures,
+ bool* presenceFlag = nullptr);
+
+ protected:
+ nsTArray<nsWatcherWindowEnumerator*> mEnumeratorList;
+ nsWatcherWindowEntry* mOldestWindow;
+ mozilla::Mutex mListLock MOZ_UNANNOTATED;
+
+ nsCOMPtr<nsIWindowCreator> mWindowCreator;
+};
+
+#endif
diff --git a/toolkit/components/windowwatcher/test/browser.ini b/toolkit/components/windowwatcher/test/browser.ini
new file mode 100644
index 0000000000..4f6da72954
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/browser.ini
@@ -0,0 +1,26 @@
+[DEFAULT]
+tags = openwindow
+support-files =
+ head.js
+
+[browser_new_content_window_chromeflags.js]
+[browser_new_remote_window_flags.js]
+[browser_new_content_window_from_chrome_principal.js]
+[browser_new_sized_window.js]
+skip-if = os == 'win' # Bug 1276802 - Opening windows from content on Windows might not get the size right
+[browser_popup_condition_tab.js]
+skip-if =
+ debug # Opening many windows takes a long time on slow builds
+ tsan # Opening many windows takes a long time on slow builds
+ socketprocess_networking
+[browser_popup_condition_window.js]
+skip-if =
+ debug # Opening many windows takes a long time on slow builds
+ tsan # Opening many windows takes a long time on slow builds
+ socketprocess_networking
+[browser_popup_condition_current.js]
+skip-if =
+ debug # Opening many windows takes a long time on slow builds
+ tsan # Opening many windows takes a long time on slow builds
+ socketprocess_networking
+[browser_non_popup_from_popup.js]
diff --git a/toolkit/components/windowwatcher/test/browser_new_content_window_chromeflags.js b/toolkit/components/windowwatcher/test/browser_new_content_window_chromeflags.js
new file mode 100644
index 0000000000..d76fa9bd40
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/browser_new_content_window_chromeflags.js
@@ -0,0 +1,282 @@
+/**
+ * Tests that chromeFlags are set properly on windows that are
+ * being opened from content.
+ */
+
+// The following are not allowed from web content.
+//
+// <feature string>: {
+// flag: <associated nsIWebBrowserChrome flag>,
+// defaults_to: <what this feature defaults to normally>
+// }
+const DISALLOWED = {
+ location: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_LOCATIONBAR,
+ defaults_to: true,
+ },
+ chrome: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_OPENAS_CHROME,
+ defaults_to: false,
+ },
+ dialog: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG,
+ defaults_to: false,
+ },
+ private: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_PRIVATE_WINDOW,
+ defaults_to: false,
+ },
+ "non-private": {
+ flag: Ci.nsIWebBrowserChrome.CHROME_NON_PRIVATE_WINDOW,
+ defaults_to: false,
+ },
+ // "all":
+ // checked manually, since this is an aggregate
+ // flag.
+ //
+ // "remote":
+ // checked manually, since its default value will
+ // depend on whether or not e10s is enabled by default.
+ popup: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_POPUP,
+ defaults_to: false,
+ },
+ alwaysLowered: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_LOWERED,
+ defaults_to: false,
+ },
+ "z-lock": {
+ flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_LOWERED, // Renamed to alwaysLowered
+ defaults_to: false,
+ },
+ alwaysRaised: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_RAISED,
+ defaults_to: false,
+ },
+ alwaysOnTop: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_ALWAYS_ON_TOP,
+ defaults_to: false,
+ },
+ suppressanimation: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_SUPPRESS_ANIMATION,
+ defaults_to: false,
+ },
+ extrachrome: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_EXTRA,
+ defaults_to: false,
+ },
+ centerscreen: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_CENTER_SCREEN,
+ defaults_to: false,
+ },
+ dependent: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_DEPENDENT,
+ defaults_to: false,
+ },
+ modal: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_MODAL,
+ defaults_to: false,
+ },
+ titlebar: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_TITLEBAR,
+ defaults_to: true,
+ },
+ close: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_CLOSE,
+ defaults_to: true,
+ },
+ resizable: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_RESIZE,
+ defaults_to: true,
+ },
+ status: {
+ flag: Ci.nsIWebBrowserChrome.CHROME_STATUSBAR,
+ defaults_to: true,
+ },
+};
+
+// This magic value of 2 means that by default, when content tries
+// to open a new window, it'll actually open in a new window instead
+// of a new tab.
+Services.prefs.setIntPref("browser.link.open_newwindow", 2);
+registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.link.open_newwindow");
+});
+
+/**
+ * Given some nsIDOMWindow for a window running in the parent process,
+ * asynchronously return the nsIWebBrowserChrome chrome flags for the
+ * associated content window.
+ *
+ * @param win (nsIDOMWindow)
+ * @returns int
+ */
+function getContentChromeFlags(win) {
+ let b = win.gBrowser.selectedBrowser;
+ return SpecialPowers.spawn(b, [], async function () {
+ // Content scripts provide docShell as a global.
+ /* global docShell */
+ docShell.QueryInterface(Ci.nsIInterfaceRequestor);
+ try {
+ // This will throw if we're not a remote browser.
+ return docShell
+ .getInterface(Ci.nsIBrowserChild)
+ .QueryInterface(Ci.nsIWebBrowserChrome).chromeFlags;
+ } catch (e) {
+ // This must be a non-remote browser...
+ return docShell.treeOwner.QueryInterface(
+ Ci.nsIWebBrowserChrome
+ ).chromeFlags;
+ }
+ });
+}
+
+/**
+ * For some chromeFlags, ensures that flags in the DISALLOWED
+ * group were not modified.
+ *
+ * @param chromeFlags (int)
+ * Some chromeFlags to check.
+ */
+function assertContentFlags(chromeFlags, isPopup) {
+ for (let feature in DISALLOWED) {
+ let flag = DISALLOWED[feature].flag;
+ Assert.ok(flag, "Expected flag to be a non-zeroish value");
+ if (DISALLOWED[feature].defaults_to) {
+ // The feature is supposed to default to true, so it should
+ // stay true.
+ Assert.ok(
+ chromeFlags & flag,
+ `Expected feature ${feature} to be unchanged`
+ );
+ } else {
+ // The feature is supposed to default to false, so it should
+ // stay false.
+ Assert.ok(
+ !(chromeFlags & flag),
+ `Expected feature ${feature} to be unchanged`
+ );
+ }
+ }
+}
+
+/**
+ * Opens a window from content using window.open with the
+ * features computed from DISALLOWED. The computed feature string attempts to
+ * flip every feature away from their default.
+ */
+add_task(async function test_disallowed_flags() {
+ // Construct a features string that flips all DISALLOWED features
+ // to not be their defaults.
+ const DISALLOWED_STRING = Object.keys(DISALLOWED)
+ .map(feature => {
+ let toValue = DISALLOWED[feature].defaults_to ? "no" : "yes";
+ return `${feature}=${toValue}`;
+ })
+ .join(",");
+
+ const FEATURES = [DISALLOWED_STRING].join(",");
+
+ const SCRIPT_PAGE = `data:text/html,<script>window.open("about:blank", "_blank", "${FEATURES}");</script>`;
+ const SCRIPT_PAGE_FOR_CHROME_ALL = `data:text/html,<script>window.open("about:blank", "_blank", "all");</script>`;
+
+ let newWinPromise = BrowserTestUtils.waitForNewWindow();
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: SCRIPT_PAGE,
+ },
+ async function (browser) {
+ let win = await newWinPromise;
+ let parentChromeFlags = getParentChromeFlags(win);
+ assertContentFlags(parentChromeFlags);
+
+ if (win.gMultiProcessBrowser) {
+ Assert.ok(
+ parentChromeFlags & Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW,
+ "Should be remote by default"
+ );
+ } else {
+ Assert.ok(
+ !(parentChromeFlags & Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW),
+ "Should not be remote by default"
+ );
+ }
+
+ // Confusingly, chromeFlags also exist in the content process
+ // as part of the BrowserChild, so we have to check those too.
+ let contentChromeFlags = await getContentChromeFlags(win);
+ assertContentFlags(contentChromeFlags);
+
+ Assert.equal(
+ parentChromeFlags & Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW,
+ contentChromeFlags & Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW,
+ "Should have matching remote value in parent and content"
+ );
+
+ Assert.equal(
+ parentChromeFlags & Ci.nsIWebBrowserChrome.CHROME_FISSION_WINDOW,
+ contentChromeFlags & Ci.nsIWebBrowserChrome.CHROME_FISSION_WINDOW,
+ "Should have matching fission value in parent and content"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ }
+ );
+
+ // We check "all" manually, since that's an aggregate flag
+ // and doesn't fit nicely into the ALLOWED / DISALLOWED scheme
+ newWinPromise = BrowserTestUtils.waitForNewWindow();
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: SCRIPT_PAGE_FOR_CHROME_ALL,
+ },
+ async function (browser) {
+ let win = await newWinPromise;
+ let parentChromeFlags = getParentChromeFlags(win);
+ Assert.notEqual(
+ parentChromeFlags & Ci.nsIWebBrowserChrome.CHROME_ALL,
+ Ci.nsIWebBrowserChrome.CHROME_ALL,
+ "Should not have been able to set CHROME_ALL"
+ );
+ await BrowserTestUtils.closeWindow(win);
+ }
+ );
+});
+
+/**
+ * Opens a window with some chrome flags specified, which should not affect
+ * scrollbars flag which defaults to true when not disabled explicitly.
+ */
+add_task(async function test_scrollbars_flag() {
+ const SCRIPT = 'window.open("about:blank", "_blank", "toolbar=0");';
+ const SCRIPT_PAGE = `data:text/html,<script>${SCRIPT}</script>`;
+
+ let newWinPromise = BrowserTestUtils.waitForNewWindow();
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: SCRIPT_PAGE,
+ },
+ async function (browser) {
+ let win = await newWinPromise;
+
+ let parentChromeFlags = getParentChromeFlags(win);
+ Assert.ok(
+ parentChromeFlags & Ci.nsIWebBrowserChrome.CHROME_SCROLLBARS,
+ "Should have scrollbars when not disabled explicitly"
+ );
+
+ let contentChromeFlags = await getContentChromeFlags(win);
+ Assert.ok(
+ contentChromeFlags & Ci.nsIWebBrowserChrome.CHROME_SCROLLBARS,
+ "Should have scrollbars when not disabled explicitly"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ }
+ );
+});
diff --git a/toolkit/components/windowwatcher/test/browser_new_content_window_from_chrome_principal.js b/toolkit/components/windowwatcher/test/browser_new_content_window_from_chrome_principal.js
new file mode 100644
index 0000000000..28c1c23e60
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/browser_new_content_window_from_chrome_principal.js
@@ -0,0 +1,44 @@
+"use strict";
+
+/**
+ * Tests that if chrome-privileged code calls .open() on an
+ * unprivileged window, that the principal in the newly
+ * opened window is appropriately set.
+ */
+add_task(async function test_chrome_opens_window() {
+ // This magic value of 2 means that by default, when content tries
+ // to open a new window, it'll actually open in a new window instead
+ // of a new tab.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.link.open_newwindow", 2]],
+ });
+
+ let newWinPromise = BrowserTestUtils.waitForNewWindow({
+ url: "https://example.com/",
+ });
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ content.open("https://example.com/", "_blank");
+ });
+
+ let win = await newWinPromise;
+ let browser = win.gBrowser.selectedBrowser;
+ Assert.ok(
+ E10SUtils.isWebRemoteType(browser.remoteType),
+ "Should have the default content remote type."
+ );
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ Assert.ok(
+ !content.document.nodePrincipal.isSystemPrincipal,
+ "We should not have a system principal."
+ );
+ Assert.equal(
+ content.document.nodePrincipal.origin,
+ "https://example.com",
+ "Should have the example.com principal"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/toolkit/components/windowwatcher/test/browser_new_remote_window_flags.js b/toolkit/components/windowwatcher/test/browser_new_remote_window_flags.js
new file mode 100644
index 0000000000..06a15d123c
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/browser_new_remote_window_flags.js
@@ -0,0 +1,85 @@
+/**
+ * Tests that when a remote browser opens a new window that the
+ * newly opened window is also remote.
+ */
+
+const ANCHOR_PAGE = `data:text/html,<a href="about:blank" target="_blank">Click me!</a>`;
+const SCRIPT_PAGE = `data:text/html,<script>window.open("about:blank", "_blank");</script>`;
+
+// This magic value of 2 means that by default, when content tries
+// to open a new window, it'll actually open in a new window instead
+// of a new tab.
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.link.open_newwindow", 2]],
+ });
+});
+
+function assertFlags(win) {
+ let docShell = win.docShell;
+ let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
+ let chromeFlags = docShell.treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIAppWindow).chromeFlags;
+ Assert.ok(
+ loadContext.useRemoteTabs,
+ "Should be using remote tabs on the load context"
+ );
+ Assert.ok(
+ chromeFlags & Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW,
+ "Should have the remoteness chrome flag on the window"
+ );
+}
+
+/**
+ * Content can open a window using a target="_blank" link
+ */
+add_task(async function test_new_remote_window_flags_target_blank() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: ANCHOR_PAGE,
+ },
+ async function (browser) {
+ let newWinPromise = BrowserTestUtils.waitForNewWindow();
+ await BrowserTestUtils.synthesizeMouseAtCenter("a", {}, browser);
+ let win = await newWinPromise;
+ assertFlags(win);
+ await BrowserTestUtils.closeWindow(win);
+ }
+ );
+});
+
+/**
+ * Content can open a window using window.open
+ */
+add_task(async function test_new_remote_window_flags_window_open() {
+ let newWinPromise = BrowserTestUtils.waitForNewWindow();
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: SCRIPT_PAGE,
+ },
+ async function (browser) {
+ let win = await newWinPromise;
+ assertFlags(win);
+ await BrowserTestUtils.closeWindow(win);
+ }
+ );
+});
+
+/**
+ * Privileged content scripts can also open new windows
+ * using content.open.
+ */
+add_task(async function test_new_remote_window_flags_content_open() {
+ let newWinPromise = BrowserTestUtils.waitForNewWindow();
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ content.open("about:blank", "_blank");
+ });
+
+ let win = await newWinPromise;
+ assertFlags(win);
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/toolkit/components/windowwatcher/test/browser_new_sized_window.js b/toolkit/components/windowwatcher/test/browser_new_sized_window.js
new file mode 100644
index 0000000000..0a1e63a8c8
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/browser_new_sized_window.js
@@ -0,0 +1,67 @@
+"use strict";
+
+/**
+ * Tests that content can open windows at requested dimensions
+ * of height and width.
+ */
+
+/**
+ * This utility function does most of the actual testing. We
+ * construct a feature string suitable for the passed in width
+ * and height, and then run that script in content to open the
+ * new window. When the new window comes up, this function tests
+ * to ensure that the content area of the initial browser is the
+ * requested dimensions. Finally, we also ensure that we're not
+ * persisting the position, size or sizemode of the new browser
+ * window.
+ */
+function test_dimensions({ width, height }) {
+ let features = [];
+ if (width) {
+ features.push(`width=${width}`);
+ }
+ if (height) {
+ features.push(`height=${height}`);
+ }
+ const FEATURE_STR = features.join(",");
+ const SCRIPT_PAGE = `data:text/html,<script>window.open("about:blank", "_blank", "${FEATURE_STR}");</script>`;
+
+ let newWinPromise = BrowserTestUtils.waitForNewWindow();
+
+ return BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: SCRIPT_PAGE,
+ },
+ async function (browser) {
+ let win = await newWinPromise;
+ let rect = win.gBrowser.selectedBrowser.getBoundingClientRect();
+
+ if (width) {
+ Assert.equal(rect.width, width, "Should have the requested width");
+ }
+
+ if (height) {
+ Assert.equal(rect.height, height, "Should have the requested height");
+ }
+
+ let treeOwner = win.docShell.treeOwner;
+ let persistPosition = {};
+ let persistSize = {};
+ let persistSizeMode = {};
+ treeOwner.getPersistence(persistPosition, persistSize, persistSizeMode);
+
+ Assert.ok(!persistPosition.value, "Should not persist position");
+ Assert.ok(!persistSize.value, "Should not persist size");
+ Assert.ok(!persistSizeMode.value, "Should not persist size mode");
+
+ await BrowserTestUtils.closeWindow(win);
+ }
+ );
+}
+
+add_task(async function test_new_sized_window() {
+ await test_dimensions({ width: 100 });
+ await test_dimensions({ height: 150 });
+ await test_dimensions({ width: 300, height: 200 });
+});
diff --git a/toolkit/components/windowwatcher/test/browser_non_popup_from_popup.js b/toolkit/components/windowwatcher/test/browser_non_popup_from_popup.js
new file mode 100644
index 0000000000..52869b007f
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/browser_non_popup_from_popup.js
@@ -0,0 +1,53 @@
+"use strict";
+
+// Opening non-popup from a popup should open a new tab in the most recent
+// non-popup window.
+add_task(async function test_non_popup_from_popup() {
+ const BLANK_PAGE = "data:text/html,";
+
+ // A page opened in a new tab.
+ const OPEN_PAGE = "data:text/plain,hello";
+
+ // A page opened in a new popup.
+ // This opens a new non-popup page with OPEN_PAGE,
+ // tha should be opened in a new tab in most recent window.
+ const NON_POPUP_OPENER = btoa(
+ `data:text/html,<script>window.open('${OPEN_PAGE}', '', '')</script>`
+ );
+
+ // A page opened in a new tab.
+ // This opens a popup with NON_POPUP_OPENER.
+ const POPUP_OPENER = `data:text/html,<script>window.open(atob("${NON_POPUP_OPENER}"), "", "width=500");</script>`;
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.link.open_newwindow", 3]],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: BLANK_PAGE,
+ },
+ async function (browser) {
+ // Wait for a popup opened by POPUP_OPENER.
+ const newPopupPromise = BrowserTestUtils.waitForNewWindow();
+
+ // Wait for a new tab opened by NON_POPUP_OPENER.
+ const newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, OPEN_PAGE);
+
+ // Open a new tab that opens a popup with NON_POPUP_OPENER.
+ BrowserTestUtils.loadURIString(gBrowser, POPUP_OPENER);
+
+ let win = await newPopupPromise;
+ Assert.ok(true, "popup is opened");
+
+ let tab = await newTabPromise;
+ Assert.ok(true, "new tab is opened in the recent window");
+
+ BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.closeWindow(win);
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/toolkit/components/windowwatcher/test/browser_popup_condition_current.js b/toolkit/components/windowwatcher/test/browser_popup_condition_current.js
new file mode 100644
index 0000000000..a033c6738a
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/browser_popup_condition_current.js
@@ -0,0 +1,13 @@
+"use strict";
+
+/**
+ * Opens windows from content using window.open with several features patterns.
+ */
+add_task(async function test_popup_conditions_current() {
+ // Non-popup is opened in a current tab.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.link.open_newwindow", 1]],
+ });
+ await testPopupPatterns("current");
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/toolkit/components/windowwatcher/test/browser_popup_condition_tab.js b/toolkit/components/windowwatcher/test/browser_popup_condition_tab.js
new file mode 100644
index 0000000000..36a98fd511
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/browser_popup_condition_tab.js
@@ -0,0 +1,13 @@
+"use strict";
+
+/**
+ * Opens windows from content using window.open with several features patterns.
+ */
+add_task(async function test_popup_conditions_tab() {
+ // Non-popup is opened in a new tab (default behavior).
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.link.open_newwindow", 3]],
+ });
+ await testPopupPatterns("tab");
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/toolkit/components/windowwatcher/test/browser_popup_condition_window.js b/toolkit/components/windowwatcher/test/browser_popup_condition_window.js
new file mode 100644
index 0000000000..6f66124a1a
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/browser_popup_condition_window.js
@@ -0,0 +1,13 @@
+"use strict";
+
+/**
+ * Opens windows from content using window.open with several features patterns.
+ */
+add_task(async function test_popup_conditions_window() {
+ // Non-popup is opened in a new window.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.link.open_newwindow", 2]],
+ });
+ await testPopupPatterns("window");
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/toolkit/components/windowwatcher/test/chrome.ini b/toolkit/components/windowwatcher/test/chrome.ini
new file mode 100644
index 0000000000..91d33be633
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/chrome.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+tags = openwindow
+
+[test_alwaysOnTop_windows.html]
+skip-if = os != "win"
+[test_dialog_arguments.html]
+support-files =
+ file_test_dialog.html
+[test_modal_windows.html]
diff --git a/toolkit/components/windowwatcher/test/file_named_window.html b/toolkit/components/windowwatcher/test/file_named_window.html
new file mode 100644
index 0000000000..c66d89cd34
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/file_named_window.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+test_named_window.html new window
+</body>
+</html>
diff --git a/toolkit/components/windowwatcher/test/file_storage_copied.html b/toolkit/components/windowwatcher/test/file_storage_copied.html
new file mode 100644
index 0000000000..e32709315a
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/file_storage_copied.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+This page is opened in a new window by test_storage_copied.html.
+We need to return the sessionStorage value for the item "test-item",
+by way of postMessage.
+-->
+<head>
+<body>Opened!</body>
+<script>
+ window.postMessage(window.sessionStorage.getItem("test-item"), "*");
+</script>
+</html>
diff --git a/toolkit/components/windowwatcher/test/file_test_dialog.html b/toolkit/components/windowwatcher/test/file_test_dialog.html
new file mode 100644
index 0000000000..efb11d0077
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/file_test_dialog.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+This page is opened in a new window by test_dialog_arguments. It is
+a dialog which expects a Symbol to be passed in the dialog arguments.
+Once we load, we call back into the opener with the argument we were
+passed.
+-->
+<head>
+<body>Opened!</body>
+<script>
+ window.arguments[1].done(window.arguments[0]);
+</script>
+</html>
diff --git a/toolkit/components/windowwatcher/test/head.js b/toolkit/components/windowwatcher/test/head.js
new file mode 100644
index 0000000000..f8ab11a830
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/head.js
@@ -0,0 +1,207 @@
+/**
+ * Given some nsIDOMWindow for a window running in the parent
+ * process, return the nsIWebBrowserChrome chrome flags for
+ * the associated XUL window.
+ *
+ * @param win (nsIDOMWindow)
+ * Some window in the parent process.
+ * @returns int
+ */
+function getParentChromeFlags(win) {
+ return win.docShell.treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIAppWindow).chromeFlags;
+}
+
+const WINDOW_OPEN_FEATURES_PATTERNS = [
+ { features: "", popup: false },
+
+ // If features isn't empty, the following should be true to open tab/window:
+ // * location or toolbar (defaults to false)
+ // * menubar (defaults to false)
+ // * resizable (defaults to true)
+ // * scrollbars (defaults to false)
+ // * status (defaults to false)
+ { features: "location,menubar,resizable,scrollbars,status", popup: false },
+ { features: "toolbar,menubar,resizable,scrollbars,status", popup: false },
+
+ // resizable defaults to true.
+ { features: "location,menubar,scrollbars,status", popup: false },
+
+ // The following testcases use "location,menubar,scrollbars,status"
+ // as the base non-popup case, and test the boundary between popup
+ // vs non-popup.
+
+ // If either location or toolbar is true, not popup.
+ {
+ features: "toolbar,menubar,resizable,scrollbars,status",
+ popup: false,
+ },
+ {
+ features: "location,menubar,resizable,scrollbars,status",
+ popup: false,
+ },
+
+ // If both location and toolbar are false, popup.
+ { features: "menubar,scrollbars,status", popup: true },
+
+ // If menubar is false, popup.
+ { features: "location,resizable,scrollbars,status", popup: true },
+
+ // If resizable is true, not popup.
+ {
+ features: "location,menubar,resizable=yes,scrollbars,status",
+ popup: false,
+ },
+
+ // If resizable is false, popup.
+ { features: "location,menubar,resizable=0,scrollbars,status", popup: true },
+
+ // If scrollbars is false, popup.
+ { features: "location,menubar,resizable,status", popup: true },
+
+ // If status is false, popup.
+ { features: "location,menubar,resizable,scrollbars", popup: true },
+
+ // position and size have no effect.
+ {
+ features:
+ "location,menubar,scrollbars,status," +
+ "left=100,screenX=100,top=100,screenY=100," +
+ "width=100,innerWidth=100,outerWidth=100," +
+ "height=100,innerHeight=100,outerHeight=100",
+ popup: false,
+ },
+
+ // Most feature defaults to false if the feature is not empty.
+ // Specifying only some of them opens a popup.
+ { features: "location,toolbar,menubar", popup: true },
+ { features: "resizable,scrollbars,status", popup: true },
+
+ // Specifying unknown feature makes the feature not empty.
+ { features: "someunknownfeature", popup: true },
+
+ // noopener and noreferrer are removed before testing if feature is empty.
+ { features: "noopener,noreferrer", popup: false },
+];
+
+const WINDOW_CHROME_FLAGS = {
+ CHROME_WINDOW_BORDERS: true,
+ CHROME_WINDOW_CLOSE: true,
+ CHROME_WINDOW_RESIZE: true,
+ CHROME_LOCATIONBAR: true,
+ CHROME_STATUSBAR: true,
+ CHROME_SCROLLBARS: true,
+ CHROME_TITLEBAR: true,
+
+ CHROME_MENUBAR: true,
+ CHROME_TOOLBAR: true,
+ CHROME_PERSONAL_TOOLBAR: true,
+};
+
+const POPUP_CHROME_FLAGS = {
+ CHROME_WINDOW_BORDERS: true,
+ CHROME_WINDOW_CLOSE: true,
+ CHROME_WINDOW_RESIZE: true,
+ CHROME_LOCATIONBAR: true,
+ CHROME_STATUSBAR: true,
+ CHROME_SCROLLBARS: true,
+ CHROME_TITLEBAR: true,
+
+ CHROME_MENUBAR: false,
+ CHROME_TOOLBAR: false,
+ CHROME_PERSONAL_TOOLBAR: false,
+};
+
+async function testPopupPatterns(nonPopup) {
+ for (const { features, popup } of WINDOW_OPEN_FEATURES_PATTERNS) {
+ const BLANK_PAGE = "data:text/html,";
+ const OPEN_PAGE = "data:text/plain,hello";
+ const SCRIPT_PAGE = `data:text/html,<script>window.open("${OPEN_PAGE}", "", "${features}");</script>`;
+
+ async function testNewWindow(flags) {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: BLANK_PAGE,
+ },
+ async function (browser) {
+ const newWinPromise = BrowserTestUtils.waitForNewWindow();
+ BrowserTestUtils.loadURIString(gBrowser, SCRIPT_PAGE);
+
+ const win = await newWinPromise;
+ const parentChromeFlags = getParentChromeFlags(win);
+
+ for (const [name, visible] of Object.entries(flags)) {
+ if (visible) {
+ Assert.equal(
+ !!(parentChromeFlags & Ci.nsIWebBrowserChrome[name]),
+ true,
+ `${name} should be present for features "${features}"`
+ );
+ } else {
+ Assert.equal(
+ !!(parentChromeFlags & Ci.nsIWebBrowserChrome[name]),
+ false,
+ `${name} should not be present for features "${features}"`
+ );
+ }
+ }
+
+ await BrowserTestUtils.closeWindow(win);
+ }
+ );
+ }
+
+ async function testNewTab() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: BLANK_PAGE,
+ },
+ async function (browser) {
+ const newTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ OPEN_PAGE
+ );
+ BrowserTestUtils.loadURIString(gBrowser, SCRIPT_PAGE);
+
+ let tab = await newTabPromise;
+ BrowserTestUtils.removeTab(tab);
+ }
+ );
+ }
+
+ async function testCurrentTab() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: BLANK_PAGE,
+ },
+ async function (browser) {
+ const pagePromise = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ OPEN_PAGE
+ );
+ BrowserTestUtils.loadURIString(gBrowser, SCRIPT_PAGE);
+
+ await pagePromise;
+ }
+ );
+ }
+
+ if (!popup) {
+ if (nonPopup == "window") {
+ await testNewWindow(WINDOW_CHROME_FLAGS);
+ } else if (nonPopup == "tab") {
+ await testNewTab();
+ } else {
+ // current tab
+ await testCurrentTab();
+ }
+ } else {
+ await testNewWindow(POPUP_CHROME_FLAGS);
+ }
+ }
+}
diff --git a/toolkit/components/windowwatcher/test/mochitest.ini b/toolkit/components/windowwatcher/test/mochitest.ini
new file mode 100644
index 0000000000..e75b5dbb15
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/mochitest.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+tags = openwindow
+
+[test_blank_named_window.html]
+skip-if = (os == 'android') # Fennec doesn't support web content opening new windows (See bug 1277544 for details)
+[test_named_window.html]
+support-files =
+ file_named_window.html
+skip-if = (os == 'android') # Fennec doesn't support web content opening new windows (See bug 1277544 for details)
+[test_storage_copied.html]
+support-files =
+ file_storage_copied.html
+skip-if = (os == 'android') # Fennec doesn't support web content opening new windows (See bug 1277544 for details)
+
diff --git a/toolkit/components/windowwatcher/test/moz.build b/toolkit/components/windowwatcher/test/moz.build
new file mode 100644
index 0000000000..7b61855c01
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += [
+ "browser.ini",
+]
+
+MOCHITEST_MANIFESTS += [
+ "mochitest.ini",
+]
+
+MOCHITEST_CHROME_MANIFESTS += [
+ "chrome.ini",
+]
diff --git a/toolkit/components/windowwatcher/test/test_alwaysOnTop_windows.html b/toolkit/components/windowwatcher/test/test_alwaysOnTop_windows.html
new file mode 100644
index 0000000000..bc2b80cbfd
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/test_alwaysOnTop_windows.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests the alwaysOnTop window feature for the Windows OS.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test an alwaysOnTop window on Windows</title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+
+ <script type="application/javascript">
+
+ const {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+ const {ctypes} = ChromeUtils.import("resource://gre/modules/ctypes.jsm");
+
+ function assertAlwaysOnTop(win, expected) {
+ let docShell = win.docShell;
+ let chromeFlags = docShell.treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIAppWindow)
+ .chromeFlags;
+ let hasFlag = !!(chromeFlags & Ci.nsIWebBrowserChrome.CHROME_ALWAYS_ON_TOP);
+ is(hasFlag, expected, "Window should have CHROME_ALWAYS_ON_TOP flag.");
+
+ const hWND = Number(win.docShell.treeOwner.nsIBaseWindow.nativeHandle);
+ const WS_EX_TOPMOST = 0x00000008;
+ const GWL_EXSTYLE = -20;
+
+ let lib = ctypes.open("user32.dll");
+ // On 32-bit systems, the function we need to call is GetWindowLongW. On
+ // 64-bit systems, the function is GetWindowLongPtrW. Interestingly,
+ // the MSDN page[1] for GetWindowLongPtrW claims that calling it should work
+ // on both 32-bit and 64-bit, but that didn't appear to be the case here
+ // with local testing, hence the conditional.
+ //
+ // [1]: https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowlongptrw
+ let GetWindowFuncSymbol = Services.appinfo.is64Bit ? "GetWindowLongPtrW"
+ : "GetWindowLongW";
+ let GetWindowFunc = lib.declare(GetWindowFuncSymbol, ctypes.winapi_abi,
+ ctypes.intptr_t, ctypes.uintptr_t,
+ ctypes.int);
+
+ let styles = GetWindowFunc(hWND, GWL_EXSTYLE);
+ let isAlwaysOnTop = !!(styles & WS_EX_TOPMOST);
+ is(isAlwaysOnTop, expected, "Window should be always on top.");
+ lib.close();
+ }
+
+ if (Services.appinfo.OS != "WINNT") {
+ ok(false, "This test is only designed to run on Windows.");
+ } else {
+ add_task(async function() {
+ let normalWinOpened = BrowserTestUtils.domWindowOpenedAndLoaded();
+ window.openDialog("about:blank",
+ "_blank", "chrome,width=100,height=100,noopener", null);
+ let normalWin = await normalWinOpened;
+ ok(normalWin, "Normal window opened");
+ assertAlwaysOnTop(normalWin, false);
+ await BrowserTestUtils.closeWindow(normalWin);
+
+ let alwaysOnTopWinOpened = BrowserTestUtils.domWindowOpenedAndLoaded();
+ window.openDialog("about:blank",
+ "_blank", "chrome,width=100,height=100,alwaysOnTop,noopener", null);
+ let alwaysOnTopWin = await alwaysOnTopWinOpened;
+ ok(alwaysOnTopWin, "AlwaysOnTop window opened");
+ assertAlwaysOnTop(alwaysOnTopWin, true);
+ await BrowserTestUtils.closeWindow(alwaysOnTopWin);
+ });
+ }
+
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/toolkit/components/windowwatcher/test/test_blank_named_window.html b/toolkit/components/windowwatcher/test/test_blank_named_window.html
new file mode 100644
index 0000000000..f8472d216a
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/test_blank_named_window.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that when opening a window with the reserved name _blank that the new
+window does not get that name, and that subsequent window openings with that
+name result in new windows being opened.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test named windows</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js" type="application/javascript"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script type="application/javascript">
+ "use strict";
+ add_task(async function() {
+ // This magic value of 2 means that by default, when content tries
+ // to open a new window, it'll actually open in a new window instead
+ // of a new tab.
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["browser.link.open_newwindow", 2],
+ ]});
+
+ let win1 = window.open("data:text/html,<p>This is window 1 for test_blank_named_window.html</p>", "_blank");
+
+ let name = SpecialPowers.wrap(win1).docShell.name;
+
+ is(name, "", "Should have no name");
+
+ let win2 = window.open("data:text/html,<p>This is window 2 for test_blank_named_window.html</p>", "_blank");
+ isnot(win1, win2, "Should not have gotten back the same window");
+
+ win1.close();
+ win2.close();
+ });
+ </script>
+</body>
+</html>
diff --git a/toolkit/components/windowwatcher/test/test_dialog_arguments.html b/toolkit/components/windowwatcher/test/test_dialog_arguments.html
new file mode 100644
index 0000000000..6e937c6d01
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/test_dialog_arguments.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that arguments can be passed to dialogs.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test a modal window</title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+
+ <script type="application/javascript">
+
+ const TEST_ITEM = Symbol("test-item");
+
+ function done(returnedItem) {
+ is(returnedItem, TEST_ITEM,
+ "Dialog should have received test item");
+ win.close();
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ let win = window.browsingContext.topChromeWindow.openDialog("file_test_dialog.html", "_blank", "width=100,height=100", TEST_ITEM, window);
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/toolkit/components/windowwatcher/test/test_modal_windows.html b/toolkit/components/windowwatcher/test/test_modal_windows.html
new file mode 100644
index 0000000000..88e55a7099
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/test_modal_windows.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that the parent can open modal windows, and that the modal window
+that is opened reports itself as being modal.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test a modal window</title>
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+
+ <script type="application/javascript">
+
+ const {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+
+ add_task(async function() {
+ BrowserTestUtils.domWindowOpened().then((win) => {
+ let treeOwner = win.docShell.treeOwner;
+ let chromeFlags = treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIAppWindow)
+ .chromeFlags;
+ ok(chromeFlags & Ci.nsIWebBrowserChrome.CHROME_MODAL,
+ "Should have the modal chrome flag");
+
+ let wbc = treeOwner.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebBrowserChrome);
+ ok(wbc.isWindowModal(), "Should report as modal");
+
+ win.close();
+ });
+
+ window.openDialog("data:text/html,<p>This is a modal window for test_modal_windows.html</p>",
+ "_blank", "modal,noopener", null);
+ // Since the modal runs a nested event loop, just to be on the safe side,
+ // we'll wait a tick of the main event loop before resolving the task.
+ await new Promise(resolve => setTimeout(resolve, 0));
+ });
+
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/toolkit/components/windowwatcher/test/test_named_window.html b/toolkit/components/windowwatcher/test/test_named_window.html
new file mode 100644
index 0000000000..af3536dd5e
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/test_named_window.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that when content opens a new window with a name, that the
+newly opened window actually gets that name, and that subsequent
+attempts to open a window with that name will target the same
+window.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test named windows</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js" type="application/javascript"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <a href="#" id="link">Click me</a>
+
+ <script type="application/javascript">
+ "use strict";
+
+ const NAME = "my_window";
+ const TARGET_URL = location.href.replace("test_named_window.html",
+ "file_named_window.html");
+ const TARGET_URL_2 = TARGET_URL + "#2";
+ const TARGET_URL_3 = TARGET_URL + "#3";
+
+ /**
+ * Returns a Promise that resolves once some target has had
+ * some event dispatched on it.
+ *
+ * @param target
+ * The thing to wait for the event to be dispatched
+ * through.
+ * @param eventName
+ * The name of the event to wait for.
+ * @returns Promise
+ */
+ function promiseEvent(target, eventName) {
+ return new Promise(resolve => {
+ target.addEventListener(eventName, function(e) {
+ resolve(e);
+ }, {capture: true, once: true});
+ });
+ }
+
+ add_task(async function() {
+ // This magic value of 2 means that by default, when content tries
+ // to open a new window, it'll actually open in a new window instead
+ // of a new tab.
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["browser.link.open_newwindow", 2],
+ ]});
+
+ let win1 = window.open(TARGET_URL, "my_window");
+ await promiseEvent(win1, "load");
+
+ let name = SpecialPowers.wrap(win1).docShell.name;
+
+ is(name, NAME, "Should have the expected name");
+ is(win1.location.href, new URL(TARGET_URL).href,
+ "Should have loaded target TARGET_URL in the original window");
+
+ let hashChange = promiseEvent(win1, "hashchange");
+ let win2 = window.open(TARGET_URL_2, "my_window");
+ await hashChange;
+
+ is(win1, win2, "Should have gotten back the same window");
+ is(win1.location.href, new URL(TARGET_URL_2).href,
+ "Should have re-targeted pre-existing window");
+
+ hashChange = promiseEvent(win1, "hashchange");
+ let link = document.getElementById("link");
+ link.setAttribute("target", NAME);
+ link.setAttribute("href", TARGET_URL_3);
+ link.click();
+
+ await hashChange;
+
+ is(win1.location.href, new URL(TARGET_URL_3).href,
+ "Should have re-targeted pre-existing window");
+
+ win1.close();
+ });
+ </script>
+</body>
+</html>
diff --git a/toolkit/components/windowwatcher/test/test_storage_copied.html b/toolkit/components/windowwatcher/test/test_storage_copied.html
new file mode 100644
index 0000000000..de05cd6855
--- /dev/null
+++ b/toolkit/components/windowwatcher/test/test_storage_copied.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test sessionStorage is copied over when a new window opens to the
+same domain as the opener.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test storage copied</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="head.js" type="application/javascript"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script type="application/javascript">
+ "use strict";
+
+ function waitForMessage(win) {
+ return new Promise(resolve => {
+ win.addEventListener("message", function(event) {
+ resolve(event.data);
+ }, {once: true});
+ });
+ }
+
+ add_task(async function() {
+ const TEST_VALUE = "test-value";
+ // This magic value of 2 means that by default, when content tries
+ // to open a new window, it'll actually open in a new window instead
+ // of a new tab.
+ await SpecialPowers.pushPrefEnv({"set": [
+ ["browser.link.open_newwindow", 2],
+ ]});
+
+ window.sessionStorage.setItem("test-item", TEST_VALUE);
+ let win = window.open("file_storage_copied.html", "my_window");
+ let data = await waitForMessage(win);
+ is(data, TEST_VALUE, "Should have cloned the test value");
+ win.close();
+ });
+ </script>
+</body>
+</html>