Bug 894840, part 4 - Add an openDirectoryPicker() method to HTMLInputElement. r=mounir, r=khuey
authorJonathan Watt <jwatt@jwatt.org>
Thu, 22 Aug 2013 10:43:26 +0100
changeset 153218 be3663c7c734fb4681ed967bee9b19b150ceae44
parent 153217 fb91f8e9fbaded42b102fbfa58acc51ef1a8f670
child 153219 e5e6c3bb26b359ddd0c02a37a6f992e2b3cbaf09
push id4254
push userakeybl@mozilla.com
push dateTue, 17 Sep 2013 14:18:33 +0000
treeherdermozilla-aurora@9edd56e694b0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmounir, khuey
bugs894840
milestone26.0a1
Bug 894840, part 4 - Add an openDirectoryPicker() method to HTMLInputElement. r=mounir, r=khuey
content/html/content/src/HTMLInputElement.cpp
content/html/content/src/HTMLInputElement.h
dom/webidl/HTMLInputElement.webidl
--- a/content/html/content/src/HTMLInputElement.cpp
+++ b/content/html/content/src/HTMLInputElement.cpp
@@ -309,74 +309,303 @@ UploadLastDir::ContentPrefCallback::Hand
 NS_IMETHODIMP
 UploadLastDir::ContentPrefCallback::HandleError(nsresult error)
 {
   // HandleCompletion is always called (even with HandleError was called),
   // so we don't need to do anything special here.
   return NS_OK;
 }
 
