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/dom/Element.h"
21 #include "mozilla/Components.h"
22 #include "mozilla/StaticPrefs_widget.h"
23 #include "WidgetUtils.h"
24 #include "nsSimpleEnumerator.h"
25 #include "nsThreadUtils.h"
26 #include "nsContentUtils.h"
28 #include "nsBaseFilePicker.h"
30 using namespace mozilla::widget
;
31 using namespace mozilla::dom
;
32 using mozilla::ErrorResult
;
34 #define FILEPICKER_TITLES "chrome://global/locale/filepicker.properties"
35 #define FILEPICKER_FILTERS "chrome://global/content/filepicker.properties"
39 nsresult
LocalFileToDirectoryOrBlob(nsPIDOMWindowInner
* aWindow
,
40 bool aIsDirectory
, nsIFile
* aFile
,
41 nsISupports
** aResult
) {
47 aFile
->IsDirectory(&isDir
);
51 RefPtr
<Directory
> directory
= Directory::Create(aWindow
->AsGlobal(), aFile
);
52 MOZ_ASSERT(directory
);
54 directory
.forget(aResult
);
58 RefPtr
<File
> file
= File::CreateFromFile(aWindow
->AsGlobal(), aFile
);
59 if (NS_WARN_IF(!file
)) {
60 return NS_ERROR_FAILURE
;
67 } // anonymous namespace
71 * A runnable to dispatch from the main thread to the main thread to display
72 * the file picker while letting the showAsync method return right away.
74 * Not needed on Windows, where nsFilePicker::Open() is fully async.
76 class nsBaseFilePicker::AsyncShowFilePicker
: public mozilla::Runnable
{
78 AsyncShowFilePicker(nsBaseFilePicker
* aFilePicker
,
79 nsIFilePickerShownCallback
* aCallback
)
80 : mozilla::Runnable("AsyncShowFilePicker"),
81 mFilePicker(aFilePicker
),
82 mCallback(aCallback
) {}
84 NS_IMETHOD
Run() override
{
85 NS_ASSERTION(NS_IsMainThread(),
86 "AsyncShowFilePicker should be on the main thread!");
88 // It's possible that some widget implementations require GUI operations
89 // to be on the main thread, so that's why we're not dispatching to another
90 // thread and calling back to the main after it's done.
91 nsIFilePicker::ResultCode result
= nsIFilePicker::returnCancel
;
92 nsresult rv
= mFilePicker
->Show(&result
);
94 NS_ERROR("FilePicker's Show() implementation failed!");
98 mCallback
->Done(result
);
104 RefPtr
<nsBaseFilePicker
> mFilePicker
;
105 RefPtr
<nsIFilePickerShownCallback
> mCallback
;
109 class nsBaseFilePickerEnumerator
: public nsSimpleEnumerator
{
111 nsBaseFilePickerEnumerator(nsPIDOMWindowOuter
* aParent
,
112 nsISimpleEnumerator
* iterator
,
113 nsIFilePicker::Mode aMode
)
114 : mIterator(iterator
),
115 mParent(aParent
->GetCurrentInnerWindow()),
118 const nsID
& DefaultInterface() override
{ return NS_GET_IID(nsIFile
); }
121 GetNext(nsISupports
** aResult
) override
{
122 nsCOMPtr
<nsISupports
> tmp
;
123 nsresult rv
= mIterator
->GetNext(getter_AddRefs(tmp
));
124 NS_ENSURE_SUCCESS(rv
, rv
);
130 nsCOMPtr
<nsIFile
> localFile
= do_QueryInterface(tmp
);
132 return NS_ERROR_FAILURE
;
136 return NS_ERROR_FAILURE
;
139 return LocalFileToDirectoryOrBlob(
140 mParent
, mMode
== nsIFilePicker::modeGetFolder
, localFile
, aResult
);
144 HasMoreElements(bool* aResult
) override
{
145 return mIterator
->HasMoreElements(aResult
);
149 nsCOMPtr
<nsISimpleEnumerator
> mIterator
;
150 nsCOMPtr
<nsPIDOMWindowInner
> mParent
;
151 nsIFilePicker::Mode mMode
;
154 nsBaseFilePicker::nsBaseFilePicker()
155 : mAddToRecentDocs(true), mMode(nsIFilePicker::modeOpen
) {}
157 nsBaseFilePicker::~nsBaseFilePicker() = default;
159 NS_IMETHODIMP
nsBaseFilePicker::Init(
160 mozIDOMWindowProxy
* aParent
, const nsAString
& aTitle
,
161 nsIFilePicker::Mode aMode
,
162 mozilla::dom::BrowsingContext
* aBrowsingContext
) {
164 "Null parent passed to filepicker, no file "
167 mParent
= nsPIDOMWindowOuter::From(aParent
);
169 nsCOMPtr
<nsIWidget
> widget
= WidgetUtils::DOMWindowToWidget(mParent
);
170 NS_ENSURE_TRUE(widget
, NS_ERROR_FAILURE
);
172 mBrowsingContext
= aBrowsingContext
;
174 InitNative(widget
, aTitle
);
180 nsBaseFilePicker::IsModeSupported(nsIFilePicker::Mode aMode
, JSContext
* aCx
,
181 Promise
** aPromise
) {
183 MOZ_ASSERT(aPromise
);
185 nsIGlobalObject
* globalObject
= xpc::CurrentNativeGlobal(aCx
);
186 if (NS_WARN_IF(!globalObject
)) {
187 return NS_ERROR_FAILURE
;
191 RefPtr
<Promise
> promise
= Promise::Create(globalObject
, result
);
192 if (NS_WARN_IF(result
.Failed())) {
193 return result
.StealNSResult();
196 promise
->MaybeResolve(true);
197 promise
.forget(aPromise
);
204 nsBaseFilePicker::Open(nsIFilePickerShownCallback
* aCallback
) {
205 if (MaybeBlockFilePicker(aCallback
)) {
209 nsCOMPtr
<nsIRunnable
> filePickerEvent
=
210 new AsyncShowFilePicker(this, aCallback
);
211 return NS_DispatchToMainThread(filePickerEvent
);
216 nsBaseFilePicker::Close() {
217 NS_WARNING("Unimplemented nsFilePicker::Close");
222 nsBaseFilePicker::AppendFilters(int32_t aFilterMask
) {
223 nsCOMPtr
<nsIStringBundleService
> stringService
=
224 mozilla::components::StringBundle::Service();
225 if (!stringService
) return NS_ERROR_FAILURE
;
227 nsCOMPtr
<nsIStringBundle
> titleBundle
, filterBundle
;
229 nsresult rv
= stringService
->CreateBundle(FILEPICKER_TITLES
,
230 getter_AddRefs(titleBundle
));
231 if (NS_FAILED(rv
)) return NS_ERROR_FAILURE
;
233 rv
= stringService
->CreateBundle(FILEPICKER_FILTERS
,
234 getter_AddRefs(filterBundle
));
235 if (NS_FAILED(rv
)) return NS_ERROR_FAILURE
;
240 if (aFilterMask
& filterAll
) {
241 titleBundle
->GetStringFromName("allTitle", title
);
242 filterBundle
->GetStringFromName("allFilter", filter
);
243 AppendFilter(title
, filter
);
245 if (aFilterMask
& filterHTML
) {
246 titleBundle
->GetStringFromName("htmlTitle", title
);
247 filterBundle
->GetStringFromName("htmlFilter", filter
);
248 AppendFilter(title
, filter
);
250 if (aFilterMask
& filterText
) {
251 titleBundle
->GetStringFromName("textTitle", title
);
252 filterBundle
->GetStringFromName("textFilter", filter
);
253 AppendFilter(title
, filter
);
255 if (aFilterMask
& filterImages
) {
256 titleBundle
->GetStringFromName("imageTitle", title
);
257 filterBundle
->GetStringFromName("imageFilter", filter
);
258 AppendFilter(title
, filter
);
260 if (aFilterMask
& filterAudio
) {
261 titleBundle
->GetStringFromName("audioTitle", title
);
262 filterBundle
->GetStringFromName("audioFilter", filter
);
263 AppendFilter(title
, filter
);
265 if (aFilterMask
& filterVideo
) {
266 titleBundle
->GetStringFromName("videoTitle", title
);
267 filterBundle
->GetStringFromName("videoFilter", filter
);
268 AppendFilter(title
, filter
);
270 if (aFilterMask
& filterXML
) {
271 titleBundle
->GetStringFromName("xmlTitle", title
);
272 filterBundle
->GetStringFromName("xmlFilter", filter
);
273 AppendFilter(title
, filter
);
275 if (aFilterMask
& filterXUL
) {
276 titleBundle
->GetStringFromName("xulTitle", title
);
277 filterBundle
->GetStringFromName("xulFilter", filter
);
278 AppendFilter(title
, filter
);
280 if (aFilterMask
& filterApps
) {
281 titleBundle
->GetStringFromName("appsTitle", title
);
282 // Pass the magic string "..apps" to the platform filepicker, which it
283 // should recognize and do the correct platform behavior for.
284 AppendFilter(title
, u
"..apps"_ns
);
286 if (aFilterMask
& filterPDF
) {
287 titleBundle
->GetStringFromName("pdfTitle", title
);
288 filterBundle
->GetStringFromName("pdfFilter", filter
);
289 AppendFilter(title
, filter
);
294 NS_IMETHODIMP
nsBaseFilePicker::AppendRawFilter(const nsAString
& aFilter
) {
295 mRawFilters
.AppendElement(aFilter
);
299 NS_IMETHODIMP
nsBaseFilePicker::GetCapture(
300 nsIFilePicker::CaptureTarget
* aCapture
) {
301 *aCapture
= nsIFilePicker::CaptureTarget::captureNone
;
305 NS_IMETHODIMP
nsBaseFilePicker::SetCapture(
306 nsIFilePicker::CaptureTarget aCapture
) {
310 // Set the filter index
311 NS_IMETHODIMP
nsBaseFilePicker::GetFilterIndex(int32_t* aFilterIndex
) {
316 NS_IMETHODIMP
nsBaseFilePicker::SetFilterIndex(int32_t aFilterIndex
) {
320 NS_IMETHODIMP
nsBaseFilePicker::GetFiles(nsISimpleEnumerator
** aFiles
) {
321 NS_ENSURE_ARG_POINTER(aFiles
);
322 nsCOMArray
<nsIFile
> files
;
325 // if we get into the base class, the platform
326 // doesn't implement GetFiles() yet.
328 nsCOMPtr
<nsIFile
> file
;
329 rv
= GetFile(getter_AddRefs(file
));
330 NS_ENSURE_SUCCESS(rv
, rv
);
332 files
.AppendObject(file
);
334 return NS_NewArrayEnumerator(aFiles
, files
, NS_GET_IID(nsIFile
));
337 // Set the display directory
338 NS_IMETHODIMP
nsBaseFilePicker::SetDisplayDirectory(nsIFile
* aDirectory
) {
339 // if displaySpecialDirectory has been previously called, let's abort this
341 if (!mDisplaySpecialDirectory
.IsEmpty()) {
346 mDisplayDirectory
= nullptr;
349 nsCOMPtr
<nsIFile
> directory
;
350 nsresult rv
= aDirectory
->Clone(getter_AddRefs(directory
));
351 if (NS_FAILED(rv
)) return rv
;
352 mDisplayDirectory
= directory
;
356 // Get the display directory
357 NS_IMETHODIMP
nsBaseFilePicker::GetDisplayDirectory(nsIFile
** aDirectory
) {
358 *aDirectory
= nullptr;
360 // if displaySpecialDirectory has been previously called, let's abort this
362 if (!mDisplaySpecialDirectory
.IsEmpty()) {
366 if (!mDisplayDirectory
) return NS_OK
;
367 nsCOMPtr
<nsIFile
> directory
;
368 nsresult rv
= mDisplayDirectory
->Clone(getter_AddRefs(directory
));
372 directory
.forget(aDirectory
);
376 // Set the display special directory
377 NS_IMETHODIMP
nsBaseFilePicker::SetDisplaySpecialDirectory(
378 const nsAString
& aDirectory
) {
379 // if displayDirectory has been previously called, let's abort this operation.
380 if (mDisplayDirectory
&& mDisplaySpecialDirectory
.IsEmpty()) {
384 mDisplaySpecialDirectory
= aDirectory
;
385 if (mDisplaySpecialDirectory
.IsEmpty()) {
386 mDisplayDirectory
= nullptr;
390 return ResolveSpecialDirectory(aDirectory
);
393 bool nsBaseFilePicker::MaybeBlockFilePicker(
394 nsIFilePickerShownCallback
* aCallback
) {
395 if (!mozilla::StaticPrefs::widget_disable_file_pickers()) {
400 // File pickers are disabled, so we answer the callback with returnCancel.
401 aCallback
->Done(nsIFilePicker::returnCancel
);
403 if (mBrowsingContext
) {
404 RefPtr
<Element
> topFrameElement
= mBrowsingContext
->GetTopFrameElement();
405 if (topFrameElement
) {
406 // Dispatch an event that the frontend may use.
407 nsContentUtils::DispatchEventOnlyToChrome(
408 topFrameElement
->OwnerDoc(), topFrameElement
, u
"FilePickerBlocked"_ns
,
409 mozilla::CanBubble::eYes
, mozilla::Cancelable::eNo
);
416 nsresult
nsBaseFilePicker::ResolveSpecialDirectory(
417 const nsAString
& aSpecialDirectory
) {
418 // Only perform special-directory name resolution in the parent process.
419 // (Subclasses of `nsBaseFilePicker` used in other processes must override
421 MOZ_ASSERT(XRE_IsParentProcess());
422 return NS_GetSpecialDirectory(NS_ConvertUTF16toUTF8(aSpecialDirectory
).get(),
423 getter_AddRefs(mDisplayDirectory
));
426 // Get the display special directory
427 NS_IMETHODIMP
nsBaseFilePicker::GetDisplaySpecialDirectory(
428 nsAString
& aDirectory
) {
429 aDirectory
= mDisplaySpecialDirectory
;
434 nsBaseFilePicker::GetAddToRecentDocs(bool* aFlag
) {
435 *aFlag
= mAddToRecentDocs
;
440 nsBaseFilePicker::SetAddToRecentDocs(bool aFlag
) {
441 mAddToRecentDocs
= aFlag
;
446 nsBaseFilePicker::GetMode(nsIFilePicker::Mode
* aMode
) {
452 nsBaseFilePicker::SetOkButtonLabel(const nsAString
& aLabel
) {
453 mOkButtonLabel
= aLabel
;
458 nsBaseFilePicker::GetOkButtonLabel(nsAString
& aLabel
) {
459 aLabel
= mOkButtonLabel
;
464 nsBaseFilePicker::GetDomFileOrDirectory(nsISupports
** aValue
) {
465 nsCOMPtr
<nsIFile
> localFile
;
466 nsresult rv
= GetFile(getter_AddRefs(localFile
));
467 NS_ENSURE_SUCCESS(rv
, rv
);
474 auto* innerParent
= mParent
? mParent
->GetCurrentInnerWindow() : nullptr;
477 return NS_ERROR_FAILURE
;
480 return LocalFileToDirectoryOrBlob(
481 innerParent
, mMode
== nsIFilePicker::modeGetFolder
, localFile
, aValue
);
485 nsBaseFilePicker::GetDomFileOrDirectoryEnumerator(
486 nsISimpleEnumerator
** aValue
) {
487 nsCOMPtr
<nsISimpleEnumerator
> iter
;
488 nsresult rv
= GetFiles(getter_AddRefs(iter
));
489 NS_ENSURE_SUCCESS(rv
, rv
);
491 RefPtr
<nsBaseFilePickerEnumerator
> retIter
=
492 new nsBaseFilePickerEnumerator(mParent
, iter
, mMode
);
494 retIter
.forget(aValue
);