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 RefPtr
<BrowsingContext
> bc
= doc
->GetBrowsingContext();
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", doc
, title
);
798 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
799 "DirectoryPickerOkButtonLabel", doc
,
802 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
803 "FileUpload", doc
, title
);
806 nsCOMPtr
<nsIFilePicker
> filePicker
=
807 do_CreateInstance("@mozilla.org/filepicker;1");
808 if (!filePicker
) return NS_ERROR_FAILURE
;
810 nsIFilePicker::Mode mode
;
812 if (aType
== FILE_PICKER_DIRECTORY
) {
813 mode
= nsIFilePicker::modeGetFolder
;
814 } else if (HasAttr(nsGkAtoms::multiple
)) {
815 mode
= nsIFilePicker::modeOpenMultiple
;
817 mode
= nsIFilePicker::modeOpen
;
820 nsresult rv
= filePicker
->Init(bc
, title
, mode
);
821 NS_ENSURE_SUCCESS(rv
, rv
);
823 if (!okButtonLabel
.IsEmpty()) {
824 filePicker
->SetOkButtonLabel(okButtonLabel
);
827 // Native directory pickers ignore file type filters, so we don't spend
828 // cycles adding them for FILE_PICKER_DIRECTORY.
829 if (HasAttr(nsGkAtoms::accept
) && aType
!= FILE_PICKER_DIRECTORY
) {
830 SetFilePickerFiltersFromAccept(filePicker
);
832 if (StaticPrefs::dom_capture_enabled()) {
833 if (const nsAttrValue
* captureVal
= GetParsedAttr(nsGkAtoms::capture
)) {
834 filePicker
->SetCapture(static_cast<nsIFilePicker::CaptureTarget
>(
835 captureVal
->GetEnumValue()));
839 filePicker
->AppendFilters(nsIFilePicker::filterAll
);
842 // Set default directory and filename
843 nsAutoString defaultName
;
845 const nsTArray
<OwningFileOrDirectory
>& oldFiles
=
846 GetFilesOrDirectoriesInternal();
848 nsCOMPtr
<nsIFilePickerShownCallback
> callback
=
849 new HTMLInputElement::nsFilePickerShownCallback(this, filePicker
);
851 if (!oldFiles
.IsEmpty() && aType
!= FILE_PICKER_DIRECTORY
) {
854 nsCOMPtr
<nsIFile
> parentFile
= LastUsedDirectory(oldFiles
[0]);
856 filePicker
->SetDisplayDirectory(parentFile
);
859 // Unfortunately nsIFilePicker doesn't allow multiple files to be
860 // default-selected, so only select something by default if exactly
861 // one file was selected before.
862 if (oldFiles
.Length() == 1) {
863 nsAutoString leafName
;
864 GetDOMFileOrDirectoryName(oldFiles
[0], leafName
);
866 if (!leafName
.IsEmpty()) {
867 filePicker
->SetDefaultString(leafName
);
871 rv
= filePicker
->Open(callback
);
872 if (NS_SUCCEEDED(rv
)) {
873 mPickerRunning
= true;
879 HTMLInputElement::gUploadLastDir
->FetchDirectoryAndDisplayPicker(
880 doc
, filePicker
, callback
);
881 mPickerRunning
= true;
885 #define CPS_PREF_NAME u"browser.upload.lastDir"_ns
887 NS_IMPL_ISUPPORTS(UploadLastDir
, nsIObserver
, nsISupportsWeakReference
)
889 void HTMLInputElement::InitUploadLastDir() {
890 gUploadLastDir
= new UploadLastDir();
891 NS_ADDREF(gUploadLastDir
);
893 nsCOMPtr
<nsIObserverService
> observerService
= services::GetObserverService();
894 if (observerService
&& gUploadLastDir
) {
895 observerService
->AddObserver(gUploadLastDir
,
896 "browser:purge-session-history", true);
900 void HTMLInputElement::DestroyUploadLastDir() { NS_IF_RELEASE(gUploadLastDir
); }
902 nsresult
UploadLastDir::FetchDirectoryAndDisplayPicker(
903 Document
* aDoc
, nsIFilePicker
* aFilePicker
,
904 nsIFilePickerShownCallback
* aFpCallback
) {
905 MOZ_ASSERT(aDoc
, "aDoc is null");
906 MOZ_ASSERT(aFilePicker
, "aFilePicker is null");
907 MOZ_ASSERT(aFpCallback
, "aFpCallback is null");
909 nsIURI
* docURI
= aDoc
->GetDocumentURI();
910 MOZ_ASSERT(docURI
, "docURI is null");
912 nsCOMPtr
<nsILoadContext
> loadContext
= aDoc
->GetLoadContext();
913 nsCOMPtr
<nsIContentPrefCallback2
> prefCallback
=
914 new UploadLastDir::ContentPrefCallback(aFilePicker
, aFpCallback
);
916 // Attempt to get the CPS, if it's not present we'll fallback to use the
918 nsCOMPtr
<nsIContentPrefService2
> contentPrefService
=
919 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID
);
920 if (!contentPrefService
) {
921 prefCallback
->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR
);
925 nsAutoCString cstrSpec
;
926 docURI
->GetSpec(cstrSpec
);
927 NS_ConvertUTF8toUTF16
spec(cstrSpec
);
929 contentPrefService
->GetByDomainAndName(spec
, CPS_PREF_NAME
, loadContext
,
934 nsresult
UploadLastDir::StoreLastUsedDirectory(Document
* aDoc
, nsIFile
* aDir
) {
935 MOZ_ASSERT(aDoc
, "aDoc is null");
940 nsCOMPtr
<nsIURI
> docURI
= aDoc
->GetDocumentURI();
941 MOZ_ASSERT(docURI
, "docURI is null");
943 // Attempt to get the CPS, if it's not present we'll just return
944 nsCOMPtr
<nsIContentPrefService2
> contentPrefService
=
945 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID
);
946 if (!contentPrefService
) return NS_ERROR_NOT_AVAILABLE
;
948 nsAutoCString cstrSpec
;
949 docURI
->GetSpec(cstrSpec
);
950 NS_ConvertUTF8toUTF16
spec(cstrSpec
);
952 // Find the parent of aFile, and store it
953 nsString unicodePath
;
954 aDir
->GetPath(unicodePath
);
955 if (unicodePath
.IsEmpty()) // nothing to do
957 RefPtr
<nsVariantCC
> prefValue
= new nsVariantCC();
958 prefValue
->SetAsAString(unicodePath
);
960 // Use the document's current load context to ensure that the content pref
961 // service doesn't persistently store this directory for this domain if the
962 // user is using private browsing:
963 nsCOMPtr
<nsILoadContext
> loadContext
= aDoc
->GetLoadContext();
964 return contentPrefService
->Set(spec
, CPS_PREF_NAME
, prefValue
, loadContext
,
969 UploadLastDir::Observe(nsISupports
* aSubject
, char const* aTopic
,
970 char16_t
const* aData
) {
971 if (strcmp(aTopic
, "browser:purge-session-history") == 0) {
972 nsCOMPtr
<nsIContentPrefService2
> contentPrefService
=
973 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID
);
974 if (contentPrefService
)
975 contentPrefService
->RemoveByName(CPS_PREF_NAME
, nullptr, nullptr);
982 static nsresult
FireEventForAccessibility(HTMLInputElement
* aTarget
,
983 EventMessage aEventMessage
);
987 // construction, destruction
990 HTMLInputElement::HTMLInputElement(already_AddRefed
<dom::NodeInfo
>&& aNodeInfo
,
991 FromParser aFromParser
, FromClone aFromClone
)
992 : TextControlElement(std::move(aNodeInfo
), aFromParser
,
993 FormControlType(kInputDefaultType
->value
)),
994 mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown
),
995 mAutocompleteInfoState(nsContentUtils::eAutocompleteAttrState_Unknown
),
996 mDisabledChanged(false),
997 mValueChanged(false),
998 mUserInteracted(false),
999 mLastValueChangeWasInteractive(false),
1000 mCheckedChanged(false),
1002 mHandlingSelectEvent(false),
1003 mShouldInitChecked(false),
1004 mDoneCreating(aFromParser
== NOT_FROM_PARSER
&&
1005 aFromClone
== FromClone::No
),
1006 mInInternalActivate(false),
1007 mCheckedIsToggled(false),
1008 mIndeterminate(false),
1009 mInhibitRestoration(aFromParser
& FROM_PARSER_FRAGMENT
),
1011 mIsDraggingRange(false),
1012 mNumberControlSpinnerIsSpinning(false),
1013 mNumberControlSpinnerSpinsUp(false),
1014 mPickerRunning(false),
1015 mIsPreviewEnabled(false),
1016 mHasBeenTypePassword(false),
1017 mHasPatternAttribute(false),
1018 mRadioGroupContainer(nullptr) {
1019 // If size is above 512, mozjemalloc allocates 1kB, see
1020 // memory/build/mozjemalloc.cpp
1021 static_assert(sizeof(HTMLInputElement
) <= 512,
1022 "Keep the size of HTMLInputElement under 512 to avoid "
1023 "performance regression!");
1025 // We are in a type=text but we create TextControlState lazily.
1026 mInputData
.mState
= nullptr;
1028 void* memory
= mInputTypeMem
;
1029 mInputType
= InputType::Create(this, mType
, memory
);
1031 if (!gUploadLastDir
) HTMLInputElement::InitUploadLastDir();
1033 // Set up our default state. By default we're enabled (since we're a control
1034 // type that can be disabled but not actually disabled right now), optional,
1035 // read-write, and valid. Also by default we don't have to show validity UI
1037 AddStatesSilently(ElementState::ENABLED
| ElementState::OPTIONAL_
|
1038 ElementState::VALID
| ElementState::VALUE_EMPTY
|
1039 ElementState::READWRITE
);
1040 RemoveStatesSilently(ElementState::READONLY
);
1041 UpdateApzAwareFlag();
1044 HTMLInputElement::~HTMLInputElement() {
1045 if (mNumberControlSpinnerIsSpinning
) {
1046 StopNumberControlSpinnerSpin(eDisallowDispatchingEvents
);
1048 nsImageLoadingContent::Destroy();
1052 void HTMLInputElement::FreeData() {
1053 if (!IsSingleLineTextControl(false)) {
1054 free(mInputData
.mValue
);
1055 mInputData
.mValue
= nullptr;
1056 } else if (mInputData
.mState
) {
1057 // XXX Passing nullptr to UnbindFromFrame doesn't do anything!
1058 UnbindFromFrame(nullptr);
1059 mInputData
.mState
->Destroy();
1060 mInputData
.mState
= nullptr;
1064 mInputType
->DropReference();
1065 mInputType
= nullptr;
1069 void HTMLInputElement::EnsureEditorState() {
1070 MOZ_ASSERT(IsSingleLineTextControl(false));
1071 if (!mInputData
.mState
) {
1072 mInputData
.mState
= TextControlState::Construct(this);
1076 TextControlState
* HTMLInputElement::GetEditorState() const {
1077 if (!IsSingleLineTextControl(false)) {
1081 // We've postponed allocating TextControlState, doing that in a const
1083 const_cast<HTMLInputElement
*>(this)->EnsureEditorState();
1085 MOZ_ASSERT(mInputData
.mState
,
1086 "Single line text controls need to have a state"
1087 " associated with them");
1089 return mInputData
.mState
;
1094 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLInputElement
)
1096 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement
,
1098 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity
)
1099 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers
)
1100 if (tmp
->IsSingleLineTextControl(false) && tmp
->mInputData
.mState
) {
1101 tmp
->mInputData
.mState
->Traverse(cb
);
1104 if (tmp
->mFileData
) {
1105 tmp
->mFileData
->Traverse(cb
);
1107 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1109 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement
,
1111 NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity
)
1112 NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers
)
1113 if (tmp
->IsSingleLineTextControl(false) && tmp
->mInputData
.mState
) {
1114 tmp
->mInputData
.mState
->Unlink();
1117 if (tmp
->mFileData
) {
1118 tmp
->mFileData
->Unlink();
1120 // XXX should unlink more?
1121 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1123 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLInputElement
,
1125 imgINotificationObserver
,
1126 nsIImageLoadingContent
,
1127 nsIConstraintValidation
)
1131 nsresult
HTMLInputElement::Clone(dom::NodeInfo
* aNodeInfo
,
1132 nsINode
** aResult
) const {
1135 RefPtr
<HTMLInputElement
> it
= new (aNodeInfo
->NodeInfoManager())
1136 HTMLInputElement(do_AddRef(aNodeInfo
), NOT_FROM_PARSER
, FromClone::Yes
);
1138 nsresult rv
= const_cast<HTMLInputElement
*>(this)->CopyInnerTo(it
);
1139 NS_ENSURE_SUCCESS(rv
, rv
);
1141 switch (GetValueMode()) {
1142 case VALUE_MODE_VALUE
:
1143 if (mValueChanged
) {
1144 // We don't have our default value anymore. Set our value on
1147 GetNonFileValueInternal(value
);
1148 // SetValueInternal handles setting the VALUE_CHANGED bit for us
1150 NS_FAILED(rv
= it
->SetValueInternal(
1151 value
, {ValueSetterOption::SetValueChanged
})))) {
1156 case VALUE_MODE_FILENAME
:
1157 if (it
->OwnerDoc()->IsStaticDocument()) {
1158 // We're going to be used in print preview. Since the doc is static
1159 // we can just grab the pretty string and use it as wallpaper
1160 GetDisplayFileName(it
->mFileData
->mStaticDocFileList
);
1162 it
->mFileData
->ClearGetFilesHelpers();
1163 it
->mFileData
->mFilesOrDirectories
.Clear();
1164 it
->mFileData
->mFilesOrDirectories
.AppendElements(
1165 mFileData
->mFilesOrDirectories
);
1168 case VALUE_MODE_DEFAULT_ON
:
1169 case VALUE_MODE_DEFAULT
:
1173 if (mCheckedChanged
) {
1174 // We no longer have our original checked state. Set our
1175 // checked state on the clone.
1176 it
->DoSetChecked(mChecked
, false, true);
1177 // Then tell DoneCreatingElement() not to overwrite:
1178 it
->mShouldInitChecked
= false;
1181 it
->mIndeterminate
= mIndeterminate
;
1183 it
->DoneCreatingElement();
1185 it
->SetLastValueChangeWasInteractive(mLastValueChangeWasInteractive
);
1190 void HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
1191 const nsAttrValue
* aValue
, bool aNotify
) {
1192 if (aNameSpaceID
== kNameSpaceID_None
) {
1193 if (aNotify
&& aName
== nsGkAtoms::disabled
) {
1194 mDisabledChanged
= true;
1197 // When name or type changes, radio should be removed from radio group.
1198 // If we are not done creating the radio, we also should not do it.
1199 if (mType
== FormControlType::InputRadio
) {
1200 if ((aName
== nsGkAtoms::name
|| (aName
== nsGkAtoms::type
&& !mForm
)) &&
1201 (mForm
|| mDoneCreating
)) {
1202 RemoveFromRadioGroup();
1203 } else if (aName
== nsGkAtoms::required
) {
1204 auto* container
= GetCurrentRadioGroupContainer();
1206 if (container
&& ((aValue
&& !HasAttr(aNameSpaceID
, aName
)) ||
1207 (!aValue
&& HasAttr(aNameSpaceID
, aName
)))) {
1209 GetAttr(nsGkAtoms::name
, name
);
1210 container
->RadioRequiredWillChange(name
, !!aValue
);
1215 if (aName
== nsGkAtoms::webkitdirectory
) {
1216 Telemetry::Accumulate(Telemetry::WEBKIT_DIRECTORY_USED
, true);
1220 return nsGenericHTMLFormControlElementWithState::BeforeSetAttr(
1221 aNameSpaceID
, aName
, aValue
, aNotify
);
1224 void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
1225 const nsAttrValue
* aValue
,
1226 const nsAttrValue
* aOldValue
,
1227 nsIPrincipal
* aSubjectPrincipal
,
1229 if (aNameSpaceID
== kNameSpaceID_None
) {
1230 bool needValidityUpdate
= false;
1231 if (aName
== nsGkAtoms::src
) {
1232 mSrcTriggeringPrincipal
= nsContentUtils::GetAttrTriggeringPrincipal(
1233 this, aValue
? aValue
->GetStringValue() : EmptyString(),
1235 if (aNotify
&& mType
== FormControlType::InputImage
) {
1237 // Mark channel as urgent-start before load image if the image load is
1238 // initiated by a user interaction.
1239 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
1241 LoadImage(aValue
->GetStringValue(), true, aNotify
,
1242 eImageLoadType_Normal
, mSrcTriggeringPrincipal
);
1244 // Null value means the attr got unset; drop the image
1245 CancelImageRequests(aNotify
);
1250 if (aName
== nsGkAtoms::value
) {
1251 // If the element has a value in value mode, the value content attribute
1252 // is the default value. So if the elements value didn't change from the
1253 // default, we have to re-set it.
1254 if (!mValueChanged
&& GetValueMode() == VALUE_MODE_VALUE
) {
1255 SetDefaultValueAsValue();
1256 } else if (GetValueMode() == VALUE_MODE_DEFAULT
&& HasDirAuto()) {
1257 SetAutoDirectionality(aNotify
);
1259 // GetStepBase() depends on the `value` attribute if `min` is not present,
1260 // even if the value doesn't change.
1261 UpdateStepMismatchValidityState();
1262 needValidityUpdate
= true;
1265 // Checked must be set no matter what type of control it is, since
1266 // mChecked must reflect the new value
1267 if (aName
== nsGkAtoms::checked
) {
1268 if (IsRadioOrCheckbox()) {
1269 SetStates(ElementState::DEFAULT
, !!aValue
, aNotify
);
1271 if (!mCheckedChanged
) {
1272 // Delay setting checked if we are creating this element (wait
1273 // until everything is set)
1274 if (!mDoneCreating
) {
1275 mShouldInitChecked
= true;
1277 DoSetChecked(!!aValue
, aNotify
, false);
1280 needValidityUpdate
= true;
1283 if (aName
== nsGkAtoms::type
) {
1284 FormControlType newType
;
1286 // We're now a text input.
1287 newType
= FormControlType(kInputDefaultType
->value
);
1289 newType
= FormControlType(aValue
->GetEnumValue());
1291 if (newType
!= mType
) {
1292 HandleTypeChange(newType
, aNotify
);
1293 needValidityUpdate
= true;
1297 // When name or type changes, radio should be added to radio group.
1298 // If we are not done creating the radio, we also should not do it.
1299 if ((aName
== nsGkAtoms::name
|| (aName
== nsGkAtoms::type
&& !mForm
)) &&
1300 mType
== FormControlType::InputRadio
&& (mForm
|| mDoneCreating
)) {
1302 UpdateValueMissingValidityStateForRadio(false);
1303 needValidityUpdate
= true;
1306 if (aName
== nsGkAtoms::required
|| aName
== nsGkAtoms::disabled
||
1307 aName
== nsGkAtoms::readonly
) {
1308 if (aName
== nsGkAtoms::disabled
) {
1309 // This *has* to be called *before* validity state check because
1310 // UpdateBarredFromConstraintValidation and
1311 // UpdateValueMissingValidityState depend on our disabled state.
1312 UpdateDisabledState(aNotify
);
1315 if (aName
== nsGkAtoms::required
&& DoesRequiredApply()) {
1316 // This *has* to be called *before* UpdateValueMissingValidityState
1317 // because UpdateValueMissingValidityState depends on our required
1319 UpdateRequiredState(!!aValue
, aNotify
);
1322 if (aName
== nsGkAtoms::readonly
&& !!aValue
!= !!aOldValue
) {
1323 UpdateReadOnlyState(aNotify
);
1326 UpdateValueMissingValidityState();
1328 // This *has* to be called *after* validity has changed.
1329 if (aName
== nsGkAtoms::readonly
|| aName
== nsGkAtoms::disabled
) {
1330 UpdateBarredFromConstraintValidation();
1332 needValidityUpdate
= true;
1333 } else if (aName
== nsGkAtoms::maxlength
) {
1334 UpdateTooLongValidityState();
1335 needValidityUpdate
= true;
1336 } else if (aName
== nsGkAtoms::minlength
) {
1337 UpdateTooShortValidityState();
1338 needValidityUpdate
= true;
1339 } else if (aName
== nsGkAtoms::pattern
) {
1340 // Although pattern attribute only applies to single line text controls,
1341 // we set this flag for all input types to save having to check the type
1343 mHasPatternAttribute
= !!aValue
;
1345 if (mDoneCreating
) {
1346 UpdatePatternMismatchValidityState();
1348 needValidityUpdate
= true;
1349 } else if (aName
== nsGkAtoms::multiple
) {
1350 UpdateTypeMismatchValidityState();
1351 needValidityUpdate
= true;
1352 } else if (aName
== nsGkAtoms::max
) {
1353 UpdateHasRange(aNotify
);
1354 mInputType
->MinMaxStepAttrChanged();
1355 // Validity state must be updated *after* the UpdateValueDueToAttrChange
1356 // call above or else the following assert will not be valid.
1357 // We don't assert the state of underflow during creation since
1358 // DoneCreatingElement sanitizes.
1359 UpdateRangeOverflowValidityState();
1360 needValidityUpdate
= true;
1361 MOZ_ASSERT(!mDoneCreating
|| mType
!= FormControlType::InputRange
||
1362 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
),
1363 "HTML5 spec does not allow underflow for type=range");
1364 } else if (aName
== nsGkAtoms::min
) {
1365 UpdateHasRange(aNotify
);
1366 mInputType
->MinMaxStepAttrChanged();
1367 // See corresponding @max comment
1368 UpdateRangeUnderflowValidityState();
1369 UpdateStepMismatchValidityState();
1370 needValidityUpdate
= true;
1371 MOZ_ASSERT(!mDoneCreating
|| mType
!= FormControlType::InputRange
||
1372 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
),
1373 "HTML5 spec does not allow underflow for type=range");
1374 } else if (aName
== nsGkAtoms::step
) {
1375 mInputType
->MinMaxStepAttrChanged();
1376 // See corresponding @max comment
1377 UpdateStepMismatchValidityState();
1378 needValidityUpdate
= true;
1379 MOZ_ASSERT(!mDoneCreating
|| mType
!= FormControlType::InputRange
||
1380 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
),
1381 "HTML5 spec does not allow underflow for type=range");
1382 } else if (aName
== nsGkAtoms::dir
&& aValue
&&
1383 aValue
->Equals(nsGkAtoms::_auto
, eIgnoreCase
)) {
1384 SetAutoDirectionality(aNotify
);
1385 } else if (aName
== nsGkAtoms::lang
) {
1386 // FIXME(emilio, bug 1651070): This doesn't account for lang changes on
1388 if (mType
== FormControlType::InputNumber
) {
1389 // The validity of our value may have changed based on the locale.
1390 UpdateValidityState();
1391 needValidityUpdate
= true;
1393 } else if (aName
== nsGkAtoms::autocomplete
) {
1394 // Clear the cached @autocomplete attribute and autocompleteInfo state.
1395 mAutocompleteAttrState
= nsContentUtils::eAutocompleteAttrState_Unknown
;
1396 mAutocompleteInfoState
= nsContentUtils::eAutocompleteAttrState_Unknown
;
1397 } else if (aName
== nsGkAtoms::placeholder
) {
1398 // Full addition / removals of the attribute reconstruct right now.
1399 if (nsTextControlFrame
* f
= do_QueryFrame(GetPrimaryFrame())) {
1400 f
->PlaceholderChanged(aOldValue
, aValue
);
1402 UpdatePlaceholderShownState();
1403 needValidityUpdate
= true;
1406 if (CreatesDateTimeWidget()) {
1407 if (aName
== nsGkAtoms::value
|| aName
== nsGkAtoms::readonly
||
1408 aName
== nsGkAtoms::tabindex
|| aName
== nsGkAtoms::required
||
1409 aName
== nsGkAtoms::disabled
) {
1410 // If original target is this and not the inner text control, we should
1411 // pass the focus to the inner text control.
1412 if (Element
* dateTimeBoxElement
= GetDateTimeBoxElement()) {
1413 AsyncEventDispatcher::RunDOMEventWhenSafe(
1414 *dateTimeBoxElement
,
1415 aName
== nsGkAtoms::value
? u
"MozDateTimeValueChanged"_ns
1416 : u
"MozDateTimeAttributeChanged"_ns
,
1417 CanBubble::eNo
, ChromeOnlyDispatch::eNo
);
1421 if (needValidityUpdate
) {
1422 UpdateValidityElementStates(aNotify
);
1426 return nsGenericHTMLFormControlElementWithState::AfterSetAttr(
1427 aNameSpaceID
, aName
, aValue
, aOldValue
, aSubjectPrincipal
, aNotify
);
1430 void HTMLInputElement::BeforeSetForm(HTMLFormElement
* aForm
, bool aBindToTree
) {
1431 // No need to remove from radio group if we are just binding to tree.
1432 if (mType
== FormControlType::InputRadio
&& !aBindToTree
) {
1433 RemoveFromRadioGroup();
1436 // Dispatch event when <input> @form is set
1438 MaybeDispatchLoginManagerEvents(aForm
);
1442 void HTMLInputElement::AfterClearForm(bool aUnbindOrDelete
) {
1445 // Do not add back to radio group if we are releasing or unbinding from tree.
1446 if (mType
== FormControlType::InputRadio
&& !aUnbindOrDelete
&&
1447 !GetCurrentRadioGroupContainer()) {
1449 UpdateValueMissingValidityStateForRadio(false);
1453 void HTMLInputElement::ResultForDialogSubmit(nsAString
& aResult
) {
1454 if (mType
== FormControlType::InputImage
) {
1455 // Get a property set by the frame to find out where it was clicked.
1456 const auto* lastClickedPoint
=
1457 static_cast<CSSIntPoint
*>(GetProperty(nsGkAtoms::imageClickedPoint
));
1459 if (lastClickedPoint
) {
1460 x
= lastClickedPoint
->x
;
1461 y
= lastClickedPoint
->y
;
1465 aResult
.AppendInt(x
);
1466 aResult
.AppendLiteral(",");
1467 aResult
.AppendInt(y
);
1469 GetAttr(nsGkAtoms::value
, aResult
);
1473 void HTMLInputElement::GetAutocomplete(nsAString
& aValue
) {
1474 if (!DoesAutocompleteApply()) {
1479 const nsAttrValue
* attributeVal
= GetParsedAttr(nsGkAtoms::autocomplete
);
1481 mAutocompleteAttrState
= nsContentUtils::SerializeAutocompleteAttribute(
1482 attributeVal
, aValue
, mAutocompleteAttrState
);
1485 void HTMLInputElement::GetAutocompleteInfo(Nullable
<AutocompleteInfo
>& aInfo
) {
1486 if (!DoesAutocompleteApply()) {
1491 const nsAttrValue
* attributeVal
= GetParsedAttr(nsGkAtoms::autocomplete
);
1492 mAutocompleteInfoState
= nsContentUtils::SerializeAutocompleteAttribute(
1493 attributeVal
, aInfo
.SetValue(), mAutocompleteInfoState
, true);
1496 void HTMLInputElement::GetCapture(nsAString
& aValue
) {
1497 GetEnumAttr(nsGkAtoms::capture
, kCaptureDefault
->tag
, aValue
);
1500 void HTMLInputElement::GetFormEnctype(nsAString
& aValue
) {
1501 GetEnumAttr(nsGkAtoms::formenctype
, "", kFormDefaultEnctype
->tag
, aValue
);
1504 void HTMLInputElement::GetFormMethod(nsAString
& aValue
) {
1505 GetEnumAttr(nsGkAtoms::formmethod
, "", kFormDefaultMethod
->tag
, aValue
);
1508 void HTMLInputElement::GetType(nsAString
& aValue
) const {
1509 GetEnumAttr(nsGkAtoms::type
, kInputDefaultType
->tag
, aValue
);
1512 int32_t HTMLInputElement::TabIndexDefault() { return 0; }
1514 uint32_t HTMLInputElement::Height() {
1515 if (mType
!= FormControlType::InputImage
) {
1518 return GetWidthHeightForImage().height
;
1521 void HTMLInputElement::SetIndeterminateInternal(bool aValue
,
1522 bool aShouldInvalidate
) {
1523 mIndeterminate
= aValue
;
1524 if (mType
!= FormControlType::InputCheckbox
) {
1528 SetStates(ElementState::INDETERMINATE
, aValue
);
1530 if (aShouldInvalidate
) {
1531 // Repaint the frame
1532 if (nsIFrame
* frame
= GetPrimaryFrame()) {
1533 frame
->InvalidateFrameSubtree();
1538 void HTMLInputElement::SetIndeterminate(bool aValue
) {
1539 SetIndeterminateInternal(aValue
, true);
1542 uint32_t HTMLInputElement::Width() {
1543 if (mType
!= FormControlType::InputImage
) {
1546 return GetWidthHeightForImage().width
;
1549 bool HTMLInputElement::SanitizesOnValueGetter() const {
1550 // Don't return non-sanitized value for datetime types, email, or number.
1551 return mType
== FormControlType::InputEmail
||
1552 mType
== FormControlType::InputNumber
|| IsDateTimeInputType(mType
);
1555 void HTMLInputElement::GetValue(nsAString
& aValue
, CallerType aCallerType
) {
1556 GetValueInternal(aValue
, aCallerType
);
1558 // In the case where we need to sanitize an input value without affecting
1559 // the displayed user's input, we instead sanitize only on .value accesses.
1560 // For the more general case of input elements displaying text that isn't
1561 // their current value, see bug 805049.
1562 if (SanitizesOnValueGetter()) {
1563 SanitizeValue(aValue
, SanitizationKind::ForValueGetter
);
1567 void HTMLInputElement::GetValueInternal(nsAString
& aValue
,
1568 CallerType aCallerType
) const {
1569 if (mType
!= FormControlType::InputFile
) {
1570 GetNonFileValueInternal(aValue
);
1574 if (aCallerType
== CallerType::System
) {
1575 aValue
.Assign(mFileData
->mFirstFilePath
);
1579 if (mFileData
->mFilesOrDirectories
.IsEmpty()) {
1585 GetDOMFileOrDirectoryName(mFileData
->mFilesOrDirectories
[0], file
);
1586 if (file
.IsEmpty()) {
1591 aValue
.AssignLiteral("C:\\fakepath\\");
1592 aValue
.Append(file
);
1595 void HTMLInputElement::GetNonFileValueInternal(nsAString
& aValue
) const {
1596 switch (GetValueMode()) {
1597 case VALUE_MODE_VALUE
:
1598 if (IsSingleLineTextControl(false)) {
1599 if (mInputData
.mState
) {
1600 mInputData
.mState
->GetValue(aValue
, true, /* aForDisplay = */ false);
1602 // Value hasn't been set yet.
1605 } else if (!aValue
.Assign(mInputData
.mValue
, fallible
)) {
1610 case VALUE_MODE_FILENAME
:
1611 MOZ_ASSERT_UNREACHABLE("Someone screwed up here");
1612 // We'll just return empty string if someone does screw up.
1616 case VALUE_MODE_DEFAULT
:
1617 // Treat defaultValue as value.
1618 GetAttr(nsGkAtoms::value
, aValue
);
1621 case VALUE_MODE_DEFAULT_ON
:
1622 // Treat default value as value and returns "on" if no value.
1623 if (!GetAttr(nsGkAtoms::value
, aValue
)) {
1624 aValue
.AssignLiteral("on");
1630 void HTMLInputElement::ClearFiles(bool aSetValueChanged
) {
1631 nsTArray
<OwningFileOrDirectory
> data
;
1632 SetFilesOrDirectories(data
, aSetValueChanged
);
1635 int32_t HTMLInputElement::MonthsSinceJan1970(uint32_t aYear
,
1636 uint32_t aMonth
) const {
1637 return (aYear
- 1970) * 12 + aMonth
- 1;
1641 Decimal
HTMLInputElement::StringToDecimal(const nsAString
& aValue
) {
1642 if (!IsAscii(aValue
)) {
1643 return Decimal::nan();
1645 NS_LossyConvertUTF16toASCII
asciiString(aValue
);
1646 std::string
stdString(asciiString
.get(), asciiString
.Length());
1647 auto decimal
= Decimal::fromString(stdString
);
1648 if (!decimal
.isFinite()) {
1649 return Decimal::nan();
1651 // Numbers are considered finite IEEE 754 Double-precision floating point
1652 // values, but decimal supports a bigger range.
1653 static const Decimal maxDouble
=
1654 Decimal::fromDouble(std::numeric_limits
<double>::max());
1655 if (decimal
< -maxDouble
|| decimal
> maxDouble
) {
1656 return Decimal::nan();
1661 Decimal
HTMLInputElement::GetValueAsDecimal() const {
1662 nsAutoString stringValue
;
1663 GetNonFileValueInternal(stringValue
);
1664 Decimal result
= mInputType
->ConvertStringToNumber(stringValue
).mResult
;
1665 return result
.isFinite() ? result
: Decimal::nan();
1668 void HTMLInputElement::SetValue(const nsAString
& aValue
, CallerType aCallerType
,
1670 // check security. Note that setting the value to the empty string is always
1671 // OK and gives pages a way to clear a file input if necessary.
1672 if (mType
== FormControlType::InputFile
) {
1673 if (!aValue
.IsEmpty()) {
1674 if (aCallerType
!= CallerType::System
) {
1675 // setting the value of a "FILE" input widget requires
1677 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1680 Sequence
<nsString
> list
;
1681 if (!list
.AppendElement(aValue
, fallible
)) {
1682 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
1686 MozSetFileNameArray(list
, aRv
);
1691 if (MayFireChangeOnBlur()) {
1692 // If the value has been set by a script, we basically want to keep the
1693 // current change event state. If the element is ready to fire a change
1694 // event, we should keep it that way. Otherwise, we should make sure the
1695 // element will not fire any event because of the script interaction.
1697 // NOTE: this is currently quite expensive work (too much string
1698 // manipulation). We should probably optimize that.
1699 nsAutoString currentValue
;
1700 GetNonFileValueInternal(currentValue
);
1702 nsresult rv
= SetValueInternal(
1703 aValue
, ¤tValue
,
1704 {ValueSetterOption::ByContentAPI
, ValueSetterOption::SetValueChanged
,
1705 ValueSetterOption::MoveCursorToEndIfValueChanged
});
1706 if (NS_FAILED(rv
)) {
1711 if (mFocusedValue
.Equals(currentValue
)) {
1712 GetValue(mFocusedValue
, aCallerType
);
1715 nsresult rv
= SetValueInternal(
1717 {ValueSetterOption::ByContentAPI
, ValueSetterOption::SetValueChanged
,
1718 ValueSetterOption::MoveCursorToEndIfValueChanged
});
1719 if (NS_FAILED(rv
)) {
1727 HTMLDataListElement
* HTMLInputElement::GetList() const {
1728 nsAutoString dataListId
;
1729 GetAttr(nsGkAtoms::list_
, dataListId
);
1730 if (dataListId
.IsEmpty()) {
1734 DocumentOrShadowRoot
* docOrShadow
= GetUncomposedDocOrConnectedShadowRoot();
1739 return HTMLDataListElement::FromNodeOrNull(
1740 docOrShadow
->GetElementById(dataListId
));
1743 void HTMLInputElement::SetValue(Decimal aValue
, CallerType aCallerType
) {
1744 MOZ_ASSERT(!aValue
.isInfinity(), "aValue must not be Infinity!");
1746 if (aValue
.isNaN()) {
1747 SetValue(u
""_ns
, aCallerType
, IgnoreErrors());
1752 mInputType
->ConvertNumberToString(aValue
, value
);
1753 SetValue(value
, aCallerType
, IgnoreErrors());
1756 void HTMLInputElement::GetValueAsDate(JSContext
* aCx
,
1757 JS::MutableHandle
<JSObject
*> aObject
,
1759 aObject
.set(nullptr);
1760 if (!IsDateTimeInputType(mType
)) {
1764 Maybe
<JS::ClippedTime
> time
;
1767 case FormControlType::InputDate
: {
1768 uint32_t year
, month
, day
;
1770 GetNonFileValueInternal(value
);
1771 if (!ParseDate(value
, &year
, &month
, &day
)) {
1775 time
.emplace(JS::TimeClip(JS::MakeDate(year
, month
- 1, day
)));
1778 case FormControlType::InputTime
: {
1779 uint32_t millisecond
;
1781 GetNonFileValueInternal(value
);
1782 if (!ParseTime(value
, &millisecond
)) {
1786 time
.emplace(JS::TimeClip(millisecond
));
1787 MOZ_ASSERT(time
->toDouble() == millisecond
,
1788 "HTML times are restricted to the day after the epoch and "
1792 case FormControlType::InputMonth
: {
1793 uint32_t year
, month
;
1795 GetNonFileValueInternal(value
);
1796 if (!ParseMonth(value
, &year
, &month
)) {
1800 time
.emplace(JS::TimeClip(JS::MakeDate(year
, month
- 1, 1)));
1803 case FormControlType::InputWeek
: {
1804 uint32_t year
, week
;
1806 GetNonFileValueInternal(value
);
1807 if (!ParseWeek(value
, &year
, &week
)) {
1811 double days
= DaysSinceEpochFromWeek(year
, week
);
1812 time
.emplace(JS::TimeClip(days
* kMsPerDay
));
1816 case FormControlType::InputDatetimeLocal
: {
1817 uint32_t year
, month
, day
, timeInMs
;
1819 GetNonFileValueInternal(value
);
1820 if (!ParseDateTimeLocal(value
, &year
, &month
, &day
, &timeInMs
)) {
1824 time
.emplace(JS::TimeClip(JS::MakeDate(year
, month
- 1, day
, timeInMs
)));
1832 aObject
.set(JS::NewDateObject(aCx
, *time
));
1834 aRv
.NoteJSContextException(aCx
);
1839 MOZ_ASSERT(false, "Unrecognized input type");
1840 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1843 void HTMLInputElement::SetValueAsDate(JSContext
* aCx
,
1844 JS::Handle
<JSObject
*> aObj
,
1846 if (!IsDateTimeInputType(mType
)) {
1847 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1853 if (!JS::ObjectIsDate(aCx
, aObj
, &isDate
)) {
1854 aRv
.NoteJSContextException(aCx
);
1858 aRv
.ThrowTypeError("Value being assigned is not a date.");
1863 double milliseconds
;
1865 if (!js::DateGetMsecSinceEpoch(aCx
, aObj
, &milliseconds
)) {
1866 aRv
.NoteJSContextException(aCx
);
1870 milliseconds
= UnspecifiedNaN
<double>();
1873 // At this point we know we're not a file input, so we can just pass "not
1874 // system" as the caller type, since the caller type only matters in the file
1876 if (std::isnan(milliseconds
)) {
1877 SetValue(u
""_ns
, CallerType::NonSystem
, aRv
);
1881 if (mType
!= FormControlType::InputMonth
) {
1882 SetValue(Decimal::fromDouble(milliseconds
), CallerType::NonSystem
);
1886 // type=month expects the value to be number of months.
1887 double year
= JS::YearFromTime(milliseconds
);
1888 double month
= JS::MonthFromTime(milliseconds
);
1890 if (std::isnan(year
) || std::isnan(month
)) {
1891 SetValue(u
""_ns
, CallerType::NonSystem
, aRv
);
1895 int32_t months
= MonthsSinceJan1970(year
, month
+ 1);
1896 SetValue(Decimal(int32_t(months
)), CallerType::NonSystem
);
1899 void HTMLInputElement::SetValueAsNumber(double aValueAsNumber
,
1901 // TODO: return TypeError when HTMLInputElement is converted to WebIDL, see
1903 if (std::isinf(aValueAsNumber
)) {
1904 aRv
.Throw(NS_ERROR_INVALID_ARG
);
1908 if (!DoesValueAsNumberApply()) {
1909 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1913 // At this point we know we're not a file input, so we can just pass "not
1914 // system" as the caller type, since the caller type only matters in the file
1916 SetValue(Decimal::fromDouble(aValueAsNumber
), CallerType::NonSystem
);
1919 Decimal
HTMLInputElement::GetMinimum() const {
1921 DoesValueAsNumberApply(),
1922 "GetMinimum() should only be used for types that allow .valueAsNumber");
1924 // Only type=range has a default minimum
1925 Decimal defaultMinimum
=
1926 mType
== FormControlType::InputRange
? Decimal(0) : Decimal::nan();
1928 if (!HasAttr(nsGkAtoms::min
)) {
1929 return defaultMinimum
;
1932 nsAutoString minStr
;
1933 GetAttr(nsGkAtoms::min
, minStr
);
1935 Decimal min
= mInputType
->ConvertStringToNumber(minStr
).mResult
;
1936 return min
.isFinite() ? min
: defaultMinimum
;
1939 Decimal
HTMLInputElement::GetMaximum() const {
1941 DoesValueAsNumberApply(),
1942 "GetMaximum() should only be used for types that allow .valueAsNumber");
1944 // Only type=range has a default maximum
1945 Decimal defaultMaximum
=
1946 mType
== FormControlType::InputRange
? Decimal(100) : Decimal::nan();
1948 if (!HasAttr(nsGkAtoms::max
)) {
1949 return defaultMaximum
;
1952 nsAutoString maxStr
;
1953 GetAttr(nsGkAtoms::max
, maxStr
);
1955 Decimal max
= mInputType
->ConvertStringToNumber(maxStr
).mResult
;
1956 return max
.isFinite() ? max
: defaultMaximum
;
1959 Decimal
HTMLInputElement::GetStepBase() const {
1960 MOZ_ASSERT(IsDateTimeInputType(mType
) ||
1961 mType
== FormControlType::InputNumber
||
1962 mType
== FormControlType::InputRange
,
1963 "Check that kDefaultStepBase is correct for this new type");
1964 // Do NOT use GetMinimum here - the spec says to use "the min content
1965 // attribute", not "the minimum".
1966 nsAutoString minStr
;
1967 if (GetAttr(nsGkAtoms::min
, minStr
)) {
1968 Decimal min
= mInputType
->ConvertStringToNumber(minStr
).mResult
;
1969 if (min
.isFinite()) {
1974 // If @min is not a double, we should use @value.
1975 nsAutoString valueStr
;
1976 if (GetAttr(nsGkAtoms::value
, valueStr
)) {
1977 Decimal value
= mInputType
->ConvertStringToNumber(valueStr
).mResult
;
1978 if (value
.isFinite()) {
1983 if (mType
== FormControlType::InputWeek
) {
1984 return kDefaultStepBaseWeek
;
1987 return kDefaultStepBase
;
1990 nsresult
HTMLInputElement::GetValueIfStepped(int32_t aStep
,
1991 StepCallerType aCallerType
,
1992 Decimal
* aNextStep
) {
1993 if (!DoStepDownStepUpApply()) {
1994 return NS_ERROR_DOM_INVALID_STATE_ERR
;
1997 Decimal stepBase
= GetStepBase();
1998 Decimal step
= GetStep();
1999 if (step
== kStepAny
) {
2000 if (aCallerType
!= CALLED_FOR_USER_EVENT
) {
2001 return NS_ERROR_DOM_INVALID_STATE_ERR
;
2003 // Allow the spin buttons and up/down arrow keys to do something sensible:
2004 step
= GetDefaultStep();
2007 Decimal minimum
= GetMinimum();
2008 Decimal maximum
= GetMaximum();
2010 if (!maximum
.isNaN()) {
2011 // "max - (max - stepBase) % step" is the nearest valid value to max.
2012 maximum
= maximum
- NS_floorModulo(maximum
- stepBase
, step
);
2013 if (!minimum
.isNaN()) {
2014 if (minimum
> maximum
) {
2015 // Either the minimum was greater than the maximum prior to our
2016 // adjustment to align maximum on a step, or else (if we adjusted
2017 // maximum) there is no valid step between minimum and the unadjusted
2024 Decimal value
= GetValueAsDecimal();
2025 bool valueWasNaN
= false;
2026 if (value
.isNaN()) {
2030 Decimal valueBeforeStepping
= value
;
2032 Decimal deltaFromStep
= NS_floorModulo(value
- stepBase
, step
);
2034 if (deltaFromStep
!= Decimal(0)) {
2036 value
+= step
- deltaFromStep
; // partial step
2037 value
+= step
* Decimal(aStep
- 1); // then remaining steps
2038 } else if (aStep
< 0) {
2039 value
-= deltaFromStep
; // partial step
2040 value
+= step
* Decimal(aStep
+ 1); // then remaining steps
2043 value
+= step
* Decimal(aStep
);
2046 if (value
< minimum
) {
2048 deltaFromStep
= NS_floorModulo(value
- stepBase
, step
);
2049 if (deltaFromStep
!= Decimal(0)) {
2050 value
+= step
- deltaFromStep
;
2053 if (value
> maximum
) {
2055 deltaFromStep
= NS_floorModulo(value
- stepBase
, step
);
2056 if (deltaFromStep
!= Decimal(0)) {
2057 value
-= deltaFromStep
;
2061 if (!valueWasNaN
&& // value="", resulting in us using "0"
2062 ((aStep
> 0 && value
< valueBeforeStepping
) ||
2063 (aStep
< 0 && value
> valueBeforeStepping
))) {
2064 // We don't want step-up to effectively step down, or step-down to
2065 // effectively step up, so return;
2073 nsresult
HTMLInputElement::ApplyStep(int32_t aStep
) {
2074 Decimal nextStep
= Decimal::nan(); // unchanged if value will not change
2076 nsresult rv
= GetValueIfStepped(aStep
, CALLED_FOR_SCRIPT
, &nextStep
);
2078 if (NS_SUCCEEDED(rv
) && nextStep
.isFinite()) {
2079 // We know we're not a file input, so the caller type does not matter; just
2080 // pass "not system" to be safe.
2081 SetValue(nextStep
, CallerType::NonSystem
);
2087 bool HTMLInputElement::IsDateTimeInputType(FormControlType aType
) {
2089 case FormControlType::InputDate
:
2090 case FormControlType::InputTime
:
2091 case FormControlType::InputMonth
:
2092 case FormControlType::InputWeek
:
2093 case FormControlType::InputDatetimeLocal
:
2100 void HTMLInputElement::MozGetFileNameArray(nsTArray
<nsString
>& aArray
,
2102 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2106 const nsTArray
<OwningFileOrDirectory
>& filesOrDirs
=
2107 GetFilesOrDirectoriesInternal();
2108 for (uint32_t i
= 0; i
< filesOrDirs
.Length(); i
++) {
2110 GetDOMFileOrDirectoryPath(filesOrDirs
[i
], str
, aRv
);
2111 if (NS_WARN_IF(aRv
.Failed())) {
2115 aArray
.AppendElement(str
);
2119 void HTMLInputElement::MozSetFileArray(
2120 const Sequence
<OwningNonNull
<File
>>& aFiles
) {
2121 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2125 nsCOMPtr
<nsIGlobalObject
> global
= OwnerDoc()->GetScopeObject();
2131 nsTArray
<OwningFileOrDirectory
> files
;
2132 for (uint32_t i
= 0; i
< aFiles
.Length(); ++i
) {
2133 RefPtr
<File
> file
= File::Create(global
, aFiles
[i
].get()->Impl());
2134 if (NS_WARN_IF(!file
)) {
2138 OwningFileOrDirectory
* element
= files
.AppendElement();
2139 element
->SetAsFile() = file
;
2142 SetFilesOrDirectories(files
, true);
2145 void HTMLInputElement::MozSetFileNameArray(const Sequence
<nsString
>& aFileNames
,
2147 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2151 if (XRE_IsContentProcess()) {
2152 aRv
.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
2156 nsTArray
<OwningFileOrDirectory
> files
;
2157 for (uint32_t i
= 0; i
< aFileNames
.Length(); ++i
) {
2158 nsCOMPtr
<nsIFile
> file
;
2160 if (StringBeginsWith(aFileNames
[i
], u
"file:"_ns
,
2161 nsASCIICaseInsensitiveStringComparator
)) {
2162 // Converts the URL string into the corresponding nsIFile if possible
2163 // A local file will be created if the URL string begins with file://
2164 NS_GetFileFromURLSpec(NS_ConvertUTF16toUTF8(aFileNames
[i
]),
2165 getter_AddRefs(file
));
2169 // this is no "file://", try as local file
2170 NS_NewLocalFile(aFileNames
[i
], false, getter_AddRefs(file
));
2174 continue; // Not much we can do if the file doesn't exist
2177 nsCOMPtr
<nsIGlobalObject
> global
= OwnerDoc()->GetScopeObject();
2179 aRv
.Throw(NS_ERROR_FAILURE
);
2183 RefPtr
<File
> domFile
= File::CreateFromFile(global
, file
);
2184 if (NS_WARN_IF(!domFile
)) {
2185 aRv
.Throw(NS_ERROR_FAILURE
);
2189 OwningFileOrDirectory
* element
= files
.AppendElement();
2190 element
->SetAsFile() = domFile
;
2193 SetFilesOrDirectories(files
, true);
2196 void HTMLInputElement::MozSetDirectory(const nsAString
& aDirectoryPath
,
2198 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2202 nsCOMPtr
<nsIFile
> file
;
2203 aRv
= NS_NewLocalFile(aDirectoryPath
, true, getter_AddRefs(file
));
2204 if (NS_WARN_IF(aRv
.Failed())) {
2208 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
2209 if (NS_WARN_IF(!window
)) {
2210 aRv
.Throw(NS_ERROR_FAILURE
);
2214 RefPtr
<Directory
> directory
= Directory::Create(window
->AsGlobal(), file
);
2215 MOZ_ASSERT(directory
);
2217 nsTArray
<OwningFileOrDirectory
> array
;
2218 OwningFileOrDirectory
* element
= array
.AppendElement();
2219 element
->SetAsDirectory() = directory
;
2221 SetFilesOrDirectories(array
, true);
2224 void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue
& aValue
) {
2225 if (NS_WARN_IF(!IsDateTimeInputType(mType
)) || !mDateTimeInputBoxValue
) {
2229 aValue
= *mDateTimeInputBoxValue
;
2232 Element
* HTMLInputElement::GetDateTimeBoxElement() {
2233 if (!GetShadowRoot()) {
2237 // The datetimebox <div> is the only child of the UA Widget Shadow Root
2238 // if it is present.
2239 MOZ_ASSERT(GetShadowRoot()->IsUAWidget());
2240 MOZ_ASSERT(1 >= GetShadowRoot()->GetChildCount());
2241 if (nsIContent
* inputAreaContent
= GetShadowRoot()->GetFirstChild()) {
2242 return inputAreaContent
->AsElement();
2248 void HTMLInputElement::OpenDateTimePicker(const DateTimeValue
& aInitialValue
) {
2249 if (NS_WARN_IF(!IsDateTimeInputType(mType
))) {
2253 mDateTimeInputBoxValue
= MakeUnique
<DateTimeValue
>(aInitialValue
);
2254 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element
*>(this),
2255 u
"MozOpenDateTimePicker"_ns
,
2256 CanBubble::eYes
, Cancelable::eYes
);
2259 void HTMLInputElement::UpdateDateTimePicker(const DateTimeValue
& aValue
) {
2260 if (NS_WARN_IF(!IsDateTimeInputType(mType
))) {
2264 mDateTimeInputBoxValue
= MakeUnique
<DateTimeValue
>(aValue
);
2265 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element
*>(this),
2266 u
"MozUpdateDateTimePicker"_ns
,
2267 CanBubble::eYes
, Cancelable::eYes
);
2270 void HTMLInputElement::CloseDateTimePicker() {
2271 if (NS_WARN_IF(!IsDateTimeInputType(mType
))) {
2275 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element
*>(this),
2276 u
"MozCloseDateTimePicker"_ns
,
2277 CanBubble::eYes
, Cancelable::eYes
);
2280 void HTMLInputElement::SetFocusState(bool aIsFocused
) {
2281 if (NS_WARN_IF(!IsDateTimeInputType(mType
))) {
2284 SetStates(ElementState::FOCUS
| ElementState::FOCUSRING
, aIsFocused
);
2287 void HTMLInputElement::UpdateValidityState() {
2288 if (NS_WARN_IF(!IsDateTimeInputType(mType
))) {
2292 // For now, datetime input box call this function only when the value may
2293 // become valid/invalid. For other validity states, they will be updated when
2294 // .value is actually changed.
2295 UpdateBadInputValidityState();
2296 UpdateValidityElementStates(true);
2299 bool HTMLInputElement::MozIsTextField(bool aExcludePassword
) {
2300 // TODO: temporary until bug 888320 is fixed.
2302 // FIXME: Historically we never returned true for `number`, we should consider
2303 // changing that now that it is similar to other inputs.
2304 if (IsDateTimeInputType(mType
) || mType
== FormControlType::InputNumber
) {
2308 return IsSingleLineTextControl(aExcludePassword
);
2311 void HTMLInputElement::SetUserInput(const nsAString
& aValue
,
2312 nsIPrincipal
& aSubjectPrincipal
) {
2313 AutoHandlingUserInputStatePusher
inputStatePusher(true);
2315 if (mType
== FormControlType::InputFile
&&
2316 !aSubjectPrincipal
.IsSystemPrincipal()) {
2320 if (mType
== FormControlType::InputFile
) {
2321 Sequence
<nsString
> list
;
2322 if (!list
.AppendElement(aValue
, fallible
)) {
2326 MozSetFileNameArray(list
, IgnoreErrors());
2330 bool isInputEventDispatchedByTextControlState
=
2331 GetValueMode() == VALUE_MODE_VALUE
&& IsSingleLineTextControl(false);
2333 nsresult rv
= SetValueInternal(
2335 {ValueSetterOption::BySetUserInputAPI
, ValueSetterOption::SetValueChanged
,
2336 ValueSetterOption::MoveCursorToEndIfValueChanged
});
2337 NS_ENSURE_SUCCESS_VOID(rv
);
2339 if (!isInputEventDispatchedByTextControlState
) {
2340 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(this);
2341 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
2342 "Failed to dispatch input event");
2345 // If this element is not currently focused, it won't receive a change event
2346 // for this update through the normal channels. So fire a change event
2347 // immediately, instead.
2348 if (CreatesDateTimeWidget() || !ShouldBlur(this)) {
2349 FireChangeEventIfNeeded();
2353 nsIEditor
* HTMLInputElement::GetEditorForBindings() {
2354 if (!GetPrimaryFrame()) {
2355 // Ensure we construct frames (and thus an editor) if needed.
2356 GetPrimaryFrame(FlushType::Frames
);
2358 return GetTextEditorFromState();
2361 bool HTMLInputElement::HasEditor() const {
2362 return !!GetTextEditorWithoutCreation();
2365 TextEditor
* HTMLInputElement::GetTextEditorFromState() {
2366 TextControlState
* state
= GetEditorState();
2368 return state
->GetTextEditor();
2373 TextEditor
* HTMLInputElement::GetTextEditor() {
2374 return GetTextEditorFromState();
2377 TextEditor
* HTMLInputElement::GetTextEditorWithoutCreation() const {
2378 TextControlState
* state
= GetEditorState();
2382 return state
->GetTextEditorWithoutCreation();
2385 nsISelectionController
* HTMLInputElement::GetSelectionController() {
2386 TextControlState
* state
= GetEditorState();
2388 return state
->GetSelectionController();
2393 nsFrameSelection
* HTMLInputElement::GetConstFrameSelection() {
2394 TextControlState
* state
= GetEditorState();
2396 return state
->GetConstFrameSelection();
2401 nsresult
HTMLInputElement::BindToFrame(nsTextControlFrame
* aFrame
) {
2402 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
2403 TextControlState
* state
= GetEditorState();
2405 return state
->BindToFrame(aFrame
);
2407 return NS_ERROR_FAILURE
;
2410 void HTMLInputElement::UnbindFromFrame(nsTextControlFrame
* aFrame
) {
2411 TextControlState
* state
= GetEditorState();
2412 if (state
&& aFrame
) {
2413 state
->UnbindFromFrame(aFrame
);
2417 nsresult
HTMLInputElement::CreateEditor() {
2418 TextControlState
* state
= GetEditorState();
2420 return state
->PrepareEditor();
2422 return NS_ERROR_FAILURE
;
2425 void HTMLInputElement::SetPreviewValue(const nsAString
& aValue
) {
2426 TextControlState
* state
= GetEditorState();
2428 state
->SetPreviewText(aValue
, true);
2432 void HTMLInputElement::GetPreviewValue(nsAString
& aValue
) {
2433 TextControlState
* state
= GetEditorState();
2435 state
->GetPreviewText(aValue
);
2439 void HTMLInputElement::EnablePreview() {
2440 if (mIsPreviewEnabled
) {
2444 mIsPreviewEnabled
= true;
2445 // Reconstruct the frame to append an anonymous preview node
2446 nsLayoutUtils::PostRestyleEvent(this, RestyleHint
{0},
2447 nsChangeHint_ReconstructFrame
);
2450 bool HTMLInputElement::IsPreviewEnabled() { return mIsPreviewEnabled
; }
2452 void HTMLInputElement::GetDisplayFileName(nsAString
& aValue
) const {
2453 MOZ_ASSERT(mFileData
);
2455 if (OwnerDoc()->IsStaticDocument()) {
2456 aValue
= mFileData
->mStaticDocFileList
;
2460 if (mFileData
->mFilesOrDirectories
.Length() == 1) {
2461 GetDOMFileOrDirectoryName(mFileData
->mFilesOrDirectories
[0], aValue
);
2467 if (mFileData
->mFilesOrDirectories
.IsEmpty()) {
2468 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
2469 HasAttr(nsGkAtoms::webkitdirectory
)) {
2470 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
2471 "NoDirSelected", OwnerDoc(),
2473 } else if (HasAttr(nsGkAtoms::multiple
)) {
2474 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
2475 "NoFilesSelected", OwnerDoc(),
2478 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
2479 "NoFileSelected", OwnerDoc(),
2484 count
.AppendInt(int(mFileData
->mFilesOrDirectories
.Length()));
2486 nsContentUtils::FormatMaybeLocalizedString(
2487 value
, nsContentUtils::eFORMS_PROPERTIES
, "XFilesSelected", OwnerDoc(),
2494 const nsTArray
<OwningFileOrDirectory
>&
2495 HTMLInputElement::GetFilesOrDirectoriesInternal() const {
2496 return mFileData
->mFilesOrDirectories
;
2499 void HTMLInputElement::SetFilesOrDirectories(
2500 const nsTArray
<OwningFileOrDirectory
>& aFilesOrDirectories
,
2501 bool aSetValueChanged
) {
2502 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2506 MOZ_ASSERT(mFileData
);
2508 mFileData
->ClearGetFilesHelpers();
2510 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2511 HTMLInputElement_Binding::ClearCachedWebkitEntriesValue(this);
2512 mFileData
->mEntries
.Clear();
2515 mFileData
->mFilesOrDirectories
.Clear();
2516 mFileData
->mFilesOrDirectories
.AppendElements(aFilesOrDirectories
);
2518 AfterSetFilesOrDirectories(aSetValueChanged
);
2521 void HTMLInputElement::SetFiles(FileList
* aFiles
, bool aSetValueChanged
) {
2522 MOZ_ASSERT(mFileData
);
2524 mFileData
->mFilesOrDirectories
.Clear();
2525 mFileData
->ClearGetFilesHelpers();
2527 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2528 HTMLInputElement_Binding::ClearCachedWebkitEntriesValue(this);
2529 mFileData
->mEntries
.Clear();
2533 uint32_t listLength
= aFiles
->Length();
2534 for (uint32_t i
= 0; i
< listLength
; i
++) {
2535 OwningFileOrDirectory
* element
=
2536 mFileData
->mFilesOrDirectories
.AppendElement();
2537 element
->SetAsFile() = aFiles
->Item(i
);
2541 AfterSetFilesOrDirectories(aSetValueChanged
);
2544 // This method is used for testing only.
2545 void HTMLInputElement::MozSetDndFilesAndDirectories(
2546 const nsTArray
<OwningFileOrDirectory
>& aFilesOrDirectories
) {
2547 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2551 SetFilesOrDirectories(aFilesOrDirectories
, true);
2553 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2554 UpdateEntries(aFilesOrDirectories
);
2557 RefPtr
<DispatchChangeEventCallback
> dispatchChangeEventCallback
=
2558 new DispatchChangeEventCallback(this);
2560 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
2561 HasAttr(nsGkAtoms::webkitdirectory
)) {
2563 GetFilesHelper
* helper
=
2564 GetOrCreateGetFilesHelper(true /* recursionFlag */, rv
);
2565 if (NS_WARN_IF(rv
.Failed())) {
2566 rv
.SuppressException();
2570 helper
->AddCallback(dispatchChangeEventCallback
);
2572 dispatchChangeEventCallback
->DispatchEvents();
2576 void HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged
) {
2577 // No need to flush here, if there's no frame at this point we
2578 // don't need to force creation of one just to tell it about this
2579 // new value. We just want the display to update as needed.
2580 nsIFormControlFrame
* formControlFrame
= GetFormControlFrame(false);
2581 if (formControlFrame
) {
2582 nsAutoString readableValue
;
2583 GetDisplayFileName(readableValue
);
2584 formControlFrame
->SetFormProperty(nsGkAtoms::value
, readableValue
);
2587 // Grab the full path here for any chrome callers who access our .value via a
2588 // CPOW. This path won't be called from a CPOW meaning the potential sync IPC
2589 // call under GetMozFullPath won't be rejected for not being urgent.
2590 if (mFileData
->mFilesOrDirectories
.IsEmpty()) {
2591 mFileData
->mFirstFilePath
.Truncate();
2594 GetDOMFileOrDirectoryPath(mFileData
->mFilesOrDirectories
[0],
2595 mFileData
->mFirstFilePath
, rv
);
2596 if (NS_WARN_IF(rv
.Failed())) {
2597 rv
.SuppressException();
2601 // Null out |mFileData->mFileList| to return a new file list when asked for.
2602 // Don't clear it since the file list might come from the user via SetFiles.
2603 if (mFileData
->mFileList
) {
2604 mFileData
->mFileList
= nullptr;
2607 if (aSetValueChanged
) {
2608 SetValueChanged(true);
2611 UpdateAllValidityStates(true);
2614 void HTMLInputElement::FireChangeEventIfNeeded() {
2615 if (!MayFireChangeOnBlur()) {
2619 // We're not exposing the GetValue return value anywhere here, so it's safe to
2620 // claim to be a system caller.
2622 GetValue(value
, CallerType::System
);
2624 // NOTE(emilio): Per spec we should not set this if we don't fire the change
2625 // event, but that seems like a bug. Using mValueChanged seems reasonable to
2626 // keep the expected behavior while
2627 // https://github.com/whatwg/html/issues/10013 is resolved.
2628 if (mValueChanged
) {
2629 SetUserInteracted(true);
2631 if (mFocusedValue
.Equals(value
)) {
2634 // Dispatch the change event.
2635 mFocusedValue
= value
;
2636 nsContentUtils::DispatchTrustedEvent(
2637 OwnerDoc(), static_cast<nsIContent
*>(this), u
"change"_ns
, CanBubble::eYes
,
2641 FileList
* HTMLInputElement::GetFiles() {
2642 if (mType
!= FormControlType::InputFile
) {
2646 if (!mFileData
->mFileList
) {
2647 mFileData
->mFileList
= new FileList(static_cast<nsIContent
*>(this));
2648 for (const OwningFileOrDirectory
& item
: GetFilesOrDirectoriesInternal()) {
2649 if (item
.IsFile()) {
2650 mFileData
->mFileList
->Append(item
.GetAsFile());
2655 return mFileData
->mFileList
;
2658 void HTMLInputElement::SetFiles(FileList
* aFiles
) {
2659 if (mType
!= FormControlType::InputFile
|| !aFiles
) {
2663 // Update |mFileData->mFilesOrDirectories|
2664 SetFiles(aFiles
, true);
2666 MOZ_ASSERT(!mFileData
->mFileList
, "Should've cleared the existing file list");
2668 // Update |mFileData->mFileList| without copy
2669 mFileData
->mFileList
= aFiles
;
2673 void HTMLInputElement::HandleNumberControlSpin(void* aData
) {
2674 RefPtr
<HTMLInputElement
> input
= static_cast<HTMLInputElement
*>(aData
);
2676 NS_ASSERTION(input
->mNumberControlSpinnerIsSpinning
,
2677 "Should have called nsRepeatService::Stop()");
2679 nsNumberControlFrame
* numberControlFrame
=
2680 do_QueryFrame(input
->GetPrimaryFrame());
2681 if (input
->mType
!= FormControlType::InputNumber
|| !numberControlFrame
) {
2682 // Type has changed (and possibly our frame type hasn't been updated yet)
2683 // or else we've lost our frame. Either way, stop the timer and don't do
2685 input
->StopNumberControlSpinnerSpin();
2687 input
->StepNumberControlForUserEvent(
2688 input
->mNumberControlSpinnerSpinsUp
? 1 : -1);
2692 nsresult
HTMLInputElement::SetValueInternal(
2693 const nsAString
& aValue
, const nsAString
* aOldValue
,
2694 const ValueSetterOptions
& aOptions
) {
2695 MOZ_ASSERT(GetValueMode() != VALUE_MODE_FILENAME
,
2696 "Don't call SetValueInternal for file inputs");
2698 // We want to remember if the SetValueInternal() call is being made for a XUL
2699 // element. We do that by looking at the parent node here, and if that node
2700 // is a XUL node, we consider our control a XUL control. XUL controls preserve
2701 // edit history across value setters.
2703 // TODO(emilio): Rather than doing this maybe add an attribute instead and
2704 // read it only on chrome docs or something? That'd allow front-end code to
2705 // move away from xul without weird side-effects.
2706 const bool forcePreserveUndoHistory
= mParent
&& mParent
->IsXULElement();
2708 switch (GetValueMode()) {
2709 case VALUE_MODE_VALUE
: {
2710 // At the moment, only single line text control have to sanitize their
2711 // value Because we have to create a new string for that, we should
2712 // prevent doing it if it's useless.
2713 nsAutoString
value(aValue
);
2715 if (mDoneCreating
&&
2716 !(mType
== FormControlType::InputNumber
&&
2717 aOptions
.contains(ValueSetterOption::BySetUserInputAPI
))) {
2718 // When the value of a number input is set by a script, we need to make
2719 // sure the value is a valid floating-point number.
2720 // https://html.spec.whatwg.org/#valid-floating-point-number
2721 // When it's set by a user, however, we need to be more permissive, so
2722 // we don't sanitize its value here. See bug 1839572.
2723 SanitizeValue(value
, SanitizationKind::ForValueSetter
);
2725 // else DoneCreatingElement calls us again once mDoneCreating is true
2727 const bool setValueChanged
=
2728 aOptions
.contains(ValueSetterOption::SetValueChanged
);
2729 if (setValueChanged
) {
2730 SetValueChanged(true);
2733 if (IsSingleLineTextControl(false)) {
2734 // Note that if aOptions includes
2735 // ValueSetterOption::BySetUserInputAPI, "input" event is automatically
2736 // dispatched by TextControlState::SetValue(). If you'd change condition
2737 // of calling this method, you need to maintain SetUserInput() too. FYI:
2738 // After calling SetValue(), the input type might have been
2739 // modified so that mInputData may not store TextControlState.
2740 EnsureEditorState();
2741 if (!mInputData
.mState
->SetValue(
2743 forcePreserveUndoHistory
2744 ? aOptions
+ ValueSetterOption::PreserveUndoHistory
2746 return NS_ERROR_OUT_OF_MEMORY
;
2748 // If the caller won't dispatch "input" event via
2749 // nsContentUtils::DispatchInputEvent(), we need to modify
2750 // validationMessage value here.
2752 // FIXME(emilio): ValueSetterOption::ByInternalAPI is not supposed to
2753 // change state, but maybe we could run this too?
2754 if (aOptions
.contains(ValueSetterOption::ByContentAPI
)) {
2755 MaybeUpdateAllValidityStates(!mDoneCreating
);
2758 free(mInputData
.mValue
);
2759 mInputData
.mValue
= ToNewUnicode(value
);
2760 if (setValueChanged
) {
2761 SetValueChanged(true);
2763 if (mType
== FormControlType::InputRange
) {
2764 nsRangeFrame
* frame
= do_QueryFrame(GetPrimaryFrame());
2766 frame
->UpdateForValueChange();
2768 } else if (CreatesDateTimeWidget() &&
2769 !aOptions
.contains(ValueSetterOption::BySetUserInputAPI
)) {
2770 if (Element
* dateTimeBoxElement
= GetDateTimeBoxElement()) {
2771 AsyncEventDispatcher::RunDOMEventWhenSafe(
2772 *dateTimeBoxElement
, u
"MozDateTimeValueChanged"_ns
,
2773 CanBubble::eNo
, ChromeOnlyDispatch::eNo
);
2776 if (mDoneCreating
) {
2777 OnValueChanged(ValueChangeKind::Internal
, value
.IsEmpty(), &value
);
2779 // else DoneCreatingElement calls us again once mDoneCreating is true
2782 if (mType
== FormControlType::InputColor
) {
2783 // Update color frame, to reflect color changes
2784 nsColorControlFrame
* colorControlFrame
=
2785 do_QueryFrame(GetPrimaryFrame());
2786 if (colorControlFrame
) {
2787 colorControlFrame
->UpdateColor();
2793 case VALUE_MODE_DEFAULT
:
2794 case VALUE_MODE_DEFAULT_ON
:
2795 // If the value of a hidden input was changed, we mark it changed so that
2796 // we will know we need to save / restore the value. Yes, we are
2797 // overloading the meaning of ValueChanged just a teensy bit to save a
2798 // measly byte of storage space in HTMLInputElement. Yes, you are free to
2799 // make a new flag, NEED_TO_SAVE_VALUE, at such time as mBitField becomes
2801 if (mType
== FormControlType::InputHidden
) {
2802 SetValueChanged(true);
2805 // Make sure to keep track of the last value change not being interactive,
2806 // just in case this used to be another kind of editable input before.
2807 // Note that a checked change _could_ really be interactive, but we don't
2808 // keep track of that elsewhere so seems fine to just do this.
2809 SetLastValueChangeWasInteractive(false);
2811 // Treat value == defaultValue for other input elements.
2812 return nsGenericHTMLFormControlElementWithState::SetAttr(
2813 kNameSpaceID_None
, nsGkAtoms::value
, aValue
, true);
2815 case VALUE_MODE_FILENAME
:
2816 return NS_ERROR_UNEXPECTED
;
2819 // This return statement is required for some compilers.
2823 void HTMLInputElement::SetValueChanged(bool aValueChanged
) {
2824 if (mValueChanged
== aValueChanged
) {
2827 mValueChanged
= aValueChanged
;
2828 UpdateTooLongValidityState();
2829 UpdateTooShortValidityState();
2830 UpdateValidityElementStates(true);
2833 void HTMLInputElement::SetLastValueChangeWasInteractive(bool aWasInteractive
) {
2834 if (aWasInteractive
== mLastValueChangeWasInteractive
) {
2837 mLastValueChangeWasInteractive
= aWasInteractive
;
2838 const bool wasValid
= IsValid();
2839 UpdateTooLongValidityState();
2840 UpdateTooShortValidityState();
2841 if (wasValid
!= IsValid()) {
2842 UpdateValidityElementStates(true);
2846 void HTMLInputElement::SetCheckedChanged(bool aCheckedChanged
) {
2847 DoSetCheckedChanged(aCheckedChanged
, true);
2850 void HTMLInputElement::DoSetCheckedChanged(bool aCheckedChanged
, bool aNotify
) {
2851 if (mType
== FormControlType::InputRadio
) {
2852 if (mCheckedChanged
!= aCheckedChanged
) {
2853 nsCOMPtr
<nsIRadioVisitor
> visitor
=
2854 new nsRadioSetCheckedChangedVisitor(aCheckedChanged
);
2855 VisitGroup(visitor
);
2858 SetCheckedChangedInternal(aCheckedChanged
);
2862 void HTMLInputElement::SetCheckedChangedInternal(bool aCheckedChanged
) {
2863 if (mCheckedChanged
== aCheckedChanged
) {
2866 mCheckedChanged
= aCheckedChanged
;
2867 UpdateValidityElementStates(true);
2870 void HTMLInputElement::SetChecked(bool aChecked
) {
2871 DoSetChecked(aChecked
, true, true);
2874 void HTMLInputElement::DoSetChecked(bool aChecked
, bool aNotify
,
2875 bool aSetValueChanged
) {
2876 // If the user or JS attempts to set checked, whether it actually changes the
2877 // value or not, we say the value was changed so that defaultValue don't
2878 // affect it no more.
2879 if (aSetValueChanged
) {
2880 DoSetCheckedChanged(true, aNotify
);
2883 // Don't do anything if we're not changing whether it's checked (it would
2884 // screw up state actually, especially when you are setting radio button to
2886 if (mChecked
== aChecked
) {
2891 if (mType
!= FormControlType::InputRadio
) {
2892 SetCheckedInternal(aChecked
, aNotify
);
2896 // For radio button, we need to do some extra fun stuff
2898 RadioSetChecked(aNotify
);
2902 if (auto* container
= GetCurrentRadioGroupContainer()) {
2904 GetAttr(nsGkAtoms::name
, name
);
2905 container
->SetCurrentRadioButton(name
, nullptr);
2907 // SetCheckedInternal is going to ask all radios to update their
2908 // validity state. We have to be sure the radio group container knows
2909 // the currently selected radio.
2910 SetCheckedInternal(false, aNotify
);
2913 void HTMLInputElement::RadioSetChecked(bool aNotify
) {
2914 // Find the selected radio button so we can deselect it
2915 HTMLInputElement
* currentlySelected
= GetSelectedRadioButton();
2917 // Deselect the currently selected radio button
2918 if (currentlySelected
) {
2919 // Pass true for the aNotify parameter since the currently selected
2920 // button is already in the document.
2921 currentlySelected
->SetCheckedInternal(false, true);
2924 // Let the group know that we are now the One True Radio Button
2925 if (auto* container
= GetCurrentRadioGroupContainer()) {
2927 GetAttr(nsGkAtoms::name
, name
);
2928 container
->SetCurrentRadioButton(name
, this);
2931 // SetCheckedInternal is going to ask all radios to update their
2933 SetCheckedInternal(true, aNotify
);
2936 RadioGroupContainer
* HTMLInputElement::GetCurrentRadioGroupContainer() const {
2938 mType
== FormControlType::InputRadio
,
2939 "GetRadioGroupContainer should only be called when type='radio'");
2940 return mRadioGroupContainer
;
2943 RadioGroupContainer
* HTMLInputElement::FindTreeRadioGroupContainer() const {
2945 GetAttr(nsGkAtoms::name
, name
);
2947 if (name
.IsEmpty()) {
2951 return &mForm
->OwnedRadioGroupContainer();
2953 if (IsInNativeAnonymousSubtree()) {
2956 if (Document
* doc
= GetUncomposedDoc()) {
2957 return &doc
->OwnedRadioGroupContainer();
2959 return &static_cast<FragmentOrElement
*>(SubtreeRoot())
2960 ->OwnedRadioGroupContainer();
2963 void HTMLInputElement::DisconnectRadioGroupContainer() {
2964 mRadioGroupContainer
= nullptr;
2967 HTMLInputElement
* HTMLInputElement::GetSelectedRadioButton() const {
2968 auto* container
= GetCurrentRadioGroupContainer();
2974 GetAttr(nsGkAtoms::name
, name
);
2976 return container
->GetCurrentRadioButton(name
);
2979 void HTMLInputElement::MaybeSubmitForm(nsPresContext
* aPresContext
) {
2981 // Nothing to do here.
2985 RefPtr
<PresShell
> presShell
= aPresContext
->GetPresShell();
2990 // Get the default submit element
2991 if (RefPtr
<nsGenericHTMLFormElement
> submitContent
=
2992 mForm
->GetDefaultSubmitElement()) {
2993 WidgetMouseEvent
event(true, eMouseClick
, nullptr, WidgetMouseEvent::eReal
);
2994 nsEventStatus status
= nsEventStatus_eIgnore
;
2995 presShell
->HandleDOMEventWithTarget(submitContent
, &event
, &status
);
2996 } else if (!mForm
->ImplicitSubmissionIsDisabled()) {
2997 // If there's only one text control, just submit the form
2998 // Hold strong ref across the event
2999 RefPtr
<dom::HTMLFormElement
> form(mForm
);
3000 form
->MaybeSubmit(nullptr);
3004 void HTMLInputElement::UpdateCheckedState(bool aNotify
) {
3005 SetStates(ElementState::CHECKED
, IsRadioOrCheckbox() && mChecked
, aNotify
);
3008 void HTMLInputElement::UpdateIndeterminateState(bool aNotify
) {
3009 bool indeterminate
= [&] {
3010 if (mType
== FormControlType::InputCheckbox
) {
3011 return mIndeterminate
;
3013 if (mType
== FormControlType::InputRadio
) {
3014 return !mChecked
&& !GetSelectedRadioButton();
3018 SetStates(ElementState::INDETERMINATE
, indeterminate
, aNotify
);
3021 void HTMLInputElement::SetCheckedInternal(bool aChecked
, bool aNotify
) {
3023 mChecked
= aChecked
;
3025 if (IsRadioOrCheckbox()) {
3026 SetStates(ElementState::CHECKED
, aChecked
, aNotify
);
3029 // No need to update element state, since we're about to call
3030 // UpdateState anyway.
3031 UpdateAllValidityStatesButNotElementState();
3032 UpdateIndeterminateState(aNotify
);
3033 UpdateValidityElementStates(aNotify
);
3035 // Notify all radios in the group that value has changed, this is to let
3036 // radios to have the chance to update its states, e.g., :indeterminate.
3037 if (mType
== FormControlType::InputRadio
) {
3038 nsCOMPtr
<nsIRadioVisitor
> visitor
= new nsRadioUpdateStateVisitor(this);
3039 VisitGroup(visitor
);
3043 #if !defined(ANDROID) && !defined(XP_MACOSX)
3044 bool HTMLInputElement::IsNodeApzAwareInternal() const {
3045 // Tell APZC we may handle mouse wheel event and do preventDefault when input
3047 return mType
== FormControlType::InputNumber
||
3048 mType
== FormControlType::InputRange
||
3049 nsINode::IsNodeApzAwareInternal();
3053 bool HTMLInputElement::IsInteractiveHTMLContent() const {
3054 return mType
!= FormControlType::InputHidden
||
3055 nsGenericHTMLFormControlElementWithState::IsInteractiveHTMLContent();
3058 void HTMLInputElement::AsyncEventRunning(AsyncEventDispatcher
* aEvent
) {
3059 nsImageLoadingContent::AsyncEventRunning(aEvent
);
3062 void HTMLInputElement::Select() {
3063 if (!IsSingleLineTextControl(false)) {
3067 TextControlState
* state
= GetEditorState();
3068 MOZ_ASSERT(state
, "Single line text controls are expected to have a state");
3070 if (FocusState() != FocusTristate::eUnfocusable
) {
3071 RefPtr
<nsFrameSelection
> fs
= state
->GetConstFrameSelection();
3072 if (fs
&& fs
->MouseDownRecorded()) {
3073 // This means that we're being called while the frame selection has a
3074 // mouse down event recorded to adjust the caret during the mouse up
3075 // event. We are probably called from the focus event handler. We should
3076 // override the delayed caret data in this case to ensure that this
3077 // select() call takes effect.
3078 fs
->SetDelayedCaretData(nullptr);
3081 if (RefPtr
<nsFocusManager
> fm
= nsFocusManager::GetFocusManager()) {
3082 fm
->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL
);
3084 // A focus event handler may change the type attribute, which will destroy
3085 // the previous state object.
3086 state
= GetEditorState();
3093 // Directly call TextControlState::SetSelectionRange because
3094 // HTMLInputElement::SetSelectionRange only applies to fewer types
3095 state
->SetSelectionRange(0, UINT32_MAX
, Optional
<nsAString
>(), IgnoreErrors(),
3096 TextControlState::ScrollAfterSelection::No
);
3099 void HTMLInputElement::SelectAll(nsPresContext
* aPresContext
) {
3100 nsIFormControlFrame
* formControlFrame
= GetFormControlFrame(true);
3102 if (formControlFrame
) {
3103 formControlFrame
->SetFormProperty(nsGkAtoms::select
, u
""_ns
);
3107 bool HTMLInputElement::NeedToInitializeEditorForEvent(
3108 EventChainPreVisitor
& aVisitor
) const {
3109 // We only need to initialize the editor for single line input controls
3110 // because they are lazily initialized. We don't need to initialize the
3111 // control for certain types of events, because we know that those events are
3112 // safe to be handled without the editor being initialized. These events
3113 // include: mousein/move/out, overflow/underflow, DOM mutation, and void
3114 // events. Void events are dispatched frequently by async keyboard scrolling
3115 // to focused elements, so it's important to handle them to prevent excessive
3117 if (!IsSingleLineTextControl(false) ||
3118 aVisitor
.mEvent
->mClass
== eMutationEventClass
) {
3122 switch (aVisitor
.mEvent
->mMessage
) {
3125 case eMouseEnterIntoWidget
:
3126 case eMouseExitFromWidget
:
3129 case eScrollPortUnderflow
:
3130 case eScrollPortOverflow
:
3137 bool HTMLInputElement::IsDisabledForEvents(WidgetEvent
* aEvent
) {
3138 return IsElementDisabledForEvents(aEvent
, GetPrimaryFrame());
3141 bool HTMLInputElement::CheckActivationBehaviorPreconditions(
3142 EventChainVisitor
& aVisitor
) const {
3144 case FormControlType::InputColor
:
3145 case FormControlType::InputCheckbox
:
3146 case FormControlType::InputRadio
:
3147 case FormControlType::InputFile
:
3148 case FormControlType::InputSubmit
:
3149 case FormControlType::InputImage
:
3150 case FormControlType::InputReset
:
3151 case FormControlType::InputButton
: {
3152 // Track whether we're in the outermost Dispatch invocation that will
3153 // cause activation of the input. That is, if we're a click event, or a
3154 // DOMActivate that was dispatched directly, this will be set, but if
3155 // we're a DOMActivate dispatched from click handling, it will not be set.
3156 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
3157 bool outerActivateEvent
=
3158 (mouseEvent
&& mouseEvent
->IsLeftClickEvent()) ||
3159 (aVisitor
.mEvent
->mMessage
== eLegacyDOMActivate
&&
3160 !mInInternalActivate
);
3161 if (outerActivateEvent
) {
3162 aVisitor
.mItemFlags
|= NS_OUTER_ACTIVATE_EVENT
;
3164 return outerActivateEvent
;
3171 void HTMLInputElement::GetEventTargetParent(EventChainPreVisitor
& aVisitor
) {
3172 // Do not process any DOM events if the element is disabled
3173 aVisitor
.mCanHandle
= false;
3174 if (IsDisabledForEvents(aVisitor
.mEvent
)) {
3178 // Initialize the editor if needed.
3179 if (NeedToInitializeEditorForEvent(aVisitor
)) {
3180 nsITextControlFrame
* textControlFrame
= do_QueryFrame(GetPrimaryFrame());
3181 if (textControlFrame
) textControlFrame
->EnsureEditorInitialized();
3184 if (CheckActivationBehaviorPreconditions(aVisitor
)) {
3185 aVisitor
.mWantsActivationBehavior
= true;
3188 // We must cache type because mType may change during JS event (bug 2369)
3189 aVisitor
.mItemFlags
|= uint8_t(mType
);
3191 if (aVisitor
.mEvent
->mMessage
== eFocus
&& aVisitor
.mEvent
->IsTrusted() &&
3192 MayFireChangeOnBlur() &&
3193 // StartRangeThumbDrag already set mFocusedValue on 'mousedown' before
3194 // we get the 'focus' event.
3195 !mIsDraggingRange
) {
3196 GetValue(mFocusedValue
, CallerType::System
);
3199 // Fire onchange (if necessary), before we do the blur, bug 357684.
3200 if (aVisitor
.mEvent
->mMessage
== eBlur
) {
3201 // We set NS_PRE_HANDLE_BLUR_EVENT here and handle it in PreHandleEvent to
3202 // prevent breaking event target chain creation.
3203 aVisitor
.mWantsPreHandleEvent
= true;
3204 aVisitor
.mItemFlags
|= NS_PRE_HANDLE_BLUR_EVENT
;
3207 if (mType
== FormControlType::InputRange
&&
3208 (aVisitor
.mEvent
->mMessage
== eFocus
||
3209 aVisitor
.mEvent
->mMessage
== eBlur
)) {
3210 // Just as nsGenericHTMLFormControlElementWithState::GetEventTargetParent
3211 // calls nsIFormControlFrame::SetFocus, we handle focus here.
3212 nsIFrame
* frame
= GetPrimaryFrame();
3214 frame
->InvalidateFrameSubtree();
3218 if (mType
== FormControlType::InputNumber
&& aVisitor
.mEvent
->IsTrusted()) {
3219 if (mNumberControlSpinnerIsSpinning
) {
3220 // If the timer is running the user has depressed the mouse on one of the
3221 // spin buttons. If the mouse exits the button we either want to reverse
3222 // the direction of spin if it has moved over the other button, or else
3223 // we want to end the spin. We do this here (rather than in
3224 // PostHandleEvent) because we don't want to let content preventDefault()
3225 // the end of the spin.
3226 if (aVisitor
.mEvent
->mMessage
== eMouseMove
) {
3227 // Be aggressive about stopping the spin:
3228 bool stopSpin
= true;
3229 nsNumberControlFrame
* numberControlFrame
=
3230 do_QueryFrame(GetPrimaryFrame());
3231 if (numberControlFrame
) {
3232 bool oldNumberControlSpinTimerSpinsUpValue
=
3233 mNumberControlSpinnerSpinsUp
;
3234 switch (numberControlFrame
->GetSpinButtonForPointerEvent(
3235 aVisitor
.mEvent
->AsMouseEvent())) {
3236 case nsNumberControlFrame::eSpinButtonUp
:
3237 mNumberControlSpinnerSpinsUp
= true;
3240 case nsNumberControlFrame::eSpinButtonDown
:
3241 mNumberControlSpinnerSpinsUp
= false;
3245 if (mNumberControlSpinnerSpinsUp
!=
3246 oldNumberControlSpinTimerSpinsUpValue
) {
3247 nsNumberControlFrame
* numberControlFrame
=
3248 do_QueryFrame(GetPrimaryFrame());
3249 if (numberControlFrame
) {
3250 numberControlFrame
->SpinnerStateChanged();
3255 StopNumberControlSpinnerSpin();
3257 } else if (aVisitor
.mEvent
->mMessage
== eMouseUp
) {
3258 StopNumberControlSpinnerSpin();
3263 nsGenericHTMLFormControlElementWithState::GetEventTargetParent(aVisitor
);
3265 // Stop the event if the related target's first non-native ancestor is the
3266 // same as the original target's first non-native ancestor (we are moving
3267 // inside of the same element).
3269 // FIXME(emilio): Is this still needed now that we use Shadow DOM for this?
3270 if (CreatesDateTimeWidget() && aVisitor
.mEvent
->IsTrusted() &&
3271 (aVisitor
.mEvent
->mMessage
== eFocus
||
3272 aVisitor
.mEvent
->mMessage
== eFocusIn
||
3273 aVisitor
.mEvent
->mMessage
== eFocusOut
||
3274 aVisitor
.mEvent
->mMessage
== eBlur
)) {
3275 nsIContent
* originalTarget
= nsIContent::FromEventTargetOrNull(
3276 aVisitor
.mEvent
->AsFocusEvent()->mOriginalTarget
);
3277 nsIContent
* relatedTarget
= nsIContent::FromEventTargetOrNull(
3278 aVisitor
.mEvent
->AsFocusEvent()->mRelatedTarget
);
3280 if (originalTarget
&& relatedTarget
&&
3281 originalTarget
->FindFirstNonChromeOnlyAccessContent() ==
3282 relatedTarget
->FindFirstNonChromeOnlyAccessContent()) {
3283 aVisitor
.mCanHandle
= false;
3288 void HTMLInputElement::LegacyPreActivationBehavior(
3289 EventChainVisitor
& aVisitor
) {
3291 // Web pages expect the value of a radio button or checkbox to be set
3292 // *before* onclick and DOMActivate fire, and they expect that if they set
3293 // the value explicitly during onclick or DOMActivate it will not be toggled
3294 // or any such nonsense.
3295 // In order to support that (bug 57137 and 58460 are examples) we toggle
3296 // the checked attribute *first*, and then fire onclick. If the user
3297 // returns false, we reset the control to the old checked value. Otherwise,
3298 // we dispatch DOMActivate. If DOMActivate is cancelled, we also reset
3299 // the control to the old checked value. We need to keep track of whether
3300 // we've already toggled the state from onclick since the user could
3301 // explicitly dispatch DOMActivate on the element.
3303 // These are compatibility hacks and are defined as legacy-pre-activation
3304 // and legacy-canceled-activation behavior in HTML.
3307 // Assert mType didn't change after GetEventTargetParent
3308 MOZ_ASSERT(NS_CONTROL_TYPE(aVisitor
.mItemFlags
) == uint8_t(mType
));
3310 bool originalCheckedValue
= false;
3311 mCheckedIsToggled
= false;
3313 if (mType
== FormControlType::InputCheckbox
) {
3314 if (mIndeterminate
) {
3315 // indeterminate is always set to FALSE when the checkbox is toggled
3316 SetIndeterminateInternal(false, false);
3317 aVisitor
.mItemFlags
|= NS_ORIGINAL_INDETERMINATE_VALUE
;
3320 originalCheckedValue
= Checked();
3321 DoSetChecked(!originalCheckedValue
, true, true);
3322 mCheckedIsToggled
= true;
3324 if (aVisitor
.mEventStatus
!= nsEventStatus_eConsumeNoDefault
) {
3325 aVisitor
.mEventStatus
= nsEventStatus_eConsumeDoDefault
;
3327 } else if (mType
== FormControlType::InputRadio
) {
3328 HTMLInputElement
* selectedRadioButton
= GetSelectedRadioButton();
3329 aVisitor
.mItemData
= static_cast<Element
*>(selectedRadioButton
);
3331 originalCheckedValue
= Checked();
3332 if (!originalCheckedValue
) {
3333 DoSetChecked(true, true, true);
3334 mCheckedIsToggled
= true;
3337 if (aVisitor
.mEventStatus
!= nsEventStatus_eConsumeNoDefault
) {
3338 aVisitor
.mEventStatus
= nsEventStatus_eConsumeDoDefault
;
3342 if (originalCheckedValue
) {
3343 aVisitor
.mItemFlags
|= NS_ORIGINAL_CHECKED_VALUE
;
3346 // out-of-spec legacy pre-activation behavior needed because of bug 1803805
3347 if ((mType
== FormControlType::InputSubmit
||
3348 mType
== FormControlType::InputImage
) &&
3350 aVisitor
.mItemFlags
|= NS_IN_SUBMIT_CLICK
;
3351 aVisitor
.mItemData
= static_cast<Element
*>(mForm
);
3352 // tell the form that we are about to enter a click handler.
3353 // that means that if there are scripted submissions, the
3354 // latest one will be deferred until after the exit point of the
3356 mForm
->OnSubmitClickBegin(this);
3360 nsresult
HTMLInputElement::PreHandleEvent(EventChainVisitor
& aVisitor
) {
3361 if (aVisitor
.mItemFlags
& NS_PRE_HANDLE_BLUR_EVENT
) {
3362 MOZ_ASSERT(aVisitor
.mEvent
->mMessage
== eBlur
);
3363 FireChangeEventIfNeeded();
3365 return nsGenericHTMLFormControlElementWithState::PreHandleEvent(aVisitor
);
3368 void HTMLInputElement::StartRangeThumbDrag(WidgetGUIEvent
* aEvent
) {
3369 nsRangeFrame
* rangeFrame
= do_QueryFrame(GetPrimaryFrame());
3374 mIsDraggingRange
= true;
3375 mRangeThumbDragStartValue
= GetValueAsDecimal();
3376 // Don't use CaptureFlags::RetargetToElement, as that breaks pseudo-class
3377 // styling of the thumb.
3378 PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState
);
3380 // Before we change the value, record the current value so that we'll
3381 // correctly send a 'change' event if appropriate. We need to do this here
3382 // because the 'focus' event is handled after the 'mousedown' event that
3383 // we're being called for (i.e. too late to update mFocusedValue, since we'll
3384 // have changed it by then).
3385 GetValue(mFocusedValue
, CallerType::System
);
3387 SetValueOfRangeForUserEvent(rangeFrame
->GetValueAtEventPoint(aEvent
),
3388 SnapToTickMarks::Yes
);
3391 void HTMLInputElement::FinishRangeThumbDrag(WidgetGUIEvent
* aEvent
) {
3392 MOZ_ASSERT(mIsDraggingRange
);
3394 if (PresShell::GetCapturingContent() == this) {
3395 PresShell::ReleaseCapturingContent();
3398 nsRangeFrame
* rangeFrame
= do_QueryFrame(GetPrimaryFrame());
3399 SetValueOfRangeForUserEvent(rangeFrame
->GetValueAtEventPoint(aEvent
),
3400 SnapToTickMarks::Yes
);
3402 mIsDraggingRange
= false;
3403 FireChangeEventIfNeeded();
3406 void HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent
) {
3407 MOZ_ASSERT(mIsDraggingRange
);
3409 mIsDraggingRange
= false;
3410 if (PresShell::GetCapturingContent() == this) {
3411 PresShell::ReleaseCapturingContent();
3413 if (aIsForUserEvent
) {
3414 SetValueOfRangeForUserEvent(mRangeThumbDragStartValue
,
3415 SnapToTickMarks::Yes
);
3417 // Don't dispatch an 'input' event - at least not using
3418 // DispatchTrustedEvent.
3419 // TODO: decide what we should do here - bug 851782.
3421 mInputType
->ConvertNumberToString(mRangeThumbDragStartValue
, val
);
3422 // TODO: What should we do if SetValueInternal fails? (The allocation
3423 // is small, so we should be fine here.)
3424 SetValueInternal(val
, {ValueSetterOption::BySetUserInputAPI
,
3425 ValueSetterOption::SetValueChanged
});
3426 if (nsRangeFrame
* frame
= do_QueryFrame(GetPrimaryFrame())) {
3427 frame
->UpdateForValueChange();
3429 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(this);
3430 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
3431 "Failed to dispatch input event");
3435 void HTMLInputElement::SetValueOfRangeForUserEvent(
3436 Decimal aValue
, SnapToTickMarks aSnapToTickMarks
) {
3437 MOZ_ASSERT(aValue
.isFinite());
3438 if (aSnapToTickMarks
== SnapToTickMarks::Yes
) {
3439 MaybeSnapToTickMark(aValue
);
3442 Decimal oldValue
= GetValueAsDecimal();
3445 mInputType
->ConvertNumberToString(aValue
, val
);
3446 // TODO: What should we do if SetValueInternal fails? (The allocation
3447 // is small, so we should be fine here.)
3448 SetValueInternal(val
, {ValueSetterOption::BySetUserInputAPI
,
3449 ValueSetterOption::SetValueChanged
});
3450 if (nsRangeFrame
* frame
= do_QueryFrame(GetPrimaryFrame())) {
3451 frame
->UpdateForValueChange();
3454 if (GetValueAsDecimal() != oldValue
) {
3455 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(this);
3456 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
3457 "Failed to dispatch input event");
3461 void HTMLInputElement::StartNumberControlSpinnerSpin() {
3462 MOZ_ASSERT(!mNumberControlSpinnerIsSpinning
);
3464 mNumberControlSpinnerIsSpinning
= true;
3466 nsRepeatService::GetInstance()->Start(
3467 HandleNumberControlSpin
, this, OwnerDoc(), "HandleNumberControlSpin"_ns
);
3469 // Capture the mouse so that we can tell if the pointer moves from one
3470 // spin button to the other, or to some other element:
3471 PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState
);
3473 nsNumberControlFrame
* numberControlFrame
= do_QueryFrame(GetPrimaryFrame());
3474 if (numberControlFrame
) {
3475 numberControlFrame
->SpinnerStateChanged();
3479 void HTMLInputElement::StopNumberControlSpinnerSpin(SpinnerStopState aState
) {
3480 if (mNumberControlSpinnerIsSpinning
) {
3481 if (PresShell::GetCapturingContent() == this) {
3482 PresShell::ReleaseCapturingContent();
3485 nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin
, this);
3487 mNumberControlSpinnerIsSpinning
= false;
3489 if (aState
== eAllowDispatchingEvents
) {
3490 FireChangeEventIfNeeded();
3493 nsNumberControlFrame
* numberControlFrame
= do_QueryFrame(GetPrimaryFrame());
3494 if (numberControlFrame
) {
3495 MOZ_ASSERT(aState
== eAllowDispatchingEvents
,
3496 "Shouldn't have primary frame for the element when we're not "
3497 "allowed to dispatch events to it anymore.");
3498 numberControlFrame
->SpinnerStateChanged();
3503 void HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection
) {
3504 // We can't use GetValidityState here because the validity state is not set
3505 // if the user hasn't previously taken an action to set or change the value,
3506 // according to the specs.
3507 if (HasBadInput()) {
3508 // If the user has typed a value into the control and inadvertently made a
3509 // mistake (e.g. put a thousand separator at the wrong point) we do not
3510 // want to wipe out what they typed if they try to increment/decrement the
3511 // value. Better is to highlight the value as being invalid so that they
3512 // can correct what they typed.
3513 // We only do this if there actually is a value typed in by/displayed to
3514 // the user. (IsValid() can return false if the 'required' attribute is
3515 // set and the value is the empty string.)
3516 if (!IsValueEmpty()) {
3517 // We pass 'true' for SetUserInteracted because we need the UI to update
3518 // _now_ or the user will wonder why the step behavior isn't functioning.
3519 SetUserInteracted(true);
3524 Decimal newValue
= Decimal::nan(); // unchanged if value will not change
3526 nsresult rv
= GetValueIfStepped(aDirection
, CALLED_FOR_USER_EVENT
, &newValue
);
3528 if (NS_FAILED(rv
) || !newValue
.isFinite()) {
3529 return; // value should not or will not change
3532 nsAutoString newVal
;
3533 mInputType
->ConvertNumberToString(newValue
, newVal
);
3534 // TODO: What should we do if SetValueInternal fails? (The allocation
3535 // is small, so we should be fine here.)
3536 SetValueInternal(newVal
, {ValueSetterOption::BySetUserInputAPI
,
3537 ValueSetterOption::SetValueChanged
});
3540 static bool SelectTextFieldOnFocus() {
3541 if (!gSelectTextFieldOnFocus
) {
3542 int32_t selectTextfieldsOnKeyFocus
= -1;
3544 LookAndFeel::GetInt(LookAndFeel::IntID::SelectTextfieldsOnKeyFocus
,
3545 &selectTextfieldsOnKeyFocus
);
3546 if (NS_FAILED(rv
)) {
3547 gSelectTextFieldOnFocus
= -1;
3549 gSelectTextFieldOnFocus
= selectTextfieldsOnKeyFocus
!= 0 ? 1 : -1;
3553 return gSelectTextFieldOnFocus
== 1;
3556 bool HTMLInputElement::ShouldPreventDOMActivateDispatch(
3557 EventTarget
* aOriginalTarget
) {
3559 * For the moment, there is only one situation where we actually want to
3560 * prevent firing a DOMActivate event:
3561 * - we are a <input type='file'> that just got a click event,
3562 * - the event was targeted to our button which should have sent a
3563 * DOMActivate event.
3566 if (mType
!= FormControlType::InputFile
) {
3570 Element
* target
= Element::FromEventTargetOrNull(aOriginalTarget
);
3575 return target
->GetParent() == this &&
3576 target
->IsRootOfNativeAnonymousSubtree() &&
3577 target
->IsHTMLElement(nsGkAtoms::button
);
3580 nsresult
HTMLInputElement::MaybeInitPickers(EventChainPostVisitor
& aVisitor
) {
3581 // Open a file picker when we receive a click on a <input type='file'>, or
3582 // open a color picker when we receive a click on a <input type='color'>.
3583 // A click is handled if it's the left mouse button.
3584 // We do not prevent non-trusted click because authors can already use
3585 // .click(). However, the pickers will follow the rules of popup-blocking.
3586 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
3587 if (!(mouseEvent
&& mouseEvent
->IsLeftClickEvent())) {
3590 if (mType
== FormControlType::InputFile
) {
3591 // If the user clicked on the "Choose folder..." button we open the
3592 // directory picker, else we open the file picker.
3593 FilePickerType type
= FILE_PICKER_FILE
;
3594 nsIContent
* target
=
3595 nsIContent::FromEventTargetOrNull(aVisitor
.mEvent
->mOriginalTarget
);
3596 if (target
&& target
->FindFirstNonChromeOnlyAccessContent() == this &&
3597 StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
3598 HasAttr(nsGkAtoms::webkitdirectory
)) {
3599 type
= FILE_PICKER_DIRECTORY
;
3601 return InitFilePicker(type
);
3603 if (mType
== FormControlType::InputColor
) {
3604 return InitColorPicker();
3611 * Return true if the input event should be ignored because of its modifiers.
3612 * Control is treated specially, since sometimes we ignore it, and sometimes
3613 * we don't (for webcompat reasons).
3615 static bool IgnoreInputEventWithModifier(const WidgetInputEvent
& aEvent
,
3616 bool ignoreControl
) {
3617 return (ignoreControl
&& aEvent
.IsControl()) ||
3619 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
3620 // Meta key is the Windows Logo key on Windows and Linux which may
3621 // assign some special meaning for the events while it's pressed.
3622 // On the other hand, it's a normal modifier in macOS and Android.
3623 // Therefore, We should ignore it only in Win/Linux.
3629 bool HTMLInputElement::StepsInputValue(
3630 const WidgetKeyboardEvent
& aEvent
) const {
3631 if (mType
!= FormControlType::InputNumber
) {
3634 if (aEvent
.mMessage
!= eKeyPress
) {
3637 if (!aEvent
.IsTrusted()) {
3640 if (aEvent
.mKeyCode
!= NS_VK_UP
&& aEvent
.mKeyCode
!= NS_VK_DOWN
) {
3643 if (IgnoreInputEventWithModifier(aEvent
, false)) {
3646 if (aEvent
.DefaultPrevented()) {
3655 static bool ActivatesWithKeyboard(FormControlType aType
, uint32_t aKeyCode
) {
3657 case FormControlType::InputCheckbox
:
3658 case FormControlType::InputRadio
:
3659 // Checkbox and Radio try to submit on Enter press
3660 return aKeyCode
!= NS_VK_RETURN
;
3661 case FormControlType::InputButton
:
3662 case FormControlType::InputReset
:
3663 case FormControlType::InputSubmit
:
3664 case FormControlType::InputFile
:
3665 case FormControlType::InputImage
: // Bug 34418
3666 case FormControlType::InputColor
:
3673 nsresult
HTMLInputElement::PostHandleEvent(EventChainPostVisitor
& aVisitor
) {
3674 if (aVisitor
.mEvent
->mMessage
== eBlur
) {
3675 if (mIsDraggingRange
) {
3676 FinishRangeThumbDrag();
3677 } else if (mNumberControlSpinnerIsSpinning
) {
3678 StopNumberControlSpinnerSpin();
3682 nsresult rv
= NS_OK
;
3683 auto oldType
= FormControlType(NS_CONTROL_TYPE(aVisitor
.mItemFlags
));
3685 // Ideally we would make the default action for click and space just dispatch
3686 // DOMActivate, and the default action for DOMActivate flip the checkbox/
3687 // radio state and fire onchange. However, for backwards compatibility, we
3688 // need to flip the state before firing click, and we need to fire click
3689 // when space is pressed. So, we just nest the firing of DOMActivate inside
3690 // the click event handling, and allow cancellation of DOMActivate to cancel
3692 if (aVisitor
.mEventStatus
!= nsEventStatus_eConsumeNoDefault
&&
3693 !IsSingleLineTextControl(true) && mType
!= FormControlType::InputNumber
) {
3694 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
3695 if (mouseEvent
&& mouseEvent
->IsLeftClickEvent() &&
3696 OwnerDoc()->MayHaveDOMActivateListeners() &&
3697 !ShouldPreventDOMActivateDispatch(aVisitor
.mEvent
->mOriginalTarget
)) {
3698 // DOMActive event should be trusted since the activation is actually
3699 // occurred even if the cause is an untrusted click event.
3700 InternalUIEvent
actEvent(true, eLegacyDOMActivate
, mouseEvent
);
3701 actEvent
.mDetail
= 1;
3703 if (RefPtr
<PresShell
> presShell
=
3704 aVisitor
.mPresContext
? aVisitor
.mPresContext
->GetPresShell()
3706 nsEventStatus status
= nsEventStatus_eIgnore
;
3707 mInInternalActivate
= true;
3708 rv
= presShell
->HandleDOMEventWithTarget(this, &actEvent
, &status
);
3709 mInInternalActivate
= false;
3711 // If activate is cancelled, we must do the same as when click is
3712 // cancelled (revert the checkbox to its original value).
3713 if (status
== nsEventStatus_eConsumeNoDefault
) {
3714 aVisitor
.mEventStatus
= status
;
3720 bool preventDefault
=
3721 aVisitor
.mEventStatus
== nsEventStatus_eConsumeNoDefault
;
3722 if (IsDisabled() && oldType
!= FormControlType::InputCheckbox
&&
3723 oldType
!= FormControlType::InputRadio
) {
3724 // Behave as if defaultPrevented when the element becomes disabled by event
3725 // listeners. Checkboxes and radio buttons should still process clicks for
3727 // https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
3728 preventDefault
= true;
3731 if (NS_SUCCEEDED(rv
)) {
3732 WidgetKeyboardEvent
* keyEvent
= aVisitor
.mEvent
->AsKeyboardEvent();
3733 if (keyEvent
&& StepsInputValue(*keyEvent
)) {
3734 StepNumberControlForUserEvent(keyEvent
->mKeyCode
== NS_VK_UP
? 1 : -1);
3735 FireChangeEventIfNeeded();
3736 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
3737 } else if (!preventDefault
) {
3738 if (keyEvent
&& ActivatesWithKeyboard(mType
, keyEvent
->mKeyCode
) &&
3739 keyEvent
->IsTrusted()) {
3740 // We maybe dispatch a synthesized click for keyboard activation.
3741 HandleKeyboardActivation(aVisitor
);
3744 switch (aVisitor
.mEvent
->mMessage
) {
3746 // see if we should select the contents of the textbox. This happens
3747 // for text and password fields when the field was focused by the
3748 // keyboard or a navigation, the platform allows it, and it wasn't
3749 // just because we raised a window.
3751 // While it'd usually make sense, we don't do this for JS callers
3752 // because it causes some compat issues, see bug 1712724 for example.
3753 nsFocusManager
* fm
= nsFocusManager::GetFocusManager();
3754 if (fm
&& IsSingleLineTextControl(false) &&
3755 !aVisitor
.mEvent
->AsFocusEvent()->mFromRaise
&&
3756 SelectTextFieldOnFocus()) {
3757 if (Document
* document
= GetComposedDoc()) {
3758 uint32_t lastFocusMethod
=
3759 fm
->GetLastFocusMethod(document
->GetWindow());
3760 const bool shouldSelectAllOnFocus
= [&] {
3761 if (lastFocusMethod
& nsIFocusManager::FLAG_BYMOVEFOCUS
) {
3764 if (lastFocusMethod
& nsIFocusManager::FLAG_BYJS
) {
3767 return bool(lastFocusMethod
& nsIFocusManager::FLAG_BYKEY
);
3769 if (shouldSelectAllOnFocus
) {
3770 RefPtr
<nsPresContext
> presContext
=
3771 GetPresContext(eForComposedDoc
);
3772 SelectAll(presContext
);
3780 // For compatibility with the other browsers, we should active this
3781 // element at least when a checkbox or a radio button.
3782 // TODO: Investigate which elements are activated by space key in the
3784 if (aVisitor
.mPresContext
&& keyEvent
->IsTrusted() && !IsDisabled() &&
3785 keyEvent
->ShouldWorkAsSpaceKey() &&
3786 (mType
== FormControlType::InputCheckbox
||
3787 mType
== FormControlType::InputRadio
)) {
3788 EventStateManager::SetActiveManager(
3789 aVisitor
.mPresContext
->EventStateManager(), this);
3795 if (mType
== FormControlType::InputRadio
&& keyEvent
->IsTrusted() &&
3796 !keyEvent
->IsAlt() && !keyEvent
->IsControl() &&
3797 !keyEvent
->IsMeta()) {
3798 rv
= MaybeHandleRadioButtonNavigation(aVisitor
, keyEvent
->mKeyCode
);
3802 * For some input types, if the user hits enter, the form is
3805 * Bug 99920, bug 109463 and bug 147850:
3806 * (a) if there is a submit control in the form, click the first
3807 * submit control in the form.
3808 * (b) if there is just one text control in the form, submit by
3809 * sending a submit event directly to the form
3810 * (c) if there is more than one text input and no submit buttons, do
3811 * not submit, period.
3814 if (keyEvent
->mKeyCode
== NS_VK_RETURN
&& keyEvent
->IsTrusted() &&
3815 (IsSingleLineTextControl(false, mType
) ||
3816 IsDateTimeInputType(mType
) ||
3817 mType
== FormControlType::InputCheckbox
||
3818 mType
== FormControlType::InputRadio
)) {
3819 if (IsSingleLineTextControl(false, mType
) ||
3820 IsDateTimeInputType(mType
)) {
3821 FireChangeEventIfNeeded();
3824 if (aVisitor
.mPresContext
) {
3825 MaybeSubmitForm(aVisitor
.mPresContext
);
3829 if (mType
== FormControlType::InputRange
&& keyEvent
->IsTrusted() &&
3830 !keyEvent
->IsAlt() && !keyEvent
->IsControl() &&
3831 !keyEvent
->IsMeta() &&
3832 (keyEvent
->mKeyCode
== NS_VK_LEFT
||
3833 keyEvent
->mKeyCode
== NS_VK_RIGHT
||
3834 keyEvent
->mKeyCode
== NS_VK_UP
||
3835 keyEvent
->mKeyCode
== NS_VK_DOWN
||
3836 keyEvent
->mKeyCode
== NS_VK_PAGE_UP
||
3837 keyEvent
->mKeyCode
== NS_VK_PAGE_DOWN
||
3838 keyEvent
->mKeyCode
== NS_VK_HOME
||
3839 keyEvent
->mKeyCode
== NS_VK_END
)) {
3840 Decimal minimum
= GetMinimum();
3841 Decimal maximum
= GetMaximum();
3842 MOZ_ASSERT(minimum
.isFinite() && maximum
.isFinite());
3843 if (minimum
< maximum
) { // else the value is locked to the minimum
3844 Decimal value
= GetValueAsDecimal();
3845 Decimal step
= GetStep();
3846 if (step
== kStepAny
) {
3847 step
= GetDefaultStep();
3849 MOZ_ASSERT(value
.isFinite() && step
.isFinite());
3851 switch (keyEvent
->mKeyCode
) {
3854 (GetComputedDirectionality() == Directionality::Rtl
3860 (GetComputedDirectionality() == Directionality::Rtl
3865 // Even for horizontal range, "up" means "increase"
3866 newValue
= value
+ step
;
3869 // Even for horizontal range, "down" means "decrease"
3870 newValue
= value
- step
;
3879 // For PgUp/PgDn we jump 10% of the total range, unless step
3880 // requires us to jump more.
3882 value
+ std::max(step
, (maximum
- minimum
) / Decimal(10));
3884 case NS_VK_PAGE_DOWN
:
3886 value
- std::max(step
, (maximum
- minimum
) / Decimal(10));
3889 SetValueOfRangeForUserEvent(newValue
);
3890 FireChangeEventIfNeeded();
3891 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
3895 } break; // eKeyPress
3899 case eMouseDoubleClick
: {
3900 // cancel all of these events for buttons
3902 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
3903 if (mouseEvent
->mButton
== MouseButton::eMiddle
||
3904 mouseEvent
->mButton
== MouseButton::eSecondary
) {
3905 if (mType
== FormControlType::InputButton
||
3906 mType
== FormControlType::InputReset
||
3907 mType
== FormControlType::InputSubmit
) {
3908 if (aVisitor
.mDOMEvent
) {
3909 aVisitor
.mDOMEvent
->StopPropagation();
3911 rv
= NS_ERROR_FAILURE
;
3915 if (mType
== FormControlType::InputNumber
&&
3916 aVisitor
.mEvent
->IsTrusted()) {
3917 if (mouseEvent
->mButton
== MouseButton::ePrimary
&&
3918 !IgnoreInputEventWithModifier(*mouseEvent
, false)) {
3919 nsNumberControlFrame
* numberControlFrame
=
3920 do_QueryFrame(GetPrimaryFrame());
3921 if (numberControlFrame
) {
3922 if (aVisitor
.mEvent
->mMessage
== eMouseDown
&& IsMutable()) {
3923 switch (numberControlFrame
->GetSpinButtonForPointerEvent(
3924 aVisitor
.mEvent
->AsMouseEvent())) {
3925 case nsNumberControlFrame::eSpinButtonUp
:
3926 StepNumberControlForUserEvent(1);
3927 mNumberControlSpinnerSpinsUp
= true;
3928 StartNumberControlSpinnerSpin();
3929 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
3931 case nsNumberControlFrame::eSpinButtonDown
:
3932 StepNumberControlForUserEvent(-1);
3933 mNumberControlSpinnerSpinsUp
= false;
3934 StartNumberControlSpinnerSpin();
3935 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
3941 if (aVisitor
.mEventStatus
!= nsEventStatus_eConsumeNoDefault
) {
3942 // We didn't handle this to step up/down. Whatever this was, be
3943 // aggressive about stopping the spin. (And don't set
3944 // nsEventStatus_eConsumeNoDefault after doing so, since that
3945 // might prevent, say, the context menu from opening.)
3946 StopNumberControlSpinnerSpin();
3951 #if !defined(ANDROID) && !defined(XP_MACOSX)
3953 // Handle wheel events as increasing / decreasing the input element's
3954 // value when it's focused and it's type is number or range.
3955 WidgetWheelEvent
* wheelEvent
= aVisitor
.mEvent
->AsWheelEvent();
3956 if (!aVisitor
.mEvent
->DefaultPrevented() &&
3957 aVisitor
.mEvent
->IsTrusted() && IsMutable() && wheelEvent
&&
3958 wheelEvent
->mDeltaY
!= 0 &&
3959 wheelEvent
->mDeltaMode
!= WheelEvent_Binding::DOM_DELTA_PIXEL
) {
3960 if (mType
== FormControlType::InputNumber
) {
3961 if (nsContentUtils::IsFocusedContent(this)) {
3962 StepNumberControlForUserEvent(wheelEvent
->mDeltaY
> 0 ? -1 : 1);
3963 FireChangeEventIfNeeded();
3964 aVisitor
.mEvent
->PreventDefault();
3966 } else if (mType
== FormControlType::InputRange
&&
3967 nsContentUtils::IsFocusedContent(this) &&
3968 GetMinimum() < GetMaximum()) {
3969 Decimal value
= GetValueAsDecimal();
3970 Decimal step
= GetStep();
3971 if (step
== kStepAny
) {
3972 step
= GetDefaultStep();
3974 MOZ_ASSERT(value
.isFinite() && step
.isFinite());
3975 SetValueOfRangeForUserEvent(
3976 wheelEvent
->mDeltaY
< 0 ? value
+ step
: value
- step
);
3977 FireChangeEventIfNeeded();
3978 aVisitor
.mEvent
->PreventDefault();
3985 if (!aVisitor
.mEvent
->DefaultPrevented() &&
3986 aVisitor
.mEvent
->IsTrusted() &&
3987 aVisitor
.mEvent
->AsMouseEvent()->mButton
==
3988 MouseButton::ePrimary
) {
3989 // TODO(emilio): Handling this should ideally not move focus.
3990 if (mType
== FormControlType::InputSearch
) {
3991 if (nsSearchControlFrame
* searchControlFrame
=
3992 do_QueryFrame(GetPrimaryFrame())) {
3993 Element
* clearButton
= searchControlFrame
->GetAnonClearButton();
3995 aVisitor
.mEvent
->mOriginalTarget
== clearButton
) {
3996 SetUserInput(EmptyString(),
3997 *nsContentUtils::GetSystemPrincipal());
3998 // TODO(emilio): This should focus the input, but calling
3999 // SetFocus(this, FLAG_NOSCROLL) for some reason gets us into
4000 // an inconsistent state where we're focused but don't match
4001 // :focus-visible / :focus.
4004 } else if (mType
== FormControlType::InputPassword
) {
4005 if (nsTextControlFrame
* textControlFrame
=
4006 do_QueryFrame(GetPrimaryFrame())) {
4007 auto* reveal
= textControlFrame
->GetRevealButton();
4008 if (reveal
&& aVisitor
.mEvent
->mOriginalTarget
== reveal
) {
4009 SetRevealPassword(!RevealPassword());
4010 // TODO(emilio): This should focus the input, but calling
4011 // SetFocus(this, FLAG_NOSCROLL) for some reason gets us into
4012 // an inconsistent state where we're focused but don't match
4013 // :focus-visible / :focus.
4024 // Bug 1459231: Temporarily needed till links respect activation target,
4025 // then also remove NS_OUTER_ACTIVATE_EVENT. The appropriate
4026 // behavior/model for links is still under discussion (see
4027 // https://github.com/whatwg/html/issues/1576). For now, we aim for
4028 // consistency with other browsers.
4029 if (aVisitor
.mItemFlags
& NS_OUTER_ACTIVATE_EVENT
) {
4031 case FormControlType::InputReset
:
4032 case FormControlType::InputSubmit
:
4033 case FormControlType::InputImage
:
4035 aVisitor
.mEvent
->mFlags
.mMultipleActionsPrevented
= true;
4038 case FormControlType::InputCheckbox
:
4039 case FormControlType::InputRadio
:
4040 aVisitor
.mEvent
->mFlags
.mMultipleActionsPrevented
= true;
4049 if (NS_SUCCEEDED(rv
) && mType
== FormControlType::InputRange
) {
4050 PostHandleEventForRangeThumb(aVisitor
);
4053 if (!preventDefault
) {
4054 MOZ_TRY(MaybeInitPickers(aVisitor
));
4059 void EndSubmitClick(EventChainPostVisitor
& aVisitor
) {
4060 auto oldType
= FormControlType(NS_CONTROL_TYPE(aVisitor
.mItemFlags
));
4061 if ((aVisitor
.mItemFlags
& NS_IN_SUBMIT_CLICK
) &&
4062 (oldType
== FormControlType::InputSubmit
||
4063 oldType
== FormControlType::InputImage
)) {
4064 nsCOMPtr
<nsIContent
> content(do_QueryInterface(aVisitor
.mItemData
));
4065 RefPtr
<HTMLFormElement
> form
= HTMLFormElement::FromNodeOrNull(content
);
4066 // Tell the form that we are about to exit a click handler,
4067 // so the form knows not to defer subsequent submissions.
4068 // The pending ones that were created during the handler
4069 // will be flushed or forgotten.
4070 form
->OnSubmitClickEnd();
4071 // tell the form to flush a possible pending submission.
4072 // the reason is that the script returned false (the event was
4073 // not ignored) so if there is a stored submission, it needs to
4074 // be submitted immediately.
4075 form
->FlushPendingSubmission();
4079 void HTMLInputElement::ActivationBehavior(EventChainPostVisitor
& aVisitor
) {
4080 auto oldType
= FormControlType(NS_CONTROL_TYPE(aVisitor
.mItemFlags
));
4082 if (IsDisabled() && oldType
!= FormControlType::InputCheckbox
&&
4083 oldType
!= FormControlType::InputRadio
) {
4084 // Behave as if defaultPrevented when the element becomes disabled by event
4085 // listeners. Checkboxes and radio buttons should still process clicks for
4087 // https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
4088 EndSubmitClick(aVisitor
);
4092 if (mCheckedIsToggled
) {
4093 SetUserInteracted(true);
4095 // Fire input event and then change event.
4096 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(this);
4097 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
4098 "Failed to dispatch input event");
4100 // FIXME: Why is this different than every other change event?
4101 nsContentUtils::DispatchTrustedEvent
<WidgetEvent
>(
4102 OwnerDoc(), static_cast<Element
*>(this), eFormChange
, CanBubble::eYes
,
4104 #ifdef ACCESSIBILITY
4105 // Fire an event to notify accessibility
4106 if (mType
== FormControlType::InputCheckbox
) {
4107 if (nsContentUtils::MayHaveFormCheckboxStateChangeListeners()) {
4108 FireEventForAccessibility(this, eFormCheckboxStateChange
);
4110 } else if (nsContentUtils::MayHaveFormRadioStateChangeListeners()) {
4111 FireEventForAccessibility(this, eFormRadioStateChange
);
4112 // Fire event for the previous selected radio.
4113 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aVisitor
.mItemData
);
4114 if (auto* previous
= HTMLInputElement::FromNodeOrNull(content
)) {
4115 FireEventForAccessibility(previous
, eFormRadioStateChange
);
4122 case FormControlType::InputReset
:
4123 case FormControlType::InputSubmit
:
4124 case FormControlType::InputImage
:
4126 // Hold a strong ref while dispatching
4127 RefPtr
<HTMLFormElement
> form(mForm
);
4128 if (mType
== FormControlType::InputReset
) {
4129 form
->MaybeReset(this);
4131 form
->MaybeSubmit(this);
4133 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
4140 if (IsButtonControl()) {
4141 if (!GetInvokeTargetElement()) {
4142 HandlePopoverTargetAction();
4144 HandleInvokeTargetAction();
4148 EndSubmitClick(aVisitor
);
4151 void HTMLInputElement::LegacyCanceledActivationBehavior(
4152 EventChainPostVisitor
& aVisitor
) {
4153 bool originalCheckedValue
=
4154 !!(aVisitor
.mItemFlags
& NS_ORIGINAL_CHECKED_VALUE
);
4155 auto oldType
= FormControlType(NS_CONTROL_TYPE(aVisitor
.mItemFlags
));
4157 if (mCheckedIsToggled
) {
4158 // if it was canceled and a radio button, then set the old
4159 // selected btn to TRUE. if it is a checkbox then set it to its
4160 // original value (legacy-canceled-activation)
4161 if (oldType
== FormControlType::InputRadio
) {
4162 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aVisitor
.mItemData
);
4163 HTMLInputElement
* selectedRadioButton
=
4164 HTMLInputElement::FromNodeOrNull(content
);
4165 if (selectedRadioButton
) {
4166 selectedRadioButton
->SetChecked(true);
4168 // If there was no checked radio button or this one is no longer a
4169 // radio button we must reset it back to false to cancel the action.
4170 // See how the web of hack grows?
4171 if (!selectedRadioButton
|| mType
!= FormControlType::InputRadio
) {
4172 DoSetChecked(false, true, true);
4174 } else if (oldType
== FormControlType::InputCheckbox
) {
4175 bool originalIndeterminateValue
=
4176 !!(aVisitor
.mItemFlags
& NS_ORIGINAL_INDETERMINATE_VALUE
);
4177 SetIndeterminateInternal(originalIndeterminateValue
, false);
4178 DoSetChecked(originalCheckedValue
, true, true);
4182 // Relevant for bug 242494: submit button with "submit(); return false;"
4183 EndSubmitClick(aVisitor
);
4186 enum class RadioButtonMove
{ Back
, Forward
, None
};
4187 nsresult
HTMLInputElement::MaybeHandleRadioButtonNavigation(
4188 EventChainPostVisitor
& aVisitor
, uint32_t aKeyCode
) {
4192 return RadioButtonMove::Back
;
4194 return RadioButtonMove::Forward
;
4197 const bool isRtl
= GetComputedDirectionality() == Directionality::Rtl
;
4198 return isRtl
== (aKeyCode
== NS_VK_LEFT
) ? RadioButtonMove::Forward
4199 : RadioButtonMove::Back
;
4202 return RadioButtonMove::None
;
4204 if (move
== RadioButtonMove::None
) {
4207 // Arrow key pressed, focus+select prev/next radio button
4208 RefPtr
<HTMLInputElement
> selectedRadioButton
;
4209 if (auto* container
= GetCurrentRadioGroupContainer()) {
4211 GetAttr(nsGkAtoms::name
, name
);
4212 container
->GetNextRadioButton(name
, move
== RadioButtonMove::Back
, this,
4213 getter_AddRefs(selectedRadioButton
));
4215 if (!selectedRadioButton
) {
4218 FocusOptions options
;
4220 selectedRadioButton
->Focus(options
, CallerType::System
, error
);
4221 if (error
.Failed()) {
4222 return error
.StealNSResult();
4224 nsresult rv
= DispatchSimulatedClick(
4225 selectedRadioButton
, aVisitor
.mEvent
->IsTrusted(), aVisitor
.mPresContext
);
4226 if (NS_SUCCEEDED(rv
)) {
4227 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
4232 void HTMLInputElement::PostHandleEventForRangeThumb(
4233 EventChainPostVisitor
& aVisitor
) {
4234 MOZ_ASSERT(mType
== FormControlType::InputRange
);
4236 if (nsEventStatus_eConsumeNoDefault
== aVisitor
.mEventStatus
||
4237 !(aVisitor
.mEvent
->mClass
== eMouseEventClass
||
4238 aVisitor
.mEvent
->mClass
== eTouchEventClass
||
4239 aVisitor
.mEvent
->mClass
== eKeyboardEventClass
)) {
4243 nsRangeFrame
* rangeFrame
= do_QueryFrame(GetPrimaryFrame());
4244 if (!rangeFrame
&& mIsDraggingRange
) {
4245 CancelRangeThumbDrag();
4249 switch (aVisitor
.mEvent
->mMessage
) {
4252 if (mIsDraggingRange
) {
4255 if (PresShell::GetCapturingContent()) {
4256 break; // don't start drag if someone else is already capturing
4258 WidgetInputEvent
* inputEvent
= aVisitor
.mEvent
->AsInputEvent();
4259 if (IgnoreInputEventWithModifier(*inputEvent
, true)) {
4262 if (aVisitor
.mEvent
->mMessage
== eMouseDown
) {
4263 if (aVisitor
.mEvent
->AsMouseEvent()->mButtons
==
4264 MouseButtonsFlag::ePrimaryFlag
) {
4265 StartRangeThumbDrag(inputEvent
);
4266 } else if (mIsDraggingRange
) {
4267 CancelRangeThumbDrag();
4270 if (aVisitor
.mEvent
->AsTouchEvent()->mTouches
.Length() == 1) {
4271 StartRangeThumbDrag(inputEvent
);
4272 } else if (mIsDraggingRange
) {
4273 CancelRangeThumbDrag();
4276 aVisitor
.mEvent
->mFlags
.mMultipleActionsPrevented
= true;
4281 if (!mIsDraggingRange
) {
4284 if (PresShell::GetCapturingContent() != this) {
4285 // Someone else grabbed capture.
4286 CancelRangeThumbDrag();
4289 SetValueOfRangeForUserEvent(
4290 rangeFrame
->GetValueAtEventPoint(aVisitor
.mEvent
->AsInputEvent()),
4291 SnapToTickMarks::Yes
);
4292 aVisitor
.mEvent
->mFlags
.mMultipleActionsPrevented
= true;
4297 if (!mIsDraggingRange
) {
4300 // We don't check to see whether we are the capturing content here and
4301 // call CancelRangeThumbDrag() if that is the case. We just finish off
4302 // the drag and set our final value (unless someone has called
4303 // preventDefault() and prevents us getting here).
4304 FinishRangeThumbDrag(aVisitor
.mEvent
->AsInputEvent());
4305 aVisitor
.mEvent
->mFlags
.mMultipleActionsPrevented
= true;
4309 if (mIsDraggingRange
&&
4310 aVisitor
.mEvent
->AsKeyboardEvent()->mKeyCode
== NS_VK_ESCAPE
) {
4311 CancelRangeThumbDrag();
4316 if (mIsDraggingRange
) {
4317 CancelRangeThumbDrag();
4326 void HTMLInputElement::MaybeLoadImage() {
4327 // Our base URI may have changed; claim that our URI changed, and the
4328 // nsImageLoadingContent will decide whether a new image load is warranted.
4330 if (mType
== FormControlType::InputImage
&& GetAttr(nsGkAtoms::src
, uri
) &&
4331 (NS_FAILED(LoadImage(uri
, false, true, eImageLoadType_Normal
,
4332 mSrcTriggeringPrincipal
)) ||
4333 !LoadingEnabled())) {
4334 CancelImageRequests(true);
4338 nsresult
HTMLInputElement::BindToTree(BindContext
& aContext
, nsINode
& aParent
) {
4339 // If we are currently bound to a disconnected subtree root, remove
4340 // ourselves from it first.
4341 if (!mForm
&& mType
== FormControlType::InputRadio
) {
4342 RemoveFromRadioGroup();
4346 nsGenericHTMLFormControlElementWithState::BindToTree(aContext
, aParent
);
4347 NS_ENSURE_SUCCESS(rv
, rv
);
4349 nsImageLoadingContent::BindToTree(aContext
, aParent
);
4351 if (mType
== FormControlType::InputImage
) {
4352 // Our base URI may have changed; claim that our URI changed, and the
4353 // nsImageLoadingContent will decide whether a new image load is warranted.
4354 if (HasAttr(nsGkAtoms::src
)) {
4355 // Mark channel as urgent-start before load image if the image load is
4356 // initaiated by a user interaction.
4357 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
4359 nsContentUtils::AddScriptRunner(
4360 NewRunnableMethod("dom::HTMLInputElement::MaybeLoadImage", this,
4361 &HTMLInputElement::MaybeLoadImage
));
4365 // Add radio to document if we don't have a form already (if we do it's
4366 // already been added into that group)
4367 if (!mForm
&& mType
== FormControlType::InputRadio
) {
4371 // Set direction based on value if dir=auto
4373 SetAutoDirectionality(false);
4376 // An element can't suffer from value missing if it is not in a document.
4377 // We have to check if we suffer from that as we are now in a document.
4378 UpdateValueMissingValidityState();
4380 // If there is a disabled fieldset in the parent chain, the element is now
4381 // barred from constraint validation and can't suffer from value missing
4382 // (call done before).
4383 UpdateBarredFromConstraintValidation();
4385 // And now make sure our state is up to date
4386 UpdateValidityElementStates(true);
4388 if (CreatesDateTimeWidget() && IsInComposedDoc()) {
4389 // Construct Shadow Root so web content can be hidden in the DOM.
4390 AttachAndSetUAShadowRoot(NotifyUAWidgetSetup::Yes
, DelegatesFocus::Yes
);
4393 MaybeDispatchLoginManagerEvents(mForm
);
4398 void HTMLInputElement::MaybeDispatchLoginManagerEvents(HTMLFormElement
* aForm
) {
4399 // Don't disptach the event if the <input> is disconnected
4400 // or belongs to a disconnected form
4401 if (!IsInComposedDoc()) {
4406 Element
* target
= nullptr;
4408 if (mType
== FormControlType::InputPassword
) {
4409 // Don't fire another event if we have a pending event.
4410 if (aForm
&& aForm
->mHasPendingPasswordEvent
) {
4414 // TODO(Bug 1864404): Use one event for formless and form inputs.
4415 eventType
= aForm
? u
"DOMFormHasPassword"_ns
: u
"DOMInputPasswordAdded"_ns
;
4417 target
= aForm
? static_cast<Element
*>(aForm
) : this;
4420 aForm
->mHasPendingPasswordEvent
= true;
4423 } else if (mType
== FormControlType::InputEmail
||
4424 mType
== FormControlType::InputText
) {
4425 // Don't fire a username event if:
4426 // - <input> is not part of a form
4427 // - we have a pending event
4428 // - username only forms are not supported
4429 if (!aForm
|| aForm
->mHasPendingPossibleUsernameEvent
||
4430 !StaticPrefs::signon_usernameOnlyForm_enabled()) {
4434 eventType
= u
"DOMFormHasPossibleUsername"_ns
;
4437 aForm
->mHasPendingPossibleUsernameEvent
= true;
4443 RefPtr
<AsyncEventDispatcher
> dispatcher
= new AsyncEventDispatcher(
4444 target
, eventType
, CanBubble::eYes
, ChromeOnlyDispatch::eYes
);
4445 dispatcher
->PostDOMEvent();
4448 void HTMLInputElement::UnbindFromTree(UnbindContext
& aContext
) {
4449 if (mType
== FormControlType::InputPassword
) {
4450 MaybeFireInputPasswordRemoved();
4453 // If we have a form and are unbound from it,
4454 // nsGenericHTMLFormControlElementWithState::UnbindFromTree() will unset the
4455 // form and that takes care of form's WillRemove so we just have to take care
4456 // of the case where we're removing from the document and we don't
4458 if (!mForm
&& mType
== FormControlType::InputRadio
) {
4459 RemoveFromRadioGroup();
4462 if (CreatesDateTimeWidget() && IsInComposedDoc()) {
4463 NotifyUAWidgetTeardown();
4466 nsImageLoadingContent::UnbindFromTree();
4467 nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext
);
4469 // If we are contained within a disconnected subtree, attempt to add
4470 // ourselves to the subtree root's radio group.
4471 if (!mForm
&& mType
== FormControlType::InputRadio
) {
4475 // GetCurrentDoc is returning nullptr so we can update the value
4476 // missing validity state to reflect we are no longer into a doc.
4477 UpdateValueMissingValidityState();
4478 // We might be no longer disabled because of parent chain changed.
4479 UpdateBarredFromConstraintValidation();
4480 // And now make sure our state is up to date
4481 UpdateValidityElementStates(false);
4485 * @param aType InputElementTypes
4486 * @return true, iff SetRangeText applies to aType as specified at
4487 * https://html.spec.whatwg.org/#concept-input-apply.
4489 static bool SetRangeTextApplies(FormControlType aType
) {
4490 return aType
== FormControlType::InputText
||
4491 aType
== FormControlType::InputSearch
||
4492 aType
== FormControlType::InputUrl
||
4493 aType
== FormControlType::InputTel
||
4494 aType
== FormControlType::InputPassword
;
4497 void HTMLInputElement::HandleTypeChange(FormControlType aNewType
,
4499 FormControlType oldType
= mType
;
4500 MOZ_ASSERT(oldType
!= aNewType
);
4502 mHasBeenTypePassword
=
4503 mHasBeenTypePassword
|| aNewType
== FormControlType::InputPassword
;
4505 if (nsFocusManager
* fm
= nsFocusManager::GetFocusManager()) {
4506 // Input element can represent very different kinds of UIs, and we may
4507 // need to flush styling even when focusing the already focused input
4509 fm
->NeedsFlushBeforeEventHandling(this);
4512 if (oldType
== FormControlType::InputPassword
&&
4513 State().HasState(ElementState::REVEALED
)) {
4514 // Modify the state directly to avoid dispatching events.
4515 RemoveStates(ElementState::REVEALED
, aNotify
);
4518 if (aNewType
== FormControlType::InputFile
||
4519 oldType
== FormControlType::InputFile
) {
4520 if (aNewType
== FormControlType::InputFile
) {
4521 mFileData
.reset(new FileData());
4523 mFileData
->Unlink();
4524 mFileData
= nullptr;
4528 if (oldType
== FormControlType::InputRange
&& mIsDraggingRange
) {
4529 CancelRangeThumbDrag(false);
4532 const ValueModeType oldValueMode
= GetValueMode();
4533 nsAutoString oldValue
;
4534 if (oldValueMode
== VALUE_MODE_VALUE
) {
4535 // Doesn't matter what caller type we pass here, since we know we're not a
4536 // file input anyway.
4537 GetValue(oldValue
, CallerType::NonSystem
);
4540 TextControlState::SelectionProperties sp
;
4542 if (IsSingleLineTextControl(false) && mInputData
.mState
) {
4543 mInputData
.mState
->SyncUpSelectionPropertiesBeforeDestruction();
4544 sp
= mInputData
.mState
->GetSelectionProperties();
4547 // We already have a copy of the value, lets free it and changes the type.
4550 void* memory
= mInputTypeMem
;
4551 mInputType
= InputType::Create(this, mType
, memory
);
4553 if (IsSingleLineTextControl()) {
4554 mInputData
.mState
= TextControlState::Construct(this);
4555 if (!sp
.IsDefault()) {
4556 mInputData
.mState
->SetSelectionProperties(sp
);
4560 // Whether placeholder applies might have changed.
4561 UpdatePlaceholderShownState();
4562 // Whether readonly applies might have changed.
4563 UpdateReadOnlyState(aNotify
);
4564 UpdateCheckedState(aNotify
);
4565 UpdateIndeterminateState(aNotify
);
4566 const bool isDefault
= IsRadioOrCheckbox()
4568 : (mForm
&& mForm
->IsDefaultSubmitElement(this));
4569 SetStates(ElementState::DEFAULT
, isDefault
, aNotify
);
4571 // https://html.spec.whatwg.org/#input-type-change
4572 switch (GetValueMode()) {
4573 case VALUE_MODE_DEFAULT
:
4574 case VALUE_MODE_DEFAULT_ON
:
4575 // 1. If the previous state of the element's type attribute put the value
4576 // IDL attribute in the value mode, and the element's value is not the
4577 // empty string, and the new state of the element's type attribute puts
4578 // the value IDL attribute in either the default mode or the default/on
4579 // mode, then set the element's value content attribute to the
4581 if (oldValueMode
== VALUE_MODE_VALUE
&& !oldValue
.IsEmpty()) {
4582 SetAttr(kNameSpaceID_None
, nsGkAtoms::value
, oldValue
, true);
4585 case VALUE_MODE_VALUE
: {
4586 ValueSetterOptions options
{ValueSetterOption::ByInternalAPI
};
4587 if (!SetRangeTextApplies(oldType
) && SetRangeTextApplies(mType
)) {
4589 ValueSetterOption::MoveCursorToBeginSetSelectionDirectionForward
;
4591 if (oldValueMode
!= VALUE_MODE_VALUE
) {
4592 // 2. Otherwise, if the previous state of the element's type attribute
4593 // put the value IDL attribute in any mode other than the value
4594 // mode, and the new state of the element's type attribute puts the
4595 // value IDL attribute in the value mode, then set the value of the
4596 // element to the value of the value content attribute, if there is
4597 // one, or the empty string otherwise, and then set the control's
4598 // dirty value flag to false.
4600 GetAttr(nsGkAtoms::value
, value
);
4601 SetValueInternal(value
, options
);
4602 SetValueChanged(false);
4603 } else if (mValueChanged
) {
4604 // We're both in the "value" mode state, we need to make no change per
4605 // spec, but due to how we store the value internally we need to call
4606 // SetValueInternal, if our value had changed at all.
4607 // TODO: What should we do if SetValueInternal fails? (The allocation
4608 // may potentially be big, but most likely we've failed to allocate
4609 // before the type change.)
4610 SetValueInternal(oldValue
, options
);
4612 // The value dirty flag is not set, so our value is based on our default
4613 // value. But our default value might be dependent on the type. Make
4614 // sure to set it so that state is consistent.
4615 SetDefaultValueAsValue();
4619 case VALUE_MODE_FILENAME
:
4621 // 3. Otherwise, if the previous state of the element's type attribute
4622 // put the value IDL attribute in any mode other than the filename
4623 // mode, and the new state of the element's type attribute puts the
4624 // value IDL attribute in the filename mode, then set the value of the
4625 // element to the empty string.
4627 // Setting the attribute to the empty string is basically calling
4628 // ClearFiles, but there can't be any files.
4632 // Updating mFocusedValue in consequence:
4633 // If the new type fires a change event on blur, but the previous type
4634 // doesn't, we should set mFocusedValue to the current value.
4635 // Otherwise, if the new type doesn't fire a change event on blur, but the
4636 // previous type does, we should clear out mFocusedValue.
4637 if (MayFireChangeOnBlur(mType
) && !MayFireChangeOnBlur(oldType
)) {
4638 GetValue(mFocusedValue
, CallerType::System
);
4639 } else if (!IsSingleLineTextControl(false, mType
) &&
4640 IsSingleLineTextControl(false, oldType
)) {
4641 mFocusedValue
.Truncate();
4644 // Update or clear our required states since we may have changed from a
4645 // required input type to a non-required input type or viceversa.
4646 if (DoesRequiredApply()) {
4647 const bool isRequired
= HasAttr(nsGkAtoms::required
);
4648 UpdateRequiredState(isRequired
, aNotify
);
4650 RemoveStates(ElementState::REQUIRED_STATES
, aNotify
);
4653 UpdateHasRange(aNotify
);
4655 // Update validity states, but not element state. We'll update
4656 // element state later, as part of this attribute change.
4657 UpdateAllValidityStatesButNotElementState();
4659 UpdateApzAwareFlag();
4661 UpdateBarredFromConstraintValidation();
4663 // Changing type may affect auto directionality, or non-auto directionality
4664 // because of the special-case for <input type=tel>, as specified in
4665 // https://html.spec.whatwg.org/multipage/dom.html#the-directionality
4667 const bool autoDirAssociated
= IsAutoDirectionalityAssociated(mType
);
4668 if (IsAutoDirectionalityAssociated(oldType
) != autoDirAssociated
) {
4669 SetAutoDirectionality(aNotify
);
4671 } else if (oldType
== FormControlType::InputTel
||
4672 mType
== FormControlType::InputTel
) {
4673 RecomputeDirectionality(this, aNotify
);
4676 if (oldType
== FormControlType::InputImage
||
4677 mType
== FormControlType::InputImage
) {
4678 if (oldType
== FormControlType::InputImage
) {
4679 // We're no longer an image input. Cancel our image requests, if we have
4681 CancelImageRequests(aNotify
);
4682 RemoveStates(ElementState::BROKEN
, aNotify
);
4684 // We just got switched to be an image input; we should see whether we
4685 // have an image to load;
4686 bool hasSrc
= false;
4689 if ((hasSrc
= GetAttr(nsGkAtoms::src
, src
))) {
4690 // Mark channel as urgent-start before load image if the image load is
4691 // initiated by a user interaction.
4692 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
4694 LoadImage(src
, false, aNotify
, eImageLoadType_Normal
,
4695 mSrcTriggeringPrincipal
);
4698 hasSrc
= HasAttr(nsGkAtoms::src
);
4701 AddStates(ElementState::BROKEN
, aNotify
);
4704 // We should update our mapped attribute mapping function.
4705 if (mAttrs
.HasAttrs() && !mAttrs
.IsPendingMappedAttributeEvaluation()) {
4706 mAttrs
.InfallibleMarkAsPendingPresAttributeEvaluation();
4707 if (auto* doc
= GetComposedDoc()) {
4708 doc
->ScheduleForPresAttrEvaluation(this);
4713 MaybeDispatchLoginManagerEvents(mForm
);
4715 if (IsInComposedDoc()) {
4716 if (CreatesDateTimeWidget(oldType
)) {
4717 if (!CreatesDateTimeWidget()) {
4718 // Switch away from date/time type.
4719 NotifyUAWidgetTeardown();
4721 // Switch between date and time.
4722 NotifyUAWidgetSetupOrChange();
4724 } else if (CreatesDateTimeWidget()) {
4725 // Switch to date/time type.
4726 AttachAndSetUAShadowRoot(NotifyUAWidgetSetup::Yes
, DelegatesFocus::Yes
);
4728 // If we're becoming a text control and have focus, make sure to show focus
4730 if (State().HasState(ElementState::FOCUS
) && IsSingleLineTextControl() &&
4731 !IsSingleLineTextControl(/* aExcludePassword = */ false, oldType
)) {
4732 AddStates(ElementState::FOCUSRING
);
4737 void HTMLInputElement::MaybeSnapToTickMark(Decimal
& aValue
) {
4738 nsRangeFrame
* rangeFrame
= do_QueryFrame(GetPrimaryFrame());
4742 auto tickMark
= rangeFrame
->NearestTickMark(aValue
);
4743 if (tickMark
.isNaN()) {
4746 auto rangeFrameSize
= CSSPixel::FromAppUnits(rangeFrame
->GetSize());
4747 CSSCoord rangeTrackLength
;
4748 if (rangeFrame
->IsHorizontal()) {
4749 rangeTrackLength
= rangeFrameSize
.width
;
4751 rangeTrackLength
= rangeFrameSize
.height
;
4753 auto stepBase
= GetStepBase();
4754 auto distanceToTickMark
=
4755 rangeTrackLength
* float(rangeFrame
->GetDoubleAsFractionOfRange(
4756 stepBase
+ (tickMark
- aValue
).abs()));
4757 const CSSCoord
magnetEffectRange(
4758 StaticPrefs::dom_range_element_magnet_effect_threshold());
4759 if (distanceToTickMark
<= magnetEffectRange
) {
4764 void HTMLInputElement::SanitizeValue(nsAString
& aValue
,
4765 SanitizationKind aKind
) const {
4766 NS_ASSERTION(mDoneCreating
, "The element creation should be finished!");
4769 case FormControlType::InputText
:
4770 case FormControlType::InputSearch
:
4771 case FormControlType::InputTel
:
4772 case FormControlType::InputPassword
: {
4775 case FormControlType::InputEmail
: {
4777 aValue
= nsContentUtils::TrimWhitespace
<nsContentUtils::IsHTMLWhitespace
>(
4780 if (Multiple() && !aValue
.IsEmpty()) {
4781 nsAutoString
oldValue(aValue
);
4782 HTMLSplitOnSpacesTokenizer
tokenizer(oldValue
, ',');
4784 aValue
.Append(tokenizer
.nextToken());
4785 while (tokenizer
.hasMoreTokens() ||
4786 tokenizer
.separatorAfterCurrentToken()) {
4788 aValue
.Append(tokenizer
.nextToken());
4792 case FormControlType::InputUrl
: {
4795 aValue
= nsContentUtils::TrimWhitespace
<nsContentUtils::IsHTMLWhitespace
>(
4798 case FormControlType::InputNumber
: {
4799 if (aKind
== SanitizationKind::ForValueSetter
&& !aValue
.IsEmpty() &&
4800 (aValue
.First() == '+' || aValue
.Last() == '.')) {
4801 // A value with a leading plus or trailing dot should fail to parse.
4802 // However, the localized parser accepts this, and when we convert it
4803 // back to a Decimal, it disappears. So, we need to check first.
4805 // FIXME(emilio): Should we just use the unlocalized parser
4806 // (StringToDecimal) for the value setter? Other browsers don't seem to
4807 // allow setting localized strings there, and that way we don't need
4808 // this special-case.
4813 InputType::StringToNumberResult result
=
4814 mInputType
->ConvertStringToNumber(aValue
);
4815 if (!result
.mResult
.isFinite()) {
4820 case SanitizationKind::ForValueGetter
: {
4821 // If the default non-localized algorithm parses the value, then we're
4822 // done, don't un-localize it, to avoid precision loss, and to
4823 // preserve scientific notation as well for example.
4824 if (!result
.mLocalized
) {
4827 // For the <input type=number> value getter, we return the unlocalized
4828 // value if it doesn't parse as StringToDecimal, for compat with other
4831 DebugOnly
<bool> ok
= result
.mResult
.toString(buf
, ArrayLength(buf
));
4832 aValue
.AssignASCII(buf
);
4833 MOZ_ASSERT(ok
, "buf not big enough");
4836 case SanitizationKind::ForDisplay
:
4837 case SanitizationKind::ForValueSetter
: {
4838 // We localize as needed, but if both the localized and unlocalized
4839 // version parse with the generic parser, we just use the unlocalized
4840 // one, to preserve the input as much as possible.
4842 // FIXME(emilio, bug 1622808): Localization should ideally be more
4843 // input-preserving.
4844 nsString localizedValue
;
4845 mInputType
->ConvertNumberToString(result
.mResult
, localizedValue
);
4846 if (!StringToDecimal(localizedValue
).isFinite()) {
4847 aValue
= std::move(localizedValue
);
4854 case FormControlType::InputRange
: {
4855 Decimal minimum
= GetMinimum();
4856 Decimal maximum
= GetMaximum();
4857 MOZ_ASSERT(minimum
.isFinite() && maximum
.isFinite(),
4858 "type=range should have a default maximum/minimum");
4860 // We use this to avoid modifying the string unnecessarily, since that
4861 // may introduce rounding. This is set to true only if the value we
4862 // parse out from aValue needs to be sanitized.
4863 bool needSanitization
= false;
4865 Decimal value
= mInputType
->ConvertStringToNumber(aValue
).mResult
;
4866 if (!value
.isFinite()) {
4867 needSanitization
= true;
4868 // Set value to midway between minimum and maximum.
4869 value
= maximum
<= minimum
? minimum
4870 : minimum
+ (maximum
- minimum
) / Decimal(2);
4871 } else if (value
< minimum
|| maximum
< minimum
) {
4872 needSanitization
= true;
4874 } else if (value
> maximum
) {
4875 needSanitization
= true;
4879 Decimal step
= GetStep();
4880 if (step
!= kStepAny
) {
4881 Decimal stepBase
= GetStepBase();
4882 // There could be rounding issues below when dealing with fractional
4883 // numbers, but let's ignore that until ECMAScript supplies us with a
4884 // decimal number type.
4885 Decimal deltaToStep
= NS_floorModulo(value
- stepBase
, step
);
4886 if (deltaToStep
!= Decimal(0)) {
4887 // "suffering from a step mismatch"
4888 // Round the element's value to the nearest number for which the
4889 // element would not suffer from a step mismatch, and which is
4890 // greater than or equal to the minimum, and, if the maximum is not
4891 // less than the minimum, which is less than or equal to the
4892 // maximum, if there is a number that matches these constraints:
4893 MOZ_ASSERT(deltaToStep
> Decimal(0),
4894 "stepBelow/stepAbove will be wrong");
4895 Decimal stepBelow
= value
- deltaToStep
;
4896 Decimal stepAbove
= value
- deltaToStep
+ step
;
4897 Decimal halfStep
= step
/ Decimal(2);
4898 bool stepAboveIsClosest
= (stepAbove
- value
) <= halfStep
;
4899 bool stepAboveInRange
= stepAbove
>= minimum
&& stepAbove
<= maximum
;
4900 bool stepBelowInRange
= stepBelow
>= minimum
&& stepBelow
<= maximum
;
4902 if ((stepAboveIsClosest
|| !stepBelowInRange
) && stepAboveInRange
) {
4903 needSanitization
= true;
4905 } else if ((!stepAboveIsClosest
|| !stepAboveInRange
) &&
4907 needSanitization
= true;
4913 if (needSanitization
) {
4915 DebugOnly
<bool> ok
= value
.toString(buf
, ArrayLength(buf
));
4916 aValue
.AssignASCII(buf
);
4917 MOZ_ASSERT(ok
, "buf not big enough");
4920 case FormControlType::InputDate
: {
4921 if (!aValue
.IsEmpty() && !IsValidDate(aValue
)) {
4925 case FormControlType::InputTime
: {
4926 if (!aValue
.IsEmpty() && !IsValidTime(aValue
)) {
4930 case FormControlType::InputMonth
: {
4931 if (!aValue
.IsEmpty() && !IsValidMonth(aValue
)) {
4935 case FormControlType::InputWeek
: {
4936 if (!aValue
.IsEmpty() && !IsValidWeek(aValue
)) {
4940 case FormControlType::InputDatetimeLocal
: {
4941 if (!aValue
.IsEmpty() && !IsValidDateTimeLocal(aValue
)) {
4944 NormalizeDateTimeLocal(aValue
);
4947 case FormControlType::InputColor
: {
4948 if (IsValidSimpleColor(aValue
)) {
4949 ToLowerCase(aValue
);
4951 // Set default (black) color, if aValue wasn't parsed correctly.
4952 aValue
.AssignLiteral("#000000");
4960 Maybe
<nscolor
> HTMLInputElement::ParseSimpleColor(const nsAString
& aColor
) {
4961 // Input color string should be 7 length (i.e. a string representing a valid
4963 if (aColor
.Length() != 7 || aColor
.First() != '#') {
4967 const nsAString
& withoutHash
= StringTail(aColor
, 6);
4969 if (!NS_HexToRGBA(withoutHash
, nsHexColorType::NoAlpha
, &color
)) {
4976 bool HTMLInputElement::IsValidSimpleColor(const nsAString
& aValue
) const {
4977 if (aValue
.Length() != 7 || aValue
.First() != '#') {
4981 for (int i
= 1; i
< 7; ++i
) {
4982 if (!IsAsciiDigit(aValue
[i
]) && !(aValue
[i
] >= 'a' && aValue
[i
] <= 'f') &&
4983 !(aValue
[i
] >= 'A' && aValue
[i
] <= 'F')) {
4990 bool HTMLInputElement::IsLeapYear(uint32_t aYear
) const {
4991 if ((aYear
% 4 == 0 && aYear
% 100 != 0) || (aYear
% 400 == 0)) {
4997 uint32_t HTMLInputElement::DayOfWeek(uint32_t aYear
, uint32_t aMonth
,
4998 uint32_t aDay
, bool isoWeek
) const {
4999 MOZ_ASSERT(1 <= aMonth
&& aMonth
<= 12, "month is in 1..12");
5000 MOZ_ASSERT(1 <= aDay
&& aDay
<= 31, "day is in 1..31");
5002 // Tomohiko Sakamoto algorithm.
5003 int monthTable
[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
5004 aYear
-= aMonth
< 3;
5006 uint32_t day
= (aYear
+ aYear
/ 4 - aYear
/ 100 + aYear
/ 400 +
5007 monthTable
[aMonth
- 1] + aDay
) %
5011 return ((day
+ 6) % 7) + 1;
5017 uint32_t HTMLInputElement::MaximumWeekInYear(uint32_t aYear
) const {
5018 int day
= DayOfWeek(aYear
, 1, 1, true); // January 1.
5019 // A year starting on Thursday or a leap year starting on Wednesday has 53
5020 // weeks. All other years have 52 weeks.
5021 return day
== 4 || (day
== 3 && IsLeapYear(aYear
)) ? kMaximumWeekInYear
5022 : kMaximumWeekInYear
- 1;
5025 bool HTMLInputElement::IsValidWeek(const nsAString
& aValue
) const {
5026 uint32_t year
, week
;
5027 return ParseWeek(aValue
, &year
, &week
);
5030 bool HTMLInputElement::IsValidMonth(const nsAString
& aValue
) const {
5031 uint32_t year
, month
;
5032 return ParseMonth(aValue
, &year
, &month
);
5035 bool HTMLInputElement::IsValidDate(const nsAString
& aValue
) const {
5036 uint32_t year
, month
, day
;
5037 return ParseDate(aValue
, &year
, &month
, &day
);
5040 bool HTMLInputElement::IsValidDateTimeLocal(const nsAString
& aValue
) const {
5041 uint32_t year
, month
, day
, time
;
5042 return ParseDateTimeLocal(aValue
, &year
, &month
, &day
, &time
);
5045 bool HTMLInputElement::ParseYear(const nsAString
& aValue
,
5046 uint32_t* aYear
) const {
5047 if (aValue
.Length() < 4) {
5051 return DigitSubStringToNumber(aValue
, 0, aValue
.Length(), aYear
) &&
5055 bool HTMLInputElement::ParseMonth(const nsAString
& aValue
, uint32_t* aYear
,
5056 uint32_t* aMonth
) const {
5057 // Parse the year, month values out a string formatted as 'yyyy-mm'.
5058 if (aValue
.Length() < 7) {
5062 uint32_t endOfYearOffset
= aValue
.Length() - 3;
5063 if (aValue
[endOfYearOffset
] != '-') {
5067 const nsAString
& yearStr
= Substring(aValue
, 0, endOfYearOffset
);
5068 if (!ParseYear(yearStr
, aYear
)) {
5072 return DigitSubStringToNumber(aValue
, endOfYearOffset
+ 1, 2, aMonth
) &&
5073 *aMonth
> 0 && *aMonth
<= 12;
5076 bool HTMLInputElement::ParseWeek(const nsAString
& aValue
, uint32_t* aYear
,
5077 uint32_t* aWeek
) const {
5078 // Parse the year, month values out a string formatted as 'yyyy-Www'.
5079 if (aValue
.Length() < 8) {
5083 uint32_t endOfYearOffset
= aValue
.Length() - 4;
5084 if (aValue
[endOfYearOffset
] != '-') {
5088 if (aValue
[endOfYearOffset
+ 1] != 'W') {
5092 const nsAString
& yearStr
= Substring(aValue
, 0, endOfYearOffset
);
5093 if (!ParseYear(yearStr
, aYear
)) {
5097 return DigitSubStringToNumber(aValue
, endOfYearOffset
+ 2, 2, aWeek
) &&
5098 *aWeek
> 0 && *aWeek
<= MaximumWeekInYear(*aYear
);
5101 bool HTMLInputElement::ParseDate(const nsAString
& aValue
, uint32_t* aYear
,
5102 uint32_t* aMonth
, uint32_t* aDay
) const {
5104 * Parse the year, month, day values out a date string formatted as
5105 * yyyy-mm-dd. -The year must be 4 or more digits long, and year > 0 -The
5106 * month must be exactly 2 digits long, and 01 <= month <= 12 -The day must be
5107 * exactly 2 digit long, and 01 <= day <= maxday Where maxday is the number of
5108 * days in the month 'month' and year 'year'
5110 if (aValue
.Length() < 10) {
5114 uint32_t endOfMonthOffset
= aValue
.Length() - 3;
5115 if (aValue
[endOfMonthOffset
] != '-') {
5119 const nsAString
& yearMonthStr
= Substring(aValue
, 0, endOfMonthOffset
);
5120 if (!ParseMonth(yearMonthStr
, aYear
, aMonth
)) {
5124 return DigitSubStringToNumber(aValue
, endOfMonthOffset
+ 1, 2, aDay
) &&
5125 *aDay
> 0 && *aDay
<= NumberOfDaysInMonth(*aMonth
, *aYear
);
5128 bool HTMLInputElement::ParseDateTimeLocal(const nsAString
& aValue
,
5129 uint32_t* aYear
, uint32_t* aMonth
,
5131 uint32_t* aTime
) const {
5132 // Parse the year, month, day and time values out a string formatted as
5133 // 'yyyy-mm-ddThh:mm[:ss.s] or 'yyyy-mm-dd hh:mm[:ss.s]', where fractions of
5134 // seconds can be 1 to 3 digits.
5135 // The minimum length allowed is 16, which is of the form 'yyyy-mm-ddThh:mm'
5136 // or 'yyyy-mm-dd hh:mm'.
5137 if (aValue
.Length() < 16) {
5141 int32_t sepIndex
= aValue
.FindChar('T');
5142 if (sepIndex
== -1) {
5143 sepIndex
= aValue
.FindChar(' ');
5145 if (sepIndex
== -1) {
5150 const nsAString
& dateStr
= Substring(aValue
, 0, sepIndex
);
5151 if (!ParseDate(dateStr
, aYear
, aMonth
, aDay
)) {
5155 const nsAString
& timeStr
=
5156 Substring(aValue
, sepIndex
+ 1, aValue
.Length() - sepIndex
+ 1);
5157 if (!ParseTime(timeStr
, aTime
)) {
5164 void HTMLInputElement::NormalizeDateTimeLocal(nsAString
& aValue
) const {
5165 if (aValue
.IsEmpty()) {
5169 // Use 'T' as the separator between date string and time string.
5170 int32_t sepIndex
= aValue
.FindChar(' ');
5171 if (sepIndex
!= -1) {
5172 aValue
.ReplaceLiteral(sepIndex
, 1, u
"T");
5174 sepIndex
= aValue
.FindChar('T');
5177 // Time expressed as the shortest possible string, which is hh:mm.
5178 if ((aValue
.Length() - sepIndex
) == 6) {
5182 // Fractions of seconds part is optional, ommit it if it's 0.
5183 if ((aValue
.Length() - sepIndex
) > 9) {
5184 const uint32_t millisecSepIndex
= sepIndex
+ 9;
5185 uint32_t milliseconds
;
5186 if (!DigitSubStringToNumber(aValue
, millisecSepIndex
+ 1,
5187 aValue
.Length() - (millisecSepIndex
+ 1),
5192 if (milliseconds
!= 0) {
5196 aValue
.Cut(millisecSepIndex
, aValue
.Length() - millisecSepIndex
);
5199 // Seconds part is optional, ommit it if it's 0.
5200 const uint32_t secondSepIndex
= sepIndex
+ 6;
5202 if (!DigitSubStringToNumber(aValue
, secondSepIndex
+ 1,
5203 aValue
.Length() - (secondSepIndex
+ 1),
5212 aValue
.Cut(secondSepIndex
, aValue
.Length() - secondSepIndex
);
5215 double HTMLInputElement::DaysSinceEpochFromWeek(uint32_t aYear
,
5216 uint32_t aWeek
) const {
5217 double days
= JS::DayFromYear(aYear
) + (aWeek
- 1) * 7;
5218 uint32_t dayOneIsoWeekday
= DayOfWeek(aYear
, 1, 1, true);
5220 // If day one of that year is on/before Thursday, we should subtract the
5221 // days that belong to last year in our first week, otherwise, our first
5222 // days belong to last year's last week, and we should add those days
5224 if (dayOneIsoWeekday
<= 4) {
5225 days
-= (dayOneIsoWeekday
- 1);
5227 days
+= (7 - dayOneIsoWeekday
+ 1);
5233 uint32_t HTMLInputElement::NumberOfDaysInMonth(uint32_t aMonth
,
5234 uint32_t aYear
) const {
5236 * Returns the number of days in a month.
5237 * Months that are |longMonths| always have 31 days.
5238 * Months that are not |longMonths| have 30 days except February (month 2).
5239 * February has 29 days during leap years which are years that are divisible
5240 * by 400. or divisible by 100 and 4. February has 28 days otherwise.
5243 static const bool longMonths
[] = {true, false, true, false, true, false,
5244 true, true, false, true, false, true};
5245 MOZ_ASSERT(aMonth
<= 12 && aMonth
> 0);
5247 if (longMonths
[aMonth
- 1]) {
5255 return IsLeapYear(aYear
) ? 29 : 28;
5259 bool HTMLInputElement::DigitSubStringToNumber(const nsAString
& aStr
,
5260 uint32_t aStart
, uint32_t aLen
,
5261 uint32_t* aRetVal
) {
5262 MOZ_ASSERT(aStr
.Length() > (aStart
+ aLen
- 1));
5264 for (uint32_t offset
= 0; offset
< aLen
; ++offset
) {
5265 if (!IsAsciiDigit(aStr
[aStart
+ offset
])) {
5271 *aRetVal
= static_cast<uint32_t>(
5272 PromiseFlatString(Substring(aStr
, aStart
, aLen
)).ToInteger(&ec
));
5274 return NS_SUCCEEDED(ec
);
5277 bool HTMLInputElement::IsValidTime(const nsAString
& aValue
) const {
5278 return ParseTime(aValue
, nullptr);
5282 bool HTMLInputElement::ParseTime(const nsAString
& aValue
, uint32_t* aResult
) {
5283 /* The string must have the following parts:
5284 * - HOURS: two digits, value being in [0, 23];
5286 * - MINUTES: two digits, value being in [0, 59];
5289 * - SECONDS: two digits, value being in [0, 59];
5292 * - FRACTIONAL SECONDS: one to three digits, no value range.
5295 // The following format is the shorter one allowed: "HH:MM".
5296 if (aValue
.Length() < 5) {
5301 if (!DigitSubStringToNumber(aValue
, 0, 2, &hours
) || hours
> 23) {
5305 // Hours/minutes separator.
5306 if (aValue
[2] != ':') {
5311 if (!DigitSubStringToNumber(aValue
, 3, 2, &minutes
) || minutes
> 59) {
5315 if (aValue
.Length() == 5) {
5317 *aResult
= ((hours
* 60) + minutes
) * 60000;
5322 // The following format is the next shorter one: "HH:MM:SS".
5323 if (aValue
.Length() < 8 || aValue
[5] != ':') {
5328 if (!DigitSubStringToNumber(aValue
, 6, 2, &seconds
) || seconds
> 59) {
5332 if (aValue
.Length() == 8) {
5334 *aResult
= (((hours
* 60) + minutes
) * 60 + seconds
) * 1000;
5339 // The string must follow this format now: "HH:MM:SS.{s,ss,sss}".
5340 // There can be 1 to 3 digits for the fractions of seconds.
5341 if (aValue
.Length() == 9 || aValue
.Length() > 12 || aValue
[8] != '.') {
5345 uint32_t fractionsSeconds
;
5346 if (!DigitSubStringToNumber(aValue
, 9, aValue
.Length() - 9,
5347 &fractionsSeconds
)) {
5352 *aResult
= (((hours
* 60) + minutes
) * 60 + seconds
) * 1000 +
5353 // NOTE: there is 10.0 instead of 10 and static_cast<int> because
5354 // some old [and stupid] compilers can't just do the right thing.
5356 pow(10.0, static_cast<int>(3 - (aValue
.Length() - 9)));
5363 bool HTMLInputElement::IsDateTimeTypeSupported(
5364 FormControlType aDateTimeInputType
) {
5365 switch (aDateTimeInputType
) {
5366 case FormControlType::InputDate
:
5367 case FormControlType::InputTime
:
5368 case FormControlType::InputDatetimeLocal
:
5370 case FormControlType::InputMonth
:
5371 case FormControlType::InputWeek
:
5372 return StaticPrefs::dom_forms_datetime_others();
5378 void HTMLInputElement::GetLastInteractiveValue(nsAString
& aValue
) {
5379 if (mLastValueChangeWasInteractive
) {
5380 return GetValue(aValue
, CallerType::System
);
5382 if (TextControlState
* state
= GetEditorState()) {
5383 return aValue
.Assign(
5384 state
->LastInteractiveValueIfLastChangeWasNonInteractive());
5389 bool HTMLInputElement::ParseAttribute(int32_t aNamespaceID
, nsAtom
* aAttribute
,
5390 const nsAString
& aValue
,
5391 nsIPrincipal
* aMaybeScriptedPrincipal
,
5392 nsAttrValue
& aResult
) {
5393 // We can't make these static_asserts because kInputDefaultType and
5394 // kInputTypeTable aren't constexpr.
5396 FormControlType(kInputDefaultType
->value
) == FormControlType::InputText
,
5397 "Someone forgot to update kInputDefaultType when adding a new "
5399 MOZ_ASSERT(kInputTypeTable
[ArrayLength(kInputTypeTable
) - 1].tag
== nullptr,
5400 "Last entry in the table must be the nullptr guard");
5401 MOZ_ASSERT(FormControlType(
5402 kInputTypeTable
[ArrayLength(kInputTypeTable
) - 2].value
) ==
5403 FormControlType::InputText
,
5404 "Next to last entry in the table must be the \"text\" entry");
5406 if (aNamespaceID
== kNameSpaceID_None
) {
5407 if (aAttribute
== nsGkAtoms::type
) {
5408 aResult
.ParseEnumValue(aValue
, kInputTypeTable
, false, kInputDefaultType
);
5409 auto newType
= FormControlType(aResult
.GetEnumValue());
5410 if (IsDateTimeInputType(newType
) && !IsDateTimeTypeSupported(newType
)) {
5411 // There's no public way to set an nsAttrValue to an enum value, but we
5412 // can just re-parse with a table that doesn't have any types other than
5414 aResult
.ParseEnumValue(aValue
, kInputDefaultType
, false,
5420 if (aAttribute
== nsGkAtoms::width
) {
5421 return aResult
.ParseHTMLDimension(aValue
);
5423 if (aAttribute
== nsGkAtoms::height
) {
5424 return aResult
.ParseHTMLDimension(aValue
);
5426 if (aAttribute
== nsGkAtoms::maxlength
) {
5427 return aResult
.ParseNonNegativeIntValue(aValue
);
5429 if (aAttribute
== nsGkAtoms::minlength
) {
5430 return aResult
.ParseNonNegativeIntValue(aValue
);
5432 if (aAttribute
== nsGkAtoms::size
) {
5433 return aResult
.ParsePositiveIntValue(aValue
);
5435 if (aAttribute
== nsGkAtoms::align
) {
5436 return ParseAlignValue(aValue
, aResult
);
5438 if (aAttribute
== nsGkAtoms::formmethod
) {
5439 return aResult
.ParseEnumValue(aValue
, kFormMethodTable
, false);
5441 if (aAttribute
== nsGkAtoms::formenctype
) {
5442 return aResult
.ParseEnumValue(aValue
, kFormEnctypeTable
, false);
5444 if (aAttribute
== nsGkAtoms::autocomplete
) {
5445 aResult
.ParseAtomArray(aValue
);
5448 if (aAttribute
== nsGkAtoms::capture
) {
5449 return aResult
.ParseEnumValue(aValue
, kCaptureTable
, false,
5452 if (ParseImageAttribute(aAttribute
, aValue
, aResult
)) {
5453 // We have to call |ParseImageAttribute| unconditionally since we
5454 // don't know if we're going to have a type="image" attribute yet,
5455 // (or could have it set dynamically in the future). See bug
5461 return TextControlElement::ParseAttribute(aNamespaceID
, aAttribute
, aValue
,
5462 aMaybeScriptedPrincipal
, aResult
);
5465 void HTMLInputElement::ImageInputMapAttributesIntoRule(
5466 MappedDeclarationsBuilder
& aBuilder
) {
5467 nsGenericHTMLFormControlElementWithState::MapImageBorderAttributeInto(
5469 nsGenericHTMLFormControlElementWithState::MapImageMarginAttributeInto(
5471 nsGenericHTMLFormControlElementWithState::MapImageSizeAttributesInto(
5472 aBuilder
, MapAspectRatio::Yes
);
5473 // Images treat align as "float"
5474 nsGenericHTMLFormControlElementWithState::MapImageAlignAttributeInto(
5476 nsGenericHTMLFormControlElementWithState::MapCommonAttributesInto(aBuilder
);
5479 nsChangeHint
HTMLInputElement::GetAttributeChangeHint(const nsAtom
* aAttribute
,
5480 int32_t aModType
) const {
5481 nsChangeHint retval
=
5482 nsGenericHTMLFormControlElementWithState::GetAttributeChangeHint(
5483 aAttribute
, aModType
);
5485 const bool isAdditionOrRemoval
=
5486 aModType
== MutationEvent_Binding::ADDITION
||
5487 aModType
== MutationEvent_Binding::REMOVAL
;
5489 const bool reconstruct
= [&] {
5490 if (aAttribute
== nsGkAtoms::type
) {
5494 if (PlaceholderApplies() && aAttribute
== nsGkAtoms::placeholder
&&
5495 isAdditionOrRemoval
) {
5496 // We need to re-create our placeholder text.
5500 if (mType
== FormControlType::InputFile
&&
5501 aAttribute
== nsGkAtoms::webkitdirectory
) {
5502 // The presence or absence of the 'directory' attribute determines what
5503 // value we show in the file label when empty, via GetDisplayFileName.
5507 if (mType
== FormControlType::InputImage
&& isAdditionOrRemoval
&&
5508 (aAttribute
== nsGkAtoms::alt
|| aAttribute
== nsGkAtoms::value
)) {
5509 // We might need to rebuild our alt text. Just go ahead and
5510 // reconstruct our frame. This should be quite rare..
5517 retval
|= nsChangeHint_ReconstructFrame
;
5518 } else if (aAttribute
== nsGkAtoms::value
) {
5519 retval
|= NS_STYLE_HINT_REFLOW
;
5520 } else if (aAttribute
== nsGkAtoms::size
&& IsSingleLineTextControl(false)) {
5521 retval
|= NS_STYLE_HINT_REFLOW
;
5527 NS_IMETHODIMP_(bool)
5528 HTMLInputElement::IsAttributeMapped(const nsAtom
* aAttribute
) const {
5529 static const MappedAttributeEntry attributes
[] = {
5534 static const MappedAttributeEntry
* const map
[] = {
5536 sCommonAttributeMap
,
5537 sImageMarginSizeAttributeMap
,
5538 sImageBorderAttributeMap
,
5541 return FindAttributeDependence(aAttribute
, map
);
5544 nsMapRuleToAttributesFunc
HTMLInputElement::GetAttributeMappingFunction()
5546 // GetAttributeChangeHint guarantees that changes to mType will trigger a
5547 // reframe, and we update the mapping function in our mapped attrs when our
5548 // type changes, so it's safe to condition our attribute mapping function on
5550 if (mType
== FormControlType::InputImage
) {
5551 return &ImageInputMapAttributesIntoRule
;
5554 return &MapCommonAttributesInto
;
5557 // Directory picking methods:
5559 already_AddRefed
<Promise
> HTMLInputElement::GetFilesAndDirectories(
5561 if (mType
!= FormControlType::InputFile
) {
5562 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5566 nsCOMPtr
<nsIGlobalObject
> global
= OwnerDoc()->GetScopeObject();
5572 RefPtr
<Promise
> p
= Promise::Create(global
, aRv
);
5577 const nsTArray
<OwningFileOrDirectory
>& filesAndDirs
=
5578 GetFilesOrDirectoriesInternal();
5580 Sequence
<OwningFileOrDirectory
> filesAndDirsSeq
;
5582 if (!filesAndDirsSeq
.SetLength(filesAndDirs
.Length(), fallible
)) {
5583 p
->MaybeReject(NS_ERROR_OUT_OF_MEMORY
);
5587 for (uint32_t i
= 0; i
< filesAndDirs
.Length(); ++i
) {
5588 if (filesAndDirs
[i
].IsDirectory()) {
5589 RefPtr
<Directory
> directory
= filesAndDirs
[i
].GetAsDirectory();
5591 // In future we could refactor SetFilePickerFiltersFromAccept to return a
5592 // semicolon separated list of file extensions and include that in the
5593 // filter string passed here.
5594 directory
->SetContentFilters(u
"filter-out-sensitive"_ns
);
5595 filesAndDirsSeq
[i
].SetAsDirectory() = directory
;
5597 MOZ_ASSERT(filesAndDirs
[i
].IsFile());
5599 // This file was directly selected by the user, so don't filter it.
5600 filesAndDirsSeq
[i
].SetAsFile() = filesAndDirs
[i
].GetAsFile();
5604 p
->MaybeResolve(filesAndDirsSeq
);
5608 // Controllers Methods
5610 nsIControllers
* HTMLInputElement::GetControllers(ErrorResult
& aRv
) {
5611 // XXX: what about type "file"?
5612 if (IsSingleLineTextControl(false)) {
5613 if (!mControllers
) {
5614 mControllers
= new nsXULControllers();
5615 if (!mControllers
) {
5616 aRv
.Throw(NS_ERROR_FAILURE
);
5620 RefPtr
<nsBaseCommandController
> commandController
=
5621 nsBaseCommandController::CreateEditorController();
5622 if (!commandController
) {
5623 aRv
.Throw(NS_ERROR_FAILURE
);
5627 mControllers
->AppendController(commandController
);
5629 commandController
= nsBaseCommandController::CreateEditingController();
5630 if (!commandController
) {
5631 aRv
.Throw(NS_ERROR_FAILURE
);
5635 mControllers
->AppendController(commandController
);
5639 return mControllers
;
5642 nsresult
HTMLInputElement::GetControllers(nsIControllers
** aResult
) {
5643 NS_ENSURE_ARG_POINTER(aResult
);
5646 RefPtr
<nsIControllers
> controller
= GetControllers(rv
);
5647 controller
.forget(aResult
);
5648 return rv
.StealNSResult();
5651 int32_t HTMLInputElement::InputTextLength(CallerType aCallerType
) {
5653 GetValue(val
, aCallerType
);
5654 return val
.Length();
5657 void HTMLInputElement::SetSelectionRange(uint32_t aSelectionStart
,
5658 uint32_t aSelectionEnd
,
5659 const Optional
<nsAString
>& aDirection
,
5661 if (!SupportsTextSelection()) {
5662 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5666 TextControlState
* state
= GetEditorState();
5667 MOZ_ASSERT(state
, "SupportsTextSelection() returned true!");
5668 state
->SetSelectionRange(aSelectionStart
, aSelectionEnd
, aDirection
, aRv
);
5671 void HTMLInputElement::SetRangeText(const nsAString
& aReplacement
,
5673 if (!SupportsTextSelection()) {
5674 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5678 TextControlState
* state
= GetEditorState();
5679 MOZ_ASSERT(state
, "SupportsTextSelection() returned true!");
5680 state
->SetRangeText(aReplacement
, aRv
);
5683 void HTMLInputElement::SetRangeText(const nsAString
& aReplacement
,
5684 uint32_t aStart
, uint32_t aEnd
,
5685 SelectionMode aSelectMode
,
5687 if (!SupportsTextSelection()) {
5688 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5692 TextControlState
* state
= GetEditorState();
5693 MOZ_ASSERT(state
, "SupportsTextSelection() returned true!");
5694 state
->SetRangeText(aReplacement
, aStart
, aEnd
, aSelectMode
, aRv
);
5697 void HTMLInputElement::GetValueFromSetRangeText(nsAString
& aValue
) {
5698 GetNonFileValueInternal(aValue
);
5701 nsresult
HTMLInputElement::SetValueFromSetRangeText(const nsAString
& aValue
) {
5702 return SetValueInternal(aValue
, {ValueSetterOption::ByContentAPI
,
5703 ValueSetterOption::BySetRangeTextAPI
,
5704 ValueSetterOption::SetValueChanged
});
5707 Nullable
<uint32_t> HTMLInputElement::GetSelectionStart(ErrorResult
& aRv
) {
5708 if (!SupportsTextSelection()) {
5709 return Nullable
<uint32_t>();
5712 uint32_t selStart
= GetSelectionStartIgnoringType(aRv
);
5714 return Nullable
<uint32_t>();
5717 return Nullable
<uint32_t>(selStart
);
5720 uint32_t HTMLInputElement::GetSelectionStartIgnoringType(ErrorResult
& aRv
) {
5721 uint32_t selEnd
, selStart
;
5722 GetSelectionRange(&selStart
, &selEnd
, aRv
);
5726 void HTMLInputElement::SetSelectionStart(
5727 const Nullable
<uint32_t>& aSelectionStart
, ErrorResult
& aRv
) {
5728 if (!SupportsTextSelection()) {
5729 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5733 TextControlState
* state
= GetEditorState();
5734 MOZ_ASSERT(state
, "SupportsTextSelection() returned true!");
5735 state
->SetSelectionStart(aSelectionStart
, aRv
);
5738 Nullable
<uint32_t> HTMLInputElement::GetSelectionEnd(ErrorResult
& aRv
) {
5739 if (!SupportsTextSelection()) {
5740 return Nullable
<uint32_t>();
5743 uint32_t selEnd
= GetSelectionEndIgnoringType(aRv
);
5745 return Nullable
<uint32_t>();
5748 return Nullable
<uint32_t>(selEnd
);
5751 uint32_t HTMLInputElement::GetSelectionEndIgnoringType(ErrorResult
& aRv
) {
5752 uint32_t selEnd
, selStart
;
5753 GetSelectionRange(&selStart
, &selEnd
, aRv
);
5757 void HTMLInputElement::SetSelectionEnd(const Nullable
<uint32_t>& aSelectionEnd
,
5759 if (!SupportsTextSelection()) {
5760 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5764 TextControlState
* state
= GetEditorState();
5765 MOZ_ASSERT(state
, "SupportsTextSelection() returned true!");
5766 state
->SetSelectionEnd(aSelectionEnd
, aRv
);
5769 void HTMLInputElement::GetSelectionRange(uint32_t* aSelectionStart
,
5770 uint32_t* aSelectionEnd
,
5772 TextControlState
* state
= GetEditorState();
5774 // Not a text control.
5775 aRv
.Throw(NS_ERROR_UNEXPECTED
);
5779 state
->GetSelectionRange(aSelectionStart
, aSelectionEnd
, aRv
);
5782 void HTMLInputElement::GetSelectionDirection(nsAString
& aDirection
,
5784 if (!SupportsTextSelection()) {
5785 aDirection
.SetIsVoid(true);
5789 TextControlState
* state
= GetEditorState();
5790 MOZ_ASSERT(state
, "SupportsTextSelection came back true!");
5791 state
->GetSelectionDirectionString(aDirection
, aRv
);
5794 void HTMLInputElement::SetSelectionDirection(const nsAString
& aDirection
,
5796 if (!SupportsTextSelection()) {
5797 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5801 TextControlState
* state
= GetEditorState();
5802 MOZ_ASSERT(state
, "SupportsTextSelection came back true!");
5803 state
->SetSelectionDirection(aDirection
, aRv
);
5806 // https://html.spec.whatwg.org/multipage/input.html#dom-input-showpicker
5807 void HTMLInputElement::ShowPicker(ErrorResult
& aRv
) {
5808 // Step 1. If this is not mutable, then throw an "InvalidStateError"
5811 return aRv
.ThrowInvalidStateError(
5812 "This input is either disabled or readonly.");
5815 // Step 2. If this's relevant settings object's origin is not same origin with
5816 // this's relevant settings object's top-level origin, and this's type
5817 // attribute is not in the File Upload state or Color state, then throw a
5818 // "SecurityError" DOMException.
5819 if (mType
!= FormControlType::InputFile
&&
5820 mType
!= FormControlType::InputColor
) {
5821 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
5822 WindowGlobalChild
* windowGlobalChild
=
5823 window
? window
->GetWindowGlobalChild() : nullptr;
5824 if (!windowGlobalChild
|| !windowGlobalChild
->SameOriginWithTop()) {
5825 return aRv
.ThrowSecurityError(
5826 "Call was blocked because the current origin isn't same-origin with "
5831 // Step 3. If this's relevant global object does not have transient
5832 // activation, then throw a "NotAllowedError" DOMException.
5833 if (!OwnerDoc()->HasValidTransientUserGestureActivation()) {
5834 return aRv
.ThrowNotAllowedError(
5835 "Call was blocked due to lack of user activation.");
5838 // Step 4. Show the picker, if applicable, for this.
5840 // https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
5841 // To show the picker, if applicable for an input element element:
5843 // Step 1. Assert: element's relevant global object has transient activation.
5844 // Step 2. If element is not mutable, then return.
5847 // Step 3. If element's type attribute is in the File Upload state, then run
5848 // these steps in parallel:
5849 if (mType
== FormControlType::InputFile
) {
5850 FilePickerType type
= FILE_PICKER_FILE
;
5851 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
5852 HasAttr(nsGkAtoms::webkitdirectory
)) {
5853 type
= FILE_PICKER_DIRECTORY
;
5855 InitFilePicker(type
);
5859 // Step 4. Otherwise, the user agent should show any relevant user interface
5860 // for selecting a value for element, in the way it normally would when the
5861 // user interacts with the control
5862 if (mType
== FormControlType::InputColor
) {
5867 if (!IsInComposedDoc()) {
5871 if (IsDateTimeTypeSupported(mType
)) {
5872 if (CreatesDateTimeWidget()) {
5873 if (RefPtr
<Element
> dateTimeBoxElement
= GetDateTimeBoxElement()) {
5874 // Event is dispatched to closed-shadow tree and doesn't bubble.
5875 RefPtr
<Document
> doc
= dateTimeBoxElement
->OwnerDoc();
5876 nsContentUtils::DispatchTrustedEvent(doc
, dateTimeBoxElement
,
5877 u
"MozDateTimeShowPickerForJS"_ns
,
5878 CanBubble::eNo
, Cancelable::eNo
);
5881 DateTimeValue value
;
5882 GetDateTimeInputBoxValue(value
);
5883 OpenDateTimePicker(value
);
5888 #ifdef ACCESSIBILITY
5889 /*static*/ nsresult
FireEventForAccessibility(HTMLInputElement
* aTarget
,
5890 EventMessage aEventMessage
) {
5891 Element
* element
= static_cast<Element
*>(aTarget
);
5892 return nsContentUtils::DispatchTrustedEvent
<WidgetEvent
>(
5893 element
->OwnerDoc(), element
, aEventMessage
, CanBubble::eYes
,
5898 void HTMLInputElement::UpdateApzAwareFlag() {
5899 #if !defined(ANDROID) && !defined(XP_MACOSX)
5900 if (mType
== FormControlType::InputNumber
||
5901 mType
== FormControlType::InputRange
) {
5907 nsresult
HTMLInputElement::SetDefaultValueAsValue() {
5908 NS_ASSERTION(GetValueMode() == VALUE_MODE_VALUE
,
5909 "GetValueMode() should return VALUE_MODE_VALUE!");
5911 // The element has a content attribute value different from it's value when
5912 // it's in the value mode value.
5913 nsAutoString resetVal
;
5914 GetDefaultValue(resetVal
);
5916 // SetValueInternal is going to sanitize the value.
5917 // TODO(mbrodesser): sanitizing will only happen if `mDoneCreating` is true.
5918 return SetValueInternal(resetVal
, ValueSetterOption::ByInternalAPI
);
5921 // https://html.spec.whatwg.org/#auto-directionality
5922 void HTMLInputElement::SetAutoDirectionality(bool aNotify
,
5923 const nsAString
* aKnownValue
) {
5924 if (!IsAutoDirectionalityAssociated()) {
5925 return SetDirectionality(GetParentDirectionality(this), aNotify
);
5929 // It's unclear if per spec we should use the sanitized or unsanitized
5930 // value to set the directionality, but aKnownValue is unsanitized, so be
5931 // consistent. Using what the user is seeing to determine directionality
5932 // instead of the sanitized (empty if invalid) value probably makes more
5934 GetValueInternal(value
, CallerType::System
);
5935 aKnownValue
= &value
;
5937 SetDirectionalityFromValue(this, *aKnownValue
, aNotify
);
5941 HTMLInputElement::Reset() {
5942 // We should be able to reset all dirty flags regardless of the type.
5943 SetCheckedChanged(false);
5944 SetValueChanged(false);
5945 SetLastValueChangeWasInteractive(false);
5946 SetUserInteracted(false);
5948 switch (GetValueMode()) {
5949 case VALUE_MODE_VALUE
: {
5950 nsresult result
= SetDefaultValueAsValue();
5951 if (CreatesDateTimeWidget()) {
5952 // mFocusedValue has to be set here, so that `FireChangeEventIfNeeded`
5953 // can fire a change event if necessary.
5954 GetValue(mFocusedValue
, CallerType::System
);
5958 case VALUE_MODE_DEFAULT_ON
:
5959 DoSetChecked(DefaultChecked(), true, false);
5961 case VALUE_MODE_FILENAME
:
5964 case VALUE_MODE_DEFAULT
:
5971 HTMLInputElement::SubmitNamesValues(FormData
* aFormData
) {
5972 // For type=reset, and type=button, we just never submit, period.
5973 // For type=image and type=button, we only submit if we were the button
5975 // For type=radio and type=checkbox, we only submit if checked=true
5976 if (mType
== FormControlType::InputReset
||
5977 mType
== FormControlType::InputButton
||
5978 ((mType
== FormControlType::InputSubmit
||
5979 mType
== FormControlType::InputImage
) &&
5980 aFormData
->GetSubmitterElement() != this) ||
5981 ((mType
== FormControlType::InputRadio
||
5982 mType
== FormControlType::InputCheckbox
) &&
5989 GetAttr(nsGkAtoms::name
, name
);
5991 // Submit .x, .y for input type=image
5992 if (mType
== FormControlType::InputImage
) {
5993 // Get a property set by the frame to find out where it was clicked.
5994 const auto* lastClickedPoint
=
5995 static_cast<CSSIntPoint
*>(GetProperty(nsGkAtoms::imageClickedPoint
));
5997 if (lastClickedPoint
) {
5998 // Convert the values to strings for submission
5999 x
= lastClickedPoint
->x
;
6000 y
= lastClickedPoint
->y
;
6005 nsAutoString xVal
, yVal
;
6009 if (!name
.IsEmpty()) {
6010 aFormData
->AddNameValuePair(name
+ u
".x"_ns
, xVal
);
6011 aFormData
->AddNameValuePair(name
+ u
".y"_ns
, yVal
);
6013 // If the Image Element has no name, simply return x and y
6014 // to Nav and IE compatibility.
6015 aFormData
->AddNameValuePair(u
"x"_ns
, xVal
);
6016 aFormData
->AddNameValuePair(u
"y"_ns
, yVal
);
6022 // If name not there, don't submit
6023 if (name
.IsEmpty()) {
6028 // Submit file if its input type=file and this encoding method accepts files
6030 if (mType
== FormControlType::InputFile
) {
6033 const nsTArray
<OwningFileOrDirectory
>& files
=
6034 GetFilesOrDirectoriesInternal();
6036 if (files
.IsEmpty()) {
6037 NS_ENSURE_STATE(GetOwnerGlobal());
6039 RefPtr
<Blob
> blob
= Blob::CreateStringBlob(
6040 GetOwnerGlobal(), ""_ns
, u
"application/octet-stream"_ns
);
6041 RefPtr
<File
> file
= blob
->ToFile(u
""_ns
, rv
);
6044 aFormData
->AddNameBlobPair(name
, file
);
6047 return rv
.StealNSResult();
6050 for (uint32_t i
= 0; i
< files
.Length(); ++i
) {
6051 if (files
[i
].IsFile()) {
6052 aFormData
->AddNameBlobPair(name
, files
[i
].GetAsFile());
6054 MOZ_ASSERT(files
[i
].IsDirectory());
6055 aFormData
->AddNameDirectoryPair(name
, files
[i
].GetAsDirectory());
6062 if (mType
== FormControlType::InputHidden
&&
6063 name
.LowerCaseEqualsLiteral("_charset_")) {
6065 aFormData
->GetCharset(charset
);
6066 return aFormData
->AddNameValuePair(name
, NS_ConvertASCIItoUTF16(charset
));
6070 // Submit name=value
6075 GetValue(value
, CallerType::System
);
6077 if (mType
== FormControlType::InputSubmit
&& value
.IsEmpty() &&
6078 !HasAttr(nsGkAtoms::value
)) {
6079 // Get our default value, which is the same as our default label
6080 nsAutoString defaultValue
;
6081 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
6082 "Submit", OwnerDoc(), defaultValue
);
6083 value
= defaultValue
;
6086 const nsresult rv
= aFormData
->AddNameValuePair(name
, value
);
6087 if (NS_FAILED(rv
)) {
6091 // Submit dirname=dir
6092 if (IsAutoDirectionalityAssociated()) {
6093 return SubmitDirnameDir(aFormData
);
6099 static nsTArray
<FileContentData
> SaveFileContentData(
6100 const nsTArray
<OwningFileOrDirectory
>& aArray
) {
6101 nsTArray
<FileContentData
> res(aArray
.Length());
6102 for (const auto& it
: aArray
) {
6104 RefPtr
<BlobImpl
> impl
= it
.GetAsFile()->Impl();
6105 res
.AppendElement(std::move(impl
));
6107 MOZ_ASSERT(it
.IsDirectory());
6109 nsresult rv
= it
.GetAsDirectory()->GetFullRealPath(fullPath
);
6110 if (NS_WARN_IF(NS_FAILED(rv
))) {
6113 res
.AppendElement(std::move(fullPath
));
6119 void HTMLInputElement::SaveState() {
6120 PresState
* state
= nullptr;
6121 switch (GetValueMode()) {
6122 case VALUE_MODE_DEFAULT_ON
:
6123 if (mCheckedChanged
) {
6124 state
= GetPrimaryPresState();
6129 state
->contentData() = CheckedContentData(mChecked
);
6132 case VALUE_MODE_FILENAME
:
6133 if (!mFileData
->mFilesOrDirectories
.IsEmpty()) {
6134 state
= GetPrimaryPresState();
6139 state
->contentData() =
6140 SaveFileContentData(mFileData
->mFilesOrDirectories
);
6143 case VALUE_MODE_VALUE
:
6144 case VALUE_MODE_DEFAULT
:
6145 // VALUE_MODE_DEFAULT shouldn't have their value saved except 'hidden',
6146 // mType should have never been FormControlType::InputPassword and value
6147 // should have changed.
6148 if ((GetValueMode() == VALUE_MODE_DEFAULT
&&
6149 mType
!= FormControlType::InputHidden
) ||
6150 mHasBeenTypePassword
|| !mValueChanged
) {
6154 state
= GetPrimaryPresState();
6160 GetValue(value
, CallerType::System
);
6162 if (!IsSingleLineTextControl(false) &&
6163 NS_FAILED(nsLinebreakConverter::ConvertStringLineBreaks(
6164 value
, nsLinebreakConverter::eLinebreakPlatform
,
6165 nsLinebreakConverter::eLinebreakContent
))) {
6166 NS_ERROR("Converting linebreaks failed!");
6170 state
->contentData() =
6171 TextContentData(value
, mLastValueChangeWasInteractive
);
6175 if (mDisabledChanged
) {
6177 state
= GetPrimaryPresState();
6180 // We do not want to save the real disabled state but the disabled
6182 state
->disabled() = HasAttr(nsGkAtoms::disabled
);
6183 state
->disabledSet() = true;
6188 void HTMLInputElement::DoneCreatingElement() {
6189 mDoneCreating
= true;
6192 // Restore state as needed. Note that disabled state applies to all control
6195 bool restoredCheckedState
= false;
6196 if (!mInhibitRestoration
) {
6198 restoredCheckedState
= RestoreFormControlState();
6202 // If restore does not occur, we initialize .checked using the CHECKED
6205 if (!restoredCheckedState
&& mShouldInitChecked
) {
6206 DoSetChecked(DefaultChecked(), false, false);
6209 // Sanitize the value and potentially set mFocusedValue.
6210 if (GetValueMode() == VALUE_MODE_VALUE
) {
6212 GetValue(value
, CallerType::System
);
6213 // TODO: What should we do if SetValueInternal fails? (The allocation
6214 // may potentially be big, but most likely we've failed to allocate
6215 // before the type change.)
6216 SetValueInternal(value
, ValueSetterOption::ByInternalAPI
);
6218 if (CreatesDateTimeWidget()) {
6219 // mFocusedValue has to be set here, so that `FireChangeEventIfNeeded` can
6220 // fire a change event if necessary.
6221 mFocusedValue
= value
;
6225 mShouldInitChecked
= false;
6228 void HTMLInputElement::DestroyContent() {
6229 nsImageLoadingContent::Destroy();
6230 TextControlElement::DestroyContent();
6233 void HTMLInputElement::UpdateValidityElementStates(bool aNotify
) {
6234 AutoStateChangeNotifier
notifier(*this, aNotify
);
6235 RemoveStatesSilently(ElementState::VALIDITY_STATES
);
6236 if (!IsCandidateForConstraintValidation()) {
6241 state
|= ElementState::VALID
;
6242 if (mUserInteracted
) {
6243 state
|= ElementState::USER_VALID
;
6246 state
|= ElementState::INVALID
;
6247 if (mUserInteracted
) {
6248 state
|= ElementState::USER_INVALID
;
6251 AddStatesSilently(state
);
6254 static nsTArray
<OwningFileOrDirectory
> RestoreFileContentData(
6255 nsPIDOMWindowInner
* aWindow
, const nsTArray
<FileContentData
>& aData
) {
6256 nsTArray
<OwningFileOrDirectory
> res(aData
.Length());
6257 for (const auto& it
: aData
) {
6258 if (it
.type() == FileContentData::TBlobImpl
) {
6259 if (!it
.get_BlobImpl()) {
6260 // Serialization failed, skip this file.
6264 RefPtr
<File
> file
= File::Create(aWindow
->AsGlobal(), it
.get_BlobImpl());
6265 if (NS_WARN_IF(!file
)) {
6269 OwningFileOrDirectory
* element
= res
.AppendElement();
6270 element
->SetAsFile() = file
;
6272 MOZ_ASSERT(it
.type() == FileContentData::TnsString
);
6273 nsCOMPtr
<nsIFile
> file
;
6275 NS_NewLocalFile(it
.get_nsString(), true, getter_AddRefs(file
));
6276 if (NS_WARN_IF(NS_FAILED(rv
))) {
6280 RefPtr
<Directory
> directory
=
6281 Directory::Create(aWindow
->AsGlobal(), file
);
6282 MOZ_ASSERT(directory
);
6284 OwningFileOrDirectory
* element
= res
.AppendElement();
6285 element
->SetAsDirectory() = directory
;
6291 bool HTMLInputElement::RestoreState(PresState
* aState
) {
6292 bool restoredCheckedState
= false;
6294 const PresContentData
& inputState
= aState
->contentData();
6296 switch (GetValueMode()) {
6297 case VALUE_MODE_DEFAULT_ON
:
6298 if (inputState
.type() == PresContentData::TCheckedContentData
) {
6299 restoredCheckedState
= true;
6300 bool checked
= inputState
.get_CheckedContentData().checked();
6301 DoSetChecked(checked
, true, true);
6304 case VALUE_MODE_FILENAME
:
6305 if (inputState
.type() == PresContentData::TArrayOfFileContentData
) {
6306 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
6308 nsTArray
<OwningFileOrDirectory
> array
=
6309 RestoreFileContentData(window
, inputState
);
6310 SetFilesOrDirectories(array
, true);
6314 case VALUE_MODE_VALUE
:
6315 case VALUE_MODE_DEFAULT
:
6316 if (GetValueMode() == VALUE_MODE_DEFAULT
&&
6317 mType
!= FormControlType::InputHidden
) {
6321 if (inputState
.type() == PresContentData::TTextContentData
) {
6322 // TODO: What should we do if SetValueInternal fails? (The allocation
6323 // may potentially be big, but most likely we've failed to allocate
6324 // before the type change.)
6325 SetValueInternal(inputState
.get_TextContentData().value(),
6326 ValueSetterOption::SetValueChanged
);
6327 if (inputState
.get_TextContentData().lastValueChangeWasInteractive()) {
6328 SetLastValueChangeWasInteractive(true);
6334 if (aState
->disabledSet() && !aState
->disabled()) {
6335 SetDisabled(false, IgnoreErrors());
6338 return restoredCheckedState
;
6345 void HTMLInputElement::AddToRadioGroup() {
6346 MOZ_ASSERT(!mRadioGroupContainer
,
6347 "Radio button must be removed from previous radio group container "
6348 "before being added to another!");
6350 // If the element has no radio group container we can stop here.
6351 auto* container
= FindTreeRadioGroupContainer();
6357 GetAttr(nsGkAtoms::name
, name
);
6358 // If we are part of a radio group, the element must have a name.
6359 MOZ_ASSERT(!name
.IsEmpty());
6362 // Add the radio to the radio group container.
6364 container
->AddToRadioGroup(name
, this, mForm
);
6365 mRadioGroupContainer
= container
;
6368 // If the input element is checked, and we add it to the group, it will
6369 // deselect whatever is currently selected in that group
6373 // If it is checked, call "RadioSetChecked" to perform the selection/
6374 // deselection ritual. This has the side effect of repainting the
6375 // radio button, but as adding a checked radio button into the group
6376 // should not be that common an occurrence, I think we can live with
6378 // Make sure not to notify if we're still being created.
6380 RadioSetChecked(mDoneCreating
);
6382 bool indeterminate
= !container
->GetCurrentRadioButton(name
);
6383 SetStates(ElementState::INDETERMINATE
, indeterminate
, mDoneCreating
);
6387 // For integrity purposes, we have to ensure that "checkedChanged" is
6388 // the same for this new element as for all the others in the group
6390 bool checkedChanged
= mCheckedChanged
;
6392 nsCOMPtr
<nsIRadioVisitor
> visitor
=
6393 new nsRadioGetCheckedChangedVisitor(&checkedChanged
, this);
6394 VisitGroup(visitor
);
6396 SetCheckedChangedInternal(checkedChanged
);
6398 // We initialize the validity of the element to the validity of the group
6399 // because we assume UpdateValueMissingState() will be called after.
6400 SetValidityState(VALIDITY_STATE_VALUE_MISSING
,
6401 container
->GetValueMissingState(name
));
6404 void HTMLInputElement::RemoveFromRadioGroup() {
6405 auto* container
= GetCurrentRadioGroupContainer();
6411 GetAttr(nsGkAtoms::name
, name
);
6413 // If this button was checked, we need to notify the group that there is no
6414 // longer a selected radio button
6416 container
->SetCurrentRadioButton(name
, nullptr);
6417 nsCOMPtr
<nsIRadioVisitor
> visitor
= new nsRadioUpdateStateVisitor(this);
6418 VisitGroup(visitor
);
6420 AddStates(ElementState::INDETERMINATE
);
6423 // Remove this radio from its group in the container.
6424 // We need to call UpdateValueMissingValidityStateForRadio before to make sure
6425 // the group validity is updated (with this element being ignored).
6426 UpdateValueMissingValidityStateForRadio(true);
6427 container
->RemoveFromRadioGroup(name
, this);
6428 mRadioGroupContainer
= nullptr;
6431 bool HTMLInputElement::IsHTMLFocusable(bool aWithMouse
, bool* aIsFocusable
,
6432 int32_t* aTabIndex
) {
6433 if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
6434 aWithMouse
, aIsFocusable
, aTabIndex
)) {
6439 *aIsFocusable
= false;
6443 if (IsSingleLineTextControl(false) || mType
== FormControlType::InputRange
) {
6444 *aIsFocusable
= true;
6448 const bool defaultFocusable
= IsFormControlDefaultFocusable(aWithMouse
);
6449 if (CreatesDateTimeWidget()) {
6451 // We only want our native anonymous child to be tabable to, not ourself.
6454 *aIsFocusable
= true;
6458 if (mType
== FormControlType::InputHidden
) {
6462 *aIsFocusable
= false;
6467 // The other controls are all focusable
6468 *aIsFocusable
= defaultFocusable
;
6472 if (mType
!= FormControlType::InputRadio
) {
6473 *aIsFocusable
= defaultFocusable
;
6478 // Selected radio buttons are tabbable
6479 *aIsFocusable
= defaultFocusable
;
6483 // Current radio button is not selected.
6484 // But make it tabbable if nothing in group is selected.
6485 auto* container
= GetCurrentRadioGroupContainer();
6487 *aIsFocusable
= defaultFocusable
;
6492 GetAttr(nsGkAtoms::name
, name
);
6494 if (container
->GetCurrentRadioButton(name
)) {
6497 *aIsFocusable
= defaultFocusable
;
6501 nsresult
HTMLInputElement::VisitGroup(nsIRadioVisitor
* aVisitor
) {
6502 if (auto* container
= GetCurrentRadioGroupContainer()) {
6504 GetAttr(nsGkAtoms::name
, name
);
6505 return container
->WalkRadioGroup(name
, aVisitor
);
6508 aVisitor
->Visit(this);
6512 HTMLInputElement::ValueModeType
HTMLInputElement::GetValueMode() const {
6514 case FormControlType::InputHidden
:
6515 case FormControlType::InputSubmit
:
6516 case FormControlType::InputButton
:
6517 case FormControlType::InputReset
:
6518 case FormControlType::InputImage
:
6519 return VALUE_MODE_DEFAULT
;
6520 case FormControlType::InputCheckbox
:
6521 case FormControlType::InputRadio
:
6522 return VALUE_MODE_DEFAULT_ON
;
6523 case FormControlType::InputFile
:
6524 return VALUE_MODE_FILENAME
;
6526 case FormControlType::InputText
:
6527 case FormControlType::InputPassword
:
6528 case FormControlType::InputSearch
:
6529 case FormControlType::InputTel
:
6530 case FormControlType::InputEmail
:
6531 case FormControlType::InputUrl
:
6532 case FormControlType::InputNumber
:
6533 case FormControlType::InputRange
:
6534 case FormControlType::InputDate
:
6535 case FormControlType::InputTime
:
6536 case FormControlType::InputColor
:
6537 case FormControlType::InputMonth
:
6538 case FormControlType::InputWeek
:
6539 case FormControlType::InputDatetimeLocal
:
6540 return VALUE_MODE_VALUE
;
6542 MOZ_ASSERT_UNREACHABLE("Unexpected input type in GetValueMode()");
6543 return VALUE_MODE_VALUE
;
6546 return VALUE_MODE_VALUE
;
6551 bool HTMLInputElement::IsMutable() const {
6552 return !IsDisabled() &&
6553 !(DoesReadOnlyApply() && State().HasState(ElementState::READONLY
));
6556 bool HTMLInputElement::DoesRequiredApply() const {
6558 case FormControlType::InputHidden
:
6559 case FormControlType::InputButton
:
6560 case FormControlType::InputImage
:
6561 case FormControlType::InputReset
:
6562 case FormControlType::InputSubmit
:
6563 case FormControlType::InputRange
:
6564 case FormControlType::InputColor
:
6567 case FormControlType::InputRadio
:
6568 case FormControlType::InputCheckbox
:
6569 case FormControlType::InputFile
:
6570 case FormControlType::InputText
:
6571 case FormControlType::InputPassword
:
6572 case FormControlType::InputSearch
:
6573 case FormControlType::InputTel
:
6574 case FormControlType::InputEmail
:
6575 case FormControlType::InputUrl
:
6576 case FormControlType::InputNumber
:
6577 case FormControlType::InputDate
:
6578 case FormControlType::InputTime
:
6579 case FormControlType::InputMonth
:
6580 case FormControlType::InputWeek
:
6581 case FormControlType::InputDatetimeLocal
:
6584 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
6593 bool HTMLInputElement::PlaceholderApplies() const {
6594 if (IsDateTimeInputType(mType
)) {
6597 return IsSingleLineTextControl(false);
6600 bool HTMLInputElement::DoesMinMaxApply() const {
6602 case FormControlType::InputNumber
:
6603 case FormControlType::InputDate
:
6604 case FormControlType::InputTime
:
6605 case FormControlType::InputRange
:
6606 case FormControlType::InputMonth
:
6607 case FormControlType::InputWeek
:
6608 case FormControlType::InputDatetimeLocal
:
6611 case FormControlType::InputReset
:
6612 case FormControlType::InputSubmit
:
6613 case FormControlType::InputImage
:
6614 case FormControlType::InputButton
:
6615 case FormControlType::InputHidden
:
6616 case FormControlType::InputRadio
:
6617 case FormControlType::InputCheckbox
:
6618 case FormControlType::InputFile
:
6619 case FormControlType::InputText
:
6620 case FormControlType::InputPassword
:
6621 case FormControlType::InputSearch
:
6622 case FormControlType::InputTel
:
6623 case FormControlType::InputEmail
:
6624 case FormControlType::InputUrl
:
6625 case FormControlType::InputColor
:
6628 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
6637 bool HTMLInputElement::DoesAutocompleteApply() const {
6639 case FormControlType::InputHidden
:
6640 case FormControlType::InputText
:
6641 case FormControlType::InputSearch
:
6642 case FormControlType::InputUrl
:
6643 case FormControlType::InputTel
:
6644 case FormControlType::InputEmail
:
6645 case FormControlType::InputPassword
:
6646 case FormControlType::InputDate
:
6647 case FormControlType::InputTime
:
6648 case FormControlType::InputNumber
:
6649 case FormControlType::InputRange
:
6650 case FormControlType::InputColor
:
6651 case FormControlType::InputMonth
:
6652 case FormControlType::InputWeek
:
6653 case FormControlType::InputDatetimeLocal
:
6656 case FormControlType::InputReset
:
6657 case FormControlType::InputSubmit
:
6658 case FormControlType::InputImage
:
6659 case FormControlType::InputButton
:
6660 case FormControlType::InputRadio
:
6661 case FormControlType::InputCheckbox
:
6662 case FormControlType::InputFile
:
6665 MOZ_ASSERT_UNREACHABLE(
6666 "Unexpected input type in DoesAutocompleteApply()");
6675 Decimal
HTMLInputElement::GetStep() const {
6676 MOZ_ASSERT(DoesStepApply(), "GetStep() can only be called if @step applies");
6678 if (!HasAttr(nsGkAtoms::step
)) {
6679 return GetDefaultStep() * GetStepScaleFactor();
6682 nsAutoString stepStr
;
6683 GetAttr(nsGkAtoms::step
, stepStr
);
6685 if (stepStr
.LowerCaseEqualsLiteral("any")) {
6686 // The element can't suffer from step mismatch if there is no step.
6690 Decimal step
= StringToDecimal(stepStr
);
6691 if (!step
.isFinite() || step
<= Decimal(0)) {
6692 step
= GetDefaultStep();
6695 // For input type=date, we round the step value to have a rounded day.
6696 if (mType
== FormControlType::InputDate
||
6697 mType
== FormControlType::InputMonth
||
6698 mType
== FormControlType::InputWeek
) {
6699 step
= std::max(step
.round(), Decimal(1));
6702 return step
* GetStepScaleFactor();
6705 // ConstraintValidation
6707 void HTMLInputElement::SetCustomValidity(const nsAString
& aError
) {
6708 ConstraintValidation::SetCustomValidity(aError
);
6709 UpdateValidityElementStates(true);
6712 bool HTMLInputElement::IsTooLong() {
6713 if (!mValueChanged
|| !mLastValueChangeWasInteractive
) {
6717 return mInputType
->IsTooLong();
6720 bool HTMLInputElement::IsTooShort() {
6721 if (!mValueChanged
|| !mLastValueChangeWasInteractive
) {
6725 return mInputType
->IsTooShort();
6728 bool HTMLInputElement::IsValueMissing() const {
6729 // Should use UpdateValueMissingValidityStateForRadio() for type radio.
6730 MOZ_ASSERT(mType
!= FormControlType::InputRadio
);
6732 return mInputType
->IsValueMissing();
6735 bool HTMLInputElement::HasTypeMismatch() const {
6736 return mInputType
->HasTypeMismatch();
6739 Maybe
<bool> HTMLInputElement::HasPatternMismatch() const {
6740 return mInputType
->HasPatternMismatch();
6743 bool HTMLInputElement::IsRangeOverflow() const {
6744 return mInputType
->IsRangeOverflow();
6747 bool HTMLInputElement::IsRangeUnderflow() const {
6748 return mInputType
->IsRangeUnderflow();
6751 bool HTMLInputElement::ValueIsStepMismatch(const Decimal
& aValue
) const {
6752 if (aValue
.isNaN()) {
6753 // The element can't suffer from step mismatch if its value isn't a
6758 Decimal step
= GetStep();
6759 if (step
== kStepAny
) {
6763 // Value has to be an integral multiple of step.
6764 return NS_floorModulo(aValue
- GetStepBase(), step
) != Decimal(0);
6767 bool HTMLInputElement::HasStepMismatch() const {
6768 return mInputType
->HasStepMismatch();
6771 bool HTMLInputElement::HasBadInput() const { return mInputType
->HasBadInput(); }
6773 void HTMLInputElement::UpdateTooLongValidityState() {
6774 SetValidityState(VALIDITY_STATE_TOO_LONG
, IsTooLong());
6777 void HTMLInputElement::UpdateTooShortValidityState() {
6778 SetValidityState(VALIDITY_STATE_TOO_SHORT
, IsTooShort());
6781 void HTMLInputElement::UpdateValueMissingValidityStateForRadio(
6783 MOZ_ASSERT(mType
== FormControlType::InputRadio
,
6784 "This should be called only for radio input types");
6786 HTMLInputElement
* selection
= GetSelectedRadioButton();
6788 // If there is no selection, that might mean the radio is not in a group.
6789 // In that case, we can look for the checked state of the radio.
6790 bool selected
= selection
|| (!aIgnoreSelf
&& mChecked
);
6791 bool required
= !aIgnoreSelf
&& IsRequired();
6793 auto* container
= GetCurrentRadioGroupContainer();
6795 SetValidityState(VALIDITY_STATE_VALUE_MISSING
, false);
6800 GetAttr(nsGkAtoms::name
, name
);
6802 // If the current radio is required and not ignored, we can assume the entire
6803 // group is required.
6805 required
= (aIgnoreSelf
&& IsRequired())
6806 ? container
->GetRequiredRadioCount(name
) - 1
6807 : container
->GetRequiredRadioCount(name
);
6810 bool valueMissing
= required
&& !selected
;
6811 if (container
->GetValueMissingState(name
) != valueMissing
) {
6812 container
->SetValueMissingState(name
, valueMissing
);
6814 SetValidityState(VALIDITY_STATE_VALUE_MISSING
, valueMissing
);
6816 // nsRadioSetValueMissingState will call ElementStateChanged while visiting.
6817 nsAutoScriptBlocker scriptBlocker
;
6818 nsCOMPtr
<nsIRadioVisitor
> visitor
=
6819 new nsRadioSetValueMissingState(this, valueMissing
);
6820 VisitGroup(visitor
);
6824 void HTMLInputElement::UpdateValueMissingValidityState() {
6825 if (mType
== FormControlType::InputRadio
) {
6826 UpdateValueMissingValidityStateForRadio(false);
6830 SetValidityState(VALIDITY_STATE_VALUE_MISSING
, IsValueMissing());
6833 void HTMLInputElement::UpdateTypeMismatchValidityState() {
6834 SetValidityState(VALIDITY_STATE_TYPE_MISMATCH
, HasTypeMismatch());
6837 void HTMLInputElement::UpdatePatternMismatchValidityState() {
6838 Maybe
<bool> hasMismatch
= HasPatternMismatch();
6839 // Don't update if the JS engine failed to evaluate it.
6840 if (hasMismatch
.isSome()) {
6841 SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH
, hasMismatch
.value());
6845 void HTMLInputElement::UpdateRangeOverflowValidityState() {
6846 SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW
, IsRangeOverflow());
6847 UpdateInRange(true);
6850 void HTMLInputElement::UpdateRangeUnderflowValidityState() {
6851 SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
, IsRangeUnderflow());
6852 UpdateInRange(true);
6855 void HTMLInputElement::UpdateStepMismatchValidityState() {
6856 SetValidityState(VALIDITY_STATE_STEP_MISMATCH
, HasStepMismatch());
6859 void HTMLInputElement::UpdateBadInputValidityState() {
6860 SetValidityState(VALIDITY_STATE_BAD_INPUT
, HasBadInput());
6863 void HTMLInputElement::UpdateAllValidityStates(bool aNotify
) {
6864 bool validBefore
= IsValid();
6865 UpdateAllValidityStatesButNotElementState();
6866 if (validBefore
!= IsValid()) {
6867 UpdateValidityElementStates(aNotify
);
6871 void HTMLInputElement::UpdateAllValidityStatesButNotElementState() {
6872 UpdateTooLongValidityState();
6873 UpdateTooShortValidityState();
6874 UpdateValueMissingValidityState();
6875 UpdateTypeMismatchValidityState();
6876 UpdatePatternMismatchValidityState();
6877 UpdateRangeOverflowValidityState();
6878 UpdateRangeUnderflowValidityState();
6879 UpdateStepMismatchValidityState();
6880 UpdateBadInputValidityState();
6883 void HTMLInputElement::UpdateBarredFromConstraintValidation() {
6884 // NOTE: readonly attribute causes an element to be barred from constraint
6885 // validation even if it doesn't apply to that input type. That's rather
6886 // weird, but pre-existing behavior.
6887 bool wasCandidate
= IsCandidateForConstraintValidation();
6888 SetBarredFromConstraintValidation(
6889 mType
== FormControlType::InputHidden
||
6890 mType
== FormControlType::InputButton
||
6891 mType
== FormControlType::InputReset
|| IsDisabled() ||
6892 HasAttr(nsGkAtoms::readonly
) ||
6893 HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR
));
6894 if (IsCandidateForConstraintValidation() != wasCandidate
) {
6895 UpdateInRange(true);
6899 nsresult
HTMLInputElement::GetValidationMessage(nsAString
& aValidationMessage
,
6900 ValidityStateType aType
) {
6901 return mInputType
->GetValidationMessage(aValidationMessage
, aType
);
6904 bool HTMLInputElement::IsSingleLineTextControl() const {
6905 return IsSingleLineTextControl(false);
6908 bool HTMLInputElement::IsTextArea() const { return false; }
6910 bool HTMLInputElement::IsPasswordTextControl() const {
6911 return mType
== FormControlType::InputPassword
;
6914 int32_t HTMLInputElement::GetCols() {
6915 // Else we know (assume) it is an input with size attr
6916 const nsAttrValue
* attr
= GetParsedAttr(nsGkAtoms::size
);
6917 if (attr
&& attr
->Type() == nsAttrValue::eInteger
) {
6918 int32_t cols
= attr
->GetIntegerValue();
6924 return DEFAULT_COLS
;
6927 int32_t HTMLInputElement::GetWrapCols() {
6928 return 0; // only textarea's can have wrap cols
6931 int32_t HTMLInputElement::GetRows() { return DEFAULT_ROWS
; }
6933 void HTMLInputElement::GetDefaultValueFromContent(nsAString
& aValue
,
6935 if (!GetEditorState()) {
6938 GetDefaultValue(aValue
);
6939 // This is called by the frame to show the value.
6940 // We have to sanitize it when needed.
6941 // FIXME: Do we want to sanitize even when aForDisplay is false?
6942 if (mDoneCreating
) {
6943 SanitizeValue(aValue
, aForDisplay
? SanitizationKind::ForDisplay
6944 : SanitizationKind::ForValueGetter
);
6948 bool HTMLInputElement::ValueChanged() const { return mValueChanged
; }
6950 void HTMLInputElement::GetTextEditorValue(nsAString
& aValue
) const {
6951 if (TextControlState
* state
= GetEditorState()) {
6952 state
->GetValue(aValue
, /* aIgnoreWrap = */ true, /* aForDisplay = */ true);
6956 void HTMLInputElement::InitializeKeyboardEventListeners() {
6957 TextControlState
* state
= GetEditorState();
6959 state
->InitializeKeyboardEventListeners();
6963 void HTMLInputElement::UpdatePlaceholderShownState() {
6964 SetStates(ElementState::PLACEHOLDER_SHOWN
,
6965 IsValueEmpty() && PlaceholderApplies() &&
6966 HasAttr(nsGkAtoms::placeholder
));
6969 void HTMLInputElement::OnValueChanged(ValueChangeKind aKind
,
6970 bool aNewValueEmpty
,
6971 const nsAString
* aKnownNewValue
) {
6972 MOZ_ASSERT_IF(aKnownNewValue
, aKnownNewValue
->IsEmpty() == aNewValueEmpty
);
6973 if (aKind
!= ValueChangeKind::Internal
) {
6974 mLastValueChangeWasInteractive
= aKind
== ValueChangeKind::UserInteraction
;
6977 if (aNewValueEmpty
!= IsValueEmpty()) {
6978 SetStates(ElementState::VALUE_EMPTY
, aNewValueEmpty
);
6979 UpdatePlaceholderShownState();
6982 UpdateAllValidityStates(true);
6985 SetAutoDirectionality(true, aKnownNewValue
);
6989 bool HTMLInputElement::HasCachedSelection() {
6990 TextControlState
* state
= GetEditorState();
6994 return state
->IsSelectionCached() && state
->HasNeverInitializedBefore() &&
6995 state
->GetSelectionProperties().GetStart() !=
6996 state
->GetSelectionProperties().GetEnd();
6999 void HTMLInputElement::SetRevealPassword(bool aValue
) {
7000 if (NS_WARN_IF(mType
!= FormControlType::InputPassword
)) {
7003 if (aValue
== State().HasState(ElementState::REVEALED
)) {
7006 RefPtr doc
= OwnerDoc();
7007 // We allow chrome code to prevent this. This is important for about:logins,
7008 // which may need to run some OS-dependent authentication code before
7009 // revealing the saved passwords.
7010 bool defaultAction
= true;
7011 nsContentUtils::DispatchEventOnlyToChrome(
7012 doc
, this, u
"MozWillToggleReveal"_ns
, CanBubble::eYes
, Cancelable::eYes
,
7014 if (NS_WARN_IF(!defaultAction
)) {
7017 SetStates(ElementState::REVEALED
, aValue
);
7020 bool HTMLInputElement::RevealPassword() const {
7021 if (NS_WARN_IF(mType
!= FormControlType::InputPassword
)) {
7024 return State().HasState(ElementState::REVEALED
);
7027 void HTMLInputElement::FieldSetDisabledChanged(bool aNotify
) {
7028 // This *has* to be called *before* UpdateBarredFromConstraintValidation and
7029 // UpdateValueMissingValidityState because these two functions depend on our
7031 nsGenericHTMLFormControlElementWithState::FieldSetDisabledChanged(aNotify
);
7033 UpdateValueMissingValidityState();
7034 UpdateBarredFromConstraintValidation();
7035 UpdateValidityElementStates(aNotify
);
7038 void HTMLInputElement::SetFilePickerFiltersFromAccept(
7039 nsIFilePicker
* filePicker
) {
7040 // We always add |filterAll|
7041 filePicker
->AppendFilters(nsIFilePicker::filterAll
);
7043 NS_ASSERTION(HasAttr(nsGkAtoms::accept
),
7044 "You should not call SetFilePickerFiltersFromAccept if the"
7045 " element has no accept attribute!");
7047 // Services to retrieve image/*, audio/*, video/* filters
7048 nsCOMPtr
<nsIStringBundleService
> stringService
=
7049 components::StringBundle::Service();
7050 if (!stringService
) {
7053 nsCOMPtr
<nsIStringBundle
> filterBundle
;
7054 if (NS_FAILED(stringService
->CreateBundle(
7055 "chrome://global/content/filepicker.properties",
7056 getter_AddRefs(filterBundle
)))) {
7060 // Service to retrieve mime type information for mime types filters
7061 nsCOMPtr
<nsIMIMEService
> mimeService
= do_GetService("@mozilla.org/mime;1");
7066 nsAutoString accept
;
7067 GetAttr(nsGkAtoms::accept
, accept
);
7069 HTMLSplitOnSpacesTokenizer
tokenizer(accept
, ',');
7071 nsTArray
<nsFilePickerFilter
> filters
;
7072 nsString allExtensionsList
;
7074 // Retrieve all filters
7075 while (tokenizer
.hasMoreTokens()) {
7076 const nsDependentSubstring
& token
= tokenizer
.nextToken();
7078 if (token
.IsEmpty()) {
7082 int32_t filterMask
= 0;
7083 nsString filterName
;
7084 nsString extensionListStr
;
7086 // First, check for image/audio/video filters...
7087 if (token
.EqualsLiteral("image/*")) {
7088 filterMask
= nsIFilePicker::filterImages
;
7089 filterBundle
->GetStringFromName("imageFilter", extensionListStr
);
7090 } else if (token
.EqualsLiteral("audio/*")) {
7091 filterMask
= nsIFilePicker::filterAudio
;
7092 filterBundle
->GetStringFromName("audioFilter", extensionListStr
);
7093 } else if (token
.EqualsLiteral("video/*")) {
7094 filterMask
= nsIFilePicker::filterVideo
;
7095 filterBundle
->GetStringFromName("videoFilter", extensionListStr
);
7096 } else if (token
.First() == '.') {
7097 if (token
.Contains(';') || token
.Contains('*')) {
7098 // Ignore this filter as it contains reserved characters
7101 extensionListStr
= u
"*"_ns
+ token
;
7102 filterName
= extensionListStr
;
7104 //... if no image/audio/video filter is found, check mime types filters
7105 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
;
7107 mimeService
->GetFromTypeAndExtension(NS_ConvertUTF16toUTF8(token
),
7108 ""_ns
, // No extension
7109 getter_AddRefs(mimeInfo
))) ||
7114 // Get a name for the filter: first try the description, then the mime
7115 // type name if there is no description
7116 mimeInfo
->GetDescription(filterName
);
7117 if (filterName
.IsEmpty()) {
7118 nsCString mimeTypeName
;
7119 mimeInfo
->GetType(mimeTypeName
);
7120 CopyUTF8toUTF16(mimeTypeName
, filterName
);
7123 // Get extension list
7124 nsCOMPtr
<nsIUTF8StringEnumerator
> extensions
;
7125 mimeInfo
->GetFileExtensions(getter_AddRefs(extensions
));
7128 while (NS_SUCCEEDED(extensions
->HasMore(&hasMore
)) && hasMore
) {
7129 nsCString extension
;
7130 if (NS_FAILED(extensions
->GetNext(extension
))) {
7133 if (!extensionListStr
.IsEmpty()) {
7134 extensionListStr
.AppendLiteral("; ");
7136 extensionListStr
+= u
"*."_ns
+ NS_ConvertUTF8toUTF16(extension
);
7140 if (!filterMask
&& (extensionListStr
.IsEmpty() || filterName
.IsEmpty())) {
7141 // No valid filter found
7145 // At this point we're sure the token represents a valid filter, so pass
7146 // it directly as a raw filter.
7147 filePicker
->AppendRawFilter(token
);
7149 // If we arrived here, that means we have a valid filter: let's create it
7150 // and add it to our list, if no similar filter is already present
7151 nsFilePickerFilter filter
;
7153 filter
= nsFilePickerFilter(filterMask
);
7155 filter
= nsFilePickerFilter(filterName
, extensionListStr
);
7158 if (!filters
.Contains(filter
)) {
7159 if (!allExtensionsList
.IsEmpty()) {
7160 allExtensionsList
.AppendLiteral("; ");
7162 allExtensionsList
+= extensionListStr
;
7163 filters
.AppendElement(filter
);
7167 // Remove similar filters
7168 // Iterate over a copy, as we might modify the original filters list
7169 const nsTArray
<nsFilePickerFilter
> filtersCopy
= filters
.Clone();
7170 for (uint32_t i
= 0; i
< filtersCopy
.Length(); ++i
) {
7171 const nsFilePickerFilter
& filterToCheck
= filtersCopy
[i
];
7172 if (filterToCheck
.mFilterMask
) {
7175 for (uint32_t j
= 0; j
< filtersCopy
.Length(); ++j
) {
7179 // Check if this filter's extension list is a substring of the other one.
7180 // e.g. if filters are "*.jpeg" and "*.jpeg; *.jpg" the first one should
7182 // Add an extra "; " to be sure the check will work and avoid cases like
7183 // "*.xls" being a subtring of "*.xslx" while those are two differents
7184 // filters and none should be removed.
7185 if (FindInReadable(filterToCheck
.mFilter
+ u
";"_ns
,
7186 filtersCopy
[j
].mFilter
+ u
";"_ns
)) {
7187 // We already have a similar, less restrictive filter (i.e.
7188 // filterToCheck extensionList is just a subset of another filter
7189 // extension list): remove this one
7190 filters
.RemoveElement(filterToCheck
);
7195 // Add "All Supported Types" filter
7196 if (filters
.Length() > 1) {
7198 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
7199 "AllSupportedTypes", title
);
7200 filePicker
->AppendFilter(title
, allExtensionsList
);
7204 for (uint32_t i
= 0; i
< filters
.Length(); ++i
) {
7205 const nsFilePickerFilter
& filter
= filters
[i
];
7206 if (filter
.mFilterMask
) {
7207 filePicker
->AppendFilters(filter
.mFilterMask
);
7209 filePicker
->AppendFilter(filter
.mTitle
, filter
.mFilter
);
7213 if (filters
.Length() >= 1) {
7214 // |filterAll| will always use index=0 so we need to set index=1 as the
7215 // current filter. This will be "All Supported Types" for multiple filters.
7216 filePicker
->SetFilterIndex(1);
7220 Decimal
HTMLInputElement::GetStepScaleFactor() const {
7221 MOZ_ASSERT(DoesStepApply());
7224 case FormControlType::InputDate
:
7225 return kStepScaleFactorDate
;
7226 case FormControlType::InputNumber
:
7227 case FormControlType::InputRange
:
7228 return kStepScaleFactorNumberRange
;
7229 case FormControlType::InputTime
:
7230 case FormControlType::InputDatetimeLocal
:
7231 return kStepScaleFactorTime
;
7232 case FormControlType::InputMonth
:
7233 return kStepScaleFactorMonth
;
7234 case FormControlType::InputWeek
:
7235 return kStepScaleFactorWeek
;
7237 MOZ_ASSERT(false, "Unrecognized input type");
7238 return Decimal::nan();
7242 Decimal
HTMLInputElement::GetDefaultStep() const {
7243 MOZ_ASSERT(DoesStepApply());
7246 case FormControlType::InputDate
:
7247 case FormControlType::InputMonth
:
7248 case FormControlType::InputWeek
:
7249 case FormControlType::InputNumber
:
7250 case FormControlType::InputRange
:
7251 return kDefaultStep
;
7252 case FormControlType::InputTime
:
7253 case FormControlType::InputDatetimeLocal
:
7254 return kDefaultStepTime
;
7256 MOZ_ASSERT(false, "Unrecognized input type");
7257 return Decimal::nan();
7261 void HTMLInputElement::SetUserInteracted(bool aInteracted
) {
7262 if (mUserInteracted
== aInteracted
) {
7265 mUserInteracted
= aInteracted
;
7266 UpdateValidityElementStates(true);
7269 void HTMLInputElement::UpdateInRange(bool aNotify
) {
7270 AutoStateChangeNotifier
notifier(*this, aNotify
);
7271 RemoveStatesSilently(ElementState::INRANGE
| ElementState::OUTOFRANGE
);
7272 if (!mHasRange
|| !IsCandidateForConstraintValidation()) {
7275 bool outOfRange
= GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW
) ||
7276 GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
);
7277 AddStatesSilently(outOfRange
? ElementState::OUTOFRANGE
7278 : ElementState::INRANGE
);
7281 void HTMLInputElement::UpdateHasRange(bool aNotify
) {
7282 // There is a range if min/max applies for the type and if the element
7283 // currently have a valid min or max.
7284 const bool newHasRange
= [&] {
7285 if (!DoesMinMaxApply()) {
7288 return !GetMinimum().isNaN() || !GetMaximum().isNaN();
7291 if (newHasRange
== mHasRange
) {
7295 mHasRange
= newHasRange
;
7296 UpdateInRange(aNotify
);
7299 void HTMLInputElement::PickerClosed() { mPickerRunning
= false; }
7301 JSObject
* HTMLInputElement::WrapNode(JSContext
* aCx
,
7302 JS::Handle
<JSObject
*> aGivenProto
) {
7303 return HTMLInputElement_Binding::Wrap(aCx
, this, aGivenProto
);
7306 GetFilesHelper
* HTMLInputElement::GetOrCreateGetFilesHelper(bool aRecursiveFlag
,
7308 MOZ_ASSERT(mFileData
);
7310 if (aRecursiveFlag
) {
7311 if (!mFileData
->mGetFilesRecursiveHelper
) {
7312 mFileData
->mGetFilesRecursiveHelper
= GetFilesHelper::Create(
7313 GetFilesOrDirectoriesInternal(), aRecursiveFlag
, aRv
);
7314 if (NS_WARN_IF(aRv
.Failed())) {
7319 return mFileData
->mGetFilesRecursiveHelper
;
7322 if (!mFileData
->mGetFilesNonRecursiveHelper
) {
7323 mFileData
->mGetFilesNonRecursiveHelper
= GetFilesHelper::Create(
7324 GetFilesOrDirectoriesInternal(), aRecursiveFlag
, aRv
);
7325 if (NS_WARN_IF(aRv
.Failed())) {
7330 return mFileData
->mGetFilesNonRecursiveHelper
;
7333 void HTMLInputElement::UpdateEntries(
7334 const nsTArray
<OwningFileOrDirectory
>& aFilesOrDirectories
) {
7335 MOZ_ASSERT(mFileData
&& mFileData
->mEntries
.IsEmpty());
7337 nsCOMPtr
<nsIGlobalObject
> global
= OwnerDoc()->GetScopeObject();
7340 RefPtr
<FileSystem
> fs
= FileSystem::Create(global
);
7341 if (NS_WARN_IF(!fs
)) {
7345 Sequence
<RefPtr
<FileSystemEntry
>> entries
;
7346 for (uint32_t i
= 0; i
< aFilesOrDirectories
.Length(); ++i
) {
7347 RefPtr
<FileSystemEntry
> entry
=
7348 FileSystemEntry::Create(global
, aFilesOrDirectories
[i
], fs
);
7351 if (!entries
.AppendElement(entry
, fallible
)) {
7356 // The root fileSystem is a DirectoryEntry object that contains only the
7357 // dropped fileEntry and directoryEntry objects.
7358 fs
->CreateRoot(entries
);
7360 mFileData
->mEntries
= std::move(entries
);
7363 void HTMLInputElement::GetWebkitEntries(
7364 nsTArray
<RefPtr
<FileSystemEntry
>>& aSequence
) {
7365 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
7369 Telemetry::Accumulate(Telemetry::BLINK_FILESYSTEM_USED
, true);
7370 aSequence
.AppendElements(mFileData
->mEntries
);
7373 already_AddRefed
<nsINodeList
> HTMLInputElement::GetLabels() {
7374 if (!IsLabelable()) {
7378 return nsGenericHTMLElement::Labels();
7381 void HTMLInputElement::MaybeFireInputPasswordRemoved() {
7382 // We want this event to be fired only when the password field is removed
7383 // from the DOM tree, not when it is released (ex, tab is closed). So don't
7384 // fire an event when the password input field doesn't have a docshell.
7385 Document
* doc
= GetComposedDoc();
7386 nsIDocShell
* container
= doc
? doc
->GetDocShell() : nullptr;
7391 // Right now, only the password manager listens to the event and only listen
7392 // to it under certain circumstances. So don't fire this event unless
7394 if (!doc
->ShouldNotifyFormOrPasswordRemoved()) {
7398 AsyncEventDispatcher::RunDOMEventWhenSafe(
7399 *this, u
"DOMInputPasswordRemoved"_ns
, CanBubble::eNo
,
7400 ChromeOnlyDispatch::eYes
);
7403 } // namespace mozilla::dom
7405 #undef NS_ORIGINAL_CHECKED_VALUE