diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /xpfe/appshell/AppWindow.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xpfe/appshell/AppWindow.cpp')
-rw-r--r-- | xpfe/appshell/AppWindow.cpp | 3498 |
1 files changed, 3498 insertions, 0 deletions
diff --git a/xpfe/appshell/AppWindow.cpp b/xpfe/appshell/AppWindow.cpp new file mode 100644 index 0000000000..a5dab3b157 --- /dev/null +++ b/xpfe/appshell/AppWindow.cpp @@ -0,0 +1,3498 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 ci et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ErrorList.h" +#include "mozilla/MathAlgorithms.h" + +// Local includes +#include "AppWindow.h" +#include <algorithm> + +// Helper classes +#include "nsPrintfCString.h" +#include "nsString.h" +#include "nsWidgetsCID.h" +#include "nsThreadUtils.h" +#include "nsNetCID.h" +#include "nsQueryObject.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/Sprintf.h" + +// Interfaces needed to be included +#include "nsGlobalWindowOuter.h" +#include "nsIAppShell.h" +#include "nsIAppShellService.h" +#include "nsIContentViewer.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/CanonicalBrowsingContext.h" +#include "nsPIDOMWindow.h" +#include "nsScreen.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIIOService.h" +#include "nsIObserverService.h" +#include "nsIOpenWindowInfo.h" +#include "nsIWindowMediator.h" +#include "nsIScreenManager.h" +#include "nsIScreen.h" +#include "nsIWindowWatcher.h" +#include "nsIURI.h" +#include "nsAppShellCID.h" +#include "nsReadableUtils.h" +#include "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsContentUtils.h" +#include "nsGlobalWindow.h" +#include "nsXULTooltipListener.h" +#include "nsXULPopupManager.h" +#include "nsFocusManager.h" +#include "nsContentList.h" +#include "nsIDOMWindowUtils.h" +#include "nsServiceManagerUtils.h" + +#include "prenv.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/Preferences.h" +#include "mozilla/PresShell.h" +#include "mozilla/Services.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/dom/BarProps.h" +#include "mozilla/dom/DOMRect.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/BrowserHost.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/LoadURIOptionsBinding.h" +#include "mozilla/intl/LocaleService.h" +#include "mozilla/EventDispatcher.h" + +#ifdef XP_WIN +# include "mozilla/PreXULSkeletonUI.h" +# include "nsIWindowsUIUtils.h" +#endif + +#include "mozilla/dom/DocumentL10n.h" + +#ifdef XP_MACOSX +# include "mozilla/widget/NativeMenuSupport.h" +# define USE_NATIVE_MENUS +#endif + +#define SIZEMODE_NORMAL u"normal"_ns +#define SIZEMODE_MAXIMIZED u"maximized"_ns +#define SIZEMODE_MINIMIZED u"minimized"_ns +#define SIZEMODE_FULLSCREEN u"fullscreen"_ns + +#define SIZE_PERSISTENCE_TIMEOUT 500 // msec + +//***************************************************************************** +//*** AppWindow: Object Management +//***************************************************************************** + +namespace mozilla { + +using dom::AutoNoJSAPI; +using dom::BrowserHost; +using dom::BrowsingContext; +using dom::Document; +using dom::DocumentL10n; +using dom::Element; +using dom::EventTarget; +using dom::LoadURIOptions; +using dom::Promise; + +AppWindow::AppWindow(uint32_t aChromeFlags) + : mChromeTreeOwner(nullptr), + mContentTreeOwner(nullptr), + mPrimaryContentTreeOwner(nullptr), + mModalStatus(NS_OK), + mFullscreenChangeState(FullscreenChangeState::NotChanging), + mContinueModalLoop(false), + mDebuting(false), + mChromeLoaded(false), + mSizingShellFromXUL(false), + mShowAfterLoad(false), + mIntrinsicallySized(false), + mCenterAfterLoad(false), + mIsHiddenWindow(false), + mLockedUntilChromeLoad(false), + mIgnoreXULSize(false), + mIgnoreXULPosition(false), + mChromeFlagsFrozen(false), + mIgnoreXULSizeMode(false), + mDestroying(false), + mRegistered(false), + mDominantClientSize(false), + mChromeFlags(aChromeFlags), + mWidgetListenerDelegate(this) {} + +AppWindow::~AppWindow() { + if (mSPTimer) { + mSPTimer->Cancel(); + mSPTimer = nullptr; + } + Destroy(); +} + +//***************************************************************************** +// AppWindow::nsISupports +//***************************************************************************** + +NS_IMPL_ADDREF(AppWindow) +NS_IMPL_RELEASE(AppWindow) + +NS_INTERFACE_MAP_BEGIN(AppWindow) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAppWindow) + NS_INTERFACE_MAP_ENTRY(nsIAppWindow) + NS_INTERFACE_MAP_ENTRY(nsIBaseWindow) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) + NS_INTERFACE_MAP_ENTRY_CONCRETE(AppWindow) +NS_INTERFACE_MAP_END + +nsresult AppWindow::Initialize(nsIAppWindow* aParent, nsIAppWindow* aOpener, + int32_t aInitialWidth, int32_t aInitialHeight, + bool aIsHiddenWindow, + widget::InitData& widgetInitData) { + nsresult rv; + nsCOMPtr<nsIWidget> parentWidget; + + mIsHiddenWindow = aIsHiddenWindow; + + DesktopIntPoint initialPos; + nsCOMPtr<nsIBaseWindow> base(do_QueryInterface(aOpener)); + if (base) { + LayoutDeviceIntRect rect = base->GetPositionAndSize(); + mOpenerScreenRect = + DesktopIntRect::Round(rect / base->DevicePixelsPerDesktopPixel()); + if (!mOpenerScreenRect.IsEmpty()) { + initialPos = mOpenerScreenRect.TopLeft(); + ConstrainToOpenerScreen(&initialPos.x.value, &initialPos.y.value); + } + } + + // XXX: need to get the default window size from prefs... + // Doesn't come from prefs... will come from CSS/XUL/RDF + DesktopIntRect deskRect(initialPos, + DesktopIntSize(aInitialWidth, aInitialHeight)); + + // Create top level window + if (gfxPlatform::IsHeadless()) { + mWindow = nsIWidget::CreateHeadlessWidget(); + } else { + mWindow = nsIWidget::CreateTopLevelWindow(); + } + if (!mWindow) { + return NS_ERROR_FAILURE; + } + + /* This next bit is troublesome. We carry two different versions of a pointer + to our parent window. One is the parent window's widget, which is passed + to our own widget. The other is a weak reference we keep here to our + parent AppWindow. The former is useful to the widget, and we can't + trust its treatment of the parent reference because they're platform- + specific. The latter is useful to this class. + A better implementation would be one in which the parent keeps strong + references to its children and closes them before it allows itself + to be closed. This would mimic the behaviour of OSes that support + top-level child windows in OSes that do not. Later. + */ + nsCOMPtr<nsIBaseWindow> parentAsWin(do_QueryInterface(aParent)); + if (parentAsWin) { + parentAsWin->GetMainWidget(getter_AddRefs(parentWidget)); + mParentWindow = do_GetWeakReference(aParent); + } + + mWindow->SetWidgetListener(&mWidgetListenerDelegate); + rv = mWindow->Create((nsIWidget*)parentWidget, // Parent nsIWidget + nullptr, // Native parent widget + deskRect, // Widget dimensions + &widgetInitData); // Widget initialization data + NS_ENSURE_SUCCESS(rv, rv); + + LayoutDeviceIntRect r = mWindow->GetClientBounds(); + // Match the default background color of content. Important on windows + // since we no longer use content child widgets. + mWindow->SetBackgroundColor(NS_RGB(255, 255, 255)); + + // All Chrome BCs exist within the same BrowsingContextGroup, so we don't need + // to pass in the opener window here. The opener is set later, if needed, by + // nsWindowWatcher. + RefPtr<BrowsingContext> browsingContext = + BrowsingContext::CreateIndependent(BrowsingContext::Type::Chrome); + + // Create web shell + mDocShell = nsDocShell::Create(browsingContext); + NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE); + + // Make sure to set the item type on the docshell _before_ calling + // InitWindow() so it knows what type it is. + NS_ENSURE_SUCCESS(EnsureChromeTreeOwner(), NS_ERROR_FAILURE); + + mDocShell->SetTreeOwner(mChromeTreeOwner); + + r.MoveTo(0, 0); + NS_ENSURE_SUCCESS(mDocShell->InitWindow(nullptr, mWindow, r.X(), r.Y(), + r.Width(), r.Height()), + NS_ERROR_FAILURE); + + // Attach a WebProgress listener.during initialization... + mDocShell->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_NETWORK); + + mWindow->MaybeDispatchInitialFocusEvent(); + + return rv; +} + +//***************************************************************************** +// AppWindow::nsIIntefaceRequestor +//***************************************************************************** + +NS_IMETHODIMP AppWindow::GetInterface(const nsIID& aIID, void** aSink) { + nsresult rv; + + NS_ENSURE_ARG_POINTER(aSink); + + if (aIID.Equals(NS_GET_IID(nsIPrompt))) { + rv = EnsurePrompter(); + if (NS_FAILED(rv)) return rv; + return mPrompter->QueryInterface(aIID, aSink); + } + if (aIID.Equals(NS_GET_IID(nsIAuthPrompt))) { + rv = EnsureAuthPrompter(); + if (NS_FAILED(rv)) return rv; + return mAuthPrompter->QueryInterface(aIID, aSink); + } + if (aIID.Equals(NS_GET_IID(mozIDOMWindowProxy))) { + return GetWindowDOMWindow(reinterpret_cast<mozIDOMWindowProxy**>(aSink)); + } + if (aIID.Equals(NS_GET_IID(nsIDOMWindow))) { + nsCOMPtr<mozIDOMWindowProxy> window = nullptr; + rv = GetWindowDOMWindow(getter_AddRefs(window)); + nsCOMPtr<nsIDOMWindow> domWindow = do_QueryInterface(window); + domWindow.forget(aSink); + return rv; + } + if (aIID.Equals(NS_GET_IID(nsIWebBrowserChrome)) && + NS_SUCCEEDED(EnsureContentTreeOwner()) && + NS_SUCCEEDED(mContentTreeOwner->QueryInterface(aIID, aSink))) { + return NS_OK; + } + + return QueryInterface(aIID, aSink); +} + +//***************************************************************************** +// AppWindow::nsIAppWindow +//***************************************************************************** + +NS_IMETHODIMP AppWindow::GetDocShell(nsIDocShell** aDocShell) { + NS_ENSURE_ARG_POINTER(aDocShell); + + *aDocShell = mDocShell; + NS_IF_ADDREF(*aDocShell); + return NS_OK; +} + +NS_IMETHODIMP AppWindow::GetZLevel(uint32_t* outLevel) { + nsCOMPtr<nsIWindowMediator> mediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (mediator) + mediator->GetZLevel(this, outLevel); + else + *outLevel = normalZ; + return NS_OK; +} + +NS_IMETHODIMP AppWindow::SetZLevel(uint32_t aLevel) { + nsCOMPtr<nsIWindowMediator> mediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (!mediator) return NS_ERROR_FAILURE; + + uint32_t zLevel; + mediator->GetZLevel(this, &zLevel); + if (zLevel == aLevel) return NS_OK; + + /* refuse to raise a maximized window above the normal browser level, + for fear it could hide newly opened browser windows */ + if (aLevel > nsIAppWindow::normalZ && mWindow) { + nsSizeMode sizeMode = mWindow->SizeMode(); + if (sizeMode == nsSizeMode_Maximized || sizeMode == nsSizeMode_Fullscreen) { + return NS_ERROR_FAILURE; + } + } + + // do it + mediator->SetZLevel(this, aLevel); + PersistentAttributesDirty(PersistentAttribute::Misc, Sync); + + nsCOMPtr<nsIContentViewer> cv; + mDocShell->GetContentViewer(getter_AddRefs(cv)); + if (cv) { + RefPtr<dom::Document> doc = cv->GetDocument(); + if (doc) { + ErrorResult rv; + RefPtr<dom::Event> event = + doc->CreateEvent(u"Events"_ns, dom::CallerType::System, rv); + if (event) { + event->InitEvent(u"windowZLevel"_ns, true, false); + + event->SetTrusted(true); + + doc->DispatchEvent(*event); + } + } + } + return NS_OK; +} + +NS_IMETHODIMP AppWindow::GetChromeFlags(uint32_t* aChromeFlags) { + NS_ENSURE_ARG_POINTER(aChromeFlags); + *aChromeFlags = mChromeFlags; + return NS_OK; +} + +NS_IMETHODIMP AppWindow::SetChromeFlags(uint32_t aChromeFlags) { + NS_ASSERTION(!mChromeFlagsFrozen, + "SetChromeFlags() after AssumeChromeFlagsAreFrozen()!"); + + mChromeFlags = aChromeFlags; + if (mChromeLoaded) { + ApplyChromeFlags(); + } + return NS_OK; +} + +NS_IMETHODIMP AppWindow::AssumeChromeFlagsAreFrozen() { + mChromeFlagsFrozen = true; + return NS_OK; +} + +NS_IMETHODIMP AppWindow::SetIntrinsicallySized(bool aIntrinsicallySized) { + mIntrinsicallySized = aIntrinsicallySized; + return NS_OK; +} + +NS_IMETHODIMP AppWindow::GetIntrinsicallySized(bool* aIntrinsicallySized) { + NS_ENSURE_ARG_POINTER(aIntrinsicallySized); + + *aIntrinsicallySized = mIntrinsicallySized; + return NS_OK; +} + +NS_IMETHODIMP AppWindow::GetPrimaryContentShell( + nsIDocShellTreeItem** aDocShellTreeItem) { + NS_ENSURE_ARG_POINTER(aDocShellTreeItem); + NS_IF_ADDREF(*aDocShellTreeItem = mPrimaryContentShell); + return NS_OK; +} + +NS_IMETHODIMP +AppWindow::RemoteTabAdded(nsIRemoteTab* aTab, bool aPrimary) { + if (aPrimary) { + mPrimaryBrowserParent = aTab; + mPrimaryContentShell = nullptr; + } else if (mPrimaryBrowserParent == aTab) { + mPrimaryBrowserParent = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +AppWindow::RemoteTabRemoved(nsIRemoteTab* aTab) { + if (aTab == mPrimaryBrowserParent) { + mPrimaryBrowserParent = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +AppWindow::GetPrimaryRemoteTab(nsIRemoteTab** aTab) { + nsCOMPtr<nsIRemoteTab> tab = mPrimaryBrowserParent; + tab.forget(aTab); + return NS_OK; +} + +NS_IMETHODIMP +AppWindow::GetPrimaryContentBrowsingContext( + mozilla::dom::BrowsingContext** aBc) { + if (mPrimaryBrowserParent) { + return mPrimaryBrowserParent->GetBrowsingContext(aBc); + } + if (mPrimaryContentShell) { + return mPrimaryContentShell->GetBrowsingContextXPCOM(aBc); + } + *aBc = nullptr; + return NS_OK; +} + +static LayoutDeviceIntSize GetOuterToInnerSizeDifference(nsIWidget* aWindow) { + if (!aWindow) { + return LayoutDeviceIntSize(); + } + return aWindow->ClientToWindowSizeDifference(); +} + +static CSSIntSize GetOuterToInnerSizeDifferenceInCSSPixels( + nsIWidget* aWindow, CSSToLayoutDeviceScale aScale) { + LayoutDeviceIntSize devPixelSize = GetOuterToInnerSizeDifference(aWindow); + return RoundedToInt(devPixelSize / aScale); +} + +NS_IMETHODIMP +AppWindow::GetOuterToInnerHeightDifferenceInCSSPixels(uint32_t* aResult) { + *aResult = GetOuterToInnerSizeDifferenceInCSSPixels( + mWindow, UnscaledDevicePixelsPerCSSPixel()) + .height; + return NS_OK; +} + +NS_IMETHODIMP +AppWindow::GetOuterToInnerWidthDifferenceInCSSPixels(uint32_t* aResult) { + *aResult = GetOuterToInnerSizeDifferenceInCSSPixels( + mWindow, UnscaledDevicePixelsPerCSSPixel()) + .width; + return NS_OK; +} + +nsTArray<RefPtr<mozilla::LiveResizeListener>> +AppWindow::GetLiveResizeListeners() { + nsTArray<RefPtr<mozilla::LiveResizeListener>> listeners; + if (mPrimaryBrowserParent) { + BrowserHost* host = BrowserHost::GetFrom(mPrimaryBrowserParent.get()); + RefPtr<mozilla::LiveResizeListener> actor = host->GetActor(); + if (actor) { + listeners.AppendElement(actor); + } + } + return listeners; +} + +NS_IMETHODIMP AppWindow::AddChildWindow(nsIAppWindow* aChild) { + // we're not really keeping track of this right now + return NS_OK; +} + +NS_IMETHODIMP AppWindow::RemoveChildWindow(nsIAppWindow* aChild) { + // we're not really keeping track of this right now + return NS_OK; +} + +NS_IMETHODIMP AppWindow::ShowModal() { + AUTO_PROFILER_LABEL("AppWindow::ShowModal", OTHER); + + if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { + MOZ_ASSERT_UNREACHABLE( + "Trying to show modal window after shutdown started."); + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + // Store locally so it doesn't die on us + nsCOMPtr<nsIWidget> window = mWindow; + nsCOMPtr<nsIAppWindow> tempRef = this; + + window->SetModal(true); + mContinueModalLoop = true; + EnableParent(false); + + { + AutoNoJSAPI nojsapi; + SpinEventLoopUntil("AppWindow::ShowModal"_ns, [&]() { + if (MOZ_UNLIKELY( + AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed))) { + // TODO: Bug 1699041 would apply also here: Should we return an error + // if we are bailing out from a pre-existing modal dialog for shutdown? + ExitModalLoop(NS_OK); + } + return !mContinueModalLoop; + }); + } + + mContinueModalLoop = false; + window->SetModal(false); + /* Note there's no EnableParent(true) here to match the false one + above. That's done in ExitModalLoop. It's important that the parent + be re-enabled before this window is made invisible; to do otherwise + causes bizarre z-ordering problems. At this point, the window is + already invisible. + No known current implementation of Enable would have a problem with + re-enabling the parent twice, so we could do it again here without + breaking any current implementation. But that's unnecessary if the + modal loop is always exited using ExitModalLoop (the other way would be + to change the protected member variable directly.) + */ + + return mModalStatus; +} + +//***************************************************************************** +// AppWindow::nsIBaseWindow +//***************************************************************************** + +NS_IMETHODIMP AppWindow::InitWindow(nativeWindow aParentNativeWindow, + nsIWidget* parentWidget, int32_t x, + int32_t y, int32_t cx, int32_t cy) { + // XXX First Check In + NS_ASSERTION(false, "Not Yet Implemented"); + return NS_OK; +} + +NS_IMETHODIMP AppWindow::Destroy() { + nsCOMPtr<nsIAppWindow> kungFuDeathGrip(this); + + if (mDocShell) { + mDocShell->RemoveProgressListener(this); + } + + if (mSPTimer) { + mSPTimer->Cancel(); + SavePersistentAttributes(); + mSPTimer = nullptr; + } + + if (!mWindow) return NS_OK; + + // Ensure we don't reenter this code + if (mDestroying) return NS_OK; + + mozilla::AutoRestore<bool> guard(mDestroying); + mDestroying = true; + + nsCOMPtr<nsIAppShellService> appShell( + do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); + NS_ASSERTION(appShell, "Couldn't get appShell... xpcom shutdown?"); + if (appShell) + appShell->UnregisterTopLevelWindow(static_cast<nsIAppWindow*>(this)); + + nsCOMPtr<nsIAppWindow> parentWindow(do_QueryReferent(mParentWindow)); + if (parentWindow) parentWindow->RemoveChildWindow(this); + + // Remove modality (if any) and hide while destroying. More than + // a convenience, the hide prevents user interaction with the partially + // destroyed window. This is especially necessary when the eldest window + // in a stack of modal windows is destroyed first. It happens. + ExitModalLoop(NS_OK); + // XXX: Skip unmapping the window on Linux due to GLX hangs on the compositor + // thread with NVIDIA driver 310.32. We don't need to worry about user + // interactions with destroyed windows on X11 either. +#ifndef MOZ_WIDGET_GTK + if (mWindow) mWindow->Show(false); +#endif + +#if defined(XP_WIN) + // We need to explicitly set the focus on Windows, but + // only if the parent is visible. + nsCOMPtr<nsIBaseWindow> parent(do_QueryReferent(mParentWindow)); + if (parent) { + nsCOMPtr<nsIWidget> parentWidget; + parent->GetMainWidget(getter_AddRefs(parentWidget)); + + if (parentWidget && parentWidget->IsVisible()) { + bool isParentHiddenWindow = false; + + if (appShell) { + bool hasHiddenWindow = false; + appShell->GetHasHiddenWindow(&hasHiddenWindow); + if (hasHiddenWindow) { + nsCOMPtr<nsIBaseWindow> baseHiddenWindow; + nsCOMPtr<nsIAppWindow> hiddenWindow; + appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow)); + if (hiddenWindow) { + baseHiddenWindow = do_GetInterface(hiddenWindow); + isParentHiddenWindow = (baseHiddenWindow == parent); + } + } + } + + // somebody screwed up somewhere. hiddenwindow shouldn't be anybody's + // parent. still, when it happens, skip activating it. + if (!isParentHiddenWindow) { + parentWidget->PlaceBehind(eZPlacementTop, 0, true); + } + } + } +#endif + + RemoveTooltipSupport(); + + mDOMWindow = nullptr; + if (mDocShell) { + RefPtr<BrowsingContext> bc(mDocShell->GetBrowsingContext()); + mDocShell->Destroy(); + bc->Detach(); + mDocShell = nullptr; // this can cause reentrancy of this function + } + + mPrimaryContentShell = nullptr; + + if (mContentTreeOwner) { + mContentTreeOwner->AppWindow(nullptr); + NS_RELEASE(mContentTreeOwner); + } + if (mPrimaryContentTreeOwner) { + mPrimaryContentTreeOwner->AppWindow(nullptr); + NS_RELEASE(mPrimaryContentTreeOwner); + } + if (mChromeTreeOwner) { + mChromeTreeOwner->AppWindow(nullptr); + NS_RELEASE(mChromeTreeOwner); + } + if (mWindow) { + mWindow->SetWidgetListener(nullptr); // nsWebShellWindow hackery + mWindow->Destroy(); + mWindow = nullptr; + } + + if (!mIsHiddenWindow && mRegistered) { + /* Inform appstartup we've destroyed this window and it could + quit now if it wanted. This must happen at least after mDocShell + is destroyed, because onunload handlers fire then, and those being + script, anything could happen. A new window could open, even. + See bug 130719. */ + nsCOMPtr<nsIObserverService> obssvc = services::GetObserverService(); + NS_ASSERTION(obssvc, "Couldn't get observer service?"); + + if (obssvc) + obssvc->NotifyObservers(nullptr, "xul-window-destroyed", nullptr); + } + + return NS_OK; +} + +NS_IMETHODIMP AppWindow::GetDevicePixelsPerDesktopPixel(double* aScale) { + *aScale = mWindow ? mWindow->GetDesktopToDeviceScale().scale : 1.0; + return NS_OK; +} + +double AppWindow::GetWidgetCSSToDeviceScale() { + return mWindow ? mWindow->GetDefaultScale().scale : 1.0; +} + +NS_IMETHODIMP AppWindow::SetPositionDesktopPix(int32_t aX, int32_t aY) { + return MoveResize(Some(DesktopIntPoint(aX, aY)), Nothing(), false); +} + +// The parameters here are device pixels; do the best we can to convert to +// desktop px, using the window's current scale factor (if available). +NS_IMETHODIMP AppWindow::SetPosition(int32_t aX, int32_t aY) { + // Don't reset the window's size mode here - platforms that don't want to move + // maximized windows should reset it in their respective Move implementation. + return MoveResize(Some(LayoutDeviceIntPoint(aX, aY)), Nothing(), false); +} + +NS_IMETHODIMP AppWindow::GetPosition(int32_t* aX, int32_t* aY) { + return GetPositionAndSize(aX, aY, nullptr, nullptr); +} + +NS_IMETHODIMP AppWindow::SetSize(int32_t aCX, int32_t aCY, bool aRepaint) { + /* any attempt to set the window's size or position overrides the window's + zoom state. this is important when these two states are competing while + the window is being opened. but it should probably just always be so. */ + return MoveResize(Nothing(), Some(LayoutDeviceIntSize(aCX, aCY)), aRepaint); +} + +NS_IMETHODIMP AppWindow::GetSize(int32_t* aCX, int32_t* aCY) { + return GetPositionAndSize(nullptr, nullptr, aCX, aCY); +} + +NS_IMETHODIMP AppWindow::SetPositionAndSize(int32_t aX, int32_t aY, int32_t aCX, + int32_t aCY, uint32_t aFlags) { + /* any attempt to set the window's size or position overrides the window's + zoom state. this is important when these two states are competing while + the window is being opened. but it should probably just always be so. */ + return MoveResize(Some(LayoutDeviceIntPoint(aX, aY)), + Some(LayoutDeviceIntSize(aCX, aCY)), + !!(aFlags & nsIBaseWindow::eRepaint)); +} + +NS_IMETHODIMP AppWindow::GetPositionAndSize(int32_t* x, int32_t* y, int32_t* cx, + int32_t* cy) { + if (!mWindow) return NS_ERROR_FAILURE; + + LayoutDeviceIntRect rect = mWindow->GetScreenBounds(); + + if (x) *x = rect.X(); + if (y) *y = rect.Y(); + if (cx) *cx = rect.Width(); + if (cy) *cy = rect.Height(); + + return NS_OK; +} + +NS_IMETHODIMP +AppWindow::SetDimensions(DimensionRequest&& aRequest) { + if (aRequest.mDimensionKind == DimensionKind::Inner) { + // For the chrome the inner size is the root shell size, and for the + // content it's the primary content size. We lack an indicator here that + // would allow us to distinguish between the two. + return NS_ERROR_NOT_IMPLEMENTED; + } + + MOZ_TRY(aRequest.SupplementFrom(this)); + return aRequest.ApplyOuterTo(this); +} + +NS_IMETHODIMP +AppWindow::GetDimensions(DimensionKind aDimensionKind, int32_t* aX, int32_t* aY, + int32_t* aCX, int32_t* aCY) { + if (aDimensionKind == DimensionKind::Inner) { + // For the chrome the inner size is the root shell size, and for the + // content it's the primary content size. We lack an indicator here that + // would allow us to distinguish between the two. + return NS_ERROR_NOT_IMPLEMENTED; + } + return GetPositionAndSize(aX, aY, aCX, aCY); +} + +nsresult AppWindow::MoveResize(const Maybe<LayoutDeviceIntPoint>& aPosition, + const Maybe<LayoutDeviceIntSize>& aSize, + bool aRepaint) { + DesktopToLayoutDeviceScale scale = mWindow->GetDesktopToDeviceScale(); + + return MoveResize(aPosition ? Some(*aPosition / scale) : Nothing(), + aSize ? Some(*aSize / scale) : Nothing(), aRepaint); +} + +nsresult AppWindow::MoveResize(const Maybe<DesktopPoint>& aPosition, + const Maybe<DesktopSize>& aSize, bool aRepaint) { + NS_ENSURE_STATE(mWindow); + PersistentAttributes dirtyAttributes; + + if (!aPosition && !aSize) { + MOZ_ASSERT_UNREACHABLE("Doing nothing?"); + return NS_ERROR_UNEXPECTED; + } + + if (aSize) { + mWindow->SetSizeMode(nsSizeMode_Normal); + mIntrinsicallySized = false; + mDominantClientSize = false; + } + + if (aPosition && aSize) { + mWindow->Resize(aPosition->x, aPosition->y, aSize->width, aSize->height, + aRepaint); + dirtyAttributes = {PersistentAttribute::Size, + PersistentAttribute::Position}; + } else if (aSize) { + mWindow->Resize(aSize->width, aSize->height, aRepaint); + dirtyAttributes = {PersistentAttribute::Size}; + } else if (aPosition) { + mWindow->Move(aPosition->x, aPosition->y); + dirtyAttributes = {PersistentAttribute::Position}; + } + + if (mSizingShellFromXUL) { + // If we're invoked for sizing from XUL, we want to neither ignore anything + // nor persist anything, since it's already the value in XUL. + return NS_OK; + } + if (!mChromeLoaded) { + // If we're called before the chrome is loaded someone obviously wants this + // window at this size & in the normal size mode (since it is the only mode + // in which setting dimensions makes sense). We don't persist this one-time + // position/size. + if (aPosition) { + mIgnoreXULPosition = true; + } + if (aSize) { + mIgnoreXULSize = true; + mIgnoreXULSizeMode = true; + } + return NS_OK; + } + + PersistentAttributesDirty(dirtyAttributes, Sync); + return NS_OK; +} + +NS_IMETHODIMP AppWindow::Center(nsIAppWindow* aRelative, bool aScreen, + bool aAlert) { + DesktopIntRect rect; + bool screenCoordinates = false, windowCoordinates = false; + nsresult result; + + if (!mChromeLoaded) { + // note we lose the parameters. at time of writing, this isn't a problem. + mCenterAfterLoad = true; + return NS_OK; + } + + if (!aScreen && !aRelative) return NS_ERROR_INVALID_ARG; + + nsCOMPtr<nsIScreenManager> screenmgr = + do_GetService("@mozilla.org/gfx/screenmanager;1", &result); + if (NS_FAILED(result)) { + return result; + } + + nsCOMPtr<nsIScreen> screen; + + if (aRelative) { + nsCOMPtr<nsIBaseWindow> base(do_QueryInterface(aRelative)); + if (base) { + rect = RoundedToInt(base->GetPositionAndSize() / + base->DevicePixelsPerDesktopPixel()); + // if centering on screen, convert that to the corresponding screen + if (aScreen) { + screen = screenmgr->ScreenForRect(rect); + } else { + windowCoordinates = true; + } + } + } + if (!aRelative) { + if (!mOpenerScreenRect.IsEmpty()) { + screen = screenmgr->ScreenForRect(mOpenerScreenRect); + } else { + screenmgr->GetPrimaryScreen(getter_AddRefs(screen)); + } + } + + if (aScreen && screen) { + rect = screen->GetAvailRectDisplayPix(); + screenCoordinates = true; + } + + if (!screenCoordinates && !windowCoordinates) { + return NS_ERROR_FAILURE; + } + + NS_ASSERTION(mWindow, "what, no window?"); + const LayoutDeviceIntSize ourDevSize = GetSize(); + const DesktopIntSize ourSize = + RoundedToInt(ourDevSize / DevicePixelsPerDesktopPixel()); + auto newPos = + rect.TopLeft() + + DesktopIntPoint((rect.width - ourSize.width) / 2, + (rect.height - ourSize.height) / (aAlert ? 3 : 2)); + if (windowCoordinates) { + mWindow->ConstrainPosition(newPos); + } + + SetPositionDesktopPix(newPos.x, newPos.y); + + // If moving the window caused it to change size, re-do the centering. + if (GetSize() != ourDevSize) { + return Center(aRelative, aScreen, aAlert); + } + return NS_OK; +} + +NS_IMETHODIMP AppWindow::Repaint(bool aForce) { + // XXX First Check In + NS_ASSERTION(false, "Not Yet Implemented"); + return NS_OK; +} + +NS_IMETHODIMP AppWindow::GetParentWidget(nsIWidget** aParentWidget) { + NS_ENSURE_ARG_POINTER(aParentWidget); + NS_ENSURE_STATE(mWindow); + + NS_IF_ADDREF(*aParentWidget = mWindow->GetParent()); + return NS_OK; +} + +NS_IMETHODIMP AppWindow::SetParentWidget(nsIWidget* aParentWidget) { + // XXX First Check In + NS_ASSERTION(false, "Not Yet Implemented"); + return NS_OK; +} + +NS_IMETHODIMP AppWindow::GetParentNativeWindow( + nativeWindow* aParentNativeWindow) { + NS_ENSURE_ARG_POINTER(aParentNativeWindow); + + nsCOMPtr<nsIWidget> parentWidget; + NS_ENSURE_SUCCESS(GetParentWidget(getter_AddRefs(parentWidget)), + NS_ERROR_FAILURE); + + if (parentWidget) { + *aParentNativeWindow = parentWidget->GetNativeData(NS_NATIVE_WIDGET); + } + + return NS_OK; +} + +NS_IMETHODIMP AppWindow::SetParentNativeWindow( + nativeWindow aParentNativeWindow) { + // XXX First Check In + NS_ASSERTION(false, "Not Yet Implemented"); + return NS_OK; +} + +NS_IMETHODIMP AppWindow::GetNativeHandle(nsAString& aNativeHandle) { + nsCOMPtr<nsIWidget> mainWidget; + NS_ENSURE_SUCCESS(GetMainWidget(getter_AddRefs(mainWidget)), + NS_ERROR_FAILURE); + + if (mainWidget) { + nativeWindow nativeWindowPtr = mainWidget->GetNativeData(NS_NATIVE_WINDOW); + /* the nativeWindow pointer is converted to and exposed as a string. This + is a more reliable way not to lose information (as opposed to JS + |Number| for instance) */ + aNativeHandle = + NS_ConvertASCIItoUTF16(nsPrintfCString("0x%p", nativeWindowPtr)); + } + + return NS_OK; +} + +NS_IMETHODIMP AppWindow::GetVisibility(bool* aVisibility) { + NS_ENSURE_ARG_POINTER(aVisibility); + + // Always claim to be visible for now. See bug + // https://bugzilla.mozilla.org/show_bug.cgi?id=306245. + + *aVisibility = true; + + return NS_OK; +} + +NS_IMETHODIMP AppWindow::SetVisibility(bool aVisibility) { + if (!mChromeLoaded) { + mShowAfterLoad = aVisibility; + return NS_OK; + } + + if (mDebuting) { + return NS_OK; + } + + NS_ENSURE_STATE(mDocShell); + + mDebuting = true; // (Show / Focus is recursive) + + // XXXTAB Do we really need to show docshell and the window? Isn't + // the window good enough? + mDocShell->SetVisibility(aVisibility); + // Store locally so it doesn't die on us. 'Show' can result in the window + // being closed with AppWindow::Destroy being called. That would set + // mWindow to null and posibly destroy the nsIWidget while its Show method + // is on the stack. We need to keep it alive until Show finishes. + nsCOMPtr<nsIWidget> window = mWindow; + window->Show(aVisibility); + + nsCOMPtr<nsIWindowMediator> windowMediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (windowMediator) + windowMediator->UpdateWindowTimeStamp(static_cast<nsIAppWindow*>(this)); + + // notify observers so that we can hide the splash screen if possible + nsCOMPtr<nsIObserverService> obssvc = services::GetObserverService(); + NS_ASSERTION(obssvc, "Couldn't get observer service."); + if (obssvc) { + obssvc->NotifyObservers(static_cast<nsIAppWindow*>(this), + "xul-window-visible", nullptr); + } + + mDebuting = false; + return NS_OK; +} + +NS_IMETHODIMP AppWindow::GetEnabled(bool* aEnabled) { + NS_ENSURE_ARG_POINTER(aEnabled); + + if (mWindow) { + *aEnabled = mWindow->IsEnabled(); + return NS_OK; + } + + *aEnabled = true; // better guess than most + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP AppWindow::SetEnabled(bool aEnable) { + if (mWindow) { + mWindow->Enable(aEnable); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP AppWindow::GetMainWidget(nsIWidget** aMainWidget) { + NS_ENSURE_ARG_POINTER(aMainWidget); + NS_IF_ADDREF(*aMainWidget = mWindow); + return NS_OK; +} + +NS_IMETHODIMP AppWindow::GetTitle(nsAString& aTitle) { + aTitle = mTitle; + return NS_OK; +} + +NS_IMETHODIMP AppWindow::SetTitle(const nsAString& aTitle) { + NS_ENSURE_STATE(mWindow); + mTitle.Assign(aTitle); + mTitle.StripCRLF(); + NS_ENSURE_SUCCESS(mWindow->SetTitle(mTitle), NS_ERROR_FAILURE); + return NS_OK; +} + +//***************************************************************************** +// AppWindow: Helpers +//***************************************************************************** + +NS_IMETHODIMP AppWindow::EnsureChromeTreeOwner() { + if (mChromeTreeOwner) return NS_OK; + + mChromeTreeOwner = new nsChromeTreeOwner(); + NS_ADDREF(mChromeTreeOwner); + mChromeTreeOwner->AppWindow(this); + + return NS_OK; +} + +NS_IMETHODIMP AppWindow::EnsureContentTreeOwner() { + if (mContentTreeOwner) return NS_OK; + + mContentTreeOwner = new nsContentTreeOwner(false); + NS_ADDREF(mContentTreeOwner); + mContentTreeOwner->AppWindow(this); + + return NS_OK; +} + +NS_IMETHODIMP AppWindow::EnsurePrimaryContentTreeOwner() { + if (mPrimaryContentTreeOwner) return NS_OK; + + mPrimaryContentTreeOwner = new nsContentTreeOwner(true); + NS_ADDREF(mPrimaryContentTreeOwner); + mPrimaryContentTreeOwner->AppWindow(this); + + return NS_OK; +} + +NS_IMETHODIMP AppWindow::EnsurePrompter() { + if (mPrompter) return NS_OK; + + nsCOMPtr<mozIDOMWindowProxy> ourWindow; + nsresult rv = GetWindowDOMWindow(getter_AddRefs(ourWindow)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIWindowWatcher> wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID); + if (wwatch) wwatch->GetNewPrompter(ourWindow, getter_AddRefs(mPrompter)); + } + return mPrompter ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP AppWindow::EnsureAuthPrompter() { + if (mAuthPrompter) return NS_OK; + + nsCOMPtr<mozIDOMWindowProxy> ourWindow; + nsresult rv = GetWindowDOMWindow(getter_AddRefs(ourWindow)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIWindowWatcher> wwatch( + do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + if (wwatch) + wwatch->GetNewAuthPrompter(ourWindow, getter_AddRefs(mAuthPrompter)); + } + return mAuthPrompter ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP AppWindow::GetAvailScreenSize(int32_t* aAvailWidth, + int32_t* aAvailHeight) { + nsCOMPtr<mozIDOMWindowProxy> domWindow; + GetWindowDOMWindow(getter_AddRefs(domWindow)); + NS_ENSURE_STATE(domWindow); + + auto* window = nsGlobalWindowOuter::Cast(domWindow); + + RefPtr<nsScreen> screen = window->GetScreen(); + NS_ENSURE_STATE(screen); + + ErrorResult rv; + *aAvailWidth = screen->GetAvailWidth(rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + *aAvailHeight = screen->GetAvailHeight(rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + return NS_OK; +} + +// Rounds window size to 1000x1000, or, if there isn't enough available +// screen space, to a multiple of 200x100. +NS_IMETHODIMP AppWindow::ForceRoundedDimensions() { + if (mIsHiddenWindow) { + return NS_OK; + } + + CSSToLayoutDeviceScale scale = UnscaledDevicePixelsPerCSSPixel(); + + CSSIntSize availSizeCSS; + GetAvailScreenSize(&availSizeCSS.width, &availSizeCSS.height); + + // To get correct chrome size, we have to resize the window to a proper + // size first. So, here, we size it to its available size. + SetSpecifiedSize(availSizeCSS.width, availSizeCSS.height); + + // Get the current window size for calculating chrome UI size. + CSSIntSize windowSizeCSS = RoundedToInt(GetSize() / scale); + + // Get the content size for calculating chrome UI size. + LayoutDeviceIntSize contentSizeDev; + GetPrimaryContentSize(&contentSizeDev.width, &contentSizeDev.height); + CSSIntSize contentSizeCSS = RoundedToInt(contentSizeDev / scale); + + // Calculate the chrome UI size. + CSSIntSize chromeSizeCSS = windowSizeCSS - contentSizeCSS; + + CSSIntSize targetSizeCSS; + // Here, we use the available screen dimensions as the input dimensions to + // force the window to be rounded as the maximum available content size. + nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting( + chromeSizeCSS.width, chromeSizeCSS.height, availSizeCSS.width, + availSizeCSS.height, availSizeCSS.width, availSizeCSS.height, + false, // aSetOuterWidth + false, // aSetOuterHeight + &targetSizeCSS.width, &targetSizeCSS.height); + + LayoutDeviceIntSize targetSizeDev = RoundedToInt(targetSizeCSS * scale); + + SetPrimaryContentSize(targetSizeDev.width, targetSizeDev.height); + + return NS_OK; +} + +void AppWindow::OnChromeLoaded() { + nsresult rv = EnsureContentTreeOwner(); + + if (NS_SUCCEEDED(rv)) { + mChromeLoaded = true; + ApplyChromeFlags(); + SyncAttributesToWidget(); + if (mWindow) { + SizeShell(); + if (mShowAfterLoad) { + SetVisibility(true); + } + AddTooltipSupport(); + } + // At this point the window may have been closed already during Show() or + // SyncAttributesToWidget(), so AppWindow::Destroy may already have been + // called. Take care! + } + mPersistentAttributesMask += AllPersistentAttributes(); +} + +bool AppWindow::NeedsTooltipListener() { + nsCOMPtr<dom::Element> docShellElement = GetWindowDOMElement(); + if (!docShellElement || docShellElement->IsXULElement()) { + // Tooltips in XUL are handled by each element. + return false; + } + // All other non-XUL document types need a tooltip listener. + return true; +} + +void AppWindow::AddTooltipSupport() { + if (!NeedsTooltipListener()) { + return; + } + nsXULTooltipListener* listener = nsXULTooltipListener::GetInstance(); + if (!listener) { + return; + } + + nsCOMPtr<dom::Element> docShellElement = GetWindowDOMElement(); + MOZ_ASSERT(docShellElement); + listener->AddTooltipSupport(docShellElement); +} + +void AppWindow::RemoveTooltipSupport() { + if (!NeedsTooltipListener()) { + return; + } + nsXULTooltipListener* listener = nsXULTooltipListener::GetInstance(); + if (!listener) { + return; + } + + nsCOMPtr<dom::Element> docShellElement = GetWindowDOMElement(); + MOZ_ASSERT(docShellElement); + listener->RemoveTooltipSupport(docShellElement); +} + +static Maybe<int32_t> ReadIntAttribute(const Element& aElement, + nsAtom* aPrimary, + nsAtom* aSecondary = nullptr) { + nsAutoString attrString; + if (!aElement.GetAttr(aPrimary, attrString)) { + if (aSecondary) { + return ReadIntAttribute(aElement, aSecondary); + } + return Nothing(); + } + + nsresult res = NS_OK; + int32_t ret = attrString.ToInteger(&res); + return NS_SUCCEEDED(res) ? Some(ret) : Nothing(); +} + +// If aSpecWidth and/or aSpecHeight are > 0, we will use these CSS px sizes +// to fit to the screen when staggering windows; if they're negative, +// we use the window's current size instead. +bool AppWindow::LoadPositionFromXUL(int32_t aSpecWidth, int32_t aSpecHeight) { + bool gotPosition = false; + + // if we're the hidden window, don't try to validate our size/position. We're + // special. + if (mIsHiddenWindow) { + return false; + } + + RefPtr<dom::Element> root = GetWindowDOMElement(); + NS_ENSURE_TRUE(root, false); + + const LayoutDeviceIntRect devRect = GetPositionAndSize(); + + // Convert to global display pixels for consistent window management across + // screens with diverse resolutions + const DesktopIntPoint curPoint = + RoundedToInt(devRect.TopLeft() / DevicePixelsPerDesktopPixel()); + + // For size, use specified value if > 0, else current value + CSSIntSize cssSize(aSpecWidth, aSpecHeight); + { + CSSIntSize currentSize = + RoundedToInt(devRect.Size() / UnscaledDevicePixelsPerCSSPixel()); + if (aSpecHeight <= 0) { + cssSize.height = currentSize.height; + } + if (aSpecWidth <= 0) { + cssSize.width = currentSize.width; + } + } + + // Obtain the position information from the <xul:window> element. + DesktopIntPoint specPoint = curPoint; + + // Also read lowercase screenx/y because the front-end sometimes sets these + // via setAttribute on HTML documents like about:blank, and stuff gets + // lowercased. + // + // TODO(emilio): We should probably rename screenX/Y to screen-x/y to + // prevent this impedance mismatch. + if (auto attr = + ReadIntAttribute(*root, nsGkAtoms::screenX, nsGkAtoms::screenx)) { + specPoint.x = *attr; + gotPosition = true; + } + + if (auto attr = + ReadIntAttribute(*root, nsGkAtoms::screenY, nsGkAtoms::screeny)) { + specPoint.y = *attr; + gotPosition = true; + } + + if (gotPosition) { + // Our position will be relative to our parent, if any + nsCOMPtr<nsIBaseWindow> parent(do_QueryReferent(mParentWindow)); + if (parent) { + const DesktopIntPoint parentPos = RoundedToInt( + parent->GetPosition() / parent->DevicePixelsPerDesktopPixel()); + specPoint += parentPos; + } else { + StaggerPosition(specPoint.x.value, specPoint.y.value, cssSize.width, + cssSize.height); + } + } + mWindow->ConstrainPosition(specPoint); + if (specPoint != curPoint) { + SetPositionDesktopPix(specPoint.x, specPoint.y); + } + + return gotPosition; +} + +static Maybe<int32_t> ReadSize(const Element& aElement, nsAtom* aAttr, + nsAtom* aMinAttr, nsAtom* aMaxAttr) { + Maybe<int32_t> attr = ReadIntAttribute(aElement, aAttr); + if (!attr) { + return Nothing(); + } + + int32_t min = + std::max(100, ReadIntAttribute(aElement, aMinAttr).valueOr(100)); + int32_t max = ReadIntAttribute(aElement, aMaxAttr) + .valueOr(std::numeric_limits<int32_t>::max()); + + return Some(std::min(max, std::max(*attr, min))); +} + +bool AppWindow::LoadSizeFromXUL(int32_t& aSpecWidth, int32_t& aSpecHeight) { + bool gotSize = false; + + // if we're the hidden window, don't try to validate our size/position. We're + // special. + if (mIsHiddenWindow) { + return false; + } + + nsCOMPtr<dom::Element> windowElement = GetWindowDOMElement(); + NS_ENSURE_TRUE(windowElement, false); + + // Obtain the sizing information from the <xul:window> element. + aSpecWidth = 100; + aSpecHeight = 100; + + if (auto width = ReadSize(*windowElement, nsGkAtoms::width, + nsGkAtoms::minwidth, nsGkAtoms::maxwidth)) { + aSpecWidth = *width; + gotSize = true; + } + + if (auto height = ReadSize(*windowElement, nsGkAtoms::height, + nsGkAtoms::minheight, nsGkAtoms::maxheight)) { + aSpecHeight = *height; + gotSize = true; + } + + return gotSize; +} + +void AppWindow::SetSpecifiedSize(int32_t aSpecWidth, int32_t aSpecHeight) { + // These are in CSS pixels of the main window. + // TODO(emilio): In my testing we usually have a pres context around, can we + // just use it? That'd simplify the coordinate calculations. + { + int32_t screenWidth; + int32_t screenHeight; + + if (NS_SUCCEEDED(GetAvailScreenSize(&screenWidth, &screenHeight))) { + if (aSpecWidth > screenWidth) { + aSpecWidth = screenWidth; + } + if (aSpecHeight > screenHeight) { + aSpecHeight = screenHeight; + } + } + } + + NS_ASSERTION(mWindow, "we expected to have a window already"); + + mIntrinsicallySized = false; + + // Convert specified values to device pixels, and resize + auto newSize = RoundedToInt(CSSIntSize(aSpecWidth, aSpecHeight) * + UnscaledDevicePixelsPerCSSPixel()); + + // Note: Because of the asynchronous resizing on Linux we have to call + // SetSize even when the size doesn't appear to change. A previous call that + // has yet to complete can still change the size. We want the latest call to + // define the final size. + SetSize(newSize.width, newSize.height, false); +} + +/* Miscellaneous persistent attributes are attributes named in the + |persist| attribute, other than size and position. Those are special + because it's important to load those before one of the misc + attributes (sizemode) and they require extra processing. */ +bool AppWindow::UpdateWindowStateFromMiscXULAttributes() { + bool gotState = false; + + /* There are no misc attributes of interest to the hidden window. + It's especially important not to try to validate that window's + size or position, because some platforms (Mac OS X) need to + make it visible and offscreen. */ + if (mIsHiddenWindow) return false; + + nsCOMPtr<dom::Element> windowElement = GetWindowDOMElement(); + NS_ENSURE_TRUE(windowElement, false); + + nsAutoString stateString; + nsSizeMode sizeMode = nsSizeMode_Normal; + + // If we are told to ignore the size mode attribute, force + // normal sizemode. + if (mIgnoreXULSizeMode) { + windowElement->SetAttr(nsGkAtoms::sizemode, SIZEMODE_NORMAL, + IgnoreErrors()); + } else { + // Otherwise, read sizemode from DOM and, if the window is resizable, + // set it later. + windowElement->GetAttr(nsGkAtoms::sizemode, stateString); + if ((stateString.Equals(SIZEMODE_MAXIMIZED) || + stateString.Equals(SIZEMODE_FULLSCREEN))) { + /* Honor request to maximize only if the window is sizable. + An unsizable, unmaximizable, yet maximized window confuses + Windows OS and is something of a travesty, anyway. */ + if (mChromeFlags & nsIWebBrowserChrome::CHROME_WINDOW_RESIZE) { + mIntrinsicallySized = false; + + if (stateString.Equals(SIZEMODE_MAXIMIZED)) + sizeMode = nsSizeMode_Maximized; + else + sizeMode = nsSizeMode_Fullscreen; + } + } + } + + if (sizeMode == nsSizeMode_Fullscreen) { + nsCOMPtr<mozIDOMWindowProxy> ourWindow; + GetWindowDOMWindow(getter_AddRefs(ourWindow)); + auto* piWindow = nsPIDOMWindowOuter::From(ourWindow); + piWindow->SetFullScreen(true); + } else { + // For maximized windows, ignore the XUL size and position attributes, + // as setting them would set the window back to normal sizemode. + if (sizeMode == nsSizeMode_Maximized) { + mIgnoreXULSize = true; + mIgnoreXULPosition = true; + } + mWindow->SetSizeMode(sizeMode); + } + gotState = true; + + // zlevel + windowElement->GetAttr(nsGkAtoms::zlevel, stateString); + if (!stateString.IsEmpty()) { + nsresult errorCode; + int32_t zLevel = stateString.ToInteger(&errorCode); + if (NS_SUCCEEDED(errorCode) && zLevel >= lowestZ && zLevel <= highestZ) + SetZLevel(zLevel); + } + + return gotState; +} + +/* Stagger windows of the same type so they don't appear on top of each other. + This code does have a scary double loop -- it'll keep passing through + the entire list of open windows until it finds a non-collision. Doesn't + seem to be a problem, but it deserves watching. + The aRequested{X,Y} parameters here are in desktop pixels; + the aSpec{Width,Height} parameters are CSS pixel dimensions. +*/ +void AppWindow::StaggerPosition(int32_t& aRequestedX, int32_t& aRequestedY, + int32_t aSpecWidth, int32_t aSpecHeight) { + // These "constants" will be converted from CSS to desktop pixels + // for the appropriate screen, assuming we find a screen to use... + // hence they're not actually declared const here. + int32_t kOffset = 22; + uint32_t kSlop = 4; + + bool keepTrying; + int bouncedX = 0, // bounced off vertical edge of screen + bouncedY = 0; // bounced off horizontal edge + + // look for any other windows of this type + nsCOMPtr<nsIWindowMediator> wm(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (!wm) return; + + nsCOMPtr<dom::Element> windowElement = GetWindowDOMElement(); + if (!windowElement) return; + + nsCOMPtr<nsIAppWindow> ourAppWindow(this); + + nsAutoString windowType; + windowElement->GetAttr(nsGkAtoms::windowtype, windowType); + + DesktopIntRect screenRect; + bool gotScreen = false; + + { // fetch screen coordinates + nsCOMPtr<nsIScreenManager> screenMgr( + do_GetService("@mozilla.org/gfx/screenmanager;1")); + if (screenMgr) { + nsCOMPtr<nsIScreen> ourScreen; + // The coordinates here are already display pixels + // XXX aSpecWidth and aSpecHeight are CSS pixels! + screenMgr->ScreenForRect(aRequestedX, aRequestedY, aSpecWidth, + aSpecHeight, getter_AddRefs(ourScreen)); + if (ourScreen) { + screenRect = ourScreen->GetAvailRectDisplayPix(); + + // Get the screen's scaling factors and convert staggering constants + // from CSS px to desktop pixel units + auto scale = ourScreen->GetCSSToDesktopScale(); + kOffset = (CSSCoord(kOffset) * scale).Rounded(); + kSlop = (CSSCoord(kSlop) * scale).Rounded(); + // Convert dimensions from CSS to desktop pixels + aSpecWidth = (CSSCoord(aSpecWidth) * scale).Rounded(); + aSpecHeight = (CSSCoord(aSpecHeight) * scale).Rounded(); + gotScreen = true; + } + } + } + + // One full pass through all windows of this type, repeat until no collisions. + do { + keepTrying = false; + nsCOMPtr<nsISimpleEnumerator> windowList; + wm->GetAppWindowEnumerator(windowType.get(), getter_AddRefs(windowList)); + + if (!windowList) break; + + // One full pass through all windows of this type, offset and stop on + // collision. + do { + bool more; + windowList->HasMoreElements(&more); + if (!more) break; + + nsCOMPtr<nsISupports> supportsWindow; + windowList->GetNext(getter_AddRefs(supportsWindow)); + + nsCOMPtr<nsIAppWindow> listAppWindow(do_QueryInterface(supportsWindow)); + if (listAppWindow != ourAppWindow) { + int32_t listX, listY; + nsCOMPtr<nsIBaseWindow> listBaseWindow( + do_QueryInterface(supportsWindow)); + listBaseWindow->GetPosition(&listX, &listY); + double scale; + if (NS_SUCCEEDED( + listBaseWindow->GetDevicePixelsPerDesktopPixel(&scale))) { + listX = NSToIntRound(listX / scale); + listY = NSToIntRound(listY / scale); + } + + if (Abs(listX - aRequestedX) <= kSlop && + Abs(listY - aRequestedY) <= kSlop) { + // collision! offset and start over + if (bouncedX & 0x1) + aRequestedX -= kOffset; + else + aRequestedX += kOffset; + aRequestedY += kOffset; + + if (gotScreen) { + // if we're moving to the right and we need to bounce... + if (!(bouncedX & 0x1) && + ((aRequestedX + aSpecWidth) > screenRect.XMost())) { + aRequestedX = screenRect.XMost() - aSpecWidth; + ++bouncedX; + } + + // if we're moving to the left and we need to bounce... + if ((bouncedX & 0x1) && aRequestedX < screenRect.X()) { + aRequestedX = screenRect.X(); + ++bouncedX; + } + + // if we hit the bottom then bounce to the top + if (aRequestedY + aSpecHeight > screenRect.YMost()) { + aRequestedY = screenRect.Y(); + ++bouncedY; + } + } + + /* loop around again, + but it's time to give up once we've covered the screen. + there's a potential infinite loop with lots of windows. */ + keepTrying = bouncedX < 2 || bouncedY == 0; + break; + } + } + } while (true); + } while (keepTrying); +} + +void AppWindow::SyncAttributesToWidget() { + nsCOMPtr<dom::Element> windowElement = GetWindowDOMElement(); + if (!windowElement) return; + + MOZ_DIAGNOSTIC_ASSERT(mWindow, "No widget on SyncAttributesToWidget?"); + + nsAutoString attr; + + // Some attributes can change the client size (e.g. chromemargin on Windows + // and MacOS). But we might want to keep it. + const LayoutDeviceIntSize oldClientSize = mWindow->GetClientSize(); + // We have to check now whether we want to restore the client size, as any + // change in size will reset its state. + bool maintainClientSize = mDominantClientSize; + + // "hidechrome" attribute + if (windowElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidechrome, + nsGkAtoms::_true, eCaseMatters)) { + mWindow->HideWindowChrome(true); + } + + NS_ENSURE_TRUE_VOID(mWindow); + + // "chromemargin" attribute + nsIntMargin margins; + windowElement->GetAttribute(u"chromemargin"_ns, attr); + if (nsContentUtils::ParseIntMarginValue(attr, margins)) { + mWindow->SetNonClientMargins( + LayoutDeviceIntMargin::FromUnknownMargin(margins)); + } + + NS_ENSURE_TRUE_VOID(mWindow); + + // "windowtype", "windowclass", "windowname" attributes + nsAutoString windowClassAttr, windowNameAttr; + windowElement->GetAttr(nsGkAtoms::windowtype, attr); + windowElement->GetAttribute(u"windowclass"_ns, windowClassAttr); + windowElement->GetAttribute(u"windowname"_ns, windowNameAttr); + mWindow->SetWindowClass(attr, windowClassAttr, windowNameAttr); + + NS_ENSURE_TRUE_VOID(mWindow); + + // "icon" attribute + windowElement->GetAttribute(u"icon"_ns, attr); + if (!attr.IsEmpty()) { + mWindow->SetIcon(attr); + + NS_ENSURE_TRUE_VOID(mWindow); + } + + // "drawtitle" attribute + windowElement->GetAttribute(u"drawtitle"_ns, attr); + mWindow->SetDrawsTitle(attr.LowerCaseEqualsLiteral("true")); + + NS_ENSURE_TRUE_VOID(mWindow); + + // "toggletoolbar" attribute + windowElement->GetAttribute(u"toggletoolbar"_ns, attr); + mWindow->SetShowsToolbarButton(attr.LowerCaseEqualsLiteral("true")); + + NS_ENSURE_TRUE_VOID(mWindow); + + // "macnativefullscreen" attribute + windowElement->GetAttribute(u"macnativefullscreen"_ns, attr); + mWindow->SetSupportsNativeFullscreen(attr.LowerCaseEqualsLiteral("true")); + + NS_ENSURE_TRUE_VOID(mWindow); + + // "macanimationtype" attribute + windowElement->GetAttribute(u"macanimationtype"_ns, attr); + if (attr.EqualsLiteral("document")) { + mWindow->SetWindowAnimationType(nsIWidget::eDocumentWindowAnimation); + } + + // Check if the client size did change and if we want to restore it. + if (maintainClientSize && mWindow->SizeMode() == nsSizeMode_Normal && + oldClientSize != mWindow->GetClientSize()) { + mWindow->ResizeClient(oldClientSize / mWindow->GetDesktopToDeviceScale(), + true); + mDominantClientSize = true; + } +} + +enum class ConversionDirection { + InnerToOuter, + OuterToInner, +}; + +static void ConvertWindowSize(nsIAppWindow* aWin, const nsAtom* aAttr, + ConversionDirection aDirection, + nsAString& aInOutString) { + MOZ_ASSERT(aWin); + MOZ_ASSERT(aAttr == nsGkAtoms::width || aAttr == nsGkAtoms::height); + + nsresult rv; + int32_t size = aInOutString.ToInteger(&rv); + if (NS_FAILED(rv)) { + return; + } + + int32_t sizeDiff = aAttr == nsGkAtoms::width + ? aWin->GetOuterToInnerWidthDifferenceInCSSPixels() + : aWin->GetOuterToInnerHeightDifferenceInCSSPixels(); + + if (!sizeDiff) { + return; + } + + int32_t multiplier = aDirection == ConversionDirection::InnerToOuter ? 1 : -1; + + CopyASCIItoUTF16(nsPrintfCString("%d", size + multiplier * sizeDiff), + aInOutString); +} + +nsresult AppWindow::GetPersistentValue(const nsAtom* aAttr, nsAString& aValue) { + nsCOMPtr<dom::Element> docShellElement = GetWindowDOMElement(); + if (!docShellElement) { + return NS_ERROR_FAILURE; + } + + nsAutoString windowElementId; + docShellElement->GetId(windowElementId); + // Elements must have an ID to be persisted. + if (windowElementId.IsEmpty()) { + return NS_OK; + } + + RefPtr<dom::Document> ownerDoc = docShellElement->OwnerDoc(); + nsIURI* docURI = ownerDoc->GetDocumentURI(); + if (!docURI) { + return NS_ERROR_FAILURE; + } + nsAutoCString utf8uri; + nsresult rv = docURI->GetSpec(utf8uri); + NS_ENSURE_SUCCESS(rv, rv); + NS_ConvertUTF8toUTF16 uri(utf8uri); + + if (!mLocalStore) { + mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1"); + if (NS_WARN_IF(!mLocalStore)) { + return NS_ERROR_NOT_INITIALIZED; + } + } + + rv = mLocalStore->GetValue(uri, windowElementId, nsDependentAtomString(aAttr), + aValue); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (aAttr == nsGkAtoms::width || aAttr == nsGkAtoms::height) { + // Convert attributes from outer size to inner size for top-level + // windows, see bug 1444525 & co. + ConvertWindowSize(this, aAttr, ConversionDirection::OuterToInner, aValue); + } + + return NS_OK; +} + +nsresult AppWindow::GetDocXulStoreKeys(nsString& aUriSpec, + nsString& aWindowElementId) { + nsCOMPtr<dom::Element> docShellElement = GetWindowDOMElement(); + if (!docShellElement) { + return NS_ERROR_FAILURE; + } + + docShellElement->GetId(aWindowElementId); + // Match the behavior of XULPersist and only persist values if the element + // has an ID. + if (aWindowElementId.IsEmpty()) { + return NS_OK; + } + + RefPtr<dom::Document> ownerDoc = docShellElement->OwnerDoc(); + nsIURI* docURI = ownerDoc->GetDocumentURI(); + if (!docURI) { + return NS_ERROR_FAILURE; + } + + nsAutoCString utf8uri; + nsresult rv = docURI->GetSpec(utf8uri); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + aUriSpec = NS_ConvertUTF8toUTF16(utf8uri); + + return NS_OK; +} + +nsresult AppWindow::MaybeSaveEarlyWindowPersistentValues( + const LayoutDeviceIntRect& aRect) { +#ifdef XP_WIN + nsAutoString uri; + nsAutoString windowElementId; + nsresult rv = GetDocXulStoreKeys(uri, windowElementId); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!windowElementId.EqualsLiteral("main-window") || + !uri.EqualsLiteral("chrome://browser/content/browser.xhtml")) { + return NS_OK; + } + + SkeletonUISettings settings; + + settings.screenX = aRect.X(); + settings.screenY = aRect.Y(); + settings.width = aRect.Width(); + settings.height = aRect.Height(); + + settings.maximized = mWindow->SizeMode() == nsSizeMode_Maximized; + settings.cssToDevPixelScaling = UnscaledDevicePixelsPerCSSPixel().scale; + + nsCOMPtr<dom::Element> windowElement = GetWindowDOMElement(); + Document* doc = windowElement->GetComposedDoc(); + Element* urlbarEl = doc->GetElementById(u"urlbar"_ns); + + nsCOMPtr<nsPIDOMWindowOuter> window = mDocShell->GetWindow(); + nsCOMPtr<nsIDOMWindowUtils> utils = + nsGlobalWindowOuter::Cast(window)->WindowUtils(); + RefPtr<dom::DOMRect> urlbarRect; + rv = utils->GetBoundsWithoutFlushing(urlbarEl, getter_AddRefs(urlbarRect)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + double urlbarX = urlbarRect->X(); + double urlbarWidth = urlbarRect->Width(); + + // Hard-coding the following values and this behavior in general is rather + // fragile, and can easily get out of sync with the actual front-end values. + // This is not intended as a long-term solution, but only as the relatively + // straightforward implementation of an experimental feature. If we want to + // ship the skeleton UI to all users, we should strongly consider a more + // robust solution than this. The vertical position of the urlbar will be + // fixed. + nsAutoString attributeValue; + urlbarEl->GetAttribute(u"breakout-extend"_ns, attributeValue); + // Scale down the urlbar if it is focused + if (attributeValue.EqualsLiteral("true")) { + // defined in browser.inc.css as 2px + int urlbarBreakoutExtend = 2; + // defined in urlbar-searchbar.inc.css as 5px + int urlbarMarginInline = 5; + + // breakout-extend measurements are defined in urlbar-searchbar.inc.css + urlbarX += (double)(urlbarBreakoutExtend + urlbarMarginInline); + urlbarWidth -= (double)(2 * (urlbarBreakoutExtend + urlbarMarginInline)); + } + CSSPixelSpan urlbar; + urlbar.start = urlbarX; + urlbar.end = urlbar.start + urlbarWidth; + settings.urlbarSpan = urlbar; + + Element* navbar = doc->GetElementById(u"nav-bar"_ns); + + Element* searchbarEl = doc->GetElementById(u"searchbar"_ns); + CSSPixelSpan searchbar; + if (navbar->Contains(searchbarEl)) { + RefPtr<dom::DOMRect> searchbarRect; + rv = utils->GetBoundsWithoutFlushing(searchbarEl, + getter_AddRefs(searchbarRect)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + searchbar.start = searchbarRect->X(); + searchbar.end = searchbar.start + searchbarRect->Width(); + } else { + // There is no searchbar in the UI + searchbar.start = 0; + searchbar.end = 0; + } + settings.searchbarSpan = searchbar; + + nsAutoString bookmarksVisibility; + Preferences::GetString("browser.toolbars.bookmarks.visibility", + bookmarksVisibility); + settings.bookmarksToolbarShown = + bookmarksVisibility.EqualsLiteral("always") || + bookmarksVisibility.EqualsLiteral("newtab"); + + Element* menubar = doc->GetElementById(u"toolbar-menubar"_ns); + menubar->GetAttribute(u"autohide"_ns, attributeValue); + settings.menubarShown = attributeValue.EqualsLiteral("false"); + + ErrorResult err; + nsCOMPtr<nsIHTMLCollection> toolbarSprings = navbar->GetElementsByTagNameNS( + u"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"_ns, + u"toolbarspring"_ns, err); + if (err.Failed()) { + return NS_ERROR_FAILURE; + } + mozilla::Vector<CSSPixelSpan> springs; + for (size_t i = 0; i < toolbarSprings->Length(); i++) { + RefPtr<Element> springEl = toolbarSprings->Item(i); + RefPtr<dom::DOMRect> springRect; + rv = utils->GetBoundsWithoutFlushing(springEl, getter_AddRefs(springRect)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + CSSPixelSpan spring; + spring.start = springRect->X(); + spring.end = spring.start + springRect->Width(); + if (!settings.springs.append(spring)) { + return NS_ERROR_FAILURE; + } + } + + settings.rtlEnabled = intl::LocaleService::GetInstance()->IsAppLocaleRTL(); + + bool isInTabletMode = false; + bool autoTouchModePref = + Preferences::GetBool("browser.touchmode.auto", false); + if (autoTouchModePref) { + nsCOMPtr<nsIWindowsUIUtils> uiUtils( + do_GetService("@mozilla.org/windows-ui-utils;1")); + if (!NS_WARN_IF(!uiUtils)) { + uiUtils->GetInTabletMode(&isInTabletMode); + } + } + + if (isInTabletMode) { + settings.uiDensity = SkeletonUIDensity::Touch; + } else { + int uiDensityPref = Preferences::GetInt("browser.uidensity", 0); + switch (uiDensityPref) { + case 0: { + settings.uiDensity = SkeletonUIDensity::Default; + break; + } + case 1: { + settings.uiDensity = SkeletonUIDensity::Compact; + break; + } + case 2: { + settings.uiDensity = SkeletonUIDensity::Touch; + break; + } + } + } + + Unused << PersistPreXULSkeletonUIValues(settings); +#endif + + return NS_OK; +} + +nsresult AppWindow::SetPersistentValue(const nsAtom* aAttr, + const nsAString& aValue) { + nsAutoString uri; + nsAutoString windowElementId; + nsresult rv = GetDocXulStoreKeys(uri, windowElementId); + + if (NS_FAILED(rv) || windowElementId.IsEmpty()) { + return rv; + } + + nsAutoString maybeConvertedValue(aValue); + if (aAttr == nsGkAtoms::width || aAttr == nsGkAtoms::height) { + // Make sure we store the <window> attributes as outer window size, see + // bug 1444525 & co. + ConvertWindowSize(this, aAttr, ConversionDirection::InnerToOuter, + maybeConvertedValue); + } + + if (!mLocalStore) { + mLocalStore = do_GetService("@mozilla.org/xul/xulstore;1"); + if (NS_WARN_IF(!mLocalStore)) { + return NS_ERROR_NOT_INITIALIZED; + } + } + + return mLocalStore->SetValue( + uri, windowElementId, nsDependentAtomString(aAttr), maybeConvertedValue); +} + +void AppWindow::MaybeSavePersistentPositionAndSize( + PersistentAttributes aAttributes, Element& aRootElement, + const nsAString& aPersistString, bool aShouldPersist) { + if ((aAttributes & PersistentAttributes{PersistentAttribute::Position, + PersistentAttribute::Size}) + .isEmpty()) { + return; + } + + // get our size, position and mode to persist + LayoutDeviceIntRect rect; + if (NS_FAILED(mWindow->GetRestoredBounds(rect))) { + return; + } + + // we use CSS pixels for size, but desktop pixels for position + CSSToLayoutDeviceScale sizeScale = UnscaledDevicePixelsPerCSSPixel(); + DesktopToLayoutDeviceScale posScale = DevicePixelsPerDesktopPixel(); + + // make our position relative to our parent, if any + nsCOMPtr<nsIBaseWindow> parent(do_QueryReferent(mParentWindow)); + if (parent) { + int32_t parentX, parentY; + if (NS_SUCCEEDED(parent->GetPosition(&parentX, &parentY))) { + rect.MoveBy(-parentX, -parentY); + } + } + + nsAutoString sizeString; + // (only for size elements which are persisted) + if (aAttributes.contains(PersistentAttribute::Position)) { + if (aPersistString.Find(u"screenX") >= 0) { + sizeString.Truncate(); + sizeString.AppendInt(NSToIntRound(rect.X() / posScale.scale)); + aRootElement.SetAttr(nsGkAtoms::screenX, sizeString, IgnoreErrors()); + if (aShouldPersist) { + Unused << SetPersistentValue(nsGkAtoms::screenX, sizeString); + } + } + if (aPersistString.Find(u"screenY") >= 0) { + sizeString.Truncate(); + sizeString.AppendInt(NSToIntRound(rect.Y() / posScale.scale)); + aRootElement.SetAttr(nsGkAtoms::screenY, sizeString, IgnoreErrors()); + if (aShouldPersist) { + Unused << SetPersistentValue(nsGkAtoms::screenY, sizeString); + } + } + } + + if (aAttributes.contains(PersistentAttribute::Size)) { + LayoutDeviceIntRect innerRect = + rect - GetOuterToInnerSizeDifference(mWindow); + if (aPersistString.Find(u"width") >= 0) { + sizeString.Truncate(); + sizeString.AppendInt(NSToIntRound(innerRect.Width() / sizeScale.scale)); + aRootElement.SetAttr(nsGkAtoms::width, sizeString, IgnoreErrors()); + if (aShouldPersist) { + Unused << SetPersistentValue(nsGkAtoms::width, sizeString); + } + } + if (aPersistString.Find(u"height") >= 0) { + sizeString.Truncate(); + sizeString.AppendInt(NSToIntRound(innerRect.Height() / sizeScale.scale)); + aRootElement.SetAttr(nsGkAtoms::height, sizeString, IgnoreErrors()); + if (aShouldPersist) { + Unused << SetPersistentValue(nsGkAtoms::height, sizeString); + } + } + } + + Unused << MaybeSaveEarlyWindowPersistentValues(rect); +} + +void AppWindow::MaybeSavePersistentMiscAttributes( + PersistentAttributes aAttributes, Element& aRootElement, + const nsAString& aPersistString, bool aShouldPersist) { + if (!aAttributes.contains(PersistentAttribute::Misc)) { + return; + } + + nsSizeMode sizeMode = mWindow->SizeMode(); + nsAutoString sizeString; + if (sizeMode != nsSizeMode_Minimized) { + if (sizeMode == nsSizeMode_Maximized) { + sizeString.Assign(SIZEMODE_MAXIMIZED); + } else if (sizeMode == nsSizeMode_Fullscreen) { + sizeString.Assign(SIZEMODE_FULLSCREEN); + } else { + sizeString.Assign(SIZEMODE_NORMAL); + } + aRootElement.SetAttr(nsGkAtoms::sizemode, sizeString, IgnoreErrors()); + if (aShouldPersist && aPersistString.Find(u"sizemode") >= 0) { + Unused << SetPersistentValue(nsGkAtoms::sizemode, sizeString); + } + } + aRootElement.SetAttribute(u"gtktiledwindow"_ns, + mWindow->IsTiled() ? u"true"_ns : u"false"_ns, + IgnoreErrors()); + if (aPersistString.Find(u"zlevel") >= 0) { + uint32_t zLevel; + nsCOMPtr<nsIWindowMediator> mediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (mediator) { + mediator->GetZLevel(this, &zLevel); + sizeString.Truncate(); + sizeString.AppendInt(zLevel); + aRootElement.SetAttr(nsGkAtoms::zlevel, sizeString, IgnoreErrors()); + if (aShouldPersist) { + Unused << SetPersistentValue(nsGkAtoms::zlevel, sizeString); + } + } + } +} + +void AppWindow::SavePersistentAttributes( + const PersistentAttributes aAttributes) { + // can happen when the persistence timer fires at an inopportune time + // during window shutdown + if (!mDocShell) { + return; + } + + nsCOMPtr<dom::Element> docShellElement = GetWindowDOMElement(); + if (!docShellElement) { + return; + } + + nsAutoString persistString; + docShellElement->GetAttr(nsGkAtoms::persist, persistString); + if (persistString.IsEmpty()) { // quick check which sometimes helps + mPersistentAttributesDirty.clear(); + return; + } + + bool shouldPersist = mWindow->SizeMode() != nsSizeMode_Fullscreen; + MaybeSavePersistentPositionAndSize(aAttributes, *docShellElement, + persistString, shouldPersist); + MaybeSavePersistentMiscAttributes(aAttributes, *docShellElement, + persistString, shouldPersist); + mPersistentAttributesDirty -= aAttributes; +} + +NS_IMETHODIMP AppWindow::GetWindowDOMWindow(mozIDOMWindowProxy** aDOMWindow) { + NS_ENSURE_STATE(mDocShell); + + if (!mDOMWindow) mDOMWindow = mDocShell->GetWindow(); + NS_ENSURE_TRUE(mDOMWindow, NS_ERROR_FAILURE); + + *aDOMWindow = mDOMWindow; + NS_ADDREF(*aDOMWindow); + return NS_OK; +} + +dom::Element* AppWindow::GetWindowDOMElement() const { + NS_ENSURE_TRUE(mDocShell, nullptr); + + nsCOMPtr<nsIContentViewer> cv; + mDocShell->GetContentViewer(getter_AddRefs(cv)); + NS_ENSURE_TRUE(cv, nullptr); + + const dom::Document* document = cv->GetDocument(); + NS_ENSURE_TRUE(document, nullptr); + + return document->GetRootElement(); +} + +nsresult AppWindow::ContentShellAdded(nsIDocShellTreeItem* aContentShell, + bool aPrimary) { + // Set the default content tree owner + if (aPrimary) { + NS_ENSURE_SUCCESS(EnsurePrimaryContentTreeOwner(), NS_ERROR_FAILURE); + aContentShell->SetTreeOwner(mPrimaryContentTreeOwner); + mPrimaryContentShell = aContentShell; + mPrimaryBrowserParent = nullptr; + } else { + NS_ENSURE_SUCCESS(EnsureContentTreeOwner(), NS_ERROR_FAILURE); + aContentShell->SetTreeOwner(mContentTreeOwner); + if (mPrimaryContentShell == aContentShell) mPrimaryContentShell = nullptr; + } + + return NS_OK; +} + +nsresult AppWindow::ContentShellRemoved(nsIDocShellTreeItem* aContentShell) { + if (mPrimaryContentShell == aContentShell) { + mPrimaryContentShell = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +AppWindow::GetPrimaryContentSize(int32_t* aWidth, int32_t* aHeight) { + if (mPrimaryBrowserParent) { + return GetPrimaryRemoteTabSize(aWidth, aHeight); + } + if (mPrimaryContentShell) { + return GetPrimaryContentShellSize(aWidth, aHeight); + } + return NS_ERROR_UNEXPECTED; +} + +nsresult AppWindow::GetPrimaryRemoteTabSize(int32_t* aWidth, int32_t* aHeight) { + BrowserHost* host = BrowserHost::GetFrom(mPrimaryBrowserParent.get()); + // Need strong ref, since Client* can run script. + RefPtr<dom::Element> element = host->GetOwnerElement(); + NS_ENSURE_STATE(element); + + CSSIntSize size(element->ClientWidth(), element->ClientHeight()); + LayoutDeviceIntSize sizeDev = + RoundedToInt(size * UnscaledDevicePixelsPerCSSPixel()); + if (aWidth) { + *aWidth = sizeDev.width; + } + if (aHeight) { + *aHeight = sizeDev.height; + } + return NS_OK; +} + +nsresult AppWindow::GetPrimaryContentShellSize(int32_t* aWidth, + int32_t* aHeight) { + NS_ENSURE_STATE(mPrimaryContentShell); + + nsCOMPtr<nsIBaseWindow> shellWindow(do_QueryInterface(mPrimaryContentShell)); + NS_ENSURE_STATE(shellWindow); + + LayoutDeviceIntSize sizeDev = shellWindow->GetSize(); + if (aWidth) { + *aWidth = sizeDev.width; + } + if (aHeight) { + *aHeight = sizeDev.height; + } + return NS_OK; +} + +NS_IMETHODIMP +AppWindow::SetPrimaryContentSize(int32_t aWidth, int32_t aHeight) { + if (mPrimaryBrowserParent) { + return SetPrimaryRemoteTabSize(aWidth, aHeight); + } + if (mPrimaryContentShell) { + return SizeShellTo(mPrimaryContentShell, aWidth, aHeight); + } + return NS_ERROR_UNEXPECTED; +} + +nsresult AppWindow::SetPrimaryRemoteTabSize(int32_t aWidth, int32_t aHeight) { + int32_t shellWidth, shellHeight; + GetPrimaryRemoteTabSize(&shellWidth, &shellHeight); + SizeShellToWithLimit(aWidth, aHeight, shellWidth, shellHeight); + return NS_OK; +} + +nsresult AppWindow::GetRootShellSize(int32_t* aWidth, int32_t* aHeight) { + NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE); + return mDocShell->GetSize(aWidth, aHeight); +} + +nsresult AppWindow::SetRootShellSize(int32_t aWidth, int32_t aHeight) { + return SizeShellTo(mDocShell, aWidth, aHeight); +} + +NS_IMETHODIMP AppWindow::SizeShellTo(nsIDocShellTreeItem* aShellItem, + int32_t aCX, int32_t aCY) { + MOZ_ASSERT(aShellItem == mDocShell || aShellItem == mPrimaryContentShell); + if (aShellItem == mDocShell) { + auto newSize = + LayoutDeviceIntSize(aCX, aCY) + GetOuterToInnerSizeDifference(mWindow); + SetSize(newSize.width, newSize.height, /* aRepaint = */ true); + mDominantClientSize = true; + return NS_OK; + } + + // XXXTAB This is wrong, we should actually reflow based on the passed in + // shell. For now we are hacking and doing delta sizing. This is bad + // because it assumes all size we add will go to the shell which probably + // won't happen. + nsCOMPtr<nsIBaseWindow> shellAsWin(do_QueryInterface(aShellItem)); + NS_ENSURE_TRUE(shellAsWin, NS_ERROR_FAILURE); + + int32_t width = 0; + int32_t height = 0; + shellAsWin->GetSize(&width, &height); + + SizeShellToWithLimit(aCX, aCY, width, height); + + return NS_OK; +} + +NS_IMETHODIMP AppWindow::ExitModalLoop(nsresult aStatus) { + if (mContinueModalLoop) EnableParent(true); + mContinueModalLoop = false; + mModalStatus = aStatus; + return NS_OK; +} + +// top-level function to create a new window +NS_IMETHODIMP AppWindow::CreateNewWindow(int32_t aChromeFlags, + nsIOpenWindowInfo* aOpenWindowInfo, + nsIAppWindow** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + + if (aChromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_CHROME) { + MOZ_RELEASE_ASSERT( + !aOpenWindowInfo, + "Unexpected nsOpenWindowInfo when creating a new chrome window"); + return CreateNewChromeWindow(aChromeFlags, _retval); + } + + return CreateNewContentWindow(aChromeFlags, aOpenWindowInfo, _retval); +} + +NS_IMETHODIMP AppWindow::CreateNewChromeWindow(int32_t aChromeFlags, + nsIAppWindow** _retval) { + nsCOMPtr<nsIAppShellService> appShell( + do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); + NS_ENSURE_TRUE(appShell, NS_ERROR_FAILURE); + + // Just do a normal create of a window and return. + nsCOMPtr<nsIAppWindow> newWindow; + appShell->CreateTopLevelWindow( + this, nullptr, aChromeFlags, nsIAppShellService::SIZE_TO_CONTENT, + nsIAppShellService::SIZE_TO_CONTENT, getter_AddRefs(newWindow)); + + NS_ENSURE_TRUE(newWindow, NS_ERROR_FAILURE); + + newWindow.forget(_retval); + + return NS_OK; +} + +NS_IMETHODIMP AppWindow::CreateNewContentWindow( + int32_t aChromeFlags, nsIOpenWindowInfo* aOpenWindowInfo, + nsIAppWindow** _retval) { + nsCOMPtr<nsIAppShellService> appShell( + do_GetService(NS_APPSHELLSERVICE_CONTRACTID)); + NS_ENSURE_TRUE(appShell, NS_ERROR_FAILURE); + + // We need to create a new top level window and then enter a nested + // loop. Eventually the new window will be told that it has loaded, + // at which time we know it is safe to spin out of the nested loop + // and allow the opening code to proceed. + + nsCOMPtr<nsIURI> uri; + nsAutoCString urlStr; + urlStr.AssignLiteral(BROWSER_CHROME_URL_QUOTED); + + nsCOMPtr<nsIIOService> service(do_GetService(NS_IOSERVICE_CONTRACTID)); + if (service) { + service->NewURI(urlStr, nullptr, nullptr, getter_AddRefs(uri)); + } + NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE); + + // We need to create a chrome window to contain the content window we're about + // to pass back. The subject principal needs to be system while we're creating + // it to make things work right, so force a system caller. See bug 799348 + // comment 13 for a description of what happens when we don't. + nsCOMPtr<nsIAppWindow> newWindow; + { + AutoNoJSAPI nojsapi; + appShell->CreateTopLevelWindow(this, uri, aChromeFlags, 615, 480, + getter_AddRefs(newWindow)); + NS_ENSURE_TRUE(newWindow, NS_ERROR_FAILURE); + } + + AppWindow* appWin = + static_cast<AppWindow*>(static_cast<nsIAppWindow*>(newWindow)); + + // Specify which flags should be used by browser.xhtml to create the initial + // content browser window. + appWin->mInitialOpenWindowInfo = aOpenWindowInfo; + + // Specify that we want the window to remain locked until the chrome has + // loaded. + appWin->LockUntilChromeLoad(); + + { + AutoNoJSAPI nojsapi; + SpinEventLoopUntil("AppWindow::CreateNewContentWindow"_ns, + [&]() { return !appWin->IsLocked(); }); + } + + NS_ENSURE_STATE(appWin->mPrimaryContentShell || + appWin->mPrimaryBrowserParent); + MOZ_ASSERT_IF(appWin->mPrimaryContentShell, + !aOpenWindowInfo->GetNextRemoteBrowser()); + + newWindow.forget(_retval); + + return NS_OK; +} + +NS_IMETHODIMP AppWindow::GetHasPrimaryContent(bool* aResult) { + *aResult = mPrimaryBrowserParent || mPrimaryContentShell; + return NS_OK; +} + +void AppWindow::EnableParent(bool aEnable) { + nsCOMPtr<nsIBaseWindow> parentWindow; + nsCOMPtr<nsIWidget> parentWidget; + + parentWindow = do_QueryReferent(mParentWindow); + if (parentWindow) parentWindow->GetMainWidget(getter_AddRefs(parentWidget)); + if (parentWidget) parentWidget->Enable(aEnable); +} + +// Constrain the window to its proper z-level +bool AppWindow::ConstrainToZLevel(bool aImmediate, nsWindowZ* aPlacement, + nsIWidget* aReqBelow, + nsIWidget** aActualBelow) { +#if 0 + /* Do we have a parent window? This means our z-order is already constrained, + since we're a dependent window. Our window list isn't hierarchical, + so we can't properly calculate placement for such a window. + Should we just abort? */ + nsCOMPtr<nsIBaseWindow> parentWindow = do_QueryReferent(mParentWindow); + if (parentWindow) + return false; +#endif + + nsCOMPtr<nsIWindowMediator> mediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (!mediator) return false; + + bool altered; + uint32_t position, newPosition, zLevel; + nsIAppWindow* us = this; + + altered = false; + mediator->GetZLevel(this, &zLevel); + + // translate from WidgetGUIEvent to nsIWindowMediator constants + position = nsIWindowMediator::zLevelTop; + if (*aPlacement == nsWindowZBottom || zLevel == nsIAppWindow::lowestZ) + position = nsIWindowMediator::zLevelBottom; + else if (*aPlacement == nsWindowZRelative) + position = nsIWindowMediator::zLevelBelow; + + if (NS_SUCCEEDED(mediator->CalculateZPosition( + us, position, aReqBelow, &newPosition, aActualBelow, &altered))) { + /* If we were asked to move to the top but constrained to remain + below one of our other windows, first move all windows in that + window's layer and above to the top. This allows the user to + click a window which can't be topmost and still bring mozilla + to the foreground. */ + if (altered && + (position == nsIWindowMediator::zLevelTop || + (position == nsIWindowMediator::zLevelBelow && aReqBelow == 0))) + PlaceWindowLayersBehind(zLevel + 1, nsIAppWindow::highestZ, 0); + + if (*aPlacement != nsWindowZBottom && + position == nsIWindowMediator::zLevelBottom) + altered = true; + if (altered || aImmediate) { + if (newPosition == nsIWindowMediator::zLevelTop) + *aPlacement = nsWindowZTop; + else if (newPosition == nsIWindowMediator::zLevelBottom) + *aPlacement = nsWindowZBottom; + else + *aPlacement = nsWindowZRelative; + + if (aImmediate) { + nsCOMPtr<nsIBaseWindow> ourBase = do_QueryObject(this); + if (ourBase) { + nsCOMPtr<nsIWidget> ourWidget; + ourBase->GetMainWidget(getter_AddRefs(ourWidget)); + ourWidget->PlaceBehind(*aPlacement == nsWindowZBottom + ? eZPlacementBottom + : eZPlacementBelow, + *aActualBelow, false); + } + } + } + + /* CalculateZPosition can tell us to be below nothing, because it tries + not to change something it doesn't recognize. A request to verify + being below an unrecognized window, then, is treated as a request + to come to the top (below null) */ + nsCOMPtr<nsIAppWindow> windowAbove; + if (newPosition == nsIWindowMediator::zLevelBelow && *aActualBelow) { + windowAbove = (*aActualBelow)->GetWidgetListener()->GetAppWindow(); + } + + mediator->SetZPosition(us, newPosition, windowAbove); + } + + return altered; +} + +/* Re-z-position all windows in the layers from aLowLevel to aHighLevel, + inclusive, to be behind aBehind. aBehind of null means on top. + Note this method actually does nothing to our relative window positions. + (And therefore there's no need to inform WindowMediator we're moving + things, because we aren't.) This method is useful for, say, moving + a range of layers of our own windows relative to windows belonging to + external applications. +*/ +void AppWindow::PlaceWindowLayersBehind(uint32_t aLowLevel, uint32_t aHighLevel, + nsIAppWindow* aBehind) { + // step through windows in z-order from top to bottommost window + + nsCOMPtr<nsIWindowMediator> mediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (!mediator) return; + + nsCOMPtr<nsISimpleEnumerator> windowEnumerator; + mediator->GetZOrderAppWindowEnumerator(0, true, + getter_AddRefs(windowEnumerator)); + if (!windowEnumerator) return; + + // each window will be moved behind previousHighWidget, itself + // a moving target. initialize it. + nsCOMPtr<nsIWidget> previousHighWidget; + if (aBehind) { + nsCOMPtr<nsIBaseWindow> highBase(do_QueryInterface(aBehind)); + if (highBase) highBase->GetMainWidget(getter_AddRefs(previousHighWidget)); + } + + // get next lower window + bool more; + while (NS_SUCCEEDED(windowEnumerator->HasMoreElements(&more)) && more) { + uint32_t nextZ; // z-level of nextWindow + nsCOMPtr<nsISupports> nextWindow; + windowEnumerator->GetNext(getter_AddRefs(nextWindow)); + nsCOMPtr<nsIAppWindow> nextAppWindow(do_QueryInterface(nextWindow)); + nextAppWindow->GetZLevel(&nextZ); + if (nextZ < aLowLevel) + break; // we've processed all windows through aLowLevel + + // move it just below its next higher window + nsCOMPtr<nsIBaseWindow> nextBase(do_QueryInterface(nextAppWindow)); + if (nextBase) { + nsCOMPtr<nsIWidget> nextWidget; + nextBase->GetMainWidget(getter_AddRefs(nextWidget)); + if (nextZ <= aHighLevel) + nextWidget->PlaceBehind(eZPlacementBelow, previousHighWidget, false); + previousHighWidget = nextWidget; + } + } +} + +void AppWindow::SetContentScrollbarVisibility(bool aVisible) { + nsCOMPtr<nsPIDOMWindowOuter> contentWin( + do_GetInterface(mPrimaryContentShell)); + if (!contentWin) { + return; + } + + nsContentUtils::SetScrollbarsVisibility(contentWin->GetDocShell(), aVisible); +} + +void AppWindow::ApplyChromeFlags() { + nsCOMPtr<dom::Element> window = GetWindowDOMElement(); + if (!window) { + return; + } + + if (mChromeLoaded) { + // The two calls in this block don't need to happen early because they + // don't cause a global restyle on the document. Not only that, but the + // scrollbar stuff needs a content area to toggle the scrollbars on anyway. + // So just don't do these until mChromeLoaded is true. + + // Scrollbars have their own special treatment. + SetContentScrollbarVisibility(mChromeFlags & + nsIWebBrowserChrome::CHROME_SCROLLBARS); + } + + /* the other flags are handled together. we have style rules + in navigator.css that trigger visibility based on + the 'chromehidden' attribute of the <window> tag. */ + nsAutoString newvalue; + + if (!(mChromeFlags & nsIWebBrowserChrome::CHROME_MENUBAR)) + newvalue.AppendLiteral("menubar "); + + if (!(mChromeFlags & nsIWebBrowserChrome::CHROME_TOOLBAR)) + newvalue.AppendLiteral("toolbar "); + + if (!(mChromeFlags & nsIWebBrowserChrome::CHROME_LOCATIONBAR)) + newvalue.AppendLiteral("location "); + + if (!(mChromeFlags & nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR)) + newvalue.AppendLiteral("directories "); + + if (!(mChromeFlags & nsIWebBrowserChrome::CHROME_STATUSBAR)) + newvalue.AppendLiteral("status "); + + if (!(mChromeFlags & nsIWebBrowserChrome::CHROME_EXTRA)) + newvalue.AppendLiteral("extrachrome "); + + // Note that if we're not actually changing the value this will be a no-op, + // so no need to compare to the old value. + IgnoredErrorResult rv; + window->SetAttribute(u"chromehidden"_ns, newvalue, rv); +} + +NS_IMETHODIMP +AppWindow::BeforeStartLayout() { + ApplyChromeFlags(); + // Ordering here is important, loading width/height values in + // LoadPersistentWindowState() depends on the chromemargin attribute (since + // we need to translate outer to inner sizes). + SyncAttributesToWidget(); + LoadPersistentWindowState(); + if (mWindow) { + SizeShell(); + } + return NS_OK; +} + +NS_IMETHODIMP +AppWindow::LockAspectRatio(bool aShouldLock) { + mWindow->LockAspectRatio(aShouldLock); + return NS_OK; +} + +NS_IMETHODIMP +AppWindow::NeedFastSnaphot() { + MOZ_ASSERT(mWindow); + if (!mWindow) { + return NS_ERROR_FAILURE; + } + mWindow->SetNeedFastSnaphot(); + return NS_OK; +} + +void AppWindow::LoadPersistentWindowState() { + nsCOMPtr<dom::Element> docShellElement = GetWindowDOMElement(); + if (!docShellElement) { + return; + } + + // Check if the window wants to persist anything. + nsAutoString persist; + docShellElement->GetAttr(nsGkAtoms::persist, persist); + if (persist.IsEmpty()) { + return; + } + + auto loadValue = [&](nsAtom* aAttr) { + nsDependentAtomString attrString(aAttr); + if (persist.Find(attrString) >= 0) { + nsAutoString value; + nsresult rv = GetPersistentValue(aAttr, value); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to get persistent state."); + if (NS_SUCCEEDED(rv) && !value.IsEmpty()) { + docShellElement->SetAttr(aAttr, value, IgnoreErrors()); + } + } + }; + + loadValue(nsGkAtoms::screenX); + loadValue(nsGkAtoms::screenY); + loadValue(nsGkAtoms::width); + loadValue(nsGkAtoms::height); + loadValue(nsGkAtoms::sizemode); +} + +void AppWindow::IntrinsicallySizeShell(const CSSIntSize& aWindowDiff, + int32_t& aSpecWidth, + int32_t& aSpecHeight) { + nsCOMPtr<nsIContentViewer> cv; + mDocShell->GetContentViewer(getter_AddRefs(cv)); + if (!cv) { + return; + } + RefPtr<nsDocShell> docShell = mDocShell; + + CSSIntCoord maxWidth = 0; + CSSIntCoord maxHeight = 0; + CSSIntCoord prefWidth = 0; + if (RefPtr element = GetWindowDOMElement()) { + nsAutoString prefWidthAttr; + if (element->GetAttr(nsGkAtoms::prefwidth, prefWidthAttr)) { + // TODO: Make this more generic perhaps? + if (prefWidthAttr.EqualsLiteral("min-width")) { + if (auto* f = element->GetPrimaryFrame(FlushType::Frames)) { + const auto& coord = f->StylePosition()->mMinWidth; + if (coord.ConvertsToLength()) { + prefWidth = CSSPixel::FromAppUnitsRounded(coord.ToLength()); + } + } + } + } + } + + Maybe<CSSIntSize> size = cv->GetContentSize(maxWidth, maxHeight, prefWidth); + if (!size) { + return; + } + nsPresContext* pc = cv->GetPresContext(); + MOZ_ASSERT(pc, "Should have pres context"); + + int32_t width = pc->CSSPixelsToDevPixels(size->width); + int32_t height = pc->CSSPixelsToDevPixels(size->height); + SizeShellTo(docShell, width, height); + + // Update specified size for the final LoadPositionFromXUL call. + aSpecWidth = size->width + aWindowDiff.width; + aSpecHeight = size->height + aWindowDiff.height; +} + +void AppWindow::SizeShell() { + AutoRestore<bool> sizingShellFromXUL(mSizingShellFromXUL); + mSizingShellFromXUL = true; + + int32_t specWidth = -1, specHeight = -1; + bool gotSize = false; + + nsAutoString windowType; + if (nsCOMPtr<dom::Element> windowElement = GetWindowDOMElement()) { + windowElement->GetAttr(nsGkAtoms::windowtype, windowType); + } + + const CSSIntSize windowDiff = GetOuterToInnerSizeDifferenceInCSSPixels( + mWindow, UnscaledDevicePixelsPerCSSPixel()); + + // If we're using fingerprint resistance, we're going to resize the window + // once we have primary content. + if (nsContentUtils::ShouldResistFingerprinting( + "if RFP is enabled we want to round the dimensions of the new" + "new pop up window regardless of their origin") && + windowType.EqualsLiteral("navigator:browser")) { + // Once we've got primary content, force dimensions. + if (mPrimaryContentShell || mPrimaryBrowserParent) { + ForceRoundedDimensions(); + } + // Always avoid setting size/sizemode on this window. + mIgnoreXULSize = true; + mIgnoreXULSizeMode = true; + } else if (!mIgnoreXULSize) { + gotSize = LoadSizeFromXUL(specWidth, specHeight); + specWidth += windowDiff.width; + specHeight += windowDiff.height; + } + + bool positionSet = !mIgnoreXULPosition; + nsCOMPtr<nsIAppWindow> parentWindow(do_QueryReferent(mParentWindow)); +#if defined(XP_UNIX) && !defined(XP_MACOSX) + // don't override WM placement on unix for independent, top-level windows + // (however, we think the benefits of intelligent dependent window placement + // trump that override.) + if (!parentWindow) positionSet = false; +#endif + if (positionSet) { + // We have to do this before sizing the window, because sizing depends + // on the resolution of the screen we're on. But positioning needs to + // know the size so that it can constrain to screen bounds.... as an + // initial guess here, we'll use the specified size (if any). + positionSet = LoadPositionFromXUL(specWidth, specHeight); + } + + if (gotSize) { + SetSpecifiedSize(specWidth, specHeight); + } + + // If LoadSizeFromXUL set the size, mIntrinsicallySized will be false. + if (mIntrinsicallySized) { + IntrinsicallySizeShell(windowDiff, specWidth, specHeight); + } + + // Now that we have set the window's final size, we can re-do its + // positioning so that it is properly constrained to the screen. + if (positionSet) { + LoadPositionFromXUL(specWidth, specHeight); + } + + UpdateWindowStateFromMiscXULAttributes(); + + if (mChromeLoaded && mCenterAfterLoad && !positionSet && + mWindow->SizeMode() == nsSizeMode_Normal) { + Center(parentWindow, parentWindow ? false : true, false); + } +} + +NS_IMETHODIMP AppWindow::GetXULBrowserWindow( + nsIXULBrowserWindow** aXULBrowserWindow) { + NS_IF_ADDREF(*aXULBrowserWindow = mXULBrowserWindow); + return NS_OK; +} + +NS_IMETHODIMP AppWindow::SetXULBrowserWindow( + nsIXULBrowserWindow* aXULBrowserWindow) { + mXULBrowserWindow = aXULBrowserWindow; + return NS_OK; +} + +// Given the dimensions of some content area held within this XUL window, and +// assuming that that content area will change its dimensions in linear +// proportion to the dimensions of this XUL window, changes the size of the XUL +// window so that the content area reaches a particular size. +void AppWindow::SizeShellToWithLimit(int32_t aDesiredWidth, + int32_t aDesiredHeight, + int32_t shellItemWidth, + int32_t shellItemHeight) { + int32_t widthDelta = aDesiredWidth - shellItemWidth; + int32_t heightDelta = aDesiredHeight - shellItemHeight; + + int32_t winWidth = 0; + int32_t winHeight = 0; + + GetSize(&winWidth, &winHeight); + // There's no point in trying to make the window smaller than the + // desired content area size --- that's not likely to work. This whole + // function assumes that the outer docshell is adding some constant + // "border" chrome to the content area. + winWidth = std::max(winWidth + widthDelta, aDesiredWidth); + winHeight = std::max(winHeight + heightDelta, aDesiredHeight); + + // Note: Because of the asynchronous resizing on Linux we have to call + // SetSize even when the size doesn't appear to change. A previous call that + // has yet to complete can still change the size. We want the latest call to + // define the final size. + SetSize(winWidth, winHeight, true); + mDominantClientSize = true; +} + +nsresult AppWindow::GetTabCount(uint32_t* aResult) { + if (mXULBrowserWindow) { + return mXULBrowserWindow->GetTabCount(aResult); + } + + *aResult = 0; + return NS_OK; +} + +nsresult AppWindow::GetInitialOpenWindowInfo( + nsIOpenWindowInfo** aOpenWindowInfo) { + NS_ENSURE_ARG_POINTER(aOpenWindowInfo); + *aOpenWindowInfo = do_AddRef(mInitialOpenWindowInfo).take(); + return NS_OK; +} + +PresShell* AppWindow::GetPresShell() { + if (!mDocShell) { + return nullptr; + } + return mDocShell->GetPresShell(); +} + +bool AppWindow::WindowMoved(nsIWidget* aWidget, int32_t x, int32_t y) { + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (pm) { + nsCOMPtr<nsPIDOMWindowOuter> window = + mDocShell ? mDocShell->GetWindow() : nullptr; + pm->AdjustPopupsOnWindowChange(window); + } + + // Notify all tabs that the widget moved. + if (mDocShell && mDocShell->GetWindow()) { + nsCOMPtr<EventTarget> eventTarget = + mDocShell->GetWindow()->GetTopWindowRoot(); + nsContentUtils::DispatchChromeEvent( + mDocShell->GetDocument(), eventTarget, u"MozUpdateWindowPos"_ns, + CanBubble::eNo, Cancelable::eNo, nullptr); + } + + // Persist position, but not immediately, in case this OS is firing + // repeated move events as the user drags the window + PersistentAttributesDirty(PersistentAttribute::Position, Async); + return false; +} + +bool AppWindow::WindowResized(nsIWidget* aWidget, int32_t aWidth, + int32_t aHeight) { + mDominantClientSize = false; + if (mDocShell) { + mDocShell->SetPositionAndSize(0, 0, aWidth, aHeight, 0); + } + // Persist size, but not immediately, in case this OS is firing + // repeated size events as the user drags the sizing handle + if (!IsLocked()) { + PersistentAttributesDirty(AllPersistentAttributes(), Async); + } + // Check if we need to continue a fullscreen change. + switch (mFullscreenChangeState) { + case FullscreenChangeState::WillChange: + mFullscreenChangeState = FullscreenChangeState::WidgetResized; + break; + case FullscreenChangeState::WidgetEnteredFullscreen: + FinishFullscreenChange(true); + break; + case FullscreenChangeState::WidgetExitedFullscreen: + FinishFullscreenChange(false); + break; + case FullscreenChangeState::WidgetResized: + case FullscreenChangeState::NotChanging: + break; + } + return true; +} + +bool AppWindow::RequestWindowClose(nsIWidget* aWidget) { + // Maintain a reference to this as it is about to get destroyed. + nsCOMPtr<nsIAppWindow> appWindow(this); + + nsCOMPtr<nsPIDOMWindowOuter> window(mDocShell ? mDocShell->GetWindow() + : nullptr); + nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(window); + + RefPtr<PresShell> presShell = mDocShell->GetPresShell(); + if (!presShell) { + mozilla::DebugOnly<bool> dying; + MOZ_ASSERT(NS_SUCCEEDED(mDocShell->IsBeingDestroyed(&dying)) && dying, + "No presShell, but window is not being destroyed"); + } else if (eventTarget) { + RefPtr<nsPresContext> presContext = presShell->GetPresContext(); + + nsEventStatus status = nsEventStatus_eIgnore; + WidgetMouseEvent event(true, eClose, nullptr, WidgetMouseEvent::eReal); + if (NS_SUCCEEDED(EventDispatcher::Dispatch(eventTarget, presContext, &event, + nullptr, &status)) && + status == nsEventStatus_eConsumeNoDefault) + return false; + } + + Destroy(); + return false; +} + +void AppWindow::SizeModeChanged(nsSizeMode aSizeMode) { + const bool wasWidgetInFullscreen = mIsWidgetInFullscreen; + // Fullscreen and minimized states are usually compatible, and the widget + // typically returns to fullscreen after restoration. By not updating the + // widget's fullscreen state while it is minimized, we can avoid unnecessary + // fullscreen exits, such as those encountered in bug 1823284. + if (aSizeMode != nsSizeMode_Minimized) { + mIsWidgetInFullscreen = aSizeMode == nsSizeMode_Fullscreen; + } + + const bool fullscreenChanged = wasWidgetInFullscreen != mIsWidgetInFullscreen; + if (fullscreenChanged) { + FullscreenWillChange(mIsWidgetInFullscreen); + } + + // An alwaysRaised (or higher) window will hide any newly opened normal + // browser windows, so here we just drop a raised window to the normal + // zlevel if it's maximized. We make no provision for automatically + // re-raising it when restored. + if (aSizeMode == nsSizeMode_Maximized || aSizeMode == nsSizeMode_Fullscreen) { + uint32_t zLevel; + GetZLevel(&zLevel); + if (zLevel > nsIAppWindow::normalZ) { + SetZLevel(nsIAppWindow::normalZ); + } + } + + RecomputeBrowsingContextVisibility(); + + PersistentAttributesDirty(PersistentAttribute::Misc, Sync); + nsCOMPtr<nsPIDOMWindowOuter> ourWindow = + mDocShell ? mDocShell->GetWindow() : nullptr; + if (ourWindow) { + // Always fire a user-defined sizemodechange event on the window + ourWindow->DispatchCustomEvent(u"sizemodechange"_ns); + } + + if (PresShell* presShell = GetPresShell()) { + presShell->GetPresContext()->SizeModeChanged(aSizeMode); + } + + if (fullscreenChanged) { + FullscreenChanged(mIsWidgetInFullscreen); + } + + // Note the current implementation of SetSizeMode just stores + // the new state; it doesn't actually resize. So here we store + // the state and pass the event on to the OS. The day is coming + // when we'll handle the event here, and the return result will + // then need to be different. +} + +void AppWindow::UIResolutionChanged() { + nsCOMPtr<nsPIDOMWindowOuter> ourWindow = + mDocShell ? mDocShell->GetWindow() : nullptr; + if (ourWindow) { + ourWindow->DispatchCustomEvent(u"resolutionchange"_ns, + ChromeOnlyDispatch::eYes); + } +} + +void AppWindow::FullscreenWillChange(bool aInFullscreen) { + if (mDocShell) { + if (nsCOMPtr<nsPIDOMWindowOuter> ourWindow = mDocShell->GetWindow()) { + ourWindow->FullscreenWillChange(aInFullscreen); + } + } + MOZ_ASSERT(mFullscreenChangeState == FullscreenChangeState::NotChanging); + + int32_t winWidth = 0; + int32_t winHeight = 0; + GetSize(&winWidth, &winHeight); + + int32_t screenWidth = 0; + int32_t screenHeight = 0; + GetAvailScreenSize(&screenWidth, &screenHeight); + + // Check if the window is already at the expected dimensions. If it is, set + // the fullscreen change state to WidgetResized to avoid waiting for a resize + // event. On macOS, a fullscreen window could be slightly higher than + // available screen size because of the OS menu bar isn't yet hidden. + mFullscreenChangeState = + (aInFullscreen == (winWidth == screenWidth && winHeight >= screenHeight)) + ? FullscreenChangeState::WidgetResized + : FullscreenChangeState::WillChange; +} + +void AppWindow::FullscreenChanged(bool aInFullscreen) { + if (mFullscreenChangeState == FullscreenChangeState::WidgetResized) { + FinishFullscreenChange(aInFullscreen); + } else { + NS_WARNING_ASSERTION( + mFullscreenChangeState == FullscreenChangeState::WillChange, + "Unexpected fullscreen change state"); + FullscreenChangeState newState = + aInFullscreen ? FullscreenChangeState::WidgetEnteredFullscreen + : FullscreenChangeState::WidgetExitedFullscreen; + mFullscreenChangeState = newState; + nsCOMPtr<nsIAppWindow> kungFuDeathGrip(this); + // Wait for resize for a small amount of time. + // 80ms is actually picked arbitrarily. But it shouldn't be too large + // in case the widget resize is not going to happen at all, which can + // be the case for some Linux window managers and possibly Android. + NS_DelayedDispatchToCurrentThread( + NS_NewRunnableFunction( + "AppWindow::FullscreenChanged", + [this, kungFuDeathGrip, newState, aInFullscreen]() { + if (mFullscreenChangeState == newState) { + FinishFullscreenChange(aInFullscreen); + } + }), + 80); + } +} + +void AppWindow::FinishFullscreenChange(bool aInFullscreen) { + mFullscreenChangeState = FullscreenChangeState::NotChanging; + if (mDocShell) { + if (nsCOMPtr<nsPIDOMWindowOuter> ourWindow = mDocShell->GetWindow()) { + ourWindow->FinishFullscreenChange(aInFullscreen); + } + } +} + +void AppWindow::MacFullscreenMenubarOverlapChanged( + mozilla::DesktopCoord aOverlapAmount) { + if (mDocShell) { + if (nsCOMPtr<nsPIDOMWindowOuter> ourWindow = mDocShell->GetWindow()) { + ourWindow->MacFullscreenMenubarOverlapChanged(aOverlapAmount); + } + } +} + +void AppWindow::RecomputeBrowsingContextVisibility() { + if (!mDocShell) { + return; + } + RefPtr bc = mDocShell->GetBrowsingContext(); + if (!bc) { + return; + } + bc->Canonical()->RecomputeAppWindowVisibility(); +} + +void AppWindow::OcclusionStateChanged(bool aIsFullyOccluded) { + if (!mDocShell) { + return; + } + RecomputeBrowsingContextVisibility(); + if (RefPtr win = mDocShell->GetWindow()) { + // And always fire a user-defined occlusionstatechange event on the window + win->DispatchCustomEvent(u"occlusionstatechange"_ns, + ChromeOnlyDispatch::eYes); + } +} + +void AppWindow::OSToolbarButtonPressed() { + // Keep a reference as setting the chrome flags can fire events. + nsCOMPtr<nsIAppWindow> appWindow(this); + + // rjc: don't use "nsIWebBrowserChrome::CHROME_EXTRA" + // due to components with multiple sidebar components + // (such as Mail/News, Addressbook, etc)... and frankly, + // Mac IE, OmniWeb, and other Mac OS X apps all work this way + uint32_t chromeMask = (nsIWebBrowserChrome::CHROME_TOOLBAR | + nsIWebBrowserChrome::CHROME_LOCATIONBAR | + nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR); + + nsCOMPtr<nsIWebBrowserChrome> wbc(do_GetInterface(appWindow)); + if (!wbc) return; + + uint32_t chromeFlags, newChromeFlags = 0; + wbc->GetChromeFlags(&chromeFlags); + newChromeFlags = chromeFlags & chromeMask; + if (!newChromeFlags) + chromeFlags |= chromeMask; + else + chromeFlags &= (~newChromeFlags); + wbc->SetChromeFlags(chromeFlags); +} + +bool AppWindow::ZLevelChanged(bool aImmediate, nsWindowZ* aPlacement, + nsIWidget* aRequestBelow, + nsIWidget** aActualBelow) { + if (aActualBelow) *aActualBelow = nullptr; + + return ConstrainToZLevel(aImmediate, aPlacement, aRequestBelow, aActualBelow); +} + +void AppWindow::WindowActivated() { + nsCOMPtr<nsIAppWindow> appWindow(this); + + // focusing the window could cause it to close, so keep a reference to it + if (mDocShell) { + if (nsCOMPtr<nsPIDOMWindowOuter> window = mDocShell->GetWindow()) { + if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { + fm->WindowRaised(window, nsFocusManager::GenerateFocusActionId()); + } + } + } + + if (mChromeLoaded) { + PersistentAttributesDirty(AllPersistentAttributes(), Sync); + } +} + +void AppWindow::WindowDeactivated() { + if (mDocShell) { + if (nsCOMPtr<nsPIDOMWindowOuter> window = mDocShell->GetWindow()) { + if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) { + if (!fm->IsTestMode()) { + fm->WindowLowered(window, nsFocusManager::GenerateFocusActionId()); + } + } + } + } +} + +#ifdef USE_NATIVE_MENUS + +struct LoadNativeMenusListener { + LoadNativeMenusListener(Document* aDoc, nsIWidget* aParentWindow) + : mDocument(aDoc), mParentWindow(aParentWindow) {} + + RefPtr<Document> mDocument; + nsCOMPtr<nsIWidget> mParentWindow; +}; + +static bool sHiddenWindowLoadedNativeMenus = false; +static nsTArray<LoadNativeMenusListener> sLoadNativeMenusListeners; + +static void BeginLoadNativeMenus(Document* aDoc, nsIWidget* aParentWindow); + +static void LoadNativeMenus(Document* aDoc, nsIWidget* aParentWindow) { + MOZ_ASSERT(!gfxPlatform::IsHeadless()); + + // Find the menubar tag (if there is more than one, we ignore all but + // the first). + nsCOMPtr<nsINodeList> menubarElements = aDoc->GetElementsByTagNameNS( + nsLiteralString( + u"http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"), + u"menubar"_ns); + + nsCOMPtr<nsINode> menubarNode; + if (menubarElements) { + menubarNode = menubarElements->Item(0); + } + + using widget::NativeMenuSupport; + if (menubarNode) { + nsCOMPtr<Element> menubarContent(do_QueryInterface(menubarNode)); + NativeMenuSupport::CreateNativeMenuBar(aParentWindow, menubarContent); + } else { + NativeMenuSupport::CreateNativeMenuBar(aParentWindow, nullptr); + } + + if (!sHiddenWindowLoadedNativeMenus) { + sHiddenWindowLoadedNativeMenus = true; + for (auto& listener : sLoadNativeMenusListeners) { + BeginLoadNativeMenus(listener.mDocument, listener.mParentWindow); + } + sLoadNativeMenusListeners.Clear(); + } +} + +class L10nReadyPromiseHandler final : public dom::PromiseNativeHandler { + public: + NS_DECL_ISUPPORTS + + L10nReadyPromiseHandler(Document* aDoc, nsIWidget* aParentWindow) + : mDocument(aDoc), mWindow(aParentWindow) {} + + void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override { + LoadNativeMenus(mDocument, mWindow); + } + + void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aRv) override { + // Again, this shouldn't happen, but fallback to loading the menus as is. + NS_WARNING( + "L10nReadyPromiseHandler rejected - loading fallback native " + "menu."); + LoadNativeMenus(mDocument, mWindow); + } + + private: + ~L10nReadyPromiseHandler() = default; + + RefPtr<Document> mDocument; + nsCOMPtr<nsIWidget> mWindow; +}; + +NS_IMPL_ISUPPORTS0(L10nReadyPromiseHandler) + +static void BeginLoadNativeMenus(Document* aDoc, nsIWidget* aParentWindow) { + RefPtr<DocumentL10n> l10n = aDoc->GetL10n(); + if (l10n) { + // Wait for l10n to be ready so the menus are localized. + RefPtr<Promise> promise = l10n->Ready(); + MOZ_ASSERT(promise); + RefPtr<L10nReadyPromiseHandler> handler = + new L10nReadyPromiseHandler(aDoc, aParentWindow); + promise->AppendNativeHandler(handler); + } else { + // Something went wrong loading the doc and l10n wasn't created. This + // shouldn't really happen, but if it does fallback to trying to load + // the menus as is. + LoadNativeMenus(aDoc, aParentWindow); + } +} + +#endif + +class AppWindowTimerCallback final : public nsITimerCallback, public nsINamed { + public: + explicit AppWindowTimerCallback(AppWindow* aWindow) : mWindow(aWindow) {} + + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD Notify(nsITimer* aTimer) override { + // Although this object participates in a refcount cycle (this -> mWindow + // -> mSPTimer -> this), mSPTimer is a one-shot timer and releases this + // after it fires. So we don't need to release mWindow here. + + mWindow->FirePersistenceTimer(); + return NS_OK; + } + + NS_IMETHOD GetName(nsACString& aName) override { + aName.AssignLiteral("AppWindowTimerCallback"); + return NS_OK; + } + + private: + ~AppWindowTimerCallback() {} + + RefPtr<AppWindow> mWindow; +}; + +NS_IMPL_ISUPPORTS(AppWindowTimerCallback, nsITimerCallback, nsINamed) + +void AppWindow::PersistentAttributesDirty(PersistentAttributes aAttributes, + PersistentAttributeUpdate aUpdate) { + aAttributes = aAttributes & mPersistentAttributesMask; + if (aAttributes.isEmpty()) { + return; + } + + mPersistentAttributesDirty += aAttributes; + if (aUpdate == Sync) { + // Only apply the attributes we've been requested to apply sync, not other + // potentially dirty attributes that have been requested asynchronously. + SavePersistentAttributes(aAttributes); + return; + } + if (!mSPTimer) { + mSPTimer = NS_NewTimer(); + if (!mSPTimer) { + NS_WARNING("Couldn't create timer instance?"); + return; + } + } + + RefPtr<AppWindowTimerCallback> callback = new AppWindowTimerCallback(this); + mSPTimer->InitWithCallback(callback, SIZE_PERSISTENCE_TIMEOUT, + nsITimer::TYPE_ONE_SHOT); +} + +void AppWindow::FirePersistenceTimer() { SavePersistentAttributes(); } + +//---------------------------------------- +// nsIWebProgessListener implementation +//---------------------------------------- +NS_IMETHODIMP +AppWindow::OnProgressChange(nsIWebProgress* aProgress, nsIRequest* aRequest, + int32_t aCurSelfProgress, int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) { + MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +AppWindow::OnStateChange(nsIWebProgress* aProgress, nsIRequest* aRequest, + uint32_t aStateFlags, nsresult aStatus) { + // If the notification is not about a document finishing, then just + // ignore it... + if (!(aStateFlags & nsIWebProgressListener::STATE_STOP) || + !(aStateFlags & nsIWebProgressListener::STATE_IS_NETWORK)) { + return NS_OK; + } + + if (mChromeLoaded) return NS_OK; + + // If this document notification is for a frame then ignore it... + nsCOMPtr<mozIDOMWindowProxy> eventWin; + aProgress->GetDOMWindow(getter_AddRefs(eventWin)); + auto* eventPWin = nsPIDOMWindowOuter::From(eventWin); + if (eventPWin) { + nsPIDOMWindowOuter* rootPWin = eventPWin->GetPrivateRoot(); + if (eventPWin != rootPWin) return NS_OK; + } + + mChromeLoaded = true; + mLockedUntilChromeLoad = false; + +#ifdef USE_NATIVE_MENUS + /////////////////////////////// + // Find the Menubar DOM and Load the menus, hooking them up to the loaded + // commands + /////////////////////////////// + if (!gfxPlatform::IsHeadless()) { + nsCOMPtr<nsIContentViewer> cv; + mDocShell->GetContentViewer(getter_AddRefs(cv)); + if (cv) { + RefPtr<Document> menubarDoc = cv->GetDocument(); + if (menubarDoc) { + if (mIsHiddenWindow || sHiddenWindowLoadedNativeMenus) { + BeginLoadNativeMenus(menubarDoc, mWindow); + } else { + sLoadNativeMenusListeners.EmplaceBack(menubarDoc, mWindow); + } + } + } + } +#endif // USE_NATIVE_MENUS + + OnChromeLoaded(); + + return NS_OK; +} + +NS_IMETHODIMP +AppWindow::OnLocationChange(nsIWebProgress* aProgress, nsIRequest* aRequest, + nsIURI* aURI, uint32_t aFlags) { + MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +AppWindow::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + nsresult aStatus, const char16_t* aMessage) { + MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +AppWindow::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, + uint32_t aState) { + MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +AppWindow::OnContentBlockingEvent(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, uint32_t aEvent) { + MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +/** + * ExecuteCloseHandler - Run the close handler, if any. + * @return true iff we found a close handler to run. + */ +bool AppWindow::ExecuteCloseHandler() { + /* If the event handler closes this window -- a likely scenario -- + things get deleted out of order without this death grip. + (The problem may be the death grip in nsWindow::windowProc, + which forces this window's widget to remain alive longer + than it otherwise would.) */ + nsCOMPtr<nsIAppWindow> kungFuDeathGrip(this); + + nsCOMPtr<EventTarget> eventTarget; + if (mDocShell) { + eventTarget = do_QueryInterface(mDocShell->GetWindow()); + } + + if (eventTarget) { + nsCOMPtr<nsIContentViewer> contentViewer; + mDocShell->GetContentViewer(getter_AddRefs(contentViewer)); + if (contentViewer) { + RefPtr<nsPresContext> presContext = contentViewer->GetPresContext(); + + nsEventStatus status = nsEventStatus_eIgnore; + WidgetMouseEvent event(true, eClose, nullptr, WidgetMouseEvent::eReal); + + nsresult rv = EventDispatcher::Dispatch(eventTarget, presContext, &event, + nullptr, &status); + if (NS_SUCCEEDED(rv) && status == nsEventStatus_eConsumeNoDefault) + return true; + // else fall through and return false + } + } + + return false; +} // ExecuteCloseHandler + +void AppWindow::ConstrainToOpenerScreen(int32_t* aX, int32_t* aY) { + if (mOpenerScreenRect.IsEmpty()) { + *aX = *aY = 0; + return; + } + + int32_t left, top, width, height; + // Constrain initial positions to the same screen as opener + nsCOMPtr<nsIScreenManager> screenmgr = + do_GetService("@mozilla.org/gfx/screenmanager;1"); + if (screenmgr) { + nsCOMPtr<nsIScreen> screen = screenmgr->ScreenForRect(mOpenerScreenRect); + if (screen) { + screen->GetAvailRectDisplayPix(&left, &top, &width, &height); + if (*aX < left || *aX > left + width) { + *aX = left; + } + if (*aY < top || *aY > top + height) { + *aY = top; + } + } + } +} + +nsIAppWindow* AppWindow::WidgetListenerDelegate::GetAppWindow() { + return mAppWindow->GetAppWindow(); +} + +PresShell* AppWindow::WidgetListenerDelegate::GetPresShell() { + return mAppWindow->GetPresShell(); +} + +bool AppWindow::WidgetListenerDelegate::WindowMoved(nsIWidget* aWidget, + int32_t aX, int32_t aY, + ByMoveToRect) { + RefPtr<AppWindow> holder = mAppWindow; + return holder->WindowMoved(aWidget, aX, aY); +} + +bool AppWindow::WidgetListenerDelegate::WindowResized(nsIWidget* aWidget, + int32_t aWidth, + int32_t aHeight) { + RefPtr<AppWindow> holder = mAppWindow; + return holder->WindowResized(aWidget, aWidth, aHeight); +} + +bool AppWindow::WidgetListenerDelegate::RequestWindowClose(nsIWidget* aWidget) { + RefPtr<AppWindow> holder = mAppWindow; + return holder->RequestWindowClose(aWidget); +} + +void AppWindow::WidgetListenerDelegate::SizeModeChanged(nsSizeMode aSizeMode) { + RefPtr<AppWindow> holder = mAppWindow; + holder->SizeModeChanged(aSizeMode); +} + +void AppWindow::WidgetListenerDelegate::UIResolutionChanged() { + RefPtr<AppWindow> holder = mAppWindow; + holder->UIResolutionChanged(); +} + +void AppWindow::WidgetListenerDelegate::MacFullscreenMenubarOverlapChanged( + DesktopCoord aOverlapAmount) { + RefPtr<AppWindow> holder = mAppWindow; + return holder->MacFullscreenMenubarOverlapChanged(aOverlapAmount); +} + +void AppWindow::WidgetListenerDelegate::OcclusionStateChanged( + bool aIsFullyOccluded) { + RefPtr<AppWindow> holder = mAppWindow; + holder->OcclusionStateChanged(aIsFullyOccluded); +} + +void AppWindow::WidgetListenerDelegate::OSToolbarButtonPressed() { + RefPtr<AppWindow> holder = mAppWindow; + holder->OSToolbarButtonPressed(); +} + +bool AppWindow::WidgetListenerDelegate::ZLevelChanged( + bool aImmediate, nsWindowZ* aPlacement, nsIWidget* aRequestBelow, + nsIWidget** aActualBelow) { + RefPtr<AppWindow> holder = mAppWindow; + return holder->ZLevelChanged(aImmediate, aPlacement, aRequestBelow, + aActualBelow); +} + +void AppWindow::WidgetListenerDelegate::WindowActivated() { + RefPtr<AppWindow> holder = mAppWindow; + holder->WindowActivated(); +} + +void AppWindow::WidgetListenerDelegate::WindowDeactivated() { + RefPtr<AppWindow> holder = mAppWindow; + holder->WindowDeactivated(); +} + +} // namespace mozilla |