summaryrefslogtreecommitdiffstats
path: root/widget/nsBaseFilePicker.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--widget/nsBaseFilePicker.cpp496
1 files changed, 496 insertions, 0 deletions
diff --git a/widget/nsBaseFilePicker.cpp b/widget/nsBaseFilePicker.cpp
new file mode 100644
index 0000000000..53be3d80b7
--- /dev/null
+++ b/widget/nsBaseFilePicker.cpp
@@ -0,0 +1,496 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsPIDOMWindow.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWidget.h"
+
+#include "nsIStringBundle.h"
+#include "nsString.h"
+#include "nsCOMArray.h"
+#include "nsIFile.h"
+#include "nsEnumeratorUtils.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Components.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "WidgetUtils.h"
+#include "nsSimpleEnumerator.h"
+#include "nsThreadUtils.h"
+#include "nsContentUtils.h"
+
+#include "nsBaseFilePicker.h"
+
+using namespace mozilla::widget;
+using namespace mozilla::dom;
+using mozilla::ErrorResult;
+
+#define FILEPICKER_TITLES "chrome://global/locale/filepicker.properties"
+#define FILEPICKER_FILTERS "chrome://global/content/filepicker.properties"
+
+namespace {
+
+nsresult LocalFileToDirectoryOrBlob(nsPIDOMWindowInner* aWindow,
+ bool aIsDirectory, nsIFile* aFile,
+ nsISupports** aResult) {
+ MOZ_ASSERT(aWindow);
+
+ if (aIsDirectory) {
+#ifdef DEBUG
+ bool isDir;
+ aFile->IsDirectory(&isDir);
+ MOZ_ASSERT(isDir);
+#endif
+
+ RefPtr<Directory> directory = Directory::Create(aWindow->AsGlobal(), aFile);
+ MOZ_ASSERT(directory);
+
+ directory.forget(aResult);
+ return NS_OK;
+ }
+
+ RefPtr<File> file = File::CreateFromFile(aWindow->AsGlobal(), aFile);
+ if (NS_WARN_IF(!file)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ file.forget(aResult);
+ return NS_OK;
+}
+
+} // anonymous namespace
+
+#ifndef XP_WIN
+/**
+ * A runnable to dispatch from the main thread to the main thread to display
+ * the file picker while letting the showAsync method return right away.
+ *
+ * Not needed on Windows, where nsFilePicker::Open() is fully async.
+ */
+class nsBaseFilePicker::AsyncShowFilePicker : public mozilla::Runnable {
+ public:
+ AsyncShowFilePicker(nsBaseFilePicker* aFilePicker,
+ nsIFilePickerShownCallback* aCallback)
+ : mozilla::Runnable("AsyncShowFilePicker"),
+ mFilePicker(aFilePicker),
+ mCallback(aCallback) {}
+
+ NS_IMETHOD Run() override {
+ NS_ASSERTION(NS_IsMainThread(),
+ "AsyncShowFilePicker should be on the main thread!");
+
+ // It's possible that some widget implementations require GUI operations
+ // to be on the main thread, so that's why we're not dispatching to another
+ // thread and calling back to the main after it's done.
+ nsIFilePicker::ResultCode result = nsIFilePicker::returnCancel;
+ nsresult rv = mFilePicker->Show(&result);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("FilePicker's Show() implementation failed!");
+ }
+
+ if (mCallback) {
+ mCallback->Done(result);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsBaseFilePicker> mFilePicker;
+ RefPtr<nsIFilePickerShownCallback> mCallback;
+};
+#endif
+
+class nsBaseFilePickerEnumerator : public nsSimpleEnumerator {
+ public:
+ nsBaseFilePickerEnumerator(nsPIDOMWindowOuter* aParent,
+ nsISimpleEnumerator* iterator,
+ nsIFilePicker::Mode aMode)
+ : mIterator(iterator),
+ mParent(aParent->GetCurrentInnerWindow()),
+ mMode(aMode) {}
+
+ const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); }
+
+ NS_IMETHOD
+ GetNext(nsISupports** aResult) override {
+ nsCOMPtr<nsISupports> tmp;
+ nsresult rv = mIterator->GetNext(getter_AddRefs(tmp));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!tmp) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> localFile = do_QueryInterface(tmp);
+ if (!localFile) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mParent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return LocalFileToDirectoryOrBlob(
+ mParent, mMode == nsIFilePicker::modeGetFolder, localFile, aResult);
+ }
+
+ NS_IMETHOD
+ HasMoreElements(bool* aResult) override {
+ return mIterator->HasMoreElements(aResult);
+ }
+
+ private:
+ nsCOMPtr<nsISimpleEnumerator> mIterator;
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ nsIFilePicker::Mode mMode;
+};
+
+nsBaseFilePicker::nsBaseFilePicker()
+ : mAddToRecentDocs(true), mMode(nsIFilePicker::modeOpen) {}
+
+nsBaseFilePicker::~nsBaseFilePicker() = default;
+
+NS_IMETHODIMP nsBaseFilePicker::Init(
+ mozIDOMWindowProxy* aParent, const nsAString& aTitle,
+ nsIFilePicker::Mode aMode,
+ mozilla::dom::BrowsingContext* aBrowsingContext) {
+ MOZ_ASSERT(aParent,
+ "Null parent passed to filepicker, no file "
+ "picker for you!");
+
+ mParent = nsPIDOMWindowOuter::From(aParent);
+
+ nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(mParent);
+ NS_ENSURE_TRUE(widget, NS_ERROR_FAILURE);
+
+ mBrowsingContext = aBrowsingContext;
+ mMode = aMode;
+ InitNative(widget, aTitle);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::IsModeSupported(nsIFilePicker::Mode aMode, JSContext* aCx,
+ Promise** aPromise) {
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aPromise);
+
+ nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!globalObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult result;
+ RefPtr<Promise> promise = Promise::Create(globalObject, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return result.StealNSResult();
+ }
+
+ promise->MaybeResolve(true);
+ promise.forget(aPromise);
+
+ return NS_OK;
+}
+
+#ifndef XP_WIN
+NS_IMETHODIMP
+nsBaseFilePicker::Open(nsIFilePickerShownCallback* aCallback) {
+ if (MaybeBlockFilePicker(aCallback)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIRunnable> filePickerEvent =
+ new AsyncShowFilePicker(this, aCallback);
+ return NS_DispatchToMainThread(filePickerEvent);
+}
+#endif
+
+NS_IMETHODIMP
+nsBaseFilePicker::Close() {
+ NS_WARNING("Unimplemented nsFilePicker::Close");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::AppendFilters(int32_t aFilterMask) {
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::components::StringBundle::Service();
+ if (!stringService) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIStringBundle> titleBundle, filterBundle;
+
+ nsresult rv = stringService->CreateBundle(FILEPICKER_TITLES,
+ getter_AddRefs(titleBundle));
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+
+ rv = stringService->CreateBundle(FILEPICKER_FILTERS,
+ getter_AddRefs(filterBundle));
+ if (NS_FAILED(rv)) return NS_ERROR_FAILURE;
+
+ nsAutoString title;
+ nsAutoString filter;
+
+ if (aFilterMask & filterAll) {
+ titleBundle->GetStringFromName("allTitle", title);
+ filterBundle->GetStringFromName("allFilter", filter);
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterHTML) {
+ titleBundle->GetStringFromName("htmlTitle", title);
+ filterBundle->GetStringFromName("htmlFilter", filter);
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterText) {
+ titleBundle->GetStringFromName("textTitle", title);
+ filterBundle->GetStringFromName("textFilter", filter);
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterImages) {
+ titleBundle->GetStringFromName("imageTitle", title);
+ filterBundle->GetStringFromName("imageFilter", filter);
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterAudio) {
+ titleBundle->GetStringFromName("audioTitle", title);
+ filterBundle->GetStringFromName("audioFilter", filter);
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterVideo) {
+ titleBundle->GetStringFromName("videoTitle", title);
+ filterBundle->GetStringFromName("videoFilter", filter);
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterXML) {
+ titleBundle->GetStringFromName("xmlTitle", title);
+ filterBundle->GetStringFromName("xmlFilter", filter);
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterXUL) {
+ titleBundle->GetStringFromName("xulTitle", title);
+ filterBundle->GetStringFromName("xulFilter", filter);
+ AppendFilter(title, filter);
+ }
+ if (aFilterMask & filterApps) {
+ titleBundle->GetStringFromName("appsTitle", title);
+ // Pass the magic string "..apps" to the platform filepicker, which it
+ // should recognize and do the correct platform behavior for.
+ AppendFilter(title, u"..apps"_ns);
+ }
+ if (aFilterMask & filterPDF) {
+ titleBundle->GetStringFromName("pdfTitle", title);
+ filterBundle->GetStringFromName("pdfFilter", filter);
+ AppendFilter(title, filter);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseFilePicker::AppendRawFilter(const nsAString& aFilter) {
+ mRawFilters.AppendElement(aFilter);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseFilePicker::GetCapture(
+ nsIFilePicker::CaptureTarget* aCapture) {
+ *aCapture = nsIFilePicker::CaptureTarget::captureNone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseFilePicker::SetCapture(
+ nsIFilePicker::CaptureTarget aCapture) {
+ return NS_OK;
+}
+
+// Set the filter index
+NS_IMETHODIMP nsBaseFilePicker::GetFilterIndex(int32_t* aFilterIndex) {
+ *aFilterIndex = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseFilePicker::SetFilterIndex(int32_t aFilterIndex) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseFilePicker::GetFiles(nsISimpleEnumerator** aFiles) {
+ NS_ENSURE_ARG_POINTER(aFiles);
+ nsCOMArray<nsIFile> files;
+ nsresult rv;
+
+ // if we get into the base class, the platform
+ // doesn't implement GetFiles() yet.
+ // so we fake it.
+ nsCOMPtr<nsIFile> file;
+ rv = GetFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ files.AppendObject(file);
+
+ return NS_NewArrayEnumerator(aFiles, files, NS_GET_IID(nsIFile));
+}
+
+// Set the display directory
+NS_IMETHODIMP nsBaseFilePicker::SetDisplayDirectory(nsIFile* aDirectory) {
+ // if displaySpecialDirectory has been previously called, let's abort this
+ // operation.
+ if (!mDisplaySpecialDirectory.IsEmpty()) {
+ return NS_OK;
+ }
+
+ if (!aDirectory) {
+ mDisplayDirectory = nullptr;
+ return NS_OK;
+ }
+ nsCOMPtr<nsIFile> directory;
+ nsresult rv = aDirectory->Clone(getter_AddRefs(directory));
+ if (NS_FAILED(rv)) return rv;
+ mDisplayDirectory = directory;
+ return NS_OK;
+}
+
+// Get the display directory
+NS_IMETHODIMP nsBaseFilePicker::GetDisplayDirectory(nsIFile** aDirectory) {
+ *aDirectory = nullptr;
+
+ // if displaySpecialDirectory has been previously called, let's abort this
+ // operation.
+ if (!mDisplaySpecialDirectory.IsEmpty()) {
+ return NS_OK;
+ }
+
+ if (!mDisplayDirectory) return NS_OK;
+ nsCOMPtr<nsIFile> directory;
+ nsresult rv = mDisplayDirectory->Clone(getter_AddRefs(directory));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ directory.forget(aDirectory);
+ return NS_OK;
+}
+
+// Set the display special directory
+NS_IMETHODIMP nsBaseFilePicker::SetDisplaySpecialDirectory(
+ const nsAString& aDirectory) {
+ // if displayDirectory has been previously called, let's abort this operation.
+ if (mDisplayDirectory && mDisplaySpecialDirectory.IsEmpty()) {
+ return NS_OK;
+ }
+
+ mDisplaySpecialDirectory = aDirectory;
+ if (mDisplaySpecialDirectory.IsEmpty()) {
+ mDisplayDirectory = nullptr;
+ return NS_OK;
+ }
+
+ return ResolveSpecialDirectory(aDirectory);
+}
+
+bool nsBaseFilePicker::MaybeBlockFilePicker(
+ nsIFilePickerShownCallback* aCallback) {
+ if (!mozilla::StaticPrefs::widget_disable_file_pickers()) {
+ return false;
+ }
+
+ if (aCallback) {
+ // File pickers are disabled, so we answer the callback with returnCancel.
+ aCallback->Done(nsIFilePicker::returnCancel);
+ }
+ if (mBrowsingContext) {
+ RefPtr<Element> topFrameElement = mBrowsingContext->GetTopFrameElement();
+ if (topFrameElement) {
+ // Dispatch an event that the frontend may use.
+ nsContentUtils::DispatchEventOnlyToChrome(
+ topFrameElement->OwnerDoc(), topFrameElement, u"FilePickerBlocked"_ns,
+ mozilla::CanBubble::eYes, mozilla::Cancelable::eNo);
+ }
+ }
+
+ return true;
+}
+
+nsresult nsBaseFilePicker::ResolveSpecialDirectory(
+ const nsAString& aSpecialDirectory) {
+ // Only perform special-directory name resolution in the parent process.
+ // (Subclasses of `nsBaseFilePicker` used in other processes must override
+ // this function.)
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return NS_GetSpecialDirectory(NS_ConvertUTF16toUTF8(aSpecialDirectory).get(),
+ getter_AddRefs(mDisplayDirectory));
+}
+
+// Get the display special directory
+NS_IMETHODIMP nsBaseFilePicker::GetDisplaySpecialDirectory(
+ nsAString& aDirectory) {
+ aDirectory = mDisplaySpecialDirectory;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::GetAddToRecentDocs(bool* aFlag) {
+ *aFlag = mAddToRecentDocs;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::SetAddToRecentDocs(bool aFlag) {
+ mAddToRecentDocs = aFlag;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::GetMode(nsIFilePicker::Mode* aMode) {
+ *aMode = mMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::SetOkButtonLabel(const nsAString& aLabel) {
+ mOkButtonLabel = aLabel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::GetOkButtonLabel(nsAString& aLabel) {
+ aLabel = mOkButtonLabel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::GetDomFileOrDirectory(nsISupports** aValue) {
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = GetFile(getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!localFile) {
+ *aValue = nullptr;
+ return NS_OK;
+ }
+
+ auto* innerParent = mParent ? mParent->GetCurrentInnerWindow() : nullptr;
+
+ if (!innerParent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return LocalFileToDirectoryOrBlob(
+ innerParent, mMode == nsIFilePicker::modeGetFolder, localFile, aValue);
+}
+
+NS_IMETHODIMP
+nsBaseFilePicker::GetDomFileOrDirectoryEnumerator(
+ nsISimpleEnumerator** aValue) {
+ nsCOMPtr<nsISimpleEnumerator> iter;
+ nsresult rv = GetFiles(getter_AddRefs(iter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsBaseFilePickerEnumerator> retIter =
+ new nsBaseFilePickerEnumerator(mParent, iter, mMode);
+
+ retIter.forget(aValue);
+ return NS_OK;
+}