summaryrefslogtreecommitdiffstats
path: root/embedding/components
diff options
context:
space:
mode:
Diffstat (limited to 'embedding/components')
-rw-r--r--embedding/components/appstartup/moz.build15
-rw-r--r--embedding/components/appstartup/nsAppStartupNotifier.cpp90
-rw-r--r--embedding/components/appstartup/nsAppStartupNotifier.h30
-rw-r--r--embedding/components/appstartup/nsIAppStartupNotifier.h59
-rw-r--r--embedding/components/build/moz.build38
-rw-r--r--embedding/components/build/nsEmbeddingModule.cpp120
-rw-r--r--embedding/components/commandhandler/moz.build26
-rw-r--r--embedding/components/commandhandler/nsBaseCommandController.cpp184
-rw-r--r--embedding/components/commandhandler/nsBaseCommandController.h49
-rw-r--r--embedding/components/commandhandler/nsCommandGroup.cpp296
-rw-r--r--embedding/components/commandhandler/nsCommandGroup.h44
-rw-r--r--embedding/components/commandhandler/nsCommandManager.cpp262
-rw-r--r--embedding/components/commandhandler/nsCommandManager.h50
-rw-r--r--embedding/components/commandhandler/nsCommandParams.cpp264
-rw-r--r--embedding/components/commandhandler/nsCommandParams.h131
-rw-r--r--embedding/components/commandhandler/nsControllerCommandTable.cpp209
-rw-r--r--embedding/components/commandhandler/nsControllerCommandTable.h36
-rw-r--r--embedding/components/commandhandler/nsICommandManager.idl118
-rw-r--r--embedding/components/commandhandler/nsICommandParams.idl85
-rw-r--r--embedding/components/commandhandler/nsIControllerCommand.idl51
-rw-r--r--embedding/components/commandhandler/nsIControllerCommandTable.idl100
-rw-r--r--embedding/components/commandhandler/nsIControllerContext.idl35
-rw-r--r--embedding/components/commandhandler/nsPICommandUpdater.idl39
-rw-r--r--embedding/components/find/moz.build19
-rw-r--r--embedding/components/find/nsFind.cpp1383
-rw-r--r--embedding/components/find/nsFind.h83
-rw-r--r--embedding/components/find/nsIFind.idl34
-rw-r--r--embedding/components/find/nsIWebBrowserFind.idl145
-rw-r--r--embedding/components/find/nsWebBrowserFind.cpp868
-rw-r--r--embedding/components/find/nsWebBrowserFind.h95
-rw-r--r--embedding/components/moz.build20
-rw-r--r--embedding/components/printingui/ipc/PPrintProgressDialog.ipdl35
-rw-r--r--embedding/components/printingui/ipc/PPrintSettingsDialog.ipdl29
-rw-r--r--embedding/components/printingui/ipc/PPrinting.ipdl48
-rw-r--r--embedding/components/printingui/ipc/PPrintingTypes.ipdlh126
-rw-r--r--embedding/components/printingui/ipc/PrintDataUtils.cpp158
-rw-r--r--embedding/components/printingui/ipc/PrintDataUtils.h39
-rw-r--r--embedding/components/printingui/ipc/PrintProgressDialogChild.cpp132
-rw-r--r--embedding/components/printingui/ipc/PrintProgressDialogChild.h40
-rw-r--r--embedding/components/printingui/ipc/PrintProgressDialogParent.cpp113
-rw-r--r--embedding/components/printingui/ipc/PrintProgressDialogParent.h64
-rw-r--r--embedding/components/printingui/ipc/PrintSettingsDialogChild.cpp38
-rw-r--r--embedding/components/printingui/ipc/PrintSettingsDialogChild.h35
-rw-r--r--embedding/components/printingui/ipc/PrintSettingsDialogParent.cpp28
-rw-r--r--embedding/components/printingui/ipc/PrintSettingsDialogParent.h29
-rw-r--r--embedding/components/printingui/ipc/PrintingParent.cpp336
-rw-r--r--embedding/components/printingui/ipc/PrintingParent.h109
-rw-r--r--embedding/components/printingui/ipc/moz.build35
-rw-r--r--embedding/components/printingui/ipc/nsPrintingProxy.cpp269
-rw-r--r--embedding/components/printingui/ipc/nsPrintingProxy.h57
-rw-r--r--embedding/components/printingui/mac/moz.build16
-rw-r--r--embedding/components/printingui/mac/nsPrintProgress.cpp213
-rw-r--r--embedding/components/printingui/mac/nsPrintProgress.h44
-rw-r--r--embedding/components/printingui/mac/nsPrintProgressParams.cpp47
-rw-r--r--embedding/components/printingui/mac/nsPrintProgressParams.h28
-rw-r--r--embedding/components/printingui/mac/nsPrintingPromptService.h43
-rw-r--r--embedding/components/printingui/mac/nsPrintingPromptServiceX.mm128
-rw-r--r--embedding/components/printingui/moz.build17
-rw-r--r--embedding/components/printingui/unixshared/moz.build13
-rw-r--r--embedding/components/printingui/unixshared/nsPrintProgress.cpp267
-rw-r--r--embedding/components/printingui/unixshared/nsPrintProgress.h46
-rw-r--r--embedding/components/printingui/unixshared/nsPrintProgressParams.cpp47
-rw-r--r--embedding/components/printingui/unixshared/nsPrintProgressParams.h28
-rw-r--r--embedding/components/printingui/unixshared/nsPrintingPromptService.cpp298
-rw-r--r--embedding/components/printingui/unixshared/nsPrintingPromptService.h58
-rw-r--r--embedding/components/printingui/win/moz.build18
-rw-r--r--embedding/components/printingui/win/nsPrintDialogUtil.cpp854
-rw-r--r--embedding/components/printingui/win/nsPrintDialogUtil.h12
-rw-r--r--embedding/components/printingui/win/nsPrintProgress.cpp293
-rw-r--r--embedding/components/printingui/win/nsPrintProgress.h43
-rw-r--r--embedding/components/printingui/win/nsPrintProgressParams.cpp47
-rw-r--r--embedding/components/printingui/win/nsPrintProgressParams.h27
-rw-r--r--embedding/components/printingui/win/nsPrintingPromptService.cpp341
-rw-r--r--embedding/components/printingui/win/nsPrintingPromptService.h58
-rw-r--r--embedding/components/webbrowserpersist/PWebBrowserPersistDocument.ipdl90
-rw-r--r--embedding/components/webbrowserpersist/PWebBrowserPersistResources.ipdl26
-rw-r--r--embedding/components/webbrowserpersist/PWebBrowserPersistSerialize.ipdl29
-rw-r--r--embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.cpp159
-rw-r--r--embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.h62
-rw-r--r--embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.cpp125
-rw-r--r--embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.h79
-rw-r--r--embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp1475
-rw-r--r--embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h51
-rw-r--r--embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp186
-rw-r--r--embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.h55
-rw-r--r--embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp73
-rw-r--r--embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.h31
-rw-r--r--embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.cpp87
-rw-r--r--embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.h54
-rw-r--r--embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.cpp143
-rw-r--r--embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.h39
-rw-r--r--embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.cpp90
-rw-r--r--embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.h49
-rw-r--r--embedding/components/webbrowserpersist/moz.build49
-rw-r--r--embedding/components/webbrowserpersist/nsCWebBrowserPersist.idl15
-rw-r--r--embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl286
-rw-r--r--embedding/components/webbrowserpersist/nsIWebBrowserPersistDocument.idl197
-rw-r--r--embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl41
-rw-r--r--embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp2812
-rw-r--r--embedding/components/webbrowserpersist/nsWebBrowserPersist.h181
-rw-r--r--embedding/components/windowwatcher/moz.build49
-rw-r--r--embedding/components/windowwatcher/nsAutoWindowStateHelper.cpp73
-rw-r--r--embedding/components/windowwatcher/nsAutoWindowStateHelper.h35
-rw-r--r--embedding/components/windowwatcher/nsDialogParamBlock.cpp101
-rw-r--r--embedding/components/windowwatcher/nsDialogParamBlock.h45
-rw-r--r--embedding/components/windowwatcher/nsIDialogParamBlock.idl42
-rw-r--r--embedding/components/windowwatcher/nsIPromptFactory.idl22
-rw-r--r--embedding/components/windowwatcher/nsIPromptService.idl346
-rw-r--r--embedding/components/windowwatcher/nsIPromptService2.idl45
-rw-r--r--embedding/components/windowwatcher/nsIWindowWatcher.idl167
-rw-r--r--embedding/components/windowwatcher/nsPIPromptService.idl31
-rw-r--r--embedding/components/windowwatcher/nsPIWindowWatcher.idl146
-rw-r--r--embedding/components/windowwatcher/nsPromptUtils.h141
-rw-r--r--embedding/components/windowwatcher/nsWindowWatcher.cpp2610
-rw-r--r--embedding/components/windowwatcher/nsWindowWatcher.h156
-rw-r--r--embedding/components/windowwatcher/test/browser.ini9
-rw-r--r--embedding/components/windowwatcher/test/browser_new_content_window_chromeflags.js278
-rw-r--r--embedding/components/windowwatcher/test/browser_new_content_window_from_chrome_principal.js34
-rw-r--r--embedding/components/windowwatcher/test/browser_new_remote_window_flags.js78
-rw-r--r--embedding/components/windowwatcher/test/browser_new_sized_window.js67
-rw-r--r--embedding/components/windowwatcher/test/chrome.ini7
-rw-r--r--embedding/components/windowwatcher/test/file_storage_copied.html13
-rw-r--r--embedding/components/windowwatcher/test/file_test_dialog.html14
-rw-r--r--embedding/components/windowwatcher/test/mochitest.ini12
-rw-r--r--embedding/components/windowwatcher/test/moz.build18
-rw-r--r--embedding/components/windowwatcher/test/test_blank_named_window.html45
-rw-r--r--embedding/components/windowwatcher/test/test_dialog_arguments.html38
-rw-r--r--embedding/components/windowwatcher/test/test_modal_windows.html56
-rw-r--r--embedding/components/windowwatcher/test/test_named_window.html92
-rw-r--r--embedding/components/windowwatcher/test/test_storage_copied.html45
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("&amp;");
+ break;
+ case '<':
+ aBuffer.AppendLiteral("&lt;");
+ break;
+ case '>':
+ aBuffer.AppendLiteral("&gt;");
+ break;
+ case '"':
+ aBuffer.AppendLiteral("&quot;");
+ 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 &lt;pre&gt;), 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 &nbsp; instead of character code 0xa0.
+ * The basic set is just &nbsp; &amp; &lt; &gt; &quot; for interoperability
+ * with older products that don't support &alpha; 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