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(
157 mozIDOMWindowProxy
* aParent
, const nsAString
& aTitle
,
158 nsIFilePicker::Mode aMode
,
159 mozilla::dom::BrowsingContext
* aBrowsingContext
) {
161 "Null parent passed to filepicker, no file "
164 mParent
= nsPIDOMWindowOuter::From(aParent
);
166 nsCOMPtr
<nsIWidget
> widget
= WidgetUtils::DOMWindowToWidget(mParent
);
167 NS_ENSURE_TRUE(widget
, NS_ERROR_FAILURE
);
169 mBrowsingContext
= aBrowsingContext
;
171 InitNative(widget
, aTitle
);
177 nsBaseFilePicker::IsModeSupported(nsIFilePicker::Mode aMode
, JSContext
* aCx
,
178 Promise
** aPromise
) {
180 MOZ_ASSERT(aPromise
);
182 nsIGlobalObject
* globalObject
= xpc::CurrentNativeGlobal(aCx
);
183 if (NS_WARN_IF(!globalObject
)) {
184 return NS_ERROR_FAILURE
;
188 RefPtr
<Promise
> promise
= Promise::Create(globalObject
, result
);
189 if (NS_WARN_IF(result
.Failed())) {
190 return result
.StealNSResult();
193 promise
->MaybeResolve(true);
194 promise
.forget(aPromise
);
201 nsBaseFilePicker::Open(nsIFilePickerShownCallback
* aCallback
) {
202 nsCOMPtr
<nsIRunnable
> filePickerEvent
=
203 new AsyncShowFilePicker(this, aCallback
);
204 return NS_DispatchToMainThread(filePickerEvent
);
209 nsBaseFilePicker::Close() {
210 NS_WARNING("Unimplemented nsFilePicker::Close");
215 nsBaseFilePicker::AppendFilters(int32_t aFilterMask
) {
216 nsCOMPtr
<nsIStringBundleService
> stringService
=
217 mozilla::components::StringBundle::Service();
218 if (!stringService
) return NS_ERROR_FAILURE
;
220 nsCOMPtr
<nsIStringBundle
> titleBundle
, filterBundle
;
222 nsresult rv
= stringService
->CreateBundle(FILEPICKER_TITLES
,
223 getter_AddRefs(titleBundle
));
224 if (NS_FAILED(rv
)) return NS_ERROR_FAILURE
;
226 rv
= stringService
->CreateBundle(FILEPICKER_FILTERS
,
227 getter_AddRefs(filterBundle
));
228 if (NS_FAILED(rv
)) return NS_ERROR_FAILURE
;
233 if (aFilterMask
& filterAll
) {
234 titleBundle
->GetStringFromName("allTitle", title
);
235 filterBundle
->GetStringFromName("allFilter", filter
);
236 AppendFilter(title
, filter
);
238 if (aFilterMask
& filterHTML
) {
239 titleBundle
->GetStringFromName("htmlTitle", title
);
240 filterBundle
->GetStringFromName("htmlFilter", filter
);
241 AppendFilter(title
, filter
);
243 if (aFilterMask
& filterText
) {
244 titleBundle
->GetStringFromName("textTitle", title
);
245 filterBundle
->GetStringFromName("textFilter", filter
);
246 AppendFilter(title
, filter
);
248 if (aFilterMask
& filterImages
) {
249 titleBundle
->GetStringFromName("imageTitle", title
);
250 filterBundle
->GetStringFromName("imageFilter", filter
);
251 AppendFilter(title
, filter
);
253 if (aFilterMask
& filterAudio
) {
254 titleBundle
->GetStringFromName("audioTitle", title
);
255 filterBundle
->GetStringFromName("audioFilter", filter
);
256 AppendFilter(title
, filter
);
258 if (aFilterMask
& filterVideo
) {
259 titleBundle
->GetStringFromName("videoTitle", title
);
260 filterBundle
->GetStringFromName("videoFilter", filter
);
261 AppendFilter(title
, filter
);
263 if (aFilterMask
& filterXML
) {
264 titleBundle
->GetStringFromName("xmlTitle", title
);
265 filterBundle
->GetStringFromName("xmlFilter", filter
);
266 AppendFilter(title
, filter
);
268 if (aFilterMask
& filterXUL
) {
269 titleBundle
->GetStringFromName("xulTitle", title
);
270 filterBundle
->GetStringFromName("xulFilter", filter
);
271 AppendFilter(title
, filter
);
273 if (aFilterMask
& filterApps
) {
274 titleBundle
->GetStringFromName("appsTitle", title
);
275 // Pass the magic string "..apps" to the platform filepicker, which it
276 // should recognize and do the correct platform behavior for.
277 AppendFilter(title
, u
"..apps"_ns
);
279 if (aFilterMask
& filterPDF
) {
280 titleBundle
->GetStringFromName("pdfTitle", title
);
281 filterBundle
->GetStringFromName("pdfFilter", filter
);
282 AppendFilter(title
, filter
);
287 NS_IMETHODIMP
nsBaseFilePicker::AppendRawFilter(const nsAString
& aFilter
) {
288 mRawFilters
.AppendElement(aFilter
);
292 NS_IMETHODIMP
nsBaseFilePicker::GetCapture(
293 nsIFilePicker::CaptureTarget
* aCapture
) {
294 *aCapture
= nsIFilePicker::CaptureTarget::captureNone
;
298 NS_IMETHODIMP
nsBaseFilePicker::SetCapture(
299 nsIFilePicker::CaptureTarget aCapture
) {
303 // Set the filter index
304 NS_IMETHODIMP
nsBaseFilePicker::GetFilterIndex(int32_t* aFilterIndex
) {
309 NS_IMETHODIMP
nsBaseFilePicker::SetFilterIndex(int32_t aFilterIndex
) {
313 NS_IMETHODIMP
nsBaseFilePicker::GetFiles(nsISimpleEnumerator
** aFiles
) {
314 NS_ENSURE_ARG_POINTER(aFiles
);
315 nsCOMArray
<nsIFile
> files
;
318 // if we get into the base class, the platform
319 // doesn't implement GetFiles() yet.
321 nsCOMPtr
<nsIFile
> file
;
322 rv
= GetFile(getter_AddRefs(file
));
323 NS_ENSURE_SUCCESS(rv
, rv
);
325 files
.AppendObject(file
);
327 return NS_NewArrayEnumerator(aFiles
, files
, NS_GET_IID(nsIFile
));
330 // Set the display directory
331 NS_IMETHODIMP
nsBaseFilePicker::SetDisplayDirectory(nsIFile
* aDirectory
) {
332 // if displaySpecialDirectory has been previously called, let's abort this
334 if (!mDisplaySpecialDirectory
.IsEmpty()) {
339 mDisplayDirectory
= nullptr;
342 nsCOMPtr
<nsIFile
> directory
;
343 nsresult rv
= aDirectory
->Clone(getter_AddRefs(directory
));
344 if (NS_FAILED(rv
)) return rv
;
345 mDisplayDirectory
= directory
;
349 // Get the display directory
350 NS_IMETHODIMP
nsBaseFilePicker::GetDisplayDirectory(nsIFile
** aDirectory
) {
351 *aDirectory
= nullptr;
353 // if displaySpecialDirectory has been previously called, let's abort this
355 if (!mDisplaySpecialDirectory
.IsEmpty()) {
359 if (!mDisplayDirectory
) return NS_OK
;
360 nsCOMPtr
<nsIFile
> directory
;
361 nsresult rv
= mDisplayDirectory
->Clone(getter_AddRefs(directory
));
365 directory
.forget(aDirectory
);
369 // Set the display special directory
370 NS_IMETHODIMP
nsBaseFilePicker::SetDisplaySpecialDirectory(
371 const nsAString
& aDirectory
) {
372 // if displayDirectory has been previously called, let's abort this operation.
373 if (mDisplayDirectory
&& mDisplaySpecialDirectory
.IsEmpty()) {
377 mDisplaySpecialDirectory
= aDirectory
;
378 if (mDisplaySpecialDirectory
.IsEmpty()) {
379 mDisplayDirectory
= nullptr;
383 return ResolveSpecialDirectory(aDirectory
);
386 nsresult
nsBaseFilePicker::ResolveSpecialDirectory(
387 const nsAString
& aSpecialDirectory
) {
388 // Only perform special-directory name resolution in the parent process.
389 // (Subclasses of `nsBaseFilePicker` used in other processes must override
391 MOZ_ASSERT(XRE_IsParentProcess());
392 return NS_GetSpecialDirectory(NS_ConvertUTF16toUTF8(aSpecialDirectory
).get(),
393 getter_AddRefs(mDisplayDirectory
));
396 // Get the display special directory
397 NS_IMETHODIMP
nsBaseFilePicker::GetDisplaySpecialDirectory(
398 nsAString
& aDirectory
) {
399 aDirectory
= mDisplaySpecialDirectory
;
404 nsBaseFilePicker::GetAddToRecentDocs(bool* aFlag
) {
405 *aFlag
= mAddToRecentDocs
;
410 nsBaseFilePicker::SetAddToRecentDocs(bool aFlag
) {
411 mAddToRecentDocs
= aFlag
;
416 nsBaseFilePicker::GetMode(nsIFilePicker::Mode
* aMode
) {
422 nsBaseFilePicker::SetOkButtonLabel(const nsAString
& aLabel
) {
423 mOkButtonLabel
= aLabel
;
428 nsBaseFilePicker::GetOkButtonLabel(nsAString
& aLabel
) {
429 aLabel
= mOkButtonLabel
;
434 nsBaseFilePicker::GetDomFileOrDirectory(nsISupports
** aValue
) {
435 nsCOMPtr
<nsIFile
> localFile
;
436 nsresult rv
= GetFile(getter_AddRefs(localFile
));
437 NS_ENSURE_SUCCESS(rv
, rv
);
444 auto* innerParent
= mParent
? mParent
->GetCurrentInnerWindow() : nullptr;
447 return NS_ERROR_FAILURE
;
450 return LocalFileToDirectoryOrBlob(
451 innerParent
, mMode
== nsIFilePicker::modeGetFolder
, localFile
, aValue
);
455 nsBaseFilePicker::GetDomFileOrDirectoryEnumerator(
456 nsISimpleEnumerator
** aValue
) {
457 nsCOMPtr
<nsISimpleEnumerator
> iter
;
458 nsresult rv
= GetFiles(getter_AddRefs(iter
));
459 NS_ENSURE_SUCCESS(rv
, rv
);
461 RefPtr
<nsBaseFilePickerEnumerator
> retIter
=
462 new nsBaseFilePickerEnumerator(mParent
, iter
, mMode
);
464 retIter
.forget(aValue
);