1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "nsWindowWatcher.h"
8 #include "nsAutoWindowStateHelper.h"
9 
10 #include "nsCRT.h"
11 #include "nsNetUtil.h"
12 #include "nsIAuthPrompt.h"
13 #include "nsIAuthPrompt2.h"
14 #include "nsISimpleEnumerator.h"
15 #include "nsIInterfaceRequestorUtils.h"
16 #include "nsJSUtils.h"
17 #include "plstr.h"
18 
19 #include "nsDocShell.h"
20 #include "nsGlobalWindow.h"
21 #include "nsHashPropertyBag.h"
22 #include "nsIBaseWindow.h"
23 #include "nsIBrowserDOMWindow.h"
24 #include "nsIDocShell.h"
25 #include "nsDocShellLoadState.h"
26 #include "nsIDocShellTreeItem.h"
27 #include "nsIDocShellTreeOwner.h"
28 #include "mozilla/dom/BrowsingContext.h"
29 #include "mozilla/dom/BrowsingContextGroup.h"
30 #include "mozilla/dom/Document.h"
31 #include "mozilla/dom/DocumentInlines.h"
32 #include "mozilla/Telemetry.h"
33 #include "nsIDOMChromeWindow.h"
34 #include "nsIPrompt.h"
35 #include "nsIScriptObjectPrincipal.h"
36 #include "nsIScreen.h"
37 #include "nsIScreenManager.h"
38 #include "nsIScriptContext.h"
39 #include "nsIObserverService.h"
40 #include "nsXPCOM.h"
41 #include "nsIURI.h"
42 #include "nsIWebBrowser.h"
43 #include "nsIWebBrowserChrome.h"
44 #include "nsIWebNavigation.h"
45 #include "nsIWindowCreator.h"
46 #include "nsIXULRuntime.h"
47 #include "nsPIDOMWindow.h"
48 #include "nsIWindowProvider.h"
49 #include "nsIMutableArray.h"
50 #include "nsIDOMStorageManager.h"
51 #include "nsIWidget.h"
52 #include "nsFocusManager.h"
53 #include "nsOpenWindowInfo.h"
54 #include "nsPresContext.h"
55 #include "nsContentUtils.h"
56 #include "nsIPrefBranch.h"
57 #include "nsIPrefService.h"
58 #include "nsSandboxFlags.h"
59 #include "nsSimpleEnumerator.h"
60 #include "mozilla/BasePrincipal.h"
61 #include "mozilla/CheckedInt.h"
62 #include "mozilla/NullPrincipal.h"
63 #include "mozilla/Preferences.h"
64 #include "mozilla/ResultExtensions.h"
65 #include "mozilla/StaticPrefs_full_screen_api.h"
66 #include "mozilla/dom/Element.h"
67 #include "mozilla/dom/Storage.h"
68 #include "mozilla/dom/ScriptSettings.h"
69 #include "mozilla/dom/BrowserParent.h"
70 #include "mozilla/dom/BrowserHost.h"
71 #include "mozilla/dom/DocGroup.h"
72 #include "mozilla/dom/SessionStorageManager.h"
73 #include "nsIAppWindow.h"
74 #include "nsIXULBrowserWindow.h"
75 #include "nsGlobalWindow.h"
76 #include "ReferrerInfo.h"
77 
78 using namespace mozilla;
79 using namespace mozilla::dom;
80 
81 /****************************************************************
82  ******************** nsWatcherWindowEntry **********************
83  ****************************************************************/
84 
85 class nsWindowWatcher;
86 
87 struct nsWatcherWindowEntry {
nsWatcherWindowEntrynsWatcherWindowEntry88   nsWatcherWindowEntry(mozIDOMWindowProxy* aWindow,
89                        nsIWebBrowserChrome* aChrome)
90       : mChrome(nullptr) {
91     mWindow = aWindow;
92     nsCOMPtr<nsISupportsWeakReference> supportsweak(do_QueryInterface(aChrome));
93     if (supportsweak) {
94       supportsweak->GetWeakReference(getter_AddRefs(mChromeWeak));
95     } else {
96       mChrome = aChrome;
97       mChromeWeak = nullptr;
98     }
99     ReferenceSelf();
100   }
101   ~nsWatcherWindowEntry() = default;
102 
103   void InsertAfter(nsWatcherWindowEntry* aOlder);
104   void Unlink();
105   void ReferenceSelf();
106 
107   mozIDOMWindowProxy* mWindow;
108   nsIWebBrowserChrome* mChrome;
109   nsWeakPtr mChromeWeak;
110   // each struct is in a circular, doubly-linked list
111   nsWatcherWindowEntry* mYounger;  // next younger in sequence
112   nsWatcherWindowEntry* mOlder;
113 };
114 
InsertAfter(nsWatcherWindowEntry * aOlder)115 void nsWatcherWindowEntry::InsertAfter(nsWatcherWindowEntry* aOlder) {
116   if (aOlder) {
117     mOlder = aOlder;
118     mYounger = aOlder->mYounger;
119     mOlder->mYounger = this;
120     if (mOlder->mOlder == mOlder) {
121       mOlder->mOlder = this;
122     }
123     mYounger->mOlder = this;
124     if (mYounger->mYounger == mYounger) {
125       mYounger->mYounger = this;
126     }
127   }
128 }
129 
Unlink()130 void nsWatcherWindowEntry::Unlink() {
131   mOlder->mYounger = mYounger;
132   mYounger->mOlder = mOlder;
133   ReferenceSelf();
134 }
135 
ReferenceSelf()136 void nsWatcherWindowEntry::ReferenceSelf() {
137   mYounger = this;
138   mOlder = this;
139 }
140 
141 /****************************************************************
142  ****************** nsWatcherWindowEnumerator *******************
143  ****************************************************************/
144 
145 class nsWatcherWindowEnumerator : public nsSimpleEnumerator {
146  public:
147   explicit nsWatcherWindowEnumerator(nsWindowWatcher* aWatcher);
148   NS_IMETHOD HasMoreElements(bool* aResult) override;
149   NS_IMETHOD GetNext(nsISupports** aResult) override;
150 
151  protected:
152   ~nsWatcherWindowEnumerator() override;
153 
154  private:
155   friend class nsWindowWatcher;
156 
157   nsWatcherWindowEntry* FindNext();
158   void WindowRemoved(nsWatcherWindowEntry* aInfo);
159 
160   nsWindowWatcher* mWindowWatcher;
161   nsWatcherWindowEntry* mCurrentPosition;
162 };
163 
nsWatcherWindowEnumerator(nsWindowWatcher * aWatcher)164 nsWatcherWindowEnumerator::nsWatcherWindowEnumerator(nsWindowWatcher* aWatcher)
165     : mWindowWatcher(aWatcher), mCurrentPosition(aWatcher->mOldestWindow) {
166   mWindowWatcher->AddEnumerator(this);
167   mWindowWatcher->AddRef();
168 }
169 
~nsWatcherWindowEnumerator()170 nsWatcherWindowEnumerator::~nsWatcherWindowEnumerator() {
171   mWindowWatcher->RemoveEnumerator(this);
172   mWindowWatcher->Release();
173 }
174 
175 NS_IMETHODIMP
HasMoreElements(bool * aResult)176 nsWatcherWindowEnumerator::HasMoreElements(bool* aResult) {
177   if (!aResult) {
178     return NS_ERROR_INVALID_ARG;
179   }
180 
181   *aResult = !!mCurrentPosition;
182   return NS_OK;
183 }
184 
185 NS_IMETHODIMP
GetNext(nsISupports ** aResult)186 nsWatcherWindowEnumerator::GetNext(nsISupports** aResult) {
187   if (!aResult) {
188     return NS_ERROR_INVALID_ARG;
189   }
190 
191   *aResult = nullptr;
192 
193   if (mCurrentPosition) {
194     CallQueryInterface(mCurrentPosition->mWindow, aResult);
195     mCurrentPosition = FindNext();
196     return NS_OK;
197   }
198   return NS_ERROR_FAILURE;
199 }
200 
FindNext()201 nsWatcherWindowEntry* nsWatcherWindowEnumerator::FindNext() {
202   nsWatcherWindowEntry* info;
203 
204   if (!mCurrentPosition) {
205     return 0;
206   }
207 
208   info = mCurrentPosition->mYounger;
209   return info == mWindowWatcher->mOldestWindow ? 0 : info;
210 }
211 
212 // if a window is being removed adjust the iterator's current position
WindowRemoved(nsWatcherWindowEntry * aInfo)213 void nsWatcherWindowEnumerator::WindowRemoved(nsWatcherWindowEntry* aInfo) {
214   if (mCurrentPosition == aInfo) {
215     mCurrentPosition =
216         mCurrentPosition != aInfo->mYounger ? aInfo->mYounger : 0;
217   }
218 }
219 
220 /****************************************************************
221  *********************** nsWindowWatcher ************************
222  ****************************************************************/
223 
224 NS_IMPL_ADDREF(nsWindowWatcher)
NS_IMPL_RELEASE(nsWindowWatcher)225 NS_IMPL_RELEASE(nsWindowWatcher)
226 NS_IMPL_QUERY_INTERFACE(nsWindowWatcher, nsIWindowWatcher, nsIPromptFactory,
227                         nsPIWindowWatcher)
228 
229 nsWindowWatcher::nsWindowWatcher()
230     : mEnumeratorList(),
231       mOldestWindow(0),
232       mListLock("nsWindowWatcher.mListLock") {}
233 
~nsWindowWatcher()234 nsWindowWatcher::~nsWindowWatcher() {
235   // delete data
236   while (mOldestWindow) {
237     RemoveWindow(mOldestWindow);
238   }
239 }
240 
Init()241 nsresult nsWindowWatcher::Init() { return NS_OK; }
242 
243 /**
244  * Convert aArguments into either an nsIArray or nullptr.
245  *
246  *  - If aArguments is nullptr, return nullptr.
247  *  - If aArguments is an nsArray, return nullptr if it's empty, or otherwise
248  *    return the array.
249  *  - If aArguments is an nsIArray, return nullptr if it's empty, or
250  *    otherwise just return the array.
251  *  - Otherwise, return an nsIArray with one element: aArguments.
252  */
ConvertArgsToArray(nsISupports * aArguments)253 static already_AddRefed<nsIArray> ConvertArgsToArray(nsISupports* aArguments) {
254   if (!aArguments) {
255     return nullptr;
256   }
257 
258   nsCOMPtr<nsIArray> array = do_QueryInterface(aArguments);
259   if (array) {
260     uint32_t argc = 0;
261     array->GetLength(&argc);
262     if (argc == 0) {
263       return nullptr;
264     }
265 
266     return array.forget();
267   }
268 
269   nsCOMPtr<nsIMutableArray> singletonArray =
270       do_CreateInstance(NS_ARRAY_CONTRACTID);
271   NS_ENSURE_TRUE(singletonArray, nullptr);
272 
273   nsresult rv = singletonArray->AppendElement(aArguments);
274   NS_ENSURE_SUCCESS(rv, nullptr);
275 
276   return singletonArray.forget();
277 }
278 
279 NS_IMETHODIMP
OpenWindow(mozIDOMWindowProxy * aParent,const nsACString & aUrl,const nsACString & aName,const nsACString & aFeatures,nsISupports * aArguments,mozIDOMWindowProxy ** aResult)280 nsWindowWatcher::OpenWindow(mozIDOMWindowProxy* aParent, const nsACString& aUrl,
281                             const nsACString& aName,
282                             const nsACString& aFeatures,
283                             nsISupports* aArguments,
284                             mozIDOMWindowProxy** aResult) {
285   nsCOMPtr<nsIArray> argv = ConvertArgsToArray(aArguments);
286 
287   uint32_t argc = 0;
288   if (argv) {
289     argv->GetLength(&argc);
290   }
291   bool dialog = (argc != 0);
292 
293   RefPtr<BrowsingContext> bc;
294   MOZ_TRY(OpenWindowInternal(aParent, aUrl, aName, aFeatures,
295                              /* calledFromJS = */ false, dialog,
296                              /* navigate = */ true, argv,
297                              /* aIsPopupSpam = */ false,
298                              /* aForceNoOpener = */ false,
299                              /* aForceNoReferrer = */ false, PRINT_NONE,
300                              /* aLoadState = */ nullptr, getter_AddRefs(bc)));
301   if (bc) {
302     nsCOMPtr<mozIDOMWindowProxy> win(bc->GetDOMWindow());
303     win.forget(aResult);
304   }
305   return NS_OK;
306 }
307 
308 struct SizeSpec {
SizeSpecSizeSpec309   SizeSpec()
310       : mLeft(0),
311         mTop(0),
312         mOuterWidth(0),
313         mOuterHeight(0),
314         mInnerWidth(0),
315         mInnerHeight(0),
316         mLeftSpecified(false),
317         mTopSpecified(false),
318         mOuterWidthSpecified(false),
319         mOuterHeightSpecified(false),
320         mInnerWidthSpecified(false),
321         mInnerHeightSpecified(false),
322         mLockAspectRatio(false) {}
323 
324   int32_t mLeft;
325   int32_t mTop;
326   int32_t mOuterWidth;   // Total window width
327   int32_t mOuterHeight;  // Total window height
328   int32_t mInnerWidth;   // Content area width
329   int32_t mInnerHeight;  // Content area height
330 
331   bool mLeftSpecified;
332   bool mTopSpecified;
333   bool mOuterWidthSpecified;
334   bool mOuterHeightSpecified;
335   bool mInnerWidthSpecified;
336   bool mInnerHeightSpecified;
337   bool mLockAspectRatio;
338 
PositionSpecifiedSizeSpec339   bool PositionSpecified() const { return mLeftSpecified || mTopSpecified; }
340 
SizeSpecifiedSizeSpec341   bool SizeSpecified() const { return WidthSpecified() || HeightSpecified(); }
342 
WidthSpecifiedSizeSpec343   bool WidthSpecified() const {
344     return mOuterWidthSpecified || mInnerWidthSpecified;
345   }
346 
HeightSpecifiedSizeSpec347   bool HeightSpecified() const {
348     return mOuterHeightSpecified || mInnerHeightSpecified;
349   }
350 };
351 
352 NS_IMETHODIMP
OpenWindow2(mozIDOMWindowProxy * aParent,const nsACString & aUrl,const nsACString & aName,const nsACString & aFeatures,bool aCalledFromScript,bool aDialog,bool aNavigate,nsISupports * aArguments,bool aIsPopupSpam,bool aForceNoOpener,bool aForceNoReferrer,PrintKind aPrintKind,nsDocShellLoadState * aLoadState,BrowsingContext ** aResult)353 nsWindowWatcher::OpenWindow2(mozIDOMWindowProxy* aParent,
354                              const nsACString& aUrl, const nsACString& aName,
355                              const nsACString& aFeatures,
356                              bool aCalledFromScript, bool aDialog,
357                              bool aNavigate, nsISupports* aArguments,
358                              bool aIsPopupSpam, bool aForceNoOpener,
359                              bool aForceNoReferrer, PrintKind aPrintKind,
360                              nsDocShellLoadState* aLoadState,
361                              BrowsingContext** aResult) {
362   nsCOMPtr<nsIArray> argv = ConvertArgsToArray(aArguments);
363 
364   uint32_t argc = 0;
365   if (argv) {
366     argv->GetLength(&argc);
367   }
368 
369   // This is extremely messed up, but this behavior is necessary because
370   // callers lie about whether they're a dialog window and whether they're
371   // called from script.  Fixing this is bug 779939.
372   bool dialog = aDialog;
373   if (!aCalledFromScript) {
374     dialog = argc > 0;
375   }
376 
377   return OpenWindowInternal(aParent, aUrl, aName, aFeatures, aCalledFromScript,
378                             dialog, aNavigate, argv, aIsPopupSpam,
379                             aForceNoOpener, aForceNoReferrer, aPrintKind,
380                             aLoadState, aResult);
381 }
382 
383 // This static function checks if the aDocShell uses an UserContextId equal to
384 // the userContextId of subjectPrincipal, if not null.
CheckUserContextCompatibility(nsIDocShell * aDocShell)385 static bool CheckUserContextCompatibility(nsIDocShell* aDocShell) {
386   MOZ_ASSERT(aDocShell);
387 
388   uint32_t userContextId =
389       static_cast<nsDocShell*>(aDocShell)->GetOriginAttributes().mUserContextId;
390 
391   nsCOMPtr<nsIPrincipal> subjectPrincipal =
392       nsContentUtils::GetCurrentJSContext() ? nsContentUtils::SubjectPrincipal()
393                                             : nullptr;
394 
395   // If we don't have a valid principal, probably we are in e10s mode, parent
396   // side.
397   if (!subjectPrincipal) {
398     return true;
399   }
400 
401   // DocShell can have UsercontextID set but loading a document with system
402   // principal. In this case, we consider everything ok.
403   if (subjectPrincipal->IsSystemPrincipal()) {
404     return true;
405   }
406 
407   return subjectPrincipal->GetUserContextId() == userContextId;
408 }
409 
CreateChromeWindow(nsIWebBrowserChrome * aParentChrome,uint32_t aChromeFlags,nsIOpenWindowInfo * aOpenWindowInfo,nsIWebBrowserChrome ** aResult)410 nsresult nsWindowWatcher::CreateChromeWindow(nsIWebBrowserChrome* aParentChrome,
411                                              uint32_t aChromeFlags,
412                                              nsIOpenWindowInfo* aOpenWindowInfo,
413                                              nsIWebBrowserChrome** aResult) {
414   if (NS_WARN_IF(!mWindowCreator)) {
415     return NS_ERROR_UNEXPECTED;
416   }
417 
418   bool cancel = false;
419   nsCOMPtr<nsIWebBrowserChrome> newWindowChrome;
420   nsresult rv = mWindowCreator->CreateChromeWindow(
421       aParentChrome, aChromeFlags, aOpenWindowInfo, &cancel,
422       getter_AddRefs(newWindowChrome));
423 
424   if (NS_SUCCEEDED(rv) && cancel) {
425     newWindowChrome = nullptr;
426     return NS_ERROR_ABORT;
427   }
428 
429   newWindowChrome.forget(aResult);
430   return NS_OK;
431 }
432 
433 /**
434  * Disable persistence of size/position in popups (determined by
435  * determining whether the features parameter specifies width or height
436  * in any way). We consider any overriding of the window's size or position
437  * in the open call as disabling persistence of those attributes.
438  * Popup windows (which should not persist size or position) generally set
439  * the size.
440  *
441  * @param aFeatures
442  *        The features that was used to open the window.
443  * @param aTreeOwner
444  *        The nsIDocShellTreeOwner of the newly opened window. If null,
445  *        this function is a no-op.
446  */
MaybeDisablePersistence(const SizeSpec & sizeSpec,nsIDocShellTreeOwner * aTreeOwner)447 void nsWindowWatcher::MaybeDisablePersistence(
448     const SizeSpec& sizeSpec, nsIDocShellTreeOwner* aTreeOwner) {
449   if (!aTreeOwner) {
450     return;
451   }
452 
453   if (sizeSpec.SizeSpecified()) {
454     aTreeOwner->SetPersistence(false, false, false);
455   }
456 }
457 
458 NS_IMETHODIMP
OpenWindowWithRemoteTab(nsIRemoteTab * aRemoteTab,const nsACString & aFeatures,bool aCalledFromJS,float aOpenerFullZoom,nsIOpenWindowInfo * aOpenWindowInfo,nsIRemoteTab ** aResult)459 nsWindowWatcher::OpenWindowWithRemoteTab(nsIRemoteTab* aRemoteTab,
460                                          const nsACString& aFeatures,
461                                          bool aCalledFromJS,
462                                          float aOpenerFullZoom,
463                                          nsIOpenWindowInfo* aOpenWindowInfo,
464                                          nsIRemoteTab** aResult) {
465   MOZ_ASSERT(XRE_IsParentProcess());
466   MOZ_ASSERT(mWindowCreator);
467 
468   if (!nsContentUtils::IsSafeToRunScript()) {
469     nsContentUtils::WarnScriptWasIgnored(nullptr);
470     return NS_ERROR_FAILURE;
471   }
472 
473   if (NS_WARN_IF(!mWindowCreator)) {
474     return NS_ERROR_UNEXPECTED;
475   }
476 
477   bool isFissionWindow = FissionAutostart();
478   bool isPrivateBrowsingWindow =
479       Preferences::GetBool("browser.privatebrowsing.autostart");
480 
481   nsCOMPtr<nsPIDOMWindowOuter> parentWindowOuter;
482   RefPtr<BrowsingContext> parentBC = aOpenWindowInfo->GetParent();
483   if (parentBC) {
484     RefPtr<Element> browserElement = parentBC->Top()->GetEmbedderElement();
485     if (browserElement && browserElement->GetOwnerGlobal() &&
486         browserElement->GetOwnerGlobal()->AsInnerWindow()) {
487       parentWindowOuter =
488           browserElement->GetOwnerGlobal()->AsInnerWindow()->GetOuterWindow();
489     }
490 
491     isFissionWindow = parentBC->UseRemoteSubframes();
492     isPrivateBrowsingWindow =
493         isPrivateBrowsingWindow || parentBC->UsePrivateBrowsing();
494   }
495 
496   if (!parentWindowOuter) {
497     // We couldn't find a browser window for the opener, so either we
498     // never were passed aRemoteTab, the window is closed,
499     // or it's in the process of closing. Either way, we'll use
500     // the most recently opened browser window instead.
501     parentWindowOuter = nsContentUtils::GetMostRecentNonPBWindow();
502   }
503 
504   if (NS_WARN_IF(!parentWindowOuter)) {
505     return NS_ERROR_UNEXPECTED;
506   }
507 
508   nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner =
509       parentWindowOuter->GetTreeOwner();
510   if (NS_WARN_IF(!parentTreeOwner)) {
511     return NS_ERROR_UNEXPECTED;
512   }
513 
514   if (NS_WARN_IF(!mWindowCreator)) {
515     return NS_ERROR_UNEXPECTED;
516   }
517 
518   WindowFeatures features;
519   features.Tokenize(aFeatures);
520 
521   SizeSpec sizeSpec;
522   CalcSizeSpec(features, false, sizeSpec);
523 
524   uint32_t chromeFlags = CalculateChromeFlagsForContent(features, sizeSpec);
525 
526   if (isPrivateBrowsingWindow) {
527     chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
528   }
529 
530   // A content process has asked for a new window, which implies
531   // that the new window will need to be remote.
532   chromeFlags |= nsIWebBrowserChrome::CHROME_REMOTE_WINDOW;
533 
534   if (isFissionWindow) {
535     chromeFlags |= nsIWebBrowserChrome::CHROME_FISSION_WINDOW;
536   }
537 
538   nsCOMPtr<nsIWebBrowserChrome> parentChrome(do_GetInterface(parentTreeOwner));
539   nsCOMPtr<nsIWebBrowserChrome> newWindowChrome;
540 
541   CreateChromeWindow(parentChrome, chromeFlags, aOpenWindowInfo,
542                      getter_AddRefs(newWindowChrome));
543 
544   if (NS_WARN_IF(!newWindowChrome)) {
545     return NS_ERROR_UNEXPECTED;
546   }
547 
548   nsCOMPtr<nsIDocShellTreeItem> chromeTreeItem =
549       do_GetInterface(newWindowChrome);
550   if (NS_WARN_IF(!chromeTreeItem)) {
551     return NS_ERROR_UNEXPECTED;
552   }
553 
554   nsCOMPtr<nsIDocShellTreeOwner> chromeTreeOwner;
555   chromeTreeItem->GetTreeOwner(getter_AddRefs(chromeTreeOwner));
556   if (NS_WARN_IF(!chromeTreeOwner)) {
557     return NS_ERROR_UNEXPECTED;
558   }
559 
560   nsCOMPtr<nsILoadContext> chromeContext = do_QueryInterface(chromeTreeItem);
561   if (NS_WARN_IF(!chromeContext)) {
562     return NS_ERROR_UNEXPECTED;
563   }
564 
565   MOZ_ASSERT(chromeContext->UsePrivateBrowsing() == isPrivateBrowsingWindow);
566   MOZ_ASSERT(chromeContext->UseRemoteSubframes() == isFissionWindow);
567 
568   // Tabs opened from a content process can only open new windows
569   // that will also run with out-of-process tabs.
570   MOZ_ASSERT(chromeContext->UseRemoteTabs());
571 
572   MaybeDisablePersistence(sizeSpec, chromeTreeOwner);
573 
574   SizeOpenedWindow(chromeTreeOwner, parentWindowOuter, false, sizeSpec,
575                    Some(aOpenerFullZoom));
576 
577   nsCOMPtr<nsIRemoteTab> newBrowserParent;
578   chromeTreeOwner->GetPrimaryRemoteTab(getter_AddRefs(newBrowserParent));
579   if (NS_WARN_IF(!newBrowserParent)) {
580     return NS_ERROR_UNEXPECTED;
581   }
582 
583   newBrowserParent.forget(aResult);
584   return NS_OK;
585 }
586 
OpenWindowInternal(mozIDOMWindowProxy * aParent,const nsACString & aUrl,const nsACString & aName,const nsACString & aFeatures,bool aCalledFromJS,bool aDialog,bool aNavigate,nsIArray * aArgv,bool aIsPopupSpam,bool aForceNoOpener,bool aForceNoReferrer,PrintKind aPrintKind,nsDocShellLoadState * aLoadState,BrowsingContext ** aResult)587 nsresult nsWindowWatcher::OpenWindowInternal(
588     mozIDOMWindowProxy* aParent, const nsACString& aUrl,
589     const nsACString& aName, const nsACString& aFeatures, bool aCalledFromJS,
590     bool aDialog, bool aNavigate, nsIArray* aArgv, bool aIsPopupSpam,
591     bool aForceNoOpener, bool aForceNoReferrer, PrintKind aPrintKind,
592     nsDocShellLoadState* aLoadState, BrowsingContext** aResult) {
593   MOZ_ASSERT_IF(aForceNoReferrer, aForceNoOpener);
594 
595   nsresult rv = NS_OK;
596   bool isNewToplevelWindow = false;
597   bool windowIsNew = false;
598   bool windowNeedsName = false;
599   bool windowIsModal = false;
600   bool uriToLoadIsChrome = false;
601 
602   uint32_t chromeFlags;
603   nsAutoString name;           // string version of aName
604   nsCOMPtr<nsIURI> uriToLoad;  // from aUrl, if any
605   nsCOMPtr<nsIDocShellTreeOwner>
606       parentTreeOwner;            // from the parent window, if any
607   RefPtr<BrowsingContext> newBC;  // from the new window
608 
609   nsCOMPtr<nsPIDOMWindowOuter> parentWindow =
610       aParent ? nsPIDOMWindowOuter::From(aParent) : nullptr;
611 
612   NS_ENSURE_ARG_POINTER(aResult);
613   *aResult = 0;
614 
615   if (!nsContentUtils::IsSafeToRunScript()) {
616     nsContentUtils::WarnScriptWasIgnored(nullptr);
617     return NS_ERROR_FAILURE;
618   }
619 
620   if (parentWindow) {
621     parentTreeOwner = parentWindow->GetTreeOwner();
622   }
623 
624   // We expect BrowserParent to have provided us the absolute URI of the window
625   // we're to open, so there's no need to call URIfromURL (or more importantly,
626   // to check for a chrome URI, which cannot be opened from a remote tab).
627   if (!aUrl.IsVoid()) {
628     rv = URIfromURL(aUrl, aParent, getter_AddRefs(uriToLoad));
629     if (NS_FAILED(rv)) {
630       return rv;
631     }
632     uriToLoadIsChrome = uriToLoad->SchemeIs("chrome");
633   }
634 
635   bool nameSpecified = false;
636   if (!aName.IsEmpty()) {
637     CopyUTF8toUTF16(aName, name);
638     nameSpecified = true;
639   } else {
640     name.SetIsVoid(true);
641   }
642 
643   WindowFeatures features;
644   nsAutoCString featuresStr;
645   if (!aFeatures.IsEmpty()) {
646     featuresStr.Assign(aFeatures);
647     features.Tokenize(featuresStr);
648   } else {
649     featuresStr.SetIsVoid(true);
650   }
651 
652   RefPtr<BrowsingContext> parentBC(
653       parentWindow ? parentWindow->GetBrowsingContext() : nullptr);
654   nsCOMPtr<nsIDocShell> parentDocShell(parentBC ? parentBC->GetDocShell()
655                                                 : nullptr);
656 
657   // Return null for any attempt to trigger a load from a discarded browsing
658   // context. The spec is non-normative, and doesn't specify what should happen
659   // when window.open is called on a window with a null browsing context, but it
660   // does give us broad discretion over when we can decide to ignore an open
661   // request and return null.
662   //
663   // Regardless, we cannot trigger a cross-process load from a discarded
664   // browsing context, and ideally we should behave consistently whether a load
665   // is same-process or cross-process.
666   if (parentBC && parentBC->IsDiscarded()) {
667     return NS_ERROR_ABORT;
668   }
669 
670   // try to find an extant browsing context with the given name
671   newBC = GetBrowsingContextByName(name, aForceNoOpener, parentBC);
672 
673   // Do sandbox checks here, instead of waiting until nsIDocShell::LoadURI.
674   // The state of the window can change before this call and if we are blocked
675   // because of sandboxing, we wouldn't want that to happen.
676   if (parentBC && parentBC->IsSandboxedFrom(newBC)) {
677     return NS_ERROR_DOM_INVALID_ACCESS_ERR;
678   }
679 
680   // If our target BrowsingContext is still pending initialization, ignore the
681   // navigation request targeting it.
682   if (newBC && NS_WARN_IF(newBC->GetPendingInitialization())) {
683     return NS_ERROR_ABORT;
684   }
685 
686   // no extant window? make a new one.
687 
688   // If no parent, consider it chrome when running in the parent process.
689   bool hasChromeParent = XRE_IsContentProcess() ? false : true;
690   if (aParent) {
691     // Check if the parent document has chrome privileges.
692     Document* doc = parentWindow->GetDoc();
693     hasChromeParent = doc && nsContentUtils::IsChromeDoc(doc);
694   }
695 
696   bool isCallerChrome = nsContentUtils::LegacyIsCallerChromeOrNativeCode();
697 
698   SizeSpec sizeSpec;
699   CalcSizeSpec(features, hasChromeParent, sizeSpec);
700 
701   // Make sure we calculate the chromeFlags *before* we push the
702   // callee context onto the context stack so that
703   // the calculation sees the actual caller when doing its
704   // security checks.
705   if (isCallerChrome && XRE_IsParentProcess()) {
706     chromeFlags = CalculateChromeFlagsForSystem(
707         features, sizeSpec, aDialog, uriToLoadIsChrome, hasChromeParent);
708   } else {
709     MOZ_DIAGNOSTIC_ASSERT(parentBC && parentBC->IsContent(),
710                           "content caller must provide content parent");
711     chromeFlags = CalculateChromeFlagsForContent(features, sizeSpec);
712 
713     if (aDialog) {
714       MOZ_ASSERT(XRE_IsParentProcess());
715       chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG;
716     }
717   }
718 
719   bool windowTypeIsChrome =
720       chromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_CHROME;
721 
722   if (parentBC && !aForceNoOpener) {
723     if (parentBC->IsChrome() && !windowTypeIsChrome) {
724       NS_WARNING(
725           "Content windows may never have chrome windows as their openers.");
726       return NS_ERROR_INVALID_ARG;
727     }
728     if (parentBC->IsContent() && windowTypeIsChrome) {
729       NS_WARNING(
730           "Chrome windows may never have content windows as their openers.");
731       return NS_ERROR_INVALID_ARG;
732     }
733   }
734 
735   // If we're opening a content window from a content window, we need to exactly
736   // inherit fission and e10s status flags from parentBC. Only new toplevel
737   // windows may change these options.
738   if (parentBC && parentBC->IsContent() && !windowTypeIsChrome) {
739     chromeFlags &= ~(nsIWebBrowserChrome::CHROME_REMOTE_WINDOW |
740                      nsIWebBrowserChrome::CHROME_FISSION_WINDOW);
741     if (parentBC->UseRemoteTabs()) {
742       chromeFlags |= nsIWebBrowserChrome::CHROME_REMOTE_WINDOW;
743     }
744     if (parentBC->UseRemoteSubframes()) {
745       chromeFlags |= nsIWebBrowserChrome::CHROME_FISSION_WINDOW;
746     }
747   }
748 
749   // XXXbz Why is an AutoJSAPI good enough here?  Wouldn't AutoEntryScript (so
750   // we affect the entry global) make more sense?  Or do we just want to affect
751   // GetSubjectPrincipal()?
752   dom::AutoJSAPI jsapiChromeGuard;
753 
754   if (isCallerChrome && !hasChromeParent && !windowTypeIsChrome) {
755     // open() is called from chrome on a non-chrome window, initialize an
756     // AutoJSAPI with the callee to prevent the caller's privileges from leaking
757     // into code that runs while opening the new window.
758     //
759     // The reasoning for this is in bug 289204. Basically, chrome sometimes does
760     // someContentWindow.open(untrustedURL), and wants to be insulated from
761     // nasty javascript: URLs and such. But there are also cases where we create
762     // a window parented to a content window (such as a download dialog),
763     // usually directly with nsIWindowWatcher. In those cases, we want the
764     // principal of the initial about:blank document to be system, so that the
765     // subsequent XUL load can reuse the inner window and avoid blowing away
766     // expandos. As such, we decide whether to load with the principal of the
767     // caller or of the parent based on whether the docshell type is chrome or
768     // content.
769 
770     nsCOMPtr<nsIGlobalObject> parentGlobalObject = do_QueryInterface(aParent);
771     if (!aParent) {
772       jsapiChromeGuard.Init();
773     } else if (NS_WARN_IF(!jsapiChromeGuard.Init(parentGlobalObject))) {
774       return NS_ERROR_UNEXPECTED;
775     }
776   }
777 
778   // Information used when opening new content windows. This object will be
779   // passed through to the inner nsFrameLoader.
780   RefPtr<nsOpenWindowInfo> openWindowInfo;
781   if (!newBC && !windowTypeIsChrome) {
782     openWindowInfo = new nsOpenWindowInfo();
783     openWindowInfo->mForceNoOpener = aForceNoOpener;
784     openWindowInfo->mParent = parentBC;
785     openWindowInfo->mIsForPrinting = aPrintKind != PRINT_NONE;
786     openWindowInfo->mIsForWindowDotPrint = aPrintKind == PRINT_WINDOW_DOT_PRINT;
787 
788     // We're going to want the window to be immediately available, meaning we
789     // want it to match the current remoteness.
790     openWindowInfo->mIsRemote = XRE_IsContentProcess();
791 
792     // If we have a non-system non-expanded subject principal, we can inherit
793     // our OriginAttributes from it.
794     nsCOMPtr<nsIPrincipal> subjectPrincipal =
795         nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller();
796     if (subjectPrincipal &&
797         !nsContentUtils::IsSystemOrExpandedPrincipal(subjectPrincipal)) {
798       openWindowInfo->mOriginAttributes =
799           subjectPrincipal->OriginAttributesRef();
800     } else if (parentBC) {
801       openWindowInfo->mOriginAttributes = parentBC->OriginAttributesRef();
802     }
803 
804     MOZ_DIAGNOSTIC_ASSERT(
805         !parentBC || openWindowInfo->mOriginAttributes.EqualsIgnoringFPD(
806                          parentBC->OriginAttributesRef()),
807         "subject principal origin attributes doesn't match opener");
808   }
809 
810   uint32_t activeDocsSandboxFlags = 0;
811   if (!newBC) {
812     // We're going to either open up a new window ourselves or ask a
813     // nsIWindowProvider for one.  In either case, we'll want to set the right
814     // name on it.
815     windowNeedsName = true;
816 
817     // If the parent trying to open a new window is sandboxed
818     // without 'allow-popups', this is not allowed and we fail here.
819     if (aParent) {
820       if (Document* doc = parentWindow->GetDoc()) {
821         // Save sandbox flags for copying to new browsing context (docShell).
822         activeDocsSandboxFlags = doc->GetSandboxFlags();
823         // Check to see if this frame is allowed to navigate, but don't check if
824         // we're printing, as that's not a real navigation.
825         if (aPrintKind == PRINT_NONE &&
826             (activeDocsSandboxFlags & SANDBOXED_AUXILIARY_NAVIGATION)) {
827           return NS_ERROR_DOM_INVALID_ACCESS_ERR;
828         }
829       }
830     }
831 
832     // Now check whether it's ok to ask a window provider for a window.  Don't
833     // do it if we're opening a dialog or if our parent is a chrome window or
834     // if we're opening something that has modal, dialog, or chrome flags set.
835     if (parentTreeOwner && !aDialog && parentBC->IsContent() &&
836         !(chromeFlags & (nsIWebBrowserChrome::CHROME_MODAL |
837                          nsIWebBrowserChrome::CHROME_OPENAS_DIALOG |
838                          nsIWebBrowserChrome::CHROME_OPENAS_CHROME))) {
839       MOZ_ASSERT(openWindowInfo);
840 
841       nsCOMPtr<nsIWindowProvider> provider = do_GetInterface(parentTreeOwner);
842       if (provider) {
843         rv = provider->ProvideWindow(openWindowInfo, chromeFlags, aCalledFromJS,
844                                      sizeSpec.WidthSpecified(), uriToLoad, name,
845                                      featuresStr, aForceNoOpener,
846                                      aForceNoReferrer, aLoadState, &windowIsNew,
847                                      getter_AddRefs(newBC));
848 
849         if (NS_SUCCEEDED(rv) && newBC) {
850           nsCOMPtr<nsIDocShell> newDocShell = newBC->GetDocShell();
851 
852           // If this is a new window, but it's incompatible with the current
853           // userContextId, we ignore it and we pretend that nothing has been
854           // returned by ProvideWindow.
855           if (!windowIsNew && newDocShell) {
856             if (!CheckUserContextCompatibility(newDocShell)) {
857               newBC = nullptr;
858               windowIsNew = false;
859             }
860           }
861 
862         } else if (rv == NS_ERROR_ABORT) {
863           // NS_ERROR_ABORT means the window provider has flat-out rejected
864           // the open-window call and we should bail.  Don't return an error
865           // here, because our caller may propagate that error, which might
866           // cause e.g. window.open to throw!  Just return null for our out
867           // param.
868           return NS_OK;
869         }
870       }
871     }
872   }
873 
874   bool newWindowShouldBeModal = false;
875   bool parentIsModal = false;
876   if (!newBC) {
877     if (XRE_IsContentProcess()) {
878       // If our window provider failed to provide a window in the content
879       // process, we cannot recover. Reject the window open request and bail.
880       return NS_OK;
881     }
882 
883     windowIsNew = true;
884     isNewToplevelWindow = true;
885 
886     nsCOMPtr<nsIWebBrowserChrome> parentChrome(
887         do_GetInterface(parentTreeOwner));
888 
889     // is the parent (if any) modal? if so, we must be, too.
890     bool weAreModal = (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL) != 0;
891     newWindowShouldBeModal = weAreModal;
892     if (!weAreModal && parentChrome) {
893       parentChrome->IsWindowModal(&weAreModal);
894       parentIsModal = weAreModal;
895     }
896 
897     if (weAreModal) {
898       windowIsModal = true;
899       // in case we added this because weAreModal
900       chromeFlags |= nsIWebBrowserChrome::CHROME_MODAL |
901                      nsIWebBrowserChrome::CHROME_DEPENDENT;
902     }
903 
904     // Make sure to not create modal windows if our parent is invisible and
905     // isn't a chrome window.  Otherwise we can end up in a bizarre situation
906     // where we can't shut down because an invisible window is open.  If
907     // someone tries to do this, throw.
908     if (!hasChromeParent && (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL)) {
909       nsCOMPtr<nsIBaseWindow> parentWindow(do_GetInterface(parentTreeOwner));
910       nsCOMPtr<nsIWidget> parentWidget;
911       if (parentWindow) {
912         parentWindow->GetMainWidget(getter_AddRefs(parentWidget));
913       }
914       // NOTE: the logic for this visibility check is duplicated in
915       // nsIDOMWindowUtils::isParentWindowMainWidgetVisible - if we change
916       // how a window is determined "visible" in this context then we should
917       // also adjust that attribute and/or any consumers of it...
918       if (parentWidget && !parentWidget->IsVisible()) {
919         return NS_ERROR_NOT_AVAILABLE;
920       }
921     }
922 
923     NS_ASSERTION(mWindowCreator,
924                  "attempted to open a new window with no WindowCreator");
925     rv = NS_ERROR_FAILURE;
926     if (mWindowCreator) {
927       nsCOMPtr<nsIWebBrowserChrome> newChrome;
928 
929       nsCOMPtr<nsPIDOMWindowInner> parentTopInnerWindow;
930       if (parentWindow) {
931         nsCOMPtr<nsPIDOMWindowOuter> parentTopWindow =
932             parentWindow->GetInProcessTop();
933         if (parentTopWindow) {
934           parentTopInnerWindow = parentTopWindow->GetCurrentInnerWindow();
935         }
936       }
937 
938       if (parentTopInnerWindow) {
939         parentTopInnerWindow->Suspend();
940       }
941 
942       /* We can give the window creator some hints. The only hint at this time
943          is whether the opening window is in a situation that's likely to mean
944          this is an unrequested popup window we're creating. However we're not
945          completely honest: we clear that indicator if the opener is chrome, so
946          that the downstream consumer can treat the indicator to mean simply
947          that the new window is subject to popup control. */
948       rv = CreateChromeWindow(parentChrome, chromeFlags, openWindowInfo,
949                               getter_AddRefs(newChrome));
950       if (parentTopInnerWindow) {
951         parentTopInnerWindow->Resume();
952       }
953 
954       if (newChrome) {
955         /* It might be a chrome AppWindow, in which case it won't have
956             an nsIDOMWindow (primary content shell). But in that case, it'll
957             be able to hand over an nsIDocShellTreeItem directly. */
958         nsCOMPtr<nsPIDOMWindowOuter> newWindow(do_GetInterface(newChrome));
959         nsCOMPtr<nsIDocShellTreeItem> newDocShellItem;
960         if (newWindow) {
961           newDocShellItem = newWindow->GetDocShell();
962         }
963         if (!newDocShellItem) {
964           newDocShellItem = do_GetInterface(newChrome);
965         }
966         if (!newDocShellItem) {
967           rv = NS_ERROR_FAILURE;
968         }
969         newBC = newDocShellItem->GetBrowsingContext();
970       }
971     }
972   }
973 
974   // better have a window to use by this point
975   if (!newBC) {
976     return rv;
977   }
978 
979   // If our parent is sandboxed, set it as the one permitted sandboxed navigator
980   // on the new window we're opening.
981   if (activeDocsSandboxFlags && parentBC) {
982     MOZ_ALWAYS_SUCCEEDS(newBC->SetOnePermittedSandboxedNavigator(parentBC));
983   }
984 
985   if (!aForceNoOpener && parentBC) {
986     // If we've created a new content window, its opener should have been set
987     // when its BrowsingContext was created, in order to ensure that the context
988     // is loaded within the correct BrowsingContextGroup.
989     if (windowIsNew && newBC->IsContent()) {
990       if (parentBC->IsDiscarded()) {
991         // If the parent BC was discarded in a nested event loop before we got
992         // to this point, we can't set it as the opener. Ideally we would still
993         // set `HadOriginalOpener()` in that case, but that's somewhat
994         // nontrivial, and not worth the effort given the nature of the corner
995         // case (see comment in `nsFrameLoader::CreateBrowsingContext`.
996         MOZ_RELEASE_ASSERT(newBC->GetOpenerId() == parentBC->Id() ||
997                            newBC->GetOpenerId() == 0);
998       } else {
999         MOZ_RELEASE_ASSERT(newBC->GetOpenerId() == parentBC->Id());
1000         MOZ_RELEASE_ASSERT(newBC->HadOriginalOpener());
1001       }
1002     } else {
1003       // Update the opener for an existing or chrome BC.
1004       newBC->SetOpener(parentBC);
1005     }
1006   }
1007 
1008   RefPtr<nsDocShell> newDocShell(nsDocShell::Cast(newBC->GetDocShell()));
1009 
1010   // As required by spec, new windows always start out same-process, even if the
1011   // URL being loaded will eventually load in a new process.
1012   MOZ_DIAGNOSTIC_ASSERT(!windowIsNew || newDocShell);
1013   // New top-level windows are only opened in the parent process and are, by
1014   // definition, always in-process.
1015   MOZ_DIAGNOSTIC_ASSERT(!isNewToplevelWindow || newDocShell);
1016 
1017   // Copy sandbox flags to the new window if activeDocsSandboxFlags says to do
1018   // so.  Note that it's only nonzero if the window is new, so clobbering
1019   // sandbox flags on the window makes sense in that case.
1020   if (activeDocsSandboxFlags &
1021       SANDBOX_PROPAGATES_TO_AUXILIARY_BROWSING_CONTEXTS) {
1022     MOZ_ASSERT(windowIsNew, "Should only get here for new windows");
1023     MOZ_ALWAYS_SUCCEEDS(newBC->SetSandboxFlags(activeDocsSandboxFlags));
1024     MOZ_ALWAYS_SUCCEEDS(
1025         newBC->SetInitialSandboxFlags(newBC->GetSandboxFlags()));
1026   }
1027 
1028   RefPtr<nsGlobalWindowOuter> win(
1029       nsGlobalWindowOuter::Cast(newBC->GetDOMWindow()));
1030   if (win) {
1031     if (windowIsNew) {
1032 #ifdef DEBUG
1033       // Assert that we're not loading things right now.  If we are, when
1034       // that load completes it will clobber whatever principals we set up
1035       // on this new window!
1036       nsCOMPtr<nsIChannel> chan;
1037       newDocShell->GetDocumentChannel(getter_AddRefs(chan));
1038       MOZ_ASSERT(!chan, "Why is there a document channel?");
1039 #endif
1040 
1041       if (RefPtr<Document> doc = win->GetExtantDoc()) {
1042         doc->SetIsInitialDocument(true);
1043       }
1044     }
1045   }
1046 
1047   MOZ_ASSERT(win || !windowIsNew, "New windows are always created in-process");
1048 
1049   *aResult = do_AddRef(newBC).take();
1050 
1051   if (isNewToplevelWindow) {
1052     nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner;
1053     newDocShell->GetTreeOwner(getter_AddRefs(newTreeOwner));
1054     MaybeDisablePersistence(sizeSpec, newTreeOwner);
1055   }
1056 
1057   if (aDialog && aArgv) {
1058     MOZ_ASSERT(win);
1059     NS_ENSURE_TRUE(win, NS_ERROR_UNEXPECTED);
1060 
1061     // Set the args on the new window.
1062     MOZ_TRY(win->SetArguments(aArgv));
1063   }
1064 
1065   /* allow a window that we found by name to keep its name (important for cases
1066      like _self where the given name is different (and invalid)).  Also, _blank
1067      is not a window name. */
1068   if (windowNeedsName) {
1069     if (nameSpecified && !name.LowerCaseEqualsLiteral("_blank")) {
1070       MOZ_ALWAYS_SUCCEEDS(newBC->SetName(name));
1071     } else {
1072       MOZ_ALWAYS_SUCCEEDS(newBC->SetName(u""_ns));
1073     }
1074   }
1075 
1076   // Now we have to set the right opener principal on the new window.  Note
1077   // that we have to do this _before_ starting any URI loads, thanks to the
1078   // sync nature of javascript: loads.
1079   //
1080   // Note: The check for the current JSContext isn't necessarily sensical.
1081   // It's just designed to preserve old semantics during a mass-conversion
1082   // patch.
1083   // Bug 1498605 verify usages of systemPrincipal here
1084   JSContext* cx = nsContentUtils::GetCurrentJSContext();
1085   nsCOMPtr<nsIPrincipal> subjectPrincipal =
1086       cx ? nsContentUtils::SubjectPrincipal()
1087          : nsContentUtils::GetSystemPrincipal();
1088 
1089   if (windowIsNew) {
1090     if (subjectPrincipal &&
1091         !nsContentUtils::IsSystemOrExpandedPrincipal(subjectPrincipal) &&
1092         newBC->IsContent()) {
1093       MOZ_DIAGNOSTIC_ASSERT(
1094           subjectPrincipal->OriginAttributesRef().EqualsIgnoringFPD(
1095               newBC->OriginAttributesRef()));
1096     }
1097 
1098     bool autoPrivateBrowsing =
1099         Preferences::GetBool("browser.privatebrowsing.autostart");
1100 
1101     if (!autoPrivateBrowsing &&
1102         (chromeFlags & nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW)) {
1103       if (newBC->IsChrome()) {
1104         newBC->SetUsePrivateBrowsing(false);
1105       }
1106       MOZ_DIAGNOSTIC_ASSERT(
1107           !newBC->UsePrivateBrowsing(),
1108           "CHROME_NON_PRIVATE_WINDOW passed, but got private window");
1109     } else if (autoPrivateBrowsing ||
1110                (chromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW)) {
1111       if (newBC->IsChrome()) {
1112         newBC->SetUsePrivateBrowsing(true);
1113       }
1114       MOZ_DIAGNOSTIC_ASSERT(
1115           newBC->UsePrivateBrowsing(),
1116           "CHROME_PRIVATE_WINDOW passed, but got non-private window");
1117     }
1118 
1119     // Now set the opener principal on the new window.  Note that we need to do
1120     // this no matter whether we were opened from JS; if there is nothing on
1121     // the JS stack, just use the principal of our parent window.  In those
1122     // cases we do _not_ set the parent window principal as the owner of the
1123     // load--since we really don't know who the owner is, just leave it null.
1124     NS_ASSERTION(win == newDocShell->GetWindow(), "Different windows??");
1125 
1126     // The principal of the initial about:blank document gets set up in
1127     // nsWindowWatcher::AddWindow. Make sure to call it. In the common case
1128     // this call already happened when the window was created, but
1129     // SetInitialPrincipalToSubject is safe to call multiple times.
1130     if (win) {
1131       nsCOMPtr<nsIContentSecurityPolicy> cspToInheritForAboutBlank;
1132       Maybe<nsILoadInfo::CrossOriginEmbedderPolicy> coepToInheritForAboutBlank;
1133       nsCOMPtr<mozIDOMWindowProxy> targetOpener = win->GetSameProcessOpener();
1134       nsCOMPtr<nsIDocShell> openerDocShell(do_GetInterface(targetOpener));
1135       if (openerDocShell) {
1136         RefPtr<Document> openerDoc =
1137             static_cast<nsDocShell*>(openerDocShell.get())->GetDocument();
1138         cspToInheritForAboutBlank = openerDoc ? openerDoc->GetCsp() : nullptr;
1139         coepToInheritForAboutBlank = openerDoc->GetEmbedderPolicy();
1140       }
1141       win->SetInitialPrincipalToSubject(cspToInheritForAboutBlank,
1142                                         coepToInheritForAboutBlank);
1143 
1144       if (aIsPopupSpam) {
1145         MOZ_ASSERT(!newBC->GetIsPopupSpam(),
1146                    "Who marked it as popup spam already???");
1147         // Make sure we don't mess up our counter even if the above assert
1148         // fails.
1149         if (!newBC->GetIsPopupSpam()) {
1150           MOZ_ALWAYS_SUCCEEDS(newBC->SetIsPopupSpam(true));
1151         }
1152       }
1153     }
1154   }
1155 
1156   // We rely on CalculateChromeFlags to decide whether remote (out-of-process)
1157   // tabs should be used.
1158   MOZ_DIAGNOSTIC_ASSERT(
1159       newBC->UseRemoteTabs() ==
1160       !!(chromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW));
1161   MOZ_DIAGNOSTIC_ASSERT(
1162       newBC->UseRemoteSubframes() ==
1163       !!(chromeFlags & nsIWebBrowserChrome::CHROME_FISSION_WINDOW));
1164 
1165   nsCOMPtr<nsPIDOMWindowInner> pInnerWin =
1166       parentWindow ? parentWindow->GetCurrentInnerWindow() : nullptr;
1167   ;
1168   RefPtr<nsDocShellLoadState> loadState = aLoadState;
1169   if (uriToLoad && loadState) {
1170     // If a URI was passed to this function, open that, not what was passed in
1171     // the original LoadState. See Bug 1515433.
1172     loadState->SetURI(uriToLoad);
1173   } else if (uriToLoad && aNavigate && !loadState) {
1174     RefPtr<WindowContext> context =
1175         pInnerWin ? pInnerWin->GetWindowContext() : nullptr;
1176     loadState = new nsDocShellLoadState(uriToLoad);
1177 
1178     loadState->SetSourceBrowsingContext(parentBC);
1179     loadState->SetAllowFocusMove(true);
1180     loadState->SetHasValidUserGestureActivation(
1181         context && context->HasValidTransientUserGestureActivation());
1182     if (parentBC) {
1183       loadState->SetTriggeringSandboxFlags(parentBC->GetSandboxFlags());
1184     }
1185 
1186     if (subjectPrincipal) {
1187       loadState->SetTriggeringPrincipal(subjectPrincipal);
1188     }
1189 #ifndef ANDROID
1190     MOZ_ASSERT(subjectPrincipal,
1191                "nsWindowWatcher: triggeringPrincipal required");
1192 #endif
1193 
1194     if (!aForceNoReferrer) {
1195       /* use the URL from the *extant* document, if any. The usual accessor
1196          GetDocument will synchronously create an about:blank document if
1197          it has no better answer, and we only care about a real document.
1198          Also using GetDocument to force document creation seems to
1199          screw up focus in the hidden window; see bug 36016.
1200       */
1201       RefPtr<Document> doc = GetEntryDocument();
1202       if (!doc && parentWindow) {
1203         doc = parentWindow->GetExtantDoc();
1204       }
1205       if (doc) {
1206         auto referrerInfo = MakeRefPtr<ReferrerInfo>(*doc);
1207         loadState->SetReferrerInfo(referrerInfo);
1208       }
1209     }
1210   }
1211 
1212   if (loadState && cx) {
1213     nsGlobalWindowInner* win = xpc::CurrentWindowOrNull(cx);
1214     if (win) {
1215       nsCOMPtr<nsIContentSecurityPolicy> csp = win->GetCsp();
1216       loadState->SetCsp(csp);
1217     }
1218   }
1219 
1220   if (isNewToplevelWindow) {
1221     // Notify observers that the window is open and ready.
1222     // The window has not yet started to load a document.
1223     nsCOMPtr<nsIObserverService> obsSvc =
1224         mozilla::services::GetObserverService();
1225     if (obsSvc) {
1226       obsSvc->NotifyObservers(ToSupports(win), "toplevel-window-ready",
1227                               nullptr);
1228     }
1229   }
1230 
1231   // Before loading the URI we want to be 100% sure that we use the correct
1232   // userContextId.
1233   MOZ_ASSERT_IF(newDocShell, CheckUserContextCompatibility(newDocShell));
1234 
1235   // If this tab or window has been opened by a window.open call, we have to
1236   // provide all the data needed to send a
1237   // webNavigation.onCreatedNavigationTarget event.
1238   if (parentDocShell && windowIsNew) {
1239     nsCOMPtr<nsIObserverService> obsSvc =
1240         mozilla::services::GetObserverService();
1241 
1242     if (obsSvc) {
1243       RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
1244 
1245       if (uriToLoad) {
1246         // The url notified in the webNavigation.onCreatedNavigationTarget
1247         // event.
1248         props->SetPropertyAsACString(u"url"_ns, uriToLoad->GetSpecOrDefault());
1249       }
1250 
1251       props->SetPropertyAsInterface(u"sourceTabDocShell"_ns, parentDocShell);
1252       props->SetPropertyAsInterface(u"createdTabDocShell"_ns,
1253                                     ToSupports(newDocShell));
1254 
1255       obsSvc->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
1256                               "webNavigation-createdNavigationTarget-from-js",
1257                               nullptr);
1258     }
1259   }
1260 
1261   if (uriToLoad && aNavigate) {
1262     // XXXBFCache Per spec this should effectively use
1263     // LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL when noopener is passed to
1264     // window.open(). Bug 1694993.
1265     loadState->SetLoadFlags(
1266         windowIsNew
1267             ? static_cast<uint32_t>(nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD)
1268             : static_cast<uint32_t>(nsIWebNavigation::LOAD_FLAGS_NONE));
1269     loadState->SetFirstParty(true);
1270 
1271     // Should this pay attention to errors returned by LoadURI?
1272     newBC->LoadURI(loadState);
1273   }
1274 
1275   // Copy the current session storage for the current domain. Don't perform the
1276   // copy if we're forcing noopener, however.
1277   if (!aForceNoOpener && subjectPrincipal && parentDocShell && newDocShell) {
1278     const RefPtr<SessionStorageManager> parentStorageManager =
1279         parentDocShell->GetBrowsingContext()->GetSessionStorageManager();
1280     const RefPtr<SessionStorageManager> newStorageManager =
1281         newDocShell->GetBrowsingContext()->GetSessionStorageManager();
1282 
1283     if (parentStorageManager && newStorageManager) {
1284       RefPtr<Storage> storage;
1285       parentStorageManager->GetStorage(
1286           pInnerWin, subjectPrincipal, subjectPrincipal,
1287           newBC->UsePrivateBrowsing(), getter_AddRefs(storage));
1288       if (storage) {
1289         newStorageManager->CloneStorage(storage);
1290       }
1291     }
1292   }
1293 
1294   if (isNewToplevelWindow) {
1295     nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner;
1296     newDocShell->GetTreeOwner(getter_AddRefs(newTreeOwner));
1297     SizeOpenedWindow(newTreeOwner, aParent, isCallerChrome, sizeSpec);
1298   }
1299 
1300   if (windowIsModal) {
1301     NS_ENSURE_TRUE(newDocShell, NS_ERROR_NOT_IMPLEMENTED);
1302 
1303     nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner;
1304     newDocShell->GetTreeOwner(getter_AddRefs(newTreeOwner));
1305     nsCOMPtr<nsIWebBrowserChrome> newChrome(do_GetInterface(newTreeOwner));
1306 
1307     // Throw an exception here if no web browser chrome is available,
1308     // we need that to show a modal window.
1309     NS_ENSURE_TRUE(newChrome, NS_ERROR_NOT_AVAILABLE);
1310 
1311     // Dispatch dialog events etc, but we only want to do that if
1312     // we're opening a modal content window (the helper classes are
1313     // no-ops if given no window), for chrome dialogs we don't want to
1314     // do any of that (it's done elsewhere for us).
1315     // Make sure we maintain the state on an outer window, because
1316     // that's where it lives; inner windows assert if you try to
1317     // maintain the state on them.
1318     nsAutoWindowStateHelper windowStateHelper(parentWindow);
1319 
1320     if (!windowStateHelper.DefaultEnabled()) {
1321       // Default to cancel not opening the modal window.
1322       NS_RELEASE(*aResult);
1323 
1324       return NS_OK;
1325     }
1326 
1327     bool isAppModal = false;
1328     nsCOMPtr<nsIBaseWindow> parentWindow(do_GetInterface(newTreeOwner));
1329     nsCOMPtr<nsIWidget> parentWidget;
1330     if (parentWindow) {
1331       parentWindow->GetMainWidget(getter_AddRefs(parentWidget));
1332       if (parentWidget) {
1333         isAppModal = parentWidget->IsRunningAppModal();
1334       }
1335     }
1336     if (parentWidget &&
1337         ((!newWindowShouldBeModal && parentIsModal) || isAppModal)) {
1338       parentWidget->SetFakeModal(true);
1339     } else {
1340       // Reset popup state while opening a modal dialog, and firing
1341       // events about the dialog, to prevent the current state from
1342       // being active the whole time a modal dialog is open.
1343       AutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused);
1344 
1345       newChrome->ShowAsModal();
1346     }
1347   }
1348   // If a website opens a popup exit DOM fullscreen
1349   if (StaticPrefs::full_screen_api_exit_on_windowOpen() && windowIsNew &&
1350       aCalledFromJS && !hasChromeParent && !isCallerChrome && parentWindow) {
1351     Document::AsyncExitFullscreen(parentWindow->GetDoc());
1352   }
1353 
1354   if (aForceNoOpener && windowIsNew) {
1355     NS_RELEASE(*aResult);
1356   }
1357 
1358   return NS_OK;
1359 }
1360 
1361 NS_IMETHODIMP
RegisterNotification(nsIObserver * aObserver)1362 nsWindowWatcher::RegisterNotification(nsIObserver* aObserver) {
1363   // just a convenience method; it delegates to nsIObserverService
1364 
1365   if (!aObserver) {
1366     return NS_ERROR_INVALID_ARG;
1367   }
1368 
1369   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1370   if (!os) {
1371     return NS_ERROR_FAILURE;
1372   }
1373 
1374   nsresult rv = os->AddObserver(aObserver, "domwindowopened", false);
1375   if (NS_SUCCEEDED(rv)) {
1376     rv = os->AddObserver(aObserver, "domwindowclosed", false);
1377   }
1378 
1379   return rv;
1380 }
1381 
1382 NS_IMETHODIMP
UnregisterNotification(nsIObserver * aObserver)1383 nsWindowWatcher::UnregisterNotification(nsIObserver* aObserver) {
1384   // just a convenience method; it delegates to nsIObserverService
1385 
1386   if (!aObserver) {
1387     return NS_ERROR_INVALID_ARG;
1388   }
1389 
1390   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1391   if (!os) {
1392     return NS_ERROR_FAILURE;
1393   }
1394 
1395   os->RemoveObserver(aObserver, "domwindowopened");
1396   os->RemoveObserver(aObserver, "domwindowclosed");
1397 
1398   return NS_OK;
1399 }
1400 
1401 NS_IMETHODIMP
GetWindowEnumerator(nsISimpleEnumerator ** aResult)1402 nsWindowWatcher::GetWindowEnumerator(nsISimpleEnumerator** aResult) {
1403   if (!aResult) {
1404     return NS_ERROR_INVALID_ARG;
1405   }
1406 
1407   MutexAutoLock lock(mListLock);
1408   RefPtr<nsWatcherWindowEnumerator> enumerator =
1409       new nsWatcherWindowEnumerator(this);
1410   enumerator.forget(aResult);
1411   return NS_OK;
1412 }
1413 
1414 NS_IMETHODIMP
GetNewPrompter(mozIDOMWindowProxy * aParent,nsIPrompt ** aResult)1415 nsWindowWatcher::GetNewPrompter(mozIDOMWindowProxy* aParent,
1416                                 nsIPrompt** aResult) {
1417   // This is for backwards compat only. Callers should just use the prompt
1418   // service directly.
1419   nsresult rv;
1420   nsCOMPtr<nsIPromptFactory> factory =
1421       do_GetService("@mozilla.org/prompter;1", &rv);
1422   NS_ENSURE_SUCCESS(rv, rv);
1423   return factory->GetPrompt(aParent, NS_GET_IID(nsIPrompt),
1424                             reinterpret_cast<void**>(aResult));
1425 }
1426 
1427 NS_IMETHODIMP
GetNewAuthPrompter(mozIDOMWindowProxy * aParent,nsIAuthPrompt ** aResult)1428 nsWindowWatcher::GetNewAuthPrompter(mozIDOMWindowProxy* aParent,
1429                                     nsIAuthPrompt** aResult) {
1430   // This is for backwards compat only. Callers should just use the prompt
1431   // service directly.
1432   nsresult rv;
1433   nsCOMPtr<nsIPromptFactory> factory =
1434       do_GetService("@mozilla.org/prompter;1", &rv);
1435   NS_ENSURE_SUCCESS(rv, rv);
1436   return factory->GetPrompt(aParent, NS_GET_IID(nsIAuthPrompt),
1437                             reinterpret_cast<void**>(aResult));
1438 }
1439 
1440 NS_IMETHODIMP
GetPrompt(mozIDOMWindowProxy * aParent,const nsIID & aIID,void ** aResult)1441 nsWindowWatcher::GetPrompt(mozIDOMWindowProxy* aParent, const nsIID& aIID,
1442                            void** aResult) {
1443   // This is for backwards compat only. Callers should just use the prompt
1444   // service directly.
1445   nsresult rv;
1446   nsCOMPtr<nsIPromptFactory> factory =
1447       do_GetService("@mozilla.org/prompter;1", &rv);
1448   NS_ENSURE_SUCCESS(rv, rv);
1449   rv = factory->GetPrompt(aParent, aIID, aResult);
1450 
1451   // Allow for an embedding implementation to not support nsIAuthPrompt2.
1452   if (rv == NS_NOINTERFACE && aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
1453     nsCOMPtr<nsIAuthPrompt> oldPrompt;
1454     rv = factory->GetPrompt(aParent, NS_GET_IID(nsIAuthPrompt),
1455                             getter_AddRefs(oldPrompt));
1456     NS_ENSURE_SUCCESS(rv, rv);
1457 
1458     NS_WrapAuthPrompt(oldPrompt, reinterpret_cast<nsIAuthPrompt2**>(aResult));
1459     if (!*aResult) {
1460       rv = NS_ERROR_NOT_AVAILABLE;
1461     }
1462   }
1463   return rv;
1464 }
1465 
1466 NS_IMETHODIMP
SetWindowCreator(nsIWindowCreator * aCreator)1467 nsWindowWatcher::SetWindowCreator(nsIWindowCreator* aCreator) {
1468   mWindowCreator = aCreator;
1469   return NS_OK;
1470 }
1471 
1472 NS_IMETHODIMP
HasWindowCreator(bool * aResult)1473 nsWindowWatcher::HasWindowCreator(bool* aResult) {
1474   *aResult = mWindowCreator;
1475   return NS_OK;
1476 }
1477 
1478 NS_IMETHODIMP
GetActiveWindow(mozIDOMWindowProxy ** aActiveWindow)1479 nsWindowWatcher::GetActiveWindow(mozIDOMWindowProxy** aActiveWindow) {
1480   *aActiveWindow = nullptr;
1481   nsFocusManager* fm = nsFocusManager::GetFocusManager();
1482   if (fm) {
1483     return fm->GetActiveWindow(aActiveWindow);
1484   }
1485   return NS_OK;
1486 }
1487 
1488 NS_IMETHODIMP
AddWindow(mozIDOMWindowProxy * aWindow,nsIWebBrowserChrome * aChrome)1489 nsWindowWatcher::AddWindow(mozIDOMWindowProxy* aWindow,
1490                            nsIWebBrowserChrome* aChrome) {
1491   if (!aWindow) {
1492     return NS_ERROR_INVALID_ARG;
1493   }
1494 
1495   {
1496     nsWatcherWindowEntry* info;
1497     MutexAutoLock lock(mListLock);
1498 
1499     // if we already have an entry for this window, adjust
1500     // its chrome mapping and return
1501     info = FindWindowEntry(aWindow);
1502     if (info) {
1503       nsCOMPtr<nsISupportsWeakReference> supportsweak(
1504           do_QueryInterface(aChrome));
1505       if (supportsweak) {
1506         supportsweak->GetWeakReference(getter_AddRefs(info->mChromeWeak));
1507       } else {
1508         info->mChrome = aChrome;
1509         info->mChromeWeak = nullptr;
1510       }
1511       return NS_OK;
1512     }
1513 
1514     // create a window info struct and add it to the list of windows
1515     info = new nsWatcherWindowEntry(aWindow, aChrome);
1516     if (!info) {
1517       return NS_ERROR_OUT_OF_MEMORY;
1518     }
1519 
1520     if (mOldestWindow) {
1521       info->InsertAfter(mOldestWindow->mOlder);
1522     } else {
1523       mOldestWindow = info;
1524     }
1525   }  // leave the mListLock
1526 
1527   // a window being added to us signifies a newly opened window.
1528   // send notifications.
1529   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1530   if (!os) {
1531     return NS_ERROR_FAILURE;
1532   }
1533 
1534   nsCOMPtr<nsISupports> domwin(do_QueryInterface(aWindow));
1535   return os->NotifyObservers(domwin, "domwindowopened", 0);
1536 }
1537 
1538 NS_IMETHODIMP
RemoveWindow(mozIDOMWindowProxy * aWindow)1539 nsWindowWatcher::RemoveWindow(mozIDOMWindowProxy* aWindow) {
1540   // find the corresponding nsWatcherWindowEntry, remove it
1541 
1542   if (!aWindow) {
1543     return NS_ERROR_INVALID_ARG;
1544   }
1545 
1546   nsWatcherWindowEntry* info = FindWindowEntry(aWindow);
1547   if (info) {
1548     RemoveWindow(info);
1549     return NS_OK;
1550   }
1551   NS_WARNING("requested removal of nonexistent window");
1552   return NS_ERROR_INVALID_ARG;
1553 }
1554 
FindWindowEntry(mozIDOMWindowProxy * aWindow)1555 nsWatcherWindowEntry* nsWindowWatcher::FindWindowEntry(
1556     mozIDOMWindowProxy* aWindow) {
1557   // find the corresponding nsWatcherWindowEntry
1558   nsWatcherWindowEntry* info;
1559   nsWatcherWindowEntry* listEnd;
1560 
1561   info = mOldestWindow;
1562   listEnd = 0;
1563   while (info != listEnd) {
1564     if (info->mWindow == aWindow) {
1565       return info;
1566     }
1567     info = info->mYounger;
1568     listEnd = mOldestWindow;
1569   }
1570   return 0;
1571 }
1572 
RemoveWindow(nsWatcherWindowEntry * aInfo)1573 nsresult nsWindowWatcher::RemoveWindow(nsWatcherWindowEntry* aInfo) {
1574   uint32_t count = mEnumeratorList.Length();
1575 
1576   {
1577     // notify the enumerators
1578     MutexAutoLock lock(mListLock);
1579     for (uint32_t ctr = 0; ctr < count; ++ctr) {
1580       mEnumeratorList[ctr]->WindowRemoved(aInfo);
1581     }
1582 
1583     // remove the element from the list
1584     if (aInfo == mOldestWindow) {
1585       mOldestWindow = aInfo->mYounger == mOldestWindow ? 0 : aInfo->mYounger;
1586     }
1587     aInfo->Unlink();
1588   }
1589 
1590   // a window being removed from us signifies a newly closed window.
1591   // send notifications.
1592   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1593   if (os) {
1594     nsCOMPtr<nsISupports> domwin(do_QueryInterface(aInfo->mWindow));
1595     os->NotifyObservers(domwin, "domwindowclosed", 0);
1596   }
1597 
1598   delete aInfo;
1599   return NS_OK;
1600 }
1601 
1602 NS_IMETHODIMP
GetChromeForWindow(mozIDOMWindowProxy * aWindow,nsIWebBrowserChrome ** aResult)1603 nsWindowWatcher::GetChromeForWindow(mozIDOMWindowProxy* aWindow,
1604                                     nsIWebBrowserChrome** aResult) {
1605   if (!aWindow || !aResult) {
1606     return NS_ERROR_INVALID_ARG;
1607   }
1608   *aResult = 0;
1609 
1610   MutexAutoLock lock(mListLock);
1611   nsWatcherWindowEntry* info = FindWindowEntry(aWindow);
1612   if (info) {
1613     if (info->mChromeWeak) {
1614       return info->mChromeWeak->QueryReferent(
1615           NS_GET_IID(nsIWebBrowserChrome), reinterpret_cast<void**>(aResult));
1616     }
1617     *aResult = info->mChrome;
1618     NS_IF_ADDREF(*aResult);
1619   }
1620   return NS_OK;
1621 }
1622 
1623 NS_IMETHODIMP
GetWindowByName(const nsAString & aTargetName,mozIDOMWindowProxy * aCurrentWindow,mozIDOMWindowProxy ** aResult)1624 nsWindowWatcher::GetWindowByName(const nsAString& aTargetName,
1625                                  mozIDOMWindowProxy* aCurrentWindow,
1626                                  mozIDOMWindowProxy** aResult) {
1627   if (!aResult) {
1628     return NS_ERROR_INVALID_ARG;
1629   }
1630 
1631   *aResult = nullptr;
1632 
1633   BrowsingContext* currentContext =
1634       aCurrentWindow
1635           ? nsPIDOMWindowOuter::From(aCurrentWindow)->GetBrowsingContext()
1636           : nullptr;
1637 
1638   RefPtr<BrowsingContext> context =
1639       GetBrowsingContextByName(aTargetName, false, currentContext);
1640 
1641   if (context) {
1642     *aResult = do_AddRef(context->GetDOMWindow()).take();
1643     MOZ_ASSERT(*aResult);
1644   }
1645 
1646   return NS_OK;
1647 }
1648 
AddEnumerator(nsWatcherWindowEnumerator * aEnumerator)1649 bool nsWindowWatcher::AddEnumerator(nsWatcherWindowEnumerator* aEnumerator) {
1650   // (requires a lock; assumes it's called by someone holding the lock)
1651   // XXX(Bug 1631371) Check if this should use a fallible operation as it
1652   // pretended earlier, or change the return type to void.
1653   mEnumeratorList.AppendElement(aEnumerator);
1654   return true;
1655 }
1656 
RemoveEnumerator(nsWatcherWindowEnumerator * aEnumerator)1657 bool nsWindowWatcher::RemoveEnumerator(nsWatcherWindowEnumerator* aEnumerator) {
1658   // (requires a lock; assumes it's called by someone holding the lock)
1659   return mEnumeratorList.RemoveElement(aEnumerator);
1660 }
1661 
URIfromURL(const nsACString & aURL,mozIDOMWindowProxy * aParent,nsIURI ** aURI)1662 nsresult nsWindowWatcher::URIfromURL(const nsACString& aURL,
1663                                      mozIDOMWindowProxy* aParent,
1664                                      nsIURI** aURI) {
1665   // Build the URI relative to the entry global.
1666   nsCOMPtr<nsPIDOMWindowInner> baseWindow = do_QueryInterface(GetEntryGlobal());
1667 
1668   // failing that, build it relative to the parent window, if possible
1669   if (!baseWindow && aParent) {
1670     baseWindow = nsPIDOMWindowOuter::From(aParent)->GetCurrentInnerWindow();
1671   }
1672 
1673   // failing that, use the given URL unmodified. It had better not be relative.
1674 
1675   nsIURI* baseURI = nullptr;
1676 
1677   // get baseWindow's document URI
1678   if (baseWindow) {
1679     if (Document* doc = baseWindow->GetDoc()) {
1680       baseURI = doc->GetDocBaseURI();
1681     }
1682   }
1683 
1684   // build and return the absolute URI
1685   return NS_NewURI(aURI, aURL, nullptr, baseURI);
1686 }
1687 
1688 // static
CalculateChromeFlagsHelper(uint32_t aInitialFlags,const WindowFeatures & aFeatures,const SizeSpec & aSizeSpec,bool * presenceFlag,bool aHasChromeParent)1689 uint32_t nsWindowWatcher::CalculateChromeFlagsHelper(
1690     uint32_t aInitialFlags, const WindowFeatures& aFeatures,
1691     const SizeSpec& aSizeSpec, bool* presenceFlag, bool aHasChromeParent) {
1692   uint32_t chromeFlags = aInitialFlags;
1693 
1694   if (aFeatures.GetBoolWithDefault("titlebar", false, presenceFlag)) {
1695     chromeFlags |= nsIWebBrowserChrome::CHROME_TITLEBAR;
1696   }
1697   if (aFeatures.GetBoolWithDefault("close", false, presenceFlag)) {
1698     chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_CLOSE;
1699   }
1700   if (aFeatures.GetBoolWithDefault("toolbar", false, presenceFlag)) {
1701     chromeFlags |= nsIWebBrowserChrome::CHROME_TOOLBAR;
1702   }
1703   if (aFeatures.GetBoolWithDefault("location", false, presenceFlag)) {
1704     chromeFlags |= nsIWebBrowserChrome::CHROME_LOCATIONBAR;
1705   }
1706   if (aFeatures.GetBoolWithDefault("personalbar", false, presenceFlag)) {
1707     chromeFlags |= nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR;
1708   }
1709   if (aFeatures.GetBoolWithDefault("status", false, presenceFlag)) {
1710     chromeFlags |= nsIWebBrowserChrome::CHROME_STATUSBAR;
1711   }
1712   if (aFeatures.GetBoolWithDefault("menubar", false, presenceFlag)) {
1713     chromeFlags |= nsIWebBrowserChrome::CHROME_MENUBAR;
1714   }
1715   if (aFeatures.GetBoolWithDefault("resizable", false, presenceFlag)) {
1716     chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_RESIZE;
1717   }
1718   if (aFeatures.GetBoolWithDefault("minimizable", false, presenceFlag)) {
1719     chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_MIN;
1720   }
1721 
1722   if (aFeatures.GetBoolWithDefault("scrollbars", true, presenceFlag)) {
1723     chromeFlags |= nsIWebBrowserChrome::CHROME_SCROLLBARS;
1724   }
1725 
1726   if (aHasChromeParent) {
1727     return chromeFlags;
1728   }
1729 
1730   // Web content isn't allowed to control UI visibility separately, but only
1731   // whether to open a popup or not.
1732   //
1733   // The above code is still necessary to calculate `presenceFlag`.
1734   // (`ShouldOpenPopup` early returns and doesn't check all feature)
1735 
1736   if (ShouldOpenPopup(aFeatures, aSizeSpec)) {
1737     // Flags for opening a popup, that doesn't have the following:
1738     //   * nsIWebBrowserChrome::CHROME_TOOLBAR
1739     //   * nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR
1740     //   * nsIWebBrowserChrome::CHROME_MENUBAR
1741     return aInitialFlags | nsIWebBrowserChrome::CHROME_TITLEBAR |
1742            nsIWebBrowserChrome::CHROME_WINDOW_CLOSE |
1743            nsIWebBrowserChrome::CHROME_LOCATIONBAR |
1744            nsIWebBrowserChrome::CHROME_STATUSBAR |
1745            nsIWebBrowserChrome::CHROME_WINDOW_RESIZE |
1746            nsIWebBrowserChrome::CHROME_WINDOW_MIN |
1747            nsIWebBrowserChrome::CHROME_SCROLLBARS;
1748   }
1749 
1750   // Otherwise open the current/new tab in the current/new window
1751   // (depends on browser.link.open_newwindow).
1752   return aInitialFlags | nsIWebBrowserChrome::CHROME_ALL;
1753 }
1754 
1755 // static
EnsureFlagsSafeForContent(uint32_t aChromeFlags,bool aChromeURL)1756 uint32_t nsWindowWatcher::EnsureFlagsSafeForContent(uint32_t aChromeFlags,
1757                                                     bool aChromeURL) {
1758   aChromeFlags |= nsIWebBrowserChrome::CHROME_TITLEBAR;
1759   aChromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_CLOSE;
1760   aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_LOWERED;
1761   aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_RAISED;
1762   aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_POPUP;
1763   /* Untrusted script is allowed to pose modal windows with a chrome
1764      scheme. This check could stand to be better. But it effectively
1765      prevents untrusted script from opening modal windows in general
1766      while still allowing alerts and the like. */
1767   if (!aChromeURL) {
1768     aChromeFlags &= ~(nsIWebBrowserChrome::CHROME_MODAL |
1769                       nsIWebBrowserChrome::CHROME_OPENAS_CHROME);
1770   }
1771 
1772   if (!(aChromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_CHROME)) {
1773     aChromeFlags &= ~nsIWebBrowserChrome::CHROME_DEPENDENT;
1774   }
1775 
1776   return aChromeFlags;
1777 }
1778 
1779 // Determine if we should open a new popup instead of a new tab.
1780 //
1781 // Also collect a telemetry how the popup/tab is requested, to help creating a
1782 // proposal for standardizing window.open's `features` parameter.
1783 //
1784 //  * NoPopup_{Empty, Other}
1785 //    (expected) Both current behavior and proposed behavior opens a non-popup
1786 //  * Popup_Width
1787 //    (expected) Both current behavior and proposed behavior opens a popup
1788 //  * Popup_{Location, Menubar, Resizable, Scrollbars, Status}
1789 //    (unexpected) Current behavior opens a popup, and proposed behavior opens
1790 //    a non-popup
1791 //
1792 // static
ShouldOpenPopup(const WindowFeatures & aFeatures,const SizeSpec & aSizeSpec)1793 bool nsWindowWatcher::ShouldOpenPopup(const WindowFeatures& aFeatures,
1794                                       const SizeSpec& aSizeSpec) {
1795   if (aFeatures.IsEmpty()) {
1796     AccumulateCategorical(
1797         mozilla::Telemetry::LABELS_WINDOW_OPEN_TYPE::NoPopup_Empty);
1798     return false;
1799   }
1800 
1801   // Follow Safari's behavior that opens a popup when width is specified.
1802   // This also follows current proposal.
1803   if (aSizeSpec.WidthSpecified()) {
1804     AccumulateCategorical(
1805         mozilla::Telemetry::LABELS_WINDOW_OPEN_TYPE::Popup_Width);
1806     return true;
1807   }
1808 
1809   // Remaining conditions excluding the last non-popup don't follow the
1810   // current proposal.
1811 
1812   // Follow Google Chrome's behavior that opens a popup depending on
1813   // the following features.
1814   if (!aFeatures.GetBoolWithDefault("location", false) &&
1815       !aFeatures.GetBoolWithDefault("toolbar", false)) {
1816     AccumulateCategorical(
1817         mozilla::Telemetry::LABELS_WINDOW_OPEN_TYPE::Popup_Location);
1818     return true;
1819   }
1820 
1821   if (!aFeatures.GetBoolWithDefault("menubar", false)) {
1822     AccumulateCategorical(
1823         mozilla::Telemetry::LABELS_WINDOW_OPEN_TYPE::Popup_Menubar);
1824     return true;
1825   }
1826 
1827   if (!aFeatures.GetBoolWithDefault("resizable", true)) {
1828     AccumulateCategorical(
1829         mozilla::Telemetry::LABELS_WINDOW_OPEN_TYPE::Popup_Resizable);
1830     return true;
1831   }
1832 
1833   if (!aFeatures.GetBoolWithDefault("scrollbars", false)) {
1834     AccumulateCategorical(
1835         mozilla::Telemetry::LABELS_WINDOW_OPEN_TYPE::Popup_Scrollbars);
1836     return true;
1837   }
1838 
1839   if (!aFeatures.GetBoolWithDefault("status", false)) {
1840     AccumulateCategorical(
1841         mozilla::Telemetry::LABELS_WINDOW_OPEN_TYPE::Popup_Status);
1842     return true;
1843   }
1844 
1845   AccumulateCategorical(
1846       mozilla::Telemetry::LABELS_WINDOW_OPEN_TYPE::NoPopup_Other);
1847   return false;
1848 }
1849 
1850 /**
1851  * Calculate the chrome bitmask from a string list of features requested
1852  * from a child process. The feature string can only control whether to open a
1853  * new tab or a new popup.
1854  * @param aFeatures a string containing a list of named features
1855  * @param aSizeSpec the result of CalcSizeSpec
1856  * @return the chrome bitmask
1857  */
1858 // static
CalculateChromeFlagsForContent(const WindowFeatures & aFeatures,const SizeSpec & aSizeSpec)1859 uint32_t nsWindowWatcher::CalculateChromeFlagsForContent(
1860     const WindowFeatures& aFeatures, const SizeSpec& aSizeSpec) {
1861   if (aFeatures.IsEmpty()) {
1862     return nsIWebBrowserChrome::CHROME_ALL;
1863   }
1864 
1865   uint32_t chromeFlags = CalculateChromeFlagsHelper(
1866       nsIWebBrowserChrome::CHROME_WINDOW_BORDERS, aFeatures, aSizeSpec);
1867 
1868   return EnsureFlagsSafeForContent(chromeFlags);
1869 }
1870 
1871 /**
1872  * Calculate the chrome bitmask from a string list of features for a new
1873  * privileged window.
1874  * @param aFeatures a string containing a list of named chrome features
1875  * @param aDialog affects the assumptions made about unnamed features
1876  * @param aChromeURL true if the window is being sent to a chrome:// URL
1877  * @param aHasChromeParent true if the parent window is privileged
1878  * @return the chrome bitmask
1879  */
1880 // static
CalculateChromeFlagsForSystem(const WindowFeatures & aFeatures,const SizeSpec & aSizeSpec,bool aDialog,bool aChromeURL,bool aHasChromeParent)1881 uint32_t nsWindowWatcher::CalculateChromeFlagsForSystem(
1882     const WindowFeatures& aFeatures, const SizeSpec& aSizeSpec, bool aDialog,
1883     bool aChromeURL, bool aHasChromeParent) {
1884   MOZ_ASSERT(XRE_IsParentProcess());
1885   MOZ_ASSERT(nsContentUtils::LegacyIsCallerChromeOrNativeCode());
1886 
1887   uint32_t chromeFlags = 0;
1888 
1889   // The features string is made void by OpenWindowInternal
1890   // if nullptr was originally passed as the features string.
1891   if (aFeatures.IsEmpty()) {
1892     chromeFlags = nsIWebBrowserChrome::CHROME_ALL;
1893     if (aDialog) {
1894       chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG |
1895                      nsIWebBrowserChrome::CHROME_OPENAS_CHROME;
1896     }
1897   } else {
1898     chromeFlags = nsIWebBrowserChrome::CHROME_WINDOW_BORDERS;
1899   }
1900 
1901   /* This function has become complicated since browser windows and
1902      dialogs diverged. The difference is, browser windows assume all
1903      chrome not explicitly mentioned is off, if the features string
1904      is not null. Exceptions are some OS border chrome new with Mozilla.
1905      Dialogs interpret a (mostly) empty features string to mean
1906      "OS's choice," and also support an "all" flag explicitly disallowed
1907      in the standards-compliant window.(normal)open. */
1908 
1909   bool presenceFlag = false;
1910   if (aDialog && aFeatures.GetBoolWithDefault("all", false, &presenceFlag)) {
1911     chromeFlags = nsIWebBrowserChrome::CHROME_ALL;
1912   }
1913 
1914   /* Next, allow explicitly named options to override the initial settings */
1915   chromeFlags = CalculateChromeFlagsHelper(chromeFlags, aFeatures, aSizeSpec,
1916                                            &presenceFlag, aHasChromeParent);
1917 
1918   // Determine whether the window is a private browsing window
1919   if (aFeatures.GetBoolWithDefault("private", false, &presenceFlag)) {
1920     chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
1921   }
1922   if (aFeatures.GetBoolWithDefault("non-private", false, &presenceFlag)) {
1923     chromeFlags |= nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW;
1924   }
1925 
1926   // Determine whether the window should have remote tabs.
1927   bool remote = BrowserTabsRemoteAutostart();
1928 
1929   if (remote) {
1930     remote = !aFeatures.GetBoolWithDefault("non-remote", false, &presenceFlag);
1931   } else {
1932     remote = aFeatures.GetBoolWithDefault("remote", false, &presenceFlag);
1933   }
1934 
1935   if (remote) {
1936     chromeFlags |= nsIWebBrowserChrome::CHROME_REMOTE_WINDOW;
1937   }
1938 
1939   // Determine whether the window should have remote subframes
1940   bool fission = FissionAutostart();
1941 
1942   if (fission) {
1943     fission =
1944         !aFeatures.GetBoolWithDefault("non-fission", false, &presenceFlag);
1945   } else {
1946     fission = aFeatures.GetBoolWithDefault("fission", false, &presenceFlag);
1947   }
1948 
1949   if (fission) {
1950     chromeFlags |= nsIWebBrowserChrome::CHROME_FISSION_WINDOW;
1951   }
1952 
1953   if (aFeatures.GetBoolWithDefault("popup", false, &presenceFlag)) {
1954     chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_POPUP;
1955   }
1956 
1957   /* OK.
1958      Normal browser windows, in spite of a stated pattern of turning off
1959      all chrome not mentioned explicitly, will want the new OS chrome (window
1960      borders, titlebars, closebox) on, unless explicitly turned off.
1961      Dialogs, on the other hand, take the absence of any explicit settings
1962      to mean "OS' choice." */
1963 
1964   // default titlebar and closebox to "on," if not mentioned at all
1965   if (!(chromeFlags & nsIWebBrowserChrome::CHROME_WINDOW_POPUP)) {
1966     if (!aFeatures.Exists("titlebar")) {
1967       chromeFlags |= nsIWebBrowserChrome::CHROME_TITLEBAR;
1968     }
1969     if (!aFeatures.Exists("close")) {
1970       chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_CLOSE;
1971     }
1972   }
1973 
1974   if (aDialog && !aFeatures.IsEmpty() && !presenceFlag) {
1975     chromeFlags = nsIWebBrowserChrome::CHROME_DEFAULT;
1976   }
1977 
1978   /* Finally, once all the above normal chrome has been divined, deal
1979      with the features that are more operating hints than appearance
1980      instructions. (Note modality implies dependence.) */
1981 
1982   if (aFeatures.GetBoolWithDefault("alwayslowered", false) ||
1983       aFeatures.GetBoolWithDefault("z-lock", false)) {
1984     chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_LOWERED;
1985   } else if (aFeatures.GetBoolWithDefault("alwaysraised", false)) {
1986     chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_RAISED;
1987   }
1988 
1989   if (aFeatures.GetBoolWithDefault("suppressanimation", false)) {
1990     chromeFlags |= nsIWebBrowserChrome::CHROME_SUPPRESS_ANIMATION;
1991   }
1992   if (aFeatures.GetBoolWithDefault("alwaysontop", false)) {
1993     chromeFlags |= nsIWebBrowserChrome::CHROME_ALWAYS_ON_TOP;
1994   }
1995   if (aFeatures.GetBoolWithDefault("chrome", false)) {
1996     chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_CHROME;
1997   }
1998   if (aFeatures.GetBoolWithDefault("extrachrome", false)) {
1999     chromeFlags |= nsIWebBrowserChrome::CHROME_EXTRA;
2000   }
2001   if (aFeatures.GetBoolWithDefault("centerscreen", false)) {
2002     chromeFlags |= nsIWebBrowserChrome::CHROME_CENTER_SCREEN;
2003   }
2004   if (aFeatures.GetBoolWithDefault("dependent", false)) {
2005     chromeFlags |= nsIWebBrowserChrome::CHROME_DEPENDENT;
2006   }
2007   if (aFeatures.GetBoolWithDefault("modal", false)) {
2008     chromeFlags |= nsIWebBrowserChrome::CHROME_MODAL |
2009                    nsIWebBrowserChrome::CHROME_DEPENDENT;
2010   }
2011 
2012   /* On mobile we want to ignore the dialog window feature, since the mobile UI
2013      does not provide any affordance for dialog windows. This does not interfere
2014      with dialog windows created through openDialog. */
2015   bool disableDialogFeature = false;
2016   nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID);
2017 
2018   branch->GetBoolPref("dom.disable_window_open_dialog_feature",
2019                       &disableDialogFeature);
2020 
2021   if (!disableDialogFeature) {
2022     if (aFeatures.GetBoolWithDefault("dialog", false)) {
2023       chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG;
2024     }
2025   }
2026 
2027   /* and dialogs need to have the last word. assume dialogs are dialogs,
2028      and opened as chrome, unless explicitly told otherwise. */
2029   if (aDialog) {
2030     if (!aFeatures.Exists("dialog")) {
2031       chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG;
2032     }
2033     if (!aFeatures.Exists("chrome")) {
2034       chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_CHROME;
2035     }
2036   }
2037 
2038   /* missing
2039      chromeFlags->copy_history
2040    */
2041 
2042   // Check security state for use in determing window dimensions
2043   if (!aHasChromeParent) {
2044     chromeFlags = EnsureFlagsSafeForContent(chromeFlags, aChromeURL);
2045   }
2046 
2047   return chromeFlags;
2048 }
2049 
GetBrowsingContextByName(const nsAString & aName,bool aForceNoOpener,BrowsingContext * aCurrentContext)2050 already_AddRefed<BrowsingContext> nsWindowWatcher::GetBrowsingContextByName(
2051     const nsAString& aName, bool aForceNoOpener,
2052     BrowsingContext* aCurrentContext) {
2053   if (aName.IsEmpty()) {
2054     return nullptr;
2055   }
2056 
2057   if (aForceNoOpener && !nsContentUtils::IsSpecialName(aName)) {
2058     // Ignore all other names in the noopener case.
2059     return nullptr;
2060   }
2061 
2062   RefPtr<BrowsingContext> foundContext;
2063   if (aCurrentContext) {
2064     foundContext = aCurrentContext->FindWithName(aName);
2065   } else if (!nsContentUtils::IsSpecialName(aName)) {
2066     // If we are looking for an item and we don't have a docshell we are
2067     // checking on, let's just look in the chrome browsing context group!
2068     for (RefPtr<BrowsingContext> toplevel :
2069          BrowsingContextGroup::GetChromeGroup()->Toplevels()) {
2070       foundContext = toplevel->FindWithNameInSubtree(aName, *toplevel);
2071       if (foundContext) {
2072         break;
2073       }
2074     }
2075   }
2076 
2077   return foundContext.forget();
2078 }
2079 
2080 // static
CalcSizeSpec(const WindowFeatures & aFeatures,bool aHasChromeParent,SizeSpec & aResult)2081 void nsWindowWatcher::CalcSizeSpec(const WindowFeatures& aFeatures,
2082                                    bool aHasChromeParent, SizeSpec& aResult) {
2083   // https://drafts.csswg.org/cssom-view/#set-up-browsing-context-features
2084   // To set up browsing context features for a browsing context `target` given
2085   // a map `tokenizedFeatures`:
2086 
2087   // Step 1. Let `x` be null.
2088   // (implicit)
2089 
2090   // Step 2. Let `y` be null.
2091   // (implicit)
2092 
2093   // Step 3. Let `width` be null.
2094   // (implicit)
2095 
2096   // Step 4. Let `height` be null.
2097   // (implicit)
2098 
2099   // Step 5. If `tokenizedFeatures["left"]` exists:
2100   if (aFeatures.Exists("left")) {
2101     // Step 5.1. Set `x` to the result of invoking the rules for parsing
2102     // integers on `tokenizedFeatures["left"]`.
2103     //
2104     // Step 5.2. If `x` is an error, set `x` to 0.
2105     int32_t x = aFeatures.GetInt("left");
2106 
2107     // Step 5.3. Optionally, clamp `x` in a user-agent-defined manner so that
2108     // the window does not move outside the Web-exposed available screen area.
2109     // (done later)
2110 
2111     // Step 5.4. Optionally, move `target`’s window such that the window’s
2112     // left edge is at the horizontal coordinate `x` relative to the left edge
2113     // of the Web-exposed screen area, measured in CSS pixels of target.
2114     // The positive axis is rightward.
2115     aResult.mLeft = x;
2116     aResult.mLeftSpecified = true;
2117   }
2118 
2119   // Step 6. If `tokenizedFeatures["top"]` exists:
2120   if (aFeatures.Exists("top")) {
2121     // Step 6.1. Set `y` to the result of invoking the rules for parsing
2122     // integers on `tokenizedFeatures["top"]`.
2123     //
2124     // Step 6.2. If `y` is an error, set `y` to 0.
2125     int32_t y = aFeatures.GetInt("top");
2126 
2127     // Step 6.3. Optionally, clamp `y` in a user-agent-defined manner so that
2128     // the window does not move outside the Web-exposed available screen area.
2129     // (done later)
2130 
2131     // Step 6.4. Optionally, move `target`’s window such that the window’s top
2132     // edge is at the vertical coordinate `y` relative to the top edge of the
2133     // Web-exposed screen area, measured in CSS pixels of target. The positive
2134     // axis is downward.
2135     aResult.mTop = y;
2136     aResult.mTopSpecified = true;
2137   }
2138 
2139   // Non-standard extension.
2140   // Not exposed to web content.
2141   if (aHasChromeParent && aFeatures.Exists("outerwidth")) {
2142     int32_t width = aFeatures.GetInt("outerwidth");
2143     if (width) {
2144       aResult.mOuterWidth = width;
2145       aResult.mOuterWidthSpecified = true;
2146     }
2147   }
2148 
2149   if (!aResult.mOuterWidthSpecified) {
2150     // Step 7. If `tokenizedFeatures["width"]` exists:
2151     if (aFeatures.Exists("width")) {
2152       // Step 7.1. Set `width` to the result of invoking the rules for parsing
2153       // integers on `tokenizedFeatures["width"]`.
2154       //
2155       // Step 7.2. If `width` is an error, set `width` to 0.
2156       int32_t width = aFeatures.GetInt("width");
2157 
2158       // Step 7.3. If `width` is not 0:
2159       if (width) {
2160         // Step 7.3.1. Optionally, clamp `width` in a user-agent-defined manner
2161         // so that the window does not get too small or bigger than the
2162         // Web-exposed available screen area.
2163         // (done later)
2164 
2165         // Step 7.3.2. Optionally, size `target`’s window by moving its right
2166         // edge such that the distance between the left and right edges of the
2167         // viewport are `width` CSS pixels of target.
2168         aResult.mInnerWidth = width;
2169         aResult.mInnerWidthSpecified = true;
2170 
2171         // Step 7.3.3. Optionally, move target’s window in a user-agent-defined
2172         // manner so that it does not grow outside the Web-exposed available
2173         // screen area.
2174         // (done later)
2175       }
2176     }
2177   }
2178 
2179   // Non-standard extension.
2180   // Not exposed to web content.
2181   if (aHasChromeParent && aFeatures.Exists("outerheight")) {
2182     int32_t height = aFeatures.GetInt("outerheight");
2183     if (height) {
2184       aResult.mOuterHeight = height;
2185       aResult.mOuterHeightSpecified = true;
2186     }
2187   }
2188 
2189   if (!aResult.mOuterHeightSpecified) {
2190     // Step 8. If `tokenizedFeatures["height"]` exists:
2191     if (aFeatures.Exists("height")) {
2192       // Step 8.1. Set `height` to the result of invoking the rules for parsing
2193       // integers on `tokenizedFeatures["height"]`.
2194       //
2195       // Step 8.2. If `height` is an error, set `height` to 0.
2196       int32_t height = aFeatures.GetInt("height");
2197 
2198       // Step 8.3. If `height` is not 0:
2199       if (height) {
2200         // Step 8.3.1. Optionally, clamp `height` in a user-agent-defined manner
2201         // so that the window does not get too small or bigger than the
2202         // Web-exposed available screen area.
2203         // (done later)
2204 
2205         // Step 8.3.2. Optionally, size `target`’s window by moving its bottom
2206         // edge such that the distance between the top and bottom edges of the
2207         // viewport are `height` CSS pixels of target.
2208         aResult.mInnerHeight = height;
2209         aResult.mInnerHeightSpecified = true;
2210 
2211         // Step 8.3.3. Optionally, move target’s window in a user-agent-defined
2212         // manner so that it does not grow outside the Web-exposed available
2213         // screen area.
2214         // (done later)
2215       }
2216     }
2217   }
2218 
2219   // NOTE: The value is handled only on chrome-priv code.
2220   // See nsWindowWatcher::SizeOpenedWindow.
2221   aResult.mLockAspectRatio =
2222       aFeatures.GetBoolWithDefault("lockaspectratio", false);
2223 }
2224 
2225 /* Size and position a new window according to aSizeSpec. This method
2226    is assumed to be called after the window has already been given
2227    a default position and size; thus its current position and size are
2228    accurate defaults. The new window is made visible at method end.
2229    @param aTreeOwner
2230           The top-level nsIDocShellTreeOwner of the newly opened window.
2231    @param aParent (optional)
2232           The parent window from which to inherit zoom factors from if
2233           aOpenerFullZoom is none.
2234    @param aIsCallerChrome
2235           True if the code requesting the new window is privileged.
2236    @param aSizeSpec
2237           The size that the new window should be.
2238    @param aOpenerFullZoom
2239           If not nothing, a zoom factor to scale the content to.
2240 */
SizeOpenedWindow(nsIDocShellTreeOwner * aTreeOwner,mozIDOMWindowProxy * aParent,bool aIsCallerChrome,const SizeSpec & aSizeSpec,const Maybe<float> & aOpenerFullZoom)2241 void nsWindowWatcher::SizeOpenedWindow(nsIDocShellTreeOwner* aTreeOwner,
2242                                        mozIDOMWindowProxy* aParent,
2243                                        bool aIsCallerChrome,
2244                                        const SizeSpec& aSizeSpec,
2245                                        const Maybe<float>& aOpenerFullZoom) {
2246   // We should only be sizing top-level windows if we're in the parent
2247   // process.
2248   MOZ_ASSERT(XRE_IsParentProcess());
2249 
2250   // position and size of window
2251   int32_t left = 0, top = 0, width = 100, height = 100;
2252   // difference between chrome and content size
2253   int32_t chromeWidth = 0, chromeHeight = 0;
2254   // whether the window size spec refers to chrome or content
2255   bool sizeChromeWidth = true, sizeChromeHeight = true;
2256 
2257   // get various interfaces for aDocShellItem, used throughout this method
2258   nsCOMPtr<nsIBaseWindow> treeOwnerAsWin(do_QueryInterface(aTreeOwner));
2259   if (!treeOwnerAsWin) {  // we'll need this to actually size the docshell
2260     return;
2261   }
2262 
2263   double openerZoom = aOpenerFullZoom.valueOr(1.0);
2264   if (aParent && aOpenerFullZoom.isNothing()) {
2265     nsCOMPtr<nsPIDOMWindowOuter> piWindow = nsPIDOMWindowOuter::From(aParent);
2266     if (Document* doc = piWindow->GetDoc()) {
2267       if (nsPresContext* presContext = doc->GetPresContext()) {
2268         openerZoom = presContext->GetFullZoom();
2269       }
2270     }
2271   }
2272 
2273   double scale = 1.0;
2274   treeOwnerAsWin->GetUnscaledDevicePixelsPerCSSPixel(&scale);
2275 
2276   /* The current position and size will be unchanged if not specified
2277      (and they fit entirely onscreen). Also, calculate the difference
2278      between chrome and content sizes on aDocShellItem's window.
2279      This latter point becomes important if chrome and content
2280      specifications are mixed in aFeatures, and when bringing the window
2281      back from too far off the right or bottom edges of the screen. */
2282 
2283   treeOwnerAsWin->GetPositionAndSize(&left, &top, &width, &height);
2284   left = NSToIntRound(left / scale);
2285   top = NSToIntRound(top / scale);
2286   width = NSToIntRound(width / scale);
2287   height = NSToIntRound(height / scale);
2288   {
2289     int32_t contentWidth, contentHeight;
2290     bool hasPrimaryContent = false;
2291     aTreeOwner->GetHasPrimaryContent(&hasPrimaryContent);
2292     if (hasPrimaryContent) {
2293       aTreeOwner->GetPrimaryContentSize(&contentWidth, &contentHeight);
2294     } else {
2295       aTreeOwner->GetRootShellSize(&contentWidth, &contentHeight);
2296     }
2297     chromeWidth = width - contentWidth;
2298     chromeHeight = height - contentHeight;
2299   }
2300 
2301   // Set up left/top
2302   if (aSizeSpec.mLeftSpecified) {
2303     left = NSToIntRound(aSizeSpec.mLeft * openerZoom);
2304   }
2305 
2306   if (aSizeSpec.mTopSpecified) {
2307     top = NSToIntRound(aSizeSpec.mTop * openerZoom);
2308   }
2309 
2310   // Set up width
2311   if (aSizeSpec.mOuterWidthSpecified) {
2312     width = NSToIntRound(aSizeSpec.mOuterWidth * openerZoom);
2313   } else if (aSizeSpec.mInnerWidthSpecified) {
2314     sizeChromeWidth = false;
2315     width = NSToIntRound(aSizeSpec.mInnerWidth * openerZoom);
2316   }
2317 
2318   // Set up height
2319   if (aSizeSpec.mOuterHeightSpecified) {
2320     height = NSToIntRound(aSizeSpec.mOuterHeight * openerZoom);
2321   } else if (aSizeSpec.mInnerHeightSpecified) {
2322     sizeChromeHeight = false;
2323     height = NSToIntRound(aSizeSpec.mInnerHeight * openerZoom);
2324   }
2325 
2326   bool positionSpecified = aSizeSpec.PositionSpecified();
2327 
2328   // Check security state for use in determing window dimensions
2329   bool enabled = false;
2330   if (aIsCallerChrome) {
2331     // Only enable special priveleges for chrome when chrome calls
2332     // open() on a chrome window
2333     nsCOMPtr<nsIDOMChromeWindow> chromeWin(do_QueryInterface(aParent));
2334     enabled = !aParent || chromeWin;
2335   }
2336 
2337   if (!enabled) {
2338     // Security check failed.  Ensure all args meet minimum reqs.
2339 
2340     int32_t oldTop = top, oldLeft = left;
2341 
2342     // We'll also need the screen dimensions
2343     nsCOMPtr<nsIScreen> screen;
2344     nsCOMPtr<nsIScreenManager> screenMgr(
2345         do_GetService("@mozilla.org/gfx/screenmanager;1"));
2346     if (screenMgr)
2347       screenMgr->ScreenForRect(left, top, width, height,
2348                                getter_AddRefs(screen));
2349     if (screen) {
2350       int32_t screenLeft, screenTop, screenWidth, screenHeight;
2351       int32_t winWidth = width + (sizeChromeWidth ? 0 : chromeWidth),
2352               winHeight = height + (sizeChromeHeight ? 0 : chromeHeight);
2353 
2354       // Get screen dimensions (in device pixels)
2355       screen->GetAvailRect(&screenLeft, &screenTop, &screenWidth,
2356                            &screenHeight);
2357       // Convert them to CSS pixels
2358       screenLeft = NSToIntRound(screenLeft / scale);
2359       screenTop = NSToIntRound(screenTop / scale);
2360       screenWidth = NSToIntRound(screenWidth / scale);
2361       screenHeight = NSToIntRound(screenHeight / scale);
2362 
2363       if (aSizeSpec.SizeSpecified()) {
2364         if (!nsContentUtils::ShouldResistFingerprinting()) {
2365           /* Unlike position, force size out-of-bounds check only if
2366              size actually was specified. Otherwise, intrinsically sized
2367              windows are broken. */
2368           if (height < 100) {
2369             height = 100;
2370             winHeight = height + (sizeChromeHeight ? 0 : chromeHeight);
2371           }
2372           if (winHeight > screenHeight) {
2373             height = screenHeight - (sizeChromeHeight ? 0 : chromeHeight);
2374           }
2375           if (width < 100) {
2376             width = 100;
2377             winWidth = width + (sizeChromeWidth ? 0 : chromeWidth);
2378           }
2379           if (winWidth > screenWidth) {
2380             width = screenWidth - (sizeChromeWidth ? 0 : chromeWidth);
2381           }
2382         } else {
2383           int32_t targetContentWidth = 0;
2384           int32_t targetContentHeight = 0;
2385 
2386           nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting(
2387               chromeWidth, chromeHeight, screenWidth, screenHeight, width,
2388               height, sizeChromeWidth, sizeChromeHeight, &targetContentWidth,
2389               &targetContentHeight);
2390 
2391           if (aSizeSpec.mInnerWidthSpecified ||
2392               aSizeSpec.mOuterWidthSpecified) {
2393             width = targetContentWidth;
2394             winWidth = width + (sizeChromeWidth ? 0 : chromeWidth);
2395           }
2396 
2397           if (aSizeSpec.mInnerHeightSpecified ||
2398               aSizeSpec.mOuterHeightSpecified) {
2399             height = targetContentHeight;
2400             winHeight = height + (sizeChromeHeight ? 0 : chromeHeight);
2401           }
2402         }
2403       }
2404 
2405       CheckedInt<decltype(left)> leftPlusWinWidth = left;
2406       leftPlusWinWidth += winWidth;
2407       if (!leftPlusWinWidth.isValid() ||
2408           leftPlusWinWidth.value() > screenLeft + screenWidth) {
2409         left = screenLeft + screenWidth - winWidth;
2410       }
2411       if (left < screenLeft) {
2412         left = screenLeft;
2413       }
2414 
2415       CheckedInt<decltype(top)> topPlusWinHeight = top;
2416       topPlusWinHeight += winHeight;
2417       if (!topPlusWinHeight.isValid() ||
2418           topPlusWinHeight.value() > screenTop + screenHeight) {
2419         top = screenTop + screenHeight - winHeight;
2420       }
2421       if (top < screenTop) {
2422         top = screenTop;
2423       }
2424 
2425       if (top != oldTop || left != oldLeft) {
2426         positionSpecified = true;
2427       }
2428     }
2429   }
2430 
2431   // size and position the window
2432 
2433   if (positionSpecified) {
2434     // Get the scale factor appropriate for the screen we're actually
2435     // positioning on.
2436     nsCOMPtr<nsIScreen> screen;
2437     nsCOMPtr<nsIScreenManager> screenMgr(
2438         do_GetService("@mozilla.org/gfx/screenmanager;1"));
2439     if (screenMgr) {
2440       screenMgr->ScreenForRect(left, top, 1, 1, getter_AddRefs(screen));
2441     }
2442     if (screen) {
2443       double cssToDevPixScale, desktopToDevPixScale;
2444       screen->GetDefaultCSSScaleFactor(&cssToDevPixScale);
2445       screen->GetContentsScaleFactor(&desktopToDevPixScale);
2446       double cssToDesktopScale = cssToDevPixScale / desktopToDevPixScale;
2447       int32_t screenLeft, screenTop, screenWd, screenHt;
2448       screen->GetRectDisplayPix(&screenLeft, &screenTop, &screenWd, &screenHt);
2449       // Adjust by desktop-pixel origin of the target screen when scaling
2450       // to convert from per-screen CSS-px coords to global desktop coords.
2451       treeOwnerAsWin->SetPositionDesktopPix(
2452           (left - screenLeft) * cssToDesktopScale + screenLeft,
2453           (top - screenTop) * cssToDesktopScale + screenTop);
2454     } else {
2455       // Couldn't find screen? This shouldn't happen.
2456       treeOwnerAsWin->SetPosition(left * scale, top * scale);
2457     }
2458     // This shouldn't be necessary, given the screen check above, but in case
2459     // moving the window didn't put it where we expected (e.g. due to issues
2460     // at the widget level, or whatever), let's re-fetch the scale factor for
2461     // wherever it really ended up
2462     treeOwnerAsWin->GetUnscaledDevicePixelsPerCSSPixel(&scale);
2463   }
2464   if (aSizeSpec.SizeSpecified()) {
2465     /* Prefer to trust the interfaces, which think in terms of pure
2466        chrome or content sizes. If we have a mix, use the chrome size
2467        adjusted by the chrome/content differences calculated earlier. */
2468     if (!sizeChromeWidth && !sizeChromeHeight) {
2469       bool hasPrimaryContent = false;
2470       aTreeOwner->GetHasPrimaryContent(&hasPrimaryContent);
2471       if (hasPrimaryContent) {
2472         aTreeOwner->SetPrimaryContentSize(width * scale, height * scale);
2473       } else {
2474         aTreeOwner->SetRootShellSize(width * scale, height * scale);
2475       }
2476     } else {
2477       if (!sizeChromeWidth) {
2478         width += chromeWidth;
2479       }
2480       if (!sizeChromeHeight) {
2481         height += chromeHeight;
2482       }
2483       treeOwnerAsWin->SetSize(width * scale, height * scale, false);
2484     }
2485   }
2486 
2487   if (aIsCallerChrome) {
2488     nsCOMPtr<nsIAppWindow> appWin = do_GetInterface(treeOwnerAsWin);
2489     if (appWin && aSizeSpec.mLockAspectRatio) {
2490       appWin->LockAspectRatio(true);
2491     }
2492   }
2493 
2494   treeOwnerAsWin->SetVisibility(true);
2495 }
2496 
2497 /* static */
GetWindowOpenLocation(nsPIDOMWindowOuter * aParent,uint32_t aChromeFlags,bool aCalledFromJS,bool aWidthSpecified,bool aIsForPrinting)2498 int32_t nsWindowWatcher::GetWindowOpenLocation(nsPIDOMWindowOuter* aParent,
2499                                                uint32_t aChromeFlags,
2500                                                bool aCalledFromJS,
2501                                                bool aWidthSpecified,
2502                                                bool aIsForPrinting) {
2503   // These windows are not actually visible to the user, so we return the thing
2504   // that we can always handle.
2505   if (aIsForPrinting) {
2506     return nsIBrowserDOMWindow::OPEN_PRINT_BROWSER;
2507   }
2508 
2509   // Where should we open this?
2510   int32_t containerPref;
2511   if (NS_FAILED(
2512           Preferences::GetInt("browser.link.open_newwindow", &containerPref))) {
2513     // We couldn't read the user preference, so fall back on the default.
2514     return nsIBrowserDOMWindow::OPEN_NEWTAB;
2515   }
2516 
2517   bool isDisabledOpenNewWindow =
2518       aParent->GetFullScreen() &&
2519       Preferences::GetBool(
2520           "browser.link.open_newwindow.disabled_in_fullscreen");
2521 
2522   if (isDisabledOpenNewWindow &&
2523       (containerPref == nsIBrowserDOMWindow::OPEN_NEWWINDOW)) {
2524     containerPref = nsIBrowserDOMWindow::OPEN_NEWTAB;
2525   }
2526 
2527   if (containerPref != nsIBrowserDOMWindow::OPEN_NEWTAB &&
2528       containerPref != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW) {
2529     // Just open a window normally
2530     return nsIBrowserDOMWindow::OPEN_NEWWINDOW;
2531   }
2532 
2533   if (aCalledFromJS) {
2534     /* Now check our restriction pref.  The restriction pref is a power-user's
2535        fine-tuning pref. values:
2536        0: no restrictions - divert everything
2537        1: don't divert window.open at all
2538        2: don't divert window.open with features
2539     */
2540     int32_t restrictionPref =
2541         Preferences::GetInt("browser.link.open_newwindow.restriction", 2);
2542     if (restrictionPref < 0 || restrictionPref > 2) {
2543       restrictionPref = 2;  // Sane default behavior
2544     }
2545 
2546     if (isDisabledOpenNewWindow) {
2547       // In browser fullscreen, the window should be opened
2548       // in the current window with no features (see bug 803675)
2549       restrictionPref = 0;
2550     }
2551 
2552     if (restrictionPref == 1) {
2553       return nsIBrowserDOMWindow::OPEN_NEWWINDOW;
2554     }
2555 
2556     if (restrictionPref == 2) {
2557       // Only continue if there are no width feature and no special
2558       // chrome flags - with the exception of the remoteness and private flags,
2559       // which might have been automatically flipped by Gecko.
2560       int32_t uiChromeFlags = aChromeFlags;
2561       uiChromeFlags &= ~(nsIWebBrowserChrome::CHROME_REMOTE_WINDOW |
2562                          nsIWebBrowserChrome::CHROME_FISSION_WINDOW |
2563                          nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW |
2564                          nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW |
2565                          nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME);
2566       if (uiChromeFlags != nsIWebBrowserChrome::CHROME_ALL || aWidthSpecified) {
2567         return nsIBrowserDOMWindow::OPEN_NEWWINDOW;
2568       }
2569     }
2570   }
2571 
2572   return containerPref;
2573 }
2574