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