From f66babd8b8368ada3e5aa29cdef1c77291ee4ddd Mon Sep 17 00:00:00 2001 From: Moonchild Date: Sat, 12 Feb 2022 17:47:03 +0000 Subject: Create the Goanna Runtime Environment --- components/windowwatcher/moz.build | 39 + .../windowwatcher/public/nsIDialogParamBlock.idl | 42 + .../windowwatcher/public/nsIPromptFactory.idl | 22 + .../windowwatcher/public/nsIPromptService.idl | 346 +++ .../windowwatcher/public/nsIPromptService2.idl | 45 + .../windowwatcher/public/nsIWindowWatcher.idl | 167 ++ .../windowwatcher/public/nsPIPromptService.idl | 31 + .../windowwatcher/public/nsPIWindowWatcher.idl | 146 ++ .../windowwatcher/src/nsAutoWindowStateHelper.cpp | 72 + .../windowwatcher/src/nsAutoWindowStateHelper.h | 34 + .../windowwatcher/src/nsDialogParamBlock.cpp | 100 + components/windowwatcher/src/nsDialogParamBlock.h | 44 + components/windowwatcher/src/nsPromptUtils.h | 140 ++ components/windowwatcher/src/nsWindowWatcher.cpp | 2576 ++++++++++++++++++++ components/windowwatcher/src/nsWindowWatcher.h | 155 ++ 15 files changed, 3959 insertions(+) create mode 100644 components/windowwatcher/moz.build create mode 100644 components/windowwatcher/public/nsIDialogParamBlock.idl create mode 100644 components/windowwatcher/public/nsIPromptFactory.idl create mode 100644 components/windowwatcher/public/nsIPromptService.idl create mode 100644 components/windowwatcher/public/nsIPromptService2.idl create mode 100644 components/windowwatcher/public/nsIWindowWatcher.idl create mode 100644 components/windowwatcher/public/nsPIPromptService.idl create mode 100644 components/windowwatcher/public/nsPIWindowWatcher.idl create mode 100644 components/windowwatcher/src/nsAutoWindowStateHelper.cpp create mode 100644 components/windowwatcher/src/nsAutoWindowStateHelper.h create mode 100644 components/windowwatcher/src/nsDialogParamBlock.cpp create mode 100644 components/windowwatcher/src/nsDialogParamBlock.h create mode 100644 components/windowwatcher/src/nsPromptUtils.h create mode 100644 components/windowwatcher/src/nsWindowWatcher.cpp create mode 100644 components/windowwatcher/src/nsWindowWatcher.h (limited to 'components/windowwatcher') diff --git a/components/windowwatcher/moz.build b/components/windowwatcher/moz.build new file mode 100644 index 000000000..096f39cbd --- /dev/null +++ b/components/windowwatcher/moz.build @@ -0,0 +1,39 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# 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('/ipc/chromium/chromium-config.mozbuild') + +XPIDL_SOURCES += [ + 'public/nsIDialogParamBlock.idl', + 'public/nsIPromptFactory.idl', + 'public/nsIPromptService.idl', + 'public/nsIPromptService2.idl', + 'public/nsIWindowWatcher.idl', + 'public/nsPIPromptService.idl', + 'public/nsPIWindowWatcher.idl', +] + +EXPORTS += [ + 'src/nsPromptUtils.h', + 'src/nsWindowWatcher.h', +] + +SOURCES += [ + 'src/nsAutoWindowStateHelper.cpp', + 'src/nsDialogParamBlock.cpp', + 'src/nsWindowWatcher.cpp', +] + +# For nsJSUtils +LOCAL_INCLUDES += [ + '/dom/base', + '/system/docshell/base', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] + +XPIDL_MODULE = 'windowwatcher' +FINAL_LIBRARY = 'xul' \ No newline at end of file diff --git a/components/windowwatcher/public/nsIDialogParamBlock.idl b/components/windowwatcher/public/nsIDialogParamBlock.idl new file mode 100644 index 000000000..d917776ec --- /dev/null +++ b/components/windowwatcher/public/nsIDialogParamBlock.idl @@ -0,0 +1,42 @@ +/* -*- 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/components/windowwatcher/public/nsIPromptFactory.idl b/components/windowwatcher/public/nsIPromptFactory.idl new file mode 100644 index 000000000..b16db3d49 --- /dev/null +++ b/components/windowwatcher/public/nsIPromptFactory.idl @@ -0,0 +1,22 @@ +/* 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/components/windowwatcher/public/nsIPromptService.idl b/components/windowwatcher/public/nsIPromptService.idl new file mode 100644 index 000000000..24c75e3a9 --- /dev/null +++ b/components/windowwatcher/public/nsIPromptService.idl @@ -0,0 +1,346 @@ +/* -*- 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; + +/** + * 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); + + /** + * 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); + + /** + * 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); + + /** + * 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); + + /** + * 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); + + + /** + * 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); + + /** + * 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); + + /** + * Puts up a dialog with an edit field, a password 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 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. + * @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 promptUsernameAndPassword(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText, + inout wstring aUsername, + inout wstring aPassword, + in wstring aCheckMsg, + inout boolean aCheckState); + + /** + * Puts up a dialog with a password 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 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. + * @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 promptPassword(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText, + inout wstring aPassword, + in wstring aCheckMsg, + inout boolean aCheckState); + + /** + * 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 aCount + * The length of the aSelectList array parameter. + * @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 uint32_t aCount, + [array, size_is(aCount)] in wstring aSelectList, + out long aOutSelection); +}; diff --git a/components/windowwatcher/public/nsIPromptService2.idl b/components/windowwatcher/public/nsIPromptService2.idl new file mode 100644 index 000000000..4afa0975b --- /dev/null +++ b/components/windowwatcher/public/nsIPromptService2.idl @@ -0,0 +1,45 @@ +/* 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 "nsIPromptService.idl" + +interface nsIAuthInformation; +interface nsIAuthPromptCallback; +interface nsICancelable; +interface nsIChannel; +interface mozIDOMWindowProxy; + +/** + * This is an improved version of nsIPromptService that is less prescriptive + * about the resulting user interface. + * + * @status INCOMPLETE do not freeze before fixing bug 228207 + */ +[scriptable, uuid(3775ad32-8326-422b-9ff3-87ef1d3f9f0e)] +interface nsIPromptService2 : nsIPromptService { + // NOTE: These functions differ from their nsIAuthPrompt counterparts by + // having additional checkbox parameters + // checkValue can be null meaning to show no checkbox + // checkboxLabel is a wstring so that it can be null from both JS and C++ in + // a convenient way + // + // 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, + in wstring checkboxLabel, + inout boolean checkValue); + + nsICancelable asyncPromptAuth(in mozIDOMWindowProxy aParent, + in nsIChannel aChannel, + in nsIAuthPromptCallback aCallback, + in nsISupports aContext, + in uint32_t level, + in nsIAuthInformation authInfo, + in wstring checkboxLabel, + inout boolean checkValue); +}; + diff --git a/components/windowwatcher/public/nsIWindowWatcher.idl b/components/windowwatcher/public/nsIWindowWatcher.idl new file mode 100644 index 000000000..deea268c2 --- /dev/null +++ b/components/windowwatcher/public/nsIWindowWatcher.idl @@ -0,0 +1,167 @@ +/* -*- 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 string aUrl, + in string aName, in string 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 window (or frame). + @param aTargetName the window name + @param aCurrentWindow a starting point in the window hierarchy to + begin the search. If null, each toplevel window + will be searched. + + 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, + in mozIDOMWindowProxy aCurrentWindow); + + /** The Watcher serves as a global storage facility for the current active + (frontmost non-floating-palette-type) window, storing and returning + it on demand. Users must keep this attribute current, including after + the topmost window is closed. This attribute obviously can return null + if no windows are open, but should otherwise always return a valid + window. + */ + attribute mozIDOMWindowProxy activeWindow; + +}; + +%{C++ +#define NS_WINDOWWATCHER_CONTRACTID "@mozilla.org/embedcomp/window-watcher;1" +%} diff --git a/components/windowwatcher/public/nsPIPromptService.idl b/components/windowwatcher/public/nsPIPromptService.idl new file mode 100644 index 000000000..32459b26b --- /dev/null +++ b/components/windowwatcher/public/nsPIPromptService.idl @@ -0,0 +1,31 @@ +/* -*- 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/components/windowwatcher/public/nsPIWindowWatcher.idl b/components/windowwatcher/public/nsPIWindowWatcher.idl new file mode 100644 index 000000000..47b243364 --- /dev/null +++ b/components/windowwatcher/public/nsPIWindowWatcher.idl @@ -0,0 +1,146 @@ +/* -*- 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" + +interface mozIDOMWindowProxy; +interface nsIDOMWindow; +interface nsISimpleEnumerator; +interface nsIWebBrowserChrome; +interface nsIDocShellTreeItem; +interface nsIArray; +interface nsITabParent; +interface nsIDocShellLoadInfo; + +[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); + + /** 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 nsIDOMWindow::openDialog) + @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 aLoadInfo if aNavigate is true, this allows the caller to pass in + an nsIDocShellLoadInfo 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. + */ + mozIDOMWindowProxy openWindow2(in mozIDOMWindowProxy aParent, in string aUrl, + in string aName, in string aFeatures, + in boolean aCalledFromScript, + in boolean aDialog, + in boolean aNavigate, + in nsISupports aArgs, + in boolean aIsPopupSpam, + in boolean aForceNoOpener, + in nsIDocShellLoadInfo aLoadInfo); + + /** + * Opens a new window using the most recent non-private browser + * window as its parent. + * + * @return the nsITabParent of the initial browser for the newly opened + * window. + */ + nsITabParent openWindowWithoutParent(); + + /** + * 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 nsITabParent 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. + * + * @return the nsITabParent of the initial browser for the newly opened + * window. + */ + nsITabParent openWindowWithTabParent(in nsITabParent aOpeningTab, + in ACString aFeatures, + in boolean aCalledFromJS, + in float aOpenerFullZoom); + + /** + * Find a named docshell tree item amongst all windows registered + * with the window watcher. This may be a subframe in some window, + * for example. + * + * @param aName the name of the window. Must not be null. + * @param aRequestor the tree item immediately making the request. + * We should make sure to not recurse down into its findItemWithName + * method. + * @param aOriginalRequestor the original treeitem that made the request. + * Used for security checks. + * @return the tree item with aName as the name, or null if there + * isn't one. "Special" names, like _self, _top, etc, will be + * treated specially only if aRequestor is null; in that case they + * will be resolved relative to the first window the windowwatcher + * knows about. + * @see findItemWithName methods on nsIDocShellTreeItem and + * nsIDocShellTreeOwner + */ + nsIDocShellTreeItem findItemWithName(in AString aName, + in nsIDocShellTreeItem aRequestor, + in nsIDocShellTreeItem aOriginalRequestor); +}; + diff --git a/components/windowwatcher/src/nsAutoWindowStateHelper.cpp b/components/windowwatcher/src/nsAutoWindowStateHelper.cpp new file mode 100644 index 000000000..e7c66e83a --- /dev/null +++ b/components/windowwatcher/src/nsAutoWindowStateHelper.cpp @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAutoWindowStateHelper.h" + +#include "mozilla/dom/Event.h" +#include "nsIDocument.h" +#include "nsIDOMEvent.h" +#include "nsIDOMWindow.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. + nsIDocument* doc = mWindow->GetExtantDoc(); + if (!doc) { + return true; + } + + ErrorResult rv; + RefPtr event = doc->CreateEvent(NS_LITERAL_STRING("Events"), rv); + if (rv.Failed()) { + rv.SuppressException(); + return false; + } + event->InitEvent(NS_ConvertASCIItoUTF16(aEventName), true, true); + event->SetTrusted(true); + event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; + + nsCOMPtr target = do_QueryInterface(mWindow); + bool defaultActionEnabled; + target->DispatchEvent(event, &defaultActionEnabled); + return defaultActionEnabled; +} diff --git a/components/windowwatcher/src/nsAutoWindowStateHelper.h b/components/windowwatcher/src/nsAutoWindowStateHelper.h new file mode 100644 index 000000000..c23c637d7 --- /dev/null +++ b/components/windowwatcher/src/nsAutoWindowStateHelper.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#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 mWindow; + bool mDefaultEnabled; +}; + +#endif diff --git a/components/windowwatcher/src/nsDialogParamBlock.cpp b/components/windowwatcher/src/nsDialogParamBlock.cpp new file mode 100644 index 000000000..573082db1 --- /dev/null +++ b/components/windowwatcher/src/nsDialogParamBlock.cpp @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "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/components/windowwatcher/src/nsDialogParamBlock.h b/components/windowwatcher/src/nsDialogParamBlock.h new file mode 100644 index 000000000..a34ba251f --- /dev/null +++ b/components/windowwatcher/src/nsDialogParamBlock.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef __nsDialogParamBlock_h +#define __nsDialogParamBlock_h + +#include "nsIDialogParamBlock.h" +#include "nsIMutableArray.h" +#include "nsCOMPtr.h" + +// {4E4AAE11-8901-46cc-8217-DAD7C5415873} +#define NS_DIALOGPARAMBLOCK_CID \ + {0x4e4aae11, 0x8901, 0x46cc, {0x82, 0x17, 0xda, 0xd7, 0xc5, 0x41, 0x58, 0x73}} + +class nsString; + +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 mObjects; +}; + +#endif diff --git a/components/windowwatcher/src/nsPromptUtils.h b/components/windowwatcher/src/nsPromptUtils.h new file mode 100644 index 000000000..80589b9cb --- /dev/null +++ b/components/windowwatcher/src/nsPromptUtils.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#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 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 proxied(do_QueryInterface(aChannel)); + NS_ASSERTION(proxied, "proxy auth needs nsIProxiedChannel"); + + nsCOMPtr info; + proxied->GetProxyInfo(getter_AddRefs(info)); + NS_ASSERTION(info, "proxy auth needs nsIProxyInfo"); + + nsAutoCString idnhost; + info->GetHost(idnhost); + info->GetPort(aPort); + + if (aMachineProcessing) { + nsCOMPtr 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 http(do_QueryInterface(aChannel)); + if (!http) { + nsCOMPtr 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/components/windowwatcher/src/nsWindowWatcher.cpp b/components/windowwatcher/src/nsWindowWatcher.cpp new file mode 100644 index 000000000..bd931233b --- /dev/null +++ b/components/windowwatcher/src/nsWindowWatcher.cpp @@ -0,0 +1,2576 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +//#define USEWEAKREFS // (haven't quite figured that out yet) + +#include "nsWindowWatcher.h" +#include "nsAutoWindowStateHelper.h" + +#include "nsCRT.h" +#include "nsNetUtil.h" +#include "nsIAuthPrompt.h" +#include "nsIAuthPrompt2.h" +#include "nsISimpleEnumerator.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsJSUtils.h" +#include "plstr.h" + +#include "nsDocShell.h" +#include "nsGlobalWindow.h" +#include "nsIBaseWindow.h" +#include "nsIBrowserDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIDocShellLoadInfo.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIDocumentLoader.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIDOMWindow.h" +#include "nsIDOMChromeWindow.h" +#include "nsIDOMModalContentWindow.h" +#include "nsIPrompt.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScreen.h" +#include "nsIScreenManager.h" +#include "nsIScriptContext.h" +#include "nsIObserverService.h" +#include "nsIScriptSecurityManager.h" +#include "nsXPCOM.h" +#include "nsIURI.h" +#include "nsIWebBrowser.h" +#include "nsIWebBrowserChrome.h" +#include "nsIWebNavigation.h" +#include "nsIWindowCreator.h" +#include "nsIWindowCreator2.h" +#include "nsIXPConnect.h" +#include "nsIXULRuntime.h" +#include "nsPIDOMWindow.h" +#include "nsIContentViewer.h" +#include "nsIWindowProvider.h" +#include "nsIMutableArray.h" +#include "nsIDOMStorageManager.h" +#include "nsIWidget.h" +#include "nsFocusManager.h" +#include "nsIPresShell.h" +#include "nsPresContext.h" +#include "nsContentUtils.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsSandboxFlags.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/DOMStorage.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/TabParent.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/TabGroup.h" +#include "nsIXULWindow.h" +#include "nsIXULBrowserWindow.h" +#include "nsGlobalWindow.h" + +#ifdef USEWEAKREFS +#include "nsIWeakReference.h" +#endif + +using namespace mozilla; +using namespace mozilla::dom; + +/**************************************************************** + ******************** nsWatcherWindowEntry ********************** + ****************************************************************/ + +class nsWindowWatcher; + +struct nsWatcherWindowEntry +{ + + nsWatcherWindowEntry(mozIDOMWindowProxy* aWindow, nsIWebBrowserChrome* aChrome) + : mChrome(nullptr) + { +#ifdef USEWEAKREFS + mWindow = do_GetWeakReference(aWindow); +#else + mWindow = aWindow; +#endif + nsCOMPtr supportsweak(do_QueryInterface(aChrome)); + if (supportsweak) { + supportsweak->GetWeakReference(getter_AddRefs(mChromeWeak)); + } else { + mChrome = aChrome; + mChromeWeak = nullptr; + } + ReferenceSelf(); + } + ~nsWatcherWindowEntry() {} + + void InsertAfter(nsWatcherWindowEntry* aOlder); + void Unlink(); + void ReferenceSelf(); + +#ifdef USEWEAKREFS + nsCOMPtr mWindow; +#else // still not an owning ref + mozIDOMWindowProxy* mWindow; +#endif + 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 nsISimpleEnumerator +{ + +public: + explicit nsWatcherWindowEnumerator(nsWindowWatcher* aWatcher); + NS_IMETHOD HasMoreElements(bool* aResult) override; + NS_IMETHOD GetNext(nsISupports** aResult) override; + + NS_DECL_ISUPPORTS + +protected: + virtual ~nsWatcherWindowEnumerator(); + +private: + friend class nsWindowWatcher; + + nsWatcherWindowEntry* FindNext(); + void WindowRemoved(nsWatcherWindowEntry* aInfo); + + nsWindowWatcher* mWindowWatcher; + nsWatcherWindowEntry* mCurrentPosition; +}; + +NS_IMPL_ADDREF(nsWatcherWindowEnumerator) +NS_IMPL_RELEASE(nsWatcherWindowEnumerator) +NS_IMPL_QUERY_INTERFACE(nsWatcherWindowEnumerator, nsISimpleEnumerator) + +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; + +#ifdef USEWEAKREFS + while (mCurrentPosition) { + CallQueryReferent(mCurrentPosition->mWindow, aResult); + if (*aResult) { + mCurrentPosition = FindNext(); + break; + } else { // window is gone! + mWindowWatcher->RemoveWindow(mCurrentPosition); + } + } + NS_IF_ADDREF(*aResult); +#else + if (mCurrentPosition) { + CallQueryInterface(mCurrentPosition->mWindow, aResult); + mCurrentPosition = FindNext(); + } +#endif + return NS_OK; +} + +nsWatcherWindowEntry* +nsWatcherWindowEnumerator::FindNext() +{ + nsWatcherWindowEntry* info; + + if (!mCurrentPosition) { + return 0; + } + + info = mCurrentPosition->mYounger; + return info == mWindowWatcher->mOldestWindow ? 0 : info; +} + +// if a window is being removed adjust the iterator's current position +void +nsWatcherWindowEnumerator::WindowRemoved(nsWatcherWindowEntry* aInfo) +{ + + if (mCurrentPosition == aInfo) { + mCurrentPosition = + mCurrentPosition != aInfo->mYounger ? aInfo->mYounger : 0; + } +} + +/**************************************************************** + *********************** nsWindowWatcher ************************ + ****************************************************************/ + +NS_IMPL_ADDREF(nsWindowWatcher) +NS_IMPL_RELEASE(nsWindowWatcher) +NS_IMPL_QUERY_INTERFACE(nsWindowWatcher, + nsIWindowWatcher, + nsIPromptFactory, + nsPIWindowWatcher) + +nsWindowWatcher::nsWindowWatcher() + : mEnumeratorList() + , mOldestWindow(0) + , mListLock("nsWindowWatcher.mListLock") +{ +} + +nsWindowWatcher::~nsWindowWatcher() +{ + // delete data + while (mOldestWindow) { + RemoveWindow(mOldestWindow); + } +} + +nsresult +nsWindowWatcher::Init() +{ + return NS_OK; +} + +/** + * Convert aArguments into either an nsIArray or nullptr. + * + * - If aArguments is nullptr, return nullptr. + * - If aArguments is an nsArray, return nullptr if it's empty, or otherwise + * return the array. + * - If aArguments is an nsIArray, return nullptr if it's empty, or + * otherwise just return the array. + * - Otherwise, return an nsIArray with one element: aArguments. + */ +static already_AddRefed +ConvertArgsToArray(nsISupports* aArguments) +{ + if (!aArguments) { + return nullptr; + } + + nsCOMPtr array = do_QueryInterface(aArguments); + if (array) { + uint32_t argc = 0; + array->GetLength(&argc); + if (argc == 0) { + return nullptr; + } + + return array.forget(); + } + + nsCOMPtr singletonArray = + do_CreateInstance(NS_ARRAY_CONTRACTID); + NS_ENSURE_TRUE(singletonArray, nullptr); + + nsresult rv = singletonArray->AppendElement(aArguments, /* aWeak = */ false); + NS_ENSURE_SUCCESS(rv, nullptr); + + return singletonArray.forget(); +} + +NS_IMETHODIMP +nsWindowWatcher::OpenWindow(mozIDOMWindowProxy* aParent, + const char* aUrl, + const char* aName, + const char* aFeatures, + nsISupports* aArguments, + mozIDOMWindowProxy** aResult) +{ + nsCOMPtr argv = ConvertArgsToArray(aArguments); + + uint32_t argc = 0; + if (argv) { + argv->GetLength(&argc); + } + bool dialog = (argc != 0); + + return OpenWindowInternal(aParent, aUrl, aName, aFeatures, + /* calledFromJS = */ false, dialog, + /* navigate = */ true, argv, + /* aIsPopupSpam = */ false, + /* aForceNoOpener = */ false, + /* aLoadInfo = */ nullptr, + aResult); +} + +struct SizeSpec +{ + SizeSpec() + : mLeft(0) + , mTop(0) + , mOuterWidth(0) + , mOuterHeight(0) + , mInnerWidth(0) + , mInnerHeight(0) + , mLeftSpecified(false) + , mTopSpecified(false) + , mOuterWidthSpecified(false) + , mOuterHeightSpecified(false) + , mInnerWidthSpecified(false) + , mInnerHeightSpecified(false) + , mUseDefaultWidth(false) + , mUseDefaultHeight(false) + { + } + + int32_t mLeft; + int32_t mTop; + int32_t mOuterWidth; // Total window width + int32_t mOuterHeight; // Total window height + int32_t mInnerWidth; // Content area width + int32_t mInnerHeight; // Content area height + + bool mLeftSpecified; + bool mTopSpecified; + bool mOuterWidthSpecified; + bool mOuterHeightSpecified; + bool mInnerWidthSpecified; + bool mInnerHeightSpecified; + + // If these booleans are true, don't look at the corresponding width values + // even if they're specified -- they'll be bogus + bool mUseDefaultWidth; + bool mUseDefaultHeight; + + bool PositionSpecified() const + { + return mLeftSpecified || mTopSpecified; + } + + bool SizeSpecified() const + { + return mOuterWidthSpecified || mOuterHeightSpecified || + mInnerWidthSpecified || mInnerHeightSpecified; + } +}; + +NS_IMETHODIMP +nsWindowWatcher::OpenWindow2(mozIDOMWindowProxy* aParent, + const char* aUrl, + const char* aName, + const char* aFeatures, + bool aCalledFromScript, + bool aDialog, + bool aNavigate, + nsISupports* aArguments, + bool aIsPopupSpam, + bool aForceNoOpener, + nsIDocShellLoadInfo* aLoadInfo, + mozIDOMWindowProxy** aResult) +{ + nsCOMPtr 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, aLoadInfo, 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(aDocShell)->GetOriginAttributes().mUserContextId; + + nsCOMPtr 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 (nsContentUtils::IsSystemPrincipal(subjectPrincipal)) { + return true; + } + + return subjectPrincipal->GetUserContextId() == userContextId; +} + +NS_IMETHODIMP +nsWindowWatcher::OpenWindowWithoutParent(nsITabParent** aResult) +{ + return OpenWindowWithTabParent(nullptr, EmptyCString(), true, 1.0f, aResult); +} + +nsresult +nsWindowWatcher::CreateChromeWindow(const nsACString& aFeatures, + nsIWebBrowserChrome* aParentChrome, + uint32_t aChromeFlags, + uint32_t aContextFlags, + nsITabParent* aOpeningTabParent, + mozIDOMWindowProxy* aOpener, + nsIWebBrowserChrome** aResult) +{ + nsCOMPtr windowCreator2(do_QueryInterface(mWindowCreator)); + if (NS_WARN_IF(!windowCreator2)) { + return NS_ERROR_UNEXPECTED; + } + + bool cancel = false; + nsCOMPtr newWindowChrome; + nsresult rv = + windowCreator2->CreateChromeWindow2(aParentChrome, aChromeFlags, aContextFlags, + aOpeningTabParent, aOpener, &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 string that was used to open the window. + * @param aTreeOwner + * The nsIDocShellTreeOwner of the newly opened window. If null, + * this function is a no-op. + */ +void +nsWindowWatcher::MaybeDisablePersistence(const nsACString& aFeatures, + nsIDocShellTreeOwner* aTreeOwner) +{ + if (!aTreeOwner) { + return; + } + + // At the moment, the strings "height=" or "width=" never happen + // outside a size specification, so we can do this the Q&D way. + if (PL_strcasestr(aFeatures.BeginReading(), "width=") || + PL_strcasestr(aFeatures.BeginReading(), "height=")) { + aTreeOwner->SetPersistence(false, false, false); + } +} + +NS_IMETHODIMP +nsWindowWatcher::OpenWindowWithTabParent(nsITabParent* aOpeningTabParent, + const nsACString& aFeatures, + bool aCalledFromJS, + float aOpenerFullZoom, + nsITabParent** 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 isPrivateBrowsingWindow = + Preferences::GetBool("browser.privatebrowsing.autostart"); + + nsCOMPtr parentWindowOuter; + if (aOpeningTabParent) { + // We need to examine the window that aOpeningTabParent belongs to in + // order to inform us of what kind of window we're going to open. + TabParent* openingTab = TabParent::GetFrom(aOpeningTabParent); + parentWindowOuter = openingTab->GetParentWindowOuter(); + + // Propagate the privacy status of the parent window, if + // available, to the child. + if (!isPrivateBrowsingWindow) { + nsCOMPtr parentContext = openingTab->GetLoadContext(); + if (parentContext) { + isPrivateBrowsingWindow = parentContext->UsePrivateBrowsing(); + } + } + } + + if (!parentWindowOuter) { + // We couldn't find a browser window for the opener, so either we + // never were passed aOpeningTabParent, 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 parentTreeOwner; + GetWindowTreeOwner(parentWindowOuter, getter_AddRefs(parentTreeOwner)); + if (NS_WARN_IF(!parentTreeOwner)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr windowCreator2(do_QueryInterface(mWindowCreator)); + if (NS_WARN_IF(!windowCreator2)) { + return NS_ERROR_UNEXPECTED; + } + + uint32_t contextFlags = 0; + if (parentWindowOuter->IsLoadingOrRunningTimeout()) { + contextFlags |= + nsIWindowCreator2::PARENT_IS_LOADING_OR_RUNNING_TIMEOUT; + } + + uint32_t chromeFlags = CalculateChromeFlagsForChild(aFeatures); + + // 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; + + nsCOMPtr parentChrome(do_GetInterface(parentTreeOwner)); + nsCOMPtr newWindowChrome; + + CreateChromeWindow(aFeatures, parentChrome, chromeFlags, contextFlags, + aOpeningTabParent, nullptr, getter_AddRefs(newWindowChrome)); + + if (NS_WARN_IF(!newWindowChrome)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr chromeTreeItem = do_GetInterface(newWindowChrome); + if (NS_WARN_IF(!chromeTreeItem)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr chromeTreeOwner; + chromeTreeItem->GetTreeOwner(getter_AddRefs(chromeTreeOwner)); + if (NS_WARN_IF(!chromeTreeOwner)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr chromeContext = do_QueryInterface(chromeTreeItem); + if (NS_WARN_IF(!chromeContext)) { + return NS_ERROR_UNEXPECTED; + } + + chromeContext->SetPrivateBrowsing(isPrivateBrowsingWindow); + + // Tabs opened from a content process can only open new windows + // that will also run with out-of-process tabs. + chromeContext->SetRemoteTabs(true); + + MaybeDisablePersistence(aFeatures, chromeTreeOwner); + + SizeSpec sizeSpec; + CalcSizeSpec(aFeatures, sizeSpec); + SizeOpenedWindow(chromeTreeOwner, parentWindowOuter, false, sizeSpec, + Some(aOpenerFullZoom)); + + nsCOMPtr newTabParent; + chromeTreeOwner->GetPrimaryTabParent(getter_AddRefs(newTabParent)); + if (NS_WARN_IF(!newTabParent)) { + return NS_ERROR_UNEXPECTED; + } + + newTabParent.forget(aResult); + return NS_OK; +} + +nsresult +nsWindowWatcher::OpenWindowInternal(mozIDOMWindowProxy* aParent, + const char* aUrl, + const char* aName, + const char* aFeatures, + bool aCalledFromJS, + bool aDialog, + bool aNavigate, + nsIArray* aArgv, + bool aIsPopupSpam, + bool aForceNoOpener, + nsIDocShellLoadInfo* aLoadInfo, + mozIDOMWindowProxy** aResult) +{ + 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 + nsAutoCString features; // string version of aFeatures + nsCOMPtr uriToLoad; // from aUrl, if any + nsCOMPtr parentTreeOwner; // from the parent window, if any + nsCOMPtr newDocShellItem; // from the new window + + nsCOMPtr parent = + aParent ? nsPIDOMWindowOuter::From(aParent) : nullptr; + + NS_ENSURE_ARG_POINTER(aResult); + *aResult = 0; + + if (!nsContentUtils::IsSafeToRunScript()) { + nsContentUtils::WarnScriptWasIgnored(nullptr); + return NS_ERROR_FAILURE; + } + + GetWindowTreeOwner(parent, getter_AddRefs(parentTreeOwner)); + + // We expect TabParent 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) { + rv = URIfromURL(aUrl, aParent, getter_AddRefs(uriToLoad)); + if (NS_FAILED(rv)) { + return rv; + } + uriToLoad->SchemeIs("chrome", &uriToLoadIsChrome); + } + + bool nameSpecified = false; + if (aName) { + CopyUTF8toUTF16(aName, name); + nameSpecified = true; + } else { + name.SetIsVoid(true); + } + + if (aFeatures) { + features.Assign(aFeatures); + features.StripWhitespace(); + } else { + features.SetIsVoid(true); + } + + // try to find an extant window with the given name + nsCOMPtr foundWindow = + SafeGetWindowByName(name, aForceNoOpener, aParent); + GetWindowTreeItem(foundWindow, getter_AddRefs(newDocShellItem)); + + // 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. + nsCOMPtr parentWindow = + aParent ? nsPIDOMWindowOuter::From(aParent) : nullptr; + nsCOMPtr parentDocShell; + if (parentWindow) { + parentDocShell = parentWindow->GetDocShell(); + if (parentDocShell) { + nsCOMPtr foundDocShell = do_QueryInterface(newDocShellItem); + if (parentDocShell->IsSandboxedFrom(foundDocShell)) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } + } + } + + // no extant window? make a new one. + + // If no parent, consider it chrome when running in the parent process. + bool hasChromeParent = XRE_IsContentProcess() ? false : true; + if (aParent) { + // Check if the parent document has chrome privileges. + nsIDocument* doc = parentWindow->GetDoc(); + hasChromeParent = doc && nsContentUtils::IsChromeDoc(doc); + } + + bool isCallerChrome = nsContentUtils::LegacyIsCallerChromeOrNativeCode(); + + // Make sure we calculate the chromeFlags *before* we push the + // callee context onto the context stack so that + // the calculation sees the actual caller when doing its + // security checks. + if (isCallerChrome && XRE_IsParentProcess()) { + chromeFlags = CalculateChromeFlagsForParent(aParent, features, + aDialog, uriToLoadIsChrome, + hasChromeParent, aCalledFromJS); + } else { + chromeFlags = CalculateChromeFlagsForChild(features); + + if (aDialog) { + MOZ_ASSERT(XRE_IsParentProcess()); + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG; + } + } + + SizeSpec sizeSpec; + CalcSizeSpec(features, sizeSpec); + + nsCOMPtr sm( + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID)); + + + // 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; + + bool windowTypeIsChrome = + chromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_CHROME; + 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 parentGlobalObject = do_QueryInterface(aParent); + if (!aParent) { + jsapiChromeGuard.Init(); + } else if (NS_WARN_IF(!jsapiChromeGuard.Init(parentGlobalObject))) { + return NS_ERROR_UNEXPECTED; + } + } + + uint32_t activeDocsSandboxFlags = 0; + if (!newDocShellItem) { + // We're going to either open up a new window ourselves or ask a + // nsIWindowProvider for one. In either case, we'll want to set the right + // name on it. + windowNeedsName = true; + + // If the parent trying to open a new window is sandboxed + // without 'allow-popups', this is not allowed and we fail here. + if (aParent) { + if (nsIDocument* doc = parentWindow->GetDoc()) { + // Save sandbox flags for copying to new browsing context (docShell). + activeDocsSandboxFlags = doc->GetSandboxFlags(); + if (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. + nsCOMPtr chromeWin = do_QueryInterface(aParent); + if (!aDialog && !chromeWin && + !(chromeFlags & (nsIWebBrowserChrome::CHROME_MODAL | + nsIWebBrowserChrome::CHROME_OPENAS_DIALOG | + nsIWebBrowserChrome::CHROME_OPENAS_CHROME))) { + nsCOMPtr provider; + if (parentTreeOwner) { + provider = do_GetInterface(parentTreeOwner); + } else if (XRE_IsContentProcess()) { + // we're in a content process but we don't have a tabchild we can + // use. + provider = nsContentUtils::GetWindowProviderForContentProcess(); + } + + if (provider) { + nsCOMPtr newWindow; + rv = provider->ProvideWindow(aParent, chromeFlags, aCalledFromJS, + sizeSpec.PositionSpecified(), + sizeSpec.SizeSpecified(), + uriToLoad, name, features, aForceNoOpener, + &windowIsNew, getter_AddRefs(newWindow)); + + if (NS_SUCCEEDED(rv)) { + GetWindowTreeItem(newWindow, getter_AddRefs(newDocShellItem)); + if (windowIsNew && newDocShellItem) { + // Make sure to stop any loads happening in this window that the + // window provider might have started. Otherwise if our caller + // manipulates the window it just opened and then the load + // completes their stuff will get blown away. + nsCOMPtr webNav = + do_QueryInterface(newDocShellItem); + webNav->Stop(nsIWebNavigation::STOP_NETWORK); + } + + // 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 && newDocShellItem) { + nsCOMPtr docShell = do_QueryInterface(newDocShellItem); + if (!CheckUserContextCompatibility(docShell)) { + newWindow = nullptr; + newDocShellItem = 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 (!newDocShellItem) { + windowIsNew = true; + isNewToplevelWindow = true; + + nsCOMPtr 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 parentWindow(do_GetInterface(parentTreeOwner)); + nsCOMPtr 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 newChrome; + + nsCOMPtr parentTopInnerWindow; + if (parentWindow) { + nsCOMPtr parentTopWindow = parentWindow->GetTop(); + if (parentTopWindow) { + parentTopInnerWindow = parentTopWindow->GetCurrentInnerWindow(); + } + } + + if (parentTopInnerWindow) { + parentTopInnerWindow->Suspend(); + } + + /* If the window creator is an nsIWindowCreator2, we can give it + 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. */ + nsCOMPtr windowCreator2( + do_QueryInterface(mWindowCreator)); + if (windowCreator2) { + uint32_t contextFlags = 0; + bool popupConditions = false; + + // is the parent under popup conditions? + if (parentWindow) { + popupConditions = parentWindow->IsLoadingOrRunningTimeout(); + } + + // chrome is always allowed, so clear the flag if the opener is chrome + if (popupConditions) { + popupConditions = !isCallerChrome; + } + + if (popupConditions) { + contextFlags |= + nsIWindowCreator2::PARENT_IS_LOADING_OR_RUNNING_TIMEOUT; + } + + mozIDOMWindowProxy* openerWindow = aForceNoOpener ? nullptr : aParent; + rv = CreateChromeWindow(features, parentChrome, chromeFlags, contextFlags, + nullptr, openerWindow, getter_AddRefs(newChrome)); + + } else { + rv = mWindowCreator->CreateChromeWindow(parentChrome, chromeFlags, + getter_AddRefs(newChrome)); + } + + if (parentTopInnerWindow) { + parentTopInnerWindow->Resume(); + } + + if (newChrome) { + nsCOMPtr xulWin = do_GetInterface(newChrome); + if (xulWin) { + nsCOMPtr xulBrowserWin; + xulWin->GetXULBrowserWindow(getter_AddRefs(xulBrowserWin)); + if (xulBrowserWin) { + nsPIDOMWindowOuter* openerWindow = aForceNoOpener ? nullptr : parentWindow.get(); + xulBrowserWin->ForceInitialBrowserNonRemote(openerWindow); + } + } + /* It might be a chrome nsXULWindow, 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 newWindow(do_GetInterface(newChrome)); + if (newWindow) { + GetWindowTreeItem(newWindow, getter_AddRefs(newDocShellItem)); + } + if (!newDocShellItem) { + newDocShellItem = do_GetInterface(newChrome); + } + if (!newDocShellItem) { + rv = NS_ERROR_FAILURE; + } + } + } + } + + // better have a window to use by this point + if (!newDocShellItem) { + return rv; + } + + nsCOMPtr newDocShell(do_QueryInterface(newDocShellItem)); + NS_ENSURE_TRUE(newDocShell, NS_ERROR_UNEXPECTED); + + // If our parent is sandboxed, set it as the one permitted sandboxed navigator + // on the new window we're opening. + if (activeDocsSandboxFlags && parentWindow) { + newDocShell->SetOnePermittedSandboxedNavigator( + parentWindow->GetDocShell()); + } + + // 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) { + newDocShell->SetSandboxFlags(activeDocsSandboxFlags); + } + + rv = ReadyOpenedDocShellItem(newDocShellItem, parentWindow, windowIsNew, + aForceNoOpener, aResult); + if (NS_FAILED(rv)) { + return rv; + } + + if (isNewToplevelWindow) { + nsCOMPtr newTreeOwner; + newDocShellItem->GetTreeOwner(getter_AddRefs(newTreeOwner)); + MaybeDisablePersistence(features, newTreeOwner); + } + + if (aDialog && aArgv) { + // Set the args on the new window. + nsCOMPtr piwin(do_QueryInterface(*aResult)); + NS_ENSURE_TRUE(piwin, NS_ERROR_UNEXPECTED); + + rv = piwin->SetArguments(aArgv); + NS_ENSURE_SUCCESS(rv, rv); + } + + /* 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")) { + newDocShellItem->SetName(name); + } else { + newDocShellItem->SetName(EmptyString()); + } + } + + // Now we have to set the right opener principal on the new window. Note + // that we have to do this _before_ starting any URI loads, thanks to the + // sync nature of javascript: loads. + // + // Note: The check for the current JSContext isn't necessarily sensical. + // It's just designed to preserve old semantics during a mass-conversion + // patch. + nsCOMPtr subjectPrincipal = + nsContentUtils::GetCurrentJSContext() ? nsContentUtils::SubjectPrincipal() : + nullptr; + + bool isPrivateBrowsingWindow = false; + + if (windowIsNew) { + auto* docShell = static_cast(newDocShell.get()); + + // If this is not a chrome docShell, we apply originAttributes from the + // subjectPrincipal unless if it's an expanded or system principal. + if (subjectPrincipal && + !nsContentUtils::IsSystemOrExpandedPrincipal(subjectPrincipal) && + docShell->ItemType() != nsIDocShellTreeItem::typeChrome) { + DocShellOriginAttributes attrs; + attrs.InheritFromDocToChildDocShell(BasePrincipal::Cast(subjectPrincipal)->OriginAttributesRef()); + isPrivateBrowsingWindow = !!attrs.mPrivateBrowsingId; + docShell->SetOriginAttributes(attrs); + } else { + nsCOMPtr parentItem; + GetWindowTreeItem(aParent, getter_AddRefs(parentItem)); + nsCOMPtr parentContext = do_QueryInterface(parentItem); + if (parentContext) { + isPrivateBrowsingWindow = parentContext->UsePrivateBrowsing(); + } + } + + bool autoPrivateBrowsing = + Preferences::GetBool("browser.privatebrowsing.autostart"); + + if (!autoPrivateBrowsing && + (chromeFlags & nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW)) { + isPrivateBrowsingWindow = false; + } else if (autoPrivateBrowsing || + (chromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW)) { + isPrivateBrowsingWindow = true; + } + + // 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. + nsCOMPtr newWindow = do_QueryInterface(*aResult); + NS_ASSERTION(newWindow == newDocShell->GetWindow(), "Different windows??"); + + // The principal of the initial about:blank document gets set up in + // nsWindowWatcher::AddWindow. Make sure to call it. In the common case + // this call already happened when the window was created, but + // SetInitialPrincipalToSubject is safe to call multiple times. + if (newWindow) { + newWindow->SetInitialPrincipalToSubject(); + if (aIsPopupSpam) { + nsGlobalWindow* globalWin = nsGlobalWindow::Cast(newWindow); + MOZ_ASSERT(!globalWin->IsPopupSpamWindow(), + "Who marked it as popup spam already???"); + if (!globalWin->IsPopupSpamWindow()) { // Make sure we don't mess up our + // counter even if the above + // assert fails. + globalWin->SetIsPopupSpamWindow(true); + } + } + } + } + + // We rely on CalculateChromeFlags to decide whether remote (out-of-process) + // tabs should be used. + bool isRemoteWindow = + !!(chromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW); + + if (isNewToplevelWindow) { + nsCOMPtr childRoot; + newDocShellItem->GetRootTreeItem(getter_AddRefs(childRoot)); + nsCOMPtr childContext = do_QueryInterface(childRoot); + if (childContext) { + childContext->SetPrivateBrowsing(isPrivateBrowsingWindow); + childContext->SetRemoteTabs(isRemoteWindow); + } + } else if (windowIsNew) { + nsCOMPtr childContext = do_QueryInterface(newDocShellItem); + if (childContext) { + childContext->SetPrivateBrowsing(isPrivateBrowsingWindow); + childContext->SetRemoteTabs(isRemoteWindow); + } + } + + nsCOMPtr loadInfo = aLoadInfo; + if (uriToLoad && aNavigate && !loadInfo) { + newDocShell->CreateLoadInfo(getter_AddRefs(loadInfo)); + NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE); + + if (subjectPrincipal) { + loadInfo->SetTriggeringPrincipal(subjectPrincipal); + } + + /* 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. + */ + nsCOMPtr doc = GetEntryDocument(); + if (!doc && parentWindow) { + doc = parentWindow->GetExtantDoc(); + } + if (doc) { + // Set the referrer + loadInfo->SetReferrer(doc->GetDocumentURI()); + loadInfo->SetReferrerPolicy(doc->GetReferrerPolicy()); + } + } + + if (isNewToplevelWindow) { + // Notify observers that the window is open and ready. + // The window has not yet started to load a document. + nsCOMPtr obsSvc = + mozilla::services::GetObserverService(); + if (obsSvc) { + obsSvc->NotifyObservers(*aResult, "toplevel-window-ready", nullptr); + } + } + + // Before loading the URI we want to be 100% sure that we use the correct + // userContextId. + MOZ_ASSERT(CheckUserContextCompatibility(newDocShell)); + + if (uriToLoad && aNavigate) { + newDocShell->LoadURI( + uriToLoad, + loadInfo, + windowIsNew ? + static_cast(nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD) : + static_cast(nsIWebNavigation::LOAD_FLAGS_NONE), + true); + } + + // Copy the current session storage for the current domain. + if (subjectPrincipal && parentDocShell) { + nsCOMPtr parentStorageManager = + do_QueryInterface(parentDocShell); + nsCOMPtr newStorageManager = + do_QueryInterface(newDocShell); + + if (parentStorageManager && newStorageManager) { + nsCOMPtr storage; + nsCOMPtr pInnerWin = parentWindow->GetCurrentInnerWindow(); + + parentStorageManager->GetStorage(pInnerWin, subjectPrincipal, + isPrivateBrowsingWindow, + getter_AddRefs(storage)); + if (storage) { + newStorageManager->CloneStorage(storage); + } + } + } + + if (isNewToplevelWindow) { + nsCOMPtr newTreeOwner; + newDocShellItem->GetTreeOwner(getter_AddRefs(newTreeOwner)); + SizeOpenedWindow(newTreeOwner, aParent, isCallerChrome, sizeSpec); + } + + if (windowIsModal) { + nsCOMPtr newTreeOwner; + newDocShellItem->GetTreeOwner(getter_AddRefs(newTreeOwner)); + nsCOMPtr newChrome(do_GetInterface(newTreeOwner)); + + // Throw an exception here if no web browser chrome is available, + // we need that to show a modal window. + NS_ENSURE_TRUE(newChrome, NS_ERROR_NOT_AVAILABLE); + + // Dispatch dialog events etc, but we only want to do that if + // we're opening a modal content window (the helper classes are + // no-ops if given no window), for chrome dialogs we don't want to + // do any of that (it's done elsewhere for us). + // Make sure we maintain the state on an outer window, because + // that's where it lives; inner windows assert if you try to + // maintain the state on them. + nsAutoWindowStateHelper windowStateHelper( + parentWindow ? parentWindow->GetOuterWindow() : nullptr); + + if (!windowStateHelper.DefaultEnabled()) { + // Default to cancel not opening the modal window. + NS_RELEASE(*aResult); + + return NS_OK; + } + + bool isAppModal = false; + nsCOMPtr parentWindow(do_GetInterface(newTreeOwner)); + nsCOMPtr 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. + nsAutoPopupStatePusher popupStatePusher(openAbused); + + newChrome->ShowAsModal(); + } + } + + // If a website opens a popup exit DOM fullscreen + if (windowIsNew && aCalledFromJS && !hasChromeParent && !isCallerChrome && + parentWindow) { + nsIDocument::AsyncExitFullscreen(parentWindow->GetDoc()); + } + + if (aForceNoOpener && windowIsNew) { + NS_RELEASE(*aResult); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::RegisterNotification(nsIObserver* aObserver) +{ + // just a convenience method; it delegates to nsIObserverService + + if (!aObserver) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr 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 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); + nsWatcherWindowEnumerator* enumerator = new nsWatcherWindowEnumerator(this); + if (enumerator) { + return CallQueryInterface(enumerator, aResult); + } + + return NS_ERROR_OUT_OF_MEMORY; +} + +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 factory = + do_GetService("@mozilla.org/prompter;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + return factory->GetPrompt(aParent, NS_GET_IID(nsIPrompt), + reinterpret_cast(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 factory = + do_GetService("@mozilla.org/prompter;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + return factory->GetPrompt(aParent, NS_GET_IID(nsIAuthPrompt), + reinterpret_cast(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 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 oldPrompt; + rv = factory->GetPrompt( + aParent, NS_GET_IID(nsIAuthPrompt), getter_AddRefs(oldPrompt)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_WrapAuthPrompt(oldPrompt, reinterpret_cast(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; + nsCOMPtr fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (fm) { + return fm->GetActiveWindow(aActiveWindow); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::SetActiveWindow(mozIDOMWindowProxy* aActiveWindow) +{ + nsCOMPtr fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (fm) { + return fm->SetActiveWindow(aActiveWindow); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::AddWindow(mozIDOMWindowProxy* aWindow, nsIWebBrowserChrome* aChrome) +{ + if (!aWindow) { + return NS_ERROR_INVALID_ARG; + } + +#ifdef DEBUG + { + nsCOMPtr win(do_QueryInterface(aWindow)); + + NS_ASSERTION(win->IsOuterWindow(), + "Uh, the active window must be an outer window!"); + } +#endif + + { + 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 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 os = mozilla::services::GetObserverService(); + if (!os) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr domwin(do_QueryInterface(aWindow)); + return os->NotifyObservers(domwin, "domwindowopened", 0); +} + +NS_IMETHODIMP +nsWindowWatcher::RemoveWindow(mozIDOMWindowProxy* aWindow) +{ + // find the corresponding nsWatcherWindowEntry, remove it + + if (!aWindow) { + return NS_ERROR_INVALID_ARG; + } + + nsWatcherWindowEntry* info = FindWindowEntry(aWindow); + if (info) { + RemoveWindow(info); + return NS_OK; + } + NS_WARNING("requested removal of nonexistent window"); + return NS_ERROR_INVALID_ARG; +} + +nsWatcherWindowEntry* +nsWindowWatcher::FindWindowEntry(mozIDOMWindowProxy* aWindow) +{ + // find the corresponding nsWatcherWindowEntry + nsWatcherWindowEntry* info; + nsWatcherWindowEntry* listEnd; +#ifdef USEWEAKREFS + nsresult rv; + bool found; +#endif + + info = mOldestWindow; + listEnd = 0; +#ifdef USEWEAKREFS + rv = NS_OK; + found = false; + while (info != listEnd && NS_SUCCEEDED(rv)) { + nsCOMPtr infoWindow(do_QueryReferent(info->mWindow)); + if (!infoWindow) { // clean up dangling reference, while we're here + rv = RemoveWindow(info); + } else if (infoWindow.get() == aWindow) { + return info; + } + + info = info->mYounger; + listEnd = mOldestWindow; + } + return 0; +#else + while (info != listEnd) { + if (info->mWindow == aWindow) { + return info; + } + info = info->mYounger; + listEnd = mOldestWindow; + } + return 0; +#endif +} + +nsresult +nsWindowWatcher::RemoveWindow(nsWatcherWindowEntry* aInfo) +{ + uint32_t count = mEnumeratorList.Length(); + + { + // notify the enumerators + MutexAutoLock lock(mListLock); + for (uint32_t ctr = 0; ctr < count; ++ctr) { + mEnumeratorList[ctr]->WindowRemoved(aInfo); + } + + // remove the element from the list + if (aInfo == mOldestWindow) { + mOldestWindow = aInfo->mYounger == mOldestWindow ? 0 : aInfo->mYounger; + } + aInfo->Unlink(); + } + + // a window being removed from us signifies a newly closed window. + // send notifications. + nsCOMPtr os = mozilla::services::GetObserverService(); + if (os) { +#ifdef USEWEAKREFS + nsCOMPtr domwin(do_QueryReferent(aInfo->mWindow)); + if (domwin) { + os->NotifyObservers(domwin, "domwindowclosed", 0); + } + // else bummer. since the window is gone, there's nothing to notify with. +#else + nsCOMPtr domwin(do_QueryInterface(aInfo->mWindow)); + os->NotifyObservers(domwin, "domwindowclosed", 0); +#endif + } + + delete aInfo; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::GetChromeForWindow(mozIDOMWindowProxy* aWindow, + nsIWebBrowserChrome** aResult) +{ + if (!aWindow || !aResult) { + return NS_ERROR_INVALID_ARG; + } + *aResult = 0; + + MutexAutoLock lock(mListLock); + nsWatcherWindowEntry* info = FindWindowEntry(aWindow); + if (info) { + if (info->mChromeWeak) { + return info->mChromeWeak->QueryReferent( + NS_GET_IID(nsIWebBrowserChrome), reinterpret_cast(aResult)); + } + *aResult = info->mChrome; + NS_IF_ADDREF(*aResult); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::GetWindowByName(const nsAString& aTargetName, + mozIDOMWindowProxy* aCurrentWindow, + mozIDOMWindowProxy** aResult) +{ + if (!aResult) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = nullptr; + + nsPIDOMWindowOuter* currentWindow = + aCurrentWindow ? nsPIDOMWindowOuter::From(aCurrentWindow) : nullptr; + + nsCOMPtr treeItem; + + nsCOMPtr startItem; + GetWindowTreeItem(currentWindow, getter_AddRefs(startItem)); + if (startItem) { + // Note: original requestor is null here, per idl comments + startItem->FindItemWithName(aTargetName, nullptr, nullptr, + getter_AddRefs(treeItem)); + } else { + // Note: original requestor is null here, per idl comments + FindItemWithName(aTargetName, nullptr, nullptr, getter_AddRefs(treeItem)); + } + + if (treeItem) { + nsCOMPtr domWindow = treeItem->GetWindow(); + domWindow.forget(aResult); + } + + return NS_OK; +} + +bool +nsWindowWatcher::AddEnumerator(nsWatcherWindowEnumerator* aEnumerator) +{ + // (requires a lock; assumes it's called by someone holding the lock) + return mEnumeratorList.AppendElement(aEnumerator) != nullptr; +} + +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 char* aURL, + mozIDOMWindowProxy* aParent, + nsIURI** aURI) +{ + // Build the URI relative to the entry global. + nsCOMPtr 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 (nsIDocument* doc = baseWindow->GetDoc()) { + baseURI = doc->GetDocBaseURI(); + } + } + + // build and return the absolute URI + return NS_NewURI(aURI, aURL, baseURI); +} + +#define NS_CALCULATE_CHROME_FLAG_FOR(feature, flag) \ + prefBranch->GetBoolPref(feature, &forceEnable); \ + if (forceEnable && !aDialog && !aHasChromeParent && !aChromeURL) { \ + chromeFlags |= flag; \ + } else { \ + chromeFlags |= \ + WinHasOption(aFeatures, feature, 0, &presenceFlag) ? flag : 0; \ + } + +// static +uint32_t +nsWindowWatcher::CalculateChromeFlagsHelper(uint32_t aInitialFlags, + const nsACString& aFeatures, + bool& presenceFlag, + bool aDialog, + bool aHasChromeParent, + bool aChromeURL) +{ + uint32_t chromeFlags = aInitialFlags; + + nsresult rv; + nsCOMPtr prefBranch; + nsCOMPtr prefs = + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + + NS_ENSURE_SUCCESS(rv, nsIWebBrowserChrome::CHROME_DEFAULT); + + rv = prefs->GetBranch("dom.disable_window_open_feature.", + getter_AddRefs(prefBranch)); + + NS_ENSURE_SUCCESS(rv, nsIWebBrowserChrome::CHROME_DEFAULT); + + // NS_CALCULATE_CHROME_FLAG_FOR requires aFeatures, forceEnable, aDialog + // aHasChromeParent, aChromeURL, presenceFlag and chromeFlags to be in + // scope. + bool forceEnable = false; + + NS_CALCULATE_CHROME_FLAG_FOR("titlebar", + nsIWebBrowserChrome::CHROME_TITLEBAR); + NS_CALCULATE_CHROME_FLAG_FOR("close", + nsIWebBrowserChrome::CHROME_WINDOW_CLOSE); + NS_CALCULATE_CHROME_FLAG_FOR("toolbar", + nsIWebBrowserChrome::CHROME_TOOLBAR); + NS_CALCULATE_CHROME_FLAG_FOR("location", + nsIWebBrowserChrome::CHROME_LOCATIONBAR); + NS_CALCULATE_CHROME_FLAG_FOR("personalbar", + nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR); + NS_CALCULATE_CHROME_FLAG_FOR("status", + nsIWebBrowserChrome::CHROME_STATUSBAR); + NS_CALCULATE_CHROME_FLAG_FOR("menubar", + nsIWebBrowserChrome::CHROME_MENUBAR); + NS_CALCULATE_CHROME_FLAG_FOR("resizable", + nsIWebBrowserChrome::CHROME_WINDOW_RESIZE); + NS_CALCULATE_CHROME_FLAG_FOR("minimizable", + nsIWebBrowserChrome::CHROME_WINDOW_MIN); + + // default scrollbar to "on," unless explicitly turned off + if (WinHasOption(aFeatures, "scrollbars", 1, &presenceFlag) || !presenceFlag) { + chromeFlags |= nsIWebBrowserChrome::CHROME_SCROLLBARS; + } + + return chromeFlags; +} + +// static +uint32_t +nsWindowWatcher::EnsureFlagsSafeForContent(uint32_t aChromeFlags, + bool aChromeURL) +{ + aChromeFlags |= nsIWebBrowserChrome::CHROME_TITLEBAR; + aChromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_CLOSE; + aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_LOWERED; + aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_RAISED; + aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_POPUP; + /* Untrusted script is allowed to pose modal windows with a chrome + scheme. This check could stand to be better. But it effectively + prevents untrusted script from opening modal windows in general + while still allowing alerts and the like. */ + if (!aChromeURL) { + aChromeFlags &= ~(nsIWebBrowserChrome::CHROME_MODAL | + nsIWebBrowserChrome::CHROME_OPENAS_CHROME); + } + + if (!(aChromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_CHROME)) { + aChromeFlags &= ~nsIWebBrowserChrome::CHROME_DEPENDENT; + } + + return aChromeFlags; +} + +/** + * Calculate the chrome bitmask from a string list of features requested + * from a child process. Feature strings that are restricted to the parent + * process are ignored here. + * @param aFeatures a string containing a list of named features + * @return the chrome bitmask + */ +// static +uint32_t +nsWindowWatcher::CalculateChromeFlagsForChild(const nsACString& aFeatures) +{ + if (aFeatures.IsVoid()) { + return nsIWebBrowserChrome::CHROME_ALL; + } + + bool presenceFlag = false; + uint32_t chromeFlags = CalculateChromeFlagsHelper( + nsIWebBrowserChrome::CHROME_WINDOW_BORDERS, aFeatures, presenceFlag); + + return EnsureFlagsSafeForContent(chromeFlags); +} + +/** + * Calculate the chrome bitmask from a string list of features for a new + * privileged window. + * @param aParent the opener window + * @param aFeatures a string containing a list of named chrome features + * @param aDialog affects the assumptions made about unnamed features + * @param aChromeURL true if the window is being sent to a chrome:// URL + * @param aHasChromeParent true if the parent window is privileged + * @param aCalledFromJS true if the window open request came from script. + * @return the chrome bitmask + */ +// static +uint32_t +nsWindowWatcher::CalculateChromeFlagsForParent(mozIDOMWindowProxy* aParent, + const nsACString& aFeatures, + bool aDialog, + bool aChromeURL, + bool aHasChromeParent, + bool aCalledFromJS) +{ + 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.IsVoid()) { + 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 && WinHasOption(aFeatures, "all", 0, &presenceFlag)) { + chromeFlags = nsIWebBrowserChrome::CHROME_ALL; + } + + /* Next, allow explicitly named options to override the initial settings */ + chromeFlags = CalculateChromeFlagsHelper(chromeFlags, aFeatures, presenceFlag, + aDialog, aHasChromeParent, aChromeURL); + + // Determine whether the window is a private browsing window + chromeFlags |= WinHasOption(aFeatures, "private", 0, &presenceFlag) ? + nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW : 0; + chromeFlags |= WinHasOption(aFeatures, "non-private", 0, &presenceFlag) ? + nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW : 0; + + // Check if it's a popup window + chromeFlags |= WinHasOption(aFeatures, "popup", 0, &presenceFlag) ? + nsIWebBrowserChrome::CHROME_WINDOW_POPUP : 0; + + /* 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 (!PL_strcasestr(aFeatures.BeginReading(), "titlebar")) { + chromeFlags |= nsIWebBrowserChrome::CHROME_TITLEBAR; + } + if (!PL_strcasestr(aFeatures.BeginReading(), "close")) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_CLOSE; + } + } + + if (aDialog && !aFeatures.IsVoid() && !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 (WinHasOption(aFeatures, "alwaysLowered", 0, nullptr) || + WinHasOption(aFeatures, "z-lock", 0, nullptr)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_LOWERED; + } else if (WinHasOption(aFeatures, "alwaysRaised", 0, nullptr)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_RAISED; + } + + chromeFlags |= WinHasOption(aFeatures, "macsuppressanimation", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_MAC_SUPPRESS_ANIMATION : 0; + + chromeFlags |= WinHasOption(aFeatures, "chrome", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_OPENAS_CHROME : 0; + chromeFlags |= WinHasOption(aFeatures, "extrachrome", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_EXTRA : 0; + chromeFlags |= WinHasOption(aFeatures, "centerscreen", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_CENTER_SCREEN : 0; + chromeFlags |= WinHasOption(aFeatures, "dependent", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_DEPENDENT : 0; + chromeFlags |= WinHasOption(aFeatures, "modal", 0, nullptr) ? + (nsIWebBrowserChrome::CHROME_MODAL | nsIWebBrowserChrome::CHROME_DEPENDENT) : 0; + + /* 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 branch = do_GetService(NS_PREFSERVICE_CONTRACTID); + + branch->GetBoolPref("dom.disable_window_open_dialog_feature", + &disableDialogFeature); + + if (!disableDialogFeature) { + chromeFlags |= WinHasOption(aFeatures, "dialog", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_OPENAS_DIALOG : 0; + } + + /* and dialogs need to have the last word. assume dialogs are dialogs, + and opened as chrome, unless explicitly told otherwise. */ + if (aDialog) { + if (!PL_strcasestr(aFeatures.BeginReading(), "dialog")) { + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG; + } + if (!PL_strcasestr(aFeatures.BeginReading(), "chrome")) { + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_CHROME; + } + } + + /* missing + chromeFlags->copy_history + */ + + // Check security state for use in determing window dimensions + if (!aHasChromeParent) { + chromeFlags = EnsureFlagsSafeForContent(chromeFlags, aChromeURL); + } + + // Disable CHROME_OPENAS_DIALOG if the window is inside