diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /embedding/components | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'embedding/components')
130 files changed, 20983 insertions, 0 deletions
diff --git a/embedding/components/appstartup/moz.build b/embedding/components/appstartup/moz.build new file mode 100644 index 000000000..237591a50 --- /dev/null +++ b/embedding/components/appstartup/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/. + +EXPORTS += [ + 'nsIAppStartupNotifier.h', +] + +SOURCES += [ + 'nsAppStartupNotifier.cpp', +] + +FINAL_LIBRARY = 'xul' diff --git a/embedding/components/appstartup/nsAppStartupNotifier.cpp b/embedding/components/appstartup/nsAppStartupNotifier.cpp new file mode 100644 index 000000000..4ef38fb8d --- /dev/null +++ b/embedding/components/appstartup/nsAppStartupNotifier.cpp @@ -0,0 +1,90 @@ +/* -*- 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 "nsCOMPtr.h" +#include "nsString.h" +#include "nsXPIDLString.h" +#include "nsIServiceManager.h" +#include "nsICategoryManager.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsAppStartupNotifier.h" +#include "nsISimpleEnumerator.h" + +NS_IMPL_ISUPPORTS(nsAppStartupNotifier, nsIObserver) + +nsAppStartupNotifier::nsAppStartupNotifier() +{ +} + +nsAppStartupNotifier::~nsAppStartupNotifier() +{ +} + +NS_IMETHODIMP nsAppStartupNotifier::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData) +{ + NS_ENSURE_ARG(aTopic); + nsresult rv; + + // now initialize all startup listeners + nsCOMPtr<nsICategoryManager> categoryManager = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = categoryManager->EnumerateCategory(aTopic, + getter_AddRefs(enumerator)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsISupports> entry; + while (NS_SUCCEEDED(enumerator->GetNext(getter_AddRefs(entry)))) { + nsCOMPtr<nsISupportsCString> category = do_QueryInterface(entry, &rv); + + if (NS_SUCCEEDED(rv)) { + nsAutoCString categoryEntry; + rv = category->GetData(categoryEntry); + + nsXPIDLCString contractId; + categoryManager->GetCategoryEntry(aTopic, + categoryEntry.get(), + getter_Copies(contractId)); + + if (NS_SUCCEEDED(rv)) { + + // If we see the word "service," in the beginning + // of the contractId then we create it as a service + // if not we do a createInstance + + nsCOMPtr<nsISupports> startupInstance; + if (Substring(contractId, 0, 8).EqualsLiteral("service,")) + startupInstance = do_GetService(contractId.get() + 8, &rv); + else + startupInstance = do_CreateInstance(contractId, &rv); + + if (NS_SUCCEEDED(rv)) { + // Try to QI to nsIObserver + nsCOMPtr<nsIObserver> startupObserver = + do_QueryInterface(startupInstance, &rv); + if (NS_SUCCEEDED(rv)) { + rv = startupObserver->Observe(nullptr, aTopic, nullptr); + + // mainly for debugging if you want to know if your observer worked. + NS_ASSERTION(NS_SUCCEEDED(rv), "Startup Observer failed!\n"); + } + } + else { + #ifdef DEBUG + nsAutoCString warnStr("Cannot create startup observer : "); + warnStr += contractId.get(); + NS_WARNING(warnStr.get()); + #endif + } + + } + } + } + + return NS_OK; +} diff --git a/embedding/components/appstartup/nsAppStartupNotifier.h b/embedding/components/appstartup/nsAppStartupNotifier.h new file mode 100644 index 000000000..31077ed38 --- /dev/null +++ b/embedding/components/appstartup/nsAppStartupNotifier.h @@ -0,0 +1,30 @@ +/* -*- 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 nsAppStartupNotifier_h___ +#define nsAppStartupNotifier_h___ + +#include "nsIAppStartupNotifier.h" + +// {1F59B001-02C9-11d5-AE76-CC92F7DB9E03} +#define NS_APPSTARTUPNOTIFIER_CID \ + { 0x1f59b001, 0x2c9, 0x11d5, { 0xae, 0x76, 0xcc, 0x92, 0xf7, 0xdb, 0x9e, 0x3 } } + +class nsAppStartupNotifier : public nsIObserver +{ +public: + NS_DEFINE_STATIC_CID_ACCESSOR( NS_APPSTARTUPNOTIFIER_CID ) + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + nsAppStartupNotifier(); + +protected: + virtual ~nsAppStartupNotifier(); +}; + +#endif /* nsAppStartupNotifier_h___ */ + diff --git a/embedding/components/appstartup/nsIAppStartupNotifier.h b/embedding/components/appstartup/nsIAppStartupNotifier.h new file mode 100644 index 000000000..113bfa171 --- /dev/null +++ b/embedding/components/appstartup/nsIAppStartupNotifier.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/. */ + +#ifndef nsIAppStartupNotifier_h___ +#define nsIAppStartupNotifier_h___ + +#include "nsIObserver.h" + +/* + Some components need to be run at the startup of mozilla or embedding - to + start new services etc. + + This interface provides a generic way to start up arbitrary components + without requiring them to hack into main1() (or into NS_InitEmbedding) as + it's currently being done for services such as wallet, command line handlers + etc. + + We will have a category called "app-startup" which components register + themselves in using the CategoryManager. + + Components can also (optionally) add the word "service," as a prefix + to the "value" they pass in during a call to AddCategoryEntry() as + shown below: + + categoryManager->AddCategoryEntry(APPSTARTUP_CATEGORY, "testcomp", + "service," NS_WALLETSERVICE_CONTRACTID + true, true, + getter_Copies(previous)); + + Presence of the "service" keyword indicates the components desire to + be started as a service. When the "service" keyword is not present + we just do a do_CreateInstance. + + When mozilla starts (and when NS_InitEmbedding()) is invoked + we create an instance of the AppStartupNotifier component (which + implements nsIObserver) and invoke its Observe() method. + + Observe() will enumerate the components registered into the + APPSTARTUP_CATEGORY and notify them that startup has begun + and release them. +*/ + +#define NS_APPSTARTUPNOTIFIER_CONTRACTID "@mozilla.org/embedcomp/appstartup-notifier;1" + +#define APPSTARTUP_CATEGORY "app-startup" +#define APPSTARTUP_TOPIC "app-startup" + + +/* + Please note that there's not a new interface in this file. + We're just leveraging nsIObserver instead of creating a + new one + + This file exists solely to provide the defines above +*/ + +#endif /* nsIAppStartupNotifier_h___ */ diff --git a/embedding/components/build/moz.build b/embedding/components/build/moz.build new file mode 100644 index 000000000..defbfc0c8 --- /dev/null +++ b/embedding/components/build/moz.build @@ -0,0 +1,38 @@ +# -*- 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 += [ + 'nsEmbeddingModule.cpp', +] + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '../appstartup', + '../commandhandler', + '../find', + '../printingui/ipc', + '../webbrowserpersist', + '../windowwatcher', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + DEFINES['PROXY_PRINTING'] = 1 + LOCAL_INCLUDES += [ + '../printingui/win', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + DEFINES['PROXY_PRINTING'] = 1 + LOCAL_INCLUDES += [ + '../printingui/mac', + ] + +if CONFIG['MOZ_PDF_PRINTING']: + DEFINES['PROXY_PRINTING'] = 1 + LOCAL_INCLUDES += [ + '../printingui/unixshared', + ] + +include('/ipc/chromium/chromium-config.mozbuild') diff --git a/embedding/components/build/nsEmbeddingModule.cpp b/embedding/components/build/nsEmbeddingModule.cpp new file mode 100644 index 000000000..48351ec3a --- /dev/null +++ b/embedding/components/build/nsEmbeddingModule.cpp @@ -0,0 +1,120 @@ +/* -*- 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 "nsDialogParamBlock.h" +#include "nsWindowWatcher.h" +#include "nsAppStartupNotifier.h" +#include "nsFind.h" +#include "nsWebBrowserFind.h" +#include "nsWebBrowserPersist.h" +#include "nsCommandManager.h" +#include "nsControllerCommandTable.h" +#include "nsCommandParams.h" +#include "nsCommandGroup.h" +#include "nsBaseCommandController.h" +#include "nsNetCID.h" +#include "nsEmbedCID.h" + +#ifdef NS_PRINTING +#include "nsPrintingPromptService.h" +#include "nsPrintingProxy.h" +#endif + + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWindowWatcher, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsAppStartupNotifier) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsFind) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebBrowserFind) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebBrowserPersist) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsControllerCommandTable) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsCommandManager) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsCommandParams) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsControllerCommandGroup) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsBaseCommandController) + +#ifdef MOZ_XUL +NS_GENERIC_FACTORY_CONSTRUCTOR(nsDialogParamBlock) +#ifdef NS_PRINTING +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintingPromptService, Init) +#ifdef PROXY_PRINTING +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsPrintingProxy, + nsPrintingProxy::GetInstance) +#endif +#endif +#endif + +#ifdef MOZ_XUL +NS_DEFINE_NAMED_CID(NS_DIALOGPARAMBLOCK_CID); +#ifdef NS_PRINTING +NS_DEFINE_NAMED_CID(NS_PRINTINGPROMPTSERVICE_CID); +#endif +#endif +NS_DEFINE_NAMED_CID(NS_WINDOWWATCHER_CID); +NS_DEFINE_NAMED_CID(NS_FIND_CID); +NS_DEFINE_NAMED_CID(NS_WEB_BROWSER_FIND_CID); +NS_DEFINE_NAMED_CID(NS_APPSTARTUPNOTIFIER_CID); +NS_DEFINE_NAMED_CID(NS_WEBBROWSERPERSIST_CID); +NS_DEFINE_NAMED_CID(NS_CONTROLLERCOMMANDTABLE_CID); +NS_DEFINE_NAMED_CID(NS_COMMAND_MANAGER_CID); +NS_DEFINE_NAMED_CID(NS_COMMAND_PARAMS_CID); +NS_DEFINE_NAMED_CID(NS_CONTROLLER_COMMAND_GROUP_CID); +NS_DEFINE_NAMED_CID(NS_BASECOMMANDCONTROLLER_CID); + +static const mozilla::Module::CIDEntry kEmbeddingCIDs[] = { +#ifdef MOZ_XUL + { &kNS_DIALOGPARAMBLOCK_CID, false, nullptr, nsDialogParamBlockConstructor }, +#ifdef NS_PRINTING + +#ifdef PROXY_PRINTING + { &kNS_PRINTINGPROMPTSERVICE_CID, false, nullptr, nsPrintingPromptServiceConstructor, + mozilla::Module::MAIN_PROCESS_ONLY }, + { &kNS_PRINTINGPROMPTSERVICE_CID, false, nullptr, nsPrintingProxyConstructor, + mozilla::Module::CONTENT_PROCESS_ONLY }, +#else + { &kNS_PRINTINGPROMPTSERVICE_CID, false, nullptr, nsPrintingPromptServiceConstructor }, +#endif +#endif +#endif + { &kNS_WINDOWWATCHER_CID, false, nullptr, nsWindowWatcherConstructor }, + { &kNS_FIND_CID, false, nullptr, nsFindConstructor }, + { &kNS_WEB_BROWSER_FIND_CID, false, nullptr, nsWebBrowserFindConstructor }, + { &kNS_APPSTARTUPNOTIFIER_CID, false, nullptr, nsAppStartupNotifierConstructor }, + { &kNS_WEBBROWSERPERSIST_CID, false, nullptr, nsWebBrowserPersistConstructor }, + { &kNS_CONTROLLERCOMMANDTABLE_CID, false, nullptr, nsControllerCommandTableConstructor }, + { &kNS_COMMAND_MANAGER_CID, false, nullptr, nsCommandManagerConstructor }, + { &kNS_COMMAND_PARAMS_CID, false, nullptr, nsCommandParamsConstructor }, + { &kNS_CONTROLLER_COMMAND_GROUP_CID, false, nullptr, nsControllerCommandGroupConstructor }, + { &kNS_BASECOMMANDCONTROLLER_CID, false, nullptr, nsBaseCommandControllerConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kEmbeddingContracts[] = { +#ifdef MOZ_XUL + { NS_DIALOGPARAMBLOCK_CONTRACTID, &kNS_DIALOGPARAMBLOCK_CID }, +#ifdef NS_PRINTING + { NS_PRINTINGPROMPTSERVICE_CONTRACTID, &kNS_PRINTINGPROMPTSERVICE_CID }, +#endif +#endif + { NS_WINDOWWATCHER_CONTRACTID, &kNS_WINDOWWATCHER_CID }, + { NS_FIND_CONTRACTID, &kNS_FIND_CID }, + { NS_WEB_BROWSER_FIND_CONTRACTID, &kNS_WEB_BROWSER_FIND_CID }, + { NS_APPSTARTUPNOTIFIER_CONTRACTID, &kNS_APPSTARTUPNOTIFIER_CID }, + { NS_WEBBROWSERPERSIST_CONTRACTID, &kNS_WEBBROWSERPERSIST_CID }, + { NS_CONTROLLERCOMMANDTABLE_CONTRACTID, &kNS_CONTROLLERCOMMANDTABLE_CID }, + { NS_COMMAND_MANAGER_CONTRACTID, &kNS_COMMAND_MANAGER_CID }, + { NS_COMMAND_PARAMS_CONTRACTID, &kNS_COMMAND_PARAMS_CID }, + { NS_CONTROLLER_COMMAND_GROUP_CONTRACTID, &kNS_CONTROLLER_COMMAND_GROUP_CID }, + { NS_BASECOMMANDCONTROLLER_CONTRACTID, &kNS_BASECOMMANDCONTROLLER_CID }, + { nullptr } +}; + +static const mozilla::Module kEmbeddingModule = { + mozilla::Module::kVersion, + kEmbeddingCIDs, + kEmbeddingContracts +}; + +NSMODULE_DEFN(embedcomponents) = &kEmbeddingModule; diff --git a/embedding/components/commandhandler/moz.build b/embedding/components/commandhandler/moz.build new file mode 100644 index 000000000..d44d57f20 --- /dev/null +++ b/embedding/components/commandhandler/moz.build @@ -0,0 +1,26 @@ +# -*- 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 += [ + 'nsICommandManager.idl', + 'nsICommandParams.idl', + 'nsIControllerCommand.idl', + 'nsIControllerCommandTable.idl', + 'nsIControllerContext.idl', + 'nsPICommandUpdater.idl', +] + +XPIDL_MODULE = 'commandhandler' + +UNIFIED_SOURCES += [ + 'nsBaseCommandController.cpp', + 'nsCommandGroup.cpp', + 'nsCommandManager.cpp', + 'nsCommandParams.cpp', + 'nsControllerCommandTable.cpp', +] + +FINAL_LIBRARY = 'xul' diff --git a/embedding/components/commandhandler/nsBaseCommandController.cpp b/embedding/components/commandhandler/nsBaseCommandController.cpp new file mode 100644 index 000000000..2bd87b6f5 --- /dev/null +++ b/embedding/components/commandhandler/nsBaseCommandController.cpp @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" +#include "nsIComponentManager.h" +#include "nsBaseCommandController.h" + +#include "nsString.h" +#include "nsWeakPtr.h" + +NS_IMPL_ADDREF(nsBaseCommandController) +NS_IMPL_RELEASE(nsBaseCommandController) + +NS_INTERFACE_MAP_BEGIN(nsBaseCommandController) + NS_INTERFACE_MAP_ENTRY(nsIController) + NS_INTERFACE_MAP_ENTRY(nsICommandController) + NS_INTERFACE_MAP_ENTRY(nsIControllerContext) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIControllerContext) +NS_INTERFACE_MAP_END + +nsBaseCommandController::nsBaseCommandController() + : mCommandContextRawPtr(nullptr) +{ +} + +nsBaseCommandController::~nsBaseCommandController() +{ +} + +NS_IMETHODIMP +nsBaseCommandController::Init(nsIControllerCommandTable* aCommandTable) +{ + nsresult rv = NS_OK; + + if (aCommandTable) { + mCommandTable = aCommandTable; + } else { + mCommandTable = + do_CreateInstance(NS_CONTROLLERCOMMANDTABLE_CONTRACTID, &rv); + } + + return rv; +} + +NS_IMETHODIMP +nsBaseCommandController::SetCommandContext(nsISupports* aCommandContext) +{ + mCommandContextWeakPtr = nullptr; + mCommandContextRawPtr = nullptr; + + if (aCommandContext) { + nsCOMPtr<nsISupportsWeakReference> weak = do_QueryInterface(aCommandContext); + if (weak) { + nsresult rv = + weak->GetWeakReference(getter_AddRefs(mCommandContextWeakPtr)); + NS_ENSURE_SUCCESS(rv, rv); + } else { + mCommandContextRawPtr = aCommandContext; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBaseCommandController::GetInterface(const nsIID& aIID, void** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + if (NS_SUCCEEDED(QueryInterface(aIID, aResult))) { + return NS_OK; + } + + if (aIID.Equals(NS_GET_IID(nsIControllerCommandTable))) { + if (mCommandTable) { + return mCommandTable->QueryInterface(aIID, aResult); + } + return NS_ERROR_NOT_INITIALIZED; + } + + return NS_NOINTERFACE; +} + +/* ======================================================================= + * nsIController + * ======================================================================= */ + +NS_IMETHODIMP +nsBaseCommandController::IsCommandEnabled(const char* aCommand, bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aCommand); + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_STATE(mCommandTable); + + nsISupports* context = mCommandContextRawPtr; + nsCOMPtr<nsISupports> weak; + if (!context) { + weak = do_QueryReferent(mCommandContextWeakPtr); + context = weak; + } + return mCommandTable->IsCommandEnabled(aCommand, context, aResult); +} + +NS_IMETHODIMP +nsBaseCommandController::SupportsCommand(const char* aCommand, bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aCommand); + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_STATE(mCommandTable); + + nsISupports* context = mCommandContextRawPtr; + nsCOMPtr<nsISupports> weak; + if (!context) { + weak = do_QueryReferent(mCommandContextWeakPtr); + context = weak; + } + return mCommandTable->SupportsCommand(aCommand, context, aResult); +} + +NS_IMETHODIMP +nsBaseCommandController::DoCommand(const char* aCommand) +{ + NS_ENSURE_ARG_POINTER(aCommand); + NS_ENSURE_STATE(mCommandTable); + + nsISupports* context = mCommandContextRawPtr; + nsCOMPtr<nsISupports> weak; + if (!context) { + weak = do_QueryReferent(mCommandContextWeakPtr); + context = weak; + } + return mCommandTable->DoCommand(aCommand, context); +} + +NS_IMETHODIMP +nsBaseCommandController::DoCommandWithParams(const char* aCommand, + nsICommandParams* aParams) +{ + NS_ENSURE_ARG_POINTER(aCommand); + NS_ENSURE_STATE(mCommandTable); + + nsISupports* context = mCommandContextRawPtr; + nsCOMPtr<nsISupports> weak; + if (!context) { + weak = do_QueryReferent(mCommandContextWeakPtr); + context = weak; + } + return mCommandTable->DoCommandParams(aCommand, aParams, context); +} + +NS_IMETHODIMP +nsBaseCommandController::GetCommandStateWithParams(const char* aCommand, + nsICommandParams* aParams) +{ + NS_ENSURE_ARG_POINTER(aCommand); + NS_ENSURE_STATE(mCommandTable); + + nsISupports* context = mCommandContextRawPtr; + nsCOMPtr<nsISupports> weak; + if (!context) { + weak = do_QueryReferent(mCommandContextWeakPtr); + context = weak; + } + return mCommandTable->GetCommandState(aCommand, aParams, context); +} + +NS_IMETHODIMP +nsBaseCommandController::OnEvent(const char* aEventName) +{ + NS_ENSURE_ARG_POINTER(aEventName); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseCommandController::GetSupportedCommands(uint32_t* aCount, + char*** aCommands) +{ + NS_ENSURE_STATE(mCommandTable); + return mCommandTable->GetSupportedCommands(aCount, aCommands); +} diff --git a/embedding/components/commandhandler/nsBaseCommandController.h b/embedding/components/commandhandler/nsBaseCommandController.h new file mode 100644 index 000000000..83976faac --- /dev/null +++ b/embedding/components/commandhandler/nsBaseCommandController.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsBaseCommandController_h__ +#define nsBaseCommandController_h__ + +#define NS_BASECOMMANDCONTROLLER_CID \ + { 0xbf88b48c, 0xfd8e, 0x40b4, { 0xba, 0x36, 0xc7, 0xc3, 0xad, 0x6d, 0x8a, 0xc9 } } +#define NS_BASECOMMANDCONTROLLER_CONTRACTID \ + "@mozilla.org/embedcomp/base-command-controller;1" + +#include "nsIController.h" +#include "nsIControllerContext.h" +#include "nsIControllerCommandTable.h" +#include "nsIInterfaceRequestor.h" +#include "nsIWeakReferenceUtils.h" + +// The base editor controller is used for both text widgets, and all other text +// and html editing +class nsBaseCommandController + : public nsIController + , public nsIControllerContext + , public nsIInterfaceRequestor + , public nsICommandController +{ +public: + nsBaseCommandController(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICONTROLLER + NS_DECL_NSICOMMANDCONTROLLER + NS_DECL_NSICONTROLLERCONTEXT + NS_DECL_NSIINTERFACEREQUESTOR + +protected: + virtual ~nsBaseCommandController(); + +private: + nsWeakPtr mCommandContextWeakPtr; + nsISupports* mCommandContextRawPtr; + + // Our reference to the command manager + nsCOMPtr<nsIControllerCommandTable> mCommandTable; +}; + +#endif /* nsBaseCommandController_h_ */ diff --git a/embedding/components/commandhandler/nsCommandGroup.cpp b/embedding/components/commandhandler/nsCommandGroup.cpp new file mode 100644 index 000000000..696b93f67 --- /dev/null +++ b/embedding/components/commandhandler/nsCommandGroup.cpp @@ -0,0 +1,296 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsTArray.h" +#include "nsISimpleEnumerator.h" +#include "nsXPCOM.h" +#include "nsSupportsPrimitives.h" +#include "nsIComponentManager.h" +#include "nsCommandGroup.h" +#include "nsIControllerCommand.h" +#include "nsCRT.h" + +class nsGroupsEnumerator : public nsISimpleEnumerator +{ +public: + explicit nsGroupsEnumerator( + nsControllerCommandGroup::GroupsHashtable& aInHashTable); + + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATOR + +protected: + virtual ~nsGroupsEnumerator(); + + nsresult Initialize(); + +protected: + nsControllerCommandGroup::GroupsHashtable& mHashTable; + int32_t mIndex; + const char** mGroupNames; // array of pointers to char16_t* in the hash table + bool mInitted; +}; + +/* Implementation file */ +NS_IMPL_ISUPPORTS(nsGroupsEnumerator, nsISimpleEnumerator) + +nsGroupsEnumerator::nsGroupsEnumerator( + nsControllerCommandGroup::GroupsHashtable& aInHashTable) + : mHashTable(aInHashTable) + , mIndex(-1) + , mGroupNames(nullptr) + , mInitted(false) +{ +} + +nsGroupsEnumerator::~nsGroupsEnumerator() +{ + delete[] mGroupNames; +} + +NS_IMETHODIMP +nsGroupsEnumerator::HasMoreElements(bool* aResult) +{ + nsresult rv = NS_OK; + + NS_ENSURE_ARG_POINTER(aResult); + + if (!mInitted) { + rv = Initialize(); + if (NS_FAILED(rv)) { + return rv; + } + } + + *aResult = (mIndex < static_cast<int32_t>(mHashTable.Count()) - 1); + return NS_OK; +} + +NS_IMETHODIMP +nsGroupsEnumerator::GetNext(nsISupports** aResult) +{ + nsresult rv = NS_OK; + + NS_ENSURE_ARG_POINTER(aResult); + + if (!mInitted) { + rv = Initialize(); + if (NS_FAILED(rv)) { + return rv; + } + } + + mIndex++; + if (mIndex >= static_cast<int32_t>(mHashTable.Count())) { + return NS_ERROR_FAILURE; + } + + const char* thisGroupName = mGroupNames[mIndex]; + + nsCOMPtr<nsISupportsCString> supportsString = + do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + supportsString->SetData(nsDependentCString(thisGroupName)); + return CallQueryInterface(supportsString, aResult); +} + +nsresult +nsGroupsEnumerator::Initialize() +{ + if (mInitted) { + return NS_OK; + } + + mGroupNames = new const char*[mHashTable.Count()]; + if (!mGroupNames) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mIndex = 0; + for (auto iter = mHashTable.Iter(); !iter.Done(); iter.Next()) { + mGroupNames[mIndex] = iter.Key().Data(); + mIndex++; + } + + mIndex = -1; + mInitted = true; + return NS_OK; +} + +class nsNamedGroupEnumerator : public nsISimpleEnumerator +{ +public: + explicit nsNamedGroupEnumerator(nsTArray<nsCString>* aInArray); + + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATOR + +protected: + virtual ~nsNamedGroupEnumerator(); + + nsTArray<nsCString>* mGroupArray; + int32_t mIndex; +}; + +nsNamedGroupEnumerator::nsNamedGroupEnumerator(nsTArray<nsCString>* aInArray) + : mGroupArray(aInArray) + , mIndex(-1) +{ +} + +nsNamedGroupEnumerator::~nsNamedGroupEnumerator() +{ +} + +NS_IMPL_ISUPPORTS(nsNamedGroupEnumerator, nsISimpleEnumerator) + +NS_IMETHODIMP +nsNamedGroupEnumerator::HasMoreElements(bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + int32_t arrayLen = mGroupArray ? mGroupArray->Length() : 0; + *aResult = (mIndex < arrayLen - 1); + return NS_OK; +} + +NS_IMETHODIMP +nsNamedGroupEnumerator::GetNext(nsISupports** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + if (!mGroupArray) { + return NS_ERROR_FAILURE; + } + + mIndex++; + if (mIndex >= int32_t(mGroupArray->Length())) { + return NS_ERROR_FAILURE; + } + + const nsCString& thisGroupName = mGroupArray->ElementAt(mIndex); + + nsresult rv; + nsCOMPtr<nsISupportsCString> supportsString = + do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + supportsString->SetData(thisGroupName); + return CallQueryInterface(supportsString, aResult); +} + +NS_IMPL_ISUPPORTS(nsControllerCommandGroup, nsIControllerCommandGroup) + +nsControllerCommandGroup::nsControllerCommandGroup() +{ +} + +nsControllerCommandGroup::~nsControllerCommandGroup() +{ + ClearGroupsHash(); +} + +void +nsControllerCommandGroup::ClearGroupsHash() +{ + mGroupsHash.Clear(); +} + +NS_IMETHODIMP +nsControllerCommandGroup::AddCommandToGroup(const char* aCommand, + const char* aGroup) +{ + nsDependentCString groupKey(aGroup); + nsTArray<nsCString>* commandList = mGroupsHash.Get(groupKey); + if (!commandList) { + // make this list + commandList = new AutoTArray<nsCString, 8>; + mGroupsHash.Put(groupKey, commandList); + } + +#ifdef DEBUG + nsCString* appended = +#endif + commandList->AppendElement(aCommand); + NS_ASSERTION(appended, "Append failed"); + + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandGroup::RemoveCommandFromGroup(const char* aCommand, + const char* aGroup) +{ + nsDependentCString groupKey(aGroup); + nsTArray<nsCString>* commandList = mGroupsHash.Get(groupKey); + if (!commandList) { + return NS_OK; // no group + } + + uint32_t numEntries = commandList->Length(); + for (uint32_t i = 0; i < numEntries; i++) { + nsCString commandString = commandList->ElementAt(i); + if (nsDependentCString(aCommand) != commandString) { + commandList->RemoveElementAt(i); + break; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandGroup::IsCommandInGroup(const char* aCommand, + const char* aGroup, bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + + nsDependentCString groupKey(aGroup); + nsTArray<nsCString>* commandList = mGroupsHash.Get(groupKey); + if (!commandList) { + return NS_OK; // no group + } + + uint32_t numEntries = commandList->Length(); + for (uint32_t i = 0; i < numEntries; i++) { + nsCString commandString = commandList->ElementAt(i); + if (nsDependentCString(aCommand) != commandString) { + *aResult = true; + break; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandGroup::GetGroupsEnumerator(nsISimpleEnumerator** aResult) +{ + RefPtr<nsGroupsEnumerator> groupsEnum = new nsGroupsEnumerator(mGroupsHash); + + groupsEnum.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandGroup::GetEnumeratorForGroup(const char* aGroup, + nsISimpleEnumerator** aResult) +{ + nsDependentCString groupKey(aGroup); + nsTArray<nsCString>* commandList = mGroupsHash.Get(groupKey); // may be null + + RefPtr<nsNamedGroupEnumerator> theGroupEnum = + new nsNamedGroupEnumerator(commandList); + + theGroupEnum.forget(aResult); + return NS_OK; +} diff --git a/embedding/components/commandhandler/nsCommandGroup.h b/embedding/components/commandhandler/nsCommandGroup.h new file mode 100644 index 000000000..b06d725b3 --- /dev/null +++ b/embedding/components/commandhandler/nsCommandGroup.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCommandGroup_h__ +#define nsCommandGroup_h__ + +#include "nsIController.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" + +// {ecd55a01-2780-11d5-a73c-ca641a6813bc} +#define NS_CONTROLLER_COMMAND_GROUP_CID \ + { 0xecd55a01, 0x2780, 0x11d5, { 0xa7, 0x3c, 0xca, 0x64, 0x1a, 0x68, 0x13, 0xbc } } + +#define NS_CONTROLLER_COMMAND_GROUP_CONTRACTID \ + "@mozilla.org/embedcomp/controller-command-group;1" + +class nsControllerCommandGroup : public nsIControllerCommandGroup +{ +public: + nsControllerCommandGroup(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICONTROLLERCOMMANDGROUP + +public: + typedef nsClassHashtable<nsCStringHashKey, nsTArray<nsCString>> + GroupsHashtable; + +protected: + virtual ~nsControllerCommandGroup(); + + void ClearGroupsHash(); + +protected: + // Hash keyed on command group. This could be made more space-efficient, + // maybe with atoms. + GroupsHashtable mGroupsHash; +}; + +#endif // nsCommandGroup_h__ diff --git a/embedding/components/commandhandler/nsCommandManager.cpp b/embedding/components/commandhandler/nsCommandManager.cpp new file mode 100644 index 000000000..c11a901b2 --- /dev/null +++ b/embedding/components/commandhandler/nsCommandManager.cpp @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" + +#include "nsIController.h" +#include "nsIControllers.h" +#include "nsIObserver.h" + +#include "nsIComponentManager.h" + +#include "nsServiceManagerUtils.h" +#include "nsIScriptSecurityManager.h" + +#include "nsContentUtils.h" +#include "nsIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsPIWindowRoot.h" +#include "nsIFocusManager.h" + +#include "nsCOMArray.h" + +#include "nsCommandManager.h" + +nsCommandManager::nsCommandManager() + : mWindow(nullptr) +{ +} + +nsCommandManager::~nsCommandManager() +{ +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsCommandManager) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsCommandManager) + tmp->mObserversTable.Clear(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsCommandManager) + for (auto iter = tmp->mObserversTable.Iter(); !iter.Done(); iter.Next()) { + nsCommandManager::ObserverList* observers = iter.UserData(); + int32_t numItems = observers->Length(); + for (int32_t i = 0; i < numItems; ++i) { + cb.NoteXPCOMChild(observers->ElementAt(i)); + } + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsCommandManager) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsCommandManager) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCommandManager) + NS_INTERFACE_MAP_ENTRY(nsICommandManager) + NS_INTERFACE_MAP_ENTRY(nsPICommandUpdater) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsICommandManager) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +nsCommandManager::Init(mozIDOMWindowProxy* aWindow) +{ + NS_ENSURE_ARG_POINTER(aWindow); + + mWindow = aWindow; // weak ptr + return NS_OK; +} + +NS_IMETHODIMP +nsCommandManager::CommandStatusChanged(const char* aCommandName) +{ + ObserverList* commandObservers; + mObserversTable.Get(aCommandName, &commandObservers); + + if (commandObservers) { + // XXX Should we worry about observers removing themselves from Observe()? + int32_t i, numItems = commandObservers->Length(); + for (i = 0; i < numItems; ++i) { + nsCOMPtr<nsIObserver> observer = commandObservers->ElementAt(i); + // should we get the command state to pass here? This might be expensive. + observer->Observe(NS_ISUPPORTS_CAST(nsICommandManager*, this), + aCommandName, + u"command_status_changed"); + } + } + + return NS_OK; +} + +#if 0 +#pragma mark - +#endif + +NS_IMETHODIMP +nsCommandManager::AddCommandObserver(nsIObserver* aCommandObserver, + const char* aCommandToObserve) +{ + NS_ENSURE_ARG(aCommandObserver); + + // XXX todo: handle special cases of aCommandToObserve being null, or empty + + // for each command in the table, we make a list of observers for that command + ObserverList* commandObservers; + if (!mObserversTable.Get(aCommandToObserve, &commandObservers)) { + commandObservers = new ObserverList; + mObserversTable.Put(aCommandToObserve, commandObservers); + } + + // need to check that this command observer hasn't already been registered + int32_t existingIndex = commandObservers->IndexOf(aCommandObserver); + if (existingIndex == -1) { + commandObservers->AppendElement(aCommandObserver); + } else { + NS_WARNING("Registering command observer twice on the same command"); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCommandManager::RemoveCommandObserver(nsIObserver* aCommandObserver, + const char* aCommandObserved) +{ + NS_ENSURE_ARG(aCommandObserver); + + // XXX todo: handle special cases of aCommandToObserve being null, or empty + + ObserverList* commandObservers; + if (!mObserversTable.Get(aCommandObserved, &commandObservers)) { + return NS_ERROR_UNEXPECTED; + } + + commandObservers->RemoveElement(aCommandObserver); + + return NS_OK; +} + +NS_IMETHODIMP +nsCommandManager::IsCommandSupported(const char* aCommandName, + mozIDOMWindowProxy* aTargetWindow, + bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + nsCOMPtr<nsIController> controller; + GetControllerForCommand(aCommandName, aTargetWindow, + getter_AddRefs(controller)); + *aResult = (controller.get() != nullptr); + return NS_OK; +} + +NS_IMETHODIMP +nsCommandManager::IsCommandEnabled(const char* aCommandName, + mozIDOMWindowProxy* aTargetWindow, + bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + bool commandEnabled = false; + + nsCOMPtr<nsIController> controller; + GetControllerForCommand(aCommandName, aTargetWindow, + getter_AddRefs(controller)); + if (controller) { + controller->IsCommandEnabled(aCommandName, &commandEnabled); + } + *aResult = commandEnabled; + return NS_OK; +} + +NS_IMETHODIMP +nsCommandManager::GetCommandState(const char* aCommandName, + mozIDOMWindowProxy* aTargetWindow, + nsICommandParams* aCommandParams) +{ + nsCOMPtr<nsIController> controller; + nsAutoString tValue; + nsresult rv = GetControllerForCommand(aCommandName, aTargetWindow, + getter_AddRefs(controller)); + if (!controller) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsICommandController> commandController = + do_QueryInterface(controller); + if (commandController) { + rv = commandController->GetCommandStateWithParams(aCommandName, + aCommandParams); + } else { + rv = NS_ERROR_NOT_IMPLEMENTED; + } + return rv; +} + +NS_IMETHODIMP +nsCommandManager::DoCommand(const char* aCommandName, + nsICommandParams* aCommandParams, + mozIDOMWindowProxy* aTargetWindow) +{ + nsCOMPtr<nsIController> controller; + nsresult rv = GetControllerForCommand(aCommandName, aTargetWindow, + getter_AddRefs(controller)); + if (!controller) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsICommandController> commandController = + do_QueryInterface(controller); + if (commandController && aCommandParams) { + rv = commandController->DoCommandWithParams(aCommandName, aCommandParams); + } else { + rv = controller->DoCommand(aCommandName); + } + return rv; +} + +nsresult +nsCommandManager::GetControllerForCommand(const char* aCommand, + mozIDOMWindowProxy* aTargetWindow, + nsIController** aResult) +{ + nsresult rv = NS_ERROR_FAILURE; + *aResult = nullptr; + + // check if we're in content or chrome + // if we're not chrome we must have a target window or we bail + if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) { + if (!aTargetWindow) { + return rv; + } + + // if a target window is specified, it must be the window we expect + if (aTargetWindow != mWindow) { + return NS_ERROR_FAILURE; + } + } + + if (auto* targetWindow = nsPIDOMWindowOuter::From(aTargetWindow)) { + // get the controller for this particular window + nsCOMPtr<nsIControllers> controllers; + rv = targetWindow->GetControllers(getter_AddRefs(controllers)); + if (NS_FAILED(rv)) { + return rv; + } + if (!controllers) { + return NS_ERROR_FAILURE; + } + + // dispatch the command + return controllers->GetControllerForCommand(aCommand, aResult); + } + + auto* window = nsPIDOMWindowOuter::From(mWindow); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot(); + NS_ENSURE_TRUE(root, NS_ERROR_FAILURE); + + // no target window; send command to focus controller + return root->GetControllerForCommand(aCommand, aResult); +} diff --git a/embedding/components/commandhandler/nsCommandManager.h b/embedding/components/commandhandler/nsCommandManager.h new file mode 100644 index 000000000..1d26c40ba --- /dev/null +++ b/embedding/components/commandhandler/nsCommandManager.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCommandManager_h__ +#define nsCommandManager_h__ + +#include "nsString.h" +#include "nsClassHashtable.h" +#include "nsWeakReference.h" + +#include "nsICommandManager.h" +#include "nsPICommandUpdater.h" +#include "nsCycleCollectionParticipant.h" + +class nsIController; +template<class E> class nsCOMArray; + +class nsCommandManager + : public nsICommandManager + , public nsPICommandUpdater + , public nsSupportsWeakReference +{ +public: + typedef nsTArray<nsCOMPtr<nsIObserver> > ObserverList; + + nsCommandManager(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsCommandManager, nsICommandManager) + + NS_DECL_NSICOMMANDMANAGER + NS_DECL_NSPICOMMANDUPDATER + +protected: + virtual ~nsCommandManager(); + + nsresult GetControllerForCommand(const char* aCommand, + mozIDOMWindowProxy* aDirectedToThisWindow, + nsIController** aResult); + +protected: + nsClassHashtable<nsCharPtrHashKey, ObserverList> mObserversTable; + + mozIDOMWindowProxy* mWindow; // weak ptr. The window should always outlive us +}; + +#endif // nsCommandManager_h__ diff --git a/embedding/components/commandhandler/nsCommandParams.cpp b/embedding/components/commandhandler/nsCommandParams.cpp new file mode 100644 index 000000000..86647d4dd --- /dev/null +++ b/embedding/components/commandhandler/nsCommandParams.cpp @@ -0,0 +1,264 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcom-config.h" +#include <new> +#include "nscore.h" +#include "nsCRT.h" + +#include "nsCommandParams.h" +#include "mozilla/HashFunctions.h" + +using namespace mozilla; + +const PLDHashTableOps nsCommandParams::sHashOps = +{ + HashKey, + HashMatchEntry, + HashMoveEntry, + HashClearEntry +}; + +NS_IMPL_ISUPPORTS(nsCommandParams, nsICommandParams) + +nsCommandParams::nsCommandParams() + : mValuesHash(&sHashOps, sizeof(HashEntry), 2) +{ +} + +nsCommandParams::~nsCommandParams() +{ +} + +NS_IMETHODIMP +nsCommandParams::GetValueType(const char* aName, int16_t* aRetVal) +{ + NS_ENSURE_ARG_POINTER(aRetVal); + + HashEntry* foundEntry = GetNamedEntry(aName); + if (foundEntry) { + *aRetVal = foundEntry->mEntryType; + return NS_OK; + } + *aRetVal = eNoType; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsCommandParams::GetBooleanValue(const char* aName, bool* aRetVal) +{ + NS_ENSURE_ARG_POINTER(aRetVal); + + HashEntry* foundEntry = GetNamedEntry(aName); + if (foundEntry && foundEntry->mEntryType == eBooleanType) { + *aRetVal = foundEntry->mData.mBoolean; + return NS_OK; + } + *aRetVal = false; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsCommandParams::GetLongValue(const char* aName, int32_t* aRetVal) +{ + NS_ENSURE_ARG_POINTER(aRetVal); + + HashEntry* foundEntry = GetNamedEntry(aName); + if (foundEntry && foundEntry->mEntryType == eLongType) { + *aRetVal = foundEntry->mData.mLong; + return NS_OK; + } + *aRetVal = false; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsCommandParams::GetDoubleValue(const char* aName, double* aRetVal) +{ + NS_ENSURE_ARG_POINTER(aRetVal); + + HashEntry* foundEntry = GetNamedEntry(aName); + if (foundEntry && foundEntry->mEntryType == eDoubleType) { + *aRetVal = foundEntry->mData.mDouble; + return NS_OK; + } + *aRetVal = 0.0; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsCommandParams::GetStringValue(const char* aName, nsAString& aRetVal) +{ + HashEntry* foundEntry = GetNamedEntry(aName); + if (foundEntry && foundEntry->mEntryType == eWStringType) { + NS_ASSERTION(foundEntry->mData.mString, "Null string"); + aRetVal.Assign(*foundEntry->mData.mString); + return NS_OK; + } + aRetVal.Truncate(); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsCommandParams::GetCStringValue(const char* aName, char** aRetVal) +{ + NS_ENSURE_ARG_POINTER(aRetVal); + + HashEntry* foundEntry = GetNamedEntry(aName); + if (foundEntry && foundEntry->mEntryType == eStringType) { + NS_ASSERTION(foundEntry->mData.mCString, "Null string"); + *aRetVal = ToNewCString(*foundEntry->mData.mCString); + return NS_OK; + } + *aRetVal = nullptr; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsCommandParams::GetISupportsValue(const char* aName, nsISupports** aRetVal) +{ + NS_ENSURE_ARG_POINTER(aRetVal); + + HashEntry* foundEntry = GetNamedEntry(aName); + if (foundEntry && foundEntry->mEntryType == eISupportsType) { + NS_IF_ADDREF(*aRetVal = foundEntry->mISupports.get()); + return NS_OK; + } + *aRetVal = nullptr; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsCommandParams::SetBooleanValue(const char* aName, bool aValue) +{ + HashEntry* foundEntry = GetOrMakeEntry(aName, eBooleanType); + if (!foundEntry) { + return NS_ERROR_OUT_OF_MEMORY; + } + foundEntry->mData.mBoolean = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsCommandParams::SetLongValue(const char* aName, int32_t aValue) +{ + HashEntry* foundEntry = GetOrMakeEntry(aName, eLongType); + if (!foundEntry) { + return NS_ERROR_OUT_OF_MEMORY; + } + foundEntry->mData.mLong = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsCommandParams::SetDoubleValue(const char* aName, double aValue) +{ + HashEntry* foundEntry = GetOrMakeEntry(aName, eDoubleType); + if (!foundEntry) { + return NS_ERROR_OUT_OF_MEMORY; + } + foundEntry->mData.mDouble = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsCommandParams::SetStringValue(const char* aName, const nsAString& aValue) +{ + HashEntry* foundEntry = GetOrMakeEntry(aName, eWStringType); + if (!foundEntry) { + return NS_ERROR_OUT_OF_MEMORY; + } + foundEntry->mData.mString = new nsString(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsCommandParams::SetCStringValue(const char* aName, const char* aValue) +{ + HashEntry* foundEntry = GetOrMakeEntry(aName, eStringType); + if (!foundEntry) { + return NS_ERROR_OUT_OF_MEMORY; + } + foundEntry->mData.mCString = new nsCString(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsCommandParams::SetISupportsValue(const char* aName, nsISupports* aValue) +{ + HashEntry* foundEntry = GetOrMakeEntry(aName, eISupportsType); + if (!foundEntry) { + return NS_ERROR_OUT_OF_MEMORY; + } + foundEntry->mISupports = aValue; // addrefs + return NS_OK; +} + +NS_IMETHODIMP +nsCommandParams::RemoveValue(const char* aName) +{ + mValuesHash.Remove((void*)aName); + return NS_OK; +} + +nsCommandParams::HashEntry* +nsCommandParams::GetNamedEntry(const char* aName) +{ + return static_cast<HashEntry*>(mValuesHash.Search((void*)aName)); +} + +nsCommandParams::HashEntry* +nsCommandParams::GetOrMakeEntry(const char* aName, uint8_t aEntryType) +{ + auto foundEntry = static_cast<HashEntry*>(mValuesHash.Search((void*)aName)); + if (foundEntry) { // reuse existing entry + foundEntry->Reset(aEntryType); + return foundEntry; + } + + foundEntry = static_cast<HashEntry*>(mValuesHash.Add((void*)aName, fallible)); + if (!foundEntry) { + return nullptr; + } + + // Use placement new. Our ctor does not clobber keyHash, which is important. + new (foundEntry) HashEntry(aEntryType, aName); + return foundEntry; +} + +PLDHashNumber +nsCommandParams::HashKey(const void* aKey) +{ + return HashString((const char*)aKey); +} + +bool +nsCommandParams::HashMatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey) +{ + const char* keyString = (const char*)aKey; + const HashEntry* thisEntry = static_cast<const HashEntry*>(aEntry); + return thisEntry->mEntryName.Equals(keyString); +} + +void +nsCommandParams::HashMoveEntry(PLDHashTable* aTable, + const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo) +{ + const HashEntry* fromEntry = static_cast<const HashEntry*>(aFrom); + HashEntry* toEntry = static_cast<HashEntry*>(aTo); + + new (toEntry) HashEntry(*fromEntry); + + fromEntry->~HashEntry(); +} + +void +nsCommandParams::HashClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry) +{ + HashEntry* thisEntry = static_cast<HashEntry*>(aEntry); + thisEntry->~HashEntry(); +} diff --git a/embedding/components/commandhandler/nsCommandParams.h b/embedding/components/commandhandler/nsCommandParams.h new file mode 100644 index 000000000..52df28d79 --- /dev/null +++ b/embedding/components/commandhandler/nsCommandParams.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCommandParams_h__ +#define nsCommandParams_h__ + +#include "nsString.h" +#include "nsICommandParams.h" +#include "nsCOMPtr.h" +#include "PLDHashTable.h" + +class nsCommandParams : public nsICommandParams +{ +public: + nsCommandParams(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICOMMANDPARAMS + +protected: + virtual ~nsCommandParams(); + + struct HashEntry : public PLDHashEntryHdr + { + nsCString mEntryName; + + uint8_t mEntryType; + union + { + bool mBoolean; + int32_t mLong; + double mDouble; + nsString* mString; + nsCString* mCString; + } mData; + + nsCOMPtr<nsISupports> mISupports; + + HashEntry(uint8_t aType, const char* aEntryName) + : mEntryName(aEntryName) + , mEntryType(aType) + { + Reset(mEntryType); + } + + HashEntry(const HashEntry& aRHS) + : mEntryType(aRHS.mEntryType) + { + Reset(mEntryType); + switch (mEntryType) { + case eBooleanType: + mData.mBoolean = aRHS.mData.mBoolean; + break; + case eLongType: + mData.mLong = aRHS.mData.mLong; + break; + case eDoubleType: + mData.mDouble = aRHS.mData.mDouble; + break; + case eWStringType: + NS_ASSERTION(aRHS.mData.mString, "Source entry has no string"); + mData.mString = new nsString(*aRHS.mData.mString); + break; + case eStringType: + NS_ASSERTION(aRHS.mData.mCString, "Source entry has no string"); + mData.mCString = new nsCString(*aRHS.mData.mCString); + break; + case eISupportsType: + mISupports = aRHS.mISupports.get(); + break; + default: + NS_ERROR("Unknown type"); + } + } + + ~HashEntry() { Reset(eNoType); } + + void Reset(uint8_t aNewType) + { + switch (mEntryType) { + case eNoType: + break; + case eBooleanType: + mData.mBoolean = false; + break; + case eLongType: + mData.mLong = 0; + break; + case eDoubleType: + mData.mDouble = 0.0; + break; + case eWStringType: + delete mData.mString; + mData.mString = nullptr; + break; + case eISupportsType: + mISupports = nullptr; + break; + case eStringType: + delete mData.mCString; + mData.mCString = nullptr; + break; + default: + NS_ERROR("Unknown type"); + } + mEntryType = aNewType; + } + }; + + HashEntry* GetNamedEntry(const char* aName); + HashEntry* GetOrMakeEntry(const char* aName, uint8_t aEntryType); + +protected: + static PLDHashNumber HashKey(const void* aKey); + + static bool HashMatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey); + + static void HashMoveEntry(PLDHashTable* aTable, const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo); + + static void HashClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry); + + PLDHashTable mValuesHash; + + static const PLDHashTableOps sHashOps; +}; + +#endif // nsCommandParams_h__ diff --git a/embedding/components/commandhandler/nsControllerCommandTable.cpp b/embedding/components/commandhandler/nsControllerCommandTable.cpp new file mode 100644 index 000000000..91075bba4 --- /dev/null +++ b/embedding/components/commandhandler/nsControllerCommandTable.cpp @@ -0,0 +1,209 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" +#include "nsIControllerCommand.h" +#include "nsControllerCommandTable.h" + +nsresult NS_NewControllerCommandTable(nsIControllerCommandTable** aResult); + +// this value is used to size the hash table. Just a sensible upper bound +#define NUM_COMMANDS_LENGTH 32 + +nsControllerCommandTable::nsControllerCommandTable() + : mCommandsTable(NUM_COMMANDS_LENGTH) + , mMutable(true) +{ +} + +nsControllerCommandTable::~nsControllerCommandTable() +{ +} + +NS_IMPL_ISUPPORTS(nsControllerCommandTable, nsIControllerCommandTable, + nsISupportsWeakReference) + +NS_IMETHODIMP +nsControllerCommandTable::MakeImmutable(void) +{ + mMutable = false; + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandTable::RegisterCommand(const char* aCommandName, + nsIControllerCommand* aCommand) +{ + NS_ENSURE_TRUE(mMutable, NS_ERROR_FAILURE); + + mCommandsTable.Put(nsDependentCString(aCommandName), aCommand); + + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandTable::UnregisterCommand(const char* aCommandName, + nsIControllerCommand* aCommand) +{ + NS_ENSURE_TRUE(mMutable, NS_ERROR_FAILURE); + + nsDependentCString commandKey(aCommandName); + if (!mCommandsTable.Get(commandKey, nullptr)) { + return NS_ERROR_FAILURE; + } + + mCommandsTable.Remove(commandKey); + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandTable::FindCommandHandler(const char* aCommandName, + nsIControllerCommand** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = nullptr; + + nsCOMPtr<nsIControllerCommand> foundCommand; + mCommandsTable.Get(nsDependentCString(aCommandName), + getter_AddRefs(foundCommand)); + if (!foundCommand) { + return NS_ERROR_FAILURE; + } + + foundCommand.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandTable::IsCommandEnabled(const char* aCommandName, + nsISupports* aCommandRefCon, + bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = false; + + nsCOMPtr<nsIControllerCommand> commandHandler; + FindCommandHandler(aCommandName, getter_AddRefs(commandHandler)); + if (!commandHandler) { + NS_WARNING("Controller command table asked about a command that it does " + "not handle"); + return NS_OK; + } + + return commandHandler->IsCommandEnabled(aCommandName, aCommandRefCon, + aResult); +} + +NS_IMETHODIMP +nsControllerCommandTable::UpdateCommandState(const char* aCommandName, + nsISupports* aCommandRefCon) +{ + nsCOMPtr<nsIControllerCommand> commandHandler; + FindCommandHandler(aCommandName, getter_AddRefs(commandHandler)); + if (!commandHandler) { + NS_WARNING("Controller command table asked to update the state of a " + "command that it does not handle"); + return NS_OK; + } + + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsControllerCommandTable::SupportsCommand(const char* aCommandName, + nsISupports* aCommandRefCon, + bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + // XXX: need to check the readonly and disabled states + + *aResult = false; + + nsCOMPtr<nsIControllerCommand> commandHandler; + FindCommandHandler(aCommandName, getter_AddRefs(commandHandler)); + + *aResult = (commandHandler.get() != nullptr); + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandTable::DoCommand(const char* aCommandName, + nsISupports* aCommandRefCon) +{ + nsCOMPtr<nsIControllerCommand> commandHandler; + FindCommandHandler(aCommandName, getter_AddRefs(commandHandler)); + if (!commandHandler) { + NS_WARNING("Controller command table asked to do a command that it does " + "not handle"); + return NS_OK; + } + + return commandHandler->DoCommand(aCommandName, aCommandRefCon); +} + +NS_IMETHODIMP +nsControllerCommandTable::DoCommandParams(const char* aCommandName, + nsICommandParams* aParams, + nsISupports* aCommandRefCon) +{ + nsCOMPtr<nsIControllerCommand> commandHandler; + FindCommandHandler(aCommandName, getter_AddRefs(commandHandler)); + if (!commandHandler) { + NS_WARNING("Controller command table asked to do a command that it does " + "not handle"); + return NS_OK; + } + return commandHandler->DoCommandParams(aCommandName, aParams, aCommandRefCon); +} + +NS_IMETHODIMP +nsControllerCommandTable::GetCommandState(const char* aCommandName, + nsICommandParams* aParams, + nsISupports* aCommandRefCon) +{ + nsCOMPtr<nsIControllerCommand> commandHandler; + FindCommandHandler(aCommandName, getter_AddRefs(commandHandler)); + if (!commandHandler) { + NS_WARNING("Controller command table asked to do a command that it does " + "not handle"); + return NS_OK; + } + return commandHandler->GetCommandStateParams(aCommandName, aParams, + aCommandRefCon); +} + +NS_IMETHODIMP +nsControllerCommandTable::GetSupportedCommands(uint32_t* aCount, + char*** aCommands) +{ + char** commands = + static_cast<char**>(moz_xmalloc(sizeof(char*) * mCommandsTable.Count())); + *aCount = mCommandsTable.Count(); + *aCommands = commands; + + for (auto iter = mCommandsTable.Iter(); !iter.Done(); iter.Next()) { + *commands = ToNewCString(iter.Key()); + commands++; + } + return NS_OK; +} + +nsresult +NS_NewControllerCommandTable(nsIControllerCommandTable** aResult) +{ + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + nsControllerCommandTable* newCommandTable = new nsControllerCommandTable(); + NS_ADDREF(newCommandTable); + *aResult = newCommandTable; + return NS_OK; +} diff --git a/embedding/components/commandhandler/nsControllerCommandTable.h b/embedding/components/commandhandler/nsControllerCommandTable.h new file mode 100644 index 000000000..353850bc0 --- /dev/null +++ b/embedding/components/commandhandler/nsControllerCommandTable.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsControllerCommandTable_h_ +#define nsControllerCommandTable_h_ + +#include "nsIControllerCommandTable.h" +#include "nsWeakReference.h" +#include "nsInterfaceHashtable.h" + +class nsIControllerCommand; + +class nsControllerCommandTable final + : public nsIControllerCommandTable + , public nsSupportsWeakReference +{ +public: + nsControllerCommandTable(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICONTROLLERCOMMANDTABLE + +protected: + virtual ~nsControllerCommandTable(); + + // Hash table of nsIControllerCommands, keyed by command name. + nsInterfaceHashtable<nsCStringHashKey, nsIControllerCommand> mCommandsTable; + + // Are we mutable? + bool mMutable; +}; + +#endif // nsControllerCommandTable_h_ diff --git a/embedding/components/commandhandler/nsICommandManager.idl b/embedding/components/commandhandler/nsICommandManager.idl new file mode 100644 index 000000000..958108740 --- /dev/null +++ b/embedding/components/commandhandler/nsICommandManager.idl @@ -0,0 +1,118 @@ +/* -*- 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" +#include "nsIObserver.idl" +#include "nsICommandParams.idl" + +interface mozIDOMWindowProxy; + +/* + * nsICommandManager is an interface used to executing user-level commands, + * and getting the state of available commands. + * + * Commands are identified by strings, which are documented elsewhere. + * In addition, the list of required and optional parameters for + * each command, that are passed in via the nsICommandParams, are + * also documented elsewhere. (Where? Need a good location for this). + */ + + +[scriptable, uuid(bb5a1730-d83b-4fa2-831b-35b9d5842e84)] +interface nsICommandManager : nsISupports +{ + /* + * Register an observer on the specified command. The observer's Observe + * method will get called when the state (enabled/disbaled, or toggled etc) + * of the command changes. + * + * You can register the same observer on multiple commmands by calling this + * multiple times. + */ + void addCommandObserver(in nsIObserver aCommandObserver, + in string aCommandToObserve); + + /* + * Stop an observer from observering the specified command. If the observer + * was also registered on ther commands, they will continue to be observed. + * + * Passing an empty string in 'aCommandObserved' will remove the observer + * from all commands. + */ + void removeCommandObserver(in nsIObserver aCommandObserver, + in string aCommandObserved); + + /* + * Ask the command manager if the specified command is supported. + * If aTargetWindow is null, the focused window is used. + * + */ + boolean isCommandSupported(in string aCommandName, + in mozIDOMWindowProxy aTargetWindow); + + /* + * Ask the command manager if the specified command is currently. + * enabled. + * If aTargetWindow is null, the focused window is used. + */ + boolean isCommandEnabled(in string aCommandName, + in mozIDOMWindowProxy aTargetWindow); + + /* + * Get the state of the specified commands. + * + * On input: aCommandParams filled in with values that the caller cares + * about, most of which are command-specific (see the command documentation + * for details). One boolean value, "enabled", applies to all commands, + * and, in return will be set to indicate whether the command is enabled + * (equivalent to calling isCommandEnabled). + * + * aCommandName is the name of the command that needs the state + * aTargetWindow is the source of command controller + * (null means use focus controller) + * On output: aCommandParams: values set by the caller filled in with + * state from the command. + */ + void getCommandState(in string aCommandName, + in mozIDOMWindowProxy aTargetWindow, + /* inout */ in nsICommandParams aCommandParams); + + /* + * Execute the specified command. + * The command will be executed in aTargetWindow if it is specified. + * If aTargetWindow is null, it will go to the focused window. + * + * param: aCommandParams, a list of name-value pairs of command parameters, + * may be null for parameter-less commands. + * + */ + void doCommand(in string aCommandName, + in nsICommandParams aCommandParams, + in mozIDOMWindowProxy aTargetWindow); + +}; + + +/* + +Arguments to observers "Observe" method are as follows: + + void Observe( in nsISupports aSubject, // The nsICommandManager calling this Observer + in string aTopic, // Name of the command + in wstring aDummy ); // unused + +*/ + +// {64edb481-0c04-11d5-a73c-e964b968b0bc} +%{C++ +#define NS_COMMAND_MANAGER_CID \ +{ 0x64edb481, 0x0c04, 0x11d5, { 0xa7, 0x3c, 0xe9, 0x64, 0xb9, 0x68, 0xb0, 0xbc } } + +#define NS_COMMAND_MANAGER_CONTRACTID \ + "@mozilla.org/embedcomp/command-manager;1" +%} + + + diff --git a/embedding/components/commandhandler/nsICommandParams.idl b/embedding/components/commandhandler/nsICommandParams.idl new file mode 100644 index 000000000..fcd3ff973 --- /dev/null +++ b/embedding/components/commandhandler/nsICommandParams.idl @@ -0,0 +1,85 @@ +/* -*- 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" + +/* + * nsICommandParams is used to pass parameters to commands executed + * via nsICommandManager, and to get command state. + * + */ + +[scriptable, uuid(b1fdf3c4-74e3-4f7d-a14d-2b76bcf53482)] +interface nsICommandParams : nsISupports +{ + /* + * List of primitive types for parameter values. + */ + const short eNoType = 0; /* Only used for sanity checking */ + const short eBooleanType = 1; + const short eLongType = 2; + const short eDoubleType = 3; + const short eWStringType = 4; + const short eISupportsType = 5; + const short eStringType = 6; + + /* + * getValueType + * + * Get the type of a specified parameter + */ + short getValueType(in string name); + + /* + * get_Value + * + * Get the value of a specified parameter. Will return + * an error if the parameter does not exist, or if the value + * is of the wrong type (no coercion is performed for you). + * + * nsISupports values can contain any XPCOM interface, + * as documented for the command. It is permissible + * for it to contain nsICommandParams, but not *this* + * one (i.e. self-containing is not allowed). + */ + boolean getBooleanValue(in string name); + long getLongValue(in string name); + double getDoubleValue(in string name); + AString getStringValue(in string name); + string getCStringValue(in string name); + nsISupports getISupportsValue(in string name); + + /* + * set_Value + * + * Set the value of a specified parameter (thus creating + * an entry for it). + * + * nsISupports values can contain any XPCOM interface, + * as documented for the command. It is permissible + * for it to contain nsICommandParams, but not *this* + * one (i.e. self-containing is not allowed). + */ + void setBooleanValue(in string name, in boolean value); + void setLongValue(in string name, in long value); + void setDoubleValue(in string name, in double value); + void setStringValue(in string name, in AString value); + void setCStringValue(in string name, in string value); + void setISupportsValue(in string name, in nsISupports value); + + /* + * removeValue + * + * Remove the specified parameter from the list. + */ + void removeValue(in string name); +}; + +// {f7fa4581-238e-11d5-a73c-ab64fb68f2bc} +%{C++ +#define NS_COMMAND_PARAMS_CID { 0xf7fa4581, 0x238e, 0x11d5, { 0xa7, 0x3c, 0xab, 0x64, 0xfb, 0x68, 0xf2, 0xbc } } +#define NS_COMMAND_PARAMS_CONTRACTID "@mozilla.org/embedcomp/command-params;1" +%} + diff --git a/embedding/components/commandhandler/nsIControllerCommand.idl b/embedding/components/commandhandler/nsIControllerCommand.idl new file mode 100644 index 000000000..a1ea583a4 --- /dev/null +++ b/embedding/components/commandhandler/nsIControllerCommand.idl @@ -0,0 +1,51 @@ +/* -*- 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" +#include "nsICommandParams.idl" + +/** + * nsIControllerCommand + * + * A generic command interface. You can register an nsIControllerCommand + * with the nsIControllerCommandTable. + */ + +[scriptable, uuid(0eae9a46-1dd2-11b2-aca0-9176f05fe9db)] +interface nsIControllerCommand : nsISupports +{ + + /** + * Returns true if the command is currently enabled. An nsIControllerCommand + * can implement more than one commands; say, a group of related commands + * (e.g. delete left/delete right). Because of this, the command name is + * passed to each method. + * + * @param aCommandName the name of the command for which we want the enabled + * state. + * @param aCommandContext a cookie held by the nsIControllerCommandTable, + * allowing the command to get some context information. + * The contents of this cookie are implementation-defined. + */ + boolean isCommandEnabled(in string aCommandName, in nsISupports aCommandContext); + + void getCommandStateParams(in string aCommandName, in nsICommandParams aParams, in nsISupports aCommandContext); + + /** + * Execute the name command. + * + * @param aCommandName the name of the command to execute. + * + * @param aCommandContext a cookie held by the nsIControllerCommandTable, + * allowing the command to get some context information. + * The contents of this cookie are implementation-defined. + */ + void doCommand(in string aCommandName, in nsISupports aCommandContext); + + void doCommandParams(in string aCommandName, in nsICommandParams aParams, in nsISupports aCommandContext); + +}; + diff --git a/embedding/components/commandhandler/nsIControllerCommandTable.idl b/embedding/components/commandhandler/nsIControllerCommandTable.idl new file mode 100644 index 000000000..fb2c86fae --- /dev/null +++ b/embedding/components/commandhandler/nsIControllerCommandTable.idl @@ -0,0 +1,100 @@ +/* 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 "nsIControllerCommand.idl" +#include "nsICommandParams.idl" + +/** + * nsIControllerCommandTable + * + * An interface via which a controller can maintain a series of commands, + * and efficiently dispatch commands to their respective handlers. + * + * Controllers that use an nsIControllerCommandTable should support + * nsIInterfaceRequestor, and be able to return an interface to their + * controller command table via getInterface(). + * + */ + +[scriptable, uuid(c847f90e-b8f3-49db-a4df-8867831f2800)] +interface nsIControllerCommandTable : nsISupports +{ + /** + * Make this command table immutable, so that commands cannot + * be registered or unregistered. Some command tables are made + * mutable after command registration so that they can be + * used as singletons. + */ + void makeImmutable(); + + /** + * Register and unregister commands with the command table. + * + * @param aCommandName the name of the command under which to register or + * unregister the given command handler. + * + * @param aCommand the handler for this command. + */ + void registerCommand(in string aCommandName, in nsIControllerCommand aCommand); + void unregisterCommand(in string aCommandName, in nsIControllerCommand aCommand); + + /** + * Find the command handler which has been registered to handle the named command. + * + * @param aCommandName the name of the command to find the handler for. + */ + nsIControllerCommand findCommandHandler(in string aCommandName); + + /** + * Get whether the named command is enabled. + * + * @param aCommandName the name of the command to test + * @param aCommandRefCon the command context data + */ + boolean isCommandEnabled(in string aCommandName, in nsISupports aCommandRefCon); + + /** + * Tell the command to update its state (if it is a state updating command) + * + * @param aCommandName the name of the command to update + * @param aCommandRefCon the command context data + */ + void updateCommandState(in string aCommandName, in nsISupports aCommandRefCon); + + /** + * Get whether the named command is supported. + * + * @param aCommandName the name of the command to test + * @param aCommandRefCon the command context data + */ + boolean supportsCommand(in string aCommandName, in nsISupports aCommandRefCon); + + /** + * Execute the named command. + * + * @param aCommandName the name of the command to execute + * @param aCommandRefCon the command context data + */ + void doCommand(in string aCommandName, in nsISupports aCommandRefCon); + + void doCommandParams(in string aCommandName, in nsICommandParams aParam, in nsISupports aCommandRefCon); + + void getCommandState(in string aCommandName, in nsICommandParams aParam, in nsISupports aCommandRefCon); + + void getSupportedCommands(out unsigned long count, + [array, size_is(count), retval] out string commands); +}; + + + +%{C++ +// {670ee5da-6ad5-11d7-9950-000393636592} +#define NS_CONTROLLERCOMMANDTABLE_CID \ + {0x670ee5da, 0x6ad5, 0x11d7, \ + { 0x99, 0x50, 0x00, 0x03, 0x93, 0x63, 0x65, 0x92 }} + +#define NS_CONTROLLERCOMMANDTABLE_CONTRACTID \ + "@mozilla.org/embedcomp/controller-command-table;1" +%} diff --git a/embedding/components/commandhandler/nsIControllerContext.idl b/embedding/components/commandhandler/nsIControllerContext.idl new file mode 100644 index 000000000..7c7ed67fd --- /dev/null +++ b/embedding/components/commandhandler/nsIControllerContext.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/. */ + +#include "nsISupports.idl" +#include "nsIControllerCommandTable.idl" + +[scriptable, uuid(47B82B60-A36F-4167-8072-6F421151ED50)] +interface nsIControllerContext : nsISupports +{ + + /** + * Init the controller, optionally passing a controller + * command table. + * + * @param aCommandTable a command table, used internally + * by this controller. May be null, in + * which case the controller will create + * a new, empty table. + */ + void init(in nsIControllerCommandTable aCommandTable); + + /** + * Set a context on this controller, which is passed + * to commands to give them some context when they execute. + * + * @param aCommandContext the context passed to commands. + * Note that this is *not* addreffed by the + * controller, and so needs to outlive it, + * or be nulled out. + */ + void setCommandContext(in nsISupports aCommandContext); + +}; diff --git a/embedding/components/commandhandler/nsPICommandUpdater.idl b/embedding/components/commandhandler/nsPICommandUpdater.idl new file mode 100644 index 000000000..3712dbdff --- /dev/null +++ b/embedding/components/commandhandler/nsPICommandUpdater.idl @@ -0,0 +1,39 @@ +/* -*- 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 mozIDOMWindowProxy; + +/* + The nsPICommandUpdater interface is used by modules that implement + commands, to tell the command manager that commands need updating. + This is a private interface; embedders should not use it. + + Command-implementing modules should get one of these by a QI + from an nsICommandManager. +*/ + +[scriptable, uuid(35e474ae-8016-4c34-9644-edc11f8b0ce1)] +interface nsPICommandUpdater : nsISupports +{ + + /* + * Init the command updater, passing an nsIDOMWindow which + * is the window that the command updater lives on. + * + */ + void init(in mozIDOMWindowProxy aWindow); + + /* + * Notify the command manager that the status of a command + * changed. It may have changed from enabled to disabled, + * or vice versa, or become toggled etc. + */ + void commandStatusChanged(in string aCommandName); + +}; + + diff --git a/embedding/components/find/moz.build b/embedding/components/find/moz.build new file mode 100644 index 000000000..6586914da --- /dev/null +++ b/embedding/components/find/moz.build @@ -0,0 +1,19 @@ +# -*- 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 += [ + 'nsIFind.idl', + 'nsIWebBrowserFind.idl', +] + +XPIDL_MODULE = 'find' + +UNIFIED_SOURCES += [ + 'nsFind.cpp', + 'nsWebBrowserFind.cpp', +] + +FINAL_LIBRARY = 'xul' diff --git a/embedding/components/find/nsFind.cpp b/embedding/components/find/nsFind.cpp new file mode 100644 index 000000000..cbc42298b --- /dev/null +++ b/embedding/components/find/nsFind.cpp @@ -0,0 +1,1383 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//#define DEBUG_FIND 1 + +#include "nsFind.h" +#include "nsContentCID.h" +#include "nsIContent.h" +#include "nsIDOMNode.h" +#include "nsIDOMNodeList.h" +#include "nsISelection.h" +#include "nsISelectionController.h" +#include "nsIFrame.h" +#include "nsITextControlFrame.h" +#include "nsIFormControl.h" +#include "nsIEditor.h" +#include "nsIPlaintextEditor.h" +#include "nsTextFragment.h" +#include "nsString.h" +#include "nsIAtom.h" +#include "nsServiceManagerUtils.h" +#include "nsUnicharUtils.h" +#include "nsIDOMElement.h" +#include "nsIWordBreaker.h" +#include "nsCRT.h" +#include "nsRange.h" +#include "nsContentUtils.h" +#include "mozilla/DebugOnly.h" + +using namespace mozilla; + +// Yikes! Casting a char to unichar can fill with ones! +#define CHAR_TO_UNICHAR(c) ((char16_t)(const unsigned char)c) + +static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID); +static NS_DEFINE_CID(kCPreContentIteratorCID, NS_PRECONTENTITERATOR_CID); + +#define CH_QUOTE ((char16_t)0x22) +#define CH_APOSTROPHE ((char16_t)0x27) +#define CH_LEFT_SINGLE_QUOTE ((char16_t)0x2018) +#define CH_RIGHT_SINGLE_QUOTE ((char16_t)0x2019) +#define CH_LEFT_DOUBLE_QUOTE ((char16_t)0x201C) +#define CH_RIGHT_DOUBLE_QUOTE ((char16_t)0x201D) + +#define CH_SHY ((char16_t)0xAD) + +// nsFind::Find casts CH_SHY to char before calling StripChars +// This works correctly if and only if CH_SHY <= 255 +static_assert(CH_SHY <= 255, "CH_SHY is not an ascii character"); + +// nsFindContentIterator is a special iterator that also goes through any +// existing <textarea>'s or text <input>'s editor to lookup the anonymous DOM +// content there. +// +// Details: +// 1) We use two iterators: The "outer-iterator" goes through the normal DOM. +// The "inner-iterator" goes through the anonymous DOM inside the editor. +// +// 2) [MaybeSetupInnerIterator] As soon as the outer-iterator's current node is +// changed, a check is made to see if the node is a <textarea> or a text <input> +// node. If so, an inner-iterator is created to lookup the anynomous contents of +// the editor underneath the text control. +// +// 3) When the inner-iterator is created, we position the outer-iterator 'after' +// (or 'before' in backward search) the text control to avoid revisiting that +// control. +// +// 4) As a consequence of searching through text controls, we can be called via +// FindNext with the current selection inside a <textarea> or a text <input>. +// This means that we can be given an initial search range that stretches across +// the anonymous DOM and the normal DOM. To cater for this situation, we split +// the anonymous part into the inner-iterator and then reposition the outer- +// iterator outside. +// +// 5) The implementation assumes that First() and Next() are only called in +// find-forward mode, while Last() and Prev() are used in find-backward. + +class nsFindContentIterator final : public nsIContentIterator +{ +public: + explicit nsFindContentIterator(bool aFindBackward) + : mStartOffset(0) + , mEndOffset(0) + , mFindBackward(aFindBackward) + { + } + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(nsFindContentIterator) + + // nsIContentIterator + virtual nsresult Init(nsINode* aRoot) override + { + NS_NOTREACHED("internal error"); + return NS_ERROR_NOT_IMPLEMENTED; + } + virtual nsresult Init(nsIDOMRange* aRange) override + { + NS_NOTREACHED("internal error"); + return NS_ERROR_NOT_IMPLEMENTED; + } + // Not a range because one of the endpoints may be anonymous. + nsresult Init(nsIDOMNode* aStartNode, int32_t aStartOffset, + nsIDOMNode* aEndNode, int32_t aEndOffset); + virtual void First() override; + virtual void Last() override; + virtual void Next() override; + virtual void Prev() override; + virtual nsINode* GetCurrentNode() override; + virtual bool IsDone() override; + virtual nsresult PositionAt(nsINode* aCurNode) override; + +protected: + virtual ~nsFindContentIterator() {} + +private: + static already_AddRefed<nsIDOMRange> CreateRange(nsINode* aNode) + { + RefPtr<nsRange> range = new nsRange(aNode); + range->SetMaySpanAnonymousSubtrees(true); + return range.forget(); + } + + nsCOMPtr<nsIContentIterator> mOuterIterator; + nsCOMPtr<nsIContentIterator> mInnerIterator; + // Can't use a range here, since we want to represent part of the flattened + // tree, including native anonymous content. + nsCOMPtr<nsIDOMNode> mStartNode; + int32_t mStartOffset; + nsCOMPtr<nsIDOMNode> mEndNode; + int32_t mEndOffset; + + nsCOMPtr<nsIContent> mStartOuterContent; + nsCOMPtr<nsIContent> mEndOuterContent; + bool mFindBackward; + + void Reset(); + void MaybeSetupInnerIterator(); + void SetupInnerIterator(nsIContent* aContent); +}; + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFindContentIterator) + NS_INTERFACE_MAP_ENTRY(nsIContentIterator) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFindContentIterator) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFindContentIterator) + +NS_IMPL_CYCLE_COLLECTION(nsFindContentIterator, mOuterIterator, mInnerIterator, + mStartOuterContent, mEndOuterContent, mEndNode, + mStartNode) + +nsresult +nsFindContentIterator::Init(nsIDOMNode* aStartNode, int32_t aStartOffset, + nsIDOMNode* aEndNode, int32_t aEndOffset) +{ + NS_ENSURE_ARG_POINTER(aStartNode); + NS_ENSURE_ARG_POINTER(aEndNode); + if (!mOuterIterator) { + if (mFindBackward) { + // Use post-order in the reverse case, so we get parents before children + // in case we want to prevent descending into a node. + mOuterIterator = do_CreateInstance(kCContentIteratorCID); + } else { + // Use pre-order in the forward case, so we get parents before children in + // case we want to prevent descending into a node. + mOuterIterator = do_CreateInstance(kCPreContentIteratorCID); + } + NS_ENSURE_ARG_POINTER(mOuterIterator); + } + + // Set up the search "range" that we will examine + mStartNode = aStartNode; + mStartOffset = aStartOffset; + mEndNode = aEndNode; + mEndOffset = aEndOffset; + + return NS_OK; +} + +void +nsFindContentIterator::First() +{ + Reset(); +} + +void +nsFindContentIterator::Last() +{ + Reset(); +} + +void +nsFindContentIterator::Next() +{ + if (mInnerIterator) { + mInnerIterator->Next(); + if (!mInnerIterator->IsDone()) { + return; + } + + // by construction, mOuterIterator is already on the next node + } else { + mOuterIterator->Next(); + } + MaybeSetupInnerIterator(); +} + +void +nsFindContentIterator::Prev() +{ + if (mInnerIterator) { + mInnerIterator->Prev(); + if (!mInnerIterator->IsDone()) { + return; + } + + // by construction, mOuterIterator is already on the previous node + } else { + mOuterIterator->Prev(); + } + MaybeSetupInnerIterator(); +} + +nsINode* +nsFindContentIterator::GetCurrentNode() +{ + if (mInnerIterator && !mInnerIterator->IsDone()) { + return mInnerIterator->GetCurrentNode(); + } + return mOuterIterator->GetCurrentNode(); +} + +bool +nsFindContentIterator::IsDone() +{ + if (mInnerIterator && !mInnerIterator->IsDone()) { + return false; + } + return mOuterIterator->IsDone(); +} + +nsresult +nsFindContentIterator::PositionAt(nsINode* aCurNode) +{ + nsINode* oldNode = mOuterIterator->GetCurrentNode(); + nsresult rv = mOuterIterator->PositionAt(aCurNode); + if (NS_SUCCEEDED(rv)) { + MaybeSetupInnerIterator(); + } else { + mOuterIterator->PositionAt(oldNode); + if (mInnerIterator) { + rv = mInnerIterator->PositionAt(aCurNode); + } + } + return rv; +} + +void +nsFindContentIterator::Reset() +{ + mInnerIterator = nullptr; + mStartOuterContent = nullptr; + mEndOuterContent = nullptr; + + // As a consequence of searching through text controls, we may have been + // initialized with a selection inside a <textarea> or a text <input>. + + // see if the start node is an anonymous text node inside a text control + nsCOMPtr<nsIContent> startContent(do_QueryInterface(mStartNode)); + if (startContent) { + mStartOuterContent = startContent->FindFirstNonChromeOnlyAccessContent(); + } + + // see if the end node is an anonymous text node inside a text control + nsCOMPtr<nsIContent> endContent(do_QueryInterface(mEndNode)); + if (endContent) { + mEndOuterContent = endContent->FindFirstNonChromeOnlyAccessContent(); + } + + // Note: OK to just set up the outer iterator here; if our range has a native + // anonymous endpoint we'll end up setting up an inner iterator, and reset the + // outer one in the process. + nsCOMPtr<nsINode> node = do_QueryInterface(mStartNode); + NS_ENSURE_TRUE_VOID(node); + + nsCOMPtr<nsIDOMRange> range = CreateRange(node); + range->SetStart(mStartNode, mStartOffset); + range->SetEnd(mEndNode, mEndOffset); + mOuterIterator->Init(range); + + if (!mFindBackward) { + if (mStartOuterContent != startContent) { + // the start node was an anonymous text node + SetupInnerIterator(mStartOuterContent); + if (mInnerIterator) { + mInnerIterator->First(); + } + } + if (!mOuterIterator->IsDone()) { + mOuterIterator->First(); + } + } else { + if (mEndOuterContent != endContent) { + // the end node was an anonymous text node + SetupInnerIterator(mEndOuterContent); + if (mInnerIterator) { + mInnerIterator->Last(); + } + } + if (!mOuterIterator->IsDone()) { + mOuterIterator->Last(); + } + } + + // if we didn't create an inner-iterator, the boundary node could still be + // a text control, in which case we also need an inner-iterator straightaway + if (!mInnerIterator) { + MaybeSetupInnerIterator(); + } +} + +void +nsFindContentIterator::MaybeSetupInnerIterator() +{ + mInnerIterator = nullptr; + + nsCOMPtr<nsIContent> content = + do_QueryInterface(mOuterIterator->GetCurrentNode()); + if (!content || !content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) { + return; + } + + nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(content)); + if (!formControl->IsTextControl(true)) { + return; + } + + SetupInnerIterator(content); + if (mInnerIterator) { + if (!mFindBackward) { + mInnerIterator->First(); + // finish setup: position mOuterIterator on the actual "next" node (this + // completes its re-init, @see SetupInnerIterator) + if (!mOuterIterator->IsDone()) { + mOuterIterator->First(); + } + } else { + mInnerIterator->Last(); + // finish setup: position mOuterIterator on the actual "previous" node + // (this completes its re-init, @see SetupInnerIterator) + if (!mOuterIterator->IsDone()) { + mOuterIterator->Last(); + } + } + } +} + +void +nsFindContentIterator::SetupInnerIterator(nsIContent* aContent) +{ + if (!aContent) { + return; + } + NS_ASSERTION(!aContent->IsRootOfNativeAnonymousSubtree(), "invalid call"); + + nsITextControlFrame* tcFrame = do_QueryFrame(aContent->GetPrimaryFrame()); + if (!tcFrame) { + return; + } + + nsCOMPtr<nsIEditor> editor; + tcFrame->GetEditor(getter_AddRefs(editor)); + if (!editor) { + return; + } + + // don't mess with disabled input fields + uint32_t editorFlags = 0; + editor->GetFlags(&editorFlags); + if (editorFlags & nsIPlaintextEditor::eEditorDisabledMask) { + return; + } + + nsCOMPtr<nsIDOMElement> rootElement; + editor->GetRootElement(getter_AddRefs(rootElement)); + + nsCOMPtr<nsIDOMRange> innerRange = CreateRange(aContent); + nsCOMPtr<nsIDOMRange> outerRange = CreateRange(aContent); + if (!innerRange || !outerRange) { + return; + } + + // now create the inner-iterator + mInnerIterator = do_CreateInstance(kCPreContentIteratorCID); + + if (mInnerIterator) { + innerRange->SelectNodeContents(rootElement); + + // fix up the inner bounds, we may have to only lookup a portion + // of the text control if the current node is a boundary point + if (aContent == mStartOuterContent) { + innerRange->SetStart(mStartNode, mStartOffset); + } + if (aContent == mEndOuterContent) { + innerRange->SetEnd(mEndNode, mEndOffset); + } + // Note: we just init here. We do First() or Last() later. + mInnerIterator->Init(innerRange); + + // make sure to place the outer-iterator outside the text control so that we + // don't go there again. + nsresult res1, res2; + nsCOMPtr<nsIDOMNode> outerNode(do_QueryInterface(aContent)); + if (!mFindBackward) { // find forward + // cut the outer-iterator after the current node + res1 = outerRange->SetEnd(mEndNode, mEndOffset); + res2 = outerRange->SetStartAfter(outerNode); + } else { // find backward + // cut the outer-iterator before the current node + res1 = outerRange->SetStart(mStartNode, mStartOffset); + res2 = outerRange->SetEndBefore(outerNode); + } + if (NS_FAILED(res1) || NS_FAILED(res2)) { + // we are done with the outer-iterator, the inner-iterator will traverse + // what we want + outerRange->Collapse(true); + } + + // Note: we just re-init here, using the segment of our search range that + // is yet to be visited. Thus when we later do mOuterIterator->First() [or + // mOuterIterator->Last()], we will effectively be on the next node [or + // the previous node] _with respect to_ the search range. + mOuterIterator->Init(outerRange); + } +} + +nsresult +NS_NewFindContentIterator(bool aFindBackward, nsIContentIterator** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + nsFindContentIterator* it = new nsFindContentIterator(aFindBackward); + if (!it) { + return NS_ERROR_OUT_OF_MEMORY; + } + return it->QueryInterface(NS_GET_IID(nsIContentIterator), (void**)aResult); +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFind) + NS_INTERFACE_MAP_ENTRY(nsIFind) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFind) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFind) + +NS_IMPL_CYCLE_COLLECTION(nsFind, mLastBlockParent, mIterNode, mIterator) + +nsFind::nsFind() + : mFindBackward(false) + , mCaseSensitive(false) + , mIterOffset(0) +{ +} + +nsFind::~nsFind() +{ +} + +#ifdef DEBUG_FIND +static void +DumpNode(nsIDOMNode* aNode) +{ + if (!aNode) { + printf(">>>> Node: NULL\n"); + return; + } + nsAutoString nodeName; + aNode->GetNodeName(nodeName); + nsCOMPtr<nsIContent> textContent(do_QueryInterface(aNode)); + if (textContent && textContent->IsNodeOfType(nsINode::eTEXT)) { + nsAutoString newText; + textContent->AppendTextTo(newText); + printf(">>>> Text node (node name %s): '%s'\n", + NS_LossyConvertUTF16toASCII(nodeName).get(), + NS_LossyConvertUTF16toASCII(newText).get()); + } else { + printf(">>>> Node: %s\n", NS_LossyConvertUTF16toASCII(nodeName).get()); + } +} +#endif + +nsresult +nsFind::InitIterator(nsIDOMNode* aStartNode, int32_t aStartOffset, + nsIDOMNode* aEndNode, int32_t aEndOffset) +{ + if (!mIterator) { + mIterator = new nsFindContentIterator(mFindBackward); + NS_ENSURE_TRUE(mIterator, NS_ERROR_OUT_OF_MEMORY); + } + + NS_ENSURE_ARG_POINTER(aStartNode); + NS_ENSURE_ARG_POINTER(aEndNode); + +#ifdef DEBUG_FIND + printf("InitIterator search range:\n"); + printf(" -- start %d, ", aStartOffset); + DumpNode(aStartNode); + printf(" -- end %d, ", aEndOffset); + DumpNode(aEndNode); +#endif + + nsresult rv = mIterator->Init(aStartNode, aStartOffset, aEndNode, aEndOffset); + NS_ENSURE_SUCCESS(rv, rv); + if (mFindBackward) { + mIterator->Last(); + } else { + mIterator->First(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsFind::GetFindBackwards(bool* aFindBackward) +{ + if (!aFindBackward) { + return NS_ERROR_NULL_POINTER; + } + + *aFindBackward = mFindBackward; + return NS_OK; +} + +NS_IMETHODIMP +nsFind::SetFindBackwards(bool aFindBackward) +{ + mFindBackward = aFindBackward; + return NS_OK; +} + +NS_IMETHODIMP +nsFind::GetCaseSensitive(bool* aCaseSensitive) +{ + if (!aCaseSensitive) { + return NS_ERROR_NULL_POINTER; + } + + *aCaseSensitive = mCaseSensitive; + return NS_OK; +} + +NS_IMETHODIMP +nsFind::SetCaseSensitive(bool aCaseSensitive) +{ + mCaseSensitive = aCaseSensitive; + return NS_OK; +} + +/* attribute boolean entireWord; */ +NS_IMETHODIMP +nsFind::GetEntireWord(bool *aEntireWord) +{ + if (!aEntireWord) + return NS_ERROR_NULL_POINTER; + + *aEntireWord = !!mWordBreaker; + return NS_OK; +} + +NS_IMETHODIMP +nsFind::SetEntireWord(bool aEntireWord) +{ + mWordBreaker = aEntireWord ? nsContentUtils::WordBreaker() : nullptr; + return NS_OK; +} + +// Here begins the find code. A ten-thousand-foot view of how it works: Find +// needs to be able to compare across inline (but not block) nodes, e.g. find +// for "abc" should match a<b>b</b>c. So after we've searched a node, we're not +// done with it; in the case of a partial match we may need to reset the +// iterator to go back to a previously visited node, so we always save the +// "match anchor" node and offset. +// +// Text nodes store their text in an nsTextFragment, which is effectively a +// union of a one-byte string or a two-byte string. Single and double strings +// are intermixed in the dom. We don't have string classes which can deal with +// intermixed strings, so all the handling is done explicitly here. + +nsresult +nsFind::NextNode(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, nsIDOMRange* aEndPoint, + bool aContinueOk) +{ + nsresult rv; + + nsCOMPtr<nsIContent> content; + + if (!mIterator || aContinueOk) { + // If we are continuing, that means we have a match in progress. In that + // case, we want to continue from the end point (where we are now) to the + // beginning/end of the search range. + nsCOMPtr<nsIDOMNode> startNode; + nsCOMPtr<nsIDOMNode> endNode; + int32_t startOffset, endOffset; + if (aContinueOk) { +#ifdef DEBUG_FIND + printf("Match in progress: continuing past endpoint\n"); +#endif + if (mFindBackward) { + aSearchRange->GetStartContainer(getter_AddRefs(startNode)); + aSearchRange->GetStartOffset(&startOffset); + aEndPoint->GetStartContainer(getter_AddRefs(endNode)); + aEndPoint->GetStartOffset(&endOffset); + } else { // forward + aEndPoint->GetEndContainer(getter_AddRefs(startNode)); + aEndPoint->GetEndOffset(&startOffset); + aSearchRange->GetEndContainer(getter_AddRefs(endNode)); + aSearchRange->GetEndOffset(&endOffset); + } + } else { // Normal, not continuing + if (mFindBackward) { + aSearchRange->GetStartContainer(getter_AddRefs(startNode)); + aSearchRange->GetStartOffset(&startOffset); + aStartPoint->GetEndContainer(getter_AddRefs(endNode)); + aStartPoint->GetEndOffset(&endOffset); + // XXX Needs work: Problem with this approach: if there is a match which + // starts just before the current selection and continues into the + // selection, we will miss it, because our search algorithm only starts + // searching from the end of the word, so we would have to search the + // current selection but discount any matches that fall entirely inside + // it. + } else { // forward + aStartPoint->GetStartContainer(getter_AddRefs(startNode)); + aStartPoint->GetStartOffset(&startOffset); + aEndPoint->GetEndContainer(getter_AddRefs(endNode)); + aEndPoint->GetEndOffset(&endOffset); + } + } + + rv = InitIterator(startNode, startOffset, endNode, endOffset); + NS_ENSURE_SUCCESS(rv, rv); + if (!aStartPoint) { + aStartPoint = aSearchRange; + } + + content = do_QueryInterface(mIterator->GetCurrentNode()); +#ifdef DEBUG_FIND + nsCOMPtr<nsIDOMNode> dnode(do_QueryInterface(content)); + printf(":::::: Got the first node "); + DumpNode(dnode); +#endif + if (content && content->IsNodeOfType(nsINode::eTEXT) && + !SkipNode(content)) { + mIterNode = do_QueryInterface(content); + // Also set mIterOffset if appropriate: + nsCOMPtr<nsIDOMNode> node; + if (mFindBackward) { + aStartPoint->GetEndContainer(getter_AddRefs(node)); + if (mIterNode.get() == node.get()) { + aStartPoint->GetEndOffset(&mIterOffset); + } else { + mIterOffset = -1; // sign to start from end + } + } else { + aStartPoint->GetStartContainer(getter_AddRefs(node)); + if (mIterNode.get() == node.get()) { + aStartPoint->GetStartOffset(&mIterOffset); + } else { + mIterOffset = 0; + } + } +#ifdef DEBUG_FIND + printf("Setting initial offset to %d\n", mIterOffset); +#endif + return NS_OK; + } + } + + while (true) { + if (mFindBackward) { + mIterator->Prev(); + } else { + mIterator->Next(); + } + + content = do_QueryInterface(mIterator->GetCurrentNode()); + if (!content) { + break; + } + +#ifdef DEBUG_FIND + nsCOMPtr<nsIDOMNode> dnode(do_QueryInterface(content)); + printf(":::::: Got another node "); + DumpNode(dnode); +#endif + + // If we ever cross a block node, we might want to reset the match anchor: + // we don't match patterns extending across block boundaries. But we can't + // depend on this test here now, because the iterator doesn't give us the + // parent going in and going out, and we need it both times to depend on + // this. + //if (IsBlockNode(content)) + + // Now see if we need to skip this node -- e.g. is it part of a script or + // other invisible node? Note that we don't ask for CSS information; a node + // can be invisible due to CSS, and we'd still find it. + if (SkipNode(content)) { + continue; + } + + if (content->IsNodeOfType(nsINode::eTEXT)) { + break; + } +#ifdef DEBUG_FIND + dnode = do_QueryInterface(content); + printf("Not a text node: "); + DumpNode(dnode); +#endif + } + + if (content) { + mIterNode = do_QueryInterface(content); + } else { + mIterNode = nullptr; + } + mIterOffset = -1; + +#ifdef DEBUG_FIND + printf("Iterator gave: "); + DumpNode(mIterNode); +#endif + return NS_OK; +} + +class MOZ_STACK_CLASS PeekNextCharRestoreState final +{ +public: + explicit PeekNextCharRestoreState(nsFind* aFind) + : mIterOffset(aFind->mIterOffset), + mIterNode(aFind->mIterNode), + mCurrNode(aFind->mIterator->GetCurrentNode()), + mFind(aFind) + { + } + + ~PeekNextCharRestoreState() + { + mFind->mIterOffset = mIterOffset; + mFind->mIterNode = mIterNode; + mFind->mIterator->PositionAt(mCurrNode); + } + +private: + int32_t mIterOffset; + nsCOMPtr<nsIDOMNode> mIterNode; + nsCOMPtr<nsINode> mCurrNode; + RefPtr<nsFind> mFind; +}; + +char16_t +nsFind::PeekNextChar(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, + nsIDOMRange* aEndPoint) +{ + // We need to restore the necessary member variables before this function + // returns. + PeekNextCharRestoreState restoreState(this); + + nsCOMPtr<nsIContent> tc; + nsresult rv; + const nsTextFragment *frag; + int32_t fragLen; + + // Loop through non-block nodes until we find one that's not empty. + do { + tc = nullptr; + NextNode(aSearchRange, aStartPoint, aEndPoint, false); + + // Get the text content: + tc = do_QueryInterface(mIterNode); + + // Get the block parent. + nsCOMPtr<nsIDOMNode> blockParent; + rv = GetBlockParent(mIterNode, getter_AddRefs(blockParent)); + if (NS_FAILED(rv)) + return L'\0'; + + // If out of nodes or in new parent. + if (!mIterNode || !tc || (blockParent != mLastBlockParent)) + return L'\0'; + + frag = tc->GetText(); + fragLen = frag->GetLength(); + } while (fragLen <= 0); + + const char16_t *t2b = nullptr; + const char *t1b = nullptr; + + if (frag->Is2b()) { + t2b = frag->Get2b(); + } else { + t1b = frag->Get1b(); + } + + // Index of char to return. + int32_t index = mFindBackward ? fragLen - 1 : 0; + + return t1b ? CHAR_TO_UNICHAR(t1b[index]) : t2b[index]; +} + +bool +nsFind::IsBlockNode(nsIContent* aContent) +{ + if (aContent->IsAnyOfHTMLElements(nsGkAtoms::img, + nsGkAtoms::hr, + nsGkAtoms::th, + nsGkAtoms::td)) { + return true; + } + + return nsContentUtils::IsHTMLBlock(aContent); +} + +bool +nsFind::IsTextNode(nsIDOMNode* aNode) +{ + uint16_t nodeType; + aNode->GetNodeType(&nodeType); + + return nodeType == nsIDOMNode::TEXT_NODE || + nodeType == nsIDOMNode::CDATA_SECTION_NODE; +} + +bool +nsFind::IsVisibleNode(nsIDOMNode* aDOMNode) +{ + nsCOMPtr<nsIContent> content(do_QueryInterface(aDOMNode)); + if (!content) { + return false; + } + + nsIFrame* frame = content->GetPrimaryFrame(); + if (!frame) { + // No frame! Not visible then. + return false; + } + + return frame->StyleVisibility()->IsVisible(); +} + +bool +nsFind::SkipNode(nsIContent* aContent) +{ +#ifdef HAVE_BIDI_ITERATOR + // We may not need to skip comment nodes, now that IsTextNode distinguishes + // them from real text nodes. + return aContent->IsNodeOfType(nsINode::eCOMMENT) || + aContent->IsAnyOfHTMLElements(sScriptAtom, sNoframesAtom, sSelectAtom); + +#else /* HAVE_BIDI_ITERATOR */ + // Temporary: eventually we will have an iterator to do this, but for now, we + // have to climb up the tree for each node and see whether any parent is a + // skipped node, and take the performance hit. + + nsIContent* content = aContent; + while (content) { + if (aContent->IsNodeOfType(nsINode::eCOMMENT) || + content->IsAnyOfHTMLElements(nsGkAtoms::script, + nsGkAtoms::noframes, + nsGkAtoms::select)) { +#ifdef DEBUG_FIND + printf("Skipping node: "); + nsCOMPtr<nsIDOMNode> node(do_QueryInterface(content)); + DumpNode(node); +#endif + + return true; + } + + // Only climb to the nearest block node + if (IsBlockNode(content)) { + return false; + } + + content = content->GetParent(); + } + + return false; +#endif /* HAVE_BIDI_ITERATOR */ +} + +nsresult +nsFind::GetBlockParent(nsIDOMNode* aNode, nsIDOMNode** aParent) +{ + while (aNode) { + nsCOMPtr<nsIDOMNode> parent; + nsresult rv = aNode->GetParentNode(getter_AddRefs(parent)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIContent> content(do_QueryInterface(parent)); + if (content && IsBlockNode(content)) { + *aParent = parent; + NS_ADDREF(*aParent); + return NS_OK; + } + aNode = parent; + } + return NS_ERROR_FAILURE; +} + +// Call ResetAll before returning, to remove all references to external objects. +void +nsFind::ResetAll() +{ + mIterator = nullptr; + mLastBlockParent = nullptr; +} + +#define NBSP_CHARCODE (CHAR_TO_UNICHAR(160)) +#define IsSpace(c) (nsCRT::IsAsciiSpace(c) || (c) == NBSP_CHARCODE) +#define OVERFLOW_PINDEX (mFindBackward ? pindex < 0 : pindex > patLen) +#define DONE_WITH_PINDEX (mFindBackward ? pindex <= 0 : pindex >= patLen) +#define ALMOST_DONE_WITH_PINDEX (mFindBackward ? pindex <= 0 : pindex >= patLen - 1) + +// Take nodes out of the tree with NextNode, until null (NextNode will return 0 +// at the end of our range). +NS_IMETHODIMP +nsFind::Find(const char16_t* aPatText, nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, nsIDOMRange* aEndPoint, + nsIDOMRange** aRangeRet) +{ +#ifdef DEBUG_FIND + printf("============== nsFind::Find('%s'%s, %p, %p, %p)\n", + NS_LossyConvertUTF16toASCII(aPatText).get(), + mFindBackward ? " (backward)" : " (forward)", + (void*)aSearchRange, (void*)aStartPoint, (void*)aEndPoint); +#endif + + NS_ENSURE_ARG(aSearchRange); + NS_ENSURE_ARG(aStartPoint); + NS_ENSURE_ARG(aEndPoint); + NS_ENSURE_ARG_POINTER(aRangeRet); + *aRangeRet = 0; + + if (!aPatText) { + return NS_ERROR_NULL_POINTER; + } + + ResetAll(); + + nsAutoString patAutoStr(aPatText); + if (!mCaseSensitive) { + ToLowerCase(patAutoStr); + } + + // Ignore soft hyphens in the pattern + static const char kShy[] = { char(CH_SHY), 0 }; + patAutoStr.StripChars(kShy); + + const char16_t* patStr = patAutoStr.get(); + int32_t patLen = patAutoStr.Length() - 1; + + // current offset into the pattern -- reset to beginning/end: + int32_t pindex = (mFindBackward ? patLen : 0); + + // Current offset into the fragment + int32_t findex = 0; + + // Direction to move pindex and ptr* + int incr = (mFindBackward ? -1 : 1); + + nsCOMPtr<nsIContent> tc; + const nsTextFragment* frag = nullptr; + int32_t fragLen = 0; + + // Pointers into the current fragment: + const char16_t* t2b = nullptr; + const char* t1b = nullptr; + + // Keep track of when we're in whitespace: + // (only matters when we're matching) + bool inWhitespace = false; + // Keep track of whether the previous char was a word-breaking one. + bool wordBreakPrev = false; + + // Place to save the range start point in case we find a match: + nsCOMPtr<nsIDOMNode> matchAnchorNode; + int32_t matchAnchorOffset = 0; + + // Get the end point, so we know when to end searches: + nsCOMPtr<nsIDOMNode> endNode; + int32_t endOffset; + aEndPoint->GetEndContainer(getter_AddRefs(endNode)); + aEndPoint->GetEndOffset(&endOffset); + + char16_t c = 0; + char16_t patc = 0; + char16_t prevChar = 0; + char16_t prevCharInMatch = 0; + while (1) { +#ifdef DEBUG_FIND + printf("Loop ...\n"); +#endif + + // If this is our first time on a new node, reset the pointers: + if (!frag) { + + tc = nullptr; + NextNode(aSearchRange, aStartPoint, aEndPoint, false); + if (!mIterNode) { // Out of nodes + // Are we in the middle of a match? If so, try again with continuation. + if (matchAnchorNode) { + NextNode(aSearchRange, aStartPoint, aEndPoint, true); + } + + // Reset the iterator, so this nsFind will be usable if the user wants + // to search again (from beginning/end). + ResetAll(); + return NS_OK; + } + + // We have a new text content. If its block parent is different from the + // block parent of the last text content, then we need to clear the match + // since we don't want to find across block boundaries. + nsCOMPtr<nsIDOMNode> blockParent; + GetBlockParent(mIterNode, getter_AddRefs(blockParent)); +#ifdef DEBUG_FIND + printf("New node: old blockparent = %p, new = %p\n", + (void*)mLastBlockParent.get(), (void*)blockParent.get()); +#endif + if (blockParent != mLastBlockParent) { +#ifdef DEBUG_FIND + printf("Different block parent!\n"); +#endif + mLastBlockParent = blockParent; + // End any pending match: + matchAnchorNode = nullptr; + matchAnchorOffset = 0; + pindex = (mFindBackward ? patLen : 0); + inWhitespace = false; + } + + // Get the text content: + tc = do_QueryInterface(mIterNode); + if (!tc || !(frag = tc->GetText())) { // Out of nodes + mIterator = nullptr; + mLastBlockParent = nullptr; + ResetAll(); + return NS_OK; + } + + fragLen = frag->GetLength(); + + // Set our starting point in this node. If we're going back to the anchor + // node, which means that we just ended a partial match, use the saved + // offset: + if (mIterNode == matchAnchorNode) { + findex = matchAnchorOffset + (mFindBackward ? 1 : 0); + } + + // mIterOffset, if set, is the range's idea of an offset, and points + // between characters. But when translated to a string index, it points to + // a character. If we're going backward, this is one character too late + // and we'll match part of our previous pattern. + else if (mIterOffset >= 0) { + findex = mIterOffset - (mFindBackward ? 1 : 0); + } + + // Otherwise, just start at the appropriate end of the fragment: + else if (mFindBackward) { + findex = fragLen - 1; + } else { + findex = 0; + } + + // Offset can only apply to the first node: + mIterOffset = -1; + + // If this is outside the bounds of the string, then skip this node: + if (findex < 0 || findex > fragLen - 1) { +#ifdef DEBUG_FIND + printf("At the end of a text node -- skipping to the next\n"); +#endif + frag = 0; + continue; + } + +#ifdef DEBUG_FIND + printf("Starting from offset %d\n", findex); +#endif + if (frag->Is2b()) { + t2b = frag->Get2b(); + t1b = nullptr; +#ifdef DEBUG_FIND + nsAutoString str2(t2b, fragLen); + printf("2 byte, '%s'\n", NS_LossyConvertUTF16toASCII(str2).get()); +#endif + } else { + t1b = frag->Get1b(); + t2b = nullptr; +#ifdef DEBUG_FIND + nsAutoCString str1(t1b, fragLen); + printf("1 byte, '%s'\n", str1.get()); +#endif + } + } else { + // Still on the old node. Advance the pointers, then see if we need to + // pull a new node. + findex += incr; +#ifdef DEBUG_FIND + printf("Same node -- (%d, %d)\n", pindex, findex); +#endif + if (mFindBackward ? (findex < 0) : (findex >= fragLen)) { +#ifdef DEBUG_FIND + printf("Will need to pull a new node: mAO = %d, frag len=%d\n", + matchAnchorOffset, fragLen); +#endif + // Done with this node. Pull a new one. + frag = nullptr; + continue; + } + } + + // Have we gone past the endpoint yet? If we have, and we're not in the + // middle of a match, return. + if (mIterNode == endNode && + ((mFindBackward && findex < endOffset) || + (!mFindBackward && findex > endOffset))) { + ResetAll(); + return NS_OK; + } + + // Save the previous character for word boundary detection + prevChar = c; + // The two characters we'll be comparing: + c = (t2b ? t2b[findex] : CHAR_TO_UNICHAR(t1b[findex])); + patc = patStr[pindex]; + +#ifdef DEBUG_FIND + printf("Comparing '%c'=%x to '%c' (%d of %d), findex=%d%s\n", + (char)c, (int)c, patc, pindex, patLen, findex, + inWhitespace ? " (inWhitespace)" : ""); +#endif + + // Do we need to go back to non-whitespace mode? If inWhitespace, then this + // space in the pat str has already matched at least one space in the + // document. + if (inWhitespace && !IsSpace(c)) { + inWhitespace = false; + pindex += incr; +#ifdef DEBUG + // This shouldn't happen -- if we were still matching, and we were at the + // end of the pat string, then we should have caught it in the last + // iteration and returned success. + if (OVERFLOW_PINDEX) { + NS_ASSERTION(false, "Missed a whitespace match"); + } +#endif + patc = patStr[pindex]; + } + if (!inWhitespace && IsSpace(patc)) { + inWhitespace = true; + } else if (!inWhitespace && !mCaseSensitive && IsUpperCase(c)) { + c = ToLowerCase(c); + } + + if (c == CH_SHY) { + // ignore soft hyphens in the document + continue; + } + + if (!mCaseSensitive) { + switch (c) { + // treat curly and straight quotes as identical + case CH_LEFT_SINGLE_QUOTE: + case CH_RIGHT_SINGLE_QUOTE: + c = CH_APOSTROPHE; + break; + case CH_LEFT_DOUBLE_QUOTE: + case CH_RIGHT_DOUBLE_QUOTE: + c = CH_QUOTE; + break; + } + + switch (patc) { + // treat curly and straight quotes as identical + case CH_LEFT_SINGLE_QUOTE: + case CH_RIGHT_SINGLE_QUOTE: + patc = CH_APOSTROPHE; + break; + case CH_LEFT_DOUBLE_QUOTE: + case CH_RIGHT_DOUBLE_QUOTE: + patc = CH_QUOTE; + break; + } + } + + // a '\n' between CJ characters is ignored + if (pindex != (mFindBackward ? patLen : 0) && c != patc && !inWhitespace) { + if (c == '\n' && t2b && IS_CJ_CHAR(prevCharInMatch)) { + int32_t nindex = findex + incr; + if (mFindBackward ? (nindex >= 0) : (nindex < fragLen)) { + if (IS_CJ_CHAR(t2b[nindex])) { + continue; + } + } + } + } + + wordBreakPrev = false; + if (mWordBreaker) { + if (prevChar == NBSP_CHARCODE) + prevChar = CHAR_TO_UNICHAR(' '); + wordBreakPrev = mWordBreaker->BreakInBetween(&prevChar, 1, &c, 1); + } + + // Compare. Match if we're in whitespace and c is whitespace, or if the + // characters match and at least one of the following is true: + // a) we're not matching the entire word + // b) a match has already been stored + // c) the previous character is a different "class" than the current character. + if ((c == patc && (!mWordBreaker || matchAnchorNode || wordBreakPrev)) || + (inWhitespace && IsSpace(c))) + { + prevCharInMatch = c; +#ifdef DEBUG_FIND + if (inWhitespace) { + printf("YES (whitespace)(%d of %d)\n", pindex, patLen); + } else { + printf("YES! '%c' == '%c' (%d of %d)\n", c, patc, pindex, patLen); + } +#endif + + // Save the range anchors if we haven't already: + if (!matchAnchorNode) { + matchAnchorNode = mIterNode; + matchAnchorOffset = findex; + } + + // Are we done? + if (DONE_WITH_PINDEX) { + // Matched the whole string! +#ifdef DEBUG_FIND + printf("Found a match!\n"); +#endif + + // Make the range: + nsCOMPtr<nsIDOMNode> startParent; + nsCOMPtr<nsIDOMNode> endParent; + + // Check for word break (if necessary) + if (mWordBreaker) { + int32_t nextfindex = findex + incr; + + char16_t nextChar; + // If still in array boundaries, get nextChar. + if (mFindBackward ? (nextfindex >= 0) : (nextfindex < fragLen)) + nextChar = (t2b ? t2b[nextfindex] : CHAR_TO_UNICHAR(t1b[nextfindex])); + // Get next character from the next node. + else + nextChar = PeekNextChar(aSearchRange, aStartPoint, aEndPoint); + + if (nextChar == NBSP_CHARCODE) + nextChar = CHAR_TO_UNICHAR(' '); + + // If a word break isn't there when it needs to be, reset search. + if (!mWordBreaker->BreakInBetween(&c, 1, &nextChar, 1)) { + matchAnchorNode = nullptr; + continue; + } + } + + nsCOMPtr<nsIDOMRange> range = new nsRange(tc); + if (range) { + int32_t matchStartOffset, matchEndOffset; + // convert char index to range point: + int32_t mao = matchAnchorOffset + (mFindBackward ? 1 : 0); + if (mFindBackward) { + startParent = do_QueryInterface(tc); + endParent = matchAnchorNode; + matchStartOffset = findex; + matchEndOffset = mao; + } else { + startParent = matchAnchorNode; + endParent = do_QueryInterface(tc); + matchStartOffset = mao; + matchEndOffset = findex + 1; + } + if (startParent && endParent && + IsVisibleNode(startParent) && IsVisibleNode(endParent)) { + range->SetStart(startParent, matchStartOffset); + range->SetEnd(endParent, matchEndOffset); + *aRangeRet = range.get(); + NS_ADDREF(*aRangeRet); + } else { + // This match is no good -- invisible or bad range + startParent = nullptr; + } + } + + if (startParent) { + // If startParent == nullptr, we didn't successfully make range + // or, we didn't make a range because the start or end node were + // invisible. Reset the offset to the other end of the found string: + mIterOffset = findex + (mFindBackward ? 1 : 0); +#ifdef DEBUG_FIND + printf("mIterOffset = %d, mIterNode = ", mIterOffset); + DumpNode(mIterNode); +#endif + + ResetAll(); + return NS_OK; + } + // This match is no good, continue on in document + matchAnchorNode = nullptr; + } + + if (matchAnchorNode) { + // Not done, but still matching. Advance and loop around for the next + // characters. But don't advance from a space to a non-space: + if (!inWhitespace || DONE_WITH_PINDEX || + IsSpace(patStr[pindex + incr])) { + pindex += incr; + inWhitespace = false; +#ifdef DEBUG_FIND + printf("Advancing pindex to %d\n", pindex); +#endif + } + + continue; + } + } + +#ifdef DEBUG_FIND + printf("NOT: %c == %c\n", c, patc); +#endif + + // If we didn't match, go back to the beginning of patStr, and set findex + // back to the next char after we started the current match. + if (matchAnchorNode) { // we're ending a partial match + findex = matchAnchorOffset; + mIterOffset = matchAnchorOffset; + // +incr will be added to findex when we continue + + // Are we going back to a previous node? + if (matchAnchorNode != mIterNode) { + nsCOMPtr<nsIContent> content(do_QueryInterface(matchAnchorNode)); + DebugOnly<nsresult> rv = NS_ERROR_UNEXPECTED; + if (content) { + rv = mIterator->PositionAt(content); + } + frag = 0; + NS_ASSERTION(NS_SUCCEEDED(rv), "Text content wasn't nsIContent!"); +#ifdef DEBUG_FIND + printf("Repositioned anchor node\n"); +#endif + } +#ifdef DEBUG_FIND + printf("Ending a partial match; findex -> %d, mIterOffset -> %d\n", + findex, mIterOffset); +#endif + } + matchAnchorNode = nullptr; + matchAnchorOffset = 0; + inWhitespace = false; + pindex = (mFindBackward ? patLen : 0); +#ifdef DEBUG_FIND + printf("Setting findex back to %d, pindex to %d\n", findex, pindex); + +#endif + } + + // Out of nodes, and didn't match. + ResetAll(); + return NS_OK; +} diff --git a/embedding/components/find/nsFind.h b/embedding/components/find/nsFind.h new file mode 100644 index 000000000..7f588f187 --- /dev/null +++ b/embedding/components/find/nsFind.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsFind_h__ +#define nsFind_h__ + +#include "nsIFind.h" + +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIDOMNode.h" +#include "nsIDOMRange.h" +#include "nsIContentIterator.h" +#include "nsIWordBreaker.h" + +class nsIContent; + +#define NS_FIND_CONTRACTID "@mozilla.org/embedcomp/rangefind;1" + +#define NS_FIND_CID \ + {0x471f4944, 0x1dd2, 0x11b2, {0x87, 0xac, 0x90, 0xbe, 0x0a, 0x51, 0xd6, 0x09}} + +class nsFindContentIterator; + +class nsFind : public nsIFind +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIFIND + NS_DECL_CYCLE_COLLECTION_CLASS(nsFind) + + nsFind(); + +protected: + virtual ~nsFind(); + + // Parameters set from the interface: + //nsCOMPtr<nsIDOMRange> mRange; // search only in this range + bool mFindBackward; + bool mCaseSensitive; + + // Use "find entire words" mode by setting to a word breaker or null, to + // disable "entire words" mode. + nsCOMPtr<nsIWordBreaker> mWordBreaker; + + int32_t mIterOffset; + nsCOMPtr<nsIDOMNode> mIterNode; + + // Last block parent, so that we will notice crossing block boundaries: + nsCOMPtr<nsIDOMNode> mLastBlockParent; + nsresult GetBlockParent(nsIDOMNode* aNode, nsIDOMNode** aParent); + + // Utility routines: + bool IsTextNode(nsIDOMNode* aNode); + bool IsBlockNode(nsIContent* aNode); + bool SkipNode(nsIContent* aNode); + bool IsVisibleNode(nsIDOMNode* aNode); + + // Move in the right direction for our search: + nsresult NextNode(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, nsIDOMRange* aEndPoint, + bool aContinueOk); + + // Get the first character from the next node (last if mFindBackward). + char16_t PeekNextChar(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, + nsIDOMRange* aEndPoint); + + // Reset variables before returning -- don't hold any references. + void ResetAll(); + + // The iterator we use to move through the document: + nsresult InitIterator(nsIDOMNode* aStartNode, int32_t aStartOffset, + nsIDOMNode* aEndNode, int32_t aEndOffset); + RefPtr<nsFindContentIterator> mIterator; + + friend class PeekNextCharRestoreState; +}; + +#endif // nsFind_h__ diff --git a/embedding/components/find/nsIFind.idl b/embedding/components/find/nsIFind.idl new file mode 100644 index 000000000..2c9b17703 --- /dev/null +++ b/embedding/components/find/nsIFind.idl @@ -0,0 +1,34 @@ +/* -*- 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 nsIDOMRange; +interface nsIWordBreaker; + +[scriptable, uuid(40aba110-2a56-4678-be90-e2c17a9ae7d7)] +interface nsIFind : nsISupports +{ + attribute boolean findBackwards; + attribute boolean caseSensitive; + attribute boolean entireWord; + + /** + * Find some text in the current context. The implementation is + * responsible for performing the find and highlighting the text. + * + * @param aPatText The text to search for. + * @param aSearchRange A Range specifying domain of search. + * @param aStartPoint A Range specifying search start point. + * If not collapsed, we'll start from + * end (forward) or start (backward). + * @param aEndPoint A Range specifying search end point. + * If not collapsed, we'll end at + * end (forward) or start (backward). + * @retval A range spanning the match that was found (or null). + */ + nsIDOMRange Find(in wstring aPatText, in nsIDOMRange aSearchRange, + in nsIDOMRange aStartPoint, in nsIDOMRange aEndPoint); +}; diff --git a/embedding/components/find/nsIWebBrowserFind.idl b/embedding/components/find/nsIWebBrowserFind.idl new file mode 100644 index 000000000..a00763f78 --- /dev/null +++ b/embedding/components/find/nsIWebBrowserFind.idl @@ -0,0 +1,145 @@ +/* -*- 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 "domstubs.idl" + +interface mozIDOMWindowProxy; + +/* THIS IS A PUBLIC EMBEDDING API */ + + +/** + * nsIWebBrowserFind + * + * Searches for text in a web browser. + * + * Get one by doing a GetInterface on an nsIWebBrowser. + * + * By default, the implementation will search the focussed frame, or + * if there is no focussed frame, the web browser content area. It + * does not by default search subframes or iframes. To change this + * behaviour, and to explicitly set the frame to search, + * QueryInterface to nsIWebBrowserFindInFrames. + */ + +[scriptable, uuid(e4920136-b3e0-49e0-b1cd-6c783d2591a8)] +interface nsIWebBrowserFind : nsISupports +{ + /** + * findNext + * + * Finds, highlights, and scrolls into view the next occurrence of the + * search string, using the current search settings. Fails if the + * search string is empty. + * + * @return Whether an occurrence was found + */ + boolean findNext(); + + /** + * searchString + * + * The string to search for. This must be non-empty to search. + */ + attribute wstring searchString; + + /** + * findBackwards + * + * Whether to find backwards (towards the beginning of the document). + * Default is false (search forward). + */ + attribute boolean findBackwards; + + /** + * wrapFind + * + * Whether the search wraps around to the start (or end) of the document + * if no match was found between the current position and the end (or + * beginning). Works correctly when searching backwards. Default is + * false. + */ + attribute boolean wrapFind; + + /** + * entireWord + * + * Whether to match entire words only. Default is false. + */ + attribute boolean entireWord; + + /** + * matchCase + * + * Whether to match case (case sensitive) when searching. Default is false. + */ + attribute boolean matchCase; + + /** + * searchFrames + * + * Whether to search through all frames in the content area. Default is true. + * + * Note that you can control whether the search propagates into child or + * parent frames explicitly using nsIWebBrowserFindInFrames, but if one, + * but not both, of searchSubframes and searchParentFrames are set, this + * returns false. + */ + attribute boolean searchFrames; +}; + + + +/** + * nsIWebBrowserFindInFrames + * + * Controls how find behaves when multiple frames or iframes are present. + * + * Get by doing a QueryInterface from nsIWebBrowserFind. + */ + +[scriptable, uuid(e0f5d182-34bc-11d5-be5b-b760676c6ebc)] +interface nsIWebBrowserFindInFrames : nsISupports +{ + /** + * currentSearchFrame + * + * Frame at which to start the search. Once the search is done, this will + * be set to be the last frame searched, whether or not a result was found. + * Has to be equal to or contained within the rootSearchFrame. + */ + attribute mozIDOMWindowProxy currentSearchFrame; + + /** + * rootSearchFrame + * + * Frame within which to confine the search (normally the content area frame). + * Set this to only search a subtree of the frame hierarchy. + */ + attribute mozIDOMWindowProxy rootSearchFrame; + + /** + * searchSubframes + * + * Whether to recurse down into subframes while searching. Default is true. + * + * Setting nsIWebBrowserfind.searchFrames to true sets this to true. + */ + attribute boolean searchSubframes; + + /** + * searchParentFrames + * + * Whether to allow the search to propagate out of the currentSearchFrame into its + * parent frame(s). Search is always confined within the rootSearchFrame. Default + * is true. + * + * Setting nsIWebBrowserfind.searchFrames to true sets this to true. + */ + attribute boolean searchParentFrames; + +}; diff --git a/embedding/components/find/nsWebBrowserFind.cpp b/embedding/components/find/nsWebBrowserFind.cpp new file mode 100644 index 000000000..af44ce59b --- /dev/null +++ b/embedding/components/find/nsWebBrowserFind.cpp @@ -0,0 +1,868 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsWebBrowserFind.h" + +// Only need this for NS_FIND_CONTRACTID, +// else we could use nsIDOMRange.h and nsIFind.h. +#include "nsFind.h" + +#include "nsIComponentManager.h" +#include "nsIScriptSecurityManager.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsPIDOMWindow.h" +#include "nsIURI.h" +#include "nsIDocShell.h" +#include "nsIPresShell.h" +#include "nsPresContext.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsISelectionController.h" +#include "nsISelection.h" +#include "nsIFrame.h" +#include "nsITextControlFrame.h" +#include "nsReadableUtils.h" +#include "nsIDOMHTMLElement.h" +#include "nsIDOMHTMLDocument.h" +#include "nsIContent.h" +#include "nsContentCID.h" +#include "nsIServiceManager.h" +#include "nsIObserverService.h" +#include "nsISupportsPrimitives.h" +#include "nsFind.h" +#include "nsError.h" +#include "nsFocusManager.h" +#include "mozilla/Services.h" +#include "mozilla/dom/Element.h" +#include "nsISimpleEnumerator.h" +#include "nsContentUtils.h" + +#if DEBUG +#include "nsIWebNavigation.h" +#include "nsXPIDLString.h" +#endif + +nsWebBrowserFind::nsWebBrowserFind() + : mFindBackwards(false) + , mWrapFind(false) + , mEntireWord(false) + , mMatchCase(false) + , mSearchSubFrames(true) + , mSearchParentFrames(true) +{ +} + +nsWebBrowserFind::~nsWebBrowserFind() +{ +} + +NS_IMPL_ISUPPORTS(nsWebBrowserFind, nsIWebBrowserFind, + nsIWebBrowserFindInFrames) + +NS_IMETHODIMP +nsWebBrowserFind::FindNext(bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + + NS_ENSURE_TRUE(CanFindNext(), NS_ERROR_NOT_INITIALIZED); + + nsresult rv = NS_OK; + nsCOMPtr<nsPIDOMWindowOuter> searchFrame = do_QueryReferent(mCurrentSearchFrame); + NS_ENSURE_TRUE(searchFrame, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsPIDOMWindowOuter> rootFrame = do_QueryReferent(mRootSearchFrame); + NS_ENSURE_TRUE(rootFrame, NS_ERROR_NOT_INITIALIZED); + + // first, if there's a "cmd_findagain" observer around, check to see if it + // wants to perform the find again command . If it performs the find again + // it will return true, in which case we exit ::FindNext() early. + // Otherwise, nsWebBrowserFind needs to perform the find again command itself + // this is used by nsTypeAheadFind, which controls find again when it was + // the last executed find in the current window. + nsCOMPtr<nsIObserverService> observerSvc = + mozilla::services::GetObserverService(); + if (observerSvc) { + nsCOMPtr<nsISupportsInterfacePointer> windowSupportsData = + do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsISupports> searchWindowSupports = do_QueryInterface(rootFrame); + windowSupportsData->SetData(searchWindowSupports); + NS_NAMED_LITERAL_STRING(dnStr, "down"); + NS_NAMED_LITERAL_STRING(upStr, "up"); + observerSvc->NotifyObservers(windowSupportsData, + "nsWebBrowserFind_FindAgain", + mFindBackwards ? upStr.get() : dnStr.get()); + windowSupportsData->GetData(getter_AddRefs(searchWindowSupports)); + // findnext performed if search window data cleared out + *aResult = searchWindowSupports == nullptr; + if (*aResult) { + return NS_OK; + } + } + + // next, look in the current frame. If found, return. + + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + rv = SearchInFrame(searchFrame, false, aResult); + if (NS_FAILED(rv)) { + return rv; + } + if (*aResult) { + return OnFind(searchFrame); // we are done + } + + // if we are not searching other frames, return + if (!mSearchSubFrames && !mSearchParentFrames) { + return NS_OK; + } + + nsIDocShell* rootDocShell = rootFrame->GetDocShell(); + if (!rootDocShell) { + return NS_ERROR_FAILURE; + } + + int32_t enumDirection = mFindBackwards ? nsIDocShell::ENUMERATE_BACKWARDS : + nsIDocShell::ENUMERATE_FORWARDS; + + nsCOMPtr<nsISimpleEnumerator> docShellEnumerator; + rv = rootDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeAll, + enumDirection, + getter_AddRefs(docShellEnumerator)); + if (NS_FAILED(rv)) { + return rv; + } + + // remember where we started + nsCOMPtr<nsIDocShellTreeItem> startingItem = + do_QueryInterface(searchFrame->GetDocShell(), &rv); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIDocShellTreeItem> curItem; + + // XXX We should avoid searching in frameset documents here. + // We also need to honour mSearchSubFrames and mSearchParentFrames. + bool hasMore, doFind = false; + while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMore)) && + hasMore) { + nsCOMPtr<nsISupports> curSupports; + rv = docShellEnumerator->GetNext(getter_AddRefs(curSupports)); + if (NS_FAILED(rv)) { + break; + } + curItem = do_QueryInterface(curSupports, &rv); + if (NS_FAILED(rv)) { + break; + } + + if (doFind) { + searchFrame = curItem->GetWindow(); + if (!searchFrame) { + break; + } + + OnStartSearchFrame(searchFrame); + + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + rv = SearchInFrame(searchFrame, false, aResult); + if (NS_FAILED(rv)) { + return rv; + } + if (*aResult) { + return OnFind(searchFrame); // we are done + } + + OnEndSearchFrame(searchFrame); + } + + if (curItem.get() == startingItem.get()) { + doFind = true; // start looking in frames after this one + } + } + + if (!mWrapFind) { + // remember where we left off + SetCurrentSearchFrame(searchFrame); + return NS_OK; + } + + // From here on, we're wrapping, first through the other frames, then finally + // from the beginning of the starting frame back to the starting point. + + // because nsISimpleEnumerator is totally lame and isn't resettable, I have to + // make a new one + docShellEnumerator = nullptr; + rv = rootDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeAll, + enumDirection, + getter_AddRefs(docShellEnumerator)); + if (NS_FAILED(rv)) { + return rv; + } + + while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMore)) && + hasMore) { + nsCOMPtr<nsISupports> curSupports; + rv = docShellEnumerator->GetNext(getter_AddRefs(curSupports)); + if (NS_FAILED(rv)) { + break; + } + curItem = do_QueryInterface(curSupports, &rv); + if (NS_FAILED(rv)) { + break; + } + + searchFrame = curItem->GetWindow(); + if (!searchFrame) { + rv = NS_ERROR_FAILURE; + break; + } + + if (curItem.get() == startingItem.get()) { + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + rv = SearchInFrame(searchFrame, true, aResult); + if (NS_FAILED(rv)) { + return rv; + } + if (*aResult) { + return OnFind(searchFrame); // we are done + } + break; + } + + OnStartSearchFrame(searchFrame); + + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + rv = SearchInFrame(searchFrame, false, aResult); + if (NS_FAILED(rv)) { + return rv; + } + if (*aResult) { + return OnFind(searchFrame); // we are done + } + + OnEndSearchFrame(searchFrame); + } + + // remember where we left off + SetCurrentSearchFrame(searchFrame); + + NS_ASSERTION(NS_SUCCEEDED(rv), "Something failed"); + return rv; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetSearchString(char16_t** aSearchString) +{ + NS_ENSURE_ARG_POINTER(aSearchString); + *aSearchString = ToNewUnicode(mSearchString); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetSearchString(const char16_t* aSearchString) +{ + mSearchString.Assign(aSearchString); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetFindBackwards(bool* aFindBackwards) +{ + NS_ENSURE_ARG_POINTER(aFindBackwards); + *aFindBackwards = mFindBackwards; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetFindBackwards(bool aFindBackwards) +{ + mFindBackwards = aFindBackwards; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetWrapFind(bool* aWrapFind) +{ + NS_ENSURE_ARG_POINTER(aWrapFind); + *aWrapFind = mWrapFind; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetWrapFind(bool aWrapFind) +{ + mWrapFind = aWrapFind; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetEntireWord(bool* aEntireWord) +{ + NS_ENSURE_ARG_POINTER(aEntireWord); + *aEntireWord = mEntireWord; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetEntireWord(bool aEntireWord) +{ + mEntireWord = aEntireWord; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetMatchCase(bool* aMatchCase) +{ + NS_ENSURE_ARG_POINTER(aMatchCase); + *aMatchCase = mMatchCase; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetMatchCase(bool aMatchCase) +{ + mMatchCase = aMatchCase; + return NS_OK; +} + +static bool +IsInNativeAnonymousSubtree(nsIContent* aContent) +{ + while (aContent) { + nsIContent* bindingParent = aContent->GetBindingParent(); + if (bindingParent == aContent) { + return true; + } + + aContent = bindingParent; + } + + return false; +} + +void +nsWebBrowserFind::SetSelectionAndScroll(nsPIDOMWindowOuter* aWindow, + nsIDOMRange* aRange) +{ + nsCOMPtr<nsIDocument> doc = aWindow->GetDoc(); + if (!doc) { + return; + } + + nsIPresShell* presShell = doc->GetShell(); + if (!presShell) { + return; + } + + nsCOMPtr<nsIDOMNode> node; + aRange->GetStartContainer(getter_AddRefs(node)); + nsCOMPtr<nsIContent> content(do_QueryInterface(node)); + nsIFrame* frame = content->GetPrimaryFrame(); + if (!frame) { + return; + } + nsCOMPtr<nsISelectionController> selCon; + frame->GetSelectionController(presShell->GetPresContext(), + getter_AddRefs(selCon)); + + // since the match could be an anonymous textnode inside a + // <textarea> or text <input>, we need to get the outer frame + nsITextControlFrame* tcFrame = nullptr; + for (; content; content = content->GetParent()) { + if (!IsInNativeAnonymousSubtree(content)) { + nsIFrame* f = content->GetPrimaryFrame(); + if (!f) { + return; + } + tcFrame = do_QueryFrame(f); + break; + } + } + + nsCOMPtr<nsISelection> selection; + + selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); + selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(selection)); + if (selection) { + selection->RemoveAllRanges(); + selection->AddRange(aRange); + + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (fm) { + if (tcFrame) { + nsCOMPtr<nsIDOMElement> newFocusedElement(do_QueryInterface(content)); + fm->SetFocus(newFocusedElement, nsIFocusManager::FLAG_NOSCROLL); + } else { + nsCOMPtr<nsIDOMElement> result; + fm->MoveFocus(aWindow, nullptr, nsIFocusManager::MOVEFOCUS_CARET, + nsIFocusManager::FLAG_NOSCROLL, getter_AddRefs(result)); + } + } + + // Scroll if necessary to make the selection visible: + // Must be the last thing to do - bug 242056 + + // After ScrollSelectionIntoView(), the pending notifications might be + // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. + selCon->ScrollSelectionIntoView( + nsISelectionController::SELECTION_NORMAL, + nsISelectionController::SELECTION_WHOLE_SELECTION, + nsISelectionController::SCROLL_CENTER_VERTICALLY | + nsISelectionController::SCROLL_SYNCHRONOUS); + } +} + +// Adapted from nsTextServicesDocument::GetDocumentContentRootNode +nsresult +nsWebBrowserFind::GetRootNode(nsIDOMDocument* aDomDoc, nsIDOMNode** aNode) +{ + nsresult rv; + + NS_ENSURE_ARG_POINTER(aNode); + *aNode = 0; + + nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(aDomDoc); + if (htmlDoc) { + // For HTML documents, the content root node is the body. + nsCOMPtr<nsIDOMHTMLElement> bodyElement; + rv = htmlDoc->GetBody(getter_AddRefs(bodyElement)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_ARG_POINTER(bodyElement); + bodyElement.forget(aNode); + return NS_OK; + } + + // For non-HTML documents, the content root node will be the doc element. + nsCOMPtr<nsIDOMElement> docElement; + rv = aDomDoc->GetDocumentElement(getter_AddRefs(docElement)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_ARG_POINTER(docElement); + docElement.forget(aNode); + return NS_OK; +} + +nsresult +nsWebBrowserFind::SetRangeAroundDocument(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPt, + nsIDOMRange* aEndPt, + nsIDOMDocument* aDoc) +{ + nsCOMPtr<nsIDOMNode> bodyNode; + nsresult rv = GetRootNode(aDoc, getter_AddRefs(bodyNode)); + nsCOMPtr<nsIContent> bodyContent(do_QueryInterface(bodyNode)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_ARG_POINTER(bodyContent); + + uint32_t childCount = bodyContent->GetChildCount(); + + aSearchRange->SetStart(bodyNode, 0); + aSearchRange->SetEnd(bodyNode, childCount); + + if (mFindBackwards) { + aStartPt->SetStart(bodyNode, childCount); + aStartPt->SetEnd(bodyNode, childCount); + aEndPt->SetStart(bodyNode, 0); + aEndPt->SetEnd(bodyNode, 0); + } else { + aStartPt->SetStart(bodyNode, 0); + aStartPt->SetEnd(bodyNode, 0); + aEndPt->SetStart(bodyNode, childCount); + aEndPt->SetEnd(bodyNode, childCount); + } + + return NS_OK; +} + +// Set the range to go from the end of the current selection to the end of the +// document (forward), or beginning to beginning (reverse). or around the whole +// document if there's no selection. +nsresult +nsWebBrowserFind::GetSearchLimits(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPt, nsIDOMRange* aEndPt, + nsIDOMDocument* aDoc, nsISelection* aSel, + bool aWrap) +{ + NS_ENSURE_ARG_POINTER(aSel); + + // There is a selection. + int32_t count = -1; + nsresult rv = aSel->GetRangeCount(&count); + NS_ENSURE_SUCCESS(rv, rv); + if (count < 1) { + return SetRangeAroundDocument(aSearchRange, aStartPt, aEndPt, aDoc); + } + + // Need bodyNode, for the start/end of the document + nsCOMPtr<nsIDOMNode> bodyNode; + rv = GetRootNode(aDoc, getter_AddRefs(bodyNode)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIContent> bodyContent(do_QueryInterface(bodyNode)); + NS_ENSURE_ARG_POINTER(bodyContent); + + uint32_t childCount = bodyContent->GetChildCount(); + + // There are four possible range endpoints we might use: + // DocumentStart, SelectionStart, SelectionEnd, DocumentEnd. + + nsCOMPtr<nsIDOMRange> range; + nsCOMPtr<nsIDOMNode> node; + int32_t offset; + + // Forward, not wrapping: SelEnd to DocEnd + if (!mFindBackwards && !aWrap) { + // This isn't quite right, since the selection's ranges aren't + // necessarily in order; but they usually will be. + aSel->GetRangeAt(count - 1, getter_AddRefs(range)); + if (!range) { + return NS_ERROR_UNEXPECTED; + } + range->GetEndContainer(getter_AddRefs(node)); + if (!node) { + return NS_ERROR_UNEXPECTED; + } + range->GetEndOffset(&offset); + + aSearchRange->SetStart(node, offset); + aSearchRange->SetEnd(bodyNode, childCount); + aStartPt->SetStart(node, offset); + aStartPt->SetEnd(node, offset); + aEndPt->SetStart(bodyNode, childCount); + aEndPt->SetEnd(bodyNode, childCount); + } + // Backward, not wrapping: DocStart to SelStart + else if (mFindBackwards && !aWrap) { + aSel->GetRangeAt(0, getter_AddRefs(range)); + if (!range) { + return NS_ERROR_UNEXPECTED; + } + range->GetStartContainer(getter_AddRefs(node)); + if (!node) { + return NS_ERROR_UNEXPECTED; + } + range->GetStartOffset(&offset); + + aSearchRange->SetStart(bodyNode, 0); + aSearchRange->SetEnd(bodyNode, childCount); + aStartPt->SetStart(node, offset); + aStartPt->SetEnd(node, offset); + aEndPt->SetStart(bodyNode, 0); + aEndPt->SetEnd(bodyNode, 0); + } + // Forward, wrapping: DocStart to SelEnd + else if (!mFindBackwards && aWrap) { + aSel->GetRangeAt(count - 1, getter_AddRefs(range)); + if (!range) { + return NS_ERROR_UNEXPECTED; + } + range->GetEndContainer(getter_AddRefs(node)); + if (!node) { + return NS_ERROR_UNEXPECTED; + } + range->GetEndOffset(&offset); + + aSearchRange->SetStart(bodyNode, 0); + aSearchRange->SetEnd(bodyNode, childCount); + aStartPt->SetStart(bodyNode, 0); + aStartPt->SetEnd(bodyNode, 0); + aEndPt->SetStart(node, offset); + aEndPt->SetEnd(node, offset); + } + // Backward, wrapping: SelStart to DocEnd + else if (mFindBackwards && aWrap) { + aSel->GetRangeAt(0, getter_AddRefs(range)); + if (!range) { + return NS_ERROR_UNEXPECTED; + } + range->GetStartContainer(getter_AddRefs(node)); + if (!node) { + return NS_ERROR_UNEXPECTED; + } + range->GetStartOffset(&offset); + + aSearchRange->SetStart(bodyNode, 0); + aSearchRange->SetEnd(bodyNode, childCount); + aStartPt->SetStart(bodyNode, childCount); + aStartPt->SetEnd(bodyNode, childCount); + aEndPt->SetStart(node, offset); + aEndPt->SetEnd(node, offset); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetSearchFrames(bool* aSearchFrames) +{ + NS_ENSURE_ARG_POINTER(aSearchFrames); + // this only returns true if we are searching both sub and parent frames. + // There is ambiguity if the caller has previously set one, but not both of + // these. + *aSearchFrames = mSearchSubFrames && mSearchParentFrames; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetSearchFrames(bool aSearchFrames) +{ + mSearchSubFrames = aSearchFrames; + mSearchParentFrames = aSearchFrames; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetCurrentSearchFrame(mozIDOMWindowProxy** aCurrentSearchFrame) +{ + NS_ENSURE_ARG_POINTER(aCurrentSearchFrame); + nsCOMPtr<mozIDOMWindowProxy> searchFrame = do_QueryReferent(mCurrentSearchFrame); + searchFrame.forget(aCurrentSearchFrame); + return (*aCurrentSearchFrame) ? NS_OK : NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetCurrentSearchFrame(mozIDOMWindowProxy* aCurrentSearchFrame) +{ + // is it ever valid to set this to null? + NS_ENSURE_ARG(aCurrentSearchFrame); + mCurrentSearchFrame = do_GetWeakReference(aCurrentSearchFrame); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetRootSearchFrame(mozIDOMWindowProxy** aRootSearchFrame) +{ + NS_ENSURE_ARG_POINTER(aRootSearchFrame); + nsCOMPtr<mozIDOMWindowProxy> searchFrame = do_QueryReferent(mRootSearchFrame); + searchFrame.forget(aRootSearchFrame); + return (*aRootSearchFrame) ? NS_OK : NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetRootSearchFrame(mozIDOMWindowProxy* aRootSearchFrame) +{ + // is it ever valid to set this to null? + NS_ENSURE_ARG(aRootSearchFrame); + mRootSearchFrame = do_GetWeakReference(aRootSearchFrame); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetSearchSubframes(bool* aSearchSubframes) +{ + NS_ENSURE_ARG_POINTER(aSearchSubframes); + *aSearchSubframes = mSearchSubFrames; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetSearchSubframes(bool aSearchSubframes) +{ + mSearchSubFrames = aSearchSubframes; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetSearchParentFrames(bool* aSearchParentFrames) +{ + NS_ENSURE_ARG_POINTER(aSearchParentFrames); + *aSearchParentFrames = mSearchParentFrames; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetSearchParentFrames(bool aSearchParentFrames) +{ + mSearchParentFrames = aSearchParentFrames; + return NS_OK; +} + +/* + This method handles finding in a single window (aka frame). + +*/ +nsresult +nsWebBrowserFind::SearchInFrame(nsPIDOMWindowOuter* aWindow, bool aWrapping, + bool* aDidFind) +{ + NS_ENSURE_ARG(aWindow); + NS_ENSURE_ARG_POINTER(aDidFind); + + *aDidFind = false; + + // Do security check, to ensure that the frame we're searching is + // acccessible from the frame where the Find is being run. + + // get a uri for the window + nsCOMPtr<nsIDocument> theDoc = aWindow->GetDoc(); + if (!theDoc) { + return NS_ERROR_FAILURE; + } + + if (!nsContentUtils::SubjectPrincipal()->Subsumes(theDoc->NodePrincipal())) { + return NS_ERROR_DOM_PROP_ACCESS_DENIED; + } + + nsresult rv; + nsCOMPtr<nsIFind> find = do_CreateInstance(NS_FIND_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + (void)find->SetCaseSensitive(mMatchCase); + (void)find->SetFindBackwards(mFindBackwards); + + (void)find->SetEntireWord(mEntireWord); + + // Now make sure the content (for actual finding) and frame (for + // selection) models are up to date. + theDoc->FlushPendingNotifications(Flush_Frames); + + nsCOMPtr<nsISelection> sel = GetFrameSelection(aWindow); + NS_ENSURE_ARG_POINTER(sel); + + nsCOMPtr<nsIDOMRange> searchRange = new nsRange(theDoc); + NS_ENSURE_ARG_POINTER(searchRange); + nsCOMPtr<nsIDOMRange> startPt = new nsRange(theDoc); + NS_ENSURE_ARG_POINTER(startPt); + nsCOMPtr<nsIDOMRange> endPt = new nsRange(theDoc); + NS_ENSURE_ARG_POINTER(endPt); + + nsCOMPtr<nsIDOMRange> foundRange; + + nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(theDoc); + MOZ_ASSERT(domDoc); + + // If !aWrapping, search from selection to end + if (!aWrapping) + rv = GetSearchLimits(searchRange, startPt, endPt, domDoc, sel, false); + + // If aWrapping, search the part of the starting frame + // up to the point where we left off. + else + rv = GetSearchLimits(searchRange, startPt, endPt, domDoc, sel, true); + + NS_ENSURE_SUCCESS(rv, rv); + + rv = find->Find(mSearchString.get(), searchRange, startPt, endPt, + getter_AddRefs(foundRange)); + + if (NS_SUCCEEDED(rv) && foundRange) { + *aDidFind = true; + sel->RemoveAllRanges(); + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + SetSelectionAndScroll(aWindow, foundRange); + } + + return rv; +} + +// called when we start searching a frame that is not the initial focussed +// frame. Prepare the frame to be searched. we clear the selection, so that the +// search starts from the top of the frame. +nsresult +nsWebBrowserFind::OnStartSearchFrame(nsPIDOMWindowOuter* aWindow) +{ + return ClearFrameSelection(aWindow); +} + +// called when we are done searching a frame and didn't find anything, and about +// about to start searching the next frame. +nsresult +nsWebBrowserFind::OnEndSearchFrame(nsPIDOMWindowOuter* aWindow) +{ + return NS_OK; +} + +already_AddRefed<nsISelection> +nsWebBrowserFind::GetFrameSelection(nsPIDOMWindowOuter* aWindow) +{ + nsCOMPtr<nsIDocument> doc = aWindow->GetDoc(); + if (!doc) { + return nullptr; + } + + nsIPresShell* presShell = doc->GetShell(); + if (!presShell) { + return nullptr; + } + + // text input controls have their independent selection controllers that we + // must use when they have focus. + nsPresContext* presContext = presShell->GetPresContext(); + + nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; + nsCOMPtr<nsIContent> focusedContent = nsFocusManager::GetFocusedDescendant( + aWindow, false, getter_AddRefs(focusedWindow)); + + nsIFrame* frame = + focusedContent ? focusedContent->GetPrimaryFrame() : nullptr; + + nsCOMPtr<nsISelectionController> selCon; + nsCOMPtr<nsISelection> sel; + if (frame) { + frame->GetSelectionController(presContext, getter_AddRefs(selCon)); + selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(sel)); + if (sel) { + int32_t count = -1; + sel->GetRangeCount(&count); + if (count > 0) { + return sel.forget(); + } + } + } + + selCon = do_QueryInterface(presShell); + selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(sel)); + return sel.forget(); +} + +nsresult +nsWebBrowserFind::ClearFrameSelection(nsPIDOMWindowOuter* aWindow) +{ + NS_ENSURE_ARG(aWindow); + nsCOMPtr<nsISelection> selection = GetFrameSelection(aWindow); + if (selection) { + selection->RemoveAllRanges(); + } + + return NS_OK; +} + +nsresult +nsWebBrowserFind::OnFind(nsPIDOMWindowOuter* aFoundWindow) +{ + SetCurrentSearchFrame(aFoundWindow); + + // We don't want a selection to appear in two frames simultaneously + nsCOMPtr<nsPIDOMWindowOuter> lastFocusedWindow = + do_QueryReferent(mLastFocusedWindow); + if (lastFocusedWindow && lastFocusedWindow != aFoundWindow) { + ClearFrameSelection(lastFocusedWindow); + } + + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (fm) { + // get the containing frame and focus it. For top-level windows, the right + // window should already be focused. + nsCOMPtr<nsIDOMElement> frameElement = + do_QueryInterface(aFoundWindow->GetFrameElementInternal()); + if (frameElement) { + fm->SetFocus(frameElement, 0); + } + + mLastFocusedWindow = do_GetWeakReference(aFoundWindow); + } + + return NS_OK; +} diff --git a/embedding/components/find/nsWebBrowserFind.h b/embedding/components/find/nsWebBrowserFind.h new file mode 100644 index 000000000..eaf94ae97 --- /dev/null +++ b/embedding/components/find/nsWebBrowserFind.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsWebBrowserFindImpl_h__ +#define nsWebBrowserFindImpl_h__ + +#include "nsIWebBrowserFind.h" + +#include "nsCOMPtr.h" +#include "nsWeakReference.h" + +#include "nsIFind.h" + +#include "nsString.h" + +#define NS_WEB_BROWSER_FIND_CONTRACTID "@mozilla.org/embedcomp/find;1" + +#define NS_WEB_BROWSER_FIND_CID \ + {0x57cf9383, 0x3405, 0x11d5, {0xbe, 0x5b, 0xaa, 0x20, 0xfa, 0x2c, 0xf3, 0x7c}} + +class nsISelection; +class nsIDOMWindow; + +class nsIDocShell; + +//***************************************************************************** +// class nsWebBrowserFind +//***************************************************************************** + +class nsWebBrowserFind + : public nsIWebBrowserFind + , public nsIWebBrowserFindInFrames +{ +public: + nsWebBrowserFind(); + + // nsISupports + NS_DECL_ISUPPORTS + + // nsIWebBrowserFind + NS_DECL_NSIWEBBROWSERFIND + + // nsIWebBrowserFindInFrames + NS_DECL_NSIWEBBROWSERFINDINFRAMES + +protected: + virtual ~nsWebBrowserFind(); + + bool CanFindNext() { return mSearchString.Length() != 0; } + + nsresult SearchInFrame(nsPIDOMWindowOuter* aWindow, bool aWrapping, + bool* aDidFind); + + nsresult OnStartSearchFrame(nsPIDOMWindowOuter* aWindow); + nsresult OnEndSearchFrame(nsPIDOMWindowOuter* aWindow); + + already_AddRefed<nsISelection> GetFrameSelection(nsPIDOMWindowOuter* aWindow); + nsresult ClearFrameSelection(nsPIDOMWindowOuter* aWindow); + + nsresult OnFind(nsPIDOMWindowOuter* aFoundWindow); + + void SetSelectionAndScroll(nsPIDOMWindowOuter* aWindow, nsIDOMRange* aRange); + + nsresult GetRootNode(nsIDOMDocument* aDomDoc, nsIDOMNode** aNode); + nsresult GetSearchLimits(nsIDOMRange* aRange, + nsIDOMRange* aStartPt, nsIDOMRange* aEndPt, + nsIDOMDocument* aDoc, nsISelection* aSel, + bool aWrap); + nsresult SetRangeAroundDocument(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, + nsIDOMRange* aEndPoint, + nsIDOMDocument* aDoc); + +protected: + nsString mSearchString; + + bool mFindBackwards; + bool mWrapFind; + bool mEntireWord; + bool mMatchCase; + + bool mSearchSubFrames; + bool mSearchParentFrames; + + // These are all weak because who knows if windows can go away during our + // lifetime. + nsWeakPtr mCurrentSearchFrame; + nsWeakPtr mRootSearchFrame; + nsWeakPtr mLastFocusedWindow; +}; + +#endif diff --git a/embedding/components/moz.build b/embedding/components/moz.build new file mode 100644 index 000000000..483c52d58 --- /dev/null +++ b/embedding/components/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/. + +# You'd think we could skip building ui if XUL is disabled, +# but we need to export interface headers from those directories. +DIRS += [ + 'windowwatcher', + 'appstartup', + 'find', + 'webbrowserpersist', + 'commandhandler', +] + +if CONFIG['MOZ_XUL']: + DIRS += ['printingui'] + +DIRS += ['build'] diff --git a/embedding/components/printingui/ipc/PPrintProgressDialog.ipdl b/embedding/components/printingui/ipc/PPrintProgressDialog.ipdl new file mode 100644 index 000000000..1da29ef87 --- /dev/null +++ b/embedding/components/printingui/ipc/PPrintProgressDialog.ipdl @@ -0,0 +1,35 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 protocol PPrinting; + +namespace mozilla { +namespace embedding { + +protocol PPrintProgressDialog +{ + manager PPrinting; + +parent: + async StateChange(long stateFlags, + nsresult status); + + async ProgressChange(long curSelfProgress, + long maxSelfProgress, + long curTotalProgress, + long maxTotalProgress); + + async DocTitleChange(nsString newTitle); + + async DocURLChange(nsString newURL); + + async __delete__(); + +child: + async DialogOpened(); +}; + +} // namespace embedding +} // namespace mozilla diff --git a/embedding/components/printingui/ipc/PPrintSettingsDialog.ipdl b/embedding/components/printingui/ipc/PPrintSettingsDialog.ipdl new file mode 100644 index 000000000..3e436892e --- /dev/null +++ b/embedding/components/printingui/ipc/PPrintSettingsDialog.ipdl @@ -0,0 +1,29 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 PPrintingTypes; +include protocol PPrinting; +include protocol PRemotePrintJob; + +namespace mozilla { +namespace embedding { + +// A PrintData for success, a failure nsresult for failure. +union PrintDataOrNSResult +{ + PrintData; + nsresult; +}; + +protocol PPrintSettingsDialog +{ + manager PPrinting; + +child: + async __delete__(PrintDataOrNSResult result); +}; + +} // namespace embedding +} // namespace mozilla diff --git a/embedding/components/printingui/ipc/PPrinting.ipdl b/embedding/components/printingui/ipc/PPrinting.ipdl new file mode 100644 index 000000000..0b4a1cf64 --- /dev/null +++ b/embedding/components/printingui/ipc/PPrinting.ipdl @@ -0,0 +1,48 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 PPrintingTypes; +include protocol PContent; +include protocol PBrowser; +include protocol PPrintProgressDialog; +include protocol PPrintSettingsDialog; +include protocol PRemotePrintJob; + +namespace mozilla { +namespace embedding { + +sync protocol PPrinting +{ + manager PContent; + manages PPrintProgressDialog; + manages PPrintSettingsDialog; + manages PRemotePrintJob; + +parent: + sync ShowProgress(PBrowser browser, + PPrintProgressDialog printProgressDialog, + nullable PRemotePrintJob remotePrintJob, + bool isForPrinting) + returns(bool notifyOnOpen, + nsresult rv); + + async ShowPrintDialog(PPrintSettingsDialog dialog, + nullable PBrowser browser, + PrintData settings); + + async PPrintProgressDialog(); + async PPrintSettingsDialog(); + + sync SavePrintSettings(PrintData settings, bool usePrinterNamePrefix, + uint32_t flags) + returns(nsresult rv); + +child: + async PRemotePrintJob(); + async __delete__(); +}; + +} // namespace embedding +} // namespace mozilla diff --git a/embedding/components/printingui/ipc/PPrintingTypes.ipdlh b/embedding/components/printingui/ipc/PPrintingTypes.ipdlh new file mode 100644 index 000000000..084047baf --- /dev/null +++ b/embedding/components/printingui/ipc/PPrintingTypes.ipdlh @@ -0,0 +1,126 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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 protocol PRemotePrintJob; + +namespace mozilla { +namespace embedding { + +struct CStringKeyValue { + nsCString key; + nsCString value; +}; + +struct PrintData { + nullable PRemotePrintJob remotePrintJob; + int32_t startPageRange; + int32_t endPageRange; + double edgeTop; + double edgeLeft; + double edgeBottom; + double edgeRight; + double marginTop; + double marginLeft; + double marginBottom; + double marginRight; + double unwriteableMarginTop; + double unwriteableMarginLeft; + double unwriteableMarginBottom; + double unwriteableMarginRight; + double scaling; + bool printBGColors; + bool printBGImages; + short printRange; + nsString title; + nsString docURL; + nsString headerStrLeft; + nsString headerStrCenter; + nsString headerStrRight; + nsString footerStrLeft; + nsString footerStrCenter; + nsString footerStrRight; + + short howToEnableFrameUI; + bool isCancelled; + short printFrameTypeUsage; + short printFrameType; + bool printSilent; + bool shrinkToFit; + bool showPrintProgress; + + nsString paperName; + short paperData; + double paperWidth; + double paperHeight; + short paperSizeUnit; + bool printReversed; + bool printInColor; + int32_t orientation; + int32_t numCopies; + nsString printerName; + bool printToFile; + nsString toFileName; + short outputFormat; + int32_t printPageDelay; + int32_t resolution; + int32_t duplex; + bool isInitializedFromPrinter; + bool isInitializedFromPrefs; + int32_t optionFlags; + + /* Windows-specific things */ + nsString driverName; + nsString deviceName; + double printableWidthInInches; + double printableHeightInInches; + bool isFramesetDocument; + bool isFramesetFrameSelected; + bool isIFrameSelected; + bool isRangeSelection; + uint8_t[] devModeData; + + /** + * GTK-specific things. Some of these might look like dupes of the + * information we're already passing, but the generalized settings that + * we hold in nsIPrintSettings don't map perfectly to GTK's GtkPrintSettings, + * so there are some nuances. GtkPrintSettings, for example, stores both an + * internal name for paper size, as well as the display name. + */ + CStringKeyValue[] GTKPrintSettings; + + /** + * OS X specific things. + */ + nsString printJobName; + bool printAllPages; + bool mustCollate; + nsString disposition; + /** TODO: Is there an "unsigned short" primitive? **/ + short pagesAcross; + short pagesDown; + double printTime; + bool detailedErrorReporting; + nsString faxNumber; + bool addHeaderAndFooter; + bool fileNameExtensionHidden; + /* + * Holds the scaling factor from the Print dialog when shrink + * to fit is not used. This is needed by the child when it + * isn't using remote printing. When shrink to fit is enabled + * (default), print dialog code ensures this value is 1.0. + */ + float scalingFactor; + /* + * Scaling factor for converting from OS X native paper size + * units to inches. + */ + float widthScale; + float heightScale; + double adjustedPaperWidth; + double adjustedPaperHeight; +}; + +} // namespace embedding +} // namespace mozilla diff --git a/embedding/components/printingui/ipc/PrintDataUtils.cpp b/embedding/components/printingui/ipc/PrintDataUtils.cpp new file mode 100644 index 000000000..30e2075b6 --- /dev/null +++ b/embedding/components/printingui/ipc/PrintDataUtils.cpp @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PrintDataUtils.h"
+#include "nsIPrintSettings.h"
+#include "nsIServiceManager.h"
+#include "nsIWebBrowserPrint.h"
+#include "nsXPIDLString.h"
+
+namespace mozilla {
+namespace embedding {
+
+/**
+ * MockWebBrowserPrint is a mostly useless implementation of nsIWebBrowserPrint,
+ * but wraps a PrintData so that it's able to return information to print
+ * settings dialogs that need an nsIWebBrowserPrint to interrogate.
+ */
+
+NS_IMPL_ISUPPORTS(MockWebBrowserPrint, nsIWebBrowserPrint);
+
+MockWebBrowserPrint::MockWebBrowserPrint(const PrintData &aData)
+ : mData(aData)
+{
+ MOZ_COUNT_CTOR(MockWebBrowserPrint);
+}
+
+MockWebBrowserPrint::~MockWebBrowserPrint()
+{
+ MOZ_COUNT_DTOR(MockWebBrowserPrint);
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetGlobalPrintSettings(nsIPrintSettings **aGlobalPrintSettings)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetCurrentPrintSettings(nsIPrintSettings **aCurrentPrintSettings)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetCurrentChildDOMWindow(mozIDOMWindowProxy **aCurrentPrintSettings)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetDoingPrint(bool *aDoingPrint)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetDoingPrintPreview(bool *aDoingPrintPreview)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetIsFramesetDocument(bool *aIsFramesetDocument)
+{
+ *aIsFramesetDocument = mData.isFramesetDocument();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetIsFramesetFrameSelected(bool *aIsFramesetFrameSelected)
+{
+ *aIsFramesetFrameSelected = mData.isFramesetFrameSelected();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetIsIFrameSelected(bool *aIsIFrameSelected)
+{
+ *aIsIFrameSelected = mData.isIFrameSelected();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetIsRangeSelection(bool *aIsRangeSelection)
+{
+ *aIsRangeSelection = mData.isRangeSelection();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetPrintPreviewNumPages(int32_t *aPrintPreviewNumPages)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::Print(nsIPrintSettings* aThePrintSettings,
+ nsIWebProgressListener* aWPListener)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::PrintPreview(nsIPrintSettings* aThePrintSettings,
+ mozIDOMWindowProxy* aChildDOMWin,
+ nsIWebProgressListener* aWPListener)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::PrintPreviewNavigate(int16_t aNavType,
+ int32_t aPageNum)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::Cancel()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::EnumerateDocumentNames(uint32_t* aCount,
+ char16_t*** aResult)
+{
+ *aCount = 0;
+ *aResult = nullptr;
+
+ if (mData.printJobName().IsEmpty()) {
+ return NS_OK;
+ }
+
+ // The only consumer that cares about this is the OS X printing
+ // dialog, and even then, it only cares about the first document
+ // name. That's why we only send a single document name through
+ // PrintData.
+ char16_t** array = (char16_t**) moz_xmalloc(sizeof(char16_t*));
+ array[0] = ToNewUnicode(mData.printJobName());
+
+ *aCount = 1;
+ *aResult = array;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::ExitPrintPreview()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace embedding
+} // namespace mozilla
+
diff --git a/embedding/components/printingui/ipc/PrintDataUtils.h b/embedding/components/printingui/ipc/PrintDataUtils.h new file mode 100644 index 000000000..da313b2f3 --- /dev/null +++ b/embedding/components/printingui/ipc/PrintDataUtils.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_embedding_PrintDataUtils_h
+#define mozilla_embedding_PrintDataUtils_h
+
+#include "mozilla/embedding/PPrinting.h"
+#include "nsIWebBrowserPrint.h"
+
+/**
+ * nsIPrintSettings and nsIWebBrowserPrint information is sent back and forth
+ * across PPrinting via the PrintData struct. These are utilities for
+ * manipulating PrintData that can be used on either side of the communications
+ * channel.
+ */
+
+namespace mozilla {
+namespace embedding {
+
+class MockWebBrowserPrint final : public nsIWebBrowserPrint
+{
+public:
+ explicit MockWebBrowserPrint(const PrintData &aData);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBBROWSERPRINT
+
+private:
+ ~MockWebBrowserPrint();
+ PrintData mData;
+};
+
+} // namespace embedding
+} // namespace mozilla
+
+#endif
diff --git a/embedding/components/printingui/ipc/PrintProgressDialogChild.cpp b/embedding/components/printingui/ipc/PrintProgressDialogChild.cpp new file mode 100644 index 000000000..a6dc5de4e --- /dev/null +++ b/embedding/components/printingui/ipc/PrintProgressDialogChild.cpp @@ -0,0 +1,132 @@ +/* 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/Unused.h" +#include "nsIObserver.h" +#include "PrintProgressDialogChild.h" + +class nsIWebProgress; +class nsIRequest; + +using mozilla::Unused; + +namespace mozilla { +namespace embedding { + +NS_IMPL_ISUPPORTS(PrintProgressDialogChild, + nsIWebProgressListener, + nsIPrintProgressParams) + +PrintProgressDialogChild::PrintProgressDialogChild( + nsIObserver* aOpenObserver) : + mOpenObserver(aOpenObserver) +{ + MOZ_COUNT_CTOR(PrintProgressDialogChild); +} + +PrintProgressDialogChild::~PrintProgressDialogChild() +{ + // When the printing engine stops supplying information about printing + // progress, it'll drop references to us and destroy us. We need to signal + // the parent to decrement its refcount, as well as prevent it from attempting + // to contact us further. + Unused << Send__delete__(this); + MOZ_COUNT_DTOR(PrintProgressDialogChild); +} + +bool +PrintProgressDialogChild::RecvDialogOpened() +{ + // nsPrintEngine's observer, which we're reporting to here, doesn't care + // what gets passed as the subject, topic or data, so we'll just send + // nullptrs. + mOpenObserver->Observe(nullptr, nullptr, nullptr); + return true; +} + +// nsIWebProgressListener + +NS_IMETHODIMP +PrintProgressDialogChild::OnStateChange(nsIWebProgress* aProgress, + nsIRequest* aRequest, + uint32_t aStateFlags, + nsresult aStatus) +{ + Unused << SendStateChange(aStateFlags, aStatus); + return NS_OK; +} + +NS_IMETHODIMP +PrintProgressDialogChild::OnProgressChange(nsIWebProgress * aProgress, + nsIRequest * aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) +{ + Unused << SendProgressChange(aCurSelfProgress, aMaxSelfProgress, + aCurTotalProgress, aMaxTotalProgress); + return NS_OK; +} + +NS_IMETHODIMP +PrintProgressDialogChild::OnLocationChange(nsIWebProgress* aProgress, + nsIRequest* aRequest, + nsIURI* aURI, + uint32_t aFlags) +{ + return NS_OK; +} + +NS_IMETHODIMP +PrintProgressDialogChild::OnStatusChange(nsIWebProgress* aProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) +{ + return NS_OK; +} + +NS_IMETHODIMP +PrintProgressDialogChild::OnSecurityChange(nsIWebProgress* aProgress, + nsIRequest* aRequest, + uint32_t aState) +{ + return NS_OK; +} + +// nsIPrintProgressParams + +NS_IMETHODIMP PrintProgressDialogChild::GetDocTitle(char16_t* *aDocTitle) +{ + NS_ENSURE_ARG(aDocTitle); + + *aDocTitle = ToNewUnicode(mDocTitle); + return NS_OK; +} + +NS_IMETHODIMP PrintProgressDialogChild::SetDocTitle(const char16_t* aDocTitle) +{ + mDocTitle = aDocTitle; + Unused << SendDocTitleChange(nsString(aDocTitle)); + return NS_OK; +} + +NS_IMETHODIMP PrintProgressDialogChild::GetDocURL(char16_t **aDocURL) +{ + NS_ENSURE_ARG(aDocURL); + + *aDocURL = ToNewUnicode(mDocURL); + return NS_OK; +} + +NS_IMETHODIMP PrintProgressDialogChild::SetDocURL(const char16_t* aDocURL) +{ + mDocURL = aDocURL; + Unused << SendDocURLChange(nsString(aDocURL)); + return NS_OK; +} + +} // namespace embedding +} // namespace mozilla diff --git a/embedding/components/printingui/ipc/PrintProgressDialogChild.h b/embedding/components/printingui/ipc/PrintProgressDialogChild.h new file mode 100644 index 000000000..b0a7105d7 --- /dev/null +++ b/embedding/components/printingui/ipc/PrintProgressDialogChild.h @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_embedding_PrintProgressDialogChild_h +#define mozilla_embedding_PrintProgressDialogChild_h + +#include "mozilla/embedding/PPrintProgressDialogChild.h" +#include "nsIPrintProgressParams.h" +#include "nsIWebProgressListener.h" + +class nsIObserver; + +namespace mozilla { +namespace embedding { + +class PrintProgressDialogChild final : public PPrintProgressDialogChild, + public nsIWebProgressListener, + public nsIPrintProgressParams +{ + NS_DECL_ISUPPORTS + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_NSIPRINTPROGRESSPARAMS + +public: + MOZ_IMPLICIT PrintProgressDialogChild(nsIObserver* aOpenObserver); + + virtual bool RecvDialogOpened() override; + +private: + virtual ~PrintProgressDialogChild(); + nsCOMPtr<nsIObserver> mOpenObserver; + nsString mDocTitle; + nsString mDocURL; +}; + +} // namespace embedding +} // namespace mozilla + +#endif diff --git a/embedding/components/printingui/ipc/PrintProgressDialogParent.cpp b/embedding/components/printingui/ipc/PrintProgressDialogParent.cpp new file mode 100644 index 000000000..6e34704ff --- /dev/null +++ b/embedding/components/printingui/ipc/PrintProgressDialogParent.cpp @@ -0,0 +1,113 @@ +/* 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/Unused.h" +#include "nsIPrintProgressParams.h" +#include "nsIWebProgressListener.h" +#include "PrintProgressDialogParent.h" + +using mozilla::Unused; + +namespace mozilla { +namespace embedding { + +NS_IMPL_ISUPPORTS(PrintProgressDialogParent, nsIObserver) + +PrintProgressDialogParent::PrintProgressDialogParent() : + mActive(true) +{ + MOZ_COUNT_CTOR(PrintProgressDialogParent); +} + +PrintProgressDialogParent::~PrintProgressDialogParent() +{ + MOZ_COUNT_DTOR(PrintProgressDialogParent); +} + +void +PrintProgressDialogParent::SetWebProgressListener(nsIWebProgressListener* aListener) +{ + mWebProgressListener = aListener; +} + +void +PrintProgressDialogParent::SetPrintProgressParams(nsIPrintProgressParams* aParams) +{ + mPrintProgressParams = aParams; +} + +bool +PrintProgressDialogParent::RecvStateChange(const long& stateFlags, + const nsresult& status) +{ + if (mWebProgressListener) { + mWebProgressListener->OnStateChange(nullptr, nullptr, stateFlags, status); + } + return true; +} + +bool +PrintProgressDialogParent::RecvProgressChange(const long& curSelfProgress, + const long& maxSelfProgress, + const long& curTotalProgress, + const long& maxTotalProgress) +{ + if (mWebProgressListener) { + mWebProgressListener->OnProgressChange(nullptr, nullptr, curSelfProgress, + maxSelfProgress, curTotalProgress, + maxTotalProgress); + } + return true; +} + +bool +PrintProgressDialogParent::RecvDocTitleChange(const nsString& newTitle) +{ + if (mPrintProgressParams) { + mPrintProgressParams->SetDocTitle(newTitle.get()); + } + return true; +} + +bool +PrintProgressDialogParent::RecvDocURLChange(const nsString& newURL) +{ + if (mPrintProgressParams) { + mPrintProgressParams->SetDocURL(newURL.get()); + } + return true; +} + +void +PrintProgressDialogParent::ActorDestroy(ActorDestroyReason aWhy) +{ +} + +bool +PrintProgressDialogParent::Recv__delete__() +{ + // The child has requested that we tear down the connection, so we set a + // member to make sure we don't try to contact it after the fact. + mActive = false; + return true; +} + +// nsIObserver +NS_IMETHODIMP +PrintProgressDialogParent::Observe(nsISupports *aSubject, const char *aTopic, + const char16_t *aData) +{ + if (mActive) { + Unused << SendDialogOpened(); + } else { + NS_WARNING("The print progress dialog finished opening, but communications " + "with the child have been closed."); + } + + return NS_OK; +} + + +} // namespace embedding +} // namespace mozilla diff --git a/embedding/components/printingui/ipc/PrintProgressDialogParent.h b/embedding/components/printingui/ipc/PrintProgressDialogParent.h new file mode 100644 index 000000000..540609162 --- /dev/null +++ b/embedding/components/printingui/ipc/PrintProgressDialogParent.h @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_embedding_PrintProgressDialogParent_h +#define mozilla_embedding_PrintProgressDialogParent_h + +#include "mozilla/embedding/PPrintProgressDialogParent.h" +#include "nsIObserver.h" + +class nsIPrintProgressParams; +class nsIWebProgressListener; + +namespace mozilla { +namespace embedding { +class PrintProgressDialogParent final : public PPrintProgressDialogParent, + public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + MOZ_IMPLICIT PrintProgressDialogParent(); + + void SetWebProgressListener(nsIWebProgressListener* aListener); + + void SetPrintProgressParams(nsIPrintProgressParams* aParams); + + virtual bool + RecvStateChange( + const long& stateFlags, + const nsresult& status) override; + + virtual bool + RecvProgressChange( + const long& curSelfProgress, + const long& maxSelfProgress, + const long& curTotalProgress, + const long& maxTotalProgress) override; + + virtual bool + RecvDocTitleChange(const nsString& newTitle) override; + + virtual bool + RecvDocURLChange(const nsString& newURL) override; + + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + virtual bool + Recv__delete__() override; + +private: + virtual ~PrintProgressDialogParent(); + + nsCOMPtr<nsIWebProgressListener> mWebProgressListener; + nsCOMPtr<nsIPrintProgressParams> mPrintProgressParams; + bool mActive; +}; + +} // namespace embedding +} // namespace mozilla + +#endif diff --git a/embedding/components/printingui/ipc/PrintSettingsDialogChild.cpp b/embedding/components/printingui/ipc/PrintSettingsDialogChild.cpp new file mode 100644 index 000000000..ee9c5de59 --- /dev/null +++ b/embedding/components/printingui/ipc/PrintSettingsDialogChild.cpp @@ -0,0 +1,38 @@ +/* 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 "PrintSettingsDialogChild.h" + +using mozilla::Unused; + +namespace mozilla { +namespace embedding { + +PrintSettingsDialogChild::PrintSettingsDialogChild() +: mReturned(false) +{ + MOZ_COUNT_CTOR(PrintSettingsDialogChild); +} + +PrintSettingsDialogChild::~PrintSettingsDialogChild() +{ + MOZ_COUNT_DTOR(PrintSettingsDialogChild); +} + +bool +PrintSettingsDialogChild::Recv__delete__(const PrintDataOrNSResult& aData) +{ + if (aData.type() == PrintDataOrNSResult::Tnsresult) { + mResult = aData.get_nsresult(); + MOZ_ASSERT(NS_FAILED(mResult), "expected a failure result"); + } else { + mResult = NS_OK; + mData = aData.get_PrintData(); + } + mReturned = true; + return true; +} + +} // namespace embedding +} // namespace mozilla diff --git a/embedding/components/printingui/ipc/PrintSettingsDialogChild.h b/embedding/components/printingui/ipc/PrintSettingsDialogChild.h new file mode 100644 index 000000000..6db60e221 --- /dev/null +++ b/embedding/components/printingui/ipc/PrintSettingsDialogChild.h @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_embedding_PrintSettingsDialogChild_h +#define mozilla_embedding_PrintSettingsDialogChild_h + +#include "mozilla/embedding/PPrintSettingsDialogChild.h" +namespace mozilla { +namespace embedding { + +class PrintSettingsDialogChild final : public PPrintSettingsDialogChild +{ + NS_INLINE_DECL_REFCOUNTING(PrintSettingsDialogChild) + +public: + MOZ_IMPLICIT PrintSettingsDialogChild(); + + virtual bool Recv__delete__(const PrintDataOrNSResult& aData) override; + + bool returned() { return mReturned; }; + nsresult result() { return mResult; }; + PrintData data() { return mData; }; + +private: + virtual ~PrintSettingsDialogChild(); + bool mReturned; + nsresult mResult; + PrintData mData; +}; + +} // namespace embedding +} // namespace mozilla + +#endif diff --git a/embedding/components/printingui/ipc/PrintSettingsDialogParent.cpp b/embedding/components/printingui/ipc/PrintSettingsDialogParent.cpp new file mode 100644 index 000000000..016677d96 --- /dev/null +++ b/embedding/components/printingui/ipc/PrintSettingsDialogParent.cpp @@ -0,0 +1,28 @@ +/* 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 "PrintSettingsDialogParent.h" + +// C++ file contents +namespace mozilla { +namespace embedding { + +PrintSettingsDialogParent::PrintSettingsDialogParent() +{ + MOZ_COUNT_CTOR(PrintSettingsDialogParent); +} + +PrintSettingsDialogParent::~PrintSettingsDialogParent() +{ + MOZ_COUNT_DTOR(PrintSettingsDialogParent); +} + +void +PrintSettingsDialogParent::ActorDestroy(ActorDestroyReason aWhy) +{ +} + +} // namespace embedding +} // namespace mozilla + diff --git a/embedding/components/printingui/ipc/PrintSettingsDialogParent.h b/embedding/components/printingui/ipc/PrintSettingsDialogParent.h new file mode 100644 index 000000000..e655903e7 --- /dev/null +++ b/embedding/components/printingui/ipc/PrintSettingsDialogParent.h @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_embedding_PrintSettingsDialogParent_h +#define mozilla_embedding_PrintSettingsDialogParent_h + +#include "mozilla/embedding/PPrintSettingsDialogParent.h" + +// Header file contents +namespace mozilla { +namespace embedding { + +class PrintSettingsDialogParent final : public PPrintSettingsDialogParent +{ +public: + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + MOZ_IMPLICIT PrintSettingsDialogParent(); + +private: + virtual ~PrintSettingsDialogParent(); +}; + +} // namespace embedding +} // namespace mozilla + +#endif diff --git a/embedding/components/printingui/ipc/PrintingParent.cpp b/embedding/components/printingui/ipc/PrintingParent.cpp new file mode 100644 index 000000000..46469844a --- /dev/null +++ b/embedding/components/printingui/ipc/PrintingParent.cpp @@ -0,0 +1,336 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/Element.h" +#include "mozilla/dom/TabParent.h" +#include "mozilla/Preferences.h" +#include "mozilla/Unused.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIDOMWindow.h" +#include "nsIPrintingPromptService.h" +#include "nsIPrintProgressParams.h" +#include "nsIPrintSettingsService.h" +#include "nsIServiceManager.h" +#include "nsServiceManagerUtils.h" +#include "nsIWebProgressListener.h" +#include "PrintingParent.h" +#include "PrintDataUtils.h" +#include "PrintProgressDialogParent.h" +#include "PrintSettingsDialogParent.h" +#include "mozilla/layout/RemotePrintJobParent.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::layout; + +namespace mozilla { +namespace embedding { +bool +PrintingParent::RecvShowProgress(PBrowserParent* parent, + PPrintProgressDialogParent* printProgressDialog, + PRemotePrintJobParent* remotePrintJob, + const bool& isForPrinting, + bool* notifyOnOpen, + nsresult* result) +{ + *result = NS_ERROR_FAILURE; + *notifyOnOpen = false; + + nsCOMPtr<nsPIDOMWindowOuter> parentWin = DOMWindowFromBrowserParent(parent); + if (!parentWin) { + return true; + } + + nsCOMPtr<nsIPrintingPromptService> pps(do_GetService("@mozilla.org/embedcomp/printingprompt-service;1")); + + if (!pps) { + return true; + } + + PrintProgressDialogParent* dialogParent = + static_cast<PrintProgressDialogParent*>(printProgressDialog); + nsCOMPtr<nsIObserver> observer = do_QueryInterface(dialogParent); + + nsCOMPtr<nsIWebProgressListener> printProgressListener; + nsCOMPtr<nsIPrintProgressParams> printProgressParams; + + *result = pps->ShowProgress(parentWin, nullptr, nullptr, observer, + isForPrinting, + getter_AddRefs(printProgressListener), + getter_AddRefs(printProgressParams), + notifyOnOpen); + NS_ENSURE_SUCCESS(*result, true); + + if (remotePrintJob) { + // If we have a RemotePrintJob use that as a more general forwarder for + // print progress listeners. + static_cast<RemotePrintJobParent*>(remotePrintJob) + ->RegisterListener(printProgressListener); + } else { + dialogParent->SetWebProgressListener(printProgressListener); + } + + dialogParent->SetPrintProgressParams(printProgressParams); + + return true; +} + +nsresult +PrintingParent::ShowPrintDialog(PBrowserParent* aParent, + const PrintData& aData, + PrintData* aResult) +{ + // If aParent is null this call is just being used to get print settings from + // the printer for print preview. + bool isPrintPreview = !aParent; + nsCOMPtr<nsPIDOMWindowOuter> parentWin; + if (aParent) { + parentWin = DOMWindowFromBrowserParent(aParent); + if (!parentWin) { + return NS_ERROR_FAILURE; + } + } + + nsCOMPtr<nsIPrintingPromptService> pps(do_GetService("@mozilla.org/embedcomp/printingprompt-service;1")); + if (!pps) { + return NS_ERROR_FAILURE; + } + + // The initSettings we got can be wrapped using + // PrintDataUtils' MockWebBrowserPrint, which implements enough of + // nsIWebBrowserPrint to keep the dialogs happy. + nsCOMPtr<nsIWebBrowserPrint> wbp = new MockWebBrowserPrint(aData); + + // Use the existing RemotePrintJob and its settings, if we have one, to make + // sure they stay current. + RemotePrintJobParent* remotePrintJob = + static_cast<RemotePrintJobParent*>(aData.remotePrintJobParent()); + nsCOMPtr<nsIPrintSettings> settings; + nsresult rv; + if (remotePrintJob) { + settings = remotePrintJob->GetPrintSettings(); + } else { + rv = mPrintSettingsSvc->GetNewPrintSettings(getter_AddRefs(settings)); + NS_ENSURE_SUCCESS(rv, rv); + } + + // We only want to use the print silently setting from the parent. + bool printSilently; + rv = settings->GetPrintSilent(&printSilently); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mPrintSettingsSvc->DeserializeToPrintSettings(aData, settings); + NS_ENSURE_SUCCESS(rv, rv); + + rv = settings->SetPrintSilent(printSilently); + NS_ENSURE_SUCCESS(rv, rv); + + // If this is for print preview or we are printing silently then we just need + // to initialize the print settings with anything specific from the printer. + if (isPrintPreview || printSilently || + Preferences::GetBool("print.always_print_silent", printSilently)) { + nsXPIDLString printerName; + rv = settings->GetPrinterName(getter_Copies(printerName)); + NS_ENSURE_SUCCESS(rv, rv); + + settings->SetIsInitializedFromPrinter(false); + mPrintSettingsSvc->InitPrintSettingsFromPrinter(printerName, settings); + } else { + rv = pps->ShowPrintDialog(parentWin, wbp, settings); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (isPrintPreview) { + // For print preview we don't want a RemotePrintJob just the settings. + rv = mPrintSettingsSvc->SerializeToPrintData(settings, nullptr, aResult); + } else { + rv = SerializeAndEnsureRemotePrintJob(settings, nullptr, remotePrintJob, + aResult); + } + + return rv; +} + +bool +PrintingParent::RecvShowPrintDialog(PPrintSettingsDialogParent* aDialog, + PBrowserParent* aParent, + const PrintData& aData) +{ + PrintData resultData; + nsresult rv = ShowPrintDialog(aParent, aData, &resultData); + + // The child has been spinning an event loop while waiting + // to hear about the print settings. We return the results + // with an async message which frees the child process from + // its nested event loop. + if (NS_FAILED(rv)) { + mozilla::Unused << aDialog->Send__delete__(aDialog, rv); + } else { + mozilla::Unused << aDialog->Send__delete__(aDialog, resultData); + } + return true; +} + +bool +PrintingParent::RecvSavePrintSettings(const PrintData& aData, + const bool& aUsePrinterNamePrefix, + const uint32_t& aFlags, + nsresult* aResult) +{ + nsCOMPtr<nsIPrintSettings> settings; + *aResult = mPrintSettingsSvc->GetNewPrintSettings(getter_AddRefs(settings)); + NS_ENSURE_SUCCESS(*aResult, true); + + *aResult = mPrintSettingsSvc->DeserializeToPrintSettings(aData, settings); + NS_ENSURE_SUCCESS(*aResult, true); + + *aResult = mPrintSettingsSvc->SavePrintSettingsToPrefs(settings, + aUsePrinterNamePrefix, + aFlags); + + return true; +} + +PPrintProgressDialogParent* +PrintingParent::AllocPPrintProgressDialogParent() +{ + PrintProgressDialogParent* actor = new PrintProgressDialogParent(); + NS_ADDREF(actor); // De-ref'd in the __delete__ handler for + // PrintProgressDialogParent. + return actor; +} + +bool +PrintingParent::DeallocPPrintProgressDialogParent(PPrintProgressDialogParent* doomed) +{ + // We can't just delete the PrintProgressDialogParent since somebody might + // still be holding a reference to it as nsIObserver, so just decrement the + // refcount instead. + PrintProgressDialogParent* actor = static_cast<PrintProgressDialogParent*>(doomed); + NS_RELEASE(actor); + return true; +} + +PPrintSettingsDialogParent* +PrintingParent::AllocPPrintSettingsDialogParent() +{ + return new PrintSettingsDialogParent(); +} + +bool +PrintingParent::DeallocPPrintSettingsDialogParent(PPrintSettingsDialogParent* aDoomed) +{ + delete aDoomed; + return true; +} + +PRemotePrintJobParent* +PrintingParent::AllocPRemotePrintJobParent() +{ + MOZ_ASSERT_UNREACHABLE("No default constructors for implementations."); + return nullptr; +} + +bool +PrintingParent::DeallocPRemotePrintJobParent(PRemotePrintJobParent* aDoomed) +{ + delete aDoomed; + return true; +} + +void +PrintingParent::ActorDestroy(ActorDestroyReason aWhy) +{ +} + +nsPIDOMWindowOuter* +PrintingParent::DOMWindowFromBrowserParent(PBrowserParent* parent) +{ + if (!parent) { + return nullptr; + } + + TabParent* tabParent = TabParent::GetFrom(parent); + if (!tabParent) { + return nullptr; + } + + nsCOMPtr<Element> frameElement = tabParent->GetOwnerElement(); + if (!frameElement) { + return nullptr; + } + + nsCOMPtr<nsIContent> frame(do_QueryInterface(frameElement)); + if (!frame) { + return nullptr; + } + + nsCOMPtr<nsPIDOMWindowOuter> parentWin = frame->OwnerDoc()->GetWindow(); + if (!parentWin) { + return nullptr; + } + + return parentWin; +} + +nsresult +PrintingParent::SerializeAndEnsureRemotePrintJob( + nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aListener, + layout::RemotePrintJobParent* aRemotePrintJob, PrintData* aPrintData) +{ + MOZ_ASSERT(aPrintData); + + nsresult rv; + nsCOMPtr<nsIPrintSettings> printSettings; + if (aPrintSettings) { + printSettings = aPrintSettings; + } else { + rv = mPrintSettingsSvc->GetNewPrintSettings(getter_AddRefs(printSettings)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + rv = mPrintSettingsSvc->SerializeToPrintData(printSettings, nullptr, + aPrintData); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + RemotePrintJobParent* remotePrintJob; + if (aRemotePrintJob) { + remotePrintJob = aRemotePrintJob; + aPrintData->remotePrintJobParent() = remotePrintJob; + } else { + remotePrintJob = new RemotePrintJobParent(aPrintSettings); + aPrintData->remotePrintJobParent() = + SendPRemotePrintJobConstructor(remotePrintJob); + } + if (aListener) { + remotePrintJob->RegisterListener(aListener); + } + + return NS_OK; +} + +PrintingParent::PrintingParent() +{ + MOZ_COUNT_CTOR(PrintingParent); + + mPrintSettingsSvc = + do_GetService("@mozilla.org/gfx/printsettings-service;1"); + MOZ_ASSERT(mPrintSettingsSvc); +} + +PrintingParent::~PrintingParent() +{ + MOZ_COUNT_DTOR(PrintingParent); +} + +} // namespace embedding +} // namespace mozilla + diff --git a/embedding/components/printingui/ipc/PrintingParent.h b/embedding/components/printingui/ipc/PrintingParent.h new file mode 100644 index 000000000..66f87726c --- /dev/null +++ b/embedding/components/printingui/ipc/PrintingParent.h @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set sw=4 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_embedding_PrintingParent_h +#define mozilla_embedding_PrintingParent_h + +#include "mozilla/dom/PBrowserParent.h" +#include "mozilla/embedding/PPrintingParent.h" + +class nsIPrintSettingsService; +class nsIWebProgressListener; +class nsPIDOMWindowOuter; +class PPrintProgressDialogParent; +class PPrintSettingsDialogParent; + +namespace mozilla { +namespace layout { +class PRemotePrintJobParent; +class RemotePrintJobParent; +} + +namespace embedding { + +class PrintingParent final : public PPrintingParent +{ +public: + NS_INLINE_DECL_REFCOUNTING(PrintingParent) + + virtual bool + RecvShowProgress(PBrowserParent* parent, + PPrintProgressDialogParent* printProgressDialog, + PRemotePrintJobParent* remotePrintJob, + const bool& isForPrinting, + bool* notifyOnOpen, + nsresult* result); + virtual bool + RecvShowPrintDialog(PPrintSettingsDialogParent* aDialog, + PBrowserParent* aParent, + const PrintData& aData); + + virtual bool + RecvSavePrintSettings(const PrintData& data, + const bool& usePrinterNamePrefix, + const uint32_t& flags, + nsresult* rv); + + virtual PPrintProgressDialogParent* + AllocPPrintProgressDialogParent(); + + virtual bool + DeallocPPrintProgressDialogParent(PPrintProgressDialogParent* aActor); + + virtual PPrintSettingsDialogParent* + AllocPPrintSettingsDialogParent(); + + virtual bool + DeallocPPrintSettingsDialogParent(PPrintSettingsDialogParent* aActor); + + virtual PRemotePrintJobParent* + AllocPRemotePrintJobParent(); + + virtual bool + DeallocPRemotePrintJobParent(PRemotePrintJobParent* aActor); + + virtual void + ActorDestroy(ActorDestroyReason aWhy); + + MOZ_IMPLICIT PrintingParent(); + + /** + * Serialize nsIPrintSettings to PrintData ready for sending to a child + * process. A RemotePrintJob will be created and added to the PrintData. + * An optional progress listener can be given, which will be registered + * with the RemotePrintJob, so that progress can be tracked in the parent. + * + * @param aPrintSettings optional print settings to serialize, otherwise a + * default print settings will be used. + * @param aProgressListener optional print progress listener. + * @param aRemotePrintJob optional remote print job, so that an existing + * one can be used. + * @param aPrintData PrintData to populate. + */ + nsresult + SerializeAndEnsureRemotePrintJob(nsIPrintSettings* aPrintSettings, + nsIWebProgressListener* aListener, + layout::RemotePrintJobParent* aRemotePrintJob, + PrintData* aPrintData); + +private: + virtual ~PrintingParent(); + + nsPIDOMWindowOuter* + DOMWindowFromBrowserParent(PBrowserParent* parent); + + nsresult + ShowPrintDialog(PBrowserParent* parent, + const PrintData& data, + PrintData* result); + + nsCOMPtr<nsIPrintSettingsService> mPrintSettingsSvc; +}; + +} // namespace embedding +} // namespace mozilla + +#endif diff --git a/embedding/components/printingui/ipc/moz.build b/embedding/components/printingui/ipc/moz.build new file mode 100644 index 000000000..29822e652 --- /dev/null +++ b/embedding/components/printingui/ipc/moz.build @@ -0,0 +1,35 @@ +# -*- 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/. + +EXPORTS += [ + 'nsPrintingProxy.h', +] + +EXPORTS.mozilla.embedding.printingui += [ + 'PrintingParent.h', +] + +if CONFIG['NS_PRINTING']: + UNIFIED_SOURCES += [ + 'nsPrintingProxy.cpp', + 'PrintDataUtils.cpp', + 'PrintingParent.cpp', + 'PrintProgressDialogChild.cpp', + 'PrintProgressDialogParent.cpp', + 'PrintSettingsDialogChild.cpp', + 'PrintSettingsDialogParent.cpp', + ] + +IPDL_SOURCES += [ + 'PPrinting.ipdl', + 'PPrintingTypes.ipdlh', + 'PPrintProgressDialog.ipdl', + 'PPrintSettingsDialog.ipdl', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' diff --git a/embedding/components/printingui/ipc/nsPrintingProxy.cpp b/embedding/components/printingui/ipc/nsPrintingProxy.cpp new file mode 100644 index 000000000..353be04ee --- /dev/null +++ b/embedding/components/printingui/ipc/nsPrintingProxy.cpp @@ -0,0 +1,269 @@ +/* -*- 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 "nsPrintingProxy.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/TabChild.h" +#include "mozilla/layout/RemotePrintJobChild.h" +#include "mozilla/Unused.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIPrintingPromptService.h" +#include "nsIPrintSession.h" +#include "nsPIDOMWindow.h" +#include "nsPrintOptionsImpl.h" +#include "nsServiceManagerUtils.h" +#include "PrintDataUtils.h" +#include "PrintProgressDialogChild.h" +#include "PrintSettingsDialogChild.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::embedding; +using namespace mozilla::layout; + +static StaticRefPtr<nsPrintingProxy> sPrintingProxyInstance; + +NS_IMPL_ISUPPORTS(nsPrintingProxy, nsIPrintingPromptService) + +nsPrintingProxy::nsPrintingProxy() +{ +} + +nsPrintingProxy::~nsPrintingProxy() +{ +} + +/* static */ +already_AddRefed<nsPrintingProxy> +nsPrintingProxy::GetInstance() +{ + if (!sPrintingProxyInstance) { + sPrintingProxyInstance = new nsPrintingProxy(); + if (!sPrintingProxyInstance) { + return nullptr; + } + nsresult rv = sPrintingProxyInstance->Init(); + if (NS_FAILED(rv)) { + sPrintingProxyInstance = nullptr; + return nullptr; + } + ClearOnShutdown(&sPrintingProxyInstance); + } + + RefPtr<nsPrintingProxy> inst = sPrintingProxyInstance.get(); + return inst.forget(); +} + +nsresult +nsPrintingProxy::Init() +{ + mozilla::Unused << ContentChild::GetSingleton()->SendPPrintingConstructor(this); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingProxy::ShowPrintDialog(mozIDOMWindowProxy *parent, + nsIWebBrowserPrint *webBrowserPrint, + nsIPrintSettings *printSettings) +{ + NS_ENSURE_ARG(webBrowserPrint); + NS_ENSURE_ARG(printSettings); + + // If parent is null we are just being called to retrieve the print settings + // from the printer in the parent for print preview. + TabChild* pBrowser = nullptr; + if (parent) { + // Get the TabChild for this nsIDOMWindow, which we can then pass up to + // the parent. + nsCOMPtr<nsPIDOMWindowOuter> pwin = nsPIDOMWindowOuter::From(parent); + NS_ENSURE_STATE(pwin); + nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell(); + NS_ENSURE_STATE(docShell); + + nsCOMPtr<nsITabChild> tabchild = docShell->GetTabChild(); + NS_ENSURE_STATE(tabchild); + + pBrowser = static_cast<TabChild*>(tabchild.get()); + } + + // Next, serialize the nsIWebBrowserPrint and nsIPrintSettings we were given. + nsresult rv = NS_OK; + nsCOMPtr<nsIPrintSettingsService> printSettingsSvc = + do_GetService("@mozilla.org/gfx/printsettings-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + PrintData inSettings; + rv = printSettingsSvc->SerializeToPrintData(printSettings, webBrowserPrint, + &inSettings); + NS_ENSURE_SUCCESS(rv, rv); + + // Now, the waiting game. The parent process should be showing + // the printing dialog soon. In the meantime, we need to spin a + // nested event loop while we wait for the results of the dialog + // to be returned to us. + + RefPtr<PrintSettingsDialogChild> dialog = new PrintSettingsDialogChild(); + SendPPrintSettingsDialogConstructor(dialog); + + mozilla::Unused << SendShowPrintDialog(dialog, pBrowser, inSettings); + + while(!dialog->returned()) { + NS_ProcessNextEvent(nullptr, true); + } + + rv = dialog->result(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = printSettingsSvc->DeserializeToPrintSettings(dialog->data(), + printSettings); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingProxy::ShowProgress(mozIDOMWindowProxy* parent, + nsIWebBrowserPrint* webBrowserPrint, // ok to be null + nsIPrintSettings* printSettings, // ok to be null + nsIObserver* openDialogObserver, // ok to be null + bool isForPrinting, + nsIWebProgressListener** webProgressListener, + nsIPrintProgressParams** printProgressParams, + bool* notifyOnOpen) +{ + NS_ENSURE_ARG(parent); + NS_ENSURE_ARG(webProgressListener); + NS_ENSURE_ARG(printProgressParams); + NS_ENSURE_ARG(notifyOnOpen); + + // Get the TabChild for this nsIDOMWindow, which we can then pass up to + // the parent. + nsCOMPtr<nsPIDOMWindowOuter> pwin = nsPIDOMWindowOuter::From(parent); + NS_ENSURE_STATE(pwin); + nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell(); + NS_ENSURE_STATE(docShell); + nsCOMPtr<nsITabChild> tabchild = docShell->GetTabChild(); + TabChild* pBrowser = static_cast<TabChild*>(tabchild.get()); + + RefPtr<PrintProgressDialogChild> dialogChild = + new PrintProgressDialogChild(openDialogObserver); + + SendPPrintProgressDialogConstructor(dialogChild); + + // Get the RemotePrintJob if we have one available. + RefPtr<mozilla::layout::RemotePrintJobChild> remotePrintJob; + if (printSettings) { + nsCOMPtr<nsIPrintSession> printSession; + nsresult rv = printSettings->GetPrintSession(getter_AddRefs(printSession)); + if (NS_SUCCEEDED(rv) && printSession) { + printSession->GetRemotePrintJob(getter_AddRefs(remotePrintJob)); + } + } + + nsresult rv = NS_OK; + mozilla::Unused << SendShowProgress(pBrowser, dialogChild, remotePrintJob, + isForPrinting, notifyOnOpen, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + // If we have a RemotePrintJob that will be being used as a more general + // forwarder for print progress listeners. Once we always have one we can + // remove the interface from PrintProgressDialogChild. + if (!remotePrintJob) { + NS_ADDREF(*webProgressListener = dialogChild); + } + NS_ADDREF(*printProgressParams = dialogChild); + + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingProxy::ShowPageSetup(mozIDOMWindowProxy *parent, + nsIPrintSettings *printSettings, + nsIObserver *aObs) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPrintingProxy::ShowPrinterProperties(mozIDOMWindowProxy *parent, + const char16_t *printerName, + nsIPrintSettings *printSettings) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsPrintingProxy::SavePrintSettings(nsIPrintSettings* aPS, + bool aUsePrinterNamePrefix, + uint32_t aFlags) +{ + nsresult rv; + nsCOMPtr<nsIPrintSettingsService> printSettingsSvc = + do_GetService("@mozilla.org/gfx/printsettings-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + PrintData settings; + rv = printSettingsSvc->SerializeToPrintData(aPS, nullptr, &settings); + NS_ENSURE_SUCCESS(rv, rv); + + Unused << SendSavePrintSettings(settings, aUsePrinterNamePrefix, aFlags, + &rv); + return rv; +} + +PPrintProgressDialogChild* +nsPrintingProxy::AllocPPrintProgressDialogChild() +{ + // The parent process will never initiate the PPrintProgressDialog + // protocol connection, so no need to provide an allocator here. + NS_NOTREACHED("Allocator for PPrintProgressDialogChild should not be " + "called on nsPrintingProxy."); + return nullptr; +} + +bool +nsPrintingProxy::DeallocPPrintProgressDialogChild(PPrintProgressDialogChild* aActor) +{ + // The PrintProgressDialogChild implements refcounting, and + // will take itself out. + return true; +} + +PPrintSettingsDialogChild* +nsPrintingProxy::AllocPPrintSettingsDialogChild() +{ + // The parent process will never initiate the PPrintSettingsDialog + // protocol connection, so no need to provide an allocator here. + NS_NOTREACHED("Allocator for PPrintSettingsDialogChild should not be " + "called on nsPrintingProxy."); + return nullptr; +} + +bool +nsPrintingProxy::DeallocPPrintSettingsDialogChild(PPrintSettingsDialogChild* aActor) +{ + // The PrintSettingsDialogChild implements refcounting, and + // will take itself out. + return true; +} + +PRemotePrintJobChild* +nsPrintingProxy::AllocPRemotePrintJobChild() +{ + RefPtr<RemotePrintJobChild> remotePrintJob = new RemotePrintJobChild(); + return remotePrintJob.forget().take(); +} + +bool +nsPrintingProxy::DeallocPRemotePrintJobChild(PRemotePrintJobChild* aDoomed) +{ + RemotePrintJobChild* remotePrintJob = static_cast<RemotePrintJobChild*>(aDoomed); + NS_RELEASE(remotePrintJob); + return true; +} diff --git a/embedding/components/printingui/ipc/nsPrintingProxy.h b/embedding/components/printingui/ipc/nsPrintingProxy.h new file mode 100644 index 000000000..6b4d9cf5a --- /dev/null +++ b/embedding/components/printingui/ipc/nsPrintingProxy.h @@ -0,0 +1,57 @@ +/* -*- 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 __nsPrintingProxy_h +#define __nsPrintingProxy_h + +#include "nsIPrintingPromptService.h" +#include "mozilla/embedding/PPrintingChild.h" + +namespace mozilla { +namespace layout { +class PRemotePrintJobChild; +} +} + +class nsPrintingProxy: public nsIPrintingPromptService, + public mozilla::embedding::PPrintingChild +{ + virtual ~nsPrintingProxy(); + +public: + nsPrintingProxy(); + + static already_AddRefed<nsPrintingProxy> GetInstance(); + + nsresult Init(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIPRINTINGPROMPTSERVICE + + nsresult SavePrintSettings(nsIPrintSettings* aPS, + bool aUsePrinterNamePrefix, + uint32_t aFlags); + + virtual PPrintProgressDialogChild* + AllocPPrintProgressDialogChild() override; + + virtual bool + DeallocPPrintProgressDialogChild(PPrintProgressDialogChild* aActor) override; + + virtual PPrintSettingsDialogChild* + AllocPPrintSettingsDialogChild() override; + + virtual bool + DeallocPPrintSettingsDialogChild(PPrintSettingsDialogChild* aActor) override; + + virtual PRemotePrintJobChild* + AllocPRemotePrintJobChild() override; + + virtual bool + DeallocPRemotePrintJobChild(PRemotePrintJobChild* aActor) override; +}; + +#endif + diff --git a/embedding/components/printingui/mac/moz.build b/embedding/components/printingui/mac/moz.build new file mode 100644 index 000000000..aa3047c53 --- /dev/null +++ b/embedding/components/printingui/mac/moz.build @@ -0,0 +1,16 @@ +# -*- 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/. + +UNIFIED_SOURCES += [ + 'nsPrintProgress.cpp', + 'nsPrintProgressParams.cpp', +] + +SOURCES += [ + 'nsPrintingPromptServiceX.mm', +] + +FINAL_LIBRARY = 'xul' diff --git a/embedding/components/printingui/mac/nsPrintProgress.cpp b/embedding/components/printingui/mac/nsPrintProgress.cpp new file mode 100644 index 000000000..0ae0a63d9 --- /dev/null +++ b/embedding/components/printingui/mac/nsPrintProgress.cpp @@ -0,0 +1,213 @@ +/* -*- 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 "nsPrintProgress.h" + +#include "nsIBaseWindow.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIComponentManager.h" +#include "nsPIDOMWindow.h" + +NS_IMPL_ADDREF(nsPrintProgress) +NS_IMPL_RELEASE(nsPrintProgress) + +NS_INTERFACE_MAP_BEGIN(nsPrintProgress) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrintStatusFeedback) + NS_INTERFACE_MAP_ENTRY(nsIPrintProgress) + NS_INTERFACE_MAP_ENTRY(nsIPrintStatusFeedback) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) +NS_INTERFACE_MAP_END_THREADSAFE + + +nsPrintProgress::nsPrintProgress() +{ + m_closeProgress = false; + m_processCanceled = false; + m_pendingStateFlags = -1; + m_pendingStateValue = NS_OK; +} + +nsPrintProgress::~nsPrintProgress() +{ + (void)ReleaseListeners(); +} + +NS_IMETHODIMP nsPrintProgress::OpenProgressDialog(mozIDOMWindowProxy *parent, + const char *dialogURL, + nsISupports *parameters, + nsIObserver *openDialogObserver, + bool *notifyOnOpen) +{ + MOZ_ASSERT_UNREACHABLE("The nsPrintingPromptService::ShowProgress " + "implementation for OS X returns " + "NS_ERROR_NOT_IMPLEMENTED, so we should never get " + "here."); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::CloseProgressDialog(bool forceClose) +{ + MOZ_ASSERT_UNREACHABLE("The nsPrintingPromptService::ShowProgress " + "implementation for OS X returns " + "NS_ERROR_NOT_IMPLEMENTED, so we should never get " + "here."); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::GetPrompter(nsIPrompt **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nullptr; + + if (! m_closeProgress && m_dialog) { + nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(m_dialog); + MOZ_ASSERT(window); + return window->GetPrompter(_retval); + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsPrintProgress::GetProcessCanceledByUser(bool *aProcessCanceledByUser) +{ + NS_ENSURE_ARG_POINTER(aProcessCanceledByUser); + *aProcessCanceledByUser = m_processCanceled; + return NS_OK; +} +NS_IMETHODIMP nsPrintProgress::SetProcessCanceledByUser(bool aProcessCanceledByUser) +{ + m_processCanceled = aProcessCanceledByUser; + OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_OK); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::RegisterListener(nsIWebProgressListener * listener) +{ + if (!listener) //Nothing to do with a null listener! + return NS_OK; + + m_listenerList.AppendObject(listener); + if (m_closeProgress || m_processCanceled) + listener->OnStateChange(nullptr, nullptr, + nsIWebProgressListener::STATE_STOP, NS_OK); + else + { + listener->OnStatusChange(nullptr, nullptr, NS_OK, m_pendingStatus.get()); + if (m_pendingStateFlags != -1) + listener->OnStateChange(nullptr, nullptr, m_pendingStateFlags, m_pendingStateValue); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::UnregisterListener(nsIWebProgressListener *listener) +{ + if (listener) + m_listenerList.RemoveObject(listener); + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::DoneIniting() +{ + if (m_observer) { + m_observer->Observe(nullptr, nullptr, nullptr); + } + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus) +{ + m_pendingStateFlags = aStateFlags; + m_pendingStateValue = aStatus; + + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) +{ + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags) +{ + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage) +{ + if (aMessage && *aMessage) + m_pendingStatus = aMessage; + + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state) +{ + return NS_OK; +} + +nsresult nsPrintProgress::ReleaseListeners() +{ + m_listenerList.Clear(); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::ShowStatusString(const char16_t *status) +{ + return OnStatusChange(nullptr, nullptr, NS_OK, status); +} + +NS_IMETHODIMP nsPrintProgress::StartMeteors() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::StopMeteors() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::ShowProgress(int32_t percent) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::SetDocShell(nsIDocShell *shell, + mozIDOMWindowProxy *window) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::CloseWindow() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + diff --git a/embedding/components/printingui/mac/nsPrintProgress.h b/embedding/components/printingui/mac/nsPrintProgress.h new file mode 100644 index 000000000..938558c3e --- /dev/null +++ b/embedding/components/printingui/mac/nsPrintProgress.h @@ -0,0 +1,44 @@ +/* -*- 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 __nsPrintProgress_h +#define __nsPrintProgress_h + +#include "nsIPrintProgress.h" + +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsIPrintStatusFeedback.h" +#include "nsIObserver.h" +#include "nsString.h" + +class nsPrintProgress : public nsIPrintProgress, public nsIPrintStatusFeedback +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPRINTPROGRESS + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_NSIPRINTSTATUSFEEDBACK + + nsPrintProgress(); + +protected: + virtual ~nsPrintProgress(); + +private: + nsresult ReleaseListeners(); + + bool m_closeProgress; + bool m_processCanceled; + nsString m_pendingStatus; + int32_t m_pendingStateFlags; + nsresult m_pendingStateValue; + // XXX This member is read-only. + nsCOMPtr<mozIDOMWindowProxy> m_dialog; + nsCOMArray<nsIWebProgressListener> m_listenerList; + nsCOMPtr<nsIObserver> m_observer; +}; + +#endif diff --git a/embedding/components/printingui/mac/nsPrintProgressParams.cpp b/embedding/components/printingui/mac/nsPrintProgressParams.cpp new file mode 100644 index 000000000..eba86b298 --- /dev/null +++ b/embedding/components/printingui/mac/nsPrintProgressParams.cpp @@ -0,0 +1,47 @@ +/* -*- 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 "nsPrintProgressParams.h" +#include "nsReadableUtils.h" + + +NS_IMPL_ISUPPORTS(nsPrintProgressParams, nsIPrintProgressParams) + +nsPrintProgressParams::nsPrintProgressParams() +{ +} + +nsPrintProgressParams::~nsPrintProgressParams() +{ +} + +NS_IMETHODIMP nsPrintProgressParams::GetDocTitle(char16_t * *aDocTitle) +{ + NS_ENSURE_ARG(aDocTitle); + + *aDocTitle = ToNewUnicode(mDocTitle); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::SetDocTitle(const char16_t * aDocTitle) +{ + mDocTitle = aDocTitle; + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::GetDocURL(char16_t * *aDocURL) +{ + NS_ENSURE_ARG(aDocURL); + + *aDocURL = ToNewUnicode(mDocURL); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::SetDocURL(const char16_t * aDocURL) +{ + mDocURL = aDocURL; + return NS_OK; +} + diff --git a/embedding/components/printingui/mac/nsPrintProgressParams.h b/embedding/components/printingui/mac/nsPrintProgressParams.h new file mode 100644 index 000000000..65c00d98f --- /dev/null +++ b/embedding/components/printingui/mac/nsPrintProgressParams.h @@ -0,0 +1,28 @@ +/* -*- 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 __nsPrintProgressParams_h +#define __nsPrintProgressParams_h + +#include "nsIPrintProgressParams.h" +#include "nsString.h" + +class nsPrintProgressParams : public nsIPrintProgressParams +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRINTPROGRESSPARAMS + + nsPrintProgressParams(); + +protected: + virtual ~nsPrintProgressParams(); + +private: + nsString mDocTitle; + nsString mDocURL; +}; + +#endif diff --git a/embedding/components/printingui/mac/nsPrintingPromptService.h b/embedding/components/printingui/mac/nsPrintingPromptService.h new file mode 100644 index 000000000..0334db223 --- /dev/null +++ b/embedding/components/printingui/mac/nsPrintingPromptService.h @@ -0,0 +1,43 @@ +/* -*- 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 __nsPrintingPromptService_h +#define __nsPrintingPromptService_h + +// {E042570C-62DE-4bb6-A6E0-798E3C07B4DF} +#define NS_PRINTINGPROMPTSERVICE_CID \ + {0xe042570c, 0x62de, 0x4bb6, { 0xa6, 0xe0, 0x79, 0x8e, 0x3c, 0x7, 0xb4, 0xdf}} +#define NS_PRINTINGPROMPTSERVICE_CONTRACTID \ + "@mozilla.org/embedcomp/printingprompt-service;1" + +#include "nsCOMPtr.h" +#include "nsIPrintingPromptService.h" +#include "nsPIPromptService.h" +#include "nsIWindowWatcher.h" + +// Printing Progress Includes +#include "nsPrintProgress.h" +#include "nsIWebProgressListener.h" + +class nsPrintingPromptService: public nsIPrintingPromptService, + public nsIWebProgressListener +{ +public: + nsPrintingPromptService(); + + nsresult Init(); + + NS_DECL_NSIPRINTINGPROMPTSERVICE + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_ISUPPORTS + +protected: + virtual ~nsPrintingPromptService(); + +private: + nsCOMPtr<nsIPrintProgress> mPrintProgress; +}; + +#endif diff --git a/embedding/components/printingui/mac/nsPrintingPromptServiceX.mm b/embedding/components/printingui/mac/nsPrintingPromptServiceX.mm new file mode 100644 index 000000000..3b956100b --- /dev/null +++ b/embedding/components/printingui/mac/nsPrintingPromptServiceX.mm @@ -0,0 +1,128 @@ +/* -*- 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/. */ + +#include "nsPrintingPromptService.h" + +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" +#include "nsObjCExceptions.h" + +#include "nsIPrintingPromptService.h" +#include "nsIFactory.h" +#include "nsIPrintDialogService.h" +#include "nsPIDOMWindow.h" + +//***************************************************************************** +// nsPrintingPromptService +//***************************************************************************** + +NS_IMPL_ISUPPORTS(nsPrintingPromptService, nsIPrintingPromptService, nsIWebProgressListener) + +nsPrintingPromptService::nsPrintingPromptService() +{ +} + +nsPrintingPromptService::~nsPrintingPromptService() +{ +} + +nsresult nsPrintingPromptService::Init() +{ + return NS_OK; +} + +//***************************************************************************** +// nsPrintingPromptService::nsIPrintingPromptService +//***************************************************************************** + +NS_IMETHODIMP +nsPrintingPromptService::ShowPrintDialog(mozIDOMWindowProxy *parent, nsIWebBrowserPrint *webBrowserPrint, nsIPrintSettings *printSettings) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + nsCOMPtr<nsIPrintDialogService> dlgPrint(do_GetService( + NS_PRINTDIALOGSERVICE_CONTRACTID)); + if (dlgPrint) { + return dlgPrint->Show(nsPIDOMWindowOuter::From(parent), printSettings, + webBrowserPrint); + } + + return NS_ERROR_FAILURE; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowProgress(mozIDOMWindowProxy* parent, + nsIWebBrowserPrint* webBrowserPrint, // ok to be null + nsIPrintSettings* printSettings, // ok to be null + nsIObserver* openDialogObserver, // ok to be null + bool isForPrinting, + nsIWebProgressListener** webProgressListener, + nsIPrintProgressParams** printProgressParams, + bool* notifyOnOpen) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowPageSetup(mozIDOMWindowProxy *parent, nsIPrintSettings *printSettings, nsIObserver *aObs) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + nsCOMPtr<nsIPrintDialogService> dlgPrint(do_GetService( + NS_PRINTDIALOGSERVICE_CONTRACTID)); + if (dlgPrint) { + return dlgPrint->ShowPageSetup(nsPIDOMWindowOuter::From(parent), printSettings); + } + + return NS_ERROR_FAILURE; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowPrinterProperties(mozIDOMWindowProxy *parent, const char16_t *printerName, nsIPrintSettings *printSettings) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +//***************************************************************************** +// nsPrintingPromptService::nsIWebProgressListener +//***************************************************************************** + +NS_IMETHODIMP +nsPrintingPromptService::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus) +{ + return NS_OK; +} + +/* void onProgressChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long aCurSelfProgress, in long aMaxSelfProgress, in long aCurTotalProgress, in long aMaxTotalProgress); */ +NS_IMETHODIMP +nsPrintingPromptService::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) +{ + return NS_OK; +} + +/* void onLocationChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsIURI location, in unsigned long aFlags); */ +NS_IMETHODIMP +nsPrintingPromptService::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags) +{ + return NS_OK; +} + +/* void onStatusChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsresult aStatus, in wstring aMessage); */ +NS_IMETHODIMP +nsPrintingPromptService::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage) +{ + return NS_OK; +} + +/* void onSecurityChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long state); */ +NS_IMETHODIMP +nsPrintingPromptService::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state) +{ + return NS_OK; +} diff --git a/embedding/components/printingui/moz.build b/embedding/components/printingui/moz.build new file mode 100644 index 000000000..ea9ece413 --- /dev/null +++ b/embedding/components/printingui/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/. + +toolkit = CONFIG['MOZ_WIDGET_TOOLKIT'] + +DIRS += ['ipc'] + +if CONFIG['NS_PRINTING']: + if toolkit == 'windows': + DIRS += ['win'] + elif toolkit == 'cocoa': + DIRS += ['mac'] + elif CONFIG['MOZ_PDF_PRINTING']: + DIRS += ['unixshared'] diff --git a/embedding/components/printingui/unixshared/moz.build b/embedding/components/printingui/unixshared/moz.build new file mode 100644 index 000000000..b204f402f --- /dev/null +++ b/embedding/components/printingui/unixshared/moz.build @@ -0,0 +1,13 @@ +# -*- 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/. + +UNIFIED_SOURCES += [ + 'nsPrintingPromptService.cpp', + 'nsPrintProgress.cpp', + 'nsPrintProgressParams.cpp', +] + +FINAL_LIBRARY = 'xul' diff --git a/embedding/components/printingui/unixshared/nsPrintProgress.cpp b/embedding/components/printingui/unixshared/nsPrintProgress.cpp new file mode 100644 index 000000000..1a871c008 --- /dev/null +++ b/embedding/components/printingui/unixshared/nsPrintProgress.cpp @@ -0,0 +1,267 @@ +/* -*- 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 "nsPrintProgress.h" + +#include "nsArray.h" +#include "nsIBaseWindow.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIXULWindow.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIComponentManager.h" +#include "nsPIDOMWindow.h" + + +NS_IMPL_ADDREF(nsPrintProgress) +NS_IMPL_RELEASE(nsPrintProgress) + +NS_INTERFACE_MAP_BEGIN(nsPrintProgress) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrintStatusFeedback) + NS_INTERFACE_MAP_ENTRY(nsIPrintProgress) + NS_INTERFACE_MAP_ENTRY(nsIPrintStatusFeedback) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) +NS_INTERFACE_MAP_END_THREADSAFE + + +nsPrintProgress::nsPrintProgress(nsIPrintSettings* aPrintSettings) +{ + m_closeProgress = false; + m_processCanceled = false; + m_pendingStateFlags = -1; + m_pendingStateValue = NS_OK; + m_PrintSetting = aPrintSettings; +} + +nsPrintProgress::~nsPrintProgress() +{ + (void)ReleaseListeners(); +} + +NS_IMETHODIMP nsPrintProgress::OpenProgressDialog(mozIDOMWindowProxy *parent, + const char *dialogURL, + nsISupports *parameters, + nsIObserver *openDialogObserver, + bool *notifyOnOpen) +{ + *notifyOnOpen = true; + m_observer = openDialogObserver; + nsresult rv = NS_ERROR_FAILURE; + + if (m_dialog) + return NS_ERROR_ALREADY_INITIALIZED; + + if (!dialogURL || !*dialogURL) + return NS_ERROR_INVALID_ARG; + + if (parent) + { + // Set up window.arguments[0]... + nsCOMPtr<nsIMutableArray> array = nsArray::Create(); + + nsCOMPtr<nsISupportsInterfacePointer> ifptr = + do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + ifptr->SetData(static_cast<nsIPrintProgress*>(this)); + ifptr->SetDataIID(&NS_GET_IID(nsIPrintProgress)); + + array->AppendElement(ifptr, /*weak =*/ false); + + array->AppendElement(parameters, /*weak =*/ false); + + // We will set the opener of the dialog to be the nsIDOMWindow for the + // browser XUL window itself, as opposed to the content. That way, the + // progress window has access to the opener. + auto* pParentWindow = nsPIDOMWindowOuter::From(parent); + nsCOMPtr<nsIDocShell> docShell = pParentWindow->GetDocShell(); + NS_ENSURE_STATE(docShell); + + nsCOMPtr<nsIDocShellTreeOwner> owner; + docShell->GetTreeOwner(getter_AddRefs(owner)); + + nsCOMPtr<nsIXULWindow> ownerXULWindow = do_GetInterface(owner); + nsCOMPtr<mozIDOMWindowProxy> ownerWindow = do_GetInterface(ownerXULWindow); + NS_ENSURE_STATE(ownerWindow); + + nsCOMPtr<nsPIDOMWindowOuter> piOwnerWindow = nsPIDOMWindowOuter::From(ownerWindow); + + // Open the dialog. + nsCOMPtr<nsPIDOMWindowOuter> newWindow; + + rv = piOwnerWindow->OpenDialog(NS_ConvertASCIItoUTF16(dialogURL), + NS_LITERAL_STRING("_blank"), + NS_LITERAL_STRING("chrome,titlebar,dependent,centerscreen"), + array, getter_AddRefs(newWindow)); + } + + return rv; +} + +NS_IMETHODIMP nsPrintProgress::CloseProgressDialog(bool forceClose) +{ + m_closeProgress = true; + // XXX Invalid cast of bool to nsresult (bug 778106) + return OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, + (nsresult)forceClose); +} + +NS_IMETHODIMP nsPrintProgress::GetPrompter(nsIPrompt **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nullptr; + + if (! m_closeProgress && m_dialog) { + nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(m_dialog); + MOZ_ASSERT(window); + return window->GetPrompter(_retval); + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsPrintProgress::GetProcessCanceledByUser(bool *aProcessCanceledByUser) +{ + NS_ENSURE_ARG_POINTER(aProcessCanceledByUser); + *aProcessCanceledByUser = m_processCanceled; + return NS_OK; +} +NS_IMETHODIMP nsPrintProgress::SetProcessCanceledByUser(bool aProcessCanceledByUser) +{ + if(m_PrintSetting) + m_PrintSetting->SetIsCancelled(true); + m_processCanceled = aProcessCanceledByUser; + OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_OK); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::RegisterListener(nsIWebProgressListener * listener) +{ + if (!listener) //Nothing to do with a null listener! + return NS_OK; + + m_listenerList.AppendObject(listener); + if (m_closeProgress || m_processCanceled) + listener->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_OK); + else + { + listener->OnStatusChange(nullptr, nullptr, NS_OK, m_pendingStatus.get()); + if (m_pendingStateFlags != -1) + listener->OnStateChange(nullptr, nullptr, m_pendingStateFlags, m_pendingStateValue); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::UnregisterListener(nsIWebProgressListener *listener) +{ + if (listener) + m_listenerList.RemoveObject(listener); + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::DoneIniting() +{ + if (m_observer) { + m_observer->Observe(nullptr, nullptr, nullptr); + } + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus) +{ + m_pendingStateFlags = aStateFlags; + m_pendingStateValue = aStatus; + + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) +{ + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage) +{ + if (aMessage && *aMessage) + m_pendingStatus = aMessage; + + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state) +{ + return NS_OK; +} + +nsresult nsPrintProgress::ReleaseListeners() +{ + m_listenerList.Clear(); + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::ShowStatusString(const char16_t *status) +{ + return OnStatusChange(nullptr, nullptr, NS_OK, status); +} + +NS_IMETHODIMP nsPrintProgress::StartMeteors() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::StopMeteors() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::ShowProgress(int32_t percent) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::SetDocShell(nsIDocShell *shell, mozIDOMWindowProxy *window) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::CloseWindow() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + diff --git a/embedding/components/printingui/unixshared/nsPrintProgress.h b/embedding/components/printingui/unixshared/nsPrintProgress.h new file mode 100644 index 000000000..5b60151c0 --- /dev/null +++ b/embedding/components/printingui/unixshared/nsPrintProgress.h @@ -0,0 +1,46 @@ +/* -*- 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 __nsPrintProgress_h +#define __nsPrintProgress_h + +#include "nsIPrintProgress.h" +#include "nsIPrintingPromptService.h" + +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsIDOMWindow.h" +#include "nsIPrintStatusFeedback.h" +#include "nsIObserver.h" +#include "nsString.h" + +class nsPrintProgress : public nsIPrintProgress, public nsIPrintStatusFeedback +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPRINTPROGRESS + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_NSIPRINTSTATUSFEEDBACK + + explicit nsPrintProgress(nsIPrintSettings* aPrintSettings); + +protected: + virtual ~nsPrintProgress(); + +private: + nsresult ReleaseListeners(); + + bool m_closeProgress; + bool m_processCanceled; + nsString m_pendingStatus; + int32_t m_pendingStateFlags; + nsresult m_pendingStateValue; + nsCOMPtr<nsIDOMWindow> m_dialog; + nsCOMArray<nsIWebProgressListener> m_listenerList; + nsCOMPtr<nsIObserver> m_observer; + nsCOMPtr<nsIPrintSettings> m_PrintSetting; +}; + +#endif diff --git a/embedding/components/printingui/unixshared/nsPrintProgressParams.cpp b/embedding/components/printingui/unixshared/nsPrintProgressParams.cpp new file mode 100644 index 000000000..eba86b298 --- /dev/null +++ b/embedding/components/printingui/unixshared/nsPrintProgressParams.cpp @@ -0,0 +1,47 @@ +/* -*- 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 "nsPrintProgressParams.h" +#include "nsReadableUtils.h" + + +NS_IMPL_ISUPPORTS(nsPrintProgressParams, nsIPrintProgressParams) + +nsPrintProgressParams::nsPrintProgressParams() +{ +} + +nsPrintProgressParams::~nsPrintProgressParams() +{ +} + +NS_IMETHODIMP nsPrintProgressParams::GetDocTitle(char16_t * *aDocTitle) +{ + NS_ENSURE_ARG(aDocTitle); + + *aDocTitle = ToNewUnicode(mDocTitle); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::SetDocTitle(const char16_t * aDocTitle) +{ + mDocTitle = aDocTitle; + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::GetDocURL(char16_t * *aDocURL) +{ + NS_ENSURE_ARG(aDocURL); + + *aDocURL = ToNewUnicode(mDocURL); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::SetDocURL(const char16_t * aDocURL) +{ + mDocURL = aDocURL; + return NS_OK; +} + diff --git a/embedding/components/printingui/unixshared/nsPrintProgressParams.h b/embedding/components/printingui/unixshared/nsPrintProgressParams.h new file mode 100644 index 000000000..839d0e08e --- /dev/null +++ b/embedding/components/printingui/unixshared/nsPrintProgressParams.h @@ -0,0 +1,28 @@ +/* -*- 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 __nsPrintProgressParams_h +#define __nsPrintProgressParams_h + +#include "nsIPrintProgressParams.h" +#include "nsString.h" + +class nsPrintProgressParams : public nsIPrintProgressParams +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRINTPROGRESSPARAMS + + nsPrintProgressParams(); + +protected: + virtual ~nsPrintProgressParams(); + +private: + nsString mDocTitle; + nsString mDocURL; +}; + +#endif diff --git a/embedding/components/printingui/unixshared/nsPrintingPromptService.cpp b/embedding/components/printingui/unixshared/nsPrintingPromptService.cpp new file mode 100644 index 000000000..fd10a4742 --- /dev/null +++ b/embedding/components/printingui/unixshared/nsPrintingPromptService.cpp @@ -0,0 +1,298 @@ +/* -*- Mode: C++; tab-width: 2; 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 "nsPrintingPromptService.h" + +#include "nsArray.h" +#include "nsIComponentManager.h" +#include "nsIDialogParamBlock.h" +#include "nsIDOMWindow.h" +#include "nsIServiceManager.h" +#include "nsISupportsUtils.h" +#include "nsString.h" +#include "nsIPrintDialogService.h" + +// Printing Progress Includes +#include "nsPrintProgress.h" +#include "nsPrintProgressParams.h" + +static const char *kPrintDialogURL = "chrome://global/content/printdialog.xul"; +static const char *kPrintProgressDialogURL = "chrome://global/content/printProgress.xul"; +static const char *kPrtPrvProgressDialogURL = "chrome://global/content/printPreviewProgress.xul"; +static const char *kPageSetupDialogURL = "chrome://global/content/printPageSetup.xul"; +static const char *kPrinterPropertiesURL = "chrome://global/content/printjoboptions.xul"; + +/**************************************************************** + ************************* ParamBlock *************************** + ****************************************************************/ + +class ParamBlock { + +public: + ParamBlock() + { + mBlock = 0; + } + ~ParamBlock() + { + NS_IF_RELEASE(mBlock); + } + nsresult Init() { + return CallCreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID, &mBlock); + } + nsIDialogParamBlock * operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { return mBlock; } + operator nsIDialogParamBlock * () const { return mBlock; } + +private: + nsIDialogParamBlock *mBlock; +}; + +/**************************************************************** + ***************** nsPrintingPromptService ********************** + ****************************************************************/ + +NS_IMPL_ISUPPORTS(nsPrintingPromptService, nsIPrintingPromptService, nsIWebProgressListener) + +nsPrintingPromptService::nsPrintingPromptService() +{ +} + +nsPrintingPromptService::~nsPrintingPromptService() +{ +} + +nsresult +nsPrintingPromptService::Init() +{ + nsresult rv; + mWatcher = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + return rv; +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowPrintDialog(mozIDOMWindowProxy *parent, + nsIWebBrowserPrint *webBrowserPrint, + nsIPrintSettings *printSettings) +{ + NS_ENSURE_ARG(webBrowserPrint); + NS_ENSURE_ARG(printSettings); + + // Try to access a component dialog + nsCOMPtr<nsIPrintDialogService> dlgPrint(do_GetService( + NS_PRINTDIALOGSERVICE_CONTRACTID)); + if (dlgPrint) + return dlgPrint->Show(nsPIDOMWindowOuter::From(parent), + printSettings, webBrowserPrint); + + // Show the built-in dialog instead + ParamBlock block; + nsresult rv = block.Init(); + if (NS_FAILED(rv)) + return rv; + + block->SetInt(0, 0); + return DoDialog(parent, block, webBrowserPrint, printSettings, kPrintDialogURL); +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowProgress(mozIDOMWindowProxy* parent, + nsIWebBrowserPrint* webBrowserPrint, // ok to be null + nsIPrintSettings* printSettings, // ok to be null + nsIObserver* openDialogObserver, // ok to be null + bool isForPrinting, + nsIWebProgressListener** webProgressListener, + nsIPrintProgressParams** printProgressParams, + bool* notifyOnOpen) +{ + NS_ENSURE_ARG(webProgressListener); + NS_ENSURE_ARG(printProgressParams); + NS_ENSURE_ARG(notifyOnOpen); + + *notifyOnOpen = false; + + nsPrintProgress* prtProgress = new nsPrintProgress(printSettings); + mPrintProgress = prtProgress; + mWebProgressListener = prtProgress; + + nsCOMPtr<nsIPrintProgressParams> prtProgressParams = new nsPrintProgressParams(); + + nsCOMPtr<mozIDOMWindowProxy> parentWindow = parent; + + if (mWatcher && !parentWindow) { + mWatcher->GetActiveWindow(getter_AddRefs(parentWindow)); + } + + if (parentWindow) { + mPrintProgress->OpenProgressDialog(parentWindow, + isForPrinting ? kPrintProgressDialogURL : kPrtPrvProgressDialogURL, + prtProgressParams, openDialogObserver, notifyOnOpen); + } + + prtProgressParams.forget(printProgressParams); + NS_ADDREF(*webProgressListener = this); + + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowPageSetup(mozIDOMWindowProxy *parent, + nsIPrintSettings *printSettings, + nsIObserver *aObs) +{ + NS_ENSURE_ARG(printSettings); + + // Try to access a component dialog + nsCOMPtr<nsIPrintDialogService> dlgPrint(do_GetService( + NS_PRINTDIALOGSERVICE_CONTRACTID)); + if (dlgPrint) + return dlgPrint->ShowPageSetup(nsPIDOMWindowOuter::From(parent), + printSettings); + + ParamBlock block; + nsresult rv = block.Init(); + if (NS_FAILED(rv)) + return rv; + + block->SetInt(0, 0); + return DoDialog(parent, block, nullptr, printSettings, kPageSetupDialogURL); +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowPrinterProperties(mozIDOMWindowProxy *parent, + const char16_t *printerName, + nsIPrintSettings *printSettings) +{ + /* fixme: We simply ignore the |aPrinter| argument here + * We should get the supported printer attributes from the printer and + * populate the print job options dialog with these data instead of using + * the "default set" here. + * However, this requires changes on all platforms and is another big chunk + * of patches ... ;-( + */ + NS_ENSURE_ARG(printerName); + NS_ENSURE_ARG(printSettings); + + ParamBlock block; + nsresult rv = block.Init(); + if (NS_FAILED(rv)) + return rv; + + block->SetInt(0, 0); + return DoDialog(parent, block, nullptr, printSettings, kPrinterPropertiesURL); + +} + +nsresult +nsPrintingPromptService::DoDialog(mozIDOMWindowProxy *aParent, + nsIDialogParamBlock *aParamBlock, + nsIWebBrowserPrint *aWebBrowserPrint, + nsIPrintSettings* aPS, + const char *aChromeURL) +{ + NS_ENSURE_ARG(aParamBlock); + NS_ENSURE_ARG(aPS); + NS_ENSURE_ARG(aChromeURL); + + if (!mWatcher) + return NS_ERROR_FAILURE; + + // get a parent, if at all possible + // (though we'd rather this didn't fail, it's OK if it does. so there's + // no failure or null check.) + nsCOMPtr<mozIDOMWindowProxy> activeParent; + if (!aParent) + { + mWatcher->GetActiveWindow(getter_AddRefs(activeParent)); + aParent = activeParent; + } + + // create a nsIMutableArray of the parameters + // being passed to the window + nsCOMPtr<nsIMutableArray> array = nsArray::Create(); + + nsCOMPtr<nsISupports> psSupports(do_QueryInterface(aPS)); + NS_ASSERTION(psSupports, "PrintSettings must be a supports"); + array->AppendElement(psSupports, /*weak =*/ false); + + if (aWebBrowserPrint) { + nsCOMPtr<nsISupports> wbpSupports(do_QueryInterface(aWebBrowserPrint)); + NS_ASSERTION(wbpSupports, "nsIWebBrowserPrint must be a supports"); + array->AppendElement(wbpSupports, /*weak =*/ false); + } + + nsCOMPtr<nsISupports> blkSupps(do_QueryInterface(aParamBlock)); + NS_ASSERTION(blkSupps, "IOBlk must be a supports"); + array->AppendElement(blkSupps, /*weak =*/ false); + + nsCOMPtr<mozIDOMWindowProxy> dialog; + nsresult rv = mWatcher->OpenWindow(aParent, aChromeURL, "_blank", + "centerscreen,chrome,modal,titlebar", array, + getter_AddRefs(dialog)); + + // if aWebBrowserPrint is not null then we are printing + // so we want to pass back NS_ERROR_ABORT on cancel + if (NS_SUCCEEDED(rv) && aWebBrowserPrint) + { + int32_t status; + aParamBlock->GetInt(0, &status); + return status == 0?NS_ERROR_ABORT:NS_OK; + } + + return rv; +} + +////////////////////////////////////////////////////////////////////// +// nsIWebProgressListener +////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsPrintingPromptService::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus) +{ + if ((aStateFlags & STATE_STOP) && mWebProgressListener) { + mWebProgressListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); + if (mPrintProgress) { + mPrintProgress->CloseProgressDialog(true); + } + mPrintProgress = nullptr; + mWebProgressListener = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingPromptService::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) +{ + if (mWebProgressListener) { + return mWebProgressListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress); + } + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingPromptService::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags) +{ + if (mWebProgressListener) { + return mWebProgressListener->OnLocationChange(aWebProgress, aRequest, location, aFlags); + } + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingPromptService::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage) +{ + if (mWebProgressListener) { + return mWebProgressListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); + } + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingPromptService::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state) +{ + if (mWebProgressListener) { + return mWebProgressListener->OnSecurityChange(aWebProgress, aRequest, state); + } + return NS_OK; +} diff --git a/embedding/components/printingui/unixshared/nsPrintingPromptService.h b/embedding/components/printingui/unixshared/nsPrintingPromptService.h new file mode 100644 index 000000000..cc0c76929 --- /dev/null +++ b/embedding/components/printingui/unixshared/nsPrintingPromptService.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 __nsPrintingPromptService_h +#define __nsPrintingPromptService_h + +// {E042570C-62DE-4bb6-A6E0-798E3C07B4DF} +#define NS_PRINTINGPROMPTSERVICE_CID \ + {0xe042570c, 0x62de, 0x4bb6, { 0xa6, 0xe0, 0x79, 0x8e, 0x3c, 0x7, 0xb4, 0xdf}} +#define NS_PRINTINGPROMPTSERVICE_CONTRACTID \ + "@mozilla.org/embedcomp/printingprompt-service;1" + +#include "nsCOMPtr.h" +#include "nsIPrintingPromptService.h" +#include "nsPIPromptService.h" +#include "nsIWindowWatcher.h" + +// Printing Progress Includes +#include "nsPrintProgress.h" +#include "nsPrintProgressParams.h" +#include "nsIWebProgressListener.h" + +class nsIDOMWindow; +class nsIDialogParamBlock; + +class nsPrintingPromptService: public nsIPrintingPromptService, + public nsIWebProgressListener +{ + +public: + + nsPrintingPromptService(); + + nsresult Init(); + + NS_DECL_NSIPRINTINGPROMPTSERVICE + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_ISUPPORTS + +protected: + virtual ~nsPrintingPromptService(); + +private: + nsresult DoDialog(mozIDOMWindowProxy *aParent, + nsIDialogParamBlock *aParamBlock, + nsIWebBrowserPrint *aWebBrowserPrint, + nsIPrintSettings* aPS, + const char *aChromeURL); + + nsCOMPtr<nsIWindowWatcher> mWatcher; + nsCOMPtr<nsIPrintProgress> mPrintProgress; + nsCOMPtr<nsIWebProgressListener> mWebProgressListener; +}; + +#endif + diff --git a/embedding/components/printingui/win/moz.build b/embedding/components/printingui/win/moz.build new file mode 100644 index 000000000..fe7b11b2d --- /dev/null +++ b/embedding/components/printingui/win/moz.build @@ -0,0 +1,18 @@ +# -*- 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/. + +UNIFIED_SOURCES += [ + 'nsPrintDialogUtil.cpp', + 'nsPrintingPromptService.cpp', + 'nsPrintProgress.cpp', + 'nsPrintProgressParams.cpp', +] + +EXPORTS += [ + 'nsPrintDialogUtil.h', +] + +FINAL_LIBRARY = 'xul' diff --git a/embedding/components/printingui/win/nsPrintDialogUtil.cpp b/embedding/components/printingui/win/nsPrintDialogUtil.cpp new file mode 100644 index 000000000..896c58e85 --- /dev/null +++ b/embedding/components/printingui/win/nsPrintDialogUtil.cpp @@ -0,0 +1,854 @@ +/* -*- 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/. */ + +/* ------------------------------------------------------------------- +To Build This: + + You need to add this to the the makefile.win in mozilla/dom/base: + + .\$(OBJDIR)\nsFlyOwnPrintDialog.obj \ + + + And this to the makefile.win in mozilla/content/build: + +WIN_LIBS= \ + winspool.lib \ + comctl32.lib \ + comdlg32.lib + +---------------------------------------------------------------------- */ + +#include "plstr.h" +#include <windows.h> +#include <tchar.h> + +#include <unknwn.h> +#include <commdlg.h> + +#include "nsIWebBrowserPrint.h" +#include "nsString.h" +#include "nsIServiceManager.h" +#include "nsReadableUtils.h" +#include "nsIPrintSettings.h" +#include "nsIPrintSettingsWin.h" +#include "nsIPrinterEnumerator.h" + +#include "nsRect.h" + +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" + +#include "nsCRT.h" +#include "prenv.h" /* for PR_GetEnv */ + +#include <windows.h> +#include <winspool.h> + +// For Localization +#include "nsIStringBundle.h" + +// For NS_CopyUnicodeToNative +#include "nsNativeCharsetUtils.h" + +// This is for extending the dialog +#include <dlgs.h> + +#include "nsWindowsHelpers.h" +#include "WinUtils.h" + +// Default labels for the radio buttons +static const char* kAsLaidOutOnScreenStr = "As &laid out on the screen"; +static const char* kTheSelectedFrameStr = "The selected &frame"; +static const char* kEachFrameSeparately = "&Each frame separately"; + + +//----------------------------------------------- +// Global Data +//----------------------------------------------- +// Identifies which new radio btn was cliked on +static UINT gFrameSelectedRadioBtn = 0; + +// Indicates whether the native print dialog was successfully extended +static bool gDialogWasExtended = false; + +#define PRINTDLG_PROPERTIES "chrome://global/locale/printdialog.properties" + +static HWND gParentWnd = nullptr; + +//---------------------------------------------------------------------------------- +// Return localized bundle for resource strings +static nsresult +GetLocalizedBundle(const char * aPropFileName, nsIStringBundle** aStrBundle) +{ + NS_ENSURE_ARG_POINTER(aPropFileName); + NS_ENSURE_ARG_POINTER(aStrBundle); + + nsresult rv; + nsCOMPtr<nsIStringBundle> bundle; + + + // Create bundle + nsCOMPtr<nsIStringBundleService> stringService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && stringService) { + rv = stringService->CreateBundle(aPropFileName, aStrBundle); + } + + return rv; +} + +//-------------------------------------------------------- +// Return localized string +static nsresult +GetLocalizedString(nsIStringBundle* aStrBundle, const char* aKey, nsString& oVal) +{ + NS_ENSURE_ARG_POINTER(aStrBundle); + NS_ENSURE_ARG_POINTER(aKey); + + // Determine default label from string bundle + nsXPIDLString valUni; + nsAutoString key; + key.AssignWithConversion(aKey); + nsresult rv = aStrBundle->GetStringFromName(key.get(), getter_Copies(valUni)); + if (NS_SUCCEEDED(rv) && valUni) { + oVal.Assign(valUni); + } else { + oVal.Truncate(); + } + return rv; +} + +//-------------------------------------------------------- +// Set a multi-byte string in the control +static void SetTextOnWnd(HWND aControl, const nsString& aStr) +{ + nsAutoCString text; + if (NS_SUCCEEDED(NS_CopyUnicodeToNative(aStr, text))) { + ::SetWindowText(aControl, text.get()); + } +} + +//-------------------------------------------------------- +// Will get the control and localized string by "key" +static void SetText(HWND aParent, + UINT aId, + nsIStringBundle* aStrBundle, + const char* aKey) +{ + HWND wnd = GetDlgItem (aParent, aId); + if (!wnd) { + return; + } + nsAutoString str; + nsresult rv = GetLocalizedString(aStrBundle, aKey, str); + if (NS_SUCCEEDED(rv)) { + SetTextOnWnd(wnd, str); + } +} + +//-------------------------------------------------------- +static void SetRadio(HWND aParent, + UINT aId, + bool aIsSet, + bool isEnabled = true) +{ + HWND wnd = ::GetDlgItem (aParent, aId); + if (!wnd) { + return; + } + if (!isEnabled) { + ::EnableWindow(wnd, FALSE); + return; + } + ::EnableWindow(wnd, TRUE); + ::SendMessage(wnd, BM_SETCHECK, (WPARAM)aIsSet, (LPARAM)0); +} + +//-------------------------------------------------------- +static void SetRadioOfGroup(HWND aDlg, int aRadId) +{ + int radioIds[] = {rad4, rad5, rad6}; + int numRads = 3; + + for (int i=0;i<numRads;i++) { + HWND radWnd = ::GetDlgItem(aDlg, radioIds[i]); + if (radWnd != nullptr) { + ::SendMessage(radWnd, BM_SETCHECK, (WPARAM)(radioIds[i] == aRadId), (LPARAM)0); + } + } +} + +//-------------------------------------------------------- +typedef struct { + const char * mKeyStr; + long mKeyId; +} PropKeyInfo; + +// These are the control ids used in the dialog and +// defined by MS-Windows in commdlg.h +static PropKeyInfo gAllPropKeys[] = { + {"printFramesTitleWindows", grp3}, + {"asLaidOutWindows", rad4}, + {"selectedFrameWindows", rad5}, + {"separateFramesWindows", rad6}, + {nullptr, 0}}; + +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------------------------------------------------------- +// Get the absolute coords of the child windows relative +// to its parent window +static void GetLocalRect(HWND aWnd, RECT& aRect, HWND aParent) +{ + ::GetWindowRect(aWnd, &aRect); + + // MapWindowPoints converts screen coordinates to client coordinates. + // It works correctly in both left-to-right and right-to-left windows. + ::MapWindowPoints(nullptr, aParent, (LPPOINT)&aRect, 2); +} + +//-------------------------------------------------------- +// Show or Hide the control +static void Show(HWND aWnd, bool bState) +{ + if (aWnd) { + ::ShowWindow(aWnd, bState?SW_SHOW:SW_HIDE); + } +} + +//-------------------------------------------------------- +// Create a child window "control" +static HWND CreateControl(LPCTSTR aType, + DWORD aStyle, + HINSTANCE aHInst, + HWND aHdlg, + int aId, + const nsAString& aStr, + const nsIntRect& aRect) +{ + nsAutoCString str; + if (NS_FAILED(NS_CopyUnicodeToNative(aStr, str))) + return nullptr; + + HWND hWnd = ::CreateWindow (aType, str.get(), + WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | aStyle, + aRect.x, aRect.y, aRect.width, aRect.height, + (HWND)aHdlg, (HMENU)(intptr_t)aId, + aHInst, nullptr); + if (hWnd == nullptr) return nullptr; + + // get the native font for the dialog and + // set it into the new control + HFONT hFont = (HFONT)::SendMessage(aHdlg, WM_GETFONT, (WPARAM)0, (LPARAM)0); + if (hFont != nullptr) { + ::SendMessage(hWnd, WM_SETFONT, (WPARAM) hFont, (LPARAM)0); + } + return hWnd; +} + +//-------------------------------------------------------- +// Create a Radio Button +static HWND CreateRadioBtn(HINSTANCE aHInst, + HWND aHdlg, + int aId, + const char* aStr, + const nsIntRect& aRect) +{ + nsString cStr; + cStr.AssignWithConversion(aStr); + return CreateControl("BUTTON", BS_RADIOBUTTON, aHInst, aHdlg, aId, cStr, aRect); +} + +//-------------------------------------------------------- +// Create a Group Box +static HWND CreateGroupBox(HINSTANCE aHInst, + HWND aHdlg, + int aId, + const nsAString& aStr, + const nsIntRect& aRect) +{ + return CreateControl("BUTTON", BS_GROUPBOX, aHInst, aHdlg, aId, aStr, aRect); +} + +//-------------------------------------------------------- +// Localizes and initializes the radio buttons and group +static void InitializeExtendedDialog(HWND hdlg, int16_t aHowToEnableFrameUI) +{ + MOZ_ASSERT(aHowToEnableFrameUI != nsIPrintSettings::kFrameEnableNone, + "should not be called"); + + // Localize the new controls in the print dialog + nsCOMPtr<nsIStringBundle> strBundle; + if (NS_SUCCEEDED(GetLocalizedBundle(PRINTDLG_PROPERTIES, getter_AddRefs(strBundle)))) { + int32_t i = 0; + while (gAllPropKeys[i].mKeyStr != nullptr) { + SetText(hdlg, gAllPropKeys[i].mKeyId, strBundle, gAllPropKeys[i].mKeyStr); + i++; + } + } + + // Set up radio buttons + if (aHowToEnableFrameUI == nsIPrintSettings::kFrameEnableAll) { + SetRadio(hdlg, rad4, false); + SetRadio(hdlg, rad5, true); + SetRadio(hdlg, rad6, false); + // set default so user doesn't have to actually press on it + gFrameSelectedRadioBtn = rad5; + + } else { // nsIPrintSettings::kFrameEnableAsIsAndEach + SetRadio(hdlg, rad4, false); + SetRadio(hdlg, rad5, false, false); + SetRadio(hdlg, rad6, true); + // set default so user doesn't have to actually press on it + gFrameSelectedRadioBtn = rad6; + } +} + + +//-------------------------------------------------------- +// Special Hook Procedure for handling the print dialog messages +static UINT CALLBACK PrintHookProc(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam) +{ + + if (uiMsg == WM_COMMAND) { + UINT id = LOWORD(wParam); + if (id == rad4 || id == rad5 || id == rad6) { + gFrameSelectedRadioBtn = id; + SetRadioOfGroup(hdlg, id); + } + + } else if (uiMsg == WM_INITDIALOG) { + PRINTDLG * printDlg = (PRINTDLG *)lParam; + if (printDlg == nullptr) return 0L; + + int16_t howToEnableFrameUI = (int16_t)printDlg->lCustData; + // don't add frame options if they would be disabled anyway + // because there are no frames + if (howToEnableFrameUI == nsIPrintSettings::kFrameEnableNone) + return TRUE; + + HINSTANCE hInst = (HINSTANCE)::GetWindowLongPtr(hdlg, GWLP_HINSTANCE); + if (hInst == nullptr) return 0L; + + // Start by getting the local rects of several of the controls + // so we can calculate where the new controls are + HWND wnd = ::GetDlgItem(hdlg, grp1); + if (wnd == nullptr) return 0L; + RECT dlgRect; + GetLocalRect(wnd, dlgRect, hdlg); + + wnd = ::GetDlgItem(hdlg, rad1); // this is the top control "All" + if (wnd == nullptr) return 0L; + RECT rad1Rect; + GetLocalRect(wnd, rad1Rect, hdlg); + + wnd = ::GetDlgItem(hdlg, rad2); // this is the bottom control "Selection" + if (wnd == nullptr) return 0L; + RECT rad2Rect; + GetLocalRect(wnd, rad2Rect, hdlg); + + wnd = ::GetDlgItem(hdlg, rad3); // this is the middle control "Pages" + if (wnd == nullptr) return 0L; + RECT rad3Rect; + GetLocalRect(wnd, rad3Rect, hdlg); + + HWND okWnd = ::GetDlgItem(hdlg, IDOK); + if (okWnd == nullptr) return 0L; + RECT okRect; + GetLocalRect(okWnd, okRect, hdlg); + + wnd = ::GetDlgItem(hdlg, grp4); // this is the "Print range" groupbox + if (wnd == nullptr) return 0L; + RECT prtRect; + GetLocalRect(wnd, prtRect, hdlg); + + + // calculate various different "gaps" for layout purposes + + int rbGap = rad3Rect.top - rad1Rect.bottom; // gap between radiobtns + int grpBotGap = dlgRect.bottom - rad2Rect.bottom; // gap from bottom rb to bottom of grpbox + int grpGap = dlgRect.top - prtRect.bottom ; // gap between group boxes + int top = dlgRect.bottom + grpGap; + int radHgt = rad1Rect.bottom - rad1Rect.top + 1; // top of new group box + int y = top+(rad1Rect.top-dlgRect.top); // starting pos of first radio + int rbWidth = dlgRect.right - rad1Rect.left - 5; // measure from rb left to the edge of the groupbox + // (5 is arbitrary) + nsIntRect rect; + + // Create and position the radio buttons + // + // If any one control cannot be created then + // hide the others and bail out + // + rect.SetRect(rad1Rect.left, y, rbWidth,radHgt); + HWND rad4Wnd = CreateRadioBtn(hInst, hdlg, rad4, kAsLaidOutOnScreenStr, rect); + if (rad4Wnd == nullptr) return 0L; + y += radHgt + rbGap; + + rect.SetRect(rad1Rect.left, y, rbWidth, radHgt); + HWND rad5Wnd = CreateRadioBtn(hInst, hdlg, rad5, kTheSelectedFrameStr, rect); + if (rad5Wnd == nullptr) { + Show(rad4Wnd, FALSE); // hide + return 0L; + } + y += radHgt + rbGap; + + rect.SetRect(rad1Rect.left, y, rbWidth, radHgt); + HWND rad6Wnd = CreateRadioBtn(hInst, hdlg, rad6, kEachFrameSeparately, rect); + if (rad6Wnd == nullptr) { + Show(rad4Wnd, FALSE); // hide + Show(rad5Wnd, FALSE); // hide + return 0L; + } + y += radHgt + grpBotGap; + + // Create and position the group box + rect.SetRect (dlgRect.left, top, dlgRect.right-dlgRect.left+1, y-top+1); + HWND grpBoxWnd = CreateGroupBox(hInst, hdlg, grp3, NS_LITERAL_STRING("Print Frame"), rect); + if (grpBoxWnd == nullptr) { + Show(rad4Wnd, FALSE); // hide + Show(rad5Wnd, FALSE); // hide + Show(rad6Wnd, FALSE); // hide + return 0L; + } + + // Here we figure out the old height of the dlg + // then figure its gap from the old grpbx to the bottom + // then size the dlg + RECT pr, cr; + ::GetWindowRect(hdlg, &pr); + ::GetClientRect(hdlg, &cr); + + int dlgHgt = (cr.bottom - cr.top) + 1; + int bottomGap = dlgHgt - okRect.bottom; + pr.bottom += (dlgRect.bottom-dlgRect.top) + grpGap + 1 - (dlgHgt-dlgRect.bottom) + bottomGap; + + ::SetWindowPos(hdlg, nullptr, pr.left, pr.top, pr.right-pr.left+1, pr.bottom-pr.top+1, + SWP_NOMOVE|SWP_NOREDRAW|SWP_NOZORDER); + + // figure out the new height of the dialog + ::GetClientRect(hdlg, &cr); + dlgHgt = (cr.bottom - cr.top) + 1; + + // Reposition the OK and Cancel btns + int okHgt = okRect.bottom - okRect.top + 1; + ::SetWindowPos(okWnd, nullptr, okRect.left, dlgHgt-bottomGap-okHgt, 0, 0, + SWP_NOSIZE|SWP_NOREDRAW|SWP_NOZORDER); + + HWND cancelWnd = ::GetDlgItem(hdlg, IDCANCEL); + if (cancelWnd == nullptr) return 0L; + + RECT cancelRect; + GetLocalRect(cancelWnd, cancelRect, hdlg); + int cancelHgt = cancelRect.bottom - cancelRect.top + 1; + ::SetWindowPos(cancelWnd, nullptr, cancelRect.left, dlgHgt-bottomGap-cancelHgt, 0, 0, + SWP_NOSIZE|SWP_NOREDRAW|SWP_NOZORDER); + + // localize and initialize the groupbox and radiobuttons + InitializeExtendedDialog(hdlg, howToEnableFrameUI); + + // Looks like we were able to extend the dialog + gDialogWasExtended = true; + return TRUE; + } + return 0L; +} + +//---------------------------------------------------------------------------------- +// Returns a Global Moveable Memory Handle to a DevMode +// from the Printer by the name of aPrintName +// +// NOTE: +// This function assumes that aPrintName has already been converted from +// unicode +// +static nsReturnRef<nsHGLOBAL> +CreateGlobalDevModeAndInit(const nsXPIDLString& aPrintName, + nsIPrintSettings* aPS) +{ + nsHPRINTER hPrinter = nullptr; + // const cast kludge for silly Win32 api's + LPWSTR printName = const_cast<wchar_t*>(static_cast<const wchar_t*>(aPrintName.get())); + BOOL status = ::OpenPrinterW(printName, &hPrinter, nullptr); + if (!status) { + return nsReturnRef<nsHGLOBAL>(); + } + + // Make sure hPrinter is closed on all paths + nsAutoPrinter autoPrinter(hPrinter); + + // Get the buffer size + LONG needed = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, nullptr, + nullptr, 0); + if (needed < 0) { + return nsReturnRef<nsHGLOBAL>(); + } + + // Allocate a buffer of the correct size. + nsAutoDevMode newDevMode((LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, + needed)); + if (!newDevMode) { + return nsReturnRef<nsHGLOBAL>(); + } + + nsHGLOBAL hDevMode = ::GlobalAlloc(GHND, needed); + nsAutoGlobalMem globalDevMode(hDevMode); + if (!hDevMode) { + return nsReturnRef<nsHGLOBAL>(); + } + + LONG ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, newDevMode, + nullptr, DM_OUT_BUFFER); + if (ret != IDOK) { + return nsReturnRef<nsHGLOBAL>(); + } + + // Lock memory and copy contents from DEVMODE (current printer) + // to Global Memory DEVMODE + LPDEVMODEW devMode = (DEVMODEW *)::GlobalLock(hDevMode); + if (!devMode) { + return nsReturnRef<nsHGLOBAL>(); + } + + memcpy(devMode, newDevMode.get(), needed); + // Initialize values from the PrintSettings + nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS); + MOZ_ASSERT(psWin); + psWin->CopyToNative(devMode); + + // Sets back the changes we made to the DevMode into the Printer Driver + ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, devMode, devMode, + DM_IN_BUFFER | DM_OUT_BUFFER); + if (ret != IDOK) { + ::GlobalUnlock(hDevMode); + return nsReturnRef<nsHGLOBAL>(); + } + + ::GlobalUnlock(hDevMode); + + return globalDevMode.out(); +} + +//------------------------------------------------------------------ +// helper +static void GetDefaultPrinterNameFromGlobalPrinters(nsXPIDLString &printerName) +{ + nsCOMPtr<nsIPrinterEnumerator> prtEnum = do_GetService("@mozilla.org/gfx/printerenumerator;1"); + if (prtEnum) { + prtEnum->GetDefaultPrinterName(getter_Copies(printerName)); + } +} + +// Determine whether we have a completely native dialog +// or whether we cshould extend it +static bool ShouldExtendPrintDialog() +{ + nsresult rv; + nsCOMPtr<nsIPrefService> prefs = + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, true); + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv, true); + + bool result; + rv = prefBranch->GetBoolPref("print.extend_native_print_dialog", &result); + NS_ENSURE_SUCCESS(rv, true); + return result; +} + +//------------------------------------------------------------------ +// Displays the native Print Dialog +static nsresult +ShowNativePrintDialog(HWND aHWnd, + nsIPrintSettings* aPrintSettings) +{ + //NS_ENSURE_ARG_POINTER(aHWnd); + NS_ENSURE_ARG_POINTER(aPrintSettings); + + gDialogWasExtended = false; + + // Get the Print Name to be used + nsXPIDLString printerName; + aPrintSettings->GetPrinterName(getter_Copies(printerName)); + + // If there is no name then use the default printer + if (printerName.IsEmpty()) { + GetDefaultPrinterNameFromGlobalPrinters(printerName); + } else { + HANDLE hPrinter = nullptr; + if(!::OpenPrinterW(const_cast<wchar_t*>(static_cast<const wchar_t*>(printerName.get())), + &hPrinter, nullptr)) { + // If the last used printer is not found, we should use default printer. + GetDefaultPrinterNameFromGlobalPrinters(printerName); + } else { + ::ClosePrinter(hPrinter); + } + } + + // Now create a DEVNAMES struct so the the dialog is initialized correctly. + + uint32_t len = printerName.Length(); + nsHGLOBAL hDevNames = ::GlobalAlloc(GHND, sizeof(wchar_t) * (len + 1) + + sizeof(DEVNAMES)); + nsAutoGlobalMem autoDevNames(hDevNames); + if (!hDevNames) { + return NS_ERROR_OUT_OF_MEMORY; + } + + DEVNAMES* pDevNames = (DEVNAMES*)::GlobalLock(hDevNames); + if (!pDevNames) { + return NS_ERROR_FAILURE; + } + pDevNames->wDriverOffset = sizeof(DEVNAMES)/sizeof(wchar_t); + pDevNames->wDeviceOffset = sizeof(DEVNAMES)/sizeof(wchar_t); + pDevNames->wOutputOffset = sizeof(DEVNAMES)/sizeof(wchar_t)+len; + pDevNames->wDefault = 0; + + memcpy(pDevNames+1, printerName, (len + 1) * sizeof(wchar_t)); + ::GlobalUnlock(hDevNames); + + // Create a Moveable Memory Object that holds a new DevMode + // from the Printer Name + // The PRINTDLG.hDevMode requires that it be a moveable memory object + // NOTE: autoDevMode is automatically freed when any error occurred + nsAutoGlobalMem autoDevMode(CreateGlobalDevModeAndInit(printerName, aPrintSettings)); + + // Prepare to Display the Print Dialog + PRINTDLGW prntdlg; + memset(&prntdlg, 0, sizeof(PRINTDLGW)); + + prntdlg.lStructSize = sizeof(prntdlg); + prntdlg.hwndOwner = aHWnd; + prntdlg.hDevMode = autoDevMode.get(); + prntdlg.hDevNames = hDevNames; + prntdlg.hDC = nullptr; + prntdlg.Flags = PD_ALLPAGES | PD_RETURNIC | + PD_USEDEVMODECOPIESANDCOLLATE | PD_COLLATE; + + // if there is a current selection then enable the "Selection" radio button + int16_t howToEnableFrameUI = nsIPrintSettings::kFrameEnableNone; + bool isOn; + aPrintSettings->GetPrintOptions(nsIPrintSettings::kEnableSelectionRB, &isOn); + if (!isOn) { + prntdlg.Flags |= PD_NOSELECTION; + } + aPrintSettings->GetHowToEnableFrameUI(&howToEnableFrameUI); + + int32_t pg = 1; + aPrintSettings->GetStartPageRange(&pg); + prntdlg.nFromPage = pg; + + aPrintSettings->GetEndPageRange(&pg); + prntdlg.nToPage = pg; + + prntdlg.nMinPage = 1; + prntdlg.nMaxPage = 0xFFFF; + prntdlg.nCopies = 1; + prntdlg.lpfnSetupHook = nullptr; + prntdlg.lpSetupTemplateName = nullptr; + prntdlg.hPrintTemplate = nullptr; + prntdlg.hSetupTemplate = nullptr; + + prntdlg.hInstance = nullptr; + prntdlg.lpPrintTemplateName = nullptr; + + if (!ShouldExtendPrintDialog()) { + prntdlg.lCustData = 0; + prntdlg.lpfnPrintHook = nullptr; + } else { + // Set up print dialog "hook" procedure for extending the dialog + prntdlg.lCustData = (DWORD)howToEnableFrameUI; + prntdlg.lpfnPrintHook = (LPPRINTHOOKPROC)PrintHookProc; + prntdlg.Flags |= PD_ENABLEPRINTHOOK; + } + + BOOL result; + { + mozilla::widget::WinUtils::AutoSystemDpiAware dpiAwareness; + result = ::PrintDlgW(&prntdlg); + } + + if (TRUE == result) { + // check to make sure we don't have any nullptr pointers + NS_ENSURE_TRUE(aPrintSettings && prntdlg.hDevMode, NS_ERROR_FAILURE); + + if (prntdlg.hDevNames == nullptr) { + return NS_ERROR_FAILURE; + } + // Lock the deviceNames and check for nullptr + DEVNAMES *devnames = (DEVNAMES *)::GlobalLock(prntdlg.hDevNames); + if (devnames == nullptr) { + return NS_ERROR_FAILURE; + } + + char16_t* device = &(((char16_t *)devnames)[devnames->wDeviceOffset]); + char16_t* driver = &(((char16_t *)devnames)[devnames->wDriverOffset]); + + // Check to see if the "Print To File" control is checked + // then take the name from devNames and set it in the PrintSettings + // + // NOTE: + // As per Microsoft SDK documentation the returned value offset from + // devnames->wOutputOffset is either "FILE:" or nullptr + // if the "Print To File" checkbox is checked it MUST be "FILE:" + // We assert as an extra safety check. + if (prntdlg.Flags & PD_PRINTTOFILE) { + char16ptr_t fileName = &(((wchar_t *)devnames)[devnames->wOutputOffset]); + NS_ASSERTION(wcscmp(fileName, L"FILE:") == 0, "FileName must be `FILE:`"); + aPrintSettings->SetToFileName(fileName); + aPrintSettings->SetPrintToFile(true); + } else { + // clear "print to file" info + aPrintSettings->SetPrintToFile(false); + aPrintSettings->SetToFileName(nullptr); + } + + nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings)); + if (!psWin) { + return NS_ERROR_FAILURE; + } + + // Setup local Data members + psWin->SetDeviceName(device); + psWin->SetDriverName(driver); + +#if defined(DEBUG_rods) || defined(DEBUG_dcone) + wprintf(L"printer: driver %s, device %s flags: %d\n", driver, device, prntdlg.Flags); +#endif + // fill the print options with the info from the dialog + + aPrintSettings->SetPrinterName(device); + + if (prntdlg.Flags & PD_SELECTION) { + aPrintSettings->SetPrintRange(nsIPrintSettings::kRangeSelection); + + } else if (prntdlg.Flags & PD_PAGENUMS) { + aPrintSettings->SetPrintRange(nsIPrintSettings::kRangeSpecifiedPageRange); + aPrintSettings->SetStartPageRange(prntdlg.nFromPage); + aPrintSettings->SetEndPageRange(prntdlg.nToPage); + + } else { // (prntdlg.Flags & PD_ALLPAGES) + aPrintSettings->SetPrintRange(nsIPrintSettings::kRangeAllPages); + } + + if (howToEnableFrameUI != nsIPrintSettings::kFrameEnableNone) { + // make sure the dialog got extended + if (gDialogWasExtended) { + // check to see about the frame radio buttons + switch (gFrameSelectedRadioBtn) { + case rad4: + aPrintSettings->SetPrintFrameType(nsIPrintSettings::kFramesAsIs); + break; + case rad5: + aPrintSettings->SetPrintFrameType(nsIPrintSettings::kSelectedFrame); + break; + case rad6: + aPrintSettings->SetPrintFrameType(nsIPrintSettings::kEachFrameSep); + break; + } // switch + } else { + // if it didn't get extended then have it default to printing + // each frame separately + aPrintSettings->SetPrintFrameType(nsIPrintSettings::kEachFrameSep); + } + } else { + aPrintSettings->SetPrintFrameType(nsIPrintSettings::kNoFrames); + } + // Unlock DeviceNames + ::GlobalUnlock(prntdlg.hDevNames); + + // Transfer the settings from the native data to the PrintSettings + LPDEVMODEW devMode = (LPDEVMODEW)::GlobalLock(prntdlg.hDevMode); + if (!devMode || !prntdlg.hDC) { + return NS_ERROR_FAILURE; + } + psWin->SetDevMode(devMode); // copies DevMode + psWin->CopyFromNative(prntdlg.hDC, devMode); + ::GlobalUnlock(prntdlg.hDevMode); + ::DeleteDC(prntdlg.hDC); + +#if defined(DEBUG_rods) || defined(DEBUG_dcone) + bool printSelection = prntdlg.Flags & PD_SELECTION; + bool printAllPages = prntdlg.Flags & PD_ALLPAGES; + bool printNumPages = prntdlg.Flags & PD_PAGENUMS; + int32_t fromPageNum = 0; + int32_t toPageNum = 0; + + if (printNumPages) { + fromPageNum = prntdlg.nFromPage; + toPageNum = prntdlg.nToPage; + } + if (printSelection) { + printf("Printing the selection\n"); + + } else if (printAllPages) { + printf("Printing all the pages\n"); + + } else { + printf("Printing from page no. %d to %d\n", fromPageNum, toPageNum); + } +#endif + + } else { + ::SetFocus(aHWnd); + aPrintSettings->SetIsCancelled(true); + return NS_ERROR_ABORT; + } + + return NS_OK; +} + +//------------------------------------------------------------------ +static void +PrepareForPrintDialog(nsIWebBrowserPrint* aWebBrowserPrint, nsIPrintSettings* aPS) +{ + NS_ASSERTION(aWebBrowserPrint, "Can't be null"); + NS_ASSERTION(aPS, "Can't be null"); + + bool isFramesetDocument; + bool isFramesetFrameSelected; + bool isIFrameSelected; + bool isRangeSelection; + + aWebBrowserPrint->GetIsFramesetDocument(&isFramesetDocument); + aWebBrowserPrint->GetIsFramesetFrameSelected(&isFramesetFrameSelected); + aWebBrowserPrint->GetIsIFrameSelected(&isIFrameSelected); + aWebBrowserPrint->GetIsRangeSelection(&isRangeSelection); + + // Setup print options for UI + if (isFramesetDocument) { + if (isFramesetFrameSelected) { + aPS->SetHowToEnableFrameUI(nsIPrintSettings::kFrameEnableAll); + } else { + aPS->SetHowToEnableFrameUI(nsIPrintSettings::kFrameEnableAsIsAndEach); + } + } else { + aPS->SetHowToEnableFrameUI(nsIPrintSettings::kFrameEnableNone); + } + + // Now determine how to set up the Frame print UI + aPS->SetPrintOptions(nsIPrintSettings::kEnableSelectionRB, isRangeSelection || isIFrameSelected); + +} + +//---------------------------------------------------------------------------------- +//-- Show Print Dialog +//---------------------------------------------------------------------------------- +nsresult NativeShowPrintDialog(HWND aHWnd, + nsIWebBrowserPrint* aWebBrowserPrint, + nsIPrintSettings* aPrintSettings) +{ + PrepareForPrintDialog(aWebBrowserPrint, aPrintSettings); + + nsresult rv = ShowNativePrintDialog(aHWnd, aPrintSettings); + if (aHWnd) { + ::DestroyWindow(aHWnd); + } + + return rv; +} + diff --git a/embedding/components/printingui/win/nsPrintDialogUtil.h b/embedding/components/printingui/win/nsPrintDialogUtil.h new file mode 100644 index 000000000..ada3da239 --- /dev/null +++ b/embedding/components/printingui/win/nsPrintDialogUtil.h @@ -0,0 +1,12 @@ +/* -*- 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 nsFlyOwnDialog_h___ +#define nsFlyOwnDialog_h___ + +nsresult NativeShowPrintDialog(HWND aHWnd, + nsIWebBrowserPrint* aWebBrowserPrint, + nsIPrintSettings* aPrintSettings); + +#endif /* nsFlyOwnDialog_h___ */ diff --git a/embedding/components/printingui/win/nsPrintProgress.cpp b/embedding/components/printingui/win/nsPrintProgress.cpp new file mode 100644 index 000000000..0b1c10a2c --- /dev/null +++ b/embedding/components/printingui/win/nsPrintProgress.cpp @@ -0,0 +1,293 @@ +/* -*- 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 "nsPrintProgress.h" + +#include "nsArray.h" +#include "nsIBaseWindow.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIXULWindow.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsPIDOMWindow.h" + +#if 0 +NS_IMPL_ADDREF(nsPrintProgress) +NS_IMPL_RELEASE(nsPrintProgress) +#else +NS_IMETHODIMP_(MozExternalRefCountType) nsPrintProgress::AddRef(void) +{ + NS_PRECONDITION(int32_t(mRefCnt) >= 0, "illegal refcnt"); + nsrefcnt count; + count = ++mRefCnt; + //NS_LOG_ADDREF(this, count, "nsPrintProgress", sizeof(*this)); + return count; +} + +NS_IMETHODIMP_(MozExternalRefCountType) nsPrintProgress::Release(void) +{ + nsrefcnt count; + NS_PRECONDITION(0 != mRefCnt, "dup release"); + count = --mRefCnt; + //NS_LOG_RELEASE(this, count, "nsPrintProgress"); + if (0 == count) { + mRefCnt = 1; /* stabilize */ + /* enable this to find non-threadsafe destructors: */ + /* NS_ASSERT_OWNINGTHREAD(nsPrintProgress); */ + delete this; + return 0; + } + return count; +} + +#endif + +NS_INTERFACE_MAP_BEGIN(nsPrintProgress) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrintStatusFeedback) + NS_INTERFACE_MAP_ENTRY(nsIPrintProgress) + NS_INTERFACE_MAP_ENTRY(nsIPrintStatusFeedback) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) +NS_INTERFACE_MAP_END_THREADSAFE + + +nsPrintProgress::nsPrintProgress() +{ + m_closeProgress = false; + m_processCanceled = false; + m_pendingStateFlags = -1; + m_pendingStateValue = NS_OK; +} + +nsPrintProgress::~nsPrintProgress() +{ + (void)ReleaseListeners(); +} + +NS_IMETHODIMP nsPrintProgress::OpenProgressDialog(mozIDOMWindowProxy *parent, + const char *dialogURL, + nsISupports *parameters, + nsIObserver *openDialogObserver, + bool *notifyOnOpen) +{ + *notifyOnOpen = true; + m_observer = openDialogObserver; + + nsresult rv = NS_ERROR_FAILURE; + + if (m_dialog) + return NS_ERROR_ALREADY_INITIALIZED; + + if (!dialogURL || !*dialogURL) + return NS_ERROR_INVALID_ARG; + + if (parent) + { + // Set up window.arguments[0]... + nsCOMPtr<nsIMutableArray> array = nsArray::Create(); + + nsCOMPtr<nsISupportsInterfacePointer> ifptr = + do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + ifptr->SetData(static_cast<nsIPrintProgress*>(this)); + ifptr->SetDataIID(&NS_GET_IID(nsIPrintProgress)); + + array->AppendElement(ifptr, /*weak =*/ false); + + array->AppendElement(parameters, /*weak = */ false); + + // We will set the opener of the dialog to be the nsIDOMWindow for the + // browser XUL window itself, as opposed to the content. That way, the + // progress window has access to the opener. + nsCOMPtr<nsPIDOMWindowOuter> pParentWindow = nsPIDOMWindowOuter::From(parent); + NS_ENSURE_STATE(pParentWindow); + + nsCOMPtr<nsIDocShell> docShell = pParentWindow->GetDocShell(); + NS_ENSURE_STATE(docShell); + + nsCOMPtr<nsIDocShellTreeOwner> owner; + docShell->GetTreeOwner(getter_AddRefs(owner)); + + nsCOMPtr<nsIXULWindow> ownerXULWindow = do_GetInterface(owner); + nsCOMPtr<mozIDOMWindowProxy> ownerWindow = do_GetInterface(ownerXULWindow); + NS_ENSURE_STATE(ownerWindow); + + nsCOMPtr<nsPIDOMWindowOuter> piOwnerWindow = nsPIDOMWindowOuter::From(ownerWindow); + + // Open the dialog. + nsCOMPtr<nsPIDOMWindowOuter> newWindow; + rv = piOwnerWindow->OpenDialog(NS_ConvertASCIItoUTF16(dialogURL), + NS_LITERAL_STRING("_blank"), + NS_LITERAL_STRING("chrome,titlebar,dependent,centerscreen"), + array, getter_AddRefs(newWindow)); + } + + return rv; +} + +NS_IMETHODIMP nsPrintProgress::CloseProgressDialog(bool forceClose) +{ + m_closeProgress = true; + // XXX Casting from bool to nsresult + return OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, + static_cast<nsresult>(forceClose)); +} + +NS_IMETHODIMP nsPrintProgress::GetPrompter(nsIPrompt **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nullptr; + + if (! m_closeProgress && m_dialog) { + nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(m_dialog); + MOZ_ASSERT(window); + return window->GetPrompter(_retval); + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsPrintProgress::GetProcessCanceledByUser(bool *aProcessCanceledByUser) +{ + NS_ENSURE_ARG_POINTER(aProcessCanceledByUser); + *aProcessCanceledByUser = m_processCanceled; + return NS_OK; +} +NS_IMETHODIMP nsPrintProgress::SetProcessCanceledByUser(bool aProcessCanceledByUser) +{ + m_processCanceled = aProcessCanceledByUser; + OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_OK); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::RegisterListener(nsIWebProgressListener * listener) +{ + if (!listener) //Nothing to do with a null listener! + return NS_OK; + + m_listenerList.AppendObject(listener); + if (m_closeProgress || m_processCanceled) + listener->OnStateChange(nullptr, nullptr, + nsIWebProgressListener::STATE_STOP, NS_OK); + else + { + listener->OnStatusChange(nullptr, nullptr, NS_OK, m_pendingStatus.get()); + if (m_pendingStateFlags != -1) + listener->OnStateChange(nullptr, nullptr, m_pendingStateFlags, m_pendingStateValue); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::UnregisterListener(nsIWebProgressListener *listener) +{ + if (listener) + m_listenerList.RemoveObject(listener); + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::DoneIniting() +{ + if (m_observer) { + m_observer->Observe(nullptr, nullptr, nullptr); + } + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus) +{ + m_pendingStateFlags = aStateFlags; + m_pendingStateValue = aStatus; + + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) +{ + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags) +{ + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage) +{ + if (aMessage && *aMessage) + m_pendingStatus = aMessage; + + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state) +{ + return NS_OK; +} + +nsresult nsPrintProgress::ReleaseListeners() +{ + m_listenerList.Clear(); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::ShowStatusString(const char16_t *status) +{ + return OnStatusChange(nullptr, nullptr, NS_OK, status); +} + +NS_IMETHODIMP nsPrintProgress::StartMeteors() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::StopMeteors() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::ShowProgress(int32_t percent) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::SetDocShell(nsIDocShell *shell, mozIDOMWindowProxy *window) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::CloseWindow() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/embedding/components/printingui/win/nsPrintProgress.h b/embedding/components/printingui/win/nsPrintProgress.h new file mode 100644 index 000000000..adc5a14cc --- /dev/null +++ b/embedding/components/printingui/win/nsPrintProgress.h @@ -0,0 +1,43 @@ +/* -*- 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 __nsPrintProgress_h +#define __nsPrintProgress_h + +#include "nsIPrintProgress.h" + +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsIDOMWindow.h" +#include "nsIPrintStatusFeedback.h" +#include "nsString.h" +#include "nsIWindowWatcher.h" +#include "nsIObserver.h" + +class nsPrintProgress : public nsIPrintProgress, public nsIPrintStatusFeedback +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPRINTPROGRESS + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_NSIPRINTSTATUSFEEDBACK + + nsPrintProgress(); + virtual ~nsPrintProgress(); + +private: + nsresult ReleaseListeners(); + + bool m_closeProgress; + bool m_processCanceled; + nsString m_pendingStatus; + int32_t m_pendingStateFlags; + nsresult m_pendingStateValue; + nsCOMPtr<nsIDOMWindow> m_dialog; + nsCOMArray<nsIWebProgressListener> m_listenerList; + nsCOMPtr<nsIObserver> m_observer; +}; + +#endif diff --git a/embedding/components/printingui/win/nsPrintProgressParams.cpp b/embedding/components/printingui/win/nsPrintProgressParams.cpp new file mode 100644 index 000000000..eba86b298 --- /dev/null +++ b/embedding/components/printingui/win/nsPrintProgressParams.cpp @@ -0,0 +1,47 @@ +/* -*- 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 "nsPrintProgressParams.h" +#include "nsReadableUtils.h" + + +NS_IMPL_ISUPPORTS(nsPrintProgressParams, nsIPrintProgressParams) + +nsPrintProgressParams::nsPrintProgressParams() +{ +} + +nsPrintProgressParams::~nsPrintProgressParams() +{ +} + +NS_IMETHODIMP nsPrintProgressParams::GetDocTitle(char16_t * *aDocTitle) +{ + NS_ENSURE_ARG(aDocTitle); + + *aDocTitle = ToNewUnicode(mDocTitle); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::SetDocTitle(const char16_t * aDocTitle) +{ + mDocTitle = aDocTitle; + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::GetDocURL(char16_t * *aDocURL) +{ + NS_ENSURE_ARG(aDocURL); + + *aDocURL = ToNewUnicode(mDocURL); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::SetDocURL(const char16_t * aDocURL) +{ + mDocURL = aDocURL; + return NS_OK; +} + diff --git a/embedding/components/printingui/win/nsPrintProgressParams.h b/embedding/components/printingui/win/nsPrintProgressParams.h new file mode 100644 index 000000000..aebfbff7f --- /dev/null +++ b/embedding/components/printingui/win/nsPrintProgressParams.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsPrintProgressParams_h +#define __nsPrintProgressParams_h + +#include "nsIPrintProgressParams.h" +#include "nsString.h" + +class nsPrintProgressParams : public nsIPrintProgressParams +{ + virtual ~nsPrintProgressParams(); + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRINTPROGRESSPARAMS + + nsPrintProgressParams(); + +private: + nsString mDocTitle; + nsString mDocURL; +}; + +#endif diff --git a/embedding/components/printingui/win/nsPrintingPromptService.cpp b/embedding/components/printingui/win/nsPrintingPromptService.cpp new file mode 100644 index 000000000..83727ee4e --- /dev/null +++ b/embedding/components/printingui/win/nsPrintingPromptService.cpp @@ -0,0 +1,341 @@ +/* -*- 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 "nsCOMPtr.h" + +#include "nsPrintingPromptService.h" +#include "nsIPrintingPromptService.h" +#include "nsIFactory.h" +#include "nsPIDOMWindow.h" +#include "nsReadableUtils.h" +#include "nsIEmbeddingSiteWindow.h" +#include "nsIServiceManager.h" +#include "nsIWebBrowserChrome.h" +#include "nsIWindowWatcher.h" +#include "nsPrintDialogUtil.h" + +// Printing Progress Includes +#include "nsPrintProgress.h" +#include "nsPrintProgressParams.h" +#include "nsIWebProgressListener.h" + +// XP Dialog includes +#include "nsArray.h" +#include "nsIDialogParamBlock.h" +#include "nsISupportsUtils.h" + +// Includes need to locate the native Window +#include "nsIWidget.h" +#include "nsIBaseWindow.h" +#include "nsIWebBrowserChrome.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShell.h" +#include "nsIInterfaceRequestorUtils.h" + + +static const char *kPrintProgressDialogURL = "chrome://global/content/printProgress.xul"; +static const char *kPrtPrvProgressDialogURL = "chrome://global/content/printPreviewProgress.xul"; +static const char *kPageSetupDialogURL = "chrome://global/content/printPageSetup.xul"; + +/**************************************************************** + ************************* ParamBlock *************************** + ****************************************************************/ + +class ParamBlock { + +public: + ParamBlock() + { + mBlock = 0; + } + ~ParamBlock() + { + NS_IF_RELEASE(mBlock); + } + nsresult Init() { + return CallCreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID, &mBlock); + } + nsIDialogParamBlock * operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { return mBlock; } + operator nsIDialogParamBlock * const () { return mBlock; } + +private: + nsIDialogParamBlock *mBlock; +}; + +//***************************************************************************** + +NS_IMPL_ISUPPORTS(nsPrintingPromptService, nsIPrintingPromptService, nsIWebProgressListener) + +nsPrintingPromptService::nsPrintingPromptService() +{ +} + +//----------------------------------------------------------- +nsPrintingPromptService::~nsPrintingPromptService() +{ +} + +//----------------------------------------------------------- +nsresult +nsPrintingPromptService::Init() +{ + nsresult rv; + mWatcher = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + return rv; +} + +//----------------------------------------------------------- +HWND +nsPrintingPromptService::GetHWNDForDOMWindow(mozIDOMWindowProxy *aWindow) +{ + nsCOMPtr<nsIWebBrowserChrome> chrome; + + // We might be embedded so check this path first + if (mWatcher) { + nsCOMPtr<mozIDOMWindowProxy> fosterParent; + if (!aWindow) + { // it will be a dependent window. try to find a foster parent. + mWatcher->GetActiveWindow(getter_AddRefs(fosterParent)); + aWindow = fosterParent; + } + mWatcher->GetChromeForWindow(aWindow, getter_AddRefs(chrome)); + } + + if (chrome) { + nsCOMPtr<nsIEmbeddingSiteWindow> site(do_QueryInterface(chrome)); + if (site) + { + HWND w; + site->GetSiteWindow(reinterpret_cast<void **>(&w)); + return w; + } + } + + // Now we might be the Browser so check this path + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); + + nsCOMPtr<nsIDocShellTreeItem> treeItem = + do_QueryInterface(window->GetDocShell()); + if (!treeItem) return nullptr; + + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + treeItem->GetTreeOwner(getter_AddRefs(treeOwner)); + if (!treeOwner) return nullptr; + + nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome(do_GetInterface(treeOwner)); + if (!webBrowserChrome) return nullptr; + + nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(webBrowserChrome)); + if (!baseWin) return nullptr; + + nsCOMPtr<nsIWidget> widget; + baseWin->GetMainWidget(getter_AddRefs(widget)); + if (!widget) return nullptr; + + return (HWND)widget->GetNativeData(NS_NATIVE_TMP_WINDOW); + +} + + +/////////////////////////////////////////////////////////////////////////////// +// nsIPrintingPromptService + +//----------------------------------------------------------- +NS_IMETHODIMP +nsPrintingPromptService::ShowPrintDialog(mozIDOMWindowProxy *parent, nsIWebBrowserPrint *webBrowserPrint, nsIPrintSettings *printSettings) +{ + NS_ENSURE_ARG(parent); + + HWND hWnd = GetHWNDForDOMWindow(parent); + NS_ASSERTION(hWnd, "Couldn't get native window for PRint Dialog!"); + + return NativeShowPrintDialog(hWnd, webBrowserPrint, printSettings); +} + + +NS_IMETHODIMP +nsPrintingPromptService::ShowProgress(mozIDOMWindowProxy* parent, + nsIWebBrowserPrint* webBrowserPrint, // ok to be null + nsIPrintSettings* printSettings, // ok to be null + nsIObserver* openDialogObserver, // ok to be null + bool isForPrinting, + nsIWebProgressListener** webProgressListener, + nsIPrintProgressParams** printProgressParams, + bool* notifyOnOpen) +{ + NS_ENSURE_ARG(webProgressListener); + NS_ENSURE_ARG(printProgressParams); + NS_ENSURE_ARG(notifyOnOpen); + + *notifyOnOpen = false; + if (mPrintProgress) { + *webProgressListener = nullptr; + *printProgressParams = nullptr; + return NS_ERROR_FAILURE; + } + + nsPrintProgress* prtProgress = new nsPrintProgress(); + mPrintProgress = prtProgress; + mWebProgressListener = prtProgress; + + nsCOMPtr<nsIPrintProgressParams> prtProgressParams = new nsPrintProgressParams(); + + nsCOMPtr<mozIDOMWindowProxy> parentWindow = parent; + + if (mWatcher && !parentWindow) { + mWatcher->GetActiveWindow(getter_AddRefs(parentWindow)); + } + + if (parentWindow) { + mPrintProgress->OpenProgressDialog(parentWindow, + isForPrinting ? kPrintProgressDialogURL : kPrtPrvProgressDialogURL, + prtProgressParams, openDialogObserver, notifyOnOpen); + } + + prtProgressParams.forget(printProgressParams); + NS_ADDREF(*webProgressListener = this); + + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowPageSetup(mozIDOMWindowProxy *parent, nsIPrintSettings *printSettings, nsIObserver *aObs) +{ + NS_ENSURE_ARG(printSettings); + + ParamBlock block; + nsresult rv = block.Init(); + if (NS_FAILED(rv)) + return rv; + + block->SetInt(0, 0); + rv = DoDialog(parent, block, printSettings, kPageSetupDialogURL); + + // if aWebBrowserPrint is not null then we are printing + // so we want to pass back NS_ERROR_ABORT on cancel + if (NS_SUCCEEDED(rv)) + { + int32_t status; + block->GetInt(0, &status); + return status == 0?NS_ERROR_ABORT:NS_OK; + } + + return rv; +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowPrinterProperties(mozIDOMWindowProxy *parent, const char16_t *printerName, nsIPrintSettings *printSettings) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +//----------------------------------------------------------- +// Helper to Fly XP Dialog +nsresult +nsPrintingPromptService::DoDialog(mozIDOMWindowProxy *aParent, + nsIDialogParamBlock *aParamBlock, + nsIPrintSettings* aPS, + const char *aChromeURL) +{ + NS_ENSURE_ARG(aParamBlock); + NS_ENSURE_ARG(aPS); + NS_ENSURE_ARG(aChromeURL); + + if (!mWatcher) + return NS_ERROR_FAILURE; + + // get a parent, if at all possible + // (though we'd rather this didn't fail, it's OK if it does. so there's + // no failure or null check.) + nsCOMPtr<mozIDOMWindowProxy> activeParent; // retain ownership for method lifetime + if (!aParent) + { + mWatcher->GetActiveWindow(getter_AddRefs(activeParent)); + aParent = activeParent; + } + + // create a nsIMutableArray of the parameters + // being passed to the window + nsCOMPtr<nsIMutableArray> array = nsArray::Create(); + + nsCOMPtr<nsISupports> psSupports(do_QueryInterface(aPS)); + NS_ASSERTION(psSupports, "PrintSettings must be a supports"); + array->AppendElement(psSupports, /*weak =*/ false); + + nsCOMPtr<nsISupports> blkSupps(do_QueryInterface(aParamBlock)); + NS_ASSERTION(blkSupps, "IOBlk must be a supports"); + array->AppendElement(blkSupps, /*weak =*/ false); + + nsCOMPtr<mozIDOMWindowProxy> dialog; + nsresult rv = mWatcher->OpenWindow(aParent, aChromeURL, "_blank", + "centerscreen,chrome,modal,titlebar", array, + getter_AddRefs(dialog)); + + return rv; +} + +////////////////////////////////////////////////////////////////////// +// nsIWebProgressListener +////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsPrintingPromptService::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus) +{ + if ((aStateFlags & STATE_STOP) && mWebProgressListener) + { + mWebProgressListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); + if (mPrintProgress) + { + mPrintProgress->CloseProgressDialog(true); + } + mPrintProgress = nullptr; + mWebProgressListener = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingPromptService::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) +{ + if (mWebProgressListener) + { + return mWebProgressListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsPrintingPromptService::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags) +{ + if (mWebProgressListener) + { + return mWebProgressListener->OnLocationChange(aWebProgress, aRequest, location, aFlags); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsPrintingPromptService::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage) +{ + if (mWebProgressListener) + { + return mWebProgressListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsPrintingPromptService::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state) +{ + if (mWebProgressListener) + { + return mWebProgressListener->OnSecurityChange(aWebProgress, aRequest, state); + } + return NS_ERROR_FAILURE; +} + + diff --git a/embedding/components/printingui/win/nsPrintingPromptService.h b/embedding/components/printingui/win/nsPrintingPromptService.h new file mode 100644 index 000000000..76bc7a804 --- /dev/null +++ b/embedding/components/printingui/win/nsPrintingPromptService.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 __nsPrintingPromptService_h +#define __nsPrintingPromptService_h + +#include <windows.h> + +// {E042570C-62DE-4bb6-A6E0-798E3C07B4DF} +#define NS_PRINTINGPROMPTSERVICE_CID \ + {0xe042570c, 0x62de, 0x4bb6, { 0xa6, 0xe0, 0x79, 0x8e, 0x3c, 0x7, 0xb4, 0xdf}} +#define NS_PRINTINGPROMPTSERVICE_CONTRACTID \ + "@mozilla.org/embedcomp/printingprompt-service;1" + +#include "nsCOMPtr.h" +#include "nsIPrintingPromptService.h" +#include "nsPIPromptService.h" +#include "nsIWindowWatcher.h" + +// Printing Progress Includes +#include "nsPrintProgress.h" +#include "nsPrintProgressParams.h" +#include "nsIWebProgressListener.h" + +class nsIDOMWindow; +class nsIDialogParamBlock; + +class nsPrintingPromptService: public nsIPrintingPromptService, + public nsIWebProgressListener +{ + virtual ~nsPrintingPromptService(); + +public: + nsPrintingPromptService(); + + nsresult Init(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIPRINTINGPROMPTSERVICE + NS_DECL_NSIWEBPROGRESSLISTENER + +private: + HWND GetHWNDForDOMWindow(mozIDOMWindowProxy *parent); + nsresult DoDialog(mozIDOMWindowProxy *aParent, + nsIDialogParamBlock *aParamBlock, + nsIPrintSettings* aPS, + const char *aChromeURL); + + nsCOMPtr<nsIWindowWatcher> mWatcher; + nsCOMPtr<nsIPrintProgress> mPrintProgress; + nsCOMPtr<nsIWebProgressListener> mWebProgressListener; + +}; + +#endif + diff --git a/embedding/components/webbrowserpersist/PWebBrowserPersistDocument.ipdl b/embedding/components/webbrowserpersist/PWebBrowserPersistDocument.ipdl new file mode 100644 index 000000000..143af0ebd --- /dev/null +++ b/embedding/components/webbrowserpersist/PWebBrowserPersistDocument.ipdl @@ -0,0 +1,90 @@ +/* -*- Mode: IDL; 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/. */ + +include protocol PContent; +include protocol PWebBrowserPersistResources; +include protocol PWebBrowserPersistSerialize; + +include InputStreamParams; + +namespace mozilla { + +// nsIWebBrowserPersistDocument has attributes which can be read +// synchronously. To avoid using sync IPC for them, the actor sends +// this structure from the child to the parent before the parent actor +// is exposed to XPCOM. +struct WebBrowserPersistDocumentAttrs { + bool isPrivate; + nsCString documentURI; + nsCString baseURI; + nsCString contentType; + nsCString characterSet; + nsString title; + nsString referrer; + nsString contentDisposition; + uint32_t cacheKey; + uint32_t persistFlags; +}; + +// IPDL doesn't have tuples, so this gives the pair of strings from +// nsIWebBrowserPersistURIMap::getURIMapping a name. +struct WebBrowserPersistURIMapEntry { + nsCString mapFrom; + nsCString mapTo; +}; + +// nsIWebBrowserPersistURIMap is just copied over IPC as one of these, +// not proxied, to simplify the protocol. +struct WebBrowserPersistURIMap { + WebBrowserPersistURIMapEntry[] mapURIs; + nsCString targetBaseURI; +}; + +// This remotes nsIWebBrowserPersistDocument and its visitors. The +// lifecycle is a little complicated: the initial document is +// constructed parent->child, but subdocuments are constructed +// child->parent and then passed back. Subdocuments aren't subactors, +// because that would impose a lifetime relationship that doesn't +// exist in the XPIDL; instead they're all managed by the enclosing +// PContent. +protocol PWebBrowserPersistDocument { + manager PContent; + manages PWebBrowserPersistResources; + manages PWebBrowserPersistSerialize; + +parent: + // The actor isn't exposed to XPCOM until after it gets one of these + // two messages; see also the state transition rules. The message + // is either a response to the constructor (if it was parent->child) + // or sent after it (if it was child->parent). + async Attributes(WebBrowserPersistDocumentAttrs aAttrs, + OptionalInputStreamParams postData, + FileDescriptor[] postFiles); + async InitFailure(nsresult aStatus); + +child: + async SetPersistFlags(uint32_t aNewFlags); + async PWebBrowserPersistResources(); + async PWebBrowserPersistSerialize(WebBrowserPersistURIMap aMap, + nsCString aRequestedContentType, + uint32_t aEncoderFlags, + uint32_t aWrapColumn); + async __delete__(); + +state START: + recv Attributes goto MAIN; + recv InitFailure goto FAILED; + +state MAIN: + send SetPersistFlags goto MAIN; + send PWebBrowserPersistResources goto MAIN; + send PWebBrowserPersistSerialize goto MAIN; + send __delete__; + +state FAILED: + send __delete__; +}; + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/PWebBrowserPersistResources.ipdl b/embedding/components/webbrowserpersist/PWebBrowserPersistResources.ipdl new file mode 100644 index 000000000..3c36ae495 --- /dev/null +++ b/embedding/components/webbrowserpersist/PWebBrowserPersistResources.ipdl @@ -0,0 +1,26 @@ +/* -*- Mode: IDL; 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/. */ + +include protocol PWebBrowserPersistDocument; + +namespace mozilla { + +// == nsIWebBrowserPersistResourceVisitor +protocol PWebBrowserPersistResources { + manager PWebBrowserPersistDocument; + +parent: + async VisitResource(nsCString aURI); + + // The actor sent here is in the START state; the parent-side + // receiver will have to wait for it to enter the MAIN state + // before exposing it with a visitDocument call. + async VisitDocument(PWebBrowserPersistDocument aSubDocument); + + // This reflects the endVisit method. + async __delete__(nsresult aStatus); +}; + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/PWebBrowserPersistSerialize.ipdl b/embedding/components/webbrowserpersist/PWebBrowserPersistSerialize.ipdl new file mode 100644 index 000000000..ff09fd388 --- /dev/null +++ b/embedding/components/webbrowserpersist/PWebBrowserPersistSerialize.ipdl @@ -0,0 +1,29 @@ +/* -*- Mode: IDL; 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/. */ + +include protocol PWebBrowserPersistDocument; + +namespace mozilla { + +// This actor represents both an nsIWebBrowserPersistWriteCompletion +// and the nsIOutputStream passed with it to the writeContent method. +protocol PWebBrowserPersistSerialize { + manager PWebBrowserPersistDocument; + +parent: + // This sends the data with no flow control, so the parent could + // wind up buffering an arbitrarily large amount of data... but + // it's a serialized DOM that's already in memory as DOM nodes, so + // this is at worst just a constant-factor increase in memory usage. + // Also, Chromium does the same thing; see + // content::RenderViewImpl::didSerializeDataForFrame. + async WriteData(uint8_t[] aData); + + // This is the onFinish method. + async __delete__(nsCString aContentType, + nsresult aStatus); +}; + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.cpp new file mode 100644 index 000000000..9e152cd46 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.cpp @@ -0,0 +1,159 @@ +/* -*- 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 "WebBrowserPersistDocumentChild.h" + +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsIDocument.h" +#include "nsIInputStream.h" +#include "WebBrowserPersistLocalDocument.h" +#include "WebBrowserPersistResourcesChild.h" +#include "WebBrowserPersistSerializeChild.h" + +namespace mozilla { + +WebBrowserPersistDocumentChild::WebBrowserPersistDocumentChild() +{ +} + +WebBrowserPersistDocumentChild::~WebBrowserPersistDocumentChild() +{ +} + +void +WebBrowserPersistDocumentChild::Start(nsIDocument* aDocument) +{ + RefPtr<WebBrowserPersistLocalDocument> doc; + if (aDocument) { + doc = new WebBrowserPersistLocalDocument(aDocument); + } + Start(doc); +} + +void +WebBrowserPersistDocumentChild::Start(nsIWebBrowserPersistDocument* aDocument) +{ + MOZ_ASSERT(!mDocument); + if (!aDocument) { + SendInitFailure(NS_ERROR_FAILURE); + return; + } + + WebBrowserPersistDocumentAttrs attrs; + nsCOMPtr<nsIInputStream> postDataStream; + OptionalInputStreamParams postData; + nsTArray<FileDescriptor> postFiles; +#define ENSURE(e) do { \ + nsresult rv = (e); \ + if (NS_FAILED(rv)) { \ + SendInitFailure(rv); \ + return; \ + } \ + } while(0) + ENSURE(aDocument->GetIsPrivate(&(attrs.isPrivate()))); + ENSURE(aDocument->GetDocumentURI(attrs.documentURI())); + ENSURE(aDocument->GetBaseURI(attrs.baseURI())); + ENSURE(aDocument->GetContentType(attrs.contentType())); + ENSURE(aDocument->GetCharacterSet(attrs.characterSet())); + ENSURE(aDocument->GetTitle(attrs.title())); + ENSURE(aDocument->GetReferrer(attrs.referrer())); + ENSURE(aDocument->GetContentDisposition(attrs.contentDisposition())); + ENSURE(aDocument->GetCacheKey(&(attrs.cacheKey()))); + ENSURE(aDocument->GetPersistFlags(&(attrs.persistFlags()))); + ENSURE(aDocument->GetPostData(getter_AddRefs(postDataStream))); + ipc::SerializeInputStream(postDataStream, + postData, + postFiles); +#undef ENSURE + mDocument = aDocument; + SendAttributes(attrs, postData, postFiles); +} + +bool +WebBrowserPersistDocumentChild::RecvSetPersistFlags(const uint32_t& aNewFlags) +{ + mDocument->SetPersistFlags(aNewFlags); + return true; +} + +PWebBrowserPersistResourcesChild* +WebBrowserPersistDocumentChild::AllocPWebBrowserPersistResourcesChild() +{ + auto* actor = new WebBrowserPersistResourcesChild(); + NS_ADDREF(actor); + return actor; +} + +bool +WebBrowserPersistDocumentChild::RecvPWebBrowserPersistResourcesConstructor(PWebBrowserPersistResourcesChild* aActor) +{ + RefPtr<WebBrowserPersistResourcesChild> visitor = + static_cast<WebBrowserPersistResourcesChild*>(aActor); + nsresult rv = mDocument->ReadResources(visitor); + if (NS_FAILED(rv)) { + // This is a sync failure on the child side but an async + // failure on the parent side -- it already got NS_OK from + // ReadResources, so the error has to be reported via the + // visitor instead. + visitor->EndVisit(mDocument, rv); + } + return true; +} + +bool +WebBrowserPersistDocumentChild::DeallocPWebBrowserPersistResourcesChild(PWebBrowserPersistResourcesChild* aActor) +{ + auto* castActor = + static_cast<WebBrowserPersistResourcesChild*>(aActor); + NS_RELEASE(castActor); + return true; +} + +PWebBrowserPersistSerializeChild* +WebBrowserPersistDocumentChild::AllocPWebBrowserPersistSerializeChild( + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) +{ + auto* actor = new WebBrowserPersistSerializeChild(aMap); + NS_ADDREF(actor); + return actor; +} + +bool +WebBrowserPersistDocumentChild::RecvPWebBrowserPersistSerializeConstructor( + PWebBrowserPersistSerializeChild* aActor, + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) +{ + auto* castActor = + static_cast<WebBrowserPersistSerializeChild*>(aActor); + // This actor performs the roles of: completion, URI map, and output stream. + nsresult rv = mDocument->WriteContent(castActor, + castActor, + aRequestedContentType, + aEncoderFlags, + aWrapColumn, + castActor); + if (NS_FAILED(rv)) { + castActor->OnFinish(mDocument, castActor, aRequestedContentType, rv); + } + return true; +} + +bool +WebBrowserPersistDocumentChild::DeallocPWebBrowserPersistSerializeChild(PWebBrowserPersistSerializeChild* aActor) +{ + auto* castActor = + static_cast<WebBrowserPersistSerializeChild*>(aActor); + NS_RELEASE(castActor); + return true; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.h b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.h new file mode 100644 index 000000000..a72291f3c --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.h @@ -0,0 +1,62 @@ +/* -*- 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 WebBrowserPersistDocumentChild_h__ +#define WebBrowserPersistDocumentChild_h__ + +#include "mozilla/PWebBrowserPersistDocumentChild.h" +#include "nsCOMPtr.h" +#include "nsIWebBrowserPersistDocument.h" + +class nsIDocument; + +namespace mozilla { + +class WebBrowserPersistDocumentChild final + : public PWebBrowserPersistDocumentChild +{ +public: + WebBrowserPersistDocumentChild(); + ~WebBrowserPersistDocumentChild(); + + // This sends either Attributes or InitFailure and thereby causes + // the actor to leave the START state. + void Start(nsIWebBrowserPersistDocument* aDocument); + void Start(nsIDocument* aDocument); + + virtual bool + RecvSetPersistFlags(const uint32_t& aNewFlags) override; + + virtual PWebBrowserPersistResourcesChild* + AllocPWebBrowserPersistResourcesChild() override; + virtual bool + RecvPWebBrowserPersistResourcesConstructor(PWebBrowserPersistResourcesChild* aActor) override; + virtual bool + DeallocPWebBrowserPersistResourcesChild(PWebBrowserPersistResourcesChild* aActor) override; + + virtual PWebBrowserPersistSerializeChild* + AllocPWebBrowserPersistSerializeChild( + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) override; + virtual bool + RecvPWebBrowserPersistSerializeConstructor( + PWebBrowserPersistSerializeChild* aActor, + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) override; + virtual bool + DeallocPWebBrowserPersistSerializeChild(PWebBrowserPersistSerializeChild* aActor) override; + +private: + nsCOMPtr<nsIWebBrowserPersistDocument> mDocument; +}; + +} // namespace mozilla + +#endif // WebBrowserPersistDocumentChild_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.cpp new file mode 100644 index 000000000..248b05b9b --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.cpp @@ -0,0 +1,125 @@ +/* -*- 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 "WebBrowserPersistDocumentParent.h" + +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsIInputStream.h" +#include "nsThreadUtils.h" +#include "WebBrowserPersistResourcesParent.h" +#include "WebBrowserPersistSerializeParent.h" +#include "WebBrowserPersistRemoteDocument.h" + +namespace mozilla { + +WebBrowserPersistDocumentParent::WebBrowserPersistDocumentParent() +: mReflection(nullptr) +{ +} + +void +WebBrowserPersistDocumentParent::SetOnReady(nsIWebBrowserPersistDocumentReceiver* aOnReady) +{ + MOZ_ASSERT(aOnReady); + MOZ_ASSERT(!mOnReady); + MOZ_ASSERT(!mReflection); + mOnReady = aOnReady; +} + +void +WebBrowserPersistDocumentParent::ActorDestroy(ActorDestroyReason aWhy) +{ + if (mReflection) { + mReflection->ActorDestroy(); + mReflection = nullptr; + } + if (mOnReady) { + // Bug 1202887: If this is part of a subtree destruction, then + // anything which could cause another actor in that subtree to + // be Send__delete__()ed will cause use-after-free -- such as + // dropping the last reference to another document's + // WebBrowserPersistRemoteDocument. To avoid that, defer the + // callback until after the entire subtree is destroyed. + nsCOMPtr<nsIRunnable> errorLater = NewRunnableMethod + <nsresult>(mOnReady, &nsIWebBrowserPersistDocumentReceiver::OnError, + NS_ERROR_FAILURE); + NS_DispatchToCurrentThread(errorLater); + mOnReady = nullptr; + } +} + +WebBrowserPersistDocumentParent::~WebBrowserPersistDocumentParent() +{ + MOZ_RELEASE_ASSERT(!mReflection); + MOZ_ASSERT(!mOnReady); +} + +bool +WebBrowserPersistDocumentParent::RecvAttributes(const Attrs& aAttrs, + const OptionalInputStreamParams& aPostData, + nsTArray<FileDescriptor>&& aPostFiles) +{ + // Deserialize the postData unconditionally so that fds aren't leaked. + nsCOMPtr<nsIInputStream> postData = + ipc::DeserializeInputStream(aPostData, aPostFiles); + if (!mOnReady || mReflection) { + return false; + } + mReflection = new WebBrowserPersistRemoteDocument(this, aAttrs, postData); + RefPtr<WebBrowserPersistRemoteDocument> reflection = mReflection; + mOnReady->OnDocumentReady(reflection); + mOnReady = nullptr; + return true; +} + +bool +WebBrowserPersistDocumentParent::RecvInitFailure(const nsresult& aFailure) +{ + if (!mOnReady || mReflection) { + return false; + } + mOnReady->OnError(aFailure); + mOnReady = nullptr; + // Warning: Send__delete__ deallocates this object. + return Send__delete__(this); +} + +PWebBrowserPersistResourcesParent* +WebBrowserPersistDocumentParent::AllocPWebBrowserPersistResourcesParent() +{ + MOZ_CRASH("Don't use this; construct the actor directly and AddRef."); + return nullptr; +} + +bool +WebBrowserPersistDocumentParent::DeallocPWebBrowserPersistResourcesParent(PWebBrowserPersistResourcesParent* aActor) +{ + // Turn the ref held by IPC back into an nsRefPtr. + RefPtr<WebBrowserPersistResourcesParent> actor = + already_AddRefed<WebBrowserPersistResourcesParent>( + static_cast<WebBrowserPersistResourcesParent*>(aActor)); + return true; +} + +PWebBrowserPersistSerializeParent* +WebBrowserPersistDocumentParent::AllocPWebBrowserPersistSerializeParent( + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) +{ + MOZ_CRASH("Don't use this; construct the actor directly."); + return nullptr; +} + +bool +WebBrowserPersistDocumentParent::DeallocPWebBrowserPersistSerializeParent(PWebBrowserPersistSerializeParent* aActor) +{ + delete aActor; + return true; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.h b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.h new file mode 100644 index 000000000..b02b37706 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.h @@ -0,0 +1,79 @@ +/* -*- 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 WebBrowserPersistDocumentParent_h__ +#define WebBrowserPersistDocumentParent_h__ + +#include "mozilla/Maybe.h" +#include "mozilla/PWebBrowserPersistDocumentParent.h" +#include "nsCOMPtr.h" +#include "nsIWebBrowserPersistDocument.h" + +// This class is the IPC half of the glue between the +// nsIWebBrowserPersistDocument interface and a remote document. When +// (and if) it receives the Attributes message it constructs an +// WebBrowserPersistRemoteDocument and releases it into the XPCOM +// universe; otherwise, it invokes the document receiver's error +// callback. +// +// This object's lifetime is the normal IPC lifetime; on destruction, +// it calls its XPCOM reflection (if it exists yet) to remove that +// reference. Normal deletion occurs when the XPCOM object is being +// destroyed or after an InitFailure is received and handled. +// +// See also: TabParent::StartPersistence. + +namespace mozilla { + +class WebBrowserPersistRemoteDocument; + +class WebBrowserPersistDocumentParent final + : public PWebBrowserPersistDocumentParent +{ +public: + WebBrowserPersistDocumentParent(); + virtual ~WebBrowserPersistDocumentParent(); + + // Set a callback to be invoked when the actor leaves the START + // state. This method must be called exactly once while the actor + // is still in the START state (or is unconstructed). + void SetOnReady(nsIWebBrowserPersistDocumentReceiver* aOnReady); + + using Attrs = WebBrowserPersistDocumentAttrs; + + // IPDL methods: + virtual bool + RecvAttributes(const Attrs& aAttrs, + const OptionalInputStreamParams& aPostData, + nsTArray<FileDescriptor>&& aPostFiles) override; + virtual bool + RecvInitFailure(const nsresult& aFailure) override; + + virtual PWebBrowserPersistResourcesParent* + AllocPWebBrowserPersistResourcesParent() override; + virtual bool + DeallocPWebBrowserPersistResourcesParent(PWebBrowserPersistResourcesParent* aActor) override; + + virtual PWebBrowserPersistSerializeParent* + AllocPWebBrowserPersistSerializeParent( + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) override; + virtual bool + DeallocPWebBrowserPersistSerializeParent(PWebBrowserPersistSerializeParent* aActor) override; + + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; +private: + // This is reset to nullptr when the callback is invoked. + nsCOMPtr<nsIWebBrowserPersistDocumentReceiver> mOnReady; + WebBrowserPersistRemoteDocument* mReflection; +}; + +} // namespace mozilla + +#endif // WebBrowserPersistDocumentParent_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp new file mode 100644 index 000000000..23513387e --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp @@ -0,0 +1,1475 @@ +/* -*- 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 "WebBrowserPersistLocalDocument.h" +#include "WebBrowserPersistDocumentParent.h" + +#include "mozilla/dom/HTMLInputElement.h" +#include "mozilla/dom/HTMLSharedElement.h" +#include "mozilla/dom/HTMLSharedObjectElement.h" +#include "mozilla/dom/TabParent.h" +#include "nsComponentManagerUtils.h" +#include "nsContentUtils.h" +#include "nsContentCID.h" +#include "nsCycleCollectionParticipant.h" +#include "nsFrameLoader.h" +#include "nsIComponentRegistrar.h" +#include "nsIContent.h" +#include "nsIDOMAttr.h" +#include "nsIDOMComment.h" +#include "nsIDOMDocument.h" +#include "nsIDOMHTMLAnchorElement.h" +#include "nsIDOMHTMLAppletElement.h" +#include "nsIDOMHTMLAreaElement.h" +#include "nsIDOMHTMLBaseElement.h" +#include "nsIDOMHTMLCollection.h" +#include "nsIDOMHTMLDocument.h" +#include "nsIDOMHTMLEmbedElement.h" +#include "nsIDOMHTMLFrameElement.h" +#include "nsIDOMHTMLIFrameElement.h" +#include "nsIDOMHTMLImageElement.h" +#include "nsIDOMHTMLInputElement.h" +#include "nsIDOMHTMLLinkElement.h" +#include "nsIDOMHTMLMediaElement.h" +#include "nsIDOMHTMLObjectElement.h" +#include "nsIDOMHTMLOptionElement.h" +#include "nsIDOMHTMLScriptElement.h" +#include "nsIDOMHTMLSourceElement.h" +#include "nsIDOMHTMLTextAreaElement.h" +#include "nsIDOMMozNamedAttrMap.h" +#include "nsIDOMNode.h" +#include "nsIDOMNodeFilter.h" +#include "nsIDOMNodeList.h" +#include "nsIDOMProcessingInstruction.h" +#include "nsIDOMTreeWalker.h" +#include "nsIDOMWindowUtils.h" +#include "nsIDocShell.h" +#include "nsIDocument.h" +#include "nsIDocumentEncoder.h" +#include "nsILoadContext.h" +#include "nsIProtocolHandler.h" +#include "nsISHEntry.h" +#include "nsISupportsPrimitives.h" +#include "nsITabParent.h" +#include "nsIWebBrowserPersist.h" +#include "nsIWebNavigation.h" +#include "nsIWebPageDescriptor.h" +#include "nsNetUtil.h" + +namespace mozilla { + +NS_IMPL_CYCLE_COLLECTING_ADDREF(WebBrowserPersistLocalDocument) +NS_IMPL_CYCLE_COLLECTING_RELEASE(WebBrowserPersistLocalDocument) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebBrowserPersistLocalDocument) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersistDocument) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION(WebBrowserPersistLocalDocument, mDocument) + + +WebBrowserPersistLocalDocument::WebBrowserPersistLocalDocument(nsIDocument* aDocument) +: mDocument(aDocument) +, mPersistFlags(0) +{ + MOZ_ASSERT(mDocument); +} + +WebBrowserPersistLocalDocument::~WebBrowserPersistLocalDocument() +{ +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::SetPersistFlags(uint32_t aFlags) +{ + mPersistFlags = aFlags; + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetPersistFlags(uint32_t* aFlags) +{ + *aFlags = mPersistFlags; + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetIsPrivate(bool* aIsPrivate) +{ + nsCOMPtr<nsILoadContext> privacyContext = mDocument->GetLoadContext(); + *aIsPrivate = privacyContext && privacyContext->UsePrivateBrowsing(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetDocumentURI(nsACString& aURISpec) +{ + nsCOMPtr<nsIURI> uri = mDocument->GetDocumentURI(); + if (!uri) { + return NS_ERROR_UNEXPECTED; + } + return uri->GetSpec(aURISpec); +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetBaseURI(nsACString& aURISpec) +{ + nsCOMPtr<nsIURI> uri = GetBaseURI(); + if (!uri) { + return NS_ERROR_UNEXPECTED; + } + return uri->GetSpec(aURISpec); +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetContentType(nsACString& aContentType) +{ + nsAutoString utf16Type; + nsresult rv; + + rv = mDocument->GetContentType(utf16Type); + NS_ENSURE_SUCCESS(rv, rv); + aContentType = NS_ConvertUTF16toUTF8(utf16Type); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetCharacterSet(nsACString& aCharSet) +{ + aCharSet = GetCharacterSet(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetTitle(nsAString& aTitle) +{ + nsAutoString titleBuffer; + mDocument->GetTitle(titleBuffer); + aTitle = titleBuffer; + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetReferrer(nsAString& aReferrer) +{ + mDocument->GetReferrer(aReferrer); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetContentDisposition(nsAString& aCD) +{ + nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetDefaultView(); + if (NS_WARN_IF(!window)) { + aCD.SetIsVoid(true); + return NS_OK; + } + nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window); + if (NS_WARN_IF(!utils)) { + aCD.SetIsVoid(true); + return NS_OK; + } + nsresult rv = utils->GetDocumentMetadata( + NS_LITERAL_STRING("content-disposition"), aCD); + if (NS_WARN_IF(NS_FAILED(rv))) { + aCD.SetIsVoid(true); + } + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetCacheKey(uint32_t* aKey) +{ + nsCOMPtr<nsISHEntry> history = GetHistory(); + if (!history) { + *aKey = 0; + return NS_OK; + } + nsCOMPtr<nsISupports> abstractKey; + nsresult rv = history->GetCacheKey(getter_AddRefs(abstractKey)); + if (NS_WARN_IF(NS_FAILED(rv)) || !abstractKey) { + *aKey = 0; + return NS_OK; + } + nsCOMPtr<nsISupportsPRUint32> u32 = do_QueryInterface(abstractKey); + if (NS_WARN_IF(!u32)) { + *aKey = 0; + return NS_OK; + } + return u32->GetData(aKey); +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetPostData(nsIInputStream** aStream) +{ + nsCOMPtr<nsISHEntry> history = GetHistory(); + if (!history) { + *aStream = nullptr; + return NS_OK; + } + return history->GetPostData(aStream); +} + +already_AddRefed<nsISHEntry> +WebBrowserPersistLocalDocument::GetHistory() +{ + nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetDefaultView(); + if (NS_WARN_IF(!window)) { + return nullptr; + } + nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(window); + if (NS_WARN_IF(!webNav)) { + return nullptr; + } + nsCOMPtr<nsIWebPageDescriptor> desc = do_QueryInterface(webNav); + if (NS_WARN_IF(!desc)) { + return nullptr; + } + nsCOMPtr<nsISupports> curDesc; + nsresult rv = desc->GetCurrentDescriptor(getter_AddRefs(curDesc)); + // This can fail if, e.g., the document is a Print Preview. + if (NS_FAILED(rv) || NS_WARN_IF(!curDesc)) { + return nullptr; + } + nsCOMPtr<nsISHEntry> history = do_QueryInterface(curDesc); + return history.forget(); +} + +const nsCString& +WebBrowserPersistLocalDocument::GetCharacterSet() const +{ + return mDocument->GetDocumentCharacterSet(); +} + +uint32_t +WebBrowserPersistLocalDocument::GetPersistFlags() const +{ + return mPersistFlags; +} + + +already_AddRefed<nsIURI> +WebBrowserPersistLocalDocument::GetBaseURI() const +{ + return mDocument->GetBaseURI(); +} + + +namespace { + +// Helper class for ReadResources(). +class ResourceReader final : public nsIWebBrowserPersistDocumentReceiver { +public: + ResourceReader(WebBrowserPersistLocalDocument* aParent, + nsIWebBrowserPersistResourceVisitor* aVisitor); + nsresult OnWalkDOMNode(nsIDOMNode* aNode); + + // This is called both to indicate the end of the document walk + // and when a subdocument is (maybe asynchronously) sent to the + // visitor. The call to EndVisit needs to happen after both of + // those have finished. + void DocumentDone(nsresult aStatus); + + NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER + NS_DECL_ISUPPORTS + +private: + RefPtr<WebBrowserPersistLocalDocument> mParent; + nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor; + nsCOMPtr<nsIURI> mCurrentBaseURI; + uint32_t mPersistFlags; + + // The number of DocumentDone calls after which EndVisit will be + // called on the visitor. Counts the main document if it's still + // being walked and any outstanding asynchronous subdocument + // StartPersistence calls. + size_t mOutstandingDocuments; + // Collects the status parameters to DocumentDone calls. + nsresult mEndStatus; + + nsresult OnWalkURI(const nsACString& aURISpec); + nsresult OnWalkURI(nsIURI* aURI); + nsresult OnWalkAttribute(nsIDOMNode* aNode, + const char* aAttribute, + const char* aNamespaceURI = ""); + nsresult OnWalkSubframe(nsIDOMNode* aNode); + + bool IsFlagSet(uint32_t aFlag) const { + return mParent->GetPersistFlags() & aFlag; + } + + ~ResourceReader(); + + using IWBP = nsIWebBrowserPersist; +}; + +NS_IMPL_ISUPPORTS(ResourceReader, nsIWebBrowserPersistDocumentReceiver) + +ResourceReader::ResourceReader(WebBrowserPersistLocalDocument* aParent, + nsIWebBrowserPersistResourceVisitor* aVisitor) +: mParent(aParent) +, mVisitor(aVisitor) +, mCurrentBaseURI(aParent->GetBaseURI()) +, mPersistFlags(aParent->GetPersistFlags()) +, mOutstandingDocuments(1) +, mEndStatus(NS_OK) +{ + MOZ_ASSERT(mCurrentBaseURI); +} + +ResourceReader::~ResourceReader() +{ + MOZ_ASSERT(mOutstandingDocuments == 0); +} + +void +ResourceReader::DocumentDone(nsresult aStatus) +{ + MOZ_ASSERT(mOutstandingDocuments > 0); + if (NS_SUCCEEDED(mEndStatus)) { + mEndStatus = aStatus; + } + if (--mOutstandingDocuments == 0) { + mVisitor->EndVisit(mParent, mEndStatus); + } +} + +nsresult +ResourceReader::OnWalkSubframe(nsIDOMNode* aNode) +{ + nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(aNode); + NS_ENSURE_STATE(loaderOwner); + RefPtr<nsFrameLoader> loader = loaderOwner->GetFrameLoader(); + NS_ENSURE_STATE(loader); + + ++mOutstandingDocuments; + // Pass in 0 as the outer window ID so that we start + // persisting the root of this subframe, and not some other + // subframe child of this subframe. + nsresult rv = loader->StartPersistence(0, this); + if (NS_FAILED(rv)) { + if (rv == NS_ERROR_NO_CONTENT) { + // Just ignore frames with no content document. + rv = NS_OK; + } + // StartPersistence won't eventually call this if it failed, + // so this does so (to keep mOutstandingDocuments correct). + DocumentDone(rv); + } + return rv; +} + +NS_IMETHODIMP +ResourceReader::OnDocumentReady(nsIWebBrowserPersistDocument* aDocument) +{ + mVisitor->VisitDocument(mParent, aDocument); + DocumentDone(NS_OK); + return NS_OK; +} + +NS_IMETHODIMP +ResourceReader::OnError(nsresult aFailure) +{ + DocumentDone(aFailure); + return NS_OK; +} + +nsresult +ResourceReader::OnWalkURI(nsIURI* aURI) +{ + // Test if this URI should be persisted. By default + // we should assume the URI is persistable. + bool doNotPersistURI; + nsresult rv = NS_URIChainHasFlags(aURI, + nsIProtocolHandler::URI_NON_PERSISTABLE, + &doNotPersistURI); + if (NS_SUCCEEDED(rv) && doNotPersistURI) { + return NS_OK; + } + + nsAutoCString stringURI; + rv = aURI->GetSpec(stringURI); + NS_ENSURE_SUCCESS(rv, rv); + return mVisitor->VisitResource(mParent, stringURI); +} + +nsresult +ResourceReader::OnWalkURI(const nsACString& aURISpec) +{ + nsresult rv; + nsCOMPtr<nsIURI> uri; + + rv = NS_NewURI(getter_AddRefs(uri), + aURISpec, + mParent->GetCharacterSet().get(), + mCurrentBaseURI); + NS_ENSURE_SUCCESS(rv, rv); + return OnWalkURI(uri); +} + +static nsresult +ExtractAttribute(nsIDOMNode* aNode, + const char* aAttribute, + const char* aNamespaceURI, + nsCString& aValue) +{ + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode); + MOZ_ASSERT(element); + + // Find the named URI attribute on the (element) node and store + // a reference to the URI that maps onto a local file name + + nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap; + nsresult rv = element->GetAttributes(getter_AddRefs(attrMap)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI); + NS_ConvertASCIItoUTF16 attribute(aAttribute); + nsCOMPtr<nsIDOMAttr> attr; + rv = attrMap->GetNamedItemNS(namespaceURI, attribute, getter_AddRefs(attr)); + NS_ENSURE_SUCCESS(rv, rv); + if (attr) { + nsAutoString value; + rv = attr->GetValue(value); + NS_ENSURE_SUCCESS(rv, rv); + aValue = NS_ConvertUTF16toUTF8(value); + } else { + aValue.Truncate(); + } + return NS_OK; +} + +nsresult +ResourceReader::OnWalkAttribute(nsIDOMNode* aNode, + const char* aAttribute, + const char* aNamespaceURI) +{ + nsAutoCString uriSpec; + nsresult rv = ExtractAttribute(aNode, aAttribute, aNamespaceURI, uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + if (uriSpec.IsEmpty()) { + return NS_OK; + } + return OnWalkURI(uriSpec); +} + +static nsresult +GetXMLStyleSheetLink(nsIDOMProcessingInstruction *aPI, nsAString &aHref) +{ + nsresult rv; + nsAutoString data; + rv = aPI->GetData(data); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::href, aHref); + return NS_OK; +} + +nsresult +ResourceReader::OnWalkDOMNode(nsIDOMNode* aNode) +{ + nsresult rv; + + // Fixup xml-stylesheet processing instructions + nsCOMPtr<nsIDOMProcessingInstruction> nodeAsPI = do_QueryInterface(aNode); + if (nodeAsPI) { + nsAutoString target; + rv = nodeAsPI->GetTarget(target); + NS_ENSURE_SUCCESS(rv, rv); + if (target.EqualsLiteral("xml-stylesheet")) { + nsAutoString href; + GetXMLStyleSheetLink(nodeAsPI, href); + if (!href.IsEmpty()) { + return OnWalkURI(NS_ConvertUTF16toUTF8(href)); + } + } + return NS_OK; + } + + nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); + if (!content) { + return NS_OK; + } + + // Test the node to see if it's an image, frame, iframe, css, js + nsCOMPtr<nsIDOMHTMLImageElement> nodeAsImage = do_QueryInterface(aNode); + if (nodeAsImage) { + return OnWalkAttribute(aNode, "src"); + } + + if (content->IsSVGElement(nsGkAtoms::img)) { + return OnWalkAttribute(aNode, "href", "http://www.w3.org/1999/xlink"); + } + + nsCOMPtr<nsIDOMHTMLMediaElement> nodeAsMedia = do_QueryInterface(aNode); + if (nodeAsMedia) { + return OnWalkAttribute(aNode, "src"); + } + nsCOMPtr<nsIDOMHTMLSourceElement> nodeAsSource = do_QueryInterface(aNode); + if (nodeAsSource) { + return OnWalkAttribute(aNode, "src"); + } + + if (content->IsHTMLElement(nsGkAtoms::body)) { + return OnWalkAttribute(aNode, "background"); + } + + if (content->IsHTMLElement(nsGkAtoms::table)) { + return OnWalkAttribute(aNode, "background"); + } + + if (content->IsHTMLElement(nsGkAtoms::tr)) { + return OnWalkAttribute(aNode, "background"); + } + + if (content->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) { + return OnWalkAttribute(aNode, "background"); + } + + nsCOMPtr<nsIDOMHTMLScriptElement> nodeAsScript = do_QueryInterface(aNode); + if (nodeAsScript) { + return OnWalkAttribute(aNode, "src"); + } + + if (content->IsSVGElement(nsGkAtoms::script)) { + return OnWalkAttribute(aNode, "href", "http://www.w3.org/1999/xlink"); + } + + nsCOMPtr<nsIDOMHTMLEmbedElement> nodeAsEmbed = do_QueryInterface(aNode); + if (nodeAsEmbed) { + return OnWalkAttribute(aNode, "src"); + } + + nsCOMPtr<nsIDOMHTMLObjectElement> nodeAsObject = do_QueryInterface(aNode); + if (nodeAsObject) { + return OnWalkAttribute(aNode, "data"); + } + + nsCOMPtr<nsIDOMHTMLAppletElement> nodeAsApplet = do_QueryInterface(aNode); + if (nodeAsApplet) { + // For an applet, relative URIs are resolved relative to the + // codebase (which is resolved relative to the base URI). + nsCOMPtr<nsIURI> oldBase = mCurrentBaseURI; + nsAutoString codebase; + rv = nodeAsApplet->GetCodeBase(codebase); + NS_ENSURE_SUCCESS(rv, rv); + if (!codebase.IsEmpty()) { + nsCOMPtr<nsIURI> baseURI; + rv = NS_NewURI(getter_AddRefs(baseURI), codebase, + mParent->GetCharacterSet().get(), mCurrentBaseURI); + NS_ENSURE_SUCCESS(rv, rv); + if (baseURI) { + mCurrentBaseURI = baseURI; + // Must restore this before returning (or ENSURE'ing). + } + } + + // We only store 'code' locally if there is no 'archive', + // otherwise we assume the archive file(s) contains it (bug 430283). + nsAutoCString archiveAttr; + rv = ExtractAttribute(aNode, "archive", "", archiveAttr); + if (NS_SUCCEEDED(rv)) { + if (!archiveAttr.IsEmpty()) { + rv = OnWalkURI(archiveAttr); + } else { + rv = OnWalkAttribute(aNode, "core"); + } + } + + // restore the base URI we really want to have + mCurrentBaseURI = oldBase; + return rv; + } + + nsCOMPtr<nsIDOMHTMLLinkElement> nodeAsLink = do_QueryInterface(aNode); + if (nodeAsLink) { + // Test if the link has a rel value indicating it to be a stylesheet + nsAutoString linkRel; + if (NS_SUCCEEDED(nodeAsLink->GetRel(linkRel)) && !linkRel.IsEmpty()) { + nsReadingIterator<char16_t> start; + nsReadingIterator<char16_t> end; + nsReadingIterator<char16_t> current; + + linkRel.BeginReading(start); + linkRel.EndReading(end); + + // Walk through space delimited string looking for "stylesheet" + for (current = start; current != end; ++current) { + // Ignore whitespace + if (nsCRT::IsAsciiSpace(*current)) { + continue; + } + + // Grab the next space delimited word + nsReadingIterator<char16_t> startWord = current; + do { + ++current; + } while (current != end && !nsCRT::IsAsciiSpace(*current)); + + // Store the link for fix up if it says "stylesheet" + if (Substring(startWord, current) + .LowerCaseEqualsLiteral("stylesheet")) { + OnWalkAttribute(aNode, "href"); + return NS_OK; + } + if (current == end) { + break; + } + } + } + return NS_OK; + } + + nsCOMPtr<nsIDOMHTMLFrameElement> nodeAsFrame = do_QueryInterface(aNode); + if (nodeAsFrame) { + return OnWalkSubframe(aNode); + } + + nsCOMPtr<nsIDOMHTMLIFrameElement> nodeAsIFrame = do_QueryInterface(aNode); + if (nodeAsIFrame && !(mPersistFlags & + IWBP::PERSIST_FLAGS_IGNORE_IFRAMES)) { + return OnWalkSubframe(aNode); + } + + nsCOMPtr<nsIDOMHTMLInputElement> nodeAsInput = do_QueryInterface(aNode); + if (nodeAsInput) { + return OnWalkAttribute(aNode, "src"); + } + + return NS_OK; +} + +// Helper class for node rewriting in writeContent(). +class PersistNodeFixup final : public nsIDocumentEncoderNodeFixup { +public: + PersistNodeFixup(WebBrowserPersistLocalDocument* aParent, + nsIWebBrowserPersistURIMap* aMap, + nsIURI* aTargetURI); + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOCUMENTENCODERNODEFIXUP +private: + virtual ~PersistNodeFixup() { } + RefPtr<WebBrowserPersistLocalDocument> mParent; + nsClassHashtable<nsCStringHashKey, nsCString> mMap; + nsCOMPtr<nsIURI> mCurrentBaseURI; + nsCOMPtr<nsIURI> mTargetBaseURI; + + bool IsFlagSet(uint32_t aFlag) const { + return mParent->GetPersistFlags() & aFlag; + } + + nsresult GetNodeToFixup(nsIDOMNode* aNodeIn, nsIDOMNode** aNodeOut); + nsresult FixupURI(nsAString& aURI); + nsresult FixupAttribute(nsIDOMNode* aNode, + const char* aAttribute, + const char* aNamespaceURI = ""); + nsresult FixupAnchor(nsIDOMNode* aNode); + nsresult FixupXMLStyleSheetLink(nsIDOMProcessingInstruction* aPI, + const nsAString& aHref); + + using IWBP = nsIWebBrowserPersist; +}; + +NS_IMPL_ISUPPORTS(PersistNodeFixup, nsIDocumentEncoderNodeFixup) + +PersistNodeFixup::PersistNodeFixup(WebBrowserPersistLocalDocument* aParent, + nsIWebBrowserPersistURIMap* aMap, + nsIURI* aTargetURI) +: mParent(aParent) +, mCurrentBaseURI(aParent->GetBaseURI()) +, mTargetBaseURI(aTargetURI) +{ + if (aMap) { + uint32_t mapSize; + nsresult rv = aMap->GetNumMappedURIs(&mapSize); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + NS_ENSURE_SUCCESS_VOID(rv); + for (uint32_t i = 0; i < mapSize; ++i) { + nsAutoCString urlFrom; + nsCString* urlTo = new nsCString(); + + rv = aMap->GetURIMapping(i, urlFrom, *urlTo); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_SUCCEEDED(rv)) { + mMap.Put(urlFrom, urlTo); + } + } + } +} + +nsresult +PersistNodeFixup::GetNodeToFixup(nsIDOMNode *aNodeIn, nsIDOMNode **aNodeOut) +{ + // Avoid mixups in FixupNode that could leak objects; this goes + // against the usual out parameter convention, but it's a private + // method so shouldn't be a problem. + MOZ_ASSERT(!*aNodeOut); + + if (!IsFlagSet(IWBP::PERSIST_FLAGS_FIXUP_ORIGINAL_DOM)) { + nsresult rv = aNodeIn->CloneNode(false, 1, aNodeOut); + NS_ENSURE_SUCCESS(rv, rv); + } else { + NS_ADDREF(*aNodeOut = aNodeIn); + } + nsCOMPtr<nsIDOMHTMLElement> element(do_QueryInterface(*aNodeOut)); + if (element) { + // Make sure this is not XHTML + nsAutoString namespaceURI; + element->GetNamespaceURI(namespaceURI); + if (namespaceURI.IsEmpty()) { + // This is a tag-soup node. It may have a _base_href attribute + // stuck on it by the parser, but since we're fixing up all URIs + // relative to the overall document base that will screw us up. + // Just remove the _base_href. + element->RemoveAttribute(NS_LITERAL_STRING("_base_href")); + } + } + return NS_OK; +} + +nsresult +PersistNodeFixup::FixupURI(nsAString &aURI) +{ + // get the current location of the file (absolutized) + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI, + mParent->GetCharacterSet().get(), mCurrentBaseURI); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString spec; + rv = uri->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + const nsCString* replacement = mMap.Get(spec); + if (!replacement) { + // Note that most callers ignore this "failure". + return NS_ERROR_FAILURE; + } + if (!replacement->IsEmpty()) { + aURI = NS_ConvertUTF8toUTF16(*replacement); + } + return NS_OK; +} + +nsresult +PersistNodeFixup::FixupAttribute(nsIDOMNode* aNode, + const char* aAttribute, + const char* aNamespaceURI) +{ + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode); + MOZ_ASSERT(element); + + nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap; + nsresult rv = element->GetAttributes(getter_AddRefs(attrMap)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + NS_ConvertASCIItoUTF16 attribute(aAttribute); + NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI); + nsCOMPtr<nsIDOMAttr> attr; + rv = attrMap->GetNamedItemNS(namespaceURI, attribute, getter_AddRefs(attr)); + if (attr) { + nsString uri; + attr->GetValue(uri); + rv = FixupURI(uri); + if (NS_SUCCEEDED(rv)) { + attr->SetValue(uri); + } + } + + return rv; +} + +nsresult +PersistNodeFixup::FixupAnchor(nsIDOMNode *aNode) +{ + if (IsFlagSet(IWBP::PERSIST_FLAGS_DONT_FIXUP_LINKS)) { + return NS_OK; + } + + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode); + MOZ_ASSERT(element); + + nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap; + nsresult rv = element->GetAttributes(getter_AddRefs(attrMap)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Make all anchor links absolute so they point off onto the Internet + nsString attribute(NS_LITERAL_STRING("href")); + nsCOMPtr<nsIDOMAttr> attr; + rv = attrMap->GetNamedItem(attribute, getter_AddRefs(attr)); + if (attr) { + nsString oldValue; + attr->GetValue(oldValue); + NS_ConvertUTF16toUTF8 oldCValue(oldValue); + + // Skip empty values and self-referencing bookmarks + if (oldCValue.IsEmpty() || oldCValue.CharAt(0) == '#') { + return NS_OK; + } + + // if saving file to same location, we don't need to do any fixup + bool isEqual; + if (mTargetBaseURI && + NS_SUCCEEDED(mCurrentBaseURI->Equals(mTargetBaseURI, &isEqual)) && + isEqual) { + return NS_OK; + } + + nsCOMPtr<nsIURI> relativeURI; + relativeURI = IsFlagSet(IWBP::PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION) + ? mTargetBaseURI : mCurrentBaseURI; + // Make a new URI to replace the current one + nsCOMPtr<nsIURI> newURI; + rv = NS_NewURI(getter_AddRefs(newURI), oldCValue, + mParent->GetCharacterSet().get(), relativeURI); + if (NS_SUCCEEDED(rv) && newURI) { + newURI->SetUserPass(EmptyCString()); + nsAutoCString uriSpec; + rv = newURI->GetSpec(uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + attr->SetValue(NS_ConvertUTF8toUTF16(uriSpec)); + } + } + + return NS_OK; +} + +static void +AppendXMLAttr(const nsAString& key, const nsAString& aValue, nsAString& aBuffer) +{ + if (!aBuffer.IsEmpty()) { + aBuffer.Append(' '); + } + aBuffer.Append(key); + aBuffer.AppendLiteral("=\""); + for (size_t i = 0; i < aValue.Length(); ++i) { + switch (aValue[i]) { + case '&': + aBuffer.AppendLiteral("&"); + break; + case '<': + aBuffer.AppendLiteral("<"); + break; + case '>': + aBuffer.AppendLiteral(">"); + break; + case '"': + aBuffer.AppendLiteral("""); + break; + default: + aBuffer.Append(aValue[i]); + break; + } + } + aBuffer.Append('"'); +} + +nsresult +PersistNodeFixup::FixupXMLStyleSheetLink(nsIDOMProcessingInstruction* aPI, + const nsAString& aHref) +{ + NS_ENSURE_ARG_POINTER(aPI); + nsresult rv = NS_OK; + + nsAutoString data; + rv = aPI->GetData(data); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + nsAutoString href; + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::href, + href); + + // Construct and set a new data value for the xml-stylesheet + if (!aHref.IsEmpty() && !href.IsEmpty()) + { + nsAutoString alternate; + nsAutoString charset; + nsAutoString title; + nsAutoString type; + nsAutoString media; + + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::alternate, + alternate); + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::charset, + charset); + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::title, + title); + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::type, + type); + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::media, + media); + + nsAutoString newData; + AppendXMLAttr(NS_LITERAL_STRING("href"), aHref, newData); + if (!title.IsEmpty()) { + AppendXMLAttr(NS_LITERAL_STRING("title"), title, newData); + } + if (!media.IsEmpty()) { + AppendXMLAttr(NS_LITERAL_STRING("media"), media, newData); + } + if (!type.IsEmpty()) { + AppendXMLAttr(NS_LITERAL_STRING("type"), type, newData); + } + if (!charset.IsEmpty()) { + AppendXMLAttr(NS_LITERAL_STRING("charset"), charset, newData); + } + if (!alternate.IsEmpty()) { + AppendXMLAttr(NS_LITERAL_STRING("alternate"), alternate, newData); + } + aPI->SetData(newData); + } + + return rv; +} + +NS_IMETHODIMP +PersistNodeFixup::FixupNode(nsIDOMNode *aNodeIn, + bool *aSerializeCloneKids, + nsIDOMNode **aNodeOut) +{ + *aNodeOut = nullptr; + *aSerializeCloneKids = false; + + uint16_t type; + nsresult rv = aNodeIn->GetNodeType(&type); + NS_ENSURE_SUCCESS(rv, rv); + if (type != nsIDOMNode::ELEMENT_NODE && + type != nsIDOMNode::PROCESSING_INSTRUCTION_NODE) { + return NS_OK; + } + + // Fixup xml-stylesheet processing instructions + nsCOMPtr<nsIDOMProcessingInstruction> nodeAsPI = do_QueryInterface(aNodeIn); + if (nodeAsPI) { + nsAutoString target; + nodeAsPI->GetTarget(target); + if (target.EqualsLiteral("xml-stylesheet")) + { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + nsCOMPtr<nsIDOMProcessingInstruction> outNode = + do_QueryInterface(*aNodeOut); + nsAutoString href; + GetXMLStyleSheetLink(nodeAsPI, href); + if (!href.IsEmpty()) { + FixupURI(href); + FixupXMLStyleSheetLink(outNode, href); + } + } + } + return NS_OK; + } + + // BASE elements are replaced by a comment so relative links are not hosed. + if (!IsFlagSet(IWBP::PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS)) { + nsCOMPtr<nsIDOMHTMLBaseElement> nodeAsBase = do_QueryInterface(aNodeIn); + if (nodeAsBase) { + nsCOMPtr<nsIDOMDocument> ownerDocument; + auto* base = static_cast<dom::HTMLSharedElement*>(nodeAsBase.get()); + base->GetOwnerDocument(getter_AddRefs(ownerDocument)); + if (ownerDocument) { + nsAutoString href; + base->GetHref(href); // Doesn't matter if this fails + nsCOMPtr<nsIDOMComment> comment; + nsAutoString commentText; + commentText.AssignLiteral(" base "); + if (!href.IsEmpty()) { + commentText += NS_LITERAL_STRING("href=\"") + href + + NS_LITERAL_STRING("\" "); + } + rv = ownerDocument->CreateComment(commentText, + getter_AddRefs(comment)); + if (comment) { + return CallQueryInterface(comment, aNodeOut); + } + } + return NS_OK; + } + } + + nsCOMPtr<nsIContent> content = do_QueryInterface(aNodeIn); + if (!content) { + return NS_OK; + } + + // Fix up href and file links in the elements + nsCOMPtr<nsIDOMHTMLAnchorElement> nodeAsAnchor = do_QueryInterface(aNodeIn); + if (nodeAsAnchor) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAnchor(*aNodeOut); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLAreaElement> nodeAsArea = do_QueryInterface(aNodeIn); + if (nodeAsArea) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAnchor(*aNodeOut); + } + return rv; + } + + if (content->IsHTMLElement(nsGkAtoms::body)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "background"); + } + return rv; + } + + if (content->IsHTMLElement(nsGkAtoms::table)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "background"); + } + return rv; + } + + if (content->IsHTMLElement(nsGkAtoms::tr)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "background"); + } + return rv; + } + + if (content->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "background"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLImageElement> nodeAsImage = do_QueryInterface(aNodeIn); + if (nodeAsImage) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + // Disable image loads + nsCOMPtr<nsIImageLoadingContent> imgCon = + do_QueryInterface(*aNodeOut); + if (imgCon) { + imgCon->SetLoadingEnabled(false); + } + FixupAnchor(*aNodeOut); + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLMediaElement> nodeAsMedia = do_QueryInterface(aNodeIn); + if (nodeAsMedia) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLSourceElement> nodeAsSource = do_QueryInterface(aNodeIn); + if (nodeAsSource) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + if (content->IsSVGElement(nsGkAtoms::img)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + // Disable image loads + nsCOMPtr<nsIImageLoadingContent> imgCon = + do_QueryInterface(*aNodeOut); + if (imgCon) + imgCon->SetLoadingEnabled(false); + + // FixupAnchor(*aNodeOut); // XXXjwatt: is this line needed? + FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLScriptElement> nodeAsScript = do_QueryInterface(aNodeIn); + if (nodeAsScript) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + if (content->IsSVGElement(nsGkAtoms::script)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLEmbedElement> nodeAsEmbed = do_QueryInterface(aNodeIn); + if (nodeAsEmbed) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLObjectElement> nodeAsObject = do_QueryInterface(aNodeIn); + if (nodeAsObject) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "data"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLAppletElement> nodeAsApplet = do_QueryInterface(aNodeIn); + if (nodeAsApplet) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + nsCOMPtr<nsIDOMHTMLAppletElement> newApplet = + do_QueryInterface(*aNodeOut); + // For an applet, relative URIs are resolved relative to the + // codebase (which is resolved relative to the base URI). + nsCOMPtr<nsIURI> oldBase = mCurrentBaseURI; + nsAutoString codebase; + nodeAsApplet->GetCodeBase(codebase); + if (!codebase.IsEmpty()) { + nsCOMPtr<nsIURI> baseURI; + NS_NewURI(getter_AddRefs(baseURI), codebase, + mParent->GetCharacterSet().get(), mCurrentBaseURI); + if (baseURI) { + mCurrentBaseURI = baseURI; + } + } + // Unset the codebase too, since we'll correctly relativize the + // code and archive paths. + static_cast<dom::HTMLSharedObjectElement*>(newApplet.get())-> + RemoveAttribute(NS_LITERAL_STRING("codebase")); + FixupAttribute(*aNodeOut, "code"); + FixupAttribute(*aNodeOut, "archive"); + // restore the base URI we really want to have + mCurrentBaseURI = oldBase; + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLLinkElement> nodeAsLink = do_QueryInterface(aNodeIn); + if (nodeAsLink) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + // First see if the link represents linked content + rv = FixupAttribute(*aNodeOut, "href"); + if (NS_FAILED(rv)) { + // Perhaps this link is actually an anchor to related content + FixupAnchor(*aNodeOut); + } + // TODO if "type" attribute == "text/css" + // fixup stylesheet + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLFrameElement> nodeAsFrame = do_QueryInterface(aNodeIn); + if (nodeAsFrame) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLIFrameElement> nodeAsIFrame = do_QueryInterface(aNodeIn); + if (nodeAsIFrame) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLInputElement> nodeAsInput = do_QueryInterface(aNodeIn); + if (nodeAsInput) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + // Disable image loads + nsCOMPtr<nsIImageLoadingContent> imgCon = + do_QueryInterface(*aNodeOut); + if (imgCon) { + imgCon->SetLoadingEnabled(false); + } + + FixupAttribute(*aNodeOut, "src"); + + nsAutoString valueStr; + NS_NAMED_LITERAL_STRING(valueAttr, "value"); + // Update element node attributes with user-entered form state + nsCOMPtr<nsIContent> content = do_QueryInterface(*aNodeOut); + RefPtr<dom::HTMLInputElement> outElt = + dom::HTMLInputElement::FromContentOrNull(content); + nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(*aNodeOut); + switch (formControl->GetType()) { + case NS_FORM_INPUT_EMAIL: + case NS_FORM_INPUT_SEARCH: + case NS_FORM_INPUT_TEXT: + case NS_FORM_INPUT_TEL: + case NS_FORM_INPUT_URL: + case NS_FORM_INPUT_NUMBER: + case NS_FORM_INPUT_RANGE: + case NS_FORM_INPUT_DATE: + case NS_FORM_INPUT_TIME: + case NS_FORM_INPUT_COLOR: + nodeAsInput->GetValue(valueStr); + // Avoid superfluous value="" serialization + if (valueStr.IsEmpty()) + outElt->RemoveAttribute(valueAttr); + else + outElt->SetAttribute(valueAttr, valueStr); + break; + case NS_FORM_INPUT_CHECKBOX: + case NS_FORM_INPUT_RADIO: + bool checked; + nodeAsInput->GetChecked(&checked); + outElt->SetDefaultChecked(checked); + break; + default: + break; + } + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLTextAreaElement> nodeAsTextArea = do_QueryInterface(aNodeIn); + if (nodeAsTextArea) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + // Tell the document encoder to serialize the text child we create below + *aSerializeCloneKids = true; + + nsAutoString valueStr; + nodeAsTextArea->GetValue(valueStr); + + (*aNodeOut)->SetTextContent(valueStr); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLOptionElement> nodeAsOption = do_QueryInterface(aNodeIn); + if (nodeAsOption) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + nsCOMPtr<nsIDOMHTMLOptionElement> outElt = do_QueryInterface(*aNodeOut); + bool selected; + nodeAsOption->GetSelected(&selected); + outElt->SetDefaultSelected(selected); + } + return rv; + } + + return NS_OK; +} + +} // unnamed namespace + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::ReadResources(nsIWebBrowserPersistResourceVisitor* aVisitor) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIWebBrowserPersistResourceVisitor> visitor = aVisitor; + + nsCOMPtr<nsIDOMNode> docAsNode = do_QueryInterface(mDocument); + NS_ENSURE_TRUE(docAsNode, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDOMTreeWalker> walker; + nsCOMPtr<nsIDOMDocument> oldStyleDoc = do_QueryInterface(mDocument); + MOZ_ASSERT(oldStyleDoc); + rv = oldStyleDoc->CreateTreeWalker(docAsNode, + nsIDOMNodeFilter::SHOW_ELEMENT | + nsIDOMNodeFilter::SHOW_DOCUMENT | + nsIDOMNodeFilter::SHOW_PROCESSING_INSTRUCTION, + nullptr, 1, getter_AddRefs(walker)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + MOZ_ASSERT(walker); + + RefPtr<ResourceReader> reader = new ResourceReader(this, aVisitor); + nsCOMPtr<nsIDOMNode> currentNode; + walker->GetCurrentNode(getter_AddRefs(currentNode)); + while (currentNode) { + rv = reader->OnWalkDOMNode(currentNode); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + rv = walker->NextNode(getter_AddRefs(currentNode)); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + } + reader->DocumentDone(rv); + // If NS_FAILED(rv), it was / will be reported by an EndVisit call + // via DocumentDone. This method must return a failure if and + // only if visitor won't be invoked. + return NS_OK; +} + +static uint32_t +ConvertEncoderFlags(uint32_t aEncoderFlags) +{ + uint32_t encoderFlags = 0; + + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_SELECTION_ONLY) + encoderFlags |= nsIDocumentEncoder::OutputSelectionOnly; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_FORMATTED) + encoderFlags |= nsIDocumentEncoder::OutputFormatted; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_RAW) + encoderFlags |= nsIDocumentEncoder::OutputRaw; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_BODY_ONLY) + encoderFlags |= nsIDocumentEncoder::OutputBodyOnly; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_PREFORMATTED) + encoderFlags |= nsIDocumentEncoder::OutputPreformatted; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_WRAP) + encoderFlags |= nsIDocumentEncoder::OutputWrap; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_FORMAT_FLOWED) + encoderFlags |= nsIDocumentEncoder::OutputFormatFlowed; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ABSOLUTE_LINKS) + encoderFlags |= nsIDocumentEncoder::OutputAbsoluteLinks; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_BASIC_ENTITIES) + encoderFlags |= nsIDocumentEncoder::OutputEncodeBasicEntities; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES) + encoderFlags |= nsIDocumentEncoder::OutputEncodeLatin1Entities; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_HTML_ENTITIES) + encoderFlags |= nsIDocumentEncoder::OutputEncodeHTMLEntities; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_W3C_ENTITIES) + encoderFlags |= nsIDocumentEncoder::OutputEncodeW3CEntities; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_CR_LINEBREAKS) + encoderFlags |= nsIDocumentEncoder::OutputCRLineBreak; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_LF_LINEBREAKS) + encoderFlags |= nsIDocumentEncoder::OutputLFLineBreak; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_NOSCRIPT_CONTENT) + encoderFlags |= nsIDocumentEncoder::OutputNoScriptContent; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_NOFRAMES_CONTENT) + encoderFlags |= nsIDocumentEncoder::OutputNoFramesContent; + + return encoderFlags; +} + +static bool +ContentTypeEncoderExists(const nsACString& aType) +{ + nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE); + contractID.Append(aType); + + nsCOMPtr<nsIComponentRegistrar> registrar; + nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(registrar)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_SUCCEEDED(rv) && registrar) { + bool result; + rv = registrar->IsContractIDRegistered(contractID.get(), &result); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return NS_SUCCEEDED(rv) && result; + } + return false; +} + +void +WebBrowserPersistLocalDocument::DecideContentType(nsACString& aContentType) +{ + if (aContentType.IsEmpty()) { + if (NS_WARN_IF(NS_FAILED(GetContentType(aContentType)))) { + aContentType.Truncate(); + } + } + if (!aContentType.IsEmpty() && + !ContentTypeEncoderExists(aContentType)) { + aContentType.Truncate(); + } + if (aContentType.IsEmpty()) { + aContentType.AssignLiteral("text/html"); + } +} + +nsresult +WebBrowserPersistLocalDocument::GetDocEncoder(const nsACString& aContentType, + uint32_t aEncoderFlags, + nsIDocumentEncoder** aEncoder) +{ + nsresult rv; + nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE); + contractID.Append(aContentType); + nsCOMPtr<nsIDocumentEncoder> encoder = + do_CreateInstance(contractID.get(), &rv); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + rv = encoder->NativeInit(mDocument, + NS_ConvertASCIItoUTF16(aContentType), + ConvertEncoderFlags(aEncoderFlags)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + nsAutoCString charSet; + rv = GetCharacterSet(charSet); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + rv = encoder->SetCharset(charSet); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + encoder.forget(aEncoder); + return NS_OK; +} + + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::WriteContent( + nsIOutputStream* aStream, + nsIWebBrowserPersistURIMap* aMap, + const nsACString& aRequestedContentType, + uint32_t aEncoderFlags, + uint32_t aWrapColumn, + nsIWebBrowserPersistWriteCompletion* aCompletion) +{ + NS_ENSURE_ARG_POINTER(aStream); + NS_ENSURE_ARG_POINTER(aCompletion); + nsAutoCString contentType(aRequestedContentType); + DecideContentType(contentType); + + nsCOMPtr<nsIDocumentEncoder> encoder; + nsresult rv = GetDocEncoder(contentType, aEncoderFlags, + getter_AddRefs(encoder)); + NS_ENSURE_SUCCESS(rv, rv); + + if (aWrapColumn != 0 && (aEncoderFlags + & nsIWebBrowserPersist::ENCODE_FLAGS_WRAP)) { + encoder->SetWrapColumn(aWrapColumn); + } + + nsCOMPtr<nsIURI> targetURI; + if (aMap) { + nsAutoCString targetURISpec; + rv = aMap->GetTargetBaseURI(targetURISpec); + if (NS_SUCCEEDED(rv) && !targetURISpec.IsEmpty()) { + rv = NS_NewURI(getter_AddRefs(targetURI), targetURISpec, + /* charset: */ nullptr, /* base: */ nullptr); + NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED); + } else if (mPersistFlags & nsIWebBrowserPersist::PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION) { + return NS_ERROR_UNEXPECTED; + } + } + rv = encoder->SetNodeFixup(new PersistNodeFixup(this, aMap, targetURI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + rv = encoder->EncodeToStream(aStream); + aCompletion->OnFinish(this, aStream, contentType, rv); + return NS_OK; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h new file mode 100644 index 000000000..9110d6c35 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h @@ -0,0 +1,51 @@ +/* -*- 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 WebBrowserPersistLocalDocument_h__ +#define WebBrowserPersistLocalDocument_h__ + +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIDocument.h" +#include "nsIURI.h" +#include "nsIWebBrowserPersistDocument.h" + +class nsIDocumentEncoder; +class nsISHEntry; + +namespace mozilla { + +class WebBrowserPersistLocalDocument final + : public nsIWebBrowserPersistDocument +{ +public: + explicit WebBrowserPersistLocalDocument(nsIDocument* aDocument); + + const nsCString& GetCharacterSet() const; + uint32_t GetPersistFlags() const; + already_AddRefed<nsIURI> GetBaseURI() const; + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIWEBBROWSERPERSISTDOCUMENT + + NS_DECL_CYCLE_COLLECTION_CLASS(WebBrowserPersistLocalDocument) + +private: + nsCOMPtr<nsIDocument> mDocument; + uint32_t mPersistFlags; + + void DecideContentType(nsACString& aContentType); + nsresult GetDocEncoder(const nsACString& aContentType, + uint32_t aEncoderFlags, + nsIDocumentEncoder** aEncoder); + already_AddRefed<nsISHEntry> GetHistory(); + + virtual ~WebBrowserPersistLocalDocument(); +}; + +} // namespace mozilla + +#endif // WebBrowserPersistLocalDocument_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp new file mode 100644 index 000000000..c7dcca861 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp @@ -0,0 +1,186 @@ +/* -*- 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 "WebBrowserPersistRemoteDocument.h" +#include "WebBrowserPersistDocumentParent.h" +#include "WebBrowserPersistResourcesParent.h" +#include "WebBrowserPersistSerializeParent.h" +#include "mozilla/Unused.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(WebBrowserPersistRemoteDocument, + nsIWebBrowserPersistDocument) + +WebBrowserPersistRemoteDocument +::WebBrowserPersistRemoteDocument(WebBrowserPersistDocumentParent* aActor, + const Attrs& aAttrs, + nsIInputStream* aPostData) +: mActor(aActor) +, mAttrs(aAttrs) +, mPostData(aPostData) +{ +} + +WebBrowserPersistRemoteDocument::~WebBrowserPersistRemoteDocument() +{ + if (mActor) { + Unused << mActor->Send__delete__(mActor); + // That will call mActor->ActorDestroy, which calls this->ActorDestroy + // (whether or not the IPC send succeeds). + } + MOZ_ASSERT(!mActor); +} + +void +WebBrowserPersistRemoteDocument::ActorDestroy(void) +{ + mActor = nullptr; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetIsPrivate(bool* aIsPrivate) +{ + *aIsPrivate = mAttrs.isPrivate(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetDocumentURI(nsACString& aURISpec) +{ + aURISpec = mAttrs.documentURI(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetBaseURI(nsACString& aURISpec) +{ + aURISpec = mAttrs.baseURI(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetContentType(nsACString& aContentType) +{ + aContentType = mAttrs.contentType(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetCharacterSet(nsACString& aCharSet) +{ + aCharSet = mAttrs.characterSet(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetTitle(nsAString& aTitle) +{ + aTitle = mAttrs.title(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetReferrer(nsAString& aReferrer) +{ + aReferrer = mAttrs.referrer(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetContentDisposition(nsAString& aDisp) +{ + aDisp = mAttrs.contentDisposition(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetCacheKey(uint32_t* aCacheKey) +{ + *aCacheKey = mAttrs.cacheKey(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetPersistFlags(uint32_t* aFlags) +{ + *aFlags = mAttrs.persistFlags(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::SetPersistFlags(uint32_t aFlags) +{ + if (!mActor) { + return NS_ERROR_FAILURE; + } + if (!mActor->SendSetPersistFlags(aFlags)) { + return NS_ERROR_FAILURE; + } + mAttrs.persistFlags() = aFlags; + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetPostData(nsIInputStream** aStream) +{ + nsCOMPtr<nsIInputStream> stream = mPostData; + stream.forget(aStream); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::ReadResources(nsIWebBrowserPersistResourceVisitor* aVisitor) +{ + if (!mActor) { + return NS_ERROR_FAILURE; + } + RefPtr<WebBrowserPersistResourcesParent> subActor = + new WebBrowserPersistResourcesParent(this, aVisitor); + return mActor->SendPWebBrowserPersistResourcesConstructor( + subActor.forget().take()) + ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::WriteContent( + nsIOutputStream* aStream, + nsIWebBrowserPersistURIMap* aMap, + const nsACString& aRequestedContentType, + uint32_t aEncoderFlags, + uint32_t aWrapColumn, + nsIWebBrowserPersistWriteCompletion* aCompletion) +{ + if (!mActor) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + WebBrowserPersistURIMap map; + uint32_t numMappedURIs; + if (aMap) { + rv = aMap->GetTargetBaseURI(map.targetBaseURI()); + NS_ENSURE_SUCCESS(rv, rv); + rv = aMap->GetNumMappedURIs(&numMappedURIs); + NS_ENSURE_SUCCESS(rv, rv); + for (uint32_t i = 0; i < numMappedURIs; ++i) { + WebBrowserPersistURIMapEntry& nextEntry = + *(map.mapURIs().AppendElement()); + rv = aMap->GetURIMapping(i, nextEntry.mapFrom(), nextEntry.mapTo()); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + auto* subActor = new WebBrowserPersistSerializeParent(this, + aStream, + aCompletion); + nsCString requestedContentType(aRequestedContentType); // Sigh. + return mActor->SendPWebBrowserPersistSerializeConstructor( + subActor, map, requestedContentType, aEncoderFlags, aWrapColumn) + ? NS_OK : NS_ERROR_FAILURE; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.h b/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.h new file mode 100644 index 000000000..08d435903 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.h @@ -0,0 +1,55 @@ +/* -*- 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 WebBrowserPersistRemoteDocument_h__ +#define WebBrowserPersistRemoteDocument_h__ + +#include "mozilla/Maybe.h" +#include "mozilla/PWebBrowserPersistDocumentParent.h" +#include "nsCOMPtr.h" +#include "nsIWebBrowserPersistDocument.h" +#include "nsIInputStream.h" + +// This class is the XPCOM half of the glue between the +// nsIWebBrowserPersistDocument interface and a remote document; it is +// created by WebBrowserPersistDocumentParent when (and if) it +// receives the information needed to populate the interface's +// properties. +// +// This object has a normal refcounted lifetime. The corresponding +// IPC actor holds a weak reference to this class; when the last +// strong reference is released, it sends an IPC delete message and +// thereby removes that reference. + +namespace mozilla { + +class WebBrowserPersistDocumentParent; + +class WebBrowserPersistRemoteDocument final + : public nsIWebBrowserPersistDocument +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWEBBROWSERPERSISTDOCUMENT + +private: + using Attrs = WebBrowserPersistDocumentAttrs; + WebBrowserPersistDocumentParent* mActor; + Attrs mAttrs; + nsCOMPtr<nsIInputStream> mPostData; + + friend class WebBrowserPersistDocumentParent; + WebBrowserPersistRemoteDocument(WebBrowserPersistDocumentParent* aActor, + const Attrs& aAttrs, + nsIInputStream* aPostData); + ~WebBrowserPersistRemoteDocument(); + + void ActorDestroy(void); +}; + +} // namespace mozilla + +#endif // WebBrowserPersistRemoteDocument_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp new file mode 100644 index 000000000..c28f034c8 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp @@ -0,0 +1,73 @@ +/* -*- 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 "WebBrowserPersistResourcesChild.h" + +#include "WebBrowserPersistDocumentChild.h" +#include "mozilla/dom/ContentChild.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(WebBrowserPersistResourcesChild, + nsIWebBrowserPersistResourceVisitor) + +WebBrowserPersistResourcesChild::WebBrowserPersistResourcesChild() +{ +} + +WebBrowserPersistResourcesChild::~WebBrowserPersistResourcesChild() +{ +} + +NS_IMETHODIMP +WebBrowserPersistResourcesChild::VisitResource(nsIWebBrowserPersistDocument *aDocument, + const nsACString& aURI) +{ + nsCString copiedURI(aURI); // Yay, XPIDL/IPDL mismatch. + SendVisitResource(copiedURI); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistResourcesChild::VisitDocument(nsIWebBrowserPersistDocument* aDocument, + nsIWebBrowserPersistDocument* aSubDocument) +{ + auto* subActor = new WebBrowserPersistDocumentChild(); + // As a consequence of how PWebBrowserPersistDocumentConstructor + // can be sent by both the parent and the child, we must pass the + // aBrowser and outerWindowID arguments here, but the values are + // ignored by the parent. In particular, the TabChild in which + // persistence started does not necessarily exist at this point; + // see bug 1203602. + if (!Manager()->Manager() + ->SendPWebBrowserPersistDocumentConstructor(subActor, nullptr, 0)) { + // NOTE: subActor is freed at this point. + return NS_ERROR_FAILURE; + } + // ...but here, IPC won't free subActor until after this returns + // to the event loop. + + // The order of these two messages will be preserved, because + // they're the same toplevel protocol and priority. + // + // With this ordering, it's always the transition out of START + // state that causes a document's parent actor to be exposed to + // XPCOM (for both parent->child and child->parent construction), + // which simplifies the lifetime management. + SendVisitDocument(subActor); + subActor->Start(aSubDocument); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistResourcesChild::EndVisit(nsIWebBrowserPersistDocument *aDocument, + nsresult aStatus) +{ + Send__delete__(this, aStatus); + return NS_OK; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.h b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.h new file mode 100644 index 000000000..d6ab10d02 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.h @@ -0,0 +1,31 @@ +/* -*- 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 WebBrowserPersistResourcesChild_h__ +#define WebBrowserPersistResourcesChild_h__ + +#include "mozilla/PWebBrowserPersistResourcesChild.h" + +#include "nsIWebBrowserPersistDocument.h" + +namespace mozilla { + +class WebBrowserPersistResourcesChild final + : public PWebBrowserPersistResourcesChild + , public nsIWebBrowserPersistResourceVisitor +{ +public: + WebBrowserPersistResourcesChild(); + + NS_DECL_NSIWEBBROWSERPERSISTRESOURCEVISITOR + NS_DECL_ISUPPORTS +private: + virtual ~WebBrowserPersistResourcesChild(); +}; + +} // namespace mozilla + +#endif // WebBrowserPersistDocumentChild_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.cpp new file mode 100644 index 000000000..195cabb58 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.cpp @@ -0,0 +1,87 @@ +/* -*- 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 "WebBrowserPersistResourcesParent.h" + +#include "nsThreadUtils.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(WebBrowserPersistResourcesParent, + nsIWebBrowserPersistDocumentReceiver) + +WebBrowserPersistResourcesParent::WebBrowserPersistResourcesParent( + nsIWebBrowserPersistDocument* aDocument, + nsIWebBrowserPersistResourceVisitor* aVisitor) +: mDocument(aDocument) +, mVisitor(aVisitor) +{ + MOZ_ASSERT(aDocument); + MOZ_ASSERT(aVisitor); +} + +WebBrowserPersistResourcesParent::~WebBrowserPersistResourcesParent() +{ +} + +void +WebBrowserPersistResourcesParent::ActorDestroy(ActorDestroyReason aWhy) +{ + if (aWhy != Deletion && mVisitor) { + // See comment in WebBrowserPersistDocumentParent::ActorDestroy + // (or bug 1202887) for why this is deferred. + nsCOMPtr<nsIRunnable> errorLater = NewRunnableMethod + <nsCOMPtr<nsIWebBrowserPersistDocument>, nsresult> + (mVisitor, &nsIWebBrowserPersistResourceVisitor::EndVisit, + mDocument, NS_ERROR_FAILURE); + NS_DispatchToCurrentThread(errorLater); + } + mVisitor = nullptr; +} + +bool +WebBrowserPersistResourcesParent::Recv__delete__(const nsresult& aStatus) +{ + mVisitor->EndVisit(mDocument, aStatus); + mVisitor = nullptr; + return true; +} + +bool +WebBrowserPersistResourcesParent::RecvVisitResource(const nsCString& aURI) +{ + mVisitor->VisitResource(mDocument, aURI); + return true; +} + +bool +WebBrowserPersistResourcesParent::RecvVisitDocument(PWebBrowserPersistDocumentParent* aSubDocument) +{ + // Don't expose the subdocument to the visitor until it's ready + // (until the actor isn't in START state). + static_cast<WebBrowserPersistDocumentParent*>(aSubDocument) + ->SetOnReady(this); + return true; +} + +NS_IMETHODIMP +WebBrowserPersistResourcesParent::OnDocumentReady(nsIWebBrowserPersistDocument* aSubDocument) +{ + if (!mVisitor) { + return NS_ERROR_FAILURE; + } + mVisitor->VisitDocument(mDocument, aSubDocument); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistResourcesParent::OnError(nsresult aFailure) +{ + // Nothing useful to do but ignore the failed document. + return NS_OK; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.h b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.h new file mode 100644 index 000000000..9d70fd11b --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.h @@ -0,0 +1,54 @@ +/* -*- 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 WebBrowserPersistResourcesParent_h__ +#define WebBrowserPersistResourcesParent_h__ + +#include "mozilla/PWebBrowserPersistResourcesParent.h" + +#include "WebBrowserPersistDocumentParent.h" +#include "nsCOMPtr.h" +#include "nsIWebBrowserPersistDocument.h" + +namespace mozilla { + +class WebBrowserPersistResourcesParent final + : public PWebBrowserPersistResourcesParent + , public nsIWebBrowserPersistDocumentReceiver +{ +public: + WebBrowserPersistResourcesParent(nsIWebBrowserPersistDocument* aDocument, + nsIWebBrowserPersistResourceVisitor* aVisitor); + + virtual bool + RecvVisitResource(const nsCString& aURI) override; + + virtual bool + RecvVisitDocument(PWebBrowserPersistDocumentParent* aSubDocument) override; + + virtual bool + Recv__delete__(const nsresult& aStatus) override; + + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER + NS_DECL_ISUPPORTS + +private: + // Note: even if the XPIDL didn't need mDocument for visitor + // callbacks, this object still needs to hold a strong reference + // to it to defer actor subtree deletion until after the + // visitation is finished. + nsCOMPtr<nsIWebBrowserPersistDocument> mDocument; + nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor; + + virtual ~WebBrowserPersistResourcesParent(); +}; + +} // namespace mozilla + +#endif // WebBrowserPersistResourcesParent_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.cpp new file mode 100644 index 000000000..2b6fcbde4 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.cpp @@ -0,0 +1,143 @@ +/* -*- 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 "WebBrowserPersistSerializeChild.h" + +#include <algorithm> + +#include "nsThreadUtils.h" +#include "ipc/IPCMessageUtils.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(WebBrowserPersistSerializeChild, + nsIWebBrowserPersistWriteCompletion, + nsIWebBrowserPersistURIMap, + nsIOutputStream) + +WebBrowserPersistSerializeChild::WebBrowserPersistSerializeChild(const WebBrowserPersistURIMap& aMap) +: mMap(aMap) +{ +} + +WebBrowserPersistSerializeChild::~WebBrowserPersistSerializeChild() +{ +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::OnFinish(nsIWebBrowserPersistDocument* aDocument, + nsIOutputStream* aStream, + const nsACString& aContentType, + nsresult aStatus) +{ + MOZ_ASSERT(aStream == this); + nsCString contentType(aContentType); + Send__delete__(this, contentType, aStatus); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::GetNumMappedURIs(uint32_t* aNum) +{ + *aNum = static_cast<uint32_t>(mMap.mapURIs().Length()); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::GetURIMapping(uint32_t aIndex, + nsACString& aMapFrom, + nsACString& aMapTo) +{ + if (aIndex >= mMap.mapURIs().Length()) { + return NS_ERROR_INVALID_ARG; + } + aMapFrom = mMap.mapURIs()[aIndex].mapFrom(); + aMapTo = mMap.mapURIs()[aIndex].mapTo(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::GetTargetBaseURI(nsACString& aURI) +{ + aURI = mMap.targetBaseURI(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::Close() +{ + NS_WARNING("WebBrowserPersistSerializeChild::Close()"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::Flush() +{ + NS_WARNING("WebBrowserPersistSerializeChild::Flush()"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::Write(const char* aBuf, uint32_t aCount, + uint32_t* aWritten) +{ + // Normally an nsIOutputStream would have to be thread-safe, but + // nsDocumentEncoder currently doesn't call this off the main + // thread (which also means it's difficult to test the + // thread-safety code this class doesn't yet have). + // + // This is *not* an NS_ERROR_NOT_IMPLEMENTED, because at this + // point we've probably already misused the non-thread-safe + // refcounting. + MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Fix this class to be thread-safe."); + + // Work around bug 1181433 by sending multiple messages if + // necessary to write the entire aCount bytes, even though + // nsIOutputStream.idl says we're allowed to do a short write. + const char* buf = aBuf; + uint32_t count = aCount; + *aWritten = 0; + while (count > 0) { + uint32_t toWrite = std::min(IPC::MAX_MESSAGE_SIZE, count); + nsTArray<uint8_t> arrayBuf; + // It would be nice if this extra copy could be avoided. + arrayBuf.AppendElements(buf, toWrite); + SendWriteData(Move(arrayBuf)); + *aWritten += toWrite; + buf += toWrite; + count -= toWrite; + } + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::WriteFrom(nsIInputStream* aFrom, + uint32_t aCount, + uint32_t* aWritten) +{ + NS_WARNING("WebBrowserPersistSerializeChild::WriteFrom()"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::WriteSegments(nsReadSegmentFun aFun, + void* aCtx, + uint32_t aCount, + uint32_t* aWritten) +{ + NS_WARNING("WebBrowserPersistSerializeChild::WriteSegments()"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::IsNonBlocking(bool* aNonBlocking) +{ + // Writes will never fail with NS_BASE_STREAM_WOULD_BLOCK, so: + *aNonBlocking = false; + return NS_OK; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.h b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.h new file mode 100644 index 000000000..ddca44ece --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.h @@ -0,0 +1,39 @@ +/* -*- 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 WebBrowserPersistSerializeChild_h__ +#define WebBrowserPersistSerializeChild_h__ + +#include "mozilla/PWebBrowserPersistSerializeChild.h" + +#include "mozilla/PWebBrowserPersistDocument.h" +#include "nsIWebBrowserPersistDocument.h" +#include "nsIOutputStream.h" + +namespace mozilla { + +class WebBrowserPersistSerializeChild final + : public PWebBrowserPersistSerializeChild + , public nsIWebBrowserPersistWriteCompletion + , public nsIWebBrowserPersistURIMap + , public nsIOutputStream +{ +public: + explicit WebBrowserPersistSerializeChild(const WebBrowserPersistURIMap& aMap); + + NS_DECL_NSIWEBBROWSERPERSISTWRITECOMPLETION + NS_DECL_NSIWEBBROWSERPERSISTURIMAP + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_ISUPPORTS +private: + WebBrowserPersistURIMap mMap; + + virtual ~WebBrowserPersistSerializeChild(); +}; + +} // namespace mozilla + +#endif // WebBrowserPersistSerializeChild_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.cpp new file mode 100644 index 000000000..2f76ffb33 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.cpp @@ -0,0 +1,90 @@ +/* -*- 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 "WebBrowserPersistSerializeParent.h" + +#include "nsReadableUtils.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +WebBrowserPersistSerializeParent::WebBrowserPersistSerializeParent( + nsIWebBrowserPersistDocument* aDocument, + nsIOutputStream* aStream, + nsIWebBrowserPersistWriteCompletion* aFinish) +: mDocument(aDocument) +, mStream(aStream) +, mFinish(aFinish) +, mOutputError(NS_OK) +{ + MOZ_ASSERT(aDocument); + MOZ_ASSERT(aStream); + MOZ_ASSERT(aFinish); +} + +WebBrowserPersistSerializeParent::~WebBrowserPersistSerializeParent() +{ +} + +bool +WebBrowserPersistSerializeParent::RecvWriteData(nsTArray<uint8_t>&& aData) +{ + if (NS_FAILED(mOutputError)) { + return true; + } + + uint32_t written = 0; + static_assert(sizeof(char) == sizeof(uint8_t), + "char must be (at least?) 8 bits"); + const char* data = reinterpret_cast<const char*>(aData.Elements()); + // nsIOutputStream::Write is allowed to return short writes. + while (written < aData.Length()) { + uint32_t writeReturn; + nsresult rv = mStream->Write(data + written, + aData.Length() - written, + &writeReturn); + if (NS_FAILED(rv)) { + mOutputError = rv; + return true; + } + written += writeReturn; + } + return true; +} + +bool +WebBrowserPersistSerializeParent::Recv__delete__(const nsCString& aContentType, + const nsresult& aStatus) +{ + if (NS_SUCCEEDED(mOutputError)) { + mOutputError = aStatus; + } + mFinish->OnFinish(mDocument, + mStream, + aContentType, + mOutputError); + mFinish = nullptr; + return true; +} + +void +WebBrowserPersistSerializeParent::ActorDestroy(ActorDestroyReason aWhy) +{ + if (mFinish) { + MOZ_ASSERT(aWhy != Deletion); + // See comment in WebBrowserPersistDocumentParent::ActorDestroy + // (or bug 1202887) for why this is deferred. + nsCOMPtr<nsIRunnable> errorLater = NewRunnableMethod + <nsCOMPtr<nsIWebBrowserPersistDocument>, nsCOMPtr<nsIOutputStream>, + nsCString, nsresult> + (mFinish, &nsIWebBrowserPersistWriteCompletion::OnFinish, + mDocument, mStream, EmptyCString(), NS_ERROR_FAILURE); + NS_DispatchToCurrentThread(errorLater); + mFinish = nullptr; + } +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.h b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.h new file mode 100644 index 000000000..452feef4b --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.h @@ -0,0 +1,49 @@ +/* -*- 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 WebBrowserPersistSerializeParent_h__ +#define WebBrowserPersistSerializeParent_h__ + +#include "mozilla/PWebBrowserPersistSerializeParent.h" + +#include "nsCOMPtr.h" +#include "nsIOutputStream.h" +#include "nsIWebBrowserPersistDocument.h" + +namespace mozilla { + +class WebBrowserPersistSerializeParent + : public PWebBrowserPersistSerializeParent +{ +public: + WebBrowserPersistSerializeParent( + nsIWebBrowserPersistDocument* aDocument, + nsIOutputStream* aStream, + nsIWebBrowserPersistWriteCompletion* aFinish); + virtual ~WebBrowserPersistSerializeParent(); + + virtual bool + RecvWriteData(nsTArray<uint8_t>&& aData) override; + + virtual bool + Recv__delete__(const nsCString& aContentType, + const nsresult& aStatus) override; + + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + +private: + // See also ...ReadParent::mDocument for the other reason this + // strong reference needs to be here. + nsCOMPtr<nsIWebBrowserPersistDocument> mDocument; + nsCOMPtr<nsIOutputStream> mStream; + nsCOMPtr<nsIWebBrowserPersistWriteCompletion> mFinish; + nsresult mOutputError; +}; + +} // namespace mozilla + +#endif // WebBrowserPersistSerializeParent_h__ diff --git a/embedding/components/webbrowserpersist/moz.build b/embedding/components/webbrowserpersist/moz.build new file mode 100644 index 000000000..0d4108388 --- /dev/null +++ b/embedding/components/webbrowserpersist/moz.build @@ -0,0 +1,49 @@ +# -*- 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 += [ + 'nsCWebBrowserPersist.idl', + 'nsIWebBrowserPersist.idl', + 'nsIWebBrowserPersistable.idl', + 'nsIWebBrowserPersistDocument.idl', +] + +XPIDL_MODULE = 'webbrowserpersist' + +IPDL_SOURCES += [ + 'PWebBrowserPersistDocument.ipdl', + 'PWebBrowserPersistResources.ipdl', + 'PWebBrowserPersistSerialize.ipdl', +] + +SOURCES += [ + 'nsWebBrowserPersist.cpp', + 'WebBrowserPersistDocumentChild.cpp', + 'WebBrowserPersistDocumentParent.cpp', + 'WebBrowserPersistLocalDocument.cpp', + 'WebBrowserPersistRemoteDocument.cpp', + 'WebBrowserPersistResourcesChild.cpp', + 'WebBrowserPersistResourcesParent.cpp', + 'WebBrowserPersistSerializeChild.cpp', + 'WebBrowserPersistSerializeParent.cpp', +] + +EXPORTS.mozilla += [ + 'WebBrowserPersistDocumentChild.h', + 'WebBrowserPersistDocumentParent.h', + 'WebBrowserPersistLocalDocument.h', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '/dom/base', + '/dom/html', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/embedding/components/webbrowserpersist/nsCWebBrowserPersist.idl b/embedding/components/webbrowserpersist/nsCWebBrowserPersist.idl new file mode 100644 index 000000000..69f2db7ca --- /dev/null +++ b/embedding/components/webbrowserpersist/nsCWebBrowserPersist.idl @@ -0,0 +1,15 @@ +/* -*- 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 "nsIWebBrowserPersist.idl" + +%{ C++ +// {7E677795-C582-4cd1-9E8D-8271B3474D2A} +#define NS_WEBBROWSERPERSIST_CID \ +{ 0x7e677795, 0xc582, 0x4cd1, { 0x9e, 0x8d, 0x82, 0x71, 0xb3, 0x47, 0x4d, 0x2a } } +#define NS_WEBBROWSERPERSIST_CONTRACTID \ +"@mozilla.org/embedding/browser/nsWebBrowserPersist;1" +%} diff --git a/embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl b/embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl new file mode 100644 index 000000000..cd4bd8b69 --- /dev/null +++ b/embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl @@ -0,0 +1,286 @@ +/* -*- 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 "nsICancelable.idl" + +interface nsIURI; +interface nsIInputStream; +interface nsIDOMDocument; +interface nsIWebProgressListener; +interface nsIFile; +interface nsIChannel; +interface nsILoadContext; + +/** + * Interface for persisting DOM documents and URIs to local or remote storage. + */ +[scriptable, uuid(8cd752a4-60b1-42c3-a819-65c7a1138a28)] +interface nsIWebBrowserPersist : nsICancelable +{ + /** No special persistence behaviour. */ + const unsigned long PERSIST_FLAGS_NONE = 0; + /** Use cached data if present (skipping validation), else load from network */ + const unsigned long PERSIST_FLAGS_FROM_CACHE = 1; + /** Bypass the cached data. */ + const unsigned long PERSIST_FLAGS_BYPASS_CACHE = 2; + /** Ignore any redirected data (usually adverts). */ + const unsigned long PERSIST_FLAGS_IGNORE_REDIRECTED_DATA = 4; + /** Ignore IFRAME content (usually adverts). */ + const unsigned long PERSIST_FLAGS_IGNORE_IFRAMES = 8; + /** Do not run the incoming data through a content converter e.g. to decompress it */ + const unsigned long PERSIST_FLAGS_NO_CONVERSION = 16; + /** Replace existing files on the disk (use with due diligence!) */ + const unsigned long PERSIST_FLAGS_REPLACE_EXISTING_FILES = 32; + /** Don't modify or add base tags */ + const unsigned long PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS = 64; + /** Make changes to original dom rather than cloning nodes */ + const unsigned long PERSIST_FLAGS_FIXUP_ORIGINAL_DOM = 128; + /** Fix links relative to destination location (not origin) */ + const unsigned long PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION = 256; + /** Don't make any adjustments to links */ + const unsigned long PERSIST_FLAGS_DONT_FIXUP_LINKS = 512; + /** Force serialization of output (one file at a time; not concurrent) */ + const unsigned long PERSIST_FLAGS_SERIALIZE_OUTPUT = 1024; + /** Don't make any adjustments to filenames */ + const unsigned long PERSIST_FLAGS_DONT_CHANGE_FILENAMES = 2048; + /** Fail on broken inline links */ + const unsigned long PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS = 4096; + /** + * Automatically cleanup after a failed or cancelled operation, deleting all + * created files and directories. This flag does nothing for failed upload + * operations to remote servers. + */ + const unsigned long PERSIST_FLAGS_CLEANUP_ON_FAILURE = 8192; + /** + * Let the WebBrowserPersist decide whether the incoming data is encoded + * and whether it needs to go through a content converter e.g. to + * decompress it. + */ + const unsigned long PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION = 16384; + /** + * Append the downloaded data to the target file. + * This can only be used when persisting to a local file. + */ + const unsigned long PERSIST_FLAGS_APPEND_TO_FILE = 32768; + + /** + * Force relevant cookies to be sent with this load even if normally they + * wouldn't be. + */ + const unsigned long PERSIST_FLAGS_FORCE_ALLOW_COOKIES = 65536; + + /** + * Flags governing how data is fetched and saved from the network. + * It is best to set this value explicitly unless you are prepared + * to accept the default values. + */ + attribute unsigned long persistFlags; + + /** Persister is ready to save data */ + const unsigned long PERSIST_STATE_READY = 1; + /** Persister is saving data */ + const unsigned long PERSIST_STATE_SAVING = 2; + /** Persister has finished saving data */ + const unsigned long PERSIST_STATE_FINISHED = 3; + + /** + * Current state of the persister object. + */ + readonly attribute unsigned long currentState; + + /** + * Value indicating the success or failure of the persist + * operation. + * + * @throws NS_BINDING_ABORTED Operation cancelled. + * @throws NS_ERROR_FAILURE Non-specific failure. + */ + readonly attribute nsresult result; + + /** + * Callback listener for progress notifications. The object that the + * embbedder supplies may also implement nsIInterfaceRequestor and be + * prepared to return nsIAuthPrompt or other interfaces that may be required + * to download data. + * + * @see nsIAuthPrompt + * @see nsIInterfaceRequestor + */ + attribute nsIWebProgressListener progressListener; + + /** + * Save the specified URI to file. + * + * @param aURI URI to save to file. Some implementations of this interface + * may also support <CODE>nullptr</CODE> to imply the currently + * loaded URI. + * @param aCacheKey An object representing the URI in the cache or + * <CODE>nullptr</CODE>. This can be a necko cache key, + * an nsIWebPageDescriptor, or the currentDescriptor of an + * nsIWebPageDescriptor. + * @param aReferrer The referrer URI to pass with an HTTP request or + * <CODE>nullptr</CODE>. + * @param aReferrerPolicy The referrer policy for when and what to send via + * HTTP Referer header. Ignored if aReferrer is + * <CODE>nullptr</CODE>. Taken from REFERRER_POLICY + * constants in nsIHttpChannel. + * @param aPostData Post data to pass with an HTTP request or + * <CODE>nullptr</CODE>. + * @param aExtraHeaders Additional headers to supply with an HTTP request + * or <CODE>nullptr</CODE>. + * @param aFile Target file. This may be a nsIFile object or an + * nsIURI object with a file scheme or a scheme that + * supports uploading (e.g. ftp). + * @param aPrivacyContext A context from which the privacy status of this + * save operation can be determined. Must only be null + * in situations in which no such context is available + * (eg. the operation has no logical association with any + * window or document) + * + * @see nsIFile + * @see nsIURI + * @see nsIInputStream + * + * @throws NS_ERROR_INVALID_ARG One or more arguments was invalid. + */ + void saveURI(in nsIURI aURI, in nsISupports aCacheKey, + in nsIURI aReferrer, in unsigned long aReferrerPolicy, + in nsIInputStream aPostData, + in string aExtraHeaders, in nsISupports aFile, + in nsILoadContext aPrivacyContext); + + /** + * @param aIsPrivate Treat the save operation as private (ie. with + * regards to networking operations and persistence + * of intermediate data, etc.) + * @see saveURI for all other parameter descriptions + */ + void savePrivacyAwareURI(in nsIURI aURI, in nsISupports aCacheKey, + in nsIURI aReferrer, in unsigned long aReferrerPolicy, + in nsIInputStream aPostData, + in string aExtraHeaders, in nsISupports aFile, + in boolean aIsPrivate); + + /** + * Save a channel to a file. It must not be opened yet. + * @see saveURI + */ + void saveChannel(in nsIChannel aChannel, in nsISupports aFile); + + /** Output only the current selection as opposed to the whole document. */ + const unsigned long ENCODE_FLAGS_SELECTION_ONLY = 1; + /** + * For plaintext output. Convert html to plaintext that looks like the html. + * Implies wrap (except inside <pre>), since html wraps. + * HTML output: always do prettyprinting, ignoring existing formatting. + */ + const unsigned long ENCODE_FLAGS_FORMATTED = 2; + /** + * Output without formatting or wrapping the content. This flag + * may be used to preserve the original formatting as much as possible. + */ + const unsigned long ENCODE_FLAGS_RAW = 4; + /** Output only the body section, no HTML tags. */ + const unsigned long ENCODE_FLAGS_BODY_ONLY = 8; + /** Wrap even if when not doing formatted output (e.g. for text fields). */ + const unsigned long ENCODE_FLAGS_PREFORMATTED = 16; + /** Wrap documents at the specified column. */ + const unsigned long ENCODE_FLAGS_WRAP = 32; + /** + * For plaintext output. Output for format flowed (RFC 2646). This is used + * when converting to text for mail sending. This differs just slightly + * but in an important way from normal formatted, and that is that + * lines are space stuffed. This can't (correctly) be done later. + */ + const unsigned long ENCODE_FLAGS_FORMAT_FLOWED = 64; + /** Convert links to absolute links where possible. */ + const unsigned long ENCODE_FLAGS_ABSOLUTE_LINKS = 128; + + /** + * Attempt to encode entities standardized at W3C (HTML, MathML, etc). + * This is a catch-all flag for documents with mixed contents. Beware of + * interoperability issues. See below for other flags which might likely + * do what you want. + */ + const unsigned long ENCODE_FLAGS_ENCODE_W3C_ENTITIES = 256; + + /** + * Output with carriage return line breaks. May also be combined with + * ENCODE_FLAGS_LF_LINEBREAKS and if neither is specified, the platform + * default format is used. + */ + const unsigned long ENCODE_FLAGS_CR_LINEBREAKS = 512; + /** + * Output with linefeed line breaks. May also be combined with + * ENCODE_FLAGS_CR_LINEBREAKS and if neither is specified, the platform + * default format is used. + */ + const unsigned long ENCODE_FLAGS_LF_LINEBREAKS = 1024; + /** For plaintext output. Output the content of noscript elements. */ + const unsigned long ENCODE_FLAGS_NOSCRIPT_CONTENT = 2048; + /** For plaintext output. Output the content of noframes elements. */ + const unsigned long ENCODE_FLAGS_NOFRAMES_CONTENT = 4096; + + /** + * Encode basic entities, e.g. output instead of character code 0xa0. + * The basic set is just & < > " for interoperability + * with older products that don't support α and friends. + */ + const unsigned long ENCODE_FLAGS_ENCODE_BASIC_ENTITIES = 8192; + /** + * Encode Latin1 entities. This includes the basic set and + * accented letters between 128 and 255. + */ + const unsigned long ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES = 16384; + /** + * Encode HTML4 entities. This includes the basic set, accented + * letters, greek letters and certain special markup symbols. + */ + const unsigned long ENCODE_FLAGS_ENCODE_HTML_ENTITIES = 32768; + + /** + * Save the specified DOM document to file and optionally all linked files + * (e.g. images, CSS, JS & subframes). Do not call this method until the + * document has finished loading! + * + * @param aDocument Document to save to file. Some implementations of + * this interface may also support <CODE>nullptr</CODE> + * to imply the currently loaded document. Can be an + * nsIWebBrowserPersistDocument or nsIDOMDocument. + * @param aFile Target local file. This may be a nsIFile object or an + * nsIURI object with a file scheme or a scheme that + * supports uploading (e.g. ftp). + * @param aDataPath Path to directory where URIs linked to the document + * are saved or nullptr if no linked URIs should be saved. + * This may be a nsIFile object or an nsIURI object + * with a file scheme. + * @param aOutputContentType The desired MIME type format to save the + * document and all subdocuments into or nullptr to use + * the default behaviour. + * @param aEncodingFlags Flags to pass to the encoder. + * @param aWrapColumn For text documents, indicates the desired width to + * wrap text at. Parameter is ignored if wrapping is not + * specified by the encoding flags. + * + * @see nsIWebBrowserPersistDocument + * @see nsIWebBrowserPersistable + * @see nsIFile + * @see nsIURI + * + * @throws NS_ERROR_INVALID_ARG One or more arguments was invalid. + */ + void saveDocument(in nsISupports aDocument, + in nsISupports aFile, in nsISupports aDataPath, + in string aOutputContentType, in unsigned long aEncodingFlags, + in unsigned long aWrapColumn); + + /** + * Cancels the current operation. The caller is responsible for cleaning up + * partially written files or directories. This has the same effect as calling + * cancel with an argument of NS_BINDING_ABORTED. + */ + void cancelSave(); +}; diff --git a/embedding/components/webbrowserpersist/nsIWebBrowserPersistDocument.idl b/embedding/components/webbrowserpersist/nsIWebBrowserPersistDocument.idl new file mode 100644 index 000000000..ba5bea8b2 --- /dev/null +++ b/embedding/components/webbrowserpersist/nsIWebBrowserPersistDocument.idl @@ -0,0 +1,197 @@ +/* -*- Mode: IDL; 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 nsIDOMDocument; +interface nsIInputStream; +interface nsIOutputStream; +interface nsITabParent; +interface nsIWebBrowserPersistResourceVisitor; +interface nsIWebBrowserPersistWriteCompletion; + +/** + * Interface for the URI-mapping information that can be supplied when + * serializing the DOM of an nsIWebBrowserPersistDocument. + * + * @see nsIWebBrowserPersistDocument + */ +[scriptable, uuid(d52e8b93-2771-45e8-a5b0-6e12b667046b)] +interface nsIWebBrowserPersistURIMap : nsISupports +{ + /** + * The number of URI mappings. + */ + readonly attribute unsigned long numMappedURIs; + + /** + * Obtain the URI mapping at the given index, which must be less than + * numMappedURIs, as a pair of URI spec strings. + */ + void getURIMapping(in unsigned long aIndex, + out AUTF8String aMapFrom, + out AUTF8String aMapTo); + + /** + * The spec of the base URI that the document will have after it is + * serialized. + */ + readonly attribute AUTF8String targetBaseURI; +}; + +/** + * Interface representing a document that can be serialized with + * nsIWebBrowserPersist; it may or may not be in this process. Some + * information is exposed as attributes, which may or may not reflect + * changes made to the underlying document; most of these are + * self-explanatory from their names and types. + */ +[scriptable, uuid(74aa4918-5d15-46b6-9ccf-74f9696d721d)] +interface nsIWebBrowserPersistDocument : nsISupports +{ + readonly attribute boolean isPrivate; + readonly attribute AUTF8String documentURI; + readonly attribute AUTF8String baseURI; + readonly attribute ACString contentType; + readonly attribute ACString characterSet; + readonly attribute AString title; + readonly attribute AString referrer; + readonly attribute AString contentDisposition; + readonly attribute nsIInputStream postData; + + /** + * The cache key. Unlike in nsISHEntry, where it's wrapped in an + * nsISupportsPRUint32, this is just the integer. + */ + readonly attribute unsigned long cacheKey; + + /** + * This attribute is set by nsIWebBrowserPersist implementations to + * propagate persist flags that apply to the DOM traversal and + * serialization (rather than to managing file I/O). + */ + attribute unsigned long persistFlags; + + /** + * Walk the DOM searching for external resources needed to render it. + * The visitor callbacks may be called either before or after + * readResources returns. + * + * @see nsIWebBrowserPersistResourceVisitor + */ + void readResources(in nsIWebBrowserPersistResourceVisitor aVisitor); + + /** + * Serialize the document's DOM. + * + * @param aStream The output stream to write the document to. + * + * @param aURIMap Optional; specifies URI rewriting to perform on + * external references (as read by readResources). + * If given, also causes relative hyperlinks to be + * converted to absolute in the written text. + * + * @param aRequestedContentType + * The desired MIME type to save the document as; + * optional and defaults to the document's type. + * (If no encoder exists for that type, "text/html" + * is used instead.) + * + * @param aEncoderFlags Flags to pass to the encoder. + * + * @param aWrapColumn Desired text width, ignored if wrapping is not + * specified by the encoding flags, or if 0. + * + * @param aCompletion Callback invoked when writing is complete. + * It may be called either before or after writeContent + * returns. + * + * @see nsIDocumentEncoder + */ + void writeContent(in nsIOutputStream aStream, + in nsIWebBrowserPersistURIMap aURIMap, + in ACString aRequestedContentType, + in unsigned long aEncoderFlags, + in unsigned long aWrapColumn, + in nsIWebBrowserPersistWriteCompletion aCompletion); +}; + +/** + * Asynchronous visitor that receives external resources linked by an + * nsIWebBrowserPersistDocument and which are needed to render the + * document. + */ +[scriptable, uuid(8ce37706-b7d3-481a-be68-54f174fc0d0a)] +interface nsIWebBrowserPersistResourceVisitor : nsISupports +{ + /** + * Indicates a resource that is not a document; e.g., an image, script, + * or stylesheet. + * + * @param aDocument The document containing the reference. + * @param aURI The absolute URI spec for the referenced resource. + */ + void visitResource(in nsIWebBrowserPersistDocument aDocument, + in AUTF8String aURI); + /** + * Indicates a subdocument resource; e.g., a frame or iframe. + * + * @param aDocument The document containing the reference. + * @param aSubDocument The referenced document. + */ + void visitDocument(in nsIWebBrowserPersistDocument aDocument, + in nsIWebBrowserPersistDocument aSubDocument); + + /** + * Indicates that the document traversal is complete. + * + * @param aDocument The document that was being traversed. + * @param aStatus Indicates whether the traversal encountered an error. + */ + void endVisit(in nsIWebBrowserPersistDocument aDocument, + in nsresult aStatus); +}; + +/** + * Asynchronous callback for when nsIWebBrowserPersistDocument is finished + * serializing the document's DOM. + */ +[scriptable, function, uuid(a07e6892-38ae-4207-8340-7fa6ec446ed6)] +interface nsIWebBrowserPersistWriteCompletion : nsISupports +{ + /** + * Indicates that serialization is finished. + * + * @param aDocument The document that was being serialized. + * + * @param aStream The stream that was being written to. If it + * needs to be closed, the callback must do that; + * the serialization process leaves it open. + * + * @param aContentType The content type with which the document was + * actually serialized; this may be useful to set + * metadata on the result, or if uploading it. + * + * @param aStatus Indicates whether serialization encountered an error. + */ + void onFinish(in nsIWebBrowserPersistDocument aDocument, + in nsIOutputStream aStream, + in ACString aContentType, + in nsresult aStatus); +}; + +/** + * Asynchronous callback for creating a persistable document from some + * other object. + * + * @see nsIWebBrowserPersistable. + */ +[scriptable, uuid(321e3174-594f-4036-b7be-791b821bd376)] +interface nsIWebBrowserPersistDocumentReceiver : nsISupports +{ + void onDocumentReady(in nsIWebBrowserPersistDocument aDocument); + void onError(in nsresult aFailure); +}; diff --git a/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl b/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl new file mode 100644 index 000000000..39ea6b33b --- /dev/null +++ b/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl @@ -0,0 +1,41 @@ +/* -*- Mode: IDL; 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 nsIWebBrowserPersistDocumentReceiver; + +/** + * Interface for objects which represent a document that can be + * serialized with nsIWebBrowserPersist. This interface is + * asynchronous because the actual document can be in another process + * (e.g., if this object is an nsFrameLoader for an out-of-process + * frame). + * + * Warning: this is currently implemented only by nsFrameLoader, and + * may change in the future to become more frame-loader-specific or be + * merged into nsIFrameLoader. See bug 1101100 comment #34. + * + * @see nsIWebBrowserPersistDocumentReceiver + * @see nsIWebBrowserPersistDocument + * @see nsIWebBrowserPersist + * + * @param aOuterWindowID + * The outer window ID of the subframe we'd like to persist. + * If set at 0, nsIWebBrowserPersistable will attempt to persist + * the top-level document. If the outer window ID is for a subframe + * that does not exist, or is not held beneath the nsIWebBrowserPersistable, + * aRecv's onError method will be called with NS_ERROR_NO_CONTENT. + * @param aRecv + * The nsIWebBrowserPersistDocumentReceiver is a callback that + * will be fired once the document is ready for persisting. + */ +[scriptable, uuid(f4c3fa8e-83e9-49f8-ac6f-951fc7541fe4)] +interface nsIWebBrowserPersistable : nsISupports +{ + void startPersistence(in unsigned long long aOuterWindowID, + in nsIWebBrowserPersistDocumentReceiver aRecv); +}; diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp new file mode 100644 index 000000000..de85a59bb --- /dev/null +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp @@ -0,0 +1,2812 @@ +/* -*- 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/ArrayUtils.h" + +#include "nspr.h" + +#include "nsIFileStreams.h" // New Necko file streams +#include <algorithm> + +#include "nsAutoPtr.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIPrivateBrowsingChannel.h" +#include "nsComponentManagerUtils.h" +#include "nsIComponentRegistrar.h" +#include "nsIStorageStream.h" +#include "nsISeekableStream.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIEncodedChannel.h" +#include "nsIUploadChannel.h" +#include "nsICacheInfoChannel.h" +#include "nsIFileChannel.h" +#include "nsEscape.h" +#include "nsUnicharUtils.h" +#include "nsIStringEnumerator.h" +#include "nsCRT.h" +#include "nsContentCID.h" +#include "nsStreamUtils.h" + +#include "nsCExternalHandlerService.h" + +#include "nsIURL.h" +#include "nsIFileURL.h" +#include "nsIWebProgressListener.h" +#include "nsIAuthPrompt.h" +#include "nsIPrompt.h" +#include "nsISHEntry.h" +#include "nsIWebPageDescriptor.h" +#include "nsIFormControl.h" +#include "nsContentUtils.h" + +#include "nsIImageLoadingContent.h" + +#include "ftpCore.h" +#include "nsITransport.h" +#include "nsISocketTransport.h" +#include "nsIStringBundle.h" +#include "nsIProtocolHandler.h" + +#include "nsIWebBrowserPersistable.h" +#include "nsWebBrowserPersist.h" +#include "WebBrowserPersistLocalDocument.h" + +#include "nsIContent.h" +#include "nsIMIMEInfo.h" +#include "mozilla/dom/HTMLInputElement.h" +#include "mozilla/dom/HTMLSharedElement.h" +#include "mozilla/dom/HTMLSharedObjectElement.h" + +using namespace mozilla; +using namespace mozilla::dom; + +// Buffer file writes in 32kb chunks +#define BUFFERED_OUTPUT_SIZE (1024 * 32) + +struct nsWebBrowserPersist::WalkData +{ + nsCOMPtr<nsIWebBrowserPersistDocument> mDocument; + nsCOMPtr<nsIURI> mFile; + nsCOMPtr<nsIURI> mDataPath; +}; + +// Information about a DOM document +struct nsWebBrowserPersist::DocData +{ + nsCOMPtr<nsIURI> mBaseURI; + nsCOMPtr<nsIWebBrowserPersistDocument> mDocument; + nsCOMPtr<nsIURI> mFile; + nsCString mCharset; +}; + +// Information about a URI +struct nsWebBrowserPersist::URIData +{ + bool mNeedsPersisting; + bool mSaved; + bool mIsSubFrame; + bool mDataPathIsRelative; + bool mNeedsFixup; + nsString mFilename; + nsString mSubFrameExt; + nsCOMPtr<nsIURI> mFile; + nsCOMPtr<nsIURI> mDataPath; + nsCOMPtr<nsIURI> mRelativeDocumentURI; + nsCString mRelativePathToData; + nsCString mCharset; + + nsresult GetLocalURI(nsIURI *targetBaseURI, nsCString& aSpecOut); +}; + +// Information about the output stream +struct nsWebBrowserPersist::OutputData +{ + nsCOMPtr<nsIURI> mFile; + nsCOMPtr<nsIURI> mOriginalLocation; + nsCOMPtr<nsIOutputStream> mStream; + int64_t mSelfProgress; + int64_t mSelfProgressMax; + bool mCalcFileExt; + + OutputData(nsIURI *aFile, nsIURI *aOriginalLocation, bool aCalcFileExt) : + mFile(aFile), + mOriginalLocation(aOriginalLocation), + mSelfProgress(0), + mSelfProgressMax(10000), + mCalcFileExt(aCalcFileExt) + { + } + ~OutputData() + { + if (mStream) + { + mStream->Close(); + } + } +}; + +struct nsWebBrowserPersist::UploadData +{ + nsCOMPtr<nsIURI> mFile; + int64_t mSelfProgress; + int64_t mSelfProgressMax; + + explicit UploadData(nsIURI *aFile) : + mFile(aFile), + mSelfProgress(0), + mSelfProgressMax(10000) + { + } +}; + +struct nsWebBrowserPersist::CleanupData +{ + nsCOMPtr<nsIFile> mFile; + // Snapshot of what the file actually is at the time of creation so that if + // it transmutes into something else later on it can be ignored. For example, + // catch files that turn into dirs or vice versa. + bool mIsDirectory; +}; + +class nsWebBrowserPersist::OnWalk final + : public nsIWebBrowserPersistResourceVisitor +{ +public: + OnWalk(nsWebBrowserPersist* aParent, nsIURI* aFile, nsIFile* aDataPath) + : mParent(aParent) + , mFile(aFile) + , mDataPath(aDataPath) + { } + + NS_DECL_NSIWEBBROWSERPERSISTRESOURCEVISITOR + NS_DECL_ISUPPORTS +private: + RefPtr<nsWebBrowserPersist> mParent; + nsCOMPtr<nsIURI> mFile; + nsCOMPtr<nsIFile> mDataPath; + + virtual ~OnWalk() { } +}; + +NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWalk, + nsIWebBrowserPersistResourceVisitor) + +class nsWebBrowserPersist::OnWrite final + : public nsIWebBrowserPersistWriteCompletion +{ +public: + OnWrite(nsWebBrowserPersist* aParent, + nsIURI* aFile, + nsIFile* aLocalFile) + : mParent(aParent) + , mFile(aFile) + , mLocalFile(aLocalFile) + { } + + NS_DECL_NSIWEBBROWSERPERSISTWRITECOMPLETION + NS_DECL_ISUPPORTS +private: + RefPtr<nsWebBrowserPersist> mParent; + nsCOMPtr<nsIURI> mFile; + nsCOMPtr<nsIFile> mLocalFile; + + virtual ~OnWrite() { } +}; + +NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWrite, + nsIWebBrowserPersistWriteCompletion) + +class nsWebBrowserPersist::FlatURIMap final + : public nsIWebBrowserPersistURIMap +{ +public: + explicit FlatURIMap(const nsACString& aTargetBase) + : mTargetBase(aTargetBase) { } + + void Add(const nsACString& aMapFrom, const nsACString& aMapTo) { + mMapFrom.AppendElement(aMapFrom); + mMapTo.AppendElement(aMapTo); + } + + NS_DECL_NSIWEBBROWSERPERSISTURIMAP + NS_DECL_ISUPPORTS + +private: + nsTArray<nsCString> mMapFrom; + nsTArray<nsCString> mMapTo; + nsCString mTargetBase; + + virtual ~FlatURIMap() { } +}; + +NS_IMPL_ISUPPORTS(nsWebBrowserPersist::FlatURIMap, nsIWebBrowserPersistURIMap) + +NS_IMETHODIMP +nsWebBrowserPersist::FlatURIMap::GetNumMappedURIs(uint32_t* aNum) +{ + MOZ_ASSERT(mMapFrom.Length() == mMapTo.Length()); + *aNum = mMapTo.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserPersist::FlatURIMap::GetTargetBaseURI(nsACString& aTargetBase) +{ + aTargetBase = mTargetBase; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserPersist::FlatURIMap::GetURIMapping(uint32_t aIndex, + nsACString& aMapFrom, + nsACString& aMapTo) +{ + MOZ_ASSERT(mMapFrom.Length() == mMapTo.Length()); + if (aIndex >= mMapTo.Length()) { + return NS_ERROR_INVALID_ARG; + } + aMapFrom = mMapFrom[aIndex]; + aMapTo = mMapTo[aIndex]; + return NS_OK; +} + + +// Maximum file length constant. The max file name length is +// volume / server dependent but it is difficult to obtain +// that information. Instead this constant is a reasonable value that +// modern systems should able to cope with. +const uint32_t kDefaultMaxFilenameLength = 64; + +// Default flags for persistence +const uint32_t kDefaultPersistFlags = + nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION | + nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES; + +// String bundle where error messages come from +const char *kWebBrowserPersistStringBundle = + "chrome://global/locale/nsWebBrowserPersist.properties"; + +nsWebBrowserPersist::nsWebBrowserPersist() : + mCurrentDataPathIsRelative(false), + mCurrentThingsToPersist(0), + mFirstAndOnlyUse(true), + mSavingDocument(false), + mCancel(false), + mCompleted(false), + mStartSaving(false), + mReplaceExisting(true), + mSerializingOutput(false), + mIsPrivate(false), + mPersistFlags(kDefaultPersistFlags), + mPersistResult(NS_OK), + mTotalCurrentProgress(0), + mTotalMaxProgress(0), + mWrapColumn(72), + mEncodingFlags(0) +{ +} + +nsWebBrowserPersist::~nsWebBrowserPersist() +{ + Cleanup(); +} + +//***************************************************************************** +// nsWebBrowserPersist::nsISupports +//***************************************************************************** + +NS_IMPL_ADDREF(nsWebBrowserPersist) +NS_IMPL_RELEASE(nsWebBrowserPersist) + +NS_INTERFACE_MAP_BEGIN(nsWebBrowserPersist) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebBrowserPersist) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersist) + NS_INTERFACE_MAP_ENTRY(nsICancelable) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIStreamListener) + NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) + NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink) +NS_INTERFACE_MAP_END + + +//***************************************************************************** +// nsWebBrowserPersist::nsIInterfaceRequestor +//***************************************************************************** + +NS_IMETHODIMP nsWebBrowserPersist::GetInterface(const nsIID & aIID, void **aIFace) +{ + NS_ENSURE_ARG_POINTER(aIFace); + + *aIFace = nullptr; + + nsresult rv = QueryInterface(aIID, aIFace); + if (NS_SUCCEEDED(rv)) + { + return rv; + } + + if (mProgressListener && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) + || aIID.Equals(NS_GET_IID(nsIPrompt)))) + { + mProgressListener->QueryInterface(aIID, aIFace); + if (*aIFace) + return NS_OK; + } + + nsCOMPtr<nsIInterfaceRequestor> req = do_QueryInterface(mProgressListener); + if (req) + { + return req->GetInterface(aIID, aIFace); + } + + return NS_ERROR_NO_INTERFACE; +} + + +//***************************************************************************** +// nsWebBrowserPersist::nsIWebBrowserPersist +//***************************************************************************** + +NS_IMETHODIMP nsWebBrowserPersist::GetPersistFlags(uint32_t *aPersistFlags) +{ + NS_ENSURE_ARG_POINTER(aPersistFlags); + *aPersistFlags = mPersistFlags; + return NS_OK; +} +NS_IMETHODIMP nsWebBrowserPersist::SetPersistFlags(uint32_t aPersistFlags) +{ + mPersistFlags = aPersistFlags; + mReplaceExisting = (mPersistFlags & PERSIST_FLAGS_REPLACE_EXISTING_FILES) ? true : false; + mSerializingOutput = (mPersistFlags & PERSIST_FLAGS_SERIALIZE_OUTPUT) ? true : false; + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::GetCurrentState(uint32_t *aCurrentState) +{ + NS_ENSURE_ARG_POINTER(aCurrentState); + if (mCompleted) + { + *aCurrentState = PERSIST_STATE_FINISHED; + } + else if (mFirstAndOnlyUse) + { + *aCurrentState = PERSIST_STATE_SAVING; + } + else + { + *aCurrentState = PERSIST_STATE_READY; + } + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::GetResult(nsresult *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mPersistResult; + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::GetProgressListener( + nsIWebProgressListener * *aProgressListener) +{ + NS_ENSURE_ARG_POINTER(aProgressListener); + *aProgressListener = mProgressListener; + NS_IF_ADDREF(*aProgressListener); + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::SetProgressListener( + nsIWebProgressListener * aProgressListener) +{ + mProgressListener = aProgressListener; + mProgressListener2 = do_QueryInterface(aProgressListener); + mEventSink = do_GetInterface(aProgressListener); + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::SaveURI( + nsIURI *aURI, nsISupports *aCacheKey, + nsIURI *aReferrer, uint32_t aReferrerPolicy, + nsIInputStream *aPostData, const char *aExtraHeaders, + nsISupports *aFile, nsILoadContext* aPrivacyContext) +{ + return SavePrivacyAwareURI(aURI, aCacheKey, aReferrer, aReferrerPolicy, + aPostData, aExtraHeaders, aFile, + aPrivacyContext && aPrivacyContext->UsePrivateBrowsing()); +} + +NS_IMETHODIMP nsWebBrowserPersist::SavePrivacyAwareURI( + nsIURI *aURI, nsISupports *aCacheKey, + nsIURI *aReferrer, uint32_t aReferrerPolicy, + nsIInputStream *aPostData, const char *aExtraHeaders, + nsISupports *aFile, bool aIsPrivate) +{ + NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE); + mFirstAndOnlyUse = false; // Stop people from reusing this object! + + nsCOMPtr<nsIURI> fileAsURI; + nsresult rv; + rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); + + // SaveURI doesn't like broken uris. + mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS; + rv = SaveURIInternal(aURI, aCacheKey, aReferrer, aReferrerPolicy, + aPostData, aExtraHeaders, fileAsURI, false, aIsPrivate); + return NS_FAILED(rv) ? rv : NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::SaveChannel( + nsIChannel *aChannel, nsISupports *aFile) +{ + NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE); + mFirstAndOnlyUse = false; // Stop people from reusing this object! + + nsCOMPtr<nsIURI> fileAsURI; + nsresult rv; + rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); + + rv = aChannel->GetURI(getter_AddRefs(mURI)); + NS_ENSURE_SUCCESS(rv, rv); + + // SaveURI doesn't like broken uris. + mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS; + rv = SaveChannelInternal(aChannel, fileAsURI, false); + return NS_FAILED(rv) ? rv : NS_OK; +} + + +NS_IMETHODIMP nsWebBrowserPersist::SaveDocument( + nsISupports *aDocument, nsISupports *aFile, nsISupports *aDataPath, + const char *aOutputContentType, uint32_t aEncodingFlags, uint32_t aWrapColumn) +{ + NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE); + mFirstAndOnlyUse = false; // Stop people from reusing this object! + + // We need a STATE_IS_NETWORK start/stop pair to bracket the + // notification callbacks. For a whole document we generate those + // here and in EndDownload(), but for the single-request methods + // that's done in On{Start,Stop}Request instead. + mSavingDocument = true; + + NS_ENSURE_ARG_POINTER(aDocument); + NS_ENSURE_ARG_POINTER(aFile); + + nsCOMPtr<nsIURI> fileAsURI; + nsCOMPtr<nsIURI> datapathAsURI; + nsresult rv; + + rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); + if (aDataPath) + { + rv = GetValidURIFromObject(aDataPath, getter_AddRefs(datapathAsURI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); + } + + mWrapColumn = aWrapColumn; + mEncodingFlags = aEncodingFlags; + + if (aOutputContentType) + { + mContentType.AssignASCII(aOutputContentType); + } + + // State start notification + if (mProgressListener) { + mProgressListener->OnStateChange(nullptr, nullptr, + nsIWebProgressListener::STATE_START + | nsIWebProgressListener::STATE_IS_NETWORK, NS_OK); + } + + nsCOMPtr<nsIWebBrowserPersistDocument> doc = do_QueryInterface(aDocument); + if (!doc) { + nsCOMPtr<nsIDocument> localDoc = do_QueryInterface(aDocument); + if (localDoc) { + doc = new mozilla::WebBrowserPersistLocalDocument(localDoc); + } else { + rv = NS_ERROR_NO_INTERFACE; + } + } + if (doc) { + rv = SaveDocumentInternal(doc, fileAsURI, datapathAsURI); + } + if (NS_FAILED(rv)) { + SendErrorStatusChange(true, rv, nullptr, mURI); + EndDownload(rv); + } + return rv; +} + +NS_IMETHODIMP nsWebBrowserPersist::Cancel(nsresult aReason) +{ + mCancel = true; + EndDownload(aReason); + return NS_OK; +} + + +NS_IMETHODIMP nsWebBrowserPersist::CancelSave() +{ + return Cancel(NS_BINDING_ABORTED); +} + + +nsresult +nsWebBrowserPersist::StartUpload(nsIStorageStream *storStream, + nsIURI *aDestinationURI, const nsACString &aContentType) +{ + // setup the upload channel if the destination is not local + nsCOMPtr<nsIInputStream> inputstream; + nsresult rv = storStream->NewInputStream(0, getter_AddRefs(inputstream)); + NS_ENSURE_TRUE(inputstream, NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + return StartUpload(inputstream, aDestinationURI, aContentType); +} + +nsresult +nsWebBrowserPersist::StartUpload(nsIInputStream *aInputStream, + nsIURI *aDestinationURI, const nsACString &aContentType) +{ + nsCOMPtr<nsIChannel> destChannel; + CreateChannelFromURI(aDestinationURI, getter_AddRefs(destChannel)); + nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(destChannel)); + NS_ENSURE_TRUE(uploadChannel, NS_ERROR_FAILURE); + + // Set the upload stream + // NOTE: ALL data must be available in "inputstream" + nsresult rv = uploadChannel->SetUploadStream(aInputStream, aContentType, -1); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + rv = destChannel->AsyncOpen2(this); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // add this to the upload list + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(destChannel); + mUploadList.Put(keyPtr, new UploadData(aDestinationURI)); + + return NS_OK; +} + +void +nsWebBrowserPersist::SerializeNextFile() +{ + nsresult rv = NS_OK; + MOZ_ASSERT(mWalkStack.Length() == 0); + + // First, handle gathered URIs. + // Count how many URIs in the URI map require persisting + uint32_t urisToPersist = 0; + if (mURIMap.Count() > 0) { + // This is potentially O(n^2), when taking into account the + // number of times this method is called. If it becomes a + // bottleneck, the count of not-yet-persisted URIs could be + // maintained separately. + for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) { + URIData *data = iter.UserData(); + if (data->mNeedsPersisting && !data->mSaved) { + urisToPersist++; + } + } + } + + if (urisToPersist > 0) { + // Persist each file in the uri map. The document(s) + // will be saved after the last one of these is saved. + for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) { + URIData *data = iter.UserData(); + + if (!data->mNeedsPersisting || data->mSaved) { + continue; + } + + nsresult rv; + + // Create a URI from the key. + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), iter.Key(), + data->mCharset.get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + // Make a URI to save the data to. + nsCOMPtr<nsIURI> fileAsURI; + rv = data->mDataPath->Clone(getter_AddRefs(fileAsURI)); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + rv = AppendPathToURI(fileAsURI, data->mFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + // The Referrer Policy doesn't matter here since the referrer is + // nullptr. + rv = SaveURIInternal(uri, nullptr, nullptr, + mozilla::net::RP_Default, nullptr, nullptr, + fileAsURI, true, mIsPrivate); + // If SaveURIInternal fails, then it will have called EndDownload, + // which means that |data| is no longer valid memory. We MUST bail. + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + if (rv == NS_OK) { + // Store the actual object because once it's persisted this + // will be fixed up with the right file extension. + data->mFile = fileAsURI; + data->mSaved = true; + } else { + data->mNeedsFixup = false; + } + + if (mSerializingOutput) { + break; + } + } + } + + // If there are downloads happening, wait until they're done; the + // OnStopRequest handler will call this method again. + if (mOutputMap.Count() > 0) { + return; + } + + // If serializing, also wait until last upload is done. + if (mSerializingOutput && mUploadList.Count() > 0) { + return; + } + + // If there are also no more documents, then we're done. + if (mDocList.Length() == 0) { + // ...or not quite done, if there are still uploads. + if (mUploadList.Count() > 0) { + return; + } + // Finish and clean things up. Defer this because the caller + // may have been expecting to use the listeners that that + // method will clear. + NS_DispatchToCurrentThread(NewRunnableMethod(this, + &nsWebBrowserPersist::FinishDownload)); + return; + } + + // There are no URIs to save, so just save the next document. + mStartSaving = true; + mozilla::UniquePtr<DocData> docData(mDocList.ElementAt(0)); + mDocList.RemoveElementAt(0); // O(n^2) but probably doesn't matter. + MOZ_ASSERT(docData); + if (!docData) { + EndDownload(NS_ERROR_FAILURE); + return; + } + + mCurrentBaseURI = docData->mBaseURI; + mCurrentCharset = docData->mCharset; + mTargetBaseURI = docData->mFile; + + // Save the document, fixing it up with the new URIs as we do + + nsAutoCString targetBaseSpec; + if (mTargetBaseURI) { + rv = mTargetBaseURI->GetSpec(targetBaseSpec); + if (NS_FAILED(rv)) { + SendErrorStatusChange(true, rv, nullptr, nullptr); + EndDownload(rv); + return; + } + } + + // mFlatURIMap must be rebuilt each time through SerializeNextFile, as + // mTargetBaseURI is used to create the relative URLs and will be different + // with each serialized document. + RefPtr<FlatURIMap> flatMap = new FlatURIMap(targetBaseSpec); + for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) { + nsAutoCString mapTo; + nsresult rv = iter.UserData()->GetLocalURI(mTargetBaseURI, mapTo); + if (NS_SUCCEEDED(rv) || !mapTo.IsVoid()) { + flatMap->Add(iter.Key(), mapTo); + } + } + mFlatURIMap = flatMap.forget(); + + nsCOMPtr<nsIFile> localFile; + GetLocalFileFromURI(docData->mFile, getter_AddRefs(localFile)); + if (localFile) { + // if we're not replacing an existing file but the file + // exists, something is wrong + bool fileExists = false; + rv = localFile->Exists(&fileExists); + if (NS_SUCCEEDED(rv) && !mReplaceExisting && fileExists) { + rv = NS_ERROR_FILE_ALREADY_EXISTS; + } + if (NS_FAILED(rv)) { + SendErrorStatusChange(false, rv, nullptr, docData->mFile); + EndDownload(rv); + return; + } + } + nsCOMPtr<nsIOutputStream> outputStream; + rv = MakeOutputStream(docData->mFile, getter_AddRefs(outputStream)); + if (NS_SUCCEEDED(rv) && !outputStream) { + rv = NS_ERROR_FAILURE; + } + if (NS_FAILED(rv)) { + SendErrorStatusChange(false, rv, nullptr, docData->mFile); + EndDownload(rv); + return; + } + + RefPtr<OnWrite> finish = new OnWrite(this, docData->mFile, localFile); + rv = docData->mDocument->WriteContent(outputStream, + mFlatURIMap, + NS_ConvertUTF16toUTF8(mContentType), + mEncodingFlags, + mWrapColumn, + finish); + if (NS_FAILED(rv)) { + SendErrorStatusChange(false, rv, nullptr, docData->mFile); + EndDownload(rv); + } +} + +NS_IMETHODIMP +nsWebBrowserPersist::OnWrite::OnFinish(nsIWebBrowserPersistDocument* aDoc, + nsIOutputStream *aStream, + const nsACString& aContentType, + nsresult aStatus) +{ + nsresult rv = aStatus; + + if (NS_FAILED(rv)) { + mParent->SendErrorStatusChange(false, rv, nullptr, mFile); + mParent->EndDownload(rv); + return NS_OK; + } + if (!mLocalFile) { + nsCOMPtr<nsIStorageStream> storStream(do_QueryInterface(aStream)); + if (storStream) { + aStream->Close(); + rv = mParent->StartUpload(storStream, mFile, aContentType); + if (NS_FAILED(rv)) { + mParent->SendErrorStatusChange(false, rv, nullptr, mFile); + mParent->EndDownload(rv); + } + // Either we failed and we're done, or we're uploading and + // the OnStopRequest callback is responsible for the next + // SerializeNextFile(). + return NS_OK; + } + } + NS_DispatchToCurrentThread(NewRunnableMethod(mParent, + &nsWebBrowserPersist::SerializeNextFile)); + return NS_OK; +} + +//***************************************************************************** +// nsWebBrowserPersist::nsIRequestObserver +//***************************************************************************** + +NS_IMETHODIMP nsWebBrowserPersist::OnStartRequest( + nsIRequest* request, nsISupports *ctxt) +{ + if (mProgressListener) + { + uint32_t stateFlags = nsIWebProgressListener::STATE_START | + nsIWebProgressListener::STATE_IS_REQUEST; + if (!mSavingDocument) { + stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK; + } + mProgressListener->OnStateChange(nullptr, request, stateFlags, NS_OK); + } + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); + + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request); + OutputData *data = mOutputMap.Get(keyPtr); + + // NOTE: This code uses the channel as a hash key so it will not + // recognize redirected channels because the key is not the same. + // When that happens we remove and add the data entry to use the + // new channel as the hash key. + if (!data) + { + UploadData *upData = mUploadList.Get(keyPtr); + if (!upData) + { + // Redirect? Try and fixup the output table + nsresult rv = FixRedirectedChannelEntry(channel); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Should be able to find the data after fixup unless redirects + // are disabled. + data = mOutputMap.Get(keyPtr); + if (!data) + { + return NS_ERROR_FAILURE; + } + } + } + + if (data && data->mFile) + { + // If PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION is set in mPersistFlags, + // try to determine whether this channel needs to apply Content-Encoding + // conversions. + NS_ASSERTION(!((mPersistFlags & PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION) && + (mPersistFlags & PERSIST_FLAGS_NO_CONVERSION)), + "Conflict in persist flags: both AUTODETECT and NO_CONVERSION set"); + if (mPersistFlags & PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION) + SetApplyConversionIfNeeded(channel); + + if (data->mCalcFileExt && !(mPersistFlags & PERSIST_FLAGS_DONT_CHANGE_FILENAMES)) + { + // this is the first point at which the server can tell us the mimetype + CalculateAndAppendFileExt(data->mFile, channel, data->mOriginalLocation); + + // now make filename conformant and unique + CalculateUniqueFilename(data->mFile); + } + + // compare uris and bail before we add to output map if they are equal + bool isEqual = false; + if (NS_SUCCEEDED(data->mFile->Equals(data->mOriginalLocation, &isEqual)) + && isEqual) + { + // remove from output map + mOutputMap.Remove(keyPtr); + + // cancel; we don't need to know any more + // stop request will get called + request->Cancel(NS_BINDING_ABORTED); + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::OnStopRequest( + nsIRequest* request, nsISupports *ctxt, nsresult status) +{ + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request); + OutputData *data = mOutputMap.Get(keyPtr); + if (data) { + if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(status)) { + SendErrorStatusChange(true, status, request, data->mFile); + } + + // This will automatically close the output stream + mOutputMap.Remove(keyPtr); + } else { + // if we didn't find the data in mOutputMap, try mUploadList + UploadData *upData = mUploadList.Get(keyPtr); + if (upData) { + mUploadList.Remove(keyPtr); + } + } + + // Do more work. + SerializeNextFile(); + + if (mProgressListener) { + uint32_t stateFlags = nsIWebProgressListener::STATE_STOP | + nsIWebProgressListener::STATE_IS_REQUEST; + if (!mSavingDocument) { + stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK; + } + mProgressListener->OnStateChange(nullptr, request, stateFlags, status); + } + + return NS_OK; +} + +//***************************************************************************** +// nsWebBrowserPersist::nsIStreamListener +//***************************************************************************** + +NS_IMETHODIMP +nsWebBrowserPersist::OnDataAvailable( + nsIRequest* request, nsISupports *aContext, nsIInputStream *aIStream, + uint64_t aOffset, uint32_t aLength) +{ + bool cancel = mCancel; + if (!cancel) + { + nsresult rv = NS_OK; + uint32_t bytesRemaining = aLength; + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); + + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request); + OutputData *data = mOutputMap.Get(keyPtr); + if (!data) { + // might be uploadData; consume necko's buffer and bail... + uint32_t n; + return aIStream->ReadSegments(NS_DiscardSegment, nullptr, aLength, &n); + } + + bool readError = true; + + // Make the output stream + if (!data->mStream) + { + rv = MakeOutputStream(data->mFile, getter_AddRefs(data->mStream)); + if (NS_FAILED(rv)) + { + readError = false; + cancel = true; + } + } + + // Read data from the input and write to the output + char buffer[8192]; + uint32_t bytesRead; + while (!cancel && bytesRemaining) + { + readError = true; + rv = aIStream->Read(buffer, + std::min(uint32_t(sizeof(buffer)), bytesRemaining), + &bytesRead); + if (NS_SUCCEEDED(rv)) + { + readError = false; + // Write out the data until something goes wrong, or, it is + // all written. We loop because for some errors (e.g., disk + // full), we get NS_OK with some bytes written, then an error. + // So, we want to write again in that case to get the actual + // error code. + const char *bufPtr = buffer; // Where to write from. + while (NS_SUCCEEDED(rv) && bytesRead) + { + uint32_t bytesWritten = 0; + rv = data->mStream->Write(bufPtr, bytesRead, &bytesWritten); + if (NS_SUCCEEDED(rv)) + { + bytesRead -= bytesWritten; + bufPtr += bytesWritten; + bytesRemaining -= bytesWritten; + // Force an error if (for some reason) we get NS_OK but + // no bytes written. + if (!bytesWritten) + { + rv = NS_ERROR_FAILURE; + cancel = true; + } + } + else + { + // Disaster - can't write out the bytes - disk full / permission? + cancel = true; + } + } + } + else + { + // Disaster - can't read the bytes - broken link / file error? + cancel = true; + } + } + + int64_t channelContentLength = -1; + if (!cancel && + NS_SUCCEEDED(channel->GetContentLength(&channelContentLength))) + { + // if we get -1 at this point, we didn't get content-length header + // assume that we got all of the data and push what we have; + // that's the best we can do now + if ((-1 == channelContentLength) || + ((channelContentLength - (aOffset + aLength)) == 0)) + { + NS_WARNING_ASSERTION( + channelContentLength != -1, + "nsWebBrowserPersist::OnDataAvailable() no content length " + "header, pushing what we have"); + // we're done with this pass; see if we need to do upload + nsAutoCString contentType; + channel->GetContentType(contentType); + // if we don't have the right type of output stream then it's a local file + nsCOMPtr<nsIStorageStream> storStream(do_QueryInterface(data->mStream)); + if (storStream) + { + data->mStream->Close(); + data->mStream = nullptr; // null out stream so we don't close it later + rv = StartUpload(storStream, data->mFile, contentType); + if (NS_FAILED(rv)) + { + readError = false; + cancel = true; + } + } + } + } + + // Notify listener if an error occurred. + if (cancel) + { + SendErrorStatusChange(readError, rv, + readError ? request : nullptr, data->mFile); + } + } + + // Cancel reading? + if (cancel) + { + EndDownload(NS_BINDING_ABORTED); + } + + return NS_OK; +} + + +//***************************************************************************** +// nsWebBrowserPersist::nsIProgressEventSink +//***************************************************************************** + +NS_IMETHODIMP nsWebBrowserPersist::OnProgress( + nsIRequest *request, nsISupports *ctxt, int64_t aProgress, + int64_t aProgressMax) +{ + if (!mProgressListener) + { + return NS_OK; + } + + // Store the progress of this request + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request); + OutputData *data = mOutputMap.Get(keyPtr); + if (data) + { + data->mSelfProgress = aProgress; + data->mSelfProgressMax = aProgressMax; + } + else + { + UploadData *upData = mUploadList.Get(keyPtr); + if (upData) + { + upData->mSelfProgress = aProgress; + upData->mSelfProgressMax = aProgressMax; + } + } + + // Notify listener of total progress + CalcTotalProgress(); + if (mProgressListener2) + { + mProgressListener2->OnProgressChange64(nullptr, request, aProgress, + aProgressMax, mTotalCurrentProgress, mTotalMaxProgress); + } + else + { + // have to truncate 64-bit to 32bit + mProgressListener->OnProgressChange(nullptr, request, uint64_t(aProgress), + uint64_t(aProgressMax), mTotalCurrentProgress, mTotalMaxProgress); + } + + // If our progress listener implements nsIProgressEventSink, + // forward the notification + if (mEventSink) + { + mEventSink->OnProgress(request, ctxt, aProgress, aProgressMax); + } + + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::OnStatus( + nsIRequest *request, nsISupports *ctxt, nsresult status, + const char16_t *statusArg) +{ + if (mProgressListener) + { + // We need to filter out non-error error codes. + // Is the only NS_SUCCEEDED value NS_OK? + switch ( status ) + { + case NS_NET_STATUS_RESOLVING_HOST: + case NS_NET_STATUS_RESOLVED_HOST: + case NS_NET_STATUS_BEGIN_FTP_TRANSACTION: + case NS_NET_STATUS_END_FTP_TRANSACTION: + case NS_NET_STATUS_CONNECTING_TO: + case NS_NET_STATUS_CONNECTED_TO: + case NS_NET_STATUS_SENDING_TO: + case NS_NET_STATUS_RECEIVING_FROM: + case NS_NET_STATUS_WAITING_FOR: + case NS_NET_STATUS_READING: + case NS_NET_STATUS_WRITING: + break; + + default: + // Pass other notifications (for legitimate errors) along. + mProgressListener->OnStatusChange(nullptr, request, status, statusArg); + break; + } + + } + + // If our progress listener implements nsIProgressEventSink, + // forward the notification + if (mEventSink) + { + mEventSink->OnStatus(request, ctxt, status, statusArg); + } + + return NS_OK; +} + + +//***************************************************************************** +// nsWebBrowserPersist private methods +//***************************************************************************** + +// Convert error info into proper message text and send OnStatusChange notification +// to the web progress listener. +nsresult nsWebBrowserPersist::SendErrorStatusChange( + bool aIsReadError, nsresult aResult, nsIRequest *aRequest, nsIURI *aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + + if (!mProgressListener) + { + // Do nothing + return NS_OK; + } + + // Get the file path or spec from the supplied URI + nsCOMPtr<nsIFile> file; + GetLocalFileFromURI(aURI, getter_AddRefs(file)); + nsAutoString path; + nsresult rv; + if (file) + { + file->GetPath(path); + } + else + { + nsAutoCString fileurl; + rv = aURI->GetSpec(fileurl); + NS_ENSURE_SUCCESS(rv, rv); + AppendUTF8toUTF16(fileurl, path); + } + + nsAutoString msgId; + switch(aResult) + { + case NS_ERROR_FILE_NAME_TOO_LONG: + // File name too long. + msgId.AssignLiteral("fileNameTooLongError"); + break; + case NS_ERROR_FILE_ALREADY_EXISTS: + // File exists with same name as directory. + msgId.AssignLiteral("fileAlreadyExistsError"); + break; + case NS_ERROR_FILE_DISK_FULL: + case NS_ERROR_FILE_NO_DEVICE_SPACE: + // Out of space on target volume. + msgId.AssignLiteral("diskFull"); + break; + + case NS_ERROR_FILE_READ_ONLY: + // Attempt to write to read/only file. + msgId.AssignLiteral("readOnly"); + break; + + case NS_ERROR_FILE_ACCESS_DENIED: + // Attempt to write without sufficient permissions. + msgId.AssignLiteral("accessError"); + break; + + default: + // Generic read/write error message. + if (aIsReadError) + msgId.AssignLiteral("readError"); + else + msgId.AssignLiteral("writeError"); + break; + } + // Get properties file bundle and extract status string. + nsCOMPtr<nsIStringBundleService> s = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && s, NS_ERROR_FAILURE); + + nsCOMPtr<nsIStringBundle> bundle; + rv = s->CreateBundle(kWebBrowserPersistStringBundle, getter_AddRefs(bundle)); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && bundle, NS_ERROR_FAILURE); + + nsXPIDLString msgText; + const char16_t *strings[1]; + strings[0] = path.get(); + rv = bundle->FormatStringFromName(msgId.get(), strings, 1, getter_Copies(msgText)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + mProgressListener->OnStatusChange(nullptr, aRequest, aResult, msgText); + + return NS_OK; +} + +nsresult nsWebBrowserPersist::GetValidURIFromObject(nsISupports *aObject, nsIURI **aURI) const +{ + NS_ENSURE_ARG_POINTER(aObject); + NS_ENSURE_ARG_POINTER(aURI); + + nsCOMPtr<nsIFile> objAsFile = do_QueryInterface(aObject); + if (objAsFile) + { + return NS_NewFileURI(aURI, objAsFile); + } + nsCOMPtr<nsIURI> objAsURI = do_QueryInterface(aObject); + if (objAsURI) + { + *aURI = objAsURI; + NS_ADDREF(*aURI); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +/* static */ nsresult +nsWebBrowserPersist::GetLocalFileFromURI(nsIURI *aURI, nsIFile **aLocalFile) +{ + nsresult rv; + + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIFile> file; + rv = fileURL->GetFile(getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return rv; + } + + file.forget(aLocalFile); + return NS_OK; +} + +/* static */ nsresult +nsWebBrowserPersist::AppendPathToURI(nsIURI *aURI, const nsAString & aPath) +{ + NS_ENSURE_ARG_POINTER(aURI); + + nsAutoCString newPath; + nsresult rv = aURI->GetPath(newPath); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Append a forward slash if necessary + int32_t len = newPath.Length(); + if (len > 0 && newPath.CharAt(len - 1) != '/') + { + newPath.Append('/'); + } + + // Store the path back on the URI + AppendUTF16toUTF8(aPath, newPath); + aURI->SetPath(newPath); + + return NS_OK; +} + +nsresult nsWebBrowserPersist::SaveURIInternal( + nsIURI *aURI, nsISupports *aCacheKey, nsIURI *aReferrer, + uint32_t aReferrerPolicy, nsIInputStream *aPostData, + const char *aExtraHeaders, nsIURI *aFile, + bool aCalcFileExt, bool aIsPrivate) +{ + NS_ENSURE_ARG_POINTER(aURI); + NS_ENSURE_ARG_POINTER(aFile); + + nsresult rv = NS_OK; + + mURI = aURI; + + nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL; + if (mPersistFlags & PERSIST_FLAGS_BYPASS_CACHE) + { + loadFlags |= nsIRequest::LOAD_BYPASS_CACHE; + } + else if (mPersistFlags & PERSIST_FLAGS_FROM_CACHE) + { + loadFlags |= nsIRequest::LOAD_FROM_CACHE; + } + + // Extract the cache key + nsCOMPtr<nsISupports> cacheKey; + if (aCacheKey) + { + // Test if the cache key is actually a web page descriptor (docshell) + // or session history entry. + nsCOMPtr<nsISHEntry> shEntry = do_QueryInterface(aCacheKey); + if (!shEntry) + { + nsCOMPtr<nsIWebPageDescriptor> webPageDescriptor = + do_QueryInterface(aCacheKey); + if (webPageDescriptor) + { + nsCOMPtr<nsISupports> currentDescriptor; + webPageDescriptor->GetCurrentDescriptor(getter_AddRefs(currentDescriptor)); + shEntry = do_QueryInterface(currentDescriptor); + } + } + + if (shEntry) + { + shEntry->GetCacheKey(getter_AddRefs(cacheKey)); + } + else + { + // Assume a plain cache key + cacheKey = aCacheKey; + } + } + + // Open a channel to the URI + nsCOMPtr<nsIChannel> inputChannel; + rv = NS_NewChannel(getter_AddRefs(inputChannel), + aURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // aLoadGroup + static_cast<nsIInterfaceRequestor*>(this), + loadFlags); + + nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(inputChannel); + if (pbChannel) + { + pbChannel->SetPrivate(aIsPrivate); + } + + if (NS_FAILED(rv) || inputChannel == nullptr) + { + EndDownload(NS_ERROR_FAILURE); + return NS_ERROR_FAILURE; + } + + // Disable content conversion + if (mPersistFlags & PERSIST_FLAGS_NO_CONVERSION) + { + nsCOMPtr<nsIEncodedChannel> encodedChannel(do_QueryInterface(inputChannel)); + if (encodedChannel) + { + encodedChannel->SetApplyConversion(false); + } + } + + if (mPersistFlags & PERSIST_FLAGS_FORCE_ALLOW_COOKIES) + { + nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = + do_QueryInterface(inputChannel); + if (httpChannelInternal) + httpChannelInternal->SetThirdPartyFlags(nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW); + } + + // Set the referrer, post data and headers if any + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(inputChannel)); + if (httpChannel) + { + // Referrer + if (aReferrer) + { + httpChannel->SetReferrerWithPolicy(aReferrer, aReferrerPolicy); + } + + // Post data + if (aPostData) + { + nsCOMPtr<nsISeekableStream> stream(do_QueryInterface(aPostData)); + if (stream) + { + // Rewind the postdata stream + stream->Seek(nsISeekableStream::NS_SEEK_SET, 0); + nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel)); + NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel"); + // Attach the postdata to the http channel + uploadChannel->SetUploadStream(aPostData, EmptyCString(), -1); + } + } + + // Cache key + nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(httpChannel)); + if (cacheChannel && cacheKey) + { + cacheChannel->SetCacheKey(cacheKey); + } + + // Headers + if (aExtraHeaders) + { + nsAutoCString oneHeader; + nsAutoCString headerName; + nsAutoCString headerValue; + int32_t crlf = 0; + int32_t colon = 0; + const char *kWhitespace = "\b\t\r\n "; + nsAutoCString extraHeaders(aExtraHeaders); + while (true) + { + crlf = extraHeaders.Find("\r\n", true); + if (crlf == -1) + break; + extraHeaders.Mid(oneHeader, 0, crlf); + extraHeaders.Cut(0, crlf + 2); + colon = oneHeader.Find(":"); + if (colon == -1) + break; // Should have a colon + oneHeader.Left(headerName, colon); + colon++; + oneHeader.Mid(headerValue, colon, oneHeader.Length() - colon); + headerName.Trim(kWhitespace); + headerValue.Trim(kWhitespace); + // Add the header (merging if required) + rv = httpChannel->SetRequestHeader(headerName, headerValue, true); + if (NS_FAILED(rv)) + { + EndDownload(NS_ERROR_FAILURE); + return NS_ERROR_FAILURE; + } + } + } + } + return SaveChannelInternal(inputChannel, aFile, aCalcFileExt); +} + +nsresult nsWebBrowserPersist::SaveChannelInternal( + nsIChannel *aChannel, nsIURI *aFile, bool aCalcFileExt) +{ + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aFile); + + // The default behaviour of SaveChannelInternal is to download the source + // into a storage stream and upload that to the target. MakeOutputStream + // special-cases a file target and creates a file output stream directly. + // We want to special-case a file source and create a file input stream, + // but we don't need to do this in the case of a file target. + nsCOMPtr<nsIFileChannel> fc(do_QueryInterface(aChannel)); + nsCOMPtr<nsIFileURL> fu(do_QueryInterface(aFile)); + + if (fc && !fu) { + nsCOMPtr<nsIInputStream> fileInputStream, bufferedInputStream; + nsresult rv = NS_MaybeOpenChannelUsingOpen2(aChannel, + getter_AddRefs(fileInputStream)); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedInputStream), + fileInputStream, BUFFERED_OUTPUT_SIZE); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString contentType; + aChannel->GetContentType(contentType); + return StartUpload(bufferedInputStream, aFile, contentType); + } + + // Read from the input channel + nsresult rv = NS_MaybeOpenChannelUsingAsyncOpen2(aChannel, this); + if (rv == NS_ERROR_NO_CONTENT) + { + // Assume this is a protocol such as mailto: which does not feed out + // data and just ignore it. + return NS_SUCCESS_DONT_FIXUP; + } + + if (NS_FAILED(rv)) + { + // Opening failed, but do we care? + if (mPersistFlags & PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS) + { + SendErrorStatusChange(true, rv, aChannel, aFile); + EndDownload(NS_ERROR_FAILURE); + return NS_ERROR_FAILURE; + } + return NS_SUCCESS_DONT_FIXUP; + } + + // Add the output transport to the output map with the channel as the key + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aChannel); + mOutputMap.Put(keyPtr, new OutputData(aFile, mURI, aCalcFileExt)); + + return NS_OK; +} + +nsresult +nsWebBrowserPersist::GetExtensionForContentType(const char16_t *aContentType, char16_t **aExt) +{ + NS_ENSURE_ARG_POINTER(aContentType); + NS_ENSURE_ARG_POINTER(aExt); + + *aExt = nullptr; + + nsresult rv; + if (!mMIMEService) + { + mMIMEService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv); + NS_ENSURE_TRUE(mMIMEService, NS_ERROR_FAILURE); + } + + nsAutoCString contentType; + contentType.AssignWithConversion(aContentType); + nsAutoCString ext; + rv = mMIMEService->GetPrimaryExtension(contentType, EmptyCString(), ext); + if (NS_SUCCEEDED(rv)) + { + *aExt = UTF8ToNewUnicode(ext); + NS_ENSURE_TRUE(*aExt, NS_ERROR_OUT_OF_MEMORY); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +nsresult +nsWebBrowserPersist::SaveDocumentDeferred(mozilla::UniquePtr<WalkData>&& aData) +{ + nsresult rv = + SaveDocumentInternal(aData->mDocument, aData->mFile, aData->mDataPath); + if (NS_FAILED(rv)) { + SendErrorStatusChange(true, rv, nullptr, mURI); + EndDownload(rv); + } + return rv; +} + +nsresult nsWebBrowserPersist::SaveDocumentInternal( + nsIWebBrowserPersistDocument *aDocument, nsIURI *aFile, nsIURI *aDataPath) +{ + mURI = nullptr; + NS_ENSURE_ARG_POINTER(aDocument); + NS_ENSURE_ARG_POINTER(aFile); + + nsresult rv = aDocument->SetPersistFlags(mPersistFlags); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aDocument->GetIsPrivate(&mIsPrivate); + NS_ENSURE_SUCCESS(rv, rv); + + // See if we can get the local file representation of this URI + nsCOMPtr<nsIFile> localFile; + rv = GetLocalFileFromURI(aFile, getter_AddRefs(localFile)); + + nsCOMPtr<nsIFile> localDataPath; + if (NS_SUCCEEDED(rv) && aDataPath) + { + // See if we can get the local file representation of this URI + rv = GetLocalFileFromURI(aDataPath, getter_AddRefs(localDataPath)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } + + // Persist the main document + rv = aDocument->GetCharacterSet(mCurrentCharset); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString uriSpec; + rv = aDocument->GetDocumentURI(uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_NewURI(getter_AddRefs(mURI), uriSpec, mCurrentCharset.get()); + NS_ENSURE_SUCCESS(rv, rv); + rv = aDocument->GetBaseURI(uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_NewURI(getter_AddRefs(mCurrentBaseURI), uriSpec, + mCurrentCharset.get()); + NS_ENSURE_SUCCESS(rv, rv); + + // Does the caller want to fixup the referenced URIs and save those too? + if (aDataPath) + { + // Basic steps are these. + // + // 1. Iterate through the document (and subdocuments) building a list + // of unique URIs. + // 2. For each URI create an OutputData entry and open a channel to save + // it. As each URI is saved, discover the mime type and fix up the + // local filename with the correct extension. + // 3. Store the document in a list and wait for URI persistence to finish + // 4. After URI persistence completes save the list of documents, + // fixing it up as it goes out to file. + + mCurrentDataPathIsRelative = false; + mCurrentDataPath = aDataPath; + mCurrentRelativePathToData = ""; + mCurrentThingsToPersist = 0; + mTargetBaseURI = aFile; + + // Determine if the specified data path is relative to the + // specified file, (e.g. c:\docs\htmldata is relative to + // c:\docs\myfile.htm, but not to d:\foo\data. + + // Starting with the data dir work back through its parents + // checking if one of them matches the base directory. + + if (localDataPath && localFile) + { + nsCOMPtr<nsIFile> baseDir; + localFile->GetParent(getter_AddRefs(baseDir)); + + nsAutoCString relativePathToData; + nsCOMPtr<nsIFile> dataDirParent; + dataDirParent = localDataPath; + while (dataDirParent) + { + bool sameDir = false; + dataDirParent->Equals(baseDir, &sameDir); + if (sameDir) + { + mCurrentRelativePathToData = relativePathToData; + mCurrentDataPathIsRelative = true; + break; + } + + nsAutoString dirName; + dataDirParent->GetLeafName(dirName); + + nsAutoCString newRelativePathToData; + newRelativePathToData = NS_ConvertUTF16toUTF8(dirName) + + NS_LITERAL_CSTRING("/") + + relativePathToData; + relativePathToData = newRelativePathToData; + + nsCOMPtr<nsIFile> newDataDirParent; + rv = dataDirParent->GetParent(getter_AddRefs(newDataDirParent)); + dataDirParent = newDataDirParent; + } + } + else + { + // generate a relative path if possible + nsCOMPtr<nsIURL> pathToBaseURL(do_QueryInterface(aFile)); + if (pathToBaseURL) + { + nsAutoCString relativePath; // nsACString + if (NS_SUCCEEDED(pathToBaseURL->GetRelativeSpec(aDataPath, relativePath))) + { + mCurrentDataPathIsRelative = true; + mCurrentRelativePathToData = relativePath; + } + } + } + + // Store the document in a list so when URI persistence is done and the + // filenames of saved URIs are known, the documents can be fixed up and + // saved + + DocData *docData = new DocData; + docData->mBaseURI = mCurrentBaseURI; + docData->mCharset = mCurrentCharset; + docData->mDocument = aDocument; + docData->mFile = aFile; + mDocList.AppendElement(docData); + + // Walk the DOM gathering a list of externally referenced URIs in the uri map + nsCOMPtr<nsIWebBrowserPersistResourceVisitor> visit = + new OnWalk(this, aFile, localDataPath); + return aDocument->ReadResources(visit); + } + else + { + DocData *docData = new DocData; + docData->mBaseURI = mCurrentBaseURI; + docData->mCharset = mCurrentCharset; + docData->mDocument = aDocument; + docData->mFile = aFile; + mDocList.AppendElement(docData); + + // Not walking DOMs, so go directly to serialization. + SerializeNextFile(); + return NS_OK; + } +} + +NS_IMETHODIMP +nsWebBrowserPersist::OnWalk::VisitResource(nsIWebBrowserPersistDocument* aDoc, + const nsACString& aURI) +{ + return mParent->StoreURI(nsAutoCString(aURI).get()); +} + +NS_IMETHODIMP +nsWebBrowserPersist::OnWalk::VisitDocument(nsIWebBrowserPersistDocument* aDoc, + nsIWebBrowserPersistDocument* aSubDoc) +{ + URIData* data = nullptr; + nsAutoCString uriSpec; + nsresult rv = aSubDoc->GetDocumentURI(uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + rv = mParent->StoreURI(uriSpec.get(), false, &data); + NS_ENSURE_SUCCESS(rv, rv); + if (!data) { + // If the URI scheme isn't persistable, then don't persist. + return NS_OK; + } + data->mIsSubFrame = true; + return mParent->SaveSubframeContent(aSubDoc, uriSpec, data); +} + + +NS_IMETHODIMP +nsWebBrowserPersist::OnWalk::EndVisit(nsIWebBrowserPersistDocument* aDoc, + nsresult aStatus) +{ + if (NS_FAILED(aStatus)) { + mParent->SendErrorStatusChange(true, aStatus, nullptr, mFile); + mParent->EndDownload(aStatus); + return aStatus; + } + mParent->FinishSaveDocumentInternal(mFile, mDataPath); + return NS_OK; +} + +void +nsWebBrowserPersist::FinishSaveDocumentInternal(nsIURI* aFile, + nsIFile* aDataPath) +{ + // If there are things to persist, create a directory to hold them + if (mCurrentThingsToPersist > 0) { + if (aDataPath) { + bool exists = false; + bool haveDir = false; + + aDataPath->Exists(&exists); + if (exists) { + aDataPath->IsDirectory(&haveDir); + } + if (!haveDir) { + nsresult rv = + aDataPath->Create(nsIFile::DIRECTORY_TYPE, 0755); + if (NS_SUCCEEDED(rv)) { + haveDir = true; + } else { + SendErrorStatusChange(false, rv, nullptr, aFile); + } + } + if (!haveDir) { + EndDownload(NS_ERROR_FAILURE); + return; + } + if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE) { + // Add to list of things to delete later if all goes wrong + CleanupData *cleanupData = new CleanupData; + cleanupData->mFile = aDataPath; + cleanupData->mIsDirectory = true; + mCleanupList.AppendElement(cleanupData); + } + } + } + + if (mWalkStack.Length() > 0) { + mozilla::UniquePtr<WalkData> toWalk; + mWalkStack.LastElement().swap(toWalk); + mWalkStack.TruncateLength(mWalkStack.Length() - 1); + // Bounce this off the event loop to avoid stack overflow. + typedef StoreCopyPassByRRef<decltype(toWalk)> WalkStorage; + auto saveMethod = &nsWebBrowserPersist::SaveDocumentDeferred; + nsCOMPtr<nsIRunnable> saveLater = + NewRunnableMethod<WalkStorage>(this, saveMethod, + mozilla::Move(toWalk)); + NS_DispatchToCurrentThread(saveLater); + } else { + // Done walking DOMs; on to the serialization phase. + SerializeNextFile(); + } +} + +void nsWebBrowserPersist::Cleanup() +{ + mURIMap.Clear(); + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(iter.Key()); + if (channel) { + channel->Cancel(NS_BINDING_ABORTED); + } + } + mOutputMap.Clear(); + + for (auto iter = mUploadList.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(iter.Key()); + if (channel) { + channel->Cancel(NS_BINDING_ABORTED); + } + } + mUploadList.Clear(); + + uint32_t i; + for (i = 0; i < mDocList.Length(); i++) { + DocData *docData = mDocList.ElementAt(i); + delete docData; + } + mDocList.Clear(); + + for (i = 0; i < mCleanupList.Length(); i++) { + CleanupData *cleanupData = mCleanupList.ElementAt(i); + delete cleanupData; + } + mCleanupList.Clear(); + + mFilenameList.Clear(); +} + +void nsWebBrowserPersist::CleanupLocalFiles() +{ + // Two passes, the first pass cleans up files, the second pass tests + // for and then deletes empty directories. Directories that are not + // empty after the first pass must contain files from something else + // and are not deleted. + int pass; + for (pass = 0; pass < 2; pass++) + { + uint32_t i; + for (i = 0; i < mCleanupList.Length(); i++) + { + CleanupData *cleanupData = mCleanupList.ElementAt(i); + nsCOMPtr<nsIFile> file = cleanupData->mFile; + + // Test if the dir / file exists (something in an earlier loop + // may have already removed it) + bool exists = false; + file->Exists(&exists); + if (!exists) + continue; + + // Test if the file has changed in between creation and deletion + // in some way that means it should be ignored + bool isDirectory = false; + file->IsDirectory(&isDirectory); + if (isDirectory != cleanupData->mIsDirectory) + continue; // A file has become a dir or vice versa ! + + if (pass == 0 && !isDirectory) + { + file->Remove(false); + } + else if (pass == 1 && isDirectory) // Directory + { + // Directories are more complicated. Enumerate through + // children looking for files. Any files created by the + // persist object would have been deleted by the first + // pass so if there are any there at this stage, the dir + // cannot be deleted because it has someone else's files + // in it. Empty child dirs are deleted but they must be + // recursed through to ensure they are actually empty. + + bool isEmptyDirectory = true; + nsCOMArray<nsISimpleEnumerator> dirStack; + int32_t stackSize = 0; + + // Push the top level enum onto the stack + nsCOMPtr<nsISimpleEnumerator> pos; + if (NS_SUCCEEDED(file->GetDirectoryEntries(getter_AddRefs(pos)))) + dirStack.AppendObject(pos); + + while (isEmptyDirectory && (stackSize = dirStack.Count())) + { + // Pop the last element + nsCOMPtr<nsISimpleEnumerator> curPos; + curPos = dirStack[stackSize-1]; + dirStack.RemoveObjectAt(stackSize - 1); + + // Test if the enumerator has any more files in it + bool hasMoreElements = false; + curPos->HasMoreElements(&hasMoreElements); + if (!hasMoreElements) + { + continue; + } + + // Child files automatically make this code drop out, + // while child dirs keep the loop going. + nsCOMPtr<nsISupports> child; + curPos->GetNext(getter_AddRefs(child)); + NS_ASSERTION(child, "No child element, but hasMoreElements says otherwise"); + if (!child) + continue; + nsCOMPtr<nsIFile> childAsFile = do_QueryInterface(child); + NS_ASSERTION(childAsFile, "This should be a file but isn't"); + + bool childIsSymlink = false; + childAsFile->IsSymlink(&childIsSymlink); + bool childIsDir = false; + childAsFile->IsDirectory(&childIsDir); + if (!childIsDir || childIsSymlink) + { + // Some kind of file or symlink which means dir + // is not empty so just drop out. + isEmptyDirectory = false; + break; + } + // Push parent enumerator followed by child enumerator + nsCOMPtr<nsISimpleEnumerator> childPos; + childAsFile->GetDirectoryEntries(getter_AddRefs(childPos)); + dirStack.AppendObject(curPos); + if (childPos) + dirStack.AppendObject(childPos); + + } + dirStack.Clear(); + + // If after all that walking the dir is deemed empty, delete it + if (isEmptyDirectory) + { + file->Remove(true); + } + } + } + } +} + +nsresult +nsWebBrowserPersist::CalculateUniqueFilename(nsIURI *aURI) +{ + nsCOMPtr<nsIURL> url(do_QueryInterface(aURI)); + NS_ENSURE_TRUE(url, NS_ERROR_FAILURE); + + bool nameHasChanged = false; + nsresult rv; + + // Get the old filename + nsAutoCString filename; + rv = url->GetFileName(filename); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + nsAutoCString directory; + rv = url->GetDirectory(directory); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Split the filename into a base and an extension. + // e.g. "foo.html" becomes "foo" & ".html" + // + // The nsIURL methods GetFileBaseName & GetFileExtension don't + // preserve the dot whereas this code does to save some effort + // later when everything is put back together. + int32_t lastDot = filename.RFind("."); + nsAutoCString base; + nsAutoCString ext; + if (lastDot >= 0) + { + filename.Mid(base, 0, lastDot); + filename.Mid(ext, lastDot, filename.Length() - lastDot); // includes dot + } + else + { + // filename contains no dot + base = filename; + } + + // Test if the filename is longer than allowed by the OS + int32_t needToChop = filename.Length() - kDefaultMaxFilenameLength; + if (needToChop > 0) + { + // Truncate the base first and then the ext if necessary + if (base.Length() > (uint32_t) needToChop) + { + base.Truncate(base.Length() - needToChop); + } + else + { + needToChop -= base.Length() - 1; + base.Truncate(1); + if (ext.Length() > (uint32_t) needToChop) + { + ext.Truncate(ext.Length() - needToChop); + } + else + { + ext.Truncate(0); + } + // If kDefaultMaxFilenameLength were 1 we'd be in trouble here, + // but that won't happen because it will be set to a sensible + // value. + } + + filename.Assign(base); + filename.Append(ext); + nameHasChanged = true; + } + + // Ensure the filename is unique + // Create a filename if it's empty, or if the filename / datapath is + // already taken by another URI and create an alternate name. + + if (base.IsEmpty() || !mFilenameList.IsEmpty()) + { + nsAutoCString tmpPath; + nsAutoCString tmpBase; + uint32_t duplicateCounter = 1; + while (1) + { + // Make a file name, + // Foo become foo_001, foo_002, etc. + // Empty files become _001, _002 etc. + + if (base.IsEmpty() || duplicateCounter > 1) + { + char * tmp = PR_smprintf("_%03d", duplicateCounter); + NS_ENSURE_TRUE(tmp, NS_ERROR_OUT_OF_MEMORY); + if (filename.Length() < kDefaultMaxFilenameLength - 4) + { + tmpBase = base; + } + else + { + base.Mid(tmpBase, 0, base.Length() - 4); + } + tmpBase.Append(tmp); + PR_smprintf_free(tmp); + } + else + { + tmpBase = base; + } + + tmpPath.Assign(directory); + tmpPath.Append(tmpBase); + tmpPath.Append(ext); + + // Test if the name is a duplicate + if (!mFilenameList.Contains(tmpPath)) + { + if (!base.Equals(tmpBase)) + { + filename.Assign(tmpBase); + filename.Append(ext); + nameHasChanged = true; + } + break; + } + duplicateCounter++; + } + } + + // Add name to list of those already used + nsAutoCString newFilepath(directory); + newFilepath.Append(filename); + mFilenameList.AppendElement(newFilepath); + + // Update the uri accordingly if the filename actually changed + if (nameHasChanged) + { + // Final sanity test + if (filename.Length() > kDefaultMaxFilenameLength) + { + NS_WARNING("Filename wasn't truncated less than the max file length - how can that be?"); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIFile> localFile; + GetLocalFileFromURI(aURI, getter_AddRefs(localFile)); + + if (localFile) + { + nsAutoString filenameAsUnichar; + filenameAsUnichar.AssignWithConversion(filename.get()); + localFile->SetLeafName(filenameAsUnichar); + + // Resync the URI with the file after the extension has been appended + nsresult rv; + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + fileURL->SetFile(localFile); // this should recalculate uri + } + else + { + url->SetFileName(filename); + } + } + + return NS_OK; +} + + +nsresult +nsWebBrowserPersist::MakeFilenameFromURI(nsIURI *aURI, nsString &aFilename) +{ + // Try to get filename from the URI. + nsAutoString fileName; + + // Get a suggested file name from the URL but strip it of characters + // likely to cause the name to be illegal. + + nsCOMPtr<nsIURL> url(do_QueryInterface(aURI)); + if (url) + { + nsAutoCString nameFromURL; + url->GetFileName(nameFromURL); + if (mPersistFlags & PERSIST_FLAGS_DONT_CHANGE_FILENAMES) + { + fileName.AssignWithConversion(NS_UnescapeURL(nameFromURL).BeginReading()); + aFilename = fileName; + return NS_OK; + } + if (!nameFromURL.IsEmpty()) + { + // Unescape the file name (GetFileName escapes it) + NS_UnescapeURL(nameFromURL); + uint32_t nameLength = 0; + const char *p = nameFromURL.get(); + for (;*p && *p != ';' && *p != '?' && *p != '#' && *p != '.' + ;p++) + { + if (nsCRT::IsAsciiAlpha(*p) || nsCRT::IsAsciiDigit(*p) + || *p == '.' || *p == '-' || *p == '_' || (*p == ' ')) + { + fileName.Append(char16_t(*p)); + if (++nameLength == kDefaultMaxFilenameLength) + { + // Note: + // There is no point going any further since it will be + // truncated in CalculateUniqueFilename anyway. + // More importantly, certain implementations of + // nsIFile (e.g. the Mac impl) might truncate + // names in undesirable ways, such as truncating from + // the middle, inserting ellipsis and so on. + break; + } + } + } + } + } + + // Empty filenames can confuse the local file object later + // when it attempts to set the leaf name in CalculateUniqueFilename + // for duplicates and ends up replacing the parent dir. To avoid + // the problem, all filenames are made at least one character long. + if (fileName.IsEmpty()) + { + fileName.Append(char16_t('a')); // 'a' is for arbitrary + } + + aFilename = fileName; + return NS_OK; +} + + +nsresult +nsWebBrowserPersist::CalculateAndAppendFileExt(nsIURI *aURI, nsIChannel *aChannel, nsIURI *aOriginalURIWithExtension) +{ + nsresult rv; + + if (!mMIMEService) + { + mMIMEService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv); + NS_ENSURE_TRUE(mMIMEService, NS_ERROR_FAILURE); + } + + nsAutoCString contentType; + + // Get the content type from the channel + aChannel->GetContentType(contentType); + + // Get the content type from the MIME service + if (contentType.IsEmpty()) + { + nsCOMPtr<nsIURI> uri; + aChannel->GetOriginalURI(getter_AddRefs(uri)); + mMIMEService->GetTypeFromURI(uri, contentType); + } + + // Append the extension onto the file + if (!contentType.IsEmpty()) + { + nsCOMPtr<nsIMIMEInfo> mimeInfo; + mMIMEService->GetFromTypeAndExtension( + contentType, EmptyCString(), getter_AddRefs(mimeInfo)); + + nsCOMPtr<nsIFile> localFile; + GetLocalFileFromURI(aURI, getter_AddRefs(localFile)); + + if (mimeInfo) + { + nsCOMPtr<nsIURL> url(do_QueryInterface(aURI)); + NS_ENSURE_TRUE(url, NS_ERROR_FAILURE); + + nsAutoCString newFileName; + url->GetFileName(newFileName); + + // Test if the current extension is current for the mime type + bool hasExtension = false; + int32_t ext = newFileName.RFind("."); + if (ext != -1) + { + mimeInfo->ExtensionExists(Substring(newFileName, ext + 1), &hasExtension); + } + + // Append the mime file extension + nsAutoCString fileExt; + if (!hasExtension) + { + // Test if previous extension is acceptable + nsCOMPtr<nsIURL> oldurl(do_QueryInterface(aOriginalURIWithExtension)); + NS_ENSURE_TRUE(oldurl, NS_ERROR_FAILURE); + oldurl->GetFileExtension(fileExt); + bool useOldExt = false; + if (!fileExt.IsEmpty()) + { + mimeInfo->ExtensionExists(fileExt, &useOldExt); + } + + // can't use old extension so use primary extension + if (!useOldExt) + { + mimeInfo->GetPrimaryExtension(fileExt); + } + + if (!fileExt.IsEmpty()) + { + uint32_t newLength = newFileName.Length() + fileExt.Length() + 1; + if (newLength > kDefaultMaxFilenameLength) + { + if (fileExt.Length() > kDefaultMaxFilenameLength/2) + fileExt.Truncate(kDefaultMaxFilenameLength/2); + + uint32_t diff = kDefaultMaxFilenameLength - 1 - + fileExt.Length(); + if (newFileName.Length() > diff) + newFileName.Truncate(diff); + } + newFileName.Append('.'); + newFileName.Append(fileExt); + } + + if (localFile) + { + localFile->SetLeafName(NS_ConvertUTF8toUTF16(newFileName)); + + // Resync the URI with the file after the extension has been appended + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + fileURL->SetFile(localFile); // this should recalculate uri + } + else + { + url->SetFileName(newFileName); + } + } + + } + } + + return NS_OK; +} + +nsresult +nsWebBrowserPersist::MakeOutputStream( + nsIURI *aURI, nsIOutputStream **aOutputStream) +{ + nsresult rv; + + nsCOMPtr<nsIFile> localFile; + GetLocalFileFromURI(aURI, getter_AddRefs(localFile)); + if (localFile) + rv = MakeOutputStreamFromFile(localFile, aOutputStream); + else + rv = MakeOutputStreamFromURI(aURI, aOutputStream); + + return rv; +} + +nsresult +nsWebBrowserPersist::MakeOutputStreamFromFile( + nsIFile *aFile, nsIOutputStream **aOutputStream) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsIFileOutputStream> fileOutputStream = + do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // XXX brade: get the right flags here! + int32_t ioFlags = -1; + if (mPersistFlags & nsIWebBrowserPersist::PERSIST_FLAGS_APPEND_TO_FILE) + ioFlags = PR_APPEND | PR_CREATE_FILE | PR_WRONLY; + rv = fileOutputStream->Init(aFile, ioFlags, -1, 0); + NS_ENSURE_SUCCESS(rv, rv); + + *aOutputStream = NS_BufferOutputStream(fileOutputStream, + BUFFERED_OUTPUT_SIZE).take(); + + if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE) + { + // Add to cleanup list in event of failure + CleanupData *cleanupData = new CleanupData; + if (!cleanupData) { + NS_RELEASE(*aOutputStream); + return NS_ERROR_OUT_OF_MEMORY; + } + cleanupData->mFile = aFile; + cleanupData->mIsDirectory = false; + mCleanupList.AppendElement(cleanupData); + } + + return NS_OK; +} + +nsresult +nsWebBrowserPersist::MakeOutputStreamFromURI( + nsIURI *aURI, nsIOutputStream **aOutputStream) +{ + uint32_t segsize = 8192; + uint32_t maxsize = uint32_t(-1); + nsCOMPtr<nsIStorageStream> storStream; + nsresult rv = NS_NewStorageStream(segsize, maxsize, getter_AddRefs(storStream)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_SUCCESS(CallQueryInterface(storStream, aOutputStream), NS_ERROR_FAILURE); + return NS_OK; +} + +void +nsWebBrowserPersist::FinishDownload() +{ + EndDownload(NS_OK); +} + +void +nsWebBrowserPersist::EndDownload(nsresult aResult) +{ + // Store the error code in the result if it is an error + if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(aResult)) + { + mPersistResult = aResult; + } + + // mCompleted needs to be set before issuing the stop notification. + // (Bug 1224437) + mCompleted = true; + // State stop notification + if (mProgressListener) { + mProgressListener->OnStateChange(nullptr, nullptr, + nsIWebProgressListener::STATE_STOP + | nsIWebProgressListener::STATE_IS_NETWORK, mPersistResult); + } + + // Do file cleanup if required + if (NS_FAILED(aResult) && (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE)) + { + CleanupLocalFiles(); + } + + // Cleanup the channels + Cleanup(); + + mProgressListener = nullptr; + mProgressListener2 = nullptr; + mEventSink = nullptr; +} + +nsresult +nsWebBrowserPersist::FixRedirectedChannelEntry(nsIChannel *aNewChannel) +{ + NS_ENSURE_ARG_POINTER(aNewChannel); + + // Iterate through existing open channels looking for one with a URI + // matching the one specified. + nsCOMPtr<nsIURI> originalURI; + aNewChannel->GetOriginalURI(getter_AddRefs(originalURI)); + nsISupports* matchingKey = nullptr; + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + nsISupports* key = iter.Key(); + nsCOMPtr<nsIChannel> thisChannel = do_QueryInterface(key); + nsCOMPtr<nsIURI> thisURI; + + thisChannel->GetOriginalURI(getter_AddRefs(thisURI)); + + // Compare this channel's URI to the one passed in. + bool matchingURI = false; + thisURI->Equals(originalURI, &matchingURI); + if (matchingURI) { + matchingKey = key; + break; + } + } + + if (matchingKey) { + // If a match was found, remove the data entry with the old channel + // key and re-add it with the new channel key. + nsAutoPtr<OutputData> outputData; + mOutputMap.RemoveAndForget(matchingKey, outputData); + NS_ENSURE_TRUE(outputData, NS_ERROR_FAILURE); + + // Store data again with new channel unless told to ignore redirects. + if (!(mPersistFlags & PERSIST_FLAGS_IGNORE_REDIRECTED_DATA)) { + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aNewChannel); + mOutputMap.Put(keyPtr, outputData.forget()); + } + } + + return NS_OK; +} + +void +nsWebBrowserPersist::CalcTotalProgress() +{ + mTotalCurrentProgress = 0; + mTotalMaxProgress = 0; + + if (mOutputMap.Count() > 0) { + // Total up the progress of each output stream + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + // Only count toward total progress if destination file is local. + OutputData* data = iter.UserData(); + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(data->mFile); + if (fileURL) { + mTotalCurrentProgress += data->mSelfProgress; + mTotalMaxProgress += data->mSelfProgressMax; + } + } + } + + if (mUploadList.Count() > 0) { + // Total up the progress of each upload + for (auto iter = mUploadList.Iter(); !iter.Done(); iter.Next()) { + UploadData* data = iter.UserData(); + if (data) { + mTotalCurrentProgress += data->mSelfProgress; + mTotalMaxProgress += data->mSelfProgressMax; + } + } + } + + // XXX this code seems pretty bogus and pointless + if (mTotalCurrentProgress == 0 && mTotalMaxProgress == 0) + { + // No output streams so we must be complete + mTotalCurrentProgress = 10000; + mTotalMaxProgress = 10000; + } +} + +nsresult +nsWebBrowserPersist::StoreURI( + const char *aURI, bool aNeedsPersisting, URIData **aData) +{ + NS_ENSURE_ARG_POINTER(aURI); + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), + nsDependentCString(aURI), + mCurrentCharset.get(), + mCurrentBaseURI); + NS_ENSURE_SUCCESS(rv, rv); + + return StoreURI(uri, aNeedsPersisting, aData); +} + +nsresult +nsWebBrowserPersist::StoreURI( + nsIURI *aURI, bool aNeedsPersisting, URIData **aData) +{ + NS_ENSURE_ARG_POINTER(aURI); + if (aData) + { + *aData = nullptr; + } + + // Test if this URI should be persisted. By default + // we should assume the URI is persistable. + bool doNotPersistURI; + nsresult rv = NS_URIChainHasFlags(aURI, + nsIProtocolHandler::URI_NON_PERSISTABLE, + &doNotPersistURI); + if (NS_FAILED(rv)) + { + doNotPersistURI = false; + } + + if (doNotPersistURI) + { + return NS_OK; + } + + URIData *data = nullptr; + MakeAndStoreLocalFilenameInURIMap(aURI, aNeedsPersisting, &data); + if (aData) + { + *aData = data; + } + + return NS_OK; +} + +nsresult +nsWebBrowserPersist::URIData::GetLocalURI(nsIURI *targetBaseURI, nsCString& aSpecOut) +{ + aSpecOut.SetIsVoid(true); + if (!mNeedsFixup) { + return NS_OK; + } + nsresult rv; + nsCOMPtr<nsIURI> fileAsURI; + if (mFile) { + rv = mFile->Clone(getter_AddRefs(fileAsURI)); + NS_ENSURE_SUCCESS(rv, rv); + } else { + rv = mDataPath->Clone(getter_AddRefs(fileAsURI)); + NS_ENSURE_SUCCESS(rv, rv); + rv = AppendPathToURI(fileAsURI, mFilename); + NS_ENSURE_SUCCESS(rv, rv); + } + + // remove username/password if present + fileAsURI->SetUserPass(EmptyCString()); + + // reset node attribute + // Use relative or absolute links + if (mDataPathIsRelative) { + bool isEqual = false; + if (NS_SUCCEEDED(mRelativeDocumentURI->Equals(targetBaseURI, &isEqual)) && isEqual) { + nsCOMPtr<nsIURL> url(do_QueryInterface(fileAsURI)); + if (!url) { + return NS_ERROR_FAILURE; + } + + nsAutoCString filename; + url->GetFileName(filename); + + nsAutoCString rawPathURL(mRelativePathToData); + rawPathURL.Append(filename); + + rv = NS_EscapeURL(rawPathURL, esc_FilePath, aSpecOut, fallible); + NS_ENSURE_SUCCESS(rv, rv); + } else { + nsAutoCString rawPathURL; + + nsCOMPtr<nsIFile> dataFile; + rv = GetLocalFileFromURI(mFile, getter_AddRefs(dataFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> docFile; + rv = GetLocalFileFromURI(targetBaseURI, getter_AddRefs(docFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> parentDir; + rv = docFile->GetParent(getter_AddRefs(parentDir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = dataFile->GetRelativePath(parentDir, rawPathURL); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_EscapeURL(rawPathURL, esc_FilePath, aSpecOut, fallible); + NS_ENSURE_SUCCESS(rv, rv); + } + } else { + fileAsURI->GetSpec(aSpecOut); + } + if (mIsSubFrame) { + AppendUTF16toUTF8(mSubFrameExt, aSpecOut); + } + + return NS_OK; +} + +bool +nsWebBrowserPersist::DocumentEncoderExists(const char *aContentType) +{ + // Check if there is an encoder for the desired content type. + nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE); + contractID.Append(aContentType); + + nsCOMPtr<nsIComponentRegistrar> registrar; + NS_GetComponentRegistrar(getter_AddRefs(registrar)); + if (registrar) + { + bool result; + nsresult rv = registrar->IsContractIDRegistered(contractID.get(), + &result); + if (NS_SUCCEEDED(rv) && result) + { + return true; + } + } + return false; +} + +nsresult +nsWebBrowserPersist::SaveSubframeContent( + nsIWebBrowserPersistDocument *aFrameContent, + const nsCString& aURISpec, + URIData *aData) +{ + NS_ENSURE_ARG_POINTER(aData); + + // Extract the content type for the frame's contents. + nsAutoCString contentType; + nsresult rv = aFrameContent->GetContentType(contentType); + NS_ENSURE_SUCCESS(rv, rv); + + nsXPIDLString ext; + GetExtensionForContentType(NS_ConvertASCIItoUTF16(contentType).get(), + getter_Copies(ext)); + + // We must always have an extension so we will try to re-assign + // the original extension if GetExtensionForContentType fails. + if (ext.IsEmpty()) { + nsCOMPtr<nsIURI> docURI; + rv = NS_NewURI(getter_AddRefs(docURI), aURISpec, mCurrentCharset.get()); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURL> url(do_QueryInterface(docURI, &rv)); + nsAutoCString extension; + if (NS_SUCCEEDED(rv)) { + url->GetFileExtension(extension); + } else { + extension.AssignLiteral("htm"); + } + aData->mSubFrameExt.Assign(char16_t('.')); + AppendUTF8toUTF16(extension, aData->mSubFrameExt); + } else { + aData->mSubFrameExt.Assign(char16_t('.')); + aData->mSubFrameExt.Append(ext); + } + + nsString filenameWithExt = aData->mFilename; + filenameWithExt.Append(aData->mSubFrameExt); + + // Work out the path for the subframe + nsCOMPtr<nsIURI> frameURI; + rv = mCurrentDataPath->Clone(getter_AddRefs(frameURI)); + NS_ENSURE_SUCCESS(rv, rv); + rv = AppendPathToURI(frameURI, filenameWithExt); + NS_ENSURE_SUCCESS(rv, rv); + + // Work out the path for the subframe data + nsCOMPtr<nsIURI> frameDataURI; + rv = mCurrentDataPath->Clone(getter_AddRefs(frameDataURI)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoString newFrameDataPath(aData->mFilename); + + // Append _data + newFrameDataPath.AppendLiteral("_data"); + rv = AppendPathToURI(frameDataURI, newFrameDataPath); + NS_ENSURE_SUCCESS(rv, rv); + + // Make frame document & data path conformant and unique + rv = CalculateUniqueFilename(frameURI); + NS_ENSURE_SUCCESS(rv, rv); + rv = CalculateUniqueFilename(frameDataURI); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentThingsToPersist++; + + // We shouldn't use SaveDocumentInternal for the contents + // of frames that are not documents, e.g. images. + if (DocumentEncoderExists(contentType.get())) { + auto toWalk = mozilla::MakeUnique<WalkData>(); + toWalk->mDocument = aFrameContent; + toWalk->mFile = frameURI; + toWalk->mDataPath = frameDataURI; + mWalkStack.AppendElement(mozilla::Move(toWalk)); + } else { + rv = StoreURI(aURISpec.get()); + } + NS_ENSURE_SUCCESS(rv, rv); + + // Store the updated uri to the frame + aData->mFile = frameURI; + aData->mSubFrameExt.Truncate(); // we already put this in frameURI + + return NS_OK; +} + +nsresult +nsWebBrowserPersist::CreateChannelFromURI(nsIURI *aURI, nsIChannel **aChannel) +{ + nsresult rv = NS_OK; + *aChannel = nullptr; + + rv = NS_NewChannel(aChannel, + aURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_ARG_POINTER(*aChannel); + + rv = (*aChannel)->SetNotificationCallbacks(static_cast<nsIInterfaceRequestor*>(this)); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + + +// we store the current location as the key (absolutized version of domnode's attribute's value) +nsresult +nsWebBrowserPersist::MakeAndStoreLocalFilenameInURIMap( + nsIURI *aURI, bool aNeedsPersisting, URIData **aData) +{ + NS_ENSURE_ARG_POINTER(aURI); + + nsAutoCString spec; + nsresult rv = aURI->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Create a sensibly named filename for the URI and store in the URI map + URIData *data; + if (mURIMap.Contains(spec)) + { + data = mURIMap.Get(spec); + if (aNeedsPersisting) + { + data->mNeedsPersisting = true; + } + if (aData) + { + *aData = data; + } + return NS_OK; + } + + // Create a unique file name for the uri + nsString filename; + rv = MakeFilenameFromURI(aURI, filename); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Store the file name + data = new URIData; + NS_ENSURE_TRUE(data, NS_ERROR_OUT_OF_MEMORY); + + data->mNeedsPersisting = aNeedsPersisting; + data->mNeedsFixup = true; + data->mFilename = filename; + data->mSaved = false; + data->mIsSubFrame = false; + data->mDataPath = mCurrentDataPath; + data->mDataPathIsRelative = mCurrentDataPathIsRelative; + data->mRelativePathToData = mCurrentRelativePathToData; + data->mRelativeDocumentURI = mTargetBaseURI; + data->mCharset = mCurrentCharset; + + if (aNeedsPersisting) + mCurrentThingsToPersist++; + + mURIMap.Put(spec, data); + if (aData) + { + *aData = data; + } + + return NS_OK; +} + +// Decide if we need to apply conversion to the passed channel. +void nsWebBrowserPersist::SetApplyConversionIfNeeded(nsIChannel *aChannel) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface(aChannel, &rv); + if (NS_FAILED(rv)) + return; + + // Set the default conversion preference: + encChannel->SetApplyConversion(false); + + nsCOMPtr<nsIURI> thisURI; + aChannel->GetURI(getter_AddRefs(thisURI)); + nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(thisURI)); + if (!sourceURL) + return; + nsAutoCString extension; + sourceURL->GetFileExtension(extension); + + nsCOMPtr<nsIUTF8StringEnumerator> encEnum; + encChannel->GetContentEncodings(getter_AddRefs(encEnum)); + if (!encEnum) + return; + nsCOMPtr<nsIExternalHelperAppService> helperAppService = + do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return; + bool hasMore; + rv = encEnum->HasMore(&hasMore); + if (NS_SUCCEEDED(rv) && hasMore) + { + nsAutoCString encType; + rv = encEnum->GetNext(encType); + if (NS_SUCCEEDED(rv)) + { + bool applyConversion = false; + rv = helperAppService->ApplyDecodingForExtension(extension, encType, + &applyConversion); + if (NS_SUCCEEDED(rv)) + encChannel->SetApplyConversion(applyConversion); + } + } +} diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h new file mode 100644 index 000000000..1816af0a6 --- /dev/null +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h @@ -0,0 +1,181 @@ +/* -*- 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 nsWebBrowserPersist_h__ +#define nsWebBrowserPersist_h__ + +#include "nsCOMPtr.h" +#include "nsWeakReference.h" + +#include "nsIInterfaceRequestor.h" +#include "nsIMIMEService.h" +#include "nsIStreamListener.h" +#include "nsIOutputStream.h" +#include "nsIInputStream.h" +#include "nsIChannel.h" +#include "nsIDocumentEncoder.h" +#include "nsITransport.h" +#include "nsIProgressEventSink.h" +#include "nsIFile.h" +#include "nsIWebProgressListener2.h" +#include "nsIWebBrowserPersistDocument.h" + +#include "mozilla/UniquePtr.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" +#include "nsTArray.h" + +#include "nsCWebBrowserPersist.h" + +class nsIStorageStream; +class nsIWebBrowserPersistDocument; + +class nsWebBrowserPersist final : public nsIInterfaceRequestor, + public nsIWebBrowserPersist, + public nsIStreamListener, + public nsIProgressEventSink, + public nsSupportsWeakReference +{ + friend class nsEncoderNodeFixup; + +// Public members +public: + nsWebBrowserPersist(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICANCELABLE + NS_DECL_NSIWEBBROWSERPERSIST + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIPROGRESSEVENTSINK + +// Private members +private: + virtual ~nsWebBrowserPersist(); + nsresult SaveURIInternal( + nsIURI *aURI, nsISupports *aCacheKey, nsIURI *aReferrer, + uint32_t aReferrerPolicy, nsIInputStream *aPostData, + const char *aExtraHeaders, nsIURI *aFile, + bool aCalcFileExt, bool aIsPrivate); + nsresult SaveChannelInternal( + nsIChannel *aChannel, nsIURI *aFile, bool aCalcFileExt); + nsresult SaveDocumentInternal( + nsIWebBrowserPersistDocument *aDocument, + nsIURI *aFile, + nsIURI *aDataPath); + nsresult SaveDocuments(); + void FinishSaveDocumentInternal(nsIURI* aFile, nsIFile* aDataPath); + nsresult GetExtensionForContentType( + const char16_t *aContentType, char16_t **aExt); + + struct CleanupData; + struct DocData; + struct OutputData; + struct UploadData; + struct URIData; + struct WalkData; + struct URIFixupData; + + class OnWalk; + class OnWrite; + class FlatURIMap; + friend class OnWalk; + friend class OnWrite; + + nsresult SaveDocumentDeferred(mozilla::UniquePtr<WalkData>&& aData); + void Cleanup(); + void CleanupLocalFiles(); + nsresult GetValidURIFromObject(nsISupports *aObject, nsIURI **aURI) const; + static nsresult GetLocalFileFromURI(nsIURI *aURI, nsIFile **aLocalFile); + static nsresult AppendPathToURI(nsIURI *aURI, const nsAString & aPath); + nsresult MakeAndStoreLocalFilenameInURIMap( + nsIURI *aURI, bool aNeedsPersisting, URIData **aData); + nsresult MakeOutputStream( + nsIURI *aFile, nsIOutputStream **aOutputStream); + nsresult MakeOutputStreamFromFile( + nsIFile *aFile, nsIOutputStream **aOutputStream); + nsresult MakeOutputStreamFromURI(nsIURI *aURI, nsIOutputStream **aOutStream); + nsresult CreateChannelFromURI(nsIURI *aURI, nsIChannel **aChannel); + nsresult StartUpload(nsIStorageStream *aOutStream, nsIURI *aDestinationURI, + const nsACString &aContentType); + nsresult StartUpload(nsIInputStream *aInputStream, nsIURI *aDestinationURI, + const nsACString &aContentType); + nsresult CalculateAndAppendFileExt(nsIURI *aURI, nsIChannel *aChannel, + nsIURI *aOriginalURIWithExtension); + nsresult CalculateUniqueFilename(nsIURI *aURI); + nsresult MakeFilenameFromURI( + nsIURI *aURI, nsString &aFilename); + nsresult StoreURI( + const char *aURI, + bool aNeedsPersisting = true, + URIData **aData = nullptr); + nsresult StoreURI( + nsIURI *aURI, + bool aNeedsPersisting = true, + URIData **aData = nullptr); + bool DocumentEncoderExists(const char *aContentType); + + nsresult SaveSubframeContent( + nsIWebBrowserPersistDocument *aFrameContent, + const nsCString& aURISpec, + URIData *aData); + nsresult SendErrorStatusChange( + bool aIsReadError, nsresult aResult, nsIRequest *aRequest, nsIURI *aURI); + + nsresult FixRedirectedChannelEntry(nsIChannel *aNewChannel); + + void EndDownload(nsresult aResult); + void FinishDownload(); + void SerializeNextFile(); + void CalcTotalProgress(); + + void SetApplyConversionIfNeeded(nsIChannel *aChannel); + + nsCOMPtr<nsIURI> mCurrentDataPath; + bool mCurrentDataPathIsRelative; + nsCString mCurrentRelativePathToData; + nsCOMPtr<nsIURI> mCurrentBaseURI; + nsCString mCurrentCharset; + nsCOMPtr<nsIURI> mTargetBaseURI; + uint32_t mCurrentThingsToPersist; + + nsCOMPtr<nsIMIMEService> mMIMEService; + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsIWebProgressListener> mProgressListener; + /** + * Progress listener for 64-bit values; this is the same object as + * mProgressListener, but is a member to avoid having to qi it for each + * progress notification. + */ + nsCOMPtr<nsIWebProgressListener2> mProgressListener2; + nsCOMPtr<nsIProgressEventSink> mEventSink; + nsClassHashtable<nsISupportsHashKey, OutputData> mOutputMap; + nsClassHashtable<nsISupportsHashKey, UploadData> mUploadList; + nsClassHashtable<nsCStringHashKey, URIData> mURIMap; + nsCOMPtr<nsIWebBrowserPersistURIMap> mFlatURIMap; + nsTArray<mozilla::UniquePtr<WalkData>> mWalkStack; + nsTArray<DocData*> mDocList; + nsTArray<CleanupData*> mCleanupList; + nsTArray<nsCString> mFilenameList; + bool mFirstAndOnlyUse; + bool mSavingDocument; + bool mCancel; + bool mCompleted; + bool mStartSaving; + bool mReplaceExisting; + bool mSerializingOutput; + bool mIsPrivate; + uint32_t mPersistFlags; + nsresult mPersistResult; + int64_t mTotalCurrentProgress; + int64_t mTotalMaxProgress; + int16_t mWrapColumn; + uint32_t mEncodingFlags; + nsString mContentType; +}; + +#endif diff --git a/embedding/components/windowwatcher/moz.build b/embedding/components/windowwatcher/moz.build new file mode 100644 index 000000000..31aa718c9 --- /dev/null +++ b/embedding/components/windowwatcher/moz.build @@ -0,0 +1,49 @@ +# -*- 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/. + +TEST_DIRS += ['test'] + +XPIDL_SOURCES += [ + 'nsIDialogParamBlock.idl', + 'nsIPromptFactory.idl', + 'nsIPromptService.idl', + 'nsIPromptService2.idl', + 'nsIWindowWatcher.idl', + 'nsPIPromptService.idl', + 'nsPIWindowWatcher.idl', +] + +XPIDL_MODULE = 'windowwatcher' + +EXPORTS += [ + 'nsPromptUtils.h', +] + +UNIFIED_SOURCES += [ + 'nsAutoWindowStateHelper.cpp', + 'nsWindowWatcher.cpp', +] + +EXPORTS += [ + 'nsWindowWatcher.h', +] + +if CONFIG['MOZ_XUL']: + UNIFIED_SOURCES += [ + 'nsDialogParamBlock.cpp', + ] + +FINAL_LIBRARY = 'xul' +# For nsJSUtils +LOCAL_INCLUDES += [ + '/docshell/base', + '/dom/base', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] + +include('/ipc/chromium/chromium-config.mozbuild') diff --git a/embedding/components/windowwatcher/nsAutoWindowStateHelper.cpp b/embedding/components/windowwatcher/nsAutoWindowStateHelper.cpp new file mode 100644 index 000000000..068f2a6dc --- /dev/null +++ b/embedding/components/windowwatcher/nsAutoWindowStateHelper.cpp @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAutoWindowStateHelper.h" + +#include "mozilla/dom/Event.h" +#include "nsIDocument.h" +#include "nsIDOMEvent.h" +#include "nsIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsString.h" + +using namespace mozilla; +using namespace mozilla::dom; + +/**************************************************************** + ****************** nsAutoWindowStateHelper ********************* + ****************************************************************/ + +nsAutoWindowStateHelper::nsAutoWindowStateHelper(nsPIDOMWindowOuter* aWindow) + : mWindow(aWindow) + , mDefaultEnabled(DispatchEventToChrome("DOMWillOpenModalDialog")) +{ + if (mWindow) { + mWindow->EnterModalState(); + } +} + +nsAutoWindowStateHelper::~nsAutoWindowStateHelper() +{ + if (mWindow) { + mWindow->LeaveModalState(); + } + + if (mDefaultEnabled) { + DispatchEventToChrome("DOMModalDialogClosed"); + } +} + +bool +nsAutoWindowStateHelper::DispatchEventToChrome(const char* aEventName) +{ + // XXXbz should we skip dispatching the event if the inner changed? + // That is, should we store both the inner and the outer? + if (!mWindow) { + return true; + } + + // The functions of nsContentUtils do not provide the required behavior, + // so the following is inlined. + nsIDocument* doc = mWindow->GetExtantDoc(); + if (!doc) { + return true; + } + + ErrorResult rv; + RefPtr<Event> event = doc->CreateEvent(NS_LITERAL_STRING("Events"), rv); + if (rv.Failed()) { + rv.SuppressException(); + return false; + } + event->InitEvent(NS_ConvertASCIItoUTF16(aEventName), true, true); + event->SetTrusted(true); + event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; + + nsCOMPtr<EventTarget> target = do_QueryInterface(mWindow); + bool defaultActionEnabled; + target->DispatchEvent(event, &defaultActionEnabled); + return defaultActionEnabled; +} diff --git a/embedding/components/windowwatcher/nsAutoWindowStateHelper.h b/embedding/components/windowwatcher/nsAutoWindowStateHelper.h new file mode 100644 index 000000000..875fb4ba0 --- /dev/null +++ b/embedding/components/windowwatcher/nsAutoWindowStateHelper.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsAutoWindowStateHelper_h +#define __nsAutoWindowStateHelper_h + +#include "nsCOMPtr.h" +#include "nsPIDOMWindow.h" + +/** + * Helper class for dealing with notifications around opening modal + * windows. + */ + +class nsPIDOMWindowOuter; + +class nsAutoWindowStateHelper +{ +public: + explicit nsAutoWindowStateHelper(nsPIDOMWindowOuter* aWindow); + ~nsAutoWindowStateHelper(); + + bool DefaultEnabled() { return mDefaultEnabled; } + +protected: + bool DispatchEventToChrome(const char* aEventName); + + nsCOMPtr<nsPIDOMWindowOuter> mWindow; + bool mDefaultEnabled; +}; + +#endif diff --git a/embedding/components/windowwatcher/nsDialogParamBlock.cpp b/embedding/components/windowwatcher/nsDialogParamBlock.cpp new file mode 100644 index 000000000..f6f7af967 --- /dev/null +++ b/embedding/components/windowwatcher/nsDialogParamBlock.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDialogParamBlock.h" +#include "nsString.h" +#include "nsReadableUtils.h" + +NS_IMPL_ISUPPORTS(nsDialogParamBlock, nsIDialogParamBlock) + +nsDialogParamBlock::nsDialogParamBlock() + : mNumStrings(0) + , mString(nullptr) +{ + for (int32_t i = 0; i < kNumInts; i++) { + mInt[i] = 0; + } +} + +nsDialogParamBlock::~nsDialogParamBlock() +{ + delete[] mString; +} + +NS_IMETHODIMP +nsDialogParamBlock::SetNumberStrings(int32_t aNumStrings) +{ + if (mString) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + mString = new nsString[aNumStrings]; + if (!mString) { + return NS_ERROR_OUT_OF_MEMORY; + } + mNumStrings = aNumStrings; + return NS_OK; +} + +NS_IMETHODIMP +nsDialogParamBlock::GetInt(int32_t aIndex, int32_t* aResult) +{ + nsresult rv = InBounds(aIndex, kNumInts); + if (rv == NS_OK) { + *aResult = mInt[aIndex]; + } + return rv; +} + +NS_IMETHODIMP +nsDialogParamBlock::SetInt(int32_t aIndex, int32_t aInt) +{ + nsresult rv = InBounds(aIndex, kNumInts); + if (rv == NS_OK) { + mInt[aIndex] = aInt; + } + return rv; +} + +NS_IMETHODIMP +nsDialogParamBlock::GetString(int32_t aIndex, char16_t** aResult) +{ + if (mNumStrings == 0) { + SetNumberStrings(kNumStrings); + } + nsresult rv = InBounds(aIndex, mNumStrings); + if (rv == NS_OK) { + *aResult = ToNewUnicode(mString[aIndex]); + } + return rv; +} + +NS_IMETHODIMP +nsDialogParamBlock::SetString(int32_t aIndex, const char16_t* aString) +{ + if (mNumStrings == 0) { + SetNumberStrings(kNumStrings); + } + nsresult rv = InBounds(aIndex, mNumStrings); + if (rv == NS_OK) { + mString[aIndex] = aString; + } + return rv; +} + +NS_IMETHODIMP +nsDialogParamBlock::GetObjects(nsIMutableArray** aObjects) +{ + NS_ENSURE_ARG_POINTER(aObjects); + NS_IF_ADDREF(*aObjects = mObjects); + return NS_OK; +} + +NS_IMETHODIMP +nsDialogParamBlock::SetObjects(nsIMutableArray* aObjects) +{ + mObjects = aObjects; + return NS_OK; +} diff --git a/embedding/components/windowwatcher/nsDialogParamBlock.h b/embedding/components/windowwatcher/nsDialogParamBlock.h new file mode 100644 index 000000000..c1b06ea53 --- /dev/null +++ b/embedding/components/windowwatcher/nsDialogParamBlock.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsDialogParamBlock_h +#define __nsDialogParamBlock_h + +#include "nsIDialogParamBlock.h" +#include "nsIMutableArray.h" +#include "nsCOMPtr.h" + +// {4E4AAE11-8901-46cc-8217-DAD7C5415873} +#define NS_DIALOGPARAMBLOCK_CID \ + {0x4e4aae11, 0x8901, 0x46cc, {0x82, 0x17, 0xda, 0xd7, 0xc5, 0x41, 0x58, 0x73}} + +class nsString; + +class nsDialogParamBlock : public nsIDialogParamBlock +{ +public: + nsDialogParamBlock(); + + NS_DECL_NSIDIALOGPARAMBLOCK + NS_DECL_ISUPPORTS + +protected: + virtual ~nsDialogParamBlock(); + +private: + enum { kNumInts = 8, kNumStrings = 16 }; + + nsresult InBounds(int32_t aIndex, int32_t aMax) + { + return aIndex >= 0 && aIndex < aMax ? NS_OK : NS_ERROR_ILLEGAL_VALUE; + } + + int32_t mInt[kNumInts]; + int32_t mNumStrings; + nsString* mString; + nsCOMPtr<nsIMutableArray> mObjects; +}; + +#endif diff --git a/embedding/components/windowwatcher/nsIDialogParamBlock.idl b/embedding/components/windowwatcher/nsIDialogParamBlock.idl new file mode 100644 index 000000000..d917776ec --- /dev/null +++ b/embedding/components/windowwatcher/nsIDialogParamBlock.idl @@ -0,0 +1,42 @@ +/* -*- 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 nsIMutableArray; + +/** + * An interface to pass strings, integers and nsISupports to a dialog + */ + +[scriptable, uuid(f76c0901-437a-11d3-b7a0-e35db351b4bc)] +interface nsIDialogParamBlock: nsISupports { + + /** Get or set an integer to pass. + * Index must be in the range 0..7 + */ + int32_t GetInt( in int32_t inIndex ); + void SetInt( in int32_t inIndex, in int32_t inInt ); + + /** Set the maximum number of strings to pass. Default is 16. + * Use before setting any string (If you want to change it from the default). + */ + void SetNumberStrings( in int32_t inNumStrings ); + + /** Get or set an string to pass. + * Index starts at 0 + */ + wstring GetString( in int32_t inIndex ); + void SetString( in int32_t inIndex, in wstring inString); + + /** + * A place where you can store an nsIMutableArray to pass nsISupports + */ + attribute nsIMutableArray objects; +}; + +%{C++ +#define NS_DIALOGPARAMBLOCK_CONTRACTID "@mozilla.org/embedcomp/dialogparam;1" +%} + diff --git a/embedding/components/windowwatcher/nsIPromptFactory.idl b/embedding/components/windowwatcher/nsIPromptFactory.idl new file mode 100644 index 000000000..b16db3d49 --- /dev/null +++ b/embedding/components/windowwatcher/nsIPromptFactory.idl @@ -0,0 +1,22 @@ +/* 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 mozIDOMWindowProxy; + +/** + * This interface allows creating various prompts that have a specific parent. + */ +[scriptable, uuid(2803541c-c96a-4ff1-bd7c-9cb566d46aeb)] +interface nsIPromptFactory : nsISupports +{ + /** + * Returns an object implementing the specified interface that creates + * prompts parented to aParent. + */ + void getPrompt(in mozIDOMWindowProxy aParent, in nsIIDRef iid, + [iid_is(iid),retval] out nsQIResult result); +}; + diff --git a/embedding/components/windowwatcher/nsIPromptService.idl b/embedding/components/windowwatcher/nsIPromptService.idl new file mode 100644 index 000000000..24c75e3a9 --- /dev/null +++ b/embedding/components/windowwatcher/nsIPromptService.idl @@ -0,0 +1,346 @@ +/* -*- Mode: C++; tab-width: 2; 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 mozIDOMWindowProxy; + +/** + * This is the interface to the embeddable prompt service; the service that + * implements nsIPrompt. Its interface is designed to be just nsIPrompt, each + * method modified to take a parent window parameter. + * + * Accesskeys can be attached to buttons and checkboxes by inserting an & + * before the accesskey character in the checkbox message or button title. For + * a real &, use && instead. (A "button title" generally refers to the text + * label of a button.) + * + * One note: in all cases, the parent window parameter can be null. However, + * these windows are all intended to have parents. So when no parent is + * specified, the implementation should try hard to find a suitable foster + * parent. + * + * Implementations are free to choose how they present the various button + * types. For example, while prompts that give the user a choice between OK + * and Cancel are required to return a boolean value indicating whether or not + * the user accepted the prompt (pressed OK) or rejected the prompt (pressed + * Cancel), the implementation of this interface could very well speak the + * prompt to the user instead of rendering any visual user-interface. The + * standard button types are merely idioms used to convey the nature of the + * choice the user is to make. + * + * Because implementations of this interface may loosely interpret the various + * button types, it is advised that text messages passed to these prompts do + * not refer to the button types by name. For example, it is inadvisable to + * tell the user to "Press OK to proceed." Instead, such a prompt might be + * rewritten to ask the user: "Would you like to proceed?" + */ +[scriptable, uuid(404ebfa2-d8f4-4c94-8416-e65a55f9df5a)] +interface nsIPromptService : nsISupports +{ + /** + * Puts up an alert dialog with an OK button. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + */ + void alert(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText); + + /** + * Puts up an alert dialog with an OK button and a labeled checkbox. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + * @param aCheckMsg + * Text to appear with the checkbox. + * @param aCheckState + * Contains the initial checked state of the checkbox when this method + * is called and the final checked state after this method returns. + */ + void alertCheck(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText, + in wstring aCheckMsg, + inout boolean aCheckState); + + /** + * Puts up a dialog with OK and Cancel buttons. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + * + * @return true for OK, false for Cancel + */ + boolean confirm(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText); + + /** + * Puts up a dialog with OK and Cancel buttons and a labeled checkbox. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + * @param aCheckMsg + * Text to appear with the checkbox. + * @param aCheckState + * Contains the initial checked state of the checkbox when this method + * is called and the final checked state after this method returns. + * + * @return true for OK, false for Cancel + */ + boolean confirmCheck(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText, + in wstring aCheckMsg, + inout boolean aCheckState); + + /** + * Button Flags + * + * The following flags are combined to form the aButtonFlags parameter passed + * to confirmEx. See confirmEx for more information on how the flags may be + * combined. + */ + + /** + * Button Position Flags + */ + const unsigned long BUTTON_POS_0 = 1; + const unsigned long BUTTON_POS_1 = 1 << 8; + const unsigned long BUTTON_POS_2 = 1 << 16; + + /** + * Button Title Flags (used to set the labels of buttons in the prompt) + */ + const unsigned long BUTTON_TITLE_OK = 1; + const unsigned long BUTTON_TITLE_CANCEL = 2; + const unsigned long BUTTON_TITLE_YES = 3; + const unsigned long BUTTON_TITLE_NO = 4; + const unsigned long BUTTON_TITLE_SAVE = 5; + const unsigned long BUTTON_TITLE_DONT_SAVE = 6; + const unsigned long BUTTON_TITLE_REVERT = 7; + const unsigned long BUTTON_TITLE_IS_STRING = 127; + + /** + * Button Default Flags (used to select which button is the default one) + */ + const unsigned long BUTTON_POS_0_DEFAULT = 0; + const unsigned long BUTTON_POS_1_DEFAULT = 1 << 24; + const unsigned long BUTTON_POS_2_DEFAULT = 1 << 25; + + /** + * Causes the buttons to be initially disabled. They are enabled after a + * timeout expires. The implementation may interpret this loosely as the + * intent is to ensure that the user does not click through a security dialog + * too quickly. Strictly speaking, the implementation could choose to ignore + * this flag. + */ + const unsigned long BUTTON_DELAY_ENABLE = 1 << 26; + + /** + * Selects the standard set of OK/Cancel buttons. + */ + const unsigned long STD_OK_CANCEL_BUTTONS = (BUTTON_TITLE_OK * BUTTON_POS_0) + + (BUTTON_TITLE_CANCEL * BUTTON_POS_1); + + /** + * Selects the standard set of Yes/No buttons. + */ + const unsigned long STD_YES_NO_BUTTONS = (BUTTON_TITLE_YES * BUTTON_POS_0) + + (BUTTON_TITLE_NO * BUTTON_POS_1); + + + /** + * Puts up a dialog with up to 3 buttons and an optional, labeled checkbox. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + * @param aButtonFlags + * A combination of Button Flags. + * @param aButton0Title + * Used when button 0 uses TITLE_IS_STRING + * @param aButton1Title + * Used when button 1 uses TITLE_IS_STRING + * @param aButton2Title + * Used when button 2 uses TITLE_IS_STRING + * @param aCheckMsg + * Text to appear with the checkbox. Null if no checkbox. + * @param aCheckState + * Contains the initial checked state of the checkbox when this method + * is called and the final checked state after this method returns. + * + * @return index of the button pressed. + * + * Buttons are numbered 0 - 2. The implementation can decide whether the + * sequence goes from right to left or left to right. Button 0 is the + * default button unless one of the Button Default Flags is specified. + * + * A button may use a predefined title, specified by one of the Button Title + * Flags values. Each title value can be multiplied by a position value to + * assign the title to a particular button. If BUTTON_TITLE_IS_STRING is + * used for a button, the string parameter for that button will be used. If + * the value for a button position is zero, the button will not be shown. + * + * In general, aButtonFlags is constructed per the following example: + * + * aButtonFlags = (BUTTON_POS_0) * (BUTTON_TITLE_AAA) + + * (BUTTON_POS_1) * (BUTTON_TITLE_BBB) + + * BUTTON_POS_1_DEFAULT; + * + * where "AAA" and "BBB" correspond to one of the button titles. + */ + int32_t confirmEx(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText, + in unsigned long aButtonFlags, + in wstring aButton0Title, + in wstring aButton1Title, + in wstring aButton2Title, + in wstring aCheckMsg, + inout boolean aCheckState); + + /** + * Puts up a dialog with an edit field and an optional, labeled checkbox. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + * @param aValue + * Contains the default value for the dialog field when this method + * is called (null value is ok). Upon return, if the user pressed + * OK, then this parameter contains a newly allocated string value. + * Otherwise, the parameter's value is unmodified. + * @param aCheckMsg + * Text to appear with the checkbox. If null, check box will not be shown. + * @param aCheckState + * Contains the initial checked state of the checkbox when this method + * is called and the final checked state after this method returns. + * + * @return true for OK, false for Cancel. + */ + boolean prompt(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText, + inout wstring aValue, + in wstring aCheckMsg, + inout boolean aCheckState); + + /** + * Puts up a dialog with an edit field, a password field, and an optional, + * labeled checkbox. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + * @param aUsername + * Contains the default value for the username field when this method + * is called (null value is ok). Upon return, if the user pressed OK, + * then this parameter contains a newly allocated string value. + * Otherwise, the parameter's value is unmodified. + * @param aPassword + * Contains the default value for the password field when this method + * is called (null value is ok). Upon return, if the user pressed OK, + * then this parameter contains a newly allocated string value. + * Otherwise, the parameter's value is unmodified. + * @param aCheckMsg + * Text to appear with the checkbox. If null, check box will not be shown. + * @param aCheckState + * Contains the initial checked state of the checkbox when this method + * is called and the final checked state after this method returns. + * + * @return true for OK, false for Cancel. + */ + boolean promptUsernameAndPassword(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText, + inout wstring aUsername, + inout wstring aPassword, + in wstring aCheckMsg, + inout boolean aCheckState); + + /** + * Puts up a dialog with a password field and an optional, labeled checkbox. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + * @param aPassword + * Contains the default value for the password field when this method + * is called (null value is ok). Upon return, if the user pressed OK, + * then this parameter contains a newly allocated string value. + * Otherwise, the parameter's value is unmodified. + * @param aCheckMsg + * Text to appear with the checkbox. If null, check box will not be shown. + * @param aCheckState + * Contains the initial checked state of the checkbox when this method + * is called and the final checked state after this method returns. + * + * @return true for OK, false for Cancel. + */ + boolean promptPassword(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText, + inout wstring aPassword, + in wstring aCheckMsg, + inout boolean aCheckState); + + /** + * Puts up a dialog box which has a list box of strings from which the user + * may make a single selection. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + * @param aCount + * The length of the aSelectList array parameter. + * @param aSelectList + * The list of strings to display. + * @param aOutSelection + * Contains the index of the selected item in the list when this + * method returns true. + * + * @return true for OK, false for Cancel. + */ + boolean select(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText, + in uint32_t aCount, + [array, size_is(aCount)] in wstring aSelectList, + out long aOutSelection); +}; diff --git a/embedding/components/windowwatcher/nsIPromptService2.idl b/embedding/components/windowwatcher/nsIPromptService2.idl new file mode 100644 index 000000000..4afa0975b --- /dev/null +++ b/embedding/components/windowwatcher/nsIPromptService2.idl @@ -0,0 +1,45 @@ +/* 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 "nsIPromptService.idl" + +interface nsIAuthInformation; +interface nsIAuthPromptCallback; +interface nsICancelable; +interface nsIChannel; +interface mozIDOMWindowProxy; + +/** + * This is an improved version of nsIPromptService that is less prescriptive + * about the resulting user interface. + * + * @status INCOMPLETE do not freeze before fixing bug 228207 + */ +[scriptable, uuid(3775ad32-8326-422b-9ff3-87ef1d3f9f0e)] +interface nsIPromptService2 : nsIPromptService { + // NOTE: These functions differ from their nsIAuthPrompt counterparts by + // having additional checkbox parameters + // checkValue can be null meaning to show no checkbox + // checkboxLabel is a wstring so that it can be null from both JS and C++ in + // a convenient way + // + // See nsIAuthPrompt2 for documentation on the semantics of the other + // parameters. + boolean promptAuth(in mozIDOMWindowProxy aParent, + in nsIChannel aChannel, + in uint32_t level, + in nsIAuthInformation authInfo, + in wstring checkboxLabel, + inout boolean checkValue); + + nsICancelable asyncPromptAuth(in mozIDOMWindowProxy aParent, + in nsIChannel aChannel, + in nsIAuthPromptCallback aCallback, + in nsISupports aContext, + in uint32_t level, + in nsIAuthInformation authInfo, + in wstring checkboxLabel, + inout boolean checkValue); +}; + diff --git a/embedding/components/windowwatcher/nsIWindowWatcher.idl b/embedding/components/windowwatcher/nsIWindowWatcher.idl new file mode 100644 index 000000000..deea268c2 --- /dev/null +++ b/embedding/components/windowwatcher/nsIWindowWatcher.idl @@ -0,0 +1,167 @@ +/* -*- 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 mozIDOMWindowProxy; +interface nsIObserver; +interface nsIPrompt; +interface nsIAuthPrompt; +interface nsISimpleEnumerator; +interface nsIWebBrowserChrome; +interface nsIWindowCreator; + + + +/** + * nsIWindowWatcher is the keeper of Gecko/DOM Windows. It maintains + * a list of open top-level windows, and allows some operations on them. + + * Usage notes: + + * This component has an |activeWindow| property. Clients may expect + * this property to be always current, so to properly integrate this component + * the application will need to keep it current by setting the property + * as the active window changes. + * This component should not keep a (XPCOM) reference to any windows; + * the implementation will claim no ownership. Windows must notify + * this component when they are created or destroyed, so only a weak + * reference is kept. Note that there is no interface for such notifications + * (not a public one, anyway). This is taken care of both in Mozilla and + * by common embedding code. Embedding clients need do nothing special + * about that requirement. + * This component must be initialized at application startup by calling + * setWindowCreator. + */ +[scriptable, uuid(641fe945-6902-4b3f-87c2-0daef32499b3)] +interface nsIWindowWatcher : nsISupports { + + /** Create a new window. It will automatically be added to our list + (via addWindow()). + @param aParent parent window, if any. Null if no parent. If it is + impossible to get to an nsIWebBrowserChrome from aParent, this + method will effectively act as if aParent were null. + @param aURL url to which to open the new window. Must already be + escaped, if applicable. can be null. + @param aName window name from JS window.open. can be null. If a window + with this name already exists, the openWindow call may just load + aUrl in it (if aUrl is not null) and return it. + @param aFeatures window features from JS window.open. can be null. + @param aArguments extra argument(s) to the new window, to be attached + as the |arguments| property. An nsIArray will be + unwound into multiple arguments (but not recursively!). + can be null. + @return the new window + + @note This method may examine the JS context stack for purposes of + determining the security context to use for the search for a given + window named aName. + @note This method should try to set the default charset for the new + window to the default charset of aParent. This is not guaranteed, + however. + @note This method may dispatch a "toplevel-window-ready" notification + via nsIObserverService if the window did not already exist. + */ + mozIDOMWindowProxy openWindow(in mozIDOMWindowProxy aParent, in string aUrl, + in string aName, in string aFeatures, + in nsISupports aArguments); + + /** Clients of this service can register themselves to be notified + when a window is opened or closed (added to or removed from this + service). This method adds an aObserver to the list of objects + to be notified. + @param aObserver the object to be notified when windows are + opened or closed. Its Observe method will be + called with the following parameters: + + aObserver::Observe interprets its parameters so: + aSubject the window being opened or closed, sent as an nsISupports + which can be QIed to an nsIDOMWindow. + aTopic a wstring, either "domwindowopened" or "domwindowclosed". + someData not used. + */ + void registerNotification(in nsIObserver aObserver); + + /** Clients of this service can register themselves to be notified + when a window is opened or closed (added to or removed from this + service). This method removes an aObserver from the list of objects + to be notified. + @param aObserver the observer to be removed. + */ + void unregisterNotification(in nsIObserver aObserver); + + /** Get an iterator for currently open windows in the order they were opened, + guaranteeing that each will be visited exactly once. + @return an enumerator which will itself return nsISupports objects which + can be QIed to an nsIDOMWindow + */ + + nsISimpleEnumerator getWindowEnumerator(); + + /** Return a newly created nsIPrompt implementation. + @param aParent the parent window used for posing alerts. can be null. + @return a new nsIPrompt object + */ + + nsIPrompt getNewPrompter(in mozIDOMWindowProxy aParent); + + /** Return a newly created nsIAuthPrompt implementation. + @param aParent the parent window used for posing alerts. can be null. + @return a new nsIAuthPrompt object + */ + + nsIAuthPrompt getNewAuthPrompter(in mozIDOMWindowProxy aParent); + + /** Set the window creator callback. It must be filled in by the app. + openWindow will use it to create new windows. + @param creator the callback. if null, the callback will be cleared + and window creation capabilities lost. + */ + void setWindowCreator(in nsIWindowCreator creator); + + /** Returns true if a window creator callback has been set, false otherwise. + */ + boolean hasWindowCreator(); + + + /** Retrieve the chrome window mapped to the given DOM window. Window + Watcher keeps a list of all top-level DOM windows currently open, + along with their corresponding chrome interfaces. Since DOM Windows + lack a (public) means of retrieving their corresponding chrome, + this method will do that. + @param aWindow the DOM window whose chrome window the caller needs + @return the corresponding chrome window + */ + nsIWebBrowserChrome getChromeForWindow(in mozIDOMWindowProxy aWindow); + + /** + Retrieve an existing window (or frame). + @param aTargetName the window name + @param aCurrentWindow a starting point in the window hierarchy to + begin the search. If null, each toplevel window + will be searched. + + Note: This method will search all open windows for any window or + frame with the given window name. Make sure you understand the + security implications of this before using this method! + */ + mozIDOMWindowProxy getWindowByName(in AString aTargetName, + in mozIDOMWindowProxy aCurrentWindow); + + /** The Watcher serves as a global storage facility for the current active + (frontmost non-floating-palette-type) window, storing and returning + it on demand. Users must keep this attribute current, including after + the topmost window is closed. This attribute obviously can return null + if no windows are open, but should otherwise always return a valid + window. + */ + attribute mozIDOMWindowProxy activeWindow; + +}; + +%{C++ +#define NS_WINDOWWATCHER_CONTRACTID "@mozilla.org/embedcomp/window-watcher;1" +%} diff --git a/embedding/components/windowwatcher/nsPIPromptService.idl b/embedding/components/windowwatcher/nsPIPromptService.idl new file mode 100644 index 000000000..32459b26b --- /dev/null +++ b/embedding/components/windowwatcher/nsPIPromptService.idl @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 2; 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 general dialog posing function within nsPromptService, for + private consumption, only. */ + +#include "nsISupports.idl" + +interface nsIDOMWindow; +interface nsIDialogParamBlock; + +[uuid(C60A1955-6CB3-4827-8EF8-4F5C668AF0B3)] +interface nsPIPromptService : nsISupports +{ +%{C++ + // eOpeningSound is obsolete but we need to support it for the compatibility. + // The implementers should use eSoundEventId instead. + enum {eMsg=0, eCheckboxMsg=1, eIconClass=2, eTitleMessage=3, eEditfield1Msg=4, + eEditfield2Msg=5, eEditfield1Value=6, eEditfield2Value=7, + eButton0Text=8, eButton1Text=9, eButton2Text=10, eButton3Text=11, + eDialogTitle=12, eOpeningSound=13}; + enum {eButtonPressed=0, eCheckboxState=1, eNumberButtons=2, + eNumberEditfields=3, eEditField1Password=4, eDefaultButton=5, + eDelayButtonEnable=6, eSoundEventId=7}; +%} + + void doDialog(in nsIDOMWindow aParent, in nsIDialogParamBlock aParamBlock, in string aChromeURL); +}; + diff --git a/embedding/components/windowwatcher/nsPIWindowWatcher.idl b/embedding/components/windowwatcher/nsPIWindowWatcher.idl new file mode 100644 index 000000000..47b243364 --- /dev/null +++ b/embedding/components/windowwatcher/nsPIWindowWatcher.idl @@ -0,0 +1,146 @@ +/* -*- 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/. */ + +/* Private "control" methods on the Window Watcher. These are annoying + bookkeeping methods, not part of the public (embedding) interface. +*/ + +#include "nsISupports.idl" + +interface mozIDOMWindowProxy; +interface nsIDOMWindow; +interface nsISimpleEnumerator; +interface nsIWebBrowserChrome; +interface nsIDocShellTreeItem; +interface nsIArray; +interface nsITabParent; +interface nsIDocShellLoadInfo; + +[uuid(d162f9c4-19d5-4723-931f-f1e51bfa9f68)] + +interface nsPIWindowWatcher : nsISupports +{ + /** A window has been created. Add it to our list. + @param aWindow the window to add + @param aChrome the corresponding chrome window. The DOM window + and chrome will be mapped together, and the corresponding + chrome can be retrieved using the (not private) + method getChromeForWindow. If null, any extant mapping + will be cleared. + */ + void addWindow(in mozIDOMWindowProxy aWindow, + in nsIWebBrowserChrome aChrome); + + /** A window has been closed. Remove it from our list. + @param aWindow the window to remove + */ + void removeWindow(in mozIDOMWindowProxy aWindow); + + /** Like the public interface's open(), but can handle openDialog-style + arguments and calls which shouldn't result in us navigating the window. + + @param aParent parent window, if any. Null if no parent. If it is + impossible to get to an nsIWebBrowserChrome from aParent, this + method will effectively act as if aParent were null. + @param aURL url to which to open the new window. Must already be + escaped, if applicable. can be null. + @param aName window name from JS window.open. can be null. If a window + with this name already exists, the openWindow call may just load + aUrl in it (if aUrl is not null) and return it. + @param aFeatures window features from JS window.open. can be null. + @param aCalledFromScript true if we were called from script. + @param aDialog use dialog defaults (see nsIDOMWindow::openDialog) + @param aNavigate true if we should navigate the new window to the + specified URL. + @param aArgs Window argument + @param aIsPopupSpam true if the window is a popup spam window; used for + popup blocker internals. + @param aForceNoOpener If true, force noopener behavior. This means not + looking for existing windows with the given name, + not setting an opener on the newly opened window, + and returning null from this method. + @param aLoadInfo if aNavigate is true, this allows the caller to pass in + an nsIDocShellLoadInfo to use for the navigation. + Callers can pass in null if they want the windowwatcher + to just construct a loadinfo itself. If aNavigate is + false, this argument is ignored. + + @return the new window + + @note This method may examine the JS context stack for purposes of + determining the security context to use for the search for a given + window named aName. + @note This method should try to set the default charset for the new + window to the default charset of the document in the calling window + (which is determined based on the JS stack and the value of + aParent). This is not guaranteed, however. + */ + mozIDOMWindowProxy openWindow2(in mozIDOMWindowProxy aParent, in string aUrl, + in string aName, in string aFeatures, + in boolean aCalledFromScript, + in boolean aDialog, + in boolean aNavigate, + in nsISupports aArgs, + in boolean aIsPopupSpam, + in boolean aForceNoOpener, + in nsIDocShellLoadInfo aLoadInfo); + + /** + * Opens a new window using the most recent non-private browser + * window as its parent. + * + * @return the nsITabParent of the initial browser for the newly opened + * window. + */ + nsITabParent openWindowWithoutParent(); + + /** + * Opens a new window so that the window that aOpeningTab belongs to + * is set as the parent window. The newly opened window will also + * inherit load context information from aOpeningTab. + * + * @param aOpeningTab + * The nsITabParent that is requesting the new window be opened. + * @param aFeatures + * Window features if called with window.open or similar. + * @param aCalledFromJS + * True if called via window.open or similar. + * @param aOpenerFullZoom + * The current zoom multiplier for the opener tab. This is then + * applied to the newly opened window. + * + * @return the nsITabParent of the initial browser for the newly opened + * window. + */ + nsITabParent openWindowWithTabParent(in nsITabParent aOpeningTab, + in ACString aFeatures, + in boolean aCalledFromJS, + in float aOpenerFullZoom); + + /** + * Find a named docshell tree item amongst all windows registered + * with the window watcher. This may be a subframe in some window, + * for example. + * + * @param aName the name of the window. Must not be null. + * @param aRequestor the tree item immediately making the request. + * We should make sure to not recurse down into its findItemWithName + * method. + * @param aOriginalRequestor the original treeitem that made the request. + * Used for security checks. + * @return the tree item with aName as the name, or null if there + * isn't one. "Special" names, like _self, _top, etc, will be + * treated specially only if aRequestor is null; in that case they + * will be resolved relative to the first window the windowwatcher + * knows about. + * @see findItemWithName methods on nsIDocShellTreeItem and + * nsIDocShellTreeOwner + */ + nsIDocShellTreeItem findItemWithName(in AString aName, + in nsIDocShellTreeItem aRequestor, + in nsIDocShellTreeItem aOriginalRequestor); +}; + diff --git a/embedding/components/windowwatcher/nsPromptUtils.h b/embedding/components/windowwatcher/nsPromptUtils.h new file mode 100644 index 000000000..bd1267bef --- /dev/null +++ b/embedding/components/windowwatcher/nsPromptUtils.h @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSPROMPTUTILS_H_ +#define NSPROMPTUTILS_H_ + +#include "nsIHttpChannel.h" + +/** + * @file + * This file defines some helper functions that simplify interaction + * with authentication prompts. + */ + +/** + * Given a username (possibly in DOMAIN\user form) and password, parses the + * domain out of the username if necessary and sets domain, username and + * password on the auth information object. + */ +inline void +NS_SetAuthInfo(nsIAuthInformation* aAuthInfo, const nsString& aUser, + const nsString& aPassword) +{ + uint32_t flags; + aAuthInfo->GetFlags(&flags); + if (flags & nsIAuthInformation::NEED_DOMAIN) { + // Domain is separated from username by a backslash + int32_t idx = aUser.FindChar(char16_t('\\')); + if (idx == kNotFound) { + aAuthInfo->SetUsername(aUser); + } else { + aAuthInfo->SetDomain(Substring(aUser, 0, idx)); + aAuthInfo->SetUsername(Substring(aUser, idx + 1)); + } + } else { + aAuthInfo->SetUsername(aUser); + } + aAuthInfo->SetPassword(aPassword); +} + +/** + * Gets the host and port from a channel and authentication info. This is the + * "logical" host and port for this authentication, i.e. for a proxy + * authentication it refers to the proxy, while for a host authentication it + * is the actual host. + * + * @param machineProcessing + * When this parameter is true, the host will be returned in ASCII + * (instead of UTF-8; this is relevant when IDN is used). In addition, + * the port will be returned as the real port even when it was not + * explicitly specified (when false, the port will be returned as -1 in + * this case) + */ +inline void +NS_GetAuthHostPort(nsIChannel* aChannel, nsIAuthInformation* aAuthInfo, + bool aMachineProcessing, nsCString& aHost, int32_t* aPort) +{ + nsCOMPtr<nsIURI> uri; + nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); + if (NS_FAILED(rv)) { + return; + } + + // Have to distinguish proxy auth and host auth here... + uint32_t flags; + aAuthInfo->GetFlags(&flags); + if (flags & nsIAuthInformation::AUTH_PROXY) { + nsCOMPtr<nsIProxiedChannel> proxied(do_QueryInterface(aChannel)); + NS_ASSERTION(proxied, "proxy auth needs nsIProxiedChannel"); + + nsCOMPtr<nsIProxyInfo> info; + proxied->GetProxyInfo(getter_AddRefs(info)); + NS_ASSERTION(info, "proxy auth needs nsIProxyInfo"); + + nsAutoCString idnhost; + info->GetHost(idnhost); + info->GetPort(aPort); + + if (aMachineProcessing) { + nsCOMPtr<nsIIDNService> idnService = + do_GetService(NS_IDNSERVICE_CONTRACTID); + if (idnService) { + idnService->ConvertUTF8toACE(idnhost, aHost); + } else { + // Not much we can do here... + aHost = idnhost; + } + } else { + aHost = idnhost; + } + } else { + if (aMachineProcessing) { + uri->GetAsciiHost(aHost); + *aPort = NS_GetRealPort(uri); + } else { + uri->GetHost(aHost); + uri->GetPort(aPort); + } + } +} + +/** + * Creates the key for looking up passwords in the password manager. This + * function uses the same format that Gecko functions have always used, thus + * ensuring backwards compatibility. + */ +inline void +NS_GetAuthKey(nsIChannel* aChannel, nsIAuthInformation* aAuthInfo, + nsCString& aKey) +{ + // HTTP does this differently from other protocols + nsCOMPtr<nsIHttpChannel> http(do_QueryInterface(aChannel)); + if (!http) { + nsCOMPtr<nsIURI> uri; + aChannel->GetURI(getter_AddRefs(uri)); + uri->GetPrePath(aKey); + return; + } + + // NOTE: For backwards-compatibility reasons, this must be the ASCII host. + nsCString host; + int32_t port = -1; + + NS_GetAuthHostPort(aChannel, aAuthInfo, true, host, &port); + + nsAutoString realm; + aAuthInfo->GetRealm(realm); + + // Now assemble the key: host:port (realm) + aKey.Append(host); + aKey.Append(':'); + aKey.AppendInt(port); + aKey.AppendLiteral(" ("); + AppendUTF16toUTF8(realm, aKey); + aKey.Append(')'); +} + +#endif diff --git a/embedding/components/windowwatcher/nsWindowWatcher.cpp b/embedding/components/windowwatcher/nsWindowWatcher.cpp new file mode 100644 index 000000000..07872410e --- /dev/null +++ b/embedding/components/windowwatcher/nsWindowWatcher.cpp @@ -0,0 +1,2610 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//#define USEWEAKREFS // (haven't quite figured that out yet) + +#include "nsWindowWatcher.h" +#include "nsAutoWindowStateHelper.h" + +#include "nsCRT.h" +#include "nsNetUtil.h" +#include "nsIAuthPrompt.h" +#include "nsIAuthPrompt2.h" +#include "nsISimpleEnumerator.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsJSUtils.h" +#include "plstr.h" + +#include "nsDocShell.h" +#include "nsGlobalWindow.h" +#include "nsIBaseWindow.h" +#include "nsIBrowserDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIDocShellLoadInfo.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIDocumentLoader.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIDOMWindow.h" +#include "nsIDOMChromeWindow.h" +#include "nsIDOMModalContentWindow.h" +#include "nsIPrompt.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScreen.h" +#include "nsIScreenManager.h" +#include "nsIScriptContext.h" +#include "nsIObserverService.h" +#include "nsIScriptSecurityManager.h" +#include "nsXPCOM.h" +#include "nsIURI.h" +#include "nsIWebBrowser.h" +#include "nsIWebBrowserChrome.h" +#include "nsIWebNavigation.h" +#include "nsIWindowCreator.h" +#include "nsIWindowCreator2.h" +#include "nsIXPConnect.h" +#include "nsIXULRuntime.h" +#include "nsPIDOMWindow.h" +#include "nsIContentViewer.h" +#include "nsIWindowProvider.h" +#include "nsIMutableArray.h" +#include "nsIDOMStorageManager.h" +#include "nsIWidget.h" +#include "nsFocusManager.h" +#include "nsIPresShell.h" +#include "nsPresContext.h" +#include "nsContentUtils.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsSandboxFlags.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/DOMStorage.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/TabParent.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/TabGroup.h" +#include "nsIXULWindow.h" +#include "nsIXULBrowserWindow.h" +#include "nsGlobalWindow.h" + +#ifdef USEWEAKREFS +#include "nsIWeakReference.h" +#endif + +using namespace mozilla; +using namespace mozilla::dom; + +/**************************************************************** + ******************** nsWatcherWindowEntry ********************** + ****************************************************************/ + +class nsWindowWatcher; + +struct nsWatcherWindowEntry +{ + + nsWatcherWindowEntry(mozIDOMWindowProxy* aWindow, nsIWebBrowserChrome* aChrome) + : mChrome(nullptr) + { +#ifdef USEWEAKREFS + mWindow = do_GetWeakReference(aWindow); +#else + mWindow = aWindow; +#endif + nsCOMPtr<nsISupportsWeakReference> supportsweak(do_QueryInterface(aChrome)); + if (supportsweak) { + supportsweak->GetWeakReference(getter_AddRefs(mChromeWeak)); + } else { + mChrome = aChrome; + mChromeWeak = nullptr; + } + ReferenceSelf(); + } + ~nsWatcherWindowEntry() {} + + void InsertAfter(nsWatcherWindowEntry* aOlder); + void Unlink(); + void ReferenceSelf(); + +#ifdef USEWEAKREFS + nsCOMPtr<nsIWeakReference> mWindow; +#else // still not an owning ref + mozIDOMWindowProxy* mWindow; +#endif + nsIWebBrowserChrome* mChrome; + nsWeakPtr mChromeWeak; + // each struct is in a circular, doubly-linked list + nsWatcherWindowEntry* mYounger; // next younger in sequence + nsWatcherWindowEntry* mOlder; +}; + +void +nsWatcherWindowEntry::InsertAfter(nsWatcherWindowEntry* aOlder) +{ + if (aOlder) { + mOlder = aOlder; + mYounger = aOlder->mYounger; + mOlder->mYounger = this; + if (mOlder->mOlder == mOlder) { + mOlder->mOlder = this; + } + mYounger->mOlder = this; + if (mYounger->mYounger == mYounger) { + mYounger->mYounger = this; + } + } +} + +void +nsWatcherWindowEntry::Unlink() +{ + mOlder->mYounger = mYounger; + mYounger->mOlder = mOlder; + ReferenceSelf(); +} + +void +nsWatcherWindowEntry::ReferenceSelf() +{ + + mYounger = this; + mOlder = this; +} + +/**************************************************************** + ****************** nsWatcherWindowEnumerator ******************* + ****************************************************************/ + +class nsWatcherWindowEnumerator : public nsISimpleEnumerator +{ + +public: + explicit nsWatcherWindowEnumerator(nsWindowWatcher* aWatcher); + NS_IMETHOD HasMoreElements(bool* aResult) override; + NS_IMETHOD GetNext(nsISupports** aResult) override; + + NS_DECL_ISUPPORTS + +protected: + virtual ~nsWatcherWindowEnumerator(); + +private: + friend class nsWindowWatcher; + + nsWatcherWindowEntry* FindNext(); + void WindowRemoved(nsWatcherWindowEntry* aInfo); + + nsWindowWatcher* mWindowWatcher; + nsWatcherWindowEntry* mCurrentPosition; +}; + +NS_IMPL_ADDREF(nsWatcherWindowEnumerator) +NS_IMPL_RELEASE(nsWatcherWindowEnumerator) +NS_IMPL_QUERY_INTERFACE(nsWatcherWindowEnumerator, nsISimpleEnumerator) + +nsWatcherWindowEnumerator::nsWatcherWindowEnumerator(nsWindowWatcher* aWatcher) + : mWindowWatcher(aWatcher) + , mCurrentPosition(aWatcher->mOldestWindow) +{ + mWindowWatcher->AddEnumerator(this); + mWindowWatcher->AddRef(); +} + +nsWatcherWindowEnumerator::~nsWatcherWindowEnumerator() +{ + mWindowWatcher->RemoveEnumerator(this); + mWindowWatcher->Release(); +} + +NS_IMETHODIMP +nsWatcherWindowEnumerator::HasMoreElements(bool* aResult) +{ + if (!aResult) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = !!mCurrentPosition; + return NS_OK; +} + +NS_IMETHODIMP +nsWatcherWindowEnumerator::GetNext(nsISupports** aResult) +{ + if (!aResult) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = nullptr; + +#ifdef USEWEAKREFS + while (mCurrentPosition) { + CallQueryReferent(mCurrentPosition->mWindow, aResult); + if (*aResult) { + mCurrentPosition = FindNext(); + break; + } else { // window is gone! + mWindowWatcher->RemoveWindow(mCurrentPosition); + } + } + NS_IF_ADDREF(*aResult); +#else + if (mCurrentPosition) { + CallQueryInterface(mCurrentPosition->mWindow, aResult); + mCurrentPosition = FindNext(); + } +#endif + return NS_OK; +} + +nsWatcherWindowEntry* +nsWatcherWindowEnumerator::FindNext() +{ + nsWatcherWindowEntry* info; + + if (!mCurrentPosition) { + return 0; + } + + info = mCurrentPosition->mYounger; + return info == mWindowWatcher->mOldestWindow ? 0 : info; +} + +// if a window is being removed adjust the iterator's current position +void +nsWatcherWindowEnumerator::WindowRemoved(nsWatcherWindowEntry* aInfo) +{ + + if (mCurrentPosition == aInfo) { + mCurrentPosition = + mCurrentPosition != aInfo->mYounger ? aInfo->mYounger : 0; + } +} + +/**************************************************************** + *********************** nsWindowWatcher ************************ + ****************************************************************/ + +NS_IMPL_ADDREF(nsWindowWatcher) +NS_IMPL_RELEASE(nsWindowWatcher) +NS_IMPL_QUERY_INTERFACE(nsWindowWatcher, + nsIWindowWatcher, + nsIPromptFactory, + nsPIWindowWatcher) + +nsWindowWatcher::nsWindowWatcher() + : mEnumeratorList() + , mOldestWindow(0) + , mListLock("nsWindowWatcher.mListLock") +{ +} + +nsWindowWatcher::~nsWindowWatcher() +{ + // delete data + while (mOldestWindow) { + RemoveWindow(mOldestWindow); + } +} + +nsresult +nsWindowWatcher::Init() +{ + return NS_OK; +} + +/** + * Convert aArguments into either an nsIArray or nullptr. + * + * - If aArguments is nullptr, return nullptr. + * - If aArguments is an nsArray, return nullptr if it's empty, or otherwise + * return the array. + * - If aArguments is an nsIArray, return nullptr if it's empty, or + * otherwise just return the array. + * - Otherwise, return an nsIArray with one element: aArguments. + */ +static already_AddRefed<nsIArray> +ConvertArgsToArray(nsISupports* aArguments) +{ + if (!aArguments) { + return nullptr; + } + + nsCOMPtr<nsIArray> array = do_QueryInterface(aArguments); + if (array) { + uint32_t argc = 0; + array->GetLength(&argc); + if (argc == 0) { + return nullptr; + } + + return array.forget(); + } + + nsCOMPtr<nsIMutableArray> singletonArray = + do_CreateInstance(NS_ARRAY_CONTRACTID); + NS_ENSURE_TRUE(singletonArray, nullptr); + + nsresult rv = singletonArray->AppendElement(aArguments, /* aWeak = */ false); + NS_ENSURE_SUCCESS(rv, nullptr); + + return singletonArray.forget(); +} + +NS_IMETHODIMP +nsWindowWatcher::OpenWindow(mozIDOMWindowProxy* aParent, + const char* aUrl, + const char* aName, + const char* aFeatures, + nsISupports* aArguments, + mozIDOMWindowProxy** aResult) +{ + nsCOMPtr<nsIArray> argv = ConvertArgsToArray(aArguments); + + uint32_t argc = 0; + if (argv) { + argv->GetLength(&argc); + } + bool dialog = (argc != 0); + + return OpenWindowInternal(aParent, aUrl, aName, aFeatures, + /* calledFromJS = */ false, dialog, + /* navigate = */ true, argv, + /* aIsPopupSpam = */ false, + /* aForceNoOpener = */ false, + /* aLoadInfo = */ nullptr, + aResult); +} + +struct SizeSpec +{ + SizeSpec() + : mLeft(0) + , mTop(0) + , mOuterWidth(0) + , mOuterHeight(0) + , mInnerWidth(0) + , mInnerHeight(0) + , mLeftSpecified(false) + , mTopSpecified(false) + , mOuterWidthSpecified(false) + , mOuterHeightSpecified(false) + , mInnerWidthSpecified(false) + , mInnerHeightSpecified(false) + , mUseDefaultWidth(false) + , mUseDefaultHeight(false) + { + } + + int32_t mLeft; + int32_t mTop; + int32_t mOuterWidth; // Total window width + int32_t mOuterHeight; // Total window height + int32_t mInnerWidth; // Content area width + int32_t mInnerHeight; // Content area height + + bool mLeftSpecified; + bool mTopSpecified; + bool mOuterWidthSpecified; + bool mOuterHeightSpecified; + bool mInnerWidthSpecified; + bool mInnerHeightSpecified; + + // If these booleans are true, don't look at the corresponding width values + // even if they're specified -- they'll be bogus + bool mUseDefaultWidth; + bool mUseDefaultHeight; + + bool PositionSpecified() const + { + return mLeftSpecified || mTopSpecified; + } + + bool SizeSpecified() const + { + return mOuterWidthSpecified || mOuterHeightSpecified || + mInnerWidthSpecified || mInnerHeightSpecified; + } +}; + +NS_IMETHODIMP +nsWindowWatcher::OpenWindow2(mozIDOMWindowProxy* aParent, + const char* aUrl, + const char* aName, + const char* aFeatures, + bool aCalledFromScript, + bool aDialog, + bool aNavigate, + nsISupports* aArguments, + bool aIsPopupSpam, + bool aForceNoOpener, + nsIDocShellLoadInfo* aLoadInfo, + mozIDOMWindowProxy** aResult) +{ + nsCOMPtr<nsIArray> argv = ConvertArgsToArray(aArguments); + + uint32_t argc = 0; + if (argv) { + argv->GetLength(&argc); + } + + // This is extremely messed up, but this behavior is necessary because + // callers lie about whether they're a dialog window and whether they're + // called from script. Fixing this is bug 779939. + bool dialog = aDialog; + if (!aCalledFromScript) { + dialog = argc > 0; + } + + return OpenWindowInternal(aParent, aUrl, aName, aFeatures, + aCalledFromScript, dialog, + aNavigate, argv, aIsPopupSpam, + aForceNoOpener, aLoadInfo, aResult); +} + +// This static function checks if the aDocShell uses an UserContextId equal to +// the userContextId of subjectPrincipal, if not null. +static bool +CheckUserContextCompatibility(nsIDocShell* aDocShell) +{ + MOZ_ASSERT(aDocShell); + + uint32_t userContextId = + static_cast<nsDocShell*>(aDocShell)->GetOriginAttributes().mUserContextId; + + nsCOMPtr<nsIPrincipal> subjectPrincipal = + nsContentUtils::GetCurrentJSContext() + ? nsContentUtils::SubjectPrincipal() : nullptr; + + // If we don't have a valid principal, probably we are in e10s mode, parent + // side. + if (!subjectPrincipal) { + return true; + } + + // DocShell can have UsercontextID set but loading a document with system + // principal. In this case, we consider everything ok. + if (nsContentUtils::IsSystemPrincipal(subjectPrincipal)) { + return true; + } + + return subjectPrincipal->GetUserContextId() == userContextId; +} + +NS_IMETHODIMP +nsWindowWatcher::OpenWindowWithoutParent(nsITabParent** aResult) +{ + return OpenWindowWithTabParent(nullptr, EmptyCString(), true, 1.0f, aResult); +} + +nsresult +nsWindowWatcher::CreateChromeWindow(const nsACString& aFeatures, + nsIWebBrowserChrome* aParentChrome, + uint32_t aChromeFlags, + uint32_t aContextFlags, + nsITabParent* aOpeningTabParent, + mozIDOMWindowProxy* aOpener, + nsIWebBrowserChrome** aResult) +{ + nsCOMPtr<nsIWindowCreator2> windowCreator2(do_QueryInterface(mWindowCreator)); + if (NS_WARN_IF(!windowCreator2)) { + return NS_ERROR_UNEXPECTED; + } + + // B2G multi-screen support. mozDisplayId is returned from the + // "display-changed" event, it is also platform-dependent. +#ifdef MOZ_WIDGET_GONK + int retval = WinHasOption(aFeatures, "mozDisplayId", 0, nullptr); + windowCreator2->SetScreenId(retval); +#endif + + bool cancel = false; + nsCOMPtr<nsIWebBrowserChrome> newWindowChrome; + nsresult rv = + windowCreator2->CreateChromeWindow2(aParentChrome, aChromeFlags, aContextFlags, + aOpeningTabParent, aOpener, &cancel, + getter_AddRefs(newWindowChrome)); + + if (NS_SUCCEEDED(rv) && cancel) { + newWindowChrome = nullptr; + return NS_ERROR_ABORT; + } + + newWindowChrome.forget(aResult); + return NS_OK; +} + +/** + * Disable persistence of size/position in popups (determined by + * determining whether the features parameter specifies width or height + * in any way). We consider any overriding of the window's size or position + * in the open call as disabling persistence of those attributes. + * Popup windows (which should not persist size or position) generally set + * the size. + * + * @param aFeatures + * The features string that was used to open the window. + * @param aTreeOwner + * The nsIDocShellTreeOwner of the newly opened window. If null, + * this function is a no-op. + */ +void +nsWindowWatcher::MaybeDisablePersistence(const nsACString& aFeatures, + nsIDocShellTreeOwner* aTreeOwner) +{ + if (!aTreeOwner) { + return; + } + + // At the moment, the strings "height=" or "width=" never happen + // outside a size specification, so we can do this the Q&D way. + if (PL_strcasestr(aFeatures.BeginReading(), "width=") || + PL_strcasestr(aFeatures.BeginReading(), "height=")) { + aTreeOwner->SetPersistence(false, false, false); + } +} + +NS_IMETHODIMP +nsWindowWatcher::OpenWindowWithTabParent(nsITabParent* aOpeningTabParent, + const nsACString& aFeatures, + bool aCalledFromJS, + float aOpenerFullZoom, + nsITabParent** aResult) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(mWindowCreator); + + if (!nsContentUtils::IsSafeToRunScript()) { + nsContentUtils::WarnScriptWasIgnored(nullptr); + return NS_ERROR_FAILURE; + } + + if (NS_WARN_IF(!mWindowCreator)) { + return NS_ERROR_UNEXPECTED; + } + + bool isPrivateBrowsingWindow = + Preferences::GetBool("browser.privatebrowsing.autostart"); + + nsCOMPtr<nsPIDOMWindowOuter> parentWindowOuter; + if (aOpeningTabParent) { + // We need to examine the window that aOpeningTabParent belongs to in + // order to inform us of what kind of window we're going to open. + TabParent* openingTab = TabParent::GetFrom(aOpeningTabParent); + parentWindowOuter = openingTab->GetParentWindowOuter(); + + // Propagate the privacy status of the parent window, if + // available, to the child. + if (!isPrivateBrowsingWindow) { + nsCOMPtr<nsILoadContext> parentContext = openingTab->GetLoadContext(); + if (parentContext) { + isPrivateBrowsingWindow = parentContext->UsePrivateBrowsing(); + } + } + } + + if (!parentWindowOuter) { + // We couldn't find a browser window for the opener, so either we + // never were passed aOpeningTabParent, the window is closed, + // or it's in the process of closing. Either way, we'll use + // the most recently opened browser window instead. + parentWindowOuter = nsContentUtils::GetMostRecentNonPBWindow(); + } + + if (NS_WARN_IF(!parentWindowOuter)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner; + GetWindowTreeOwner(parentWindowOuter, getter_AddRefs(parentTreeOwner)); + if (NS_WARN_IF(!parentTreeOwner)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIWindowCreator2> windowCreator2(do_QueryInterface(mWindowCreator)); + if (NS_WARN_IF(!windowCreator2)) { + return NS_ERROR_UNEXPECTED; + } + + uint32_t contextFlags = 0; + if (parentWindowOuter->IsLoadingOrRunningTimeout()) { + contextFlags |= + nsIWindowCreator2::PARENT_IS_LOADING_OR_RUNNING_TIMEOUT; + } + + uint32_t chromeFlags = CalculateChromeFlagsForChild(aFeatures); + + // A content process has asked for a new window, which implies + // that the new window will need to be remote. + chromeFlags |= nsIWebBrowserChrome::CHROME_REMOTE_WINDOW; + + nsCOMPtr<nsIWebBrowserChrome> parentChrome(do_GetInterface(parentTreeOwner)); + nsCOMPtr<nsIWebBrowserChrome> newWindowChrome; + + CreateChromeWindow(aFeatures, parentChrome, chromeFlags, contextFlags, + aOpeningTabParent, nullptr, getter_AddRefs(newWindowChrome)); + + if (NS_WARN_IF(!newWindowChrome)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIDocShellTreeItem> chromeTreeItem = do_GetInterface(newWindowChrome); + if (NS_WARN_IF(!chromeTreeItem)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIDocShellTreeOwner> chromeTreeOwner; + chromeTreeItem->GetTreeOwner(getter_AddRefs(chromeTreeOwner)); + if (NS_WARN_IF(!chromeTreeOwner)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsILoadContext> chromeContext = do_QueryInterface(chromeTreeItem); + if (NS_WARN_IF(!chromeContext)) { + return NS_ERROR_UNEXPECTED; + } + + chromeContext->SetPrivateBrowsing(isPrivateBrowsingWindow); + + // Tabs opened from a content process can only open new windows + // that will also run with out-of-process tabs. + chromeContext->SetRemoteTabs(true); + + MaybeDisablePersistence(aFeatures, chromeTreeOwner); + + SizeSpec sizeSpec; + CalcSizeSpec(aFeatures, sizeSpec); + SizeOpenedWindow(chromeTreeOwner, parentWindowOuter, false, sizeSpec, + Some(aOpenerFullZoom)); + + nsCOMPtr<nsITabParent> newTabParent; + chromeTreeOwner->GetPrimaryTabParent(getter_AddRefs(newTabParent)); + if (NS_WARN_IF(!newTabParent)) { + return NS_ERROR_UNEXPECTED; + } + + newTabParent.forget(aResult); + return NS_OK; +} + +nsresult +nsWindowWatcher::OpenWindowInternal(mozIDOMWindowProxy* aParent, + const char* aUrl, + const char* aName, + const char* aFeatures, + bool aCalledFromJS, + bool aDialog, + bool aNavigate, + nsIArray* aArgv, + bool aIsPopupSpam, + bool aForceNoOpener, + nsIDocShellLoadInfo* aLoadInfo, + mozIDOMWindowProxy** aResult) +{ + nsresult rv = NS_OK; + bool isNewToplevelWindow = false; + bool windowIsNew = false; + bool windowNeedsName = false; + bool windowIsModal = false; + bool uriToLoadIsChrome = false; + bool windowIsModalContentDialog = false; + + uint32_t chromeFlags; + nsAutoString name; // string version of aName + nsAutoCString features; // string version of aFeatures + nsCOMPtr<nsIURI> uriToLoad; // from aUrl, if any + nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner; // from the parent window, if any + nsCOMPtr<nsIDocShellTreeItem> newDocShellItem; // from the new window + + nsCOMPtr<nsPIDOMWindowOuter> parent = + aParent ? nsPIDOMWindowOuter::From(aParent) : nullptr; + + NS_ENSURE_ARG_POINTER(aResult); + *aResult = 0; + + if (!nsContentUtils::IsSafeToRunScript()) { + nsContentUtils::WarnScriptWasIgnored(nullptr); + return NS_ERROR_FAILURE; + } + + GetWindowTreeOwner(parent, getter_AddRefs(parentTreeOwner)); + + // We expect TabParent to have provided us the absolute URI of the window + // we're to open, so there's no need to call URIfromURL (or more importantly, + // to check for a chrome URI, which cannot be opened from a remote tab). + if (aUrl) { + rv = URIfromURL(aUrl, aParent, getter_AddRefs(uriToLoad)); + if (NS_FAILED(rv)) { + return rv; + } + uriToLoad->SchemeIs("chrome", &uriToLoadIsChrome); + } + + bool nameSpecified = false; + if (aName) { + CopyUTF8toUTF16(aName, name); + nameSpecified = true; + } else { + name.SetIsVoid(true); + } + + if (aFeatures) { + features.Assign(aFeatures); + features.StripWhitespace(); + } else { + features.SetIsVoid(true); + } + + // try to find an extant window with the given name + nsCOMPtr<nsPIDOMWindowOuter> foundWindow = + SafeGetWindowByName(name, aForceNoOpener, aParent); + GetWindowTreeItem(foundWindow, getter_AddRefs(newDocShellItem)); + + // Do sandbox checks here, instead of waiting until nsIDocShell::LoadURI. + // The state of the window can change before this call and if we are blocked + // because of sandboxing, we wouldn't want that to happen. + nsCOMPtr<nsPIDOMWindowOuter> parentWindow = + aParent ? nsPIDOMWindowOuter::From(aParent) : nullptr; + nsCOMPtr<nsIDocShell> parentDocShell; + if (parentWindow) { + parentDocShell = parentWindow->GetDocShell(); + if (parentDocShell) { + nsCOMPtr<nsIDocShell> foundDocShell = do_QueryInterface(newDocShellItem); + if (parentDocShell->IsSandboxedFrom(foundDocShell)) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } + } + } + + // no extant window? make a new one. + + // If no parent, consider it chrome when running in the parent process. + bool hasChromeParent = XRE_IsContentProcess() ? false : true; + if (aParent) { + // Check if the parent document has chrome privileges. + nsIDocument* doc = parentWindow->GetDoc(); + hasChromeParent = doc && nsContentUtils::IsChromeDoc(doc); + } + + bool isCallerChrome = nsContentUtils::LegacyIsCallerChromeOrNativeCode(); + + // Make sure we calculate the chromeFlags *before* we push the + // callee context onto the context stack so that + // the calculation sees the actual caller when doing its + // security checks. + if (isCallerChrome && XRE_IsParentProcess()) { + chromeFlags = CalculateChromeFlagsForParent(aParent, features, + aDialog, uriToLoadIsChrome, + hasChromeParent, aCalledFromJS); + } else { + chromeFlags = CalculateChromeFlagsForChild(features); + + // Until ShowModalDialog is removed, it's still possible for content to + // request dialogs, but only in single-process mode. + if (aDialog) { + MOZ_ASSERT(XRE_IsParentProcess()); + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG; + } + } + + // If we're not called through our JS version of the API, and we got + // our internal modal option, treat the window we're opening as a + // modal content window (and set the modal chrome flag). + if (!aCalledFromJS && aArgv && + WinHasOption(features, "-moz-internal-modal", 0, nullptr)) { + windowIsModalContentDialog = true; + + // CHROME_MODAL gets inherited by dependent windows, which affects various + // platform-specific window state (especially on OSX). So we need some way + // to determine that this window was actually opened by nsGlobalWindow:: + // ShowModalDialog(), and that somebody is actually going to be watching + // for return values and all that. + chromeFlags |= nsIWebBrowserChrome::CHROME_MODAL_CONTENT_WINDOW; + chromeFlags |= nsIWebBrowserChrome::CHROME_MODAL; + } + + SizeSpec sizeSpec; + CalcSizeSpec(features, sizeSpec); + + nsCOMPtr<nsIScriptSecurityManager> sm( + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID)); + + + // XXXbz Why is an AutoJSAPI good enough here? Wouldn't AutoEntryScript (so + // we affect the entry global) make more sense? Or do we just want to affect + // GetSubjectPrincipal()? + dom::AutoJSAPI jsapiChromeGuard; + + bool windowTypeIsChrome = + chromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_CHROME; + if (isCallerChrome && !hasChromeParent && !windowTypeIsChrome) { + // open() is called from chrome on a non-chrome window, initialize an + // AutoJSAPI with the callee to prevent the caller's privileges from leaking + // into code that runs while opening the new window. + // + // The reasoning for this is in bug 289204. Basically, chrome sometimes does + // someContentWindow.open(untrustedURL), and wants to be insulated from nasty + // javascript: URLs and such. But there are also cases where we create a + // window parented to a content window (such as a download dialog), usually + // directly with nsIWindowWatcher. In those cases, we want the principal of + // the initial about:blank document to be system, so that the subsequent XUL + // load can reuse the inner window and avoid blowing away expandos. As such, + // we decide whether to load with the principal of the caller or of the parent + // based on whether the docshell type is chrome or content. + + nsCOMPtr<nsIGlobalObject> parentGlobalObject = do_QueryInterface(aParent); + if (!aParent) { + jsapiChromeGuard.Init(); + } else if (NS_WARN_IF(!jsapiChromeGuard.Init(parentGlobalObject))) { + return NS_ERROR_UNEXPECTED; + } + } + + uint32_t activeDocsSandboxFlags = 0; + if (!newDocShellItem) { + // We're going to either open up a new window ourselves or ask a + // nsIWindowProvider for one. In either case, we'll want to set the right + // name on it. + windowNeedsName = true; + + // If the parent trying to open a new window is sandboxed + // without 'allow-popups', this is not allowed and we fail here. + if (aParent) { + if (nsIDocument* doc = parentWindow->GetDoc()) { + // Save sandbox flags for copying to new browsing context (docShell). + activeDocsSandboxFlags = doc->GetSandboxFlags(); + if (activeDocsSandboxFlags & SANDBOXED_AUXILIARY_NAVIGATION) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } + } + } + + // Now check whether it's ok to ask a window provider for a window. Don't + // do it if we're opening a dialog or if our parent is a chrome window or + // if we're opening something that has modal, dialog, or chrome flags set. + nsCOMPtr<nsIDOMChromeWindow> chromeWin = do_QueryInterface(aParent); + if (!aDialog && !chromeWin && + !(chromeFlags & (nsIWebBrowserChrome::CHROME_MODAL | + nsIWebBrowserChrome::CHROME_OPENAS_DIALOG | + nsIWebBrowserChrome::CHROME_OPENAS_CHROME))) { + nsCOMPtr<nsIWindowProvider> provider; + if (parentTreeOwner) { + provider = do_GetInterface(parentTreeOwner); + } else if (XRE_IsContentProcess()) { + // we're in a content process but we don't have a tabchild we can + // use. + provider = nsContentUtils::GetWindowProviderForContentProcess(); + } + + if (provider) { + nsCOMPtr<mozIDOMWindowProxy> newWindow; + rv = provider->ProvideWindow(aParent, chromeFlags, aCalledFromJS, + sizeSpec.PositionSpecified(), + sizeSpec.SizeSpecified(), + uriToLoad, name, features, aForceNoOpener, + &windowIsNew, getter_AddRefs(newWindow)); + + if (NS_SUCCEEDED(rv)) { + GetWindowTreeItem(newWindow, getter_AddRefs(newDocShellItem)); + if (windowIsNew && newDocShellItem) { + // Make sure to stop any loads happening in this window that the + // window provider might have started. Otherwise if our caller + // manipulates the window it just opened and then the load + // completes their stuff will get blown away. + nsCOMPtr<nsIWebNavigation> webNav = + do_QueryInterface(newDocShellItem); + webNav->Stop(nsIWebNavigation::STOP_NETWORK); + } + + // If this is a new window, but it's incompatible with the current + // userContextId, we ignore it and we pretend that nothing has been + // returned by ProvideWindow. + if (!windowIsNew && newDocShellItem) { + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(newDocShellItem); + if (!CheckUserContextCompatibility(docShell)) { + newWindow = nullptr; + newDocShellItem = nullptr; + windowIsNew = false; + } + } + + } else if (rv == NS_ERROR_ABORT) { + // NS_ERROR_ABORT means the window provider has flat-out rejected + // the open-window call and we should bail. Don't return an error + // here, because our caller may propagate that error, which might + // cause e.g. window.open to throw! Just return null for our out + // param. + return NS_OK; + } + } + } + } + + bool newWindowShouldBeModal = false; + bool parentIsModal = false; + if (!newDocShellItem) { + windowIsNew = true; + isNewToplevelWindow = true; + + nsCOMPtr<nsIWebBrowserChrome> parentChrome(do_GetInterface(parentTreeOwner)); + + // is the parent (if any) modal? if so, we must be, too. + bool weAreModal = (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL) != 0; + newWindowShouldBeModal = weAreModal; + if (!weAreModal && parentChrome) { + parentChrome->IsWindowModal(&weAreModal); + parentIsModal = weAreModal; + } + + if (weAreModal) { + windowIsModal = true; + // in case we added this because weAreModal + chromeFlags |= nsIWebBrowserChrome::CHROME_MODAL | + nsIWebBrowserChrome::CHROME_DEPENDENT; + } + + // Make sure to not create modal windows if our parent is invisible and + // isn't a chrome window. Otherwise we can end up in a bizarre situation + // where we can't shut down because an invisible window is open. If + // someone tries to do this, throw. + if (!hasChromeParent && (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL)) { + nsCOMPtr<nsIBaseWindow> parentWindow(do_GetInterface(parentTreeOwner)); + nsCOMPtr<nsIWidget> parentWidget; + if (parentWindow) { + parentWindow->GetMainWidget(getter_AddRefs(parentWidget)); + } + // NOTE: the logic for this visibility check is duplicated in + // nsIDOMWindowUtils::isParentWindowMainWidgetVisible - if we change + // how a window is determined "visible" in this context then we should + // also adjust that attribute and/or any consumers of it... + if (parentWidget && !parentWidget->IsVisible()) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + NS_ASSERTION(mWindowCreator, + "attempted to open a new window with no WindowCreator"); + rv = NS_ERROR_FAILURE; + if (mWindowCreator) { + nsCOMPtr<nsIWebBrowserChrome> newChrome; + + nsCOMPtr<nsPIDOMWindowInner> parentTopInnerWindow; + if (parentWindow) { + nsCOMPtr<nsPIDOMWindowOuter> parentTopWindow = parentWindow->GetTop(); + if (parentTopWindow) { + parentTopInnerWindow = parentTopWindow->GetCurrentInnerWindow(); + } + } + + if (parentTopInnerWindow) { + parentTopInnerWindow->Suspend(); + } + + /* If the window creator is an nsIWindowCreator2, we can give it + some hints. The only hint at this time is whether the opening window + is in a situation that's likely to mean this is an unrequested + popup window we're creating. However we're not completely honest: + we clear that indicator if the opener is chrome, so that the + downstream consumer can treat the indicator to mean simply + that the new window is subject to popup control. */ + nsCOMPtr<nsIWindowCreator2> windowCreator2( + do_QueryInterface(mWindowCreator)); + if (windowCreator2) { + uint32_t contextFlags = 0; + bool popupConditions = false; + + // is the parent under popup conditions? + if (parentWindow) { + popupConditions = parentWindow->IsLoadingOrRunningTimeout(); + } + + // chrome is always allowed, so clear the flag if the opener is chrome + if (popupConditions) { + popupConditions = !isCallerChrome; + } + + if (popupConditions) { + contextFlags |= + nsIWindowCreator2::PARENT_IS_LOADING_OR_RUNNING_TIMEOUT; + } + + mozIDOMWindowProxy* openerWindow = aForceNoOpener ? nullptr : aParent; + rv = CreateChromeWindow(features, parentChrome, chromeFlags, contextFlags, + nullptr, openerWindow, getter_AddRefs(newChrome)); + + } else { + rv = mWindowCreator->CreateChromeWindow(parentChrome, chromeFlags, + getter_AddRefs(newChrome)); + } + + if (parentTopInnerWindow) { + parentTopInnerWindow->Resume(); + } + + if (newChrome) { + nsCOMPtr<nsIXULWindow> xulWin = do_GetInterface(newChrome); + if (xulWin) { + nsCOMPtr<nsIXULBrowserWindow> xulBrowserWin; + xulWin->GetXULBrowserWindow(getter_AddRefs(xulBrowserWin)); + if (xulBrowserWin) { + nsPIDOMWindowOuter* openerWindow = aForceNoOpener ? nullptr : parentWindow.get(); + xulBrowserWin->ForceInitialBrowserNonRemote(openerWindow); + } + } + /* It might be a chrome nsXULWindow, in which case it won't have + an nsIDOMWindow (primary content shell). But in that case, it'll + be able to hand over an nsIDocShellTreeItem directly. */ + nsCOMPtr<nsPIDOMWindowOuter> newWindow(do_GetInterface(newChrome)); + if (newWindow) { + GetWindowTreeItem(newWindow, getter_AddRefs(newDocShellItem)); + } + if (!newDocShellItem) { + newDocShellItem = do_GetInterface(newChrome); + } + if (!newDocShellItem) { + rv = NS_ERROR_FAILURE; + } + } + } + } + + // better have a window to use by this point + if (!newDocShellItem) { + return rv; + } + + nsCOMPtr<nsIDocShell> newDocShell(do_QueryInterface(newDocShellItem)); + NS_ENSURE_TRUE(newDocShell, NS_ERROR_UNEXPECTED); + + // If our parent is sandboxed, set it as the one permitted sandboxed navigator + // on the new window we're opening. + if (activeDocsSandboxFlags && parentWindow) { + newDocShell->SetOnePermittedSandboxedNavigator( + parentWindow->GetDocShell()); + } + + // Copy sandbox flags to the new window if activeDocsSandboxFlags says to do + // so. Note that it's only nonzero if the window is new, so clobbering + // sandbox flags on the window makes sense in that case. + if (activeDocsSandboxFlags & + SANDBOX_PROPAGATES_TO_AUXILIARY_BROWSING_CONTEXTS) { + newDocShell->SetSandboxFlags(activeDocsSandboxFlags); + } + + rv = ReadyOpenedDocShellItem(newDocShellItem, parentWindow, windowIsNew, + aForceNoOpener, aResult); + if (NS_FAILED(rv)) { + return rv; + } + + if (isNewToplevelWindow) { + nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner; + newDocShellItem->GetTreeOwner(getter_AddRefs(newTreeOwner)); + MaybeDisablePersistence(features, newTreeOwner); + } + + if ((aDialog || windowIsModalContentDialog) && aArgv) { + // Set the args on the new window. + nsCOMPtr<nsPIDOMWindowOuter> piwin(do_QueryInterface(*aResult)); + NS_ENSURE_TRUE(piwin, NS_ERROR_UNEXPECTED); + + rv = piwin->SetArguments(aArgv); + NS_ENSURE_SUCCESS(rv, rv); + } + + /* allow a window that we found by name to keep its name (important for cases + like _self where the given name is different (and invalid)). Also, _blank + is not a window name. */ + if (windowNeedsName) { + if (nameSpecified && !name.LowerCaseEqualsLiteral("_blank")) { + newDocShellItem->SetName(name); + } else { + newDocShellItem->SetName(EmptyString()); + } + } + + // Now we have to set the right opener principal on the new window. Note + // that we have to do this _before_ starting any URI loads, thanks to the + // sync nature of javascript: loads. + // + // Note: The check for the current JSContext isn't necessarily sensical. + // It's just designed to preserve old semantics during a mass-conversion + // patch. + nsCOMPtr<nsIPrincipal> subjectPrincipal = + nsContentUtils::GetCurrentJSContext() ? nsContentUtils::SubjectPrincipal() : + nullptr; + + bool isPrivateBrowsingWindow = false; + + if (windowIsNew) { + auto* docShell = static_cast<nsDocShell*>(newDocShell.get()); + + // If this is not a chrome docShell, we apply originAttributes from the + // subjectPrincipal unless if it's an expanded or system principal. + if (subjectPrincipal && + !nsContentUtils::IsSystemOrExpandedPrincipal(subjectPrincipal) && + docShell->ItemType() != nsIDocShellTreeItem::typeChrome) { + DocShellOriginAttributes attrs; + attrs.InheritFromDocToChildDocShell(BasePrincipal::Cast(subjectPrincipal)->OriginAttributesRef()); + isPrivateBrowsingWindow = !!attrs.mPrivateBrowsingId; + docShell->SetOriginAttributes(attrs); + } else { + nsCOMPtr<nsIDocShellTreeItem> parentItem; + GetWindowTreeItem(aParent, getter_AddRefs(parentItem)); + nsCOMPtr<nsILoadContext> parentContext = do_QueryInterface(parentItem); + if (parentContext) { + isPrivateBrowsingWindow = parentContext->UsePrivateBrowsing(); + } + } + + bool autoPrivateBrowsing = + Preferences::GetBool("browser.privatebrowsing.autostart"); + + if (!autoPrivateBrowsing && + (chromeFlags & nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW)) { + isPrivateBrowsingWindow = false; + } else if (autoPrivateBrowsing || + (chromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW)) { + isPrivateBrowsingWindow = true; + } + + // Now set the opener principal on the new window. Note that we need to do + // this no matter whether we were opened from JS; if there is nothing on + // the JS stack, just use the principal of our parent window. In those + // cases we do _not_ set the parent window principal as the owner of the + // load--since we really don't know who the owner is, just leave it null. + nsCOMPtr<nsPIDOMWindowOuter> newWindow = do_QueryInterface(*aResult); + NS_ASSERTION(newWindow == newDocShell->GetWindow(), "Different windows??"); + + // The principal of the initial about:blank document gets set up in + // nsWindowWatcher::AddWindow. Make sure to call it. In the common case + // this call already happened when the window was created, but + // SetInitialPrincipalToSubject is safe to call multiple times. + if (newWindow) { + newWindow->SetInitialPrincipalToSubject(); + if (aIsPopupSpam) { + nsGlobalWindow* globalWin = nsGlobalWindow::Cast(newWindow); + MOZ_ASSERT(!globalWin->IsPopupSpamWindow(), + "Who marked it as popup spam already???"); + if (!globalWin->IsPopupSpamWindow()) { // Make sure we don't mess up our + // counter even if the above + // assert fails. + globalWin->SetIsPopupSpamWindow(true); + } + } + } + } + + // We rely on CalculateChromeFlags to decide whether remote (out-of-process) + // tabs should be used. + bool isRemoteWindow = + !!(chromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW); + + if (isNewToplevelWindow) { + nsCOMPtr<nsIDocShellTreeItem> childRoot; + newDocShellItem->GetRootTreeItem(getter_AddRefs(childRoot)); + nsCOMPtr<nsILoadContext> childContext = do_QueryInterface(childRoot); + if (childContext) { + childContext->SetPrivateBrowsing(isPrivateBrowsingWindow); + childContext->SetRemoteTabs(isRemoteWindow); + } + } else if (windowIsNew) { + nsCOMPtr<nsILoadContext> childContext = do_QueryInterface(newDocShellItem); + if (childContext) { + childContext->SetPrivateBrowsing(isPrivateBrowsingWindow); + childContext->SetRemoteTabs(isRemoteWindow); + } + } + + nsCOMPtr<nsIDocShellLoadInfo> loadInfo = aLoadInfo; + if (uriToLoad && aNavigate && !loadInfo) { + newDocShell->CreateLoadInfo(getter_AddRefs(loadInfo)); + NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE); + + if (subjectPrincipal) { + loadInfo->SetTriggeringPrincipal(subjectPrincipal); + } + + /* use the URL from the *extant* document, if any. The usual accessor + GetDocument will synchronously create an about:blank document if + it has no better answer, and we only care about a real document. + Also using GetDocument to force document creation seems to + screw up focus in the hidden window; see bug 36016. + */ + nsCOMPtr<nsIDocument> doc = GetEntryDocument(); + if (!doc && parentWindow) { + doc = parentWindow->GetExtantDoc(); + } + if (doc) { + // Set the referrer + loadInfo->SetReferrer(doc->GetDocumentURI()); + loadInfo->SetReferrerPolicy(doc->GetReferrerPolicy()); + } + } + + if (isNewToplevelWindow) { + // Notify observers that the window is open and ready. + // The window has not yet started to load a document. + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + if (obsSvc) { + obsSvc->NotifyObservers(*aResult, "toplevel-window-ready", nullptr); + } + } + + // Before loading the URI we want to be 100% sure that we use the correct + // userContextId. + MOZ_ASSERT(CheckUserContextCompatibility(newDocShell)); + + if (uriToLoad && aNavigate) { + newDocShell->LoadURI( + uriToLoad, + loadInfo, + windowIsNew ? + static_cast<uint32_t>(nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD) : + static_cast<uint32_t>(nsIWebNavigation::LOAD_FLAGS_NONE), + true); + } + + // Copy the current session storage for the current domain. + if (subjectPrincipal && parentDocShell) { + nsCOMPtr<nsIDOMStorageManager> parentStorageManager = + do_QueryInterface(parentDocShell); + nsCOMPtr<nsIDOMStorageManager> newStorageManager = + do_QueryInterface(newDocShell); + + if (parentStorageManager && newStorageManager) { + nsCOMPtr<nsIDOMStorage> storage; + nsCOMPtr<nsPIDOMWindowInner> pInnerWin = parentWindow->GetCurrentInnerWindow(); + + parentStorageManager->GetStorage(pInnerWin, subjectPrincipal, + isPrivateBrowsingWindow, + getter_AddRefs(storage)); + if (storage) { + newStorageManager->CloneStorage(storage); + } + } + } + + if (isNewToplevelWindow) { + nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner; + newDocShellItem->GetTreeOwner(getter_AddRefs(newTreeOwner)); + SizeOpenedWindow(newTreeOwner, aParent, isCallerChrome, sizeSpec); + } + + // XXXbz isn't windowIsModal always true when windowIsModalContentDialog? + if (windowIsModal || windowIsModalContentDialog) { + nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner; + newDocShellItem->GetTreeOwner(getter_AddRefs(newTreeOwner)); + nsCOMPtr<nsIWebBrowserChrome> newChrome(do_GetInterface(newTreeOwner)); + + // Throw an exception here if no web browser chrome is available, + // we need that to show a modal window. + NS_ENSURE_TRUE(newChrome, NS_ERROR_NOT_AVAILABLE); + + // Dispatch dialog events etc, but we only want to do that if + // we're opening a modal content window (the helper classes are + // no-ops if given no window), for chrome dialogs we don't want to + // do any of that (it's done elsewhere for us). + // Make sure we maintain the state on an outer window, because + // that's where it lives; inner windows assert if you try to + // maintain the state on them. + nsAutoWindowStateHelper windowStateHelper( + parentWindow ? parentWindow->GetOuterWindow() : nullptr); + + if (!windowStateHelper.DefaultEnabled()) { + // Default to cancel not opening the modal window. + NS_RELEASE(*aResult); + + return NS_OK; + } + + bool isAppModal = false; + nsCOMPtr<nsIBaseWindow> parentWindow(do_GetInterface(newTreeOwner)); + nsCOMPtr<nsIWidget> parentWidget; + if (parentWindow) { + parentWindow->GetMainWidget(getter_AddRefs(parentWidget)); + if (parentWidget) { + isAppModal = parentWidget->IsRunningAppModal(); + } + } + if (parentWidget && + ((!newWindowShouldBeModal && parentIsModal) || isAppModal)) { + parentWidget->SetFakeModal(true); + } else { + // Reset popup state while opening a modal dialog, and firing + // events about the dialog, to prevent the current state from + // being active the whole time a modal dialog is open. + nsAutoPopupStatePusher popupStatePusher(openAbused); + + newChrome->ShowAsModal(); + } + } + + if (aForceNoOpener && windowIsNew) { + NS_RELEASE(*aResult); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::RegisterNotification(nsIObserver* aObserver) +{ + // just a convenience method; it delegates to nsIObserverService + + if (!aObserver) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (!os) { + return NS_ERROR_FAILURE; + } + + nsresult rv = os->AddObserver(aObserver, "domwindowopened", false); + if (NS_SUCCEEDED(rv)) { + rv = os->AddObserver(aObserver, "domwindowclosed", false); + } + + return rv; +} + +NS_IMETHODIMP +nsWindowWatcher::UnregisterNotification(nsIObserver* aObserver) +{ + // just a convenience method; it delegates to nsIObserverService + + if (!aObserver) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (!os) { + return NS_ERROR_FAILURE; + } + + os->RemoveObserver(aObserver, "domwindowopened"); + os->RemoveObserver(aObserver, "domwindowclosed"); + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::GetWindowEnumerator(nsISimpleEnumerator** aResult) +{ + if (!aResult) { + return NS_ERROR_INVALID_ARG; + } + + MutexAutoLock lock(mListLock); + nsWatcherWindowEnumerator* enumerator = new nsWatcherWindowEnumerator(this); + if (enumerator) { + return CallQueryInterface(enumerator, aResult); + } + + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsWindowWatcher::GetNewPrompter(mozIDOMWindowProxy* aParent, nsIPrompt** aResult) +{ + // This is for backwards compat only. Callers should just use the prompt + // service directly. + nsresult rv; + nsCOMPtr<nsIPromptFactory> factory = + do_GetService("@mozilla.org/prompter;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + return factory->GetPrompt(aParent, NS_GET_IID(nsIPrompt), + reinterpret_cast<void**>(aResult)); +} + +NS_IMETHODIMP +nsWindowWatcher::GetNewAuthPrompter(mozIDOMWindowProxy* aParent, + nsIAuthPrompt** aResult) +{ + // This is for backwards compat only. Callers should just use the prompt + // service directly. + nsresult rv; + nsCOMPtr<nsIPromptFactory> factory = + do_GetService("@mozilla.org/prompter;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + return factory->GetPrompt(aParent, NS_GET_IID(nsIAuthPrompt), + reinterpret_cast<void**>(aResult)); +} + +NS_IMETHODIMP +nsWindowWatcher::GetPrompt(mozIDOMWindowProxy* aParent, const nsIID& aIID, + void** aResult) +{ + // This is for backwards compat only. Callers should just use the prompt + // service directly. + nsresult rv; + nsCOMPtr<nsIPromptFactory> factory = + do_GetService("@mozilla.org/prompter;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = factory->GetPrompt(aParent, aIID, aResult); + + // Allow for an embedding implementation to not support nsIAuthPrompt2. + if (rv == NS_NOINTERFACE && aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) { + nsCOMPtr<nsIAuthPrompt> oldPrompt; + rv = factory->GetPrompt( + aParent, NS_GET_IID(nsIAuthPrompt), getter_AddRefs(oldPrompt)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_WrapAuthPrompt(oldPrompt, reinterpret_cast<nsIAuthPrompt2**>(aResult)); + if (!*aResult) { + rv = NS_ERROR_NOT_AVAILABLE; + } + } + return rv; +} + +NS_IMETHODIMP +nsWindowWatcher::SetWindowCreator(nsIWindowCreator* aCreator) +{ + mWindowCreator = aCreator; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::HasWindowCreator(bool* aResult) +{ + *aResult = mWindowCreator; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::GetActiveWindow(mozIDOMWindowProxy** aActiveWindow) +{ + *aActiveWindow = nullptr; + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (fm) { + return fm->GetActiveWindow(aActiveWindow); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::SetActiveWindow(mozIDOMWindowProxy* aActiveWindow) +{ + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (fm) { + return fm->SetActiveWindow(aActiveWindow); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::AddWindow(mozIDOMWindowProxy* aWindow, nsIWebBrowserChrome* aChrome) +{ + if (!aWindow) { + return NS_ERROR_INVALID_ARG; + } + +#ifdef DEBUG + { + nsCOMPtr<nsPIDOMWindowOuter> win(do_QueryInterface(aWindow)); + + NS_ASSERTION(win->IsOuterWindow(), + "Uh, the active window must be an outer window!"); + } +#endif + + { + nsWatcherWindowEntry* info; + MutexAutoLock lock(mListLock); + + // if we already have an entry for this window, adjust + // its chrome mapping and return + info = FindWindowEntry(aWindow); + if (info) { + nsCOMPtr<nsISupportsWeakReference> supportsweak( + do_QueryInterface(aChrome)); + if (supportsweak) { + supportsweak->GetWeakReference(getter_AddRefs(info->mChromeWeak)); + } else { + info->mChrome = aChrome; + info->mChromeWeak = nullptr; + } + return NS_OK; + } + + // create a window info struct and add it to the list of windows + info = new nsWatcherWindowEntry(aWindow, aChrome); + if (!info) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (mOldestWindow) { + info->InsertAfter(mOldestWindow->mOlder); + } else { + mOldestWindow = info; + } + } // leave the mListLock + + // a window being added to us signifies a newly opened window. + // send notifications. + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (!os) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsISupports> domwin(do_QueryInterface(aWindow)); + return os->NotifyObservers(domwin, "domwindowopened", 0); +} + +NS_IMETHODIMP +nsWindowWatcher::RemoveWindow(mozIDOMWindowProxy* aWindow) +{ + // find the corresponding nsWatcherWindowEntry, remove it + + if (!aWindow) { + return NS_ERROR_INVALID_ARG; + } + + nsWatcherWindowEntry* info = FindWindowEntry(aWindow); + if (info) { + RemoveWindow(info); + return NS_OK; + } + NS_WARNING("requested removal of nonexistent window"); + return NS_ERROR_INVALID_ARG; +} + +nsWatcherWindowEntry* +nsWindowWatcher::FindWindowEntry(mozIDOMWindowProxy* aWindow) +{ + // find the corresponding nsWatcherWindowEntry + nsWatcherWindowEntry* info; + nsWatcherWindowEntry* listEnd; +#ifdef USEWEAKREFS + nsresult rv; + bool found; +#endif + + info = mOldestWindow; + listEnd = 0; +#ifdef USEWEAKREFS + rv = NS_OK; + found = false; + while (info != listEnd && NS_SUCCEEDED(rv)) { + nsCOMPtr<mozIDOMWindowProxy> infoWindow(do_QueryReferent(info->mWindow)); + if (!infoWindow) { // clean up dangling reference, while we're here + rv = RemoveWindow(info); + } else if (infoWindow.get() == aWindow) { + return info; + } + + info = info->mYounger; + listEnd = mOldestWindow; + } + return 0; +#else + while (info != listEnd) { + if (info->mWindow == aWindow) { + return info; + } + info = info->mYounger; + listEnd = mOldestWindow; + } + return 0; +#endif +} + +nsresult +nsWindowWatcher::RemoveWindow(nsWatcherWindowEntry* aInfo) +{ + uint32_t count = mEnumeratorList.Length(); + + { + // notify the enumerators + MutexAutoLock lock(mListLock); + for (uint32_t ctr = 0; ctr < count; ++ctr) { + mEnumeratorList[ctr]->WindowRemoved(aInfo); + } + + // remove the element from the list + if (aInfo == mOldestWindow) { + mOldestWindow = aInfo->mYounger == mOldestWindow ? 0 : aInfo->mYounger; + } + aInfo->Unlink(); + } + + // a window being removed from us signifies a newly closed window. + // send notifications. + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { +#ifdef USEWEAKREFS + nsCOMPtr<nsISupports> domwin(do_QueryReferent(aInfo->mWindow)); + if (domwin) { + os->NotifyObservers(domwin, "domwindowclosed", 0); + } + // else bummer. since the window is gone, there's nothing to notify with. +#else + nsCOMPtr<nsISupports> domwin(do_QueryInterface(aInfo->mWindow)); + os->NotifyObservers(domwin, "domwindowclosed", 0); +#endif + } + + delete aInfo; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::GetChromeForWindow(mozIDOMWindowProxy* aWindow, + nsIWebBrowserChrome** aResult) +{ + if (!aWindow || !aResult) { + return NS_ERROR_INVALID_ARG; + } + *aResult = 0; + + MutexAutoLock lock(mListLock); + nsWatcherWindowEntry* info = FindWindowEntry(aWindow); + if (info) { + if (info->mChromeWeak) { + return info->mChromeWeak->QueryReferent( + NS_GET_IID(nsIWebBrowserChrome), reinterpret_cast<void**>(aResult)); + } + *aResult = info->mChrome; + NS_IF_ADDREF(*aResult); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::GetWindowByName(const nsAString& aTargetName, + mozIDOMWindowProxy* aCurrentWindow, + mozIDOMWindowProxy** aResult) +{ + if (!aResult) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = nullptr; + + nsPIDOMWindowOuter* currentWindow = + aCurrentWindow ? nsPIDOMWindowOuter::From(aCurrentWindow) : nullptr; + + nsCOMPtr<nsIDocShellTreeItem> treeItem; + + nsCOMPtr<nsIDocShellTreeItem> startItem; + GetWindowTreeItem(currentWindow, getter_AddRefs(startItem)); + if (startItem) { + // Note: original requestor is null here, per idl comments + startItem->FindItemWithName(aTargetName, nullptr, nullptr, + getter_AddRefs(treeItem)); + } else { + // Note: original requestor is null here, per idl comments + FindItemWithName(aTargetName, nullptr, nullptr, getter_AddRefs(treeItem)); + } + + if (treeItem) { + nsCOMPtr<nsPIDOMWindowOuter> domWindow = treeItem->GetWindow(); + domWindow.forget(aResult); + } + + return NS_OK; +} + +bool +nsWindowWatcher::AddEnumerator(nsWatcherWindowEnumerator* aEnumerator) +{ + // (requires a lock; assumes it's called by someone holding the lock) + return mEnumeratorList.AppendElement(aEnumerator) != nullptr; +} + +bool +nsWindowWatcher::RemoveEnumerator(nsWatcherWindowEnumerator* aEnumerator) +{ + // (requires a lock; assumes it's called by someone holding the lock) + return mEnumeratorList.RemoveElement(aEnumerator); +} + +nsresult +nsWindowWatcher::URIfromURL(const char* aURL, + mozIDOMWindowProxy* aParent, + nsIURI** aURI) +{ + // Build the URI relative to the entry global. + nsCOMPtr<nsPIDOMWindowInner> baseWindow = do_QueryInterface(GetEntryGlobal()); + + // failing that, build it relative to the parent window, if possible + if (!baseWindow && aParent) { + baseWindow = nsPIDOMWindowOuter::From(aParent)->GetCurrentInnerWindow(); + } + + // failing that, use the given URL unmodified. It had better not be relative. + + nsIURI* baseURI = nullptr; + + // get baseWindow's document URI + if (baseWindow) { + if (nsIDocument* doc = baseWindow->GetDoc()) { + baseURI = doc->GetDocBaseURI(); + } + } + + // build and return the absolute URI + return NS_NewURI(aURI, aURL, baseURI); +} + +#define NS_CALCULATE_CHROME_FLAG_FOR(feature, flag) \ + prefBranch->GetBoolPref(feature, &forceEnable); \ + if (forceEnable && !aDialog && !aHasChromeParent && !aChromeURL) { \ + chromeFlags |= flag; \ + } else { \ + chromeFlags |= \ + WinHasOption(aFeatures, feature, 0, &presenceFlag) ? flag : 0; \ + } + +// static +uint32_t +nsWindowWatcher::CalculateChromeFlagsHelper(uint32_t aInitialFlags, + const nsACString& aFeatures, + bool& presenceFlag, + bool aDialog, + bool aHasChromeParent, + bool aChromeURL) +{ + uint32_t chromeFlags = aInitialFlags; + + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch; + nsCOMPtr<nsIPrefService> prefs = + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + + NS_ENSURE_SUCCESS(rv, nsIWebBrowserChrome::CHROME_DEFAULT); + + rv = prefs->GetBranch("dom.disable_window_open_feature.", + getter_AddRefs(prefBranch)); + + NS_ENSURE_SUCCESS(rv, nsIWebBrowserChrome::CHROME_DEFAULT); + + // NS_CALCULATE_CHROME_FLAG_FOR requires aFeatures, forceEnable, aDialog + // aHasChromeParent, aChromeURL, presenceFlag and chromeFlags to be in + // scope. + bool forceEnable = false; + + NS_CALCULATE_CHROME_FLAG_FOR("titlebar", + nsIWebBrowserChrome::CHROME_TITLEBAR); + NS_CALCULATE_CHROME_FLAG_FOR("close", + nsIWebBrowserChrome::CHROME_WINDOW_CLOSE); + NS_CALCULATE_CHROME_FLAG_FOR("toolbar", + nsIWebBrowserChrome::CHROME_TOOLBAR); + NS_CALCULATE_CHROME_FLAG_FOR("location", + nsIWebBrowserChrome::CHROME_LOCATIONBAR); + NS_CALCULATE_CHROME_FLAG_FOR("personalbar", + nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR); + NS_CALCULATE_CHROME_FLAG_FOR("status", + nsIWebBrowserChrome::CHROME_STATUSBAR); + NS_CALCULATE_CHROME_FLAG_FOR("menubar", + nsIWebBrowserChrome::CHROME_MENUBAR); + NS_CALCULATE_CHROME_FLAG_FOR("resizable", + nsIWebBrowserChrome::CHROME_WINDOW_RESIZE); + NS_CALCULATE_CHROME_FLAG_FOR("minimizable", + nsIWebBrowserChrome::CHROME_WINDOW_MIN); + + // default scrollbar to "on," unless explicitly turned off + if (WinHasOption(aFeatures, "scrollbars", 1, &presenceFlag) || !presenceFlag) { + chromeFlags |= nsIWebBrowserChrome::CHROME_SCROLLBARS; + } + + return chromeFlags; +} + +// static +uint32_t +nsWindowWatcher::EnsureFlagsSafeForContent(uint32_t aChromeFlags, + bool aChromeURL) +{ + aChromeFlags |= nsIWebBrowserChrome::CHROME_TITLEBAR; + aChromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_CLOSE; + aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_LOWERED; + aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_RAISED; + aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_POPUP; + /* Untrusted script is allowed to pose modal windows with a chrome + scheme. This check could stand to be better. But it effectively + prevents untrusted script from opening modal windows in general + while still allowing alerts and the like. */ + if (!aChromeURL) { + aChromeFlags &= ~(nsIWebBrowserChrome::CHROME_MODAL | + nsIWebBrowserChrome::CHROME_OPENAS_CHROME); + } + + if (!(aChromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_CHROME)) { + aChromeFlags &= ~nsIWebBrowserChrome::CHROME_DEPENDENT; + } + + return aChromeFlags; +} + +/** + * Calculate the chrome bitmask from a string list of features requested + * from a child process. Feature strings that are restricted to the parent + * process are ignored here. + * @param aFeatures a string containing a list of named features + * @return the chrome bitmask + */ +// static +uint32_t +nsWindowWatcher::CalculateChromeFlagsForChild(const nsACString& aFeatures) +{ + if (aFeatures.IsVoid()) { + return nsIWebBrowserChrome::CHROME_ALL; + } + + bool presenceFlag = false; + uint32_t chromeFlags = CalculateChromeFlagsHelper( + nsIWebBrowserChrome::CHROME_WINDOW_BORDERS, aFeatures, presenceFlag); + + return EnsureFlagsSafeForContent(chromeFlags); +} + +/** + * Calculate the chrome bitmask from a string list of features for a new + * privileged window. + * @param aParent the opener window + * @param aFeatures a string containing a list of named chrome features + * @param aDialog affects the assumptions made about unnamed features + * @param aChromeURL true if the window is being sent to a chrome:// URL + * @param aHasChromeParent true if the parent window is privileged + * @param aCalledFromJS true if the window open request came from script. + * @return the chrome bitmask + */ +// static +uint32_t +nsWindowWatcher::CalculateChromeFlagsForParent(mozIDOMWindowProxy* aParent, + const nsACString& aFeatures, + bool aDialog, + bool aChromeURL, + bool aHasChromeParent, + bool aCalledFromJS) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(nsContentUtils::LegacyIsCallerChromeOrNativeCode()); + + uint32_t chromeFlags = 0; + + // The features string is made void by OpenWindowInternal + // if nullptr was originally passed as the features string. + if (aFeatures.IsVoid()) { + chromeFlags = nsIWebBrowserChrome::CHROME_ALL; + if (aDialog) { + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG | + nsIWebBrowserChrome::CHROME_OPENAS_CHROME; + } + } else { + chromeFlags = nsIWebBrowserChrome::CHROME_WINDOW_BORDERS; + } + + /* This function has become complicated since browser windows and + dialogs diverged. The difference is, browser windows assume all + chrome not explicitly mentioned is off, if the features string + is not null. Exceptions are some OS border chrome new with Mozilla. + Dialogs interpret a (mostly) empty features string to mean + "OS's choice," and also support an "all" flag explicitly disallowed + in the standards-compliant window.(normal)open. */ + + bool presenceFlag = false; + if (aDialog && WinHasOption(aFeatures, "all", 0, &presenceFlag)) { + chromeFlags = nsIWebBrowserChrome::CHROME_ALL; + } + + /* Next, allow explicitly named options to override the initial settings */ + chromeFlags = CalculateChromeFlagsHelper(chromeFlags, aFeatures, presenceFlag, + aDialog, aHasChromeParent, aChromeURL); + + // Determine whether the window is a private browsing window + chromeFlags |= WinHasOption(aFeatures, "private", 0, &presenceFlag) ? + nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW : 0; + chromeFlags |= WinHasOption(aFeatures, "non-private", 0, &presenceFlag) ? + nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW : 0; + + // Determine whether the window should have remote tabs. + bool remote = BrowserTabsRemoteAutostart(); + + if (remote) { + remote = !WinHasOption(aFeatures, "non-remote", 0, &presenceFlag); + } else { + remote = WinHasOption(aFeatures, "remote", 0, &presenceFlag); + } + + if (remote) { + chromeFlags |= nsIWebBrowserChrome::CHROME_REMOTE_WINDOW; + } + + chromeFlags |= WinHasOption(aFeatures, "popup", 0, &presenceFlag) ? + nsIWebBrowserChrome::CHROME_WINDOW_POPUP : 0; + + /* OK. + Normal browser windows, in spite of a stated pattern of turning off + all chrome not mentioned explicitly, will want the new OS chrome (window + borders, titlebars, closebox) on, unless explicitly turned off. + Dialogs, on the other hand, take the absence of any explicit settings + to mean "OS' choice." */ + + // default titlebar and closebox to "on," if not mentioned at all + if (!(chromeFlags & nsIWebBrowserChrome::CHROME_WINDOW_POPUP)) { + if (!PL_strcasestr(aFeatures.BeginReading(), "titlebar")) { + chromeFlags |= nsIWebBrowserChrome::CHROME_TITLEBAR; + } + if (!PL_strcasestr(aFeatures.BeginReading(), "close")) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_CLOSE; + } + } + + if (aDialog && !aFeatures.IsVoid() && !presenceFlag) { + chromeFlags = nsIWebBrowserChrome::CHROME_DEFAULT; + } + + /* Finally, once all the above normal chrome has been divined, deal + with the features that are more operating hints than appearance + instructions. (Note modality implies dependence.) */ + + if (WinHasOption(aFeatures, "alwaysLowered", 0, nullptr) || + WinHasOption(aFeatures, "z-lock", 0, nullptr)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_LOWERED; + } else if (WinHasOption(aFeatures, "alwaysRaised", 0, nullptr)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_RAISED; + } + + chromeFlags |= WinHasOption(aFeatures, "macsuppressanimation", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_MAC_SUPPRESS_ANIMATION : 0; + + chromeFlags |= WinHasOption(aFeatures, "chrome", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_OPENAS_CHROME : 0; + chromeFlags |= WinHasOption(aFeatures, "extrachrome", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_EXTRA : 0; + chromeFlags |= WinHasOption(aFeatures, "centerscreen", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_CENTER_SCREEN : 0; + chromeFlags |= WinHasOption(aFeatures, "dependent", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_DEPENDENT : 0; + chromeFlags |= WinHasOption(aFeatures, "modal", 0, nullptr) ? + (nsIWebBrowserChrome::CHROME_MODAL | nsIWebBrowserChrome::CHROME_DEPENDENT) : 0; + + /* On mobile we want to ignore the dialog window feature, since the mobile UI + does not provide any affordance for dialog windows. This does not interfere + with dialog windows created through openDialog. */ + bool disableDialogFeature = false; + nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID); + + branch->GetBoolPref("dom.disable_window_open_dialog_feature", + &disableDialogFeature); + + if (!disableDialogFeature) { + chromeFlags |= WinHasOption(aFeatures, "dialog", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_OPENAS_DIALOG : 0; + } + + /* and dialogs need to have the last word. assume dialogs are dialogs, + and opened as chrome, unless explicitly told otherwise. */ + if (aDialog) { + if (!PL_strcasestr(aFeatures.BeginReading(), "dialog")) { + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG; + } + if (!PL_strcasestr(aFeatures.BeginReading(), "chrome")) { + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_CHROME; + } + } + + /* missing + chromeFlags->copy_history + */ + + // Check security state for use in determing window dimensions + if (!aHasChromeParent) { + chromeFlags = EnsureFlagsSafeForContent(chromeFlags, aChromeURL); + } + + // Disable CHROME_OPENAS_DIALOG if the window is inside <iframe mozbrowser>. + // It's up to the embedder to interpret what dialog=1 means. + nsCOMPtr<nsIDocShell> docshell = do_GetInterface(aParent); + if (docshell && docshell->GetIsInMozBrowserOrApp()) { + chromeFlags &= ~nsIWebBrowserChrome::CHROME_OPENAS_DIALOG; + } + + return chromeFlags; +} + +// static +int32_t +nsWindowWatcher::WinHasOption(const nsACString& aOptions, const char* aName, + int32_t aDefault, bool* aPresenceFlag) +{ + if (aOptions.IsEmpty()) { + return 0; + } + + const char* options = aOptions.BeginReading(); + char* comma; + char* equal; + int32_t found = 0; + +#ifdef DEBUG + NS_ASSERTION(nsAutoCString(aOptions).FindCharInSet(" \n\r\t") == kNotFound, + "There should be no whitespace in this string!"); +#endif + + while (true) { + comma = PL_strchr(options, ','); + if (comma) { + *comma = '\0'; + } + equal = PL_strchr(options, '='); + if (equal) { + *equal = '\0'; + } + if (nsCRT::strcasecmp(options, aName) == 0) { + if (aPresenceFlag) { + *aPresenceFlag = true; + } + if (equal) + if (*(equal + 1) == '*') { + found = aDefault; + } else if (nsCRT::strcasecmp(equal + 1, "yes") == 0) { + found = 1; + } else { + found = atoi(equal + 1); + } + else { + found = 1; + } + } + if (equal) { + *equal = '='; + } + if (comma) { + *comma = ','; + } + if (found || !comma) { + break; + } + options = comma + 1; + } + return found; +} + +/* try to find an nsIDocShellTreeItem with the given name in any + known open window. a failure to find the item will not + necessarily return a failure method value. check aFoundItem. +*/ +NS_IMETHODIMP +nsWindowWatcher::FindItemWithName(const nsAString& aName, + nsIDocShellTreeItem* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + nsIDocShellTreeItem** aFoundItem) +{ + *aFoundItem = nullptr; + if (aName.IsEmpty()) { + return NS_OK; + } + + if (aName.LowerCaseEqualsLiteral("_blank") || + aName.LowerCaseEqualsLiteral("_top") || + aName.LowerCaseEqualsLiteral("_parent") || + aName.LowerCaseEqualsLiteral("_self")) { + return NS_OK; + } + + // If we are looking for an item and we don't have a docshell we are checking + // on, let's just look in the chrome tab group! + return TabGroup::GetChromeTabGroup()->FindItemWithName(aName, + aRequestor, + aOriginalRequestor, + aFoundItem); +} + +already_AddRefed<nsIDocShellTreeItem> +nsWindowWatcher::GetCallerTreeItem(nsIDocShellTreeItem* aParentItem) +{ + nsCOMPtr<nsIWebNavigation> callerWebNav = do_GetInterface(GetEntryGlobal()); + nsCOMPtr<nsIDocShellTreeItem> callerItem = do_QueryInterface(callerWebNav); + if (!callerItem) { + callerItem = aParentItem; + } + + return callerItem.forget(); +} + +nsPIDOMWindowOuter* +nsWindowWatcher::SafeGetWindowByName(const nsAString& aName, + bool aForceNoOpener, + mozIDOMWindowProxy* aCurrentWindow) +{ + if (aForceNoOpener) { + if (!aName.LowerCaseEqualsLiteral("_self") && + !aName.LowerCaseEqualsLiteral("_top") && + !aName.LowerCaseEqualsLiteral("_parent")) { + // Ignore all other names in the noopener case. + return nullptr; + } + } + + nsCOMPtr<nsIDocShellTreeItem> startItem; + GetWindowTreeItem(aCurrentWindow, getter_AddRefs(startItem)); + + nsCOMPtr<nsIDocShellTreeItem> callerItem = GetCallerTreeItem(startItem); + + nsCOMPtr<nsIDocShellTreeItem> foundItem; + if (startItem) { + startItem->FindItemWithName(aName, nullptr, callerItem, + getter_AddRefs(foundItem)); + } else { + FindItemWithName(aName, nullptr, callerItem, + getter_AddRefs(foundItem)); + } + + return foundItem ? foundItem->GetWindow() : nullptr; +} + +/* Fetch the nsIDOMWindow corresponding to the given nsIDocShellTreeItem. + This forces the creation of a script context, if one has not already + been created. Note it also sets the window's opener to the parent, + if applicable -- because it's just convenient, that's all. null aParent + is acceptable. */ +nsresult +nsWindowWatcher::ReadyOpenedDocShellItem(nsIDocShellTreeItem* aOpenedItem, + nsPIDOMWindowOuter* aParent, + bool aWindowIsNew, + bool aForceNoOpener, + mozIDOMWindowProxy** aOpenedWindow) +{ + nsresult rv = NS_ERROR_FAILURE; + + NS_ENSURE_ARG(aOpenedWindow); + + *aOpenedWindow = 0; + nsCOMPtr<nsPIDOMWindowOuter> piOpenedWindow = aOpenedItem->GetWindow(); + if (piOpenedWindow) { + if (!aForceNoOpener) { + piOpenedWindow->SetOpenerWindow(aParent, aWindowIsNew); // damnit + } else if (aParent && aParent != piOpenedWindow) { + MOZ_ASSERT(piOpenedWindow->TabGroup() != aParent->TabGroup(), + "If we're forcing no opener, they should be in different tab groups"); + } + + if (aWindowIsNew) { +#ifdef DEBUG + // Assert that we're not loading things right now. If we are, when + // that load completes it will clobber whatever principals we set up + // on this new window! + nsCOMPtr<nsIDocumentLoader> docloader = do_QueryInterface(aOpenedItem); + NS_ASSERTION(docloader, "How can we not have a docloader here?"); + + nsCOMPtr<nsIChannel> chan; + docloader->GetDocumentChannel(getter_AddRefs(chan)); + NS_ASSERTION(!chan, "Why is there a document channel?"); +#endif + + nsCOMPtr<nsIDocument> doc = piOpenedWindow->GetExtantDoc(); + if (doc) { + doc->SetIsInitialDocument(true); + } + } + rv = CallQueryInterface(piOpenedWindow, aOpenedWindow); + } + return rv; +} + +// static +void +nsWindowWatcher::CalcSizeSpec(const nsACString& aFeatures, SizeSpec& aResult) +{ + // Parse position spec, if any, from aFeatures + bool present; + int32_t temp; + + present = false; + if ((temp = WinHasOption(aFeatures, "left", 0, &present)) || present) { + aResult.mLeft = temp; + } else if ((temp = WinHasOption(aFeatures, "screenX", 0, &present)) || + present) { + aResult.mLeft = temp; + } + aResult.mLeftSpecified = present; + + present = false; + if ((temp = WinHasOption(aFeatures, "top", 0, &present)) || present) { + aResult.mTop = temp; + } else if ((temp = WinHasOption(aFeatures, "screenY", 0, &present)) || + present) { + aResult.mTop = temp; + } + aResult.mTopSpecified = present; + + // Parse size spec, if any. Chrome size overrides content size. + if ((temp = WinHasOption(aFeatures, "outerWidth", INT32_MIN, nullptr))) { + if (temp == INT32_MIN) { + aResult.mUseDefaultWidth = true; + } else { + aResult.mOuterWidth = temp; + } + aResult.mOuterWidthSpecified = true; + } else if ((temp = WinHasOption(aFeatures, "width", INT32_MIN, nullptr)) || + (temp = WinHasOption(aFeatures, "innerWidth", INT32_MIN, + nullptr))) { + if (temp == INT32_MIN) { + aResult.mUseDefaultWidth = true; + } else { + aResult.mInnerWidth = temp; + } + aResult.mInnerWidthSpecified = true; + } + + if ((temp = WinHasOption(aFeatures, "outerHeight", INT32_MIN, nullptr))) { + if (temp == INT32_MIN) { + aResult.mUseDefaultHeight = true; + } else { + aResult.mOuterHeight = temp; + } + aResult.mOuterHeightSpecified = true; + } else if ((temp = WinHasOption(aFeatures, "height", INT32_MIN, + nullptr)) || + (temp = WinHasOption(aFeatures, "innerHeight", INT32_MIN, + nullptr))) { + if (temp == INT32_MIN) { + aResult.mUseDefaultHeight = true; + } else { + aResult.mInnerHeight = temp; + } + aResult.mInnerHeightSpecified = true; + } +} + +/* Size and position a new window according to aSizeSpec. This method + is assumed to be called after the window has already been given + a default position and size; thus its current position and size are + accurate defaults. The new window is made visible at method end. + @param aTreeOwner + The top-level nsIDocShellTreeOwner of the newly opened window. + @param aParent (optional) + The parent window from which to inherit zoom factors from if + aOpenerFullZoom is none. + @param aIsCallerChrome + True if the code requesting the new window is privileged. + @param aSizeSpec + The size that the new window should be. + @param aOpenerFullZoom + If not nothing, a zoom factor to scale the content to. +*/ +void +nsWindowWatcher::SizeOpenedWindow(nsIDocShellTreeOwner* aTreeOwner, + mozIDOMWindowProxy* aParent, + bool aIsCallerChrome, + const SizeSpec& aSizeSpec, + Maybe<float> aOpenerFullZoom) +{ + // We should only be sizing top-level windows if we're in the parent + // process. + MOZ_ASSERT(XRE_IsParentProcess()); + + // position and size of window + int32_t left = 0, top = 0, width = 100, height = 100; + // difference between chrome and content size + int32_t chromeWidth = 0, chromeHeight = 0; + // whether the window size spec refers to chrome or content + bool sizeChromeWidth = true, sizeChromeHeight = true; + + // get various interfaces for aDocShellItem, used throughout this method + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin(do_QueryInterface(aTreeOwner)); + if (!treeOwnerAsWin) { // we'll need this to actually size the docshell + return; + } + + double openerZoom = aOpenerFullZoom.valueOr(1.0); + if (aParent && aOpenerFullZoom.isNothing()) { + nsCOMPtr<nsPIDOMWindowOuter> piWindow = nsPIDOMWindowOuter::From(aParent); + if (nsIDocument* doc = piWindow->GetDoc()) { + if (nsIPresShell* shell = doc->GetShell()) { + if (nsPresContext* presContext = shell->GetPresContext()) { + openerZoom = presContext->GetFullZoom(); + } + } + } + } + + double scale = 1.0; + treeOwnerAsWin->GetUnscaledDevicePixelsPerCSSPixel(&scale); + + /* The current position and size will be unchanged if not specified + (and they fit entirely onscreen). Also, calculate the difference + between chrome and content sizes on aDocShellItem's window. + This latter point becomes important if chrome and content + specifications are mixed in aFeatures, and when bringing the window + back from too far off the right or bottom edges of the screen. */ + + treeOwnerAsWin->GetPositionAndSize(&left, &top, &width, &height); + left = NSToIntRound(left / scale); + top = NSToIntRound(top / scale); + width = NSToIntRound(width / scale); + height = NSToIntRound(height / scale); + { + int32_t contentWidth, contentHeight; + bool hasPrimaryContent = false; + aTreeOwner->GetHasPrimaryContent(&hasPrimaryContent); + if (hasPrimaryContent) { + aTreeOwner->GetPrimaryContentSize(&contentWidth, &contentHeight); + } else { + aTreeOwner->GetRootShellSize(&contentWidth, &contentHeight); + } + chromeWidth = width - contentWidth; + chromeHeight = height - contentHeight; + } + + // Set up left/top + if (aSizeSpec.mLeftSpecified) { + left = NSToIntRound(aSizeSpec.mLeft * openerZoom); + } + + if (aSizeSpec.mTopSpecified) { + top = NSToIntRound(aSizeSpec.mTop * openerZoom); + } + + // Set up width + if (aSizeSpec.mOuterWidthSpecified) { + if (!aSizeSpec.mUseDefaultWidth) { + width = NSToIntRound(aSizeSpec.mOuterWidth * openerZoom); + } // Else specified to default; just use our existing width + } else if (aSizeSpec.mInnerWidthSpecified) { + sizeChromeWidth = false; + if (aSizeSpec.mUseDefaultWidth) { + width = width - chromeWidth; + } else { + width = NSToIntRound(aSizeSpec.mInnerWidth * openerZoom); + } + } + + // Set up height + if (aSizeSpec.mOuterHeightSpecified) { + if (!aSizeSpec.mUseDefaultHeight) { + height = NSToIntRound(aSizeSpec.mOuterHeight * openerZoom); + } // Else specified to default; just use our existing height + } else if (aSizeSpec.mInnerHeightSpecified) { + sizeChromeHeight = false; + if (aSizeSpec.mUseDefaultHeight) { + height = height - chromeHeight; + } else { + height = NSToIntRound(aSizeSpec.mInnerHeight * openerZoom); + } + } + + bool positionSpecified = aSizeSpec.PositionSpecified(); + + // Check security state for use in determing window dimensions + bool enabled = false; + if (aIsCallerChrome) { + // Only enable special priveleges for chrome when chrome calls + // open() on a chrome window + nsCOMPtr<nsIDOMChromeWindow> chromeWin(do_QueryInterface(aParent)); + enabled = !aParent || chromeWin; + } + + if (!enabled) { + // Security check failed. Ensure all args meet minimum reqs. + + int32_t oldTop = top, oldLeft = left; + + // We'll also need the screen dimensions + nsCOMPtr<nsIScreen> screen; + nsCOMPtr<nsIScreenManager> screenMgr( + do_GetService("@mozilla.org/gfx/screenmanager;1")); + if (screenMgr) + screenMgr->ScreenForRect(left, top, width, height, + getter_AddRefs(screen)); + if (screen) { + int32_t screenLeft, screenTop, screenWidth, screenHeight; + int32_t winWidth = width + (sizeChromeWidth ? 0 : chromeWidth), + winHeight = height + (sizeChromeHeight ? 0 : chromeHeight); + + // Get screen dimensions (in device pixels) + screen->GetAvailRect(&screenLeft, &screenTop, &screenWidth, + &screenHeight); + // Convert them to CSS pixels + screenLeft = NSToIntRound(screenLeft / scale); + screenTop = NSToIntRound(screenTop / scale); + screenWidth = NSToIntRound(screenWidth / scale); + screenHeight = NSToIntRound(screenHeight / scale); + + if (aSizeSpec.SizeSpecified()) { + /* Unlike position, force size out-of-bounds check only if + size actually was specified. Otherwise, intrinsically sized + windows are broken. */ + if (height < 100) { + height = 100; + winHeight = height + (sizeChromeHeight ? 0 : chromeHeight); + } + if (winHeight > screenHeight) { + height = screenHeight - (sizeChromeHeight ? 0 : chromeHeight); + } + if (width < 100) { + width = 100; + winWidth = width + (sizeChromeWidth ? 0 : chromeWidth); + } + if (winWidth > screenWidth) { + width = screenWidth - (sizeChromeWidth ? 0 : chromeWidth); + } + } + + if (left + winWidth > screenLeft + screenWidth || + left + winWidth < left) { + left = screenLeft + screenWidth - winWidth; + } + if (left < screenLeft) { + left = screenLeft; + } + if (top + winHeight > screenTop + screenHeight || top + winHeight < top) { + top = screenTop + screenHeight - winHeight; + } + if (top < screenTop) { + top = screenTop; + } + if (top != oldTop || left != oldLeft) { + positionSpecified = true; + } + } + } + + // size and position the window + + if (positionSpecified) { + // Get the scale factor appropriate for the screen we're actually + // positioning on. + nsCOMPtr<nsIScreen> screen; + nsCOMPtr<nsIScreenManager> screenMgr( + do_GetService("@mozilla.org/gfx/screenmanager;1")); + if (screenMgr) { + screenMgr->ScreenForRect(left, top, 1, 1, getter_AddRefs(screen)); + } + if (screen) { + double cssToDevPixScale, desktopToDevPixScale; + screen->GetDefaultCSSScaleFactor(&cssToDevPixScale); + screen->GetContentsScaleFactor(&desktopToDevPixScale); + double cssToDesktopScale = cssToDevPixScale / desktopToDevPixScale; + int32_t screenLeft, screenTop, screenWd, screenHt; + screen->GetRectDisplayPix(&screenLeft, &screenTop, &screenWd, &screenHt); + // Adjust by desktop-pixel origin of the target screen when scaling + // to convert from per-screen CSS-px coords to global desktop coords. + treeOwnerAsWin->SetPositionDesktopPix( + (left - screenLeft) * cssToDesktopScale + screenLeft, + (top - screenTop) * cssToDesktopScale + screenTop); + } else { + // Couldn't find screen? This shouldn't happen. + treeOwnerAsWin->SetPosition(left * scale, top * scale); + } + // This shouldn't be necessary, given the screen check above, but in case + // moving the window didn't put it where we expected (e.g. due to issues + // at the widget level, or whatever), let's re-fetch the scale factor for + // wherever it really ended up + treeOwnerAsWin->GetUnscaledDevicePixelsPerCSSPixel(&scale); + } + if (aSizeSpec.SizeSpecified()) { + /* Prefer to trust the interfaces, which think in terms of pure + chrome or content sizes. If we have a mix, use the chrome size + adjusted by the chrome/content differences calculated earlier. */ + if (!sizeChromeWidth && !sizeChromeHeight) { + bool hasPrimaryContent = false; + aTreeOwner->GetHasPrimaryContent(&hasPrimaryContent); + if (hasPrimaryContent) { + aTreeOwner->SetPrimaryContentSize(width * scale, height * scale); + } else { + aTreeOwner->SetRootShellSize(width * scale, height * scale); + } + } else { + if (!sizeChromeWidth) { + width += chromeWidth; + } + if (!sizeChromeHeight) { + height += chromeHeight; + } + treeOwnerAsWin->SetSize(width * scale, height * scale, false); + } + } + treeOwnerAsWin->SetVisibility(true); +} + +void +nsWindowWatcher::GetWindowTreeItem(mozIDOMWindowProxy* aWindow, + nsIDocShellTreeItem** aResult) +{ + *aResult = 0; + + if (aWindow) { + nsIDocShell* docshell = nsPIDOMWindowOuter::From(aWindow)->GetDocShell(); + if (docshell) { + CallQueryInterface(docshell, aResult); + } + } +} + +void +nsWindowWatcher::GetWindowTreeOwner(nsPIDOMWindowOuter* aWindow, + nsIDocShellTreeOwner** aResult) +{ + *aResult = 0; + + nsCOMPtr<nsIDocShellTreeItem> treeItem; + GetWindowTreeItem(aWindow, getter_AddRefs(treeItem)); + if (treeItem) { + treeItem->GetTreeOwner(aResult); + } +} + +/* static */ +int32_t +nsWindowWatcher::GetWindowOpenLocation(nsPIDOMWindowOuter* aParent, + uint32_t aChromeFlags, + bool aCalledFromJS, + bool aPositionSpecified, + bool aSizeSpecified) +{ + bool isFullScreen = aParent->GetFullScreen(); + + // Where should we open this? + int32_t containerPref; + if (NS_FAILED(Preferences::GetInt("browser.link.open_newwindow", + &containerPref))) { + // We couldn't read the user preference, so fall back on the default. + return nsIBrowserDOMWindow::OPEN_NEWTAB; + } + + bool isDisabledOpenNewWindow = + isFullScreen && + Preferences::GetBool("browser.link.open_newwindow.disabled_in_fullscreen"); + + if (isDisabledOpenNewWindow && + (containerPref == nsIBrowserDOMWindow::OPEN_NEWWINDOW)) { + containerPref = nsIBrowserDOMWindow::OPEN_NEWTAB; + } + + if (containerPref != nsIBrowserDOMWindow::OPEN_NEWTAB && + containerPref != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW) { + // Just open a window normally + return nsIBrowserDOMWindow::OPEN_NEWWINDOW; + } + + if (aCalledFromJS) { + /* Now check our restriction pref. The restriction pref is a power-user's + fine-tuning pref. values: + 0: no restrictions - divert everything + 1: don't divert window.open at all + 2: don't divert window.open with features + */ + int32_t restrictionPref = + Preferences::GetInt("browser.link.open_newwindow.restriction", 2); + if (restrictionPref < 0 || restrictionPref > 2) { + restrictionPref = 2; // Sane default behavior + } + + if (isDisabledOpenNewWindow) { + // In browser fullscreen, the window should be opened + // in the current window with no features (see bug 803675) + restrictionPref = 0; + } + + if (restrictionPref == 1) { + return nsIBrowserDOMWindow::OPEN_NEWWINDOW; + } + + if (restrictionPref == 2) { + // Only continue if there are no size/position features and no special + // chrome flags - with the exception of the remoteness and private flags, + // which might have been automatically flipped by Gecko. + int32_t uiChromeFlags = aChromeFlags; + uiChromeFlags &= ~(nsIWebBrowserChrome::CHROME_REMOTE_WINDOW | + nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW | + nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW | + nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME); + if (uiChromeFlags != nsIWebBrowserChrome::CHROME_ALL || + aPositionSpecified || aSizeSpecified) { + return nsIBrowserDOMWindow::OPEN_NEWWINDOW; + } + } + } + + return containerPref; +} diff --git a/embedding/components/windowwatcher/nsWindowWatcher.h b/embedding/components/windowwatcher/nsWindowWatcher.h new file mode 100644 index 000000000..f2728bc16 --- /dev/null +++ b/embedding/components/windowwatcher/nsWindowWatcher.h @@ -0,0 +1,156 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsWindowWatcher_h__ +#define __nsWindowWatcher_h__ + +// {a21bfa01-f349-4394-a84c-8de5cf0737d0} +#define NS_WINDOWWATCHER_CID \ + {0xa21bfa01, 0xf349, 0x4394, {0xa8, 0x4c, 0x8d, 0xe5, 0xcf, 0x7, 0x37, 0xd0}} + +#include "nsCOMPtr.h" +#include "mozilla/Mutex.h" +#include "mozilla/Maybe.h" +#include "nsIWindowCreator.h" // for stupid compilers +#include "nsIWindowWatcher.h" +#include "nsIPromptFactory.h" +#include "nsITabParent.h" +#include "nsPIWindowWatcher.h" +#include "nsTArray.h" + +class nsIURI; +class nsIDocShellTreeItem; +class nsIDocShellTreeOwner; +class nsPIDOMWindowOuter; +class nsWatcherWindowEnumerator; +class nsPromptService; +struct nsWatcherWindowEntry; +struct SizeSpec; + +class nsWindowWatcher + : public nsIWindowWatcher + , public nsPIWindowWatcher + , public nsIPromptFactory +{ + friend class nsWatcherWindowEnumerator; + +public: + nsWindowWatcher(); + + nsresult Init(); + + NS_DECL_ISUPPORTS + + NS_DECL_NSIWINDOWWATCHER + NS_DECL_NSPIWINDOWWATCHER + NS_DECL_NSIPROMPTFACTORY + + static int32_t GetWindowOpenLocation(nsPIDOMWindowOuter* aParent, + uint32_t aChromeFlags, + bool aCalledFromJS, + bool aPositionSpecified, + bool aSizeSpecified); + +protected: + virtual ~nsWindowWatcher(); + + friend class nsPromptService; + bool AddEnumerator(nsWatcherWindowEnumerator* aEnumerator); + bool RemoveEnumerator(nsWatcherWindowEnumerator* aEnumerator); + + nsWatcherWindowEntry* FindWindowEntry(mozIDOMWindowProxy* aWindow); + nsresult RemoveWindow(nsWatcherWindowEntry* aInfo); + + // Get the caller tree item. Look on the JS stack, then fall back + // to the parent if there's nothing there. + already_AddRefed<nsIDocShellTreeItem> GetCallerTreeItem( + nsIDocShellTreeItem* aParentItem); + + // Unlike GetWindowByName this will look for a caller on the JS + // stack, and then fall back on aCurrentWindow if it can't find one. + // It also knows to not look for things if aForceNoOpener is set. + nsPIDOMWindowOuter* SafeGetWindowByName(const nsAString& aName, + bool aForceNoOpener, + mozIDOMWindowProxy* aCurrentWindow); + + // Just like OpenWindowJS, but knows whether it got called via OpenWindowJS + // (which means called from script) or called via OpenWindow. + nsresult OpenWindowInternal(mozIDOMWindowProxy* aParent, + const char* aUrl, + const char* aName, + const char* aFeatures, + bool aCalledFromJS, + bool aDialog, + bool aNavigate, + nsIArray* aArgv, + bool aIsPopupSpam, + bool aForceNoOpener, + nsIDocShellLoadInfo* aLoadInfo, + mozIDOMWindowProxy** aResult); + + static nsresult URIfromURL(const char* aURL, + mozIDOMWindowProxy* aParent, + nsIURI** aURI); + + static uint32_t CalculateChromeFlagsForChild(const nsACString& aFeaturesStr); + + static uint32_t CalculateChromeFlagsForParent(mozIDOMWindowProxy* aParent, + const nsACString& aFeaturesStr, + bool aDialog, + bool aChromeURL, + bool aHasChromeParent, + bool aCalledFromJS); + + static int32_t WinHasOption(const nsACString& aOptions, const char* aName, + int32_t aDefault, bool* aPresenceFlag); + /* Compute the right SizeSpec based on aFeatures */ + static void CalcSizeSpec(const nsACString& aFeatures, SizeSpec& aResult); + static nsresult ReadyOpenedDocShellItem(nsIDocShellTreeItem* aOpenedItem, + nsPIDOMWindowOuter* aParent, + bool aWindowIsNew, + bool aForceNoOpener, + mozIDOMWindowProxy** aOpenedWindow); + static void SizeOpenedWindow(nsIDocShellTreeOwner* aTreeOwner, + mozIDOMWindowProxy* aParent, + bool aIsCallerChrome, + const SizeSpec& aSizeSpec, + mozilla::Maybe<float> aOpenerFullZoom = + mozilla::Nothing()); + static void GetWindowTreeItem(mozIDOMWindowProxy* aWindow, + nsIDocShellTreeItem** aResult); + static void GetWindowTreeOwner(nsPIDOMWindowOuter* aWindow, + nsIDocShellTreeOwner** aResult); + +private: + nsresult CreateChromeWindow(const nsACString& aFeatures, + nsIWebBrowserChrome* aParentChrome, + uint32_t aChromeFlags, + uint32_t aContextFlags, + nsITabParent* aOpeningTabParent, + mozIDOMWindowProxy* aOpener, + nsIWebBrowserChrome** aResult); + + void MaybeDisablePersistence(const nsACString& aFeatures, + nsIDocShellTreeOwner* aTreeOwner); + + static uint32_t CalculateChromeFlagsHelper(uint32_t aInitialFlags, + const nsACString& aFeatures, + bool &presenceFlag, + bool aDialog = false, + bool aHasChromeParent = false, + bool aChromeURL = false); + static uint32_t EnsureFlagsSafeForContent(uint32_t aChromeFlags, + bool aChromeURL = false); + +protected: + nsTArray<nsWatcherWindowEnumerator*> mEnumeratorList; + nsWatcherWindowEntry* mOldestWindow; + mozilla::Mutex mListLock; + + nsCOMPtr<nsIWindowCreator> mWindowCreator; +}; + +#endif diff --git a/embedding/components/windowwatcher/test/browser.ini b/embedding/components/windowwatcher/test/browser.ini new file mode 100644 index 000000000..925b96e34 --- /dev/null +++ b/embedding/components/windowwatcher/test/browser.ini @@ -0,0 +1,9 @@ +[DEFAULT] +tags = openwindow + +[browser_new_content_window_chromeflags.js] +[browser_new_remote_window_flags.js] +run-if = e10s +[browser_new_content_window_from_chrome_principal.js] +[browser_new_sized_window.js] +skip-if = os == 'win' # Bug 1276802 - Opening windows from content on Windows might not get the size right diff --git a/embedding/components/windowwatcher/test/browser_new_content_window_chromeflags.js b/embedding/components/windowwatcher/test/browser_new_content_window_chromeflags.js new file mode 100644 index 000000000..887f86152 --- /dev/null +++ b/embedding/components/windowwatcher/test/browser_new_content_window_chromeflags.js @@ -0,0 +1,278 @@ +/** + * Tests that chromeFlags are set properly on windows that are + * being opened from content. + */ + +// The following features set chrome flags on new windows and are +// supported by web content. The schema for each property on this +// object is as follows: +// +// <feature string>: { +// flag: <associated nsIWebBrowserChrome flag>, +// defaults_to: <what this feature defaults to normally> +// } +const ALLOWED = { + "toolbar": { + flag: Ci.nsIWebBrowserChrome.CHROME_TOOLBAR, + defaults_to: true, + }, + "personalbar": { + flag: Ci.nsIWebBrowserChrome.CHROME_PERSONAL_TOOLBAR, + defaults_to: true, + }, + "menubar": { + flag: Ci.nsIWebBrowserChrome.CHROME_MENUBAR, + defaults_to: true, + }, + "scrollbars": { + flag: Ci.nsIWebBrowserChrome.CHROME_SCROLLBARS, + defaults_to: false, + }, + "minimizable": { + flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_MIN, + defaults_to: true, + }, +}; + +// Construct a features string that flips all ALLOWED features +// to not be their defaults. +const ALLOWED_STRING = Object.keys(ALLOWED).map(feature => { + let toValue = ALLOWED[feature].defaults_to ? "no" : "yes"; + return `${feature}=${toValue}`; +}).join(","); + +// The following are not allowed from web content, at least +// in the default case (since some are disabled by default +// via the dom.disable_window_open_feature pref branch). +const DISALLOWED = { + "location": { + flag: Ci.nsIWebBrowserChrome.CHROME_LOCATIONBAR, + defaults_to: true, + }, + "chrome": { + flag: Ci.nsIWebBrowserChrome.CHROME_OPENAS_CHROME, + defaults_to: false, + }, + "dialog": { + flag: Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG, + defaults_to: false, + }, + "private": { + flag: Ci.nsIWebBrowserChrome.CHROME_PRIVATE_WINDOW, + defaults_to: false, + }, + "non-private": { + flag: Ci.nsIWebBrowserChrome.CHROME_NON_PRIVATE_WINDOW, + defaults_to: false, + }, + // "all": + // checked manually, since this is an aggregate + // flag. + // + // "remote": + // checked manually, since its default value will + // depend on whether or not e10s is enabled by default. + "popup": { + flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_POPUP, + defaults_to: false, + }, + "alwaysLowered": { + flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_LOWERED, + defaults_to: false, + }, + "z-lock": { + flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_LOWERED, // Renamed to alwaysLowered + defaults_to: false, + }, + "alwaysRaised": { + flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_RAISED, + defaults_to: false, + }, + "macsuppressanimation": { + flag: Ci.nsIWebBrowserChrome.CHROME_MAC_SUPPRESS_ANIMATION, + defaults_to: false, + }, + "extrachrome": { + flag: Ci.nsIWebBrowserChrome.CHROME_EXTRA, + defaults_to: false, + }, + "centerscreen": { + flag: Ci.nsIWebBrowserChrome.CHROME_CENTER_SCREEN, + defaults_to: false, + }, + "dependent": { + flag: Ci.nsIWebBrowserChrome.CHROME_DEPENDENT, + defaults_to: false, + }, + "modal": { + flag: Ci.nsIWebBrowserChrome.CHROME_MODAL, + defaults_to: false, + }, + "titlebar": { + flag: Ci.nsIWebBrowserChrome.CHROME_TITLEBAR, + defaults_to: true, + }, + "close": { + flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_CLOSE, + defaults_to: true, + }, + "resizable": { + flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_RESIZE, + defaults_to: true, + }, + "status": { + flag: Ci.nsIWebBrowserChrome.CHROME_STATUSBAR, + defaults_to: true, + }, +}; + +// Construct a features string that flips all DISALLOWED features +// to not be their defaults. +const DISALLOWED_STRING = Object.keys(DISALLOWED).map(feature => { + let toValue = DISALLOWED[feature].defaults_to ? "no" : "yes"; + return `${feature}=${toValue}`; +}).join(","); + +const FEATURES = [ALLOWED_STRING, DISALLOWED_STRING].join(","); + +const SCRIPT_PAGE = `data:text/html,<script>window.open("about:blank", "_blank", "${FEATURES}");</script>`; +const SCRIPT_PAGE_FOR_CHROME_ALL = `data:text/html,<script>window.open("about:blank", "_blank", "all");</script>`; + +// This magic value of 2 means that by default, when content tries +// to open a new window, it'll actually open in a new window instead +// of a new tab. +Services.prefs.setIntPref("browser.link.open_newwindow", 2); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("browser.link.open_newwindow"); +}); + +/** + * Given some nsIDOMWindow for a window running in the parent + * process, return the nsIWebBrowserChrome chrome flags for + * the associated XUL window. + * + * @param win (nsIDOMWindow) + * Some window in the parent process. + * @returns int + */ +function getParentChromeFlags(win) { + return win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .treeOwner + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIXULWindow) + .chromeFlags; +} + +/** + * For some chromeFlags, ensures that flags that are in the + * ALLOWED group were modified, and that flags in the DISALLOWED + * group were not modified. + * + * @param chromeFlags (int) + * Some chromeFlags to check. + */ +function assertContentFlags(chromeFlags) { + for (let feature in ALLOWED) { + let flag = ALLOWED[feature].flag; + + if (ALLOWED[feature].defaults_to) { + // The feature is supposed to default to true, so we should + // have been able to flip it off. + Assert.ok(!(chromeFlags & flag), + `Expected feature ${feature} to be disabled`); + } else { + // The feature is supposed to default to false, so we should + // have been able to flip it on. + Assert.ok((chromeFlags & flag), + `Expected feature ${feature} to be enabled`); + } + } + + for (let feature in DISALLOWED) { + let flag = DISALLOWED[feature].flag; + if (DISALLOWED[feature].defaults_to) { + // The feature is supposed to default to true, so it should + // stay true. + Assert.ok((chromeFlags & flag), + `Expected feature ${feature} to be unchanged`); + } else { + // The feature is supposed to default to false, so it should + // stay false. + Assert.ok(!(chromeFlags & flag), + `Expected feature ${feature} to be unchanged`); + } + } +} + +/** + * Opens a window from content using window.open with the + * features computed from ALLOWED and DISALLOWED. The computed + * feature string attempts to flip every feature away from their + * default. + */ +add_task(function* test_new_remote_window_flags() { + let newWinPromise = BrowserTestUtils.waitForNewWindow(); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: SCRIPT_PAGE, + }, function*(browser) { + let win = yield newWinPromise; + let parentChromeFlags = getParentChromeFlags(win); + assertContentFlags(parentChromeFlags); + + if (win.gMultiProcessBrowser) { + Assert.ok(parentChromeFlags & + Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW, + "Should be remote by default"); + } else { + Assert.ok(!(parentChromeFlags & + Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW), + "Should not be remote by default"); + } + + // Confusingly, chromeFlags also exist in the content process + // as part of the TabChild, so we have to check those too. + let b = win.gBrowser.selectedBrowser; + let contentChromeFlags = yield ContentTask.spawn(b, null, function*() { + docShell.QueryInterface(Ci.nsIInterfaceRequestor); + try { + // This will throw if we're not a remote browser. + return docShell.getInterface(Ci.nsITabChild) + .QueryInterface(Ci.nsIWebBrowserChrome) + .chromeFlags; + } catch(e) { + // This must be a non-remote browser... + return docShell.QueryInterface(Ci.nsIDocShellTreeItem) + .treeOwner + .QueryInterface(Ci.nsIWebBrowserChrome) + .chromeFlags; + } + }); + + assertContentFlags(contentChromeFlags); + Assert.ok(!(contentChromeFlags & + Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW), + "Should not be remote in the content process."); + + yield BrowserTestUtils.closeWindow(win); + }); + + // We check "all" manually, since that's an aggregate flag + // and doesn't fit nicely into the ALLOWED / DISALLOWED scheme + newWinPromise = BrowserTestUtils.waitForNewWindow(); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: SCRIPT_PAGE_FOR_CHROME_ALL, + }, function*(browser) { + let win = yield newWinPromise; + let parentChromeFlags = getParentChromeFlags(win); + Assert.notEqual((parentChromeFlags & Ci.nsIWebBrowserChrome.CHROME_ALL), + Ci.nsIWebBrowserChrome.CHROME_ALL, + "Should not have been able to set CHROME_ALL"); + yield BrowserTestUtils.closeWindow(win); + }); +}); diff --git a/embedding/components/windowwatcher/test/browser_new_content_window_from_chrome_principal.js b/embedding/components/windowwatcher/test/browser_new_content_window_from_chrome_principal.js new file mode 100644 index 000000000..c8a24d43e --- /dev/null +++ b/embedding/components/windowwatcher/test/browser_new_content_window_from_chrome_principal.js @@ -0,0 +1,34 @@ +"use strict"; + +/** + * Tests that if chrome-privileged code calls .open() on an + * unprivileged window, that the principal in the newly + * opened window is appropriately set. + */ +add_task(function* test_chrome_opens_window() { + // This magic value of 2 means that by default, when content tries + // to open a new window, it'll actually open in a new window instead + // of a new tab. + yield SpecialPowers.pushPrefEnv({"set": [ + ["browser.link.open_newwindow", 2], + ]}); + + let newWinPromise = BrowserTestUtils.waitForNewWindow(true, "http://example.com/"); + + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + content.open("http://example.com/", "_blank"); + }); + + let win = yield newWinPromise; + let browser = win.gBrowser.selectedBrowser; + + yield ContentTask.spawn(browser, null, function*() { + Assert.ok(!content.document.nodePrincipal.isSystemPrincipal, + "We should not have a system principal.") + Assert.equal(content.document.nodePrincipal.origin, + "http://example.com", + "Should have the example.com principal"); + }); + + yield BrowserTestUtils.closeWindow(win); +});
\ No newline at end of file diff --git a/embedding/components/windowwatcher/test/browser_new_remote_window_flags.js b/embedding/components/windowwatcher/test/browser_new_remote_window_flags.js new file mode 100644 index 000000000..e4f3d1ddf --- /dev/null +++ b/embedding/components/windowwatcher/test/browser_new_remote_window_flags.js @@ -0,0 +1,78 @@ +/** + * Tests that when a remote browser opens a new window that the + * newly opened window is also remote. + */ + +const ANCHOR_PAGE = `data:text/html,<a href="about:blank" target="_blank">Click me!</a>`; +const SCRIPT_PAGE = `data:text/html,<script>window.open("about:blank", "_blank");</script>`; + +// This magic value of 2 means that by default, when content tries +// to open a new window, it'll actually open in a new window instead +// of a new tab. +add_task(function* setup() { + yield SpecialPowers.pushPrefEnv({"set": [ + ["browser.link.open_newwindow", 2], + ]}); +}); + +function assertFlags(win) { + let webNav = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation); + let loadContext = webNav.QueryInterface(Ci.nsILoadContext); + let chromeFlags = webNav.QueryInterface(Ci.nsIDocShellTreeItem) + .treeOwner + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIXULWindow) + .chromeFlags; + Assert.ok(loadContext.useRemoteTabs, + "Should be using remote tabs on the load context"); + Assert.ok(chromeFlags & Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW, + "Should have the remoteness chrome flag on the window"); +} + +/** + * Content can open a window using a target="_blank" link + */ +add_task(function* test_new_remote_window_flags_target_blank() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: ANCHOR_PAGE, + }, function*(browser) { + let newWinPromise = BrowserTestUtils.waitForNewWindow(); + yield BrowserTestUtils.synthesizeMouseAtCenter("a", {}, browser); + let win = yield newWinPromise; + assertFlags(win); + yield BrowserTestUtils.closeWindow(win); + }); +}); + +/** + * Content can open a window using window.open + */ +add_task(function* test_new_remote_window_flags_window_open() { + let newWinPromise = BrowserTestUtils.waitForNewWindow(); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: SCRIPT_PAGE, + }, function*(browser) { + let win = yield newWinPromise; + assertFlags(win); + yield BrowserTestUtils.closeWindow(win); + }); +}); + +/** + * Privileged content scripts can also open new windows + * using content.open. + */ +add_task(function* test_new_remote_window_flags_content_open() { + let newWinPromise = BrowserTestUtils.waitForNewWindow(); + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + content.open("about:blank", "_blank"); + }); + + let win = yield newWinPromise; + assertFlags(win); + yield BrowserTestUtils.closeWindow(win); +}); diff --git a/embedding/components/windowwatcher/test/browser_new_sized_window.js b/embedding/components/windowwatcher/test/browser_new_sized_window.js new file mode 100644 index 000000000..f4b1890b1 --- /dev/null +++ b/embedding/components/windowwatcher/test/browser_new_sized_window.js @@ -0,0 +1,67 @@ +"use strict"; + +/** + * Tests that content can open windows at requested dimensions + * of height and width. + */ + +/** + * This utility function does most of the actual testing. We + * construct a feature string suitable for the passed in width + * and height, and then run that script in content to open the + * new window. When the new window comes up, this function tests + * to ensure that the content area of the initial browser is the + * requested dimensions. Finally, we also ensure that we're not + * persisting the position, size or sizemode of the new browser + * window. + */ +function test_dimensions({ width, height}) { + let features = []; + if (width) { + features.push(`width=${width}`); + } + if (height) { + features.push(`height=${height}`); + } + const FEATURE_STR = features.join(","); + const SCRIPT_PAGE = `data:text/html,<script>window.open("about:blank", "_blank", "${FEATURE_STR}");</script>`; + + let newWinPromise = BrowserTestUtils.waitForNewWindow(); + + return BrowserTestUtils.withNewTab({ + gBrowser, + url: SCRIPT_PAGE, + }, function*(browser) { + let win = yield newWinPromise; + let rect = win.gBrowser.selectedBrowser.getBoundingClientRect(); + + if (width) { + Assert.equal(rect.width, width, "Should have the requested width"); + } + + if (height) { + Assert.equal(rect.height, height, "Should have the requested height"); + } + + let treeOwner = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIDocShellTreeItem) + .treeOwner; + let persistPosition = {}; + let persistSize = {}; + let persistSizeMode = {}; + treeOwner.getPersistence(persistPosition, persistSize, persistSizeMode); + + Assert.ok(!persistPosition.value, "Should not persist position"); + Assert.ok(!persistSize.value, "Should not persist size"); + Assert.ok(!persistSizeMode.value, "Should not persist size mode"); + + yield BrowserTestUtils.closeWindow(win); + }); +} + +add_task(function* test_new_sized_window() { + yield test_dimensions({ width: 100 }); + yield test_dimensions({ height: 150 }); + yield test_dimensions({ width: 300, height: 200 }); +}); diff --git a/embedding/components/windowwatcher/test/chrome.ini b/embedding/components/windowwatcher/test/chrome.ini new file mode 100644 index 000000000..5f3b49433 --- /dev/null +++ b/embedding/components/windowwatcher/test/chrome.ini @@ -0,0 +1,7 @@ +[DEFAULT] +tags = openwindow + +[test_dialog_arguments.html] +support-files = + file_test_dialog.html +[test_modal_windows.html] diff --git a/embedding/components/windowwatcher/test/file_storage_copied.html b/embedding/components/windowwatcher/test/file_storage_copied.html new file mode 100644 index 000000000..250c7891a --- /dev/null +++ b/embedding/components/windowwatcher/test/file_storage_copied.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<html> +<!-- +This page is opened in a new window by test_storage_copied.html. +We need to return the sessionStorage value for the item "test-item", +by way of postMessage. +--> +<head> +<body>Opened!</body> +<script> + window.postMessage(window.sessionStorage.getItem("test-item"), "*"); +</script> +</html>
\ No newline at end of file diff --git a/embedding/components/windowwatcher/test/file_test_dialog.html b/embedding/components/windowwatcher/test/file_test_dialog.html new file mode 100644 index 000000000..b323ba526 --- /dev/null +++ b/embedding/components/windowwatcher/test/file_test_dialog.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<!-- +This page is opened in a new window by test_dialog_arguments. It is +a dialog which expects a Symbol to be passed in the dialog arguments. +Once we load, we call back into the opener with the argument we were +passed. +--> +<head> +<body>Opened!</body> +<script> + window.opener.done(window.arguments[0]); +</script> +</html>
\ No newline at end of file diff --git a/embedding/components/windowwatcher/test/mochitest.ini b/embedding/components/windowwatcher/test/mochitest.ini new file mode 100644 index 000000000..42955d496 --- /dev/null +++ b/embedding/components/windowwatcher/test/mochitest.ini @@ -0,0 +1,12 @@ +[DEFAULT] +tags = openwindow + +[test_blank_named_window.html] +skip-if = (os == 'android') # Fennec doesn't support web content opening new windows (See bug 1277544 for details) +[test_named_window.html] +skip-if = (os == 'android') # Fennec doesn't support web content opening new windows (See bug 1277544 for details) +[test_storage_copied.html] +support-files = + file_storage_copied.html +skip-if = (os == 'android') # Fennec doesn't support web content opening new windows (See bug 1277544 for details) + diff --git a/embedding/components/windowwatcher/test/moz.build b/embedding/components/windowwatcher/test/moz.build new file mode 100644 index 000000000..bd6f51aff --- /dev/null +++ b/embedding/components/windowwatcher/test/moz.build @@ -0,0 +1,18 @@ +# -*- 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/. + +BROWSER_CHROME_MANIFESTS += [ + 'browser.ini', +] + +MOCHITEST_MANIFESTS += [ + 'mochitest.ini', +] + +MOCHITEST_CHROME_MANIFESTS += [ + 'chrome.ini', +] + diff --git a/embedding/components/windowwatcher/test/test_blank_named_window.html b/embedding/components/windowwatcher/test/test_blank_named_window.html new file mode 100644 index 000000000..b45ad1f70 --- /dev/null +++ b/embedding/components/windowwatcher/test/test_blank_named_window.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test that when opening a window with the reserved name _blank that the new +window does not get that name, and that subsequent window openings with that +name result in new windows being opened. +--> +<head> + <meta charset="utf-8"> + <title>Test named windows</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script src="head.js" type="application/javascript;version=1.8"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <script type="application/javascript"> + "use strict"; + add_task(function*() { + // This magic value of 2 means that by default, when content tries + // to open a new window, it'll actually open in a new window instead + // of a new tab. + yield SpecialPowers.pushPrefEnv({"set": [ + ["browser.link.open_newwindow", 2], + ]}); + + let win1 = window.open("data:text/html,<p>This is window 1 for test_blank_named_window.html</p>", "_blank"); + + let name = SpecialPowers.wrap(win1) + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIWebNavigation) + .QueryInterface(SpecialPowers.Ci.nsIDocShellTreeItem) + .name; + + is(name, "", "Should have no name"); + + let win2 = window.open("data:text/html,<p>This is window 2 for test_blank_named_window.html</p>", "_blank"); + isnot(win1, win2, "Should not have gotten back the same window"); + + win1.close(); + win2.close(); + }); + </script> +</body> +</html>
\ No newline at end of file diff --git a/embedding/components/windowwatcher/test/test_dialog_arguments.html b/embedding/components/windowwatcher/test/test_dialog_arguments.html new file mode 100644 index 000000000..7cde69401 --- /dev/null +++ b/embedding/components/windowwatcher/test/test_dialog_arguments.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test that arguments can be passed to dialogs. +--> +<head> + <meta charset="utf-8"> + <title>Test a modal window</title> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + + <script type="application/javascript;version=1.8"> + const {utils: Cu, interfaces: Ci} = Components; + + Cu.import("resource://gre/modules/Services.jsm"); + + const TEST_ITEM = Symbol("test-item"); + + function done(returnedItem) { + is(returnedItem, TEST_ITEM, + "Dialog should have received test item"); + win.close(); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + let win = window.openDialog("file_test_dialog.html", "_blank", "width=100,height=100", TEST_ITEM); + </script> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> +</html>
\ No newline at end of file diff --git a/embedding/components/windowwatcher/test/test_modal_windows.html b/embedding/components/windowwatcher/test/test_modal_windows.html new file mode 100644 index 000000000..8a2c03be0 --- /dev/null +++ b/embedding/components/windowwatcher/test/test_modal_windows.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test that the parent can open modal windows, and that the modal window +that is opened reports itself as being modal. +--> +<head> + <meta charset="utf-8"> + <title>Test a modal window</title> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + + <script type="application/javascript;version=1.8"> + const {utils: Cu, interfaces: Ci} = Components; + + Cu.import("resource://gre/modules/Services.jsm"); + Cu.import("resource://testing-common/BrowserTestUtils.jsm"); + + add_task(function*() { + BrowserTestUtils.domWindowOpened().then((win) => { + let treeOwner = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .treeOwner + let chromeFlags = treeOwner.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIXULWindow) + .chromeFlags; + ok(chromeFlags & Ci.nsIWebBrowserChrome.CHROME_MODAL, + "Should have the modal chrome flag"); + + let wbc = treeOwner.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebBrowserChrome); + ok(wbc.isWindowModal(), "Should report as modal"); + + win.close(); + }); + + let modal = window.openDialog("data:text/html,<p>This is a modal window for test_modal_windows.html</p>", + "_blank", "modal", null); + // Since the modal runs a nested event loop, just to be on the safe side, + // we'll wait a tick of the main event loop before resolving the task. + yield new Promise(resolve => setTimeout(resolve, 0)); + }); + + </script> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> +</html>
\ No newline at end of file diff --git a/embedding/components/windowwatcher/test/test_named_window.html b/embedding/components/windowwatcher/test/test_named_window.html new file mode 100644 index 000000000..58bd8a9cf --- /dev/null +++ b/embedding/components/windowwatcher/test/test_named_window.html @@ -0,0 +1,92 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test that when content opens a new window with a name, that the +newly opened window actually gets that name, and that subsequent +attempts to open a window with that name will target the same +window. +--> +<head> + <meta charset="utf-8"> + <title>Test named windows</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script src="head.js" type="application/javascript;version=1.8"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <a href="#" id="link">Click me</a> + + <script type="application/javascript"> + "use strict"; + + const NAME = "my_window"; + const TARGET_URL = "data:text/html,<html><body>test_named_window.html new window</body></html>"; + const TARGET_URL_2 = TARGET_URL + "#2"; + const TARGET_URL_3 = TARGET_URL + "#3"; + + /** + * Returns a Promise that resolves once some target has had + * some event dispatched on it. + * + * @param target + * The thing to wait for the event to be dispatched + * through. + * @param eventName + * The name of the event to wait for. + * @returns Promise + */ + function promiseEvent(target, eventName) { + return new Promise(resolve => { + target.addEventListener(eventName, function onEvent(e) { + target.removeEventListener(eventName, onEvent, true); + resolve(e); + }, true); + }); + } + + add_task(function*() { + // This magic value of 2 means that by default, when content tries + // to open a new window, it'll actually open in a new window instead + // of a new tab. + yield SpecialPowers.pushPrefEnv({"set": [ + ["browser.link.open_newwindow", 2], + ]}); + + let win1 = window.open(TARGET_URL, "my_window"); + yield promiseEvent(win1, "load"); + + let name = SpecialPowers.wrap(win1) + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIWebNavigation) + .QueryInterface(SpecialPowers.Ci.nsIDocShellTreeItem) + .name; + + is(name, NAME, "Should have the expected name"); + is(win1.location.href, new URL(TARGET_URL).href, + "Should have loaded target TARGET_URL in the original window"); + + let hashChange = promiseEvent(win1, "hashchange"); + let win2 = window.open(TARGET_URL_2, "my_window"); + yield hashChange; + + is(win1, win2, "Should have gotten back the same window"); + is(win1.location.href, new URL(TARGET_URL_2).href, + "Should have re-targeted pre-existing window"); + + hashChange = promiseEvent(win1, "hashchange"); + let link = document.getElementById("link"); + link.setAttribute("target", NAME); + link.setAttribute("href", TARGET_URL_3); + link.click(); + + yield hashChange; + + is(win1.location.href, new URL(TARGET_URL_3).href, + "Should have re-targeted pre-existing window"); + + win1.close(); + }); + </script> +</body> +</html>
\ No newline at end of file diff --git a/embedding/components/windowwatcher/test/test_storage_copied.html b/embedding/components/windowwatcher/test/test_storage_copied.html new file mode 100644 index 000000000..27aeb51a9 --- /dev/null +++ b/embedding/components/windowwatcher/test/test_storage_copied.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test sessionStorage is copied over when a new window opens to the +same domain as the opener. +--> +<head> + <meta charset="utf-8"> + <title>Test storage copied</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script src="head.js" type="application/javascript;version=1.8"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <script type="application/javascript"> + "use strict"; + + function waitForMessage(win) { + return new Promise(resolve => { + win.addEventListener("message", function onMessage(event) { + win.removeEventListener("message", onMessage); + resolve(event.data); + }); + }); + } + + add_task(function*() { + const TEST_VALUE = "test-value"; + // This magic value of 2 means that by default, when content tries + // to open a new window, it'll actually open in a new window instead + // of a new tab. + yield SpecialPowers.pushPrefEnv({"set": [ + ["browser.link.open_newwindow", 2], + ]}); + + window.sessionStorage.setItem("test-item", TEST_VALUE); + let win = window.open("file_storage_copied.html", "my_window"); + let data = yield waitForMessage(win); + is(data, TEST_VALUE, "Should have cloned the test value"); + win.close(); + }); + </script> +</body> +</html>
\ No newline at end of file |