1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/HTMLInputElement.h"
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/AsyncEventDispatcher.h"
11 #include "mozilla/BasePrincipal.h"
12 #include "mozilla/DebugOnly.h"
13 #include "mozilla/Components.h"
14 #include "mozilla/dom/AutocompleteInfoBinding.h"
15 #include "mozilla/dom/BlobImpl.h"
16 #include "mozilla/dom/Directory.h"
17 #include "mozilla/dom/DocumentOrShadowRoot.h"
18 #include "mozilla/dom/ElementBinding.h"
19 #include "mozilla/dom/FileSystemUtils.h"
20 #include "mozilla/dom/FormData.h"
21 #include "mozilla/dom/GetFilesHelper.h"
22 #include "mozilla/dom/NumericInputTypes.h"
23 #include "mozilla/dom/WindowContext.h"
24 #include "mozilla/dom/InputType.h"
25 #include "mozilla/dom/UserActivation.h"
26 #include "mozilla/dom/MutationEventBinding.h"
27 #include "mozilla/dom/WheelEventBinding.h"
28 #include "mozilla/dom/WindowGlobalChild.h"
29 #include "mozilla/EventStateManager.h"
30 #include "mozilla/PresShell.h"
31 #include "mozilla/StaticPrefs_dom.h"
32 #include "mozilla/StaticPrefs_signon.h"
33 #include "mozilla/TextUtils.h"
34 #include "mozilla/Try.h"
35 #include "nsAttrValueInlines.h"
36 #include "nsCRTGlue.h"
37 #include "nsIFilePicker.h"
38 #include "nsNetUtil.h"
39 #include "nsQueryObject.h"
41 #include "HTMLDataListElement.h"
42 #include "HTMLFormSubmissionConstants.h"
43 #include "mozilla/Telemetry.h"
44 #include "nsBaseCommandController.h"
45 #include "nsIStringBundle.h"
46 #include "nsFocusManager.h"
47 #include "nsColorControlFrame.h"
48 #include "nsNumberControlFrame.h"
49 #include "nsSearchControlFrame.h"
50 #include "nsPIDOMWindow.h"
51 #include "nsRepeatService.h"
52 #include "nsContentCID.h"
53 #include "mozilla/dom/ProgressEvent.h"
54 #include "nsGkAtoms.h"
55 #include "nsStyleConsts.h"
56 #include "nsPresContext.h"
57 #include "nsIFormControl.h"
58 #include "mozilla/dom/Document.h"
59 #include "mozilla/dom/HTMLDataListElement.h"
60 #include "mozilla/dom/HTMLOptionElement.h"
61 #include "nsIFormControlFrame.h"
62 #include "nsITextControlFrame.h"
64 #include "nsRangeFrame.h"
66 #include "nsIEditor.h"
67 #include "nsIPromptCollection.h"
69 #include "mozilla/PresState.h"
70 #include "nsLinebreakConverter.h" //to strip out carriage returns
71 #include "nsReadableUtils.h"
72 #include "nsUnicharUtils.h"
73 #include "nsLayoutUtils.h"
74 #include "nsVariant.h"
76 #include "mozilla/ContentEvents.h"
77 #include "mozilla/EventDispatcher.h"
78 #include "mozilla/MappedDeclarationsBuilder.h"
79 #include "mozilla/InternalMutationEvent.h"
80 #include "mozilla/TextControlState.h"
81 #include "mozilla/TextEditor.h"
82 #include "mozilla/TextEvents.h"
83 #include "mozilla/TouchEvents.h"
88 #include "mozilla/dom/RadioGroupContainer.h"
89 #include "nsIRadioVisitor.h"
90 #include "nsRadioVisitor.h"
93 #include "mozilla/dom/FileSystemEntry.h"
94 #include "mozilla/dom/FileSystem.h"
95 #include "mozilla/dom/File.h"
96 #include "mozilla/dom/FileList.h"
98 #include "nsDirectoryServiceDefs.h"
99 #include "nsIContentPrefService2.h"
100 #include "nsIMIMEService.h"
101 #include "nsIObserverService.h"
104 #include "nsImageLoadingContent.h"
105 #include "imgRequestProxy.h"
107 #include "mozAutoDocUpdate.h"
108 #include "nsContentCreatorFunctions.h"
109 #include "nsContentUtils.h"
110 #include "mozilla/dom/DirectionalityUtils.h"
112 #include "mozilla/LookAndFeel.h"
113 #include "mozilla/Preferences.h"
114 #include "mozilla/MathAlgorithms.h"
118 #include "nsIColorPicker.h"
119 #include "nsIStringEnumerator.h"
120 #include "HTMLSplitOnSpacesTokenizer.h"
121 #include "nsIMIMEInfo.h"
122 #include "nsFrameSelection.h"
123 #include "nsXULControllers.h"
128 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Input
)
130 // XXX align=left, hspace, vspace, border? other nav4 attrs
132 namespace mozilla::dom
{
134 // First bits are needed for the control type.
135 #define NS_OUTER_ACTIVATE_EVENT (1 << 9)
136 #define NS_ORIGINAL_CHECKED_VALUE (1 << 10)
137 // (1 << 11 is unused)
138 #define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
139 #define NS_PRE_HANDLE_BLUR_EVENT (1 << 13)
140 #define NS_IN_SUBMIT_CLICK (1 << 15)
141 #define NS_CONTROL_TYPE(bits) \
142 ((bits) & ~(NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | \
143 NS_ORIGINAL_INDETERMINATE_VALUE | NS_PRE_HANDLE_BLUR_EVENT | \
146 // whether textfields should be selected once focused:
147 // -1: no, 1: yes, 0: uninitialized
148 static int32_t gSelectTextFieldOnFocus
;
149 UploadLastDir
* HTMLInputElement::gUploadLastDir
;
151 static const nsAttrValue::EnumTable kInputTypeTable
[] = {
152 {"button", FormControlType::InputButton
},
153 {"checkbox", FormControlType::InputCheckbox
},
154 {"color", FormControlType::InputColor
},
155 {"date", FormControlType::InputDate
},
156 {"datetime-local", FormControlType::InputDatetimeLocal
},
157 {"email", FormControlType::InputEmail
},
158 {"file", FormControlType::InputFile
},
159 {"hidden", FormControlType::InputHidden
},
160 {"reset", FormControlType::InputReset
},
161 {"image", FormControlType::InputImage
},
162 {"month", FormControlType::InputMonth
},
163 {"number", FormControlType::InputNumber
},
164 {"password", FormControlType::InputPassword
},
165 {"radio", FormControlType::InputRadio
},
166 {"range", FormControlType::InputRange
},
167 {"search", FormControlType::InputSearch
},
168 {"submit", FormControlType::InputSubmit
},
169 {"tel", FormControlType::InputTel
},
170 {"time", FormControlType::InputTime
},
171 {"url", FormControlType::InputUrl
},
172 {"week", FormControlType::InputWeek
},
173 // "text" must be last for ParseAttribute to work right. If you add things
174 // before it, please update kInputDefaultType.
175 {"text", FormControlType::InputText
},
178 // Default type is 'text'.
179 static const nsAttrValue::EnumTable
* kInputDefaultType
=
180 &kInputTypeTable
[ArrayLength(kInputTypeTable
) - 2];
182 static const nsAttrValue::EnumTable kCaptureTable
[] = {
183 {"user", nsIFilePicker::captureUser
},
184 {"environment", nsIFilePicker::captureEnv
},
185 {"", nsIFilePicker::captureDefault
},
186 {nullptr, nsIFilePicker::captureNone
}};
188 static const nsAttrValue::EnumTable
* kCaptureDefault
= &kCaptureTable
[2];
190 using namespace blink
;
192 constexpr Decimal
HTMLInputElement::kStepScaleFactorDate(86400000_d
);
193 constexpr Decimal
HTMLInputElement::kStepScaleFactorNumberRange(1_d
);
194 constexpr Decimal
HTMLInputElement::kStepScaleFactorTime(1000_d
);
195 constexpr Decimal
HTMLInputElement::kStepScaleFactorMonth(1_d
);
196 constexpr Decimal
HTMLInputElement::kStepScaleFactorWeek(7 * 86400000_d
);
197 constexpr Decimal
HTMLInputElement::kDefaultStepBase(0_d
);
198 constexpr Decimal
HTMLInputElement::kDefaultStepBaseWeek(-259200000_d
);
199 constexpr Decimal
HTMLInputElement::kDefaultStep(1_d
);
200 constexpr Decimal
HTMLInputElement::kDefaultStepTime(60_d
);
201 constexpr Decimal
HTMLInputElement::kStepAny(0_d
);
203 const double HTMLInputElement::kMinimumYear
= 1;
204 const double HTMLInputElement::kMaximumYear
= 275760;
205 const double HTMLInputElement::kMaximumWeekInMaximumYear
= 37;
206 const double HTMLInputElement::kMaximumDayInMaximumYear
= 13;
207 const double HTMLInputElement::kMaximumMonthInMaximumYear
= 9;
208 const double HTMLInputElement::kMaximumWeekInYear
= 53;
209 const double HTMLInputElement::kMsPerDay
= 24 * 60 * 60 * 1000;
211 // An helper class for the dispatching of the 'change' event.
212 // This class is used when the FilePicker finished its task (or when files and
213 // directories are set by some chrome/test only method).
214 // The task of this class is to postpone the dispatching of 'change' and 'input'
215 // events at the end of the exploration of the directories.
216 class DispatchChangeEventCallback final
: public GetFilesCallback
{
218 explicit DispatchChangeEventCallback(HTMLInputElement
* aInputElement
)
219 : mInputElement(aInputElement
) {
220 MOZ_ASSERT(aInputElement
);
223 virtual void Callback(
225 const FallibleTArray
<RefPtr
<BlobImpl
>>& aBlobImpls
) override
{
226 if (!mInputElement
->GetOwnerGlobal()) {
230 nsTArray
<OwningFileOrDirectory
> array
;
231 for (uint32_t i
= 0; i
< aBlobImpls
.Length(); ++i
) {
232 OwningFileOrDirectory
* element
= array
.AppendElement();
234 File::Create(mInputElement
->GetOwnerGlobal(), aBlobImpls
[i
]);
235 if (NS_WARN_IF(!file
)) {
239 element
->SetAsFile() = file
;
242 mInputElement
->SetFilesOrDirectories(array
, true);
243 Unused
<< NS_WARN_IF(NS_FAILED(DispatchEvents()));
246 MOZ_CAN_RUN_SCRIPT_BOUNDARY
247 nsresult
DispatchEvents() {
248 RefPtr
<HTMLInputElement
> inputElement(mInputElement
);
249 nsresult rv
= nsContentUtils::DispatchInputEvent(inputElement
);
250 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "Failed to dispatch input event");
251 mInputElement
->SetUserInteracted(true);
252 rv
= nsContentUtils::DispatchTrustedEvent(mInputElement
->OwnerDoc(),
253 mInputElement
, u
"change"_ns
,
254 CanBubble::eYes
, Cancelable::eNo
);
260 RefPtr
<HTMLInputElement
> mInputElement
;
263 struct HTMLInputElement::FileData
{
265 * The value of the input if it is a file input. This is the list of files or
266 * directories DOM objects used when uploading a file. It is vital that this
267 * is kept separate from mValue so that it won't be possible to 'leak' the
268 * value from a text-input to a file-input. Additionally, the logic for this
269 * value is kept as simple as possible to avoid accidental errors where the
270 * wrong filename is used. Therefor the list of filenames is always owned by
271 * this member, never by the frame. Whenever the frame wants to change the
272 * filename it has to call SetFilesOrDirectories to update this member.
274 nsTArray
<OwningFileOrDirectory
> mFilesOrDirectories
;
276 RefPtr
<GetFilesHelper
> mGetFilesRecursiveHelper
;
277 RefPtr
<GetFilesHelper
> mGetFilesNonRecursiveHelper
;
280 * Hack for bug 1086684: Stash the .value when we're a file picker.
282 nsString mFirstFilePath
;
284 RefPtr
<FileList
> mFileList
;
285 Sequence
<RefPtr
<FileSystemEntry
>> mEntries
;
287 nsString mStaticDocFileList
;
289 void ClearGetFilesHelpers() {
290 if (mGetFilesRecursiveHelper
) {
291 mGetFilesRecursiveHelper
->Unlink();
292 mGetFilesRecursiveHelper
= nullptr;
295 if (mGetFilesNonRecursiveHelper
) {
296 mGetFilesNonRecursiveHelper
->Unlink();
297 mGetFilesNonRecursiveHelper
= nullptr;
301 // Cycle Collection support.
302 void Traverse(nsCycleCollectionTraversalCallback
& cb
) {
303 FileData
* tmp
= this;
304 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilesOrDirectories
)
305 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList
)
306 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEntries
)
307 if (mGetFilesRecursiveHelper
) {
308 mGetFilesRecursiveHelper
->Traverse(cb
);
311 if (mGetFilesNonRecursiveHelper
) {
312 mGetFilesNonRecursiveHelper
->Traverse(cb
);
317 FileData
* tmp
= this;
318 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilesOrDirectories
)
319 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList
)
320 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEntries
)
321 ClearGetFilesHelpers();
325 HTMLInputElement::nsFilePickerShownCallback::nsFilePickerShownCallback(
326 HTMLInputElement
* aInput
, nsIFilePicker
* aFilePicker
)
327 : mFilePicker(aFilePicker
), mInput(aInput
) {}
329 NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback
, nsIContentPrefCallback2
)
332 UploadLastDir::ContentPrefCallback::HandleCompletion(uint16_t aReason
) {
333 nsCOMPtr
<nsIFile
> localFile
;
334 nsAutoString prefStr
;
336 if (aReason
== nsIContentPrefCallback2::COMPLETE_ERROR
|| !mResult
) {
337 Preferences::GetString("dom.input.fallbackUploadDir", prefStr
);
340 if (prefStr
.IsEmpty() && mResult
) {
341 nsCOMPtr
<nsIVariant
> pref
;
342 mResult
->GetValue(getter_AddRefs(pref
));
343 pref
->GetAsAString(prefStr
);
346 if (!prefStr
.IsEmpty()) {
347 localFile
= do_CreateInstance(NS_LOCAL_FILE_CONTRACTID
);
348 if (localFile
&& NS_WARN_IF(NS_FAILED(localFile
->InitWithPath(prefStr
)))) {
354 mFilePicker
->SetDisplayDirectory(localFile
);
356 // If no custom directory was set through the pref, default to
357 // "desktop" directory for each platform.
358 mFilePicker
->SetDisplaySpecialDirectory(
359 NS_LITERAL_STRING_FROM_CSTRING(NS_OS_DESKTOP_DIR
));
362 mFilePicker
->Open(mFpCallback
);
367 UploadLastDir::ContentPrefCallback::HandleResult(nsIContentPref
* pref
) {
373 UploadLastDir::ContentPrefCallback::HandleError(nsresult error
) {
374 // HandleCompletion is always called (even with HandleError was called),
375 // so we don't need to do anything special here.
382 * This may return nullptr if the DOM File's implementation of
383 * File::mozFullPathInternal does not successfully return a non-empty
384 * string that is a valid path. This can happen on Firefox OS, for example,
385 * where the file picker can create Blobs.
387 static already_AddRefed
<nsIFile
> LastUsedDirectory(
388 const OwningFileOrDirectory
& aData
) {
389 if (aData
.IsFile()) {
392 aData
.GetAsFile()->GetMozFullPathInternal(path
, error
);
393 if (error
.Failed() || path
.IsEmpty()) {
394 error
.SuppressException();
398 nsCOMPtr
<nsIFile
> localFile
;
399 nsresult rv
= NS_NewLocalFile(path
, true, getter_AddRefs(localFile
));
400 if (NS_WARN_IF(NS_FAILED(rv
))) {
404 nsCOMPtr
<nsIFile
> parentFile
;
405 rv
= localFile
->GetParent(getter_AddRefs(parentFile
));
406 if (NS_WARN_IF(NS_FAILED(rv
))) {
410 return parentFile
.forget();
413 MOZ_ASSERT(aData
.IsDirectory());
415 nsCOMPtr
<nsIFile
> localFile
= aData
.GetAsDirectory()->GetInternalNsIFile();
416 MOZ_ASSERT(localFile
);
418 return localFile
.forget();
421 void GetDOMFileOrDirectoryName(const OwningFileOrDirectory
& aData
,
423 if (aData
.IsFile()) {
424 aData
.GetAsFile()->GetName(aName
);
426 MOZ_ASSERT(aData
.IsDirectory());
428 aData
.GetAsDirectory()->GetName(aName
, rv
);
429 if (NS_WARN_IF(rv
.Failed())) {
430 rv
.SuppressException();
435 void GetDOMFileOrDirectoryPath(const OwningFileOrDirectory
& aData
,
436 nsAString
& aPath
, ErrorResult
& aRv
) {
437 if (aData
.IsFile()) {
438 aData
.GetAsFile()->GetMozFullPathInternal(aPath
, aRv
);
440 MOZ_ASSERT(aData
.IsDirectory());
441 aData
.GetAsDirectory()->GetFullRealPath(aPath
);
448 HTMLInputElement::nsFilePickerShownCallback::Done(
449 nsIFilePicker::ResultCode aResult
) {
450 mInput
->PickerClosed();
452 if (aResult
== nsIFilePicker::returnCancel
) {
453 RefPtr
<HTMLInputElement
> inputElement(mInput
);
454 return nsContentUtils::DispatchTrustedEvent(
455 inputElement
->OwnerDoc(), inputElement
, u
"cancel"_ns
, CanBubble::eYes
,
459 mInput
->OwnerDoc()->NotifyUserGestureActivation();
461 nsIFilePicker::Mode mode
;
462 mFilePicker
->GetMode(&mode
);
464 // Collect new selected filenames
465 nsTArray
<OwningFileOrDirectory
> newFilesOrDirectories
;
466 if (mode
== nsIFilePicker::modeOpenMultiple
) {
467 nsCOMPtr
<nsISimpleEnumerator
> iter
;
469 mFilePicker
->GetDomFileOrDirectoryEnumerator(getter_AddRefs(iter
));
470 NS_ENSURE_SUCCESS(rv
, rv
);
476 nsCOMPtr
<nsISupports
> tmp
;
479 while (NS_SUCCEEDED(iter
->HasMoreElements(&hasMore
)) && hasMore
) {
480 iter
->GetNext(getter_AddRefs(tmp
));
481 RefPtr
<Blob
> domBlob
= do_QueryObject(tmp
);
483 "Null file object from FilePicker's file enumerator?");
488 OwningFileOrDirectory
* element
= newFilesOrDirectories
.AppendElement();
489 element
->SetAsFile() = domBlob
->ToFile();
492 MOZ_ASSERT(mode
== nsIFilePicker::modeOpen
||
493 mode
== nsIFilePicker::modeGetFolder
);
494 nsCOMPtr
<nsISupports
> tmp
;
495 nsresult rv
= mFilePicker
->GetDomFileOrDirectory(getter_AddRefs(tmp
));
496 NS_ENSURE_SUCCESS(rv
, rv
);
498 // Show a prompt to get user confirmation before allowing folder access.
499 // This is to prevent sites from tricking the user into uploading files.
501 if (mode
== nsIFilePicker::modeGetFolder
) {
502 nsCOMPtr
<nsIPromptCollection
> prompter
=
503 do_GetService("@mozilla.org/embedcomp/prompt-collection;1");
505 return NS_ERROR_NOT_AVAILABLE
;
508 bool confirmed
= false;
509 BrowsingContext
* bc
= mInput
->OwnerDoc()->GetBrowsingContext();
511 // Get directory name
512 RefPtr
<Directory
> directory
= static_cast<Directory
*>(tmp
.get());
513 nsAutoString directoryName
;
515 directory
->GetName(directoryName
, error
);
516 if (NS_WARN_IF(error
.Failed())) {
517 return error
.StealNSResult();
520 rv
= prompter
->ConfirmFolderUpload(bc
, directoryName
, &confirmed
);
521 NS_ENSURE_SUCCESS(rv
, rv
);
523 // User aborted upload
528 RefPtr
<Blob
> blob
= do_QueryObject(tmp
);
530 RefPtr
<File
> file
= blob
->ToFile();
533 OwningFileOrDirectory
* element
= newFilesOrDirectories
.AppendElement();
534 element
->SetAsFile() = file
;
536 RefPtr
<Directory
> directory
= static_cast<Directory
*>(tmp
.get());
537 OwningFileOrDirectory
* element
= newFilesOrDirectories
.AppendElement();
538 element
->SetAsDirectory() = directory
;
542 if (newFilesOrDirectories
.IsEmpty()) {
546 // Store the last used directory using the content pref service:
547 nsCOMPtr
<nsIFile
> lastUsedDir
= LastUsedDirectory(newFilesOrDirectories
[0]);
550 HTMLInputElement::gUploadLastDir
->StoreLastUsedDirectory(mInput
->OwnerDoc(),
554 // The text control frame (if there is one) isn't going to send a change
555 // event because it will think this is done by a script.
556 // So, we can safely send one by ourself.
557 mInput
->SetFilesOrDirectories(newFilesOrDirectories
, true);
559 // mInput(HTMLInputElement) has no scriptGlobalObject, don't create
560 // DispatchChangeEventCallback
561 if (!mInput
->GetOwnerGlobal()) {
564 RefPtr
<DispatchChangeEventCallback
> dispatchChangeEventCallback
=
565 new DispatchChangeEventCallback(mInput
);
567 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
568 mInput
->HasAttr(nsGkAtoms::webkitdirectory
)) {
570 GetFilesHelper
* helper
= mInput
->GetOrCreateGetFilesHelper(true, error
);
571 if (NS_WARN_IF(error
.Failed())) {
572 return error
.StealNSResult();
575 helper
->AddCallback(dispatchChangeEventCallback
);
579 return dispatchChangeEventCallback
->DispatchEvents();
582 NS_IMPL_ISUPPORTS(HTMLInputElement::nsFilePickerShownCallback
,
583 nsIFilePickerShownCallback
)
585 class nsColorPickerShownCallback final
: public nsIColorPickerShownCallback
{
586 ~nsColorPickerShownCallback() = default;
589 nsColorPickerShownCallback(HTMLInputElement
* aInput
,
590 nsIColorPicker
* aColorPicker
)
591 : mInput(aInput
), mColorPicker(aColorPicker
), mValueChanged(false) {}
595 MOZ_CAN_RUN_SCRIPT_BOUNDARY
596 NS_IMETHOD
Update(const nsAString
& aColor
) override
;
597 MOZ_CAN_RUN_SCRIPT_BOUNDARY
598 NS_IMETHOD
Done(const nsAString
& aColor
) override
;
602 * Updates the internals of the object using aColor as the new value.
603 * If aTrustedUpdate is true, it will consider that aColor is a new value.
604 * Otherwise, it will check that aColor is different from the current value.
607 nsresult
UpdateInternal(const nsAString
& aColor
, bool aTrustedUpdate
);
609 RefPtr
<HTMLInputElement
> mInput
;
610 nsCOMPtr
<nsIColorPicker
> mColorPicker
;
614 nsresult
nsColorPickerShownCallback::UpdateInternal(const nsAString
& aColor
,
615 bool aTrustedUpdate
) {
616 bool valueChanged
= false;
617 nsAutoString oldValue
;
618 if (aTrustedUpdate
) {
619 mInput
->OwnerDoc()->NotifyUserGestureActivation();
622 mInput
->GetValue(oldValue
, CallerType::System
);
625 mInput
->SetValue(aColor
, CallerType::System
, IgnoreErrors());
627 if (!aTrustedUpdate
) {
628 nsAutoString newValue
;
629 mInput
->GetValue(newValue
, CallerType::System
);
630 if (!oldValue
.Equals(newValue
)) {
639 mValueChanged
= true;
640 RefPtr
<HTMLInputElement
> input(mInput
);
641 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(input
);
642 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
643 "Failed to dispatch input event");
648 nsColorPickerShownCallback::Update(const nsAString
& aColor
) {
649 return UpdateInternal(aColor
, true);
653 nsColorPickerShownCallback::Done(const nsAString
& aColor
) {
655 * When Done() is called, we might be at the end of a serie of Update() calls
656 * in which case mValueChanged is set to true and a change event will have to
657 * be fired but we might also be in a one shot Done() call situation in which
658 * case we should fire a change event iif the value actually changed.
659 * UpdateInternal(bool) is taking care of that logic for us.
663 mInput
->PickerClosed();
665 if (!aColor
.IsEmpty()) {
666 UpdateInternal(aColor
, false);
670 mInput
->SetUserInteracted(true);
671 rv
= nsContentUtils::DispatchTrustedEvent(
672 mInput
->OwnerDoc(), static_cast<Element
*>(mInput
.get()), u
"change"_ns
,
673 CanBubble::eYes
, Cancelable::eNo
);
679 NS_IMPL_ISUPPORTS(nsColorPickerShownCallback
, nsIColorPickerShownCallback
)
681 static bool IsPopupBlocked(Document
* aDoc
) {
682 if (aDoc
->ConsumeTransientUserGestureActivation()) {
686 WindowContext
* wc
= aDoc
->GetWindowContext();
687 if (wc
&& wc
->CanShowPopup()) {
691 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag
, "DOM"_ns
, aDoc
,
692 nsContentUtils::eDOM_PROPERTIES
,
693 "InputPickerBlockedNoUserActivation");
697 nsTArray
<nsString
> HTMLInputElement::GetColorsFromList() {
698 RefPtr
<HTMLDataListElement
> dataList
= GetList();
703 nsTArray
<nsString
> colors
;
705 RefPtr
<nsContentList
> options
= dataList
->Options();
706 uint32_t length
= options
->Length(true);
707 for (uint32_t i
= 0; i
< length
; ++i
) {
708 auto* option
= HTMLOptionElement::FromNodeOrNull(options
->Item(i
, false));
714 option
->GetValue(value
);
715 if (IsValidSimpleColor(value
)) {
717 colors
.AppendElement(value
);
724 nsresult
HTMLInputElement::InitColorPicker() {
725 MOZ_ASSERT(IsMutable());
727 if (mPickerRunning
) {
728 NS_WARNING("Just one nsIColorPicker is allowed");
729 return NS_ERROR_FAILURE
;
732 nsCOMPtr
<Document
> doc
= OwnerDoc();
734 nsCOMPtr
<nsPIDOMWindowOuter
> win
= doc
->GetWindow();
736 return NS_ERROR_FAILURE
;
739 if (IsPopupBlocked(doc
)) {
745 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
746 "ColorPicker", title
);
748 nsCOMPtr
<nsIColorPicker
> colorPicker
=
749 do_CreateInstance("@mozilla.org/colorpicker;1");
751 return NS_ERROR_FAILURE
;
754 nsAutoString initialValue
;
755 GetNonFileValueInternal(initialValue
);
756 nsTArray
<nsString
> colors
= GetColorsFromList();
757 nsresult rv
= colorPicker
->Init(win
, title
, initialValue
, colors
);
758 NS_ENSURE_SUCCESS(rv
, rv
);
760 nsCOMPtr
<nsIColorPickerShownCallback
> callback
=
761 new nsColorPickerShownCallback(this, colorPicker
);
763 rv
= colorPicker
->Open(callback
);
764 if (NS_SUCCEEDED(rv
)) {
765 mPickerRunning
= true;
771 nsresult
HTMLInputElement::InitFilePicker(FilePickerType aType
) {
772 MOZ_ASSERT(IsMutable());
774 if (mPickerRunning
) {
775 NS_WARNING("Just one nsIFilePicker is allowed");
776 return NS_ERROR_FAILURE
;
779 // Get parent nsPIDOMWindow object.
780 nsCOMPtr
<Document
> doc
= OwnerDoc();
782 nsCOMPtr
<nsPIDOMWindowOuter
> win
= doc
->GetWindow();
784 return NS_ERROR_FAILURE
;
787 if (IsPopupBlocked(doc
)) {
793 nsAutoString okButtonLabel
;
794 if (aType
== FILE_PICKER_DIRECTORY
) {
795 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
796 "DirectoryUpload", OwnerDoc(),
799 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
800 "DirectoryPickerOkButtonLabel",
801 OwnerDoc(), okButtonLabel
);
803 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
804 "FileUpload", OwnerDoc(), title
);
807 nsCOMPtr
<nsIFilePicker
> filePicker
=
808 do_CreateInstance("@mozilla.org/filepicker;1");
809 if (!filePicker
) return NS_ERROR_FAILURE
;
811 nsIFilePicker::Mode mode
;
813 if (aType
== FILE_PICKER_DIRECTORY
) {
814 mode
= nsIFilePicker::modeGetFolder
;
815 } else if (HasAttr(nsGkAtoms::multiple
)) {
816 mode
= nsIFilePicker::modeOpenMultiple
;
818 mode
= nsIFilePicker::modeOpen
;
822 filePicker
->Init(win
, title
, mode
, OwnerDoc()->GetBrowsingContext());
823 NS_ENSURE_SUCCESS(rv
, rv
);
825 if (!okButtonLabel
.IsEmpty()) {
826 filePicker
->SetOkButtonLabel(okButtonLabel
);
829 // Native directory pickers ignore file type filters, so we don't spend
830 // cycles adding them for FILE_PICKER_DIRECTORY.
831 if (HasAttr(nsGkAtoms::accept
) && aType
!= FILE_PICKER_DIRECTORY
) {
832 SetFilePickerFiltersFromAccept(filePicker
);
834 if (StaticPrefs::dom_capture_enabled()) {
835 if (const nsAttrValue
* captureVal
= GetParsedAttr(nsGkAtoms::capture
)) {
836 filePicker
->SetCapture(static_cast<nsIFilePicker::CaptureTarget
>(
837 captureVal
->GetEnumValue()));
841 filePicker
->AppendFilters(nsIFilePicker::filterAll
);
844 // Set default directory and filename
845 nsAutoString defaultName
;
847 const nsTArray
<OwningFileOrDirectory
>& oldFiles
=
848 GetFilesOrDirectoriesInternal();
850 nsCOMPtr
<nsIFilePickerShownCallback
> callback
=
851 new HTMLInputElement::nsFilePickerShownCallback(this, filePicker
);
853 if (!oldFiles
.IsEmpty() && aType
!= FILE_PICKER_DIRECTORY
) {
856 nsCOMPtr
<nsIFile
> parentFile
= LastUsedDirectory(oldFiles
[0]);
858 filePicker
->SetDisplayDirectory(parentFile
);
861 // Unfortunately nsIFilePicker doesn't allow multiple files to be
862 // default-selected, so only select something by default if exactly
863 // one file was selected before.
864 if (oldFiles
.Length() == 1) {
865 nsAutoString leafName
;
866 GetDOMFileOrDirectoryName(oldFiles
[0], leafName
);
868 if (!leafName
.IsEmpty()) {
869 filePicker
->SetDefaultString(leafName
);
873 rv
= filePicker
->Open(callback
);
874 if (NS_SUCCEEDED(rv
)) {
875 mPickerRunning
= true;
881 HTMLInputElement::gUploadLastDir
->FetchDirectoryAndDisplayPicker(
882 doc
, filePicker
, callback
);
883 mPickerRunning
= true;
887 #define CPS_PREF_NAME u"browser.upload.lastDir"_ns
889 NS_IMPL_ISUPPORTS(UploadLastDir
, nsIObserver
, nsISupportsWeakReference
)
891 void HTMLInputElement::InitUploadLastDir() {
892 gUploadLastDir
= new UploadLastDir();
893 NS_ADDREF(gUploadLastDir
);
895 nsCOMPtr
<nsIObserverService
> observerService
= services::GetObserverService();
896 if (observerService
&& gUploadLastDir
) {
897 observerService
->AddObserver(gUploadLastDir
,
898 "browser:purge-session-history", true);
902 void HTMLInputElement::DestroyUploadLastDir() { NS_IF_RELEASE(gUploadLastDir
); }
904 nsresult
UploadLastDir::FetchDirectoryAndDisplayPicker(
905 Document
* aDoc
, nsIFilePicker
* aFilePicker
,
906 nsIFilePickerShownCallback
* aFpCallback
) {
907 MOZ_ASSERT(aDoc
, "aDoc is null");
908 MOZ_ASSERT(aFilePicker
, "aFilePicker is null");
909 MOZ_ASSERT(aFpCallback
, "aFpCallback is null");
911 nsIURI
* docURI
= aDoc
->GetDocumentURI();
912 MOZ_ASSERT(docURI
, "docURI is null");
914 nsCOMPtr
<nsILoadContext
> loadContext
= aDoc
->GetLoadContext();
915 nsCOMPtr
<nsIContentPrefCallback2
> prefCallback
=
916 new UploadLastDir::ContentPrefCallback(aFilePicker
, aFpCallback
);
918 // Attempt to get the CPS, if it's not present we'll fallback to use the
920 nsCOMPtr
<nsIContentPrefService2
> contentPrefService
=
921 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID
);
922 if (!contentPrefService
) {
923 prefCallback
->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR
);
927 nsAutoCString cstrSpec
;
928 docURI
->GetSpec(cstrSpec
);
929 NS_ConvertUTF8toUTF16
spec(cstrSpec
);
931 contentPrefService
->GetByDomainAndName(spec
, CPS_PREF_NAME
, loadContext
,
936 nsresult
UploadLastDir::StoreLastUsedDirectory(Document
* aDoc
, nsIFile
* aDir
) {
937 MOZ_ASSERT(aDoc
, "aDoc is null");
942 nsCOMPtr
<nsIURI
> docURI
= aDoc
->GetDocumentURI();
943 MOZ_ASSERT(docURI
, "docURI is null");
945 // Attempt to get the CPS, if it's not present we'll just return
946 nsCOMPtr
<nsIContentPrefService2
> contentPrefService
=
947 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID
);
948 if (!contentPrefService
) return NS_ERROR_NOT_AVAILABLE
;
950 nsAutoCString cstrSpec
;
951 docURI
->GetSpec(cstrSpec
);
952 NS_ConvertUTF8toUTF16
spec(cstrSpec
);
954 // Find the parent of aFile, and store it
955 nsString unicodePath
;
956 aDir
->GetPath(unicodePath
);
957 if (unicodePath
.IsEmpty()) // nothing to do
959 RefPtr
<nsVariantCC
> prefValue
= new nsVariantCC();
960 prefValue
->SetAsAString(unicodePath
);
962 // Use the document's current load context to ensure that the content pref
963 // service doesn't persistently store this directory for this domain if the
964 // user is using private browsing:
965 nsCOMPtr
<nsILoadContext
> loadContext
= aDoc
->GetLoadContext();
966 return contentPrefService
->Set(spec
, CPS_PREF_NAME
, prefValue
, loadContext
,
971 UploadLastDir::Observe(nsISupports
* aSubject
, char const* aTopic
,
972 char16_t
const* aData
) {
973 if (strcmp(aTopic
, "browser:purge-session-history") == 0) {
974 nsCOMPtr
<nsIContentPrefService2
> contentPrefService
=
975 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID
);
976 if (contentPrefService
)
977 contentPrefService
->RemoveByName(CPS_PREF_NAME
, nullptr, nullptr);
984 static nsresult
FireEventForAccessibility(HTMLInputElement
* aTarget
,
985 EventMessage aEventMessage
);
989 // construction, destruction
992 HTMLInputElement::HTMLInputElement(already_AddRefed
<dom::NodeInfo
>&& aNodeInfo
,
993 FromParser aFromParser
, FromClone aFromClone
)
994 : TextControlElement(std::move(aNodeInfo
), aFromParser
,
995 FormControlType(kInputDefaultType
->value
)),
996 mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown
),
997 mAutocompleteInfoState(nsContentUtils::eAutocompleteAttrState_Unknown
),
998 mDisabledChanged(false),
999 mValueChanged(false),
1000 mUserInteracted(false),
1001 mLastValueChangeWasInteractive(false),
1002 mCheckedChanged(false),
1004 mHandlingSelectEvent(false),
1005 mShouldInitChecked(false),
1006 mDoneCreating(aFromParser
== NOT_FROM_PARSER
&&
1007 aFromClone
== FromClone::No
),
1008 mInInternalActivate(false),
1009 mCheckedIsToggled(false),
1010 mIndeterminate(false),
1011 mInhibitRestoration(aFromParser
& FROM_PARSER_FRAGMENT
),
1013 mIsDraggingRange(false),
1014 mNumberControlSpinnerIsSpinning(false),
1015 mNumberControlSpinnerSpinsUp(false),
1016 mPickerRunning(false),
1017 mIsPreviewEnabled(false),
1018 mHasBeenTypePassword(false),
1019 mHasPatternAttribute(false),
1020 mRadioGroupContainer(nullptr) {
1021 // If size is above 512, mozjemalloc allocates 1kB, see
1022 // memory/build/mozjemalloc.cpp
1023 static_assert(sizeof(HTMLInputElement
) <= 512,
1024 "Keep the size of HTMLInputElement under 512 to avoid "
1025 "performance regression!");
1027 // We are in a type=text but we create TextControlState lazily.
1028 mInputData
.mState
= nullptr;
1030 void* memory
= mInputTypeMem
;
1031 mInputType
= InputType::Create(this, mType
, memory
);
1033 if (!gUploadLastDir
) HTMLInputElement::InitUploadLastDir();
1035 // Set up our default state. By default we're enabled (since we're a control
1036 // type that can be disabled but not actually disabled right now), optional,
1037 // read-write, and valid. Also by default we don't have to show validity UI
1039 AddStatesSilently(ElementState::ENABLED
| ElementState::OPTIONAL_
|
1040 ElementState::VALID
| ElementState::VALUE_EMPTY
|
1041 ElementState::READWRITE
);
1042 RemoveStatesSilently(ElementState::READONLY
);
1043 UpdateApzAwareFlag();
1046 HTMLInputElement::~HTMLInputElement() {
1047 if (mNumberControlSpinnerIsSpinning
) {
1048 StopNumberControlSpinnerSpin(eDisallowDispatchingEvents
);
1050 nsImageLoadingContent::Destroy();
1054 void HTMLInputElement::FreeData() {
1055 if (!IsSingleLineTextControl(false)) {
1056 free(mInputData
.mValue
);
1057 mInputData
.mValue
= nullptr;
1058 } else if (mInputData
.mState
) {
1059 // XXX Passing nullptr to UnbindFromFrame doesn't do anything!
1060 UnbindFromFrame(nullptr);
1061 mInputData
.mState
->Destroy();
1062 mInputData
.mState
= nullptr;
1066 mInputType
->DropReference();
1067 mInputType
= nullptr;
1071 void HTMLInputElement::EnsureEditorState() {
1072 MOZ_ASSERT(IsSingleLineTextControl(false));
1073 if (!mInputData
.mState
) {
1074 mInputData
.mState
= TextControlState::Construct(this);
1078 TextControlState
* HTMLInputElement::GetEditorState() const {
1079 if (!IsSingleLineTextControl(false)) {
1083 // We've postponed allocating TextControlState, doing that in a const
1085 const_cast<HTMLInputElement
*>(this)->EnsureEditorState();
1087 MOZ_ASSERT(mInputData
.mState
,
1088 "Single line text controls need to have a state"
1089 " associated with them");
1091 return mInputData
.mState
;
1096 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLInputElement
)
1098 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement
,
1100 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity
)
1101 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers
)
1102 if (tmp
->IsSingleLineTextControl(false) && tmp
->mInputData
.mState
) {
1103 tmp
->mInputData
.mState
->Traverse(cb
);
1106 if (tmp
->mFileData
) {
1107 tmp
->mFileData
->Traverse(cb
);
1109 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1111 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement
,
1113 NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity
)
1114 NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers
)
1115 if (tmp
->IsSingleLineTextControl(false) && tmp
->mInputData
.mState
) {
1116 tmp
->mInputData
.mState
->Unlink();
1119 if (tmp
->mFileData
) {
1120 tmp
->mFileData
->Unlink();
1122 // XXX should unlink more?
1123 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1125 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLInputElement
,
1127 imgINotificationObserver
,
1128 nsIImageLoadingContent
,
1129 nsIConstraintValidation
)
1133 nsresult
HTMLInputElement::Clone(dom::NodeInfo
* aNodeInfo
,
1134 nsINode
** aResult
) const {
1137 RefPtr
<HTMLInputElement
> it
= new (aNodeInfo
->NodeInfoManager())
1138 HTMLInputElement(do_AddRef(aNodeInfo
), NOT_FROM_PARSER
, FromClone::Yes
);
1140 nsresult rv
= const_cast<HTMLInputElement
*>(this)->CopyInnerTo(it
);
1141 NS_ENSURE_SUCCESS(rv
, rv
);
1143 switch (GetValueMode()) {
1144 case VALUE_MODE_VALUE
:
1145 if (mValueChanged
) {
1146 // We don't have our default value anymore. Set our value on
1149 GetNonFileValueInternal(value
);
1150 // SetValueInternal handles setting the VALUE_CHANGED bit for us
1152 NS_FAILED(rv
= it
->SetValueInternal(
1153 value
, {ValueSetterOption::SetValueChanged
})))) {
1158 case VALUE_MODE_FILENAME
:
1159 if (it
->OwnerDoc()->IsStaticDocument()) {
1160 // We're going to be used in print preview. Since the doc is static
1161 // we can just grab the pretty string and use it as wallpaper
1162 GetDisplayFileName(it
->mFileData
->mStaticDocFileList
);
1164 it
->mFileData
->ClearGetFilesHelpers();
1165 it
->mFileData
->mFilesOrDirectories
.Clear();
1166 it
->mFileData
->mFilesOrDirectories
.AppendElements(
1167 mFileData
->mFilesOrDirectories
);
1170 case VALUE_MODE_DEFAULT_ON
:
1171 case VALUE_MODE_DEFAULT
:
1175 if (mCheckedChanged
) {
1176 // We no longer have our original checked state. Set our
1177 // checked state on the clone.
1178 it
->DoSetChecked(mChecked
, false, true);
1179 // Then tell DoneCreatingElement() not to overwrite:
1180 it
->mShouldInitChecked
= false;
1183 it
->mIndeterminate
= mIndeterminate
;
1185 it
->DoneCreatingElement();
1187 it
->SetLastValueChangeWasInteractive(mLastValueChangeWasInteractive
);
1192 void HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
1193 const nsAttrValue
* aValue
, bool aNotify
) {
1194 if (aNameSpaceID
== kNameSpaceID_None
) {
1195 if (aNotify
&& aName
== nsGkAtoms::disabled
) {
1196 mDisabledChanged
= true;
1199 // When name or type changes, radio should be removed from radio group.
1200 // If we are not done creating the radio, we also should not do it.
1201 if (mType
== FormControlType::InputRadio
) {
1202 if ((aName
== nsGkAtoms::name
|| (aName
== nsGkAtoms::type
&& !mForm
)) &&
1203 (mForm
|| mDoneCreating
)) {
1204 RemoveFromRadioGroup();
1205 } else if (aName
== nsGkAtoms::required
) {
1206 auto* container
= GetCurrentRadioGroupContainer();
1208 if (container
&& ((aValue
&& !HasAttr(aNameSpaceID
, aName
)) ||
1209 (!aValue
&& HasAttr(aNameSpaceID
, aName
)))) {
1211 GetAttr(nsGkAtoms::name
, name
);
1212 container
->RadioRequiredWillChange(name
, !!aValue
);
1217 if (aName
== nsGkAtoms::webkitdirectory
) {
1218 Telemetry::Accumulate(Telemetry::WEBKIT_DIRECTORY_USED
, true);
1222 return nsGenericHTMLFormControlElementWithState::BeforeSetAttr(
1223 aNameSpaceID
, aName
, aValue
, aNotify
);
1226 void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
1227 const nsAttrValue
* aValue
,
1228 const nsAttrValue
* aOldValue
,
1229 nsIPrincipal
* aSubjectPrincipal
,
1231 if (aNameSpaceID
== kNameSpaceID_None
) {
1232 bool needValidityUpdate
= false;
1233 if (aName
== nsGkAtoms::src
) {
1234 mSrcTriggeringPrincipal
= nsContentUtils::GetAttrTriggeringPrincipal(
1235 this, aValue
? aValue
->GetStringValue() : EmptyString(),
1237 if (aNotify
&& mType
== FormControlType::InputImage
) {
1239 // Mark channel as urgent-start before load image if the image load is
1240 // initiated by a user interaction.
1241 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
1243 LoadImage(aValue
->GetStringValue(), true, aNotify
,
1244 eImageLoadType_Normal
, mSrcTriggeringPrincipal
);
1246 // Null value means the attr got unset; drop the image
1247 CancelImageRequests(aNotify
);
1252 if (aName
== nsGkAtoms::value
) {
1253 // If the element has a value in value mode, the value content attribute
1254 // is the default value. So if the elements value didn't change from the
1255 // default, we have to re-set it.
1256 if (!mValueChanged
&& GetValueMode() == VALUE_MODE_VALUE
) {
1257 SetDefaultValueAsValue();
1258 } else if (GetValueMode() == VALUE_MODE_DEFAULT
&& HasDirAuto()) {
1259 SetAutoDirectionality(aNotify
);
1261 // GetStepBase() depends on the `value` attribute if `min` is not present,
1262 // even if the value doesn't change.
1263 UpdateStepMismatchValidityState();
1264 needValidityUpdate
= true;
1267 // Checked must be set no matter what type of control it is, since
1268 // mChecked must reflect the new value
1269 if (aName
== nsGkAtoms::checked
) {
1270 if (IsRadioOrCheckbox()) {
1271 SetStates(ElementState::DEFAULT
, !!aValue
, aNotify
);
1273 if (!mCheckedChanged
) {
1274 // Delay setting checked if we are creating this element (wait
1275 // until everything is set)
1276 if (!mDoneCreating
) {
1277 mShouldInitChecked
= true;
1279 DoSetChecked(!!aValue
, aNotify
, false);
1282 needValidityUpdate
= true;
1285 if (aName
== nsGkAtoms::type
) {
1286 FormControlType newType
;
1288 // We're now a text input.
1289 newType
= FormControlType(kInputDefaultType
->value
);
1291 newType
= FormControlType(aValue
->GetEnumValue());
1293 if (newType
!= mType
) {
1294 HandleTypeChange(newType
, aNotify
);
1295 needValidityUpdate
= true;
1299 // When name or type changes, radio should be added to radio group.
1300 // If we are not done creating the radio, we also should not do it.
1301 if ((aName
== nsGkAtoms::name
|| (aName
== nsGkAtoms::type
&& !mForm
)) &&
1302 mType
== FormControlType::InputRadio
&& (mForm
|| mDoneCreating
)) {
1304 UpdateValueMissingValidityStateForRadio(false);
1305 needValidityUpdate
= true;
1308 if (aName
== nsGkAtoms::required
|| aName
== nsGkAtoms::disabled
||
1309 aName
== nsGkAtoms::readonly
) {
1310 if (aName
== nsGkAtoms::disabled
) {
1311 // This *has* to be called *before* validity state check because
1312 // UpdateBarredFromConstraintValidation and
1313 // UpdateValueMissingValidityState depend on our disabled state.
1314 UpdateDisabledState(aNotify
);
1317 if (aName
== nsGkAtoms::required
&& DoesRequiredApply()) {
1318 // This *has* to be called *before* UpdateValueMissingValidityState
1319 // because UpdateValueMissingValidityState depends on our required
1321 UpdateRequiredState(!!aValue
, aNotify
);
1324 if (aName
== nsGkAtoms::readonly
&& !!aValue
!= !!aOldValue
) {
1325 UpdateReadOnlyState(aNotify
);
1328 UpdateValueMissingValidityState();
1330 // This *has* to be called *after* validity has changed.
1331 if (aName
== nsGkAtoms::readonly
|| aName
== nsGkAtoms::disabled
) {
1332 UpdateBarredFromConstraintValidation();
1334 needValidityUpdate
= true;
1335 } else if (aName
== nsGkAtoms::maxlength
) {
1336 UpdateTooLongValidityState();
1337 needValidityUpdate
= true;
1338 } else if (aName
== nsGkAtoms::minlength
) {
1339 UpdateTooShortValidityState();
1340 needValidityUpdate
= true;
1341 } else if (aName
== nsGkAtoms::pattern
) {
1342 // Although pattern attribute only applies to single line text controls,
1343 // we set this flag for all input types to save having to check the type
1345 mHasPatternAttribute
= !!aValue
;
1347 if (mDoneCreating
) {
1348 UpdatePatternMismatchValidityState();
1350 needValidityUpdate
= true;
1351 } else if (aName
== nsGkAtoms::multiple
) {
1352 UpdateTypeMismatchValidityState();
1353 needValidityUpdate
= true;
1354 } else if (aName
== nsGkAtoms::max
) {
1355 UpdateHasRange(aNotify
);
1356 mInputType
->MinMaxStepAttrChanged();
1357 // Validity state must be updated *after* the UpdateValueDueToAttrChange
1358 // call above or else the following assert will not be valid.
1359 // We don't assert the state of underflow during creation since
1360 // DoneCreatingElement sanitizes.
1361 UpdateRangeOverflowValidityState();
1362 needValidityUpdate
= true;
1363 MOZ_ASSERT(!mDoneCreating
|| mType
!= FormControlType::InputRange
||
1364 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
),
1365 "HTML5 spec does not allow underflow for type=range");
1366 } else if (aName
== nsGkAtoms::min
) {
1367 UpdateHasRange(aNotify
);
1368 mInputType
->MinMaxStepAttrChanged();
1369 // See corresponding @max comment
1370 UpdateRangeUnderflowValidityState();
1371 UpdateStepMismatchValidityState();
1372 needValidityUpdate
= true;
1373 MOZ_ASSERT(!mDoneCreating
|| mType
!= FormControlType::InputRange
||
1374 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
),
1375 "HTML5 spec does not allow underflow for type=range");
1376 } else if (aName
== nsGkAtoms::step
) {
1377 mInputType
->MinMaxStepAttrChanged();
1378 // See corresponding @max comment
1379 UpdateStepMismatchValidityState();
1380 needValidityUpdate
= true;
1381 MOZ_ASSERT(!mDoneCreating
|| mType
!= FormControlType::InputRange
||
1382 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
),
1383 "HTML5 spec does not allow underflow for type=range");
1384 } else if (aName
== nsGkAtoms::dir
&& aValue
&&
1385 aValue
->Equals(nsGkAtoms::_auto
, eIgnoreCase
)) {
1386 SetAutoDirectionality(aNotify
);
1387 } else if (aName
== nsGkAtoms::lang
) {
1388 // FIXME(emilio, bug 1651070): This doesn't account for lang changes on
1390 if (mType
== FormControlType::InputNumber
) {
1391 // The validity of our value may have changed based on the locale.
1392 UpdateValidityState();
1393 needValidityUpdate
= true;
1395 } else if (aName
== nsGkAtoms::autocomplete
) {
1396 // Clear the cached @autocomplete attribute and autocompleteInfo state.
1397 mAutocompleteAttrState
= nsContentUtils::eAutocompleteAttrState_Unknown
;
1398 mAutocompleteInfoState
= nsContentUtils::eAutocompleteAttrState_Unknown
;
1399 } else if (aName
== nsGkAtoms::placeholder
) {
1400 // Full addition / removals of the attribute reconstruct right now.
1401 if (nsTextControlFrame
* f
= do_QueryFrame(GetPrimaryFrame())) {
1402 f
->PlaceholderChanged(aOldValue
, aValue
);
1404 UpdatePlaceholderShownState();
1405 needValidityUpdate
= true;
1408 if (CreatesDateTimeWidget()) {
1409 if (aName
== nsGkAtoms::value
|| aName
== nsGkAtoms::readonly
||
1410 aName
== nsGkAtoms::tabindex
|| aName
== nsGkAtoms::required
||
1411 aName
== nsGkAtoms::disabled
) {
1412 // If original target is this and not the inner text control, we should
1413 // pass the focus to the inner text control.
1414 if (Element
* dateTimeBoxElement
= GetDateTimeBoxElement()) {
1415 AsyncEventDispatcher::RunDOMEventWhenSafe(
1416 *dateTimeBoxElement
,
1417 aName
== nsGkAtoms::value
? u
"MozDateTimeValueChanged"_ns
1418 : u
"MozDateTimeAttributeChanged"_ns
,
1419 CanBubble::eNo
, ChromeOnlyDispatch::eNo
);
1423 if (needValidityUpdate
) {
1424 UpdateValidityElementStates(aNotify
);
1428 return nsGenericHTMLFormControlElementWithState::AfterSetAttr(
1429 aNameSpaceID
, aName
, aValue
, aOldValue
, aSubjectPrincipal
, aNotify
);
1432 void HTMLInputElement::BeforeSetForm(HTMLFormElement
* aForm
, bool aBindToTree
) {
1433 // No need to remove from radio group if we are just binding to tree.
1434 if (mType
== FormControlType::InputRadio
&& !aBindToTree
) {
1435 RemoveFromRadioGroup();
1438 // Dispatch event when <input> @form is set
1440 MaybeDispatchLoginManagerEvents(aForm
);
1444 void HTMLInputElement::AfterClearForm(bool aUnbindOrDelete
) {
1447 // Do not add back to radio group if we are releasing or unbinding from tree.
1448 if (mType
== FormControlType::InputRadio
&& !aUnbindOrDelete
&&
1449 !GetCurrentRadioGroupContainer()) {
1451 UpdateValueMissingValidityStateForRadio(false);
1455 void HTMLInputElement::ResultForDialogSubmit(nsAString
& aResult
) {
1456 if (mType
== FormControlType::InputImage
) {
1457 // Get a property set by the frame to find out where it was clicked.
1458 const auto* lastClickedPoint
=
1459 static_cast<CSSIntPoint
*>(GetProperty(nsGkAtoms::imageClickedPoint
));
1461 if (lastClickedPoint
) {
1462 x
= lastClickedPoint
->x
;
1463 y
= lastClickedPoint
->y
;
1467 aResult
.AppendInt(x
);
1468 aResult
.AppendLiteral(",");
1469 aResult
.AppendInt(y
);
1471 GetAttr(nsGkAtoms::value
, aResult
);
1475 void HTMLInputElement::GetAutocomplete(nsAString
& aValue
) {
1476 if (!DoesAutocompleteApply()) {
1481 const nsAttrValue
* attributeVal
= GetParsedAttr(nsGkAtoms::autocomplete
);
1483 mAutocompleteAttrState
= nsContentUtils::SerializeAutocompleteAttribute(
1484 attributeVal
, aValue
, mAutocompleteAttrState
);
1487 void HTMLInputElement::GetAutocompleteInfo(Nullable
<AutocompleteInfo
>& aInfo
) {
1488 if (!DoesAutocompleteApply()) {
1493 const nsAttrValue
* attributeVal
= GetParsedAttr(nsGkAtoms::autocomplete
);
1494 mAutocompleteInfoState
= nsContentUtils::SerializeAutocompleteAttribute(
1495 attributeVal
, aInfo
.SetValue(), mAutocompleteInfoState
, true);
1498 void HTMLInputElement::GetCapture(nsAString
& aValue
) {
1499 GetEnumAttr(nsGkAtoms::capture
, kCaptureDefault
->tag
, aValue
);
1502 void HTMLInputElement::GetFormEnctype(nsAString
& aValue
) {
1503 GetEnumAttr(nsGkAtoms::formenctype
, "", kFormDefaultEnctype
->tag
, aValue
);
1506 void HTMLInputElement::GetFormMethod(nsAString
& aValue
) {
1507 GetEnumAttr(nsGkAtoms::formmethod
, "", kFormDefaultMethod
->tag
, aValue
);
1510 void HTMLInputElement::GetType(nsAString
& aValue
) const {
1511 GetEnumAttr(nsGkAtoms::type
, kInputDefaultType
->tag
, aValue
);
1514 int32_t HTMLInputElement::TabIndexDefault() { return 0; }
1516 uint32_t HTMLInputElement::Height() {
1517 if (mType
!= FormControlType::InputImage
) {
1520 return GetWidthHeightForImage().height
;
1523 void HTMLInputElement::SetIndeterminateInternal(bool aValue
,
1524 bool aShouldInvalidate
) {
1525 mIndeterminate
= aValue
;
1526 if (mType
!= FormControlType::InputCheckbox
) {
1530 SetStates(ElementState::INDETERMINATE
, aValue
);
1532 if (aShouldInvalidate
) {
1533 // Repaint the frame
1534 if (nsIFrame
* frame
= GetPrimaryFrame()) {
1535 frame
->InvalidateFrameSubtree();
1540 void HTMLInputElement::SetIndeterminate(bool aValue
) {
1541 SetIndeterminateInternal(aValue
, true);
1544 uint32_t HTMLInputElement::Width() {
1545 if (mType
!= FormControlType::InputImage
) {
1548 return GetWidthHeightForImage().width
;
1551 bool HTMLInputElement::SanitizesOnValueGetter() const {
1552 // Don't return non-sanitized value for datetime types, email, or number.
1553 return mType
== FormControlType::InputEmail
||
1554 mType
== FormControlType::InputNumber
|| IsDateTimeInputType(mType
);
1557 void HTMLInputElement::GetValue(nsAString
& aValue
, CallerType aCallerType
) {
1558 GetValueInternal(aValue
, aCallerType
);
1560 // In the case where we need to sanitize an input value without affecting
1561 // the displayed user's input, we instead sanitize only on .value accesses.
1562 // For the more general case of input elements displaying text that isn't
1563 // their current value, see bug 805049.
1564 if (SanitizesOnValueGetter()) {
1565 SanitizeValue(aValue
, SanitizationKind::ForValueGetter
);
1569 void HTMLInputElement::GetValueInternal(nsAString
& aValue
,
1570 CallerType aCallerType
) const {
1571 if (mType
!= FormControlType::InputFile
) {
1572 GetNonFileValueInternal(aValue
);
1576 if (aCallerType
== CallerType::System
) {
1577 aValue
.Assign(mFileData
->mFirstFilePath
);
1581 if (mFileData
->mFilesOrDirectories
.IsEmpty()) {
1587 GetDOMFileOrDirectoryName(mFileData
->mFilesOrDirectories
[0], file
);
1588 if (file
.IsEmpty()) {
1593 aValue
.AssignLiteral("C:\\fakepath\\");
1594 aValue
.Append(file
);
1597 void HTMLInputElement::GetNonFileValueInternal(nsAString
& aValue
) const {
1598 switch (GetValueMode()) {
1599 case VALUE_MODE_VALUE
:
1600 if (IsSingleLineTextControl(false)) {
1601 if (mInputData
.mState
) {
1602 mInputData
.mState
->GetValue(aValue
, true, /* aForDisplay = */ false);
1604 // Value hasn't been set yet.
1607 } else if (!aValue
.Assign(mInputData
.mValue
, fallible
)) {
1612 case VALUE_MODE_FILENAME
:
1613 MOZ_ASSERT_UNREACHABLE("Someone screwed up here");
1614 // We'll just return empty string if someone does screw up.
1618 case VALUE_MODE_DEFAULT
:
1619 // Treat defaultValue as value.
1620 GetAttr(nsGkAtoms::value
, aValue
);
1623 case VALUE_MODE_DEFAULT_ON
:
1624 // Treat default value as value and returns "on" if no value.
1625 if (!GetAttr(nsGkAtoms::value
, aValue
)) {
1626 aValue
.AssignLiteral("on");
1632 void HTMLInputElement::ClearFiles(bool aSetValueChanged
) {
1633 nsTArray
<OwningFileOrDirectory
> data
;
1634 SetFilesOrDirectories(data
, aSetValueChanged
);
1637 int32_t HTMLInputElement::MonthsSinceJan1970(uint32_t aYear
,
1638 uint32_t aMonth
) const {
1639 return (aYear
- 1970) * 12 + aMonth
- 1;
1643 Decimal
HTMLInputElement::StringToDecimal(const nsAString
& aValue
) {
1644 if (!IsAscii(aValue
)) {
1645 return Decimal::nan();
1647 NS_LossyConvertUTF16toASCII
asciiString(aValue
);
1648 std::string
stdString(asciiString
.get(), asciiString
.Length());
1649 auto decimal
= Decimal::fromString(stdString
);
1650 if (!decimal
.isFinite()) {
1651 return Decimal::nan();
1653 // Numbers are considered finite IEEE 754 Double-precision floating point
1654 // values, but decimal supports a bigger range.
1655 static const Decimal maxDouble
=
1656 Decimal::fromDouble(std::numeric_limits
<double>::max());
1657 if (decimal
< -maxDouble
|| decimal
> maxDouble
) {
1658 return Decimal::nan();
1663 Decimal
HTMLInputElement::GetValueAsDecimal() const {
1664 nsAutoString stringValue
;
1665 GetNonFileValueInternal(stringValue
);
1666 Decimal result
= mInputType
->ConvertStringToNumber(stringValue
).mResult
;
1667 return result
.isFinite() ? result
: Decimal::nan();
1670 void HTMLInputElement::SetValue(const nsAString
& aValue
, CallerType aCallerType
,
1672 // check security. Note that setting the value to the empty string is always
1673 // OK and gives pages a way to clear a file input if necessary.
1674 if (mType
== FormControlType::InputFile
) {
1675 if (!aValue
.IsEmpty()) {
1676 if (aCallerType
!= CallerType::System
) {
1677 // setting the value of a "FILE" input widget requires
1679 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1682 Sequence
<nsString
> list
;
1683 if (!list
.AppendElement(aValue
, fallible
)) {
1684 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
1688 MozSetFileNameArray(list
, aRv
);
1693 if (MayFireChangeOnBlur()) {
1694 // If the value has been set by a script, we basically want to keep the
1695 // current change event state. If the element is ready to fire a change
1696 // event, we should keep it that way. Otherwise, we should make sure the
1697 // element will not fire any event because of the script interaction.
1699 // NOTE: this is currently quite expensive work (too much string
1700 // manipulation). We should probably optimize that.
1701 nsAutoString currentValue
;
1702 GetNonFileValueInternal(currentValue
);
1704 nsresult rv
= SetValueInternal(
1705 aValue
, ¤tValue
,
1706 {ValueSetterOption::ByContentAPI
, ValueSetterOption::SetValueChanged
,
1707 ValueSetterOption::MoveCursorToEndIfValueChanged
});
1708 if (NS_FAILED(rv
)) {
1713 if (mFocusedValue
.Equals(currentValue
)) {
1714 GetValue(mFocusedValue
, aCallerType
);
1717 nsresult rv
= SetValueInternal(
1719 {ValueSetterOption::ByContentAPI
, ValueSetterOption::SetValueChanged
,
1720 ValueSetterOption::MoveCursorToEndIfValueChanged
});
1721 if (NS_FAILED(rv
)) {
1729 HTMLDataListElement
* HTMLInputElement::GetList() const {
1730 nsAutoString dataListId
;
1731 GetAttr(nsGkAtoms::list_
, dataListId
);
1732 if (dataListId
.IsEmpty()) {
1736 DocumentOrShadowRoot
* docOrShadow
= GetUncomposedDocOrConnectedShadowRoot();
1741 return HTMLDataListElement::FromNodeOrNull(
1742 docOrShadow
->GetElementById(dataListId
));
1745 void HTMLInputElement::SetValue(Decimal aValue
, CallerType aCallerType
) {
1746 MOZ_ASSERT(!aValue
.isInfinity(), "aValue must not be Infinity!");
1748 if (aValue
.isNaN()) {
1749 SetValue(u
""_ns
, aCallerType
, IgnoreErrors());
1754 mInputType
->ConvertNumberToString(aValue
, value
);
1755 SetValue(value
, aCallerType
, IgnoreErrors());
1758 void HTMLInputElement::GetValueAsDate(JSContext
* aCx
,
1759 JS::MutableHandle
<JSObject
*> aObject
,
1761 aObject
.set(nullptr);
1762 if (!IsDateTimeInputType(mType
)) {
1766 Maybe
<JS::ClippedTime
> time
;
1769 case FormControlType::InputDate
: {
1770 uint32_t year
, month
, day
;
1772 GetNonFileValueInternal(value
);
1773 if (!ParseDate(value
, &year
, &month
, &day
)) {
1777 time
.emplace(JS::TimeClip(JS::MakeDate(year
, month
- 1, day
)));
1780 case FormControlType::InputTime
: {
1781 uint32_t millisecond
;
1783 GetNonFileValueInternal(value
);
1784 if (!ParseTime(value
, &millisecond
)) {
1788 time
.emplace(JS::TimeClip(millisecond
));
1789 MOZ_ASSERT(time
->toDouble() == millisecond
,
1790 "HTML times are restricted to the day after the epoch and "
1794 case FormControlType::InputMonth
: {
1795 uint32_t year
, month
;
1797 GetNonFileValueInternal(value
);
1798 if (!ParseMonth(value
, &year
, &month
)) {
1802 time
.emplace(JS::TimeClip(JS::MakeDate(year
, month
- 1, 1)));
1805 case FormControlType::InputWeek
: {
1806 uint32_t year
, week
;
1808 GetNonFileValueInternal(value
);
1809 if (!ParseWeek(value
, &year
, &week
)) {
1813 double days
= DaysSinceEpochFromWeek(year
, week
);
1814 time
.emplace(JS::TimeClip(days
* kMsPerDay
));
1818 case FormControlType::InputDatetimeLocal
: {
1819 uint32_t year
, month
, day
, timeInMs
;
1821 GetNonFileValueInternal(value
);
1822 if (!ParseDateTimeLocal(value
, &year
, &month
, &day
, &timeInMs
)) {
1826 time
.emplace(JS::TimeClip(JS::MakeDate(year
, month
- 1, day
, timeInMs
)));
1834 aObject
.set(JS::NewDateObject(aCx
, *time
));
1836 aRv
.NoteJSContextException(aCx
);
1841 MOZ_ASSERT(false, "Unrecognized input type");
1842 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1845 void HTMLInputElement::SetValueAsDate(JSContext
* aCx
,
1846 JS::Handle
<JSObject
*> aObj
,
1848 if (!IsDateTimeInputType(mType
)) {
1849 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1855 if (!JS::ObjectIsDate(aCx
, aObj
, &isDate
)) {
1856 aRv
.NoteJSContextException(aCx
);
1860 aRv
.ThrowTypeError("Value being assigned is not a date.");
1865 double milliseconds
;
1867 if (!js::DateGetMsecSinceEpoch(aCx
, aObj
, &milliseconds
)) {
1868 aRv
.NoteJSContextException(aCx
);
1872 milliseconds
= UnspecifiedNaN
<double>();
1875 // At this point we know we're not a file input, so we can just pass "not
1876 // system" as the caller type, since the caller type only matters in the file
1878 if (std::isnan(milliseconds
)) {
1879 SetValue(u
""_ns
, CallerType::NonSystem
, aRv
);
1883 if (mType
!= FormControlType::InputMonth
) {
1884 SetValue(Decimal::fromDouble(milliseconds
), CallerType::NonSystem
);
1888 // type=month expects the value to be number of months.
1889 double year
= JS::YearFromTime(milliseconds
);
1890 double month
= JS::MonthFromTime(milliseconds
);
1892 if (std::isnan(year
) || std::isnan(month
)) {
1893 SetValue(u
""_ns
, CallerType::NonSystem
, aRv
);
1897 int32_t months
= MonthsSinceJan1970(year
, month
+ 1);
1898 SetValue(Decimal(int32_t(months
)), CallerType::NonSystem
);
1901 void HTMLInputElement::SetValueAsNumber(double aValueAsNumber
,
1903 // TODO: return TypeError when HTMLInputElement is converted to WebIDL, see
1905 if (std::isinf(aValueAsNumber
)) {
1906 aRv
.Throw(NS_ERROR_INVALID_ARG
);
1910 if (!DoesValueAsNumberApply()) {
1911 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1915 // At this point we know we're not a file input, so we can just pass "not
1916 // system" as the caller type, since the caller type only matters in the file
1918 SetValue(Decimal::fromDouble(aValueAsNumber
), CallerType::NonSystem
);
1921 Decimal
HTMLInputElement::GetMinimum() const {
1923 DoesValueAsNumberApply(),
1924 "GetMinimum() should only be used for types that allow .valueAsNumber");
1926 // Only type=range has a default minimum
1927 Decimal defaultMinimum
=
1928 mType
== FormControlType::InputRange
? Decimal(0) : Decimal::nan();
1930 if (!HasAttr(nsGkAtoms::min
)) {
1931 return defaultMinimum
;
1934 nsAutoString minStr
;
1935 GetAttr(nsGkAtoms::min
, minStr
);
1937 Decimal min
= mInputType
->ConvertStringToNumber(minStr
).mResult
;
1938 return min
.isFinite() ? min
: defaultMinimum
;
1941 Decimal
HTMLInputElement::GetMaximum() const {
1943 DoesValueAsNumberApply(),
1944 "GetMaximum() should only be used for types that allow .valueAsNumber");
1946 // Only type=range has a default maximum
1947 Decimal defaultMaximum
=
1948 mType
== FormControlType::InputRange
? Decimal(100) : Decimal::nan();
1950 if (!HasAttr(nsGkAtoms::max
)) {
1951 return defaultMaximum
;
1954 nsAutoString maxStr
;
1955 GetAttr(nsGkAtoms::max
, maxStr
);
1957 Decimal max
= mInputType
->ConvertStringToNumber(maxStr
).mResult
;
1958 return max
.isFinite() ? max
: defaultMaximum
;
1961 Decimal
HTMLInputElement::GetStepBase() const {
1962 MOZ_ASSERT(IsDateTimeInputType(mType
) ||
1963 mType
== FormControlType::InputNumber
||
1964 mType
== FormControlType::InputRange
,
1965 "Check that kDefaultStepBase is correct for this new type");
1966 // Do NOT use GetMinimum here - the spec says to use "the min content
1967 // attribute", not "the minimum".
1968 nsAutoString minStr
;
1969 if (GetAttr(nsGkAtoms::min
, minStr
)) {
1970 Decimal min
= mInputType
->ConvertStringToNumber(minStr
).mResult
;
1971 if (min
.isFinite()) {
1976 // If @min is not a double, we should use @value.
1977 nsAutoString valueStr
;
1978 if (GetAttr(nsGkAtoms::value
, valueStr
)) {
1979 Decimal value
= mInputType
->ConvertStringToNumber(valueStr
).mResult
;
1980 if (value
.isFinite()) {
1985 if (mType
== FormControlType::InputWeek
) {
1986 return kDefaultStepBaseWeek
;
1989 return kDefaultStepBase
;
1992 nsresult
HTMLInputElement::GetValueIfStepped(int32_t aStep
,
1993 StepCallerType aCallerType
,
1994 Decimal
* aNextStep
) {
1995 if (!DoStepDownStepUpApply()) {
1996 return NS_ERROR_DOM_INVALID_STATE_ERR
;
1999 Decimal stepBase
= GetStepBase();
2000 Decimal step
= GetStep();
2001 if (step
== kStepAny
) {
2002 if (aCallerType
!= CALLED_FOR_USER_EVENT
) {
2003 return NS_ERROR_DOM_INVALID_STATE_ERR
;
2005 // Allow the spin buttons and up/down arrow keys to do something sensible:
2006 step
= GetDefaultStep();
2009 Decimal minimum
= GetMinimum();
2010 Decimal maximum
= GetMaximum();
2012 if (!maximum
.isNaN()) {
2013 // "max - (max - stepBase) % step" is the nearest valid value to max.
2014 maximum
= maximum
- NS_floorModulo(maximum
- stepBase
, step
);
2015 if (!minimum
.isNaN()) {
2016 if (minimum
> maximum
) {
2017 // Either the minimum was greater than the maximum prior to our
2018 // adjustment to align maximum on a step, or else (if we adjusted
2019 // maximum) there is no valid step between minimum and the unadjusted
2026 Decimal value
= GetValueAsDecimal();
2027 bool valueWasNaN
= false;
2028 if (value
.isNaN()) {
2032 Decimal valueBeforeStepping
= value
;
2034 Decimal deltaFromStep
= NS_floorModulo(value
- stepBase
, step
);
2036 if (deltaFromStep
!= Decimal(0)) {
2038 value
+= step
- deltaFromStep
; // partial step
2039 value
+= step
* Decimal(aStep
- 1); // then remaining steps
2040 } else if (aStep
< 0) {
2041 value
-= deltaFromStep
; // partial step
2042 value
+= step
* Decimal(aStep
+ 1); // then remaining steps
2045 value
+= step
* Decimal(aStep
);
2048 if (value
< minimum
) {
2050 deltaFromStep
= NS_floorModulo(value
- stepBase
, step
);
2051 if (deltaFromStep
!= Decimal(0)) {
2052 value
+= step
- deltaFromStep
;
2055 if (value
> maximum
) {
2057 deltaFromStep
= NS_floorModulo(value
- stepBase
, step
);
2058 if (deltaFromStep
!= Decimal(0)) {
2059 value
-= deltaFromStep
;
2063 if (!valueWasNaN
&& // value="", resulting in us using "0"
2064 ((aStep
> 0 && value
< valueBeforeStepping
) ||
2065 (aStep
< 0 && value
> valueBeforeStepping
))) {
2066 // We don't want step-up to effectively step down, or step-down to
2067 // effectively step up, so return;
2075 nsresult
HTMLInputElement::ApplyStep(int32_t aStep
) {
2076 Decimal nextStep
= Decimal::nan(); // unchanged if value will not change
2078 nsresult rv
= GetValueIfStepped(aStep
, CALLED_FOR_SCRIPT
, &nextStep
);
2080 if (NS_SUCCEEDED(rv
) && nextStep
.isFinite()) {
2081 // We know we're not a file input, so the caller type does not matter; just
2082 // pass "not system" to be safe.
2083 SetValue(nextStep
, CallerType::NonSystem
);
2089 bool HTMLInputElement::IsDateTimeInputType(FormControlType aType
) {
2091 case FormControlType::InputDate
:
2092 case FormControlType::InputTime
:
2093 case FormControlType::InputMonth
:
2094 case FormControlType::InputWeek
:
2095 case FormControlType::InputDatetimeLocal
:
2102 void HTMLInputElement::MozGetFileNameArray(nsTArray
<nsString
>& aArray
,
2104 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2108 const nsTArray
<OwningFileOrDirectory
>& filesOrDirs
=
2109 GetFilesOrDirectoriesInternal();
2110 for (uint32_t i
= 0; i
< filesOrDirs
.Length(); i
++) {
2112 GetDOMFileOrDirectoryPath(filesOrDirs
[i
], str
, aRv
);
2113 if (NS_WARN_IF(aRv
.Failed())) {
2117 aArray
.AppendElement(str
);
2121 void HTMLInputElement::MozSetFileArray(
2122 const Sequence
<OwningNonNull
<File
>>& aFiles
) {
2123 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2127 nsCOMPtr
<nsIGlobalObject
> global
= OwnerDoc()->GetScopeObject();
2133 nsTArray
<OwningFileOrDirectory
> files
;
2134 for (uint32_t i
= 0; i
< aFiles
.Length(); ++i
) {
2135 RefPtr
<File
> file
= File::Create(global
, aFiles
[i
].get()->Impl());
2136 if (NS_WARN_IF(!file
)) {
2140 OwningFileOrDirectory
* element
= files
.AppendElement();
2141 element
->SetAsFile() = file
;
2144 SetFilesOrDirectories(files
, true);
2147 void HTMLInputElement::MozSetFileNameArray(const Sequence
<nsString
>& aFileNames
,
2149 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2153 if (XRE_IsContentProcess()) {
2154 aRv
.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
2158 nsTArray
<OwningFileOrDirectory
> files
;
2159 for (uint32_t i
= 0; i
< aFileNames
.Length(); ++i
) {
2160 nsCOMPtr
<nsIFile
> file
;
2162 if (StringBeginsWith(aFileNames
[i
], u
"file:"_ns
,
2163 nsASCIICaseInsensitiveStringComparator
)) {
2164 // Converts the URL string into the corresponding nsIFile if possible
2165 // A local file will be created if the URL string begins with file://
2166 NS_GetFileFromURLSpec(NS_ConvertUTF16toUTF8(aFileNames
[i
]),
2167 getter_AddRefs(file
));
2171 // this is no "file://", try as local file
2172 NS_NewLocalFile(aFileNames
[i
], false, getter_AddRefs(file
));
2176 continue; // Not much we can do if the file doesn't exist
2179 nsCOMPtr
<nsIGlobalObject
> global
= OwnerDoc()->GetScopeObject();
2181 aRv
.Throw(NS_ERROR_FAILURE
);
2185 RefPtr
<File
> domFile
= File::CreateFromFile(global
, file
);
2186 if (NS_WARN_IF(!domFile
)) {
2187 aRv
.Throw(NS_ERROR_FAILURE
);
2191 OwningFileOrDirectory
* element
= files
.AppendElement();
2192 element
->SetAsFile() = domFile
;
2195 SetFilesOrDirectories(files
, true);
2198 void HTMLInputElement::MozSetDirectory(const nsAString
& aDirectoryPath
,
2200 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2204 nsCOMPtr
<nsIFile
> file
;
2205 aRv
= NS_NewLocalFile(aDirectoryPath
, true, getter_AddRefs(file
));
2206 if (NS_WARN_IF(aRv
.Failed())) {
2210 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
2211 if (NS_WARN_IF(!window
)) {
2212 aRv
.Throw(NS_ERROR_FAILURE
);
2216 RefPtr
<Directory
> directory
= Directory::Create(window
->AsGlobal(), file
);
2217 MOZ_ASSERT(directory
);
2219 nsTArray
<OwningFileOrDirectory
> array
;
2220 OwningFileOrDirectory
* element
= array
.AppendElement();
2221 element
->SetAsDirectory() = directory
;
2223 SetFilesOrDirectories(array
, true);
2226 void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue
& aValue
) {
2227 if (NS_WARN_IF(!IsDateTimeInputType(mType
)) || !mDateTimeInputBoxValue
) {
2231 aValue
= *mDateTimeInputBoxValue
;
2234 Element
* HTMLInputElement::GetDateTimeBoxElement() {
2235 if (!GetShadowRoot()) {
2239 // The datetimebox <div> is the only child of the UA Widget Shadow Root
2240 // if it is present.
2241 MOZ_ASSERT(GetShadowRoot()->IsUAWidget());
2242 MOZ_ASSERT(1 >= GetShadowRoot()->GetChildCount());
2243 if (nsIContent
* inputAreaContent
= GetShadowRoot()->GetFirstChild()) {
2244 return inputAreaContent
->AsElement();
2250 void HTMLInputElement::OpenDateTimePicker(const DateTimeValue
& aInitialValue
) {
2251 if (NS_WARN_IF(!IsDateTimeInputType(mType
))) {
2255 mDateTimeInputBoxValue
= MakeUnique
<DateTimeValue
>(aInitialValue
);
2256 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element
*>(this),
2257 u
"MozOpenDateTimePicker"_ns
,
2258 CanBubble::eYes
, Cancelable::eYes
);
2261 void HTMLInputElement::UpdateDateTimePicker(const DateTimeValue
& aValue
) {
2262 if (NS_WARN_IF(!IsDateTimeInputType(mType
))) {
2266 mDateTimeInputBoxValue
= MakeUnique
<DateTimeValue
>(aValue
);
2267 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element
*>(this),
2268 u
"MozUpdateDateTimePicker"_ns
,
2269 CanBubble::eYes
, Cancelable::eYes
);
2272 void HTMLInputElement::CloseDateTimePicker() {
2273 if (NS_WARN_IF(!IsDateTimeInputType(mType
))) {
2277 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element
*>(this),
2278 u
"MozCloseDateTimePicker"_ns
,
2279 CanBubble::eYes
, Cancelable::eYes
);
2282 void HTMLInputElement::SetFocusState(bool aIsFocused
) {
2283 if (NS_WARN_IF(!IsDateTimeInputType(mType
))) {
2286 SetStates(ElementState::FOCUS
| ElementState::FOCUSRING
, aIsFocused
);
2289 void HTMLInputElement::UpdateValidityState() {
2290 if (NS_WARN_IF(!IsDateTimeInputType(mType
))) {
2294 // For now, datetime input box call this function only when the value may
2295 // become valid/invalid. For other validity states, they will be updated when
2296 // .value is actually changed.
2297 UpdateBadInputValidityState();
2298 UpdateValidityElementStates(true);
2301 bool HTMLInputElement::MozIsTextField(bool aExcludePassword
) {
2302 // TODO: temporary until bug 888320 is fixed.
2304 // FIXME: Historically we never returned true for `number`, we should consider
2305 // changing that now that it is similar to other inputs.
2306 if (IsDateTimeInputType(mType
) || mType
== FormControlType::InputNumber
) {
2310 return IsSingleLineTextControl(aExcludePassword
);
2313 void HTMLInputElement::SetUserInput(const nsAString
& aValue
,
2314 nsIPrincipal
& aSubjectPrincipal
) {
2315 AutoHandlingUserInputStatePusher
inputStatePusher(true);
2317 if (mType
== FormControlType::InputFile
&&
2318 !aSubjectPrincipal
.IsSystemPrincipal()) {
2322 if (mType
== FormControlType::InputFile
) {
2323 Sequence
<nsString
> list
;
2324 if (!list
.AppendElement(aValue
, fallible
)) {
2328 MozSetFileNameArray(list
, IgnoreErrors());
2332 bool isInputEventDispatchedByTextControlState
=
2333 GetValueMode() == VALUE_MODE_VALUE
&& IsSingleLineTextControl(false);
2335 nsresult rv
= SetValueInternal(
2337 {ValueSetterOption::BySetUserInputAPI
, ValueSetterOption::SetValueChanged
,
2338 ValueSetterOption::MoveCursorToEndIfValueChanged
});
2339 NS_ENSURE_SUCCESS_VOID(rv
);
2341 if (!isInputEventDispatchedByTextControlState
) {
2342 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(this);
2343 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
2344 "Failed to dispatch input event");
2347 // If this element is not currently focused, it won't receive a change event
2348 // for this update through the normal channels. So fire a change event
2349 // immediately, instead.
2350 if (CreatesDateTimeWidget() || !ShouldBlur(this)) {
2351 FireChangeEventIfNeeded();
2355 nsIEditor
* HTMLInputElement::GetEditorForBindings() {
2356 if (!GetPrimaryFrame()) {
2357 // Ensure we construct frames (and thus an editor) if needed.
2358 GetPrimaryFrame(FlushType::Frames
);
2360 return GetTextEditorFromState();
2363 bool HTMLInputElement::HasEditor() const {
2364 return !!GetTextEditorWithoutCreation();
2367 TextEditor
* HTMLInputElement::GetTextEditorFromState() {
2368 TextControlState
* state
= GetEditorState();
2370 return state
->GetTextEditor();
2375 TextEditor
* HTMLInputElement::GetTextEditor() {
2376 return GetTextEditorFromState();
2379 TextEditor
* HTMLInputElement::GetTextEditorWithoutCreation() const {
2380 TextControlState
* state
= GetEditorState();
2384 return state
->GetTextEditorWithoutCreation();
2387 nsISelectionController
* HTMLInputElement::GetSelectionController() {
2388 TextControlState
* state
= GetEditorState();
2390 return state
->GetSelectionController();
2395 nsFrameSelection
* HTMLInputElement::GetConstFrameSelection() {
2396 TextControlState
* state
= GetEditorState();
2398 return state
->GetConstFrameSelection();
2403 nsresult
HTMLInputElement::BindToFrame(nsTextControlFrame
* aFrame
) {
2404 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
2405 TextControlState
* state
= GetEditorState();
2407 return state
->BindToFrame(aFrame
);
2409 return NS_ERROR_FAILURE
;
2412 void HTMLInputElement::UnbindFromFrame(nsTextControlFrame
* aFrame
) {
2413 TextControlState
* state
= GetEditorState();
2414 if (state
&& aFrame
) {
2415 state
->UnbindFromFrame(aFrame
);
2419 nsresult
HTMLInputElement::CreateEditor() {
2420 TextControlState
* state
= GetEditorState();
2422 return state
->PrepareEditor();
2424 return NS_ERROR_FAILURE
;
2427 void HTMLInputElement::SetPreviewValue(const nsAString
& aValue
) {
2428 TextControlState
* state
= GetEditorState();
2430 state
->SetPreviewText(aValue
, true);
2434 void HTMLInputElement::GetPreviewValue(nsAString
& aValue
) {
2435 TextControlState
* state
= GetEditorState();
2437 state
->GetPreviewText(aValue
);
2441 void HTMLInputElement::EnablePreview() {
2442 if (mIsPreviewEnabled
) {
2446 mIsPreviewEnabled
= true;
2447 // Reconstruct the frame to append an anonymous preview node
2448 nsLayoutUtils::PostRestyleEvent(this, RestyleHint
{0},
2449 nsChangeHint_ReconstructFrame
);
2452 bool HTMLInputElement::IsPreviewEnabled() { return mIsPreviewEnabled
; }
2454 void HTMLInputElement::GetDisplayFileName(nsAString
& aValue
) const {
2455 MOZ_ASSERT(mFileData
);
2457 if (OwnerDoc()->IsStaticDocument()) {
2458 aValue
= mFileData
->mStaticDocFileList
;
2462 if (mFileData
->mFilesOrDirectories
.Length() == 1) {
2463 GetDOMFileOrDirectoryName(mFileData
->mFilesOrDirectories
[0], aValue
);
2469 if (mFileData
->mFilesOrDirectories
.IsEmpty()) {
2470 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
2471 HasAttr(nsGkAtoms::webkitdirectory
)) {
2472 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
2473 "NoDirSelected", OwnerDoc(),
2475 } else if (HasAttr(nsGkAtoms::multiple
)) {
2476 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
2477 "NoFilesSelected", OwnerDoc(),
2480 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
2481 "NoFileSelected", OwnerDoc(),
2486 count
.AppendInt(int(mFileData
->mFilesOrDirectories
.Length()));
2488 nsContentUtils::FormatMaybeLocalizedString(
2489 value
, nsContentUtils::eFORMS_PROPERTIES
, "XFilesSelected", OwnerDoc(),
2496 const nsTArray
<OwningFileOrDirectory
>&
2497 HTMLInputElement::GetFilesOrDirectoriesInternal() const {
2498 return mFileData
->mFilesOrDirectories
;
2501 void HTMLInputElement::SetFilesOrDirectories(
2502 const nsTArray
<OwningFileOrDirectory
>& aFilesOrDirectories
,
2503 bool aSetValueChanged
) {
2504 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2508 MOZ_ASSERT(mFileData
);
2510 mFileData
->ClearGetFilesHelpers();
2512 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2513 HTMLInputElement_Binding::ClearCachedWebkitEntriesValue(this);
2514 mFileData
->mEntries
.Clear();
2517 mFileData
->mFilesOrDirectories
.Clear();
2518 mFileData
->mFilesOrDirectories
.AppendElements(aFilesOrDirectories
);
2520 AfterSetFilesOrDirectories(aSetValueChanged
);
2523 void HTMLInputElement::SetFiles(FileList
* aFiles
, bool aSetValueChanged
) {
2524 MOZ_ASSERT(mFileData
);
2526 mFileData
->mFilesOrDirectories
.Clear();
2527 mFileData
->ClearGetFilesHelpers();
2529 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2530 HTMLInputElement_Binding::ClearCachedWebkitEntriesValue(this);
2531 mFileData
->mEntries
.Clear();
2535 uint32_t listLength
= aFiles
->Length();
2536 for (uint32_t i
= 0; i
< listLength
; i
++) {
2537 OwningFileOrDirectory
* element
=
2538 mFileData
->mFilesOrDirectories
.AppendElement();
2539 element
->SetAsFile() = aFiles
->Item(i
);
2543 AfterSetFilesOrDirectories(aSetValueChanged
);
2546 // This method is used for testing only.
2547 void HTMLInputElement::MozSetDndFilesAndDirectories(
2548 const nsTArray
<OwningFileOrDirectory
>& aFilesOrDirectories
) {
2549 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2553 SetFilesOrDirectories(aFilesOrDirectories
, true);
2555 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2556 UpdateEntries(aFilesOrDirectories
);
2559 RefPtr
<DispatchChangeEventCallback
> dispatchChangeEventCallback
=
2560 new DispatchChangeEventCallback(this);
2562 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
2563 HasAttr(nsGkAtoms::webkitdirectory
)) {
2565 GetFilesHelper
* helper
=
2566 GetOrCreateGetFilesHelper(true /* recursionFlag */, rv
);
2567 if (NS_WARN_IF(rv
.Failed())) {
2568 rv
.SuppressException();
2572 helper
->AddCallback(dispatchChangeEventCallback
);
2574 dispatchChangeEventCallback
->DispatchEvents();
2578 void HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged
) {
2579 // No need to flush here, if there's no frame at this point we
2580 // don't need to force creation of one just to tell it about this
2581 // new value. We just want the display to update as needed.
2582 nsIFormControlFrame
* formControlFrame
= GetFormControlFrame(false);
2583 if (formControlFrame
) {
2584 nsAutoString readableValue
;
2585 GetDisplayFileName(readableValue
);
2586 formControlFrame
->SetFormProperty(nsGkAtoms::value
, readableValue
);
2589 // Grab the full path here for any chrome callers who access our .value via a
2590 // CPOW. This path won't be called from a CPOW meaning the potential sync IPC
2591 // call under GetMozFullPath won't be rejected for not being urgent.
2592 if (mFileData
->mFilesOrDirectories
.IsEmpty()) {
2593 mFileData
->mFirstFilePath
.Truncate();
2596 GetDOMFileOrDirectoryPath(mFileData
->mFilesOrDirectories
[0],
2597 mFileData
->mFirstFilePath
, rv
);
2598 if (NS_WARN_IF(rv
.Failed())) {
2599 rv
.SuppressException();
2603 // Null out |mFileData->mFileList| to return a new file list when asked for.
2604 // Don't clear it since the file list might come from the user via SetFiles.
2605 if (mFileData
->mFileList
) {
2606 mFileData
->mFileList
= nullptr;
2609 if (aSetValueChanged
) {
2610 SetValueChanged(true);
2613 UpdateAllValidityStates(true);
2616 void HTMLInputElement::FireChangeEventIfNeeded() {
2617 if (!MayFireChangeOnBlur()) {
2621 // We're not exposing the GetValue return value anywhere here, so it's safe to
2622 // claim to be a system caller.
2624 GetValue(value
, CallerType::System
);
2626 // NOTE(emilio): Per spec we should not set this if we don't fire the change
2627 // event, but that seems like a bug. Using mValueChanged seems reasonable to
2628 // keep the expected behavior while
2629 // https://github.com/whatwg/html/issues/10013 is resolved.
2630 if (mValueChanged
) {
2631 SetUserInteracted(true);
2633 if (mFocusedValue
.Equals(value
)) {
2636 // Dispatch the change event.
2637 mFocusedValue
= value
;
2638 nsContentUtils::DispatchTrustedEvent(
2639 OwnerDoc(), static_cast<nsIContent
*>(this), u
"change"_ns
, CanBubble::eYes
,
2643 FileList
* HTMLInputElement::GetFiles() {
2644 if (mType
!= FormControlType::InputFile
) {
2648 if (!mFileData
->mFileList
) {
2649 mFileData
->mFileList
= new FileList(static_cast<nsIContent
*>(this));
2650 for (const OwningFileOrDirectory
& item
: GetFilesOrDirectoriesInternal()) {
2651 if (item
.IsFile()) {
2652 mFileData
->mFileList
->Append(item
.GetAsFile());
2657 return mFileData
->mFileList
;
2660 void HTMLInputElement::SetFiles(FileList
* aFiles
) {
2661 if (mType
!= FormControlType::InputFile
|| !aFiles
) {
2665 // Update |mFileData->mFilesOrDirectories|
2666 SetFiles(aFiles
, true);
2668 MOZ_ASSERT(!mFileData
->mFileList
, "Should've cleared the existing file list");
2670 // Update |mFileData->mFileList| without copy
2671 mFileData
->mFileList
= aFiles
;
2675 void HTMLInputElement::HandleNumberControlSpin(void* aData
) {
2676 RefPtr
<HTMLInputElement
> input
= static_cast<HTMLInputElement
*>(aData
);
2678 NS_ASSERTION(input
->mNumberControlSpinnerIsSpinning
,
2679 "Should have called nsRepeatService::Stop()");
2681 nsNumberControlFrame
* numberControlFrame
=
2682 do_QueryFrame(input
->GetPrimaryFrame());
2683 if (input
->mType
!= FormControlType::InputNumber
|| !numberControlFrame
) {
2684 // Type has changed (and possibly our frame type hasn't been updated yet)
2685 // or else we've lost our frame. Either way, stop the timer and don't do
2687 input
->StopNumberControlSpinnerSpin();
2689 input
->StepNumberControlForUserEvent(
2690 input
->mNumberControlSpinnerSpinsUp
? 1 : -1);
2694 nsresult
HTMLInputElement::SetValueInternal(
2695 const nsAString
& aValue
, const nsAString
* aOldValue
,
2696 const ValueSetterOptions
& aOptions
) {
2697 MOZ_ASSERT(GetValueMode() != VALUE_MODE_FILENAME
,
2698 "Don't call SetValueInternal for file inputs");
2700 // We want to remember if the SetValueInternal() call is being made for a XUL
2701 // element. We do that by looking at the parent node here, and if that node
2702 // is a XUL node, we consider our control a XUL control. XUL controls preserve
2703 // edit history across value setters.
2705 // TODO(emilio): Rather than doing this maybe add an attribute instead and
2706 // read it only on chrome docs or something? That'd allow front-end code to
2707 // move away from xul without weird side-effects.
2708 const bool forcePreserveUndoHistory
= mParent
&& mParent
->IsXULElement();
2710 switch (GetValueMode()) {
2711 case VALUE_MODE_VALUE
: {
2712 // At the moment, only single line text control have to sanitize their
2713 // value Because we have to create a new string for that, we should
2714 // prevent doing it if it's useless.
2715 nsAutoString
value(aValue
);
2717 if (mDoneCreating
&&
2718 !(mType
== FormControlType::InputNumber
&&
2719 aOptions
.contains(ValueSetterOption::BySetUserInputAPI
))) {
2720 // When the value of a number input is set by a script, we need to make
2721 // sure the value is a valid floating-point number.
2722 // https://html.spec.whatwg.org/#valid-floating-point-number
2723 // When it's set by a user, however, we need to be more permissive, so
2724 // we don't sanitize its value here. See bug 1839572.
2725 SanitizeValue(value
, SanitizationKind::ForValueSetter
);
2727 // else DoneCreatingElement calls us again once mDoneCreating is true
2729 const bool setValueChanged
=
2730 aOptions
.contains(ValueSetterOption::SetValueChanged
);
2731 if (setValueChanged
) {
2732 SetValueChanged(true);
2735 if (IsSingleLineTextControl(false)) {
2736 // Note that if aOptions includes
2737 // ValueSetterOption::BySetUserInputAPI, "input" event is automatically
2738 // dispatched by TextControlState::SetValue(). If you'd change condition
2739 // of calling this method, you need to maintain SetUserInput() too. FYI:
2740 // After calling SetValue(), the input type might have been
2741 // modified so that mInputData may not store TextControlState.
2742 EnsureEditorState();
2743 if (!mInputData
.mState
->SetValue(
2745 forcePreserveUndoHistory
2746 ? aOptions
+ ValueSetterOption::PreserveUndoHistory
2748 return NS_ERROR_OUT_OF_MEMORY
;
2750 // If the caller won't dispatch "input" event via
2751 // nsContentUtils::DispatchInputEvent(), we need to modify
2752 // validationMessage value here.
2754 // FIXME(emilio): ValueSetterOption::ByInternalAPI is not supposed to
2755 // change state, but maybe we could run this too?
2756 if (aOptions
.contains(ValueSetterOption::ByContentAPI
)) {
2757 MaybeUpdateAllValidityStates(!mDoneCreating
);
2760 free(mInputData
.mValue
);
2761 mInputData
.mValue
= ToNewUnicode(value
);
2762 if (setValueChanged
) {
2763 SetValueChanged(true);
2765 if (mType
== FormControlType::InputRange
) {
2766 nsRangeFrame
* frame
= do_QueryFrame(GetPrimaryFrame());
2768 frame
->UpdateForValueChange();
2770 } else if (CreatesDateTimeWidget() &&
2771 !aOptions
.contains(ValueSetterOption::BySetUserInputAPI
)) {
2772 if (Element
* dateTimeBoxElement
= GetDateTimeBoxElement()) {
2773 AsyncEventDispatcher::RunDOMEventWhenSafe(
2774 *dateTimeBoxElement
, u
"MozDateTimeValueChanged"_ns
,
2775 CanBubble::eNo
, ChromeOnlyDispatch::eNo
);
2778 if (mDoneCreating
) {
2779 OnValueChanged(ValueChangeKind::Internal
, value
.IsEmpty(), &value
);
2781 // else DoneCreatingElement calls us again once mDoneCreating is true
2784 if (mType
== FormControlType::InputColor
) {
2785 // Update color frame, to reflect color changes
2786 nsColorControlFrame
* colorControlFrame
=
2787 do_QueryFrame(GetPrimaryFrame());
2788 if (colorControlFrame
) {
2789 colorControlFrame
->UpdateColor();
2795 case VALUE_MODE_DEFAULT
:
2796 case VALUE_MODE_DEFAULT_ON
:
2797 // If the value of a hidden input was changed, we mark it changed so that
2798 // we will know we need to save / restore the value. Yes, we are
2799 // overloading the meaning of ValueChanged just a teensy bit to save a
2800 // measly byte of storage space in HTMLInputElement. Yes, you are free to
2801 // make a new flag, NEED_TO_SAVE_VALUE, at such time as mBitField becomes
2803 if (mType
== FormControlType::InputHidden
) {
2804 SetValueChanged(true);
2807 // Make sure to keep track of the last value change not being interactive,
2808 // just in case this used to be another kind of editable input before.
2809 // Note that a checked change _could_ really be interactive, but we don't
2810 // keep track of that elsewhere so seems fine to just do this.
2811 SetLastValueChangeWasInteractive(false);
2813 // Treat value == defaultValue for other input elements.
2814 return nsGenericHTMLFormControlElementWithState::SetAttr(
2815 kNameSpaceID_None
, nsGkAtoms::value
, aValue
, true);
2817 case VALUE_MODE_FILENAME
:
2818 return NS_ERROR_UNEXPECTED
;
2821 // This return statement is required for some compilers.
2825 void HTMLInputElement::SetValueChanged(bool aValueChanged
) {
2826 if (mValueChanged
== aValueChanged
) {
2829 mValueChanged
= aValueChanged
;
2830 UpdateTooLongValidityState();
2831 UpdateTooShortValidityState();
2832 UpdateValidityElementStates(true);
2835 void HTMLInputElement::SetLastValueChangeWasInteractive(bool aWasInteractive
) {
2836 if (aWasInteractive
== mLastValueChangeWasInteractive
) {
2839 mLastValueChangeWasInteractive
= aWasInteractive
;
2840 const bool wasValid
= IsValid();
2841 UpdateTooLongValidityState();
2842 UpdateTooShortValidityState();
2843 if (wasValid
!= IsValid()) {
2844 UpdateValidityElementStates(true);
2848 void HTMLInputElement::SetCheckedChanged(bool aCheckedChanged
) {
2849 DoSetCheckedChanged(aCheckedChanged
, true);
2852 void HTMLInputElement::DoSetCheckedChanged(bool aCheckedChanged
, bool aNotify
) {
2853 if (mType
== FormControlType::InputRadio
) {
2854 if (mCheckedChanged
!= aCheckedChanged
) {
2855 nsCOMPtr
<nsIRadioVisitor
> visitor
=
2856 new nsRadioSetCheckedChangedVisitor(aCheckedChanged
);
2857 VisitGroup(visitor
);
2860 SetCheckedChangedInternal(aCheckedChanged
);
2864 void HTMLInputElement::SetCheckedChangedInternal(bool aCheckedChanged
) {
2865 if (mCheckedChanged
== aCheckedChanged
) {
2868 mCheckedChanged
= aCheckedChanged
;
2869 UpdateValidityElementStates(true);
2872 void HTMLInputElement::SetChecked(bool aChecked
) {
2873 DoSetChecked(aChecked
, true, true);
2876 void HTMLInputElement::DoSetChecked(bool aChecked
, bool aNotify
,
2877 bool aSetValueChanged
) {
2878 // If the user or JS attempts to set checked, whether it actually changes the
2879 // value or not, we say the value was changed so that defaultValue don't
2880 // affect it no more.
2881 if (aSetValueChanged
) {
2882 DoSetCheckedChanged(true, aNotify
);
2885 // Don't do anything if we're not changing whether it's checked (it would
2886 // screw up state actually, especially when you are setting radio button to
2888 if (mChecked
== aChecked
) {
2893 if (mType
!= FormControlType::InputRadio
) {
2894 SetCheckedInternal(aChecked
, aNotify
);
2898 // For radio button, we need to do some extra fun stuff
2900 RadioSetChecked(aNotify
);
2904 if (auto* container
= GetCurrentRadioGroupContainer()) {
2906 GetAttr(nsGkAtoms::name
, name
);
2907 container
->SetCurrentRadioButton(name
, nullptr);
2909 // SetCheckedInternal is going to ask all radios to update their
2910 // validity state. We have to be sure the radio group container knows
2911 // the currently selected radio.
2912 SetCheckedInternal(false, aNotify
);
2915 void HTMLInputElement::RadioSetChecked(bool aNotify
) {
2916 // Find the selected radio button so we can deselect it
2917 HTMLInputElement
* currentlySelected
= GetSelectedRadioButton();
2919 // Deselect the currently selected radio button
2920 if (currentlySelected
) {
2921 // Pass true for the aNotify parameter since the currently selected
2922 // button is already in the document.
2923 currentlySelected
->SetCheckedInternal(false, true);
2926 // Let the group know that we are now the One True Radio Button
2927 if (auto* container
= GetCurrentRadioGroupContainer()) {
2929 GetAttr(nsGkAtoms::name
, name
);
2930 container
->SetCurrentRadioButton(name
, this);
2933 // SetCheckedInternal is going to ask all radios to update their
2935 SetCheckedInternal(true, aNotify
);
2938 RadioGroupContainer
* HTMLInputElement::GetCurrentRadioGroupContainer() const {
2940 mType
== FormControlType::InputRadio
,
2941 "GetRadioGroupContainer should only be called when type='radio'");
2942 return mRadioGroupContainer
;
2945 RadioGroupContainer
* HTMLInputElement::FindTreeRadioGroupContainer() const {
2947 GetAttr(nsGkAtoms::name
, name
);
2949 if (name
.IsEmpty()) {
2953 return &mForm
->OwnedRadioGroupContainer();
2955 if (IsInNativeAnonymousSubtree()) {
2958 if (Document
* doc
= GetUncomposedDoc()) {
2959 return &doc
->OwnedRadioGroupContainer();
2961 return &static_cast<FragmentOrElement
*>(SubtreeRoot())
2962 ->OwnedRadioGroupContainer();
2965 void HTMLInputElement::DisconnectRadioGroupContainer() {
2966 mRadioGroupContainer
= nullptr;
2969 HTMLInputElement
* HTMLInputElement::GetSelectedRadioButton() const {
2970 auto* container
= GetCurrentRadioGroupContainer();
2976 GetAttr(nsGkAtoms::name
, name
);
2978 return container
->GetCurrentRadioButton(name
);
2981 void HTMLInputElement::MaybeSubmitForm(nsPresContext
* aPresContext
) {
2983 // Nothing to do here.
2987 RefPtr
<PresShell
> presShell
= aPresContext
->GetPresShell();
2992 // Get the default submit element
2993 if (RefPtr
<nsGenericHTMLFormElement
> submitContent
=
2994 mForm
->GetDefaultSubmitElement()) {
2995 WidgetMouseEvent
event(true, eMouseClick
, nullptr, WidgetMouseEvent::eReal
);
2996 nsEventStatus status
= nsEventStatus_eIgnore
;
2997 presShell
->HandleDOMEventWithTarget(submitContent
, &event
, &status
);
2998 } else if (!mForm
->ImplicitSubmissionIsDisabled()) {
2999 // If there's only one text control, just submit the form
3000 // Hold strong ref across the event
3001 RefPtr
<dom::HTMLFormElement
> form(mForm
);
3002 form
->MaybeSubmit(nullptr);
3006 void HTMLInputElement::UpdateCheckedState(bool aNotify
) {
3007 SetStates(ElementState::CHECKED
, IsRadioOrCheckbox() && mChecked
, aNotify
);
3010 void HTMLInputElement::UpdateIndeterminateState(bool aNotify
) {
3011 bool indeterminate
= [&] {
3012 if (mType
== FormControlType::InputCheckbox
) {
3013 return mIndeterminate
;
3015 if (mType
== FormControlType::InputRadio
) {
3016 return !mChecked
&& !GetSelectedRadioButton();
3020 SetStates(ElementState::INDETERMINATE
, indeterminate
, aNotify
);
3023 void HTMLInputElement::SetCheckedInternal(bool aChecked
, bool aNotify
) {
3025 mChecked
= aChecked
;
3027 if (IsRadioOrCheckbox()) {
3028 SetStates(ElementState::CHECKED
, aChecked
, aNotify
);
3031 // No need to update element state, since we're about to call
3032 // UpdateState anyway.
3033 UpdateAllValidityStatesButNotElementState();
3034 UpdateIndeterminateState(aNotify
);
3035 UpdateValidityElementStates(aNotify
);
3037 // Notify all radios in the group that value has changed, this is to let
3038 // radios to have the chance to update its states, e.g., :indeterminate.
3039 if (mType
== FormControlType::InputRadio
) {
3040 nsCOMPtr
<nsIRadioVisitor
> visitor
= new nsRadioUpdateStateVisitor(this);
3041 VisitGroup(visitor
);
3045 #if !defined(ANDROID) && !defined(XP_MACOSX)
3046 bool HTMLInputElement::IsNodeApzAwareInternal() const {
3047 // Tell APZC we may handle mouse wheel event and do preventDefault when input
3049 return mType
== FormControlType::InputNumber
||
3050 mType
== FormControlType::InputRange
||
3051 nsINode::IsNodeApzAwareInternal();
3055 bool HTMLInputElement::IsInteractiveHTMLContent() const {
3056 return mType
!= FormControlType::InputHidden
||
3057 nsGenericHTMLFormControlElementWithState::IsInteractiveHTMLContent();
3060 void HTMLInputElement::AsyncEventRunning(AsyncEventDispatcher
* aEvent
) {
3061 nsImageLoadingContent::AsyncEventRunning(aEvent
);
3064 void HTMLInputElement::Select() {
3065 if (!IsSingleLineTextControl(false)) {
3069 TextControlState
* state
= GetEditorState();
3070 MOZ_ASSERT(state
, "Single line text controls are expected to have a state");
3072 if (FocusState() != FocusTristate::eUnfocusable
) {
3073 RefPtr
<nsFrameSelection
> fs
= state
->GetConstFrameSelection();
3074 if (fs
&& fs
->MouseDownRecorded()) {
3075 // This means that we're being called while the frame selection has a
3076 // mouse down event recorded to adjust the caret during the mouse up
3077 // event. We are probably called from the focus event handler. We should
3078 // override the delayed caret data in this case to ensure that this
3079 // select() call takes effect.
3080 fs
->SetDelayedCaretData(nullptr);
3083 if (RefPtr
<nsFocusManager
> fm
= nsFocusManager::GetFocusManager()) {
3084 fm
->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL
);
3086 // A focus event handler may change the type attribute, which will destroy
3087 // the previous state object.
3088 state
= GetEditorState();
3095 // Directly call TextControlState::SetSelectionRange because
3096 // HTMLInputElement::SetSelectionRange only applies to fewer types
3097 state
->SetSelectionRange(0, UINT32_MAX
, Optional
<nsAString
>(), IgnoreErrors(),
3098 TextControlState::ScrollAfterSelection::No
);
3101 void HTMLInputElement::SelectAll(nsPresContext
* aPresContext
) {
3102 nsIFormControlFrame
* formControlFrame
= GetFormControlFrame(true);
3104 if (formControlFrame
) {
3105 formControlFrame
->SetFormProperty(nsGkAtoms::select
, u
""_ns
);
3109 bool HTMLInputElement::NeedToInitializeEditorForEvent(
3110 EventChainPreVisitor
& aVisitor
) const {
3111 // We only need to initialize the editor for single line input controls
3112 // because they are lazily initialized. We don't need to initialize the
3113 // control for certain types of events, because we know that those events are
3114 // safe to be handled without the editor being initialized. These events
3115 // include: mousein/move/out, overflow/underflow, DOM mutation, and void
3116 // events. Void events are dispatched frequently by async keyboard scrolling
3117 // to focused elements, so it's important to handle them to prevent excessive
3119 if (!IsSingleLineTextControl(false) ||
3120 aVisitor
.mEvent
->mClass
== eMutationEventClass
) {
3124 switch (aVisitor
.mEvent
->mMessage
) {
3127 case eMouseEnterIntoWidget
:
3128 case eMouseExitFromWidget
:
3131 case eScrollPortUnderflow
:
3132 case eScrollPortOverflow
:
3139 bool HTMLInputElement::IsDisabledForEvents(WidgetEvent
* aEvent
) {
3140 return IsElementDisabledForEvents(aEvent
, GetPrimaryFrame());
3143 bool HTMLInputElement::CheckActivationBehaviorPreconditions(
3144 EventChainVisitor
& aVisitor
) const {
3146 case FormControlType::InputColor
:
3147 case FormControlType::InputCheckbox
:
3148 case FormControlType::InputRadio
:
3149 case FormControlType::InputFile
:
3150 case FormControlType::InputSubmit
:
3151 case FormControlType::InputImage
:
3152 case FormControlType::InputReset
:
3153 case FormControlType::InputButton
: {
3154 // Track whether we're in the outermost Dispatch invocation that will
3155 // cause activation of the input. That is, if we're a click event, or a
3156 // DOMActivate that was dispatched directly, this will be set, but if
3157 // we're a DOMActivate dispatched from click handling, it will not be set.
3158 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
3159 bool outerActivateEvent
=
3160 (mouseEvent
&& mouseEvent
->IsLeftClickEvent()) ||
3161 (aVisitor
.mEvent
->mMessage
== eLegacyDOMActivate
&&
3162 !mInInternalActivate
);
3163 if (outerActivateEvent
) {
3164 aVisitor
.mItemFlags
|= NS_OUTER_ACTIVATE_EVENT
;
3166 return outerActivateEvent
;
3173 void HTMLInputElement::GetEventTargetParent(EventChainPreVisitor
& aVisitor
) {
3174 // Do not process any DOM events if the element is disabled
3175 aVisitor
.mCanHandle
= false;
3176 if (IsDisabledForEvents(aVisitor
.mEvent
)) {
3180 // Initialize the editor if needed.
3181 if (NeedToInitializeEditorForEvent(aVisitor
)) {
3182 nsITextControlFrame
* textControlFrame
= do_QueryFrame(GetPrimaryFrame());
3183 if (textControlFrame
) textControlFrame
->EnsureEditorInitialized();
3186 if (CheckActivationBehaviorPreconditions(aVisitor
)) {
3187 aVisitor
.mWantsActivationBehavior
= true;
3190 // We must cache type because mType may change during JS event (bug 2369)
3191 aVisitor
.mItemFlags
|= uint8_t(mType
);
3193 if (aVisitor
.mEvent
->mMessage
== eFocus
&& aVisitor
.mEvent
->IsTrusted() &&
3194 MayFireChangeOnBlur() &&
3195 // StartRangeThumbDrag already set mFocusedValue on 'mousedown' before
3196 // we get the 'focus' event.
3197 !mIsDraggingRange
) {
3198 GetValue(mFocusedValue
, CallerType::System
);
3201 // Fire onchange (if necessary), before we do the blur, bug 357684.
3202 if (aVisitor
.mEvent
->mMessage
== eBlur
) {
3203 // We set NS_PRE_HANDLE_BLUR_EVENT here and handle it in PreHandleEvent to
3204 // prevent breaking event target chain creation.
3205 aVisitor
.mWantsPreHandleEvent
= true;
3206 aVisitor
.mItemFlags
|= NS_PRE_HANDLE_BLUR_EVENT
;
3209 if (mType
== FormControlType::InputRange
&&
3210 (aVisitor
.mEvent
->mMessage
== eFocus
||
3211 aVisitor
.mEvent
->mMessage
== eBlur
)) {
3212 // Just as nsGenericHTMLFormControlElementWithState::GetEventTargetParent
3213 // calls nsIFormControlFrame::SetFocus, we handle focus here.
3214 nsIFrame
* frame
= GetPrimaryFrame();
3216 frame
->InvalidateFrameSubtree();
3220 if (mType
== FormControlType::InputNumber
&& aVisitor
.mEvent
->IsTrusted()) {
3221 if (mNumberControlSpinnerIsSpinning
) {
3222 // If the timer is running the user has depressed the mouse on one of the
3223 // spin buttons. If the mouse exits the button we either want to reverse
3224 // the direction of spin if it has moved over the other button, or else
3225 // we want to end the spin. We do this here (rather than in
3226 // PostHandleEvent) because we don't want to let content preventDefault()
3227 // the end of the spin.
3228 if (aVisitor
.mEvent
->mMessage
== eMouseMove
) {
3229 // Be aggressive about stopping the spin:
3230 bool stopSpin
= true;
3231 nsNumberControlFrame
* numberControlFrame
=
3232 do_QueryFrame(GetPrimaryFrame());
3233 if (numberControlFrame
) {
3234 bool oldNumberControlSpinTimerSpinsUpValue
=
3235 mNumberControlSpinnerSpinsUp
;
3236 switch (numberControlFrame
->GetSpinButtonForPointerEvent(
3237 aVisitor
.mEvent
->AsMouseEvent())) {
3238 case nsNumberControlFrame::eSpinButtonUp
:
3239 mNumberControlSpinnerSpinsUp
= true;
3242 case nsNumberControlFrame::eSpinButtonDown
:
3243 mNumberControlSpinnerSpinsUp
= false;
3247 if (mNumberControlSpinnerSpinsUp
!=
3248 oldNumberControlSpinTimerSpinsUpValue
) {
3249 nsNumberControlFrame
* numberControlFrame
=
3250 do_QueryFrame(GetPrimaryFrame());
3251 if (numberControlFrame
) {
3252 numberControlFrame
->SpinnerStateChanged();
3257 StopNumberControlSpinnerSpin();
3259 } else if (aVisitor
.mEvent
->mMessage
== eMouseUp
) {
3260 StopNumberControlSpinnerSpin();
3265 nsGenericHTMLFormControlElementWithState::GetEventTargetParent(aVisitor
);
3267 // Stop the event if the related target's first non-native ancestor is the
3268 // same as the original target's first non-native ancestor (we are moving
3269 // inside of the same element).
3271 // FIXME(emilio): Is this still needed now that we use Shadow DOM for this?
3272 if (CreatesDateTimeWidget() && aVisitor
.mEvent
->IsTrusted() &&
3273 (aVisitor
.mEvent
->mMessage
== eFocus
||
3274 aVisitor
.mEvent
->mMessage
== eFocusIn
||
3275 aVisitor
.mEvent
->mMessage
== eFocusOut
||
3276 aVisitor
.mEvent
->mMessage
== eBlur
)) {
3277 nsIContent
* originalTarget
= nsIContent::FromEventTargetOrNull(
3278 aVisitor
.mEvent
->AsFocusEvent()->mOriginalTarget
);
3279 nsIContent
* relatedTarget
= nsIContent::FromEventTargetOrNull(
3280 aVisitor
.mEvent
->AsFocusEvent()->mRelatedTarget
);
3282 if (originalTarget
&& relatedTarget
&&
3283 originalTarget
->FindFirstNonChromeOnlyAccessContent() ==
3284 relatedTarget
->FindFirstNonChromeOnlyAccessContent()) {
3285 aVisitor
.mCanHandle
= false;
3290 void HTMLInputElement::LegacyPreActivationBehavior(
3291 EventChainVisitor
& aVisitor
) {
3293 // Web pages expect the value of a radio button or checkbox to be set
3294 // *before* onclick and DOMActivate fire, and they expect that if they set
3295 // the value explicitly during onclick or DOMActivate it will not be toggled
3296 // or any such nonsense.
3297 // In order to support that (bug 57137 and 58460 are examples) we toggle
3298 // the checked attribute *first*, and then fire onclick. If the user
3299 // returns false, we reset the control to the old checked value. Otherwise,
3300 // we dispatch DOMActivate. If DOMActivate is cancelled, we also reset
3301 // the control to the old checked value. We need to keep track of whether
3302 // we've already toggled the state from onclick since the user could
3303 // explicitly dispatch DOMActivate on the element.
3305 // These are compatibility hacks and are defined as legacy-pre-activation
3306 // and legacy-canceled-activation behavior in HTML.
3309 // Assert mType didn't change after GetEventTargetParent
3310 MOZ_ASSERT(NS_CONTROL_TYPE(aVisitor
.mItemFlags
) == uint8_t(mType
));
3312 bool originalCheckedValue
= false;
3313 mCheckedIsToggled
= false;
3315 if (mType
== FormControlType::InputCheckbox
) {
3316 if (mIndeterminate
) {
3317 // indeterminate is always set to FALSE when the checkbox is toggled
3318 SetIndeterminateInternal(false, false);
3319 aVisitor
.mItemFlags
|= NS_ORIGINAL_INDETERMINATE_VALUE
;
3322 originalCheckedValue
= Checked();
3323 DoSetChecked(!originalCheckedValue
, true, true);
3324 mCheckedIsToggled
= true;
3326 if (aVisitor
.mEventStatus
!= nsEventStatus_eConsumeNoDefault
) {
3327 aVisitor
.mEventStatus
= nsEventStatus_eConsumeDoDefault
;
3329 } else if (mType
== FormControlType::InputRadio
) {
3330 HTMLInputElement
* selectedRadioButton
= GetSelectedRadioButton();
3331 aVisitor
.mItemData
= static_cast<Element
*>(selectedRadioButton
);
3333 originalCheckedValue
= Checked();
3334 if (!originalCheckedValue
) {
3335 DoSetChecked(true, true, true);
3336 mCheckedIsToggled
= true;
3339 if (aVisitor
.mEventStatus
!= nsEventStatus_eConsumeNoDefault
) {
3340 aVisitor
.mEventStatus
= nsEventStatus_eConsumeDoDefault
;
3344 if (originalCheckedValue
) {
3345 aVisitor
.mItemFlags
|= NS_ORIGINAL_CHECKED_VALUE
;
3348 // out-of-spec legacy pre-activation behavior needed because of bug 1803805
3349 if ((mType
== FormControlType::InputSubmit
||
3350 mType
== FormControlType::InputImage
) &&
3352 aVisitor
.mItemFlags
|= NS_IN_SUBMIT_CLICK
;
3353 aVisitor
.mItemData
= static_cast<Element
*>(mForm
);
3354 // tell the form that we are about to enter a click handler.
3355 // that means that if there are scripted submissions, the
3356 // latest one will be deferred until after the exit point of the
3358 mForm
->OnSubmitClickBegin(this);
3362 nsresult
HTMLInputElement::PreHandleEvent(EventChainVisitor
& aVisitor
) {
3363 if (aVisitor
.mItemFlags
& NS_PRE_HANDLE_BLUR_EVENT
) {
3364 MOZ_ASSERT(aVisitor
.mEvent
->mMessage
== eBlur
);
3365 FireChangeEventIfNeeded();
3367 return nsGenericHTMLFormControlElementWithState::PreHandleEvent(aVisitor
);
3370 void HTMLInputElement::StartRangeThumbDrag(WidgetGUIEvent
* aEvent
) {
3371 nsRangeFrame
* rangeFrame
= do_QueryFrame(GetPrimaryFrame());
3376 mIsDraggingRange
= true;
3377 mRangeThumbDragStartValue
= GetValueAsDecimal();
3378 // Don't use CaptureFlags::RetargetToElement, as that breaks pseudo-class
3379 // styling of the thumb.
3380 PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState
);
3382 // Before we change the value, record the current value so that we'll
3383 // correctly send a 'change' event if appropriate. We need to do this here
3384 // because the 'focus' event is handled after the 'mousedown' event that
3385 // we're being called for (i.e. too late to update mFocusedValue, since we'll
3386 // have changed it by then).
3387 GetValue(mFocusedValue
, CallerType::System
);
3389 SetValueOfRangeForUserEvent(rangeFrame
->GetValueAtEventPoint(aEvent
),
3390 SnapToTickMarks::Yes
);
3393 void HTMLInputElement::FinishRangeThumbDrag(WidgetGUIEvent
* aEvent
) {
3394 MOZ_ASSERT(mIsDraggingRange
);
3396 if (PresShell::GetCapturingContent() == this) {
3397 PresShell::ReleaseCapturingContent();
3400 nsRangeFrame
* rangeFrame
= do_QueryFrame(GetPrimaryFrame());
3401 SetValueOfRangeForUserEvent(rangeFrame
->GetValueAtEventPoint(aEvent
),
3402 SnapToTickMarks::Yes
);
3404 mIsDraggingRange
= false;
3405 FireChangeEventIfNeeded();
3408 void HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent
) {
3409 MOZ_ASSERT(mIsDraggingRange
);
3411 mIsDraggingRange
= false;
3412 if (PresShell::GetCapturingContent() == this) {
3413 PresShell::ReleaseCapturingContent();
3415 if (aIsForUserEvent
) {
3416 SetValueOfRangeForUserEvent(mRangeThumbDragStartValue
,
3417 SnapToTickMarks::Yes
);
3419 // Don't dispatch an 'input' event - at least not using
3420 // DispatchTrustedEvent.
3421 // TODO: decide what we should do here - bug 851782.
3423 mInputType
->ConvertNumberToString(mRangeThumbDragStartValue
, val
);
3424 // TODO: What should we do if SetValueInternal fails? (The allocation
3425 // is small, so we should be fine here.)
3426 SetValueInternal(val
, {ValueSetterOption::BySetUserInputAPI
,
3427 ValueSetterOption::SetValueChanged
});
3428 if (nsRangeFrame
* frame
= do_QueryFrame(GetPrimaryFrame())) {
3429 frame
->UpdateForValueChange();
3431 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(this);
3432 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
3433 "Failed to dispatch input event");
3437 void HTMLInputElement::SetValueOfRangeForUserEvent(
3438 Decimal aValue
, SnapToTickMarks aSnapToTickMarks
) {
3439 MOZ_ASSERT(aValue
.isFinite());
3440 if (aSnapToTickMarks
== SnapToTickMarks::Yes
) {
3441 MaybeSnapToTickMark(aValue
);
3444 Decimal oldValue
= GetValueAsDecimal();
3447 mInputType
->ConvertNumberToString(aValue
, val
);
3448 // TODO: What should we do if SetValueInternal fails? (The allocation
3449 // is small, so we should be fine here.)
3450 SetValueInternal(val
, {ValueSetterOption::BySetUserInputAPI
,
3451 ValueSetterOption::SetValueChanged
});
3452 if (nsRangeFrame
* frame
= do_QueryFrame(GetPrimaryFrame())) {
3453 frame
->UpdateForValueChange();
3456 if (GetValueAsDecimal() != oldValue
) {
3457 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(this);
3458 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
3459 "Failed to dispatch input event");
3463 void HTMLInputElement::StartNumberControlSpinnerSpin() {
3464 MOZ_ASSERT(!mNumberControlSpinnerIsSpinning
);
3466 mNumberControlSpinnerIsSpinning
= true;
3468 nsRepeatService::GetInstance()->Start(
3469 HandleNumberControlSpin
, this, OwnerDoc(), "HandleNumberControlSpin"_ns
);
3471 // Capture the mouse so that we can tell if the pointer moves from one
3472 // spin button to the other, or to some other element:
3473 PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState
);
3475 nsNumberControlFrame
* numberControlFrame
= do_QueryFrame(GetPrimaryFrame());
3476 if (numberControlFrame
) {
3477 numberControlFrame
->SpinnerStateChanged();
3481 void HTMLInputElement::StopNumberControlSpinnerSpin(SpinnerStopState aState
) {
3482 if (mNumberControlSpinnerIsSpinning
) {
3483 if (PresShell::GetCapturingContent() == this) {
3484 PresShell::ReleaseCapturingContent();
3487 nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin
, this);
3489 mNumberControlSpinnerIsSpinning
= false;
3491 if (aState
== eAllowDispatchingEvents
) {
3492 FireChangeEventIfNeeded();
3495 nsNumberControlFrame
* numberControlFrame
= do_QueryFrame(GetPrimaryFrame());
3496 if (numberControlFrame
) {
3497 MOZ_ASSERT(aState
== eAllowDispatchingEvents
,
3498 "Shouldn't have primary frame for the element when we're not "
3499 "allowed to dispatch events to it anymore.");
3500 numberControlFrame
->SpinnerStateChanged();
3505 void HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection
) {
3506 // We can't use GetValidityState here because the validity state is not set
3507 // if the user hasn't previously taken an action to set or change the value,
3508 // according to the specs.
3509 if (HasBadInput()) {
3510 // If the user has typed a value into the control and inadvertently made a
3511 // mistake (e.g. put a thousand separator at the wrong point) we do not
3512 // want to wipe out what they typed if they try to increment/decrement the
3513 // value. Better is to highlight the value as being invalid so that they
3514 // can correct what they typed.
3515 // We only do this if there actually is a value typed in by/displayed to
3516 // the user. (IsValid() can return false if the 'required' attribute is
3517 // set and the value is the empty string.)
3518 if (!IsValueEmpty()) {
3519 // We pass 'true' for SetUserInteracted because we need the UI to update
3520 // _now_ or the user will wonder why the step behavior isn't functioning.
3521 SetUserInteracted(true);
3526 Decimal newValue
= Decimal::nan(); // unchanged if value will not change
3528 nsresult rv
= GetValueIfStepped(aDirection
, CALLED_FOR_USER_EVENT
, &newValue
);
3530 if (NS_FAILED(rv
) || !newValue
.isFinite()) {
3531 return; // value should not or will not change
3534 nsAutoString newVal
;
3535 mInputType
->ConvertNumberToString(newValue
, newVal
);
3536 // TODO: What should we do if SetValueInternal fails? (The allocation
3537 // is small, so we should be fine here.)
3538 SetValueInternal(newVal
, {ValueSetterOption::BySetUserInputAPI
,
3539 ValueSetterOption::SetValueChanged
});
3542 static bool SelectTextFieldOnFocus() {
3543 if (!gSelectTextFieldOnFocus
) {
3544 int32_t selectTextfieldsOnKeyFocus
= -1;
3546 LookAndFeel::GetInt(LookAndFeel::IntID::SelectTextfieldsOnKeyFocus
,
3547 &selectTextfieldsOnKeyFocus
);
3548 if (NS_FAILED(rv
)) {
3549 gSelectTextFieldOnFocus
= -1;
3551 gSelectTextFieldOnFocus
= selectTextfieldsOnKeyFocus
!= 0 ? 1 : -1;
3555 return gSelectTextFieldOnFocus
== 1;
3558 bool HTMLInputElement::ShouldPreventDOMActivateDispatch(
3559 EventTarget
* aOriginalTarget
) {
3561 * For the moment, there is only one situation where we actually want to
3562 * prevent firing a DOMActivate event:
3563 * - we are a <input type='file'> that just got a click event,
3564 * - the event was targeted to our button which should have sent a
3565 * DOMActivate event.
3568 if (mType
!= FormControlType::InputFile
) {
3572 Element
* target
= Element::FromEventTargetOrNull(aOriginalTarget
);
3577 return target
->GetParent() == this &&
3578 target
->IsRootOfNativeAnonymousSubtree() &&
3579 target
->IsHTMLElement(nsGkAtoms::button
);
3582 nsresult
HTMLInputElement::MaybeInitPickers(EventChainPostVisitor
& aVisitor
) {
3583 // Open a file picker when we receive a click on a <input type='file'>, or
3584 // open a color picker when we receive a click on a <input type='color'>.
3585 // A click is handled if it's the left mouse button.
3586 // We do not prevent non-trusted click because authors can already use
3587 // .click(). However, the pickers will follow the rules of popup-blocking.
3588 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
3589 if (!(mouseEvent
&& mouseEvent
->IsLeftClickEvent())) {
3592 if (mType
== FormControlType::InputFile
) {
3593 // If the user clicked on the "Choose folder..." button we open the
3594 // directory picker, else we open the file picker.
3595 FilePickerType type
= FILE_PICKER_FILE
;
3596 nsIContent
* target
=
3597 nsIContent::FromEventTargetOrNull(aVisitor
.mEvent
->mOriginalTarget
);
3598 if (target
&& target
->FindFirstNonChromeOnlyAccessContent() == this &&
3599 StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
3600 HasAttr(nsGkAtoms::webkitdirectory
)) {
3601 type
= FILE_PICKER_DIRECTORY
;
3603 return InitFilePicker(type
);
3605 if (mType
== FormControlType::InputColor
) {
3606 return InitColorPicker();
3613 * Return true if the input event should be ignored because of its modifiers.
3614 * Control is treated specially, since sometimes we ignore it, and sometimes
3615 * we don't (for webcompat reasons).
3617 static bool IgnoreInputEventWithModifier(const WidgetInputEvent
& aEvent
,
3618 bool ignoreControl
) {
3619 return (ignoreControl
&& aEvent
.IsControl()) ||
3621 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
3622 // Meta key is the Windows Logo key on Windows and Linux which may
3623 // assign some special meaning for the events while it's pressed.
3624 // On the other hand, it's a normal modifier in macOS and Android.
3625 // Therefore, We should ignore it only in Win/Linux.
3631 bool HTMLInputElement::StepsInputValue(
3632 const WidgetKeyboardEvent
& aEvent
) const {
3633 if (mType
!= FormControlType::InputNumber
) {
3636 if (aEvent
.mMessage
!= eKeyPress
) {
3639 if (!aEvent
.IsTrusted()) {
3642 if (aEvent
.mKeyCode
!= NS_VK_UP
&& aEvent
.mKeyCode
!= NS_VK_DOWN
) {
3645 if (IgnoreInputEventWithModifier(aEvent
, false)) {
3648 if (aEvent
.DefaultPrevented()) {
3657 static bool ActivatesWithKeyboard(FormControlType aType
, uint32_t aKeyCode
) {
3659 case FormControlType::InputCheckbox
:
3660 case FormControlType::InputRadio
:
3661 // Checkbox and Radio try to submit on Enter press
3662 return aKeyCode
!= NS_VK_RETURN
;
3663 case FormControlType::InputButton
:
3664 case FormControlType::InputReset
:
3665 case FormControlType::InputSubmit
:
3666 case FormControlType::InputFile
:
3667 case FormControlType::InputImage
: // Bug 34418
3668 case FormControlType::InputColor
:
3675 nsresult
HTMLInputElement::PostHandleEvent(EventChainPostVisitor
& aVisitor
) {
3676 if (aVisitor
.mEvent
->mMessage
== eBlur
) {
3677 if (mIsDraggingRange
) {
3678 FinishRangeThumbDrag();
3679 } else if (mNumberControlSpinnerIsSpinning
) {
3680 StopNumberControlSpinnerSpin();
3684 nsresult rv
= NS_OK
;
3685 auto oldType
= FormControlType(NS_CONTROL_TYPE(aVisitor
.mItemFlags
));
3687 // Ideally we would make the default action for click and space just dispatch
3688 // DOMActivate, and the default action for DOMActivate flip the checkbox/
3689 // radio state and fire onchange. However, for backwards compatibility, we
3690 // need to flip the state before firing click, and we need to fire click
3691 // when space is pressed. So, we just nest the firing of DOMActivate inside
3692 // the click event handling, and allow cancellation of DOMActivate to cancel
3694 if (aVisitor
.mEventStatus
!= nsEventStatus_eConsumeNoDefault
&&
3695 !IsSingleLineTextControl(true) && mType
!= FormControlType::InputNumber
) {
3696 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
3697 if (mouseEvent
&& mouseEvent
->IsLeftClickEvent() &&
3698 OwnerDoc()->MayHaveDOMActivateListeners() &&
3699 !ShouldPreventDOMActivateDispatch(aVisitor
.mEvent
->mOriginalTarget
)) {
3700 // DOMActive event should be trusted since the activation is actually
3701 // occurred even if the cause is an untrusted click event.
3702 InternalUIEvent
actEvent(true, eLegacyDOMActivate
, mouseEvent
);
3703 actEvent
.mDetail
= 1;
3705 if (RefPtr
<PresShell
> presShell
=
3706 aVisitor
.mPresContext
? aVisitor
.mPresContext
->GetPresShell()
3708 nsEventStatus status
= nsEventStatus_eIgnore
;
3709 mInInternalActivate
= true;
3710 rv
= presShell
->HandleDOMEventWithTarget(this, &actEvent
, &status
);
3711 mInInternalActivate
= false;
3713 // If activate is cancelled, we must do the same as when click is
3714 // cancelled (revert the checkbox to its original value).
3715 if (status
== nsEventStatus_eConsumeNoDefault
) {
3716 aVisitor
.mEventStatus
= status
;
3722 bool preventDefault
=
3723 aVisitor
.mEventStatus
== nsEventStatus_eConsumeNoDefault
;
3724 if (IsDisabled() && oldType
!= FormControlType::InputCheckbox
&&
3725 oldType
!= FormControlType::InputRadio
) {
3726 // Behave as if defaultPrevented when the element becomes disabled by event
3727 // listeners. Checkboxes and radio buttons should still process clicks for
3729 // https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
3730 preventDefault
= true;
3733 if (NS_SUCCEEDED(rv
)) {
3734 WidgetKeyboardEvent
* keyEvent
= aVisitor
.mEvent
->AsKeyboardEvent();
3735 if (keyEvent
&& StepsInputValue(*keyEvent
)) {
3736 StepNumberControlForUserEvent(keyEvent
->mKeyCode
== NS_VK_UP
? 1 : -1);
3737 FireChangeEventIfNeeded();
3738 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
3739 } else if (!preventDefault
) {
3740 if (keyEvent
&& ActivatesWithKeyboard(mType
, keyEvent
->mKeyCode
) &&
3741 keyEvent
->IsTrusted()) {
3742 // We maybe dispatch a synthesized click for keyboard activation.
3743 HandleKeyboardActivation(aVisitor
);
3746 switch (aVisitor
.mEvent
->mMessage
) {
3748 // see if we should select the contents of the textbox. This happens
3749 // for text and password fields when the field was focused by the
3750 // keyboard or a navigation, the platform allows it, and it wasn't
3751 // just because we raised a window.
3753 // While it'd usually make sense, we don't do this for JS callers
3754 // because it causes some compat issues, see bug 1712724 for example.
3755 nsFocusManager
* fm
= nsFocusManager::GetFocusManager();
3756 if (fm
&& IsSingleLineTextControl(false) &&
3757 !aVisitor
.mEvent
->AsFocusEvent()->mFromRaise
&&
3758 SelectTextFieldOnFocus()) {
3759 if (Document
* document
= GetComposedDoc()) {
3760 uint32_t lastFocusMethod
=
3761 fm
->GetLastFocusMethod(document
->GetWindow());
3762 const bool shouldSelectAllOnFocus
= [&] {
3763 if (lastFocusMethod
& nsIFocusManager::FLAG_BYMOVEFOCUS
) {
3766 if (lastFocusMethod
& nsIFocusManager::FLAG_BYJS
) {
3769 return bool(lastFocusMethod
& nsIFocusManager::FLAG_BYKEY
);
3771 if (shouldSelectAllOnFocus
) {
3772 RefPtr
<nsPresContext
> presContext
=
3773 GetPresContext(eForComposedDoc
);
3774 SelectAll(presContext
);
3782 // For compatibility with the other browsers, we should active this
3783 // element at least when a checkbox or a radio button.
3784 // TODO: Investigate which elements are activated by space key in the
3786 if (aVisitor
.mPresContext
&& keyEvent
->IsTrusted() && !IsDisabled() &&
3787 keyEvent
->ShouldWorkAsSpaceKey() &&
3788 (mType
== FormControlType::InputCheckbox
||
3789 mType
== FormControlType::InputRadio
)) {
3790 EventStateManager::SetActiveManager(
3791 aVisitor
.mPresContext
->EventStateManager(), this);
3797 if (mType
== FormControlType::InputRadio
&& keyEvent
->IsTrusted() &&
3798 !keyEvent
->IsAlt() && !keyEvent
->IsControl() &&
3799 !keyEvent
->IsMeta()) {
3800 rv
= MaybeHandleRadioButtonNavigation(aVisitor
, keyEvent
->mKeyCode
);
3804 * For some input types, if the user hits enter, the form is
3807 * Bug 99920, bug 109463 and bug 147850:
3808 * (a) if there is a submit control in the form, click the first
3809 * submit control in the form.
3810 * (b) if there is just one text control in the form, submit by
3811 * sending a submit event directly to the form
3812 * (c) if there is more than one text input and no submit buttons, do
3813 * not submit, period.
3816 if (keyEvent
->mKeyCode
== NS_VK_RETURN
&& keyEvent
->IsTrusted() &&
3817 (IsSingleLineTextControl(false, mType
) ||
3818 IsDateTimeInputType(mType
) ||
3819 mType
== FormControlType::InputCheckbox
||
3820 mType
== FormControlType::InputRadio
)) {
3821 if (IsSingleLineTextControl(false, mType
) ||
3822 IsDateTimeInputType(mType
)) {
3823 FireChangeEventIfNeeded();
3826 if (aVisitor
.mPresContext
) {
3827 MaybeSubmitForm(aVisitor
.mPresContext
);
3831 if (mType
== FormControlType::InputRange
&& keyEvent
->IsTrusted() &&
3832 !keyEvent
->IsAlt() && !keyEvent
->IsControl() &&
3833 !keyEvent
->IsMeta() &&
3834 (keyEvent
->mKeyCode
== NS_VK_LEFT
||
3835 keyEvent
->mKeyCode
== NS_VK_RIGHT
||
3836 keyEvent
->mKeyCode
== NS_VK_UP
||
3837 keyEvent
->mKeyCode
== NS_VK_DOWN
||
3838 keyEvent
->mKeyCode
== NS_VK_PAGE_UP
||
3839 keyEvent
->mKeyCode
== NS_VK_PAGE_DOWN
||
3840 keyEvent
->mKeyCode
== NS_VK_HOME
||
3841 keyEvent
->mKeyCode
== NS_VK_END
)) {
3842 Decimal minimum
= GetMinimum();
3843 Decimal maximum
= GetMaximum();
3844 MOZ_ASSERT(minimum
.isFinite() && maximum
.isFinite());
3845 if (minimum
< maximum
) { // else the value is locked to the minimum
3846 Decimal value
= GetValueAsDecimal();
3847 Decimal step
= GetStep();
3848 if (step
== kStepAny
) {
3849 step
= GetDefaultStep();
3851 MOZ_ASSERT(value
.isFinite() && step
.isFinite());
3853 switch (keyEvent
->mKeyCode
) {
3856 (GetComputedDirectionality() == Directionality::Rtl
3862 (GetComputedDirectionality() == Directionality::Rtl
3867 // Even for horizontal range, "up" means "increase"
3868 newValue
= value
+ step
;
3871 // Even for horizontal range, "down" means "decrease"
3872 newValue
= value
- step
;
3881 // For PgUp/PgDn we jump 10% of the total range, unless step
3882 // requires us to jump more.
3884 value
+ std::max(step
, (maximum
- minimum
) / Decimal(10));
3886 case NS_VK_PAGE_DOWN
:
3888 value
- std::max(step
, (maximum
- minimum
) / Decimal(10));
3891 SetValueOfRangeForUserEvent(newValue
);
3892 FireChangeEventIfNeeded();
3893 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
3897 } break; // eKeyPress
3901 case eMouseDoubleClick
: {
3902 // cancel all of these events for buttons
3904 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
3905 if (mouseEvent
->mButton
== MouseButton::eMiddle
||
3906 mouseEvent
->mButton
== MouseButton::eSecondary
) {
3907 if (mType
== FormControlType::InputButton
||
3908 mType
== FormControlType::InputReset
||
3909 mType
== FormControlType::InputSubmit
) {
3910 if (aVisitor
.mDOMEvent
) {
3911 aVisitor
.mDOMEvent
->StopPropagation();
3913 rv
= NS_ERROR_FAILURE
;
3917 if (mType
== FormControlType::InputNumber
&&
3918 aVisitor
.mEvent
->IsTrusted()) {
3919 if (mouseEvent
->mButton
== MouseButton::ePrimary
&&
3920 !IgnoreInputEventWithModifier(*mouseEvent
, false)) {
3921 nsNumberControlFrame
* numberControlFrame
=
3922 do_QueryFrame(GetPrimaryFrame());
3923 if (numberControlFrame
) {
3924 if (aVisitor
.mEvent
->mMessage
== eMouseDown
&& IsMutable()) {
3925 switch (numberControlFrame
->GetSpinButtonForPointerEvent(
3926 aVisitor
.mEvent
->AsMouseEvent())) {
3927 case nsNumberControlFrame::eSpinButtonUp
:
3928 StepNumberControlForUserEvent(1);
3929 mNumberControlSpinnerSpinsUp
= true;
3930 StartNumberControlSpinnerSpin();
3931 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
3933 case nsNumberControlFrame::eSpinButtonDown
:
3934 StepNumberControlForUserEvent(-1);
3935 mNumberControlSpinnerSpinsUp
= false;
3936 StartNumberControlSpinnerSpin();
3937 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
3943 if (aVisitor
.mEventStatus
!= nsEventStatus_eConsumeNoDefault
) {
3944 // We didn't handle this to step up/down. Whatever this was, be
3945 // aggressive about stopping the spin. (And don't set
3946 // nsEventStatus_eConsumeNoDefault after doing so, since that
3947 // might prevent, say, the context menu from opening.)
3948 StopNumberControlSpinnerSpin();
3953 #if !defined(ANDROID) && !defined(XP_MACOSX)
3955 // Handle wheel events as increasing / decreasing the input element's
3956 // value when it's focused and it's type is number or range.
3957 WidgetWheelEvent
* wheelEvent
= aVisitor
.mEvent
->AsWheelEvent();
3958 if (!aVisitor
.mEvent
->DefaultPrevented() &&
3959 aVisitor
.mEvent
->IsTrusted() && IsMutable() && wheelEvent
&&
3960 wheelEvent
->mDeltaY
!= 0 &&
3961 wheelEvent
->mDeltaMode
!= WheelEvent_Binding::DOM_DELTA_PIXEL
) {
3962 if (mType
== FormControlType::InputNumber
) {
3963 if (nsContentUtils::IsFocusedContent(this)) {
3964 StepNumberControlForUserEvent(wheelEvent
->mDeltaY
> 0 ? -1 : 1);
3965 FireChangeEventIfNeeded();
3966 aVisitor
.mEvent
->PreventDefault();
3968 } else if (mType
== FormControlType::InputRange
&&
3969 nsContentUtils::IsFocusedContent(this) &&
3970 GetMinimum() < GetMaximum()) {
3971 Decimal value
= GetValueAsDecimal();
3972 Decimal step
= GetStep();
3973 if (step
== kStepAny
) {
3974 step
= GetDefaultStep();
3976 MOZ_ASSERT(value
.isFinite() && step
.isFinite());
3977 SetValueOfRangeForUserEvent(
3978 wheelEvent
->mDeltaY
< 0 ? value
+ step
: value
- step
);
3979 FireChangeEventIfNeeded();
3980 aVisitor
.mEvent
->PreventDefault();
3987 if (!aVisitor
.mEvent
->DefaultPrevented() &&
3988 aVisitor
.mEvent
->IsTrusted() &&
3989 aVisitor
.mEvent
->AsMouseEvent()->mButton
==
3990 MouseButton::ePrimary
) {
3991 // TODO(emilio): Handling this should ideally not move focus.
3992 if (mType
== FormControlType::InputSearch
) {
3993 if (nsSearchControlFrame
* searchControlFrame
=
3994 do_QueryFrame(GetPrimaryFrame())) {
3995 Element
* clearButton
= searchControlFrame
->GetAnonClearButton();
3997 aVisitor
.mEvent
->mOriginalTarget
== clearButton
) {
3998 SetUserInput(EmptyString(),
3999 *nsContentUtils::GetSystemPrincipal());
4000 // TODO(emilio): This should focus the input, but calling
4001 // SetFocus(this, FLAG_NOSCROLL) for some reason gets us into
4002 // an inconsistent state where we're focused but don't match
4003 // :focus-visible / :focus.
4006 } else if (mType
== FormControlType::InputPassword
) {
4007 if (nsTextControlFrame
* textControlFrame
=
4008 do_QueryFrame(GetPrimaryFrame())) {
4009 auto* reveal
= textControlFrame
->GetRevealButton();
4010 if (reveal
&& aVisitor
.mEvent
->mOriginalTarget
== reveal
) {
4011 SetRevealPassword(!RevealPassword());
4012 // TODO(emilio): This should focus the input, but calling
4013 // SetFocus(this, FLAG_NOSCROLL) for some reason gets us into
4014 // an inconsistent state where we're focused but don't match
4015 // :focus-visible / :focus.
4026 // Bug 1459231: Temporarily needed till links respect activation target,
4027 // then also remove NS_OUTER_ACTIVATE_EVENT. The appropriate
4028 // behavior/model for links is still under discussion (see
4029 // https://github.com/whatwg/html/issues/1576). For now, we aim for
4030 // consistency with other browsers.
4031 if (aVisitor
.mItemFlags
& NS_OUTER_ACTIVATE_EVENT
) {
4033 case FormControlType::InputReset
:
4034 case FormControlType::InputSubmit
:
4035 case FormControlType::InputImage
:
4037 aVisitor
.mEvent
->mFlags
.mMultipleActionsPrevented
= true;
4040 case FormControlType::InputCheckbox
:
4041 case FormControlType::InputRadio
:
4042 aVisitor
.mEvent
->mFlags
.mMultipleActionsPrevented
= true;
4051 if (NS_SUCCEEDED(rv
) && mType
== FormControlType::InputRange
) {
4052 PostHandleEventForRangeThumb(aVisitor
);
4055 if (!preventDefault
) {
4056 MOZ_TRY(MaybeInitPickers(aVisitor
));
4061 void EndSubmitClick(EventChainPostVisitor
& aVisitor
) {
4062 auto oldType
= FormControlType(NS_CONTROL_TYPE(aVisitor
.mItemFlags
));
4063 if ((aVisitor
.mItemFlags
& NS_IN_SUBMIT_CLICK
) &&
4064 (oldType
== FormControlType::InputSubmit
||
4065 oldType
== FormControlType::InputImage
)) {
4066 nsCOMPtr
<nsIContent
> content(do_QueryInterface(aVisitor
.mItemData
));
4067 RefPtr
<HTMLFormElement
> form
= HTMLFormElement::FromNodeOrNull(content
);
4068 // Tell the form that we are about to exit a click handler,
4069 // so the form knows not to defer subsequent submissions.
4070 // The pending ones that were created during the handler
4071 // will be flushed or forgotten.
4072 form
->OnSubmitClickEnd();
4073 // tell the form to flush a possible pending submission.
4074 // the reason is that the script returned false (the event was
4075 // not ignored) so if there is a stored submission, it needs to
4076 // be submitted immediately.
4077 form
->FlushPendingSubmission();
4081 void HTMLInputElement::ActivationBehavior(EventChainPostVisitor
& aVisitor
) {
4082 auto oldType
= FormControlType(NS_CONTROL_TYPE(aVisitor
.mItemFlags
));
4084 if (IsDisabled() && oldType
!= FormControlType::InputCheckbox
&&
4085 oldType
!= FormControlType::InputRadio
) {
4086 // Behave as if defaultPrevented when the element becomes disabled by event
4087 // listeners. Checkboxes and radio buttons should still process clicks for
4089 // https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
4090 EndSubmitClick(aVisitor
);
4094 if (mCheckedIsToggled
) {
4095 SetUserInteracted(true);
4097 // Fire input event and then change event.
4098 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(this);
4099 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
4100 "Failed to dispatch input event");
4102 // FIXME: Why is this different than every other change event?
4103 nsContentUtils::DispatchTrustedEvent
<WidgetEvent
>(
4104 OwnerDoc(), static_cast<Element
*>(this), eFormChange
, CanBubble::eYes
,
4106 #ifdef ACCESSIBILITY
4107 // Fire an event to notify accessibility
4108 if (mType
== FormControlType::InputCheckbox
) {
4109 if (nsContentUtils::MayHaveFormCheckboxStateChangeListeners()) {
4110 FireEventForAccessibility(this, eFormCheckboxStateChange
);
4112 } else if (nsContentUtils::MayHaveFormRadioStateChangeListeners()) {
4113 FireEventForAccessibility(this, eFormRadioStateChange
);
4114 // Fire event for the previous selected radio.
4115 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aVisitor
.mItemData
);
4116 if (auto* previous
= HTMLInputElement::FromNodeOrNull(content
)) {
4117 FireEventForAccessibility(previous
, eFormRadioStateChange
);
4124 case FormControlType::InputReset
:
4125 case FormControlType::InputSubmit
:
4126 case FormControlType::InputImage
:
4128 // Hold a strong ref while dispatching
4129 RefPtr
<HTMLFormElement
> form(mForm
);
4130 if (mType
== FormControlType::InputReset
) {
4131 form
->MaybeReset(this);
4133 form
->MaybeSubmit(this);
4135 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
4142 if (IsButtonControl()) {
4143 if (!GetInvokeTargetElement()) {
4144 HandlePopoverTargetAction();
4146 HandleInvokeTargetAction();
4150 EndSubmitClick(aVisitor
);
4153 void HTMLInputElement::LegacyCanceledActivationBehavior(
4154 EventChainPostVisitor
& aVisitor
) {
4155 bool originalCheckedValue
=
4156 !!(aVisitor
.mItemFlags
& NS_ORIGINAL_CHECKED_VALUE
);
4157 auto oldType
= FormControlType(NS_CONTROL_TYPE(aVisitor
.mItemFlags
));
4159 if (mCheckedIsToggled
) {
4160 // if it was canceled and a radio button, then set the old
4161 // selected btn to TRUE. if it is a checkbox then set it to its
4162 // original value (legacy-canceled-activation)
4163 if (oldType
== FormControlType::InputRadio
) {
4164 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aVisitor
.mItemData
);
4165 HTMLInputElement
* selectedRadioButton
=
4166 HTMLInputElement::FromNodeOrNull(content
);
4167 if (selectedRadioButton
) {
4168 selectedRadioButton
->SetChecked(true);
4170 // If there was no checked radio button or this one is no longer a
4171 // radio button we must reset it back to false to cancel the action.
4172 // See how the web of hack grows?
4173 if (!selectedRadioButton
|| mType
!= FormControlType::InputRadio
) {
4174 DoSetChecked(false, true, true);
4176 } else if (oldType
== FormControlType::InputCheckbox
) {
4177 bool originalIndeterminateValue
=
4178 !!(aVisitor
.mItemFlags
& NS_ORIGINAL_INDETERMINATE_VALUE
);
4179 SetIndeterminateInternal(originalIndeterminateValue
, false);
4180 DoSetChecked(originalCheckedValue
, true, true);
4184 // Relevant for bug 242494: submit button with "submit(); return false;"
4185 EndSubmitClick(aVisitor
);
4188 enum class RadioButtonMove
{ Back
, Forward
, None
};
4189 nsresult
HTMLInputElement::MaybeHandleRadioButtonNavigation(
4190 EventChainPostVisitor
& aVisitor
, uint32_t aKeyCode
) {
4194 return RadioButtonMove::Back
;
4196 return RadioButtonMove::Forward
;
4199 const bool isRtl
= GetComputedDirectionality() == Directionality::Rtl
;
4200 return isRtl
== (aKeyCode
== NS_VK_LEFT
) ? RadioButtonMove::Forward
4201 : RadioButtonMove::Back
;
4204 return RadioButtonMove::None
;
4206 if (move
== RadioButtonMove::None
) {
4209 // Arrow key pressed, focus+select prev/next radio button
4210 RefPtr
<HTMLInputElement
> selectedRadioButton
;
4211 if (auto* container
= GetCurrentRadioGroupContainer()) {
4213 GetAttr(nsGkAtoms::name
, name
);
4214 container
->GetNextRadioButton(name
, move
== RadioButtonMove::Back
, this,
4215 getter_AddRefs(selectedRadioButton
));
4217 if (!selectedRadioButton
) {
4220 FocusOptions options
;
4222 selectedRadioButton
->Focus(options
, CallerType::System
, error
);
4223 if (error
.Failed()) {
4224 return error
.StealNSResult();
4226 nsresult rv
= DispatchSimulatedClick(
4227 selectedRadioButton
, aVisitor
.mEvent
->IsTrusted(), aVisitor
.mPresContext
);
4228 if (NS_SUCCEEDED(rv
)) {
4229 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
4234 void HTMLInputElement::PostHandleEventForRangeThumb(
4235 EventChainPostVisitor
& aVisitor
) {
4236 MOZ_ASSERT(mType
== FormControlType::InputRange
);
4238 if (nsEventStatus_eConsumeNoDefault
== aVisitor
.mEventStatus
||
4239 !(aVisitor
.mEvent
->mClass
== eMouseEventClass
||
4240 aVisitor
.mEvent
->mClass
== eTouchEventClass
||
4241 aVisitor
.mEvent
->mClass
== eKeyboardEventClass
)) {
4245 nsRangeFrame
* rangeFrame
= do_QueryFrame(GetPrimaryFrame());
4246 if (!rangeFrame
&& mIsDraggingRange
) {
4247 CancelRangeThumbDrag();
4251 switch (aVisitor
.mEvent
->mMessage
) {
4254 if (mIsDraggingRange
) {
4257 if (PresShell::GetCapturingContent()) {
4258 break; // don't start drag if someone else is already capturing
4260 WidgetInputEvent
* inputEvent
= aVisitor
.mEvent
->AsInputEvent();
4261 if (IgnoreInputEventWithModifier(*inputEvent
, true)) {
4264 if (aVisitor
.mEvent
->mMessage
== eMouseDown
) {
4265 if (aVisitor
.mEvent
->AsMouseEvent()->mButtons
==
4266 MouseButtonsFlag::ePrimaryFlag
) {
4267 StartRangeThumbDrag(inputEvent
);
4268 } else if (mIsDraggingRange
) {
4269 CancelRangeThumbDrag();
4272 if (aVisitor
.mEvent
->AsTouchEvent()->mTouches
.Length() == 1) {
4273 StartRangeThumbDrag(inputEvent
);
4274 } else if (mIsDraggingRange
) {
4275 CancelRangeThumbDrag();
4278 aVisitor
.mEvent
->mFlags
.mMultipleActionsPrevented
= true;
4283 if (!mIsDraggingRange
) {
4286 if (PresShell::GetCapturingContent() != this) {
4287 // Someone else grabbed capture.
4288 CancelRangeThumbDrag();
4291 SetValueOfRangeForUserEvent(
4292 rangeFrame
->GetValueAtEventPoint(aVisitor
.mEvent
->AsInputEvent()),
4293 SnapToTickMarks::Yes
);
4294 aVisitor
.mEvent
->mFlags
.mMultipleActionsPrevented
= true;
4299 if (!mIsDraggingRange
) {
4302 // We don't check to see whether we are the capturing content here and
4303 // call CancelRangeThumbDrag() if that is the case. We just finish off
4304 // the drag and set our final value (unless someone has called
4305 // preventDefault() and prevents us getting here).
4306 FinishRangeThumbDrag(aVisitor
.mEvent
->AsInputEvent());
4307 aVisitor
.mEvent
->mFlags
.mMultipleActionsPrevented
= true;
4311 if (mIsDraggingRange
&&
4312 aVisitor
.mEvent
->AsKeyboardEvent()->mKeyCode
== NS_VK_ESCAPE
) {
4313 CancelRangeThumbDrag();
4318 if (mIsDraggingRange
) {
4319 CancelRangeThumbDrag();
4328 void HTMLInputElement::MaybeLoadImage() {
4329 // Our base URI may have changed; claim that our URI changed, and the
4330 // nsImageLoadingContent will decide whether a new image load is warranted.
4332 if (mType
== FormControlType::InputImage
&& GetAttr(nsGkAtoms::src
, uri
) &&
4333 (NS_FAILED(LoadImage(uri
, false, true, eImageLoadType_Normal
,
4334 mSrcTriggeringPrincipal
)) ||
4335 !LoadingEnabled())) {
4336 CancelImageRequests(true);
4340 nsresult
HTMLInputElement::BindToTree(BindContext
& aContext
, nsINode
& aParent
) {
4341 // If we are currently bound to a disconnected subtree root, remove
4342 // ourselves from it first.
4343 if (!mForm
&& mType
== FormControlType::InputRadio
) {
4344 RemoveFromRadioGroup();
4348 nsGenericHTMLFormControlElementWithState::BindToTree(aContext
, aParent
);
4349 NS_ENSURE_SUCCESS(rv
, rv
);
4351 nsImageLoadingContent::BindToTree(aContext
, aParent
);
4353 if (mType
== FormControlType::InputImage
) {
4354 // Our base URI may have changed; claim that our URI changed, and the
4355 // nsImageLoadingContent will decide whether a new image load is warranted.
4356 if (HasAttr(nsGkAtoms::src
)) {
4357 // Mark channel as urgent-start before load image if the image load is
4358 // initaiated by a user interaction.
4359 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
4361 nsContentUtils::AddScriptRunner(
4362 NewRunnableMethod("dom::HTMLInputElement::MaybeLoadImage", this,
4363 &HTMLInputElement::MaybeLoadImage
));
4367 // Add radio to document if we don't have a form already (if we do it's
4368 // already been added into that group)
4369 if (!mForm
&& mType
== FormControlType::InputRadio
) {
4373 // Set direction based on value if dir=auto
4375 SetAutoDirectionality(false);
4378 // An element can't suffer from value missing if it is not in a document.
4379 // We have to check if we suffer from that as we are now in a document.
4380 UpdateValueMissingValidityState();
4382 // If there is a disabled fieldset in the parent chain, the element is now
4383 // barred from constraint validation and can't suffer from value missing
4384 // (call done before).
4385 UpdateBarredFromConstraintValidation();
4387 // And now make sure our state is up to date
4388 UpdateValidityElementStates(true);
4390 if (CreatesDateTimeWidget() && IsInComposedDoc()) {
4391 // Construct Shadow Root so web content can be hidden in the DOM.
4392 AttachAndSetUAShadowRoot(NotifyUAWidgetSetup::Yes
, DelegatesFocus::Yes
);
4395 MaybeDispatchLoginManagerEvents(mForm
);
4400 void HTMLInputElement::MaybeDispatchLoginManagerEvents(HTMLFormElement
* aForm
) {
4401 // Don't disptach the event if the <input> is disconnected
4402 // or belongs to a disconnected form
4403 if (!IsInComposedDoc()) {
4408 Element
* target
= nullptr;
4410 if (mType
== FormControlType::InputPassword
) {
4411 // Don't fire another event if we have a pending event.
4412 if (aForm
&& aForm
->mHasPendingPasswordEvent
) {
4416 // TODO(Bug 1864404): Use one event for formless and form inputs.
4417 eventType
= aForm
? u
"DOMFormHasPassword"_ns
: u
"DOMInputPasswordAdded"_ns
;
4419 target
= aForm
? static_cast<Element
*>(aForm
) : this;
4422 aForm
->mHasPendingPasswordEvent
= true;
4425 } else if (mType
== FormControlType::InputEmail
||
4426 mType
== FormControlType::InputText
) {
4427 // Don't fire a username event if:
4428 // - <input> is not part of a form
4429 // - we have a pending event
4430 // - username only forms are not supported
4431 if (!aForm
|| aForm
->mHasPendingPossibleUsernameEvent
||
4432 !StaticPrefs::signon_usernameOnlyForm_enabled()) {
4436 eventType
= u
"DOMFormHasPossibleUsername"_ns
;
4439 aForm
->mHasPendingPossibleUsernameEvent
= true;
4445 RefPtr
<AsyncEventDispatcher
> dispatcher
= new AsyncEventDispatcher(
4446 target
, eventType
, CanBubble::eYes
, ChromeOnlyDispatch::eYes
);
4447 dispatcher
->PostDOMEvent();
4450 void HTMLInputElement::UnbindFromTree(UnbindContext
& aContext
) {
4451 if (mType
== FormControlType::InputPassword
) {
4452 MaybeFireInputPasswordRemoved();
4455 // If we have a form and are unbound from it,
4456 // nsGenericHTMLFormControlElementWithState::UnbindFromTree() will unset the
4457 // form and that takes care of form's WillRemove so we just have to take care
4458 // of the case where we're removing from the document and we don't
4460 if (!mForm
&& mType
== FormControlType::InputRadio
) {
4461 RemoveFromRadioGroup();
4464 if (CreatesDateTimeWidget() && IsInComposedDoc()) {
4465 NotifyUAWidgetTeardown();
4468 nsImageLoadingContent::UnbindFromTree();
4469 nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext
);
4471 // If we are contained within a disconnected subtree, attempt to add
4472 // ourselves to the subtree root's radio group.
4473 if (!mForm
&& mType
== FormControlType::InputRadio
) {
4477 // GetCurrentDoc is returning nullptr so we can update the value
4478 // missing validity state to reflect we are no longer into a doc.
4479 UpdateValueMissingValidityState();
4480 // We might be no longer disabled because of parent chain changed.
4481 UpdateBarredFromConstraintValidation();
4482 // And now make sure our state is up to date
4483 UpdateValidityElementStates(false);
4487 * @param aType InputElementTypes
4488 * @return true, iff SetRangeText applies to aType as specified at
4489 * https://html.spec.whatwg.org/#concept-input-apply.
4491 static bool SetRangeTextApplies(FormControlType aType
) {
4492 return aType
== FormControlType::InputText
||
4493 aType
== FormControlType::InputSearch
||
4494 aType
== FormControlType::InputUrl
||
4495 aType
== FormControlType::InputTel
||
4496 aType
== FormControlType::InputPassword
;
4499 void HTMLInputElement::HandleTypeChange(FormControlType aNewType
,
4501 FormControlType oldType
= mType
;
4502 MOZ_ASSERT(oldType
!= aNewType
);
4504 mHasBeenTypePassword
=
4505 mHasBeenTypePassword
|| aNewType
== FormControlType::InputPassword
;
4507 if (nsFocusManager
* fm
= nsFocusManager::GetFocusManager()) {
4508 // Input element can represent very different kinds of UIs, and we may
4509 // need to flush styling even when focusing the already focused input
4511 fm
->NeedsFlushBeforeEventHandling(this);
4514 if (oldType
== FormControlType::InputPassword
&&
4515 State().HasState(ElementState::REVEALED
)) {
4516 // Modify the state directly to avoid dispatching events.
4517 RemoveStates(ElementState::REVEALED
, aNotify
);
4520 if (aNewType
== FormControlType::InputFile
||
4521 oldType
== FormControlType::InputFile
) {
4522 if (aNewType
== FormControlType::InputFile
) {
4523 mFileData
.reset(new FileData());
4525 mFileData
->Unlink();
4526 mFileData
= nullptr;
4530 if (oldType
== FormControlType::InputRange
&& mIsDraggingRange
) {
4531 CancelRangeThumbDrag(false);
4534 const ValueModeType oldValueMode
= GetValueMode();
4535 nsAutoString oldValue
;
4536 if (oldValueMode
== VALUE_MODE_VALUE
) {
4537 // Doesn't matter what caller type we pass here, since we know we're not a
4538 // file input anyway.
4539 GetValue(oldValue
, CallerType::NonSystem
);
4542 TextControlState::SelectionProperties sp
;
4544 if (IsSingleLineTextControl(false) && mInputData
.mState
) {
4545 mInputData
.mState
->SyncUpSelectionPropertiesBeforeDestruction();
4546 sp
= mInputData
.mState
->GetSelectionProperties();
4549 // We already have a copy of the value, lets free it and changes the type.
4552 void* memory
= mInputTypeMem
;
4553 mInputType
= InputType::Create(this, mType
, memory
);
4555 if (IsSingleLineTextControl()) {
4556 mInputData
.mState
= TextControlState::Construct(this);
4557 if (!sp
.IsDefault()) {
4558 mInputData
.mState
->SetSelectionProperties(sp
);
4562 // Whether placeholder applies might have changed.
4563 UpdatePlaceholderShownState();
4564 // Whether readonly applies might have changed.
4565 UpdateReadOnlyState(aNotify
);
4566 UpdateCheckedState(aNotify
);
4567 UpdateIndeterminateState(aNotify
);
4568 const bool isDefault
= IsRadioOrCheckbox()
4570 : (mForm
&& mForm
->IsDefaultSubmitElement(this));
4571 SetStates(ElementState::DEFAULT
, isDefault
, aNotify
);
4573 // https://html.spec.whatwg.org/#input-type-change
4574 switch (GetValueMode()) {
4575 case VALUE_MODE_DEFAULT
:
4576 case VALUE_MODE_DEFAULT_ON
:
4577 // 1. If the previous state of the element's type attribute put the value
4578 // IDL attribute in the value mode, and the element's value is not the
4579 // empty string, and the new state of the element's type attribute puts
4580 // the value IDL attribute in either the default mode or the default/on
4581 // mode, then set the element's value content attribute to the
4583 if (oldValueMode
== VALUE_MODE_VALUE
&& !oldValue
.IsEmpty()) {
4584 SetAttr(kNameSpaceID_None
, nsGkAtoms::value
, oldValue
, true);
4587 case VALUE_MODE_VALUE
: {
4588 ValueSetterOptions options
{ValueSetterOption::ByInternalAPI
};
4589 if (!SetRangeTextApplies(oldType
) && SetRangeTextApplies(mType
)) {
4591 ValueSetterOption::MoveCursorToBeginSetSelectionDirectionForward
;
4593 if (oldValueMode
!= VALUE_MODE_VALUE
) {
4594 // 2. Otherwise, if the previous state of the element's type attribute
4595 // put the value IDL attribute in any mode other than the value
4596 // mode, and the new state of the element's type attribute puts the
4597 // value IDL attribute in the value mode, then set the value of the
4598 // element to the value of the value content attribute, if there is
4599 // one, or the empty string otherwise, and then set the control's
4600 // dirty value flag to false.
4602 GetAttr(nsGkAtoms::value
, value
);
4603 SetValueInternal(value
, options
);
4604 SetValueChanged(false);
4605 } else if (mValueChanged
) {
4606 // We're both in the "value" mode state, we need to make no change per
4607 // spec, but due to how we store the value internally we need to call
4608 // SetValueInternal, if our value had changed at all.
4609 // TODO: What should we do if SetValueInternal fails? (The allocation
4610 // may potentially be big, but most likely we've failed to allocate
4611 // before the type change.)
4612 SetValueInternal(oldValue
, options
);
4614 // The value dirty flag is not set, so our value is based on our default
4615 // value. But our default value might be dependent on the type. Make
4616 // sure to set it so that state is consistent.
4617 SetDefaultValueAsValue();
4621 case VALUE_MODE_FILENAME
:
4623 // 3. Otherwise, if the previous state of the element's type attribute
4624 // put the value IDL attribute in any mode other than the filename
4625 // mode, and the new state of the element's type attribute puts the
4626 // value IDL attribute in the filename mode, then set the value of the
4627 // element to the empty string.
4629 // Setting the attribute to the empty string is basically calling
4630 // ClearFiles, but there can't be any files.
4634 // Updating mFocusedValue in consequence:
4635 // If the new type fires a change event on blur, but the previous type
4636 // doesn't, we should set mFocusedValue to the current value.
4637 // Otherwise, if the new type doesn't fire a change event on blur, but the
4638 // previous type does, we should clear out mFocusedValue.
4639 if (MayFireChangeOnBlur(mType
) && !MayFireChangeOnBlur(oldType
)) {
4640 GetValue(mFocusedValue
, CallerType::System
);
4641 } else if (!IsSingleLineTextControl(false, mType
) &&
4642 IsSingleLineTextControl(false, oldType
)) {
4643 mFocusedValue
.Truncate();
4646 // Update or clear our required states since we may have changed from a
4647 // required input type to a non-required input type or viceversa.
4648 if (DoesRequiredApply()) {
4649 const bool isRequired
= HasAttr(nsGkAtoms::required
);
4650 UpdateRequiredState(isRequired
, aNotify
);
4652 RemoveStates(ElementState::REQUIRED_STATES
, aNotify
);
4655 UpdateHasRange(aNotify
);
4657 // Update validity states, but not element state. We'll update
4658 // element state later, as part of this attribute change.
4659 UpdateAllValidityStatesButNotElementState();
4661 UpdateApzAwareFlag();
4663 UpdateBarredFromConstraintValidation();
4665 // Changing type may affect auto directionality, or non-auto directionality
4666 // because of the special-case for <input type=tel>, as specified in
4667 // https://html.spec.whatwg.org/multipage/dom.html#the-directionality
4669 const bool autoDirAssociated
= IsAutoDirectionalityAssociated(mType
);
4670 if (IsAutoDirectionalityAssociated(oldType
) != autoDirAssociated
) {
4671 SetAutoDirectionality(aNotify
);
4673 } else if (oldType
== FormControlType::InputTel
||
4674 mType
== FormControlType::InputTel
) {
4675 RecomputeDirectionality(this, aNotify
);
4678 if (oldType
== FormControlType::InputImage
||
4679 mType
== FormControlType::InputImage
) {
4680 if (oldType
== FormControlType::InputImage
) {
4681 // We're no longer an image input. Cancel our image requests, if we have
4683 CancelImageRequests(aNotify
);
4684 RemoveStates(ElementState::BROKEN
, aNotify
);
4686 // We just got switched to be an image input; we should see whether we
4687 // have an image to load;
4688 bool hasSrc
= false;
4691 if ((hasSrc
= GetAttr(nsGkAtoms::src
, src
))) {
4692 // Mark channel as urgent-start before load image if the image load is
4693 // initiated by a user interaction.
4694 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
4696 LoadImage(src
, false, aNotify
, eImageLoadType_Normal
,
4697 mSrcTriggeringPrincipal
);
4700 hasSrc
= HasAttr(nsGkAtoms::src
);
4703 AddStates(ElementState::BROKEN
, aNotify
);
4706 // We should update our mapped attribute mapping function.
4707 if (mAttrs
.HasAttrs() && !mAttrs
.IsPendingMappedAttributeEvaluation()) {
4708 mAttrs
.InfallibleMarkAsPendingPresAttributeEvaluation();
4709 if (auto* doc
= GetComposedDoc()) {
4710 doc
->ScheduleForPresAttrEvaluation(this);
4715 MaybeDispatchLoginManagerEvents(mForm
);
4717 if (IsInComposedDoc()) {
4718 if (CreatesDateTimeWidget(oldType
)) {
4719 if (!CreatesDateTimeWidget()) {
4720 // Switch away from date/time type.
4721 NotifyUAWidgetTeardown();
4723 // Switch between date and time.
4724 NotifyUAWidgetSetupOrChange();
4726 } else if (CreatesDateTimeWidget()) {
4727 // Switch to date/time type.
4728 AttachAndSetUAShadowRoot(NotifyUAWidgetSetup::Yes
, DelegatesFocus::Yes
);
4730 // If we're becoming a text control and have focus, make sure to show focus
4732 if (State().HasState(ElementState::FOCUS
) && IsSingleLineTextControl() &&
4733 !IsSingleLineTextControl(/* aExcludePassword = */ false, oldType
)) {
4734 AddStates(ElementState::FOCUSRING
);
4739 void HTMLInputElement::MaybeSnapToTickMark(Decimal
& aValue
) {
4740 nsRangeFrame
* rangeFrame
= do_QueryFrame(GetPrimaryFrame());
4744 auto tickMark
= rangeFrame
->NearestTickMark(aValue
);
4745 if (tickMark
.isNaN()) {
4748 auto rangeFrameSize
= CSSPixel::FromAppUnits(rangeFrame
->GetSize());
4749 CSSCoord rangeTrackLength
;
4750 if (rangeFrame
->IsHorizontal()) {
4751 rangeTrackLength
= rangeFrameSize
.width
;
4753 rangeTrackLength
= rangeFrameSize
.height
;
4755 auto stepBase
= GetStepBase();
4756 auto distanceToTickMark
=
4757 rangeTrackLength
* float(rangeFrame
->GetDoubleAsFractionOfRange(
4758 stepBase
+ (tickMark
- aValue
).abs()));
4759 const CSSCoord
magnetEffectRange(
4760 StaticPrefs::dom_range_element_magnet_effect_threshold());
4761 if (distanceToTickMark
<= magnetEffectRange
) {
4766 void HTMLInputElement::SanitizeValue(nsAString
& aValue
,
4767 SanitizationKind aKind
) const {
4768 NS_ASSERTION(mDoneCreating
, "The element creation should be finished!");
4771 case FormControlType::InputText
:
4772 case FormControlType::InputSearch
:
4773 case FormControlType::InputTel
:
4774 case FormControlType::InputPassword
: {
4777 case FormControlType::InputEmail
: {
4779 aValue
= nsContentUtils::TrimWhitespace
<nsContentUtils::IsHTMLWhitespace
>(
4782 if (Multiple() && !aValue
.IsEmpty()) {
4783 nsAutoString
oldValue(aValue
);
4784 HTMLSplitOnSpacesTokenizer
tokenizer(oldValue
, ',');
4786 aValue
.Append(tokenizer
.nextToken());
4787 while (tokenizer
.hasMoreTokens() ||
4788 tokenizer
.separatorAfterCurrentToken()) {
4790 aValue
.Append(tokenizer
.nextToken());
4794 case FormControlType::InputUrl
: {
4797 aValue
= nsContentUtils::TrimWhitespace
<nsContentUtils::IsHTMLWhitespace
>(
4800 case FormControlType::InputNumber
: {
4801 if (aKind
== SanitizationKind::ForValueSetter
&& !aValue
.IsEmpty() &&
4802 (aValue
.First() == '+' || aValue
.Last() == '.')) {
4803 // A value with a leading plus or trailing dot should fail to parse.
4804 // However, the localized parser accepts this, and when we convert it
4805 // back to a Decimal, it disappears. So, we need to check first.
4807 // FIXME(emilio): Should we just use the unlocalized parser
4808 // (StringToDecimal) for the value setter? Other browsers don't seem to
4809 // allow setting localized strings there, and that way we don't need
4810 // this special-case.
4815 InputType::StringToNumberResult result
=
4816 mInputType
->ConvertStringToNumber(aValue
);
4817 if (!result
.mResult
.isFinite()) {
4822 case SanitizationKind::ForValueGetter
: {
4823 // If the default non-localized algorithm parses the value, then we're
4824 // done, don't un-localize it, to avoid precision loss, and to
4825 // preserve scientific notation as well for example.
4826 if (!result
.mLocalized
) {
4829 // For the <input type=number> value getter, we return the unlocalized
4830 // value if it doesn't parse as StringToDecimal, for compat with other
4833 DebugOnly
<bool> ok
= result
.mResult
.toString(buf
, ArrayLength(buf
));
4834 aValue
.AssignASCII(buf
);
4835 MOZ_ASSERT(ok
, "buf not big enough");
4838 case SanitizationKind::ForDisplay
:
4839 case SanitizationKind::ForValueSetter
: {
4840 // We localize as needed, but if both the localized and unlocalized
4841 // version parse with the generic parser, we just use the unlocalized
4842 // one, to preserve the input as much as possible.
4844 // FIXME(emilio, bug 1622808): Localization should ideally be more
4845 // input-preserving.
4846 nsString localizedValue
;
4847 mInputType
->ConvertNumberToString(result
.mResult
, localizedValue
);
4848 if (!StringToDecimal(localizedValue
).isFinite()) {
4849 aValue
= std::move(localizedValue
);
4856 case FormControlType::InputRange
: {
4857 Decimal minimum
= GetMinimum();
4858 Decimal maximum
= GetMaximum();
4859 MOZ_ASSERT(minimum
.isFinite() && maximum
.isFinite(),
4860 "type=range should have a default maximum/minimum");
4862 // We use this to avoid modifying the string unnecessarily, since that
4863 // may introduce rounding. This is set to true only if the value we
4864 // parse out from aValue needs to be sanitized.
4865 bool needSanitization
= false;
4867 Decimal value
= mInputType
->ConvertStringToNumber(aValue
).mResult
;
4868 if (!value
.isFinite()) {
4869 needSanitization
= true;
4870 // Set value to midway between minimum and maximum.
4871 value
= maximum
<= minimum
? minimum
4872 : minimum
+ (maximum
- minimum
) / Decimal(2);
4873 } else if (value
< minimum
|| maximum
< minimum
) {
4874 needSanitization
= true;
4876 } else if (value
> maximum
) {
4877 needSanitization
= true;
4881 Decimal step
= GetStep();
4882 if (step
!= kStepAny
) {
4883 Decimal stepBase
= GetStepBase();
4884 // There could be rounding issues below when dealing with fractional
4885 // numbers, but let's ignore that until ECMAScript supplies us with a
4886 // decimal number type.
4887 Decimal deltaToStep
= NS_floorModulo(value
- stepBase
, step
);
4888 if (deltaToStep
!= Decimal(0)) {
4889 // "suffering from a step mismatch"
4890 // Round the element's value to the nearest number for which the
4891 // element would not suffer from a step mismatch, and which is
4892 // greater than or equal to the minimum, and, if the maximum is not
4893 // less than the minimum, which is less than or equal to the
4894 // maximum, if there is a number that matches these constraints:
4895 MOZ_ASSERT(deltaToStep
> Decimal(0),
4896 "stepBelow/stepAbove will be wrong");
4897 Decimal stepBelow
= value
- deltaToStep
;
4898 Decimal stepAbove
= value
- deltaToStep
+ step
;
4899 Decimal halfStep
= step
/ Decimal(2);
4900 bool stepAboveIsClosest
= (stepAbove
- value
) <= halfStep
;
4901 bool stepAboveInRange
= stepAbove
>= minimum
&& stepAbove
<= maximum
;
4902 bool stepBelowInRange
= stepBelow
>= minimum
&& stepBelow
<= maximum
;
4904 if ((stepAboveIsClosest
|| !stepBelowInRange
) && stepAboveInRange
) {
4905 needSanitization
= true;
4907 } else if ((!stepAboveIsClosest
|| !stepAboveInRange
) &&
4909 needSanitization
= true;
4915 if (needSanitization
) {
4917 DebugOnly
<bool> ok
= value
.toString(buf
, ArrayLength(buf
));
4918 aValue
.AssignASCII(buf
);
4919 MOZ_ASSERT(ok
, "buf not big enough");
4922 case FormControlType::InputDate
: {
4923 if (!aValue
.IsEmpty() && !IsValidDate(aValue
)) {
4927 case FormControlType::InputTime
: {
4928 if (!aValue
.IsEmpty() && !IsValidTime(aValue
)) {
4932 case FormControlType::InputMonth
: {
4933 if (!aValue
.IsEmpty() && !IsValidMonth(aValue
)) {
4937 case FormControlType::InputWeek
: {
4938 if (!aValue
.IsEmpty() && !IsValidWeek(aValue
)) {
4942 case FormControlType::InputDatetimeLocal
: {
4943 if (!aValue
.IsEmpty() && !IsValidDateTimeLocal(aValue
)) {
4946 NormalizeDateTimeLocal(aValue
);
4949 case FormControlType::InputColor
: {
4950 if (IsValidSimpleColor(aValue
)) {
4951 ToLowerCase(aValue
);
4953 // Set default (black) color, if aValue wasn't parsed correctly.
4954 aValue
.AssignLiteral("#000000");
4962 Maybe
<nscolor
> HTMLInputElement::ParseSimpleColor(const nsAString
& aColor
) {
4963 // Input color string should be 7 length (i.e. a string representing a valid
4965 if (aColor
.Length() != 7 || aColor
.First() != '#') {
4969 const nsAString
& withoutHash
= StringTail(aColor
, 6);
4971 if (!NS_HexToRGBA(withoutHash
, nsHexColorType::NoAlpha
, &color
)) {
4978 bool HTMLInputElement::IsValidSimpleColor(const nsAString
& aValue
) const {
4979 if (aValue
.Length() != 7 || aValue
.First() != '#') {
4983 for (int i
= 1; i
< 7; ++i
) {
4984 if (!IsAsciiDigit(aValue
[i
]) && !(aValue
[i
] >= 'a' && aValue
[i
] <= 'f') &&
4985 !(aValue
[i
] >= 'A' && aValue
[i
] <= 'F')) {
4992 bool HTMLInputElement::IsLeapYear(uint32_t aYear
) const {
4993 if ((aYear
% 4 == 0 && aYear
% 100 != 0) || (aYear
% 400 == 0)) {
4999 uint32_t HTMLInputElement::DayOfWeek(uint32_t aYear
, uint32_t aMonth
,
5000 uint32_t aDay
, bool isoWeek
) const {
5001 MOZ_ASSERT(1 <= aMonth
&& aMonth
<= 12, "month is in 1..12");
5002 MOZ_ASSERT(1 <= aDay
&& aDay
<= 31, "day is in 1..31");
5004 // Tomohiko Sakamoto algorithm.
5005 int monthTable
[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
5006 aYear
-= aMonth
< 3;
5008 uint32_t day
= (aYear
+ aYear
/ 4 - aYear
/ 100 + aYear
/ 400 +
5009 monthTable
[aMonth
- 1] + aDay
) %
5013 return ((day
+ 6) % 7) + 1;
5019 uint32_t HTMLInputElement::MaximumWeekInYear(uint32_t aYear
) const {
5020 int day
= DayOfWeek(aYear
, 1, 1, true); // January 1.
5021 // A year starting on Thursday or a leap year starting on Wednesday has 53
5022 // weeks. All other years have 52 weeks.
5023 return day
== 4 || (day
== 3 && IsLeapYear(aYear
)) ? kMaximumWeekInYear
5024 : kMaximumWeekInYear
- 1;
5027 bool HTMLInputElement::IsValidWeek(const nsAString
& aValue
) const {
5028 uint32_t year
, week
;
5029 return ParseWeek(aValue
, &year
, &week
);
5032 bool HTMLInputElement::IsValidMonth(const nsAString
& aValue
) const {
5033 uint32_t year
, month
;
5034 return ParseMonth(aValue
, &year
, &month
);
5037 bool HTMLInputElement::IsValidDate(const nsAString
& aValue
) const {
5038 uint32_t year
, month
, day
;
5039 return ParseDate(aValue
, &year
, &month
, &day
);
5042 bool HTMLInputElement::IsValidDateTimeLocal(const nsAString
& aValue
) const {
5043 uint32_t year
, month
, day
, time
;
5044 return ParseDateTimeLocal(aValue
, &year
, &month
, &day
, &time
);
5047 bool HTMLInputElement::ParseYear(const nsAString
& aValue
,
5048 uint32_t* aYear
) const {
5049 if (aValue
.Length() < 4) {
5053 return DigitSubStringToNumber(aValue
, 0, aValue
.Length(), aYear
) &&
5057 bool HTMLInputElement::ParseMonth(const nsAString
& aValue
, uint32_t* aYear
,
5058 uint32_t* aMonth
) const {
5059 // Parse the year, month values out a string formatted as 'yyyy-mm'.
5060 if (aValue
.Length() < 7) {
5064 uint32_t endOfYearOffset
= aValue
.Length() - 3;
5065 if (aValue
[endOfYearOffset
] != '-') {
5069 const nsAString
& yearStr
= Substring(aValue
, 0, endOfYearOffset
);
5070 if (!ParseYear(yearStr
, aYear
)) {
5074 return DigitSubStringToNumber(aValue
, endOfYearOffset
+ 1, 2, aMonth
) &&
5075 *aMonth
> 0 && *aMonth
<= 12;
5078 bool HTMLInputElement::ParseWeek(const nsAString
& aValue
, uint32_t* aYear
,
5079 uint32_t* aWeek
) const {
5080 // Parse the year, month values out a string formatted as 'yyyy-Www'.
5081 if (aValue
.Length() < 8) {
5085 uint32_t endOfYearOffset
= aValue
.Length() - 4;
5086 if (aValue
[endOfYearOffset
] != '-') {
5090 if (aValue
[endOfYearOffset
+ 1] != 'W') {
5094 const nsAString
& yearStr
= Substring(aValue
, 0, endOfYearOffset
);
5095 if (!ParseYear(yearStr
, aYear
)) {
5099 return DigitSubStringToNumber(aValue
, endOfYearOffset
+ 2, 2, aWeek
) &&
5100 *aWeek
> 0 && *aWeek
<= MaximumWeekInYear(*aYear
);
5103 bool HTMLInputElement::ParseDate(const nsAString
& aValue
, uint32_t* aYear
,
5104 uint32_t* aMonth
, uint32_t* aDay
) const {
5106 * Parse the year, month, day values out a date string formatted as
5107 * yyyy-mm-dd. -The year must be 4 or more digits long, and year > 0 -The
5108 * month must be exactly 2 digits long, and 01 <= month <= 12 -The day must be
5109 * exactly 2 digit long, and 01 <= day <= maxday Where maxday is the number of
5110 * days in the month 'month' and year 'year'
5112 if (aValue
.Length() < 10) {
5116 uint32_t endOfMonthOffset
= aValue
.Length() - 3;
5117 if (aValue
[endOfMonthOffset
] != '-') {
5121 const nsAString
& yearMonthStr
= Substring(aValue
, 0, endOfMonthOffset
);
5122 if (!ParseMonth(yearMonthStr
, aYear
, aMonth
)) {
5126 return DigitSubStringToNumber(aValue
, endOfMonthOffset
+ 1, 2, aDay
) &&
5127 *aDay
> 0 && *aDay
<= NumberOfDaysInMonth(*aMonth
, *aYear
);
5130 bool HTMLInputElement::ParseDateTimeLocal(const nsAString
& aValue
,
5131 uint32_t* aYear
, uint32_t* aMonth
,
5133 uint32_t* aTime
) const {
5134 // Parse the year, month, day and time values out a string formatted as
5135 // 'yyyy-mm-ddThh:mm[:ss.s] or 'yyyy-mm-dd hh:mm[:ss.s]', where fractions of
5136 // seconds can be 1 to 3 digits.
5137 // The minimum length allowed is 16, which is of the form 'yyyy-mm-ddThh:mm'
5138 // or 'yyyy-mm-dd hh:mm'.
5139 if (aValue
.Length() < 16) {
5143 int32_t sepIndex
= aValue
.FindChar('T');
5144 if (sepIndex
== -1) {
5145 sepIndex
= aValue
.FindChar(' ');
5147 if (sepIndex
== -1) {
5152 const nsAString
& dateStr
= Substring(aValue
, 0, sepIndex
);
5153 if (!ParseDate(dateStr
, aYear
, aMonth
, aDay
)) {
5157 const nsAString
& timeStr
=
5158 Substring(aValue
, sepIndex
+ 1, aValue
.Length() - sepIndex
+ 1);
5159 if (!ParseTime(timeStr
, aTime
)) {
5166 void HTMLInputElement::NormalizeDateTimeLocal(nsAString
& aValue
) const {
5167 if (aValue
.IsEmpty()) {
5171 // Use 'T' as the separator between date string and time string.
5172 int32_t sepIndex
= aValue
.FindChar(' ');
5173 if (sepIndex
!= -1) {
5174 aValue
.ReplaceLiteral(sepIndex
, 1, u
"T");
5176 sepIndex
= aValue
.FindChar('T');
5179 // Time expressed as the shortest possible string, which is hh:mm.
5180 if ((aValue
.Length() - sepIndex
) == 6) {
5184 // Fractions of seconds part is optional, ommit it if it's 0.
5185 if ((aValue
.Length() - sepIndex
) > 9) {
5186 const uint32_t millisecSepIndex
= sepIndex
+ 9;
5187 uint32_t milliseconds
;
5188 if (!DigitSubStringToNumber(aValue
, millisecSepIndex
+ 1,
5189 aValue
.Length() - (millisecSepIndex
+ 1),
5194 if (milliseconds
!= 0) {
5198 aValue
.Cut(millisecSepIndex
, aValue
.Length() - millisecSepIndex
);
5201 // Seconds part is optional, ommit it if it's 0.
5202 const uint32_t secondSepIndex
= sepIndex
+ 6;
5204 if (!DigitSubStringToNumber(aValue
, secondSepIndex
+ 1,
5205 aValue
.Length() - (secondSepIndex
+ 1),
5214 aValue
.Cut(secondSepIndex
, aValue
.Length() - secondSepIndex
);
5217 double HTMLInputElement::DaysSinceEpochFromWeek(uint32_t aYear
,
5218 uint32_t aWeek
) const {
5219 double days
= JS::DayFromYear(aYear
) + (aWeek
- 1) * 7;
5220 uint32_t dayOneIsoWeekday
= DayOfWeek(aYear
, 1, 1, true);
5222 // If day one of that year is on/before Thursday, we should subtract the
5223 // days that belong to last year in our first week, otherwise, our first
5224 // days belong to last year's last week, and we should add those days
5226 if (dayOneIsoWeekday
<= 4) {
5227 days
-= (dayOneIsoWeekday
- 1);
5229 days
+= (7 - dayOneIsoWeekday
+ 1);
5235 uint32_t HTMLInputElement::NumberOfDaysInMonth(uint32_t aMonth
,
5236 uint32_t aYear
) const {
5238 * Returns the number of days in a month.
5239 * Months that are |longMonths| always have 31 days.
5240 * Months that are not |longMonths| have 30 days except February (month 2).
5241 * February has 29 days during leap years which are years that are divisible
5242 * by 400. or divisible by 100 and 4. February has 28 days otherwise.
5245 static const bool longMonths
[] = {true, false, true, false, true, false,
5246 true, true, false, true, false, true};
5247 MOZ_ASSERT(aMonth
<= 12 && aMonth
> 0);
5249 if (longMonths
[aMonth
- 1]) {
5257 return IsLeapYear(aYear
) ? 29 : 28;
5261 bool HTMLInputElement::DigitSubStringToNumber(const nsAString
& aStr
,
5262 uint32_t aStart
, uint32_t aLen
,
5263 uint32_t* aRetVal
) {
5264 MOZ_ASSERT(aStr
.Length() > (aStart
+ aLen
- 1));
5266 for (uint32_t offset
= 0; offset
< aLen
; ++offset
) {
5267 if (!IsAsciiDigit(aStr
[aStart
+ offset
])) {
5273 *aRetVal
= static_cast<uint32_t>(
5274 PromiseFlatString(Substring(aStr
, aStart
, aLen
)).ToInteger(&ec
));
5276 return NS_SUCCEEDED(ec
);
5279 bool HTMLInputElement::IsValidTime(const nsAString
& aValue
) const {
5280 return ParseTime(aValue
, nullptr);
5284 bool HTMLInputElement::ParseTime(const nsAString
& aValue
, uint32_t* aResult
) {
5285 /* The string must have the following parts:
5286 * - HOURS: two digits, value being in [0, 23];
5288 * - MINUTES: two digits, value being in [0, 59];
5291 * - SECONDS: two digits, value being in [0, 59];
5294 * - FRACTIONAL SECONDS: one to three digits, no value range.
5297 // The following format is the shorter one allowed: "HH:MM".
5298 if (aValue
.Length() < 5) {
5303 if (!DigitSubStringToNumber(aValue
, 0, 2, &hours
) || hours
> 23) {
5307 // Hours/minutes separator.
5308 if (aValue
[2] != ':') {
5313 if (!DigitSubStringToNumber(aValue
, 3, 2, &minutes
) || minutes
> 59) {
5317 if (aValue
.Length() == 5) {
5319 *aResult
= ((hours
* 60) + minutes
) * 60000;
5324 // The following format is the next shorter one: "HH:MM:SS".
5325 if (aValue
.Length() < 8 || aValue
[5] != ':') {
5330 if (!DigitSubStringToNumber(aValue
, 6, 2, &seconds
) || seconds
> 59) {
5334 if (aValue
.Length() == 8) {
5336 *aResult
= (((hours
* 60) + minutes
) * 60 + seconds
) * 1000;
5341 // The string must follow this format now: "HH:MM:SS.{s,ss,sss}".
5342 // There can be 1 to 3 digits for the fractions of seconds.
5343 if (aValue
.Length() == 9 || aValue
.Length() > 12 || aValue
[8] != '.') {
5347 uint32_t fractionsSeconds
;
5348 if (!DigitSubStringToNumber(aValue
, 9, aValue
.Length() - 9,
5349 &fractionsSeconds
)) {
5354 *aResult
= (((hours
* 60) + minutes
) * 60 + seconds
) * 1000 +
5355 // NOTE: there is 10.0 instead of 10 and static_cast<int> because
5356 // some old [and stupid] compilers can't just do the right thing.
5358 pow(10.0, static_cast<int>(3 - (aValue
.Length() - 9)));
5365 bool HTMLInputElement::IsDateTimeTypeSupported(
5366 FormControlType aDateTimeInputType
) {
5367 switch (aDateTimeInputType
) {
5368 case FormControlType::InputDate
:
5369 case FormControlType::InputTime
:
5370 case FormControlType::InputDatetimeLocal
:
5372 case FormControlType::InputMonth
:
5373 case FormControlType::InputWeek
:
5374 return StaticPrefs::dom_forms_datetime_others();
5380 void HTMLInputElement::GetLastInteractiveValue(nsAString
& aValue
) {
5381 if (mLastValueChangeWasInteractive
) {
5382 return GetValue(aValue
, CallerType::System
);
5384 if (TextControlState
* state
= GetEditorState()) {
5385 return aValue
.Assign(
5386 state
->LastInteractiveValueIfLastChangeWasNonInteractive());
5391 bool HTMLInputElement::ParseAttribute(int32_t aNamespaceID
, nsAtom
* aAttribute
,
5392 const nsAString
& aValue
,
5393 nsIPrincipal
* aMaybeScriptedPrincipal
,
5394 nsAttrValue
& aResult
) {
5395 // We can't make these static_asserts because kInputDefaultType and
5396 // kInputTypeTable aren't constexpr.
5398 FormControlType(kInputDefaultType
->value
) == FormControlType::InputText
,
5399 "Someone forgot to update kInputDefaultType when adding a new "
5401 MOZ_ASSERT(kInputTypeTable
[ArrayLength(kInputTypeTable
) - 1].tag
== nullptr,
5402 "Last entry in the table must be the nullptr guard");
5403 MOZ_ASSERT(FormControlType(
5404 kInputTypeTable
[ArrayLength(kInputTypeTable
) - 2].value
) ==
5405 FormControlType::InputText
,
5406 "Next to last entry in the table must be the \"text\" entry");
5408 if (aNamespaceID
== kNameSpaceID_None
) {
5409 if (aAttribute
== nsGkAtoms::type
) {
5410 aResult
.ParseEnumValue(aValue
, kInputTypeTable
, false, kInputDefaultType
);
5411 auto newType
= FormControlType(aResult
.GetEnumValue());
5412 if (IsDateTimeInputType(newType
) && !IsDateTimeTypeSupported(newType
)) {
5413 // There's no public way to set an nsAttrValue to an enum value, but we
5414 // can just re-parse with a table that doesn't have any types other than
5416 aResult
.ParseEnumValue(aValue
, kInputDefaultType
, false,
5422 if (aAttribute
== nsGkAtoms::width
) {
5423 return aResult
.ParseHTMLDimension(aValue
);
5425 if (aAttribute
== nsGkAtoms::height
) {
5426 return aResult
.ParseHTMLDimension(aValue
);
5428 if (aAttribute
== nsGkAtoms::maxlength
) {
5429 return aResult
.ParseNonNegativeIntValue(aValue
);
5431 if (aAttribute
== nsGkAtoms::minlength
) {
5432 return aResult
.ParseNonNegativeIntValue(aValue
);
5434 if (aAttribute
== nsGkAtoms::size
) {
5435 return aResult
.ParsePositiveIntValue(aValue
);
5437 if (aAttribute
== nsGkAtoms::align
) {
5438 return ParseAlignValue(aValue
, aResult
);
5440 if (aAttribute
== nsGkAtoms::formmethod
) {
5441 return aResult
.ParseEnumValue(aValue
, kFormMethodTable
, false);
5443 if (aAttribute
== nsGkAtoms::formenctype
) {
5444 return aResult
.ParseEnumValue(aValue
, kFormEnctypeTable
, false);
5446 if (aAttribute
== nsGkAtoms::autocomplete
) {
5447 aResult
.ParseAtomArray(aValue
);
5450 if (aAttribute
== nsGkAtoms::capture
) {
5451 return aResult
.ParseEnumValue(aValue
, kCaptureTable
, false,
5454 if (ParseImageAttribute(aAttribute
, aValue
, aResult
)) {
5455 // We have to call |ParseImageAttribute| unconditionally since we
5456 // don't know if we're going to have a type="image" attribute yet,
5457 // (or could have it set dynamically in the future). See bug
5463 return TextControlElement::ParseAttribute(aNamespaceID
, aAttribute
, aValue
,
5464 aMaybeScriptedPrincipal
, aResult
);
5467 void HTMLInputElement::ImageInputMapAttributesIntoRule(
5468 MappedDeclarationsBuilder
& aBuilder
) {
5469 nsGenericHTMLFormControlElementWithState::MapImageBorderAttributeInto(
5471 nsGenericHTMLFormControlElementWithState::MapImageMarginAttributeInto(
5473 nsGenericHTMLFormControlElementWithState::MapImageSizeAttributesInto(
5474 aBuilder
, MapAspectRatio::Yes
);
5475 // Images treat align as "float"
5476 nsGenericHTMLFormControlElementWithState::MapImageAlignAttributeInto(
5478 nsGenericHTMLFormControlElementWithState::MapCommonAttributesInto(aBuilder
);
5481 nsChangeHint
HTMLInputElement::GetAttributeChangeHint(const nsAtom
* aAttribute
,
5482 int32_t aModType
) const {
5483 nsChangeHint retval
=
5484 nsGenericHTMLFormControlElementWithState::GetAttributeChangeHint(
5485 aAttribute
, aModType
);
5487 const bool isAdditionOrRemoval
=
5488 aModType
== MutationEvent_Binding::ADDITION
||
5489 aModType
== MutationEvent_Binding::REMOVAL
;
5491 const bool reconstruct
= [&] {
5492 if (aAttribute
== nsGkAtoms::type
) {
5496 if (PlaceholderApplies() && aAttribute
== nsGkAtoms::placeholder
&&
5497 isAdditionOrRemoval
) {
5498 // We need to re-create our placeholder text.
5502 if (mType
== FormControlType::InputFile
&&
5503 aAttribute
== nsGkAtoms::webkitdirectory
) {
5504 // The presence or absence of the 'directory' attribute determines what
5505 // value we show in the file label when empty, via GetDisplayFileName.
5509 if (mType
== FormControlType::InputImage
&& isAdditionOrRemoval
&&
5510 (aAttribute
== nsGkAtoms::alt
|| aAttribute
== nsGkAtoms::value
)) {
5511 // We might need to rebuild our alt text. Just go ahead and
5512 // reconstruct our frame. This should be quite rare..
5519 retval
|= nsChangeHint_ReconstructFrame
;
5520 } else if (aAttribute
== nsGkAtoms::value
) {
5521 retval
|= NS_STYLE_HINT_REFLOW
;
5522 } else if (aAttribute
== nsGkAtoms::size
&& IsSingleLineTextControl(false)) {
5523 retval
|= NS_STYLE_HINT_REFLOW
;
5529 NS_IMETHODIMP_(bool)
5530 HTMLInputElement::IsAttributeMapped(const nsAtom
* aAttribute
) const {
5531 static const MappedAttributeEntry attributes
[] = {
5536 static const MappedAttributeEntry
* const map
[] = {
5538 sCommonAttributeMap
,
5539 sImageMarginSizeAttributeMap
,
5540 sImageBorderAttributeMap
,
5543 return FindAttributeDependence(aAttribute
, map
);
5546 nsMapRuleToAttributesFunc
HTMLInputElement::GetAttributeMappingFunction()
5548 // GetAttributeChangeHint guarantees that changes to mType will trigger a
5549 // reframe, and we update the mapping function in our mapped attrs when our
5550 // type changes, so it's safe to condition our attribute mapping function on
5552 if (mType
== FormControlType::InputImage
) {
5553 return &ImageInputMapAttributesIntoRule
;
5556 return &MapCommonAttributesInto
;
5559 // Directory picking methods:
5561 already_AddRefed
<Promise
> HTMLInputElement::GetFilesAndDirectories(
5563 if (mType
!= FormControlType::InputFile
) {
5564 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5568 nsCOMPtr
<nsIGlobalObject
> global
= OwnerDoc()->GetScopeObject();
5574 RefPtr
<Promise
> p
= Promise::Create(global
, aRv
);
5579 const nsTArray
<OwningFileOrDirectory
>& filesAndDirs
=
5580 GetFilesOrDirectoriesInternal();
5582 Sequence
<OwningFileOrDirectory
> filesAndDirsSeq
;
5584 if (!filesAndDirsSeq
.SetLength(filesAndDirs
.Length(), fallible
)) {
5585 p
->MaybeReject(NS_ERROR_OUT_OF_MEMORY
);
5589 for (uint32_t i
= 0; i
< filesAndDirs
.Length(); ++i
) {
5590 if (filesAndDirs
[i
].IsDirectory()) {
5591 RefPtr
<Directory
> directory
= filesAndDirs
[i
].GetAsDirectory();
5593 // In future we could refactor SetFilePickerFiltersFromAccept to return a
5594 // semicolon separated list of file extensions and include that in the
5595 // filter string passed here.
5596 directory
->SetContentFilters(u
"filter-out-sensitive"_ns
);
5597 filesAndDirsSeq
[i
].SetAsDirectory() = directory
;
5599 MOZ_ASSERT(filesAndDirs
[i
].IsFile());
5601 // This file was directly selected by the user, so don't filter it.
5602 filesAndDirsSeq
[i
].SetAsFile() = filesAndDirs
[i
].GetAsFile();
5606 p
->MaybeResolve(filesAndDirsSeq
);
5610 // Controllers Methods
5612 nsIControllers
* HTMLInputElement::GetControllers(ErrorResult
& aRv
) {
5613 // XXX: what about type "file"?
5614 if (IsSingleLineTextControl(false)) {
5615 if (!mControllers
) {
5616 mControllers
= new nsXULControllers();
5617 if (!mControllers
) {
5618 aRv
.Throw(NS_ERROR_FAILURE
);
5622 RefPtr
<nsBaseCommandController
> commandController
=
5623 nsBaseCommandController::CreateEditorController();
5624 if (!commandController
) {
5625 aRv
.Throw(NS_ERROR_FAILURE
);
5629 mControllers
->AppendController(commandController
);
5631 commandController
= nsBaseCommandController::CreateEditingController();
5632 if (!commandController
) {
5633 aRv
.Throw(NS_ERROR_FAILURE
);
5637 mControllers
->AppendController(commandController
);
5641 return mControllers
;
5644 nsresult
HTMLInputElement::GetControllers(nsIControllers
** aResult
) {
5645 NS_ENSURE_ARG_POINTER(aResult
);
5648 RefPtr
<nsIControllers
> controller
= GetControllers(rv
);
5649 controller
.forget(aResult
);
5650 return rv
.StealNSResult();
5653 int32_t HTMLInputElement::InputTextLength(CallerType aCallerType
) {
5655 GetValue(val
, aCallerType
);
5656 return val
.Length();
5659 void HTMLInputElement::SetSelectionRange(uint32_t aSelectionStart
,
5660 uint32_t aSelectionEnd
,
5661 const Optional
<nsAString
>& aDirection
,
5663 if (!SupportsTextSelection()) {
5664 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5668 TextControlState
* state
= GetEditorState();
5669 MOZ_ASSERT(state
, "SupportsTextSelection() returned true!");
5670 state
->SetSelectionRange(aSelectionStart
, aSelectionEnd
, aDirection
, aRv
);
5673 void HTMLInputElement::SetRangeText(const nsAString
& aReplacement
,
5675 if (!SupportsTextSelection()) {
5676 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5680 TextControlState
* state
= GetEditorState();
5681 MOZ_ASSERT(state
, "SupportsTextSelection() returned true!");
5682 state
->SetRangeText(aReplacement
, aRv
);
5685 void HTMLInputElement::SetRangeText(const nsAString
& aReplacement
,
5686 uint32_t aStart
, uint32_t aEnd
,
5687 SelectionMode aSelectMode
,
5689 if (!SupportsTextSelection()) {
5690 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5694 TextControlState
* state
= GetEditorState();
5695 MOZ_ASSERT(state
, "SupportsTextSelection() returned true!");
5696 state
->SetRangeText(aReplacement
, aStart
, aEnd
, aSelectMode
, aRv
);
5699 void HTMLInputElement::GetValueFromSetRangeText(nsAString
& aValue
) {
5700 GetNonFileValueInternal(aValue
);
5703 nsresult
HTMLInputElement::SetValueFromSetRangeText(const nsAString
& aValue
) {
5704 return SetValueInternal(aValue
, {ValueSetterOption::ByContentAPI
,
5705 ValueSetterOption::BySetRangeTextAPI
,
5706 ValueSetterOption::SetValueChanged
});
5709 Nullable
<uint32_t> HTMLInputElement::GetSelectionStart(ErrorResult
& aRv
) {
5710 if (!SupportsTextSelection()) {
5711 return Nullable
<uint32_t>();
5714 uint32_t selStart
= GetSelectionStartIgnoringType(aRv
);
5716 return Nullable
<uint32_t>();
5719 return Nullable
<uint32_t>(selStart
);
5722 uint32_t HTMLInputElement::GetSelectionStartIgnoringType(ErrorResult
& aRv
) {
5723 uint32_t selEnd
, selStart
;
5724 GetSelectionRange(&selStart
, &selEnd
, aRv
);
5728 void HTMLInputElement::SetSelectionStart(
5729 const Nullable
<uint32_t>& aSelectionStart
, ErrorResult
& aRv
) {
5730 if (!SupportsTextSelection()) {
5731 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5735 TextControlState
* state
= GetEditorState();
5736 MOZ_ASSERT(state
, "SupportsTextSelection() returned true!");
5737 state
->SetSelectionStart(aSelectionStart
, aRv
);
5740 Nullable
<uint32_t> HTMLInputElement::GetSelectionEnd(ErrorResult
& aRv
) {
5741 if (!SupportsTextSelection()) {
5742 return Nullable
<uint32_t>();
5745 uint32_t selEnd
= GetSelectionEndIgnoringType(aRv
);
5747 return Nullable
<uint32_t>();
5750 return Nullable
<uint32_t>(selEnd
);
5753 uint32_t HTMLInputElement::GetSelectionEndIgnoringType(ErrorResult
& aRv
) {
5754 uint32_t selEnd
, selStart
;
5755 GetSelectionRange(&selStart
, &selEnd
, aRv
);
5759 void HTMLInputElement::SetSelectionEnd(const Nullable
<uint32_t>& aSelectionEnd
,
5761 if (!SupportsTextSelection()) {
5762 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5766 TextControlState
* state
= GetEditorState();
5767 MOZ_ASSERT(state
, "SupportsTextSelection() returned true!");
5768 state
->SetSelectionEnd(aSelectionEnd
, aRv
);
5771 void HTMLInputElement::GetSelectionRange(uint32_t* aSelectionStart
,
5772 uint32_t* aSelectionEnd
,
5774 TextControlState
* state
= GetEditorState();
5776 // Not a text control.
5777 aRv
.Throw(NS_ERROR_UNEXPECTED
);
5781 state
->GetSelectionRange(aSelectionStart
, aSelectionEnd
, aRv
);
5784 void HTMLInputElement::GetSelectionDirection(nsAString
& aDirection
,
5786 if (!SupportsTextSelection()) {
5787 aDirection
.SetIsVoid(true);
5791 TextControlState
* state
= GetEditorState();
5792 MOZ_ASSERT(state
, "SupportsTextSelection came back true!");
5793 state
->GetSelectionDirectionString(aDirection
, aRv
);
5796 void HTMLInputElement::SetSelectionDirection(const nsAString
& aDirection
,
5798 if (!SupportsTextSelection()) {
5799 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5803 TextControlState
* state
= GetEditorState();
5804 MOZ_ASSERT(state
, "SupportsTextSelection came back true!");
5805 state
->SetSelectionDirection(aDirection
, aRv
);
5808 // https://html.spec.whatwg.org/multipage/input.html#dom-input-showpicker
5809 void HTMLInputElement::ShowPicker(ErrorResult
& aRv
) {
5810 // Step 1. If this is not mutable, then throw an "InvalidStateError"
5813 return aRv
.ThrowInvalidStateError(
5814 "This input is either disabled or readonly.");
5817 // Step 2. If this's relevant settings object's origin is not same origin with
5818 // this's relevant settings object's top-level origin, and this's type
5819 // attribute is not in the File Upload state or Color state, then throw a
5820 // "SecurityError" DOMException.
5821 if (mType
!= FormControlType::InputFile
&&
5822 mType
!= FormControlType::InputColor
) {
5823 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
5824 WindowGlobalChild
* windowGlobalChild
=
5825 window
? window
->GetWindowGlobalChild() : nullptr;
5826 if (!windowGlobalChild
|| !windowGlobalChild
->SameOriginWithTop()) {
5827 return aRv
.ThrowSecurityError(
5828 "Call was blocked because the current origin isn't same-origin with "
5833 // Step 3. If this's relevant global object does not have transient
5834 // activation, then throw a "NotAllowedError" DOMException.
5835 if (!OwnerDoc()->HasValidTransientUserGestureActivation()) {
5836 return aRv
.ThrowNotAllowedError(
5837 "Call was blocked due to lack of user activation.");
5840 // Step 4. Show the picker, if applicable, for this.
5842 // https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
5843 // To show the picker, if applicable for an input element element:
5845 // Step 1. Assert: element's relevant global object has transient activation.
5846 // Step 2. If element is not mutable, then return.
5849 // Step 3. If element's type attribute is in the File Upload state, then run
5850 // these steps in parallel:
5851 if (mType
== FormControlType::InputFile
) {
5852 FilePickerType type
= FILE_PICKER_FILE
;
5853 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
5854 HasAttr(nsGkAtoms::webkitdirectory
)) {
5855 type
= FILE_PICKER_DIRECTORY
;
5857 InitFilePicker(type
);
5861 // Step 4. Otherwise, the user agent should show any relevant user interface
5862 // for selecting a value for element, in the way it normally would when the
5863 // user interacts with the control
5864 if (mType
== FormControlType::InputColor
) {
5869 if (!IsInComposedDoc()) {
5873 if (IsDateTimeTypeSupported(mType
)) {
5874 if (CreatesDateTimeWidget()) {
5875 if (RefPtr
<Element
> dateTimeBoxElement
= GetDateTimeBoxElement()) {
5876 // Event is dispatched to closed-shadow tree and doesn't bubble.
5877 RefPtr
<Document
> doc
= dateTimeBoxElement
->OwnerDoc();
5878 nsContentUtils::DispatchTrustedEvent(doc
, dateTimeBoxElement
,
5879 u
"MozDateTimeShowPickerForJS"_ns
,
5880 CanBubble::eNo
, Cancelable::eNo
);
5883 DateTimeValue value
;
5884 GetDateTimeInputBoxValue(value
);
5885 OpenDateTimePicker(value
);
5890 #ifdef ACCESSIBILITY
5891 /*static*/ nsresult
FireEventForAccessibility(HTMLInputElement
* aTarget
,
5892 EventMessage aEventMessage
) {
5893 Element
* element
= static_cast<Element
*>(aTarget
);
5894 return nsContentUtils::DispatchTrustedEvent
<WidgetEvent
>(
5895 element
->OwnerDoc(), element
, aEventMessage
, CanBubble::eYes
,
5900 void HTMLInputElement::UpdateApzAwareFlag() {
5901 #if !defined(ANDROID) && !defined(XP_MACOSX)
5902 if (mType
== FormControlType::InputNumber
||
5903 mType
== FormControlType::InputRange
) {
5909 nsresult
HTMLInputElement::SetDefaultValueAsValue() {
5910 NS_ASSERTION(GetValueMode() == VALUE_MODE_VALUE
,
5911 "GetValueMode() should return VALUE_MODE_VALUE!");
5913 // The element has a content attribute value different from it's value when
5914 // it's in the value mode value.
5915 nsAutoString resetVal
;
5916 GetDefaultValue(resetVal
);
5918 // SetValueInternal is going to sanitize the value.
5919 // TODO(mbrodesser): sanitizing will only happen if `mDoneCreating` is true.
5920 return SetValueInternal(resetVal
, ValueSetterOption::ByInternalAPI
);
5923 // https://html.spec.whatwg.org/#auto-directionality
5924 void HTMLInputElement::SetAutoDirectionality(bool aNotify
,
5925 const nsAString
* aKnownValue
) {
5926 if (!IsAutoDirectionalityAssociated()) {
5927 return SetDirectionality(GetParentDirectionality(this), aNotify
);
5931 // It's unclear if per spec we should use the sanitized or unsanitized
5932 // value to set the directionality, but aKnownValue is unsanitized, so be
5933 // consistent. Using what the user is seeing to determine directionality
5934 // instead of the sanitized (empty if invalid) value probably makes more
5936 GetValueInternal(value
, CallerType::System
);
5937 aKnownValue
= &value
;
5939 SetDirectionalityFromValue(this, *aKnownValue
, aNotify
);
5943 HTMLInputElement::Reset() {
5944 // We should be able to reset all dirty flags regardless of the type.
5945 SetCheckedChanged(false);
5946 SetValueChanged(false);
5947 SetLastValueChangeWasInteractive(false);
5948 SetUserInteracted(false);
5950 switch (GetValueMode()) {
5951 case VALUE_MODE_VALUE
: {
5952 nsresult result
= SetDefaultValueAsValue();
5953 if (CreatesDateTimeWidget()) {
5954 // mFocusedValue has to be set here, so that `FireChangeEventIfNeeded`
5955 // can fire a change event if necessary.
5956 GetValue(mFocusedValue
, CallerType::System
);
5960 case VALUE_MODE_DEFAULT_ON
:
5961 DoSetChecked(DefaultChecked(), true, false);
5963 case VALUE_MODE_FILENAME
:
5966 case VALUE_MODE_DEFAULT
:
5973 HTMLInputElement::SubmitNamesValues(FormData
* aFormData
) {
5974 // For type=reset, and type=button, we just never submit, period.
5975 // For type=image and type=button, we only submit if we were the button
5977 // For type=radio and type=checkbox, we only submit if checked=true
5978 if (mType
== FormControlType::InputReset
||
5979 mType
== FormControlType::InputButton
||
5980 ((mType
== FormControlType::InputSubmit
||
5981 mType
== FormControlType::InputImage
) &&
5982 aFormData
->GetSubmitterElement() != this) ||
5983 ((mType
== FormControlType::InputRadio
||
5984 mType
== FormControlType::InputCheckbox
) &&
5991 GetAttr(nsGkAtoms::name
, name
);
5993 // Submit .x, .y for input type=image
5994 if (mType
== FormControlType::InputImage
) {
5995 // Get a property set by the frame to find out where it was clicked.
5996 const auto* lastClickedPoint
=
5997 static_cast<CSSIntPoint
*>(GetProperty(nsGkAtoms::imageClickedPoint
));
5999 if (lastClickedPoint
) {
6000 // Convert the values to strings for submission
6001 x
= lastClickedPoint
->x
;
6002 y
= lastClickedPoint
->y
;
6007 nsAutoString xVal
, yVal
;
6011 if (!name
.IsEmpty()) {
6012 aFormData
->AddNameValuePair(name
+ u
".x"_ns
, xVal
);
6013 aFormData
->AddNameValuePair(name
+ u
".y"_ns
, yVal
);
6015 // If the Image Element has no name, simply return x and y
6016 // to Nav and IE compatibility.
6017 aFormData
->AddNameValuePair(u
"x"_ns
, xVal
);
6018 aFormData
->AddNameValuePair(u
"y"_ns
, yVal
);
6024 // If name not there, don't submit
6025 if (name
.IsEmpty()) {
6030 // Submit file if its input type=file and this encoding method accepts files
6032 if (mType
== FormControlType::InputFile
) {
6035 const nsTArray
<OwningFileOrDirectory
>& files
=
6036 GetFilesOrDirectoriesInternal();
6038 if (files
.IsEmpty()) {
6039 NS_ENSURE_STATE(GetOwnerGlobal());
6041 RefPtr
<Blob
> blob
= Blob::CreateStringBlob(
6042 GetOwnerGlobal(), ""_ns
, u
"application/octet-stream"_ns
);
6043 RefPtr
<File
> file
= blob
->ToFile(u
""_ns
, rv
);
6046 aFormData
->AddNameBlobPair(name
, file
);
6049 return rv
.StealNSResult();
6052 for (uint32_t i
= 0; i
< files
.Length(); ++i
) {
6053 if (files
[i
].IsFile()) {
6054 aFormData
->AddNameBlobPair(name
, files
[i
].GetAsFile());
6056 MOZ_ASSERT(files
[i
].IsDirectory());
6057 aFormData
->AddNameDirectoryPair(name
, files
[i
].GetAsDirectory());
6064 if (mType
== FormControlType::InputHidden
&&
6065 name
.LowerCaseEqualsLiteral("_charset_")) {
6067 aFormData
->GetCharset(charset
);
6068 return aFormData
->AddNameValuePair(name
, NS_ConvertASCIItoUTF16(charset
));
6072 // Submit name=value
6077 GetValue(value
, CallerType::System
);
6079 if (mType
== FormControlType::InputSubmit
&& value
.IsEmpty() &&
6080 !HasAttr(nsGkAtoms::value
)) {
6081 // Get our default value, which is the same as our default label
6082 nsAutoString defaultValue
;
6083 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
6084 "Submit", OwnerDoc(), defaultValue
);
6085 value
= defaultValue
;
6088 const nsresult rv
= aFormData
->AddNameValuePair(name
, value
);
6089 if (NS_FAILED(rv
)) {
6093 // Submit dirname=dir
6094 if (IsAutoDirectionalityAssociated()) {
6095 return SubmitDirnameDir(aFormData
);
6101 static nsTArray
<FileContentData
> SaveFileContentData(
6102 const nsTArray
<OwningFileOrDirectory
>& aArray
) {
6103 nsTArray
<FileContentData
> res(aArray
.Length());
6104 for (const auto& it
: aArray
) {
6106 RefPtr
<BlobImpl
> impl
= it
.GetAsFile()->Impl();
6107 res
.AppendElement(std::move(impl
));
6109 MOZ_ASSERT(it
.IsDirectory());
6111 nsresult rv
= it
.GetAsDirectory()->GetFullRealPath(fullPath
);
6112 if (NS_WARN_IF(NS_FAILED(rv
))) {
6115 res
.AppendElement(std::move(fullPath
));
6121 void HTMLInputElement::SaveState() {
6122 PresState
* state
= nullptr;
6123 switch (GetValueMode()) {
6124 case VALUE_MODE_DEFAULT_ON
:
6125 if (mCheckedChanged
) {
6126 state
= GetPrimaryPresState();
6131 state
->contentData() = CheckedContentData(mChecked
);
6134 case VALUE_MODE_FILENAME
:
6135 if (!mFileData
->mFilesOrDirectories
.IsEmpty()) {
6136 state
= GetPrimaryPresState();
6141 state
->contentData() =
6142 SaveFileContentData(mFileData
->mFilesOrDirectories
);
6145 case VALUE_MODE_VALUE
:
6146 case VALUE_MODE_DEFAULT
:
6147 // VALUE_MODE_DEFAULT shouldn't have their value saved except 'hidden',
6148 // mType should have never been FormControlType::InputPassword and value
6149 // should have changed.
6150 if ((GetValueMode() == VALUE_MODE_DEFAULT
&&
6151 mType
!= FormControlType::InputHidden
) ||
6152 mHasBeenTypePassword
|| !mValueChanged
) {
6156 state
= GetPrimaryPresState();
6162 GetValue(value
, CallerType::System
);
6164 if (!IsSingleLineTextControl(false) &&
6165 NS_FAILED(nsLinebreakConverter::ConvertStringLineBreaks(
6166 value
, nsLinebreakConverter::eLinebreakPlatform
,
6167 nsLinebreakConverter::eLinebreakContent
))) {
6168 NS_ERROR("Converting linebreaks failed!");
6172 state
->contentData() =
6173 TextContentData(value
, mLastValueChangeWasInteractive
);
6177 if (mDisabledChanged
) {
6179 state
= GetPrimaryPresState();
6182 // We do not want to save the real disabled state but the disabled
6184 state
->disabled() = HasAttr(nsGkAtoms::disabled
);
6185 state
->disabledSet() = true;
6190 void HTMLInputElement::DoneCreatingElement() {
6191 mDoneCreating
= true;
6194 // Restore state as needed. Note that disabled state applies to all control
6197 bool restoredCheckedState
= false;
6198 if (!mInhibitRestoration
) {
6200 restoredCheckedState
= RestoreFormControlState();
6204 // If restore does not occur, we initialize .checked using the CHECKED
6207 if (!restoredCheckedState
&& mShouldInitChecked
) {
6208 DoSetChecked(DefaultChecked(), false, false);
6211 // Sanitize the value and potentially set mFocusedValue.
6212 if (GetValueMode() == VALUE_MODE_VALUE
) {
6214 GetValue(value
, CallerType::System
);
6215 // TODO: What should we do if SetValueInternal fails? (The allocation
6216 // may potentially be big, but most likely we've failed to allocate
6217 // before the type change.)
6218 SetValueInternal(value
, ValueSetterOption::ByInternalAPI
);
6220 if (CreatesDateTimeWidget()) {
6221 // mFocusedValue has to be set here, so that `FireChangeEventIfNeeded` can
6222 // fire a change event if necessary.
6223 mFocusedValue
= value
;
6227 mShouldInitChecked
= false;
6230 void HTMLInputElement::DestroyContent() {
6231 nsImageLoadingContent::Destroy();
6232 TextControlElement::DestroyContent();
6235 void HTMLInputElement::UpdateValidityElementStates(bool aNotify
) {
6236 AutoStateChangeNotifier
notifier(*this, aNotify
);
6237 RemoveStatesSilently(ElementState::VALIDITY_STATES
);
6238 if (!IsCandidateForConstraintValidation()) {
6243 state
|= ElementState::VALID
;
6244 if (mUserInteracted
) {
6245 state
|= ElementState::USER_VALID
;
6248 state
|= ElementState::INVALID
;
6249 if (mUserInteracted
) {
6250 state
|= ElementState::USER_INVALID
;
6253 AddStatesSilently(state
);
6256 static nsTArray
<OwningFileOrDirectory
> RestoreFileContentData(
6257 nsPIDOMWindowInner
* aWindow
, const nsTArray
<FileContentData
>& aData
) {
6258 nsTArray
<OwningFileOrDirectory
> res(aData
.Length());
6259 for (const auto& it
: aData
) {
6260 if (it
.type() == FileContentData::TBlobImpl
) {
6261 if (!it
.get_BlobImpl()) {
6262 // Serialization failed, skip this file.
6266 RefPtr
<File
> file
= File::Create(aWindow
->AsGlobal(), it
.get_BlobImpl());
6267 if (NS_WARN_IF(!file
)) {
6271 OwningFileOrDirectory
* element
= res
.AppendElement();
6272 element
->SetAsFile() = file
;
6274 MOZ_ASSERT(it
.type() == FileContentData::TnsString
);
6275 nsCOMPtr
<nsIFile
> file
;
6277 NS_NewLocalFile(it
.get_nsString(), true, getter_AddRefs(file
));
6278 if (NS_WARN_IF(NS_FAILED(rv
))) {
6282 RefPtr
<Directory
> directory
=
6283 Directory::Create(aWindow
->AsGlobal(), file
);
6284 MOZ_ASSERT(directory
);
6286 OwningFileOrDirectory
* element
= res
.AppendElement();
6287 element
->SetAsDirectory() = directory
;
6293 bool HTMLInputElement::RestoreState(PresState
* aState
) {
6294 bool restoredCheckedState
= false;
6296 const PresContentData
& inputState
= aState
->contentData();
6298 switch (GetValueMode()) {
6299 case VALUE_MODE_DEFAULT_ON
:
6300 if (inputState
.type() == PresContentData::TCheckedContentData
) {
6301 restoredCheckedState
= true;
6302 bool checked
= inputState
.get_CheckedContentData().checked();
6303 DoSetChecked(checked
, true, true);
6306 case VALUE_MODE_FILENAME
:
6307 if (inputState
.type() == PresContentData::TArrayOfFileContentData
) {
6308 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
6310 nsTArray
<OwningFileOrDirectory
> array
=
6311 RestoreFileContentData(window
, inputState
);
6312 SetFilesOrDirectories(array
, true);
6316 case VALUE_MODE_VALUE
:
6317 case VALUE_MODE_DEFAULT
:
6318 if (GetValueMode() == VALUE_MODE_DEFAULT
&&
6319 mType
!= FormControlType::InputHidden
) {
6323 if (inputState
.type() == PresContentData::TTextContentData
) {
6324 // TODO: What should we do if SetValueInternal fails? (The allocation
6325 // may potentially be big, but most likely we've failed to allocate
6326 // before the type change.)
6327 SetValueInternal(inputState
.get_TextContentData().value(),
6328 ValueSetterOption::SetValueChanged
);
6329 if (inputState
.get_TextContentData().lastValueChangeWasInteractive()) {
6330 SetLastValueChangeWasInteractive(true);
6336 if (aState
->disabledSet() && !aState
->disabled()) {
6337 SetDisabled(false, IgnoreErrors());
6340 return restoredCheckedState
;
6347 void HTMLInputElement::AddToRadioGroup() {
6348 MOZ_ASSERT(!mRadioGroupContainer
,
6349 "Radio button must be removed from previous radio group container "
6350 "before being added to another!");
6352 // If the element has no radio group container we can stop here.
6353 auto* container
= FindTreeRadioGroupContainer();
6359 GetAttr(nsGkAtoms::name
, name
);
6360 // If we are part of a radio group, the element must have a name.
6361 MOZ_ASSERT(!name
.IsEmpty());
6364 // Add the radio to the radio group container.
6366 container
->AddToRadioGroup(name
, this, mForm
);
6367 mRadioGroupContainer
= container
;
6370 // If the input element is checked, and we add it to the group, it will
6371 // deselect whatever is currently selected in that group
6375 // If it is checked, call "RadioSetChecked" to perform the selection/
6376 // deselection ritual. This has the side effect of repainting the
6377 // radio button, but as adding a checked radio button into the group
6378 // should not be that common an occurrence, I think we can live with
6380 // Make sure not to notify if we're still being created.
6382 RadioSetChecked(mDoneCreating
);
6384 bool indeterminate
= !container
->GetCurrentRadioButton(name
);
6385 SetStates(ElementState::INDETERMINATE
, indeterminate
, mDoneCreating
);
6389 // For integrity purposes, we have to ensure that "checkedChanged" is
6390 // the same for this new element as for all the others in the group
6392 bool checkedChanged
= mCheckedChanged
;
6394 nsCOMPtr
<nsIRadioVisitor
> visitor
=
6395 new nsRadioGetCheckedChangedVisitor(&checkedChanged
, this);
6396 VisitGroup(visitor
);
6398 SetCheckedChangedInternal(checkedChanged
);
6400 // We initialize the validity of the element to the validity of the group
6401 // because we assume UpdateValueMissingState() will be called after.
6402 SetValidityState(VALIDITY_STATE_VALUE_MISSING
,
6403 container
->GetValueMissingState(name
));
6406 void HTMLInputElement::RemoveFromRadioGroup() {
6407 auto* container
= GetCurrentRadioGroupContainer();
6413 GetAttr(nsGkAtoms::name
, name
);
6415 // If this button was checked, we need to notify the group that there is no
6416 // longer a selected radio button
6418 container
->SetCurrentRadioButton(name
, nullptr);
6419 nsCOMPtr
<nsIRadioVisitor
> visitor
= new nsRadioUpdateStateVisitor(this);
6420 VisitGroup(visitor
);
6422 AddStates(ElementState::INDETERMINATE
);
6425 // Remove this radio from its group in the container.
6426 // We need to call UpdateValueMissingValidityStateForRadio before to make sure
6427 // the group validity is updated (with this element being ignored).
6428 UpdateValueMissingValidityStateForRadio(true);
6429 container
->RemoveFromRadioGroup(name
, this);
6430 mRadioGroupContainer
= nullptr;
6433 bool HTMLInputElement::IsHTMLFocusable(bool aWithMouse
, bool* aIsFocusable
,
6434 int32_t* aTabIndex
) {
6435 if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
6436 aWithMouse
, aIsFocusable
, aTabIndex
)) {
6441 *aIsFocusable
= false;
6445 if (IsSingleLineTextControl(false) || mType
== FormControlType::InputRange
) {
6446 *aIsFocusable
= true;
6450 const bool defaultFocusable
= IsFormControlDefaultFocusable(aWithMouse
);
6451 if (CreatesDateTimeWidget()) {
6453 // We only want our native anonymous child to be tabable to, not ourself.
6456 *aIsFocusable
= true;
6460 if (mType
== FormControlType::InputHidden
) {
6464 *aIsFocusable
= false;
6469 // The other controls are all focusable
6470 *aIsFocusable
= defaultFocusable
;
6474 if (mType
!= FormControlType::InputRadio
) {
6475 *aIsFocusable
= defaultFocusable
;
6480 // Selected radio buttons are tabbable
6481 *aIsFocusable
= defaultFocusable
;
6485 // Current radio button is not selected.
6486 // But make it tabbable if nothing in group is selected.
6487 auto* container
= GetCurrentRadioGroupContainer();
6489 *aIsFocusable
= defaultFocusable
;
6494 GetAttr(nsGkAtoms::name
, name
);
6496 if (container
->GetCurrentRadioButton(name
)) {
6499 *aIsFocusable
= defaultFocusable
;
6503 nsresult
HTMLInputElement::VisitGroup(nsIRadioVisitor
* aVisitor
) {
6504 if (auto* container
= GetCurrentRadioGroupContainer()) {
6506 GetAttr(nsGkAtoms::name
, name
);
6507 return container
->WalkRadioGroup(name
, aVisitor
);
6510 aVisitor
->Visit(this);
6514 HTMLInputElement::ValueModeType
HTMLInputElement::GetValueMode() const {
6516 case FormControlType::InputHidden
:
6517 case FormControlType::InputSubmit
:
6518 case FormControlType::InputButton
:
6519 case FormControlType::InputReset
:
6520 case FormControlType::InputImage
:
6521 return VALUE_MODE_DEFAULT
;
6522 case FormControlType::InputCheckbox
:
6523 case FormControlType::InputRadio
:
6524 return VALUE_MODE_DEFAULT_ON
;
6525 case FormControlType::InputFile
:
6526 return VALUE_MODE_FILENAME
;
6528 case FormControlType::InputText
:
6529 case FormControlType::InputPassword
:
6530 case FormControlType::InputSearch
:
6531 case FormControlType::InputTel
:
6532 case FormControlType::InputEmail
:
6533 case FormControlType::InputUrl
:
6534 case FormControlType::InputNumber
:
6535 case FormControlType::InputRange
:
6536 case FormControlType::InputDate
:
6537 case FormControlType::InputTime
:
6538 case FormControlType::InputColor
:
6539 case FormControlType::InputMonth
:
6540 case FormControlType::InputWeek
:
6541 case FormControlType::InputDatetimeLocal
:
6542 return VALUE_MODE_VALUE
;
6544 MOZ_ASSERT_UNREACHABLE("Unexpected input type in GetValueMode()");
6545 return VALUE_MODE_VALUE
;
6548 return VALUE_MODE_VALUE
;
6553 bool HTMLInputElement::IsMutable() const {
6554 return !IsDisabled() &&
6555 !(DoesReadOnlyApply() && State().HasState(ElementState::READONLY
));
6558 bool HTMLInputElement::DoesRequiredApply() const {
6560 case FormControlType::InputHidden
:
6561 case FormControlType::InputButton
:
6562 case FormControlType::InputImage
:
6563 case FormControlType::InputReset
:
6564 case FormControlType::InputSubmit
:
6565 case FormControlType::InputRange
:
6566 case FormControlType::InputColor
:
6569 case FormControlType::InputRadio
:
6570 case FormControlType::InputCheckbox
:
6571 case FormControlType::InputFile
:
6572 case FormControlType::InputText
:
6573 case FormControlType::InputPassword
:
6574 case FormControlType::InputSearch
:
6575 case FormControlType::InputTel
:
6576 case FormControlType::InputEmail
:
6577 case FormControlType::InputUrl
:
6578 case FormControlType::InputNumber
:
6579 case FormControlType::InputDate
:
6580 case FormControlType::InputTime
:
6581 case FormControlType::InputMonth
:
6582 case FormControlType::InputWeek
:
6583 case FormControlType::InputDatetimeLocal
:
6586 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
6595 bool HTMLInputElement::PlaceholderApplies() const {
6596 if (IsDateTimeInputType(mType
)) {
6599 return IsSingleLineTextControl(false);
6602 bool HTMLInputElement::DoesMinMaxApply() const {
6604 case FormControlType::InputNumber
:
6605 case FormControlType::InputDate
:
6606 case FormControlType::InputTime
:
6607 case FormControlType::InputRange
:
6608 case FormControlType::InputMonth
:
6609 case FormControlType::InputWeek
:
6610 case FormControlType::InputDatetimeLocal
:
6613 case FormControlType::InputReset
:
6614 case FormControlType::InputSubmit
:
6615 case FormControlType::InputImage
:
6616 case FormControlType::InputButton
:
6617 case FormControlType::InputHidden
:
6618 case FormControlType::InputRadio
:
6619 case FormControlType::InputCheckbox
:
6620 case FormControlType::InputFile
:
6621 case FormControlType::InputText
:
6622 case FormControlType::InputPassword
:
6623 case FormControlType::InputSearch
:
6624 case FormControlType::InputTel
:
6625 case FormControlType::InputEmail
:
6626 case FormControlType::InputUrl
:
6627 case FormControlType::InputColor
:
6630 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
6639 bool HTMLInputElement::DoesAutocompleteApply() const {
6641 case FormControlType::InputHidden
:
6642 case FormControlType::InputText
:
6643 case FormControlType::InputSearch
:
6644 case FormControlType::InputUrl
:
6645 case FormControlType::InputTel
:
6646 case FormControlType::InputEmail
:
6647 case FormControlType::InputPassword
:
6648 case FormControlType::InputDate
:
6649 case FormControlType::InputTime
:
6650 case FormControlType::InputNumber
:
6651 case FormControlType::InputRange
:
6652 case FormControlType::InputColor
:
6653 case FormControlType::InputMonth
:
6654 case FormControlType::InputWeek
:
6655 case FormControlType::InputDatetimeLocal
:
6658 case FormControlType::InputReset
:
6659 case FormControlType::InputSubmit
:
6660 case FormControlType::InputImage
:
6661 case FormControlType::InputButton
:
6662 case FormControlType::InputRadio
:
6663 case FormControlType::InputCheckbox
:
6664 case FormControlType::InputFile
:
6667 MOZ_ASSERT_UNREACHABLE(
6668 "Unexpected input type in DoesAutocompleteApply()");
6677 Decimal
HTMLInputElement::GetStep() const {
6678 MOZ_ASSERT(DoesStepApply(), "GetStep() can only be called if @step applies");
6680 if (!HasAttr(nsGkAtoms::step
)) {
6681 return GetDefaultStep() * GetStepScaleFactor();
6684 nsAutoString stepStr
;
6685 GetAttr(nsGkAtoms::step
, stepStr
);
6687 if (stepStr
.LowerCaseEqualsLiteral("any")) {
6688 // The element can't suffer from step mismatch if there is no step.
6692 Decimal step
= StringToDecimal(stepStr
);
6693 if (!step
.isFinite() || step
<= Decimal(0)) {
6694 step
= GetDefaultStep();
6697 // For input type=date, we round the step value to have a rounded day.
6698 if (mType
== FormControlType::InputDate
||
6699 mType
== FormControlType::InputMonth
||
6700 mType
== FormControlType::InputWeek
) {
6701 step
= std::max(step
.round(), Decimal(1));
6704 return step
* GetStepScaleFactor();
6707 // ConstraintValidation
6709 void HTMLInputElement::SetCustomValidity(const nsAString
& aError
) {
6710 ConstraintValidation::SetCustomValidity(aError
);
6711 UpdateValidityElementStates(true);
6714 bool HTMLInputElement::IsTooLong() {
6715 if (!mValueChanged
|| !mLastValueChangeWasInteractive
) {
6719 return mInputType
->IsTooLong();
6722 bool HTMLInputElement::IsTooShort() {
6723 if (!mValueChanged
|| !mLastValueChangeWasInteractive
) {
6727 return mInputType
->IsTooShort();
6730 bool HTMLInputElement::IsValueMissing() const {
6731 // Should use UpdateValueMissingValidityStateForRadio() for type radio.
6732 MOZ_ASSERT(mType
!= FormControlType::InputRadio
);
6734 return mInputType
->IsValueMissing();
6737 bool HTMLInputElement::HasTypeMismatch() const {
6738 return mInputType
->HasTypeMismatch();
6741 Maybe
<bool> HTMLInputElement::HasPatternMismatch() const {
6742 return mInputType
->HasPatternMismatch();
6745 bool HTMLInputElement::IsRangeOverflow() const {
6746 return mInputType
->IsRangeOverflow();
6749 bool HTMLInputElement::IsRangeUnderflow() const {
6750 return mInputType
->IsRangeUnderflow();
6753 bool HTMLInputElement::ValueIsStepMismatch(const Decimal
& aValue
) const {
6754 if (aValue
.isNaN()) {
6755 // The element can't suffer from step mismatch if its value isn't a
6760 Decimal step
= GetStep();
6761 if (step
== kStepAny
) {
6765 // Value has to be an integral multiple of step.
6766 return NS_floorModulo(aValue
- GetStepBase(), step
) != Decimal(0);
6769 bool HTMLInputElement::HasStepMismatch() const {
6770 return mInputType
->HasStepMismatch();
6773 bool HTMLInputElement::HasBadInput() const { return mInputType
->HasBadInput(); }
6775 void HTMLInputElement::UpdateTooLongValidityState() {
6776 SetValidityState(VALIDITY_STATE_TOO_LONG
, IsTooLong());
6779 void HTMLInputElement::UpdateTooShortValidityState() {
6780 SetValidityState(VALIDITY_STATE_TOO_SHORT
, IsTooShort());
6783 void HTMLInputElement::UpdateValueMissingValidityStateForRadio(
6785 MOZ_ASSERT(mType
== FormControlType::InputRadio
,
6786 "This should be called only for radio input types");
6788 HTMLInputElement
* selection
= GetSelectedRadioButton();
6790 // If there is no selection, that might mean the radio is not in a group.
6791 // In that case, we can look for the checked state of the radio.
6792 bool selected
= selection
|| (!aIgnoreSelf
&& mChecked
);
6793 bool required
= !aIgnoreSelf
&& IsRequired();
6795 auto* container
= GetCurrentRadioGroupContainer();
6797 SetValidityState(VALIDITY_STATE_VALUE_MISSING
, false);
6802 GetAttr(nsGkAtoms::name
, name
);
6804 // If the current radio is required and not ignored, we can assume the entire
6805 // group is required.
6807 required
= (aIgnoreSelf
&& IsRequired())
6808 ? container
->GetRequiredRadioCount(name
) - 1
6809 : container
->GetRequiredRadioCount(name
);
6812 bool valueMissing
= required
&& !selected
;
6813 if (container
->GetValueMissingState(name
) != valueMissing
) {
6814 container
->SetValueMissingState(name
, valueMissing
);
6816 SetValidityState(VALIDITY_STATE_VALUE_MISSING
, valueMissing
);
6818 // nsRadioSetValueMissingState will call ElementStateChanged while visiting.
6819 nsAutoScriptBlocker scriptBlocker
;
6820 nsCOMPtr
<nsIRadioVisitor
> visitor
=
6821 new nsRadioSetValueMissingState(this, valueMissing
);
6822 VisitGroup(visitor
);
6826 void HTMLInputElement::UpdateValueMissingValidityState() {
6827 if (mType
== FormControlType::InputRadio
) {
6828 UpdateValueMissingValidityStateForRadio(false);
6832 SetValidityState(VALIDITY_STATE_VALUE_MISSING
, IsValueMissing());
6835 void HTMLInputElement::UpdateTypeMismatchValidityState() {
6836 SetValidityState(VALIDITY_STATE_TYPE_MISMATCH
, HasTypeMismatch());
6839 void HTMLInputElement::UpdatePatternMismatchValidityState() {
6840 Maybe
<bool> hasMismatch
= HasPatternMismatch();
6841 // Don't update if the JS engine failed to evaluate it.
6842 if (hasMismatch
.isSome()) {
6843 SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH
, hasMismatch
.value());
6847 void HTMLInputElement::UpdateRangeOverflowValidityState() {
6848 SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW
, IsRangeOverflow());
6849 UpdateInRange(true);
6852 void HTMLInputElement::UpdateRangeUnderflowValidityState() {
6853 SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
, IsRangeUnderflow());
6854 UpdateInRange(true);
6857 void HTMLInputElement::UpdateStepMismatchValidityState() {
6858 SetValidityState(VALIDITY_STATE_STEP_MISMATCH
, HasStepMismatch());
6861 void HTMLInputElement::UpdateBadInputValidityState() {
6862 SetValidityState(VALIDITY_STATE_BAD_INPUT
, HasBadInput());
6865 void HTMLInputElement::UpdateAllValidityStates(bool aNotify
) {
6866 bool validBefore
= IsValid();
6867 UpdateAllValidityStatesButNotElementState();
6868 if (validBefore
!= IsValid()) {
6869 UpdateValidityElementStates(aNotify
);
6873 void HTMLInputElement::UpdateAllValidityStatesButNotElementState() {
6874 UpdateTooLongValidityState();
6875 UpdateTooShortValidityState();
6876 UpdateValueMissingValidityState();
6877 UpdateTypeMismatchValidityState();
6878 UpdatePatternMismatchValidityState();
6879 UpdateRangeOverflowValidityState();
6880 UpdateRangeUnderflowValidityState();
6881 UpdateStepMismatchValidityState();
6882 UpdateBadInputValidityState();
6885 void HTMLInputElement::UpdateBarredFromConstraintValidation() {
6886 // NOTE: readonly attribute causes an element to be barred from constraint
6887 // validation even if it doesn't apply to that input type. That's rather
6888 // weird, but pre-existing behavior.
6889 bool wasCandidate
= IsCandidateForConstraintValidation();
6890 SetBarredFromConstraintValidation(
6891 mType
== FormControlType::InputHidden
||
6892 mType
== FormControlType::InputButton
||
6893 mType
== FormControlType::InputReset
|| IsDisabled() ||
6894 HasAttr(nsGkAtoms::readonly
) ||
6895 HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR
));
6896 if (IsCandidateForConstraintValidation() != wasCandidate
) {
6897 UpdateInRange(true);
6901 nsresult
HTMLInputElement::GetValidationMessage(nsAString
& aValidationMessage
,
6902 ValidityStateType aType
) {
6903 return mInputType
->GetValidationMessage(aValidationMessage
, aType
);
6906 bool HTMLInputElement::IsSingleLineTextControl() const {
6907 return IsSingleLineTextControl(false);
6910 bool HTMLInputElement::IsTextArea() const { return false; }
6912 bool HTMLInputElement::IsPasswordTextControl() const {
6913 return mType
== FormControlType::InputPassword
;
6916 int32_t HTMLInputElement::GetCols() {
6917 // Else we know (assume) it is an input with size attr
6918 const nsAttrValue
* attr
= GetParsedAttr(nsGkAtoms::size
);
6919 if (attr
&& attr
->Type() == nsAttrValue::eInteger
) {
6920 int32_t cols
= attr
->GetIntegerValue();
6926 return DEFAULT_COLS
;
6929 int32_t HTMLInputElement::GetWrapCols() {
6930 return 0; // only textarea's can have wrap cols
6933 int32_t HTMLInputElement::GetRows() { return DEFAULT_ROWS
; }
6935 void HTMLInputElement::GetDefaultValueFromContent(nsAString
& aValue
,
6937 if (!GetEditorState()) {
6940 GetDefaultValue(aValue
);
6941 // This is called by the frame to show the value.
6942 // We have to sanitize it when needed.
6943 // FIXME: Do we want to sanitize even when aForDisplay is false?
6944 if (mDoneCreating
) {
6945 SanitizeValue(aValue
, aForDisplay
? SanitizationKind::ForDisplay
6946 : SanitizationKind::ForValueGetter
);
6950 bool HTMLInputElement::ValueChanged() const { return mValueChanged
; }
6952 void HTMLInputElement::GetTextEditorValue(nsAString
& aValue
) const {
6953 if (TextControlState
* state
= GetEditorState()) {
6954 state
->GetValue(aValue
, /* aIgnoreWrap = */ true, /* aForDisplay = */ true);
6958 void HTMLInputElement::InitializeKeyboardEventListeners() {
6959 TextControlState
* state
= GetEditorState();
6961 state
->InitializeKeyboardEventListeners();
6965 void HTMLInputElement::UpdatePlaceholderShownState() {
6966 SetStates(ElementState::PLACEHOLDER_SHOWN
,
6967 IsValueEmpty() && PlaceholderApplies() &&
6968 HasAttr(nsGkAtoms::placeholder
));
6971 void HTMLInputElement::OnValueChanged(ValueChangeKind aKind
,
6972 bool aNewValueEmpty
,
6973 const nsAString
* aKnownNewValue
) {
6974 MOZ_ASSERT_IF(aKnownNewValue
, aKnownNewValue
->IsEmpty() == aNewValueEmpty
);
6975 if (aKind
!= ValueChangeKind::Internal
) {
6976 mLastValueChangeWasInteractive
= aKind
== ValueChangeKind::UserInteraction
;
6979 if (aNewValueEmpty
!= IsValueEmpty()) {
6980 SetStates(ElementState::VALUE_EMPTY
, aNewValueEmpty
);
6981 UpdatePlaceholderShownState();
6984 UpdateAllValidityStates(true);
6987 SetAutoDirectionality(true, aKnownNewValue
);
6991 bool HTMLInputElement::HasCachedSelection() {
6992 TextControlState
* state
= GetEditorState();
6996 return state
->IsSelectionCached() && state
->HasNeverInitializedBefore() &&
6997 state
->GetSelectionProperties().GetStart() !=
6998 state
->GetSelectionProperties().GetEnd();
7001 void HTMLInputElement::SetRevealPassword(bool aValue
) {
7002 if (NS_WARN_IF(mType
!= FormControlType::InputPassword
)) {
7005 if (aValue
== State().HasState(ElementState::REVEALED
)) {
7008 RefPtr doc
= OwnerDoc();
7009 // We allow chrome code to prevent this. This is important for about:logins,
7010 // which may need to run some OS-dependent authentication code before
7011 // revealing the saved passwords.
7012 bool defaultAction
= true;
7013 nsContentUtils::DispatchEventOnlyToChrome(
7014 doc
, this, u
"MozWillToggleReveal"_ns
, CanBubble::eYes
, Cancelable::eYes
,
7016 if (NS_WARN_IF(!defaultAction
)) {
7019 SetStates(ElementState::REVEALED
, aValue
);
7022 bool HTMLInputElement::RevealPassword() const {
7023 if (NS_WARN_IF(mType
!= FormControlType::InputPassword
)) {
7026 return State().HasState(ElementState::REVEALED
);
7029 void HTMLInputElement::FieldSetDisabledChanged(bool aNotify
) {
7030 // This *has* to be called *before* UpdateBarredFromConstraintValidation and
7031 // UpdateValueMissingValidityState because these two functions depend on our
7033 nsGenericHTMLFormControlElementWithState::FieldSetDisabledChanged(aNotify
);
7035 UpdateValueMissingValidityState();
7036 UpdateBarredFromConstraintValidation();
7037 UpdateValidityElementStates(aNotify
);
7040 void HTMLInputElement::SetFilePickerFiltersFromAccept(
7041 nsIFilePicker
* filePicker
) {
7042 // We always add |filterAll|
7043 filePicker
->AppendFilters(nsIFilePicker::filterAll
);
7045 NS_ASSERTION(HasAttr(nsGkAtoms::accept
),
7046 "You should not call SetFilePickerFiltersFromAccept if the"
7047 " element has no accept attribute!");
7049 // Services to retrieve image/*, audio/*, video/* filters
7050 nsCOMPtr
<nsIStringBundleService
> stringService
=
7051 components::StringBundle::Service();
7052 if (!stringService
) {
7055 nsCOMPtr
<nsIStringBundle
> filterBundle
;
7056 if (NS_FAILED(stringService
->CreateBundle(
7057 "chrome://global/content/filepicker.properties",
7058 getter_AddRefs(filterBundle
)))) {
7062 // Service to retrieve mime type information for mime types filters
7063 nsCOMPtr
<nsIMIMEService
> mimeService
= do_GetService("@mozilla.org/mime;1");
7068 nsAutoString accept
;
7069 GetAttr(nsGkAtoms::accept
, accept
);
7071 HTMLSplitOnSpacesTokenizer
tokenizer(accept
, ',');
7073 nsTArray
<nsFilePickerFilter
> filters
;
7074 nsString allExtensionsList
;
7076 // Retrieve all filters
7077 while (tokenizer
.hasMoreTokens()) {
7078 const nsDependentSubstring
& token
= tokenizer
.nextToken();
7080 if (token
.IsEmpty()) {
7084 int32_t filterMask
= 0;
7085 nsString filterName
;
7086 nsString extensionListStr
;
7088 // First, check for image/audio/video filters...
7089 if (token
.EqualsLiteral("image/*")) {
7090 filterMask
= nsIFilePicker::filterImages
;
7091 filterBundle
->GetStringFromName("imageFilter", extensionListStr
);
7092 } else if (token
.EqualsLiteral("audio/*")) {
7093 filterMask
= nsIFilePicker::filterAudio
;
7094 filterBundle
->GetStringFromName("audioFilter", extensionListStr
);
7095 } else if (token
.EqualsLiteral("video/*")) {
7096 filterMask
= nsIFilePicker::filterVideo
;
7097 filterBundle
->GetStringFromName("videoFilter", extensionListStr
);
7098 } else if (token
.First() == '.') {
7099 if (token
.Contains(';') || token
.Contains('*')) {
7100 // Ignore this filter as it contains reserved characters
7103 extensionListStr
= u
"*"_ns
+ token
;
7104 filterName
= extensionListStr
;
7106 //... if no image/audio/video filter is found, check mime types filters
7107 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
;
7109 mimeService
->GetFromTypeAndExtension(NS_ConvertUTF16toUTF8(token
),
7110 ""_ns
, // No extension
7111 getter_AddRefs(mimeInfo
))) ||
7116 // Get a name for the filter: first try the description, then the mime
7117 // type name if there is no description
7118 mimeInfo
->GetDescription(filterName
);
7119 if (filterName
.IsEmpty()) {
7120 nsCString mimeTypeName
;
7121 mimeInfo
->GetType(mimeTypeName
);
7122 CopyUTF8toUTF16(mimeTypeName
, filterName
);
7125 // Get extension list
7126 nsCOMPtr
<nsIUTF8StringEnumerator
> extensions
;
7127 mimeInfo
->GetFileExtensions(getter_AddRefs(extensions
));
7130 while (NS_SUCCEEDED(extensions
->HasMore(&hasMore
)) && hasMore
) {
7131 nsCString extension
;
7132 if (NS_FAILED(extensions
->GetNext(extension
))) {
7135 if (!extensionListStr
.IsEmpty()) {
7136 extensionListStr
.AppendLiteral("; ");
7138 extensionListStr
+= u
"*."_ns
+ NS_ConvertUTF8toUTF16(extension
);
7142 if (!filterMask
&& (extensionListStr
.IsEmpty() || filterName
.IsEmpty())) {
7143 // No valid filter found
7147 // At this point we're sure the token represents a valid filter, so pass
7148 // it directly as a raw filter.
7149 filePicker
->AppendRawFilter(token
);
7151 // If we arrived here, that means we have a valid filter: let's create it
7152 // and add it to our list, if no similar filter is already present
7153 nsFilePickerFilter filter
;
7155 filter
= nsFilePickerFilter(filterMask
);
7157 filter
= nsFilePickerFilter(filterName
, extensionListStr
);
7160 if (!filters
.Contains(filter
)) {
7161 if (!allExtensionsList
.IsEmpty()) {
7162 allExtensionsList
.AppendLiteral("; ");
7164 allExtensionsList
+= extensionListStr
;
7165 filters
.AppendElement(filter
);
7169 // Remove similar filters
7170 // Iterate over a copy, as we might modify the original filters list
7171 const nsTArray
<nsFilePickerFilter
> filtersCopy
= filters
.Clone();
7172 for (uint32_t i
= 0; i
< filtersCopy
.Length(); ++i
) {
7173 const nsFilePickerFilter
& filterToCheck
= filtersCopy
[i
];
7174 if (filterToCheck
.mFilterMask
) {
7177 for (uint32_t j
= 0; j
< filtersCopy
.Length(); ++j
) {
7181 // Check if this filter's extension list is a substring of the other one.
7182 // e.g. if filters are "*.jpeg" and "*.jpeg; *.jpg" the first one should
7184 // Add an extra "; " to be sure the check will work and avoid cases like
7185 // "*.xls" being a subtring of "*.xslx" while those are two differents
7186 // filters and none should be removed.
7187 if (FindInReadable(filterToCheck
.mFilter
+ u
";"_ns
,
7188 filtersCopy
[j
].mFilter
+ u
";"_ns
)) {
7189 // We already have a similar, less restrictive filter (i.e.
7190 // filterToCheck extensionList is just a subset of another filter
7191 // extension list): remove this one
7192 filters
.RemoveElement(filterToCheck
);
7197 // Add "All Supported Types" filter
7198 if (filters
.Length() > 1) {
7200 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
7201 "AllSupportedTypes", title
);
7202 filePicker
->AppendFilter(title
, allExtensionsList
);
7206 for (uint32_t i
= 0; i
< filters
.Length(); ++i
) {
7207 const nsFilePickerFilter
& filter
= filters
[i
];
7208 if (filter
.mFilterMask
) {
7209 filePicker
->AppendFilters(filter
.mFilterMask
);
7211 filePicker
->AppendFilter(filter
.mTitle
, filter
.mFilter
);
7215 if (filters
.Length() >= 1) {
7216 // |filterAll| will always use index=0 so we need to set index=1 as the
7217 // current filter. This will be "All Supported Types" for multiple filters.
7218 filePicker
->SetFilterIndex(1);
7222 Decimal
HTMLInputElement::GetStepScaleFactor() const {
7223 MOZ_ASSERT(DoesStepApply());
7226 case FormControlType::InputDate
:
7227 return kStepScaleFactorDate
;
7228 case FormControlType::InputNumber
:
7229 case FormControlType::InputRange
:
7230 return kStepScaleFactorNumberRange
;
7231 case FormControlType::InputTime
:
7232 case FormControlType::InputDatetimeLocal
:
7233 return kStepScaleFactorTime
;
7234 case FormControlType::InputMonth
:
7235 return kStepScaleFactorMonth
;
7236 case FormControlType::InputWeek
:
7237 return kStepScaleFactorWeek
;
7239 MOZ_ASSERT(false, "Unrecognized input type");
7240 return Decimal::nan();
7244 Decimal
HTMLInputElement::GetDefaultStep() const {
7245 MOZ_ASSERT(DoesStepApply());
7248 case FormControlType::InputDate
:
7249 case FormControlType::InputMonth
:
7250 case FormControlType::InputWeek
:
7251 case FormControlType::InputNumber
:
7252 case FormControlType::InputRange
:
7253 return kDefaultStep
;
7254 case FormControlType::InputTime
:
7255 case FormControlType::InputDatetimeLocal
:
7256 return kDefaultStepTime
;
7258 MOZ_ASSERT(false, "Unrecognized input type");
7259 return Decimal::nan();
7263 void HTMLInputElement::SetUserInteracted(bool aInteracted
) {
7264 if (mUserInteracted
== aInteracted
) {
7267 mUserInteracted
= aInteracted
;
7268 UpdateValidityElementStates(true);
7271 void HTMLInputElement::UpdateInRange(bool aNotify
) {
7272 AutoStateChangeNotifier
notifier(*this, aNotify
);
7273 RemoveStatesSilently(ElementState::INRANGE
| ElementState::OUTOFRANGE
);
7274 if (!mHasRange
|| !IsCandidateForConstraintValidation()) {
7277 bool outOfRange
= GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW
) ||
7278 GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
);
7279 AddStatesSilently(outOfRange
? ElementState::OUTOFRANGE
7280 : ElementState::INRANGE
);
7283 void HTMLInputElement::UpdateHasRange(bool aNotify
) {
7284 // There is a range if min/max applies for the type and if the element
7285 // currently have a valid min or max.
7286 const bool newHasRange
= [&] {
7287 if (!DoesMinMaxApply()) {
7290 return !GetMinimum().isNaN() || !GetMaximum().isNaN();
7293 if (newHasRange
== mHasRange
) {
7297 mHasRange
= newHasRange
;
7298 UpdateInRange(aNotify
);
7301 void HTMLInputElement::PickerClosed() { mPickerRunning
= false; }
7303 JSObject
* HTMLInputElement::WrapNode(JSContext
* aCx
,
7304 JS::Handle
<JSObject
*> aGivenProto
) {
7305 return HTMLInputElement_Binding::Wrap(aCx
, this, aGivenProto
);
7308 GetFilesHelper
* HTMLInputElement::GetOrCreateGetFilesHelper(bool aRecursiveFlag
,
7310 MOZ_ASSERT(mFileData
);
7312 if (aRecursiveFlag
) {
7313 if (!mFileData
->mGetFilesRecursiveHelper
) {
7314 mFileData
->mGetFilesRecursiveHelper
= GetFilesHelper::Create(
7315 GetFilesOrDirectoriesInternal(), aRecursiveFlag
, aRv
);
7316 if (NS_WARN_IF(aRv
.Failed())) {
7321 return mFileData
->mGetFilesRecursiveHelper
;
7324 if (!mFileData
->mGetFilesNonRecursiveHelper
) {
7325 mFileData
->mGetFilesNonRecursiveHelper
= GetFilesHelper::Create(
7326 GetFilesOrDirectoriesInternal(), aRecursiveFlag
, aRv
);
7327 if (NS_WARN_IF(aRv
.Failed())) {
7332 return mFileData
->mGetFilesNonRecursiveHelper
;
7335 void HTMLInputElement::UpdateEntries(
7336 const nsTArray
<OwningFileOrDirectory
>& aFilesOrDirectories
) {
7337 MOZ_ASSERT(mFileData
&& mFileData
->mEntries
.IsEmpty());
7339 nsCOMPtr
<nsIGlobalObject
> global
= OwnerDoc()->GetScopeObject();
7342 RefPtr
<FileSystem
> fs
= FileSystem::Create(global
);
7343 if (NS_WARN_IF(!fs
)) {
7347 Sequence
<RefPtr
<FileSystemEntry
>> entries
;
7348 for (uint32_t i
= 0; i
< aFilesOrDirectories
.Length(); ++i
) {
7349 RefPtr
<FileSystemEntry
> entry
=
7350 FileSystemEntry::Create(global
, aFilesOrDirectories
[i
], fs
);
7353 if (!entries
.AppendElement(entry
, fallible
)) {
7358 // The root fileSystem is a DirectoryEntry object that contains only the
7359 // dropped fileEntry and directoryEntry objects.
7360 fs
->CreateRoot(entries
);
7362 mFileData
->mEntries
= std::move(entries
);
7365 void HTMLInputElement::GetWebkitEntries(
7366 nsTArray
<RefPtr
<FileSystemEntry
>>& aSequence
) {
7367 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
7371 Telemetry::Accumulate(Telemetry::BLINK_FILESYSTEM_USED
, true);
7372 aSequence
.AppendElements(mFileData
->mEntries
);
7375 already_AddRefed
<nsINodeList
> HTMLInputElement::GetLabels() {
7376 if (!IsLabelable()) {
7380 return nsGenericHTMLElement::Labels();
7383 void HTMLInputElement::MaybeFireInputPasswordRemoved() {
7384 // We want this event to be fired only when the password field is removed
7385 // from the DOM tree, not when it is released (ex, tab is closed). So don't
7386 // fire an event when the password input field doesn't have a docshell.
7387 Document
* doc
= GetComposedDoc();
7388 nsIDocShell
* container
= doc
? doc
->GetDocShell() : nullptr;
7393 // Right now, only the password manager listens to the event and only listen
7394 // to it under certain circumstances. So don't fire this event unless
7396 if (!doc
->ShouldNotifyFormOrPasswordRemoved()) {
7400 AsyncEventDispatcher::RunDOMEventWhenSafe(
7401 *this, u
"DOMInputPasswordRemoved"_ns
, CanBubble::eNo
,
7402 ChromeOnlyDispatch::eYes
);
7405 } // namespace mozilla::dom
7407 #undef NS_ORIGINAL_CHECKED_VALUE