+namespace {
+
+/**
+ * This enumerator returns nsDOMFileFile objects after wrapping a single
+ * nsIFile representing a directory. It enumerates the files under that
+ * directory and its subdirectories as a flat list of files, ignoring/skipping
+ * over symbolic links.
+ *
+ * The enumeration involves I/O, so this class must NOT be used on the main
+ * thread or else the main thread could be blocked for a very long time.
+ *
+ * This enumerator does not walk the directory tree breadth-first, but it also
+ * is not guaranteed to walk it depth-first either (since it uses
+ * nsIFile::GetDirectoryEntries, which is not guaranteed to group a directory's
+ * subdirectories at the beginning of the list that it returns).
+ */
+class DirPickerRecursiveFileEnumerator MOZ_FINAL
+  : public nsISimpleEnumerator
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  DirPickerRecursiveFileEnumerator(nsIFile* aTopDir)
+    : mTopDir(aTopDir)
+  {
+    MOZ_ASSERT(!NS_IsMainThread(), "This class blocks on I/O!");
+
+#ifdef DEBUG
+    {
+      bool isDir;
+      aTopDir->IsDirectory(&isDir);
+      MOZ_ASSERT(isDir);
+    }
+#endif
+
+    nsCOMPtr<nsISimpleEnumerator> entries;
+    if (NS_SUCCEEDED(mTopDir->GetDirectoryEntries(getter_AddRefs(entries))) &&
+        entries) {
+      mDirEnumeratorStack.AppendElement(entries);
+      LookupAndCacheNext();
+    }
+  }
+
+  NS_IMETHOD
+  GetNext(nsISupports** aResult)
+  {
+    MOZ_ASSERT(!NS_IsMainThread(),
+               "Walking the directory tree involves I/O, so using this "
+               "enumerator can block a thread for a long time!");
+
+    if (!mNextFile) {
+      return NS_ERROR_FAILURE;
+    }
+    nsRefPtr<nsDOMFileFile> domFile = new nsDOMFileFile(mNextFile);
+    *aResult = static_cast<nsIDOMFile*>(domFile.forget().get());
+    LookupAndCacheNext();
+    return NS_OK;
+  }
+
+  NS_IMETHOD
+  HasMoreElements(bool* aResult)
+  {
+    *aResult = !!mNextFile;
+    return NS_OK;
+  }
+
+private:
+
+  void
+  LookupAndCacheNext()
+  {
+    for (;;) {
+      if (mDirEnumeratorStack.IsEmpty()) {
+        mNextFile = nullptr;
+        break;
+      }
+
+      nsISimpleEnumerator* currentDirEntries =
+        mDirEnumeratorStack.LastElement();
+
+      bool hasMore;
+      DebugOnly<nsresult> rv = currentDirEntries->HasMoreElements(&hasMore);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+      if (!hasMore) {
+        mDirEnumeratorStack.RemoveElementAt(mDirEnumeratorStack.Length() - 1);
+        continue;
+      }
+
+      nsCOMPtr<nsISupports> entry;
+      rv = currentDirEntries->GetNext(getter_AddRefs(entry));
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+      nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
+      MOZ_ASSERT(file);
+
+      bool isLink, isSpecial;
+      file->IsSymlink(&isLink);
+      file->IsSpecial(&isSpecial);
+      if (isLink || isSpecial) {
+        continue;
+      }
+
+      bool isDir;
+      file->IsDirectory(&isDir);
+      if (isDir) {
+        nsCOMPtr<nsISimpleEnumerator> subDirEntries;
+        rv = file->GetDirectoryEntries(getter_AddRefs(subDirEntries));
+        MOZ_ASSERT(NS_SUCCEEDED(rv) && subDirEntries);
+        mDirEnumeratorStack.AppendElement(subDirEntries);
+        continue;
+      }
+
+#ifdef DEBUG
+      {
+        bool isFile;
+        file->IsFile(&isFile);
+        MOZ_ASSERT(isFile);
+      }
+#endif
+
+      mNextFile.swap(file);
+      return;
+    }
+  }
+
+private:
+  nsCOMPtr<nsIFile> mTopDir;
+  nsCOMPtr<nsIFile> mNextFile;
+  nsTArray<nsCOMPtr<nsISimpleEnumerator> > mDirEnumeratorStack;
+};
+
+NS_IMPL_ISUPPORTS1(DirPickerRecursiveFileEnumerator, nsISimpleEnumerator)
+
+class DirPickerBuildFileListTask MOZ_FINAL
+  : public nsRunnable
+{
+public:
+  DirPickerBuildFileListTask(HTMLInputElement* aInput, nsIFile* aTopDir)
+    : mInput(aInput)
+    , mTopDir(aTopDir)
+  {}
+
+  NS_IMETHOD Run() {
+    if (!NS_IsMainThread()) {
+      // Build up list of nsDOMFileFile objects on this dedicated thread:
+      nsCOMPtr<nsISimpleEnumerator> iter =
+        new DirPickerRecursiveFileEnumerator(mTopDir);
+      bool hasMore = true;
+      nsCOMPtr<nsISupports> tmp;
+      while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
+        iter->GetNext(getter_AddRefs(tmp));
+        nsCOMPtr<nsIDOMFile> domFile = do_QueryInterface(tmp);
+        MOZ_ASSERT(domFile);
+        mFileList.AppendElement(domFile);
+      }
+      return NS_DispatchToMainThread(this);
+    }
+
+    // Now back on the main thread, set the list on our HTMLInputElement:
+    if (mFileList.IsEmpty()) {
+      return NS_OK;
+    }
+    // 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->SetFiles(mFileList, true);
+    nsresult rv =
+      nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
+                                           static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
+                                           NS_LITERAL_STRING("change"), true,
+                                           false);
+    // Clear mInput to make sure that it can't lose its last strong ref off the
+    // main thread (which may happen if our dtor runs off the main thread)!
+    mInput = nullptr;
+    return rv;
+  }
+
+private:
+  nsRefPtr<HTMLInputElement> mInput;
+  nsCOMPtr<nsIFile> mTopDir;
+  nsTArray<nsCOMPtr<nsIDOMFile> > mFileList;
+};
+
+static already_AddRefed<nsIFile>
+DOMFileToLocalFile(nsIDOMFile* aDomFile)
+{
+  nsString path;
+  nsresult rv = aDomFile->GetMozFullPathInternal(path);
+  if (NS_FAILED(rv) || path.IsEmpty()) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIFile> localFile;
+  rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true,
+                             getter_AddRefs(localFile));
+  NS_ENSURE_SUCCESS(rv, nullptr);
+
+  return localFile.forget();
+}
+
+} // anonymous namespace
+
 NS_IMETHODIMP
 HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult)
 {
   if (aResult == nsIFilePicker::returnCancel) {
     return NS_OK;
   }
 
   int16_t mode;
   mFilePicker->GetMode(&mode);
-  bool multi = mode == static_cast<int16_t>(nsIFilePicker::modeOpenMultiple);
+
+  if (mode == static_cast<int16_t>(nsIFilePicker::modeGetFolder)) {
+    // Directory picking is different, since we still need to do more I/O to
+    // build up the list of nsDOMFileFile objects. Since this may block for a
+    // long time, we need to build the list off on another dedicated thread to
+    // avoid blocking any other activities that the browser is carrying out.
+
+    // The user selected this directory, so we always save this dir, even if
+    // no files are found under it.
+    nsCOMPtr<nsIFile> pickedDir;
+    mFilePicker->GetFile(getter_AddRefs(pickedDir));
+
+#ifdef DEBUG
+    {
+      bool isDir;
+      pickedDir->IsDirectory(&isDir);
+      MOZ_ASSERT(isDir);
+    }
+#endif
+
+    HTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(
+      mInput->OwnerDoc(), pickedDir);
+
+    nsCOMPtr<nsIEventTarget> target
+      = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+    NS_ASSERTION(target, "Must have stream transport service");
+
+    // DirPickerBuildFileListTask takes care of calling SetFiles() and
+    // dispatching the "change" event.
+    nsRefPtr<DirPickerBuildFileListTask> event =
+      new DirPickerBuildFileListTask(mInput.get(), pickedDir.get());
+    return target->Dispatch(event, NS_DISPATCH_NORMAL);
+  }
 
   // Collect new selected filenames
   nsTArray<nsCOMPtr<nsIDOMFile> > newFiles;
-  if (multi) {
+  if (mode == static_cast<int16_t>(nsIFilePicker::modeOpenMultiple)) {
     nsCOMPtr<nsISimpleEnumerator> iter;
     nsresult rv = mFilePicker->GetDomfiles(getter_AddRefs(iter));
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (!iter) {
       return NS_OK;
     }
 
     nsCOMPtr<nsISupports> tmp;
-    bool prefSaved = false;
-    bool loop = true;
-
-    while (NS_SUCCEEDED(iter->HasMoreElements(&loop)) && loop) {
+    bool hasMore = true;
+
+    while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
       iter->GetNext(getter_AddRefs(tmp));
       nsCOMPtr<nsIDOMFile> domFile = do_QueryInterface(tmp);
       MOZ_ASSERT(domFile);
-
       newFiles.AppendElement(domFile);
-
-      if (!prefSaved) {
-        // Store the last used directory using the content pref service
-        HTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(
-          mInput->OwnerDoc(), domFile);
-        prefSaved = true;
-      }
     }
-  }
-  else {
+  } else {
+    MOZ_ASSERT(mode == static_cast<int16_t>(nsIFilePicker::modeOpen));
     nsCOMPtr<nsIDOMFile> domFile;
     nsresult rv = mFilePicker->GetDomfile(getter_AddRefs(domFile));
     NS_ENSURE_SUCCESS(rv, rv);
     if (domFile) {
       newFiles.AppendElement(domFile);
-
-      // Store the last used directory using the content pref service
-      HTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(
-        mInput->OwnerDoc(), domFile);
     }
   }
 
   if (newFiles.IsEmpty()) {
     return NS_OK;
   }
 
+  // Store the last used directory using the content pref service:
+  nsCOMPtr<nsIFile> file = DOMFileToLocalFile(newFiles[0]);
+  nsCOMPtr<nsIFile> lastUsedDir;
+  file->GetParent(getter_AddRefs(lastUsedDir));
+  HTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(
+    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->SetFiles(newFiles, true);
   return nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
                                               static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
                                               NS_LITERAL_STRING("change"), true,
                                               false);
@@ -538,17 +767,17 @@ HTMLInputElement::InitColorPicker()
 
   nsCOMPtr<nsIColorPickerShownCallback> callback =
     new nsColorPickerShownCallback(this, colorPicker);
 
   return colorPicker->Open(callback);
 }
 
 nsresult
-HTMLInputElement::InitFilePicker()
+HTMLInputElement::InitFilePicker(FilePickerType aType)
 {
   // Get parent nsPIDOMWindow object.
   nsCOMPtr<nsIDocument> doc = OwnerDoc();
 
   nsCOMPtr<nsPIDOMWindow> win = doc->GetWindow();
   if (!win) {
     return NS_ERROR_FAILURE;
   }
@@ -562,39 +791,48 @@ HTMLInputElement::InitFilePicker()
   nsXPIDLString title;
   nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
                                      "FileUpload", title);
 
   nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1");
   if (!filePicker)
     return NS_ERROR_FAILURE;
 
-  bool multi = HasAttr(kNameSpaceID_None, nsGkAtoms::multiple);
-
-  nsresult rv = filePicker->Init(win, title,
-                                 multi
-                                  ? static_cast<int16_t>(nsIFilePicker::modeOpenMultiple)
-                                  : static_cast<int16_t>(nsIFilePicker::modeOpen));
+  int16_t mode;
+
+  if (aType == FILE_PICKER_DIRECTORY) {
+    mode = static_cast<int16_t>(nsIFilePicker::modeGetFolder);
+  } else if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
+    mode = static_cast<int16_t>(nsIFilePicker::modeOpenMultiple);
+  } else {
+    mode = static_cast<int16_t>(nsIFilePicker::modeOpen);
+  }
+
+  nsresult rv = filePicker->Init(win, title, mode);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (HasAttr(kNameSpaceID_None, nsGkAtoms::accept)) {
+  // Native directory pickers ignore file type filters, so we don't spend
+  // cycles adding them for FILE_PICKER_DIRECTORY.
+  if (HasAttr(kNameSpaceID_None, nsGkAtoms::accept) &&
+      aType != FILE_PICKER_DIRECTORY) {
     SetFilePickerFiltersFromAccept(filePicker);
   } else {
     filePicker->AppendFilters(nsIFilePicker::filterAll);
   }
 
   // Set default directry and filename
   nsAutoString defaultName;
 
   const nsTArray<nsCOMPtr<nsIDOMFile> >& oldFiles = GetFilesInternal();
 
   nsCOMPtr<nsIFilePickerShownCallback> callback =
     new HTMLInputElement::nsFilePickerShownCallback(this, filePicker);
 
-  if (!oldFiles.IsEmpty()) {
+  if (!oldFiles.IsEmpty() &&
+      aType != FILE_PICKER_DIRECTORY) {
     nsString path;
 
     oldFiles[0]->GetMozFullPathInternal(path);
 
     nsCOMPtr<nsIFile> localFile;
     rv = NS_NewLocalFile(path, false, getter_AddRefs(localFile));
 
     if (NS_SUCCEEDED(rv)) {
@@ -616,17 +854,16 @@ HTMLInputElement::InitFilePicker()
       }
     }
 
     return filePicker->Open(callback);
   }
 
   HTMLInputElement::gUploadLastDir->FetchDirectoryAndDisplayPicker(doc, filePicker, callback);
   return NS_OK;
-
 }
 
 #define CPS_PREF_NAME NS_LITERAL_STRING("browser.upload.lastDir")
 
 NS_IMPL_ISUPPORTS2(UploadLastDir, nsIObserver, nsISupportsWeakReference)
 
 void
 HTMLInputElement::InitUploadLastDir() {
@@ -675,54 +912,39 @@ UploadLastDir::FetchDirectoryAndDisplayP
   docURI->GetSpec(cstrSpec);
   NS_ConvertUTF8toUTF16 spec(cstrSpec);
 
   contentPrefService->GetByDomainAndName(spec, CPS_PREF_NAME, loadContext, prefCallback);
   return NS_OK;
 }
 
 nsresult
