1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "nsPIDOMWindow.h"
9 #include "nsIInterfaceRequestorUtils.h"
10 #include "nsIWidget.h"
12 #include "nsIStringBundle.h"
14 #include "nsCOMArray.h"
16 #include "nsEnumeratorUtils.h"
17 #include "mozilla/dom/Directory.h"
18 #include "mozilla/dom/File.h"
19 #include "mozilla/dom/Promise.h"
20 #include "mozilla/Components.h"
21 #include "WidgetUtils.h"
22 #include "nsSimpleEnumerator.h"
23 #include "nsThreadUtils.h"
25 #include "nsBaseFilePicker.h"
27 using namespace mozilla::widget
;
28 using namespace mozilla::dom
;
29 using mozilla::ErrorResult
;
31 #define FILEPICKER_TITLES "chrome://global/locale/filepicker.properties"
32 #define FILEPICKER_FILTERS "chrome://global/content/filepicker.properties"
36 nsresult
LocalFileToDirectoryOrBlob(nsPIDOMWindowInner
* aWindow
,
37 bool aIsDirectory
, nsIFile
* aFile
,
38 nsISupports
** aResult
) {
44 aFile
->IsDirectory(&isDir
);
48 RefPtr
<Directory
> directory
= Directory::Create(aWindow
->AsGlobal(), aFile
);
49 MOZ_ASSERT(directory
);
51 directory
.forget(aResult
);
55 RefPtr
<File
> file
= File::CreateFromFile(aWindow
->AsGlobal(), aFile
);
56 if (NS_WARN_IF(!file
)) {
57 return NS_ERROR_FAILURE
;
64 } // anonymous namespace
68 * A runnable to dispatch from the main thread to the main thread to display
69 * the file picker while letting the showAsync method return right away.
71 * Not needed on Windows, where nsFilePicker::Open() is fully async.
73 class nsBaseFilePicker::AsyncShowFilePicker
: public mozilla::Runnable
{
75 AsyncShowFilePicker(nsBaseFilePicker
* aFilePicker
,
76 nsIFilePickerShownCallback
* aCallback
)
77 : mozilla::Runnable("AsyncShowFilePicker"),
78 mFilePicker(aFilePicker
),
79 mCallback(aCallback
) {}
81 NS_IMETHOD
Run() override
{
82 NS_ASSERTION(NS_IsMainThread(),
83 "AsyncShowFilePicker should be on the main thread!");
85 // It's possible that some widget implementations require GUI operations
86 // to be on the main thread, so that's why we're not dispatching to another
87 // thread and calling back to the main after it's done.
88 nsIFilePicker::ResultCode result
= nsIFilePicker::returnCancel
;
89 nsresult rv
= mFilePicker
->Show(&result
);
91 NS_ERROR("FilePicker's Show() implementation failed!");
95 mCallback
->Done(result
);
101 RefPtr
<nsBaseFilePicker
> mFilePicker
;
102 RefPtr
<nsIFilePickerShownCallback
> mCallback
;
106 class nsBaseFilePickerEnumerator
: public nsSimpleEnumerator
{
108 nsBaseFilePickerEnumerator(nsPIDOMWindowOuter
* aParent
,
109 nsISimpleEnumerator
* iterator
,
110 nsIFilePicker::Mode aMode
)
111 : mIterator(iterator
),
112 mParent(aParent
->GetCurrentInnerWindow()),
115 const nsID
& DefaultInterface() override
{ return NS_GET_IID(nsIFile
); }
118 GetNext(nsISupports
** aResult
) override
{
119 nsCOMPtr
<nsISupports
> tmp
;
120 nsresult rv
= mIterator
->GetNext(getter_AddRefs(tmp
));
121 NS_ENSURE_SUCCESS(rv
, rv
);
127 nsCOMPtr
<nsIFile
> localFile
= do_QueryInterface(tmp
);
129 return NS_ERROR_FAILURE
;
133 return NS_ERROR_FAILURE
;
136 return LocalFileToDirectoryOrBlob(
137 mParent
, mMode
== nsIFilePicker::modeGetFolder
, localFile
, aResult
);
141 HasMoreElements(bool* aResult
) override
{
142 return mIterator
->HasMoreElements(aResult
);
146 nsCOMPtr
<nsISimpleEnumerator
> mIterator
;
147 nsCOMPtr
<nsPIDOMWindowInner
> mParent
;
148 nsIFilePicker::Mode mMode
;
151 nsBaseFilePicker::nsBaseFilePicker()
152 : mAddToRecentDocs(true), mMode(nsIFilePicker::modeOpen
) {}
154 nsBaseFilePicker::~nsBaseFilePicker() = default;
156 NS_IMETHODIMP
nsBaseFilePicker::Init(mozIDOMWindowProxy
* aParent
,
157 const nsAString
& aTitle
,
158 nsIFilePicker::Mode aMode
) {
160 "Null parent passed to filepicker, no file "
163 mParent
= nsPIDOMWindowOuter::From(aParent
);
165 nsCOMPtr
<nsIWidget
> widget
= WidgetUtils::DOMWindowToWidget(mParent
);
166 NS_ENSURE_TRUE(widget
, NS_ERROR_FAILURE
);
169 InitNative(widget
, aTitle
);
175 nsBaseFilePicker::IsModeSupported(nsIFilePicker::Mode aMode
, JSContext
* aCx
,
176 Promise
** aPromise
) {
178 MOZ_ASSERT(aPromise
);
180 nsIGlobalObject
* globalObject
= xpc::CurrentNativeGlobal(aCx
);
181 if (NS_WARN_IF(!globalObject
)) {
182 return NS_ERROR_FAILURE
;
186 RefPtr
<Promise
> promise
= Promise::Create(globalObject
, result
);
187 if (NS_WARN_IF(result
.Failed())) {
188 return result
.StealNSResult();
191 promise
->MaybeResolve(true);
192 promise
.forget(aPromise
);
199 nsBaseFilePicker::Open(nsIFilePickerShownCallback
* aCallback
) {
200 nsCOMPtr
<nsIRunnable
> filePickerEvent
=
201 new AsyncShowFilePicker(this, aCallback
);
202 return NS_DispatchToMainThread(filePickerEvent
);
207 nsBaseFilePicker::Close() {
208 NS_WARNING("Unimplemented nsFilePicker::Close");
213 nsBaseFilePicker::AppendFilters(int32_t aFilterMask
) {
214 nsCOMPtr
<nsIStringBundleService
> stringService
=
215 mozilla::components::StringBundle::Service();
216 if (!stringService
) return NS_ERROR_FAILURE
;
218 nsCOMPtr
<nsIStringBundle
> titleBundle
, filterBundle
;
220 nsresult rv
= stringService
->CreateBundle(FILEPICKER_TITLES
,
221 getter_AddRefs(titleBundle
));
222 if (NS_FAILED(rv
)) return NS_ERROR_FAILURE
;
224 rv
= stringService
->CreateBundle(FILEPICKER_FILTERS
,
225 getter_AddRefs(filterBundle
));
226 if (NS_FAILED(rv
)) return NS_ERROR_FAILURE
;
231 if (aFilterMask
& filterAll
) {
232 titleBundle
->GetStringFromName("allTitle", title
);
233 filterBundle
->GetStringFromName("allFilter", filter
);
234 AppendFilter(title
, filter
);
236 if (aFilterMask
& filterHTML
) {
237 titleBundle
->GetStringFromName("htmlTitle", title
);
238 filterBundle
->GetStringFromName("htmlFilter", filter
);
239 AppendFilter(title
, filter
);
241 if (aFilterMask
& filterText
) {
242 titleBundle
->GetStringFromName("textTitle", title
);
243 filterBundle
->GetStringFromName("textFilter", filter
);
244 AppendFilter(title
, filter
);
246 if (aFilterMask
& filterImages
) {
247 titleBundle
->GetStringFromName("imageTitle", title
);
248 filterBundle
->GetStringFromName("imageFilter", filter
);
249 AppendFilter(title
, filter
);
251 if (aFilterMask
& filterAudio
) {
252 titleBundle
->GetStringFromName("audioTitle", title
);
253 filterBundle
->GetStringFromName("audioFilter", filter
);
254 AppendFilter(title
, filter
);
256 if (aFilterMask
& filterVideo
) {
257 titleBundle
->GetStringFromName("videoTitle", title
);
258 filterBundle
->GetStringFromName("videoFilter", filter
);
259 AppendFilter(title
, filter
);
261 if (aFilterMask
& filterXML
) {
262 titleBundle
->GetStringFromName("xmlTitle", title
);
263 filterBundle
->GetStringFromName("xmlFilter", filter
);
264 AppendFilter(title
, filter
);
266 if (aFilterMask
& filterXUL
) {
267 titleBundle
->GetStringFromName("xulTitle", title
);
268 filterBundle
->GetStringFromName("xulFilter", filter
);
269 AppendFilter(title
, filter
);
271 if (aFilterMask
& filterApps
) {
272 titleBundle
->GetStringFromName("appsTitle", title
);
273 // Pass the magic string "..apps" to the platform filepicker, which it
274 // should recognize and do the correct platform behavior for.
275 AppendFilter(title
, u
"..apps"_ns
);
277 if (aFilterMask
& filterPDF
) {
278 titleBundle
->GetStringFromName("pdfTitle", title
);
279 filterBundle
->GetStringFromName("pdfFilter", filter
);
280 AppendFilter(title
, filter
);
285 NS_IMETHODIMP
nsBaseFilePicker::AppendRawFilter(const nsAString
& aFilter
) {
286 mRawFilters
.AppendElement(aFilter
);
290 NS_IMETHODIMP
nsBaseFilePicker::GetCapture(
291 nsIFilePicker::CaptureTarget
* aCapture
) {
292 *aCapture
= nsIFilePicker::CaptureTarget::captureNone
;
296 NS_IMETHODIMP
nsBaseFilePicker::SetCapture(
297 nsIFilePicker::CaptureTarget aCapture
) {
301 // Set the filter index
302 NS_IMETHODIMP
nsBaseFilePicker::GetFilterIndex(int32_t* aFilterIndex
) {
307 NS_IMETHODIMP
nsBaseFilePicker::SetFilterIndex(int32_t aFilterIndex
) {
311 NS_IMETHODIMP
nsBaseFilePicker::GetFiles(nsISimpleEnumerator
** aFiles
) {
312 NS_ENSURE_ARG_POINTER(aFiles
);
313 nsCOMArray
<nsIFile
> files
;
316 // if we get into the base class, the platform
317 // doesn't implement GetFiles() yet.
319 nsCOMPtr
<nsIFile
> file
;
320 rv
= GetFile(getter_AddRefs(file
));
321 NS_ENSURE_SUCCESS(rv
, rv
);
323 files
.AppendObject(file
);
325 return NS_NewArrayEnumerator(aFiles
, files
, NS_GET_IID(nsIFile
));
328 // Set the display directory
329 NS_IMETHODIMP
nsBaseFilePicker::SetDisplayDirectory(nsIFile
* aDirectory
) {
330 // if displaySpecialDirectory has been previously called, let's abort this
332 if (!mDisplaySpecialDirectory
.IsEmpty()) {
337 mDisplayDirectory
= nullptr;
340 nsCOMPtr
<nsIFile
> directory
;
341 nsresult rv
= aDirectory
->Clone(getter_AddRefs(directory
));
342 if (NS_FAILED(rv
)) return rv
;
343 mDisplayDirectory
= directory
;
347 // Get the display directory
348 NS_IMETHODIMP
nsBaseFilePicker::GetDisplayDirectory(nsIFile
** aDirectory
) {
349 *aDirectory
= nullptr;
351 // if displaySpecialDirectory has been previously called, let's abort this
353 if (!mDisplaySpecialDirectory
.IsEmpty()) {
357 if (!mDisplayDirectory
) return NS_OK
;
358 nsCOMPtr
<nsIFile
> directory
;
359 nsresult rv
= mDisplayDirectory
->Clone(getter_AddRefs(directory
));
363 directory
.forget(aDirectory
);
367 // Set the display special directory
368 NS_IMETHODIMP
nsBaseFilePicker::SetDisplaySpecialDirectory(
369 const nsAString
& aDirectory
) {
370 // if displayDirectory has been previously called, let's abort this operation.
371 if (mDisplayDirectory
&& mDisplaySpecialDirectory
.IsEmpty()) {
375 mDisplaySpecialDirectory
= aDirectory
;
376 if (mDisplaySpecialDirectory
.IsEmpty()) {
377 mDisplayDirectory
= nullptr;
381 return ResolveSpecialDirectory(aDirectory
);
384 nsresult
nsBaseFilePicker::ResolveSpecialDirectory(
385 const nsAString
& aSpecialDirectory
) {
386 // Only perform special-directory name resolution in the parent process.
387 // (Subclasses of `nsBaseFilePicker` used in other processes must override
389 MOZ_ASSERT(XRE_IsParentProcess());
390 return NS_GetSpecialDirectory(NS_ConvertUTF16toUTF8(aSpecialDirectory
).get(),
391 getter_AddRefs(mDisplayDirectory
));
394 // Get the display special directory
395 NS_IMETHODIMP
nsBaseFilePicker::GetDisplaySpecialDirectory(
396 nsAString
& aDirectory
) {
397 aDirectory
= mDisplaySpecialDirectory
;
402 nsBaseFilePicker::GetAddToRecentDocs(bool* aFlag
) {
403 *aFlag
= mAddToRecentDocs
;
408 nsBaseFilePicker::SetAddToRecentDocs(bool aFlag
) {
409 mAddToRecentDocs
= aFlag
;
414 nsBaseFilePicker::GetMode(nsIFilePicker::Mode
* aMode
) {
420 nsBaseFilePicker::SetOkButtonLabel(const nsAString
& aLabel
) {
421 mOkButtonLabel
= aLabel
;
426 nsBaseFilePicker::GetOkButtonLabel(nsAString
& aLabel
) {
427 aLabel
= mOkButtonLabel
;
432 nsBaseFilePicker::GetDomFileOrDirectory(nsISupports
** aValue
) {
433 nsCOMPtr
<nsIFile
> localFile
;
434 nsresult rv
= GetFile(getter_AddRefs(localFile
));
435 NS_ENSURE_SUCCESS(rv
, rv
);
442 auto* innerParent
= mParent
? mParent
->GetCurrentInnerWindow() : nullptr;
445 return NS_ERROR_FAILURE
;
448 return LocalFileToDirectoryOrBlob(
449 innerParent
, mMode
== nsIFilePicker::modeGetFolder
, localFile
, aValue
);
453 nsBaseFilePicker::GetDomFileOrDirectoryEnumerator(
454 nsISimpleEnumerator
** aValue
) {
455 nsCOMPtr
<nsISimpleEnumerator
> iter
;
456 nsresult rv
= GetFiles(getter_AddRefs(iter
));
457 NS_ENSURE_SUCCESS(rv
, rv
);
459 RefPtr
<nsBaseFilePickerEnumerator
> retIter
=
460 new nsBaseFilePickerEnumerator(mParent
, iter
, mMode
);
462 retIter
.forget(aValue
);