diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /embedding | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'embedding')
221 files changed, 30272 insertions, 0 deletions
diff --git a/embedding/browser/build/moz.build b/embedding/browser/build/moz.build new file mode 100644 index 000000000..cdd0f5a76 --- /dev/null +++ b/embedding/browser/build/moz.build @@ -0,0 +1,14 @@ +# -*- 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 += [ + 'nsWebBrowserModule.cpp', +] + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '..', +] diff --git a/embedding/browser/build/nsWebBrowserModule.cpp b/embedding/browser/build/nsWebBrowserModule.cpp new file mode 100644 index 000000000..a061a2b43 --- /dev/null +++ b/embedding/browser/build/nsWebBrowserModule.cpp @@ -0,0 +1,53 @@ +/* -*- 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 "mozilla/ModuleUtils.h" +#include "nsIServiceManager.h" +#include "nsXPIDLString.h" + +#include "nsEmbedCID.h" + +#include "nsWebBrowser.h" +#include "nsCommandHandler.h" +#include "nsWebBrowserContentPolicy.h" + +// Factory Constructors + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebBrowser) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebBrowserContentPolicy) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsCommandHandler) + +NS_DEFINE_NAMED_CID(NS_WEBBROWSER_CID); +NS_DEFINE_NAMED_CID(NS_COMMANDHANDLER_CID); +NS_DEFINE_NAMED_CID(NS_WEBBROWSERCONTENTPOLICY_CID); + +static const mozilla::Module::CIDEntry kWebBrowserCIDs[] = { + { &kNS_WEBBROWSER_CID, false, nullptr, nsWebBrowserConstructor }, + { &kNS_COMMANDHANDLER_CID, false, nullptr, nsCommandHandlerConstructor }, + { &kNS_WEBBROWSERCONTENTPOLICY_CID, false, nullptr, nsWebBrowserContentPolicyConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kWebBrowserContracts[] = { + { NS_WEBBROWSER_CONTRACTID, &kNS_WEBBROWSER_CID }, + { NS_COMMANDHANDLER_CONTRACTID, &kNS_COMMANDHANDLER_CID }, + { NS_WEBBROWSERCONTENTPOLICY_CONTRACTID, &kNS_WEBBROWSERCONTENTPOLICY_CID }, + { nullptr } +}; + +static const mozilla::Module::CategoryEntry kWebBrowserCategories[] = { + { "content-policy", NS_WEBBROWSERCONTENTPOLICY_CONTRACTID, NS_WEBBROWSERCONTENTPOLICY_CONTRACTID }, + { nullptr } +}; + +static const mozilla::Module kWebBrowserModule = { + mozilla::Module::kVersion, + kWebBrowserCIDs, + kWebBrowserContracts, + kWebBrowserCategories +}; + +NSMODULE_DEFN(Browser_Embedding_Module) = &kWebBrowserModule; diff --git a/embedding/browser/moz.build b/embedding/browser/moz.build new file mode 100644 index 000000000..179a6b6c8 --- /dev/null +++ b/embedding/browser/moz.build @@ -0,0 +1,57 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += ['build'] + +XPIDL_SOURCES += [ + 'nsCWebBrowser.idl', + 'nsICommandHandler.idl', + 'nsIContextMenuListener.idl', + 'nsIContextMenuListener2.idl', + 'nsIEmbeddingSiteWindow.idl', + 'nsITooltipListener.idl', + 'nsITooltipTextProvider.idl', + 'nsIWebBrowser.idl', + 'nsIWebBrowserChrome.idl', + 'nsIWebBrowserChrome2.idl', + 'nsIWebBrowserChrome3.idl', + 'nsIWebBrowserChromeFocus.idl', + 'nsIWebBrowserFocus.idl', + 'nsIWebBrowserSetup.idl', + 'nsIWebBrowserStream.idl', +] + +if CONFIG['NS_PRINTING']: + XPIDL_SOURCES += [ + 'nsIPrintingPromptService.idl', + 'nsIWebBrowserPrint.idl', + ] + +XPIDL_MODULE = 'webBrowser_core' + +EXPORTS += [ + 'nsCTooltipTextProvider.h', +] + +UNIFIED_SOURCES += [ + 'nsCommandHandler.cpp', + 'nsContextMenuInfo.cpp', + 'nsDocShellTreeOwner.cpp', + 'nsEmbedStream.cpp', + 'nsWebBrowser.cpp', + 'nsWebBrowserContentPolicy.cpp', +] + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '/docshell/base', + '/dom/base', + '/dom/svg', + '/layout/style', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/embedding/browser/nsCTooltipTextProvider.h b/embedding/browser/nsCTooltipTextProvider.h new file mode 100644 index 000000000..95fb00432 --- /dev/null +++ b/embedding/browser/nsCTooltipTextProvider.h @@ -0,0 +1,17 @@ +/* -*- 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 NSCTOOLTIPTEXTPROVIDER_H +#define NSCTOOLTIPTEXTPROVIDER_H + +#include "nsITooltipTextProvider.h" + +#define NS_TOOLTIPTEXTPROVIDER_CONTRACTID \ + "@mozilla.org/embedcomp/tooltiptextprovider;1" +#define NS_DEFAULTTOOLTIPTEXTPROVIDER_CONTRACTID \ + "@mozilla.org/embedcomp/default-tooltiptextprovider;1" + +#endif diff --git a/embedding/browser/nsCWebBrowser.idl b/embedding/browser/nsCWebBrowser.idl new file mode 100644 index 000000000..21927f7c7 --- /dev/null +++ b/embedding/browser/nsCWebBrowser.idl @@ -0,0 +1,34 @@ +/* -*- 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 "nsIWebBrowser.idl" +#include "nsIBaseWindow.idl" +#include "nsIScrollable.idl" +#include "nsITextScroll.idl" + +/* +nsCWebBrowser implements: +------------------------- +nsIWebBrowser +nsIDocShellTreeItem +nsIWebNavigation +nsIWebProgress +nsIBaseWindow +nsIScrollable +nsITextScroll +nsIInterfaceRequestor + + +Outwardly communicates with: +---------------------------- +nsIWebBrowserChrome +nsIBaseWindow +nsIInterfaceRequestor +*/ + +%{ C++ +#include "nsEmbedCID.h" +%} diff --git a/embedding/browser/nsCommandHandler.cpp b/embedding/browser/nsCommandHandler.cpp new file mode 100644 index 000000000..1e41e265a --- /dev/null +++ b/embedding/browser/nsCommandHandler.cpp @@ -0,0 +1,139 @@ +/* -*- 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 "nsCommandHandler.h" +#include "nsWebBrowser.h" +#include "nsDocShellTreeOwner.h" + +#include "nsMemory.h" +#include "nsPIDOMWindow.h" + +nsCommandHandler::nsCommandHandler() + : mWindow(nullptr) +{ +} + +nsCommandHandler::~nsCommandHandler() +{ +} + +nsresult +nsCommandHandler::GetCommandHandler(nsICommandHandler** aCommandHandler) +{ + NS_ENSURE_ARG_POINTER(aCommandHandler); + + *aCommandHandler = nullptr; + if (!mWindow) { + return NS_ERROR_FAILURE; + } + + // Get the document tree owner + + nsCOMPtr<nsIDocShellTreeItem> docShellAsTreeItem = + do_QueryInterface(mWindow->GetDocShell()); + nsIDocShellTreeOwner* treeOwner = nullptr; + docShellAsTreeItem->GetTreeOwner(&treeOwner); + + // Make sure the tree owner is an an nsDocShellTreeOwner object + // by QI'ing for a hidden interface. If it doesn't have the interface + // then it's not safe to do the casting. + + nsCOMPtr<nsICDocShellTreeOwner> realTreeOwner(do_QueryInterface(treeOwner)); + if (realTreeOwner) { + nsDocShellTreeOwner* tree = static_cast<nsDocShellTreeOwner*>(treeOwner); + if (tree->mTreeOwner) { + nsresult rv; + rv = tree->mTreeOwner->QueryInterface(NS_GET_IID(nsICommandHandler), + (void**)aCommandHandler); + NS_RELEASE(treeOwner); + return rv; + } + + NS_RELEASE(treeOwner); + } + + *aCommandHandler = nullptr; + + return NS_OK; +} + +NS_IMPL_ADDREF(nsCommandHandler) +NS_IMPL_RELEASE(nsCommandHandler) + +NS_INTERFACE_MAP_BEGIN(nsCommandHandler) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsICommandHandler) + NS_INTERFACE_MAP_ENTRY(nsICommandHandlerInit) + NS_INTERFACE_MAP_ENTRY(nsICommandHandler) +NS_INTERFACE_MAP_END + +/////////////////////////////////////////////////////////////////////////////// +// nsICommandHandlerInit implementation + +NS_IMETHODIMP +nsCommandHandler::GetWindow(mozIDOMWindowProxy** aWindow) +{ + *aWindow = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsCommandHandler::SetWindow(mozIDOMWindowProxy* aWindow) +{ + if (!aWindow) { + return NS_ERROR_FAILURE; + } + mWindow = nsPIDOMWindowOuter::From(aWindow); + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsICommandHandler implementation + +NS_IMETHODIMP +nsCommandHandler::Exec(const char* aCommand, const char* aStatus, + char** aResult) +{ + NS_ENSURE_ARG_POINTER(aCommand); + NS_ENSURE_ARG_POINTER(aResult); + + nsCOMPtr<nsICommandHandler> commandHandler; + GetCommandHandler(getter_AddRefs(commandHandler)); + + // Call the client's command handler to deal with this command + if (commandHandler) { + *aResult = nullptr; + return commandHandler->Exec(aCommand, aStatus, aResult); + } + + // Return an empty string + const char szEmpty[] = ""; + *aResult = (char*)nsMemory::Clone(szEmpty, sizeof(szEmpty)); + + return NS_OK; +} + +NS_IMETHODIMP +nsCommandHandler::Query(const char* aCommand, const char* aStatus, + char** aResult) +{ + NS_ENSURE_ARG_POINTER(aCommand); + NS_ENSURE_ARG_POINTER(aResult); + + nsCOMPtr<nsICommandHandler> commandHandler; + GetCommandHandler(getter_AddRefs(commandHandler)); + + // Call the client's command handler to deal with this command + if (commandHandler) { + *aResult = nullptr; + return commandHandler->Query(aCommand, aStatus, aResult); + } + + // Return an empty string + const char szEmpty[] = ""; + *aResult = (char*)nsMemory::Clone(szEmpty, sizeof(szEmpty)); + + return NS_OK; +} diff --git a/embedding/browser/nsCommandHandler.h b/embedding/browser/nsCommandHandler.h new file mode 100644 index 000000000..3d229b9d6 --- /dev/null +++ b/embedding/browser/nsCommandHandler.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 NSCOMMANDHANDLER_H +#define NSCOMMANDHANDLER_H + +#include "nsISupports.h" +#include "nsICommandHandler.h" + +class nsPIDOMWindowOuter; + +class nsCommandHandler + : public nsICommandHandlerInit + , public nsICommandHandler +{ +public: + nsCommandHandler(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICOMMANDHANDLERINIT + NS_DECL_NSICOMMANDHANDLER + +protected: + virtual ~nsCommandHandler(); + +private: + nsresult GetCommandHandler(nsICommandHandler** aCommandHandler); + + nsPIDOMWindowOuter* mWindow; +}; + +#endif diff --git a/embedding/browser/nsContextMenuInfo.cpp b/embedding/browser/nsContextMenuInfo.cpp new file mode 100644 index 000000000..5052dda65 --- /dev/null +++ b/embedding/browser/nsContextMenuInfo.cpp @@ -0,0 +1,326 @@ +/* -*- 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 "nsContextMenuInfo.h" + +#include "nsIImageLoadingContent.h" +#include "imgLoader.h" +#include "nsIDOMDocument.h" +#include "nsIDOMHTMLDocument.h" +#include "nsIDOMHTMLElement.h" +#include "nsIDOMHTMLHtmlElement.h" +#include "nsIDOMHTMLAnchorElement.h" +#include "nsIDOMHTMLImageElement.h" +#include "nsIDOMHTMLAreaElement.h" +#include "nsIDOMHTMLLinkElement.h" +#include "nsIDOMWindow.h" +#include "nsICSSDeclaration.h" +#include "nsIDOMCSSValue.h" +#include "nsIDOMCSSPrimitiveValue.h" +#include "nsNetUtil.h" +#include "nsUnicharUtils.h" +#include "nsIDocument.h" +#include "nsIPrincipal.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIContentPolicy.h" +#include "imgRequestProxy.h" + +using mozilla::dom::Element; +using mozilla::ErrorResult; + +NS_IMPL_ISUPPORTS(nsContextMenuInfo, nsIContextMenuInfo) + +nsContextMenuInfo::nsContextMenuInfo() +{ +} + +nsContextMenuInfo::~nsContextMenuInfo() +{ +} + +NS_IMETHODIMP +nsContextMenuInfo::GetMouseEvent(nsIDOMEvent** aEvent) +{ + NS_ENSURE_ARG_POINTER(aEvent); + NS_IF_ADDREF(*aEvent = mMouseEvent); + return NS_OK; +} + +NS_IMETHODIMP +nsContextMenuInfo::GetTargetNode(nsIDOMNode** aNode) +{ + NS_ENSURE_ARG_POINTER(aNode); + NS_IF_ADDREF(*aNode = mDOMNode); + return NS_OK; +} + +NS_IMETHODIMP +nsContextMenuInfo::GetAssociatedLink(nsAString& aHRef) +{ + NS_ENSURE_STATE(mAssociatedLink); + aHRef.Truncate(0); + + nsCOMPtr<nsIDOMElement> content(do_QueryInterface(mAssociatedLink)); + nsAutoString localName; + if (content) { + content->GetLocalName(localName); + } + + nsCOMPtr<nsIDOMElement> linkContent; + ToLowerCase(localName); + if (localName.EqualsLiteral("a") || + localName.EqualsLiteral("area") || + localName.EqualsLiteral("link")) { + bool hasAttr; + content->HasAttribute(NS_LITERAL_STRING("href"), &hasAttr); + if (hasAttr) { + linkContent = content; + nsCOMPtr<nsIDOMHTMLAnchorElement> anchor(do_QueryInterface(linkContent)); + if (anchor) { + anchor->GetHref(aHRef); + } else { + nsCOMPtr<nsIDOMHTMLAreaElement> area(do_QueryInterface(linkContent)); + if (area) { + area->GetHref(aHRef); + } else { + nsCOMPtr<nsIDOMHTMLLinkElement> link(do_QueryInterface(linkContent)); + if (link) { + link->GetHref(aHRef); + } + } + } + } + } else { + nsCOMPtr<nsIDOMNode> curr; + mAssociatedLink->GetParentNode(getter_AddRefs(curr)); + while (curr) { + content = do_QueryInterface(curr); + if (!content) { + break; + } + content->GetLocalName(localName); + ToLowerCase(localName); + if (localName.EqualsLiteral("a")) { + bool hasAttr; + content->HasAttribute(NS_LITERAL_STRING("href"), &hasAttr); + if (hasAttr) { + linkContent = content; + nsCOMPtr<nsIDOMHTMLAnchorElement> anchor( + do_QueryInterface(linkContent)); + if (anchor) { + anchor->GetHref(aHRef); + } + } else { + linkContent = nullptr; // Links can't be nested. + } + break; + } + + nsCOMPtr<nsIDOMNode> temp = curr; + temp->GetParentNode(getter_AddRefs(curr)); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsContextMenuInfo::GetImageContainer(imgIContainer** aImageContainer) +{ + NS_ENSURE_ARG_POINTER(aImageContainer); + NS_ENSURE_STATE(mDOMNode); + + nsCOMPtr<imgIRequest> request; + GetImageRequest(mDOMNode, getter_AddRefs(request)); + if (request) { + return request->GetImage(aImageContainer); + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsContextMenuInfo::GetImageSrc(nsIURI** aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + NS_ENSURE_STATE(mDOMNode); + + nsCOMPtr<nsIImageLoadingContent> content(do_QueryInterface(mDOMNode)); + NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); + return content->GetCurrentURI(aURI); +} + +NS_IMETHODIMP +nsContextMenuInfo::GetBackgroundImageContainer(imgIContainer** aImageContainer) +{ + NS_ENSURE_ARG_POINTER(aImageContainer); + NS_ENSURE_STATE(mDOMNode); + + RefPtr<imgRequestProxy> request; + GetBackgroundImageRequest(mDOMNode, getter_AddRefs(request)); + if (request) { + return request->GetImage(aImageContainer); + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsContextMenuInfo::GetBackgroundImageSrc(nsIURI** aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + NS_ENSURE_STATE(mDOMNode); + + RefPtr<imgRequestProxy> request; + GetBackgroundImageRequest(mDOMNode, getter_AddRefs(request)); + if (request) { + return request->GetURI(aURI); + } + + return NS_ERROR_FAILURE; +} + +nsresult +nsContextMenuInfo::GetImageRequest(nsIDOMNode* aDOMNode, imgIRequest** aRequest) +{ + NS_ENSURE_ARG(aDOMNode); + NS_ENSURE_ARG_POINTER(aRequest); + + // Get content + nsCOMPtr<nsIImageLoadingContent> content(do_QueryInterface(aDOMNode)); + NS_ENSURE_TRUE(content, NS_ERROR_FAILURE); + + return content->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, aRequest); +} + +bool +nsContextMenuInfo::HasBackgroundImage(nsIDOMNode* aDOMNode) +{ + NS_ENSURE_TRUE(aDOMNode, false); + + RefPtr<imgRequestProxy> request; + GetBackgroundImageRequest(aDOMNode, getter_AddRefs(request)); + + return (request != nullptr); +} + +nsresult +nsContextMenuInfo::GetBackgroundImageRequest(nsIDOMNode* aDOMNode, + imgRequestProxy** aRequest) +{ + + NS_ENSURE_ARG(aDOMNode); + NS_ENSURE_ARG_POINTER(aRequest); + + nsCOMPtr<nsIDOMNode> domNode = aDOMNode; + + // special case for the <html> element: if it has no background-image + // we'll defer to <body> + nsCOMPtr<nsIDOMHTMLHtmlElement> htmlElement = do_QueryInterface(domNode); + if (htmlElement) { + nsCOMPtr<nsIDOMHTMLElement> element = do_QueryInterface(domNode); + nsAutoString nameSpace; + element->GetNamespaceURI(nameSpace); + if (nameSpace.IsEmpty()) { + nsresult rv = GetBackgroundImageRequestInternal(domNode, aRequest); + if (NS_SUCCEEDED(rv) && *aRequest) { + return NS_OK; + } + + // no background-image found + nsCOMPtr<nsIDOMDocument> document; + domNode->GetOwnerDocument(getter_AddRefs(document)); + nsCOMPtr<nsIDOMHTMLDocument> htmlDocument(do_QueryInterface(document)); + NS_ENSURE_TRUE(htmlDocument, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDOMHTMLElement> body; + htmlDocument->GetBody(getter_AddRefs(body)); + domNode = do_QueryInterface(body); + NS_ENSURE_TRUE(domNode, NS_ERROR_FAILURE); + } + } + return GetBackgroundImageRequestInternal(domNode, aRequest); +} + +nsresult +nsContextMenuInfo::GetBackgroundImageRequestInternal(nsIDOMNode* aDOMNode, + imgRequestProxy** aRequest) +{ + NS_ENSURE_ARG_POINTER(aDOMNode); + + nsCOMPtr<nsIDOMNode> domNode = aDOMNode; + nsCOMPtr<nsIDOMNode> parentNode; + + nsCOMPtr<nsIDOMDocument> document; + domNode->GetOwnerDocument(getter_AddRefs(document)); + NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); + + nsCOMPtr<mozIDOMWindowProxy> window; + document->GetDefaultView(getter_AddRefs(window)); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + + auto* piWindow = nsPIDOMWindowOuter::From(window); + nsPIDOMWindowInner* innerWindow = piWindow->GetCurrentInnerWindow(); + MOZ_ASSERT(innerWindow); + + nsCOMPtr<nsIDOMCSSPrimitiveValue> primitiveValue; + nsAutoString bgStringValue; + + nsCOMPtr<nsIDocument> doc(do_QueryInterface(document)); + nsCOMPtr<nsIPrincipal> principal = doc ? doc->NodePrincipal() : nullptr; + + while (true) { + nsCOMPtr<Element> domElement(do_QueryInterface(domNode)); + // bail for the parent node of the root element or null argument + if (!domElement) { + break; + } + + ErrorResult dummy; + nsCOMPtr<nsICSSDeclaration> computedStyle = + innerWindow->GetComputedStyle(*domElement, EmptyString(), dummy); + dummy.SuppressException(); + if (computedStyle) { + nsCOMPtr<nsIDOMCSSValue> cssValue; + computedStyle->GetPropertyCSSValue(NS_LITERAL_STRING("background-image"), + getter_AddRefs(cssValue)); + primitiveValue = do_QueryInterface(cssValue); + if (primitiveValue) { + primitiveValue->GetStringValue(bgStringValue); + if (!bgStringValue.EqualsLiteral("none")) { + nsCOMPtr<nsIURI> bgUri; + NS_NewURI(getter_AddRefs(bgUri), bgStringValue); + NS_ENSURE_TRUE(bgUri, NS_ERROR_FAILURE); + + imgLoader* il = imgLoader::NormalLoader(); + NS_ENSURE_TRUE(il, NS_ERROR_FAILURE); + + return il->LoadImage(bgUri, nullptr, nullptr, + doc->GetReferrerPolicy(), principal, nullptr, + nullptr, nullptr, nullptr, nsIRequest::LOAD_NORMAL, + nullptr, nsIContentPolicy::TYPE_INTERNAL_IMAGE, + EmptyString(), aRequest); + } + } + + // bail if we encounter non-transparent background-color + computedStyle->GetPropertyCSSValue(NS_LITERAL_STRING("background-color"), + getter_AddRefs(cssValue)); + primitiveValue = do_QueryInterface(cssValue); + if (primitiveValue) { + primitiveValue->GetStringValue(bgStringValue); + if (!bgStringValue.EqualsLiteral("transparent")) { + return NS_ERROR_FAILURE; + } + } + } + + domNode->GetParentNode(getter_AddRefs(parentNode)); + domNode = parentNode; + } + + return NS_ERROR_FAILURE; +} diff --git a/embedding/browser/nsContextMenuInfo.h b/embedding/browser/nsContextMenuInfo.h new file mode 100644 index 000000000..998045f97 --- /dev/null +++ b/embedding/browser/nsContextMenuInfo.h @@ -0,0 +1,54 @@ +/* -*- 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 nsContextMenuInfo_h__ +#define nsContextMenuInfo_h__ + +#include "nsCOMPtr.h" +#include "nsIContextMenuListener2.h" +#include "nsIDOMNode.h" +#include "nsIDOMEvent.h" +#include "imgIContainer.h" +#include "imgIRequest.h" + +class ChromeContextMenuListener; +class imgRequestProxy; + +// Helper class for implementors of nsIContextMenuListener2 +class nsContextMenuInfo : public nsIContextMenuInfo +{ + friend class ChromeContextMenuListener; + +public: + nsContextMenuInfo(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICONTEXTMENUINFO + +private: + virtual ~nsContextMenuInfo(); + + void SetMouseEvent(nsIDOMEvent* aEvent) { mMouseEvent = aEvent; } + void SetDOMNode(nsIDOMNode* aNode) { mDOMNode = aNode; } + void SetAssociatedLink(nsIDOMNode* aLink) { mAssociatedLink = aLink; } + + nsresult GetImageRequest(nsIDOMNode* aDOMNode, imgIRequest** aRequest); + + bool HasBackgroundImage(nsIDOMNode* aDOMNode); + + nsresult GetBackgroundImageRequest(nsIDOMNode* aDOMNode, + imgRequestProxy** aRequest); + + nsresult GetBackgroundImageRequestInternal(nsIDOMNode* aDOMNode, + imgRequestProxy** aRequest); + +private: + nsCOMPtr<nsIDOMEvent> mMouseEvent; + nsCOMPtr<nsIDOMNode> mDOMNode; + nsCOMPtr<nsIDOMNode> mAssociatedLink; +}; + +#endif // nsContextMenuInfo_h__ diff --git a/embedding/browser/nsDocShellTreeOwner.cpp b/embedding/browser/nsDocShellTreeOwner.cpp new file mode 100644 index 000000000..73397cc8b --- /dev/null +++ b/embedding/browser/nsDocShellTreeOwner.cpp @@ -0,0 +1,1674 @@ +/* -*- 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/. */ + +// Local Includes +#include "nsDocShellTreeOwner.h" +#include "nsWebBrowser.h" + +// Helper Classes +#include "nsStyleCoord.h" +#include "nsSize.h" +#include "mozilla/ReflowInput.h" +#include "nsIServiceManager.h" +#include "nsComponentManagerUtils.h" +#include "nsXPIDLString.h" +#include "nsIAtom.h" +#include "nsReadableUtils.h" +#include "nsUnicharUtils.h" +#include "nsISimpleEnumerator.h" +#include "mozilla/LookAndFeel.h" + +// Interfaces needed to be included +#include "nsPresContext.h" +#include "nsIContextMenuListener.h" +#include "nsIContextMenuListener2.h" +#include "nsITooltipListener.h" +#include "nsIDOMNode.h" +#include "nsIDOMNodeList.h" +#include "nsIDOMDocument.h" +#include "nsIDOMDocumentType.h" +#include "nsIDOMElement.h" +#include "Link.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/SVGTitleElement.h" +#include "nsIDOMEvent.h" +#include "nsIDOMFileList.h" +#include "nsIDOMMouseEvent.h" +#include "nsIFormControl.h" +#include "nsIDOMHTMLInputElement.h" +#include "nsIDOMHTMLTextAreaElement.h" +#include "nsIDOMHTMLHtmlElement.h" +#include "nsIDOMHTMLAppletElement.h" +#include "nsIDOMHTMLObjectElement.h" +#include "nsIDOMHTMLEmbedElement.h" +#include "nsIDOMHTMLDocument.h" +#include "nsIImageLoadingContent.h" +#include "nsIWebNavigation.h" +#include "nsIDOMHTMLElement.h" +#include "nsIPresShell.h" +#include "nsIStringBundle.h" +#include "nsPIDOMWindow.h" +#include "nsPIWindowRoot.h" +#include "nsIDOMWindowCollection.h" +#include "nsIWindowWatcher.h" +#include "nsPIWindowWatcher.h" +#include "nsIPrompt.h" +#include "nsITabParent.h" +#include "nsITabChild.h" +#include "nsRect.h" +#include "nsIWebBrowserChromeFocus.h" +#include "nsIContent.h" +#include "imgIContainer.h" +#include "nsContextMenuInfo.h" +#include "nsPresContext.h" +#include "nsViewManager.h" +#include "nsView.h" +#include "nsIDOMDragEvent.h" +#include "nsIConstraintValidation.h" +#include "mozilla/Attributes.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent() +#include "mozilla/dom/File.h" // for input type=file +#include "mozilla/dom/FileList.h" // for input type=file + +using namespace mozilla; +using namespace mozilla::dom; + +// A helper routine that navigates the tricky path from a |nsWebBrowser| to +// a |EventTarget| via the window root and chrome event handler. +static nsresult +GetDOMEventTarget(nsWebBrowser* aInBrowser, EventTarget** aTarget) +{ + if (!aInBrowser) { + return NS_ERROR_INVALID_POINTER; + } + + nsCOMPtr<mozIDOMWindowProxy> domWindow; + aInBrowser->GetContentDOMWindow(getter_AddRefs(domWindow)); + if (!domWindow) { + return NS_ERROR_FAILURE; + } + + auto* outerWindow = nsPIDOMWindowOuter::From(domWindow); + nsPIDOMWindowOuter* rootWindow = outerWindow->GetPrivateRoot(); + NS_ENSURE_TRUE(rootWindow, NS_ERROR_FAILURE); + nsCOMPtr<EventTarget> target = rootWindow->GetChromeEventHandler(); + NS_ENSURE_TRUE(target, NS_ERROR_FAILURE); + target.forget(aTarget); + + return NS_OK; +} + +nsDocShellTreeOwner::nsDocShellTreeOwner() + : mWebBrowser(nullptr) + , mTreeOwner(nullptr) + , mPrimaryContentShell(nullptr) + , mWebBrowserChrome(nullptr) + , mOwnerWin(nullptr) + , mOwnerRequestor(nullptr) +{ +} + +nsDocShellTreeOwner::~nsDocShellTreeOwner() +{ + RemoveChromeListeners(); +} + +NS_IMPL_ADDREF(nsDocShellTreeOwner) +NS_IMPL_RELEASE(nsDocShellTreeOwner) + +NS_INTERFACE_MAP_BEGIN(nsDocShellTreeOwner) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocShellTreeOwner) + NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeOwner) + NS_INTERFACE_MAP_ENTRY(nsIBaseWindow) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) + NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) + NS_INTERFACE_MAP_ENTRY(nsICDocShellTreeOwner) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + +//***************************************************************************** +// nsDocShellTreeOwner::nsIInterfaceRequestor +//***************************************************************************** + +NS_IMETHODIMP +nsDocShellTreeOwner::GetInterface(const nsIID& aIID, void** aSink) +{ + NS_ENSURE_ARG_POINTER(aSink); + + if (NS_SUCCEEDED(QueryInterface(aIID, aSink))) { + return NS_OK; + } + + if (aIID.Equals(NS_GET_IID(nsIWebBrowserChromeFocus))) { + if (mWebBrowserChromeWeak != nullptr) { + return mWebBrowserChromeWeak->QueryReferent(aIID, aSink); + } + return mOwnerWin->QueryInterface(aIID, aSink); + } + + if (aIID.Equals(NS_GET_IID(nsIPrompt))) { + nsCOMPtr<nsIPrompt> prompt; + EnsurePrompter(); + prompt = mPrompter; + if (prompt) { + prompt.forget(aSink); + return NS_OK; + } + return NS_NOINTERFACE; + } + + if (aIID.Equals(NS_GET_IID(nsIAuthPrompt))) { + nsCOMPtr<nsIAuthPrompt> prompt; + EnsureAuthPrompter(); + prompt = mAuthPrompter; + if (prompt) { + prompt.forget(aSink); + return NS_OK; + } + return NS_NOINTERFACE; + } + + nsCOMPtr<nsIInterfaceRequestor> req = GetOwnerRequestor(); + if (req) { + return req->GetInterface(aIID, aSink); + } + + return NS_NOINTERFACE; +} + +//***************************************************************************** +// nsDocShellTreeOwner::nsIDocShellTreeOwner +//***************************************************************************** + +void +nsDocShellTreeOwner::EnsurePrompter() +{ + if (mPrompter) { + return; + } + + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + if (wwatch && mWebBrowser) { + nsCOMPtr<mozIDOMWindowProxy> domWindow; + mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow)); + if (domWindow) { + wwatch->GetNewPrompter(domWindow, getter_AddRefs(mPrompter)); + } + } +} + +void +nsDocShellTreeOwner::EnsureAuthPrompter() +{ + if (mAuthPrompter) { + return; + } + + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + if (wwatch && mWebBrowser) { + nsCOMPtr<mozIDOMWindowProxy> domWindow; + mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow)); + if (domWindow) { + wwatch->GetNewAuthPrompter(domWindow, getter_AddRefs(mAuthPrompter)); + } + } +} + +void +nsDocShellTreeOwner::AddToWatcher() +{ + if (mWebBrowser) { + nsCOMPtr<mozIDOMWindowProxy> domWindow; + mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow)); + if (domWindow) { + nsCOMPtr<nsPIWindowWatcher> wwatch( + do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + if (wwatch) { + nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome(); + if (webBrowserChrome) { + wwatch->AddWindow(domWindow, webBrowserChrome); + } + } + } + } +} + +void +nsDocShellTreeOwner::RemoveFromWatcher() +{ + if (mWebBrowser) { + nsCOMPtr<mozIDOMWindowProxy> domWindow; + mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow)); + if (domWindow) { + nsCOMPtr<nsPIWindowWatcher> wwatch( + do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + if (wwatch) { + wwatch->RemoveWindow(domWindow); + } + } + } +} + +void +nsDocShellTreeOwner::EnsureContentTreeOwner() +{ + if (mContentTreeOwner) { + return; + } + + mContentTreeOwner = new nsDocShellTreeOwner(); + nsCOMPtr<nsIWebBrowserChrome> browserChrome = GetWebBrowserChrome(); + if (browserChrome) { + mContentTreeOwner->SetWebBrowserChrome(browserChrome); + } + + if (mWebBrowser) { + mContentTreeOwner->WebBrowser(mWebBrowser); + } +} + +NS_IMETHODIMP +nsDocShellTreeOwner::ContentShellAdded(nsIDocShellTreeItem* aContentShell, + bool aPrimary, bool aTargetable, + const nsAString& aID) +{ + if (mTreeOwner) + return mTreeOwner->ContentShellAdded(aContentShell, aPrimary, aTargetable, + aID); + + EnsureContentTreeOwner(); + aContentShell->SetTreeOwner(mContentTreeOwner); + + if (aPrimary) { + mPrimaryContentShell = aContentShell; + mPrimaryTabParent = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::ContentShellRemoved(nsIDocShellTreeItem* aContentShell) +{ + if (mTreeOwner) { + return mTreeOwner->ContentShellRemoved(aContentShell); + } + + if (mPrimaryContentShell == aContentShell) { + mPrimaryContentShell = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetPrimaryContentShell(nsIDocShellTreeItem** aShell) +{ + NS_ENSURE_ARG_POINTER(aShell); + + if (mTreeOwner) { + return mTreeOwner->GetPrimaryContentShell(aShell); + } + + nsCOMPtr<nsIDocShellTreeItem> shell; + if (!mPrimaryTabParent) { + shell = + mPrimaryContentShell ? mPrimaryContentShell : mWebBrowser->mDocShell; + } + shell.forget(aShell); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::TabParentAdded(nsITabParent* aTab, bool aPrimary) +{ + if (mTreeOwner) { + return mTreeOwner->TabParentAdded(aTab, aPrimary); + } + + if (aPrimary) { + mPrimaryTabParent = aTab; + mPrimaryContentShell = nullptr; + } else if (mPrimaryTabParent == aTab) { + mPrimaryTabParent = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::TabParentRemoved(nsITabParent* aTab) +{ + if (mTreeOwner) { + return mTreeOwner->TabParentRemoved(aTab); + } + + if (aTab == mPrimaryTabParent) { + mPrimaryTabParent = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetPrimaryTabParent(nsITabParent** aTab) +{ + if (mTreeOwner) { + return mTreeOwner->GetPrimaryTabParent(aTab); + } + + nsCOMPtr<nsITabParent> tab = mPrimaryTabParent; + tab.forget(aTab); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetPrimaryContentSize(int32_t* aWidth, + int32_t* aHeight) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetPrimaryContentSize(int32_t aWidth, + int32_t aHeight) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetRootShellSize(int32_t* aWidth, + int32_t* aHeight) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetRootShellSize(int32_t aWidth, + int32_t aHeight) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SizeShellTo(nsIDocShellTreeItem* aShellItem, + int32_t aCX, int32_t aCY) +{ + nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome(); + + NS_ENSURE_STATE(mTreeOwner || webBrowserChrome); + + if (mTreeOwner) { + return mTreeOwner->SizeShellTo(aShellItem, aCX, aCY); + } + + if (aShellItem == mWebBrowser->mDocShell) { + nsCOMPtr<nsITabChild> tabChild = do_QueryInterface(webBrowserChrome); + if (tabChild) { + // The XUL window to resize is in the parent process, but there we + // won't be able to get aShellItem to do the hack in nsXULWindow::SizeShellTo, + // so let's send the width and height of aShellItem too. + nsCOMPtr<nsIBaseWindow> shellAsWin(do_QueryInterface(aShellItem)); + NS_ENSURE_TRUE(shellAsWin, NS_ERROR_FAILURE); + + int32_t width = 0; + int32_t height = 0; + shellAsWin->GetSize(&width, &height); + return tabChild->RemoteSizeShellTo(aCX, aCY, width, height); + } + return webBrowserChrome->SizeBrowserTo(aCX, aCY); + } + + nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(aShellItem)); + NS_ENSURE_TRUE(webNav, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDOMDocument> domDocument; + webNav->GetDocument(getter_AddRefs(domDocument)); + NS_ENSURE_TRUE(domDocument, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDOMElement> domElement; + domDocument->GetDocumentElement(getter_AddRefs(domElement)); + NS_ENSURE_TRUE(domElement, NS_ERROR_FAILURE); + + // Set the preferred Size + //XXX + NS_ERROR("Implement this"); + /* + Set the preferred size on the aShellItem. + */ + + RefPtr<nsPresContext> presContext; + mWebBrowser->mDocShell->GetPresContext(getter_AddRefs(presContext)); + NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); + + nsIPresShell* presShell = presContext->GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + + NS_ENSURE_SUCCESS( + presShell->ResizeReflow(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE), + NS_ERROR_FAILURE); + + nsRect shellArea = presContext->GetVisibleArea(); + + int32_t browserCX = presContext->AppUnitsToDevPixels(shellArea.width); + int32_t browserCY = presContext->AppUnitsToDevPixels(shellArea.height); + + return webBrowserChrome->SizeBrowserTo(browserCX, browserCY); +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetPersistence(bool aPersistPosition, + bool aPersistSize, + bool aPersistSizeMode) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetPersistence(bool* aPersistPosition, + bool* aPersistSize, + bool* aPersistSizeMode) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetTargetableShellCount(uint32_t* aResult) +{ + if (mTreeOwner) { + mTreeOwner->GetTargetableShellCount(aResult); + } else { + *aResult = 0; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetHasPrimaryContent(bool* aResult) +{ + *aResult = mPrimaryTabParent || mPrimaryContentShell; + return NS_OK; +} + +//***************************************************************************** +// nsDocShellTreeOwner::nsIBaseWindow +//***************************************************************************** + +NS_IMETHODIMP +nsDocShellTreeOwner::InitWindow(nativeWindow aParentNativeWindow, + nsIWidget* aParentWidget, int32_t aX, + int32_t aY, int32_t aCX, int32_t aCY) +{ + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::Create() +{ + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::Destroy() +{ + nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome(); + if (webBrowserChrome) { + return webBrowserChrome->DestroyBrowserWindow(); + } + + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetUnscaledDevicePixelsPerCSSPixel(double* aScale) +{ + if (mWebBrowser) { + return mWebBrowser->GetUnscaledDevicePixelsPerCSSPixel(aScale); + } + + *aScale = 1.0; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetDevicePixelsPerDesktopPixel(double* aScale) +{ + if (mWebBrowser) { + return mWebBrowser->GetDevicePixelsPerDesktopPixel(aScale); + } + + *aScale = 1.0; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetPositionDesktopPix(int32_t aX, int32_t aY) +{ + if (mWebBrowser) { + nsresult rv = mWebBrowser->SetPositionDesktopPix(aX, aY); + NS_ENSURE_SUCCESS(rv, rv); + } + + double scale = 1.0; + GetDevicePixelsPerDesktopPixel(&scale); + return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale)); +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetPosition(int32_t aX, int32_t aY) +{ + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->SetDimensions(nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION, + aX, aY, 0, 0); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetPosition(int32_t* aX, int32_t* aY) +{ + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->GetDimensions(nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION, + aX, aY, nullptr, nullptr); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetSize(int32_t aCX, int32_t aCY, bool aRepaint) +{ + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->SetDimensions(nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER, + 0, 0, aCX, aCY); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetSize(int32_t* aCX, int32_t* aCY) +{ + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->GetDimensions(nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER, + nullptr, nullptr, aCX, aCY); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetPositionAndSize(int32_t aX, int32_t aY, int32_t aCX, + int32_t aCY, uint32_t aFlags) +{ + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->SetDimensions( + nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER | + nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION, + aX, aY, aCX, aCY); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aCX, + int32_t* aCY) +{ + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->GetDimensions( + nsIEmbeddingSiteWindow::DIM_FLAGS_SIZE_OUTER | + nsIEmbeddingSiteWindow::DIM_FLAGS_POSITION, + aX, aY, aCX, aCY); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::Repaint(bool aForce) +{ + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetParentWidget(nsIWidget** aParentWidget) +{ + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetParentWidget(nsIWidget* aParentWidget) +{ + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetParentNativeWindow(nativeWindow* aParentNativeWindow) +{ + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->GetSiteWindow(aParentNativeWindow); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetParentNativeWindow(nativeWindow aParentNativeWindow) +{ + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetNativeHandle(nsAString& aNativeHandle) +{ + // the nativeHandle should be accessed from nsIXULWindow + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetVisibility(bool* aVisibility) +{ + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->GetVisibility(aVisibility); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetVisibility(bool aVisibility) +{ + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->SetVisibility(aVisibility); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetEnabled(bool* aEnabled) +{ + NS_ENSURE_ARG_POINTER(aEnabled); + *aEnabled = true; + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetEnabled(bool aEnabled) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetMainWidget(nsIWidget** aMainWidget) +{ + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetFocus() +{ + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->SetFocus(); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::GetTitle(char16_t** aTitle) +{ + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->GetTitle(aTitle); + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetTitle(const char16_t* aTitle) +{ + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin = GetOwnerWin(); + if (ownerWin) { + return ownerWin->SetTitle(aTitle); + } + return NS_ERROR_NULL_POINTER; +} + +//***************************************************************************** +// nsDocShellTreeOwner::nsIWebProgressListener +//***************************************************************************** + +NS_IMETHODIMP +nsDocShellTreeOwner::OnProgressChange(nsIWebProgress* aProgress, + nsIRequest* aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) +{ + // In the absence of DOM document creation event, this method is the + // most convenient place to install the mouse listener on the + // DOM document. + return AddChromeListeners(); +} + +NS_IMETHODIMP +nsDocShellTreeOwner::OnStateChange(nsIWebProgress* aProgress, + nsIRequest* aRequest, + uint32_t aProgressStateFlags, + nsresult aStatus) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI* aURI, + uint32_t aFlags) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aState) +{ + return NS_OK; +} + +//***************************************************************************** +// nsDocShellTreeOwner: Accessors +//***************************************************************************** + +void +nsDocShellTreeOwner::WebBrowser(nsWebBrowser* aWebBrowser) +{ + if (!aWebBrowser) { + RemoveChromeListeners(); + } + if (aWebBrowser != mWebBrowser) { + mPrompter = nullptr; + mAuthPrompter = nullptr; + } + + mWebBrowser = aWebBrowser; + + if (mContentTreeOwner) { + mContentTreeOwner->WebBrowser(aWebBrowser); + if (!aWebBrowser) { + mContentTreeOwner = nullptr; + } + } +} + +nsWebBrowser* +nsDocShellTreeOwner::WebBrowser() +{ + return mWebBrowser; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner) +{ + if (aTreeOwner) { + nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome(do_GetInterface(aTreeOwner)); + NS_ENSURE_TRUE(webBrowserChrome, NS_ERROR_INVALID_ARG); + NS_ENSURE_SUCCESS(SetWebBrowserChrome(webBrowserChrome), + NS_ERROR_INVALID_ARG); + mTreeOwner = aTreeOwner; + } else { + mTreeOwner = nullptr; + nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome(); + if (!webBrowserChrome) { + NS_ENSURE_SUCCESS(SetWebBrowserChrome(nullptr), NS_ERROR_FAILURE); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::SetWebBrowserChrome(nsIWebBrowserChrome* aWebBrowserChrome) +{ + if (!aWebBrowserChrome) { + mWebBrowserChrome = nullptr; + mOwnerWin = nullptr; + mOwnerRequestor = nullptr; + mWebBrowserChromeWeak = nullptr; + } else { + nsCOMPtr<nsISupportsWeakReference> supportsweak = + do_QueryInterface(aWebBrowserChrome); + if (supportsweak) { + supportsweak->GetWeakReference(getter_AddRefs(mWebBrowserChromeWeak)); + } else { + nsCOMPtr<nsIEmbeddingSiteWindow> ownerWin( + do_QueryInterface(aWebBrowserChrome)); + nsCOMPtr<nsIInterfaceRequestor> requestor( + do_QueryInterface(aWebBrowserChrome)); + + // it's ok for ownerWin or requestor to be null. + mWebBrowserChrome = aWebBrowserChrome; + mOwnerWin = ownerWin; + mOwnerRequestor = requestor; + } + } + + if (mContentTreeOwner) { + mContentTreeOwner->SetWebBrowserChrome(aWebBrowserChrome); + } + + return NS_OK; +} + +// Hook up things to the chrome like context menus and tooltips, if the chrome +// has implemented the right interfaces. +NS_IMETHODIMP +nsDocShellTreeOwner::AddChromeListeners() +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome(); + if (!webBrowserChrome) { + return NS_ERROR_FAILURE; + } + + // install tooltips + if (!mChromeTooltipListener) { + nsCOMPtr<nsITooltipListener> tooltipListener( + do_QueryInterface(webBrowserChrome)); + if (tooltipListener) { + mChromeTooltipListener = new ChromeTooltipListener(mWebBrowser, + webBrowserChrome); + rv = mChromeTooltipListener->AddChromeListeners(); + } + } + + // install context menus + if (!mChromeContextMenuListener) { + nsCOMPtr<nsIContextMenuListener2> contextListener2( + do_QueryInterface(webBrowserChrome)); + nsCOMPtr<nsIContextMenuListener> contextListener( + do_QueryInterface(webBrowserChrome)); + if (contextListener2 || contextListener) { + mChromeContextMenuListener = + new ChromeContextMenuListener(mWebBrowser, webBrowserChrome); + rv = mChromeContextMenuListener->AddChromeListeners(); + } + } + + // register dragover and drop event listeners with the listener manager + nsCOMPtr<EventTarget> target; + GetDOMEventTarget(mWebBrowser, getter_AddRefs(target)); + + EventListenerManager* elmP = target->GetOrCreateListenerManager(); + if (elmP) { + elmP->AddEventListenerByType(this, NS_LITERAL_STRING("dragover"), + TrustedEventsAtSystemGroupBubble()); + elmP->AddEventListenerByType(this, NS_LITERAL_STRING("drop"), + TrustedEventsAtSystemGroupBubble()); + } + + return rv; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::RemoveChromeListeners() +{ + if (mChromeTooltipListener) { + mChromeTooltipListener->RemoveChromeListeners(); + mChromeTooltipListener = nullptr; + } + if (mChromeContextMenuListener) { + mChromeContextMenuListener->RemoveChromeListeners(); + mChromeContextMenuListener = nullptr; + } + + nsCOMPtr<EventTarget> piTarget; + GetDOMEventTarget(mWebBrowser, getter_AddRefs(piTarget)); + if (!piTarget) { + return NS_OK; + } + + EventListenerManager* elmP = piTarget->GetOrCreateListenerManager(); + if (elmP) { + elmP->RemoveEventListenerByType(this, NS_LITERAL_STRING("dragover"), + TrustedEventsAtSystemGroupBubble()); + elmP->RemoveEventListenerByType(this, NS_LITERAL_STRING("drop"), + TrustedEventsAtSystemGroupBubble()); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShellTreeOwner::HandleEvent(nsIDOMEvent* aEvent) +{ + nsCOMPtr<nsIDOMDragEvent> dragEvent = do_QueryInterface(aEvent); + NS_ENSURE_TRUE(dragEvent, NS_ERROR_INVALID_ARG); + + bool defaultPrevented; + aEvent->GetDefaultPrevented(&defaultPrevented); + if (defaultPrevented) { + return NS_OK; + } + + nsCOMPtr<nsIDroppedLinkHandler> handler = + do_GetService("@mozilla.org/content/dropped-link-handler;1"); + if (handler) { + nsAutoString eventType; + aEvent->GetType(eventType); + if (eventType.EqualsLiteral("dragover")) { + bool canDropLink = false; + handler->CanDropLink(dragEvent, false, &canDropLink); + if (canDropLink) { + aEvent->PreventDefault(); + } + } else if (eventType.EqualsLiteral("drop")) { + nsIWebNavigation* webnav = static_cast<nsIWebNavigation*>(mWebBrowser); + + uint32_t linksCount; + nsIDroppedLinkItem** links; + if (webnav && + NS_SUCCEEDED(handler->DropLinks(dragEvent, true, &linksCount, &links))) { + if (linksCount >= 1) { + nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome(); + if (webBrowserChrome) { + nsCOMPtr<nsITabChild> tabChild = do_QueryInterface(webBrowserChrome); + if (tabChild) { + nsresult rv = tabChild->RemoteDropLinks(linksCount, links); + for (uint32_t i = 0; i < linksCount; i++) { + NS_RELEASE(links[i]); + } + free(links); + return rv; + } + } + nsAutoString url; + if (NS_SUCCEEDED(links[0]->GetUrl(url))) { + if (!url.IsEmpty()) { + webnav->LoadURI(url.get(), 0, nullptr, nullptr, nullptr); + } + } + + for (uint32_t i = 0; i < linksCount; i++) { + NS_RELEASE(links[i]); + } + free(links); + } + } else { + aEvent->StopPropagation(); + aEvent->PreventDefault(); + } + } + } + + return NS_OK; +} + +already_AddRefed<nsIWebBrowserChrome> +nsDocShellTreeOwner::GetWebBrowserChrome() +{ + nsCOMPtr<nsIWebBrowserChrome> chrome; + if (mWebBrowserChromeWeak) { + chrome = do_QueryReferent(mWebBrowserChromeWeak); + } else if (mWebBrowserChrome) { + chrome = mWebBrowserChrome; + } + return chrome.forget(); +} + +already_AddRefed<nsIEmbeddingSiteWindow> +nsDocShellTreeOwner::GetOwnerWin() +{ + nsCOMPtr<nsIEmbeddingSiteWindow> win; + if (mWebBrowserChromeWeak) { + win = do_QueryReferent(mWebBrowserChromeWeak); + } else if (mOwnerWin) { + win = mOwnerWin; + } + return win.forget(); +} + +already_AddRefed<nsIInterfaceRequestor> +nsDocShellTreeOwner::GetOwnerRequestor() +{ + nsCOMPtr<nsIInterfaceRequestor> req; + if (mWebBrowserChromeWeak) { + req = do_QueryReferent(mWebBrowserChromeWeak); + } else if (mOwnerRequestor) { + req = mOwnerRequestor; + } + return req.forget(); +} + +NS_IMPL_ISUPPORTS(ChromeTooltipListener, nsIDOMEventListener) + +ChromeTooltipListener::ChromeTooltipListener(nsWebBrowser* aInBrowser, + nsIWebBrowserChrome* aInChrome) + : mWebBrowser(aInBrowser) + , mWebBrowserChrome(aInChrome) + , mTooltipListenerInstalled(false) + , mMouseClientX(0) + , mMouseClientY(0) + , mMouseScreenX(0) + , mMouseScreenY(0) + , mShowingTooltip(false) + , mTooltipShownOnce(false) +{ + mTooltipTextProvider = do_GetService(NS_TOOLTIPTEXTPROVIDER_CONTRACTID); + if (!mTooltipTextProvider) { + mTooltipTextProvider = do_GetService(NS_DEFAULTTOOLTIPTEXTPROVIDER_CONTRACTID); + } +} + +ChromeTooltipListener::~ChromeTooltipListener() +{ +} + +// Hook up things to the chrome like context menus and tooltips, if the chrome +// has implemented the right interfaces. +NS_IMETHODIMP +ChromeTooltipListener::AddChromeListeners() +{ + if (!mEventTarget) { + GetDOMEventTarget(mWebBrowser, getter_AddRefs(mEventTarget)); + } + + // Register the appropriate events for tooltips, but only if + // the embedding chrome cares. + nsresult rv = NS_OK; + nsCOMPtr<nsITooltipListener> tooltipListener( + do_QueryInterface(mWebBrowserChrome)); + if (tooltipListener && !mTooltipListenerInstalled) { + rv = AddTooltipListener(); + if (NS_FAILED(rv)) { + return rv; + } + } + + return rv; +} + +// Subscribe to the events that will allow us to track tooltips. We need "mouse" +// for mouseExit, "mouse motion" for mouseMove, and "key" for keyDown. As we +// add the listeners, keep track of how many succeed so we can clean up +// correctly in Release(). +NS_IMETHODIMP +ChromeTooltipListener::AddTooltipListener() +{ + if (mEventTarget) { + nsresult rv = NS_OK; + rv = mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("keydown"), + this, false, false); + NS_ENSURE_SUCCESS(rv, rv); + rv = mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("mousedown"), + this, false, false); + NS_ENSURE_SUCCESS(rv, rv); + rv = mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("mouseout"), + this, false, false); + NS_ENSURE_SUCCESS(rv, rv); + rv = mEventTarget->AddSystemEventListener(NS_LITERAL_STRING("mousemove"), + this, false, false); + NS_ENSURE_SUCCESS(rv, rv); + + mTooltipListenerInstalled = true; + } + + return NS_OK; +} + +// Unsubscribe from the various things we've hooked up to the window root. +NS_IMETHODIMP +ChromeTooltipListener::RemoveChromeListeners() +{ + HideTooltip(); + + if (mTooltipListenerInstalled) { + RemoveTooltipListener(); + } + + mEventTarget = nullptr; + + // it really doesn't matter if these fail... + return NS_OK; +} + +// Unsubscribe from all the various tooltip events that we were listening to. +NS_IMETHODIMP +ChromeTooltipListener::RemoveTooltipListener() +{ + if (mEventTarget) { + nsresult rv = NS_OK; + rv = mEventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("keydown"), + this, false); + NS_ENSURE_SUCCESS(rv, rv); + rv = mEventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("mousedown"), + this, false); + NS_ENSURE_SUCCESS(rv, rv); + rv = mEventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("mouseout"), + this, false); + NS_ENSURE_SUCCESS(rv, rv); + rv = mEventTarget->RemoveSystemEventListener(NS_LITERAL_STRING("mousemove"), + this, false); + NS_ENSURE_SUCCESS(rv, rv); + + mTooltipListenerInstalled = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +ChromeTooltipListener::HandleEvent(nsIDOMEvent* aEvent) +{ + nsAutoString eventType; + aEvent->GetType(eventType); + + if (eventType.EqualsLiteral("keydown") || + eventType.EqualsLiteral("mousedown")) { + return HideTooltip(); + } else if (eventType.EqualsLiteral("mouseout")) { + // Reset flag so that tooltip will display on the next MouseMove + mTooltipShownOnce = false; + return HideTooltip(); + } else if (eventType.EqualsLiteral("mousemove")) { + return MouseMove(aEvent); + } + + NS_ERROR("Unexpected event type"); + return NS_OK; +} + +// If we're a tooltip, fire off a timer to see if a tooltip should be shown. If +// the timer fires, we cache the node in |mPossibleTooltipNode|. +nsresult +ChromeTooltipListener::MouseMove(nsIDOMEvent* aMouseEvent) +{ + nsCOMPtr<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(aMouseEvent)); + if (!mouseEvent) { + return NS_OK; + } + + // stash the coordinates of the event so that we can still get back to it from + // within the timer callback. On win32, we'll get a MouseMove event even when + // a popup goes away -- even when the mouse doesn't change position! To get + // around this, we make sure the mouse has really moved before proceeding. + int32_t newMouseX, newMouseY; + mouseEvent->GetClientX(&newMouseX); + mouseEvent->GetClientY(&newMouseY); + if (mMouseClientX == newMouseX && mMouseClientY == newMouseY) { + return NS_OK; + } + + // Filter out minor mouse movements. + if (mShowingTooltip && + (abs(mMouseClientX - newMouseX) <= kTooltipMouseMoveTolerance) && + (abs(mMouseClientY - newMouseY) <= kTooltipMouseMoveTolerance)) { + return NS_OK; + } + + mMouseClientX = newMouseX; + mMouseClientY = newMouseY; + mouseEvent->GetScreenX(&mMouseScreenX); + mouseEvent->GetScreenY(&mMouseScreenY); + + if (mTooltipTimer) { + mTooltipTimer->Cancel(); + } + + if (!mShowingTooltip && !mTooltipShownOnce) { + mTooltipTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (mTooltipTimer) { + nsCOMPtr<EventTarget> eventTarget = + aMouseEvent->InternalDOMEvent()->GetTarget(); + if (eventTarget) { + mPossibleTooltipNode = do_QueryInterface(eventTarget); + } + if (mPossibleTooltipNode) { + nsresult rv = mTooltipTimer->InitWithFuncCallback( + sTooltipCallback, this, + LookAndFeel::GetInt(LookAndFeel::eIntID_TooltipDelay, 500), + nsITimer::TYPE_ONE_SHOT); + if (NS_FAILED(rv)) { + mPossibleTooltipNode = nullptr; + } + } + } else { + NS_WARNING("Could not create a timer for tooltip tracking"); + } + } else { + mTooltipShownOnce = true; + return HideTooltip(); + } + + return NS_OK; +} + +// Tell the registered chrome that they should show the tooltip. +NS_IMETHODIMP +ChromeTooltipListener::ShowTooltip(int32_t aInXCoords, int32_t aInYCoords, + const nsAString& aInTipText, + const nsAString& aTipDir) +{ + nsresult rv = NS_OK; + + // do the work to call the client + nsCOMPtr<nsITooltipListener> tooltipListener( + do_QueryInterface(mWebBrowserChrome)); + if (tooltipListener) { + rv = tooltipListener->OnShowTooltip(aInXCoords, aInYCoords, + PromiseFlatString(aInTipText).get(), + PromiseFlatString(aTipDir).get()); + if (NS_SUCCEEDED(rv)) { + mShowingTooltip = true; + } + } + + return rv; +} + +// Tell the registered chrome that they should rollup the tooltip +// NOTE: This routine is safe to call even if the popup is already closed. +NS_IMETHODIMP +ChromeTooltipListener::HideTooltip() +{ + nsresult rv = NS_OK; + + // shut down the relevant timers + if (mTooltipTimer) { + mTooltipTimer->Cancel(); + mTooltipTimer = nullptr; + // release tooltip target + mPossibleTooltipNode = nullptr; + } + + // if we're showing the tip, tell the chrome to hide it + if (mShowingTooltip) { + nsCOMPtr<nsITooltipListener> tooltipListener( + do_QueryInterface(mWebBrowserChrome)); + if (tooltipListener) { + rv = tooltipListener->OnHideTooltip(); + if (NS_SUCCEEDED(rv)) { + mShowingTooltip = false; + } + } + } + + return rv; +} + +// A timer callback, fired when the mouse has hovered inside of a frame for the +// appropriate amount of time. Getting to this point means that we should show +// the tooltip, but only after we determine there is an appropriate TITLE +// element. +// +// This relies on certain things being cached into the |aChromeTooltipListener| +// object passed to us by the timer: +// -- the x/y coordinates of the mouse (mMouseClientY, mMouseClientX) +// -- the dom node the user hovered over (mPossibleTooltipNode) +void +ChromeTooltipListener::sTooltipCallback(nsITimer* aTimer, + void* aChromeTooltipListener) +{ + auto self = static_cast<ChromeTooltipListener*>(aChromeTooltipListener); + if (self && self->mPossibleTooltipNode) { + // The actual coordinates we want to put the tooltip at are relative to the + // toplevel docshell of our mWebBrowser. We know what the screen + // coordinates of the mouse event were, which means we just need the screen + // coordinates of the docshell. Unfortunately, there is no good way to + // find those short of groveling for the presentation in that docshell and + // finding the screen coords of its toplevel widget... + nsCOMPtr<nsIDocShell> docShell = + do_GetInterface(static_cast<nsIWebBrowser*>(self->mWebBrowser)); + nsCOMPtr<nsIPresShell> shell; + if (docShell) { + shell = docShell->GetPresShell(); + } + + nsIWidget* widget = nullptr; + if (shell) { + nsViewManager* vm = shell->GetViewManager(); + if (vm) { + nsView* view = vm->GetRootView(); + if (view) { + nsPoint offset; + widget = view->GetNearestWidget(&offset); + } + } + } + + if (!widget) { + // release tooltip target if there is one, NO MATTER WHAT + self->mPossibleTooltipNode = nullptr; + return; + } + + // if there is text associated with the node, show the tip and fire + // off a timer to auto-hide it. + + nsXPIDLString tooltipText; + nsXPIDLString directionText; + if (self->mTooltipTextProvider) { + bool textFound = false; + + self->mTooltipTextProvider->GetNodeText( + self->mPossibleTooltipNode, getter_Copies(tooltipText), + getter_Copies(directionText), &textFound); + + if (textFound) { + nsString tipText(tooltipText); + nsString dirText(directionText); + LayoutDeviceIntPoint screenDot = widget->WidgetToScreenOffset(); + double scaleFactor = 1.0; + if (shell->GetPresContext()) { + nsDeviceContext* dc = shell->GetPresContext()->DeviceContext(); + scaleFactor = double(nsPresContext::AppUnitsPerCSSPixel()) / + dc->AppUnitsPerDevPixelAtUnitFullZoom(); + } + // ShowTooltip expects widget-relative position. + self->ShowTooltip(self->mMouseScreenX - screenDot.x / scaleFactor, + self->mMouseScreenY - screenDot.y / scaleFactor, + tipText, dirText); + } + } + + // release tooltip target if there is one, NO MATTER WHAT + self->mPossibleTooltipNode = nullptr; + } +} + +NS_IMPL_ISUPPORTS(ChromeContextMenuListener, nsIDOMEventListener) + +ChromeContextMenuListener::ChromeContextMenuListener( + nsWebBrowser* aInBrowser, + nsIWebBrowserChrome* aInChrome) + : mContextMenuListenerInstalled(false) + , mWebBrowser(aInBrowser) + , mWebBrowserChrome(aInChrome) +{ +} + +ChromeContextMenuListener::~ChromeContextMenuListener() +{ +} + +// Subscribe to the events that will allow us to track context menus. Bascially, +// this is just the context-menu DOM event. +NS_IMETHODIMP +ChromeContextMenuListener::AddContextMenuListener() +{ + if (mEventTarget) { + nsresult rv = mEventTarget->AddEventListener( + NS_LITERAL_STRING("contextmenu"), this, false, false); + NS_ENSURE_SUCCESS(rv, rv); + + mContextMenuListenerInstalled = true; + } + + return NS_OK; +} + +// Unsubscribe from all the various context menu events that we were listening +// to. +NS_IMETHODIMP +ChromeContextMenuListener::RemoveContextMenuListener() +{ + if (mEventTarget) { + nsresult rv = mEventTarget->RemoveEventListener( + NS_LITERAL_STRING("contextmenu"), this, false); + NS_ENSURE_SUCCESS(rv, rv); + + mContextMenuListenerInstalled = false; + } + + return NS_OK; +} + +// Hook up things to the chrome like context menus and tooltips, if the chrome +// has implemented the right interfaces. +NS_IMETHODIMP +ChromeContextMenuListener::AddChromeListeners() +{ + if (!mEventTarget) { + GetDOMEventTarget(mWebBrowser, getter_AddRefs(mEventTarget)); + } + + // Register the appropriate events for context menus, but only if + // the embedding chrome cares. + nsresult rv = NS_OK; + + nsCOMPtr<nsIContextMenuListener2> contextListener2( + do_QueryInterface(mWebBrowserChrome)); + nsCOMPtr<nsIContextMenuListener> contextListener( + do_QueryInterface(mWebBrowserChrome)); + if ((contextListener || contextListener2) && !mContextMenuListenerInstalled) { + rv = AddContextMenuListener(); + } + + return rv; +} + +// Unsubscribe from the various things we've hooked up to the window root. +NS_IMETHODIMP +ChromeContextMenuListener::RemoveChromeListeners() +{ + if (mContextMenuListenerInstalled) { + RemoveContextMenuListener(); + } + + mEventTarget = nullptr; + + // it really doesn't matter if these fail... + return NS_OK; +} + +// We're on call to show the context menu. Dig around in the DOM to find the +// type of object we're dealing with and notify the front end chrome. +NS_IMETHODIMP +ChromeContextMenuListener::HandleEvent(nsIDOMEvent* aMouseEvent) +{ + nsCOMPtr<nsIDOMMouseEvent> mouseEvent = do_QueryInterface(aMouseEvent); + NS_ENSURE_TRUE(mouseEvent, NS_ERROR_UNEXPECTED); + + bool isDefaultPrevented = false; + aMouseEvent->GetDefaultPrevented(&isDefaultPrevented); + if (isDefaultPrevented) { + return NS_OK; + } + + nsCOMPtr<EventTarget> targetNode = + aMouseEvent->InternalDOMEvent()->GetTarget(); + if (!targetNode) { + return NS_ERROR_NULL_POINTER; + } + + nsCOMPtr<nsIDOMNode> targetDOMnode; + nsCOMPtr<nsIDOMNode> node = do_QueryInterface(targetNode); + if (!node) { + return NS_OK; + } + + // Stop the context menu event going to other windows (bug 78396) + aMouseEvent->PreventDefault(); + + // If the listener is a nsIContextMenuListener2, create the info object + nsCOMPtr<nsIContextMenuListener2> menuListener2( + do_QueryInterface(mWebBrowserChrome)); + nsContextMenuInfo* menuInfoImpl = nullptr; + nsCOMPtr<nsIContextMenuInfo> menuInfo; + if (menuListener2) { + menuInfoImpl = new nsContextMenuInfo; + menuInfo = menuInfoImpl; + } + + uint32_t flags = nsIContextMenuListener::CONTEXT_NONE; + uint32_t flags2 = nsIContextMenuListener2::CONTEXT_NONE; + + // XXX test for selected text + + uint16_t nodeType; + nsresult res = node->GetNodeType(&nodeType); + NS_ENSURE_SUCCESS(res, res); + + // First, checks for nodes that never have children. + if (nodeType == nsIDOMNode::ELEMENT_NODE) { + nsCOMPtr<nsIImageLoadingContent> content(do_QueryInterface(node)); + if (content) { + nsCOMPtr<nsIURI> imgUri; + content->GetCurrentURI(getter_AddRefs(imgUri)); + if (imgUri) { + flags |= nsIContextMenuListener::CONTEXT_IMAGE; + flags2 |= nsIContextMenuListener2::CONTEXT_IMAGE; + targetDOMnode = node; + } + } + + nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(node)); + if (formControl) { + if (formControl->GetType() == NS_FORM_TEXTAREA) { + flags |= nsIContextMenuListener::CONTEXT_TEXT; + flags2 |= nsIContextMenuListener2::CONTEXT_TEXT; + targetDOMnode = node; + } else { + nsCOMPtr<nsIDOMHTMLInputElement> inputElement( + do_QueryInterface(formControl)); + if (inputElement) { + flags |= nsIContextMenuListener::CONTEXT_INPUT; + flags2 |= nsIContextMenuListener2::CONTEXT_INPUT; + + if (menuListener2) { + if (formControl->IsSingleLineTextControl(false)) { + flags2 |= nsIContextMenuListener2::CONTEXT_TEXT; + } + } + + targetDOMnode = node; + } + } + } + + // always consume events for plugins and Java who may throw their + // own context menus but not for image objects. Document objects + // will never be targets or ancestors of targets, so that's OK. + nsCOMPtr<nsIDOMHTMLObjectElement> objectElement; + if (!(flags & nsIContextMenuListener::CONTEXT_IMAGE)) { + objectElement = do_QueryInterface(node); + } + nsCOMPtr<nsIDOMHTMLEmbedElement> embedElement(do_QueryInterface(node)); + nsCOMPtr<nsIDOMHTMLAppletElement> appletElement(do_QueryInterface(node)); + + if (objectElement || embedElement || appletElement) { + return NS_OK; + } + } + + // Bubble out, looking for items of interest + do { + uint16_t nodeType; + res = node->GetNodeType(&nodeType); + NS_ENSURE_SUCCESS(res, res); + + if (nodeType == nsIDOMNode::ELEMENT_NODE) { + + // Test if the element has an associated link + nsCOMPtr<nsIDOMElement> element(do_QueryInterface(node)); + + bool hasAttr = false; + res = element->HasAttribute(NS_LITERAL_STRING("href"), &hasAttr); + + if (NS_SUCCEEDED(res) && hasAttr) { + flags |= nsIContextMenuListener::CONTEXT_LINK; + flags2 |= nsIContextMenuListener2::CONTEXT_LINK; + if (!targetDOMnode) { + targetDOMnode = node; + } + if (menuInfoImpl) { + menuInfoImpl->SetAssociatedLink(node); + } + break; // exit do-while + } + } + + // walk-up-the-tree + nsCOMPtr<nsIDOMNode> parentNode; + node->GetParentNode(getter_AddRefs(parentNode)); + node = parentNode; + } while (node); + + if (!flags && !flags2) { + // We found nothing of interest so far, check if we + // have at least an html document. + nsCOMPtr<nsIDOMDocument> document; + node = do_QueryInterface(targetNode); + node->GetOwnerDocument(getter_AddRefs(document)); + nsCOMPtr<nsIDOMHTMLDocument> htmlDocument(do_QueryInterface(document)); + if (htmlDocument) { + flags |= nsIContextMenuListener::CONTEXT_DOCUMENT; + flags2 |= nsIContextMenuListener2::CONTEXT_DOCUMENT; + targetDOMnode = node; + if (!(flags & nsIContextMenuListener::CONTEXT_IMAGE)) { + // check if this is a background image that the user was trying to click + // on and if the listener is ready for that (only + // nsIContextMenuListener2 and up) + if (menuInfoImpl && menuInfoImpl->HasBackgroundImage(targetDOMnode)) { + flags2 |= nsIContextMenuListener2::CONTEXT_BACKGROUND_IMAGE; + // For the embedder to get the correct background image + // targetDOMnode must point to the original node. + targetDOMnode = do_QueryInterface(targetNode); + } + } + } + } + + // we need to cache the event target into the focus controller's popupNode + // so we can get at it later from command code, etc.: + + // get the dom window + nsCOMPtr<mozIDOMWindowProxy> win; + res = mWebBrowser->GetContentDOMWindow(getter_AddRefs(win)); + NS_ENSURE_SUCCESS(res, res); + NS_ENSURE_TRUE(win, NS_ERROR_FAILURE); + + auto* window = nsPIDOMWindowOuter::From(win); + nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot(); + NS_ENSURE_TRUE(root, NS_ERROR_FAILURE); + if (root) { + // set the window root's popup node to the event target + root->SetPopupNode(targetDOMnode); + } + + // Tell the listener all about the event + if (menuListener2) { + menuInfoImpl->SetMouseEvent(aMouseEvent); + menuInfoImpl->SetDOMNode(targetDOMnode); + menuListener2->OnShowContextMenu(flags2, menuInfo); + } else { + nsCOMPtr<nsIContextMenuListener> menuListener( + do_QueryInterface(mWebBrowserChrome)); + if (menuListener) { + menuListener->OnShowContextMenu(flags, aMouseEvent, targetDOMnode); + } + } + + return NS_OK; +} diff --git a/embedding/browser/nsDocShellTreeOwner.h b/embedding/browser/nsDocShellTreeOwner.h new file mode 100644 index 000000000..d935ba165 --- /dev/null +++ b/embedding/browser/nsDocShellTreeOwner.h @@ -0,0 +1,246 @@ +/* -*- 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 nsDocShellTreeOwner_h__ +#define nsDocShellTreeOwner_h__ + +// Helper Classes +#include "nsCOMPtr.h" +#include "nsString.h" + +// Interfaces Needed +#include "nsIBaseWindow.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIWebBrowserChrome.h" +#include "nsIDOMEventListener.h" +#include "nsIEmbeddingSiteWindow.h" +#include "nsIWebProgressListener.h" +#include "nsWeakReference.h" +#include "nsITimer.h" +#include "nsIPrompt.h" +#include "nsIAuthPrompt.h" +#include "nsITooltipListener.h" +#include "nsITooltipTextProvider.h" +#include "nsCTooltipTextProvider.h" +#include "nsIDroppedLinkHandler.h" +#include "nsCommandHandler.h" + +namespace mozilla { +namespace dom { +class EventTarget; +} // namespace dom +} // namespace mozilla + +class nsWebBrowser; +class ChromeTooltipListener; +class ChromeContextMenuListener; + +// {6D10C180-6888-11d4-952B-0020183BF181} +#define NS_ICDOCSHELLTREEOWNER_IID \ + { 0x6d10c180, 0x6888, 0x11d4, { 0x95, 0x2b, 0x0, 0x20, 0x18, 0x3b, 0xf1, 0x81 } } + +// This is a fake 'hidden' interface that nsDocShellTreeOwner implements. +// Classes such as nsCommandHandler can QI for this interface to be sure that +// they're dealing with a valid nsDocShellTreeOwner and not some other object +// that implements nsIDocShellTreeOwner. +class nsICDocShellTreeOwner : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICDOCSHELLTREEOWNER_IID) +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsICDocShellTreeOwner, NS_ICDOCSHELLTREEOWNER_IID) + +class nsDocShellTreeOwner final : public nsIDocShellTreeOwner, + public nsIBaseWindow, + public nsIInterfaceRequestor, + public nsIWebProgressListener, + public nsIDOMEventListener, + public nsICDocShellTreeOwner, + public nsSupportsWeakReference +{ + friend class nsWebBrowser; + friend class nsCommandHandler; + +public: + NS_DECL_ISUPPORTS + + NS_DECL_NSIBASEWINDOW + NS_DECL_NSIDOCSHELLTREEOWNER + NS_DECL_NSIDOMEVENTLISTENER + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSIWEBPROGRESSLISTENER + +protected: + nsDocShellTreeOwner(); + virtual ~nsDocShellTreeOwner(); + + void WebBrowser(nsWebBrowser* aWebBrowser); + + nsWebBrowser* WebBrowser(); + NS_IMETHOD SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner); + NS_IMETHOD SetWebBrowserChrome(nsIWebBrowserChrome* aWebBrowserChrome); + + NS_IMETHOD AddChromeListeners(); + NS_IMETHOD RemoveChromeListeners(); + + nsresult FindItemWithNameAcrossWindows( + const char16_t* aName, + nsIDocShellTreeItem* aRequestor, nsIDocShellTreeItem* aOriginalRequestor, + nsIDocShellTreeItem** aFoundItem); + + void EnsurePrompter(); + void EnsureAuthPrompter(); + + void AddToWatcher(); + void RemoveFromWatcher(); + + void EnsureContentTreeOwner(); + + // These helper functions return the correct instances of the requested + // interfaces. If the object passed to SetWebBrowserChrome() implements + // nsISupportsWeakReference, then these functions call QueryReferent on + // that object. Otherwise, they return an addrefed pointer. If the + // WebBrowserChrome object doesn't exist, they return nullptr. + already_AddRefed<nsIWebBrowserChrome> GetWebBrowserChrome(); + already_AddRefed<nsIEmbeddingSiteWindow> GetOwnerWin(); + already_AddRefed<nsIInterfaceRequestor> GetOwnerRequestor(); + +protected: + // Weak References + nsWebBrowser* mWebBrowser; + nsIDocShellTreeOwner* mTreeOwner; + nsIDocShellTreeItem* mPrimaryContentShell; + + nsIWebBrowserChrome* mWebBrowserChrome; + nsIEmbeddingSiteWindow* mOwnerWin; + nsIInterfaceRequestor* mOwnerRequestor; + + nsWeakPtr mWebBrowserChromeWeak; // nsIWebBrowserChrome + + // the objects that listen for chrome events like context menus and tooltips. + // They are separate objects to avoid circular references between |this| + // and the DOM. + RefPtr<ChromeTooltipListener> mChromeTooltipListener; + RefPtr<ChromeContextMenuListener> mChromeContextMenuListener; + + RefPtr<nsDocShellTreeOwner> mContentTreeOwner; + + nsCOMPtr<nsIPrompt> mPrompter; + nsCOMPtr<nsIAuthPrompt> mAuthPrompter; + nsCOMPtr<nsITabParent> mPrimaryTabParent; +}; + + +// The class that listens to the chrome events and tells the embedding chrome to +// show tooltips, as appropriate. Handles registering itself with the DOM with +// AddChromeListeners() and removing itself with RemoveChromeListeners(). +class ChromeTooltipListener final : public nsIDOMEventListener +{ +protected: + virtual ~ChromeTooltipListener(); + +public: + NS_DECL_ISUPPORTS + + ChromeTooltipListener(nsWebBrowser* aInBrowser, nsIWebBrowserChrome* aInChrome); + + NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override; + NS_IMETHOD MouseMove(nsIDOMEvent* aMouseEvent); + + // Add/remove the relevant listeners, based on what interfaces the embedding + // chrome implements. + NS_IMETHOD AddChromeListeners(); + NS_IMETHOD RemoveChromeListeners(); + +private: + // various delays for tooltips + enum + { + kTooltipAutoHideTime = 5000, // ms + kTooltipMouseMoveTolerance = 7 // pixel tolerance for mousemove event + }; + + NS_IMETHOD AddTooltipListener(); + NS_IMETHOD RemoveTooltipListener(); + + NS_IMETHOD ShowTooltip(int32_t aInXCoords, int32_t aInYCoords, + const nsAString& aInTipText, + const nsAString& aDirText); + NS_IMETHOD HideTooltip(); + + nsWebBrowser* mWebBrowser; + nsCOMPtr<mozilla::dom::EventTarget> mEventTarget; + nsCOMPtr<nsITooltipTextProvider> mTooltipTextProvider; + + // This must be a strong ref in order to make sure we can hide the tooltip if + // the window goes away while we're displaying one. If we don't hold a strong + // ref, the chrome might have been disposed of before we get a chance to tell + // it, and no one would ever tell us of that fact. + nsCOMPtr<nsIWebBrowserChrome> mWebBrowserChrome; + + bool mTooltipListenerInstalled; + + nsCOMPtr<nsITimer> mTooltipTimer; + static void sTooltipCallback(nsITimer* aTimer, void* aListener); + + // Mouse coordinates for last mousemove event we saw + int32_t mMouseClientX; + int32_t mMouseClientY; + + // Mouse coordinates for tooltip event + int32_t mMouseScreenX; + int32_t mMouseScreenY; + + bool mShowingTooltip; + bool mTooltipShownOnce; + + // The node hovered over that fired the timer. This may turn into the node + // that triggered the tooltip, but only if the timer ever gets around to + // firing. This is a strong reference, because the tooltip content can be + // destroyed while we're waiting for the tooltip to pup up, and we need to + // detect that. It's set only when the tooltip timer is created and launched. + // The timer must either fire or be cancelled (or possibly released?), and we + // release this reference in each of those cases. So we don't leak. + nsCOMPtr<nsIDOMNode> mPossibleTooltipNode; +}; + +// The class that listens to the chrome events and tells the embedding chrome to +// show context menus, as appropriate. Handles registering itself with the DOM +// with AddChromeListeners() and removing itself with RemoveChromeListeners(). +class ChromeContextMenuListener : public nsIDOMEventListener +{ +protected: + virtual ~ChromeContextMenuListener(); + +public: + NS_DECL_ISUPPORTS + + ChromeContextMenuListener(nsWebBrowser* aInBrowser, + nsIWebBrowserChrome* aInChrome); + + // nsIDOMContextMenuListener + NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) override; + + // Add/remove the relevant listeners, based on what interfaces + // the embedding chrome implements. + NS_IMETHOD AddChromeListeners(); + NS_IMETHOD RemoveChromeListeners(); + +private: + NS_IMETHOD AddContextMenuListener(); + NS_IMETHOD RemoveContextMenuListener(); + + bool mContextMenuListenerInstalled; + + nsWebBrowser* mWebBrowser; + nsCOMPtr<mozilla::dom::EventTarget> mEventTarget; + nsCOMPtr<nsIWebBrowserChrome> mWebBrowserChrome; +}; + +#endif /* nsDocShellTreeOwner_h__ */ diff --git a/embedding/browser/nsEmbedStream.cpp b/embedding/browser/nsEmbedStream.cpp new file mode 100644 index 000000000..35347060c --- /dev/null +++ b/embedding/browser/nsEmbedStream.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 "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIDocShell.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIPipe.h" + +#include "nsEmbedStream.h" +#include "nsError.h" +#include "nsString.h" + +NS_IMPL_ISUPPORTS0(nsEmbedStream) + +nsEmbedStream::nsEmbedStream() +{ + mOwner = nullptr; +} + +nsEmbedStream::~nsEmbedStream() +{ +} + +void +nsEmbedStream::InitOwner(nsIWebBrowser* aOwner) +{ + mOwner = aOwner; +} + +nsresult +nsEmbedStream::Init(void) +{ + return NS_OK; +} + +nsresult +nsEmbedStream::OpenStream(nsIURI* aBaseURI, const nsACString& aContentType) +{ + nsresult rv; + NS_ENSURE_ARG_POINTER(aBaseURI); + NS_ENSURE_TRUE(IsASCII(aContentType), NS_ERROR_INVALID_ARG); + + // if we're already doing a stream, return an error + if (mOutputStream) { + return NS_ERROR_IN_PROGRESS; + } + + nsCOMPtr<nsIAsyncInputStream> inputStream; + nsCOMPtr<nsIAsyncOutputStream> outputStream; + rv = NS_NewPipe2(getter_AddRefs(inputStream), getter_AddRefs(outputStream), + true, false, 0, UINT32_MAX); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIDocShell> docShell = do_GetInterface(mOwner); + rv = docShell->LoadStream(inputStream, aBaseURI, aContentType, + EmptyCString(), nullptr); + if (NS_FAILED(rv)) { + return rv; + } + + mOutputStream = outputStream; + return rv; +} + +nsresult +nsEmbedStream::AppendToStream(const uint8_t* aData, uint32_t aLen) +{ + nsresult rv; + NS_ENSURE_STATE(mOutputStream); + + uint32_t bytesWritten = 0; + rv = mOutputStream->Write(reinterpret_cast<const char*>(aData), + aLen, &bytesWritten); + if (NS_FAILED(rv)) { + return rv; + } + + NS_ASSERTION(bytesWritten == aLen, + "underlying buffer couldn't handle the write"); + return rv; +} + +nsresult +nsEmbedStream::CloseStream(void) +{ + nsresult rv = NS_OK; + + // NS_ENSURE_STATE returns NS_ERROR_UNEXPECTED if the condition isn't + // satisfied; this is exactly what we want to return. + NS_ENSURE_STATE(mOutputStream); + mOutputStream->Close(); + mOutputStream = nullptr; + + return rv; +} diff --git a/embedding/browser/nsEmbedStream.h b/embedding/browser/nsEmbedStream.h new file mode 100644 index 000000000..03dca7936 --- /dev/null +++ b/embedding/browser/nsEmbedStream.h @@ -0,0 +1,37 @@ +/* -*- 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 nsEmbedStream_h__ +#define nsEmbedStream_h__ + +#include "nsCOMPtr.h" +#include "nsIOutputStream.h" +#include "nsIURI.h" +#include "nsIWebBrowser.h" + +class nsEmbedStream : public nsISupports +{ +public: + nsEmbedStream(); + + void InitOwner(nsIWebBrowser* aOwner); + nsresult Init(void); + + nsresult OpenStream(nsIURI* aBaseURI, const nsACString& aContentType); + nsresult AppendToStream(const uint8_t* aData, uint32_t aLen); + nsresult CloseStream(void); + + NS_DECL_ISUPPORTS + +protected: + virtual ~nsEmbedStream(); + +private: + nsIWebBrowser* mOwner; + nsCOMPtr<nsIOutputStream> mOutputStream; +}; + +#endif // nsEmbedStream_h__ diff --git a/embedding/browser/nsICommandHandler.idl b/embedding/browser/nsICommandHandler.idl new file mode 100644 index 000000000..15a2fd23b --- /dev/null +++ b/embedding/browser/nsICommandHandler.idl @@ -0,0 +1,40 @@ +/* -*- 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; + +[scriptable, uuid(08aed3cc-69f7-47ba-a110-f2efa8a6d7ea)] +interface nsICommandHandlerInit : nsISupports +{ + attribute mozIDOMWindowProxy window; +}; + +[scriptable, uuid(34A4FCF0-66FC-11d4-9528-0020183BF181)] +interface nsICommandHandler : nsISupports +{ + /* + * Execute the specified command with the specified parameters and return + * the result to the caller. The format of the command, parameters and + * the result are determined by the acutal implementation. + */ + string exec(in string aCommand, in string aParameters); + /* + * Query the status of the specified command with the specified parameters + * and return the result to the caller. The format of the command, + * parameters and the result are determined by the implementation. + */ + string query(in string aCommand, in string aParameters); +}; + +%{ C++ +// {3A449110-66FD-11d4-9528-0020183BF181} - +#define NS_COMMANDHANDLER_CID \ +{ 0x3a449110, 0x66fd, 0x11d4, { 0x95, 0x28, 0x0, 0x20, 0x18, 0x3b, 0xf1, 0x81 } } +#define NS_COMMANDHANDLER_CONTRACTID \ +"@mozilla.org/embedding/browser/nsCommandHandler;1" +%} + diff --git a/embedding/browser/nsIContextMenuListener.idl b/embedding/browser/nsIContextMenuListener.idl new file mode 100644 index 000000000..2e4d1c1e9 --- /dev/null +++ b/embedding/browser/nsIContextMenuListener.idl @@ -0,0 +1,65 @@ +/* -*- 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" + +interface nsIDOMEvent; +interface nsIDOMNode; + +/** + * An optional interface for embedding clients wishing to receive + * notifications for context menu events (e.g. generated by + * a user right-mouse clicking on a link). The embedder implements + * this interface on the web browser chrome object associated + * with the window that notifications are required for. When a context + * menu event, the browser will call this interface if present. + * + * @see nsIDOMNode + * @see nsIDOMEvent + */ +[scriptable, uuid(3478b6b0-3875-11d4-94ef-0020183bf181)] +interface nsIContextMenuListener : nsISupports +{ + /** Flag. No context. */ + const unsigned long CONTEXT_NONE = 0; + /** Flag. Context is a link element. */ + const unsigned long CONTEXT_LINK = 1; + /** Flag. Context is an image element. */ + const unsigned long CONTEXT_IMAGE = 2; + /** Flag. Context is the whole document. */ + const unsigned long CONTEXT_DOCUMENT = 4; + /** Flag. Context is a text area element. */ + const unsigned long CONTEXT_TEXT = 8; + /** Flag. Context is an input element. */ + const unsigned long CONTEXT_INPUT = 16; + + /** + * Called when the browser receives a context menu event (e.g. user is right-mouse + * clicking somewhere on the document). The combination of flags, event and node + * provided in the call indicate where and what was clicked on. + * + * The following table describes what context flags and node combinations are + * possible. + * + * <TABLE> + * <TR><TD><B>aContextFlag</B></TD><TD>aNode</TD></TR> + * <TR><TD>CONTEXT_LINK</TD><TD><A></TD></TR> + * <TR><TD>CONTEXT_IMAGE</TD><TD><IMG></TD></TR> + * <TR><TD>CONTEXT_IMAGE | CONTEXT_LINK</TD><TD><IMG> + * with an <A> as an ancestor</TD></TR> + * <TR><TD>CONTEXT_INPUT</TD><TD><INPUT></TD></TR> + * <TR><TD>CONTEXT_TEXT</TD><TD><TEXTAREA></TD></TR> + * <TR><TD>CONTEXT_DOCUMENT</TD><TD><HTML></TD></TR> + * </TABLE> + * + * @param aContextFlags Flags indicating the kind of context. + * @param aEvent The DOM context menu event. + * @param aNode The DOM node most relevant to the context. + * + * @return <CODE>NS_OK</CODE> always. + */ + void onShowContextMenu(in unsigned long aContextFlags, in nsIDOMEvent aEvent, in nsIDOMNode aNode); +}; + diff --git a/embedding/browser/nsIContextMenuListener2.idl b/embedding/browser/nsIContextMenuListener2.idl new file mode 100644 index 000000000..19d898011 --- /dev/null +++ b/embedding/browser/nsIContextMenuListener2.idl @@ -0,0 +1,121 @@ +/* -*- Mode: IDL; 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 nsIDOMEvent; +interface nsIDOMNode; +interface imgIContainer; +interface nsIURI; +interface nsIContextMenuInfo; + +/* THIS IS A PUBLIC EMBEDDING API */ + +/** + * nsIContextMenuListener2 + * + * This is an extended version of nsIContextMenuListener + * It provides a helper class, nsIContextMenuInfo, to allow access to + * background images as well as various utilities. + * + * @see nsIContextMenuListener + * @see nsIContextMenuInfo + */ + +[scriptable, uuid(7fb719b3-d804-4964-9596-77cf924ee314)] +interface nsIContextMenuListener2 : nsISupports +{ + /** Flag. No context. */ + const unsigned long CONTEXT_NONE = 0; + /** Flag. Context is a link element. */ + const unsigned long CONTEXT_LINK = 1; + /** Flag. Context is an image element. */ + const unsigned long CONTEXT_IMAGE = 2; + /** Flag. Context is the whole document. */ + const unsigned long CONTEXT_DOCUMENT = 4; + /** Flag. Context is a text area element. */ + const unsigned long CONTEXT_TEXT = 8; + /** Flag. Context is an input element. */ + const unsigned long CONTEXT_INPUT = 16; + /** Flag. Context is a background image. */ + const unsigned long CONTEXT_BACKGROUND_IMAGE = 32; + + /** + * Called when the browser receives a context menu event (e.g. user is right-mouse + * clicking somewhere on the document). The combination of flags, along with the + * attributes of <CODE>aUtils</CODE>, indicate where and what was clicked on. + * + * The following table describes what context flags and node combinations are + * possible. + * + * aContextFlags aUtils.targetNode + * + * CONTEXT_LINK <A> + * CONTEXT_IMAGE <IMG> + * CONTEXT_IMAGE | CONTEXT_LINK <IMG> with <A> as an ancestor + * CONTEXT_INPUT <INPUT> + * CONTEXT_INPUT | CONTEXT_IMAGE <INPUT> with type=image + * CONTEXT_TEXT <TEXTAREA> + * CONTEXT_DOCUMENT <HTML> + * CONTEXT_BACKGROUND_IMAGE <HTML> with background image + * + * @param aContextFlags Flags indicating the kind of context. + * @param aUtils Context information and helper utilities. + * + * @see nsIContextMenuInfo + */ + void onShowContextMenu(in unsigned long aContextFlags, in nsIContextMenuInfo aUtils); +}; + +/** + * nsIContextMenuInfo + * + * A helper object for implementors of nsIContextMenuListener2. + */ + +[scriptable, uuid(2f977d56-5485-11d4-87e2-0010a4e75ef2)] +interface nsIContextMenuInfo : nsISupports +{ + /** + * The DOM context menu event. + */ + readonly attribute nsIDOMEvent mouseEvent; + + /** + * The DOM node most relevant to the context. + */ + readonly attribute nsIDOMNode targetNode; + + /** + * Given the <CODE>CONTEXT_LINK</CODE> flag, <CODE>targetNode</CODE> may not + * nescesarily be a link. This returns the anchor from <CODE>targetNode</CODE> + * if it has one or that of its nearest ancestor if it does not. + */ + readonly attribute AString associatedLink; + + /** + * Given the <CODE>CONTEXT_IMAGE</CODE> flag, these methods can be + * used in order to get the image for viewing, saving, or for the clipboard. + * + * @return <CODE>NS_OK</CODE> if successful, otherwise <CODE>NS_ERROR_FAILURE</CODE> if no + * image was found, or NS_ERROR_NULL_POINTER if an internal error occurs where we think there + * is an image, but for some reason it cannot be returned. + */ + + readonly attribute imgIContainer imageContainer; + readonly attribute nsIURI imageSrc; + + /** + * Given the <CODE>CONTEXT_BACKGROUND_IMAGE</CODE> flag, these methods can be + * used in order to get the image for viewing, saving, or for the clipboard. + * + * @return <CODE>NS_OK</CODE> if successful, otherwise <CODE>NS_ERROR_FAILURE</CODE> if no background + * image was found, or NS_ERROR_NULL_POINTER if an internal error occurs where we think there is a + * background image, but for some reason it cannot be returned. + */ + + readonly attribute imgIContainer backgroundImageContainer; + readonly attribute nsIURI backgroundImageSrc; +}; diff --git a/embedding/browser/nsIEmbeddingSiteWindow.idl b/embedding/browser/nsIEmbeddingSiteWindow.idl new file mode 100644 index 000000000..a8eefacb4 --- /dev/null +++ b/embedding/browser/nsIEmbeddingSiteWindow.idl @@ -0,0 +1,157 @@ +/* -*- 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" + +/* THIS IS A PUBLIC EMBEDDING API */ + +/** + * The nsIEmbeddingSiteWindow is implemented by the embedder to provide + * Gecko with the means to call up to the host to resize the window, + * hide or show it and set/get its title. + */ +[scriptable, uuid(0b976267-4aaa-4f36-a2d4-27b5ca8d73bb)] +interface nsIEmbeddingSiteWindow : nsISupports +{ + /** + * Flag indicates that position of the top left corner of the outer area + * is required/specified. + * + * @see setDimensions + * @see getDimensions + */ + const unsigned long DIM_FLAGS_POSITION = 1; + + /** + * Flag indicates that the size of the inner area is required/specified. + * + * @note The inner and outer flags are mutually exclusive and it is + * invalid to combine them. + * + * @see setDimensions + * @see getDimensions + * @see DIM_FLAGS_SIZE_OUTER + */ + const unsigned long DIM_FLAGS_SIZE_INNER = 2; + + /** + * Flag indicates that the size of the outer area is required/specified. + * + * @see setDimensions + * @see getDimensions + * @see DIM_FLAGS_SIZE_INNER + */ + const unsigned long DIM_FLAGS_SIZE_OUTER = 4; + + /** + * Flag indicates that the x parameter should be ignored. + * + * @see setDimensions + */ + const unsigned long DIM_FLAGS_IGNORE_X = 8; + + /** + * Flag indicates that the y parameter should be ignored. + * + * @see setDimensions + */ + const unsigned long DIM_FLAGS_IGNORE_Y = 16; + + /** + * Flag indicates that the cx parameter should be ignored. + * + * @see setDimensions + */ + const unsigned long DIM_FLAGS_IGNORE_CX = 32; + + /** + * Flag indicates that the cy parameter should be ignored. + * + * @see setDimensions + */ + const unsigned long DIM_FLAGS_IGNORE_CY = 64; + + + /** + * Sets the dimensions for the window; the position & size. The + * flags to indicate what the caller wants to set and whether the size + * refers to the inner or outer area. The inner area refers to just + * the embedded area, wheras the outer area can also include any + * surrounding chrome, window frame, title bar, and so on. + * + * @param flags Combination of position, inner and outer size flags. + * The ignore flags are telling the parent to use the + * current values for those dimensions and ignore the + * corresponding parameters the child sends. + * @param x Left hand corner of the outer area. + * @param y Top corner of the outer area. + * @param cx Width of the inner or outer area. + * @param cy Height of the inner or outer area. + * + * @return <code>NS_OK</code> if operation was performed correctly; + * <code>NS_ERROR_UNEXPECTED</code> if window could not be + * destroyed; + * <code>NS_ERROR_INVALID_ARG</code> for bad flag combination + * or illegal dimensions. + * + * @see getDimensions + * @see DIM_FLAGS_POSITION + * @see DIM_FLAGS_SIZE_OUTER + * @see DIM_FLAGS_SIZE_INNER + */ + void setDimensions(in unsigned long flags, in long x, in long y, in long cx, in long cy); + + /** + * Gets the dimensions of the window. The caller may pass + * <CODE>nullptr</CODE> for any value it is uninterested in receiving. + * + * @param flags Combination of position, inner and outer size flag . + * @param x Left hand corner of the outer area; or <CODE>nullptr</CODE>. + * @param y Top corner of the outer area; or <CODE>nullptr</CODE>. + * @param cx Width of the inner or outer area; or <CODE>nullptr</CODE>. + * @param cy Height of the inner or outer area; or <CODE>nullptr</CODE>. + * + * @see setDimensions + * @see DIM_FLAGS_POSITION + * @see DIM_FLAGS_SIZE_OUTER + * @see DIM_FLAGS_SIZE_INNER + */ + void getDimensions(in unsigned long flags, out long x, out long y, out long cx, out long cy); + + /** + * Give the window focus. + */ + void setFocus(); + + /** + * Visibility of the window. + */ + attribute boolean visibility; + + /** + * Title of the window. + */ + attribute wstring title; + + /** + * Native window for the site's window. The implementor should copy the + * native window object into the address supplied by the caller. The + * type of the native window that the address refers to is platform + * and OS specific as follows: + * + * <ul> + * <li>On Win32 it is an <CODE>HWND</CODE>.</li> + * <li>On MacOS this is a <CODE>WindowPtr</CODE>.</li> + * <li>On GTK this is a <CODE>GtkWidget*</CODE>.</li> + * </ul> + */ + [noscript] readonly attribute voidPtr siteWindow; + + /** + * Blur the window. This should unfocus the window and send an onblur event. + */ + void blur(); +}; diff --git a/embedding/browser/nsIPrintPreviewNavigation.idl b/embedding/browser/nsIPrintPreviewNavigation.idl new file mode 100644 index 000000000..87a39ff72 --- /dev/null +++ b/embedding/browser/nsIPrintPreviewNavigation.idl @@ -0,0 +1,52 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + + +/** + * The nsIPrintPreviewNavigation + */ +[scriptable, uuid(8148E3F1-2E8B-11d5-A86C-00105A183419)] +interface nsIPrintPreviewNavigation : nsISupports +{ + + readonly attribute long pageCount; + + + /** + * Preview the next Page + * + * Return - PR_TRUE if success + */ + boolean nextPage(); + + /** + * Preview the previous Page + * + * Return - PR_TRUE if success + */ + boolean previousPage(); + + /** + * Go to a page to preview + * + * aPageNumber - Page to go preview + * Return - PR_TRUE if success + */ + boolean goToPage(unsigned long aPageNumber); + + + /** + * Skip pages + * + * aNumPages - number of pages to skip including the current page. Neg. goes back + * Return - true if success + */ + boolean skipPages(long aNumPages); + + +}; diff --git a/embedding/browser/nsIPrintingPromptService.idl b/embedding/browser/nsIPrintingPromptService.idl new file mode 100644 index 000000000..81d79b57c --- /dev/null +++ b/embedding/browser/nsIPrintingPromptService.idl @@ -0,0 +1,165 @@ +/* -*- 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/. */ + +/* Doc interface here */ + +#include "nsISupports.idl" +#include "nsIWebBrowserPrint.idl" +#include "nsIWebProgressListener.idl" +#include "nsIPrintProgressParams.idl" +#include "nsIPrintSettings.idl" +#include "nsIObserver.idl" + +interface nsIDOMWindow; + +[scriptable, uuid(328daa3e-09e4-455f-bb6f-0a921766042f)] +interface nsIPrintingPromptService : nsISupports +{ + /** + * This service enables embedders to implement their own Print and Progress Dialogs. + * Each platform has a "base" or "basckstop" implementation of the service. The + * service is automatically registered at start up. + * + * Historically, platform toolkits with native dialogs have implemented them in the GFX layer + * Usually they were displayed when a new DeviceContextSpec specific to that platform + * was created. + * + * Windows: The GFX layer no longers supports default toolkit behavior for displaying the + * native Print Dialog. + * If an embedder implemented service returns any error code (other than NS_ERROR_ABORT) + * printing will terminate. + * + * Returning NS_OK assumes that the PrintSettings object was correctly filled in and + * if it does not have valid fields for printer name, etc. it may also terminate. + * + * Defaults for platform service: + * showPrintDialog - displays a native dialog + * showPageSetup - displays a XUL dialog + * showProgress - displays a XUL dialog + * showPrinterProperties - n/a + * + * Summary for Windows Embedders: + * Stated once again: There is no "fallback" native platform support in GFX for the + * displaying of the native print dialog. The current default implementation for Windows + * display a native print dialog but a XUL-based progress dialog. + * If you wish to have a native progress dialog on Windows you will have to create and + * register your own service. + * + * Note: The Windows version Mozilla implements this service which is + * automatically built and registered for you. You can use it as an example. + * It is located at "mozilla/embedding/components/printingui/win". That service + * is capable of displaying a native print dialog and a XUL progress dialog. + * + * To fly your own dialog you may: + * + * 1) Implement this service to display at least the Print Dialog and a Print Progress Dialog + * or you may implement just one of the dialogs and pass back NS_ERROR_NOT_IMPLEMENTED + * for any of the others. + * + * 2) For the Print Dialog: + * You may stub out this service by having all the methods return NS_ERROR_NOT_IMPLEMENTED. + * You can then fly you own dialog and then properly fill in the PrintSettings object + * before calling nsIWebBrowserPrint's Print method. If you stub out this service + * you MUST set "printSilent" to true, if you do not, Printing will terminate and an + * error dialog will be displayed. + * + * Mac: The GFX layer still supports default toolkit behavior for displaying the Print Dialog. + * If an embedder implemented service returns NS_ERROR_NOT_IMPLEMENTED for "showPrintDialog" + * The toolkit will display the native print dialog. + * + * Defaults for platform service: + * Mac OS9: showPrintDialog - displays a native dialog + * showPageSetup - displays a native dialog + * showProgress - displays a XUL dialog + * showPrinterProperties - n/a + * + * Mac OSX: showPrintDialog - displays a native dialog + * showPageSetup - displays a native dialog + * showProgress - not implemented (provided by OS) + * showPrinterProperties - n/a + * + * GTK: There are no native dialog for GTK. + * + * Defaults for platform service: + * showPrintDialog - displays a XUL dialog + * showPageSetup - displays a XUL dialog + * showProgress - displays a XUL dialog + * showPrinterProperties - displays a XUL dialog + * + */ + + + + /** + * Show the Print Dialog + * + * @param parent - a DOM windows the dialog will be parented to (required) + * @param webBrowserPrint - represents the document to be printed (required) + * @param printSettings - PrintSettings for print "job" (required) + * + */ + void showPrintDialog(in mozIDOMWindowProxy parent, + in nsIWebBrowserPrint webBrowserPrint, + in nsIPrintSettings printSettings); + + /** + * Shows the print progress dialog + * + * @param parent - a DOM windows the dialog will be parented to + * @param webBrowserPrint - represents the document to be printed + * @param printSettings - PrintSettings for print "job" + * @param openDialogObserver - an observer that will be notifed when the dialog is opened + * @param isForPrinting - true - for printing, false for print preview + * @param webProgressListener - additional listener can be registered for progress notifications + * @param printProgressParams - parameter object for passing progress state + * @param notifyOnOpen - this indicates that the observer will be notified when the progress + * dialog has been opened. If false is returned it means the observer + * (usually the caller) shouldn't wait + * For Print Preview Progress there is intermediate progress + */ + void showProgress(in mozIDOMWindowProxy parent, + in nsIWebBrowserPrint webBrowserPrint, + in nsIPrintSettings printSettings, + in nsIObserver openDialogObserver, + in boolean isForPrinting, + out nsIWebProgressListener webProgressListener, + out nsIPrintProgressParams printProgressParams, + out boolean notifyOnOpen); + + /** + * Shows the print progress dialog + * + * @param parent - a DOM windows the dialog will be parented to (required) + * @param printSettings - PrintSettings for page setup (required) + * @param aObs - An observer to know if the contents of the Print Settings + * object has changed while the dialog is being shown. + * For example, some platforms may implement an "Apply" button (not required) + */ + void showPageSetup(in mozIDOMWindowProxy parent, + in nsIPrintSettings printSettings, + in nsIObserver aObs); + + /** + * Sometimes platforms need to bring up a special properties dialog for showing + * print specific properties. Although the PrintSettings has a place to set the + * printer name, here is is an argument to be clear as to what printer is being + * asked to have the properties set for it. The Printer name in the PS is ignored. + * + * @param parent - a DOM windows the dialog will be parented to (required) + * @param printerName - name of printer (required) + * @param printSettings - PrintSettings for page setup (required) + */ + void showPrinterProperties(in mozIDOMWindowProxy parent, + in wstring printerName, + in nsIPrintSettings printSettings); + +}; + +%{C++ +// {260FEDC5-524D-4aa6-9A41-E829F4C78B92} +#define NS_PRINTINGPROMPTSERVICE_IID \ + {0x260fedc5, 0x524d, 0x4aa6, { 0x9a, 0x41, 0xe8, 0x29, 0xf4, 0xc7, 0x8b, 0x92}} +%} + diff --git a/embedding/browser/nsITooltipListener.idl b/embedding/browser/nsITooltipListener.idl new file mode 100644 index 000000000..c6e53736f --- /dev/null +++ b/embedding/browser/nsITooltipListener.idl @@ -0,0 +1,44 @@ +/* -*- 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" + +/** + * An optional interface for embedding clients wishing to receive + * notifications for when a tooltip should be displayed or removed. + * The embedder implements this interface on the web browser chrome + * object associated with the window that notifications are required + * for. + * + * @see nsITooltipTextProvider + */ +[scriptable, uuid(44b78386-1dd2-11b2-9ad2-e4eee2ca1916)] +interface nsITooltipListener : nsISupports +{ + /** + * Called when a tooltip should be displayed. + * + * @param aXCoords The tooltip left edge X coordinate. + * @param aYCoords The tooltip top edge Y coordinate. + * @param aTipText The text to display in the tooltip, typically obtained + * from the TITLE attribute of the node (or containing parent) + * over which the pointer has been positioned. + * @param aTipDir The direction (ltr or rtl) in which to display the text + * + * @note + * Coordinates are specified in pixels, relative to the top-left + * corner of the browser area. + * + * @return <code>NS_OK</code> if the tooltip was displayed. + */ + void onShowTooltip(in long aXCoords, in long aYCoords, in wstring aTipText, + in wstring aTipDir); + + /** + * Called when the tooltip should be hidden, either because the pointer + * has moved or the tooltip has timed out. + */ + void onHideTooltip(); +}; diff --git a/embedding/browser/nsITooltipTextProvider.idl b/embedding/browser/nsITooltipTextProvider.idl new file mode 100644 index 000000000..e87344ae0 --- /dev/null +++ b/embedding/browser/nsITooltipTextProvider.idl @@ -0,0 +1,44 @@ +/* -*- 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 "nsISupports.idl" + +interface nsIDOMNode; + +/** + * An interface implemented by a tooltip text provider service. This + * service is called to discover what tooltip text is associated + * with the node that the pointer is positioned over. + * + * Embedders may implement and register their own tooltip text provider + * service if they wish to provide different tooltip text. + * + * The default service returns the text stored in the TITLE + * attribute of the node or a containing parent. + * + * @note + * The tooltip text provider service is registered with the contract + * defined in NS_TOOLTIPTEXTPROVIDER_CONTRACTID. + * + * @see nsITooltipListener + * @see nsIComponentManager + * @see nsIDOMNode + */ +[scriptable, uuid(b128a1e6-44f3-4331-8fbe-5af360ff21ee)] +interface nsITooltipTextProvider : nsISupports +{ + /** + * Called to obtain the tooltip text for a node. + * + * @arg aNode The node to obtain the text from. + * @arg aText The tooltip text. + * @arg aDirection The text direction (ltr or rtl) to use + * + * @return <CODE>PR_TRUE</CODE> if tooltip text is associated + * with the node and was returned in the aText argument; + * <CODE>PR_FALSE</CODE> otherwise. + */ + boolean getNodeText(in nsIDOMNode aNode, out wstring aText, out wstring aDirection); +}; diff --git a/embedding/browser/nsIWebBrowser.idl b/embedding/browser/nsIWebBrowser.idl new file mode 100644 index 000000000..e6ff70ae7 --- /dev/null +++ b/embedding/browser/nsIWebBrowser.idl @@ -0,0 +1,163 @@ +/* -*- 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" + +interface nsIInterfaceRequestor; +interface nsIWebBrowserChrome; +interface nsIURIContentListener; +interface nsIDOMWindow; +interface mozIDOMWindowProxy; +interface nsIWeakReference; + +%{C++ +namespace mozilla { +class DocShellOriginAttributes; +} +%} + +[ref] native const_OriginAttributesRef(const mozilla::DocShellOriginAttributes); + +/** + * The nsIWebBrowser interface is implemented by web browser objects. + * Embedders use this interface during initialisation to associate + * the new web browser instance with the embedders chrome and + * to register any listeners. The interface may also be used at runtime + * to obtain the content DOM window and from that the rest of the DOM. + */ +[scriptable, uuid(4052b6da-4faa-4646-b3a1-7e16a01c2dc2)] +interface nsIWebBrowser : nsISupports +{ + /** + * Registers a listener of the type specified by the iid to receive + * callbacks. The browser stores a weak reference to the listener + * to avoid any circular dependencies. + * Typically this method will be called to register an object + * to receive <CODE>nsIWebProgressListener</CODE> or + * <CODE>nsISHistoryListener</CODE> notifications in which case the + * the IID is that of the interface. + * + * @param aListener The listener to be added. + * @param aIID The IID of the interface that will be called + * on the listener as appropriate. + * @return <CODE>NS_OK</CODE> for successful registration; + * <CODE>NS_ERROR_INVALID_ARG</CODE> if aIID is not + * supposed to be registered using this method; + * <CODE>NS_ERROR_FAILURE</CODE> either aListener did not + * expose the interface specified by the IID, or some + * other internal error occurred. + * + * @see removeWebBrowserListener + * @see nsIWeakReference + * @see nsIWebProgressListener + * @see nsISHistoryListener + * + * @return <CODE>NS_OK</CODE>, listener was successfully added; + * <CODE>NS_ERROR_INVALID_ARG</CODE>, one of the arguments was + * invalid or the object did not implement the interface + * specified by the IID. + */ + void addWebBrowserListener(in nsIWeakReference aListener, in nsIIDRef aIID); + + /** + * Removes a previously registered listener. + * + * @param aListener The listener to be removed. + * @param aIID The IID of the interface on the listener that will + * no longer be called. + * + * @return <CODE>NS_OK</CODE>, listener was successfully removed; + * <CODE>NS_ERROR_INVALID_ARG</CODE> arguments was invalid or + * the object did not implement the interface specified by the IID. + * + * @see addWebBrowserListener + * @see nsIWeakReference + */ + void removeWebBrowserListener(in nsIWeakReference aListener, in nsIIDRef aIID); + + /** + * The chrome object associated with the browser instance. The embedder + * must create one chrome object for <I>each</I> browser object + * that is instantiated. The embedder must associate the two by setting + * this property to point to the chrome object before creating the browser + * window via the browser's <CODE>nsIBaseWindow</CODE> interface. + * + * The chrome object must also implement <CODE>nsIEmbeddingSiteWindow</CODE>. + * + * The chrome may optionally implement <CODE>nsIInterfaceRequestor</CODE>, + * <CODE>nsIWebBrowserChromeFocus</CODE>, + * <CODE>nsIContextMenuListener</CODE> and + * <CODE>nsITooltipListener</CODE> to receive additional notifications + * from the browser object. + * + * The chrome object may optionally implement <CODE>nsIWebProgressListener</CODE> + * instead of explicitly calling <CODE>addWebBrowserListener</CODE> and + * <CODE>removeWebBrowserListener</CODE> to register a progress listener + * object. If the implementation does this, it must also implement + * <CODE>nsIWeakReference</CODE>. + * + * @note The implementation should not refcount the supplied chrome + * object; it should assume that a non <CODE>nullptr</CODE> value is + * always valid. The embedder must explicitly set this value back + * to nullptr if the chrome object is destroyed before the browser + * object. + * + * @see nsIBaseWindow + * @see nsIWebBrowserChrome + * @see nsIEmbeddingSiteWindow + * @see nsIInterfaceRequestor + * @see nsIWebBrowserChromeFocus + * @see nsIContextMenuListener + * @see nsITooltipListener + * @see nsIWeakReference + * @see nsIWebProgressListener + */ + attribute nsIWebBrowserChrome containerWindow; + + /** + * URI content listener parent. The embedder may set this property to + * their own implementation if they intend to override or prevent + * how certain kinds of content are loaded. + * + * @note If this attribute is set to an object that implements + * nsISupportsWeakReference, the implementation should get the + * nsIWeakReference and hold that. Otherwise, the implementation + * should not refcount this interface; it should assume that a non + * null value is always valid. In that case, the embedder should + * explicitly set this value back to null if the parent content + * listener is destroyed before the browser object. + * + * @see nsIURIContentListener + */ + attribute nsIURIContentListener parentURIContentListener; + + /** + * The top-level DOM window. The embedder may walk the entire + * DOM starting from this value. + * + * @see nsIDOMWindow + */ + readonly attribute mozIDOMWindowProxy contentDOMWindow; + + /** + * Whether this web browser is active. Active means that it's visible + * enough that we want to avoid certain optimizations like discarding + * decoded image data and throttling the refresh driver. In Firefox, + * this corresponds to the visible tab. + * + * Defaults to true. For optimal performance, set it to false when + * appropriate. + */ + attribute boolean isActive; + + /** + * Set Origin Attributes on the nsIWebBrowser. + * The Origin Attributes will be passed to the docshell once it has been + * created + */ + [noscript, notxpcom, nostdcall, binaryname(SetOriginAttributes)] + void binarySetOriginAttributes(in const_OriginAttributesRef aOriginAttrs); +}; diff --git a/embedding/browser/nsIWebBrowserChrome.idl b/embedding/browser/nsIWebBrowserChrome.idl new file mode 100644 index 000000000..40f03cbe4 --- /dev/null +++ b/embedding/browser/nsIWebBrowserChrome.idl @@ -0,0 +1,147 @@ +/* -*- 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" + +interface nsIWebBrowser; +interface nsIDocShellTreeItem; + +/** + * nsIWebBrowserChrome corresponds to the top-level, outermost window + * containing an embedded Gecko web browser. + */ + +[scriptable, uuid(E8C414C4-DC38-4BA3-AB4E-EC4CBBE22907)] +interface nsIWebBrowserChrome : nsISupports +{ + const unsigned long STATUS_SCRIPT = 0x00000001; + const unsigned long STATUS_LINK = 0x00000003; + + /** + * Called when the status text in the chrome needs to be updated. + * @param statusType indicates what is setting the text + * @param status status string. null is an acceptable value meaning + * no status. + */ + void setStatus(in unsigned long statusType, in wstring status); + + /** + * The currently loaded WebBrowser. The browser chrome may be + * told to set the WebBrowser object to a new object by setting this + * attribute. In this case the implementer is responsible for taking the + * new WebBrowser object and doing any necessary initialization or setup + * as if it had created the WebBrowser itself. This includes positioning + * setting up listeners etc. + */ + attribute nsIWebBrowser webBrowser; + + /** + * Definitions for the chrome flags + */ + const unsigned long CHROME_DEFAULT = 0x00000001; + const unsigned long CHROME_WINDOW_BORDERS = 0x00000002; + const unsigned long CHROME_WINDOW_CLOSE = 0x00000004; + const unsigned long CHROME_WINDOW_RESIZE = 0x00000008; + const unsigned long CHROME_MENUBAR = 0x00000010; + const unsigned long CHROME_TOOLBAR = 0x00000020; + const unsigned long CHROME_LOCATIONBAR = 0x00000040; + const unsigned long CHROME_STATUSBAR = 0x00000080; + const unsigned long CHROME_PERSONAL_TOOLBAR = 0x00000100; + const unsigned long CHROME_SCROLLBARS = 0x00000200; + const unsigned long CHROME_TITLEBAR = 0x00000400; + const unsigned long CHROME_EXTRA = 0x00000800; + + // createBrowserWindow specific flags + const unsigned long CHROME_WITH_SIZE = 0x00001000; + const unsigned long CHROME_WITH_POSITION = 0x00002000; + + // special cases + const unsigned long CHROME_WINDOW_MIN = 0x00004000; + const unsigned long CHROME_WINDOW_POPUP = 0x00008000; + + // whether to open a new private window. CHROME_NON_PRIVATE_WINDOW + // forces the opened window to be non-private, and overrides + // CHROME_PRIVATE_WINDOW if it's set. CHROME_PRIVATE_WINDOW + // forces the opened window to be private. If neither of these + // flags are specified, the opened window will inherit the privacy + // status of its opener. If there is no opener window, the new + // window will be non-private. + // + // CHROME_PRIVATE_LIFETIME causes the docshell to affect private-browsing + // session lifetime. This flag is currently respected only for remote + // docshells. + const unsigned long CHROME_PRIVATE_WINDOW = 0x00010000; + const unsigned long CHROME_NON_PRIVATE_WINDOW = 0x00020000; + const unsigned long CHROME_PRIVATE_LIFETIME = 0x00040000; + + // Whether this was opened by nsGlobalWindow::ShowModalDialog. + const unsigned long CHROME_MODAL_CONTENT_WINDOW = 0x00080000; + + // Whether this window should use remote (out-of-process) tabs. + const unsigned long CHROME_REMOTE_WINDOW = 0x00100000; + + // Prevents new window animations on Mac OS X Lion. Ignored on other + // platforms. + const unsigned long CHROME_MAC_SUPPRESS_ANIMATION = 0x01000000; + + const unsigned long CHROME_WINDOW_RAISED = 0x02000000; + const unsigned long CHROME_WINDOW_LOWERED = 0x04000000; + const unsigned long CHROME_CENTER_SCREEN = 0x08000000; + + // Make the new window dependent on the parent. This flag is only + // meaningful if CHROME_OPENAS_CHROME is set; content windows should not be + // dependent. + const unsigned long CHROME_DEPENDENT = 0x10000000; + + // Note: The modal style bit just affects the way the window looks and does + // mean it's actually modal. + const unsigned long CHROME_MODAL = 0x20000000; + const unsigned long CHROME_OPENAS_DIALOG = 0x40000000; + const unsigned long CHROME_OPENAS_CHROME = 0x80000000; + + const unsigned long CHROME_ALL = 0x00000ffe; + + /** + * The chrome flags for this browser chrome. The implementation should + * reflect the value of this attribute by hiding or showing its chrome + * appropriately. + */ + attribute unsigned long chromeFlags; + + /** + * Asks the implementer to destroy the window associated with this + * WebBrowser object. + */ + void destroyBrowserWindow(); + + /** + * Tells the chrome to size itself such that the browser will be the + * specified size. + * @param aCX new width of the browser + * @param aCY new height of the browser + */ + void sizeBrowserTo(in long aCX, in long aCY); + + /** + * Shows the window as a modal window. + * @return (the function error code) the status value specified by + * in exitModalEventLoop. + */ + void showAsModal(); + + /** + * Is the window modal (that is, currently executing a modal loop)? + * @return true if it's a modal window + */ + boolean isWindowModal(); + + /** + * Exit a modal event loop if we're in one. The implementation + * should also exit out of the loop if the window is destroyed. + * @param aStatus - the result code to return from showAsModal + */ + void exitModalEventLoop(in nsresult aStatus); +}; diff --git a/embedding/browser/nsIWebBrowserChrome2.idl b/embedding/browser/nsIWebBrowserChrome2.idl new file mode 100644 index 000000000..4b479b400 --- /dev/null +++ b/embedding/browser/nsIWebBrowserChrome2.idl @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsIWebBrowserChrome.idl" + +/** + * nsIWebBrowserChrome2 is an extension to nsIWebBrowserChrome. + */ +[scriptable, uuid(2585a7b1-7b47-43c4-bf17-c6bf84e09b7b)] +interface nsIWebBrowserChrome2 : nsIWebBrowserChrome +{ + /** + * Called when the status text in the chrome needs to be updated. This + * method may be called instead of nsIWebBrowserChrome::SetStatus. An + * implementor of this method, should still implement SetStatus. + * + * @param statusType + * Indicates what is setting the text. + * @param status + * Status string. Null is an acceptable value meaning no status. + * @param contextNode + * An object that provides context pertaining to the status type. + * If statusType is STATUS_LINK, then statusContext may be a DOM + * node corresponding to the source of the link. This value can + * be null if there is no context. + */ + void setStatusWithContext(in unsigned long statusType, + in AString statusText, + in nsISupports statusContext); +}; diff --git a/embedding/browser/nsIWebBrowserChrome3.idl b/embedding/browser/nsIWebBrowserChrome3.idl new file mode 100644 index 000000000..a95cab911 --- /dev/null +++ b/embedding/browser/nsIWebBrowserChrome3.idl @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsIWebBrowserChrome2.idl" +#include "nsIURI.idl" +#include "nsIDOMNode.idl" + +interface nsIDocShell; +interface nsIInputStream; + +/** + * nsIWebBrowserChrome3 is an extension to nsIWebBrowserChrome2. + */ +[scriptable, uuid(542b6625-35a9-426a-8257-c12a345383b0)] +interface nsIWebBrowserChrome3 : nsIWebBrowserChrome2 +{ + /** + * Determines the appropriate target for a link. + * + * @param originalTarget + * The original link target. + * @param linkURI + * Link destination URI. + * @param aDOMNode + * Link DOM node. + * @param isAppTab + * Whether or not the link is in an app tab. + * @returns A new link target, if appropriate. + * Otherwise returns originalTarget. + */ + AString onBeforeLinkTraversal(in AString originalTarget, + in nsIURI linkURI, + in nsIDOMNode linkNode, + in boolean isAppTab); + + /** + * Determines whether a load should continue. + * + * @param aDocShell + * The docshell performing the load. + * @param aURI + * The URI being loaded. + * @param aReferrer + * The referrer of the load. + */ + bool shouldLoadURI(in nsIDocShell aDocShell, + in nsIURI aURI, + in nsIURI aReferrer); + + /** + * Attempts to load the currently loaded page into a fresh process to increase + * available memory. + * + * @param aDocShell + * The docshell performing the load. + */ + bool reloadInFreshProcess(in nsIDocShell aDocShell, + in nsIURI aURI, + in nsIURI aReferrer); +}; diff --git a/embedding/browser/nsIWebBrowserChromeFocus.idl b/embedding/browser/nsIWebBrowserChromeFocus.idl new file mode 100644 index 000000000..8e0b849b9 --- /dev/null +++ b/embedding/browser/nsIWebBrowserChromeFocus.idl @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 0 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * The nsIWebBrowserChromeFocus is implemented by the same object as the + * nsIEmbeddingSiteWindow. It represents the focus up-calls from mozilla + * to the embedding chrome. See mozilla bug #70224 for gratuitous info. + */ + +[scriptable, uuid(947B2EE6-51ED-4C2B-9F45-426C27CA84C6)] +interface nsIWebBrowserChromeFocus : nsISupports +{ + /** + * Set the focus at the next focusable element in the chrome. If + * aForDocumentNavigation is true, this was a document navigation, so + * focus the parent window. + */ + + void focusNextElement(in bool aForDocumentNavigation); + + /** + * Set the focus at the previous focusable element in the chrome. + */ + + void focusPrevElement(in bool aForDocumentNavigation); + +}; diff --git a/embedding/browser/nsIWebBrowserFocus.idl b/embedding/browser/nsIWebBrowserFocus.idl new file mode 100644 index 000000000..e2b4a2788 --- /dev/null +++ b/embedding/browser/nsIWebBrowserFocus.idl @@ -0,0 +1,76 @@ +/* -*- 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/. */ + +interface mozIDOMWindowProxy; +interface nsIDOMElement; + +#include "nsISupports.idl" + +/** + * nsIWebBrowserFocus + * Interface that embedders use for controlling and interacting + * with the browser focus management. The embedded browser can be focused by + * clicking in it or tabbing into it. If the browser is currently focused and + * the embedding application's top level window is disabled, deactivate() must + * be called, and activate() called again when the top level window is + * reactivated for the browser's focus memory to work correctly. + */ + +[scriptable, uuid(7f8c754e-5b36-44be-bc96-191b49f08ea6)] +interface nsIWebBrowserFocus : nsISupports +{ + /** + * MANDATORY + * activate() is a mandatory call that must be made to the browser + * when the embedding application's window is activated *and* the + * browser area was the last thing in focus. This method can also be called + * if the embedding application wishes to give the browser area focus, + * without affecting the currently focused element within the browser. + * + * @note + * If you fail to make this call, mozilla focus memory will not work + * correctly. + */ + void activate(); + + /** + * MANDATORY + * deactivate() is a mandatory call that must be made to the browser + * when the embedding application's window is deactivated *and* the + * browser area was the last thing in focus. On non-windows platforms, + * deactivate() should also be called when focus moves from the browser + * to the embedding chrome. + * + * @note + * If you fail to make this call, mozilla focus memory will not work + * correctly. + */ + void deactivate(); + + /** + * Give the first element focus within mozilla + * (i.e. TAB was pressed and focus should enter mozilla) + */ + void setFocusAtFirstElement(); + + /** + * Give the last element focus within mozilla + * (i.e. SHIFT-TAB was pressed and focus should enter mozilla) + */ + void setFocusAtLastElement(); + + /** + * The currently focused nsDOMWindow when the browser is active, + * or the last focused nsDOMWindow when the browser is inactive. + */ + attribute mozIDOMWindowProxy focusedWindow; + + /** + * The currently focused nsDOMElement when the browser is active, + * or the last focused nsDOMElement when the browser is inactive. + */ + attribute nsIDOMElement focusedElement; +}; diff --git a/embedding/browser/nsIWebBrowserPrint.idl b/embedding/browser/nsIWebBrowserPrint.idl new file mode 100644 index 000000000..c5a014e6a --- /dev/null +++ b/embedding/browser/nsIWebBrowserPrint.idl @@ -0,0 +1,152 @@ +/* -*- 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" + +interface mozIDOMWindowProxy; +interface nsIPrintSettings; +interface nsIWebProgressListener; + +/** + * nsIWebBrowserPrint corresponds to the main interface + * for printing an embedded Gecko web browser window/document + */ +[scriptable, uuid(c9a934ed-fff1-4971-bfba-6c25ad70e1e6)] +interface nsIWebBrowserPrint : nsISupports +{ + /** + * PrintPreview Navigation Constants + */ + const short PRINTPREVIEW_GOTO_PAGENUM = 0; + const short PRINTPREVIEW_PREV_PAGE = 1; + const short PRINTPREVIEW_NEXT_PAGE = 2; + const short PRINTPREVIEW_HOME = 3; + const short PRINTPREVIEW_END = 4; + + /** + * Returns a "global" PrintSettings object + * Creates a new the first time, if one doesn't exist. + * + * Then returns the same object each time after that. + * + * Initializes the globalPrintSettings from the default printer + */ + readonly attribute nsIPrintSettings globalPrintSettings; + + /** + * Returns a pointer to the PrintSettings object that + * that was passed into either "print" or "print preview" + * + * This enables any consumers of the interface to have access + * to the "current" PrintSetting at later points in the execution + */ + readonly attribute nsIPrintSettings currentPrintSettings; + + /** + * Returns a pointer to the current child DOMWindow + * that is being print previewed. (FrameSet Frames) + * + * Returns null if parent document is not a frameset or the entire FrameSet + * document is being print previewed + * + * This enables any consumers of the interface to have access + * to the "current" child DOMWindow at later points in the execution + */ + readonly attribute mozIDOMWindowProxy currentChildDOMWindow; + + /** + * Returns whether it is in Print mode + */ + readonly attribute boolean doingPrint; + + /** + * Returns whether it is in Print Preview mode + */ + readonly attribute boolean doingPrintPreview; + + /** + * This returns whether the current document is a frameset document + */ + readonly attribute boolean isFramesetDocument; + + /** + * This returns whether the current document is a frameset document + */ + readonly attribute boolean isFramesetFrameSelected; + + /** + * This returns whether there is an IFrame selected + */ + readonly attribute boolean isIFrameSelected; + + /** + * This returns whether there is a "range" selection + */ + readonly attribute boolean isRangeSelection; + + /** + * This returns the total number of pages for the Print Preview + */ + readonly attribute long printPreviewNumPages; + + /** + * Print the specified DOM window + * + * @param aThePrintSettings - Printer Settings for the print job, if aThePrintSettings is null + * then the global PS will be used. + * @param aWPListener - is updated during the print + * @return void + */ + void print(in nsIPrintSettings aThePrintSettings, + in nsIWebProgressListener aWPListener); + + /** + * Print Preview the specified DOM window + * + * @param aThePrintSettings - Printer Settings for the print preview, if aThePrintSettings is null + * then the global PS will be used. + * @param aChildDOMWin - DOM Window to be print previewed. + * @param aWPListener - is updated during the printpreview + * @return void + */ + void printPreview(in nsIPrintSettings aThePrintSettings, + in mozIDOMWindowProxy aChildDOMWin, + in nsIWebProgressListener aWPListener); + + /** + * Print Preview - Navigates within the window + * + * @param aNavType - navigation enum + * @param aPageNum - page num to navigate to when aNavType = ePrintPreviewGoToPageNum + * @return void + */ + void printPreviewNavigate(in short aNavType, in long aPageNum); + + /** + * Cancels the current print + * @return void + */ + void cancel(); + + /** + * Returns an array of the names of all documents names (Title or URL) + * and sub-documents. This will return a single item if the attr "isFramesetDocument" is false + * and may return any number of items is "isFramesetDocument" is true + * + * @param aCount - returns number of printers returned + * @param aResult - returns array of names + * @return void + */ + void enumerateDocumentNames(out uint32_t aCount,[retval, array, size_is(aCount)] out wstring aResult); + + /** + * This exists PrintPreview mode and returns browser window to galley mode + * @return void + */ + void exitPrintPreview(); + +}; + diff --git a/embedding/browser/nsIWebBrowserSetup.idl b/embedding/browser/nsIWebBrowserSetup.idl new file mode 100644 index 000000000..d30645126 --- /dev/null +++ b/embedding/browser/nsIWebBrowserSetup.idl @@ -0,0 +1,109 @@ +/* -*- Mode: IDL; 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" + +/** + * The nsIWebBrowserSetup interface lets you set properties on a browser + * object; you can do so at any time during the life cycle of the browser. + * + * @note Unless stated otherwise, settings are presumed to be enabled by + * default. + */ +[scriptable, uuid(F15398A0-8018-11d3-AF70-00A024FFC08C)] +interface nsIWebBrowserSetup : nsISupports +{ + /** + * Boolean. Enables/disables plugin support for this browser. + * + * @see setProperty + */ + const unsigned long SETUP_ALLOW_PLUGINS = 1; + + /** + * Boolean. Enables/disables Javascript support for this browser. + * + * @see setProperty + */ + const unsigned long SETUP_ALLOW_JAVASCRIPT = 2; + + /** + * Boolean. Enables/disables meta redirect support for this browser. + * Meta redirect timers will be ignored if this option is disabled. + * + * @see setProperty + */ + const unsigned long SETUP_ALLOW_META_REDIRECTS = 3; + + /** + * Boolean. Enables/disables subframes within the browser + * + * @see setProperty + */ + const unsigned long SETUP_ALLOW_SUBFRAMES = 4; + + /** + * Boolean. Enables/disables image loading for this browser + * window. If you disable the images, load a page, then enable the images, + * the page will *not* automatically load the images for the previously + * loaded page. This flag controls the state of a webBrowser at load time + * and does not automatically re-load a page when the state is toggled. + * Reloading must be done by hand, or by walking through the DOM tree and + * re-setting the src attributes. + * + * @see setProperty + */ + const unsigned long SETUP_ALLOW_IMAGES = 5; + + /** + * Boolean. Enables/disables whether the document as a whole gets focus before + * traversing the document's content, or after traversing its content. + * + * NOTE: this property is obsolete and now has no effect + * + * @see setProperty + */ + const unsigned long SETUP_FOCUS_DOC_BEFORE_CONTENT = 6; + + /** + * Boolean. Enables/disables the use of global history in the browser. Visited + * URLs will not be recorded in the global history when it is disabled. + * + * @see setProperty + */ + const unsigned long SETUP_USE_GLOBAL_HISTORY = 256; + + /** + * Boolean. A value of PR_TRUE makes the browser a chrome wrapper. + * Default is PR_FALSE. + * + * @since mozilla1.0 + * + * @see setProperty + */ + const unsigned long SETUP_IS_CHROME_WRAPPER = 7; + + + /** + * Boolean. Enables/disables DNS prefetch for HTML anchors in this browser. + * This takes effect starting with the next pageload after the property is + * set. The default is to not allow DNS prefetch, for backwards + * compatibility. + * + * @see setProperty + */ + const unsigned long SETUP_ALLOW_DNS_PREFETCH = 8; + + /** + * Sets an integer or boolean property on the new web browser object. + * Only PR_TRUE and PR_FALSE are legal boolean values. + * + * @param aId The identifier of the property to be set. + * @param aValue The value of the property. + */ + void setProperty(in unsigned long aId, in unsigned long aValue); +}; + diff --git a/embedding/browser/nsIWebBrowserStream.idl b/embedding/browser/nsIWebBrowserStream.idl new file mode 100644 index 000000000..9326b2eaa --- /dev/null +++ b/embedding/browser/nsIWebBrowserStream.idl @@ -0,0 +1,56 @@ +/* -*- 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 nsIURI; + +/** + * This interface provides a way to stream data to the web browser. This allows + * loading of data from sources which can not be accessed using URIs and + * nsIWebNavigation. + */ +[scriptable, uuid(86d02f0e-219b-4cfc-9c88-bd98d2cce0b8)] +interface nsIWebBrowserStream : nsISupports +{ + /** + * Prepare to load a stream of data. When this function returns successfully, + * it must be paired by a call to closeStream. + * + * @param aBaseURI + * The base URI of the data. Must not be null. Relative + * URIs will be resolved relative to this URI. + * @param aContentType + * ASCII string giving the content type of the data. If rendering + * content of this type is not supported, this method fails. + * This string may include a charset declaration, for example: + * text/html;charset=ISO-8859-1 + * + * @throw NS_ERROR_NOT_AVAILABLE + * The requested content type is not supported. + * @throw NS_ERROR_IN_PROGRESS + * openStream was called twice without an intermediate closeStream. + */ + void openStream(in nsIURI aBaseURI, in ACString aContentType); + + /** + * Append data to this stream. + * @param aData The data to append + * @param aLen Length of the data to append. + * + * @note To append more than 4 GB of data, call this method multiple times. + */ + void appendToStream([const, array, size_is(aLen)] in octet aData, + in unsigned long aLen); + + /** + * Notifies the browser that all the data has been appended. This may notify + * the user that the browser is "done loading" in some form. + * + * @throw NS_ERROR_UNEXPECTED + * This method was called without a preceding openStream. + */ + void closeStream(); +}; diff --git a/embedding/browser/nsWebBrowser.cpp b/embedding/browser/nsWebBrowser.cpp new file mode 100644 index 000000000..655aa1e43 --- /dev/null +++ b/embedding/browser/nsWebBrowser.cpp @@ -0,0 +1,1952 @@ +/* -*- 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/. */ + +// Local Includes +#include "nsWebBrowser.h" + +// Helper Classes +#include "nsGfxCIID.h" +#include "nsWidgetsCID.h" + +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" + +// Interfaces Needed +#include "nsReadableUtils.h" +#include "nsIComponentManager.h" +#include "nsIDOMDocument.h" +#include "nsIDOMWindow.h" +#include "nsIDOMElement.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIWebBrowserChrome.h" +#include "nsPIDOMWindow.h" +#include "nsIWebProgress.h" +#include "nsIWebProgressListener.h" +#include "nsIWebBrowserFocus.h" +#include "nsIWebBrowserStream.h" +#include "nsIPresShell.h" +#include "nsIURIContentListener.h" +#include "nsISHistoryListener.h" +#include "nsIURI.h" +#include "nsIWebBrowserPersist.h" +#include "nsCWebBrowserPersist.h" +#include "nsIServiceManager.h" +#include "nsFocusManager.h" +#include "Layers.h" +#include "gfxContext.h" +#include "nsILoadContext.h" +#include "nsDocShell.h" + +// for painting the background window +#include "mozilla/LookAndFeel.h" + +// Printing Includes +#ifdef NS_PRINTING +#include "nsIWebBrowserPrint.h" +#include "nsIContentViewer.h" +#endif + +// PSM2 includes +#include "nsISecureBrowserUI.h" +#include "nsXULAppAPI.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::layers; + +static NS_DEFINE_CID(kChildCID, NS_CHILD_CID); + +nsWebBrowser::nsWebBrowser() + : mInitInfo(new nsWebBrowserInitInfo()) + , mContentType(typeContentWrapper) + , mActivating(false) + , mShouldEnableHistory(true) + , mIsActive(true) + , mParentNativeWindow(nullptr) + , mProgressListener(nullptr) + , mWidgetListenerDelegate(this) + , mBackgroundColor(0) + , mPersistCurrentState(nsIWebBrowserPersist::PERSIST_STATE_READY) + , mPersistResult(NS_OK) + , mPersistFlags(nsIWebBrowserPersist::PERSIST_FLAGS_NONE) + , mParentWidget(nullptr) +{ + mWWatch = do_GetService(NS_WINDOWWATCHER_CONTRACTID); + NS_ASSERTION(mWWatch, "failed to get WindowWatcher"); +} + +nsWebBrowser::~nsWebBrowser() +{ + InternalDestroy(); +} + +NS_IMETHODIMP +nsWebBrowser::InternalDestroy() +{ + if (mInternalWidget) { + mInternalWidget->SetWidgetListener(nullptr); + mInternalWidget->Destroy(); + mInternalWidget = nullptr; // Force release here. + } + + SetDocShell(nullptr); + + if (mDocShellTreeOwner) { + mDocShellTreeOwner->WebBrowser(nullptr); + mDocShellTreeOwner = nullptr; + } + + mInitInfo = nullptr; + + mListenerArray = nullptr; + + return NS_OK; +} + +NS_IMPL_ADDREF(nsWebBrowser) +NS_IMPL_RELEASE(nsWebBrowser) + +NS_INTERFACE_MAP_BEGIN(nsWebBrowser) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebBrowser) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowser) + NS_INTERFACE_MAP_ENTRY(nsIWebNavigation) + NS_INTERFACE_MAP_ENTRY(nsIBaseWindow) + NS_INTERFACE_MAP_ENTRY(nsIScrollable) + NS_INTERFACE_MAP_ENTRY(nsITextScroll) + NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeItem) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserSetup) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersist) + NS_INTERFACE_MAP_ENTRY(nsICancelable) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserFocus) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserStream) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + +///***************************************************************************** +// nsWebBrowser::nsIInterfaceRequestor +//***************************************************************************** + +NS_IMETHODIMP +nsWebBrowser::GetInterface(const nsIID& aIID, void** aSink) +{ + NS_ENSURE_ARG_POINTER(aSink); + + if (NS_SUCCEEDED(QueryInterface(aIID, aSink))) { + return NS_OK; + } + + if (mDocShell) { +#ifdef NS_PRINTING + if (aIID.Equals(NS_GET_IID(nsIWebBrowserPrint))) { + nsCOMPtr<nsIContentViewer> viewer; + mDocShell->GetContentViewer(getter_AddRefs(viewer)); + if (!viewer) { + return NS_NOINTERFACE; + } + + nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint(do_QueryInterface(viewer)); + nsIWebBrowserPrint* print = (nsIWebBrowserPrint*)webBrowserPrint.get(); + NS_ASSERTION(print, "This MUST support this interface!"); + NS_ADDREF(print); + *aSink = print; + return NS_OK; + } +#endif + return mDocShellAsReq->GetInterface(aIID, aSink); + } + + return NS_NOINTERFACE; +} + +//***************************************************************************** +// nsWebBrowser::nsIWebBrowser +//***************************************************************************** + +// listeners that currently support registration through AddWebBrowserListener: +// - nsIWebProgressListener +NS_IMETHODIMP +nsWebBrowser::AddWebBrowserListener(nsIWeakReference* aListener, + const nsIID& aIID) +{ + NS_ENSURE_ARG_POINTER(aListener); + + nsresult rv = NS_OK; + if (!mWebProgress) { + // The window hasn't been created yet, so queue up the listener. They'll be + // registered when the window gets created. + if (!mListenerArray) { + mListenerArray = new nsTArray<nsWebBrowserListenerState>(); + } + + nsWebBrowserListenerState* state = mListenerArray->AppendElement(); + state->mWeakPtr = aListener; + state->mID = aIID; + } else { + nsCOMPtr<nsISupports> supports(do_QueryReferent(aListener)); + if (!supports) { + return NS_ERROR_INVALID_ARG; + } + rv = BindListener(supports, aIID); + } + + return rv; +} + +NS_IMETHODIMP +nsWebBrowser::BindListener(nsISupports* aListener, const nsIID& aIID) +{ + NS_ENSURE_ARG_POINTER(aListener); + NS_ASSERTION(mWebProgress, + "this should only be called after we've retrieved a progress iface"); + nsresult rv = NS_OK; + + // register this listener for the specified interface id + if (aIID.Equals(NS_GET_IID(nsIWebProgressListener))) { + nsCOMPtr<nsIWebProgressListener> listener = do_QueryInterface(aListener, &rv); + if (NS_FAILED(rv)) { + return rv; + } + NS_ENSURE_STATE(mWebProgress); + rv = mWebProgress->AddProgressListener(listener, nsIWebProgress::NOTIFY_ALL); + } else if (aIID.Equals(NS_GET_IID(nsISHistoryListener))) { + nsCOMPtr<nsISHistory> shistory(do_GetInterface(mDocShell, &rv)); + if (NS_FAILED(rv)) { + return rv; + } + nsCOMPtr<nsISHistoryListener> listener(do_QueryInterface(aListener, &rv)); + if (NS_FAILED(rv)) { + return rv; + } + rv = shistory->AddSHistoryListener(listener); + } + return rv; +} + +NS_IMETHODIMP +nsWebBrowser::RemoveWebBrowserListener(nsIWeakReference* aListener, + const nsIID& aIID) +{ + NS_ENSURE_ARG_POINTER(aListener); + + nsresult rv = NS_OK; + if (!mWebProgress) { + // if there's no-one to register the listener w/, and we don't have a queue + // going, the the called is calling Remove before an Add which doesn't make + // sense. + if (!mListenerArray) { + return NS_ERROR_FAILURE; + } + + // iterate the array and remove the queued listener + int32_t count = mListenerArray->Length(); + while (count > 0) { + if (mListenerArray->ElementAt(count-1).Equals(aListener, aIID)) { + mListenerArray->RemoveElementAt(count-1); + break; + } + count--; + } + + // if we've emptied the array, get rid of it. + if (0 >= mListenerArray->Length()) { + mListenerArray = nullptr; + } + + } else { + nsCOMPtr<nsISupports> supports(do_QueryReferent(aListener)); + if (!supports) { + return NS_ERROR_INVALID_ARG; + } + rv = UnBindListener(supports, aIID); + } + + return rv; +} + +NS_IMETHODIMP +nsWebBrowser::UnBindListener(nsISupports* aListener, const nsIID& aIID) +{ + NS_ENSURE_ARG_POINTER(aListener); + NS_ASSERTION(mWebProgress, + "this should only be called after we've retrieved a progress iface"); + nsresult rv = NS_OK; + + // remove the listener for the specified interface id + if (aIID.Equals(NS_GET_IID(nsIWebProgressListener))) { + nsCOMPtr<nsIWebProgressListener> listener = do_QueryInterface(aListener, &rv); + if (NS_FAILED(rv)) { + return rv; + } + NS_ENSURE_STATE(mWebProgress); + rv = mWebProgress->RemoveProgressListener(listener); + } else if (aIID.Equals(NS_GET_IID(nsISHistoryListener))) { + nsCOMPtr<nsISHistory> shistory(do_GetInterface(mDocShell, &rv)); + if (NS_FAILED(rv)) { + return rv; + } + nsCOMPtr<nsISHistoryListener> listener(do_QueryInterface(aListener, &rv)); + if (NS_FAILED(rv)) { + return rv; + } + rv = shistory->RemoveSHistoryListener(listener); + } + return rv; +} + +NS_IMETHODIMP +nsWebBrowser::EnableGlobalHistory(bool aEnable) +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShell->SetUseGlobalHistory(aEnable); +} + +NS_IMETHODIMP +nsWebBrowser::GetContainerWindow(nsIWebBrowserChrome** aTopWindow) +{ + NS_ENSURE_ARG_POINTER(aTopWindow); + + nsCOMPtr<nsIWebBrowserChrome> top; + if (mDocShellTreeOwner) { + top = mDocShellTreeOwner->GetWebBrowserChrome(); + } + + top.forget(aTopWindow); + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetContainerWindow(nsIWebBrowserChrome* aTopWindow) +{ + NS_ENSURE_SUCCESS(EnsureDocShellTreeOwner(), NS_ERROR_FAILURE); + return mDocShellTreeOwner->SetWebBrowserChrome(aTopWindow); +} + +NS_IMETHODIMP +nsWebBrowser::GetParentURIContentListener( + nsIURIContentListener** aParentContentListener) +{ + NS_ENSURE_ARG_POINTER(aParentContentListener); + *aParentContentListener = nullptr; + + // get the interface from the docshell + nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(mDocShell)); + + if (listener) { + return listener->GetParentContentListener(aParentContentListener); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetParentURIContentListener( + nsIURIContentListener* aParentContentListener) +{ + // get the interface from the docshell + nsCOMPtr<nsIURIContentListener> listener(do_GetInterface(mDocShell)); + + if (listener) { + return listener->SetParentContentListener(aParentContentListener); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWebBrowser::GetContentDOMWindow(mozIDOMWindowProxy** aResult) +{ + if (!mDocShell) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsPIDOMWindowOuter> retval = mDocShell->GetWindow(); + retval.forget(aResult); + return *aResult ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWebBrowser::GetIsActive(bool* aResult) +{ + *aResult = mIsActive; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetIsActive(bool aIsActive) +{ + // Set our copy of the value + mIsActive = aIsActive; + + // If we have a docshell, pass on the request + if (mDocShell) { + return mDocShell->SetIsActive(aIsActive); + } + return NS_OK; +} + +void +nsWebBrowser::SetOriginAttributes(const DocShellOriginAttributes& aAttrs) +{ + mOriginAttributes = aAttrs; +} + +//***************************************************************************** +// nsWebBrowser::nsIDocShellTreeItem +//***************************************************************************** + +NS_IMETHODIMP +nsWebBrowser::GetName(nsAString& aName) +{ + if (mDocShell) { + mDocShell->GetName(aName); + } else { + aName = mInitInfo->name; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetName(const nsAString& aName) +{ + if (mDocShell) { + return mDocShell->SetName(aName); + } else { + mInitInfo->name = aName; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::NameEquals(const nsAString& aName, bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + if (mDocShell) { + return mDocShell->NameEquals(aName, aResult); + } else { + *aResult = mInitInfo->name.Equals(aName); + } + + return NS_OK; +} + +/* virtual */ int32_t +nsWebBrowser::ItemType() +{ + return mContentType; +} + +NS_IMETHODIMP +nsWebBrowser::GetItemType(int32_t* aItemType) +{ + NS_ENSURE_ARG_POINTER(aItemType); + + *aItemType = ItemType(); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetItemType(int32_t aItemType) +{ + NS_ENSURE_TRUE( + aItemType == typeContentWrapper || aItemType == typeChromeWrapper, + NS_ERROR_FAILURE); + mContentType = aItemType; + if (mDocShell) { + mDocShell->SetItemType(mContentType == typeChromeWrapper ? + static_cast<int32_t>(typeChrome) : + static_cast<int32_t>(typeContent)); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetParent(nsIDocShellTreeItem** aParent) +{ + *aParent = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetSameTypeParent(nsIDocShellTreeItem** aParent) +{ + *aParent = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetRootTreeItem(nsIDocShellTreeItem** aRootTreeItem) +{ + NS_ENSURE_ARG_POINTER(aRootTreeItem); + *aRootTreeItem = static_cast<nsIDocShellTreeItem*>(this); + + nsCOMPtr<nsIDocShellTreeItem> parent; + NS_ENSURE_SUCCESS(GetParent(getter_AddRefs(parent)), NS_ERROR_FAILURE); + while (parent) { + *aRootTreeItem = parent; + NS_ENSURE_SUCCESS((*aRootTreeItem)->GetParent(getter_AddRefs(parent)), + NS_ERROR_FAILURE); + } + NS_ADDREF(*aRootTreeItem); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetSameTypeRootTreeItem(nsIDocShellTreeItem** aRootTreeItem) +{ + NS_ENSURE_ARG_POINTER(aRootTreeItem); + *aRootTreeItem = static_cast<nsIDocShellTreeItem*>(this); + + nsCOMPtr<nsIDocShellTreeItem> parent; + NS_ENSURE_SUCCESS(GetSameTypeParent(getter_AddRefs(parent)), + NS_ERROR_FAILURE); + while (parent) { + *aRootTreeItem = parent; + NS_ENSURE_SUCCESS((*aRootTreeItem)->GetSameTypeParent(getter_AddRefs(parent)), + NS_ERROR_FAILURE); + } + NS_ADDREF(*aRootTreeItem); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::FindItemWithName(const nsAString& aName, + nsISupports* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + nsIDocShellTreeItem** aResult) +{ + NS_ENSURE_STATE(mDocShell); + NS_ASSERTION(mDocShellTreeOwner, + "This should always be set when in this situation"); + + return mDocShell->FindItemWithName( + aName, static_cast<nsIDocShellTreeOwner*>(mDocShellTreeOwner), + aOriginalRequestor, aResult); +} + +nsIDocument* +nsWebBrowser::GetDocument() +{ + return mDocShell ? mDocShell->GetDocument() : nullptr; +} + +nsPIDOMWindowOuter* +nsWebBrowser::GetWindow() +{ + return mDocShell ? mDocShell->GetWindow() : nullptr; +} + +NS_IMETHODIMP +nsWebBrowser::GetTreeOwner(nsIDocShellTreeOwner** aTreeOwner) +{ + NS_ENSURE_ARG_POINTER(aTreeOwner); + *aTreeOwner = nullptr; + if (mDocShellTreeOwner) { + if (mDocShellTreeOwner->mTreeOwner) { + *aTreeOwner = mDocShellTreeOwner->mTreeOwner; + } else { + *aTreeOwner = mDocShellTreeOwner; + } + } + NS_IF_ADDREF(*aTreeOwner); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner) +{ + NS_ENSURE_SUCCESS(EnsureDocShellTreeOwner(), NS_ERROR_FAILURE); + return mDocShellTreeOwner->SetTreeOwner(aTreeOwner); +} + +//***************************************************************************** +// nsWebBrowser::nsIDocShellTreeItem +//***************************************************************************** + +NS_IMETHODIMP +nsWebBrowser::GetChildCount(int32_t* aChildCount) +{ + NS_ENSURE_ARG_POINTER(aChildCount); + *aChildCount = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::AddChild(nsIDocShellTreeItem* aChild) +{ + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +nsWebBrowser::RemoveChild(nsIDocShellTreeItem* aChild) +{ + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +nsWebBrowser::GetChildAt(int32_t aIndex, nsIDocShellTreeItem** aChild) +{ + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +nsWebBrowser::FindChildWithName(const nsAString& aName, + bool aRecurse, + bool aSameType, + nsIDocShellTreeItem* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + nsIDocShellTreeItem** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = nullptr; + return NS_OK; +} + +//***************************************************************************** +// nsWebBrowser::nsIWebNavigation +//***************************************************************************** + +NS_IMETHODIMP +nsWebBrowser::GetCanGoBack(bool* aCanGoBack) +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsNav->GetCanGoBack(aCanGoBack); +} + +NS_IMETHODIMP +nsWebBrowser::GetCanGoForward(bool* aCanGoForward) +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsNav->GetCanGoForward(aCanGoForward); +} + +NS_IMETHODIMP +nsWebBrowser::GoBack() +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsNav->GoBack(); +} + +NS_IMETHODIMP +nsWebBrowser::GoForward() +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsNav->GoForward(); +} + +NS_IMETHODIMP +nsWebBrowser::LoadURIWithOptions(const char16_t* aURI, uint32_t aLoadFlags, + nsIURI* aReferringURI, + uint32_t aReferrerPolicy, + nsIInputStream* aPostDataStream, + nsIInputStream* aExtraHeaderStream, + nsIURI* aBaseURI) +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsNav->LoadURIWithOptions( + aURI, aLoadFlags, aReferringURI, aReferrerPolicy, aPostDataStream, + aExtraHeaderStream, aBaseURI); +} + +NS_IMETHODIMP +nsWebBrowser::SetOriginAttributesBeforeLoading(JS::Handle<JS::Value> aOriginAttributes) +{ + return mDocShellAsNav->SetOriginAttributesBeforeLoading(aOriginAttributes); +} + +NS_IMETHODIMP +nsWebBrowser::LoadURI(const char16_t* aURI, uint32_t aLoadFlags, + nsIURI* aReferringURI, + nsIInputStream* aPostDataStream, + nsIInputStream* aExtraHeaderStream) +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsNav->LoadURI( + aURI, aLoadFlags, aReferringURI, aPostDataStream, aExtraHeaderStream); +} + +NS_IMETHODIMP +nsWebBrowser::Reload(uint32_t aReloadFlags) +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsNav->Reload(aReloadFlags); +} + +NS_IMETHODIMP +nsWebBrowser::GotoIndex(int32_t aIndex) +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsNav->GotoIndex(aIndex); +} + +NS_IMETHODIMP +nsWebBrowser::Stop(uint32_t aStopFlags) +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsNav->Stop(aStopFlags); +} + +NS_IMETHODIMP +nsWebBrowser::GetCurrentURI(nsIURI** aURI) +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsNav->GetCurrentURI(aURI); +} + +NS_IMETHODIMP +nsWebBrowser::GetReferringURI(nsIURI** aURI) +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsNav->GetReferringURI(aURI); +} + +NS_IMETHODIMP +nsWebBrowser::SetSessionHistory(nsISHistory* aSessionHistory) +{ + if (mDocShell) { + return mDocShellAsNav->SetSessionHistory(aSessionHistory); + } else { + mInitInfo->sessionHistory = aSessionHistory; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetSessionHistory(nsISHistory** aSessionHistory) +{ + NS_ENSURE_ARG_POINTER(aSessionHistory); + if (mDocShell) { + return mDocShellAsNav->GetSessionHistory(aSessionHistory); + } else { + *aSessionHistory = mInitInfo->sessionHistory; + } + + NS_IF_ADDREF(*aSessionHistory); + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetDocument(nsIDOMDocument** aDocument) +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsNav->GetDocument(aDocument); +} + +//***************************************************************************** +// nsWebBrowser::nsIWebBrowserSetup +//***************************************************************************** + +NS_IMETHODIMP +nsWebBrowser::SetProperty(uint32_t aId, uint32_t aValue) +{ + nsresult rv = NS_OK; + + switch (aId) { + case nsIWebBrowserSetup::SETUP_ALLOW_PLUGINS: { + NS_ENSURE_STATE(mDocShell); + NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) || + aValue == static_cast<uint32_t>(false)), + NS_ERROR_INVALID_ARG); + mDocShell->SetAllowPlugins(!!aValue); + break; + } + case nsIWebBrowserSetup::SETUP_ALLOW_JAVASCRIPT: { + NS_ENSURE_STATE(mDocShell); + NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) || + aValue == static_cast<uint32_t>(false)), + NS_ERROR_INVALID_ARG); + mDocShell->SetAllowJavascript(!!aValue); + break; + } + case nsIWebBrowserSetup::SETUP_ALLOW_META_REDIRECTS: { + NS_ENSURE_STATE(mDocShell); + NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) || + aValue == static_cast<uint32_t>(false)), + NS_ERROR_INVALID_ARG); + mDocShell->SetAllowMetaRedirects(!!aValue); + break; + } + case nsIWebBrowserSetup::SETUP_ALLOW_SUBFRAMES: { + NS_ENSURE_STATE(mDocShell); + NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) || + aValue == static_cast<uint32_t>(false)), + NS_ERROR_INVALID_ARG); + mDocShell->SetAllowSubframes(!!aValue); + break; + } + case nsIWebBrowserSetup::SETUP_ALLOW_IMAGES: { + NS_ENSURE_STATE(mDocShell); + NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) || + aValue == static_cast<uint32_t>(false)), + NS_ERROR_INVALID_ARG); + mDocShell->SetAllowImages(!!aValue); + break; + } + case nsIWebBrowserSetup::SETUP_ALLOW_DNS_PREFETCH: { + NS_ENSURE_STATE(mDocShell); + NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) || + aValue == static_cast<uint32_t>(false)), + NS_ERROR_INVALID_ARG); + mDocShell->SetAllowDNSPrefetch(!!aValue); + break; + } + case nsIWebBrowserSetup::SETUP_USE_GLOBAL_HISTORY: { + NS_ENSURE_STATE(mDocShell); + NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) || + aValue == static_cast<uint32_t>(false)), + NS_ERROR_INVALID_ARG); + rv = EnableGlobalHistory(!!aValue); + mShouldEnableHistory = aValue; + break; + } + case nsIWebBrowserSetup::SETUP_FOCUS_DOC_BEFORE_CONTENT: { + // obsolete + break; + } + case nsIWebBrowserSetup::SETUP_IS_CHROME_WRAPPER: { + NS_ENSURE_TRUE((aValue == static_cast<uint32_t>(true) || + aValue == static_cast<uint32_t>(false)), + NS_ERROR_INVALID_ARG); + SetItemType(aValue ? static_cast<int32_t>(typeChromeWrapper) : + static_cast<int32_t>(typeContentWrapper)); + break; + } + default: + rv = NS_ERROR_INVALID_ARG; + } + return rv; +} + +//***************************************************************************** +// nsWebBrowser::nsIWebProgressListener +//***************************************************************************** + +NS_IMETHODIMP +nsWebBrowser::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aStateFlags, + nsresult aStatus) +{ + if (mPersist) { + mPersist->GetCurrentState(&mPersistCurrentState); + } + if (aStateFlags & STATE_IS_NETWORK && aStateFlags & STATE_STOP) { + mPersist = nullptr; + } + if (mProgressListener) { + return mProgressListener->OnStateChange(aWebProgress, aRequest, aStateFlags, + aStatus); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::OnProgressChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) +{ + if (mPersist) { + mPersist->GetCurrentState(&mPersistCurrentState); + } + if (mProgressListener) { + return mProgressListener->OnProgressChange( + aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, + aCurTotalProgress, aMaxTotalProgress); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI* aLocation, + uint32_t aFlags) +{ + if (mProgressListener) { + return mProgressListener->OnLocationChange(aWebProgress, aRequest, aLocation, + aFlags); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) +{ + if (mProgressListener) { + return mProgressListener->OnStatusChange(aWebProgress, aRequest, aStatus, + aMessage); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aState) +{ + if (mProgressListener) { + return mProgressListener->OnSecurityChange(aWebProgress, aRequest, aState); + } + return NS_OK; +} + +//***************************************************************************** +// nsWebBrowser::nsIWebBrowserPersist +//***************************************************************************** + +NS_IMETHODIMP +nsWebBrowser::GetPersistFlags(uint32_t* aPersistFlags) +{ + NS_ENSURE_ARG_POINTER(aPersistFlags); + nsresult rv = NS_OK; + if (mPersist) { + rv = mPersist->GetPersistFlags(&mPersistFlags); + } + *aPersistFlags = mPersistFlags; + return rv; +} + +NS_IMETHODIMP +nsWebBrowser::SetPersistFlags(uint32_t aPersistFlags) +{ + nsresult rv = NS_OK; + mPersistFlags = aPersistFlags; + if (mPersist) { + rv = mPersist->SetPersistFlags(mPersistFlags); + mPersist->GetPersistFlags(&mPersistFlags); + } + return rv; +} + +NS_IMETHODIMP +nsWebBrowser::GetCurrentState(uint32_t* aCurrentState) +{ + NS_ENSURE_ARG_POINTER(aCurrentState); + if (mPersist) { + mPersist->GetCurrentState(&mPersistCurrentState); + } + *aCurrentState = mPersistCurrentState; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetResult(nsresult* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + if (mPersist) { + mPersist->GetResult(&mPersistResult); + } + *aResult = mPersistResult; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetProgressListener(nsIWebProgressListener** aProgressListener) +{ + NS_ENSURE_ARG_POINTER(aProgressListener); + *aProgressListener = mProgressListener; + NS_IF_ADDREF(*aProgressListener); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetProgressListener(nsIWebProgressListener* aProgressListener) +{ + mProgressListener = aProgressListener; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::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 +nsWebBrowser::SavePrivacyAwareURI(nsIURI* aURI, + nsISupports* aCacheKey, + nsIURI* aReferrer, + uint32_t aReferrerPolicy, + nsIInputStream* aPostData, + const char* aExtraHeaders, + nsISupports* aFile, + bool aIsPrivate) +{ + if (mPersist) { + uint32_t currentState; + mPersist->GetCurrentState(¤tState); + if (currentState == PERSIST_STATE_FINISHED) { + mPersist = nullptr; + } else { + // You can't save again until the last save has completed + return NS_ERROR_FAILURE; + } + } + + nsCOMPtr<nsIURI> uri; + if (aURI) { + uri = aURI; + } else { + nsresult rv = GetCurrentURI(getter_AddRefs(uri)); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + } + + // Create a throwaway persistence object to do the work + nsresult rv; + mPersist = do_CreateInstance(NS_WEBBROWSERPERSIST_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + mPersist->SetProgressListener(this); + mPersist->SetPersistFlags(mPersistFlags); + mPersist->GetCurrentState(&mPersistCurrentState); + + rv = mPersist->SavePrivacyAwareURI(uri, aCacheKey, aReferrer, aReferrerPolicy, + aPostData, aExtraHeaders, aFile, aIsPrivate); + if (NS_FAILED(rv)) { + mPersist = nullptr; + } + return rv; +} + +NS_IMETHODIMP +nsWebBrowser::SaveChannel(nsIChannel* aChannel, nsISupports* aFile) +{ + if (mPersist) { + uint32_t currentState; + mPersist->GetCurrentState(¤tState); + if (currentState == PERSIST_STATE_FINISHED) { + mPersist = nullptr; + } else { + // You can't save again until the last save has completed + return NS_ERROR_FAILURE; + } + } + + // Create a throwaway persistence object to do the work + nsresult rv; + mPersist = do_CreateInstance(NS_WEBBROWSERPERSIST_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + mPersist->SetProgressListener(this); + mPersist->SetPersistFlags(mPersistFlags); + mPersist->GetCurrentState(&mPersistCurrentState); + rv = mPersist->SaveChannel(aChannel, aFile); + if (NS_FAILED(rv)) { + mPersist = nullptr; + } + return rv; +} + +NS_IMETHODIMP +nsWebBrowser::SaveDocument(nsISupports* aDocumentish, + nsISupports* aFile, + nsISupports* aDataPath, + const char* aOutputContentType, + uint32_t aEncodingFlags, + uint32_t aWrapColumn) +{ + if (mPersist) { + uint32_t currentState; + mPersist->GetCurrentState(¤tState); + if (currentState == PERSIST_STATE_FINISHED) { + mPersist = nullptr; + } else { + // You can't save again until the last save has completed + return NS_ERROR_FAILURE; + } + } + + // Use the specified DOM document, or if none is specified, the one + // attached to the web browser. + + nsCOMPtr<nsISupports> doc; + if (aDocumentish) { + doc = aDocumentish; + } else { + nsCOMPtr<nsIDOMDocument> domDoc; + GetDocument(getter_AddRefs(domDoc)); + doc = domDoc.forget(); + } + if (!doc) { + return NS_ERROR_FAILURE; + } + + // Create a throwaway persistence object to do the work + nsresult rv; + mPersist = do_CreateInstance(NS_WEBBROWSERPERSIST_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + mPersist->SetProgressListener(this); + mPersist->SetPersistFlags(mPersistFlags); + mPersist->GetCurrentState(&mPersistCurrentState); + rv = mPersist->SaveDocument(doc, aFile, aDataPath, aOutputContentType, + aEncodingFlags, aWrapColumn); + if (NS_FAILED(rv)) { + mPersist = nullptr; + } + return rv; +} + +NS_IMETHODIMP +nsWebBrowser::CancelSave() +{ + if (mPersist) { + return mPersist->CancelSave(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::Cancel(nsresult aReason) +{ + if (mPersist) { + return mPersist->Cancel(aReason); + } + return NS_OK; +} + +//***************************************************************************** +// nsWebBrowser::nsIBaseWindow +//***************************************************************************** + +NS_IMETHODIMP +nsWebBrowser::InitWindow(nativeWindow aParentNativeWindow, + nsIWidget* aParentWidget, + int32_t aX, int32_t aY, + int32_t aCX, int32_t aCY) +{ + NS_ENSURE_ARG(aParentNativeWindow || aParentWidget); + NS_ENSURE_STATE(!mDocShell || mInitInfo); + + if (aParentWidget) { + NS_ENSURE_SUCCESS(SetParentWidget(aParentWidget), NS_ERROR_FAILURE); + } else + NS_ENSURE_SUCCESS(SetParentNativeWindow(aParentNativeWindow), + NS_ERROR_FAILURE); + + NS_ENSURE_SUCCESS(SetPositionAndSize(aX, aY, aCX, aCY, 0), + NS_ERROR_FAILURE); + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::Create() +{ + NS_ENSURE_STATE(!mDocShell && (mParentNativeWindow || mParentWidget)); + + nsresult rv = EnsureDocShellTreeOwner(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIWidget> docShellParentWidget(mParentWidget); + if (!mParentWidget) { + // Create the widget + mInternalWidget = do_CreateInstance(kChildCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + docShellParentWidget = mInternalWidget; + nsWidgetInitData widgetInit; + + widgetInit.clipChildren = true; + + widgetInit.mWindowType = eWindowType_child; + LayoutDeviceIntRect bounds(mInitInfo->x, mInitInfo->y, + mInitInfo->cx, mInitInfo->cy); + + mInternalWidget->SetWidgetListener(&mWidgetListenerDelegate); + rv = mInternalWidget->Create(nullptr, mParentNativeWindow, bounds, + &widgetInit); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIDocShell> docShell( + do_CreateInstance("@mozilla.org/docshell;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsDocShell::Cast(docShell)->SetOriginAttributes(mOriginAttributes); + rv = SetDocShell(docShell); + NS_ENSURE_SUCCESS(rv, rv); + + // get the system default window background colour + LookAndFeel::GetColor(LookAndFeel::eColorID_WindowBackground, + &mBackgroundColor); + + // the docshell has been set so we now have our listener registrars. + if (mListenerArray) { + // we had queued up some listeners, let's register them now. + uint32_t count = mListenerArray->Length(); + uint32_t i = 0; + NS_ASSERTION(count > 0, "array construction problem"); + while (i < count) { + nsWebBrowserListenerState& state = mListenerArray->ElementAt(i); + nsCOMPtr<nsISupports> listener = do_QueryReferent(state.mWeakPtr); + NS_ASSERTION(listener, "bad listener"); + (void)BindListener(listener, state.mID); + i++; + } + mListenerArray = nullptr; + } + + // HACK ALERT - this registration registers the nsDocShellTreeOwner as a + // nsIWebBrowserListener so it can setup its MouseListener in one of the + // progress callbacks. If we can register the MouseListener another way, this + // registration can go away, and nsDocShellTreeOwner can stop implementing + // nsIWebProgressListener. + nsCOMPtr<nsISupports> supports = nullptr; + (void)mDocShellTreeOwner->QueryInterface( + NS_GET_IID(nsIWebProgressListener), + static_cast<void**>(getter_AddRefs(supports))); + (void)BindListener(supports, NS_GET_IID(nsIWebProgressListener)); + + NS_ENSURE_SUCCESS(mDocShellAsWin->InitWindow(nullptr, docShellParentWidget, + mInitInfo->x, mInitInfo->y, + mInitInfo->cx, mInitInfo->cy), + NS_ERROR_FAILURE); + + mDocShell->SetName(mInitInfo->name); + if (mContentType == typeChromeWrapper) { + mDocShell->SetItemType(nsIDocShellTreeItem::typeChrome); + } else { + mDocShell->SetItemType(nsIDocShellTreeItem::typeContent); + } + mDocShell->SetTreeOwner(mDocShellTreeOwner); + + // If the webbrowser is a content docshell item then we won't hear any + // events from subframes. To solve that we install our own chrome event + // handler that always gets called (even for subframes) for any bubbling + // event. + + if (!mInitInfo->sessionHistory) { + mInitInfo->sessionHistory = do_CreateInstance(NS_SHISTORY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + mDocShellAsNav->SetSessionHistory(mInitInfo->sessionHistory); + + if (XRE_IsParentProcess()) { + // Hook up global history. Do not fail if we can't - just warn. + rv = EnableGlobalHistory(mShouldEnableHistory); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EnableGlobalHistory() failed"); + } + + NS_ENSURE_SUCCESS(mDocShellAsWin->Create(), NS_ERROR_FAILURE); + + // Hook into the OnSecurityChange() notification for lock/unlock icon + // updates + nsCOMPtr<mozIDOMWindowProxy> domWindow; + rv = GetContentDOMWindow(getter_AddRefs(domWindow)); + if (NS_SUCCEEDED(rv)) { + // this works because the implementation of nsISecureBrowserUI + // (nsSecureBrowserUIImpl) gets a docShell from the domWindow, + // and calls docShell->SetSecurityUI(this); + nsCOMPtr<nsISecureBrowserUI> securityUI = + do_CreateInstance(NS_SECURE_BROWSER_UI_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + securityUI->Init(domWindow); + } + } + + mDocShellTreeOwner->AddToWatcher(); // evil twin of Remove in SetDocShell(0) + mDocShellTreeOwner->AddChromeListeners(); + + mInitInfo = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::Destroy() +{ + InternalDestroy(); + + if (!mInitInfo) { + mInitInfo = new nsWebBrowserInitInfo(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetUnscaledDevicePixelsPerCSSPixel(double* aScale) +{ + *aScale = mParentWidget ? mParentWidget->GetDefaultScale().scale : 1.0; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetDevicePixelsPerDesktopPixel(double* aScale) +{ + *aScale = mParentWidget ? mParentWidget->GetDesktopToDeviceScale().scale + : 1.0; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetPositionDesktopPix(int32_t aX, int32_t aY) +{ + // XXX jfkthame + // It's not clear to me whether this will be fully correct across + // potential multi-screen, mixed-DPI configurations for all platforms; + // we might need to add code paths that make it possible to pass the + // desktop-pix parameters all the way through to the native widget, + // to avoid the risk of device-pixel coords mapping to the wrong + // display on OS X with mixed retina/non-retina screens. + double scale = 1.0; + GetDevicePixelsPerDesktopPixel(&scale); + return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale)); +} + +NS_IMETHODIMP +nsWebBrowser::SetPosition(int32_t aX, int32_t aY) +{ + int32_t cx = 0; + int32_t cy = 0; + + GetSize(&cx, &cy); + + return SetPositionAndSize(aX, aY, cx, cy, 0); +} + +NS_IMETHODIMP +nsWebBrowser::GetPosition(int32_t* aX, int32_t* aY) +{ + return GetPositionAndSize(aX, aY, nullptr, nullptr); +} + +NS_IMETHODIMP +nsWebBrowser::SetSize(int32_t aCX, int32_t aCY, bool aRepaint) +{ + int32_t x = 0; + int32_t y = 0; + + GetPosition(&x, &y); + + return SetPositionAndSize(x, y, aCX, aCY, + aRepaint ? nsIBaseWindow::eRepaint : 0); +} + +NS_IMETHODIMP +nsWebBrowser::GetSize(int32_t* aCX, int32_t* aCY) +{ + return GetPositionAndSize(nullptr, nullptr, aCX, aCY); +} + +NS_IMETHODIMP +nsWebBrowser::SetPositionAndSize(int32_t aX, int32_t aY, + int32_t aCX, int32_t aCY, uint32_t aFlags) +{ + if (!mDocShell) { + mInitInfo->x = aX; + mInitInfo->y = aY; + mInitInfo->cx = aCX; + mInitInfo->cy = aCY; + } else { + int32_t doc_x = aX; + int32_t doc_y = aY; + + // If there is an internal widget we need to make the docShell coordinates + // relative to the internal widget rather than the calling app's parent. + // We also need to resize our widget then. + if (mInternalWidget) { + doc_x = doc_y = 0; + NS_ENSURE_SUCCESS(mInternalWidget->Resize(aX, aY, aCX, aCY, + !!(aFlags & nsIBaseWindow::eRepaint)), + NS_ERROR_FAILURE); + } + // Now reposition/ resize the doc + NS_ENSURE_SUCCESS( + mDocShellAsWin->SetPositionAndSize(doc_x, doc_y, aCX, aCY, aFlags), + NS_ERROR_FAILURE); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetPositionAndSize(int32_t* aX, int32_t* aY, + int32_t* aCX, int32_t* aCY) +{ + if (!mDocShell) { + if (aX) { + *aX = mInitInfo->x; + } + if (aY) { + *aY = mInitInfo->y; + } + if (aCX) { + *aCX = mInitInfo->cx; + } + if (aCY) { + *aCY = mInitInfo->cy; + } + } else if (mInternalWidget) { + LayoutDeviceIntRect bounds = mInternalWidget->GetBounds(); + + if (aX) { + *aX = bounds.x; + } + if (aY) { + *aY = bounds.y; + } + if (aCX) { + *aCX = bounds.width; + } + if (aCY) { + *aCY = bounds.height; + } + return NS_OK; + } else { + // Can directly return this as it is the + // same interface, thus same returns. + return mDocShellAsWin->GetPositionAndSize(aX, aY, aCX, aCY); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::Repaint(bool aForce) +{ + NS_ENSURE_STATE(mDocShell); + // Can directly return this as it is the + // same interface, thus same returns. + return mDocShellAsWin->Repaint(aForce); +} + +NS_IMETHODIMP +nsWebBrowser::GetParentWidget(nsIWidget** aParentWidget) +{ + NS_ENSURE_ARG_POINTER(aParentWidget); + + *aParentWidget = mParentWidget; + + NS_IF_ADDREF(*aParentWidget); + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetParentWidget(nsIWidget* aParentWidget) +{ + NS_ENSURE_STATE(!mDocShell); + + mParentWidget = aParentWidget; + if (mParentWidget) { + mParentNativeWindow = mParentWidget->GetNativeData(NS_NATIVE_WIDGET); + } else { + mParentNativeWindow = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetParentNativeWindow(nativeWindow* aParentNativeWindow) +{ + NS_ENSURE_ARG_POINTER(aParentNativeWindow); + + *aParentNativeWindow = mParentNativeWindow; + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetParentNativeWindow(nativeWindow aParentNativeWindow) +{ + NS_ENSURE_STATE(!mDocShell); + + mParentNativeWindow = aParentNativeWindow; + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetNativeHandle(nsAString& aNativeHandle) +{ + // the nativeHandle should be accessed from nsIXULWindow + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsWebBrowser::GetVisibility(bool* aVisibility) +{ + NS_ENSURE_ARG_POINTER(aVisibility); + + if (!mDocShell) { + *aVisibility = mInitInfo->visible; + } else { + NS_ENSURE_SUCCESS(mDocShellAsWin->GetVisibility(aVisibility), + NS_ERROR_FAILURE); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetVisibility(bool aVisibility) +{ + if (!mDocShell) { + mInitInfo->visible = aVisibility; + } else { + NS_ENSURE_SUCCESS(mDocShellAsWin->SetVisibility(aVisibility), + NS_ERROR_FAILURE); + if (mInternalWidget) { + mInternalWidget->Show(aVisibility); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetEnabled(bool* aEnabled) +{ + if (mInternalWidget) { + *aEnabled = mInternalWidget->IsEnabled(); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWebBrowser::SetEnabled(bool aEnabled) +{ + if (mInternalWidget) { + return mInternalWidget->Enable(aEnabled); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWebBrowser::GetMainWidget(nsIWidget** aMainWidget) +{ + NS_ENSURE_ARG_POINTER(aMainWidget); + + if (mInternalWidget) { + *aMainWidget = mInternalWidget; + } else { + *aMainWidget = mParentWidget; + } + + NS_IF_ADDREF(*aMainWidget); + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetFocus() +{ + nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow(); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + return fm ? fm->SetFocusedWindow(window) : NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetTitle(char16_t** aTitle) +{ + NS_ENSURE_ARG_POINTER(aTitle); + NS_ENSURE_STATE(mDocShell); + + NS_ENSURE_SUCCESS(mDocShellAsWin->GetTitle(aTitle), NS_ERROR_FAILURE); + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetTitle(const char16_t* aTitle) +{ + NS_ENSURE_STATE(mDocShell); + + NS_ENSURE_SUCCESS(mDocShellAsWin->SetTitle(aTitle), NS_ERROR_FAILURE); + + return NS_OK; +} + +//***************************************************************************** +// nsWebBrowser::nsIScrollable +//***************************************************************************** + +NS_IMETHODIMP +nsWebBrowser::GetDefaultScrollbarPreferences(int32_t aScrollOrientation, + int32_t* aScrollbarPref) +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsScrollable->GetDefaultScrollbarPreferences( + aScrollOrientation, aScrollbarPref); +} + +NS_IMETHODIMP +nsWebBrowser::SetDefaultScrollbarPreferences(int32_t aScrollOrientation, + int32_t aScrollbarPref) +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsScrollable->SetDefaultScrollbarPreferences( + aScrollOrientation, aScrollbarPref); +} + +NS_IMETHODIMP +nsWebBrowser::GetScrollbarVisibility(bool* aVerticalVisible, + bool* aHorizontalVisible) +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsScrollable->GetScrollbarVisibility(aVerticalVisible, + aHorizontalVisible); +} + +//***************************************************************************** +// nsWebBrowser::nsITextScroll +//***************************************************************************** + +NS_IMETHODIMP +nsWebBrowser::ScrollByLines(int32_t aNumLines) +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsTextScroll->ScrollByLines(aNumLines); +} + +NS_IMETHODIMP +nsWebBrowser::ScrollByPages(int32_t aNumPages) +{ + NS_ENSURE_STATE(mDocShell); + + return mDocShellAsTextScroll->ScrollByPages(aNumPages); +} + +//***************************************************************************** +// nsWebBrowser: Listener Helpers +//***************************************************************************** + +NS_IMETHODIMP +nsWebBrowser::SetDocShell(nsIDocShell* aDocShell) +{ + // We need to keep the docshell alive while we perform the changes, but we + // don't need to call any methods on it. + nsCOMPtr<nsIDocShell> kungFuDeathGrip(mDocShell); + mozilla::Unused << kungFuDeathGrip; + + if (aDocShell) { + NS_ENSURE_TRUE(!mDocShell, NS_ERROR_FAILURE); + + nsCOMPtr<nsIInterfaceRequestor> req(do_QueryInterface(aDocShell)); + nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(aDocShell)); + nsCOMPtr<nsIWebNavigation> nav(do_QueryInterface(aDocShell)); + nsCOMPtr<nsIScrollable> scrollable(do_QueryInterface(aDocShell)); + nsCOMPtr<nsITextScroll> textScroll(do_QueryInterface(aDocShell)); + nsCOMPtr<nsIWebProgress> progress(do_GetInterface(aDocShell)); + NS_ENSURE_TRUE(req && baseWin && nav && scrollable && textScroll && progress, + NS_ERROR_FAILURE); + + mDocShell = aDocShell; + mDocShellAsReq = req; + mDocShellAsWin = baseWin; + mDocShellAsNav = nav; + mDocShellAsScrollable = scrollable; + mDocShellAsTextScroll = textScroll; + mWebProgress = progress; + + // By default, do not allow DNS prefetch, so we don't break our frozen + // API. Embeddors who decide to enable it should do so manually. + mDocShell->SetAllowDNSPrefetch(false); + + // It's possible to call setIsActive() on us before we have a docshell. + // If we're getting a docshell now, pass along our desired value. The + // default here (true) matches the default of the docshell, so this is + // a no-op unless setIsActive(false) has been called on us. + mDocShell->SetIsActive(mIsActive); + } else { + if (mDocShellTreeOwner) { + mDocShellTreeOwner->RemoveFromWatcher(); // evil twin of Add in Create() + } + if (mDocShellAsWin) { + mDocShellAsWin->Destroy(); + } + + mDocShell = nullptr; + mDocShellAsReq = nullptr; + mDocShellAsWin = nullptr; + mDocShellAsNav = nullptr; + mDocShellAsScrollable = nullptr; + mDocShellAsTextScroll = nullptr; + mWebProgress = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::EnsureDocShellTreeOwner() +{ + if (mDocShellTreeOwner) { + return NS_OK; + } + + mDocShellTreeOwner = new nsDocShellTreeOwner(); + mDocShellTreeOwner->WebBrowser(this); + + return NS_OK; +} + +static void +DrawPaintedLayer(PaintedLayer* aLayer, + gfxContext* aContext, + const nsIntRegion& aRegionToDraw, + const nsIntRegion& aDirtyRegion, + DrawRegionClip aClip, + const nsIntRegion& aRegionToInvalidate, + void* aCallbackData) +{ + DrawTarget& aDrawTarget = *aContext->GetDrawTarget(); + + ColorPattern color(ToDeviceColor(*static_cast<nscolor*>(aCallbackData))); + nsIntRect dirtyRect = aRegionToDraw.GetBounds(); + aDrawTarget.FillRect( + Rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height), color); +} + +void +nsWebBrowser::WindowRaised(nsIWidget* aWidget) +{ +#if defined(DEBUG_smaug) + nsCOMPtr<nsIDocument> document = mDocShell->GetDocument(); + nsAutoString documentURI; + document->GetDocumentURI(documentURI); + printf("nsWebBrowser::NS_ACTIVATE %p %s\n", (void*)this, + NS_ConvertUTF16toUTF8(documentURI).get()); +#endif + Activate(); +} + +void +nsWebBrowser::WindowLowered(nsIWidget* aWidget) +{ +#if defined(DEBUG_smaug) + nsCOMPtr<nsIDocument> document = mDocShell->GetDocument(); + nsAutoString documentURI; + document->GetDocumentURI(documentURI); + printf("nsWebBrowser::NS_DEACTIVATE %p %s\n", (void*)this, + NS_ConvertUTF16toUTF8(documentURI).get()); +#endif + Deactivate(); +} + +bool +nsWebBrowser::PaintWindow(nsIWidget* aWidget, LayoutDeviceIntRegion aRegion) +{ + LayerManager* layerManager = aWidget->GetLayerManager(); + NS_ASSERTION(layerManager, "Must be in paint event"); + + layerManager->BeginTransaction(); + RefPtr<PaintedLayer> root = layerManager->CreatePaintedLayer(); + if (root) { + nsIntRect dirtyRect = aRegion.GetBounds().ToUnknownRect(); + root->SetVisibleRegion(LayerIntRegion::FromUnknownRegion(dirtyRect)); + layerManager->SetRoot(root); + } + + layerManager->EndTransaction(DrawPaintedLayer, &mBackgroundColor); + return true; +} +/* +NS_IMETHODIMP +nsWebBrowser::GetPrimaryContentWindow(mozIDOMWindowProxy** aDOMWindow) +{ + *aDOMWindow = nullptr; + + nsCOMPtr<nsIDocShellTreeItem> item; + NS_ENSURE_TRUE(mDocShellTreeOwner, NS_ERROR_FAILURE); + mDocShellTreeOwner->GetPrimaryContentShell(getter_AddRefs(item)); + NS_ENSURE_TRUE(item, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDocShell> docShell; + docShell = do_QueryInterface(item); + NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE); + + nsCOMPtr<nsPIDOMWindowOuter> domWindow = docShell->GetWindow(); + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); + + *aDOMWindow = domWindow; + NS_ADDREF(*aDOMWindow); + return NS_OK; +} +*/ +//***************************************************************************** +// nsWebBrowser::nsIWebBrowserFocus +//***************************************************************************** + +NS_IMETHODIMP +nsWebBrowser::Activate(void) +{ + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow(); + if (fm && window) { + return fm->WindowRaised(window); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::Deactivate(void) +{ + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow(); + if (fm && window) { + return fm->WindowLowered(window); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetFocusAtFirstElement(void) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetFocusAtLastElement(void) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetFocusedWindow(mozIDOMWindowProxy** aFocusedWindow) +{ + NS_ENSURE_ARG_POINTER(aFocusedWindow); + *aFocusedWindow = nullptr; + + NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE); + + nsCOMPtr<nsPIDOMWindowOuter> window = mDocShell->GetWindow(); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDOMElement> focusedElement; + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + return fm ? fm->GetFocusedElementForWindow(window, true, aFocusedWindow, + getter_AddRefs(focusedElement)) : + NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetFocusedWindow(mozIDOMWindowProxy* aFocusedWindow) +{ + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + return fm ? fm->SetFocusedWindow(aFocusedWindow) : NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::GetFocusedElement(nsIDOMElement** aFocusedElement) +{ + NS_ENSURE_ARG_POINTER(aFocusedElement); + NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE); + + nsCOMPtr<nsPIDOMWindowOuter> window = mDocShell->GetWindow(); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + return + fm ? fm->GetFocusedElementForWindow(window, true, nullptr, aFocusedElement) : + NS_OK; +} + +NS_IMETHODIMP +nsWebBrowser::SetFocusedElement(nsIDOMElement* aFocusedElement) +{ + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + return fm ? fm->SetFocus(aFocusedElement, 0) : NS_OK; +} + +//***************************************************************************** +// nsWebBrowser::nsIWebBrowserStream +//***************************************************************************** + +NS_IMETHODIMP +nsWebBrowser::OpenStream(nsIURI* aBaseURI, const nsACString& aContentType) +{ + nsresult rv; + + if (!mStream) { + mStream = new nsEmbedStream(); + mStream->InitOwner(this); + rv = mStream->Init(); + if (NS_FAILED(rv)) { + return rv; + } + } + + return mStream->OpenStream(aBaseURI, aContentType); +} + + +NS_IMETHODIMP +nsWebBrowser::AppendToStream(const uint8_t* aData, uint32_t aLen) +{ + if (!mStream) { + return NS_ERROR_FAILURE; + } + + return mStream->AppendToStream(aData, aLen); +} + +NS_IMETHODIMP +nsWebBrowser::CloseStream() +{ + nsresult rv; + + if (!mStream) { + return NS_ERROR_FAILURE; + } + rv = mStream->CloseStream(); + + mStream = nullptr; + + return rv; +} + +bool +nsWebBrowser::WidgetListenerDelegate::PaintWindow( + nsIWidget* aWidget, mozilla::LayoutDeviceIntRegion aRegion) +{ + RefPtr<nsWebBrowser> holder = mWebBrowser; + return holder->PaintWindow(aWidget, aRegion); +} diff --git a/embedding/browser/nsWebBrowser.h b/embedding/browser/nsWebBrowser.h new file mode 100644 index 000000000..80c106cb9 --- /dev/null +++ b/embedding/browser/nsWebBrowser.h @@ -0,0 +1,185 @@ +/* -*- 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 nsWebBrowser_h__ +#define nsWebBrowser_h__ + +// Local Includes +#include "nsDocShellTreeOwner.h" + +// Core Includes +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" + +// Interfaces needed +#include "nsCWebBrowser.h" +#include "nsIBaseWindow.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeItem.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIScrollable.h" +#include "nsISHistory.h" +#include "nsITextScroll.h" +#include "nsIWidget.h" +#include "nsIWebProgress.h" +#include "nsISecureBrowserUI.h" +#include "nsIWebBrowser.h" +#include "nsIWebNavigation.h" +#include "nsIWebBrowserSetup.h" +#include "nsIWebBrowserPersist.h" +#include "nsIWebBrowserFocus.h" +#include "nsIWebBrowserStream.h" +#include "nsIWindowWatcher.h" +#include "nsIPrintSettings.h" +#include "nsEmbedStream.h" +#include "nsIWidgetListener.h" + +#include "mozilla/BasePrincipal.h" +#include "nsTArray.h" +#include "nsWeakPtr.h" + +class nsWebBrowserInitInfo +{ +public: + // nsIBaseWindow Stuff + int32_t x; + int32_t y; + int32_t cx; + int32_t cy; + bool visible; + nsCOMPtr<nsISHistory> sessionHistory; + nsString name; +}; + +class nsWebBrowserListenerState +{ +public: + bool Equals(nsIWeakReference* aListener, const nsIID& aID) + { + return mWeakPtr.get() == aListener && mID.Equals(aID); + } + + nsWeakPtr mWeakPtr; + nsIID mID; +}; + +// {cda5863a-aa9c-411e-be49-ea0d525ab4b5} - +#define NS_WEBBROWSER_CID \ + { 0xcda5863a, 0xaa9c, 0x411e, { 0xbe, 0x49, 0xea, 0x0d, 0x52, 0x5a, 0xb4, 0xb5 } } + + +class nsWebBrowser final : public nsIWebBrowser, + public nsIWebNavigation, + public nsIWebBrowserSetup, + public nsIDocShellTreeItem, + public nsIBaseWindow, + public nsIScrollable, + public nsITextScroll, + public nsIInterfaceRequestor, + public nsIWebBrowserPersist, + public nsIWebBrowserFocus, + public nsIWebProgressListener, + public nsIWebBrowserStream, + public nsSupportsWeakReference +{ + friend class nsDocShellTreeOwner; + +public: + + // The implementation of non-refcounted nsIWidgetListener, which would hold a + // strong reference on stack before calling nsWebBrowser. + class WidgetListenerDelegate : public nsIWidgetListener + { + public: + explicit WidgetListenerDelegate(nsWebBrowser* aWebBrowser) + : mWebBrowser(aWebBrowser) {} + virtual bool PaintWindow( + nsIWidget* aWidget, mozilla::LayoutDeviceIntRegion aRegion) override; + + private: + // The lifetime of WidgetListenerDelegate is bound to nsWebBrowser so we + // just use raw pointer here. + nsWebBrowser* mWebBrowser; + }; + + nsWebBrowser(); + + NS_DECL_ISUPPORTS + + NS_DECL_NSIBASEWINDOW + NS_DECL_NSIDOCSHELLTREEITEM + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSISCROLLABLE + NS_DECL_NSITEXTSCROLL + NS_DECL_NSIWEBBROWSER + NS_DECL_NSIWEBNAVIGATION + NS_DECL_NSIWEBBROWSERSETUP + NS_DECL_NSIWEBBROWSERPERSIST + NS_DECL_NSICANCELABLE + NS_DECL_NSIWEBBROWSERFOCUS + NS_DECL_NSIWEBBROWSERSTREAM + NS_DECL_NSIWEBPROGRESSLISTENER + +protected: + virtual ~nsWebBrowser(); + NS_IMETHOD InternalDestroy(); + + // XXXbz why are these NS_IMETHOD? They're not interface methods! + NS_IMETHOD SetDocShell(nsIDocShell* aDocShell); + NS_IMETHOD EnsureDocShellTreeOwner(); + NS_IMETHOD BindListener(nsISupports* aListener, const nsIID& aIID); + NS_IMETHOD UnBindListener(nsISupports* aListener, const nsIID& aIID); + NS_IMETHOD EnableGlobalHistory(bool aEnable); + + // nsIWidgetListener + virtual void WindowRaised(nsIWidget* aWidget); + virtual void WindowLowered(nsIWidget* aWidget); + bool PaintWindow(nsIWidget* aWidget, mozilla::LayoutDeviceIntRegion aRegion); + +protected: + RefPtr<nsDocShellTreeOwner> mDocShellTreeOwner; + nsCOMPtr<nsIDocShell> mDocShell; + nsCOMPtr<nsIInterfaceRequestor> mDocShellAsReq; + nsCOMPtr<nsIBaseWindow> mDocShellAsWin; + nsCOMPtr<nsIWebNavigation> mDocShellAsNav; + nsCOMPtr<nsIScrollable> mDocShellAsScrollable; + nsCOMPtr<nsITextScroll> mDocShellAsTextScroll; + mozilla::DocShellOriginAttributes mOriginAttributes; + + nsCOMPtr<nsIWidget> mInternalWidget; + nsCOMPtr<nsIWindowWatcher> mWWatch; + nsAutoPtr<nsWebBrowserInitInfo> mInitInfo; + uint32_t mContentType; + bool mActivating; + bool mShouldEnableHistory; + bool mIsActive; + nativeWindow mParentNativeWindow; + nsIWebProgressListener* mProgressListener; + nsCOMPtr<nsIWebProgress> mWebProgress; + + nsCOMPtr<nsIPrintSettings> mPrintSettings; + + WidgetListenerDelegate mWidgetListenerDelegate; + + // cached background color + nscolor mBackgroundColor; + + // persistence object + nsCOMPtr<nsIWebBrowserPersist> mPersist; + uint32_t mPersistCurrentState; + nsresult mPersistResult; + uint32_t mPersistFlags; + + // stream + RefPtr<nsEmbedStream> mStream; + + // Weak Reference interfaces... + nsIWidget* mParentWidget; + nsAutoPtr<nsTArray<nsWebBrowserListenerState> > mListenerArray; +}; + +#endif /* nsWebBrowser_h__ */ diff --git a/embedding/browser/nsWebBrowserContentPolicy.cpp b/embedding/browser/nsWebBrowserContentPolicy.cpp new file mode 100644 index 000000000..f6b17a197 --- /dev/null +++ b/embedding/browser/nsWebBrowserContentPolicy.cpp @@ -0,0 +1,108 @@ +/* -*- 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 "nsWebBrowserContentPolicy.h" +#include "nsIDocShell.h" +#include "nsCOMPtr.h" +#include "nsContentPolicyUtils.h" +#include "nsIContentViewer.h" + +nsWebBrowserContentPolicy::nsWebBrowserContentPolicy() +{ + MOZ_COUNT_CTOR(nsWebBrowserContentPolicy); +} + +nsWebBrowserContentPolicy::~nsWebBrowserContentPolicy() +{ + MOZ_COUNT_DTOR(nsWebBrowserContentPolicy); +} + +NS_IMPL_ISUPPORTS(nsWebBrowserContentPolicy, nsIContentPolicy) + +NS_IMETHODIMP +nsWebBrowserContentPolicy::ShouldLoad(uint32_t aContentType, + nsIURI* aContentLocation, + nsIURI* aRequestingLocation, + nsISupports* aRequestingContext, + const nsACString& aMimeGuess, + nsISupports* aExtra, + nsIPrincipal* aRequestPrincipal, + int16_t* aShouldLoad) +{ + NS_PRECONDITION(aShouldLoad, "Null out param"); + + MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType), + "We should only see external content policy types here."); + + *aShouldLoad = nsIContentPolicy::ACCEPT; + + nsIDocShell* shell = NS_CP_GetDocShellFromContext(aRequestingContext); + /* We're going to dereference shell, so make sure it isn't null */ + if (!shell) { + return NS_OK; + } + + nsresult rv; + bool allowed = true; + + switch (aContentType) { + case nsIContentPolicy::TYPE_SCRIPT: + rv = shell->GetAllowJavascript(&allowed); + break; + case nsIContentPolicy::TYPE_SUBDOCUMENT: + rv = shell->GetAllowSubframes(&allowed); + break; +#if 0 + /* XXXtw: commented out in old code; add during conpol phase 2 */ + case nsIContentPolicy::TYPE_REFRESH: + rv = shell->GetAllowMetaRedirects(&allowed); /* meta _refresh_ */ + break; +#endif + case nsIContentPolicy::TYPE_IMAGE: + case nsIContentPolicy::TYPE_IMAGESET: + rv = shell->GetAllowImages(&allowed); + break; + default: + return NS_OK; + } + + if (NS_SUCCEEDED(rv) && !allowed) { + *aShouldLoad = nsIContentPolicy::REJECT_TYPE; + } + return rv; +} + +NS_IMETHODIMP +nsWebBrowserContentPolicy::ShouldProcess(uint32_t aContentType, + nsIURI* aContentLocation, + nsIURI* aRequestingLocation, + nsISupports* aRequestingContext, + const nsACString& aMimeGuess, + nsISupports* aExtra, + nsIPrincipal* aRequestPrincipal, + int16_t* aShouldProcess) +{ + NS_PRECONDITION(aShouldProcess, "Null out param"); + + MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType), + "We should only see external content policy types here."); + + *aShouldProcess = nsIContentPolicy::ACCEPT; + + // Object tags will always open channels with TYPE_OBJECT, but may end up + // loading with TYPE_IMAGE or TYPE_DOCUMENT as their final type, so we block + // actual-plugins at the process stage + if (aContentType != nsIContentPolicy::TYPE_OBJECT) { + return NS_OK; + } + + nsIDocShell* shell = NS_CP_GetDocShellFromContext(aRequestingContext); + if (shell && (!shell->PluginsAllowedInCurrentDoc())) { + *aShouldProcess = nsIContentPolicy::REJECT_TYPE; + } + + return NS_OK; +} diff --git a/embedding/browser/nsWebBrowserContentPolicy.h b/embedding/browser/nsWebBrowserContentPolicy.h new file mode 100644 index 000000000..e360e2110 --- /dev/null +++ b/embedding/browser/nsWebBrowserContentPolicy.h @@ -0,0 +1,26 @@ +/* -*- 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 "nsIContentPolicy.h" + +/* f66bc334-1dd1-11b2-bab2-90e04fe15c19 */ +#define NS_WEBBROWSERCONTENTPOLICY_CID \ + { 0xf66bc334, 0x1dd1, 0x11b2, { 0xba, 0xb2, 0x90, 0xe0, 0x4f, 0xe1, 0x5c, 0x19 } } + +#define NS_WEBBROWSERCONTENTPOLICY_CONTRACTID \ + "@mozilla.org/embedding/browser/content-policy;1" + +class nsWebBrowserContentPolicy : public nsIContentPolicy +{ +protected: + virtual ~nsWebBrowserContentPolicy(); + +public: + nsWebBrowserContentPolicy(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICONTENTPOLICY +}; diff --git a/embedding/components/appstartup/moz.build b/embedding/components/appstartup/moz.build new file mode 100644 index 000000000..237591a50 --- /dev/null +++ b/embedding/components/appstartup/moz.build @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'nsIAppStartupNotifier.h', +] + +SOURCES += [ + 'nsAppStartupNotifier.cpp', +] + +FINAL_LIBRARY = 'xul' diff --git a/embedding/components/appstartup/nsAppStartupNotifier.cpp b/embedding/components/appstartup/nsAppStartupNotifier.cpp new file mode 100644 index 000000000..4ef38fb8d --- /dev/null +++ b/embedding/components/appstartup/nsAppStartupNotifier.cpp @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsXPIDLString.h" +#include "nsIServiceManager.h" +#include "nsICategoryManager.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsAppStartupNotifier.h" +#include "nsISimpleEnumerator.h" + +NS_IMPL_ISUPPORTS(nsAppStartupNotifier, nsIObserver) + +nsAppStartupNotifier::nsAppStartupNotifier() +{ +} + +nsAppStartupNotifier::~nsAppStartupNotifier() +{ +} + +NS_IMETHODIMP nsAppStartupNotifier::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData) +{ + NS_ENSURE_ARG(aTopic); + nsresult rv; + + // now initialize all startup listeners + nsCOMPtr<nsICategoryManager> categoryManager = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = categoryManager->EnumerateCategory(aTopic, + getter_AddRefs(enumerator)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsISupports> entry; + while (NS_SUCCEEDED(enumerator->GetNext(getter_AddRefs(entry)))) { + nsCOMPtr<nsISupportsCString> category = do_QueryInterface(entry, &rv); + + if (NS_SUCCEEDED(rv)) { + nsAutoCString categoryEntry; + rv = category->GetData(categoryEntry); + + nsXPIDLCString contractId; + categoryManager->GetCategoryEntry(aTopic, + categoryEntry.get(), + getter_Copies(contractId)); + + if (NS_SUCCEEDED(rv)) { + + // If we see the word "service," in the beginning + // of the contractId then we create it as a service + // if not we do a createInstance + + nsCOMPtr<nsISupports> startupInstance; + if (Substring(contractId, 0, 8).EqualsLiteral("service,")) + startupInstance = do_GetService(contractId.get() + 8, &rv); + else + startupInstance = do_CreateInstance(contractId, &rv); + + if (NS_SUCCEEDED(rv)) { + // Try to QI to nsIObserver + nsCOMPtr<nsIObserver> startupObserver = + do_QueryInterface(startupInstance, &rv); + if (NS_SUCCEEDED(rv)) { + rv = startupObserver->Observe(nullptr, aTopic, nullptr); + + // mainly for debugging if you want to know if your observer worked. + NS_ASSERTION(NS_SUCCEEDED(rv), "Startup Observer failed!\n"); + } + } + else { + #ifdef DEBUG + nsAutoCString warnStr("Cannot create startup observer : "); + warnStr += contractId.get(); + NS_WARNING(warnStr.get()); + #endif + } + + } + } + } + + return NS_OK; +} diff --git a/embedding/components/appstartup/nsAppStartupNotifier.h b/embedding/components/appstartup/nsAppStartupNotifier.h new file mode 100644 index 000000000..31077ed38 --- /dev/null +++ b/embedding/components/appstartup/nsAppStartupNotifier.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAppStartupNotifier_h___ +#define nsAppStartupNotifier_h___ + +#include "nsIAppStartupNotifier.h" + +// {1F59B001-02C9-11d5-AE76-CC92F7DB9E03} +#define NS_APPSTARTUPNOTIFIER_CID \ + { 0x1f59b001, 0x2c9, 0x11d5, { 0xae, 0x76, 0xcc, 0x92, 0xf7, 0xdb, 0x9e, 0x3 } } + +class nsAppStartupNotifier : public nsIObserver +{ +public: + NS_DEFINE_STATIC_CID_ACCESSOR( NS_APPSTARTUPNOTIFIER_CID ) + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + nsAppStartupNotifier(); + +protected: + virtual ~nsAppStartupNotifier(); +}; + +#endif /* nsAppStartupNotifier_h___ */ + diff --git a/embedding/components/appstartup/nsIAppStartupNotifier.h b/embedding/components/appstartup/nsIAppStartupNotifier.h new file mode 100644 index 000000000..113bfa171 --- /dev/null +++ b/embedding/components/appstartup/nsIAppStartupNotifier.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsIAppStartupNotifier_h___ +#define nsIAppStartupNotifier_h___ + +#include "nsIObserver.h" + +/* + Some components need to be run at the startup of mozilla or embedding - to + start new services etc. + + This interface provides a generic way to start up arbitrary components + without requiring them to hack into main1() (or into NS_InitEmbedding) as + it's currently being done for services such as wallet, command line handlers + etc. + + We will have a category called "app-startup" which components register + themselves in using the CategoryManager. + + Components can also (optionally) add the word "service," as a prefix + to the "value" they pass in during a call to AddCategoryEntry() as + shown below: + + categoryManager->AddCategoryEntry(APPSTARTUP_CATEGORY, "testcomp", + "service," NS_WALLETSERVICE_CONTRACTID + true, true, + getter_Copies(previous)); + + Presence of the "service" keyword indicates the components desire to + be started as a service. When the "service" keyword is not present + we just do a do_CreateInstance. + + When mozilla starts (and when NS_InitEmbedding()) is invoked + we create an instance of the AppStartupNotifier component (which + implements nsIObserver) and invoke its Observe() method. + + Observe() will enumerate the components registered into the + APPSTARTUP_CATEGORY and notify them that startup has begun + and release them. +*/ + +#define NS_APPSTARTUPNOTIFIER_CONTRACTID "@mozilla.org/embedcomp/appstartup-notifier;1" + +#define APPSTARTUP_CATEGORY "app-startup" +#define APPSTARTUP_TOPIC "app-startup" + + +/* + Please note that there's not a new interface in this file. + We're just leveraging nsIObserver instead of creating a + new one + + This file exists solely to provide the defines above +*/ + +#endif /* nsIAppStartupNotifier_h___ */ diff --git a/embedding/components/build/moz.build b/embedding/components/build/moz.build new file mode 100644 index 000000000..defbfc0c8 --- /dev/null +++ b/embedding/components/build/moz.build @@ -0,0 +1,38 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'nsEmbeddingModule.cpp', +] + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '../appstartup', + '../commandhandler', + '../find', + '../printingui/ipc', + '../webbrowserpersist', + '../windowwatcher', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': + DEFINES['PROXY_PRINTING'] = 1 + LOCAL_INCLUDES += [ + '../printingui/win', + ] +elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + DEFINES['PROXY_PRINTING'] = 1 + LOCAL_INCLUDES += [ + '../printingui/mac', + ] + +if CONFIG['MOZ_PDF_PRINTING']: + DEFINES['PROXY_PRINTING'] = 1 + LOCAL_INCLUDES += [ + '../printingui/unixshared', + ] + +include('/ipc/chromium/chromium-config.mozbuild') diff --git a/embedding/components/build/nsEmbeddingModule.cpp b/embedding/components/build/nsEmbeddingModule.cpp new file mode 100644 index 000000000..48351ec3a --- /dev/null +++ b/embedding/components/build/nsEmbeddingModule.cpp @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ModuleUtils.h" +#include "nsDialogParamBlock.h" +#include "nsWindowWatcher.h" +#include "nsAppStartupNotifier.h" +#include "nsFind.h" +#include "nsWebBrowserFind.h" +#include "nsWebBrowserPersist.h" +#include "nsCommandManager.h" +#include "nsControllerCommandTable.h" +#include "nsCommandParams.h" +#include "nsCommandGroup.h" +#include "nsBaseCommandController.h" +#include "nsNetCID.h" +#include "nsEmbedCID.h" + +#ifdef NS_PRINTING +#include "nsPrintingPromptService.h" +#include "nsPrintingProxy.h" +#endif + + +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWindowWatcher, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsAppStartupNotifier) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsFind) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebBrowserFind) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsWebBrowserPersist) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsControllerCommandTable) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsCommandManager) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsCommandParams) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsControllerCommandGroup) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsBaseCommandController) + +#ifdef MOZ_XUL +NS_GENERIC_FACTORY_CONSTRUCTOR(nsDialogParamBlock) +#ifdef NS_PRINTING +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrintingPromptService, Init) +#ifdef PROXY_PRINTING +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsPrintingProxy, + nsPrintingProxy::GetInstance) +#endif +#endif +#endif + +#ifdef MOZ_XUL +NS_DEFINE_NAMED_CID(NS_DIALOGPARAMBLOCK_CID); +#ifdef NS_PRINTING +NS_DEFINE_NAMED_CID(NS_PRINTINGPROMPTSERVICE_CID); +#endif +#endif +NS_DEFINE_NAMED_CID(NS_WINDOWWATCHER_CID); +NS_DEFINE_NAMED_CID(NS_FIND_CID); +NS_DEFINE_NAMED_CID(NS_WEB_BROWSER_FIND_CID); +NS_DEFINE_NAMED_CID(NS_APPSTARTUPNOTIFIER_CID); +NS_DEFINE_NAMED_CID(NS_WEBBROWSERPERSIST_CID); +NS_DEFINE_NAMED_CID(NS_CONTROLLERCOMMANDTABLE_CID); +NS_DEFINE_NAMED_CID(NS_COMMAND_MANAGER_CID); +NS_DEFINE_NAMED_CID(NS_COMMAND_PARAMS_CID); +NS_DEFINE_NAMED_CID(NS_CONTROLLER_COMMAND_GROUP_CID); +NS_DEFINE_NAMED_CID(NS_BASECOMMANDCONTROLLER_CID); + +static const mozilla::Module::CIDEntry kEmbeddingCIDs[] = { +#ifdef MOZ_XUL + { &kNS_DIALOGPARAMBLOCK_CID, false, nullptr, nsDialogParamBlockConstructor }, +#ifdef NS_PRINTING + +#ifdef PROXY_PRINTING + { &kNS_PRINTINGPROMPTSERVICE_CID, false, nullptr, nsPrintingPromptServiceConstructor, + mozilla::Module::MAIN_PROCESS_ONLY }, + { &kNS_PRINTINGPROMPTSERVICE_CID, false, nullptr, nsPrintingProxyConstructor, + mozilla::Module::CONTENT_PROCESS_ONLY }, +#else + { &kNS_PRINTINGPROMPTSERVICE_CID, false, nullptr, nsPrintingPromptServiceConstructor }, +#endif +#endif +#endif + { &kNS_WINDOWWATCHER_CID, false, nullptr, nsWindowWatcherConstructor }, + { &kNS_FIND_CID, false, nullptr, nsFindConstructor }, + { &kNS_WEB_BROWSER_FIND_CID, false, nullptr, nsWebBrowserFindConstructor }, + { &kNS_APPSTARTUPNOTIFIER_CID, false, nullptr, nsAppStartupNotifierConstructor }, + { &kNS_WEBBROWSERPERSIST_CID, false, nullptr, nsWebBrowserPersistConstructor }, + { &kNS_CONTROLLERCOMMANDTABLE_CID, false, nullptr, nsControllerCommandTableConstructor }, + { &kNS_COMMAND_MANAGER_CID, false, nullptr, nsCommandManagerConstructor }, + { &kNS_COMMAND_PARAMS_CID, false, nullptr, nsCommandParamsConstructor }, + { &kNS_CONTROLLER_COMMAND_GROUP_CID, false, nullptr, nsControllerCommandGroupConstructor }, + { &kNS_BASECOMMANDCONTROLLER_CID, false, nullptr, nsBaseCommandControllerConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kEmbeddingContracts[] = { +#ifdef MOZ_XUL + { NS_DIALOGPARAMBLOCK_CONTRACTID, &kNS_DIALOGPARAMBLOCK_CID }, +#ifdef NS_PRINTING + { NS_PRINTINGPROMPTSERVICE_CONTRACTID, &kNS_PRINTINGPROMPTSERVICE_CID }, +#endif +#endif + { NS_WINDOWWATCHER_CONTRACTID, &kNS_WINDOWWATCHER_CID }, + { NS_FIND_CONTRACTID, &kNS_FIND_CID }, + { NS_WEB_BROWSER_FIND_CONTRACTID, &kNS_WEB_BROWSER_FIND_CID }, + { NS_APPSTARTUPNOTIFIER_CONTRACTID, &kNS_APPSTARTUPNOTIFIER_CID }, + { NS_WEBBROWSERPERSIST_CONTRACTID, &kNS_WEBBROWSERPERSIST_CID }, + { NS_CONTROLLERCOMMANDTABLE_CONTRACTID, &kNS_CONTROLLERCOMMANDTABLE_CID }, + { NS_COMMAND_MANAGER_CONTRACTID, &kNS_COMMAND_MANAGER_CID }, + { NS_COMMAND_PARAMS_CONTRACTID, &kNS_COMMAND_PARAMS_CID }, + { NS_CONTROLLER_COMMAND_GROUP_CONTRACTID, &kNS_CONTROLLER_COMMAND_GROUP_CID }, + { NS_BASECOMMANDCONTROLLER_CONTRACTID, &kNS_BASECOMMANDCONTROLLER_CID }, + { nullptr } +}; + +static const mozilla::Module kEmbeddingModule = { + mozilla::Module::kVersion, + kEmbeddingCIDs, + kEmbeddingContracts +}; + +NSMODULE_DEFN(embedcomponents) = &kEmbeddingModule; diff --git a/embedding/components/commandhandler/moz.build b/embedding/components/commandhandler/moz.build new file mode 100644 index 000000000..d44d57f20 --- /dev/null +++ b/embedding/components/commandhandler/moz.build @@ -0,0 +1,26 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + 'nsICommandManager.idl', + 'nsICommandParams.idl', + 'nsIControllerCommand.idl', + 'nsIControllerCommandTable.idl', + 'nsIControllerContext.idl', + 'nsPICommandUpdater.idl', +] + +XPIDL_MODULE = 'commandhandler' + +UNIFIED_SOURCES += [ + 'nsBaseCommandController.cpp', + 'nsCommandGroup.cpp', + 'nsCommandManager.cpp', + 'nsCommandParams.cpp', + 'nsControllerCommandTable.cpp', +] + +FINAL_LIBRARY = 'xul' diff --git a/embedding/components/commandhandler/nsBaseCommandController.cpp b/embedding/components/commandhandler/nsBaseCommandController.cpp new file mode 100644 index 000000000..2bd87b6f5 --- /dev/null +++ b/embedding/components/commandhandler/nsBaseCommandController.cpp @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" +#include "nsIComponentManager.h" +#include "nsBaseCommandController.h" + +#include "nsString.h" +#include "nsWeakPtr.h" + +NS_IMPL_ADDREF(nsBaseCommandController) +NS_IMPL_RELEASE(nsBaseCommandController) + +NS_INTERFACE_MAP_BEGIN(nsBaseCommandController) + NS_INTERFACE_MAP_ENTRY(nsIController) + NS_INTERFACE_MAP_ENTRY(nsICommandController) + NS_INTERFACE_MAP_ENTRY(nsIControllerContext) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIControllerContext) +NS_INTERFACE_MAP_END + +nsBaseCommandController::nsBaseCommandController() + : mCommandContextRawPtr(nullptr) +{ +} + +nsBaseCommandController::~nsBaseCommandController() +{ +} + +NS_IMETHODIMP +nsBaseCommandController::Init(nsIControllerCommandTable* aCommandTable) +{ + nsresult rv = NS_OK; + + if (aCommandTable) { + mCommandTable = aCommandTable; + } else { + mCommandTable = + do_CreateInstance(NS_CONTROLLERCOMMANDTABLE_CONTRACTID, &rv); + } + + return rv; +} + +NS_IMETHODIMP +nsBaseCommandController::SetCommandContext(nsISupports* aCommandContext) +{ + mCommandContextWeakPtr = nullptr; + mCommandContextRawPtr = nullptr; + + if (aCommandContext) { + nsCOMPtr<nsISupportsWeakReference> weak = do_QueryInterface(aCommandContext); + if (weak) { + nsresult rv = + weak->GetWeakReference(getter_AddRefs(mCommandContextWeakPtr)); + NS_ENSURE_SUCCESS(rv, rv); + } else { + mCommandContextRawPtr = aCommandContext; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBaseCommandController::GetInterface(const nsIID& aIID, void** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + if (NS_SUCCEEDED(QueryInterface(aIID, aResult))) { + return NS_OK; + } + + if (aIID.Equals(NS_GET_IID(nsIControllerCommandTable))) { + if (mCommandTable) { + return mCommandTable->QueryInterface(aIID, aResult); + } + return NS_ERROR_NOT_INITIALIZED; + } + + return NS_NOINTERFACE; +} + +/* ======================================================================= + * nsIController + * ======================================================================= */ + +NS_IMETHODIMP +nsBaseCommandController::IsCommandEnabled(const char* aCommand, bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aCommand); + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_STATE(mCommandTable); + + nsISupports* context = mCommandContextRawPtr; + nsCOMPtr<nsISupports> weak; + if (!context) { + weak = do_QueryReferent(mCommandContextWeakPtr); + context = weak; + } + return mCommandTable->IsCommandEnabled(aCommand, context, aResult); +} + +NS_IMETHODIMP +nsBaseCommandController::SupportsCommand(const char* aCommand, bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aCommand); + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_STATE(mCommandTable); + + nsISupports* context = mCommandContextRawPtr; + nsCOMPtr<nsISupports> weak; + if (!context) { + weak = do_QueryReferent(mCommandContextWeakPtr); + context = weak; + } + return mCommandTable->SupportsCommand(aCommand, context, aResult); +} + +NS_IMETHODIMP +nsBaseCommandController::DoCommand(const char* aCommand) +{ + NS_ENSURE_ARG_POINTER(aCommand); + NS_ENSURE_STATE(mCommandTable); + + nsISupports* context = mCommandContextRawPtr; + nsCOMPtr<nsISupports> weak; + if (!context) { + weak = do_QueryReferent(mCommandContextWeakPtr); + context = weak; + } + return mCommandTable->DoCommand(aCommand, context); +} + +NS_IMETHODIMP +nsBaseCommandController::DoCommandWithParams(const char* aCommand, + nsICommandParams* aParams) +{ + NS_ENSURE_ARG_POINTER(aCommand); + NS_ENSURE_STATE(mCommandTable); + + nsISupports* context = mCommandContextRawPtr; + nsCOMPtr<nsISupports> weak; + if (!context) { + weak = do_QueryReferent(mCommandContextWeakPtr); + context = weak; + } + return mCommandTable->DoCommandParams(aCommand, aParams, context); +} + +NS_IMETHODIMP +nsBaseCommandController::GetCommandStateWithParams(const char* aCommand, + nsICommandParams* aParams) +{ + NS_ENSURE_ARG_POINTER(aCommand); + NS_ENSURE_STATE(mCommandTable); + + nsISupports* context = mCommandContextRawPtr; + nsCOMPtr<nsISupports> weak; + if (!context) { + weak = do_QueryReferent(mCommandContextWeakPtr); + context = weak; + } + return mCommandTable->GetCommandState(aCommand, aParams, context); +} + +NS_IMETHODIMP +nsBaseCommandController::OnEvent(const char* aEventName) +{ + NS_ENSURE_ARG_POINTER(aEventName); + return NS_OK; +} + +NS_IMETHODIMP +nsBaseCommandController::GetSupportedCommands(uint32_t* aCount, + char*** aCommands) +{ + NS_ENSURE_STATE(mCommandTable); + return mCommandTable->GetSupportedCommands(aCount, aCommands); +} diff --git a/embedding/components/commandhandler/nsBaseCommandController.h b/embedding/components/commandhandler/nsBaseCommandController.h new file mode 100644 index 000000000..83976faac --- /dev/null +++ b/embedding/components/commandhandler/nsBaseCommandController.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsBaseCommandController_h__ +#define nsBaseCommandController_h__ + +#define NS_BASECOMMANDCONTROLLER_CID \ + { 0xbf88b48c, 0xfd8e, 0x40b4, { 0xba, 0x36, 0xc7, 0xc3, 0xad, 0x6d, 0x8a, 0xc9 } } +#define NS_BASECOMMANDCONTROLLER_CONTRACTID \ + "@mozilla.org/embedcomp/base-command-controller;1" + +#include "nsIController.h" +#include "nsIControllerContext.h" +#include "nsIControllerCommandTable.h" +#include "nsIInterfaceRequestor.h" +#include "nsIWeakReferenceUtils.h" + +// The base editor controller is used for both text widgets, and all other text +// and html editing +class nsBaseCommandController + : public nsIController + , public nsIControllerContext + , public nsIInterfaceRequestor + , public nsICommandController +{ +public: + nsBaseCommandController(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICONTROLLER + NS_DECL_NSICOMMANDCONTROLLER + NS_DECL_NSICONTROLLERCONTEXT + NS_DECL_NSIINTERFACEREQUESTOR + +protected: + virtual ~nsBaseCommandController(); + +private: + nsWeakPtr mCommandContextWeakPtr; + nsISupports* mCommandContextRawPtr; + + // Our reference to the command manager + nsCOMPtr<nsIControllerCommandTable> mCommandTable; +}; + +#endif /* nsBaseCommandController_h_ */ diff --git a/embedding/components/commandhandler/nsCommandGroup.cpp b/embedding/components/commandhandler/nsCommandGroup.cpp new file mode 100644 index 000000000..696b93f67 --- /dev/null +++ b/embedding/components/commandhandler/nsCommandGroup.cpp @@ -0,0 +1,296 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsTArray.h" +#include "nsISimpleEnumerator.h" +#include "nsXPCOM.h" +#include "nsSupportsPrimitives.h" +#include "nsIComponentManager.h" +#include "nsCommandGroup.h" +#include "nsIControllerCommand.h" +#include "nsCRT.h" + +class nsGroupsEnumerator : public nsISimpleEnumerator +{ +public: + explicit nsGroupsEnumerator( + nsControllerCommandGroup::GroupsHashtable& aInHashTable); + + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATOR + +protected: + virtual ~nsGroupsEnumerator(); + + nsresult Initialize(); + +protected: + nsControllerCommandGroup::GroupsHashtable& mHashTable; + int32_t mIndex; + const char** mGroupNames; // array of pointers to char16_t* in the hash table + bool mInitted; +}; + +/* Implementation file */ +NS_IMPL_ISUPPORTS(nsGroupsEnumerator, nsISimpleEnumerator) + +nsGroupsEnumerator::nsGroupsEnumerator( + nsControllerCommandGroup::GroupsHashtable& aInHashTable) + : mHashTable(aInHashTable) + , mIndex(-1) + , mGroupNames(nullptr) + , mInitted(false) +{ +} + +nsGroupsEnumerator::~nsGroupsEnumerator() +{ + delete[] mGroupNames; +} + +NS_IMETHODIMP +nsGroupsEnumerator::HasMoreElements(bool* aResult) +{ + nsresult rv = NS_OK; + + NS_ENSURE_ARG_POINTER(aResult); + + if (!mInitted) { + rv = Initialize(); + if (NS_FAILED(rv)) { + return rv; + } + } + + *aResult = (mIndex < static_cast<int32_t>(mHashTable.Count()) - 1); + return NS_OK; +} + +NS_IMETHODIMP +nsGroupsEnumerator::GetNext(nsISupports** aResult) +{ + nsresult rv = NS_OK; + + NS_ENSURE_ARG_POINTER(aResult); + + if (!mInitted) { + rv = Initialize(); + if (NS_FAILED(rv)) { + return rv; + } + } + + mIndex++; + if (mIndex >= static_cast<int32_t>(mHashTable.Count())) { + return NS_ERROR_FAILURE; + } + + const char* thisGroupName = mGroupNames[mIndex]; + + nsCOMPtr<nsISupportsCString> supportsString = + do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + supportsString->SetData(nsDependentCString(thisGroupName)); + return CallQueryInterface(supportsString, aResult); +} + +nsresult +nsGroupsEnumerator::Initialize() +{ + if (mInitted) { + return NS_OK; + } + + mGroupNames = new const char*[mHashTable.Count()]; + if (!mGroupNames) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mIndex = 0; + for (auto iter = mHashTable.Iter(); !iter.Done(); iter.Next()) { + mGroupNames[mIndex] = iter.Key().Data(); + mIndex++; + } + + mIndex = -1; + mInitted = true; + return NS_OK; +} + +class nsNamedGroupEnumerator : public nsISimpleEnumerator +{ +public: + explicit nsNamedGroupEnumerator(nsTArray<nsCString>* aInArray); + + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATOR + +protected: + virtual ~nsNamedGroupEnumerator(); + + nsTArray<nsCString>* mGroupArray; + int32_t mIndex; +}; + +nsNamedGroupEnumerator::nsNamedGroupEnumerator(nsTArray<nsCString>* aInArray) + : mGroupArray(aInArray) + , mIndex(-1) +{ +} + +nsNamedGroupEnumerator::~nsNamedGroupEnumerator() +{ +} + +NS_IMPL_ISUPPORTS(nsNamedGroupEnumerator, nsISimpleEnumerator) + +NS_IMETHODIMP +nsNamedGroupEnumerator::HasMoreElements(bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + int32_t arrayLen = mGroupArray ? mGroupArray->Length() : 0; + *aResult = (mIndex < arrayLen - 1); + return NS_OK; +} + +NS_IMETHODIMP +nsNamedGroupEnumerator::GetNext(nsISupports** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + if (!mGroupArray) { + return NS_ERROR_FAILURE; + } + + mIndex++; + if (mIndex >= int32_t(mGroupArray->Length())) { + return NS_ERROR_FAILURE; + } + + const nsCString& thisGroupName = mGroupArray->ElementAt(mIndex); + + nsresult rv; + nsCOMPtr<nsISupportsCString> supportsString = + do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + supportsString->SetData(thisGroupName); + return CallQueryInterface(supportsString, aResult); +} + +NS_IMPL_ISUPPORTS(nsControllerCommandGroup, nsIControllerCommandGroup) + +nsControllerCommandGroup::nsControllerCommandGroup() +{ +} + +nsControllerCommandGroup::~nsControllerCommandGroup() +{ + ClearGroupsHash(); +} + +void +nsControllerCommandGroup::ClearGroupsHash() +{ + mGroupsHash.Clear(); +} + +NS_IMETHODIMP +nsControllerCommandGroup::AddCommandToGroup(const char* aCommand, + const char* aGroup) +{ + nsDependentCString groupKey(aGroup); + nsTArray<nsCString>* commandList = mGroupsHash.Get(groupKey); + if (!commandList) { + // make this list + commandList = new AutoTArray<nsCString, 8>; + mGroupsHash.Put(groupKey, commandList); + } + +#ifdef DEBUG + nsCString* appended = +#endif + commandList->AppendElement(aCommand); + NS_ASSERTION(appended, "Append failed"); + + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandGroup::RemoveCommandFromGroup(const char* aCommand, + const char* aGroup) +{ + nsDependentCString groupKey(aGroup); + nsTArray<nsCString>* commandList = mGroupsHash.Get(groupKey); + if (!commandList) { + return NS_OK; // no group + } + + uint32_t numEntries = commandList->Length(); + for (uint32_t i = 0; i < numEntries; i++) { + nsCString commandString = commandList->ElementAt(i); + if (nsDependentCString(aCommand) != commandString) { + commandList->RemoveElementAt(i); + break; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandGroup::IsCommandInGroup(const char* aCommand, + const char* aGroup, bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + + nsDependentCString groupKey(aGroup); + nsTArray<nsCString>* commandList = mGroupsHash.Get(groupKey); + if (!commandList) { + return NS_OK; // no group + } + + uint32_t numEntries = commandList->Length(); + for (uint32_t i = 0; i < numEntries; i++) { + nsCString commandString = commandList->ElementAt(i); + if (nsDependentCString(aCommand) != commandString) { + *aResult = true; + break; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandGroup::GetGroupsEnumerator(nsISimpleEnumerator** aResult) +{ + RefPtr<nsGroupsEnumerator> groupsEnum = new nsGroupsEnumerator(mGroupsHash); + + groupsEnum.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandGroup::GetEnumeratorForGroup(const char* aGroup, + nsISimpleEnumerator** aResult) +{ + nsDependentCString groupKey(aGroup); + nsTArray<nsCString>* commandList = mGroupsHash.Get(groupKey); // may be null + + RefPtr<nsNamedGroupEnumerator> theGroupEnum = + new nsNamedGroupEnumerator(commandList); + + theGroupEnum.forget(aResult); + return NS_OK; +} diff --git a/embedding/components/commandhandler/nsCommandGroup.h b/embedding/components/commandhandler/nsCommandGroup.h new file mode 100644 index 000000000..b06d725b3 --- /dev/null +++ b/embedding/components/commandhandler/nsCommandGroup.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCommandGroup_h__ +#define nsCommandGroup_h__ + +#include "nsIController.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" + +// {ecd55a01-2780-11d5-a73c-ca641a6813bc} +#define NS_CONTROLLER_COMMAND_GROUP_CID \ + { 0xecd55a01, 0x2780, 0x11d5, { 0xa7, 0x3c, 0xca, 0x64, 0x1a, 0x68, 0x13, 0xbc } } + +#define NS_CONTROLLER_COMMAND_GROUP_CONTRACTID \ + "@mozilla.org/embedcomp/controller-command-group;1" + +class nsControllerCommandGroup : public nsIControllerCommandGroup +{ +public: + nsControllerCommandGroup(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICONTROLLERCOMMANDGROUP + +public: + typedef nsClassHashtable<nsCStringHashKey, nsTArray<nsCString>> + GroupsHashtable; + +protected: + virtual ~nsControllerCommandGroup(); + + void ClearGroupsHash(); + +protected: + // Hash keyed on command group. This could be made more space-efficient, + // maybe with atoms. + GroupsHashtable mGroupsHash; +}; + +#endif // nsCommandGroup_h__ diff --git a/embedding/components/commandhandler/nsCommandManager.cpp b/embedding/components/commandhandler/nsCommandManager.cpp new file mode 100644 index 000000000..c11a901b2 --- /dev/null +++ b/embedding/components/commandhandler/nsCommandManager.cpp @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" + +#include "nsIController.h" +#include "nsIControllers.h" +#include "nsIObserver.h" + +#include "nsIComponentManager.h" + +#include "nsServiceManagerUtils.h" +#include "nsIScriptSecurityManager.h" + +#include "nsContentUtils.h" +#include "nsIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsPIWindowRoot.h" +#include "nsIFocusManager.h" + +#include "nsCOMArray.h" + +#include "nsCommandManager.h" + +nsCommandManager::nsCommandManager() + : mWindow(nullptr) +{ +} + +nsCommandManager::~nsCommandManager() +{ +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsCommandManager) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsCommandManager) + tmp->mObserversTable.Clear(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsCommandManager) + for (auto iter = tmp->mObserversTable.Iter(); !iter.Done(); iter.Next()) { + nsCommandManager::ObserverList* observers = iter.UserData(); + int32_t numItems = observers->Length(); + for (int32_t i = 0; i < numItems; ++i) { + cb.NoteXPCOMChild(observers->ElementAt(i)); + } + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsCommandManager) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsCommandManager) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsCommandManager) + NS_INTERFACE_MAP_ENTRY(nsICommandManager) + NS_INTERFACE_MAP_ENTRY(nsPICommandUpdater) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsICommandManager) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +nsCommandManager::Init(mozIDOMWindowProxy* aWindow) +{ + NS_ENSURE_ARG_POINTER(aWindow); + + mWindow = aWindow; // weak ptr + return NS_OK; +} + +NS_IMETHODIMP +nsCommandManager::CommandStatusChanged(const char* aCommandName) +{ + ObserverList* commandObservers; + mObserversTable.Get(aCommandName, &commandObservers); + + if (commandObservers) { + // XXX Should we worry about observers removing themselves from Observe()? + int32_t i, numItems = commandObservers->Length(); + for (i = 0; i < numItems; ++i) { + nsCOMPtr<nsIObserver> observer = commandObservers->ElementAt(i); + // should we get the command state to pass here? This might be expensive. + observer->Observe(NS_ISUPPORTS_CAST(nsICommandManager*, this), + aCommandName, + u"command_status_changed"); + } + } + + return NS_OK; +} + +#if 0 +#pragma mark - +#endif + +NS_IMETHODIMP +nsCommandManager::AddCommandObserver(nsIObserver* aCommandObserver, + const char* aCommandToObserve) +{ + NS_ENSURE_ARG(aCommandObserver); + + // XXX todo: handle special cases of aCommandToObserve being null, or empty + + // for each command in the table, we make a list of observers for that command + ObserverList* commandObservers; + if (!mObserversTable.Get(aCommandToObserve, &commandObservers)) { + commandObservers = new ObserverList; + mObserversTable.Put(aCommandToObserve, commandObservers); + } + + // need to check that this command observer hasn't already been registered + int32_t existingIndex = commandObservers->IndexOf(aCommandObserver); + if (existingIndex == -1) { + commandObservers->AppendElement(aCommandObserver); + } else { + NS_WARNING("Registering command observer twice on the same command"); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCommandManager::RemoveCommandObserver(nsIObserver* aCommandObserver, + const char* aCommandObserved) +{ + NS_ENSURE_ARG(aCommandObserver); + + // XXX todo: handle special cases of aCommandToObserve being null, or empty + + ObserverList* commandObservers; + if (!mObserversTable.Get(aCommandObserved, &commandObservers)) { + return NS_ERROR_UNEXPECTED; + } + + commandObservers->RemoveElement(aCommandObserver); + + return NS_OK; +} + +NS_IMETHODIMP +nsCommandManager::IsCommandSupported(const char* aCommandName, + mozIDOMWindowProxy* aTargetWindow, + bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + nsCOMPtr<nsIController> controller; + GetControllerForCommand(aCommandName, aTargetWindow, + getter_AddRefs(controller)); + *aResult = (controller.get() != nullptr); + return NS_OK; +} + +NS_IMETHODIMP +nsCommandManager::IsCommandEnabled(const char* aCommandName, + mozIDOMWindowProxy* aTargetWindow, + bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + bool commandEnabled = false; + + nsCOMPtr<nsIController> controller; + GetControllerForCommand(aCommandName, aTargetWindow, + getter_AddRefs(controller)); + if (controller) { + controller->IsCommandEnabled(aCommandName, &commandEnabled); + } + *aResult = commandEnabled; + return NS_OK; +} + +NS_IMETHODIMP +nsCommandManager::GetCommandState(const char* aCommandName, + mozIDOMWindowProxy* aTargetWindow, + nsICommandParams* aCommandParams) +{ + nsCOMPtr<nsIController> controller; + nsAutoString tValue; + nsresult rv = GetControllerForCommand(aCommandName, aTargetWindow, + getter_AddRefs(controller)); + if (!controller) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsICommandController> commandController = + do_QueryInterface(controller); + if (commandController) { + rv = commandController->GetCommandStateWithParams(aCommandName, + aCommandParams); + } else { + rv = NS_ERROR_NOT_IMPLEMENTED; + } + return rv; +} + +NS_IMETHODIMP +nsCommandManager::DoCommand(const char* aCommandName, + nsICommandParams* aCommandParams, + mozIDOMWindowProxy* aTargetWindow) +{ + nsCOMPtr<nsIController> controller; + nsresult rv = GetControllerForCommand(aCommandName, aTargetWindow, + getter_AddRefs(controller)); + if (!controller) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsICommandController> commandController = + do_QueryInterface(controller); + if (commandController && aCommandParams) { + rv = commandController->DoCommandWithParams(aCommandName, aCommandParams); + } else { + rv = controller->DoCommand(aCommandName); + } + return rv; +} + +nsresult +nsCommandManager::GetControllerForCommand(const char* aCommand, + mozIDOMWindowProxy* aTargetWindow, + nsIController** aResult) +{ + nsresult rv = NS_ERROR_FAILURE; + *aResult = nullptr; + + // check if we're in content or chrome + // if we're not chrome we must have a target window or we bail + if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) { + if (!aTargetWindow) { + return rv; + } + + // if a target window is specified, it must be the window we expect + if (aTargetWindow != mWindow) { + return NS_ERROR_FAILURE; + } + } + + if (auto* targetWindow = nsPIDOMWindowOuter::From(aTargetWindow)) { + // get the controller for this particular window + nsCOMPtr<nsIControllers> controllers; + rv = targetWindow->GetControllers(getter_AddRefs(controllers)); + if (NS_FAILED(rv)) { + return rv; + } + if (!controllers) { + return NS_ERROR_FAILURE; + } + + // dispatch the command + return controllers->GetControllerForCommand(aCommand, aResult); + } + + auto* window = nsPIDOMWindowOuter::From(mWindow); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot(); + NS_ENSURE_TRUE(root, NS_ERROR_FAILURE); + + // no target window; send command to focus controller + return root->GetControllerForCommand(aCommand, aResult); +} diff --git a/embedding/components/commandhandler/nsCommandManager.h b/embedding/components/commandhandler/nsCommandManager.h new file mode 100644 index 000000000..1d26c40ba --- /dev/null +++ b/embedding/components/commandhandler/nsCommandManager.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCommandManager_h__ +#define nsCommandManager_h__ + +#include "nsString.h" +#include "nsClassHashtable.h" +#include "nsWeakReference.h" + +#include "nsICommandManager.h" +#include "nsPICommandUpdater.h" +#include "nsCycleCollectionParticipant.h" + +class nsIController; +template<class E> class nsCOMArray; + +class nsCommandManager + : public nsICommandManager + , public nsPICommandUpdater + , public nsSupportsWeakReference +{ +public: + typedef nsTArray<nsCOMPtr<nsIObserver> > ObserverList; + + nsCommandManager(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsCommandManager, nsICommandManager) + + NS_DECL_NSICOMMANDMANAGER + NS_DECL_NSPICOMMANDUPDATER + +protected: + virtual ~nsCommandManager(); + + nsresult GetControllerForCommand(const char* aCommand, + mozIDOMWindowProxy* aDirectedToThisWindow, + nsIController** aResult); + +protected: + nsClassHashtable<nsCharPtrHashKey, ObserverList> mObserversTable; + + mozIDOMWindowProxy* mWindow; // weak ptr. The window should always outlive us +}; + +#endif // nsCommandManager_h__ diff --git a/embedding/components/commandhandler/nsCommandParams.cpp b/embedding/components/commandhandler/nsCommandParams.cpp new file mode 100644 index 000000000..86647d4dd --- /dev/null +++ b/embedding/components/commandhandler/nsCommandParams.cpp @@ -0,0 +1,264 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcom-config.h" +#include <new> +#include "nscore.h" +#include "nsCRT.h" + +#include "nsCommandParams.h" +#include "mozilla/HashFunctions.h" + +using namespace mozilla; + +const PLDHashTableOps nsCommandParams::sHashOps = +{ + HashKey, + HashMatchEntry, + HashMoveEntry, + HashClearEntry +}; + +NS_IMPL_ISUPPORTS(nsCommandParams, nsICommandParams) + +nsCommandParams::nsCommandParams() + : mValuesHash(&sHashOps, sizeof(HashEntry), 2) +{ +} + +nsCommandParams::~nsCommandParams() +{ +} + +NS_IMETHODIMP +nsCommandParams::GetValueType(const char* aName, int16_t* aRetVal) +{ + NS_ENSURE_ARG_POINTER(aRetVal); + + HashEntry* foundEntry = GetNamedEntry(aName); + if (foundEntry) { + *aRetVal = foundEntry->mEntryType; + return NS_OK; + } + *aRetVal = eNoType; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsCommandParams::GetBooleanValue(const char* aName, bool* aRetVal) +{ + NS_ENSURE_ARG_POINTER(aRetVal); + + HashEntry* foundEntry = GetNamedEntry(aName); + if (foundEntry && foundEntry->mEntryType == eBooleanType) { + *aRetVal = foundEntry->mData.mBoolean; + return NS_OK; + } + *aRetVal = false; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsCommandParams::GetLongValue(const char* aName, int32_t* aRetVal) +{ + NS_ENSURE_ARG_POINTER(aRetVal); + + HashEntry* foundEntry = GetNamedEntry(aName); + if (foundEntry && foundEntry->mEntryType == eLongType) { + *aRetVal = foundEntry->mData.mLong; + return NS_OK; + } + *aRetVal = false; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsCommandParams::GetDoubleValue(const char* aName, double* aRetVal) +{ + NS_ENSURE_ARG_POINTER(aRetVal); + + HashEntry* foundEntry = GetNamedEntry(aName); + if (foundEntry && foundEntry->mEntryType == eDoubleType) { + *aRetVal = foundEntry->mData.mDouble; + return NS_OK; + } + *aRetVal = 0.0; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsCommandParams::GetStringValue(const char* aName, nsAString& aRetVal) +{ + HashEntry* foundEntry = GetNamedEntry(aName); + if (foundEntry && foundEntry->mEntryType == eWStringType) { + NS_ASSERTION(foundEntry->mData.mString, "Null string"); + aRetVal.Assign(*foundEntry->mData.mString); + return NS_OK; + } + aRetVal.Truncate(); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsCommandParams::GetCStringValue(const char* aName, char** aRetVal) +{ + NS_ENSURE_ARG_POINTER(aRetVal); + + HashEntry* foundEntry = GetNamedEntry(aName); + if (foundEntry && foundEntry->mEntryType == eStringType) { + NS_ASSERTION(foundEntry->mData.mCString, "Null string"); + *aRetVal = ToNewCString(*foundEntry->mData.mCString); + return NS_OK; + } + *aRetVal = nullptr; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsCommandParams::GetISupportsValue(const char* aName, nsISupports** aRetVal) +{ + NS_ENSURE_ARG_POINTER(aRetVal); + + HashEntry* foundEntry = GetNamedEntry(aName); + if (foundEntry && foundEntry->mEntryType == eISupportsType) { + NS_IF_ADDREF(*aRetVal = foundEntry->mISupports.get()); + return NS_OK; + } + *aRetVal = nullptr; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsCommandParams::SetBooleanValue(const char* aName, bool aValue) +{ + HashEntry* foundEntry = GetOrMakeEntry(aName, eBooleanType); + if (!foundEntry) { + return NS_ERROR_OUT_OF_MEMORY; + } + foundEntry->mData.mBoolean = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsCommandParams::SetLongValue(const char* aName, int32_t aValue) +{ + HashEntry* foundEntry = GetOrMakeEntry(aName, eLongType); + if (!foundEntry) { + return NS_ERROR_OUT_OF_MEMORY; + } + foundEntry->mData.mLong = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsCommandParams::SetDoubleValue(const char* aName, double aValue) +{ + HashEntry* foundEntry = GetOrMakeEntry(aName, eDoubleType); + if (!foundEntry) { + return NS_ERROR_OUT_OF_MEMORY; + } + foundEntry->mData.mDouble = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsCommandParams::SetStringValue(const char* aName, const nsAString& aValue) +{ + HashEntry* foundEntry = GetOrMakeEntry(aName, eWStringType); + if (!foundEntry) { + return NS_ERROR_OUT_OF_MEMORY; + } + foundEntry->mData.mString = new nsString(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsCommandParams::SetCStringValue(const char* aName, const char* aValue) +{ + HashEntry* foundEntry = GetOrMakeEntry(aName, eStringType); + if (!foundEntry) { + return NS_ERROR_OUT_OF_MEMORY; + } + foundEntry->mData.mCString = new nsCString(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsCommandParams::SetISupportsValue(const char* aName, nsISupports* aValue) +{ + HashEntry* foundEntry = GetOrMakeEntry(aName, eISupportsType); + if (!foundEntry) { + return NS_ERROR_OUT_OF_MEMORY; + } + foundEntry->mISupports = aValue; // addrefs + return NS_OK; +} + +NS_IMETHODIMP +nsCommandParams::RemoveValue(const char* aName) +{ + mValuesHash.Remove((void*)aName); + return NS_OK; +} + +nsCommandParams::HashEntry* +nsCommandParams::GetNamedEntry(const char* aName) +{ + return static_cast<HashEntry*>(mValuesHash.Search((void*)aName)); +} + +nsCommandParams::HashEntry* +nsCommandParams::GetOrMakeEntry(const char* aName, uint8_t aEntryType) +{ + auto foundEntry = static_cast<HashEntry*>(mValuesHash.Search((void*)aName)); + if (foundEntry) { // reuse existing entry + foundEntry->Reset(aEntryType); + return foundEntry; + } + + foundEntry = static_cast<HashEntry*>(mValuesHash.Add((void*)aName, fallible)); + if (!foundEntry) { + return nullptr; + } + + // Use placement new. Our ctor does not clobber keyHash, which is important. + new (foundEntry) HashEntry(aEntryType, aName); + return foundEntry; +} + +PLDHashNumber +nsCommandParams::HashKey(const void* aKey) +{ + return HashString((const char*)aKey); +} + +bool +nsCommandParams::HashMatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey) +{ + const char* keyString = (const char*)aKey; + const HashEntry* thisEntry = static_cast<const HashEntry*>(aEntry); + return thisEntry->mEntryName.Equals(keyString); +} + +void +nsCommandParams::HashMoveEntry(PLDHashTable* aTable, + const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo) +{ + const HashEntry* fromEntry = static_cast<const HashEntry*>(aFrom); + HashEntry* toEntry = static_cast<HashEntry*>(aTo); + + new (toEntry) HashEntry(*fromEntry); + + fromEntry->~HashEntry(); +} + +void +nsCommandParams::HashClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry) +{ + HashEntry* thisEntry = static_cast<HashEntry*>(aEntry); + thisEntry->~HashEntry(); +} diff --git a/embedding/components/commandhandler/nsCommandParams.h b/embedding/components/commandhandler/nsCommandParams.h new file mode 100644 index 000000000..52df28d79 --- /dev/null +++ b/embedding/components/commandhandler/nsCommandParams.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsCommandParams_h__ +#define nsCommandParams_h__ + +#include "nsString.h" +#include "nsICommandParams.h" +#include "nsCOMPtr.h" +#include "PLDHashTable.h" + +class nsCommandParams : public nsICommandParams +{ +public: + nsCommandParams(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICOMMANDPARAMS + +protected: + virtual ~nsCommandParams(); + + struct HashEntry : public PLDHashEntryHdr + { + nsCString mEntryName; + + uint8_t mEntryType; + union + { + bool mBoolean; + int32_t mLong; + double mDouble; + nsString* mString; + nsCString* mCString; + } mData; + + nsCOMPtr<nsISupports> mISupports; + + HashEntry(uint8_t aType, const char* aEntryName) + : mEntryName(aEntryName) + , mEntryType(aType) + { + Reset(mEntryType); + } + + HashEntry(const HashEntry& aRHS) + : mEntryType(aRHS.mEntryType) + { + Reset(mEntryType); + switch (mEntryType) { + case eBooleanType: + mData.mBoolean = aRHS.mData.mBoolean; + break; + case eLongType: + mData.mLong = aRHS.mData.mLong; + break; + case eDoubleType: + mData.mDouble = aRHS.mData.mDouble; + break; + case eWStringType: + NS_ASSERTION(aRHS.mData.mString, "Source entry has no string"); + mData.mString = new nsString(*aRHS.mData.mString); + break; + case eStringType: + NS_ASSERTION(aRHS.mData.mCString, "Source entry has no string"); + mData.mCString = new nsCString(*aRHS.mData.mCString); + break; + case eISupportsType: + mISupports = aRHS.mISupports.get(); + break; + default: + NS_ERROR("Unknown type"); + } + } + + ~HashEntry() { Reset(eNoType); } + + void Reset(uint8_t aNewType) + { + switch (mEntryType) { + case eNoType: + break; + case eBooleanType: + mData.mBoolean = false; + break; + case eLongType: + mData.mLong = 0; + break; + case eDoubleType: + mData.mDouble = 0.0; + break; + case eWStringType: + delete mData.mString; + mData.mString = nullptr; + break; + case eISupportsType: + mISupports = nullptr; + break; + case eStringType: + delete mData.mCString; + mData.mCString = nullptr; + break; + default: + NS_ERROR("Unknown type"); + } + mEntryType = aNewType; + } + }; + + HashEntry* GetNamedEntry(const char* aName); + HashEntry* GetOrMakeEntry(const char* aName, uint8_t aEntryType); + +protected: + static PLDHashNumber HashKey(const void* aKey); + + static bool HashMatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey); + + static void HashMoveEntry(PLDHashTable* aTable, const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo); + + static void HashClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry); + + PLDHashTable mValuesHash; + + static const PLDHashTableOps sHashOps; +}; + +#endif // nsCommandParams_h__ diff --git a/embedding/components/commandhandler/nsControllerCommandTable.cpp b/embedding/components/commandhandler/nsControllerCommandTable.cpp new file mode 100644 index 000000000..91075bba4 --- /dev/null +++ b/embedding/components/commandhandler/nsControllerCommandTable.cpp @@ -0,0 +1,209 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsString.h" +#include "nsIControllerCommand.h" +#include "nsControllerCommandTable.h" + +nsresult NS_NewControllerCommandTable(nsIControllerCommandTable** aResult); + +// this value is used to size the hash table. Just a sensible upper bound +#define NUM_COMMANDS_LENGTH 32 + +nsControllerCommandTable::nsControllerCommandTable() + : mCommandsTable(NUM_COMMANDS_LENGTH) + , mMutable(true) +{ +} + +nsControllerCommandTable::~nsControllerCommandTable() +{ +} + +NS_IMPL_ISUPPORTS(nsControllerCommandTable, nsIControllerCommandTable, + nsISupportsWeakReference) + +NS_IMETHODIMP +nsControllerCommandTable::MakeImmutable(void) +{ + mMutable = false; + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandTable::RegisterCommand(const char* aCommandName, + nsIControllerCommand* aCommand) +{ + NS_ENSURE_TRUE(mMutable, NS_ERROR_FAILURE); + + mCommandsTable.Put(nsDependentCString(aCommandName), aCommand); + + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandTable::UnregisterCommand(const char* aCommandName, + nsIControllerCommand* aCommand) +{ + NS_ENSURE_TRUE(mMutable, NS_ERROR_FAILURE); + + nsDependentCString commandKey(aCommandName); + if (!mCommandsTable.Get(commandKey, nullptr)) { + return NS_ERROR_FAILURE; + } + + mCommandsTable.Remove(commandKey); + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandTable::FindCommandHandler(const char* aCommandName, + nsIControllerCommand** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = nullptr; + + nsCOMPtr<nsIControllerCommand> foundCommand; + mCommandsTable.Get(nsDependentCString(aCommandName), + getter_AddRefs(foundCommand)); + if (!foundCommand) { + return NS_ERROR_FAILURE; + } + + foundCommand.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandTable::IsCommandEnabled(const char* aCommandName, + nsISupports* aCommandRefCon, + bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = false; + + nsCOMPtr<nsIControllerCommand> commandHandler; + FindCommandHandler(aCommandName, getter_AddRefs(commandHandler)); + if (!commandHandler) { + NS_WARNING("Controller command table asked about a command that it does " + "not handle"); + return NS_OK; + } + + return commandHandler->IsCommandEnabled(aCommandName, aCommandRefCon, + aResult); +} + +NS_IMETHODIMP +nsControllerCommandTable::UpdateCommandState(const char* aCommandName, + nsISupports* aCommandRefCon) +{ + nsCOMPtr<nsIControllerCommand> commandHandler; + FindCommandHandler(aCommandName, getter_AddRefs(commandHandler)); + if (!commandHandler) { + NS_WARNING("Controller command table asked to update the state of a " + "command that it does not handle"); + return NS_OK; + } + + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsControllerCommandTable::SupportsCommand(const char* aCommandName, + nsISupports* aCommandRefCon, + bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + // XXX: need to check the readonly and disabled states + + *aResult = false; + + nsCOMPtr<nsIControllerCommand> commandHandler; + FindCommandHandler(aCommandName, getter_AddRefs(commandHandler)); + + *aResult = (commandHandler.get() != nullptr); + return NS_OK; +} + +NS_IMETHODIMP +nsControllerCommandTable::DoCommand(const char* aCommandName, + nsISupports* aCommandRefCon) +{ + nsCOMPtr<nsIControllerCommand> commandHandler; + FindCommandHandler(aCommandName, getter_AddRefs(commandHandler)); + if (!commandHandler) { + NS_WARNING("Controller command table asked to do a command that it does " + "not handle"); + return NS_OK; + } + + return commandHandler->DoCommand(aCommandName, aCommandRefCon); +} + +NS_IMETHODIMP +nsControllerCommandTable::DoCommandParams(const char* aCommandName, + nsICommandParams* aParams, + nsISupports* aCommandRefCon) +{ + nsCOMPtr<nsIControllerCommand> commandHandler; + FindCommandHandler(aCommandName, getter_AddRefs(commandHandler)); + if (!commandHandler) { + NS_WARNING("Controller command table asked to do a command that it does " + "not handle"); + return NS_OK; + } + return commandHandler->DoCommandParams(aCommandName, aParams, aCommandRefCon); +} + +NS_IMETHODIMP +nsControllerCommandTable::GetCommandState(const char* aCommandName, + nsICommandParams* aParams, + nsISupports* aCommandRefCon) +{ + nsCOMPtr<nsIControllerCommand> commandHandler; + FindCommandHandler(aCommandName, getter_AddRefs(commandHandler)); + if (!commandHandler) { + NS_WARNING("Controller command table asked to do a command that it does " + "not handle"); + return NS_OK; + } + return commandHandler->GetCommandStateParams(aCommandName, aParams, + aCommandRefCon); +} + +NS_IMETHODIMP +nsControllerCommandTable::GetSupportedCommands(uint32_t* aCount, + char*** aCommands) +{ + char** commands = + static_cast<char**>(moz_xmalloc(sizeof(char*) * mCommandsTable.Count())); + *aCount = mCommandsTable.Count(); + *aCommands = commands; + + for (auto iter = mCommandsTable.Iter(); !iter.Done(); iter.Next()) { + *commands = ToNewCString(iter.Key()); + commands++; + } + return NS_OK; +} + +nsresult +NS_NewControllerCommandTable(nsIControllerCommandTable** aResult) +{ + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + nsControllerCommandTable* newCommandTable = new nsControllerCommandTable(); + NS_ADDREF(newCommandTable); + *aResult = newCommandTable; + return NS_OK; +} diff --git a/embedding/components/commandhandler/nsControllerCommandTable.h b/embedding/components/commandhandler/nsControllerCommandTable.h new file mode 100644 index 000000000..353850bc0 --- /dev/null +++ b/embedding/components/commandhandler/nsControllerCommandTable.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsControllerCommandTable_h_ +#define nsControllerCommandTable_h_ + +#include "nsIControllerCommandTable.h" +#include "nsWeakReference.h" +#include "nsInterfaceHashtable.h" + +class nsIControllerCommand; + +class nsControllerCommandTable final + : public nsIControllerCommandTable + , public nsSupportsWeakReference +{ +public: + nsControllerCommandTable(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICONTROLLERCOMMANDTABLE + +protected: + virtual ~nsControllerCommandTable(); + + // Hash table of nsIControllerCommands, keyed by command name. + nsInterfaceHashtable<nsCStringHashKey, nsIControllerCommand> mCommandsTable; + + // Are we mutable? + bool mMutable; +}; + +#endif // nsControllerCommandTable_h_ diff --git a/embedding/components/commandhandler/nsICommandManager.idl b/embedding/components/commandhandler/nsICommandManager.idl new file mode 100644 index 000000000..958108740 --- /dev/null +++ b/embedding/components/commandhandler/nsICommandManager.idl @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIObserver.idl" +#include "nsICommandParams.idl" + +interface mozIDOMWindowProxy; + +/* + * nsICommandManager is an interface used to executing user-level commands, + * and getting the state of available commands. + * + * Commands are identified by strings, which are documented elsewhere. + * In addition, the list of required and optional parameters for + * each command, that are passed in via the nsICommandParams, are + * also documented elsewhere. (Where? Need a good location for this). + */ + + +[scriptable, uuid(bb5a1730-d83b-4fa2-831b-35b9d5842e84)] +interface nsICommandManager : nsISupports +{ + /* + * Register an observer on the specified command. The observer's Observe + * method will get called when the state (enabled/disbaled, or toggled etc) + * of the command changes. + * + * You can register the same observer on multiple commmands by calling this + * multiple times. + */ + void addCommandObserver(in nsIObserver aCommandObserver, + in string aCommandToObserve); + + /* + * Stop an observer from observering the specified command. If the observer + * was also registered on ther commands, they will continue to be observed. + * + * Passing an empty string in 'aCommandObserved' will remove the observer + * from all commands. + */ + void removeCommandObserver(in nsIObserver aCommandObserver, + in string aCommandObserved); + + /* + * Ask the command manager if the specified command is supported. + * If aTargetWindow is null, the focused window is used. + * + */ + boolean isCommandSupported(in string aCommandName, + in mozIDOMWindowProxy aTargetWindow); + + /* + * Ask the command manager if the specified command is currently. + * enabled. + * If aTargetWindow is null, the focused window is used. + */ + boolean isCommandEnabled(in string aCommandName, + in mozIDOMWindowProxy aTargetWindow); + + /* + * Get the state of the specified commands. + * + * On input: aCommandParams filled in with values that the caller cares + * about, most of which are command-specific (see the command documentation + * for details). One boolean value, "enabled", applies to all commands, + * and, in return will be set to indicate whether the command is enabled + * (equivalent to calling isCommandEnabled). + * + * aCommandName is the name of the command that needs the state + * aTargetWindow is the source of command controller + * (null means use focus controller) + * On output: aCommandParams: values set by the caller filled in with + * state from the command. + */ + void getCommandState(in string aCommandName, + in mozIDOMWindowProxy aTargetWindow, + /* inout */ in nsICommandParams aCommandParams); + + /* + * Execute the specified command. + * The command will be executed in aTargetWindow if it is specified. + * If aTargetWindow is null, it will go to the focused window. + * + * param: aCommandParams, a list of name-value pairs of command parameters, + * may be null for parameter-less commands. + * + */ + void doCommand(in string aCommandName, + in nsICommandParams aCommandParams, + in mozIDOMWindowProxy aTargetWindow); + +}; + + +/* + +Arguments to observers "Observe" method are as follows: + + void Observe( in nsISupports aSubject, // The nsICommandManager calling this Observer + in string aTopic, // Name of the command + in wstring aDummy ); // unused + +*/ + +// {64edb481-0c04-11d5-a73c-e964b968b0bc} +%{C++ +#define NS_COMMAND_MANAGER_CID \ +{ 0x64edb481, 0x0c04, 0x11d5, { 0xa7, 0x3c, 0xe9, 0x64, 0xb9, 0x68, 0xb0, 0xbc } } + +#define NS_COMMAND_MANAGER_CONTRACTID \ + "@mozilla.org/embedcomp/command-manager;1" +%} + + + diff --git a/embedding/components/commandhandler/nsICommandParams.idl b/embedding/components/commandhandler/nsICommandParams.idl new file mode 100644 index 000000000..fcd3ff973 --- /dev/null +++ b/embedding/components/commandhandler/nsICommandParams.idl @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/* + * nsICommandParams is used to pass parameters to commands executed + * via nsICommandManager, and to get command state. + * + */ + +[scriptable, uuid(b1fdf3c4-74e3-4f7d-a14d-2b76bcf53482)] +interface nsICommandParams : nsISupports +{ + /* + * List of primitive types for parameter values. + */ + const short eNoType = 0; /* Only used for sanity checking */ + const short eBooleanType = 1; + const short eLongType = 2; + const short eDoubleType = 3; + const short eWStringType = 4; + const short eISupportsType = 5; + const short eStringType = 6; + + /* + * getValueType + * + * Get the type of a specified parameter + */ + short getValueType(in string name); + + /* + * get_Value + * + * Get the value of a specified parameter. Will return + * an error if the parameter does not exist, or if the value + * is of the wrong type (no coercion is performed for you). + * + * nsISupports values can contain any XPCOM interface, + * as documented for the command. It is permissible + * for it to contain nsICommandParams, but not *this* + * one (i.e. self-containing is not allowed). + */ + boolean getBooleanValue(in string name); + long getLongValue(in string name); + double getDoubleValue(in string name); + AString getStringValue(in string name); + string getCStringValue(in string name); + nsISupports getISupportsValue(in string name); + + /* + * set_Value + * + * Set the value of a specified parameter (thus creating + * an entry for it). + * + * nsISupports values can contain any XPCOM interface, + * as documented for the command. It is permissible + * for it to contain nsICommandParams, but not *this* + * one (i.e. self-containing is not allowed). + */ + void setBooleanValue(in string name, in boolean value); + void setLongValue(in string name, in long value); + void setDoubleValue(in string name, in double value); + void setStringValue(in string name, in AString value); + void setCStringValue(in string name, in string value); + void setISupportsValue(in string name, in nsISupports value); + + /* + * removeValue + * + * Remove the specified parameter from the list. + */ + void removeValue(in string name); +}; + +// {f7fa4581-238e-11d5-a73c-ab64fb68f2bc} +%{C++ +#define NS_COMMAND_PARAMS_CID { 0xf7fa4581, 0x238e, 0x11d5, { 0xa7, 0x3c, 0xab, 0x64, 0xfb, 0x68, 0xf2, 0xbc } } +#define NS_COMMAND_PARAMS_CONTRACTID "@mozilla.org/embedcomp/command-params;1" +%} + diff --git a/embedding/components/commandhandler/nsIControllerCommand.idl b/embedding/components/commandhandler/nsIControllerCommand.idl new file mode 100644 index 000000000..a1ea583a4 --- /dev/null +++ b/embedding/components/commandhandler/nsIControllerCommand.idl @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsICommandParams.idl" + +/** + * nsIControllerCommand + * + * A generic command interface. You can register an nsIControllerCommand + * with the nsIControllerCommandTable. + */ + +[scriptable, uuid(0eae9a46-1dd2-11b2-aca0-9176f05fe9db)] +interface nsIControllerCommand : nsISupports +{ + + /** + * Returns true if the command is currently enabled. An nsIControllerCommand + * can implement more than one commands; say, a group of related commands + * (e.g. delete left/delete right). Because of this, the command name is + * passed to each method. + * + * @param aCommandName the name of the command for which we want the enabled + * state. + * @param aCommandContext a cookie held by the nsIControllerCommandTable, + * allowing the command to get some context information. + * The contents of this cookie are implementation-defined. + */ + boolean isCommandEnabled(in string aCommandName, in nsISupports aCommandContext); + + void getCommandStateParams(in string aCommandName, in nsICommandParams aParams, in nsISupports aCommandContext); + + /** + * Execute the name command. + * + * @param aCommandName the name of the command to execute. + * + * @param aCommandContext a cookie held by the nsIControllerCommandTable, + * allowing the command to get some context information. + * The contents of this cookie are implementation-defined. + */ + void doCommand(in string aCommandName, in nsISupports aCommandContext); + + void doCommandParams(in string aCommandName, in nsICommandParams aParams, in nsISupports aCommandContext); + +}; + diff --git a/embedding/components/commandhandler/nsIControllerCommandTable.idl b/embedding/components/commandhandler/nsIControllerCommandTable.idl new file mode 100644 index 000000000..fb2c86fae --- /dev/null +++ b/embedding/components/commandhandler/nsIControllerCommandTable.idl @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIControllerCommand.idl" +#include "nsICommandParams.idl" + +/** + * nsIControllerCommandTable + * + * An interface via which a controller can maintain a series of commands, + * and efficiently dispatch commands to their respective handlers. + * + * Controllers that use an nsIControllerCommandTable should support + * nsIInterfaceRequestor, and be able to return an interface to their + * controller command table via getInterface(). + * + */ + +[scriptable, uuid(c847f90e-b8f3-49db-a4df-8867831f2800)] +interface nsIControllerCommandTable : nsISupports +{ + /** + * Make this command table immutable, so that commands cannot + * be registered or unregistered. Some command tables are made + * mutable after command registration so that they can be + * used as singletons. + */ + void makeImmutable(); + + /** + * Register and unregister commands with the command table. + * + * @param aCommandName the name of the command under which to register or + * unregister the given command handler. + * + * @param aCommand the handler for this command. + */ + void registerCommand(in string aCommandName, in nsIControllerCommand aCommand); + void unregisterCommand(in string aCommandName, in nsIControllerCommand aCommand); + + /** + * Find the command handler which has been registered to handle the named command. + * + * @param aCommandName the name of the command to find the handler for. + */ + nsIControllerCommand findCommandHandler(in string aCommandName); + + /** + * Get whether the named command is enabled. + * + * @param aCommandName the name of the command to test + * @param aCommandRefCon the command context data + */ + boolean isCommandEnabled(in string aCommandName, in nsISupports aCommandRefCon); + + /** + * Tell the command to update its state (if it is a state updating command) + * + * @param aCommandName the name of the command to update + * @param aCommandRefCon the command context data + */ + void updateCommandState(in string aCommandName, in nsISupports aCommandRefCon); + + /** + * Get whether the named command is supported. + * + * @param aCommandName the name of the command to test + * @param aCommandRefCon the command context data + */ + boolean supportsCommand(in string aCommandName, in nsISupports aCommandRefCon); + + /** + * Execute the named command. + * + * @param aCommandName the name of the command to execute + * @param aCommandRefCon the command context data + */ + void doCommand(in string aCommandName, in nsISupports aCommandRefCon); + + void doCommandParams(in string aCommandName, in nsICommandParams aParam, in nsISupports aCommandRefCon); + + void getCommandState(in string aCommandName, in nsICommandParams aParam, in nsISupports aCommandRefCon); + + void getSupportedCommands(out unsigned long count, + [array, size_is(count), retval] out string commands); +}; + + + +%{C++ +// {670ee5da-6ad5-11d7-9950-000393636592} +#define NS_CONTROLLERCOMMANDTABLE_CID \ + {0x670ee5da, 0x6ad5, 0x11d7, \ + { 0x99, 0x50, 0x00, 0x03, 0x93, 0x63, 0x65, 0x92 }} + +#define NS_CONTROLLERCOMMANDTABLE_CONTRACTID \ + "@mozilla.org/embedcomp/controller-command-table;1" +%} diff --git a/embedding/components/commandhandler/nsIControllerContext.idl b/embedding/components/commandhandler/nsIControllerContext.idl new file mode 100644 index 000000000..7c7ed67fd --- /dev/null +++ b/embedding/components/commandhandler/nsIControllerContext.idl @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIControllerCommandTable.idl" + +[scriptable, uuid(47B82B60-A36F-4167-8072-6F421151ED50)] +interface nsIControllerContext : nsISupports +{ + + /** + * Init the controller, optionally passing a controller + * command table. + * + * @param aCommandTable a command table, used internally + * by this controller. May be null, in + * which case the controller will create + * a new, empty table. + */ + void init(in nsIControllerCommandTable aCommandTable); + + /** + * Set a context on this controller, which is passed + * to commands to give them some context when they execute. + * + * @param aCommandContext the context passed to commands. + * Note that this is *not* addreffed by the + * controller, and so needs to outlive it, + * or be nulled out. + */ + void setCommandContext(in nsISupports aCommandContext); + +}; diff --git a/embedding/components/commandhandler/nsPICommandUpdater.idl b/embedding/components/commandhandler/nsPICommandUpdater.idl new file mode 100644 index 000000000..3712dbdff --- /dev/null +++ b/embedding/components/commandhandler/nsPICommandUpdater.idl @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface mozIDOMWindowProxy; + +/* + The nsPICommandUpdater interface is used by modules that implement + commands, to tell the command manager that commands need updating. + This is a private interface; embedders should not use it. + + Command-implementing modules should get one of these by a QI + from an nsICommandManager. +*/ + +[scriptable, uuid(35e474ae-8016-4c34-9644-edc11f8b0ce1)] +interface nsPICommandUpdater : nsISupports +{ + + /* + * Init the command updater, passing an nsIDOMWindow which + * is the window that the command updater lives on. + * + */ + void init(in mozIDOMWindowProxy aWindow); + + /* + * Notify the command manager that the status of a command + * changed. It may have changed from enabled to disabled, + * or vice versa, or become toggled etc. + */ + void commandStatusChanged(in string aCommandName); + +}; + + diff --git a/embedding/components/find/moz.build b/embedding/components/find/moz.build new file mode 100644 index 000000000..6586914da --- /dev/null +++ b/embedding/components/find/moz.build @@ -0,0 +1,19 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + 'nsIFind.idl', + 'nsIWebBrowserFind.idl', +] + +XPIDL_MODULE = 'find' + +UNIFIED_SOURCES += [ + 'nsFind.cpp', + 'nsWebBrowserFind.cpp', +] + +FINAL_LIBRARY = 'xul' diff --git a/embedding/components/find/nsFind.cpp b/embedding/components/find/nsFind.cpp new file mode 100644 index 000000000..cbc42298b --- /dev/null +++ b/embedding/components/find/nsFind.cpp @@ -0,0 +1,1383 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//#define DEBUG_FIND 1 + +#include "nsFind.h" +#include "nsContentCID.h" +#include "nsIContent.h" +#include "nsIDOMNode.h" +#include "nsIDOMNodeList.h" +#include "nsISelection.h" +#include "nsISelectionController.h" +#include "nsIFrame.h" +#include "nsITextControlFrame.h" +#include "nsIFormControl.h" +#include "nsIEditor.h" +#include "nsIPlaintextEditor.h" +#include "nsTextFragment.h" +#include "nsString.h" +#include "nsIAtom.h" +#include "nsServiceManagerUtils.h" +#include "nsUnicharUtils.h" +#include "nsIDOMElement.h" +#include "nsIWordBreaker.h" +#include "nsCRT.h" +#include "nsRange.h" +#include "nsContentUtils.h" +#include "mozilla/DebugOnly.h" + +using namespace mozilla; + +// Yikes! Casting a char to unichar can fill with ones! +#define CHAR_TO_UNICHAR(c) ((char16_t)(const unsigned char)c) + +static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID); +static NS_DEFINE_CID(kCPreContentIteratorCID, NS_PRECONTENTITERATOR_CID); + +#define CH_QUOTE ((char16_t)0x22) +#define CH_APOSTROPHE ((char16_t)0x27) +#define CH_LEFT_SINGLE_QUOTE ((char16_t)0x2018) +#define CH_RIGHT_SINGLE_QUOTE ((char16_t)0x2019) +#define CH_LEFT_DOUBLE_QUOTE ((char16_t)0x201C) +#define CH_RIGHT_DOUBLE_QUOTE ((char16_t)0x201D) + +#define CH_SHY ((char16_t)0xAD) + +// nsFind::Find casts CH_SHY to char before calling StripChars +// This works correctly if and only if CH_SHY <= 255 +static_assert(CH_SHY <= 255, "CH_SHY is not an ascii character"); + +// nsFindContentIterator is a special iterator that also goes through any +// existing <textarea>'s or text <input>'s editor to lookup the anonymous DOM +// content there. +// +// Details: +// 1) We use two iterators: The "outer-iterator" goes through the normal DOM. +// The "inner-iterator" goes through the anonymous DOM inside the editor. +// +// 2) [MaybeSetupInnerIterator] As soon as the outer-iterator's current node is +// changed, a check is made to see if the node is a <textarea> or a text <input> +// node. If so, an inner-iterator is created to lookup the anynomous contents of +// the editor underneath the text control. +// +// 3) When the inner-iterator is created, we position the outer-iterator 'after' +// (or 'before' in backward search) the text control to avoid revisiting that +// control. +// +// 4) As a consequence of searching through text controls, we can be called via +// FindNext with the current selection inside a <textarea> or a text <input>. +// This means that we can be given an initial search range that stretches across +// the anonymous DOM and the normal DOM. To cater for this situation, we split +// the anonymous part into the inner-iterator and then reposition the outer- +// iterator outside. +// +// 5) The implementation assumes that First() and Next() are only called in +// find-forward mode, while Last() and Prev() are used in find-backward. + +class nsFindContentIterator final : public nsIContentIterator +{ +public: + explicit nsFindContentIterator(bool aFindBackward) + : mStartOffset(0) + , mEndOffset(0) + , mFindBackward(aFindBackward) + { + } + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(nsFindContentIterator) + + // nsIContentIterator + virtual nsresult Init(nsINode* aRoot) override + { + NS_NOTREACHED("internal error"); + return NS_ERROR_NOT_IMPLEMENTED; + } + virtual nsresult Init(nsIDOMRange* aRange) override + { + NS_NOTREACHED("internal error"); + return NS_ERROR_NOT_IMPLEMENTED; + } + // Not a range because one of the endpoints may be anonymous. + nsresult Init(nsIDOMNode* aStartNode, int32_t aStartOffset, + nsIDOMNode* aEndNode, int32_t aEndOffset); + virtual void First() override; + virtual void Last() override; + virtual void Next() override; + virtual void Prev() override; + virtual nsINode* GetCurrentNode() override; + virtual bool IsDone() override; + virtual nsresult PositionAt(nsINode* aCurNode) override; + +protected: + virtual ~nsFindContentIterator() {} + +private: + static already_AddRefed<nsIDOMRange> CreateRange(nsINode* aNode) + { + RefPtr<nsRange> range = new nsRange(aNode); + range->SetMaySpanAnonymousSubtrees(true); + return range.forget(); + } + + nsCOMPtr<nsIContentIterator> mOuterIterator; + nsCOMPtr<nsIContentIterator> mInnerIterator; + // Can't use a range here, since we want to represent part of the flattened + // tree, including native anonymous content. + nsCOMPtr<nsIDOMNode> mStartNode; + int32_t mStartOffset; + nsCOMPtr<nsIDOMNode> mEndNode; + int32_t mEndOffset; + + nsCOMPtr<nsIContent> mStartOuterContent; + nsCOMPtr<nsIContent> mEndOuterContent; + bool mFindBackward; + + void Reset(); + void MaybeSetupInnerIterator(); + void SetupInnerIterator(nsIContent* aContent); +}; + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFindContentIterator) + NS_INTERFACE_MAP_ENTRY(nsIContentIterator) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFindContentIterator) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFindContentIterator) + +NS_IMPL_CYCLE_COLLECTION(nsFindContentIterator, mOuterIterator, mInnerIterator, + mStartOuterContent, mEndOuterContent, mEndNode, + mStartNode) + +nsresult +nsFindContentIterator::Init(nsIDOMNode* aStartNode, int32_t aStartOffset, + nsIDOMNode* aEndNode, int32_t aEndOffset) +{ + NS_ENSURE_ARG_POINTER(aStartNode); + NS_ENSURE_ARG_POINTER(aEndNode); + if (!mOuterIterator) { + if (mFindBackward) { + // Use post-order in the reverse case, so we get parents before children + // in case we want to prevent descending into a node. + mOuterIterator = do_CreateInstance(kCContentIteratorCID); + } else { + // Use pre-order in the forward case, so we get parents before children in + // case we want to prevent descending into a node. + mOuterIterator = do_CreateInstance(kCPreContentIteratorCID); + } + NS_ENSURE_ARG_POINTER(mOuterIterator); + } + + // Set up the search "range" that we will examine + mStartNode = aStartNode; + mStartOffset = aStartOffset; + mEndNode = aEndNode; + mEndOffset = aEndOffset; + + return NS_OK; +} + +void +nsFindContentIterator::First() +{ + Reset(); +} + +void +nsFindContentIterator::Last() +{ + Reset(); +} + +void +nsFindContentIterator::Next() +{ + if (mInnerIterator) { + mInnerIterator->Next(); + if (!mInnerIterator->IsDone()) { + return; + } + + // by construction, mOuterIterator is already on the next node + } else { + mOuterIterator->Next(); + } + MaybeSetupInnerIterator(); +} + +void +nsFindContentIterator::Prev() +{ + if (mInnerIterator) { + mInnerIterator->Prev(); + if (!mInnerIterator->IsDone()) { + return; + } + + // by construction, mOuterIterator is already on the previous node + } else { + mOuterIterator->Prev(); + } + MaybeSetupInnerIterator(); +} + +nsINode* +nsFindContentIterator::GetCurrentNode() +{ + if (mInnerIterator && !mInnerIterator->IsDone()) { + return mInnerIterator->GetCurrentNode(); + } + return mOuterIterator->GetCurrentNode(); +} + +bool +nsFindContentIterator::IsDone() +{ + if (mInnerIterator && !mInnerIterator->IsDone()) { + return false; + } + return mOuterIterator->IsDone(); +} + +nsresult +nsFindContentIterator::PositionAt(nsINode* aCurNode) +{ + nsINode* oldNode = mOuterIterator->GetCurrentNode(); + nsresult rv = mOuterIterator->PositionAt(aCurNode); + if (NS_SUCCEEDED(rv)) { + MaybeSetupInnerIterator(); + } else { + mOuterIterator->PositionAt(oldNode); + if (mInnerIterator) { + rv = mInnerIterator->PositionAt(aCurNode); + } + } + return rv; +} + +void +nsFindContentIterator::Reset() +{ + mInnerIterator = nullptr; + mStartOuterContent = nullptr; + mEndOuterContent = nullptr; + + // As a consequence of searching through text controls, we may have been + // initialized with a selection inside a <textarea> or a text <input>. + + // see if the start node is an anonymous text node inside a text control + nsCOMPtr<nsIContent> startContent(do_QueryInterface(mStartNode)); + if (startContent) { + mStartOuterContent = startContent->FindFirstNonChromeOnlyAccessContent(); + } + + // see if the end node is an anonymous text node inside a text control + nsCOMPtr<nsIContent> endContent(do_QueryInterface(mEndNode)); + if (endContent) { + mEndOuterContent = endContent->FindFirstNonChromeOnlyAccessContent(); + } + + // Note: OK to just set up the outer iterator here; if our range has a native + // anonymous endpoint we'll end up setting up an inner iterator, and reset the + // outer one in the process. + nsCOMPtr<nsINode> node = do_QueryInterface(mStartNode); + NS_ENSURE_TRUE_VOID(node); + + nsCOMPtr<nsIDOMRange> range = CreateRange(node); + range->SetStart(mStartNode, mStartOffset); + range->SetEnd(mEndNode, mEndOffset); + mOuterIterator->Init(range); + + if (!mFindBackward) { + if (mStartOuterContent != startContent) { + // the start node was an anonymous text node + SetupInnerIterator(mStartOuterContent); + if (mInnerIterator) { + mInnerIterator->First(); + } + } + if (!mOuterIterator->IsDone()) { + mOuterIterator->First(); + } + } else { + if (mEndOuterContent != endContent) { + // the end node was an anonymous text node + SetupInnerIterator(mEndOuterContent); + if (mInnerIterator) { + mInnerIterator->Last(); + } + } + if (!mOuterIterator->IsDone()) { + mOuterIterator->Last(); + } + } + + // if we didn't create an inner-iterator, the boundary node could still be + // a text control, in which case we also need an inner-iterator straightaway + if (!mInnerIterator) { + MaybeSetupInnerIterator(); + } +} + +void +nsFindContentIterator::MaybeSetupInnerIterator() +{ + mInnerIterator = nullptr; + + nsCOMPtr<nsIContent> content = + do_QueryInterface(mOuterIterator->GetCurrentNode()); + if (!content || !content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL)) { + return; + } + + nsCOMPtr<nsIFormControl> formControl(do_QueryInterface(content)); + if (!formControl->IsTextControl(true)) { + return; + } + + SetupInnerIterator(content); + if (mInnerIterator) { + if (!mFindBackward) { + mInnerIterator->First(); + // finish setup: position mOuterIterator on the actual "next" node (this + // completes its re-init, @see SetupInnerIterator) + if (!mOuterIterator->IsDone()) { + mOuterIterator->First(); + } + } else { + mInnerIterator->Last(); + // finish setup: position mOuterIterator on the actual "previous" node + // (this completes its re-init, @see SetupInnerIterator) + if (!mOuterIterator->IsDone()) { + mOuterIterator->Last(); + } + } + } +} + +void +nsFindContentIterator::SetupInnerIterator(nsIContent* aContent) +{ + if (!aContent) { + return; + } + NS_ASSERTION(!aContent->IsRootOfNativeAnonymousSubtree(), "invalid call"); + + nsITextControlFrame* tcFrame = do_QueryFrame(aContent->GetPrimaryFrame()); + if (!tcFrame) { + return; + } + + nsCOMPtr<nsIEditor> editor; + tcFrame->GetEditor(getter_AddRefs(editor)); + if (!editor) { + return; + } + + // don't mess with disabled input fields + uint32_t editorFlags = 0; + editor->GetFlags(&editorFlags); + if (editorFlags & nsIPlaintextEditor::eEditorDisabledMask) { + return; + } + + nsCOMPtr<nsIDOMElement> rootElement; + editor->GetRootElement(getter_AddRefs(rootElement)); + + nsCOMPtr<nsIDOMRange> innerRange = CreateRange(aContent); + nsCOMPtr<nsIDOMRange> outerRange = CreateRange(aContent); + if (!innerRange || !outerRange) { + return; + } + + // now create the inner-iterator + mInnerIterator = do_CreateInstance(kCPreContentIteratorCID); + + if (mInnerIterator) { + innerRange->SelectNodeContents(rootElement); + + // fix up the inner bounds, we may have to only lookup a portion + // of the text control if the current node is a boundary point + if (aContent == mStartOuterContent) { + innerRange->SetStart(mStartNode, mStartOffset); + } + if (aContent == mEndOuterContent) { + innerRange->SetEnd(mEndNode, mEndOffset); + } + // Note: we just init here. We do First() or Last() later. + mInnerIterator->Init(innerRange); + + // make sure to place the outer-iterator outside the text control so that we + // don't go there again. + nsresult res1, res2; + nsCOMPtr<nsIDOMNode> outerNode(do_QueryInterface(aContent)); + if (!mFindBackward) { // find forward + // cut the outer-iterator after the current node + res1 = outerRange->SetEnd(mEndNode, mEndOffset); + res2 = outerRange->SetStartAfter(outerNode); + } else { // find backward + // cut the outer-iterator before the current node + res1 = outerRange->SetStart(mStartNode, mStartOffset); + res2 = outerRange->SetEndBefore(outerNode); + } + if (NS_FAILED(res1) || NS_FAILED(res2)) { + // we are done with the outer-iterator, the inner-iterator will traverse + // what we want + outerRange->Collapse(true); + } + + // Note: we just re-init here, using the segment of our search range that + // is yet to be visited. Thus when we later do mOuterIterator->First() [or + // mOuterIterator->Last()], we will effectively be on the next node [or + // the previous node] _with respect to_ the search range. + mOuterIterator->Init(outerRange); + } +} + +nsresult +NS_NewFindContentIterator(bool aFindBackward, nsIContentIterator** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + nsFindContentIterator* it = new nsFindContentIterator(aFindBackward); + if (!it) { + return NS_ERROR_OUT_OF_MEMORY; + } + return it->QueryInterface(NS_GET_IID(nsIContentIterator), (void**)aResult); +} + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFind) + NS_INTERFACE_MAP_ENTRY(nsIFind) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFind) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFind) + +NS_IMPL_CYCLE_COLLECTION(nsFind, mLastBlockParent, mIterNode, mIterator) + +nsFind::nsFind() + : mFindBackward(false) + , mCaseSensitive(false) + , mIterOffset(0) +{ +} + +nsFind::~nsFind() +{ +} + +#ifdef DEBUG_FIND +static void +DumpNode(nsIDOMNode* aNode) +{ + if (!aNode) { + printf(">>>> Node: NULL\n"); + return; + } + nsAutoString nodeName; + aNode->GetNodeName(nodeName); + nsCOMPtr<nsIContent> textContent(do_QueryInterface(aNode)); + if (textContent && textContent->IsNodeOfType(nsINode::eTEXT)) { + nsAutoString newText; + textContent->AppendTextTo(newText); + printf(">>>> Text node (node name %s): '%s'\n", + NS_LossyConvertUTF16toASCII(nodeName).get(), + NS_LossyConvertUTF16toASCII(newText).get()); + } else { + printf(">>>> Node: %s\n", NS_LossyConvertUTF16toASCII(nodeName).get()); + } +} +#endif + +nsresult +nsFind::InitIterator(nsIDOMNode* aStartNode, int32_t aStartOffset, + nsIDOMNode* aEndNode, int32_t aEndOffset) +{ + if (!mIterator) { + mIterator = new nsFindContentIterator(mFindBackward); + NS_ENSURE_TRUE(mIterator, NS_ERROR_OUT_OF_MEMORY); + } + + NS_ENSURE_ARG_POINTER(aStartNode); + NS_ENSURE_ARG_POINTER(aEndNode); + +#ifdef DEBUG_FIND + printf("InitIterator search range:\n"); + printf(" -- start %d, ", aStartOffset); + DumpNode(aStartNode); + printf(" -- end %d, ", aEndOffset); + DumpNode(aEndNode); +#endif + + nsresult rv = mIterator->Init(aStartNode, aStartOffset, aEndNode, aEndOffset); + NS_ENSURE_SUCCESS(rv, rv); + if (mFindBackward) { + mIterator->Last(); + } else { + mIterator->First(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsFind::GetFindBackwards(bool* aFindBackward) +{ + if (!aFindBackward) { + return NS_ERROR_NULL_POINTER; + } + + *aFindBackward = mFindBackward; + return NS_OK; +} + +NS_IMETHODIMP +nsFind::SetFindBackwards(bool aFindBackward) +{ + mFindBackward = aFindBackward; + return NS_OK; +} + +NS_IMETHODIMP +nsFind::GetCaseSensitive(bool* aCaseSensitive) +{ + if (!aCaseSensitive) { + return NS_ERROR_NULL_POINTER; + } + + *aCaseSensitive = mCaseSensitive; + return NS_OK; +} + +NS_IMETHODIMP +nsFind::SetCaseSensitive(bool aCaseSensitive) +{ + mCaseSensitive = aCaseSensitive; + return NS_OK; +} + +/* attribute boolean entireWord; */ +NS_IMETHODIMP +nsFind::GetEntireWord(bool *aEntireWord) +{ + if (!aEntireWord) + return NS_ERROR_NULL_POINTER; + + *aEntireWord = !!mWordBreaker; + return NS_OK; +} + +NS_IMETHODIMP +nsFind::SetEntireWord(bool aEntireWord) +{ + mWordBreaker = aEntireWord ? nsContentUtils::WordBreaker() : nullptr; + return NS_OK; +} + +// Here begins the find code. A ten-thousand-foot view of how it works: Find +// needs to be able to compare across inline (but not block) nodes, e.g. find +// for "abc" should match a<b>b</b>c. So after we've searched a node, we're not +// done with it; in the case of a partial match we may need to reset the +// iterator to go back to a previously visited node, so we always save the +// "match anchor" node and offset. +// +// Text nodes store their text in an nsTextFragment, which is effectively a +// union of a one-byte string or a two-byte string. Single and double strings +// are intermixed in the dom. We don't have string classes which can deal with +// intermixed strings, so all the handling is done explicitly here. + +nsresult +nsFind::NextNode(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, nsIDOMRange* aEndPoint, + bool aContinueOk) +{ + nsresult rv; + + nsCOMPtr<nsIContent> content; + + if (!mIterator || aContinueOk) { + // If we are continuing, that means we have a match in progress. In that + // case, we want to continue from the end point (where we are now) to the + // beginning/end of the search range. + nsCOMPtr<nsIDOMNode> startNode; + nsCOMPtr<nsIDOMNode> endNode; + int32_t startOffset, endOffset; + if (aContinueOk) { +#ifdef DEBUG_FIND + printf("Match in progress: continuing past endpoint\n"); +#endif + if (mFindBackward) { + aSearchRange->GetStartContainer(getter_AddRefs(startNode)); + aSearchRange->GetStartOffset(&startOffset); + aEndPoint->GetStartContainer(getter_AddRefs(endNode)); + aEndPoint->GetStartOffset(&endOffset); + } else { // forward + aEndPoint->GetEndContainer(getter_AddRefs(startNode)); + aEndPoint->GetEndOffset(&startOffset); + aSearchRange->GetEndContainer(getter_AddRefs(endNode)); + aSearchRange->GetEndOffset(&endOffset); + } + } else { // Normal, not continuing + if (mFindBackward) { + aSearchRange->GetStartContainer(getter_AddRefs(startNode)); + aSearchRange->GetStartOffset(&startOffset); + aStartPoint->GetEndContainer(getter_AddRefs(endNode)); + aStartPoint->GetEndOffset(&endOffset); + // XXX Needs work: Problem with this approach: if there is a match which + // starts just before the current selection and continues into the + // selection, we will miss it, because our search algorithm only starts + // searching from the end of the word, so we would have to search the + // current selection but discount any matches that fall entirely inside + // it. + } else { // forward + aStartPoint->GetStartContainer(getter_AddRefs(startNode)); + aStartPoint->GetStartOffset(&startOffset); + aEndPoint->GetEndContainer(getter_AddRefs(endNode)); + aEndPoint->GetEndOffset(&endOffset); + } + } + + rv = InitIterator(startNode, startOffset, endNode, endOffset); + NS_ENSURE_SUCCESS(rv, rv); + if (!aStartPoint) { + aStartPoint = aSearchRange; + } + + content = do_QueryInterface(mIterator->GetCurrentNode()); +#ifdef DEBUG_FIND + nsCOMPtr<nsIDOMNode> dnode(do_QueryInterface(content)); + printf(":::::: Got the first node "); + DumpNode(dnode); +#endif + if (content && content->IsNodeOfType(nsINode::eTEXT) && + !SkipNode(content)) { + mIterNode = do_QueryInterface(content); + // Also set mIterOffset if appropriate: + nsCOMPtr<nsIDOMNode> node; + if (mFindBackward) { + aStartPoint->GetEndContainer(getter_AddRefs(node)); + if (mIterNode.get() == node.get()) { + aStartPoint->GetEndOffset(&mIterOffset); + } else { + mIterOffset = -1; // sign to start from end + } + } else { + aStartPoint->GetStartContainer(getter_AddRefs(node)); + if (mIterNode.get() == node.get()) { + aStartPoint->GetStartOffset(&mIterOffset); + } else { + mIterOffset = 0; + } + } +#ifdef DEBUG_FIND + printf("Setting initial offset to %d\n", mIterOffset); +#endif + return NS_OK; + } + } + + while (true) { + if (mFindBackward) { + mIterator->Prev(); + } else { + mIterator->Next(); + } + + content = do_QueryInterface(mIterator->GetCurrentNode()); + if (!content) { + break; + } + +#ifdef DEBUG_FIND + nsCOMPtr<nsIDOMNode> dnode(do_QueryInterface(content)); + printf(":::::: Got another node "); + DumpNode(dnode); +#endif + + // If we ever cross a block node, we might want to reset the match anchor: + // we don't match patterns extending across block boundaries. But we can't + // depend on this test here now, because the iterator doesn't give us the + // parent going in and going out, and we need it both times to depend on + // this. + //if (IsBlockNode(content)) + + // Now see if we need to skip this node -- e.g. is it part of a script or + // other invisible node? Note that we don't ask for CSS information; a node + // can be invisible due to CSS, and we'd still find it. + if (SkipNode(content)) { + continue; + } + + if (content->IsNodeOfType(nsINode::eTEXT)) { + break; + } +#ifdef DEBUG_FIND + dnode = do_QueryInterface(content); + printf("Not a text node: "); + DumpNode(dnode); +#endif + } + + if (content) { + mIterNode = do_QueryInterface(content); + } else { + mIterNode = nullptr; + } + mIterOffset = -1; + +#ifdef DEBUG_FIND + printf("Iterator gave: "); + DumpNode(mIterNode); +#endif + return NS_OK; +} + +class MOZ_STACK_CLASS PeekNextCharRestoreState final +{ +public: + explicit PeekNextCharRestoreState(nsFind* aFind) + : mIterOffset(aFind->mIterOffset), + mIterNode(aFind->mIterNode), + mCurrNode(aFind->mIterator->GetCurrentNode()), + mFind(aFind) + { + } + + ~PeekNextCharRestoreState() + { + mFind->mIterOffset = mIterOffset; + mFind->mIterNode = mIterNode; + mFind->mIterator->PositionAt(mCurrNode); + } + +private: + int32_t mIterOffset; + nsCOMPtr<nsIDOMNode> mIterNode; + nsCOMPtr<nsINode> mCurrNode; + RefPtr<nsFind> mFind; +}; + +char16_t +nsFind::PeekNextChar(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, + nsIDOMRange* aEndPoint) +{ + // We need to restore the necessary member variables before this function + // returns. + PeekNextCharRestoreState restoreState(this); + + nsCOMPtr<nsIContent> tc; + nsresult rv; + const nsTextFragment *frag; + int32_t fragLen; + + // Loop through non-block nodes until we find one that's not empty. + do { + tc = nullptr; + NextNode(aSearchRange, aStartPoint, aEndPoint, false); + + // Get the text content: + tc = do_QueryInterface(mIterNode); + + // Get the block parent. + nsCOMPtr<nsIDOMNode> blockParent; + rv = GetBlockParent(mIterNode, getter_AddRefs(blockParent)); + if (NS_FAILED(rv)) + return L'\0'; + + // If out of nodes or in new parent. + if (!mIterNode || !tc || (blockParent != mLastBlockParent)) + return L'\0'; + + frag = tc->GetText(); + fragLen = frag->GetLength(); + } while (fragLen <= 0); + + const char16_t *t2b = nullptr; + const char *t1b = nullptr; + + if (frag->Is2b()) { + t2b = frag->Get2b(); + } else { + t1b = frag->Get1b(); + } + + // Index of char to return. + int32_t index = mFindBackward ? fragLen - 1 : 0; + + return t1b ? CHAR_TO_UNICHAR(t1b[index]) : t2b[index]; +} + +bool +nsFind::IsBlockNode(nsIContent* aContent) +{ + if (aContent->IsAnyOfHTMLElements(nsGkAtoms::img, + nsGkAtoms::hr, + nsGkAtoms::th, + nsGkAtoms::td)) { + return true; + } + + return nsContentUtils::IsHTMLBlock(aContent); +} + +bool +nsFind::IsTextNode(nsIDOMNode* aNode) +{ + uint16_t nodeType; + aNode->GetNodeType(&nodeType); + + return nodeType == nsIDOMNode::TEXT_NODE || + nodeType == nsIDOMNode::CDATA_SECTION_NODE; +} + +bool +nsFind::IsVisibleNode(nsIDOMNode* aDOMNode) +{ + nsCOMPtr<nsIContent> content(do_QueryInterface(aDOMNode)); + if (!content) { + return false; + } + + nsIFrame* frame = content->GetPrimaryFrame(); + if (!frame) { + // No frame! Not visible then. + return false; + } + + return frame->StyleVisibility()->IsVisible(); +} + +bool +nsFind::SkipNode(nsIContent* aContent) +{ +#ifdef HAVE_BIDI_ITERATOR + // We may not need to skip comment nodes, now that IsTextNode distinguishes + // them from real text nodes. + return aContent->IsNodeOfType(nsINode::eCOMMENT) || + aContent->IsAnyOfHTMLElements(sScriptAtom, sNoframesAtom, sSelectAtom); + +#else /* HAVE_BIDI_ITERATOR */ + // Temporary: eventually we will have an iterator to do this, but for now, we + // have to climb up the tree for each node and see whether any parent is a + // skipped node, and take the performance hit. + + nsIContent* content = aContent; + while (content) { + if (aContent->IsNodeOfType(nsINode::eCOMMENT) || + content->IsAnyOfHTMLElements(nsGkAtoms::script, + nsGkAtoms::noframes, + nsGkAtoms::select)) { +#ifdef DEBUG_FIND + printf("Skipping node: "); + nsCOMPtr<nsIDOMNode> node(do_QueryInterface(content)); + DumpNode(node); +#endif + + return true; + } + + // Only climb to the nearest block node + if (IsBlockNode(content)) { + return false; + } + + content = content->GetParent(); + } + + return false; +#endif /* HAVE_BIDI_ITERATOR */ +} + +nsresult +nsFind::GetBlockParent(nsIDOMNode* aNode, nsIDOMNode** aParent) +{ + while (aNode) { + nsCOMPtr<nsIDOMNode> parent; + nsresult rv = aNode->GetParentNode(getter_AddRefs(parent)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIContent> content(do_QueryInterface(parent)); + if (content && IsBlockNode(content)) { + *aParent = parent; + NS_ADDREF(*aParent); + return NS_OK; + } + aNode = parent; + } + return NS_ERROR_FAILURE; +} + +// Call ResetAll before returning, to remove all references to external objects. +void +nsFind::ResetAll() +{ + mIterator = nullptr; + mLastBlockParent = nullptr; +} + +#define NBSP_CHARCODE (CHAR_TO_UNICHAR(160)) +#define IsSpace(c) (nsCRT::IsAsciiSpace(c) || (c) == NBSP_CHARCODE) +#define OVERFLOW_PINDEX (mFindBackward ? pindex < 0 : pindex > patLen) +#define DONE_WITH_PINDEX (mFindBackward ? pindex <= 0 : pindex >= patLen) +#define ALMOST_DONE_WITH_PINDEX (mFindBackward ? pindex <= 0 : pindex >= patLen - 1) + +// Take nodes out of the tree with NextNode, until null (NextNode will return 0 +// at the end of our range). +NS_IMETHODIMP +nsFind::Find(const char16_t* aPatText, nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, nsIDOMRange* aEndPoint, + nsIDOMRange** aRangeRet) +{ +#ifdef DEBUG_FIND + printf("============== nsFind::Find('%s'%s, %p, %p, %p)\n", + NS_LossyConvertUTF16toASCII(aPatText).get(), + mFindBackward ? " (backward)" : " (forward)", + (void*)aSearchRange, (void*)aStartPoint, (void*)aEndPoint); +#endif + + NS_ENSURE_ARG(aSearchRange); + NS_ENSURE_ARG(aStartPoint); + NS_ENSURE_ARG(aEndPoint); + NS_ENSURE_ARG_POINTER(aRangeRet); + *aRangeRet = 0; + + if (!aPatText) { + return NS_ERROR_NULL_POINTER; + } + + ResetAll(); + + nsAutoString patAutoStr(aPatText); + if (!mCaseSensitive) { + ToLowerCase(patAutoStr); + } + + // Ignore soft hyphens in the pattern + static const char kShy[] = { char(CH_SHY), 0 }; + patAutoStr.StripChars(kShy); + + const char16_t* patStr = patAutoStr.get(); + int32_t patLen = patAutoStr.Length() - 1; + + // current offset into the pattern -- reset to beginning/end: + int32_t pindex = (mFindBackward ? patLen : 0); + + // Current offset into the fragment + int32_t findex = 0; + + // Direction to move pindex and ptr* + int incr = (mFindBackward ? -1 : 1); + + nsCOMPtr<nsIContent> tc; + const nsTextFragment* frag = nullptr; + int32_t fragLen = 0; + + // Pointers into the current fragment: + const char16_t* t2b = nullptr; + const char* t1b = nullptr; + + // Keep track of when we're in whitespace: + // (only matters when we're matching) + bool inWhitespace = false; + // Keep track of whether the previous char was a word-breaking one. + bool wordBreakPrev = false; + + // Place to save the range start point in case we find a match: + nsCOMPtr<nsIDOMNode> matchAnchorNode; + int32_t matchAnchorOffset = 0; + + // Get the end point, so we know when to end searches: + nsCOMPtr<nsIDOMNode> endNode; + int32_t endOffset; + aEndPoint->GetEndContainer(getter_AddRefs(endNode)); + aEndPoint->GetEndOffset(&endOffset); + + char16_t c = 0; + char16_t patc = 0; + char16_t prevChar = 0; + char16_t prevCharInMatch = 0; + while (1) { +#ifdef DEBUG_FIND + printf("Loop ...\n"); +#endif + + // If this is our first time on a new node, reset the pointers: + if (!frag) { + + tc = nullptr; + NextNode(aSearchRange, aStartPoint, aEndPoint, false); + if (!mIterNode) { // Out of nodes + // Are we in the middle of a match? If so, try again with continuation. + if (matchAnchorNode) { + NextNode(aSearchRange, aStartPoint, aEndPoint, true); + } + + // Reset the iterator, so this nsFind will be usable if the user wants + // to search again (from beginning/end). + ResetAll(); + return NS_OK; + } + + // We have a new text content. If its block parent is different from the + // block parent of the last text content, then we need to clear the match + // since we don't want to find across block boundaries. + nsCOMPtr<nsIDOMNode> blockParent; + GetBlockParent(mIterNode, getter_AddRefs(blockParent)); +#ifdef DEBUG_FIND + printf("New node: old blockparent = %p, new = %p\n", + (void*)mLastBlockParent.get(), (void*)blockParent.get()); +#endif + if (blockParent != mLastBlockParent) { +#ifdef DEBUG_FIND + printf("Different block parent!\n"); +#endif + mLastBlockParent = blockParent; + // End any pending match: + matchAnchorNode = nullptr; + matchAnchorOffset = 0; + pindex = (mFindBackward ? patLen : 0); + inWhitespace = false; + } + + // Get the text content: + tc = do_QueryInterface(mIterNode); + if (!tc || !(frag = tc->GetText())) { // Out of nodes + mIterator = nullptr; + mLastBlockParent = nullptr; + ResetAll(); + return NS_OK; + } + + fragLen = frag->GetLength(); + + // Set our starting point in this node. If we're going back to the anchor + // node, which means that we just ended a partial match, use the saved + // offset: + if (mIterNode == matchAnchorNode) { + findex = matchAnchorOffset + (mFindBackward ? 1 : 0); + } + + // mIterOffset, if set, is the range's idea of an offset, and points + // between characters. But when translated to a string index, it points to + // a character. If we're going backward, this is one character too late + // and we'll match part of our previous pattern. + else if (mIterOffset >= 0) { + findex = mIterOffset - (mFindBackward ? 1 : 0); + } + + // Otherwise, just start at the appropriate end of the fragment: + else if (mFindBackward) { + findex = fragLen - 1; + } else { + findex = 0; + } + + // Offset can only apply to the first node: + mIterOffset = -1; + + // If this is outside the bounds of the string, then skip this node: + if (findex < 0 || findex > fragLen - 1) { +#ifdef DEBUG_FIND + printf("At the end of a text node -- skipping to the next\n"); +#endif + frag = 0; + continue; + } + +#ifdef DEBUG_FIND + printf("Starting from offset %d\n", findex); +#endif + if (frag->Is2b()) { + t2b = frag->Get2b(); + t1b = nullptr; +#ifdef DEBUG_FIND + nsAutoString str2(t2b, fragLen); + printf("2 byte, '%s'\n", NS_LossyConvertUTF16toASCII(str2).get()); +#endif + } else { + t1b = frag->Get1b(); + t2b = nullptr; +#ifdef DEBUG_FIND + nsAutoCString str1(t1b, fragLen); + printf("1 byte, '%s'\n", str1.get()); +#endif + } + } else { + // Still on the old node. Advance the pointers, then see if we need to + // pull a new node. + findex += incr; +#ifdef DEBUG_FIND + printf("Same node -- (%d, %d)\n", pindex, findex); +#endif + if (mFindBackward ? (findex < 0) : (findex >= fragLen)) { +#ifdef DEBUG_FIND + printf("Will need to pull a new node: mAO = %d, frag len=%d\n", + matchAnchorOffset, fragLen); +#endif + // Done with this node. Pull a new one. + frag = nullptr; + continue; + } + } + + // Have we gone past the endpoint yet? If we have, and we're not in the + // middle of a match, return. + if (mIterNode == endNode && + ((mFindBackward && findex < endOffset) || + (!mFindBackward && findex > endOffset))) { + ResetAll(); + return NS_OK; + } + + // Save the previous character for word boundary detection + prevChar = c; + // The two characters we'll be comparing: + c = (t2b ? t2b[findex] : CHAR_TO_UNICHAR(t1b[findex])); + patc = patStr[pindex]; + +#ifdef DEBUG_FIND + printf("Comparing '%c'=%x to '%c' (%d of %d), findex=%d%s\n", + (char)c, (int)c, patc, pindex, patLen, findex, + inWhitespace ? " (inWhitespace)" : ""); +#endif + + // Do we need to go back to non-whitespace mode? If inWhitespace, then this + // space in the pat str has already matched at least one space in the + // document. + if (inWhitespace && !IsSpace(c)) { + inWhitespace = false; + pindex += incr; +#ifdef DEBUG + // This shouldn't happen -- if we were still matching, and we were at the + // end of the pat string, then we should have caught it in the last + // iteration and returned success. + if (OVERFLOW_PINDEX) { + NS_ASSERTION(false, "Missed a whitespace match"); + } +#endif + patc = patStr[pindex]; + } + if (!inWhitespace && IsSpace(patc)) { + inWhitespace = true; + } else if (!inWhitespace && !mCaseSensitive && IsUpperCase(c)) { + c = ToLowerCase(c); + } + + if (c == CH_SHY) { + // ignore soft hyphens in the document + continue; + } + + if (!mCaseSensitive) { + switch (c) { + // treat curly and straight quotes as identical + case CH_LEFT_SINGLE_QUOTE: + case CH_RIGHT_SINGLE_QUOTE: + c = CH_APOSTROPHE; + break; + case CH_LEFT_DOUBLE_QUOTE: + case CH_RIGHT_DOUBLE_QUOTE: + c = CH_QUOTE; + break; + } + + switch (patc) { + // treat curly and straight quotes as identical + case CH_LEFT_SINGLE_QUOTE: + case CH_RIGHT_SINGLE_QUOTE: + patc = CH_APOSTROPHE; + break; + case CH_LEFT_DOUBLE_QUOTE: + case CH_RIGHT_DOUBLE_QUOTE: + patc = CH_QUOTE; + break; + } + } + + // a '\n' between CJ characters is ignored + if (pindex != (mFindBackward ? patLen : 0) && c != patc && !inWhitespace) { + if (c == '\n' && t2b && IS_CJ_CHAR(prevCharInMatch)) { + int32_t nindex = findex + incr; + if (mFindBackward ? (nindex >= 0) : (nindex < fragLen)) { + if (IS_CJ_CHAR(t2b[nindex])) { + continue; + } + } + } + } + + wordBreakPrev = false; + if (mWordBreaker) { + if (prevChar == NBSP_CHARCODE) + prevChar = CHAR_TO_UNICHAR(' '); + wordBreakPrev = mWordBreaker->BreakInBetween(&prevChar, 1, &c, 1); + } + + // Compare. Match if we're in whitespace and c is whitespace, or if the + // characters match and at least one of the following is true: + // a) we're not matching the entire word + // b) a match has already been stored + // c) the previous character is a different "class" than the current character. + if ((c == patc && (!mWordBreaker || matchAnchorNode || wordBreakPrev)) || + (inWhitespace && IsSpace(c))) + { + prevCharInMatch = c; +#ifdef DEBUG_FIND + if (inWhitespace) { + printf("YES (whitespace)(%d of %d)\n", pindex, patLen); + } else { + printf("YES! '%c' == '%c' (%d of %d)\n", c, patc, pindex, patLen); + } +#endif + + // Save the range anchors if we haven't already: + if (!matchAnchorNode) { + matchAnchorNode = mIterNode; + matchAnchorOffset = findex; + } + + // Are we done? + if (DONE_WITH_PINDEX) { + // Matched the whole string! +#ifdef DEBUG_FIND + printf("Found a match!\n"); +#endif + + // Make the range: + nsCOMPtr<nsIDOMNode> startParent; + nsCOMPtr<nsIDOMNode> endParent; + + // Check for word break (if necessary) + if (mWordBreaker) { + int32_t nextfindex = findex + incr; + + char16_t nextChar; + // If still in array boundaries, get nextChar. + if (mFindBackward ? (nextfindex >= 0) : (nextfindex < fragLen)) + nextChar = (t2b ? t2b[nextfindex] : CHAR_TO_UNICHAR(t1b[nextfindex])); + // Get next character from the next node. + else + nextChar = PeekNextChar(aSearchRange, aStartPoint, aEndPoint); + + if (nextChar == NBSP_CHARCODE) + nextChar = CHAR_TO_UNICHAR(' '); + + // If a word break isn't there when it needs to be, reset search. + if (!mWordBreaker->BreakInBetween(&c, 1, &nextChar, 1)) { + matchAnchorNode = nullptr; + continue; + } + } + + nsCOMPtr<nsIDOMRange> range = new nsRange(tc); + if (range) { + int32_t matchStartOffset, matchEndOffset; + // convert char index to range point: + int32_t mao = matchAnchorOffset + (mFindBackward ? 1 : 0); + if (mFindBackward) { + startParent = do_QueryInterface(tc); + endParent = matchAnchorNode; + matchStartOffset = findex; + matchEndOffset = mao; + } else { + startParent = matchAnchorNode; + endParent = do_QueryInterface(tc); + matchStartOffset = mao; + matchEndOffset = findex + 1; + } + if (startParent && endParent && + IsVisibleNode(startParent) && IsVisibleNode(endParent)) { + range->SetStart(startParent, matchStartOffset); + range->SetEnd(endParent, matchEndOffset); + *aRangeRet = range.get(); + NS_ADDREF(*aRangeRet); + } else { + // This match is no good -- invisible or bad range + startParent = nullptr; + } + } + + if (startParent) { + // If startParent == nullptr, we didn't successfully make range + // or, we didn't make a range because the start or end node were + // invisible. Reset the offset to the other end of the found string: + mIterOffset = findex + (mFindBackward ? 1 : 0); +#ifdef DEBUG_FIND + printf("mIterOffset = %d, mIterNode = ", mIterOffset); + DumpNode(mIterNode); +#endif + + ResetAll(); + return NS_OK; + } + // This match is no good, continue on in document + matchAnchorNode = nullptr; + } + + if (matchAnchorNode) { + // Not done, but still matching. Advance and loop around for the next + // characters. But don't advance from a space to a non-space: + if (!inWhitespace || DONE_WITH_PINDEX || + IsSpace(patStr[pindex + incr])) { + pindex += incr; + inWhitespace = false; +#ifdef DEBUG_FIND + printf("Advancing pindex to %d\n", pindex); +#endif + } + + continue; + } + } + +#ifdef DEBUG_FIND + printf("NOT: %c == %c\n", c, patc); +#endif + + // If we didn't match, go back to the beginning of patStr, and set findex + // back to the next char after we started the current match. + if (matchAnchorNode) { // we're ending a partial match + findex = matchAnchorOffset; + mIterOffset = matchAnchorOffset; + // +incr will be added to findex when we continue + + // Are we going back to a previous node? + if (matchAnchorNode != mIterNode) { + nsCOMPtr<nsIContent> content(do_QueryInterface(matchAnchorNode)); + DebugOnly<nsresult> rv = NS_ERROR_UNEXPECTED; + if (content) { + rv = mIterator->PositionAt(content); + } + frag = 0; + NS_ASSERTION(NS_SUCCEEDED(rv), "Text content wasn't nsIContent!"); +#ifdef DEBUG_FIND + printf("Repositioned anchor node\n"); +#endif + } +#ifdef DEBUG_FIND + printf("Ending a partial match; findex -> %d, mIterOffset -> %d\n", + findex, mIterOffset); +#endif + } + matchAnchorNode = nullptr; + matchAnchorOffset = 0; + inWhitespace = false; + pindex = (mFindBackward ? patLen : 0); +#ifdef DEBUG_FIND + printf("Setting findex back to %d, pindex to %d\n", findex, pindex); + +#endif + } + + // Out of nodes, and didn't match. + ResetAll(); + return NS_OK; +} diff --git a/embedding/components/find/nsFind.h b/embedding/components/find/nsFind.h new file mode 100644 index 000000000..7f588f187 --- /dev/null +++ b/embedding/components/find/nsFind.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsFind_h__ +#define nsFind_h__ + +#include "nsIFind.h" + +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIDOMNode.h" +#include "nsIDOMRange.h" +#include "nsIContentIterator.h" +#include "nsIWordBreaker.h" + +class nsIContent; + +#define NS_FIND_CONTRACTID "@mozilla.org/embedcomp/rangefind;1" + +#define NS_FIND_CID \ + {0x471f4944, 0x1dd2, 0x11b2, {0x87, 0xac, 0x90, 0xbe, 0x0a, 0x51, 0xd6, 0x09}} + +class nsFindContentIterator; + +class nsFind : public nsIFind +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIFIND + NS_DECL_CYCLE_COLLECTION_CLASS(nsFind) + + nsFind(); + +protected: + virtual ~nsFind(); + + // Parameters set from the interface: + //nsCOMPtr<nsIDOMRange> mRange; // search only in this range + bool mFindBackward; + bool mCaseSensitive; + + // Use "find entire words" mode by setting to a word breaker or null, to + // disable "entire words" mode. + nsCOMPtr<nsIWordBreaker> mWordBreaker; + + int32_t mIterOffset; + nsCOMPtr<nsIDOMNode> mIterNode; + + // Last block parent, so that we will notice crossing block boundaries: + nsCOMPtr<nsIDOMNode> mLastBlockParent; + nsresult GetBlockParent(nsIDOMNode* aNode, nsIDOMNode** aParent); + + // Utility routines: + bool IsTextNode(nsIDOMNode* aNode); + bool IsBlockNode(nsIContent* aNode); + bool SkipNode(nsIContent* aNode); + bool IsVisibleNode(nsIDOMNode* aNode); + + // Move in the right direction for our search: + nsresult NextNode(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, nsIDOMRange* aEndPoint, + bool aContinueOk); + + // Get the first character from the next node (last if mFindBackward). + char16_t PeekNextChar(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, + nsIDOMRange* aEndPoint); + + // Reset variables before returning -- don't hold any references. + void ResetAll(); + + // The iterator we use to move through the document: + nsresult InitIterator(nsIDOMNode* aStartNode, int32_t aStartOffset, + nsIDOMNode* aEndNode, int32_t aEndOffset); + RefPtr<nsFindContentIterator> mIterator; + + friend class PeekNextCharRestoreState; +}; + +#endif // nsFind_h__ diff --git a/embedding/components/find/nsIFind.idl b/embedding/components/find/nsIFind.idl new file mode 100644 index 000000000..2c9b17703 --- /dev/null +++ b/embedding/components/find/nsIFind.idl @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDOMRange; +interface nsIWordBreaker; + +[scriptable, uuid(40aba110-2a56-4678-be90-e2c17a9ae7d7)] +interface nsIFind : nsISupports +{ + attribute boolean findBackwards; + attribute boolean caseSensitive; + attribute boolean entireWord; + + /** + * Find some text in the current context. The implementation is + * responsible for performing the find and highlighting the text. + * + * @param aPatText The text to search for. + * @param aSearchRange A Range specifying domain of search. + * @param aStartPoint A Range specifying search start point. + * If not collapsed, we'll start from + * end (forward) or start (backward). + * @param aEndPoint A Range specifying search end point. + * If not collapsed, we'll end at + * end (forward) or start (backward). + * @retval A range spanning the match that was found (or null). + */ + nsIDOMRange Find(in wstring aPatText, in nsIDOMRange aSearchRange, + in nsIDOMRange aStartPoint, in nsIDOMRange aEndPoint); +}; diff --git a/embedding/components/find/nsIWebBrowserFind.idl b/embedding/components/find/nsIWebBrowserFind.idl new file mode 100644 index 000000000..a00763f78 --- /dev/null +++ b/embedding/components/find/nsIWebBrowserFind.idl @@ -0,0 +1,145 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +#include "domstubs.idl" + +interface mozIDOMWindowProxy; + +/* THIS IS A PUBLIC EMBEDDING API */ + + +/** + * nsIWebBrowserFind + * + * Searches for text in a web browser. + * + * Get one by doing a GetInterface on an nsIWebBrowser. + * + * By default, the implementation will search the focussed frame, or + * if there is no focussed frame, the web browser content area. It + * does not by default search subframes or iframes. To change this + * behaviour, and to explicitly set the frame to search, + * QueryInterface to nsIWebBrowserFindInFrames. + */ + +[scriptable, uuid(e4920136-b3e0-49e0-b1cd-6c783d2591a8)] +interface nsIWebBrowserFind : nsISupports +{ + /** + * findNext + * + * Finds, highlights, and scrolls into view the next occurrence of the + * search string, using the current search settings. Fails if the + * search string is empty. + * + * @return Whether an occurrence was found + */ + boolean findNext(); + + /** + * searchString + * + * The string to search for. This must be non-empty to search. + */ + attribute wstring searchString; + + /** + * findBackwards + * + * Whether to find backwards (towards the beginning of the document). + * Default is false (search forward). + */ + attribute boolean findBackwards; + + /** + * wrapFind + * + * Whether the search wraps around to the start (or end) of the document + * if no match was found between the current position and the end (or + * beginning). Works correctly when searching backwards. Default is + * false. + */ + attribute boolean wrapFind; + + /** + * entireWord + * + * Whether to match entire words only. Default is false. + */ + attribute boolean entireWord; + + /** + * matchCase + * + * Whether to match case (case sensitive) when searching. Default is false. + */ + attribute boolean matchCase; + + /** + * searchFrames + * + * Whether to search through all frames in the content area. Default is true. + * + * Note that you can control whether the search propagates into child or + * parent frames explicitly using nsIWebBrowserFindInFrames, but if one, + * but not both, of searchSubframes and searchParentFrames are set, this + * returns false. + */ + attribute boolean searchFrames; +}; + + + +/** + * nsIWebBrowserFindInFrames + * + * Controls how find behaves when multiple frames or iframes are present. + * + * Get by doing a QueryInterface from nsIWebBrowserFind. + */ + +[scriptable, uuid(e0f5d182-34bc-11d5-be5b-b760676c6ebc)] +interface nsIWebBrowserFindInFrames : nsISupports +{ + /** + * currentSearchFrame + * + * Frame at which to start the search. Once the search is done, this will + * be set to be the last frame searched, whether or not a result was found. + * Has to be equal to or contained within the rootSearchFrame. + */ + attribute mozIDOMWindowProxy currentSearchFrame; + + /** + * rootSearchFrame + * + * Frame within which to confine the search (normally the content area frame). + * Set this to only search a subtree of the frame hierarchy. + */ + attribute mozIDOMWindowProxy rootSearchFrame; + + /** + * searchSubframes + * + * Whether to recurse down into subframes while searching. Default is true. + * + * Setting nsIWebBrowserfind.searchFrames to true sets this to true. + */ + attribute boolean searchSubframes; + + /** + * searchParentFrames + * + * Whether to allow the search to propagate out of the currentSearchFrame into its + * parent frame(s). Search is always confined within the rootSearchFrame. Default + * is true. + * + * Setting nsIWebBrowserfind.searchFrames to true sets this to true. + */ + attribute boolean searchParentFrames; + +}; diff --git a/embedding/components/find/nsWebBrowserFind.cpp b/embedding/components/find/nsWebBrowserFind.cpp new file mode 100644 index 000000000..af44ce59b --- /dev/null +++ b/embedding/components/find/nsWebBrowserFind.cpp @@ -0,0 +1,868 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsWebBrowserFind.h" + +// Only need this for NS_FIND_CONTRACTID, +// else we could use nsIDOMRange.h and nsIFind.h. +#include "nsFind.h" + +#include "nsIComponentManager.h" +#include "nsIScriptSecurityManager.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsPIDOMWindow.h" +#include "nsIURI.h" +#include "nsIDocShell.h" +#include "nsIPresShell.h" +#include "nsPresContext.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsISelectionController.h" +#include "nsISelection.h" +#include "nsIFrame.h" +#include "nsITextControlFrame.h" +#include "nsReadableUtils.h" +#include "nsIDOMHTMLElement.h" +#include "nsIDOMHTMLDocument.h" +#include "nsIContent.h" +#include "nsContentCID.h" +#include "nsIServiceManager.h" +#include "nsIObserverService.h" +#include "nsISupportsPrimitives.h" +#include "nsFind.h" +#include "nsError.h" +#include "nsFocusManager.h" +#include "mozilla/Services.h" +#include "mozilla/dom/Element.h" +#include "nsISimpleEnumerator.h" +#include "nsContentUtils.h" + +#if DEBUG +#include "nsIWebNavigation.h" +#include "nsXPIDLString.h" +#endif + +nsWebBrowserFind::nsWebBrowserFind() + : mFindBackwards(false) + , mWrapFind(false) + , mEntireWord(false) + , mMatchCase(false) + , mSearchSubFrames(true) + , mSearchParentFrames(true) +{ +} + +nsWebBrowserFind::~nsWebBrowserFind() +{ +} + +NS_IMPL_ISUPPORTS(nsWebBrowserFind, nsIWebBrowserFind, + nsIWebBrowserFindInFrames) + +NS_IMETHODIMP +nsWebBrowserFind::FindNext(bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + + NS_ENSURE_TRUE(CanFindNext(), NS_ERROR_NOT_INITIALIZED); + + nsresult rv = NS_OK; + nsCOMPtr<nsPIDOMWindowOuter> searchFrame = do_QueryReferent(mCurrentSearchFrame); + NS_ENSURE_TRUE(searchFrame, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsPIDOMWindowOuter> rootFrame = do_QueryReferent(mRootSearchFrame); + NS_ENSURE_TRUE(rootFrame, NS_ERROR_NOT_INITIALIZED); + + // first, if there's a "cmd_findagain" observer around, check to see if it + // wants to perform the find again command . If it performs the find again + // it will return true, in which case we exit ::FindNext() early. + // Otherwise, nsWebBrowserFind needs to perform the find again command itself + // this is used by nsTypeAheadFind, which controls find again when it was + // the last executed find in the current window. + nsCOMPtr<nsIObserverService> observerSvc = + mozilla::services::GetObserverService(); + if (observerSvc) { + nsCOMPtr<nsISupportsInterfacePointer> windowSupportsData = + do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsISupports> searchWindowSupports = do_QueryInterface(rootFrame); + windowSupportsData->SetData(searchWindowSupports); + NS_NAMED_LITERAL_STRING(dnStr, "down"); + NS_NAMED_LITERAL_STRING(upStr, "up"); + observerSvc->NotifyObservers(windowSupportsData, + "nsWebBrowserFind_FindAgain", + mFindBackwards ? upStr.get() : dnStr.get()); + windowSupportsData->GetData(getter_AddRefs(searchWindowSupports)); + // findnext performed if search window data cleared out + *aResult = searchWindowSupports == nullptr; + if (*aResult) { + return NS_OK; + } + } + + // next, look in the current frame. If found, return. + + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + rv = SearchInFrame(searchFrame, false, aResult); + if (NS_FAILED(rv)) { + return rv; + } + if (*aResult) { + return OnFind(searchFrame); // we are done + } + + // if we are not searching other frames, return + if (!mSearchSubFrames && !mSearchParentFrames) { + return NS_OK; + } + + nsIDocShell* rootDocShell = rootFrame->GetDocShell(); + if (!rootDocShell) { + return NS_ERROR_FAILURE; + } + + int32_t enumDirection = mFindBackwards ? nsIDocShell::ENUMERATE_BACKWARDS : + nsIDocShell::ENUMERATE_FORWARDS; + + nsCOMPtr<nsISimpleEnumerator> docShellEnumerator; + rv = rootDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeAll, + enumDirection, + getter_AddRefs(docShellEnumerator)); + if (NS_FAILED(rv)) { + return rv; + } + + // remember where we started + nsCOMPtr<nsIDocShellTreeItem> startingItem = + do_QueryInterface(searchFrame->GetDocShell(), &rv); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsIDocShellTreeItem> curItem; + + // XXX We should avoid searching in frameset documents here. + // We also need to honour mSearchSubFrames and mSearchParentFrames. + bool hasMore, doFind = false; + while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMore)) && + hasMore) { + nsCOMPtr<nsISupports> curSupports; + rv = docShellEnumerator->GetNext(getter_AddRefs(curSupports)); + if (NS_FAILED(rv)) { + break; + } + curItem = do_QueryInterface(curSupports, &rv); + if (NS_FAILED(rv)) { + break; + } + + if (doFind) { + searchFrame = curItem->GetWindow(); + if (!searchFrame) { + break; + } + + OnStartSearchFrame(searchFrame); + + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + rv = SearchInFrame(searchFrame, false, aResult); + if (NS_FAILED(rv)) { + return rv; + } + if (*aResult) { + return OnFind(searchFrame); // we are done + } + + OnEndSearchFrame(searchFrame); + } + + if (curItem.get() == startingItem.get()) { + doFind = true; // start looking in frames after this one + } + } + + if (!mWrapFind) { + // remember where we left off + SetCurrentSearchFrame(searchFrame); + return NS_OK; + } + + // From here on, we're wrapping, first through the other frames, then finally + // from the beginning of the starting frame back to the starting point. + + // because nsISimpleEnumerator is totally lame and isn't resettable, I have to + // make a new one + docShellEnumerator = nullptr; + rv = rootDocShell->GetDocShellEnumerator(nsIDocShellTreeItem::typeAll, + enumDirection, + getter_AddRefs(docShellEnumerator)); + if (NS_FAILED(rv)) { + return rv; + } + + while (NS_SUCCEEDED(docShellEnumerator->HasMoreElements(&hasMore)) && + hasMore) { + nsCOMPtr<nsISupports> curSupports; + rv = docShellEnumerator->GetNext(getter_AddRefs(curSupports)); + if (NS_FAILED(rv)) { + break; + } + curItem = do_QueryInterface(curSupports, &rv); + if (NS_FAILED(rv)) { + break; + } + + searchFrame = curItem->GetWindow(); + if (!searchFrame) { + rv = NS_ERROR_FAILURE; + break; + } + + if (curItem.get() == startingItem.get()) { + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + rv = SearchInFrame(searchFrame, true, aResult); + if (NS_FAILED(rv)) { + return rv; + } + if (*aResult) { + return OnFind(searchFrame); // we are done + } + break; + } + + OnStartSearchFrame(searchFrame); + + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + rv = SearchInFrame(searchFrame, false, aResult); + if (NS_FAILED(rv)) { + return rv; + } + if (*aResult) { + return OnFind(searchFrame); // we are done + } + + OnEndSearchFrame(searchFrame); + } + + // remember where we left off + SetCurrentSearchFrame(searchFrame); + + NS_ASSERTION(NS_SUCCEEDED(rv), "Something failed"); + return rv; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetSearchString(char16_t** aSearchString) +{ + NS_ENSURE_ARG_POINTER(aSearchString); + *aSearchString = ToNewUnicode(mSearchString); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetSearchString(const char16_t* aSearchString) +{ + mSearchString.Assign(aSearchString); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetFindBackwards(bool* aFindBackwards) +{ + NS_ENSURE_ARG_POINTER(aFindBackwards); + *aFindBackwards = mFindBackwards; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetFindBackwards(bool aFindBackwards) +{ + mFindBackwards = aFindBackwards; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetWrapFind(bool* aWrapFind) +{ + NS_ENSURE_ARG_POINTER(aWrapFind); + *aWrapFind = mWrapFind; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetWrapFind(bool aWrapFind) +{ + mWrapFind = aWrapFind; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetEntireWord(bool* aEntireWord) +{ + NS_ENSURE_ARG_POINTER(aEntireWord); + *aEntireWord = mEntireWord; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetEntireWord(bool aEntireWord) +{ + mEntireWord = aEntireWord; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetMatchCase(bool* aMatchCase) +{ + NS_ENSURE_ARG_POINTER(aMatchCase); + *aMatchCase = mMatchCase; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetMatchCase(bool aMatchCase) +{ + mMatchCase = aMatchCase; + return NS_OK; +} + +static bool +IsInNativeAnonymousSubtree(nsIContent* aContent) +{ + while (aContent) { + nsIContent* bindingParent = aContent->GetBindingParent(); + if (bindingParent == aContent) { + return true; + } + + aContent = bindingParent; + } + + return false; +} + +void +nsWebBrowserFind::SetSelectionAndScroll(nsPIDOMWindowOuter* aWindow, + nsIDOMRange* aRange) +{ + nsCOMPtr<nsIDocument> doc = aWindow->GetDoc(); + if (!doc) { + return; + } + + nsIPresShell* presShell = doc->GetShell(); + if (!presShell) { + return; + } + + nsCOMPtr<nsIDOMNode> node; + aRange->GetStartContainer(getter_AddRefs(node)); + nsCOMPtr<nsIContent> content(do_QueryInterface(node)); + nsIFrame* frame = content->GetPrimaryFrame(); + if (!frame) { + return; + } + nsCOMPtr<nsISelectionController> selCon; + frame->GetSelectionController(presShell->GetPresContext(), + getter_AddRefs(selCon)); + + // since the match could be an anonymous textnode inside a + // <textarea> or text <input>, we need to get the outer frame + nsITextControlFrame* tcFrame = nullptr; + for (; content; content = content->GetParent()) { + if (!IsInNativeAnonymousSubtree(content)) { + nsIFrame* f = content->GetPrimaryFrame(); + if (!f) { + return; + } + tcFrame = do_QueryFrame(f); + break; + } + } + + nsCOMPtr<nsISelection> selection; + + selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON); + selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(selection)); + if (selection) { + selection->RemoveAllRanges(); + selection->AddRange(aRange); + + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (fm) { + if (tcFrame) { + nsCOMPtr<nsIDOMElement> newFocusedElement(do_QueryInterface(content)); + fm->SetFocus(newFocusedElement, nsIFocusManager::FLAG_NOSCROLL); + } else { + nsCOMPtr<nsIDOMElement> result; + fm->MoveFocus(aWindow, nullptr, nsIFocusManager::MOVEFOCUS_CARET, + nsIFocusManager::FLAG_NOSCROLL, getter_AddRefs(result)); + } + } + + // Scroll if necessary to make the selection visible: + // Must be the last thing to do - bug 242056 + + // After ScrollSelectionIntoView(), the pending notifications might be + // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. + selCon->ScrollSelectionIntoView( + nsISelectionController::SELECTION_NORMAL, + nsISelectionController::SELECTION_WHOLE_SELECTION, + nsISelectionController::SCROLL_CENTER_VERTICALLY | + nsISelectionController::SCROLL_SYNCHRONOUS); + } +} + +// Adapted from nsTextServicesDocument::GetDocumentContentRootNode +nsresult +nsWebBrowserFind::GetRootNode(nsIDOMDocument* aDomDoc, nsIDOMNode** aNode) +{ + nsresult rv; + + NS_ENSURE_ARG_POINTER(aNode); + *aNode = 0; + + nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(aDomDoc); + if (htmlDoc) { + // For HTML documents, the content root node is the body. + nsCOMPtr<nsIDOMHTMLElement> bodyElement; + rv = htmlDoc->GetBody(getter_AddRefs(bodyElement)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_ARG_POINTER(bodyElement); + bodyElement.forget(aNode); + return NS_OK; + } + + // For non-HTML documents, the content root node will be the doc element. + nsCOMPtr<nsIDOMElement> docElement; + rv = aDomDoc->GetDocumentElement(getter_AddRefs(docElement)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_ARG_POINTER(docElement); + docElement.forget(aNode); + return NS_OK; +} + +nsresult +nsWebBrowserFind::SetRangeAroundDocument(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPt, + nsIDOMRange* aEndPt, + nsIDOMDocument* aDoc) +{ + nsCOMPtr<nsIDOMNode> bodyNode; + nsresult rv = GetRootNode(aDoc, getter_AddRefs(bodyNode)); + nsCOMPtr<nsIContent> bodyContent(do_QueryInterface(bodyNode)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_ARG_POINTER(bodyContent); + + uint32_t childCount = bodyContent->GetChildCount(); + + aSearchRange->SetStart(bodyNode, 0); + aSearchRange->SetEnd(bodyNode, childCount); + + if (mFindBackwards) { + aStartPt->SetStart(bodyNode, childCount); + aStartPt->SetEnd(bodyNode, childCount); + aEndPt->SetStart(bodyNode, 0); + aEndPt->SetEnd(bodyNode, 0); + } else { + aStartPt->SetStart(bodyNode, 0); + aStartPt->SetEnd(bodyNode, 0); + aEndPt->SetStart(bodyNode, childCount); + aEndPt->SetEnd(bodyNode, childCount); + } + + return NS_OK; +} + +// Set the range to go from the end of the current selection to the end of the +// document (forward), or beginning to beginning (reverse). or around the whole +// document if there's no selection. +nsresult +nsWebBrowserFind::GetSearchLimits(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPt, nsIDOMRange* aEndPt, + nsIDOMDocument* aDoc, nsISelection* aSel, + bool aWrap) +{ + NS_ENSURE_ARG_POINTER(aSel); + + // There is a selection. + int32_t count = -1; + nsresult rv = aSel->GetRangeCount(&count); + NS_ENSURE_SUCCESS(rv, rv); + if (count < 1) { + return SetRangeAroundDocument(aSearchRange, aStartPt, aEndPt, aDoc); + } + + // Need bodyNode, for the start/end of the document + nsCOMPtr<nsIDOMNode> bodyNode; + rv = GetRootNode(aDoc, getter_AddRefs(bodyNode)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIContent> bodyContent(do_QueryInterface(bodyNode)); + NS_ENSURE_ARG_POINTER(bodyContent); + + uint32_t childCount = bodyContent->GetChildCount(); + + // There are four possible range endpoints we might use: + // DocumentStart, SelectionStart, SelectionEnd, DocumentEnd. + + nsCOMPtr<nsIDOMRange> range; + nsCOMPtr<nsIDOMNode> node; + int32_t offset; + + // Forward, not wrapping: SelEnd to DocEnd + if (!mFindBackwards && !aWrap) { + // This isn't quite right, since the selection's ranges aren't + // necessarily in order; but they usually will be. + aSel->GetRangeAt(count - 1, getter_AddRefs(range)); + if (!range) { + return NS_ERROR_UNEXPECTED; + } + range->GetEndContainer(getter_AddRefs(node)); + if (!node) { + return NS_ERROR_UNEXPECTED; + } + range->GetEndOffset(&offset); + + aSearchRange->SetStart(node, offset); + aSearchRange->SetEnd(bodyNode, childCount); + aStartPt->SetStart(node, offset); + aStartPt->SetEnd(node, offset); + aEndPt->SetStart(bodyNode, childCount); + aEndPt->SetEnd(bodyNode, childCount); + } + // Backward, not wrapping: DocStart to SelStart + else if (mFindBackwards && !aWrap) { + aSel->GetRangeAt(0, getter_AddRefs(range)); + if (!range) { + return NS_ERROR_UNEXPECTED; + } + range->GetStartContainer(getter_AddRefs(node)); + if (!node) { + return NS_ERROR_UNEXPECTED; + } + range->GetStartOffset(&offset); + + aSearchRange->SetStart(bodyNode, 0); + aSearchRange->SetEnd(bodyNode, childCount); + aStartPt->SetStart(node, offset); + aStartPt->SetEnd(node, offset); + aEndPt->SetStart(bodyNode, 0); + aEndPt->SetEnd(bodyNode, 0); + } + // Forward, wrapping: DocStart to SelEnd + else if (!mFindBackwards && aWrap) { + aSel->GetRangeAt(count - 1, getter_AddRefs(range)); + if (!range) { + return NS_ERROR_UNEXPECTED; + } + range->GetEndContainer(getter_AddRefs(node)); + if (!node) { + return NS_ERROR_UNEXPECTED; + } + range->GetEndOffset(&offset); + + aSearchRange->SetStart(bodyNode, 0); + aSearchRange->SetEnd(bodyNode, childCount); + aStartPt->SetStart(bodyNode, 0); + aStartPt->SetEnd(bodyNode, 0); + aEndPt->SetStart(node, offset); + aEndPt->SetEnd(node, offset); + } + // Backward, wrapping: SelStart to DocEnd + else if (mFindBackwards && aWrap) { + aSel->GetRangeAt(0, getter_AddRefs(range)); + if (!range) { + return NS_ERROR_UNEXPECTED; + } + range->GetStartContainer(getter_AddRefs(node)); + if (!node) { + return NS_ERROR_UNEXPECTED; + } + range->GetStartOffset(&offset); + + aSearchRange->SetStart(bodyNode, 0); + aSearchRange->SetEnd(bodyNode, childCount); + aStartPt->SetStart(bodyNode, childCount); + aStartPt->SetEnd(bodyNode, childCount); + aEndPt->SetStart(node, offset); + aEndPt->SetEnd(node, offset); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetSearchFrames(bool* aSearchFrames) +{ + NS_ENSURE_ARG_POINTER(aSearchFrames); + // this only returns true if we are searching both sub and parent frames. + // There is ambiguity if the caller has previously set one, but not both of + // these. + *aSearchFrames = mSearchSubFrames && mSearchParentFrames; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetSearchFrames(bool aSearchFrames) +{ + mSearchSubFrames = aSearchFrames; + mSearchParentFrames = aSearchFrames; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetCurrentSearchFrame(mozIDOMWindowProxy** aCurrentSearchFrame) +{ + NS_ENSURE_ARG_POINTER(aCurrentSearchFrame); + nsCOMPtr<mozIDOMWindowProxy> searchFrame = do_QueryReferent(mCurrentSearchFrame); + searchFrame.forget(aCurrentSearchFrame); + return (*aCurrentSearchFrame) ? NS_OK : NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetCurrentSearchFrame(mozIDOMWindowProxy* aCurrentSearchFrame) +{ + // is it ever valid to set this to null? + NS_ENSURE_ARG(aCurrentSearchFrame); + mCurrentSearchFrame = do_GetWeakReference(aCurrentSearchFrame); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetRootSearchFrame(mozIDOMWindowProxy** aRootSearchFrame) +{ + NS_ENSURE_ARG_POINTER(aRootSearchFrame); + nsCOMPtr<mozIDOMWindowProxy> searchFrame = do_QueryReferent(mRootSearchFrame); + searchFrame.forget(aRootSearchFrame); + return (*aRootSearchFrame) ? NS_OK : NS_ERROR_NOT_INITIALIZED; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetRootSearchFrame(mozIDOMWindowProxy* aRootSearchFrame) +{ + // is it ever valid to set this to null? + NS_ENSURE_ARG(aRootSearchFrame); + mRootSearchFrame = do_GetWeakReference(aRootSearchFrame); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetSearchSubframes(bool* aSearchSubframes) +{ + NS_ENSURE_ARG_POINTER(aSearchSubframes); + *aSearchSubframes = mSearchSubFrames; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetSearchSubframes(bool aSearchSubframes) +{ + mSearchSubFrames = aSearchSubframes; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::GetSearchParentFrames(bool* aSearchParentFrames) +{ + NS_ENSURE_ARG_POINTER(aSearchParentFrames); + *aSearchParentFrames = mSearchParentFrames; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserFind::SetSearchParentFrames(bool aSearchParentFrames) +{ + mSearchParentFrames = aSearchParentFrames; + return NS_OK; +} + +/* + This method handles finding in a single window (aka frame). + +*/ +nsresult +nsWebBrowserFind::SearchInFrame(nsPIDOMWindowOuter* aWindow, bool aWrapping, + bool* aDidFind) +{ + NS_ENSURE_ARG(aWindow); + NS_ENSURE_ARG_POINTER(aDidFind); + + *aDidFind = false; + + // Do security check, to ensure that the frame we're searching is + // acccessible from the frame where the Find is being run. + + // get a uri for the window + nsCOMPtr<nsIDocument> theDoc = aWindow->GetDoc(); + if (!theDoc) { + return NS_ERROR_FAILURE; + } + + if (!nsContentUtils::SubjectPrincipal()->Subsumes(theDoc->NodePrincipal())) { + return NS_ERROR_DOM_PROP_ACCESS_DENIED; + } + + nsresult rv; + nsCOMPtr<nsIFind> find = do_CreateInstance(NS_FIND_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + (void)find->SetCaseSensitive(mMatchCase); + (void)find->SetFindBackwards(mFindBackwards); + + (void)find->SetEntireWord(mEntireWord); + + // Now make sure the content (for actual finding) and frame (for + // selection) models are up to date. + theDoc->FlushPendingNotifications(Flush_Frames); + + nsCOMPtr<nsISelection> sel = GetFrameSelection(aWindow); + NS_ENSURE_ARG_POINTER(sel); + + nsCOMPtr<nsIDOMRange> searchRange = new nsRange(theDoc); + NS_ENSURE_ARG_POINTER(searchRange); + nsCOMPtr<nsIDOMRange> startPt = new nsRange(theDoc); + NS_ENSURE_ARG_POINTER(startPt); + nsCOMPtr<nsIDOMRange> endPt = new nsRange(theDoc); + NS_ENSURE_ARG_POINTER(endPt); + + nsCOMPtr<nsIDOMRange> foundRange; + + nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(theDoc); + MOZ_ASSERT(domDoc); + + // If !aWrapping, search from selection to end + if (!aWrapping) + rv = GetSearchLimits(searchRange, startPt, endPt, domDoc, sel, false); + + // If aWrapping, search the part of the starting frame + // up to the point where we left off. + else + rv = GetSearchLimits(searchRange, startPt, endPt, domDoc, sel, true); + + NS_ENSURE_SUCCESS(rv, rv); + + rv = find->Find(mSearchString.get(), searchRange, startPt, endPt, + getter_AddRefs(foundRange)); + + if (NS_SUCCEEDED(rv) && foundRange) { + *aDidFind = true; + sel->RemoveAllRanges(); + // Beware! This may flush notifications via synchronous + // ScrollSelectionIntoView. + SetSelectionAndScroll(aWindow, foundRange); + } + + return rv; +} + +// called when we start searching a frame that is not the initial focussed +// frame. Prepare the frame to be searched. we clear the selection, so that the +// search starts from the top of the frame. +nsresult +nsWebBrowserFind::OnStartSearchFrame(nsPIDOMWindowOuter* aWindow) +{ + return ClearFrameSelection(aWindow); +} + +// called when we are done searching a frame and didn't find anything, and about +// about to start searching the next frame. +nsresult +nsWebBrowserFind::OnEndSearchFrame(nsPIDOMWindowOuter* aWindow) +{ + return NS_OK; +} + +already_AddRefed<nsISelection> +nsWebBrowserFind::GetFrameSelection(nsPIDOMWindowOuter* aWindow) +{ + nsCOMPtr<nsIDocument> doc = aWindow->GetDoc(); + if (!doc) { + return nullptr; + } + + nsIPresShell* presShell = doc->GetShell(); + if (!presShell) { + return nullptr; + } + + // text input controls have their independent selection controllers that we + // must use when they have focus. + nsPresContext* presContext = presShell->GetPresContext(); + + nsCOMPtr<nsPIDOMWindowOuter> focusedWindow; + nsCOMPtr<nsIContent> focusedContent = nsFocusManager::GetFocusedDescendant( + aWindow, false, getter_AddRefs(focusedWindow)); + + nsIFrame* frame = + focusedContent ? focusedContent->GetPrimaryFrame() : nullptr; + + nsCOMPtr<nsISelectionController> selCon; + nsCOMPtr<nsISelection> sel; + if (frame) { + frame->GetSelectionController(presContext, getter_AddRefs(selCon)); + selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(sel)); + if (sel) { + int32_t count = -1; + sel->GetRangeCount(&count); + if (count > 0) { + return sel.forget(); + } + } + } + + selCon = do_QueryInterface(presShell); + selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, + getter_AddRefs(sel)); + return sel.forget(); +} + +nsresult +nsWebBrowserFind::ClearFrameSelection(nsPIDOMWindowOuter* aWindow) +{ + NS_ENSURE_ARG(aWindow); + nsCOMPtr<nsISelection> selection = GetFrameSelection(aWindow); + if (selection) { + selection->RemoveAllRanges(); + } + + return NS_OK; +} + +nsresult +nsWebBrowserFind::OnFind(nsPIDOMWindowOuter* aFoundWindow) +{ + SetCurrentSearchFrame(aFoundWindow); + + // We don't want a selection to appear in two frames simultaneously + nsCOMPtr<nsPIDOMWindowOuter> lastFocusedWindow = + do_QueryReferent(mLastFocusedWindow); + if (lastFocusedWindow && lastFocusedWindow != aFoundWindow) { + ClearFrameSelection(lastFocusedWindow); + } + + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (fm) { + // get the containing frame and focus it. For top-level windows, the right + // window should already be focused. + nsCOMPtr<nsIDOMElement> frameElement = + do_QueryInterface(aFoundWindow->GetFrameElementInternal()); + if (frameElement) { + fm->SetFocus(frameElement, 0); + } + + mLastFocusedWindow = do_GetWeakReference(aFoundWindow); + } + + return NS_OK; +} diff --git a/embedding/components/find/nsWebBrowserFind.h b/embedding/components/find/nsWebBrowserFind.h new file mode 100644 index 000000000..eaf94ae97 --- /dev/null +++ b/embedding/components/find/nsWebBrowserFind.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsWebBrowserFindImpl_h__ +#define nsWebBrowserFindImpl_h__ + +#include "nsIWebBrowserFind.h" + +#include "nsCOMPtr.h" +#include "nsWeakReference.h" + +#include "nsIFind.h" + +#include "nsString.h" + +#define NS_WEB_BROWSER_FIND_CONTRACTID "@mozilla.org/embedcomp/find;1" + +#define NS_WEB_BROWSER_FIND_CID \ + {0x57cf9383, 0x3405, 0x11d5, {0xbe, 0x5b, 0xaa, 0x20, 0xfa, 0x2c, 0xf3, 0x7c}} + +class nsISelection; +class nsIDOMWindow; + +class nsIDocShell; + +//***************************************************************************** +// class nsWebBrowserFind +//***************************************************************************** + +class nsWebBrowserFind + : public nsIWebBrowserFind + , public nsIWebBrowserFindInFrames +{ +public: + nsWebBrowserFind(); + + // nsISupports + NS_DECL_ISUPPORTS + + // nsIWebBrowserFind + NS_DECL_NSIWEBBROWSERFIND + + // nsIWebBrowserFindInFrames + NS_DECL_NSIWEBBROWSERFINDINFRAMES + +protected: + virtual ~nsWebBrowserFind(); + + bool CanFindNext() { return mSearchString.Length() != 0; } + + nsresult SearchInFrame(nsPIDOMWindowOuter* aWindow, bool aWrapping, + bool* aDidFind); + + nsresult OnStartSearchFrame(nsPIDOMWindowOuter* aWindow); + nsresult OnEndSearchFrame(nsPIDOMWindowOuter* aWindow); + + already_AddRefed<nsISelection> GetFrameSelection(nsPIDOMWindowOuter* aWindow); + nsresult ClearFrameSelection(nsPIDOMWindowOuter* aWindow); + + nsresult OnFind(nsPIDOMWindowOuter* aFoundWindow); + + void SetSelectionAndScroll(nsPIDOMWindowOuter* aWindow, nsIDOMRange* aRange); + + nsresult GetRootNode(nsIDOMDocument* aDomDoc, nsIDOMNode** aNode); + nsresult GetSearchLimits(nsIDOMRange* aRange, + nsIDOMRange* aStartPt, nsIDOMRange* aEndPt, + nsIDOMDocument* aDoc, nsISelection* aSel, + bool aWrap); + nsresult SetRangeAroundDocument(nsIDOMRange* aSearchRange, + nsIDOMRange* aStartPoint, + nsIDOMRange* aEndPoint, + nsIDOMDocument* aDoc); + +protected: + nsString mSearchString; + + bool mFindBackwards; + bool mWrapFind; + bool mEntireWord; + bool mMatchCase; + + bool mSearchSubFrames; + bool mSearchParentFrames; + + // These are all weak because who knows if windows can go away during our + // lifetime. + nsWeakPtr mCurrentSearchFrame; + nsWeakPtr mRootSearchFrame; + nsWeakPtr mLastFocusedWindow; +}; + +#endif diff --git a/embedding/components/moz.build b/embedding/components/moz.build new file mode 100644 index 000000000..483c52d58 --- /dev/null +++ b/embedding/components/moz.build @@ -0,0 +1,20 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# You'd think we could skip building ui if XUL is disabled, +# but we need to export interface headers from those directories. +DIRS += [ + 'windowwatcher', + 'appstartup', + 'find', + 'webbrowserpersist', + 'commandhandler', +] + +if CONFIG['MOZ_XUL']: + DIRS += ['printingui'] + +DIRS += ['build'] diff --git a/embedding/components/printingui/ipc/PPrintProgressDialog.ipdl b/embedding/components/printingui/ipc/PPrintProgressDialog.ipdl new file mode 100644 index 000000000..1da29ef87 --- /dev/null +++ b/embedding/components/printingui/ipc/PPrintProgressDialog.ipdl @@ -0,0 +1,35 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PPrinting; + +namespace mozilla { +namespace embedding { + +protocol PPrintProgressDialog +{ + manager PPrinting; + +parent: + async StateChange(long stateFlags, + nsresult status); + + async ProgressChange(long curSelfProgress, + long maxSelfProgress, + long curTotalProgress, + long maxTotalProgress); + + async DocTitleChange(nsString newTitle); + + async DocURLChange(nsString newURL); + + async __delete__(); + +child: + async DialogOpened(); +}; + +} // namespace embedding +} // namespace mozilla diff --git a/embedding/components/printingui/ipc/PPrintSettingsDialog.ipdl b/embedding/components/printingui/ipc/PPrintSettingsDialog.ipdl new file mode 100644 index 000000000..3e436892e --- /dev/null +++ b/embedding/components/printingui/ipc/PPrintSettingsDialog.ipdl @@ -0,0 +1,29 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include PPrintingTypes; +include protocol PPrinting; +include protocol PRemotePrintJob; + +namespace mozilla { +namespace embedding { + +// A PrintData for success, a failure nsresult for failure. +union PrintDataOrNSResult +{ + PrintData; + nsresult; +}; + +protocol PPrintSettingsDialog +{ + manager PPrinting; + +child: + async __delete__(PrintDataOrNSResult result); +}; + +} // namespace embedding +} // namespace mozilla diff --git a/embedding/components/printingui/ipc/PPrinting.ipdl b/embedding/components/printingui/ipc/PPrinting.ipdl new file mode 100644 index 000000000..0b4a1cf64 --- /dev/null +++ b/embedding/components/printingui/ipc/PPrinting.ipdl @@ -0,0 +1,48 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include PPrintingTypes; +include protocol PContent; +include protocol PBrowser; +include protocol PPrintProgressDialog; +include protocol PPrintSettingsDialog; +include protocol PRemotePrintJob; + +namespace mozilla { +namespace embedding { + +sync protocol PPrinting +{ + manager PContent; + manages PPrintProgressDialog; + manages PPrintSettingsDialog; + manages PRemotePrintJob; + +parent: + sync ShowProgress(PBrowser browser, + PPrintProgressDialog printProgressDialog, + nullable PRemotePrintJob remotePrintJob, + bool isForPrinting) + returns(bool notifyOnOpen, + nsresult rv); + + async ShowPrintDialog(PPrintSettingsDialog dialog, + nullable PBrowser browser, + PrintData settings); + + async PPrintProgressDialog(); + async PPrintSettingsDialog(); + + sync SavePrintSettings(PrintData settings, bool usePrinterNamePrefix, + uint32_t flags) + returns(nsresult rv); + +child: + async PRemotePrintJob(); + async __delete__(); +}; + +} // namespace embedding +} // namespace mozilla diff --git a/embedding/components/printingui/ipc/PPrintingTypes.ipdlh b/embedding/components/printingui/ipc/PPrintingTypes.ipdlh new file mode 100644 index 000000000..084047baf --- /dev/null +++ b/embedding/components/printingui/ipc/PPrintingTypes.ipdlh @@ -0,0 +1,126 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PRemotePrintJob; + +namespace mozilla { +namespace embedding { + +struct CStringKeyValue { + nsCString key; + nsCString value; +}; + +struct PrintData { + nullable PRemotePrintJob remotePrintJob; + int32_t startPageRange; + int32_t endPageRange; + double edgeTop; + double edgeLeft; + double edgeBottom; + double edgeRight; + double marginTop; + double marginLeft; + double marginBottom; + double marginRight; + double unwriteableMarginTop; + double unwriteableMarginLeft; + double unwriteableMarginBottom; + double unwriteableMarginRight; + double scaling; + bool printBGColors; + bool printBGImages; + short printRange; + nsString title; + nsString docURL; + nsString headerStrLeft; + nsString headerStrCenter; + nsString headerStrRight; + nsString footerStrLeft; + nsString footerStrCenter; + nsString footerStrRight; + + short howToEnableFrameUI; + bool isCancelled; + short printFrameTypeUsage; + short printFrameType; + bool printSilent; + bool shrinkToFit; + bool showPrintProgress; + + nsString paperName; + short paperData; + double paperWidth; + double paperHeight; + short paperSizeUnit; + bool printReversed; + bool printInColor; + int32_t orientation; + int32_t numCopies; + nsString printerName; + bool printToFile; + nsString toFileName; + short outputFormat; + int32_t printPageDelay; + int32_t resolution; + int32_t duplex; + bool isInitializedFromPrinter; + bool isInitializedFromPrefs; + int32_t optionFlags; + + /* Windows-specific things */ + nsString driverName; + nsString deviceName; + double printableWidthInInches; + double printableHeightInInches; + bool isFramesetDocument; + bool isFramesetFrameSelected; + bool isIFrameSelected; + bool isRangeSelection; + uint8_t[] devModeData; + + /** + * GTK-specific things. Some of these might look like dupes of the + * information we're already passing, but the generalized settings that + * we hold in nsIPrintSettings don't map perfectly to GTK's GtkPrintSettings, + * so there are some nuances. GtkPrintSettings, for example, stores both an + * internal name for paper size, as well as the display name. + */ + CStringKeyValue[] GTKPrintSettings; + + /** + * OS X specific things. + */ + nsString printJobName; + bool printAllPages; + bool mustCollate; + nsString disposition; + /** TODO: Is there an "unsigned short" primitive? **/ + short pagesAcross; + short pagesDown; + double printTime; + bool detailedErrorReporting; + nsString faxNumber; + bool addHeaderAndFooter; + bool fileNameExtensionHidden; + /* + * Holds the scaling factor from the Print dialog when shrink + * to fit is not used. This is needed by the child when it + * isn't using remote printing. When shrink to fit is enabled + * (default), print dialog code ensures this value is 1.0. + */ + float scalingFactor; + /* + * Scaling factor for converting from OS X native paper size + * units to inches. + */ + float widthScale; + float heightScale; + double adjustedPaperWidth; + double adjustedPaperHeight; +}; + +} // namespace embedding +} // namespace mozilla diff --git a/embedding/components/printingui/ipc/PrintDataUtils.cpp b/embedding/components/printingui/ipc/PrintDataUtils.cpp new file mode 100644 index 000000000..30e2075b6 --- /dev/null +++ b/embedding/components/printingui/ipc/PrintDataUtils.cpp @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PrintDataUtils.h"
+#include "nsIPrintSettings.h"
+#include "nsIServiceManager.h"
+#include "nsIWebBrowserPrint.h"
+#include "nsXPIDLString.h"
+
+namespace mozilla {
+namespace embedding {
+
+/**
+ * MockWebBrowserPrint is a mostly useless implementation of nsIWebBrowserPrint,
+ * but wraps a PrintData so that it's able to return information to print
+ * settings dialogs that need an nsIWebBrowserPrint to interrogate.
+ */
+
+NS_IMPL_ISUPPORTS(MockWebBrowserPrint, nsIWebBrowserPrint);
+
+MockWebBrowserPrint::MockWebBrowserPrint(const PrintData &aData)
+ : mData(aData)
+{
+ MOZ_COUNT_CTOR(MockWebBrowserPrint);
+}
+
+MockWebBrowserPrint::~MockWebBrowserPrint()
+{
+ MOZ_COUNT_DTOR(MockWebBrowserPrint);
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetGlobalPrintSettings(nsIPrintSettings **aGlobalPrintSettings)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetCurrentPrintSettings(nsIPrintSettings **aCurrentPrintSettings)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetCurrentChildDOMWindow(mozIDOMWindowProxy **aCurrentPrintSettings)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetDoingPrint(bool *aDoingPrint)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetDoingPrintPreview(bool *aDoingPrintPreview)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetIsFramesetDocument(bool *aIsFramesetDocument)
+{
+ *aIsFramesetDocument = mData.isFramesetDocument();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetIsFramesetFrameSelected(bool *aIsFramesetFrameSelected)
+{
+ *aIsFramesetFrameSelected = mData.isFramesetFrameSelected();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetIsIFrameSelected(bool *aIsIFrameSelected)
+{
+ *aIsIFrameSelected = mData.isIFrameSelected();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetIsRangeSelection(bool *aIsRangeSelection)
+{
+ *aIsRangeSelection = mData.isRangeSelection();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::GetPrintPreviewNumPages(int32_t *aPrintPreviewNumPages)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::Print(nsIPrintSettings* aThePrintSettings,
+ nsIWebProgressListener* aWPListener)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::PrintPreview(nsIPrintSettings* aThePrintSettings,
+ mozIDOMWindowProxy* aChildDOMWin,
+ nsIWebProgressListener* aWPListener)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::PrintPreviewNavigate(int16_t aNavType,
+ int32_t aPageNum)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::Cancel()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::EnumerateDocumentNames(uint32_t* aCount,
+ char16_t*** aResult)
+{
+ *aCount = 0;
+ *aResult = nullptr;
+
+ if (mData.printJobName().IsEmpty()) {
+ return NS_OK;
+ }
+
+ // The only consumer that cares about this is the OS X printing
+ // dialog, and even then, it only cares about the first document
+ // name. That's why we only send a single document name through
+ // PrintData.
+ char16_t** array = (char16_t**) moz_xmalloc(sizeof(char16_t*));
+ array[0] = ToNewUnicode(mData.printJobName());
+
+ *aCount = 1;
+ *aResult = array;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MockWebBrowserPrint::ExitPrintPreview()
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace embedding
+} // namespace mozilla
+
diff --git a/embedding/components/printingui/ipc/PrintDataUtils.h b/embedding/components/printingui/ipc/PrintDataUtils.h new file mode 100644 index 000000000..da313b2f3 --- /dev/null +++ b/embedding/components/printingui/ipc/PrintDataUtils.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_embedding_PrintDataUtils_h
+#define mozilla_embedding_PrintDataUtils_h
+
+#include "mozilla/embedding/PPrinting.h"
+#include "nsIWebBrowserPrint.h"
+
+/**
+ * nsIPrintSettings and nsIWebBrowserPrint information is sent back and forth
+ * across PPrinting via the PrintData struct. These are utilities for
+ * manipulating PrintData that can be used on either side of the communications
+ * channel.
+ */
+
+namespace mozilla {
+namespace embedding {
+
+class MockWebBrowserPrint final : public nsIWebBrowserPrint
+{
+public:
+ explicit MockWebBrowserPrint(const PrintData &aData);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIWEBBROWSERPRINT
+
+private:
+ ~MockWebBrowserPrint();
+ PrintData mData;
+};
+
+} // namespace embedding
+} // namespace mozilla
+
+#endif
diff --git a/embedding/components/printingui/ipc/PrintProgressDialogChild.cpp b/embedding/components/printingui/ipc/PrintProgressDialogChild.cpp new file mode 100644 index 000000000..a6dc5de4e --- /dev/null +++ b/embedding/components/printingui/ipc/PrintProgressDialogChild.cpp @@ -0,0 +1,132 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Unused.h" +#include "nsIObserver.h" +#include "PrintProgressDialogChild.h" + +class nsIWebProgress; +class nsIRequest; + +using mozilla::Unused; + +namespace mozilla { +namespace embedding { + +NS_IMPL_ISUPPORTS(PrintProgressDialogChild, + nsIWebProgressListener, + nsIPrintProgressParams) + +PrintProgressDialogChild::PrintProgressDialogChild( + nsIObserver* aOpenObserver) : + mOpenObserver(aOpenObserver) +{ + MOZ_COUNT_CTOR(PrintProgressDialogChild); +} + +PrintProgressDialogChild::~PrintProgressDialogChild() +{ + // When the printing engine stops supplying information about printing + // progress, it'll drop references to us and destroy us. We need to signal + // the parent to decrement its refcount, as well as prevent it from attempting + // to contact us further. + Unused << Send__delete__(this); + MOZ_COUNT_DTOR(PrintProgressDialogChild); +} + +bool +PrintProgressDialogChild::RecvDialogOpened() +{ + // nsPrintEngine's observer, which we're reporting to here, doesn't care + // what gets passed as the subject, topic or data, so we'll just send + // nullptrs. + mOpenObserver->Observe(nullptr, nullptr, nullptr); + return true; +} + +// nsIWebProgressListener + +NS_IMETHODIMP +PrintProgressDialogChild::OnStateChange(nsIWebProgress* aProgress, + nsIRequest* aRequest, + uint32_t aStateFlags, + nsresult aStatus) +{ + Unused << SendStateChange(aStateFlags, aStatus); + return NS_OK; +} + +NS_IMETHODIMP +PrintProgressDialogChild::OnProgressChange(nsIWebProgress * aProgress, + nsIRequest * aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) +{ + Unused << SendProgressChange(aCurSelfProgress, aMaxSelfProgress, + aCurTotalProgress, aMaxTotalProgress); + return NS_OK; +} + +NS_IMETHODIMP +PrintProgressDialogChild::OnLocationChange(nsIWebProgress* aProgress, + nsIRequest* aRequest, + nsIURI* aURI, + uint32_t aFlags) +{ + return NS_OK; +} + +NS_IMETHODIMP +PrintProgressDialogChild::OnStatusChange(nsIWebProgress* aProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) +{ + return NS_OK; +} + +NS_IMETHODIMP +PrintProgressDialogChild::OnSecurityChange(nsIWebProgress* aProgress, + nsIRequest* aRequest, + uint32_t aState) +{ + return NS_OK; +} + +// nsIPrintProgressParams + +NS_IMETHODIMP PrintProgressDialogChild::GetDocTitle(char16_t* *aDocTitle) +{ + NS_ENSURE_ARG(aDocTitle); + + *aDocTitle = ToNewUnicode(mDocTitle); + return NS_OK; +} + +NS_IMETHODIMP PrintProgressDialogChild::SetDocTitle(const char16_t* aDocTitle) +{ + mDocTitle = aDocTitle; + Unused << SendDocTitleChange(nsString(aDocTitle)); + return NS_OK; +} + +NS_IMETHODIMP PrintProgressDialogChild::GetDocURL(char16_t **aDocURL) +{ + NS_ENSURE_ARG(aDocURL); + + *aDocURL = ToNewUnicode(mDocURL); + return NS_OK; +} + +NS_IMETHODIMP PrintProgressDialogChild::SetDocURL(const char16_t* aDocURL) +{ + mDocURL = aDocURL; + Unused << SendDocURLChange(nsString(aDocURL)); + return NS_OK; +} + +} // namespace embedding +} // namespace mozilla diff --git a/embedding/components/printingui/ipc/PrintProgressDialogChild.h b/embedding/components/printingui/ipc/PrintProgressDialogChild.h new file mode 100644 index 000000000..b0a7105d7 --- /dev/null +++ b/embedding/components/printingui/ipc/PrintProgressDialogChild.h @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_embedding_PrintProgressDialogChild_h +#define mozilla_embedding_PrintProgressDialogChild_h + +#include "mozilla/embedding/PPrintProgressDialogChild.h" +#include "nsIPrintProgressParams.h" +#include "nsIWebProgressListener.h" + +class nsIObserver; + +namespace mozilla { +namespace embedding { + +class PrintProgressDialogChild final : public PPrintProgressDialogChild, + public nsIWebProgressListener, + public nsIPrintProgressParams +{ + NS_DECL_ISUPPORTS + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_NSIPRINTPROGRESSPARAMS + +public: + MOZ_IMPLICIT PrintProgressDialogChild(nsIObserver* aOpenObserver); + + virtual bool RecvDialogOpened() override; + +private: + virtual ~PrintProgressDialogChild(); + nsCOMPtr<nsIObserver> mOpenObserver; + nsString mDocTitle; + nsString mDocURL; +}; + +} // namespace embedding +} // namespace mozilla + +#endif diff --git a/embedding/components/printingui/ipc/PrintProgressDialogParent.cpp b/embedding/components/printingui/ipc/PrintProgressDialogParent.cpp new file mode 100644 index 000000000..6e34704ff --- /dev/null +++ b/embedding/components/printingui/ipc/PrintProgressDialogParent.cpp @@ -0,0 +1,113 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Unused.h" +#include "nsIPrintProgressParams.h" +#include "nsIWebProgressListener.h" +#include "PrintProgressDialogParent.h" + +using mozilla::Unused; + +namespace mozilla { +namespace embedding { + +NS_IMPL_ISUPPORTS(PrintProgressDialogParent, nsIObserver) + +PrintProgressDialogParent::PrintProgressDialogParent() : + mActive(true) +{ + MOZ_COUNT_CTOR(PrintProgressDialogParent); +} + +PrintProgressDialogParent::~PrintProgressDialogParent() +{ + MOZ_COUNT_DTOR(PrintProgressDialogParent); +} + +void +PrintProgressDialogParent::SetWebProgressListener(nsIWebProgressListener* aListener) +{ + mWebProgressListener = aListener; +} + +void +PrintProgressDialogParent::SetPrintProgressParams(nsIPrintProgressParams* aParams) +{ + mPrintProgressParams = aParams; +} + +bool +PrintProgressDialogParent::RecvStateChange(const long& stateFlags, + const nsresult& status) +{ + if (mWebProgressListener) { + mWebProgressListener->OnStateChange(nullptr, nullptr, stateFlags, status); + } + return true; +} + +bool +PrintProgressDialogParent::RecvProgressChange(const long& curSelfProgress, + const long& maxSelfProgress, + const long& curTotalProgress, + const long& maxTotalProgress) +{ + if (mWebProgressListener) { + mWebProgressListener->OnProgressChange(nullptr, nullptr, curSelfProgress, + maxSelfProgress, curTotalProgress, + maxTotalProgress); + } + return true; +} + +bool +PrintProgressDialogParent::RecvDocTitleChange(const nsString& newTitle) +{ + if (mPrintProgressParams) { + mPrintProgressParams->SetDocTitle(newTitle.get()); + } + return true; +} + +bool +PrintProgressDialogParent::RecvDocURLChange(const nsString& newURL) +{ + if (mPrintProgressParams) { + mPrintProgressParams->SetDocURL(newURL.get()); + } + return true; +} + +void +PrintProgressDialogParent::ActorDestroy(ActorDestroyReason aWhy) +{ +} + +bool +PrintProgressDialogParent::Recv__delete__() +{ + // The child has requested that we tear down the connection, so we set a + // member to make sure we don't try to contact it after the fact. + mActive = false; + return true; +} + +// nsIObserver +NS_IMETHODIMP +PrintProgressDialogParent::Observe(nsISupports *aSubject, const char *aTopic, + const char16_t *aData) +{ + if (mActive) { + Unused << SendDialogOpened(); + } else { + NS_WARNING("The print progress dialog finished opening, but communications " + "with the child have been closed."); + } + + return NS_OK; +} + + +} // namespace embedding +} // namespace mozilla diff --git a/embedding/components/printingui/ipc/PrintProgressDialogParent.h b/embedding/components/printingui/ipc/PrintProgressDialogParent.h new file mode 100644 index 000000000..540609162 --- /dev/null +++ b/embedding/components/printingui/ipc/PrintProgressDialogParent.h @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_embedding_PrintProgressDialogParent_h +#define mozilla_embedding_PrintProgressDialogParent_h + +#include "mozilla/embedding/PPrintProgressDialogParent.h" +#include "nsIObserver.h" + +class nsIPrintProgressParams; +class nsIWebProgressListener; + +namespace mozilla { +namespace embedding { +class PrintProgressDialogParent final : public PPrintProgressDialogParent, + public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + MOZ_IMPLICIT PrintProgressDialogParent(); + + void SetWebProgressListener(nsIWebProgressListener* aListener); + + void SetPrintProgressParams(nsIPrintProgressParams* aParams); + + virtual bool + RecvStateChange( + const long& stateFlags, + const nsresult& status) override; + + virtual bool + RecvProgressChange( + const long& curSelfProgress, + const long& maxSelfProgress, + const long& curTotalProgress, + const long& maxTotalProgress) override; + + virtual bool + RecvDocTitleChange(const nsString& newTitle) override; + + virtual bool + RecvDocURLChange(const nsString& newURL) override; + + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + virtual bool + Recv__delete__() override; + +private: + virtual ~PrintProgressDialogParent(); + + nsCOMPtr<nsIWebProgressListener> mWebProgressListener; + nsCOMPtr<nsIPrintProgressParams> mPrintProgressParams; + bool mActive; +}; + +} // namespace embedding +} // namespace mozilla + +#endif diff --git a/embedding/components/printingui/ipc/PrintSettingsDialogChild.cpp b/embedding/components/printingui/ipc/PrintSettingsDialogChild.cpp new file mode 100644 index 000000000..ee9c5de59 --- /dev/null +++ b/embedding/components/printingui/ipc/PrintSettingsDialogChild.cpp @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PrintSettingsDialogChild.h" + +using mozilla::Unused; + +namespace mozilla { +namespace embedding { + +PrintSettingsDialogChild::PrintSettingsDialogChild() +: mReturned(false) +{ + MOZ_COUNT_CTOR(PrintSettingsDialogChild); +} + +PrintSettingsDialogChild::~PrintSettingsDialogChild() +{ + MOZ_COUNT_DTOR(PrintSettingsDialogChild); +} + +bool +PrintSettingsDialogChild::Recv__delete__(const PrintDataOrNSResult& aData) +{ + if (aData.type() == PrintDataOrNSResult::Tnsresult) { + mResult = aData.get_nsresult(); + MOZ_ASSERT(NS_FAILED(mResult), "expected a failure result"); + } else { + mResult = NS_OK; + mData = aData.get_PrintData(); + } + mReturned = true; + return true; +} + +} // namespace embedding +} // namespace mozilla diff --git a/embedding/components/printingui/ipc/PrintSettingsDialogChild.h b/embedding/components/printingui/ipc/PrintSettingsDialogChild.h new file mode 100644 index 000000000..6db60e221 --- /dev/null +++ b/embedding/components/printingui/ipc/PrintSettingsDialogChild.h @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_embedding_PrintSettingsDialogChild_h +#define mozilla_embedding_PrintSettingsDialogChild_h + +#include "mozilla/embedding/PPrintSettingsDialogChild.h" +namespace mozilla { +namespace embedding { + +class PrintSettingsDialogChild final : public PPrintSettingsDialogChild +{ + NS_INLINE_DECL_REFCOUNTING(PrintSettingsDialogChild) + +public: + MOZ_IMPLICIT PrintSettingsDialogChild(); + + virtual bool Recv__delete__(const PrintDataOrNSResult& aData) override; + + bool returned() { return mReturned; }; + nsresult result() { return mResult; }; + PrintData data() { return mData; }; + +private: + virtual ~PrintSettingsDialogChild(); + bool mReturned; + nsresult mResult; + PrintData mData; +}; + +} // namespace embedding +} // namespace mozilla + +#endif diff --git a/embedding/components/printingui/ipc/PrintSettingsDialogParent.cpp b/embedding/components/printingui/ipc/PrintSettingsDialogParent.cpp new file mode 100644 index 000000000..016677d96 --- /dev/null +++ b/embedding/components/printingui/ipc/PrintSettingsDialogParent.cpp @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PrintSettingsDialogParent.h" + +// C++ file contents +namespace mozilla { +namespace embedding { + +PrintSettingsDialogParent::PrintSettingsDialogParent() +{ + MOZ_COUNT_CTOR(PrintSettingsDialogParent); +} + +PrintSettingsDialogParent::~PrintSettingsDialogParent() +{ + MOZ_COUNT_DTOR(PrintSettingsDialogParent); +} + +void +PrintSettingsDialogParent::ActorDestroy(ActorDestroyReason aWhy) +{ +} + +} // namespace embedding +} // namespace mozilla + diff --git a/embedding/components/printingui/ipc/PrintSettingsDialogParent.h b/embedding/components/printingui/ipc/PrintSettingsDialogParent.h new file mode 100644 index 000000000..e655903e7 --- /dev/null +++ b/embedding/components/printingui/ipc/PrintSettingsDialogParent.h @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_embedding_PrintSettingsDialogParent_h +#define mozilla_embedding_PrintSettingsDialogParent_h + +#include "mozilla/embedding/PPrintSettingsDialogParent.h" + +// Header file contents +namespace mozilla { +namespace embedding { + +class PrintSettingsDialogParent final : public PPrintSettingsDialogParent +{ +public: + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + MOZ_IMPLICIT PrintSettingsDialogParent(); + +private: + virtual ~PrintSettingsDialogParent(); +}; + +} // namespace embedding +} // namespace mozilla + +#endif diff --git a/embedding/components/printingui/ipc/PrintingParent.cpp b/embedding/components/printingui/ipc/PrintingParent.cpp new file mode 100644 index 000000000..46469844a --- /dev/null +++ b/embedding/components/printingui/ipc/PrintingParent.cpp @@ -0,0 +1,336 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/Element.h" +#include "mozilla/dom/TabParent.h" +#include "mozilla/Preferences.h" +#include "mozilla/Unused.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIDOMWindow.h" +#include "nsIPrintingPromptService.h" +#include "nsIPrintProgressParams.h" +#include "nsIPrintSettingsService.h" +#include "nsIServiceManager.h" +#include "nsServiceManagerUtils.h" +#include "nsIWebProgressListener.h" +#include "PrintingParent.h" +#include "PrintDataUtils.h" +#include "PrintProgressDialogParent.h" +#include "PrintSettingsDialogParent.h" +#include "mozilla/layout/RemotePrintJobParent.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::layout; + +namespace mozilla { +namespace embedding { +bool +PrintingParent::RecvShowProgress(PBrowserParent* parent, + PPrintProgressDialogParent* printProgressDialog, + PRemotePrintJobParent* remotePrintJob, + const bool& isForPrinting, + bool* notifyOnOpen, + nsresult* result) +{ + *result = NS_ERROR_FAILURE; + *notifyOnOpen = false; + + nsCOMPtr<nsPIDOMWindowOuter> parentWin = DOMWindowFromBrowserParent(parent); + if (!parentWin) { + return true; + } + + nsCOMPtr<nsIPrintingPromptService> pps(do_GetService("@mozilla.org/embedcomp/printingprompt-service;1")); + + if (!pps) { + return true; + } + + PrintProgressDialogParent* dialogParent = + static_cast<PrintProgressDialogParent*>(printProgressDialog); + nsCOMPtr<nsIObserver> observer = do_QueryInterface(dialogParent); + + nsCOMPtr<nsIWebProgressListener> printProgressListener; + nsCOMPtr<nsIPrintProgressParams> printProgressParams; + + *result = pps->ShowProgress(parentWin, nullptr, nullptr, observer, + isForPrinting, + getter_AddRefs(printProgressListener), + getter_AddRefs(printProgressParams), + notifyOnOpen); + NS_ENSURE_SUCCESS(*result, true); + + if (remotePrintJob) { + // If we have a RemotePrintJob use that as a more general forwarder for + // print progress listeners. + static_cast<RemotePrintJobParent*>(remotePrintJob) + ->RegisterListener(printProgressListener); + } else { + dialogParent->SetWebProgressListener(printProgressListener); + } + + dialogParent->SetPrintProgressParams(printProgressParams); + + return true; +} + +nsresult +PrintingParent::ShowPrintDialog(PBrowserParent* aParent, + const PrintData& aData, + PrintData* aResult) +{ + // If aParent is null this call is just being used to get print settings from + // the printer for print preview. + bool isPrintPreview = !aParent; + nsCOMPtr<nsPIDOMWindowOuter> parentWin; + if (aParent) { + parentWin = DOMWindowFromBrowserParent(aParent); + if (!parentWin) { + return NS_ERROR_FAILURE; + } + } + + nsCOMPtr<nsIPrintingPromptService> pps(do_GetService("@mozilla.org/embedcomp/printingprompt-service;1")); + if (!pps) { + return NS_ERROR_FAILURE; + } + + // The initSettings we got can be wrapped using + // PrintDataUtils' MockWebBrowserPrint, which implements enough of + // nsIWebBrowserPrint to keep the dialogs happy. + nsCOMPtr<nsIWebBrowserPrint> wbp = new MockWebBrowserPrint(aData); + + // Use the existing RemotePrintJob and its settings, if we have one, to make + // sure they stay current. + RemotePrintJobParent* remotePrintJob = + static_cast<RemotePrintJobParent*>(aData.remotePrintJobParent()); + nsCOMPtr<nsIPrintSettings> settings; + nsresult rv; + if (remotePrintJob) { + settings = remotePrintJob->GetPrintSettings(); + } else { + rv = mPrintSettingsSvc->GetNewPrintSettings(getter_AddRefs(settings)); + NS_ENSURE_SUCCESS(rv, rv); + } + + // We only want to use the print silently setting from the parent. + bool printSilently; + rv = settings->GetPrintSilent(&printSilently); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mPrintSettingsSvc->DeserializeToPrintSettings(aData, settings); + NS_ENSURE_SUCCESS(rv, rv); + + rv = settings->SetPrintSilent(printSilently); + NS_ENSURE_SUCCESS(rv, rv); + + // If this is for print preview or we are printing silently then we just need + // to initialize the print settings with anything specific from the printer. + if (isPrintPreview || printSilently || + Preferences::GetBool("print.always_print_silent", printSilently)) { + nsXPIDLString printerName; + rv = settings->GetPrinterName(getter_Copies(printerName)); + NS_ENSURE_SUCCESS(rv, rv); + + settings->SetIsInitializedFromPrinter(false); + mPrintSettingsSvc->InitPrintSettingsFromPrinter(printerName, settings); + } else { + rv = pps->ShowPrintDialog(parentWin, wbp, settings); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (isPrintPreview) { + // For print preview we don't want a RemotePrintJob just the settings. + rv = mPrintSettingsSvc->SerializeToPrintData(settings, nullptr, aResult); + } else { + rv = SerializeAndEnsureRemotePrintJob(settings, nullptr, remotePrintJob, + aResult); + } + + return rv; +} + +bool +PrintingParent::RecvShowPrintDialog(PPrintSettingsDialogParent* aDialog, + PBrowserParent* aParent, + const PrintData& aData) +{ + PrintData resultData; + nsresult rv = ShowPrintDialog(aParent, aData, &resultData); + + // The child has been spinning an event loop while waiting + // to hear about the print settings. We return the results + // with an async message which frees the child process from + // its nested event loop. + if (NS_FAILED(rv)) { + mozilla::Unused << aDialog->Send__delete__(aDialog, rv); + } else { + mozilla::Unused << aDialog->Send__delete__(aDialog, resultData); + } + return true; +} + +bool +PrintingParent::RecvSavePrintSettings(const PrintData& aData, + const bool& aUsePrinterNamePrefix, + const uint32_t& aFlags, + nsresult* aResult) +{ + nsCOMPtr<nsIPrintSettings> settings; + *aResult = mPrintSettingsSvc->GetNewPrintSettings(getter_AddRefs(settings)); + NS_ENSURE_SUCCESS(*aResult, true); + + *aResult = mPrintSettingsSvc->DeserializeToPrintSettings(aData, settings); + NS_ENSURE_SUCCESS(*aResult, true); + + *aResult = mPrintSettingsSvc->SavePrintSettingsToPrefs(settings, + aUsePrinterNamePrefix, + aFlags); + + return true; +} + +PPrintProgressDialogParent* +PrintingParent::AllocPPrintProgressDialogParent() +{ + PrintProgressDialogParent* actor = new PrintProgressDialogParent(); + NS_ADDREF(actor); // De-ref'd in the __delete__ handler for + // PrintProgressDialogParent. + return actor; +} + +bool +PrintingParent::DeallocPPrintProgressDialogParent(PPrintProgressDialogParent* doomed) +{ + // We can't just delete the PrintProgressDialogParent since somebody might + // still be holding a reference to it as nsIObserver, so just decrement the + // refcount instead. + PrintProgressDialogParent* actor = static_cast<PrintProgressDialogParent*>(doomed); + NS_RELEASE(actor); + return true; +} + +PPrintSettingsDialogParent* +PrintingParent::AllocPPrintSettingsDialogParent() +{ + return new PrintSettingsDialogParent(); +} + +bool +PrintingParent::DeallocPPrintSettingsDialogParent(PPrintSettingsDialogParent* aDoomed) +{ + delete aDoomed; + return true; +} + +PRemotePrintJobParent* +PrintingParent::AllocPRemotePrintJobParent() +{ + MOZ_ASSERT_UNREACHABLE("No default constructors for implementations."); + return nullptr; +} + +bool +PrintingParent::DeallocPRemotePrintJobParent(PRemotePrintJobParent* aDoomed) +{ + delete aDoomed; + return true; +} + +void +PrintingParent::ActorDestroy(ActorDestroyReason aWhy) +{ +} + +nsPIDOMWindowOuter* +PrintingParent::DOMWindowFromBrowserParent(PBrowserParent* parent) +{ + if (!parent) { + return nullptr; + } + + TabParent* tabParent = TabParent::GetFrom(parent); + if (!tabParent) { + return nullptr; + } + + nsCOMPtr<Element> frameElement = tabParent->GetOwnerElement(); + if (!frameElement) { + return nullptr; + } + + nsCOMPtr<nsIContent> frame(do_QueryInterface(frameElement)); + if (!frame) { + return nullptr; + } + + nsCOMPtr<nsPIDOMWindowOuter> parentWin = frame->OwnerDoc()->GetWindow(); + if (!parentWin) { + return nullptr; + } + + return parentWin; +} + +nsresult +PrintingParent::SerializeAndEnsureRemotePrintJob( + nsIPrintSettings* aPrintSettings, nsIWebProgressListener* aListener, + layout::RemotePrintJobParent* aRemotePrintJob, PrintData* aPrintData) +{ + MOZ_ASSERT(aPrintData); + + nsresult rv; + nsCOMPtr<nsIPrintSettings> printSettings; + if (aPrintSettings) { + printSettings = aPrintSettings; + } else { + rv = mPrintSettingsSvc->GetNewPrintSettings(getter_AddRefs(printSettings)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + rv = mPrintSettingsSvc->SerializeToPrintData(printSettings, nullptr, + aPrintData); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + RemotePrintJobParent* remotePrintJob; + if (aRemotePrintJob) { + remotePrintJob = aRemotePrintJob; + aPrintData->remotePrintJobParent() = remotePrintJob; + } else { + remotePrintJob = new RemotePrintJobParent(aPrintSettings); + aPrintData->remotePrintJobParent() = + SendPRemotePrintJobConstructor(remotePrintJob); + } + if (aListener) { + remotePrintJob->RegisterListener(aListener); + } + + return NS_OK; +} + +PrintingParent::PrintingParent() +{ + MOZ_COUNT_CTOR(PrintingParent); + + mPrintSettingsSvc = + do_GetService("@mozilla.org/gfx/printsettings-service;1"); + MOZ_ASSERT(mPrintSettingsSvc); +} + +PrintingParent::~PrintingParent() +{ + MOZ_COUNT_DTOR(PrintingParent); +} + +} // namespace embedding +} // namespace mozilla + diff --git a/embedding/components/printingui/ipc/PrintingParent.h b/embedding/components/printingui/ipc/PrintingParent.h new file mode 100644 index 000000000..66f87726c --- /dev/null +++ b/embedding/components/printingui/ipc/PrintingParent.h @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set sw=4 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_embedding_PrintingParent_h +#define mozilla_embedding_PrintingParent_h + +#include "mozilla/dom/PBrowserParent.h" +#include "mozilla/embedding/PPrintingParent.h" + +class nsIPrintSettingsService; +class nsIWebProgressListener; +class nsPIDOMWindowOuter; +class PPrintProgressDialogParent; +class PPrintSettingsDialogParent; + +namespace mozilla { +namespace layout { +class PRemotePrintJobParent; +class RemotePrintJobParent; +} + +namespace embedding { + +class PrintingParent final : public PPrintingParent +{ +public: + NS_INLINE_DECL_REFCOUNTING(PrintingParent) + + virtual bool + RecvShowProgress(PBrowserParent* parent, + PPrintProgressDialogParent* printProgressDialog, + PRemotePrintJobParent* remotePrintJob, + const bool& isForPrinting, + bool* notifyOnOpen, + nsresult* result); + virtual bool + RecvShowPrintDialog(PPrintSettingsDialogParent* aDialog, + PBrowserParent* aParent, + const PrintData& aData); + + virtual bool + RecvSavePrintSettings(const PrintData& data, + const bool& usePrinterNamePrefix, + const uint32_t& flags, + nsresult* rv); + + virtual PPrintProgressDialogParent* + AllocPPrintProgressDialogParent(); + + virtual bool + DeallocPPrintProgressDialogParent(PPrintProgressDialogParent* aActor); + + virtual PPrintSettingsDialogParent* + AllocPPrintSettingsDialogParent(); + + virtual bool + DeallocPPrintSettingsDialogParent(PPrintSettingsDialogParent* aActor); + + virtual PRemotePrintJobParent* + AllocPRemotePrintJobParent(); + + virtual bool + DeallocPRemotePrintJobParent(PRemotePrintJobParent* aActor); + + virtual void + ActorDestroy(ActorDestroyReason aWhy); + + MOZ_IMPLICIT PrintingParent(); + + /** + * Serialize nsIPrintSettings to PrintData ready for sending to a child + * process. A RemotePrintJob will be created and added to the PrintData. + * An optional progress listener can be given, which will be registered + * with the RemotePrintJob, so that progress can be tracked in the parent. + * + * @param aPrintSettings optional print settings to serialize, otherwise a + * default print settings will be used. + * @param aProgressListener optional print progress listener. + * @param aRemotePrintJob optional remote print job, so that an existing + * one can be used. + * @param aPrintData PrintData to populate. + */ + nsresult + SerializeAndEnsureRemotePrintJob(nsIPrintSettings* aPrintSettings, + nsIWebProgressListener* aListener, + layout::RemotePrintJobParent* aRemotePrintJob, + PrintData* aPrintData); + +private: + virtual ~PrintingParent(); + + nsPIDOMWindowOuter* + DOMWindowFromBrowserParent(PBrowserParent* parent); + + nsresult + ShowPrintDialog(PBrowserParent* parent, + const PrintData& data, + PrintData* result); + + nsCOMPtr<nsIPrintSettingsService> mPrintSettingsSvc; +}; + +} // namespace embedding +} // namespace mozilla + +#endif diff --git a/embedding/components/printingui/ipc/moz.build b/embedding/components/printingui/ipc/moz.build new file mode 100644 index 000000000..29822e652 --- /dev/null +++ b/embedding/components/printingui/ipc/moz.build @@ -0,0 +1,35 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'nsPrintingProxy.h', +] + +EXPORTS.mozilla.embedding.printingui += [ + 'PrintingParent.h', +] + +if CONFIG['NS_PRINTING']: + UNIFIED_SOURCES += [ + 'nsPrintingProxy.cpp', + 'PrintDataUtils.cpp', + 'PrintingParent.cpp', + 'PrintProgressDialogChild.cpp', + 'PrintProgressDialogParent.cpp', + 'PrintSettingsDialogChild.cpp', + 'PrintSettingsDialogParent.cpp', + ] + +IPDL_SOURCES += [ + 'PPrinting.ipdl', + 'PPrintingTypes.ipdlh', + 'PPrintProgressDialog.ipdl', + 'PPrintSettingsDialog.ipdl', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' diff --git a/embedding/components/printingui/ipc/nsPrintingProxy.cpp b/embedding/components/printingui/ipc/nsPrintingProxy.cpp new file mode 100644 index 000000000..353be04ee --- /dev/null +++ b/embedding/components/printingui/ipc/nsPrintingProxy.cpp @@ -0,0 +1,269 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPrintingProxy.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/TabChild.h" +#include "mozilla/layout/RemotePrintJobChild.h" +#include "mozilla/Unused.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIPrintingPromptService.h" +#include "nsIPrintSession.h" +#include "nsPIDOMWindow.h" +#include "nsPrintOptionsImpl.h" +#include "nsServiceManagerUtils.h" +#include "PrintDataUtils.h" +#include "PrintProgressDialogChild.h" +#include "PrintSettingsDialogChild.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::embedding; +using namespace mozilla::layout; + +static StaticRefPtr<nsPrintingProxy> sPrintingProxyInstance; + +NS_IMPL_ISUPPORTS(nsPrintingProxy, nsIPrintingPromptService) + +nsPrintingProxy::nsPrintingProxy() +{ +} + +nsPrintingProxy::~nsPrintingProxy() +{ +} + +/* static */ +already_AddRefed<nsPrintingProxy> +nsPrintingProxy::GetInstance() +{ + if (!sPrintingProxyInstance) { + sPrintingProxyInstance = new nsPrintingProxy(); + if (!sPrintingProxyInstance) { + return nullptr; + } + nsresult rv = sPrintingProxyInstance->Init(); + if (NS_FAILED(rv)) { + sPrintingProxyInstance = nullptr; + return nullptr; + } + ClearOnShutdown(&sPrintingProxyInstance); + } + + RefPtr<nsPrintingProxy> inst = sPrintingProxyInstance.get(); + return inst.forget(); +} + +nsresult +nsPrintingProxy::Init() +{ + mozilla::Unused << ContentChild::GetSingleton()->SendPPrintingConstructor(this); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingProxy::ShowPrintDialog(mozIDOMWindowProxy *parent, + nsIWebBrowserPrint *webBrowserPrint, + nsIPrintSettings *printSettings) +{ + NS_ENSURE_ARG(webBrowserPrint); + NS_ENSURE_ARG(printSettings); + + // If parent is null we are just being called to retrieve the print settings + // from the printer in the parent for print preview. + TabChild* pBrowser = nullptr; + if (parent) { + // Get the TabChild for this nsIDOMWindow, which we can then pass up to + // the parent. + nsCOMPtr<nsPIDOMWindowOuter> pwin = nsPIDOMWindowOuter::From(parent); + NS_ENSURE_STATE(pwin); + nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell(); + NS_ENSURE_STATE(docShell); + + nsCOMPtr<nsITabChild> tabchild = docShell->GetTabChild(); + NS_ENSURE_STATE(tabchild); + + pBrowser = static_cast<TabChild*>(tabchild.get()); + } + + // Next, serialize the nsIWebBrowserPrint and nsIPrintSettings we were given. + nsresult rv = NS_OK; + nsCOMPtr<nsIPrintSettingsService> printSettingsSvc = + do_GetService("@mozilla.org/gfx/printsettings-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + PrintData inSettings; + rv = printSettingsSvc->SerializeToPrintData(printSettings, webBrowserPrint, + &inSettings); + NS_ENSURE_SUCCESS(rv, rv); + + // Now, the waiting game. The parent process should be showing + // the printing dialog soon. In the meantime, we need to spin a + // nested event loop while we wait for the results of the dialog + // to be returned to us. + + RefPtr<PrintSettingsDialogChild> dialog = new PrintSettingsDialogChild(); + SendPPrintSettingsDialogConstructor(dialog); + + mozilla::Unused << SendShowPrintDialog(dialog, pBrowser, inSettings); + + while(!dialog->returned()) { + NS_ProcessNextEvent(nullptr, true); + } + + rv = dialog->result(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = printSettingsSvc->DeserializeToPrintSettings(dialog->data(), + printSettings); + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingProxy::ShowProgress(mozIDOMWindowProxy* parent, + nsIWebBrowserPrint* webBrowserPrint, // ok to be null + nsIPrintSettings* printSettings, // ok to be null + nsIObserver* openDialogObserver, // ok to be null + bool isForPrinting, + nsIWebProgressListener** webProgressListener, + nsIPrintProgressParams** printProgressParams, + bool* notifyOnOpen) +{ + NS_ENSURE_ARG(parent); + NS_ENSURE_ARG(webProgressListener); + NS_ENSURE_ARG(printProgressParams); + NS_ENSURE_ARG(notifyOnOpen); + + // Get the TabChild for this nsIDOMWindow, which we can then pass up to + // the parent. + nsCOMPtr<nsPIDOMWindowOuter> pwin = nsPIDOMWindowOuter::From(parent); + NS_ENSURE_STATE(pwin); + nsCOMPtr<nsIDocShell> docShell = pwin->GetDocShell(); + NS_ENSURE_STATE(docShell); + nsCOMPtr<nsITabChild> tabchild = docShell->GetTabChild(); + TabChild* pBrowser = static_cast<TabChild*>(tabchild.get()); + + RefPtr<PrintProgressDialogChild> dialogChild = + new PrintProgressDialogChild(openDialogObserver); + + SendPPrintProgressDialogConstructor(dialogChild); + + // Get the RemotePrintJob if we have one available. + RefPtr<mozilla::layout::RemotePrintJobChild> remotePrintJob; + if (printSettings) { + nsCOMPtr<nsIPrintSession> printSession; + nsresult rv = printSettings->GetPrintSession(getter_AddRefs(printSession)); + if (NS_SUCCEEDED(rv) && printSession) { + printSession->GetRemotePrintJob(getter_AddRefs(remotePrintJob)); + } + } + + nsresult rv = NS_OK; + mozilla::Unused << SendShowProgress(pBrowser, dialogChild, remotePrintJob, + isForPrinting, notifyOnOpen, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + // If we have a RemotePrintJob that will be being used as a more general + // forwarder for print progress listeners. Once we always have one we can + // remove the interface from PrintProgressDialogChild. + if (!remotePrintJob) { + NS_ADDREF(*webProgressListener = dialogChild); + } + NS_ADDREF(*printProgressParams = dialogChild); + + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingProxy::ShowPageSetup(mozIDOMWindowProxy *parent, + nsIPrintSettings *printSettings, + nsIObserver *aObs) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPrintingProxy::ShowPrinterProperties(mozIDOMWindowProxy *parent, + const char16_t *printerName, + nsIPrintSettings *printSettings) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +nsPrintingProxy::SavePrintSettings(nsIPrintSettings* aPS, + bool aUsePrinterNamePrefix, + uint32_t aFlags) +{ + nsresult rv; + nsCOMPtr<nsIPrintSettingsService> printSettingsSvc = + do_GetService("@mozilla.org/gfx/printsettings-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + PrintData settings; + rv = printSettingsSvc->SerializeToPrintData(aPS, nullptr, &settings); + NS_ENSURE_SUCCESS(rv, rv); + + Unused << SendSavePrintSettings(settings, aUsePrinterNamePrefix, aFlags, + &rv); + return rv; +} + +PPrintProgressDialogChild* +nsPrintingProxy::AllocPPrintProgressDialogChild() +{ + // The parent process will never initiate the PPrintProgressDialog + // protocol connection, so no need to provide an allocator here. + NS_NOTREACHED("Allocator for PPrintProgressDialogChild should not be " + "called on nsPrintingProxy."); + return nullptr; +} + +bool +nsPrintingProxy::DeallocPPrintProgressDialogChild(PPrintProgressDialogChild* aActor) +{ + // The PrintProgressDialogChild implements refcounting, and + // will take itself out. + return true; +} + +PPrintSettingsDialogChild* +nsPrintingProxy::AllocPPrintSettingsDialogChild() +{ + // The parent process will never initiate the PPrintSettingsDialog + // protocol connection, so no need to provide an allocator here. + NS_NOTREACHED("Allocator for PPrintSettingsDialogChild should not be " + "called on nsPrintingProxy."); + return nullptr; +} + +bool +nsPrintingProxy::DeallocPPrintSettingsDialogChild(PPrintSettingsDialogChild* aActor) +{ + // The PrintSettingsDialogChild implements refcounting, and + // will take itself out. + return true; +} + +PRemotePrintJobChild* +nsPrintingProxy::AllocPRemotePrintJobChild() +{ + RefPtr<RemotePrintJobChild> remotePrintJob = new RemotePrintJobChild(); + return remotePrintJob.forget().take(); +} + +bool +nsPrintingProxy::DeallocPRemotePrintJobChild(PRemotePrintJobChild* aDoomed) +{ + RemotePrintJobChild* remotePrintJob = static_cast<RemotePrintJobChild*>(aDoomed); + NS_RELEASE(remotePrintJob); + return true; +} diff --git a/embedding/components/printingui/ipc/nsPrintingProxy.h b/embedding/components/printingui/ipc/nsPrintingProxy.h new file mode 100644 index 000000000..6b4d9cf5a --- /dev/null +++ b/embedding/components/printingui/ipc/nsPrintingProxy.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsPrintingProxy_h +#define __nsPrintingProxy_h + +#include "nsIPrintingPromptService.h" +#include "mozilla/embedding/PPrintingChild.h" + +namespace mozilla { +namespace layout { +class PRemotePrintJobChild; +} +} + +class nsPrintingProxy: public nsIPrintingPromptService, + public mozilla::embedding::PPrintingChild +{ + virtual ~nsPrintingProxy(); + +public: + nsPrintingProxy(); + + static already_AddRefed<nsPrintingProxy> GetInstance(); + + nsresult Init(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIPRINTINGPROMPTSERVICE + + nsresult SavePrintSettings(nsIPrintSettings* aPS, + bool aUsePrinterNamePrefix, + uint32_t aFlags); + + virtual PPrintProgressDialogChild* + AllocPPrintProgressDialogChild() override; + + virtual bool + DeallocPPrintProgressDialogChild(PPrintProgressDialogChild* aActor) override; + + virtual PPrintSettingsDialogChild* + AllocPPrintSettingsDialogChild() override; + + virtual bool + DeallocPPrintSettingsDialogChild(PPrintSettingsDialogChild* aActor) override; + + virtual PRemotePrintJobChild* + AllocPRemotePrintJobChild() override; + + virtual bool + DeallocPRemotePrintJobChild(PRemotePrintJobChild* aActor) override; +}; + +#endif + diff --git a/embedding/components/printingui/mac/moz.build b/embedding/components/printingui/mac/moz.build new file mode 100644 index 000000000..aa3047c53 --- /dev/null +++ b/embedding/components/printingui/mac/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + 'nsPrintProgress.cpp', + 'nsPrintProgressParams.cpp', +] + +SOURCES += [ + 'nsPrintingPromptServiceX.mm', +] + +FINAL_LIBRARY = 'xul' diff --git a/embedding/components/printingui/mac/nsPrintProgress.cpp b/embedding/components/printingui/mac/nsPrintProgress.cpp new file mode 100644 index 000000000..0ae0a63d9 --- /dev/null +++ b/embedding/components/printingui/mac/nsPrintProgress.cpp @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPrintProgress.h" + +#include "nsIBaseWindow.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIComponentManager.h" +#include "nsPIDOMWindow.h" + +NS_IMPL_ADDREF(nsPrintProgress) +NS_IMPL_RELEASE(nsPrintProgress) + +NS_INTERFACE_MAP_BEGIN(nsPrintProgress) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrintStatusFeedback) + NS_INTERFACE_MAP_ENTRY(nsIPrintProgress) + NS_INTERFACE_MAP_ENTRY(nsIPrintStatusFeedback) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) +NS_INTERFACE_MAP_END_THREADSAFE + + +nsPrintProgress::nsPrintProgress() +{ + m_closeProgress = false; + m_processCanceled = false; + m_pendingStateFlags = -1; + m_pendingStateValue = NS_OK; +} + +nsPrintProgress::~nsPrintProgress() +{ + (void)ReleaseListeners(); +} + +NS_IMETHODIMP nsPrintProgress::OpenProgressDialog(mozIDOMWindowProxy *parent, + const char *dialogURL, + nsISupports *parameters, + nsIObserver *openDialogObserver, + bool *notifyOnOpen) +{ + MOZ_ASSERT_UNREACHABLE("The nsPrintingPromptService::ShowProgress " + "implementation for OS X returns " + "NS_ERROR_NOT_IMPLEMENTED, so we should never get " + "here."); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::CloseProgressDialog(bool forceClose) +{ + MOZ_ASSERT_UNREACHABLE("The nsPrintingPromptService::ShowProgress " + "implementation for OS X returns " + "NS_ERROR_NOT_IMPLEMENTED, so we should never get " + "here."); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::GetPrompter(nsIPrompt **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nullptr; + + if (! m_closeProgress && m_dialog) { + nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(m_dialog); + MOZ_ASSERT(window); + return window->GetPrompter(_retval); + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsPrintProgress::GetProcessCanceledByUser(bool *aProcessCanceledByUser) +{ + NS_ENSURE_ARG_POINTER(aProcessCanceledByUser); + *aProcessCanceledByUser = m_processCanceled; + return NS_OK; +} +NS_IMETHODIMP nsPrintProgress::SetProcessCanceledByUser(bool aProcessCanceledByUser) +{ + m_processCanceled = aProcessCanceledByUser; + OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_OK); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::RegisterListener(nsIWebProgressListener * listener) +{ + if (!listener) //Nothing to do with a null listener! + return NS_OK; + + m_listenerList.AppendObject(listener); + if (m_closeProgress || m_processCanceled) + listener->OnStateChange(nullptr, nullptr, + nsIWebProgressListener::STATE_STOP, NS_OK); + else + { + listener->OnStatusChange(nullptr, nullptr, NS_OK, m_pendingStatus.get()); + if (m_pendingStateFlags != -1) + listener->OnStateChange(nullptr, nullptr, m_pendingStateFlags, m_pendingStateValue); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::UnregisterListener(nsIWebProgressListener *listener) +{ + if (listener) + m_listenerList.RemoveObject(listener); + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::DoneIniting() +{ + if (m_observer) { + m_observer->Observe(nullptr, nullptr, nullptr); + } + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus) +{ + m_pendingStateFlags = aStateFlags; + m_pendingStateValue = aStatus; + + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) +{ + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags) +{ + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage) +{ + if (aMessage && *aMessage) + m_pendingStatus = aMessage; + + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state) +{ + return NS_OK; +} + +nsresult nsPrintProgress::ReleaseListeners() +{ + m_listenerList.Clear(); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::ShowStatusString(const char16_t *status) +{ + return OnStatusChange(nullptr, nullptr, NS_OK, status); +} + +NS_IMETHODIMP nsPrintProgress::StartMeteors() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::StopMeteors() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::ShowProgress(int32_t percent) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::SetDocShell(nsIDocShell *shell, + mozIDOMWindowProxy *window) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::CloseWindow() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + diff --git a/embedding/components/printingui/mac/nsPrintProgress.h b/embedding/components/printingui/mac/nsPrintProgress.h new file mode 100644 index 000000000..938558c3e --- /dev/null +++ b/embedding/components/printingui/mac/nsPrintProgress.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsPrintProgress_h +#define __nsPrintProgress_h + +#include "nsIPrintProgress.h" + +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsIPrintStatusFeedback.h" +#include "nsIObserver.h" +#include "nsString.h" + +class nsPrintProgress : public nsIPrintProgress, public nsIPrintStatusFeedback +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPRINTPROGRESS + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_NSIPRINTSTATUSFEEDBACK + + nsPrintProgress(); + +protected: + virtual ~nsPrintProgress(); + +private: + nsresult ReleaseListeners(); + + bool m_closeProgress; + bool m_processCanceled; + nsString m_pendingStatus; + int32_t m_pendingStateFlags; + nsresult m_pendingStateValue; + // XXX This member is read-only. + nsCOMPtr<mozIDOMWindowProxy> m_dialog; + nsCOMArray<nsIWebProgressListener> m_listenerList; + nsCOMPtr<nsIObserver> m_observer; +}; + +#endif diff --git a/embedding/components/printingui/mac/nsPrintProgressParams.cpp b/embedding/components/printingui/mac/nsPrintProgressParams.cpp new file mode 100644 index 000000000..eba86b298 --- /dev/null +++ b/embedding/components/printingui/mac/nsPrintProgressParams.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPrintProgressParams.h" +#include "nsReadableUtils.h" + + +NS_IMPL_ISUPPORTS(nsPrintProgressParams, nsIPrintProgressParams) + +nsPrintProgressParams::nsPrintProgressParams() +{ +} + +nsPrintProgressParams::~nsPrintProgressParams() +{ +} + +NS_IMETHODIMP nsPrintProgressParams::GetDocTitle(char16_t * *aDocTitle) +{ + NS_ENSURE_ARG(aDocTitle); + + *aDocTitle = ToNewUnicode(mDocTitle); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::SetDocTitle(const char16_t * aDocTitle) +{ + mDocTitle = aDocTitle; + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::GetDocURL(char16_t * *aDocURL) +{ + NS_ENSURE_ARG(aDocURL); + + *aDocURL = ToNewUnicode(mDocURL); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::SetDocURL(const char16_t * aDocURL) +{ + mDocURL = aDocURL; + return NS_OK; +} + diff --git a/embedding/components/printingui/mac/nsPrintProgressParams.h b/embedding/components/printingui/mac/nsPrintProgressParams.h new file mode 100644 index 000000000..65c00d98f --- /dev/null +++ b/embedding/components/printingui/mac/nsPrintProgressParams.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsPrintProgressParams_h +#define __nsPrintProgressParams_h + +#include "nsIPrintProgressParams.h" +#include "nsString.h" + +class nsPrintProgressParams : public nsIPrintProgressParams +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRINTPROGRESSPARAMS + + nsPrintProgressParams(); + +protected: + virtual ~nsPrintProgressParams(); + +private: + nsString mDocTitle; + nsString mDocURL; +}; + +#endif diff --git a/embedding/components/printingui/mac/nsPrintingPromptService.h b/embedding/components/printingui/mac/nsPrintingPromptService.h new file mode 100644 index 000000000..0334db223 --- /dev/null +++ b/embedding/components/printingui/mac/nsPrintingPromptService.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsPrintingPromptService_h +#define __nsPrintingPromptService_h + +// {E042570C-62DE-4bb6-A6E0-798E3C07B4DF} +#define NS_PRINTINGPROMPTSERVICE_CID \ + {0xe042570c, 0x62de, 0x4bb6, { 0xa6, 0xe0, 0x79, 0x8e, 0x3c, 0x7, 0xb4, 0xdf}} +#define NS_PRINTINGPROMPTSERVICE_CONTRACTID \ + "@mozilla.org/embedcomp/printingprompt-service;1" + +#include "nsCOMPtr.h" +#include "nsIPrintingPromptService.h" +#include "nsPIPromptService.h" +#include "nsIWindowWatcher.h" + +// Printing Progress Includes +#include "nsPrintProgress.h" +#include "nsIWebProgressListener.h" + +class nsPrintingPromptService: public nsIPrintingPromptService, + public nsIWebProgressListener +{ +public: + nsPrintingPromptService(); + + nsresult Init(); + + NS_DECL_NSIPRINTINGPROMPTSERVICE + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_ISUPPORTS + +protected: + virtual ~nsPrintingPromptService(); + +private: + nsCOMPtr<nsIPrintProgress> mPrintProgress; +}; + +#endif diff --git a/embedding/components/printingui/mac/nsPrintingPromptServiceX.mm b/embedding/components/printingui/mac/nsPrintingPromptServiceX.mm new file mode 100644 index 000000000..3b956100b --- /dev/null +++ b/embedding/components/printingui/mac/nsPrintingPromptServiceX.mm @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPrintingPromptService.h" + +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" +#include "nsObjCExceptions.h" + +#include "nsIPrintingPromptService.h" +#include "nsIFactory.h" +#include "nsIPrintDialogService.h" +#include "nsPIDOMWindow.h" + +//***************************************************************************** +// nsPrintingPromptService +//***************************************************************************** + +NS_IMPL_ISUPPORTS(nsPrintingPromptService, nsIPrintingPromptService, nsIWebProgressListener) + +nsPrintingPromptService::nsPrintingPromptService() +{ +} + +nsPrintingPromptService::~nsPrintingPromptService() +{ +} + +nsresult nsPrintingPromptService::Init() +{ + return NS_OK; +} + +//***************************************************************************** +// nsPrintingPromptService::nsIPrintingPromptService +//***************************************************************************** + +NS_IMETHODIMP +nsPrintingPromptService::ShowPrintDialog(mozIDOMWindowProxy *parent, nsIWebBrowserPrint *webBrowserPrint, nsIPrintSettings *printSettings) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + nsCOMPtr<nsIPrintDialogService> dlgPrint(do_GetService( + NS_PRINTDIALOGSERVICE_CONTRACTID)); + if (dlgPrint) { + return dlgPrint->Show(nsPIDOMWindowOuter::From(parent), printSettings, + webBrowserPrint); + } + + return NS_ERROR_FAILURE; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowProgress(mozIDOMWindowProxy* parent, + nsIWebBrowserPrint* webBrowserPrint, // ok to be null + nsIPrintSettings* printSettings, // ok to be null + nsIObserver* openDialogObserver, // ok to be null + bool isForPrinting, + nsIWebProgressListener** webProgressListener, + nsIPrintProgressParams** printProgressParams, + bool* notifyOnOpen) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowPageSetup(mozIDOMWindowProxy *parent, nsIPrintSettings *printSettings, nsIObserver *aObs) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + nsCOMPtr<nsIPrintDialogService> dlgPrint(do_GetService( + NS_PRINTDIALOGSERVICE_CONTRACTID)); + if (dlgPrint) { + return dlgPrint->ShowPageSetup(nsPIDOMWindowOuter::From(parent), printSettings); + } + + return NS_ERROR_FAILURE; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowPrinterProperties(mozIDOMWindowProxy *parent, const char16_t *printerName, nsIPrintSettings *printSettings) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +//***************************************************************************** +// nsPrintingPromptService::nsIWebProgressListener +//***************************************************************************** + +NS_IMETHODIMP +nsPrintingPromptService::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus) +{ + return NS_OK; +} + +/* void onProgressChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long aCurSelfProgress, in long aMaxSelfProgress, in long aCurTotalProgress, in long aMaxTotalProgress); */ +NS_IMETHODIMP +nsPrintingPromptService::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) +{ + return NS_OK; +} + +/* void onLocationChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsIURI location, in unsigned long aFlags); */ +NS_IMETHODIMP +nsPrintingPromptService::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags) +{ + return NS_OK; +} + +/* void onStatusChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsresult aStatus, in wstring aMessage); */ +NS_IMETHODIMP +nsPrintingPromptService::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage) +{ + return NS_OK; +} + +/* void onSecurityChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long state); */ +NS_IMETHODIMP +nsPrintingPromptService::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state) +{ + return NS_OK; +} diff --git a/embedding/components/printingui/moz.build b/embedding/components/printingui/moz.build new file mode 100644 index 000000000..ea9ece413 --- /dev/null +++ b/embedding/components/printingui/moz.build @@ -0,0 +1,17 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +toolkit = CONFIG['MOZ_WIDGET_TOOLKIT'] + +DIRS += ['ipc'] + +if CONFIG['NS_PRINTING']: + if toolkit == 'windows': + DIRS += ['win'] + elif toolkit == 'cocoa': + DIRS += ['mac'] + elif CONFIG['MOZ_PDF_PRINTING']: + DIRS += ['unixshared'] diff --git a/embedding/components/printingui/unixshared/moz.build b/embedding/components/printingui/unixshared/moz.build new file mode 100644 index 000000000..b204f402f --- /dev/null +++ b/embedding/components/printingui/unixshared/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + 'nsPrintingPromptService.cpp', + 'nsPrintProgress.cpp', + 'nsPrintProgressParams.cpp', +] + +FINAL_LIBRARY = 'xul' diff --git a/embedding/components/printingui/unixshared/nsPrintProgress.cpp b/embedding/components/printingui/unixshared/nsPrintProgress.cpp new file mode 100644 index 000000000..1a871c008 --- /dev/null +++ b/embedding/components/printingui/unixshared/nsPrintProgress.cpp @@ -0,0 +1,267 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPrintProgress.h" + +#include "nsArray.h" +#include "nsIBaseWindow.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIXULWindow.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIComponentManager.h" +#include "nsPIDOMWindow.h" + + +NS_IMPL_ADDREF(nsPrintProgress) +NS_IMPL_RELEASE(nsPrintProgress) + +NS_INTERFACE_MAP_BEGIN(nsPrintProgress) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrintStatusFeedback) + NS_INTERFACE_MAP_ENTRY(nsIPrintProgress) + NS_INTERFACE_MAP_ENTRY(nsIPrintStatusFeedback) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) +NS_INTERFACE_MAP_END_THREADSAFE + + +nsPrintProgress::nsPrintProgress(nsIPrintSettings* aPrintSettings) +{ + m_closeProgress = false; + m_processCanceled = false; + m_pendingStateFlags = -1; + m_pendingStateValue = NS_OK; + m_PrintSetting = aPrintSettings; +} + +nsPrintProgress::~nsPrintProgress() +{ + (void)ReleaseListeners(); +} + +NS_IMETHODIMP nsPrintProgress::OpenProgressDialog(mozIDOMWindowProxy *parent, + const char *dialogURL, + nsISupports *parameters, + nsIObserver *openDialogObserver, + bool *notifyOnOpen) +{ + *notifyOnOpen = true; + m_observer = openDialogObserver; + nsresult rv = NS_ERROR_FAILURE; + + if (m_dialog) + return NS_ERROR_ALREADY_INITIALIZED; + + if (!dialogURL || !*dialogURL) + return NS_ERROR_INVALID_ARG; + + if (parent) + { + // Set up window.arguments[0]... + nsCOMPtr<nsIMutableArray> array = nsArray::Create(); + + nsCOMPtr<nsISupportsInterfacePointer> ifptr = + do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + ifptr->SetData(static_cast<nsIPrintProgress*>(this)); + ifptr->SetDataIID(&NS_GET_IID(nsIPrintProgress)); + + array->AppendElement(ifptr, /*weak =*/ false); + + array->AppendElement(parameters, /*weak =*/ false); + + // We will set the opener of the dialog to be the nsIDOMWindow for the + // browser XUL window itself, as opposed to the content. That way, the + // progress window has access to the opener. + auto* pParentWindow = nsPIDOMWindowOuter::From(parent); + nsCOMPtr<nsIDocShell> docShell = pParentWindow->GetDocShell(); + NS_ENSURE_STATE(docShell); + + nsCOMPtr<nsIDocShellTreeOwner> owner; + docShell->GetTreeOwner(getter_AddRefs(owner)); + + nsCOMPtr<nsIXULWindow> ownerXULWindow = do_GetInterface(owner); + nsCOMPtr<mozIDOMWindowProxy> ownerWindow = do_GetInterface(ownerXULWindow); + NS_ENSURE_STATE(ownerWindow); + + nsCOMPtr<nsPIDOMWindowOuter> piOwnerWindow = nsPIDOMWindowOuter::From(ownerWindow); + + // Open the dialog. + nsCOMPtr<nsPIDOMWindowOuter> newWindow; + + rv = piOwnerWindow->OpenDialog(NS_ConvertASCIItoUTF16(dialogURL), + NS_LITERAL_STRING("_blank"), + NS_LITERAL_STRING("chrome,titlebar,dependent,centerscreen"), + array, getter_AddRefs(newWindow)); + } + + return rv; +} + +NS_IMETHODIMP nsPrintProgress::CloseProgressDialog(bool forceClose) +{ + m_closeProgress = true; + // XXX Invalid cast of bool to nsresult (bug 778106) + return OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, + (nsresult)forceClose); +} + +NS_IMETHODIMP nsPrintProgress::GetPrompter(nsIPrompt **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nullptr; + + if (! m_closeProgress && m_dialog) { + nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(m_dialog); + MOZ_ASSERT(window); + return window->GetPrompter(_retval); + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsPrintProgress::GetProcessCanceledByUser(bool *aProcessCanceledByUser) +{ + NS_ENSURE_ARG_POINTER(aProcessCanceledByUser); + *aProcessCanceledByUser = m_processCanceled; + return NS_OK; +} +NS_IMETHODIMP nsPrintProgress::SetProcessCanceledByUser(bool aProcessCanceledByUser) +{ + if(m_PrintSetting) + m_PrintSetting->SetIsCancelled(true); + m_processCanceled = aProcessCanceledByUser; + OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_OK); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::RegisterListener(nsIWebProgressListener * listener) +{ + if (!listener) //Nothing to do with a null listener! + return NS_OK; + + m_listenerList.AppendObject(listener); + if (m_closeProgress || m_processCanceled) + listener->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_OK); + else + { + listener->OnStatusChange(nullptr, nullptr, NS_OK, m_pendingStatus.get()); + if (m_pendingStateFlags != -1) + listener->OnStateChange(nullptr, nullptr, m_pendingStateFlags, m_pendingStateValue); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::UnregisterListener(nsIWebProgressListener *listener) +{ + if (listener) + m_listenerList.RemoveObject(listener); + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::DoneIniting() +{ + if (m_observer) { + m_observer->Observe(nullptr, nullptr, nullptr); + } + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus) +{ + m_pendingStateFlags = aStateFlags; + m_pendingStateValue = aStatus; + + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) +{ + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage) +{ + if (aMessage && *aMessage) + m_pendingStatus = aMessage; + + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state) +{ + return NS_OK; +} + +nsresult nsPrintProgress::ReleaseListeners() +{ + m_listenerList.Clear(); + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::ShowStatusString(const char16_t *status) +{ + return OnStatusChange(nullptr, nullptr, NS_OK, status); +} + +NS_IMETHODIMP nsPrintProgress::StartMeteors() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::StopMeteors() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::ShowProgress(int32_t percent) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::SetDocShell(nsIDocShell *shell, mozIDOMWindowProxy *window) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::CloseWindow() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + diff --git a/embedding/components/printingui/unixshared/nsPrintProgress.h b/embedding/components/printingui/unixshared/nsPrintProgress.h new file mode 100644 index 000000000..5b60151c0 --- /dev/null +++ b/embedding/components/printingui/unixshared/nsPrintProgress.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsPrintProgress_h +#define __nsPrintProgress_h + +#include "nsIPrintProgress.h" +#include "nsIPrintingPromptService.h" + +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsIDOMWindow.h" +#include "nsIPrintStatusFeedback.h" +#include "nsIObserver.h" +#include "nsString.h" + +class nsPrintProgress : public nsIPrintProgress, public nsIPrintStatusFeedback +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPRINTPROGRESS + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_NSIPRINTSTATUSFEEDBACK + + explicit nsPrintProgress(nsIPrintSettings* aPrintSettings); + +protected: + virtual ~nsPrintProgress(); + +private: + nsresult ReleaseListeners(); + + bool m_closeProgress; + bool m_processCanceled; + nsString m_pendingStatus; + int32_t m_pendingStateFlags; + nsresult m_pendingStateValue; + nsCOMPtr<nsIDOMWindow> m_dialog; + nsCOMArray<nsIWebProgressListener> m_listenerList; + nsCOMPtr<nsIObserver> m_observer; + nsCOMPtr<nsIPrintSettings> m_PrintSetting; +}; + +#endif diff --git a/embedding/components/printingui/unixshared/nsPrintProgressParams.cpp b/embedding/components/printingui/unixshared/nsPrintProgressParams.cpp new file mode 100644 index 000000000..eba86b298 --- /dev/null +++ b/embedding/components/printingui/unixshared/nsPrintProgressParams.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPrintProgressParams.h" +#include "nsReadableUtils.h" + + +NS_IMPL_ISUPPORTS(nsPrintProgressParams, nsIPrintProgressParams) + +nsPrintProgressParams::nsPrintProgressParams() +{ +} + +nsPrintProgressParams::~nsPrintProgressParams() +{ +} + +NS_IMETHODIMP nsPrintProgressParams::GetDocTitle(char16_t * *aDocTitle) +{ + NS_ENSURE_ARG(aDocTitle); + + *aDocTitle = ToNewUnicode(mDocTitle); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::SetDocTitle(const char16_t * aDocTitle) +{ + mDocTitle = aDocTitle; + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::GetDocURL(char16_t * *aDocURL) +{ + NS_ENSURE_ARG(aDocURL); + + *aDocURL = ToNewUnicode(mDocURL); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::SetDocURL(const char16_t * aDocURL) +{ + mDocURL = aDocURL; + return NS_OK; +} + diff --git a/embedding/components/printingui/unixshared/nsPrintProgressParams.h b/embedding/components/printingui/unixshared/nsPrintProgressParams.h new file mode 100644 index 000000000..839d0e08e --- /dev/null +++ b/embedding/components/printingui/unixshared/nsPrintProgressParams.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsPrintProgressParams_h +#define __nsPrintProgressParams_h + +#include "nsIPrintProgressParams.h" +#include "nsString.h" + +class nsPrintProgressParams : public nsIPrintProgressParams +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRINTPROGRESSPARAMS + + nsPrintProgressParams(); + +protected: + virtual ~nsPrintProgressParams(); + +private: + nsString mDocTitle; + nsString mDocURL; +}; + +#endif diff --git a/embedding/components/printingui/unixshared/nsPrintingPromptService.cpp b/embedding/components/printingui/unixshared/nsPrintingPromptService.cpp new file mode 100644 index 000000000..fd10a4742 --- /dev/null +++ b/embedding/components/printingui/unixshared/nsPrintingPromptService.cpp @@ -0,0 +1,298 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPrintingPromptService.h" + +#include "nsArray.h" +#include "nsIComponentManager.h" +#include "nsIDialogParamBlock.h" +#include "nsIDOMWindow.h" +#include "nsIServiceManager.h" +#include "nsISupportsUtils.h" +#include "nsString.h" +#include "nsIPrintDialogService.h" + +// Printing Progress Includes +#include "nsPrintProgress.h" +#include "nsPrintProgressParams.h" + +static const char *kPrintDialogURL = "chrome://global/content/printdialog.xul"; +static const char *kPrintProgressDialogURL = "chrome://global/content/printProgress.xul"; +static const char *kPrtPrvProgressDialogURL = "chrome://global/content/printPreviewProgress.xul"; +static const char *kPageSetupDialogURL = "chrome://global/content/printPageSetup.xul"; +static const char *kPrinterPropertiesURL = "chrome://global/content/printjoboptions.xul"; + +/**************************************************************** + ************************* ParamBlock *************************** + ****************************************************************/ + +class ParamBlock { + +public: + ParamBlock() + { + mBlock = 0; + } + ~ParamBlock() + { + NS_IF_RELEASE(mBlock); + } + nsresult Init() { + return CallCreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID, &mBlock); + } + nsIDialogParamBlock * operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { return mBlock; } + operator nsIDialogParamBlock * () const { return mBlock; } + +private: + nsIDialogParamBlock *mBlock; +}; + +/**************************************************************** + ***************** nsPrintingPromptService ********************** + ****************************************************************/ + +NS_IMPL_ISUPPORTS(nsPrintingPromptService, nsIPrintingPromptService, nsIWebProgressListener) + +nsPrintingPromptService::nsPrintingPromptService() +{ +} + +nsPrintingPromptService::~nsPrintingPromptService() +{ +} + +nsresult +nsPrintingPromptService::Init() +{ + nsresult rv; + mWatcher = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + return rv; +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowPrintDialog(mozIDOMWindowProxy *parent, + nsIWebBrowserPrint *webBrowserPrint, + nsIPrintSettings *printSettings) +{ + NS_ENSURE_ARG(webBrowserPrint); + NS_ENSURE_ARG(printSettings); + + // Try to access a component dialog + nsCOMPtr<nsIPrintDialogService> dlgPrint(do_GetService( + NS_PRINTDIALOGSERVICE_CONTRACTID)); + if (dlgPrint) + return dlgPrint->Show(nsPIDOMWindowOuter::From(parent), + printSettings, webBrowserPrint); + + // Show the built-in dialog instead + ParamBlock block; + nsresult rv = block.Init(); + if (NS_FAILED(rv)) + return rv; + + block->SetInt(0, 0); + return DoDialog(parent, block, webBrowserPrint, printSettings, kPrintDialogURL); +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowProgress(mozIDOMWindowProxy* parent, + nsIWebBrowserPrint* webBrowserPrint, // ok to be null + nsIPrintSettings* printSettings, // ok to be null + nsIObserver* openDialogObserver, // ok to be null + bool isForPrinting, + nsIWebProgressListener** webProgressListener, + nsIPrintProgressParams** printProgressParams, + bool* notifyOnOpen) +{ + NS_ENSURE_ARG(webProgressListener); + NS_ENSURE_ARG(printProgressParams); + NS_ENSURE_ARG(notifyOnOpen); + + *notifyOnOpen = false; + + nsPrintProgress* prtProgress = new nsPrintProgress(printSettings); + mPrintProgress = prtProgress; + mWebProgressListener = prtProgress; + + nsCOMPtr<nsIPrintProgressParams> prtProgressParams = new nsPrintProgressParams(); + + nsCOMPtr<mozIDOMWindowProxy> parentWindow = parent; + + if (mWatcher && !parentWindow) { + mWatcher->GetActiveWindow(getter_AddRefs(parentWindow)); + } + + if (parentWindow) { + mPrintProgress->OpenProgressDialog(parentWindow, + isForPrinting ? kPrintProgressDialogURL : kPrtPrvProgressDialogURL, + prtProgressParams, openDialogObserver, notifyOnOpen); + } + + prtProgressParams.forget(printProgressParams); + NS_ADDREF(*webProgressListener = this); + + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowPageSetup(mozIDOMWindowProxy *parent, + nsIPrintSettings *printSettings, + nsIObserver *aObs) +{ + NS_ENSURE_ARG(printSettings); + + // Try to access a component dialog + nsCOMPtr<nsIPrintDialogService> dlgPrint(do_GetService( + NS_PRINTDIALOGSERVICE_CONTRACTID)); + if (dlgPrint) + return dlgPrint->ShowPageSetup(nsPIDOMWindowOuter::From(parent), + printSettings); + + ParamBlock block; + nsresult rv = block.Init(); + if (NS_FAILED(rv)) + return rv; + + block->SetInt(0, 0); + return DoDialog(parent, block, nullptr, printSettings, kPageSetupDialogURL); +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowPrinterProperties(mozIDOMWindowProxy *parent, + const char16_t *printerName, + nsIPrintSettings *printSettings) +{ + /* fixme: We simply ignore the |aPrinter| argument here + * We should get the supported printer attributes from the printer and + * populate the print job options dialog with these data instead of using + * the "default set" here. + * However, this requires changes on all platforms and is another big chunk + * of patches ... ;-( + */ + NS_ENSURE_ARG(printerName); + NS_ENSURE_ARG(printSettings); + + ParamBlock block; + nsresult rv = block.Init(); + if (NS_FAILED(rv)) + return rv; + + block->SetInt(0, 0); + return DoDialog(parent, block, nullptr, printSettings, kPrinterPropertiesURL); + +} + +nsresult +nsPrintingPromptService::DoDialog(mozIDOMWindowProxy *aParent, + nsIDialogParamBlock *aParamBlock, + nsIWebBrowserPrint *aWebBrowserPrint, + nsIPrintSettings* aPS, + const char *aChromeURL) +{ + NS_ENSURE_ARG(aParamBlock); + NS_ENSURE_ARG(aPS); + NS_ENSURE_ARG(aChromeURL); + + if (!mWatcher) + return NS_ERROR_FAILURE; + + // get a parent, if at all possible + // (though we'd rather this didn't fail, it's OK if it does. so there's + // no failure or null check.) + nsCOMPtr<mozIDOMWindowProxy> activeParent; + if (!aParent) + { + mWatcher->GetActiveWindow(getter_AddRefs(activeParent)); + aParent = activeParent; + } + + // create a nsIMutableArray of the parameters + // being passed to the window + nsCOMPtr<nsIMutableArray> array = nsArray::Create(); + + nsCOMPtr<nsISupports> psSupports(do_QueryInterface(aPS)); + NS_ASSERTION(psSupports, "PrintSettings must be a supports"); + array->AppendElement(psSupports, /*weak =*/ false); + + if (aWebBrowserPrint) { + nsCOMPtr<nsISupports> wbpSupports(do_QueryInterface(aWebBrowserPrint)); + NS_ASSERTION(wbpSupports, "nsIWebBrowserPrint must be a supports"); + array->AppendElement(wbpSupports, /*weak =*/ false); + } + + nsCOMPtr<nsISupports> blkSupps(do_QueryInterface(aParamBlock)); + NS_ASSERTION(blkSupps, "IOBlk must be a supports"); + array->AppendElement(blkSupps, /*weak =*/ false); + + nsCOMPtr<mozIDOMWindowProxy> dialog; + nsresult rv = mWatcher->OpenWindow(aParent, aChromeURL, "_blank", + "centerscreen,chrome,modal,titlebar", array, + getter_AddRefs(dialog)); + + // if aWebBrowserPrint is not null then we are printing + // so we want to pass back NS_ERROR_ABORT on cancel + if (NS_SUCCEEDED(rv) && aWebBrowserPrint) + { + int32_t status; + aParamBlock->GetInt(0, &status); + return status == 0?NS_ERROR_ABORT:NS_OK; + } + + return rv; +} + +////////////////////////////////////////////////////////////////////// +// nsIWebProgressListener +////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsPrintingPromptService::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus) +{ + if ((aStateFlags & STATE_STOP) && mWebProgressListener) { + mWebProgressListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); + if (mPrintProgress) { + mPrintProgress->CloseProgressDialog(true); + } + mPrintProgress = nullptr; + mWebProgressListener = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingPromptService::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) +{ + if (mWebProgressListener) { + return mWebProgressListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress); + } + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingPromptService::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags) +{ + if (mWebProgressListener) { + return mWebProgressListener->OnLocationChange(aWebProgress, aRequest, location, aFlags); + } + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingPromptService::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage) +{ + if (mWebProgressListener) { + return mWebProgressListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); + } + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingPromptService::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state) +{ + if (mWebProgressListener) { + return mWebProgressListener->OnSecurityChange(aWebProgress, aRequest, state); + } + return NS_OK; +} diff --git a/embedding/components/printingui/unixshared/nsPrintingPromptService.h b/embedding/components/printingui/unixshared/nsPrintingPromptService.h new file mode 100644 index 000000000..cc0c76929 --- /dev/null +++ b/embedding/components/printingui/unixshared/nsPrintingPromptService.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsPrintingPromptService_h +#define __nsPrintingPromptService_h + +// {E042570C-62DE-4bb6-A6E0-798E3C07B4DF} +#define NS_PRINTINGPROMPTSERVICE_CID \ + {0xe042570c, 0x62de, 0x4bb6, { 0xa6, 0xe0, 0x79, 0x8e, 0x3c, 0x7, 0xb4, 0xdf}} +#define NS_PRINTINGPROMPTSERVICE_CONTRACTID \ + "@mozilla.org/embedcomp/printingprompt-service;1" + +#include "nsCOMPtr.h" +#include "nsIPrintingPromptService.h" +#include "nsPIPromptService.h" +#include "nsIWindowWatcher.h" + +// Printing Progress Includes +#include "nsPrintProgress.h" +#include "nsPrintProgressParams.h" +#include "nsIWebProgressListener.h" + +class nsIDOMWindow; +class nsIDialogParamBlock; + +class nsPrintingPromptService: public nsIPrintingPromptService, + public nsIWebProgressListener +{ + +public: + + nsPrintingPromptService(); + + nsresult Init(); + + NS_DECL_NSIPRINTINGPROMPTSERVICE + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_ISUPPORTS + +protected: + virtual ~nsPrintingPromptService(); + +private: + nsresult DoDialog(mozIDOMWindowProxy *aParent, + nsIDialogParamBlock *aParamBlock, + nsIWebBrowserPrint *aWebBrowserPrint, + nsIPrintSettings* aPS, + const char *aChromeURL); + + nsCOMPtr<nsIWindowWatcher> mWatcher; + nsCOMPtr<nsIPrintProgress> mPrintProgress; + nsCOMPtr<nsIWebProgressListener> mWebProgressListener; +}; + +#endif + diff --git a/embedding/components/printingui/win/moz.build b/embedding/components/printingui/win/moz.build new file mode 100644 index 000000000..fe7b11b2d --- /dev/null +++ b/embedding/components/printingui/win/moz.build @@ -0,0 +1,18 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES += [ + 'nsPrintDialogUtil.cpp', + 'nsPrintingPromptService.cpp', + 'nsPrintProgress.cpp', + 'nsPrintProgressParams.cpp', +] + +EXPORTS += [ + 'nsPrintDialogUtil.h', +] + +FINAL_LIBRARY = 'xul' diff --git a/embedding/components/printingui/win/nsPrintDialogUtil.cpp b/embedding/components/printingui/win/nsPrintDialogUtil.cpp new file mode 100644 index 000000000..896c58e85 --- /dev/null +++ b/embedding/components/printingui/win/nsPrintDialogUtil.cpp @@ -0,0 +1,854 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* ------------------------------------------------------------------- +To Build This: + + You need to add this to the the makefile.win in mozilla/dom/base: + + .\$(OBJDIR)\nsFlyOwnPrintDialog.obj \ + + + And this to the makefile.win in mozilla/content/build: + +WIN_LIBS= \ + winspool.lib \ + comctl32.lib \ + comdlg32.lib + +---------------------------------------------------------------------- */ + +#include "plstr.h" +#include <windows.h> +#include <tchar.h> + +#include <unknwn.h> +#include <commdlg.h> + +#include "nsIWebBrowserPrint.h" +#include "nsString.h" +#include "nsIServiceManager.h" +#include "nsReadableUtils.h" +#include "nsIPrintSettings.h" +#include "nsIPrintSettingsWin.h" +#include "nsIPrinterEnumerator.h" + +#include "nsRect.h" + +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" + +#include "nsCRT.h" +#include "prenv.h" /* for PR_GetEnv */ + +#include <windows.h> +#include <winspool.h> + +// For Localization +#include "nsIStringBundle.h" + +// For NS_CopyUnicodeToNative +#include "nsNativeCharsetUtils.h" + +// This is for extending the dialog +#include <dlgs.h> + +#include "nsWindowsHelpers.h" +#include "WinUtils.h" + +// Default labels for the radio buttons +static const char* kAsLaidOutOnScreenStr = "As &laid out on the screen"; +static const char* kTheSelectedFrameStr = "The selected &frame"; +static const char* kEachFrameSeparately = "&Each frame separately"; + + +//----------------------------------------------- +// Global Data +//----------------------------------------------- +// Identifies which new radio btn was cliked on +static UINT gFrameSelectedRadioBtn = 0; + +// Indicates whether the native print dialog was successfully extended +static bool gDialogWasExtended = false; + +#define PRINTDLG_PROPERTIES "chrome://global/locale/printdialog.properties" + +static HWND gParentWnd = nullptr; + +//---------------------------------------------------------------------------------- +// Return localized bundle for resource strings +static nsresult +GetLocalizedBundle(const char * aPropFileName, nsIStringBundle** aStrBundle) +{ + NS_ENSURE_ARG_POINTER(aPropFileName); + NS_ENSURE_ARG_POINTER(aStrBundle); + + nsresult rv; + nsCOMPtr<nsIStringBundle> bundle; + + + // Create bundle + nsCOMPtr<nsIStringBundleService> stringService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && stringService) { + rv = stringService->CreateBundle(aPropFileName, aStrBundle); + } + + return rv; +} + +//-------------------------------------------------------- +// Return localized string +static nsresult +GetLocalizedString(nsIStringBundle* aStrBundle, const char* aKey, nsString& oVal) +{ + NS_ENSURE_ARG_POINTER(aStrBundle); + NS_ENSURE_ARG_POINTER(aKey); + + // Determine default label from string bundle + nsXPIDLString valUni; + nsAutoString key; + key.AssignWithConversion(aKey); + nsresult rv = aStrBundle->GetStringFromName(key.get(), getter_Copies(valUni)); + if (NS_SUCCEEDED(rv) && valUni) { + oVal.Assign(valUni); + } else { + oVal.Truncate(); + } + return rv; +} + +//-------------------------------------------------------- +// Set a multi-byte string in the control +static void SetTextOnWnd(HWND aControl, const nsString& aStr) +{ + nsAutoCString text; + if (NS_SUCCEEDED(NS_CopyUnicodeToNative(aStr, text))) { + ::SetWindowText(aControl, text.get()); + } +} + +//-------------------------------------------------------- +// Will get the control and localized string by "key" +static void SetText(HWND aParent, + UINT aId, + nsIStringBundle* aStrBundle, + const char* aKey) +{ + HWND wnd = GetDlgItem (aParent, aId); + if (!wnd) { + return; + } + nsAutoString str; + nsresult rv = GetLocalizedString(aStrBundle, aKey, str); + if (NS_SUCCEEDED(rv)) { + SetTextOnWnd(wnd, str); + } +} + +//-------------------------------------------------------- +static void SetRadio(HWND aParent, + UINT aId, + bool aIsSet, + bool isEnabled = true) +{ + HWND wnd = ::GetDlgItem (aParent, aId); + if (!wnd) { + return; + } + if (!isEnabled) { + ::EnableWindow(wnd, FALSE); + return; + } + ::EnableWindow(wnd, TRUE); + ::SendMessage(wnd, BM_SETCHECK, (WPARAM)aIsSet, (LPARAM)0); +} + +//-------------------------------------------------------- +static void SetRadioOfGroup(HWND aDlg, int aRadId) +{ + int radioIds[] = {rad4, rad5, rad6}; + int numRads = 3; + + for (int i=0;i<numRads;i++) { + HWND radWnd = ::GetDlgItem(aDlg, radioIds[i]); + if (radWnd != nullptr) { + ::SendMessage(radWnd, BM_SETCHECK, (WPARAM)(radioIds[i] == aRadId), (LPARAM)0); + } + } +} + +//-------------------------------------------------------- +typedef struct { + const char * mKeyStr; + long mKeyId; +} PropKeyInfo; + +// These are the control ids used in the dialog and +// defined by MS-Windows in commdlg.h +static PropKeyInfo gAllPropKeys[] = { + {"printFramesTitleWindows", grp3}, + {"asLaidOutWindows", rad4}, + {"selectedFrameWindows", rad5}, + {"separateFramesWindows", rad6}, + {nullptr, 0}}; + +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------------------------------------------------------- +//-------------------------------------------------------- +// Get the absolute coords of the child windows relative +// to its parent window +static void GetLocalRect(HWND aWnd, RECT& aRect, HWND aParent) +{ + ::GetWindowRect(aWnd, &aRect); + + // MapWindowPoints converts screen coordinates to client coordinates. + // It works correctly in both left-to-right and right-to-left windows. + ::MapWindowPoints(nullptr, aParent, (LPPOINT)&aRect, 2); +} + +//-------------------------------------------------------- +// Show or Hide the control +static void Show(HWND aWnd, bool bState) +{ + if (aWnd) { + ::ShowWindow(aWnd, bState?SW_SHOW:SW_HIDE); + } +} + +//-------------------------------------------------------- +// Create a child window "control" +static HWND CreateControl(LPCTSTR aType, + DWORD aStyle, + HINSTANCE aHInst, + HWND aHdlg, + int aId, + const nsAString& aStr, + const nsIntRect& aRect) +{ + nsAutoCString str; + if (NS_FAILED(NS_CopyUnicodeToNative(aStr, str))) + return nullptr; + + HWND hWnd = ::CreateWindow (aType, str.get(), + WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | aStyle, + aRect.x, aRect.y, aRect.width, aRect.height, + (HWND)aHdlg, (HMENU)(intptr_t)aId, + aHInst, nullptr); + if (hWnd == nullptr) return nullptr; + + // get the native font for the dialog and + // set it into the new control + HFONT hFont = (HFONT)::SendMessage(aHdlg, WM_GETFONT, (WPARAM)0, (LPARAM)0); + if (hFont != nullptr) { + ::SendMessage(hWnd, WM_SETFONT, (WPARAM) hFont, (LPARAM)0); + } + return hWnd; +} + +//-------------------------------------------------------- +// Create a Radio Button +static HWND CreateRadioBtn(HINSTANCE aHInst, + HWND aHdlg, + int aId, + const char* aStr, + const nsIntRect& aRect) +{ + nsString cStr; + cStr.AssignWithConversion(aStr); + return CreateControl("BUTTON", BS_RADIOBUTTON, aHInst, aHdlg, aId, cStr, aRect); +} + +//-------------------------------------------------------- +// Create a Group Box +static HWND CreateGroupBox(HINSTANCE aHInst, + HWND aHdlg, + int aId, + const nsAString& aStr, + const nsIntRect& aRect) +{ + return CreateControl("BUTTON", BS_GROUPBOX, aHInst, aHdlg, aId, aStr, aRect); +} + +//-------------------------------------------------------- +// Localizes and initializes the radio buttons and group +static void InitializeExtendedDialog(HWND hdlg, int16_t aHowToEnableFrameUI) +{ + MOZ_ASSERT(aHowToEnableFrameUI != nsIPrintSettings::kFrameEnableNone, + "should not be called"); + + // Localize the new controls in the print dialog + nsCOMPtr<nsIStringBundle> strBundle; + if (NS_SUCCEEDED(GetLocalizedBundle(PRINTDLG_PROPERTIES, getter_AddRefs(strBundle)))) { + int32_t i = 0; + while (gAllPropKeys[i].mKeyStr != nullptr) { + SetText(hdlg, gAllPropKeys[i].mKeyId, strBundle, gAllPropKeys[i].mKeyStr); + i++; + } + } + + // Set up radio buttons + if (aHowToEnableFrameUI == nsIPrintSettings::kFrameEnableAll) { + SetRadio(hdlg, rad4, false); + SetRadio(hdlg, rad5, true); + SetRadio(hdlg, rad6, false); + // set default so user doesn't have to actually press on it + gFrameSelectedRadioBtn = rad5; + + } else { // nsIPrintSettings::kFrameEnableAsIsAndEach + SetRadio(hdlg, rad4, false); + SetRadio(hdlg, rad5, false, false); + SetRadio(hdlg, rad6, true); + // set default so user doesn't have to actually press on it + gFrameSelectedRadioBtn = rad6; + } +} + + +//-------------------------------------------------------- +// Special Hook Procedure for handling the print dialog messages +static UINT CALLBACK PrintHookProc(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam) +{ + + if (uiMsg == WM_COMMAND) { + UINT id = LOWORD(wParam); + if (id == rad4 || id == rad5 || id == rad6) { + gFrameSelectedRadioBtn = id; + SetRadioOfGroup(hdlg, id); + } + + } else if (uiMsg == WM_INITDIALOG) { + PRINTDLG * printDlg = (PRINTDLG *)lParam; + if (printDlg == nullptr) return 0L; + + int16_t howToEnableFrameUI = (int16_t)printDlg->lCustData; + // don't add frame options if they would be disabled anyway + // because there are no frames + if (howToEnableFrameUI == nsIPrintSettings::kFrameEnableNone) + return TRUE; + + HINSTANCE hInst = (HINSTANCE)::GetWindowLongPtr(hdlg, GWLP_HINSTANCE); + if (hInst == nullptr) return 0L; + + // Start by getting the local rects of several of the controls + // so we can calculate where the new controls are + HWND wnd = ::GetDlgItem(hdlg, grp1); + if (wnd == nullptr) return 0L; + RECT dlgRect; + GetLocalRect(wnd, dlgRect, hdlg); + + wnd = ::GetDlgItem(hdlg, rad1); // this is the top control "All" + if (wnd == nullptr) return 0L; + RECT rad1Rect; + GetLocalRect(wnd, rad1Rect, hdlg); + + wnd = ::GetDlgItem(hdlg, rad2); // this is the bottom control "Selection" + if (wnd == nullptr) return 0L; + RECT rad2Rect; + GetLocalRect(wnd, rad2Rect, hdlg); + + wnd = ::GetDlgItem(hdlg, rad3); // this is the middle control "Pages" + if (wnd == nullptr) return 0L; + RECT rad3Rect; + GetLocalRect(wnd, rad3Rect, hdlg); + + HWND okWnd = ::GetDlgItem(hdlg, IDOK); + if (okWnd == nullptr) return 0L; + RECT okRect; + GetLocalRect(okWnd, okRect, hdlg); + + wnd = ::GetDlgItem(hdlg, grp4); // this is the "Print range" groupbox + if (wnd == nullptr) return 0L; + RECT prtRect; + GetLocalRect(wnd, prtRect, hdlg); + + + // calculate various different "gaps" for layout purposes + + int rbGap = rad3Rect.top - rad1Rect.bottom; // gap between radiobtns + int grpBotGap = dlgRect.bottom - rad2Rect.bottom; // gap from bottom rb to bottom of grpbox + int grpGap = dlgRect.top - prtRect.bottom ; // gap between group boxes + int top = dlgRect.bottom + grpGap; + int radHgt = rad1Rect.bottom - rad1Rect.top + 1; // top of new group box + int y = top+(rad1Rect.top-dlgRect.top); // starting pos of first radio + int rbWidth = dlgRect.right - rad1Rect.left - 5; // measure from rb left to the edge of the groupbox + // (5 is arbitrary) + nsIntRect rect; + + // Create and position the radio buttons + // + // If any one control cannot be created then + // hide the others and bail out + // + rect.SetRect(rad1Rect.left, y, rbWidth,radHgt); + HWND rad4Wnd = CreateRadioBtn(hInst, hdlg, rad4, kAsLaidOutOnScreenStr, rect); + if (rad4Wnd == nullptr) return 0L; + y += radHgt + rbGap; + + rect.SetRect(rad1Rect.left, y, rbWidth, radHgt); + HWND rad5Wnd = CreateRadioBtn(hInst, hdlg, rad5, kTheSelectedFrameStr, rect); + if (rad5Wnd == nullptr) { + Show(rad4Wnd, FALSE); // hide + return 0L; + } + y += radHgt + rbGap; + + rect.SetRect(rad1Rect.left, y, rbWidth, radHgt); + HWND rad6Wnd = CreateRadioBtn(hInst, hdlg, rad6, kEachFrameSeparately, rect); + if (rad6Wnd == nullptr) { + Show(rad4Wnd, FALSE); // hide + Show(rad5Wnd, FALSE); // hide + return 0L; + } + y += radHgt + grpBotGap; + + // Create and position the group box + rect.SetRect (dlgRect.left, top, dlgRect.right-dlgRect.left+1, y-top+1); + HWND grpBoxWnd = CreateGroupBox(hInst, hdlg, grp3, NS_LITERAL_STRING("Print Frame"), rect); + if (grpBoxWnd == nullptr) { + Show(rad4Wnd, FALSE); // hide + Show(rad5Wnd, FALSE); // hide + Show(rad6Wnd, FALSE); // hide + return 0L; + } + + // Here we figure out the old height of the dlg + // then figure its gap from the old grpbx to the bottom + // then size the dlg + RECT pr, cr; + ::GetWindowRect(hdlg, &pr); + ::GetClientRect(hdlg, &cr); + + int dlgHgt = (cr.bottom - cr.top) + 1; + int bottomGap = dlgHgt - okRect.bottom; + pr.bottom += (dlgRect.bottom-dlgRect.top) + grpGap + 1 - (dlgHgt-dlgRect.bottom) + bottomGap; + + ::SetWindowPos(hdlg, nullptr, pr.left, pr.top, pr.right-pr.left+1, pr.bottom-pr.top+1, + SWP_NOMOVE|SWP_NOREDRAW|SWP_NOZORDER); + + // figure out the new height of the dialog + ::GetClientRect(hdlg, &cr); + dlgHgt = (cr.bottom - cr.top) + 1; + + // Reposition the OK and Cancel btns + int okHgt = okRect.bottom - okRect.top + 1; + ::SetWindowPos(okWnd, nullptr, okRect.left, dlgHgt-bottomGap-okHgt, 0, 0, + SWP_NOSIZE|SWP_NOREDRAW|SWP_NOZORDER); + + HWND cancelWnd = ::GetDlgItem(hdlg, IDCANCEL); + if (cancelWnd == nullptr) return 0L; + + RECT cancelRect; + GetLocalRect(cancelWnd, cancelRect, hdlg); + int cancelHgt = cancelRect.bottom - cancelRect.top + 1; + ::SetWindowPos(cancelWnd, nullptr, cancelRect.left, dlgHgt-bottomGap-cancelHgt, 0, 0, + SWP_NOSIZE|SWP_NOREDRAW|SWP_NOZORDER); + + // localize and initialize the groupbox and radiobuttons + InitializeExtendedDialog(hdlg, howToEnableFrameUI); + + // Looks like we were able to extend the dialog + gDialogWasExtended = true; + return TRUE; + } + return 0L; +} + +//---------------------------------------------------------------------------------- +// Returns a Global Moveable Memory Handle to a DevMode +// from the Printer by the name of aPrintName +// +// NOTE: +// This function assumes that aPrintName has already been converted from +// unicode +// +static nsReturnRef<nsHGLOBAL> +CreateGlobalDevModeAndInit(const nsXPIDLString& aPrintName, + nsIPrintSettings* aPS) +{ + nsHPRINTER hPrinter = nullptr; + // const cast kludge for silly Win32 api's + LPWSTR printName = const_cast<wchar_t*>(static_cast<const wchar_t*>(aPrintName.get())); + BOOL status = ::OpenPrinterW(printName, &hPrinter, nullptr); + if (!status) { + return nsReturnRef<nsHGLOBAL>(); + } + + // Make sure hPrinter is closed on all paths + nsAutoPrinter autoPrinter(hPrinter); + + // Get the buffer size + LONG needed = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, nullptr, + nullptr, 0); + if (needed < 0) { + return nsReturnRef<nsHGLOBAL>(); + } + + // Allocate a buffer of the correct size. + nsAutoDevMode newDevMode((LPDEVMODEW)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, + needed)); + if (!newDevMode) { + return nsReturnRef<nsHGLOBAL>(); + } + + nsHGLOBAL hDevMode = ::GlobalAlloc(GHND, needed); + nsAutoGlobalMem globalDevMode(hDevMode); + if (!hDevMode) { + return nsReturnRef<nsHGLOBAL>(); + } + + LONG ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, newDevMode, + nullptr, DM_OUT_BUFFER); + if (ret != IDOK) { + return nsReturnRef<nsHGLOBAL>(); + } + + // Lock memory and copy contents from DEVMODE (current printer) + // to Global Memory DEVMODE + LPDEVMODEW devMode = (DEVMODEW *)::GlobalLock(hDevMode); + if (!devMode) { + return nsReturnRef<nsHGLOBAL>(); + } + + memcpy(devMode, newDevMode.get(), needed); + // Initialize values from the PrintSettings + nsCOMPtr<nsIPrintSettingsWin> psWin = do_QueryInterface(aPS); + MOZ_ASSERT(psWin); + psWin->CopyToNative(devMode); + + // Sets back the changes we made to the DevMode into the Printer Driver + ret = ::DocumentPropertiesW(gParentWnd, hPrinter, printName, devMode, devMode, + DM_IN_BUFFER | DM_OUT_BUFFER); + if (ret != IDOK) { + ::GlobalUnlock(hDevMode); + return nsReturnRef<nsHGLOBAL>(); + } + + ::GlobalUnlock(hDevMode); + + return globalDevMode.out(); +} + +//------------------------------------------------------------------ +// helper +static void GetDefaultPrinterNameFromGlobalPrinters(nsXPIDLString &printerName) +{ + nsCOMPtr<nsIPrinterEnumerator> prtEnum = do_GetService("@mozilla.org/gfx/printerenumerator;1"); + if (prtEnum) { + prtEnum->GetDefaultPrinterName(getter_Copies(printerName)); + } +} + +// Determine whether we have a completely native dialog +// or whether we cshould extend it +static bool ShouldExtendPrintDialog() +{ + nsresult rv; + nsCOMPtr<nsIPrefService> prefs = + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, true); + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv, true); + + bool result; + rv = prefBranch->GetBoolPref("print.extend_native_print_dialog", &result); + NS_ENSURE_SUCCESS(rv, true); + return result; +} + +//------------------------------------------------------------------ +// Displays the native Print Dialog +static nsresult +ShowNativePrintDialog(HWND aHWnd, + nsIPrintSettings* aPrintSettings) +{ + //NS_ENSURE_ARG_POINTER(aHWnd); + NS_ENSURE_ARG_POINTER(aPrintSettings); + + gDialogWasExtended = false; + + // Get the Print Name to be used + nsXPIDLString printerName; + aPrintSettings->GetPrinterName(getter_Copies(printerName)); + + // If there is no name then use the default printer + if (printerName.IsEmpty()) { + GetDefaultPrinterNameFromGlobalPrinters(printerName); + } else { + HANDLE hPrinter = nullptr; + if(!::OpenPrinterW(const_cast<wchar_t*>(static_cast<const wchar_t*>(printerName.get())), + &hPrinter, nullptr)) { + // If the last used printer is not found, we should use default printer. + GetDefaultPrinterNameFromGlobalPrinters(printerName); + } else { + ::ClosePrinter(hPrinter); + } + } + + // Now create a DEVNAMES struct so the the dialog is initialized correctly. + + uint32_t len = printerName.Length(); + nsHGLOBAL hDevNames = ::GlobalAlloc(GHND, sizeof(wchar_t) * (len + 1) + + sizeof(DEVNAMES)); + nsAutoGlobalMem autoDevNames(hDevNames); + if (!hDevNames) { + return NS_ERROR_OUT_OF_MEMORY; + } + + DEVNAMES* pDevNames = (DEVNAMES*)::GlobalLock(hDevNames); + if (!pDevNames) { + return NS_ERROR_FAILURE; + } + pDevNames->wDriverOffset = sizeof(DEVNAMES)/sizeof(wchar_t); + pDevNames->wDeviceOffset = sizeof(DEVNAMES)/sizeof(wchar_t); + pDevNames->wOutputOffset = sizeof(DEVNAMES)/sizeof(wchar_t)+len; + pDevNames->wDefault = 0; + + memcpy(pDevNames+1, printerName, (len + 1) * sizeof(wchar_t)); + ::GlobalUnlock(hDevNames); + + // Create a Moveable Memory Object that holds a new DevMode + // from the Printer Name + // The PRINTDLG.hDevMode requires that it be a moveable memory object + // NOTE: autoDevMode is automatically freed when any error occurred + nsAutoGlobalMem autoDevMode(CreateGlobalDevModeAndInit(printerName, aPrintSettings)); + + // Prepare to Display the Print Dialog + PRINTDLGW prntdlg; + memset(&prntdlg, 0, sizeof(PRINTDLGW)); + + prntdlg.lStructSize = sizeof(prntdlg); + prntdlg.hwndOwner = aHWnd; + prntdlg.hDevMode = autoDevMode.get(); + prntdlg.hDevNames = hDevNames; + prntdlg.hDC = nullptr; + prntdlg.Flags = PD_ALLPAGES | PD_RETURNIC | + PD_USEDEVMODECOPIESANDCOLLATE | PD_COLLATE; + + // if there is a current selection then enable the "Selection" radio button + int16_t howToEnableFrameUI = nsIPrintSettings::kFrameEnableNone; + bool isOn; + aPrintSettings->GetPrintOptions(nsIPrintSettings::kEnableSelectionRB, &isOn); + if (!isOn) { + prntdlg.Flags |= PD_NOSELECTION; + } + aPrintSettings->GetHowToEnableFrameUI(&howToEnableFrameUI); + + int32_t pg = 1; + aPrintSettings->GetStartPageRange(&pg); + prntdlg.nFromPage = pg; + + aPrintSettings->GetEndPageRange(&pg); + prntdlg.nToPage = pg; + + prntdlg.nMinPage = 1; + prntdlg.nMaxPage = 0xFFFF; + prntdlg.nCopies = 1; + prntdlg.lpfnSetupHook = nullptr; + prntdlg.lpSetupTemplateName = nullptr; + prntdlg.hPrintTemplate = nullptr; + prntdlg.hSetupTemplate = nullptr; + + prntdlg.hInstance = nullptr; + prntdlg.lpPrintTemplateName = nullptr; + + if (!ShouldExtendPrintDialog()) { + prntdlg.lCustData = 0; + prntdlg.lpfnPrintHook = nullptr; + } else { + // Set up print dialog "hook" procedure for extending the dialog + prntdlg.lCustData = (DWORD)howToEnableFrameUI; + prntdlg.lpfnPrintHook = (LPPRINTHOOKPROC)PrintHookProc; + prntdlg.Flags |= PD_ENABLEPRINTHOOK; + } + + BOOL result; + { + mozilla::widget::WinUtils::AutoSystemDpiAware dpiAwareness; + result = ::PrintDlgW(&prntdlg); + } + + if (TRUE == result) { + // check to make sure we don't have any nullptr pointers + NS_ENSURE_TRUE(aPrintSettings && prntdlg.hDevMode, NS_ERROR_FAILURE); + + if (prntdlg.hDevNames == nullptr) { + return NS_ERROR_FAILURE; + } + // Lock the deviceNames and check for nullptr + DEVNAMES *devnames = (DEVNAMES *)::GlobalLock(prntdlg.hDevNames); + if (devnames == nullptr) { + return NS_ERROR_FAILURE; + } + + char16_t* device = &(((char16_t *)devnames)[devnames->wDeviceOffset]); + char16_t* driver = &(((char16_t *)devnames)[devnames->wDriverOffset]); + + // Check to see if the "Print To File" control is checked + // then take the name from devNames and set it in the PrintSettings + // + // NOTE: + // As per Microsoft SDK documentation the returned value offset from + // devnames->wOutputOffset is either "FILE:" or nullptr + // if the "Print To File" checkbox is checked it MUST be "FILE:" + // We assert as an extra safety check. + if (prntdlg.Flags & PD_PRINTTOFILE) { + char16ptr_t fileName = &(((wchar_t *)devnames)[devnames->wOutputOffset]); + NS_ASSERTION(wcscmp(fileName, L"FILE:") == 0, "FileName must be `FILE:`"); + aPrintSettings->SetToFileName(fileName); + aPrintSettings->SetPrintToFile(true); + } else { + // clear "print to file" info + aPrintSettings->SetPrintToFile(false); + aPrintSettings->SetToFileName(nullptr); + } + + nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(aPrintSettings)); + if (!psWin) { + return NS_ERROR_FAILURE; + } + + // Setup local Data members + psWin->SetDeviceName(device); + psWin->SetDriverName(driver); + +#if defined(DEBUG_rods) || defined(DEBUG_dcone) + wprintf(L"printer: driver %s, device %s flags: %d\n", driver, device, prntdlg.Flags); +#endif + // fill the print options with the info from the dialog + + aPrintSettings->SetPrinterName(device); + + if (prntdlg.Flags & PD_SELECTION) { + aPrintSettings->SetPrintRange(nsIPrintSettings::kRangeSelection); + + } else if (prntdlg.Flags & PD_PAGENUMS) { + aPrintSettings->SetPrintRange(nsIPrintSettings::kRangeSpecifiedPageRange); + aPrintSettings->SetStartPageRange(prntdlg.nFromPage); + aPrintSettings->SetEndPageRange(prntdlg.nToPage); + + } else { // (prntdlg.Flags & PD_ALLPAGES) + aPrintSettings->SetPrintRange(nsIPrintSettings::kRangeAllPages); + } + + if (howToEnableFrameUI != nsIPrintSettings::kFrameEnableNone) { + // make sure the dialog got extended + if (gDialogWasExtended) { + // check to see about the frame radio buttons + switch (gFrameSelectedRadioBtn) { + case rad4: + aPrintSettings->SetPrintFrameType(nsIPrintSettings::kFramesAsIs); + break; + case rad5: + aPrintSettings->SetPrintFrameType(nsIPrintSettings::kSelectedFrame); + break; + case rad6: + aPrintSettings->SetPrintFrameType(nsIPrintSettings::kEachFrameSep); + break; + } // switch + } else { + // if it didn't get extended then have it default to printing + // each frame separately + aPrintSettings->SetPrintFrameType(nsIPrintSettings::kEachFrameSep); + } + } else { + aPrintSettings->SetPrintFrameType(nsIPrintSettings::kNoFrames); + } + // Unlock DeviceNames + ::GlobalUnlock(prntdlg.hDevNames); + + // Transfer the settings from the native data to the PrintSettings + LPDEVMODEW devMode = (LPDEVMODEW)::GlobalLock(prntdlg.hDevMode); + if (!devMode || !prntdlg.hDC) { + return NS_ERROR_FAILURE; + } + psWin->SetDevMode(devMode); // copies DevMode + psWin->CopyFromNative(prntdlg.hDC, devMode); + ::GlobalUnlock(prntdlg.hDevMode); + ::DeleteDC(prntdlg.hDC); + +#if defined(DEBUG_rods) || defined(DEBUG_dcone) + bool printSelection = prntdlg.Flags & PD_SELECTION; + bool printAllPages = prntdlg.Flags & PD_ALLPAGES; + bool printNumPages = prntdlg.Flags & PD_PAGENUMS; + int32_t fromPageNum = 0; + int32_t toPageNum = 0; + + if (printNumPages) { + fromPageNum = prntdlg.nFromPage; + toPageNum = prntdlg.nToPage; + } + if (printSelection) { + printf("Printing the selection\n"); + + } else if (printAllPages) { + printf("Printing all the pages\n"); + + } else { + printf("Printing from page no. %d to %d\n", fromPageNum, toPageNum); + } +#endif + + } else { + ::SetFocus(aHWnd); + aPrintSettings->SetIsCancelled(true); + return NS_ERROR_ABORT; + } + + return NS_OK; +} + +//------------------------------------------------------------------ +static void +PrepareForPrintDialog(nsIWebBrowserPrint* aWebBrowserPrint, nsIPrintSettings* aPS) +{ + NS_ASSERTION(aWebBrowserPrint, "Can't be null"); + NS_ASSERTION(aPS, "Can't be null"); + + bool isFramesetDocument; + bool isFramesetFrameSelected; + bool isIFrameSelected; + bool isRangeSelection; + + aWebBrowserPrint->GetIsFramesetDocument(&isFramesetDocument); + aWebBrowserPrint->GetIsFramesetFrameSelected(&isFramesetFrameSelected); + aWebBrowserPrint->GetIsIFrameSelected(&isIFrameSelected); + aWebBrowserPrint->GetIsRangeSelection(&isRangeSelection); + + // Setup print options for UI + if (isFramesetDocument) { + if (isFramesetFrameSelected) { + aPS->SetHowToEnableFrameUI(nsIPrintSettings::kFrameEnableAll); + } else { + aPS->SetHowToEnableFrameUI(nsIPrintSettings::kFrameEnableAsIsAndEach); + } + } else { + aPS->SetHowToEnableFrameUI(nsIPrintSettings::kFrameEnableNone); + } + + // Now determine how to set up the Frame print UI + aPS->SetPrintOptions(nsIPrintSettings::kEnableSelectionRB, isRangeSelection || isIFrameSelected); + +} + +//---------------------------------------------------------------------------------- +//-- Show Print Dialog +//---------------------------------------------------------------------------------- +nsresult NativeShowPrintDialog(HWND aHWnd, + nsIWebBrowserPrint* aWebBrowserPrint, + nsIPrintSettings* aPrintSettings) +{ + PrepareForPrintDialog(aWebBrowserPrint, aPrintSettings); + + nsresult rv = ShowNativePrintDialog(aHWnd, aPrintSettings); + if (aHWnd) { + ::DestroyWindow(aHWnd); + } + + return rv; +} + diff --git a/embedding/components/printingui/win/nsPrintDialogUtil.h b/embedding/components/printingui/win/nsPrintDialogUtil.h new file mode 100644 index 000000000..ada3da239 --- /dev/null +++ b/embedding/components/printingui/win/nsPrintDialogUtil.h @@ -0,0 +1,12 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsFlyOwnDialog_h___ +#define nsFlyOwnDialog_h___ + +nsresult NativeShowPrintDialog(HWND aHWnd, + nsIWebBrowserPrint* aWebBrowserPrint, + nsIPrintSettings* aPrintSettings); + +#endif /* nsFlyOwnDialog_h___ */ diff --git a/embedding/components/printingui/win/nsPrintProgress.cpp b/embedding/components/printingui/win/nsPrintProgress.cpp new file mode 100644 index 000000000..0b1c10a2c --- /dev/null +++ b/embedding/components/printingui/win/nsPrintProgress.cpp @@ -0,0 +1,293 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPrintProgress.h" + +#include "nsArray.h" +#include "nsIBaseWindow.h" +#include "nsIDocShell.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIXULWindow.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsPIDOMWindow.h" + +#if 0 +NS_IMPL_ADDREF(nsPrintProgress) +NS_IMPL_RELEASE(nsPrintProgress) +#else +NS_IMETHODIMP_(MozExternalRefCountType) nsPrintProgress::AddRef(void) +{ + NS_PRECONDITION(int32_t(mRefCnt) >= 0, "illegal refcnt"); + nsrefcnt count; + count = ++mRefCnt; + //NS_LOG_ADDREF(this, count, "nsPrintProgress", sizeof(*this)); + return count; +} + +NS_IMETHODIMP_(MozExternalRefCountType) nsPrintProgress::Release(void) +{ + nsrefcnt count; + NS_PRECONDITION(0 != mRefCnt, "dup release"); + count = --mRefCnt; + //NS_LOG_RELEASE(this, count, "nsPrintProgress"); + if (0 == count) { + mRefCnt = 1; /* stabilize */ + /* enable this to find non-threadsafe destructors: */ + /* NS_ASSERT_OWNINGTHREAD(nsPrintProgress); */ + delete this; + return 0; + } + return count; +} + +#endif + +NS_INTERFACE_MAP_BEGIN(nsPrintProgress) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrintStatusFeedback) + NS_INTERFACE_MAP_ENTRY(nsIPrintProgress) + NS_INTERFACE_MAP_ENTRY(nsIPrintStatusFeedback) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) +NS_INTERFACE_MAP_END_THREADSAFE + + +nsPrintProgress::nsPrintProgress() +{ + m_closeProgress = false; + m_processCanceled = false; + m_pendingStateFlags = -1; + m_pendingStateValue = NS_OK; +} + +nsPrintProgress::~nsPrintProgress() +{ + (void)ReleaseListeners(); +} + +NS_IMETHODIMP nsPrintProgress::OpenProgressDialog(mozIDOMWindowProxy *parent, + const char *dialogURL, + nsISupports *parameters, + nsIObserver *openDialogObserver, + bool *notifyOnOpen) +{ + *notifyOnOpen = true; + m_observer = openDialogObserver; + + nsresult rv = NS_ERROR_FAILURE; + + if (m_dialog) + return NS_ERROR_ALREADY_INITIALIZED; + + if (!dialogURL || !*dialogURL) + return NS_ERROR_INVALID_ARG; + + if (parent) + { + // Set up window.arguments[0]... + nsCOMPtr<nsIMutableArray> array = nsArray::Create(); + + nsCOMPtr<nsISupportsInterfacePointer> ifptr = + do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + ifptr->SetData(static_cast<nsIPrintProgress*>(this)); + ifptr->SetDataIID(&NS_GET_IID(nsIPrintProgress)); + + array->AppendElement(ifptr, /*weak =*/ false); + + array->AppendElement(parameters, /*weak = */ false); + + // We will set the opener of the dialog to be the nsIDOMWindow for the + // browser XUL window itself, as opposed to the content. That way, the + // progress window has access to the opener. + nsCOMPtr<nsPIDOMWindowOuter> pParentWindow = nsPIDOMWindowOuter::From(parent); + NS_ENSURE_STATE(pParentWindow); + + nsCOMPtr<nsIDocShell> docShell = pParentWindow->GetDocShell(); + NS_ENSURE_STATE(docShell); + + nsCOMPtr<nsIDocShellTreeOwner> owner; + docShell->GetTreeOwner(getter_AddRefs(owner)); + + nsCOMPtr<nsIXULWindow> ownerXULWindow = do_GetInterface(owner); + nsCOMPtr<mozIDOMWindowProxy> ownerWindow = do_GetInterface(ownerXULWindow); + NS_ENSURE_STATE(ownerWindow); + + nsCOMPtr<nsPIDOMWindowOuter> piOwnerWindow = nsPIDOMWindowOuter::From(ownerWindow); + + // Open the dialog. + nsCOMPtr<nsPIDOMWindowOuter> newWindow; + rv = piOwnerWindow->OpenDialog(NS_ConvertASCIItoUTF16(dialogURL), + NS_LITERAL_STRING("_blank"), + NS_LITERAL_STRING("chrome,titlebar,dependent,centerscreen"), + array, getter_AddRefs(newWindow)); + } + + return rv; +} + +NS_IMETHODIMP nsPrintProgress::CloseProgressDialog(bool forceClose) +{ + m_closeProgress = true; + // XXX Casting from bool to nsresult + return OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, + static_cast<nsresult>(forceClose)); +} + +NS_IMETHODIMP nsPrintProgress::GetPrompter(nsIPrompt **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nullptr; + + if (! m_closeProgress && m_dialog) { + nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(m_dialog); + MOZ_ASSERT(window); + return window->GetPrompter(_retval); + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsPrintProgress::GetProcessCanceledByUser(bool *aProcessCanceledByUser) +{ + NS_ENSURE_ARG_POINTER(aProcessCanceledByUser); + *aProcessCanceledByUser = m_processCanceled; + return NS_OK; +} +NS_IMETHODIMP nsPrintProgress::SetProcessCanceledByUser(bool aProcessCanceledByUser) +{ + m_processCanceled = aProcessCanceledByUser; + OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP, NS_OK); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::RegisterListener(nsIWebProgressListener * listener) +{ + if (!listener) //Nothing to do with a null listener! + return NS_OK; + + m_listenerList.AppendObject(listener); + if (m_closeProgress || m_processCanceled) + listener->OnStateChange(nullptr, nullptr, + nsIWebProgressListener::STATE_STOP, NS_OK); + else + { + listener->OnStatusChange(nullptr, nullptr, NS_OK, m_pendingStatus.get()); + if (m_pendingStateFlags != -1) + listener->OnStateChange(nullptr, nullptr, m_pendingStateFlags, m_pendingStateValue); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::UnregisterListener(nsIWebProgressListener *listener) +{ + if (listener) + m_listenerList.RemoveObject(listener); + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::DoneIniting() +{ + if (m_observer) { + m_observer->Observe(nullptr, nullptr, nullptr); + } + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus) +{ + m_pendingStateFlags = aStateFlags; + m_pendingStateValue = aStatus; + + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) +{ + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags) +{ + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage) +{ + if (aMessage && *aMessage) + m_pendingStatus = aMessage; + + uint32_t count = m_listenerList.Count(); + for (uint32_t i = count - 1; i < count; i --) + { + nsCOMPtr<nsIWebProgressListener> progressListener = m_listenerList.SafeObjectAt(i); + if (progressListener) + progressListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); + } + + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state) +{ + return NS_OK; +} + +nsresult nsPrintProgress::ReleaseListeners() +{ + m_listenerList.Clear(); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgress::ShowStatusString(const char16_t *status) +{ + return OnStatusChange(nullptr, nullptr, NS_OK, status); +} + +NS_IMETHODIMP nsPrintProgress::StartMeteors() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::StopMeteors() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::ShowProgress(int32_t percent) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::SetDocShell(nsIDocShell *shell, mozIDOMWindowProxy *window) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrintProgress::CloseWindow() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/embedding/components/printingui/win/nsPrintProgress.h b/embedding/components/printingui/win/nsPrintProgress.h new file mode 100644 index 000000000..adc5a14cc --- /dev/null +++ b/embedding/components/printingui/win/nsPrintProgress.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsPrintProgress_h +#define __nsPrintProgress_h + +#include "nsIPrintProgress.h" + +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsIDOMWindow.h" +#include "nsIPrintStatusFeedback.h" +#include "nsString.h" +#include "nsIWindowWatcher.h" +#include "nsIObserver.h" + +class nsPrintProgress : public nsIPrintProgress, public nsIPrintStatusFeedback +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPRINTPROGRESS + NS_DECL_NSIWEBPROGRESSLISTENER + NS_DECL_NSIPRINTSTATUSFEEDBACK + + nsPrintProgress(); + virtual ~nsPrintProgress(); + +private: + nsresult ReleaseListeners(); + + bool m_closeProgress; + bool m_processCanceled; + nsString m_pendingStatus; + int32_t m_pendingStateFlags; + nsresult m_pendingStateValue; + nsCOMPtr<nsIDOMWindow> m_dialog; + nsCOMArray<nsIWebProgressListener> m_listenerList; + nsCOMPtr<nsIObserver> m_observer; +}; + +#endif diff --git a/embedding/components/printingui/win/nsPrintProgressParams.cpp b/embedding/components/printingui/win/nsPrintProgressParams.cpp new file mode 100644 index 000000000..eba86b298 --- /dev/null +++ b/embedding/components/printingui/win/nsPrintProgressParams.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPrintProgressParams.h" +#include "nsReadableUtils.h" + + +NS_IMPL_ISUPPORTS(nsPrintProgressParams, nsIPrintProgressParams) + +nsPrintProgressParams::nsPrintProgressParams() +{ +} + +nsPrintProgressParams::~nsPrintProgressParams() +{ +} + +NS_IMETHODIMP nsPrintProgressParams::GetDocTitle(char16_t * *aDocTitle) +{ + NS_ENSURE_ARG(aDocTitle); + + *aDocTitle = ToNewUnicode(mDocTitle); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::SetDocTitle(const char16_t * aDocTitle) +{ + mDocTitle = aDocTitle; + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::GetDocURL(char16_t * *aDocURL) +{ + NS_ENSURE_ARG(aDocURL); + + *aDocURL = ToNewUnicode(mDocURL); + return NS_OK; +} + +NS_IMETHODIMP nsPrintProgressParams::SetDocURL(const char16_t * aDocURL) +{ + mDocURL = aDocURL; + return NS_OK; +} + diff --git a/embedding/components/printingui/win/nsPrintProgressParams.h b/embedding/components/printingui/win/nsPrintProgressParams.h new file mode 100644 index 000000000..aebfbff7f --- /dev/null +++ b/embedding/components/printingui/win/nsPrintProgressParams.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsPrintProgressParams_h +#define __nsPrintProgressParams_h + +#include "nsIPrintProgressParams.h" +#include "nsString.h" + +class nsPrintProgressParams : public nsIPrintProgressParams +{ + virtual ~nsPrintProgressParams(); + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRINTPROGRESSPARAMS + + nsPrintProgressParams(); + +private: + nsString mDocTitle; + nsString mDocURL; +}; + +#endif diff --git a/embedding/components/printingui/win/nsPrintingPromptService.cpp b/embedding/components/printingui/win/nsPrintingPromptService.cpp new file mode 100644 index 000000000..83727ee4e --- /dev/null +++ b/embedding/components/printingui/win/nsPrintingPromptService.cpp @@ -0,0 +1,341 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" + +#include "nsPrintingPromptService.h" +#include "nsIPrintingPromptService.h" +#include "nsIFactory.h" +#include "nsPIDOMWindow.h" +#include "nsReadableUtils.h" +#include "nsIEmbeddingSiteWindow.h" +#include "nsIServiceManager.h" +#include "nsIWebBrowserChrome.h" +#include "nsIWindowWatcher.h" +#include "nsPrintDialogUtil.h" + +// Printing Progress Includes +#include "nsPrintProgress.h" +#include "nsPrintProgressParams.h" +#include "nsIWebProgressListener.h" + +// XP Dialog includes +#include "nsArray.h" +#include "nsIDialogParamBlock.h" +#include "nsISupportsUtils.h" + +// Includes need to locate the native Window +#include "nsIWidget.h" +#include "nsIBaseWindow.h" +#include "nsIWebBrowserChrome.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShell.h" +#include "nsIInterfaceRequestorUtils.h" + + +static const char *kPrintProgressDialogURL = "chrome://global/content/printProgress.xul"; +static const char *kPrtPrvProgressDialogURL = "chrome://global/content/printPreviewProgress.xul"; +static const char *kPageSetupDialogURL = "chrome://global/content/printPageSetup.xul"; + +/**************************************************************** + ************************* ParamBlock *************************** + ****************************************************************/ + +class ParamBlock { + +public: + ParamBlock() + { + mBlock = 0; + } + ~ParamBlock() + { + NS_IF_RELEASE(mBlock); + } + nsresult Init() { + return CallCreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID, &mBlock); + } + nsIDialogParamBlock * operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { return mBlock; } + operator nsIDialogParamBlock * const () { return mBlock; } + +private: + nsIDialogParamBlock *mBlock; +}; + +//***************************************************************************** + +NS_IMPL_ISUPPORTS(nsPrintingPromptService, nsIPrintingPromptService, nsIWebProgressListener) + +nsPrintingPromptService::nsPrintingPromptService() +{ +} + +//----------------------------------------------------------- +nsPrintingPromptService::~nsPrintingPromptService() +{ +} + +//----------------------------------------------------------- +nsresult +nsPrintingPromptService::Init() +{ + nsresult rv; + mWatcher = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + return rv; +} + +//----------------------------------------------------------- +HWND +nsPrintingPromptService::GetHWNDForDOMWindow(mozIDOMWindowProxy *aWindow) +{ + nsCOMPtr<nsIWebBrowserChrome> chrome; + + // We might be embedded so check this path first + if (mWatcher) { + nsCOMPtr<mozIDOMWindowProxy> fosterParent; + if (!aWindow) + { // it will be a dependent window. try to find a foster parent. + mWatcher->GetActiveWindow(getter_AddRefs(fosterParent)); + aWindow = fosterParent; + } + mWatcher->GetChromeForWindow(aWindow, getter_AddRefs(chrome)); + } + + if (chrome) { + nsCOMPtr<nsIEmbeddingSiteWindow> site(do_QueryInterface(chrome)); + if (site) + { + HWND w; + site->GetSiteWindow(reinterpret_cast<void **>(&w)); + return w; + } + } + + // Now we might be the Browser so check this path + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); + + nsCOMPtr<nsIDocShellTreeItem> treeItem = + do_QueryInterface(window->GetDocShell()); + if (!treeItem) return nullptr; + + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + treeItem->GetTreeOwner(getter_AddRefs(treeOwner)); + if (!treeOwner) return nullptr; + + nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome(do_GetInterface(treeOwner)); + if (!webBrowserChrome) return nullptr; + + nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(webBrowserChrome)); + if (!baseWin) return nullptr; + + nsCOMPtr<nsIWidget> widget; + baseWin->GetMainWidget(getter_AddRefs(widget)); + if (!widget) return nullptr; + + return (HWND)widget->GetNativeData(NS_NATIVE_TMP_WINDOW); + +} + + +/////////////////////////////////////////////////////////////////////////////// +// nsIPrintingPromptService + +//----------------------------------------------------------- +NS_IMETHODIMP +nsPrintingPromptService::ShowPrintDialog(mozIDOMWindowProxy *parent, nsIWebBrowserPrint *webBrowserPrint, nsIPrintSettings *printSettings) +{ + NS_ENSURE_ARG(parent); + + HWND hWnd = GetHWNDForDOMWindow(parent); + NS_ASSERTION(hWnd, "Couldn't get native window for PRint Dialog!"); + + return NativeShowPrintDialog(hWnd, webBrowserPrint, printSettings); +} + + +NS_IMETHODIMP +nsPrintingPromptService::ShowProgress(mozIDOMWindowProxy* parent, + nsIWebBrowserPrint* webBrowserPrint, // ok to be null + nsIPrintSettings* printSettings, // ok to be null + nsIObserver* openDialogObserver, // ok to be null + bool isForPrinting, + nsIWebProgressListener** webProgressListener, + nsIPrintProgressParams** printProgressParams, + bool* notifyOnOpen) +{ + NS_ENSURE_ARG(webProgressListener); + NS_ENSURE_ARG(printProgressParams); + NS_ENSURE_ARG(notifyOnOpen); + + *notifyOnOpen = false; + if (mPrintProgress) { + *webProgressListener = nullptr; + *printProgressParams = nullptr; + return NS_ERROR_FAILURE; + } + + nsPrintProgress* prtProgress = new nsPrintProgress(); + mPrintProgress = prtProgress; + mWebProgressListener = prtProgress; + + nsCOMPtr<nsIPrintProgressParams> prtProgressParams = new nsPrintProgressParams(); + + nsCOMPtr<mozIDOMWindowProxy> parentWindow = parent; + + if (mWatcher && !parentWindow) { + mWatcher->GetActiveWindow(getter_AddRefs(parentWindow)); + } + + if (parentWindow) { + mPrintProgress->OpenProgressDialog(parentWindow, + isForPrinting ? kPrintProgressDialogURL : kPrtPrvProgressDialogURL, + prtProgressParams, openDialogObserver, notifyOnOpen); + } + + prtProgressParams.forget(printProgressParams); + NS_ADDREF(*webProgressListener = this); + + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowPageSetup(mozIDOMWindowProxy *parent, nsIPrintSettings *printSettings, nsIObserver *aObs) +{ + NS_ENSURE_ARG(printSettings); + + ParamBlock block; + nsresult rv = block.Init(); + if (NS_FAILED(rv)) + return rv; + + block->SetInt(0, 0); + rv = DoDialog(parent, block, printSettings, kPageSetupDialogURL); + + // if aWebBrowserPrint is not null then we are printing + // so we want to pass back NS_ERROR_ABORT on cancel + if (NS_SUCCEEDED(rv)) + { + int32_t status; + block->GetInt(0, &status); + return status == 0?NS_ERROR_ABORT:NS_OK; + } + + return rv; +} + +NS_IMETHODIMP +nsPrintingPromptService::ShowPrinterProperties(mozIDOMWindowProxy *parent, const char16_t *printerName, nsIPrintSettings *printSettings) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +//----------------------------------------------------------- +// Helper to Fly XP Dialog +nsresult +nsPrintingPromptService::DoDialog(mozIDOMWindowProxy *aParent, + nsIDialogParamBlock *aParamBlock, + nsIPrintSettings* aPS, + const char *aChromeURL) +{ + NS_ENSURE_ARG(aParamBlock); + NS_ENSURE_ARG(aPS); + NS_ENSURE_ARG(aChromeURL); + + if (!mWatcher) + return NS_ERROR_FAILURE; + + // get a parent, if at all possible + // (though we'd rather this didn't fail, it's OK if it does. so there's + // no failure or null check.) + nsCOMPtr<mozIDOMWindowProxy> activeParent; // retain ownership for method lifetime + if (!aParent) + { + mWatcher->GetActiveWindow(getter_AddRefs(activeParent)); + aParent = activeParent; + } + + // create a nsIMutableArray of the parameters + // being passed to the window + nsCOMPtr<nsIMutableArray> array = nsArray::Create(); + + nsCOMPtr<nsISupports> psSupports(do_QueryInterface(aPS)); + NS_ASSERTION(psSupports, "PrintSettings must be a supports"); + array->AppendElement(psSupports, /*weak =*/ false); + + nsCOMPtr<nsISupports> blkSupps(do_QueryInterface(aParamBlock)); + NS_ASSERTION(blkSupps, "IOBlk must be a supports"); + array->AppendElement(blkSupps, /*weak =*/ false); + + nsCOMPtr<mozIDOMWindowProxy> dialog; + nsresult rv = mWatcher->OpenWindow(aParent, aChromeURL, "_blank", + "centerscreen,chrome,modal,titlebar", array, + getter_AddRefs(dialog)); + + return rv; +} + +////////////////////////////////////////////////////////////////////// +// nsIWebProgressListener +////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsPrintingPromptService::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus) +{ + if ((aStateFlags & STATE_STOP) && mWebProgressListener) + { + mWebProgressListener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus); + if (mPrintProgress) + { + mPrintProgress->CloseProgressDialog(true); + } + mPrintProgress = nullptr; + mWebProgressListener = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +nsPrintingPromptService::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) +{ + if (mWebProgressListener) + { + return mWebProgressListener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsPrintingPromptService::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags) +{ + if (mWebProgressListener) + { + return mWebProgressListener->OnLocationChange(aWebProgress, aRequest, location, aFlags); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsPrintingPromptService::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage) +{ + if (mWebProgressListener) + { + return mWebProgressListener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsPrintingPromptService::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state) +{ + if (mWebProgressListener) + { + return mWebProgressListener->OnSecurityChange(aWebProgress, aRequest, state); + } + return NS_ERROR_FAILURE; +} + + diff --git a/embedding/components/printingui/win/nsPrintingPromptService.h b/embedding/components/printingui/win/nsPrintingPromptService.h new file mode 100644 index 000000000..76bc7a804 --- /dev/null +++ b/embedding/components/printingui/win/nsPrintingPromptService.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsPrintingPromptService_h +#define __nsPrintingPromptService_h + +#include <windows.h> + +// {E042570C-62DE-4bb6-A6E0-798E3C07B4DF} +#define NS_PRINTINGPROMPTSERVICE_CID \ + {0xe042570c, 0x62de, 0x4bb6, { 0xa6, 0xe0, 0x79, 0x8e, 0x3c, 0x7, 0xb4, 0xdf}} +#define NS_PRINTINGPROMPTSERVICE_CONTRACTID \ + "@mozilla.org/embedcomp/printingprompt-service;1" + +#include "nsCOMPtr.h" +#include "nsIPrintingPromptService.h" +#include "nsPIPromptService.h" +#include "nsIWindowWatcher.h" + +// Printing Progress Includes +#include "nsPrintProgress.h" +#include "nsPrintProgressParams.h" +#include "nsIWebProgressListener.h" + +class nsIDOMWindow; +class nsIDialogParamBlock; + +class nsPrintingPromptService: public nsIPrintingPromptService, + public nsIWebProgressListener +{ + virtual ~nsPrintingPromptService(); + +public: + nsPrintingPromptService(); + + nsresult Init(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIPRINTINGPROMPTSERVICE + NS_DECL_NSIWEBPROGRESSLISTENER + +private: + HWND GetHWNDForDOMWindow(mozIDOMWindowProxy *parent); + nsresult DoDialog(mozIDOMWindowProxy *aParent, + nsIDialogParamBlock *aParamBlock, + nsIPrintSettings* aPS, + const char *aChromeURL); + + nsCOMPtr<nsIWindowWatcher> mWatcher; + nsCOMPtr<nsIPrintProgress> mPrintProgress; + nsCOMPtr<nsIWebProgressListener> mWebProgressListener; + +}; + +#endif + diff --git a/embedding/components/webbrowserpersist/PWebBrowserPersistDocument.ipdl b/embedding/components/webbrowserpersist/PWebBrowserPersistDocument.ipdl new file mode 100644 index 000000000..143af0ebd --- /dev/null +++ b/embedding/components/webbrowserpersist/PWebBrowserPersistDocument.ipdl @@ -0,0 +1,90 @@ +/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PContent; +include protocol PWebBrowserPersistResources; +include protocol PWebBrowserPersistSerialize; + +include InputStreamParams; + +namespace mozilla { + +// nsIWebBrowserPersistDocument has attributes which can be read +// synchronously. To avoid using sync IPC for them, the actor sends +// this structure from the child to the parent before the parent actor +// is exposed to XPCOM. +struct WebBrowserPersistDocumentAttrs { + bool isPrivate; + nsCString documentURI; + nsCString baseURI; + nsCString contentType; + nsCString characterSet; + nsString title; + nsString referrer; + nsString contentDisposition; + uint32_t cacheKey; + uint32_t persistFlags; +}; + +// IPDL doesn't have tuples, so this gives the pair of strings from +// nsIWebBrowserPersistURIMap::getURIMapping a name. +struct WebBrowserPersistURIMapEntry { + nsCString mapFrom; + nsCString mapTo; +}; + +// nsIWebBrowserPersistURIMap is just copied over IPC as one of these, +// not proxied, to simplify the protocol. +struct WebBrowserPersistURIMap { + WebBrowserPersistURIMapEntry[] mapURIs; + nsCString targetBaseURI; +}; + +// This remotes nsIWebBrowserPersistDocument and its visitors. The +// lifecycle is a little complicated: the initial document is +// constructed parent->child, but subdocuments are constructed +// child->parent and then passed back. Subdocuments aren't subactors, +// because that would impose a lifetime relationship that doesn't +// exist in the XPIDL; instead they're all managed by the enclosing +// PContent. +protocol PWebBrowserPersistDocument { + manager PContent; + manages PWebBrowserPersistResources; + manages PWebBrowserPersistSerialize; + +parent: + // The actor isn't exposed to XPCOM until after it gets one of these + // two messages; see also the state transition rules. The message + // is either a response to the constructor (if it was parent->child) + // or sent after it (if it was child->parent). + async Attributes(WebBrowserPersistDocumentAttrs aAttrs, + OptionalInputStreamParams postData, + FileDescriptor[] postFiles); + async InitFailure(nsresult aStatus); + +child: + async SetPersistFlags(uint32_t aNewFlags); + async PWebBrowserPersistResources(); + async PWebBrowserPersistSerialize(WebBrowserPersistURIMap aMap, + nsCString aRequestedContentType, + uint32_t aEncoderFlags, + uint32_t aWrapColumn); + async __delete__(); + +state START: + recv Attributes goto MAIN; + recv InitFailure goto FAILED; + +state MAIN: + send SetPersistFlags goto MAIN; + send PWebBrowserPersistResources goto MAIN; + send PWebBrowserPersistSerialize goto MAIN; + send __delete__; + +state FAILED: + send __delete__; +}; + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/PWebBrowserPersistResources.ipdl b/embedding/components/webbrowserpersist/PWebBrowserPersistResources.ipdl new file mode 100644 index 000000000..3c36ae495 --- /dev/null +++ b/embedding/components/webbrowserpersist/PWebBrowserPersistResources.ipdl @@ -0,0 +1,26 @@ +/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PWebBrowserPersistDocument; + +namespace mozilla { + +// == nsIWebBrowserPersistResourceVisitor +protocol PWebBrowserPersistResources { + manager PWebBrowserPersistDocument; + +parent: + async VisitResource(nsCString aURI); + + // The actor sent here is in the START state; the parent-side + // receiver will have to wait for it to enter the MAIN state + // before exposing it with a visitDocument call. + async VisitDocument(PWebBrowserPersistDocument aSubDocument); + + // This reflects the endVisit method. + async __delete__(nsresult aStatus); +}; + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/PWebBrowserPersistSerialize.ipdl b/embedding/components/webbrowserpersist/PWebBrowserPersistSerialize.ipdl new file mode 100644 index 000000000..ff09fd388 --- /dev/null +++ b/embedding/components/webbrowserpersist/PWebBrowserPersistSerialize.ipdl @@ -0,0 +1,29 @@ +/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PWebBrowserPersistDocument; + +namespace mozilla { + +// This actor represents both an nsIWebBrowserPersistWriteCompletion +// and the nsIOutputStream passed with it to the writeContent method. +protocol PWebBrowserPersistSerialize { + manager PWebBrowserPersistDocument; + +parent: + // This sends the data with no flow control, so the parent could + // wind up buffering an arbitrarily large amount of data... but + // it's a serialized DOM that's already in memory as DOM nodes, so + // this is at worst just a constant-factor increase in memory usage. + // Also, Chromium does the same thing; see + // content::RenderViewImpl::didSerializeDataForFrame. + async WriteData(uint8_t[] aData); + + // This is the onFinish method. + async __delete__(nsCString aContentType, + nsresult aStatus); +}; + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.cpp new file mode 100644 index 000000000..9e152cd46 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.cpp @@ -0,0 +1,159 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WebBrowserPersistDocumentChild.h" + +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsIDocument.h" +#include "nsIInputStream.h" +#include "WebBrowserPersistLocalDocument.h" +#include "WebBrowserPersistResourcesChild.h" +#include "WebBrowserPersistSerializeChild.h" + +namespace mozilla { + +WebBrowserPersistDocumentChild::WebBrowserPersistDocumentChild() +{ +} + +WebBrowserPersistDocumentChild::~WebBrowserPersistDocumentChild() +{ +} + +void +WebBrowserPersistDocumentChild::Start(nsIDocument* aDocument) +{ + RefPtr<WebBrowserPersistLocalDocument> doc; + if (aDocument) { + doc = new WebBrowserPersistLocalDocument(aDocument); + } + Start(doc); +} + +void +WebBrowserPersistDocumentChild::Start(nsIWebBrowserPersistDocument* aDocument) +{ + MOZ_ASSERT(!mDocument); + if (!aDocument) { + SendInitFailure(NS_ERROR_FAILURE); + return; + } + + WebBrowserPersistDocumentAttrs attrs; + nsCOMPtr<nsIInputStream> postDataStream; + OptionalInputStreamParams postData; + nsTArray<FileDescriptor> postFiles; +#define ENSURE(e) do { \ + nsresult rv = (e); \ + if (NS_FAILED(rv)) { \ + SendInitFailure(rv); \ + return; \ + } \ + } while(0) + ENSURE(aDocument->GetIsPrivate(&(attrs.isPrivate()))); + ENSURE(aDocument->GetDocumentURI(attrs.documentURI())); + ENSURE(aDocument->GetBaseURI(attrs.baseURI())); + ENSURE(aDocument->GetContentType(attrs.contentType())); + ENSURE(aDocument->GetCharacterSet(attrs.characterSet())); + ENSURE(aDocument->GetTitle(attrs.title())); + ENSURE(aDocument->GetReferrer(attrs.referrer())); + ENSURE(aDocument->GetContentDisposition(attrs.contentDisposition())); + ENSURE(aDocument->GetCacheKey(&(attrs.cacheKey()))); + ENSURE(aDocument->GetPersistFlags(&(attrs.persistFlags()))); + ENSURE(aDocument->GetPostData(getter_AddRefs(postDataStream))); + ipc::SerializeInputStream(postDataStream, + postData, + postFiles); +#undef ENSURE + mDocument = aDocument; + SendAttributes(attrs, postData, postFiles); +} + +bool +WebBrowserPersistDocumentChild::RecvSetPersistFlags(const uint32_t& aNewFlags) +{ + mDocument->SetPersistFlags(aNewFlags); + return true; +} + +PWebBrowserPersistResourcesChild* +WebBrowserPersistDocumentChild::AllocPWebBrowserPersistResourcesChild() +{ + auto* actor = new WebBrowserPersistResourcesChild(); + NS_ADDREF(actor); + return actor; +} + +bool +WebBrowserPersistDocumentChild::RecvPWebBrowserPersistResourcesConstructor(PWebBrowserPersistResourcesChild* aActor) +{ + RefPtr<WebBrowserPersistResourcesChild> visitor = + static_cast<WebBrowserPersistResourcesChild*>(aActor); + nsresult rv = mDocument->ReadResources(visitor); + if (NS_FAILED(rv)) { + // This is a sync failure on the child side but an async + // failure on the parent side -- it already got NS_OK from + // ReadResources, so the error has to be reported via the + // visitor instead. + visitor->EndVisit(mDocument, rv); + } + return true; +} + +bool +WebBrowserPersistDocumentChild::DeallocPWebBrowserPersistResourcesChild(PWebBrowserPersistResourcesChild* aActor) +{ + auto* castActor = + static_cast<WebBrowserPersistResourcesChild*>(aActor); + NS_RELEASE(castActor); + return true; +} + +PWebBrowserPersistSerializeChild* +WebBrowserPersistDocumentChild::AllocPWebBrowserPersistSerializeChild( + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) +{ + auto* actor = new WebBrowserPersistSerializeChild(aMap); + NS_ADDREF(actor); + return actor; +} + +bool +WebBrowserPersistDocumentChild::RecvPWebBrowserPersistSerializeConstructor( + PWebBrowserPersistSerializeChild* aActor, + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) +{ + auto* castActor = + static_cast<WebBrowserPersistSerializeChild*>(aActor); + // This actor performs the roles of: completion, URI map, and output stream. + nsresult rv = mDocument->WriteContent(castActor, + castActor, + aRequestedContentType, + aEncoderFlags, + aWrapColumn, + castActor); + if (NS_FAILED(rv)) { + castActor->OnFinish(mDocument, castActor, aRequestedContentType, rv); + } + return true; +} + +bool +WebBrowserPersistDocumentChild::DeallocPWebBrowserPersistSerializeChild(PWebBrowserPersistSerializeChild* aActor) +{ + auto* castActor = + static_cast<WebBrowserPersistSerializeChild*>(aActor); + NS_RELEASE(castActor); + return true; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.h b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.h new file mode 100644 index 000000000..a72291f3c --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentChild.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WebBrowserPersistDocumentChild_h__ +#define WebBrowserPersistDocumentChild_h__ + +#include "mozilla/PWebBrowserPersistDocumentChild.h" +#include "nsCOMPtr.h" +#include "nsIWebBrowserPersistDocument.h" + +class nsIDocument; + +namespace mozilla { + +class WebBrowserPersistDocumentChild final + : public PWebBrowserPersistDocumentChild +{ +public: + WebBrowserPersistDocumentChild(); + ~WebBrowserPersistDocumentChild(); + + // This sends either Attributes or InitFailure and thereby causes + // the actor to leave the START state. + void Start(nsIWebBrowserPersistDocument* aDocument); + void Start(nsIDocument* aDocument); + + virtual bool + RecvSetPersistFlags(const uint32_t& aNewFlags) override; + + virtual PWebBrowserPersistResourcesChild* + AllocPWebBrowserPersistResourcesChild() override; + virtual bool + RecvPWebBrowserPersistResourcesConstructor(PWebBrowserPersistResourcesChild* aActor) override; + virtual bool + DeallocPWebBrowserPersistResourcesChild(PWebBrowserPersistResourcesChild* aActor) override; + + virtual PWebBrowserPersistSerializeChild* + AllocPWebBrowserPersistSerializeChild( + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) override; + virtual bool + RecvPWebBrowserPersistSerializeConstructor( + PWebBrowserPersistSerializeChild* aActor, + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) override; + virtual bool + DeallocPWebBrowserPersistSerializeChild(PWebBrowserPersistSerializeChild* aActor) override; + +private: + nsCOMPtr<nsIWebBrowserPersistDocument> mDocument; +}; + +} // namespace mozilla + +#endif // WebBrowserPersistDocumentChild_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.cpp new file mode 100644 index 000000000..248b05b9b --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.cpp @@ -0,0 +1,125 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WebBrowserPersistDocumentParent.h" + +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsIInputStream.h" +#include "nsThreadUtils.h" +#include "WebBrowserPersistResourcesParent.h" +#include "WebBrowserPersistSerializeParent.h" +#include "WebBrowserPersistRemoteDocument.h" + +namespace mozilla { + +WebBrowserPersistDocumentParent::WebBrowserPersistDocumentParent() +: mReflection(nullptr) +{ +} + +void +WebBrowserPersistDocumentParent::SetOnReady(nsIWebBrowserPersistDocumentReceiver* aOnReady) +{ + MOZ_ASSERT(aOnReady); + MOZ_ASSERT(!mOnReady); + MOZ_ASSERT(!mReflection); + mOnReady = aOnReady; +} + +void +WebBrowserPersistDocumentParent::ActorDestroy(ActorDestroyReason aWhy) +{ + if (mReflection) { + mReflection->ActorDestroy(); + mReflection = nullptr; + } + if (mOnReady) { + // Bug 1202887: If this is part of a subtree destruction, then + // anything which could cause another actor in that subtree to + // be Send__delete__()ed will cause use-after-free -- such as + // dropping the last reference to another document's + // WebBrowserPersistRemoteDocument. To avoid that, defer the + // callback until after the entire subtree is destroyed. + nsCOMPtr<nsIRunnable> errorLater = NewRunnableMethod + <nsresult>(mOnReady, &nsIWebBrowserPersistDocumentReceiver::OnError, + NS_ERROR_FAILURE); + NS_DispatchToCurrentThread(errorLater); + mOnReady = nullptr; + } +} + +WebBrowserPersistDocumentParent::~WebBrowserPersistDocumentParent() +{ + MOZ_RELEASE_ASSERT(!mReflection); + MOZ_ASSERT(!mOnReady); +} + +bool +WebBrowserPersistDocumentParent::RecvAttributes(const Attrs& aAttrs, + const OptionalInputStreamParams& aPostData, + nsTArray<FileDescriptor>&& aPostFiles) +{ + // Deserialize the postData unconditionally so that fds aren't leaked. + nsCOMPtr<nsIInputStream> postData = + ipc::DeserializeInputStream(aPostData, aPostFiles); + if (!mOnReady || mReflection) { + return false; + } + mReflection = new WebBrowserPersistRemoteDocument(this, aAttrs, postData); + RefPtr<WebBrowserPersistRemoteDocument> reflection = mReflection; + mOnReady->OnDocumentReady(reflection); + mOnReady = nullptr; + return true; +} + +bool +WebBrowserPersistDocumentParent::RecvInitFailure(const nsresult& aFailure) +{ + if (!mOnReady || mReflection) { + return false; + } + mOnReady->OnError(aFailure); + mOnReady = nullptr; + // Warning: Send__delete__ deallocates this object. + return Send__delete__(this); +} + +PWebBrowserPersistResourcesParent* +WebBrowserPersistDocumentParent::AllocPWebBrowserPersistResourcesParent() +{ + MOZ_CRASH("Don't use this; construct the actor directly and AddRef."); + return nullptr; +} + +bool +WebBrowserPersistDocumentParent::DeallocPWebBrowserPersistResourcesParent(PWebBrowserPersistResourcesParent* aActor) +{ + // Turn the ref held by IPC back into an nsRefPtr. + RefPtr<WebBrowserPersistResourcesParent> actor = + already_AddRefed<WebBrowserPersistResourcesParent>( + static_cast<WebBrowserPersistResourcesParent*>(aActor)); + return true; +} + +PWebBrowserPersistSerializeParent* +WebBrowserPersistDocumentParent::AllocPWebBrowserPersistSerializeParent( + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) +{ + MOZ_CRASH("Don't use this; construct the actor directly."); + return nullptr; +} + +bool +WebBrowserPersistDocumentParent::DeallocPWebBrowserPersistSerializeParent(PWebBrowserPersistSerializeParent* aActor) +{ + delete aActor; + return true; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.h b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.h new file mode 100644 index 000000000..b02b37706 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistDocumentParent.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WebBrowserPersistDocumentParent_h__ +#define WebBrowserPersistDocumentParent_h__ + +#include "mozilla/Maybe.h" +#include "mozilla/PWebBrowserPersistDocumentParent.h" +#include "nsCOMPtr.h" +#include "nsIWebBrowserPersistDocument.h" + +// This class is the IPC half of the glue between the +// nsIWebBrowserPersistDocument interface and a remote document. When +// (and if) it receives the Attributes message it constructs an +// WebBrowserPersistRemoteDocument and releases it into the XPCOM +// universe; otherwise, it invokes the document receiver's error +// callback. +// +// This object's lifetime is the normal IPC lifetime; on destruction, +// it calls its XPCOM reflection (if it exists yet) to remove that +// reference. Normal deletion occurs when the XPCOM object is being +// destroyed or after an InitFailure is received and handled. +// +// See also: TabParent::StartPersistence. + +namespace mozilla { + +class WebBrowserPersistRemoteDocument; + +class WebBrowserPersistDocumentParent final + : public PWebBrowserPersistDocumentParent +{ +public: + WebBrowserPersistDocumentParent(); + virtual ~WebBrowserPersistDocumentParent(); + + // Set a callback to be invoked when the actor leaves the START + // state. This method must be called exactly once while the actor + // is still in the START state (or is unconstructed). + void SetOnReady(nsIWebBrowserPersistDocumentReceiver* aOnReady); + + using Attrs = WebBrowserPersistDocumentAttrs; + + // IPDL methods: + virtual bool + RecvAttributes(const Attrs& aAttrs, + const OptionalInputStreamParams& aPostData, + nsTArray<FileDescriptor>&& aPostFiles) override; + virtual bool + RecvInitFailure(const nsresult& aFailure) override; + + virtual PWebBrowserPersistResourcesParent* + AllocPWebBrowserPersistResourcesParent() override; + virtual bool + DeallocPWebBrowserPersistResourcesParent(PWebBrowserPersistResourcesParent* aActor) override; + + virtual PWebBrowserPersistSerializeParent* + AllocPWebBrowserPersistSerializeParent( + const WebBrowserPersistURIMap& aMap, + const nsCString& aRequestedContentType, + const uint32_t& aEncoderFlags, + const uint32_t& aWrapColumn) override; + virtual bool + DeallocPWebBrowserPersistSerializeParent(PWebBrowserPersistSerializeParent* aActor) override; + + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; +private: + // This is reset to nullptr when the callback is invoked. + nsCOMPtr<nsIWebBrowserPersistDocumentReceiver> mOnReady; + WebBrowserPersistRemoteDocument* mReflection; +}; + +} // namespace mozilla + +#endif // WebBrowserPersistDocumentParent_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp new file mode 100644 index 000000000..23513387e --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.cpp @@ -0,0 +1,1475 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WebBrowserPersistLocalDocument.h" +#include "WebBrowserPersistDocumentParent.h" + +#include "mozilla/dom/HTMLInputElement.h" +#include "mozilla/dom/HTMLSharedElement.h" +#include "mozilla/dom/HTMLSharedObjectElement.h" +#include "mozilla/dom/TabParent.h" +#include "nsComponentManagerUtils.h" +#include "nsContentUtils.h" +#include "nsContentCID.h" +#include "nsCycleCollectionParticipant.h" +#include "nsFrameLoader.h" +#include "nsIComponentRegistrar.h" +#include "nsIContent.h" +#include "nsIDOMAttr.h" +#include "nsIDOMComment.h" +#include "nsIDOMDocument.h" +#include "nsIDOMHTMLAnchorElement.h" +#include "nsIDOMHTMLAppletElement.h" +#include "nsIDOMHTMLAreaElement.h" +#include "nsIDOMHTMLBaseElement.h" +#include "nsIDOMHTMLCollection.h" +#include "nsIDOMHTMLDocument.h" +#include "nsIDOMHTMLEmbedElement.h" +#include "nsIDOMHTMLFrameElement.h" +#include "nsIDOMHTMLIFrameElement.h" +#include "nsIDOMHTMLImageElement.h" +#include "nsIDOMHTMLInputElement.h" +#include "nsIDOMHTMLLinkElement.h" +#include "nsIDOMHTMLMediaElement.h" +#include "nsIDOMHTMLObjectElement.h" +#include "nsIDOMHTMLOptionElement.h" +#include "nsIDOMHTMLScriptElement.h" +#include "nsIDOMHTMLSourceElement.h" +#include "nsIDOMHTMLTextAreaElement.h" +#include "nsIDOMMozNamedAttrMap.h" +#include "nsIDOMNode.h" +#include "nsIDOMNodeFilter.h" +#include "nsIDOMNodeList.h" +#include "nsIDOMProcessingInstruction.h" +#include "nsIDOMTreeWalker.h" +#include "nsIDOMWindowUtils.h" +#include "nsIDocShell.h" +#include "nsIDocument.h" +#include "nsIDocumentEncoder.h" +#include "nsILoadContext.h" +#include "nsIProtocolHandler.h" +#include "nsISHEntry.h" +#include "nsISupportsPrimitives.h" +#include "nsITabParent.h" +#include "nsIWebBrowserPersist.h" +#include "nsIWebNavigation.h" +#include "nsIWebPageDescriptor.h" +#include "nsNetUtil.h" + +namespace mozilla { + +NS_IMPL_CYCLE_COLLECTING_ADDREF(WebBrowserPersistLocalDocument) +NS_IMPL_CYCLE_COLLECTING_RELEASE(WebBrowserPersistLocalDocument) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebBrowserPersistLocalDocument) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersistDocument) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION(WebBrowserPersistLocalDocument, mDocument) + + +WebBrowserPersistLocalDocument::WebBrowserPersistLocalDocument(nsIDocument* aDocument) +: mDocument(aDocument) +, mPersistFlags(0) +{ + MOZ_ASSERT(mDocument); +} + +WebBrowserPersistLocalDocument::~WebBrowserPersistLocalDocument() +{ +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::SetPersistFlags(uint32_t aFlags) +{ + mPersistFlags = aFlags; + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetPersistFlags(uint32_t* aFlags) +{ + *aFlags = mPersistFlags; + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetIsPrivate(bool* aIsPrivate) +{ + nsCOMPtr<nsILoadContext> privacyContext = mDocument->GetLoadContext(); + *aIsPrivate = privacyContext && privacyContext->UsePrivateBrowsing(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetDocumentURI(nsACString& aURISpec) +{ + nsCOMPtr<nsIURI> uri = mDocument->GetDocumentURI(); + if (!uri) { + return NS_ERROR_UNEXPECTED; + } + return uri->GetSpec(aURISpec); +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetBaseURI(nsACString& aURISpec) +{ + nsCOMPtr<nsIURI> uri = GetBaseURI(); + if (!uri) { + return NS_ERROR_UNEXPECTED; + } + return uri->GetSpec(aURISpec); +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetContentType(nsACString& aContentType) +{ + nsAutoString utf16Type; + nsresult rv; + + rv = mDocument->GetContentType(utf16Type); + NS_ENSURE_SUCCESS(rv, rv); + aContentType = NS_ConvertUTF16toUTF8(utf16Type); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetCharacterSet(nsACString& aCharSet) +{ + aCharSet = GetCharacterSet(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetTitle(nsAString& aTitle) +{ + nsAutoString titleBuffer; + mDocument->GetTitle(titleBuffer); + aTitle = titleBuffer; + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetReferrer(nsAString& aReferrer) +{ + mDocument->GetReferrer(aReferrer); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetContentDisposition(nsAString& aCD) +{ + nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetDefaultView(); + if (NS_WARN_IF(!window)) { + aCD.SetIsVoid(true); + return NS_OK; + } + nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window); + if (NS_WARN_IF(!utils)) { + aCD.SetIsVoid(true); + return NS_OK; + } + nsresult rv = utils->GetDocumentMetadata( + NS_LITERAL_STRING("content-disposition"), aCD); + if (NS_WARN_IF(NS_FAILED(rv))) { + aCD.SetIsVoid(true); + } + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetCacheKey(uint32_t* aKey) +{ + nsCOMPtr<nsISHEntry> history = GetHistory(); + if (!history) { + *aKey = 0; + return NS_OK; + } + nsCOMPtr<nsISupports> abstractKey; + nsresult rv = history->GetCacheKey(getter_AddRefs(abstractKey)); + if (NS_WARN_IF(NS_FAILED(rv)) || !abstractKey) { + *aKey = 0; + return NS_OK; + } + nsCOMPtr<nsISupportsPRUint32> u32 = do_QueryInterface(abstractKey); + if (NS_WARN_IF(!u32)) { + *aKey = 0; + return NS_OK; + } + return u32->GetData(aKey); +} + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::GetPostData(nsIInputStream** aStream) +{ + nsCOMPtr<nsISHEntry> history = GetHistory(); + if (!history) { + *aStream = nullptr; + return NS_OK; + } + return history->GetPostData(aStream); +} + +already_AddRefed<nsISHEntry> +WebBrowserPersistLocalDocument::GetHistory() +{ + nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetDefaultView(); + if (NS_WARN_IF(!window)) { + return nullptr; + } + nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(window); + if (NS_WARN_IF(!webNav)) { + return nullptr; + } + nsCOMPtr<nsIWebPageDescriptor> desc = do_QueryInterface(webNav); + if (NS_WARN_IF(!desc)) { + return nullptr; + } + nsCOMPtr<nsISupports> curDesc; + nsresult rv = desc->GetCurrentDescriptor(getter_AddRefs(curDesc)); + // This can fail if, e.g., the document is a Print Preview. + if (NS_FAILED(rv) || NS_WARN_IF(!curDesc)) { + return nullptr; + } + nsCOMPtr<nsISHEntry> history = do_QueryInterface(curDesc); + return history.forget(); +} + +const nsCString& +WebBrowserPersistLocalDocument::GetCharacterSet() const +{ + return mDocument->GetDocumentCharacterSet(); +} + +uint32_t +WebBrowserPersistLocalDocument::GetPersistFlags() const +{ + return mPersistFlags; +} + + +already_AddRefed<nsIURI> +WebBrowserPersistLocalDocument::GetBaseURI() const +{ + return mDocument->GetBaseURI(); +} + + +namespace { + +// Helper class for ReadResources(). +class ResourceReader final : public nsIWebBrowserPersistDocumentReceiver { +public: + ResourceReader(WebBrowserPersistLocalDocument* aParent, + nsIWebBrowserPersistResourceVisitor* aVisitor); + nsresult OnWalkDOMNode(nsIDOMNode* aNode); + + // This is called both to indicate the end of the document walk + // and when a subdocument is (maybe asynchronously) sent to the + // visitor. The call to EndVisit needs to happen after both of + // those have finished. + void DocumentDone(nsresult aStatus); + + NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER + NS_DECL_ISUPPORTS + +private: + RefPtr<WebBrowserPersistLocalDocument> mParent; + nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor; + nsCOMPtr<nsIURI> mCurrentBaseURI; + uint32_t mPersistFlags; + + // The number of DocumentDone calls after which EndVisit will be + // called on the visitor. Counts the main document if it's still + // being walked and any outstanding asynchronous subdocument + // StartPersistence calls. + size_t mOutstandingDocuments; + // Collects the status parameters to DocumentDone calls. + nsresult mEndStatus; + + nsresult OnWalkURI(const nsACString& aURISpec); + nsresult OnWalkURI(nsIURI* aURI); + nsresult OnWalkAttribute(nsIDOMNode* aNode, + const char* aAttribute, + const char* aNamespaceURI = ""); + nsresult OnWalkSubframe(nsIDOMNode* aNode); + + bool IsFlagSet(uint32_t aFlag) const { + return mParent->GetPersistFlags() & aFlag; + } + + ~ResourceReader(); + + using IWBP = nsIWebBrowserPersist; +}; + +NS_IMPL_ISUPPORTS(ResourceReader, nsIWebBrowserPersistDocumentReceiver) + +ResourceReader::ResourceReader(WebBrowserPersistLocalDocument* aParent, + nsIWebBrowserPersistResourceVisitor* aVisitor) +: mParent(aParent) +, mVisitor(aVisitor) +, mCurrentBaseURI(aParent->GetBaseURI()) +, mPersistFlags(aParent->GetPersistFlags()) +, mOutstandingDocuments(1) +, mEndStatus(NS_OK) +{ + MOZ_ASSERT(mCurrentBaseURI); +} + +ResourceReader::~ResourceReader() +{ + MOZ_ASSERT(mOutstandingDocuments == 0); +} + +void +ResourceReader::DocumentDone(nsresult aStatus) +{ + MOZ_ASSERT(mOutstandingDocuments > 0); + if (NS_SUCCEEDED(mEndStatus)) { + mEndStatus = aStatus; + } + if (--mOutstandingDocuments == 0) { + mVisitor->EndVisit(mParent, mEndStatus); + } +} + +nsresult +ResourceReader::OnWalkSubframe(nsIDOMNode* aNode) +{ + nsCOMPtr<nsIFrameLoaderOwner> loaderOwner = do_QueryInterface(aNode); + NS_ENSURE_STATE(loaderOwner); + RefPtr<nsFrameLoader> loader = loaderOwner->GetFrameLoader(); + NS_ENSURE_STATE(loader); + + ++mOutstandingDocuments; + // Pass in 0 as the outer window ID so that we start + // persisting the root of this subframe, and not some other + // subframe child of this subframe. + nsresult rv = loader->StartPersistence(0, this); + if (NS_FAILED(rv)) { + if (rv == NS_ERROR_NO_CONTENT) { + // Just ignore frames with no content document. + rv = NS_OK; + } + // StartPersistence won't eventually call this if it failed, + // so this does so (to keep mOutstandingDocuments correct). + DocumentDone(rv); + } + return rv; +} + +NS_IMETHODIMP +ResourceReader::OnDocumentReady(nsIWebBrowserPersistDocument* aDocument) +{ + mVisitor->VisitDocument(mParent, aDocument); + DocumentDone(NS_OK); + return NS_OK; +} + +NS_IMETHODIMP +ResourceReader::OnError(nsresult aFailure) +{ + DocumentDone(aFailure); + return NS_OK; +} + +nsresult +ResourceReader::OnWalkURI(nsIURI* aURI) +{ + // Test if this URI should be persisted. By default + // we should assume the URI is persistable. + bool doNotPersistURI; + nsresult rv = NS_URIChainHasFlags(aURI, + nsIProtocolHandler::URI_NON_PERSISTABLE, + &doNotPersistURI); + if (NS_SUCCEEDED(rv) && doNotPersistURI) { + return NS_OK; + } + + nsAutoCString stringURI; + rv = aURI->GetSpec(stringURI); + NS_ENSURE_SUCCESS(rv, rv); + return mVisitor->VisitResource(mParent, stringURI); +} + +nsresult +ResourceReader::OnWalkURI(const nsACString& aURISpec) +{ + nsresult rv; + nsCOMPtr<nsIURI> uri; + + rv = NS_NewURI(getter_AddRefs(uri), + aURISpec, + mParent->GetCharacterSet().get(), + mCurrentBaseURI); + NS_ENSURE_SUCCESS(rv, rv); + return OnWalkURI(uri); +} + +static nsresult +ExtractAttribute(nsIDOMNode* aNode, + const char* aAttribute, + const char* aNamespaceURI, + nsCString& aValue) +{ + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode); + MOZ_ASSERT(element); + + // Find the named URI attribute on the (element) node and store + // a reference to the URI that maps onto a local file name + + nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap; + nsresult rv = element->GetAttributes(getter_AddRefs(attrMap)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI); + NS_ConvertASCIItoUTF16 attribute(aAttribute); + nsCOMPtr<nsIDOMAttr> attr; + rv = attrMap->GetNamedItemNS(namespaceURI, attribute, getter_AddRefs(attr)); + NS_ENSURE_SUCCESS(rv, rv); + if (attr) { + nsAutoString value; + rv = attr->GetValue(value); + NS_ENSURE_SUCCESS(rv, rv); + aValue = NS_ConvertUTF16toUTF8(value); + } else { + aValue.Truncate(); + } + return NS_OK; +} + +nsresult +ResourceReader::OnWalkAttribute(nsIDOMNode* aNode, + const char* aAttribute, + const char* aNamespaceURI) +{ + nsAutoCString uriSpec; + nsresult rv = ExtractAttribute(aNode, aAttribute, aNamespaceURI, uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + if (uriSpec.IsEmpty()) { + return NS_OK; + } + return OnWalkURI(uriSpec); +} + +static nsresult +GetXMLStyleSheetLink(nsIDOMProcessingInstruction *aPI, nsAString &aHref) +{ + nsresult rv; + nsAutoString data; + rv = aPI->GetData(data); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::href, aHref); + return NS_OK; +} + +nsresult +ResourceReader::OnWalkDOMNode(nsIDOMNode* aNode) +{ + nsresult rv; + + // Fixup xml-stylesheet processing instructions + nsCOMPtr<nsIDOMProcessingInstruction> nodeAsPI = do_QueryInterface(aNode); + if (nodeAsPI) { + nsAutoString target; + rv = nodeAsPI->GetTarget(target); + NS_ENSURE_SUCCESS(rv, rv); + if (target.EqualsLiteral("xml-stylesheet")) { + nsAutoString href; + GetXMLStyleSheetLink(nodeAsPI, href); + if (!href.IsEmpty()) { + return OnWalkURI(NS_ConvertUTF16toUTF8(href)); + } + } + return NS_OK; + } + + nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); + if (!content) { + return NS_OK; + } + + // Test the node to see if it's an image, frame, iframe, css, js + nsCOMPtr<nsIDOMHTMLImageElement> nodeAsImage = do_QueryInterface(aNode); + if (nodeAsImage) { + return OnWalkAttribute(aNode, "src"); + } + + if (content->IsSVGElement(nsGkAtoms::img)) { + return OnWalkAttribute(aNode, "href", "http://www.w3.org/1999/xlink"); + } + + nsCOMPtr<nsIDOMHTMLMediaElement> nodeAsMedia = do_QueryInterface(aNode); + if (nodeAsMedia) { + return OnWalkAttribute(aNode, "src"); + } + nsCOMPtr<nsIDOMHTMLSourceElement> nodeAsSource = do_QueryInterface(aNode); + if (nodeAsSource) { + return OnWalkAttribute(aNode, "src"); + } + + if (content->IsHTMLElement(nsGkAtoms::body)) { + return OnWalkAttribute(aNode, "background"); + } + + if (content->IsHTMLElement(nsGkAtoms::table)) { + return OnWalkAttribute(aNode, "background"); + } + + if (content->IsHTMLElement(nsGkAtoms::tr)) { + return OnWalkAttribute(aNode, "background"); + } + + if (content->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) { + return OnWalkAttribute(aNode, "background"); + } + + nsCOMPtr<nsIDOMHTMLScriptElement> nodeAsScript = do_QueryInterface(aNode); + if (nodeAsScript) { + return OnWalkAttribute(aNode, "src"); + } + + if (content->IsSVGElement(nsGkAtoms::script)) { + return OnWalkAttribute(aNode, "href", "http://www.w3.org/1999/xlink"); + } + + nsCOMPtr<nsIDOMHTMLEmbedElement> nodeAsEmbed = do_QueryInterface(aNode); + if (nodeAsEmbed) { + return OnWalkAttribute(aNode, "src"); + } + + nsCOMPtr<nsIDOMHTMLObjectElement> nodeAsObject = do_QueryInterface(aNode); + if (nodeAsObject) { + return OnWalkAttribute(aNode, "data"); + } + + nsCOMPtr<nsIDOMHTMLAppletElement> nodeAsApplet = do_QueryInterface(aNode); + if (nodeAsApplet) { + // For an applet, relative URIs are resolved relative to the + // codebase (which is resolved relative to the base URI). + nsCOMPtr<nsIURI> oldBase = mCurrentBaseURI; + nsAutoString codebase; + rv = nodeAsApplet->GetCodeBase(codebase); + NS_ENSURE_SUCCESS(rv, rv); + if (!codebase.IsEmpty()) { + nsCOMPtr<nsIURI> baseURI; + rv = NS_NewURI(getter_AddRefs(baseURI), codebase, + mParent->GetCharacterSet().get(), mCurrentBaseURI); + NS_ENSURE_SUCCESS(rv, rv); + if (baseURI) { + mCurrentBaseURI = baseURI; + // Must restore this before returning (or ENSURE'ing). + } + } + + // We only store 'code' locally if there is no 'archive', + // otherwise we assume the archive file(s) contains it (bug 430283). + nsAutoCString archiveAttr; + rv = ExtractAttribute(aNode, "archive", "", archiveAttr); + if (NS_SUCCEEDED(rv)) { + if (!archiveAttr.IsEmpty()) { + rv = OnWalkURI(archiveAttr); + } else { + rv = OnWalkAttribute(aNode, "core"); + } + } + + // restore the base URI we really want to have + mCurrentBaseURI = oldBase; + return rv; + } + + nsCOMPtr<nsIDOMHTMLLinkElement> nodeAsLink = do_QueryInterface(aNode); + if (nodeAsLink) { + // Test if the link has a rel value indicating it to be a stylesheet + nsAutoString linkRel; + if (NS_SUCCEEDED(nodeAsLink->GetRel(linkRel)) && !linkRel.IsEmpty()) { + nsReadingIterator<char16_t> start; + nsReadingIterator<char16_t> end; + nsReadingIterator<char16_t> current; + + linkRel.BeginReading(start); + linkRel.EndReading(end); + + // Walk through space delimited string looking for "stylesheet" + for (current = start; current != end; ++current) { + // Ignore whitespace + if (nsCRT::IsAsciiSpace(*current)) { + continue; + } + + // Grab the next space delimited word + nsReadingIterator<char16_t> startWord = current; + do { + ++current; + } while (current != end && !nsCRT::IsAsciiSpace(*current)); + + // Store the link for fix up if it says "stylesheet" + if (Substring(startWord, current) + .LowerCaseEqualsLiteral("stylesheet")) { + OnWalkAttribute(aNode, "href"); + return NS_OK; + } + if (current == end) { + break; + } + } + } + return NS_OK; + } + + nsCOMPtr<nsIDOMHTMLFrameElement> nodeAsFrame = do_QueryInterface(aNode); + if (nodeAsFrame) { + return OnWalkSubframe(aNode); + } + + nsCOMPtr<nsIDOMHTMLIFrameElement> nodeAsIFrame = do_QueryInterface(aNode); + if (nodeAsIFrame && !(mPersistFlags & + IWBP::PERSIST_FLAGS_IGNORE_IFRAMES)) { + return OnWalkSubframe(aNode); + } + + nsCOMPtr<nsIDOMHTMLInputElement> nodeAsInput = do_QueryInterface(aNode); + if (nodeAsInput) { + return OnWalkAttribute(aNode, "src"); + } + + return NS_OK; +} + +// Helper class for node rewriting in writeContent(). +class PersistNodeFixup final : public nsIDocumentEncoderNodeFixup { +public: + PersistNodeFixup(WebBrowserPersistLocalDocument* aParent, + nsIWebBrowserPersistURIMap* aMap, + nsIURI* aTargetURI); + + NS_DECL_ISUPPORTS + NS_DECL_NSIDOCUMENTENCODERNODEFIXUP +private: + virtual ~PersistNodeFixup() { } + RefPtr<WebBrowserPersistLocalDocument> mParent; + nsClassHashtable<nsCStringHashKey, nsCString> mMap; + nsCOMPtr<nsIURI> mCurrentBaseURI; + nsCOMPtr<nsIURI> mTargetBaseURI; + + bool IsFlagSet(uint32_t aFlag) const { + return mParent->GetPersistFlags() & aFlag; + } + + nsresult GetNodeToFixup(nsIDOMNode* aNodeIn, nsIDOMNode** aNodeOut); + nsresult FixupURI(nsAString& aURI); + nsresult FixupAttribute(nsIDOMNode* aNode, + const char* aAttribute, + const char* aNamespaceURI = ""); + nsresult FixupAnchor(nsIDOMNode* aNode); + nsresult FixupXMLStyleSheetLink(nsIDOMProcessingInstruction* aPI, + const nsAString& aHref); + + using IWBP = nsIWebBrowserPersist; +}; + +NS_IMPL_ISUPPORTS(PersistNodeFixup, nsIDocumentEncoderNodeFixup) + +PersistNodeFixup::PersistNodeFixup(WebBrowserPersistLocalDocument* aParent, + nsIWebBrowserPersistURIMap* aMap, + nsIURI* aTargetURI) +: mParent(aParent) +, mCurrentBaseURI(aParent->GetBaseURI()) +, mTargetBaseURI(aTargetURI) +{ + if (aMap) { + uint32_t mapSize; + nsresult rv = aMap->GetNumMappedURIs(&mapSize); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + NS_ENSURE_SUCCESS_VOID(rv); + for (uint32_t i = 0; i < mapSize; ++i) { + nsAutoCString urlFrom; + nsCString* urlTo = new nsCString(); + + rv = aMap->GetURIMapping(i, urlFrom, *urlTo); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_SUCCEEDED(rv)) { + mMap.Put(urlFrom, urlTo); + } + } + } +} + +nsresult +PersistNodeFixup::GetNodeToFixup(nsIDOMNode *aNodeIn, nsIDOMNode **aNodeOut) +{ + // Avoid mixups in FixupNode that could leak objects; this goes + // against the usual out parameter convention, but it's a private + // method so shouldn't be a problem. + MOZ_ASSERT(!*aNodeOut); + + if (!IsFlagSet(IWBP::PERSIST_FLAGS_FIXUP_ORIGINAL_DOM)) { + nsresult rv = aNodeIn->CloneNode(false, 1, aNodeOut); + NS_ENSURE_SUCCESS(rv, rv); + } else { + NS_ADDREF(*aNodeOut = aNodeIn); + } + nsCOMPtr<nsIDOMHTMLElement> element(do_QueryInterface(*aNodeOut)); + if (element) { + // Make sure this is not XHTML + nsAutoString namespaceURI; + element->GetNamespaceURI(namespaceURI); + if (namespaceURI.IsEmpty()) { + // This is a tag-soup node. It may have a _base_href attribute + // stuck on it by the parser, but since we're fixing up all URIs + // relative to the overall document base that will screw us up. + // Just remove the _base_href. + element->RemoveAttribute(NS_LITERAL_STRING("_base_href")); + } + } + return NS_OK; +} + +nsresult +PersistNodeFixup::FixupURI(nsAString &aURI) +{ + // get the current location of the file (absolutized) + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI, + mParent->GetCharacterSet().get(), mCurrentBaseURI); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString spec; + rv = uri->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + const nsCString* replacement = mMap.Get(spec); + if (!replacement) { + // Note that most callers ignore this "failure". + return NS_ERROR_FAILURE; + } + if (!replacement->IsEmpty()) { + aURI = NS_ConvertUTF8toUTF16(*replacement); + } + return NS_OK; +} + +nsresult +PersistNodeFixup::FixupAttribute(nsIDOMNode* aNode, + const char* aAttribute, + const char* aNamespaceURI) +{ + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode); + MOZ_ASSERT(element); + + nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap; + nsresult rv = element->GetAttributes(getter_AddRefs(attrMap)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + NS_ConvertASCIItoUTF16 attribute(aAttribute); + NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI); + nsCOMPtr<nsIDOMAttr> attr; + rv = attrMap->GetNamedItemNS(namespaceURI, attribute, getter_AddRefs(attr)); + if (attr) { + nsString uri; + attr->GetValue(uri); + rv = FixupURI(uri); + if (NS_SUCCEEDED(rv)) { + attr->SetValue(uri); + } + } + + return rv; +} + +nsresult +PersistNodeFixup::FixupAnchor(nsIDOMNode *aNode) +{ + if (IsFlagSet(IWBP::PERSIST_FLAGS_DONT_FIXUP_LINKS)) { + return NS_OK; + } + + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode); + MOZ_ASSERT(element); + + nsCOMPtr<nsIDOMMozNamedAttrMap> attrMap; + nsresult rv = element->GetAttributes(getter_AddRefs(attrMap)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Make all anchor links absolute so they point off onto the Internet + nsString attribute(NS_LITERAL_STRING("href")); + nsCOMPtr<nsIDOMAttr> attr; + rv = attrMap->GetNamedItem(attribute, getter_AddRefs(attr)); + if (attr) { + nsString oldValue; + attr->GetValue(oldValue); + NS_ConvertUTF16toUTF8 oldCValue(oldValue); + + // Skip empty values and self-referencing bookmarks + if (oldCValue.IsEmpty() || oldCValue.CharAt(0) == '#') { + return NS_OK; + } + + // if saving file to same location, we don't need to do any fixup + bool isEqual; + if (mTargetBaseURI && + NS_SUCCEEDED(mCurrentBaseURI->Equals(mTargetBaseURI, &isEqual)) && + isEqual) { + return NS_OK; + } + + nsCOMPtr<nsIURI> relativeURI; + relativeURI = IsFlagSet(IWBP::PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION) + ? mTargetBaseURI : mCurrentBaseURI; + // Make a new URI to replace the current one + nsCOMPtr<nsIURI> newURI; + rv = NS_NewURI(getter_AddRefs(newURI), oldCValue, + mParent->GetCharacterSet().get(), relativeURI); + if (NS_SUCCEEDED(rv) && newURI) { + newURI->SetUserPass(EmptyCString()); + nsAutoCString uriSpec; + rv = newURI->GetSpec(uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + attr->SetValue(NS_ConvertUTF8toUTF16(uriSpec)); + } + } + + return NS_OK; +} + +static void +AppendXMLAttr(const nsAString& key, const nsAString& aValue, nsAString& aBuffer) +{ + if (!aBuffer.IsEmpty()) { + aBuffer.Append(' '); + } + aBuffer.Append(key); + aBuffer.AppendLiteral("=\""); + for (size_t i = 0; i < aValue.Length(); ++i) { + switch (aValue[i]) { + case '&': + aBuffer.AppendLiteral("&"); + break; + case '<': + aBuffer.AppendLiteral("<"); + break; + case '>': + aBuffer.AppendLiteral(">"); + break; + case '"': + aBuffer.AppendLiteral("""); + break; + default: + aBuffer.Append(aValue[i]); + break; + } + } + aBuffer.Append('"'); +} + +nsresult +PersistNodeFixup::FixupXMLStyleSheetLink(nsIDOMProcessingInstruction* aPI, + const nsAString& aHref) +{ + NS_ENSURE_ARG_POINTER(aPI); + nsresult rv = NS_OK; + + nsAutoString data; + rv = aPI->GetData(data); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + nsAutoString href; + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::href, + href); + + // Construct and set a new data value for the xml-stylesheet + if (!aHref.IsEmpty() && !href.IsEmpty()) + { + nsAutoString alternate; + nsAutoString charset; + nsAutoString title; + nsAutoString type; + nsAutoString media; + + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::alternate, + alternate); + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::charset, + charset); + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::title, + title); + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::type, + type); + nsContentUtils::GetPseudoAttributeValue(data, + nsGkAtoms::media, + media); + + nsAutoString newData; + AppendXMLAttr(NS_LITERAL_STRING("href"), aHref, newData); + if (!title.IsEmpty()) { + AppendXMLAttr(NS_LITERAL_STRING("title"), title, newData); + } + if (!media.IsEmpty()) { + AppendXMLAttr(NS_LITERAL_STRING("media"), media, newData); + } + if (!type.IsEmpty()) { + AppendXMLAttr(NS_LITERAL_STRING("type"), type, newData); + } + if (!charset.IsEmpty()) { + AppendXMLAttr(NS_LITERAL_STRING("charset"), charset, newData); + } + if (!alternate.IsEmpty()) { + AppendXMLAttr(NS_LITERAL_STRING("alternate"), alternate, newData); + } + aPI->SetData(newData); + } + + return rv; +} + +NS_IMETHODIMP +PersistNodeFixup::FixupNode(nsIDOMNode *aNodeIn, + bool *aSerializeCloneKids, + nsIDOMNode **aNodeOut) +{ + *aNodeOut = nullptr; + *aSerializeCloneKids = false; + + uint16_t type; + nsresult rv = aNodeIn->GetNodeType(&type); + NS_ENSURE_SUCCESS(rv, rv); + if (type != nsIDOMNode::ELEMENT_NODE && + type != nsIDOMNode::PROCESSING_INSTRUCTION_NODE) { + return NS_OK; + } + + // Fixup xml-stylesheet processing instructions + nsCOMPtr<nsIDOMProcessingInstruction> nodeAsPI = do_QueryInterface(aNodeIn); + if (nodeAsPI) { + nsAutoString target; + nodeAsPI->GetTarget(target); + if (target.EqualsLiteral("xml-stylesheet")) + { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + nsCOMPtr<nsIDOMProcessingInstruction> outNode = + do_QueryInterface(*aNodeOut); + nsAutoString href; + GetXMLStyleSheetLink(nodeAsPI, href); + if (!href.IsEmpty()) { + FixupURI(href); + FixupXMLStyleSheetLink(outNode, href); + } + } + } + return NS_OK; + } + + // BASE elements are replaced by a comment so relative links are not hosed. + if (!IsFlagSet(IWBP::PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS)) { + nsCOMPtr<nsIDOMHTMLBaseElement> nodeAsBase = do_QueryInterface(aNodeIn); + if (nodeAsBase) { + nsCOMPtr<nsIDOMDocument> ownerDocument; + auto* base = static_cast<dom::HTMLSharedElement*>(nodeAsBase.get()); + base->GetOwnerDocument(getter_AddRefs(ownerDocument)); + if (ownerDocument) { + nsAutoString href; + base->GetHref(href); // Doesn't matter if this fails + nsCOMPtr<nsIDOMComment> comment; + nsAutoString commentText; + commentText.AssignLiteral(" base "); + if (!href.IsEmpty()) { + commentText += NS_LITERAL_STRING("href=\"") + href + + NS_LITERAL_STRING("\" "); + } + rv = ownerDocument->CreateComment(commentText, + getter_AddRefs(comment)); + if (comment) { + return CallQueryInterface(comment, aNodeOut); + } + } + return NS_OK; + } + } + + nsCOMPtr<nsIContent> content = do_QueryInterface(aNodeIn); + if (!content) { + return NS_OK; + } + + // Fix up href and file links in the elements + nsCOMPtr<nsIDOMHTMLAnchorElement> nodeAsAnchor = do_QueryInterface(aNodeIn); + if (nodeAsAnchor) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAnchor(*aNodeOut); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLAreaElement> nodeAsArea = do_QueryInterface(aNodeIn); + if (nodeAsArea) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAnchor(*aNodeOut); + } + return rv; + } + + if (content->IsHTMLElement(nsGkAtoms::body)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "background"); + } + return rv; + } + + if (content->IsHTMLElement(nsGkAtoms::table)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "background"); + } + return rv; + } + + if (content->IsHTMLElement(nsGkAtoms::tr)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "background"); + } + return rv; + } + + if (content->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "background"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLImageElement> nodeAsImage = do_QueryInterface(aNodeIn); + if (nodeAsImage) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + // Disable image loads + nsCOMPtr<nsIImageLoadingContent> imgCon = + do_QueryInterface(*aNodeOut); + if (imgCon) { + imgCon->SetLoadingEnabled(false); + } + FixupAnchor(*aNodeOut); + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLMediaElement> nodeAsMedia = do_QueryInterface(aNodeIn); + if (nodeAsMedia) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLSourceElement> nodeAsSource = do_QueryInterface(aNodeIn); + if (nodeAsSource) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + if (content->IsSVGElement(nsGkAtoms::img)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + // Disable image loads + nsCOMPtr<nsIImageLoadingContent> imgCon = + do_QueryInterface(*aNodeOut); + if (imgCon) + imgCon->SetLoadingEnabled(false); + + // FixupAnchor(*aNodeOut); // XXXjwatt: is this line needed? + FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLScriptElement> nodeAsScript = do_QueryInterface(aNodeIn); + if (nodeAsScript) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + if (content->IsSVGElement(nsGkAtoms::script)) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "href", "http://www.w3.org/1999/xlink"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLEmbedElement> nodeAsEmbed = do_QueryInterface(aNodeIn); + if (nodeAsEmbed) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLObjectElement> nodeAsObject = do_QueryInterface(aNodeIn); + if (nodeAsObject) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "data"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLAppletElement> nodeAsApplet = do_QueryInterface(aNodeIn); + if (nodeAsApplet) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + nsCOMPtr<nsIDOMHTMLAppletElement> newApplet = + do_QueryInterface(*aNodeOut); + // For an applet, relative URIs are resolved relative to the + // codebase (which is resolved relative to the base URI). + nsCOMPtr<nsIURI> oldBase = mCurrentBaseURI; + nsAutoString codebase; + nodeAsApplet->GetCodeBase(codebase); + if (!codebase.IsEmpty()) { + nsCOMPtr<nsIURI> baseURI; + NS_NewURI(getter_AddRefs(baseURI), codebase, + mParent->GetCharacterSet().get(), mCurrentBaseURI); + if (baseURI) { + mCurrentBaseURI = baseURI; + } + } + // Unset the codebase too, since we'll correctly relativize the + // code and archive paths. + static_cast<dom::HTMLSharedObjectElement*>(newApplet.get())-> + RemoveAttribute(NS_LITERAL_STRING("codebase")); + FixupAttribute(*aNodeOut, "code"); + FixupAttribute(*aNodeOut, "archive"); + // restore the base URI we really want to have + mCurrentBaseURI = oldBase; + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLLinkElement> nodeAsLink = do_QueryInterface(aNodeIn); + if (nodeAsLink) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + // First see if the link represents linked content + rv = FixupAttribute(*aNodeOut, "href"); + if (NS_FAILED(rv)) { + // Perhaps this link is actually an anchor to related content + FixupAnchor(*aNodeOut); + } + // TODO if "type" attribute == "text/css" + // fixup stylesheet + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLFrameElement> nodeAsFrame = do_QueryInterface(aNodeIn); + if (nodeAsFrame) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLIFrameElement> nodeAsIFrame = do_QueryInterface(aNodeIn); + if (nodeAsIFrame) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + FixupAttribute(*aNodeOut, "src"); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLInputElement> nodeAsInput = do_QueryInterface(aNodeIn); + if (nodeAsInput) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + // Disable image loads + nsCOMPtr<nsIImageLoadingContent> imgCon = + do_QueryInterface(*aNodeOut); + if (imgCon) { + imgCon->SetLoadingEnabled(false); + } + + FixupAttribute(*aNodeOut, "src"); + + nsAutoString valueStr; + NS_NAMED_LITERAL_STRING(valueAttr, "value"); + // Update element node attributes with user-entered form state + nsCOMPtr<nsIContent> content = do_QueryInterface(*aNodeOut); + RefPtr<dom::HTMLInputElement> outElt = + dom::HTMLInputElement::FromContentOrNull(content); + nsCOMPtr<nsIFormControl> formControl = do_QueryInterface(*aNodeOut); + switch (formControl->GetType()) { + case NS_FORM_INPUT_EMAIL: + case NS_FORM_INPUT_SEARCH: + case NS_FORM_INPUT_TEXT: + case NS_FORM_INPUT_TEL: + case NS_FORM_INPUT_URL: + case NS_FORM_INPUT_NUMBER: + case NS_FORM_INPUT_RANGE: + case NS_FORM_INPUT_DATE: + case NS_FORM_INPUT_TIME: + case NS_FORM_INPUT_COLOR: + nodeAsInput->GetValue(valueStr); + // Avoid superfluous value="" serialization + if (valueStr.IsEmpty()) + outElt->RemoveAttribute(valueAttr); + else + outElt->SetAttribute(valueAttr, valueStr); + break; + case NS_FORM_INPUT_CHECKBOX: + case NS_FORM_INPUT_RADIO: + bool checked; + nodeAsInput->GetChecked(&checked); + outElt->SetDefaultChecked(checked); + break; + default: + break; + } + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLTextAreaElement> nodeAsTextArea = do_QueryInterface(aNodeIn); + if (nodeAsTextArea) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + // Tell the document encoder to serialize the text child we create below + *aSerializeCloneKids = true; + + nsAutoString valueStr; + nodeAsTextArea->GetValue(valueStr); + + (*aNodeOut)->SetTextContent(valueStr); + } + return rv; + } + + nsCOMPtr<nsIDOMHTMLOptionElement> nodeAsOption = do_QueryInterface(aNodeIn); + if (nodeAsOption) { + rv = GetNodeToFixup(aNodeIn, aNodeOut); + if (NS_SUCCEEDED(rv) && *aNodeOut) { + nsCOMPtr<nsIDOMHTMLOptionElement> outElt = do_QueryInterface(*aNodeOut); + bool selected; + nodeAsOption->GetSelected(&selected); + outElt->SetDefaultSelected(selected); + } + return rv; + } + + return NS_OK; +} + +} // unnamed namespace + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::ReadResources(nsIWebBrowserPersistResourceVisitor* aVisitor) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIWebBrowserPersistResourceVisitor> visitor = aVisitor; + + nsCOMPtr<nsIDOMNode> docAsNode = do_QueryInterface(mDocument); + NS_ENSURE_TRUE(docAsNode, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDOMTreeWalker> walker; + nsCOMPtr<nsIDOMDocument> oldStyleDoc = do_QueryInterface(mDocument); + MOZ_ASSERT(oldStyleDoc); + rv = oldStyleDoc->CreateTreeWalker(docAsNode, + nsIDOMNodeFilter::SHOW_ELEMENT | + nsIDOMNodeFilter::SHOW_DOCUMENT | + nsIDOMNodeFilter::SHOW_PROCESSING_INSTRUCTION, + nullptr, 1, getter_AddRefs(walker)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + MOZ_ASSERT(walker); + + RefPtr<ResourceReader> reader = new ResourceReader(this, aVisitor); + nsCOMPtr<nsIDOMNode> currentNode; + walker->GetCurrentNode(getter_AddRefs(currentNode)); + while (currentNode) { + rv = reader->OnWalkDOMNode(currentNode); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + rv = walker->NextNode(getter_AddRefs(currentNode)); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + } + reader->DocumentDone(rv); + // If NS_FAILED(rv), it was / will be reported by an EndVisit call + // via DocumentDone. This method must return a failure if and + // only if visitor won't be invoked. + return NS_OK; +} + +static uint32_t +ConvertEncoderFlags(uint32_t aEncoderFlags) +{ + uint32_t encoderFlags = 0; + + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_SELECTION_ONLY) + encoderFlags |= nsIDocumentEncoder::OutputSelectionOnly; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_FORMATTED) + encoderFlags |= nsIDocumentEncoder::OutputFormatted; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_RAW) + encoderFlags |= nsIDocumentEncoder::OutputRaw; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_BODY_ONLY) + encoderFlags |= nsIDocumentEncoder::OutputBodyOnly; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_PREFORMATTED) + encoderFlags |= nsIDocumentEncoder::OutputPreformatted; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_WRAP) + encoderFlags |= nsIDocumentEncoder::OutputWrap; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_FORMAT_FLOWED) + encoderFlags |= nsIDocumentEncoder::OutputFormatFlowed; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ABSOLUTE_LINKS) + encoderFlags |= nsIDocumentEncoder::OutputAbsoluteLinks; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_BASIC_ENTITIES) + encoderFlags |= nsIDocumentEncoder::OutputEncodeBasicEntities; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES) + encoderFlags |= nsIDocumentEncoder::OutputEncodeLatin1Entities; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_HTML_ENTITIES) + encoderFlags |= nsIDocumentEncoder::OutputEncodeHTMLEntities; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_ENCODE_W3C_ENTITIES) + encoderFlags |= nsIDocumentEncoder::OutputEncodeW3CEntities; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_CR_LINEBREAKS) + encoderFlags |= nsIDocumentEncoder::OutputCRLineBreak; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_LF_LINEBREAKS) + encoderFlags |= nsIDocumentEncoder::OutputLFLineBreak; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_NOSCRIPT_CONTENT) + encoderFlags |= nsIDocumentEncoder::OutputNoScriptContent; + if (aEncoderFlags & nsIWebBrowserPersist::ENCODE_FLAGS_NOFRAMES_CONTENT) + encoderFlags |= nsIDocumentEncoder::OutputNoFramesContent; + + return encoderFlags; +} + +static bool +ContentTypeEncoderExists(const nsACString& aType) +{ + nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE); + contractID.Append(aType); + + nsCOMPtr<nsIComponentRegistrar> registrar; + nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(registrar)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_SUCCEEDED(rv) && registrar) { + bool result; + rv = registrar->IsContractIDRegistered(contractID.get(), &result); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return NS_SUCCEEDED(rv) && result; + } + return false; +} + +void +WebBrowserPersistLocalDocument::DecideContentType(nsACString& aContentType) +{ + if (aContentType.IsEmpty()) { + if (NS_WARN_IF(NS_FAILED(GetContentType(aContentType)))) { + aContentType.Truncate(); + } + } + if (!aContentType.IsEmpty() && + !ContentTypeEncoderExists(aContentType)) { + aContentType.Truncate(); + } + if (aContentType.IsEmpty()) { + aContentType.AssignLiteral("text/html"); + } +} + +nsresult +WebBrowserPersistLocalDocument::GetDocEncoder(const nsACString& aContentType, + uint32_t aEncoderFlags, + nsIDocumentEncoder** aEncoder) +{ + nsresult rv; + nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE); + contractID.Append(aContentType); + nsCOMPtr<nsIDocumentEncoder> encoder = + do_CreateInstance(contractID.get(), &rv); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + rv = encoder->NativeInit(mDocument, + NS_ConvertASCIItoUTF16(aContentType), + ConvertEncoderFlags(aEncoderFlags)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + nsAutoCString charSet; + rv = GetCharacterSet(charSet); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + rv = encoder->SetCharset(charSet); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + encoder.forget(aEncoder); + return NS_OK; +} + + +NS_IMETHODIMP +WebBrowserPersistLocalDocument::WriteContent( + nsIOutputStream* aStream, + nsIWebBrowserPersistURIMap* aMap, + const nsACString& aRequestedContentType, + uint32_t aEncoderFlags, + uint32_t aWrapColumn, + nsIWebBrowserPersistWriteCompletion* aCompletion) +{ + NS_ENSURE_ARG_POINTER(aStream); + NS_ENSURE_ARG_POINTER(aCompletion); + nsAutoCString contentType(aRequestedContentType); + DecideContentType(contentType); + + nsCOMPtr<nsIDocumentEncoder> encoder; + nsresult rv = GetDocEncoder(contentType, aEncoderFlags, + getter_AddRefs(encoder)); + NS_ENSURE_SUCCESS(rv, rv); + + if (aWrapColumn != 0 && (aEncoderFlags + & nsIWebBrowserPersist::ENCODE_FLAGS_WRAP)) { + encoder->SetWrapColumn(aWrapColumn); + } + + nsCOMPtr<nsIURI> targetURI; + if (aMap) { + nsAutoCString targetURISpec; + rv = aMap->GetTargetBaseURI(targetURISpec); + if (NS_SUCCEEDED(rv) && !targetURISpec.IsEmpty()) { + rv = NS_NewURI(getter_AddRefs(targetURI), targetURISpec, + /* charset: */ nullptr, /* base: */ nullptr); + NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED); + } else if (mPersistFlags & nsIWebBrowserPersist::PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION) { + return NS_ERROR_UNEXPECTED; + } + } + rv = encoder->SetNodeFixup(new PersistNodeFixup(this, aMap, targetURI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + rv = encoder->EncodeToStream(aStream); + aCompletion->OnFinish(this, aStream, contentType, rv); + return NS_OK; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h new file mode 100644 index 000000000..9110d6c35 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistLocalDocument.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WebBrowserPersistLocalDocument_h__ +#define WebBrowserPersistLocalDocument_h__ + +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIDocument.h" +#include "nsIURI.h" +#include "nsIWebBrowserPersistDocument.h" + +class nsIDocumentEncoder; +class nsISHEntry; + +namespace mozilla { + +class WebBrowserPersistLocalDocument final + : public nsIWebBrowserPersistDocument +{ +public: + explicit WebBrowserPersistLocalDocument(nsIDocument* aDocument); + + const nsCString& GetCharacterSet() const; + uint32_t GetPersistFlags() const; + already_AddRefed<nsIURI> GetBaseURI() const; + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIWEBBROWSERPERSISTDOCUMENT + + NS_DECL_CYCLE_COLLECTION_CLASS(WebBrowserPersistLocalDocument) + +private: + nsCOMPtr<nsIDocument> mDocument; + uint32_t mPersistFlags; + + void DecideContentType(nsACString& aContentType); + nsresult GetDocEncoder(const nsACString& aContentType, + uint32_t aEncoderFlags, + nsIDocumentEncoder** aEncoder); + already_AddRefed<nsISHEntry> GetHistory(); + + virtual ~WebBrowserPersistLocalDocument(); +}; + +} // namespace mozilla + +#endif // WebBrowserPersistLocalDocument_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp new file mode 100644 index 000000000..c7dcca861 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.cpp @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WebBrowserPersistRemoteDocument.h" +#include "WebBrowserPersistDocumentParent.h" +#include "WebBrowserPersistResourcesParent.h" +#include "WebBrowserPersistSerializeParent.h" +#include "mozilla/Unused.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(WebBrowserPersistRemoteDocument, + nsIWebBrowserPersistDocument) + +WebBrowserPersistRemoteDocument +::WebBrowserPersistRemoteDocument(WebBrowserPersistDocumentParent* aActor, + const Attrs& aAttrs, + nsIInputStream* aPostData) +: mActor(aActor) +, mAttrs(aAttrs) +, mPostData(aPostData) +{ +} + +WebBrowserPersistRemoteDocument::~WebBrowserPersistRemoteDocument() +{ + if (mActor) { + Unused << mActor->Send__delete__(mActor); + // That will call mActor->ActorDestroy, which calls this->ActorDestroy + // (whether or not the IPC send succeeds). + } + MOZ_ASSERT(!mActor); +} + +void +WebBrowserPersistRemoteDocument::ActorDestroy(void) +{ + mActor = nullptr; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetIsPrivate(bool* aIsPrivate) +{ + *aIsPrivate = mAttrs.isPrivate(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetDocumentURI(nsACString& aURISpec) +{ + aURISpec = mAttrs.documentURI(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetBaseURI(nsACString& aURISpec) +{ + aURISpec = mAttrs.baseURI(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetContentType(nsACString& aContentType) +{ + aContentType = mAttrs.contentType(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetCharacterSet(nsACString& aCharSet) +{ + aCharSet = mAttrs.characterSet(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetTitle(nsAString& aTitle) +{ + aTitle = mAttrs.title(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetReferrer(nsAString& aReferrer) +{ + aReferrer = mAttrs.referrer(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetContentDisposition(nsAString& aDisp) +{ + aDisp = mAttrs.contentDisposition(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetCacheKey(uint32_t* aCacheKey) +{ + *aCacheKey = mAttrs.cacheKey(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetPersistFlags(uint32_t* aFlags) +{ + *aFlags = mAttrs.persistFlags(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::SetPersistFlags(uint32_t aFlags) +{ + if (!mActor) { + return NS_ERROR_FAILURE; + } + if (!mActor->SendSetPersistFlags(aFlags)) { + return NS_ERROR_FAILURE; + } + mAttrs.persistFlags() = aFlags; + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::GetPostData(nsIInputStream** aStream) +{ + nsCOMPtr<nsIInputStream> stream = mPostData; + stream.forget(aStream); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::ReadResources(nsIWebBrowserPersistResourceVisitor* aVisitor) +{ + if (!mActor) { + return NS_ERROR_FAILURE; + } + RefPtr<WebBrowserPersistResourcesParent> subActor = + new WebBrowserPersistResourcesParent(this, aVisitor); + return mActor->SendPWebBrowserPersistResourcesConstructor( + subActor.forget().take()) + ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +WebBrowserPersistRemoteDocument::WriteContent( + nsIOutputStream* aStream, + nsIWebBrowserPersistURIMap* aMap, + const nsACString& aRequestedContentType, + uint32_t aEncoderFlags, + uint32_t aWrapColumn, + nsIWebBrowserPersistWriteCompletion* aCompletion) +{ + if (!mActor) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + WebBrowserPersistURIMap map; + uint32_t numMappedURIs; + if (aMap) { + rv = aMap->GetTargetBaseURI(map.targetBaseURI()); + NS_ENSURE_SUCCESS(rv, rv); + rv = aMap->GetNumMappedURIs(&numMappedURIs); + NS_ENSURE_SUCCESS(rv, rv); + for (uint32_t i = 0; i < numMappedURIs; ++i) { + WebBrowserPersistURIMapEntry& nextEntry = + *(map.mapURIs().AppendElement()); + rv = aMap->GetURIMapping(i, nextEntry.mapFrom(), nextEntry.mapTo()); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + auto* subActor = new WebBrowserPersistSerializeParent(this, + aStream, + aCompletion); + nsCString requestedContentType(aRequestedContentType); // Sigh. + return mActor->SendPWebBrowserPersistSerializeConstructor( + subActor, map, requestedContentType, aEncoderFlags, aWrapColumn) + ? NS_OK : NS_ERROR_FAILURE; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.h b/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.h new file mode 100644 index 000000000..08d435903 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistRemoteDocument.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WebBrowserPersistRemoteDocument_h__ +#define WebBrowserPersistRemoteDocument_h__ + +#include "mozilla/Maybe.h" +#include "mozilla/PWebBrowserPersistDocumentParent.h" +#include "nsCOMPtr.h" +#include "nsIWebBrowserPersistDocument.h" +#include "nsIInputStream.h" + +// This class is the XPCOM half of the glue between the +// nsIWebBrowserPersistDocument interface and a remote document; it is +// created by WebBrowserPersistDocumentParent when (and if) it +// receives the information needed to populate the interface's +// properties. +// +// This object has a normal refcounted lifetime. The corresponding +// IPC actor holds a weak reference to this class; when the last +// strong reference is released, it sends an IPC delete message and +// thereby removes that reference. + +namespace mozilla { + +class WebBrowserPersistDocumentParent; + +class WebBrowserPersistRemoteDocument final + : public nsIWebBrowserPersistDocument +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWEBBROWSERPERSISTDOCUMENT + +private: + using Attrs = WebBrowserPersistDocumentAttrs; + WebBrowserPersistDocumentParent* mActor; + Attrs mAttrs; + nsCOMPtr<nsIInputStream> mPostData; + + friend class WebBrowserPersistDocumentParent; + WebBrowserPersistRemoteDocument(WebBrowserPersistDocumentParent* aActor, + const Attrs& aAttrs, + nsIInputStream* aPostData); + ~WebBrowserPersistRemoteDocument(); + + void ActorDestroy(void); +}; + +} // namespace mozilla + +#endif // WebBrowserPersistRemoteDocument_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp new file mode 100644 index 000000000..c28f034c8 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.cpp @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WebBrowserPersistResourcesChild.h" + +#include "WebBrowserPersistDocumentChild.h" +#include "mozilla/dom/ContentChild.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(WebBrowserPersistResourcesChild, + nsIWebBrowserPersistResourceVisitor) + +WebBrowserPersistResourcesChild::WebBrowserPersistResourcesChild() +{ +} + +WebBrowserPersistResourcesChild::~WebBrowserPersistResourcesChild() +{ +} + +NS_IMETHODIMP +WebBrowserPersistResourcesChild::VisitResource(nsIWebBrowserPersistDocument *aDocument, + const nsACString& aURI) +{ + nsCString copiedURI(aURI); // Yay, XPIDL/IPDL mismatch. + SendVisitResource(copiedURI); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistResourcesChild::VisitDocument(nsIWebBrowserPersistDocument* aDocument, + nsIWebBrowserPersistDocument* aSubDocument) +{ + auto* subActor = new WebBrowserPersistDocumentChild(); + // As a consequence of how PWebBrowserPersistDocumentConstructor + // can be sent by both the parent and the child, we must pass the + // aBrowser and outerWindowID arguments here, but the values are + // ignored by the parent. In particular, the TabChild in which + // persistence started does not necessarily exist at this point; + // see bug 1203602. + if (!Manager()->Manager() + ->SendPWebBrowserPersistDocumentConstructor(subActor, nullptr, 0)) { + // NOTE: subActor is freed at this point. + return NS_ERROR_FAILURE; + } + // ...but here, IPC won't free subActor until after this returns + // to the event loop. + + // The order of these two messages will be preserved, because + // they're the same toplevel protocol and priority. + // + // With this ordering, it's always the transition out of START + // state that causes a document's parent actor to be exposed to + // XPCOM (for both parent->child and child->parent construction), + // which simplifies the lifetime management. + SendVisitDocument(subActor); + subActor->Start(aSubDocument); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistResourcesChild::EndVisit(nsIWebBrowserPersistDocument *aDocument, + nsresult aStatus) +{ + Send__delete__(this, aStatus); + return NS_OK; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.h b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.h new file mode 100644 index 000000000..d6ab10d02 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesChild.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WebBrowserPersistResourcesChild_h__ +#define WebBrowserPersistResourcesChild_h__ + +#include "mozilla/PWebBrowserPersistResourcesChild.h" + +#include "nsIWebBrowserPersistDocument.h" + +namespace mozilla { + +class WebBrowserPersistResourcesChild final + : public PWebBrowserPersistResourcesChild + , public nsIWebBrowserPersistResourceVisitor +{ +public: + WebBrowserPersistResourcesChild(); + + NS_DECL_NSIWEBBROWSERPERSISTRESOURCEVISITOR + NS_DECL_ISUPPORTS +private: + virtual ~WebBrowserPersistResourcesChild(); +}; + +} // namespace mozilla + +#endif // WebBrowserPersistDocumentChild_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.cpp new file mode 100644 index 000000000..195cabb58 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.cpp @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WebBrowserPersistResourcesParent.h" + +#include "nsThreadUtils.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(WebBrowserPersistResourcesParent, + nsIWebBrowserPersistDocumentReceiver) + +WebBrowserPersistResourcesParent::WebBrowserPersistResourcesParent( + nsIWebBrowserPersistDocument* aDocument, + nsIWebBrowserPersistResourceVisitor* aVisitor) +: mDocument(aDocument) +, mVisitor(aVisitor) +{ + MOZ_ASSERT(aDocument); + MOZ_ASSERT(aVisitor); +} + +WebBrowserPersistResourcesParent::~WebBrowserPersistResourcesParent() +{ +} + +void +WebBrowserPersistResourcesParent::ActorDestroy(ActorDestroyReason aWhy) +{ + if (aWhy != Deletion && mVisitor) { + // See comment in WebBrowserPersistDocumentParent::ActorDestroy + // (or bug 1202887) for why this is deferred. + nsCOMPtr<nsIRunnable> errorLater = NewRunnableMethod + <nsCOMPtr<nsIWebBrowserPersistDocument>, nsresult> + (mVisitor, &nsIWebBrowserPersistResourceVisitor::EndVisit, + mDocument, NS_ERROR_FAILURE); + NS_DispatchToCurrentThread(errorLater); + } + mVisitor = nullptr; +} + +bool +WebBrowserPersistResourcesParent::Recv__delete__(const nsresult& aStatus) +{ + mVisitor->EndVisit(mDocument, aStatus); + mVisitor = nullptr; + return true; +} + +bool +WebBrowserPersistResourcesParent::RecvVisitResource(const nsCString& aURI) +{ + mVisitor->VisitResource(mDocument, aURI); + return true; +} + +bool +WebBrowserPersistResourcesParent::RecvVisitDocument(PWebBrowserPersistDocumentParent* aSubDocument) +{ + // Don't expose the subdocument to the visitor until it's ready + // (until the actor isn't in START state). + static_cast<WebBrowserPersistDocumentParent*>(aSubDocument) + ->SetOnReady(this); + return true; +} + +NS_IMETHODIMP +WebBrowserPersistResourcesParent::OnDocumentReady(nsIWebBrowserPersistDocument* aSubDocument) +{ + if (!mVisitor) { + return NS_ERROR_FAILURE; + } + mVisitor->VisitDocument(mDocument, aSubDocument); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistResourcesParent::OnError(nsresult aFailure) +{ + // Nothing useful to do but ignore the failed document. + return NS_OK; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.h b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.h new file mode 100644 index 000000000..9d70fd11b --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistResourcesParent.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WebBrowserPersistResourcesParent_h__ +#define WebBrowserPersistResourcesParent_h__ + +#include "mozilla/PWebBrowserPersistResourcesParent.h" + +#include "WebBrowserPersistDocumentParent.h" +#include "nsCOMPtr.h" +#include "nsIWebBrowserPersistDocument.h" + +namespace mozilla { + +class WebBrowserPersistResourcesParent final + : public PWebBrowserPersistResourcesParent + , public nsIWebBrowserPersistDocumentReceiver +{ +public: + WebBrowserPersistResourcesParent(nsIWebBrowserPersistDocument* aDocument, + nsIWebBrowserPersistResourceVisitor* aVisitor); + + virtual bool + RecvVisitResource(const nsCString& aURI) override; + + virtual bool + RecvVisitDocument(PWebBrowserPersistDocumentParent* aSubDocument) override; + + virtual bool + Recv__delete__(const nsresult& aStatus) override; + + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + NS_DECL_NSIWEBBROWSERPERSISTDOCUMENTRECEIVER + NS_DECL_ISUPPORTS + +private: + // Note: even if the XPIDL didn't need mDocument for visitor + // callbacks, this object still needs to hold a strong reference + // to it to defer actor subtree deletion until after the + // visitation is finished. + nsCOMPtr<nsIWebBrowserPersistDocument> mDocument; + nsCOMPtr<nsIWebBrowserPersistResourceVisitor> mVisitor; + + virtual ~WebBrowserPersistResourcesParent(); +}; + +} // namespace mozilla + +#endif // WebBrowserPersistResourcesParent_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.cpp new file mode 100644 index 000000000..2b6fcbde4 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.cpp @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WebBrowserPersistSerializeChild.h" + +#include <algorithm> + +#include "nsThreadUtils.h" +#include "ipc/IPCMessageUtils.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(WebBrowserPersistSerializeChild, + nsIWebBrowserPersistWriteCompletion, + nsIWebBrowserPersistURIMap, + nsIOutputStream) + +WebBrowserPersistSerializeChild::WebBrowserPersistSerializeChild(const WebBrowserPersistURIMap& aMap) +: mMap(aMap) +{ +} + +WebBrowserPersistSerializeChild::~WebBrowserPersistSerializeChild() +{ +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::OnFinish(nsIWebBrowserPersistDocument* aDocument, + nsIOutputStream* aStream, + const nsACString& aContentType, + nsresult aStatus) +{ + MOZ_ASSERT(aStream == this); + nsCString contentType(aContentType); + Send__delete__(this, contentType, aStatus); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::GetNumMappedURIs(uint32_t* aNum) +{ + *aNum = static_cast<uint32_t>(mMap.mapURIs().Length()); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::GetURIMapping(uint32_t aIndex, + nsACString& aMapFrom, + nsACString& aMapTo) +{ + if (aIndex >= mMap.mapURIs().Length()) { + return NS_ERROR_INVALID_ARG; + } + aMapFrom = mMap.mapURIs()[aIndex].mapFrom(); + aMapTo = mMap.mapURIs()[aIndex].mapTo(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::GetTargetBaseURI(nsACString& aURI) +{ + aURI = mMap.targetBaseURI(); + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::Close() +{ + NS_WARNING("WebBrowserPersistSerializeChild::Close()"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::Flush() +{ + NS_WARNING("WebBrowserPersistSerializeChild::Flush()"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::Write(const char* aBuf, uint32_t aCount, + uint32_t* aWritten) +{ + // Normally an nsIOutputStream would have to be thread-safe, but + // nsDocumentEncoder currently doesn't call this off the main + // thread (which also means it's difficult to test the + // thread-safety code this class doesn't yet have). + // + // This is *not* an NS_ERROR_NOT_IMPLEMENTED, because at this + // point we've probably already misused the non-thread-safe + // refcounting. + MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Fix this class to be thread-safe."); + + // Work around bug 1181433 by sending multiple messages if + // necessary to write the entire aCount bytes, even though + // nsIOutputStream.idl says we're allowed to do a short write. + const char* buf = aBuf; + uint32_t count = aCount; + *aWritten = 0; + while (count > 0) { + uint32_t toWrite = std::min(IPC::MAX_MESSAGE_SIZE, count); + nsTArray<uint8_t> arrayBuf; + // It would be nice if this extra copy could be avoided. + arrayBuf.AppendElements(buf, toWrite); + SendWriteData(Move(arrayBuf)); + *aWritten += toWrite; + buf += toWrite; + count -= toWrite; + } + return NS_OK; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::WriteFrom(nsIInputStream* aFrom, + uint32_t aCount, + uint32_t* aWritten) +{ + NS_WARNING("WebBrowserPersistSerializeChild::WriteFrom()"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::WriteSegments(nsReadSegmentFun aFun, + void* aCtx, + uint32_t aCount, + uint32_t* aWritten) +{ + NS_WARNING("WebBrowserPersistSerializeChild::WriteSegments()"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +WebBrowserPersistSerializeChild::IsNonBlocking(bool* aNonBlocking) +{ + // Writes will never fail with NS_BASE_STREAM_WOULD_BLOCK, so: + *aNonBlocking = false; + return NS_OK; +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.h b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.h new file mode 100644 index 000000000..ddca44ece --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeChild.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WebBrowserPersistSerializeChild_h__ +#define WebBrowserPersistSerializeChild_h__ + +#include "mozilla/PWebBrowserPersistSerializeChild.h" + +#include "mozilla/PWebBrowserPersistDocument.h" +#include "nsIWebBrowserPersistDocument.h" +#include "nsIOutputStream.h" + +namespace mozilla { + +class WebBrowserPersistSerializeChild final + : public PWebBrowserPersistSerializeChild + , public nsIWebBrowserPersistWriteCompletion + , public nsIWebBrowserPersistURIMap + , public nsIOutputStream +{ +public: + explicit WebBrowserPersistSerializeChild(const WebBrowserPersistURIMap& aMap); + + NS_DECL_NSIWEBBROWSERPERSISTWRITECOMPLETION + NS_DECL_NSIWEBBROWSERPERSISTURIMAP + NS_DECL_NSIOUTPUTSTREAM + NS_DECL_ISUPPORTS +private: + WebBrowserPersistURIMap mMap; + + virtual ~WebBrowserPersistSerializeChild(); +}; + +} // namespace mozilla + +#endif // WebBrowserPersistSerializeChild_h__ diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.cpp b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.cpp new file mode 100644 index 000000000..2f76ffb33 --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.cpp @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WebBrowserPersistSerializeParent.h" + +#include "nsReadableUtils.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +WebBrowserPersistSerializeParent::WebBrowserPersistSerializeParent( + nsIWebBrowserPersistDocument* aDocument, + nsIOutputStream* aStream, + nsIWebBrowserPersistWriteCompletion* aFinish) +: mDocument(aDocument) +, mStream(aStream) +, mFinish(aFinish) +, mOutputError(NS_OK) +{ + MOZ_ASSERT(aDocument); + MOZ_ASSERT(aStream); + MOZ_ASSERT(aFinish); +} + +WebBrowserPersistSerializeParent::~WebBrowserPersistSerializeParent() +{ +} + +bool +WebBrowserPersistSerializeParent::RecvWriteData(nsTArray<uint8_t>&& aData) +{ + if (NS_FAILED(mOutputError)) { + return true; + } + + uint32_t written = 0; + static_assert(sizeof(char) == sizeof(uint8_t), + "char must be (at least?) 8 bits"); + const char* data = reinterpret_cast<const char*>(aData.Elements()); + // nsIOutputStream::Write is allowed to return short writes. + while (written < aData.Length()) { + uint32_t writeReturn; + nsresult rv = mStream->Write(data + written, + aData.Length() - written, + &writeReturn); + if (NS_FAILED(rv)) { + mOutputError = rv; + return true; + } + written += writeReturn; + } + return true; +} + +bool +WebBrowserPersistSerializeParent::Recv__delete__(const nsCString& aContentType, + const nsresult& aStatus) +{ + if (NS_SUCCEEDED(mOutputError)) { + mOutputError = aStatus; + } + mFinish->OnFinish(mDocument, + mStream, + aContentType, + mOutputError); + mFinish = nullptr; + return true; +} + +void +WebBrowserPersistSerializeParent::ActorDestroy(ActorDestroyReason aWhy) +{ + if (mFinish) { + MOZ_ASSERT(aWhy != Deletion); + // See comment in WebBrowserPersistDocumentParent::ActorDestroy + // (or bug 1202887) for why this is deferred. + nsCOMPtr<nsIRunnable> errorLater = NewRunnableMethod + <nsCOMPtr<nsIWebBrowserPersistDocument>, nsCOMPtr<nsIOutputStream>, + nsCString, nsresult> + (mFinish, &nsIWebBrowserPersistWriteCompletion::OnFinish, + mDocument, mStream, EmptyCString(), NS_ERROR_FAILURE); + NS_DispatchToCurrentThread(errorLater); + mFinish = nullptr; + } +} + +} // namespace mozilla diff --git a/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.h b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.h new file mode 100644 index 000000000..452feef4b --- /dev/null +++ b/embedding/components/webbrowserpersist/WebBrowserPersistSerializeParent.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef WebBrowserPersistSerializeParent_h__ +#define WebBrowserPersistSerializeParent_h__ + +#include "mozilla/PWebBrowserPersistSerializeParent.h" + +#include "nsCOMPtr.h" +#include "nsIOutputStream.h" +#include "nsIWebBrowserPersistDocument.h" + +namespace mozilla { + +class WebBrowserPersistSerializeParent + : public PWebBrowserPersistSerializeParent +{ +public: + WebBrowserPersistSerializeParent( + nsIWebBrowserPersistDocument* aDocument, + nsIOutputStream* aStream, + nsIWebBrowserPersistWriteCompletion* aFinish); + virtual ~WebBrowserPersistSerializeParent(); + + virtual bool + RecvWriteData(nsTArray<uint8_t>&& aData) override; + + virtual bool + Recv__delete__(const nsCString& aContentType, + const nsresult& aStatus) override; + + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + +private: + // See also ...ReadParent::mDocument for the other reason this + // strong reference needs to be here. + nsCOMPtr<nsIWebBrowserPersistDocument> mDocument; + nsCOMPtr<nsIOutputStream> mStream; + nsCOMPtr<nsIWebBrowserPersistWriteCompletion> mFinish; + nsresult mOutputError; +}; + +} // namespace mozilla + +#endif // WebBrowserPersistSerializeParent_h__ diff --git a/embedding/components/webbrowserpersist/moz.build b/embedding/components/webbrowserpersist/moz.build new file mode 100644 index 000000000..0d4108388 --- /dev/null +++ b/embedding/components/webbrowserpersist/moz.build @@ -0,0 +1,49 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + 'nsCWebBrowserPersist.idl', + 'nsIWebBrowserPersist.idl', + 'nsIWebBrowserPersistable.idl', + 'nsIWebBrowserPersistDocument.idl', +] + +XPIDL_MODULE = 'webbrowserpersist' + +IPDL_SOURCES += [ + 'PWebBrowserPersistDocument.ipdl', + 'PWebBrowserPersistResources.ipdl', + 'PWebBrowserPersistSerialize.ipdl', +] + +SOURCES += [ + 'nsWebBrowserPersist.cpp', + 'WebBrowserPersistDocumentChild.cpp', + 'WebBrowserPersistDocumentParent.cpp', + 'WebBrowserPersistLocalDocument.cpp', + 'WebBrowserPersistRemoteDocument.cpp', + 'WebBrowserPersistResourcesChild.cpp', + 'WebBrowserPersistResourcesParent.cpp', + 'WebBrowserPersistSerializeChild.cpp', + 'WebBrowserPersistSerializeParent.cpp', +] + +EXPORTS.mozilla += [ + 'WebBrowserPersistDocumentChild.h', + 'WebBrowserPersistDocumentParent.h', + 'WebBrowserPersistLocalDocument.h', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' +LOCAL_INCLUDES += [ + '/dom/base', + '/dom/html', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/embedding/components/webbrowserpersist/nsCWebBrowserPersist.idl b/embedding/components/webbrowserpersist/nsCWebBrowserPersist.idl new file mode 100644 index 000000000..69f2db7ca --- /dev/null +++ b/embedding/components/webbrowserpersist/nsCWebBrowserPersist.idl @@ -0,0 +1,15 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIWebBrowserPersist.idl" + +%{ C++ +// {7E677795-C582-4cd1-9E8D-8271B3474D2A} +#define NS_WEBBROWSERPERSIST_CID \ +{ 0x7e677795, 0xc582, 0x4cd1, { 0x9e, 0x8d, 0x82, 0x71, 0xb3, 0x47, 0x4d, 0x2a } } +#define NS_WEBBROWSERPERSIST_CONTRACTID \ +"@mozilla.org/embedding/browser/nsWebBrowserPersist;1" +%} diff --git a/embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl b/embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl new file mode 100644 index 000000000..cd4bd8b69 --- /dev/null +++ b/embedding/components/webbrowserpersist/nsIWebBrowserPersist.idl @@ -0,0 +1,286 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsICancelable.idl" + +interface nsIURI; +interface nsIInputStream; +interface nsIDOMDocument; +interface nsIWebProgressListener; +interface nsIFile; +interface nsIChannel; +interface nsILoadContext; + +/** + * Interface for persisting DOM documents and URIs to local or remote storage. + */ +[scriptable, uuid(8cd752a4-60b1-42c3-a819-65c7a1138a28)] +interface nsIWebBrowserPersist : nsICancelable +{ + /** No special persistence behaviour. */ + const unsigned long PERSIST_FLAGS_NONE = 0; + /** Use cached data if present (skipping validation), else load from network */ + const unsigned long PERSIST_FLAGS_FROM_CACHE = 1; + /** Bypass the cached data. */ + const unsigned long PERSIST_FLAGS_BYPASS_CACHE = 2; + /** Ignore any redirected data (usually adverts). */ + const unsigned long PERSIST_FLAGS_IGNORE_REDIRECTED_DATA = 4; + /** Ignore IFRAME content (usually adverts). */ + const unsigned long PERSIST_FLAGS_IGNORE_IFRAMES = 8; + /** Do not run the incoming data through a content converter e.g. to decompress it */ + const unsigned long PERSIST_FLAGS_NO_CONVERSION = 16; + /** Replace existing files on the disk (use with due diligence!) */ + const unsigned long PERSIST_FLAGS_REPLACE_EXISTING_FILES = 32; + /** Don't modify or add base tags */ + const unsigned long PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS = 64; + /** Make changes to original dom rather than cloning nodes */ + const unsigned long PERSIST_FLAGS_FIXUP_ORIGINAL_DOM = 128; + /** Fix links relative to destination location (not origin) */ + const unsigned long PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION = 256; + /** Don't make any adjustments to links */ + const unsigned long PERSIST_FLAGS_DONT_FIXUP_LINKS = 512; + /** Force serialization of output (one file at a time; not concurrent) */ + const unsigned long PERSIST_FLAGS_SERIALIZE_OUTPUT = 1024; + /** Don't make any adjustments to filenames */ + const unsigned long PERSIST_FLAGS_DONT_CHANGE_FILENAMES = 2048; + /** Fail on broken inline links */ + const unsigned long PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS = 4096; + /** + * Automatically cleanup after a failed or cancelled operation, deleting all + * created files and directories. This flag does nothing for failed upload + * operations to remote servers. + */ + const unsigned long PERSIST_FLAGS_CLEANUP_ON_FAILURE = 8192; + /** + * Let the WebBrowserPersist decide whether the incoming data is encoded + * and whether it needs to go through a content converter e.g. to + * decompress it. + */ + const unsigned long PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION = 16384; + /** + * Append the downloaded data to the target file. + * This can only be used when persisting to a local file. + */ + const unsigned long PERSIST_FLAGS_APPEND_TO_FILE = 32768; + + /** + * Force relevant cookies to be sent with this load even if normally they + * wouldn't be. + */ + const unsigned long PERSIST_FLAGS_FORCE_ALLOW_COOKIES = 65536; + + /** + * Flags governing how data is fetched and saved from the network. + * It is best to set this value explicitly unless you are prepared + * to accept the default values. + */ + attribute unsigned long persistFlags; + + /** Persister is ready to save data */ + const unsigned long PERSIST_STATE_READY = 1; + /** Persister is saving data */ + const unsigned long PERSIST_STATE_SAVING = 2; + /** Persister has finished saving data */ + const unsigned long PERSIST_STATE_FINISHED = 3; + + /** + * Current state of the persister object. + */ + readonly attribute unsigned long currentState; + + /** + * Value indicating the success or failure of the persist + * operation. + * + * @throws NS_BINDING_ABORTED Operation cancelled. + * @throws NS_ERROR_FAILURE Non-specific failure. + */ + readonly attribute nsresult result; + + /** + * Callback listener for progress notifications. The object that the + * embbedder supplies may also implement nsIInterfaceRequestor and be + * prepared to return nsIAuthPrompt or other interfaces that may be required + * to download data. + * + * @see nsIAuthPrompt + * @see nsIInterfaceRequestor + */ + attribute nsIWebProgressListener progressListener; + + /** + * Save the specified URI to file. + * + * @param aURI URI to save to file. Some implementations of this interface + * may also support <CODE>nullptr</CODE> to imply the currently + * loaded URI. + * @param aCacheKey An object representing the URI in the cache or + * <CODE>nullptr</CODE>. This can be a necko cache key, + * an nsIWebPageDescriptor, or the currentDescriptor of an + * nsIWebPageDescriptor. + * @param aReferrer The referrer URI to pass with an HTTP request or + * <CODE>nullptr</CODE>. + * @param aReferrerPolicy The referrer policy for when and what to send via + * HTTP Referer header. Ignored if aReferrer is + * <CODE>nullptr</CODE>. Taken from REFERRER_POLICY + * constants in nsIHttpChannel. + * @param aPostData Post data to pass with an HTTP request or + * <CODE>nullptr</CODE>. + * @param aExtraHeaders Additional headers to supply with an HTTP request + * or <CODE>nullptr</CODE>. + * @param aFile Target file. This may be a nsIFile object or an + * nsIURI object with a file scheme or a scheme that + * supports uploading (e.g. ftp). + * @param aPrivacyContext A context from which the privacy status of this + * save operation can be determined. Must only be null + * in situations in which no such context is available + * (eg. the operation has no logical association with any + * window or document) + * + * @see nsIFile + * @see nsIURI + * @see nsIInputStream + * + * @throws NS_ERROR_INVALID_ARG One or more arguments was invalid. + */ + void saveURI(in nsIURI aURI, in nsISupports aCacheKey, + in nsIURI aReferrer, in unsigned long aReferrerPolicy, + in nsIInputStream aPostData, + in string aExtraHeaders, in nsISupports aFile, + in nsILoadContext aPrivacyContext); + + /** + * @param aIsPrivate Treat the save operation as private (ie. with + * regards to networking operations and persistence + * of intermediate data, etc.) + * @see saveURI for all other parameter descriptions + */ + void savePrivacyAwareURI(in nsIURI aURI, in nsISupports aCacheKey, + in nsIURI aReferrer, in unsigned long aReferrerPolicy, + in nsIInputStream aPostData, + in string aExtraHeaders, in nsISupports aFile, + in boolean aIsPrivate); + + /** + * Save a channel to a file. It must not be opened yet. + * @see saveURI + */ + void saveChannel(in nsIChannel aChannel, in nsISupports aFile); + + /** Output only the current selection as opposed to the whole document. */ + const unsigned long ENCODE_FLAGS_SELECTION_ONLY = 1; + /** + * For plaintext output. Convert html to plaintext that looks like the html. + * Implies wrap (except inside <pre>), since html wraps. + * HTML output: always do prettyprinting, ignoring existing formatting. + */ + const unsigned long ENCODE_FLAGS_FORMATTED = 2; + /** + * Output without formatting or wrapping the content. This flag + * may be used to preserve the original formatting as much as possible. + */ + const unsigned long ENCODE_FLAGS_RAW = 4; + /** Output only the body section, no HTML tags. */ + const unsigned long ENCODE_FLAGS_BODY_ONLY = 8; + /** Wrap even if when not doing formatted output (e.g. for text fields). */ + const unsigned long ENCODE_FLAGS_PREFORMATTED = 16; + /** Wrap documents at the specified column. */ + const unsigned long ENCODE_FLAGS_WRAP = 32; + /** + * For plaintext output. Output for format flowed (RFC 2646). This is used + * when converting to text for mail sending. This differs just slightly + * but in an important way from normal formatted, and that is that + * lines are space stuffed. This can't (correctly) be done later. + */ + const unsigned long ENCODE_FLAGS_FORMAT_FLOWED = 64; + /** Convert links to absolute links where possible. */ + const unsigned long ENCODE_FLAGS_ABSOLUTE_LINKS = 128; + + /** + * Attempt to encode entities standardized at W3C (HTML, MathML, etc). + * This is a catch-all flag for documents with mixed contents. Beware of + * interoperability issues. See below for other flags which might likely + * do what you want. + */ + const unsigned long ENCODE_FLAGS_ENCODE_W3C_ENTITIES = 256; + + /** + * Output with carriage return line breaks. May also be combined with + * ENCODE_FLAGS_LF_LINEBREAKS and if neither is specified, the platform + * default format is used. + */ + const unsigned long ENCODE_FLAGS_CR_LINEBREAKS = 512; + /** + * Output with linefeed line breaks. May also be combined with + * ENCODE_FLAGS_CR_LINEBREAKS and if neither is specified, the platform + * default format is used. + */ + const unsigned long ENCODE_FLAGS_LF_LINEBREAKS = 1024; + /** For plaintext output. Output the content of noscript elements. */ + const unsigned long ENCODE_FLAGS_NOSCRIPT_CONTENT = 2048; + /** For plaintext output. Output the content of noframes elements. */ + const unsigned long ENCODE_FLAGS_NOFRAMES_CONTENT = 4096; + + /** + * Encode basic entities, e.g. output instead of character code 0xa0. + * The basic set is just & < > " for interoperability + * with older products that don't support α and friends. + */ + const unsigned long ENCODE_FLAGS_ENCODE_BASIC_ENTITIES = 8192; + /** + * Encode Latin1 entities. This includes the basic set and + * accented letters between 128 and 255. + */ + const unsigned long ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES = 16384; + /** + * Encode HTML4 entities. This includes the basic set, accented + * letters, greek letters and certain special markup symbols. + */ + const unsigned long ENCODE_FLAGS_ENCODE_HTML_ENTITIES = 32768; + + /** + * Save the specified DOM document to file and optionally all linked files + * (e.g. images, CSS, JS & subframes). Do not call this method until the + * document has finished loading! + * + * @param aDocument Document to save to file. Some implementations of + * this interface may also support <CODE>nullptr</CODE> + * to imply the currently loaded document. Can be an + * nsIWebBrowserPersistDocument or nsIDOMDocument. + * @param aFile Target local file. This may be a nsIFile object or an + * nsIURI object with a file scheme or a scheme that + * supports uploading (e.g. ftp). + * @param aDataPath Path to directory where URIs linked to the document + * are saved or nullptr if no linked URIs should be saved. + * This may be a nsIFile object or an nsIURI object + * with a file scheme. + * @param aOutputContentType The desired MIME type format to save the + * document and all subdocuments into or nullptr to use + * the default behaviour. + * @param aEncodingFlags Flags to pass to the encoder. + * @param aWrapColumn For text documents, indicates the desired width to + * wrap text at. Parameter is ignored if wrapping is not + * specified by the encoding flags. + * + * @see nsIWebBrowserPersistDocument + * @see nsIWebBrowserPersistable + * @see nsIFile + * @see nsIURI + * + * @throws NS_ERROR_INVALID_ARG One or more arguments was invalid. + */ + void saveDocument(in nsISupports aDocument, + in nsISupports aFile, in nsISupports aDataPath, + in string aOutputContentType, in unsigned long aEncodingFlags, + in unsigned long aWrapColumn); + + /** + * Cancels the current operation. The caller is responsible for cleaning up + * partially written files or directories. This has the same effect as calling + * cancel with an argument of NS_BINDING_ABORTED. + */ + void cancelSave(); +}; diff --git a/embedding/components/webbrowserpersist/nsIWebBrowserPersistDocument.idl b/embedding/components/webbrowserpersist/nsIWebBrowserPersistDocument.idl new file mode 100644 index 000000000..ba5bea8b2 --- /dev/null +++ b/embedding/components/webbrowserpersist/nsIWebBrowserPersistDocument.idl @@ -0,0 +1,197 @@ +/* -*- Mode: IDL; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIDOMDocument; +interface nsIInputStream; +interface nsIOutputStream; +interface nsITabParent; +interface nsIWebBrowserPersistResourceVisitor; +interface nsIWebBrowserPersistWriteCompletion; + +/** + * Interface for the URI-mapping information that can be supplied when + * serializing the DOM of an nsIWebBrowserPersistDocument. + * + * @see nsIWebBrowserPersistDocument + */ +[scriptable, uuid(d52e8b93-2771-45e8-a5b0-6e12b667046b)] +interface nsIWebBrowserPersistURIMap : nsISupports +{ + /** + * The number of URI mappings. + */ + readonly attribute unsigned long numMappedURIs; + + /** + * Obtain the URI mapping at the given index, which must be less than + * numMappedURIs, as a pair of URI spec strings. + */ + void getURIMapping(in unsigned long aIndex, + out AUTF8String aMapFrom, + out AUTF8String aMapTo); + + /** + * The spec of the base URI that the document will have after it is + * serialized. + */ + readonly attribute AUTF8String targetBaseURI; +}; + +/** + * Interface representing a document that can be serialized with + * nsIWebBrowserPersist; it may or may not be in this process. Some + * information is exposed as attributes, which may or may not reflect + * changes made to the underlying document; most of these are + * self-explanatory from their names and types. + */ +[scriptable, uuid(74aa4918-5d15-46b6-9ccf-74f9696d721d)] +interface nsIWebBrowserPersistDocument : nsISupports +{ + readonly attribute boolean isPrivate; + readonly attribute AUTF8String documentURI; + readonly attribute AUTF8String baseURI; + readonly attribute ACString contentType; + readonly attribute ACString characterSet; + readonly attribute AString title; + readonly attribute AString referrer; + readonly attribute AString contentDisposition; + readonly attribute nsIInputStream postData; + + /** + * The cache key. Unlike in nsISHEntry, where it's wrapped in an + * nsISupportsPRUint32, this is just the integer. + */ + readonly attribute unsigned long cacheKey; + + /** + * This attribute is set by nsIWebBrowserPersist implementations to + * propagate persist flags that apply to the DOM traversal and + * serialization (rather than to managing file I/O). + */ + attribute unsigned long persistFlags; + + /** + * Walk the DOM searching for external resources needed to render it. + * The visitor callbacks may be called either before or after + * readResources returns. + * + * @see nsIWebBrowserPersistResourceVisitor + */ + void readResources(in nsIWebBrowserPersistResourceVisitor aVisitor); + + /** + * Serialize the document's DOM. + * + * @param aStream The output stream to write the document to. + * + * @param aURIMap Optional; specifies URI rewriting to perform on + * external references (as read by readResources). + * If given, also causes relative hyperlinks to be + * converted to absolute in the written text. + * + * @param aRequestedContentType + * The desired MIME type to save the document as; + * optional and defaults to the document's type. + * (If no encoder exists for that type, "text/html" + * is used instead.) + * + * @param aEncoderFlags Flags to pass to the encoder. + * + * @param aWrapColumn Desired text width, ignored if wrapping is not + * specified by the encoding flags, or if 0. + * + * @param aCompletion Callback invoked when writing is complete. + * It may be called either before or after writeContent + * returns. + * + * @see nsIDocumentEncoder + */ + void writeContent(in nsIOutputStream aStream, + in nsIWebBrowserPersistURIMap aURIMap, + in ACString aRequestedContentType, + in unsigned long aEncoderFlags, + in unsigned long aWrapColumn, + in nsIWebBrowserPersistWriteCompletion aCompletion); +}; + +/** + * Asynchronous visitor that receives external resources linked by an + * nsIWebBrowserPersistDocument and which are needed to render the + * document. + */ +[scriptable, uuid(8ce37706-b7d3-481a-be68-54f174fc0d0a)] +interface nsIWebBrowserPersistResourceVisitor : nsISupports +{ + /** + * Indicates a resource that is not a document; e.g., an image, script, + * or stylesheet. + * + * @param aDocument The document containing the reference. + * @param aURI The absolute URI spec for the referenced resource. + */ + void visitResource(in nsIWebBrowserPersistDocument aDocument, + in AUTF8String aURI); + /** + * Indicates a subdocument resource; e.g., a frame or iframe. + * + * @param aDocument The document containing the reference. + * @param aSubDocument The referenced document. + */ + void visitDocument(in nsIWebBrowserPersistDocument aDocument, + in nsIWebBrowserPersistDocument aSubDocument); + + /** + * Indicates that the document traversal is complete. + * + * @param aDocument The document that was being traversed. + * @param aStatus Indicates whether the traversal encountered an error. + */ + void endVisit(in nsIWebBrowserPersistDocument aDocument, + in nsresult aStatus); +}; + +/** + * Asynchronous callback for when nsIWebBrowserPersistDocument is finished + * serializing the document's DOM. + */ +[scriptable, function, uuid(a07e6892-38ae-4207-8340-7fa6ec446ed6)] +interface nsIWebBrowserPersistWriteCompletion : nsISupports +{ + /** + * Indicates that serialization is finished. + * + * @param aDocument The document that was being serialized. + * + * @param aStream The stream that was being written to. If it + * needs to be closed, the callback must do that; + * the serialization process leaves it open. + * + * @param aContentType The content type with which the document was + * actually serialized; this may be useful to set + * metadata on the result, or if uploading it. + * + * @param aStatus Indicates whether serialization encountered an error. + */ + void onFinish(in nsIWebBrowserPersistDocument aDocument, + in nsIOutputStream aStream, + in ACString aContentType, + in nsresult aStatus); +}; + +/** + * Asynchronous callback for creating a persistable document from some + * other object. + * + * @see nsIWebBrowserPersistable. + */ +[scriptable, uuid(321e3174-594f-4036-b7be-791b821bd376)] +interface nsIWebBrowserPersistDocumentReceiver : nsISupports +{ + void onDocumentReady(in nsIWebBrowserPersistDocument aDocument); + void onError(in nsresult aFailure); +}; diff --git a/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl b/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl new file mode 100644 index 000000000..39ea6b33b --- /dev/null +++ b/embedding/components/webbrowserpersist/nsIWebBrowserPersistable.idl @@ -0,0 +1,41 @@ +/* -*- Mode: IDL; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIWebBrowserPersistDocumentReceiver; + +/** + * Interface for objects which represent a document that can be + * serialized with nsIWebBrowserPersist. This interface is + * asynchronous because the actual document can be in another process + * (e.g., if this object is an nsFrameLoader for an out-of-process + * frame). + * + * Warning: this is currently implemented only by nsFrameLoader, and + * may change in the future to become more frame-loader-specific or be + * merged into nsIFrameLoader. See bug 1101100 comment #34. + * + * @see nsIWebBrowserPersistDocumentReceiver + * @see nsIWebBrowserPersistDocument + * @see nsIWebBrowserPersist + * + * @param aOuterWindowID + * The outer window ID of the subframe we'd like to persist. + * If set at 0, nsIWebBrowserPersistable will attempt to persist + * the top-level document. If the outer window ID is for a subframe + * that does not exist, or is not held beneath the nsIWebBrowserPersistable, + * aRecv's onError method will be called with NS_ERROR_NO_CONTENT. + * @param aRecv + * The nsIWebBrowserPersistDocumentReceiver is a callback that + * will be fired once the document is ready for persisting. + */ +[scriptable, uuid(f4c3fa8e-83e9-49f8-ac6f-951fc7541fe4)] +interface nsIWebBrowserPersistable : nsISupports +{ + void startPersistence(in unsigned long long aOuterWindowID, + in nsIWebBrowserPersistDocumentReceiver aRecv); +}; diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp new file mode 100644 index 000000000..de85a59bb --- /dev/null +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.cpp @@ -0,0 +1,2812 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" + +#include "nspr.h" + +#include "nsIFileStreams.h" // New Necko file streams +#include <algorithm> + +#include "nsAutoPtr.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIPrivateBrowsingChannel.h" +#include "nsComponentManagerUtils.h" +#include "nsIComponentRegistrar.h" +#include "nsIStorageStream.h" +#include "nsISeekableStream.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIEncodedChannel.h" +#include "nsIUploadChannel.h" +#include "nsICacheInfoChannel.h" +#include "nsIFileChannel.h" +#include "nsEscape.h" +#include "nsUnicharUtils.h" +#include "nsIStringEnumerator.h" +#include "nsCRT.h" +#include "nsContentCID.h" +#include "nsStreamUtils.h" + +#include "nsCExternalHandlerService.h" + +#include "nsIURL.h" +#include "nsIFileURL.h" +#include "nsIWebProgressListener.h" +#include "nsIAuthPrompt.h" +#include "nsIPrompt.h" +#include "nsISHEntry.h" +#include "nsIWebPageDescriptor.h" +#include "nsIFormControl.h" +#include "nsContentUtils.h" + +#include "nsIImageLoadingContent.h" + +#include "ftpCore.h" +#include "nsITransport.h" +#include "nsISocketTransport.h" +#include "nsIStringBundle.h" +#include "nsIProtocolHandler.h" + +#include "nsIWebBrowserPersistable.h" +#include "nsWebBrowserPersist.h" +#include "WebBrowserPersistLocalDocument.h" + +#include "nsIContent.h" +#include "nsIMIMEInfo.h" +#include "mozilla/dom/HTMLInputElement.h" +#include "mozilla/dom/HTMLSharedElement.h" +#include "mozilla/dom/HTMLSharedObjectElement.h" + +using namespace mozilla; +using namespace mozilla::dom; + +// Buffer file writes in 32kb chunks +#define BUFFERED_OUTPUT_SIZE (1024 * 32) + +struct nsWebBrowserPersist::WalkData +{ + nsCOMPtr<nsIWebBrowserPersistDocument> mDocument; + nsCOMPtr<nsIURI> mFile; + nsCOMPtr<nsIURI> mDataPath; +}; + +// Information about a DOM document +struct nsWebBrowserPersist::DocData +{ + nsCOMPtr<nsIURI> mBaseURI; + nsCOMPtr<nsIWebBrowserPersistDocument> mDocument; + nsCOMPtr<nsIURI> mFile; + nsCString mCharset; +}; + +// Information about a URI +struct nsWebBrowserPersist::URIData +{ + bool mNeedsPersisting; + bool mSaved; + bool mIsSubFrame; + bool mDataPathIsRelative; + bool mNeedsFixup; + nsString mFilename; + nsString mSubFrameExt; + nsCOMPtr<nsIURI> mFile; + nsCOMPtr<nsIURI> mDataPath; + nsCOMPtr<nsIURI> mRelativeDocumentURI; + nsCString mRelativePathToData; + nsCString mCharset; + + nsresult GetLocalURI(nsIURI *targetBaseURI, nsCString& aSpecOut); +}; + +// Information about the output stream +struct nsWebBrowserPersist::OutputData +{ + nsCOMPtr<nsIURI> mFile; + nsCOMPtr<nsIURI> mOriginalLocation; + nsCOMPtr<nsIOutputStream> mStream; + int64_t mSelfProgress; + int64_t mSelfProgressMax; + bool mCalcFileExt; + + OutputData(nsIURI *aFile, nsIURI *aOriginalLocation, bool aCalcFileExt) : + mFile(aFile), + mOriginalLocation(aOriginalLocation), + mSelfProgress(0), + mSelfProgressMax(10000), + mCalcFileExt(aCalcFileExt) + { + } + ~OutputData() + { + if (mStream) + { + mStream->Close(); + } + } +}; + +struct nsWebBrowserPersist::UploadData +{ + nsCOMPtr<nsIURI> mFile; + int64_t mSelfProgress; + int64_t mSelfProgressMax; + + explicit UploadData(nsIURI *aFile) : + mFile(aFile), + mSelfProgress(0), + mSelfProgressMax(10000) + { + } +}; + +struct nsWebBrowserPersist::CleanupData +{ + nsCOMPtr<nsIFile> mFile; + // Snapshot of what the file actually is at the time of creation so that if + // it transmutes into something else later on it can be ignored. For example, + // catch files that turn into dirs or vice versa. + bool mIsDirectory; +}; + +class nsWebBrowserPersist::OnWalk final + : public nsIWebBrowserPersistResourceVisitor +{ +public: + OnWalk(nsWebBrowserPersist* aParent, nsIURI* aFile, nsIFile* aDataPath) + : mParent(aParent) + , mFile(aFile) + , mDataPath(aDataPath) + { } + + NS_DECL_NSIWEBBROWSERPERSISTRESOURCEVISITOR + NS_DECL_ISUPPORTS +private: + RefPtr<nsWebBrowserPersist> mParent; + nsCOMPtr<nsIURI> mFile; + nsCOMPtr<nsIFile> mDataPath; + + virtual ~OnWalk() { } +}; + +NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWalk, + nsIWebBrowserPersistResourceVisitor) + +class nsWebBrowserPersist::OnWrite final + : public nsIWebBrowserPersistWriteCompletion +{ +public: + OnWrite(nsWebBrowserPersist* aParent, + nsIURI* aFile, + nsIFile* aLocalFile) + : mParent(aParent) + , mFile(aFile) + , mLocalFile(aLocalFile) + { } + + NS_DECL_NSIWEBBROWSERPERSISTWRITECOMPLETION + NS_DECL_ISUPPORTS +private: + RefPtr<nsWebBrowserPersist> mParent; + nsCOMPtr<nsIURI> mFile; + nsCOMPtr<nsIFile> mLocalFile; + + virtual ~OnWrite() { } +}; + +NS_IMPL_ISUPPORTS(nsWebBrowserPersist::OnWrite, + nsIWebBrowserPersistWriteCompletion) + +class nsWebBrowserPersist::FlatURIMap final + : public nsIWebBrowserPersistURIMap +{ +public: + explicit FlatURIMap(const nsACString& aTargetBase) + : mTargetBase(aTargetBase) { } + + void Add(const nsACString& aMapFrom, const nsACString& aMapTo) { + mMapFrom.AppendElement(aMapFrom); + mMapTo.AppendElement(aMapTo); + } + + NS_DECL_NSIWEBBROWSERPERSISTURIMAP + NS_DECL_ISUPPORTS + +private: + nsTArray<nsCString> mMapFrom; + nsTArray<nsCString> mMapTo; + nsCString mTargetBase; + + virtual ~FlatURIMap() { } +}; + +NS_IMPL_ISUPPORTS(nsWebBrowserPersist::FlatURIMap, nsIWebBrowserPersistURIMap) + +NS_IMETHODIMP +nsWebBrowserPersist::FlatURIMap::GetNumMappedURIs(uint32_t* aNum) +{ + MOZ_ASSERT(mMapFrom.Length() == mMapTo.Length()); + *aNum = mMapTo.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserPersist::FlatURIMap::GetTargetBaseURI(nsACString& aTargetBase) +{ + aTargetBase = mTargetBase; + return NS_OK; +} + +NS_IMETHODIMP +nsWebBrowserPersist::FlatURIMap::GetURIMapping(uint32_t aIndex, + nsACString& aMapFrom, + nsACString& aMapTo) +{ + MOZ_ASSERT(mMapFrom.Length() == mMapTo.Length()); + if (aIndex >= mMapTo.Length()) { + return NS_ERROR_INVALID_ARG; + } + aMapFrom = mMapFrom[aIndex]; + aMapTo = mMapTo[aIndex]; + return NS_OK; +} + + +// Maximum file length constant. The max file name length is +// volume / server dependent but it is difficult to obtain +// that information. Instead this constant is a reasonable value that +// modern systems should able to cope with. +const uint32_t kDefaultMaxFilenameLength = 64; + +// Default flags for persistence +const uint32_t kDefaultPersistFlags = + nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION | + nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES; + +// String bundle where error messages come from +const char *kWebBrowserPersistStringBundle = + "chrome://global/locale/nsWebBrowserPersist.properties"; + +nsWebBrowserPersist::nsWebBrowserPersist() : + mCurrentDataPathIsRelative(false), + mCurrentThingsToPersist(0), + mFirstAndOnlyUse(true), + mSavingDocument(false), + mCancel(false), + mCompleted(false), + mStartSaving(false), + mReplaceExisting(true), + mSerializingOutput(false), + mIsPrivate(false), + mPersistFlags(kDefaultPersistFlags), + mPersistResult(NS_OK), + mTotalCurrentProgress(0), + mTotalMaxProgress(0), + mWrapColumn(72), + mEncodingFlags(0) +{ +} + +nsWebBrowserPersist::~nsWebBrowserPersist() +{ + Cleanup(); +} + +//***************************************************************************** +// nsWebBrowserPersist::nsISupports +//***************************************************************************** + +NS_IMPL_ADDREF(nsWebBrowserPersist) +NS_IMPL_RELEASE(nsWebBrowserPersist) + +NS_INTERFACE_MAP_BEGIN(nsWebBrowserPersist) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebBrowserPersist) + NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersist) + NS_INTERFACE_MAP_ENTRY(nsICancelable) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIStreamListener) + NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) + NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink) +NS_INTERFACE_MAP_END + + +//***************************************************************************** +// nsWebBrowserPersist::nsIInterfaceRequestor +//***************************************************************************** + +NS_IMETHODIMP nsWebBrowserPersist::GetInterface(const nsIID & aIID, void **aIFace) +{ + NS_ENSURE_ARG_POINTER(aIFace); + + *aIFace = nullptr; + + nsresult rv = QueryInterface(aIID, aIFace); + if (NS_SUCCEEDED(rv)) + { + return rv; + } + + if (mProgressListener && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) + || aIID.Equals(NS_GET_IID(nsIPrompt)))) + { + mProgressListener->QueryInterface(aIID, aIFace); + if (*aIFace) + return NS_OK; + } + + nsCOMPtr<nsIInterfaceRequestor> req = do_QueryInterface(mProgressListener); + if (req) + { + return req->GetInterface(aIID, aIFace); + } + + return NS_ERROR_NO_INTERFACE; +} + + +//***************************************************************************** +// nsWebBrowserPersist::nsIWebBrowserPersist +//***************************************************************************** + +NS_IMETHODIMP nsWebBrowserPersist::GetPersistFlags(uint32_t *aPersistFlags) +{ + NS_ENSURE_ARG_POINTER(aPersistFlags); + *aPersistFlags = mPersistFlags; + return NS_OK; +} +NS_IMETHODIMP nsWebBrowserPersist::SetPersistFlags(uint32_t aPersistFlags) +{ + mPersistFlags = aPersistFlags; + mReplaceExisting = (mPersistFlags & PERSIST_FLAGS_REPLACE_EXISTING_FILES) ? true : false; + mSerializingOutput = (mPersistFlags & PERSIST_FLAGS_SERIALIZE_OUTPUT) ? true : false; + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::GetCurrentState(uint32_t *aCurrentState) +{ + NS_ENSURE_ARG_POINTER(aCurrentState); + if (mCompleted) + { + *aCurrentState = PERSIST_STATE_FINISHED; + } + else if (mFirstAndOnlyUse) + { + *aCurrentState = PERSIST_STATE_SAVING; + } + else + { + *aCurrentState = PERSIST_STATE_READY; + } + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::GetResult(nsresult *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mPersistResult; + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::GetProgressListener( + nsIWebProgressListener * *aProgressListener) +{ + NS_ENSURE_ARG_POINTER(aProgressListener); + *aProgressListener = mProgressListener; + NS_IF_ADDREF(*aProgressListener); + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::SetProgressListener( + nsIWebProgressListener * aProgressListener) +{ + mProgressListener = aProgressListener; + mProgressListener2 = do_QueryInterface(aProgressListener); + mEventSink = do_GetInterface(aProgressListener); + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::SaveURI( + nsIURI *aURI, nsISupports *aCacheKey, + nsIURI *aReferrer, uint32_t aReferrerPolicy, + nsIInputStream *aPostData, const char *aExtraHeaders, + nsISupports *aFile, nsILoadContext* aPrivacyContext) +{ + return SavePrivacyAwareURI(aURI, aCacheKey, aReferrer, aReferrerPolicy, + aPostData, aExtraHeaders, aFile, + aPrivacyContext && aPrivacyContext->UsePrivateBrowsing()); +} + +NS_IMETHODIMP nsWebBrowserPersist::SavePrivacyAwareURI( + nsIURI *aURI, nsISupports *aCacheKey, + nsIURI *aReferrer, uint32_t aReferrerPolicy, + nsIInputStream *aPostData, const char *aExtraHeaders, + nsISupports *aFile, bool aIsPrivate) +{ + NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE); + mFirstAndOnlyUse = false; // Stop people from reusing this object! + + nsCOMPtr<nsIURI> fileAsURI; + nsresult rv; + rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); + + // SaveURI doesn't like broken uris. + mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS; + rv = SaveURIInternal(aURI, aCacheKey, aReferrer, aReferrerPolicy, + aPostData, aExtraHeaders, fileAsURI, false, aIsPrivate); + return NS_FAILED(rv) ? rv : NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::SaveChannel( + nsIChannel *aChannel, nsISupports *aFile) +{ + NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE); + mFirstAndOnlyUse = false; // Stop people from reusing this object! + + nsCOMPtr<nsIURI> fileAsURI; + nsresult rv; + rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); + + rv = aChannel->GetURI(getter_AddRefs(mURI)); + NS_ENSURE_SUCCESS(rv, rv); + + // SaveURI doesn't like broken uris. + mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS; + rv = SaveChannelInternal(aChannel, fileAsURI, false); + return NS_FAILED(rv) ? rv : NS_OK; +} + + +NS_IMETHODIMP nsWebBrowserPersist::SaveDocument( + nsISupports *aDocument, nsISupports *aFile, nsISupports *aDataPath, + const char *aOutputContentType, uint32_t aEncodingFlags, uint32_t aWrapColumn) +{ + NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE); + mFirstAndOnlyUse = false; // Stop people from reusing this object! + + // We need a STATE_IS_NETWORK start/stop pair to bracket the + // notification callbacks. For a whole document we generate those + // here and in EndDownload(), but for the single-request methods + // that's done in On{Start,Stop}Request instead. + mSavingDocument = true; + + NS_ENSURE_ARG_POINTER(aDocument); + NS_ENSURE_ARG_POINTER(aFile); + + nsCOMPtr<nsIURI> fileAsURI; + nsCOMPtr<nsIURI> datapathAsURI; + nsresult rv; + + rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); + if (aDataPath) + { + rv = GetValidURIFromObject(aDataPath, getter_AddRefs(datapathAsURI)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); + } + + mWrapColumn = aWrapColumn; + mEncodingFlags = aEncodingFlags; + + if (aOutputContentType) + { + mContentType.AssignASCII(aOutputContentType); + } + + // State start notification + if (mProgressListener) { + mProgressListener->OnStateChange(nullptr, nullptr, + nsIWebProgressListener::STATE_START + | nsIWebProgressListener::STATE_IS_NETWORK, NS_OK); + } + + nsCOMPtr<nsIWebBrowserPersistDocument> doc = do_QueryInterface(aDocument); + if (!doc) { + nsCOMPtr<nsIDocument> localDoc = do_QueryInterface(aDocument); + if (localDoc) { + doc = new mozilla::WebBrowserPersistLocalDocument(localDoc); + } else { + rv = NS_ERROR_NO_INTERFACE; + } + } + if (doc) { + rv = SaveDocumentInternal(doc, fileAsURI, datapathAsURI); + } + if (NS_FAILED(rv)) { + SendErrorStatusChange(true, rv, nullptr, mURI); + EndDownload(rv); + } + return rv; +} + +NS_IMETHODIMP nsWebBrowserPersist::Cancel(nsresult aReason) +{ + mCancel = true; + EndDownload(aReason); + return NS_OK; +} + + +NS_IMETHODIMP nsWebBrowserPersist::CancelSave() +{ + return Cancel(NS_BINDING_ABORTED); +} + + +nsresult +nsWebBrowserPersist::StartUpload(nsIStorageStream *storStream, + nsIURI *aDestinationURI, const nsACString &aContentType) +{ + // setup the upload channel if the destination is not local + nsCOMPtr<nsIInputStream> inputstream; + nsresult rv = storStream->NewInputStream(0, getter_AddRefs(inputstream)); + NS_ENSURE_TRUE(inputstream, NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + return StartUpload(inputstream, aDestinationURI, aContentType); +} + +nsresult +nsWebBrowserPersist::StartUpload(nsIInputStream *aInputStream, + nsIURI *aDestinationURI, const nsACString &aContentType) +{ + nsCOMPtr<nsIChannel> destChannel; + CreateChannelFromURI(aDestinationURI, getter_AddRefs(destChannel)); + nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(destChannel)); + NS_ENSURE_TRUE(uploadChannel, NS_ERROR_FAILURE); + + // Set the upload stream + // NOTE: ALL data must be available in "inputstream" + nsresult rv = uploadChannel->SetUploadStream(aInputStream, aContentType, -1); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + rv = destChannel->AsyncOpen2(this); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // add this to the upload list + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(destChannel); + mUploadList.Put(keyPtr, new UploadData(aDestinationURI)); + + return NS_OK; +} + +void +nsWebBrowserPersist::SerializeNextFile() +{ + nsresult rv = NS_OK; + MOZ_ASSERT(mWalkStack.Length() == 0); + + // First, handle gathered URIs. + // Count how many URIs in the URI map require persisting + uint32_t urisToPersist = 0; + if (mURIMap.Count() > 0) { + // This is potentially O(n^2), when taking into account the + // number of times this method is called. If it becomes a + // bottleneck, the count of not-yet-persisted URIs could be + // maintained separately. + for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) { + URIData *data = iter.UserData(); + if (data->mNeedsPersisting && !data->mSaved) { + urisToPersist++; + } + } + } + + if (urisToPersist > 0) { + // Persist each file in the uri map. The document(s) + // will be saved after the last one of these is saved. + for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) { + URIData *data = iter.UserData(); + + if (!data->mNeedsPersisting || data->mSaved) { + continue; + } + + nsresult rv; + + // Create a URI from the key. + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), iter.Key(), + data->mCharset.get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + // Make a URI to save the data to. + nsCOMPtr<nsIURI> fileAsURI; + rv = data->mDataPath->Clone(getter_AddRefs(fileAsURI)); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + rv = AppendPathToURI(fileAsURI, data->mFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + // The Referrer Policy doesn't matter here since the referrer is + // nullptr. + rv = SaveURIInternal(uri, nullptr, nullptr, + mozilla::net::RP_Default, nullptr, nullptr, + fileAsURI, true, mIsPrivate); + // If SaveURIInternal fails, then it will have called EndDownload, + // which means that |data| is no longer valid memory. We MUST bail. + if (NS_WARN_IF(NS_FAILED(rv))) { + break; + } + + if (rv == NS_OK) { + // Store the actual object because once it's persisted this + // will be fixed up with the right file extension. + data->mFile = fileAsURI; + data->mSaved = true; + } else { + data->mNeedsFixup = false; + } + + if (mSerializingOutput) { + break; + } + } + } + + // If there are downloads happening, wait until they're done; the + // OnStopRequest handler will call this method again. + if (mOutputMap.Count() > 0) { + return; + } + + // If serializing, also wait until last upload is done. + if (mSerializingOutput && mUploadList.Count() > 0) { + return; + } + + // If there are also no more documents, then we're done. + if (mDocList.Length() == 0) { + // ...or not quite done, if there are still uploads. + if (mUploadList.Count() > 0) { + return; + } + // Finish and clean things up. Defer this because the caller + // may have been expecting to use the listeners that that + // method will clear. + NS_DispatchToCurrentThread(NewRunnableMethod(this, + &nsWebBrowserPersist::FinishDownload)); + return; + } + + // There are no URIs to save, so just save the next document. + mStartSaving = true; + mozilla::UniquePtr<DocData> docData(mDocList.ElementAt(0)); + mDocList.RemoveElementAt(0); // O(n^2) but probably doesn't matter. + MOZ_ASSERT(docData); + if (!docData) { + EndDownload(NS_ERROR_FAILURE); + return; + } + + mCurrentBaseURI = docData->mBaseURI; + mCurrentCharset = docData->mCharset; + mTargetBaseURI = docData->mFile; + + // Save the document, fixing it up with the new URIs as we do + + nsAutoCString targetBaseSpec; + if (mTargetBaseURI) { + rv = mTargetBaseURI->GetSpec(targetBaseSpec); + if (NS_FAILED(rv)) { + SendErrorStatusChange(true, rv, nullptr, nullptr); + EndDownload(rv); + return; + } + } + + // mFlatURIMap must be rebuilt each time through SerializeNextFile, as + // mTargetBaseURI is used to create the relative URLs and will be different + // with each serialized document. + RefPtr<FlatURIMap> flatMap = new FlatURIMap(targetBaseSpec); + for (auto iter = mURIMap.Iter(); !iter.Done(); iter.Next()) { + nsAutoCString mapTo; + nsresult rv = iter.UserData()->GetLocalURI(mTargetBaseURI, mapTo); + if (NS_SUCCEEDED(rv) || !mapTo.IsVoid()) { + flatMap->Add(iter.Key(), mapTo); + } + } + mFlatURIMap = flatMap.forget(); + + nsCOMPtr<nsIFile> localFile; + GetLocalFileFromURI(docData->mFile, getter_AddRefs(localFile)); + if (localFile) { + // if we're not replacing an existing file but the file + // exists, something is wrong + bool fileExists = false; + rv = localFile->Exists(&fileExists); + if (NS_SUCCEEDED(rv) && !mReplaceExisting && fileExists) { + rv = NS_ERROR_FILE_ALREADY_EXISTS; + } + if (NS_FAILED(rv)) { + SendErrorStatusChange(false, rv, nullptr, docData->mFile); + EndDownload(rv); + return; + } + } + nsCOMPtr<nsIOutputStream> outputStream; + rv = MakeOutputStream(docData->mFile, getter_AddRefs(outputStream)); + if (NS_SUCCEEDED(rv) && !outputStream) { + rv = NS_ERROR_FAILURE; + } + if (NS_FAILED(rv)) { + SendErrorStatusChange(false, rv, nullptr, docData->mFile); + EndDownload(rv); + return; + } + + RefPtr<OnWrite> finish = new OnWrite(this, docData->mFile, localFile); + rv = docData->mDocument->WriteContent(outputStream, + mFlatURIMap, + NS_ConvertUTF16toUTF8(mContentType), + mEncodingFlags, + mWrapColumn, + finish); + if (NS_FAILED(rv)) { + SendErrorStatusChange(false, rv, nullptr, docData->mFile); + EndDownload(rv); + } +} + +NS_IMETHODIMP +nsWebBrowserPersist::OnWrite::OnFinish(nsIWebBrowserPersistDocument* aDoc, + nsIOutputStream *aStream, + const nsACString& aContentType, + nsresult aStatus) +{ + nsresult rv = aStatus; + + if (NS_FAILED(rv)) { + mParent->SendErrorStatusChange(false, rv, nullptr, mFile); + mParent->EndDownload(rv); + return NS_OK; + } + if (!mLocalFile) { + nsCOMPtr<nsIStorageStream> storStream(do_QueryInterface(aStream)); + if (storStream) { + aStream->Close(); + rv = mParent->StartUpload(storStream, mFile, aContentType); + if (NS_FAILED(rv)) { + mParent->SendErrorStatusChange(false, rv, nullptr, mFile); + mParent->EndDownload(rv); + } + // Either we failed and we're done, or we're uploading and + // the OnStopRequest callback is responsible for the next + // SerializeNextFile(). + return NS_OK; + } + } + NS_DispatchToCurrentThread(NewRunnableMethod(mParent, + &nsWebBrowserPersist::SerializeNextFile)); + return NS_OK; +} + +//***************************************************************************** +// nsWebBrowserPersist::nsIRequestObserver +//***************************************************************************** + +NS_IMETHODIMP nsWebBrowserPersist::OnStartRequest( + nsIRequest* request, nsISupports *ctxt) +{ + if (mProgressListener) + { + uint32_t stateFlags = nsIWebProgressListener::STATE_START | + nsIWebProgressListener::STATE_IS_REQUEST; + if (!mSavingDocument) { + stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK; + } + mProgressListener->OnStateChange(nullptr, request, stateFlags, NS_OK); + } + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); + + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request); + OutputData *data = mOutputMap.Get(keyPtr); + + // NOTE: This code uses the channel as a hash key so it will not + // recognize redirected channels because the key is not the same. + // When that happens we remove and add the data entry to use the + // new channel as the hash key. + if (!data) + { + UploadData *upData = mUploadList.Get(keyPtr); + if (!upData) + { + // Redirect? Try and fixup the output table + nsresult rv = FixRedirectedChannelEntry(channel); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Should be able to find the data after fixup unless redirects + // are disabled. + data = mOutputMap.Get(keyPtr); + if (!data) + { + return NS_ERROR_FAILURE; + } + } + } + + if (data && data->mFile) + { + // If PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION is set in mPersistFlags, + // try to determine whether this channel needs to apply Content-Encoding + // conversions. + NS_ASSERTION(!((mPersistFlags & PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION) && + (mPersistFlags & PERSIST_FLAGS_NO_CONVERSION)), + "Conflict in persist flags: both AUTODETECT and NO_CONVERSION set"); + if (mPersistFlags & PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION) + SetApplyConversionIfNeeded(channel); + + if (data->mCalcFileExt && !(mPersistFlags & PERSIST_FLAGS_DONT_CHANGE_FILENAMES)) + { + // this is the first point at which the server can tell us the mimetype + CalculateAndAppendFileExt(data->mFile, channel, data->mOriginalLocation); + + // now make filename conformant and unique + CalculateUniqueFilename(data->mFile); + } + + // compare uris and bail before we add to output map if they are equal + bool isEqual = false; + if (NS_SUCCEEDED(data->mFile->Equals(data->mOriginalLocation, &isEqual)) + && isEqual) + { + // remove from output map + mOutputMap.Remove(keyPtr); + + // cancel; we don't need to know any more + // stop request will get called + request->Cancel(NS_BINDING_ABORTED); + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::OnStopRequest( + nsIRequest* request, nsISupports *ctxt, nsresult status) +{ + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request); + OutputData *data = mOutputMap.Get(keyPtr); + if (data) { + if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(status)) { + SendErrorStatusChange(true, status, request, data->mFile); + } + + // This will automatically close the output stream + mOutputMap.Remove(keyPtr); + } else { + // if we didn't find the data in mOutputMap, try mUploadList + UploadData *upData = mUploadList.Get(keyPtr); + if (upData) { + mUploadList.Remove(keyPtr); + } + } + + // Do more work. + SerializeNextFile(); + + if (mProgressListener) { + uint32_t stateFlags = nsIWebProgressListener::STATE_STOP | + nsIWebProgressListener::STATE_IS_REQUEST; + if (!mSavingDocument) { + stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK; + } + mProgressListener->OnStateChange(nullptr, request, stateFlags, status); + } + + return NS_OK; +} + +//***************************************************************************** +// nsWebBrowserPersist::nsIStreamListener +//***************************************************************************** + +NS_IMETHODIMP +nsWebBrowserPersist::OnDataAvailable( + nsIRequest* request, nsISupports *aContext, nsIInputStream *aIStream, + uint64_t aOffset, uint32_t aLength) +{ + bool cancel = mCancel; + if (!cancel) + { + nsresult rv = NS_OK; + uint32_t bytesRemaining = aLength; + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); + + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request); + OutputData *data = mOutputMap.Get(keyPtr); + if (!data) { + // might be uploadData; consume necko's buffer and bail... + uint32_t n; + return aIStream->ReadSegments(NS_DiscardSegment, nullptr, aLength, &n); + } + + bool readError = true; + + // Make the output stream + if (!data->mStream) + { + rv = MakeOutputStream(data->mFile, getter_AddRefs(data->mStream)); + if (NS_FAILED(rv)) + { + readError = false; + cancel = true; + } + } + + // Read data from the input and write to the output + char buffer[8192]; + uint32_t bytesRead; + while (!cancel && bytesRemaining) + { + readError = true; + rv = aIStream->Read(buffer, + std::min(uint32_t(sizeof(buffer)), bytesRemaining), + &bytesRead); + if (NS_SUCCEEDED(rv)) + { + readError = false; + // Write out the data until something goes wrong, or, it is + // all written. We loop because for some errors (e.g., disk + // full), we get NS_OK with some bytes written, then an error. + // So, we want to write again in that case to get the actual + // error code. + const char *bufPtr = buffer; // Where to write from. + while (NS_SUCCEEDED(rv) && bytesRead) + { + uint32_t bytesWritten = 0; + rv = data->mStream->Write(bufPtr, bytesRead, &bytesWritten); + if (NS_SUCCEEDED(rv)) + { + bytesRead -= bytesWritten; + bufPtr += bytesWritten; + bytesRemaining -= bytesWritten; + // Force an error if (for some reason) we get NS_OK but + // no bytes written. + if (!bytesWritten) + { + rv = NS_ERROR_FAILURE; + cancel = true; + } + } + else + { + // Disaster - can't write out the bytes - disk full / permission? + cancel = true; + } + } + } + else + { + // Disaster - can't read the bytes - broken link / file error? + cancel = true; + } + } + + int64_t channelContentLength = -1; + if (!cancel && + NS_SUCCEEDED(channel->GetContentLength(&channelContentLength))) + { + // if we get -1 at this point, we didn't get content-length header + // assume that we got all of the data and push what we have; + // that's the best we can do now + if ((-1 == channelContentLength) || + ((channelContentLength - (aOffset + aLength)) == 0)) + { + NS_WARNING_ASSERTION( + channelContentLength != -1, + "nsWebBrowserPersist::OnDataAvailable() no content length " + "header, pushing what we have"); + // we're done with this pass; see if we need to do upload + nsAutoCString contentType; + channel->GetContentType(contentType); + // if we don't have the right type of output stream then it's a local file + nsCOMPtr<nsIStorageStream> storStream(do_QueryInterface(data->mStream)); + if (storStream) + { + data->mStream->Close(); + data->mStream = nullptr; // null out stream so we don't close it later + rv = StartUpload(storStream, data->mFile, contentType); + if (NS_FAILED(rv)) + { + readError = false; + cancel = true; + } + } + } + } + + // Notify listener if an error occurred. + if (cancel) + { + SendErrorStatusChange(readError, rv, + readError ? request : nullptr, data->mFile); + } + } + + // Cancel reading? + if (cancel) + { + EndDownload(NS_BINDING_ABORTED); + } + + return NS_OK; +} + + +//***************************************************************************** +// nsWebBrowserPersist::nsIProgressEventSink +//***************************************************************************** + +NS_IMETHODIMP nsWebBrowserPersist::OnProgress( + nsIRequest *request, nsISupports *ctxt, int64_t aProgress, + int64_t aProgressMax) +{ + if (!mProgressListener) + { + return NS_OK; + } + + // Store the progress of this request + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request); + OutputData *data = mOutputMap.Get(keyPtr); + if (data) + { + data->mSelfProgress = aProgress; + data->mSelfProgressMax = aProgressMax; + } + else + { + UploadData *upData = mUploadList.Get(keyPtr); + if (upData) + { + upData->mSelfProgress = aProgress; + upData->mSelfProgressMax = aProgressMax; + } + } + + // Notify listener of total progress + CalcTotalProgress(); + if (mProgressListener2) + { + mProgressListener2->OnProgressChange64(nullptr, request, aProgress, + aProgressMax, mTotalCurrentProgress, mTotalMaxProgress); + } + else + { + // have to truncate 64-bit to 32bit + mProgressListener->OnProgressChange(nullptr, request, uint64_t(aProgress), + uint64_t(aProgressMax), mTotalCurrentProgress, mTotalMaxProgress); + } + + // If our progress listener implements nsIProgressEventSink, + // forward the notification + if (mEventSink) + { + mEventSink->OnProgress(request, ctxt, aProgress, aProgressMax); + } + + return NS_OK; +} + +NS_IMETHODIMP nsWebBrowserPersist::OnStatus( + nsIRequest *request, nsISupports *ctxt, nsresult status, + const char16_t *statusArg) +{ + if (mProgressListener) + { + // We need to filter out non-error error codes. + // Is the only NS_SUCCEEDED value NS_OK? + switch ( status ) + { + case NS_NET_STATUS_RESOLVING_HOST: + case NS_NET_STATUS_RESOLVED_HOST: + case NS_NET_STATUS_BEGIN_FTP_TRANSACTION: + case NS_NET_STATUS_END_FTP_TRANSACTION: + case NS_NET_STATUS_CONNECTING_TO: + case NS_NET_STATUS_CONNECTED_TO: + case NS_NET_STATUS_SENDING_TO: + case NS_NET_STATUS_RECEIVING_FROM: + case NS_NET_STATUS_WAITING_FOR: + case NS_NET_STATUS_READING: + case NS_NET_STATUS_WRITING: + break; + + default: + // Pass other notifications (for legitimate errors) along. + mProgressListener->OnStatusChange(nullptr, request, status, statusArg); + break; + } + + } + + // If our progress listener implements nsIProgressEventSink, + // forward the notification + if (mEventSink) + { + mEventSink->OnStatus(request, ctxt, status, statusArg); + } + + return NS_OK; +} + + +//***************************************************************************** +// nsWebBrowserPersist private methods +//***************************************************************************** + +// Convert error info into proper message text and send OnStatusChange notification +// to the web progress listener. +nsresult nsWebBrowserPersist::SendErrorStatusChange( + bool aIsReadError, nsresult aResult, nsIRequest *aRequest, nsIURI *aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + + if (!mProgressListener) + { + // Do nothing + return NS_OK; + } + + // Get the file path or spec from the supplied URI + nsCOMPtr<nsIFile> file; + GetLocalFileFromURI(aURI, getter_AddRefs(file)); + nsAutoString path; + nsresult rv; + if (file) + { + file->GetPath(path); + } + else + { + nsAutoCString fileurl; + rv = aURI->GetSpec(fileurl); + NS_ENSURE_SUCCESS(rv, rv); + AppendUTF8toUTF16(fileurl, path); + } + + nsAutoString msgId; + switch(aResult) + { + case NS_ERROR_FILE_NAME_TOO_LONG: + // File name too long. + msgId.AssignLiteral("fileNameTooLongError"); + break; + case NS_ERROR_FILE_ALREADY_EXISTS: + // File exists with same name as directory. + msgId.AssignLiteral("fileAlreadyExistsError"); + break; + case NS_ERROR_FILE_DISK_FULL: + case NS_ERROR_FILE_NO_DEVICE_SPACE: + // Out of space on target volume. + msgId.AssignLiteral("diskFull"); + break; + + case NS_ERROR_FILE_READ_ONLY: + // Attempt to write to read/only file. + msgId.AssignLiteral("readOnly"); + break; + + case NS_ERROR_FILE_ACCESS_DENIED: + // Attempt to write without sufficient permissions. + msgId.AssignLiteral("accessError"); + break; + + default: + // Generic read/write error message. + if (aIsReadError) + msgId.AssignLiteral("readError"); + else + msgId.AssignLiteral("writeError"); + break; + } + // Get properties file bundle and extract status string. + nsCOMPtr<nsIStringBundleService> s = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && s, NS_ERROR_FAILURE); + + nsCOMPtr<nsIStringBundle> bundle; + rv = s->CreateBundle(kWebBrowserPersistStringBundle, getter_AddRefs(bundle)); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && bundle, NS_ERROR_FAILURE); + + nsXPIDLString msgText; + const char16_t *strings[1]; + strings[0] = path.get(); + rv = bundle->FormatStringFromName(msgId.get(), strings, 1, getter_Copies(msgText)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + mProgressListener->OnStatusChange(nullptr, aRequest, aResult, msgText); + + return NS_OK; +} + +nsresult nsWebBrowserPersist::GetValidURIFromObject(nsISupports *aObject, nsIURI **aURI) const +{ + NS_ENSURE_ARG_POINTER(aObject); + NS_ENSURE_ARG_POINTER(aURI); + + nsCOMPtr<nsIFile> objAsFile = do_QueryInterface(aObject); + if (objAsFile) + { + return NS_NewFileURI(aURI, objAsFile); + } + nsCOMPtr<nsIURI> objAsURI = do_QueryInterface(aObject); + if (objAsURI) + { + *aURI = objAsURI; + NS_ADDREF(*aURI); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +/* static */ nsresult +nsWebBrowserPersist::GetLocalFileFromURI(nsIURI *aURI, nsIFile **aLocalFile) +{ + nsresult rv; + + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIFile> file; + rv = fileURL->GetFile(getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return rv; + } + + file.forget(aLocalFile); + return NS_OK; +} + +/* static */ nsresult +nsWebBrowserPersist::AppendPathToURI(nsIURI *aURI, const nsAString & aPath) +{ + NS_ENSURE_ARG_POINTER(aURI); + + nsAutoCString newPath; + nsresult rv = aURI->GetPath(newPath); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Append a forward slash if necessary + int32_t len = newPath.Length(); + if (len > 0 && newPath.CharAt(len - 1) != '/') + { + newPath.Append('/'); + } + + // Store the path back on the URI + AppendUTF16toUTF8(aPath, newPath); + aURI->SetPath(newPath); + + return NS_OK; +} + +nsresult nsWebBrowserPersist::SaveURIInternal( + nsIURI *aURI, nsISupports *aCacheKey, nsIURI *aReferrer, + uint32_t aReferrerPolicy, nsIInputStream *aPostData, + const char *aExtraHeaders, nsIURI *aFile, + bool aCalcFileExt, bool aIsPrivate) +{ + NS_ENSURE_ARG_POINTER(aURI); + NS_ENSURE_ARG_POINTER(aFile); + + nsresult rv = NS_OK; + + mURI = aURI; + + nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL; + if (mPersistFlags & PERSIST_FLAGS_BYPASS_CACHE) + { + loadFlags |= nsIRequest::LOAD_BYPASS_CACHE; + } + else if (mPersistFlags & PERSIST_FLAGS_FROM_CACHE) + { + loadFlags |= nsIRequest::LOAD_FROM_CACHE; + } + + // Extract the cache key + nsCOMPtr<nsISupports> cacheKey; + if (aCacheKey) + { + // Test if the cache key is actually a web page descriptor (docshell) + // or session history entry. + nsCOMPtr<nsISHEntry> shEntry = do_QueryInterface(aCacheKey); + if (!shEntry) + { + nsCOMPtr<nsIWebPageDescriptor> webPageDescriptor = + do_QueryInterface(aCacheKey); + if (webPageDescriptor) + { + nsCOMPtr<nsISupports> currentDescriptor; + webPageDescriptor->GetCurrentDescriptor(getter_AddRefs(currentDescriptor)); + shEntry = do_QueryInterface(currentDescriptor); + } + } + + if (shEntry) + { + shEntry->GetCacheKey(getter_AddRefs(cacheKey)); + } + else + { + // Assume a plain cache key + cacheKey = aCacheKey; + } + } + + // Open a channel to the URI + nsCOMPtr<nsIChannel> inputChannel; + rv = NS_NewChannel(getter_AddRefs(inputChannel), + aURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // aLoadGroup + static_cast<nsIInterfaceRequestor*>(this), + loadFlags); + + nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(inputChannel); + if (pbChannel) + { + pbChannel->SetPrivate(aIsPrivate); + } + + if (NS_FAILED(rv) || inputChannel == nullptr) + { + EndDownload(NS_ERROR_FAILURE); + return NS_ERROR_FAILURE; + } + + // Disable content conversion + if (mPersistFlags & PERSIST_FLAGS_NO_CONVERSION) + { + nsCOMPtr<nsIEncodedChannel> encodedChannel(do_QueryInterface(inputChannel)); + if (encodedChannel) + { + encodedChannel->SetApplyConversion(false); + } + } + + if (mPersistFlags & PERSIST_FLAGS_FORCE_ALLOW_COOKIES) + { + nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = + do_QueryInterface(inputChannel); + if (httpChannelInternal) + httpChannelInternal->SetThirdPartyFlags(nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW); + } + + // Set the referrer, post data and headers if any + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(inputChannel)); + if (httpChannel) + { + // Referrer + if (aReferrer) + { + httpChannel->SetReferrerWithPolicy(aReferrer, aReferrerPolicy); + } + + // Post data + if (aPostData) + { + nsCOMPtr<nsISeekableStream> stream(do_QueryInterface(aPostData)); + if (stream) + { + // Rewind the postdata stream + stream->Seek(nsISeekableStream::NS_SEEK_SET, 0); + nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel)); + NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel"); + // Attach the postdata to the http channel + uploadChannel->SetUploadStream(aPostData, EmptyCString(), -1); + } + } + + // Cache key + nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(httpChannel)); + if (cacheChannel && cacheKey) + { + cacheChannel->SetCacheKey(cacheKey); + } + + // Headers + if (aExtraHeaders) + { + nsAutoCString oneHeader; + nsAutoCString headerName; + nsAutoCString headerValue; + int32_t crlf = 0; + int32_t colon = 0; + const char *kWhitespace = "\b\t\r\n "; + nsAutoCString extraHeaders(aExtraHeaders); + while (true) + { + crlf = extraHeaders.Find("\r\n", true); + if (crlf == -1) + break; + extraHeaders.Mid(oneHeader, 0, crlf); + extraHeaders.Cut(0, crlf + 2); + colon = oneHeader.Find(":"); + if (colon == -1) + break; // Should have a colon + oneHeader.Left(headerName, colon); + colon++; + oneHeader.Mid(headerValue, colon, oneHeader.Length() - colon); + headerName.Trim(kWhitespace); + headerValue.Trim(kWhitespace); + // Add the header (merging if required) + rv = httpChannel->SetRequestHeader(headerName, headerValue, true); + if (NS_FAILED(rv)) + { + EndDownload(NS_ERROR_FAILURE); + return NS_ERROR_FAILURE; + } + } + } + } + return SaveChannelInternal(inputChannel, aFile, aCalcFileExt); +} + +nsresult nsWebBrowserPersist::SaveChannelInternal( + nsIChannel *aChannel, nsIURI *aFile, bool aCalcFileExt) +{ + NS_ENSURE_ARG_POINTER(aChannel); + NS_ENSURE_ARG_POINTER(aFile); + + // The default behaviour of SaveChannelInternal is to download the source + // into a storage stream and upload that to the target. MakeOutputStream + // special-cases a file target and creates a file output stream directly. + // We want to special-case a file source and create a file input stream, + // but we don't need to do this in the case of a file target. + nsCOMPtr<nsIFileChannel> fc(do_QueryInterface(aChannel)); + nsCOMPtr<nsIFileURL> fu(do_QueryInterface(aFile)); + + if (fc && !fu) { + nsCOMPtr<nsIInputStream> fileInputStream, bufferedInputStream; + nsresult rv = NS_MaybeOpenChannelUsingOpen2(aChannel, + getter_AddRefs(fileInputStream)); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedInputStream), + fileInputStream, BUFFERED_OUTPUT_SIZE); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString contentType; + aChannel->GetContentType(contentType); + return StartUpload(bufferedInputStream, aFile, contentType); + } + + // Read from the input channel + nsresult rv = NS_MaybeOpenChannelUsingAsyncOpen2(aChannel, this); + if (rv == NS_ERROR_NO_CONTENT) + { + // Assume this is a protocol such as mailto: which does not feed out + // data and just ignore it. + return NS_SUCCESS_DONT_FIXUP; + } + + if (NS_FAILED(rv)) + { + // Opening failed, but do we care? + if (mPersistFlags & PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS) + { + SendErrorStatusChange(true, rv, aChannel, aFile); + EndDownload(NS_ERROR_FAILURE); + return NS_ERROR_FAILURE; + } + return NS_SUCCESS_DONT_FIXUP; + } + + // Add the output transport to the output map with the channel as the key + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aChannel); + mOutputMap.Put(keyPtr, new OutputData(aFile, mURI, aCalcFileExt)); + + return NS_OK; +} + +nsresult +nsWebBrowserPersist::GetExtensionForContentType(const char16_t *aContentType, char16_t **aExt) +{ + NS_ENSURE_ARG_POINTER(aContentType); + NS_ENSURE_ARG_POINTER(aExt); + + *aExt = nullptr; + + nsresult rv; + if (!mMIMEService) + { + mMIMEService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv); + NS_ENSURE_TRUE(mMIMEService, NS_ERROR_FAILURE); + } + + nsAutoCString contentType; + contentType.AssignWithConversion(aContentType); + nsAutoCString ext; + rv = mMIMEService->GetPrimaryExtension(contentType, EmptyCString(), ext); + if (NS_SUCCEEDED(rv)) + { + *aExt = UTF8ToNewUnicode(ext); + NS_ENSURE_TRUE(*aExt, NS_ERROR_OUT_OF_MEMORY); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +nsresult +nsWebBrowserPersist::SaveDocumentDeferred(mozilla::UniquePtr<WalkData>&& aData) +{ + nsresult rv = + SaveDocumentInternal(aData->mDocument, aData->mFile, aData->mDataPath); + if (NS_FAILED(rv)) { + SendErrorStatusChange(true, rv, nullptr, mURI); + EndDownload(rv); + } + return rv; +} + +nsresult nsWebBrowserPersist::SaveDocumentInternal( + nsIWebBrowserPersistDocument *aDocument, nsIURI *aFile, nsIURI *aDataPath) +{ + mURI = nullptr; + NS_ENSURE_ARG_POINTER(aDocument); + NS_ENSURE_ARG_POINTER(aFile); + + nsresult rv = aDocument->SetPersistFlags(mPersistFlags); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aDocument->GetIsPrivate(&mIsPrivate); + NS_ENSURE_SUCCESS(rv, rv); + + // See if we can get the local file representation of this URI + nsCOMPtr<nsIFile> localFile; + rv = GetLocalFileFromURI(aFile, getter_AddRefs(localFile)); + + nsCOMPtr<nsIFile> localDataPath; + if (NS_SUCCEEDED(rv) && aDataPath) + { + // See if we can get the local file representation of this URI + rv = GetLocalFileFromURI(aDataPath, getter_AddRefs(localDataPath)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } + + // Persist the main document + rv = aDocument->GetCharacterSet(mCurrentCharset); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString uriSpec; + rv = aDocument->GetDocumentURI(uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_NewURI(getter_AddRefs(mURI), uriSpec, mCurrentCharset.get()); + NS_ENSURE_SUCCESS(rv, rv); + rv = aDocument->GetBaseURI(uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_NewURI(getter_AddRefs(mCurrentBaseURI), uriSpec, + mCurrentCharset.get()); + NS_ENSURE_SUCCESS(rv, rv); + + // Does the caller want to fixup the referenced URIs and save those too? + if (aDataPath) + { + // Basic steps are these. + // + // 1. Iterate through the document (and subdocuments) building a list + // of unique URIs. + // 2. For each URI create an OutputData entry and open a channel to save + // it. As each URI is saved, discover the mime type and fix up the + // local filename with the correct extension. + // 3. Store the document in a list and wait for URI persistence to finish + // 4. After URI persistence completes save the list of documents, + // fixing it up as it goes out to file. + + mCurrentDataPathIsRelative = false; + mCurrentDataPath = aDataPath; + mCurrentRelativePathToData = ""; + mCurrentThingsToPersist = 0; + mTargetBaseURI = aFile; + + // Determine if the specified data path is relative to the + // specified file, (e.g. c:\docs\htmldata is relative to + // c:\docs\myfile.htm, but not to d:\foo\data. + + // Starting with the data dir work back through its parents + // checking if one of them matches the base directory. + + if (localDataPath && localFile) + { + nsCOMPtr<nsIFile> baseDir; + localFile->GetParent(getter_AddRefs(baseDir)); + + nsAutoCString relativePathToData; + nsCOMPtr<nsIFile> dataDirParent; + dataDirParent = localDataPath; + while (dataDirParent) + { + bool sameDir = false; + dataDirParent->Equals(baseDir, &sameDir); + if (sameDir) + { + mCurrentRelativePathToData = relativePathToData; + mCurrentDataPathIsRelative = true; + break; + } + + nsAutoString dirName; + dataDirParent->GetLeafName(dirName); + + nsAutoCString newRelativePathToData; + newRelativePathToData = NS_ConvertUTF16toUTF8(dirName) + + NS_LITERAL_CSTRING("/") + + relativePathToData; + relativePathToData = newRelativePathToData; + + nsCOMPtr<nsIFile> newDataDirParent; + rv = dataDirParent->GetParent(getter_AddRefs(newDataDirParent)); + dataDirParent = newDataDirParent; + } + } + else + { + // generate a relative path if possible + nsCOMPtr<nsIURL> pathToBaseURL(do_QueryInterface(aFile)); + if (pathToBaseURL) + { + nsAutoCString relativePath; // nsACString + if (NS_SUCCEEDED(pathToBaseURL->GetRelativeSpec(aDataPath, relativePath))) + { + mCurrentDataPathIsRelative = true; + mCurrentRelativePathToData = relativePath; + } + } + } + + // Store the document in a list so when URI persistence is done and the + // filenames of saved URIs are known, the documents can be fixed up and + // saved + + DocData *docData = new DocData; + docData->mBaseURI = mCurrentBaseURI; + docData->mCharset = mCurrentCharset; + docData->mDocument = aDocument; + docData->mFile = aFile; + mDocList.AppendElement(docData); + + // Walk the DOM gathering a list of externally referenced URIs in the uri map + nsCOMPtr<nsIWebBrowserPersistResourceVisitor> visit = + new OnWalk(this, aFile, localDataPath); + return aDocument->ReadResources(visit); + } + else + { + DocData *docData = new DocData; + docData->mBaseURI = mCurrentBaseURI; + docData->mCharset = mCurrentCharset; + docData->mDocument = aDocument; + docData->mFile = aFile; + mDocList.AppendElement(docData); + + // Not walking DOMs, so go directly to serialization. + SerializeNextFile(); + return NS_OK; + } +} + +NS_IMETHODIMP +nsWebBrowserPersist::OnWalk::VisitResource(nsIWebBrowserPersistDocument* aDoc, + const nsACString& aURI) +{ + return mParent->StoreURI(nsAutoCString(aURI).get()); +} + +NS_IMETHODIMP +nsWebBrowserPersist::OnWalk::VisitDocument(nsIWebBrowserPersistDocument* aDoc, + nsIWebBrowserPersistDocument* aSubDoc) +{ + URIData* data = nullptr; + nsAutoCString uriSpec; + nsresult rv = aSubDoc->GetDocumentURI(uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + rv = mParent->StoreURI(uriSpec.get(), false, &data); + NS_ENSURE_SUCCESS(rv, rv); + if (!data) { + // If the URI scheme isn't persistable, then don't persist. + return NS_OK; + } + data->mIsSubFrame = true; + return mParent->SaveSubframeContent(aSubDoc, uriSpec, data); +} + + +NS_IMETHODIMP +nsWebBrowserPersist::OnWalk::EndVisit(nsIWebBrowserPersistDocument* aDoc, + nsresult aStatus) +{ + if (NS_FAILED(aStatus)) { + mParent->SendErrorStatusChange(true, aStatus, nullptr, mFile); + mParent->EndDownload(aStatus); + return aStatus; + } + mParent->FinishSaveDocumentInternal(mFile, mDataPath); + return NS_OK; +} + +void +nsWebBrowserPersist::FinishSaveDocumentInternal(nsIURI* aFile, + nsIFile* aDataPath) +{ + // If there are things to persist, create a directory to hold them + if (mCurrentThingsToPersist > 0) { + if (aDataPath) { + bool exists = false; + bool haveDir = false; + + aDataPath->Exists(&exists); + if (exists) { + aDataPath->IsDirectory(&haveDir); + } + if (!haveDir) { + nsresult rv = + aDataPath->Create(nsIFile::DIRECTORY_TYPE, 0755); + if (NS_SUCCEEDED(rv)) { + haveDir = true; + } else { + SendErrorStatusChange(false, rv, nullptr, aFile); + } + } + if (!haveDir) { + EndDownload(NS_ERROR_FAILURE); + return; + } + if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE) { + // Add to list of things to delete later if all goes wrong + CleanupData *cleanupData = new CleanupData; + cleanupData->mFile = aDataPath; + cleanupData->mIsDirectory = true; + mCleanupList.AppendElement(cleanupData); + } + } + } + + if (mWalkStack.Length() > 0) { + mozilla::UniquePtr<WalkData> toWalk; + mWalkStack.LastElement().swap(toWalk); + mWalkStack.TruncateLength(mWalkStack.Length() - 1); + // Bounce this off the event loop to avoid stack overflow. + typedef StoreCopyPassByRRef<decltype(toWalk)> WalkStorage; + auto saveMethod = &nsWebBrowserPersist::SaveDocumentDeferred; + nsCOMPtr<nsIRunnable> saveLater = + NewRunnableMethod<WalkStorage>(this, saveMethod, + mozilla::Move(toWalk)); + NS_DispatchToCurrentThread(saveLater); + } else { + // Done walking DOMs; on to the serialization phase. + SerializeNextFile(); + } +} + +void nsWebBrowserPersist::Cleanup() +{ + mURIMap.Clear(); + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(iter.Key()); + if (channel) { + channel->Cancel(NS_BINDING_ABORTED); + } + } + mOutputMap.Clear(); + + for (auto iter = mUploadList.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(iter.Key()); + if (channel) { + channel->Cancel(NS_BINDING_ABORTED); + } + } + mUploadList.Clear(); + + uint32_t i; + for (i = 0; i < mDocList.Length(); i++) { + DocData *docData = mDocList.ElementAt(i); + delete docData; + } + mDocList.Clear(); + + for (i = 0; i < mCleanupList.Length(); i++) { + CleanupData *cleanupData = mCleanupList.ElementAt(i); + delete cleanupData; + } + mCleanupList.Clear(); + + mFilenameList.Clear(); +} + +void nsWebBrowserPersist::CleanupLocalFiles() +{ + // Two passes, the first pass cleans up files, the second pass tests + // for and then deletes empty directories. Directories that are not + // empty after the first pass must contain files from something else + // and are not deleted. + int pass; + for (pass = 0; pass < 2; pass++) + { + uint32_t i; + for (i = 0; i < mCleanupList.Length(); i++) + { + CleanupData *cleanupData = mCleanupList.ElementAt(i); + nsCOMPtr<nsIFile> file = cleanupData->mFile; + + // Test if the dir / file exists (something in an earlier loop + // may have already removed it) + bool exists = false; + file->Exists(&exists); + if (!exists) + continue; + + // Test if the file has changed in between creation and deletion + // in some way that means it should be ignored + bool isDirectory = false; + file->IsDirectory(&isDirectory); + if (isDirectory != cleanupData->mIsDirectory) + continue; // A file has become a dir or vice versa ! + + if (pass == 0 && !isDirectory) + { + file->Remove(false); + } + else if (pass == 1 && isDirectory) // Directory + { + // Directories are more complicated. Enumerate through + // children looking for files. Any files created by the + // persist object would have been deleted by the first + // pass so if there are any there at this stage, the dir + // cannot be deleted because it has someone else's files + // in it. Empty child dirs are deleted but they must be + // recursed through to ensure they are actually empty. + + bool isEmptyDirectory = true; + nsCOMArray<nsISimpleEnumerator> dirStack; + int32_t stackSize = 0; + + // Push the top level enum onto the stack + nsCOMPtr<nsISimpleEnumerator> pos; + if (NS_SUCCEEDED(file->GetDirectoryEntries(getter_AddRefs(pos)))) + dirStack.AppendObject(pos); + + while (isEmptyDirectory && (stackSize = dirStack.Count())) + { + // Pop the last element + nsCOMPtr<nsISimpleEnumerator> curPos; + curPos = dirStack[stackSize-1]; + dirStack.RemoveObjectAt(stackSize - 1); + + // Test if the enumerator has any more files in it + bool hasMoreElements = false; + curPos->HasMoreElements(&hasMoreElements); + if (!hasMoreElements) + { + continue; + } + + // Child files automatically make this code drop out, + // while child dirs keep the loop going. + nsCOMPtr<nsISupports> child; + curPos->GetNext(getter_AddRefs(child)); + NS_ASSERTION(child, "No child element, but hasMoreElements says otherwise"); + if (!child) + continue; + nsCOMPtr<nsIFile> childAsFile = do_QueryInterface(child); + NS_ASSERTION(childAsFile, "This should be a file but isn't"); + + bool childIsSymlink = false; + childAsFile->IsSymlink(&childIsSymlink); + bool childIsDir = false; + childAsFile->IsDirectory(&childIsDir); + if (!childIsDir || childIsSymlink) + { + // Some kind of file or symlink which means dir + // is not empty so just drop out. + isEmptyDirectory = false; + break; + } + // Push parent enumerator followed by child enumerator + nsCOMPtr<nsISimpleEnumerator> childPos; + childAsFile->GetDirectoryEntries(getter_AddRefs(childPos)); + dirStack.AppendObject(curPos); + if (childPos) + dirStack.AppendObject(childPos); + + } + dirStack.Clear(); + + // If after all that walking the dir is deemed empty, delete it + if (isEmptyDirectory) + { + file->Remove(true); + } + } + } + } +} + +nsresult +nsWebBrowserPersist::CalculateUniqueFilename(nsIURI *aURI) +{ + nsCOMPtr<nsIURL> url(do_QueryInterface(aURI)); + NS_ENSURE_TRUE(url, NS_ERROR_FAILURE); + + bool nameHasChanged = false; + nsresult rv; + + // Get the old filename + nsAutoCString filename; + rv = url->GetFileName(filename); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + nsAutoCString directory; + rv = url->GetDirectory(directory); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Split the filename into a base and an extension. + // e.g. "foo.html" becomes "foo" & ".html" + // + // The nsIURL methods GetFileBaseName & GetFileExtension don't + // preserve the dot whereas this code does to save some effort + // later when everything is put back together. + int32_t lastDot = filename.RFind("."); + nsAutoCString base; + nsAutoCString ext; + if (lastDot >= 0) + { + filename.Mid(base, 0, lastDot); + filename.Mid(ext, lastDot, filename.Length() - lastDot); // includes dot + } + else + { + // filename contains no dot + base = filename; + } + + // Test if the filename is longer than allowed by the OS + int32_t needToChop = filename.Length() - kDefaultMaxFilenameLength; + if (needToChop > 0) + { + // Truncate the base first and then the ext if necessary + if (base.Length() > (uint32_t) needToChop) + { + base.Truncate(base.Length() - needToChop); + } + else + { + needToChop -= base.Length() - 1; + base.Truncate(1); + if (ext.Length() > (uint32_t) needToChop) + { + ext.Truncate(ext.Length() - needToChop); + } + else + { + ext.Truncate(0); + } + // If kDefaultMaxFilenameLength were 1 we'd be in trouble here, + // but that won't happen because it will be set to a sensible + // value. + } + + filename.Assign(base); + filename.Append(ext); + nameHasChanged = true; + } + + // Ensure the filename is unique + // Create a filename if it's empty, or if the filename / datapath is + // already taken by another URI and create an alternate name. + + if (base.IsEmpty() || !mFilenameList.IsEmpty()) + { + nsAutoCString tmpPath; + nsAutoCString tmpBase; + uint32_t duplicateCounter = 1; + while (1) + { + // Make a file name, + // Foo become foo_001, foo_002, etc. + // Empty files become _001, _002 etc. + + if (base.IsEmpty() || duplicateCounter > 1) + { + char * tmp = PR_smprintf("_%03d", duplicateCounter); + NS_ENSURE_TRUE(tmp, NS_ERROR_OUT_OF_MEMORY); + if (filename.Length() < kDefaultMaxFilenameLength - 4) + { + tmpBase = base; + } + else + { + base.Mid(tmpBase, 0, base.Length() - 4); + } + tmpBase.Append(tmp); + PR_smprintf_free(tmp); + } + else + { + tmpBase = base; + } + + tmpPath.Assign(directory); + tmpPath.Append(tmpBase); + tmpPath.Append(ext); + + // Test if the name is a duplicate + if (!mFilenameList.Contains(tmpPath)) + { + if (!base.Equals(tmpBase)) + { + filename.Assign(tmpBase); + filename.Append(ext); + nameHasChanged = true; + } + break; + } + duplicateCounter++; + } + } + + // Add name to list of those already used + nsAutoCString newFilepath(directory); + newFilepath.Append(filename); + mFilenameList.AppendElement(newFilepath); + + // Update the uri accordingly if the filename actually changed + if (nameHasChanged) + { + // Final sanity test + if (filename.Length() > kDefaultMaxFilenameLength) + { + NS_WARNING("Filename wasn't truncated less than the max file length - how can that be?"); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIFile> localFile; + GetLocalFileFromURI(aURI, getter_AddRefs(localFile)); + + if (localFile) + { + nsAutoString filenameAsUnichar; + filenameAsUnichar.AssignWithConversion(filename.get()); + localFile->SetLeafName(filenameAsUnichar); + + // Resync the URI with the file after the extension has been appended + nsresult rv; + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + fileURL->SetFile(localFile); // this should recalculate uri + } + else + { + url->SetFileName(filename); + } + } + + return NS_OK; +} + + +nsresult +nsWebBrowserPersist::MakeFilenameFromURI(nsIURI *aURI, nsString &aFilename) +{ + // Try to get filename from the URI. + nsAutoString fileName; + + // Get a suggested file name from the URL but strip it of characters + // likely to cause the name to be illegal. + + nsCOMPtr<nsIURL> url(do_QueryInterface(aURI)); + if (url) + { + nsAutoCString nameFromURL; + url->GetFileName(nameFromURL); + if (mPersistFlags & PERSIST_FLAGS_DONT_CHANGE_FILENAMES) + { + fileName.AssignWithConversion(NS_UnescapeURL(nameFromURL).BeginReading()); + aFilename = fileName; + return NS_OK; + } + if (!nameFromURL.IsEmpty()) + { + // Unescape the file name (GetFileName escapes it) + NS_UnescapeURL(nameFromURL); + uint32_t nameLength = 0; + const char *p = nameFromURL.get(); + for (;*p && *p != ';' && *p != '?' && *p != '#' && *p != '.' + ;p++) + { + if (nsCRT::IsAsciiAlpha(*p) || nsCRT::IsAsciiDigit(*p) + || *p == '.' || *p == '-' || *p == '_' || (*p == ' ')) + { + fileName.Append(char16_t(*p)); + if (++nameLength == kDefaultMaxFilenameLength) + { + // Note: + // There is no point going any further since it will be + // truncated in CalculateUniqueFilename anyway. + // More importantly, certain implementations of + // nsIFile (e.g. the Mac impl) might truncate + // names in undesirable ways, such as truncating from + // the middle, inserting ellipsis and so on. + break; + } + } + } + } + } + + // Empty filenames can confuse the local file object later + // when it attempts to set the leaf name in CalculateUniqueFilename + // for duplicates and ends up replacing the parent dir. To avoid + // the problem, all filenames are made at least one character long. + if (fileName.IsEmpty()) + { + fileName.Append(char16_t('a')); // 'a' is for arbitrary + } + + aFilename = fileName; + return NS_OK; +} + + +nsresult +nsWebBrowserPersist::CalculateAndAppendFileExt(nsIURI *aURI, nsIChannel *aChannel, nsIURI *aOriginalURIWithExtension) +{ + nsresult rv; + + if (!mMIMEService) + { + mMIMEService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv); + NS_ENSURE_TRUE(mMIMEService, NS_ERROR_FAILURE); + } + + nsAutoCString contentType; + + // Get the content type from the channel + aChannel->GetContentType(contentType); + + // Get the content type from the MIME service + if (contentType.IsEmpty()) + { + nsCOMPtr<nsIURI> uri; + aChannel->GetOriginalURI(getter_AddRefs(uri)); + mMIMEService->GetTypeFromURI(uri, contentType); + } + + // Append the extension onto the file + if (!contentType.IsEmpty()) + { + nsCOMPtr<nsIMIMEInfo> mimeInfo; + mMIMEService->GetFromTypeAndExtension( + contentType, EmptyCString(), getter_AddRefs(mimeInfo)); + + nsCOMPtr<nsIFile> localFile; + GetLocalFileFromURI(aURI, getter_AddRefs(localFile)); + + if (mimeInfo) + { + nsCOMPtr<nsIURL> url(do_QueryInterface(aURI)); + NS_ENSURE_TRUE(url, NS_ERROR_FAILURE); + + nsAutoCString newFileName; + url->GetFileName(newFileName); + + // Test if the current extension is current for the mime type + bool hasExtension = false; + int32_t ext = newFileName.RFind("."); + if (ext != -1) + { + mimeInfo->ExtensionExists(Substring(newFileName, ext + 1), &hasExtension); + } + + // Append the mime file extension + nsAutoCString fileExt; + if (!hasExtension) + { + // Test if previous extension is acceptable + nsCOMPtr<nsIURL> oldurl(do_QueryInterface(aOriginalURIWithExtension)); + NS_ENSURE_TRUE(oldurl, NS_ERROR_FAILURE); + oldurl->GetFileExtension(fileExt); + bool useOldExt = false; + if (!fileExt.IsEmpty()) + { + mimeInfo->ExtensionExists(fileExt, &useOldExt); + } + + // can't use old extension so use primary extension + if (!useOldExt) + { + mimeInfo->GetPrimaryExtension(fileExt); + } + + if (!fileExt.IsEmpty()) + { + uint32_t newLength = newFileName.Length() + fileExt.Length() + 1; + if (newLength > kDefaultMaxFilenameLength) + { + if (fileExt.Length() > kDefaultMaxFilenameLength/2) + fileExt.Truncate(kDefaultMaxFilenameLength/2); + + uint32_t diff = kDefaultMaxFilenameLength - 1 - + fileExt.Length(); + if (newFileName.Length() > diff) + newFileName.Truncate(diff); + } + newFileName.Append('.'); + newFileName.Append(fileExt); + } + + if (localFile) + { + localFile->SetLeafName(NS_ConvertUTF8toUTF16(newFileName)); + + // Resync the URI with the file after the extension has been appended + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + fileURL->SetFile(localFile); // this should recalculate uri + } + else + { + url->SetFileName(newFileName); + } + } + + } + } + + return NS_OK; +} + +nsresult +nsWebBrowserPersist::MakeOutputStream( + nsIURI *aURI, nsIOutputStream **aOutputStream) +{ + nsresult rv; + + nsCOMPtr<nsIFile> localFile; + GetLocalFileFromURI(aURI, getter_AddRefs(localFile)); + if (localFile) + rv = MakeOutputStreamFromFile(localFile, aOutputStream); + else + rv = MakeOutputStreamFromURI(aURI, aOutputStream); + + return rv; +} + +nsresult +nsWebBrowserPersist::MakeOutputStreamFromFile( + nsIFile *aFile, nsIOutputStream **aOutputStream) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsIFileOutputStream> fileOutputStream = + do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // XXX brade: get the right flags here! + int32_t ioFlags = -1; + if (mPersistFlags & nsIWebBrowserPersist::PERSIST_FLAGS_APPEND_TO_FILE) + ioFlags = PR_APPEND | PR_CREATE_FILE | PR_WRONLY; + rv = fileOutputStream->Init(aFile, ioFlags, -1, 0); + NS_ENSURE_SUCCESS(rv, rv); + + *aOutputStream = NS_BufferOutputStream(fileOutputStream, + BUFFERED_OUTPUT_SIZE).take(); + + if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE) + { + // Add to cleanup list in event of failure + CleanupData *cleanupData = new CleanupData; + if (!cleanupData) { + NS_RELEASE(*aOutputStream); + return NS_ERROR_OUT_OF_MEMORY; + } + cleanupData->mFile = aFile; + cleanupData->mIsDirectory = false; + mCleanupList.AppendElement(cleanupData); + } + + return NS_OK; +} + +nsresult +nsWebBrowserPersist::MakeOutputStreamFromURI( + nsIURI *aURI, nsIOutputStream **aOutputStream) +{ + uint32_t segsize = 8192; + uint32_t maxsize = uint32_t(-1); + nsCOMPtr<nsIStorageStream> storStream; + nsresult rv = NS_NewStorageStream(segsize, maxsize, getter_AddRefs(storStream)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_SUCCESS(CallQueryInterface(storStream, aOutputStream), NS_ERROR_FAILURE); + return NS_OK; +} + +void +nsWebBrowserPersist::FinishDownload() +{ + EndDownload(NS_OK); +} + +void +nsWebBrowserPersist::EndDownload(nsresult aResult) +{ + // Store the error code in the result if it is an error + if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(aResult)) + { + mPersistResult = aResult; + } + + // mCompleted needs to be set before issuing the stop notification. + // (Bug 1224437) + mCompleted = true; + // State stop notification + if (mProgressListener) { + mProgressListener->OnStateChange(nullptr, nullptr, + nsIWebProgressListener::STATE_STOP + | nsIWebProgressListener::STATE_IS_NETWORK, mPersistResult); + } + + // Do file cleanup if required + if (NS_FAILED(aResult) && (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE)) + { + CleanupLocalFiles(); + } + + // Cleanup the channels + Cleanup(); + + mProgressListener = nullptr; + mProgressListener2 = nullptr; + mEventSink = nullptr; +} + +nsresult +nsWebBrowserPersist::FixRedirectedChannelEntry(nsIChannel *aNewChannel) +{ + NS_ENSURE_ARG_POINTER(aNewChannel); + + // Iterate through existing open channels looking for one with a URI + // matching the one specified. + nsCOMPtr<nsIURI> originalURI; + aNewChannel->GetOriginalURI(getter_AddRefs(originalURI)); + nsISupports* matchingKey = nullptr; + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + nsISupports* key = iter.Key(); + nsCOMPtr<nsIChannel> thisChannel = do_QueryInterface(key); + nsCOMPtr<nsIURI> thisURI; + + thisChannel->GetOriginalURI(getter_AddRefs(thisURI)); + + // Compare this channel's URI to the one passed in. + bool matchingURI = false; + thisURI->Equals(originalURI, &matchingURI); + if (matchingURI) { + matchingKey = key; + break; + } + } + + if (matchingKey) { + // If a match was found, remove the data entry with the old channel + // key and re-add it with the new channel key. + nsAutoPtr<OutputData> outputData; + mOutputMap.RemoveAndForget(matchingKey, outputData); + NS_ENSURE_TRUE(outputData, NS_ERROR_FAILURE); + + // Store data again with new channel unless told to ignore redirects. + if (!(mPersistFlags & PERSIST_FLAGS_IGNORE_REDIRECTED_DATA)) { + nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aNewChannel); + mOutputMap.Put(keyPtr, outputData.forget()); + } + } + + return NS_OK; +} + +void +nsWebBrowserPersist::CalcTotalProgress() +{ + mTotalCurrentProgress = 0; + mTotalMaxProgress = 0; + + if (mOutputMap.Count() > 0) { + // Total up the progress of each output stream + for (auto iter = mOutputMap.Iter(); !iter.Done(); iter.Next()) { + // Only count toward total progress if destination file is local. + OutputData* data = iter.UserData(); + nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(data->mFile); + if (fileURL) { + mTotalCurrentProgress += data->mSelfProgress; + mTotalMaxProgress += data->mSelfProgressMax; + } + } + } + + if (mUploadList.Count() > 0) { + // Total up the progress of each upload + for (auto iter = mUploadList.Iter(); !iter.Done(); iter.Next()) { + UploadData* data = iter.UserData(); + if (data) { + mTotalCurrentProgress += data->mSelfProgress; + mTotalMaxProgress += data->mSelfProgressMax; + } + } + } + + // XXX this code seems pretty bogus and pointless + if (mTotalCurrentProgress == 0 && mTotalMaxProgress == 0) + { + // No output streams so we must be complete + mTotalCurrentProgress = 10000; + mTotalMaxProgress = 10000; + } +} + +nsresult +nsWebBrowserPersist::StoreURI( + const char *aURI, bool aNeedsPersisting, URIData **aData) +{ + NS_ENSURE_ARG_POINTER(aURI); + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), + nsDependentCString(aURI), + mCurrentCharset.get(), + mCurrentBaseURI); + NS_ENSURE_SUCCESS(rv, rv); + + return StoreURI(uri, aNeedsPersisting, aData); +} + +nsresult +nsWebBrowserPersist::StoreURI( + nsIURI *aURI, bool aNeedsPersisting, URIData **aData) +{ + NS_ENSURE_ARG_POINTER(aURI); + if (aData) + { + *aData = nullptr; + } + + // Test if this URI should be persisted. By default + // we should assume the URI is persistable. + bool doNotPersistURI; + nsresult rv = NS_URIChainHasFlags(aURI, + nsIProtocolHandler::URI_NON_PERSISTABLE, + &doNotPersistURI); + if (NS_FAILED(rv)) + { + doNotPersistURI = false; + } + + if (doNotPersistURI) + { + return NS_OK; + } + + URIData *data = nullptr; + MakeAndStoreLocalFilenameInURIMap(aURI, aNeedsPersisting, &data); + if (aData) + { + *aData = data; + } + + return NS_OK; +} + +nsresult +nsWebBrowserPersist::URIData::GetLocalURI(nsIURI *targetBaseURI, nsCString& aSpecOut) +{ + aSpecOut.SetIsVoid(true); + if (!mNeedsFixup) { + return NS_OK; + } + nsresult rv; + nsCOMPtr<nsIURI> fileAsURI; + if (mFile) { + rv = mFile->Clone(getter_AddRefs(fileAsURI)); + NS_ENSURE_SUCCESS(rv, rv); + } else { + rv = mDataPath->Clone(getter_AddRefs(fileAsURI)); + NS_ENSURE_SUCCESS(rv, rv); + rv = AppendPathToURI(fileAsURI, mFilename); + NS_ENSURE_SUCCESS(rv, rv); + } + + // remove username/password if present + fileAsURI->SetUserPass(EmptyCString()); + + // reset node attribute + // Use relative or absolute links + if (mDataPathIsRelative) { + bool isEqual = false; + if (NS_SUCCEEDED(mRelativeDocumentURI->Equals(targetBaseURI, &isEqual)) && isEqual) { + nsCOMPtr<nsIURL> url(do_QueryInterface(fileAsURI)); + if (!url) { + return NS_ERROR_FAILURE; + } + + nsAutoCString filename; + url->GetFileName(filename); + + nsAutoCString rawPathURL(mRelativePathToData); + rawPathURL.Append(filename); + + rv = NS_EscapeURL(rawPathURL, esc_FilePath, aSpecOut, fallible); + NS_ENSURE_SUCCESS(rv, rv); + } else { + nsAutoCString rawPathURL; + + nsCOMPtr<nsIFile> dataFile; + rv = GetLocalFileFromURI(mFile, getter_AddRefs(dataFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> docFile; + rv = GetLocalFileFromURI(targetBaseURI, getter_AddRefs(docFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> parentDir; + rv = docFile->GetParent(getter_AddRefs(parentDir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = dataFile->GetRelativePath(parentDir, rawPathURL); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_EscapeURL(rawPathURL, esc_FilePath, aSpecOut, fallible); + NS_ENSURE_SUCCESS(rv, rv); + } + } else { + fileAsURI->GetSpec(aSpecOut); + } + if (mIsSubFrame) { + AppendUTF16toUTF8(mSubFrameExt, aSpecOut); + } + + return NS_OK; +} + +bool +nsWebBrowserPersist::DocumentEncoderExists(const char *aContentType) +{ + // Check if there is an encoder for the desired content type. + nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE); + contractID.Append(aContentType); + + nsCOMPtr<nsIComponentRegistrar> registrar; + NS_GetComponentRegistrar(getter_AddRefs(registrar)); + if (registrar) + { + bool result; + nsresult rv = registrar->IsContractIDRegistered(contractID.get(), + &result); + if (NS_SUCCEEDED(rv) && result) + { + return true; + } + } + return false; +} + +nsresult +nsWebBrowserPersist::SaveSubframeContent( + nsIWebBrowserPersistDocument *aFrameContent, + const nsCString& aURISpec, + URIData *aData) +{ + NS_ENSURE_ARG_POINTER(aData); + + // Extract the content type for the frame's contents. + nsAutoCString contentType; + nsresult rv = aFrameContent->GetContentType(contentType); + NS_ENSURE_SUCCESS(rv, rv); + + nsXPIDLString ext; + GetExtensionForContentType(NS_ConvertASCIItoUTF16(contentType).get(), + getter_Copies(ext)); + + // We must always have an extension so we will try to re-assign + // the original extension if GetExtensionForContentType fails. + if (ext.IsEmpty()) { + nsCOMPtr<nsIURI> docURI; + rv = NS_NewURI(getter_AddRefs(docURI), aURISpec, mCurrentCharset.get()); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURL> url(do_QueryInterface(docURI, &rv)); + nsAutoCString extension; + if (NS_SUCCEEDED(rv)) { + url->GetFileExtension(extension); + } else { + extension.AssignLiteral("htm"); + } + aData->mSubFrameExt.Assign(char16_t('.')); + AppendUTF8toUTF16(extension, aData->mSubFrameExt); + } else { + aData->mSubFrameExt.Assign(char16_t('.')); + aData->mSubFrameExt.Append(ext); + } + + nsString filenameWithExt = aData->mFilename; + filenameWithExt.Append(aData->mSubFrameExt); + + // Work out the path for the subframe + nsCOMPtr<nsIURI> frameURI; + rv = mCurrentDataPath->Clone(getter_AddRefs(frameURI)); + NS_ENSURE_SUCCESS(rv, rv); + rv = AppendPathToURI(frameURI, filenameWithExt); + NS_ENSURE_SUCCESS(rv, rv); + + // Work out the path for the subframe data + nsCOMPtr<nsIURI> frameDataURI; + rv = mCurrentDataPath->Clone(getter_AddRefs(frameDataURI)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoString newFrameDataPath(aData->mFilename); + + // Append _data + newFrameDataPath.AppendLiteral("_data"); + rv = AppendPathToURI(frameDataURI, newFrameDataPath); + NS_ENSURE_SUCCESS(rv, rv); + + // Make frame document & data path conformant and unique + rv = CalculateUniqueFilename(frameURI); + NS_ENSURE_SUCCESS(rv, rv); + rv = CalculateUniqueFilename(frameDataURI); + NS_ENSURE_SUCCESS(rv, rv); + + mCurrentThingsToPersist++; + + // We shouldn't use SaveDocumentInternal for the contents + // of frames that are not documents, e.g. images. + if (DocumentEncoderExists(contentType.get())) { + auto toWalk = mozilla::MakeUnique<WalkData>(); + toWalk->mDocument = aFrameContent; + toWalk->mFile = frameURI; + toWalk->mDataPath = frameDataURI; + mWalkStack.AppendElement(mozilla::Move(toWalk)); + } else { + rv = StoreURI(aURISpec.get()); + } + NS_ENSURE_SUCCESS(rv, rv); + + // Store the updated uri to the frame + aData->mFile = frameURI; + aData->mSubFrameExt.Truncate(); // we already put this in frameURI + + return NS_OK; +} + +nsresult +nsWebBrowserPersist::CreateChannelFromURI(nsIURI *aURI, nsIChannel **aChannel) +{ + nsresult rv = NS_OK; + *aChannel = nullptr; + + rv = NS_NewChannel(aChannel, + aURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_ARG_POINTER(*aChannel); + + rv = (*aChannel)->SetNotificationCallbacks(static_cast<nsIInterfaceRequestor*>(this)); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + + +// we store the current location as the key (absolutized version of domnode's attribute's value) +nsresult +nsWebBrowserPersist::MakeAndStoreLocalFilenameInURIMap( + nsIURI *aURI, bool aNeedsPersisting, URIData **aData) +{ + NS_ENSURE_ARG_POINTER(aURI); + + nsAutoCString spec; + nsresult rv = aURI->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Create a sensibly named filename for the URI and store in the URI map + URIData *data; + if (mURIMap.Contains(spec)) + { + data = mURIMap.Get(spec); + if (aNeedsPersisting) + { + data->mNeedsPersisting = true; + } + if (aData) + { + *aData = data; + } + return NS_OK; + } + + // Create a unique file name for the uri + nsString filename; + rv = MakeFilenameFromURI(aURI, filename); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // Store the file name + data = new URIData; + NS_ENSURE_TRUE(data, NS_ERROR_OUT_OF_MEMORY); + + data->mNeedsPersisting = aNeedsPersisting; + data->mNeedsFixup = true; + data->mFilename = filename; + data->mSaved = false; + data->mIsSubFrame = false; + data->mDataPath = mCurrentDataPath; + data->mDataPathIsRelative = mCurrentDataPathIsRelative; + data->mRelativePathToData = mCurrentRelativePathToData; + data->mRelativeDocumentURI = mTargetBaseURI; + data->mCharset = mCurrentCharset; + + if (aNeedsPersisting) + mCurrentThingsToPersist++; + + mURIMap.Put(spec, data); + if (aData) + { + *aData = data; + } + + return NS_OK; +} + +// Decide if we need to apply conversion to the passed channel. +void nsWebBrowserPersist::SetApplyConversionIfNeeded(nsIChannel *aChannel) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface(aChannel, &rv); + if (NS_FAILED(rv)) + return; + + // Set the default conversion preference: + encChannel->SetApplyConversion(false); + + nsCOMPtr<nsIURI> thisURI; + aChannel->GetURI(getter_AddRefs(thisURI)); + nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(thisURI)); + if (!sourceURL) + return; + nsAutoCString extension; + sourceURL->GetFileExtension(extension); + + nsCOMPtr<nsIUTF8StringEnumerator> encEnum; + encChannel->GetContentEncodings(getter_AddRefs(encEnum)); + if (!encEnum) + return; + nsCOMPtr<nsIExternalHelperAppService> helperAppService = + do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return; + bool hasMore; + rv = encEnum->HasMore(&hasMore); + if (NS_SUCCEEDED(rv) && hasMore) + { + nsAutoCString encType; + rv = encEnum->GetNext(encType); + if (NS_SUCCEEDED(rv)) + { + bool applyConversion = false; + rv = helperAppService->ApplyDecodingForExtension(extension, encType, + &applyConversion); + if (NS_SUCCEEDED(rv)) + encChannel->SetApplyConversion(applyConversion); + } + } +} diff --git a/embedding/components/webbrowserpersist/nsWebBrowserPersist.h b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h new file mode 100644 index 000000000..1816af0a6 --- /dev/null +++ b/embedding/components/webbrowserpersist/nsWebBrowserPersist.h @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsWebBrowserPersist_h__ +#define nsWebBrowserPersist_h__ + +#include "nsCOMPtr.h" +#include "nsWeakReference.h" + +#include "nsIInterfaceRequestor.h" +#include "nsIMIMEService.h" +#include "nsIStreamListener.h" +#include "nsIOutputStream.h" +#include "nsIInputStream.h" +#include "nsIChannel.h" +#include "nsIDocumentEncoder.h" +#include "nsITransport.h" +#include "nsIProgressEventSink.h" +#include "nsIFile.h" +#include "nsIWebProgressListener2.h" +#include "nsIWebBrowserPersistDocument.h" + +#include "mozilla/UniquePtr.h" +#include "nsClassHashtable.h" +#include "nsHashKeys.h" +#include "nsTArray.h" + +#include "nsCWebBrowserPersist.h" + +class nsIStorageStream; +class nsIWebBrowserPersistDocument; + +class nsWebBrowserPersist final : public nsIInterfaceRequestor, + public nsIWebBrowserPersist, + public nsIStreamListener, + public nsIProgressEventSink, + public nsSupportsWeakReference +{ + friend class nsEncoderNodeFixup; + +// Public members +public: + nsWebBrowserPersist(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICANCELABLE + NS_DECL_NSIWEBBROWSERPERSIST + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIPROGRESSEVENTSINK + +// Private members +private: + virtual ~nsWebBrowserPersist(); + nsresult SaveURIInternal( + nsIURI *aURI, nsISupports *aCacheKey, nsIURI *aReferrer, + uint32_t aReferrerPolicy, nsIInputStream *aPostData, + const char *aExtraHeaders, nsIURI *aFile, + bool aCalcFileExt, bool aIsPrivate); + nsresult SaveChannelInternal( + nsIChannel *aChannel, nsIURI *aFile, bool aCalcFileExt); + nsresult SaveDocumentInternal( + nsIWebBrowserPersistDocument *aDocument, + nsIURI *aFile, + nsIURI *aDataPath); + nsresult SaveDocuments(); + void FinishSaveDocumentInternal(nsIURI* aFile, nsIFile* aDataPath); + nsresult GetExtensionForContentType( + const char16_t *aContentType, char16_t **aExt); + + struct CleanupData; + struct DocData; + struct OutputData; + struct UploadData; + struct URIData; + struct WalkData; + struct URIFixupData; + + class OnWalk; + class OnWrite; + class FlatURIMap; + friend class OnWalk; + friend class OnWrite; + + nsresult SaveDocumentDeferred(mozilla::UniquePtr<WalkData>&& aData); + void Cleanup(); + void CleanupLocalFiles(); + nsresult GetValidURIFromObject(nsISupports *aObject, nsIURI **aURI) const; + static nsresult GetLocalFileFromURI(nsIURI *aURI, nsIFile **aLocalFile); + static nsresult AppendPathToURI(nsIURI *aURI, const nsAString & aPath); + nsresult MakeAndStoreLocalFilenameInURIMap( + nsIURI *aURI, bool aNeedsPersisting, URIData **aData); + nsresult MakeOutputStream( + nsIURI *aFile, nsIOutputStream **aOutputStream); + nsresult MakeOutputStreamFromFile( + nsIFile *aFile, nsIOutputStream **aOutputStream); + nsresult MakeOutputStreamFromURI(nsIURI *aURI, nsIOutputStream **aOutStream); + nsresult CreateChannelFromURI(nsIURI *aURI, nsIChannel **aChannel); + nsresult StartUpload(nsIStorageStream *aOutStream, nsIURI *aDestinationURI, + const nsACString &aContentType); + nsresult StartUpload(nsIInputStream *aInputStream, nsIURI *aDestinationURI, + const nsACString &aContentType); + nsresult CalculateAndAppendFileExt(nsIURI *aURI, nsIChannel *aChannel, + nsIURI *aOriginalURIWithExtension); + nsresult CalculateUniqueFilename(nsIURI *aURI); + nsresult MakeFilenameFromURI( + nsIURI *aURI, nsString &aFilename); + nsresult StoreURI( + const char *aURI, + bool aNeedsPersisting = true, + URIData **aData = nullptr); + nsresult StoreURI( + nsIURI *aURI, + bool aNeedsPersisting = true, + URIData **aData = nullptr); + bool DocumentEncoderExists(const char *aContentType); + + nsresult SaveSubframeContent( + nsIWebBrowserPersistDocument *aFrameContent, + const nsCString& aURISpec, + URIData *aData); + nsresult SendErrorStatusChange( + bool aIsReadError, nsresult aResult, nsIRequest *aRequest, nsIURI *aURI); + + nsresult FixRedirectedChannelEntry(nsIChannel *aNewChannel); + + void EndDownload(nsresult aResult); + void FinishDownload(); + void SerializeNextFile(); + void CalcTotalProgress(); + + void SetApplyConversionIfNeeded(nsIChannel *aChannel); + + nsCOMPtr<nsIURI> mCurrentDataPath; + bool mCurrentDataPathIsRelative; + nsCString mCurrentRelativePathToData; + nsCOMPtr<nsIURI> mCurrentBaseURI; + nsCString mCurrentCharset; + nsCOMPtr<nsIURI> mTargetBaseURI; + uint32_t mCurrentThingsToPersist; + + nsCOMPtr<nsIMIMEService> mMIMEService; + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsIWebProgressListener> mProgressListener; + /** + * Progress listener for 64-bit values; this is the same object as + * mProgressListener, but is a member to avoid having to qi it for each + * progress notification. + */ + nsCOMPtr<nsIWebProgressListener2> mProgressListener2; + nsCOMPtr<nsIProgressEventSink> mEventSink; + nsClassHashtable<nsISupportsHashKey, OutputData> mOutputMap; + nsClassHashtable<nsISupportsHashKey, UploadData> mUploadList; + nsClassHashtable<nsCStringHashKey, URIData> mURIMap; + nsCOMPtr<nsIWebBrowserPersistURIMap> mFlatURIMap; + nsTArray<mozilla::UniquePtr<WalkData>> mWalkStack; + nsTArray<DocData*> mDocList; + nsTArray<CleanupData*> mCleanupList; + nsTArray<nsCString> mFilenameList; + bool mFirstAndOnlyUse; + bool mSavingDocument; + bool mCancel; + bool mCompleted; + bool mStartSaving; + bool mReplaceExisting; + bool mSerializingOutput; + bool mIsPrivate; + uint32_t mPersistFlags; + nsresult mPersistResult; + int64_t mTotalCurrentProgress; + int64_t mTotalMaxProgress; + int16_t mWrapColumn; + uint32_t mEncodingFlags; + nsString mContentType; +}; + +#endif diff --git a/embedding/components/windowwatcher/moz.build b/embedding/components/windowwatcher/moz.build new file mode 100644 index 000000000..31aa718c9 --- /dev/null +++ b/embedding/components/windowwatcher/moz.build @@ -0,0 +1,49 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +TEST_DIRS += ['test'] + +XPIDL_SOURCES += [ + 'nsIDialogParamBlock.idl', + 'nsIPromptFactory.idl', + 'nsIPromptService.idl', + 'nsIPromptService2.idl', + 'nsIWindowWatcher.idl', + 'nsPIPromptService.idl', + 'nsPIWindowWatcher.idl', +] + +XPIDL_MODULE = 'windowwatcher' + +EXPORTS += [ + 'nsPromptUtils.h', +] + +UNIFIED_SOURCES += [ + 'nsAutoWindowStateHelper.cpp', + 'nsWindowWatcher.cpp', +] + +EXPORTS += [ + 'nsWindowWatcher.h', +] + +if CONFIG['MOZ_XUL']: + UNIFIED_SOURCES += [ + 'nsDialogParamBlock.cpp', + ] + +FINAL_LIBRARY = 'xul' +# For nsJSUtils +LOCAL_INCLUDES += [ + '/docshell/base', + '/dom/base', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] + +include('/ipc/chromium/chromium-config.mozbuild') diff --git a/embedding/components/windowwatcher/nsAutoWindowStateHelper.cpp b/embedding/components/windowwatcher/nsAutoWindowStateHelper.cpp new file mode 100644 index 000000000..068f2a6dc --- /dev/null +++ b/embedding/components/windowwatcher/nsAutoWindowStateHelper.cpp @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsAutoWindowStateHelper.h" + +#include "mozilla/dom/Event.h" +#include "nsIDocument.h" +#include "nsIDOMEvent.h" +#include "nsIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "nsString.h" + +using namespace mozilla; +using namespace mozilla::dom; + +/**************************************************************** + ****************** nsAutoWindowStateHelper ********************* + ****************************************************************/ + +nsAutoWindowStateHelper::nsAutoWindowStateHelper(nsPIDOMWindowOuter* aWindow) + : mWindow(aWindow) + , mDefaultEnabled(DispatchEventToChrome("DOMWillOpenModalDialog")) +{ + if (mWindow) { + mWindow->EnterModalState(); + } +} + +nsAutoWindowStateHelper::~nsAutoWindowStateHelper() +{ + if (mWindow) { + mWindow->LeaveModalState(); + } + + if (mDefaultEnabled) { + DispatchEventToChrome("DOMModalDialogClosed"); + } +} + +bool +nsAutoWindowStateHelper::DispatchEventToChrome(const char* aEventName) +{ + // XXXbz should we skip dispatching the event if the inner changed? + // That is, should we store both the inner and the outer? + if (!mWindow) { + return true; + } + + // The functions of nsContentUtils do not provide the required behavior, + // so the following is inlined. + nsIDocument* doc = mWindow->GetExtantDoc(); + if (!doc) { + return true; + } + + ErrorResult rv; + RefPtr<Event> event = doc->CreateEvent(NS_LITERAL_STRING("Events"), rv); + if (rv.Failed()) { + rv.SuppressException(); + return false; + } + event->InitEvent(NS_ConvertASCIItoUTF16(aEventName), true, true); + event->SetTrusted(true); + event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; + + nsCOMPtr<EventTarget> target = do_QueryInterface(mWindow); + bool defaultActionEnabled; + target->DispatchEvent(event, &defaultActionEnabled); + return defaultActionEnabled; +} diff --git a/embedding/components/windowwatcher/nsAutoWindowStateHelper.h b/embedding/components/windowwatcher/nsAutoWindowStateHelper.h new file mode 100644 index 000000000..875fb4ba0 --- /dev/null +++ b/embedding/components/windowwatcher/nsAutoWindowStateHelper.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsAutoWindowStateHelper_h +#define __nsAutoWindowStateHelper_h + +#include "nsCOMPtr.h" +#include "nsPIDOMWindow.h" + +/** + * Helper class for dealing with notifications around opening modal + * windows. + */ + +class nsPIDOMWindowOuter; + +class nsAutoWindowStateHelper +{ +public: + explicit nsAutoWindowStateHelper(nsPIDOMWindowOuter* aWindow); + ~nsAutoWindowStateHelper(); + + bool DefaultEnabled() { return mDefaultEnabled; } + +protected: + bool DispatchEventToChrome(const char* aEventName); + + nsCOMPtr<nsPIDOMWindowOuter> mWindow; + bool mDefaultEnabled; +}; + +#endif diff --git a/embedding/components/windowwatcher/nsDialogParamBlock.cpp b/embedding/components/windowwatcher/nsDialogParamBlock.cpp new file mode 100644 index 000000000..f6f7af967 --- /dev/null +++ b/embedding/components/windowwatcher/nsDialogParamBlock.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDialogParamBlock.h" +#include "nsString.h" +#include "nsReadableUtils.h" + +NS_IMPL_ISUPPORTS(nsDialogParamBlock, nsIDialogParamBlock) + +nsDialogParamBlock::nsDialogParamBlock() + : mNumStrings(0) + , mString(nullptr) +{ + for (int32_t i = 0; i < kNumInts; i++) { + mInt[i] = 0; + } +} + +nsDialogParamBlock::~nsDialogParamBlock() +{ + delete[] mString; +} + +NS_IMETHODIMP +nsDialogParamBlock::SetNumberStrings(int32_t aNumStrings) +{ + if (mString) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + mString = new nsString[aNumStrings]; + if (!mString) { + return NS_ERROR_OUT_OF_MEMORY; + } + mNumStrings = aNumStrings; + return NS_OK; +} + +NS_IMETHODIMP +nsDialogParamBlock::GetInt(int32_t aIndex, int32_t* aResult) +{ + nsresult rv = InBounds(aIndex, kNumInts); + if (rv == NS_OK) { + *aResult = mInt[aIndex]; + } + return rv; +} + +NS_IMETHODIMP +nsDialogParamBlock::SetInt(int32_t aIndex, int32_t aInt) +{ + nsresult rv = InBounds(aIndex, kNumInts); + if (rv == NS_OK) { + mInt[aIndex] = aInt; + } + return rv; +} + +NS_IMETHODIMP +nsDialogParamBlock::GetString(int32_t aIndex, char16_t** aResult) +{ + if (mNumStrings == 0) { + SetNumberStrings(kNumStrings); + } + nsresult rv = InBounds(aIndex, mNumStrings); + if (rv == NS_OK) { + *aResult = ToNewUnicode(mString[aIndex]); + } + return rv; +} + +NS_IMETHODIMP +nsDialogParamBlock::SetString(int32_t aIndex, const char16_t* aString) +{ + if (mNumStrings == 0) { + SetNumberStrings(kNumStrings); + } + nsresult rv = InBounds(aIndex, mNumStrings); + if (rv == NS_OK) { + mString[aIndex] = aString; + } + return rv; +} + +NS_IMETHODIMP +nsDialogParamBlock::GetObjects(nsIMutableArray** aObjects) +{ + NS_ENSURE_ARG_POINTER(aObjects); + NS_IF_ADDREF(*aObjects = mObjects); + return NS_OK; +} + +NS_IMETHODIMP +nsDialogParamBlock::SetObjects(nsIMutableArray* aObjects) +{ + mObjects = aObjects; + return NS_OK; +} diff --git a/embedding/components/windowwatcher/nsDialogParamBlock.h b/embedding/components/windowwatcher/nsDialogParamBlock.h new file mode 100644 index 000000000..c1b06ea53 --- /dev/null +++ b/embedding/components/windowwatcher/nsDialogParamBlock.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsDialogParamBlock_h +#define __nsDialogParamBlock_h + +#include "nsIDialogParamBlock.h" +#include "nsIMutableArray.h" +#include "nsCOMPtr.h" + +// {4E4AAE11-8901-46cc-8217-DAD7C5415873} +#define NS_DIALOGPARAMBLOCK_CID \ + {0x4e4aae11, 0x8901, 0x46cc, {0x82, 0x17, 0xda, 0xd7, 0xc5, 0x41, 0x58, 0x73}} + +class nsString; + +class nsDialogParamBlock : public nsIDialogParamBlock +{ +public: + nsDialogParamBlock(); + + NS_DECL_NSIDIALOGPARAMBLOCK + NS_DECL_ISUPPORTS + +protected: + virtual ~nsDialogParamBlock(); + +private: + enum { kNumInts = 8, kNumStrings = 16 }; + + nsresult InBounds(int32_t aIndex, int32_t aMax) + { + return aIndex >= 0 && aIndex < aMax ? NS_OK : NS_ERROR_ILLEGAL_VALUE; + } + + int32_t mInt[kNumInts]; + int32_t mNumStrings; + nsString* mString; + nsCOMPtr<nsIMutableArray> mObjects; +}; + +#endif diff --git a/embedding/components/windowwatcher/nsIDialogParamBlock.idl b/embedding/components/windowwatcher/nsIDialogParamBlock.idl new file mode 100644 index 000000000..d917776ec --- /dev/null +++ b/embedding/components/windowwatcher/nsIDialogParamBlock.idl @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +interface nsIMutableArray; + +/** + * An interface to pass strings, integers and nsISupports to a dialog + */ + +[scriptable, uuid(f76c0901-437a-11d3-b7a0-e35db351b4bc)] +interface nsIDialogParamBlock: nsISupports { + + /** Get or set an integer to pass. + * Index must be in the range 0..7 + */ + int32_t GetInt( in int32_t inIndex ); + void SetInt( in int32_t inIndex, in int32_t inInt ); + + /** Set the maximum number of strings to pass. Default is 16. + * Use before setting any string (If you want to change it from the default). + */ + void SetNumberStrings( in int32_t inNumStrings ); + + /** Get or set an string to pass. + * Index starts at 0 + */ + wstring GetString( in int32_t inIndex ); + void SetString( in int32_t inIndex, in wstring inString); + + /** + * A place where you can store an nsIMutableArray to pass nsISupports + */ + attribute nsIMutableArray objects; +}; + +%{C++ +#define NS_DIALOGPARAMBLOCK_CONTRACTID "@mozilla.org/embedcomp/dialogparam;1" +%} + diff --git a/embedding/components/windowwatcher/nsIPromptFactory.idl b/embedding/components/windowwatcher/nsIPromptFactory.idl new file mode 100644 index 000000000..b16db3d49 --- /dev/null +++ b/embedding/components/windowwatcher/nsIPromptFactory.idl @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface mozIDOMWindowProxy; + +/** + * This interface allows creating various prompts that have a specific parent. + */ +[scriptable, uuid(2803541c-c96a-4ff1-bd7c-9cb566d46aeb)] +interface nsIPromptFactory : nsISupports +{ + /** + * Returns an object implementing the specified interface that creates + * prompts parented to aParent. + */ + void getPrompt(in mozIDOMWindowProxy aParent, in nsIIDRef iid, + [iid_is(iid),retval] out nsQIResult result); +}; + diff --git a/embedding/components/windowwatcher/nsIPromptService.idl b/embedding/components/windowwatcher/nsIPromptService.idl new file mode 100644 index 000000000..24c75e3a9 --- /dev/null +++ b/embedding/components/windowwatcher/nsIPromptService.idl @@ -0,0 +1,346 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface mozIDOMWindowProxy; + +/** + * This is the interface to the embeddable prompt service; the service that + * implements nsIPrompt. Its interface is designed to be just nsIPrompt, each + * method modified to take a parent window parameter. + * + * Accesskeys can be attached to buttons and checkboxes by inserting an & + * before the accesskey character in the checkbox message or button title. For + * a real &, use && instead. (A "button title" generally refers to the text + * label of a button.) + * + * One note: in all cases, the parent window parameter can be null. However, + * these windows are all intended to have parents. So when no parent is + * specified, the implementation should try hard to find a suitable foster + * parent. + * + * Implementations are free to choose how they present the various button + * types. For example, while prompts that give the user a choice between OK + * and Cancel are required to return a boolean value indicating whether or not + * the user accepted the prompt (pressed OK) or rejected the prompt (pressed + * Cancel), the implementation of this interface could very well speak the + * prompt to the user instead of rendering any visual user-interface. The + * standard button types are merely idioms used to convey the nature of the + * choice the user is to make. + * + * Because implementations of this interface may loosely interpret the various + * button types, it is advised that text messages passed to these prompts do + * not refer to the button types by name. For example, it is inadvisable to + * tell the user to "Press OK to proceed." Instead, such a prompt might be + * rewritten to ask the user: "Would you like to proceed?" + */ +[scriptable, uuid(404ebfa2-d8f4-4c94-8416-e65a55f9df5a)] +interface nsIPromptService : nsISupports +{ + /** + * Puts up an alert dialog with an OK button. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + */ + void alert(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText); + + /** + * Puts up an alert dialog with an OK button and a labeled checkbox. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + * @param aCheckMsg + * Text to appear with the checkbox. + * @param aCheckState + * Contains the initial checked state of the checkbox when this method + * is called and the final checked state after this method returns. + */ + void alertCheck(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText, + in wstring aCheckMsg, + inout boolean aCheckState); + + /** + * Puts up a dialog with OK and Cancel buttons. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + * + * @return true for OK, false for Cancel + */ + boolean confirm(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText); + + /** + * Puts up a dialog with OK and Cancel buttons and a labeled checkbox. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + * @param aCheckMsg + * Text to appear with the checkbox. + * @param aCheckState + * Contains the initial checked state of the checkbox when this method + * is called and the final checked state after this method returns. + * + * @return true for OK, false for Cancel + */ + boolean confirmCheck(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText, + in wstring aCheckMsg, + inout boolean aCheckState); + + /** + * Button Flags + * + * The following flags are combined to form the aButtonFlags parameter passed + * to confirmEx. See confirmEx for more information on how the flags may be + * combined. + */ + + /** + * Button Position Flags + */ + const unsigned long BUTTON_POS_0 = 1; + const unsigned long BUTTON_POS_1 = 1 << 8; + const unsigned long BUTTON_POS_2 = 1 << 16; + + /** + * Button Title Flags (used to set the labels of buttons in the prompt) + */ + const unsigned long BUTTON_TITLE_OK = 1; + const unsigned long BUTTON_TITLE_CANCEL = 2; + const unsigned long BUTTON_TITLE_YES = 3; + const unsigned long BUTTON_TITLE_NO = 4; + const unsigned long BUTTON_TITLE_SAVE = 5; + const unsigned long BUTTON_TITLE_DONT_SAVE = 6; + const unsigned long BUTTON_TITLE_REVERT = 7; + const unsigned long BUTTON_TITLE_IS_STRING = 127; + + /** + * Button Default Flags (used to select which button is the default one) + */ + const unsigned long BUTTON_POS_0_DEFAULT = 0; + const unsigned long BUTTON_POS_1_DEFAULT = 1 << 24; + const unsigned long BUTTON_POS_2_DEFAULT = 1 << 25; + + /** + * Causes the buttons to be initially disabled. They are enabled after a + * timeout expires. The implementation may interpret this loosely as the + * intent is to ensure that the user does not click through a security dialog + * too quickly. Strictly speaking, the implementation could choose to ignore + * this flag. + */ + const unsigned long BUTTON_DELAY_ENABLE = 1 << 26; + + /** + * Selects the standard set of OK/Cancel buttons. + */ + const unsigned long STD_OK_CANCEL_BUTTONS = (BUTTON_TITLE_OK * BUTTON_POS_0) + + (BUTTON_TITLE_CANCEL * BUTTON_POS_1); + + /** + * Selects the standard set of Yes/No buttons. + */ + const unsigned long STD_YES_NO_BUTTONS = (BUTTON_TITLE_YES * BUTTON_POS_0) + + (BUTTON_TITLE_NO * BUTTON_POS_1); + + + /** + * Puts up a dialog with up to 3 buttons and an optional, labeled checkbox. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + * @param aButtonFlags + * A combination of Button Flags. + * @param aButton0Title + * Used when button 0 uses TITLE_IS_STRING + * @param aButton1Title + * Used when button 1 uses TITLE_IS_STRING + * @param aButton2Title + * Used when button 2 uses TITLE_IS_STRING + * @param aCheckMsg + * Text to appear with the checkbox. Null if no checkbox. + * @param aCheckState + * Contains the initial checked state of the checkbox when this method + * is called and the final checked state after this method returns. + * + * @return index of the button pressed. + * + * Buttons are numbered 0 - 2. The implementation can decide whether the + * sequence goes from right to left or left to right. Button 0 is the + * default button unless one of the Button Default Flags is specified. + * + * A button may use a predefined title, specified by one of the Button Title + * Flags values. Each title value can be multiplied by a position value to + * assign the title to a particular button. If BUTTON_TITLE_IS_STRING is + * used for a button, the string parameter for that button will be used. If + * the value for a button position is zero, the button will not be shown. + * + * In general, aButtonFlags is constructed per the following example: + * + * aButtonFlags = (BUTTON_POS_0) * (BUTTON_TITLE_AAA) + + * (BUTTON_POS_1) * (BUTTON_TITLE_BBB) + + * BUTTON_POS_1_DEFAULT; + * + * where "AAA" and "BBB" correspond to one of the button titles. + */ + int32_t confirmEx(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText, + in unsigned long aButtonFlags, + in wstring aButton0Title, + in wstring aButton1Title, + in wstring aButton2Title, + in wstring aCheckMsg, + inout boolean aCheckState); + + /** + * Puts up a dialog with an edit field and an optional, labeled checkbox. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + * @param aValue + * Contains the default value for the dialog field when this method + * is called (null value is ok). Upon return, if the user pressed + * OK, then this parameter contains a newly allocated string value. + * Otherwise, the parameter's value is unmodified. + * @param aCheckMsg + * Text to appear with the checkbox. If null, check box will not be shown. + * @param aCheckState + * Contains the initial checked state of the checkbox when this method + * is called and the final checked state after this method returns. + * + * @return true for OK, false for Cancel. + */ + boolean prompt(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText, + inout wstring aValue, + in wstring aCheckMsg, + inout boolean aCheckState); + + /** + * Puts up a dialog with an edit field, a password field, and an optional, + * labeled checkbox. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + * @param aUsername + * Contains the default value for the username field when this method + * is called (null value is ok). Upon return, if the user pressed OK, + * then this parameter contains a newly allocated string value. + * Otherwise, the parameter's value is unmodified. + * @param aPassword + * Contains the default value for the password field when this method + * is called (null value is ok). Upon return, if the user pressed OK, + * then this parameter contains a newly allocated string value. + * Otherwise, the parameter's value is unmodified. + * @param aCheckMsg + * Text to appear with the checkbox. If null, check box will not be shown. + * @param aCheckState + * Contains the initial checked state of the checkbox when this method + * is called and the final checked state after this method returns. + * + * @return true for OK, false for Cancel. + */ + boolean promptUsernameAndPassword(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText, + inout wstring aUsername, + inout wstring aPassword, + in wstring aCheckMsg, + inout boolean aCheckState); + + /** + * Puts up a dialog with a password field and an optional, labeled checkbox. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + * @param aPassword + * Contains the default value for the password field when this method + * is called (null value is ok). Upon return, if the user pressed OK, + * then this parameter contains a newly allocated string value. + * Otherwise, the parameter's value is unmodified. + * @param aCheckMsg + * Text to appear with the checkbox. If null, check box will not be shown. + * @param aCheckState + * Contains the initial checked state of the checkbox when this method + * is called and the final checked state after this method returns. + * + * @return true for OK, false for Cancel. + */ + boolean promptPassword(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText, + inout wstring aPassword, + in wstring aCheckMsg, + inout boolean aCheckState); + + /** + * Puts up a dialog box which has a list box of strings from which the user + * may make a single selection. + * + * @param aParent + * The parent window or null. + * @param aDialogTitle + * Text to appear in the title of the dialog. + * @param aText + * Text to appear in the body of the dialog. + * @param aCount + * The length of the aSelectList array parameter. + * @param aSelectList + * The list of strings to display. + * @param aOutSelection + * Contains the index of the selected item in the list when this + * method returns true. + * + * @return true for OK, false for Cancel. + */ + boolean select(in mozIDOMWindowProxy aParent, + in wstring aDialogTitle, + in wstring aText, + in uint32_t aCount, + [array, size_is(aCount)] in wstring aSelectList, + out long aOutSelection); +}; diff --git a/embedding/components/windowwatcher/nsIPromptService2.idl b/embedding/components/windowwatcher/nsIPromptService2.idl new file mode 100644 index 000000000..4afa0975b --- /dev/null +++ b/embedding/components/windowwatcher/nsIPromptService2.idl @@ -0,0 +1,45 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIPromptService.idl" + +interface nsIAuthInformation; +interface nsIAuthPromptCallback; +interface nsICancelable; +interface nsIChannel; +interface mozIDOMWindowProxy; + +/** + * This is an improved version of nsIPromptService that is less prescriptive + * about the resulting user interface. + * + * @status INCOMPLETE do not freeze before fixing bug 228207 + */ +[scriptable, uuid(3775ad32-8326-422b-9ff3-87ef1d3f9f0e)] +interface nsIPromptService2 : nsIPromptService { + // NOTE: These functions differ from their nsIAuthPrompt counterparts by + // having additional checkbox parameters + // checkValue can be null meaning to show no checkbox + // checkboxLabel is a wstring so that it can be null from both JS and C++ in + // a convenient way + // + // See nsIAuthPrompt2 for documentation on the semantics of the other + // parameters. + boolean promptAuth(in mozIDOMWindowProxy aParent, + in nsIChannel aChannel, + in uint32_t level, + in nsIAuthInformation authInfo, + in wstring checkboxLabel, + inout boolean checkValue); + + nsICancelable asyncPromptAuth(in mozIDOMWindowProxy aParent, + in nsIChannel aChannel, + in nsIAuthPromptCallback aCallback, + in nsISupports aContext, + in uint32_t level, + in nsIAuthInformation authInfo, + in wstring checkboxLabel, + inout boolean checkValue); +}; + diff --git a/embedding/components/windowwatcher/nsIWindowWatcher.idl b/embedding/components/windowwatcher/nsIWindowWatcher.idl new file mode 100644 index 000000000..deea268c2 --- /dev/null +++ b/embedding/components/windowwatcher/nsIWindowWatcher.idl @@ -0,0 +1,167 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface mozIDOMWindowProxy; +interface nsIObserver; +interface nsIPrompt; +interface nsIAuthPrompt; +interface nsISimpleEnumerator; +interface nsIWebBrowserChrome; +interface nsIWindowCreator; + + + +/** + * nsIWindowWatcher is the keeper of Gecko/DOM Windows. It maintains + * a list of open top-level windows, and allows some operations on them. + + * Usage notes: + + * This component has an |activeWindow| property. Clients may expect + * this property to be always current, so to properly integrate this component + * the application will need to keep it current by setting the property + * as the active window changes. + * This component should not keep a (XPCOM) reference to any windows; + * the implementation will claim no ownership. Windows must notify + * this component when they are created or destroyed, so only a weak + * reference is kept. Note that there is no interface for such notifications + * (not a public one, anyway). This is taken care of both in Mozilla and + * by common embedding code. Embedding clients need do nothing special + * about that requirement. + * This component must be initialized at application startup by calling + * setWindowCreator. + */ +[scriptable, uuid(641fe945-6902-4b3f-87c2-0daef32499b3)] +interface nsIWindowWatcher : nsISupports { + + /** Create a new window. It will automatically be added to our list + (via addWindow()). + @param aParent parent window, if any. Null if no parent. If it is + impossible to get to an nsIWebBrowserChrome from aParent, this + method will effectively act as if aParent were null. + @param aURL url to which to open the new window. Must already be + escaped, if applicable. can be null. + @param aName window name from JS window.open. can be null. If a window + with this name already exists, the openWindow call may just load + aUrl in it (if aUrl is not null) and return it. + @param aFeatures window features from JS window.open. can be null. + @param aArguments extra argument(s) to the new window, to be attached + as the |arguments| property. An nsIArray will be + unwound into multiple arguments (but not recursively!). + can be null. + @return the new window + + @note This method may examine the JS context stack for purposes of + determining the security context to use for the search for a given + window named aName. + @note This method should try to set the default charset for the new + window to the default charset of aParent. This is not guaranteed, + however. + @note This method may dispatch a "toplevel-window-ready" notification + via nsIObserverService if the window did not already exist. + */ + mozIDOMWindowProxy openWindow(in mozIDOMWindowProxy aParent, in string aUrl, + in string aName, in string aFeatures, + in nsISupports aArguments); + + /** Clients of this service can register themselves to be notified + when a window is opened or closed (added to or removed from this + service). This method adds an aObserver to the list of objects + to be notified. + @param aObserver the object to be notified when windows are + opened or closed. Its Observe method will be + called with the following parameters: + + aObserver::Observe interprets its parameters so: + aSubject the window being opened or closed, sent as an nsISupports + which can be QIed to an nsIDOMWindow. + aTopic a wstring, either "domwindowopened" or "domwindowclosed". + someData not used. + */ + void registerNotification(in nsIObserver aObserver); + + /** Clients of this service can register themselves to be notified + when a window is opened or closed (added to or removed from this + service). This method removes an aObserver from the list of objects + to be notified. + @param aObserver the observer to be removed. + */ + void unregisterNotification(in nsIObserver aObserver); + + /** Get an iterator for currently open windows in the order they were opened, + guaranteeing that each will be visited exactly once. + @return an enumerator which will itself return nsISupports objects which + can be QIed to an nsIDOMWindow + */ + + nsISimpleEnumerator getWindowEnumerator(); + + /** Return a newly created nsIPrompt implementation. + @param aParent the parent window used for posing alerts. can be null. + @return a new nsIPrompt object + */ + + nsIPrompt getNewPrompter(in mozIDOMWindowProxy aParent); + + /** Return a newly created nsIAuthPrompt implementation. + @param aParent the parent window used for posing alerts. can be null. + @return a new nsIAuthPrompt object + */ + + nsIAuthPrompt getNewAuthPrompter(in mozIDOMWindowProxy aParent); + + /** Set the window creator callback. It must be filled in by the app. + openWindow will use it to create new windows. + @param creator the callback. if null, the callback will be cleared + and window creation capabilities lost. + */ + void setWindowCreator(in nsIWindowCreator creator); + + /** Returns true if a window creator callback has been set, false otherwise. + */ + boolean hasWindowCreator(); + + + /** Retrieve the chrome window mapped to the given DOM window. Window + Watcher keeps a list of all top-level DOM windows currently open, + along with their corresponding chrome interfaces. Since DOM Windows + lack a (public) means of retrieving their corresponding chrome, + this method will do that. + @param aWindow the DOM window whose chrome window the caller needs + @return the corresponding chrome window + */ + nsIWebBrowserChrome getChromeForWindow(in mozIDOMWindowProxy aWindow); + + /** + Retrieve an existing window (or frame). + @param aTargetName the window name + @param aCurrentWindow a starting point in the window hierarchy to + begin the search. If null, each toplevel window + will be searched. + + Note: This method will search all open windows for any window or + frame with the given window name. Make sure you understand the + security implications of this before using this method! + */ + mozIDOMWindowProxy getWindowByName(in AString aTargetName, + in mozIDOMWindowProxy aCurrentWindow); + + /** The Watcher serves as a global storage facility for the current active + (frontmost non-floating-palette-type) window, storing and returning + it on demand. Users must keep this attribute current, including after + the topmost window is closed. This attribute obviously can return null + if no windows are open, but should otherwise always return a valid + window. + */ + attribute mozIDOMWindowProxy activeWindow; + +}; + +%{C++ +#define NS_WINDOWWATCHER_CONTRACTID "@mozilla.org/embedcomp/window-watcher;1" +%} diff --git a/embedding/components/windowwatcher/nsPIPromptService.idl b/embedding/components/windowwatcher/nsPIPromptService.idl new file mode 100644 index 000000000..32459b26b --- /dev/null +++ b/embedding/components/windowwatcher/nsPIPromptService.idl @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* The general dialog posing function within nsPromptService, for + private consumption, only. */ + +#include "nsISupports.idl" + +interface nsIDOMWindow; +interface nsIDialogParamBlock; + +[uuid(C60A1955-6CB3-4827-8EF8-4F5C668AF0B3)] +interface nsPIPromptService : nsISupports +{ +%{C++ + // eOpeningSound is obsolete but we need to support it for the compatibility. + // The implementers should use eSoundEventId instead. + enum {eMsg=0, eCheckboxMsg=1, eIconClass=2, eTitleMessage=3, eEditfield1Msg=4, + eEditfield2Msg=5, eEditfield1Value=6, eEditfield2Value=7, + eButton0Text=8, eButton1Text=9, eButton2Text=10, eButton3Text=11, + eDialogTitle=12, eOpeningSound=13}; + enum {eButtonPressed=0, eCheckboxState=1, eNumberButtons=2, + eNumberEditfields=3, eEditField1Password=4, eDefaultButton=5, + eDelayButtonEnable=6, eSoundEventId=7}; +%} + + void doDialog(in nsIDOMWindow aParent, in nsIDialogParamBlock aParamBlock, in string aChromeURL); +}; + diff --git a/embedding/components/windowwatcher/nsPIWindowWatcher.idl b/embedding/components/windowwatcher/nsPIWindowWatcher.idl new file mode 100644 index 000000000..47b243364 --- /dev/null +++ b/embedding/components/windowwatcher/nsPIWindowWatcher.idl @@ -0,0 +1,146 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Private "control" methods on the Window Watcher. These are annoying + bookkeeping methods, not part of the public (embedding) interface. +*/ + +#include "nsISupports.idl" + +interface mozIDOMWindowProxy; +interface nsIDOMWindow; +interface nsISimpleEnumerator; +interface nsIWebBrowserChrome; +interface nsIDocShellTreeItem; +interface nsIArray; +interface nsITabParent; +interface nsIDocShellLoadInfo; + +[uuid(d162f9c4-19d5-4723-931f-f1e51bfa9f68)] + +interface nsPIWindowWatcher : nsISupports +{ + /** A window has been created. Add it to our list. + @param aWindow the window to add + @param aChrome the corresponding chrome window. The DOM window + and chrome will be mapped together, and the corresponding + chrome can be retrieved using the (not private) + method getChromeForWindow. If null, any extant mapping + will be cleared. + */ + void addWindow(in mozIDOMWindowProxy aWindow, + in nsIWebBrowserChrome aChrome); + + /** A window has been closed. Remove it from our list. + @param aWindow the window to remove + */ + void removeWindow(in mozIDOMWindowProxy aWindow); + + /** Like the public interface's open(), but can handle openDialog-style + arguments and calls which shouldn't result in us navigating the window. + + @param aParent parent window, if any. Null if no parent. If it is + impossible to get to an nsIWebBrowserChrome from aParent, this + method will effectively act as if aParent were null. + @param aURL url to which to open the new window. Must already be + escaped, if applicable. can be null. + @param aName window name from JS window.open. can be null. If a window + with this name already exists, the openWindow call may just load + aUrl in it (if aUrl is not null) and return it. + @param aFeatures window features from JS window.open. can be null. + @param aCalledFromScript true if we were called from script. + @param aDialog use dialog defaults (see nsIDOMWindow::openDialog) + @param aNavigate true if we should navigate the new window to the + specified URL. + @param aArgs Window argument + @param aIsPopupSpam true if the window is a popup spam window; used for + popup blocker internals. + @param aForceNoOpener If true, force noopener behavior. This means not + looking for existing windows with the given name, + not setting an opener on the newly opened window, + and returning null from this method. + @param aLoadInfo if aNavigate is true, this allows the caller to pass in + an nsIDocShellLoadInfo to use for the navigation. + Callers can pass in null if they want the windowwatcher + to just construct a loadinfo itself. If aNavigate is + false, this argument is ignored. + + @return the new window + + @note This method may examine the JS context stack for purposes of + determining the security context to use for the search for a given + window named aName. + @note This method should try to set the default charset for the new + window to the default charset of the document in the calling window + (which is determined based on the JS stack and the value of + aParent). This is not guaranteed, however. + */ + mozIDOMWindowProxy openWindow2(in mozIDOMWindowProxy aParent, in string aUrl, + in string aName, in string aFeatures, + in boolean aCalledFromScript, + in boolean aDialog, + in boolean aNavigate, + in nsISupports aArgs, + in boolean aIsPopupSpam, + in boolean aForceNoOpener, + in nsIDocShellLoadInfo aLoadInfo); + + /** + * Opens a new window using the most recent non-private browser + * window as its parent. + * + * @return the nsITabParent of the initial browser for the newly opened + * window. + */ + nsITabParent openWindowWithoutParent(); + + /** + * Opens a new window so that the window that aOpeningTab belongs to + * is set as the parent window. The newly opened window will also + * inherit load context information from aOpeningTab. + * + * @param aOpeningTab + * The nsITabParent that is requesting the new window be opened. + * @param aFeatures + * Window features if called with window.open or similar. + * @param aCalledFromJS + * True if called via window.open or similar. + * @param aOpenerFullZoom + * The current zoom multiplier for the opener tab. This is then + * applied to the newly opened window. + * + * @return the nsITabParent of the initial browser for the newly opened + * window. + */ + nsITabParent openWindowWithTabParent(in nsITabParent aOpeningTab, + in ACString aFeatures, + in boolean aCalledFromJS, + in float aOpenerFullZoom); + + /** + * Find a named docshell tree item amongst all windows registered + * with the window watcher. This may be a subframe in some window, + * for example. + * + * @param aName the name of the window. Must not be null. + * @param aRequestor the tree item immediately making the request. + * We should make sure to not recurse down into its findItemWithName + * method. + * @param aOriginalRequestor the original treeitem that made the request. + * Used for security checks. + * @return the tree item with aName as the name, or null if there + * isn't one. "Special" names, like _self, _top, etc, will be + * treated specially only if aRequestor is null; in that case they + * will be resolved relative to the first window the windowwatcher + * knows about. + * @see findItemWithName methods on nsIDocShellTreeItem and + * nsIDocShellTreeOwner + */ + nsIDocShellTreeItem findItemWithName(in AString aName, + in nsIDocShellTreeItem aRequestor, + in nsIDocShellTreeItem aOriginalRequestor); +}; + diff --git a/embedding/components/windowwatcher/nsPromptUtils.h b/embedding/components/windowwatcher/nsPromptUtils.h new file mode 100644 index 000000000..bd1267bef --- /dev/null +++ b/embedding/components/windowwatcher/nsPromptUtils.h @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NSPROMPTUTILS_H_ +#define NSPROMPTUTILS_H_ + +#include "nsIHttpChannel.h" + +/** + * @file + * This file defines some helper functions that simplify interaction + * with authentication prompts. + */ + +/** + * Given a username (possibly in DOMAIN\user form) and password, parses the + * domain out of the username if necessary and sets domain, username and + * password on the auth information object. + */ +inline void +NS_SetAuthInfo(nsIAuthInformation* aAuthInfo, const nsString& aUser, + const nsString& aPassword) +{ + uint32_t flags; + aAuthInfo->GetFlags(&flags); + if (flags & nsIAuthInformation::NEED_DOMAIN) { + // Domain is separated from username by a backslash + int32_t idx = aUser.FindChar(char16_t('\\')); + if (idx == kNotFound) { + aAuthInfo->SetUsername(aUser); + } else { + aAuthInfo->SetDomain(Substring(aUser, 0, idx)); + aAuthInfo->SetUsername(Substring(aUser, idx + 1)); + } + } else { + aAuthInfo->SetUsername(aUser); + } + aAuthInfo->SetPassword(aPassword); +} + +/** + * Gets the host and port from a channel and authentication info. This is the + * "logical" host and port for this authentication, i.e. for a proxy + * authentication it refers to the proxy, while for a host authentication it + * is the actual host. + * + * @param machineProcessing + * When this parameter is true, the host will be returned in ASCII + * (instead of UTF-8; this is relevant when IDN is used). In addition, + * the port will be returned as the real port even when it was not + * explicitly specified (when false, the port will be returned as -1 in + * this case) + */ +inline void +NS_GetAuthHostPort(nsIChannel* aChannel, nsIAuthInformation* aAuthInfo, + bool aMachineProcessing, nsCString& aHost, int32_t* aPort) +{ + nsCOMPtr<nsIURI> uri; + nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); + if (NS_FAILED(rv)) { + return; + } + + // Have to distinguish proxy auth and host auth here... + uint32_t flags; + aAuthInfo->GetFlags(&flags); + if (flags & nsIAuthInformation::AUTH_PROXY) { + nsCOMPtr<nsIProxiedChannel> proxied(do_QueryInterface(aChannel)); + NS_ASSERTION(proxied, "proxy auth needs nsIProxiedChannel"); + + nsCOMPtr<nsIProxyInfo> info; + proxied->GetProxyInfo(getter_AddRefs(info)); + NS_ASSERTION(info, "proxy auth needs nsIProxyInfo"); + + nsAutoCString idnhost; + info->GetHost(idnhost); + info->GetPort(aPort); + + if (aMachineProcessing) { + nsCOMPtr<nsIIDNService> idnService = + do_GetService(NS_IDNSERVICE_CONTRACTID); + if (idnService) { + idnService->ConvertUTF8toACE(idnhost, aHost); + } else { + // Not much we can do here... + aHost = idnhost; + } + } else { + aHost = idnhost; + } + } else { + if (aMachineProcessing) { + uri->GetAsciiHost(aHost); + *aPort = NS_GetRealPort(uri); + } else { + uri->GetHost(aHost); + uri->GetPort(aPort); + } + } +} + +/** + * Creates the key for looking up passwords in the password manager. This + * function uses the same format that Gecko functions have always used, thus + * ensuring backwards compatibility. + */ +inline void +NS_GetAuthKey(nsIChannel* aChannel, nsIAuthInformation* aAuthInfo, + nsCString& aKey) +{ + // HTTP does this differently from other protocols + nsCOMPtr<nsIHttpChannel> http(do_QueryInterface(aChannel)); + if (!http) { + nsCOMPtr<nsIURI> uri; + aChannel->GetURI(getter_AddRefs(uri)); + uri->GetPrePath(aKey); + return; + } + + // NOTE: For backwards-compatibility reasons, this must be the ASCII host. + nsCString host; + int32_t port = -1; + + NS_GetAuthHostPort(aChannel, aAuthInfo, true, host, &port); + + nsAutoString realm; + aAuthInfo->GetRealm(realm); + + // Now assemble the key: host:port (realm) + aKey.Append(host); + aKey.Append(':'); + aKey.AppendInt(port); + aKey.AppendLiteral(" ("); + AppendUTF16toUTF8(realm, aKey); + aKey.Append(')'); +} + +#endif diff --git a/embedding/components/windowwatcher/nsWindowWatcher.cpp b/embedding/components/windowwatcher/nsWindowWatcher.cpp new file mode 100644 index 000000000..07872410e --- /dev/null +++ b/embedding/components/windowwatcher/nsWindowWatcher.cpp @@ -0,0 +1,2610 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//#define USEWEAKREFS // (haven't quite figured that out yet) + +#include "nsWindowWatcher.h" +#include "nsAutoWindowStateHelper.h" + +#include "nsCRT.h" +#include "nsNetUtil.h" +#include "nsIAuthPrompt.h" +#include "nsIAuthPrompt2.h" +#include "nsISimpleEnumerator.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsJSUtils.h" +#include "plstr.h" + +#include "nsDocShell.h" +#include "nsGlobalWindow.h" +#include "nsIBaseWindow.h" +#include "nsIBrowserDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIDocShellLoadInfo.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIDocumentLoader.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIDOMWindow.h" +#include "nsIDOMChromeWindow.h" +#include "nsIDOMModalContentWindow.h" +#include "nsIPrompt.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScreen.h" +#include "nsIScreenManager.h" +#include "nsIScriptContext.h" +#include "nsIObserverService.h" +#include "nsIScriptSecurityManager.h" +#include "nsXPCOM.h" +#include "nsIURI.h" +#include "nsIWebBrowser.h" +#include "nsIWebBrowserChrome.h" +#include "nsIWebNavigation.h" +#include "nsIWindowCreator.h" +#include "nsIWindowCreator2.h" +#include "nsIXPConnect.h" +#include "nsIXULRuntime.h" +#include "nsPIDOMWindow.h" +#include "nsIContentViewer.h" +#include "nsIWindowProvider.h" +#include "nsIMutableArray.h" +#include "nsIDOMStorageManager.h" +#include "nsIWidget.h" +#include "nsFocusManager.h" +#include "nsIPresShell.h" +#include "nsPresContext.h" +#include "nsContentUtils.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsSandboxFlags.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/DOMStorage.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/TabParent.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/TabGroup.h" +#include "nsIXULWindow.h" +#include "nsIXULBrowserWindow.h" +#include "nsGlobalWindow.h" + +#ifdef USEWEAKREFS +#include "nsIWeakReference.h" +#endif + +using namespace mozilla; +using namespace mozilla::dom; + +/**************************************************************** + ******************** nsWatcherWindowEntry ********************** + ****************************************************************/ + +class nsWindowWatcher; + +struct nsWatcherWindowEntry +{ + + nsWatcherWindowEntry(mozIDOMWindowProxy* aWindow, nsIWebBrowserChrome* aChrome) + : mChrome(nullptr) + { +#ifdef USEWEAKREFS + mWindow = do_GetWeakReference(aWindow); +#else + mWindow = aWindow; +#endif + nsCOMPtr<nsISupportsWeakReference> supportsweak(do_QueryInterface(aChrome)); + if (supportsweak) { + supportsweak->GetWeakReference(getter_AddRefs(mChromeWeak)); + } else { + mChrome = aChrome; + mChromeWeak = nullptr; + } + ReferenceSelf(); + } + ~nsWatcherWindowEntry() {} + + void InsertAfter(nsWatcherWindowEntry* aOlder); + void Unlink(); + void ReferenceSelf(); + +#ifdef USEWEAKREFS + nsCOMPtr<nsIWeakReference> mWindow; +#else // still not an owning ref + mozIDOMWindowProxy* mWindow; +#endif + nsIWebBrowserChrome* mChrome; + nsWeakPtr mChromeWeak; + // each struct is in a circular, doubly-linked list + nsWatcherWindowEntry* mYounger; // next younger in sequence + nsWatcherWindowEntry* mOlder; +}; + +void +nsWatcherWindowEntry::InsertAfter(nsWatcherWindowEntry* aOlder) +{ + if (aOlder) { + mOlder = aOlder; + mYounger = aOlder->mYounger; + mOlder->mYounger = this; + if (mOlder->mOlder == mOlder) { + mOlder->mOlder = this; + } + mYounger->mOlder = this; + if (mYounger->mYounger == mYounger) { + mYounger->mYounger = this; + } + } +} + +void +nsWatcherWindowEntry::Unlink() +{ + mOlder->mYounger = mYounger; + mYounger->mOlder = mOlder; + ReferenceSelf(); +} + +void +nsWatcherWindowEntry::ReferenceSelf() +{ + + mYounger = this; + mOlder = this; +} + +/**************************************************************** + ****************** nsWatcherWindowEnumerator ******************* + ****************************************************************/ + +class nsWatcherWindowEnumerator : public nsISimpleEnumerator +{ + +public: + explicit nsWatcherWindowEnumerator(nsWindowWatcher* aWatcher); + NS_IMETHOD HasMoreElements(bool* aResult) override; + NS_IMETHOD GetNext(nsISupports** aResult) override; + + NS_DECL_ISUPPORTS + +protected: + virtual ~nsWatcherWindowEnumerator(); + +private: + friend class nsWindowWatcher; + + nsWatcherWindowEntry* FindNext(); + void WindowRemoved(nsWatcherWindowEntry* aInfo); + + nsWindowWatcher* mWindowWatcher; + nsWatcherWindowEntry* mCurrentPosition; +}; + +NS_IMPL_ADDREF(nsWatcherWindowEnumerator) +NS_IMPL_RELEASE(nsWatcherWindowEnumerator) +NS_IMPL_QUERY_INTERFACE(nsWatcherWindowEnumerator, nsISimpleEnumerator) + +nsWatcherWindowEnumerator::nsWatcherWindowEnumerator(nsWindowWatcher* aWatcher) + : mWindowWatcher(aWatcher) + , mCurrentPosition(aWatcher->mOldestWindow) +{ + mWindowWatcher->AddEnumerator(this); + mWindowWatcher->AddRef(); +} + +nsWatcherWindowEnumerator::~nsWatcherWindowEnumerator() +{ + mWindowWatcher->RemoveEnumerator(this); + mWindowWatcher->Release(); +} + +NS_IMETHODIMP +nsWatcherWindowEnumerator::HasMoreElements(bool* aResult) +{ + if (!aResult) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = !!mCurrentPosition; + return NS_OK; +} + +NS_IMETHODIMP +nsWatcherWindowEnumerator::GetNext(nsISupports** aResult) +{ + if (!aResult) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = nullptr; + +#ifdef USEWEAKREFS + while (mCurrentPosition) { + CallQueryReferent(mCurrentPosition->mWindow, aResult); + if (*aResult) { + mCurrentPosition = FindNext(); + break; + } else { // window is gone! + mWindowWatcher->RemoveWindow(mCurrentPosition); + } + } + NS_IF_ADDREF(*aResult); +#else + if (mCurrentPosition) { + CallQueryInterface(mCurrentPosition->mWindow, aResult); + mCurrentPosition = FindNext(); + } +#endif + return NS_OK; +} + +nsWatcherWindowEntry* +nsWatcherWindowEnumerator::FindNext() +{ + nsWatcherWindowEntry* info; + + if (!mCurrentPosition) { + return 0; + } + + info = mCurrentPosition->mYounger; + return info == mWindowWatcher->mOldestWindow ? 0 : info; +} + +// if a window is being removed adjust the iterator's current position +void +nsWatcherWindowEnumerator::WindowRemoved(nsWatcherWindowEntry* aInfo) +{ + + if (mCurrentPosition == aInfo) { + mCurrentPosition = + mCurrentPosition != aInfo->mYounger ? aInfo->mYounger : 0; + } +} + +/**************************************************************** + *********************** nsWindowWatcher ************************ + ****************************************************************/ + +NS_IMPL_ADDREF(nsWindowWatcher) +NS_IMPL_RELEASE(nsWindowWatcher) +NS_IMPL_QUERY_INTERFACE(nsWindowWatcher, + nsIWindowWatcher, + nsIPromptFactory, + nsPIWindowWatcher) + +nsWindowWatcher::nsWindowWatcher() + : mEnumeratorList() + , mOldestWindow(0) + , mListLock("nsWindowWatcher.mListLock") +{ +} + +nsWindowWatcher::~nsWindowWatcher() +{ + // delete data + while (mOldestWindow) { + RemoveWindow(mOldestWindow); + } +} + +nsresult +nsWindowWatcher::Init() +{ + return NS_OK; +} + +/** + * Convert aArguments into either an nsIArray or nullptr. + * + * - If aArguments is nullptr, return nullptr. + * - If aArguments is an nsArray, return nullptr if it's empty, or otherwise + * return the array. + * - If aArguments is an nsIArray, return nullptr if it's empty, or + * otherwise just return the array. + * - Otherwise, return an nsIArray with one element: aArguments. + */ +static already_AddRefed<nsIArray> +ConvertArgsToArray(nsISupports* aArguments) +{ + if (!aArguments) { + return nullptr; + } + + nsCOMPtr<nsIArray> array = do_QueryInterface(aArguments); + if (array) { + uint32_t argc = 0; + array->GetLength(&argc); + if (argc == 0) { + return nullptr; + } + + return array.forget(); + } + + nsCOMPtr<nsIMutableArray> singletonArray = + do_CreateInstance(NS_ARRAY_CONTRACTID); + NS_ENSURE_TRUE(singletonArray, nullptr); + + nsresult rv = singletonArray->AppendElement(aArguments, /* aWeak = */ false); + NS_ENSURE_SUCCESS(rv, nullptr); + + return singletonArray.forget(); +} + +NS_IMETHODIMP +nsWindowWatcher::OpenWindow(mozIDOMWindowProxy* aParent, + const char* aUrl, + const char* aName, + const char* aFeatures, + nsISupports* aArguments, + mozIDOMWindowProxy** aResult) +{ + nsCOMPtr<nsIArray> argv = ConvertArgsToArray(aArguments); + + uint32_t argc = 0; + if (argv) { + argv->GetLength(&argc); + } + bool dialog = (argc != 0); + + return OpenWindowInternal(aParent, aUrl, aName, aFeatures, + /* calledFromJS = */ false, dialog, + /* navigate = */ true, argv, + /* aIsPopupSpam = */ false, + /* aForceNoOpener = */ false, + /* aLoadInfo = */ nullptr, + aResult); +} + +struct SizeSpec +{ + SizeSpec() + : mLeft(0) + , mTop(0) + , mOuterWidth(0) + , mOuterHeight(0) + , mInnerWidth(0) + , mInnerHeight(0) + , mLeftSpecified(false) + , mTopSpecified(false) + , mOuterWidthSpecified(false) + , mOuterHeightSpecified(false) + , mInnerWidthSpecified(false) + , mInnerHeightSpecified(false) + , mUseDefaultWidth(false) + , mUseDefaultHeight(false) + { + } + + int32_t mLeft; + int32_t mTop; + int32_t mOuterWidth; // Total window width + int32_t mOuterHeight; // Total window height + int32_t mInnerWidth; // Content area width + int32_t mInnerHeight; // Content area height + + bool mLeftSpecified; + bool mTopSpecified; + bool mOuterWidthSpecified; + bool mOuterHeightSpecified; + bool mInnerWidthSpecified; + bool mInnerHeightSpecified; + + // If these booleans are true, don't look at the corresponding width values + // even if they're specified -- they'll be bogus + bool mUseDefaultWidth; + bool mUseDefaultHeight; + + bool PositionSpecified() const + { + return mLeftSpecified || mTopSpecified; + } + + bool SizeSpecified() const + { + return mOuterWidthSpecified || mOuterHeightSpecified || + mInnerWidthSpecified || mInnerHeightSpecified; + } +}; + +NS_IMETHODIMP +nsWindowWatcher::OpenWindow2(mozIDOMWindowProxy* aParent, + const char* aUrl, + const char* aName, + const char* aFeatures, + bool aCalledFromScript, + bool aDialog, + bool aNavigate, + nsISupports* aArguments, + bool aIsPopupSpam, + bool aForceNoOpener, + nsIDocShellLoadInfo* aLoadInfo, + mozIDOMWindowProxy** aResult) +{ + nsCOMPtr<nsIArray> argv = ConvertArgsToArray(aArguments); + + uint32_t argc = 0; + if (argv) { + argv->GetLength(&argc); + } + + // This is extremely messed up, but this behavior is necessary because + // callers lie about whether they're a dialog window and whether they're + // called from script. Fixing this is bug 779939. + bool dialog = aDialog; + if (!aCalledFromScript) { + dialog = argc > 0; + } + + return OpenWindowInternal(aParent, aUrl, aName, aFeatures, + aCalledFromScript, dialog, + aNavigate, argv, aIsPopupSpam, + aForceNoOpener, aLoadInfo, aResult); +} + +// This static function checks if the aDocShell uses an UserContextId equal to +// the userContextId of subjectPrincipal, if not null. +static bool +CheckUserContextCompatibility(nsIDocShell* aDocShell) +{ + MOZ_ASSERT(aDocShell); + + uint32_t userContextId = + static_cast<nsDocShell*>(aDocShell)->GetOriginAttributes().mUserContextId; + + nsCOMPtr<nsIPrincipal> subjectPrincipal = + nsContentUtils::GetCurrentJSContext() + ? nsContentUtils::SubjectPrincipal() : nullptr; + + // If we don't have a valid principal, probably we are in e10s mode, parent + // side. + if (!subjectPrincipal) { + return true; + } + + // DocShell can have UsercontextID set but loading a document with system + // principal. In this case, we consider everything ok. + if (nsContentUtils::IsSystemPrincipal(subjectPrincipal)) { + return true; + } + + return subjectPrincipal->GetUserContextId() == userContextId; +} + +NS_IMETHODIMP +nsWindowWatcher::OpenWindowWithoutParent(nsITabParent** aResult) +{ + return OpenWindowWithTabParent(nullptr, EmptyCString(), true, 1.0f, aResult); +} + +nsresult +nsWindowWatcher::CreateChromeWindow(const nsACString& aFeatures, + nsIWebBrowserChrome* aParentChrome, + uint32_t aChromeFlags, + uint32_t aContextFlags, + nsITabParent* aOpeningTabParent, + mozIDOMWindowProxy* aOpener, + nsIWebBrowserChrome** aResult) +{ + nsCOMPtr<nsIWindowCreator2> windowCreator2(do_QueryInterface(mWindowCreator)); + if (NS_WARN_IF(!windowCreator2)) { + return NS_ERROR_UNEXPECTED; + } + + // B2G multi-screen support. mozDisplayId is returned from the + // "display-changed" event, it is also platform-dependent. +#ifdef MOZ_WIDGET_GONK + int retval = WinHasOption(aFeatures, "mozDisplayId", 0, nullptr); + windowCreator2->SetScreenId(retval); +#endif + + bool cancel = false; + nsCOMPtr<nsIWebBrowserChrome> newWindowChrome; + nsresult rv = + windowCreator2->CreateChromeWindow2(aParentChrome, aChromeFlags, aContextFlags, + aOpeningTabParent, aOpener, &cancel, + getter_AddRefs(newWindowChrome)); + + if (NS_SUCCEEDED(rv) && cancel) { + newWindowChrome = nullptr; + return NS_ERROR_ABORT; + } + + newWindowChrome.forget(aResult); + return NS_OK; +} + +/** + * Disable persistence of size/position in popups (determined by + * determining whether the features parameter specifies width or height + * in any way). We consider any overriding of the window's size or position + * in the open call as disabling persistence of those attributes. + * Popup windows (which should not persist size or position) generally set + * the size. + * + * @param aFeatures + * The features string that was used to open the window. + * @param aTreeOwner + * The nsIDocShellTreeOwner of the newly opened window. If null, + * this function is a no-op. + */ +void +nsWindowWatcher::MaybeDisablePersistence(const nsACString& aFeatures, + nsIDocShellTreeOwner* aTreeOwner) +{ + if (!aTreeOwner) { + return; + } + + // At the moment, the strings "height=" or "width=" never happen + // outside a size specification, so we can do this the Q&D way. + if (PL_strcasestr(aFeatures.BeginReading(), "width=") || + PL_strcasestr(aFeatures.BeginReading(), "height=")) { + aTreeOwner->SetPersistence(false, false, false); + } +} + +NS_IMETHODIMP +nsWindowWatcher::OpenWindowWithTabParent(nsITabParent* aOpeningTabParent, + const nsACString& aFeatures, + bool aCalledFromJS, + float aOpenerFullZoom, + nsITabParent** aResult) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(mWindowCreator); + + if (!nsContentUtils::IsSafeToRunScript()) { + nsContentUtils::WarnScriptWasIgnored(nullptr); + return NS_ERROR_FAILURE; + } + + if (NS_WARN_IF(!mWindowCreator)) { + return NS_ERROR_UNEXPECTED; + } + + bool isPrivateBrowsingWindow = + Preferences::GetBool("browser.privatebrowsing.autostart"); + + nsCOMPtr<nsPIDOMWindowOuter> parentWindowOuter; + if (aOpeningTabParent) { + // We need to examine the window that aOpeningTabParent belongs to in + // order to inform us of what kind of window we're going to open. + TabParent* openingTab = TabParent::GetFrom(aOpeningTabParent); + parentWindowOuter = openingTab->GetParentWindowOuter(); + + // Propagate the privacy status of the parent window, if + // available, to the child. + if (!isPrivateBrowsingWindow) { + nsCOMPtr<nsILoadContext> parentContext = openingTab->GetLoadContext(); + if (parentContext) { + isPrivateBrowsingWindow = parentContext->UsePrivateBrowsing(); + } + } + } + + if (!parentWindowOuter) { + // We couldn't find a browser window for the opener, so either we + // never were passed aOpeningTabParent, the window is closed, + // or it's in the process of closing. Either way, we'll use + // the most recently opened browser window instead. + parentWindowOuter = nsContentUtils::GetMostRecentNonPBWindow(); + } + + if (NS_WARN_IF(!parentWindowOuter)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner; + GetWindowTreeOwner(parentWindowOuter, getter_AddRefs(parentTreeOwner)); + if (NS_WARN_IF(!parentTreeOwner)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIWindowCreator2> windowCreator2(do_QueryInterface(mWindowCreator)); + if (NS_WARN_IF(!windowCreator2)) { + return NS_ERROR_UNEXPECTED; + } + + uint32_t contextFlags = 0; + if (parentWindowOuter->IsLoadingOrRunningTimeout()) { + contextFlags |= + nsIWindowCreator2::PARENT_IS_LOADING_OR_RUNNING_TIMEOUT; + } + + uint32_t chromeFlags = CalculateChromeFlagsForChild(aFeatures); + + // A content process has asked for a new window, which implies + // that the new window will need to be remote. + chromeFlags |= nsIWebBrowserChrome::CHROME_REMOTE_WINDOW; + + nsCOMPtr<nsIWebBrowserChrome> parentChrome(do_GetInterface(parentTreeOwner)); + nsCOMPtr<nsIWebBrowserChrome> newWindowChrome; + + CreateChromeWindow(aFeatures, parentChrome, chromeFlags, contextFlags, + aOpeningTabParent, nullptr, getter_AddRefs(newWindowChrome)); + + if (NS_WARN_IF(!newWindowChrome)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIDocShellTreeItem> chromeTreeItem = do_GetInterface(newWindowChrome); + if (NS_WARN_IF(!chromeTreeItem)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIDocShellTreeOwner> chromeTreeOwner; + chromeTreeItem->GetTreeOwner(getter_AddRefs(chromeTreeOwner)); + if (NS_WARN_IF(!chromeTreeOwner)) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsILoadContext> chromeContext = do_QueryInterface(chromeTreeItem); + if (NS_WARN_IF(!chromeContext)) { + return NS_ERROR_UNEXPECTED; + } + + chromeContext->SetPrivateBrowsing(isPrivateBrowsingWindow); + + // Tabs opened from a content process can only open new windows + // that will also run with out-of-process tabs. + chromeContext->SetRemoteTabs(true); + + MaybeDisablePersistence(aFeatures, chromeTreeOwner); + + SizeSpec sizeSpec; + CalcSizeSpec(aFeatures, sizeSpec); + SizeOpenedWindow(chromeTreeOwner, parentWindowOuter, false, sizeSpec, + Some(aOpenerFullZoom)); + + nsCOMPtr<nsITabParent> newTabParent; + chromeTreeOwner->GetPrimaryTabParent(getter_AddRefs(newTabParent)); + if (NS_WARN_IF(!newTabParent)) { + return NS_ERROR_UNEXPECTED; + } + + newTabParent.forget(aResult); + return NS_OK; +} + +nsresult +nsWindowWatcher::OpenWindowInternal(mozIDOMWindowProxy* aParent, + const char* aUrl, + const char* aName, + const char* aFeatures, + bool aCalledFromJS, + bool aDialog, + bool aNavigate, + nsIArray* aArgv, + bool aIsPopupSpam, + bool aForceNoOpener, + nsIDocShellLoadInfo* aLoadInfo, + mozIDOMWindowProxy** aResult) +{ + nsresult rv = NS_OK; + bool isNewToplevelWindow = false; + bool windowIsNew = false; + bool windowNeedsName = false; + bool windowIsModal = false; + bool uriToLoadIsChrome = false; + bool windowIsModalContentDialog = false; + + uint32_t chromeFlags; + nsAutoString name; // string version of aName + nsAutoCString features; // string version of aFeatures + nsCOMPtr<nsIURI> uriToLoad; // from aUrl, if any + nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner; // from the parent window, if any + nsCOMPtr<nsIDocShellTreeItem> newDocShellItem; // from the new window + + nsCOMPtr<nsPIDOMWindowOuter> parent = + aParent ? nsPIDOMWindowOuter::From(aParent) : nullptr; + + NS_ENSURE_ARG_POINTER(aResult); + *aResult = 0; + + if (!nsContentUtils::IsSafeToRunScript()) { + nsContentUtils::WarnScriptWasIgnored(nullptr); + return NS_ERROR_FAILURE; + } + + GetWindowTreeOwner(parent, getter_AddRefs(parentTreeOwner)); + + // We expect TabParent to have provided us the absolute URI of the window + // we're to open, so there's no need to call URIfromURL (or more importantly, + // to check for a chrome URI, which cannot be opened from a remote tab). + if (aUrl) { + rv = URIfromURL(aUrl, aParent, getter_AddRefs(uriToLoad)); + if (NS_FAILED(rv)) { + return rv; + } + uriToLoad->SchemeIs("chrome", &uriToLoadIsChrome); + } + + bool nameSpecified = false; + if (aName) { + CopyUTF8toUTF16(aName, name); + nameSpecified = true; + } else { + name.SetIsVoid(true); + } + + if (aFeatures) { + features.Assign(aFeatures); + features.StripWhitespace(); + } else { + features.SetIsVoid(true); + } + + // try to find an extant window with the given name + nsCOMPtr<nsPIDOMWindowOuter> foundWindow = + SafeGetWindowByName(name, aForceNoOpener, aParent); + GetWindowTreeItem(foundWindow, getter_AddRefs(newDocShellItem)); + + // Do sandbox checks here, instead of waiting until nsIDocShell::LoadURI. + // The state of the window can change before this call and if we are blocked + // because of sandboxing, we wouldn't want that to happen. + nsCOMPtr<nsPIDOMWindowOuter> parentWindow = + aParent ? nsPIDOMWindowOuter::From(aParent) : nullptr; + nsCOMPtr<nsIDocShell> parentDocShell; + if (parentWindow) { + parentDocShell = parentWindow->GetDocShell(); + if (parentDocShell) { + nsCOMPtr<nsIDocShell> foundDocShell = do_QueryInterface(newDocShellItem); + if (parentDocShell->IsSandboxedFrom(foundDocShell)) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } + } + } + + // no extant window? make a new one. + + // If no parent, consider it chrome when running in the parent process. + bool hasChromeParent = XRE_IsContentProcess() ? false : true; + if (aParent) { + // Check if the parent document has chrome privileges. + nsIDocument* doc = parentWindow->GetDoc(); + hasChromeParent = doc && nsContentUtils::IsChromeDoc(doc); + } + + bool isCallerChrome = nsContentUtils::LegacyIsCallerChromeOrNativeCode(); + + // Make sure we calculate the chromeFlags *before* we push the + // callee context onto the context stack so that + // the calculation sees the actual caller when doing its + // security checks. + if (isCallerChrome && XRE_IsParentProcess()) { + chromeFlags = CalculateChromeFlagsForParent(aParent, features, + aDialog, uriToLoadIsChrome, + hasChromeParent, aCalledFromJS); + } else { + chromeFlags = CalculateChromeFlagsForChild(features); + + // Until ShowModalDialog is removed, it's still possible for content to + // request dialogs, but only in single-process mode. + if (aDialog) { + MOZ_ASSERT(XRE_IsParentProcess()); + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG; + } + } + + // If we're not called through our JS version of the API, and we got + // our internal modal option, treat the window we're opening as a + // modal content window (and set the modal chrome flag). + if (!aCalledFromJS && aArgv && + WinHasOption(features, "-moz-internal-modal", 0, nullptr)) { + windowIsModalContentDialog = true; + + // CHROME_MODAL gets inherited by dependent windows, which affects various + // platform-specific window state (especially on OSX). So we need some way + // to determine that this window was actually opened by nsGlobalWindow:: + // ShowModalDialog(), and that somebody is actually going to be watching + // for return values and all that. + chromeFlags |= nsIWebBrowserChrome::CHROME_MODAL_CONTENT_WINDOW; + chromeFlags |= nsIWebBrowserChrome::CHROME_MODAL; + } + + SizeSpec sizeSpec; + CalcSizeSpec(features, sizeSpec); + + nsCOMPtr<nsIScriptSecurityManager> sm( + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID)); + + + // XXXbz Why is an AutoJSAPI good enough here? Wouldn't AutoEntryScript (so + // we affect the entry global) make more sense? Or do we just want to affect + // GetSubjectPrincipal()? + dom::AutoJSAPI jsapiChromeGuard; + + bool windowTypeIsChrome = + chromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_CHROME; + if (isCallerChrome && !hasChromeParent && !windowTypeIsChrome) { + // open() is called from chrome on a non-chrome window, initialize an + // AutoJSAPI with the callee to prevent the caller's privileges from leaking + // into code that runs while opening the new window. + // + // The reasoning for this is in bug 289204. Basically, chrome sometimes does + // someContentWindow.open(untrustedURL), and wants to be insulated from nasty + // javascript: URLs and such. But there are also cases where we create a + // window parented to a content window (such as a download dialog), usually + // directly with nsIWindowWatcher. In those cases, we want the principal of + // the initial about:blank document to be system, so that the subsequent XUL + // load can reuse the inner window and avoid blowing away expandos. As such, + // we decide whether to load with the principal of the caller or of the parent + // based on whether the docshell type is chrome or content. + + nsCOMPtr<nsIGlobalObject> parentGlobalObject = do_QueryInterface(aParent); + if (!aParent) { + jsapiChromeGuard.Init(); + } else if (NS_WARN_IF(!jsapiChromeGuard.Init(parentGlobalObject))) { + return NS_ERROR_UNEXPECTED; + } + } + + uint32_t activeDocsSandboxFlags = 0; + if (!newDocShellItem) { + // We're going to either open up a new window ourselves or ask a + // nsIWindowProvider for one. In either case, we'll want to set the right + // name on it. + windowNeedsName = true; + + // If the parent trying to open a new window is sandboxed + // without 'allow-popups', this is not allowed and we fail here. + if (aParent) { + if (nsIDocument* doc = parentWindow->GetDoc()) { + // Save sandbox flags for copying to new browsing context (docShell). + activeDocsSandboxFlags = doc->GetSandboxFlags(); + if (activeDocsSandboxFlags & SANDBOXED_AUXILIARY_NAVIGATION) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } + } + } + + // Now check whether it's ok to ask a window provider for a window. Don't + // do it if we're opening a dialog or if our parent is a chrome window or + // if we're opening something that has modal, dialog, or chrome flags set. + nsCOMPtr<nsIDOMChromeWindow> chromeWin = do_QueryInterface(aParent); + if (!aDialog && !chromeWin && + !(chromeFlags & (nsIWebBrowserChrome::CHROME_MODAL | + nsIWebBrowserChrome::CHROME_OPENAS_DIALOG | + nsIWebBrowserChrome::CHROME_OPENAS_CHROME))) { + nsCOMPtr<nsIWindowProvider> provider; + if (parentTreeOwner) { + provider = do_GetInterface(parentTreeOwner); + } else if (XRE_IsContentProcess()) { + // we're in a content process but we don't have a tabchild we can + // use. + provider = nsContentUtils::GetWindowProviderForContentProcess(); + } + + if (provider) { + nsCOMPtr<mozIDOMWindowProxy> newWindow; + rv = provider->ProvideWindow(aParent, chromeFlags, aCalledFromJS, + sizeSpec.PositionSpecified(), + sizeSpec.SizeSpecified(), + uriToLoad, name, features, aForceNoOpener, + &windowIsNew, getter_AddRefs(newWindow)); + + if (NS_SUCCEEDED(rv)) { + GetWindowTreeItem(newWindow, getter_AddRefs(newDocShellItem)); + if (windowIsNew && newDocShellItem) { + // Make sure to stop any loads happening in this window that the + // window provider might have started. Otherwise if our caller + // manipulates the window it just opened and then the load + // completes their stuff will get blown away. + nsCOMPtr<nsIWebNavigation> webNav = + do_QueryInterface(newDocShellItem); + webNav->Stop(nsIWebNavigation::STOP_NETWORK); + } + + // If this is a new window, but it's incompatible with the current + // userContextId, we ignore it and we pretend that nothing has been + // returned by ProvideWindow. + if (!windowIsNew && newDocShellItem) { + nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(newDocShellItem); + if (!CheckUserContextCompatibility(docShell)) { + newWindow = nullptr; + newDocShellItem = nullptr; + windowIsNew = false; + } + } + + } else if (rv == NS_ERROR_ABORT) { + // NS_ERROR_ABORT means the window provider has flat-out rejected + // the open-window call and we should bail. Don't return an error + // here, because our caller may propagate that error, which might + // cause e.g. window.open to throw! Just return null for our out + // param. + return NS_OK; + } + } + } + } + + bool newWindowShouldBeModal = false; + bool parentIsModal = false; + if (!newDocShellItem) { + windowIsNew = true; + isNewToplevelWindow = true; + + nsCOMPtr<nsIWebBrowserChrome> parentChrome(do_GetInterface(parentTreeOwner)); + + // is the parent (if any) modal? if so, we must be, too. + bool weAreModal = (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL) != 0; + newWindowShouldBeModal = weAreModal; + if (!weAreModal && parentChrome) { + parentChrome->IsWindowModal(&weAreModal); + parentIsModal = weAreModal; + } + + if (weAreModal) { + windowIsModal = true; + // in case we added this because weAreModal + chromeFlags |= nsIWebBrowserChrome::CHROME_MODAL | + nsIWebBrowserChrome::CHROME_DEPENDENT; + } + + // Make sure to not create modal windows if our parent is invisible and + // isn't a chrome window. Otherwise we can end up in a bizarre situation + // where we can't shut down because an invisible window is open. If + // someone tries to do this, throw. + if (!hasChromeParent && (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL)) { + nsCOMPtr<nsIBaseWindow> parentWindow(do_GetInterface(parentTreeOwner)); + nsCOMPtr<nsIWidget> parentWidget; + if (parentWindow) { + parentWindow->GetMainWidget(getter_AddRefs(parentWidget)); + } + // NOTE: the logic for this visibility check is duplicated in + // nsIDOMWindowUtils::isParentWindowMainWidgetVisible - if we change + // how a window is determined "visible" in this context then we should + // also adjust that attribute and/or any consumers of it... + if (parentWidget && !parentWidget->IsVisible()) { + return NS_ERROR_NOT_AVAILABLE; + } + } + + NS_ASSERTION(mWindowCreator, + "attempted to open a new window with no WindowCreator"); + rv = NS_ERROR_FAILURE; + if (mWindowCreator) { + nsCOMPtr<nsIWebBrowserChrome> newChrome; + + nsCOMPtr<nsPIDOMWindowInner> parentTopInnerWindow; + if (parentWindow) { + nsCOMPtr<nsPIDOMWindowOuter> parentTopWindow = parentWindow->GetTop(); + if (parentTopWindow) { + parentTopInnerWindow = parentTopWindow->GetCurrentInnerWindow(); + } + } + + if (parentTopInnerWindow) { + parentTopInnerWindow->Suspend(); + } + + /* If the window creator is an nsIWindowCreator2, we can give it + some hints. The only hint at this time is whether the opening window + is in a situation that's likely to mean this is an unrequested + popup window we're creating. However we're not completely honest: + we clear that indicator if the opener is chrome, so that the + downstream consumer can treat the indicator to mean simply + that the new window is subject to popup control. */ + nsCOMPtr<nsIWindowCreator2> windowCreator2( + do_QueryInterface(mWindowCreator)); + if (windowCreator2) { + uint32_t contextFlags = 0; + bool popupConditions = false; + + // is the parent under popup conditions? + if (parentWindow) { + popupConditions = parentWindow->IsLoadingOrRunningTimeout(); + } + + // chrome is always allowed, so clear the flag if the opener is chrome + if (popupConditions) { + popupConditions = !isCallerChrome; + } + + if (popupConditions) { + contextFlags |= + nsIWindowCreator2::PARENT_IS_LOADING_OR_RUNNING_TIMEOUT; + } + + mozIDOMWindowProxy* openerWindow = aForceNoOpener ? nullptr : aParent; + rv = CreateChromeWindow(features, parentChrome, chromeFlags, contextFlags, + nullptr, openerWindow, getter_AddRefs(newChrome)); + + } else { + rv = mWindowCreator->CreateChromeWindow(parentChrome, chromeFlags, + getter_AddRefs(newChrome)); + } + + if (parentTopInnerWindow) { + parentTopInnerWindow->Resume(); + } + + if (newChrome) { + nsCOMPtr<nsIXULWindow> xulWin = do_GetInterface(newChrome); + if (xulWin) { + nsCOMPtr<nsIXULBrowserWindow> xulBrowserWin; + xulWin->GetXULBrowserWindow(getter_AddRefs(xulBrowserWin)); + if (xulBrowserWin) { + nsPIDOMWindowOuter* openerWindow = aForceNoOpener ? nullptr : parentWindow.get(); + xulBrowserWin->ForceInitialBrowserNonRemote(openerWindow); + } + } + /* It might be a chrome nsXULWindow, in which case it won't have + an nsIDOMWindow (primary content shell). But in that case, it'll + be able to hand over an nsIDocShellTreeItem directly. */ + nsCOMPtr<nsPIDOMWindowOuter> newWindow(do_GetInterface(newChrome)); + if (newWindow) { + GetWindowTreeItem(newWindow, getter_AddRefs(newDocShellItem)); + } + if (!newDocShellItem) { + newDocShellItem = do_GetInterface(newChrome); + } + if (!newDocShellItem) { + rv = NS_ERROR_FAILURE; + } + } + } + } + + // better have a window to use by this point + if (!newDocShellItem) { + return rv; + } + + nsCOMPtr<nsIDocShell> newDocShell(do_QueryInterface(newDocShellItem)); + NS_ENSURE_TRUE(newDocShell, NS_ERROR_UNEXPECTED); + + // If our parent is sandboxed, set it as the one permitted sandboxed navigator + // on the new window we're opening. + if (activeDocsSandboxFlags && parentWindow) { + newDocShell->SetOnePermittedSandboxedNavigator( + parentWindow->GetDocShell()); + } + + // Copy sandbox flags to the new window if activeDocsSandboxFlags says to do + // so. Note that it's only nonzero if the window is new, so clobbering + // sandbox flags on the window makes sense in that case. + if (activeDocsSandboxFlags & + SANDBOX_PROPAGATES_TO_AUXILIARY_BROWSING_CONTEXTS) { + newDocShell->SetSandboxFlags(activeDocsSandboxFlags); + } + + rv = ReadyOpenedDocShellItem(newDocShellItem, parentWindow, windowIsNew, + aForceNoOpener, aResult); + if (NS_FAILED(rv)) { + return rv; + } + + if (isNewToplevelWindow) { + nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner; + newDocShellItem->GetTreeOwner(getter_AddRefs(newTreeOwner)); + MaybeDisablePersistence(features, newTreeOwner); + } + + if ((aDialog || windowIsModalContentDialog) && aArgv) { + // Set the args on the new window. + nsCOMPtr<nsPIDOMWindowOuter> piwin(do_QueryInterface(*aResult)); + NS_ENSURE_TRUE(piwin, NS_ERROR_UNEXPECTED); + + rv = piwin->SetArguments(aArgv); + NS_ENSURE_SUCCESS(rv, rv); + } + + /* allow a window that we found by name to keep its name (important for cases + like _self where the given name is different (and invalid)). Also, _blank + is not a window name. */ + if (windowNeedsName) { + if (nameSpecified && !name.LowerCaseEqualsLiteral("_blank")) { + newDocShellItem->SetName(name); + } else { + newDocShellItem->SetName(EmptyString()); + } + } + + // Now we have to set the right opener principal on the new window. Note + // that we have to do this _before_ starting any URI loads, thanks to the + // sync nature of javascript: loads. + // + // Note: The check for the current JSContext isn't necessarily sensical. + // It's just designed to preserve old semantics during a mass-conversion + // patch. + nsCOMPtr<nsIPrincipal> subjectPrincipal = + nsContentUtils::GetCurrentJSContext() ? nsContentUtils::SubjectPrincipal() : + nullptr; + + bool isPrivateBrowsingWindow = false; + + if (windowIsNew) { + auto* docShell = static_cast<nsDocShell*>(newDocShell.get()); + + // If this is not a chrome docShell, we apply originAttributes from the + // subjectPrincipal unless if it's an expanded or system principal. + if (subjectPrincipal && + !nsContentUtils::IsSystemOrExpandedPrincipal(subjectPrincipal) && + docShell->ItemType() != nsIDocShellTreeItem::typeChrome) { + DocShellOriginAttributes attrs; + attrs.InheritFromDocToChildDocShell(BasePrincipal::Cast(subjectPrincipal)->OriginAttributesRef()); + isPrivateBrowsingWindow = !!attrs.mPrivateBrowsingId; + docShell->SetOriginAttributes(attrs); + } else { + nsCOMPtr<nsIDocShellTreeItem> parentItem; + GetWindowTreeItem(aParent, getter_AddRefs(parentItem)); + nsCOMPtr<nsILoadContext> parentContext = do_QueryInterface(parentItem); + if (parentContext) { + isPrivateBrowsingWindow = parentContext->UsePrivateBrowsing(); + } + } + + bool autoPrivateBrowsing = + Preferences::GetBool("browser.privatebrowsing.autostart"); + + if (!autoPrivateBrowsing && + (chromeFlags & nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW)) { + isPrivateBrowsingWindow = false; + } else if (autoPrivateBrowsing || + (chromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW)) { + isPrivateBrowsingWindow = true; + } + + // Now set the opener principal on the new window. Note that we need to do + // this no matter whether we were opened from JS; if there is nothing on + // the JS stack, just use the principal of our parent window. In those + // cases we do _not_ set the parent window principal as the owner of the + // load--since we really don't know who the owner is, just leave it null. + nsCOMPtr<nsPIDOMWindowOuter> newWindow = do_QueryInterface(*aResult); + NS_ASSERTION(newWindow == newDocShell->GetWindow(), "Different windows??"); + + // The principal of the initial about:blank document gets set up in + // nsWindowWatcher::AddWindow. Make sure to call it. In the common case + // this call already happened when the window was created, but + // SetInitialPrincipalToSubject is safe to call multiple times. + if (newWindow) { + newWindow->SetInitialPrincipalToSubject(); + if (aIsPopupSpam) { + nsGlobalWindow* globalWin = nsGlobalWindow::Cast(newWindow); + MOZ_ASSERT(!globalWin->IsPopupSpamWindow(), + "Who marked it as popup spam already???"); + if (!globalWin->IsPopupSpamWindow()) { // Make sure we don't mess up our + // counter even if the above + // assert fails. + globalWin->SetIsPopupSpamWindow(true); + } + } + } + } + + // We rely on CalculateChromeFlags to decide whether remote (out-of-process) + // tabs should be used. + bool isRemoteWindow = + !!(chromeFlags & nsIWebBrowserChrome::CHROME_REMOTE_WINDOW); + + if (isNewToplevelWindow) { + nsCOMPtr<nsIDocShellTreeItem> childRoot; + newDocShellItem->GetRootTreeItem(getter_AddRefs(childRoot)); + nsCOMPtr<nsILoadContext> childContext = do_QueryInterface(childRoot); + if (childContext) { + childContext->SetPrivateBrowsing(isPrivateBrowsingWindow); + childContext->SetRemoteTabs(isRemoteWindow); + } + } else if (windowIsNew) { + nsCOMPtr<nsILoadContext> childContext = do_QueryInterface(newDocShellItem); + if (childContext) { + childContext->SetPrivateBrowsing(isPrivateBrowsingWindow); + childContext->SetRemoteTabs(isRemoteWindow); + } + } + + nsCOMPtr<nsIDocShellLoadInfo> loadInfo = aLoadInfo; + if (uriToLoad && aNavigate && !loadInfo) { + newDocShell->CreateLoadInfo(getter_AddRefs(loadInfo)); + NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE); + + if (subjectPrincipal) { + loadInfo->SetTriggeringPrincipal(subjectPrincipal); + } + + /* use the URL from the *extant* document, if any. The usual accessor + GetDocument will synchronously create an about:blank document if + it has no better answer, and we only care about a real document. + Also using GetDocument to force document creation seems to + screw up focus in the hidden window; see bug 36016. + */ + nsCOMPtr<nsIDocument> doc = GetEntryDocument(); + if (!doc && parentWindow) { + doc = parentWindow->GetExtantDoc(); + } + if (doc) { + // Set the referrer + loadInfo->SetReferrer(doc->GetDocumentURI()); + loadInfo->SetReferrerPolicy(doc->GetReferrerPolicy()); + } + } + + if (isNewToplevelWindow) { + // Notify observers that the window is open and ready. + // The window has not yet started to load a document. + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + if (obsSvc) { + obsSvc->NotifyObservers(*aResult, "toplevel-window-ready", nullptr); + } + } + + // Before loading the URI we want to be 100% sure that we use the correct + // userContextId. + MOZ_ASSERT(CheckUserContextCompatibility(newDocShell)); + + if (uriToLoad && aNavigate) { + newDocShell->LoadURI( + uriToLoad, + loadInfo, + windowIsNew ? + static_cast<uint32_t>(nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD) : + static_cast<uint32_t>(nsIWebNavigation::LOAD_FLAGS_NONE), + true); + } + + // Copy the current session storage for the current domain. + if (subjectPrincipal && parentDocShell) { + nsCOMPtr<nsIDOMStorageManager> parentStorageManager = + do_QueryInterface(parentDocShell); + nsCOMPtr<nsIDOMStorageManager> newStorageManager = + do_QueryInterface(newDocShell); + + if (parentStorageManager && newStorageManager) { + nsCOMPtr<nsIDOMStorage> storage; + nsCOMPtr<nsPIDOMWindowInner> pInnerWin = parentWindow->GetCurrentInnerWindow(); + + parentStorageManager->GetStorage(pInnerWin, subjectPrincipal, + isPrivateBrowsingWindow, + getter_AddRefs(storage)); + if (storage) { + newStorageManager->CloneStorage(storage); + } + } + } + + if (isNewToplevelWindow) { + nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner; + newDocShellItem->GetTreeOwner(getter_AddRefs(newTreeOwner)); + SizeOpenedWindow(newTreeOwner, aParent, isCallerChrome, sizeSpec); + } + + // XXXbz isn't windowIsModal always true when windowIsModalContentDialog? + if (windowIsModal || windowIsModalContentDialog) { + nsCOMPtr<nsIDocShellTreeOwner> newTreeOwner; + newDocShellItem->GetTreeOwner(getter_AddRefs(newTreeOwner)); + nsCOMPtr<nsIWebBrowserChrome> newChrome(do_GetInterface(newTreeOwner)); + + // Throw an exception here if no web browser chrome is available, + // we need that to show a modal window. + NS_ENSURE_TRUE(newChrome, NS_ERROR_NOT_AVAILABLE); + + // Dispatch dialog events etc, but we only want to do that if + // we're opening a modal content window (the helper classes are + // no-ops if given no window), for chrome dialogs we don't want to + // do any of that (it's done elsewhere for us). + // Make sure we maintain the state on an outer window, because + // that's where it lives; inner windows assert if you try to + // maintain the state on them. + nsAutoWindowStateHelper windowStateHelper( + parentWindow ? parentWindow->GetOuterWindow() : nullptr); + + if (!windowStateHelper.DefaultEnabled()) { + // Default to cancel not opening the modal window. + NS_RELEASE(*aResult); + + return NS_OK; + } + + bool isAppModal = false; + nsCOMPtr<nsIBaseWindow> parentWindow(do_GetInterface(newTreeOwner)); + nsCOMPtr<nsIWidget> parentWidget; + if (parentWindow) { + parentWindow->GetMainWidget(getter_AddRefs(parentWidget)); + if (parentWidget) { + isAppModal = parentWidget->IsRunningAppModal(); + } + } + if (parentWidget && + ((!newWindowShouldBeModal && parentIsModal) || isAppModal)) { + parentWidget->SetFakeModal(true); + } else { + // Reset popup state while opening a modal dialog, and firing + // events about the dialog, to prevent the current state from + // being active the whole time a modal dialog is open. + nsAutoPopupStatePusher popupStatePusher(openAbused); + + newChrome->ShowAsModal(); + } + } + + if (aForceNoOpener && windowIsNew) { + NS_RELEASE(*aResult); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::RegisterNotification(nsIObserver* aObserver) +{ + // just a convenience method; it delegates to nsIObserverService + + if (!aObserver) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (!os) { + return NS_ERROR_FAILURE; + } + + nsresult rv = os->AddObserver(aObserver, "domwindowopened", false); + if (NS_SUCCEEDED(rv)) { + rv = os->AddObserver(aObserver, "domwindowclosed", false); + } + + return rv; +} + +NS_IMETHODIMP +nsWindowWatcher::UnregisterNotification(nsIObserver* aObserver) +{ + // just a convenience method; it delegates to nsIObserverService + + if (!aObserver) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (!os) { + return NS_ERROR_FAILURE; + } + + os->RemoveObserver(aObserver, "domwindowopened"); + os->RemoveObserver(aObserver, "domwindowclosed"); + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::GetWindowEnumerator(nsISimpleEnumerator** aResult) +{ + if (!aResult) { + return NS_ERROR_INVALID_ARG; + } + + MutexAutoLock lock(mListLock); + nsWatcherWindowEnumerator* enumerator = new nsWatcherWindowEnumerator(this); + if (enumerator) { + return CallQueryInterface(enumerator, aResult); + } + + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsWindowWatcher::GetNewPrompter(mozIDOMWindowProxy* aParent, nsIPrompt** aResult) +{ + // This is for backwards compat only. Callers should just use the prompt + // service directly. + nsresult rv; + nsCOMPtr<nsIPromptFactory> factory = + do_GetService("@mozilla.org/prompter;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + return factory->GetPrompt(aParent, NS_GET_IID(nsIPrompt), + reinterpret_cast<void**>(aResult)); +} + +NS_IMETHODIMP +nsWindowWatcher::GetNewAuthPrompter(mozIDOMWindowProxy* aParent, + nsIAuthPrompt** aResult) +{ + // This is for backwards compat only. Callers should just use the prompt + // service directly. + nsresult rv; + nsCOMPtr<nsIPromptFactory> factory = + do_GetService("@mozilla.org/prompter;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + return factory->GetPrompt(aParent, NS_GET_IID(nsIAuthPrompt), + reinterpret_cast<void**>(aResult)); +} + +NS_IMETHODIMP +nsWindowWatcher::GetPrompt(mozIDOMWindowProxy* aParent, const nsIID& aIID, + void** aResult) +{ + // This is for backwards compat only. Callers should just use the prompt + // service directly. + nsresult rv; + nsCOMPtr<nsIPromptFactory> factory = + do_GetService("@mozilla.org/prompter;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = factory->GetPrompt(aParent, aIID, aResult); + + // Allow for an embedding implementation to not support nsIAuthPrompt2. + if (rv == NS_NOINTERFACE && aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) { + nsCOMPtr<nsIAuthPrompt> oldPrompt; + rv = factory->GetPrompt( + aParent, NS_GET_IID(nsIAuthPrompt), getter_AddRefs(oldPrompt)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_WrapAuthPrompt(oldPrompt, reinterpret_cast<nsIAuthPrompt2**>(aResult)); + if (!*aResult) { + rv = NS_ERROR_NOT_AVAILABLE; + } + } + return rv; +} + +NS_IMETHODIMP +nsWindowWatcher::SetWindowCreator(nsIWindowCreator* aCreator) +{ + mWindowCreator = aCreator; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::HasWindowCreator(bool* aResult) +{ + *aResult = mWindowCreator; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::GetActiveWindow(mozIDOMWindowProxy** aActiveWindow) +{ + *aActiveWindow = nullptr; + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (fm) { + return fm->GetActiveWindow(aActiveWindow); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::SetActiveWindow(mozIDOMWindowProxy* aActiveWindow) +{ + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (fm) { + return fm->SetActiveWindow(aActiveWindow); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::AddWindow(mozIDOMWindowProxy* aWindow, nsIWebBrowserChrome* aChrome) +{ + if (!aWindow) { + return NS_ERROR_INVALID_ARG; + } + +#ifdef DEBUG + { + nsCOMPtr<nsPIDOMWindowOuter> win(do_QueryInterface(aWindow)); + + NS_ASSERTION(win->IsOuterWindow(), + "Uh, the active window must be an outer window!"); + } +#endif + + { + nsWatcherWindowEntry* info; + MutexAutoLock lock(mListLock); + + // if we already have an entry for this window, adjust + // its chrome mapping and return + info = FindWindowEntry(aWindow); + if (info) { + nsCOMPtr<nsISupportsWeakReference> supportsweak( + do_QueryInterface(aChrome)); + if (supportsweak) { + supportsweak->GetWeakReference(getter_AddRefs(info->mChromeWeak)); + } else { + info->mChrome = aChrome; + info->mChromeWeak = nullptr; + } + return NS_OK; + } + + // create a window info struct and add it to the list of windows + info = new nsWatcherWindowEntry(aWindow, aChrome); + if (!info) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (mOldestWindow) { + info->InsertAfter(mOldestWindow->mOlder); + } else { + mOldestWindow = info; + } + } // leave the mListLock + + // a window being added to us signifies a newly opened window. + // send notifications. + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (!os) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsISupports> domwin(do_QueryInterface(aWindow)); + return os->NotifyObservers(domwin, "domwindowopened", 0); +} + +NS_IMETHODIMP +nsWindowWatcher::RemoveWindow(mozIDOMWindowProxy* aWindow) +{ + // find the corresponding nsWatcherWindowEntry, remove it + + if (!aWindow) { + return NS_ERROR_INVALID_ARG; + } + + nsWatcherWindowEntry* info = FindWindowEntry(aWindow); + if (info) { + RemoveWindow(info); + return NS_OK; + } + NS_WARNING("requested removal of nonexistent window"); + return NS_ERROR_INVALID_ARG; +} + +nsWatcherWindowEntry* +nsWindowWatcher::FindWindowEntry(mozIDOMWindowProxy* aWindow) +{ + // find the corresponding nsWatcherWindowEntry + nsWatcherWindowEntry* info; + nsWatcherWindowEntry* listEnd; +#ifdef USEWEAKREFS + nsresult rv; + bool found; +#endif + + info = mOldestWindow; + listEnd = 0; +#ifdef USEWEAKREFS + rv = NS_OK; + found = false; + while (info != listEnd && NS_SUCCEEDED(rv)) { + nsCOMPtr<mozIDOMWindowProxy> infoWindow(do_QueryReferent(info->mWindow)); + if (!infoWindow) { // clean up dangling reference, while we're here + rv = RemoveWindow(info); + } else if (infoWindow.get() == aWindow) { + return info; + } + + info = info->mYounger; + listEnd = mOldestWindow; + } + return 0; +#else + while (info != listEnd) { + if (info->mWindow == aWindow) { + return info; + } + info = info->mYounger; + listEnd = mOldestWindow; + } + return 0; +#endif +} + +nsresult +nsWindowWatcher::RemoveWindow(nsWatcherWindowEntry* aInfo) +{ + uint32_t count = mEnumeratorList.Length(); + + { + // notify the enumerators + MutexAutoLock lock(mListLock); + for (uint32_t ctr = 0; ctr < count; ++ctr) { + mEnumeratorList[ctr]->WindowRemoved(aInfo); + } + + // remove the element from the list + if (aInfo == mOldestWindow) { + mOldestWindow = aInfo->mYounger == mOldestWindow ? 0 : aInfo->mYounger; + } + aInfo->Unlink(); + } + + // a window being removed from us signifies a newly closed window. + // send notifications. + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { +#ifdef USEWEAKREFS + nsCOMPtr<nsISupports> domwin(do_QueryReferent(aInfo->mWindow)); + if (domwin) { + os->NotifyObservers(domwin, "domwindowclosed", 0); + } + // else bummer. since the window is gone, there's nothing to notify with. +#else + nsCOMPtr<nsISupports> domwin(do_QueryInterface(aInfo->mWindow)); + os->NotifyObservers(domwin, "domwindowclosed", 0); +#endif + } + + delete aInfo; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::GetChromeForWindow(mozIDOMWindowProxy* aWindow, + nsIWebBrowserChrome** aResult) +{ + if (!aWindow || !aResult) { + return NS_ERROR_INVALID_ARG; + } + *aResult = 0; + + MutexAutoLock lock(mListLock); + nsWatcherWindowEntry* info = FindWindowEntry(aWindow); + if (info) { + if (info->mChromeWeak) { + return info->mChromeWeak->QueryReferent( + NS_GET_IID(nsIWebBrowserChrome), reinterpret_cast<void**>(aResult)); + } + *aResult = info->mChrome; + NS_IF_ADDREF(*aResult); + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowWatcher::GetWindowByName(const nsAString& aTargetName, + mozIDOMWindowProxy* aCurrentWindow, + mozIDOMWindowProxy** aResult) +{ + if (!aResult) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = nullptr; + + nsPIDOMWindowOuter* currentWindow = + aCurrentWindow ? nsPIDOMWindowOuter::From(aCurrentWindow) : nullptr; + + nsCOMPtr<nsIDocShellTreeItem> treeItem; + + nsCOMPtr<nsIDocShellTreeItem> startItem; + GetWindowTreeItem(currentWindow, getter_AddRefs(startItem)); + if (startItem) { + // Note: original requestor is null here, per idl comments + startItem->FindItemWithName(aTargetName, nullptr, nullptr, + getter_AddRefs(treeItem)); + } else { + // Note: original requestor is null here, per idl comments + FindItemWithName(aTargetName, nullptr, nullptr, getter_AddRefs(treeItem)); + } + + if (treeItem) { + nsCOMPtr<nsPIDOMWindowOuter> domWindow = treeItem->GetWindow(); + domWindow.forget(aResult); + } + + return NS_OK; +} + +bool +nsWindowWatcher::AddEnumerator(nsWatcherWindowEnumerator* aEnumerator) +{ + // (requires a lock; assumes it's called by someone holding the lock) + return mEnumeratorList.AppendElement(aEnumerator) != nullptr; +} + +bool +nsWindowWatcher::RemoveEnumerator(nsWatcherWindowEnumerator* aEnumerator) +{ + // (requires a lock; assumes it's called by someone holding the lock) + return mEnumeratorList.RemoveElement(aEnumerator); +} + +nsresult +nsWindowWatcher::URIfromURL(const char* aURL, + mozIDOMWindowProxy* aParent, + nsIURI** aURI) +{ + // Build the URI relative to the entry global. + nsCOMPtr<nsPIDOMWindowInner> baseWindow = do_QueryInterface(GetEntryGlobal()); + + // failing that, build it relative to the parent window, if possible + if (!baseWindow && aParent) { + baseWindow = nsPIDOMWindowOuter::From(aParent)->GetCurrentInnerWindow(); + } + + // failing that, use the given URL unmodified. It had better not be relative. + + nsIURI* baseURI = nullptr; + + // get baseWindow's document URI + if (baseWindow) { + if (nsIDocument* doc = baseWindow->GetDoc()) { + baseURI = doc->GetDocBaseURI(); + } + } + + // build and return the absolute URI + return NS_NewURI(aURI, aURL, baseURI); +} + +#define NS_CALCULATE_CHROME_FLAG_FOR(feature, flag) \ + prefBranch->GetBoolPref(feature, &forceEnable); \ + if (forceEnable && !aDialog && !aHasChromeParent && !aChromeURL) { \ + chromeFlags |= flag; \ + } else { \ + chromeFlags |= \ + WinHasOption(aFeatures, feature, 0, &presenceFlag) ? flag : 0; \ + } + +// static +uint32_t +nsWindowWatcher::CalculateChromeFlagsHelper(uint32_t aInitialFlags, + const nsACString& aFeatures, + bool& presenceFlag, + bool aDialog, + bool aHasChromeParent, + bool aChromeURL) +{ + uint32_t chromeFlags = aInitialFlags; + + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch; + nsCOMPtr<nsIPrefService> prefs = + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + + NS_ENSURE_SUCCESS(rv, nsIWebBrowserChrome::CHROME_DEFAULT); + + rv = prefs->GetBranch("dom.disable_window_open_feature.", + getter_AddRefs(prefBranch)); + + NS_ENSURE_SUCCESS(rv, nsIWebBrowserChrome::CHROME_DEFAULT); + + // NS_CALCULATE_CHROME_FLAG_FOR requires aFeatures, forceEnable, aDialog + // aHasChromeParent, aChromeURL, presenceFlag and chromeFlags to be in + // scope. + bool forceEnable = false; + + NS_CALCULATE_CHROME_FLAG_FOR("titlebar", + nsIWebBrowserChrome::CHROME_TITLEBAR); + NS_CALCULATE_CHROME_FLAG_FOR("close", + nsIWebBrowserChrome::CHROME_WINDOW_CLOSE); + NS_CALCULATE_CHROME_FLAG_FOR("toolbar", + nsIWebBrowserChrome::CHROME_TOOLBAR); + NS_CALCULATE_CHROME_FLAG_FOR("location", + nsIWebBrowserChrome::CHROME_LOCATIONBAR); + NS_CALCULATE_CHROME_FLAG_FOR("personalbar", + nsIWebBrowserChrome::CHROME_PERSONAL_TOOLBAR); + NS_CALCULATE_CHROME_FLAG_FOR("status", + nsIWebBrowserChrome::CHROME_STATUSBAR); + NS_CALCULATE_CHROME_FLAG_FOR("menubar", + nsIWebBrowserChrome::CHROME_MENUBAR); + NS_CALCULATE_CHROME_FLAG_FOR("resizable", + nsIWebBrowserChrome::CHROME_WINDOW_RESIZE); + NS_CALCULATE_CHROME_FLAG_FOR("minimizable", + nsIWebBrowserChrome::CHROME_WINDOW_MIN); + + // default scrollbar to "on," unless explicitly turned off + if (WinHasOption(aFeatures, "scrollbars", 1, &presenceFlag) || !presenceFlag) { + chromeFlags |= nsIWebBrowserChrome::CHROME_SCROLLBARS; + } + + return chromeFlags; +} + +// static +uint32_t +nsWindowWatcher::EnsureFlagsSafeForContent(uint32_t aChromeFlags, + bool aChromeURL) +{ + aChromeFlags |= nsIWebBrowserChrome::CHROME_TITLEBAR; + aChromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_CLOSE; + aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_LOWERED; + aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_RAISED; + aChromeFlags &= ~nsIWebBrowserChrome::CHROME_WINDOW_POPUP; + /* Untrusted script is allowed to pose modal windows with a chrome + scheme. This check could stand to be better. But it effectively + prevents untrusted script from opening modal windows in general + while still allowing alerts and the like. */ + if (!aChromeURL) { + aChromeFlags &= ~(nsIWebBrowserChrome::CHROME_MODAL | + nsIWebBrowserChrome::CHROME_OPENAS_CHROME); + } + + if (!(aChromeFlags & nsIWebBrowserChrome::CHROME_OPENAS_CHROME)) { + aChromeFlags &= ~nsIWebBrowserChrome::CHROME_DEPENDENT; + } + + return aChromeFlags; +} + +/** + * Calculate the chrome bitmask from a string list of features requested + * from a child process. Feature strings that are restricted to the parent + * process are ignored here. + * @param aFeatures a string containing a list of named features + * @return the chrome bitmask + */ +// static +uint32_t +nsWindowWatcher::CalculateChromeFlagsForChild(const nsACString& aFeatures) +{ + if (aFeatures.IsVoid()) { + return nsIWebBrowserChrome::CHROME_ALL; + } + + bool presenceFlag = false; + uint32_t chromeFlags = CalculateChromeFlagsHelper( + nsIWebBrowserChrome::CHROME_WINDOW_BORDERS, aFeatures, presenceFlag); + + return EnsureFlagsSafeForContent(chromeFlags); +} + +/** + * Calculate the chrome bitmask from a string list of features for a new + * privileged window. + * @param aParent the opener window + * @param aFeatures a string containing a list of named chrome features + * @param aDialog affects the assumptions made about unnamed features + * @param aChromeURL true if the window is being sent to a chrome:// URL + * @param aHasChromeParent true if the parent window is privileged + * @param aCalledFromJS true if the window open request came from script. + * @return the chrome bitmask + */ +// static +uint32_t +nsWindowWatcher::CalculateChromeFlagsForParent(mozIDOMWindowProxy* aParent, + const nsACString& aFeatures, + bool aDialog, + bool aChromeURL, + bool aHasChromeParent, + bool aCalledFromJS) +{ + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(nsContentUtils::LegacyIsCallerChromeOrNativeCode()); + + uint32_t chromeFlags = 0; + + // The features string is made void by OpenWindowInternal + // if nullptr was originally passed as the features string. + if (aFeatures.IsVoid()) { + chromeFlags = nsIWebBrowserChrome::CHROME_ALL; + if (aDialog) { + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG | + nsIWebBrowserChrome::CHROME_OPENAS_CHROME; + } + } else { + chromeFlags = nsIWebBrowserChrome::CHROME_WINDOW_BORDERS; + } + + /* This function has become complicated since browser windows and + dialogs diverged. The difference is, browser windows assume all + chrome not explicitly mentioned is off, if the features string + is not null. Exceptions are some OS border chrome new with Mozilla. + Dialogs interpret a (mostly) empty features string to mean + "OS's choice," and also support an "all" flag explicitly disallowed + in the standards-compliant window.(normal)open. */ + + bool presenceFlag = false; + if (aDialog && WinHasOption(aFeatures, "all", 0, &presenceFlag)) { + chromeFlags = nsIWebBrowserChrome::CHROME_ALL; + } + + /* Next, allow explicitly named options to override the initial settings */ + chromeFlags = CalculateChromeFlagsHelper(chromeFlags, aFeatures, presenceFlag, + aDialog, aHasChromeParent, aChromeURL); + + // Determine whether the window is a private browsing window + chromeFlags |= WinHasOption(aFeatures, "private", 0, &presenceFlag) ? + nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW : 0; + chromeFlags |= WinHasOption(aFeatures, "non-private", 0, &presenceFlag) ? + nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW : 0; + + // Determine whether the window should have remote tabs. + bool remote = BrowserTabsRemoteAutostart(); + + if (remote) { + remote = !WinHasOption(aFeatures, "non-remote", 0, &presenceFlag); + } else { + remote = WinHasOption(aFeatures, "remote", 0, &presenceFlag); + } + + if (remote) { + chromeFlags |= nsIWebBrowserChrome::CHROME_REMOTE_WINDOW; + } + + chromeFlags |= WinHasOption(aFeatures, "popup", 0, &presenceFlag) ? + nsIWebBrowserChrome::CHROME_WINDOW_POPUP : 0; + + /* OK. + Normal browser windows, in spite of a stated pattern of turning off + all chrome not mentioned explicitly, will want the new OS chrome (window + borders, titlebars, closebox) on, unless explicitly turned off. + Dialogs, on the other hand, take the absence of any explicit settings + to mean "OS' choice." */ + + // default titlebar and closebox to "on," if not mentioned at all + if (!(chromeFlags & nsIWebBrowserChrome::CHROME_WINDOW_POPUP)) { + if (!PL_strcasestr(aFeatures.BeginReading(), "titlebar")) { + chromeFlags |= nsIWebBrowserChrome::CHROME_TITLEBAR; + } + if (!PL_strcasestr(aFeatures.BeginReading(), "close")) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_CLOSE; + } + } + + if (aDialog && !aFeatures.IsVoid() && !presenceFlag) { + chromeFlags = nsIWebBrowserChrome::CHROME_DEFAULT; + } + + /* Finally, once all the above normal chrome has been divined, deal + with the features that are more operating hints than appearance + instructions. (Note modality implies dependence.) */ + + if (WinHasOption(aFeatures, "alwaysLowered", 0, nullptr) || + WinHasOption(aFeatures, "z-lock", 0, nullptr)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_LOWERED; + } else if (WinHasOption(aFeatures, "alwaysRaised", 0, nullptr)) { + chromeFlags |= nsIWebBrowserChrome::CHROME_WINDOW_RAISED; + } + + chromeFlags |= WinHasOption(aFeatures, "macsuppressanimation", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_MAC_SUPPRESS_ANIMATION : 0; + + chromeFlags |= WinHasOption(aFeatures, "chrome", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_OPENAS_CHROME : 0; + chromeFlags |= WinHasOption(aFeatures, "extrachrome", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_EXTRA : 0; + chromeFlags |= WinHasOption(aFeatures, "centerscreen", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_CENTER_SCREEN : 0; + chromeFlags |= WinHasOption(aFeatures, "dependent", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_DEPENDENT : 0; + chromeFlags |= WinHasOption(aFeatures, "modal", 0, nullptr) ? + (nsIWebBrowserChrome::CHROME_MODAL | nsIWebBrowserChrome::CHROME_DEPENDENT) : 0; + + /* On mobile we want to ignore the dialog window feature, since the mobile UI + does not provide any affordance for dialog windows. This does not interfere + with dialog windows created through openDialog. */ + bool disableDialogFeature = false; + nsCOMPtr<nsIPrefBranch> branch = do_GetService(NS_PREFSERVICE_CONTRACTID); + + branch->GetBoolPref("dom.disable_window_open_dialog_feature", + &disableDialogFeature); + + if (!disableDialogFeature) { + chromeFlags |= WinHasOption(aFeatures, "dialog", 0, nullptr) ? + nsIWebBrowserChrome::CHROME_OPENAS_DIALOG : 0; + } + + /* and dialogs need to have the last word. assume dialogs are dialogs, + and opened as chrome, unless explicitly told otherwise. */ + if (aDialog) { + if (!PL_strcasestr(aFeatures.BeginReading(), "dialog")) { + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_DIALOG; + } + if (!PL_strcasestr(aFeatures.BeginReading(), "chrome")) { + chromeFlags |= nsIWebBrowserChrome::CHROME_OPENAS_CHROME; + } + } + + /* missing + chromeFlags->copy_history + */ + + // Check security state for use in determing window dimensions + if (!aHasChromeParent) { + chromeFlags = EnsureFlagsSafeForContent(chromeFlags, aChromeURL); + } + + // Disable CHROME_OPENAS_DIALOG if the window is inside <iframe mozbrowser>. + // It's up to the embedder to interpret what dialog=1 means. + nsCOMPtr<nsIDocShell> docshell = do_GetInterface(aParent); + if (docshell && docshell->GetIsInMozBrowserOrApp()) { + chromeFlags &= ~nsIWebBrowserChrome::CHROME_OPENAS_DIALOG; + } + + return chromeFlags; +} + +// static +int32_t +nsWindowWatcher::WinHasOption(const nsACString& aOptions, const char* aName, + int32_t aDefault, bool* aPresenceFlag) +{ + if (aOptions.IsEmpty()) { + return 0; + } + + const char* options = aOptions.BeginReading(); + char* comma; + char* equal; + int32_t found = 0; + +#ifdef DEBUG + NS_ASSERTION(nsAutoCString(aOptions).FindCharInSet(" \n\r\t") == kNotFound, + "There should be no whitespace in this string!"); +#endif + + while (true) { + comma = PL_strchr(options, ','); + if (comma) { + *comma = '\0'; + } + equal = PL_strchr(options, '='); + if (equal) { + *equal = '\0'; + } + if (nsCRT::strcasecmp(options, aName) == 0) { + if (aPresenceFlag) { + *aPresenceFlag = true; + } + if (equal) + if (*(equal + 1) == '*') { + found = aDefault; + } else if (nsCRT::strcasecmp(equal + 1, "yes") == 0) { + found = 1; + } else { + found = atoi(equal + 1); + } + else { + found = 1; + } + } + if (equal) { + *equal = '='; + } + if (comma) { + *comma = ','; + } + if (found || !comma) { + break; + } + options = comma + 1; + } + return found; +} + +/* try to find an nsIDocShellTreeItem with the given name in any + known open window. a failure to find the item will not + necessarily return a failure method value. check aFoundItem. +*/ +NS_IMETHODIMP +nsWindowWatcher::FindItemWithName(const nsAString& aName, + nsIDocShellTreeItem* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + nsIDocShellTreeItem** aFoundItem) +{ + *aFoundItem = nullptr; + if (aName.IsEmpty()) { + return NS_OK; + } + + if (aName.LowerCaseEqualsLiteral("_blank") || + aName.LowerCaseEqualsLiteral("_top") || + aName.LowerCaseEqualsLiteral("_parent") || + aName.LowerCaseEqualsLiteral("_self")) { + return NS_OK; + } + + // If we are looking for an item and we don't have a docshell we are checking + // on, let's just look in the chrome tab group! + return TabGroup::GetChromeTabGroup()->FindItemWithName(aName, + aRequestor, + aOriginalRequestor, + aFoundItem); +} + +already_AddRefed<nsIDocShellTreeItem> +nsWindowWatcher::GetCallerTreeItem(nsIDocShellTreeItem* aParentItem) +{ + nsCOMPtr<nsIWebNavigation> callerWebNav = do_GetInterface(GetEntryGlobal()); + nsCOMPtr<nsIDocShellTreeItem> callerItem = do_QueryInterface(callerWebNav); + if (!callerItem) { + callerItem = aParentItem; + } + + return callerItem.forget(); +} + +nsPIDOMWindowOuter* +nsWindowWatcher::SafeGetWindowByName(const nsAString& aName, + bool aForceNoOpener, + mozIDOMWindowProxy* aCurrentWindow) +{ + if (aForceNoOpener) { + if (!aName.LowerCaseEqualsLiteral("_self") && + !aName.LowerCaseEqualsLiteral("_top") && + !aName.LowerCaseEqualsLiteral("_parent")) { + // Ignore all other names in the noopener case. + return nullptr; + } + } + + nsCOMPtr<nsIDocShellTreeItem> startItem; + GetWindowTreeItem(aCurrentWindow, getter_AddRefs(startItem)); + + nsCOMPtr<nsIDocShellTreeItem> callerItem = GetCallerTreeItem(startItem); + + nsCOMPtr<nsIDocShellTreeItem> foundItem; + if (startItem) { + startItem->FindItemWithName(aName, nullptr, callerItem, + getter_AddRefs(foundItem)); + } else { + FindItemWithName(aName, nullptr, callerItem, + getter_AddRefs(foundItem)); + } + + return foundItem ? foundItem->GetWindow() : nullptr; +} + +/* Fetch the nsIDOMWindow corresponding to the given nsIDocShellTreeItem. + This forces the creation of a script context, if one has not already + been created. Note it also sets the window's opener to the parent, + if applicable -- because it's just convenient, that's all. null aParent + is acceptable. */ +nsresult +nsWindowWatcher::ReadyOpenedDocShellItem(nsIDocShellTreeItem* aOpenedItem, + nsPIDOMWindowOuter* aParent, + bool aWindowIsNew, + bool aForceNoOpener, + mozIDOMWindowProxy** aOpenedWindow) +{ + nsresult rv = NS_ERROR_FAILURE; + + NS_ENSURE_ARG(aOpenedWindow); + + *aOpenedWindow = 0; + nsCOMPtr<nsPIDOMWindowOuter> piOpenedWindow = aOpenedItem->GetWindow(); + if (piOpenedWindow) { + if (!aForceNoOpener) { + piOpenedWindow->SetOpenerWindow(aParent, aWindowIsNew); // damnit + } else if (aParent && aParent != piOpenedWindow) { + MOZ_ASSERT(piOpenedWindow->TabGroup() != aParent->TabGroup(), + "If we're forcing no opener, they should be in different tab groups"); + } + + if (aWindowIsNew) { +#ifdef DEBUG + // Assert that we're not loading things right now. If we are, when + // that load completes it will clobber whatever principals we set up + // on this new window! + nsCOMPtr<nsIDocumentLoader> docloader = do_QueryInterface(aOpenedItem); + NS_ASSERTION(docloader, "How can we not have a docloader here?"); + + nsCOMPtr<nsIChannel> chan; + docloader->GetDocumentChannel(getter_AddRefs(chan)); + NS_ASSERTION(!chan, "Why is there a document channel?"); +#endif + + nsCOMPtr<nsIDocument> doc = piOpenedWindow->GetExtantDoc(); + if (doc) { + doc->SetIsInitialDocument(true); + } + } + rv = CallQueryInterface(piOpenedWindow, aOpenedWindow); + } + return rv; +} + +// static +void +nsWindowWatcher::CalcSizeSpec(const nsACString& aFeatures, SizeSpec& aResult) +{ + // Parse position spec, if any, from aFeatures + bool present; + int32_t temp; + + present = false; + if ((temp = WinHasOption(aFeatures, "left", 0, &present)) || present) { + aResult.mLeft = temp; + } else if ((temp = WinHasOption(aFeatures, "screenX", 0, &present)) || + present) { + aResult.mLeft = temp; + } + aResult.mLeftSpecified = present; + + present = false; + if ((temp = WinHasOption(aFeatures, "top", 0, &present)) || present) { + aResult.mTop = temp; + } else if ((temp = WinHasOption(aFeatures, "screenY", 0, &present)) || + present) { + aResult.mTop = temp; + } + aResult.mTopSpecified = present; + + // Parse size spec, if any. Chrome size overrides content size. + if ((temp = WinHasOption(aFeatures, "outerWidth", INT32_MIN, nullptr))) { + if (temp == INT32_MIN) { + aResult.mUseDefaultWidth = true; + } else { + aResult.mOuterWidth = temp; + } + aResult.mOuterWidthSpecified = true; + } else if ((temp = WinHasOption(aFeatures, "width", INT32_MIN, nullptr)) || + (temp = WinHasOption(aFeatures, "innerWidth", INT32_MIN, + nullptr))) { + if (temp == INT32_MIN) { + aResult.mUseDefaultWidth = true; + } else { + aResult.mInnerWidth = temp; + } + aResult.mInnerWidthSpecified = true; + } + + if ((temp = WinHasOption(aFeatures, "outerHeight", INT32_MIN, nullptr))) { + if (temp == INT32_MIN) { + aResult.mUseDefaultHeight = true; + } else { + aResult.mOuterHeight = temp; + } + aResult.mOuterHeightSpecified = true; + } else if ((temp = WinHasOption(aFeatures, "height", INT32_MIN, + nullptr)) || + (temp = WinHasOption(aFeatures, "innerHeight", INT32_MIN, + nullptr))) { + if (temp == INT32_MIN) { + aResult.mUseDefaultHeight = true; + } else { + aResult.mInnerHeight = temp; + } + aResult.mInnerHeightSpecified = true; + } +} + +/* Size and position a new window according to aSizeSpec. This method + is assumed to be called after the window has already been given + a default position and size; thus its current position and size are + accurate defaults. The new window is made visible at method end. + @param aTreeOwner + The top-level nsIDocShellTreeOwner of the newly opened window. + @param aParent (optional) + The parent window from which to inherit zoom factors from if + aOpenerFullZoom is none. + @param aIsCallerChrome + True if the code requesting the new window is privileged. + @param aSizeSpec + The size that the new window should be. + @param aOpenerFullZoom + If not nothing, a zoom factor to scale the content to. +*/ +void +nsWindowWatcher::SizeOpenedWindow(nsIDocShellTreeOwner* aTreeOwner, + mozIDOMWindowProxy* aParent, + bool aIsCallerChrome, + const SizeSpec& aSizeSpec, + Maybe<float> aOpenerFullZoom) +{ + // We should only be sizing top-level windows if we're in the parent + // process. + MOZ_ASSERT(XRE_IsParentProcess()); + + // position and size of window + int32_t left = 0, top = 0, width = 100, height = 100; + // difference between chrome and content size + int32_t chromeWidth = 0, chromeHeight = 0; + // whether the window size spec refers to chrome or content + bool sizeChromeWidth = true, sizeChromeHeight = true; + + // get various interfaces for aDocShellItem, used throughout this method + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin(do_QueryInterface(aTreeOwner)); + if (!treeOwnerAsWin) { // we'll need this to actually size the docshell + return; + } + + double openerZoom = aOpenerFullZoom.valueOr(1.0); + if (aParent && aOpenerFullZoom.isNothing()) { + nsCOMPtr<nsPIDOMWindowOuter> piWindow = nsPIDOMWindowOuter::From(aParent); + if (nsIDocument* doc = piWindow->GetDoc()) { + if (nsIPresShell* shell = doc->GetShell()) { + if (nsPresContext* presContext = shell->GetPresContext()) { + openerZoom = presContext->GetFullZoom(); + } + } + } + } + + double scale = 1.0; + treeOwnerAsWin->GetUnscaledDevicePixelsPerCSSPixel(&scale); + + /* The current position and size will be unchanged if not specified + (and they fit entirely onscreen). Also, calculate the difference + between chrome and content sizes on aDocShellItem's window. + This latter point becomes important if chrome and content + specifications are mixed in aFeatures, and when bringing the window + back from too far off the right or bottom edges of the screen. */ + + treeOwnerAsWin->GetPositionAndSize(&left, &top, &width, &height); + left = NSToIntRound(left / scale); + top = NSToIntRound(top / scale); + width = NSToIntRound(width / scale); + height = NSToIntRound(height / scale); + { + int32_t contentWidth, contentHeight; + bool hasPrimaryContent = false; + aTreeOwner->GetHasPrimaryContent(&hasPrimaryContent); + if (hasPrimaryContent) { + aTreeOwner->GetPrimaryContentSize(&contentWidth, &contentHeight); + } else { + aTreeOwner->GetRootShellSize(&contentWidth, &contentHeight); + } + chromeWidth = width - contentWidth; + chromeHeight = height - contentHeight; + } + + // Set up left/top + if (aSizeSpec.mLeftSpecified) { + left = NSToIntRound(aSizeSpec.mLeft * openerZoom); + } + + if (aSizeSpec.mTopSpecified) { + top = NSToIntRound(aSizeSpec.mTop * openerZoom); + } + + // Set up width + if (aSizeSpec.mOuterWidthSpecified) { + if (!aSizeSpec.mUseDefaultWidth) { + width = NSToIntRound(aSizeSpec.mOuterWidth * openerZoom); + } // Else specified to default; just use our existing width + } else if (aSizeSpec.mInnerWidthSpecified) { + sizeChromeWidth = false; + if (aSizeSpec.mUseDefaultWidth) { + width = width - chromeWidth; + } else { + width = NSToIntRound(aSizeSpec.mInnerWidth * openerZoom); + } + } + + // Set up height + if (aSizeSpec.mOuterHeightSpecified) { + if (!aSizeSpec.mUseDefaultHeight) { + height = NSToIntRound(aSizeSpec.mOuterHeight * openerZoom); + } // Else specified to default; just use our existing height + } else if (aSizeSpec.mInnerHeightSpecified) { + sizeChromeHeight = false; + if (aSizeSpec.mUseDefaultHeight) { + height = height - chromeHeight; + } else { + height = NSToIntRound(aSizeSpec.mInnerHeight * openerZoom); + } + } + + bool positionSpecified = aSizeSpec.PositionSpecified(); + + // Check security state for use in determing window dimensions + bool enabled = false; + if (aIsCallerChrome) { + // Only enable special priveleges for chrome when chrome calls + // open() on a chrome window + nsCOMPtr<nsIDOMChromeWindow> chromeWin(do_QueryInterface(aParent)); + enabled = !aParent || chromeWin; + } + + if (!enabled) { + // Security check failed. Ensure all args meet minimum reqs. + + int32_t oldTop = top, oldLeft = left; + + // We'll also need the screen dimensions + nsCOMPtr<nsIScreen> screen; + nsCOMPtr<nsIScreenManager> screenMgr( + do_GetService("@mozilla.org/gfx/screenmanager;1")); + if (screenMgr) + screenMgr->ScreenForRect(left, top, width, height, + getter_AddRefs(screen)); + if (screen) { + int32_t screenLeft, screenTop, screenWidth, screenHeight; + int32_t winWidth = width + (sizeChromeWidth ? 0 : chromeWidth), + winHeight = height + (sizeChromeHeight ? 0 : chromeHeight); + + // Get screen dimensions (in device pixels) + screen->GetAvailRect(&screenLeft, &screenTop, &screenWidth, + &screenHeight); + // Convert them to CSS pixels + screenLeft = NSToIntRound(screenLeft / scale); + screenTop = NSToIntRound(screenTop / scale); + screenWidth = NSToIntRound(screenWidth / scale); + screenHeight = NSToIntRound(screenHeight / scale); + + if (aSizeSpec.SizeSpecified()) { + /* Unlike position, force size out-of-bounds check only if + size actually was specified. Otherwise, intrinsically sized + windows are broken. */ + if (height < 100) { + height = 100; + winHeight = height + (sizeChromeHeight ? 0 : chromeHeight); + } + if (winHeight > screenHeight) { + height = screenHeight - (sizeChromeHeight ? 0 : chromeHeight); + } + if (width < 100) { + width = 100; + winWidth = width + (sizeChromeWidth ? 0 : chromeWidth); + } + if (winWidth > screenWidth) { + width = screenWidth - (sizeChromeWidth ? 0 : chromeWidth); + } + } + + if (left + winWidth > screenLeft + screenWidth || + left + winWidth < left) { + left = screenLeft + screenWidth - winWidth; + } + if (left < screenLeft) { + left = screenLeft; + } + if (top + winHeight > screenTop + screenHeight || top + winHeight < top) { + top = screenTop + screenHeight - winHeight; + } + if (top < screenTop) { + top = screenTop; + } + if (top != oldTop || left != oldLeft) { + positionSpecified = true; + } + } + } + + // size and position the window + + if (positionSpecified) { + // Get the scale factor appropriate for the screen we're actually + // positioning on. + nsCOMPtr<nsIScreen> screen; + nsCOMPtr<nsIScreenManager> screenMgr( + do_GetService("@mozilla.org/gfx/screenmanager;1")); + if (screenMgr) { + screenMgr->ScreenForRect(left, top, 1, 1, getter_AddRefs(screen)); + } + if (screen) { + double cssToDevPixScale, desktopToDevPixScale; + screen->GetDefaultCSSScaleFactor(&cssToDevPixScale); + screen->GetContentsScaleFactor(&desktopToDevPixScale); + double cssToDesktopScale = cssToDevPixScale / desktopToDevPixScale; + int32_t screenLeft, screenTop, screenWd, screenHt; + screen->GetRectDisplayPix(&screenLeft, &screenTop, &screenWd, &screenHt); + // Adjust by desktop-pixel origin of the target screen when scaling + // to convert from per-screen CSS-px coords to global desktop coords. + treeOwnerAsWin->SetPositionDesktopPix( + (left - screenLeft) * cssToDesktopScale + screenLeft, + (top - screenTop) * cssToDesktopScale + screenTop); + } else { + // Couldn't find screen? This shouldn't happen. + treeOwnerAsWin->SetPosition(left * scale, top * scale); + } + // This shouldn't be necessary, given the screen check above, but in case + // moving the window didn't put it where we expected (e.g. due to issues + // at the widget level, or whatever), let's re-fetch the scale factor for + // wherever it really ended up + treeOwnerAsWin->GetUnscaledDevicePixelsPerCSSPixel(&scale); + } + if (aSizeSpec.SizeSpecified()) { + /* Prefer to trust the interfaces, which think in terms of pure + chrome or content sizes. If we have a mix, use the chrome size + adjusted by the chrome/content differences calculated earlier. */ + if (!sizeChromeWidth && !sizeChromeHeight) { + bool hasPrimaryContent = false; + aTreeOwner->GetHasPrimaryContent(&hasPrimaryContent); + if (hasPrimaryContent) { + aTreeOwner->SetPrimaryContentSize(width * scale, height * scale); + } else { + aTreeOwner->SetRootShellSize(width * scale, height * scale); + } + } else { + if (!sizeChromeWidth) { + width += chromeWidth; + } + if (!sizeChromeHeight) { + height += chromeHeight; + } + treeOwnerAsWin->SetSize(width * scale, height * scale, false); + } + } + treeOwnerAsWin->SetVisibility(true); +} + +void +nsWindowWatcher::GetWindowTreeItem(mozIDOMWindowProxy* aWindow, + nsIDocShellTreeItem** aResult) +{ + *aResult = 0; + + if (aWindow) { + nsIDocShell* docshell = nsPIDOMWindowOuter::From(aWindow)->GetDocShell(); + if (docshell) { + CallQueryInterface(docshell, aResult); + } + } +} + +void +nsWindowWatcher::GetWindowTreeOwner(nsPIDOMWindowOuter* aWindow, + nsIDocShellTreeOwner** aResult) +{ + *aResult = 0; + + nsCOMPtr<nsIDocShellTreeItem> treeItem; + GetWindowTreeItem(aWindow, getter_AddRefs(treeItem)); + if (treeItem) { + treeItem->GetTreeOwner(aResult); + } +} + +/* static */ +int32_t +nsWindowWatcher::GetWindowOpenLocation(nsPIDOMWindowOuter* aParent, + uint32_t aChromeFlags, + bool aCalledFromJS, + bool aPositionSpecified, + bool aSizeSpecified) +{ + bool isFullScreen = aParent->GetFullScreen(); + + // Where should we open this? + int32_t containerPref; + if (NS_FAILED(Preferences::GetInt("browser.link.open_newwindow", + &containerPref))) { + // We couldn't read the user preference, so fall back on the default. + return nsIBrowserDOMWindow::OPEN_NEWTAB; + } + + bool isDisabledOpenNewWindow = + isFullScreen && + Preferences::GetBool("browser.link.open_newwindow.disabled_in_fullscreen"); + + if (isDisabledOpenNewWindow && + (containerPref == nsIBrowserDOMWindow::OPEN_NEWWINDOW)) { + containerPref = nsIBrowserDOMWindow::OPEN_NEWTAB; + } + + if (containerPref != nsIBrowserDOMWindow::OPEN_NEWTAB && + containerPref != nsIBrowserDOMWindow::OPEN_CURRENTWINDOW) { + // Just open a window normally + return nsIBrowserDOMWindow::OPEN_NEWWINDOW; + } + + if (aCalledFromJS) { + /* Now check our restriction pref. The restriction pref is a power-user's + fine-tuning pref. values: + 0: no restrictions - divert everything + 1: don't divert window.open at all + 2: don't divert window.open with features + */ + int32_t restrictionPref = + Preferences::GetInt("browser.link.open_newwindow.restriction", 2); + if (restrictionPref < 0 || restrictionPref > 2) { + restrictionPref = 2; // Sane default behavior + } + + if (isDisabledOpenNewWindow) { + // In browser fullscreen, the window should be opened + // in the current window with no features (see bug 803675) + restrictionPref = 0; + } + + if (restrictionPref == 1) { + return nsIBrowserDOMWindow::OPEN_NEWWINDOW; + } + + if (restrictionPref == 2) { + // Only continue if there are no size/position features and no special + // chrome flags - with the exception of the remoteness and private flags, + // which might have been automatically flipped by Gecko. + int32_t uiChromeFlags = aChromeFlags; + uiChromeFlags &= ~(nsIWebBrowserChrome::CHROME_REMOTE_WINDOW | + nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW | + nsIWebBrowserChrome::CHROME_NON_PRIVATE_WINDOW | + nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME); + if (uiChromeFlags != nsIWebBrowserChrome::CHROME_ALL || + aPositionSpecified || aSizeSpecified) { + return nsIBrowserDOMWindow::OPEN_NEWWINDOW; + } + } + } + + return containerPref; +} diff --git a/embedding/components/windowwatcher/nsWindowWatcher.h b/embedding/components/windowwatcher/nsWindowWatcher.h new file mode 100644 index 000000000..f2728bc16 --- /dev/null +++ b/embedding/components/windowwatcher/nsWindowWatcher.h @@ -0,0 +1,156 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __nsWindowWatcher_h__ +#define __nsWindowWatcher_h__ + +// {a21bfa01-f349-4394-a84c-8de5cf0737d0} +#define NS_WINDOWWATCHER_CID \ + {0xa21bfa01, 0xf349, 0x4394, {0xa8, 0x4c, 0x8d, 0xe5, 0xcf, 0x7, 0x37, 0xd0}} + +#include "nsCOMPtr.h" +#include "mozilla/Mutex.h" +#include "mozilla/Maybe.h" +#include "nsIWindowCreator.h" // for stupid compilers +#include "nsIWindowWatcher.h" +#include "nsIPromptFactory.h" +#include "nsITabParent.h" +#include "nsPIWindowWatcher.h" +#include "nsTArray.h" + +class nsIURI; +class nsIDocShellTreeItem; +class nsIDocShellTreeOwner; +class nsPIDOMWindowOuter; +class nsWatcherWindowEnumerator; +class nsPromptService; +struct nsWatcherWindowEntry; +struct SizeSpec; + +class nsWindowWatcher + : public nsIWindowWatcher + , public nsPIWindowWatcher + , public nsIPromptFactory +{ + friend class nsWatcherWindowEnumerator; + +public: + nsWindowWatcher(); + + nsresult Init(); + + NS_DECL_ISUPPORTS + + NS_DECL_NSIWINDOWWATCHER + NS_DECL_NSPIWINDOWWATCHER + NS_DECL_NSIPROMPTFACTORY + + static int32_t GetWindowOpenLocation(nsPIDOMWindowOuter* aParent, + uint32_t aChromeFlags, + bool aCalledFromJS, + bool aPositionSpecified, + bool aSizeSpecified); + +protected: + virtual ~nsWindowWatcher(); + + friend class nsPromptService; + bool AddEnumerator(nsWatcherWindowEnumerator* aEnumerator); + bool RemoveEnumerator(nsWatcherWindowEnumerator* aEnumerator); + + nsWatcherWindowEntry* FindWindowEntry(mozIDOMWindowProxy* aWindow); + nsresult RemoveWindow(nsWatcherWindowEntry* aInfo); + + // Get the caller tree item. Look on the JS stack, then fall back + // to the parent if there's nothing there. + already_AddRefed<nsIDocShellTreeItem> GetCallerTreeItem( + nsIDocShellTreeItem* aParentItem); + + // Unlike GetWindowByName this will look for a caller on the JS + // stack, and then fall back on aCurrentWindow if it can't find one. + // It also knows to not look for things if aForceNoOpener is set. + nsPIDOMWindowOuter* SafeGetWindowByName(const nsAString& aName, + bool aForceNoOpener, + mozIDOMWindowProxy* aCurrentWindow); + + // Just like OpenWindowJS, but knows whether it got called via OpenWindowJS + // (which means called from script) or called via OpenWindow. + nsresult OpenWindowInternal(mozIDOMWindowProxy* aParent, + const char* aUrl, + const char* aName, + const char* aFeatures, + bool aCalledFromJS, + bool aDialog, + bool aNavigate, + nsIArray* aArgv, + bool aIsPopupSpam, + bool aForceNoOpener, + nsIDocShellLoadInfo* aLoadInfo, + mozIDOMWindowProxy** aResult); + + static nsresult URIfromURL(const char* aURL, + mozIDOMWindowProxy* aParent, + nsIURI** aURI); + + static uint32_t CalculateChromeFlagsForChild(const nsACString& aFeaturesStr); + + static uint32_t CalculateChromeFlagsForParent(mozIDOMWindowProxy* aParent, + const nsACString& aFeaturesStr, + bool aDialog, + bool aChromeURL, + bool aHasChromeParent, + bool aCalledFromJS); + + static int32_t WinHasOption(const nsACString& aOptions, const char* aName, + int32_t aDefault, bool* aPresenceFlag); + /* Compute the right SizeSpec based on aFeatures */ + static void CalcSizeSpec(const nsACString& aFeatures, SizeSpec& aResult); + static nsresult ReadyOpenedDocShellItem(nsIDocShellTreeItem* aOpenedItem, + nsPIDOMWindowOuter* aParent, + bool aWindowIsNew, + bool aForceNoOpener, + mozIDOMWindowProxy** aOpenedWindow); + static void SizeOpenedWindow(nsIDocShellTreeOwner* aTreeOwner, + mozIDOMWindowProxy* aParent, + bool aIsCallerChrome, + const SizeSpec& aSizeSpec, + mozilla::Maybe<float> aOpenerFullZoom = + mozilla::Nothing()); + static void GetWindowTreeItem(mozIDOMWindowProxy* aWindow, + nsIDocShellTreeItem** aResult); + static void GetWindowTreeOwner(nsPIDOMWindowOuter* aWindow, + nsIDocShellTreeOwner** aResult); + +private: + nsresult CreateChromeWindow(const nsACString& aFeatures, + nsIWebBrowserChrome* aParentChrome, + uint32_t aChromeFlags, + uint32_t aContextFlags, + nsITabParent* aOpeningTabParent, + mozIDOMWindowProxy* aOpener, + nsIWebBrowserChrome** aResult); + + void MaybeDisablePersistence(const nsACString& aFeatures, + nsIDocShellTreeOwner* aTreeOwner); + + static uint32_t CalculateChromeFlagsHelper(uint32_t aInitialFlags, + const nsACString& aFeatures, + bool &presenceFlag, + bool aDialog = false, + bool aHasChromeParent = false, + bool aChromeURL = false); + static uint32_t EnsureFlagsSafeForContent(uint32_t aChromeFlags, + bool aChromeURL = false); + +protected: + nsTArray<nsWatcherWindowEnumerator*> mEnumeratorList; + nsWatcherWindowEntry* mOldestWindow; + mozilla::Mutex mListLock; + + nsCOMPtr<nsIWindowCreator> mWindowCreator; +}; + +#endif diff --git a/embedding/components/windowwatcher/test/browser.ini b/embedding/components/windowwatcher/test/browser.ini new file mode 100644 index 000000000..925b96e34 --- /dev/null +++ b/embedding/components/windowwatcher/test/browser.ini @@ -0,0 +1,9 @@ +[DEFAULT] +tags = openwindow + +[browser_new_content_window_chromeflags.js] +[browser_new_remote_window_flags.js] +run-if = e10s +[browser_new_content_window_from_chrome_principal.js] +[browser_new_sized_window.js] +skip-if = os == 'win' # Bug 1276802 - Opening windows from content on Windows might not get the size right diff --git a/embedding/components/windowwatcher/test/browser_new_content_window_chromeflags.js b/embedding/components/windowwatcher/test/browser_new_content_window_chromeflags.js new file mode 100644 index 000000000..887f86152 --- /dev/null +++ b/embedding/components/windowwatcher/test/browser_new_content_window_chromeflags.js @@ -0,0 +1,278 @@ +/** + * Tests that chromeFlags are set properly on windows that are + * being opened from content. + */ + +// The following features set chrome flags on new windows and are +// supported by web content. The schema for each property on this +// object is as follows: +// +// <feature string>: { +// flag: <associated nsIWebBrowserChrome flag>, +// defaults_to: <what this feature defaults to normally> +// } +const ALLOWED = { + "toolbar": { + flag: Ci.nsIWebBrowserChrome.CHROME_TOOLBAR, + defaults_to: true, + }, + "personalbar": { + flag: Ci.nsIWebBrowserChrome.CHROME_PERSONAL_TOOLBAR, + defaults_to: true, + }, + "menubar": { + flag: Ci.nsIWebBrowserChrome.CHROME_MENUBAR, + defaults_to: true, + }, + "scrollbars": { + flag: Ci.nsIWebBrowserChrome.CHROME_SCROLLBARS, + defaults_to: false, + }, + "minimizable": { + flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_MIN, + defaults_to: true, + }, +}; + +// Construct a features string that flips all ALLOWED features +// to not be their defaults. +const ALLOWED_STRING = Object.keys(ALLOWED).map(feature => { + let toValue = ALLOWED[feature].defaults_to ? "no" : "yes"; + return `${feature}=${toValue}`; +}).join(","); + +// The following are not allowed from web content, at least +// in the default case (since some are disabled by default +// via the dom.disable_window_open_feature pref branch). +const DISALLOWED = { + "location": { + flag: Ci.nsIWebBrowserChrome.CHROME_LOCATIONBAR, + defaults_to: true, + }, + "chrome": { + flag: Ci.nsIWebBrowserChrome.CHROME_OPENAS_CHROME, + defaults_to: false, + }, + "dialog": { + flag: Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG, + defaults_to: false, + }, + "private": { + flag: Ci.nsIWebBrowserChrome.CHROME_PRIVATE_WINDOW, + defaults_to: false, + }, + "non-private": { + flag: Ci.nsIWebBrowserChrome.CHROME_NON_PRIVATE_WINDOW, + defaults_to: false, + }, + // "all": + // checked manually, since this is an aggregate + // flag. + // + // "remote": + // checked manually, since its default value will + // depend on whether or not e10s is enabled by default. + "popup": { + flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_POPUP, + defaults_to: false, + }, + "alwaysLowered": { + flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_LOWERED, + defaults_to: false, + }, + "z-lock": { + flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_LOWERED, // Renamed to alwaysLowered + defaults_to: false, + }, + "alwaysRaised": { + flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_RAISED, + defaults_to: false, + }, + "macsuppressanimation": { + flag: Ci.nsIWebBrowserChrome.CHROME_MAC_SUPPRESS_ANIMATION, + defaults_to: false, + }, + "extrachrome": { + flag: Ci.nsIWebBrowserChrome.CHROME_EXTRA, + defaults_to: false, + }, + "centerscreen": { + flag: Ci.nsIWebBrowserChrome.CHROME_CENTER_SCREEN, + defaults_to: false, + }, + "dependent": { + flag: Ci.nsIWebBrowserChrome.CHROME_DEPENDENT, + defaults_to: false, + }, + "modal": { + flag: Ci.nsIWebBrowserChrome.CHROME_MODAL, + defaults_to: false, + }, + "titlebar": { + flag: Ci.nsIWebBrowserChrome.CHROME_TITLEBAR, + defaults_to: true, + }, + "close": { + flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_CLOSE, + defaults_to: true, + }, + "resizable": { + flag: Ci.nsIWebBrowserChrome.CHROME_WINDOW_RESIZE, + defaults_to: true, + }, + "status": { + flag: Ci.nsIWebBrowserChrome.CHROME_STATUSBAR, + defaults_to: true, + }, +}; + +// Construct a features string that flips all DISALLOWED features +// to not be their defaults. +const DISALLOWED_STRING = Object.keys(DISALLOWED).map(feature => { + let toValue = DISALLOWED[feature].defaults_to ? "no" : "yes"; + return `${feature}=${toValue}`; +}).join(","); + +const FEATURES = [ALLOWED_STRING, DISALLOWED_STRING].join(","); + +const SCRIPT_PAGE = `data:text/html,<script>window.open("about:blank", "_blank", "${FEATURES}");</script>`; +const SCRIPT_PAGE_FOR_CHROME_ALL = `data:text/html,<script>window.open("about:blank", "_blank", "all");</script>`; + +// This magic value of 2 means that by default, when content tries +// to open a new window, it'll actually open in a new window instead +// of a new tab. +Services.prefs.setIntPref("browser.link.open_newwindow", 2); +registerCleanupFunction(() => { + Services.prefs.clearUserPref("browser.link.open_newwindow"); +}); + +/** + * Given some nsIDOMWindow for a window running in the parent + * process, return the nsIWebBrowserChrome chrome flags for + * the associated XUL window. + * + * @param win (nsIDOMWindow) + * Some window in the parent process. + * @returns int + */ +function getParentChromeFlags(win) { + return win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .treeOwner + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIXULWindow) + .chromeFlags; +} + +/** + * For some chromeFlags, ensures that flags that are in the + * ALLOWED group were modified, and that flags in the DISALLOWED + * group were not modified. + * + * @param chromeFlags (int) + * Some chromeFlags to check. + */ +function assertContentFlags(chromeFlags) { + for (let feature in ALLOWED) { + let flag = ALLOWED[feature].flag; + + if (ALLOWED[feature].defaults_to) { + // The feature is supposed to default to true, so we should + // have been able to flip it off. + Assert.ok(!(chromeFlags & flag), + `Expected feature ${feature} to be disabled`); + } else { + // The feature is supposed to default to false, so we should + // have been able to flip it on. + Assert.ok((chromeFlags & flag), + `Expected feature ${feature} to be enabled`); + } + } + + for (let feature in DISALLOWED) { + let flag = DISALLOWED[feature].flag; + if (DISALLOWED[feature].defaults_to) { + // The feature is supposed to default to true, so it should + // stay true. + Assert.ok((chromeFlags & flag), + `Expected feature ${feature} to be unchanged`); + } else { + // The feature is supposed to default to false, so it should + // stay false. + Assert.ok(!(chromeFlags & flag), + `Expected feature ${feature} to be unchanged`); + } + } +} + +/** + * Opens a window from content using window.open with the + * features computed from ALLOWED and DISALLOWED. The computed + * feature string attempts to flip every feature away from their + * default. + */ +add_task(function* test_new_remote_window_flags() { + let newWinPromise = BrowserTestUtils.waitForNewWindow(); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: SCRIPT_PAGE, + }, function*(browser) { + let win = yield newWinPromise; + let parentChromeFlags = getParentChromeFlags(win); + assertContentFlags(parentChromeFlags); + + if (win.gMultiProcessBrowser) { + Assert.ok(parentChromeFlags & + Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW, + "Should be remote by default"); + } else { + Assert.ok(!(parentChromeFlags & + Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW), + "Should not be remote by default"); + } + + // Confusingly, chromeFlags also exist in the content process + // as part of the TabChild, so we have to check those too. + let b = win.gBrowser.selectedBrowser; + let contentChromeFlags = yield ContentTask.spawn(b, null, function*() { + docShell.QueryInterface(Ci.nsIInterfaceRequestor); + try { + // This will throw if we're not a remote browser. + return docShell.getInterface(Ci.nsITabChild) + .QueryInterface(Ci.nsIWebBrowserChrome) + .chromeFlags; + } catch(e) { + // This must be a non-remote browser... + return docShell.QueryInterface(Ci.nsIDocShellTreeItem) + .treeOwner + .QueryInterface(Ci.nsIWebBrowserChrome) + .chromeFlags; + } + }); + + assertContentFlags(contentChromeFlags); + Assert.ok(!(contentChromeFlags & + Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW), + "Should not be remote in the content process."); + + yield BrowserTestUtils.closeWindow(win); + }); + + // We check "all" manually, since that's an aggregate flag + // and doesn't fit nicely into the ALLOWED / DISALLOWED scheme + newWinPromise = BrowserTestUtils.waitForNewWindow(); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: SCRIPT_PAGE_FOR_CHROME_ALL, + }, function*(browser) { + let win = yield newWinPromise; + let parentChromeFlags = getParentChromeFlags(win); + Assert.notEqual((parentChromeFlags & Ci.nsIWebBrowserChrome.CHROME_ALL), + Ci.nsIWebBrowserChrome.CHROME_ALL, + "Should not have been able to set CHROME_ALL"); + yield BrowserTestUtils.closeWindow(win); + }); +}); diff --git a/embedding/components/windowwatcher/test/browser_new_content_window_from_chrome_principal.js b/embedding/components/windowwatcher/test/browser_new_content_window_from_chrome_principal.js new file mode 100644 index 000000000..c8a24d43e --- /dev/null +++ b/embedding/components/windowwatcher/test/browser_new_content_window_from_chrome_principal.js @@ -0,0 +1,34 @@ +"use strict"; + +/** + * Tests that if chrome-privileged code calls .open() on an + * unprivileged window, that the principal in the newly + * opened window is appropriately set. + */ +add_task(function* test_chrome_opens_window() { + // This magic value of 2 means that by default, when content tries + // to open a new window, it'll actually open in a new window instead + // of a new tab. + yield SpecialPowers.pushPrefEnv({"set": [ + ["browser.link.open_newwindow", 2], + ]}); + + let newWinPromise = BrowserTestUtils.waitForNewWindow(true, "http://example.com/"); + + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + content.open("http://example.com/", "_blank"); + }); + + let win = yield newWinPromise; + let browser = win.gBrowser.selectedBrowser; + + yield ContentTask.spawn(browser, null, function*() { + Assert.ok(!content.document.nodePrincipal.isSystemPrincipal, + "We should not have a system principal.") + Assert.equal(content.document.nodePrincipal.origin, + "http://example.com", + "Should have the example.com principal"); + }); + + yield BrowserTestUtils.closeWindow(win); +});
\ No newline at end of file diff --git a/embedding/components/windowwatcher/test/browser_new_remote_window_flags.js b/embedding/components/windowwatcher/test/browser_new_remote_window_flags.js new file mode 100644 index 000000000..e4f3d1ddf --- /dev/null +++ b/embedding/components/windowwatcher/test/browser_new_remote_window_flags.js @@ -0,0 +1,78 @@ +/** + * Tests that when a remote browser opens a new window that the + * newly opened window is also remote. + */ + +const ANCHOR_PAGE = `data:text/html,<a href="about:blank" target="_blank">Click me!</a>`; +const SCRIPT_PAGE = `data:text/html,<script>window.open("about:blank", "_blank");</script>`; + +// This magic value of 2 means that by default, when content tries +// to open a new window, it'll actually open in a new window instead +// of a new tab. +add_task(function* setup() { + yield SpecialPowers.pushPrefEnv({"set": [ + ["browser.link.open_newwindow", 2], + ]}); +}); + +function assertFlags(win) { + let webNav = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation); + let loadContext = webNav.QueryInterface(Ci.nsILoadContext); + let chromeFlags = webNav.QueryInterface(Ci.nsIDocShellTreeItem) + .treeOwner + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIXULWindow) + .chromeFlags; + Assert.ok(loadContext.useRemoteTabs, + "Should be using remote tabs on the load context"); + Assert.ok(chromeFlags & Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW, + "Should have the remoteness chrome flag on the window"); +} + +/** + * Content can open a window using a target="_blank" link + */ +add_task(function* test_new_remote_window_flags_target_blank() { + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: ANCHOR_PAGE, + }, function*(browser) { + let newWinPromise = BrowserTestUtils.waitForNewWindow(); + yield BrowserTestUtils.synthesizeMouseAtCenter("a", {}, browser); + let win = yield newWinPromise; + assertFlags(win); + yield BrowserTestUtils.closeWindow(win); + }); +}); + +/** + * Content can open a window using window.open + */ +add_task(function* test_new_remote_window_flags_window_open() { + let newWinPromise = BrowserTestUtils.waitForNewWindow(); + + yield BrowserTestUtils.withNewTab({ + gBrowser, + url: SCRIPT_PAGE, + }, function*(browser) { + let win = yield newWinPromise; + assertFlags(win); + yield BrowserTestUtils.closeWindow(win); + }); +}); + +/** + * Privileged content scripts can also open new windows + * using content.open. + */ +add_task(function* test_new_remote_window_flags_content_open() { + let newWinPromise = BrowserTestUtils.waitForNewWindow(); + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + content.open("about:blank", "_blank"); + }); + + let win = yield newWinPromise; + assertFlags(win); + yield BrowserTestUtils.closeWindow(win); +}); diff --git a/embedding/components/windowwatcher/test/browser_new_sized_window.js b/embedding/components/windowwatcher/test/browser_new_sized_window.js new file mode 100644 index 000000000..f4b1890b1 --- /dev/null +++ b/embedding/components/windowwatcher/test/browser_new_sized_window.js @@ -0,0 +1,67 @@ +"use strict"; + +/** + * Tests that content can open windows at requested dimensions + * of height and width. + */ + +/** + * This utility function does most of the actual testing. We + * construct a feature string suitable for the passed in width + * and height, and then run that script in content to open the + * new window. When the new window comes up, this function tests + * to ensure that the content area of the initial browser is the + * requested dimensions. Finally, we also ensure that we're not + * persisting the position, size or sizemode of the new browser + * window. + */ +function test_dimensions({ width, height}) { + let features = []; + if (width) { + features.push(`width=${width}`); + } + if (height) { + features.push(`height=${height}`); + } + const FEATURE_STR = features.join(","); + const SCRIPT_PAGE = `data:text/html,<script>window.open("about:blank", "_blank", "${FEATURE_STR}");</script>`; + + let newWinPromise = BrowserTestUtils.waitForNewWindow(); + + return BrowserTestUtils.withNewTab({ + gBrowser, + url: SCRIPT_PAGE, + }, function*(browser) { + let win = yield newWinPromise; + let rect = win.gBrowser.selectedBrowser.getBoundingClientRect(); + + if (width) { + Assert.equal(rect.width, width, "Should have the requested width"); + } + + if (height) { + Assert.equal(rect.height, height, "Should have the requested height"); + } + + let treeOwner = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDocShell) + .QueryInterface(Ci.nsIDocShellTreeItem) + .treeOwner; + let persistPosition = {}; + let persistSize = {}; + let persistSizeMode = {}; + treeOwner.getPersistence(persistPosition, persistSize, persistSizeMode); + + Assert.ok(!persistPosition.value, "Should not persist position"); + Assert.ok(!persistSize.value, "Should not persist size"); + Assert.ok(!persistSizeMode.value, "Should not persist size mode"); + + yield BrowserTestUtils.closeWindow(win); + }); +} + +add_task(function* test_new_sized_window() { + yield test_dimensions({ width: 100 }); + yield test_dimensions({ height: 150 }); + yield test_dimensions({ width: 300, height: 200 }); +}); diff --git a/embedding/components/windowwatcher/test/chrome.ini b/embedding/components/windowwatcher/test/chrome.ini new file mode 100644 index 000000000..5f3b49433 --- /dev/null +++ b/embedding/components/windowwatcher/test/chrome.ini @@ -0,0 +1,7 @@ +[DEFAULT] +tags = openwindow + +[test_dialog_arguments.html] +support-files = + file_test_dialog.html +[test_modal_windows.html] diff --git a/embedding/components/windowwatcher/test/file_storage_copied.html b/embedding/components/windowwatcher/test/file_storage_copied.html new file mode 100644 index 000000000..250c7891a --- /dev/null +++ b/embedding/components/windowwatcher/test/file_storage_copied.html @@ -0,0 +1,13 @@ +<!DOCTYPE HTML> +<html> +<!-- +This page is opened in a new window by test_storage_copied.html. +We need to return the sessionStorage value for the item "test-item", +by way of postMessage. +--> +<head> +<body>Opened!</body> +<script> + window.postMessage(window.sessionStorage.getItem("test-item"), "*"); +</script> +</html>
\ No newline at end of file diff --git a/embedding/components/windowwatcher/test/file_test_dialog.html b/embedding/components/windowwatcher/test/file_test_dialog.html new file mode 100644 index 000000000..b323ba526 --- /dev/null +++ b/embedding/components/windowwatcher/test/file_test_dialog.html @@ -0,0 +1,14 @@ +<!DOCTYPE HTML> +<html> +<!-- +This page is opened in a new window by test_dialog_arguments. It is +a dialog which expects a Symbol to be passed in the dialog arguments. +Once we load, we call back into the opener with the argument we were +passed. +--> +<head> +<body>Opened!</body> +<script> + window.opener.done(window.arguments[0]); +</script> +</html>
\ No newline at end of file diff --git a/embedding/components/windowwatcher/test/mochitest.ini b/embedding/components/windowwatcher/test/mochitest.ini new file mode 100644 index 000000000..42955d496 --- /dev/null +++ b/embedding/components/windowwatcher/test/mochitest.ini @@ -0,0 +1,12 @@ +[DEFAULT] +tags = openwindow + +[test_blank_named_window.html] +skip-if = (os == 'android') # Fennec doesn't support web content opening new windows (See bug 1277544 for details) +[test_named_window.html] +skip-if = (os == 'android') # Fennec doesn't support web content opening new windows (See bug 1277544 for details) +[test_storage_copied.html] +support-files = + file_storage_copied.html +skip-if = (os == 'android') # Fennec doesn't support web content opening new windows (See bug 1277544 for details) + diff --git a/embedding/components/windowwatcher/test/moz.build b/embedding/components/windowwatcher/test/moz.build new file mode 100644 index 000000000..bd6f51aff --- /dev/null +++ b/embedding/components/windowwatcher/test/moz.build @@ -0,0 +1,18 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +BROWSER_CHROME_MANIFESTS += [ + 'browser.ini', +] + +MOCHITEST_MANIFESTS += [ + 'mochitest.ini', +] + +MOCHITEST_CHROME_MANIFESTS += [ + 'chrome.ini', +] + diff --git a/embedding/components/windowwatcher/test/test_blank_named_window.html b/embedding/components/windowwatcher/test/test_blank_named_window.html new file mode 100644 index 000000000..b45ad1f70 --- /dev/null +++ b/embedding/components/windowwatcher/test/test_blank_named_window.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test that when opening a window with the reserved name _blank that the new +window does not get that name, and that subsequent window openings with that +name result in new windows being opened. +--> +<head> + <meta charset="utf-8"> + <title>Test named windows</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script src="head.js" type="application/javascript;version=1.8"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <script type="application/javascript"> + "use strict"; + add_task(function*() { + // This magic value of 2 means that by default, when content tries + // to open a new window, it'll actually open in a new window instead + // of a new tab. + yield SpecialPowers.pushPrefEnv({"set": [ + ["browser.link.open_newwindow", 2], + ]}); + + let win1 = window.open("data:text/html,<p>This is window 1 for test_blank_named_window.html</p>", "_blank"); + + let name = SpecialPowers.wrap(win1) + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIWebNavigation) + .QueryInterface(SpecialPowers.Ci.nsIDocShellTreeItem) + .name; + + is(name, "", "Should have no name"); + + let win2 = window.open("data:text/html,<p>This is window 2 for test_blank_named_window.html</p>", "_blank"); + isnot(win1, win2, "Should not have gotten back the same window"); + + win1.close(); + win2.close(); + }); + </script> +</body> +</html>
\ No newline at end of file diff --git a/embedding/components/windowwatcher/test/test_dialog_arguments.html b/embedding/components/windowwatcher/test/test_dialog_arguments.html new file mode 100644 index 000000000..7cde69401 --- /dev/null +++ b/embedding/components/windowwatcher/test/test_dialog_arguments.html @@ -0,0 +1,38 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test that arguments can be passed to dialogs. +--> +<head> + <meta charset="utf-8"> + <title>Test a modal window</title> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + + <script type="application/javascript;version=1.8"> + const {utils: Cu, interfaces: Ci} = Components; + + Cu.import("resource://gre/modules/Services.jsm"); + + const TEST_ITEM = Symbol("test-item"); + + function done(returnedItem) { + is(returnedItem, TEST_ITEM, + "Dialog should have received test item"); + win.close(); + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + let win = window.openDialog("file_test_dialog.html", "_blank", "width=100,height=100", TEST_ITEM); + </script> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> +</html>
\ No newline at end of file diff --git a/embedding/components/windowwatcher/test/test_modal_windows.html b/embedding/components/windowwatcher/test/test_modal_windows.html new file mode 100644 index 000000000..8a2c03be0 --- /dev/null +++ b/embedding/components/windowwatcher/test/test_modal_windows.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test that the parent can open modal windows, and that the modal window +that is opened reports itself as being modal. +--> +<head> + <meta charset="utf-8"> + <title>Test a modal window</title> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + + <script type="application/javascript;version=1.8"> + const {utils: Cu, interfaces: Ci} = Components; + + Cu.import("resource://gre/modules/Services.jsm"); + Cu.import("resource://testing-common/BrowserTestUtils.jsm"); + + add_task(function*() { + BrowserTestUtils.domWindowOpened().then((win) => { + let treeOwner = win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem) + .treeOwner + let chromeFlags = treeOwner.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIXULWindow) + .chromeFlags; + ok(chromeFlags & Ci.nsIWebBrowserChrome.CHROME_MODAL, + "Should have the modal chrome flag"); + + let wbc = treeOwner.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebBrowserChrome); + ok(wbc.isWindowModal(), "Should report as modal"); + + win.close(); + }); + + let modal = window.openDialog("data:text/html,<p>This is a modal window for test_modal_windows.html</p>", + "_blank", "modal", null); + // Since the modal runs a nested event loop, just to be on the safe side, + // we'll wait a tick of the main event loop before resolving the task. + yield new Promise(resolve => setTimeout(resolve, 0)); + }); + + </script> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"> +</div> +<pre id="test"> +</pre> +</body> +</html>
\ No newline at end of file diff --git a/embedding/components/windowwatcher/test/test_named_window.html b/embedding/components/windowwatcher/test/test_named_window.html new file mode 100644 index 000000000..58bd8a9cf --- /dev/null +++ b/embedding/components/windowwatcher/test/test_named_window.html @@ -0,0 +1,92 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test that when content opens a new window with a name, that the +newly opened window actually gets that name, and that subsequent +attempts to open a window with that name will target the same +window. +--> +<head> + <meta charset="utf-8"> + <title>Test named windows</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script src="head.js" type="application/javascript;version=1.8"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <a href="#" id="link">Click me</a> + + <script type="application/javascript"> + "use strict"; + + const NAME = "my_window"; + const TARGET_URL = "data:text/html,<html><body>test_named_window.html new window</body></html>"; + const TARGET_URL_2 = TARGET_URL + "#2"; + const TARGET_URL_3 = TARGET_URL + "#3"; + + /** + * Returns a Promise that resolves once some target has had + * some event dispatched on it. + * + * @param target + * The thing to wait for the event to be dispatched + * through. + * @param eventName + * The name of the event to wait for. + * @returns Promise + */ + function promiseEvent(target, eventName) { + return new Promise(resolve => { + target.addEventListener(eventName, function onEvent(e) { + target.removeEventListener(eventName, onEvent, true); + resolve(e); + }, true); + }); + } + + add_task(function*() { + // This magic value of 2 means that by default, when content tries + // to open a new window, it'll actually open in a new window instead + // of a new tab. + yield SpecialPowers.pushPrefEnv({"set": [ + ["browser.link.open_newwindow", 2], + ]}); + + let win1 = window.open(TARGET_URL, "my_window"); + yield promiseEvent(win1, "load"); + + let name = SpecialPowers.wrap(win1) + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) + .getInterface(SpecialPowers.Ci.nsIWebNavigation) + .QueryInterface(SpecialPowers.Ci.nsIDocShellTreeItem) + .name; + + is(name, NAME, "Should have the expected name"); + is(win1.location.href, new URL(TARGET_URL).href, + "Should have loaded target TARGET_URL in the original window"); + + let hashChange = promiseEvent(win1, "hashchange"); + let win2 = window.open(TARGET_URL_2, "my_window"); + yield hashChange; + + is(win1, win2, "Should have gotten back the same window"); + is(win1.location.href, new URL(TARGET_URL_2).href, + "Should have re-targeted pre-existing window"); + + hashChange = promiseEvent(win1, "hashchange"); + let link = document.getElementById("link"); + link.setAttribute("target", NAME); + link.setAttribute("href", TARGET_URL_3); + link.click(); + + yield hashChange; + + is(win1.location.href, new URL(TARGET_URL_3).href, + "Should have re-targeted pre-existing window"); + + win1.close(); + }); + </script> +</body> +</html>
\ No newline at end of file diff --git a/embedding/components/windowwatcher/test/test_storage_copied.html b/embedding/components/windowwatcher/test/test_storage_copied.html new file mode 100644 index 000000000..27aeb51a9 --- /dev/null +++ b/embedding/components/windowwatcher/test/test_storage_copied.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test sessionStorage is copied over when a new window opens to the +same domain as the opener. +--> +<head> + <meta charset="utf-8"> + <title>Test storage copied</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script src="head.js" type="application/javascript;version=1.8"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + <script type="application/javascript"> + "use strict"; + + function waitForMessage(win) { + return new Promise(resolve => { + win.addEventListener("message", function onMessage(event) { + win.removeEventListener("message", onMessage); + resolve(event.data); + }); + }); + } + + add_task(function*() { + const TEST_VALUE = "test-value"; + // This magic value of 2 means that by default, when content tries + // to open a new window, it'll actually open in a new window instead + // of a new tab. + yield SpecialPowers.pushPrefEnv({"set": [ + ["browser.link.open_newwindow", 2], + ]}); + + window.sessionStorage.setItem("test-item", TEST_VALUE); + let win = window.open("file_storage_copied.html", "my_window"); + let data = yield waitForMessage(win); + is(data, TEST_VALUE, "Should have cloned the test value"); + win.close(); + }); + </script> +</body> +</html>
\ No newline at end of file diff --git a/embedding/ios/GeckoEmbed/GeckoEmbed.xcodeproj/project.pbxproj b/embedding/ios/GeckoEmbed/GeckoEmbed.xcodeproj/project.pbxproj new file mode 100644 index 000000000..3ee40d37b --- /dev/null +++ b/embedding/ios/GeckoEmbed/GeckoEmbed.xcodeproj/project.pbxproj @@ -0,0 +1,595 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + D2669E901AD6EC830027914D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D2669E8E1AD6EC830027914D /* Main.storyboard */; }; + D2669E921AD6EC830027914D /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D2669E911AD6EC830027914D /* Images.xcassets */; }; + D2669E951AD6EC830027914D /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = D2669E931AD6EC830027914D /* LaunchScreen.xib */; }; + D2C9CF2E1AA4960F0041BE29 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = D2C9CF2D1AA4960F0041BE29 /* main.mm */; }; + D2C9CF311AA4960F0041BE29 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D2C9CF301AA4960F0041BE29 /* AppDelegate.m */; }; + D2C9CF341AA4960F0041BE29 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D2C9CF331AA4960F0041BE29 /* ViewController.m */; }; + D2C9CF371AA4960F0041BE29 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D2C9CF351AA4960F0041BE29 /* Main.storyboard */; }; + D2C9CF391AA4960F0041BE29 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D2C9CF381AA4960F0041BE29 /* Images.xcassets */; }; + D2C9CF3C1AA4960F0041BE29 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = D2C9CF3A1AA4960F0041BE29 /* LaunchScreen.xib */; }; + D2C9CF521AA4A23A0041BE29 /* browser in Resources */ = {isa = PBXBuildFile; fileRef = D2C9CF511AA4A23A0041BE29 /* browser */; }; + D2CA73561ADDE3F10022A192 /* shell.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D2CA73551ADDE3F10022A192 /* shell.cpp */; }; + D2CA73591ADDEBAB0022A192 /* dirs.m in Sources */ = {isa = PBXBuildFile; fileRef = D2CA73581ADDEBAB0022A192 /* dirs.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + D2669EA81AD6ECFC0027914D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D2C9CF201AA4960F0041BE29 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D2C9CF271AA4960F0041BE29; + remoteInfo = GeckoEmbed; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + D20C8D111ADDD6E600CE4BC8 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + D20C8D121ADDD87C00CE4BC8 /* libmozglue.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libmozglue.dylib; path = "../../../../debug-mozilla-central/mozglue/build/libmozglue.dylib"; sourceTree = "<group>"; }; + D2669E821AD6EC830027914D /* js.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = js.app; sourceTree = BUILT_PRODUCTS_DIR; }; + D2669E851AD6EC830027914D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; + D2669E8F1AD6EC830027914D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; + D2669E911AD6EC830027914D /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; }; + D2669E941AD6EC830027914D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; }; + D2C9CF281AA4960F0041BE29 /* GeckoEmbed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GeckoEmbed.app; sourceTree = BUILT_PRODUCTS_DIR; }; + D2C9CF2C1AA4960F0041BE29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; + D2C9CF2D1AA4960F0041BE29 /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; }; + D2C9CF2F1AA4960F0041BE29 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; }; + D2C9CF301AA4960F0041BE29 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; }; + D2C9CF321AA4960F0041BE29 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; }; + D2C9CF331AA4960F0041BE29 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; }; + D2C9CF361AA4960F0041BE29 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; + D2C9CF381AA4960F0041BE29 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; }; + D2C9CF3B1AA4960F0041BE29 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; }; + D2C9CF511AA4A23A0041BE29 /* browser */ = {isa = PBXFileReference; lastKnownFileType = folder; path = browser; sourceTree = "<group>"; }; + D2C9CF541AA4A26E0041BE29 /* libxpcomglue.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libxpcomglue.a; path = "../../../../../iphone-simulator-debug/xpcom/glue/standalone/libxpcomglue.a"; sourceTree = "<group>"; }; + D2CA73551ADDE3F10022A192 /* shell.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = shell.cpp; sourceTree = "<group>"; }; + D2CA73581ADDEBAB0022A192 /* dirs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = dirs.m; sourceTree = "<group>"; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D2669E7F1AD6EC830027914D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2C9CF251AA4960F0041BE29 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D2669E831AD6EC830027914D /* js */ = { + isa = PBXGroup; + children = ( + D2669E8E1AD6EC830027914D /* Main.storyboard */, + D2669E911AD6EC830027914D /* Images.xcassets */, + D2669E931AD6EC830027914D /* LaunchScreen.xib */, + D2669E841AD6EC830027914D /* Supporting Files */, + D2CA73551ADDE3F10022A192 /* shell.cpp */, + D2CA73581ADDEBAB0022A192 /* dirs.m */, + ); + path = js; + sourceTree = "<group>"; + }; + D2669E841AD6EC830027914D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + D2669E851AD6EC830027914D /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = "<group>"; + }; + D2C9CF1F1AA4960F0041BE29 = { + isa = PBXGroup; + children = ( + D20C8D121ADDD87C00CE4BC8 /* libmozglue.dylib */, + D2C9CF2A1AA4960F0041BE29 /* GeckoEmbed */, + D2669E831AD6EC830027914D /* js */, + D2C9CF291AA4960F0041BE29 /* Products */, + ); + sourceTree = "<group>"; + }; + D2C9CF291AA4960F0041BE29 /* Products */ = { + isa = PBXGroup; + children = ( + D2C9CF281AA4960F0041BE29 /* GeckoEmbed.app */, + D2669E821AD6EC830027914D /* js.app */, + ); + name = Products; + sourceTree = "<group>"; + }; + D2C9CF2A1AA4960F0041BE29 /* GeckoEmbed */ = { + isa = PBXGroup; + children = ( + D2C9CF541AA4A26E0041BE29 /* libxpcomglue.a */, + D2C9CF511AA4A23A0041BE29 /* browser */, + D2C9CF2D1AA4960F0041BE29 /* main.mm */, + D2C9CF2F1AA4960F0041BE29 /* AppDelegate.h */, + D2C9CF301AA4960F0041BE29 /* AppDelegate.m */, + D2C9CF321AA4960F0041BE29 /* ViewController.h */, + D2C9CF331AA4960F0041BE29 /* ViewController.m */, + D2C9CF351AA4960F0041BE29 /* Main.storyboard */, + D2C9CF381AA4960F0041BE29 /* Images.xcassets */, + D2C9CF3A1AA4960F0041BE29 /* LaunchScreen.xib */, + D2C9CF2B1AA4960F0041BE29 /* Supporting Files */, + ); + path = GeckoEmbed; + sourceTree = "<group>"; + }; + D2C9CF2B1AA4960F0041BE29 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + D2C9CF2C1AA4960F0041BE29 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + D2669E811AD6EC830027914D /* js */ = { + isa = PBXNativeTarget; + buildConfigurationList = D2669EA61AD6EC830027914D /* Build configuration list for PBXNativeTarget "js" */; + buildPhases = ( + D20C8D141ADDD97100CE4BC8 /* Sources */, + D2669E7F1AD6EC830027914D /* Frameworks */, + D2669E801AD6EC830027914D /* Resources */, + D20C8D111ADDD6E600CE4BC8 /* CopyFiles */, + D2CA73571ADDEA7A0022A192 /* Run Script */, + ); + buildRules = ( + ); + dependencies = ( + D2669EA91AD6ECFC0027914D /* PBXTargetDependency */, + ); + name = js; + productName = js; + productReference = D2669E821AD6EC830027914D /* js.app */; + productType = "com.apple.product-type.application"; + }; + D2C9CF271AA4960F0041BE29 /* GeckoEmbed */ = { + isa = PBXNativeTarget; + buildConfigurationList = D2C9CF4B1AA4960F0041BE29 /* Build configuration list for PBXNativeTarget "GeckoEmbed" */; + buildPhases = ( + D2C9CF5D1AA4A6D80041BE29 /* Run Script */, + D2C9CF241AA4960F0041BE29 /* Sources */, + D2C9CF251AA4960F0041BE29 /* Frameworks */, + D2C9CF261AA4960F0041BE29 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GeckoEmbed; + productName = GeckoEmbed; + productReference = D2C9CF281AA4960F0041BE29 /* GeckoEmbed.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D2C9CF201AA4960F0041BE29 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = Mozilla; + TargetAttributes = { + D2669E811AD6EC830027914D = { + CreatedOnToolsVersion = 6.1.1; + }; + D2C9CF271AA4960F0041BE29 = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = D2C9CF231AA4960F0041BE29 /* Build configuration list for PBXProject "GeckoEmbed" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = D2C9CF1F1AA4960F0041BE29; + productRefGroup = D2C9CF291AA4960F0041BE29 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D2C9CF271AA4960F0041BE29 /* GeckoEmbed */, + D2669E811AD6EC830027914D /* js */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D2669E801AD6EC830027914D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D2669E901AD6EC830027914D /* Main.storyboard in Resources */, + D2669E951AD6EC830027914D /* LaunchScreen.xib in Resources */, + D2669E921AD6EC830027914D /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2C9CF261AA4960F0041BE29 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D2C9CF371AA4960F0041BE29 /* Main.storyboard in Resources */, + D2C9CF3C1AA4960F0041BE29 /* LaunchScreen.xib in Resources */, + D2C9CF391AA4960F0041BE29 /* Images.xcassets in Resources */, + D2C9CF521AA4A23A0041BE29 /* browser in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + D2C9CF5D1AA4A6D80041BE29 /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/bash; + shellScript = "${SRCROOT}/build-gecko.sh"; + }; + D2CA73571ADDEA7A0022A192 /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "${SRCROOT}/copy-jsshell.sh"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D20C8D141ADDD97100CE4BC8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D2CA73561ADDE3F10022A192 /* shell.cpp in Sources */, + D2CA73591ADDEBAB0022A192 /* dirs.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2C9CF241AA4960F0041BE29 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D2C9CF341AA4960F0041BE29 /* ViewController.m in Sources */, + D2C9CF311AA4960F0041BE29 /* AppDelegate.m in Sources */, + D2C9CF2E1AA4960F0041BE29 /* main.mm in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + D2669EA91AD6ECFC0027914D /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D2C9CF271AA4960F0041BE29 /* GeckoEmbed */; + targetProxy = D2669EA81AD6ECFC0027914D /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + D2669E8E1AD6EC830027914D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + D2669E8F1AD6EC830027914D /* Base */, + ); + name = Main.storyboard; + sourceTree = "<group>"; + }; + D2669E931AD6EC830027914D /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + D2669E941AD6EC830027914D /* Base */, + ); + name = LaunchScreen.xib; + sourceTree = "<group>"; + }; + D2C9CF351AA4960F0041BE29 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + D2C9CF361AA4960F0041BE29 /* Base */, + ); + name = Main.storyboard; + sourceTree = "<group>"; + }; + D2C9CF3A1AA4960F0041BE29 /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + D2C9CF3B1AA4960F0041BE29 /* Base */, + ); + name = LaunchScreen.xib; + sourceTree = "<group>"; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + D2669EA21AD6EC830027914D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + GCC_PREPROCESSOR_DEFINITIONS = ( + "IMPL_MFBT=1", + "EXPORT_JS_API=1", + "DEBUG=1", + "MOZILLA_CLIENT=1", + ); + INFOPLIST_FILE = js/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(GECKO_OBJDIR)/config/external/nss", + "$(GECKO_OBJDIR)/mozglue/build", + "$(GECKO_OBJDIR)/js/src", + "$(GECKO_OBJDIR)/js/src/editline", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-include", + "$(GECKO_OBJDIR)/js/src/js-confdefs.h", + ); + OTHER_LDFLAGS = ( + "-ljs_static", + "-lmozglue", + "-lnss3", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + USER_HEADER_SEARCH_PATHS = "$(GECKO_OBJDIR)/dist/include/nspr $(GECKO_OBJDIR)/dist/include $(SRCROOT)/../../../js/src $(SRCROOT)/../../../js/src/shell $(SRCROOT)/../../../js/src/editline"; + }; + name = Debug; + }; + D2669EA31AD6EC830027914D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + GCC_PREPROCESSOR_DEFINITIONS = ( + "IMPL_MFBT=1", + "EXPORT_JS_API=1", + "MOZILLA_CLIENT=1", + ); + INFOPLIST_FILE = js/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(GECKO_OBJDIR)/config/external/nss", + "$(GECKO_OBJDIR)/mozglue/build", + "$(GECKO_OBJDIR)/js/src", + "$(GECKO_OBJDIR)/js/src/editline", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-include", + "$(GECKO_OBJDIR)/js/src/js-confdefs.h", + ); + OTHER_LDFLAGS = ( + "-ljs_static", + "-lmozglue", + "-lnss3", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + USER_HEADER_SEARCH_PATHS = "$(GECKO_OBJDIR)/dist/include/nspr $(GECKO_OBJDIR)/dist/include $(SRCROOT)/../../../js/src $(SRCROOT)/../../../js/src/shell $(SRCROOT)/../../../js/src/editline"; + }; + name = Release; + }; + D2C9CF491AA4960F0041BE29 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD)"; + "ARCHS[sdk=iphoneos*]" = armv7; + "ARCHS[sdk=iphonesimulator*]" = "$(ARCHS_STANDARD)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GECKO_OBJDIR = ""; + "GECKO_OBJDIR[sdk=iphoneos*]" = "$(SRCROOT)/../../../../iphone-device-debug/"; + "GECKO_OBJDIR[sdk=iphonesimulator*]" = "$(SRCROOT)/../../../../iphone-simulator-debug/"; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + D2C9CF4A1AA4960F0041BE29 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD)"; + "ARCHS[sdk=iphoneos*]" = armv7; + "ARCHS[sdk=iphonesimulator*]" = "$(ARCHS_STANDARD)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + GECKO_OBJDIR = ""; + "GECKO_OBJDIR[sdk=iphoneos*]" = "$(SRCROOT)/../../../../iphone-device-opt/"; + "GECKO_OBJDIR[sdk=iphonesimulator*]" = "$(SRCROOT)/../../../../iphone-simulator-opt/"; + IPHONEOS_DEPLOYMENT_TARGET = 8.1; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + D2C9CF4C1AA4960F0041BE29 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD)"; + "ARCHS[sdk=iphoneos*]" = armv7; + "ARCHS[sdk=iphonesimulator*]" = "$(ARCHS_STANDARD)"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GECKO_OBJDIR = ""; + "GECKO_OBJDIR[sdk=iphoneos*]" = "$(SRCROOT)/../../../../iphone-device-debug/"; + "GECKO_OBJDIR[sdk=iphonesimulator*]" = "$(SRCROOT)/../../../../iphone-simulator-debug/"; + INFOPLIST_FILE = GeckoEmbed/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(GECKO_OBJDIR)/dist/lib", + "$(GECKO_OBJDIR)/mozglue/build", + "$(GECKO_OBJDIR)/xpcom/glue/standalone", + ); + OTHER_CODE_SIGN_FLAGS = ""; + OTHER_LDFLAGS = ( + "$(GECKO_OBJDIR)/xpcom/glue/standalone/libxpcomglue.a", + "$(GECKO_OBJDIR)/mozglue/build/libmozglue.dylib", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + USER_HEADER_SEARCH_PATHS = "$(GECKO_OBJDIR)/dist/include $(GECKO_OBJDIR)/dist/include/nspr"; + }; + name = Debug; + }; + D2C9CF4D1AA4960F0041BE29 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD)"; + "ARCHS[sdk=iphoneos*]" = armv7; + "ARCHS[sdk=iphonesimulator*]" = "$(ARCHS_STANDARD)"; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + GECKO_OBJDIR = ""; + "GECKO_OBJDIR[sdk=iphoneos*]" = "$(SRCROOT)/../../../../iphone-device-opt/"; + "GECKO_OBJDIR[sdk=iphonesimulator*]" = "$(SRCROOT)/../../../../iphone-simulator-opt/"; + INFOPLIST_FILE = GeckoEmbed/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(GECKO_OBJDIR)/dist/lib", + "$(GECKO_OBJDIR)/mozglue/build", + "$(GECKO_OBJDIR)/xpcom/glue/standalone", + ); + OTHER_CODE_SIGN_FLAGS = ""; + OTHER_LDFLAGS = ( + "$(GECKO_OBJDIR)/xpcom/glue/standalone/libxpcomglue.a", + "$(GECKO_OBJDIR)/mozglue/build/libmozglue.dylib", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + USER_HEADER_SEARCH_PATHS = "$(GECKO_OBJDIR)/dist/include $(GECKO_OBJDIR)/dist/include/nspr"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D2669EA61AD6EC830027914D /* Build configuration list for PBXNativeTarget "js" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D2669EA21AD6EC830027914D /* Debug */, + D2669EA31AD6EC830027914D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D2C9CF231AA4960F0041BE29 /* Build configuration list for PBXProject "GeckoEmbed" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D2C9CF491AA4960F0041BE29 /* Debug */, + D2C9CF4A1AA4960F0041BE29 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D2C9CF4B1AA4960F0041BE29 /* Build configuration list for PBXNativeTarget "GeckoEmbed" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D2C9CF4C1AA4960F0041BE29 /* Debug */, + D2C9CF4D1AA4960F0041BE29 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D2C9CF201AA4960F0041BE29 /* Project object */; +} diff --git a/embedding/ios/GeckoEmbed/GeckoEmbed.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/embedding/ios/GeckoEmbed/GeckoEmbed.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..a060b27f3 --- /dev/null +++ b/embedding/ios/GeckoEmbed/GeckoEmbed.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Workspace + version = "1.0"> + <FileRef + location = "self:GeckoEmbed.xcodeproj"> + </FileRef> +</Workspace> diff --git a/embedding/ios/GeckoEmbed/GeckoEmbed/AppDelegate.h b/embedding/ios/GeckoEmbed/GeckoEmbed/AppDelegate.h new file mode 100644 index 000000000..46f13ca55 --- /dev/null +++ b/embedding/ios/GeckoEmbed/GeckoEmbed/AppDelegate.h @@ -0,0 +1,17 @@ +// +// AppDelegate.h +// GeckoEmbed +// +// Created by Ted Mielczarek on 3/2/15. +// Copyright (c) 2015 Mozilla. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@interface AppDelegate : UIResponder <UIApplicationDelegate> + +@property (strong, nonatomic) UIWindow *window; + + +@end + diff --git a/embedding/ios/GeckoEmbed/GeckoEmbed/AppDelegate.m b/embedding/ios/GeckoEmbed/GeckoEmbed/AppDelegate.m new file mode 100644 index 000000000..4f14692e1 --- /dev/null +++ b/embedding/ios/GeckoEmbed/GeckoEmbed/AppDelegate.m @@ -0,0 +1,45 @@ +// +// AppDelegate.m +// GeckoEmbed +// +// Created by Ted Mielczarek on 3/2/15. +// Copyright (c) 2015 Mozilla. All rights reserved. +// + +#import "AppDelegate.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + +@end diff --git a/embedding/ios/GeckoEmbed/GeckoEmbed/Base.lproj/LaunchScreen.xib b/embedding/ios/GeckoEmbed/GeckoEmbed/Base.lproj/LaunchScreen.xib new file mode 100644 index 000000000..a00bb55e5 --- /dev/null +++ b/embedding/ios/GeckoEmbed/GeckoEmbed/Base.lproj/LaunchScreen.xib @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6214" systemVersion="14A314h" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES"> + <dependencies> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6207"/> + <capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/> + </dependencies> + <objects> + <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> + <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> + <view contentMode="scaleToFill" id="iN0-l3-epB"> + <rect key="frame" x="0.0" y="0.0" width="480" height="480"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 Mozilla. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye"> + <rect key="frame" x="20" y="439" width="441" height="21"/> + <fontDescription key="fontDescription" type="system" pointSize="17"/> + <color key="textColor" cocoaTouchSystemColor="darkTextColor"/> + <nil key="highlightedColor"/> + </label> + <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="GeckoEmbed" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX"> + <rect key="frame" x="20" y="140" width="441" height="43"/> + <fontDescription key="fontDescription" type="boldSystem" pointSize="36"/> + <color key="textColor" cocoaTouchSystemColor="darkTextColor"/> + <nil key="highlightedColor"/> + </label> + </subviews> + <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> + <constraints> + <constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/> + <constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/> + <constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/> + <constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/> + <constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/> + <constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/> + </constraints> + <nil key="simulatedStatusBarMetrics"/> + <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/> + <point key="canvasLocation" x="548" y="455"/> + </view> + </objects> +</document> diff --git a/embedding/ios/GeckoEmbed/GeckoEmbed/Base.lproj/Main.storyboard b/embedding/ios/GeckoEmbed/GeckoEmbed/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f56d2f3bb --- /dev/null +++ b/embedding/ios/GeckoEmbed/GeckoEmbed/Base.lproj/Main.storyboard @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r"> + <dependencies> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/> + </dependencies> + <scenes> + <!--View Controller--> + <scene sceneID="tne-QT-ifu"> + <objects> + <viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController"> + <layoutGuides> + <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/> + <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/> + </layoutGuides> + <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> + <rect key="frame" x="0.0" y="0.0" width="600" height="600"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> + </view> + </viewController> + <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> + </objects> + </scene> + </scenes> +</document> diff --git a/embedding/ios/GeckoEmbed/GeckoEmbed/Images.xcassets/AppIcon.appiconset/Contents.json b/embedding/ios/GeckoEmbed/GeckoEmbed/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..36d2c80d8 --- /dev/null +++ b/embedding/ios/GeckoEmbed/GeckoEmbed/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +}
\ No newline at end of file diff --git a/embedding/ios/GeckoEmbed/GeckoEmbed/Info.plist b/embedding/ios/GeckoEmbed/GeckoEmbed/Info.plist new file mode 100644 index 000000000..758a02b68 --- /dev/null +++ b/embedding/ios/GeckoEmbed/GeckoEmbed/Info.plist @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleExecutable</key> + <string>$(EXECUTABLE_NAME)</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.$(PRODUCT_NAME:rfc1034identifier)</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>$(PRODUCT_NAME)</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>2</string> + <key>LSRequiresIPhoneOS</key> + <true/> + <key>UILaunchStoryboardName</key> + <string>LaunchScreen</string> + <key>UIMainStoryboardFile</key> + <string>Main</string> + <key>UIRequiredDeviceCapabilities</key> + <array> + <string>armv7</string> + </array> + <key>UISupportedInterfaceOrientations</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> + <key>UISupportedInterfaceOrientations~ipad</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationPortraitUpsideDown</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> +</dict> +</plist> diff --git a/embedding/ios/GeckoEmbed/GeckoEmbed/ViewController.h b/embedding/ios/GeckoEmbed/GeckoEmbed/ViewController.h new file mode 100644 index 000000000..ff812f5a2 --- /dev/null +++ b/embedding/ios/GeckoEmbed/GeckoEmbed/ViewController.h @@ -0,0 +1,15 @@ +// +// ViewController.h +// GeckoEmbed +// +// Created by Ted Mielczarek on 3/2/15. +// Copyright (c) 2015 Mozilla. All rights reserved. +// + +#import <UIKit/UIKit.h> + +@interface ViewController : UIViewController + + +@end + diff --git a/embedding/ios/GeckoEmbed/GeckoEmbed/ViewController.m b/embedding/ios/GeckoEmbed/GeckoEmbed/ViewController.m new file mode 100644 index 000000000..b4d9c73d4 --- /dev/null +++ b/embedding/ios/GeckoEmbed/GeckoEmbed/ViewController.m @@ -0,0 +1,27 @@ +// +// ViewController.m +// GeckoEmbed +// +// Created by Ted Mielczarek on 3/2/15. +// Copyright (c) 2015 Mozilla. All rights reserved. +// + +#import "ViewController.h" + +@interface ViewController () + +@end + +@implementation ViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view, typically from a nib. +} + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +@end diff --git a/embedding/ios/GeckoEmbed/GeckoEmbed/browser/application.ini b/embedding/ios/GeckoEmbed/GeckoEmbed/browser/application.ini new file mode 100644 index 000000000..8f2c2248c --- /dev/null +++ b/embedding/ios/GeckoEmbed/GeckoEmbed/browser/application.ini @@ -0,0 +1,10 @@ +[App] +Vendor=Mozilla +Name=browser +Version=1.0 +BuildID=20150209 +ID=browser@mozilla.org + +[Gecko] +MinVersion=34 +MaxVersion=1000 diff --git a/embedding/ios/GeckoEmbed/GeckoEmbed/browser/chrome.manifest b/embedding/ios/GeckoEmbed/GeckoEmbed/browser/chrome.manifest new file mode 100644 index 000000000..e2a1c3592 --- /dev/null +++ b/embedding/ios/GeckoEmbed/GeckoEmbed/browser/chrome.manifest @@ -0,0 +1 @@ +content mybrowser file:chrome/content/
\ No newline at end of file diff --git a/embedding/ios/GeckoEmbed/GeckoEmbed/browser/chrome/content/hello.js b/embedding/ios/GeckoEmbed/GeckoEmbed/browser/chrome/content/hello.js new file mode 100644 index 000000000..424001a28 --- /dev/null +++ b/embedding/ios/GeckoEmbed/GeckoEmbed/browser/chrome/content/hello.js @@ -0,0 +1,9 @@ +Components.utils.import("resource://gre/modules/Services.jsm"); +addEventListener("DOMContentLoaded", function loaded() { + removeEventListener("DOMContentLoaded", loaded); + var b = document.getElementById("browser"); + Services.obs.notifyObservers(b.docShell, + "geckoembed-browser-loaded", + null); + b.loadURI("http://people.mozilla.org/~tmielczarek/iosstart.html"); +}); diff --git a/embedding/ios/GeckoEmbed/GeckoEmbed/browser/chrome/content/hello.xul b/embedding/ios/GeckoEmbed/GeckoEmbed/browser/chrome/content/hello.xul new file mode 100644 index 000000000..7744ff3a6 --- /dev/null +++ b/embedding/ios/GeckoEmbed/GeckoEmbed/browser/chrome/content/hello.xul @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin" type="text/css"?> + +<window title="browser" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + sizemode="maximized" + flex="1"> + <browser type="content" src="http://mozilla.org/" flex="1"/> +</window> diff --git a/embedding/ios/GeckoEmbed/GeckoEmbed/browser/defaults/preferences/prefs.js b/embedding/ios/GeckoEmbed/GeckoEmbed/browser/defaults/preferences/prefs.js new file mode 100644 index 000000000..464b02234 --- /dev/null +++ b/embedding/ios/GeckoEmbed/GeckoEmbed/browser/defaults/preferences/prefs.js @@ -0,0 +1,3 @@ +pref("toolkit.defaultChromeURI", "chrome://mybrowser/content/hello.xul"); +pref("browser.dom.window.dump.enabled", true); +pref("dom.max_script_run_time", 0); diff --git a/embedding/ios/GeckoEmbed/GeckoEmbed/main.mm b/embedding/ios/GeckoEmbed/GeckoEmbed/main.mm new file mode 100644 index 000000000..c79b46115 --- /dev/null +++ b/embedding/ios/GeckoEmbed/GeckoEmbed/main.mm @@ -0,0 +1,96 @@ +// +// main.m +// XulRunner +// +// Created by Ted Mielczarek on 2/9/15. +// Copyright (c) 2015 Mozilla. All rights reserved. +// + +#import <UIKit/UIKit.h> + +#include "mozilla-config.h" +#define XPCOM_GLUE 1 +#include "nsXULAppAPI.h" +#include "nsXPCOMGlue.h" +#include "nsXREAppData.h" +#include "mozilla/AppData.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsIPrefService.h" +#include "nsServiceManagerUtils.h" + +static const nsXREAppData sAppData = { + sizeof(nsXREAppData), + nullptr, // directory + "Mozilla", + "Browser", + nullptr, + "38.0a1", + "201502090123", + "browser@mozilla.org", + nullptr, // copyright + 0, + nullptr, // xreDirectory + "38.0a1", + "*", + "https://crash-reports.mozilla.com/submit", + nullptr, + "Firefox" +}; + +XRE_GetFileFromPathType XRE_GetFileFromPath; +XRE_CreateAppDataType XRE_CreateAppData; +XRE_FreeAppDataType XRE_FreeAppData; +XRE_mainType XRE_main; + +static const nsDynamicFunctionLoad kXULFuncs[] = { + { "XRE_GetFileFromPath", (NSFuncPtr*) &XRE_GetFileFromPath }, + { "XRE_CreateAppData", (NSFuncPtr*) &XRE_CreateAppData }, + { "XRE_FreeAppData", (NSFuncPtr*) &XRE_FreeAppData }, + { "XRE_main", (NSFuncPtr*) &XRE_main }, + { nullptr, nullptr } +}; + +const int MAXPATHLEN = 1024; +const char* XPCOM_DLL = "XUL"; + +int main(int argc, char * argv[]) { + char exeDir[MAXPATHLEN]; + NSString* bundlePath = [[NSBundle mainBundle] bundlePath]; + strncpy(exeDir, [bundlePath UTF8String], MAXPATHLEN); + strcat(exeDir, "/Frameworks/"); + strncat(exeDir, XPCOM_DLL, MAXPATHLEN - strlen(exeDir)); + + nsresult rv = XPCOMGlueStartup(exeDir); + if (NS_FAILED(rv)) { + printf("Couldn't load XPCOM (0x%08x) from %s\n", rv, exeDir); + return 255; + } + + rv = XPCOMGlueLoadXULFunctions(kXULFuncs); + if (NS_FAILED(rv)) { + printf("Couldn't load XRE functions.\n"); + return 255; + } + + mozilla::ScopedAppData appData(&sAppData); + + nsCOMPtr<nsIFile> greDir; + rv = NS_NewNativeLocalFile(nsDependentCString([bundlePath UTF8String]), true, + getter_AddRefs(greDir)); + if (NS_FAILED(rv)) { + printf("Couldn't find the application directory.\n"); + return 255; + } + + nsCOMPtr<nsIFile> appSubdir; + greDir->Clone(getter_AddRefs(appSubdir)); + greDir->Append(NS_LITERAL_STRING("Frameworks")); + appSubdir->Append(NS_LITERAL_STRING("browser")); + + mozilla::SetStrongPtr(appData.directory, static_cast<nsIFile*>(appSubdir.get())); + greDir.forget(&appData.xreDirectory); + + int result = XRE_main(argc, argv, &appData, 0); + return result; +} diff --git a/embedding/ios/GeckoEmbed/build-gecko.sh b/embedding/ios/GeckoEmbed/build-gecko.sh new file mode 100755 index 000000000..1475fa916 --- /dev/null +++ b/embedding/ios/GeckoEmbed/build-gecko.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e + +if test "${ACTION}" != "clean"; then + echo "Building in ${GECKO_OBJDIR}" + make -j8 -s -C $GECKO_OBJDIR binaries + + echo "Copying files from ${GECKO_OBJDIR}/dist/bin" + rsync -pvtrlL --exclude "Test*" \ + --exclude "test_*" --exclude "*_unittest" \ + --exclude xulrunner \ + ${GECKO_OBJDIR}/dist/bin/ $BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Frameworks + + if test ${ARCHS} == "armv7"; then + for x in $BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Frameworks/*.dylib $BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Frameworks/XUL; do + echo "Signing $x" + /usr/bin/codesign --force --sign "${EXPANDED_CODE_SIGN_IDENTITY}" --preserve-metadata=identifier,entitlements,resource-rules $x + done + fi +fi diff --git a/embedding/ios/GeckoEmbed/copy-jsshell.sh b/embedding/ios/GeckoEmbed/copy-jsshell.sh new file mode 100755 index 000000000..a2e9c5d6e --- /dev/null +++ b/embedding/ios/GeckoEmbed/copy-jsshell.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -e + +if test -z ${GECKO_OBJDIR}; then + echo "Error: GECKO_OBJDIR not set!" + exit 1 +fi + +if test "${ACTION}" != "clean"; then + echo "Copying files from ${GECKO_OBJDIR}/dist/bin" + mkdir -p $BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Frameworks + cp ${GECKO_OBJDIR}/mozglue/build/libmozglue.dylib $BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Frameworks + cp ${GECKO_OBJDIR}/config/external/nss/libnss3.dylib $BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Frameworks + + if test ${ARCHS} == "armv7"; then + for x in $BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Frameworks/*.dylib; do + echo "Signing $x" + /usr/bin/codesign --force --sign "${EXPANDED_CODE_SIGN_IDENTITY}" --preserve-metadata=identifier,entitlements,resource-rules $x + done + fi +fi diff --git a/embedding/ios/GeckoEmbed/js/Base.lproj/LaunchScreen.xib b/embedding/ios/GeckoEmbed/js/Base.lproj/LaunchScreen.xib new file mode 100644 index 000000000..50022f3d3 --- /dev/null +++ b/embedding/ios/GeckoEmbed/js/Base.lproj/LaunchScreen.xib @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6214" systemVersion="14A314h" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES"> + <dependencies> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6207"/> + <capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/> + </dependencies> + <objects> + <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/> + <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/> + <view contentMode="scaleToFill" id="iN0-l3-epB"> + <rect key="frame" x="0.0" y="0.0" width="480" height="480"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 Mozilla. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye"> + <rect key="frame" x="20" y="439" width="441" height="21"/> + <fontDescription key="fontDescription" type="system" pointSize="17"/> + <color key="textColor" cocoaTouchSystemColor="darkTextColor"/> + <nil key="highlightedColor"/> + </label> + <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="js" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX"> + <rect key="frame" x="20" y="140" width="441" height="43"/> + <fontDescription key="fontDescription" type="boldSystem" pointSize="36"/> + <color key="textColor" cocoaTouchSystemColor="darkTextColor"/> + <nil key="highlightedColor"/> + </label> + </subviews> + <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> + <constraints> + <constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/> + <constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/> + <constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/> + <constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/> + <constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/> + <constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/> + </constraints> + <nil key="simulatedStatusBarMetrics"/> + <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/> + <point key="canvasLocation" x="548" y="455"/> + </view> + </objects> +</document> diff --git a/embedding/ios/GeckoEmbed/js/Base.lproj/Main.storyboard b/embedding/ios/GeckoEmbed/js/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f56d2f3bb --- /dev/null +++ b/embedding/ios/GeckoEmbed/js/Base.lproj/Main.storyboard @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r"> + <dependencies> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/> + </dependencies> + <scenes> + <!--View Controller--> + <scene sceneID="tne-QT-ifu"> + <objects> + <viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController"> + <layoutGuides> + <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/> + <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/> + </layoutGuides> + <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> + <rect key="frame" x="0.0" y="0.0" width="600" height="600"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> + </view> + </viewController> + <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> + </objects> + </scene> + </scenes> +</document> diff --git a/embedding/ios/GeckoEmbed/js/Images.xcassets/AppIcon.appiconset/Contents.json b/embedding/ios/GeckoEmbed/js/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..36d2c80d8 --- /dev/null +++ b/embedding/ios/GeckoEmbed/js/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +}
\ No newline at end of file diff --git a/embedding/ios/GeckoEmbed/js/Info.plist b/embedding/ios/GeckoEmbed/js/Info.plist new file mode 100644 index 000000000..e7375d54c --- /dev/null +++ b/embedding/ios/GeckoEmbed/js/Info.plist @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleExecutable</key> + <string>$(EXECUTABLE_NAME)</string> + <key>CFBundleIdentifier</key> + <string>org.mozilla.$(PRODUCT_NAME:rfc1034identifier)</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>$(PRODUCT_NAME)</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1</string> + <key>LSRequiresIPhoneOS</key> + <true/> + <key>UILaunchStoryboardName</key> + <string>LaunchScreen</string> + <key>UIMainStoryboardFile</key> + <string>Main</string> + <key>UIRequiredDeviceCapabilities</key> + <array> + <string>armv7</string> + </array> + <key>UISupportedInterfaceOrientations</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> + <key>UISupportedInterfaceOrientations~ipad</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationPortraitUpsideDown</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> +</dict> +</plist> diff --git a/embedding/ios/GeckoEmbed/js/dirs.m b/embedding/ios/GeckoEmbed/js/dirs.m new file mode 100644 index 000000000..dc74df976 --- /dev/null +++ b/embedding/ios/GeckoEmbed/js/dirs.m @@ -0,0 +1,12 @@ +#import <Foundation/Foundation.h> + +bool GetDocumentsDirectory(char* dir) +{ + NSSearchPathDirectory directory = NSDocumentDirectory; + NSArray* paths = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES); + if ([paths count] == 0) { + return false; + } + strcpy(dir, [[paths objectAtIndex:0] UTF8String]); + return true; +} diff --git a/embedding/ios/GeckoEmbed/js/shell.cpp b/embedding/ios/GeckoEmbed/js/shell.cpp new file mode 100644 index 000000000..82171ca37 --- /dev/null +++ b/embedding/ios/GeckoEmbed/js/shell.cpp @@ -0,0 +1,25 @@ +#include "OSObject.cpp" +#include "jsoptparse.cpp" +#define main shell_main +#include "js.cpp" +#undef main + +#include <unistd.h> + +extern "C" bool GetDocumentsDirectory(char *dir); + +// Fake editline +char* readline(const char* prompt) +{ + return nullptr; +} + +void add_history(char* line) {} + +int main(int argc, char** argv, char** envp) +{ + char dir[1024]; + GetDocumentsDirectory(dir); + chdir(dir); + return shell_main(argc, argv, envp); +} diff --git a/embedding/ios/app.mozbuild b/embedding/ios/app.mozbuild new file mode 100644 index 000000000..5168ced14 --- /dev/null +++ b/embedding/ios/app.mozbuild @@ -0,0 +1,10 @@ +# -*- 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/. + +include('/toolkit/toolkit.mozbuild') + +if CONFIG['MOZ_EXTENSIONS']: + DIRS += ['/extensions'] diff --git a/embedding/ios/build.mk b/embedding/ios/build.mk new file mode 100644 index 000000000..017f3dff1 --- /dev/null +++ b/embedding/ios/build.mk @@ -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/. + +installer: + +package: + +package-compare: + +stage-package: + +sdk: + +install:: + +clean:: + +distclean:: + +source-package:: + +upload:: + +source-upload:: + +hg-bundle:: + +l10n-check:: diff --git a/embedding/ios/confvars.sh b/embedding/ios/confvars.sh new file mode 100644 index 000000000..8bac3cf30 --- /dev/null +++ b/embedding/ios/confvars.sh @@ -0,0 +1,10 @@ +#! /bin/sh +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +MOZ_APP_NAME=geckoembed +MOZ_APP_DISPLAYNAME=GeckoEmbed +MOZ_UPDATER= +MOZ_APP_VERSION=$MOZILLA_VERSION +MOZ_EXTENSIONS_DEFAULT=" gio" diff --git a/embedding/ios/moz.configure b/embedding/ios/moz.configure new file mode 100644 index 000000000..00849b08b --- /dev/null +++ b/embedding/ios/moz.configure @@ -0,0 +1,9 @@ +# -*- 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/. + +imply_option('MOZ_PLACES', True) +imply_option('MOZ_SERVICES_HEALTHREPORT', True) +imply_option('MOZ_SERVICES_SYNC', True) diff --git a/embedding/moz.build b/embedding/moz.build new file mode 100644 index 000000000..79ec779b8 --- /dev/null +++ b/embedding/moz.build @@ -0,0 +1,24 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += ['components', 'browser'] + +TEST_DIRS += ['test'] + +if CONFIG['ENABLE_TESTS']: + XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini'] + +XPIDL_SOURCES += [ + 'nsIWindowCreator.idl', + 'nsIWindowCreator2.idl', + 'nsIWindowProvider.idl', +] + +XPIDL_MODULE = 'embed_base' + +EXPORTS += [ + 'nsEmbedCID.h', +] diff --git a/embedding/nsEmbedCID.h b/embedding/nsEmbedCID.h new file mode 100644 index 000000000..57dc4b453 --- /dev/null +++ b/embedding/nsEmbedCID.h @@ -0,0 +1,58 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 NSEMBEDCID_H +#define NSEMBEDCID_H + +/** + * @file + * @brief List of, and documentation for, frozen Gecko embedding contracts. + */ + +/** + * Web Browser ContractID + * Creating an instance of this ContractID (via createInstanceByContractID) + * is the basic way to instantiate a Gecko browser. + * + * This contract implements the following interfaces: + * nsIWebBrowser + * nsIWebBrowserSetup + * nsIInterfaceRequestor + * + * @note This contract does not guarantee implementation of any other + * interfaces and does not guarantee ability to get any particular + * interfaces via the nsIInterfaceRequestor implementation. + */ +#define NS_WEBBROWSER_CONTRACTID \ + "@mozilla.org/embedding/browser/nsWebBrowser;1" + +/** + * Prompt Service ContractID + * The prompt service (which can be gotten by calling getServiceByContractID + * on this ContractID) is the way to pose various prompts, alerts, + * and confirmation dialogs to the user. + * + * This contract implements the following interfaces: + * nsIPromptService + * nsIPromptService2 (optional) + * + * Embedders may override this ContractID with their own implementation if they + * want more control over the way prompts, alerts, and confirmation dialogs are + * presented to the user. + */ +#define NS_PROMPTSERVICE_CONTRACTID \ + "@mozilla.org/embedcomp/prompt-service;1" + +/** + * This contract ID should be implemented by password managers to be able to + * override the standard implementation of nsIAuthPrompt2. It will be used as + * a service. + * + * This contract implements the following interfaces: + * nsIPromptFactory + */ +#define NS_PWMGR_AUTHPROMPTFACTORY \ + "@mozilla.org/passwordmanager/authpromptfactory;1" + +#endif // NSEMBEDCID_H diff --git a/embedding/nsIWindowCreator.idl b/embedding/nsIWindowCreator.idl new file mode 100644 index 000000000..ade2dc467 --- /dev/null +++ b/embedding/nsIWindowCreator.idl @@ -0,0 +1,41 @@ +/* -*- 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/. */ + +/** + * nsIWindowCreator is a callback interface used by Gecko to create + * new browser windows. The application, either Mozilla or an embedding app, + * must provide an implementation of the Window Watcher component and + * notify the WindowWatcher during application initialization. + * @see nsIWindowWatcher + */ + +#include "nsISupports.idl" + +interface nsIWebBrowserChrome; + +[scriptable, uuid(30465632-A777-44cc-90F9-8145475EF999)] + +interface nsIWindowCreator : nsISupports { + + /** Create a new window. Gecko will/may call this method, if made + available to it, to create new windows. + @param parent parent window, if any. null if not. the newly created + window should be made a child/dependent window of + the parent, if any (and if the concept applies + to the underlying OS). + @param chromeFlags chrome features from nsIWebBrowserChrome + @return the new window + */ + nsIWebBrowserChrome createChromeWindow(in nsIWebBrowserChrome parent, + in uint32_t chromeFlags); +}; + +%{C++ +// {30465632-A777-44cc-90F9-8145475EF999} +#define NS_WINDOWCREATOR_IID \ + {0x30465632, 0xa777, 0x44cc, {0x90, 0xf9, 0x81, 0x45, 0x47, 0x5e, 0xf9, 0x99}} +%} + diff --git a/embedding/nsIWindowCreator2.idl b/embedding/nsIWindowCreator2.idl new file mode 100644 index 000000000..c07a794a0 --- /dev/null +++ b/embedding/nsIWindowCreator2.idl @@ -0,0 +1,70 @@ +/* -*- 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/. */ + +/** + * nsIWindowCreator2 is an extension of nsIWindowCreator which allows + * additional information about the context of the window creation to + * be passed. + * + * @see nsIWindowCreator + * @see nsIWindowWatcher + * + * @status + */ + +#include "nsIWindowCreator.idl" + +interface nsITabParent; +interface nsIURI; +interface nsIWebBrowserChrome; +interface mozIDOMWindowProxy; + +[scriptable, uuid(b6c44689-f97e-4f32-a723-29eeddfbdc53)] + +interface nsIWindowCreator2 : nsIWindowCreator { + + /** + * Definitions for contextFlags + */ + + // Likely that the window is an advertising popup. + const unsigned long PARENT_IS_LOADING_OR_RUNNING_TIMEOUT = 0x00000001; + + /** Create a new window. Gecko will/may call this method, if made + available to it, to create new windows. + @param parent Parent window, if any. Null if not. The newly created + window should be made a child/dependent window of + the parent, if any (and if the concept applies + to the underlying OS). + @param chromeFlags Chrome features from nsIWebBrowserChrome + @param contextFlags Flags about the context of the window being created. + @param aOpeningTab The TabParent that is trying to open this new chrome + window. Can be nullptr. + @param aOpener The window which is trying to open this new chrome window. + Can be nullptr + @param cancel Return |true| to reject window creation. If true the + implementation has determined the window should not + be created at all. The caller should not default + to any possible backup scheme for creating the window. + @return the new window. Will be null if canceled or an error occurred. + */ + nsIWebBrowserChrome createChromeWindow2(in nsIWebBrowserChrome parent, + in uint32_t chromeFlags, + in uint32_t contextFlags, + in nsITabParent aOpeningTab, + in mozIDOMWindowProxy aOpener, + out boolean cancel); + + /** + * B2G multi-screen support. When open another top-level window on b2g, + * a screen ID is needed for identifying which screen this window is + * opened to. + * @param aScreenId Differentiate screens of windows. It is platform- + * specific due to the hardware limitation for now. + */ + [noscript] + void setScreenId(in uint32_t aScreenId); +}; diff --git a/embedding/nsIWindowProvider.idl b/embedding/nsIWindowProvider.idl new file mode 100644 index 000000000..34c517312 --- /dev/null +++ b/embedding/nsIWindowProvider.idl @@ -0,0 +1,103 @@ +/* -*- 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/. */ + +/** + * nsIWindowProvider is a callback interface used by Gecko when it needs to + * open a new window. This interface can be implemented by Gecko consumers who + * wish to provide a custom "new window" of their own (for example by returning + * a new tab, an existing window, etc) instead of just having a real new + * toplevel window open. + */ + +#include "nsISupports.idl" + +interface mozIDOMWindowProxy; +interface nsIURI; + +/** + * The nsIWindowProvider interface exists so that the window watcher's default + * behavior of opening a new window can be easly modified. When the window + * watcher needs to open a new window, it will first check with the + * nsIWindowProvider it gets from the parent window. If there is no provider + * or the provider does not provide a window, the window watcher will proceed + * to actually open a new window. + */ +[scriptable, uuid(e97a3830-15ef-499b-8372-c22d128091c1)] +interface nsIWindowProvider : nsISupports +{ + /** + * A method to request that this provider provide a window. The window + * returned need not to have the right name or parent set on it; setting + * those is the caller's responsibility. The provider can always return null + * to have the caller create a brand-new window. + * + * @param aParent Must not be null. This is the window that the caller wants + * to use as the parent for the new window. Generally, + * nsIWindowProvider implementors can expect to be somehow related to + * aParent; the relationship may depend on the nsIWindowProvider + * implementation. + * + * @param aChromeFlags The chrome flags the caller will use to create a new + * window if this provider returns null. See nsIWebBrowserChrome for + * the possible values of this field. + * + * @param aPositionSpecified Whether the attempt to create a window is trying + * to specify a position for the new window. + * + * @param aSizeSpecified Whether the attempt to create a window is trying to + * specify a size for the new window. + * + * @param aURI The URI to be loaded in the new window (may be NULL). The + * nsIWindowProvider implementation must not load this URI into the + * window it returns. This URI is provided solely to help the + * nsIWindowProvider implementation make decisions; the caller will + * handle loading the URI in the window returned if provideWindow + * returns a window. + * + * When making decisions based on aURI, note that even when it's not + * null, aURI may not represent all relevant information about the + * load. For example, the load may have extra load flags, POST data, + * etc. + * + * @param aName The name of the window being opened. Setting the name on the + * return value of provideWindow will be handled by the caller; aName + * is provided solely to help the nsIWindowProvider implementation + * make decisions. + * + * @param aFeatures The feature string for the window being opened. This may + * be empty. The nsIWindowProvider implementation is allowed to apply + * the feature string to the window it returns in any way it sees fit. + * See the nsIWindowWatcher interface for details on feature strings. + * + * @param aWindowIsNew [out] Whether the window being returned was just + * created by the window provider implementation. This can be used by + * callers to keep track of which windows were opened by the user as + * opposed to being opened programmatically. This should be set to + * false if the window being returned existed before the + * provideWindow() call. The value of this out parameter is + * meaningless if provideWindow() returns null. + + * @return A window the caller should use or null if the caller should just + * create a new window. The returned window may be newly opened by + * the nsIWindowProvider implementation or may be a window that + * already existed. + * + * @throw NS_ERROR_ABORT if the caller should cease its attempt to open a new + * window. + * + * @see nsIWindowWatcher for more information on aFeatures. + * @see nsIWebBrowserChrome for more information on aChromeFlags. + */ + mozIDOMWindowProxy provideWindow(in mozIDOMWindowProxy aParent, + in unsigned long aChromeFlags, + in boolean aCalledFromJS, + in boolean aPositionSpecified, + in boolean aSizeSpecified, + in nsIURI aURI, + in AString aName, + in AUTF8String aFeatures, + in boolean aForceNoOpener, + out boolean aWindowIsNew); +}; diff --git a/embedding/test/320x240.ogv b/embedding/test/320x240.ogv Binary files differnew file mode 100644 index 000000000..093158432 --- /dev/null +++ b/embedding/test/320x240.ogv diff --git a/embedding/test/browser.ini b/embedding/test/browser.ini new file mode 100644 index 000000000..936b07104 --- /dev/null +++ b/embedding/test/browser.ini @@ -0,0 +1,6 @@ +[DEFAULT] +support-files = + bug1204626_doc0.html + bug1204626_doc1.html + +[browser_bug1204626.js] diff --git a/embedding/test/browser_bug1204626.js b/embedding/test/browser_bug1204626.js new file mode 100644 index 000000000..165cb9b43 --- /dev/null +++ b/embedding/test/browser_bug1204626.js @@ -0,0 +1,87 @@ +"use strict"; // -*- js-indent-level: 2; indent-tabs-mode: nil -*- +var Cc = Components.classes; +var Ci = Components.interfaces; +const contentBase = "https://example.com/browser/embedding/test/"; +const chromeBase = "chrome://mochitests/content/browser/embedding/test/"; +const testPageURL = contentBase + "bug1204626_doc0.html"; + +function one_test(delay, continuation) { + let delayStr = delay === null ? "no delay" : "delay = " + delay + "ms"; + let browser; + + BrowserTestUtils.openNewForegroundTab(gBrowser, testPageURL).then((tab) => { + browser = tab.linkedBrowser; + let persistable = browser.QueryInterface(Ci.nsIFrameLoaderOwner) + .frameLoader + .QueryInterface(Ci.nsIWebBrowserPersistable); + persistable.startPersistence(/* outer window ID: */ 0, { + onDocumentReady, + onError: function(status) { + ok(false, new Components.Exception("startPersistence failed", status)); + continuation(); + } + }); + }); + + function onDocumentReady(doc) { + const nameStem="test_bug1204626_" + Date.now(); + let wbp = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(Ci.nsIWebBrowserPersist); + let tmp = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("TmpD", Ci.nsIFile); + let tmpFile = tmp.clone(); + tmpFile.append(nameStem + "_saved.html"); + let tmpDir = tmp.clone(); + tmpDir.append(nameStem + "_files"); + + registerCleanupFunction(function cleanUp() { + if (tmpFile.exists()) { + tmpFile.remove(/* recursive: */ false); + } + if (tmpDir.exists()) { + tmpDir.remove(/* recursive: */ true); + } + }); + + wbp.progressListener = { + onProgressChange: function(){}, + onLocationChange: function(){}, + onStatusChange: function(){}, + onSecurityChange: function(){}, + onStateChange: function wbp_stateChange(_wbp, _req, state, _status) { + if ((state & Ci.nsIWebProgressListener.STATE_STOP) == 0) { + return; + } + ok(true, "Finished save (" + delayStr + ") but might have crashed."); + continuation(); + } + } + + function doSave() { + wbp.saveDocument(doc, tmpFile, tmpDir, null, 0, 0); + } + if (delay === null) { + doSave(); + } else { + setTimeout(doSave, delay); + } + browser.messageManager.loadFrameScript("data:,content.window.close()", true); + } +} + +function test() { + waitForExplicitFinish(); + // 0ms breaks having the actor under PBrowser, but not 10ms. + // 10ms provokes the double-__delete__, but not 0ms. + // And a few others, just in case. + const testRuns = [null, 0, 10, 0, 10, 20, 50, 100]; + let i = 0; + (function next_test() { + if (i < testRuns.length) { + one_test(testRuns[i++], next_test); + } else { + finish(); + } + })(); +} diff --git a/embedding/test/bug1170334_iframe.xml b/embedding/test/bug1170334_iframe.xml new file mode 100644 index 000000000..1821e07f9 --- /dev/null +++ b/embedding/test/bug1170334_iframe.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="utf-8"?> +<?xml-stylesheet href="bug1170334_style.css" type="text/css" title=""?>FAIL"?> +<thing/> diff --git a/embedding/test/bug1170334_style.css b/embedding/test/bug1170334_style.css new file mode 100644 index 000000000..476c22b69 --- /dev/null +++ b/embedding/test/bug1170334_style.css @@ -0,0 +1 @@ +/* This stylesheet intentionally left blank. */ diff --git a/embedding/test/bug1204626_doc0.html b/embedding/test/bug1204626_doc0.html new file mode 100644 index 000000000..cbced762c --- /dev/null +++ b/embedding/test/bug1204626_doc0.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<p>This is a document, and it contains an iframe:</p> +<iframe src="bug1204626_doc1.html"></iframe> diff --git a/embedding/test/bug1204626_doc1.html b/embedding/test/bug1204626_doc1.html new file mode 100644 index 000000000..cffc283d2 --- /dev/null +++ b/embedding/test/bug1204626_doc1.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<p>This is the document inside the iframe. <small>(Currently this +document doesn't even need to exist in order to reproduce the bug in +question, as long as the parent contains a frame, but it's probably +best not to depend on that.)</small></p> diff --git a/embedding/test/bug293834_form.html b/embedding/test/bug293834_form.html new file mode 100644 index 000000000..6ad19f02f --- /dev/null +++ b/embedding/test/bug293834_form.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> + +<html> + <head> + <title>Nested iframe for bug 293834</title> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + </head> + <body> + <form> + not prefilled: <input id="a-text" type="text"></input><br> + prefilled: <input id="a-prefilled-text" type="text" value="prefill "></input><br> + + <input name="a-radio" id="radioa" value="radio-a" type="radio">Should be saved checked</input><br> + <input name="a-radio" value="radio-c" type="radio" checked="true">Initially checked</input><br> + <select id="aselect"> + <option value="target">Should be saved selected</option> + <option value="default" selected="selected">Default Selected</option> + </select><br> + not prefilled: <textarea id="a-textbox"></textarea><br> + prefilled: <textarea id="a-prefilled-textbox">prefill </textarea><br> + <input id="a-checkbox" type="checkbox">Should be saved checked</input><br> + <input id="a-prefilled-checkbox" type="checkbox" checked="true">Initiallly checked</input><br> + </form> + </body> +</html> + diff --git a/embedding/test/bug449141_page.html b/embedding/test/bug449141_page.html new file mode 100644 index 000000000..586b95505 --- /dev/null +++ b/embedding/test/bug449141_page.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> + +<html> + <head> + <title>Nested iframe for bug 449141</title> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> + </head> + <body> + <video src='320x240.ogv'></video> + </body> +</html> + diff --git a/embedding/test/chrome.ini b/embedding/test/chrome.ini new file mode 100644 index 000000000..9954dde59 --- /dev/null +++ b/embedding/test/chrome.ini @@ -0,0 +1,11 @@ +[DEFAULT] +support-files = + 320x240.ogv + bug449141_page.html + bug1170334_iframe.xml + bug1170334_style.css + +[test_bug449141.html] +skip-if = toolkit == 'android' +[test_bug1170334_wbp_xmlstyle.html] +[test_bug1192654.html] diff --git a/embedding/test/mochitest.ini b/embedding/test/mochitest.ini new file mode 100644 index 000000000..79705d2d7 --- /dev/null +++ b/embedding/test/mochitest.ini @@ -0,0 +1,14 @@ +[DEFAULT] +support-files = + bug293834_form.html + +[test_bug293834.html] +skip-if = (toolkit == "cocoa" && e10s) # bug 1252223 +[test_bug499115.html] +[test_nsFind.html] +[test_private_window_from_content.html] +# Next two tests are disabled in e10s because of bug 989501. +[test_window_open_position_constraint.html] +skip-if = toolkit == 'android' +[test_window_open_units.html] +skip-if = toolkit == 'android' diff --git a/embedding/test/moz.build b/embedding/test/moz.build new file mode 100644 index 000000000..1ec8488c9 --- /dev/null +++ b/embedding/test/moz.build @@ -0,0 +1,9 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +MOCHITEST_MANIFESTS += ['mochitest.ini'] +MOCHITEST_CHROME_MANIFESTS += ['chrome.ini'] +BROWSER_CHROME_MANIFESTS += ['browser.ini'] diff --git a/embedding/test/test_bug1170334_wbp_xmlstyle.html b/embedding/test/test_bug1170334_wbp_xmlstyle.html new file mode 100644 index 000000000..4dee9a6ba --- /dev/null +++ b/embedding/test/test_bug1170334_wbp_xmlstyle.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1170334 +--> +<head> + <title>Test for Bug 1170334 (nsWebBrowserPersist vs. XML stylesheets)</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" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1170334">Mozilla Bug 1170334</a> +<p id="display"></p> +<pre id="results"></pre> +<div id="content"> + <iframe src="bug1170334_iframe.xml" id="iframe"></iframe> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript;version=1.7"> +const nameStem="test_bug1170334_" + Date.now(); +const { Ci, Cc, Cu, Cr } = SpecialPowers; +let iframe = document.getElementById("iframe"); + +SimpleTest.waitForExplicitFinish(); + +iframe.onload = function iframe_onload1() { + let doc = iframe.contentDocument; + ok(doc, "iframe content document exists"); + + let wbp = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(Ci.nsIWebBrowserPersist); + let ios = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + let tmp = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("TmpD", Ci.nsIFile); + let tmpFile = tmp.clone(); + tmpFile.append(nameStem + "_iframe.xml"); + let tmpDir = tmp.clone(); + tmpDir.append(nameStem + "_files"); + + // When the document in the iframe is saved, try to load the result. + wbp.progressListener = { + onProgressChange: function(){}, + onLocationChange: function(){}, + onStatusChange: function(){}, + onSecurityChange: function(){}, + onStateChange: function wbp_stateChange(_wbp, _req, state, status) { + if ((state & Ci.nsIWebProgressListener.STATE_STOP) == 0) { + return; + } + is(status, Cr.NS_OK, "nsWebBrowserPersist status"); + iframe.onload = function iframe_onload2() { + let elem = iframe.contentDocument.documentElement; + is(elem && elem.tagName, "thing", "document element tag"); + if (elem && elem.tagName == "parsererror") { + ok(false, "Parser error: " + elem.textContent); + } + + cleanUp(); + SimpleTest.finish(); + }; + iframe.src = ios.newFileURI(tmpFile).spec; + } + }; + wbp.saveDocument(doc, tmpFile, tmpDir, null, 0, 0); + + function cleanUp() { + if (tmpFile.exists()) { + tmpFile.remove(/* recursive: */ false); + } + if (tmpDir.exists()) { + tmpDir.remove(/* recursive: */ true); + } + } +}; +</script> +</pre> +</body> +</html> diff --git a/embedding/test/test_bug1192654.html b/embedding/test/test_bug1192654.html new file mode 100644 index 000000000..f7e97f6a6 --- /dev/null +++ b/embedding/test/test_bug1192654.html @@ -0,0 +1,78 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1192654 +--> +<head> + <title>Test for Bug 1192654 (nsWebBrowser vs. nonpersistable subdocuments)</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" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1192654">Mozilla Bug 1192654</a> +<p id="display"></p> +<pre id="results"></pre> +<div id="content"> + <!-- The outer iframe uses a data URI for simplicity; this would + also work if it were loaded from a support file by relative + URI. The inner iframe (the one nsWebBrowserPersist traverses) + uses a data URI because data: is a non-persistable scheme and + thus triggers the bug. + --> + <iframe src="data:text/html,<iframe%20src=%22data:text/plain,Example%22>" + id="iframe"></iframe> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript;version=1.7"> +const nameStem="test_bug1192654_" + Date.now(); +const { Ci, Cc, Cu, Cr } = SpecialPowers; +let iframe = document.getElementById("iframe"); + +SimpleTest.waitForExplicitFinish(); + +iframe.onload = function iframe_onload1() { + let doc = iframe.contentDocument; + ok(doc, "iframe content document exists"); + + let wbp = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(Ci.nsIWebBrowserPersist); + let tmp = Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("TmpD", Ci.nsIFile); + let tmpFile = tmp.clone(); + tmpFile.append(nameStem + "_iframe.xml"); + let tmpDir = tmp.clone(); + tmpDir.append(nameStem + "_files"); + + wbp.progressListener = { + onProgressChange: function(){}, + onLocationChange: function(){}, + onStatusChange: function(){}, + onSecurityChange: function(){}, + onStateChange: wbp_stateChange, + }; + SimpleTest.registerCleanupFunction(cleanUp); + + wbp.saveDocument(doc, tmpFile, tmpDir, null, 0, 0); + + function wbp_stateChange(_wbp, _req, state, status) { + if ((state & Ci.nsIWebProgressListener.STATE_STOP) == 0) { + return; + } + is(status, Cr.NS_OK, "nsWebBrowserPersist status"); + SimpleTest.finish(); + } + + function cleanUp() { + if (tmpFile.exists()) { + tmpFile.remove(/* recursive: */ false); + } + if (tmpDir.exists()) { + tmpDir.remove(/* recursive: */ true); + } + } +}; +</script> +</pre> +</body> +</html> diff --git a/embedding/test/test_bug293834.html b/embedding/test/test_bug293834.html new file mode 100644 index 000000000..0747bf8b7 --- /dev/null +++ b/embedding/test/test_bug293834.html @@ -0,0 +1,137 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=293834 +--> +<head> + <title>Test for Bug 293834</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=293834">Mozilla Bug 293834</a> +<p id="display"> + +</p> +<pre id="results"></pre> +<div id="content" style="display: none"> + <iframe src="bug293834_form.html" id="source"></iframe> + <br> + <iframe id="dest"></iframe> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +/** Test for Bug 293834 **/ + +var textareas = ["a-textbox", "a-prefilled-textbox"]; +var textboxes = ["a-text", "a-prefilled-text"]; + +function fillform(doc) { + for (var i in textareas) { + doc.getElementById(textareas[i]).textContent += "form state"; + } + for (var i in textboxes) { + doc.getElementById(textboxes[i]).value += "form state"; + } + doc.getElementById('a-checkbox').checked = true; + doc.getElementById("radioa").checked = true; + doc.getElementById("aselect").selectedIndex = 0; +} + +function checkform(doc) { + for (var i in textareas) { + var textContent = doc.getElementById(textareas[i]).textContent; + ok(/form\s+state/m.test(textContent), + "Modified textarea "+textareas[i]+" form state not preserved!"); + } + for (var i in textboxes) { + var value = doc.getElementById(textboxes[i]).value; + ok(/form\s+state/m.test(value), + "Modified textbox "+textboxes[i]+" form state not preserved!"); + } + ok(doc.getElementById('a-checkbox').checked, + "Modified checkbox checked state not preserved!"); + ok(doc.getElementById("radioa").checked, + "Modified radio checked state not preserved!"); + ok(doc.getElementById("aselect").selectedIndex == 0, + "Modified select selected index not preserved"); +} + +const Cc = SpecialPowers.Cc; +const Ci = SpecialPowers.Ci; + +function getTempDir() { + return Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("TmpD", Ci.nsILocalFile); +} + +function getFileContents(aFile) { + const PR_RDONLY = 0x01; + var fileStream = Cc["@mozilla.org/network/file-input-stream;1"] + .createInstance(Ci.nsIFileInputStream); + fileStream.init(aFile, PR_RDONLY, 0400, + Ci.nsIFileInputStream.DELETE_ON_CLOSE + | Ci.nsIFileInputStream.CLOSE_ON_EOF); + var inputStream = Cc["@mozilla.org/scriptableinputstream;1"] + .createInstance(Ci.nsIScriptableInputStream); + inputStream.init(fileStream); + var data = ""; + do { + var str = inputStream.read(inputStream.available()); + data += str; + } while(str.length > 0); + + return data; +} + +function persistDocument(aDoc) { + const nsIWBP = Ci.nsIWebBrowserPersist; + const persistFlags = + nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES + | nsIWBP.PERSIST_FLAGS_FROM_CACHE + | nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION + const encodingFlags = + nsIWBP.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES; + + var ioService = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + + + var file = getTempDir(); + file.append("bug293834-serialized.html"); + + var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(Ci.nsIWebBrowserPersist); + persist.progressListener = null; + persist.persistFlags = persistFlags; + const kWrapColumn = 80; + var folder = getTempDir(); + folder.append("bug293834-serialized"); + persist.saveDocument(aDoc, ioService.newFileURI(file), + folder, + aDoc.contentType, + encodingFlags, kWrapColumn); + return getFileContents(file); +} + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + var srcDoc = document.getElementById('source').contentDocument; + fillform(srcDoc); + checkform(srcDoc); + var serializedString = persistDocument(srcDoc); + + // We can't access file:/// URLs directly for security reasons, + // so we have to parse the serialized content string indirectly + var targetDoc = document.getElementById('dest').contentDocument; + targetDoc.write(serializedString); + + checkform(targetDoc); + SimpleTest.finish(); +}); +</script> +</pre> +</body> +</html> diff --git a/embedding/test/test_bug449141.html b/embedding/test/test_bug449141.html new file mode 100644 index 000000000..0b75ff331 --- /dev/null +++ b/embedding/test/test_bug449141.html @@ -0,0 +1,102 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=449141 +--> +<head> + <title>Test for Bug 449141</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" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=449141">Mozilla Bug 449141</a> +<p id="display"> + +</p> +<pre id="results"></pre> +<div id="content" style="display: none"> + <iframe src="bug449141_page.html" id="source"></iframe> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +/** Test for Bug 449141 **/ + +const Cc = SpecialPowers.Cc; +const Ci = SpecialPowers.Ci; + +function getTempDir() { + return Cc["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("TmpD", Ci.nsILocalFile); +} + +// STATE_STOP from nsIWebProgressListener.idl +const STATE_STOP = 0x00000010; + +var progressListener = { + onProgressChange: function() { + /* Ignore progress callback */ + }, + onStateChange: function(aProgress, aRequest, aStateFlag, aStatus) { + if (aStateFlag & STATE_STOP) { + var dirExists = false; + var videoExists = false; + + var videoFile = getTempDir(); + videoFile.append(this.dirName); + dirExists = videoFile.exists(); + videoFile.append("320x240.ogv"); + videoExists = videoFile.exists(); + this.folder.remove(true); + this.file.remove(false); + ok(dirExists, 'Directory containing video file should be created'); + ok(videoExists, 'Video should be persisted with document'); + SimpleTest.finish(); + } + } +}; + +function persistDocument(aDoc) { + const nsIWBP = Ci.nsIWebBrowserPersist; + const persistFlags = + nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES + | nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION + const encodingFlags = + nsIWBP.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES; + + var ioService = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + + var id = Math.round(Math.random() * 10000); + var dirName = "bug449141_serialized" + id; + progressListener.dirName = dirName; + + var file = getTempDir(); + file.append("bug449141-serialized" + id + ".html"); + + var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(Ci.nsIWebBrowserPersist); + persist.progressListener = progressListener; + persist.persistFlags = persistFlags; + const kWrapColumn = 80; + var folder = getTempDir(); + folder.append(dirName); + progressListener.folder = folder; + progressListener.file = file; + persist.saveDocument(aDoc, ioService.newFileURI(file), + folder, + aDoc.contentType, + encodingFlags, kWrapColumn); +} + +SimpleTest.waitForExplicitFinish(); + +addLoadEvent(function() { + netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect'); + var srcDoc = document.getElementById('source').contentDocument; + persistDocument(srcDoc); +}); +</script> +</pre> +</body> +</html> diff --git a/embedding/test/test_bug499115.html b/embedding/test/test_bug499115.html new file mode 100644 index 000000000..c51dc61a8 --- /dev/null +++ b/embedding/test/test_bug499115.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=499115 +--> +<head> + <title>Test for Bug 499115</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body onload="onLoad();"> + <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=499115">Mozilla Bug 499115</a> + <p id="display"></p> + <div id="content" style="display: none"> + </div> + <pre id="test"> + <script type="application/javascript"> + + /** Test for Bug 499115 **/ + SimpleTest.waitForExplicitFinish(); + + const SEARCH_TEXT="minefield"; + + function getMatches() { + var numMatches = 0; + + var searchRange = document.createRange(); + searchRange.selectNodeContents(document.body); + + var startPoint = searchRange.cloneRange(); + startPoint.collapse(true); + + var endPoint = searchRange.cloneRange(); + endPoint.collapse(false); + + var retRange = null; + var finder = SpecialPowers.Cc["@mozilla.org/embedcomp/rangefind;1"] + .createInstance(SpecialPowers.Ci.nsIFind); + + finder.caseSensitive = false; + + while ((retRange = finder.Find(SEARCH_TEXT, searchRange, + startPoint, endPoint))) { + numMatches++; + + // Start next search from end of current match + startPoint = retRange.cloneRange(); + startPoint.collapse(false); + } + + return numMatches; + } + + function onLoad() { + var matches = getMatches(); + is(matches, 2, "found second match in anonymous content"); + SimpleTest.finish(); + } + </script> + </pre> +<input type="text" value="minefield minefield"></body> +</html> diff --git a/embedding/test/test_nsFind.html b/embedding/test/test_nsFind.html new file mode 100644 index 000000000..5f5a4687a --- /dev/null +++ b/embedding/test/test_nsFind.html @@ -0,0 +1,241 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=450048 +--> +<head> + <meta charset="UTF-8"> + <title>Test for nsFind::Find()</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450048">Mozilla Bug 450048</a> +<p id="display">This is the text to search i<b>n­t</b>o</p> +<p id="quotes">"straight" and “curly” and ‘didn't’ and 'doesn’t'</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 450048 **/ + + // Check nsFind class and its nsIFind interface. + + var rf = SpecialPowers.Cc["@mozilla.org/embedcomp/rangefind;1"] + .getService(SpecialPowers.Ci.nsIFind); + + var display = window.document.getElementById("display"); + var searchRange = window.document.createRange(); + searchRange.setStart(display, 0); + searchRange.setEnd(display, display.childNodes.length); + var startPt = searchRange; + var endPt = searchRange; + + // Check |null| detection on |aPatText| parameter. + try { + rf.Find(null, searchRange, startPt, endPt); + + ok(false, "Missing NS_ERROR_NULL_POINTER exception"); + } catch (e) { + e = SpecialPowers.wrap(e); + if (e.result == SpecialPowers.Cr.NS_ERROR_NULL_POINTER) { + ok(true, null); + } else { + throw e; + } + } + + // Check |null| detection on |aSearchRange| parameter. + try { + rf.Find("", null, startPt, endPt); + + ok(false, "Missing NS_ERROR_ILLEGAL_VALUE exception"); + } catch (e) { + e = SpecialPowers.wrap(e); + if (e.result == SpecialPowers.Cr.NS_ERROR_ILLEGAL_VALUE) { + ok(true, null); + } else { + throw e; + } + } + + // Check |null| detection on |aStartPoint| parameter. + try { + rf.Find("", searchRange, null, endPt); + + ok(false, "Missing NS_ERROR_ILLEGAL_VALUE exception"); + } catch (e) { + e = SpecialPowers.wrap(e); + if (e.result == SpecialPowers.Cr.NS_ERROR_ILLEGAL_VALUE) { + ok(true, null); + } else { + throw e; + } + } + + // Check |null| detection on |aEndPoint| parameter. + try { + rf.Find("", searchRange, startPt, null); + + ok(false, "Missing NS_ERROR_ILLEGAL_VALUE exception"); + } catch (e) { + e = SpecialPowers.wrap(e); + if (e.result == SpecialPowers.Cr.NS_ERROR_ILLEGAL_VALUE) { + ok(true, null); + } else { + throw e; + } + } + + var searchValue, retRange; + + rf.findBackwards = false; + + rf.caseSensitive = false; + + searchValue = "TexT"; + retRange = rf.Find(searchValue, searchRange, startPt, endPt); + ok(retRange, "\"" + searchValue + "\" not found (not caseSensitive)"); + + rf.caseSensitive = true; + + // searchValue = "TexT"; + retRange = rf.Find(searchValue, searchRange, startPt, endPt); + ok(!retRange, "\"" + searchValue + "\" found (caseSensitive)"); + + searchValue = "text"; + retRange = rf.Find(searchValue, searchRange, startPt, endPt); + ok(retRange, "\"" + searchValue + "\" not found"); + + // Matches |i<b>n­t</b>o|. + searchValue = "into"; + retRange = rf.Find(searchValue, searchRange, startPt, endPt); + ok(retRange, "\"" + searchValue + "\" not found"); + + // Matches inside |search|. + searchValue = "ear"; + retRange = rf.Find(searchValue, searchRange, startPt, endPt); + ok(retRange, "\"" + searchValue + "\" not found"); + + // Set new start point (to end of last search). + startPt = retRange.endContainer.ownerDocument.createRange(); + startPt.setStart(retRange.endContainer, retRange.endOffset); + startPt.setEnd(retRange.endContainer, retRange.endOffset); + + searchValue = "t"; + retRange = rf.Find(searchValue, searchRange, startPt, endPt); + ok(retRange, "\"" + searchValue + "\" not found (forward)"); + + searchValue = "the"; + retRange = rf.Find(searchValue, searchRange, startPt, endPt); + ok(!retRange, "\"" + searchValue + "\" found (forward)"); + + rf.findBackwards = true; + + // searchValue = "the"; + retRange = rf.Find(searchValue, searchRange, startPt, endPt); + ok(retRange, "\"" + searchValue + "\" not found (backward)"); + + + // Curly quotes and straight quotes should match. + + rf.caseSensitive = false; + rf.findBackwards = false; + + function find(node, searchValue) { + var range = document.createRange(); + range.setStart(node, 0); + range.setEnd(node, node.childNodes.length); + return rf.Find(searchValue, range, range, range); + } + + function assertFound(node, searchValue) { + ok(find(node, searchValue), "\"" + searchValue + "\" not found"); + } + + function assertNotFound(node, searchValue) { + ok(!find(node, searchValue), "\"" + searchValue + "\" found"); + } + + var quotes = document.getElementById("quotes"); + + assertFound(quotes, "\"straight\""); + assertFound(quotes, "\u201Cstraight\u201D"); + + assertNotFound(quotes, "'straight'"); + assertNotFound(quotes, "\u2018straight\u2019"); + assertNotFound(quotes, "\u2019straight\u2018"); + assertNotFound(quotes, ".straight."); + + assertFound(quotes, "\"curly\""); + assertFound(quotes, "\u201Ccurly\u201D"); + + assertNotFound(quotes, "'curly'"); + assertNotFound(quotes, "\u2018curly\u2019"); + assertNotFound(quotes, ".curly."); + + assertFound(quotes, "didn't"); + assertFound(quotes, "didn\u2018t"); + assertFound(quotes, "didn\u2019t"); + + assertNotFound(quotes, "didnt"); + assertNotFound(quotes, "didn t"); + assertNotFound(quotes, "didn.t"); + + assertFound(quotes, "'didn't'"); + assertFound(quotes, "'didn\u2018t'"); + assertFound(quotes, "'didn\u2019t'"); + assertFound(quotes, "\u2018didn't\u2019"); + assertFound(quotes, "\u2019didn't\u2018"); + assertFound(quotes, "\u2018didn't\u2018"); + assertFound(quotes, "\u2019didn't\u2019"); + assertFound(quotes, "\u2018didn\u2019t\u2019"); + assertFound(quotes, "\u2019didn\u2018t\u2019"); + assertFound(quotes, "\u2018didn\u2019t\u2018"); + + assertNotFound(quotes, "\"didn't\""); + assertNotFound(quotes, "\u201Cdidn't\u201D"); + + assertFound(quotes, "doesn't"); + assertFound(quotes, "doesn\u2018t"); + assertFound(quotes, "doesn\u2019t"); + + assertNotFound(quotes, "doesnt"); + assertNotFound(quotes, "doesn t"); + assertNotFound(quotes, "doesn.t"); + + assertFound(quotes, "'doesn't'"); + assertFound(quotes, "'doesn\u2018t'"); + assertFound(quotes, "'doesn\u2019t'"); + assertFound(quotes, "\u2018doesn't\u2019"); + assertFound(quotes, "\u2019doesn't\u2018"); + assertFound(quotes, "\u2018doesn't\u2018"); + assertFound(quotes, "\u2019doesn't\u2019"); + assertFound(quotes, "\u2018doesn\u2019t\u2019"); + assertFound(quotes, "\u2019doesn\u2018t\u2019"); + assertFound(quotes, "\u2018doesn\u2019t\u2018"); + + assertNotFound(quotes, "\"doesn't\""); + assertNotFound(quotes, "\u201Cdoesn't\u201D"); + + // Curly quotes and straight quotes should not match. + rf.caseSensitive = true; + + assertFound(quotes, "\"straight\""); + assertNotFound(quotes, "\u201Cstraight\u201D"); + + assertNotFound(quotes, "\"curly\""); + assertFound(quotes, "\u201Ccurly\u201D"); + + assertFound(quotes, "\u2018didn't\u2019"); + assertNotFound(quotes, "'didn't'"); + + assertFound(quotes, "'doesn\u2019t'"); + assertNotFound(quotes, "'doesn\u2018t'"); + assertNotFound(quotes, "'doesn't'"); +</script> +</pre> +</body> +</html> diff --git a/embedding/test/test_private_window_from_content.html b/embedding/test/test_private_window_from_content.html new file mode 100644 index 000000000..4b64fb232 --- /dev/null +++ b/embedding/test/test_private_window_from_content.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> +<script> + // Make sure that we cannot open private browsing windows from unprivileged content + var win = window.open("about:blank", "_blank", "private"); + ok(!SpecialPowers.isContentWindowPrivate(win)); + win.close(); + // Also, make sure that passing non-private doesn't make any difference either + win = window.open("about:blank", "_blank", "non-private"); + ok(!SpecialPowers.isContentWindowPrivate(win)); + win.close(); +</script> diff --git a/embedding/test/test_window_open_position_constraint.html b/embedding/test/test_window_open_position_constraint.html new file mode 100644 index 000000000..d2c777c74 --- /dev/null +++ b/embedding/test/test_window_open_position_constraint.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Bug 978847</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=978847">Mozilla Bug 978847</a> +<p id="display"></p> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +SimpleTest.waitForFocus(function() { + var x; + var y; + + // window should be constrained to the screen rect, + // which we assume is less than 10000px square + var w1 = window.open("about:blank", "", "left=16000,top=16000,width=100,height=100"); + SimpleTest.waitForFocus(function() { + ok(w1.screenX < 10000 && w1.screenY < 10000, + "window should not be opened off-screen: got location " + + w1.screenX + "," + w1.screenY); + x = w1.screenX; + y = w1.screenY; + w1.close(); + + // larger window dimensions should result in a shift of the constrained location + var w2 = window.open("about:blank", "", "left=16000,top=16000,width=150,height=150"); + SimpleTest.waitForFocus(function() { + ok(w2.screenX == x - 50 && w2.screenY == y - 50, + "constrained window position did not depend on size as expected: got " + + w2.screenX + "," + w2.screenY + ", expected " + (x - 50) + "," + (y - 50)); + w2.close(); + + // now try with coordinates that are close to MAXINT, + // so adding width/height risks 32-bit integer overflow + var w3 = window.open("about:blank", "", "left=2147483600,top=2147483600,width=100,height=100"); + SimpleTest.waitForFocus(function() { + ok(w3.screenX < 10000 && w3.screenY < 10000, + "window should not be opened off-screen: got location " + + w3.screenX + "," + w3.screenY); + w3.close(); + SimpleTest.finish(); + }, w3, true); + }, w2, true); + }, w1, true); +}, window, false); + +</script> +</pre> +</body> +</html> diff --git a/embedding/test/test_window_open_units.html b/embedding/test/test_window_open_units.html new file mode 100644 index 000000000..a27a61966 --- /dev/null +++ b/embedding/test/test_window_open_units.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for Bug 594140</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=594140">Mozilla Bug 594140</a> +<p id="display"></p> +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +SimpleTest.waitForExplicitFinish(); + +SimpleTest.waitForFocus(function() { + SpecialPowers.setFullZoom(window, 2); + + var p = window; + var w = window.open("about:blank", "", "width=200,height=100"); + SimpleTest.waitForFocus(function() { + ok(w.innerWidth <= 402 && w.innerWidth >= 398, + "width (" + w.innerWidth + ") should be around twice what was requested (200)"); + ok(w.innerHeight <= 202 && w.innerHeight >= 198, + "height (" + w.innerHeight + ") should be around twice what was requested (100)"); + + SpecialPowers.setFullZoom(window, 1); + w.close(); + SimpleTest.finish(); + }, w, true); +}, window, false); + +</script> +</pre> +</body> +</html> diff --git a/embedding/tests/unit/test_wwauthpromptfactory.js b/embedding/tests/unit/test_wwauthpromptfactory.js new file mode 100644 index 000000000..358e5ca6d --- /dev/null +++ b/embedding/tests/unit/test_wwauthpromptfactory.js @@ -0,0 +1,67 @@ +var Cc = Components.classes; +var Ci = Components.interfaces; + +var authPromptRequestReceived; + +const tPFCID = Components.ID("{749e62f4-60ae-4569-a8a2-de78b649660f}"); +const tPFContract = "@mozilla.org/passwordmanager/authpromptfactory;1"; + +/* + * TestPromptFactory + * + * Implements nsIPromptFactory + */ +var TestPromptFactory = { + QueryInterface: function tPF_qi(iid) { + if (iid.equals(Ci.nsISupports) || + iid.equals(Ci.nsIFactory) || + iid.equals(Ci.nsIPromptFactory)) + return this; + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + createInstance: function tPF_ci(outer, iid) { + if (outer) + throw Components.results.NS_ERROR_NO_AGGREGATION; + return this.QueryInterface(iid); + }, + + lockFactory: function tPF_lockf(lock) { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + + getPrompt : function tPF_getPrompt(aWindow, aIID) { + if (aIID.equals(Ci.nsIAuthPrompt) || + aIID.equals(Ci.nsIAuthPrompt2)) { + authPromptRequestReceived = true; + return {}; + } + + throw Components.results.NS_ERROR_NO_INTERFACE; + } +}; // end of TestPromptFactory implementation + +/* + * The tests + */ +function run_test() { + Components.manager.nsIComponentRegistrar.registerFactory(tPFCID, + "TestPromptFactory", tPFContract, TestPromptFactory); + + // Make sure that getting both nsIAuthPrompt and nsIAuthPrompt2 works + // (these should work independently of whether the application has + // nsIPromptService2) + var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(); + + authPromptRequestReceived = false; + + do_check_neq(ww.nsIPromptFactory.getPrompt(null, Ci.nsIAuthPrompt), null); + + do_check_true(authPromptRequestReceived); + + authPromptRequestReceived = false; + + do_check_neq(ww.nsIPromptFactory.getPrompt(null, Ci.nsIAuthPrompt2), null); + + do_check_true(authPromptRequestReceived); +} diff --git a/embedding/tests/unit/test_wwpromptfactory.js b/embedding/tests/unit/test_wwpromptfactory.js new file mode 100644 index 000000000..d595988c9 --- /dev/null +++ b/embedding/tests/unit/test_wwpromptfactory.js @@ -0,0 +1,24 @@ +function run_test() { + // Make sure that getting both nsIAuthPrompt and nsIAuthPrompt2 works + // (these should work independently of whether the application has + // nsIPromptService2) + var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"] + .getService(); + + var prompt; + + prompt = ww.nsIWindowWatcher.getNewPrompter(null); + do_check_neq(prompt, null); + prompt = ww.nsIWindowWatcher.getNewAuthPrompter(null); + do_check_neq(prompt, null); + + prompt = ww.nsIPromptFactory.getPrompt(null, + Components.interfaces.nsIPrompt); + do_check_neq(prompt, null); + prompt = ww.nsIPromptFactory.getPrompt(null, + Components.interfaces.nsIAuthPrompt); + do_check_neq(prompt, null); + prompt = ww.nsIPromptFactory.getPrompt(null, + Components.interfaces.nsIAuthPrompt2); + do_check_neq(prompt, null); +} diff --git a/embedding/tests/unit/xpcshell.ini b/embedding/tests/unit/xpcshell.ini new file mode 100644 index 000000000..b3c55a1e7 --- /dev/null +++ b/embedding/tests/unit/xpcshell.ini @@ -0,0 +1,6 @@ +[DEFAULT] +head = +tail = + +[test_wwauthpromptfactory.js] +[test_wwpromptfactory.js] |