-UploadLastDir::StoreLastUsedDirectory(nsIDocument* aDoc, nsIDOMFile* aDomFile)
+UploadLastDir::StoreLastUsedDirectory(nsIDocument* aDoc, nsIFile* aDir)
 {
   NS_PRECONDITION(aDoc, "aDoc is null");
-  NS_PRECONDITION(aDomFile, "aDomFile is null");
-
-  nsString path;
-  nsresult rv = aDomFile->GetMozFullPathInternal(path);
-  if (NS_FAILED(rv) || path.IsEmpty()) {
+  if (!aDir) {
     return NS_OK;
   }
 
-  nsCOMPtr<nsIFile> localFile;
-  rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true,
-                             getter_AddRefs(localFile));
-  NS_ENSURE_SUCCESS(rv, rv);
-
   nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
   NS_PRECONDITION(docURI, "docURI is null");
 
-  nsCOMPtr<nsIFile> parentFile;
-  localFile->GetParent(getter_AddRefs(parentFile));
-  if (!parentFile) {
-    return NS_OK;
-  }
-
   // Attempt to get the CPS, if it's not present we'll just return
   nsCOMPtr<nsIContentPrefService2> contentPrefService =
     do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
   if (!contentPrefService)
     return NS_ERROR_NOT_AVAILABLE;
 
   nsAutoCString cstrSpec;
   docURI->GetSpec(cstrSpec);
   NS_ConvertUTF8toUTF16 spec(cstrSpec);
 
   // Find the parent of aFile, and store it
   nsString unicodePath;
-  parentFile->GetPath(unicodePath);
+  aDir->GetPath(unicodePath);
   if (unicodePath.IsEmpty()) // nothing to do
     return NS_OK;
   nsCOMPtr<nsIWritableVariant> prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID);
   if (!prefValue)
     return NS_ERROR_OUT_OF_MEMORY;
   prefValue->SetAsAString(unicodePath);
 
   nsCOMPtr<nsISupports> container = aDoc->GetContainer();
