diff options
Diffstat (limited to 'xpfe')
24 files changed, 8178 insertions, 0 deletions
diff --git a/xpfe/appshell/AppWindow.cpp b/xpfe/appshell/AppWindow.cpp new file mode 100644 index 0000000000..6356acd764 --- /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" +#include "mozilla/Try.h" + +// Interfaces needed to be included +#include "nsGlobalWindowOuter.h" +#include "nsIAppShell.h" +#include "nsIAppShellService.h" +#include "nsIDocumentViewer.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 "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<nsIDocumentViewer> viewer; + mDocShell->GetDocViewer(getter_AddRefs(viewer)); + if (viewer) { + RefPtr<dom::Document> doc = viewer->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::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; + +#ifdef USE_NATIVE_MENUS + if (!gfxPlatform::IsHeadless()) { + // macOS only: For modals created early in startup. + // (e.g. ProfileManager/ProfileDowngrade) this creates a fallback menu for + // the menu bar which only contains a "Quit" menu item. + // This allows the user to quit the application in a regular way with cmd+Q. + widget::NativeMenuSupport::CreateNativeMenuBar(mWindow, nullptr); + } +#endif + + 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)); + } + + // 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); + + *aAvailWidth = screen->AvailWidth(); + *aAvailHeight = screen->AvailHeight(); + 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) { + if (!XRE_IsParentProcess()) { + // The XULStore is only available in the parent process. + return NS_ERROR_UNEXPECTED; + } + + 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) { + if (!XRE_IsParentProcess()) { + // The XULStore is only available in the parent process. + return NS_ERROR_UNEXPECTED; + } + + 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<nsIDocumentViewer> viewer; + mDocShell->GetDocViewer(getter_AddRefs(viewer)); + NS_ENSURE_TRUE(viewer, nullptr); + + const dom::Document* document = viewer->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<nsIDocumentViewer> viewer; + mDocShell->GetDocViewer(getter_AddRefs(viewer)); + if (!viewer) { + 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 = + viewer->GetContentSize(maxWidth, maxHeight, prefWidth); + if (!size) { + return; + } + nsPresContext* pc = viewer->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", + RFPTarget::RoundWindowSize) && + 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); + + CSSToLayoutDeviceScale scale = UnscaledDevicePixelsPerCSSPixel(); + CSSIntSize windowSizeCSS = RoundedToInt(GetSize() / scale); + + CSSIntSize screenSizeCSS; + GetAvailScreenSize(&screenSizeCSS.width, &screenSizeCSS.height); + + // 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 == (windowSizeCSS.width == screenSizeCSS.width && + windowSizeCSS.height >= screenSizeCSS.height)) + ? 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<nsIDocumentViewer> viewer; + mDocShell->GetDocViewer(getter_AddRefs(viewer)); + if (viewer) { + RefPtr<Document> menubarDoc = viewer->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<nsIDocumentViewer> viewer; + mDocShell->GetDocViewer(getter_AddRefs(viewer)); + if (viewer) { + RefPtr<nsPresContext> presContext = viewer->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 diff --git a/xpfe/appshell/AppWindow.h b/xpfe/appshell/AppWindow.h new file mode 100644 index 0000000000..4acee5dbfe --- /dev/null +++ b/xpfe/appshell/AppWindow.h @@ -0,0 +1,398 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_AppWindow_h__ +#define mozilla_AppWindow_h__ + +// Local Includes +#include "nsChromeTreeOwner.h" +#include "nsContentTreeOwner.h" + +// Helper classes +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsString.h" +#include "nsWeakReference.h" +#include "nsCOMArray.h" +#include "nsDocShell.h" +#include "nsRect.h" +#include "Units.h" +#include "mozilla/Maybe.h" +#include "mozilla/Mutex.h" + +// Interfaces needed +#include "nsIBaseWindow.h" +#include "nsIDocShellTreeItem.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIAppWindow.h" +#include "nsIPrompt.h" +#include "nsIAuthPrompt.h" +#include "nsIXULBrowserWindow.h" +#include "nsIWidgetListener.h" +#include "nsIRemoteTab.h" +#include "nsIWebProgressListener.h" +#include "nsITimer.h" +#include "nsIXULStore.h" + +class nsAtom; +class nsXULTooltipListener; + +namespace mozilla { +class PresShell; +class AppWindowTimerCallback; +class L10nReadyPromiseHandler; +namespace dom { +class Element; +} // namespace dom +namespace widget { +struct InitData; +} // namespace widget +} // namespace mozilla + +// AppWindow + +#define NS_APPWINDOW_IMPL_CID \ + { /* 8eaec2f3-ed02-4be2-8e0f-342798477298 */ \ + 0x8eaec2f3, 0xed02, 0x4be2, { \ + 0x8e, 0x0f, 0x34, 0x27, 0x98, 0x47, 0x72, 0x98 \ + } \ + } + +class nsContentShellInfo; + +namespace mozilla { + +class AppWindow final : public nsIBaseWindow, + public nsIInterfaceRequestor, + public nsIAppWindow, + public nsSupportsWeakReference, + public nsIWebProgressListener { + friend class ::nsChromeTreeOwner; + friend class ::nsContentTreeOwner; + + public: + // The implementation of non-refcounted nsIWidgetListener, which would hold a + // strong reference on stack before calling AppWindow's + // MOZ_CAN_RUN_SCRIPT methods. + class WidgetListenerDelegate : public nsIWidgetListener { + public: + explicit WidgetListenerDelegate(AppWindow* aAppWindow) + : mAppWindow(aAppWindow) {} + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual nsIAppWindow* GetAppWindow() override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual mozilla::PresShell* GetPresShell() override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual bool WindowMoved(nsIWidget* aWidget, int32_t x, int32_t y, + ByMoveToRect) override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual bool WindowResized(nsIWidget* aWidget, int32_t aWidth, + int32_t aHeight) override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual bool RequestWindowClose(nsIWidget* aWidget) override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual void SizeModeChanged(nsSizeMode sizeMode) override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual void UIResolutionChanged() override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual void MacFullscreenMenubarOverlapChanged( + mozilla::DesktopCoord aOverlapAmount) override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual void OcclusionStateChanged(bool aIsFullyOccluded) override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual void OSToolbarButtonPressed() override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual bool ZLevelChanged(bool aImmediate, nsWindowZ* aPlacement, + nsIWidget* aRequestBelow, + nsIWidget** aActualBelow) override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual void WindowActivated() override; + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual void WindowDeactivated() override; + + private: + // The lifetime of WidgetListenerDelegate is bound to AppWindow so + // we just use a raw pointer here. + AppWindow* mAppWindow; + }; + + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIAPPWINDOW + NS_DECL_NSIBASEWINDOW + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_APPWINDOW_IMPL_CID) + + void LockUntilChromeLoad() { mLockedUntilChromeLoad = true; } + bool IsLocked() const { return mLockedUntilChromeLoad; } + void IgnoreXULSizeMode(bool aEnable) { mIgnoreXULSizeMode = aEnable; } + void WasRegistered() { mRegistered = true; } + + using nsIBaseWindow::GetPositionAndSize; + using nsIBaseWindow::GetSize; + + // AppWindow methods... + nsresult Initialize(nsIAppWindow* aParent, nsIAppWindow* aOpener, + int32_t aInitialWidth, int32_t aInitialHeight, + bool aIsHiddenWindow, widget::InitData& widgetInitData); + + nsDocShell* GetDocShell() { return mDocShell; } + + nsresult Toolbar(); + + // nsIWebProgressListener + NS_DECL_NSIWEBPROGRESSLISTENER + + // nsIWidgetListener methods for WidgetListenerDelegate. + nsIAppWindow* GetAppWindow() { return this; } + mozilla::PresShell* GetPresShell(); + MOZ_CAN_RUN_SCRIPT + bool WindowMoved(nsIWidget* aWidget, int32_t aX, int32_t aY); + MOZ_CAN_RUN_SCRIPT + bool WindowResized(nsIWidget* aWidget, int32_t aWidth, int32_t aHeight); + MOZ_CAN_RUN_SCRIPT bool RequestWindowClose(nsIWidget* aWidget); + MOZ_CAN_RUN_SCRIPT void SizeModeChanged(nsSizeMode aSizeMode); + MOZ_CAN_RUN_SCRIPT void UIResolutionChanged(); + MOZ_CAN_RUN_SCRIPT void FullscreenWillChange(bool aInFullscreen); + MOZ_CAN_RUN_SCRIPT void FullscreenChanged(bool aInFullscreen); + MOZ_CAN_RUN_SCRIPT void MacFullscreenMenubarOverlapChanged( + mozilla::DesktopCoord aOverlapAmount); + MOZ_CAN_RUN_SCRIPT void OcclusionStateChanged(bool aIsFullyOccluded); + void RecomputeBrowsingContextVisibility(); + MOZ_CAN_RUN_SCRIPT void OSToolbarButtonPressed(); + MOZ_CAN_RUN_SCRIPT + bool ZLevelChanged(bool aImmediate, nsWindowZ* aPlacement, + nsIWidget* aRequestBelow, nsIWidget** aActualBelow); + MOZ_CAN_RUN_SCRIPT void WindowActivated(); + MOZ_CAN_RUN_SCRIPT void WindowDeactivated(); + + explicit AppWindow(uint32_t aChromeFlags); + + protected: + enum class PersistentAttribute : uint8_t { + Position, + Size, + Misc, + }; + using PersistentAttributes = EnumSet<PersistentAttribute>; + + static PersistentAttributes AllPersistentAttributes() { + return {PersistentAttribute::Position, PersistentAttribute::Size, + PersistentAttribute::Misc}; + } + + virtual ~AppWindow(); + + friend class mozilla::AppWindowTimerCallback; + + MOZ_CAN_RUN_SCRIPT bool ExecuteCloseHandler(); + void ConstrainToOpenerScreen(int32_t* aX, int32_t* aY); + + void SetPersistenceTimer(uint32_t aDirtyFlags); + void FirePersistenceTimer(); + + NS_IMETHOD EnsureChromeTreeOwner(); + NS_IMETHOD EnsureContentTreeOwner(); + NS_IMETHOD EnsurePrimaryContentTreeOwner(); + NS_IMETHOD EnsurePrompter(); + NS_IMETHOD EnsureAuthPrompter(); + NS_IMETHOD ForceRoundedDimensions(); + NS_IMETHOD GetAvailScreenSize(int32_t* aAvailWidth, int32_t* aAvailHeight); + + void FinishFullscreenChange(bool aInFullscreen); + + void ApplyChromeFlags(); + MOZ_CAN_RUN_SCRIPT_BOUNDARY void SizeShell(); + void OnChromeLoaded(); + void StaggerPosition(int32_t& aRequestedX, int32_t& aRequestedY, + int32_t aSpecWidth, int32_t aSpecHeight); + bool LoadPositionFromXUL(int32_t aSpecWidth, int32_t aSpecHeight); + bool LoadSizeFromXUL(int32_t& aSpecWidth, int32_t& aSpecHeight); + void SetSpecifiedSize(int32_t aSpecWidth, int32_t aSpecHeight); + bool UpdateWindowStateFromMiscXULAttributes(); + void SyncAttributesToWidget(); + void SavePersistentAttributes(PersistentAttributes); + void MaybeSavePersistentPositionAndSize(PersistentAttributes, + dom::Element& aRootElement, + const nsAString& aPersistString, + bool aShouldPersist); + void MaybeSavePersistentMiscAttributes(PersistentAttributes, + dom::Element& aRootElement, + const nsAString& aPersistString, + bool aShouldPersist); + void SavePersistentAttributes() { + SavePersistentAttributes(mPersistentAttributesDirty); + } + + bool NeedsTooltipListener(); + void AddTooltipSupport(); + void RemoveTooltipSupport(); + + NS_IMETHOD GetWindowDOMWindow(mozIDOMWindowProxy** aDOMWindow); + dom::Element* GetWindowDOMElement() const; + + // See nsIDocShellTreeOwner for docs on next two methods + nsresult ContentShellAdded(nsIDocShellTreeItem* aContentShell, bool aPrimary); + nsresult ContentShellRemoved(nsIDocShellTreeItem* aContentShell); + NS_IMETHOD GetPrimaryContentSize(int32_t* aWidth, int32_t* aHeight); + NS_IMETHOD SetPrimaryContentSize(int32_t aWidth, int32_t aHeight); + nsresult GetRootShellSize(int32_t* aWidth, int32_t* aHeight); + nsresult SetRootShellSize(int32_t aWidth, int32_t aHeight); + + NS_IMETHOD SizeShellTo(nsIDocShellTreeItem* aShellItem, int32_t aCX, + int32_t aCY); + NS_IMETHOD ExitModalLoop(nsresult aStatus); + NS_IMETHOD CreateNewChromeWindow(int32_t aChromeFlags, + nsIAppWindow** _retval); + NS_IMETHOD CreateNewContentWindow(int32_t aChromeFlags, + nsIOpenWindowInfo* aOpenWindowInfo, + nsIAppWindow** _retval); + NS_IMETHOD GetHasPrimaryContent(bool* aResult); + + void EnableParent(bool aEnable); + bool ConstrainToZLevel(bool aImmediate, nsWindowZ* aPlacement, + nsIWidget* aReqBelow, nsIWidget** aActualBelow); + void PlaceWindowLayersBehind(uint32_t aLowLevel, uint32_t aHighLevel, + nsIAppWindow* aBehind); + void SetContentScrollbarVisibility(bool aVisible); + + enum PersistentAttributeUpdate { Sync, Async }; + void PersistentAttributesDirty(PersistentAttributes, + PersistentAttributeUpdate); + nsresult GetTabCount(uint32_t* aResult); + + void LoadPersistentWindowState(); + nsresult GetPersistentValue(const nsAtom* aAttr, nsAString& aValue); + nsresult SetPersistentValue(const nsAtom* aAttr, const nsAString& aValue); + + // Saves window size and positioning values in order to display a very early + // skeleton UI. This has to happen before we can reasonably initialize the + // xulstore (i.e., before even loading libxul), so they have to use a special + // purpose store to do so. + nsresult MaybeSaveEarlyWindowPersistentValues( + const LayoutDeviceIntRect& aRect); + + // Gets the uri spec and the window element ID for this window. + nsresult GetDocXulStoreKeys(nsString& aUriSpec, nsString& aWindowElementId); + + // Enum for the current state of a fullscreen change. + // + // It is used to ensure that fullscreen change is issued after both + // the window state change and the window size change at best effort. + // This is needed because some platforms can't guarantee the order + // between such two events. + // + // It's changed in the following way: + // +---------------------------+--------------------------------------+ + // | | | + // | v | + // | NotChanging | + // | + | + // | | FullscreenWillChange | + // | v | + // | +-----------+ WillChange +------------------+ | + // | | WindowResized FullscreenChanged | | + // | v v | + // | WidgetResized WidgetEnteredFullscreen | + // | + or WidgetExitedFullscreen | + // | | FullscreenChanged + | + // | v WindowResized or | | + // +--------+ delayed dispatch | | + // v | + // +-------------+ + // + // The delayed dispatch serves as timeout, which is necessary because it's + // not even guaranteed that the widget will be resized at all. + enum class FullscreenChangeState : uint8_t { + // No current fullscreen change. Any previous change has finished. + NotChanging, + // Indicate there is going to be a fullscreen change. + WillChange, + // The widget has been resized since WillChange. + WidgetResized, + // The widget has entered fullscreen state since WillChange. + WidgetEnteredFullscreen, + // The widget has exited fullscreen state since WillChange. + WidgetExitedFullscreen, + }; + + nsChromeTreeOwner* mChromeTreeOwner; + nsContentTreeOwner* mContentTreeOwner; + nsContentTreeOwner* mPrimaryContentTreeOwner; + nsCOMPtr<nsIWidget> mWindow; + RefPtr<nsDocShell> mDocShell; + nsCOMPtr<nsPIDOMWindowOuter> mDOMWindow; + nsWeakPtr mParentWindow; + nsCOMPtr<nsIPrompt> mPrompter; + nsCOMPtr<nsIAuthPrompt> mAuthPrompter; + nsCOMPtr<nsIXULBrowserWindow> mXULBrowserWindow; + nsCOMPtr<nsIDocShellTreeItem> mPrimaryContentShell; + nsresult mModalStatus; + FullscreenChangeState mFullscreenChangeState; + bool mContinueModalLoop; + bool mDebuting; // being made visible right now + bool mChromeLoaded; // True when chrome has loaded + bool mSizingShellFromXUL; // true when in SizeShell() + bool mShowAfterLoad; + bool mIntrinsicallySized; + bool mCenterAfterLoad; + bool mIsHiddenWindow; + bool mLockedUntilChromeLoad; + bool mIgnoreXULSize; + bool mIgnoreXULPosition; + bool mChromeFlagsFrozen; + bool mIgnoreXULSizeMode; + // mDestroying is used to prevent reentry into into Destroy(), which can + // otherwise happen due to script running as we tear down various things. + bool mDestroying; + bool mRegistered; + // Indicator for whether the client size, instead of the window size, should + // be maintained in case of a change in their relation. + bool mDominantClientSize; + PersistentAttributes mPersistentAttributesDirty; + PersistentAttributes mPersistentAttributesMask; + uint32_t mChromeFlags; + nsCOMPtr<nsIOpenWindowInfo> mInitialOpenWindowInfo; + nsString mTitle; + + // The screen rect of the opener. + mozilla::DesktopIntRect mOpenerScreenRect; + + nsCOMPtr<nsIRemoteTab> mPrimaryBrowserParent; + + nsCOMPtr<nsITimer> mSPTimer; + WidgetListenerDelegate mWidgetListenerDelegate; + + private: + MOZ_CAN_RUN_SCRIPT void IntrinsicallySizeShell(const CSSIntSize& aWindowDiff, + int32_t& aSpecWidth, + int32_t& aSpecHeight); + + // GetPrimaryBrowserParentSize is called from xpidl methods and we don't have + // a good way to annotate those with MOZ_CAN_RUN_SCRIPT yet. It takes no + // refcounted args other than "this", and the "this" uses seem ok. + MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult + GetPrimaryRemoteTabSize(int32_t* aWidth, int32_t* aHeight); + nsresult GetPrimaryContentShellSize(int32_t* aWidth, int32_t* aHeight); + nsresult SetPrimaryRemoteTabSize(int32_t aWidth, int32_t aHeight); + void SizeShellToWithLimit(int32_t aDesiredWidth, int32_t aDesiredHeight, + int32_t shellItemWidth, int32_t shellItemHeight); + nsresult MoveResize(const Maybe<LayoutDeviceIntPoint>& aPosition, + const Maybe<LayoutDeviceIntSize>& aSize, bool aRepaint); + nsresult MoveResize(const Maybe<DesktopPoint>& aPosition, + const Maybe<DesktopSize>& aSize, bool aRepaint); + nsCOMPtr<nsIXULStore> mLocalStore; + bool mIsWidgetInFullscreen = false; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(AppWindow, NS_APPWINDOW_IMPL_CID) + +} // namespace mozilla + +#endif /* mozilla_AppWindow_h__ */ diff --git a/xpfe/appshell/LiveResizeListener.h b/xpfe/appshell/LiveResizeListener.h new file mode 100644 index 0000000000..e878ad7d35 --- /dev/null +++ b/xpfe/appshell/LiveResizeListener.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_LiveResizeListener_h +#define mozilla_LiveResizeListener_h + +#include "nsISupportsImpl.h" + +namespace mozilla { + +class LiveResizeListener { + public: + virtual void LiveResizeStarted() = 0; + virtual void LiveResizeStopped() = 0; + + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + protected: + virtual ~LiveResizeListener() = default; +}; + +} // namespace mozilla + +#endif // mozilla_LiveResizeListener_h diff --git a/xpfe/appshell/components.conf b/xpfe/appshell/components.conf new file mode 100644 index 0000000000..a1d848ae1f --- /dev/null +++ b/xpfe/appshell/components.conf @@ -0,0 +1,25 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Classes = [ + { + 'js_name': 'appShell', + 'cid': '{0099907d-123c-4853-a46a-43098b5fb68c}', + 'contract_ids': ['@mozilla.org/appshell/appShellService;1'], + 'interfaces': ['nsIAppShellService'], + 'type': 'nsAppShellService', + 'headers': ['/xpfe/appshell/nsAppShellService.h'], + }, + { + 'js_name': 'wm', + 'cid': '{79a2b7cc-f05b-4605-bfa0-fac54f27eec8}', + 'contract_ids': ['@mozilla.org/appshell/window-mediator;1'], + 'interfaces': ['nsIWindowMediator'], + 'type': 'nsWindowMediator', + 'headers': ['/xpfe/appshell/nsWindowMediator.h'], + 'init_method': 'Init', + }, +] diff --git a/xpfe/appshell/moz.build b/xpfe/appshell/moz.build new file mode 100644 index 0000000000..53b0807d1d --- /dev/null +++ b/xpfe/appshell/moz.build @@ -0,0 +1,48 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Core", "Window Management") + +MOCHITEST_CHROME_MANIFESTS += ["test/chrome.toml"] + +XPIDL_SOURCES += [ + "nsIAppShellService.idl", + "nsIAppWindow.idl", + "nsIWindowlessBrowser.idl", + "nsIWindowMediator.idl", + "nsIWindowMediatorListener.idl", + "nsIXULBrowserWindow.idl", +] + +XPIDL_MODULE = "appshell" + +EXPORTS += [ + "LiveResizeListener.h", + "nsAppShellCID.h", +] + +UNIFIED_SOURCES += [ + "AppWindow.cpp", + "nsAppShellService.cpp", + "nsAppShellWindowEnumerator.cpp", + "nsChromeTreeOwner.cpp", + "nsContentTreeOwner.cpp", + "nsWindowMediator.cpp", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +LOCAL_INCLUDES += [ + "/dom/base", + "/dom/xul", +] + +FINAL_LIBRARY = "xul" + +include("/ipc/chromium/chromium-config.mozbuild") diff --git a/xpfe/appshell/nsAppShellCID.h b/xpfe/appshell/nsAppShellCID.h new file mode 100644 index 0000000000..2b0edd4aca --- /dev/null +++ b/xpfe/appshell/nsAppShellCID.h @@ -0,0 +1,10 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAppShellCID_h__ +#define nsAppShellCID_h__ + +#define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1" + +#endif diff --git a/xpfe/appshell/nsAppShellService.cpp b/xpfe/appshell/nsAppShellService.cpp new file mode 100644 index 0000000000..1ac989aaa3 --- /dev/null +++ b/xpfe/appshell/nsAppShellService.cpp @@ -0,0 +1,906 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIAppShellService.h" +#include "nsNetUtil.h" +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "nsIXULRuntime.h" + +#include "nsIWindowMediator.h" +#include "nsPIWindowWatcher.h" +#include "nsPIDOMWindow.h" +#include "AppWindow.h" + +#include "mozilla/widget/InitData.h" +#include "nsWidgetsCID.h" +#include "nsIWidget.h" + +#include "nsAppDirectoryServiceDefs.h" +#include "nsAppShellService.h" +#include "nsContentUtils.h" +#include "nsDirectoryServiceUtils.h" +#include "nsThreadUtils.h" +#include "nsILoadContext.h" +#include "nsIWebNavigation.h" +#include "nsIWindowlessBrowser.h" + +#include "mozilla/Attributes.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/StartupTimeline.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/Try.h" +#include "mozilla/intl/LocaleService.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/Document.h" + +#include "nsEmbedCID.h" +#include "nsIWebBrowser.h" +#include "nsIDocShell.h" +#include "gfxPlatform.h" + +#include "nsWebBrowser.h" +#include "nsDocShell.h" +#include "nsDocShellLoadState.h" + +#ifdef MOZ_INSTRUMENT_EVENT_LOOP +# include "EventTracer.h" +#endif + +using namespace mozilla; +using mozilla::dom::BrowsingContext; +using mozilla::intl::LocaleService; + +// Default URL for the hidden window, can be overridden by a pref on Mac +#define DEFAULT_HIDDENWINDOW_URL "resource://gre-resources/hiddenWindow.html" + +class nsIAppShell; + +nsAppShellService::nsAppShellService() + : mXPCOMWillShutDown(false), + mXPCOMShuttingDown(false), + mModalWindowCount(0), + mApplicationProvidedHiddenWindow(false), + mScreenId(0) { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + + if (obs) { + obs->AddObserver(this, "xpcom-will-shutdown", false); + obs->AddObserver(this, "xpcom-shutdown", false); + } +} + +nsAppShellService::~nsAppShellService() {} + +/* + * Implement the nsISupports methods... + */ +NS_IMPL_ISUPPORTS(nsAppShellService, nsIAppShellService, nsIObserver) + +NS_IMETHODIMP +nsAppShellService::SetScreenId(uint32_t aScreenId) { + mScreenId = aScreenId; + return NS_OK; +} + +void nsAppShellService::EnsureHiddenWindow() { + if (!mHiddenWindow) { + (void)CreateHiddenWindow(); + } +} + +NS_IMETHODIMP +nsAppShellService::CreateHiddenWindow() { + if (!XRE_IsParentProcess()) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + if (mXPCOMShuttingDown) { + return NS_ERROR_FAILURE; + } + + if (mHiddenWindow) { + return NS_OK; + } + + nsCOMPtr<nsIFile> profileDir; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profileDir)); + if (!profileDir) { + // This is too early on startup to create the hidden window + return NS_ERROR_FAILURE; + } + + nsresult rv; + int32_t initialHeight = 100, initialWidth = 100; + +#ifdef XP_MACOSX + uint32_t chromeMask = 0; + nsAutoCString prefVal; + rv = Preferences::GetCString("browser.hiddenWindowChromeURL", prefVal); + const char* hiddenWindowURL = + NS_SUCCEEDED(rv) ? prefVal.get() : DEFAULT_HIDDENWINDOW_URL; + mApplicationProvidedHiddenWindow = prefVal.get() ? true : false; +#else + static const char hiddenWindowURL[] = DEFAULT_HIDDENWINDOW_URL; + uint32_t chromeMask = nsIWebBrowserChrome::CHROME_ALL; +#endif + + nsCOMPtr<nsIURI> url; + rv = NS_NewURI(getter_AddRefs(url), hiddenWindowURL); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<AppWindow> newWindow; + rv = JustCreateTopWindow(nullptr, url, chromeMask, initialWidth, + initialHeight, true, getter_AddRefs(newWindow)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocShell> docShell; + newWindow->GetDocShell(getter_AddRefs(docShell)); + if (docShell) { + Unused << docShell->GetBrowsingContext()->SetExplicitActive( + dom::ExplicitActiveStatus::Inactive); + } + + mHiddenWindow.swap(newWindow); + + return NS_OK; +} + +NS_IMETHODIMP +nsAppShellService::DestroyHiddenWindow() { + if (mHiddenWindow) { + mHiddenWindow->Destroy(); + + mHiddenWindow = nullptr; + } + + return NS_OK; +} + +/* + * Create a new top level window and display the given URL within it... + */ +NS_IMETHODIMP +nsAppShellService::CreateTopLevelWindow(nsIAppWindow* aParent, nsIURI* aUrl, + uint32_t aChromeMask, + int32_t aInitialWidth, + int32_t aInitialHeight, + nsIAppWindow** aResult) { + nsresult rv; + + StartupTimeline::RecordOnce(StartupTimeline::CREATE_TOP_LEVEL_WINDOW); + + RefPtr<AppWindow> newWindow; + rv = JustCreateTopWindow(aParent, aUrl, aChromeMask, aInitialWidth, + aInitialHeight, false, getter_AddRefs(newWindow)); + newWindow.forget(aResult); + + if (NS_SUCCEEDED(rv)) { + // the addref resulting from this is the owning addref for this window + RegisterTopLevelWindow(*aResult); + nsCOMPtr<nsIAppWindow> parent; + if (aChromeMask & nsIWebBrowserChrome::CHROME_DEPENDENT) parent = aParent; + (*aResult)->SetZLevel(CalculateWindowZLevel(parent, aChromeMask)); + } + + return rv; +} + +/* + * This class provides a stub implementation of nsIWebBrowserChrome, as needed + * by nsAppShellService::CreateWindowlessBrowser + */ +class WebBrowserChrome2Stub final : public nsIWebBrowserChrome, + public nsIInterfaceRequestor, + public nsSupportsWeakReference { + protected: + nsCOMPtr<nsIWebBrowser> mBrowser; + virtual ~WebBrowserChrome2Stub() = default; + + public: + void SetBrowser(nsIWebBrowser* aBrowser) { mBrowser = aBrowser; } + + NS_DECL_ISUPPORTS + NS_DECL_NSIWEBBROWSERCHROME + NS_DECL_NSIINTERFACEREQUESTOR +}; + +NS_INTERFACE_MAP_BEGIN(WebBrowserChrome2Stub) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebBrowserChrome) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(WebBrowserChrome2Stub) +NS_IMPL_RELEASE(WebBrowserChrome2Stub) + +NS_IMETHODIMP +WebBrowserChrome2Stub::GetChromeFlags(uint32_t* aChromeFlags) { + *aChromeFlags = 0; + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::SetChromeFlags(uint32_t aChromeFlags) { + MOZ_ASSERT_UNREACHABLE( + "WebBrowserChrome2Stub::SetChromeFlags is " + "not supported"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::ShowAsModal() { + MOZ_ASSERT_UNREACHABLE("WebBrowserChrome2Stub::ShowAsModal is not supported"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::IsWindowModal(bool* aResult) { + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::SetLinkStatus(const nsAString& aStatusText) { + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::GetInterface(const nsIID& aIID, void** aSink) { + return QueryInterface(aIID, aSink); +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::GetDimensions(DimensionKind aDimensionKind, int32_t* aX, + int32_t* aY, int32_t* aCX, int32_t* aCY) { + if (aX) { + *aX = 0; + } + if (aY) { + *aY = 0; + } + if (aCX) { + *aCX = 0; + } + if (aCY) { + *aCY = 0; + } + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::SetDimensions(DimensionRequest&& aRequest) { + nsCOMPtr<nsIBaseWindow> window(do_QueryInterface(mBrowser)); + NS_ENSURE_STATE(window); + // Inner and outer dimensions are equal. + aRequest.mDimensionKind = DimensionKind::Outer; + MOZ_TRY(aRequest.SupplementFrom(window)); + return aRequest.ApplyOuterTo(window); +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::Blur() { return NS_ERROR_NOT_IMPLEMENTED; } + +class BrowserDestroyer final : public Runnable { + public: + BrowserDestroyer(nsIWebBrowser* aBrowser, nsISupports* aContainer) + : mozilla::Runnable("BrowserDestroyer"), + mBrowser(aBrowser), + mContainer(aContainer) {} + + static nsresult Destroy(nsIWebBrowser* aBrowser) { + nsCOMPtr<nsIBaseWindow> window(do_QueryInterface(aBrowser)); + return window->Destroy(); + } + + NS_IMETHOD + Run() override { + // Explicitly destroy the browser, in case this isn't the last reference. + return Destroy(mBrowser); + } + + protected: + virtual ~BrowserDestroyer() {} + + private: + nsCOMPtr<nsIWebBrowser> mBrowser; + nsCOMPtr<nsISupports> mContainer; +}; + +// This is the "stub" we return from CreateWindowlessBrowser - it exists +// to manage the lifetimes of the nsIWebBrowser and container window. +// In particular, it keeps a strong reference to both, to prevent them from +// being collected while this object remains alive, and ensures that they +// aren't destroyed when it's not safe to run scripts. +class WindowlessBrowser final : public nsIWindowlessBrowser, + public nsIInterfaceRequestor { + public: + WindowlessBrowser(nsIWebBrowser* aBrowser, nsISupports* aContainer) + : mBrowser(aBrowser), mContainer(aContainer), mClosed(false) { + mWebNavigation = do_QueryInterface(aBrowser); + mInterfaceRequestor = do_QueryInterface(aBrowser); + } + NS_DECL_ISUPPORTS + NS_DECL_NSIWINDOWLESSBROWSER + NS_FORWARD_SAFE_NSIWEBNAVIGATION(mWebNavigation) + NS_FORWARD_SAFE_NSIINTERFACEREQUESTOR(mInterfaceRequestor) + + private: + ~WindowlessBrowser() { + if (mClosed) { + return; + } + + NS_WARNING("Windowless browser was not closed prior to destruction"); + + // The docshell destructor needs to dispatch events, and can only run + // when it's safe to run scripts. If this was triggered by GC, it may + // not always be safe to run scripts, in which cases we need to delay + // destruction until it is. + auto runnable = MakeRefPtr<BrowserDestroyer>(mBrowser, mContainer); + nsContentUtils::AddScriptRunner(runnable.forget()); + } + + nsCOMPtr<nsIWebBrowser> mBrowser; + nsCOMPtr<nsIWebNavigation> mWebNavigation; + nsCOMPtr<nsIInterfaceRequestor> mInterfaceRequestor; + // we don't use the container but just hold a reference to it. + nsCOMPtr<nsISupports> mContainer; + + bool mClosed; +}; + +NS_IMPL_ISUPPORTS(WindowlessBrowser, nsIWindowlessBrowser, nsIWebNavigation, + nsIInterfaceRequestor) + +NS_IMETHODIMP +WindowlessBrowser::Close() { + NS_ENSURE_TRUE(!mClosed, NS_ERROR_UNEXPECTED); + NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), + "WindowlessBrowser::Close called when not safe to run scripts"); + + mClosed = true; + + mWebNavigation = nullptr; + mInterfaceRequestor = nullptr; + return BrowserDestroyer::Destroy(mBrowser); +} + +NS_IMETHODIMP +WindowlessBrowser::GetBrowsingContext(BrowsingContext** aBrowsingContext) { + nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = do_QueryInterface(mBrowser); + if (!docShellTreeItem) { + return NS_ERROR_NOT_INITIALIZED; + } + return docShellTreeItem->GetBrowsingContextXPCOM(aBrowsingContext); +} + +NS_IMETHODIMP +WindowlessBrowser::GetDocShell(nsIDocShell** aDocShell) { + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(mInterfaceRequestor); + if (!docShell) { + return NS_ERROR_NOT_INITIALIZED; + } + docShell.forget(aDocShell); + return NS_OK; +} + +NS_IMETHODIMP +nsAppShellService::CreateWindowlessBrowser(bool aIsChrome, uint32_t aChromeMask, + nsIWindowlessBrowser** aResult) { + if (aChromeMask) { + MOZ_DIAGNOSTIC_ASSERT(aIsChrome, "Got chrome flags for non-chrome browser"); + if (aChromeMask & ~(nsIWebBrowserChrome::CHROME_REMOTE_WINDOW | + nsIWebBrowserChrome::CHROME_FISSION_WINDOW | + nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW)) { + NS_ERROR("Received unexpected chrome flags"); + return NS_ERROR_FAILURE; + } + } + + /* First, we set the container window for our instance of nsWebBrowser. Since + * we don't actually have a window, we instead set the container window to be + * an instance of WebBrowserChrome2Stub, which provides a stub implementation + * of nsIWebBrowserChrome. + */ + RefPtr<WebBrowserChrome2Stub> stub = new WebBrowserChrome2Stub(); + + /* A windowless web browser doesn't have an associated OS level window. To + * accomplish this, we initialize the window associated with our instance of + * nsWebBrowser with an instance of HeadlessWidget/PuppetWidget, which provide + * a stub implementation of nsIWidget. + */ + nsCOMPtr<nsIWidget> widget; + if (gfxPlatform::IsHeadless()) { + widget = nsIWidget::CreateHeadlessWidget(); + } else { + widget = nsIWidget::CreatePuppetWidget(nullptr); + } + if (!widget) { + NS_ERROR("Couldn't create instance of stub widget"); + return NS_ERROR_FAILURE; + } + + nsresult rv = + widget->Create(nullptr, 0, LayoutDeviceIntRect(0, 0, 0, 0), nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + // Create a BrowsingContext for our windowless browser. + RefPtr<BrowsingContext> browsingContext = BrowsingContext::CreateIndependent( + aIsChrome ? BrowsingContext::Type::Chrome + : BrowsingContext::Type::Content); + + if (aChromeMask & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW) { + browsingContext->SetRemoteTabs(true); + } + if (aChromeMask & nsIWebBrowserChrome::CHROME_FISSION_WINDOW) { + browsingContext->SetRemoteSubframes(true); + } + if (aChromeMask & nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW) { + browsingContext->SetPrivateBrowsing(true); + } + + /* Next, we create an instance of nsWebBrowser. Instances of this class have + * an associated doc shell, which is what we're interested in. + */ + nsCOMPtr<nsIWebBrowser> browser = nsWebBrowser::Create( + stub, widget, browsingContext, nullptr /* initialWindowChild */); + + if (NS_WARN_IF(!browser)) { + NS_ERROR("Couldn't create instance of nsWebBrowser!"); + return NS_ERROR_FAILURE; + } + + // Make sure the container window owns the the nsWebBrowser instance. + stub->SetBrowser(browser); + + nsISupports* isstub = NS_ISUPPORTS_CAST(nsIWebBrowserChrome*, stub); + RefPtr<nsIWindowlessBrowser> result = new WindowlessBrowser(browser, isstub); + nsCOMPtr<nsIDocShell> docshell = do_GetInterface(result); + docshell->SetInvisible(true); + + result.forget(aResult); + return NS_OK; +} + +uint32_t nsAppShellService::CalculateWindowZLevel(nsIAppWindow* aParent, + uint32_t aChromeMask) { + uint32_t zLevel; + + zLevel = nsIAppWindow::normalZ; + if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_RAISED) + zLevel = nsIAppWindow::raisedZ; + else if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_LOWERED) + zLevel = nsIAppWindow::loweredZ; + +#ifdef XP_MACOSX + /* Platforms on which modal windows are always application-modal, not + window-modal (that's just the Mac, right?) want modal windows to + be stacked on top of everyone else. + + On Mac OS X, bind modality to parent window instead of app (ala Mac OS 9) + */ + uint32_t modalDepMask = + nsIWebBrowserChrome::CHROME_MODAL | nsIWebBrowserChrome::CHROME_DEPENDENT; + if (aParent && (aChromeMask & modalDepMask)) { + aParent->GetZLevel(&zLevel); + } +#else + /* Platforms with native support for dependent windows (that's everyone + but pre-Mac OS X, right?) know how to stack dependent windows. On these + platforms, give the dependent window the same level as its parent, + so we won't try to override the normal platform behaviour. */ + if ((aChromeMask & nsIWebBrowserChrome::CHROME_DEPENDENT) && aParent) { + aParent->GetZLevel(&zLevel); + } +#endif + + return zLevel; +} + +/* + * Just do the window-making part of CreateTopLevelWindow + */ +nsresult nsAppShellService::JustCreateTopWindow( + nsIAppWindow* aParent, nsIURI* aUrl, uint32_t aChromeMask, + int32_t aInitialWidth, int32_t aInitialHeight, bool aIsHiddenWindow, + AppWindow** aResult) { + using BorderStyle = widget::BorderStyle; + *aResult = nullptr; + NS_ENSURE_STATE(!mXPCOMWillShutDown); + + nsCOMPtr<nsIAppWindow> parent; + if (aChromeMask & nsIWebBrowserChrome::CHROME_DEPENDENT) parent = aParent; + + RefPtr<AppWindow> window = new AppWindow(aChromeMask); + +#ifdef XP_WIN + // If the parent is currently fullscreen, tell the child to ignore persisted + // full screen states. This way new browser windows open on top of fullscreen + // windows normally. + if (nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(aParent)) { + nsCOMPtr<nsIWidget> widget; + baseWin->GetMainWidget(getter_AddRefs(widget)); + if (widget && widget->SizeMode() == nsSizeMode_Fullscreen) { + window->IgnoreXULSizeMode(true); + } + } +#endif + + widget::InitData widgetInitData; + if (aIsHiddenWindow) { + widgetInitData.mWindowType = widget::WindowType::Invisible; + } else { + widgetInitData.mWindowType = + aChromeMask & nsIWebBrowserChrome::CHROME_OPENAS_DIALOG + ? widget::WindowType::Dialog + : widget::WindowType::TopLevel; + } + + if (aChromeMask & nsIWebBrowserChrome::CHROME_SUPPRESS_ANIMATION) { + widgetInitData.mIsAnimationSuppressed = true; + } + + if (aChromeMask & nsIWebBrowserChrome::CHROME_ALWAYS_ON_TOP) { + widgetInitData.mAlwaysOnTop = true; + } + + if (aChromeMask & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW) { + widgetInitData.mHasRemoteContent = true; + } + +#if defined(MOZ_WIDGET_GTK) || defined(XP_WIN) + // Windows/Gtk PIP window support. It's Chrome dialog window, always on top + // and without any bar. + uint32_t pipMask = nsIWebBrowserChrome::CHROME_ALWAYS_ON_TOP | + nsIWebBrowserChrome::CHROME_OPENAS_CHROME | + nsIWebBrowserChrome::CHROME_WINDOW_RESIZE; + uint32_t barMask = nsIWebBrowserChrome::CHROME_MENUBAR | + nsIWebBrowserChrome::CHROME_TOOLBAR | + nsIWebBrowserChrome::CHROME_LOCATIONBAR | + nsIWebBrowserChrome::CHROME_TITLEBAR | + nsIWebBrowserChrome::CHROME_STATUSBAR; + if (widgetInitData.mWindowType == widget::WindowType::Dialog && + ((aChromeMask & pipMask) == pipMask) && !(aChromeMask & barMask)) { + widgetInitData.mPIPWindow = true; + } +#endif + + // alert=yes is expected to be used along with dialogs, not other window + // types. + MOZ_ASSERT_IF(aChromeMask & nsIWebBrowserChrome::CHROME_ALERT, + widgetInitData.mWindowType == widget::WindowType::Dialog); + widgetInitData.mIsAlert = + !!(aChromeMask & nsIWebBrowserChrome::CHROME_ALERT) && + widgetInitData.mWindowType == widget::WindowType::Dialog; + +#ifdef XP_MACOSX + // Mac OS X sheet support + // Adding CHROME_OPENAS_CHROME to sheetMask makes modal windows opened from + // nsGlobalWindow::ShowModalDialog() be dialogs (not sheets), while modal + // windows opened from nsPromptService::DoDialog() still are sheets. This + // fixes bmo bug 395465 (see nsCocoaWindow::StandardCreate() and + // nsCocoaWindow::SetModal()). + uint32_t sheetMask = nsIWebBrowserChrome::CHROME_OPENAS_DIALOG | + nsIWebBrowserChrome::CHROME_MODAL | + nsIWebBrowserChrome::CHROME_OPENAS_CHROME; + if (parent && (parent != mHiddenWindow) && + ((aChromeMask & sheetMask) == sheetMask)) { + widgetInitData.mWindowType = widget::WindowType::Sheet; + } +#endif + +#if defined(XP_WIN) + if (widgetInitData.mWindowType == widget::WindowType::TopLevel || + widgetInitData.mWindowType == widget::WindowType::Dialog) + widgetInitData.mClipChildren = true; +#endif + + // note default chrome overrides other OS chrome settings, but + // not internal chrome + if (aChromeMask & nsIWebBrowserChrome::CHROME_DEFAULT) { + widgetInitData.mBorderStyle = BorderStyle::Default; + } else if ((aChromeMask & nsIWebBrowserChrome::CHROME_ALL) == + nsIWebBrowserChrome::CHROME_ALL) { + widgetInitData.mBorderStyle = BorderStyle::All; + } else { + widgetInitData.mBorderStyle = BorderStyle::None; // assumes none == 0x00 + if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_BORDERS) { + widgetInitData.mBorderStyle |= BorderStyle::Border; + } + if (aChromeMask & nsIWebBrowserChrome::CHROME_TITLEBAR) { + widgetInitData.mBorderStyle |= BorderStyle::Title; + } + if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_CLOSE) { + widgetInitData.mBorderStyle |= BorderStyle::Close; + } + if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_RESIZE) { + widgetInitData.mResizable = true; + widgetInitData.mBorderStyle |= BorderStyle::ResizeH; + // only resizable windows get the maximize button (but not dialogs) + if (!(aChromeMask & nsIWebBrowserChrome::CHROME_OPENAS_DIALOG)) { + widgetInitData.mBorderStyle |= BorderStyle::Maximize; + } + } + // all windows (except dialogs) get minimize buttons and the system menu + if (!(aChromeMask & nsIWebBrowserChrome::CHROME_OPENAS_DIALOG)) { + widgetInitData.mBorderStyle |= BorderStyle::Minimize | BorderStyle::Menu; + } + // but anyone can explicitly ask for a minimize button + if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_MINIMIZE) { + widgetInitData.mBorderStyle |= BorderStyle::Minimize; + } + } + + if (aInitialWidth == nsIAppShellService::SIZE_TO_CONTENT || + aInitialHeight == nsIAppShellService::SIZE_TO_CONTENT) { + aInitialWidth = 1; + aInitialHeight = 1; + window->SetIntrinsicallySized(true); + } + + bool center = aChromeMask & nsIWebBrowserChrome::CHROME_CENTER_SCREEN; + + widgetInitData.mRTL = LocaleService::GetInstance()->IsAppLocaleRTL(); + + // Enforce the Private Browsing autoStart pref first. + bool isPrivateBrowsingWindow = + StaticPrefs::browser_privatebrowsing_autostart(); + if (aChromeMask & nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW) { + // Caller requested a private window + isPrivateBrowsingWindow = true; + } + widgetInitData.mIsPrivate = isPrivateBrowsingWindow; + + nsresult rv = + window->Initialize(parent, center ? aParent : nullptr, aInitialWidth, + aInitialHeight, aIsHiddenWindow, widgetInitData); + + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<mozIDOMWindowProxy> domWin = do_GetInterface(aParent); + nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(domWin); + nsCOMPtr<nsILoadContext> parentContext = do_QueryInterface(webNav); + + if (!isPrivateBrowsingWindow && parentContext) { + // Ensure that we propagate any existing private browsing status + // from the parent, even if it will not actually be used + // as a parent value. + isPrivateBrowsingWindow = parentContext->UsePrivateBrowsing(); + } + + if (RefPtr<nsDocShell> docShell = window->GetDocShell()) { + MOZ_ASSERT(docShell->GetBrowsingContext()->IsChrome()); + + docShell->SetPrivateBrowsing(isPrivateBrowsingWindow); + docShell->SetRemoteTabs(aChromeMask & + nsIWebBrowserChrome::CHROME_REMOTE_WINDOW); + docShell->SetRemoteSubframes(aChromeMask & + nsIWebBrowserChrome::CHROME_FISSION_WINDOW); + + // Eagerly create an about:blank content viewer with the right principal + // here, rather than letting it happen in the upcoming call to + // SetInitialPrincipal. This avoids creating the about:blank document and + // then blowing it away with a second one, which can cause problems for the + // top-level chrome window case. See bug 789773. + // Toplevel chrome windows always have a system principal, so ensure the + // initial window is created with that principal. + // We need to do this even when creating a chrome window to load a content + // window, see bug 799348 comment 13 for details about what previously + // happened here due to it using the subject principal. + if (nsContentUtils::IsInitialized()) { // Sometimes this happens really + // early. See bug 793370. + MOZ_DIAGNOSTIC_ASSERT( + nsContentUtils::LegacyIsCallerChromeOrNativeCode(), + "Previously, this method would use the subject principal rather than " + "hardcoding the system principal"); + // Use the system principal as the storage principal too until the new + // window finishes navigating and gets a real storage principal. + rv = docShell->CreateAboutBlankDocumentViewer( + nsContentUtils::GetSystemPrincipal(), + nsContentUtils::GetSystemPrincipal(), + /* aCsp = */ nullptr, /* aBaseURI = */ nullptr, + /* aIsInitialDocument = */ true); + NS_ENSURE_SUCCESS(rv, rv); + RefPtr<dom::Document> doc = docShell->GetDocument(); + NS_ENSURE_TRUE(!!doc, NS_ERROR_FAILURE); + MOZ_ASSERT(doc->IsInitialDocument(), + "Document should be an initial document"); + } + + // Begin loading the URL provided. + if (aUrl) { + RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aUrl); + loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal()); + loadState->SetFirstParty(true); + rv = docShell->LoadURI(loadState, /* aSetNavigating */ true); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + window.forget(aResult); + + if (center) rv = (*aResult)->Center(parent, parent ? false : true, false); + + return rv; +} + +NS_IMETHODIMP +nsAppShellService::GetHiddenWindow(nsIAppWindow** aWindow) { + NS_ENSURE_ARG_POINTER(aWindow); + + EnsureHiddenWindow(); + + *aWindow = mHiddenWindow; + NS_IF_ADDREF(*aWindow); + return *aWindow ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsAppShellService::GetHiddenDOMWindow(mozIDOMWindowProxy** aWindow) { + NS_ENSURE_ARG_POINTER(aWindow); + + EnsureHiddenWindow(); + + nsresult rv; + nsCOMPtr<nsIDocShell> docShell; + NS_ENSURE_TRUE(mHiddenWindow, NS_ERROR_FAILURE); + + rv = mHiddenWindow->GetDocShell(getter_AddRefs(docShell)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + + nsCOMPtr<nsPIDOMWindowOuter> hiddenDOMWindow(docShell->GetWindow()); + hiddenDOMWindow.forget(aWindow); + return *aWindow ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsAppShellService::GetHasHiddenWindow(bool* aHasHiddenWindow) { + NS_ENSURE_ARG_POINTER(aHasHiddenWindow); + + *aHasHiddenWindow = !!mHiddenWindow; + return NS_OK; +} + +NS_IMETHODIMP +nsAppShellService::GetApplicationProvidedHiddenWindow(bool* aAPHW) { + *aAPHW = mApplicationProvidedHiddenWindow; + return NS_OK; +} + +/* + * Register a new top level window (created elsewhere) + */ +NS_IMETHODIMP +nsAppShellService::RegisterTopLevelWindow(nsIAppWindow* aWindow) { + NS_ENSURE_ARG_POINTER(aWindow); + + nsCOMPtr<nsIDocShell> docShell; + aWindow->GetDocShell(getter_AddRefs(docShell)); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + + nsCOMPtr<nsPIDOMWindowOuter> domWindow(docShell->GetWindow()); + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); + + // Toplevel chrome windows always have a system principal, so ensure the + // initial window is created with that principal. + // We need to do this even when creating a chrome window to load a content + // window, see bug 799348 comment 13 for details about what previously + // happened here due to it using the subject principal. + MOZ_DIAGNOSTIC_ASSERT( + nsContentUtils::LegacyIsCallerChromeOrNativeCode(), + "Previously, this method would use the subject principal rather than " + "hardcoding the system principal"); + domWindow->SetInitialPrincipal(nsContentUtils::GetSystemPrincipal(), nullptr, + Nothing()); + + // tell the window mediator about the new window + nsCOMPtr<nsIWindowMediator> mediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + NS_ASSERTION(mediator, "Couldn't get window mediator."); + + if (mediator) mediator->RegisterWindow(aWindow); + + // tell the window watcher about the new window + nsCOMPtr<nsPIWindowWatcher> wwatcher( + do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + NS_ASSERTION(wwatcher, "No windowwatcher?"); + if (wwatcher && domWindow) { + wwatcher->AddWindow(domWindow, 0); + } + + // an ongoing attempt to quit is stopped by a newly opened window + nsCOMPtr<nsIObserverService> obssvc = services::GetObserverService(); + NS_ASSERTION(obssvc, "Couldn't get observer service."); + + if (obssvc) { + obssvc->NotifyObservers(aWindow, "xul-window-registered", nullptr); + AppWindow* appWindow = static_cast<AppWindow*>(aWindow); + appWindow->WasRegistered(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAppShellService::UnregisterTopLevelWindow(nsIAppWindow* aWindow) { + if (mXPCOMShuttingDown) { + /* return an error code in order to: + - avoid doing anything with other member variables while we are in + the destructor + - notify the caller not to release the AppShellService after + unregistering the window + (we don't want to be deleted twice consecutively to + mHiddenWindow->Destroy() in our destructor) + */ + return NS_ERROR_FAILURE; + } + + NS_ENSURE_ARG_POINTER(aWindow); + + if (aWindow == mHiddenWindow) { + // CreateHiddenWindow() does not register the window, so we're done. + return NS_OK; + } + + // tell the window mediator + nsCOMPtr<nsIWindowMediator> mediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + NS_ASSERTION(mediator, "Couldn't get window mediator. Doing xpcom shutdown?"); + + if (mediator) mediator->UnregisterWindow(aWindow); + + // tell the window watcher + nsCOMPtr<nsPIWindowWatcher> wwatcher( + do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + NS_ASSERTION(wwatcher, "Couldn't get windowwatcher, doing xpcom shutdown?"); + if (wwatcher) { + nsCOMPtr<nsIDocShell> docShell; + aWindow->GetDocShell(getter_AddRefs(docShell)); + if (docShell) { + nsCOMPtr<nsPIDOMWindowOuter> domWindow(docShell->GetWindow()); + if (domWindow) wwatcher->RemoveWindow(domWindow); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAppShellService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, "xpcom-will-shutdown")) { + mXPCOMWillShutDown = true; + } else if (!strcmp(aTopic, "xpcom-shutdown")) { + mXPCOMShuttingDown = true; + if (mHiddenWindow) { + mHiddenWindow->Destroy(); + } + } else { + NS_ERROR("Unexpected observer topic!"); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAppShellService::StartEventLoopLagTracking(bool* aResult) { +#ifdef MOZ_INSTRUMENT_EVENT_LOOP + *aResult = mozilla::InitEventTracing(true); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsAppShellService::StopEventLoopLagTracking() { +#ifdef MOZ_INSTRUMENT_EVENT_LOOP + mozilla::ShutdownEventTracing(); +#endif + return NS_OK; +} diff --git a/xpfe/appshell/nsAppShellService.h b/xpfe/appshell/nsAppShellService.h new file mode 100644 index 0000000000..8e85958897 --- /dev/null +++ b/xpfe/appshell/nsAppShellService.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsAppShellService_h +#define __nsAppShellService_h + +#include "nsIAppShellService.h" +#include "nsIObserver.h" + +// Interfaces Needed +#include "AppWindow.h" +#include "nsStringFwd.h" +#include "nsIRemoteTab.h" +#include "mozilla/Attributes.h" + +// {0099907D-123C-4853-A46A-43098B5FB68C} +#define NS_APPSHELLSERVICE_CID \ + { \ + 0x99907d, 0x123c, 0x4853, { \ + 0xa4, 0x6a, 0x43, 0x9, 0x8b, 0x5f, 0xb6, 0x8c \ + } \ + } + +class nsAppShellService final : public nsIAppShellService, public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIAPPSHELLSERVICE + NS_DECL_NSIOBSERVER + + nsAppShellService(); + + protected: + ~nsAppShellService(); + + void EnsureHiddenWindow(); + + nsresult JustCreateTopWindow(nsIAppWindow* aParent, nsIURI* aUrl, + uint32_t aChromeMask, int32_t aInitialWidth, + int32_t aInitialHeight, bool aIsHiddenWindow, + mozilla::AppWindow** aResult); + uint32_t CalculateWindowZLevel(nsIAppWindow* aParent, uint32_t aChromeMask); + + RefPtr<mozilla::AppWindow> mHiddenWindow; + bool mXPCOMWillShutDown; + bool mXPCOMShuttingDown; + uint16_t mModalWindowCount; + bool mApplicationProvidedHiddenWindow; + uint32_t mScreenId; +}; + +#endif diff --git a/xpfe/appshell/nsAppShellWindowEnumerator.cpp b/xpfe/appshell/nsAppShellWindowEnumerator.cpp new file mode 100644 index 0000000000..d140f8a90f --- /dev/null +++ b/xpfe/appshell/nsAppShellWindowEnumerator.cpp @@ -0,0 +1,340 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAppShellWindowEnumerator.h" + +#include "nsIDocumentViewer.h" +#include "nsIDocShell.h" +#include "mozilla/dom/Document.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIAppWindow.h" +#include "mozilla/dom/Element.h" + +#include "nsWindowMediator.h" + +using mozilla::dom::Document; +using mozilla::dom::Element; + +// +// static helper functions +// + +static void GetAttribute(nsIAppWindow* inWindow, const nsAString& inAttribute, + nsAString& outValue); +static void GetWindowType(nsIAppWindow* inWindow, nsString& outType); + +static Element* GetElementFromDocShell(nsIDocShell* aShell) { + nsCOMPtr<nsIDocumentViewer> viewer; + aShell->GetDocViewer(getter_AddRefs(viewer)); + if (viewer) { + RefPtr<Document> doc = viewer->GetDocument(); + if (doc) { + return doc->GetDocumentElement(); + } + } + + return nullptr; +} + +// generic "retrieve the value of a XUL attribute" function +void GetAttribute(nsIAppWindow* inWindow, const nsAString& inAttribute, + nsAString& outValue) { + nsCOMPtr<nsIDocShell> shell; + if (inWindow && NS_SUCCEEDED(inWindow->GetDocShell(getter_AddRefs(shell)))) { + RefPtr<Element> webshellElement = GetElementFromDocShell(shell); + if (webshellElement) { + webshellElement->GetAttribute(inAttribute, outValue); + } + } +} + +// retrieve the window type, stored as the value of a particular +// attribute in its XUL window tag +void GetWindowType(nsIAppWindow* aWindow, nsString& outType) { + GetAttribute(aWindow, u"windowtype"_ns, outType); +} + +// +// nsWindowInfo +// + +nsWindowInfo::nsWindowInfo(nsIAppWindow* inWindow, int32_t inTimeStamp) + : mWindow(inWindow), + mTimeStamp(inTimeStamp), + mZLevel(nsIAppWindow::normalZ) { + ReferenceSelf(true, true); +} + +nsWindowInfo::~nsWindowInfo() {} + +// return true if the window described by this WindowInfo has a type +// equal to the given type +bool nsWindowInfo::TypeEquals(const nsAString& aType) { + nsAutoString rtnString; + GetWindowType(mWindow, rtnString); + return rtnString == aType; +} + +// insert the struct into their two linked lists, in position after the +// given (independent) method arguments +void nsWindowInfo::InsertAfter(nsWindowInfo* inOlder, nsWindowInfo* inHigher) { + if (inOlder) { + mOlder = inOlder; + mYounger = inOlder->mYounger; + mOlder->mYounger = this; + if (mOlder->mOlder == mOlder) mOlder->mOlder = this; + mYounger->mOlder = this; + if (mYounger->mYounger == mYounger) mYounger->mYounger = this; + } + if (inHigher) { + mHigher = inHigher; + mLower = inHigher->mLower; + mHigher->mLower = this; + if (mHigher->mHigher == mHigher) mHigher->mHigher = this; + mLower->mHigher = this; + if (mLower->mLower == mLower) mLower->mLower = this; + } +} + +// remove the struct from its linked lists +void nsWindowInfo::Unlink(bool inAge, bool inZ) { + if (inAge) { + mOlder->mYounger = mYounger; + mYounger->mOlder = mOlder; + } + if (inZ) { + mLower->mHigher = mHigher; + mHigher->mLower = mLower; + } + ReferenceSelf(inAge, inZ); +} + +// initialize the struct to be a valid linked list of one element +void nsWindowInfo::ReferenceSelf(bool inAge, bool inZ) { + if (inAge) { + mYounger = this; + mOlder = this; + } + if (inZ) { + mLower = this; + mHigher = this; + } +} + +// +// nsAppShellWindowEnumerator +// + +nsAppShellWindowEnumerator::nsAppShellWindowEnumerator( + const char16_t* aTypeString, nsWindowMediator& aMediator) + : mWindowMediator(&aMediator), + mType(aTypeString), + mCurrentPosition(nullptr) { + mWindowMediator->AddEnumerator(this); + NS_ADDREF(mWindowMediator); +} + +nsAppShellWindowEnumerator::~nsAppShellWindowEnumerator() { + mWindowMediator->RemoveEnumerator(this); + NS_RELEASE(mWindowMediator); +} + +// after mCurrentPosition has been initialized to point to the beginning +// of the appropriate list, adjust it if necessary +void nsAppShellWindowEnumerator::AdjustInitialPosition() { + if (!mType.IsEmpty() && mCurrentPosition && + !mCurrentPosition->TypeEquals(mType)) + mCurrentPosition = FindNext(); +} + +NS_IMETHODIMP nsAppShellWindowEnumerator::HasMoreElements(bool* retval) { + if (!retval) return NS_ERROR_INVALID_ARG; + + *retval = mCurrentPosition ? true : false; + return NS_OK; +} + +// if a window is being removed adjust the iterator's current position +void nsAppShellWindowEnumerator::WindowRemoved(nsWindowInfo* inInfo) { + if (mCurrentPosition == inInfo) mCurrentPosition = FindNext(); +} + +// +// nsASDOMWindowEnumerator +// + +nsASDOMWindowEnumerator::nsASDOMWindowEnumerator(const char16_t* aTypeString, + nsWindowMediator& aMediator) + : nsAppShellWindowEnumerator(aTypeString, aMediator) {} + +nsASDOMWindowEnumerator::~nsASDOMWindowEnumerator() {} + +NS_IMETHODIMP nsASDOMWindowEnumerator::GetNext(nsISupports** retval) { + if (!retval) return NS_ERROR_INVALID_ARG; + + *retval = nullptr; + while (mCurrentPosition) { + nsCOMPtr<nsPIDOMWindowOuter> domWindow; + nsWindowMediator::GetDOMWindow(mCurrentPosition->mWindow, domWindow); + mCurrentPosition = FindNext(); + if (domWindow) return CallQueryInterface(domWindow, retval); + } + return NS_ERROR_FAILURE; +} + +// +// nsASAppWindowEnumerator +// + +nsASAppWindowEnumerator::nsASAppWindowEnumerator(const char16_t* aTypeString, + nsWindowMediator& aMediator) + : nsAppShellWindowEnumerator(aTypeString, aMediator) {} + +nsASAppWindowEnumerator::~nsASAppWindowEnumerator() {} + +NS_IMETHODIMP nsASAppWindowEnumerator::GetNext(nsISupports** retval) { + if (!retval) return NS_ERROR_INVALID_ARG; + + *retval = nullptr; + if (mCurrentPosition) { + CallQueryInterface(mCurrentPosition->mWindow, retval); + mCurrentPosition = FindNext(); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +// +// nsASDOMWindowEarlyToLateEnumerator +// + +nsASDOMWindowEarlyToLateEnumerator::nsASDOMWindowEarlyToLateEnumerator( + const char16_t* aTypeString, nsWindowMediator& aMediator) + : nsASDOMWindowEnumerator(aTypeString, aMediator) { + mCurrentPosition = aMediator.mOldestWindow; + AdjustInitialPosition(); +} + +nsASDOMWindowEarlyToLateEnumerator::~nsASDOMWindowEarlyToLateEnumerator() {} + +nsWindowInfo* nsASDOMWindowEarlyToLateEnumerator::FindNext() { + nsWindowInfo *info, *listEnd; + bool allWindows = mType.IsEmpty(); + + // see AppWindowEarlyToLateEnumerator::FindNext + if (!mCurrentPosition) return nullptr; + + info = mCurrentPosition->mYounger; + listEnd = mWindowMediator->mOldestWindow; + + while (info != listEnd) { + if (allWindows || info->TypeEquals(mType)) return info; + info = info->mYounger; + } + + return nullptr; +} + +// +// nsASAppWindowEarlyToLateEnumerator +// + +nsASAppWindowEarlyToLateEnumerator::nsASAppWindowEarlyToLateEnumerator( + const char16_t* aTypeString, nsWindowMediator& aMediator) + : nsASAppWindowEnumerator(aTypeString, aMediator) { + mCurrentPosition = aMediator.mOldestWindow; + AdjustInitialPosition(); +} + +nsASAppWindowEarlyToLateEnumerator::~nsASAppWindowEarlyToLateEnumerator() {} + +nsWindowInfo* nsASAppWindowEarlyToLateEnumerator::FindNext() { + nsWindowInfo *info, *listEnd; + bool allWindows = mType.IsEmpty(); + + /* mCurrentPosition null is assumed to mean that the enumerator has run + its course and is now basically useless. It could also be interpreted + to mean that it was created at a time when there were no windows. In + that case it would probably be more appropriate to check to see whether + windows have subsequently been added. But it's not guaranteed that we'll + pick up newly added windows anyway (if they occurred previous to our + current position) so we just don't worry about that. */ + if (!mCurrentPosition) return nullptr; + + info = mCurrentPosition->mYounger; + listEnd = mWindowMediator->mOldestWindow; + + while (info != listEnd) { + if (allWindows || info->TypeEquals(mType)) return info; + info = info->mYounger; + } + + return nullptr; +} + +// +// nsASAppWindowFrontToBackEnumerator +// + +nsASAppWindowFrontToBackEnumerator::nsASAppWindowFrontToBackEnumerator( + const char16_t* aTypeString, nsWindowMediator& aMediator) + : nsASAppWindowEnumerator(aTypeString, aMediator) { + mCurrentPosition = aMediator.mTopmostWindow; + AdjustInitialPosition(); +} + +nsASAppWindowFrontToBackEnumerator::~nsASAppWindowFrontToBackEnumerator() {} + +nsWindowInfo* nsASAppWindowFrontToBackEnumerator::FindNext() { + nsWindowInfo *info, *listEnd; + bool allWindows = mType.IsEmpty(); + + // see AppWindowEarlyToLateEnumerator::FindNext + if (!mCurrentPosition) return nullptr; + + info = mCurrentPosition->mLower; + listEnd = mWindowMediator->mTopmostWindow; + + while (info != listEnd) { + if (allWindows || info->TypeEquals(mType)) return info; + info = info->mLower; + } + + return nullptr; +} + +// +// nsASAppWindowBackToFrontEnumerator +// + +nsASAppWindowBackToFrontEnumerator::nsASAppWindowBackToFrontEnumerator( + const char16_t* aTypeString, nsWindowMediator& aMediator) + : nsASAppWindowEnumerator(aTypeString, aMediator) { + mCurrentPosition = + aMediator.mTopmostWindow ? aMediator.mTopmostWindow->mHigher : nullptr; + AdjustInitialPosition(); +} + +nsASAppWindowBackToFrontEnumerator::~nsASAppWindowBackToFrontEnumerator() {} + +nsWindowInfo* nsASAppWindowBackToFrontEnumerator::FindNext() { + nsWindowInfo *info, *listEnd; + bool allWindows = mType.IsEmpty(); + + // see AppWindowEarlyToLateEnumerator::FindNext + if (!mCurrentPosition) return nullptr; + + info = mCurrentPosition->mHigher; + listEnd = mWindowMediator->mTopmostWindow; + if (listEnd) listEnd = listEnd->mHigher; + + while (info != listEnd) { + if (allWindows || info->TypeEquals(mType)) return info; + info = info->mHigher; + } + + return nullptr; +} diff --git a/xpfe/appshell/nsAppShellWindowEnumerator.h b/xpfe/appshell/nsAppShellWindowEnumerator.h new file mode 100644 index 0000000000..b5dc828dcf --- /dev/null +++ b/xpfe/appshell/nsAppShellWindowEnumerator.h @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAppShellWindowEnumerator_h +#define nsAppShellWindowEnumerator_h + +#include "nsCOMPtr.h" +#include "nsString.h" + +#include "nsSimpleEnumerator.h" +#include "nsIAppWindow.h" + +class nsWindowMediator; + +// +// nsWindowInfo +// + +struct nsWindowInfo { + nsWindowInfo(nsIAppWindow* inWindow, int32_t inTimeStamp); + ~nsWindowInfo(); + + nsCOMPtr<nsIAppWindow> mWindow; + int32_t mTimeStamp; + uint32_t mZLevel; + + // each struct is in two, independent, circular, doubly-linked lists + nsWindowInfo *mYounger, // next younger in sequence + *mOlder; + nsWindowInfo *mLower, // next lower in z-order + *mHigher; + + bool TypeEquals(const nsAString& aType); + void InsertAfter(nsWindowInfo* inOlder, nsWindowInfo* inHigher); + void Unlink(bool inAge, bool inZ); + void ReferenceSelf(bool inAge, bool inZ); +}; + +// +// virtual enumerators +// + +class nsAppShellWindowEnumerator : public nsSimpleEnumerator { + friend class nsWindowMediator; + + public: + nsAppShellWindowEnumerator(const char16_t* aTypeString, + nsWindowMediator& inMediator); + NS_IMETHOD GetNext(nsISupports** retval) override = 0; + NS_IMETHOD HasMoreElements(bool* retval) override; + + protected: + ~nsAppShellWindowEnumerator() override; + + void AdjustInitialPosition(); + virtual nsWindowInfo* FindNext() = 0; + + void WindowRemoved(nsWindowInfo* inInfo); + + nsWindowMediator* mWindowMediator; + nsString mType; + nsWindowInfo* mCurrentPosition; +}; + +class nsASDOMWindowEnumerator : public nsAppShellWindowEnumerator { + public: + nsASDOMWindowEnumerator(const char16_t* aTypeString, + nsWindowMediator& inMediator); + virtual ~nsASDOMWindowEnumerator(); + NS_IMETHOD GetNext(nsISupports** retval) override; +}; + +class nsASAppWindowEnumerator : public nsAppShellWindowEnumerator { + public: + nsASAppWindowEnumerator(const char16_t* aTypeString, + nsWindowMediator& inMediator); + virtual ~nsASAppWindowEnumerator(); + NS_IMETHOD GetNext(nsISupports** retval) override; + + const nsID& DefaultInterface() override { return NS_GET_IID(nsIAppWindow); } +}; + +// +// concrete enumerators +// + +class nsASDOMWindowEarlyToLateEnumerator : public nsASDOMWindowEnumerator { + public: + nsASDOMWindowEarlyToLateEnumerator(const char16_t* aTypeString, + nsWindowMediator& inMediator); + + virtual ~nsASDOMWindowEarlyToLateEnumerator(); + + protected: + virtual nsWindowInfo* FindNext() override; +}; + +class nsASAppWindowEarlyToLateEnumerator : public nsASAppWindowEnumerator { + public: + nsASAppWindowEarlyToLateEnumerator(const char16_t* aTypeString, + nsWindowMediator& inMediator); + + virtual ~nsASAppWindowEarlyToLateEnumerator(); + + protected: + virtual nsWindowInfo* FindNext() override; +}; + +class nsASAppWindowFrontToBackEnumerator : public nsASAppWindowEnumerator { + public: + nsASAppWindowFrontToBackEnumerator(const char16_t* aTypeString, + nsWindowMediator& inMediator); + + virtual ~nsASAppWindowFrontToBackEnumerator(); + + protected: + virtual nsWindowInfo* FindNext() override; +}; + +class nsASAppWindowBackToFrontEnumerator : public nsASAppWindowEnumerator { + public: + nsASAppWindowBackToFrontEnumerator(const char16_t* aTypeString, + nsWindowMediator& inMediator); + + virtual ~nsASAppWindowBackToFrontEnumerator(); + + protected: + virtual nsWindowInfo* FindNext() override; +}; + +#endif diff --git a/xpfe/appshell/nsChromeTreeOwner.cpp b/xpfe/appshell/nsChromeTreeOwner.cpp new file mode 100644 index 0000000000..9c34cd20b9 --- /dev/null +++ b/xpfe/appshell/nsChromeTreeOwner.cpp @@ -0,0 +1,481 @@ +/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Local Includes +#include "nsChromeTreeOwner.h" +#include "AppWindow.h" + +// Helper Classes +#include "nsString.h" +#include "nsIDocShellTreeItem.h" + +// Interfaces needed to include +#include "nsIPrompt.h" +#include "nsIAuthPrompt.h" +#include "nsIWebProgress.h" +#include "nsIWidget.h" +#include "mozilla/Try.h" +#include "mozilla/dom/Element.h" + +using namespace mozilla; + +//***************************************************************************** +// nsChromeTreeOwner string literals +//***************************************************************************** + +const nsLiteralString kPersist(u"persist"); +const nsLiteralString kScreenX(u"screenX"); +const nsLiteralString kScreenY(u"screenY"); +const nsLiteralString kWidth(u"width"); +const nsLiteralString kHeight(u"height"); +const nsLiteralString kSizemode(u"sizemode"); +const nsLiteralString kSpace(u" "); + +//***************************************************************************** +//*** nsChromeTreeOwner: Object Management +//***************************************************************************** + +nsChromeTreeOwner::nsChromeTreeOwner() : mAppWindow(nullptr) {} + +nsChromeTreeOwner::~nsChromeTreeOwner() {} + +//***************************************************************************** +// nsChromeTreeOwner::nsISupports +//***************************************************************************** + +NS_IMPL_ADDREF(nsChromeTreeOwner) +NS_IMPL_RELEASE(nsChromeTreeOwner) + +NS_INTERFACE_MAP_BEGIN(nsChromeTreeOwner) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocShellTreeOwner) + NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeOwner) + NS_INTERFACE_MAP_ENTRY(nsIBaseWindow) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + +//***************************************************************************** +// nsChromeTreeOwner::nsIInterfaceRequestor +//***************************************************************************** + +NS_IMETHODIMP nsChromeTreeOwner::GetInterface(const nsIID& aIID, void** aSink) { + NS_ENSURE_ARG_POINTER(aSink); + + if (aIID.Equals(NS_GET_IID(nsIPrompt))) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetInterface(aIID, aSink); + } + if (aIID.Equals(NS_GET_IID(nsIAuthPrompt))) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetInterface(aIID, aSink); + } + if (aIID.Equals(NS_GET_IID(nsIWebBrowserChrome))) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetInterface(aIID, aSink); + } + if (aIID.Equals(NS_GET_IID(nsIAppWindow))) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->QueryInterface(aIID, aSink); + } + + return QueryInterface(aIID, aSink); +} + +//***************************************************************************** +// nsChromeTreeOwner::nsIDocShellTreeOwner +//***************************************************************************** + +NS_IMETHODIMP +nsChromeTreeOwner::ContentShellAdded(nsIDocShellTreeItem* aContentShell, + bool aPrimary) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->ContentShellAdded(aContentShell, aPrimary); +} + +NS_IMETHODIMP +nsChromeTreeOwner::ContentShellRemoved(nsIDocShellTreeItem* aContentShell) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->ContentShellRemoved(aContentShell); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetPrimaryContentShell( + nsIDocShellTreeItem** aShell) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetPrimaryContentShell(aShell); +} + +NS_IMETHODIMP +nsChromeTreeOwner::RemoteTabAdded(nsIRemoteTab* aTab, bool aPrimary) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->RemoteTabAdded(aTab, aPrimary); +} + +NS_IMETHODIMP +nsChromeTreeOwner::RemoteTabRemoved(nsIRemoteTab* aTab) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->RemoteTabRemoved(aTab); +} + +NS_IMETHODIMP +nsChromeTreeOwner::GetPrimaryRemoteTab(nsIRemoteTab** aTab) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetPrimaryRemoteTab(aTab); +} + +NS_IMETHODIMP +nsChromeTreeOwner::GetPrimaryContentBrowsingContext( + mozilla::dom::BrowsingContext** aBc) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetPrimaryContentBrowsingContext(aBc); +} + +NS_IMETHODIMP +nsChromeTreeOwner::GetPrimaryContentSize(int32_t* aWidth, int32_t* aHeight) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetPrimaryContentSize(aWidth, aHeight); +} + +NS_IMETHODIMP +nsChromeTreeOwner::SetPrimaryContentSize(int32_t aWidth, int32_t aHeight) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetPrimaryContentSize(aWidth, aHeight); +} + +NS_IMETHODIMP +nsChromeTreeOwner::GetRootShellSize(int32_t* aWidth, int32_t* aHeight) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetRootShellSize(aWidth, aHeight); +} + +NS_IMETHODIMP +nsChromeTreeOwner::SetRootShellSize(int32_t aWidth, int32_t aHeight) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetRootShellSize(aWidth, aHeight); +} + +NS_IMETHODIMP nsChromeTreeOwner::SizeShellTo(nsIDocShellTreeItem* aShellItem, + int32_t aCX, int32_t aCY) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SizeShellTo(aShellItem, aCX, aCY); +} + +NS_IMETHODIMP +nsChromeTreeOwner::SetPersistence(bool aPersistPosition, bool aPersistSize, + bool aPersistSizeMode) { + NS_ENSURE_STATE(mAppWindow); + nsCOMPtr<dom::Element> docShellElement = mAppWindow->GetWindowDOMElement(); + if (!docShellElement) return NS_ERROR_FAILURE; + + nsAutoString persistString; + docShellElement->GetAttribute(kPersist, persistString); + + bool saveString = false; + int32_t index; + +#define FIND_PERSIST_STRING(aString, aCond) \ + index = persistString.Find(aString); \ + if (!aCond && index > kNotFound) { \ + persistString.Cut(index, aString.Length()); \ + saveString = true; \ + } else if (aCond && index == kNotFound) { \ + persistString.Append(kSpace + aString); \ + saveString = true; \ + } + FIND_PERSIST_STRING(kScreenX, aPersistPosition); + FIND_PERSIST_STRING(kScreenY, aPersistPosition); + FIND_PERSIST_STRING(kWidth, aPersistSize); + FIND_PERSIST_STRING(kHeight, aPersistSize); + FIND_PERSIST_STRING(kSizemode, aPersistSizeMode); + + ErrorResult rv; + if (saveString) { + docShellElement->SetAttribute(kPersist, persistString, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsChromeTreeOwner::GetPersistence(bool* aPersistPosition, bool* aPersistSize, + bool* aPersistSizeMode) { + NS_ENSURE_STATE(mAppWindow); + nsCOMPtr<dom::Element> docShellElement = mAppWindow->GetWindowDOMElement(); + if (!docShellElement) return NS_ERROR_FAILURE; + + nsAutoString persistString; + docShellElement->GetAttribute(kPersist, persistString); + + // data structure doesn't quite match the question, but it's close enough + // for what we want (since this method is never actually called...) + if (aPersistPosition) + *aPersistPosition = persistString.Find(kScreenX) > kNotFound || + persistString.Find(kScreenY) > kNotFound; + if (aPersistSize) + *aPersistSize = persistString.Find(kWidth) > kNotFound || + persistString.Find(kHeight) > kNotFound; + if (aPersistSizeMode) + *aPersistSizeMode = persistString.Find(kSizemode) > kNotFound; + + return NS_OK; +} + +NS_IMETHODIMP +nsChromeTreeOwner::GetTabCount(uint32_t* aResult) { + if (mAppWindow) { + return mAppWindow->GetTabCount(aResult); + } + + *aResult = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsChromeTreeOwner::GetHasPrimaryContent(bool* aResult) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetHasPrimaryContent(aResult); +} + +//***************************************************************************** +// nsChromeTreeOwner::nsIBaseWindow +//***************************************************************************** + +NS_IMETHODIMP nsChromeTreeOwner::InitWindow(nativeWindow aParentNativeWindow, + nsIWidget* parentWidget, int32_t x, + int32_t y, int32_t cx, int32_t cy) { + // Ignore widget parents for now. Don't think those are a vaild thing to + // call. + NS_ENSURE_SUCCESS(SetPositionAndSize(x, y, cx, cy, 0), NS_ERROR_FAILURE); + + return NS_OK; +} + +NS_IMETHODIMP nsChromeTreeOwner::Destroy() { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->Destroy(); +} + +double nsChromeTreeOwner::GetWidgetCSSToDeviceScale() { + return mAppWindow ? mAppWindow->GetWidgetCSSToDeviceScale() : 1.0; +} + +NS_IMETHODIMP nsChromeTreeOwner::GetDevicePixelsPerDesktopPixel( + double* aScale) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetDevicePixelsPerDesktopPixel(aScale); +} + +NS_IMETHODIMP nsChromeTreeOwner::SetPositionDesktopPix(int32_t x, int32_t y) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetPositionDesktopPix(x, y); +} + +NS_IMETHODIMP nsChromeTreeOwner::SetPosition(int32_t x, int32_t y) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetPosition(x, y); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetPosition(int32_t* x, int32_t* y) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetPosition(x, y); +} + +NS_IMETHODIMP nsChromeTreeOwner::SetSize(int32_t cx, int32_t cy, + bool fRepaint) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetSize(cx, cy, fRepaint); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetSize(int32_t* cx, int32_t* cy) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetSize(cx, cy); +} + +NS_IMETHODIMP nsChromeTreeOwner::SetPositionAndSize(int32_t x, int32_t y, + int32_t cx, int32_t cy, + uint32_t aFlags) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetPositionAndSize(x, y, cx, cy, aFlags); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetPositionAndSize(int32_t* x, int32_t* y, + int32_t* cx, int32_t* cy) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetPositionAndSize(x, y, cx, cy); +} + +NS_IMETHODIMP +nsChromeTreeOwner::SetDimensions(DimensionRequest&& aRequest) { + NS_ENSURE_STATE(mAppWindow); + if (aRequest.mDimensionKind == DimensionKind::Outer) { + return mAppWindow->SetDimensions(std::move(aRequest)); + } + + MOZ_TRY(aRequest.SupplementFrom(this)); + return aRequest.ApplyInnerTo(this, /* aAsRootShell */ true); +} + +NS_IMETHODIMP +nsChromeTreeOwner::GetDimensions(DimensionKind aDimensionKind, int32_t* aX, + int32_t* aY, int32_t* aCX, int32_t* aCY) { + NS_ENSURE_STATE(mAppWindow); + if (aDimensionKind == DimensionKind::Outer) { + return mAppWindow->GetDimensions(aDimensionKind, aX, aY, aCX, aCY); + } + if (aY || aX) { + return NS_ERROR_NOT_IMPLEMENTED; + } + return GetRootShellSize(aCX, aCY); +} + +NS_IMETHODIMP nsChromeTreeOwner::Repaint(bool aForce) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->Repaint(aForce); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetParentWidget(nsIWidget** aParentWidget) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetParentWidget(aParentWidget); +} + +NS_IMETHODIMP nsChromeTreeOwner::SetParentWidget(nsIWidget* aParentWidget) { + NS_ASSERTION(false, "You can't call this"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsChromeTreeOwner::GetParentNativeWindow( + nativeWindow* aParentNativeWindow) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetParentNativeWindow(aParentNativeWindow); +} + +NS_IMETHODIMP nsChromeTreeOwner::SetParentNativeWindow( + nativeWindow aParentNativeWindow) { + NS_ASSERTION(false, "You can't call this"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsChromeTreeOwner::GetNativeHandle(nsAString& aNativeHandle) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetNativeHandle(aNativeHandle); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetVisibility(bool* aVisibility) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetVisibility(aVisibility); +} + +NS_IMETHODIMP nsChromeTreeOwner::SetVisibility(bool aVisibility) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetVisibility(aVisibility); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetEnabled(bool* aEnabled) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetEnabled(aEnabled); +} + +NS_IMETHODIMP nsChromeTreeOwner::SetEnabled(bool aEnable) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetEnabled(aEnable); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetMainWidget(nsIWidget** aMainWidget) { + NS_ENSURE_ARG_POINTER(aMainWidget); + NS_ENSURE_STATE(mAppWindow); + + *aMainWidget = mAppWindow->mWindow; + NS_IF_ADDREF(*aMainWidget); + + return NS_OK; +} + +NS_IMETHODIMP nsChromeTreeOwner::GetTitle(nsAString& aTitle) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetTitle(aTitle); +} + +NS_IMETHODIMP nsChromeTreeOwner::SetTitle(const nsAString& aTitle) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetTitle(aTitle); +} + +//***************************************************************************** +// nsChromeTreeOwner::nsIWebProgressListener +//***************************************************************************** + +NS_IMETHODIMP +nsChromeTreeOwner::OnProgressChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) { + return NS_OK; +} + +NS_IMETHODIMP +nsChromeTreeOwner::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aProgressStateFlags, + nsresult aStatus) { + return NS_OK; +} + +NS_IMETHODIMP +nsChromeTreeOwner::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, nsIURI* aLocation, + uint32_t aFlags) { + NS_ENSURE_STATE(mAppWindow); + + // If loading a new root .xul document, then redo chrome. + if (aWebProgress) { + nsCOMPtr<nsIDocShell> docshell; + mAppWindow->GetDocShell(getter_AddRefs(docshell)); + + nsCOMPtr<nsIWebProgress> webProgress(do_QueryInterface(docshell)); + if (webProgress != aWebProgress) { + return NS_OK; + } + } + + mAppWindow->mChromeLoaded = false; + return NS_OK; +} + +NS_IMETHODIMP +nsChromeTreeOwner::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, nsresult aStatus, + const char16_t* aMessage) { + return NS_OK; +} + +NS_IMETHODIMP +nsChromeTreeOwner::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, uint32_t aState) { + return NS_OK; +} + +NS_IMETHODIMP +nsChromeTreeOwner::OnContentBlockingEvent(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aEvent) { + return NS_OK; +} + +//***************************************************************************** +// nsChromeTreeOwner: Helpers +//***************************************************************************** + +//***************************************************************************** +// nsChromeTreeOwner: Accessors +//***************************************************************************** + +void nsChromeTreeOwner::AppWindow(mozilla::AppWindow* aAppWindow) { + mAppWindow = aAppWindow; +} + +mozilla::AppWindow* nsChromeTreeOwner::AppWindow() { return mAppWindow; } diff --git a/xpfe/appshell/nsChromeTreeOwner.h b/xpfe/appshell/nsChromeTreeOwner.h new file mode 100644 index 0000000000..768fc44628 --- /dev/null +++ b/xpfe/appshell/nsChromeTreeOwner.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsChromeTreeOwner_h__ +#define nsChromeTreeOwner_h__ + +// Helper Classes +#include "nsCOMPtr.h" + +// Interfaces Needed +#include "nsIBaseWindow.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIWebProgressListener.h" +#include "nsWeakReference.h" + +namespace mozilla { +class AppWindow; +} + +class nsChromeTreeOwner : public nsIDocShellTreeOwner, + public nsIBaseWindow, + public nsIInterfaceRequestor, + public nsIWebProgressListener, + public nsSupportsWeakReference { + friend class mozilla::AppWindow; + + public: + NS_DECL_ISUPPORTS + + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIBASEWINDOW + NS_DECL_NSIDOCSHELLTREEOWNER + NS_DECL_NSIWEBPROGRESSLISTENER + + protected: + nsChromeTreeOwner(); + virtual ~nsChromeTreeOwner(); + + void AppWindow(mozilla::AppWindow* aAppWindow); + mozilla::AppWindow* AppWindow(); + + protected: + mozilla::AppWindow* mAppWindow; +}; + +#endif /* nsChromeTreeOwner_h__ */ diff --git a/xpfe/appshell/nsContentTreeOwner.cpp b/xpfe/appshell/nsContentTreeOwner.cpp new file mode 100644 index 0000000000..72fa807bba --- /dev/null +++ b/xpfe/appshell/nsContentTreeOwner.cpp @@ -0,0 +1,664 @@ +/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=2 sw=2 et tw=80: + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Local Includes +#include "nsContentTreeOwner.h" +#include "AppWindow.h" + +// Interfaces needed to be included +#include "nsGlobalWindowOuter.h" +#include "nsIDOMWindow.h" +#include "nsIBrowserDOMWindow.h" +#include "nsIOpenWindowInfo.h" +#include "nsIPrompt.h" +#include "nsIAuthPrompt.h" +#include "nsIXULBrowserWindow.h" +#include "nsIPrincipal.h" +#include "nsIURIFixup.h" +#include "nsIWebNavigation.h" +#include "nsDocShellCID.h" +#include "nsIMIMEInfo.h" +#include "nsIWidget.h" +#include "nsWindowWatcher.h" +#include "nsIWindowMediator.h" +#include "mozilla/Components.h" +#include "mozilla/NullPrincipal.h" +#include "nsDocShell.h" +#include "nsDocShellLoadState.h" +#include "nsQueryActor.h" + +#include "nsIScriptObjectPrincipal.h" +#include "nsIURI.h" +#include "mozilla/dom/Document.h" +#if defined(XP_MACOSX) +# include "nsThreadUtils.h" +#endif + +#include "mozilla/Preferences.h" +#include "mozilla/Try.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/UserActivation.h" + +using namespace mozilla; + +//***************************************************************************** +//*** nsContentTreeOwner: Object Management +//***************************************************************************** + +nsContentTreeOwner::nsContentTreeOwner(bool fPrimary) + : mAppWindow(nullptr), mPrimary(fPrimary) {} + +//***************************************************************************** +// nsContentTreeOwner::nsISupports +//***************************************************************************** + +NS_IMPL_ADDREF(nsContentTreeOwner) +NS_IMPL_RELEASE(nsContentTreeOwner) + +NS_INTERFACE_MAP_BEGIN(nsContentTreeOwner) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocShellTreeOwner) + NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeOwner) + NS_INTERFACE_MAP_ENTRY(nsIBaseWindow) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsIWindowProvider) +NS_INTERFACE_MAP_END + +//***************************************************************************** +// nsContentTreeOwner::nsIInterfaceRequestor +//***************************************************************************** + +NS_IMETHODIMP nsContentTreeOwner::GetInterface(const nsIID& aIID, + void** aSink) { + NS_ENSURE_ARG_POINTER(aSink); + *aSink = nullptr; + + if (aIID.Equals(NS_GET_IID(nsIPrompt))) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetInterface(aIID, aSink); + } + if (aIID.Equals(NS_GET_IID(nsIAuthPrompt))) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetInterface(aIID, aSink); + } + if (aIID.Equals(NS_GET_IID(nsIDocShellTreeItem))) { + NS_ENSURE_STATE(mAppWindow); + nsCOMPtr<nsIDocShell> shell; + mAppWindow->GetDocShell(getter_AddRefs(shell)); + if (shell) return shell->QueryInterface(aIID, aSink); + return NS_ERROR_FAILURE; + } + + if (aIID.Equals(NS_GET_IID(nsIDOMWindow)) || + aIID.Equals(NS_GET_IID(nsPIDOMWindowOuter))) { + NS_ENSURE_STATE(mAppWindow); + nsCOMPtr<nsIDocShellTreeItem> shell; + mAppWindow->GetPrimaryContentShell(getter_AddRefs(shell)); + if (shell) { + nsCOMPtr<nsIInterfaceRequestor> thing(do_QueryInterface(shell)); + if (thing) return thing->GetInterface(aIID, aSink); + } + return NS_ERROR_FAILURE; + } + + if (aIID.Equals(NS_GET_IID(nsIAppWindow))) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->QueryInterface(aIID, aSink); + } + + return QueryInterface(aIID, aSink); +} + +//***************************************************************************** +// nsContentTreeOwner::nsIDocShellTreeOwner +//***************************************************************************** + +NS_IMETHODIMP +nsContentTreeOwner::ContentShellAdded(nsIDocShellTreeItem* aContentShell, + bool aPrimary) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->ContentShellAdded(aContentShell, aPrimary); +} + +NS_IMETHODIMP +nsContentTreeOwner::ContentShellRemoved(nsIDocShellTreeItem* aContentShell) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->ContentShellRemoved(aContentShell); +} + +NS_IMETHODIMP +nsContentTreeOwner::GetPrimaryContentShell(nsIDocShellTreeItem** aShell) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetPrimaryContentShell(aShell); +} + +NS_IMETHODIMP +nsContentTreeOwner::RemoteTabAdded(nsIRemoteTab* aTab, bool aPrimary) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->RemoteTabAdded(aTab, aPrimary); +} + +NS_IMETHODIMP +nsContentTreeOwner::RemoteTabRemoved(nsIRemoteTab* aTab) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->RemoteTabRemoved(aTab); +} + +NS_IMETHODIMP +nsContentTreeOwner::GetPrimaryRemoteTab(nsIRemoteTab** aTab) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetPrimaryRemoteTab(aTab); +} + +NS_IMETHODIMP +nsContentTreeOwner::GetPrimaryContentBrowsingContext( + mozilla::dom::BrowsingContext** aBc) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetPrimaryContentBrowsingContext(aBc); +} + +NS_IMETHODIMP +nsContentTreeOwner::GetPrimaryContentSize(int32_t* aWidth, int32_t* aHeight) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetPrimaryContentSize(aWidth, aHeight); +} + +NS_IMETHODIMP +nsContentTreeOwner::SetPrimaryContentSize(int32_t aWidth, int32_t aHeight) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetPrimaryContentSize(aWidth, aHeight); +} + +NS_IMETHODIMP +nsContentTreeOwner::GetRootShellSize(int32_t* aWidth, int32_t* aHeight) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetRootShellSize(aWidth, aHeight); +} + +NS_IMETHODIMP +nsContentTreeOwner::SetRootShellSize(int32_t aWidth, int32_t aHeight) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetRootShellSize(aWidth, aHeight); +} + +NS_IMETHODIMP nsContentTreeOwner::SizeShellTo(nsIDocShellTreeItem* aShellItem, + int32_t aCX, int32_t aCY) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SizeShellTo(aShellItem, aCX, aCY); +} + +NS_IMETHODIMP +nsContentTreeOwner::SetPersistence(bool aPersistPosition, bool aPersistSize, + bool aPersistSizeMode) { + NS_ENSURE_STATE(mAppWindow); + nsCOMPtr<dom::Element> docShellElement = mAppWindow->GetWindowDOMElement(); + if (!docShellElement) return NS_ERROR_FAILURE; + + nsAutoString persistString; + docShellElement->GetAttr(nsGkAtoms::persist, persistString); + + bool saveString = false; + int32_t index; + + // Set X + index = persistString.Find(u"screenX"); + if (!aPersistPosition && index >= 0) { + persistString.Cut(index, 7); + saveString = true; + } else if (aPersistPosition && index < 0) { + persistString.AppendLiteral(" screenX"); + saveString = true; + } + // Set Y + index = persistString.Find(u"screenY"); + if (!aPersistPosition && index >= 0) { + persistString.Cut(index, 7); + saveString = true; + } else if (aPersistPosition && index < 0) { + persistString.AppendLiteral(" screenY"); + saveString = true; + } + // Set CX + index = persistString.Find(u"width"); + if (!aPersistSize && index >= 0) { + persistString.Cut(index, 5); + saveString = true; + } else if (aPersistSize && index < 0) { + persistString.AppendLiteral(" width"); + saveString = true; + } + // Set CY + index = persistString.Find(u"height"); + if (!aPersistSize && index >= 0) { + persistString.Cut(index, 6); + saveString = true; + } else if (aPersistSize && index < 0) { + persistString.AppendLiteral(" height"); + saveString = true; + } + // Set SizeMode + index = persistString.Find(u"sizemode"); + if (!aPersistSizeMode && (index >= 0)) { + persistString.Cut(index, 8); + saveString = true; + } else if (aPersistSizeMode && (index < 0)) { + persistString.AppendLiteral(" sizemode"); + saveString = true; + } + + ErrorResult rv; + if (saveString) { + docShellElement->SetAttribute(u"persist"_ns, persistString, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsContentTreeOwner::GetPersistence(bool* aPersistPosition, bool* aPersistSize, + bool* aPersistSizeMode) { + NS_ENSURE_STATE(mAppWindow); + nsCOMPtr<dom::Element> docShellElement = mAppWindow->GetWindowDOMElement(); + if (!docShellElement) return NS_ERROR_FAILURE; + + nsAutoString persistString; + docShellElement->GetAttr(nsGkAtoms::persist, persistString); + + // data structure doesn't quite match the question, but it's close enough + // for what we want (since this method is never actually called...) + if (aPersistPosition) { + *aPersistPosition = persistString.Find(u"screenX") >= 0 || + persistString.Find(u"screenY") >= 0; + } + if (aPersistSize) { + *aPersistSize = + persistString.Find(u"width") >= 0 || persistString.Find(u"height") >= 0; + } + if (aPersistSizeMode) { + *aPersistSizeMode = persistString.Find(u"sizemode") >= 0; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsContentTreeOwner::GetTabCount(uint32_t* aResult) { + if (mAppWindow) { + return mAppWindow->GetTabCount(aResult); + } + + *aResult = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsContentTreeOwner::GetHasPrimaryContent(bool* aResult) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetHasPrimaryContent(aResult); +} + +//***************************************************************************** +// nsContentTreeOwner::nsIWebBrowserChrome +//***************************************************************************** + +NS_IMETHODIMP nsContentTreeOwner::SetLinkStatus(const nsAString& aStatusText) { + NS_ENSURE_STATE(mAppWindow); + + nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow; + mAppWindow->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow)); + + if (xulBrowserWindow) { + xulBrowserWindow->SetOverLink(aStatusText); + } + + return NS_OK; +} + +NS_IMETHODIMP nsContentTreeOwner::SetChromeFlags(uint32_t aChromeFlags) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetChromeFlags(aChromeFlags); +} + +NS_IMETHODIMP nsContentTreeOwner::GetChromeFlags(uint32_t* aChromeFlags) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetChromeFlags(aChromeFlags); +} + +NS_IMETHODIMP nsContentTreeOwner::ShowAsModal() { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->ShowModal(); +} + +NS_IMETHODIMP nsContentTreeOwner::IsWindowModal(bool* _retval) { + NS_ENSURE_STATE(mAppWindow); + *_retval = mAppWindow->mContinueModalLoop; + return NS_OK; +} + +//***************************************************************************** +// nsContentTreeOwner::nsIBaseWindow +//***************************************************************************** + +NS_IMETHODIMP nsContentTreeOwner::InitWindow(nativeWindow aParentNativeWindow, + nsIWidget* parentWidget, int32_t x, + int32_t y, int32_t cx, + int32_t cy) { + // Ignore wigdet parents for now. Don't think those are a vaild thing to + // call. + NS_ENSURE_SUCCESS(SetPositionAndSize(x, y, cx, cy, 0), NS_ERROR_FAILURE); + + return NS_OK; +} + +NS_IMETHODIMP nsContentTreeOwner::Destroy() { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->Destroy(); +} + +double nsContentTreeOwner::GetWidgetCSSToDeviceScale() { + return mAppWindow ? mAppWindow->GetWidgetCSSToDeviceScale() : 1.0; +} + +NS_IMETHODIMP nsContentTreeOwner::GetDevicePixelsPerDesktopPixel( + double* aScale) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetDevicePixelsPerDesktopPixel(aScale); +} + +NS_IMETHODIMP nsContentTreeOwner::SetPositionDesktopPix(int32_t aX, + int32_t aY) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetPositionDesktopPix(aX, aY); +} + +NS_IMETHODIMP nsContentTreeOwner::SetPosition(int32_t aX, int32_t aY) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetPosition(aX, aY); +} + +NS_IMETHODIMP nsContentTreeOwner::GetPosition(int32_t* aX, int32_t* aY) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetPosition(aX, aY); +} + +NS_IMETHODIMP nsContentTreeOwner::SetSize(int32_t aCX, int32_t aCY, + bool aRepaint) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetSize(aCX, aCY, aRepaint); +} + +NS_IMETHODIMP nsContentTreeOwner::GetSize(int32_t* aCX, int32_t* aCY) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetSize(aCX, aCY); +} + +NS_IMETHODIMP nsContentTreeOwner::SetPositionAndSize(int32_t aX, int32_t aY, + int32_t aCX, int32_t aCY, + uint32_t aFlags) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetPositionAndSize(aX, aY, aCX, aCY, aFlags); +} + +NS_IMETHODIMP nsContentTreeOwner::GetPositionAndSize(int32_t* aX, int32_t* aY, + int32_t* aCX, + int32_t* aCY) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetPositionAndSize(aX, aY, aCX, aCY); +} + +NS_IMETHODIMP +nsContentTreeOwner::SetDimensions(DimensionRequest&& aRequest) { + NS_ENSURE_STATE(mAppWindow); + if (aRequest.mDimensionKind == DimensionKind::Outer) { + return mAppWindow->SetDimensions(std::move(aRequest)); + } + + MOZ_TRY(aRequest.SupplementFrom(this)); + return aRequest.ApplyInnerTo(this, /* aAsRootShell */ false); +} + +NS_IMETHODIMP +nsContentTreeOwner::GetDimensions(DimensionKind aDimensionKind, int32_t* aX, + int32_t* aY, int32_t* aCX, int32_t* aCY) { + NS_ENSURE_STATE(mAppWindow); + if (aDimensionKind == DimensionKind::Outer) { + return mAppWindow->GetDimensions(aDimensionKind, aX, aY, aCX, aCY); + } + if (aY || aX) { + return NS_ERROR_NOT_IMPLEMENTED; + } + return GetPrimaryContentSize(aCX, aCY); +} + +NS_IMETHODIMP nsContentTreeOwner::Repaint(bool aForce) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->Repaint(aForce); +} + +NS_IMETHODIMP nsContentTreeOwner::GetParentWidget(nsIWidget** aParentWidget) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetParentWidget(aParentWidget); +} + +NS_IMETHODIMP nsContentTreeOwner::SetParentWidget(nsIWidget* aParentWidget) { + NS_ASSERTION(false, "You can't call this"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsContentTreeOwner::GetParentNativeWindow( + nativeWindow* aParentNativeWindow) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetParentNativeWindow(aParentNativeWindow); +} + +NS_IMETHODIMP nsContentTreeOwner::SetParentNativeWindow( + nativeWindow aParentNativeWindow) { + NS_ASSERTION(false, "You can't call this"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsContentTreeOwner::GetNativeHandle(nsAString& aNativeHandle) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetNativeHandle(aNativeHandle); +} + +NS_IMETHODIMP nsContentTreeOwner::GetVisibility(bool* aVisibility) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetVisibility(aVisibility); +} + +NS_IMETHODIMP nsContentTreeOwner::SetVisibility(bool aVisibility) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetVisibility(aVisibility); +} + +NS_IMETHODIMP nsContentTreeOwner::GetEnabled(bool* aEnabled) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->GetEnabled(aEnabled); +} + +NS_IMETHODIMP nsContentTreeOwner::SetEnabled(bool aEnable) { + NS_ENSURE_STATE(mAppWindow); + return mAppWindow->SetEnabled(aEnable); +} + +NS_IMETHODIMP nsContentTreeOwner::GetMainWidget(nsIWidget** aMainWidget) { + NS_ENSURE_ARG_POINTER(aMainWidget); + NS_ENSURE_STATE(mAppWindow); + + *aMainWidget = mAppWindow->mWindow; + NS_IF_ADDREF(*aMainWidget); + + return NS_OK; +} + +NS_IMETHODIMP nsContentTreeOwner::GetTitle(nsAString& aTitle) { + NS_ENSURE_STATE(mAppWindow); + + return mAppWindow->GetTitle(aTitle); +} + +NS_IMETHODIMP nsContentTreeOwner::SetTitle(const nsAString& aTitle) { + return NS_OK; +} + +//***************************************************************************** +// nsContentTreeOwner: nsIWindowProvider +//***************************************************************************** +NS_IMETHODIMP +nsContentTreeOwner::ProvideWindow( + nsIOpenWindowInfo* aOpenWindowInfo, uint32_t aChromeFlags, + bool aCalledFromJS, nsIURI* aURI, const nsAString& aName, + const nsACString& aFeatures, + const mozilla::dom::UserActivation::Modifiers& aModifiers, + bool aForceNoOpener, bool aForceNoReferrer, bool aIsPopupRequested, + nsDocShellLoadState* aLoadState, bool* aWindowIsNew, + dom::BrowsingContext** aReturn) { + NS_ENSURE_ARG_POINTER(aOpenWindowInfo); + + RefPtr<dom::BrowsingContext> parent = aOpenWindowInfo->GetParent(); + + *aReturn = nullptr; + + if (!mAppWindow) { + // Nothing to do here + return NS_OK; + } + +#ifdef DEBUG + nsCOMPtr<nsIDocShell> docshell = parent->GetDocShell(); + nsCOMPtr<nsIDocShellTreeOwner> parentOwner = do_GetInterface(docshell); + NS_ASSERTION( + SameCOMIdentity(parentOwner, static_cast<nsIDocShellTreeOwner*>(this)), + "Parent from wrong docshell tree?"); +#endif + + int32_t openLocation = nsWindowWatcher::GetWindowOpenLocation( + parent->GetDOMWindow(), aChromeFlags, aModifiers, aCalledFromJS, + aOpenWindowInfo->GetIsForPrinting()); + + if (openLocation != nsIBrowserDOMWindow::OPEN_NEWTAB && + openLocation != nsIBrowserDOMWindow::OPEN_NEWTAB_BACKGROUND && + openLocation != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW && + openLocation != nsIBrowserDOMWindow::OPEN_PRINT_BROWSER) { + // Just open a window normally + return NS_OK; + } + + nsCOMPtr<mozIDOMWindowProxy> domWin; + mAppWindow->GetWindowDOMWindow(getter_AddRefs(domWin)); + if (!domWin || !nsGlobalWindowOuter::Cast(domWin)->IsChromeWindow()) { + // Really odd... but whatever + NS_WARNING("AppWindow's DOMWindow is not a chrome window"); + return NS_OK; + } + + nsCOMPtr<nsIBrowserDOMWindow> browserDOMWin = + nsGlobalWindowOuter::Cast(domWin)->GetBrowserDOMWindow(); + if (!browserDOMWin) { + return NS_OK; + } + + *aWindowIsNew = (openLocation != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW); + + { + dom::AutoNoJSAPI nojsapi; + + uint32_t flags = nsIBrowserDOMWindow::OPEN_NEW; + if (aForceNoOpener) { + flags |= nsIBrowserDOMWindow::OPEN_NO_OPENER; + } + if (aForceNoReferrer) { + flags |= nsIBrowserDOMWindow::OPEN_NO_REFERRER; + } + + // Get a new rendering area from the browserDOMWin. + // Since we are not loading any URI, we follow the principle of least + // privilege and use a nullPrincipal as the triggeringPrincipal. + // + // This method handles setting the opener for us, so we don't need to set it + // ourselves. + RefPtr<NullPrincipal> nullPrincipal = + NullPrincipal::CreateWithoutOriginAttributes(); + return browserDOMWin->CreateContentWindow(aURI, aOpenWindowInfo, + openLocation, flags, + nullPrincipal, nullptr, aReturn); + } +} + +//***************************************************************************** +// nsContentTreeOwner: Accessors +//***************************************************************************** + +void nsContentTreeOwner::AppWindow(mozilla::AppWindow* aAppWindow) { + mAppWindow = aAppWindow; +} + +mozilla::AppWindow* nsContentTreeOwner::AppWindow() { return mAppWindow; } + +/* this implementation focuses another window. if there isn't another + window to focus, we do nothing. */ +NS_IMETHODIMP +nsContentTreeOwner::Blur() { + NS_DEFINE_CID(kWindowMediatorCID, NS_WINDOWMEDIATOR_CID); + + nsCOMPtr<nsISimpleEnumerator> windowEnumerator; + nsCOMPtr<nsIAppWindow> appWindow; + bool more, foundUs; + + { + nsCOMPtr<nsIWindowMediator> windowMediator( + do_GetService(kWindowMediatorCID)); + if (windowMediator) { + windowMediator->GetZOrderAppWindowEnumerator( + nullptr, true, getter_AddRefs(windowEnumerator)); + } + } + + if (!windowEnumerator) return NS_ERROR_FAILURE; + + // step through the top-level windows + foundUs = false; + windowEnumerator->HasMoreElements(&more); + while (more) { + nsCOMPtr<nsISupports> nextWindow; + nsCOMPtr<nsIAppWindow> nextAppWindow; + + windowEnumerator->GetNext(getter_AddRefs(nextWindow)); + nextAppWindow = do_QueryInterface(nextWindow); + + // got it!(?) + if (foundUs) { + appWindow = nextAppWindow; + break; + } + + // remember the very first one, in case we have to wrap + if (!appWindow) appWindow = nextAppWindow; + + // look for us + if (nextAppWindow == mAppWindow) { + foundUs = true; + } + + windowEnumerator->HasMoreElements(&more); + } + + // change focus to the window we just found + if (appWindow) { + nsCOMPtr<nsIDocShell> docshell; + appWindow->GetDocShell(getter_AddRefs(docshell)); + if (!docshell) { + return NS_OK; + } + + nsCOMPtr<nsPIDOMWindowOuter> domWindow = docshell->GetWindow(); + if (domWindow) domWindow->Focus(mozilla::dom::CallerType::System); + } + return NS_OK; +} diff --git a/xpfe/appshell/nsContentTreeOwner.h b/xpfe/appshell/nsContentTreeOwner.h new file mode 100644 index 0000000000..a2544e0bfb --- /dev/null +++ b/xpfe/appshell/nsContentTreeOwner.h @@ -0,0 +1,61 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsContentTreeOwner_h__ +#define nsContentTreeOwner_h__ + +// Helper Classes +#include "nsCOMPtr.h" +#include "nsString.h" + +// Interfaces Needed +#include "nsIBaseWindow.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIWebBrowserChrome.h" +#include "nsIWindowProvider.h" + +namespace mozilla { +class AppWindow; +} + +class nsContentTreeOwner final : public nsIDocShellTreeOwner, + public nsIBaseWindow, + public nsIInterfaceRequestor, + public nsIWebBrowserChrome, + public nsIWindowProvider { + friend class mozilla::AppWindow; + + public: + NS_DECL_ISUPPORTS + + NS_DECL_NSIBASEWINDOW + NS_DECL_NSIDOCSHELLTREEOWNER + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIWINDOWPROVIDER + + /* nsIWebBrowserChrome (Get/SetDimensions overlap with nsIBaseWindow) */ + NS_IMETHOD SetLinkStatus(const nsAString& status) override; + NS_IMETHOD GetChromeFlags(uint32_t* aChromeFlags) override; + NS_IMETHOD SetChromeFlags(uint32_t aChromeFlags) override; + NS_IMETHOD ShowAsModal() override; + NS_IMETHOD IsWindowModal(bool* _retval) override; + NS_IMETHOD Blur() override; + + protected: + explicit nsContentTreeOwner(bool fPrimary); + virtual ~nsContentTreeOwner() = default; + + void AppWindow(mozilla::AppWindow* aAppWindow); + mozilla::AppWindow* AppWindow(); + + protected: + mozilla::AppWindow* mAppWindow; + bool mPrimary; +}; + +#endif /* nsContentTreeOwner_h__ */ diff --git a/xpfe/appshell/nsIAppShellService.idl b/xpfe/appshell/nsIAppShellService.idl new file mode 100644 index 0000000000..d8de8ae149 --- /dev/null +++ b/xpfe/appshell/nsIAppShellService.idl @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIAppWindow; +interface nsIWindowlessBrowser; +interface nsIURI; +interface mozIDOMWindowProxy; +interface nsIAppShell; +interface nsIRemoteTab; + +[ptr] native JSContext(JSContext); + +%{C++ +#include "js/TypeDecls.h" +%} + +[scriptable, uuid(19266025-354c-4bb9-986b-3483b2b1cdef)] +interface nsIAppShellService : nsISupports +{ + /** + * Create a window, which will be initially invisible. + * @param aParent the parent window. Can be null. + * @param aUrl the contents of the new window. + * @param aChromeMask chrome flags affecting the kind of OS border + * given to the window. see nsIWebBrowserChrome for + * bit/flag definitions. + * @param aCallbacks interface providing C++ hooks for window initialization + * before the window is made visible. Can be null. + * Deprecated. + * @param aInitialWidth width, in pixels, of the window. Width of window + * at creation. Can be overridden by the "width" + * tag in the XUL. Set to NS_SIZETOCONTENT to force + * the window to wrap to its contents. + * @param aInitialHeight like aInitialWidth, but subtly different. + */ + const long SIZE_TO_CONTENT = -1; + nsIAppWindow createTopLevelWindow(in nsIAppWindow aParent, + in nsIURI aUrl, + in uint32_t aChromeMask, + in long aInitialWidth, + in long aInitialHeight); + + /** + * This is the constructor for creating an invisible DocShell. + * It is used to simulate DOM windows without an actual physical + * representation. + * @param aIsChrome Set true if you want to use it for chrome content. + * @param aChromeMask Used to specify chrome flags that should be set on the + * window. See nsIWebBrowserChrome for flag definitions. + */ + nsIWindowlessBrowser createWindowlessBrowser([optional] in bool aIsChrome, + [optional] in uint32_t aChromeMask); + + [noscript] + void createHiddenWindow(); + + void destroyHiddenWindow(); + + /** + * B2G multi-screen support. When open another top-level window on b2g, + * a screen ID is needed for identifying which screen this window is + * opened to. + * @param aScreenId Differentiate screens of windows. It is platform- + * specific due to the hardware limitation for now. + */ + [noscript] + void setScreenId(in uint32_t aScreenId); + + /** + * Return the (singleton) application hidden window, automatically created + * and maintained by this AppShellService. + * @param aResult the hidden window. Do not unhide hidden window. + * Do not taunt hidden window. + */ + readonly attribute nsIAppWindow hiddenWindow; + + /** + * Return the (singleton) application hidden window, automatically created + * and maintained by this AppShellService. + * @param aResult the hidden window. Do not unhide hidden window. + * Do not taunt hidden window. + */ + readonly attribute mozIDOMWindowProxy hiddenDOMWindow; + + /** + * Return true if the application hidden window was provided by the + * application. If it wasn't, the default hidden window was used. This will + * usually be false on all non-mac platforms. + */ + readonly attribute boolean applicationProvidedHiddenWindow; + + /** + * Add a window to the application's registry of windows. These windows + * are generally shown in the Windows taskbar, and the application + * knows it can't quit until it's out of registered windows. + * @param aWindow the window to register + * @note When this method is successful, it fires the global notification + * "xul-window-registered" + */ + void registerTopLevelWindow(in nsIAppWindow aWindow); + + /** + * Remove a window from the application's window registry. Note that + * this method won't automatically attempt to quit the app when + * the last window is unregistered. For that, see Quit(). + * @param aWindow you see the pattern + */ + void unregisterTopLevelWindow(in nsIAppWindow aWindow); + + /** + * Whether the hidden window has been lazily created. + */ + readonly attribute boolean hasHiddenWindow; + + /** + * Start/stop tracking lags in the event loop. + * If the event loop gets unresponsive, a "event-loop-lag" notification + * is sent. Note that calling `startEventLoopLagTracking` when tracking + * is already enabled has no effect. + * @return true if tracking succeeded. + */ + bool startEventLoopLagTracking(); + void stopEventLoopLagTracking(); +}; diff --git a/xpfe/appshell/nsIAppWindow.idl b/xpfe/appshell/nsIAppWindow.idl new file mode 100644 index 0000000000..3c3229071a --- /dev/null +++ b/xpfe/appshell/nsIAppWindow.idl @@ -0,0 +1,155 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * The nsIAppWindow + * + * When the window is destroyed, it will fire a "xul-window-destroyed" + * notification through the global observer service. + */ + +%{C++ +#include "LiveResizeListener.h" +#include "nsTArray.h" +%} + +interface nsIDocShell; +interface nsIDocShellTreeItem; +interface nsIXULBrowserWindow; +interface nsIRemoteTab; +interface mozIDOMWindowProxy; +interface nsIOpenWindowInfo; +webidl BrowsingContext; + +native LiveResizeListenerArray(nsTArray<RefPtr<mozilla::LiveResizeListener>>); + +[builtinclass, scriptable, uuid(d6d7a014-e28d-4c9d-8727-1cf6d870619b)] +interface nsIAppWindow : nsISupports +{ + /** + * The docshell owning the XUL for this window. + */ + readonly attribute nsIDocShell docShell; + + /** + * Indicates if this window is instrinsically sized. + */ + attribute boolean intrinsicallySized; + + /** + * The primary content shell. + * + * Note that this is a docshell tree item and therefore can not be assured of + * what object it is. It could be an editor, a docshell, or a browser object. + * Or down the road any other object that supports being a DocShellTreeItem + * Query accordingly to determine the capabilities. + */ + readonly attribute nsIDocShellTreeItem primaryContentShell; + + /** + * In multiprocess case we may not have primaryContentShell but + * primaryRemoteTab. + */ + readonly attribute nsIRemoteTab primaryRemoteTab; + + /** + * Helper for getting the BrowsingContext from either `primaryContentShell` or + * `primaryRemoteTab` depending on which is available. + */ + readonly attribute BrowsingContext primaryContentBrowsingContext; + + void remoteTabAdded(in nsIRemoteTab aTab, in boolean aPrimary); + void remoteTabRemoved(in nsIRemoteTab aTab); + + [noscript,notxpcom] LiveResizeListenerArray getLiveResizeListeners(); + + /** + * Returns the difference between the inner window size (client size) and the + * outer window size, in CSS pixels. + */ + [infallible] readonly attribute unsigned long outerToInnerHeightDifferenceInCSSPixels; + [infallible] readonly attribute unsigned long outerToInnerWidthDifferenceInCSSPixels; + + /** + * Move the window to a centered position. + * @param aRelative If not null, the window relative to which the window is + * moved. See aScreen parameter for details. + * @param aScreen PR_TRUE to center the window relative to the screen + * containing aRelative if aRelative is not null. If + * aRelative is null then relative to the screen of the + * opener window if it was initialized by passing it to + * nsWebShellWindow::Initialize. Failing that relative to + * the main screen. + * PR_FALSE to center it relative to aRelative itself. + * @param aAlert PR_TRUE to move the window to an alert position, + * generally centered horizontally and 1/3 down from the top. + */ + void center(in nsIAppWindow aRelative, in boolean aScreen, in boolean aAlert); + + /** + * Shows the window as a modal window. That is, ensures that it is visible + * and runs a local event loop, exiting only once the window has been closed. + */ + void showModal(); + + /** + * Locks the aspect ratio for a window. + * @param aShouldLock boolean + */ + void lockAspectRatio(in bool aShouldLock); + + const unsigned long lowestZ = 0; + const unsigned long loweredZ = 4; /* "alwaysLowered" attribute */ + const unsigned long normalZ = 5; + const unsigned long raisedZ = 6; /* "alwaysRaised" attribute */ + const unsigned long highestZ = 9; + + attribute unsigned long zLevel; + + attribute uint32_t chromeFlags; + + /** + * Begin assuming |chromeFlags| don't change hereafter, and assert + * if they do change. The state change is one-way and idempotent. + */ + void assumeChromeFlagsAreFrozen(); + + /** + * Create a new window. + * @param aChromeFlags see nsIWebBrowserChrome + * @param aOpenWindowInfo information about the request for a content window + * to be opened. Will be null for non-content loads. + * @return the newly minted window + */ + nsIAppWindow createNewWindow(in int32_t aChromeFlags, + in nsIOpenWindowInfo aOpenWindowInfo); + + attribute nsIXULBrowserWindow XULBrowserWindow; + + /** + * Back-door method to make sure some stuff is done when the document is + * ready for layout, that would cause expensive computation otherwise later. + * + * Do NOT call this unless you know what you're doing! In particular, + * calling this when this XUL window doesn't yet have a document in its + * docshell could cause problems. + */ + [noscript] void beforeStartLayout(); + + /** + * If the window was opened as a content window, this will return the initial + * nsIOpenWindowInfo to use. + */ + readonly attribute nsIOpenWindowInfo initialOpenWindowInfo; + + /** + * Request fast snapshot at RenderCompositor of WebRender. + * Since readback of Windows DirectComposition is very slow. + */ + void needFastSnaphot(); +}; diff --git a/xpfe/appshell/nsIWindowMediator.idl b/xpfe/appshell/nsIWindowMediator.idl new file mode 100644 index 0000000000..ef891c0537 --- /dev/null +++ b/xpfe/appshell/nsIWindowMediator.idl @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsISimpleEnumerator.idl" + +%{C++ +#define NS_WINDOWMEDIATOR_CID \ +{ 0x79a2b7cc, 0xf05b, 0x4605, \ + { 0xbf, 0xa0, 0xfa, 0xc5, 0x4f, 0x27, 0xee, 0xc8 } } + +#define NS_WINDOWMEDIATOR_CONTRACTID \ + "@mozilla.org/appshell/window-mediator;1" +%} + +interface mozIDOMWindow; +interface mozIDOMWindowProxy; +interface nsIAppWindow; +interface nsIWidget; +interface nsIWindowMediatorListener; + +[scriptable, uuid(df0da056-357d-427f-bafd-e6cbf19c9381)] +interface nsIWindowMediator: nsISupports +{ + /** Return an enumerator which iterates over all windows of type aWindowType + * from the oldest window to the youngest. + * @param aWindowType the returned enumerator will enumerate only + * windows of this type. ("type" is the + * |windowtype| attribute of the XML <window> element.) + * If null, all windows will be enumerated. + * @return an enumerator of nsIDOMWindows. Note that windows close + * asynchronously in many cases, so windows returned from this + * enumerator can have .closed set to true. Caveat enumerator! + */ + nsISimpleEnumerator getEnumerator(in wstring aWindowType); + + /** Identical to getEnumerator except: + * @return an enumerator of nsIAppWindows + */ + nsISimpleEnumerator getAppWindowEnumerator(in wstring aWindowType); + + /** Return an enumerator which iterates over all windows of type aWindowType + * in their z (front-to-back) order. Note this interface makes + * no requirement that a window couldn't be revisited if windows + * are re-ordered while z-order enumerators are active. + * @param aWindowType the returned enumerator will enumerate only + * windows of this type. ("type" is the + * |windowtype| attribute of the XML <window> element.) + * If null, all windows will be enumerated. + * @param aFrontToBack if true, the enumerator enumerates windows in order + * from front to back. back to front if false. + * @return an enumerator of nsIAppWindows + */ + nsISimpleEnumerator getZOrderAppWindowEnumerator(in wstring aWindowType, + in boolean aFrontToBack); + + /** This is a shortcut for simply fetching the first window in + * front to back order. + * @param aWindowType return the topmost window of this type. + * ("type" is the |windowtype| attribute of + * the XML <window> element.) + * If null, return the topmost window of any type. + * @return the topmost window + */ + mozIDOMWindowProxy getMostRecentWindow(in wstring aWindowType); + + /** This is a shortcut for getMostRecentWindow('navigator:browser'), but + * if that fails it also tries 'navigator:geckoview' and 'mail:3pane'. + * + * @return the topmost browser window + */ + mozIDOMWindowProxy getMostRecentBrowserWindow(); + + /** + * Same as getMostRecentWindow, but ignores private browsing + * windows. + */ + mozIDOMWindowProxy getMostRecentNonPBWindow(in wstring aWindowType); + + /** + * Return the outer window with the given ID, if any. Can return null. + */ + mozIDOMWindowProxy getOuterWindowWithId(in unsigned long long aOuterWindowID); + + /** + * Return the inner window with the given current window ID, if any. + * Can return null if no inner window with the ID exists or if it's not + * a current inner anymore. + */ + mozIDOMWindow getCurrentInnerWindowWithId(in unsigned long long aInnerWindowID); + + /** Add the window to the list of known windows. Listeners (see + * addListener) will be notified through their onOpenWindow method. + * @param aWindow the window to add + */ + [noscript] void registerWindow(in nsIAppWindow aWindow); + + /** Remove the window from the list of known windows. Listeners (see + * addListener) will be be notified through their onCloseWindow method. + * @param aWindow the window to remove + */ + [noscript] void unregisterWindow(in nsIAppWindow aWindow); + + /** Call this method when a window gains focus. It's a primitive means of + * determining the most recent window. It's no longer necessary and it + * really should be removed. + * @param aWindow the window which has gained focus + */ + [noscript] void updateWindowTimeStamp(in nsIAppWindow aWindow); + + /* z-ordering: */ + + const unsigned long zLevelTop = 1; + const unsigned long zLevelBottom = 2; + const unsigned long zLevelBelow = 3; // below some window + + /** A window wants to be moved in z-order. Calculate whether and how + * it should be constrained. Note this method is advisory only: + * it changes nothing either in WindowMediator's internal state + * or with the window. + * Note it compares the nsIAppWindow to nsIWidgets. A pure interface + * would use all nsIAppWindows. But we expect this to be called from + * callbacks originating in native window code. They are expected to + * hand us comparison values which are pulled from general storage + * in the native widget, and may not correspond to an nsIWidget at all. + * For that reason this interface requires only objects one step + * removed from the native window (nsIWidgets), and its implementation + * must be very understanding of what may be completely invalid + * pointers in those parameters. + * + * @param inWindow the window in question + * @param inPosition requested position + * values: zLevelTop: topmost window. zLevelBottom: bottom. + * zLevelBelow: below ioBelow. (the value of ioBelow will + * be ignored for zLevelTop and Bottom.) + * @param inBelow if inPosition==zLevelBelow, the window + * below which inWindow wants to be placed. Otherwise this + * variable is ignored. + * @param outPosition constrained position, values like inPosition. + * @param outBelow if outPosition==zLevelBelow, the window + * below which inWindow should be placed. Otherwise this + * this value will be null. + * @return PR_TRUE if the position returned is different from + * the position given. + */ + + [noscript] boolean calculateZPosition(in nsIAppWindow inWindow, + in unsigned long inPosition, + in nsIWidget inBelow, + out unsigned long outPosition, + out nsIWidget outBelow); + + /** A window has been positioned behind another. Inform WindowMediator + * @param inWindow the window in question + * @param inPosition new position. values: + * zLevelTop: topmost window. + * zLevelBottom: bottom. + * zLevelBelow: below inBelow. (inBelow is ignored + * for other values of inPosition.) + * @param inBelow the window inWindow is behind, if zLevelBelow + */ + [noscript] void setZPosition(in nsIAppWindow inWindow, + in unsigned long inPosition, + in nsIAppWindow inBelow); + + /** Return the window's Z level (as defined in nsIAppWindow). + * @param aWindow the window in question + * @return aWindow's z level + */ + [noscript] uint32_t getZLevel(in nsIAppWindow aWindow); + + /** Set the window's Z level (as defined in nsIAppWindow). The implementation + * will reposition the window as necessary to match its new Z level. + * The implementation will assume a window's Z level to be + * nsIAppWindow::normalZ until it has been informed of a different level. + * @param aWindow the window in question + * @param aZLevel the window's new Z level + */ + [noscript] void setZLevel(in nsIAppWindow aWindow, in uint32_t aZLevel); + + /** Register a listener for window status changes. + * keeps strong ref? (to be decided) + * @param aListener the listener to register + */ + void addListener(in nsIWindowMediatorListener aListener); + + /** Unregister a listener of window status changes. + * @param aListener the listener to unregister + */ + void removeListener(in nsIWindowMediatorListener aListener); +}; diff --git a/xpfe/appshell/nsIWindowMediatorListener.idl b/xpfe/appshell/nsIWindowMediatorListener.idl new file mode 100644 index 0000000000..39c2c64ba7 --- /dev/null +++ b/xpfe/appshell/nsIWindowMediatorListener.idl @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIAppWindow; + +[scriptable, uuid(2F276982-0D60-4377-A595-D350BA516395)] +interface nsIWindowMediatorListener : nsISupports +{ + void onOpenWindow(in nsIAppWindow window); + void onCloseWindow(in nsIAppWindow window); +}; diff --git a/xpfe/appshell/nsIWindowlessBrowser.idl b/xpfe/appshell/nsIWindowlessBrowser.idl new file mode 100644 index 0000000000..c8c08a3499 --- /dev/null +++ b/xpfe/appshell/nsIWindowlessBrowser.idl @@ -0,0 +1,41 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIWebNavigation.idl" + +interface nsIDocShell; +webidl BrowsingContext; + +/** + * This interface represents a nsIWebBrowser instance with no associated OS + * window. Its main function is to manage the lifetimes of those windows. + * A strong reference to this object must be held until the window is + * ready to be destroyed. + */ +[scriptable, builtinclass, uuid(abb46f48-abfc-41bf-aa9a-7feccefcf977)] +interface nsIWindowlessBrowser : nsIWebNavigation +{ + /** + * "Closes" the windowless browser and destroys its associated nsIWebBrowser + * and docshell. + * + * This method *must* be called for every windowless browser before its last + * reference is released. + */ + void close(); + + /** + * Get the docshell for this browser. This is the docshell that gets + * navigated when the browser's nsIWebNavigation interface is used. + */ + readonly attribute nsIDocShell docShell; + + /** + * Get the Browsing Context for this browser. This is the Browsing Context + * that owns the docshell used for navigation. + */ + readonly attribute BrowsingContext browsingContext; +}; diff --git a/xpfe/appshell/nsIXULBrowserWindow.idl b/xpfe/appshell/nsIXULBrowserWindow.idl new file mode 100644 index 0000000000..9adb4dd5a1 --- /dev/null +++ b/xpfe/appshell/nsIXULBrowserWindow.idl @@ -0,0 +1,58 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIURI.idl" + +interface nsIBrowser; +interface nsIRequest; +interface nsIInputStream; +interface nsIDocShell; +interface nsIRemoteTab; +interface nsIPrincipal; +interface mozIDOMWindowProxy; +interface nsIContentSecurityPolicy; +interface nsIReferrerInfo; + +webidl Element; +webidl Node; + +/** + * The nsIXULBrowserWindow supplies the methods that may be called from the + * internals of the browser area to tell the containing xul window to update + * its ui. + */ +[scriptable, uuid(a8675fa9-c8b4-4350-9803-c38f344a9e38)] +interface nsIXULBrowserWindow : nsISupports +{ + /** + * Tells the object implementing this function what link we are currently + * over. + */ + void setOverLink(in AString link); + + /** + * Determines the appropriate target for a link. + */ + AString onBeforeLinkTraversal(in AString originalTarget, + in nsIURI linkURI, + in Node linkNode, + in boolean isAppTab); + + /** + * Show/hide a tooltip (when the user mouses over a link, say). + * + * x and y coordinates are in device pixels. + */ + void showTooltip(in long x, in long y, in AString tooltip, in AString direction, + in Element browser); + void hideTooltip(); + + /** + * Return the number of tabs in this window. + */ + uint32_t getTabCount(); +}; diff --git a/xpfe/appshell/nsWindowMediator.cpp b/xpfe/appshell/nsWindowMediator.cpp new file mode 100644 index 0000000000..0e5f38898c --- /dev/null +++ b/xpfe/appshell/nsWindowMediator.cpp @@ -0,0 +1,749 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "nsEnumeratorUtils.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsUnicharUtils.h" +#include "nsTArray.h" +#include "nsIBaseWindow.h" +#include "nsIWidget.h" +#include "nsIObserverService.h" +#include "nsISimpleEnumerator.h" +#include "nsAppShellWindowEnumerator.h" +#include "nsWindowMediator.h" +#include "nsIWindowMediatorListener.h" +#include "nsGlobalWindowInner.h" +#include "nsGlobalWindowOuter.h" +#include "nsServiceManagerUtils.h" + +#include "nsIDocShell.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIAppWindow.h" + +using namespace mozilla; + +nsresult nsWindowMediator::GetDOMWindow( + nsIAppWindow* inWindow, nsCOMPtr<nsPIDOMWindowOuter>& outDOMWindow) { + nsCOMPtr<nsIDocShell> docShell; + + outDOMWindow = nullptr; + inWindow->GetDocShell(getter_AddRefs(docShell)); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + + outDOMWindow = docShell->GetWindow(); + return outDOMWindow ? NS_OK : NS_ERROR_FAILURE; +} + +nsWindowMediator::nsWindowMediator() + : mOldestWindow(nullptr), + mTopmostWindow(nullptr), + mTimeStamp(0), + mSortingZOrder(false), + mReady(false) {} + +nsWindowMediator::~nsWindowMediator() { + while (mOldestWindow) UnregisterWindow(mOldestWindow); +} + +nsresult nsWindowMediator::Init() { + nsresult rv; + nsCOMPtr<nsIObserverService> obsSvc = + do_GetService("@mozilla.org/observer-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = obsSvc->AddObserver(this, "xpcom-shutdown", true); + NS_ENSURE_SUCCESS(rv, rv); + + mReady = true; + return NS_OK; +} + +NS_IMETHODIMP nsWindowMediator::RegisterWindow(nsIAppWindow* inWindow) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + if (!mReady) { + NS_ERROR("Mediator is not initialized or about to die."); + return NS_ERROR_FAILURE; + } + + if (GetInfoFor(inWindow)) { + NS_ERROR("multiple window registration"); + return NS_ERROR_FAILURE; + } + + mTimeStamp++; + + // Create window info struct and add to list of windows + nsWindowInfo* windowInfo = new nsWindowInfo(inWindow, mTimeStamp); + + for (const auto& listener : mListeners.ForwardRange()) { + listener->OnOpenWindow(inWindow); + } + + if (mOldestWindow) + windowInfo->InsertAfter(mOldestWindow->mOlder, nullptr); + else + mOldestWindow = windowInfo; + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::UnregisterWindow(nsIAppWindow* inWindow) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mReady); + NS_ENSURE_STATE(mReady); + nsWindowInfo* info = GetInfoFor(inWindow); + if (info) return UnregisterWindow(info); + return NS_ERROR_INVALID_ARG; +} + +nsresult nsWindowMediator::UnregisterWindow(nsWindowInfo* inInfo) { + // Inform the iterators + uint32_t index = 0; + while (index < mEnumeratorList.Length()) { + mEnumeratorList[index]->WindowRemoved(inInfo); + index++; + } + + nsIAppWindow* window = inInfo->mWindow.get(); + for (const auto& listener : mListeners.ForwardRange()) { + listener->OnCloseWindow(window); + } + + // Remove from the lists and free up + if (inInfo == mOldestWindow) mOldestWindow = inInfo->mYounger; + if (inInfo == mTopmostWindow) mTopmostWindow = inInfo->mLower; + inInfo->Unlink(true, true); + if (inInfo == mOldestWindow) mOldestWindow = nullptr; + if (inInfo == mTopmostWindow) mTopmostWindow = nullptr; + delete inInfo; + + return NS_OK; +} + +nsWindowInfo* nsWindowMediator::GetInfoFor(nsIAppWindow* aWindow) { + nsWindowInfo *info, *listEnd; + + if (!aWindow) return nullptr; + + info = mOldestWindow; + listEnd = nullptr; + while (info != listEnd) { + if (info->mWindow.get() == aWindow) return info; + info = info->mYounger; + listEnd = mOldestWindow; + } + return nullptr; +} + +nsWindowInfo* nsWindowMediator::GetInfoFor(nsIWidget* aWindow) { + nsWindowInfo *info, *listEnd; + + if (!aWindow) return nullptr; + + info = mOldestWindow; + listEnd = nullptr; + + nsCOMPtr<nsIWidget> scanWidget; + while (info != listEnd) { + nsCOMPtr<nsIBaseWindow> base(do_QueryInterface(info->mWindow)); + if (base) base->GetMainWidget(getter_AddRefs(scanWidget)); + if (aWindow == scanWidget.get()) return info; + info = info->mYounger; + listEnd = mOldestWindow; + } + return nullptr; +} + +NS_IMETHODIMP +nsWindowMediator::GetEnumerator(const char16_t* inType, + nsISimpleEnumerator** outEnumerator) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(outEnumerator); + if (!mReady) { + // If we get here with mReady false, we most likely did observe + // xpcom-shutdown. We will return an empty enumerator such that + // we make happy Javascripts calling late without throwing. + return NS_NewEmptyEnumerator(outEnumerator); + } + RefPtr<nsAppShellWindowEnumerator> enumerator = + new nsASDOMWindowEarlyToLateEnumerator(inType, *this); + enumerator.forget(outEnumerator); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::GetAppWindowEnumerator(const char16_t* inType, + nsISimpleEnumerator** outEnumerator) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(outEnumerator); + if (!mReady) { + // If we get here with mReady false, we most likely did observe + // xpcom-shutdown. We will return an empty enumerator such that + // we make happy Javascripts calling late without throwing. + return NS_NewEmptyEnumerator(outEnumerator); + } + RefPtr<nsAppShellWindowEnumerator> enumerator = + new nsASAppWindowEarlyToLateEnumerator(inType, *this); + enumerator.forget(outEnumerator); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::GetZOrderAppWindowEnumerator(const char16_t* aWindowType, + bool aFrontToBack, + nsISimpleEnumerator** _retval) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(_retval); + if (!mReady) { + // If we get here with mReady false, we most likely did observe + // xpcom-shutdown. We will return an empty enumerator such that + // we make happy Javascripts calling late without throwing. + return NS_NewEmptyEnumerator(_retval); + } + RefPtr<nsAppShellWindowEnumerator> enumerator; + if (aFrontToBack) + enumerator = new nsASAppWindowFrontToBackEnumerator(aWindowType, *this); + else + enumerator = new nsASAppWindowBackToFrontEnumerator(aWindowType, *this); + + enumerator.forget(_retval); + return NS_OK; +} + +void nsWindowMediator::AddEnumerator(nsAppShellWindowEnumerator* inEnumerator) { + mEnumeratorList.AppendElement(inEnumerator); +} + +int32_t nsWindowMediator::RemoveEnumerator( + nsAppShellWindowEnumerator* inEnumerator) { + return mEnumeratorList.RemoveElement(inEnumerator); +} + +// Returns the window of type inType ( if null return any window type ) which +// has the most recent time stamp +NS_IMETHODIMP +nsWindowMediator::GetMostRecentWindow(const char16_t* inType, + mozIDOMWindowProxy** outWindow) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(outWindow); + *outWindow = nullptr; + if (!mReady) return NS_OK; + + // Find the most window with the highest time stamp that matches + // the requested type + nsWindowInfo* info = MostRecentWindowInfo(inType, false); + if (info && info->mWindow) { + nsCOMPtr<nsPIDOMWindowOuter> DOMWindow; + if (NS_SUCCEEDED(GetDOMWindow(info->mWindow, DOMWindow))) { + DOMWindow.forget(outWindow); + return NS_OK; + } + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::GetMostRecentBrowserWindow(mozIDOMWindowProxy** outWindow) { + nsresult rv = GetMostRecentWindow(u"navigator:browser", outWindow); + NS_ENSURE_SUCCESS(rv, rv); + +#ifdef MOZ_WIDGET_ANDROID + if (!*outWindow) { + rv = GetMostRecentWindow(u"navigator:geckoview", outWindow); + NS_ENSURE_SUCCESS(rv, rv); + } +#endif + +#ifdef MOZ_THUNDERBIRD + if (!*outWindow) { + rv = GetMostRecentWindow(u"mail:3pane", outWindow); + NS_ENSURE_SUCCESS(rv, rv); + } +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::GetMostRecentNonPBWindow(const char16_t* aType, + mozIDOMWindowProxy** aWindow) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(aWindow); + *aWindow = nullptr; + + nsWindowInfo* info = MostRecentWindowInfo(aType, true); + nsCOMPtr<nsPIDOMWindowOuter> domWindow; + if (info && info->mWindow) { + GetDOMWindow(info->mWindow, domWindow); + } + + if (!domWindow) { + return NS_ERROR_FAILURE; + } + + domWindow.forget(aWindow); + return NS_OK; +} + +nsWindowInfo* nsWindowMediator::MostRecentWindowInfo( + const char16_t* inType, bool aSkipPrivateBrowsingOrClosed) { + int32_t lastTimeStamp = -1; + nsAutoString typeString(inType); + bool allWindows = !inType || typeString.IsEmpty(); + + // Find the most recent window with the highest time stamp that matches + // the requested type and has the correct browsing mode. + nsWindowInfo* searchInfo = mOldestWindow; + nsWindowInfo* listEnd = nullptr; + nsWindowInfo* foundInfo = nullptr; + for (; searchInfo != listEnd; searchInfo = searchInfo->mYounger) { + listEnd = mOldestWindow; + + if (!allWindows && !searchInfo->TypeEquals(typeString)) { + continue; + } + if (searchInfo->mTimeStamp < lastTimeStamp) { + continue; + } + if (!searchInfo->mWindow) { + continue; + } + if (aSkipPrivateBrowsingOrClosed) { + nsCOMPtr<nsIDocShell> docShell; + searchInfo->mWindow->GetDocShell(getter_AddRefs(docShell)); + nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell); + if (!loadContext || loadContext->UsePrivateBrowsing()) { + continue; + } + + nsCOMPtr<nsPIDOMWindowOuter> piwindow = docShell->GetWindow(); + if (!piwindow || piwindow->Closed()) { + continue; + } + } + + foundInfo = searchInfo; + lastTimeStamp = searchInfo->mTimeStamp; + } + + return foundInfo; +} + +NS_IMETHODIMP +nsWindowMediator::GetOuterWindowWithId(uint64_t aWindowID, + mozIDOMWindowProxy** aWindow) { + RefPtr<nsGlobalWindowOuter> window = + nsGlobalWindowOuter::GetOuterWindowWithId(aWindowID); + window.forget(aWindow); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::GetCurrentInnerWindowWithId(uint64_t aWindowID, + mozIDOMWindow** aWindow) { + RefPtr<nsGlobalWindowInner> window = + nsGlobalWindowInner::GetInnerWindowWithId(aWindowID); + + // not found + if (!window) return NS_OK; + + nsCOMPtr<nsPIDOMWindowOuter> outer = window->GetOuterWindow(); + NS_ENSURE_TRUE(outer, NS_ERROR_UNEXPECTED); + + // outer is already using another inner, so it's same as not found + if (outer->GetCurrentInnerWindow() != window) return NS_OK; + + window.forget(aWindow); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::UpdateWindowTimeStamp(nsIAppWindow* inWindow) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mReady); + NS_ENSURE_STATE(mReady); + nsWindowInfo* info = GetInfoFor(inWindow); + if (info) { + // increment the window's time stamp + info->mTimeStamp = ++mTimeStamp; + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +/* This method's plan is to intervene only when absolutely necessary. + We will get requests to place our windows behind unknown windows. + For the most part, we need to leave those alone (turning them into + explicit requests to be on top breaks Windows.) So generally we + calculate a change as seldom as possible. +*/ +NS_IMETHODIMP +nsWindowMediator::CalculateZPosition(nsIAppWindow* inWindow, + uint32_t inPosition, nsIWidget* inBelow, + uint32_t* outPosition, + nsIWidget** outBelow, bool* outAltered) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(outBelow); + MOZ_ASSERT(mReady); + NS_ENSURE_STATE(mReady); + + *outBelow = nullptr; + + if (!inWindow || !outPosition || !outAltered) return NS_ERROR_NULL_POINTER; + + if (inPosition != nsIWindowMediator::zLevelTop && + inPosition != nsIWindowMediator::zLevelBottom && + inPosition != nsIWindowMediator::zLevelBelow) + return NS_ERROR_INVALID_ARG; + + nsWindowInfo* info = mTopmostWindow; + nsIAppWindow* belowWindow = nullptr; + bool found = false; + nsresult result = NS_OK; + + *outPosition = inPosition; + *outAltered = false; + + if (mSortingZOrder) { // don't fight SortZOrder() + *outBelow = inBelow; + NS_IF_ADDREF(*outBelow); + return NS_OK; + } + + uint32_t inZ; + GetZLevel(inWindow, &inZ); + + if (inPosition == nsIWindowMediator::zLevelBelow) { + // locate inBelow. use topmost if it can't be found or isn't in the + // z-order list + info = GetInfoFor(inBelow); + if (!info || (info->mYounger != info && info->mLower == info)) + info = mTopmostWindow; + else + found = true; + + if (!found) { + /* Treat unknown windows as a request to be on top. + Not as it should be, but that's what Windows gives us. + Note we change inPosition, but not *outPosition. This forces + us to go through the "on top" calculation just below, without + necessarily changing the output parameters. */ + inPosition = nsIWindowMediator::zLevelTop; + } + } + + if (inPosition == nsIWindowMediator::zLevelTop) { + if (mTopmostWindow && mTopmostWindow->mZLevel > inZ) { + // asked for topmost, can't have it. locate highest allowed position. + do { + if (info->mZLevel <= inZ) break; + info = info->mLower; + } while (info != mTopmostWindow); + + *outPosition = nsIWindowMediator::zLevelBelow; + belowWindow = info->mHigher->mWindow; + *outAltered = true; + } + } else if (inPosition == nsIWindowMediator::zLevelBottom) { + if (mTopmostWindow && mTopmostWindow->mHigher->mZLevel < inZ) { + // asked for bottommost, can't have it. locate lowest allowed position. + do { + info = info->mHigher; + if (info->mZLevel >= inZ) break; + } while (info != mTopmostWindow); + + *outPosition = nsIWindowMediator::zLevelBelow; + belowWindow = info->mWindow; + *outAltered = true; + } + } else { + unsigned long relativeZ; + + // check that we're in the right z-plane + if (found) { + belowWindow = info->mWindow; + relativeZ = info->mZLevel; + if (relativeZ > inZ) { + // might be OK. is lower window, if any, lower? + if (info->mLower != info && info->mLower->mZLevel > inZ) { + do { + if (info->mZLevel <= inZ) break; + info = info->mLower; + } while (info != mTopmostWindow); + + belowWindow = info->mHigher->mWindow; + *outAltered = true; + } + } else if (relativeZ < inZ) { + // nope. look for a higher window to be behind. + do { + info = info->mHigher; + if (info->mZLevel >= inZ) break; + } while (info != mTopmostWindow); + + if (info->mZLevel >= inZ) + belowWindow = info->mWindow; + else + *outPosition = nsIWindowMediator::zLevelTop; + *outAltered = true; + } // else they're equal, so it's OK + } + } + + if (NS_SUCCEEDED(result) && belowWindow) { + nsCOMPtr<nsIBaseWindow> base(do_QueryInterface(belowWindow)); + if (base) + base->GetMainWidget(outBelow); + else + result = NS_ERROR_NO_INTERFACE; + } + + return result; +} + +NS_IMETHODIMP +nsWindowMediator::SetZPosition(nsIAppWindow* inWindow, uint32_t inPosition, + nsIAppWindow* inBelow) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + nsWindowInfo *inInfo, *belowInfo; + + if ((inPosition != nsIWindowMediator::zLevelTop && + inPosition != nsIWindowMediator::zLevelBottom && + inPosition != nsIWindowMediator::zLevelBelow) || + !inWindow) { + return NS_ERROR_INVALID_ARG; + } + + if (mSortingZOrder) // don't fight SortZOrder() + return NS_OK; + + MOZ_ASSERT(mReady); + NS_ENSURE_STATE(mReady); + + /* Locate inWindow and unlink it from the z-order list. + It's important we look for it in the age list, not the z-order list. + This is because the former is guaranteed complete, while + now may be this window's first exposure to the latter. */ + inInfo = GetInfoFor(inWindow); + if (!inInfo) return NS_ERROR_INVALID_ARG; + + // locate inBelow, place inWindow behind it + if (inPosition == nsIWindowMediator::zLevelBelow) { + belowInfo = GetInfoFor(inBelow); + // it had better also be in the z-order list + if (belowInfo && belowInfo->mYounger != belowInfo && + belowInfo->mLower == belowInfo) { + belowInfo = nullptr; + } + if (!belowInfo) { + if (inBelow) + return NS_ERROR_INVALID_ARG; + else + inPosition = nsIWindowMediator::zLevelTop; + } + } + if (inPosition == nsIWindowMediator::zLevelTop || + inPosition == nsIWindowMediator::zLevelBottom) + belowInfo = mTopmostWindow ? mTopmostWindow->mHigher : nullptr; + + if (inInfo != belowInfo) { + inInfo->Unlink(false, true); + inInfo->InsertAfter(nullptr, belowInfo); + } + if (inPosition == nsIWindowMediator::zLevelTop) mTopmostWindow = inInfo; + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::GetZLevel(nsIAppWindow* aWindow, uint32_t* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nsIAppWindow::normalZ; + // This can fail during window destruction. + nsWindowInfo* info = GetInfoFor(aWindow); + if (info) { + *_retval = info->mZLevel; + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::SetZLevel(nsIAppWindow* aWindow, uint32_t aZLevel) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mReady); + NS_ENSURE_STATE(mReady); + + nsWindowInfo* info = GetInfoFor(aWindow); + NS_ASSERTION(info, "setting z level of unregistered window"); + if (!info) return NS_ERROR_FAILURE; + + if (info->mZLevel != aZLevel) { + bool lowered = info->mZLevel > aZLevel; + info->mZLevel = aZLevel; + if (lowered) + SortZOrderFrontToBack(); + else + SortZOrderBackToFront(); + } + return NS_OK; +} + +/* Fix potentially out-of-order windows by performing an insertion sort + on the z-order list. The method will work no matter how broken the + list, but its assumed usage is immediately after one window's z level + has been changed, so one window is potentially out of place. Such a sort + is most efficiently done in a particular direction. Use this one + if a window's z level has just been reduced, so the sort is most efficiently + done front to back. + Note it's hardly worth going to all the trouble to write two versions + of this method except that if we choose the inefficient sorting direction, + on slow systems windows could visibly bubble around the window that + was moved. +*/ +void nsWindowMediator::SortZOrderFrontToBack() { + nsWindowInfo *scan, // scans list looking for problems + *search, // searches for correct placement for scan window + *prev, // previous search element + *lowest; // bottom-most window in list + bool finished; + + if (!mTopmostWindow) // early during program execution there's no z list yet + return; // there's also only one window, so this is not dangerous + + mSortingZOrder = true; + + /* Step through the list from top to bottom. If we find a window which + should be moved down in the list, move it to its highest legal position. */ + do { + finished = true; + lowest = mTopmostWindow->mHigher; + scan = mTopmostWindow; + while (scan != lowest) { + uint32_t scanZ = scan->mZLevel; + if (scanZ < scan->mLower->mZLevel) { // out of order + search = scan->mLower; + do { + prev = search; + search = search->mLower; + } while (prev != lowest && scanZ < search->mZLevel); + + // reposition |scan| within the list + if (scan == mTopmostWindow) mTopmostWindow = scan->mLower; + scan->Unlink(false, true); + scan->InsertAfter(nullptr, prev); + + // fix actual window order + nsCOMPtr<nsIBaseWindow> base; + nsCOMPtr<nsIWidget> scanWidget; + nsCOMPtr<nsIWidget> prevWidget; + base = do_QueryInterface(scan->mWindow); + if (base) base->GetMainWidget(getter_AddRefs(scanWidget)); + base = do_QueryInterface(prev->mWindow); + if (base) base->GetMainWidget(getter_AddRefs(prevWidget)); + if (scanWidget) + scanWidget->PlaceBehind(eZPlacementBelow, prevWidget, false); + + finished = false; + break; + } + scan = scan->mLower; + } + } while (!finished); + + mSortingZOrder = false; +} + +// see comment for SortZOrderFrontToBack +void nsWindowMediator::SortZOrderBackToFront() { + nsWindowInfo *scan, // scans list looking for problems + *search, // searches for correct placement for scan window + *lowest; // bottom-most window in list + bool finished; + + if (!mTopmostWindow) // early during program execution there's no z list yet + return; // there's also only one window, so this is not dangerous + + mSortingZOrder = true; + + /* Step through the list from bottom to top. If we find a window which + should be moved up in the list, move it to its lowest legal position. */ + do { + finished = true; + lowest = mTopmostWindow->mHigher; + scan = lowest; + while (scan != mTopmostWindow) { + uint32_t scanZ = scan->mZLevel; + if (scanZ > scan->mHigher->mZLevel) { // out of order + search = scan; + do { + search = search->mHigher; + } while (search != lowest && scanZ > search->mZLevel); + + // reposition |scan| within the list + if (scan != search && scan != search->mLower) { + scan->Unlink(false, true); + scan->InsertAfter(nullptr, search); + } + if (search == lowest) mTopmostWindow = scan; + + // fix actual window order + nsCOMPtr<nsIBaseWindow> base; + nsCOMPtr<nsIWidget> scanWidget; + nsCOMPtr<nsIWidget> searchWidget; + base = do_QueryInterface(scan->mWindow); + if (base) base->GetMainWidget(getter_AddRefs(scanWidget)); + if (mTopmostWindow != scan) { + base = do_QueryInterface(search->mWindow); + if (base) base->GetMainWidget(getter_AddRefs(searchWidget)); + } + if (scanWidget) + scanWidget->PlaceBehind(eZPlacementBelow, searchWidget, false); + finished = false; + break; + } + scan = scan->mHigher; + } + } while (!finished); + + mSortingZOrder = false; +} + +NS_IMPL_ISUPPORTS(nsWindowMediator, nsIWindowMediator, nsIObserver, + nsISupportsWeakReference) + +NS_IMETHODIMP +nsWindowMediator::AddListener(nsIWindowMediatorListener* aListener) { + NS_ENSURE_ARG_POINTER(aListener); + + mListeners.AppendElement(aListener); + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::RemoveListener(nsIWindowMediatorListener* aListener) { + NS_ENSURE_ARG_POINTER(aListener); + + mListeners.RemoveElement(aListener); + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, "xpcom-shutdown") && mReady) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + while (mOldestWindow) UnregisterWindow(mOldestWindow); + mReady = false; + } + return NS_OK; +} diff --git a/xpfe/appshell/nsWindowMediator.h b/xpfe/appshell/nsWindowMediator.h new file mode 100644 index 0000000000..578f901ea7 --- /dev/null +++ b/xpfe/appshell/nsWindowMediator.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsWindowMediator_h_ +#define nsWindowMediator_h_ + +#include "nsCOMPtr.h" +#include "nsIWindowMediator.h" +#include "nsIObserver.h" +#include "nsTArray.h" +#include "nsPIDOMWindow.h" +#include "nsString.h" +#include "nsWeakReference.h" +#include "nsTObserverArray.h" + +class nsAppShellWindowEnumerator; +class nsASAppWindowEarlyToLateEnumerator; +class nsASDOMWindowEarlyToLateEnumerator; +class nsASAppWindowFrontToBackEnumerator; +class nsASAppWindowBackToFrontEnumerator; +class nsIWindowMediatorListener; +struct nsWindowInfo; + +class nsWindowMediator : public nsIWindowMediator, + public nsIObserver, + public nsSupportsWeakReference { + friend class nsAppShellWindowEnumerator; + friend class nsASAppWindowEarlyToLateEnumerator; + friend class nsASDOMWindowEarlyToLateEnumerator; + friend class nsASAppWindowFrontToBackEnumerator; + friend class nsASAppWindowBackToFrontEnumerator; + + protected: + virtual ~nsWindowMediator(); + + public: + nsWindowMediator(); + + nsresult Init(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIWINDOWMEDIATOR + NS_DECL_NSIOBSERVER + + static nsresult GetDOMWindow(nsIAppWindow* inWindow, + nsCOMPtr<nsPIDOMWindowOuter>& outDOMWindow); + + private: + void AddEnumerator(nsAppShellWindowEnumerator* inEnumerator); + int32_t RemoveEnumerator(nsAppShellWindowEnumerator* inEnumerator); + nsWindowInfo* MostRecentWindowInfo(const char16_t* inType, + bool aSkipPrivateBrowsingOrClosed = false); + + nsresult UnregisterWindow(nsWindowInfo* inInfo); + nsWindowInfo* GetInfoFor(nsIAppWindow* aWindow); + nsWindowInfo* GetInfoFor(nsIWidget* aWindow); + void SortZOrderFrontToBack(); + void SortZOrderBackToFront(); + + nsTArray<nsAppShellWindowEnumerator*> mEnumeratorList; + nsWindowInfo* mOldestWindow; + nsWindowInfo* mTopmostWindow; + int32_t mTimeStamp; + bool mSortingZOrder; + bool mReady; + + typedef nsTObserverArray<nsCOMPtr<nsIWindowMediatorListener>> ListenerArray; + ListenerArray mListeners; +}; + +#endif diff --git a/xpfe/appshell/test/chrome.toml b/xpfe/appshell/test/chrome.toml new file mode 100644 index 0000000000..aba2e1e99a --- /dev/null +++ b/xpfe/appshell/test/chrome.toml @@ -0,0 +1,4 @@ +[DEFAULT] +skip-if = ["os == 'android'"] + +["test_windowlessBrowser.xhtml"] diff --git a/xpfe/appshell/test/test_windowlessBrowser.xhtml b/xpfe/appshell/test/test_windowlessBrowser.xhtml new file mode 100644 index 0000000000..73759a9117 --- /dev/null +++ b/xpfe/appshell/test/test_windowlessBrowser.xhtml @@ -0,0 +1,67 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=815847 +--> +<window title="Mozilla Bug 815847" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" /> + +<body xmlns="http://www.w3.org/1999/xhtml"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1214174">Mozilla Bug 1214174</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +function testWindowlessBrowser(chromePrivileged) { + var webNav = Services.appShell.createWindowlessBrowser(chromePrivileged); + + ok(webNav, "createWindowlessBrowser should return a wevNav"); + + let docShell = webNav.docShell; + + ok(docShell, "docShell should be defined"); + ok(docShell.docViewer.DOMDocument.defaultView, "docShell defaultView should be defined"); + + var win = docShell.docViewer.DOMDocument.defaultView; + + ok(win.screenX == 0, "window.screenX should be 0 in a windowless browser"); + ok(win.screenY == 0, "window.screenY should be 0 in a windowless browser"); + ok(win.outerWidth == 0, "window.outerWidth should be 0 in a windowless browser"); + ok(win.outerHeight == 0, "window.outerHeight should be 0 in a windowless browser"); + + ok(win.external, "window.external should be defined"); + + var exception; + + try { + win.external.AddSearchProvider("http://test-fake.url"); + } catch(e) { + exception = e; + } + + ok(!exception, "window.external.AddSearchProvider should be ignore withour raising an exception"); + + webNav.close(); +} + +info("Test Bug 1214174 on a content privileged windowless browser"); +testWindowlessBrowser(false); + +info("Test Bug 1214174 on a chrome privileged windowless browser"); +testWindowlessBrowser(true); + +]]> +</script> + +</window> |