diff options
Diffstat (limited to 'xpfe')
45 files changed, 13232 insertions, 0 deletions
diff --git a/xpfe/appshell/moz.build b/xpfe/appshell/moz.build new file mode 100644 index 000000000..5cfe41924 --- /dev/null +++ b/xpfe/appshell/moz.build @@ -0,0 +1,42 @@ +# -*- 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/. + +MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini'] + +XPIDL_SOURCES += [ + 'nsIAppShellService.idl', + 'nsIPopupWindowManager.idl', + 'nsIWindowlessBrowser.idl', + 'nsIWindowMediator.idl', + 'nsIWindowMediatorListener.idl', + 'nsIXULBrowserWindow.idl', + 'nsIXULWindow.idl', +] + +XPIDL_MODULE = 'appshell' + +EXPORTS += [ + 'nsAppShellCID.h', +] + +UNIFIED_SOURCES += [ + 'nsAppShellFactory.cpp', + 'nsAppShellService.cpp', + 'nsAppShellWindowEnumerator.cpp', + 'nsChromeTreeOwner.cpp', + 'nsContentTreeOwner.cpp', + 'nsWebShellWindow.cpp', + 'nsWindowMediator.cpp', + 'nsXULWindow.cpp', +] + +LOCAL_INCLUDES += [ + '/dom/base', +] + +FINAL_LIBRARY = 'xul' + +include('/ipc/chromium/chromium-config.mozbuild')
\ No newline at end of file diff --git a/xpfe/appshell/nsAppShellCID.h b/xpfe/appshell/nsAppShellCID.h new file mode 100644 index 000000000..2b0edd4ac --- /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/nsAppShellFactory.cpp b/xpfe/appshell/nsAppShellFactory.cpp new file mode 100644 index 000000000..0b6d3c47a --- /dev/null +++ b/xpfe/appshell/nsAppShellFactory.cpp @@ -0,0 +1,56 @@ +/* -*- 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 "mozilla/ModuleUtils.h" +#include "nscore.h" +#include "nsIWindowMediator.h" + +#include "nsIAppShellService.h" +#include "nsAppShellService.h" +#include "nsWindowMediator.h" +#include "nsChromeTreeOwner.h" +#include "nsAppShellCID.h" + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsAppShellService) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWindowMediator, Init) + +NS_DEFINE_NAMED_CID(NS_APPSHELLSERVICE_CID); +NS_DEFINE_NAMED_CID(NS_WINDOWMEDIATOR_CID); + +static const mozilla::Module::CIDEntry kAppShellCIDs[] = { + { &kNS_APPSHELLSERVICE_CID, false, nullptr, nsAppShellServiceConstructor }, + { &kNS_WINDOWMEDIATOR_CID, false, nullptr, nsWindowMediatorConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kAppShellContracts[] = { + { NS_APPSHELLSERVICE_CONTRACTID, &kNS_APPSHELLSERVICE_CID }, + { NS_WINDOWMEDIATOR_CONTRACTID, &kNS_WINDOWMEDIATOR_CID }, + { nullptr } +}; + +static nsresult +nsAppShellModuleConstructor() +{ + return nsChromeTreeOwner::InitGlobals(); +} + +static void +nsAppShellModuleDestructor() +{ + nsChromeTreeOwner::FreeGlobals(); +} + +static const mozilla::Module kAppShellModule = { + mozilla::Module::kVersion, + kAppShellCIDs, + kAppShellContracts, + nullptr, + nullptr, + nsAppShellModuleConstructor, + nsAppShellModuleDestructor +}; + +NSMODULE_DEFN(appshell) = &kAppShellModule; diff --git a/xpfe/appshell/nsAppShellService.cpp b/xpfe/appshell/nsAppShellService.cpp new file mode 100644 index 000000000..7e1ddf16c --- /dev/null +++ b/xpfe/appshell/nsAppShellService.cpp @@ -0,0 +1,1001 @@ +/* -*- 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 "nsIComponentManager.h" +#include "nsIURL.h" +#include "nsNetUtil.h" +#include "nsIServiceManager.h" +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "nsIXPConnect.h" +#include "nsIXULRuntime.h" + +#include "nsIWindowMediator.h" +#include "nsIWindowWatcher.h" +#include "nsPIWindowWatcher.h" +#include "nsIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsWebShellWindow.h" + +#include "prprf.h" + +#include "nsWidgetInitData.h" +#include "nsWidgetsCID.h" +#include "nsIWidget.h" +#include "nsIRequestObserver.h" +#include "nsIEmbeddingSiteWindow.h" + +#include "nsAppShellService.h" +#include "nsContentUtils.h" +#include "nsThreadUtils.h" +#include "nsISupportsPrimitives.h" +#include "nsIChromeRegistry.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 "nsEmbedCID.h" +#include "nsIWebBrowser.h" +#include "nsIDocShell.h" + +#ifdef MOZ_INSTRUMENT_EVENT_LOOP +#include "EventTracer.h" +#endif + +using namespace mozilla; + +// 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::CreateHiddenWindow() +{ + return CreateHiddenWindowHelper(false); +} + +NS_IMETHODIMP +nsAppShellService::SetScreenId(uint32_t aScreenId) +{ + mScreenId = aScreenId; + return NS_OK; +} + +void +nsAppShellService::EnsurePrivateHiddenWindow() +{ + if (!mHiddenPrivateWindow) { + CreateHiddenWindowHelper(true); + } +} + +nsresult +nsAppShellService::CreateHiddenWindowHelper(bool aIsPrivate) +{ + nsresult rv; + int32_t initialHeight = 100, initialWidth = 100; + +#ifdef XP_MACOSX + uint32_t chromeMask = 0; + nsAdoptingCString prefVal = + Preferences::GetCString("browser.hiddenWindowChromeURL"); + const char* hiddenWindowURL = prefVal.get() ? prefVal.get() : DEFAULT_HIDDENWINDOW_URL; + if (aIsPrivate) { + hiddenWindowURL = DEFAULT_HIDDENWINDOW_URL; + } else { + 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<nsWebShellWindow> newWindow; + if (!aIsPrivate) { + rv = JustCreateTopWindow(nullptr, url, + chromeMask, initialWidth, initialHeight, + true, nullptr, nullptr, getter_AddRefs(newWindow)); + NS_ENSURE_SUCCESS(rv, rv); + + mHiddenWindow.swap(newWindow); + } else { + // Create the hidden private window + chromeMask |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW; + + rv = JustCreateTopWindow(nullptr, url, + chromeMask, initialWidth, initialHeight, + true, nullptr, nullptr, getter_AddRefs(newWindow)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocShell> docShell; + newWindow->GetDocShell(getter_AddRefs(docShell)); + if (docShell) { + docShell->SetAffectPrivateSessionLifetime(false); + } + + mHiddenPrivateWindow.swap(newWindow); + } + + // RegisterTopLevelWindow(newWindow); -- Mac only + + return NS_OK; +} + +NS_IMETHODIMP +nsAppShellService::DestroyHiddenWindow() +{ + if (mHiddenWindow) { + mHiddenWindow->Destroy(); + + mHiddenWindow = nullptr; + } + + if (mHiddenPrivateWindow) { + mHiddenPrivateWindow->Destroy(); + + mHiddenPrivateWindow = nullptr; + } + + return NS_OK; +} + +/* + * Create a new top level window and display the given URL within it... + */ +NS_IMETHODIMP +nsAppShellService::CreateTopLevelWindow(nsIXULWindow *aParent, + nsIURI *aUrl, + uint32_t aChromeMask, + int32_t aInitialWidth, + int32_t aInitialHeight, + nsITabParent *aOpeningTab, + mozIDOMWindowProxy *aOpenerWindow, + nsIXULWindow **aResult) + +{ + nsresult rv; + + StartupTimeline::RecordOnce(StartupTimeline::CREATE_TOP_LEVEL_WINDOW); + + RefPtr<nsWebShellWindow> newWindow; + rv = JustCreateTopWindow(aParent, aUrl, + aChromeMask, aInitialWidth, aInitialHeight, + false, aOpeningTab, aOpenerWindow, + 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<nsIXULWindow> parent; + if (aChromeMask & nsIWebBrowserChrome::CHROME_DEPENDENT) + parent = aParent; + (*aResult)->SetZLevel(CalculateWindowZLevel(parent, aChromeMask)); + } + + return rv; +} + +/* + * This class provides a stub implementation of nsIWebBrowserChrome2, as needed + * by nsAppShellService::CreateWindowlessBrowser + */ +class WebBrowserChrome2Stub : public nsIWebBrowserChrome2, + public nsIEmbeddingSiteWindow, + public nsIInterfaceRequestor, + public nsSupportsWeakReference { +protected: + virtual ~WebBrowserChrome2Stub() {} +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWEBBROWSERCHROME + NS_DECL_NSIWEBBROWSERCHROME2 + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIEMBEDDINGSITEWINDOW +}; + +NS_INTERFACE_MAP_BEGIN(WebBrowserChrome2Stub) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebBrowserChrome) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome2) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIEmbeddingSiteWindow) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(WebBrowserChrome2Stub) +NS_IMPL_RELEASE(WebBrowserChrome2Stub) + +NS_IMETHODIMP +WebBrowserChrome2Stub::SetStatus(uint32_t aStatusType, const char16_t* aStatus) +{ + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::GetWebBrowser(nsIWebBrowser** aWebBrowser) +{ + NS_NOTREACHED("WebBrowserChrome2Stub::GetWebBrowser is not supported"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::SetWebBrowser(nsIWebBrowser* aWebBrowser) +{ + NS_NOTREACHED("WebBrowserChrome2Stub::SetWebBrowser is not supported"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::GetChromeFlags(uint32_t* aChromeFlags) +{ + *aChromeFlags = 0; + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::SetChromeFlags(uint32_t aChromeFlags) +{ + NS_NOTREACHED("WebBrowserChrome2Stub::SetChromeFlags is not supported"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::DestroyBrowserWindow() +{ + NS_NOTREACHED("WebBrowserChrome2Stub::DestroyBrowserWindow is not supported"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::SizeBrowserTo(int32_t aCX, int32_t aCY) +{ + NS_NOTREACHED("WebBrowserChrome2Stub::SizeBrowserTo is not supported"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::ShowAsModal() +{ + NS_NOTREACHED("WebBrowserChrome2Stub::ShowAsModal is not supported"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::IsWindowModal(bool* aResult) +{ + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::ExitModalEventLoop(nsresult aStatus) +{ + NS_NOTREACHED("WebBrowserChrome2Stub::ExitModalEventLoop is not supported"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::SetStatusWithContext(uint32_t aStatusType, + const nsAString& aStatusText, + nsISupports* aStatusContext) +{ + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::GetInterface(const nsIID& aIID, void** aSink) +{ + return QueryInterface(aIID, aSink); +} + +// nsIEmbeddingSiteWindow impl +NS_IMETHODIMP +WebBrowserChrome2Stub::GetDimensions(uint32_t flags, int32_t* x, int32_t* y, int32_t* cx, int32_t* cy) +{ + if (x) { + *x = 0; + } + + if (y) { + *y = 0; + } + + if (cx) { + *cx = 0; + } + + if (cy) { + *cy = 0; + } + + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::SetDimensions(uint32_t flags, int32_t x, int32_t y, int32_t cx, int32_t cy) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::SetFocus() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::GetVisibility(bool* aVisibility) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} +NS_IMETHODIMP +WebBrowserChrome2Stub::SetVisibility(bool aVisibility) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::GetTitle(char16_t** aTitle) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} +NS_IMETHODIMP +WebBrowserChrome2Stub::SetTitle(const char16_t* aTitle) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::GetSiteWindow(void** aSiteWindow) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserChrome2Stub::Blur() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +class BrowserDestroyer final : public Runnable +{ +public: + BrowserDestroyer(nsIWebBrowser *aBrowser, nsISupports *aContainer) : + mBrowser(aBrowser), + mContainer(aContainer) + { + } + + NS_IMETHOD + Run() override + { + // Explicitly destroy the browser, in case this isn't the last reference. + nsCOMPtr<nsIBaseWindow> window = do_QueryInterface(mBrowser); + return window->Destroy(); + } + +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_FORWARD_SAFE_NSIWEBNAVIGATION(mWebNavigation) + NS_FORWARD_SAFE_NSIINTERFACEREQUESTOR(mInterfaceRequestor) + + NS_IMETHOD + Close() override + { + 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; + + nsCOMPtr<nsIBaseWindow> window = do_QueryInterface(mBrowser); + return window->Destroy(); + } + +protected: + virtual ~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. + nsCOMPtr<nsIRunnable> runnable = new BrowserDestroyer(mBrowser, mContainer); + nsContentUtils::AddScriptRunner(runnable); + } + +private: + 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 +nsAppShellService::CreateWindowlessBrowser(bool aIsChrome, nsIWindowlessBrowser **aResult) +{ + /* First, 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 = do_CreateInstance(NS_WEBBROWSER_CONTRACTID); + if (!browser) { + NS_ERROR("Couldn't create instance of nsWebBrowser!"); + return NS_ERROR_FAILURE; + } + + /* Next, 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 nsIWebBrowserChrome2. + */ + RefPtr<WebBrowserChrome2Stub> stub = new WebBrowserChrome2Stub(); + browser->SetContainerWindow(stub); + + nsCOMPtr<nsIWebNavigation> navigation = do_QueryInterface(browser); + + nsCOMPtr<nsIDocShellTreeItem> item = do_QueryInterface(navigation); + item->SetItemType(aIsChrome ? nsIDocShellTreeItem::typeChromeWrapper + : nsIDocShellTreeItem::typeContentWrapper); + + /* 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 PuppetWidget, which provides a stub + * implementation of nsIWidget. + */ + nsCOMPtr<nsIWidget> widget = nsIWidget::CreatePuppetWidget(nullptr); + if (!widget) { + NS_ERROR("Couldn't create instance of PuppetWidget"); + return NS_ERROR_FAILURE; + } + nsresult rv = + widget->Create(nullptr, 0, LayoutDeviceIntRect(0, 0, 0, 0), nullptr); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIBaseWindow> window = do_QueryInterface(navigation); + window->InitWindow(0, widget, 0, 0, 0, 0); + window->Create(); + + nsISupports *isstub = NS_ISUPPORTS_CAST(nsIWebBrowserChrome2*, 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(nsIXULWindow *aParent, + uint32_t aChromeMask) +{ + uint32_t zLevel; + + zLevel = nsIXULWindow::normalZ; + if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_RAISED) + zLevel = nsIXULWindow::raisedZ; + else if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_LOWERED) + zLevel = nsIXULWindow::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; +} + +#ifdef XP_WIN +/* + * Checks to see if any existing window is currently in fullscreen mode. + */ +static bool +CheckForFullscreenWindow() +{ + nsCOMPtr<nsIWindowMediator> wm(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (!wm) + return false; + + nsCOMPtr<nsISimpleEnumerator> windowList; + wm->GetXULWindowEnumerator(nullptr, getter_AddRefs(windowList)); + if (!windowList) + return false; + + for (;;) { + bool more = false; + windowList->HasMoreElements(&more); + if (!more) + return false; + + nsCOMPtr<nsISupports> supportsWindow; + windowList->GetNext(getter_AddRefs(supportsWindow)); + nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(supportsWindow)); + if (baseWin) { + nsCOMPtr<nsIWidget> widget; + baseWin->GetMainWidget(getter_AddRefs(widget)); + if (widget && widget->SizeMode() == nsSizeMode_Fullscreen) { + return true; + } + } + } + return false; +} +#endif + +/* + * Just do the window-making part of CreateTopLevelWindow + */ +nsresult +nsAppShellService::JustCreateTopWindow(nsIXULWindow *aParent, + nsIURI *aUrl, + uint32_t aChromeMask, + int32_t aInitialWidth, + int32_t aInitialHeight, + bool aIsHiddenWindow, + nsITabParent *aOpeningTab, + mozIDOMWindowProxy *aOpenerWindow, + nsWebShellWindow **aResult) +{ + *aResult = nullptr; + NS_ENSURE_STATE(!mXPCOMWillShutDown); + + nsCOMPtr<nsIXULWindow> parent; + if (aChromeMask & nsIWebBrowserChrome::CHROME_DEPENDENT) + parent = aParent; + + RefPtr<nsWebShellWindow> window = new nsWebShellWindow(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 (window && CheckForFullscreenWindow()) + window->IgnoreXULSizeMode(true); +#endif + + nsWidgetInitData widgetInitData; + + if (aIsHiddenWindow) + widgetInitData.mWindowType = eWindowType_invisible; + else + widgetInitData.mWindowType = aChromeMask & nsIWebBrowserChrome::CHROME_OPENAS_DIALOG ? + eWindowType_dialog : eWindowType_toplevel; + + if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_POPUP) + widgetInitData.mWindowType = eWindowType_popup; + + if (aChromeMask & nsIWebBrowserChrome::CHROME_MAC_SUPPRESS_ANIMATION) + widgetInitData.mIsAnimationSuppressed = true; + +#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 && parent != mHiddenPrivateWindow) && + ((aChromeMask & sheetMask) == sheetMask)) { + widgetInitData.mWindowType = eWindowType_sheet; + } +#endif + +#if defined(XP_WIN) + if (widgetInitData.mWindowType == eWindowType_toplevel || + widgetInitData.mWindowType == eWindowType_dialog) + widgetInitData.clipChildren = true; +#endif + + // note default chrome overrides other OS chrome settings, but + // not internal chrome + if (aChromeMask & nsIWebBrowserChrome::CHROME_DEFAULT) + widgetInitData.mBorderStyle = eBorderStyle_default; + else if ((aChromeMask & nsIWebBrowserChrome::CHROME_ALL) == nsIWebBrowserChrome::CHROME_ALL) + widgetInitData.mBorderStyle = eBorderStyle_all; + else { + widgetInitData.mBorderStyle = eBorderStyle_none; // assumes none == 0x00 + if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_BORDERS) + widgetInitData.mBorderStyle = static_cast<enum nsBorderStyle>(widgetInitData.mBorderStyle | eBorderStyle_border); + if (aChromeMask & nsIWebBrowserChrome::CHROME_TITLEBAR) + widgetInitData.mBorderStyle = static_cast<enum nsBorderStyle>(widgetInitData.mBorderStyle | eBorderStyle_title); + if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_CLOSE) + widgetInitData.mBorderStyle = static_cast<enum nsBorderStyle>(widgetInitData.mBorderStyle | eBorderStyle_close); + if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_RESIZE) { + widgetInitData.mBorderStyle = static_cast<enum nsBorderStyle>(widgetInitData.mBorderStyle | eBorderStyle_resizeh); + // only resizable windows get the maximize button (but not dialogs) + if (!(aChromeMask & nsIWebBrowserChrome::CHROME_OPENAS_DIALOG)) + widgetInitData.mBorderStyle = static_cast<enum nsBorderStyle>(widgetInitData.mBorderStyle | eBorderStyle_maximize); + } + // all windows (except dialogs) get minimize buttons and the system menu + if (!(aChromeMask & nsIWebBrowserChrome::CHROME_OPENAS_DIALOG)) + widgetInitData.mBorderStyle = static_cast<enum nsBorderStyle>(widgetInitData.mBorderStyle | eBorderStyle_minimize | eBorderStyle_menu); + // but anyone can explicitly ask for a minimize button + if (aChromeMask & nsIWebBrowserChrome::CHROME_WINDOW_MIN) { + widgetInitData.mBorderStyle = static_cast<enum nsBorderStyle>(widgetInitData.mBorderStyle | eBorderStyle_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; + + nsCOMPtr<nsIXULChromeRegistry> reg = + mozilla::services::GetXULChromeRegistryService(); + if (reg) { + nsAutoCString package; + package.AssignLiteral("global"); + bool isRTL = false; + reg->IsLocaleRTL(package, &isRTL); + widgetInitData.mRTL = isRTL; + } + +#ifdef MOZ_WIDGET_GONK + // B2G multi-screen support. Screen ID is for differentiating screens of + // windows, and due to the hardware limitation, it is platform-specific for + // now, which align with the value of display type defined in HWC. + widgetInitData.mScreenId = mScreenId; +#endif + + nsresult rv = window->Initialize(parent, center ? aParent : nullptr, + aUrl, aInitialWidth, aInitialHeight, + aIsHiddenWindow, aOpeningTab, + aOpenerWindow, widgetInitData); + + NS_ENSURE_SUCCESS(rv, rv); + + // Enforce the Private Browsing autoStart pref first. + bool isPrivateBrowsingWindow = + Preferences::GetBool("browser.privatebrowsing.autostart"); + bool isUsingRemoteTabs = mozilla::BrowserTabsRemoteAutostart(); + + if (aChromeMask & nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW) { + // Caller requested a private window + isPrivateBrowsingWindow = true; + } + if (aChromeMask & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW) { + isUsingRemoteTabs = true; + } + + 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 (parentContext) { + isUsingRemoteTabs = parentContext->UseRemoteTabs(); + } + + nsCOMPtr<mozIDOMWindowProxy> newDomWin = + do_GetInterface(NS_ISUPPORTS_CAST(nsIBaseWindow*, window)); + nsCOMPtr<nsIWebNavigation> newWebNav = do_GetInterface(newDomWin); + nsCOMPtr<nsILoadContext> thisContext = do_GetInterface(newWebNav); + if (thisContext) { + thisContext->SetPrivateBrowsing(isPrivateBrowsingWindow); + thisContext->SetRemoteTabs(isUsingRemoteTabs); + } + + window.forget(aResult); + if (parent) + parent->AddChildWindow(*aResult); + + if (center) + rv = (*aResult)->Center(parent, parent ? false : true, false); + + return rv; +} + +NS_IMETHODIMP +nsAppShellService::GetHiddenWindow(nsIXULWindow **aWindow) +{ + NS_ENSURE_ARG_POINTER(aWindow); + + *aWindow = mHiddenWindow; + NS_IF_ADDREF(*aWindow); + return *aWindow ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsAppShellService::GetHiddenDOMWindow(mozIDOMWindowProxy **aWindow) +{ + 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::GetHiddenPrivateWindow(nsIXULWindow **aWindow) +{ + NS_ENSURE_ARG_POINTER(aWindow); + + EnsurePrivateHiddenWindow(); + + *aWindow = mHiddenPrivateWindow; + NS_IF_ADDREF(*aWindow); + return *aWindow ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsAppShellService::GetHiddenPrivateDOMWindow(mozIDOMWindowProxy **aWindow) +{ + EnsurePrivateHiddenWindow(); + + nsresult rv; + nsCOMPtr<nsIDocShell> docShell; + NS_ENSURE_TRUE(mHiddenPrivateWindow, NS_ERROR_FAILURE); + + rv = mHiddenPrivateWindow->GetDocShell(getter_AddRefs(docShell)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + + nsCOMPtr<nsPIDOMWindowOuter> hiddenPrivateDOMWindow(docShell->GetWindow()); + hiddenPrivateDOMWindow.forget(aWindow); + return *aWindow ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsAppShellService::GetHasHiddenPrivateWindow(bool* aHasPrivateWindow) +{ + NS_ENSURE_ARG_POINTER(aHasPrivateWindow); + + *aHasPrivateWindow = !!mHiddenPrivateWindow; + 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(nsIXULWindow* 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); + domWindow->SetInitialPrincipalToSubject(); + + // 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); + nsXULWindow* xulWindow = static_cast<nsXULWindow*>(aWindow); + xulWindow->WasRegistered(); + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsAppShellService::UnregisterTopLevelWindow(nsIXULWindow* 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; + } + if (aWindow == mHiddenPrivateWindow) { + // 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(); + } + if (mHiddenPrivateWindow) { + mHiddenPrivateWindow->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 000000000..feb7b9947 --- /dev/null +++ b/xpfe/appshell/nsAppShellService.h @@ -0,0 +1,58 @@ +/* -*- 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 "nsWebShellWindow.h" +#include "nsStringFwd.h" +#include "nsAutoPtr.h" +#include "nsITabParent.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(); + + nsresult CreateHiddenWindowHelper(bool aIsPrivate); + void EnsurePrivateHiddenWindow(); + + nsresult JustCreateTopWindow(nsIXULWindow *aParent, + nsIURI *aUrl, + uint32_t aChromeMask, + int32_t aInitialWidth, int32_t aInitialHeight, + bool aIsHiddenWindow, + nsITabParent *aOpeningTab, + mozIDOMWindowProxy *aOpenerWindow, + nsWebShellWindow **aResult); + uint32_t CalculateWindowZLevel(nsIXULWindow *aParent, uint32_t aChromeMask); + + RefPtr<nsWebShellWindow> mHiddenWindow; + RefPtr<nsWebShellWindow> mHiddenPrivateWindow; + 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 000000000..dc5fc120a --- /dev/null +++ b/xpfe/appshell/nsAppShellWindowEnumerator.cpp @@ -0,0 +1,495 @@ +/* -*- 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 "nsIContentViewer.h" +#include "nsIDocShell.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIDOMElement.h" +#include "nsIDOMWindow.h" +#include "nsIFactory.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIXULWindow.h" + +#include "nsWindowMediator.h" + +// +// static helper functions +// + +static nsCOMPtr<nsIDOMNode> GetDOMNodeFromDocShell(nsIDocShell *aShell); +static void GetAttribute(nsIXULWindow *inWindow, const nsAString &inAttribute, + nsAString &outValue); +static void GetWindowType(nsIXULWindow* inWindow, nsString &outType); + +nsCOMPtr<nsIDOMNode> GetDOMNodeFromDocShell(nsIDocShell *aShell) +{ + nsCOMPtr<nsIDOMNode> node; + + nsCOMPtr<nsIContentViewer> cv; + aShell->GetContentViewer(getter_AddRefs(cv)); + if (cv) { + nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(cv->GetDocument())); + if (domdoc) { + nsCOMPtr<nsIDOMElement> element; + domdoc->GetDocumentElement(getter_AddRefs(element)); + if (element) + node = element; + } + } + + return node; +} + +// generic "retrieve the value of a XUL attribute" function +void GetAttribute(nsIXULWindow *inWindow, const nsAString &inAttribute, + nsAString &outValue) +{ + nsCOMPtr<nsIDocShell> shell; + if (inWindow && NS_SUCCEEDED(inWindow->GetDocShell(getter_AddRefs(shell)))) { + nsCOMPtr<nsIDOMNode> node(GetDOMNodeFromDocShell(shell)); + if (node) { + nsCOMPtr<nsIDOMElement> webshellElement(do_QueryInterface(node)); + 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(nsIXULWindow* aWindow, nsString &outType) +{ + GetAttribute(aWindow, NS_LITERAL_STRING("windowtype"), outType); +} + +// +// nsWindowInfo +// + +nsWindowInfo::nsWindowInfo(nsIXULWindow* inWindow, int32_t inTimeStamp) : + mWindow(inWindow),mTimeStamp(inTimeStamp),mZLevel(nsIXULWindow::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 +// + +NS_IMPL_ISUPPORTS(nsAppShellWindowEnumerator, nsISimpleEnumerator) + +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_OK; +} + +// +// nsASXULWindowEnumerator +// + +nsASXULWindowEnumerator::nsASXULWindowEnumerator( + const char16_t* aTypeString, + nsWindowMediator& aMediator) : + nsAppShellWindowEnumerator(aTypeString, aMediator) +{ +} + +nsASXULWindowEnumerator::~nsASXULWindowEnumerator() +{ +} + +NS_IMETHODIMP nsASXULWindowEnumerator::GetNext(nsISupports **retval) +{ + if (!retval) + return NS_ERROR_INVALID_ARG; + + *retval = nullptr; + if (mCurrentPosition) { + CallQueryInterface(mCurrentPosition->mWindow, retval); + mCurrentPosition = FindNext(); + } + return NS_OK; +} + +// +// 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 nsXULWindowEarlyToLateEnumerator::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; +} + +// +// nsASXULWindowEarlyToLateEnumerator +// + +nsASXULWindowEarlyToLateEnumerator::nsASXULWindowEarlyToLateEnumerator( + const char16_t *aTypeString, + nsWindowMediator &aMediator) : + nsASXULWindowEnumerator(aTypeString, aMediator) +{ + mCurrentPosition = aMediator.mOldestWindow; + AdjustInitialPosition(); +} + +nsASXULWindowEarlyToLateEnumerator::~nsASXULWindowEarlyToLateEnumerator() +{ +} + +nsWindowInfo *nsASXULWindowEarlyToLateEnumerator::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; +} + +// +// nsASDOMWindowFrontToBackEnumerator +// + +nsASDOMWindowFrontToBackEnumerator::nsASDOMWindowFrontToBackEnumerator( + const char16_t *aTypeString, + nsWindowMediator &aMediator) : + nsASDOMWindowEnumerator(aTypeString, aMediator) +{ + mCurrentPosition = aMediator.mTopmostWindow; + AdjustInitialPosition(); +} + +nsASDOMWindowFrontToBackEnumerator::~nsASDOMWindowFrontToBackEnumerator() +{ +} + +nsWindowInfo *nsASDOMWindowFrontToBackEnumerator::FindNext() +{ + nsWindowInfo *info, + *listEnd; + bool allWindows = mType.IsEmpty(); + + // see nsXULWindowEarlyToLateEnumerator::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; +} + +// +// nsASXULWindowFrontToBackEnumerator +// + +nsASXULWindowFrontToBackEnumerator::nsASXULWindowFrontToBackEnumerator( + const char16_t *aTypeString, + nsWindowMediator &aMediator) : + nsASXULWindowEnumerator(aTypeString, aMediator) +{ + mCurrentPosition = aMediator.mTopmostWindow; + AdjustInitialPosition(); +} + +nsASXULWindowFrontToBackEnumerator::~nsASXULWindowFrontToBackEnumerator() +{ +} + +nsWindowInfo *nsASXULWindowFrontToBackEnumerator::FindNext() +{ + nsWindowInfo *info, + *listEnd; + bool allWindows = mType.IsEmpty(); + + // see nsXULWindowEarlyToLateEnumerator::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; +} + +// +// nsASDOMWindowBackToFrontEnumerator +// + +nsASDOMWindowBackToFrontEnumerator::nsASDOMWindowBackToFrontEnumerator( + const char16_t *aTypeString, + nsWindowMediator &aMediator) : + nsASDOMWindowEnumerator(aTypeString, aMediator) +{ + mCurrentPosition = aMediator.mTopmostWindow ? + aMediator.mTopmostWindow->mHigher : nullptr; + AdjustInitialPosition(); +} + +nsASDOMWindowBackToFrontEnumerator::~nsASDOMWindowBackToFrontEnumerator() +{ +} + +nsWindowInfo *nsASDOMWindowBackToFrontEnumerator::FindNext() +{ + nsWindowInfo *info, + *listEnd; + bool allWindows = mType.IsEmpty(); + + // see nsXULWindowEarlyToLateEnumerator::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; +} + +// +// nsASXULWindowBackToFrontEnumerator +// + +nsASXULWindowBackToFrontEnumerator::nsASXULWindowBackToFrontEnumerator( + const char16_t *aTypeString, + nsWindowMediator &aMediator) : + nsASXULWindowEnumerator(aTypeString, aMediator) +{ + mCurrentPosition = aMediator.mTopmostWindow ? + aMediator.mTopmostWindow->mHigher : nullptr; + AdjustInitialPosition(); +} + +nsASXULWindowBackToFrontEnumerator::~nsASXULWindowBackToFrontEnumerator() +{ +} + +nsWindowInfo *nsASXULWindowBackToFrontEnumerator::FindNext() +{ + nsWindowInfo *info, + *listEnd; + bool allWindows = mType.IsEmpty(); + + // see nsXULWindowEarlyToLateEnumerator::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 000000000..f9a8a2d52 --- /dev/null +++ b/xpfe/appshell/nsAppShellWindowEnumerator.h @@ -0,0 +1,166 @@ +/* -*- 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 "nsISimpleEnumerator.h" +#include "nsIXULWindow.h" + +class nsWindowMediator; + +// +// nsWindowInfo +// + +struct nsWindowInfo +{ + nsWindowInfo(nsIXULWindow* inWindow, int32_t inTimeStamp); + ~nsWindowInfo(); + + nsCOMPtr<nsIXULWindow> 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 nsISimpleEnumerator { + +friend class nsWindowMediator; + +public: + nsAppShellWindowEnumerator(const char16_t* aTypeString, + nsWindowMediator& inMediator); + NS_IMETHOD GetNext(nsISupports **retval) override = 0; + NS_IMETHOD HasMoreElements(bool *retval) override; + + NS_DECL_ISUPPORTS + +protected: + + virtual ~nsAppShellWindowEnumerator(); + + 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); +}; + +class nsASXULWindowEnumerator : public nsAppShellWindowEnumerator { + +public: + nsASXULWindowEnumerator(const char16_t* aTypeString, + nsWindowMediator& inMediator); + virtual ~nsASXULWindowEnumerator(); + NS_IMETHOD GetNext(nsISupports **retval); +}; + +// +// concrete enumerators +// + +class nsASDOMWindowEarlyToLateEnumerator : public nsASDOMWindowEnumerator { + +public: + nsASDOMWindowEarlyToLateEnumerator(const char16_t* aTypeString, + nsWindowMediator& inMediator); + + virtual ~nsASDOMWindowEarlyToLateEnumerator(); + +protected: + virtual nsWindowInfo *FindNext(); +}; + +class nsASXULWindowEarlyToLateEnumerator : public nsASXULWindowEnumerator { + +public: + nsASXULWindowEarlyToLateEnumerator(const char16_t* aTypeString, + nsWindowMediator& inMediator); + + virtual ~nsASXULWindowEarlyToLateEnumerator(); + +protected: + virtual nsWindowInfo *FindNext(); +}; + +class nsASDOMWindowFrontToBackEnumerator : public nsASDOMWindowEnumerator { + +public: + nsASDOMWindowFrontToBackEnumerator(const char16_t* aTypeString, + nsWindowMediator& inMediator); + + virtual ~nsASDOMWindowFrontToBackEnumerator(); + +protected: + virtual nsWindowInfo *FindNext(); +}; + +class nsASXULWindowFrontToBackEnumerator : public nsASXULWindowEnumerator { + +public: + nsASXULWindowFrontToBackEnumerator(const char16_t* aTypeString, + nsWindowMediator& inMediator); + + virtual ~nsASXULWindowFrontToBackEnumerator(); + +protected: + virtual nsWindowInfo *FindNext(); +}; + +class nsASDOMWindowBackToFrontEnumerator : public nsASDOMWindowEnumerator { + +public: + nsASDOMWindowBackToFrontEnumerator(const char16_t* aTypeString, + nsWindowMediator& inMediator); + + virtual ~nsASDOMWindowBackToFrontEnumerator(); + +protected: + virtual nsWindowInfo *FindNext(); +}; + +class nsASXULWindowBackToFrontEnumerator : public nsASXULWindowEnumerator { + +public: + nsASXULWindowBackToFrontEnumerator(const char16_t* aTypeString, + nsWindowMediator& inMediator); + + virtual ~nsASXULWindowBackToFrontEnumerator(); + +protected: + virtual nsWindowInfo *FindNext(); +}; + +#endif diff --git a/xpfe/appshell/nsChromeTreeOwner.cpp b/xpfe/appshell/nsChromeTreeOwner.cpp new file mode 100644 index 000000000..63fa86373 --- /dev/null +++ b/xpfe/appshell/nsChromeTreeOwner.cpp @@ -0,0 +1,561 @@ +/* -*- 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 "nsXULWindow.h" + +// Helper Classes +#include "nsString.h" +#include "nsIEmbeddingSiteWindow.h" +#include "nsIServiceManager.h" +#include "nsIDocShellTreeItem.h" + +// Interfaces needed to include +#include "nsIPrompt.h" +#include "nsIAuthPrompt.h" +#include "nsIBrowserDOMWindow.h" +#include "nsIWebProgress.h" +#include "nsIWidget.h" +#include "nsIWindowMediator.h" +#include "nsIDOMChromeWindow.h" +#include "nsIDOMNode.h" +#include "nsIDOMElement.h" +#include "nsIDOMNodeList.h" +#include "nsIDOMXULElement.h" +#include "nsIXULBrowserWindow.h" +#include "mozilla/dom/Element.h" + +using namespace mozilla; + +//***************************************************************************** +// nsChromeTreeOwner string literals +//***************************************************************************** + +struct nsChromeTreeOwnerLiterals +{ + const nsLiteralString kPersist; + const nsLiteralString kScreenX; + const nsLiteralString kScreenY; + const nsLiteralString kWidth; + const nsLiteralString kHeight; + const nsLiteralString kSizemode; + const nsLiteralString kSpace; + + nsChromeTreeOwnerLiterals() + : NS_LITERAL_STRING_INIT(kPersist,"persist") + , NS_LITERAL_STRING_INIT(kScreenX,"screenX") + , NS_LITERAL_STRING_INIT(kScreenY,"screenY") + , NS_LITERAL_STRING_INIT(kWidth,"width") + , NS_LITERAL_STRING_INIT(kHeight,"height") + , NS_LITERAL_STRING_INIT(kSizemode,"sizemode") + , NS_LITERAL_STRING_INIT(kSpace," ") + {} +}; + +static nsChromeTreeOwnerLiterals *gLiterals; + +nsresult +nsChromeTreeOwner::InitGlobals() +{ + NS_ASSERTION(gLiterals == nullptr, "already initialized"); + gLiterals = new nsChromeTreeOwnerLiterals(); + return NS_OK; +} + +void +nsChromeTreeOwner::FreeGlobals() +{ + delete gLiterals; + gLiterals = nullptr; +} + +//***************************************************************************** +//*** nsChromeTreeOwner: Object Management +//***************************************************************************** + +nsChromeTreeOwner::nsChromeTreeOwner() : mXULWindow(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(mXULWindow); + return mXULWindow->GetInterface(aIID, aSink); + } + if(aIID.Equals(NS_GET_IID(nsIAuthPrompt))) { + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetInterface(aIID, aSink); + } + if(aIID.Equals(NS_GET_IID(nsIWebBrowserChrome))) { + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetInterface(aIID, aSink); + } + if (aIID.Equals(NS_GET_IID(nsIEmbeddingSiteWindow))) { + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetInterface(aIID, aSink); + } + if (aIID.Equals(NS_GET_IID(nsIXULWindow))) { + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->QueryInterface(aIID, aSink); + } + + return QueryInterface(aIID, aSink); +} + +//***************************************************************************** +// nsChromeTreeOwner::nsIDocShellTreeOwner +//***************************************************************************** + +NS_IMETHODIMP +nsChromeTreeOwner::ContentShellAdded(nsIDocShellTreeItem* aContentShell, + bool aPrimary, bool aTargetable, + const nsAString& aID) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->ContentShellAdded(aContentShell, aPrimary, aTargetable, + aID); +} + +NS_IMETHODIMP +nsChromeTreeOwner::ContentShellRemoved(nsIDocShellTreeItem* aContentShell) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->ContentShellRemoved(aContentShell); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetPrimaryContentShell(nsIDocShellTreeItem** aShell) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetPrimaryContentShell(aShell); +} + +NS_IMETHODIMP +nsChromeTreeOwner::TabParentAdded(nsITabParent* aTab, bool aPrimary) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->TabParentAdded(aTab, aPrimary); +} + +NS_IMETHODIMP +nsChromeTreeOwner::TabParentRemoved(nsITabParent* aTab) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->TabParentRemoved(aTab); +} + +NS_IMETHODIMP +nsChromeTreeOwner::GetPrimaryTabParent(nsITabParent** aTab) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetPrimaryTabParent(aTab); +} + +NS_IMETHODIMP +nsChromeTreeOwner::GetPrimaryContentSize(int32_t* aWidth, + int32_t* aHeight) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetPrimaryContentSize(aWidth, aHeight); +} + +NS_IMETHODIMP +nsChromeTreeOwner::SetPrimaryContentSize(int32_t aWidth, + int32_t aHeight) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetPrimaryContentSize(aWidth, aHeight); +} + +NS_IMETHODIMP +nsChromeTreeOwner::GetRootShellSize(int32_t* aWidth, + int32_t* aHeight) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetRootShellSize(aWidth, aHeight); +} + +NS_IMETHODIMP +nsChromeTreeOwner::SetRootShellSize(int32_t aWidth, + int32_t aHeight) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetRootShellSize(aWidth, aHeight); +} + +NS_IMETHODIMP nsChromeTreeOwner::SizeShellTo(nsIDocShellTreeItem* aShellItem, + int32_t aCX, int32_t aCY) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SizeShellTo(aShellItem, aCX, aCY); +} + +NS_IMETHODIMP +nsChromeTreeOwner::SetPersistence(bool aPersistPosition, + bool aPersistSize, + bool aPersistSizeMode) +{ + NS_ENSURE_STATE(mXULWindow); + nsCOMPtr<dom::Element> docShellElement = mXULWindow->GetWindowDOMElement(); + if (!docShellElement) + return NS_ERROR_FAILURE; + + nsAutoString persistString; + docShellElement->GetAttribute(gLiterals->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(gLiterals->kSpace + aString); \ + saveString = true; \ + } + FIND_PERSIST_STRING(gLiterals->kScreenX, aPersistPosition); + FIND_PERSIST_STRING(gLiterals->kScreenY, aPersistPosition); + FIND_PERSIST_STRING(gLiterals->kWidth, aPersistSize); + FIND_PERSIST_STRING(gLiterals->kHeight, aPersistSize); + FIND_PERSIST_STRING(gLiterals->kSizemode, aPersistSizeMode); + + ErrorResult rv; + if (saveString) { + docShellElement->SetAttribute(gLiterals->kPersist, persistString, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsChromeTreeOwner::GetPersistence(bool* aPersistPosition, + bool* aPersistSize, + bool* aPersistSizeMode) +{ + NS_ENSURE_STATE(mXULWindow); + nsCOMPtr<dom::Element> docShellElement = mXULWindow->GetWindowDOMElement(); + if (!docShellElement) + return NS_ERROR_FAILURE; + + nsAutoString persistString; + docShellElement->GetAttribute(gLiterals->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(gLiterals->kScreenX) > kNotFound || + persistString.Find(gLiterals->kScreenY) > kNotFound; + if (aPersistSize) + *aPersistSize = persistString.Find(gLiterals->kWidth) > kNotFound || + persistString.Find(gLiterals->kHeight) > kNotFound; + if (aPersistSizeMode) + *aPersistSizeMode = persistString.Find(gLiterals->kSizemode) > kNotFound; + + return NS_OK; +} + +NS_IMETHODIMP +nsChromeTreeOwner::GetTargetableShellCount(uint32_t* aResult) +{ + *aResult = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsChromeTreeOwner::GetHasPrimaryContent(bool* aResult) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->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::Create() +{ + NS_ASSERTION(false, "You can't call this"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP nsChromeTreeOwner::Destroy() +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->Destroy(); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetUnscaledDevicePixelsPerCSSPixel(double *aScale) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetUnscaledDevicePixelsPerCSSPixel(aScale); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetDevicePixelsPerDesktopPixel(double *aScale) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetDevicePixelsPerDesktopPixel(aScale); +} + +NS_IMETHODIMP nsChromeTreeOwner::SetPositionDesktopPix(int32_t x, int32_t y) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetPositionDesktopPix(x, y); +} + +NS_IMETHODIMP nsChromeTreeOwner::SetPosition(int32_t x, int32_t y) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetPosition(x, y); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetPosition(int32_t* x, int32_t* y) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetPosition(x, y); +} + +NS_IMETHODIMP nsChromeTreeOwner::SetSize(int32_t cx, int32_t cy, bool fRepaint) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetSize(cx, cy, fRepaint); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetSize(int32_t* cx, int32_t* cy) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->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(mXULWindow); + return mXULWindow->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(mXULWindow); + return mXULWindow->GetPositionAndSize(x, y, cx, cy); +} + +NS_IMETHODIMP nsChromeTreeOwner::Repaint(bool aForce) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->Repaint(aForce); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetParentWidget(nsIWidget** aParentWidget) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->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(mXULWindow); + return mXULWindow->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(mXULWindow); + return mXULWindow->GetNativeHandle(aNativeHandle); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetVisibility(bool* aVisibility) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetVisibility(aVisibility); +} + +NS_IMETHODIMP nsChromeTreeOwner::SetVisibility(bool aVisibility) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetVisibility(aVisibility); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetEnabled(bool *aEnabled) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetEnabled(aEnabled); +} + +NS_IMETHODIMP nsChromeTreeOwner::SetEnabled(bool aEnable) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetEnabled(aEnable); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetMainWidget(nsIWidget** aMainWidget) +{ + NS_ENSURE_ARG_POINTER(aMainWidget); + NS_ENSURE_STATE(mXULWindow); + + *aMainWidget = mXULWindow->mWindow; + NS_IF_ADDREF(*aMainWidget); + + return NS_OK; +} + +NS_IMETHODIMP nsChromeTreeOwner::SetFocus() +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetFocus(); +} + +NS_IMETHODIMP nsChromeTreeOwner::GetTitle(char16_t** aTitle) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetTitle(aTitle); +} + +NS_IMETHODIMP nsChromeTreeOwner::SetTitle(const char16_t* aTitle) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->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) +{ + bool itsForYou = true; + + if (aWebProgress) { + NS_ENSURE_STATE(mXULWindow); + nsCOMPtr<mozIDOMWindowProxy> progressWin; + aWebProgress->GetDOMWindow(getter_AddRefs(progressWin)); + + nsCOMPtr<nsIDocShell> docshell; + mXULWindow->GetDocShell(getter_AddRefs(docshell)); + // XXXkhuey this is totally wrong, bug 1223303. + nsCOMPtr<mozIDOMWindowProxy> ourWin(do_QueryInterface(docshell)); + + if (ourWin != progressWin) + itsForYou = false; + } + + // If loading a new root .xul document, then redo chrome. + if (itsForYou) { + NS_ENSURE_STATE(mXULWindow); + mXULWindow->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 state) +{ + return NS_OK; +} + +//***************************************************************************** +// nsChromeTreeOwner: Helpers +//***************************************************************************** + +//***************************************************************************** +// nsChromeTreeOwner: Accessors +//***************************************************************************** + +void nsChromeTreeOwner::XULWindow(nsXULWindow* aXULWindow) +{ + mXULWindow = aXULWindow; +} + +nsXULWindow* nsChromeTreeOwner::XULWindow() +{ + return mXULWindow; +} diff --git a/xpfe/appshell/nsChromeTreeOwner.h b/xpfe/appshell/nsChromeTreeOwner.h new file mode 100644 index 000000000..77b0dea89 --- /dev/null +++ b/xpfe/appshell/nsChromeTreeOwner.h @@ -0,0 +1,53 @@ +/* -*- 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" + +class nsXULWindow; + +class nsChromeTreeOwner : public nsIDocShellTreeOwner, + public nsIBaseWindow, + public nsIInterfaceRequestor, + public nsIWebProgressListener, + public nsSupportsWeakReference +{ +friend class nsXULWindow; + +public: + NS_DECL_ISUPPORTS + + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIBASEWINDOW + NS_DECL_NSIDOCSHELLTREEOWNER + NS_DECL_NSIWEBPROGRESSLISTENER + + static nsresult InitGlobals(); + static void FreeGlobals(); + +protected: + nsChromeTreeOwner(); + virtual ~nsChromeTreeOwner(); + + void XULWindow(nsXULWindow* aXULWindow); + nsXULWindow* XULWindow(); + +protected: + nsXULWindow* mXULWindow; +}; + +#endif /* nsChromeTreeOwner_h__ */ diff --git a/xpfe/appshell/nsContentTreeOwner.cpp b/xpfe/appshell/nsContentTreeOwner.cpp new file mode 100644 index 000000000..b39b7610f --- /dev/null +++ b/xpfe/appshell/nsContentTreeOwner.cpp @@ -0,0 +1,1131 @@ +/* -*- Mode: C++; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=2 sw=2 et tw=79: + * + * 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 "nsXULWindow.h" + +// Helper Classes +#include "nsIServiceManager.h" +#include "nsAutoPtr.h" + +// Interfaces needed to be included +#include "nsIDOMNode.h" +#include "nsIDOMElement.h" +#include "nsIDOMNodeList.h" +#include "nsIDOMWindow.h" +#include "nsIDOMChromeWindow.h" +#include "nsIBrowserDOMWindow.h" +#include "nsIDOMXULElement.h" +#include "nsIEmbeddingSiteWindow.h" +#include "nsIPrompt.h" +#include "nsIAuthPrompt.h" +#include "nsIWindowMediator.h" +#include "nsIXULBrowserWindow.h" +#include "nsIPrincipal.h" +#include "nsIURIFixup.h" +#include "nsCDefaultURIFixup.h" +#include "nsIWebNavigation.h" +#include "nsDocShellCID.h" +#include "nsIExternalURLHandlerService.h" +#include "nsIMIMEInfo.h" +#include "nsIWidget.h" +#include "nsWindowWatcher.h" +#include "mozilla/BrowserElementParent.h" + +#include "nsIDOMDocument.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIURI.h" +#include "nsIDocument.h" +#if defined(XP_MACOSX) +#include "nsThreadUtils.h" +#endif + +#include "mozilla/Preferences.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ScriptSettings.h" + +using namespace mozilla; + +//***************************************************************************** +//*** nsSiteWindow declaration +//***************************************************************************** + +class nsSiteWindow : public nsIEmbeddingSiteWindow +{ + // nsSiteWindow shares a lifetime with nsContentTreeOwner, and proxies it's + // AddRef and Release calls to said object. + // When nsContentTreeOwner is destroyed, nsSiteWindow will be destroyed as well. + // nsContentTreeOwner is a friend class of nsSiteWindow such that it can call + // nsSiteWindow's destructor, which is private, as public destructors + // on reference counted classes are generally unsafe. + friend class nsContentTreeOwner; + +public: + explicit nsSiteWindow(nsContentTreeOwner *aAggregator); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIEMBEDDINGSITEWINDOW + +private: + virtual ~nsSiteWindow(); + nsContentTreeOwner *mAggregator; +}; + +//***************************************************************************** +//*** nsContentTreeOwner: Object Management +//***************************************************************************** + +nsContentTreeOwner::nsContentTreeOwner(bool fPrimary) : mXULWindow(nullptr), + mPrimary(fPrimary), mContentTitleSetting(false) +{ + // note if this fails, QI on nsIEmbeddingSiteWindow(2) will simply fail + mSiteWindow = new nsSiteWindow(this); +} + +nsContentTreeOwner::~nsContentTreeOwner() +{ + delete mSiteWindow; +} + +//***************************************************************************** +// 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(nsIWebBrowserChrome2) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome3) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsIWindowProvider) + // NOTE: This is using aggregation because there are some properties and + // method on nsIBaseWindow (which we implement) and on + // nsIEmbeddingSiteWindow (which we also implement) that have the same name. + // And it just so happens that we want different behavior for these methods + // and properties depending on the interface through which they're called + // (SetFocus() is a good example here). If it were not for that, we could + // ditch the aggregation and just deal with not being able to use NS_DECL_* + // macros for this stuff.... + NS_INTERFACE_MAP_ENTRY_AGGREGATED(nsIEmbeddingSiteWindow, mSiteWindow) +NS_INTERFACE_MAP_END + +//***************************************************************************** +// nsContentTreeOwner::nsIInterfaceRequestor +//***************************************************************************** + +NS_IMETHODIMP nsContentTreeOwner::GetInterface(const nsIID& aIID, void** aSink) +{ + NS_ENSURE_ARG_POINTER(aSink); + *aSink = 0; + + if(aIID.Equals(NS_GET_IID(nsIPrompt))) { + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetInterface(aIID, aSink); + } + if(aIID.Equals(NS_GET_IID(nsIAuthPrompt))) { + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetInterface(aIID, aSink); + } + if (aIID.Equals(NS_GET_IID(nsIDocShellTreeItem))) { + NS_ENSURE_STATE(mXULWindow); + nsCOMPtr<nsIDocShell> shell; + mXULWindow->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(mXULWindow); + nsCOMPtr<nsIDocShellTreeItem> shell; + mXULWindow->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(nsIXULWindow))) { + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->QueryInterface(aIID, aSink); + } + + return QueryInterface(aIID, aSink); +} + +//***************************************************************************** +// nsContentTreeOwner::nsIDocShellTreeOwner +//***************************************************************************** + +NS_IMETHODIMP +nsContentTreeOwner::ContentShellAdded(nsIDocShellTreeItem* aContentShell, + bool aPrimary, bool aTargetable, + const nsAString& aID) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->ContentShellAdded(aContentShell, aPrimary, aTargetable, + aID); +} + +NS_IMETHODIMP +nsContentTreeOwner::ContentShellRemoved(nsIDocShellTreeItem* aContentShell) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->ContentShellRemoved(aContentShell); +} + +NS_IMETHODIMP +nsContentTreeOwner::GetPrimaryContentShell(nsIDocShellTreeItem** aShell) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetPrimaryContentShell(aShell); +} + +NS_IMETHODIMP +nsContentTreeOwner::TabParentAdded(nsITabParent* aTab, bool aPrimary) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->TabParentAdded(aTab, aPrimary); +} + +NS_IMETHODIMP +nsContentTreeOwner::TabParentRemoved(nsITabParent* aTab) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->TabParentRemoved(aTab); +} + +NS_IMETHODIMP +nsContentTreeOwner::GetPrimaryTabParent(nsITabParent** aTab) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetPrimaryTabParent(aTab); +} + +NS_IMETHODIMP +nsContentTreeOwner::GetPrimaryContentSize(int32_t* aWidth, + int32_t* aHeight) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetPrimaryContentSize(aWidth, aHeight); +} + +NS_IMETHODIMP +nsContentTreeOwner::SetPrimaryContentSize(int32_t aWidth, + int32_t aHeight) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetPrimaryContentSize(aWidth, aHeight); +} + +NS_IMETHODIMP +nsContentTreeOwner::GetRootShellSize(int32_t* aWidth, + int32_t* aHeight) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetRootShellSize(aWidth, aHeight); +} + +NS_IMETHODIMP +nsContentTreeOwner::SetRootShellSize(int32_t aWidth, + int32_t aHeight) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetRootShellSize(aWidth, aHeight); +} + +NS_IMETHODIMP nsContentTreeOwner::SizeShellTo(nsIDocShellTreeItem* aShellItem, + int32_t aCX, int32_t aCY) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SizeShellTo(aShellItem, aCX, aCY); +} + +NS_IMETHODIMP +nsContentTreeOwner::SetPersistence(bool aPersistPosition, + bool aPersistSize, + bool aPersistSizeMode) +{ + NS_ENSURE_STATE(mXULWindow); + nsCOMPtr<dom::Element> docShellElement = mXULWindow->GetWindowDOMElement(); + if (!docShellElement) + return NS_ERROR_FAILURE; + + nsAutoString persistString; + docShellElement->GetAttribute(NS_LITERAL_STRING("persist"), persistString); + + bool saveString = false; + int32_t index; + + // Set X + index = persistString.Find("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("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("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("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("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(NS_LITERAL_STRING("persist"), persistString, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsContentTreeOwner::GetPersistence(bool* aPersistPosition, + bool* aPersistSize, + bool* aPersistSizeMode) +{ + NS_ENSURE_STATE(mXULWindow); + nsCOMPtr<dom::Element> docShellElement = mXULWindow->GetWindowDOMElement(); + if (!docShellElement) + return NS_ERROR_FAILURE; + + nsAutoString persistString; + docShellElement->GetAttribute(NS_LITERAL_STRING("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("screenX") >= 0 || persistString.Find("screenY") >= 0 ? true : false; + if (aPersistSize) + *aPersistSize = persistString.Find("width") >= 0 || persistString.Find("height") >= 0 ? true : false; + if (aPersistSizeMode) + *aPersistSizeMode = persistString.Find("sizemode") >= 0 ? true : false; + + return NS_OK; +} + +NS_IMETHODIMP +nsContentTreeOwner::GetTargetableShellCount(uint32_t* aResult) +{ + NS_ENSURE_STATE(mXULWindow); + *aResult = mXULWindow->mTargetableShells.Count(); + return NS_OK; +} + +NS_IMETHODIMP +nsContentTreeOwner::GetHasPrimaryContent(bool* aResult) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetHasPrimaryContent(aResult); +} + +//***************************************************************************** +// nsContentTreeOwner::nsIWebBrowserChrome3 +//***************************************************************************** + +NS_IMETHODIMP nsContentTreeOwner::OnBeforeLinkTraversal(const nsAString &originalTarget, + nsIURI *linkURI, + nsIDOMNode *linkNode, + bool isAppTab, + nsAString &_retval) +{ + NS_ENSURE_STATE(mXULWindow); + + nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow; + mXULWindow->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow)); + + if (xulBrowserWindow) + return xulBrowserWindow->OnBeforeLinkTraversal(originalTarget, linkURI, + linkNode, isAppTab, _retval); + + _retval = originalTarget; + return NS_OK; +} + +NS_IMETHODIMP nsContentTreeOwner::ShouldLoadURI(nsIDocShell *aDocShell, + nsIURI *aURI, + nsIURI *aReferrer, + bool *_retval) +{ + NS_ENSURE_STATE(mXULWindow); + + nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow; + mXULWindow->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow)); + + if (xulBrowserWindow) + return xulBrowserWindow->ShouldLoadURI(aDocShell, aURI, aReferrer, _retval); + + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP nsContentTreeOwner::ReloadInFreshProcess(nsIDocShell* aDocShell, + nsIURI* aURI, + nsIURI* aReferrer, + bool* aRetVal) +{ + NS_WARNING("Cannot reload in fresh process from a nsContentTreeOwner!"); + *aRetVal = false; + return NS_OK; +} + +//***************************************************************************** +// nsContentTreeOwner::nsIWebBrowserChrome2 +//***************************************************************************** + +NS_IMETHODIMP nsContentTreeOwner::SetStatusWithContext(uint32_t aStatusType, + const nsAString &aStatusText, + nsISupports *aStatusContext) +{ + // We only allow the status to be set from the primary content shell + if (!mPrimary && aStatusType != STATUS_LINK) + return NS_OK; + + NS_ENSURE_STATE(mXULWindow); + + nsCOMPtr<nsIXULBrowserWindow> xulBrowserWindow; + mXULWindow->GetXULBrowserWindow(getter_AddRefs(xulBrowserWindow)); + + if (xulBrowserWindow) + { + switch(aStatusType) + { + case STATUS_SCRIPT: + xulBrowserWindow->SetJSStatus(aStatusText); + break; + case STATUS_LINK: + { + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aStatusContext); + xulBrowserWindow->SetOverLink(aStatusText, element); + break; + } + } + } + + return NS_OK; +} + +//***************************************************************************** +// nsContentTreeOwner::nsIWebBrowserChrome +//***************************************************************************** + +NS_IMETHODIMP nsContentTreeOwner::SetStatus(uint32_t aStatusType, + const char16_t* aStatus) +{ + return SetStatusWithContext(aStatusType, + aStatus ? static_cast<const nsString &>(nsDependentString(aStatus)) + : EmptyString(), + nullptr); +} + +NS_IMETHODIMP nsContentTreeOwner::SetWebBrowser(nsIWebBrowser* aWebBrowser) +{ + NS_ERROR("Haven't Implemented this yet"); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsContentTreeOwner::GetWebBrowser(nsIWebBrowser** aWebBrowser) +{ + // Unimplemented, and probably will remain so; xpfe windows have docshells, + // not webbrowsers. + NS_ENSURE_ARG_POINTER(aWebBrowser); + *aWebBrowser = 0; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsContentTreeOwner::SetChromeFlags(uint32_t aChromeFlags) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetChromeFlags(aChromeFlags); +} + +NS_IMETHODIMP nsContentTreeOwner::GetChromeFlags(uint32_t* aChromeFlags) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetChromeFlags(aChromeFlags); +} + +NS_IMETHODIMP nsContentTreeOwner::DestroyBrowserWindow() +{ + NS_ERROR("Haven't Implemented this yet"); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsContentTreeOwner::SizeBrowserTo(int32_t aCX, int32_t aCY) +{ + NS_ERROR("Haven't Implemented this yet"); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsContentTreeOwner::ShowAsModal() +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->ShowModal(); +} + +NS_IMETHODIMP nsContentTreeOwner::IsWindowModal(bool *_retval) +{ + NS_ENSURE_STATE(mXULWindow); + *_retval = mXULWindow->mContinueModalLoop; + return NS_OK; +} + +NS_IMETHODIMP nsContentTreeOwner::ExitModalEventLoop(nsresult aStatus) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->ExitModalLoop(aStatus); +} + +//***************************************************************************** +// 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::Create() +{ + NS_ASSERTION(false, "You can't call this"); + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP nsContentTreeOwner::Destroy() +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->Destroy(); +} + +NS_IMETHODIMP nsContentTreeOwner::GetUnscaledDevicePixelsPerCSSPixel(double* aScale) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetUnscaledDevicePixelsPerCSSPixel(aScale); +} + +NS_IMETHODIMP nsContentTreeOwner::GetDevicePixelsPerDesktopPixel(double* aScale) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetDevicePixelsPerDesktopPixel(aScale); +} + +NS_IMETHODIMP nsContentTreeOwner::SetPositionDesktopPix(int32_t aX, int32_t aY) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetPositionDesktopPix(aX, aY); +} + +NS_IMETHODIMP nsContentTreeOwner::SetPosition(int32_t aX, int32_t aY) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetPosition(aX, aY); +} + +NS_IMETHODIMP nsContentTreeOwner::GetPosition(int32_t* aX, int32_t* aY) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetPosition(aX, aY); +} + +NS_IMETHODIMP nsContentTreeOwner::SetSize(int32_t aCX, int32_t aCY, bool aRepaint) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetSize(aCX, aCY, aRepaint); +} + +NS_IMETHODIMP nsContentTreeOwner::GetSize(int32_t* aCX, int32_t* aCY) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->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(mXULWindow); + return mXULWindow->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(mXULWindow); + return mXULWindow->GetPositionAndSize(aX, aY, aCX, aCY); +} + +NS_IMETHODIMP nsContentTreeOwner::Repaint(bool aForce) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->Repaint(aForce); +} + +NS_IMETHODIMP nsContentTreeOwner::GetParentWidget(nsIWidget** aParentWidget) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->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(mXULWindow); + return mXULWindow->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(mXULWindow); + return mXULWindow->GetNativeHandle(aNativeHandle); +} + +NS_IMETHODIMP nsContentTreeOwner::GetVisibility(bool* aVisibility) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetVisibility(aVisibility); +} + +NS_IMETHODIMP nsContentTreeOwner::SetVisibility(bool aVisibility) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetVisibility(aVisibility); +} + +NS_IMETHODIMP nsContentTreeOwner::GetEnabled(bool *aEnabled) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->GetEnabled(aEnabled); +} + +NS_IMETHODIMP nsContentTreeOwner::SetEnabled(bool aEnable) +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetEnabled(aEnable); +} + +NS_IMETHODIMP nsContentTreeOwner::GetMainWidget(nsIWidget** aMainWidget) +{ + NS_ENSURE_ARG_POINTER(aMainWidget); + NS_ENSURE_STATE(mXULWindow); + + *aMainWidget = mXULWindow->mWindow; + NS_IF_ADDREF(*aMainWidget); + + return NS_OK; +} + +NS_IMETHODIMP nsContentTreeOwner::SetFocus() +{ + NS_ENSURE_STATE(mXULWindow); + return mXULWindow->SetFocus(); +} + +NS_IMETHODIMP nsContentTreeOwner::GetTitle(char16_t** aTitle) +{ + NS_ENSURE_ARG_POINTER(aTitle); + NS_ENSURE_STATE(mXULWindow); + + return mXULWindow->GetTitle(aTitle); +} + +NS_IMETHODIMP nsContentTreeOwner::SetTitle(const char16_t* aTitle) +{ + // We only allow the title to be set from the primary content shell + if(!mPrimary || !mContentTitleSetting) + return NS_OK; + + NS_ENSURE_STATE(mXULWindow); + + nsAutoString title; + nsAutoString docTitle(aTitle); + + if (docTitle.IsEmpty()) + docTitle.Assign(mTitleDefault); + + if (!docTitle.IsEmpty()) { + if (!mTitlePreface.IsEmpty()) { + // Title will be: "Preface: Doc Title - Mozilla" + title.Assign(mTitlePreface); + title.Append(docTitle); + } + else { + // Title will be: "Doc Title - Mozilla" + title = docTitle; + } + + if (!mWindowTitleModifier.IsEmpty()) + title += mTitleSeparator + mWindowTitleModifier; + } + else + title.Assign(mWindowTitleModifier); // Title will just be plain "Mozilla" + + // + // if there is no location bar we modify the title to display at least + // the scheme and host (if any) as an anti-spoofing measure. + // + nsCOMPtr<dom::Element> docShellElement = mXULWindow->GetWindowDOMElement(); + + if (docShellElement) { + nsAutoString chromeString; + docShellElement->GetAttribute(NS_LITERAL_STRING("chromehidden"), chromeString); + if (chromeString.Find(NS_LITERAL_STRING("location")) != kNotFound) { + // + // location bar is turned off, find the browser location + // + // use the document's nsPrincipal to find the true owner + // in case of javascript: or data: documents + // + nsCOMPtr<nsIDocShellTreeItem> dsitem; + GetPrimaryContentShell(getter_AddRefs(dsitem)); + nsCOMPtr<nsIScriptObjectPrincipal> doc = + do_QueryInterface(dsitem ? dsitem->GetDocument() : nullptr); + if (doc) { + nsCOMPtr<nsIURI> uri; + nsIPrincipal* principal = doc->GetPrincipal(); + if (principal) { + principal->GetURI(getter_AddRefs(uri)); + if (uri) { + // + // remove any user:pass information + // + nsCOMPtr<nsIURIFixup> fixup(do_GetService(NS_URIFIXUP_CONTRACTID)); + if (fixup) { + nsCOMPtr<nsIURI> tmpuri; + nsresult rv = fixup->CreateExposableURI(uri,getter_AddRefs(tmpuri)); + if (NS_SUCCEEDED(rv) && tmpuri) { + // (don't bother if there's no host) + nsAutoCString host; + nsAutoCString prepath; + tmpuri->GetHost(host); + tmpuri->GetPrePath(prepath); + if (!host.IsEmpty()) { + // + // We have a scheme/host, update the title + // + title.Insert(NS_ConvertUTF8toUTF16(prepath) + + mTitleSeparator, 0); + } + } + } + } + } + } + } + nsIDocument* document = docShellElement->OwnerDoc(); + ErrorResult rv; + document->SetTitle(title, rv); + return rv.StealNSResult(); + } + + return mXULWindow->SetTitle(title.get()); +} + +//***************************************************************************** +// nsContentTreeOwner: nsIWindowProvider +//***************************************************************************** +NS_IMETHODIMP +nsContentTreeOwner::ProvideWindow(mozIDOMWindowProxy* aParent, + uint32_t aChromeFlags, + bool aCalledFromJS, + bool aPositionSpecified, + bool aSizeSpecified, + nsIURI* aURI, + const nsAString& aName, + const nsACString& aFeatures, + bool aForceNoOpener, + bool* aWindowIsNew, + mozIDOMWindowProxy** aReturn) +{ + NS_ENSURE_ARG_POINTER(aParent); + + auto* parent = nsPIDOMWindowOuter::From(aParent); + + *aReturn = nullptr; + + if (!mXULWindow) { + // Nothing to do here + return NS_OK; + } + +#ifdef DEBUG + nsCOMPtr<nsIWebNavigation> parentNav = do_GetInterface(aParent); + nsCOMPtr<nsIDocShellTreeOwner> parentOwner = do_GetInterface(parentNav); + NS_ASSERTION(SameCOMIdentity(parentOwner, + static_cast<nsIDocShellTreeOwner*>(this)), + "Parent from wrong docshell tree?"); +#endif + + // If aParent is inside an <iframe mozbrowser> and this isn't a request to + // open a modal-type window, we're going to create a new <iframe mozbrowser> + // and return its window here. + nsCOMPtr<nsIDocShell> docshell = do_GetInterface(aParent); + if (docshell && docshell->GetIsInMozBrowserOrApp() && + !(aChromeFlags & (nsIWebBrowserChrome::CHROME_MODAL | + nsIWebBrowserChrome::CHROME_OPENAS_DIALOG | + nsIWebBrowserChrome::CHROME_OPENAS_CHROME))) { + + BrowserElementParent::OpenWindowResult opened = + BrowserElementParent::OpenWindowInProcess(parent, aURI, aName, + aFeatures, aForceNoOpener, aReturn); + + // If OpenWindowInProcess handled the open (by opening it or blocking the + // popup), tell our caller not to proceed trying to create a new window + // through other means. + if (opened != BrowserElementParent::OPEN_WINDOW_IGNORED) { + *aWindowIsNew = opened == BrowserElementParent::OPEN_WINDOW_ADDED; + return *aWindowIsNew ? NS_OK : NS_ERROR_ABORT; + } + + // If we're in an app and the target is _blank, send the url to the OS + if (aName.LowerCaseEqualsLiteral("_blank")) { + nsCOMPtr<nsIExternalURLHandlerService> exUrlServ( + do_GetService(NS_EXTERNALURLHANDLERSERVICE_CONTRACTID)); + if (exUrlServ) { + + nsCOMPtr<nsIHandlerInfo> info; + bool found; + exUrlServ->GetURLHandlerInfoFromOS(aURI, &found, getter_AddRefs(info)); + + if (info && found) { + info->LaunchWithURI(aURI, nullptr); + return NS_ERROR_ABORT; + } + + } + } + } + + int32_t openLocation = + nsWindowWatcher::GetWindowOpenLocation(parent, aChromeFlags, aCalledFromJS, + aPositionSpecified, aSizeSpecified); + + if (openLocation != nsIBrowserDOMWindow::OPEN_NEWTAB && + openLocation != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW) { + // Just open a window normally + return NS_OK; + } + + nsCOMPtr<mozIDOMWindowProxy> domWin; + mXULWindow->GetWindowDOMWindow(getter_AddRefs(domWin)); + nsCOMPtr<nsIDOMChromeWindow> chromeWin = do_QueryInterface(domWin); + if (!chromeWin) { + // Really odd... but whatever + NS_WARNING("nsXULWindow's DOMWindow is not a chrome window"); + return NS_OK; + } + + nsCOMPtr<nsIBrowserDOMWindow> browserDOMWin; + chromeWin->GetBrowserDOMWindow(getter_AddRefs(browserDOMWin)); + 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; + } + + // Get a new rendering area from the browserDOMWin. We don't want + // to be starting any loads here, so get it with a null URI. + // + // This method handles setting the opener for us, so we don't need to set it + // ourselves. + return browserDOMWin->OpenURI(nullptr, aParent, + openLocation, + flags, aReturn); + } +} + +//***************************************************************************** +// nsContentTreeOwner: Accessors +//***************************************************************************** + +#if defined(XP_MACOSX) +class nsContentTitleSettingEvent : public Runnable +{ +public: + nsContentTitleSettingEvent(dom::Element* dse, const nsAString& wtm) + : mElement(dse), + mTitleDefault(wtm) {} + + NS_IMETHOD Run() override + { + ErrorResult rv; + mElement->SetAttribute(NS_LITERAL_STRING("titledefault"), mTitleDefault, rv); + mElement->RemoveAttribute(NS_LITERAL_STRING("titlemodifier"), rv); + return NS_OK; + } + +private: + nsCOMPtr<dom::Element> mElement; + nsString mTitleDefault; +}; +#endif + +void nsContentTreeOwner::XULWindow(nsXULWindow* aXULWindow) +{ + mXULWindow = aXULWindow; + if (mXULWindow && mPrimary) { + // Get the window title modifiers + nsCOMPtr<dom::Element> docShellElement = mXULWindow->GetWindowDOMElement(); + + nsAutoString contentTitleSetting; + + if(docShellElement) + { + docShellElement->GetAttribute(NS_LITERAL_STRING("contenttitlesetting"), contentTitleSetting); + if(contentTitleSetting.EqualsLiteral("true")) + { + mContentTitleSetting = true; + docShellElement->GetAttribute(NS_LITERAL_STRING("titledefault"), mTitleDefault); + docShellElement->GetAttribute(NS_LITERAL_STRING("titlemodifier"), mWindowTitleModifier); + docShellElement->GetAttribute(NS_LITERAL_STRING("titlepreface"), mTitlePreface); + +#if defined(XP_MACOSX) + // On OS X, treat the titlemodifier like it's the titledefault, and don't ever append + // the separator + appname. + if (mTitleDefault.IsEmpty()) { + NS_DispatchToCurrentThread( + new nsContentTitleSettingEvent(docShellElement, + mWindowTitleModifier)); + mTitleDefault = mWindowTitleModifier; + mWindowTitleModifier.Truncate(); + } +#endif + docShellElement->GetAttribute(NS_LITERAL_STRING("titlemenuseparator"), mTitleSeparator); + } + } + else + { + NS_ERROR("This condition should never happen. If it does, " + "we just won't get a modifier, but it still shouldn't happen."); + } + } +} + +nsXULWindow* nsContentTreeOwner::XULWindow() +{ + return mXULWindow; +} + +//***************************************************************************** +//*** nsSiteWindow implementation +//***************************************************************************** + +nsSiteWindow::nsSiteWindow(nsContentTreeOwner *aAggregator) +{ + mAggregator = aAggregator; +} + +nsSiteWindow::~nsSiteWindow() +{ +} + +NS_IMPL_ADDREF_USING_AGGREGATOR(nsSiteWindow, mAggregator) +NS_IMPL_RELEASE_USING_AGGREGATOR(nsSiteWindow, mAggregator) + +NS_INTERFACE_MAP_BEGIN(nsSiteWindow) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIEmbeddingSiteWindow) +NS_INTERFACE_MAP_END_AGGREGATED(mAggregator) + +NS_IMETHODIMP +nsSiteWindow::SetDimensions(uint32_t aFlags, + int32_t aX, int32_t aY, int32_t aCX, int32_t aCY) +{ + // XXX we're ignoring aFlags + return mAggregator->SetPositionAndSize(aX, aY, aCX, aCY, + nsIBaseWindow::eRepaint); +} + +NS_IMETHODIMP +nsSiteWindow::GetDimensions(uint32_t aFlags, + int32_t *aX, int32_t *aY, int32_t *aCX, int32_t *aCY) +{ + // XXX we're ignoring aFlags + return mAggregator->GetPositionAndSize(aX, aY, aCX, aCY); +} + +NS_IMETHODIMP +nsSiteWindow::SetFocus(void) +{ +#if 0 + /* This implementation focuses the main document and could make sense. + However this method is actually being used from within + nsGlobalWindow::Focus (providing a hook for MDI embedding apps) + and it's better for our purposes to not pick a document and + focus it, but allow nsGlobalWindow to carry on unhindered. + */ + nsXULWindow *window = mAggregator->XULWindow(); + if (window) { + nsCOMPtr<nsIDocShell> docshell; + window->GetDocShell(getter_AddRefs(docshell)); + if (docShell) { + nsCOMPtr<nsPIDOMWindowOuter> domWindow(docShell->GetWindow()); + if (domWindow) + domWindow->Focus(); + } + } +#endif + return NS_OK; +} + +/* this implementation focuses another window. if there isn't another + window to focus, we do nothing. */ +NS_IMETHODIMP +nsSiteWindow::Blur(void) +{ + NS_DEFINE_CID(kWindowMediatorCID, NS_WINDOWMEDIATOR_CID); + + nsCOMPtr<nsISimpleEnumerator> windowEnumerator; + nsCOMPtr<nsIXULWindow> xulWindow; + bool more, foundUs; + nsXULWindow *ourWindow = mAggregator->XULWindow(); + + { + nsCOMPtr<nsIWindowMediator> windowMediator(do_GetService(kWindowMediatorCID)); + if (windowMediator) + windowMediator->GetZOrderXULWindowEnumerator(0, 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<nsIXULWindow> nextXULWindow; + + windowEnumerator->GetNext(getter_AddRefs(nextWindow)); + nextXULWindow = do_QueryInterface(nextWindow); + + // got it!(?) + if (foundUs) { + xulWindow = nextXULWindow; + break; + } + + // remember the very first one, in case we have to wrap + if (!xulWindow) + xulWindow = nextXULWindow; + + // look for us + if (nextXULWindow == ourWindow) + foundUs = true; + + windowEnumerator->HasMoreElements(&more); + } + + // change focus to the window we just found + if (xulWindow) { + nsCOMPtr<nsIDocShell> docshell; + xulWindow->GetDocShell(getter_AddRefs(docshell)); + if (!docshell) { + return NS_OK; + } + + nsCOMPtr<nsPIDOMWindowOuter> domWindow = docshell->GetWindow(); + if (domWindow) + domWindow->Focus(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsSiteWindow::GetVisibility(bool *aVisibility) +{ + return mAggregator->GetVisibility(aVisibility); +} + +NS_IMETHODIMP +nsSiteWindow::SetVisibility(bool aVisibility) +{ + return mAggregator->SetVisibility(aVisibility); +} + +NS_IMETHODIMP +nsSiteWindow::GetTitle(char16_t * *aTitle) +{ + return mAggregator->GetTitle(aTitle); +} + +NS_IMETHODIMP +nsSiteWindow::SetTitle(const char16_t * aTitle) +{ + return mAggregator->SetTitle(aTitle); +} + +NS_IMETHODIMP +nsSiteWindow::GetSiteWindow(void **aSiteWindow) +{ + return mAggregator->GetParentNativeWindow(aSiteWindow); +} + diff --git a/xpfe/appshell/nsContentTreeOwner.h b/xpfe/appshell/nsContentTreeOwner.h new file mode 100644 index 000000000..d6a0d42b3 --- /dev/null +++ b/xpfe/appshell/nsContentTreeOwner.h @@ -0,0 +1,63 @@ +/* -*- 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 "nsIWebBrowserChrome3.h" +#include "nsIWindowProvider.h" + +class nsXULWindow; +class nsSiteWindow; + +class nsContentTreeOwner final : public nsIDocShellTreeOwner, + public nsIBaseWindow, + public nsIInterfaceRequestor, + public nsIWebBrowserChrome3, + public nsIWindowProvider +{ +friend class nsXULWindow; +friend class nsSiteWindow; + +public: + NS_DECL_ISUPPORTS + + NS_DECL_NSIBASEWINDOW + NS_DECL_NSIDOCSHELLTREEOWNER + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIWEBBROWSERCHROME + NS_DECL_NSIWEBBROWSERCHROME2 + NS_DECL_NSIWEBBROWSERCHROME3 + NS_DECL_NSIWINDOWPROVIDER + +protected: + explicit nsContentTreeOwner(bool fPrimary); + virtual ~nsContentTreeOwner(); + + void XULWindow(nsXULWindow* aXULWindow); + nsXULWindow* XULWindow(); + +protected: + nsXULWindow *mXULWindow; + nsSiteWindow *mSiteWindow; + bool mPrimary; + bool mContentTitleSetting; + nsString mWindowTitleModifier; + nsString mTitleSeparator; + nsString mTitlePreface; + nsString mTitleDefault; +}; + +#endif /* nsContentTreeOwner_h__ */ diff --git a/xpfe/appshell/nsIAppShellService.idl b/xpfe/appshell/nsIAppShellService.idl new file mode 100644 index 000000000..e7d96a5d1 --- /dev/null +++ b/xpfe/appshell/nsIAppShellService.idl @@ -0,0 +1,150 @@ +/* -*- 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 nsIXULWindow; +interface nsIWindowlessBrowser; +interface nsIURI; +interface mozIDOMWindowProxy; +interface nsIAppShell; +interface nsITabParent; + +[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 nsIBrowserWindow 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. + * @param aOpeningTab The TabParent that requested that this window be opened. + * Can be left null. + * @param aOpenerWindow The Window Proxy which requested that this window be opened. + * Can be left null. + */ + const long SIZE_TO_CONTENT = -1; + nsIXULWindow createTopLevelWindow(in nsIXULWindow aParent, + in nsIURI aUrl, + in uint32_t aChromeMask, + in long aInitialWidth, + in long aInitialHeight, + in nsITabParent aOpeningTab, + in mozIDOMWindowProxy aOpenerWindow); + + /** + * 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. + */ + nsIWindowlessBrowser createWindowlessBrowser([optional] in bool aIsChrome); + + [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 nsIXULWindow 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 the (singleton) application hidden private window, automatically + * created and maintained by this AppShellService. This window is created + * in private browsing mode. + * @param aResult the hidden private window. Do not unhide hidden window. + * Do not taunt hidden window. + */ + readonly attribute nsIXULWindow hiddenPrivateWindow; + + /** + * Return the (singleton) application hidden private window, automatically + * created and maintained by this AppShellService. This window is created + * in private browsing mode. + * @param aResult the hidden private window. Do not unhide hidden window. + * Do not taunt hidden window. + */ + readonly attribute mozIDOMWindowProxy hiddenPrivateDOMWindow; + + /** + * 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 nsIXULWindow 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 nsIXULWindow aWindow); + + /** + * Whether the hidden private window has been lazily created. + */ + [noscript] + readonly attribute boolean hasHiddenPrivateWindow; + + /** + * 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/nsIPopupWindowManager.idl b/xpfe/appshell/nsIPopupWindowManager.idl new file mode 100644 index 000000000..4e6cb99b3 --- /dev/null +++ b/xpfe/appshell/nsIPopupWindowManager.idl @@ -0,0 +1,35 @@ +/* -*- 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/. */ + +/** + * This is the interface to the Popup Window Manager: an object which + * maintains popup window permissions by website. + */ + +#include "nsISupports.idl" + +interface nsIPrincipal; + +[scriptable, uuid(66386aa9-2088-4bae-82c7-9f58bc02be64)] +interface nsIPopupWindowManager : nsISupports { + + /** + * These values are returned by the testPermission method + */ + const uint32_t ALLOW_POPUP = 1; + const uint32_t DENY_POPUP = 2; + const uint32_t ALLOW_POPUP_WITH_PREJUDICE = 3; + + /** + * Test whether a website has permission to show a popup window. + * @param principal is the principal to be tested + * @return one of the enumerated permission actions defined above + */ + uint32_t testPermission(in nsIPrincipal principal); +}; + +%{ C++ +#define NS_POPUPWINDOWMANAGER_CONTRACTID "@mozilla.org/PopupWindowManager;1" +%} diff --git a/xpfe/appshell/nsIWindowMediator.idl b/xpfe/appshell/nsIWindowMediator.idl new file mode 100644 index 000000000..b38297594 --- /dev/null +++ b/xpfe/appshell/nsIWindowMediator.idl @@ -0,0 +1,206 @@ +/* -*- 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 nsIXULWindow; +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 nsIXULWindows + */ + nsISimpleEnumerator getXULWindowEnumerator(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 nsIDOMWindows + */ + nsISimpleEnumerator getZOrderDOMWindowEnumerator(in wstring aWindowType, + in boolean aFrontToBack); + + /** Identical to getZOrderDOMWindowEnumerator except: + * @return an enumerator of nsIXULWindows + */ + nsISimpleEnumerator getZOrderXULWindowEnumerator(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); + + /** + * 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 nsIXULWindow 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 nsIXULWindow 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 nsIXULWindow aWindow); + + /** Call this method when a window's title changes. Listeners (see + * addListener) will be notified through their onWindowTitleChange method. + * @param aWindow the window whose title has changed + * @param inTitle the window's new title + */ + [noscript] void updateWindowTitle(in nsIXULWindow aWindow, + in wstring inTitle ); + + /* 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 nsIXULWindow to nsIWidgets. A pure interface + * would use all nsIXULWindows. 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 nsIXULWindow 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 nsIXULWindow inWindow, + in unsigned long inPosition, + in nsIXULWindow inBelow); + + /** Return the window's Z level (as defined in nsIXULWindow). + * @param aWindow the window in question + * @return aWindow's z level + */ + [noscript] uint32_t getZLevel(in nsIXULWindow aWindow); + + /** Set the window's Z level (as defined in nsIXULWindow). 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 + * nsIXULWindow::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 nsIXULWindow 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); +}; + +// XXXcatalinb: This should be merged to nsIWindowMediator. Using this +// to avoid UUID change in aurora. +[scriptable, uuid(b9ed4063-39a2-4302-8e5c-7287eef021fe)] +interface nsIWindowMediator_44 : nsIWindowMediator +{ + /** + * Same as getMostRecentWindow, but ignores private browsing + * windows. + */ + mozIDOMWindowProxy getMostRecentNonPBWindow(in wstring aWindowType); +}; diff --git a/xpfe/appshell/nsIWindowMediatorListener.idl b/xpfe/appshell/nsIWindowMediatorListener.idl new file mode 100644 index 000000000..459e3b6ab --- /dev/null +++ b/xpfe/appshell/nsIWindowMediatorListener.idl @@ -0,0 +1,19 @@ +/* -*- 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 nsIXULWindow; + +[scriptable, uuid(2F276982-0D60-4377-A595-D350BA516395)] +interface nsIWindowMediatorListener : nsISupports +{ + void onWindowTitleChange(in nsIXULWindow window, + in wstring newTitle); + + void onOpenWindow(in nsIXULWindow window); + void onCloseWindow(in nsIXULWindow window); +}; + diff --git a/xpfe/appshell/nsIWindowlessBrowser.idl b/xpfe/appshell/nsIWindowlessBrowser.idl new file mode 100644 index 000000000..c959e47a4 --- /dev/null +++ b/xpfe/appshell/nsIWindowlessBrowser.idl @@ -0,0 +1,27 @@ +/* -*- 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" + +/** + * 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, 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(); +}; + diff --git a/xpfe/appshell/nsIXULBrowserWindow.idl b/xpfe/appshell/nsIXULBrowserWindow.idl new file mode 100644 index 000000000..40f1898c8 --- /dev/null +++ b/xpfe/appshell/nsIXULBrowserWindow.idl @@ -0,0 +1,78 @@ +/* -*- 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" +#include "nsIDOMNode.idl" + +interface nsIRequest; +interface nsIDOMElement; +interface nsIInputStream; +interface nsIDocShell; +interface nsITabParent; +interface mozIDOMWindowProxy; + +/** + * 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 +{ + /** + * Sets the status according to JS' version of status. + */ + void setJSStatus(in AString status); + + /** + * Tells the object implementing this function what link we are currently + * over. + */ + void setOverLink(in AString link, in nsIDOMElement element); + + /** + * Determines the appropriate target for a link. + */ + AString onBeforeLinkTraversal(in AString originalTarget, + in nsIURI linkURI, + in nsIDOMNode linkNode, + in boolean isAppTab); + + /** + * Find the initial browser of the window and set its remote attribute. + * This can be used to ensure that there is a remote browser in a new + * window when it first spawns. + * + */ + nsITabParent forceInitialBrowserRemote(); + void forceInitialBrowserNonRemote(in mozIDOMWindowProxy openerWindow); + + /** + * Determines whether a load should continue. + * + * @param aDocShell + * The docshell performing the load. + * @param aURI + * The URI being loaded. + * @param aReferrer + * The referrer of the load. + */ + bool shouldLoadURI(in nsIDocShell aDocShell, + in nsIURI aURI, + in nsIURI aReferrer); + /** + * Show/hide a tooltip (when the user mouses over a link, say). + */ + void showTooltip(in long x, in long y, in AString tooltip, in AString direction); + void hideTooltip(); + + /** + * Return the number of tabs in this window. + */ + uint32_t getTabCount(); +}; + diff --git a/xpfe/appshell/nsIXULWindow.idl b/xpfe/appshell/nsIXULWindow.idl new file mode 100644 index 000000000..8db12adb6 --- /dev/null +++ b/xpfe/appshell/nsIXULWindow.idl @@ -0,0 +1,168 @@ +/* -*- 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 nsIXULWindow + * + * When the window is destroyed, it will fire a "xul-window-destroyed" + * notification through the global observer service. + */ + +interface nsIDocShell; +interface nsIDocShellTreeItem; +interface nsIXULBrowserWindow; +interface nsITabParent; +interface mozIDOMWindowProxy; + +[scriptable, uuid(d6d7a014-e28d-4c9d-8727-1cf6d870619b)] +interface nsIXULWindow : 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 + * primaryTabParent. + */ + readonly attribute nsITabParent primaryTabParent; + + void tabParentAdded(in nsITabParent aTab, in boolean aPrimary); + void tabParentRemoved(in nsITabParent aTab); + + /** + * The content shell specified by the supplied id. + * + * 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. + */ + nsIDocShellTreeItem getContentShellById(in wstring ID); + + /** + * Tell this window that it has picked up a child XUL window + * @param aChild the child window being added + */ + void addChildWindow(in nsIXULWindow aChild); + + /** + * Tell this window that it has lost a child XUL window + * @param aChild the child window being removed + */ + void removeChildWindow(in nsIXULWindow aChild); + + /** + * 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 nsIXULWindow 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(); + + 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; + + /** + * contextFlags are from nsIWindowCreator2 + */ + attribute uint32_t contextFlags; + + 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 aOpeningTab the TabParent that requested this new window be opened. + * Can be left null. + * @param aOpener The window which is requesting that this new window be opened. + * @return the newly minted window + */ + nsIXULWindow createNewWindow(in int32_t aChromeFlags, + in nsITabParent aOpeningTab, + in mozIDOMWindowProxy aOpener); + + attribute nsIXULBrowserWindow XULBrowserWindow; + + /** + * Back-door method to force application of chrome flags at a particular + * time. 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 applyChromeFlags(); + + /** + * 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. + * + * We need to supply the content area dimensions because sometimes + * the child's nsDocShellTreeOwner needs to propagate a SizeShellTo + * call to the parent. But the shellItem argument of the call will + * not be available on the parent side. + * + * Note: this is an internal method, other consumers should never call this. + * + * @param aDesiredWidth + * The desired width of the content area in device pixels. + * @param aDesiredHeight + * The desired height of the content area in device pixels. + * @param shellItemWidth + * The current width of the content area. + * @param shellItemHeight + * The current height of the content area. + */ + [noscript, notxpcom] void sizeShellToWithLimit(in int32_t aDesiredWidth, + in int32_t aDesiredHeight, + in int32_t shellItemWidth, + in int32_t shellItemHeight); +}; diff --git a/xpfe/appshell/nsWebShellWindow.cpp b/xpfe/appshell/nsWebShellWindow.cpp new file mode 100644 index 000000000..288d94759 --- /dev/null +++ b/xpfe/appshell/nsWebShellWindow.cpp @@ -0,0 +1,911 @@ +/* -*- 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 "nsWebShellWindow.h" + +#include "nsLayoutCID.h" +#include "nsContentCID.h" +#include "nsIWeakReference.h" +#include "nsIContentViewer.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsIURL.h" +#include "nsIIOService.h" +#include "nsIURL.h" +#include "nsNetCID.h" +#include "nsIStringBundle.h" +#include "nsReadableUtils.h" + +#include "nsContentUtils.h" +#include "nsEscape.h" +#include "nsPIDOMWindow.h" +#include "nsIWebNavigation.h" +#include "nsIWindowWatcher.h" + +#include "nsIDOMXULElement.h" + +#include "nsWidgetInitData.h" +#include "nsWidgetsCID.h" +#include "nsIWidget.h" +#include "nsIWidgetListener.h" + +#include "nsIDOMCharacterData.h" +#include "nsIDOMNodeList.h" + +#include "nsITimer.h" +#include "nsXULPopupManager.h" + + +#include "nsIDOMXULDocument.h" + +#include "nsFocusManager.h" + +#include "nsIWebProgress.h" +#include "nsIWebProgressListener.h" + +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIDOMNode.h" +#include "nsIDOMElement.h" +#include "nsIDocumentLoaderFactory.h" +#include "nsIObserverService.h" +#include "prprf.h" + +#include "nsIScreenManager.h" +#include "nsIScreen.h" + +#include "nsIContent.h" // for menus +#include "nsIScriptSecurityManager.h" + +// For calculating size +#include "nsIPresShell.h" +#include "nsPresContext.h" + +#include "nsIBaseWindow.h" +#include "nsIDocShellTreeItem.h" + +#include "mozilla/Attributes.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/MouseEvents.h" + +#include "nsPIWindowRoot.h" + +#ifdef XP_MACOSX +#include "nsINativeMenuService.h" +#define USE_NATIVE_MENUS +#endif + +using namespace mozilla; +using namespace mozilla::dom; + +/* Define Class IDs */ +static NS_DEFINE_CID(kWindowCID, NS_WINDOW_CID); + +#define SIZE_PERSISTENCE_TIMEOUT 500 // msec + +nsWebShellWindow::nsWebShellWindow(uint32_t aChromeFlags) + : nsXULWindow(aChromeFlags) + , mSPTimerLock("nsWebShellWindow.mSPTimerLock") + , mWidgetListenerDelegate(this) +{ +} + +nsWebShellWindow::~nsWebShellWindow() +{ + MutexAutoLock lock(mSPTimerLock); + if (mSPTimer) + mSPTimer->Cancel(); +} + +NS_IMPL_ADDREF_INHERITED(nsWebShellWindow, nsXULWindow) +NS_IMPL_RELEASE_INHERITED(nsWebShellWindow, nsXULWindow) + +NS_INTERFACE_MAP_BEGIN(nsWebShellWindow) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) +NS_INTERFACE_MAP_END_INHERITING(nsXULWindow) + +nsresult nsWebShellWindow::Initialize(nsIXULWindow* aParent, + nsIXULWindow* aOpener, + nsIURI* aUrl, + int32_t aInitialWidth, + int32_t aInitialHeight, + bool aIsHiddenWindow, + nsITabParent *aOpeningTab, + mozIDOMWindowProxy *aOpenerWindow, + nsWidgetInitData& widgetInitData) +{ + nsresult rv; + nsCOMPtr<nsIWidget> parentWidget; + + mIsHiddenWindow = aIsHiddenWindow; + + int32_t initialX = 0, initialY = 0; + nsCOMPtr<nsIBaseWindow> base(do_QueryInterface(aOpener)); + if (base) { + rv = base->GetPositionAndSize(&mOpenerScreenRect.x, + &mOpenerScreenRect.y, + &mOpenerScreenRect.width, + &mOpenerScreenRect.height); + if (NS_FAILED(rv)) { + mOpenerScreenRect.SetEmpty(); + } else { + double scale; + if (NS_SUCCEEDED(base->GetUnscaledDevicePixelsPerCSSPixel(&scale))) { + mOpenerScreenRect.x = NSToIntRound(mOpenerScreenRect.x / scale); + mOpenerScreenRect.y = NSToIntRound(mOpenerScreenRect.y / scale); + mOpenerScreenRect.width = NSToIntRound(mOpenerScreenRect.width / scale); + mOpenerScreenRect.height = NSToIntRound(mOpenerScreenRect.height / scale); + } + initialX = mOpenerScreenRect.x; + initialY = mOpenerScreenRect.y; + ConstrainToOpenerScreen(&initialX, &initialY); + } + } + + // XXX: need to get the default window size from prefs... + // Doesn't come from prefs... will come from CSS/XUL/RDF + DesktopIntRect deskRect(initialX, initialY, aInitialWidth, aInitialHeight); + + // Create top level window + mWindow = do_CreateInstance(kWindowCID, &rv); + if (NS_OK != rv) { + return rv; + } + + /* 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 WebShellWindow. 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)); + + // Create web shell + mDocShell = do_CreateInstance("@mozilla.org/docshell;1"); + NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE); + + mDocShell->SetOpener(aOpeningTab); + + // Make sure to set the item type on the docshell _before_ calling + // Create() so it knows what type it is. + nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(do_QueryInterface(mDocShell)); + NS_ENSURE_TRUE(docShellAsItem, NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(EnsureChromeTreeOwner(), NS_ERROR_FAILURE); + + docShellAsItem->SetTreeOwner(mChromeTreeOwner); + docShellAsItem->SetItemType(nsIDocShellTreeItem::typeChrome); + + r.x = r.y = 0; + nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(mDocShell)); + NS_ENSURE_SUCCESS(docShellAsWin->InitWindow(nullptr, mWindow, + r.x, r.y, r.width, r.height), NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(docShellAsWin->Create(), NS_ERROR_FAILURE); + + // Attach a WebProgress listener.during initialization... + nsCOMPtr<nsIWebProgress> webProgress(do_GetInterface(mDocShell, &rv)); + if (webProgress) { + webProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_NETWORK); + } + + if (aOpenerWindow) { + nsPIDOMWindowOuter* window = mDocShell->GetWindow(); + MOZ_ASSERT(window); + window->SetOpenerWindow(nsPIDOMWindowOuter::From(aOpenerWindow), true); + } + + // Eagerly create an about:blank content viewer with the right principal here, + // rather than letting it happening in the upcoming call to + // SetInitialPrincipalToSubject. 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. + // Note that we don't accept expanded principals here, similar to + // SetInitialPrincipalToSubject. + if (nsContentUtils::IsInitialized()) { // Sometimes this happens really early See bug 793370. + MOZ_ASSERT(mDocShell->ItemType() == nsIDocShellTreeItem::typeChrome); + nsCOMPtr<nsIPrincipal> principal = nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller(); + if (nsContentUtils::IsExpandedPrincipal(principal)) { + principal = nullptr; + } + rv = mDocShell->CreateAboutBlankContentViewer(principal); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIDocument> doc = mDocShell->GetDocument(); + NS_ENSURE_TRUE(!!doc, NS_ERROR_FAILURE); + doc->SetIsInitialDocument(true); + } + + if (nullptr != aUrl) { + nsCString tmpStr; + + rv = aUrl->GetSpec(tmpStr); + if (NS_FAILED(rv)) return rv; + + NS_ConvertUTF8toUTF16 urlString(tmpStr); + nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell)); + NS_ENSURE_TRUE(webNav, NS_ERROR_FAILURE); + rv = webNav->LoadURI(urlString.get(), + nsIWebNavigation::LOAD_FLAGS_NONE, + nullptr, + nullptr, + nullptr); + NS_ENSURE_SUCCESS(rv, rv); + } + + return rv; +} + +nsIPresShell* +nsWebShellWindow::GetPresShell() +{ + if (!mDocShell) + return nullptr; + + return mDocShell->GetPresShell(); +} + +bool +nsWebShellWindow::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, + NS_LITERAL_STRING("MozUpdateWindowPos"), + false, false, nullptr); + } + + // Persist position, but not immediately, in case this OS is firing + // repeated move events as the user drags the window + SetPersistenceTimer(PAD_POSITION); + return false; +} + +bool +nsWebShellWindow::WindowResized(nsIWidget* aWidget, int32_t aWidth, int32_t aHeight) +{ + nsCOMPtr<nsIBaseWindow> shellAsWin(do_QueryInterface(mDocShell)); + if (shellAsWin) { + shellAsWin->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()) + SetPersistenceTimer(PAD_POSITION | PAD_SIZE | PAD_MISC); + return true; +} + +bool +nsWebShellWindow::RequestWindowClose(nsIWidget* aWidget) +{ + // Maintain a reference to this as it is about to get destroyed. + nsCOMPtr<nsIXULWindow> xulWindow(this); + + nsCOMPtr<nsPIDOMWindowOuter> window(mDocShell ? mDocShell->GetWindow() : nullptr); + nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(window); + + nsCOMPtr<nsIPresShell> 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, eWindowClose, nullptr, + WidgetMouseEvent::eReal); + if (NS_SUCCEEDED(eventTarget->DispatchDOMEvent(&event, nullptr, presContext, &status)) && + status == nsEventStatus_eConsumeNoDefault) + return false; + } + + Destroy(); + return false; +} + +void +nsWebShellWindow::SizeModeChanged(nsSizeMode sizeMode) +{ + // 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 (sizeMode == nsSizeMode_Maximized || sizeMode == nsSizeMode_Fullscreen) { + uint32_t zLevel; + GetZLevel(&zLevel); + if (zLevel > nsIXULWindow::normalZ) + SetZLevel(nsIXULWindow::normalZ); + } + mWindow->SetSizeMode(sizeMode); + + // Persist mode, but not immediately, because in many (all?) + // cases this will merge with the similar call in NS_SIZE and + // write the attribute values only once. + SetPersistenceTimer(PAD_MISC); + nsCOMPtr<nsPIDOMWindowOuter> ourWindow = + mDocShell ? mDocShell->GetWindow() : nullptr; + if (ourWindow) { + MOZ_ASSERT(ourWindow->IsOuterWindow()); + + // Ensure that the fullscreen state is synchronized between + // the widget and the outer window object. + if (sizeMode == nsSizeMode_Fullscreen) { + ourWindow->SetFullScreen(true); + } + else if (sizeMode != nsSizeMode_Minimized) { + if (ourWindow->GetFullScreen()) { + // The first SetFullscreenInternal call below ensures that we do + // not trigger any fullscreen transition even if the window was + // put in fullscreen only for the Fullscreen API. The second + // SetFullScreen call ensures that the window really exit from + // fullscreen even if it entered fullscreen for both Fullscreen + // Mode and Fullscreen API. + ourWindow->SetFullscreenInternal(FullscreenReason::ForForceExitFullscreen, false); + ourWindow->SetFullScreen(false); + } + } + + // And always fire a user-defined sizemodechange event on the window + ourWindow->DispatchCustomEvent(NS_LITERAL_STRING("sizemodechange")); + } + + nsIPresShell* presShell; + if ((presShell = GetPresShell())) { + presShell->GetPresContext()->SizeModeChanged(sizeMode); + } + + // 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 +nsWebShellWindow::UIResolutionChanged() +{ + nsCOMPtr<nsPIDOMWindowOuter> ourWindow = + mDocShell ? mDocShell->GetWindow() : nullptr; + if (ourWindow) { + MOZ_ASSERT(ourWindow->IsOuterWindow()); + ourWindow->DispatchCustomEvent(NS_LITERAL_STRING("resolutionchange")); + } +} + +void +nsWebShellWindow::FullscreenChanged(bool aInFullscreen) +{ + if (mDocShell) { + if (nsCOMPtr<nsPIDOMWindowOuter> ourWindow = mDocShell->GetWindow()) { + ourWindow->FinishFullscreenChange(aInFullscreen); + } + } +} + +void +nsWebShellWindow::OSToolbarButtonPressed() +{ + // Keep a reference as setting the chrome flags can fire events. + nsCOMPtr<nsIXULWindow> xulWindow(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(xulWindow)); + 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 +nsWebShellWindow::ZLevelChanged(bool aImmediate, nsWindowZ *aPlacement, + nsIWidget* aRequestBelow, nsIWidget** aActualBelow) +{ + if (aActualBelow) + *aActualBelow = nullptr; + + return ConstrainToZLevel(aImmediate, aPlacement, aRequestBelow, aActualBelow); +} + +void +nsWebShellWindow::WindowActivated() +{ + nsCOMPtr<nsIXULWindow> xulWindow(this); + + // focusing the window could cause it to close, so keep a reference to it + nsCOMPtr<nsPIDOMWindowOuter> window = mDocShell ? mDocShell->GetWindow() : nullptr; + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (fm && window) + fm->WindowRaised(window); + + if (mChromeLoaded) { + PersistentAttributesDirty(PAD_POSITION | PAD_SIZE | PAD_MISC); + SavePersistentAttributes(); + } +} + +void +nsWebShellWindow::WindowDeactivated() +{ + nsCOMPtr<nsIXULWindow> xulWindow(this); + + nsCOMPtr<nsPIDOMWindowOuter> window = + mDocShell ? mDocShell->GetWindow() : nullptr; + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (fm && window) + fm->WindowLowered(window); +} + +#ifdef USE_NATIVE_MENUS +static void LoadNativeMenus(nsIDOMDocument *aDOMDoc, nsIWidget *aParentWindow) +{ + nsCOMPtr<nsINativeMenuService> nms = do_GetService("@mozilla.org/widget/nativemenuservice;1"); + if (!nms) { + return; + } + + // Find the menubar tag (if there is more than one, we ignore all but + // the first). + nsCOMPtr<nsIDOMNodeList> menubarElements; + aDOMDoc->GetElementsByTagNameNS(NS_LITERAL_STRING("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"), + NS_LITERAL_STRING("menubar"), + getter_AddRefs(menubarElements)); + + nsCOMPtr<nsIDOMNode> menubarNode; + if (menubarElements) + menubarElements->Item(0, getter_AddRefs(menubarNode)); + + if (menubarNode) { + nsCOMPtr<nsIContent> menubarContent(do_QueryInterface(menubarNode)); + nms->CreateNativeMenuBar(aParentWindow, menubarContent); + } else { + nms->CreateNativeMenuBar(aParentWindow, nullptr); + } +} +#endif + +namespace mozilla { + +class WebShellWindowTimerCallback final : public nsITimerCallback +{ +public: + explicit WebShellWindowTimerCallback(nsWebShellWindow* 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; + } + +private: + ~WebShellWindowTimerCallback() {} + + RefPtr<nsWebShellWindow> mWindow; +}; + +NS_IMPL_ISUPPORTS(WebShellWindowTimerCallback, nsITimerCallback) + +} // namespace mozilla + +void +nsWebShellWindow::SetPersistenceTimer(uint32_t aDirtyFlags) +{ + MutexAutoLock lock(mSPTimerLock); + if (!mSPTimer) { + mSPTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (!mSPTimer) { + NS_WARNING("Couldn't create @mozilla.org/timer;1 instance?"); + return; + } + } + + RefPtr<WebShellWindowTimerCallback> callback = + new WebShellWindowTimerCallback(this); + mSPTimer->InitWithCallback(callback, SIZE_PERSISTENCE_TIMEOUT, + nsITimer::TYPE_ONE_SHOT); + + PersistentAttributesDirty(aDirtyFlags); +} + +void +nsWebShellWindow::FirePersistenceTimer() +{ + MutexAutoLock lock(mSPTimerLock); + SavePersistentAttributes(); +} + + +//---------------------------------------- +// nsIWebProgessListener implementation +//---------------------------------------- +NS_IMETHODIMP +nsWebShellWindow::OnProgressChange(nsIWebProgress *aProgress, + nsIRequest *aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsWebShellWindow::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 + /////////////////////////////// + nsCOMPtr<nsIContentViewer> cv; + mDocShell->GetContentViewer(getter_AddRefs(cv)); + if (cv) { + nsCOMPtr<nsIDOMDocument> menubarDOMDoc(do_QueryInterface(cv->GetDocument())); + if (menubarDOMDoc) + LoadNativeMenus(menubarDOMDoc, mWindow); + } +#endif // USE_NATIVE_MENUS + + OnChromeLoaded(); + LoadContentAreas(); + + return NS_OK; +} + +NS_IMETHODIMP +nsWebShellWindow::OnLocationChange(nsIWebProgress *aProgress, + nsIRequest *aRequest, + nsIURI *aURI, + uint32_t aFlags) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsWebShellWindow::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsWebShellWindow::OnSecurityChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + uint32_t state) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + + +//---------------------------------------- + +// if the main document URL specified URLs for any content areas, start them loading +void nsWebShellWindow::LoadContentAreas() { + + nsAutoString searchSpec; + + // fetch the chrome document URL + nsCOMPtr<nsIContentViewer> contentViewer; + // yes, it's possible for the docshell to be null even this early + // see bug 57514. + if (mDocShell) + mDocShell->GetContentViewer(getter_AddRefs(contentViewer)); + if (contentViewer) { + nsIDocument* doc = contentViewer->GetDocument(); + if (doc) { + nsIURI* mainURL = doc->GetDocumentURI(); + + nsCOMPtr<nsIURL> url = do_QueryInterface(mainURL); + if (url) { + nsAutoCString search; + url->GetQuery(search); + + AppendUTF8toUTF16(search, searchSpec); + } + } + } + + // content URLs are specified in the search part of the URL + // as <contentareaID>=<escapedURL>[;(repeat)] + if (!searchSpec.IsEmpty()) { + int32_t begPos, + eqPos, + endPos; + nsString contentAreaID, + contentURL; + char *urlChar; + nsresult rv; + for (endPos = 0; endPos < (int32_t)searchSpec.Length(); ) { + // extract contentAreaID and URL substrings + begPos = endPos; + eqPos = searchSpec.FindChar('=', begPos); + if (eqPos < 0) + break; + + endPos = searchSpec.FindChar(';', eqPos); + if (endPos < 0) + endPos = searchSpec.Length(); + searchSpec.Mid(contentAreaID, begPos, eqPos-begPos); + searchSpec.Mid(contentURL, eqPos+1, endPos-eqPos-1); + endPos++; + + // see if we have a docshell with a matching contentAreaID + nsCOMPtr<nsIDocShellTreeItem> content; + rv = GetContentShellById(contentAreaID.get(), getter_AddRefs(content)); + if (NS_SUCCEEDED(rv) && content) { + nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(content)); + if (webNav) { + urlChar = ToNewCString(contentURL); + if (urlChar) { + nsUnescape(urlChar); + contentURL.AssignWithConversion(urlChar); + webNav->LoadURI(contentURL.get(), + nsIWebNavigation::LOAD_FLAGS_NONE, + nullptr, + nullptr, + nullptr); + free(urlChar); + } + } + } + } + } +} + +/** + * ExecuteCloseHandler - Run the close handler, if any. + * @return true iff we found a close handler to run. + */ +bool nsWebShellWindow::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<nsIXULWindow> kungFuDeathGrip(this); + + nsCOMPtr<EventTarget> eventTarget; + if (mDocShell) { + eventTarget = do_QueryInterface(mDocShell->GetWindow()); + } + + if (eventTarget) { + nsCOMPtr<nsIContentViewer> contentViewer; + mDocShell->GetContentViewer(getter_AddRefs(contentViewer)); + if (contentViewer) { + RefPtr<nsPresContext> presContext; + contentViewer->GetPresContext(getter_AddRefs(presContext)); + + nsEventStatus status = nsEventStatus_eIgnore; + WidgetMouseEvent event(true, eWindowClose, nullptr, + WidgetMouseEvent::eReal); + + nsresult rv = + eventTarget->DispatchDOMEvent(&event, nullptr, presContext, &status); + if (NS_SUCCEEDED(rv) && status == nsEventStatus_eConsumeNoDefault) + return true; + // else fall through and return false + } + } + + return false; +} // ExecuteCloseHandler + +void nsWebShellWindow::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.x, mOpenerScreenRect.y, + mOpenerScreenRect.width, mOpenerScreenRect.height, + getter_AddRefs(screen)); + if (screen) { + screen->GetAvailRectDisplayPix(&left, &top, &width, &height); + if (*aX < left || *aX > left + width) { + *aX = left; + } + if (*aY < top || *aY > top + height) { + *aY = top; + } + } + } +} + +// nsIBaseWindow +NS_IMETHODIMP nsWebShellWindow::Destroy() +{ + nsresult rv; + nsCOMPtr<nsIWebProgress> webProgress(do_GetInterface(mDocShell, &rv)); + if (webProgress) { + webProgress->RemoveProgressListener(this); + } + + nsCOMPtr<nsIXULWindow> kungFuDeathGrip(this); + { + MutexAutoLock lock(mSPTimerLock); + if (mSPTimer) { + mSPTimer->Cancel(); + SavePersistentAttributes(); + mSPTimer = nullptr; + } + } + return nsXULWindow::Destroy(); +} + +nsIXULWindow* +nsWebShellWindow::WidgetListenerDelegate::GetXULWindow() +{ + return mWebShellWindow->GetXULWindow(); +} + +nsIPresShell* +nsWebShellWindow::WidgetListenerDelegate::GetPresShell() +{ + return mWebShellWindow->GetPresShell(); +} + +bool +nsWebShellWindow::WidgetListenerDelegate::WindowMoved( + nsIWidget* aWidget, int32_t aX, int32_t aY) +{ + RefPtr<nsWebShellWindow> holder = mWebShellWindow; + return holder->WindowMoved(aWidget, aX, aY); +} + +bool +nsWebShellWindow::WidgetListenerDelegate::WindowResized( + nsIWidget* aWidget, int32_t aWidth, int32_t aHeight) +{ + RefPtr<nsWebShellWindow> holder = mWebShellWindow; + return holder->WindowResized(aWidget, aWidth, aHeight); +} + +bool +nsWebShellWindow::WidgetListenerDelegate::RequestWindowClose(nsIWidget* aWidget) +{ + RefPtr<nsWebShellWindow> holder = mWebShellWindow; + return holder->RequestWindowClose(aWidget); +} + +void +nsWebShellWindow::WidgetListenerDelegate::SizeModeChanged(nsSizeMode aSizeMode) +{ + RefPtr<nsWebShellWindow> holder = mWebShellWindow; + holder->SizeModeChanged(aSizeMode); +} + +void +nsWebShellWindow::WidgetListenerDelegate::UIResolutionChanged() +{ + RefPtr<nsWebShellWindow> holder = mWebShellWindow; + holder->UIResolutionChanged(); +} + +void +nsWebShellWindow::WidgetListenerDelegate::FullscreenChanged(bool aInFullscreen) +{ + RefPtr<nsWebShellWindow> holder = mWebShellWindow; + holder->FullscreenChanged(aInFullscreen); +} + +void +nsWebShellWindow::WidgetListenerDelegate::OSToolbarButtonPressed() +{ + RefPtr<nsWebShellWindow> holder = mWebShellWindow; + holder->OSToolbarButtonPressed(); +} + +bool +nsWebShellWindow::WidgetListenerDelegate::ZLevelChanged( + bool aImmediate, nsWindowZ *aPlacement, nsIWidget* aRequestBelow, + nsIWidget** aActualBelow) +{ + RefPtr<nsWebShellWindow> holder = mWebShellWindow; + return holder->ZLevelChanged(aImmediate, + aPlacement, + aRequestBelow, + aActualBelow); +} + +void +nsWebShellWindow::WidgetListenerDelegate::WindowActivated() +{ + RefPtr<nsWebShellWindow> holder = mWebShellWindow; + holder->WindowActivated(); +} + +void +nsWebShellWindow::WidgetListenerDelegate::WindowDeactivated() +{ + RefPtr<nsWebShellWindow> holder = mWebShellWindow; + holder->WindowDeactivated(); +} diff --git a/xpfe/appshell/nsWebShellWindow.h b/xpfe/appshell/nsWebShellWindow.h new file mode 100644 index 000000000..e2b796161 --- /dev/null +++ b/xpfe/appshell/nsWebShellWindow.h @@ -0,0 +1,116 @@ +/* -*- 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 nsWebShellWindow_h__ +#define nsWebShellWindow_h__ + +#include "mozilla/Mutex.h" +#include "nsIWebProgressListener.h" +#include "nsITimer.h" +#include "nsCOMPtr.h" +#include "nsXULWindow.h" +#include "nsIWidgetListener.h" +#include "nsITabParent.h" + +/* Forward declarations.... */ +class nsIURI; + +struct nsWidgetInitData; + +namespace mozilla { +class WebShellWindowTimerCallback; +} // namespace mozilla + +class nsWebShellWindow final : public nsXULWindow, + public nsIWebProgressListener +{ +public: + + // The implementation of non-refcounted nsIWidgetListener, which would hold a + // strong reference on stack before calling nsWebShellWindow + class WidgetListenerDelegate : public nsIWidgetListener + { + public: + explicit WidgetListenerDelegate(nsWebShellWindow* aWebShellWindow) + : mWebShellWindow(aWebShellWindow) {} + + virtual nsIXULWindow* GetXULWindow() override; + virtual nsIPresShell* GetPresShell() override; + virtual bool WindowMoved(nsIWidget* aWidget, int32_t x, int32_t y) override; + virtual bool WindowResized(nsIWidget* aWidget, int32_t aWidth, int32_t aHeight) override; + virtual bool RequestWindowClose(nsIWidget* aWidget) override; + virtual void SizeModeChanged(nsSizeMode sizeMode) override; + virtual void UIResolutionChanged() override; + virtual void FullscreenChanged(bool aInFullscreen) override; + virtual void OSToolbarButtonPressed() override; + virtual bool ZLevelChanged(bool aImmediate, + nsWindowZ *aPlacement, + nsIWidget* aRequestBelow, + nsIWidget** aActualBelow) override; + virtual void WindowActivated() override; + virtual void WindowDeactivated() override; + + private: + // The lifetime of WidgetListenerDelegate is bound to nsWebShellWindow so + // we just use a raw pointer here. + nsWebShellWindow* mWebShellWindow; + }; + + explicit nsWebShellWindow(uint32_t aChromeFlags); + + // nsISupports interface... + NS_DECL_ISUPPORTS_INHERITED + + // nsWebShellWindow methods... + nsresult Initialize(nsIXULWindow * aParent, nsIXULWindow * aOpener, + nsIURI* aUrl, + int32_t aInitialWidth, int32_t aInitialHeight, + bool aIsHiddenWindow, + nsITabParent *aOpeningTab, + mozIDOMWindowProxy *aOpenerWIndow, + nsWidgetInitData& widgetInitData); + + nsresult Toolbar(); + + // nsIWebProgressListener + NS_DECL_NSIWEBPROGRESSLISTENER + + // nsIBaseWindow + NS_IMETHOD Destroy() override; + + // nsIWidgetListener + nsIXULWindow* GetXULWindow() { return this; } + nsIPresShell* GetPresShell(); + bool WindowMoved(nsIWidget* aWidget, int32_t x, int32_t y); + bool WindowResized(nsIWidget* aWidget, int32_t aWidth, int32_t aHeight); + bool RequestWindowClose(nsIWidget* aWidget); + void SizeModeChanged(nsSizeMode sizeMode); + void UIResolutionChanged(); + void FullscreenChanged(bool aInFullscreen); + void OSToolbarButtonPressed(); + bool ZLevelChanged(bool aImmediate, nsWindowZ *aPlacement, + nsIWidget* aRequestBelow, nsIWidget** aActualBelow); + void WindowActivated(); + void WindowDeactivated(); + +protected: + friend class mozilla::WebShellWindowTimerCallback; + + virtual ~nsWebShellWindow(); + + void LoadContentAreas(); + bool ExecuteCloseHandler(); + void ConstrainToOpenerScreen(int32_t* aX, int32_t* aY); + + nsCOMPtr<nsITimer> mSPTimer; + mozilla::Mutex mSPTimerLock; + WidgetListenerDelegate mWidgetListenerDelegate; + + void SetPersistenceTimer(uint32_t aDirtyFlags); + void FirePersistenceTimer(); +}; + + +#endif /* nsWebShellWindow_h__ */ diff --git a/xpfe/appshell/nsWindowMediator.cpp b/xpfe/appshell/nsWindowMediator.cpp new file mode 100644 index 000000000..6d69bc764 --- /dev/null +++ b/xpfe/appshell/nsWindowMediator.cpp @@ -0,0 +1,846 @@ +/* -*- 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 "nsString.h" +#include "nsReadableUtils.h" +#include "nsUnicharUtils.h" +#include "nsTArray.h" +#include "nsIBaseWindow.h" +#include "nsIWidget.h" +#include "nsIDOMWindow.h" +#include "nsIObserverService.h" +#include "nsIServiceManager.h" +#include "nsISimpleEnumerator.h" +#include "nsAppShellWindowEnumerator.h" +#include "nsWindowMediator.h" +#include "nsIWindowMediatorListener.h" +#include "nsXPIDLString.h" +#include "nsGlobalWindow.h" + +#include "nsIDocShell.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIXULWindow.h" + +using namespace mozilla; + +static bool notifyOpenWindow(nsIWindowMediatorListener *aElement, void* aData); +static bool notifyCloseWindow(nsIWindowMediatorListener *aElement, void* aData); +static bool notifyWindowTitleChange(nsIWindowMediatorListener *aElement, void* aData); + +// for notifyWindowTitleChange +struct WindowTitleData { + nsIXULWindow* mWindow; + const char16_t *mTitle; +}; + +nsresult +nsWindowMediator::GetDOMWindow(nsIXULWindow* 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() : + mEnumeratorList(), 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(nsIXULWindow* inWindow) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + NS_ENSURE_STATE(mReady); + + 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); + + WindowTitleData winData = { inWindow, nullptr }; + mListeners.EnumerateForwards(notifyOpenWindow, &winData); + + if (mOldestWindow) + windowInfo->InsertAfter(mOldestWindow->mOlder, nullptr); + else + mOldestWindow = windowInfo; + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::UnregisterWindow(nsIXULWindow* inWindow) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + 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++; + } + + WindowTitleData winData = { inInfo->mWindow.get(), nullptr }; + mListeners.EnumerateForwards(notifyCloseWindow, &winData); + + // 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(nsIXULWindow *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); + NS_ENSURE_STATE(mReady); + + RefPtr<nsAppShellWindowEnumerator> enumerator = new nsASDOMWindowEarlyToLateEnumerator(inType, *this); + enumerator.forget(outEnumerator); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::GetXULWindowEnumerator(const char16_t* inType, nsISimpleEnumerator** outEnumerator) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(outEnumerator); + NS_ENSURE_STATE(mReady); + + RefPtr<nsAppShellWindowEnumerator> enumerator = new nsASXULWindowEarlyToLateEnumerator(inType, *this); + enumerator.forget(outEnumerator); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::GetZOrderDOMWindowEnumerator( + const char16_t *aWindowType, bool aFrontToBack, + nsISimpleEnumerator **_retval) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(_retval); + NS_ENSURE_STATE(mReady); + + RefPtr<nsAppShellWindowEnumerator> enumerator; + if (aFrontToBack) + enumerator = new nsASDOMWindowFrontToBackEnumerator(aWindowType, *this); + else + enumerator = new nsASDOMWindowBackToFrontEnumerator(aWindowType, *this); + + enumerator.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::GetZOrderXULWindowEnumerator( + const char16_t *aWindowType, bool aFrontToBack, + nsISimpleEnumerator **_retval) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(_retval); + NS_ENSURE_STATE(mReady); + + RefPtr<nsAppShellWindowEnumerator> enumerator; + if (aFrontToBack) + enumerator = new nsASXULWindowFrontToBackEnumerator(aWindowType, *this); + else + enumerator = new nsASXULWindowBackToFrontEnumerator(aWindowType, *this); + + enumerator.forget(_retval); + return NS_OK; +} + +int32_t +nsWindowMediator::AddEnumerator(nsAppShellWindowEnumerator * inEnumerator) +{ + return mEnumeratorList.AppendElement(inEnumerator) != nullptr; +} + +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::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<nsGlobalWindow> window = nsGlobalWindow::GetOuterWindowWithId(aWindowID); + nsCOMPtr<nsPIDOMWindowOuter> outer = window ? window->AsOuter() : nullptr; + outer.forget(aWindow); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::GetCurrentInnerWindowWithId(uint64_t aWindowID, + mozIDOMWindow** aWindow) +{ + RefPtr<nsGlobalWindow> window = nsGlobalWindow::GetInnerWindowWithId(aWindowID); + + // not found + if (!window) + return NS_OK; + + nsCOMPtr<nsPIDOMWindowInner> inner = window->AsInner(); + nsCOMPtr<nsPIDOMWindowOuter> outer = inner->GetOuterWindow(); + NS_ENSURE_TRUE(outer, NS_ERROR_UNEXPECTED); + + // outer is already using another inner, so it's same as not found + if (outer->GetCurrentInnerWindow() != inner) + return NS_OK; + + inner.forget(aWindow); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::UpdateWindowTimeStamp(nsIXULWindow* inWindow) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + 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; +} + +NS_IMETHODIMP +nsWindowMediator::UpdateWindowTitle(nsIXULWindow* inWindow, + const char16_t* inTitle) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + NS_ENSURE_STATE(mReady); + if (GetInfoFor(inWindow)) { + WindowTitleData winData = { inWindow, inTitle }; + mListeners.EnumerateForwards(notifyWindowTitleChange, &winData); + } + + return NS_OK; +} + +/* 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( + nsIXULWindow *inWindow, + uint32_t inPosition, + nsIWidget *inBelow, + uint32_t *outPosition, + nsIWidget **outBelow, + bool *outAltered) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + NS_ENSURE_ARG_POINTER(outBelow); + 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; + nsIXULWindow *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( + nsIXULWindow *inWindow, + uint32_t inPosition, + nsIXULWindow *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; + + 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(nsIXULWindow *aWindow, uint32_t *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nsIXULWindow::normalZ; + nsWindowInfo *info = GetInfoFor(aWindow); + if (info) { + *_retval = info->mZLevel; + } else { + NS_WARNING("getting z level of unregistered window"); + // this goes off during window destruction + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::SetZLevel(nsIXULWindow *aWindow, uint32_t aZLevel) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + 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_44, + nsIWindowMediator, + nsIObserver, + nsISupportsWeakReference) + +NS_IMETHODIMP +nsWindowMediator::AddListener(nsIWindowMediatorListener* aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + + mListeners.AppendObject(aListener); + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowMediator::RemoveListener(nsIWindowMediatorListener* aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + + mListeners.RemoveObject(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; +} + +bool +notifyOpenWindow(nsIWindowMediatorListener *aListener, void* aData) +{ + WindowTitleData* winData = static_cast<WindowTitleData*>(aData); + aListener->OnOpenWindow(winData->mWindow); + + return true; +} + +bool +notifyCloseWindow(nsIWindowMediatorListener *aListener, void* aData) +{ + WindowTitleData* winData = static_cast<WindowTitleData*>(aData); + aListener->OnCloseWindow(winData->mWindow); + + return true; +} + +bool +notifyWindowTitleChange(nsIWindowMediatorListener *aListener, void* aData) +{ + WindowTitleData* titleData = reinterpret_cast<WindowTitleData*>(aData); + aListener->OnWindowTitleChange(titleData->mWindow, titleData->mTitle); + + return true; +} diff --git a/xpfe/appshell/nsWindowMediator.h b/xpfe/appshell/nsWindowMediator.h new file mode 100644 index 000000000..a0eaab56d --- /dev/null +++ b/xpfe/appshell/nsWindowMediator.h @@ -0,0 +1,78 @@ +/* -*- 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 "nsXPIDLString.h" +#include "nsWeakReference.h" +#include "nsCOMArray.h" + +class nsAppShellWindowEnumerator; +class nsASXULWindowEarlyToLateEnumerator; +class nsASDOMWindowEarlyToLateEnumerator; +class nsASDOMWindowFrontToBackEnumerator; +class nsASXULWindowFrontToBackEnumerator; +class nsASDOMWindowBackToFrontEnumerator; +class nsASXULWindowBackToFrontEnumerator; +class nsIWindowMediatorListener; +struct nsWindowInfo; + +class nsWindowMediator : + public nsIWindowMediator_44, + public nsIObserver, + public nsSupportsWeakReference +{ +friend class nsAppShellWindowEnumerator; +friend class nsASXULWindowEarlyToLateEnumerator; +friend class nsASDOMWindowEarlyToLateEnumerator; +friend class nsASDOMWindowFrontToBackEnumerator; +friend class nsASXULWindowFrontToBackEnumerator; +friend class nsASDOMWindowBackToFrontEnumerator; +friend class nsASXULWindowBackToFrontEnumerator; + +protected: + virtual ~nsWindowMediator(); + +public: + nsWindowMediator(); + + nsresult Init(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIWINDOWMEDIATOR + NS_DECL_NSIWINDOWMEDIATOR_44 + NS_DECL_NSIOBSERVER + + static nsresult GetDOMWindow(nsIXULWindow* inWindow, + nsCOMPtr<nsPIDOMWindowOuter>& outDOMWindow); + +private: + int32_t AddEnumerator(nsAppShellWindowEnumerator* inEnumerator); + int32_t RemoveEnumerator(nsAppShellWindowEnumerator* inEnumerator); + nsWindowInfo* MostRecentWindowInfo(const char16_t* inType, + bool aSkipPrivateBrowsingOrClosed = false); + + nsresult UnregisterWindow(nsWindowInfo *inInfo); + nsWindowInfo *GetInfoFor(nsIXULWindow *aWindow); + nsWindowInfo *GetInfoFor(nsIWidget *aWindow); + void SortZOrderFrontToBack(); + void SortZOrderBackToFront(); + + nsTArray<nsAppShellWindowEnumerator*> mEnumeratorList; + nsWindowInfo *mOldestWindow; + nsWindowInfo *mTopmostWindow; + int32_t mTimeStamp; + bool mSortingZOrder; + bool mReady; + + nsCOMArray<nsIWindowMediatorListener> mListeners; +}; + +#endif diff --git a/xpfe/appshell/nsXULWindow.cpp b/xpfe/appshell/nsXULWindow.cpp new file mode 100644 index 000000000..8f2d9fde5 --- /dev/null +++ b/xpfe/appshell/nsXULWindow.cpp @@ -0,0 +1,2334 @@ +/* -*- 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 "mozilla/MathAlgorithms.h" + +// Local includes +#include "nsXULWindow.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/Sprintf.h" + +//Interfaces needed to be included +#include "nsIAppShell.h" +#include "nsIAppShellService.h" +#include "nsIServiceManager.h" +#include "nsIContentViewer.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIDOMXULDocument.h" +#include "nsIDOMElement.h" +#include "nsIDOMXULElement.h" +#include "nsPIDOMWindow.h" +#include "nsIDOMScreen.h" +#include "nsIEmbeddingSiteWindow.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIIOService.h" +#include "nsILoadContext.h" +#include "nsIObserverService.h" +#include "nsIWindowMediator.h" +#include "nsIScreenManager.h" +#include "nsIScreen.h" +#include "nsIScrollable.h" +#include "nsIScriptSecurityManager.h" +#include "nsIWindowWatcher.h" +#include "nsIURI.h" +#include "nsIDOMCSSStyleDeclaration.h" +#include "nsAppShellCID.h" +#include "nsReadableUtils.h" +#include "nsStyleConsts.h" +#include "nsPresContext.h" +#include "nsContentUtils.h" +#include "nsWebShellWindow.h" // get rid of this one, too... +#include "nsGlobalWindow.h" + +#include "prenv.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/dom/BarProps.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/TabParent.h" + +using namespace mozilla; +using dom::AutoNoJSAPI; + +#define SIZEMODE_NORMAL NS_LITERAL_STRING("normal") +#define SIZEMODE_MAXIMIZED NS_LITERAL_STRING("maximized") +#define SIZEMODE_MINIMIZED NS_LITERAL_STRING("minimized") +#define SIZEMODE_FULLSCREEN NS_LITERAL_STRING("fullscreen") + +#define WINDOWTYPE_ATTRIBUTE NS_LITERAL_STRING("windowtype") + +#define PERSIST_ATTRIBUTE NS_LITERAL_STRING("persist") +#define SCREENX_ATTRIBUTE NS_LITERAL_STRING("screenX") +#define SCREENY_ATTRIBUTE NS_LITERAL_STRING("screenY") +#define WIDTH_ATTRIBUTE NS_LITERAL_STRING("width") +#define HEIGHT_ATTRIBUTE NS_LITERAL_STRING("height") +#define MODE_ATTRIBUTE NS_LITERAL_STRING("sizemode") +#define ZLEVEL_ATTRIBUTE NS_LITERAL_STRING("zlevel") + + +//***************************************************************************** +//*** nsXULWindow: Object Management +//***************************************************************************** + +nsXULWindow::nsXULWindow(uint32_t aChromeFlags) + : mChromeTreeOwner(nullptr), + mContentTreeOwner(nullptr), + mPrimaryContentTreeOwner(nullptr), + mModalStatus(NS_OK), + mContinueModalLoop(false), + mDebuting(false), + mChromeLoaded(false), + mShowAfterLoad(false), + mIntrinsicallySized(false), + mCenterAfterLoad(false), + mIsHiddenWindow(false), + mLockedUntilChromeLoad(false), + mIgnoreXULSize(false), + mIgnoreXULPosition(false), + mChromeFlagsFrozen(false), + mIgnoreXULSizeMode(false), + mDestroying(false), + mRegistered(false), + mContextFlags(0), + mPersistentAttributesDirty(0), + mPersistentAttributesMask(0), + mChromeFlags(aChromeFlags) +{ +} + +nsXULWindow::~nsXULWindow() +{ + Destroy(); +} + +//***************************************************************************** +// nsXULWindow::nsISupports +//***************************************************************************** + +NS_IMPL_ADDREF(nsXULWindow) +NS_IMPL_RELEASE(nsXULWindow) + +NS_INTERFACE_MAP_BEGIN(nsXULWindow) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXULWindow) + NS_INTERFACE_MAP_ENTRY(nsIXULWindow) + NS_INTERFACE_MAP_ENTRY(nsIBaseWindow) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + if (aIID.Equals(NS_GET_IID(nsXULWindow))) + foundInterface = reinterpret_cast<nsISupports*>(this); + else +NS_INTERFACE_MAP_END + +//***************************************************************************** +// nsXULWindow::nsIIntefaceRequestor +//***************************************************************************** + +NS_IMETHODIMP nsXULWindow::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(nsIDOMWindowInternal))) { + nsCOMPtr<mozIDOMWindowProxy> window = nullptr; + rv = GetWindowDOMWindow(getter_AddRefs(window)); + nsCOMPtr<nsIDOMWindowInternal> domWindowInternal = do_QueryInterface(window); + domWindowInternal.forget(aSink); + return rv; + } + if (aIID.Equals(NS_GET_IID(nsIWebBrowserChrome)) && + NS_SUCCEEDED(EnsureContentTreeOwner()) && + NS_SUCCEEDED(mContentTreeOwner->QueryInterface(aIID, aSink))) + return NS_OK; + + if (aIID.Equals(NS_GET_IID(nsIEmbeddingSiteWindow)) && + NS_SUCCEEDED(EnsureContentTreeOwner()) && + NS_SUCCEEDED(mContentTreeOwner->QueryInterface(aIID, aSink))) + return NS_OK; + + return QueryInterface(aIID, aSink); +} + +//***************************************************************************** +// nsXULWindow::nsIXULWindow +//***************************************************************************** + +NS_IMETHODIMP nsXULWindow::GetDocShell(nsIDocShell** aDocShell) +{ + NS_ENSURE_ARG_POINTER(aDocShell); + + *aDocShell = mDocShell; + NS_IF_ADDREF(*aDocShell); + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::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 nsXULWindow::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 > nsIXULWindow::normalZ && mWindow) { + nsSizeMode sizeMode = mWindow->SizeMode(); + if (sizeMode == nsSizeMode_Maximized || sizeMode == nsSizeMode_Fullscreen) { + return NS_ERROR_FAILURE; + } + } + + // do it + mediator->SetZLevel(this, aLevel); + PersistentAttributesDirty(PAD_MISC); + SavePersistentAttributes(); + + nsCOMPtr<nsIContentViewer> cv; + mDocShell->GetContentViewer(getter_AddRefs(cv)); + if (cv) { + nsCOMPtr<nsIDocument> doc = cv->GetDocument(); + if (doc) { + ErrorResult rv; + RefPtr<dom::Event> event = + doc->CreateEvent(NS_LITERAL_STRING("Events"),rv); + if (event) { + event->InitEvent(NS_LITERAL_STRING("windowZLevel"), true, false); + + event->SetTrusted(true); + + bool defaultActionEnabled; + doc->DispatchEvent(event, &defaultActionEnabled); + } + } + } + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::GetContextFlags(uint32_t *aContextFlags) +{ + NS_ENSURE_ARG_POINTER(aContextFlags); + *aContextFlags = mContextFlags; + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::SetContextFlags(uint32_t aContextFlags) +{ + mContextFlags = aContextFlags; + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::GetChromeFlags(uint32_t *aChromeFlags) +{ + NS_ENSURE_ARG_POINTER(aChromeFlags); + *aChromeFlags = mChromeFlags; + /* mChromeFlags is kept up to date, except for scrollbar visibility. + That can be changed directly by the content DOM window, which + doesn't know to update the chrome window. So that we must check + separately. */ + + // however, it's pointless to ask if the window isn't set up yet + if (!mChromeLoaded) + return NS_OK; + + if (GetContentScrollbarVisibility()) + *aChromeFlags |= nsIWebBrowserChrome::CHROME_SCROLLBARS; + else + *aChromeFlags &= ~nsIWebBrowserChrome::CHROME_SCROLLBARS; + + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::SetChromeFlags(uint32_t aChromeFlags) +{ + NS_ASSERTION(!mChromeFlagsFrozen, + "SetChromeFlags() after AssumeChromeFlagsAreFrozen()!"); + + mChromeFlags = aChromeFlags; + if (mChromeLoaded) + NS_ENSURE_SUCCESS(ApplyChromeFlags(), NS_ERROR_FAILURE); + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::AssumeChromeFlagsAreFrozen() +{ + mChromeFlagsFrozen = true; + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::SetIntrinsicallySized(bool aIntrinsicallySized) +{ + mIntrinsicallySized = aIntrinsicallySized; + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::GetIntrinsicallySized(bool* aIntrinsicallySized) +{ + NS_ENSURE_ARG_POINTER(aIntrinsicallySized); + + *aIntrinsicallySized = mIntrinsicallySized; + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::GetPrimaryContentShell(nsIDocShellTreeItem** + aDocShellTreeItem) +{ + NS_ENSURE_ARG_POINTER(aDocShellTreeItem); + NS_IF_ADDREF(*aDocShellTreeItem = mPrimaryContentShell); + return NS_OK; +} + +NS_IMETHODIMP +nsXULWindow::TabParentAdded(nsITabParent* aTab, bool aPrimary) +{ + if (aPrimary) { + mPrimaryTabParent = aTab; + mPrimaryContentShell = nullptr; + } else if (mPrimaryTabParent == aTab) { + mPrimaryTabParent = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXULWindow::TabParentRemoved(nsITabParent* aTab) +{ + if (aTab == mPrimaryTabParent) { + mPrimaryTabParent = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXULWindow::GetPrimaryTabParent(nsITabParent** aTab) +{ + nsCOMPtr<nsITabParent> tab = mPrimaryTabParent; + tab.forget(aTab); + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::GetContentShellById(const char16_t* aID, + nsIDocShellTreeItem** aDocShellTreeItem) +{ + NS_ENSURE_ARG_POINTER(aDocShellTreeItem); + *aDocShellTreeItem = nullptr; + + uint32_t count = mContentShells.Length(); + for (uint32_t i = 0; i < count; i++) { + nsContentShellInfo* shellInfo = mContentShells.ElementAt(i); + if (shellInfo->id.Equals(aID)) { + *aDocShellTreeItem = nullptr; + if (shellInfo->child) + CallQueryReferent(shellInfo->child.get(), aDocShellTreeItem); + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsXULWindow::AddChildWindow(nsIXULWindow *aChild) +{ + // we're not really keeping track of this right now + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::RemoveChildWindow(nsIXULWindow *aChild) +{ + // we're not really keeping track of this right now + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::ShowModal() +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + + // Store locally so it doesn't die on us + nsCOMPtr<nsIWidget> window = mWindow; + nsCOMPtr<nsIXULWindow> tempRef = this; + + window->SetModal(true); + mContinueModalLoop = true; + EnableParent(false); + + { + AutoNoJSAPI nojsapi; + nsIThread *thread = NS_GetCurrentThread(); + while (mContinueModalLoop) { + if (!NS_ProcessNextEvent(thread)) + break; + } + } + + 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; +} + +//***************************************************************************** +// nsXULWindow::nsIBaseWindow +//***************************************************************************** + +NS_IMETHODIMP nsXULWindow::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 nsXULWindow::Create() +{ + //XXX First Check In + NS_ASSERTION(false, "Not Yet Implemented"); + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::Destroy() +{ + 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<nsIXULWindow*>(this)); + + nsCOMPtr<nsIXULWindow> parentWindow(do_QueryReferent(mParentWindow)); + if (parentWindow) + parentWindow->RemoveChildWindow(this); + + // let's make sure the window doesn't get deleted out from under us + // while we are trying to close....this can happen if the docshell + // we close ends up being the last owning reference to this xulwindow + + // XXXTAB This shouldn't be an issue anymore because the ownership model + // only goes in one direction. When webshell container is fully removed + // try removing this... + + nsCOMPtr<nsIXULWindow> placeHolder = 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()) { + nsCOMPtr<nsIBaseWindow> baseHiddenWindow; + if (appShell) { + nsCOMPtr<nsIXULWindow> hiddenWindow; + appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow)); + if (hiddenWindow) + baseHiddenWindow = do_GetInterface(hiddenWindow); + } + // somebody screwed up somewhere. hiddenwindow shouldn't be anybody's + // parent. still, when it happens, skip activating it. + if (baseHiddenWindow != parent) { + nsCOMPtr<nsIWidget> parentWidget; + parent->GetMainWidget(getter_AddRefs(parentWidget)); + if (parentWidget) + parentWidget->PlaceBehind(eZPlacementTop, 0, true); + } + } + } +#endif + + mDOMWindow = nullptr; + if (mDocShell) { + nsCOMPtr<nsIBaseWindow> shellAsWin(do_QueryInterface(mDocShell)); + shellAsWin->Destroy(); + mDocShell = nullptr; // this can cause reentrancy of this function + } + + // Remove our ref on the content shells + uint32_t count = mContentShells.Length(); + for (uint32_t i = 0; i < count; i++) { + nsContentShellInfo* shellInfo = mContentShells.ElementAt(i); + delete shellInfo; + } + mContentShells.Clear(); + mPrimaryContentShell = nullptr; + + if (mContentTreeOwner) { + mContentTreeOwner->XULWindow(nullptr); + NS_RELEASE(mContentTreeOwner); + } + if (mPrimaryContentTreeOwner) { + mPrimaryContentTreeOwner->XULWindow(nullptr); + NS_RELEASE(mPrimaryContentTreeOwner); + } + if (mChromeTreeOwner) { + mChromeTreeOwner->XULWindow(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 nsXULWindow::GetDevicePixelsPerDesktopPixel(double *aScale) +{ + *aScale = mWindow ? mWindow->GetDesktopToDeviceScale().scale : 1.0; + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::GetUnscaledDevicePixelsPerCSSPixel(double *aScale) +{ + *aScale = mWindow ? mWindow->GetDefaultScale().scale : 1.0; + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::SetPositionDesktopPix(int32_t aX, int32_t aY) +{ + nsresult rv = mWindow->Move(aX, aY); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + if (!mChromeLoaded) { + // If we're called before the chrome is loaded someone obviously wants this + // window at this position. We don't persist this one-time position. + mIgnoreXULPosition = true; + return NS_OK; + } + PersistentAttributesDirty(PAD_POSITION); + SavePersistentAttributes(); + return NS_OK; +} + +// 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 nsXULWindow::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. + DesktopToLayoutDeviceScale currScale = mWindow->GetDesktopToDeviceScale(); + DesktopPoint pos = LayoutDeviceIntPoint(aX, aY) / currScale; + return SetPositionDesktopPix(pos.x, pos.y); +} + +NS_IMETHODIMP nsXULWindow::GetPosition(int32_t* aX, int32_t* aY) +{ + return GetPositionAndSize(aX, aY, nullptr, nullptr); +} + +NS_IMETHODIMP nsXULWindow::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. */ + mWindow->SetSizeMode(nsSizeMode_Normal); + + mIntrinsicallySized = false; + + DesktopToLayoutDeviceScale scale = mWindow->GetDesktopToDeviceScale(); + DesktopSize size = LayoutDeviceIntSize(aCX, aCY) / scale; + nsresult rv = mWindow->Resize(size.width, size.height, aRepaint); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + 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 + // size. + mIgnoreXULSize = true; + mIgnoreXULSizeMode = true; + return NS_OK; + } + PersistentAttributesDirty(PAD_SIZE); + SavePersistentAttributes(); + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::GetSize(int32_t* aCX, int32_t* aCY) +{ + return GetPositionAndSize(nullptr, nullptr, aCX, aCY); +} + +NS_IMETHODIMP nsXULWindow::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. */ + mWindow->SetSizeMode(nsSizeMode_Normal); + + mIntrinsicallySized = false; + + DesktopToLayoutDeviceScale scale = mWindow->GetDesktopToDeviceScale(); + DesktopRect rect = LayoutDeviceIntRect(aX, aY, aCX, aCY) / scale; + nsresult rv = mWindow->Resize(rect.x, rect.y, rect.width, rect.height, + !!(aFlags & nsIBaseWindow::eRepaint)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + if (!mChromeLoaded) { + // If we're called before the chrome is loaded someone obviously wants this + // window at this size and position. We don't persist this one-time setting. + mIgnoreXULPosition = true; + mIgnoreXULSize = true; + mIgnoreXULSizeMode = true; + return NS_OK; + } + PersistentAttributesDirty(PAD_POSITION | PAD_SIZE); + SavePersistentAttributes(); + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::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 nsXULWindow::Center(nsIXULWindow *aRelative, bool aScreen, bool aAlert) +{ + int32_t left, top, width, height, + ourWidth, ourHeight; + 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, &result)); + if (base) { + // get window rect + result = base->GetPositionAndSize(&left, &top, &width, &height); + if (NS_SUCCEEDED(result)) { + double scale; + if (NS_SUCCEEDED(base->GetDevicePixelsPerDesktopPixel(&scale))) { + left = NSToIntRound(left / scale); + top = NSToIntRound(top / scale); + width = NSToIntRound(width / scale); + height = NSToIntRound(height / scale); + } + // if centering on screen, convert that to the corresponding screen + if (aScreen) + screenmgr->ScreenForRect(left, top, width, height, getter_AddRefs(screen)); + else + windowCoordinates = true; + } else { + // something's wrong with the reference window. + // fall back to the primary screen + aRelative = 0; + aScreen = true; + } + } + } + if (!aRelative) { + if (!mOpenerScreenRect.IsEmpty()) { + // FIXME - check if these are device or display pixels + screenmgr->ScreenForRect(mOpenerScreenRect.x, mOpenerScreenRect.y, + mOpenerScreenRect.width, mOpenerScreenRect.height, + getter_AddRefs(screen)); + } else { + screenmgr->GetPrimaryScreen(getter_AddRefs(screen)); + } + } + + if (aScreen && screen) { + screen->GetAvailRectDisplayPix(&left, &top, &width, &height); + screenCoordinates = true; + } + + if (screenCoordinates || windowCoordinates) { + NS_ASSERTION(mWindow, "what, no window?"); + double scale = mWindow->GetDesktopToDeviceScale().scale; + GetSize(&ourWidth, &ourHeight); + int32_t scaledWidth, scaledHeight; + scaledWidth = NSToIntRound(ourWidth / scale); + scaledHeight = NSToIntRound(ourHeight / scale); + left += (width - scaledWidth) / 2; + top += (height - scaledHeight) / (aAlert ? 3 : 2); + if (windowCoordinates) { + mWindow->ConstrainPosition(false, &left, &top); + } + SetPosition(left * scale, top * scale); + + // If moving the window caused it to change size, + // re-do the centering. + int32_t newWidth, newHeight; + GetSize(&newWidth, &newHeight); + if (newWidth != ourWidth || newHeight != ourHeight) { + return Center(aRelative, aScreen, aAlert); + } + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsXULWindow::Repaint(bool aForce) +{ + //XXX First Check In + NS_ASSERTION(false, "Not Yet Implemented"); + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::GetParentWidget(nsIWidget** aParentWidget) +{ + NS_ENSURE_ARG_POINTER(aParentWidget); + NS_ENSURE_STATE(mWindow); + + NS_IF_ADDREF(*aParentWidget = mWindow->GetParent()); + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::SetParentWidget(nsIWidget* aParentWidget) +{ + //XXX First Check In + NS_ASSERTION(false, "Not Yet Implemented"); + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::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 nsXULWindow::SetParentNativeWindow(nativeWindow aParentNativeWindow) +{ + //XXX First Check In + NS_ASSERTION(false, "Not Yet Implemented"); + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::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 nsXULWindow::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 nsXULWindow::SetVisibility(bool aVisibility) +{ + if (!mChromeLoaded) { + mShowAfterLoad = aVisibility; + return NS_OK; + } + + if (mDebuting) { + return NS_OK; + } + mDebuting = true; // (Show / Focus is recursive) + + //XXXTAB Do we really need to show docshell and the window? Isn't + // the window good enough? + nsCOMPtr<nsIBaseWindow> shellAsWin(do_QueryInterface(mDocShell)); + shellAsWin->SetVisibility(aVisibility); + // Store locally so it doesn't die on us. 'Show' can result in the window + // being closed with nsXULWindow::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<nsIXULWindow*>(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(nullptr, "xul-window-visible", nullptr); + } + + mDebuting = false; + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::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 nsXULWindow::SetEnabled(bool aEnable) +{ + if (mWindow) { + mWindow->Enable(aEnable); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsXULWindow::GetMainWidget(nsIWidget** aMainWidget) +{ + NS_ENSURE_ARG_POINTER(aMainWidget); + + *aMainWidget = mWindow; + NS_IF_ADDREF(*aMainWidget); + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::SetFocus() +{ + //XXX First Check In + NS_ASSERTION(false, "Not Yet Implemented"); + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::GetTitle(char16_t** aTitle) +{ + NS_ENSURE_ARG_POINTER(aTitle); + + *aTitle = ToNewUnicode(mTitle); + if (!*aTitle) + return NS_ERROR_OUT_OF_MEMORY; + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::SetTitle(const char16_t* aTitle) +{ + NS_ENSURE_STATE(mWindow); + mTitle.Assign(aTitle); + mTitle.StripChars("\n\r"); + NS_ENSURE_SUCCESS(mWindow->SetTitle(mTitle), NS_ERROR_FAILURE); + + // Tell the window mediator that a title has changed + nsCOMPtr<nsIWindowMediator> windowMediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (!windowMediator) + return NS_OK; + + windowMediator->UpdateWindowTitle(static_cast<nsIXULWindow*>(this), aTitle); + + return NS_OK; +} + + +//***************************************************************************** +// nsXULWindow: Helpers +//***************************************************************************** + +NS_IMETHODIMP nsXULWindow::EnsureChromeTreeOwner() +{ + if (mChromeTreeOwner) + return NS_OK; + + mChromeTreeOwner = new nsChromeTreeOwner(); + NS_ADDREF(mChromeTreeOwner); + mChromeTreeOwner->XULWindow(this); + + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::EnsureContentTreeOwner() +{ + if (mContentTreeOwner) + return NS_OK; + + mContentTreeOwner = new nsContentTreeOwner(false); + NS_ADDREF(mContentTreeOwner); + mContentTreeOwner->XULWindow(this); + + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::EnsurePrimaryContentTreeOwner() +{ + if (mPrimaryContentTreeOwner) + return NS_OK; + + mPrimaryContentTreeOwner = new nsContentTreeOwner(true); + NS_ADDREF(mPrimaryContentTreeOwner); + mPrimaryContentTreeOwner->XULWindow(this); + + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::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 nsXULWindow::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; +} + +void nsXULWindow::OnChromeLoaded() +{ + nsresult rv = EnsureContentTreeOwner(); + + if (NS_SUCCEEDED(rv)) { + mChromeLoaded = true; + ApplyChromeFlags(); + SyncAttributesToWidget(); + + int32_t specWidth = -1, specHeight = -1; + bool gotSize = false; + + if (!mIgnoreXULSize) { + gotSize = LoadSizeFromXUL(specWidth, specHeight); + } + + bool positionSet = !mIgnoreXULPosition; + nsCOMPtr<nsIXULWindow> 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 (mIntrinsicallySized) { + // (if LoadSizeFromXUL set the size, mIntrinsicallySized will be false) + nsCOMPtr<nsIContentViewer> cv; + mDocShell->GetContentViewer(getter_AddRefs(cv)); + if (cv) { + nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = do_QueryInterface(mDocShell); + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + docShellAsItem->GetTreeOwner(getter_AddRefs(treeOwner)); + if (treeOwner) { + // GetContentSize can fail, so initialise |width| and |height| to be + // on the safe side. + int32_t width = 0, height = 0; + if (NS_SUCCEEDED(cv->GetContentSize(&width, &height))) { + treeOwner->SizeShellTo(docShellAsItem, width, height); + // Update specified size for the final LoadPositionFromXUL call. + specWidth = width; + specHeight = height; + } + } + } + } + + // 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); + } + + LoadMiscPersistentAttributesFromXUL(); + + if (mCenterAfterLoad && !positionSet) { + Center(parentWindow, parentWindow ? false : true, false); + } + + if (mShowAfterLoad) { + SetVisibility(true); + // At this point the window may have been closed during Show(), so + // nsXULWindow::Destroy may already have been called. Take care! + } + } + mPersistentAttributesMask |= PAD_POSITION | PAD_SIZE | PAD_MISC; +} + +// 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 nsXULWindow::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; + + nsCOMPtr<dom::Element> windowElement = GetWindowDOMElement(); + NS_ENSURE_TRUE(windowElement, false); + + int32_t currX = 0; + int32_t currY = 0; + int32_t currWidth = 0; + int32_t currHeight = 0; + nsresult errorCode; + int32_t temp; + + GetPositionAndSize(&currX, &currY, &currWidth, &currHeight); + + // Convert to global display pixels for consistent window management across + // screens with diverse resolutions + double devToDesktopScale = 1.0 / mWindow->GetDesktopToDeviceScale().scale; + currX = NSToIntRound(currX * devToDesktopScale); + currY = NSToIntRound(currY * devToDesktopScale); + + // For size, use specified value if > 0, else current value + double devToCSSScale = 1.0 / mWindow->GetDefaultScale().scale; + int32_t cssWidth = + aSpecWidth > 0 ? aSpecWidth : NSToIntRound(currWidth * devToCSSScale); + int32_t cssHeight = + aSpecHeight > 0 ? aSpecHeight : NSToIntRound(currHeight * devToCSSScale); + + // Obtain the position information from the <xul:window> element. + int32_t specX = currX; + int32_t specY = currY; + nsAutoString posString; + + windowElement->GetAttribute(SCREENX_ATTRIBUTE, posString); + temp = posString.ToInteger(&errorCode); + if (NS_SUCCEEDED(errorCode)) { + specX = temp; + gotPosition = true; + } + windowElement->GetAttribute(SCREENY_ATTRIBUTE, posString); + temp = posString.ToInteger(&errorCode); + if (NS_SUCCEEDED(errorCode)) { + specY = temp; + gotPosition = true; + } + + if (gotPosition) { + // our position will be 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))) { + double scale; + if (NS_SUCCEEDED(parent->GetDevicePixelsPerDesktopPixel(&scale))) { + parentX = NSToIntRound(parentX / scale); + parentY = NSToIntRound(parentY / scale); + } + specX += parentX; + specY += parentY; + } + } + else { + StaggerPosition(specX, specY, cssWidth, cssHeight); + } + } + mWindow->ConstrainPosition(false, &specX, &specY); + if (specX != currX || specY != currY) { + SetPositionDesktopPix(specX, specY); + } + + return gotPosition; +} + +bool +nsXULWindow::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); + + nsresult errorCode; + int32_t temp; + + // Obtain the sizing information from the <xul:window> element. + aSpecWidth = 100; + aSpecHeight = 100; + nsAutoString sizeString; + + windowElement->GetAttribute(WIDTH_ATTRIBUTE, sizeString); + temp = sizeString.ToInteger(&errorCode); + if (NS_SUCCEEDED(errorCode) && temp > 0) { + aSpecWidth = std::max(temp, 100); + gotSize = true; + } + windowElement->GetAttribute(HEIGHT_ATTRIBUTE, sizeString); + temp = sizeString.ToInteger(&errorCode); + if (NS_SUCCEEDED(errorCode) && temp > 0) { + aSpecHeight = std::max(temp, 100); + gotSize = true; + } + + return gotSize; +} + +void +nsXULWindow::SetSpecifiedSize(int32_t aSpecWidth, int32_t aSpecHeight) +{ + // constrain to screen size + nsCOMPtr<mozIDOMWindowProxy> domWindow; + GetWindowDOMWindow(getter_AddRefs(domWindow)); + if (domWindow) { + auto* window = nsPIDOMWindowOuter::From(domWindow); + nsCOMPtr<nsIDOMScreen> screen = window->GetScreen(); + if (screen) { + int32_t screenWidth; + int32_t screenHeight; + screen->GetAvailWidth(&screenWidth); // CSS pixels + screen->GetAvailHeight(&screenHeight); + if (aSpecWidth > screenWidth) { + aSpecWidth = screenWidth; + } + if (aSpecHeight > screenHeight) { + aSpecHeight = screenHeight; + } + } + } + + NS_ASSERTION(mWindow, "we expected to have a window already"); + + int32_t currWidth = 0; + int32_t currHeight = 0; + GetSize(&currWidth, &currHeight); // returns device pixels + + // convert specified values to device pixels, and resize if needed + double cssToDevPx = mWindow ? mWindow->GetDefaultScale().scale : 1.0; + aSpecWidth = NSToIntRound(aSpecWidth * cssToDevPx); + aSpecHeight = NSToIntRound(aSpecHeight * cssToDevPx); + mIntrinsicallySized = false; + if (aSpecWidth != currWidth || aSpecHeight != currHeight) { + SetSize(aSpecWidth, aSpecHeight, 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 nsXULWindow::LoadMiscPersistentAttributesFromXUL() +{ + 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; + + // sizemode + windowElement->GetAttribute(MODE_ATTRIBUTE, stateString); + nsSizeMode sizeMode = nsSizeMode_Normal; + /* ignore request to minimize, to not confuse novices + if (stateString.Equals(SIZEMODE_MINIMIZED)) + sizeMode = nsSizeMode_Minimized; + */ + if (!mIgnoreXULSizeMode && + (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 we are told to ignore the size mode attribute update the + // document so the attribute and window are in sync. + if (mIgnoreXULSizeMode) { + nsAutoString sizeString; + if (sizeMode == nsSizeMode_Maximized) + sizeString.Assign(SIZEMODE_MAXIMIZED); + else if (sizeMode == nsSizeMode_Fullscreen) + sizeString.Assign(SIZEMODE_FULLSCREEN); + else if (sizeMode == nsSizeMode_Normal) + sizeString.Assign(SIZEMODE_NORMAL); + if (!sizeString.IsEmpty()) { + ErrorResult rv; + windowElement->SetAttribute(MODE_ATTRIBUTE, sizeString, rv); + } + } + + if (sizeMode == nsSizeMode_Fullscreen) { + nsCOMPtr<mozIDOMWindowProxy> ourWindow; + GetWindowDOMWindow(getter_AddRefs(ourWindow)); + auto* piWindow = nsPIDOMWindowOuter::From(ourWindow); + piWindow->SetFullScreen(true); + } else { + mWindow->SetSizeMode(sizeMode); + } + gotState = true; + + // zlevel + windowElement->GetAttribute(ZLEVEL_ATTRIBUTE, 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 nsXULWindow::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<nsIXULWindow> ourXULWindow(this); + + nsAutoString windowType; + windowElement->GetAttribute(WINDOWTYPE_ATTRIBUTE, windowType); + + int32_t screenTop = 0, // it's pointless to initialize these ... + screenRight = 0, // ... but to prevent oversalubrious and ... + screenBottom = 0, // ... underbright compilers from ... + screenLeft = 0; // ... issuing warnings. + 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 + screenMgr->ScreenForRect(aRequestedX, aRequestedY, + aSpecWidth, aSpecHeight, + getter_AddRefs(ourScreen)); + if (ourScreen) { + int32_t screenWidth, screenHeight; + ourScreen->GetAvailRectDisplayPix(&screenLeft, &screenTop, + &screenWidth, &screenHeight); + screenBottom = screenTop + screenHeight; + screenRight = screenLeft + screenWidth; + // Get the screen's scaling factors and convert staggering constants + // from CSS px to desktop pixel units + double desktopToDeviceScale = 1.0, cssToDeviceScale = 1.0; + ourScreen->GetContentsScaleFactor(&desktopToDeviceScale); + ourScreen->GetDefaultCSSScaleFactor(&cssToDeviceScale); + double cssToDesktopFactor = cssToDeviceScale / desktopToDeviceScale; + kOffset = NSToIntRound(kOffset * cssToDesktopFactor); + kSlop = NSToIntRound(kSlop * cssToDesktopFactor); + // Convert dimensions from CSS to desktop pixels + aSpecWidth = NSToIntRound(aSpecWidth * cssToDesktopFactor); + aSpecHeight = NSToIntRound(aSpecHeight * cssToDesktopFactor); + gotScreen = true; + } + } + } + + // One full pass through all windows of this type, repeat until no collisions. + do { + keepTrying = false; + nsCOMPtr<nsISimpleEnumerator> windowList; + wm->GetXULWindowEnumerator(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<nsIXULWindow> listXULWindow(do_QueryInterface(supportsWindow)); + if (listXULWindow != ourXULWindow) { + 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) > screenRight)) { + aRequestedX = screenRight - aSpecWidth; + ++bouncedX; + } + + // if we're moving to the left and we need to bounce... + if ((bouncedX & 0x1) && aRequestedX < screenLeft) { + aRequestedX = screenLeft; + ++bouncedX; + } + + // if we hit the bottom then bounce to the top + if (aRequestedY + aSpecHeight > screenBottom) { + aRequestedY = screenTop; + ++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(1); + } while (keepTrying); +} + +void nsXULWindow::SyncAttributesToWidget() +{ + nsCOMPtr<dom::Element> windowElement = GetWindowDOMElement(); + if (!windowElement) + return; + + nsAutoString attr; + + // "hidechrome" attribute + if (windowElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidechrome, + nsGkAtoms::_true, eCaseMatters)) { + mWindow->HideWindowChrome(true); + } + + // "chromemargin" attribute + nsIntMargin margins; + windowElement->GetAttribute(NS_LITERAL_STRING("chromemargin"), attr); + if (nsContentUtils::ParseIntMarginValue(attr, margins)) { + LayoutDeviceIntMargin tmp = LayoutDeviceIntMargin::FromUnknownMargin(margins); + mWindow->SetNonClientMargins(tmp); + } + + // "windowtype" attribute + windowElement->GetAttribute(WINDOWTYPE_ATTRIBUTE, attr); + if (!attr.IsEmpty()) { + mWindow->SetWindowClass(attr); + } + + // "id" attribute for icon + windowElement->GetAttribute(NS_LITERAL_STRING("id"), attr); + if (attr.IsEmpty()) { + attr.AssignLiteral("default"); + } + mWindow->SetIcon(attr); + + // "drawtitle" attribute + windowElement->GetAttribute(NS_LITERAL_STRING("drawtitle"), attr); + mWindow->SetDrawsTitle(attr.LowerCaseEqualsLiteral("true")); + + // "toggletoolbar" attribute + windowElement->GetAttribute(NS_LITERAL_STRING("toggletoolbar"), attr); + mWindow->SetShowsToolbarButton(attr.LowerCaseEqualsLiteral("true")); + + // "fullscreenbutton" attribute + windowElement->GetAttribute(NS_LITERAL_STRING("fullscreenbutton"), attr); + mWindow->SetShowsFullScreenButton(attr.LowerCaseEqualsLiteral("true")); + + // "macanimationtype" attribute + windowElement->GetAttribute(NS_LITERAL_STRING("macanimationtype"), attr); + if (attr.EqualsLiteral("document")) { + mWindow->SetWindowAnimationType(nsIWidget::eDocumentWindowAnimation); + } +} + +NS_IMETHODIMP nsXULWindow::SavePersistentAttributes() +{ + // can happen when the persistence timer fires at an inopportune time + // during window shutdown + if (!mDocShell) + return NS_ERROR_FAILURE; + + nsCOMPtr<dom::Element> docShellElement = GetWindowDOMElement(); + if (!docShellElement) + return NS_ERROR_FAILURE; + + nsAutoString persistString; + docShellElement->GetAttribute(PERSIST_ATTRIBUTE, persistString); + if (persistString.IsEmpty()) { // quick check which sometimes helps + mPersistentAttributesDirty = 0; + return NS_OK; + } + + bool isFullscreen = false; + if (nsPIDOMWindowOuter* domWindow = mDocShell->GetWindow()) { + isFullscreen = domWindow->GetFullScreen(); + } + + // get our size, position and mode to persist + LayoutDeviceIntRect rect; + bool gotRestoredBounds = NS_SUCCEEDED(mWindow->GetRestoredBounds(rect)); + + // we use CSS pixels for size, but desktop pixels for position + CSSToLayoutDeviceScale sizeScale = mWindow->GetDefaultScale(); + DesktopToLayoutDeviceScale posScale = mWindow->GetDesktopToDeviceScale(); + + // make our position relative to our parent, if any + nsCOMPtr<nsIBaseWindow> parent(do_QueryReferent(mParentWindow)); + if (parent && gotRestoredBounds) { + int32_t parentX, parentY; + if (NS_SUCCEEDED(parent->GetPosition(&parentX, &parentY))) { + rect.x -= parentX; + rect.y -= parentY; + } + } + + char sizeBuf[10]; + nsAutoString sizeString; + nsAutoString windowElementId; + nsCOMPtr<nsIDOMXULDocument> ownerXULDoc; + + // fetch docShellElement's ID and XUL owner document + ownerXULDoc = do_QueryInterface(docShellElement->OwnerDoc()); + if (docShellElement->IsXULElement()) { + docShellElement->GetId(windowElementId); + } + + bool shouldPersist = !isFullscreen && ownerXULDoc; + ErrorResult rv; + // (only for size elements which are persisted) + if ((mPersistentAttributesDirty & PAD_POSITION) && gotRestoredBounds) { + if (persistString.Find("screenX") >= 0) { + SprintfLiteral(sizeBuf, "%d", NSToIntRound(rect.x / posScale.scale)); + sizeString.AssignWithConversion(sizeBuf); + docShellElement->SetAttribute(SCREENX_ATTRIBUTE, sizeString, rv); + if (shouldPersist) { + ownerXULDoc->Persist(windowElementId, SCREENX_ATTRIBUTE); + } + } + if (persistString.Find("screenY") >= 0) { + SprintfLiteral(sizeBuf, "%d", NSToIntRound(rect.y / posScale.scale)); + sizeString.AssignWithConversion(sizeBuf); + docShellElement->SetAttribute(SCREENY_ATTRIBUTE, sizeString, rv); + if (shouldPersist) { + ownerXULDoc->Persist(windowElementId, SCREENY_ATTRIBUTE); + } + } + } + + if ((mPersistentAttributesDirty & PAD_SIZE) && gotRestoredBounds) { + if (persistString.Find("width") >= 0) { + SprintfLiteral(sizeBuf, "%d", NSToIntRound(rect.width / sizeScale.scale)); + sizeString.AssignWithConversion(sizeBuf); + docShellElement->SetAttribute(WIDTH_ATTRIBUTE, sizeString, rv); + if (shouldPersist) { + ownerXULDoc->Persist(windowElementId, WIDTH_ATTRIBUTE); + } + } + if (persistString.Find("height") >= 0) { + SprintfLiteral(sizeBuf, "%d", NSToIntRound(rect.height / sizeScale.scale)); + sizeString.AssignWithConversion(sizeBuf); + docShellElement->SetAttribute(HEIGHT_ATTRIBUTE, sizeString, rv); + if (shouldPersist) { + ownerXULDoc->Persist(windowElementId, HEIGHT_ATTRIBUTE); + } + } + } + + if (mPersistentAttributesDirty & PAD_MISC) { + nsSizeMode sizeMode = mWindow->SizeMode(); + + 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); + docShellElement->SetAttribute(MODE_ATTRIBUTE, sizeString, rv); + if (shouldPersist && persistString.Find("sizemode") >= 0) { + ownerXULDoc->Persist(windowElementId, MODE_ATTRIBUTE); + } + } + if (persistString.Find("zlevel") >= 0) { + uint32_t zLevel; + nsCOMPtr<nsIWindowMediator> mediator(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (mediator) { + mediator->GetZLevel(this, &zLevel); + SprintfLiteral(sizeBuf, "%" PRIu32, zLevel); + sizeString.AssignWithConversion(sizeBuf); + docShellElement->SetAttribute(ZLEVEL_ATTRIBUTE, sizeString, rv); + if (shouldPersist) { + ownerXULDoc->Persist(windowElementId, ZLEVEL_ATTRIBUTE); + } + } + } + } + + mPersistentAttributesDirty = 0; + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::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* +nsXULWindow::GetWindowDOMElement() const +{ + NS_ENSURE_TRUE(mDocShell, nullptr); + + nsCOMPtr<nsIContentViewer> cv; + mDocShell->GetContentViewer(getter_AddRefs(cv)); + NS_ENSURE_TRUE(cv, nullptr); + + const nsIDocument* document = cv->GetDocument(); + NS_ENSURE_TRUE(document, nullptr); + + return document->GetRootElement(); +} + +nsresult nsXULWindow::ContentShellAdded(nsIDocShellTreeItem* aContentShell, + bool aPrimary, bool aTargetable, const nsAString& aID) +{ + nsContentShellInfo* shellInfo = nullptr; + + uint32_t i, count = mContentShells.Length(); + nsWeakPtr contentShellWeak = do_GetWeakReference(aContentShell); + for (i = 0; i < count; i++) { + nsContentShellInfo* info = mContentShells.ElementAt(i); + if (info->id.Equals(aID)) { + // We already exist. Do a replace. + info->child = contentShellWeak; + shellInfo = info; + } + else if (info->child == contentShellWeak) + info->child = nullptr; + } + + if (!shellInfo) { + shellInfo = new nsContentShellInfo(aID, contentShellWeak); + mContentShells.AppendElement(shellInfo); + } + + // Set the default content tree owner + if (aPrimary) { + NS_ENSURE_SUCCESS(EnsurePrimaryContentTreeOwner(), NS_ERROR_FAILURE); + aContentShell->SetTreeOwner(mPrimaryContentTreeOwner); + mPrimaryContentShell = aContentShell; + mPrimaryTabParent = nullptr; + } + else { + NS_ENSURE_SUCCESS(EnsureContentTreeOwner(), NS_ERROR_FAILURE); + aContentShell->SetTreeOwner(mContentTreeOwner); + if (mPrimaryContentShell == aContentShell) + mPrimaryContentShell = nullptr; + } + + if (aTargetable) { +#ifdef DEBUG + int32_t debugCount = mTargetableShells.Count(); + int32_t debugCounter; + for (debugCounter = debugCount - 1; debugCounter >= 0; --debugCounter) { + nsCOMPtr<nsIDocShellTreeItem> curItem = + do_QueryReferent(mTargetableShells[debugCounter]); + NS_ASSERTION(!SameCOMIdentity(curItem, aContentShell), + "Adding already existing item to mTargetableShells"); + } +#endif + + // put the new shell at the start of the targetable shells list if either + // it's the new primary shell or there is no existing primary shell (which + // means that chances are this one just stopped being primary). If we + // really cared, we could keep track of the "last no longer primary shell" + // explicitly, but it probably doesn't matter enough: the difference would + // only be felt in a situation where all shells were non-primary, which + // doesn't happen much. In a situation where there is one and only one + // primary shell, and in which shells get unmarked as primary before some + // other shell gets marked as primary, this effectively stores the list of + // targetable shells in "most recently primary first" order. + bool inserted; + if (aPrimary || !mPrimaryContentShell) { + inserted = mTargetableShells.InsertObjectAt(contentShellWeak, 0); + } else { + inserted = mTargetableShells.AppendObject(contentShellWeak); + } + NS_ENSURE_TRUE(inserted, NS_ERROR_OUT_OF_MEMORY); + } + + return NS_OK; +} + +nsresult nsXULWindow::ContentShellRemoved(nsIDocShellTreeItem* aContentShell) +{ + if (mPrimaryContentShell == aContentShell) { + mPrimaryContentShell = nullptr; + } + + int32_t i, count = mContentShells.Length(); + for (i = count - 1; i >= 0; --i) { + nsContentShellInfo* info = mContentShells.ElementAt(i); + nsCOMPtr<nsIDocShellTreeItem> curItem = do_QueryReferent(info->child); + if (!curItem || SameCOMIdentity(curItem, aContentShell)) { + mContentShells.RemoveElementAt(i); + delete info; + } + } + + count = mTargetableShells.Count(); + for (i = count - 1; i >= 0; --i) { + nsCOMPtr<nsIDocShellTreeItem> curItem = + do_QueryReferent(mTargetableShells[i]); + if (!curItem || SameCOMIdentity(curItem, aContentShell)) { + mTargetableShells.RemoveObjectAt(i); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXULWindow::GetPrimaryContentSize(int32_t* aWidth, + int32_t* aHeight) +{ + if (mPrimaryTabParent) { + return GetPrimaryTabParentSize(aWidth, aHeight); + } else if (mPrimaryContentShell) { + return GetPrimaryContentShellSize(aWidth, aHeight); + } + return NS_ERROR_UNEXPECTED; +} + +nsresult +nsXULWindow::GetPrimaryTabParentSize(int32_t* aWidth, + int32_t* aHeight) +{ + TabParent* tabParent = TabParent::GetFrom(mPrimaryTabParent); + // Need strong ref, since Client* can run script. + nsCOMPtr<Element> element = tabParent->GetOwnerElement(); + NS_ENSURE_STATE(element); + + *aWidth = element->ClientWidth(); + *aHeight = element->ClientHeight(); + return NS_OK; +} + +nsresult +nsXULWindow::GetPrimaryContentShellSize(int32_t* aWidth, + int32_t* aHeight) +{ + NS_ENSURE_STATE(mPrimaryContentShell); + + nsCOMPtr<nsIBaseWindow> shellWindow(do_QueryInterface(mPrimaryContentShell)); + NS_ENSURE_STATE(shellWindow); + + int32_t devicePixelWidth, devicePixelHeight; + double shellScale = 1.0; + // We want to return CSS pixels. First, we get device pixels + // from the content area... + shellWindow->GetSize(&devicePixelWidth, &devicePixelHeight); + // And then get the device pixel scaling factor. Dividing device + // pixels by this scaling factor gives us CSS pixels. + shellWindow->GetUnscaledDevicePixelsPerCSSPixel(&shellScale); + *aWidth = NSToIntRound(devicePixelWidth / shellScale); + *aHeight = NSToIntRound(devicePixelHeight / shellScale); + return NS_OK; +} + +NS_IMETHODIMP +nsXULWindow::SetPrimaryContentSize(int32_t aWidth, + int32_t aHeight) +{ + if (mPrimaryTabParent) { + return SetPrimaryTabParentSize(aWidth, aHeight); + } else if (mPrimaryContentShell) { + return SizeShellTo(mPrimaryContentShell, aWidth, aHeight); + } + return NS_ERROR_UNEXPECTED; +} + +nsresult +nsXULWindow::SetPrimaryTabParentSize(int32_t aWidth, + int32_t aHeight) +{ + int32_t shellWidth, shellHeight; + GetPrimaryTabParentSize(&shellWidth, &shellHeight); + + double scale = 1.0; + GetUnscaledDevicePixelsPerCSSPixel(&scale); + + SizeShellToWithLimit(aWidth, aHeight, + shellWidth * scale, shellHeight * scale); + return NS_OK; +} + +nsresult +nsXULWindow::GetRootShellSize(int32_t* aWidth, + int32_t* aHeight) +{ + nsCOMPtr<nsIBaseWindow> shellAsWin = do_QueryInterface(mDocShell); + NS_ENSURE_TRUE(shellAsWin, NS_ERROR_FAILURE); + return shellAsWin->GetSize(aWidth, aHeight); +} + +nsresult +nsXULWindow::SetRootShellSize(int32_t aWidth, + int32_t aHeight) +{ + nsCOMPtr<nsIDocShellTreeItem> docShellAsItem = do_QueryInterface(mDocShell); + return SizeShellTo(docShellAsItem, aWidth, aHeight); +} + +NS_IMETHODIMP nsXULWindow::SizeShellTo(nsIDocShellTreeItem* aShellItem, + int32_t aCX, int32_t aCY) +{ + // 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 nsXULWindow::ExitModalLoop(nsresult aStatus) +{ + if (mContinueModalLoop) + EnableParent(true); + mContinueModalLoop = false; + mModalStatus = aStatus; + return NS_OK; +} + +// top-level function to create a new window +NS_IMETHODIMP nsXULWindow::CreateNewWindow(int32_t aChromeFlags, + nsITabParent *aOpeningTab, + mozIDOMWindowProxy *aOpener, + nsIXULWindow **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + if (aChromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_CHROME) + return CreateNewChromeWindow(aChromeFlags, aOpeningTab, aOpener, _retval); + return CreateNewContentWindow(aChromeFlags, aOpeningTab, aOpener, _retval); +} + +NS_IMETHODIMP nsXULWindow::CreateNewChromeWindow(int32_t aChromeFlags, + nsITabParent *aOpeningTab, + mozIDOMWindowProxy *aOpener, + nsIXULWindow **_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<nsIXULWindow> newWindow; + appShell->CreateTopLevelWindow(this, nullptr, aChromeFlags, + nsIAppShellService::SIZE_TO_CONTENT, + nsIAppShellService::SIZE_TO_CONTENT, + aOpeningTab, aOpener, + getter_AddRefs(newWindow)); + + NS_ENSURE_TRUE(newWindow, NS_ERROR_FAILURE); + + *_retval = newWindow; + NS_ADDREF(*_retval); + + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::CreateNewContentWindow(int32_t aChromeFlags, + nsITabParent *aOpeningTab, + mozIDOMWindowProxy *aOpener, + nsIXULWindow **_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; + + nsAdoptingCString urlStr = Preferences::GetCString("browser.chromeURL"); + if (urlStr.IsEmpty()) { + urlStr.AssignLiteral("chrome://navigator/content/navigator.xul"); + } + + 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<nsIXULWindow> newWindow; + { + AutoNoJSAPI nojsapi; + // We actually want this toplevel window which we are creating to have a + // null opener, as we will be creating the content xul:browser window inside + // of it, so we pass nullptr as our aOpener. + appShell->CreateTopLevelWindow(this, uri, + aChromeFlags, 615, 480, + aOpeningTab, nullptr, + getter_AddRefs(newWindow)); + NS_ENSURE_TRUE(newWindow, NS_ERROR_FAILURE); + } + + // Specify that we want the window to remain locked until the chrome has loaded. + nsXULWindow *xulWin = static_cast<nsXULWindow*> + (static_cast<nsIXULWindow*> + (newWindow)); + + if (aOpener) { + nsCOMPtr<nsIDocShell> docShell; + xulWin->GetDocShell(getter_AddRefs(docShell)); + MOZ_ASSERT(docShell); + nsCOMPtr<nsIDOMChromeWindow> chromeWindow = + do_QueryInterface(docShell->GetWindow()); + MOZ_ASSERT(chromeWindow); + + chromeWindow->SetOpenerForInitialContentBrowser(aOpener); + } + + xulWin->LockUntilChromeLoad(); + + { + AutoNoJSAPI nojsapi; + nsIThread *thread = NS_GetCurrentThread(); + while (xulWin->IsLocked()) { + if (!NS_ProcessNextEvent(thread)) + break; + } + } + + NS_ENSURE_STATE(xulWin->mPrimaryContentShell || xulWin->mPrimaryTabParent); + + *_retval = newWindow; + NS_ADDREF(*_retval); + + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::GetHasPrimaryContent(bool* aResult) +{ + *aResult = mPrimaryTabParent || mPrimaryContentShell; + return NS_OK; +} + +void nsXULWindow::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 nsXULWindow::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; + nsIXULWindow *us = this; + + altered = false; + mediator->GetZLevel(this, &zLevel); + + // translate from WidgetGUIEvent to nsIWindowMediator constants + position = nsIWindowMediator::zLevelTop; + if (*aPlacement == nsWindowZBottom || zLevel == nsIXULWindow::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, nsIXULWindow::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<nsIXULWindow> windowAbove; + if (newPosition == nsIWindowMediator::zLevelBelow && *aActualBelow) { + windowAbove = (*aActualBelow)->GetWidgetListener()->GetXULWindow(); + } + + 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 nsXULWindow::PlaceWindowLayersBehind(uint32_t aLowLevel, + uint32_t aHighLevel, + nsIXULWindow *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->GetZOrderXULWindowEnumerator(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 (windowEnumerator->HasMoreElements(&more), more) { + uint32_t nextZ; // z-level of nextWindow + nsCOMPtr<nsISupports> nextWindow; + windowEnumerator->GetNext(getter_AddRefs(nextWindow)); + nsCOMPtr<nsIXULWindow> nextXULWindow(do_QueryInterface(nextWindow)); + nextXULWindow->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(nextXULWindow)); + if (nextBase) { + nsCOMPtr<nsIWidget> nextWidget; + nextBase->GetMainWidget(getter_AddRefs(nextWidget)); + if (nextZ <= aHighLevel) + nextWidget->PlaceBehind(eZPlacementBelow, previousHighWidget, false); + previousHighWidget = nextWidget; + } + } +} + +void nsXULWindow::SetContentScrollbarVisibility(bool aVisible) +{ + nsCOMPtr<nsPIDOMWindowOuter> contentWin(do_GetInterface(mPrimaryContentShell)); + if (!contentWin) { + return; + } + + nsContentUtils::SetScrollbarsVisibility(contentWin->GetDocShell(), aVisible); +} + +bool nsXULWindow::GetContentScrollbarVisibility() +{ + // This code already exists in dom/src/base/nsBarProp.cpp, but we + // can't safely get to that from here as this function is called + // while the DOM window is being set up, and we need the DOM window + // to get to that code. + nsCOMPtr<nsIScrollable> scroller(do_QueryInterface(mPrimaryContentShell)); + + if (scroller) { + int32_t prefValue; + scroller->GetDefaultScrollbarPreferences( + nsIScrollable::ScrollOrientation_Y, &prefValue); + if (prefValue == nsIScrollable::Scrollbar_Never) // try the other way + scroller->GetDefaultScrollbarPreferences( + nsIScrollable::ScrollOrientation_X, &prefValue); + + if (prefValue == nsIScrollable::Scrollbar_Never) + return false; + } + + return true; +} + +// during spinup, attributes that haven't been loaded yet can't be dirty +void nsXULWindow::PersistentAttributesDirty(uint32_t aDirtyFlags) +{ + mPersistentAttributesDirty |= aDirtyFlags & mPersistentAttributesMask; +} + +NS_IMETHODIMP nsXULWindow::ApplyChromeFlags() +{ + nsCOMPtr<dom::Element> window = GetWindowDOMElement(); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + + 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 ? + true : false); + } + + /* 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. + ErrorResult rv; + window->SetAttribute(NS_LITERAL_STRING("chromehidden"), newvalue, rv); + + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::GetXULBrowserWindow(nsIXULBrowserWindow * *aXULBrowserWindow) +{ + NS_IF_ADDREF(*aXULBrowserWindow = mXULBrowserWindow); + return NS_OK; +} + +NS_IMETHODIMP nsXULWindow::SetXULBrowserWindow(nsIXULBrowserWindow * aXULBrowserWindow) +{ + mXULBrowserWindow = aXULBrowserWindow; + return NS_OK; +} + +void nsXULWindow::SizeShellToWithLimit(int32_t aDesiredWidth, + int32_t aDesiredHeight, + int32_t shellItemWidth, + int32_t shellItemHeight) +{ + int32_t widthDelta = aDesiredWidth - shellItemWidth; + int32_t heightDelta = aDesiredHeight - shellItemHeight; + + if (widthDelta || heightDelta) { + 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); + SetSize(winWidth, winHeight, true); + } +} + +//***************************************************************************** +//*** nsContentShellInfo: Object Management +//***************************************************************************** + +nsContentShellInfo::nsContentShellInfo(const nsAString& aID, + nsIWeakReference* aContentShell) + : id(aID), + child(aContentShell) +{ + MOZ_COUNT_CTOR(nsContentShellInfo); +} + +nsContentShellInfo::~nsContentShellInfo() +{ + MOZ_COUNT_DTOR(nsContentShellInfo); + //XXX Set Tree Owner to null if the tree owner is nsXULWindow->mContentTreeOwner +} diff --git a/xpfe/appshell/nsXULWindow.h b/xpfe/appshell/nsXULWindow.h new file mode 100644 index 000000000..eb059c939 --- /dev/null +++ b/xpfe/appshell/nsXULWindow.h @@ -0,0 +1,199 @@ +/* -*- 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 nsXULWindow_h__ +#define nsXULWindow_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 "nsRect.h" +#include "Units.h" + +// Interfaces needed +#include "nsIBaseWindow.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDOMWindow.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIXULWindow.h" +#include "nsIPrompt.h" +#include "nsIAuthPrompt.h" +#include "nsIXULBrowserWindow.h" +#include "nsIWeakReference.h" +#include "nsIWidgetListener.h" +#include "nsITabParent.h" + +namespace mozilla { +namespace dom { +class Element; +} // namespace dom +} // namespace mozilla + +// nsXULWindow + +#define NS_XULWINDOW_IMPL_CID \ +{ /* 8eaec2f3-ed02-4be2-8e0f-342798477298 */ \ + 0x8eaec2f3, \ + 0xed02, \ + 0x4be2, \ + { 0x8e, 0x0f, 0x34, 0x27, 0x98, 0x47, 0x72, 0x98 } \ +} + +class nsContentShellInfo; + +class nsXULWindow : public nsIBaseWindow, + public nsIInterfaceRequestor, + public nsIXULWindow, + public nsSupportsWeakReference +{ +friend class nsChromeTreeOwner; +friend class nsContentTreeOwner; + +public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIXULWINDOW + NS_DECL_NSIBASEWINDOW + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_XULWINDOW_IMPL_CID) + + void LockUntilChromeLoad() { mLockedUntilChromeLoad = true; } + bool IsLocked() const { return mLockedUntilChromeLoad; } + void IgnoreXULSizeMode(bool aEnable) { mIgnoreXULSizeMode = aEnable; } + void WasRegistered() { mRegistered = true; } + +protected: + enum persistentAttributes { + PAD_MISC = 0x1, + PAD_POSITION = 0x2, + PAD_SIZE = 0x4 + }; + + explicit nsXULWindow(uint32_t aChromeFlags); + virtual ~nsXULWindow(); + + NS_IMETHOD EnsureChromeTreeOwner(); + NS_IMETHOD EnsureContentTreeOwner(); + NS_IMETHOD EnsurePrimaryContentTreeOwner(); + NS_IMETHOD EnsurePrompter(); + NS_IMETHOD EnsureAuthPrompter(); + + 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 LoadMiscPersistentAttributesFromXUL(); + void SyncAttributesToWidget(); + NS_IMETHOD SavePersistentAttributes(); + + NS_IMETHOD GetWindowDOMWindow(mozIDOMWindowProxy** aDOMWindow); + mozilla::dom::Element* GetWindowDOMElement() const; + + // See nsIDocShellTreeOwner for docs on next two methods + nsresult ContentShellAdded(nsIDocShellTreeItem* aContentShell, + bool aPrimary, bool aTargetable, + const nsAString& aID); + 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, nsITabParent* aOpeningTab, mozIDOMWindowProxy* aOpenerWindow, nsIXULWindow **_retval); + NS_IMETHOD CreateNewContentWindow(int32_t aChromeFlags, nsITabParent* aOpeningTab, mozIDOMWindowProxy* aOpenerWindow, nsIXULWindow **_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, + nsIXULWindow *aBehind); + void SetContentScrollbarVisibility(bool aVisible); + bool GetContentScrollbarVisibility(); + void PersistentAttributesDirty(uint32_t aDirtyFlags); + + nsChromeTreeOwner* mChromeTreeOwner; + nsContentTreeOwner* mContentTreeOwner; + nsContentTreeOwner* mPrimaryContentTreeOwner; + nsCOMPtr<nsIWidget> mWindow; + nsCOMPtr<nsIDocShell> mDocShell; + nsCOMPtr<nsPIDOMWindowOuter> mDOMWindow; + nsCOMPtr<nsIWeakReference> mParentWindow; + nsCOMPtr<nsIPrompt> mPrompter; + nsCOMPtr<nsIAuthPrompt> mAuthPrompter; + nsCOMPtr<nsIXULBrowserWindow> mXULBrowserWindow; + nsCOMPtr<nsIDocShellTreeItem> mPrimaryContentShell; + nsTArray<nsContentShellInfo*> mContentShells; // array of doc shells by id + nsresult mModalStatus; + bool mContinueModalLoop; + bool mDebuting; // being made visible right now + bool mChromeLoaded; // True when chrome has loaded + 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; + uint32_t mContextFlags; + uint32_t mPersistentAttributesDirty; // persistentAttributes + uint32_t mPersistentAttributesMask; + uint32_t mChromeFlags; + nsString mTitle; + nsIntRect mOpenerScreenRect; // the screen rect of the opener + + nsCOMArray<nsIWeakReference> mTargetableShells; // targetable shells only + + nsCOMPtr<nsITabParent> mPrimaryTabParent; +private: + nsresult GetPrimaryTabParentSize(int32_t* aWidth, int32_t* aHeight); + nsresult GetPrimaryContentShellSize(int32_t* aWidth, int32_t* aHeight); + nsresult SetPrimaryTabParentSize(int32_t aWidth, int32_t aHeight); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsXULWindow, NS_XULWINDOW_IMPL_CID) + +// nsContentShellInfo +// Used to map shell IDs to nsIDocShellTreeItems. + +class nsContentShellInfo +{ +public: + nsContentShellInfo(const nsAString& aID, + nsIWeakReference* aContentShell); + ~nsContentShellInfo(); + +public: + nsString id; // The identifier of the content shell + nsWeakPtr child; // content shell (weak reference to nsIDocShellTreeItem) +}; + +#endif /* nsXULWindow_h__ */ diff --git a/xpfe/appshell/test/chrome.ini b/xpfe/appshell/test/chrome.ini new file mode 100644 index 000000000..6f4b72829 --- /dev/null +++ b/xpfe/appshell/test/chrome.ini @@ -0,0 +1,5 @@ +[DEFAULT] +skip-if = os == 'android' + +[test_hiddenPrivateWindow.xul] +[test_windowlessBrowser.xul] diff --git a/xpfe/appshell/test/test_hiddenPrivateWindow.xul b/xpfe/appshell/test/test_hiddenPrivateWindow.xul new file mode 100644 index 000000000..a1dfb486d --- /dev/null +++ b/xpfe/appshell/test/test_hiddenPrivateWindow.xul @@ -0,0 +1,45 @@ +<?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 type="application/javascript" + 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=815847">Mozilla Bug 815847</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> + +<script class="testbody" type="application/javascript"> +<![CDATA[ + +var Cu = Components.utils; + +Cu.import('resource://gre/modules/Services.jsm'); +Cu.import('resource://gre/modules/PrivateBrowsingUtils.jsm'); + +ok(Services.appShell.hiddenWindow, "hiddenWindow should exist"); +ok(Services.appShell.hiddenDOMWindow, "hiddenDOMWindow should exist"); +ok(Services.appShell.hiddenPrivateWindow, "hiddenPrivateWindow should exist"); +ok(Services.appShell.hiddenPrivateDOMWindow, "hiddenPrivateDOMWindow should exist"); + +ok(!PrivateBrowsingUtils.isWindowPrivate(Services.appShell.hiddenWindow.docShell), "hiddenWindow should not be private"); +ok(!PrivateBrowsingUtils.isWindowPrivate(Services.appShell.hiddenDOMWindow), "hiddenDOMWindow should not be private"); +ok(PrivateBrowsingUtils.isWindowPrivate(Services.appShell.hiddenPrivateWindow.docShell), "hiddenPrivateWindow should be private"); +ok(PrivateBrowsingUtils.isWindowPrivate(Services.appShell.hiddenPrivateDOMWindow), "hiddenPrivateDOMWindow should be private"); + +]]> +</script> + +</window> diff --git a/xpfe/appshell/test/test_windowlessBrowser.xul b/xpfe/appshell/test/test_windowlessBrowser.xul new file mode 100644 index 000000000..ef91f7655 --- /dev/null +++ b/xpfe/appshell/test/test_windowlessBrowser.xul @@ -0,0 +1,75 @@ +<?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 type="application/javascript" + 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[ + +var Cu = Components.utils; +var Ci = Components.interfaces; + +Cu.import('resource://gre/modules/Services.jsm'); + +function testWindowlessBrowser(chromePrivileged) { + var webNav = Services.appShell.createWindowlessBrowser(chromePrivileged); + + ok(webNav, "createWindowlessBrowser should return a wevNav"); + + let interfaceRequestor = webNav.QueryInterface(Ci.nsIInterfaceRequestor); + let docShell = interfaceRequestor.getInterface(Ci.nsIDocShell); + + ok(docShell, "docShell should be defined"); + ok(docShell.contentViewer.DOMDocument.defaultView, "docShell defaultView should be defined"); + + var win = docShell.contentViewer.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.sidebar, "window.sidebar should be defined"); + 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> diff --git a/xpfe/components/autocomplete/jar.mn b/xpfe/components/autocomplete/jar.mn new file mode 100644 index 000000000..d3ddf8a61 --- /dev/null +++ b/xpfe/components/autocomplete/jar.mn @@ -0,0 +1,9 @@ +# 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/. + +toolkit.jar: + content/global/autocomplete.xml (resources/content/autocomplete.xml) + +comm.jar: + content/communicator/autocomplete.css (resources/content/autocomplete.css) diff --git a/xpfe/components/autocomplete/moz.build b/xpfe/components/autocomplete/moz.build new file mode 100644 index 000000000..eb4454d28 --- /dev/null +++ b/xpfe/components/autocomplete/moz.build @@ -0,0 +1,7 @@ +# -*- 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/. + +JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file diff --git a/xpfe/components/autocomplete/resources/content/autocomplete.css b/xpfe/components/autocomplete/resources/content/autocomplete.css new file mode 100644 index 000000000..6c67bad2e --- /dev/null +++ b/xpfe/components/autocomplete/resources/content/autocomplete.css @@ -0,0 +1,46 @@ +/* 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/. */ + + +.autocomplete-result-popupset { + width: 0 !important; +} + +.autocomplete-result-popup { + display: -moz-popup !important; +} + +/* the C++ implementation of widgets is too eager to make popups visible. + this causes problems (bug 120155 and others), thus this workaround: */ +.autocomplete-result-popup[hidden="true"] { + visibility: hidden; +} + +.autocomplete-tree { + -moz-user-focus: ignore; +} + +.autocomplete-history-dropmarker { + display: none; +} + +.autocomplete-history-dropmarker[enablehistory="true"] { + display: -moz-box; +} + +/* The following rule is here to fix bug 96899 (and now 117952). + Somehow trees create a situation + in which a popupset flows itself as if its popup child is directly within it + instead of the placeholder child that should actually be inside the popupset. + This is a stopgap measure, and it does not address the real bug. */ +popupset { + max-width: 0px; + width: 0px; + min-width: 0%; + min-height: 0%; +} + +treecolpicker { + display: none; +} diff --git a/xpfe/components/autocomplete/resources/content/autocomplete.xml b/xpfe/components/autocomplete/resources/content/autocomplete.xml new file mode 100644 index 000000000..93b6dfdb0 --- /dev/null +++ b/xpfe/components/autocomplete/resources/content/autocomplete.xml @@ -0,0 +1,1646 @@ +<?xml version="1.0"?> +<!-- 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/. --> + + +<bindings id="autocompleteBindings" + xmlns="http://www.mozilla.org/xbl" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:xbl="http://www.mozilla.org/xbl"> + + <binding id="autocomplete" role="xul:combobox" + extends="chrome://global/content/bindings/textbox.xml#textbox"> + <resources> + <stylesheet src="chrome://communicator/content/autocomplete.css"/> + <stylesheet src="chrome://global/skin/autocomplete.css"/> + </resources> + + <content> + <children includes="menupopup"/> + + <xul:hbox class="autocomplete-textbox-container" flex="1" align="center"> + <children includes="image|deck|stack|box"> + <xul:image class="autocomplete-icon" allowevents="true"/> + </children> + + <xul:hbox class="textbox-input-box" flex="1" xbl:inherits="context,tooltiptext=inputtooltiptext"> + <children/> + <html:input anonid="input" class="autocomplete-textbox textbox-input" + allowevents="true" + xbl:inherits="tooltiptext=inputtooltiptext,value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint,userAction"/> + </xul:hbox> + <children includes="hbox"/> + </xul:hbox> + + <xul:dropmarker class="autocomplete-history-dropmarker" allowevents="true" + xbl:inherits="open,enablehistory" anonid="historydropmarker"/> + + <xul:popupset> + <xul:panel type="autocomplete" anonid="popup" + ignorekeys="true" noautofocus="true" level="top" + xbl:inherits="for=id,nomatch"/> + </xul:popupset> + </content> + + <implementation implements="nsIDOMXULMenuListElement"> + + <constructor><![CDATA[ + // XXX bug 90337 band-aid until we figure out what's going on here + if (this.value != this.mInputElt.value) + this.mInputElt.value = this.value; + delete this.value; + + // listen for pastes + this.mInputElt.controllers.insertControllerAt(0, this.mPasteController); + + // listen for menubar activation + window.top.addEventListener("DOMMenuBarActive", this.mMenuBarListener, true); + + // set default property values + this.ifSetAttribute("timeout", 50); + this.ifSetAttribute("pastetimeout", 1000); + this.ifSetAttribute("maxrows", 5); + this.ifSetAttribute("showpopup", true); + this.ifSetAttribute("disableKeyNavigation", true); + + // initialize the search sessions + if (this.hasAttribute("autocompletesearch")) + this.initAutoCompleteSearch(); + + // hack to work around lack of bottom-up constructor calling + if ("initialize" in this.popup) + this.popup.initialize(); + ]]></constructor> + + <destructor><![CDATA[ + this.clearResults(false); + window.top.removeEventListener("DOMMenuBarActive", this.mMenuBarListener, true); + this.mInputElt.controllers.removeController(this.mPasteController); + ]]></destructor> + + <!-- =================== nsIAutoCompleteInput =================== --> + <!-- XXX: This implementation is currently incomplete. --> + + <!-- reference to the results popup element --> + <field name="popup"><![CDATA[ + document.getAnonymousElementByAttribute(this, "anonid", "popup"); + ]]></field> + + <property name="popupOpen" + onget="return this.mMenuOpen;" + onset="if (val) this.openPopup(); else this.closePopup(); return val;"/> + + <!-- option to turn off autocomplete --> + <property name="disableAutoComplete" + onset="this.setAttribute('disableautocomplete', val); return val;" + onget="return this.getAttribute('disableautocomplete') == 'true';"/> + + <!-- if the resulting match string is not at the beginning of the typed string, + this will optionally autofill like this "bar |>> foobar|" --> + <property name="completeDefaultIndex" + onset="this.setAttribute('completedefaultindex', val); return val;" + onget="return this.getAttribute('completedefaultindex') == 'true';"/> + + <!-- option for completing to the default result whenever the user hits + enter or the textbox loses focus --> + <property name="forceComplete" + onset="this.setAttribute('forcecomplete', val); return val;" + onget="return this.getAttribute('forcecomplete') == 'true';"/> + + <property name="minResultsForPopup" + onset="this.setAttribute('minresultsforpopup', val); return val;" + onget="var t = this.getAttribute('minresultsforpopup'); return t ? parseInt(t) : 1;"/> + + <!-- maximum number of rows to display --> + <property name="maxRows" + onset="this.setAttribute('maxrows', val); return val;" + onget="return parseInt(this.getAttribute('maxrows')) || 0;"/> + + <!-- toggles a second column in the results list which contains + the string in the comment field of each autocomplete result --> + <property name="showCommentColumn" + onget="return this.getAttribute('showcommentcolumn') == 'true';"> + <setter><![CDATA[ + this.popup.showCommentColumn = val; + this.setAttribute('showcommentcolumn', val); + return val; + ]]></setter> + </property> + + <!-- number of milliseconds after a keystroke before a search begins --> + <property name="timeout" + onset="this.setAttribute('timeout', val); return val;" + onget="return parseInt(this.getAttribute('timeout')) || 0;"/> + + <property name="searchParam" + onget="return this.getAttribute('autocompletesearchparam') || '';" + onset="this.setAttribute('autocompletesearchparam', val); return val;"/> + + <property name="searchCount" readonly="true" + onget="return this.sessionCount;"/> + + <method name="getSearchAt"> + <parameter name="aIndex"/> + <body><![CDATA[ + var idx = -1; + for (var name in this.mSessions) + if (++idx == aIndex) + return name; + + return null; + ]]></body> + </method> + + <property name="textValue" + onget="return this.value;" + onset="this.setTextValue(val); return val;"/> + + <method name="onSearchBegin"> + <body><![CDATA[ + this._fireEvent("searchbegin"); + ]]></body> + </method> + + <method name="onSearchComplete"> + <body><![CDATA[ + if (this.noMatch) + this.setAttribute("nomatch", "true"); + else + this.removeAttribute("nomatch"); + + this._fireEvent("searchcomplete"); + ]]></body> + </method> + + <method name="onTextReverted"> + <body><![CDATA[ + return this._fireEvent("textreverted"); + ]]></body> + </method> + + <!-- =================== nsIDOMXULMenuListElement =================== --> + + <property name="editable" readonly="true" + onget="return true;" /> + + <property name="crop" + onset="this.setAttribute('crop', val); return val;" + onget="return this.getAttribute('crop');"/> + + <property name="label" readonly="true" + onget="return this.mInputElt.value;"/> + + <property name="open" + onget="return this.getAttribute('open') == 'true';"> + <setter> + <![CDATA[ + var historyPopup = document.getAnonymousElementByAttribute(this, "anonid", "historydropmarker"); + if (val) { + this.setAttribute('open', true); + historyPopup.showPopup(); + } else { + this.removeAttribute('open'); + historyPopup.hidePopup(); + } + ]]> + </setter> + </property> + + <!-- =================== PUBLIC PROPERTIES =================== --> + + <property name="value" + onget="return this.mInputElt.value;"> + <setter><![CDATA[ + this.ignoreInputEvent = true; + this.mInputElt.value = val; + this.ignoreInputEvent = false; + var event = document.createEvent('Events'); + event.initEvent('ValueChange', true, true); + this.mInputElt.dispatchEvent(event); + return val; + ]]></setter> + </property> + + <property name="focused" + onget="return this.getAttribute('focused') == 'true';"/> + + <method name="initAutoCompleteSearch"> + <body><![CDATA[ + var list = this.getAttribute("autocompletesearch").split(" "); + for (var i = 0; i < list.length; i++) { + var name = list[i]; + var contractid = "@mozilla.org/autocomplete/search;1?name=" + name; + if (contractid in Components.classes) { + try { + this.mSessions[name] = + Components.classes[contractid].getService(Components.interfaces.nsIAutoCompleteSearch); + this.mLastResults[name] = null; + this.mLastRows[name] = 0; + ++this.sessionCount; + } catch (e) { + dump("### ERROR - unable to create search \"" + name + "\".\n"); + } + } else { + dump("search \"" + name + "\" not found - skipping.\n"); + } + } + ]]></body> + </method> + + <!-- the number of sessions currently in use --> + <field name="sessionCount">0</field> + + <!-- number of milliseconds after a paste before a search begins --> + <property name="pasteTimeout" + onset="this.setAttribute('pastetimeout', val); return val;" + onget="var t = parseInt(this.getAttribute('pastetimeout')); return t ? t : 0;"/> + + <!-- option for filling the textbox with the best match while typing + and selecting the difference --> + <property name="autoFill" + onset="this.setAttribute('autofill', val); return val;" + onget="return this.getAttribute('autofill') == 'true';"/> + + <!-- if this attribute is set, allow different style for + non auto-completed lines --> + <property name="highlightNonMatches" + onset="this.setAttribute('highlightnonmatches', val); return val;" + onget="return this.getAttribute('highlightnonmatches') == 'true';"/> + + <!-- option to show the popup containing the results --> + <property name="showPopup" + onset="this.setAttribute('showpopup', val); return val;" + onget="return this.getAttribute('showpopup') == 'true';"/> + + <!-- option to allow scrolling through the list via the tab key, rather than + tab moving focus out of the textbox --> + <property name="tabScrolling" + onset="this.setAttribute('tabscrolling', val); return val;" + onget="return this.getAttribute('tabscrolling') == 'true';"/> + + <!-- option to completely ignore any blur events while + searches are still going on. This is useful so that nothing + gets autopicked if the window is required to lose focus for + some reason (eg in LDAP autocomplete, another window may be + brought up so that the user can enter a password to authenticate + to an LDAP server). --> + <property name="ignoreBlurWhileSearching" + onset="this.setAttribute('ignoreblurwhilesearching', val); return val;" + onget="return this.getAttribute('ignoreblurwhilesearching') == 'true';"/> + + <!-- state which indicates the current action being performed by the user. + Possible values are : none, typing, scrolling --> + <property name="userAction" + onset="this.setAttribute('userAction', val); return val;" + onget="return this.getAttribute('userAction');"/> + + <!-- state which indicates if the last search had no matches --> + <field name="noMatch">true</field> + + <!-- state which indicates a search is currently happening --> + <field name="isSearching">false</field> + + <!-- state which indicates a search timeout is current waiting --> + <property name="isWaiting" + onget="return this.mAutoCompleteTimer != 0;"/> + + <!-- =================== PRIVATE PROPERTIES =================== --> + + <field name="mSessions">({})</field> + <field name="mLastResults">({})</field> + <field name="mLastRows">({})</field> + <field name="mLastKeyCode">null</field> + <field name="mAutoCompleteTimer">0</field> + <field name="mMenuOpen">false</field> + <field name="mFireAfterSearch">false</field> + <field name="mFinishAfterSearch">false</field> + <field name="mNeedToFinish">false</field> + <field name="mNeedToComplete">false</field> + <field name="mTransientValue">false</field> + <field name="mView">null</field> + <field name="currentSearchString">""</field> + <field name="ignoreInputEvent">false</field> + <field name="oninit">null</field> + <field name="mDefaultMatchFilled">false</field> + <field name="mFirstReturn">true</field> + <field name="mIsPasting">false</field> + + <field name="mPasteController"><![CDATA[ + ({ + self: this, + kGlobalClipboard: Components.interfaces.nsIClipboard.kGlobalClipboard, + supportsCommand: function(aCommand) { + return aCommand == "cmd_paste"; + }, + isCommandEnabled: function(aCommand) { + return aCommand == "cmd_paste" && + this.self.editor.isSelectionEditable && + this.self.editor.canPaste(this.kGlobalClipboard); + }, + doCommand: function(aCommand) { + if (aCommand == "cmd_paste") { + this.self.mIsPasting = true; + this.self.editor.paste(this.kGlobalClipboard); + this.self.mIsPasting = false; + } + }, + onEvent: function() {} + }) + ]]></field> + + <field name="mMenuBarListener"><![CDATA[ + ({ + self: this, + handleEvent: function(aEvent) { + try { + this.self.finishAutoComplete(false, false, aEvent); + this.self.clearTimer(); + this.self.closePopup(); + } catch (e) { + window.top.removeEventListener("DOMMenuBarActive", this, true); + } + } + }) + ]]></field> + + <field name="mAutoCompleteObserver"><![CDATA[ + ({ + self: this, + onSearchResult: function(aSearch, aResult) { + for (var name in this.self.mSessions) + if (this.self.mSessions[name] == aSearch) + this.self.processResults(name, aResult); + } + }) + ]]></field> + + <field name="mInputElt"><![CDATA[ + document.getAnonymousElementByAttribute(this, "anonid", "input"); + ]]></field> + + <field name="mMenuAccessKey"><![CDATA[ + Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch) + .getIntPref("ui.key.menuAccessKey"); + ]]></field> + + <!-- =================== PUBLIC METHODS =================== --> + + <method name="getErrorAt"> + <parameter name="aIndex"/> + <body><![CDATA[ + var obj = aIndex < 0 ? null : this.convertIndexToSession(aIndex); + return obj && this.mLastResults[obj.session] && + this.mLastResults[obj.session].errorDescription; + ]]></body> + </method> + + <!-- get a value from the autocomplete results as a string via an absolute index--> + <method name="getResultValueAt"> + <parameter name="aIndex"/> + <body><![CDATA[ + var obj = this.convertIndexToSession(aIndex); + return obj ? this.getSessionValueAt(obj.session, obj.index) : null; + ]]></body> + </method> + + <!-- get a value from the autocomplete results as a string from a specific session --> + <method name="getSessionValueAt"> + <parameter name="aSession"/> + <parameter name="aIndex"/> + <body><![CDATA[ + var result = this.mLastResults[aSession]; + return result.errorDescription || result.getValueAt(aIndex); + ]]></body> + </method> + + <!-- get the total number of results overall --> + <method name="getResultCount"> + <body><![CDATA[ + return this.view.rowCount; + ]]></body> + </method> + + <!-- get the first session that has results --> + <method name="getDefaultSession"> + <body><![CDATA[ + for (var name in this.mLastResults) { + var results = this.mLastResults[name]; + if (results && results.matchCount > 0 && !results.errorDescription) + return name; + } + return null; + ]]></body> + </method> + + <!-- empty the cached result data and empty the results popup --> + <method name="clearResults"> + <parameter name="aInvalidate"/> + <body><![CDATA[ + this.clearResultData(); + this.clearResultElements(aInvalidate); + ]]></body> + </method> + + <!-- =================== PRIVATE METHODS =================== --> + + <!-- ::::::::::::: session searching ::::::::::::: --> + + <!-- --> + <method name="callListener"> + <parameter name="me"/> + <parameter name="aAction"/> + <body><![CDATA[ + // bail if the binding was detached or the element removed from + // document during the timeout + if (!("startLookup" in me) || !me.ownerDocument || !me.parentNode) + return; + + me.clearTimer(); + + if (me.disableAutoComplete) + return; + + switch (aAction) { + case "startLookup": + me.startLookup(); + break; + + case "stopLookup": + me.stopLookup(); + break; + } + ]]></body> + </method> + + <!-- --> + <method name="startLookup"> + <body><![CDATA[ + var str = this.currentSearchString; + if (!str) { + this.clearResults(false); + this.closePopup(); + return; + } + + this.isSearching = true; + this.mFirstReturn = true; + this.mSessionReturns = this.sessionCount; + this.mFailureItems = 0; + this.mDefaultMatchFilled = false; // clear out our prefill state. + + // Notify the input that the search is beginning. + this.onSearchBegin(); + + // tell each session to start searching... + for (var name in this.mSessions) + try { + this.mSessions[name].startSearch(str, this.searchParam, this.mLastResults[name], this.mAutoCompleteObserver); + } catch (e) { + --this.mSessionReturns; + this.searchFailed(); + } + ]]></body> + </method> + + <!-- --> + <method name="stopLookup"> + <body><![CDATA[ + for (var name in this.mSessions) + this.mSessions[name].stopSearch(); + ]]></body> + </method> + + <!-- --> + <method name="processResults"> + <parameter name="aSessionName"/> + <parameter name="aResults"/> + <body><![CDATA[ + if (this.disableAutoComplete) + return; + + const ACR = Components.interfaces.nsIAutoCompleteResult; + var status = aResults.searchResult; + if (status != ACR.RESULT_NOMATCH_ONGOING && + status != ACR.RESULT_SUCCESS_ONGOING) + --this.mSessionReturns; + + // check the many criteria for failure + if (aResults.errorDescription) + ++this.mFailureItems; + else if (status == ACR.RESULT_IGNORED || + status == ACR.RESULT_FAILURE || + status == ACR.RESULT_NOMATCH || + status == ACR.RESULT_NOMATCH_ONGOING || + aResults.matchCount == 0 || + aResults.searchString != this.currentSearchString) + { + this.mLastResults[aSessionName] = null; + if (this.mFirstReturn) + this.clearResultElements(false); + this.mFirstReturn = false; + this.searchFailed(); + return; + } + + if (this.mFirstReturn) { + if (this.view.mTree) + this.view.mTree.beginUpdateBatch(); + this.clearResultElements(false); // clear results, but don't repaint yet + } + + // always call openPopup...we may not have opened it + // if a previous search session didn't return enough search results. + // it's smart and doesn't try to open itself multiple times... + // be sure to add our result elements before calling openPopup as we need + // to know the total # of results found so far. + this.addResultElements(aSessionName, aResults); + + this.autoFillInput(aSessionName, aResults, false); + if (this.mFirstReturn && this.view.mTree) + this.view.mTree.endUpdateBatch(); + this.openPopup(); + this.mFirstReturn = false; + + // if this is the last session to return... + if (this.mSessionReturns == 0) + this.postSearchCleanup(); + + if (this.mFinishAfterSearch) + this.finishAutoComplete(false, this.mFireAfterSearch, null); + ]]></body> + </method> + + <!-- called each time a search fails, except when failure items need + to be displayed. If all searches have failed, clear the list + and close the popup --> + <method name="searchFailed"> + <body><![CDATA[ + // if all searches are done and they all failed... + if (this.mSessionReturns == 0 && this.getResultCount() == 0) { + if (this.minResultsForPopup == 0) { + this.clearResults(true); // clear data and repaint empty + this.openPopup(); + } else { + this.closePopup(); + } + } + + // if it's the last session to return, time to clean up... + if (this.mSessionReturns == 0) + this.postSearchCleanup(); + ]]></body> + </method> + + <!-- does some stuff after a search is done (success or failure) --> + <method name="postSearchCleanup"> + <body><![CDATA[ + this.isSearching = false; + + // figure out if there are no matches in all search sessions + var failed = true; + for (var name in this.mSessions) { + if (this.mLastResults[name]) + failed = this.mLastResults[name].errorDescription || + this.mLastResults[name].matchCount == 0; + if (!failed) + break; + } + this.noMatch = failed; + + // if we have processed all of our searches, and none of them gave us a default index, + // then we should try to auto fill the input field with the first match. + // note: autoFillInput is smart enough to kick out if we've already prefilled something... + if (!this.noMatch) { + var defaultSession = this.getDefaultSession(); + if (defaultSession) + this.autoFillInput(defaultSession, this.mLastResults[defaultSession], true); + } + + // Notify the input that the search is complete. + this.onSearchComplete(); + ]]></body> + </method> + + <!-- when the focus exits the widget or user hits return, + determine what value to leave in the textbox --> + <method name="finishAutoComplete"> + <parameter name="aForceComplete"/> + <parameter name="aFireTextCommand"/> + <parameter name="aTriggeringEvent"/> + <body><![CDATA[ + this.mFinishAfterSearch = false; + this.mFireAfterSearch = false; + if (this.mNeedToFinish && !this.disableAutoComplete) { + // set textbox value to either override value, or default search result + var val = this.popup.overrideValue; + if (val) { + this.setTextValue(val); + this.mNeedToFinish = false; + } else if (this.mTransientValue || + !(this.forceComplete || + (aForceComplete && + this.mDefaultMatchFilled && + this.mNeedToComplete))) { + this.mNeedToFinish = false; + } else if (this.isWaiting) { + // if the user typed, the search results are out of date, so let + // the search finish, and tell it to come back here when it's done + this.mFinishAfterSearch = true; + this.mFireAfterSearch = aFireTextCommand; + return; + } else { + // we want to use the default item index for the first session which gave us a valid + // default item index... + for (var name in this.mLastResults) { + var results = this.mLastResults[name]; + if (results && results.matchCount > 0 && + !results.errorDescription && results.defaultIndex != -1) + { + val = results.getValueAt(results.defaultIndex); + this.setTextValue(val); + this.mDefaultMatchFilled = true; + this.mNeedToFinish = false; + break; + } + } + + if (this.mNeedToFinish) { + // if a search is happening at this juncture, bail out of this function + // and let the search finish, and tell it to come back here when it's done + if (this.isSearching) { + this.mFinishAfterSearch = true; + this.mFireAfterSearch = aFireTextCommand; + return; + } + + this.mNeedToFinish = false; + var defaultSession = this.getDefaultSession(); + if (defaultSession) + { + // preselect the first one + var first = this.getSessionValueAt(defaultSession, 0); + this.setTextValue(first); + this.mDefaultMatchFilled = true; + } + } + } + + this.stopLookup(); + + this.closePopup(); + } + + this.mNeedToComplete = false; + this.clearTimer(); + + if (aFireTextCommand) + this._fireEvent("textentered", this.userAction, aTriggeringEvent); + ]]></body> + </method> + + <!-- when the user clicks an entry in the autocomplete popup --> + <method name="onResultClick"> + <body><![CDATA[ + // set textbox value to either override value, or the clicked result + var errItem = this.getErrorAt(this.popup.selectedIndex); + var val = this.popup.overrideValue; + if (val) + this.setTextValue(val); + else if (this.popup.selectedIndex != -1) { + if (errItem) { + this.setTextValue(this.currentSearchString); + this.mTransientValue = true; + } else { + this.setTextValue(this.getResultValueAt( + this.popup.selectedIndex)); + } + } + + this.mNeedToFinish = false; + this.mNeedToComplete = false; + + this.closePopup(); + + this.currentSearchString = ""; + + if (errItem) + this._fireEvent("errorcommand", errItem); + this._fireEvent("textentered", "clicking"); + ]]></body> + </method> + + <!-- when the user hits escape, revert the previously typed value in the textbox --> + <method name="undoAutoComplete"> + <body><![CDATA[ + var val = this.currentSearchString; + + var ok = this.onTextReverted(); + if ((ok || ok == undefined) && val) + this.setTextValue(val); + + this.userAction = "typing"; + + this.currentSearchString = this.value; + this.mNeedToComplete = false; + ]]></body> + </method> + + <!-- convert an absolute result index into a session name/index pair --> + <method name="convertIndexToSession"> + <parameter name="aIndex"/> + <body><![CDATA[ + for (var name in this.mLastRows) { + if (aIndex < this.mLastRows[name]) + return { session: name, index: aIndex }; + aIndex -= this.mLastRows[name]; + } + return null; + ]]></body> + </method> + + <!-- ::::::::::::: user input handling ::::::::::::: --> + + <!-- --> + <method name="processInput"> + <body><![CDATA[ + // stop current lookup in case it's async. + this.stopLookup(); + // stop the queued up lookup on a timer + this.clearTimer(); + + if (this.disableAutoComplete) + return; + + this.userAction = "typing"; + this.mFinishAfterSearch = false; + this.mNeedToFinish = true; + this.mTransientValue = false; + this.mNeedToComplete = true; + var str = this.value; + this.currentSearchString = str; + this.popup.clearSelection(); + + var timeout = this.mIsPasting ? this.pasteTimeout : this.timeout; + this.mAutoCompleteTimer = setTimeout(this.callListener, timeout, this, "startLookup"); + ]]></body> + </method> + + <!-- --> + <method name="processKeyPress"> + <parameter name="aEvent"/> + <body><![CDATA[ + this.mLastKeyCode = aEvent.keyCode; + + var killEvent = false; + + switch (aEvent.keyCode) { + case KeyEvent.DOM_VK_TAB: + if (this.tabScrolling) { + // don't kill this event if alt-tab or ctrl-tab is hit + if (!aEvent.altKey && !aEvent.ctrlKey) { + killEvent = this.mMenuOpen; + if (killEvent) + this.keyNavigation(aEvent); + } + } + break; + + case KeyEvent.DOM_VK_RETURN: + + // if this is a failure item, save it for fireErrorCommand + var errItem = this.getErrorAt(this.popup.selectedIndex); + + killEvent = this.mMenuOpen; + this.finishAutoComplete(true, true, aEvent); + this.closePopup(); + if (errItem) { + this._fireEvent("errorcommand", errItem); + } + break; + + case KeyEvent.DOM_VK_ESCAPE: + this.clearTimer(); + killEvent = this.mMenuOpen; + this.undoAutoComplete(); + this.closePopup(); + break; + + case KeyEvent.DOM_VK_LEFT: + case KeyEvent.DOM_VK_RIGHT: + case KeyEvent.DOM_VK_HOME: + case KeyEvent.DOM_VK_END: + this.finishAutoComplete(true, false, aEvent); + this.clearTimer(); + this.closePopup(); + break; + + case KeyEvent.DOM_VK_DOWN: + if (!aEvent.altKey) { + this.clearTimer(); + killEvent = this.keyNavigation(aEvent); + break; + } + // Alt+Down falls through to history popup toggling code + + case KeyEvent.DOM_VK_F4: + if (!aEvent.ctrlKey && !aEvent.shiftKey && this.getAttribute("enablehistory") == "true") { + var historyPopup = document.getAnonymousElementByAttribute(this, "anonid", "historydropmarker"); + if (historyPopup) + historyPopup.showPopup(); + else + historyPopup.hidePopup(); + } + break; + case KeyEvent.DOM_VK_PAGE_UP: + case KeyEvent.DOM_VK_PAGE_DOWN: + case KeyEvent.DOM_VK_UP: + if (!aEvent.ctrlKey && !aEvent.metaKey) { + this.clearTimer(); + killEvent = this.keyNavigation(aEvent); + } + break; + + case KeyEvent.DOM_VK_BACK_SPACE: + if (!aEvent.ctrlKey && !aEvent.altKey && !aEvent.shiftKey && + this.selectionStart == this.currentSearchString.length && + this.selectionEnd == this.value.length && + this.mDefaultMatchFilled) { + this.mDefaultMatchFilled = false; + this.value = this.currentSearchString; + } + + if (!/Mac/.test(navigator.platform)) + break; + case KeyEvent.DOM_VK_DELETE: + if (/Mac/.test(navigator.platform) && !aEvent.shiftKey) + break; + + if (this.mMenuOpen && this.popup.selectedIndex != -1) { + var obj = this.convertIndexToSession(this.popup.selectedIndex); + if (obj) { + var result = this.mLastResults[obj.session]; + if (!result.errorDescription) { + var count = result.matchCount; + result.removeValueAt(obj.index, true); + this.view.updateResults(this.popup.selectedIndex, result.matchCount - count); + killEvent = true; + } + } + } + break; + } + + if (killEvent) { + aEvent.preventDefault(); + aEvent.stopPropagation(); + } + + return true; + ]]></body> + </method> + + <!-- --> + <method name="processStartComposition"> + <body><![CDATA[ + this.finishAutoComplete(false, false, null); + this.clearTimer(); + this.closePopup(); + ]]></body> + </method> + + <!-- --> + <method name="keyNavigation"> + <parameter name="aEvent"/> + <body><![CDATA[ + var k = aEvent.keyCode; + if (k == KeyEvent.DOM_VK_TAB || + k == KeyEvent.DOM_VK_UP || k == KeyEvent.DOM_VK_DOWN || + k == KeyEvent.DOM_VK_PAGE_UP || k == KeyEvent.DOM_VK_PAGE_DOWN) + { + if (!this.mMenuOpen) { + // Original xpfe style was to allow the up and down keys to have + // their default Mac action if the popup could not be opened. + // For compatibility for toolkit we now have to predict which + // keys have a default action that we can always allow to fire. + if (/Mac/.test(navigator.platform) && + ((k == KeyEvent.DOM_VK_UP && + (this.selectionStart != 0 || + this.selectionEnd != 0)) || + (k == KeyEvent.DOM_VK_DOWN && + (this.selectionStart != this.value.length || + this.selectionEnd != this.value.length)))) + return false; + if (this.currentSearchString != this.value) { + this.processInput(); + return true; + } + if (this.view.rowCount < this.minResultsForPopup) + return true; // used to be false, see above + + this.mNeedToFinish = true; + this.openPopup(); + return true; + } + + this.userAction = "scrolling"; + this.mNeedToComplete = false; + + var reverse = k == KeyEvent.DOM_VK_TAB && aEvent.shiftKey || + k == KeyEvent.DOM_VK_UP || + k == KeyEvent.DOM_VK_PAGE_UP; + var page = k == KeyEvent.DOM_VK_PAGE_UP || + k == KeyEvent.DOM_VK_PAGE_DOWN; + var selected = this.popup.selectBy(reverse, page); + + // determine which value to place in the textbox + this.ignoreInputEvent = true; + if (selected != -1) { + if (this.getErrorAt(selected)) { + if (this.currentSearchString) + this.setTextValue(this.currentSearchString); + } else { + this.setTextValue(this.getResultValueAt(selected)); + } + this.mTransientValue = true; + } else { + if (this.currentSearchString) + this.setTextValue(this.currentSearchString); + this.mTransientValue = false; + } + + // move cursor to the end + this.mInputElt.setSelectionRange(this.value.length, this.value.length); + this.ignoreInputEvent = false; + } + return true; + ]]></body> + </method> + + <!-- while the user is typing, fill the textbox with the "default" value + if one can be assumed, and select the end of the text --> + <method name="autoFillInput"> + <parameter name="aSessionName"/> + <parameter name="aResults"/> + <parameter name="aUseFirstMatchIfNoDefault"/> + <body><![CDATA[ + if (this.mInputElt.selectionEnd < this.currentSearchString.length || + this.mDefaultMatchFilled) + return; + + if (!this.mFinishAfterSearch && + (this.autoFill || this.completeDefaultIndex) && + this.mLastKeyCode != KeyEvent.DOM_VK_BACK_SPACE && + this.mLastKeyCode != KeyEvent.DOM_VK_DELETE) { + var indexToUse = aResults.defaultIndex; + if (aUseFirstMatchIfNoDefault && indexToUse == -1) + indexToUse = 0; + + if (indexToUse != -1) { + var resultValue = this.getSessionValueAt(aSessionName, indexToUse); + var match = resultValue.toLowerCase(); + var entry = this.currentSearchString.toLowerCase(); + this.ignoreInputEvent = true; + if (match.indexOf(entry) == 0) { + var endPoint = this.value.length; + this.setTextValue(this.value + resultValue.substr(endPoint)); + this.mInputElt.setSelectionRange(endPoint, this.value.length); + } else { + if (this.completeDefaultIndex) { + this.setTextValue(this.value + " >> " + resultValue); + this.mInputElt.setSelectionRange(entry.length, this.value.length); + } else { + var postIndex = resultValue.indexOf(this.value); + if (postIndex >= 0) { + var startPt = this.value.length; + this.setTextValue(this.value + + resultValue.substr(startPt+postIndex)); + this.mInputElt.setSelectionRange(startPt, this.value.length); + } + } + } + this.mNeedToComplete = true; + this.ignoreInputEvent = false; + this.mDefaultMatchFilled = true; + } + } + ]]></body> + </method> + + <!-- ::::::::::::: popup and tree ::::::::::::: --> + + <!-- --> + <method name="openPopup"> + <body><![CDATA[ + if (!this.mMenuOpen && this.focused && + (this.getResultCount() >= this.minResultsForPopup || + this.mFailureItems)) { + var w = this.boxObject.width; + if (w != this.popup.boxObject.width) + this.popup.setAttribute("width", w); + this.popup.showPopup(this, -1, -1, "popup", "bottomleft", "topleft"); + this.mMenuOpen = true; + } + ]]></body> + </method> + + <!-- --> + <method name="closePopup"> + <body><![CDATA[ + if (this.popup && this.mMenuOpen) { + this.popup.hidePopup(); + this.mMenuOpen = false; + } + ]]></body> + </method> + + <!-- --> + <method name="addResultElements"> + <parameter name="aSession"/> + <parameter name="aResults"/> + <body><![CDATA[ + var count = aResults.errorDescription ? 1 : aResults.matchCount; + if (this.focused && this.showPopup) { + var row = 0; + for (var name in this.mSessions) { + row += this.mLastRows[name]; + if (name == aSession) + break; + } + this.view.updateResults(row, count - this.mLastRows[name]); + this.popup.adjustHeight(); + } + this.mLastResults[aSession] = aResults; + this.mLastRows[aSession] = count; + ]]></body> + </method> + + <!-- --> + <method name="clearResultElements"> + <parameter name="aInvalidate"/> + <body><![CDATA[ + for (var name in this.mSessions) + this.mLastRows[name] = 0; + this.view.clearResults(); + if (aInvalidate) + this.popup.adjustHeight(); + + this.noMatch = true; + ]]></body> + </method> + + <!-- --> + <method name="setTextValue"> + <parameter name="aValue"/> + <body><![CDATA[ + this.value = aValue; + + // Completing a result should simulate the user typing the result, + // so fire an input event. + var evt = document.createEvent("UIEvents"); + evt.initUIEvent("input", true, false, window, 0); + var oldIgnoreInput = this.ignoreInputEvent; + this.ignoreInputEvent = true; + this.dispatchEvent(evt); + this.ignoreInputEvent = oldIgnoreInput; + ]]></body> + </method> + + <!-- --> + <method name="clearResultData"> + <body><![CDATA[ + for (var name in this.mSessions) + this.mLastResults[name] = null; + ]]></body> + </method> + + <!-- ::::::::::::: miscellaneous ::::::::::::: --> + + <!-- --> + <method name="ifSetAttribute"> + <parameter name="aAttr"/> + <parameter name="aVal"/> + <body><![CDATA[ + if (!this.hasAttribute(aAttr)) + this.setAttribute(aAttr, aVal); + ]]></body> + </method> + + <!-- --> + <method name="clearTimer"> + <body><![CDATA[ + if (this.mAutoCompleteTimer) { + clearTimeout(this.mAutoCompleteTimer); + this.mAutoCompleteTimer = 0; + } + ]]></body> + </method> + + <!-- ::::::::::::: event dispatching ::::::::::::: --> + + <method name="_fireEvent"> + <parameter name="aEventType"/> + <parameter name="aEventParam"/> + <parameter name="aTriggeringEvent"/> + <body> + <![CDATA[ + var noCancel = true; + // handle any xml attribute event handlers + var handler = this.getAttribute("on"+aEventType); + if (handler) { + var fn = new Function("eventParam", "domEvent", handler); + var returned = fn.apply(this, [aEventParam, aTriggeringEvent]); + if (returned == false) + noCancel = false; + } + + return noCancel; + ]]> + </body> + </method> + + <!-- =================== TREE VIEW =================== --> + + <field name="view"><![CDATA[ + ({ + mTextbox: this, + mTree: null, + mSelection: null, + mRowCount: 0, + + clearResults: function() + { + var oldCount = this.mRowCount; + this.mRowCount = 0; + + if (this.mTree) { + this.mTree.rowCountChanged(0, -oldCount); + this.mTree.scrollToRow(0); + } + }, + + updateResults: function(aRow, aCount) + { + this.mRowCount += aCount; + + if (this.mTree) + this.mTree.rowCountChanged(aRow, aCount); + }, + + ////////////////////////////////////////////////////////// + // nsIAutoCompleteController interface + + // this is the only method required by the treebody mouseup handler + handleEnter: function(aIsPopupSelection) { + this.mTextbox.onResultClick(); + }, + + ////////////////////////////////////////////////////////// + // nsITreeView interface + + get rowCount() { + return this.mRowCount; + }, + + get selection() { + return this.mSelection; + }, + + set selection(aVal) { + return this.mSelection = aVal; + }, + + setTree: function(aTree) + { + this.mTree = aTree; + }, + + getCellText: function(aRow, aCol) + { + for (var name in this.mTextbox.mSessions) { + if (aRow < this.mTextbox.mLastRows[name]) { + var result = this.mTextbox.mLastResults[name]; + switch (aCol.id) { + case "treecolAutoCompleteValue": + return result.errorDescription || result.getLabelAt(aRow); + case "treecolAutoCompleteComment": + if (!result.errorDescription) + return result.getCommentAt(aRow); + default: + return ""; + } + } + aRow -= this.mTextbox.mLastRows[name]; + } + return ""; + }, + + getRowProperties: function(aIndex) + { + return ""; + }, + + getCellProperties: function(aIndex, aCol) + { + // for the value column, append nsIAutoCompleteItem::className + // to the property list so that we can style this column + // using that property + if (aCol.id == "treecolAutoCompleteValue") { + for (var name in this.mTextbox.mSessions) { + if (aIndex < this.mTextbox.mLastRows[name]) { + var result = this.mTextbox.mLastResults[name]; + if (result.errorDescription) + return ""; + return result.getStyleAt(aIndex); + } + aIndex -= this.mTextbox.mLastRows[name]; + } + } + return ""; + }, + + getColumnProperties: function(aCol) + { + return ""; + }, + + getImageSrc: function(aRow, aCol) + { + if (aCol.id == "treecolAutoCompleteValue") { + for (var name in this.mTextbox.mSessions) { + if (aRow < this.mTextbox.mLastRows[name]) { + var result = this.mTextbox.mLastResults[name]; + if (result.errorDescription) + return ""; + return result.getImageAt(aRow); + } + aRow -= this.mTextbox.mLastRows[name]; + } + } + return ""; + }, + + getParentIndex: function(aRowIndex) { }, + hasNextSibling: function(aRowIndex, aAfterIndex) { }, + getLevel: function(aIndex) {}, + getProgressMode: function(aRow, aCol) {}, + getCellValue: function(aRow, aCol) {}, + isContainer: function(aIndex) {}, + isContainerOpen: function(aIndex) {}, + isContainerEmpty: function(aIndex) {}, + isSeparator: function(aIndex) {}, + isSorted: function() {}, + toggleOpenState: function(aIndex) {}, + selectionChanged: function() {}, + cycleHeader: function(aCol) {}, + cycleCell: function(aRow, aCol) {}, + isEditable: function(aRow, aCol) {}, + isSelectable: function(aRow, aCol) {}, + setCellValue: function(aRow, aCol, aValue) {}, + setCellText: function(aRow, aCol, aValue) {}, + performAction: function(aAction) {}, + performActionOnRow: function(aAction, aRow) {}, + performActionOnCell: function(aAction, aRow, aCol) {} + }); + ]]></field> + + </implementation> + + <handlers> + <handler event="input" + action="if (!this.ignoreInputEvent) this.processInput();"/> + + <handler event="keypress" phase="capturing" + action="return this.processKeyPress(event);"/> + + <handler event="compositionstart" phase="capturing" + action="this.processStartComposition();"/> + + <handler event="focus" phase="capturing" + action="this.userAction = 'typing';"/> + + <handler event="blur" phase="capturing" + action="if ( !(this.ignoreBlurWhileSearching && this.isSearching) ) {this.userAction = 'none'; this.finishAutoComplete(false, false, event);}"/> + + <handler event="mousedown" phase="capturing" + action="if ( !this.mMenuOpen ) this.finishAutoComplete(false, false, event);"/> + </handlers> + </binding> + + <binding id="autocomplete-result-popup" extends="chrome://global/content/bindings/popup.xml#popup"> + <resources> + <stylesheet src="chrome://communicator/content/autocomplete.css"/> + <stylesheet src="chrome://global/skin/autocomplete.css"/> + </resources> + + <content ignorekeys="true" level="top"> + <xul:tree anonid="tree" class="autocomplete-tree plain" flex="1"> + <xul:treecols anonid="treecols"> + <xul:treecol class="autocomplete-treecol" id="treecolAutoCompleteValue" flex="2"/> + <xul:treecol class="autocomplete-treecol" id="treecolAutoCompleteComment" flex="1" hidden="true"/> + </xul:treecols> + <xul:treechildren anonid="treebody" class="autocomplete-treebody"/> + </xul:tree> + </content> + + <implementation implements="nsIAutoCompletePopup"> + <constructor><![CDATA[ + if (this.textbox && this.textbox.view) + this.initialize(); + ]]></constructor> + + <destructor><![CDATA[ + if (this.view) + this.tree.view = null; + ]]></destructor> + + <field name="textbox"> + document.getBindingParent(this); + </field> + + <field name="tree"> + document.getAnonymousElementByAttribute(this, "anonid", "tree"); + </field> + + <field name="treecols"> + document.getAnonymousElementByAttribute(this, "anonid", "treecols"); + </field> + + <field name="treebody"> + document.getAnonymousElementByAttribute(this, "anonid", "treebody"); + </field> + + <field name="view"> + null + </field> + + <!-- Setting tree.view doesn't always immediately create a selection, + so we ensure the selection by asking the tree for the view. Note: + this.view.selection is quicker if we know the selection exists. --> + <property name="selection" onget="return this.tree.view.selection;"/> + + <property name="pageCount" + onget="return this.tree.treeBoxObject.getPageLength();"/> + + <field name="maxRows">0</field> + <field name="mLastRows">0</field> + + <method name="initialize"> + <body><![CDATA[ + this.showCommentColumn = this.textbox.showCommentColumn; + this.tree.view = this.textbox.view; + this.view = this.textbox.view; + this.maxRows = this.textbox.maxRows; + ]]></body> + </method> + + <property name="showCommentColumn" + onget="return !this.treecols.lastChild.hidden;" + onset="this.treecols.lastChild.hidden = !val; return val;"/> + + <method name="adjustHeight"> + <body><![CDATA[ + // detect the desired height of the tree + var bx = this.tree.treeBoxObject; + var view = this.view; + var rows = this.maxRows || 6; + if (!view.rowCount || (rows && view.rowCount < rows)) + rows = view.rowCount; + + var height = rows * bx.rowHeight; + + if (height == 0) + this.tree.setAttribute("collapsed", "true"); + else { + if (this.tree.hasAttribute("collapsed")) + this.tree.removeAttribute("collapsed"); + this.tree.setAttribute("height", height); + } + ]]></body> + </method> + + <method name="clearSelection"> + <body> + this.selection.clearSelection(); + </body> + </method> + + <method name="getNextIndex"> + <parameter name="aReverse"/> + <parameter name="aPage"/> + <parameter name="aIndex"/> + <parameter name="aMaxRow"/> + <body><![CDATA[ + if (aMaxRow < 0) + return -1; + + if (aIndex == -1) + return aReverse ? aMaxRow : 0; + if (aIndex == (aReverse ? 0 : aMaxRow)) + return -1; + + var amount = aPage ? this.pageCount - 1 : 1; + aIndex = aReverse ? aIndex - amount : aIndex + amount; + if (aIndex > aMaxRow) + return aMaxRow; + if (aIndex < 0) + return 0; + return aIndex; + ]]></body> + </method> + + <!-- =================== nsIAutoCompletePopup =================== --> + + <field name="input"> + null + </field> + + <!-- This property is meant to be overriden by bindings extending + this one. When the user selects an item from the list by + hitting enter or clicking, this method can set the value + of the textbox to a different value if it wants to. --> + <property name="overrideValue" readonly="true" onget="return null;"/> + + <property name="selectedIndex"> + <getter> + if (!this.view || !this.selection.count) + return -1; + var start = {}, end = {}; + this.view.selection.getRangeAt(0, start, end); + return start.value; + </getter> + <setter> + if (this.view) { + this.selection.select(val); + if (val >= 0) { + this.view.selection.currentIndex = -1; + this.tree.treeBoxObject.ensureRowIsVisible(val); + } + } + return val; + </setter> + </property> + + <property name="popupOpen" onget="return !!this.input;" readonly="true"/> + + <method name="openAutocompletePopup"> + <parameter name="aInput"/> + <parameter name="aElement"/> + <body><![CDATA[ + if (!this.input) { + this.tree.view = aInput.controller; + this.view = this.tree.view; + this.showCommentColumn = aInput.showCommentColumn; + this.maxRows = aInput.maxRows; + this.invalidate(); + + var viewer = aElement + .ownerDocument + .defaultView + .QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIWebNavigation) + .QueryInterface(Components.interfaces.nsIDocShell) + .contentViewer; + var rect = aElement.getBoundingClientRect(); + var width = Math.round((rect.right - rect.left) * viewer.fullZoom); + this.setAttribute("width", width > 100 ? width : 100); + // Adjust the direction (which is not inherited) of the autocomplete + // popup list, based on the textbox direction. (Bug 707039) + this.style.direction = aElement.ownerDocument.defaultView + .getComputedStyle(aElement) + .direction; + this.popupBoxObject.setConsumeRollupEvent(aInput.consumeRollupEvent + ? PopupBoxObject.ROLLUP_CONSUME + : PopupBoxObject.ROLLUP_NO_CONSUME); + this.openPopup(aElement, "after_start", 0, 0, false, false); + if (this.state != "closed") + this.input = aInput; + } + ]]></body> + </method> + + <method name="closePopup"> + <body> + this.hidePopup(); + </body> + </method> + + <method name="invalidate"> + <body> + if (this.view) + this.adjustHeight(); + this.tree.treeBoxObject.invalidate(); + </body> + </method> + + <method name="selectBy"> + <parameter name="aReverse"/> + <parameter name="aPage"/> + <body><![CDATA[ + try { + return this.selectedIndex = this.getNextIndex(aReverse, aPage, this.selectedIndex, this.view.rowCount - 1); + } catch (ex) { + // do nothing - occasionally timer-related js errors happen here + // e.g. "this.selectedIndex has no properties", when you type fast and hit a + // navigation key before this popup has opened + return -1; + } + ]]></body> + </method> + </implementation> + + <handlers> + <handler event="popupshowing"> + if (this.textbox) + this.textbox.mMenuOpen = true; + </handler> + + <handler event="popuphiding"> + if (this.textbox) + this.textbox.mMenuOpen = false; + this.clearSelection(); + this.input = null; + </handler> + </handlers> + </binding> + + <binding id="autocomplete-treebody"> + <implementation> + <field name="popup">document.getBindingParent(this);</field> + + <field name="mLastMoveTime">Date.now()</field> + </implementation> + + <handlers> + <handler event="mouseout" action="this.popup.selectedIndex = -1;"/> + + <handler event="mouseup"><![CDATA[ + var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY); + if (rc != -1) { + this.popup.selectedIndex = rc; + this.popup.view.handleEnter(true); + } + ]]></handler> + + <handler event="mousemove"><![CDATA[ + if (Date.now() - this.mLastMoveTime > 30) { + var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY); + if (rc != -1 && rc != this.popup.selectedIndex) + this.popup.selectedIndex = rc; + this.mLastMoveTime = Date.now(); + } + ]]></handler> + </handlers> + </binding> + + <binding id="autocomplete-history-popup" + extends="chrome://global/content/bindings/popup.xml#popup-scrollbars"> + <resources> + <stylesheet src="chrome://communicator/content/autocomplete.css"/> + <stylesheet src="chrome://global/skin/autocomplete.css"/> + </resources> + + <implementation> + <method name="removeOpenAttribute"> + <parameter name="parentNode"/> + <body><![CDATA[ + parentNode.removeAttribute("open"); + ]]></body> + </method> + </implementation> + + <handlers> + <handler event="popuphiding"><![CDATA[ + setTimeout(this.removeOpenAttribute, 0, this.parentNode); + ]]></handler> + </handlers> + </binding> + + <binding id="history-dropmarker" extends="chrome://global/content/bindings/general.xml#dropmarker"> + + <implementation> + <method name="showPopup"> + <body><![CDATA[ + var textbox = document.getBindingParent(this); + var kids = textbox.getElementsByClassName("autocomplete-history-popup"); + if (kids.item(0) && textbox.getAttribute("open") != "true") { // Open history popup + var w = textbox.boxObject.width; + if (w != kids[0].boxObject.width) + kids[0].width = w; + kids[0].showPopup(textbox, -1, -1, "popup", "bottomleft", "topleft"); + textbox.setAttribute("open", "true"); + } + ]]></body> + </method> + </implementation> + + <handlers> + <handler event="mousedown"><![CDATA[ + this.showPopup(); + ]]></handler> + </handlers> + </binding> + +</bindings> diff --git a/xpfe/components/build/moz.build b/xpfe/components/build/moz.build new file mode 100644 index 000000000..3d02e0409 --- /dev/null +++ b/xpfe/components/build/moz.build @@ -0,0 +1,15 @@ +# -*- 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/. + +SOURCES += [ + 'nsModule.cpp', +] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '../directory', +] diff --git a/xpfe/components/build/nsModule.cpp b/xpfe/components/build/nsModule.cpp new file mode 100644 index 000000000..43966f94d --- /dev/null +++ b/xpfe/components/build/nsModule.cpp @@ -0,0 +1,44 @@ +/* -*- 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 "mozilla/ModuleUtils.h" +#include "nsDirectoryViewer.h" +#include "rdf.h" +#include "nsRDFCID.h" +#include "nsCURILoader.h" + +// Factory constructors +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsHTTPIndex, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsDirectoryViewerFactory) + +NS_DEFINE_NAMED_CID(NS_DIRECTORYVIEWERFACTORY_CID); +NS_DEFINE_NAMED_CID(NS_HTTPINDEX_SERVICE_CID); + +static const mozilla::Module::CIDEntry kXPFECIDs[] = { + { &kNS_DIRECTORYVIEWERFACTORY_CID, false, nullptr, nsDirectoryViewerFactoryConstructor }, + { &kNS_HTTPINDEX_SERVICE_CID, false, nullptr, nsHTTPIndexConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kXPFEContracts[] = { + { "@mozilla.org/xpfe/http-index-format-factory-constructor", &kNS_DIRECTORYVIEWERFACTORY_CID }, + { NS_HTTPINDEX_SERVICE_CONTRACTID, &kNS_HTTPINDEX_SERVICE_CID }, + { NS_HTTPINDEX_DATASOURCE_CONTRACTID, &kNS_HTTPINDEX_SERVICE_CID }, + { nullptr } +}; + +static const mozilla::Module::CategoryEntry kXPFECategories[] = { + { "Gecko-Content-Viewers", "application/http-index-format", "@mozilla.org/xpfe/http-index-format-factory-constructor" }, + { nullptr } +}; + +static const mozilla::Module kXPFEModule = { + mozilla::Module::kVersion, + kXPFECIDs, + kXPFEContracts, + kXPFECategories +}; + +NSMODULE_DEFN(application) = &kXPFEModule; diff --git a/xpfe/components/directory/moz.build b/xpfe/components/directory/moz.build new file mode 100644 index 000000000..248da07ec --- /dev/null +++ b/xpfe/components/directory/moz.build @@ -0,0 +1,20 @@ +# -*- 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/. + +XPIDL_SOURCES += [ + 'nsIHTTPIndex.idl', +] + +XPIDL_MODULE = 'directory' + +SOURCES += [ + 'nsDirectoryViewer.cpp', +] + +FINAL_LIBRARY = 'xul' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/xpfe/components/directory/nsDirectoryViewer.cpp b/xpfe/components/directory/nsDirectoryViewer.cpp new file mode 100644 index 000000000..9d23c5e74 --- /dev/null +++ b/xpfe/components/directory/nsDirectoryViewer.cpp @@ -0,0 +1,1393 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + A directory viewer object. Parses "application/http-index-format" + per Lou Montulli's original spec: + + http://www.mozilla.org/projects/netlib/dirindexformat.html + + One added change is for a description entry, for when the + target does not match the filename + +*/ + +#include "nsDirectoryViewer.h" +#include "nsArray.h" +#include "nsArrayUtils.h" +#include "nsIDirIndex.h" +#include "nsIDocShell.h" +#include "jsapi.h" +#include "nsCOMPtr.h" +#include "nsEnumeratorUtils.h" +#include "nsEscape.h" +#include "nsIRDFService.h" +#include "nsRDFCID.h" +#include "rdf.h" +#include "nsIServiceManager.h" +#include "nsIXPConnect.h" +#include "nsEnumeratorUtils.h" +#include "nsString.h" +#include "nsXPIDLString.h" +#include "nsReadableUtils.h" +#include "nsITextToSubURI.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIFTPChannel.h" +#include "nsIWindowWatcher.h" +#include "nsIPrompt.h" +#include "nsIAuthPrompt.h" +#include "nsIProgressEventSink.h" +#include "nsIDOMWindow.h" +#include "nsIDOMElement.h" +#include "nsIStreamConverterService.h" +#include "nsICategoryManager.h" +#include "nsXPCOMCID.h" +#include "nsIDocument.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsContentUtils.h" +#include "nsIURI.h" +#include "nsNetUtil.h" + +using namespace mozilla; + +static const int FORMAT_XUL = 3; + +//---------------------------------------------------------------------- +// +// Common CIDs +// + +static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + +// Various protocols we have to special case +static const char kFTPProtocol[] = "ftp://"; + +//---------------------------------------------------------------------- +// +// nsHTTPIndex +// + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsHTTPIndex) + NS_INTERFACE_MAP_ENTRY(nsIHTTPIndex) + NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource) + NS_INTERFACE_MAP_ENTRY(nsIStreamListener) + NS_INTERFACE_MAP_ENTRY(nsIDirIndexListener) + NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsIFTPEventSink) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIHTTPIndex) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION(nsHTTPIndex, mInner) +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHTTPIndex) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHTTPIndex) + +NS_IMETHODIMP +nsHTTPIndex::GetInterface(const nsIID &anIID, void **aResult ) +{ + if (anIID.Equals(NS_GET_IID(nsIFTPEventSink))) { + // If we don't have a container to store the logged data + // then don't report ourselves back to the caller + + if (!mRequestor) + return NS_ERROR_NO_INTERFACE; + *aResult = static_cast<nsIFTPEventSink*>(this); + NS_ADDREF(this); + return NS_OK; + } + + if (anIID.Equals(NS_GET_IID(nsIPrompt))) { + + if (!mRequestor) + return NS_ERROR_NO_INTERFACE; + + nsCOMPtr<nsPIDOMWindowOuter> aDOMWindow = do_GetInterface(mRequestor); + if (!aDOMWindow) + return NS_ERROR_NO_INTERFACE; + + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + + return wwatch->GetNewPrompter(aDOMWindow, (nsIPrompt**)aResult); + } + + if (anIID.Equals(NS_GET_IID(nsIAuthPrompt))) { + + if (!mRequestor) + return NS_ERROR_NO_INTERFACE; + + nsCOMPtr<nsPIDOMWindowOuter> aDOMWindow = do_GetInterface(mRequestor); + if (!aDOMWindow) + return NS_ERROR_NO_INTERFACE; + + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + + return wwatch->GetNewAuthPrompter(aDOMWindow, (nsIAuthPrompt**)aResult); + } + + if (anIID.Equals(NS_GET_IID(nsIProgressEventSink))) { + + if (!mRequestor) + return NS_ERROR_NO_INTERFACE; + + nsCOMPtr<nsIProgressEventSink> sink = do_GetInterface(mRequestor); + if (!sink) + return NS_ERROR_NO_INTERFACE; + + *aResult = sink; + NS_ADDREF((nsISupports*)*aResult); + return NS_OK; + } + + return NS_ERROR_NO_INTERFACE; +} + +NS_IMETHODIMP +nsHTTPIndex::OnFTPControlLog(bool server, const char *msg) +{ + NS_ENSURE_TRUE(mRequestor, NS_OK); + + nsCOMPtr<nsIGlobalObject> globalObject = do_GetInterface(mRequestor); + NS_ENSURE_TRUE(globalObject, NS_OK); + + // We're going to run script via JS_CallFunctionName, so we need an + // AutoEntryScript. This is Gecko specific and not in any spec. + dom::AutoEntryScript aes(globalObject, + "nsHTTPIndex OnFTPControlLog"); + JSContext* cx = aes.cx(); + + JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); + NS_ENSURE_TRUE(global, NS_OK); + + nsString unicodeMsg; + unicodeMsg.AssignWithConversion(msg); + JSString* jsMsgStr = JS_NewUCStringCopyZ(cx, unicodeMsg.get()); + NS_ENSURE_TRUE(jsMsgStr, NS_ERROR_OUT_OF_MEMORY); + + JS::AutoValueArray<2> params(cx); + params[0].setBoolean(server); + params[1].setString(jsMsgStr); + + JS::Rooted<JS::Value> val(cx); + JS_CallFunctionName(cx, + global, + "OnFTPControlLog", + params, + &val); + return NS_OK; +} + +NS_IMETHODIMP +nsHTTPIndex::SetEncoding(const char *encoding) +{ + mEncoding = encoding; + return(NS_OK); +} + +NS_IMETHODIMP +nsHTTPIndex::GetEncoding(char **encoding) +{ + NS_PRECONDITION(encoding, "null ptr"); + if (! encoding) + return(NS_ERROR_NULL_POINTER); + + *encoding = ToNewCString(mEncoding); + if (!*encoding) + return(NS_ERROR_OUT_OF_MEMORY); + + return(NS_OK); +} + +NS_IMETHODIMP +nsHTTPIndex::OnStartRequest(nsIRequest *request, nsISupports* aContext) +{ + nsresult rv; + + mParser = do_CreateInstance(NS_DIRINDEXPARSER_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + rv = mParser->SetEncoding(mEncoding.get()); + if (NS_FAILED(rv)) return rv; + + rv = mParser->SetListener(this); + if (NS_FAILED(rv)) return rv; + + rv = mParser->OnStartRequest(request,aContext); + if (NS_FAILED(rv)) return rv; + + // This should only run once... + // Unless we don't have a container to start with + // (ie called from bookmarks as an rdf datasource) + if (mBindToGlobalObject && mRequestor) { + mBindToGlobalObject = false; + + nsCOMPtr<nsIGlobalObject> globalObject = do_GetInterface(mRequestor); + NS_ENSURE_TRUE(globalObject, NS_ERROR_FAILURE); + + // We might run script via JS_SetProperty, so we need an AutoEntryScript. + // This is Gecko specific and not in any spec. + dom::AutoEntryScript aes(globalObject, + "nsHTTPIndex set HTTPIndex property"); + JSContext* cx = aes.cx(); + + JS::Rooted<JSObject*> global(cx, JS::CurrentGlobalOrNull(cx)); + + // Using XPConnect, wrap the HTTP index object... + static NS_DEFINE_CID(kXPConnectCID, NS_XPCONNECT_CID); + nsCOMPtr<nsIXPConnect> xpc(do_GetService(kXPConnectCID, &rv)); + if (NS_FAILED(rv)) return rv; + + JS::Rooted<JSObject*> jsobj(cx); + rv = xpc->WrapNative(cx, + global, + static_cast<nsIHTTPIndex*>(this), + NS_GET_IID(nsIHTTPIndex), + jsobj.address()); + + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to xpconnect-wrap http-index"); + if (NS_FAILED(rv)) return rv; + + NS_ASSERTION(jsobj, + "unable to get jsobj from xpconnect wrapper"); + if (!jsobj) return NS_ERROR_UNEXPECTED; + + JS::Rooted<JS::Value> jslistener(cx, JS::ObjectValue(*jsobj)); + + // ...and stuff it into the global context + bool ok = JS_SetProperty(cx, global, "HTTPIndex", jslistener); + NS_ASSERTION(ok, "unable to set Listener property"); + if (!ok) + return NS_ERROR_FAILURE; + } + + if (!aContext) { + nsCOMPtr<nsIChannel> channel(do_QueryInterface(request)); + NS_ASSERTION(channel, "request should be a channel"); + + // lets hijack the notifications: + channel->SetNotificationCallbacks(this); + + // now create the top most resource + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + + nsAutoCString entryuriC; + rv = uri->GetSpec(entryuriC); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIRDFResource> entry; + rv = mDirRDF->GetResource(entryuriC, getter_AddRefs(entry)); + + NS_ConvertUTF8toUTF16 uriUnicode(entryuriC); + + nsCOMPtr<nsIRDFLiteral> URLVal; + rv = mDirRDF->GetLiteral(uriUnicode.get(), getter_AddRefs(URLVal)); + + Assert(entry, kNC_URL, URLVal, true); + mDirectory = do_QueryInterface(entry); + } + else + { + // Get the directory from the context + mDirectory = do_QueryInterface(aContext); + } + + if (!mDirectory) { + request->Cancel(NS_BINDING_ABORTED); + return NS_BINDING_ABORTED; + } + + // Mark the directory as "loading" + rv = Assert(mDirectory, kNC_Loading, + kTrueLiteral, true); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + + +NS_IMETHODIMP +nsHTTPIndex::OnStopRequest(nsIRequest *request, + nsISupports* aContext, + nsresult aStatus) +{ + // If mDirectory isn't set, then we should just bail. Either an + // error occurred and OnStartRequest() never got called, or + // something exploded in OnStartRequest(). + if (! mDirectory) + return NS_BINDING_ABORTED; + + mParser->OnStopRequest(request,aContext,aStatus); + + nsresult rv; + + nsXPIDLCString commentStr; + mParser->GetComment(getter_Copies(commentStr)); + + nsCOMPtr<nsIRDFLiteral> comment; + rv = mDirRDF->GetLiteral(NS_ConvertASCIItoUTF16(commentStr).get(), getter_AddRefs(comment)); + if (NS_FAILED(rv)) return rv; + + rv = Assert(mDirectory, kNC_Comment, comment, true); + if (NS_FAILED(rv)) return rv; + + // hack: Remove the 'loading' annotation (ignore errors) + AddElement(mDirectory, kNC_Loading, kTrueLiteral); + + return NS_OK; +} + + +NS_IMETHODIMP +nsHTTPIndex::OnDataAvailable(nsIRequest *request, + nsISupports* aContext, + nsIInputStream* aStream, + uint64_t aSourceOffset, + uint32_t aCount) +{ + // If mDirectory isn't set, then we should just bail. Either an + // error occurred and OnStartRequest() never got called, or + // something exploded in OnStartRequest(). + if (! mDirectory) + return NS_BINDING_ABORTED; + + return mParser->OnDataAvailable(request, mDirectory, aStream, aSourceOffset, aCount); +} + + +nsresult +nsHTTPIndex::OnIndexAvailable(nsIRequest* aRequest, nsISupports *aContext, + nsIDirIndex* aIndex) +{ + nsCOMPtr<nsIRDFResource> parentRes = do_QueryInterface(aContext); + if (!parentRes) { + NS_ERROR("Could not obtain parent resource"); + return(NS_ERROR_UNEXPECTED); + } + + const char* baseStr; + parentRes->GetValueConst(&baseStr); + if (! baseStr) { + NS_ERROR("Could not reconstruct base uri"); + return NS_ERROR_UNEXPECTED; + } + + // we found the filename; construct a resource for its entry + nsAutoCString entryuriC(baseStr); + + nsXPIDLCString filename; + nsresult rv = aIndex->GetLocation(getter_Copies(filename)); + if (NS_FAILED(rv)) return rv; + entryuriC.Append(filename); + + // if its a directory, make sure it ends with a trailing slash. + uint32_t type; + rv = aIndex->GetType(&type); + if (NS_FAILED(rv)) + return rv; + + bool isDirType = (type == nsIDirIndex::TYPE_DIRECTORY); + if (isDirType && entryuriC.Last() != '/') { + entryuriC.Append('/'); + } + + nsCOMPtr<nsIRDFResource> entry; + rv = mDirRDF->GetResource(entryuriC, getter_AddRefs(entry)); + + // At this point, we'll (hopefully) have found the filename and + // constructed a resource for it, stored in entry. So now take a + // second pass through the values and add as statements to the RDF + // datasource. + + if (entry && NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIRDFLiteral> lit; + nsString str; + + str.AssignWithConversion(entryuriC.get()); + + rv = mDirRDF->GetLiteral(str.get(), getter_AddRefs(lit)); + + if (NS_SUCCEEDED(rv)) { + rv = Assert(entry, kNC_URL, lit, true); + if (NS_FAILED(rv)) return rv; + + nsXPIDLString xpstr; + + // description + rv = aIndex->GetDescription(getter_Copies(xpstr)); + if (NS_FAILED(rv)) return rv; + if (xpstr.Last() == '/') + xpstr.Truncate(xpstr.Length() - 1); + + rv = mDirRDF->GetLiteral(xpstr.get(), getter_AddRefs(lit)); + if (NS_FAILED(rv)) return rv; + rv = Assert(entry, kNC_Description, lit, true); + if (NS_FAILED(rv)) return rv; + + // contentlength + int64_t size; + rv = aIndex->GetSize(&size); + if (NS_FAILED(rv)) return rv; + int64_t minus1 = UINT64_MAX; + if (size != minus1) { + int32_t intSize = int32_t(size); + // XXX RDF should support 64 bit integers (bug 240160) + nsCOMPtr<nsIRDFInt> val; + rv = mDirRDF->GetIntLiteral(intSize, getter_AddRefs(val)); + if (NS_FAILED(rv)) return rv; + rv = Assert(entry, kNC_ContentLength, val, true); + if (NS_FAILED(rv)) return rv; + } + + // lastmodified + PRTime tm; + rv = aIndex->GetLastModified(&tm); + if (NS_FAILED(rv)) return rv; + if (tm != -1) { + nsCOMPtr<nsIRDFDate> val; + rv = mDirRDF->GetDateLiteral(tm, getter_AddRefs(val)); + if (NS_FAILED(rv)) return rv; + rv = Assert(entry, kNC_LastModified, val, true); + } + + // filetype + uint32_t type; + rv = aIndex->GetType(&type); + switch (type) { + case nsIDirIndex::TYPE_UNKNOWN: + rv = mDirRDF->GetLiteral(u"UNKNOWN", getter_AddRefs(lit)); + break; + case nsIDirIndex::TYPE_DIRECTORY: + rv = mDirRDF->GetLiteral(u"DIRECTORY", getter_AddRefs(lit)); + break; + case nsIDirIndex::TYPE_FILE: + rv = mDirRDF->GetLiteral(u"FILE", getter_AddRefs(lit)); + break; + case nsIDirIndex::TYPE_SYMLINK: + rv = mDirRDF->GetLiteral(u"SYMLINK", getter_AddRefs(lit)); + break; + } + + if (NS_FAILED(rv)) return rv; + rv = Assert(entry, kNC_FileType, lit, true); + if (NS_FAILED(rv)) return rv; + } + + // Since the definition of a directory depends on the protocol, we would have + // to do string comparisons all the time. + // But we're told if we're a container right here - so save that fact + if (isDirType) + Assert(entry, kNC_IsContainer, kTrueLiteral, true); + else + Assert(entry, kNC_IsContainer, kFalseLiteral, true); + +// instead of +// rv = Assert(parentRes, kNC_Child, entry, true); +// if (NS_FAILED(rv)) return rv; +// defer insertion onto a timer so that the UI isn't starved + AddElement(parentRes, kNC_Child, entry); + } + + return rv; +} + +nsresult +nsHTTPIndex::OnInformationAvailable(nsIRequest *aRequest, + nsISupports *aCtxt, + const nsAString& aInfo) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +//---------------------------------------------------------------------- +// +// nsHTTPIndex implementation +// + +nsHTTPIndex::nsHTTPIndex() + : mBindToGlobalObject(true), + mRequestor(nullptr) +{ +} + + +nsHTTPIndex::nsHTTPIndex(nsIInterfaceRequestor* aRequestor) + : mBindToGlobalObject(true), + mRequestor(aRequestor) +{ +} + + +nsHTTPIndex::~nsHTTPIndex() +{ + // note: these are NOT statics due to the native of nsHTTPIndex + // where it may or may not be treated as a singleton + + if (mTimer) + { + // be sure to cancel the timer, as it holds a + // weak reference back to nsHTTPIndex + mTimer->Cancel(); + mTimer = nullptr; + } + + mConnectionList = nullptr; + mNodeList = nullptr; + + if (mDirRDF) + { + // UnregisterDataSource() may fail; just ignore errors + mDirRDF->UnregisterDataSource(this); + } +} + + + +nsresult +nsHTTPIndex::CommonInit() +{ + nsresult rv = NS_OK; + + // set initial/default encoding to ISO-8859-1 (not UTF-8) + mEncoding = "ISO-8859-1"; + + mDirRDF = do_GetService(kRDFServiceCID, &rv); + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service"); + if (NS_FAILED(rv)) { + return(rv); + } + + mInner = do_CreateInstance("@mozilla.org/rdf/datasource;1?name=in-memory-datasource", &rv); + + if (NS_FAILED(rv)) + return rv; + + mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "child"), + getter_AddRefs(kNC_Child)); + mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "loading"), + getter_AddRefs(kNC_Loading)); + mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Comment"), + getter_AddRefs(kNC_Comment)); + mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "URL"), + getter_AddRefs(kNC_URL)); + mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Name"), + getter_AddRefs(kNC_Description)); + mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Content-Length"), + getter_AddRefs(kNC_ContentLength)); + mDirRDF->GetResource(NS_LITERAL_CSTRING(WEB_NAMESPACE_URI "LastModifiedDate"), + getter_AddRefs(kNC_LastModified)); + mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Content-Type"), + getter_AddRefs(kNC_ContentType)); + mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "File-Type"), + getter_AddRefs(kNC_FileType)); + mDirRDF->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "IsContainer"), + getter_AddRefs(kNC_IsContainer)); + + rv = mDirRDF->GetLiteral(u"true", getter_AddRefs(kTrueLiteral)); + if (NS_FAILED(rv)) return(rv); + rv = mDirRDF->GetLiteral(u"false", getter_AddRefs(kFalseLiteral)); + if (NS_FAILED(rv)) return(rv); + + mConnectionList = nsArray::Create(); + + // note: don't register DS here + return rv; +} + + +nsresult +nsHTTPIndex::Init() +{ + nsresult rv; + + // set initial/default encoding to ISO-8859-1 (not UTF-8) + mEncoding = "ISO-8859-1"; + + rv = CommonInit(); + if (NS_FAILED(rv)) return(rv); + + // (do this last) register this as a named data source with the RDF service + rv = mDirRDF->RegisterDataSource(this, false); + if (NS_FAILED(rv)) return(rv); + + return(NS_OK); +} + + + +nsresult +nsHTTPIndex::Init(nsIURI* aBaseURL) +{ + NS_PRECONDITION(aBaseURL != nullptr, "null ptr"); + if (! aBaseURL) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + rv = CommonInit(); + if (NS_FAILED(rv)) return(rv); + + // note: don't register DS here (singleton case) + + rv = aBaseURL->GetSpec(mBaseURL); + if (NS_FAILED(rv)) return rv; + + // Mark the base url as a container + nsCOMPtr<nsIRDFResource> baseRes; + mDirRDF->GetResource(mBaseURL, getter_AddRefs(baseRes)); + Assert(baseRes, kNC_IsContainer, kTrueLiteral, true); + + return NS_OK; +} + + + +nsresult +nsHTTPIndex::Create(nsIURI* aBaseURL, nsIInterfaceRequestor* aRequestor, + nsIHTTPIndex** aResult) +{ + *aResult = nullptr; + + nsHTTPIndex* result = new nsHTTPIndex(aRequestor); + nsresult rv = result->Init(aBaseURL); + if (NS_SUCCEEDED(rv)) + { + NS_ADDREF(result); + *aResult = result; + } + else + { + delete result; + } + return rv; +} + +NS_IMETHODIMP +nsHTTPIndex::GetBaseURL(char** _result) +{ + *_result = ToNewCString(mBaseURL); + if (! *_result) + return NS_ERROR_OUT_OF_MEMORY; + + return NS_OK; +} + +NS_IMETHODIMP +nsHTTPIndex::GetDataSource(nsIRDFDataSource** _result) +{ + NS_ADDREF(*_result = this); + return NS_OK; +} + +// This function finds the destination when following a given nsIRDFResource +// If the resource has a URL attribute, we use that. If not, just use +// the uri. +// +// Do NOT try to get the destination of a uri in any other way +void nsHTTPIndex::GetDestination(nsIRDFResource* r, nsXPIDLCString& dest) { + // First try the URL attribute + nsCOMPtr<nsIRDFNode> node; + + GetTarget(r, kNC_URL, true, getter_AddRefs(node)); + nsCOMPtr<nsIRDFLiteral> url; + + if (node) + url = do_QueryInterface(node); + + if (!url) { + const char* temp; + r->GetValueConst(&temp); + dest.Adopt(temp ? strdup(temp) : 0); + } else { + const char16_t* uri; + url->GetValueConst(&uri); + dest.Adopt(ToNewUTF8String(nsDependentString(uri))); + } +} + +// rjc: isWellknownContainerURI() decides whether a URI is a container for which, +// when asked (say, by the template builder), we'll make a network connection +// to get its contents. For the moment, all we speak is ftp:// URLs, even though +// a) we can get "http-index" mimetypes for really anything +// b) we could easily handle file:// URLs here +// Q: Why don't we? +// A: The file system datasource ("rdf:file"); at some point, the two +// should be perhaps united. Until then, we can't aggregate both +// "rdf:file" and "http-index" (such as with bookmarks) because we'd +// get double the # of answers we really want... also, "rdf:file" is +// less expensive in terms of both memory usage as well as speed + + + +// We use an rdf attribute to mark if this is a container or not. +// Note that we still have to do string comparisons as a fallback +// because stuff like the personal toolbar and bookmarks check whether +// a URL is a container, and we have no attribute in that case. +bool +nsHTTPIndex::isWellknownContainerURI(nsIRDFResource *r) +{ + nsCOMPtr<nsIRDFNode> node; + GetTarget(r, kNC_IsContainer, true, getter_AddRefs(node)); + if (node) { + bool isContainerFlag; + if (NS_SUCCEEDED(node->EqualsNode(kTrueLiteral, &isContainerFlag))) + return isContainerFlag; + } + + nsXPIDLCString uri; + GetDestination(r, uri); + return uri.get() && !strncmp(uri, kFTPProtocol, sizeof(kFTPProtocol) - 1) && + (uri.Last() == '/'); +} + + +NS_IMETHODIMP +nsHTTPIndex::GetURI(char * *uri) +{ + NS_PRECONDITION(uri != nullptr, "null ptr"); + if (! uri) + return(NS_ERROR_NULL_POINTER); + + if ((*uri = strdup("rdf:httpindex")) == nullptr) + return(NS_ERROR_OUT_OF_MEMORY); + + return(NS_OK); +} + + + +NS_IMETHODIMP +nsHTTPIndex::GetSource(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, + nsIRDFResource **_retval) +{ + nsresult rv = NS_ERROR_UNEXPECTED; + + *_retval = nullptr; + + if (mInner) + { + rv = mInner->GetSource(aProperty, aTarget, aTruthValue, _retval); + } + return(rv); +} + +NS_IMETHODIMP +nsHTTPIndex::GetSources(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, + nsISimpleEnumerator **_retval) +{ + nsresult rv = NS_ERROR_UNEXPECTED; + + if (mInner) + { + rv = mInner->GetSources(aProperty, aTarget, aTruthValue, _retval); + } + else + { + rv = NS_NewEmptyEnumerator(_retval); + } + return(rv); +} + +NS_IMETHODIMP +nsHTTPIndex::GetTarget(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue, + nsIRDFNode **_retval) +{ + nsresult rv = NS_ERROR_UNEXPECTED; + + *_retval = nullptr; + + if ((aTruthValue) && (aProperty == kNC_Child) && isWellknownContainerURI(aSource)) + { + // fake out the generic builder (i.e. return anything in this case) + // so that search containers never appear to be empty + NS_IF_ADDREF(aSource); + *_retval = aSource; + return(NS_OK); + } + + if (mInner) + { + rv = mInner->GetTarget(aSource, aProperty, aTruthValue, _retval); + } + return(rv); +} + +NS_IMETHODIMP +nsHTTPIndex::GetTargets(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue, + nsISimpleEnumerator **_retval) +{ + nsresult rv = NS_ERROR_UNEXPECTED; + + if (mInner) + { + rv = mInner->GetTargets(aSource, aProperty, aTruthValue, _retval); + } + else + { + rv = NS_NewEmptyEnumerator(_retval); + } + + if ((aProperty == kNC_Child) && isWellknownContainerURI(aSource)) + { + bool doNetworkRequest = true; + if (NS_SUCCEEDED(rv) && (_retval)) + { + // check and see if we already have data for the search in question; + // if we do, don't bother doing the search again + bool hasResults; + if (NS_SUCCEEDED((*_retval)->HasMoreElements(&hasResults)) && + hasResults) + doNetworkRequest = false; + } + + // Note: if we need to do a network request, do it out-of-band + // (because the XUL template builder isn't re-entrant) + // by using a global connection list and an immediately-firing timer + if (doNetworkRequest && mConnectionList) + { + uint32_t connectionIndex; + nsresult idx_rv = mConnectionList->IndexOf(0, aSource, &connectionIndex); + if (NS_FAILED(idx_rv)) + { + // add aSource into list of connections to make + mConnectionList->AppendElement(aSource, /*weak =*/ false); + + // if we don't have a timer about to fire, create one + // which should fire as soon as possible (out-of-band) + if (!mTimer) + { + mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create a timer"); + if (NS_SUCCEEDED(rv)) + { + mTimer->InitWithFuncCallback(nsHTTPIndex::FireTimer, this, 1, + nsITimer::TYPE_ONE_SHOT); + // Note: don't addref "this" as we'll cancel the + // timer in the httpIndex destructor + } + } + } + } + } + + return(rv); +} + + +nsresult +nsHTTPIndex::AddElement(nsIRDFResource *parent, nsIRDFResource *prop, nsIRDFNode *child) +{ + nsresult rv; + + if (!mNodeList) + { + mNodeList = nsArray::Create(); + } + + // order required: parent, prop, then child + mNodeList->AppendElement(parent, /*weak =*/ false); + mNodeList->AppendElement(prop, /*weak =*/ false); + mNodeList->AppendElement(child, /*weak = */ false); + + if (!mTimer) + { + mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to create a timer"); + if (NS_FAILED(rv)) return(rv); + + mTimer->InitWithFuncCallback(nsHTTPIndex::FireTimer, this, 1, + nsITimer::TYPE_ONE_SHOT); + // Note: don't addref "this" as we'll cancel the + // timer in the httpIndex destructor + } + + return(NS_OK); +} + +void +nsHTTPIndex::FireTimer(nsITimer* aTimer, void* aClosure) +{ + nsHTTPIndex *httpIndex = static_cast<nsHTTPIndex *>(aClosure); + if (!httpIndex) + return; + + // don't return out of this loop as mTimer may need to be cancelled afterwards + uint32_t numItems = 0; + if (httpIndex->mConnectionList) + { + httpIndex->mConnectionList->GetLength(&numItems); + if (numItems > 0) + { + nsCOMPtr<nsIRDFResource> source = + do_QueryElementAt(httpIndex->mConnectionList, 0); + httpIndex->mConnectionList->RemoveElementAt(0); + + nsXPIDLCString uri; + if (source) { + httpIndex->GetDestination(source, uri); + } + + if (!uri) { + NS_ERROR("Could not reconstruct uri"); + return; + } + + nsresult rv = NS_OK; + nsCOMPtr<nsIURI> url; + + rv = NS_NewURI(getter_AddRefs(url), uri.get()); + nsCOMPtr<nsIChannel> channel; + if (NS_SUCCEEDED(rv) && (url)) { + rv = NS_NewChannel(getter_AddRefs(channel), + url, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + } + if (NS_SUCCEEDED(rv) && (channel)) { + channel->SetNotificationCallbacks(httpIndex); + rv = channel->AsyncOpen2(httpIndex); + } + } + } + + if (httpIndex->mNodeList) + { + httpIndex->mNodeList->GetLength(&numItems); + if (numItems > 0) + { + // account for order required: src, prop, then target + numItems /=3; + if (numItems > 10) + numItems = 10; + + int32_t loop; + for (loop=0; loop<(int32_t)numItems; loop++) + { + nsCOMPtr<nsIRDFResource> src = do_QueryElementAt(httpIndex->mNodeList, 0); + httpIndex->mNodeList->RemoveElementAt(0); + + nsCOMPtr<nsIRDFResource> prop = do_QueryElementAt(httpIndex->mNodeList, 0); + httpIndex->mNodeList->RemoveElementAt(0); + + nsCOMPtr<nsIRDFNode> target = do_QueryElementAt(httpIndex->mNodeList, 0); + httpIndex->mNodeList->RemoveElementAt(0); + + if (src && prop && target) + { + if (prop.get() == httpIndex->kNC_Loading) + { + httpIndex->Unassert(src, prop, target); + } + else + { + httpIndex->Assert(src, prop, target, true); + } + } + } + } + } + + bool refireTimer = false; + // check both lists to see if the timer needs to continue firing + if (httpIndex->mConnectionList) + { + httpIndex->mConnectionList->GetLength(&numItems); + if (numItems > 0) + { + refireTimer = true; + } + else + { + httpIndex->mConnectionList->Clear(); + } + } + + if (httpIndex->mNodeList) + { + httpIndex->mNodeList->GetLength(&numItems); + if (numItems > 0) + { + refireTimer = true; + } + else + { + httpIndex->mNodeList->Clear(); + } + } + + // be sure to cancel the timer, as it holds a + // weak reference back to nsHTTPIndex + httpIndex->mTimer->Cancel(); + httpIndex->mTimer = nullptr; + + // after firing off any/all of the connections be sure + // to cancel the timer if we don't need to refire it + if (refireTimer) + { + httpIndex->mTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (httpIndex->mTimer) + { + httpIndex->mTimer->InitWithFuncCallback(nsHTTPIndex::FireTimer, aClosure, 10, + nsITimer::TYPE_ONE_SHOT); + // Note: don't addref "this" as we'll cancel the + // timer in the httpIndex destructor + } + } +} + +NS_IMETHODIMP +nsHTTPIndex::Assert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget, + bool aTruthValue) +{ + nsresult rv = NS_ERROR_UNEXPECTED; + if (mInner) + { + rv = mInner->Assert(aSource, aProperty, aTarget, aTruthValue); + } + return(rv); +} + +NS_IMETHODIMP +nsHTTPIndex::Unassert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget) +{ + nsresult rv = NS_ERROR_UNEXPECTED; + if (mInner) + { + rv = mInner->Unassert(aSource, aProperty, aTarget); + } + return(rv); +} + +NS_IMETHODIMP +nsHTTPIndex::Change(nsIRDFResource *aSource, nsIRDFResource *aProperty, + nsIRDFNode *aOldTarget, nsIRDFNode *aNewTarget) +{ + nsresult rv = NS_ERROR_UNEXPECTED; + if (mInner) + { + rv = mInner->Change(aSource, aProperty, aOldTarget, aNewTarget); + } + return(rv); +} + +NS_IMETHODIMP +nsHTTPIndex::Move(nsIRDFResource *aOldSource, nsIRDFResource *aNewSource, + nsIRDFResource *aProperty, nsIRDFNode *aTarget) +{ + nsresult rv = NS_ERROR_UNEXPECTED; + if (mInner) + { + rv = mInner->Move(aOldSource, aNewSource, aProperty, aTarget); + } + return(rv); +} + +NS_IMETHODIMP +nsHTTPIndex::HasAssertion(nsIRDFResource *aSource, nsIRDFResource *aProperty, + nsIRDFNode *aTarget, bool aTruthValue, bool *_retval) +{ + nsresult rv = NS_ERROR_UNEXPECTED; + if (mInner) + { + rv = mInner->HasAssertion(aSource, aProperty, aTarget, aTruthValue, _retval); + } + return(rv); +} + +NS_IMETHODIMP +nsHTTPIndex::AddObserver(nsIRDFObserver *aObserver) +{ + nsresult rv = NS_ERROR_UNEXPECTED; + if (mInner) + { + rv = mInner->AddObserver(aObserver); + } + return(rv); +} + +NS_IMETHODIMP +nsHTTPIndex::RemoveObserver(nsIRDFObserver *aObserver) +{ + nsresult rv = NS_ERROR_UNEXPECTED; + if (mInner) + { + rv = mInner->RemoveObserver(aObserver); + } + return(rv); +} + +NS_IMETHODIMP +nsHTTPIndex::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *result) +{ + if (!mInner) { + *result = false; + return NS_OK; + } + return mInner->HasArcIn(aNode, aArc, result); +} + +NS_IMETHODIMP +nsHTTPIndex::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *result) +{ + if (aArc == kNC_Child && isWellknownContainerURI(aSource)) { + *result = true; + return NS_OK; + } + + if (mInner) { + return mInner->HasArcOut(aSource, aArc, result); + } + + *result = false; + return NS_OK; +} + +NS_IMETHODIMP +nsHTTPIndex::ArcLabelsIn(nsIRDFNode *aNode, nsISimpleEnumerator **_retval) +{ + nsresult rv = NS_ERROR_UNEXPECTED; + if (mInner) + { + rv = mInner->ArcLabelsIn(aNode, _retval); + } + return(rv); +} + +NS_IMETHODIMP +nsHTTPIndex::ArcLabelsOut(nsIRDFResource *aSource, nsISimpleEnumerator **_retval) +{ + *_retval = nullptr; + + nsCOMPtr<nsISimpleEnumerator> child, anonArcs; + if (isWellknownContainerURI(aSource)) + { + NS_NewSingletonEnumerator(getter_AddRefs(child), kNC_Child); + } + + if (mInner) + { + mInner->ArcLabelsOut(aSource, getter_AddRefs(anonArcs)); + } + + return NS_NewUnionEnumerator(_retval, child, anonArcs); +} + +NS_IMETHODIMP +nsHTTPIndex::GetAllResources(nsISimpleEnumerator **_retval) +{ + nsresult rv = NS_ERROR_UNEXPECTED; + if (mInner) + { + rv = mInner->GetAllResources(_retval); + } + return(rv); +} + +NS_IMETHODIMP +nsHTTPIndex::IsCommandEnabled(nsISupports *aSources, nsIRDFResource *aCommand, + nsISupports *aArguments, bool *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsHTTPIndex::DoCommand(nsISupports *aSources, nsIRDFResource *aCommand, + nsISupports *aArguments) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsHTTPIndex::BeginUpdateBatch() +{ + return mInner->BeginUpdateBatch(); +} + +NS_IMETHODIMP +nsHTTPIndex::EndUpdateBatch() +{ + return mInner->EndUpdateBatch(); +} + +NS_IMETHODIMP +nsHTTPIndex::GetAllCmds(nsIRDFResource *aSource, nsISimpleEnumerator **_retval) +{ + nsresult rv = NS_ERROR_UNEXPECTED; + if (mInner) + { + rv = mInner->GetAllCmds(aSource, _retval); + } + return(rv); +} + + +//---------------------------------------------------------------------- +// +// nsDirectoryViewerFactory +// +nsDirectoryViewerFactory::nsDirectoryViewerFactory() +{ +} + + + +nsDirectoryViewerFactory::~nsDirectoryViewerFactory() +{ +} + + +NS_IMPL_ISUPPORTS(nsDirectoryViewerFactory, nsIDocumentLoaderFactory) + + + +NS_IMETHODIMP +nsDirectoryViewerFactory::CreateInstance(const char *aCommand, + nsIChannel* aChannel, + nsILoadGroup* aLoadGroup, + const nsACString& aContentType, + nsIDocShell* aContainer, + nsISupports* aExtraInfo, + nsIStreamListener** aDocListenerResult, + nsIContentViewer** aDocViewerResult) +{ + nsresult rv; + + bool viewSource = FindInReadable(NS_LITERAL_CSTRING("view-source"), + aContentType); + + if (!viewSource && + Preferences::GetInt("network.dir.format", FORMAT_XUL) == FORMAT_XUL) { + // ... and setup the original channel's content type + (void)aChannel->SetContentType(NS_LITERAL_CSTRING("application/vnd.mozilla.xul+xml")); + + // This is where we shunt the HTTP/Index stream into our datasource, + // and open the directory viewer XUL file as the content stream to + // load in its place. + + // Create a dummy loader that will load a stub XUL document. + nsCOMPtr<nsICategoryManager> catMan(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + nsXPIDLCString contractID; + rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", "application/vnd.mozilla.xul+xml", + getter_Copies(contractID)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIDocumentLoaderFactory> factory(do_GetService(contractID, &rv)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), "chrome://communicator/content/directory/directory.xul"); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewChannel(getter_AddRefs(channel), + uri, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + aLoadGroup); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIStreamListener> listener; + rv = factory->CreateInstance(aCommand, channel, aLoadGroup, + NS_LITERAL_CSTRING("application/vnd.mozilla.xul+xml"), + aContainer, aExtraInfo, getter_AddRefs(listener), + aDocViewerResult); + if (NS_FAILED(rv)) return rv; + + rv = channel->AsyncOpen2(listener); + if (NS_FAILED(rv)) return rv; + + // Create an HTTPIndex object so that we can stuff it into the script context + nsCOMPtr<nsIURI> baseuri; + rv = aChannel->GetURI(getter_AddRefs(baseuri)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIInterfaceRequestor> requestor = do_QueryInterface(aContainer,&rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIHTTPIndex> httpindex; + rv = nsHTTPIndex::Create(baseuri, requestor, getter_AddRefs(httpindex)); + if (NS_FAILED(rv)) return rv; + + // Now shanghai the stream into our http-index parsing datasource + // wrapper beastie. + listener = do_QueryInterface(httpindex,&rv); + *aDocListenerResult = listener.get(); + NS_ADDREF(*aDocListenerResult); + + return NS_OK; + } + + // setup the original channel's content type + (void)aChannel->SetContentType(NS_LITERAL_CSTRING("text/html")); + + // Otherwise, lets use the html listing + nsCOMPtr<nsICategoryManager> catMan(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + nsXPIDLCString contractID; + rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", "text/html", + getter_Copies(contractID)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIDocumentLoaderFactory> factory(do_GetService(contractID, &rv)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIStreamListener> listener; + + if (viewSource) { + rv = factory->CreateInstance("view-source", aChannel, aLoadGroup, + NS_LITERAL_CSTRING("text/html; x-view-type=view-source"), + aContainer, aExtraInfo, getter_AddRefs(listener), + aDocViewerResult); + } else { + rv = factory->CreateInstance("view", aChannel, aLoadGroup, + NS_LITERAL_CSTRING("text/html"), + aContainer, aExtraInfo, getter_AddRefs(listener), + aDocViewerResult); + } + + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIStreamConverterService> scs = do_GetService("@mozilla.org/streamConverters;1", &rv); + if (NS_FAILED(rv)) return rv; + + rv = scs->AsyncConvertData("application/http-index-format", + "text/html", + listener, + nullptr, + aDocListenerResult); + + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + + + +NS_IMETHODIMP +nsDirectoryViewerFactory::CreateInstanceForDocument(nsISupports* aContainer, + nsIDocument* aDocument, + const char *aCommand, + nsIContentViewer** aDocViewerResult) +{ + NS_NOTYETIMPLEMENTED("didn't expect to get here"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDirectoryViewerFactory::CreateBlankDocument(nsILoadGroup *aLoadGroup, + nsIPrincipal *aPrincipal, + nsIDocument **_retval) { + + NS_NOTYETIMPLEMENTED("didn't expect to get here"); + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/xpfe/components/directory/nsDirectoryViewer.h b/xpfe/components/directory/nsDirectoryViewer.h new file mode 100644 index 000000000..05b68f1b6 --- /dev/null +++ b/xpfe/components/directory/nsDirectoryViewer.h @@ -0,0 +1,126 @@ +/* -*- 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/. */ + +#ifndef nsdirectoryviewer__h____ +#define nsdirectoryviewer__h____ + +#include "nsCOMPtr.h" +#include "nsIStreamListener.h" +#include "nsIContentViewer.h" +#include "nsIHTTPIndex.h" +#include "nsIRDFService.h" +#include "nsIRDFDataSource.h" +#include "nsIRDFLiteral.h" +#include "nsIDocumentLoaderFactory.h" +#include "nsITimer.h" +#include "nsXPIDLString.h" +#include "nsIDirIndexListener.h" +#include "nsIFTPChannel.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIInterfaceRequestor.h" +#include "nsIURI.h" + +class nsIMutableArray; + +class nsDirectoryViewerFactory : public nsIDocumentLoaderFactory +{ +public: + nsDirectoryViewerFactory(); + + // nsISupports interface + NS_DECL_ISUPPORTS + NS_DECL_NSIDOCUMENTLOADERFACTORY + +protected: + virtual ~nsDirectoryViewerFactory(); +}; + +class nsHTTPIndex final : public nsIHTTPIndex, + public nsIRDFDataSource, + public nsIStreamListener, + public nsIDirIndexListener, + public nsIInterfaceRequestor, + public nsIFTPEventSink +{ +private: + + // note: these are NOT statics due to the native of nsHTTPIndex + // where it may or may not be treated as a singleton + + nsCOMPtr<nsIRDFResource> kNC_Child; + nsCOMPtr<nsIRDFResource> kNC_Comment; + nsCOMPtr<nsIRDFResource> kNC_Loading; + nsCOMPtr<nsIRDFResource> kNC_URL; + nsCOMPtr<nsIRDFResource> kNC_Description; + nsCOMPtr<nsIRDFResource> kNC_ContentLength; + nsCOMPtr<nsIRDFResource> kNC_LastModified; + nsCOMPtr<nsIRDFResource> kNC_ContentType; + nsCOMPtr<nsIRDFResource> kNC_FileType; + nsCOMPtr<nsIRDFResource> kNC_IsContainer; + nsCOMPtr<nsIRDFLiteral> kTrueLiteral; + nsCOMPtr<nsIRDFLiteral> kFalseLiteral; + + nsCOMPtr<nsIRDFService> mDirRDF; + +protected: + // We grab a reference to the content viewer container (which + // indirectly owns us) so that we can insert ourselves as a global + // in the script context _after_ the XUL doc has been embedded into + // content viewer. We'll know that this has happened once we receive + // an OnStartRequest() notification + + nsCOMPtr<nsIRDFDataSource> mInner; + nsCOMPtr<nsIMutableArray> mConnectionList; + nsCOMPtr<nsIMutableArray> mNodeList; + nsCOMPtr<nsITimer> mTimer; + nsCOMPtr<nsIDirIndexParser> mParser; + nsCString mBaseURL; + nsCString mEncoding; + bool mBindToGlobalObject; + nsIInterfaceRequestor* mRequestor; // WEAK + nsCOMPtr<nsIRDFResource> mDirectory; + + explicit nsHTTPIndex(nsIInterfaceRequestor* aRequestor); + nsresult CommonInit(void); + nsresult Init(nsIURI* aBaseURL); + void GetDestination(nsIRDFResource* r, nsXPIDLCString& dest); + bool isWellknownContainerURI(nsIRDFResource *r); + nsresult AddElement(nsIRDFResource *parent, nsIRDFResource *prop, + nsIRDFNode *child); + + static void FireTimer(nsITimer* aTimer, void* aClosure); + + virtual ~nsHTTPIndex(); + +public: + nsHTTPIndex(); + nsresult Init(void); + + static nsresult Create(nsIURI* aBaseURI, nsIInterfaceRequestor* aContainer, + nsIHTTPIndex** aResult); + + // nsIHTTPIndex interface + NS_DECL_NSIHTTPINDEX + + // NSIRDFDataSource interface + NS_DECL_NSIRDFDATASOURCE + + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + NS_DECL_NSIDIRINDEXLISTENER + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIFTPEVENTSINK + + // nsISupports interface + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsHTTPIndex, nsIHTTPIndex) +}; + +// {82776710-5690-11d3-BE36-00104BDE6048} +#define NS_DIRECTORYVIEWERFACTORY_CID \ +{ 0x82776710, 0x5690, 0x11d3, { 0xbe, 0x36, 0x0, 0x10, 0x4b, 0xde, 0x60, 0x48 } } + +#endif // nsdirectoryviewer__h____ diff --git a/xpfe/components/directory/nsIHTTPIndex.idl b/xpfe/components/directory/nsIHTTPIndex.idl new file mode 100644 index 000000000..47697172b --- /dev/null +++ b/xpfe/components/directory/nsIHTTPIndex.idl @@ -0,0 +1,50 @@ +/* -*- 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/. */ + +/* + + The interface to an HTTP index + +*/ + +#include "nsISupports.idl" + +interface nsIStreamListener; +interface nsIRDFDataSource; +interface nsIRDFNode; +interface nsIRDFResource; + +[scriptable, uuid(6F2BDBD0-58C3-11d3-BE36-00104BDE6048)] +interface nsIHTTPIndex : nsISupports +{ + /** + * The base URL of the HTTP index + */ + readonly attribute string BaseURL; + + /** + * The RDF datasource that contains the HTTP index information. + */ + readonly attribute nsIRDFDataSource DataSource; + + /** + * The charset to use for decoding FTP filenames + */ + attribute string encoding; +}; + +%{C++ + +// {{2587e382-1324-11d4-a652-eadbb2be3484} +#define NS_HTTPINDEX_SERVICE_CID \ +{ 0x2587e382, 0x1324, 0x11d4, { 0xa6, 0x52, 0xea, 0xdb, 0xb2, 0xbe, 0x34, 0x84 } } + +#define NS_HTTPINDEX_SERVICE_CONTRACTID \ + "@mozilla.org/browser/httpindex-service;1" + +#define NS_HTTPINDEX_DATASOURCE_CONTRACTID \ + "@mozilla.org/rdf/datasource;1?name=httpindex" + +%} diff --git a/xpfe/components/moz.build b/xpfe/components/moz.build new file mode 100644 index 000000000..cb23f336e --- /dev/null +++ b/xpfe/components/moz.build @@ -0,0 +1,12 @@ +# -*- 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/. + +DIRS += [ + 'windowds', + 'directory', + 'build', +] + diff --git a/xpfe/components/windowds/moz.build b/xpfe/components/windowds/moz.build new file mode 100644 index 000000000..0e7536b16 --- /dev/null +++ b/xpfe/components/windowds/moz.build @@ -0,0 +1,17 @@ +# -*- 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/. + +XPIDL_SOURCES += [ + 'nsIWindowDataSource.idl', +] + +XPIDL_MODULE = 'windowds' + +SOURCES += [ + 'nsWindowDataSource.cpp', +] + +FINAL_LIBRARY = 'xul' diff --git a/xpfe/components/windowds/nsIWindowDataSource.idl b/xpfe/components/windowds/nsIWindowDataSource.idl new file mode 100644 index 000000000..6143a4317 --- /dev/null +++ b/xpfe/components/windowds/nsIWindowDataSource.idl @@ -0,0 +1,17 @@ +/* -*- 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 "nsIDOMWindow.idl" + +// interface for accessing RDF-specific data from the window datasource +[scriptable, uuid(3722A5B9-5323-4ed0-BB1A-8299F27A4E89)] +interface nsIWindowDataSource : nsISupports +{ + /** + * for the given resource name, return the window + */ + nsIDOMWindow getWindowForResource(in string inResource); +}; diff --git a/xpfe/components/windowds/nsWindowDataSource.cpp b/xpfe/components/windowds/nsWindowDataSource.cpp new file mode 100644 index 000000000..3e7a42060 --- /dev/null +++ b/xpfe/components/windowds/nsWindowDataSource.cpp @@ -0,0 +1,519 @@ +/* -*- 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 "nsWindowDataSource.h" +#include "nsIXULWindow.h" +#include "rdf.h" +#include "nsIRDFContainerUtils.h" +#include "nsIServiceManager.h" +#include "nsReadableUtils.h" +#include "nsIObserverService.h" +#include "nsIWindowMediator.h" +#include "nsXPCOMCID.h" +#include "mozilla/ModuleUtils.h" +#include "nsString.h" + +// just to do the reverse-lookup! sheesh. +#include "nsIInterfaceRequestorUtils.h" +#include "nsIDocShell.h" + +uint32_t nsWindowDataSource::windowCount = 0; + +nsIRDFResource* nsWindowDataSource::kNC_Name = nullptr; +nsIRDFResource* nsWindowDataSource::kNC_WindowRoot = nullptr; +nsIRDFResource* nsWindowDataSource::kNC_KeyIndex = nullptr; + +nsIRDFService* nsWindowDataSource::gRDFService = nullptr; + +uint32_t nsWindowDataSource::gRefCnt = 0; + +#define URINC_WINDOWROOT "NC:WindowMediatorRoot" +#define URINC_NAME NC_NAMESPACE_URI "Name" +#define URINC_KEYINDEX NC_NAMESPACE_URI "KeyIndex" + +nsresult +nsWindowDataSource::Init() +{ + nsresult rv; + + if (gRefCnt++ == 0) { + rv = CallGetService("@mozilla.org/rdf/rdf-service;1", &gRDFService); + if (NS_FAILED(rv)) return rv; + + gRDFService->GetResource(NS_LITERAL_CSTRING(URINC_WINDOWROOT), &kNC_WindowRoot); + gRDFService->GetResource(NS_LITERAL_CSTRING(URINC_NAME), &kNC_Name); + gRDFService->GetResource(NS_LITERAL_CSTRING(URINC_KEYINDEX), &kNC_KeyIndex); + } + + mInner = do_CreateInstance("@mozilla.org/rdf/datasource;1?name=in-memory-datasource", &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIRDFContainerUtils> rdfc = + do_GetService("@mozilla.org/rdf/container-utils;1", &rv); + if (NS_FAILED(rv)) return rv; + + rv = rdfc->MakeSeq(this, kNC_WindowRoot, getter_AddRefs(mContainer)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIWindowMediator> windowMediator = + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + rv = windowMediator->AddListener(this); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIObserverService> observerService = + do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, + false); + } + return NS_OK; +} + +nsWindowDataSource::~nsWindowDataSource() +{ + if (--gRefCnt == 0) { + NS_IF_RELEASE(kNC_Name); + NS_IF_RELEASE(kNC_KeyIndex); + NS_IF_RELEASE(kNC_WindowRoot); + NS_IF_RELEASE(gRDFService); + } +} + +NS_IMETHODIMP +nsWindowDataSource::Observe(nsISupports *aSubject, const char* aTopic, const char16_t *aData) +{ + if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + // release these objects so that they release their reference + // to us + mContainer = nullptr; + mInner = nullptr; + } + + return NS_OK; +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsWindowDataSource) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsWindowDataSource) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsWindowDataSource) + // XXX mContainer? + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInner) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsWindowDataSource) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsWindowDataSource) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsWindowDataSource) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsIWindowMediatorListener) + NS_INTERFACE_MAP_ENTRY(nsIWindowDataSource) + NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver) +NS_INTERFACE_MAP_END + +// nsIWindowMediatorListener implementation +// handle notifications from the window mediator and reflect them into +// RDF + +NS_IMETHODIMP +nsWindowDataSource::OnWindowTitleChange(nsIXULWindow *window, + const char16_t *newTitle) +{ + nsresult rv; + + nsCOMPtr<nsIRDFResource> windowResource; + mWindowResources.Get(window, getter_AddRefs(windowResource)); + + // oops, make sure this window is in the hashtable! + if (!windowResource) { + OnOpenWindow(window); + mWindowResources.Get(window, getter_AddRefs(windowResource)); + } + + NS_ENSURE_TRUE(windowResource, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIRDFLiteral> newTitleLiteral; + rv = gRDFService->GetLiteral(newTitle, getter_AddRefs(newTitleLiteral)); + NS_ENSURE_SUCCESS(rv, rv); + + // get the old title + nsCOMPtr<nsIRDFNode> oldTitleNode; + rv = GetTarget(windowResource, kNC_Name, true, + getter_AddRefs(oldTitleNode)); + + // assert the change + if (NS_SUCCEEDED(rv) && oldTitleNode) + // has an existing window title, update it + rv = Change(windowResource, kNC_Name, oldTitleNode, newTitleLiteral); + else + // removed from the tasklist + rv = Assert(windowResource, kNC_Name, newTitleLiteral, true); + + if (rv != NS_RDF_ASSERTION_ACCEPTED) + { + NS_ERROR("unable to set window name"); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowDataSource::OnOpenWindow(nsIXULWindow *window) +{ + nsAutoCString windowId(NS_LITERAL_CSTRING("window-")); + windowId.AppendInt(windowCount++, 10); + + nsCOMPtr<nsIRDFResource> windowResource; + gRDFService->GetResource(windowId, getter_AddRefs(windowResource)); + + mWindowResources.Put(window, windowResource); + + // assert the new window + if (mContainer) + mContainer->AppendElement(windowResource); + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowDataSource::OnCloseWindow(nsIXULWindow *window) +{ + nsresult rv; + nsCOMPtr<nsIRDFResource> resource; + mWindowResources.Get(window, getter_AddRefs(resource)); + if (!resource) { + return NS_ERROR_UNEXPECTED; + } + + mWindowResources.Remove(window); + + // make sure we're not shutting down + if (!mContainer) return NS_OK; + + nsCOMPtr<nsIRDFNode> oldKeyNode; + nsCOMPtr<nsIRDFInt> oldKeyInt; + + // get the old keyIndex, if any + rv = GetTarget(resource, kNC_KeyIndex, true, + getter_AddRefs(oldKeyNode)); + if (NS_SUCCEEDED(rv) && (rv != NS_RDF_NO_VALUE)) + oldKeyInt = do_QueryInterface(oldKeyNode); + + + // update RDF and keyindex - from this point forward we'll ignore + // errors, because they just indicate some kind of RDF inconsistency + int32_t winIndex = -1; + rv = mContainer->IndexOf(resource, &winIndex); + + if (NS_FAILED(rv)) + return NS_OK; + + // unassert the old window, ignore any error + mContainer->RemoveElement(resource, true); + + nsCOMPtr<nsISimpleEnumerator> children; + rv = mContainer->GetElements(getter_AddRefs(children)); + if (NS_FAILED(rv)) + return NS_OK; + + bool more = false; + + while (NS_SUCCEEDED(rv = children->HasMoreElements(&more)) && more) { + nsCOMPtr<nsISupports> sup; + rv = children->GetNext(getter_AddRefs(sup)); + if (NS_FAILED(rv)) + break; + + nsCOMPtr<nsIRDFResource> windowResource = do_QueryInterface(sup, &rv); + if (NS_FAILED(rv)) + continue; + + int32_t currentIndex = -1; + mContainer->IndexOf(windowResource, ¤tIndex); + + // can skip updating windows with lower indexes + // than the window that was removed + if (currentIndex < winIndex) + continue; + + nsCOMPtr<nsIRDFNode> newKeyNode; + nsCOMPtr<nsIRDFInt> newKeyInt; + + rv = GetTarget(windowResource, kNC_KeyIndex, true, + getter_AddRefs(newKeyNode)); + if (NS_SUCCEEDED(rv) && (rv != NS_RDF_NO_VALUE)) + newKeyInt = do_QueryInterface(newKeyNode); + + // changing from one key index to another + if (oldKeyInt && newKeyInt) + Change(windowResource, kNC_KeyIndex, oldKeyInt, newKeyInt); + // creating a new keyindex - probably window going + // from (none) to "9" + else if (newKeyInt) + Assert(windowResource, kNC_KeyIndex, newKeyInt, true); + + // somehow inserting a window above this one, + // "9" to (none) + else if (oldKeyInt) + Unassert(windowResource, kNC_KeyIndex, oldKeyInt); + + } + return NS_OK; +} + +// nsIWindowDataSource implementation + +NS_IMETHODIMP +nsWindowDataSource::GetWindowForResource(const char *aResourceString, + nsIDOMWindow** aResult) +{ + if (NS_WARN_IF(!aResourceString)) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsIRDFResource> windowResource; + gRDFService->GetResource(nsDependentCString(aResourceString), + getter_AddRefs(windowResource)); + + // now reverse-lookup in the hashtable + for (auto iter = mWindowResources.Iter(); !iter.Done(); iter.Next()) { + nsIXULWindow* window = iter.Key(); + nsIRDFResource* resource = iter.UserData(); + + if (resource == windowResource) { + // This sucks, we have to jump through docshell to go from + // nsIXULWindow -> nsIDOMWindow. + nsCOMPtr<nsIDocShell> docShell; + window->GetDocShell(getter_AddRefs(docShell)); + + if (docShell) { + nsCOMPtr<nsIDOMWindow> result = do_GetInterface(docShell); + + *aResult = result; + NS_IF_ADDREF(*aResult); + } + break; + } + } + + return NS_OK; +} + + +// nsIRDFDataSource implementation +// mostly, we just forward to mInner, except: +// GetURI() - need to return "rdf:window-mediator" +// GetTarget() - need to handle kNC_KeyIndex + + +NS_IMETHODIMP nsWindowDataSource::GetURI(char * *aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + + *aURI = ToNewCString(NS_LITERAL_CSTRING("rdf:window-mediator")); + + if (!*aURI) + return NS_ERROR_OUT_OF_MEMORY; + + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::GetTarget(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue, nsIRDFNode **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + // add extra nullptr checking for top-crash bug # 146466 + if (!gRDFService) return NS_RDF_NO_VALUE; + if (!mInner) return NS_RDF_NO_VALUE; + if (!mContainer) return NS_RDF_NO_VALUE; + // special case kNC_KeyIndex before we forward to mInner + if (aProperty == kNC_KeyIndex) { + + int32_t theIndex = 0; + nsresult rv = mContainer->IndexOf(aSource, &theIndex); + if (NS_FAILED(rv)) return rv; + + // only allow the range of 1 to 9 for single key access + if (theIndex < 1 || theIndex > 9) return(NS_RDF_NO_VALUE); + + nsCOMPtr<nsIRDFInt> indexInt; + rv = gRDFService->GetIntLiteral(theIndex, getter_AddRefs(indexInt)); + if (NS_FAILED(rv)) return(rv); + if (!indexInt) return(NS_ERROR_FAILURE); + + indexInt.forget(_retval); + return NS_OK; + } + + return mInner->GetTarget(aSource, aProperty, aTruthValue, _retval); +} + +NS_IMETHODIMP nsWindowDataSource::GetSource(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, nsIRDFResource **_retval) +{ + if (mInner) + return mInner->GetSource(aProperty, aTarget, aTruthValue, _retval); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::GetSources(nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, nsISimpleEnumerator **_retval) +{ + if (mInner) + return mInner->GetSources(aProperty, aTarget, aTruthValue, _retval); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::GetTargets(nsIRDFResource *aSource, nsIRDFResource *aProperty, bool aTruthValue, nsISimpleEnumerator **_retval) +{ + if (mInner) + return mInner->GetTargets(aSource, aProperty, aTruthValue, _retval); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::Assert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue) +{ + if (mInner) + return mInner->Assert(aSource, aProperty, aTarget, aTruthValue); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::Unassert(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget) +{ + if (mInner) + return mInner->Unassert(aSource, aProperty, aTarget); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::Change(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aOldTarget, nsIRDFNode *aNewTarget) +{ + if (mInner) + return mInner->Change(aSource, aProperty, aOldTarget, aNewTarget); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::Move(nsIRDFResource *aOldSource, nsIRDFResource *aNewSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget) +{ + if (mInner) + return mInner->Move(aOldSource, aNewSource, aProperty, aTarget); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::HasAssertion(nsIRDFResource *aSource, nsIRDFResource *aProperty, nsIRDFNode *aTarget, bool aTruthValue, bool *_retval) +{ + if (mInner) + return mInner->HasAssertion(aSource, aProperty, aTarget, aTruthValue, _retval); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::AddObserver(nsIRDFObserver *aObserver) +{ + if (mInner) + return mInner->AddObserver(aObserver); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::RemoveObserver(nsIRDFObserver *aObserver) +{ + if (mInner) + return mInner->RemoveObserver(aObserver); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::ArcLabelsIn(nsIRDFNode *aNode, nsISimpleEnumerator **_retval) +{ + if (mInner) + return mInner->ArcLabelsIn(aNode, _retval); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::ArcLabelsOut(nsIRDFResource *aSource, nsISimpleEnumerator **_retval) +{ + if (mInner) + return mInner->ArcLabelsOut(aSource, _retval); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::GetAllResources(nsISimpleEnumerator **_retval) +{ + if (mInner) + return mInner->GetAllResources(_retval); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::IsCommandEnabled(nsISupports *aSources, nsIRDFResource *aCommand, nsISupports *aArguments, bool *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsWindowDataSource::DoCommand(nsISupports *aSources, nsIRDFResource *aCommand, nsISupports *aArguments) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsWindowDataSource::GetAllCmds(nsIRDFResource *aSource, nsISimpleEnumerator **_retval) +{ + if (mInner) + return mInner->GetAllCmds(aSource, _retval); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *_retval) +{ + if (mInner) + return mInner->HasArcIn(aNode, aArc, _retval); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *_retval) +{ + if (mInner) + return mInner->HasArcOut(aSource, aArc, _retval); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::BeginUpdateBatch() +{ + if (mInner) + return mInner->BeginUpdateBatch(); + return NS_OK; +} + +NS_IMETHODIMP nsWindowDataSource::EndUpdateBatch() +{ + if (mInner) + return mInner->EndUpdateBatch(); + return NS_OK; +} + +// The module goop + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWindowDataSource, Init) + +NS_DEFINE_NAMED_CID(NS_WINDOWDATASOURCE_CID); + +static const mozilla::Module::CIDEntry kWindowDSCIDs[] = { + { &kNS_WINDOWDATASOURCE_CID, false, nullptr, nsWindowDataSourceConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kWindowDSContracts[] = { + { NS_RDF_DATASOURCE_CONTRACTID_PREFIX "window-mediator", &kNS_WINDOWDATASOURCE_CID }, + { nullptr } +}; + +static const mozilla::Module::CategoryEntry kWindowDSCategories[] = { + { "app-startup", "Window Data Source", "service," NS_RDF_DATASOURCE_CONTRACTID_PREFIX "window-mediator" }, + { nullptr } +}; + +static const mozilla::Module kWindowDSModule = { + mozilla::Module::kVersion, + kWindowDSCIDs, + kWindowDSContracts, + kWindowDSCategories +}; + +NSMODULE_DEFN(nsWindowDataSourceModule) = &kWindowDSModule; diff --git a/xpfe/components/windowds/nsWindowDataSource.h b/xpfe/components/windowds/nsWindowDataSource.h new file mode 100644 index 000000000..351ff951b --- /dev/null +++ b/xpfe/components/windowds/nsWindowDataSource.h @@ -0,0 +1,59 @@ +/* -*- 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 "nsIRDFDataSource.h" +#include "nsIWindowMediatorListener.h" +#include "nsIWindowDataSource.h" +#include "nsIObserver.h" + +#include "nsHashKeys.h" +#include "nsIRDFService.h" +#include "nsIRDFContainer.h" +#include "nsInterfaceHashtable.h" +#include "nsCycleCollectionParticipant.h" + +// {C744CA3D-840B-460a-8D70-7CE63C51C958} +#define NS_WINDOWDATASOURCE_CID \ +{ 0xc744ca3d, 0x840b, 0x460a, \ + { 0x8d, 0x70, 0x7c, 0xe6, 0x3c, 0x51, 0xc9, 0x58 } } + + +class nsWindowDataSource final : public nsIRDFDataSource, + public nsIObserver, + public nsIWindowMediatorListener, + public nsIWindowDataSource +{ + public: + nsWindowDataSource() { } + + nsresult Init(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsWindowDataSource, + nsIRDFDataSource) + NS_DECL_NSIOBSERVER + NS_DECL_NSIWINDOWMEDIATORLISTENER + NS_DECL_NSIWINDOWDATASOURCE + NS_DECL_NSIRDFDATASOURCE + + protected: + virtual ~nsWindowDataSource(); + + private: + + // mapping of window -> RDF resource + nsInterfaceHashtable<nsPtrHashKey<nsIXULWindow>, nsIRDFResource> mWindowResources; + + static uint32_t windowCount; + static uint32_t gRefCnt; + + nsCOMPtr<nsIRDFDataSource> mInner; + nsCOMPtr<nsIRDFContainer> mContainer; + + static nsIRDFResource* kNC_Name; + static nsIRDFResource* kNC_KeyIndex; + static nsIRDFResource* kNC_WindowRoot; + static nsIRDFService* gRDFService; +}; diff --git a/xpfe/test/child-window.html b/xpfe/test/child-window.html new file mode 100644 index 000000000..228ea3462 --- /dev/null +++ b/xpfe/test/child-window.html @@ -0,0 +1,5 @@ +<html><body onload=" + var gLoadEventTime = (new Date()).getTime(); + if (window.opener) + window.setTimeout('window.opener.childIsOpen('+gLoadEventTime+');',1000); +"></body></html> diff --git a/xpfe/test/winopen.js b/xpfe/test/winopen.js new file mode 100644 index 000000000..11548c441 --- /dev/null +++ b/xpfe/test/winopen.js @@ -0,0 +1,244 @@ +// target for window.open() +const KID_URL = "child-window.html"; + +// formats final results +const SERVER_URL = "http://jrgm.mcom.com/cgi-bin/window-open-2.0/openreport.pl"; + +// let system settle between each window.open +const OPENER_DELAY = 1000; + +// three phases: single open/close; overlapped open/close; open-all/close-all +var PHASE_ONE = 10; +var PHASE_TWO = 0; +var PHASE_THREE = 0; + +// keep this many windows concurrently open during overlapped phase +var OVERLAP_COUNT = 3; + +// repeat three phases CYCLES times +var CYCLES = 1; + +// autoclose flag +var AUTOCLOSE = 1; + +// Chrome url for child windows. +var KID_CHROME = null; +var SAVED_CHROME = null; + +// URL options and correspnding vars. +const options = [ [ "phase1", "PHASE_ONE", false ], + [ "phase2", "PHASE_TWO", false ], + [ "phase3", "PHASE_THREE", false ], + [ "overlap", "OVERLAP_COUNT", false ], + [ "cycles", "CYCLES", false ], + [ "chrome", "KID_CHROME", true ], + [ "close", "AUTOCLOSE", false ] ]; + +// Note: You can attach search options to the url for this file to control +// any of the options in the array above. E.g., specifying +// mozilla --chrome "file:///D|/mozilla/xpfe/test/winopen.xul?phase1=16&close=0" +// will run this script with PHASE_ONE=16 and AUTOCLOSE=0. +// +// On Win32, you must enclose the --chrome option in quotes in order pass funny Win32 shell +// characters such as '&' or '|'! + +var opts = window.location.search.substring(1).split( '&' ); +for ( opt in opts ) { + for ( var i in options ) { + if ( opts[opt].indexOf( options[i][0]+"=" ) == 0 ) { + var newVal = opts[opt].split( '=' )[ 1 ]; + // wrap with quotes, if required. + if ( options[i][2] ) { + newVal = '"' + newVal + '"'; + } + eval( options[i][1] + "=" + newVal + ";" ); + } + } +} + +var prefs = null; + +if ( KID_CHROME ) { + // Reset browser.chromeURL so it points to KID_CHROME. + // This will cause window.open in openWindow to open that chrome. + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + prefs = Components.classes["@mozilla.org/preferences-service;1"] + .getService( Components.interfaces.nsIPrefBranch ); + SAVED_CHROME = prefs.getCharPref( "browser.chromeURL" ); + prefs.setCharPref( "browser.chromeURL", KID_CHROME ); +} + +const CYCLE_SIZE = PHASE_ONE + PHASE_TWO + PHASE_THREE; +const MAX_INDEX = CYCLE_SIZE * CYCLES; // total number of windows to open + +var windowList = []; // handles to opened windows +var startingTimes = []; // time that window.open is called +var openingTimes = []; // time that child window took to fire onload +var closingTimes = []; // collect stats for case of closing >1 windows +var currentIndex = 0; + + +function childIsOpen(aTime) { + openingTimes[currentIndex] = aTime - startingTimes[currentIndex]; + updateDisplay(currentIndex, openingTimes[currentIndex]); + reapWindows(currentIndex); + currentIndex++; + if (currentIndex < MAX_INDEX) + scheduleNextWindow(); + else + window.setTimeout(reportResults, OPENER_DELAY); +} + + +function updateDisplay(index, time) { + var formIndex = document.getElementById("formIndex"); + if (formIndex) + formIndex.setAttribute("value", index+1); + var formTime = document.getElementById("formTime"); + if (formTime) + formTime.setAttribute("value", time); +} + + +function scheduleNextWindow() { + window.setTimeout(openWindow, OPENER_DELAY); +} + + +function closeOneWindow(aIndex) { + var win = windowList[aIndex]; + // no-op if window is already closed + if (win && !win.closed) { + win.close(); + windowList[aIndex] = null; + } +} + + +function closeAllWindows(aRecordTimeToClose) { + var timeToClose = (new Date()).getTime(); + var count = 0; + for (var i = 0; i < windowList.length; i++) { + if (windowList[i]) + count++; + closeOneWindow(i); + } + if (aRecordTimeToClose && count > 0) { + timeToClose = (new Date()).getTime() - timeToClose; + closingTimes.push(parseInt(timeToClose/count)); + } +} + + +// close some, none, or all open windows in the list +function reapWindows() { + var modIndex = currentIndex % CYCLE_SIZE; + if (modIndex < PHASE_ONE-1) { + // first phase in each "cycle", are single open/close sequences + closeOneWindow(currentIndex); + } + else if (PHASE_ONE-1 <= modIndex && modIndex < PHASE_ONE+PHASE_TWO-1) { + // next phase in each "cycle", keep N windows concurrently open + closeOneWindow(currentIndex - OVERLAP_COUNT); + } + else if (modIndex == PHASE_ONE+PHASE_TWO-1) { + // end overlapping windows cycle; close all windows + closeAllWindows(false); + } + else if (PHASE_ONE+PHASE_TWO <= modIndex && modIndex < CYCLE_SIZE-1) { + // do nothing; keep adding windows + } + else if (modIndex == CYCLE_SIZE-1) { + // end open-all/close-all phase; close windows, recording time to close + closeAllWindows(true); + } +} + +function calcMedian( numbers ) { + if ( numbers.length == 0 ) { + return 0; + } else if ( numbers.length == 1 ) { + return numbers[0]; + } else if ( numbers.length == 2 ) { + return ( numbers[0] + numbers[1] ) / 2; + } else { + numbers.sort( function (a,b){ return a-b; } ); + var n = Math.floor( numbers.length / 2 ); + return numbers.length % 2 ? numbers[n] : ( numbers[n-1] + numbers[n] ) / 2; + } +} + +function reportResults() { + //XXX need to create a client-side method to do this? + var opening = openingTimes.join(':'); // times for each window open + var closing = closingTimes.join(':'); // these are for >1 url, as a group + //var ua = escape(navigator.userAgent).replace(/\+/g, "%2B"); // + == ' ', on servers + //var reportURL = SERVER_URL + + // "?opening=" + opening + + // "&closing=" + closing + + // "&maxIndex=" + MAX_INDEX + + // "&cycleSize=" + CYCLE_SIZE + + //"&ua=" + ua; + //window.open(reportURL, "test-results"); + var avgOpenTime = 0; + var minOpenTime = 99999; + var maxOpenTime = 0; + var medOpenTime = calcMedian( openingTimes.slice(1) ); + // ignore first open + for (i = 1; i < MAX_INDEX; i++) { + avgOpenTime += openingTimes[i]; + if ( minOpenTime > openingTimes[i] ) { + minOpenTime = openingTimes[i]; + } + if ( maxOpenTime < openingTimes[i] ) { + maxOpenTime = openingTimes[i]; + } + } + avgOpenTime = Math.round(avgOpenTime / (MAX_INDEX - 1)); + dump("openingTimes="+openingTimes.slice(1)+"\n"); + dump("avgOpenTime:" + avgOpenTime + "\n" ); + dump("minOpenTime:" + minOpenTime + "\n" ); + dump("maxOpenTime:" + maxOpenTime + "\n" ); + dump("medOpenTime:" + medOpenTime + "\n" ); + dump("__xulWinOpenTime:" + medOpenTime + "\n"); + // Close the root window, if required. + if ( AUTOCLOSE ) { + window.close(); + } else { + document.getElementById("formTimes").value = openingTimes.slice(1); + document.getElementById("formAvg").value = avgOpenTime; + document.getElementById("formMin").value = minOpenTime; + document.getElementById("formMax").value = maxOpenTime; + document.getElementById("formMed").value = medOpenTime; + document.getElementById("formAgain").setAttribute( "disabled", "false" ); + } +} + +function tryAgain() { + document.getElementById("formAgain").setAttribute( "disabled", "true" ); + windowList = []; + startingTimes = []; + openingTimes = []; + closingTimes = []; + currentIndex = 0; + openWindow(); +} + +function restoreChromeURL() { + // Restore browser.chromeURL pref. + if ( KID_CHROME && SAVED_CHROME.length ) { + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + prefs.setCharPref( "browser.chromeURL", SAVED_CHROME ); + } +} + +function openWindow() { + startingTimes[currentIndex] = (new Date()).getTime(); + var path = window.location.pathname.substring( 0, window.location.pathname.lastIndexOf('/') ); + var url = window.location.protocol + "//" + + window.location.hostname + path + "/" + + KID_URL; + windowList[currentIndex] = window.open(url, currentIndex); +} + + diff --git a/xpfe/test/winopen.xul b/xpfe/test/winopen.xul new file mode 100644 index 000000000..929370b8a --- /dev/null +++ b/xpfe/test/winopen.xul @@ -0,0 +1,75 @@ +<?xml version="1.0"?> <?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<window +xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" +orient="vertical" + height="300" + width="400" + windowtype="opener:test" + onunload="restoreChromeURL();" + onload="scheduleNextWindow();"> + +<script src="winopen.js" type="application/x-javascript"></script> + +<groupbox orient="vertical"> +<caption label="Window Opening Test"/> +<html> +This will open a series of browser windows, either "one at a + time" or in a sequence of some form. When this test is complete + a final window will be opened which will report the overall results. + </html> +<separator class="thick"/> +<grid> +<columns><column/><column/></columns> +<rows> +<row align="center"> +<text value="Index:"/> +<textbox id="formIndex" size="6" value=""/> +</row> +<row align="center"> +<text value="Time:"/> +<textbox id="formTime" size="6" value=""/> +<text value="msec"/> +</row> +</rows> +</grid> +<separator class="thick"/> +</groupbox> + +<groupbox orient="vertical"> + <caption label="Results"/> + <grid> + <columns> + <column/> + <column/> + </columns> + <rows> + <row align="center"> + <text value="Times (ignoring the first):"/> + <textbox id="formTimes" size="45" value=""/> + </row> + <row align="center"> + <text value="Txul (median):"/> + <hbox> + <textbox id="formMed" size="6" value=""/> + <spring/> + <button id="formAgain" onclick="tryAgain();" label="Try again" disabled="true"/> + </hbox> + </row> + <row align="center"> + <text value="Avg:"/> + <hbox><textbox id="formAvg" size="6" value=""/></hbox> + </row> + <row align="center"> + <text value="Min:"/> + <hbox><textbox id="formMin" size="6" value=""/></hbox> + </row> + <row align="center"> + <text value="Max:"/> + <hbox><textbox id="formMax" size="6" value=""/></hbox> + </row> + </rows> + </grid> +</groupbox> + +</window> + |