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
67 * A runnable to dispatch from the main thread to the main thread to display
68 * the file picker while letting the showAsync method return right away.
70 class nsBaseFilePicker::AsyncShowFilePicker
: public mozilla::Runnable
{
72 AsyncShowFilePicker(nsBaseFilePicker
* aFilePicker
,
73 nsIFilePickerShownCallback
* aCallback
)
74 : mozilla::Runnable("AsyncShowFilePicker"),
75 mFilePicker(aFilePicker
),
76 mCallback(aCallback
) {}
78 NS_IMETHOD
Run() override
{
79 NS_ASSERTION(NS_IsMainThread(),
80 "AsyncShowFilePicker should be on the main thread!");
82 // It's possible that some widget implementations require GUI operations
83 // to be on the main thread, so that's why we're not dispatching to another
84 // thread and calling back to the main after it's done.
85 nsIFilePicker::ResultCode result
= nsIFilePicker::returnCancel
;
86 nsresult rv
= mFilePicker
->Show(&result
);
88 NS_ERROR("FilePicker's Show() implementation failed!");
92 mCallback
->Done(result
);
98 RefPtr
<nsBaseFilePicker
> mFilePicker
;
99 RefPtr
<nsIFilePickerShownCallback
> mCallback
;
102 class nsBaseFilePickerEnumerator
: public nsSimpleEnumerator
{
104 nsBaseFilePickerEnumerator(nsPIDOMWindowOuter
* aParent
,
105 nsISimpleEnumerator
* iterator
,
106 nsIFilePicker::Mode aMode
)
107 : mIterator(iterator
),
108 mParent(aParent
->GetCurrentInnerWindow()),
111 const nsID
& DefaultInterface() override
{ return NS_GET_IID(nsIFile
); }
114 GetNext(nsISupports
** aResult
) override
{
115 nsCOMPtr
<nsISupports
> tmp
;
116 nsresult rv
= mIterator
->GetNext(getter_AddRefs(tmp
));
117 NS_ENSURE_SUCCESS(rv
, rv
);
123 nsCOMPtr
<nsIFile
> localFile
= do_QueryInterface(tmp
);
125 return NS_ERROR_FAILURE
;
129 return NS_ERROR_FAILURE
;
132 return LocalFileToDirectoryOrBlob(
133 mParent
, mMode
== nsIFilePicker::modeGetFolder
, localFile
, aResult
);
137 HasMoreElements(bool* aResult
) override
{
138 return mIterator
->HasMoreElements(aResult
);
142 nsCOMPtr
<nsISimpleEnumerator
> mIterator
;
143 nsCOMPtr
<nsPIDOMWindowInner
> mParent
;
144 nsIFilePicker::Mode mMode
;
147 nsBaseFilePicker::nsBaseFilePicker()
148 : mAddToRecentDocs(true), mMode(nsIFilePicker::modeOpen
) {}
150 nsBaseFilePicker::~nsBaseFilePicker() = default;
152 NS_IMETHODIMP
nsBaseFilePicker::Init(mozIDOMWindowProxy
* aParent
,
153 const nsAString
& aTitle
,
154 nsIFilePicker::Mode aMode
) {
156 "Null parent passed to filepicker, no file "
159 mParent
= nsPIDOMWindowOuter::From(aParent
);
161 nsCOMPtr
<nsIWidget
> widget
= WidgetUtils::DOMWindowToWidget(mParent
);
162 NS_ENSURE_TRUE(widget
, NS_ERROR_FAILURE
);
165 InitNative(widget
, aTitle
);
171 nsBaseFilePicker::IsModeSupported(nsIFilePicker::Mode aMode
, JSContext
* aCx
,
172 Promise
** aPromise
) {
174 MOZ_ASSERT(aPromise
);
176 nsIGlobalObject
* globalObject
= xpc::CurrentNativeGlobal(aCx
);
177 if (NS_WARN_IF(!globalObject
)) {
178 return NS_ERROR_FAILURE
;
182 RefPtr
<Promise
> promise
= Promise::Create(globalObject
, result
);
183 if (NS_WARN_IF(result
.Failed())) {
184 return result
.StealNSResult();
187 promise
->MaybeResolve(true);
188 promise
.forget(aPromise
);
194 nsBaseFilePicker::Open(nsIFilePickerShownCallback
* aCallback
) {
195 nsCOMPtr
<nsIRunnable
> filePickerEvent
=
196 new AsyncShowFilePicker(this, aCallback
);
197 return NS_DispatchToMainThread(filePickerEvent
);
201 nsBaseFilePicker::AppendFilters(int32_t aFilterMask
) {
202 nsCOMPtr
<nsIStringBundleService
> stringService
=
203 mozilla::components::StringBundle::Service();
204 if (!stringService
) return NS_ERROR_FAILURE
;
206 nsCOMPtr
<nsIStringBundle
> titleBundle
, filterBundle
;
208 nsresult rv
= stringService
->CreateBundle(FILEPICKER_TITLES
,
209 getter_AddRefs(titleBundle
));
210 if (NS_FAILED(rv
)) return NS_ERROR_FAILURE
;
212 rv
= stringService
->CreateBundle(FILEPICKER_FILTERS
,
213 getter_AddRefs(filterBundle
));
214 if (NS_FAILED(rv
)) return NS_ERROR_FAILURE
;
219 if (aFilterMask
& filterAll
) {
220 titleBundle
->GetStringFromName("allTitle", title
);
221 filterBundle
->GetStringFromName("allFilter", filter
);
222 AppendFilter(title
, filter
);
224 if (aFilterMask
& filterHTML
) {
225 titleBundle
->GetStringFromName("htmlTitle", title
);
226 filterBundle
->GetStringFromName("htmlFilter", filter
);
227 AppendFilter(title
, filter
);
229 if (aFilterMask
& filterText
) {
230 titleBundle
->GetStringFromName("textTitle", title
);
231 filterBundle
->GetStringFromName("textFilter", filter
);
232 AppendFilter(title
, filter
);
234 if (aFilterMask
& filterImages
) {
235 titleBundle
->GetStringFromName("imageTitle", title
);
236 filterBundle
->GetStringFromName("imageFilter", filter
);
237 AppendFilter(title
, filter
);
239 if (aFilterMask
& filterAudio
) {
240 titleBundle
->GetStringFromName("audioTitle", title
);
241 filterBundle
->GetStringFromName("audioFilter", filter
);
242 AppendFilter(title
, filter
);
244 if (aFilterMask
& filterVideo
) {
245 titleBundle
->GetStringFromName("videoTitle", title
);
246 filterBundle
->GetStringFromName("videoFilter", filter
);
247 AppendFilter(title
, filter
);
249 if (aFilterMask
& filterXML
) {
250 titleBundle
->GetStringFromName("xmlTitle", title
);
251 filterBundle
->GetStringFromName("xmlFilter", filter
);
252 AppendFilter(title
, filter
);
254 if (aFilterMask
& filterXUL
) {
255 titleBundle
->GetStringFromName("xulTitle", title
);
256 filterBundle
->GetStringFromName("xulFilter", filter
);
257 AppendFilter(title
, filter
);
259 if (aFilterMask
& filterApps
) {
260 titleBundle
->GetStringFromName("appsTitle", title
);
261 // Pass the magic string "..apps" to the platform filepicker, which it
262 // should recognize and do the correct platform behavior for.
263 AppendFilter(title
, u
"..apps"_ns
);
265 if (aFilterMask
& filterPDF
) {
266 titleBundle
->GetStringFromName("pdfTitle", title
);
267 filterBundle
->GetStringFromName("pdfFilter", filter
);
268 AppendFilter(title
, filter
);
273 NS_IMETHODIMP
nsBaseFilePicker::AppendRawFilter(const nsAString
& aFilter
) {
274 mRawFilters
.AppendElement(aFilter
);
278 NS_IMETHODIMP
nsBaseFilePicker::GetCapture(
279 nsIFilePicker::CaptureTarget
* aCapture
) {
280 *aCapture
= nsIFilePicker::CaptureTarget::captureNone
;
284 NS_IMETHODIMP
nsBaseFilePicker::SetCapture(
285 nsIFilePicker::CaptureTarget aCapture
) {
289 // Set the filter index
290 NS_IMETHODIMP
nsBaseFilePicker::GetFilterIndex(int32_t* aFilterIndex
) {
295 NS_IMETHODIMP
nsBaseFilePicker::SetFilterIndex(int32_t aFilterIndex
) {
299 NS_IMETHODIMP
nsBaseFilePicker::GetFiles(nsISimpleEnumerator
** aFiles
) {
300 NS_ENSURE_ARG_POINTER(aFiles
);
301 nsCOMArray
<nsIFile
> files
;
304 // if we get into the base class, the platform
305 // doesn't implement GetFiles() yet.
307 nsCOMPtr
<nsIFile
> file
;
308 rv
= GetFile(getter_AddRefs(file
));
309 NS_ENSURE_SUCCESS(rv
, rv
);
311 files
.AppendObject(file
);
313 return NS_NewArrayEnumerator(aFiles
, files
, NS_GET_IID(nsIFile
));
316 // Set the display directory
317 NS_IMETHODIMP
nsBaseFilePicker::SetDisplayDirectory(nsIFile
* aDirectory
) {
318 // if displaySpecialDirectory has been previously called, let's abort this
320 if (!mDisplaySpecialDirectory
.IsEmpty()) {
325 mDisplayDirectory
= nullptr;
328 nsCOMPtr
<nsIFile
> directory
;
329 nsresult rv
= aDirectory
->Clone(getter_AddRefs(directory
));
330 if (NS_FAILED(rv
)) return rv
;
331 mDisplayDirectory
= directory
;
335 // Get the display directory
336 NS_IMETHODIMP
nsBaseFilePicker::GetDisplayDirectory(nsIFile
** aDirectory
) {
337 *aDirectory
= nullptr;
339 // if displaySpecialDirectory has been previously called, let's abort this
341 if (!mDisplaySpecialDirectory
.IsEmpty()) {
345 if (!mDisplayDirectory
) return NS_OK
;
346 nsCOMPtr
<nsIFile
> directory
;
347 nsresult rv
= mDisplayDirectory
->Clone(getter_AddRefs(directory
));
351 directory
.forget(aDirectory
);
355 // Set the display special directory
356 NS_IMETHODIMP
nsBaseFilePicker::SetDisplaySpecialDirectory(
357 const nsAString
& aDirectory
) {
358 // if displayDirectory has been previously called, let's abort this operation.
359 if (mDisplayDirectory
&& mDisplaySpecialDirectory
.IsEmpty()) {
363 mDisplaySpecialDirectory
= aDirectory
;
364 if (mDisplaySpecialDirectory
.IsEmpty()) {
365 mDisplayDirectory
= nullptr;
369 return ResolveSpecialDirectory(aDirectory
);
372 nsresult
nsBaseFilePicker::ResolveSpecialDirectory(
373 const nsAString
& aSpecialDirectory
) {
374 // Only perform special-directory name resolution in the parent process.
375 // (Subclasses of `nsBaseFilePicker` used in other processes must override
377 MOZ_ASSERT(XRE_IsParentProcess());
378 return NS_GetSpecialDirectory(NS_ConvertUTF16toUTF8(aSpecialDirectory
).get(),
379 getter_AddRefs(mDisplayDirectory
));
382 // Get the display special directory
383 NS_IMETHODIMP
nsBaseFilePicker::GetDisplaySpecialDirectory(
384 nsAString
& aDirectory
) {
385 aDirectory
= mDisplaySpecialDirectory
;
390 nsBaseFilePicker::GetAddToRecentDocs(bool* aFlag
) {
391 *aFlag
= mAddToRecentDocs
;
396 nsBaseFilePicker::SetAddToRecentDocs(bool aFlag
) {
397 mAddToRecentDocs
= aFlag
;
402 nsBaseFilePicker::GetMode(nsIFilePicker::Mode
* aMode
) {
408 nsBaseFilePicker::SetOkButtonLabel(const nsAString
& aLabel
) {
409 mOkButtonLabel
= aLabel
;
414 nsBaseFilePicker::GetOkButtonLabel(nsAString
& aLabel
) {
415 aLabel
= mOkButtonLabel
;
420 nsBaseFilePicker::GetDomFileOrDirectory(nsISupports
** aValue
) {
421 nsCOMPtr
<nsIFile
> localFile
;
422 nsresult rv
= GetFile(getter_AddRefs(localFile
));
423 NS_ENSURE_SUCCESS(rv
, rv
);
430 auto* innerParent
= mParent
? mParent
->GetCurrentInnerWindow() : nullptr;
433 return NS_ERROR_FAILURE
;
436 return LocalFileToDirectoryOrBlob(
437 innerParent
, mMode
== nsIFilePicker::modeGetFolder
, localFile
, aValue
);
441 nsBaseFilePicker::GetDomFileOrDirectoryEnumerator(
442 nsISimpleEnumerator
** aValue
) {
443 nsCOMPtr
<nsISimpleEnumerator
> iter
;
444 nsresult rv
= GetFiles(getter_AddRefs(iter
));
445 NS_ENSURE_SUCCESS(rv
, rv
);
447 RefPtr
<nsBaseFilePickerEnumerator
> retIter
=
448 new nsBaseFilePickerEnumerator(mParent
, iter
, mMode
);
450 retIter
.forget(aValue
);