--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -1299,16 +1299,17 @@ GK_ATOM(viewport_maximum_scale, "viewpor
GK_ATOM(viewport_minimum_scale, "viewport-minimum-scale")
GK_ATOM(viewport_user_scalable, "viewport-user-scalable")
GK_ATOM(viewport_width, "viewport-width")
GK_ATOM(visibility, "visibility")
GK_ATOM(visuallyselected, "visuallyselected")
GK_ATOM(vlink, "vlink")
GK_ATOM(vspace, "vspace")
GK_ATOM(wbr, "wbr")
+GK_ATOM(webkitdirectory, "webkitdirectory")
GK_ATOM(when, "when")
GK_ATOM(where, "where")
GK_ATOM(widget, "widget")
GK_ATOM(width, "width")
GK_ATOM(window, "window")
GK_ATOM(headerWindowTarget, "window-target")
GK_ATOM(windowtype, "windowtype")
GK_ATOM(withParam, "with-param")
--- a/dom/filesystem/Directory.cpp
+++ b/dom/filesystem/Directory.cpp
@@ -161,16 +161,31 @@ Directory::GetRoot(FileSystemBase* aFile
return nullptr;
}
FileSystemPermissionRequest::RequestForTask(task);
return task->GetPromise();
}
/* static */ already_AddRefed<Directory>
+Directory::Constructor(const GlobalObject& aGlobal,
+ const nsAString& aRealPath,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIFile> path;
+ aRv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(aRealPath),
+ true, getter_AddRefs(path));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return Create(aGlobal.GetAsSupports(), path);
+}
+
+/* static */ already_AddRefed<Directory>
Directory::Create(nsISupports* aParent, nsIFile* aFile,
FileSystemBase* aFileSystem)
{
MOZ_ASSERT(aParent);
MOZ_ASSERT(aFile);
#ifdef DEBUG
bool isDir;
--- a/dom/filesystem/Directory.h
+++ b/dom/filesystem/Directory.h
@@ -58,16 +58,21 @@ public:
static bool
WebkitBlinkDirectoryPickerEnabled(JSContext* aCx, JSObject* aObj);
static already_AddRefed<Promise>
GetRoot(FileSystemBase* aFileSystem, ErrorResult& aRv);
static already_AddRefed<Directory>
+ Constructor(const GlobalObject& aGlobal,
+ const nsAString& aRealPath,
+ ErrorResult& aRv);
+
+ static already_AddRefed<Directory>
Create(nsISupports* aParent, nsIFile* aDirectory,
FileSystemBase* aFileSystem = 0);
// ========= Begin WebIDL bindings. ===========
nsISupports*
GetParentObject() const;
--- a/dom/filesystem/tests/mochitest.ini
+++ b/dom/filesystem/tests/mochitest.ini
@@ -1,8 +1,9 @@
[DEFAULT]
support-files =
filesystem_commons.js
script_fileList.js
worker_basic.js
[test_basic.html]
+[test_webkitdirectory.html]
[test_worker_basic.html]
new file mode 100644
--- /dev/null
+++ b/dom/filesystem/tests/test_webkitdirectory.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for webkitdirectory and webkitRelativePath</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<input id="inputFileWebkitDirectory" type="file" webkitdirectory></input>
+<input id="inputFileWebkitDirectoryAndDirectory" type="file" webkitdirectory directory></input>
+<input id="inputFileDirectory" type="file" directory></input>
+
+<script type="application/javascript;version=1.7">
+
+function populateInputFile(aInputFile) {
+ var url = SimpleTest.getTestFileURL("script_fileList.js");
+ var script = SpecialPowers.loadChromeScript(url);
+
+ var MockFilePicker = SpecialPowers.MockFilePicker;
+ MockFilePicker.init(window, "A Mock File Picker", SpecialPowers.Ci.nsIFilePicker.modeOpen);
+
+ function onOpened(message) {
+ MockFilePicker.useDirectory(message.dir);
+
+ var input = document.getElementById(aInputFile);
+ input.addEventListener('change', function() {
+ MockFilePicker.cleanup();
+ script.destroy();
+ next();
+ });
+
+ input.click();
+ }
+
+ script.addMessageListener("dir.opened", onOpened);
+ script.sendAsyncMessage("dir.open", { path: 'test' });
+}
+
+function checkFile(file, fileList) {
+ for (var i = 0; i < fileList.length; ++i) {
+ ok(fileList[i] instanceof File, "We want just files.");
+ if (fileList[i].name == file.name) {
+ is(fileList[i].webkitRelativePath, file.path, "Path matches");
+ return;
+ }
+ }
+
+ ok(false, "File not found.");
+}
+
+function test_fileList(aInputFile, aWhat) {
+ var input = document.getElementById(aInputFile);
+ var fileList = input.files;
+
+ if (aWhat == null) {
+ is(fileList, null, "We want a null fileList for " + aInputFile);
+ next();
+ return;
+ }
+
+ is(fileList.length, aWhat.length, "We want just " + aWhat.length + " elements for " + aInputFile);
+ for (var i = 0; i < aWhat.length; ++i) {
+ checkFile(aWhat[i], fileList);
+ }
+
+ next();
+}
+
+function test_webkitdirectory_attribute() {
+ var a = document.createElement("input");
+ a.setAttribute("type", "file");
+
+ ok("webkitdirectory" in a, "HTMLInputElement.webkitdirectory exists");
+
+ ok(!a.hasAttribute("webkitdirectory"), "No webkitdirectory DOM attribute by default");
+ ok(!a.webkitdirectory, "No webkitdirectory attribute by default");
+
+ a.webkitdirectory = true;
+
+ ok(a.hasAttribute("webkitdirectory"), "Webkitdirectory DOM attribute is set");
+ ok(a.webkitdirectory, "Webkitdirectory attribute is set");
+
+ next();
+}
+
+function test_setup() {
+ SpecialPowers.pushPrefEnv({"set": [["dom.input.dirpicker", true],
+ ["dom.webkitBlink.dirPicker.enabled", true]]}, next);
+}
+
+var tests = [
+ test_setup,
+
+ function() { populateInputFile('inputFileWebkitDirectory'); },
+ function() { populateInputFile('inputFileWebkitDirectoryAndDirectory'); },
+ function() { populateInputFile('inputFileDirectory'); },
+
+ function() { test_fileList('inputFileWebkitDirectory', [ { name: 'foo.txt', path: '/foo.txt' },
+ { name: 'bar.txt', path: '/subdir/bar.txt' }]); },
+ function() { test_fileList('inputFileWebkitDirectoryAndDirectory', [ { name: 'foo.txt', path: '/foo.txt' },
+ { name: 'bar.txt', path: '/subdir/bar.txt' }]); },
+ function() { test_fileList('inputFileDirectory', null); },
+
+ test_webkitdirectory_attribute,
+];
+
+function next() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+next();
+</script>
+</body>
+</html>
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -213,16 +213,28 @@ const Decimal HTMLInputElement::kStepAny
0x23e2, \
0x4479, \
{0xb5, 0x13, 0x7b, 0x36, 0x93, 0x43, 0xe3, 0xa0} \
}
#define PROGRESS_STR "progress"
static const uint32_t kProgressEventInterval = 50; // ms
+class GetFilesCallback
+{
+public:
+ NS_INLINE_DECL_REFCOUNTING(GetFilesCallback);
+
+ virtual void
+ Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) = 0;
+
+protected:
+ virtual ~GetFilesCallback() {}
+};
+
// Retrieving the list of files can be very time/IO consuming. We use this
// helper class to do it just once.
class GetFilesHelper final : public Runnable
{
public:
static already_AddRefed<GetFilesHelper>
Create(nsIGlobalObject* aGlobal,
const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory,
@@ -290,16 +302,31 @@ public:
mPromises.AppendElement(aPromise);
return;
}
MOZ_ASSERT(mPromises.IsEmpty());
ResolveOrRejectPromise(aPromise);
}
+ void
+ AddCallback(GetFilesCallback* aCallback)
+ {
+ MOZ_ASSERT(aCallback);
+
+ // Still working.
+ if (!mListingCompleted) {
+ mCallbacks.AppendElement(aCallback);
+ return;
+ }
+
+ MOZ_ASSERT(mCallbacks.IsEmpty());
+ RunCallback(aCallback);
+ }
+
// CC methods
void Unlink()
{
mGlobal = nullptr;
mFiles.Clear();
mPromises.Clear();
}
@@ -348,16 +375,24 @@ private:
// Let's process the pending promises.
nsTArray<RefPtr<Promise>> promises;
promises.SwapElements(mPromises);
for (uint32_t i = 0; i < promises.Length(); ++i) {
ResolveOrRejectPromise(promises[i]);
}
+ // Let's process the pending callbacks.
+ nsTArray<RefPtr<GetFilesCallback>> callbacks;
+ callbacks.SwapElements(mCallbacks);
+
+ for (uint32_t i = 0; i < callbacks.Length(); ++i) {
+ RunCallback(callbacks[i]);
+ }
+
return NS_OK;
}
void
RunIO()
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!mDirectoryPath.IsEmpty());
@@ -502,16 +537,26 @@ private:
if (NS_FAILED(mErrorResult)) {
aPromise->MaybeReject(mErrorResult);
return;
}
aPromise->MaybeResolve(mFiles);
}
+ void
+ RunCallback(GetFilesCallback* aCallback)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mListingCompleted);
+ MOZ_ASSERT(aCallback);
+
+ aCallback->Callback(mErrorResult, mFiles);
+ }
+
nsCOMPtr<nsIGlobalObject> mGlobal;
bool mRecursiveFlag;
bool mListingCompleted;
nsString mDirectoryPath;
// We populate this array in the I/O thread with the paths of the Files that
// we want to send as result to the promise objects.
@@ -523,16 +568,88 @@ private:
// This is the real File sequence that we expose via Promises.
Sequence<RefPtr<File>> mFiles;
// Error code to propagate.
nsresult mErrorResult;
nsTArray<RefPtr<Promise>> mPromises;
+ nsTArray<RefPtr<GetFilesCallback>> mCallbacks;
+};
+
+// An helper class for the dispatching of the 'change' event.
+class DispatchChangeEventCallback final : public GetFilesCallback
+{
+public:
+ explicit DispatchChangeEventCallback(HTMLInputElement* aInputElement)
+ : mInputElement(aInputElement)
+ {
+ MOZ_ASSERT(aInputElement);
+ }
+
+ virtual void
+ Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) override
+ {
+ nsTArray<OwningFileOrDirectory> array;
+ for (uint32_t i = 0; i < aFiles.Length(); ++i) {
+ OwningFileOrDirectory* element = array.AppendElement();
+ element->SetAsFile() = aFiles[i];
+ }
+
+ mInputElement->SetFilesOrDirectories(array, true);
+ NS_WARN_IF(NS_FAILED(DispatchEvents()));
+ }
+
+ nsresult
+ DispatchEvents()
+ {
+ nsresult rv = NS_OK;
+ rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(mInputElement.get()),
+ NS_LITERAL_STRING("input"), true,
+ false);
+ NS_WARN_IF(NS_FAILED(rv));
+
+ rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(mInputElement.get()),
+ NS_LITERAL_STRING("change"), true,
+ false);
+
+ return rv;
+ }
+
+private:
+ RefPtr<HTMLInputElement> mInputElement;
+};
+
+// This callback is used for postponing the calling of SetFilesOrDirectories
+// when the exploration of the directory is completed.
+class AfterSetFilesOrDirectoriesCallback : public GetFilesCallback
+{
+public:
+ AfterSetFilesOrDirectoriesCallback(HTMLInputElement* aInputElement,
+ bool aSetValueChanged)
+ : mInputElement(aInputElement)
+ , mSetValueChanged(aSetValueChanged)
+ {
+ MOZ_ASSERT(aInputElement);
+ }
+
+ void
+ Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) override
+ {
+ if (NS_SUCCEEDED(aStatus)) {
+ mInputElement->AfterSetFilesOrDirectoriesInternal(mSetValueChanged);
+ }
+ }
+
+private:
+ RefPtr<HTMLInputElement> mInputElement;
+ bool mSetValueChanged;
};
class HTMLInputElementState final : public nsISupports
{
public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_ELEMENT_STATE_IID)
NS_DECL_ISUPPORTS
@@ -857,29 +974,32 @@ HTMLInputElement::nsFilePickerShownCallb
mInput->OwnerDoc(), lastUsedDir);
}
// The text control frame (if there is one) isn't going to send a change
// event because it will think this is done by a script.
// So, we can safely send one by ourself.
mInput->SetFilesOrDirectories(newFilesOrDirectories, true);
- nsresult rv = NS_OK;
- rv = nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
- static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
- NS_LITERAL_STRING("input"), true,
- false);
- NS_WARN_IF(NS_FAILED(rv));
-
- rv = nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
- static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
- NS_LITERAL_STRING("change"), true,
- false);
-
- return rv;
+ RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
+ new DispatchChangeEventCallback(mInput);
+
+ if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+ mInput->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
+ ErrorResult error;
+ GetFilesHelper* helper = mInput->GetOrCreateGetFilesHelper(true, error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+
+ helper->AddCallback(dispatchChangeEventCallback);
+ return NS_OK;
+ }
+
+ return dispatchChangeEventCallback->DispatchEvents();
}
NS_IMPL_ISUPPORTS(HTMLInputElement::nsFilePickerShownCallback,
nsIFilePickerShownCallback)
class nsColorPickerShownCallback final
: public nsIColorPickerShownCallback
{
@@ -2913,16 +3033,29 @@ HTMLInputElement::SetFiles(nsIDOMFileLis
}
AfterSetFilesOrDirectories(aSetValueChanged);
}
void
HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged)
{
+ if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
+ // This will call AfterSetFilesOrDirectoriesInternal eventually.
+ ExploreDirectoryRecursively(aSetValueChanged);
+ return;
+ }
+
+ AfterSetFilesOrDirectoriesInternal(aSetValueChanged);
+}
+
+void
+HTMLInputElement::AfterSetFilesOrDirectoriesInternal(bool aSetValueChanged)
+{
// No need to flush here, if there's no frame at this point we
// don't need to force creation of one just to tell it about this
// new value. We just want the display to update as needed.
nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
if (formControlFrame) {
nsAutoString readableValue;
GetDisplayFileName(readableValue);
formControlFrame->SetFormProperty(nsGkAtoms::value, readableValue);
@@ -2975,17 +3108,19 @@ HTMLInputElement::FireChangeEventIfNeede
FileList*
HTMLInputElement::GetFiles()
{
if (mType != NS_FORM_INPUT_FILE) {
return nullptr;
}
if (Preferences::GetBool("dom.input.dirpicker", false) &&
- HasAttr(kNameSpaceID_None, nsGkAtoms::directory)) {
+ HasAttr(kNameSpaceID_None, nsGkAtoms::directory) &&
+ (!Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) ||
+ !HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
return nullptr;
}
if (!mFileList) {
mFileList = new FileList(static_cast<nsIContent*>(this));
UpdateFileList();
}
@@ -4065,18 +4200,20 @@ HTMLInputElement::MaybeInitPickers(Event
// If the user clicked on the "Choose folder..." button we open the
// directory picker, else we open the file picker.
FilePickerType type = FILE_PICKER_FILE;
nsCOMPtr<nsIContent> target =
do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
if (target &&
target->GetParent() == this &&
target->IsRootOfNativeAnonymousSubtree() &&
- target->HasAttr(kNameSpaceID_None, nsGkAtoms::directory)) {
- MOZ_ASSERT(Preferences::GetBool("dom.input.dirpicker", false),
+ (target->HasAttr(kNameSpaceID_None, nsGkAtoms::directory) ||
+ target->HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
+ MOZ_ASSERT(Preferences::GetBool("dom.input.dirpicker", false) ||
+ Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false),
"No API or UI should have been exposed to allow this code to "
"be reached");
type = FILE_PICKER_DIRECTORY;
}
return InitFilePicker(type);
}
if (mType == NS_FORM_INPUT_COLOR) {
return InitColorPicker();
@@ -5276,17 +5413,18 @@ nsChangeHint
HTMLInputElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
int32_t aModType) const
{
nsChangeHint retval =
nsGenericHTMLFormElementWithState::GetAttributeChangeHint(aAttribute, aModType);
if (aAttribute == nsGkAtoms::type ||
// The presence or absence of the 'directory' attribute determines what
// buttons we show for type=file.
- aAttribute == nsGkAtoms::directory) {
+ aAttribute == nsGkAtoms::directory ||
+ aAttribute == nsGkAtoms::webkitdirectory) {
retval |= NS_STYLE_HINT_FRAMECHANGE;
} else if (mType == NS_FORM_INPUT_IMAGE &&
(aAttribute == nsGkAtoms::alt ||
aAttribute == nsGkAtoms::value)) {
// We might need to rebuild our alt text. Just go ahead and
// reconstruct our frame. This should be quite rare..
retval |= NS_STYLE_HINT_FRAMECHANGE;
} else if (aAttribute == nsGkAtoms::value) {
@@ -5412,51 +5550,28 @@ HTMLInputElement::GetFilesAndDirectories
already_AddRefed<Promise>
HTMLInputElement::GetFiles(bool aRecursiveFlag, ErrorResult& aRv)
{
if (mType != NS_FORM_INPUT_FILE) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return nullptr;
}
+ GetFilesHelper* helper = GetOrCreateGetFilesHelper(aRecursiveFlag, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ MOZ_ASSERT(helper);
+
nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
MOZ_ASSERT(global);
if (!global) {
return nullptr;
}
- RefPtr<GetFilesHelper> helper;
- if (aRecursiveFlag) {
- if (!mGetFilesRecursiveHelper) {
- mGetFilesRecursiveHelper =
- GetFilesHelper::Create(global,
- GetFilesOrDirectoriesInternal(),
- aRecursiveFlag, aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- }
-
- helper = mGetFilesRecursiveHelper;
- } else {
- if (!mGetFilesNonRecursiveHelper) {
- mGetFilesNonRecursiveHelper =
- GetFilesHelper::Create(global,
- GetFilesOrDirectoriesInternal(),
- aRecursiveFlag, aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- }
-
- helper = mGetFilesNonRecursiveHelper;
- }
-
- MOZ_ASSERT(helper);
-
RefPtr<Promise> p = Promise::Create(global, aRv);
if (aRv.Failed()) {
return nullptr;
}
helper->AddPromise(p);
return p.forget();
}
@@ -7967,12 +8082,66 @@ HTMLInputElement::ClearGetFilesHelpers()
}
if (mGetFilesNonRecursiveHelper) {
mGetFilesNonRecursiveHelper->Unlink();
mGetFilesNonRecursiveHelper = nullptr;
}
}
+GetFilesHelper*
+HTMLInputElement::GetOrCreateGetFilesHelper(bool aRecursiveFlag,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
+ MOZ_ASSERT(global);
+ if (!global) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ if (aRecursiveFlag) {
+ if (!mGetFilesRecursiveHelper) {
+ mGetFilesRecursiveHelper =
+ GetFilesHelper::Create(global,
+ GetFilesOrDirectoriesInternal(),
+ aRecursiveFlag, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ return mGetFilesRecursiveHelper;
+ }
+
+ if (!mGetFilesNonRecursiveHelper) {
+ mGetFilesNonRecursiveHelper =
+ GetFilesHelper::Create(global,
+ GetFilesOrDirectoriesInternal(),
+ aRecursiveFlag, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ return mGetFilesNonRecursiveHelper;
+}
+
+void
+HTMLInputElement::ExploreDirectoryRecursively(bool aSetValueChanged)
+{
+ ErrorResult rv;
+ GetFilesHelper* helper = GetOrCreateGetFilesHelper(true /* recursionFlag */,
+ rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ AfterSetFilesOrDirectoriesInternal(aSetValueChanged);
+ return;
+ }
+
+ RefPtr<AfterSetFilesOrDirectoriesCallback> callback =
+ new AfterSetFilesOrDirectoriesCallback(this, aSetValueChanged);
+ helper->AddCallback(callback);
+}
+
} // namespace dom
} // namespace mozilla
#undef NS_ORIGINAL_CHECKED_VALUE
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -32,17 +32,19 @@ class nsIRadioVisitor;
namespace mozilla {
class EventChainPostVisitor;
class EventChainPreVisitor;
namespace dom {
+class AfterSetFilesOrDirectoriesRunnable;
class Date;
+class DispatchChangeEventCallback;
class File;
class FileList;
class GetFilesHelper;
/**
* A class we use to create a singleton object that is used to keep track of
* the last directory from which the user has picked files (via
* <input type=file>) on a per-domain basis. The implementation uses
@@ -102,16 +104,19 @@ public:
class HTMLInputElement final : public nsGenericHTMLFormElementWithState,
public nsImageLoadingContent,
public nsIDOMHTMLInputElement,
public nsITextControlElement,
public nsIPhonetic,
public nsIDOMNSEditableElement,
public nsIConstraintValidation
{
+ friend class AfterSetFilesOrDirectoriesCallback;
+ friend class DispatchChangeEventCallback;
+
public:
using nsIConstraintValidation::GetValidationMessage;
using nsIConstraintValidation::CheckValidity;
using nsIConstraintValidation::ReportValidity;
using nsIConstraintValidation::WillValidate;
using nsIConstraintValidation::Validity;
using nsGenericHTMLFormElementWithState::GetForm;
@@ -695,16 +700,26 @@ public:
return HasAttr(kNameSpaceID_None, nsGkAtoms::directory);
}
void SetDirectoryAttr(bool aValue, ErrorResult& aRv)
{
SetHTMLBoolAttr(nsGkAtoms::directory, aValue, aRv);
}
+ bool WebkitDirectoryAttr() const
+ {
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory);
+ }
+
+ void SetWebkitDirectoryAttr(bool aValue, ErrorResult& aRv)
+ {
+ SetHTMLBoolAttr(nsGkAtoms::webkitdirectory, aValue, aRv);
+ }
+
bool IsFilesAndDirectoriesSupported() const;
already_AddRefed<Promise> GetFilesAndDirectories(ErrorResult& aRv);
already_AddRefed<Promise> GetFiles(bool aRecursiveFlag, ErrorResult& aRv);
void ChooseDirectory(ErrorResult& aRv);
@@ -933,18 +948,26 @@ protected:
/**
* Update mFileList with the currently selected file.
*/
void UpdateFileList();
/**
* Called after calling one of the SetFilesOrDirectories() functions.
+ * This method can explore the directory recursively if needed.
*/
void AfterSetFilesOrDirectories(bool aSetValueChanged);
+ void AfterSetFilesOrDirectoriesInternal(bool aSetValueChanged);
+
+ /**
+ * Recursively explore the directory and populate mFileOrDirectories correctly
+ * for webkitdirectory.
+ */
+ void ExploreDirectoryRecursively(bool aSetValuechanged);
/**
* Determine whether the editor needs to be initialized explicitly for
* a particular event.
*/
bool NeedToInitializeEditorForEvent(EventChainPreVisitor& aVisitor) const;
/**
@@ -1251,16 +1274,19 @@ protected:
* Use this function before trying to open a picker.
* It checks if the page is allowed to open a new pop-up.
* If it returns true, you should not create the picker.
*
* @return true if popup should be blocked, false otherwise
*/
bool IsPopupBlocked() const;
+ GetFilesHelper* GetOrCreateGetFilesHelper(bool aRecursiveFlag,
+ ErrorResult& aRv);
+
void ClearGetFilesHelpers();
nsCOMPtr<nsIControllers> mControllers;
/*
* In mInputData, the mState field is used if IsSingleLineTextControl returns
* true and mValue is used otherwise. We have to be careful when handling it
* on a type change.
--- a/dom/webidl/Directory.webidl
+++ b/dom/webidl/Directory.webidl
@@ -10,17 +10,20 @@
* path should be a descendent path like "path/to/file.txt" and not contain a
* segment of ".." or ".". So the paths aren't allowed to walk up the directory
* tree. For example, paths like "../foo", "..", "/foo/bar" or "foo/../bar" are
* not allowed.
*
* http://w3c.github.io/filesystem-api/#idl-def-Directory
* https://microsoftedge.github.io/directory-upload/proposal.html#directory-interface
*/
-[Exposed=(Window,Worker)]
+
+// This chromeConstructor is used by the MockFilePicker for testing only.
+[ChromeConstructor(DOMString path),
+ Exposed=(Window,Worker)]
interface Directory {
/*
* The leaf name of the directory.
*/
[Throws]
readonly attribute DOMString name;
/*
--- a/dom/webidl/HTMLInputElement.webidl
+++ b/dom/webidl/HTMLInputElement.webidl
@@ -204,16 +204,19 @@ partial interface HTMLInputElement {
[Throws, Pref="dom.input.dirpicker"]
Promise<sequence<(File or Directory)>> getFilesAndDirectories();
[Throws, Pref="dom.input.dirpicker"]
Promise<sequence<File>> getFiles(optional boolean recursiveFlag = false);
[Throws, Pref="dom.input.dirpicker"]
void chooseDirectory();
+
+ [Pref="dom.webkitBlink.dirPicker.enabled", BinaryName="WebkitDirectoryAttr", SetterThrows]
+ attribute boolean webkitdirectory;
};
[NoInterfaceObject]
interface MozPhonetic {
[Pure, ChromeOnly]
readonly attribute DOMString phonetic;
};
--- a/testing/specialpowers/content/MockFilePicker.jsm
+++ b/testing/specialpowers/content/MockFilePicker.jsm
@@ -98,16 +98,21 @@ this.MockFilePicker = {
},
useBlobFile: function() {
var blob = new this.window.Blob([]);
var file = new this.window.File([blob], 'helloworld.txt', { type: 'plain/text' });
this.returnFiles = [file];
},
+ useDirectory: function(aPath) {
+ var directory = new this.window.Directory(aPath);
+ this.returnFiles = [directory];
+ },
+
isNsIFile: function(aFile) {
let ret = false;
try {
if (aFile.QueryInterface(Ci.nsIFile))
ret = true;
} catch(e) {}
return ret;