@@ -2174,16 +2396,25 @@ HTMLInputElement::GetFiles()
   if (!mFileList) {
     mFileList = new nsDOMFileList(static_cast<nsIContent*>(this));
     UpdateFileList();
   }
 
   return mFileList;
 }
 
+void
+HTMLInputElement::OpenDirectoryPicker(ErrorResult& aRv)
+{
+  if (mType != NS_FORM_INPUT_FILE) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+  }
+  InitFilePicker(FILE_PICKER_DIRECTORY);
+}
+
 nsresult
 HTMLInputElement::UpdateFileList()
 {
   if (mFileList) {
     mFileList->Clear();
 
     const nsTArray<nsCOMPtr<nsIDOMFile> >& files = GetFilesInternal();
     for (uint32_t i = 0; i < files.Length(); ++i) {
@@ -2885,17 +3116,17 @@ HTMLInputElement::MaybeInitPickers(nsEve
   // A click is handled in the following cases:
   // - preventDefault() has not been called (or something similar);
   // - it's the left mouse button.
   // We do not prevent non-trusted click because authors can already use
   // .click(). However, the pickers will follow the rules of popup-blocking.
   if (NS_IS_MOUSE_LEFT_CLICK(aVisitor.mEvent) &&
       !aVisitor.mEvent->mFlags.mDefaultPrevented) {
     if (mType == NS_FORM_INPUT_FILE) {
-      return InitFilePicker();
+      return InitFilePicker(FILE_PICKER_FILE);
     }
     if (mType == NS_FORM_INPUT_COLOR) {
       return InitColorPicker();
     }
   }
   return NS_OK;
 }
 
--- a/content/html/content/src/HTMLInputElement.h
+++ b/content/html/content/src/HTMLInputElement.h
@@ -49,20 +49,19 @@ public:
   nsresult FetchDirectoryAndDisplayPicker(nsIDocument* aDoc,
                                           nsIFilePicker* aFilePicker,
                                           nsIFilePickerShownCallback* aFpCallback);
 
   /**
    * Store the last used directory for this location using the
    * content pref service, if it is available
    * @param aURI URI of the current page
-   * @param aDomFile file chosen by the user - the path to the parent of this
-   *        file will be stored
+   * @param aDir Parent directory of the file(s)/directory chosen by the user
    */
-  nsresult StoreLastUsedDirectory(nsIDocument* aDoc, nsIDOMFile* aDomFile);
+  nsresult StoreLastUsedDirectory(nsIDocument* aDoc, nsIFile* aDir);
 
   class ContentPrefCallback MOZ_FINAL : public nsIContentPrefCallback2
   {
     public:
     ContentPrefCallback(nsIFilePicker* aFilePicker, nsIFilePickerShownCallback* aFpCallback)
     : mFilePicker(aFilePicker)
     , mFpCallback(aFpCallback)
     { }
@@ -388,16 +387,18 @@ public:
   {
     SetHTMLBoolAttr(nsGkAtoms::disabled, aValue, aRv);
   }
 
   // XPCOM GetForm() is OK
 
   nsDOMFileList* GetFiles();
 
+  void OpenDirectoryPicker(ErrorResult& aRv);
+
   // XPCOM GetFormAction() is OK
   void SetFormAction(const nsAString& aValue, ErrorResult& aRv)
   {
     SetHTMLAttr(nsGkAtoms::formaction, aValue, aRv);
   }
 
   // XPCOM GetFormEnctype() is OK
   void SetFormEnctype(const nsAString& aValue, ErrorResult& aRv)
@@ -1075,17 +1076,21 @@ protected:
 
   /**
    * Some input type (color and file) let user choose a value using a picker:
    * this function checks if it is needed, and if so, open the corresponding
    * picker (color picker or file picker).
    */
   nsresult MaybeInitPickers(nsEventChainPostVisitor& aVisitor);
 
-  nsresult InitFilePicker();
+  enum FilePickerType {
+    FILE_PICKER_FILE,
+    FILE_PICKER_DIRECTORY
+  };
+  nsresult InitFilePicker(FilePickerType aType);
   nsresult InitColorPicker();
 
   /**
    * 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
--- a/dom/webidl/HTMLInputElement.webidl
+++ b/dom/webidl/HTMLInputElement.webidl
@@ -28,16 +28,18 @@ interface HTMLInputElement : HTMLElement
   [Pure]
            attribute boolean checked;
            // Bug 850337 - attribute DOMString dirName;
   [Pure, SetterThrows]
            attribute boolean disabled;
   readonly attribute HTMLFormElement? form;
   [Pure]
   readonly attribute FileList? files;
+  [Throws, Pref="dom.input.dirpicker"]
+  void openDirectoryPicker();
   [Pure, SetterThrows]
            attribute DOMString formAction;
   [Pure, SetterThrows]
            attribute DOMString formEnctype;
   [Pure, SetterThrows]
            attribute DOMString formMethod;
   [Pure, SetterThrows]
            attribute boolean formNoValidate;