summaryrefslogtreecommitdiffstats
path: root/dom/html/HTMLInputElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/html/HTMLInputElement.cpp')
-rw-r--r--dom/html/HTMLInputElement.cpp8806
1 files changed, 8806 insertions, 0 deletions
diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp
new file mode 100644
index 000000000..78f74ae0c
--- /dev/null
+++ b/dom/html/HTMLInputElement.cpp
@@ -0,0 +1,8806 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/HTMLInputElement.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/Date.h"
+#include "mozilla/dom/Directory.h"
+#include "mozilla/dom/HTMLFormSubmission.h"
+#include "mozilla/dom/FileSystemUtils.h"
+#include "mozilla/dom/GetFilesHelper.h"
+#include "nsAttrValueInlines.h"
+#include "nsCRTGlue.h"
+
+#include "nsIDOMHTMLInputElement.h"
+#include "nsITextControlElement.h"
+#include "nsIDOMNSEditableElement.h"
+#include "nsIRadioVisitor.h"
+#include "nsIPhonetic.h"
+
+#include "HTMLFormSubmissionConstants.h"
+#include "mozilla/Telemetry.h"
+#include "nsIControllers.h"
+#include "nsIStringBundle.h"
+#include "nsFocusManager.h"
+#include "nsColorControlFrame.h"
+#include "nsNumberControlFrame.h"
+#include "nsPIDOMWindow.h"
+#include "nsRepeatService.h"
+#include "nsContentCID.h"
+#include "nsIComponentManager.h"
+#include "nsIDOMHTMLFormElement.h"
+#include "mozilla/dom/ProgressEvent.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsPresContext.h"
+#include "nsMappedAttributes.h"
+#include "nsIFormControl.h"
+#include "nsIDocument.h"
+#include "nsIPresShell.h"
+#include "nsIFormControlFrame.h"
+#include "nsITextControlFrame.h"
+#include "nsIFrame.h"
+#include "nsRangeFrame.h"
+#include "nsIServiceManager.h"
+#include "nsError.h"
+#include "nsIEditor.h"
+#include "nsIIOService.h"
+#include "nsDocument.h"
+#include "nsAttrValueOrString.h"
+#include "nsDateTimeControlFrame.h"
+
+#include "nsPresState.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMNodeList.h"
+#include "nsIDOMHTMLCollection.h"
+#include "nsLinebreakConverter.h" //to strip out carriage returns
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsVariant.h"
+
+#include "nsIDOMMutationEvent.h"
+#include "mozilla/ContentEvents.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStates.h"
+#include "mozilla/InternalMutationEvent.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TouchEvents.h"
+
+#include "nsRuleData.h"
+#include <algorithm>
+
+// input type=radio
+#include "nsIRadioGroupContainer.h"
+
+// input type=file
+#include "mozilla/dom/FileSystemEntry.h"
+#include "mozilla/dom/FileSystem.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FileList.h"
+#include "nsIFile.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIContentPrefService.h"
+#include "nsIMIMEService.h"
+#include "nsIObserverService.h"
+#include "nsIPopupWindowManager.h"
+#include "nsGlobalWindow.h"
+
+// input type=image
+#include "nsImageLoadingContent.h"
+#include "imgRequestProxy.h"
+
+#include "mozAutoDocUpdate.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsContentUtils.h"
+#include "mozilla/dom/DirectionalityUtils.h"
+#include "nsRadioVisitor.h"
+#include "nsTextEditorState.h"
+
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/MathAlgorithms.h"
+
+#include "nsIIDNService.h"
+
+#include <limits>
+
+#include "nsIColorPicker.h"
+#include "nsIDatePicker.h"
+#include "nsIStringEnumerator.h"
+#include "HTMLSplitOnSpacesTokenizer.h"
+#include "nsIController.h"
+#include "nsIMIMEInfo.h"
+#include "nsFrameSelection.h"
+
+#include "nsIConsoleService.h"
+
+// input type=date
+#include "js/Date.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Input)
+
+// XXX align=left, hspace, vspace, border? other nav4 attrs
+
+static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID);
+
+// This must come outside of any namespace, or else it won't overload with the
+// double based version in nsMathUtils.h
+inline mozilla::Decimal
+NS_floorModulo(mozilla::Decimal x, mozilla::Decimal y)
+{
+ return (x - y * (x / y).floor());
+}
+
+namespace mozilla {
+namespace dom {
+
+// First bits are needed for the control type.
+#define NS_OUTER_ACTIVATE_EVENT (1 << 9)
+#define NS_ORIGINAL_CHECKED_VALUE (1 << 10)
+#define NS_NO_CONTENT_DISPATCH (1 << 11)
+#define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
+#define NS_CONTROL_TYPE(bits) ((bits) & ~( \
+ NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | NS_NO_CONTENT_DISPATCH | \
+ NS_ORIGINAL_INDETERMINATE_VALUE))
+
+// whether textfields should be selected once focused:
+// -1: no, 1: yes, 0: uninitialized
+static int32_t gSelectTextFieldOnFocus;
+UploadLastDir* HTMLInputElement::gUploadLastDir;
+
+static const nsAttrValue::EnumTable kInputTypeTable[] = {
+ { "button", NS_FORM_INPUT_BUTTON },
+ { "checkbox", NS_FORM_INPUT_CHECKBOX },
+ { "color", NS_FORM_INPUT_COLOR },
+ { "date", NS_FORM_INPUT_DATE },
+ { "datetime-local", NS_FORM_INPUT_DATETIME_LOCAL },
+ { "email", NS_FORM_INPUT_EMAIL },
+ { "file", NS_FORM_INPUT_FILE },
+ { "hidden", NS_FORM_INPUT_HIDDEN },
+ { "reset", NS_FORM_INPUT_RESET },
+ { "image", NS_FORM_INPUT_IMAGE },
+ { "month", NS_FORM_INPUT_MONTH },
+ { "number", NS_FORM_INPUT_NUMBER },
+ { "password", NS_FORM_INPUT_PASSWORD },
+ { "radio", NS_FORM_INPUT_RADIO },
+ { "range", NS_FORM_INPUT_RANGE },
+ { "search", NS_FORM_INPUT_SEARCH },
+ { "submit", NS_FORM_INPUT_SUBMIT },
+ { "tel", NS_FORM_INPUT_TEL },
+ { "text", NS_FORM_INPUT_TEXT },
+ { "time", NS_FORM_INPUT_TIME },
+ { "url", NS_FORM_INPUT_URL },
+ { "week", NS_FORM_INPUT_WEEK },
+ { nullptr, 0 }
+};
+
+// Default type is 'text'.
+static const nsAttrValue::EnumTable* kInputDefaultType = &kInputTypeTable[18];
+
+static const uint8_t NS_INPUT_INPUTMODE_AUTO = 0;
+static const uint8_t NS_INPUT_INPUTMODE_NUMERIC = 1;
+static const uint8_t NS_INPUT_INPUTMODE_DIGIT = 2;
+static const uint8_t NS_INPUT_INPUTMODE_UPPERCASE = 3;
+static const uint8_t NS_INPUT_INPUTMODE_LOWERCASE = 4;
+static const uint8_t NS_INPUT_INPUTMODE_TITLECASE = 5;
+static const uint8_t NS_INPUT_INPUTMODE_AUTOCAPITALIZED = 6;
+
+static const nsAttrValue::EnumTable kInputInputmodeTable[] = {
+ { "auto", NS_INPUT_INPUTMODE_AUTO },
+ { "numeric", NS_INPUT_INPUTMODE_NUMERIC },
+ { "digit", NS_INPUT_INPUTMODE_DIGIT },
+ { "uppercase", NS_INPUT_INPUTMODE_UPPERCASE },
+ { "lowercase", NS_INPUT_INPUTMODE_LOWERCASE },
+ { "titlecase", NS_INPUT_INPUTMODE_TITLECASE },
+ { "autocapitalized", NS_INPUT_INPUTMODE_AUTOCAPITALIZED },
+ { nullptr, 0 }
+};
+
+// Default inputmode value is "auto".
+static const nsAttrValue::EnumTable* kInputDefaultInputmode = &kInputInputmodeTable[0];
+
+const Decimal HTMLInputElement::kStepScaleFactorDate = Decimal(86400000);
+const Decimal HTMLInputElement::kStepScaleFactorNumberRange = Decimal(1);
+const Decimal HTMLInputElement::kStepScaleFactorTime = Decimal(1000);
+const Decimal HTMLInputElement::kStepScaleFactorMonth = Decimal(1);
+const Decimal HTMLInputElement::kStepScaleFactorWeek = Decimal(7 * 86400000);
+const Decimal HTMLInputElement::kDefaultStepBase = Decimal(0);
+const Decimal HTMLInputElement::kDefaultStepBaseWeek = Decimal(-259200000);
+const Decimal HTMLInputElement::kDefaultStep = Decimal(1);
+const Decimal HTMLInputElement::kDefaultStepTime = Decimal(60);
+const Decimal HTMLInputElement::kStepAny = Decimal(0);
+
+const double HTMLInputElement::kMinimumYear = 1;
+const double HTMLInputElement::kMaximumYear = 275760;
+const double HTMLInputElement::kMaximumWeekInMaximumYear = 37;
+const double HTMLInputElement::kMaximumDayInMaximumYear = 13;
+const double HTMLInputElement::kMaximumMonthInMaximumYear = 9;
+const double HTMLInputElement::kMaximumWeekInYear = 53;
+const double HTMLInputElement::kMsPerDay = 24 * 60 * 60 * 1000;
+
+#define NS_INPUT_ELEMENT_STATE_IID \
+{ /* dc3b3d14-23e2-4479-b513-7b369343e3a0 */ \
+ 0xdc3b3d14, \
+ 0x23e2, \
+ 0x4479, \
+ {0xb5, 0x13, 0x7b, 0x36, 0x93, 0x43, 0xe3, 0xa0} \
+}
+
+#define PROGRESS_STR "progress"
+static const uint32_t kProgressEventInterval = 50; // ms
+
+// An helper class for the dispatching of the 'change' event.
+// This class is used when the FilePicker finished its task (or when files and
+// directories are set by some chrome/test only method).
+// The task of this class is to postpone the dispatching of 'change' and 'input'
+// events at the end of the exploration of the directories.
+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);
+ Unused << 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_WARNING_ASSERTION(NS_SUCCEEDED(rv), "DispatchTrustedEvent failed");
+
+ rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(mInputElement.get()),
+ NS_LITERAL_STRING("change"), true,
+ false);
+
+ return rv;
+ }
+
+private:
+ RefPtr<HTMLInputElement> mInputElement;
+};
+
+class HTMLInputElementState final : public nsISupports
+{
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_INPUT_ELEMENT_STATE_IID)
+ NS_DECL_ISUPPORTS
+
+ bool IsCheckedSet()
+ {
+ return mCheckedSet;
+ }
+
+ bool GetChecked()
+ {
+ return mChecked;
+ }
+
+ void SetChecked(bool aChecked)
+ {
+ mChecked = aChecked;
+ mCheckedSet = true;
+ }
+
+ const nsString& GetValue()
+ {
+ return mValue;
+ }
+
+ void SetValue(const nsAString& aValue)
+ {
+ mValue = aValue;
+ }
+
+ void
+ GetFilesOrDirectories(nsPIDOMWindowInner* aWindow,
+ nsTArray<OwningFileOrDirectory>& aResult) const
+ {
+ for (uint32_t i = 0; i < mBlobImplsOrDirectoryPaths.Length(); ++i) {
+ if (mBlobImplsOrDirectoryPaths[i].mType == BlobImplOrDirectoryPath::eBlobImpl) {
+ RefPtr<File> file =
+ File::Create(aWindow,
+ mBlobImplsOrDirectoryPaths[i].mBlobImpl);
+ MOZ_ASSERT(file);
+
+ OwningFileOrDirectory* element = aResult.AppendElement();
+ element->SetAsFile() = file;
+ } else {
+ MOZ_ASSERT(mBlobImplsOrDirectoryPaths[i].mType == BlobImplOrDirectoryPath::eDirectoryPath);
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv =
+ NS_NewLocalFile(mBlobImplsOrDirectoryPaths[i].mDirectoryPath,
+ true, getter_AddRefs(file));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ RefPtr<Directory> directory = Directory::Create(aWindow, file);
+ MOZ_ASSERT(directory);
+
+ OwningFileOrDirectory* element = aResult.AppendElement();
+ element->SetAsDirectory() = directory;
+ }
+ }
+ }
+
+ void SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& aArray)
+ {
+ mBlobImplsOrDirectoryPaths.Clear();
+ for (uint32_t i = 0; i < aArray.Length(); ++i) {
+ if (aArray[i].IsFile()) {
+ BlobImplOrDirectoryPath* data = mBlobImplsOrDirectoryPaths.AppendElement();
+
+ data->mBlobImpl = aArray[i].GetAsFile()->Impl();
+ data->mType = BlobImplOrDirectoryPath::eBlobImpl;
+ } else {
+ MOZ_ASSERT(aArray[i].IsDirectory());
+ nsAutoString fullPath;
+ nsresult rv = aArray[i].GetAsDirectory()->GetFullRealPath(fullPath);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ BlobImplOrDirectoryPath* data =
+ mBlobImplsOrDirectoryPaths.AppendElement();
+
+ data->mDirectoryPath = fullPath;
+ data->mType = BlobImplOrDirectoryPath::eDirectoryPath;
+ }
+ }
+ }
+
+ HTMLInputElementState()
+ : mValue()
+ , mChecked(false)
+ , mCheckedSet(false)
+ {}
+
+ protected:
+ ~HTMLInputElementState() {}
+
+ nsString mValue;
+
+ struct BlobImplOrDirectoryPath
+ {
+ RefPtr<BlobImpl> mBlobImpl;
+ nsString mDirectoryPath;
+
+ enum {
+ eBlobImpl,
+ eDirectoryPath
+ } mType;
+ };
+
+ nsTArray<BlobImplOrDirectoryPath> mBlobImplsOrDirectoryPaths;
+
+ bool mChecked;
+ bool mCheckedSet;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(HTMLInputElementState, NS_INPUT_ELEMENT_STATE_IID)
+
+NS_IMPL_ISUPPORTS(HTMLInputElementState, HTMLInputElementState)
+
+HTMLInputElement::nsFilePickerShownCallback::nsFilePickerShownCallback(
+ HTMLInputElement* aInput, nsIFilePicker* aFilePicker)
+ : mFilePicker(aFilePicker)
+ , mInput(aInput)
+{
+}
+
+NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback, nsIContentPrefCallback2)
+
+NS_IMETHODIMP
+UploadLastDir::ContentPrefCallback::HandleCompletion(uint16_t aReason)
+{
+ nsCOMPtr<nsIFile> localFile;
+ nsAutoString prefStr;
+
+ if (aReason == nsIContentPrefCallback2::COMPLETE_ERROR || !mResult) {
+ prefStr = Preferences::GetString("dom.input.fallbackUploadDir");
+ if (prefStr.IsEmpty()) {
+ // If no custom directory was set through the pref, default to
+ // "desktop" directory for each platform.
+ NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(localFile));
+ }
+ }
+
+ if (!localFile) {
+ if (prefStr.IsEmpty() && mResult) {
+ nsCOMPtr<nsIVariant> pref;
+ mResult->GetValue(getter_AddRefs(pref));
+ pref->GetAsAString(prefStr);
+ }
+ localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+ localFile->InitWithPath(prefStr);
+ }
+
+ mFilePicker->SetDisplayDirectory(localFile);
+ mFilePicker->Open(mFpCallback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+UploadLastDir::ContentPrefCallback::HandleResult(nsIContentPref* pref)
+{
+ mResult = pref;
+ return NS_OK;
+}
+
+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 may return nullptr if the DOM File's implementation of
+ * File::mozFullPathInternal does not successfully return a non-empty
+ * string that is a valid path. This can happen on Firefox OS, for example,
+ * where the file picker can create Blobs.
+ */
+static already_AddRefed<nsIFile>
+LastUsedDirectory(const OwningFileOrDirectory& aData)
+{
+ if (aData.IsFile()) {
+ nsAutoString path;
+ ErrorResult error;
+ aData.GetAsFile()->GetMozFullPathInternal(path, error);
+ if (error.Failed() || path.IsEmpty()) {
+ error.SuppressException();
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true,
+ getter_AddRefs(localFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIFile> parentFile;
+ rv = localFile->GetParent(getter_AddRefs(parentFile));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ return parentFile.forget();
+ }
+
+ MOZ_ASSERT(aData.IsDirectory());
+
+ nsCOMPtr<nsIFile> localFile = aData.GetAsDirectory()->GetInternalNsIFile();
+ MOZ_ASSERT(localFile);
+
+ return localFile.forget();
+}
+
+void
+GetDOMFileOrDirectoryName(const OwningFileOrDirectory& aData,
+ nsAString& aName)
+{
+ if (aData.IsFile()) {
+ aData.GetAsFile()->GetName(aName);
+ } else {
+ MOZ_ASSERT(aData.IsDirectory());
+ ErrorResult rv;
+ aData.GetAsDirectory()->GetName(aName, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ }
+ }
+}
+
+void
+GetDOMFileOrDirectoryPath(const OwningFileOrDirectory& aData,
+ nsAString& aPath,
+ ErrorResult& aRv)
+{
+ if (aData.IsFile()) {
+ aData.GetAsFile()->GetMozFullPathInternal(aPath, aRv);
+ } else {
+ MOZ_ASSERT(aData.IsDirectory());
+ aData.GetAsDirectory()->GetFullRealPath(aPath);
+ }
+}
+
+} // namespace
+
+/* static */
+bool
+HTMLInputElement::ValueAsDateEnabled(JSContext* cx, JSObject* obj)
+{
+ return Preferences::GetBool("dom.experimental_forms", false) ||
+ Preferences::GetBool("dom.forms.datepicker", false) ||
+ Preferences::GetBool("dom.forms.datetime", false);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::nsFilePickerShownCallback::Done(int16_t aResult)
+{
+ mInput->PickerClosed();
+
+ if (aResult == nsIFilePicker::returnCancel) {
+ return NS_OK;
+ }
+
+ int16_t mode;
+ mFilePicker->GetMode(&mode);
+
+ // Collect new selected filenames
+ nsTArray<OwningFileOrDirectory> newFilesOrDirectories;
+ if (mode == static_cast<int16_t>(nsIFilePicker::modeOpenMultiple)) {
+ nsCOMPtr<nsISimpleEnumerator> iter;
+ nsresult rv =
+ mFilePicker->GetDomFileOrDirectoryEnumerator(getter_AddRefs(iter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!iter) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISupports> tmp;
+ bool hasMore = true;
+
+ while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
+ iter->GetNext(getter_AddRefs(tmp));
+ nsCOMPtr<nsIDOMBlob> domBlob = do_QueryInterface(tmp);
+ MOZ_ASSERT(domBlob,
+ "Null file object from FilePicker's file enumerator?");
+ if (!domBlob) {
+ continue;
+ }
+
+ OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
+ element->SetAsFile() = static_cast<File*>(domBlob.get());
+ }
+ } else {
+ MOZ_ASSERT(mode == static_cast<int16_t>(nsIFilePicker::modeOpen) ||
+ mode == static_cast<int16_t>(nsIFilePicker::modeGetFolder));
+ nsCOMPtr<nsISupports> tmp;
+ nsresult rv = mFilePicker->GetDomFileOrDirectory(getter_AddRefs(tmp));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(tmp);
+ if (blob) {
+ RefPtr<File> file = static_cast<Blob*>(blob.get())->ToFile();
+ MOZ_ASSERT(file);
+
+ OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
+ element->SetAsFile() = file;
+ } else if (tmp) {
+ RefPtr<Directory> directory = static_cast<Directory*>(tmp.get());
+ OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
+ element->SetAsDirectory() = directory;
+ }
+ }
+
+ if (newFilesOrDirectories.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // Store the last used directory using the content pref service:
+ nsCOMPtr<nsIFile> lastUsedDir = LastUsedDirectory(newFilesOrDirectories[0]);
+
+ if (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->SetFilesOrDirectories(newFilesOrDirectories, true);
+
+ 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
+{
+ ~nsColorPickerShownCallback() {}
+
+public:
+ nsColorPickerShownCallback(HTMLInputElement* aInput,
+ nsIColorPicker* aColorPicker)
+ : mInput(aInput)
+ , mColorPicker(aColorPicker)
+ , mValueChanged(false)
+ {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Update(const nsAString& aColor) override;
+ NS_IMETHOD Done(const nsAString& aColor) override;
+
+private:
+ /**
+ * Updates the internals of the object using aColor as the new value.
+ * If aTrustedUpdate is true, it will consider that aColor is a new value.
+ * Otherwise, it will check that aColor is different from the current value.
+ */
+ nsresult UpdateInternal(const nsAString& aColor, bool aTrustedUpdate);
+
+ RefPtr<HTMLInputElement> mInput;
+ nsCOMPtr<nsIColorPicker> mColorPicker;
+ bool mValueChanged;
+};
+
+nsresult
+nsColorPickerShownCallback::UpdateInternal(const nsAString& aColor,
+ bool aTrustedUpdate)
+{
+ bool valueChanged = false;
+
+ nsAutoString oldValue;
+ if (aTrustedUpdate) {
+ valueChanged = true;
+ } else {
+ mInput->GetValue(oldValue);
+ }
+
+ mInput->SetValue(aColor);
+
+ if (!aTrustedUpdate) {
+ nsAutoString newValue;
+ mInput->GetValue(newValue);
+ if (!oldValue.Equals(newValue)) {
+ valueChanged = true;
+ }
+ }
+
+ if (valueChanged) {
+ mValueChanged = true;
+ return nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
+ NS_LITERAL_STRING("input"), true,
+ false);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsColorPickerShownCallback::Update(const nsAString& aColor)
+{
+ return UpdateInternal(aColor, true);
+}
+
+NS_IMETHODIMP
+nsColorPickerShownCallback::Done(const nsAString& aColor)
+{
+ /**
+ * When Done() is called, we might be at the end of a serie of Update() calls
+ * in which case mValueChanged is set to true and a change event will have to
+ * be fired but we might also be in a one shot Done() call situation in which
+ * case we should fire a change event iif the value actually changed.
+ * UpdateInternal(bool) is taking care of that logic for us.
+ */
+ nsresult rv = NS_OK;
+
+ mInput->PickerClosed();
+
+ if (!aColor.IsEmpty()) {
+ UpdateInternal(aColor, false);
+ }
+
+ if (mValueChanged) {
+ rv = nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
+ NS_LITERAL_STRING("change"), true,
+ false);
+ }
+
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsColorPickerShownCallback, nsIColorPickerShownCallback)
+
+class DatePickerShownCallback final : public nsIDatePickerShownCallback
+{
+ ~DatePickerShownCallback() {}
+public:
+ DatePickerShownCallback(HTMLInputElement* aInput,
+ nsIDatePicker* aDatePicker)
+ : mInput(aInput)
+ , mDatePicker(aDatePicker)
+ {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Done(const nsAString& aDate) override;
+ NS_IMETHOD Cancel() override;
+
+private:
+ RefPtr<HTMLInputElement> mInput;
+ nsCOMPtr<nsIDatePicker> mDatePicker;
+};
+
+NS_IMETHODIMP
+DatePickerShownCallback::Cancel()
+{
+ mInput->PickerClosed();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DatePickerShownCallback::Done(const nsAString& aDate)
+{
+ nsAutoString oldValue;
+
+ mInput->PickerClosed();
+ mInput->GetValue(oldValue);
+
+ if(!oldValue.Equals(aDate)){
+ mInput->SetValue(aDate);
+ nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
+ NS_LITERAL_STRING("input"), true,
+ false);
+ return nsContentUtils::DispatchTrustedEvent(mInput->OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(mInput.get()),
+ NS_LITERAL_STRING("change"), true,
+ false);
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(DatePickerShownCallback, nsIDatePickerShownCallback)
+
+
+bool
+HTMLInputElement::IsPopupBlocked() const
+{
+ nsCOMPtr<nsPIDOMWindowOuter> win = OwnerDoc()->GetWindow();
+ MOZ_ASSERT(win, "window should not be null");
+ if (!win) {
+ return true;
+ }
+
+ // Check if page is allowed to open the popup
+ if (win->GetPopupControlState() <= openControlled) {
+ return false;
+ }
+
+ nsCOMPtr<nsIPopupWindowManager> pm = do_GetService(NS_POPUPWINDOWMANAGER_CONTRACTID);
+ if (!pm) {
+ return true;
+ }
+
+ uint32_t permission;
+ pm->TestPermission(OwnerDoc()->NodePrincipal(), &permission);
+ return permission == nsIPopupWindowManager::DENY_POPUP;
+}
+
+nsresult
+HTMLInputElement::InitDatePicker()
+{
+ if (!Preferences::GetBool("dom.forms.datepicker", false)) {
+ return NS_OK;
+ }
+
+ if (mPickerRunning) {
+ NS_WARNING("Just one nsIDatePicker is allowed");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocument> doc = OwnerDoc();
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
+ if (!win) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (IsPopupBlocked()) {
+ win->FirePopupBlockedEvent(doc, nullptr, EmptyString(), EmptyString());
+ return NS_OK;
+ }
+
+ // Get Loc title
+ nsXPIDLString title;
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "DatePicker", title);
+
+ nsresult rv;
+ nsCOMPtr<nsIDatePicker> datePicker = do_CreateInstance("@mozilla.org/datepicker;1", &rv);
+ if (!datePicker) {
+ return rv;
+ }
+
+ nsAutoString initialValue;
+ GetValueInternal(initialValue);
+ rv = datePicker->Init(win, title, initialValue);
+
+ nsCOMPtr<nsIDatePickerShownCallback> callback =
+ new DatePickerShownCallback(this, datePicker);
+
+ rv = datePicker->Open(callback);
+ if (NS_SUCCEEDED(rv)) {
+ mPickerRunning = true;
+ }
+
+ return rv;
+}
+
+nsresult
+HTMLInputElement::InitColorPicker()
+{
+ if (mPickerRunning) {
+ NS_WARNING("Just one nsIColorPicker is allowed");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocument> doc = OwnerDoc();
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
+ if (!win) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (IsPopupBlocked()) {
+ win->FirePopupBlockedEvent(doc, nullptr, EmptyString(), EmptyString());
+ return NS_OK;
+ }
+
+ // Get Loc title
+ nsXPIDLString title;
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "ColorPicker", title);
+
+ nsCOMPtr<nsIColorPicker> colorPicker = do_CreateInstance("@mozilla.org/colorpicker;1");
+ if (!colorPicker) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString initialValue;
+ GetValueInternal(initialValue);
+ nsresult rv = colorPicker->Init(win, title, initialValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIColorPickerShownCallback> callback =
+ new nsColorPickerShownCallback(this, colorPicker);
+
+ rv = colorPicker->Open(callback);
+ if (NS_SUCCEEDED(rv)) {
+ mPickerRunning = true;
+ }
+
+ return rv;
+}
+
+nsresult
+HTMLInputElement::InitFilePicker(FilePickerType aType)
+{
+ if (mPickerRunning) {
+ NS_WARNING("Just one nsIFilePicker is allowed");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get parent nsPIDOMWindow object.
+ nsCOMPtr<nsIDocument> doc = OwnerDoc();
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
+ if (!win) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (IsPopupBlocked()) {
+ win->FirePopupBlockedEvent(doc, nullptr, EmptyString(), EmptyString());
+ return NS_OK;
+ }
+
+ // Get Loc title
+ nsXPIDLString title;
+ nsXPIDLString okButtonLabel;
+ if (aType == FILE_PICKER_DIRECTORY) {
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "DirectoryUpload", title);
+
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "DirectoryPickerOkButtonLabel",
+ okButtonLabel);
+ } else {
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "FileUpload", title);
+ }
+
+ nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1");
+ if (!filePicker)
+ return NS_ERROR_FAILURE;
+
+ 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 (!okButtonLabel.IsEmpty()) {
+ filePicker->SetOkButtonLabel(okButtonLabel);
+ }
+
+ // 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 directory and filename
+ nsAutoString defaultName;
+
+ const nsTArray<OwningFileOrDirectory>& oldFiles =
+ GetFilesOrDirectoriesInternal();
+
+ nsCOMPtr<nsIFilePickerShownCallback> callback =
+ new HTMLInputElement::nsFilePickerShownCallback(this, filePicker);
+
+ if (!oldFiles.IsEmpty() &&
+ aType != FILE_PICKER_DIRECTORY) {
+ nsAutoString path;
+
+ nsCOMPtr<nsIFile> parentFile = LastUsedDirectory(oldFiles[0]);
+ if (parentFile) {
+ filePicker->SetDisplayDirectory(parentFile);
+ }
+
+ // Unfortunately nsIFilePicker doesn't allow multiple files to be
+ // default-selected, so only select something by default if exactly
+ // one file was selected before.
+ if (oldFiles.Length() == 1) {
+ nsAutoString leafName;
+ GetDOMFileOrDirectoryName(oldFiles[0], leafName);
+
+ if (!leafName.IsEmpty()) {
+ filePicker->SetDefaultString(leafName);
+ }
+ }
+
+ rv = filePicker->Open(callback);
+ if (NS_SUCCEEDED(rv)) {
+ mPickerRunning = true;
+ }
+
+ return rv;
+ }
+
+ HTMLInputElement::gUploadLastDir->FetchDirectoryAndDisplayPicker(doc, filePicker, callback);
+ mPickerRunning = true;
+ return NS_OK;
+}
+
+#define CPS_PREF_NAME NS_LITERAL_STRING("browser.upload.lastDir")
+
+NS_IMPL_ISUPPORTS(UploadLastDir, nsIObserver, nsISupportsWeakReference)
+
+void
+HTMLInputElement::InitUploadLastDir() {
+ gUploadLastDir = new UploadLastDir();
+ NS_ADDREF(gUploadLastDir);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService && gUploadLastDir) {
+ observerService->AddObserver(gUploadLastDir, "browser:purge-session-history", true);
+ }
+}
+
+void
+HTMLInputElement::DestroyUploadLastDir() {
+ NS_IF_RELEASE(gUploadLastDir);
+}
+
+nsresult
+UploadLastDir::FetchDirectoryAndDisplayPicker(nsIDocument* aDoc,
+ nsIFilePicker* aFilePicker,
+ nsIFilePickerShownCallback* aFpCallback)
+{
+ NS_PRECONDITION(aDoc, "aDoc is null");
+ NS_PRECONDITION(aFilePicker, "aFilePicker is null");
+ NS_PRECONDITION(aFpCallback, "aFpCallback is null");
+
+ nsIURI* docURI = aDoc->GetDocumentURI();
+ NS_PRECONDITION(docURI, "docURI is null");
+
+ nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
+ nsCOMPtr<nsIContentPrefCallback2> prefCallback =
+ new UploadLastDir::ContentPrefCallback(aFilePicker, aFpCallback);
+
+#ifdef MOZ_B2G
+ if (XRE_IsContentProcess()) {
+ prefCallback->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR);
+ return NS_OK;
+ }
+#endif
+
+ // Attempt to get the CPS, if it's not present we'll fallback to use the Desktop folder
+ nsCOMPtr<nsIContentPrefService2> contentPrefService =
+ do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+ if (!contentPrefService) {
+ prefCallback->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR);
+ return NS_OK;
+ }
+
+ nsAutoCString cstrSpec;
+ docURI->GetSpec(cstrSpec);
+ NS_ConvertUTF8toUTF16 spec(cstrSpec);
+
+ contentPrefService->GetByDomainAndName(spec, CPS_PREF_NAME, loadContext, prefCallback);
+ return NS_OK;
+}
+
+nsresult
+UploadLastDir::StoreLastUsedDirectory(nsIDocument* aDoc, nsIFile* aDir)
+{
+ NS_PRECONDITION(aDoc, "aDoc is null");
+ if (!aDir) {
+ return NS_OK;
+ }
+
+#ifdef MOZ_B2G
+ if (XRE_IsContentProcess()) {
+ return NS_OK;
+ }
+#endif
+
+ nsCOMPtr<nsIURI> docURI = aDoc->GetDocumentURI();
+ NS_PRECONDITION(docURI, "docURI is null");
+
+ // 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;
+ aDir->GetPath(unicodePath);
+ if (unicodePath.IsEmpty()) // nothing to do
+ return NS_OK;
+ RefPtr<nsVariantCC> prefValue = new nsVariantCC();
+ prefValue->SetAsAString(unicodePath);
+
+ // Use the document's current load context to ensure that the content pref
+ // service doesn't persistently store this directory for this domain if the
+ // user is using private browsing:
+ nsCOMPtr<nsILoadContext> loadContext = aDoc->GetLoadContext();
+ return contentPrefService->Set(spec, CPS_PREF_NAME, prefValue, loadContext, nullptr);
+}
+
+NS_IMETHODIMP
+UploadLastDir::Observe(nsISupports* aSubject, char const* aTopic, char16_t const* aData)
+{
+ if (strcmp(aTopic, "browser:purge-session-history") == 0) {
+ nsCOMPtr<nsIContentPrefService2> contentPrefService =
+ do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+ if (contentPrefService)
+ contentPrefService->RemoveByName(CPS_PREF_NAME, nullptr, nullptr);
+ }
+ return NS_OK;
+}
+
+#ifdef ACCESSIBILITY
+//Helper method
+static nsresult FireEventForAccessibility(nsIDOMHTMLInputElement* aTarget,
+ nsPresContext* aPresContext,
+ const nsAString& aEventType);
+#endif
+
+//
+// construction, destruction
+//
+
+HTMLInputElement::HTMLInputElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
+ FromParser aFromParser, FromClone aFromClone)
+ : nsGenericHTMLFormElementWithState(aNodeInfo)
+ , mType(kInputDefaultType->value)
+ , mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown)
+ , mDisabledChanged(false)
+ , mValueChanged(false)
+ , mLastValueChangeWasInteractive(false)
+ , mCheckedChanged(false)
+ , mChecked(false)
+ , mHandlingSelectEvent(false)
+ , mShouldInitChecked(false)
+ , mDoneCreating(aFromParser == NOT_FROM_PARSER &&
+ aFromClone == FromClone::no)
+ , mInInternalActivate(false)
+ , mCheckedIsToggled(false)
+ , mIndeterminate(false)
+ , mInhibitRestoration(aFromParser & FROM_PARSER_FRAGMENT)
+ , mCanShowValidUI(true)
+ , mCanShowInvalidUI(true)
+ , mHasRange(false)
+ , mIsDraggingRange(false)
+ , mNumberControlSpinnerIsSpinning(false)
+ , mNumberControlSpinnerSpinsUp(false)
+ , mPickerRunning(false)
+ , mSelectionCached(true)
+{
+ // We are in a type=text so we now we currenty need a nsTextEditorState.
+ mInputData.mState = new nsTextEditorState(this);
+
+ if (!gUploadLastDir)
+ HTMLInputElement::InitUploadLastDir();
+
+ // Set up our default state. By default we're enabled (since we're
+ // a control type that can be disabled but not actually disabled
+ // right now), optional, and valid. We are NOT readwrite by default
+ // until someone calls UpdateEditableState on us, apparently! Also
+ // by default we don't have to show validity UI and so forth.
+ AddStatesSilently(NS_EVENT_STATE_ENABLED |
+ NS_EVENT_STATE_OPTIONAL |
+ NS_EVENT_STATE_VALID);
+ UpdateApzAwareFlag();
+}
+
+HTMLInputElement::~HTMLInputElement()
+{
+ if (mNumberControlSpinnerIsSpinning) {
+ StopNumberControlSpinnerSpin(eDisallowDispatchingEvents);
+ }
+ DestroyImageLoadingContent();
+ FreeData();
+}
+
+void
+HTMLInputElement::FreeData()
+{
+ if (!IsSingleLineTextControl(false)) {
+ free(mInputData.mValue);
+ mInputData.mValue = nullptr;
+ } else {
+ UnbindFromFrame(nullptr);
+ delete mInputData.mState;
+ mInputData.mState = nullptr;
+ }
+}
+
+nsTextEditorState*
+HTMLInputElement::GetEditorState() const
+{
+ if (!IsSingleLineTextControl(false)) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(mInputData.mState, "Single line text controls need to have a state"
+ " associated with them");
+
+ return mInputData.mState;
+}
+
+
+// nsISupports
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLInputElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement,
+ nsGenericHTMLFormElementWithState)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
+ if (tmp->IsSingleLineTextControl(false)) {
+ tmp->mInputData.mState->Traverse(cb);
+ }
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilesOrDirectories)
+
+ if (tmp->mGetFilesRecursiveHelper) {
+ tmp->mGetFilesRecursiveHelper->Traverse(cb);
+ }
+
+ if (tmp->mGetFilesNonRecursiveHelper) {
+ tmp->mGetFilesNonRecursiveHelper->Traverse(cb);
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEntries)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement,
+ nsGenericHTMLFormElementWithState)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilesOrDirectories)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mEntries)
+ if (tmp->IsSingleLineTextControl(false)) {
+ tmp->mInputData.mState->Unlink();
+ }
+
+ tmp->ClearGetFilesHelpers();
+
+ //XXX should unlink more?
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(HTMLInputElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLInputElement, Element)
+
+// QueryInterface implementation for HTMLInputElement
+NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLInputElement)
+ NS_INTERFACE_TABLE_INHERITED(HTMLInputElement,
+ nsIDOMHTMLInputElement,
+ nsITextControlElement,
+ nsIPhonetic,
+ imgINotificationObserver,
+ nsIImageLoadingContent,
+ imgIOnloadBlocker,
+ nsIDOMNSEditableElement,
+ nsIConstraintValidation)
+NS_INTERFACE_TABLE_TAIL_INHERITING(nsGenericHTMLFormElementWithState)
+
+// nsIConstraintValidation
+NS_IMPL_NSICONSTRAINTVALIDATION_EXCEPT_SETCUSTOMVALIDITY(HTMLInputElement)
+
+// nsIDOMNode
+
+nsresult
+HTMLInputElement::Clone(mozilla::dom::NodeInfo* aNodeInfo, nsINode** aResult) const
+{
+ *aResult = nullptr;
+
+ already_AddRefed<mozilla::dom::NodeInfo> ni = RefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget();
+ RefPtr<HTMLInputElement> it = new HTMLInputElement(ni, NOT_FROM_PARSER,
+ FromClone::yes);
+
+ nsresult rv = const_cast<HTMLInputElement*>(this)->CopyInnerTo(it);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch (GetValueMode()) {
+ case VALUE_MODE_VALUE:
+ if (mValueChanged) {
+ // We don't have our default value anymore. Set our value on
+ // the clone.
+ nsAutoString value;
+ GetValueInternal(value);
+ // SetValueInternal handles setting the VALUE_CHANGED bit for us
+ rv = it->SetValueInternal(value, nsTextEditorState::eSetValue_Notify);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ break;
+ case VALUE_MODE_FILENAME:
+ if (it->OwnerDoc()->IsStaticDocument()) {
+ // We're going to be used in print preview. Since the doc is static
+ // we can just grab the pretty string and use it as wallpaper
+ GetDisplayFileName(it->mStaticDocFileList);
+ } else {
+ it->ClearGetFilesHelpers();
+ it->mFilesOrDirectories.Clear();
+ it->mFilesOrDirectories.AppendElements(mFilesOrDirectories);
+ }
+ break;
+ case VALUE_MODE_DEFAULT_ON:
+ if (mCheckedChanged) {
+ // We no longer have our original checked state. Set our
+ // checked state on the clone.
+ it->DoSetChecked(mChecked, false, true);
+ // Then tell DoneCreatingElement() not to overwrite:
+ it->mShouldInitChecked = false;
+ }
+ break;
+ case VALUE_MODE_DEFAULT:
+ if (mType == NS_FORM_INPUT_IMAGE && it->OwnerDoc()->IsStaticDocument()) {
+ CreateStaticImageClone(it);
+ }
+ break;
+ }
+
+ it->DoneCreatingElement();
+
+ it->mLastValueChangeWasInteractive = mLastValueChangeWasInteractive;
+ it.forget(aResult);
+ return NS_OK;
+}
+
+nsresult
+HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ nsAttrValueOrString* aValue,
+ bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None) {
+ //
+ // When name or type changes, radio should be removed from radio group.
+ // (type changes are handled in the form itself currently)
+ // If we are not done creating the radio, we also should not do it.
+ //
+ if ((aName == nsGkAtoms::name ||
+ (aName == nsGkAtoms::type && !mForm)) &&
+ mType == NS_FORM_INPUT_RADIO &&
+ (mForm || mDoneCreating)) {
+ WillRemoveFromRadioGroup();
+ } else if (aNotify && aName == nsGkAtoms::src &&
+ mType == NS_FORM_INPUT_IMAGE) {
+ if (aValue) {
+ LoadImage(aValue->String(), true, aNotify, eImageLoadType_Normal);
+ } else {
+ // Null value means the attr got unset; drop the image
+ CancelImageRequests(aNotify);
+ }
+ } else if (aNotify && aName == nsGkAtoms::disabled) {
+ mDisabledChanged = true;
+ } else if (aName == nsGkAtoms::dir &&
+ AttrValueIs(kNameSpaceID_None, nsGkAtoms::dir,
+ nsGkAtoms::_auto, eIgnoreCase)) {
+ SetDirectionIfAuto(false, aNotify);
+ } else if (mType == NS_FORM_INPUT_RADIO && aName == nsGkAtoms::required) {
+ nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
+
+ if (container &&
+ ((aValue && !HasAttr(aNameSpaceID, aName)) ||
+ (!aValue && HasAttr(aNameSpaceID, aName)))) {
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+ container->RadioRequiredWillChange(name, !!aValue);
+ }
+ }
+
+ if (aName == nsGkAtoms::webkitdirectory) {
+ Telemetry::Accumulate(Telemetry::WEBKIT_DIRECTORY_USED, true);
+ }
+ }
+
+ return nsGenericHTMLFormElementWithState::BeforeSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+nsresult
+HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+ const nsAttrValue* aValue, bool aNotify)
+{
+ if (aNameSpaceID == kNameSpaceID_None) {
+ //
+ // When name or type changes, radio should be added to radio group.
+ // (type changes are handled in the form itself currently)
+ // If we are not done creating the radio, we also should not do it.
+ //
+ if ((aName == nsGkAtoms::name ||
+ (aName == nsGkAtoms::type && !mForm)) &&
+ mType == NS_FORM_INPUT_RADIO &&
+ (mForm || mDoneCreating)) {
+ AddedToRadioGroup();
+ UpdateValueMissingValidityStateForRadio(false);
+ }
+
+ // If @value is changed and BF_VALUE_CHANGED is false, @value is the value
+ // of the element so, if the value of the element is different than @value,
+ // we have to re-set it. This is only the case when GetValueMode() returns
+ // VALUE_MODE_VALUE.
+ if (aName == nsGkAtoms::value &&
+ !mValueChanged && GetValueMode() == VALUE_MODE_VALUE) {
+ SetDefaultValueAsValue();
+ }
+
+ //
+ // Checked must be set no matter what type of control it is, since
+ // mChecked must reflect the new value
+ if (aName == nsGkAtoms::checked && !mCheckedChanged) {
+ // Delay setting checked if we are creating this element (wait
+ // until everything is set)
+ if (!mDoneCreating) {
+ mShouldInitChecked = true;
+ } else {
+ DoSetChecked(DefaultChecked(), true, true);
+ SetCheckedChanged(false);
+ }
+ }
+
+ if (aName == nsGkAtoms::type) {
+ if (!aValue) {
+ // We're now a text input. Note that we have to handle this manually,
+ // since removing an attribute (which is what happened, since aValue is
+ // null) doesn't call ParseAttribute.
+ HandleTypeChange(kInputDefaultType->value);
+ }
+
+ UpdateBarredFromConstraintValidation();
+
+ if (mType != NS_FORM_INPUT_IMAGE) {
+ // We're no longer an image input. Cancel our image requests, if we have
+ // any. Note that doing this when we already weren't an image is ok --
+ // just does nothing.
+ CancelImageRequests(aNotify);
+ } else if (aNotify) {
+ // We just got switched to be an image input; we should see
+ // whether we have an image to load;
+ nsAutoString src;
+ if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
+ LoadImage(src, false, aNotify, eImageLoadType_Normal);
+ }
+ }
+
+ if (mType == NS_FORM_INPUT_PASSWORD && IsInComposedDoc()) {
+ AsyncEventDispatcher* dispatcher =
+ new AsyncEventDispatcher(this,
+ NS_LITERAL_STRING("DOMInputPasswordAdded"),
+ true,
+ true);
+ dispatcher->PostDOMEvent();
+ }
+ }
+
+ if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
+ aName == nsGkAtoms::readonly) {
+ UpdateValueMissingValidityState();
+
+ // This *has* to be called *after* validity has changed.
+ if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
+ UpdateBarredFromConstraintValidation();
+ }
+ } else if (MinOrMaxLengthApplies() && aName == nsGkAtoms::maxlength) {
+ UpdateTooLongValidityState();
+ } else if (MinOrMaxLengthApplies() && aName == nsGkAtoms::minlength) {
+ UpdateTooShortValidityState();
+ } else if (aName == nsGkAtoms::pattern && mDoneCreating) {
+ UpdatePatternMismatchValidityState();
+ } else if (aName == nsGkAtoms::multiple) {
+ UpdateTypeMismatchValidityState();
+ } else if (aName == nsGkAtoms::max) {
+ UpdateHasRange();
+ if (mType == NS_FORM_INPUT_RANGE) {
+ // The value may need to change when @max changes since the value may
+ // have been invalid and can now change to a valid value, or vice
+ // versa. For example, consider:
+ // <input type=range value=-1 max=1 step=3>. The valid range is 0 to 1
+ // while the nearest valid steps are -1 and 2 (the max value having
+ // prevented there being a valid step in range). Changing @max to/from
+ // 1 and a number greater than on equal to 3 should change whether we
+ // have a step mismatch or not.
+ // The value may also need to change between a value that results in
+ // a step mismatch and a value that results in overflow. For example,
+ // if @max in the example above were to change from 1 to -1.
+ nsAutoString value;
+ GetValue(value);
+ nsresult rv =
+ SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // Validity state must be updated *after* the SetValueInternal call above
+ // or else the following assert will not be valid.
+ // We don't assert the state of underflow during creation since
+ // DoneCreatingElement sanitizes.
+ UpdateRangeOverflowValidityState();
+ MOZ_ASSERT(!mDoneCreating ||
+ mType != NS_FORM_INPUT_RANGE ||
+ !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
+ "HTML5 spec does not allow underflow for type=range");
+ } else if (aName == nsGkAtoms::min) {
+ UpdateHasRange();
+ if (mType == NS_FORM_INPUT_RANGE) {
+ // See @max comment
+ nsAutoString value;
+ GetValue(value);
+ nsresult rv =
+ SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // See corresponding @max comment
+ UpdateRangeUnderflowValidityState();
+ UpdateStepMismatchValidityState();
+ MOZ_ASSERT(!mDoneCreating ||
+ mType != NS_FORM_INPUT_RANGE ||
+ !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
+ "HTML5 spec does not allow underflow for type=range");
+ } else if (aName == nsGkAtoms::step) {
+ if (mType == NS_FORM_INPUT_RANGE) {
+ // See @max comment
+ nsAutoString value;
+ GetValue(value);
+ nsresult rv =
+ SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // See corresponding @max comment
+ UpdateStepMismatchValidityState();
+ MOZ_ASSERT(!mDoneCreating ||
+ mType != NS_FORM_INPUT_RANGE ||
+ !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
+ "HTML5 spec does not allow underflow for type=range");
+ } else if (aName == nsGkAtoms::dir &&
+ aValue && aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
+ SetDirectionIfAuto(true, aNotify);
+ } else if (aName == nsGkAtoms::lang) {
+ if (mType == NS_FORM_INPUT_NUMBER) {
+ // Update the value that is displayed to the user to the new locale:
+ nsAutoString value;
+ GetValueInternal(value);
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ numberControlFrame->SetValueOfAnonTextControl(value);
+ }
+ }
+ } else if (aName == nsGkAtoms::autocomplete) {
+ // Clear the cached @autocomplete attribute state.
+ mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
+ }
+
+ UpdateState(aNotify);
+ }
+
+ return nsGenericHTMLFormElementWithState::AfterSetAttr(aNameSpaceID, aName,
+ aValue, aNotify);
+}
+
+// nsIDOMHTMLInputElement
+
+NS_IMETHODIMP
+HTMLInputElement::GetForm(nsIDOMHTMLFormElement** aForm)
+{
+ return nsGenericHTMLFormElementWithState::GetForm(aForm);
+}
+
+NS_IMPL_STRING_ATTR(HTMLInputElement, DefaultValue, value)
+NS_IMPL_BOOL_ATTR(HTMLInputElement, DefaultChecked, checked)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Accept, accept)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Align, align)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Alt, alt)
+NS_IMPL_BOOL_ATTR(HTMLInputElement, Autofocus, autofocus)
+//NS_IMPL_BOOL_ATTR(HTMLInputElement, Checked, checked)
+NS_IMPL_BOOL_ATTR(HTMLInputElement, Disabled, disabled)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Max, max)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Min, min)
+NS_IMPL_ACTION_ATTR(HTMLInputElement, FormAction, formaction)
+NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLInputElement, FormEnctype, formenctype,
+ "", kFormDefaultEnctype->tag)
+NS_IMPL_ENUM_ATTR_DEFAULT_MISSING_INVALID_VALUES(HTMLInputElement, FormMethod, formmethod,
+ "", kFormDefaultMethod->tag)
+NS_IMPL_BOOL_ATTR(HTMLInputElement, FormNoValidate, formnovalidate)
+NS_IMPL_STRING_ATTR(HTMLInputElement, FormTarget, formtarget)
+NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLInputElement, InputMode, inputmode,
+ kInputDefaultInputmode->tag)
+NS_IMPL_BOOL_ATTR(HTMLInputElement, Multiple, multiple)
+NS_IMPL_NON_NEGATIVE_INT_ATTR(HTMLInputElement, MaxLength, maxlength)
+NS_IMPL_NON_NEGATIVE_INT_ATTR(HTMLInputElement, MinLength, minlength)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Name, name)
+NS_IMPL_BOOL_ATTR(HTMLInputElement, ReadOnly, readonly)
+NS_IMPL_BOOL_ATTR(HTMLInputElement, Required, required)
+NS_IMPL_URI_ATTR(HTMLInputElement, Src, src)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Step, step)
+NS_IMPL_STRING_ATTR(HTMLInputElement, UseMap, usemap)
+//NS_IMPL_STRING_ATTR(HTMLInputElement, Value, value)
+NS_IMPL_UINT_ATTR_NON_ZERO_DEFAULT_VALUE(HTMLInputElement, Size, size, DEFAULT_COLS)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Pattern, pattern)
+NS_IMPL_STRING_ATTR(HTMLInputElement, Placeholder, placeholder)
+NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLInputElement, Type, type,
+ kInputDefaultType->tag)
+
+NS_IMETHODIMP
+HTMLInputElement::GetAutocomplete(nsAString& aValue)
+{
+ if (!DoesAutocompleteApply()) {
+ return NS_OK;
+ }
+
+ aValue.Truncate(0);
+ const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
+
+ mAutocompleteAttrState =
+ nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue,
+ mAutocompleteAttrState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetAutocomplete(const nsAString& aValue)
+{
+ return SetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, nullptr, aValue, true);
+}
+
+void
+HTMLInputElement::GetAutocompleteInfo(Nullable<AutocompleteInfo>& aInfo)
+{
+ if (!DoesAutocompleteApply()) {
+ aInfo.SetNull();
+ return;
+ }
+
+ const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
+ mAutocompleteAttrState =
+ nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aInfo.SetValue(),
+ mAutocompleteAttrState);
+}
+
+int32_t
+HTMLInputElement::TabIndexDefault()
+{
+ return 0;
+}
+
+uint32_t
+HTMLInputElement::Height()
+{
+ if (mType != NS_FORM_INPUT_IMAGE) {
+ return 0;
+ }
+ return GetWidthHeightForImage(mCurrentRequest).height;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetHeight(uint32_t* aHeight)
+{
+ *aHeight = Height();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetHeight(uint32_t aHeight)
+{
+ ErrorResult rv;
+ SetHeight(aHeight, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetIndeterminate(bool* aValue)
+{
+ *aValue = Indeterminate();
+ return NS_OK;
+}
+
+void
+HTMLInputElement::SetIndeterminateInternal(bool aValue,
+ bool aShouldInvalidate)
+{
+ mIndeterminate = aValue;
+
+ if (aShouldInvalidate) {
+ // Repaint the frame
+ nsIFrame* frame = GetPrimaryFrame();
+ if (frame)
+ frame->InvalidateFrameSubtree();
+ }
+
+ UpdateState(true);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetIndeterminate(bool aValue)
+{
+ SetIndeterminateInternal(aValue, true);
+ return NS_OK;
+}
+
+uint32_t
+HTMLInputElement::Width()
+{
+ if (mType != NS_FORM_INPUT_IMAGE) {
+ return 0;
+ }
+ return GetWidthHeightForImage(mCurrentRequest).width;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetWidth(uint32_t* aWidth)
+{
+ *aWidth = Width();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetWidth(uint32_t aWidth)
+{
+ ErrorResult rv;
+ SetWidth(aWidth, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetValue(nsAString& aValue)
+{
+ nsresult rv = GetValueInternal(aValue);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Don't return non-sanitized value for types that are experimental on mobile
+ // or datetime types
+ if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
+ SanitizeValue(aValue);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+HTMLInputElement::GetValueInternal(nsAString& aValue) const
+{
+ switch (GetValueMode()) {
+ case VALUE_MODE_VALUE:
+ if (IsSingleLineTextControl(false)) {
+ mInputData.mState->GetValue(aValue, true);
+ } else if (!aValue.Assign(mInputData.mValue, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+
+ case VALUE_MODE_FILENAME:
+ if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ aValue.Assign(mFirstFilePath);
+ } else {
+ // Just return the leaf name
+ if (mFilesOrDirectories.IsEmpty()) {
+ aValue.Truncate();
+ } else {
+ GetDOMFileOrDirectoryName(mFilesOrDirectories[0], aValue);
+ }
+ }
+
+ return NS_OK;
+
+ case VALUE_MODE_DEFAULT:
+ // Treat defaultValue as value.
+ GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue);
+ return NS_OK;
+
+ case VALUE_MODE_DEFAULT_ON:
+ // Treat default value as value and returns "on" if no value.
+ if (!GetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue)) {
+ aValue.AssignLiteral("on");
+ }
+ return NS_OK;
+ }
+
+ // This return statement is required for some compilers.
+ return NS_OK;
+}
+
+bool
+HTMLInputElement::IsValueEmpty() const
+{
+ nsAutoString value;
+ GetValueInternal(value);
+
+ return value.IsEmpty();
+}
+
+void
+HTMLInputElement::ClearFiles(bool aSetValueChanged)
+{
+ nsTArray<OwningFileOrDirectory> data;
+ SetFilesOrDirectories(data, aSetValueChanged);
+}
+
+int32_t
+HTMLInputElement::MonthsSinceJan1970(uint32_t aYear, uint32_t aMonth) const
+{
+ return (aYear - 1970) * 12 + aMonth - 1;
+}
+
+/* static */ Decimal
+HTMLInputElement::StringToDecimal(const nsAString& aValue)
+{
+ if (!IsASCII(aValue)) {
+ return Decimal::nan();
+ }
+ NS_LossyConvertUTF16toASCII asciiString(aValue);
+ std::string stdString = asciiString.get();
+ return Decimal::fromString(stdString);
+}
+
+bool
+HTMLInputElement::ConvertStringToNumber(nsAString& aValue,
+ Decimal& aResultValue) const
+{
+ MOZ_ASSERT(DoesValueAsNumberApply(),
+ "ConvertStringToNumber only applies if .valueAsNumber applies");
+
+ switch (mType) {
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_RANGE:
+ {
+ aResultValue = StringToDecimal(aValue);
+ if (!aResultValue.isFinite()) {
+ return false;
+ }
+ return true;
+ }
+ case NS_FORM_INPUT_DATE:
+ {
+ uint32_t year, month, day;
+ if (!ParseDate(aValue, &year, &month, &day)) {
+ return false;
+ }
+
+ JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day));
+ if (!time.isValid()) {
+ return false;
+ }
+
+ aResultValue = Decimal::fromDouble(time.toDouble());
+ return true;
+ }
+ case NS_FORM_INPUT_TIME:
+ uint32_t milliseconds;
+ if (!ParseTime(aValue, &milliseconds)) {
+ return false;
+ }
+
+ aResultValue = Decimal(int32_t(milliseconds));
+ return true;
+ case NS_FORM_INPUT_MONTH:
+ {
+ uint32_t year, month;
+ if (!ParseMonth(aValue, &year, &month)) {
+ return false;
+ }
+
+ if (year < kMinimumYear || year > kMaximumYear) {
+ return false;
+ }
+
+ // Maximum valid month is 275760-09.
+ if (year == kMaximumYear && month > kMaximumMonthInMaximumYear) {
+ return false;
+ }
+
+ int32_t months = MonthsSinceJan1970(year, month);
+ aResultValue = Decimal(int32_t(months));
+ return true;
+ }
+ case NS_FORM_INPUT_WEEK:
+ {
+ uint32_t year, week;
+ if (!ParseWeek(aValue, &year, &week)) {
+ return false;
+ }
+
+ if (year < kMinimumYear || year > kMaximumYear) {
+ return false;
+ }
+
+ // Maximum week is 275760-W37, the week of 275760-09-13.
+ if (year == kMaximumYear && week > kMaximumWeekInMaximumYear) {
+ return false;
+ }
+
+ double days = DaysSinceEpochFromWeek(year, week);
+ aResultValue = Decimal::fromDouble(days * kMsPerDay);
+ return true;
+ }
+ default:
+ MOZ_ASSERT(false, "Unrecognized input type");
+ return false;
+ }
+}
+
+Decimal
+HTMLInputElement::GetValueAsDecimal() const
+{
+ Decimal decimalValue;
+ nsAutoString stringValue;
+
+ GetValueInternal(stringValue);
+
+ return !ConvertStringToNumber(stringValue, decimalValue) ? Decimal::nan()
+ : decimalValue;
+}
+
+void
+HTMLInputElement::GetValue(nsAString& aValue, ErrorResult& aRv)
+{
+ nsresult rv = GetValue(aValue);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+void
+HTMLInputElement::SetValue(const nsAString& aValue, ErrorResult& aRv)
+{
+ // check security. Note that setting the value to the empty string is always
+ // OK and gives pages a way to clear a file input if necessary.
+ if (mType == NS_FORM_INPUT_FILE) {
+ if (!aValue.IsEmpty()) {
+ if (!nsContentUtils::IsCallerChrome()) {
+ // setting the value of a "FILE" input widget requires
+ // chrome privilege
+ aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return;
+ }
+ Sequence<nsString> list;
+ if (!list.AppendElement(aValue, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ MozSetFileNameArray(list, aRv);
+ return;
+ }
+ else {
+ ClearFiles(true);
+ }
+ }
+ else {
+ if (MayFireChangeOnBlur()) {
+ // If the value has been set by a script, we basically want to keep the
+ // current change event state. If the element is ready to fire a change
+ // event, we should keep it that way. Otherwise, we should make sure the
+ // element will not fire any event because of the script interaction.
+ //
+ // NOTE: this is currently quite expensive work (too much string
+ // manipulation). We should probably optimize that.
+ nsAutoString currentValue;
+ GetValue(currentValue);
+
+ nsresult rv =
+ SetValueInternal(aValue, nsTextEditorState::eSetValue_ByContent |
+ nsTextEditorState::eSetValue_Notify);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ if (mFocusedValue.Equals(currentValue)) {
+ GetValue(mFocusedValue);
+ }
+ } else {
+ nsresult rv =
+ SetValueInternal(aValue, nsTextEditorState::eSetValue_ByContent |
+ nsTextEditorState::eSetValue_Notify);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetValue(const nsAString& aValue)
+{
+ ErrorResult rv;
+ SetValue(aValue, rv);
+ return rv.StealNSResult();
+}
+
+nsGenericHTMLElement*
+HTMLInputElement::GetList() const
+{
+ nsAutoString dataListId;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::list, dataListId);
+ if (dataListId.IsEmpty()) {
+ return nullptr;
+ }
+
+ //XXXsmaug How should this all work in case input element is in Shadow DOM.
+ nsIDocument* doc = GetUncomposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+
+ Element* element = doc->GetElementById(dataListId);
+ if (!element || !element->IsHTMLElement(nsGkAtoms::datalist)) {
+ return nullptr;
+ }
+
+ return static_cast<nsGenericHTMLElement*>(element);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetList(nsIDOMHTMLElement** aValue)
+{
+ *aValue = nullptr;
+
+ RefPtr<nsGenericHTMLElement> element = GetList();
+ if (!element) {
+ return NS_OK;
+ }
+
+ element.forget(aValue);
+ return NS_OK;
+}
+
+void
+HTMLInputElement::SetValue(Decimal aValue)
+{
+ MOZ_ASSERT(!aValue.isInfinity(), "aValue must not be Infinity!");
+
+ if (aValue.isNaN()) {
+ SetValue(EmptyString());
+ return;
+ }
+
+ nsAutoString value;
+ ConvertNumberToString(aValue, value);
+ SetValue(value);
+}
+
+bool
+HTMLInputElement::ConvertNumberToString(Decimal aValue,
+ nsAString& aResultString) const
+{
+ MOZ_ASSERT(DoesValueAsNumberApply(),
+ "ConvertNumberToString is only implemented for types implementing .valueAsNumber");
+ MOZ_ASSERT(aValue.isFinite(),
+ "aValue must be a valid non-Infinite number.");
+
+ aResultString.Truncate();
+
+ switch (mType) {
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_RANGE:
+ {
+ char buf[32];
+ bool ok = aValue.toString(buf, ArrayLength(buf));
+ aResultString.AssignASCII(buf);
+ MOZ_ASSERT(ok, "buf not big enough");
+ return ok;
+ }
+ case NS_FORM_INPUT_DATE:
+ {
+ // The specs (and our JS APIs) require |aValue| to be truncated.
+ aValue = aValue.floor();
+
+ double year = JS::YearFromTime(aValue.toDouble());
+ double month = JS::MonthFromTime(aValue.toDouble());
+ double day = JS::DayFromTime(aValue.toDouble());
+
+ if (IsNaN(year) || IsNaN(month) || IsNaN(day)) {
+ return false;
+ }
+
+ aResultString.AppendPrintf("%04.0f-%02.0f-%02.0f", year,
+ month + 1, day);
+
+ return true;
+ }
+ case NS_FORM_INPUT_TIME:
+ {
+ // Per spec, we need to truncate |aValue| and we should only represent
+ // times inside a day [00:00, 24:00[, which means that we should do a
+ // modulo on |aValue| using the number of milliseconds in a day (86400000).
+ uint32_t value = NS_floorModulo(aValue.floor(), Decimal(86400000)).toDouble();
+
+ uint16_t milliseconds = value % 1000;
+ value /= 1000;
+
+ uint8_t seconds = value % 60;
+ value /= 60;
+
+ uint8_t minutes = value % 60;
+ value /= 60;
+
+ uint8_t hours = value;
+
+ if (milliseconds != 0) {
+ aResultString.AppendPrintf("%02d:%02d:%02d.%03d",
+ hours, minutes, seconds, milliseconds);
+ } else if (seconds != 0) {
+ aResultString.AppendPrintf("%02d:%02d:%02d",
+ hours, minutes, seconds);
+ } else {
+ aResultString.AppendPrintf("%02d:%02d", hours, minutes);
+ }
+
+ return true;
+ }
+ case NS_FORM_INPUT_MONTH:
+ {
+ aValue = aValue.floor();
+
+ double month = NS_floorModulo(aValue, Decimal(12)).toDouble();
+ month = (month < 0 ? month + 12 : month);
+
+ double year = 1970 + (aValue.toDouble() - month) / 12;
+
+ // Maximum valid month is 275760-09.
+ if (year < kMinimumYear || year > kMaximumYear) {
+ return false;
+ }
+
+ if (year == kMaximumYear && month > 8) {
+ return false;
+ }
+
+ aResultString.AppendPrintf("%04.0f-%02.0f", year, month + 1);
+ return true;
+ }
+ case NS_FORM_INPUT_WEEK:
+ {
+ aValue = aValue.floor();
+
+ // Based on ISO 8601 date.
+ double year = JS::YearFromTime(aValue.toDouble());
+ double month = JS::MonthFromTime(aValue.toDouble());
+ double day = JS::DayFromTime(aValue.toDouble());
+ // Adding 1 since day starts from 0.
+ double dayInYear = JS::DayWithinYear(aValue.toDouble(), year) + 1;
+
+ // Adding 1 since month starts from 0.
+ uint32_t isoWeekday = DayOfWeek(year, month + 1, day, true);
+ // Target on Wednesday since ISO 8601 states that week 1 is the week
+ // with the first Thursday of that year.
+ uint32_t week = (dayInYear - isoWeekday + 10) / 7;
+
+ if (week < 1) {
+ year--;
+ if (year < 1) {
+ return false;
+ }
+ week = MaximumWeekInYear(year);
+ } else if (week > MaximumWeekInYear(year)) {
+ year++;
+ if (year > kMaximumYear ||
+ (year == kMaximumYear && week > kMaximumWeekInMaximumYear)) {
+ return false;
+ }
+ week = 1;
+ }
+
+ aResultString.AppendPrintf("%04.0f-W%02d", year, week);
+ return true;
+ }
+ default:
+ MOZ_ASSERT(false, "Unrecognized input type");
+ return false;
+ }
+}
+
+
+Nullable<Date>
+HTMLInputElement::GetValueAsDate(ErrorResult& aRv)
+{
+ // TODO: this is temporary until bug 888331 is fixed.
+ if (!IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
+ return Nullable<Date>();
+ }
+
+ switch (mType) {
+ case NS_FORM_INPUT_DATE:
+ {
+ uint32_t year, month, day;
+ nsAutoString value;
+ GetValueInternal(value);
+ if (!ParseDate(value, &year, &month, &day)) {
+ return Nullable<Date>();
+ }
+
+ JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, day));
+ return Nullable<Date>(Date(time));
+ }
+ case NS_FORM_INPUT_TIME:
+ {
+ uint32_t millisecond;
+ nsAutoString value;
+ GetValueInternal(value);
+ if (!ParseTime(value, &millisecond)) {
+ return Nullable<Date>();
+ }
+
+ JS::ClippedTime time = JS::TimeClip(millisecond);
+ MOZ_ASSERT(time.toDouble() == millisecond,
+ "HTML times are restricted to the day after the epoch and "
+ "never clip");
+ return Nullable<Date>(Date(time));
+ }
+ case NS_FORM_INPUT_MONTH:
+ {
+ uint32_t year, month;
+ nsAutoString value;
+ GetValueInternal(value);
+ if (!ParseMonth(value, &year, &month)) {
+ return Nullable<Date>();
+ }
+
+ JS::ClippedTime time = JS::TimeClip(JS::MakeDate(year, month - 1, 1));
+ return Nullable<Date>(Date(time));
+ }
+ case NS_FORM_INPUT_WEEK:
+ {
+ uint32_t year, week;
+ nsAutoString value;
+ GetValueInternal(value);
+ if (!ParseWeek(value, &year, &week)) {
+ return Nullable<Date>();
+ }
+
+ double days = DaysSinceEpochFromWeek(year, week);
+ JS::ClippedTime time = JS::TimeClip(days * kMsPerDay);
+
+ return Nullable<Date>(Date(time));
+ }
+ }
+
+ MOZ_ASSERT(false, "Unrecognized input type");
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return Nullable<Date>();
+}
+
+void
+HTMLInputElement::SetValueAsDate(Nullable<Date> aDate, ErrorResult& aRv)
+{
+ // TODO: this is temporary until bug 888331 is fixed.
+ if (!IsDateTimeInputType(mType) || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (aDate.IsNull() || aDate.Value().IsUndefined()) {
+ aRv = SetValue(EmptyString());
+ return;
+ }
+
+ double milliseconds = aDate.Value().TimeStamp().toDouble();
+
+ if (mType != NS_FORM_INPUT_MONTH) {
+ SetValue(Decimal::fromDouble(milliseconds));
+ return;
+ }
+
+ // type=month expects the value to be number of months.
+ double year = JS::YearFromTime(milliseconds);
+ double month = JS::MonthFromTime(milliseconds);
+
+ if (IsNaN(year) || IsNaN(month)) {
+ SetValue(EmptyString());
+ return;
+ }
+
+ int32_t months = MonthsSinceJan1970(year, month + 1);
+ SetValue(Decimal(int32_t(months)));
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetValueAsNumber(double* aValueAsNumber)
+{
+ *aValueAsNumber = ValueAsNumber();
+ return NS_OK;
+}
+
+void
+HTMLInputElement::SetValueAsNumber(double aValueAsNumber, ErrorResult& aRv)
+{
+ // TODO: return TypeError when HTMLInputElement is converted to WebIDL, see
+ // bug 825197.
+ if (IsInfinite(aValueAsNumber)) {
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ if (!DoesValueAsNumberApply()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ SetValue(Decimal::fromDouble(aValueAsNumber));
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetValueAsNumber(double aValueAsNumber)
+{
+ ErrorResult rv;
+ SetValueAsNumber(aValueAsNumber, rv);
+ return rv.StealNSResult();
+}
+
+Decimal
+HTMLInputElement::GetMinimum() const
+{
+ MOZ_ASSERT(DoesValueAsNumberApply(),
+ "GetMinimum() should only be used for types that allow .valueAsNumber");
+
+ // Only type=range has a default minimum
+ Decimal defaultMinimum =
+ mType == NS_FORM_INPUT_RANGE ? Decimal(0) : Decimal::nan();
+
+ if (!HasAttr(kNameSpaceID_None, nsGkAtoms::min)) {
+ return defaultMinimum;
+ }
+
+ nsAutoString minStr;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
+
+ Decimal min;
+ return ConvertStringToNumber(minStr, min) ? min : defaultMinimum;
+}
+
+Decimal
+HTMLInputElement::GetMaximum() const
+{
+ MOZ_ASSERT(DoesValueAsNumberApply(),
+ "GetMaximum() should only be used for types that allow .valueAsNumber");
+
+ // Only type=range has a default maximum
+ Decimal defaultMaximum =
+ mType == NS_FORM_INPUT_RANGE ? Decimal(100) : Decimal::nan();
+
+ if (!HasAttr(kNameSpaceID_None, nsGkAtoms::max)) {
+ return defaultMaximum;
+ }
+
+ nsAutoString maxStr;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
+
+ Decimal max;
+ return ConvertStringToNumber(maxStr, max) ? max : defaultMaximum;
+}
+
+Decimal
+HTMLInputElement::GetStepBase() const
+{
+ MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER ||
+ mType == NS_FORM_INPUT_DATE ||
+ mType == NS_FORM_INPUT_TIME ||
+ mType == NS_FORM_INPUT_MONTH ||
+ mType == NS_FORM_INPUT_WEEK ||
+ mType == NS_FORM_INPUT_RANGE,
+ "Check that kDefaultStepBase is correct for this new type");
+
+ Decimal stepBase;
+
+ // Do NOT use GetMinimum here - the spec says to use "the min content
+ // attribute", not "the minimum".
+ nsAutoString minStr;
+ if (GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr) &&
+ ConvertStringToNumber(minStr, stepBase)) {
+ return stepBase;
+ }
+
+ // If @min is not a double, we should use @value.
+ nsAutoString valueStr;
+ if (GetAttr(kNameSpaceID_None, nsGkAtoms::value, valueStr) &&
+ ConvertStringToNumber(valueStr, stepBase)) {
+ return stepBase;
+ }
+
+ if (mType == NS_FORM_INPUT_WEEK) {
+ return kDefaultStepBaseWeek;
+ }
+
+ return kDefaultStepBase;
+}
+
+nsresult
+HTMLInputElement::GetValueIfStepped(int32_t aStep,
+ StepCallerType aCallerType,
+ Decimal* aNextStep)
+{
+ if (!DoStepDownStepUpApply()) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ Decimal stepBase = GetStepBase();
+ Decimal step = GetStep();
+ if (step == kStepAny) {
+ if (aCallerType != CALLED_FOR_USER_EVENT) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+ // Allow the spin buttons and up/down arrow keys to do something sensible:
+ step = GetDefaultStep();
+ }
+
+ Decimal minimum = GetMinimum();
+ Decimal maximum = GetMaximum();
+
+ if (!maximum.isNaN()) {
+ // "max - (max - stepBase) % step" is the nearest valid value to max.
+ maximum = maximum - NS_floorModulo(maximum - stepBase, step);
+ if (!minimum.isNaN()) {
+ if (minimum > maximum) {
+ // Either the minimum was greater than the maximum prior to our
+ // adjustment to align maximum on a step, or else (if we adjusted
+ // maximum) there is no valid step between minimum and the unadjusted
+ // maximum.
+ return NS_OK;
+ }
+ }
+ }
+
+ Decimal value = GetValueAsDecimal();
+ bool valueWasNaN = false;
+ if (value.isNaN()) {
+ value = Decimal(0);
+ valueWasNaN = true;
+ }
+ Decimal valueBeforeStepping = value;
+
+ Decimal deltaFromStep = NS_floorModulo(value - stepBase, step);
+
+ if (deltaFromStep != Decimal(0)) {
+ if (aStep > 0) {
+ value += step - deltaFromStep; // partial step
+ value += step * Decimal(aStep - 1); // then remaining steps
+ } else if (aStep < 0) {
+ value -= deltaFromStep; // partial step
+ value += step * Decimal(aStep + 1); // then remaining steps
+ }
+ } else {
+ value += step * Decimal(aStep);
+ }
+
+ if (value < minimum) {
+ value = minimum;
+ deltaFromStep = NS_floorModulo(value - stepBase, step);
+ if (deltaFromStep != Decimal(0)) {
+ value += step - deltaFromStep;
+ }
+ }
+ if (value > maximum) {
+ value = maximum;
+ deltaFromStep = NS_floorModulo(value - stepBase, step);
+ if (deltaFromStep != Decimal(0)) {
+ value -= deltaFromStep;
+ }
+ }
+
+ if (!valueWasNaN && // value="", resulting in us using "0"
+ ((aStep > 0 && value < valueBeforeStepping) ||
+ (aStep < 0 && value > valueBeforeStepping))) {
+ // We don't want step-up to effectively step down, or step-down to
+ // effectively step up, so return;
+ return NS_OK;
+ }
+
+ *aNextStep = value;
+ return NS_OK;
+}
+
+nsresult
+HTMLInputElement::ApplyStep(int32_t aStep)
+{
+ Decimal nextStep = Decimal::nan(); // unchanged if value will not change
+
+ nsresult rv = GetValueIfStepped(aStep, CALLED_FOR_SCRIPT, &nextStep);
+
+ if (NS_SUCCEEDED(rv) && nextStep.isFinite()) {
+ SetValue(nextStep);
+ }
+
+ return rv;
+}
+
+/* static */
+bool
+HTMLInputElement::IsExperimentalMobileType(uint8_t aType)
+{
+ return (aType == NS_FORM_INPUT_DATE &&
+ !Preferences::GetBool("dom.forms.datetime", false) &&
+ !Preferences::GetBool("dom.forms.datepicker", false)) ||
+ (aType == NS_FORM_INPUT_TIME &&
+ !Preferences::GetBool("dom.forms.datetime", false));
+}
+
+bool
+HTMLInputElement::IsDateTimeInputType(uint8_t aType)
+{
+ return aType == NS_FORM_INPUT_DATE ||
+ aType == NS_FORM_INPUT_TIME ||
+ aType == NS_FORM_INPUT_MONTH ||
+ aType == NS_FORM_INPUT_WEEK ||
+ aType == NS_FORM_INPUT_DATETIME_LOCAL;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::StepDown(int32_t n, uint8_t optional_argc)
+{
+ return ApplyStep(optional_argc ? -n : -1);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::StepUp(int32_t n, uint8_t optional_argc)
+{
+ return ApplyStep(optional_argc ? n : 1);
+}
+
+void
+HTMLInputElement::FlushFrames()
+{
+ if (GetComposedDoc()) {
+ GetComposedDoc()->FlushPendingNotifications(Flush_Frames);
+ }
+}
+
+void
+HTMLInputElement::MozGetFileNameArray(nsTArray<nsString>& aArray,
+ ErrorResult& aRv)
+{
+ for (uint32_t i = 0; i < mFilesOrDirectories.Length(); i++) {
+ nsAutoString str;
+ GetDOMFileOrDirectoryPath(mFilesOrDirectories[i], str, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ aArray.AppendElement(str);
+ }
+}
+
+
+NS_IMETHODIMP
+HTMLInputElement::MozGetFileNameArray(uint32_t* aLength, char16_t*** aFileNames)
+{
+ if (!nsContentUtils::IsCallerChrome()) {
+ // Since this function returns full paths it's important that normal pages
+ // can't call it.
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ ErrorResult rv;
+ nsTArray<nsString> array;
+ MozGetFileNameArray(array, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ *aLength = array.Length();
+ char16_t** ret =
+ static_cast<char16_t**>(moz_xmalloc(*aLength * sizeof(char16_t*)));
+ if (!ret) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (uint32_t i = 0; i < *aLength; ++i) {
+ ret[i] = NS_strdup(array[i].get());
+ }
+
+ *aFileNames = ret;
+
+ return NS_OK;
+}
+
+void
+HTMLInputElement::MozSetFileArray(const Sequence<OwningNonNull<File>>& aFiles)
+{
+ nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
+ MOZ_ASSERT(global);
+ if (!global) {
+ return;
+ }
+
+ nsTArray<OwningFileOrDirectory> files;
+ for (uint32_t i = 0; i < aFiles.Length(); ++i) {
+ RefPtr<File> file = File::Create(global, aFiles[i].get()->Impl());
+ MOZ_ASSERT(file);
+
+ OwningFileOrDirectory* element = files.AppendElement();
+ element->SetAsFile() = file;
+ }
+
+ SetFilesOrDirectories(files, true);
+}
+
+void
+HTMLInputElement::MozSetFileNameArray(const Sequence<nsString>& aFileNames,
+ ErrorResult& aRv)
+{
+ if (XRE_IsContentProcess()) {
+ aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ nsTArray<OwningFileOrDirectory> files;
+ for (uint32_t i = 0; i < aFileNames.Length(); ++i) {
+ nsCOMPtr<nsIFile> file;
+
+ if (StringBeginsWith(aFileNames[i], NS_LITERAL_STRING("file:"),
+ nsASCIICaseInsensitiveStringComparator())) {
+ // Converts the URL string into the corresponding nsIFile if possible
+ // A local file will be created if the URL string begins with file://
+ NS_GetFileFromURLSpec(NS_ConvertUTF16toUTF8(aFileNames[i]),
+ getter_AddRefs(file));
+ }
+
+ if (!file) {
+ // this is no "file://", try as local file
+ NS_NewLocalFile(aFileNames[i], false, getter_AddRefs(file));
+ }
+
+ if (!file) {
+ continue; // Not much we can do if the file doesn't exist
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
+ if (!global) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<File> domFile = File::CreateFromFile(global, file);
+
+ OwningFileOrDirectory* element = files.AppendElement();
+ element->SetAsFile() = domFile;
+ }
+
+ SetFilesOrDirectories(files, true);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::MozSetFileNameArray(const char16_t** aFileNames,
+ uint32_t aLength)
+{
+ if (!nsContentUtils::IsCallerChrome()) {
+ // setting the value of a "FILE" input widget requires chrome privilege
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ Sequence<nsString> list;
+ nsString* names = list.AppendElements(aLength, fallible);
+ if (!names) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ for (uint32_t i = 0; i < aLength; ++i) {
+ const char16_t* filename = aFileNames[i];
+ names[i].Rebind(filename, nsCharTraits<char16_t>::length(filename));
+ }
+
+ ErrorResult rv;
+ MozSetFileNameArray(list, rv);
+ return rv.StealNSResult();
+}
+
+void
+HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath,
+ ErrorResult& aRv)
+{
+ nsCOMPtr<nsIFile> file;
+ aRv = NS_NewLocalFile(aDirectoryPath, true, getter_AddRefs(file));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
+ if (NS_WARN_IF(!window)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ RefPtr<Directory> directory = Directory::Create(window, file);
+ MOZ_ASSERT(directory);
+
+ nsTArray<OwningFileOrDirectory> array;
+ OwningFileOrDirectory* element = array.AppendElement();
+ element->SetAsDirectory() = directory;
+
+ SetFilesOrDirectories(array, true);
+}
+
+void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue& aValue)
+{
+ if (NS_WARN_IF(!IsDateTimeInputType(mType)) || !mDateTimeInputBoxValue) {
+ return;
+ }
+
+ aValue = *mDateTimeInputBoxValue;
+}
+
+void
+HTMLInputElement::UpdateDateTimeInputBox(const DateTimeValue& aValue)
+{
+ if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+ return;
+ }
+
+ nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->SetValueFromPicker(aValue);
+ }
+}
+
+void
+HTMLInputElement::SetDateTimePickerState(bool aOpen)
+{
+ if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+ return;
+ }
+
+ nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->SetPickerState(aOpen);
+ }
+}
+
+void
+HTMLInputElement::OpenDateTimePicker(const DateTimeValue& aInitialValue)
+{
+ if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+ return;
+ }
+
+ mDateTimeInputBoxValue = new DateTimeValue(aInitialValue);
+ nsContentUtils::DispatchChromeEvent(OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(this),
+ NS_LITERAL_STRING("MozOpenDateTimePicker"),
+ true, true);
+}
+
+void
+HTMLInputElement::UpdateDateTimePicker(const DateTimeValue& aValue)
+{
+ if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+ return;
+ }
+
+ mDateTimeInputBoxValue = new DateTimeValue(aValue);
+ nsContentUtils::DispatchChromeEvent(OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(this),
+ NS_LITERAL_STRING("MozUpdateDateTimePicker"),
+ true, true);
+}
+
+void
+HTMLInputElement::CloseDateTimePicker()
+{
+ if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
+ return;
+ }
+
+ nsContentUtils::DispatchChromeEvent(OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(this),
+ NS_LITERAL_STRING("MozCloseDateTimePicker"),
+ true, true);
+}
+
+bool
+HTMLInputElement::MozIsTextField(bool aExcludePassword)
+{
+ // TODO: temporary until bug 888320 is fixed.
+ if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
+ return false;
+ }
+
+ return IsSingleLineTextControl(aExcludePassword);
+}
+
+HTMLInputElement*
+HTMLInputElement::GetOwnerNumberControl()
+{
+ if (IsInNativeAnonymousSubtree() &&
+ mType == NS_FORM_INPUT_TEXT &&
+ GetParent() && GetParent()->GetParent()) {
+ HTMLInputElement* grandparent =
+ HTMLInputElement::FromContentOrNull(GetParent()->GetParent());
+ if (grandparent && grandparent->mType == NS_FORM_INPUT_NUMBER) {
+ return grandparent;
+ }
+ }
+ return nullptr;
+}
+
+HTMLInputElement*
+HTMLInputElement::GetOwnerDateTimeControl()
+{
+ if (IsInNativeAnonymousSubtree() &&
+ mType == NS_FORM_INPUT_TEXT &&
+ GetParent() &&
+ GetParent()->GetParent() &&
+ GetParent()->GetParent()->GetParent() &&
+ GetParent()->GetParent()->GetParent()->GetParent()) {
+ // Yes, this is very very deep.
+ HTMLInputElement* ownerDateTimeControl =
+ HTMLInputElement::FromContentOrNull(
+ GetParent()->GetParent()->GetParent()->GetParent());
+ if (ownerDateTimeControl &&
+ ownerDateTimeControl->mType == NS_FORM_INPUT_TIME) {
+ return ownerDateTimeControl;
+ }
+ }
+ return nullptr;
+}
+
+
+NS_IMETHODIMP
+HTMLInputElement::MozIsTextField(bool aExcludePassword, bool* aResult)
+{
+ *aResult = MozIsTextField(aExcludePassword);
+ return NS_OK;
+}
+
+void
+HTMLInputElement::SetUserInput(const nsAString& aInput,
+ nsIPrincipal& aSubjectPrincipal) {
+ if (mType == NS_FORM_INPUT_FILE &&
+ !nsContentUtils::IsSystemPrincipal(&aSubjectPrincipal)) {
+ return;
+ }
+
+ SetUserInput(aInput);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetUserInput(const nsAString& aValue)
+{
+ if (mType == NS_FORM_INPUT_FILE)
+ {
+ Sequence<nsString> list;
+ if (!list.AppendElement(aValue, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ ErrorResult rv;
+ MozSetFileNameArray(list, rv);
+ return rv.StealNSResult();
+ } else {
+ nsresult rv =
+ SetValueInternal(aValue, nsTextEditorState::eSetValue_BySetUserInput |
+ nsTextEditorState::eSetValue_Notify);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(this),
+ NS_LITERAL_STRING("input"), true,
+ true);
+
+ // If this element is not currently focused, it won't receive a change event for this
+ // update through the normal channels. So fire a change event immediately, instead.
+ if (!ShouldBlur(this)) {
+ FireChangeEventIfNeeded();
+ }
+
+ return NS_OK;
+}
+
+nsIEditor*
+HTMLInputElement::GetEditor()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ return state->GetEditor();
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP_(nsIEditor*)
+HTMLInputElement::GetTextEditor()
+{
+ return GetEditor();
+}
+
+NS_IMETHODIMP_(nsISelectionController*)
+HTMLInputElement::GetSelectionController()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ return state->GetSelectionController();
+ }
+ return nullptr;
+}
+
+nsFrameSelection*
+HTMLInputElement::GetConstFrameSelection()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ return state->GetConstFrameSelection();
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::BindToFrame(nsTextControlFrame* aFrame)
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ return state->BindToFrame(aFrame);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP_(void)
+HTMLInputElement::UnbindFromFrame(nsTextControlFrame* aFrame)
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state && aFrame) {
+ state->UnbindFromFrame(aFrame);
+ }
+}
+
+NS_IMETHODIMP
+HTMLInputElement::CreateEditor()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ return state->PrepareEditor();
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP_(nsIContent*)
+HTMLInputElement::GetRootEditorNode()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ return state->GetRootNode();
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP_(Element*)
+HTMLInputElement::CreatePlaceholderNode()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ NS_ENSURE_SUCCESS(state->CreatePlaceholderNode(), nullptr);
+ return state->GetPlaceholderNode();
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP_(Element*)
+HTMLInputElement::GetPlaceholderNode()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ return state->GetPlaceholderNode();
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP_(void)
+HTMLInputElement::UpdatePlaceholderVisibility(bool aNotify)
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ state->UpdatePlaceholderVisibility(aNotify);
+ }
+}
+
+NS_IMETHODIMP_(bool)
+HTMLInputElement::GetPlaceholderVisibility()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (!state) {
+ return false;
+ }
+
+ return state->GetPlaceholderVisibility();
+}
+
+void
+HTMLInputElement::GetDisplayFileName(nsAString& aValue) const
+{
+ if (OwnerDoc()->IsStaticDocument()) {
+ aValue = mStaticDocFileList;
+ return;
+ }
+
+ if (mFilesOrDirectories.Length() == 1) {
+ GetDOMFileOrDirectoryName(mFilesOrDirectories[0], aValue);
+ return;
+ }
+
+ nsXPIDLString value;
+
+ if (mFilesOrDirectories.IsEmpty()) {
+ if ((Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs()) ||
+ (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "NoDirSelected", value);
+ } else if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "NoFilesSelected", value);
+ } else {
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "NoFileSelected", value);
+ }
+ } else {
+ nsString count;
+ count.AppendInt(int(mFilesOrDirectories.Length()));
+
+ const char16_t* params[] = { count.get() };
+ nsContentUtils::FormatLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "XFilesSelected", params, value);
+ }
+
+ aValue = value;
+}
+
+void
+HTMLInputElement::SetFilesOrDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories,
+ bool aSetValueChanged)
+{
+ ClearGetFilesHelpers();
+
+ if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
+ HTMLInputElementBinding::ClearCachedWebkitEntriesValue(this);
+ mEntries.Clear();
+ }
+
+ mFilesOrDirectories.Clear();
+ mFilesOrDirectories.AppendElements(aFilesOrDirectories);
+
+ AfterSetFilesOrDirectories(aSetValueChanged);
+}
+
+void
+HTMLInputElement::SetFiles(nsIDOMFileList* aFiles,
+ bool aSetValueChanged)
+{
+ RefPtr<FileList> files = static_cast<FileList*>(aFiles);
+ mFilesOrDirectories.Clear();
+ ClearGetFilesHelpers();
+
+ if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
+ HTMLInputElementBinding::ClearCachedWebkitEntriesValue(this);
+ mEntries.Clear();
+ }
+
+ if (aFiles) {
+ uint32_t listLength;
+ aFiles->GetLength(&listLength);
+ for (uint32_t i = 0; i < listLength; i++) {
+ OwningFileOrDirectory* element = mFilesOrDirectories.AppendElement();
+ element->SetAsFile() = files->Item(i);
+ }
+ }
+
+ AfterSetFilesOrDirectories(aSetValueChanged);
+}
+
+// This method is used for testing only.
+void
+HTMLInputElement::MozSetDndFilesAndDirectories(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
+{
+ SetFilesOrDirectories(aFilesOrDirectories, true);
+
+ if (Preferences::GetBool("dom.webkitBlink.filesystem.enabled", false)) {
+ UpdateEntries(aFilesOrDirectories);
+ }
+
+ RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
+ new DispatchChangeEventCallback(this);
+
+ if (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)) {
+ ErrorResult rv;
+ GetFilesHelper* helper = GetOrCreateGetFilesHelper(true /* recursionFlag */,
+ rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ return;
+ }
+
+ helper->AddCallback(dispatchChangeEventCallback);
+ } else {
+ dispatchChangeEventCallback->DispatchEvents();
+ }
+}
+
+void
+HTMLInputElement::AfterSetFilesOrDirectories(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);
+ }
+
+ // Grab the full path here for any chrome callers who access our .value via a
+ // CPOW. This path won't be called from a CPOW meaning the potential sync IPC
+ // call under GetMozFullPath won't be rejected for not being urgent.
+ // XXX Protected by the ifndef because the blob code doesn't allow us to send
+ // this message in b2g.
+ if (mFilesOrDirectories.IsEmpty()) {
+ mFirstFilePath.Truncate();
+ } else {
+ ErrorResult rv;
+ GetDOMFileOrDirectoryPath(mFilesOrDirectories[0], mFirstFilePath, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ }
+ }
+
+ UpdateFileList();
+
+ if (aSetValueChanged) {
+ SetValueChanged(true);
+ }
+
+ UpdateAllValidityStates(true);
+}
+
+void
+HTMLInputElement::FireChangeEventIfNeeded()
+{
+ nsAutoString value;
+ GetValue(value);
+
+ if (!MayFireChangeOnBlur() || mFocusedValue.Equals(value)) {
+ return;
+ }
+
+ // Dispatch the change event.
+ mFocusedValue = value;
+ nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+ static_cast<nsIContent*>(this),
+ NS_LITERAL_STRING("change"), true,
+ false);
+}
+
+FileList*
+HTMLInputElement::GetFiles()
+{
+ if (mType != NS_FORM_INPUT_FILE) {
+ return nullptr;
+ }
+
+ if (Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs() &&
+ (!Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) ||
+ !HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
+ return nullptr;
+ }
+
+ if (!mFileList) {
+ mFileList = new FileList(static_cast<nsIContent*>(this));
+ UpdateFileList();
+ }
+
+ return mFileList;
+}
+
+/* static */ void
+HTMLInputElement::HandleNumberControlSpin(void* aData)
+{
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(aData);
+
+ NS_ASSERTION(input->mNumberControlSpinnerIsSpinning,
+ "Should have called nsRepeatService::Stop()");
+
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(input->GetPrimaryFrame());
+ if (input->mType != NS_FORM_INPUT_NUMBER || !numberControlFrame) {
+ // Type has changed (and possibly our frame type hasn't been updated yet)
+ // or else we've lost our frame. Either way, stop the timer and don't do
+ // anything else.
+ input->StopNumberControlSpinnerSpin();
+ } else {
+ input->StepNumberControlForUserEvent(input->mNumberControlSpinnerSpinsUp ? 1 : -1);
+ }
+}
+
+void
+HTMLInputElement::UpdateFileList()
+{
+ if (mFileList) {
+ mFileList->Clear();
+
+ const nsTArray<OwningFileOrDirectory>& array =
+ GetFilesOrDirectoriesInternal();
+
+ for (uint32_t i = 0; i < array.Length(); ++i) {
+ if (array[i].IsFile()) {
+ mFileList->Append(array[i].GetAsFile());
+ }
+ }
+ }
+}
+
+nsresult
+HTMLInputElement::SetValueInternal(const nsAString& aValue, uint32_t aFlags)
+{
+ NS_PRECONDITION(GetValueMode() != VALUE_MODE_FILENAME,
+ "Don't call SetValueInternal for file inputs");
+
+ switch (GetValueMode()) {
+ case VALUE_MODE_VALUE:
+ {
+ // At the moment, only single line text control have to sanitize their value
+ // Because we have to create a new string for that, we should prevent doing
+ // it if it's useless.
+ nsAutoString value(aValue);
+
+ if (mDoneCreating) {
+ SanitizeValue(value);
+ }
+ // else DoneCreatingElement calls us again once mDoneCreating is true
+
+ bool setValueChanged = !!(aFlags & nsTextEditorState::eSetValue_Notify);
+ if (setValueChanged) {
+ SetValueChanged(true);
+ }
+
+ if (IsSingleLineTextControl(false)) {
+ if (!mInputData.mState->SetValue(value, aFlags)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ if (mType == NS_FORM_INPUT_EMAIL) {
+ UpdateAllValidityStates(!mDoneCreating);
+ }
+ } else {
+ free(mInputData.mValue);
+ mInputData.mValue = ToNewUnicode(value);
+ if (setValueChanged) {
+ SetValueChanged(true);
+ }
+ if (mType == NS_FORM_INPUT_NUMBER) {
+ // This has to happen before OnValueChanged is called because that
+ // method needs the new value of our frame's anon text control.
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ numberControlFrame->SetValueOfAnonTextControl(value);
+ }
+ } else if (mType == NS_FORM_INPUT_RANGE) {
+ nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->UpdateForValueChange();
+ }
+ } else if (mType == NS_FORM_INPUT_TIME &&
+ !IsExperimentalMobileType(mType)) {
+ nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->UpdateInputBoxValue();
+ }
+ }
+ if (mDoneCreating) {
+ OnValueChanged(/* aNotify = */ true,
+ /* aWasInteractiveUserChange = */ false);
+ }
+ // else DoneCreatingElement calls us again once mDoneCreating is true
+ }
+
+ if (mType == NS_FORM_INPUT_COLOR) {
+ // Update color frame, to reflect color changes
+ nsColorControlFrame* colorControlFrame = do_QueryFrame(GetPrimaryFrame());
+ if (colorControlFrame) {
+ colorControlFrame->UpdateColor();
+ }
+ }
+
+ // This call might be useless in some situations because if the element is
+ // a single line text control, nsTextEditorState::SetValue will call
+ // nsHTMLInputElement::OnValueChanged which is going to call UpdateState()
+ // if the element is focused. This bug 665547.
+ if (PlaceholderApplies() &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
+ UpdateState(true);
+ }
+
+ return NS_OK;
+ }
+
+ case VALUE_MODE_DEFAULT:
+ case VALUE_MODE_DEFAULT_ON:
+ // If the value of a hidden input was changed, we mark it changed so that we
+ // will know we need to save / restore the value. Yes, we are overloading
+ // the meaning of ValueChanged just a teensy bit to save a measly byte of
+ // storage space in HTMLInputElement. Yes, you are free to make a new flag,
+ // NEED_TO_SAVE_VALUE, at such time as mBitField becomes a 16-bit value.
+ if (mType == NS_FORM_INPUT_HIDDEN) {
+ SetValueChanged(true);
+ }
+
+ // Treat value == defaultValue for other input elements.
+ return nsGenericHTMLFormElementWithState::SetAttr(kNameSpaceID_None,
+ nsGkAtoms::value, aValue,
+ true);
+
+ case VALUE_MODE_FILENAME:
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // This return statement is required for some compilers.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetValueChanged(bool aValueChanged)
+{
+ bool valueChangedBefore = mValueChanged;
+
+ mValueChanged = aValueChanged;
+
+ if (valueChangedBefore != aValueChanged) {
+ UpdateState(true);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetChecked(bool* aChecked)
+{
+ *aChecked = Checked();
+ return NS_OK;
+}
+
+void
+HTMLInputElement::SetCheckedChanged(bool aCheckedChanged)
+{
+ DoSetCheckedChanged(aCheckedChanged, true);
+}
+
+void
+HTMLInputElement::DoSetCheckedChanged(bool aCheckedChanged,
+ bool aNotify)
+{
+ if (mType == NS_FORM_INPUT_RADIO) {
+ if (mCheckedChanged != aCheckedChanged) {
+ nsCOMPtr<nsIRadioVisitor> visitor =
+ new nsRadioSetCheckedChangedVisitor(aCheckedChanged);
+ VisitGroup(visitor, aNotify);
+ }
+ } else {
+ SetCheckedChangedInternal(aCheckedChanged);
+ }
+}
+
+void
+HTMLInputElement::SetCheckedChangedInternal(bool aCheckedChanged)
+{
+ bool checkedChangedBefore = mCheckedChanged;
+
+ mCheckedChanged = aCheckedChanged;
+
+ // This method can't be called when we are not authorized to notify
+ // so we do not need a aNotify parameter.
+ if (checkedChangedBefore != aCheckedChanged) {
+ UpdateState(true);
+ }
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetChecked(bool aChecked)
+{
+ DoSetChecked(aChecked, true, true);
+ return NS_OK;
+}
+
+void
+HTMLInputElement::DoSetChecked(bool aChecked, bool aNotify,
+ bool aSetValueChanged)
+{
+ // If the user or JS attempts to set checked, whether it actually changes the
+ // value or not, we say the value was changed so that defaultValue don't
+ // affect it no more.
+ if (aSetValueChanged) {
+ DoSetCheckedChanged(true, aNotify);
+ }
+
+ // Don't do anything if we're not changing whether it's checked (it would
+ // screw up state actually, especially when you are setting radio button to
+ // false)
+ if (mChecked == aChecked) {
+ return;
+ }
+
+ // Set checked
+ if (mType != NS_FORM_INPUT_RADIO) {
+ SetCheckedInternal(aChecked, aNotify);
+ return;
+ }
+
+ // For radio button, we need to do some extra fun stuff
+ if (aChecked) {
+ RadioSetChecked(aNotify);
+ return;
+ }
+
+ nsIRadioGroupContainer* container = GetRadioGroupContainer();
+ if (container) {
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+ container->SetCurrentRadioButton(name, nullptr);
+ }
+ // SetCheckedInternal is going to ask all radios to update their
+ // validity state. We have to be sure the radio group container knows
+ // the currently selected radio.
+ SetCheckedInternal(false, aNotify);
+}
+
+void
+HTMLInputElement::RadioSetChecked(bool aNotify)
+{
+ // Find the selected radio button so we can deselect it
+ nsCOMPtr<nsIDOMHTMLInputElement> currentlySelected = GetSelectedRadioButton();
+
+ // Deselect the currently selected radio button
+ if (currentlySelected) {
+ // Pass true for the aNotify parameter since the currently selected
+ // button is already in the document.
+ static_cast<HTMLInputElement*>(currentlySelected.get())
+ ->SetCheckedInternal(false, true);
+ }
+
+ // Let the group know that we are now the One True Radio Button
+ nsIRadioGroupContainer* container = GetRadioGroupContainer();
+ if (container) {
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+ container->SetCurrentRadioButton(name, this);
+ }
+
+ // SetCheckedInternal is going to ask all radios to update their
+ // validity state.
+ SetCheckedInternal(true, aNotify);
+}
+
+nsIRadioGroupContainer*
+HTMLInputElement::GetRadioGroupContainer() const
+{
+ NS_ASSERTION(mType == NS_FORM_INPUT_RADIO,
+ "GetRadioGroupContainer should only be called when type='radio'");
+
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+
+ if (name.IsEmpty()) {
+ return nullptr;
+ }
+
+ if (mForm) {
+ return mForm;
+ }
+
+ //XXXsmaug It isn't clear how this should work in Shadow DOM.
+ return static_cast<nsDocument*>(GetUncomposedDoc());
+}
+
+already_AddRefed<nsIDOMHTMLInputElement>
+HTMLInputElement::GetSelectedRadioButton() const
+{
+ nsIRadioGroupContainer* container = GetRadioGroupContainer();
+ if (!container) {
+ return nullptr;
+ }
+
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+
+ nsCOMPtr<nsIDOMHTMLInputElement> selected = container->GetCurrentRadioButton(name);
+ return selected.forget();
+}
+
+nsresult
+HTMLInputElement::MaybeSubmitForm(nsPresContext* aPresContext)
+{
+ if (!mForm) {
+ // Nothing to do here.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPresShell> shell = aPresContext->GetPresShell();
+ if (!shell) {
+ return NS_OK;
+ }
+
+ // Get the default submit element
+ nsIFormControl* submitControl = mForm->GetDefaultSubmitElement();
+ if (submitControl) {
+ nsCOMPtr<nsIContent> submitContent = do_QueryInterface(submitControl);
+ NS_ASSERTION(submitContent, "Form control not implementing nsIContent?!");
+ // Fire the button's onclick handler and let the button handle
+ // submitting the form.
+ WidgetMouseEvent event(true, eMouseClick, nullptr, WidgetMouseEvent::eReal);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ shell->HandleDOMEventWithTarget(submitContent, &event, &status);
+ } else if (!mForm->ImplicitSubmissionIsDisabled() &&
+ mForm->SubmissionCanProceed(nullptr)) {
+ // TODO: removing this code and have the submit event sent by the form,
+ // bug 592124.
+ // If there's only one text control, just submit the form
+ // Hold strong ref across the event
+ RefPtr<mozilla::dom::HTMLFormElement> form = mForm;
+ InternalFormEvent event(true, eFormSubmit);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ shell->HandleDOMEventWithTarget(form, &event, &status);
+ }
+
+ return NS_OK;
+}
+
+void
+HTMLInputElement::SetCheckedInternal(bool aChecked, bool aNotify)
+{
+ // Set the value
+ mChecked = aChecked;
+
+ // Notify the frame
+ if (mType == NS_FORM_INPUT_CHECKBOX || mType == NS_FORM_INPUT_RADIO) {
+ nsIFrame* frame = GetPrimaryFrame();
+ if (frame) {
+ frame->InvalidateFrameSubtree();
+ }
+ }
+
+ UpdateAllValidityStates(aNotify);
+
+ // Notify the document that the CSS :checked pseudoclass for this element
+ // has changed state.
+ UpdateState(aNotify);
+
+ // Notify all radios in the group that value has changed, this is to let
+ // radios to have the chance to update its states, e.g., :indeterminate.
+ if (mType == NS_FORM_INPUT_RADIO) {
+ nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
+ VisitGroup(visitor, aNotify);
+ }
+}
+
+void
+HTMLInputElement::Blur(ErrorResult& aError)
+{
+ if (mType == NS_FORM_INPUT_NUMBER) {
+ // Blur our anonymous text control, if we have one. (DOM 'change' event
+ // firing and other things depend on this.)
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ HTMLInputElement* textControl = numberControlFrame->GetAnonTextControl();
+ if (textControl) {
+ textControl->Blur(aError);
+ return;
+ }
+ }
+ }
+
+ if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) {
+ nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->HandleBlurEvent();
+ return;
+ }
+ }
+
+ nsGenericHTMLElement::Blur(aError);
+}
+
+void
+HTMLInputElement::Focus(ErrorResult& aError)
+{
+ if (mType == NS_FORM_INPUT_NUMBER) {
+ // Focus our anonymous text control, if we have one.
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ HTMLInputElement* textControl = numberControlFrame->GetAnonTextControl();
+ if (textControl) {
+ textControl->Focus(aError);
+ return;
+ }
+ }
+ }
+
+ if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType)) {
+ nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->HandleFocusEvent();
+ return;
+ }
+ }
+
+ if (mType != NS_FORM_INPUT_FILE) {
+ nsGenericHTMLElement::Focus(aError);
+ return;
+ }
+
+ // For file inputs, focus the first button instead. In the case of there
+ // being two buttons (when the picker is a directory picker) the user can
+ // tab to the next one.
+ nsIFrame* frame = GetPrimaryFrame();
+ if (frame) {
+ for (nsIFrame* childFrame : frame->PrincipalChildList()) {
+ // See if the child is a button control.
+ nsCOMPtr<nsIFormControl> formCtrl =
+ do_QueryInterface(childFrame->GetContent());
+ if (formCtrl && formCtrl->GetType() == NS_FORM_BUTTON_BUTTON) {
+ nsCOMPtr<nsIDOMElement> element = do_QueryInterface(formCtrl);
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm && element) {
+ fm->SetFocus(element, 0);
+ }
+ break;
+ }
+ }
+ }
+
+ return;
+}
+
+#if !defined(ANDROID) && !defined(XP_MACOSX)
+bool
+HTMLInputElement::IsNodeApzAwareInternal() const
+{
+ // Tell APZC we may handle mouse wheel event and do preventDefault when input
+ // type is number.
+ return (mType == NS_FORM_INPUT_NUMBER) || (mType == NS_FORM_INPUT_RANGE) ||
+ nsINode::IsNodeApzAwareInternal();
+}
+#endif
+
+bool
+HTMLInputElement::IsInteractiveHTMLContent(bool aIgnoreTabindex) const
+{
+ return mType != NS_FORM_INPUT_HIDDEN ||
+ nsGenericHTMLFormElementWithState::IsInteractiveHTMLContent(aIgnoreTabindex);
+}
+
+void
+HTMLInputElement::AsyncEventRunning(AsyncEventDispatcher* aEvent)
+{
+ nsImageLoadingContent::AsyncEventRunning(aEvent);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::Select()
+{
+ if (mType == NS_FORM_INPUT_NUMBER) {
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ return numberControlFrame->HandleSelectCall();
+ }
+ return NS_OK;
+ }
+
+ if (!IsSingleLineTextControl(false)) {
+ return NS_OK;
+ }
+
+ // XXX Bug? We have to give the input focus before contents can be
+ // selected
+
+ FocusTristate state = FocusState();
+ if (state == eUnfocusable) {
+ return NS_OK;
+ }
+
+ nsTextEditorState* tes = GetEditorState();
+ if (tes) {
+ RefPtr<nsFrameSelection> fs = tes->GetConstFrameSelection();
+ if (fs && fs->MouseDownRecorded()) {
+ // This means that we're being called while the frame selection has a mouse
+ // down event recorded to adjust the caret during the mouse up event.
+ // We are probably called from the focus event handler. We should override
+ // the delayed caret data in this case to ensure that this select() call
+ // takes effect.
+ fs->SetDelayedCaretData(nullptr);
+ }
+ }
+
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+
+ RefPtr<nsPresContext> presContext = GetPresContext(eForComposedDoc);
+ if (state == eInactiveWindow) {
+ if (fm)
+ fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL);
+ SelectAll(presContext);
+ return NS_OK;
+ }
+
+ if (DispatchSelectEvent(presContext) && fm) {
+ fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL);
+
+ // ensure that the element is actually focused
+ nsCOMPtr<nsIDOMElement> focusedElement;
+ fm->GetFocusedElement(getter_AddRefs(focusedElement));
+ if (SameCOMIdentity(static_cast<nsIDOMNode*>(this), focusedElement)) {
+ // Now Select all the text!
+ SelectAll(presContext);
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+HTMLInputElement::DispatchSelectEvent(nsPresContext* aPresContext)
+{
+ nsEventStatus status = nsEventStatus_eIgnore;
+
+ // If already handling select event, don't dispatch a second.
+ if (!mHandlingSelectEvent) {
+ WidgetEvent event(nsContentUtils::LegacyIsCallerChromeOrNativeCode(), eFormSelect);
+
+ mHandlingSelectEvent = true;
+ EventDispatcher::Dispatch(static_cast<nsIContent*>(this),
+ aPresContext, &event, nullptr, &status);
+ mHandlingSelectEvent = false;
+ }
+
+ // If the DOM event was not canceled (e.g. by a JS event handler
+ // returning false)
+ return (status == nsEventStatus_eIgnore);
+}
+
+void
+HTMLInputElement::SelectAll(nsPresContext* aPresContext)
+{
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+
+ if (formControlFrame) {
+ formControlFrame->SetFormProperty(nsGkAtoms::select, EmptyString());
+ }
+}
+
+bool
+HTMLInputElement::NeedToInitializeEditorForEvent(
+ EventChainPreVisitor& aVisitor) const
+{
+ // We only need to initialize the editor for single line input controls because they
+ // are lazily initialized. We don't need to initialize the control for
+ // certain types of events, because we know that those events are safe to be
+ // handled without the editor being initialized. These events include:
+ // mousein/move/out, overflow/underflow, and DOM mutation events.
+ if (!IsSingleLineTextControl(false) ||
+ aVisitor.mEvent->mClass == eMutationEventClass) {
+ return false;
+ }
+
+ switch (aVisitor.mEvent->mMessage) {
+ case eMouseMove:
+ case eMouseEnterIntoWidget:
+ case eMouseExitFromWidget:
+ case eMouseOver:
+ case eMouseOut:
+ case eScrollPortUnderflow:
+ case eScrollPortOverflow:
+ return false;
+ default:
+ return true;
+ }
+}
+
+bool
+HTMLInputElement::IsDisabledForEvents(EventMessage aMessage)
+{
+ return IsElementDisabledForEvents(aMessage, GetPrimaryFrame());
+}
+
+nsresult
+HTMLInputElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
+{
+ // Do not process any DOM events if the element is disabled
+ aVisitor.mCanHandle = false;
+ if (IsDisabledForEvents(aVisitor.mEvent->mMessage)) {
+ return NS_OK;
+ }
+
+ // Initialize the editor if needed.
+ if (NeedToInitializeEditorForEvent(aVisitor)) {
+ nsITextControlFrame* textControlFrame = do_QueryFrame(GetPrimaryFrame());
+ if (textControlFrame)
+ textControlFrame->EnsureEditorInitialized();
+ }
+
+ //FIXME Allow submission etc. also when there is no prescontext, Bug 329509.
+ if (!aVisitor.mPresContext) {
+ return nsGenericHTMLElement::PreHandleEvent(aVisitor);
+ }
+ //
+ // Web pages expect the value of a radio button or checkbox to be set
+ // *before* onclick and DOMActivate fire, and they expect that if they set
+ // the value explicitly during onclick or DOMActivate it will not be toggled
+ // or any such nonsense.
+ // In order to support that (bug 57137 and 58460 are examples) we toggle
+ // the checked attribute *first*, and then fire onclick. If the user
+ // returns false, we reset the control to the old checked value. Otherwise,
+ // we dispatch DOMActivate. If DOMActivate is cancelled, we also reset
+ // the control to the old checked value. We need to keep track of whether
+ // we've already toggled the state from onclick since the user could
+ // explicitly dispatch DOMActivate on the element.
+ //
+ // This is a compatibility hack.
+ //
+
+ // Track whether we're in the outermost Dispatch invocation that will
+ // cause activation of the input. That is, if we're a click event, or a
+ // DOMActivate that was dispatched directly, this will be set, but if we're
+ // a DOMActivate dispatched from click handling, it will not be set.
+ WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
+ bool outerActivateEvent =
+ ((mouseEvent && mouseEvent->IsLeftClickEvent()) ||
+ (aVisitor.mEvent->mMessage == eLegacyDOMActivate && !mInInternalActivate));
+
+ if (outerActivateEvent) {
+ aVisitor.mItemFlags |= NS_OUTER_ACTIVATE_EVENT;
+ }
+
+ bool originalCheckedValue = false;
+
+ if (outerActivateEvent) {
+ mCheckedIsToggled = false;
+
+ switch(mType) {
+ case NS_FORM_INPUT_CHECKBOX:
+ {
+ if (mIndeterminate) {
+ // indeterminate is always set to FALSE when the checkbox is toggled
+ SetIndeterminateInternal(false, false);
+ aVisitor.mItemFlags |= NS_ORIGINAL_INDETERMINATE_VALUE;
+ }
+
+ GetChecked(&originalCheckedValue);
+ DoSetChecked(!originalCheckedValue, true, true);
+ mCheckedIsToggled = true;
+ }
+ break;
+
+ case NS_FORM_INPUT_RADIO:
+ {
+ nsCOMPtr<nsIDOMHTMLInputElement> selectedRadioButton = GetSelectedRadioButton();
+ aVisitor.mItemData = selectedRadioButton;
+
+ originalCheckedValue = mChecked;
+ if (!originalCheckedValue) {
+ DoSetChecked(true, true, true);
+ mCheckedIsToggled = true;
+ }
+ }
+ break;
+
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_IMAGE:
+ if (mForm) {
+ // tell the form that we are about to enter a click handler.
+ // that means that if there are scripted submissions, the
+ // latest one will be deferred until after the exit point of the handler.
+ mForm->OnSubmitClickBegin(this);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (originalCheckedValue) {
+ aVisitor.mItemFlags |= NS_ORIGINAL_CHECKED_VALUE;
+ }
+
+ // If mNoContentDispatch is true we will not allow content to handle
+ // this event. But to allow middle mouse button paste to work we must allow
+ // middle clicks to go to text fields anyway.
+ if (aVisitor.mEvent->mFlags.mNoContentDispatch) {
+ aVisitor.mItemFlags |= NS_NO_CONTENT_DISPATCH;
+ }
+ if (IsSingleLineTextControl(false) &&
+ aVisitor.mEvent->mMessage == eMouseClick &&
+ aVisitor.mEvent->AsMouseEvent()->button ==
+ WidgetMouseEvent::eMiddleButton) {
+ aVisitor.mEvent->mFlags.mNoContentDispatch = false;
+ }
+
+ // We must cache type because mType may change during JS event (bug 2369)
+ aVisitor.mItemFlags |= mType;
+
+ if (aVisitor.mEvent->mMessage == eFocus &&
+ aVisitor.mEvent->IsTrusted() &&
+ MayFireChangeOnBlur() &&
+ // StartRangeThumbDrag already set mFocusedValue on 'mousedown' before
+ // we get the 'focus' event.
+ !mIsDraggingRange) {
+ GetValue(mFocusedValue);
+ }
+
+ // Fire onchange (if necessary), before we do the blur, bug 357684.
+ if (aVisitor.mEvent->mMessage == eBlur) {
+ // Experimental mobile types rely on the system UI to prevent users to not
+ // set invalid values but we have to be extra-careful. Especially if the
+ // option has been enabled on desktop.
+ if (IsExperimentalMobileType(mType)) {
+ nsAutoString aValue;
+ GetValueInternal(aValue);
+ nsresult rv =
+ SetValueInternal(aValue, nsTextEditorState::eSetValue_Internal);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ FireChangeEventIfNeeded();
+ }
+
+ if (mType == NS_FORM_INPUT_RANGE &&
+ (aVisitor.mEvent->mMessage == eFocus ||
+ aVisitor.mEvent->mMessage == eBlur)) {
+ // Just as nsGenericHTMLFormElementWithState::PreHandleEvent calls
+ // nsIFormControlFrame::SetFocus, we handle focus here.
+ nsIFrame* frame = GetPrimaryFrame();
+ if (frame) {
+ frame->InvalidateFrameSubtree();
+ }
+ }
+
+ if (mType == NS_FORM_INPUT_TIME &&
+ !IsExperimentalMobileType(mType) &&
+ aVisitor.mEvent->mMessage == eFocus &&
+ aVisitor.mEvent->mOriginalTarget == this) {
+ // If original target is this and not the anonymous text control, we should
+ // pass the focus to the anonymous text control.
+ nsDateTimeControlFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->HandleFocusEvent();
+ }
+ }
+
+ if (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted()) {
+ if (mNumberControlSpinnerIsSpinning) {
+ // If the timer is running the user has depressed the mouse on one of the
+ // spin buttons. If the mouse exits the button we either want to reverse
+ // the direction of spin if it has moved over the other button, or else
+ // we want to end the spin. We do this here (rather than in
+ // PostHandleEvent) because we don't want to let content preventDefault()
+ // the end of the spin.
+ if (aVisitor.mEvent->mMessage == eMouseMove) {
+ // Be aggressive about stopping the spin:
+ bool stopSpin = true;
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ bool oldNumberControlSpinTimerSpinsUpValue =
+ mNumberControlSpinnerSpinsUp;
+ switch (numberControlFrame->GetSpinButtonForPointerEvent(
+ aVisitor.mEvent->AsMouseEvent())) {
+ case nsNumberControlFrame::eSpinButtonUp:
+ mNumberControlSpinnerSpinsUp = true;
+ stopSpin = false;
+ break;
+ case nsNumberControlFrame::eSpinButtonDown:
+ mNumberControlSpinnerSpinsUp = false;
+ stopSpin = false;
+ break;
+ }
+ if (mNumberControlSpinnerSpinsUp !=
+ oldNumberControlSpinTimerSpinsUpValue) {
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ numberControlFrame->SpinnerStateChanged();
+ }
+ }
+ }
+ if (stopSpin) {
+ StopNumberControlSpinnerSpin();
+ }
+ } else if (aVisitor.mEvent->mMessage == eMouseUp) {
+ StopNumberControlSpinnerSpin();
+ }
+ }
+ if (aVisitor.mEvent->mMessage == eFocus ||
+ aVisitor.mEvent->mMessage == eBlur) {
+ if (aVisitor.mEvent->mMessage == eFocus) {
+ // Tell our frame it's getting focus so that it can make sure focus
+ // is moved to our anonymous text control.
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ // This could kill the frame!
+ numberControlFrame->HandleFocusEvent(aVisitor.mEvent);
+ }
+ }
+ nsIFrame* frame = GetPrimaryFrame();
+ if (frame && frame->IsThemed()) {
+ // Our frame's nested <input type=text> will be invalidated when it
+ // loses focus, but since we are also native themed we need to make
+ // sure that our entire area is repainted since any focus highlight
+ // from the theme should be removed from us (the repainting of the
+ // sub-area occupied by the anon text control is not enough to do
+ // that).
+ frame->InvalidateFrame();
+ }
+ }
+ }
+
+ nsresult rv = nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor);
+
+ // We do this after calling the base class' PreHandleEvent so that
+ // nsIContent::PreHandleEvent doesn't reset any change we make to mCanHandle.
+ if (mType == NS_FORM_INPUT_NUMBER &&
+ aVisitor.mEvent->IsTrusted() &&
+ aVisitor.mEvent->mOriginalTarget != this) {
+ // <input type=number> has an anonymous <input type=text> descendant. If
+ // 'input' or 'change' events are fired at that text control then we need
+ // to do some special handling here.
+ HTMLInputElement* textControl = nullptr;
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ textControl = numberControlFrame->GetAnonTextControl();
+ }
+ if (textControl && aVisitor.mEvent->mOriginalTarget == textControl) {
+ if (aVisitor.mEvent->mMessage == eEditorInput) {
+ // Propogate the anon text control's new value to our HTMLInputElement:
+ nsAutoString value;
+ numberControlFrame->GetValueOfAnonTextControl(value);
+ numberControlFrame->HandlingInputEvent(true);
+ nsWeakFrame weakNumberControlFrame(numberControlFrame);
+ rv = SetValueInternal(value,
+ nsTextEditorState::eSetValue_BySetUserInput |
+ nsTextEditorState::eSetValue_Notify);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (weakNumberControlFrame.IsAlive()) {
+ numberControlFrame->HandlingInputEvent(false);
+ }
+ }
+ else if (aVisitor.mEvent->mMessage == eFormChange) {
+ // We cancel the DOM 'change' event that is fired for any change to our
+ // anonymous text control since we fire our own 'change' events and
+ // content shouldn't be seeing two 'change' events. Besides that we
+ // (as a number) control have tighter restrictions on when our internal
+ // value changes than our anon text control does, so in some cases
+ // (if our text control's value doesn't parse as a number) we don't
+ // want to fire a 'change' event at all.
+ aVisitor.mCanHandle = false;
+ }
+ }
+ }
+
+ // Stop the event if the related target's first non-native ancestor is the
+ // same as the original target's first non-native ancestor (we are moving
+ // inside of the same element).
+ if (mType == NS_FORM_INPUT_TIME && !IsExperimentalMobileType(mType) &&
+ (aVisitor.mEvent->mMessage == eFocus ||
+ aVisitor.mEvent->mMessage == eFocusIn ||
+ aVisitor.mEvent->mMessage == eFocusOut ||
+ aVisitor.mEvent->mMessage == eBlur)) {
+ nsCOMPtr<nsIContent> originalTarget =
+ do_QueryInterface(aVisitor.mEvent->AsFocusEvent()->mRelatedTarget);
+ nsCOMPtr<nsIContent> relatedTarget =
+ do_QueryInterface(aVisitor.mEvent->AsFocusEvent()->mRelatedTarget);
+
+ if (originalTarget && relatedTarget &&
+ originalTarget->FindFirstNonChromeOnlyAccessContent() ==
+ relatedTarget->FindFirstNonChromeOnlyAccessContent()) {
+ aVisitor.mCanHandle = false;
+ }
+ }
+
+ return rv;
+}
+
+void
+HTMLInputElement::StartRangeThumbDrag(WidgetGUIEvent* aEvent)
+{
+ mIsDraggingRange = true;
+ mRangeThumbDragStartValue = GetValueAsDecimal();
+ // Don't use CAPTURE_RETARGETTOELEMENT, as that breaks pseudo-class styling
+ // of the thumb.
+ nsIPresShell::SetCapturingContent(this, CAPTURE_IGNOREALLOWED);
+ nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
+
+ // Before we change the value, record the current value so that we'll
+ // correctly send a 'change' event if appropriate. We need to do this here
+ // because the 'focus' event is handled after the 'mousedown' event that
+ // we're being called for (i.e. too late to update mFocusedValue, since we'll
+ // have changed it by then).
+ GetValue(mFocusedValue);
+
+ SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent));
+}
+
+void
+HTMLInputElement::FinishRangeThumbDrag(WidgetGUIEvent* aEvent)
+{
+ MOZ_ASSERT(mIsDraggingRange);
+
+ if (nsIPresShell::GetCapturingContent() == this) {
+ nsIPresShell::SetCapturingContent(nullptr, 0); // cancel capture
+ }
+ if (aEvent) {
+ nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
+ SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent));
+ }
+ mIsDraggingRange = false;
+ FireChangeEventIfNeeded();
+}
+
+void
+HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent)
+{
+ MOZ_ASSERT(mIsDraggingRange);
+
+ mIsDraggingRange = false;
+ if (nsIPresShell::GetCapturingContent() == this) {
+ nsIPresShell::SetCapturingContent(nullptr, 0); // cancel capture
+ }
+ if (aIsForUserEvent) {
+ SetValueOfRangeForUserEvent(mRangeThumbDragStartValue);
+ } else {
+ // Don't dispatch an 'input' event - at least not using
+ // DispatchTrustedEvent.
+ // TODO: decide what we should do here - bug 851782.
+ nsAutoString val;
+ ConvertNumberToString(mRangeThumbDragStartValue, val);
+ // TODO: What should we do if SetValueInternal fails? (The allocation
+ // is small, so we should be fine here.)
+ SetValueInternal(val, nsTextEditorState::eSetValue_BySetUserInput |
+ nsTextEditorState::eSetValue_Notify);
+ nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->UpdateForValueChange();
+ }
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, NS_LITERAL_STRING("input"), true, false);
+ asyncDispatcher->RunDOMEventWhenSafe();
+ }
+}
+
+void
+HTMLInputElement::SetValueOfRangeForUserEvent(Decimal aValue)
+{
+ MOZ_ASSERT(aValue.isFinite());
+
+ Decimal oldValue = GetValueAsDecimal();
+
+ nsAutoString val;
+ ConvertNumberToString(aValue, val);
+ // TODO: What should we do if SetValueInternal fails? (The allocation
+ // is small, so we should be fine here.)
+ SetValueInternal(val, nsTextEditorState::eSetValue_BySetUserInput |
+ nsTextEditorState::eSetValue_Notify);
+ nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
+ if (frame) {
+ frame->UpdateForValueChange();
+ }
+
+ if (GetValueAsDecimal() != oldValue) {
+ nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(this),
+ NS_LITERAL_STRING("input"), true,
+ false);
+ }
+}
+
+void
+HTMLInputElement::StartNumberControlSpinnerSpin()
+{
+ MOZ_ASSERT(!mNumberControlSpinnerIsSpinning);
+
+ mNumberControlSpinnerIsSpinning = true;
+
+ nsRepeatService::GetInstance()->Start(HandleNumberControlSpin, this);
+
+ // Capture the mouse so that we can tell if the pointer moves from one
+ // spin button to the other, or to some other element:
+ nsIPresShell::SetCapturingContent(this, CAPTURE_IGNOREALLOWED);
+
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ numberControlFrame->SpinnerStateChanged();
+ }
+}
+
+void
+HTMLInputElement::StopNumberControlSpinnerSpin(SpinnerStopState aState)
+{
+ if (mNumberControlSpinnerIsSpinning) {
+ if (nsIPresShell::GetCapturingContent() == this) {
+ nsIPresShell::SetCapturingContent(nullptr, 0); // cancel capture
+ }
+
+ nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin, this);
+
+ mNumberControlSpinnerIsSpinning = false;
+
+ if (aState == eAllowDispatchingEvents) {
+ FireChangeEventIfNeeded();
+ }
+
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ MOZ_ASSERT(aState == eAllowDispatchingEvents,
+ "Shouldn't have primary frame for the element when we're not "
+ "allowed to dispatch events to it anymore.");
+ numberControlFrame->SpinnerStateChanged();
+ }
+ }
+}
+
+void
+HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection)
+{
+ // We can't use GetValidityState here because the validity state is not set
+ // if the user hasn't previously taken an action to set or change the value,
+ // according to the specs.
+ if (HasBadInput()) {
+ // If the user has typed a value into the control and inadvertently made a
+ // mistake (e.g. put a thousand separator at the wrong point) we do not
+ // want to wipe out what they typed if they try to increment/decrement the
+ // value. Better is to highlight the value as being invalid so that they
+ // can correct what they typed.
+ // We only do this if there actually is a value typed in by/displayed to
+ // the user. (IsValid() can return false if the 'required' attribute is
+ // set and the value is the empty string.)
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame &&
+ !numberControlFrame->AnonTextControlIsEmpty()) {
+ // We pass 'true' for UpdateValidityUIBits' aIsFocused argument
+ // regardless because we need the UI to update _now_ or the user will
+ // wonder why the step behavior isn't functioning.
+ UpdateValidityUIBits(true);
+ UpdateState(true);
+ return;
+ }
+ }
+
+ Decimal newValue = Decimal::nan(); // unchanged if value will not change
+
+ nsresult rv = GetValueIfStepped(aDirection, CALLED_FOR_USER_EVENT, &newValue);
+
+ if (NS_FAILED(rv) || !newValue.isFinite()) {
+ return; // value should not or will not change
+ }
+
+ nsAutoString newVal;
+ ConvertNumberToString(newValue, newVal);
+ // TODO: What should we do if SetValueInternal fails? (The allocation
+ // is small, so we should be fine here.)
+ SetValueInternal(newVal, nsTextEditorState::eSetValue_BySetUserInput |
+ nsTextEditorState::eSetValue_Notify);
+
+ nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(this),
+ NS_LITERAL_STRING("input"), true,
+ false);
+}
+
+static bool
+SelectTextFieldOnFocus()
+{
+ if (!gSelectTextFieldOnFocus) {
+ int32_t selectTextfieldsOnKeyFocus = -1;
+ nsresult rv =
+ LookAndFeel::GetInt(LookAndFeel::eIntID_SelectTextfieldsOnKeyFocus,
+ &selectTextfieldsOnKeyFocus);
+ if (NS_FAILED(rv)) {
+ gSelectTextFieldOnFocus = -1;
+ } else {
+ gSelectTextFieldOnFocus = selectTextfieldsOnKeyFocus != 0 ? 1 : -1;
+ }
+ }
+
+ return gSelectTextFieldOnFocus == 1;
+}
+
+bool
+HTMLInputElement::ShouldPreventDOMActivateDispatch(EventTarget* aOriginalTarget)
+{
+ /*
+ * For the moment, there is only one situation where we actually want to
+ * prevent firing a DOMActivate event:
+ * - we are a <input type='file'> that just got a click event,
+ * - the event was targeted to our button which should have sent a
+ * DOMActivate event.
+ */
+
+ if (mType != NS_FORM_INPUT_FILE) {
+ return false;
+ }
+
+ nsCOMPtr<nsIContent> target = do_QueryInterface(aOriginalTarget);
+ if (!target) {
+ return false;
+ }
+
+ return target->GetParent() == this &&
+ target->IsRootOfNativeAnonymousSubtree() &&
+ target->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+ nsGkAtoms::button, eCaseMatters);
+}
+
+nsresult
+HTMLInputElement::MaybeInitPickers(EventChainPostVisitor& aVisitor)
+{
+ // Open a file picker when we receive a click on a <input type='file'>, or
+ // open a color picker when we receive a click on a <input type='color'>.
+ // 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 (aVisitor.mEvent->DefaultPrevented()) {
+ return NS_OK;
+ }
+ WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
+ if (!(mouseEvent && mouseEvent->IsLeftClickEvent())) {
+ return NS_OK;
+ }
+ if (mType == NS_FORM_INPUT_FILE) {
+ // 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->FindFirstNonChromeOnlyAccessContent() == this &&
+ ((Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs()) ||
+ (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)))) {
+ type = FILE_PICKER_DIRECTORY;
+ }
+ return InitFilePicker(type);
+ }
+ if (mType == NS_FORM_INPUT_COLOR) {
+ return InitColorPicker();
+ }
+ if (mType == NS_FORM_INPUT_DATE) {
+ return InitDatePicker();
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Return true if the input event should be ignore because of it's modifiers
+ */
+static bool
+IgnoreInputEventWithModifier(WidgetInputEvent* aEvent)
+{
+ return aEvent->IsShift() || aEvent->IsControl() || aEvent->IsAlt() ||
+ aEvent->IsMeta() || aEvent->IsAltGraph() || aEvent->IsFn() ||
+ aEvent->IsOS();
+}
+
+nsresult
+HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
+{
+ if (!aVisitor.mPresContext) {
+ // Hack alert! In order to open file picker even in case the element isn't
+ // in document, try to init picker even without PresContext.
+ return MaybeInitPickers(aVisitor);
+ }
+
+ if (aVisitor.mEvent->mMessage == eFocus ||
+ aVisitor.mEvent->mMessage == eBlur) {
+ if (aVisitor.mEvent->mMessage == eBlur) {
+ if (mIsDraggingRange) {
+ FinishRangeThumbDrag();
+ } else if (mNumberControlSpinnerIsSpinning) {
+ StopNumberControlSpinnerSpin();
+ }
+ }
+
+ UpdateValidityUIBits(aVisitor.mEvent->mMessage == eFocus);
+
+ UpdateState(true);
+ }
+
+ nsresult rv = NS_OK;
+ bool outerActivateEvent = !!(aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT);
+ bool originalCheckedValue =
+ !!(aVisitor.mItemFlags & NS_ORIGINAL_CHECKED_VALUE);
+ bool noContentDispatch = !!(aVisitor.mItemFlags & NS_NO_CONTENT_DISPATCH);
+ uint8_t oldType = NS_CONTROL_TYPE(aVisitor.mItemFlags);
+
+ // Ideally we would make the default action for click and space just dispatch
+ // DOMActivate, and the default action for DOMActivate flip the checkbox/
+ // radio state and fire onchange. However, for backwards compatibility, we
+ // need to flip the state before firing click, and we need to fire click
+ // when space is pressed. So, we just nest the firing of DOMActivate inside
+ // the click event handling, and allow cancellation of DOMActivate to cancel
+ // the click.
+ if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault &&
+ !IsSingleLineTextControl(true) &&
+ mType != NS_FORM_INPUT_NUMBER) {
+ WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
+ if (mouseEvent && mouseEvent->IsLeftClickEvent() &&
+ !ShouldPreventDOMActivateDispatch(aVisitor.mEvent->mOriginalTarget)) {
+ // DOMActive event should be trusted since the activation is actually
+ // occurred even if the cause is an untrusted click event.
+ InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
+ actEvent.mDetail = 1;
+
+ nsCOMPtr<nsIPresShell> shell = aVisitor.mPresContext->GetPresShell();
+ if (shell) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ mInInternalActivate = true;
+ rv = shell->HandleDOMEventWithTarget(this, &actEvent, &status);
+ mInInternalActivate = false;
+
+ // If activate is cancelled, we must do the same as when click is
+ // cancelled (revert the checkbox to its original value).
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ aVisitor.mEventStatus = status;
+ }
+ }
+ }
+ }
+
+ if (outerActivateEvent) {
+ switch(oldType) {
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_IMAGE:
+ if (mForm) {
+ // tell the form that we are about to exit a click handler
+ // so the form knows not to defer subsequent submissions
+ // the pending ones that were created during the handler
+ // will be flushed or forgoten.
+ mForm->OnSubmitClickEnd();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Reset the flag for other content besides this text field
+ aVisitor.mEvent->mFlags.mNoContentDispatch = noContentDispatch;
+
+ // now check to see if the event was "cancelled"
+ if (mCheckedIsToggled && outerActivateEvent) {
+ if (aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault) {
+ // if it was cancelled and a radio button, then set the old
+ // selected btn to TRUE. if it is a checkbox then set it to its
+ // original value
+ if (oldType == NS_FORM_INPUT_RADIO) {
+ nsCOMPtr<nsIDOMHTMLInputElement> selectedRadioButton =
+ do_QueryInterface(aVisitor.mItemData);
+ if (selectedRadioButton) {
+ selectedRadioButton->SetChecked(true);
+ }
+ // If there was no checked radio button or this one is no longer a
+ // radio button we must reset it back to false to cancel the action.
+ // See how the web of hack grows?
+ if (!selectedRadioButton || mType != NS_FORM_INPUT_RADIO) {
+ DoSetChecked(false, true, true);
+ }
+ } else if (oldType == NS_FORM_INPUT_CHECKBOX) {
+ bool originalIndeterminateValue =
+ !!(aVisitor.mItemFlags & NS_ORIGINAL_INDETERMINATE_VALUE);
+ SetIndeterminateInternal(originalIndeterminateValue, false);
+ DoSetChecked(originalCheckedValue, true, true);
+ }
+ } else {
+ // Fire input event and then change event.
+ nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(this),
+ NS_LITERAL_STRING("input"), true,
+ false);
+
+ nsContentUtils::DispatchTrustedEvent(OwnerDoc(),
+ static_cast<nsIDOMHTMLInputElement*>(this),
+ NS_LITERAL_STRING("change"), true,
+ false);
+#ifdef ACCESSIBILITY
+ // Fire an event to notify accessibility
+ if (mType == NS_FORM_INPUT_CHECKBOX) {
+ FireEventForAccessibility(this, aVisitor.mPresContext,
+ NS_LITERAL_STRING("CheckboxStateChange"));
+ } else {
+ FireEventForAccessibility(this, aVisitor.mPresContext,
+ NS_LITERAL_STRING("RadioStateChange"));
+ // Fire event for the previous selected radio.
+ nsCOMPtr<nsIDOMHTMLInputElement> previous =
+ do_QueryInterface(aVisitor.mItemData);
+ if (previous) {
+ FireEventForAccessibility(previous, aVisitor.mPresContext,
+ NS_LITERAL_STRING("RadioStateChange"));
+ }
+ }
+#endif
+ }
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
+ if (mType == NS_FORM_INPUT_NUMBER &&
+ keyEvent && keyEvent->mMessage == eKeyPress &&
+ aVisitor.mEvent->IsTrusted() &&
+ (keyEvent->mKeyCode == NS_VK_UP || keyEvent->mKeyCode == NS_VK_DOWN) &&
+ !IgnoreInputEventWithModifier(keyEvent)) {
+ // We handle the up/down arrow keys specially for <input type=number>.
+ // On some platforms the editor for the nested text control will
+ // process these keys to send the cursor to the start/end of the text
+ // control and as a result aVisitor.mEventStatus will already have been
+ // set to nsEventStatus_eConsumeNoDefault. However, we know that
+ // whenever the up/down arrow keys cause the value of the number
+ // control to change the string in the text control will change, and
+ // the cursor will be moved to the end of the text control, overwriting
+ // the editor's handling of up/down keypress events. For that reason we
+ // just ignore aVisitor.mEventStatus here and go ahead and handle the
+ // event to increase/decrease the value of the number control.
+ if (!aVisitor.mEvent->DefaultPreventedByContent() && IsMutable()) {
+ StepNumberControlForUserEvent(keyEvent->mKeyCode == NS_VK_UP ? 1 : -1);
+ FireChangeEventIfNeeded();
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ } else if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
+ switch (aVisitor.mEvent->mMessage) {
+ case eFocus: {
+ // see if we should select the contents of the textbox. This happens
+ // for text and password fields when the field was focused by the
+ // keyboard or a navigation, the platform allows it, and it wasn't
+ // just because we raised a window.
+ nsIFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm && IsSingleLineTextControl(false) &&
+ !aVisitor.mEvent->AsFocusEvent()->mFromRaise &&
+ SelectTextFieldOnFocus()) {
+ nsIDocument* document = GetComposedDoc();
+ if (document) {
+ uint32_t lastFocusMethod;
+ fm->GetLastFocusMethod(document->GetWindow(), &lastFocusMethod);
+ if (lastFocusMethod &
+ (nsIFocusManager::FLAG_BYKEY | nsIFocusManager::FLAG_BYMOVEFOCUS)) {
+ RefPtr<nsPresContext> presContext =
+ GetPresContext(eForComposedDoc);
+ if (DispatchSelectEvent(presContext)) {
+ SelectAll(presContext);
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ case eKeyPress:
+ case eKeyUp:
+ {
+ // For backwards compat, trigger checks/radios/buttons with
+ // space or enter (bug 25300)
+ WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
+ if ((aVisitor.mEvent->mMessage == eKeyPress &&
+ keyEvent->mKeyCode == NS_VK_RETURN) ||
+ (aVisitor.mEvent->mMessage == eKeyUp &&
+ keyEvent->mKeyCode == NS_VK_SPACE)) {
+ switch(mType) {
+ case NS_FORM_INPUT_CHECKBOX:
+ case NS_FORM_INPUT_RADIO:
+ {
+ // Checkbox and Radio try to submit on Enter press
+ if (keyEvent->mKeyCode != NS_VK_SPACE) {
+ MaybeSubmitForm(aVisitor.mPresContext);
+
+ break; // If we are submitting, do not send click event
+ }
+ // else fall through and treat Space like click...
+ MOZ_FALLTHROUGH;
+ }
+ case NS_FORM_INPUT_BUTTON:
+ case NS_FORM_INPUT_RESET:
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_IMAGE: // Bug 34418
+ case NS_FORM_INPUT_COLOR:
+ {
+ DispatchSimulatedClick(this, aVisitor.mEvent->IsTrusted(),
+ aVisitor.mPresContext);
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ } // case
+ } // switch
+ }
+ if (aVisitor.mEvent->mMessage == eKeyPress &&
+ mType == NS_FORM_INPUT_RADIO && !keyEvent->IsAlt() &&
+ !keyEvent->IsControl() && !keyEvent->IsMeta()) {
+ bool isMovingBack = false;
+ switch (keyEvent->mKeyCode) {
+ case NS_VK_UP:
+ case NS_VK_LEFT:
+ isMovingBack = true;
+ MOZ_FALLTHROUGH;
+ case NS_VK_DOWN:
+ case NS_VK_RIGHT:
+ // Arrow key pressed, focus+select prev/next radio button
+ nsIRadioGroupContainer* container = GetRadioGroupContainer();
+ if (container) {
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+ RefPtr<HTMLInputElement> selectedRadioButton;
+ container->GetNextRadioButton(name, isMovingBack, this,
+ getter_AddRefs(selectedRadioButton));
+ if (selectedRadioButton) {
+ rv = selectedRadioButton->Focus();
+ if (NS_SUCCEEDED(rv)) {
+ rv = DispatchSimulatedClick(selectedRadioButton,
+ aVisitor.mEvent->IsTrusted(),
+ aVisitor.mPresContext);
+ if (NS_SUCCEEDED(rv)) {
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * For some input types, if the user hits enter, the form is submitted.
+ *
+ * Bug 99920, bug 109463 and bug 147850:
+ * (a) if there is a submit control in the form, click the first
+ * submit control in the form.
+ * (b) if there is just one text control in the form, submit by
+ * sending a submit event directly to the form
+ * (c) if there is more than one text input and no submit buttons, do
+ * not submit, period.
+ */
+
+ if (aVisitor.mEvent->mMessage == eKeyPress &&
+ keyEvent->mKeyCode == NS_VK_RETURN &&
+ (IsSingleLineTextControl(false, mType) ||
+ mType == NS_FORM_INPUT_NUMBER ||
+ IsExperimentalMobileType(mType) ||
+ IsDateTimeInputType(mType))) {
+ FireChangeEventIfNeeded();
+ rv = MaybeSubmitForm(aVisitor.mPresContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (aVisitor.mEvent->mMessage == eKeyPress &&
+ mType == NS_FORM_INPUT_RANGE && !keyEvent->IsAlt() &&
+ !keyEvent->IsControl() && !keyEvent->IsMeta() &&
+ (keyEvent->mKeyCode == NS_VK_LEFT ||
+ keyEvent->mKeyCode == NS_VK_RIGHT ||
+ keyEvent->mKeyCode == NS_VK_UP ||
+ keyEvent->mKeyCode == NS_VK_DOWN ||
+ keyEvent->mKeyCode == NS_VK_PAGE_UP ||
+ keyEvent->mKeyCode == NS_VK_PAGE_DOWN ||
+ keyEvent->mKeyCode == NS_VK_HOME ||
+ keyEvent->mKeyCode == NS_VK_END)) {
+ Decimal minimum = GetMinimum();
+ Decimal maximum = GetMaximum();
+ MOZ_ASSERT(minimum.isFinite() && maximum.isFinite());
+ if (minimum < maximum) { // else the value is locked to the minimum
+ Decimal value = GetValueAsDecimal();
+ Decimal step = GetStep();
+ if (step == kStepAny) {
+ step = GetDefaultStep();
+ }
+ MOZ_ASSERT(value.isFinite() && step.isFinite());
+ Decimal newValue;
+ switch (keyEvent->mKeyCode) {
+ case NS_VK_LEFT:
+ newValue = value + (GetComputedDirectionality() == eDir_RTL
+ ? step : -step);
+ break;
+ case NS_VK_RIGHT:
+ newValue = value + (GetComputedDirectionality() == eDir_RTL
+ ? -step : step);
+ break;
+ case NS_VK_UP:
+ // Even for horizontal range, "up" means "increase"
+ newValue = value + step;
+ break;
+ case NS_VK_DOWN:
+ // Even for horizontal range, "down" means "decrease"
+ newValue = value - step;
+ break;
+ case NS_VK_HOME:
+ newValue = minimum;
+ break;
+ case NS_VK_END:
+ newValue = maximum;
+ break;
+ case NS_VK_PAGE_UP:
+ // For PgUp/PgDn we jump 10% of the total range, unless step
+ // requires us to jump more.
+ newValue = value + std::max(step, (maximum - minimum) / Decimal(10));
+ break;
+ case NS_VK_PAGE_DOWN:
+ newValue = value - std::max(step, (maximum - minimum) / Decimal(10));
+ break;
+ }
+ SetValueOfRangeForUserEvent(newValue);
+ FireChangeEventIfNeeded();
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+
+ } break; // eKeyPress || eKeyUp
+
+ case eMouseDown:
+ case eMouseUp:
+ case eMouseDoubleClick: {
+ // cancel all of these events for buttons
+ //XXXsmaug Why?
+ WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
+ if (mouseEvent->button == WidgetMouseEvent::eMiddleButton ||
+ mouseEvent->button == WidgetMouseEvent::eRightButton) {
+ if (mType == NS_FORM_INPUT_BUTTON ||
+ mType == NS_FORM_INPUT_RESET ||
+ mType == NS_FORM_INPUT_SUBMIT) {
+ if (aVisitor.mDOMEvent) {
+ aVisitor.mDOMEvent->StopPropagation();
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ }
+ if (mType == NS_FORM_INPUT_NUMBER && aVisitor.mEvent->IsTrusted()) {
+ if (mouseEvent->button == WidgetMouseEvent::eLeftButton &&
+ !IgnoreInputEventWithModifier(mouseEvent)) {
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame) {
+ if (aVisitor.mEvent->mMessage == eMouseDown &&
+ IsMutable()) {
+ switch (numberControlFrame->GetSpinButtonForPointerEvent(
+ aVisitor.mEvent->AsMouseEvent())) {
+ case nsNumberControlFrame::eSpinButtonUp:
+ StepNumberControlForUserEvent(1);
+ mNumberControlSpinnerSpinsUp = true;
+ StartNumberControlSpinnerSpin();
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ break;
+ case nsNumberControlFrame::eSpinButtonDown:
+ StepNumberControlForUserEvent(-1);
+ mNumberControlSpinnerSpinsUp = false;
+ StartNumberControlSpinnerSpin();
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ break;
+ }
+ }
+ }
+ }
+ if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
+ // We didn't handle this to step up/down. Whatever this was, be
+ // aggressive about stopping the spin. (And don't set
+ // nsEventStatus_eConsumeNoDefault after doing so, since that
+ // might prevent, say, the context menu from opening.)
+ StopNumberControlSpinnerSpin();
+ }
+ }
+ break;
+ }
+#if !defined(ANDROID) && !defined(XP_MACOSX)
+ case eWheel: {
+ // Handle wheel events as increasing / decreasing the input element's
+ // value when it's focused and it's type is number or range.
+ WidgetWheelEvent* wheelEvent = aVisitor.mEvent->AsWheelEvent();
+ if (!aVisitor.mEvent->DefaultPrevented() &&
+ aVisitor.mEvent->IsTrusted() && IsMutable() && wheelEvent &&
+ wheelEvent->mDeltaY != 0 &&
+ wheelEvent->mDeltaMode != nsIDOMWheelEvent::DOM_DELTA_PIXEL) {
+ if (mType == NS_FORM_INPUT_NUMBER) {
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame && numberControlFrame->IsFocused()) {
+ StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1 : 1);
+ FireChangeEventIfNeeded();
+ aVisitor.mEvent->PreventDefault();
+ }
+ } else if (mType == NS_FORM_INPUT_RANGE &&
+ nsContentUtils::IsFocusedContent(this) &&
+ GetMinimum() < GetMaximum()) {
+ Decimal value = GetValueAsDecimal();
+ Decimal step = GetStep();
+ if (step == kStepAny) {
+ step = GetDefaultStep();
+ }
+ MOZ_ASSERT(value.isFinite() && step.isFinite());
+ SetValueOfRangeForUserEvent(wheelEvent->mDeltaY < 0 ?
+ value + step : value - step);
+ FireChangeEventIfNeeded();
+ aVisitor.mEvent->PreventDefault();
+ }
+ }
+ break;
+ }
+#endif
+ default:
+ break;
+ }
+
+ if (outerActivateEvent) {
+ if (mForm && (oldType == NS_FORM_INPUT_SUBMIT ||
+ oldType == NS_FORM_INPUT_IMAGE)) {
+ if (mType != NS_FORM_INPUT_SUBMIT && mType != NS_FORM_INPUT_IMAGE) {
+ // If the type has changed to a non-submit type, then we want to
+ // flush the stored submission if there is one (as if the submit()
+ // was allowed to succeed)
+ mForm->FlushPendingSubmission();
+ }
+ }
+ switch(mType) {
+ case NS_FORM_INPUT_RESET:
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_IMAGE:
+ if (mForm) {
+ InternalFormEvent event(true,
+ (mType == NS_FORM_INPUT_RESET) ? eFormReset : eFormSubmit);
+ event.mOriginator = this;
+ nsEventStatus status = nsEventStatus_eIgnore;
+
+ nsCOMPtr<nsIPresShell> presShell =
+ aVisitor.mPresContext->GetPresShell();
+
+ // If |nsIPresShell::Destroy| has been called due to
+ // handling the event the pres context will return a null
+ // pres shell. See bug 125624.
+ // TODO: removing this code and have the submit event sent by the
+ // form, see bug 592124.
+ if (presShell && (event.mMessage != eFormSubmit ||
+ mForm->SubmissionCanProceed(this))) {
+ // Hold a strong ref while dispatching
+ RefPtr<mozilla::dom::HTMLFormElement> form(mForm);
+ presShell->HandleDOMEventWithTarget(form, &event, &status);
+ aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
+ }
+ }
+ break;
+
+ default:
+ break;
+ } //switch
+ } //click or outer activate event
+ } else if (outerActivateEvent &&
+ (oldType == NS_FORM_INPUT_SUBMIT ||
+ oldType == NS_FORM_INPUT_IMAGE) &&
+ mForm) {
+ // tell the form to flush a possible pending submission.
+ // the reason is that the script returned false (the event was
+ // not ignored) so if there is a stored submission, it needs to
+ // be submitted immediately.
+ mForm->FlushPendingSubmission();
+ }
+ } // if
+
+ if (NS_SUCCEEDED(rv) && mType == NS_FORM_INPUT_RANGE) {
+ PostHandleEventForRangeThumb(aVisitor);
+ }
+
+ return MaybeInitPickers(aVisitor);
+}
+
+void
+HTMLInputElement::PostHandleEventForRangeThumb(EventChainPostVisitor& aVisitor)
+{
+ MOZ_ASSERT(mType == NS_FORM_INPUT_RANGE);
+
+ if (nsEventStatus_eConsumeNoDefault == aVisitor.mEventStatus ||
+ !(aVisitor.mEvent->mClass == eMouseEventClass ||
+ aVisitor.mEvent->mClass == eTouchEventClass ||
+ aVisitor.mEvent->mClass == eKeyboardEventClass)) {
+ return;
+ }
+
+ nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
+ if (!rangeFrame && mIsDraggingRange) {
+ CancelRangeThumbDrag();
+ return;
+ }
+
+ switch (aVisitor.mEvent->mMessage)
+ {
+ case eMouseDown:
+ case eTouchStart: {
+ if (mIsDraggingRange) {
+ break;
+ }
+ if (nsIPresShell::GetCapturingContent()) {
+ break; // don't start drag if someone else is already capturing
+ }
+ WidgetInputEvent* inputEvent = aVisitor.mEvent->AsInputEvent();
+ if (IgnoreInputEventWithModifier(inputEvent)) {
+ break; // ignore
+ }
+ if (aVisitor.mEvent->mMessage == eMouseDown) {
+ if (aVisitor.mEvent->AsMouseEvent()->buttons ==
+ WidgetMouseEvent::eLeftButtonFlag) {
+ StartRangeThumbDrag(inputEvent);
+ } else if (mIsDraggingRange) {
+ CancelRangeThumbDrag();
+ }
+ } else {
+ if (aVisitor.mEvent->AsTouchEvent()->mTouches.Length() == 1) {
+ StartRangeThumbDrag(inputEvent);
+ } else if (mIsDraggingRange) {
+ CancelRangeThumbDrag();
+ }
+ }
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
+ } break;
+
+ case eMouseMove:
+ case eTouchMove:
+ if (!mIsDraggingRange) {
+ break;
+ }
+ if (nsIPresShell::GetCapturingContent() != this) {
+ // Someone else grabbed capture.
+ CancelRangeThumbDrag();
+ break;
+ }
+ SetValueOfRangeForUserEvent(
+ rangeFrame->GetValueAtEventPoint(aVisitor.mEvent->AsInputEvent()));
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
+ break;
+
+ case eMouseUp:
+ case eTouchEnd:
+ if (!mIsDraggingRange) {
+ break;
+ }
+ // We don't check to see whether we are the capturing content here and
+ // call CancelRangeThumbDrag() if that is the case. We just finish off
+ // the drag and set our final value (unless someone has called
+ // preventDefault() and prevents us getting here).
+ FinishRangeThumbDrag(aVisitor.mEvent->AsInputEvent());
+ aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
+ break;
+
+ case eKeyPress:
+ if (mIsDraggingRange &&
+ aVisitor.mEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) {
+ CancelRangeThumbDrag();
+ }
+ break;
+
+ case eTouchCancel:
+ if (mIsDraggingRange) {
+ CancelRangeThumbDrag();
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+void
+HTMLInputElement::MaybeLoadImage()
+{
+ // Our base URI may have changed; claim that our URI changed, and the
+ // nsImageLoadingContent will decide whether a new image load is warranted.
+ nsAutoString uri;
+ if (mType == NS_FORM_INPUT_IMAGE &&
+ GetAttr(kNameSpaceID_None, nsGkAtoms::src, uri) &&
+ (NS_FAILED(LoadImage(uri, false, true, eImageLoadType_Normal)) ||
+ !LoadingEnabled())) {
+ CancelImageRequests(true);
+ }
+}
+
+nsresult
+HTMLInputElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+ nsIContent* aBindingParent,
+ bool aCompileEventHandlers)
+{
+ nsresult rv = nsGenericHTMLFormElementWithState::BindToTree(aDocument, aParent,
+ aBindingParent,
+ aCompileEventHandlers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsImageLoadingContent::BindToTree(aDocument, aParent, aBindingParent,
+ aCompileEventHandlers);
+
+ if (mType == NS_FORM_INPUT_IMAGE) {
+ // Our base URI may have changed; claim that our URI changed, and the
+ // nsImageLoadingContent will decide whether a new image load is warranted.
+ if (HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
+ // FIXME: Bug 660963 it would be nice if we could just have
+ // ClearBrokenState update our state and do it fast...
+ ClearBrokenState();
+ RemoveStatesSilently(NS_EVENT_STATE_BROKEN);
+ nsContentUtils::AddScriptRunner(
+ NewRunnableMethod(this, &HTMLInputElement::MaybeLoadImage));
+ }
+ }
+
+ // Add radio to document if we don't have a form already (if we do it's
+ // already been added into that group)
+ if (aDocument && !mForm && mType == NS_FORM_INPUT_RADIO) {
+ AddedToRadioGroup();
+ }
+
+ // Set direction based on value if dir=auto
+ SetDirectionIfAuto(HasDirAuto(), false);
+
+ // An element can't suffer from value missing if it is not in a document.
+ // We have to check if we suffer from that as we are now in a document.
+ UpdateValueMissingValidityState();
+
+ // If there is a disabled fieldset in the parent chain, the element is now
+ // barred from constraint validation and can't suffer from value missing
+ // (call done before).
+ UpdateBarredFromConstraintValidation();
+
+ // And now make sure our state is up to date
+ UpdateState(false);
+
+ if (mType == NS_FORM_INPUT_PASSWORD) {
+ if (IsInComposedDoc()) {
+ AsyncEventDispatcher* dispatcher =
+ new AsyncEventDispatcher(this,
+ NS_LITERAL_STRING("DOMInputPasswordAdded"),
+ true,
+ true);
+ dispatcher->PostDOMEvent();
+ }
+
+#ifdef EARLY_BETA_OR_EARLIER
+ Telemetry::Accumulate(Telemetry::PWMGR_PASSWORD_INPUT_IN_FORM, !!mForm);
+#endif
+ }
+
+ return rv;
+}
+
+void
+HTMLInputElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+ // If we have a form and are unbound from it,
+ // nsGenericHTMLFormElementWithState::UnbindFromTree() will unset the form and
+ // that takes care of form's WillRemove so we just have to take care
+ // of the case where we're removing from the document and we don't
+ // have a form
+ if (!mForm && mType == NS_FORM_INPUT_RADIO) {
+ WillRemoveFromRadioGroup();
+ }
+
+ nsImageLoadingContent::UnbindFromTree(aDeep, aNullParent);
+ nsGenericHTMLFormElementWithState::UnbindFromTree(aDeep, aNullParent);
+
+ // GetCurrentDoc is returning nullptr so we can update the value
+ // missing validity state to reflect we are no longer into a doc.
+ UpdateValueMissingValidityState();
+ // We might be no longer disabled because of parent chain changed.
+ UpdateBarredFromConstraintValidation();
+
+ // And now make sure our state is up to date
+ UpdateState(false);
+}
+
+void
+HTMLInputElement::HandleTypeChange(uint8_t aNewType)
+{
+ if (mType == NS_FORM_INPUT_RANGE && mIsDraggingRange) {
+ CancelRangeThumbDrag(false);
+ }
+
+ ValueModeType aOldValueMode = GetValueMode();
+ uint8_t oldType = mType;
+ nsAutoString aOldValue;
+
+ if (aOldValueMode == VALUE_MODE_VALUE) {
+ GetValue(aOldValue);
+ }
+
+ nsTextEditorState::SelectionProperties sp;
+
+ if (GetEditorState()) {
+ sp = mInputData.mState->GetSelectionProperties();
+ }
+
+ // We already have a copy of the value, lets free it and changes the type.
+ FreeData();
+ mType = aNewType;
+
+ if (IsSingleLineTextControl()) {
+
+ mInputData.mState = new nsTextEditorState(this);
+ if (!sp.IsDefault()) {
+ mInputData.mState->SetSelectionProperties(sp);
+ }
+ }
+
+ /**
+ * The following code is trying to reproduce the algorithm described here:
+ * http://www.whatwg.org/specs/web-apps/current-work/complete.html#input-type-change
+ */
+ switch (GetValueMode()) {
+ case VALUE_MODE_DEFAULT:
+ case VALUE_MODE_DEFAULT_ON:
+ // If the previous value mode was value, we need to set the value content
+ // attribute to the previous value.
+ // There is no value sanitizing algorithm for elements in this mode.
+ if (aOldValueMode == VALUE_MODE_VALUE && !aOldValue.IsEmpty()) {
+ SetAttr(kNameSpaceID_None, nsGkAtoms::value, aOldValue, true);
+ }
+ break;
+ case VALUE_MODE_VALUE:
+ // If the previous value mode wasn't value, we have to set the value to
+ // the value content attribute.
+ // SetValueInternal is going to sanitize the value.
+ {
+ nsAutoString value;
+ if (aOldValueMode != VALUE_MODE_VALUE) {
+ GetAttr(kNameSpaceID_None, nsGkAtoms::value, value);
+ } else {
+ value = aOldValue;
+ }
+ // TODO: What should we do if SetValueInternal fails? (The allocation
+ // may potentially be big, but most likely we've failed to allocate
+ // before the type change.)
+ SetValueInternal(value, nsTextEditorState::eSetValue_Internal);
+ }
+ break;
+ case VALUE_MODE_FILENAME:
+ default:
+ // We don't care about the value.
+ // There is no value sanitizing algorithm for elements in this mode.
+ break;
+ }
+
+ // Updating mFocusedValue in consequence:
+ // If the new type fires a change event on blur, but the previous type
+ // doesn't, we should set mFocusedValue to the current value.
+ // Otherwise, if the new type doesn't fire a change event on blur, but the
+ // previous type does, we should clear out mFocusedValue.
+ if (MayFireChangeOnBlur(mType) && !MayFireChangeOnBlur(oldType)) {
+ GetValue(mFocusedValue);
+ } else if (!IsSingleLineTextControl(false, mType) &&
+ IsSingleLineTextControl(false, oldType)) {
+ mFocusedValue.Truncate();
+ }
+
+ UpdateHasRange();
+
+ // Do not notify, it will be done after if needed.
+ UpdateAllValidityStates(false);
+
+ UpdateApzAwareFlag();
+}
+
+void
+HTMLInputElement::SanitizeValue(nsAString& aValue)
+{
+ NS_ASSERTION(mDoneCreating, "The element creation should be finished!");
+
+ switch (mType) {
+ case NS_FORM_INPUT_TEXT:
+ case NS_FORM_INPUT_SEARCH:
+ case NS_FORM_INPUT_TEL:
+ case NS_FORM_INPUT_PASSWORD:
+ {
+ char16_t crlf[] = { char16_t('\r'), char16_t('\n'), 0 };
+ aValue.StripChars(crlf);
+ }
+ break;
+ case NS_FORM_INPUT_EMAIL:
+ case NS_FORM_INPUT_URL:
+ {
+ char16_t crlf[] = { char16_t('\r'), char16_t('\n'), 0 };
+ aValue.StripChars(crlf);
+
+ aValue = nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(aValue);
+ }
+ break;
+ case NS_FORM_INPUT_NUMBER:
+ {
+ Decimal value;
+ bool ok = ConvertStringToNumber(aValue, value);
+ if (!ok) {
+ aValue.Truncate();
+ }
+ }
+ break;
+ case NS_FORM_INPUT_RANGE:
+ {
+ Decimal minimum = GetMinimum();
+ Decimal maximum = GetMaximum();
+ MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(),
+ "type=range should have a default maximum/minimum");
+
+ // We use this to avoid modifying the string unnecessarily, since that
+ // may introduce rounding. This is set to true only if the value we
+ // parse out from aValue needs to be sanitized.
+ bool needSanitization = false;
+
+ Decimal value;
+ bool ok = ConvertStringToNumber(aValue, value);
+ if (!ok) {
+ needSanitization = true;
+ // Set value to midway between minimum and maximum.
+ value = maximum <= minimum ? minimum : minimum + (maximum - minimum)/Decimal(2);
+ } else if (value < minimum || maximum < minimum) {
+ needSanitization = true;
+ value = minimum;
+ } else if (value > maximum) {
+ needSanitization = true;
+ value = maximum;
+ }
+
+ Decimal step = GetStep();
+ if (step != kStepAny) {
+ Decimal stepBase = GetStepBase();
+ // There could be rounding issues below when dealing with fractional
+ // numbers, but let's ignore that until ECMAScript supplies us with a
+ // decimal number type.
+ Decimal deltaToStep = NS_floorModulo(value - stepBase, step);
+ if (deltaToStep != Decimal(0)) {
+ // "suffering from a step mismatch"
+ // Round the element's value to the nearest number for which the
+ // element would not suffer from a step mismatch, and which is
+ // greater than or equal to the minimum, and, if the maximum is not
+ // less than the minimum, which is less than or equal to the
+ // maximum, if there is a number that matches these constraints:
+ MOZ_ASSERT(deltaToStep > Decimal(0), "stepBelow/stepAbove will be wrong");
+ Decimal stepBelow = value - deltaToStep;
+ Decimal stepAbove = value - deltaToStep + step;
+ Decimal halfStep = step / Decimal(2);
+ bool stepAboveIsClosest = (stepAbove - value) <= halfStep;
+ bool stepAboveInRange = stepAbove >= minimum &&
+ stepAbove <= maximum;
+ bool stepBelowInRange = stepBelow >= minimum &&
+ stepBelow <= maximum;
+
+ if ((stepAboveIsClosest || !stepBelowInRange) && stepAboveInRange) {
+ needSanitization = true;
+ value = stepAbove;
+ } else if ((!stepAboveIsClosest || !stepAboveInRange) && stepBelowInRange) {
+ needSanitization = true;
+ value = stepBelow;
+ }
+ }
+ }
+
+ if (needSanitization) {
+ char buf[32];
+ DebugOnly<bool> ok = value.toString(buf, ArrayLength(buf));
+ aValue.AssignASCII(buf);
+ MOZ_ASSERT(ok, "buf not big enough");
+ }
+ }
+ break;
+ case NS_FORM_INPUT_DATE:
+ {
+ if (!aValue.IsEmpty() && !IsValidDate(aValue)) {
+ aValue.Truncate();
+ }
+ }
+ break;
+ case NS_FORM_INPUT_TIME:
+ {
+ if (!aValue.IsEmpty() && !IsValidTime(aValue)) {
+ aValue.Truncate();
+ }
+ }
+ break;
+ case NS_FORM_INPUT_MONTH:
+ {
+ if (!aValue.IsEmpty() && !IsValidMonth(aValue)) {
+ aValue.Truncate();
+ }
+ }
+ break;
+ case NS_FORM_INPUT_WEEK:
+ {
+ if (!aValue.IsEmpty() && !IsValidWeek(aValue)) {
+ aValue.Truncate();
+ }
+ }
+ break;
+ case NS_FORM_INPUT_DATETIME_LOCAL:
+ {
+ if (!aValue.IsEmpty() && !IsValidDateTimeLocal(aValue)) {
+ aValue.Truncate();
+ } else {
+ NormalizeDateTimeLocal(aValue);
+ }
+ }
+ break;
+ case NS_FORM_INPUT_COLOR:
+ {
+ if (IsValidSimpleColor(aValue)) {
+ ToLowerCase(aValue);
+ } else {
+ // Set default (black) color, if aValue wasn't parsed correctly.
+ aValue.AssignLiteral("#000000");
+ }
+ }
+ break;
+ }
+}
+
+bool HTMLInputElement::IsValidSimpleColor(const nsAString& aValue) const
+{
+ if (aValue.Length() != 7 || aValue.First() != '#') {
+ return false;
+ }
+
+ for (int i = 1; i < 7; ++i) {
+ if (!nsCRT::IsAsciiDigit(aValue[i]) &&
+ !(aValue[i] >= 'a' && aValue[i] <= 'f') &&
+ !(aValue[i] >= 'A' && aValue[i] <= 'F')) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+HTMLInputElement::IsLeapYear(uint32_t aYear) const
+{
+ if ((aYear % 4 == 0 && aYear % 100 != 0) || ( aYear % 400 == 0)) {
+ return true;
+ }
+ return false;
+}
+
+uint32_t
+HTMLInputElement::DayOfWeek(uint32_t aYear, uint32_t aMonth, uint32_t aDay,
+ bool isoWeek) const
+{
+ // Tomohiko Sakamoto algorithm.
+ int monthTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
+ aYear -= aMonth < 3;
+
+ uint32_t day = (aYear + aYear / 4 - aYear / 100 + aYear / 400 +
+ monthTable[aMonth - 1] + aDay) % 7;
+
+ if (isoWeek) {
+ return ((day + 6) % 7) + 1;
+ }
+
+ return day;
+}
+
+uint32_t
+HTMLInputElement::MaximumWeekInYear(uint32_t aYear) const
+{
+ int day = DayOfWeek(aYear, 1, 1, true); // January 1.
+ // A year starting on Thursday or a leap year starting on Wednesday has 53
+ // weeks. All other years have 52 weeks.
+ return day == 4 || (day == 3 && IsLeapYear(aYear)) ?
+ kMaximumWeekInYear : kMaximumWeekInYear - 1;
+}
+
+bool
+HTMLInputElement::IsValidWeek(const nsAString& aValue) const
+{
+ uint32_t year, week;
+ return ParseWeek(aValue, &year, &week);
+}
+
+bool
+HTMLInputElement::IsValidMonth(const nsAString& aValue) const
+{
+ uint32_t year, month;
+ return ParseMonth(aValue, &year, &month);
+}
+
+bool
+HTMLInputElement::IsValidDate(const nsAString& aValue) const
+{
+ uint32_t year, month, day;
+ return ParseDate(aValue, &year, &month, &day);
+}
+
+bool
+HTMLInputElement::IsValidDateTimeLocal(const nsAString& aValue) const
+{
+ uint32_t year, month, day, time;
+ return ParseDateTimeLocal(aValue, &year, &month, &day, &time);
+}
+
+bool
+HTMLInputElement::ParseYear(const nsAString& aValue, uint32_t* aYear) const
+{
+ if (aValue.Length() < 4) {
+ return false;
+ }
+
+ return DigitSubStringToNumber(aValue, 0, aValue.Length(), aYear) &&
+ *aYear > 0;
+}
+
+bool
+HTMLInputElement::ParseMonth(const nsAString& aValue, uint32_t* aYear,
+ uint32_t* aMonth) const
+{
+ // Parse the year, month values out a string formatted as 'yyyy-mm'.
+ if (aValue.Length() < 7) {
+ return false;
+ }
+
+ uint32_t endOfYearOffset = aValue.Length() - 3;
+ if (aValue[endOfYearOffset] != '-') {
+ return false;
+ }
+
+ const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
+ if (!ParseYear(yearStr, aYear)) {
+ return false;
+ }
+
+ return DigitSubStringToNumber(aValue, endOfYearOffset + 1, 2, aMonth) &&
+ *aMonth > 0 && *aMonth <= 12;
+}
+
+bool
+HTMLInputElement::ParseWeek(const nsAString& aValue, uint32_t* aYear,
+ uint32_t* aWeek) const
+{
+ // Parse the year, month values out a string formatted as 'yyyy-Www'.
+ if (aValue.Length() < 8) {
+ return false;
+ }
+
+ uint32_t endOfYearOffset = aValue.Length() - 4;
+ if (aValue[endOfYearOffset] != '-') {
+ return false;
+ }
+
+ if (aValue[endOfYearOffset + 1] != 'W') {
+ return false;
+ }
+
+ const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
+ if (!ParseYear(yearStr, aYear)) {
+ return false;
+ }
+
+ return DigitSubStringToNumber(aValue, endOfYearOffset + 2, 2, aWeek) &&
+ *aWeek > 0 && *aWeek <= MaximumWeekInYear(*aYear);
+
+}
+
+bool
+HTMLInputElement::ParseDate(const nsAString& aValue, uint32_t* aYear,
+ uint32_t* aMonth, uint32_t* aDay) const
+{
+/*
+ * Parse the year, month, day values out a date string formatted as 'yyyy-mm-dd'.
+ * -The year must be 4 or more digits long, and year > 0
+ * -The month must be exactly 2 digits long, and 01 <= month <= 12
+ * -The day must be exactly 2 digit long, and 01 <= day <= maxday
+ * Where maxday is the number of days in the month 'month' and year 'year'
+ */
+ if (aValue.Length() < 10) {
+ return false;
+ }
+
+ uint32_t endOfMonthOffset = aValue.Length() - 3;
+ if (aValue[endOfMonthOffset] != '-') {
+ return false;
+ }
+
+ const nsAString& yearMonthStr = Substring(aValue, 0, endOfMonthOffset);
+ if (!ParseMonth(yearMonthStr, aYear, aMonth)) {
+ return false;
+ }
+
+ return DigitSubStringToNumber(aValue, endOfMonthOffset + 1, 2, aDay) &&
+ *aDay > 0 && *aDay <= NumberOfDaysInMonth(*aMonth, *aYear);
+}
+
+bool
+HTMLInputElement::ParseDateTimeLocal(const nsAString& aValue, uint32_t* aYear,
+ uint32_t* aMonth, uint32_t* aDay,
+ uint32_t* aTime) const
+{
+ // Parse the year, month, day and time values out a string formatted as
+ // 'yyyy-mm-ddThh:mm[:ss.s] or 'yyyy-mm-dd hh:mm[:ss.s]', where fractions of
+ // seconds can be 1 to 3 digits.
+ // The minimum length allowed is 16, which is of the form 'yyyy-mm-ddThh:mm'
+ // or 'yyyy-mm-dd hh:mm'.
+ if (aValue.Length() < 16) {
+ return false;
+ }
+
+ const uint32_t sepIndex = 10;
+ if (aValue[sepIndex] != 'T' && aValue[sepIndex] != ' ') {
+ return false;
+ }
+
+ const nsAString& dateStr = Substring(aValue, 0, sepIndex);
+ if (!ParseDate(dateStr, aYear, aMonth, aDay)) {
+ return false;
+ }
+
+ const nsAString& timeStr = Substring(aValue, sepIndex + 1,
+ aValue.Length() - sepIndex + 1);
+ if (!ParseTime(timeStr, aTime)) {
+ return false;
+ }
+
+ return true;
+}
+
+void
+HTMLInputElement::NormalizeDateTimeLocal(nsAString& aValue) const
+{
+ if (aValue.IsEmpty()) {
+ return;
+ }
+
+ // Use 'T' as the separator between date string and time string.
+ const uint32_t sepIndex = 10;
+ if (aValue[sepIndex] == ' ') {
+ aValue.Replace(sepIndex, 1, NS_LITERAL_STRING("T"));
+ }
+
+ // Time expressed as the shortest possible string.
+ if (aValue.Length() == 16) {
+ return;
+ }
+
+ // Fractions of seconds part is optional, ommit it if it's 0.
+ if (aValue.Length() > 19) {
+ uint32_t milliseconds;
+ if (!DigitSubStringToNumber(aValue, 20, aValue.Length() - 20,
+ &milliseconds)) {
+ return;
+ }
+
+ if (milliseconds != 0) {
+ return;
+ }
+
+ aValue.Cut(19, aValue.Length() - 19);
+ }
+
+ // Seconds part is optional, ommit it if it's 0.
+ uint32_t seconds;
+ if (!DigitSubStringToNumber(aValue, 17, aValue.Length() - 17, &seconds)) {
+ return;
+ }
+
+ if (seconds != 0) {
+ return;
+ }
+
+ aValue.Cut(16, aValue.Length() - 16);
+}
+
+double
+HTMLInputElement::DaysSinceEpochFromWeek(uint32_t aYear, uint32_t aWeek) const
+{
+ double days = JS::DayFromYear(aYear) + (aWeek - 1) * 7;
+ uint32_t dayOneIsoWeekday = DayOfWeek(aYear, 1, 1, true);
+
+ // If day one of that year is on/before Thursday, we should subtract the
+ // days that belong to last year in our first week, otherwise, our first
+ // days belong to last year's last week, and we should add those days
+ // back.
+ if (dayOneIsoWeekday <= 4) {
+ days -= (dayOneIsoWeekday - 1);
+ } else {
+ days += (7 - dayOneIsoWeekday + 1);
+ }
+
+ return days;
+}
+
+uint32_t
+HTMLInputElement::NumberOfDaysInMonth(uint32_t aMonth, uint32_t aYear) const
+{
+/*
+ * Returns the number of days in a month.
+ * Months that are |longMonths| always have 31 days.
+ * Months that are not |longMonths| have 30 days except February (month 2).
+ * February has 29 days during leap years which are years that are divisible by 400.
+ * or divisible by 100 and 4. February has 28 days otherwise.
+ */
+
+ static const bool longMonths[] = { true, false, true, false, true, false,
+ true, true, false, true, false, true };
+ MOZ_ASSERT(aMonth <= 12 && aMonth > 0);
+
+ if (longMonths[aMonth-1]) {
+ return 31;
+ }
+
+ if (aMonth != 2) {
+ return 30;
+ }
+
+ return IsLeapYear(aYear) ? 29 : 28;
+}
+
+/* static */ bool
+HTMLInputElement::DigitSubStringToNumber(const nsAString& aStr,
+ uint32_t aStart, uint32_t aLen,
+ uint32_t* aRetVal)
+{
+ MOZ_ASSERT(aStr.Length() > (aStart + aLen - 1));
+
+ for (uint32_t offset = 0; offset < aLen; ++offset) {
+ if (!NS_IsAsciiDigit(aStr[aStart + offset])) {
+ return false;
+ }
+ }
+
+ nsresult ec;
+ *aRetVal = static_cast<uint32_t>(PromiseFlatString(Substring(aStr, aStart, aLen)).ToInteger(&ec));
+
+ return NS_SUCCEEDED(ec);
+}
+
+bool
+HTMLInputElement::IsValidTime(const nsAString& aValue) const
+{
+ return ParseTime(aValue, nullptr);
+}
+
+/* static */ bool
+HTMLInputElement::ParseTime(const nsAString& aValue, uint32_t* aResult)
+{
+ /* The string must have the following parts:
+ * - HOURS: two digits, value being in [0, 23];
+ * - Colon (:);
+ * - MINUTES: two digits, value being in [0, 59];
+ * - Optional:
+ * - Colon (:);
+ * - SECONDS: two digits, value being in [0, 59];
+ * - Optional:
+ * - DOT (.);
+ * - FRACTIONAL SECONDS: one to three digits, no value range.
+ */
+
+ // The following format is the shorter one allowed: "HH:MM".
+ if (aValue.Length() < 5) {
+ return false;
+ }
+
+ uint32_t hours;
+ if (!DigitSubStringToNumber(aValue, 0, 2, &hours) || hours > 23) {
+ return false;
+ }
+
+ // Hours/minutes separator.
+ if (aValue[2] != ':') {
+ return false;
+ }
+
+ uint32_t minutes;
+ if (!DigitSubStringToNumber(aValue, 3, 2, &minutes) || minutes > 59) {
+ return false;
+ }
+
+ if (aValue.Length() == 5) {
+ if (aResult) {
+ *aResult = ((hours * 60) + minutes) * 60000;
+ }
+ return true;
+ }
+
+ // The following format is the next shorter one: "HH:MM:SS".
+ if (aValue.Length() < 8 || aValue[5] != ':') {
+ return false;
+ }
+
+ uint32_t seconds;
+ if (!DigitSubStringToNumber(aValue, 6, 2, &seconds) || seconds > 59) {
+ return false;
+ }
+
+ if (aValue.Length() == 8) {
+ if (aResult) {
+ *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000;
+ }
+ return true;
+ }
+
+ // The string must follow this format now: "HH:MM:SS.{s,ss,sss}".
+ // There can be 1 to 3 digits for the fractions of seconds.
+ if (aValue.Length() == 9 || aValue.Length() > 12 || aValue[8] != '.') {
+ return false;
+ }
+
+ uint32_t fractionsSeconds;
+ if (!DigitSubStringToNumber(aValue, 9, aValue.Length() - 9, &fractionsSeconds)) {
+ return false;
+ }
+
+ if (aResult) {
+ *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000 +
+ // NOTE: there is 10.0 instead of 10 and static_cast<int> because
+ // some old [and stupid] compilers can't just do the right thing.
+ fractionsSeconds * pow(10.0, static_cast<int>(3 - (aValue.Length() - 9)));
+ }
+
+ return true;
+}
+
+static bool
+IsDateTimeEnabled(int32_t aNewType)
+{
+ return (aNewType == NS_FORM_INPUT_DATE &&
+ (Preferences::GetBool("dom.forms.datetime", false) ||
+ Preferences::GetBool("dom.experimental_forms", false) ||
+ Preferences::GetBool("dom.forms.datepicker", false))) ||
+ (aNewType == NS_FORM_INPUT_TIME &&
+ (Preferences::GetBool("dom.forms.datetime", false) ||
+ Preferences::GetBool("dom.experimental_forms", false))) ||
+ ((aNewType == NS_FORM_INPUT_MONTH ||
+ aNewType == NS_FORM_INPUT_WEEK ||
+ aNewType == NS_FORM_INPUT_DATETIME_LOCAL) &&
+ Preferences::GetBool("dom.forms.datetime", false));
+}
+
+bool
+HTMLInputElement::ParseAttribute(int32_t aNamespaceID,
+ nsIAtom* aAttribute,
+ const nsAString& aValue,
+ nsAttrValue& aResult)
+{
+ if (aNamespaceID == kNameSpaceID_None) {
+ if (aAttribute == nsGkAtoms::type) {
+ // XXX ARG!! This is major evilness. ParseAttribute
+ // shouldn't set members. Override SetAttr instead
+ int32_t newType;
+ bool success = aResult.ParseEnumValue(aValue, kInputTypeTable, false);
+ if (success) {
+ newType = aResult.GetEnumValue();
+ if ((IsExperimentalMobileType(newType) &&
+ !Preferences::GetBool("dom.experimental_forms", false)) ||
+ (newType == NS_FORM_INPUT_NUMBER &&
+ !Preferences::GetBool("dom.forms.number", false)) ||
+ (newType == NS_FORM_INPUT_COLOR &&
+ !Preferences::GetBool("dom.forms.color", false)) ||
+ (IsDateTimeInputType(newType) && !IsDateTimeEnabled(newType))) {
+ newType = kInputDefaultType->value;
+ aResult.SetTo(newType, &aValue);
+ }
+ } else {
+ newType = kInputDefaultType->value;
+ }
+
+ if (newType != mType) {
+ // Make sure to do the check for newType being NS_FORM_INPUT_FILE and
+ // the corresponding SetValueInternal() call _before_ we set mType.
+ // That way the logic in SetValueInternal() will work right (that logic
+ // makes assumptions about our frame based on mType, but we won't have
+ // had time to recreate frames yet -- that happens later in the
+ // SetAttr() process).
+ if (newType == NS_FORM_INPUT_FILE || mType == NS_FORM_INPUT_FILE) {
+ // This call isn't strictly needed any more since we'll never
+ // confuse values and filenames. However it's there for backwards
+ // compat.
+ ClearFiles(false);
+ }
+
+ HandleTypeChange(newType);
+ }
+
+ return success;
+ }
+ if (aAttribute == nsGkAtoms::width) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::height) {
+ return aResult.ParseSpecialIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::maxlength) {
+ return aResult.ParseNonNegativeIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::minlength) {
+ return aResult.ParseNonNegativeIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::size) {
+ return aResult.ParsePositiveIntValue(aValue);
+ }
+ if (aAttribute == nsGkAtoms::border) {
+ return aResult.ParseIntWithBounds(aValue, 0);
+ }
+ if (aAttribute == nsGkAtoms::align) {
+ return ParseAlignValue(aValue, aResult);
+ }
+ if (aAttribute == nsGkAtoms::formmethod) {
+ return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
+ }
+ if (aAttribute == nsGkAtoms::formenctype) {
+ return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
+ }
+ if (aAttribute == nsGkAtoms::autocomplete) {
+ aResult.ParseAtomArray(aValue);
+ return true;
+ }
+ if (aAttribute == nsGkAtoms::inputmode) {
+ return aResult.ParseEnumValue(aValue, kInputInputmodeTable, false);
+ }
+ if (ParseImageAttribute(aAttribute, aValue, aResult)) {
+ // We have to call |ParseImageAttribute| unconditionally since we
+ // don't know if we're going to have a type="image" attribute yet,
+ // (or could have it set dynamically in the future). See bug
+ // 214077.
+ return true;
+ }
+ }
+
+ return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+ aResult);
+}
+
+void
+HTMLInputElement::MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
+ nsRuleData* aData)
+{
+ const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::type);
+ if (value && value->Type() == nsAttrValue::eEnum &&
+ value->GetEnumValue() == NS_FORM_INPUT_IMAGE) {
+ nsGenericHTMLFormElementWithState::MapImageBorderAttributeInto(aAttributes, aData);
+ nsGenericHTMLFormElementWithState::MapImageMarginAttributeInto(aAttributes, aData);
+ nsGenericHTMLFormElementWithState::MapImageSizeAttributesInto(aAttributes, aData);
+ // Images treat align as "float"
+ nsGenericHTMLFormElementWithState::MapImageAlignAttributeInto(aAttributes, aData);
+ }
+
+ nsGenericHTMLFormElementWithState::MapCommonAttributesInto(aAttributes, aData);
+}
+
+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::allowdirs ||
+ aAttribute == nsGkAtoms::webkitdirectory) {
+ retval |= nsChangeHint_ReconstructFrame;
+ } 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 |= nsChangeHint_ReconstructFrame;
+ } else if (aAttribute == nsGkAtoms::value) {
+ retval |= NS_STYLE_HINT_REFLOW;
+ } else if (aAttribute == nsGkAtoms::size &&
+ IsSingleLineTextControl(false)) {
+ retval |= NS_STYLE_HINT_REFLOW;
+ } else if (PlaceholderApplies() && aAttribute == nsGkAtoms::placeholder) {
+ retval |= nsChangeHint_ReconstructFrame;
+ }
+ return retval;
+}
+
+NS_IMETHODIMP_(bool)
+HTMLInputElement::IsAttributeMapped(const nsIAtom* aAttribute) const
+{
+ static const MappedAttributeEntry attributes[] = {
+ { &nsGkAtoms::align },
+ { &nsGkAtoms::type },
+ { nullptr },
+ };
+
+ static const MappedAttributeEntry* const map[] = {
+ attributes,
+ sCommonAttributeMap,
+ sImageMarginSizeAttributeMap,
+ sImageBorderAttributeMap,
+ };
+
+ return FindAttributeDependence(aAttribute, map);
+}
+
+nsMapRuleToAttributesFunc
+HTMLInputElement::GetAttributeMappingFunction() const
+{
+ return &MapAttributesIntoRule;
+}
+
+
+// Directory picking methods:
+
+bool
+HTMLInputElement::IsFilesAndDirectoriesSupported() const
+{
+ // This method is supposed to return true if a file and directory picker
+ // supports the selection of both files and directories *at the same time*.
+ // Only Mac currently supports that. We could implement it for Mac, but
+ // currently we do not.
+ return false;
+}
+
+void
+HTMLInputElement::ChooseDirectory(ErrorResult& aRv)
+{
+ if (mType != NS_FORM_INPUT_FILE) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+ // Script can call this method directly, so even though we don't show the
+ // "Pick Folder..." button on platforms that don't have a directory picker
+ // we have to redirect to the file picker here.
+ InitFilePicker(
+#if defined(ANDROID) || defined(MOZ_B2G)
+ // No native directory picker - redirect to plain file picker
+ FILE_PICKER_FILE
+#else
+ FILE_PICKER_DIRECTORY
+#endif
+ );
+}
+
+already_AddRefed<Promise>
+HTMLInputElement::GetFilesAndDirectories(ErrorResult& aRv)
+{
+ if (mType != NS_FORM_INPUT_FILE) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
+ MOZ_ASSERT(global);
+ if (!global) {
+ return nullptr;
+ }
+
+ RefPtr<Promise> p = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ const nsTArray<OwningFileOrDirectory>& filesAndDirs =
+ GetFilesOrDirectoriesInternal();
+
+ Sequence<OwningFileOrDirectory> filesAndDirsSeq;
+
+ if (!filesAndDirsSeq.SetLength(filesAndDirs.Length(),
+ mozilla::fallible_t())) {
+ p->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ return p.forget();
+ }
+
+ for (uint32_t i = 0; i < filesAndDirs.Length(); ++i) {
+ if (filesAndDirs[i].IsDirectory()) {
+ RefPtr<Directory> directory = filesAndDirs[i].GetAsDirectory();
+
+ // In future we could refactor SetFilePickerFiltersFromAccept to return a
+ // semicolon separated list of file extensions and include that in the
+ // filter string passed here.
+ directory->SetContentFilters(NS_LITERAL_STRING("filter-out-sensitive"));
+ filesAndDirsSeq[i].SetAsDirectory() = directory;
+ } else {
+ MOZ_ASSERT(filesAndDirs[i].IsFile());
+
+ // This file was directly selected by the user, so don't filter it.
+ filesAndDirsSeq[i].SetAsFile() = filesAndDirs[i].GetAsFile();
+ }
+ }
+
+ p->MaybeResolve(filesAndDirsSeq);
+ return p.forget();
+}
+
+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<Promise> p = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ helper->AddPromise(p);
+ return p.forget();
+}
+
+
+// Controllers Methods
+
+nsIControllers*
+HTMLInputElement::GetControllers(ErrorResult& aRv)
+{
+ //XXX: what about type "file"?
+ if (IsSingleLineTextControl(false))
+ {
+ if (!mControllers)
+ {
+ nsresult rv;
+ mControllers = do_CreateInstance(kXULControllersCID, &rv);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIController>
+ controller(do_CreateInstance("@mozilla.org/editor/editorcontroller;1",
+ &rv));
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ mControllers->AppendController(controller);
+
+ controller = do_CreateInstance("@mozilla.org/editor/editingcontroller;1",
+ &rv);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return nullptr;
+ }
+
+ mControllers->AppendController(controller);
+ }
+ }
+
+ return mControllers;
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetControllers(nsIControllers** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ ErrorResult rv;
+ RefPtr<nsIControllers> controller = GetControllers(rv);
+ controller.forget(aResult);
+ return rv.StealNSResult();
+}
+
+int32_t
+HTMLInputElement::GetTextLength(ErrorResult& aRv)
+{
+ nsAutoString val;
+ GetValue(val);
+ return val.Length();
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetTextLength(int32_t* aTextLength)
+{
+ ErrorResult rv;
+ *aTextLength = GetTextLength(rv);
+ return rv.StealNSResult();
+}
+
+void
+HTMLInputElement::SetSelectionRange(int32_t aSelectionStart,
+ int32_t aSelectionEnd,
+ const Optional<nsAString>& aDirection,
+ ErrorResult& aRv)
+{
+ if (!SupportsTextSelection()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ nsresult rv = SetSelectionRange(aSelectionStart, aSelectionEnd,
+ aDirection.WasPassed() ? aDirection.Value() : NullString());
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetSelectionRange(int32_t aSelectionStart,
+ int32_t aSelectionEnd,
+ const nsAString& aDirection)
+{
+ nsresult rv = NS_OK;
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+ nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
+ if (textControlFrame) {
+ // Default to forward, even if not specified.
+ // Note that we don't currently support directionless selections, so
+ // "none" is treated like "forward".
+ nsITextControlFrame::SelectionDirection dir = nsITextControlFrame::eForward;
+ if (!aDirection.IsEmpty() && aDirection.EqualsLiteral("backward")) {
+ dir = nsITextControlFrame::eBackward;
+ }
+
+ rv = textControlFrame->SetSelectionRange(aSelectionStart, aSelectionEnd, dir);
+ if (NS_SUCCEEDED(rv)) {
+ rv = textControlFrame->ScrollSelectionIntoView();
+ RefPtr<AsyncEventDispatcher> asyncDispatcher =
+ new AsyncEventDispatcher(this, NS_LITERAL_STRING("select"),
+ true, false);
+ asyncDispatcher->PostDOMEvent();
+ }
+ }
+
+ return rv;
+}
+
+void
+HTMLInputElement::SetRangeText(const nsAString& aReplacement, ErrorResult& aRv)
+{
+ if (!SupportsTextSelection()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ int32_t start, end;
+ aRv = GetSelectionRange(&start, &end);
+ if (aRv.Failed()) {
+ nsTextEditorState* state = GetEditorState();
+ if (state && state->IsSelectionCached()) {
+ start = state->GetSelectionProperties().GetStart();
+ end = state->GetSelectionProperties().GetEnd();
+ aRv = NS_OK;
+ }
+ }
+
+ SetRangeText(aReplacement, start, end, mozilla::dom::SelectionMode::Preserve,
+ aRv, start, end);
+}
+
+void
+HTMLInputElement::SetRangeText(const nsAString& aReplacement, uint32_t aStart,
+ uint32_t aEnd, const SelectionMode& aSelectMode,
+ ErrorResult& aRv, int32_t aSelectionStart,
+ int32_t aSelectionEnd)
+{
+ if (!SupportsTextSelection()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (aStart > aEnd) {
+ aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
+ return;
+ }
+
+ nsAutoString value;
+ GetValueInternal(value);
+ uint32_t inputValueLength = value.Length();
+
+ if (aStart > inputValueLength) {
+ aStart = inputValueLength;
+ }
+
+ if (aEnd > inputValueLength) {
+ aEnd = inputValueLength;
+ }
+
+ if (aSelectionStart == -1 && aSelectionEnd == -1) {
+ aRv = GetSelectionRange(&aSelectionStart, &aSelectionEnd);
+ if (aRv.Failed()) {
+ nsTextEditorState* state = GetEditorState();
+ if (state && state->IsSelectionCached()) {
+ aSelectionStart = state->GetSelectionProperties().GetStart();
+ aSelectionEnd = state->GetSelectionProperties().GetEnd();
+ aRv = NS_OK;
+ }
+ }
+ }
+
+ if (aStart <= aEnd) {
+ value.Replace(aStart, aEnd - aStart, aReplacement);
+ nsresult rv =
+ SetValueInternal(value, nsTextEditorState::eSetValue_ByContent);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+ }
+
+ uint32_t newEnd = aStart + aReplacement.Length();
+ int32_t delta = aReplacement.Length() - (aEnd - aStart);
+
+ switch (aSelectMode) {
+ case mozilla::dom::SelectionMode::Select:
+ {
+ aSelectionStart = aStart;
+ aSelectionEnd = newEnd;
+ }
+ break;
+ case mozilla::dom::SelectionMode::Start:
+ {
+ aSelectionStart = aSelectionEnd = aStart;
+ }
+ break;
+ case mozilla::dom::SelectionMode::End:
+ {
+ aSelectionStart = aSelectionEnd = newEnd;
+ }
+ break;
+ case mozilla::dom::SelectionMode::Preserve:
+ {
+ if ((uint32_t)aSelectionStart > aEnd) {
+ aSelectionStart += delta;
+ } else if ((uint32_t)aSelectionStart > aStart) {
+ aSelectionStart = aStart;
+ }
+
+ if ((uint32_t)aSelectionEnd > aEnd) {
+ aSelectionEnd += delta;
+ } else if ((uint32_t)aSelectionEnd > aStart) {
+ aSelectionEnd = newEnd;
+ }
+ }
+ break;
+ default:
+ MOZ_CRASH("Unknown mode!");
+ }
+
+ Optional<nsAString> direction;
+ SetSelectionRange(aSelectionStart, aSelectionEnd, direction, aRv);
+}
+
+Nullable<int32_t>
+HTMLInputElement::GetSelectionStart(ErrorResult& aRv)
+{
+ if (!SupportsTextSelection()) {
+ return Nullable<int32_t>();
+ }
+
+ int32_t selStart;
+ nsresult rv = GetSelectionStart(&selStart);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+
+ return Nullable<int32_t>(selStart);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetSelectionStart(int32_t* aSelectionStart)
+{
+ NS_ENSURE_ARG_POINTER(aSelectionStart);
+
+ int32_t selEnd, selStart;
+ nsresult rv = GetSelectionRange(&selStart, &selEnd);
+
+ if (NS_FAILED(rv)) {
+ nsTextEditorState* state = GetEditorState();
+ if (state && state->IsSelectionCached()) {
+ *aSelectionStart = state->GetSelectionProperties().GetStart();
+ return NS_OK;
+ }
+ return rv;
+ }
+
+ *aSelectionStart = selStart;
+ return NS_OK;
+}
+
+void
+HTMLInputElement::SetSelectionStart(const Nullable<int32_t>& aSelectionStart,
+ ErrorResult& aRv)
+{
+ if (!SupportsTextSelection()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ int32_t selStart = 0;
+ if (!aSelectionStart.IsNull()) {
+ selStart = aSelectionStart.Value();
+ }
+
+ nsTextEditorState* state = GetEditorState();
+ if (state && state->IsSelectionCached()) {
+ state->GetSelectionProperties().SetStart(selStart);
+ return;
+ }
+
+ nsAutoString direction;
+ aRv = GetSelectionDirection(direction);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ int32_t start, end;
+ aRv = GetSelectionRange(&start, &end);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ start = selStart;
+ if (end < start) {
+ end = start;
+ }
+
+ aRv = SetSelectionRange(start, end, direction);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetSelectionStart(int32_t aSelectionStart)
+{
+ ErrorResult rv;
+ Nullable<int32_t> selStart(aSelectionStart);
+ SetSelectionStart(selStart, rv);
+ return rv.StealNSResult();
+}
+
+Nullable<int32_t>
+HTMLInputElement::GetSelectionEnd(ErrorResult& aRv)
+{
+ if (!SupportsTextSelection()) {
+ return Nullable<int32_t>();
+ }
+
+ int32_t selEnd;
+ nsresult rv = GetSelectionEnd(&selEnd);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+
+ return Nullable<int32_t>(selEnd);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetSelectionEnd(int32_t* aSelectionEnd)
+{
+ NS_ENSURE_ARG_POINTER(aSelectionEnd);
+
+ int32_t selEnd, selStart;
+ nsresult rv = GetSelectionRange(&selStart, &selEnd);
+
+ if (NS_FAILED(rv)) {
+ nsTextEditorState* state = GetEditorState();
+ if (state && state->IsSelectionCached()) {
+ *aSelectionEnd = state->GetSelectionProperties().GetEnd();
+ return NS_OK;
+ }
+ return rv;
+ }
+
+ *aSelectionEnd = selEnd;
+ return NS_OK;
+}
+
+void
+HTMLInputElement::SetSelectionEnd(const Nullable<int32_t>& aSelectionEnd,
+ ErrorResult& aRv)
+{
+ if (!SupportsTextSelection()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ int32_t selEnd = 0;
+ if (!aSelectionEnd.IsNull()) {
+ selEnd = aSelectionEnd.Value();
+ }
+
+ nsTextEditorState* state = GetEditorState();
+ if (state && state->IsSelectionCached()) {
+ state->GetSelectionProperties().SetEnd(selEnd);
+ return;
+ }
+
+ nsAutoString direction;
+ aRv = GetSelectionDirection(direction);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ int32_t start, end;
+ aRv = GetSelectionRange(&start, &end);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ end = selEnd;
+ if (start > end) {
+ start = end;
+ }
+
+ aRv = SetSelectionRange(start, end, direction);
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetSelectionEnd(int32_t aSelectionEnd)
+{
+ ErrorResult rv;
+ Nullable<int32_t> selEnd(aSelectionEnd);
+ SetSelectionEnd(selEnd, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetFiles(nsIDOMFileList** aFileList)
+{
+ RefPtr<FileList> list = GetFiles();
+ list.forget(aFileList);
+ return NS_OK;
+}
+
+nsresult
+HTMLInputElement::GetSelectionRange(int32_t* aSelectionStart,
+ int32_t* aSelectionEnd)
+{
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+ nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
+ if (textControlFrame) {
+ return textControlFrame->GetSelectionRange(aSelectionStart, aSelectionEnd);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+static void
+DirectionToName(nsITextControlFrame::SelectionDirection dir, nsAString& aDirection)
+{
+ if (dir == nsITextControlFrame::eNone) {
+ aDirection.AssignLiteral("none");
+ } else if (dir == nsITextControlFrame::eForward) {
+ aDirection.AssignLiteral("forward");
+ } else if (dir == nsITextControlFrame::eBackward) {
+ aDirection.AssignLiteral("backward");
+ } else {
+ NS_NOTREACHED("Invalid SelectionDirection value");
+ }
+}
+
+void
+HTMLInputElement::GetSelectionDirection(nsAString& aDirection, ErrorResult& aRv)
+{
+ if (!SupportsTextSelection()) {
+ aDirection.SetIsVoid(true);
+ return;
+ }
+
+ nsresult rv = NS_ERROR_FAILURE;
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+ nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
+ if (textControlFrame) {
+ nsITextControlFrame::SelectionDirection dir;
+ rv = textControlFrame->GetSelectionRange(nullptr, nullptr, &dir);
+ if (NS_SUCCEEDED(rv)) {
+ DirectionToName(dir, aDirection);
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ nsTextEditorState* state = GetEditorState();
+ if (state && state->IsSelectionCached()) {
+ DirectionToName(state->GetSelectionProperties().GetDirection(), aDirection);
+ return;
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ }
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetSelectionDirection(nsAString& aDirection)
+{
+ ErrorResult rv;
+ GetSelectionDirection(aDirection, rv);
+ return rv.StealNSResult();
+}
+
+void
+HTMLInputElement::SetSelectionDirection(const nsAString& aDirection, ErrorResult& aRv)
+{
+ if (!SupportsTextSelection()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ nsTextEditorState* state = GetEditorState();
+ if (state && state->IsSelectionCached()) {
+ nsITextControlFrame::SelectionDirection dir = nsITextControlFrame::eNone;
+ if (aDirection.EqualsLiteral("forward")) {
+ dir = nsITextControlFrame::eForward;
+ } else if (aDirection.EqualsLiteral("backward")) {
+ dir = nsITextControlFrame::eBackward;
+ }
+ state->GetSelectionProperties().SetDirection(dir);
+ return;
+ }
+
+ int32_t start, end;
+ aRv = GetSelectionRange(&start, &end);
+ if (!aRv.Failed()) {
+ aRv = SetSelectionRange(start, end, aDirection);
+ }
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SetSelectionDirection(const nsAString& aDirection)
+{
+ ErrorResult rv;
+ SetSelectionDirection(aDirection, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+HTMLInputElement::GetPhonetic(nsAString& aPhonetic)
+{
+ aPhonetic.Truncate();
+ nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
+ nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
+ if (textControlFrame) {
+ textControlFrame->GetPhonetic(aPhonetic);
+ }
+
+ return NS_OK;
+}
+
+#ifdef ACCESSIBILITY
+/*static*/ nsresult
+FireEventForAccessibility(nsIDOMHTMLInputElement* aTarget,
+ nsPresContext* aPresContext,
+ const nsAString& aEventType)
+{
+ nsCOMPtr<mozilla::dom::Element> element = do_QueryInterface(aTarget);
+ RefPtr<Event> event = NS_NewDOMEvent(element, aPresContext, nullptr);
+ event->InitEvent(aEventType, true, true);
+ event->SetTrusted(true);
+
+ EventDispatcher::DispatchDOMEvent(aTarget, nullptr, event, aPresContext,
+ nullptr);
+
+ return NS_OK;
+}
+#endif
+
+void
+HTMLInputElement::UpdateApzAwareFlag()
+{
+#if !defined(ANDROID) && !defined(XP_MACOSX)
+ if ((mType == NS_FORM_INPUT_NUMBER) || (mType == NS_FORM_INPUT_RANGE)) {
+ SetMayBeApzAware();
+ }
+#endif
+}
+
+nsresult
+HTMLInputElement::SetDefaultValueAsValue()
+{
+ NS_ASSERTION(GetValueMode() == VALUE_MODE_VALUE,
+ "GetValueMode() should return VALUE_MODE_VALUE!");
+
+ // The element has a content attribute value different from it's value when
+ // it's in the value mode value.
+ nsAutoString resetVal;
+ GetDefaultValue(resetVal);
+
+ // SetValueInternal is going to sanitize the value.
+ return SetValueInternal(resetVal, nsTextEditorState::eSetValue_Internal);
+}
+
+void
+HTMLInputElement::SetDirectionIfAuto(bool aAuto, bool aNotify)
+{
+ if (aAuto) {
+ SetHasDirAuto();
+ if (IsSingleLineTextControl(true)) {
+ nsAutoString value;
+ GetValue(value);
+ SetDirectionalityFromValue(this, value, aNotify);
+ }
+ } else {
+ ClearHasDirAuto();
+ }
+}
+
+NS_IMETHODIMP
+HTMLInputElement::Reset()
+{
+ // We should be able to reset all dirty flags regardless of the type.
+ SetCheckedChanged(false);
+ SetValueChanged(false);
+ mLastValueChangeWasInteractive = false;
+
+ switch (GetValueMode()) {
+ case VALUE_MODE_VALUE:
+ return SetDefaultValueAsValue();
+ case VALUE_MODE_DEFAULT_ON:
+ DoSetChecked(DefaultChecked(), true, false);
+ return NS_OK;
+ case VALUE_MODE_FILENAME:
+ ClearFiles(false);
+ return NS_OK;
+ case VALUE_MODE_DEFAULT:
+ default:
+ return NS_OK;
+ }
+}
+
+NS_IMETHODIMP
+HTMLInputElement::SubmitNamesValues(HTMLFormSubmission* aFormSubmission)
+{
+ // Disabled elements don't submit
+ // For type=reset, and type=button, we just never submit, period.
+ // For type=image and type=button, we only submit if we were the button
+ // pressed
+ // For type=radio and type=checkbox, we only submit if checked=true
+ if (IsDisabled() || mType == NS_FORM_INPUT_RESET ||
+ mType == NS_FORM_INPUT_BUTTON ||
+ ((mType == NS_FORM_INPUT_SUBMIT || mType == NS_FORM_INPUT_IMAGE) &&
+ aFormSubmission->GetOriginatingElement() != this) ||
+ ((mType == NS_FORM_INPUT_RADIO || mType == NS_FORM_INPUT_CHECKBOX) &&
+ !mChecked)) {
+ return NS_OK;
+ }
+
+ // Get the name
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+
+ // Submit .x, .y for input type=image
+ if (mType == NS_FORM_INPUT_IMAGE) {
+ // Get a property set by the frame to find out where it was clicked.
+ nsIntPoint* lastClickedPoint =
+ static_cast<nsIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
+ int32_t x, y;
+ if (lastClickedPoint) {
+ // Convert the values to strings for submission
+ x = lastClickedPoint->x;
+ y = lastClickedPoint->y;
+ } else {
+ x = y = 0;
+ }
+
+ nsAutoString xVal, yVal;
+ xVal.AppendInt(x);
+ yVal.AppendInt(y);
+
+ if (!name.IsEmpty()) {
+ aFormSubmission->AddNameValuePair(name + NS_LITERAL_STRING(".x"), xVal);
+ aFormSubmission->AddNameValuePair(name + NS_LITERAL_STRING(".y"), yVal);
+ } else {
+ // If the Image Element has no name, simply return x and y
+ // to Nav and IE compatibility.
+ aFormSubmission->AddNameValuePair(NS_LITERAL_STRING("x"), xVal);
+ aFormSubmission->AddNameValuePair(NS_LITERAL_STRING("y"), yVal);
+ }
+
+ return NS_OK;
+ }
+
+ //
+ // Submit name=value
+ //
+
+ // If name not there, don't submit
+ if (name.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // Get the value
+ nsAutoString value;
+ nsresult rv = GetValue(value);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (mType == NS_FORM_INPUT_SUBMIT && value.IsEmpty() &&
+ !HasAttr(kNameSpaceID_None, nsGkAtoms::value)) {
+ // Get our default value, which is the same as our default label
+ nsXPIDLString defaultValue;
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "Submit", defaultValue);
+ value = defaultValue;
+ }
+
+ //
+ // Submit file if its input type=file and this encoding method accepts files
+ //
+ if (mType == NS_FORM_INPUT_FILE) {
+ // Submit files
+
+ const nsTArray<OwningFileOrDirectory>& files =
+ GetFilesOrDirectoriesInternal();
+
+ if (files.IsEmpty()) {
+ aFormSubmission->AddNameBlobOrNullPair(name, nullptr);
+ return NS_OK;
+ }
+
+ for (uint32_t i = 0; i < files.Length(); ++i) {
+ if (files[i].IsFile()) {
+ aFormSubmission->AddNameBlobOrNullPair(name, files[i].GetAsFile());
+ } else {
+ MOZ_ASSERT(files[i].IsDirectory());
+ aFormSubmission->AddNameDirectoryPair(name, files[i].GetAsDirectory());
+ }
+ }
+
+ return NS_OK;
+ }
+
+ if (mType == NS_FORM_INPUT_HIDDEN && name.EqualsLiteral("_charset_")) {
+ nsCString charset;
+ aFormSubmission->GetCharset(charset);
+ return aFormSubmission->AddNameValuePair(name,
+ NS_ConvertASCIItoUTF16(charset));
+ }
+ if (IsSingleLineTextControl(true) &&
+ name.EqualsLiteral("isindex") &&
+ aFormSubmission->SupportsIsindexSubmission()) {
+ return aFormSubmission->AddIsindex(value);
+ }
+ return aFormSubmission->AddNameValuePair(name, value);
+}
+
+
+NS_IMETHODIMP
+HTMLInputElement::SaveState()
+{
+ RefPtr<HTMLInputElementState> inputState;
+ switch (GetValueMode()) {
+ case VALUE_MODE_DEFAULT_ON:
+ if (mCheckedChanged) {
+ inputState = new HTMLInputElementState();
+ inputState->SetChecked(mChecked);
+ }
+ break;
+ case VALUE_MODE_FILENAME:
+ if (!mFilesOrDirectories.IsEmpty()) {
+ inputState = new HTMLInputElementState();
+ inputState->SetFilesOrDirectories(mFilesOrDirectories);
+ }
+ break;
+ case VALUE_MODE_VALUE:
+ case VALUE_MODE_DEFAULT:
+ // VALUE_MODE_DEFAULT shouldn't have their value saved except 'hidden',
+ // mType shouldn't be NS_FORM_INPUT_PASSWORD and value should have changed.
+ if ((GetValueMode() == VALUE_MODE_DEFAULT &&
+ mType != NS_FORM_INPUT_HIDDEN) ||
+ mType == NS_FORM_INPUT_PASSWORD || !mValueChanged) {
+ break;
+ }
+
+ inputState = new HTMLInputElementState();
+ nsAutoString value;
+ nsresult rv = GetValue(value);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (!IsSingleLineTextControl(false)) {
+ rv = nsLinebreakConverter::ConvertStringLineBreaks(
+ value,
+ nsLinebreakConverter::eLinebreakPlatform,
+ nsLinebreakConverter::eLinebreakContent);
+
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Converting linebreaks failed!");
+ return rv;
+ }
+ }
+
+ inputState->SetValue(value);
+ break;
+ }
+
+ if (inputState) {
+ nsPresState* state = GetPrimaryPresState();
+ if (state) {
+ state->SetStateProperty(inputState);
+ }
+ }
+
+ if (mDisabledChanged) {
+ nsPresState* state = GetPrimaryPresState();
+ if (state) {
+ // We do not want to save the real disabled state but the disabled
+ // attribute.
+ state->SetDisabled(HasAttr(kNameSpaceID_None, nsGkAtoms::disabled));
+ }
+ }
+
+ return NS_OK;
+}
+
+void
+HTMLInputElement::DoneCreatingElement()
+{
+ mDoneCreating = true;
+
+ //
+ // Restore state as needed. Note that disabled state applies to all control
+ // types.
+ //
+ bool restoredCheckedState =
+ !mInhibitRestoration && NS_SUCCEEDED(GenerateStateKey()) && RestoreFormControlState();
+
+ //
+ // If restore does not occur, we initialize .checked using the CHECKED
+ // property.
+ //
+ if (!restoredCheckedState && mShouldInitChecked) {
+ DoSetChecked(DefaultChecked(), false, true);
+ DoSetCheckedChanged(false, false);
+ }
+
+ // Sanitize the value.
+ if (GetValueMode() == VALUE_MODE_VALUE) {
+ nsAutoString aValue;
+ GetValue(aValue);
+ // TODO: What should we do if SetValueInternal fails? (The allocation
+ // may potentially be big, but most likely we've failed to allocate
+ // before the type change.)
+ SetValueInternal(aValue, nsTextEditorState::eSetValue_Internal);
+ }
+
+ mShouldInitChecked = false;
+}
+
+EventStates
+HTMLInputElement::IntrinsicState() const
+{
+ // If you add states here, and they're type-dependent, you need to add them
+ // to the type case in AfterSetAttr.
+
+ EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState();
+ if (mType == NS_FORM_INPUT_CHECKBOX || mType == NS_FORM_INPUT_RADIO) {
+ // Check current checked state (:checked)
+ if (mChecked) {
+ state |= NS_EVENT_STATE_CHECKED;
+ }
+
+ // Check current indeterminate state (:indeterminate)
+ if (mType == NS_FORM_INPUT_CHECKBOX && mIndeterminate) {
+ state |= NS_EVENT_STATE_INDETERMINATE;
+ }
+
+ if (mType == NS_FORM_INPUT_RADIO) {
+ nsCOMPtr<nsIDOMHTMLInputElement> selected = GetSelectedRadioButton();
+ bool indeterminate = !selected && !mChecked;
+
+ if (indeterminate) {
+ state |= NS_EVENT_STATE_INDETERMINATE;
+ }
+ }
+
+ // Check whether we are the default checked element (:default)
+ if (DefaultChecked()) {
+ state |= NS_EVENT_STATE_DEFAULT;
+ }
+ } else if (mType == NS_FORM_INPUT_IMAGE) {
+ state |= nsImageLoadingContent::ImageState();
+ }
+
+ if (DoesRequiredApply() && HasAttr(kNameSpaceID_None, nsGkAtoms::required)) {
+ state |= NS_EVENT_STATE_REQUIRED;
+ } else {
+ state |= NS_EVENT_STATE_OPTIONAL;
+ }
+
+ if (IsCandidateForConstraintValidation()) {
+ if (IsValid()) {
+ state |= NS_EVENT_STATE_VALID;
+ } else {
+ state |= NS_EVENT_STATE_INVALID;
+
+ if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
+ (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
+ (mCanShowInvalidUI && ShouldShowValidityUI()))) {
+ state |= NS_EVENT_STATE_MOZ_UI_INVALID;
+ }
+ }
+
+ // :-moz-ui-valid applies if all of the following conditions are true:
+ // 1. The element is not focused, or had either :-moz-ui-valid or
+ // :-moz-ui-invalid applying before it was focused ;
+ // 2. The element is either valid or isn't allowed to have
+ // :-moz-ui-invalid applying ;
+ // 3. The element has no form owner or its form owner doesn't have the
+ // novalidate attribute set ;
+ // 4. The element has already been modified or the user tried to submit the
+ // form owner while invalid.
+ if ((!mForm || !mForm->HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate)) &&
+ (mCanShowValidUI && ShouldShowValidityUI() &&
+ (IsValid() || (!state.HasState(NS_EVENT_STATE_MOZ_UI_INVALID) &&
+ !mCanShowInvalidUI)))) {
+ state |= NS_EVENT_STATE_MOZ_UI_VALID;
+ }
+
+ // :in-range and :out-of-range only apply if the element currently has a range
+ if (mHasRange) {
+ state |= (GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW) ||
+ GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW))
+ ? NS_EVENT_STATE_OUTOFRANGE
+ : NS_EVENT_STATE_INRANGE;
+ }
+ }
+
+ if (PlaceholderApplies() &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder) &&
+ IsValueEmpty()) {
+ state |= NS_EVENT_STATE_PLACEHOLDERSHOWN;
+ }
+
+ if (mForm && !mForm->GetValidity() && IsSubmitControl()) {
+ state |= NS_EVENT_STATE_MOZ_SUBMITINVALID;
+ }
+
+ return state;
+}
+
+void
+HTMLInputElement::AddStates(EventStates aStates)
+{
+ if (mType == NS_FORM_INPUT_TEXT) {
+ EventStates focusStates(aStates & (NS_EVENT_STATE_FOCUS |
+ NS_EVENT_STATE_FOCUSRING));
+ if (!focusStates.IsEmpty()) {
+ HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
+ if (ownerNumberControl) {
+ ownerNumberControl->AddStates(focusStates);
+ } else {
+ HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
+ if (ownerDateTimeControl) {
+ ownerDateTimeControl->AddStates(focusStates);
+ }
+ }
+ }
+ }
+ nsGenericHTMLFormElementWithState::AddStates(aStates);
+}
+
+void
+HTMLInputElement::RemoveStates(EventStates aStates)
+{
+ if (mType == NS_FORM_INPUT_TEXT) {
+ EventStates focusStates(aStates & (NS_EVENT_STATE_FOCUS |
+ NS_EVENT_STATE_FOCUSRING));
+ if (!focusStates.IsEmpty()) {
+ HTMLInputElement* ownerNumberControl = GetOwnerNumberControl();
+ if (ownerNumberControl) {
+ ownerNumberControl->RemoveStates(focusStates);
+ } else {
+ HTMLInputElement* ownerDateTimeControl = GetOwnerDateTimeControl();
+ if (ownerDateTimeControl) {
+ ownerDateTimeControl->RemoveStates(focusStates);
+ }
+ }
+ }
+ }
+ nsGenericHTMLFormElementWithState::RemoveStates(aStates);
+}
+
+bool
+HTMLInputElement::RestoreState(nsPresState* aState)
+{
+ bool restoredCheckedState = false;
+
+ nsCOMPtr<HTMLInputElementState> inputState
+ (do_QueryInterface(aState->GetStateProperty()));
+
+ if (inputState) {
+ switch (GetValueMode()) {
+ case VALUE_MODE_DEFAULT_ON:
+ if (inputState->IsCheckedSet()) {
+ restoredCheckedState = true;
+ DoSetChecked(inputState->GetChecked(), true, true);
+ }
+ break;
+ case VALUE_MODE_FILENAME:
+ {
+ nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
+ if (window) {
+ nsTArray<OwningFileOrDirectory> array;
+ inputState->GetFilesOrDirectories(window, array);
+
+ SetFilesOrDirectories(array, true);
+ }
+ }
+ break;
+ case VALUE_MODE_VALUE:
+ case VALUE_MODE_DEFAULT:
+ if (GetValueMode() == VALUE_MODE_DEFAULT &&
+ mType != NS_FORM_INPUT_HIDDEN) {
+ break;
+ }
+
+ // TODO: What should we do if SetValueInternal fails? (The allocation
+ // may potentially be big, but most likely we've failed to allocate
+ // before the type change.)
+ SetValueInternal(inputState->GetValue(),
+ nsTextEditorState::eSetValue_Notify);
+ break;
+ }
+ }
+
+ if (aState->IsDisabledSet()) {
+ SetDisabled(aState->GetDisabled());
+ }
+
+ return restoredCheckedState;
+}
+
+bool
+HTMLInputElement::AllowDrop()
+{
+ // Allow drop on anything other than file inputs.
+
+ return mType != NS_FORM_INPUT_FILE;
+}
+
+/*
+ * Radio group stuff
+ */
+
+void
+HTMLInputElement::AddedToRadioGroup()
+{
+ // If the element is neither in a form nor a document, there is no group so we
+ // should just stop here.
+ if (!mForm && !IsInUncomposedDoc()) {
+ return;
+ }
+
+ // Make sure not to notify if we're still being created
+ bool notify = mDoneCreating;
+
+ //
+ // If the input element is checked, and we add it to the group, it will
+ // deselect whatever is currently selected in that group
+ //
+ if (mChecked) {
+ //
+ // If it is checked, call "RadioSetChecked" to perform the selection/
+ // deselection ritual. This has the side effect of repainting the
+ // radio button, but as adding a checked radio button into the group
+ // should not be that common an occurrence, I think we can live with
+ // that.
+ //
+ RadioSetChecked(notify);
+ }
+
+ //
+ // For integrity purposes, we have to ensure that "checkedChanged" is
+ // the same for this new element as for all the others in the group
+ //
+ bool checkedChanged = mCheckedChanged;
+
+ nsCOMPtr<nsIRadioVisitor> visitor =
+ new nsRadioGetCheckedChangedVisitor(&checkedChanged, this);
+ VisitGroup(visitor, notify);
+
+ SetCheckedChangedInternal(checkedChanged);
+
+ //
+ // Add the radio to the radio group container.
+ //
+ nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
+ if (container) {
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+ container->AddToRadioGroup(name, static_cast<nsIFormControl*>(this));
+
+ // We initialize the validity of the element to the validity of the group
+ // because we assume UpdateValueMissingState() will be called after.
+ SetValidityState(VALIDITY_STATE_VALUE_MISSING,
+ container->GetValueMissingState(name));
+ }
+}
+
+void
+HTMLInputElement::WillRemoveFromRadioGroup()
+{
+ nsIRadioGroupContainer* container = GetRadioGroupContainer();
+ if (!container) {
+ return;
+ }
+
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+
+ // If this button was checked, we need to notify the group that there is no
+ // longer a selected radio button
+ if (mChecked) {
+ container->SetCurrentRadioButton(name, nullptr);
+
+ nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
+ VisitGroup(visitor, true);
+ }
+
+ // Remove this radio from its group in the container.
+ // We need to call UpdateValueMissingValidityStateForRadio before to make sure
+ // the group validity is updated (with this element being ignored).
+ UpdateValueMissingValidityStateForRadio(true);
+ container->RemoveFromRadioGroup(name, static_cast<nsIFormControl*>(this));
+}
+
+bool
+HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t* aTabIndex)
+{
+ if (nsGenericHTMLFormElementWithState::IsHTMLFocusable(aWithMouse, aIsFocusable,
+ aTabIndex))
+ {
+ return true;
+ }
+
+ if (IsDisabled()) {
+ *aIsFocusable = false;
+ return true;
+ }
+
+ if (IsSingleLineTextControl(false) ||
+ mType == NS_FORM_INPUT_RANGE) {
+ *aIsFocusable = true;
+ return false;
+ }
+
+#ifdef XP_MACOSX
+ const bool defaultFocusable = !aWithMouse || nsFocusManager::sMouseFocusesFormControl;
+#else
+ const bool defaultFocusable = true;
+#endif
+
+ if (mType == NS_FORM_INPUT_FILE ||
+ mType == NS_FORM_INPUT_NUMBER ||
+ mType == NS_FORM_INPUT_TIME) {
+ if (aTabIndex) {
+ // We only want our native anonymous child to be tabable to, not ourself.
+ *aTabIndex = -1;
+ }
+ if (mType == NS_FORM_INPUT_NUMBER ||
+ mType == NS_FORM_INPUT_TIME) {
+ *aIsFocusable = true;
+ } else {
+ *aIsFocusable = defaultFocusable;
+ }
+ return true;
+ }
+
+ if (mType == NS_FORM_INPUT_HIDDEN) {
+ if (aTabIndex) {
+ *aTabIndex = -1;
+ }
+ *aIsFocusable = false;
+ return false;
+ }
+
+ if (!aTabIndex) {
+ // The other controls are all focusable
+ *aIsFocusable = defaultFocusable;
+ return false;
+ }
+
+ if (mType != NS_FORM_INPUT_RADIO) {
+ *aIsFocusable = defaultFocusable;
+ return false;
+ }
+
+ if (mChecked) {
+ // Selected radio buttons are tabbable
+ *aIsFocusable = defaultFocusable;
+ return false;
+ }
+
+ // Current radio button is not selected.
+ // But make it tabbable if nothing in group is selected.
+ nsIRadioGroupContainer* container = GetRadioGroupContainer();
+ if (!container) {
+ *aIsFocusable = defaultFocusable;
+ return false;
+ }
+
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+
+ if (container->GetCurrentRadioButton(name)) {
+ *aTabIndex = -1;
+ }
+ *aIsFocusable = defaultFocusable;
+ return false;
+}
+
+nsresult
+HTMLInputElement::VisitGroup(nsIRadioVisitor* aVisitor, bool aFlushContent)
+{
+ nsIRadioGroupContainer* container = GetRadioGroupContainer();
+ if (container) {
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+ return container->WalkRadioGroup(name, aVisitor, aFlushContent);
+ }
+
+ aVisitor->Visit(this);
+ return NS_OK;
+}
+
+HTMLInputElement::ValueModeType
+HTMLInputElement::GetValueMode() const
+{
+ switch (mType)
+ {
+ case NS_FORM_INPUT_HIDDEN:
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_BUTTON:
+ case NS_FORM_INPUT_RESET:
+ case NS_FORM_INPUT_IMAGE:
+ return VALUE_MODE_DEFAULT;
+ case NS_FORM_INPUT_CHECKBOX:
+ case NS_FORM_INPUT_RADIO:
+ return VALUE_MODE_DEFAULT_ON;
+ case NS_FORM_INPUT_FILE:
+ return VALUE_MODE_FILENAME;
+#ifdef DEBUG
+ case NS_FORM_INPUT_TEXT:
+ case NS_FORM_INPUT_PASSWORD:
+ case NS_FORM_INPUT_SEARCH:
+ case NS_FORM_INPUT_TEL:
+ case NS_FORM_INPUT_EMAIL:
+ case NS_FORM_INPUT_URL:
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_RANGE:
+ case NS_FORM_INPUT_DATE:
+ case NS_FORM_INPUT_TIME:
+ case NS_FORM_INPUT_COLOR:
+ case NS_FORM_INPUT_MONTH:
+ case NS_FORM_INPUT_WEEK:
+ case NS_FORM_INPUT_DATETIME_LOCAL:
+ return VALUE_MODE_VALUE;
+ default:
+ NS_NOTYETIMPLEMENTED("Unexpected input type in GetValueMode()");
+ return VALUE_MODE_VALUE;
+#else // DEBUG
+ default:
+ return VALUE_MODE_VALUE;
+#endif // DEBUG
+ }
+}
+
+bool
+HTMLInputElement::IsMutable() const
+{
+ return !IsDisabled() &&
+ !(DoesReadOnlyApply() &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::readonly));
+}
+
+bool
+HTMLInputElement::DoesReadOnlyApply() const
+{
+ switch (mType)
+ {
+ case NS_FORM_INPUT_HIDDEN:
+ case NS_FORM_INPUT_BUTTON:
+ case NS_FORM_INPUT_IMAGE:
+ case NS_FORM_INPUT_RESET:
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_RADIO:
+ case NS_FORM_INPUT_FILE:
+ case NS_FORM_INPUT_CHECKBOX:
+ case NS_FORM_INPUT_RANGE:
+ case NS_FORM_INPUT_COLOR:
+ return false;
+#ifdef DEBUG
+ case NS_FORM_INPUT_TEXT:
+ case NS_FORM_INPUT_PASSWORD:
+ case NS_FORM_INPUT_SEARCH:
+ case NS_FORM_INPUT_TEL:
+ case NS_FORM_INPUT_EMAIL:
+ case NS_FORM_INPUT_URL:
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_DATE:
+ case NS_FORM_INPUT_TIME:
+ case NS_FORM_INPUT_MONTH:
+ case NS_FORM_INPUT_WEEK:
+ case NS_FORM_INPUT_DATETIME_LOCAL:
+ return true;
+ default:
+ NS_NOTYETIMPLEMENTED("Unexpected input type in DoesReadOnlyApply()");
+ return true;
+#else // DEBUG
+ default:
+ return true;
+#endif // DEBUG
+ }
+}
+
+bool
+HTMLInputElement::DoesRequiredApply() const
+{
+ switch (mType)
+ {
+ case NS_FORM_INPUT_HIDDEN:
+ case NS_FORM_INPUT_BUTTON:
+ case NS_FORM_INPUT_IMAGE:
+ case NS_FORM_INPUT_RESET:
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_RANGE:
+ case NS_FORM_INPUT_COLOR:
+ return false;
+#ifdef DEBUG
+ case NS_FORM_INPUT_RADIO:
+ case NS_FORM_INPUT_CHECKBOX:
+ case NS_FORM_INPUT_FILE:
+ case NS_FORM_INPUT_TEXT:
+ case NS_FORM_INPUT_PASSWORD:
+ case NS_FORM_INPUT_SEARCH:
+ case NS_FORM_INPUT_TEL:
+ case NS_FORM_INPUT_EMAIL:
+ case NS_FORM_INPUT_URL:
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_DATE:
+ case NS_FORM_INPUT_TIME:
+ case NS_FORM_INPUT_MONTH:
+ case NS_FORM_INPUT_WEEK:
+ case NS_FORM_INPUT_DATETIME_LOCAL:
+ return true;
+ default:
+ NS_NOTYETIMPLEMENTED("Unexpected input type in DoesRequiredApply()");
+ return true;
+#else // DEBUG
+ default:
+ return true;
+#endif // DEBUG
+ }
+}
+
+bool
+HTMLInputElement::PlaceholderApplies() const
+{
+ if (IsDateTimeInputType(mType)) {
+ return false;
+ }
+
+ return IsSingleLineTextControl(false);
+}
+
+bool
+HTMLInputElement::DoesPatternApply() const
+{
+ // TODO: temporary until bug 773205 is fixed.
+ if (IsExperimentalMobileType(mType) || IsDateTimeInputType(mType)) {
+ return false;
+ }
+
+ return IsSingleLineTextControl(false);
+}
+
+bool
+HTMLInputElement::DoesMinMaxApply() const
+{
+ switch (mType)
+ {
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_DATE:
+ case NS_FORM_INPUT_TIME:
+ case NS_FORM_INPUT_RANGE:
+ case NS_FORM_INPUT_MONTH:
+ case NS_FORM_INPUT_WEEK:
+ case NS_FORM_INPUT_DATETIME_LOCAL:
+ return true;
+#ifdef DEBUG
+ case NS_FORM_INPUT_RESET:
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_IMAGE:
+ case NS_FORM_INPUT_BUTTON:
+ case NS_FORM_INPUT_HIDDEN:
+ case NS_FORM_INPUT_RADIO:
+ case NS_FORM_INPUT_CHECKBOX:
+ case NS_FORM_INPUT_FILE:
+ case NS_FORM_INPUT_TEXT:
+ case NS_FORM_INPUT_PASSWORD:
+ case NS_FORM_INPUT_SEARCH:
+ case NS_FORM_INPUT_TEL:
+ case NS_FORM_INPUT_EMAIL:
+ case NS_FORM_INPUT_URL:
+ case NS_FORM_INPUT_COLOR:
+ return false;
+ default:
+ NS_NOTYETIMPLEMENTED("Unexpected input type in DoesRequiredApply()");
+ return false;
+#else // DEBUG
+ default:
+ return false;
+#endif // DEBUG
+ }
+}
+
+bool
+HTMLInputElement::DoesAutocompleteApply() const
+{
+ switch (mType)
+ {
+ case NS_FORM_INPUT_HIDDEN:
+ case NS_FORM_INPUT_TEXT:
+ case NS_FORM_INPUT_SEARCH:
+ case NS_FORM_INPUT_URL:
+ case NS_FORM_INPUT_TEL:
+ case NS_FORM_INPUT_EMAIL:
+ case NS_FORM_INPUT_PASSWORD:
+ case NS_FORM_INPUT_DATE:
+ case NS_FORM_INPUT_TIME:
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_RANGE:
+ case NS_FORM_INPUT_COLOR:
+ case NS_FORM_INPUT_MONTH:
+ case NS_FORM_INPUT_WEEK:
+ case NS_FORM_INPUT_DATETIME_LOCAL:
+ return true;
+#ifdef DEBUG
+ case NS_FORM_INPUT_RESET:
+ case NS_FORM_INPUT_SUBMIT:
+ case NS_FORM_INPUT_IMAGE:
+ case NS_FORM_INPUT_BUTTON:
+ case NS_FORM_INPUT_RADIO:
+ case NS_FORM_INPUT_CHECKBOX:
+ case NS_FORM_INPUT_FILE:
+ return false;
+ default:
+ NS_NOTYETIMPLEMENTED("Unexpected input type in DoesAutocompleteApply()");
+ return false;
+#else // DEBUG
+ default:
+ return false;
+#endif // DEBUG
+ }
+}
+
+Decimal
+HTMLInputElement::GetStep() const
+{
+ MOZ_ASSERT(DoesStepApply(), "GetStep() can only be called if @step applies");
+
+ if (!HasAttr(kNameSpaceID_None, nsGkAtoms::step)) {
+ return GetDefaultStep() * GetStepScaleFactor();
+ }
+
+ nsAutoString stepStr;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::step, stepStr);
+
+ if (stepStr.LowerCaseEqualsLiteral("any")) {
+ // The element can't suffer from step mismatch if there is no step.
+ return kStepAny;
+ }
+
+ Decimal step = StringToDecimal(stepStr);
+ if (!step.isFinite() || step <= Decimal(0)) {
+ step = GetDefaultStep();
+ }
+
+ // For input type=date, we round the step value to have a rounded day.
+ if (mType == NS_FORM_INPUT_DATE || mType == NS_FORM_INPUT_MONTH ||
+ mType == NS_FORM_INPUT_WEEK) {
+ step = std::max(step.round(), Decimal(1));
+ }
+
+ return step * GetStepScaleFactor();
+}
+
+// nsIConstraintValidation
+
+NS_IMETHODIMP
+HTMLInputElement::SetCustomValidity(const nsAString& aError)
+{
+ nsIConstraintValidation::SetCustomValidity(aError);
+
+ UpdateState(true);
+
+ return NS_OK;
+}
+
+bool
+HTMLInputElement::IsTooLong()
+{
+ if (!mValueChanged ||
+ !mLastValueChangeWasInteractive ||
+ !MinOrMaxLengthApplies() ||
+ !HasAttr(kNameSpaceID_None, nsGkAtoms::maxlength)) {
+ return false;
+ }
+
+ int32_t maxLength = MaxLength();
+
+ // Maxlength of -1 means parsing error.
+ if (maxLength == -1) {
+ return false;
+ }
+
+ int32_t textLength = -1;
+ GetTextLength(&textLength);
+
+ return textLength > maxLength;
+}
+
+bool
+HTMLInputElement::IsTooShort()
+{
+ if (!mValueChanged ||
+ !mLastValueChangeWasInteractive ||
+ !MinOrMaxLengthApplies() ||
+ !HasAttr(kNameSpaceID_None, nsGkAtoms::minlength)) {
+ return false;
+ }
+
+ int32_t minLength = MinLength();
+
+ // Minlength of -1 means parsing error.
+ if (minLength == -1) {
+ return false;
+ }
+
+ int32_t textLength = -1;
+ GetTextLength(&textLength);
+
+ return textLength && textLength < minLength;
+}
+
+bool
+HTMLInputElement::IsValueMissing() const
+{
+ // Should use UpdateValueMissingValidityStateForRadio() for type radio.
+ MOZ_ASSERT(mType != NS_FORM_INPUT_RADIO);
+
+ if (!HasAttr(kNameSpaceID_None, nsGkAtoms::required) ||
+ !DoesRequiredApply()) {
+ return false;
+ }
+
+ if (!IsMutable()) {
+ return false;
+ }
+
+ switch (GetValueMode()) {
+ case VALUE_MODE_VALUE:
+ return IsValueEmpty();
+
+ case VALUE_MODE_FILENAME:
+ return GetFilesOrDirectoriesInternal().IsEmpty();
+
+ case VALUE_MODE_DEFAULT_ON:
+ // This should not be used for type radio.
+ // See the MOZ_ASSERT at the beginning of the method.
+ return !mChecked;
+
+ case VALUE_MODE_DEFAULT:
+ default:
+ return false;
+ }
+}
+
+bool
+HTMLInputElement::HasTypeMismatch() const
+{
+ if (mType != NS_FORM_INPUT_EMAIL && mType != NS_FORM_INPUT_URL) {
+ return false;
+ }
+
+ nsAutoString value;
+ NS_ENSURE_SUCCESS(GetValueInternal(value), false);
+
+ if (value.IsEmpty()) {
+ return false;
+ }
+
+ if (mType == NS_FORM_INPUT_EMAIL) {
+ return HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)
+ ? !IsValidEmailAddressList(value) : !IsValidEmailAddress(value);
+ } else if (mType == NS_FORM_INPUT_URL) {
+ /**
+ * TODO:
+ * The URL is not checked as the HTML5 specifications want it to be because
+ * there is no code to check for a valid URI/IRI according to 3986 and 3987
+ * RFC's at the moment, see bug 561586.
+ *
+ * RFC 3987 (IRI) implementation: bug 42899
+ *
+ * HTML5 specifications:
+ * http://dev.w3.org/html5/spec/infrastructure.html#valid-url
+ */
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService();
+ nsCOMPtr<nsIURI> uri;
+
+ return !NS_SUCCEEDED(ioService->NewURI(NS_ConvertUTF16toUTF8(value), nullptr,
+ nullptr, getter_AddRefs(uri)));
+ }
+
+ return false;
+}
+
+bool
+HTMLInputElement::HasPatternMismatch() const
+{
+ if (!DoesPatternApply() ||
+ !HasAttr(kNameSpaceID_None, nsGkAtoms::pattern)) {
+ return false;
+ }
+
+ nsAutoString pattern;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::pattern, pattern);
+
+ nsAutoString value;
+ NS_ENSURE_SUCCESS(GetValueInternal(value), false);
+
+ if (value.IsEmpty()) {
+ return false;
+ }
+
+ nsIDocument* doc = OwnerDoc();
+
+ return !nsContentUtils::IsPatternMatching(value, pattern, doc);
+}
+
+bool
+HTMLInputElement::IsRangeOverflow() const
+{
+ // TODO: this is temporary until bug 888331 is fixed.
+ if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
+ return false;
+ }
+
+ Decimal maximum = GetMaximum();
+ if (maximum.isNaN()) {
+ return false;
+ }
+
+ Decimal value = GetValueAsDecimal();
+ if (value.isNaN()) {
+ return false;
+ }
+
+ return value > maximum;
+}
+
+bool
+HTMLInputElement::IsRangeUnderflow() const
+{
+ // TODO: this is temporary until bug 888331 is fixed.
+ if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
+ return false;
+ }
+
+ Decimal minimum = GetMinimum();
+ if (minimum.isNaN()) {
+ return false;
+ }
+
+ Decimal value = GetValueAsDecimal();
+ if (value.isNaN()) {
+ return false;
+ }
+
+ return value < minimum;
+}
+
+bool
+HTMLInputElement::HasStepMismatch(bool aUseZeroIfValueNaN) const
+{
+ if (!DoesStepApply()) {
+ return false;
+ }
+
+ Decimal value = GetValueAsDecimal();
+ if (value.isNaN()) {
+ if (aUseZeroIfValueNaN) {
+ value = Decimal(0);
+ } else {
+ // The element can't suffer from step mismatch if it's value isn't a number.
+ return false;
+ }
+ }
+
+ Decimal step = GetStep();
+ if (step == kStepAny) {
+ return false;
+ }
+
+ // Value has to be an integral multiple of step.
+ return NS_floorModulo(value - GetStepBase(), step) != Decimal(0);
+}
+
+/**
+ * Takes aEmail and attempts to convert everything after the first "@"
+ * character (if anything) to punycode before returning the complete result via
+ * the aEncodedEmail out-param. The aIndexOfAt out-param is set to the index of
+ * the "@" character.
+ *
+ * If no "@" is found in aEmail, aEncodedEmail is simply set to aEmail and
+ * the aIndexOfAt out-param is set to kNotFound.
+ *
+ * Returns true in all cases unless an attempt to punycode encode fails. If
+ * false is returned, aEncodedEmail has not been set.
+ *
+ * This function exists because ConvertUTF8toACE() splits on ".", meaning that
+ * for 'user.name@sld.tld' it would treat "name@sld" as a label. We want to
+ * encode the domain part only.
+ */
+static bool PunycodeEncodeEmailAddress(const nsAString& aEmail,
+ nsAutoCString& aEncodedEmail,
+ uint32_t* aIndexOfAt)
+{
+ nsAutoCString value = NS_ConvertUTF16toUTF8(aEmail);
+ *aIndexOfAt = (uint32_t)value.FindChar('@');
+
+ if (*aIndexOfAt == (uint32_t)kNotFound ||
+ *aIndexOfAt == value.Length() - 1) {
+ aEncodedEmail = value;
+ return true;
+ }
+
+ nsCOMPtr<nsIIDNService> idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID);
+ if (!idnSrv) {
+ NS_ERROR("nsIIDNService isn't present!");
+ return false;
+ }
+
+ uint32_t indexOfDomain = *aIndexOfAt + 1;
+
+ const nsDependentCSubstring domain = Substring(value, indexOfDomain);
+ bool ace;
+ if (NS_SUCCEEDED(idnSrv->IsACE(domain, &ace)) && !ace) {
+ nsAutoCString domainACE;
+ if (NS_FAILED(idnSrv->ConvertUTF8toACE(domain, domainACE))) {
+ return false;
+ }
+ value.Replace(indexOfDomain, domain.Length(), domainACE);
+ }
+
+ aEncodedEmail = value;
+ return true;
+}
+
+bool
+HTMLInputElement::HasBadInput() const
+{
+ if (mType == NS_FORM_INPUT_NUMBER) {
+ nsAutoString value;
+ GetValueInternal(value);
+ if (!value.IsEmpty()) {
+ // The input can't be bad, otherwise it would have been sanitized to the
+ // empty string.
+ NS_ASSERTION(!GetValueAsDecimal().isNaN(), "Should have sanitized");
+ return false;
+ }
+ nsNumberControlFrame* numberControlFrame =
+ do_QueryFrame(GetPrimaryFrame());
+ if (numberControlFrame &&
+ !numberControlFrame->AnonTextControlIsEmpty()) {
+ // The input the user entered failed to parse as a number.
+ return true;
+ }
+ return false;
+ }
+ if (mType == NS_FORM_INPUT_EMAIL) {
+ // With regards to suffering from bad input the spec says that only the
+ // punycode conversion works, so we don't care whether the email address is
+ // valid or not here. (If the email address is invalid then we will be
+ // suffering from a type mismatch.)
+ nsAutoString value;
+ nsAutoCString unused;
+ uint32_t unused2;
+ NS_ENSURE_SUCCESS(GetValueInternal(value), false);
+ HTMLSplitOnSpacesTokenizer tokenizer(value, ',');
+ while (tokenizer.hasMoreTokens()) {
+ if (!PunycodeEncodeEmailAddress(tokenizer.nextToken(), unused, &unused2)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return false;
+}
+
+void
+HTMLInputElement::UpdateTooLongValidityState()
+{
+ SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong());
+}
+
+void
+HTMLInputElement::UpdateTooShortValidityState()
+{
+ SetValidityState(VALIDITY_STATE_TOO_SHORT, IsTooShort());
+}
+
+void
+HTMLInputElement::UpdateValueMissingValidityStateForRadio(bool aIgnoreSelf)
+{
+ bool notify = mDoneCreating;
+ nsCOMPtr<nsIDOMHTMLInputElement> selection = GetSelectedRadioButton();
+
+ aIgnoreSelf = aIgnoreSelf || !IsMutable();
+
+ // If there is no selection, that might mean the radio is not in a group.
+ // In that case, we can look for the checked state of the radio.
+ bool selected = selection || (!aIgnoreSelf && mChecked);
+ bool required = !aIgnoreSelf && HasAttr(kNameSpaceID_None, nsGkAtoms::required);
+ bool valueMissing = false;
+
+ nsCOMPtr<nsIRadioGroupContainer> container = GetRadioGroupContainer();
+
+ if (!container) {
+ SetValidityState(VALIDITY_STATE_VALUE_MISSING,
+ IsMutable() && required && !selected);
+ return;
+ }
+
+ nsAutoString name;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::name, name);
+
+ // If the current radio is required and not ignored, we can assume the entire
+ // group is required.
+ if (!required) {
+ required = (aIgnoreSelf && HasAttr(kNameSpaceID_None, nsGkAtoms::required))
+ ? container->GetRequiredRadioCount(name) - 1
+ : container->GetRequiredRadioCount(name);
+ }
+
+ valueMissing = required && !selected;
+
+ if (container->GetValueMissingState(name) != valueMissing) {
+ container->SetValueMissingState(name, valueMissing);
+
+ SetValidityState(VALIDITY_STATE_VALUE_MISSING, valueMissing);
+
+ // nsRadioSetValueMissingState will call ContentStateChanged while visiting.
+ nsAutoScriptBlocker scriptBlocker;
+ nsCOMPtr<nsIRadioVisitor> visitor =
+ new nsRadioSetValueMissingState(this, valueMissing, notify);
+ VisitGroup(visitor, notify);
+ }
+}
+
+void
+HTMLInputElement::UpdateValueMissingValidityState()
+{
+ if (mType == NS_FORM_INPUT_RADIO) {
+ UpdateValueMissingValidityStateForRadio(false);
+ return;
+ }
+
+ SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
+}
+
+void
+HTMLInputElement::UpdateTypeMismatchValidityState()
+{
+ SetValidityState(VALIDITY_STATE_TYPE_MISMATCH, HasTypeMismatch());
+}
+
+void
+HTMLInputElement::UpdatePatternMismatchValidityState()
+{
+ SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH, HasPatternMismatch());
+}
+
+void
+HTMLInputElement::UpdateRangeOverflowValidityState()
+{
+ SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW, IsRangeOverflow());
+}
+
+void
+HTMLInputElement::UpdateRangeUnderflowValidityState()
+{
+ SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW, IsRangeUnderflow());
+}
+
+void
+HTMLInputElement::UpdateStepMismatchValidityState()
+{
+ SetValidityState(VALIDITY_STATE_STEP_MISMATCH, HasStepMismatch());
+}
+
+void
+HTMLInputElement::UpdateBadInputValidityState()
+{
+ SetValidityState(VALIDITY_STATE_BAD_INPUT, HasBadInput());
+}
+
+void
+HTMLInputElement::UpdateAllValidityStates(bool aNotify)
+{
+ bool validBefore = IsValid();
+ UpdateTooLongValidityState();
+ UpdateTooShortValidityState();
+ UpdateValueMissingValidityState();
+ UpdateTypeMismatchValidityState();
+ UpdatePatternMismatchValidityState();
+ UpdateRangeOverflowValidityState();
+ UpdateRangeUnderflowValidityState();
+ UpdateStepMismatchValidityState();
+ UpdateBadInputValidityState();
+
+ if (validBefore != IsValid()) {
+ UpdateState(aNotify);
+ }
+}
+
+void
+HTMLInputElement::UpdateBarredFromConstraintValidation()
+{
+ SetBarredFromConstraintValidation(mType == NS_FORM_INPUT_HIDDEN ||
+ mType == NS_FORM_INPUT_BUTTON ||
+ mType == NS_FORM_INPUT_RESET ||
+ HasAttr(kNameSpaceID_None, nsGkAtoms::readonly) ||
+ IsDisabled());
+}
+
+void
+HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
+ ErrorResult& aRv)
+{
+ aRv = GetValidationMessage(aValidationMessage);
+}
+
+nsresult
+HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
+ ValidityStateType aType)
+{
+ nsresult rv = NS_OK;
+
+ switch (aType)
+ {
+ case VALIDITY_STATE_TOO_LONG:
+ {
+ nsXPIDLString message;
+ int32_t maxLength = MaxLength();
+ int32_t textLength = -1;
+ nsAutoString strMaxLength;
+ nsAutoString strTextLength;
+
+ GetTextLength(&textLength);
+
+ strMaxLength.AppendInt(maxLength);
+ strTextLength.AppendInt(textLength);
+
+ const char16_t* params[] = { strMaxLength.get(), strTextLength.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationTextTooLong",
+ params, message);
+ aValidationMessage = message;
+ break;
+ }
+ case VALIDITY_STATE_TOO_SHORT:
+ {
+ nsXPIDLString message;
+ int32_t minLength = MinLength();
+ int32_t textLength = -1;
+ nsAutoString strMinLength;
+ nsAutoString strTextLength;
+
+ GetTextLength(&textLength);
+
+ strMinLength.AppendInt(minLength);
+ strTextLength.AppendInt(textLength);
+
+ const char16_t* params[] = { strMinLength.get(), strTextLength.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationTextTooShort",
+ params, message);
+ aValidationMessage = message;
+ break;
+ }
+ case VALIDITY_STATE_VALUE_MISSING:
+ {
+ nsXPIDLString message;
+ nsAutoCString key;
+ switch (mType)
+ {
+ case NS_FORM_INPUT_FILE:
+ key.AssignLiteral("FormValidationFileMissing");
+ break;
+ case NS_FORM_INPUT_CHECKBOX:
+ key.AssignLiteral("FormValidationCheckboxMissing");
+ break;
+ case NS_FORM_INPUT_RADIO:
+ key.AssignLiteral("FormValidationRadioMissing");
+ break;
+ case NS_FORM_INPUT_NUMBER:
+ key.AssignLiteral("FormValidationBadInputNumber");
+ break;
+ default:
+ key.AssignLiteral("FormValidationValueMissing");
+ }
+ rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ key.get(), message);
+ aValidationMessage = message;
+ break;
+ }
+ case VALIDITY_STATE_TYPE_MISMATCH:
+ {
+ nsXPIDLString message;
+ nsAutoCString key;
+ if (mType == NS_FORM_INPUT_EMAIL) {
+ key.AssignLiteral("FormValidationInvalidEmail");
+ } else if (mType == NS_FORM_INPUT_URL) {
+ key.AssignLiteral("FormValidationInvalidURL");
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+ rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ key.get(), message);
+ aValidationMessage = message;
+ break;
+ }
+ case VALIDITY_STATE_PATTERN_MISMATCH:
+ {
+ nsXPIDLString message;
+ nsAutoString title;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::title, title);
+ if (title.IsEmpty()) {
+ rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationPatternMismatch",
+ message);
+ } else {
+ if (title.Length() > nsIConstraintValidation::sContentSpecifiedMaxLengthMessage) {
+ title.Truncate(nsIConstraintValidation::sContentSpecifiedMaxLengthMessage);
+ }
+ const char16_t* params[] = { title.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationPatternMismatchWithTitle",
+ params, message);
+ }
+ aValidationMessage = message;
+ break;
+ }
+ case VALIDITY_STATE_RANGE_OVERFLOW:
+ {
+ static const char kNumberOverTemplate[] = "FormValidationNumberRangeOverflow";
+ static const char kDateOverTemplate[] = "FormValidationDateRangeOverflow";
+ static const char kTimeOverTemplate[] = "FormValidationTimeRangeOverflow";
+
+ const char* msgTemplate;
+ nsXPIDLString message;
+
+ nsAutoString maxStr;
+ if (mType == NS_FORM_INPUT_NUMBER ||
+ mType == NS_FORM_INPUT_RANGE) {
+ msgTemplate = kNumberOverTemplate;
+
+ //We want to show the value as parsed when it's a number
+ Decimal maximum = GetMaximum();
+ MOZ_ASSERT(!maximum.isNaN());
+
+ char buf[32];
+ DebugOnly<bool> ok = maximum.toString(buf, ArrayLength(buf));
+ maxStr.AssignASCII(buf);
+ MOZ_ASSERT(ok, "buf not big enough");
+ } else if (IsDateTimeInputType(mType)) {
+ msgTemplate = mType == NS_FORM_INPUT_TIME ? kTimeOverTemplate : kDateOverTemplate;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
+ } else {
+ msgTemplate = kNumberOverTemplate;
+ NS_NOTREACHED("Unexpected input type");
+ }
+
+ const char16_t* params[] = { maxStr.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ msgTemplate,
+ params, message);
+ aValidationMessage = message;
+ break;
+ }
+ case VALIDITY_STATE_RANGE_UNDERFLOW:
+ {
+ static const char kNumberUnderTemplate[] = "FormValidationNumberRangeUnderflow";
+ static const char kDateUnderTemplate[] = "FormValidationDateRangeUnderflow";
+ static const char kTimeUnderTemplate[] = "FormValidationTimeRangeUnderflow";
+
+ const char* msgTemplate;
+ nsXPIDLString message;
+
+ nsAutoString minStr;
+ if (mType == NS_FORM_INPUT_NUMBER ||
+ mType == NS_FORM_INPUT_RANGE) {
+ msgTemplate = kNumberUnderTemplate;
+
+ Decimal minimum = GetMinimum();
+ MOZ_ASSERT(!minimum.isNaN());
+
+ char buf[32];
+ DebugOnly<bool> ok = minimum.toString(buf, ArrayLength(buf));
+ minStr.AssignASCII(buf);
+ MOZ_ASSERT(ok, "buf not big enough");
+ } else if (IsDateTimeInputType(mType)) {
+ msgTemplate = mType == NS_FORM_INPUT_TIME ? kTimeUnderTemplate : kDateUnderTemplate;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
+ } else {
+ msgTemplate = kNumberUnderTemplate;
+ NS_NOTREACHED("Unexpected input type");
+ }
+
+ const char16_t* params[] = { minStr.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ msgTemplate,
+ params, message);
+ aValidationMessage = message;
+ break;
+ }
+ case VALIDITY_STATE_STEP_MISMATCH:
+ {
+ nsXPIDLString message;
+
+ Decimal value = GetValueAsDecimal();
+ MOZ_ASSERT(!value.isNaN());
+
+ Decimal step = GetStep();
+ MOZ_ASSERT(step != kStepAny && step > Decimal(0));
+
+ Decimal stepBase = GetStepBase();
+
+ Decimal valueLow = value - NS_floorModulo(value - stepBase, step);
+ Decimal valueHigh = value + step - NS_floorModulo(value - stepBase, step);
+
+ Decimal maximum = GetMaximum();
+
+ if (maximum.isNaN() || valueHigh <= maximum) {
+ nsAutoString valueLowStr, valueHighStr;
+ ConvertNumberToString(valueLow, valueLowStr);
+ ConvertNumberToString(valueHigh, valueHighStr);
+
+ if (valueLowStr.Equals(valueHighStr)) {
+ const char16_t* params[] = { valueLowStr.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationStepMismatchOneValue",
+ params, message);
+ } else {
+ const char16_t* params[] = { valueLowStr.get(), valueHighStr.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationStepMismatch",
+ params, message);
+ }
+ } else {
+ nsAutoString valueLowStr;
+ ConvertNumberToString(valueLow, valueLowStr);
+
+ const char16_t* params[] = { valueLowStr.get() };
+ rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ "FormValidationStepMismatchOneValue",
+ params, message);
+ }
+
+ aValidationMessage = message;
+ break;
+ }
+ case VALIDITY_STATE_BAD_INPUT:
+ {
+ nsXPIDLString message;
+ nsAutoCString key;
+ if (mType == NS_FORM_INPUT_NUMBER) {
+ key.AssignLiteral("FormValidationBadInputNumber");
+ } else if (mType == NS_FORM_INPUT_EMAIL) {
+ key.AssignLiteral("FormValidationInvalidEmail");
+ } else {
+ return NS_ERROR_UNEXPECTED;
+ }
+ rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
+ key.get(), message);
+ aValidationMessage = message;
+ break;
+ }
+ default:
+ rv = nsIConstraintValidation::GetValidationMessage(aValidationMessage, aType);
+ }
+
+ return rv;
+}
+
+//static
+bool
+HTMLInputElement::IsValidEmailAddressList(const nsAString& aValue)
+{
+ HTMLSplitOnSpacesTokenizer tokenizer(aValue, ',');
+
+ while (tokenizer.hasMoreTokens()) {
+ if (!IsValidEmailAddress(tokenizer.nextToken())) {
+ return false;
+ }
+ }
+
+ return !tokenizer.separatorAfterCurrentToken();
+}
+
+//static
+bool
+HTMLInputElement::IsValidEmailAddress(const nsAString& aValue)
+{
+ // Email addresses can't be empty and can't end with a '.' or '-'.
+ if (aValue.IsEmpty() || aValue.Last() == '.' || aValue.Last() == '-') {
+ return false;
+ }
+
+ uint32_t atPos;
+ nsAutoCString value;
+ if (!PunycodeEncodeEmailAddress(aValue, value, &atPos) ||
+ atPos == (uint32_t)kNotFound || atPos == 0 || atPos == value.Length() - 1) {
+ // Could not encode, or "@" was not found, or it was at the start or end
+ // of the input - in all cases, not a valid email address.
+ return false;
+ }
+
+ uint32_t length = value.Length();
+ uint32_t i = 0;
+
+ // Parsing the username.
+ for (; i < atPos; ++i) {
+ char16_t c = value[i];
+
+ // The username characters have to be in this list to be valid.
+ if (!(nsCRT::IsAsciiAlpha(c) || nsCRT::IsAsciiDigit(c) ||
+ c == '.' || c == '!' || c == '#' || c == '$' || c == '%' ||
+ c == '&' || c == '\''|| c == '*' || c == '+' || c == '-' ||
+ c == '/' || c == '=' || c == '?' || c == '^' || c == '_' ||
+ c == '`' || c == '{' || c == '|' || c == '}' || c == '~' )) {
+ return false;
+ }
+ }
+
+ // Skip the '@'.
+ ++i;
+
+ // The domain name can't begin with a dot or a dash.
+ if (value[i] == '.' || value[i] == '-') {
+ return false;
+ }
+
+ // Parsing the domain name.
+ for (; i < length; ++i) {
+ char16_t c = value[i];
+
+ if (c == '.') {
+ // A dot can't follow a dot or a dash.
+ if (value[i-1] == '.' || value[i-1] == '-') {
+ return false;
+ }
+ } else if (c == '-'){
+ // A dash can't follow a dot.
+ if (value[i-1] == '.') {
+ return false;
+ }
+ } else if (!(nsCRT::IsAsciiAlpha(c) || nsCRT::IsAsciiDigit(c) ||
+ c == '-')) {
+ // The domain characters have to be in this list to be valid.
+ return false;
+ }
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP_(bool)
+HTMLInputElement::IsSingleLineTextControl() const
+{
+ return IsSingleLineTextControl(false);
+}
+
+NS_IMETHODIMP_(bool)
+HTMLInputElement::IsTextArea() const
+{
+ return false;
+}
+
+NS_IMETHODIMP_(bool)
+HTMLInputElement::IsPlainTextControl() const
+{
+ // need to check our HTML attribute and/or CSS.
+ return true;
+}
+
+NS_IMETHODIMP_(bool)
+HTMLInputElement::IsPasswordTextControl() const
+{
+ return mType == NS_FORM_INPUT_PASSWORD;
+}
+
+NS_IMETHODIMP_(int32_t)
+HTMLInputElement::GetCols()
+{
+ // Else we know (assume) it is an input with size attr
+ const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::size);
+ if (attr && attr->Type() == nsAttrValue::eInteger) {
+ int32_t cols = attr->GetIntegerValue();
+ if (cols > 0) {
+ return cols;
+ }
+ }
+
+ return DEFAULT_COLS;
+}
+
+NS_IMETHODIMP_(int32_t)
+HTMLInputElement::GetWrapCols()
+{
+ return 0; // only textarea's can have wrap cols
+}
+
+NS_IMETHODIMP_(int32_t)
+HTMLInputElement::GetRows()
+{
+ return DEFAULT_ROWS;
+}
+
+NS_IMETHODIMP_(void)
+HTMLInputElement::GetDefaultValueFromContent(nsAString& aValue)
+{
+ nsTextEditorState *state = GetEditorState();
+ if (state) {
+ GetDefaultValue(aValue);
+ // This is called by the frame to show the value.
+ // We have to sanitize it when needed.
+ if (mDoneCreating) {
+ SanitizeValue(aValue);
+ }
+ }
+}
+
+NS_IMETHODIMP_(bool)
+HTMLInputElement::ValueChanged() const
+{
+ return mValueChanged;
+}
+
+NS_IMETHODIMP_(void)
+HTMLInputElement::GetTextEditorValue(nsAString& aValue,
+ bool aIgnoreWrap) const
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ state->GetValue(aValue, aIgnoreWrap);
+ }
+}
+
+NS_IMETHODIMP_(void)
+HTMLInputElement::InitializeKeyboardEventListeners()
+{
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ state->InitializeKeyboardEventListeners();
+ }
+}
+
+NS_IMETHODIMP_(void)
+HTMLInputElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange)
+{
+ mLastValueChangeWasInteractive = aWasInteractiveUserChange;
+
+ UpdateAllValidityStates(aNotify);
+
+ if (HasDirAuto()) {
+ SetDirectionIfAuto(true, aNotify);
+ }
+
+ // :placeholder-shown pseudo-class may change when the value changes.
+ // However, we don't want to waste cycles if the state doesn't apply.
+ if (PlaceholderApplies() &&
+ HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) {
+ UpdateState(aNotify);
+ }
+}
+
+NS_IMETHODIMP_(bool)
+HTMLInputElement::HasCachedSelection()
+{
+ bool isCached = false;
+ nsTextEditorState* state = GetEditorState();
+ if (state) {
+ isCached = state->IsSelectionCached() &&
+ state->HasNeverInitializedBefore() &&
+ !state->GetSelectionProperties().IsDefault();
+ if (isCached) {
+ state->WillInitEagerly();
+ }
+ }
+ return isCached;
+}
+
+void
+HTMLInputElement::FieldSetDisabledChanged(bool aNotify)
+{
+ UpdateValueMissingValidityState();
+ UpdateBarredFromConstraintValidation();
+
+ nsGenericHTMLFormElementWithState::FieldSetDisabledChanged(aNotify);
+}
+
+void
+HTMLInputElement::SetFilePickerFiltersFromAccept(nsIFilePicker* filePicker)
+{
+ // We always add |filterAll|
+ filePicker->AppendFilters(nsIFilePicker::filterAll);
+
+ NS_ASSERTION(HasAttr(kNameSpaceID_None, nsGkAtoms::accept),
+ "You should not call SetFilePickerFiltersFromAccept if the"
+ " element has no accept attribute!");
+
+ // Services to retrieve image/*, audio/*, video/* filters
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::services::GetStringBundleService();
+ if (!stringService) {
+ return;
+ }
+ nsCOMPtr<nsIStringBundle> filterBundle;
+ if (NS_FAILED(stringService->CreateBundle("chrome://global/content/filepicker.properties",
+ getter_AddRefs(filterBundle)))) {
+ return;
+ }
+
+ // Service to retrieve mime type information for mime types filters
+ nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
+ if (!mimeService) {
+ return;
+ }
+
+ nsAutoString accept;
+ GetAttr(kNameSpaceID_None, nsGkAtoms::accept, accept);
+
+ HTMLSplitOnSpacesTokenizer tokenizer(accept, ',');
+
+ nsTArray<nsFilePickerFilter> filters;
+ nsString allExtensionsList;
+
+ bool allMimeTypeFiltersAreValid = true;
+ bool atLeastOneFileExtensionFilter = false;
+
+ // Retrieve all filters
+ while (tokenizer.hasMoreTokens()) {
+ const nsDependentSubstring& token = tokenizer.nextToken();
+
+ if (token.IsEmpty()) {
+ continue;
+ }
+
+ int32_t filterMask = 0;
+ nsString filterName;
+ nsString extensionListStr;
+
+ // First, check for image/audio/video filters...
+ if (token.EqualsLiteral("image/*")) {
+ filterMask = nsIFilePicker::filterImages;
+ filterBundle->GetStringFromName(u"imageFilter",
+ getter_Copies(extensionListStr));
+ } else if (token.EqualsLiteral("audio/*")) {
+ filterMask = nsIFilePicker::filterAudio;
+ filterBundle->GetStringFromName(u"audioFilter",
+ getter_Copies(extensionListStr));
+ } else if (token.EqualsLiteral("video/*")) {
+ filterMask = nsIFilePicker::filterVideo;
+ filterBundle->GetStringFromName(u"videoFilter",
+ getter_Copies(extensionListStr));
+ } else if (token.First() == '.') {
+ if (token.Contains(';') || token.Contains('*')) {
+ // Ignore this filter as it contains reserved characters
+ continue;
+ }
+ extensionListStr = NS_LITERAL_STRING("*") + token;
+ filterName = extensionListStr;
+ atLeastOneFileExtensionFilter = true;
+ } else {
+ //... if no image/audio/video filter is found, check mime types filters
+ nsCOMPtr<nsIMIMEInfo> mimeInfo;
+ if (NS_FAILED(mimeService->GetFromTypeAndExtension(
+ NS_ConvertUTF16toUTF8(token),
+ EmptyCString(), // No extension
+ getter_AddRefs(mimeInfo))) ||
+ !mimeInfo) {
+ allMimeTypeFiltersAreValid = false;
+ continue;
+ }
+
+ // Get a name for the filter: first try the description, then the mime type
+ // name if there is no description
+ mimeInfo->GetDescription(filterName);
+ if (filterName.IsEmpty()) {
+ nsCString mimeTypeName;
+ mimeInfo->GetType(mimeTypeName);
+ CopyUTF8toUTF16(mimeTypeName, filterName);
+ }
+
+ // Get extension list
+ nsCOMPtr<nsIUTF8StringEnumerator> extensions;
+ mimeInfo->GetFileExtensions(getter_AddRefs(extensions));
+
+ bool hasMore;
+ while (NS_SUCCEEDED(extensions->HasMore(&hasMore)) && hasMore) {
+ nsCString extension;
+ if (NS_FAILED(extensions->GetNext(extension))) {
+ continue;
+ }
+ if (!extensionListStr.IsEmpty()) {
+ extensionListStr.AppendLiteral("; ");
+ }
+ extensionListStr += NS_LITERAL_STRING("*.") +
+ NS_ConvertUTF8toUTF16(extension);
+ }
+ }
+
+ if (!filterMask && (extensionListStr.IsEmpty() || filterName.IsEmpty())) {
+ // No valid filter found
+ allMimeTypeFiltersAreValid = false;
+ continue;
+ }
+
+ // If we arrived here, that means we have a valid filter: let's create it
+ // and add it to our list, if no similar filter is already present
+ nsFilePickerFilter filter;
+ if (filterMask) {
+ filter = nsFilePickerFilter(filterMask);
+ } else {
+ filter = nsFilePickerFilter(filterName, extensionListStr);
+ }
+
+ if (!filters.Contains(filter)) {
+ if (!allExtensionsList.IsEmpty()) {
+ allExtensionsList.AppendLiteral("; ");
+ }
+ allExtensionsList += extensionListStr;
+ filters.AppendElement(filter);
+ }
+ }
+
+ // Remove similar filters
+ // Iterate over a copy, as we might modify the original filters list
+ nsTArray<nsFilePickerFilter> filtersCopy;
+ filtersCopy = filters;
+ for (uint32_t i = 0; i < filtersCopy.Length(); ++i) {
+ const nsFilePickerFilter& filterToCheck = filtersCopy[i];
+ if (filterToCheck.mFilterMask) {
+ continue;
+ }
+ for (uint32_t j = 0; j < filtersCopy.Length(); ++j) {
+ if (i == j) {
+ continue;
+ }
+ // Check if this filter's extension list is a substring of the other one.
+ // e.g. if filters are "*.jpeg" and "*.jpeg; *.jpg" the first one should
+ // be removed.
+ // Add an extra "; " to be sure the check will work and avoid cases like
+ // "*.xls" being a subtring of "*.xslx" while those are two differents
+ // filters and none should be removed.
+ if (FindInReadable(filterToCheck.mFilter + NS_LITERAL_STRING(";"),
+ filtersCopy[j].mFilter + NS_LITERAL_STRING(";"))) {
+ // We already have a similar, less restrictive filter (i.e.
+ // filterToCheck extensionList is just a subset of another filter
+ // extension list): remove this one
+ filters.RemoveElement(filterToCheck);
+ }
+ }
+ }
+
+ // Add "All Supported Types" filter
+ if (filters.Length() > 1) {
+ nsXPIDLString title;
+ nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "AllSupportedTypes", title);
+ filePicker->AppendFilter(title, allExtensionsList);
+ }
+
+ // Add each filter
+ for (uint32_t i = 0; i < filters.Length(); ++i) {
+ const nsFilePickerFilter& filter = filters[i];
+ if (filter.mFilterMask) {
+ filePicker->AppendFilters(filter.mFilterMask);
+ } else {
+ filePicker->AppendFilter(filter.mTitle, filter.mFilter);
+ }
+ }
+
+ if (filters.Length() >= 1 &&
+ (allMimeTypeFiltersAreValid || atLeastOneFileExtensionFilter)) {
+ // |filterAll| will always use index=0 so we need to set index=1 as the
+ // current filter.
+ filePicker->SetFilterIndex(1);
+ }
+}
+
+Decimal
+HTMLInputElement::GetStepScaleFactor() const
+{
+ MOZ_ASSERT(DoesStepApply());
+
+ switch (mType) {
+ case NS_FORM_INPUT_DATE:
+ return kStepScaleFactorDate;
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_RANGE:
+ return kStepScaleFactorNumberRange;
+ case NS_FORM_INPUT_TIME:
+ return kStepScaleFactorTime;
+ case NS_FORM_INPUT_MONTH:
+ return kStepScaleFactorMonth;
+ case NS_FORM_INPUT_WEEK:
+ return kStepScaleFactorWeek;
+ default:
+ MOZ_ASSERT(false, "Unrecognized input type");
+ return Decimal::nan();
+ }
+}
+
+Decimal
+HTMLInputElement::GetDefaultStep() const
+{
+ MOZ_ASSERT(DoesStepApply());
+
+ switch (mType) {
+ case NS_FORM_INPUT_DATE:
+ case NS_FORM_INPUT_MONTH:
+ case NS_FORM_INPUT_WEEK:
+ case NS_FORM_INPUT_NUMBER:
+ case NS_FORM_INPUT_RANGE:
+ return kDefaultStep;
+ case NS_FORM_INPUT_TIME:
+ return kDefaultStepTime;
+ default:
+ MOZ_ASSERT(false, "Unrecognized input type");
+ return Decimal::nan();
+ }
+}
+
+void
+HTMLInputElement::UpdateValidityUIBits(bool aIsFocused)
+{
+ if (aIsFocused) {
+ // If the invalid UI is shown, we should show it while focusing (and
+ // update). Otherwise, we should not.
+ mCanShowInvalidUI = !IsValid() && ShouldShowValidityUI();
+
+ // If neither invalid UI nor valid UI is shown, we shouldn't show the valid
+ // UI while typing.
+ mCanShowValidUI = ShouldShowValidityUI();
+ } else {
+ mCanShowInvalidUI = true;
+ mCanShowValidUI = true;
+ }
+}
+
+void
+HTMLInputElement::UpdateHasRange()
+{
+ /*
+ * There is a range if min/max applies for the type and if the element
+ * currently have a valid min or max.
+ */
+
+ mHasRange = false;
+
+ // TODO: this is temporary until bug 888331 is fixed.
+ if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_DATETIME_LOCAL) {
+ return;
+ }
+
+ Decimal minimum = GetMinimum();
+ if (!minimum.isNaN()) {
+ mHasRange = true;
+ return;
+ }
+
+ Decimal maximum = GetMaximum();
+ if (!maximum.isNaN()) {
+ mHasRange = true;
+ return;
+ }
+}
+
+void
+HTMLInputElement::PickerClosed()
+{
+ mPickerRunning = false;
+}
+
+JSObject*
+HTMLInputElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLInputElementBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+HTMLInputElement::ClearGetFilesHelpers()
+{
+ if (mGetFilesRecursiveHelper) {
+ mGetFilesRecursiveHelper->Unlink();
+ mGetFilesRecursiveHelper = nullptr;
+ }
+
+ 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::UpdateEntries(const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories)
+{
+ MOZ_ASSERT(mEntries.IsEmpty());
+
+ nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
+ MOZ_ASSERT(global);
+
+ RefPtr<FileSystem> fs = FileSystem::Create(global);
+ if (NS_WARN_IF(!fs)) {
+ return;
+ }
+
+ Sequence<RefPtr<FileSystemEntry>> entries;
+ for (uint32_t i = 0; i < aFilesOrDirectories.Length(); ++i) {
+ RefPtr<FileSystemEntry> entry =
+ FileSystemEntry::Create(global, aFilesOrDirectories[i], fs);
+ MOZ_ASSERT(entry);
+
+ if (!entries.AppendElement(entry, fallible)) {
+ return;
+ }
+ }
+
+ // The root fileSystem is a DirectoryEntry object that contains only the
+ // dropped fileEntry and directoryEntry objects.
+ fs->CreateRoot(entries);
+
+ mEntries.SwapElements(entries);
+}
+
+void
+HTMLInputElement::GetWebkitEntries(nsTArray<RefPtr<FileSystemEntry>>& aSequence)
+{
+ Telemetry::Accumulate(Telemetry::BLINK_FILESYSTEM_USED, true);
+ aSequence.AppendElements(mEntries);
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#undef NS_ORIGINAL_CHECKED_VALUE