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 "mozilla/dom/ProgressEvent.h"
53 #include "nsGkAtoms.h"
54 #include "nsStyleConsts.h"
55 #include "nsPresContext.h"
56 #include "nsIFormControl.h"
57 #include "mozilla/dom/Document.h"
58 #include "mozilla/dom/HTMLDataListElement.h"
59 #include "mozilla/dom/HTMLOptionElement.h"
60 #include "nsIFormControlFrame.h"
61 #include "nsITextControlFrame.h"
63 #include "nsRangeFrame.h"
65 #include "nsIEditor.h"
66 #include "nsIPromptCollection.h"
68 #include "mozilla/PresState.h"
69 #include "nsLinebreakConverter.h" //to strip out carriage returns
70 #include "nsReadableUtils.h"
71 #include "nsUnicharUtils.h"
72 #include "nsLayoutUtils.h"
73 #include "nsVariant.h"
75 #include "mozilla/ContentEvents.h"
76 #include "mozilla/EventDispatcher.h"
77 #include "mozilla/MappedDeclarationsBuilder.h"
78 #include "mozilla/InternalMutationEvent.h"
79 #include "mozilla/TextControlState.h"
80 #include "mozilla/TextEditor.h"
81 #include "mozilla/TextEvents.h"
82 #include "mozilla/TouchEvents.h"
87 #include "mozilla/dom/RadioGroupContainer.h"
88 #include "nsIRadioVisitor.h"
89 #include "nsRadioVisitor.h"
92 #include "mozilla/dom/FileSystemEntry.h"
93 #include "mozilla/dom/FileSystem.h"
94 #include "mozilla/dom/File.h"
95 #include "mozilla/dom/FileList.h"
97 #include "nsDirectoryServiceDefs.h"
98 #include "nsIContentPrefService2.h"
99 #include "nsIMIMEService.h"
100 #include "nsIObserverService.h"
103 #include "nsImageLoadingContent.h"
104 #include "imgRequestProxy.h"
106 #include "mozAutoDocUpdate.h"
107 #include "nsContentCreatorFunctions.h"
108 #include "nsContentUtils.h"
109 #include "mozilla/dom/DirectionalityUtils.h"
111 #include "mozilla/LookAndFeel.h"
112 #include "mozilla/Preferences.h"
113 #include "mozilla/MathAlgorithms.h"
117 #include "nsIColorPicker.h"
118 #include "nsIStringEnumerator.h"
119 #include "HTMLSplitOnSpacesTokenizer.h"
120 #include "nsIMIMEInfo.h"
121 #include "nsFrameSelection.h"
122 #include "nsXULControllers.h"
127 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Input
)
129 // XXX align=left, hspace, vspace, border? other nav4 attrs
131 namespace mozilla::dom
{
133 // First bits are needed for the control type.
134 #define NS_OUTER_ACTIVATE_EVENT (1 << 9)
135 #define NS_ORIGINAL_CHECKED_VALUE (1 << 10)
136 // (1 << 11 is unused)
137 #define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
138 #define NS_PRE_HANDLE_BLUR_EVENT (1 << 13)
139 #define NS_IN_SUBMIT_CLICK (1 << 15)
140 #define NS_CONTROL_TYPE(bits) \
141 ((bits) & ~(NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | \
142 NS_ORIGINAL_INDETERMINATE_VALUE | NS_PRE_HANDLE_BLUR_EVENT | \
145 // whether textfields should be selected once focused:
146 // -1: no, 1: yes, 0: uninitialized
147 static int32_t gSelectTextFieldOnFocus
;
148 UploadLastDir
* HTMLInputElement::gUploadLastDir
;
150 static const nsAttrValue::EnumTable kInputTypeTable
[] = {
151 {"button", FormControlType::InputButton
},
152 {"checkbox", FormControlType::InputCheckbox
},
153 {"color", FormControlType::InputColor
},
154 {"date", FormControlType::InputDate
},
155 {"datetime-local", FormControlType::InputDatetimeLocal
},
156 {"email", FormControlType::InputEmail
},
157 {"file", FormControlType::InputFile
},
158 {"hidden", FormControlType::InputHidden
},
159 {"reset", FormControlType::InputReset
},
160 {"image", FormControlType::InputImage
},
161 {"month", FormControlType::InputMonth
},
162 {"number", FormControlType::InputNumber
},
163 {"password", FormControlType::InputPassword
},
164 {"radio", FormControlType::InputRadio
},
165 {"range", FormControlType::InputRange
},
166 {"search", FormControlType::InputSearch
},
167 {"submit", FormControlType::InputSubmit
},
168 {"tel", FormControlType::InputTel
},
169 {"time", FormControlType::InputTime
},
170 {"url", FormControlType::InputUrl
},
171 {"week", FormControlType::InputWeek
},
172 // "text" must be last for ParseAttribute to work right. If you add things
173 // before it, please update kInputDefaultType.
174 {"text", FormControlType::InputText
},
177 // Default type is 'text'.
178 static const nsAttrValue::EnumTable
* kInputDefaultType
=
179 &kInputTypeTable
[ArrayLength(kInputTypeTable
) - 2];
181 static const nsAttrValue::EnumTable kCaptureTable
[] = {
182 {"user", nsIFilePicker::captureUser
},
183 {"environment", nsIFilePicker::captureEnv
},
184 {"", nsIFilePicker::captureDefault
},
185 {nullptr, nsIFilePicker::captureNone
}};
187 static const nsAttrValue::EnumTable
* kCaptureDefault
= &kCaptureTable
[2];
189 using namespace blink
;
191 constexpr Decimal
HTMLInputElement::kStepScaleFactorDate(86400000_d
);
192 constexpr Decimal
HTMLInputElement::kStepScaleFactorNumberRange(1_d
);
193 constexpr Decimal
HTMLInputElement::kStepScaleFactorTime(1000_d
);
194 constexpr Decimal
HTMLInputElement::kStepScaleFactorMonth(1_d
);
195 constexpr Decimal
HTMLInputElement::kStepScaleFactorWeek(7 * 86400000_d
);
196 constexpr Decimal
HTMLInputElement::kDefaultStepBase(0_d
);
197 constexpr Decimal
HTMLInputElement::kDefaultStepBaseWeek(-259200000_d
);
198 constexpr Decimal
HTMLInputElement::kDefaultStep(1_d
);
199 constexpr Decimal
HTMLInputElement::kDefaultStepTime(60_d
);
200 constexpr Decimal
HTMLInputElement::kStepAny(0_d
);
202 const double HTMLInputElement::kMinimumYear
= 1;
203 const double HTMLInputElement::kMaximumYear
= 275760;
204 const double HTMLInputElement::kMaximumWeekInMaximumYear
= 37;
205 const double HTMLInputElement::kMaximumDayInMaximumYear
= 13;
206 const double HTMLInputElement::kMaximumMonthInMaximumYear
= 9;
207 const double HTMLInputElement::kMaximumWeekInYear
= 53;
208 const double HTMLInputElement::kMsPerDay
= 24 * 60 * 60 * 1000;
210 // An helper class for the dispatching of the 'change' event.
211 // This class is used when the FilePicker finished its task (or when files and
212 // directories are set by some chrome/test only method).
213 // The task of this class is to postpone the dispatching of 'change' and 'input'
214 // events at the end of the exploration of the directories.
215 class DispatchChangeEventCallback final
: public GetFilesCallback
{
217 explicit DispatchChangeEventCallback(HTMLInputElement
* aInputElement
)
218 : mInputElement(aInputElement
) {
219 MOZ_ASSERT(aInputElement
);
222 virtual void Callback(
224 const FallibleTArray
<RefPtr
<BlobImpl
>>& aBlobImpls
) override
{
225 if (!mInputElement
->GetOwnerGlobal()) {
229 nsTArray
<OwningFileOrDirectory
> array
;
230 for (uint32_t i
= 0; i
< aBlobImpls
.Length(); ++i
) {
231 OwningFileOrDirectory
* element
= array
.AppendElement();
233 File::Create(mInputElement
->GetOwnerGlobal(), aBlobImpls
[i
]);
234 if (NS_WARN_IF(!file
)) {
238 element
->SetAsFile() = file
;
241 mInputElement
->SetFilesOrDirectories(array
, true);
242 Unused
<< NS_WARN_IF(NS_FAILED(DispatchEvents()));
245 MOZ_CAN_RUN_SCRIPT_BOUNDARY
246 nsresult
DispatchEvents() {
247 RefPtr
<HTMLInputElement
> inputElement(mInputElement
);
248 nsresult rv
= nsContentUtils::DispatchInputEvent(inputElement
);
249 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "Failed to dispatch input event");
250 mInputElement
->SetUserInteracted(true);
251 rv
= nsContentUtils::DispatchTrustedEvent(mInputElement
->OwnerDoc(),
252 mInputElement
, u
"change"_ns
,
253 CanBubble::eYes
, Cancelable::eNo
);
259 RefPtr
<HTMLInputElement
> mInputElement
;
262 struct HTMLInputElement::FileData
{
264 * The value of the input if it is a file input. This is the list of files or
265 * directories DOM objects used when uploading a file. It is vital that this
266 * is kept separate from mValue so that it won't be possible to 'leak' the
267 * value from a text-input to a file-input. Additionally, the logic for this
268 * value is kept as simple as possible to avoid accidental errors where the
269 * wrong filename is used. Therefor the list of filenames is always owned by
270 * this member, never by the frame. Whenever the frame wants to change the
271 * filename it has to call SetFilesOrDirectories to update this member.
273 nsTArray
<OwningFileOrDirectory
> mFilesOrDirectories
;
275 RefPtr
<GetFilesHelper
> mGetFilesRecursiveHelper
;
276 RefPtr
<GetFilesHelper
> mGetFilesNonRecursiveHelper
;
279 * Hack for bug 1086684: Stash the .value when we're a file picker.
281 nsString mFirstFilePath
;
283 RefPtr
<FileList
> mFileList
;
284 Sequence
<RefPtr
<FileSystemEntry
>> mEntries
;
286 nsString mStaticDocFileList
;
288 void ClearGetFilesHelpers() {
289 if (mGetFilesRecursiveHelper
) {
290 mGetFilesRecursiveHelper
->Unlink();
291 mGetFilesRecursiveHelper
= nullptr;
294 if (mGetFilesNonRecursiveHelper
) {
295 mGetFilesNonRecursiveHelper
->Unlink();
296 mGetFilesNonRecursiveHelper
= nullptr;
300 // Cycle Collection support.
301 void Traverse(nsCycleCollectionTraversalCallback
& cb
) {
302 FileData
* tmp
= this;
303 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilesOrDirectories
)
304 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList
)
305 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEntries
)
306 if (mGetFilesRecursiveHelper
) {
307 mGetFilesRecursiveHelper
->Traverse(cb
);
310 if (mGetFilesNonRecursiveHelper
) {
311 mGetFilesNonRecursiveHelper
->Traverse(cb
);
316 FileData
* tmp
= this;
317 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilesOrDirectories
)
318 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList
)
319 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEntries
)
320 ClearGetFilesHelpers();
324 HTMLInputElement::nsFilePickerShownCallback::nsFilePickerShownCallback(
325 HTMLInputElement
* aInput
, nsIFilePicker
* aFilePicker
)
326 : mFilePicker(aFilePicker
), mInput(aInput
) {}
328 NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback
, nsIContentPrefCallback2
)
331 UploadLastDir::ContentPrefCallback::HandleCompletion(uint16_t aReason
) {
332 nsCOMPtr
<nsIFile
> localFile
;
333 nsAutoString prefStr
;
335 if (aReason
== nsIContentPrefCallback2::COMPLETE_ERROR
|| !mResult
) {
336 Preferences::GetString("dom.input.fallbackUploadDir", prefStr
);
339 if (prefStr
.IsEmpty() && mResult
) {
340 nsCOMPtr
<nsIVariant
> pref
;
341 mResult
->GetValue(getter_AddRefs(pref
));
342 pref
->GetAsAString(prefStr
);
345 if (!prefStr
.IsEmpty()) {
346 localFile
= do_CreateInstance(NS_LOCAL_FILE_CONTRACTID
);
347 if (localFile
&& NS_WARN_IF(NS_FAILED(localFile
->InitWithPath(prefStr
)))) {
353 mFilePicker
->SetDisplayDirectory(localFile
);
355 // If no custom directory was set through the pref, default to
356 // "desktop" directory for each platform.
357 mFilePicker
->SetDisplaySpecialDirectory(
358 NS_LITERAL_STRING_FROM_CSTRING(NS_OS_DESKTOP_DIR
));
361 mFilePicker
->Open(mFpCallback
);
366 UploadLastDir::ContentPrefCallback::HandleResult(nsIContentPref
* pref
) {
372 UploadLastDir::ContentPrefCallback::HandleError(nsresult error
) {
373 // HandleCompletion is always called (even with HandleError was called),
374 // so we don't need to do anything special here.
381 * This may return nullptr if the DOM File's implementation of
382 * File::mozFullPathInternal does not successfully return a non-empty
383 * string that is a valid path. This can happen on Firefox OS, for example,
384 * where the file picker can create Blobs.
386 static already_AddRefed
<nsIFile
> LastUsedDirectory(
387 const OwningFileOrDirectory
& aData
) {
388 if (aData
.IsFile()) {
391 aData
.GetAsFile()->GetMozFullPathInternal(path
, error
);
392 if (error
.Failed() || path
.IsEmpty()) {
393 error
.SuppressException();
397 nsCOMPtr
<nsIFile
> localFile
;
398 nsresult rv
= NS_NewLocalFile(path
, true, getter_AddRefs(localFile
));
399 if (NS_WARN_IF(NS_FAILED(rv
))) {
403 nsCOMPtr
<nsIFile
> parentFile
;
404 rv
= localFile
->GetParent(getter_AddRefs(parentFile
));
405 if (NS_WARN_IF(NS_FAILED(rv
))) {
409 return parentFile
.forget();
412 MOZ_ASSERT(aData
.IsDirectory());
414 nsCOMPtr
<nsIFile
> localFile
= aData
.GetAsDirectory()->GetInternalNsIFile();
415 MOZ_ASSERT(localFile
);
417 return localFile
.forget();
420 void GetDOMFileOrDirectoryName(const OwningFileOrDirectory
& aData
,
422 if (aData
.IsFile()) {
423 aData
.GetAsFile()->GetName(aName
);
425 MOZ_ASSERT(aData
.IsDirectory());
427 aData
.GetAsDirectory()->GetName(aName
, rv
);
428 if (NS_WARN_IF(rv
.Failed())) {
429 rv
.SuppressException();
434 void GetDOMFileOrDirectoryPath(const OwningFileOrDirectory
& aData
,
435 nsAString
& aPath
, ErrorResult
& aRv
) {
436 if (aData
.IsFile()) {
437 aData
.GetAsFile()->GetMozFullPathInternal(aPath
, aRv
);
439 MOZ_ASSERT(aData
.IsDirectory());
440 aData
.GetAsDirectory()->GetFullRealPath(aPath
);
447 HTMLInputElement::nsFilePickerShownCallback::Done(
448 nsIFilePicker::ResultCode aResult
) {
449 mInput
->PickerClosed();
451 if (aResult
== nsIFilePicker::returnCancel
) {
452 RefPtr
<HTMLInputElement
> inputElement(mInput
);
453 return nsContentUtils::DispatchTrustedEvent(
454 inputElement
->OwnerDoc(), inputElement
, u
"cancel"_ns
, CanBubble::eYes
,
458 mInput
->OwnerDoc()->NotifyUserGestureActivation();
460 nsIFilePicker::Mode mode
;
461 mFilePicker
->GetMode(&mode
);
463 // Collect new selected filenames
464 nsTArray
<OwningFileOrDirectory
> newFilesOrDirectories
;
465 if (mode
== nsIFilePicker::modeOpenMultiple
) {
466 nsCOMPtr
<nsISimpleEnumerator
> iter
;
468 mFilePicker
->GetDomFileOrDirectoryEnumerator(getter_AddRefs(iter
));
469 NS_ENSURE_SUCCESS(rv
, rv
);
475 nsCOMPtr
<nsISupports
> tmp
;
478 while (NS_SUCCEEDED(iter
->HasMoreElements(&hasMore
)) && hasMore
) {
479 iter
->GetNext(getter_AddRefs(tmp
));
480 RefPtr
<Blob
> domBlob
= do_QueryObject(tmp
);
482 "Null file object from FilePicker's file enumerator?");
487 OwningFileOrDirectory
* element
= newFilesOrDirectories
.AppendElement();
488 element
->SetAsFile() = domBlob
->ToFile();
491 MOZ_ASSERT(mode
== nsIFilePicker::modeOpen
||
492 mode
== nsIFilePicker::modeGetFolder
);
493 nsCOMPtr
<nsISupports
> tmp
;
494 nsresult rv
= mFilePicker
->GetDomFileOrDirectory(getter_AddRefs(tmp
));
495 NS_ENSURE_SUCCESS(rv
, rv
);
497 // Show a prompt to get user confirmation before allowing folder access.
498 // This is to prevent sites from tricking the user into uploading files.
500 if (mode
== nsIFilePicker::modeGetFolder
) {
501 nsCOMPtr
<nsIPromptCollection
> prompter
=
502 do_GetService("@mozilla.org/embedcomp/prompt-collection;1");
504 return NS_ERROR_NOT_AVAILABLE
;
507 bool confirmed
= false;
508 BrowsingContext
* bc
= mInput
->OwnerDoc()->GetBrowsingContext();
510 // Get directory name
511 RefPtr
<Directory
> directory
= static_cast<Directory
*>(tmp
.get());
512 nsAutoString directoryName
;
514 directory
->GetName(directoryName
, error
);
515 if (NS_WARN_IF(error
.Failed())) {
516 return error
.StealNSResult();
519 rv
= prompter
->ConfirmFolderUpload(bc
, directoryName
, &confirmed
);
520 NS_ENSURE_SUCCESS(rv
, rv
);
522 // User aborted upload
527 RefPtr
<Blob
> blob
= do_QueryObject(tmp
);
529 RefPtr
<File
> file
= blob
->ToFile();
532 OwningFileOrDirectory
* element
= newFilesOrDirectories
.AppendElement();
533 element
->SetAsFile() = file
;
535 RefPtr
<Directory
> directory
= static_cast<Directory
*>(tmp
.get());
536 OwningFileOrDirectory
* element
= newFilesOrDirectories
.AppendElement();
537 element
->SetAsDirectory() = directory
;
541 if (newFilesOrDirectories
.IsEmpty()) {
545 // Store the last used directory using the content pref service:
546 nsCOMPtr
<nsIFile
> lastUsedDir
= LastUsedDirectory(newFilesOrDirectories
[0]);
549 HTMLInputElement::gUploadLastDir
->StoreLastUsedDirectory(mInput
->OwnerDoc(),
553 // The text control frame (if there is one) isn't going to send a change
554 // event because it will think this is done by a script.
555 // So, we can safely send one by ourself.
556 mInput
->SetFilesOrDirectories(newFilesOrDirectories
, true);
558 // mInput(HTMLInputElement) has no scriptGlobalObject, don't create
559 // DispatchChangeEventCallback
560 if (!mInput
->GetOwnerGlobal()) {
563 RefPtr
<DispatchChangeEventCallback
> dispatchChangeEventCallback
=
564 new DispatchChangeEventCallback(mInput
);
566 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
567 mInput
->HasAttr(nsGkAtoms::webkitdirectory
)) {
569 GetFilesHelper
* helper
= mInput
->GetOrCreateGetFilesHelper(true, error
);
570 if (NS_WARN_IF(error
.Failed())) {
571 return error
.StealNSResult();
574 helper
->AddCallback(dispatchChangeEventCallback
);
578 return dispatchChangeEventCallback
->DispatchEvents();
581 NS_IMPL_ISUPPORTS(HTMLInputElement::nsFilePickerShownCallback
,
582 nsIFilePickerShownCallback
)
584 class nsColorPickerShownCallback final
: public nsIColorPickerShownCallback
{
585 ~nsColorPickerShownCallback() = default;
588 nsColorPickerShownCallback(HTMLInputElement
* aInput
,
589 nsIColorPicker
* aColorPicker
)
590 : mInput(aInput
), mColorPicker(aColorPicker
), mValueChanged(false) {}
594 MOZ_CAN_RUN_SCRIPT_BOUNDARY
595 NS_IMETHOD
Update(const nsAString
& aColor
) override
;
596 MOZ_CAN_RUN_SCRIPT_BOUNDARY
597 NS_IMETHOD
Done(const nsAString
& aColor
) override
;
601 * Updates the internals of the object using aColor as the new value.
602 * If aTrustedUpdate is true, it will consider that aColor is a new value.
603 * Otherwise, it will check that aColor is different from the current value.
606 nsresult
UpdateInternal(const nsAString
& aColor
, bool aTrustedUpdate
);
608 RefPtr
<HTMLInputElement
> mInput
;
609 nsCOMPtr
<nsIColorPicker
> mColorPicker
;
613 nsresult
nsColorPickerShownCallback::UpdateInternal(const nsAString
& aColor
,
614 bool aTrustedUpdate
) {
615 bool valueChanged
= false;
616 nsAutoString oldValue
;
617 if (aTrustedUpdate
) {
618 mInput
->OwnerDoc()->NotifyUserGestureActivation();
621 mInput
->GetValue(oldValue
, CallerType::System
);
624 mInput
->SetValue(aColor
, CallerType::System
, IgnoreErrors());
626 if (!aTrustedUpdate
) {
627 nsAutoString newValue
;
628 mInput
->GetValue(newValue
, CallerType::System
);
629 if (!oldValue
.Equals(newValue
)) {
638 mValueChanged
= true;
639 RefPtr
<HTMLInputElement
> input(mInput
);
640 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(input
);
641 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
642 "Failed to dispatch input event");
647 nsColorPickerShownCallback::Update(const nsAString
& aColor
) {
648 return UpdateInternal(aColor
, true);
652 nsColorPickerShownCallback::Done(const nsAString
& aColor
) {
654 * When Done() is called, we might be at the end of a serie of Update() calls
655 * in which case mValueChanged is set to true and a change event will have to
656 * be fired but we might also be in a one shot Done() call situation in which
657 * case we should fire a change event iif the value actually changed.
658 * UpdateInternal(bool) is taking care of that logic for us.
662 mInput
->PickerClosed();
664 if (!aColor
.IsEmpty()) {
665 UpdateInternal(aColor
, false);
669 mInput
->SetUserInteracted(true);
670 rv
= nsContentUtils::DispatchTrustedEvent(
671 mInput
->OwnerDoc(), static_cast<Element
*>(mInput
.get()), u
"change"_ns
,
672 CanBubble::eYes
, Cancelable::eNo
);
678 NS_IMPL_ISUPPORTS(nsColorPickerShownCallback
, nsIColorPickerShownCallback
)
680 static bool IsPopupBlocked(Document
* aDoc
) {
681 if (aDoc
->ConsumeTransientUserGestureActivation()) {
685 WindowContext
* wc
= aDoc
->GetWindowContext();
686 if (wc
&& wc
->CanShowPopup()) {
690 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag
, "DOM"_ns
, aDoc
,
691 nsContentUtils::eDOM_PROPERTIES
,
692 "InputPickerBlockedNoUserActivation");
696 nsTArray
<nsString
> HTMLInputElement::GetColorsFromList() {
697 RefPtr
<HTMLDataListElement
> dataList
= GetList();
702 nsTArray
<nsString
> colors
;
704 RefPtr
<nsContentList
> options
= dataList
->Options();
705 uint32_t length
= options
->Length(true);
706 for (uint32_t i
= 0; i
< length
; ++i
) {
707 auto* option
= HTMLOptionElement::FromNodeOrNull(options
->Item(i
, false));
713 option
->GetValue(value
);
714 if (IsValidSimpleColor(value
)) {
716 colors
.AppendElement(value
);
723 nsresult
HTMLInputElement::InitColorPicker() {
724 MOZ_ASSERT(IsMutable());
726 if (mPickerRunning
) {
727 NS_WARNING("Just one nsIColorPicker is allowed");
728 return NS_ERROR_FAILURE
;
731 nsCOMPtr
<Document
> doc
= OwnerDoc();
733 nsCOMPtr
<nsPIDOMWindowOuter
> win
= doc
->GetWindow();
735 return NS_ERROR_FAILURE
;
738 if (IsPopupBlocked(doc
)) {
744 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
745 "ColorPicker", title
);
747 nsCOMPtr
<nsIColorPicker
> colorPicker
=
748 do_CreateInstance("@mozilla.org/colorpicker;1");
750 return NS_ERROR_FAILURE
;
753 nsAutoString initialValue
;
754 GetNonFileValueInternal(initialValue
);
755 nsTArray
<nsString
> colors
= GetColorsFromList();
756 nsresult rv
= colorPicker
->Init(win
, title
, initialValue
, colors
);
757 NS_ENSURE_SUCCESS(rv
, rv
);
759 nsCOMPtr
<nsIColorPickerShownCallback
> callback
=
760 new nsColorPickerShownCallback(this, colorPicker
);
762 rv
= colorPicker
->Open(callback
);
763 if (NS_SUCCEEDED(rv
)) {
764 mPickerRunning
= true;
770 nsresult
HTMLInputElement::InitFilePicker(FilePickerType aType
) {
771 MOZ_ASSERT(IsMutable());
773 if (mPickerRunning
) {
774 NS_WARNING("Just one nsIFilePicker is allowed");
775 return NS_ERROR_FAILURE
;
778 // Get parent nsPIDOMWindow object.
779 nsCOMPtr
<Document
> doc
= OwnerDoc();
781 RefPtr
<BrowsingContext
> bc
= doc
->GetBrowsingContext();
783 return NS_ERROR_FAILURE
;
786 if (IsPopupBlocked(doc
)) {
792 nsAutoString okButtonLabel
;
793 if (aType
== FILE_PICKER_DIRECTORY
) {
794 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
795 "DirectoryUpload", doc
, title
);
797 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
798 "DirectoryPickerOkButtonLabel", doc
,
801 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
802 "FileUpload", doc
, title
);
805 nsCOMPtr
<nsIFilePicker
> filePicker
=
806 do_CreateInstance("@mozilla.org/filepicker;1");
807 if (!filePicker
) return NS_ERROR_FAILURE
;
809 nsIFilePicker::Mode mode
;
811 if (aType
== FILE_PICKER_DIRECTORY
) {
812 mode
= nsIFilePicker::modeGetFolder
;
813 } else if (HasAttr(nsGkAtoms::multiple
)) {
814 mode
= nsIFilePicker::modeOpenMultiple
;
816 mode
= nsIFilePicker::modeOpen
;
819 nsresult rv
= filePicker
->Init(bc
, title
, mode
);
820 NS_ENSURE_SUCCESS(rv
, rv
);
822 if (!okButtonLabel
.IsEmpty()) {
823 filePicker
->SetOkButtonLabel(okButtonLabel
);
826 // Native directory pickers ignore file type filters, so we don't spend
827 // cycles adding them for FILE_PICKER_DIRECTORY.
828 if (HasAttr(nsGkAtoms::accept
) && aType
!= FILE_PICKER_DIRECTORY
) {
829 SetFilePickerFiltersFromAccept(filePicker
);
831 if (StaticPrefs::dom_capture_enabled()) {
832 if (const nsAttrValue
* captureVal
= GetParsedAttr(nsGkAtoms::capture
)) {
833 filePicker
->SetCapture(static_cast<nsIFilePicker::CaptureTarget
>(
834 captureVal
->GetEnumValue()));
838 filePicker
->AppendFilters(nsIFilePicker::filterAll
);
841 // Set default directory and filename
842 nsAutoString defaultName
;
844 const nsTArray
<OwningFileOrDirectory
>& oldFiles
=
845 GetFilesOrDirectoriesInternal();
847 nsCOMPtr
<nsIFilePickerShownCallback
> callback
=
848 new HTMLInputElement::nsFilePickerShownCallback(this, filePicker
);
850 if (!oldFiles
.IsEmpty() && aType
!= FILE_PICKER_DIRECTORY
) {
853 nsCOMPtr
<nsIFile
> parentFile
= LastUsedDirectory(oldFiles
[0]);
855 filePicker
->SetDisplayDirectory(parentFile
);
858 // Unfortunately nsIFilePicker doesn't allow multiple files to be
859 // default-selected, so only select something by default if exactly
860 // one file was selected before.
861 if (oldFiles
.Length() == 1) {
862 nsAutoString leafName
;
863 GetDOMFileOrDirectoryName(oldFiles
[0], leafName
);
865 if (!leafName
.IsEmpty()) {
866 filePicker
->SetDefaultString(leafName
);
870 rv
= filePicker
->Open(callback
);
871 if (NS_SUCCEEDED(rv
)) {
872 mPickerRunning
= true;
878 HTMLInputElement::gUploadLastDir
->FetchDirectoryAndDisplayPicker(
879 doc
, filePicker
, callback
);
880 mPickerRunning
= true;
884 #define CPS_PREF_NAME u"browser.upload.lastDir"_ns
886 NS_IMPL_ISUPPORTS(UploadLastDir
, nsIObserver
, nsISupportsWeakReference
)
888 void HTMLInputElement::InitUploadLastDir() {
889 gUploadLastDir
= new UploadLastDir();
890 NS_ADDREF(gUploadLastDir
);
892 nsCOMPtr
<nsIObserverService
> observerService
= services::GetObserverService();
893 if (observerService
&& gUploadLastDir
) {
894 observerService
->AddObserver(gUploadLastDir
,
895 "browser:purge-session-history", true);
899 void HTMLInputElement::DestroyUploadLastDir() { NS_IF_RELEASE(gUploadLastDir
); }
901 nsresult
UploadLastDir::FetchDirectoryAndDisplayPicker(
902 Document
* aDoc
, nsIFilePicker
* aFilePicker
,
903 nsIFilePickerShownCallback
* aFpCallback
) {
904 MOZ_ASSERT(aDoc
, "aDoc is null");
905 MOZ_ASSERT(aFilePicker
, "aFilePicker is null");
906 MOZ_ASSERT(aFpCallback
, "aFpCallback is null");
908 nsIURI
* docURI
= aDoc
->GetDocumentURI();
909 MOZ_ASSERT(docURI
, "docURI is null");
911 nsCOMPtr
<nsILoadContext
> loadContext
= aDoc
->GetLoadContext();
912 nsCOMPtr
<nsIContentPrefCallback2
> prefCallback
=
913 new UploadLastDir::ContentPrefCallback(aFilePicker
, aFpCallback
);
915 // Attempt to get the CPS, if it's not present we'll fallback to use the
917 nsCOMPtr
<nsIContentPrefService2
> contentPrefService
=
918 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID
);
919 if (!contentPrefService
) {
920 prefCallback
->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR
);
924 nsAutoCString cstrSpec
;
925 docURI
->GetSpec(cstrSpec
);
926 NS_ConvertUTF8toUTF16
spec(cstrSpec
);
928 contentPrefService
->GetByDomainAndName(spec
, CPS_PREF_NAME
, loadContext
,
933 nsresult
UploadLastDir::StoreLastUsedDirectory(Document
* aDoc
, nsIFile
* aDir
) {
934 MOZ_ASSERT(aDoc
, "aDoc is null");
939 nsCOMPtr
<nsIURI
> docURI
= aDoc
->GetDocumentURI();
940 MOZ_ASSERT(docURI
, "docURI is null");
942 // Attempt to get the CPS, if it's not present we'll just return
943 nsCOMPtr
<nsIContentPrefService2
> contentPrefService
=
944 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID
);
945 if (!contentPrefService
) return NS_ERROR_NOT_AVAILABLE
;
947 nsAutoCString cstrSpec
;
948 docURI
->GetSpec(cstrSpec
);
949 NS_ConvertUTF8toUTF16
spec(cstrSpec
);
951 // Find the parent of aFile, and store it
952 nsString unicodePath
;
953 aDir
->GetPath(unicodePath
);
954 if (unicodePath
.IsEmpty()) // nothing to do
956 RefPtr
<nsVariantCC
> prefValue
= new nsVariantCC();
957 prefValue
->SetAsAString(unicodePath
);
959 // Use the document's current load context to ensure that the content pref
960 // service doesn't persistently store this directory for this domain if the
961 // user is using private browsing:
962 nsCOMPtr
<nsILoadContext
> loadContext
= aDoc
->GetLoadContext();
963 return contentPrefService
->Set(spec
, CPS_PREF_NAME
, prefValue
, loadContext
,
968 UploadLastDir::Observe(nsISupports
* aSubject
, char const* aTopic
,
969 char16_t
const* aData
) {
970 if (strcmp(aTopic
, "browser:purge-session-history") == 0) {
971 nsCOMPtr
<nsIContentPrefService2
> contentPrefService
=
972 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID
);
973 if (contentPrefService
)
974 contentPrefService
->RemoveByName(CPS_PREF_NAME
, nullptr, nullptr);
981 static nsresult
FireEventForAccessibility(HTMLInputElement
* aTarget
,
982 EventMessage aEventMessage
);
986 // construction, destruction
989 HTMLInputElement::HTMLInputElement(already_AddRefed
<dom::NodeInfo
>&& aNodeInfo
,
990 FromParser aFromParser
, FromClone aFromClone
)
991 : TextControlElement(std::move(aNodeInfo
), aFromParser
,
992 FormControlType(kInputDefaultType
->value
)),
993 mAutocompleteAttrState(nsContentUtils::eAutocompleteAttrState_Unknown
),
994 mAutocompleteInfoState(nsContentUtils::eAutocompleteAttrState_Unknown
),
995 mDisabledChanged(false),
996 mValueChanged(false),
997 mUserInteracted(false),
998 mLastValueChangeWasInteractive(false),
999 mCheckedChanged(false),
1001 mHandlingSelectEvent(false),
1002 mShouldInitChecked(false),
1003 mDoneCreating(aFromParser
== NOT_FROM_PARSER
&&
1004 aFromClone
== FromClone::No
),
1005 mInInternalActivate(false),
1006 mCheckedIsToggled(false),
1007 mIndeterminate(false),
1008 mInhibitRestoration(aFromParser
& FROM_PARSER_FRAGMENT
),
1010 mIsDraggingRange(false),
1011 mNumberControlSpinnerIsSpinning(false),
1012 mNumberControlSpinnerSpinsUp(false),
1013 mPickerRunning(false),
1014 mIsPreviewEnabled(false),
1015 mHasBeenTypePassword(false),
1016 mHasPatternAttribute(false),
1017 mRadioGroupContainer(nullptr) {
1018 // If size is above 512, mozjemalloc allocates 1kB, see
1019 // memory/build/mozjemalloc.cpp
1020 static_assert(sizeof(HTMLInputElement
) <= 512,
1021 "Keep the size of HTMLInputElement under 512 to avoid "
1022 "performance regression!");
1024 // We are in a type=text but we create TextControlState lazily.
1025 mInputData
.mState
= nullptr;
1027 void* memory
= mInputTypeMem
;
1028 mInputType
= InputType::Create(this, mType
, memory
);
1030 if (!gUploadLastDir
) HTMLInputElement::InitUploadLastDir();
1032 // Set up our default state. By default we're enabled (since we're a control
1033 // type that can be disabled but not actually disabled right now), optional,
1034 // read-write, and valid. Also by default we don't have to show validity UI
1036 AddStatesSilently(ElementState::ENABLED
| ElementState::OPTIONAL_
|
1037 ElementState::VALID
| ElementState::VALUE_EMPTY
|
1038 ElementState::READWRITE
);
1039 RemoveStatesSilently(ElementState::READONLY
);
1040 UpdateApzAwareFlag();
1043 HTMLInputElement::~HTMLInputElement() {
1044 if (mNumberControlSpinnerIsSpinning
) {
1045 StopNumberControlSpinnerSpin(eDisallowDispatchingEvents
);
1047 nsImageLoadingContent::Destroy();
1051 void HTMLInputElement::FreeData() {
1052 if (!IsSingleLineTextControl(false)) {
1053 free(mInputData
.mValue
);
1054 mInputData
.mValue
= nullptr;
1055 } else if (mInputData
.mState
) {
1056 // XXX Passing nullptr to UnbindFromFrame doesn't do anything!
1057 UnbindFromFrame(nullptr);
1058 mInputData
.mState
->Destroy();
1059 mInputData
.mState
= nullptr;
1063 mInputType
->DropReference();
1064 mInputType
= nullptr;
1068 void HTMLInputElement::EnsureEditorState() {
1069 MOZ_ASSERT(IsSingleLineTextControl(false));
1070 if (!mInputData
.mState
) {
1071 mInputData
.mState
= TextControlState::Construct(this);
1075 TextControlState
* HTMLInputElement::GetEditorState() const {
1076 if (!IsSingleLineTextControl(false)) {
1080 // We've postponed allocating TextControlState, doing that in a const
1082 const_cast<HTMLInputElement
*>(this)->EnsureEditorState();
1084 MOZ_ASSERT(mInputData
.mState
,
1085 "Single line text controls need to have a state"
1086 " associated with them");
1088 return mInputData
.mState
;
1093 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLInputElement
)
1095 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement
,
1097 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity
)
1098 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers
)
1099 if (tmp
->IsSingleLineTextControl(false) && tmp
->mInputData
.mState
) {
1100 tmp
->mInputData
.mState
->Traverse(cb
);
1103 if (tmp
->mFileData
) {
1104 tmp
->mFileData
->Traverse(cb
);
1106 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1108 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement
,
1110 NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity
)
1111 NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers
)
1112 if (tmp
->IsSingleLineTextControl(false) && tmp
->mInputData
.mState
) {
1113 tmp
->mInputData
.mState
->Unlink();
1116 if (tmp
->mFileData
) {
1117 tmp
->mFileData
->Unlink();
1119 // XXX should unlink more?
1120 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1122 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLInputElement
,
1124 imgINotificationObserver
,
1125 nsIImageLoadingContent
,
1126 nsIConstraintValidation
)
1130 nsresult
HTMLInputElement::Clone(dom::NodeInfo
* aNodeInfo
,
1131 nsINode
** aResult
) const {
1134 RefPtr
<HTMLInputElement
> it
= new (aNodeInfo
->NodeInfoManager())
1135 HTMLInputElement(do_AddRef(aNodeInfo
), NOT_FROM_PARSER
, FromClone::Yes
);
1137 nsresult rv
= const_cast<HTMLInputElement
*>(this)->CopyInnerTo(it
);
1138 NS_ENSURE_SUCCESS(rv
, rv
);
1140 switch (GetValueMode()) {
1141 case VALUE_MODE_VALUE
:
1142 if (mValueChanged
) {
1143 // We don't have our default value anymore. Set our value on
1146 GetNonFileValueInternal(value
);
1147 // SetValueInternal handles setting the VALUE_CHANGED bit for us
1149 NS_FAILED(rv
= it
->SetValueInternal(
1150 value
, {ValueSetterOption::SetValueChanged
})))) {
1155 case VALUE_MODE_FILENAME
:
1156 if (it
->OwnerDoc()->IsStaticDocument()) {
1157 // We're going to be used in print preview. Since the doc is static
1158 // we can just grab the pretty string and use it as wallpaper
1159 GetDisplayFileName(it
->mFileData
->mStaticDocFileList
);
1161 it
->mFileData
->ClearGetFilesHelpers();
1162 it
->mFileData
->mFilesOrDirectories
.Clear();
1163 it
->mFileData
->mFilesOrDirectories
.AppendElements(
1164 mFileData
->mFilesOrDirectories
);
1167 case VALUE_MODE_DEFAULT_ON
:
1168 case VALUE_MODE_DEFAULT
:
1172 if (mCheckedChanged
) {
1173 // We no longer have our original checked state. Set our
1174 // checked state on the clone.
1175 it
->DoSetChecked(mChecked
, false, true);
1176 // Then tell DoneCreatingElement() not to overwrite:
1177 it
->mShouldInitChecked
= false;
1180 it
->mIndeterminate
= mIndeterminate
;
1182 it
->DoneCreatingElement();
1184 it
->SetLastValueChangeWasInteractive(mLastValueChangeWasInteractive
);
1189 void HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
1190 const nsAttrValue
* aValue
, bool aNotify
) {
1191 if (aNameSpaceID
== kNameSpaceID_None
) {
1192 if (aNotify
&& aName
== nsGkAtoms::disabled
) {
1193 mDisabledChanged
= true;
1196 // When name or type changes, radio should be removed from radio group.
1197 // If we are not done creating the radio, we also should not do it.
1198 if (mType
== FormControlType::InputRadio
) {
1199 if ((aName
== nsGkAtoms::name
|| (aName
== nsGkAtoms::type
&& !mForm
)) &&
1200 (mForm
|| mDoneCreating
)) {
1201 RemoveFromRadioGroup();
1202 } else if (aName
== nsGkAtoms::required
) {
1203 auto* container
= GetCurrentRadioGroupContainer();
1205 if (container
&& ((aValue
&& !HasAttr(aNameSpaceID
, aName
)) ||
1206 (!aValue
&& HasAttr(aNameSpaceID
, aName
)))) {
1208 GetAttr(nsGkAtoms::name
, name
);
1209 container
->RadioRequiredWillChange(name
, !!aValue
);
1214 if (aName
== nsGkAtoms::webkitdirectory
) {
1215 Telemetry::Accumulate(Telemetry::WEBKIT_DIRECTORY_USED
, true);
1219 return nsGenericHTMLFormControlElementWithState::BeforeSetAttr(
1220 aNameSpaceID
, aName
, aValue
, aNotify
);
1223 void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
1224 const nsAttrValue
* aValue
,
1225 const nsAttrValue
* aOldValue
,
1226 nsIPrincipal
* aSubjectPrincipal
,
1228 if (aNameSpaceID
== kNameSpaceID_None
) {
1229 bool needValidityUpdate
= false;
1230 if (aName
== nsGkAtoms::src
) {
1231 mSrcTriggeringPrincipal
= nsContentUtils::GetAttrTriggeringPrincipal(
1232 this, aValue
? aValue
->GetStringValue() : EmptyString(),
1234 if (aNotify
&& mType
== FormControlType::InputImage
) {
1236 // Mark channel as urgent-start before load image if the image load is
1237 // initiated by a user interaction.
1238 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
1240 LoadImage(aValue
->GetStringValue(), true, aNotify
,
1241 eImageLoadType_Normal
, mSrcTriggeringPrincipal
);
1243 // Null value means the attr got unset; drop the image
1244 CancelImageRequests(aNotify
);
1249 if (aName
== nsGkAtoms::value
) {
1250 // If the element has a value in value mode, the value content attribute
1251 // is the default value. So if the elements value didn't change from the
1252 // default, we have to re-set it.
1253 if (!mValueChanged
&& GetValueMode() == VALUE_MODE_VALUE
) {
1254 SetDefaultValueAsValue();
1255 } else if (GetValueMode() == VALUE_MODE_DEFAULT
&& HasDirAuto()) {
1256 SetAutoDirectionality(aNotify
);
1258 // GetStepBase() depends on the `value` attribute if `min` is not present,
1259 // even if the value doesn't change.
1260 UpdateStepMismatchValidityState();
1261 needValidityUpdate
= true;
1264 // Checked must be set no matter what type of control it is, since
1265 // mChecked must reflect the new value
1266 if (aName
== nsGkAtoms::checked
) {
1267 if (IsRadioOrCheckbox()) {
1268 SetStates(ElementState::DEFAULT
, !!aValue
, aNotify
);
1270 if (!mCheckedChanged
) {
1271 // Delay setting checked if we are creating this element (wait
1272 // until everything is set)
1273 if (!mDoneCreating
) {
1274 mShouldInitChecked
= true;
1276 DoSetChecked(!!aValue
, aNotify
, false);
1279 needValidityUpdate
= true;
1282 if (aName
== nsGkAtoms::type
) {
1283 FormControlType newType
;
1285 // We're now a text input.
1286 newType
= FormControlType(kInputDefaultType
->value
);
1288 newType
= FormControlType(aValue
->GetEnumValue());
1290 if (newType
!= mType
) {
1291 HandleTypeChange(newType
, aNotify
);
1292 needValidityUpdate
= true;
1296 // When name or type changes, radio should be added to radio group.
1297 // If we are not done creating the radio, we also should not do it.
1298 if ((aName
== nsGkAtoms::name
|| (aName
== nsGkAtoms::type
&& !mForm
)) &&
1299 mType
== FormControlType::InputRadio
&& (mForm
|| mDoneCreating
)) {
1301 UpdateValueMissingValidityStateForRadio(false);
1302 needValidityUpdate
= true;
1305 if (aName
== nsGkAtoms::required
|| aName
== nsGkAtoms::disabled
||
1306 aName
== nsGkAtoms::readonly
) {
1307 if (aName
== nsGkAtoms::disabled
) {
1308 // This *has* to be called *before* validity state check because
1309 // UpdateBarredFromConstraintValidation and
1310 // UpdateValueMissingValidityState depend on our disabled state.
1311 UpdateDisabledState(aNotify
);
1314 if (aName
== nsGkAtoms::required
&& DoesRequiredApply()) {
1315 // This *has* to be called *before* UpdateValueMissingValidityState
1316 // because UpdateValueMissingValidityState depends on our required
1318 UpdateRequiredState(!!aValue
, aNotify
);
1321 if (aName
== nsGkAtoms::readonly
&& !!aValue
!= !!aOldValue
) {
1322 UpdateReadOnlyState(aNotify
);
1325 UpdateValueMissingValidityState();
1327 // This *has* to be called *after* validity has changed.
1328 if (aName
== nsGkAtoms::readonly
|| aName
== nsGkAtoms::disabled
) {
1329 UpdateBarredFromConstraintValidation();
1331 needValidityUpdate
= true;
1332 } else if (aName
== nsGkAtoms::maxlength
) {
1333 UpdateTooLongValidityState();
1334 needValidityUpdate
= true;
1335 } else if (aName
== nsGkAtoms::minlength
) {
1336 UpdateTooShortValidityState();
1337 needValidityUpdate
= true;
1338 } else if (aName
== nsGkAtoms::pattern
) {
1339 // Although pattern attribute only applies to single line text controls,
1340 // we set this flag for all input types to save having to check the type
1342 mHasPatternAttribute
= !!aValue
;
1344 if (mDoneCreating
) {
1345 UpdatePatternMismatchValidityState();
1347 needValidityUpdate
= true;
1348 } else if (aName
== nsGkAtoms::multiple
) {
1349 UpdateTypeMismatchValidityState();
1350 needValidityUpdate
= true;
1351 } else if (aName
== nsGkAtoms::max
) {
1352 UpdateHasRange(aNotify
);
1353 mInputType
->MinMaxStepAttrChanged();
1354 // Validity state must be updated *after* the UpdateValueDueToAttrChange
1355 // call above or else the following assert will not be valid.
1356 // We don't assert the state of underflow during creation since
1357 // DoneCreatingElement sanitizes.
1358 UpdateRangeOverflowValidityState();
1359 needValidityUpdate
= true;
1360 MOZ_ASSERT(!mDoneCreating
|| mType
!= FormControlType::InputRange
||
1361 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
),
1362 "HTML5 spec does not allow underflow for type=range");
1363 } else if (aName
== nsGkAtoms::min
) {
1364 UpdateHasRange(aNotify
);
1365 mInputType
->MinMaxStepAttrChanged();
1366 // See corresponding @max comment
1367 UpdateRangeUnderflowValidityState();
1368 UpdateStepMismatchValidityState();
1369 needValidityUpdate
= true;
1370 MOZ_ASSERT(!mDoneCreating
|| mType
!= FormControlType::InputRange
||
1371 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
),
1372 "HTML5 spec does not allow underflow for type=range");
1373 } else if (aName
== nsGkAtoms::step
) {
1374 mInputType
->MinMaxStepAttrChanged();
1375 // See corresponding @max comment
1376 UpdateStepMismatchValidityState();
1377 needValidityUpdate
= true;
1378 MOZ_ASSERT(!mDoneCreating
|| mType
!= FormControlType::InputRange
||
1379 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
),
1380 "HTML5 spec does not allow underflow for type=range");
1381 } else if (aName
== nsGkAtoms::dir
&& aValue
&&
1382 aValue
->Equals(nsGkAtoms::_auto
, eIgnoreCase
)) {
1383 SetAutoDirectionality(aNotify
);
1384 } else if (aName
== nsGkAtoms::lang
) {
1385 // FIXME(emilio, bug 1651070): This doesn't account for lang changes on
1387 if (mType
== FormControlType::InputNumber
) {
1388 // The validity of our value may have changed based on the locale.
1389 UpdateValidityState();
1390 needValidityUpdate
= true;
1392 } else if (aName
== nsGkAtoms::autocomplete
) {
1393 // Clear the cached @autocomplete attribute and autocompleteInfo state.
1394 mAutocompleteAttrState
= nsContentUtils::eAutocompleteAttrState_Unknown
;
1395 mAutocompleteInfoState
= nsContentUtils::eAutocompleteAttrState_Unknown
;
1396 } else if (aName
== nsGkAtoms::placeholder
) {
1397 // Full addition / removals of the attribute reconstruct right now.
1398 if (nsTextControlFrame
* f
= do_QueryFrame(GetPrimaryFrame())) {
1399 f
->PlaceholderChanged(aOldValue
, aValue
);
1401 UpdatePlaceholderShownState();
1402 needValidityUpdate
= true;
1405 if (CreatesDateTimeWidget()) {
1406 if (aName
== nsGkAtoms::value
|| aName
== nsGkAtoms::readonly
||
1407 aName
== nsGkAtoms::tabindex
|| aName
== nsGkAtoms::required
||
1408 aName
== nsGkAtoms::disabled
) {
1409 // If original target is this and not the inner text control, we should
1410 // pass the focus to the inner text control.
1411 if (Element
* dateTimeBoxElement
= GetDateTimeBoxElement()) {
1412 AsyncEventDispatcher::RunDOMEventWhenSafe(
1413 *dateTimeBoxElement
,
1414 aName
== nsGkAtoms::value
? u
"MozDateTimeValueChanged"_ns
1415 : u
"MozDateTimeAttributeChanged"_ns
,
1416 CanBubble::eNo
, ChromeOnlyDispatch::eNo
);
1420 if (needValidityUpdate
) {
1421 UpdateValidityElementStates(aNotify
);
1425 return nsGenericHTMLFormControlElementWithState::AfterSetAttr(
1426 aNameSpaceID
, aName
, aValue
, aOldValue
, aSubjectPrincipal
, aNotify
);
1429 void HTMLInputElement::BeforeSetForm(HTMLFormElement
* aForm
, bool aBindToTree
) {
1430 // No need to remove from radio group if we are just binding to tree.
1431 if (mType
== FormControlType::InputRadio
&& !aBindToTree
) {
1432 RemoveFromRadioGroup();
1435 // Dispatch event when <input> @form is set
1437 MaybeDispatchLoginManagerEvents(aForm
);
1441 void HTMLInputElement::AfterClearForm(bool aUnbindOrDelete
) {
1444 // Do not add back to radio group if we are releasing or unbinding from tree.
1445 if (mType
== FormControlType::InputRadio
&& !aUnbindOrDelete
&&
1446 !GetCurrentRadioGroupContainer()) {
1448 UpdateValueMissingValidityStateForRadio(false);
1452 void HTMLInputElement::ResultForDialogSubmit(nsAString
& aResult
) {
1453 if (mType
== FormControlType::InputImage
) {
1454 // Get a property set by the frame to find out where it was clicked.
1455 const auto* lastClickedPoint
=
1456 static_cast<CSSIntPoint
*>(GetProperty(nsGkAtoms::imageClickedPoint
));
1458 if (lastClickedPoint
) {
1459 x
= lastClickedPoint
->x
;
1460 y
= lastClickedPoint
->y
;
1464 aResult
.AppendInt(x
);
1465 aResult
.AppendLiteral(",");
1466 aResult
.AppendInt(y
);
1468 GetAttr(nsGkAtoms::value
, aResult
);
1472 void HTMLInputElement::GetAutocomplete(nsAString
& aValue
) {
1473 if (!DoesAutocompleteApply()) {
1478 const nsAttrValue
* attributeVal
= GetParsedAttr(nsGkAtoms::autocomplete
);
1480 mAutocompleteAttrState
= nsContentUtils::SerializeAutocompleteAttribute(
1481 attributeVal
, aValue
, mAutocompleteAttrState
);
1484 void HTMLInputElement::GetAutocompleteInfo(Nullable
<AutocompleteInfo
>& aInfo
) {
1485 if (!DoesAutocompleteApply()) {
1490 const nsAttrValue
* attributeVal
= GetParsedAttr(nsGkAtoms::autocomplete
);
1491 mAutocompleteInfoState
= nsContentUtils::SerializeAutocompleteAttribute(
1492 attributeVal
, aInfo
.SetValue(), mAutocompleteInfoState
, true);
1495 void HTMLInputElement::GetCapture(nsAString
& aValue
) {
1496 GetEnumAttr(nsGkAtoms::capture
, kCaptureDefault
->tag
, aValue
);
1499 void HTMLInputElement::GetFormEnctype(nsAString
& aValue
) {
1500 GetEnumAttr(nsGkAtoms::formenctype
, "", kFormDefaultEnctype
->tag
, aValue
);
1503 void HTMLInputElement::GetFormMethod(nsAString
& aValue
) {
1504 GetEnumAttr(nsGkAtoms::formmethod
, "", kFormDefaultMethod
->tag
, aValue
);
1507 void HTMLInputElement::GetType(nsAString
& aValue
) const {
1508 GetEnumAttr(nsGkAtoms::type
, kInputDefaultType
->tag
, aValue
);
1511 int32_t HTMLInputElement::TabIndexDefault() { return 0; }
1513 uint32_t HTMLInputElement::Height() {
1514 if (mType
!= FormControlType::InputImage
) {
1517 return GetWidthHeightForImage().height
;
1520 void HTMLInputElement::SetIndeterminateInternal(bool aValue
,
1521 bool aShouldInvalidate
) {
1522 mIndeterminate
= aValue
;
1523 if (mType
!= FormControlType::InputCheckbox
) {
1527 SetStates(ElementState::INDETERMINATE
, aValue
);
1529 if (aShouldInvalidate
) {
1530 // Repaint the frame
1531 if (nsIFrame
* frame
= GetPrimaryFrame()) {
1532 frame
->InvalidateFrameSubtree();
1537 void HTMLInputElement::SetIndeterminate(bool aValue
) {
1538 SetIndeterminateInternal(aValue
, true);
1541 uint32_t HTMLInputElement::Width() {
1542 if (mType
!= FormControlType::InputImage
) {
1545 return GetWidthHeightForImage().width
;
1548 bool HTMLInputElement::SanitizesOnValueGetter() const {
1549 // Don't return non-sanitized value for datetime types, email, or number.
1550 return mType
== FormControlType::InputEmail
||
1551 mType
== FormControlType::InputNumber
|| IsDateTimeInputType(mType
);
1554 void HTMLInputElement::GetValue(nsAString
& aValue
, CallerType aCallerType
) {
1555 GetValueInternal(aValue
, aCallerType
);
1557 // In the case where we need to sanitize an input value without affecting
1558 // the displayed user's input, we instead sanitize only on .value accesses.
1559 // For the more general case of input elements displaying text that isn't
1560 // their current value, see bug 805049.
1561 if (SanitizesOnValueGetter()) {
1562 SanitizeValue(aValue
, SanitizationKind::ForValueGetter
);
1566 void HTMLInputElement::GetValueInternal(nsAString
& aValue
,
1567 CallerType aCallerType
) const {
1568 if (mType
!= FormControlType::InputFile
) {
1569 GetNonFileValueInternal(aValue
);
1573 if (aCallerType
== CallerType::System
) {
1574 aValue
.Assign(mFileData
->mFirstFilePath
);
1578 if (mFileData
->mFilesOrDirectories
.IsEmpty()) {
1584 GetDOMFileOrDirectoryName(mFileData
->mFilesOrDirectories
[0], file
);
1585 if (file
.IsEmpty()) {
1590 aValue
.AssignLiteral("C:\\fakepath\\");
1591 aValue
.Append(file
);
1594 void HTMLInputElement::GetNonFileValueInternal(nsAString
& aValue
) const {
1595 switch (GetValueMode()) {
1596 case VALUE_MODE_VALUE
:
1597 if (IsSingleLineTextControl(false)) {
1598 if (mInputData
.mState
) {
1599 mInputData
.mState
->GetValue(aValue
, true, /* aForDisplay = */ false);
1601 // Value hasn't been set yet.
1604 } else if (!aValue
.Assign(mInputData
.mValue
, fallible
)) {
1609 case VALUE_MODE_FILENAME
:
1610 MOZ_ASSERT_UNREACHABLE("Someone screwed up here");
1611 // We'll just return empty string if someone does screw up.
1615 case VALUE_MODE_DEFAULT
:
1616 // Treat defaultValue as value.
1617 GetAttr(nsGkAtoms::value
, aValue
);
1620 case VALUE_MODE_DEFAULT_ON
:
1621 // Treat default value as value and returns "on" if no value.
1622 if (!GetAttr(nsGkAtoms::value
, aValue
)) {
1623 aValue
.AssignLiteral("on");
1629 void HTMLInputElement::ClearFiles(bool aSetValueChanged
) {
1630 nsTArray
<OwningFileOrDirectory
> data
;
1631 SetFilesOrDirectories(data
, aSetValueChanged
);
1634 int32_t HTMLInputElement::MonthsSinceJan1970(uint32_t aYear
,
1635 uint32_t aMonth
) const {
1636 return (aYear
- 1970) * 12 + aMonth
- 1;
1640 Decimal
HTMLInputElement::StringToDecimal(const nsAString
& aValue
) {
1641 if (!IsAscii(aValue
)) {
1642 return Decimal::nan();
1644 NS_LossyConvertUTF16toASCII
asciiString(aValue
);
1645 std::string
stdString(asciiString
.get(), asciiString
.Length());
1646 auto decimal
= Decimal::fromString(stdString
);
1647 if (!decimal
.isFinite()) {
1648 return Decimal::nan();
1650 // Numbers are considered finite IEEE 754 Double-precision floating point
1651 // values, but decimal supports a bigger range.
1652 static const Decimal maxDouble
=
1653 Decimal::fromDouble(std::numeric_limits
<double>::max());
1654 if (decimal
< -maxDouble
|| decimal
> maxDouble
) {
1655 return Decimal::nan();
1660 Decimal
HTMLInputElement::GetValueAsDecimal() const {
1661 nsAutoString stringValue
;
1662 GetNonFileValueInternal(stringValue
);
1663 Decimal result
= mInputType
->ConvertStringToNumber(stringValue
).mResult
;
1664 return result
.isFinite() ? result
: Decimal::nan();
1667 void HTMLInputElement::SetValue(const nsAString
& aValue
, CallerType aCallerType
,
1669 // check security. Note that setting the value to the empty string is always
1670 // OK and gives pages a way to clear a file input if necessary.
1671 if (mType
== FormControlType::InputFile
) {
1672 if (!aValue
.IsEmpty()) {
1673 if (aCallerType
!= CallerType::System
) {
1674 // setting the value of a "FILE" input widget requires
1676 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1679 Sequence
<nsString
> list
;
1680 if (!list
.AppendElement(aValue
, fallible
)) {
1681 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
1685 MozSetFileNameArray(list
, aRv
);
1690 if (MayFireChangeOnBlur()) {
1691 // If the value has been set by a script, we basically want to keep the
1692 // current change event state. If the element is ready to fire a change
1693 // event, we should keep it that way. Otherwise, we should make sure the
1694 // element will not fire any event because of the script interaction.
1696 // NOTE: this is currently quite expensive work (too much string
1697 // manipulation). We should probably optimize that.
1698 nsAutoString currentValue
;
1699 GetNonFileValueInternal(currentValue
);
1701 nsresult rv
= SetValueInternal(
1702 aValue
, ¤tValue
,
1703 {ValueSetterOption::ByContentAPI
, ValueSetterOption::SetValueChanged
,
1704 ValueSetterOption::MoveCursorToEndIfValueChanged
});
1705 if (NS_FAILED(rv
)) {
1710 if (mFocusedValue
.Equals(currentValue
)) {
1711 GetValue(mFocusedValue
, aCallerType
);
1714 nsresult rv
= SetValueInternal(
1716 {ValueSetterOption::ByContentAPI
, ValueSetterOption::SetValueChanged
,
1717 ValueSetterOption::MoveCursorToEndIfValueChanged
});
1718 if (NS_FAILED(rv
)) {
1726 HTMLDataListElement
* HTMLInputElement::GetList() const {
1727 nsAutoString dataListId
;
1728 GetAttr(nsGkAtoms::list_
, dataListId
);
1729 if (dataListId
.IsEmpty()) {
1733 DocumentOrShadowRoot
* docOrShadow
= GetUncomposedDocOrConnectedShadowRoot();
1738 return HTMLDataListElement::FromNodeOrNull(
1739 docOrShadow
->GetElementById(dataListId
));
1742 void HTMLInputElement::SetValue(Decimal aValue
, CallerType aCallerType
) {
1743 MOZ_ASSERT(!aValue
.isInfinity(), "aValue must not be Infinity!");
1745 if (aValue
.isNaN()) {
1746 SetValue(u
""_ns
, aCallerType
, IgnoreErrors());
1751 mInputType
->ConvertNumberToString(aValue
, value
);
1752 SetValue(value
, aCallerType
, IgnoreErrors());
1755 void HTMLInputElement::GetValueAsDate(JSContext
* aCx
,
1756 JS::MutableHandle
<JSObject
*> aObject
,
1758 aObject
.set(nullptr);
1759 if (!IsDateTimeInputType(mType
)) {
1763 Maybe
<JS::ClippedTime
> time
;
1766 case FormControlType::InputDate
: {
1767 uint32_t year
, month
, day
;
1769 GetNonFileValueInternal(value
);
1770 if (!ParseDate(value
, &year
, &month
, &day
)) {
1774 time
.emplace(JS::TimeClip(JS::MakeDate(year
, month
- 1, day
)));
1777 case FormControlType::InputTime
: {
1778 uint32_t millisecond
;
1780 GetNonFileValueInternal(value
);
1781 if (!ParseTime(value
, &millisecond
)) {
1785 time
.emplace(JS::TimeClip(millisecond
));
1786 MOZ_ASSERT(time
->toDouble() == millisecond
,
1787 "HTML times are restricted to the day after the epoch and "
1791 case FormControlType::InputMonth
: {
1792 uint32_t year
, month
;
1794 GetNonFileValueInternal(value
);
1795 if (!ParseMonth(value
, &year
, &month
)) {
1799 time
.emplace(JS::TimeClip(JS::MakeDate(year
, month
- 1, 1)));
1802 case FormControlType::InputWeek
: {
1803 uint32_t year
, week
;
1805 GetNonFileValueInternal(value
);
1806 if (!ParseWeek(value
, &year
, &week
)) {
1810 double days
= DaysSinceEpochFromWeek(year
, week
);
1811 time
.emplace(JS::TimeClip(days
* kMsPerDay
));
1815 case FormControlType::InputDatetimeLocal
: {
1816 uint32_t year
, month
, day
, timeInMs
;
1818 GetNonFileValueInternal(value
);
1819 if (!ParseDateTimeLocal(value
, &year
, &month
, &day
, &timeInMs
)) {
1823 time
.emplace(JS::TimeClip(JS::MakeDate(year
, month
- 1, day
, timeInMs
)));
1831 aObject
.set(JS::NewDateObject(aCx
, *time
));
1833 aRv
.NoteJSContextException(aCx
);
1838 MOZ_ASSERT(false, "Unrecognized input type");
1839 aRv
.Throw(NS_ERROR_UNEXPECTED
);
1842 void HTMLInputElement::SetValueAsDate(JSContext
* aCx
,
1843 JS::Handle
<JSObject
*> aObj
,
1845 if (!IsDateTimeInputType(mType
)) {
1846 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1852 if (!JS::ObjectIsDate(aCx
, aObj
, &isDate
)) {
1853 aRv
.NoteJSContextException(aCx
);
1857 aRv
.ThrowTypeError("Value being assigned is not a date.");
1862 double milliseconds
;
1864 if (!js::DateGetMsecSinceEpoch(aCx
, aObj
, &milliseconds
)) {
1865 aRv
.NoteJSContextException(aCx
);
1869 milliseconds
= UnspecifiedNaN
<double>();
1872 // At this point we know we're not a file input, so we can just pass "not
1873 // system" as the caller type, since the caller type only matters in the file
1875 if (std::isnan(milliseconds
)) {
1876 SetValue(u
""_ns
, CallerType::NonSystem
, aRv
);
1880 if (mType
!= FormControlType::InputMonth
) {
1881 SetValue(Decimal::fromDouble(milliseconds
), CallerType::NonSystem
);
1885 // type=month expects the value to be number of months.
1886 double year
= JS::YearFromTime(milliseconds
);
1887 double month
= JS::MonthFromTime(milliseconds
);
1889 if (std::isnan(year
) || std::isnan(month
)) {
1890 SetValue(u
""_ns
, CallerType::NonSystem
, aRv
);
1894 int32_t months
= MonthsSinceJan1970(year
, month
+ 1);
1895 SetValue(Decimal(int32_t(months
)), CallerType::NonSystem
);
1898 void HTMLInputElement::SetValueAsNumber(double aValueAsNumber
,
1900 // TODO: return TypeError when HTMLInputElement is converted to WebIDL, see
1902 if (std::isinf(aValueAsNumber
)) {
1903 aRv
.Throw(NS_ERROR_INVALID_ARG
);
1907 if (!DoesValueAsNumberApply()) {
1908 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
1912 // At this point we know we're not a file input, so we can just pass "not
1913 // system" as the caller type, since the caller type only matters in the file
1915 SetValue(Decimal::fromDouble(aValueAsNumber
), CallerType::NonSystem
);
1918 Decimal
HTMLInputElement::GetMinimum() const {
1920 DoesValueAsNumberApply(),
1921 "GetMinimum() should only be used for types that allow .valueAsNumber");
1923 // Only type=range has a default minimum
1924 Decimal defaultMinimum
=
1925 mType
== FormControlType::InputRange
? Decimal(0) : Decimal::nan();
1927 if (!HasAttr(nsGkAtoms::min
)) {
1928 return defaultMinimum
;
1931 nsAutoString minStr
;
1932 GetAttr(nsGkAtoms::min
, minStr
);
1934 Decimal min
= mInputType
->ConvertStringToNumber(minStr
).mResult
;
1935 return min
.isFinite() ? min
: defaultMinimum
;
1938 Decimal
HTMLInputElement::GetMaximum() const {
1940 DoesValueAsNumberApply(),
1941 "GetMaximum() should only be used for types that allow .valueAsNumber");
1943 // Only type=range has a default maximum
1944 Decimal defaultMaximum
=
1945 mType
== FormControlType::InputRange
? Decimal(100) : Decimal::nan();
1947 if (!HasAttr(nsGkAtoms::max
)) {
1948 return defaultMaximum
;
1951 nsAutoString maxStr
;
1952 GetAttr(nsGkAtoms::max
, maxStr
);
1954 Decimal max
= mInputType
->ConvertStringToNumber(maxStr
).mResult
;
1955 return max
.isFinite() ? max
: defaultMaximum
;
1958 Decimal
HTMLInputElement::GetStepBase() const {
1959 MOZ_ASSERT(IsDateTimeInputType(mType
) ||
1960 mType
== FormControlType::InputNumber
||
1961 mType
== FormControlType::InputRange
,
1962 "Check that kDefaultStepBase is correct for this new type");
1963 // Do NOT use GetMinimum here - the spec says to use "the min content
1964 // attribute", not "the minimum".
1965 nsAutoString minStr
;
1966 if (GetAttr(nsGkAtoms::min
, minStr
)) {
1967 Decimal min
= mInputType
->ConvertStringToNumber(minStr
).mResult
;
1968 if (min
.isFinite()) {
1973 // If @min is not a double, we should use @value.
1974 nsAutoString valueStr
;
1975 if (GetAttr(nsGkAtoms::value
, valueStr
)) {
1976 Decimal value
= mInputType
->ConvertStringToNumber(valueStr
).mResult
;
1977 if (value
.isFinite()) {
1982 if (mType
== FormControlType::InputWeek
) {
1983 return kDefaultStepBaseWeek
;
1986 return kDefaultStepBase
;
1989 nsresult
HTMLInputElement::GetValueIfStepped(int32_t aStep
,
1990 StepCallerType aCallerType
,
1991 Decimal
* aNextStep
) {
1992 if (!DoStepDownStepUpApply()) {
1993 return NS_ERROR_DOM_INVALID_STATE_ERR
;
1996 Decimal stepBase
= GetStepBase();
1997 Decimal step
= GetStep();
1998 if (step
== kStepAny
) {
1999 if (aCallerType
!= CALLED_FOR_USER_EVENT
) {
2000 return NS_ERROR_DOM_INVALID_STATE_ERR
;
2002 // Allow the spin buttons and up/down arrow keys to do something sensible:
2003 step
= GetDefaultStep();
2006 Decimal minimum
= GetMinimum();
2007 Decimal maximum
= GetMaximum();
2009 if (!maximum
.isNaN()) {
2010 // "max - (max - stepBase) % step" is the nearest valid value to max.
2011 maximum
= maximum
- NS_floorModulo(maximum
- stepBase
, step
);
2012 if (!minimum
.isNaN()) {
2013 if (minimum
> maximum
) {
2014 // Either the minimum was greater than the maximum prior to our
2015 // adjustment to align maximum on a step, or else (if we adjusted
2016 // maximum) there is no valid step between minimum and the unadjusted
2023 Decimal value
= GetValueAsDecimal();
2024 bool valueWasNaN
= false;
2025 if (value
.isNaN()) {
2029 Decimal valueBeforeStepping
= value
;
2031 Decimal deltaFromStep
= NS_floorModulo(value
- stepBase
, step
);
2033 if (deltaFromStep
!= Decimal(0)) {
2035 value
+= step
- deltaFromStep
; // partial step
2036 value
+= step
* Decimal(aStep
- 1); // then remaining steps
2037 } else if (aStep
< 0) {
2038 value
-= deltaFromStep
; // partial step
2039 value
+= step
* Decimal(aStep
+ 1); // then remaining steps
2042 value
+= step
* Decimal(aStep
);
2045 if (value
< minimum
) {
2047 deltaFromStep
= NS_floorModulo(value
- stepBase
, step
);
2048 if (deltaFromStep
!= Decimal(0)) {
2049 value
+= step
- deltaFromStep
;
2052 if (value
> maximum
) {
2054 deltaFromStep
= NS_floorModulo(value
- stepBase
, step
);
2055 if (deltaFromStep
!= Decimal(0)) {
2056 value
-= deltaFromStep
;
2060 if (!valueWasNaN
&& // value="", resulting in us using "0"
2061 ((aStep
> 0 && value
< valueBeforeStepping
) ||
2062 (aStep
< 0 && value
> valueBeforeStepping
))) {
2063 // We don't want step-up to effectively step down, or step-down to
2064 // effectively step up, so return;
2072 nsresult
HTMLInputElement::ApplyStep(int32_t aStep
) {
2073 Decimal nextStep
= Decimal::nan(); // unchanged if value will not change
2075 nsresult rv
= GetValueIfStepped(aStep
, CALLED_FOR_SCRIPT
, &nextStep
);
2077 if (NS_SUCCEEDED(rv
) && nextStep
.isFinite()) {
2078 // We know we're not a file input, so the caller type does not matter; just
2079 // pass "not system" to be safe.
2080 SetValue(nextStep
, CallerType::NonSystem
);
2086 bool HTMLInputElement::IsDateTimeInputType(FormControlType aType
) {
2088 case FormControlType::InputDate
:
2089 case FormControlType::InputTime
:
2090 case FormControlType::InputMonth
:
2091 case FormControlType::InputWeek
:
2092 case FormControlType::InputDatetimeLocal
:
2099 void HTMLInputElement::MozGetFileNameArray(nsTArray
<nsString
>& aArray
,
2101 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2105 const nsTArray
<OwningFileOrDirectory
>& filesOrDirs
=
2106 GetFilesOrDirectoriesInternal();
2107 for (uint32_t i
= 0; i
< filesOrDirs
.Length(); i
++) {
2109 GetDOMFileOrDirectoryPath(filesOrDirs
[i
], str
, aRv
);
2110 if (NS_WARN_IF(aRv
.Failed())) {
2114 aArray
.AppendElement(str
);
2118 void HTMLInputElement::MozSetFileArray(
2119 const Sequence
<OwningNonNull
<File
>>& aFiles
) {
2120 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2124 nsCOMPtr
<nsIGlobalObject
> global
= OwnerDoc()->GetScopeObject();
2130 nsTArray
<OwningFileOrDirectory
> files
;
2131 for (uint32_t i
= 0; i
< aFiles
.Length(); ++i
) {
2132 RefPtr
<File
> file
= File::Create(global
, aFiles
[i
].get()->Impl());
2133 if (NS_WARN_IF(!file
)) {
2137 OwningFileOrDirectory
* element
= files
.AppendElement();
2138 element
->SetAsFile() = file
;
2141 SetFilesOrDirectories(files
, true);
2144 void HTMLInputElement::MozSetFileNameArray(const Sequence
<nsString
>& aFileNames
,
2146 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2150 if (XRE_IsContentProcess()) {
2151 aRv
.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
2155 nsTArray
<OwningFileOrDirectory
> files
;
2156 for (uint32_t i
= 0; i
< aFileNames
.Length(); ++i
) {
2157 nsCOMPtr
<nsIFile
> file
;
2159 if (StringBeginsWith(aFileNames
[i
], u
"file:"_ns
,
2160 nsASCIICaseInsensitiveStringComparator
)) {
2161 // Converts the URL string into the corresponding nsIFile if possible
2162 // A local file will be created if the URL string begins with file://
2163 NS_GetFileFromURLSpec(NS_ConvertUTF16toUTF8(aFileNames
[i
]),
2164 getter_AddRefs(file
));
2168 // this is no "file://", try as local file
2169 NS_NewLocalFile(aFileNames
[i
], false, getter_AddRefs(file
));
2173 continue; // Not much we can do if the file doesn't exist
2176 nsCOMPtr
<nsIGlobalObject
> global
= OwnerDoc()->GetScopeObject();
2178 aRv
.Throw(NS_ERROR_FAILURE
);
2182 RefPtr
<File
> domFile
= File::CreateFromFile(global
, file
);
2183 if (NS_WARN_IF(!domFile
)) {
2184 aRv
.Throw(NS_ERROR_FAILURE
);
2188 OwningFileOrDirectory
* element
= files
.AppendElement();
2189 element
->SetAsFile() = domFile
;
2192 SetFilesOrDirectories(files
, true);
2195 void HTMLInputElement::MozSetDirectory(const nsAString
& aDirectoryPath
,
2197 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2201 nsCOMPtr
<nsIFile
> file
;
2202 aRv
= NS_NewLocalFile(aDirectoryPath
, true, getter_AddRefs(file
));
2203 if (NS_WARN_IF(aRv
.Failed())) {
2207 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
2208 if (NS_WARN_IF(!window
)) {
2209 aRv
.Throw(NS_ERROR_FAILURE
);
2213 RefPtr
<Directory
> directory
= Directory::Create(window
->AsGlobal(), file
);
2214 MOZ_ASSERT(directory
);
2216 nsTArray
<OwningFileOrDirectory
> array
;
2217 OwningFileOrDirectory
* element
= array
.AppendElement();
2218 element
->SetAsDirectory() = directory
;
2220 SetFilesOrDirectories(array
, true);
2223 void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue
& aValue
) {
2224 if (NS_WARN_IF(!IsDateTimeInputType(mType
)) || !mDateTimeInputBoxValue
) {
2228 aValue
= *mDateTimeInputBoxValue
;
2231 Element
* HTMLInputElement::GetDateTimeBoxElement() {
2232 if (!GetShadowRoot()) {
2236 // The datetimebox <div> is the only child of the UA Widget Shadow Root
2237 // if it is present.
2238 MOZ_ASSERT(GetShadowRoot()->IsUAWidget());
2239 MOZ_ASSERT(1 >= GetShadowRoot()->GetChildCount());
2240 if (nsIContent
* inputAreaContent
= GetShadowRoot()->GetFirstChild()) {
2241 return inputAreaContent
->AsElement();
2247 void HTMLInputElement::OpenDateTimePicker(const DateTimeValue
& aInitialValue
) {
2248 if (NS_WARN_IF(!IsDateTimeInputType(mType
))) {
2252 mDateTimeInputBoxValue
= MakeUnique
<DateTimeValue
>(aInitialValue
);
2253 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element
*>(this),
2254 u
"MozOpenDateTimePicker"_ns
,
2255 CanBubble::eYes
, Cancelable::eYes
);
2258 void HTMLInputElement::UpdateDateTimePicker(const DateTimeValue
& aValue
) {
2259 if (NS_WARN_IF(!IsDateTimeInputType(mType
))) {
2263 mDateTimeInputBoxValue
= MakeUnique
<DateTimeValue
>(aValue
);
2264 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element
*>(this),
2265 u
"MozUpdateDateTimePicker"_ns
,
2266 CanBubble::eYes
, Cancelable::eYes
);
2269 void HTMLInputElement::CloseDateTimePicker() {
2270 if (NS_WARN_IF(!IsDateTimeInputType(mType
))) {
2274 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element
*>(this),
2275 u
"MozCloseDateTimePicker"_ns
,
2276 CanBubble::eYes
, Cancelable::eYes
);
2279 void HTMLInputElement::SetFocusState(bool aIsFocused
) {
2280 if (NS_WARN_IF(!IsDateTimeInputType(mType
))) {
2283 SetStates(ElementState::FOCUS
| ElementState::FOCUSRING
, aIsFocused
);
2286 void HTMLInputElement::UpdateValidityState() {
2287 if (NS_WARN_IF(!IsDateTimeInputType(mType
))) {
2291 // For now, datetime input box call this function only when the value may
2292 // become valid/invalid. For other validity states, they will be updated when
2293 // .value is actually changed.
2294 UpdateBadInputValidityState();
2295 UpdateValidityElementStates(true);
2298 bool HTMLInputElement::MozIsTextField(bool aExcludePassword
) {
2299 // TODO: temporary until bug 888320 is fixed.
2301 // FIXME: Historically we never returned true for `number`, we should consider
2302 // changing that now that it is similar to other inputs.
2303 if (IsDateTimeInputType(mType
) || mType
== FormControlType::InputNumber
) {
2307 return IsSingleLineTextControl(aExcludePassword
);
2310 void HTMLInputElement::SetUserInput(const nsAString
& aValue
,
2311 nsIPrincipal
& aSubjectPrincipal
) {
2312 AutoHandlingUserInputStatePusher
inputStatePusher(true);
2314 if (mType
== FormControlType::InputFile
&&
2315 !aSubjectPrincipal
.IsSystemPrincipal()) {
2319 if (mType
== FormControlType::InputFile
) {
2320 Sequence
<nsString
> list
;
2321 if (!list
.AppendElement(aValue
, fallible
)) {
2325 MozSetFileNameArray(list
, IgnoreErrors());
2329 bool isInputEventDispatchedByTextControlState
=
2330 GetValueMode() == VALUE_MODE_VALUE
&& IsSingleLineTextControl(false);
2332 nsresult rv
= SetValueInternal(
2334 {ValueSetterOption::BySetUserInputAPI
, ValueSetterOption::SetValueChanged
,
2335 ValueSetterOption::MoveCursorToEndIfValueChanged
});
2336 NS_ENSURE_SUCCESS_VOID(rv
);
2338 if (!isInputEventDispatchedByTextControlState
) {
2339 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(this);
2340 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
2341 "Failed to dispatch input event");
2344 // If this element is not currently focused, it won't receive a change event
2345 // for this update through the normal channels. So fire a change event
2346 // immediately, instead.
2347 if (CreatesDateTimeWidget() || !ShouldBlur(this)) {
2348 FireChangeEventIfNeeded();
2352 nsIEditor
* HTMLInputElement::GetEditorForBindings() {
2353 if (!GetPrimaryFrame()) {
2354 // Ensure we construct frames (and thus an editor) if needed.
2355 GetPrimaryFrame(FlushType::Frames
);
2357 return GetTextEditorFromState();
2360 bool HTMLInputElement::HasEditor() const {
2361 return !!GetTextEditorWithoutCreation();
2364 TextEditor
* HTMLInputElement::GetTextEditorFromState() {
2365 TextControlState
* state
= GetEditorState();
2367 return state
->GetTextEditor();
2372 TextEditor
* HTMLInputElement::GetTextEditor() {
2373 return GetTextEditorFromState();
2376 TextEditor
* HTMLInputElement::GetTextEditorWithoutCreation() const {
2377 TextControlState
* state
= GetEditorState();
2381 return state
->GetTextEditorWithoutCreation();
2384 nsISelectionController
* HTMLInputElement::GetSelectionController() {
2385 TextControlState
* state
= GetEditorState();
2387 return state
->GetSelectionController();
2392 nsFrameSelection
* HTMLInputElement::GetConstFrameSelection() {
2393 TextControlState
* state
= GetEditorState();
2395 return state
->GetConstFrameSelection();
2400 nsresult
HTMLInputElement::BindToFrame(nsTextControlFrame
* aFrame
) {
2401 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
2402 TextControlState
* state
= GetEditorState();
2404 return state
->BindToFrame(aFrame
);
2406 return NS_ERROR_FAILURE
;
2409 void HTMLInputElement::UnbindFromFrame(nsTextControlFrame
* aFrame
) {
2410 TextControlState
* state
= GetEditorState();
2411 if (state
&& aFrame
) {
2412 state
->UnbindFromFrame(aFrame
);
2416 nsresult
HTMLInputElement::CreateEditor() {
2417 TextControlState
* state
= GetEditorState();
2419 return state
->PrepareEditor();
2421 return NS_ERROR_FAILURE
;
2424 void HTMLInputElement::SetPreviewValue(const nsAString
& aValue
) {
2425 TextControlState
* state
= GetEditorState();
2427 state
->SetPreviewText(aValue
, true);
2431 void HTMLInputElement::GetPreviewValue(nsAString
& aValue
) {
2432 TextControlState
* state
= GetEditorState();
2434 state
->GetPreviewText(aValue
);
2438 void HTMLInputElement::EnablePreview() {
2439 if (mIsPreviewEnabled
) {
2443 mIsPreviewEnabled
= true;
2444 // Reconstruct the frame to append an anonymous preview node
2445 nsLayoutUtils::PostRestyleEvent(this, RestyleHint
{0},
2446 nsChangeHint_ReconstructFrame
);
2449 bool HTMLInputElement::IsPreviewEnabled() { return mIsPreviewEnabled
; }
2451 void HTMLInputElement::GetDisplayFileName(nsAString
& aValue
) const {
2452 MOZ_ASSERT(mFileData
);
2454 if (OwnerDoc()->IsStaticDocument()) {
2455 aValue
= mFileData
->mStaticDocFileList
;
2459 if (mFileData
->mFilesOrDirectories
.Length() == 1) {
2460 GetDOMFileOrDirectoryName(mFileData
->mFilesOrDirectories
[0], aValue
);
2466 if (mFileData
->mFilesOrDirectories
.IsEmpty()) {
2467 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
2468 HasAttr(nsGkAtoms::webkitdirectory
)) {
2469 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
2470 "NoDirSelected", OwnerDoc(),
2472 } else if (HasAttr(nsGkAtoms::multiple
)) {
2473 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
2474 "NoFilesSelected", OwnerDoc(),
2477 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
2478 "NoFileSelected", OwnerDoc(),
2483 count
.AppendInt(int(mFileData
->mFilesOrDirectories
.Length()));
2485 nsContentUtils::FormatMaybeLocalizedString(
2486 value
, nsContentUtils::eFORMS_PROPERTIES
, "XFilesSelected", OwnerDoc(),
2493 const nsTArray
<OwningFileOrDirectory
>&
2494 HTMLInputElement::GetFilesOrDirectoriesInternal() const {
2495 return mFileData
->mFilesOrDirectories
;
2498 void HTMLInputElement::SetFilesOrDirectories(
2499 const nsTArray
<OwningFileOrDirectory
>& aFilesOrDirectories
,
2500 bool aSetValueChanged
) {
2501 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2505 MOZ_ASSERT(mFileData
);
2507 mFileData
->ClearGetFilesHelpers();
2509 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2510 HTMLInputElement_Binding::ClearCachedWebkitEntriesValue(this);
2511 mFileData
->mEntries
.Clear();
2514 mFileData
->mFilesOrDirectories
.Clear();
2515 mFileData
->mFilesOrDirectories
.AppendElements(aFilesOrDirectories
);
2517 AfterSetFilesOrDirectories(aSetValueChanged
);
2520 void HTMLInputElement::SetFiles(FileList
* aFiles
, bool aSetValueChanged
) {
2521 MOZ_ASSERT(mFileData
);
2523 mFileData
->mFilesOrDirectories
.Clear();
2524 mFileData
->ClearGetFilesHelpers();
2526 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2527 HTMLInputElement_Binding::ClearCachedWebkitEntriesValue(this);
2528 mFileData
->mEntries
.Clear();
2532 uint32_t listLength
= aFiles
->Length();
2533 for (uint32_t i
= 0; i
< listLength
; i
++) {
2534 OwningFileOrDirectory
* element
=
2535 mFileData
->mFilesOrDirectories
.AppendElement();
2536 element
->SetAsFile() = aFiles
->Item(i
);
2540 AfterSetFilesOrDirectories(aSetValueChanged
);
2543 // This method is used for testing only.
2544 void HTMLInputElement::MozSetDndFilesAndDirectories(
2545 const nsTArray
<OwningFileOrDirectory
>& aFilesOrDirectories
) {
2546 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
2550 SetFilesOrDirectories(aFilesOrDirectories
, true);
2552 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2553 UpdateEntries(aFilesOrDirectories
);
2556 RefPtr
<DispatchChangeEventCallback
> dispatchChangeEventCallback
=
2557 new DispatchChangeEventCallback(this);
2559 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
2560 HasAttr(nsGkAtoms::webkitdirectory
)) {
2562 GetFilesHelper
* helper
=
2563 GetOrCreateGetFilesHelper(true /* recursionFlag */, rv
);
2564 if (NS_WARN_IF(rv
.Failed())) {
2565 rv
.SuppressException();
2569 helper
->AddCallback(dispatchChangeEventCallback
);
2571 dispatchChangeEventCallback
->DispatchEvents();
2575 void HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged
) {
2576 // No need to flush here, if there's no frame at this point we
2577 // don't need to force creation of one just to tell it about this
2578 // new value. We just want the display to update as needed.
2579 nsIFormControlFrame
* formControlFrame
= GetFormControlFrame(false);
2580 if (formControlFrame
) {
2581 nsAutoString readableValue
;
2582 GetDisplayFileName(readableValue
);
2583 formControlFrame
->SetFormProperty(nsGkAtoms::value
, readableValue
);
2586 // Grab the full path here for any chrome callers who access our .value via a
2587 // CPOW. This path won't be called from a CPOW meaning the potential sync IPC
2588 // call under GetMozFullPath won't be rejected for not being urgent.
2589 if (mFileData
->mFilesOrDirectories
.IsEmpty()) {
2590 mFileData
->mFirstFilePath
.Truncate();
2593 GetDOMFileOrDirectoryPath(mFileData
->mFilesOrDirectories
[0],
2594 mFileData
->mFirstFilePath
, rv
);
2595 if (NS_WARN_IF(rv
.Failed())) {
2596 rv
.SuppressException();
2600 // Null out |mFileData->mFileList| to return a new file list when asked for.
2601 // Don't clear it since the file list might come from the user via SetFiles.
2602 if (mFileData
->mFileList
) {
2603 mFileData
->mFileList
= nullptr;
2606 if (aSetValueChanged
) {
2607 SetValueChanged(true);
2610 UpdateAllValidityStates(true);
2613 void HTMLInputElement::FireChangeEventIfNeeded() {
2614 if (!MayFireChangeOnBlur()) {
2618 // We're not exposing the GetValue return value anywhere here, so it's safe to
2619 // claim to be a system caller.
2621 GetValue(value
, CallerType::System
);
2623 // NOTE(emilio): Per spec we should not set this if we don't fire the change
2624 // event, but that seems like a bug. Using mValueChanged seems reasonable to
2625 // keep the expected behavior while
2626 // https://github.com/whatwg/html/issues/10013 is resolved.
2627 if (mValueChanged
) {
2628 SetUserInteracted(true);
2630 if (mFocusedValue
.Equals(value
)) {
2633 // Dispatch the change event.
2634 mFocusedValue
= value
;
2635 nsContentUtils::DispatchTrustedEvent(
2636 OwnerDoc(), static_cast<nsIContent
*>(this), u
"change"_ns
, CanBubble::eYes
,
2640 FileList
* HTMLInputElement::GetFiles() {
2641 if (mType
!= FormControlType::InputFile
) {
2645 if (!mFileData
->mFileList
) {
2646 mFileData
->mFileList
= new FileList(static_cast<nsIContent
*>(this));
2647 for (const OwningFileOrDirectory
& item
: GetFilesOrDirectoriesInternal()) {
2648 if (item
.IsFile()) {
2649 mFileData
->mFileList
->Append(item
.GetAsFile());
2654 return mFileData
->mFileList
;
2657 void HTMLInputElement::SetFiles(FileList
* aFiles
) {
2658 if (mType
!= FormControlType::InputFile
|| !aFiles
) {
2662 // Update |mFileData->mFilesOrDirectories|
2663 SetFiles(aFiles
, true);
2665 MOZ_ASSERT(!mFileData
->mFileList
, "Should've cleared the existing file list");
2667 // Update |mFileData->mFileList| without copy
2668 mFileData
->mFileList
= aFiles
;
2672 void HTMLInputElement::HandleNumberControlSpin(void* aData
) {
2673 RefPtr
<HTMLInputElement
> input
= static_cast<HTMLInputElement
*>(aData
);
2675 NS_ASSERTION(input
->mNumberControlSpinnerIsSpinning
,
2676 "Should have called nsRepeatService::Stop()");
2678 nsNumberControlFrame
* numberControlFrame
=
2679 do_QueryFrame(input
->GetPrimaryFrame());
2680 if (input
->mType
!= FormControlType::InputNumber
|| !numberControlFrame
) {
2681 // Type has changed (and possibly our frame type hasn't been updated yet)
2682 // or else we've lost our frame. Either way, stop the timer and don't do
2684 input
->StopNumberControlSpinnerSpin();
2686 input
->StepNumberControlForUserEvent(
2687 input
->mNumberControlSpinnerSpinsUp
? 1 : -1);
2691 nsresult
HTMLInputElement::SetValueInternal(
2692 const nsAString
& aValue
, const nsAString
* aOldValue
,
2693 const ValueSetterOptions
& aOptions
) {
2694 MOZ_ASSERT(GetValueMode() != VALUE_MODE_FILENAME
,
2695 "Don't call SetValueInternal for file inputs");
2697 // We want to remember if the SetValueInternal() call is being made for a XUL
2698 // element. We do that by looking at the parent node here, and if that node
2699 // is a XUL node, we consider our control a XUL control. XUL controls preserve
2700 // edit history across value setters.
2702 // TODO(emilio): Rather than doing this maybe add an attribute instead and
2703 // read it only on chrome docs or something? That'd allow front-end code to
2704 // move away from xul without weird side-effects.
2705 const bool forcePreserveUndoHistory
= mParent
&& mParent
->IsXULElement();
2707 switch (GetValueMode()) {
2708 case VALUE_MODE_VALUE
: {
2709 // At the moment, only single line text control have to sanitize their
2710 // value Because we have to create a new string for that, we should
2711 // prevent doing it if it's useless.
2712 nsAutoString
value(aValue
);
2714 if (mDoneCreating
&&
2715 !(mType
== FormControlType::InputNumber
&&
2716 aOptions
.contains(ValueSetterOption::BySetUserInputAPI
))) {
2717 // When the value of a number input is set by a script, we need to make
2718 // sure the value is a valid floating-point number.
2719 // https://html.spec.whatwg.org/#valid-floating-point-number
2720 // When it's set by a user, however, we need to be more permissive, so
2721 // we don't sanitize its value here. See bug 1839572.
2722 SanitizeValue(value
, SanitizationKind::ForValueSetter
);
2724 // else DoneCreatingElement calls us again once mDoneCreating is true
2726 const bool setValueChanged
=
2727 aOptions
.contains(ValueSetterOption::SetValueChanged
);
2728 if (setValueChanged
) {
2729 SetValueChanged(true);
2732 if (IsSingleLineTextControl(false)) {
2733 // Note that if aOptions includes
2734 // ValueSetterOption::BySetUserInputAPI, "input" event is automatically
2735 // dispatched by TextControlState::SetValue(). If you'd change condition
2736 // of calling this method, you need to maintain SetUserInput() too. FYI:
2737 // After calling SetValue(), the input type might have been
2738 // modified so that mInputData may not store TextControlState.
2739 EnsureEditorState();
2740 if (!mInputData
.mState
->SetValue(
2742 forcePreserveUndoHistory
2743 ? aOptions
+ ValueSetterOption::PreserveUndoHistory
2745 return NS_ERROR_OUT_OF_MEMORY
;
2747 // If the caller won't dispatch "input" event via
2748 // nsContentUtils::DispatchInputEvent(), we need to modify
2749 // validationMessage value here.
2751 // FIXME(emilio): ValueSetterOption::ByInternalAPI is not supposed to
2752 // change state, but maybe we could run this too?
2753 if (aOptions
.contains(ValueSetterOption::ByContentAPI
)) {
2754 MaybeUpdateAllValidityStates(!mDoneCreating
);
2757 free(mInputData
.mValue
);
2758 mInputData
.mValue
= ToNewUnicode(value
);
2759 if (setValueChanged
) {
2760 SetValueChanged(true);
2762 if (mType
== FormControlType::InputRange
) {
2763 nsRangeFrame
* frame
= do_QueryFrame(GetPrimaryFrame());
2765 frame
->UpdateForValueChange();
2767 } else if (CreatesDateTimeWidget() &&
2768 !aOptions
.contains(ValueSetterOption::BySetUserInputAPI
)) {
2769 if (Element
* dateTimeBoxElement
= GetDateTimeBoxElement()) {
2770 AsyncEventDispatcher::RunDOMEventWhenSafe(
2771 *dateTimeBoxElement
, u
"MozDateTimeValueChanged"_ns
,
2772 CanBubble::eNo
, ChromeOnlyDispatch::eNo
);
2775 if (mDoneCreating
) {
2776 OnValueChanged(ValueChangeKind::Internal
, value
.IsEmpty(), &value
);
2778 // else DoneCreatingElement calls us again once mDoneCreating is true
2781 if (mType
== FormControlType::InputColor
) {
2782 // Update color frame, to reflect color changes
2783 nsColorControlFrame
* colorControlFrame
=
2784 do_QueryFrame(GetPrimaryFrame());
2785 if (colorControlFrame
) {
2786 colorControlFrame
->UpdateColor();
2792 case VALUE_MODE_DEFAULT
:
2793 case VALUE_MODE_DEFAULT_ON
:
2794 // If the value of a hidden input was changed, we mark it changed so that
2795 // we will know we need to save / restore the value. Yes, we are
2796 // overloading the meaning of ValueChanged just a teensy bit to save a
2797 // measly byte of storage space in HTMLInputElement. Yes, you are free to
2798 // make a new flag, NEED_TO_SAVE_VALUE, at such time as mBitField becomes
2800 if (mType
== FormControlType::InputHidden
) {
2801 SetValueChanged(true);
2804 // Make sure to keep track of the last value change not being interactive,
2805 // just in case this used to be another kind of editable input before.
2806 // Note that a checked change _could_ really be interactive, but we don't
2807 // keep track of that elsewhere so seems fine to just do this.
2808 SetLastValueChangeWasInteractive(false);
2810 // Treat value == defaultValue for other input elements.
2811 return nsGenericHTMLFormControlElementWithState::SetAttr(
2812 kNameSpaceID_None
, nsGkAtoms::value
, aValue
, true);
2814 case VALUE_MODE_FILENAME
:
2815 return NS_ERROR_UNEXPECTED
;
2818 // This return statement is required for some compilers.
2822 void HTMLInputElement::SetValueChanged(bool aValueChanged
) {
2823 if (mValueChanged
== aValueChanged
) {
2826 mValueChanged
= aValueChanged
;
2827 UpdateTooLongValidityState();
2828 UpdateTooShortValidityState();
2829 UpdateValidityElementStates(true);
2832 void HTMLInputElement::SetLastValueChangeWasInteractive(bool aWasInteractive
) {
2833 if (aWasInteractive
== mLastValueChangeWasInteractive
) {
2836 mLastValueChangeWasInteractive
= aWasInteractive
;
2837 const bool wasValid
= IsValid();
2838 UpdateTooLongValidityState();
2839 UpdateTooShortValidityState();
2840 if (wasValid
!= IsValid()) {
2841 UpdateValidityElementStates(true);
2845 void HTMLInputElement::SetCheckedChanged(bool aCheckedChanged
) {
2846 DoSetCheckedChanged(aCheckedChanged
, true);
2849 void HTMLInputElement::DoSetCheckedChanged(bool aCheckedChanged
, bool aNotify
) {
2850 if (mType
== FormControlType::InputRadio
) {
2851 if (mCheckedChanged
!= aCheckedChanged
) {
2852 nsCOMPtr
<nsIRadioVisitor
> visitor
=
2853 new nsRadioSetCheckedChangedVisitor(aCheckedChanged
);
2854 VisitGroup(visitor
);
2857 SetCheckedChangedInternal(aCheckedChanged
);
2861 void HTMLInputElement::SetCheckedChangedInternal(bool aCheckedChanged
) {
2862 if (mCheckedChanged
== aCheckedChanged
) {
2865 mCheckedChanged
= aCheckedChanged
;
2866 UpdateValidityElementStates(true);
2869 void HTMLInputElement::SetChecked(bool aChecked
) {
2870 DoSetChecked(aChecked
, true, true);
2873 void HTMLInputElement::DoSetChecked(bool aChecked
, bool aNotify
,
2874 bool aSetValueChanged
) {
2875 // If the user or JS attempts to set checked, whether it actually changes the
2876 // value or not, we say the value was changed so that defaultValue don't
2877 // affect it no more.
2878 if (aSetValueChanged
) {
2879 DoSetCheckedChanged(true, aNotify
);
2882 // Don't do anything if we're not changing whether it's checked (it would
2883 // screw up state actually, especially when you are setting radio button to
2885 if (mChecked
== aChecked
) {
2890 if (mType
!= FormControlType::InputRadio
) {
2891 SetCheckedInternal(aChecked
, aNotify
);
2895 // For radio button, we need to do some extra fun stuff
2897 RadioSetChecked(aNotify
);
2901 if (auto* container
= GetCurrentRadioGroupContainer()) {
2903 GetAttr(nsGkAtoms::name
, name
);
2904 container
->SetCurrentRadioButton(name
, nullptr);
2906 // SetCheckedInternal is going to ask all radios to update their
2907 // validity state. We have to be sure the radio group container knows
2908 // the currently selected radio.
2909 SetCheckedInternal(false, aNotify
);
2912 void HTMLInputElement::RadioSetChecked(bool aNotify
) {
2913 // Find the selected radio button so we can deselect it
2914 HTMLInputElement
* currentlySelected
= GetSelectedRadioButton();
2916 // Deselect the currently selected radio button
2917 if (currentlySelected
) {
2918 // Pass true for the aNotify parameter since the currently selected
2919 // button is already in the document.
2920 currentlySelected
->SetCheckedInternal(false, true);
2923 // Let the group know that we are now the One True Radio Button
2924 if (auto* container
= GetCurrentRadioGroupContainer()) {
2926 GetAttr(nsGkAtoms::name
, name
);
2927 container
->SetCurrentRadioButton(name
, this);
2930 // SetCheckedInternal is going to ask all radios to update their
2932 SetCheckedInternal(true, aNotify
);
2935 RadioGroupContainer
* HTMLInputElement::GetCurrentRadioGroupContainer() const {
2937 mType
== FormControlType::InputRadio
,
2938 "GetRadioGroupContainer should only be called when type='radio'");
2939 return mRadioGroupContainer
;
2942 RadioGroupContainer
* HTMLInputElement::FindTreeRadioGroupContainer() const {
2944 GetAttr(nsGkAtoms::name
, name
);
2946 if (name
.IsEmpty()) {
2950 return &mForm
->OwnedRadioGroupContainer();
2952 if (IsInNativeAnonymousSubtree()) {
2955 if (Document
* doc
= GetUncomposedDoc()) {
2956 return &doc
->OwnedRadioGroupContainer();
2958 return &static_cast<FragmentOrElement
*>(SubtreeRoot())
2959 ->OwnedRadioGroupContainer();
2962 void HTMLInputElement::DisconnectRadioGroupContainer() {
2963 mRadioGroupContainer
= nullptr;
2966 HTMLInputElement
* HTMLInputElement::GetSelectedRadioButton() const {
2967 auto* container
= GetCurrentRadioGroupContainer();
2973 GetAttr(nsGkAtoms::name
, name
);
2975 return container
->GetCurrentRadioButton(name
);
2978 void HTMLInputElement::MaybeSubmitForm(nsPresContext
* aPresContext
) {
2980 // Nothing to do here.
2984 RefPtr
<PresShell
> presShell
= aPresContext
->GetPresShell();
2989 // Get the default submit element
2990 if (RefPtr
<nsGenericHTMLFormElement
> submitContent
=
2991 mForm
->GetDefaultSubmitElement()) {
2992 WidgetMouseEvent
event(true, eMouseClick
, nullptr, WidgetMouseEvent::eReal
);
2993 nsEventStatus status
= nsEventStatus_eIgnore
;
2994 presShell
->HandleDOMEventWithTarget(submitContent
, &event
, &status
);
2995 } else if (!mForm
->ImplicitSubmissionIsDisabled()) {
2996 // If there's only one text control, just submit the form
2997 // Hold strong ref across the event
2998 RefPtr
<dom::HTMLFormElement
> form(mForm
);
2999 form
->MaybeSubmit(nullptr);
3003 void HTMLInputElement::UpdateCheckedState(bool aNotify
) {
3004 SetStates(ElementState::CHECKED
, IsRadioOrCheckbox() && mChecked
, aNotify
);
3007 void HTMLInputElement::UpdateIndeterminateState(bool aNotify
) {
3008 bool indeterminate
= [&] {
3009 if (mType
== FormControlType::InputCheckbox
) {
3010 return mIndeterminate
;
3012 if (mType
== FormControlType::InputRadio
) {
3013 return !mChecked
&& !GetSelectedRadioButton();
3017 SetStates(ElementState::INDETERMINATE
, indeterminate
, aNotify
);
3020 void HTMLInputElement::SetCheckedInternal(bool aChecked
, bool aNotify
) {
3022 mChecked
= aChecked
;
3024 if (IsRadioOrCheckbox()) {
3025 SetStates(ElementState::CHECKED
, aChecked
, aNotify
);
3028 // No need to update element state, since we're about to call
3029 // UpdateState anyway.
3030 UpdateAllValidityStatesButNotElementState();
3031 UpdateIndeterminateState(aNotify
);
3032 UpdateValidityElementStates(aNotify
);
3034 // Notify all radios in the group that value has changed, this is to let
3035 // radios to have the chance to update its states, e.g., :indeterminate.
3036 if (mType
== FormControlType::InputRadio
) {
3037 nsCOMPtr
<nsIRadioVisitor
> visitor
= new nsRadioUpdateStateVisitor(this);
3038 VisitGroup(visitor
);
3042 #if !defined(ANDROID) && !defined(XP_MACOSX)
3043 bool HTMLInputElement::IsNodeApzAwareInternal() const {
3044 // Tell APZC we may handle mouse wheel event and do preventDefault when input
3046 return mType
== FormControlType::InputNumber
||
3047 mType
== FormControlType::InputRange
||
3048 nsINode::IsNodeApzAwareInternal();
3052 bool HTMLInputElement::IsInteractiveHTMLContent() const {
3053 return mType
!= FormControlType::InputHidden
||
3054 nsGenericHTMLFormControlElementWithState::IsInteractiveHTMLContent();
3057 void HTMLInputElement::AsyncEventRunning(AsyncEventDispatcher
* aEvent
) {
3058 nsImageLoadingContent::AsyncEventRunning(aEvent
);
3061 void HTMLInputElement::Select() {
3062 if (!IsSingleLineTextControl(false)) {
3066 TextControlState
* state
= GetEditorState();
3067 MOZ_ASSERT(state
, "Single line text controls are expected to have a state");
3069 if (FocusState() != FocusTristate::eUnfocusable
) {
3070 RefPtr
<nsFrameSelection
> fs
= state
->GetConstFrameSelection();
3071 if (fs
&& fs
->MouseDownRecorded()) {
3072 // This means that we're being called while the frame selection has a
3073 // mouse down event recorded to adjust the caret during the mouse up
3074 // event. We are probably called from the focus event handler. We should
3075 // override the delayed caret data in this case to ensure that this
3076 // select() call takes effect.
3077 fs
->SetDelayedCaretData(nullptr);
3080 if (RefPtr
<nsFocusManager
> fm
= nsFocusManager::GetFocusManager()) {
3081 fm
->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL
);
3083 // A focus event handler may change the type attribute, which will destroy
3084 // the previous state object.
3085 state
= GetEditorState();
3092 // Directly call TextControlState::SetSelectionRange because
3093 // HTMLInputElement::SetSelectionRange only applies to fewer types
3094 state
->SetSelectionRange(0, UINT32_MAX
, Optional
<nsAString
>(), IgnoreErrors(),
3095 TextControlState::ScrollAfterSelection::No
);
3098 void HTMLInputElement::SelectAll(nsPresContext
* aPresContext
) {
3099 nsIFormControlFrame
* formControlFrame
= GetFormControlFrame(true);
3101 if (formControlFrame
) {
3102 formControlFrame
->SetFormProperty(nsGkAtoms::select
, u
""_ns
);
3106 bool HTMLInputElement::NeedToInitializeEditorForEvent(
3107 EventChainPreVisitor
& aVisitor
) const {
3108 // We only need to initialize the editor for single line input controls
3109 // because they are lazily initialized. We don't need to initialize the
3110 // control for certain types of events, because we know that those events are
3111 // safe to be handled without the editor being initialized. These events
3112 // include: mousein/move/out, overflow/underflow, DOM mutation, and void
3113 // events. Void events are dispatched frequently by async keyboard scrolling
3114 // to focused elements, so it's important to handle them to prevent excessive
3116 if (!IsSingleLineTextControl(false) ||
3117 aVisitor
.mEvent
->mClass
== eMutationEventClass
) {
3121 switch (aVisitor
.mEvent
->mMessage
) {
3124 case eMouseEnterIntoWidget
:
3125 case eMouseExitFromWidget
:
3128 case eScrollPortUnderflow
:
3129 case eScrollPortOverflow
:
3136 bool HTMLInputElement::IsDisabledForEvents(WidgetEvent
* aEvent
) {
3137 return IsElementDisabledForEvents(aEvent
, GetPrimaryFrame());
3140 bool HTMLInputElement::CheckActivationBehaviorPreconditions(
3141 EventChainVisitor
& aVisitor
) const {
3143 case FormControlType::InputColor
:
3144 case FormControlType::InputCheckbox
:
3145 case FormControlType::InputRadio
:
3146 case FormControlType::InputFile
:
3147 case FormControlType::InputSubmit
:
3148 case FormControlType::InputImage
:
3149 case FormControlType::InputReset
:
3150 case FormControlType::InputButton
: {
3151 // Track whether we're in the outermost Dispatch invocation that will
3152 // cause activation of the input. That is, if we're a click event, or a
3153 // DOMActivate that was dispatched directly, this will be set, but if
3154 // we're a DOMActivate dispatched from click handling, it will not be set.
3155 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
3156 bool outerActivateEvent
=
3157 (mouseEvent
&& mouseEvent
->IsLeftClickEvent()) ||
3158 (aVisitor
.mEvent
->mMessage
== eLegacyDOMActivate
&&
3159 !mInInternalActivate
);
3160 if (outerActivateEvent
) {
3161 aVisitor
.mItemFlags
|= NS_OUTER_ACTIVATE_EVENT
;
3163 return outerActivateEvent
;
3170 void HTMLInputElement::GetEventTargetParent(EventChainPreVisitor
& aVisitor
) {
3171 // Do not process any DOM events if the element is disabled
3172 aVisitor
.mCanHandle
= false;
3173 if (IsDisabledForEvents(aVisitor
.mEvent
)) {
3177 // Initialize the editor if needed.
3178 if (NeedToInitializeEditorForEvent(aVisitor
)) {
3179 nsITextControlFrame
* textControlFrame
= do_QueryFrame(GetPrimaryFrame());
3180 if (textControlFrame
) textControlFrame
->EnsureEditorInitialized();
3183 if (CheckActivationBehaviorPreconditions(aVisitor
)) {
3184 aVisitor
.mWantsActivationBehavior
= true;
3187 // We must cache type because mType may change during JS event (bug 2369)
3188 aVisitor
.mItemFlags
|= uint8_t(mType
);
3190 if (aVisitor
.mEvent
->mMessage
== eFocus
&& aVisitor
.mEvent
->IsTrusted() &&
3191 MayFireChangeOnBlur() &&
3192 // StartRangeThumbDrag already set mFocusedValue on 'mousedown' before
3193 // we get the 'focus' event.
3194 !mIsDraggingRange
) {
3195 GetValue(mFocusedValue
, CallerType::System
);
3198 // Fire onchange (if necessary), before we do the blur, bug 357684.
3199 if (aVisitor
.mEvent
->mMessage
== eBlur
) {
3200 // We set NS_PRE_HANDLE_BLUR_EVENT here and handle it in PreHandleEvent to
3201 // prevent breaking event target chain creation.
3202 aVisitor
.mWantsPreHandleEvent
= true;
3203 aVisitor
.mItemFlags
|= NS_PRE_HANDLE_BLUR_EVENT
;
3206 if (mType
== FormControlType::InputRange
&&
3207 (aVisitor
.mEvent
->mMessage
== eFocus
||
3208 aVisitor
.mEvent
->mMessage
== eBlur
)) {
3209 // Just as nsGenericHTMLFormControlElementWithState::GetEventTargetParent
3210 // calls nsIFormControlFrame::SetFocus, we handle focus here.
3211 nsIFrame
* frame
= GetPrimaryFrame();
3213 frame
->InvalidateFrameSubtree();
3217 if (mType
== FormControlType::InputNumber
&& aVisitor
.mEvent
->IsTrusted()) {
3218 if (mNumberControlSpinnerIsSpinning
) {
3219 // If the timer is running the user has depressed the mouse on one of the
3220 // spin buttons. If the mouse exits the button we either want to reverse
3221 // the direction of spin if it has moved over the other button, or else
3222 // we want to end the spin. We do this here (rather than in
3223 // PostHandleEvent) because we don't want to let content preventDefault()
3224 // the end of the spin.
3225 if (aVisitor
.mEvent
->mMessage
== eMouseMove
) {
3226 // Be aggressive about stopping the spin:
3227 bool stopSpin
= true;
3228 nsNumberControlFrame
* numberControlFrame
=
3229 do_QueryFrame(GetPrimaryFrame());
3230 if (numberControlFrame
) {
3231 bool oldNumberControlSpinTimerSpinsUpValue
=
3232 mNumberControlSpinnerSpinsUp
;
3233 switch (numberControlFrame
->GetSpinButtonForPointerEvent(
3234 aVisitor
.mEvent
->AsMouseEvent())) {
3235 case nsNumberControlFrame::eSpinButtonUp
:
3236 mNumberControlSpinnerSpinsUp
= true;
3239 case nsNumberControlFrame::eSpinButtonDown
:
3240 mNumberControlSpinnerSpinsUp
= false;
3244 if (mNumberControlSpinnerSpinsUp
!=
3245 oldNumberControlSpinTimerSpinsUpValue
) {
3246 nsNumberControlFrame
* numberControlFrame
=
3247 do_QueryFrame(GetPrimaryFrame());
3248 if (numberControlFrame
) {
3249 numberControlFrame
->SpinnerStateChanged();
3254 StopNumberControlSpinnerSpin();
3256 } else if (aVisitor
.mEvent
->mMessage
== eMouseUp
) {
3257 StopNumberControlSpinnerSpin();
3262 nsGenericHTMLFormControlElementWithState::GetEventTargetParent(aVisitor
);
3264 // Stop the event if the related target's first non-native ancestor is the
3265 // same as the original target's first non-native ancestor (we are moving
3266 // inside of the same element).
3268 // FIXME(emilio): Is this still needed now that we use Shadow DOM for this?
3269 if (CreatesDateTimeWidget() && aVisitor
.mEvent
->IsTrusted() &&
3270 (aVisitor
.mEvent
->mMessage
== eFocus
||
3271 aVisitor
.mEvent
->mMessage
== eFocusIn
||
3272 aVisitor
.mEvent
->mMessage
== eFocusOut
||
3273 aVisitor
.mEvent
->mMessage
== eBlur
)) {
3274 nsIContent
* originalTarget
= nsIContent::FromEventTargetOrNull(
3275 aVisitor
.mEvent
->AsFocusEvent()->mOriginalTarget
);
3276 nsIContent
* relatedTarget
= nsIContent::FromEventTargetOrNull(
3277 aVisitor
.mEvent
->AsFocusEvent()->mRelatedTarget
);
3279 if (originalTarget
&& relatedTarget
&&
3280 originalTarget
->FindFirstNonChromeOnlyAccessContent() ==
3281 relatedTarget
->FindFirstNonChromeOnlyAccessContent()) {
3282 aVisitor
.mCanHandle
= false;
3287 void HTMLInputElement::LegacyPreActivationBehavior(
3288 EventChainVisitor
& aVisitor
) {
3290 // Web pages expect the value of a radio button or checkbox to be set
3291 // *before* onclick and DOMActivate fire, and they expect that if they set
3292 // the value explicitly during onclick or DOMActivate it will not be toggled
3293 // or any such nonsense.
3294 // In order to support that (bug 57137 and 58460 are examples) we toggle
3295 // the checked attribute *first*, and then fire onclick. If the user
3296 // returns false, we reset the control to the old checked value. Otherwise,
3297 // we dispatch DOMActivate. If DOMActivate is cancelled, we also reset
3298 // the control to the old checked value. We need to keep track of whether
3299 // we've already toggled the state from onclick since the user could
3300 // explicitly dispatch DOMActivate on the element.
3302 // These are compatibility hacks and are defined as legacy-pre-activation
3303 // and legacy-canceled-activation behavior in HTML.
3306 // Assert mType didn't change after GetEventTargetParent
3307 MOZ_ASSERT(NS_CONTROL_TYPE(aVisitor
.mItemFlags
) == uint8_t(mType
));
3309 bool originalCheckedValue
= false;
3310 mCheckedIsToggled
= false;
3312 if (mType
== FormControlType::InputCheckbox
) {
3313 if (mIndeterminate
) {
3314 // indeterminate is always set to FALSE when the checkbox is toggled
3315 SetIndeterminateInternal(false, false);
3316 aVisitor
.mItemFlags
|= NS_ORIGINAL_INDETERMINATE_VALUE
;
3319 originalCheckedValue
= Checked();
3320 DoSetChecked(!originalCheckedValue
, true, true);
3321 mCheckedIsToggled
= true;
3323 if (aVisitor
.mEventStatus
!= nsEventStatus_eConsumeNoDefault
) {
3324 aVisitor
.mEventStatus
= nsEventStatus_eConsumeDoDefault
;
3326 } else if (mType
== FormControlType::InputRadio
) {
3327 HTMLInputElement
* selectedRadioButton
= GetSelectedRadioButton();
3328 aVisitor
.mItemData
= static_cast<Element
*>(selectedRadioButton
);
3330 originalCheckedValue
= Checked();
3331 if (!originalCheckedValue
) {
3332 DoSetChecked(true, true, true);
3333 mCheckedIsToggled
= true;
3336 if (aVisitor
.mEventStatus
!= nsEventStatus_eConsumeNoDefault
) {
3337 aVisitor
.mEventStatus
= nsEventStatus_eConsumeDoDefault
;
3341 if (originalCheckedValue
) {
3342 aVisitor
.mItemFlags
|= NS_ORIGINAL_CHECKED_VALUE
;
3345 // out-of-spec legacy pre-activation behavior needed because of bug 1803805
3346 if ((mType
== FormControlType::InputSubmit
||
3347 mType
== FormControlType::InputImage
) &&
3349 aVisitor
.mItemFlags
|= NS_IN_SUBMIT_CLICK
;
3350 aVisitor
.mItemData
= static_cast<Element
*>(mForm
);
3351 // tell the form that we are about to enter a click handler.
3352 // that means that if there are scripted submissions, the
3353 // latest one will be deferred until after the exit point of the
3355 mForm
->OnSubmitClickBegin(this);
3359 nsresult
HTMLInputElement::PreHandleEvent(EventChainVisitor
& aVisitor
) {
3360 if (aVisitor
.mItemFlags
& NS_PRE_HANDLE_BLUR_EVENT
) {
3361 MOZ_ASSERT(aVisitor
.mEvent
->mMessage
== eBlur
);
3362 FireChangeEventIfNeeded();
3364 return nsGenericHTMLFormControlElementWithState::PreHandleEvent(aVisitor
);
3367 void HTMLInputElement::StartRangeThumbDrag(WidgetGUIEvent
* aEvent
) {
3368 nsRangeFrame
* rangeFrame
= do_QueryFrame(GetPrimaryFrame());
3373 mIsDraggingRange
= true;
3374 mRangeThumbDragStartValue
= GetValueAsDecimal();
3375 // Don't use CaptureFlags::RetargetToElement, as that breaks pseudo-class
3376 // styling of the thumb.
3377 PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState
);
3379 // Before we change the value, record the current value so that we'll
3380 // correctly send a 'change' event if appropriate. We need to do this here
3381 // because the 'focus' event is handled after the 'mousedown' event that
3382 // we're being called for (i.e. too late to update mFocusedValue, since we'll
3383 // have changed it by then).
3384 GetValue(mFocusedValue
, CallerType::System
);
3386 SetValueOfRangeForUserEvent(rangeFrame
->GetValueAtEventPoint(aEvent
),
3387 SnapToTickMarks::Yes
);
3390 void HTMLInputElement::FinishRangeThumbDrag(WidgetGUIEvent
* aEvent
) {
3391 MOZ_ASSERT(mIsDraggingRange
);
3393 if (PresShell::GetCapturingContent() == this) {
3394 PresShell::ReleaseCapturingContent();
3397 nsRangeFrame
* rangeFrame
= do_QueryFrame(GetPrimaryFrame());
3398 SetValueOfRangeForUserEvent(rangeFrame
->GetValueAtEventPoint(aEvent
),
3399 SnapToTickMarks::Yes
);
3401 mIsDraggingRange
= false;
3402 FireChangeEventIfNeeded();
3405 void HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent
) {
3406 MOZ_ASSERT(mIsDraggingRange
);
3408 mIsDraggingRange
= false;
3409 if (PresShell::GetCapturingContent() == this) {
3410 PresShell::ReleaseCapturingContent();
3412 if (aIsForUserEvent
) {
3413 SetValueOfRangeForUserEvent(mRangeThumbDragStartValue
,
3414 SnapToTickMarks::Yes
);
3416 // Don't dispatch an 'input' event - at least not using
3417 // DispatchTrustedEvent.
3418 // TODO: decide what we should do here - bug 851782.
3420 mInputType
->ConvertNumberToString(mRangeThumbDragStartValue
, val
);
3421 // TODO: What should we do if SetValueInternal fails? (The allocation
3422 // is small, so we should be fine here.)
3423 SetValueInternal(val
, {ValueSetterOption::BySetUserInputAPI
,
3424 ValueSetterOption::SetValueChanged
});
3425 if (nsRangeFrame
* frame
= do_QueryFrame(GetPrimaryFrame())) {
3426 frame
->UpdateForValueChange();
3428 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(this);
3429 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
3430 "Failed to dispatch input event");
3434 void HTMLInputElement::SetValueOfRangeForUserEvent(
3435 Decimal aValue
, SnapToTickMarks aSnapToTickMarks
) {
3436 MOZ_ASSERT(aValue
.isFinite());
3437 if (aSnapToTickMarks
== SnapToTickMarks::Yes
) {
3438 MaybeSnapToTickMark(aValue
);
3441 Decimal oldValue
= GetValueAsDecimal();
3444 mInputType
->ConvertNumberToString(aValue
, val
);
3445 // TODO: What should we do if SetValueInternal fails? (The allocation
3446 // is small, so we should be fine here.)
3447 SetValueInternal(val
, {ValueSetterOption::BySetUserInputAPI
,
3448 ValueSetterOption::SetValueChanged
});
3449 if (nsRangeFrame
* frame
= do_QueryFrame(GetPrimaryFrame())) {
3450 frame
->UpdateForValueChange();
3453 if (GetValueAsDecimal() != oldValue
) {
3454 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(this);
3455 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
3456 "Failed to dispatch input event");
3460 void HTMLInputElement::StartNumberControlSpinnerSpin() {
3461 MOZ_ASSERT(!mNumberControlSpinnerIsSpinning
);
3463 mNumberControlSpinnerIsSpinning
= true;
3465 nsRepeatService::GetInstance()->Start(
3466 HandleNumberControlSpin
, this, OwnerDoc(), "HandleNumberControlSpin"_ns
);
3468 // Capture the mouse so that we can tell if the pointer moves from one
3469 // spin button to the other, or to some other element:
3470 PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState
);
3472 nsNumberControlFrame
* numberControlFrame
= do_QueryFrame(GetPrimaryFrame());
3473 if (numberControlFrame
) {
3474 numberControlFrame
->SpinnerStateChanged();
3478 void HTMLInputElement::StopNumberControlSpinnerSpin(SpinnerStopState aState
) {
3479 if (mNumberControlSpinnerIsSpinning
) {
3480 if (PresShell::GetCapturingContent() == this) {
3481 PresShell::ReleaseCapturingContent();
3484 nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin
, this);
3486 mNumberControlSpinnerIsSpinning
= false;
3488 if (aState
== eAllowDispatchingEvents
) {
3489 FireChangeEventIfNeeded();
3492 nsNumberControlFrame
* numberControlFrame
= do_QueryFrame(GetPrimaryFrame());
3493 if (numberControlFrame
) {
3494 MOZ_ASSERT(aState
== eAllowDispatchingEvents
,
3495 "Shouldn't have primary frame for the element when we're not "
3496 "allowed to dispatch events to it anymore.");
3497 numberControlFrame
->SpinnerStateChanged();
3502 void HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection
) {
3503 // We can't use GetValidityState here because the validity state is not set
3504 // if the user hasn't previously taken an action to set or change the value,
3505 // according to the specs.
3506 if (HasBadInput()) {
3507 // If the user has typed a value into the control and inadvertently made a
3508 // mistake (e.g. put a thousand separator at the wrong point) we do not
3509 // want to wipe out what they typed if they try to increment/decrement the
3510 // value. Better is to highlight the value as being invalid so that they
3511 // can correct what they typed.
3512 // We only do this if there actually is a value typed in by/displayed to
3513 // the user. (IsValid() can return false if the 'required' attribute is
3514 // set and the value is the empty string.)
3515 if (!IsValueEmpty()) {
3516 // We pass 'true' for SetUserInteracted because we need the UI to update
3517 // _now_ or the user will wonder why the step behavior isn't functioning.
3518 SetUserInteracted(true);
3523 Decimal newValue
= Decimal::nan(); // unchanged if value will not change
3525 nsresult rv
= GetValueIfStepped(aDirection
, CALLED_FOR_USER_EVENT
, &newValue
);
3527 if (NS_FAILED(rv
) || !newValue
.isFinite()) {
3528 return; // value should not or will not change
3531 nsAutoString newVal
;
3532 mInputType
->ConvertNumberToString(newValue
, newVal
);
3533 // TODO: What should we do if SetValueInternal fails? (The allocation
3534 // is small, so we should be fine here.)
3535 SetValueInternal(newVal
, {ValueSetterOption::BySetUserInputAPI
,
3536 ValueSetterOption::SetValueChanged
});
3539 static bool SelectTextFieldOnFocus() {
3540 if (!gSelectTextFieldOnFocus
) {
3541 int32_t selectTextfieldsOnKeyFocus
= -1;
3543 LookAndFeel::GetInt(LookAndFeel::IntID::SelectTextfieldsOnKeyFocus
,
3544 &selectTextfieldsOnKeyFocus
);
3545 if (NS_FAILED(rv
)) {
3546 gSelectTextFieldOnFocus
= -1;
3548 gSelectTextFieldOnFocus
= selectTextfieldsOnKeyFocus
!= 0 ? 1 : -1;
3552 return gSelectTextFieldOnFocus
== 1;
3555 bool HTMLInputElement::ShouldPreventDOMActivateDispatch(
3556 EventTarget
* aOriginalTarget
) {
3558 * For the moment, there is only one situation where we actually want to
3559 * prevent firing a DOMActivate event:
3560 * - we are a <input type='file'> that just got a click event,
3561 * - the event was targeted to our button which should have sent a
3562 * DOMActivate event.
3565 if (mType
!= FormControlType::InputFile
) {
3569 Element
* target
= Element::FromEventTargetOrNull(aOriginalTarget
);
3574 return target
->GetParent() == this &&
3575 target
->IsRootOfNativeAnonymousSubtree() &&
3576 target
->IsHTMLElement(nsGkAtoms::button
);
3579 nsresult
HTMLInputElement::MaybeInitPickers(EventChainPostVisitor
& aVisitor
) {
3580 // Open a file picker when we receive a click on a <input type='file'>, or
3581 // open a color picker when we receive a click on a <input type='color'>.
3582 // A click is handled if it's the left mouse button.
3583 // We do not prevent non-trusted click because authors can already use
3584 // .click(). However, the pickers will follow the rules of popup-blocking.
3585 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
3586 if (!(mouseEvent
&& mouseEvent
->IsLeftClickEvent())) {
3589 if (mType
== FormControlType::InputFile
) {
3590 // If the user clicked on the "Choose folder..." button we open the
3591 // directory picker, else we open the file picker.
3592 FilePickerType type
= FILE_PICKER_FILE
;
3593 nsIContent
* target
=
3594 nsIContent::FromEventTargetOrNull(aVisitor
.mEvent
->mOriginalTarget
);
3595 if (target
&& target
->FindFirstNonChromeOnlyAccessContent() == this &&
3596 StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
3597 HasAttr(nsGkAtoms::webkitdirectory
)) {
3598 type
= FILE_PICKER_DIRECTORY
;
3600 return InitFilePicker(type
);
3602 if (mType
== FormControlType::InputColor
) {
3603 return InitColorPicker();
3610 * Return true if the input event should be ignored because of its modifiers.
3611 * Control is treated specially, since sometimes we ignore it, and sometimes
3612 * we don't (for webcompat reasons).
3614 static bool IgnoreInputEventWithModifier(const WidgetInputEvent
& aEvent
,
3615 bool ignoreControl
) {
3616 return (ignoreControl
&& aEvent
.IsControl()) ||
3618 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
3619 // Meta key is the Windows Logo key on Windows and Linux which may
3620 // assign some special meaning for the events while it's pressed.
3621 // On the other hand, it's a normal modifier in macOS and Android.
3622 // Therefore, We should ignore it only in Win/Linux.
3628 bool HTMLInputElement::StepsInputValue(
3629 const WidgetKeyboardEvent
& aEvent
) const {
3630 if (mType
!= FormControlType::InputNumber
) {
3633 if (aEvent
.mMessage
!= eKeyPress
) {
3636 if (!aEvent
.IsTrusted()) {
3639 if (aEvent
.mKeyCode
!= NS_VK_UP
&& aEvent
.mKeyCode
!= NS_VK_DOWN
) {
3642 if (IgnoreInputEventWithModifier(aEvent
, false)) {
3645 if (aEvent
.DefaultPrevented()) {
3654 static bool ActivatesWithKeyboard(FormControlType aType
, uint32_t aKeyCode
) {
3656 case FormControlType::InputCheckbox
:
3657 case FormControlType::InputRadio
:
3658 // Checkbox and Radio try to submit on Enter press
3659 return aKeyCode
!= NS_VK_RETURN
;
3660 case FormControlType::InputButton
:
3661 case FormControlType::InputReset
:
3662 case FormControlType::InputSubmit
:
3663 case FormControlType::InputFile
:
3664 case FormControlType::InputImage
: // Bug 34418
3665 case FormControlType::InputColor
:
3672 nsresult
HTMLInputElement::PostHandleEvent(EventChainPostVisitor
& aVisitor
) {
3673 if (aVisitor
.mEvent
->mMessage
== eBlur
) {
3674 if (mIsDraggingRange
) {
3675 FinishRangeThumbDrag();
3676 } else if (mNumberControlSpinnerIsSpinning
) {
3677 StopNumberControlSpinnerSpin();
3681 nsresult rv
= NS_OK
;
3682 auto oldType
= FormControlType(NS_CONTROL_TYPE(aVisitor
.mItemFlags
));
3684 // Ideally we would make the default action for click and space just dispatch
3685 // DOMActivate, and the default action for DOMActivate flip the checkbox/
3686 // radio state and fire onchange. However, for backwards compatibility, we
3687 // need to flip the state before firing click, and we need to fire click
3688 // when space is pressed. So, we just nest the firing of DOMActivate inside
3689 // the click event handling, and allow cancellation of DOMActivate to cancel
3691 if (aVisitor
.mEventStatus
!= nsEventStatus_eConsumeNoDefault
&&
3692 !IsSingleLineTextControl(true) && mType
!= FormControlType::InputNumber
) {
3693 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
3694 if (mouseEvent
&& mouseEvent
->IsLeftClickEvent() &&
3695 OwnerDoc()->MayHaveDOMActivateListeners() &&
3696 !ShouldPreventDOMActivateDispatch(aVisitor
.mEvent
->mOriginalTarget
)) {
3697 // DOMActive event should be trusted since the activation is actually
3698 // occurred even if the cause is an untrusted click event.
3699 InternalUIEvent
actEvent(true, eLegacyDOMActivate
, mouseEvent
);
3700 actEvent
.mDetail
= 1;
3702 if (RefPtr
<PresShell
> presShell
=
3703 aVisitor
.mPresContext
? aVisitor
.mPresContext
->GetPresShell()
3705 nsEventStatus status
= nsEventStatus_eIgnore
;
3706 mInInternalActivate
= true;
3707 rv
= presShell
->HandleDOMEventWithTarget(this, &actEvent
, &status
);
3708 mInInternalActivate
= false;
3710 // If activate is cancelled, we must do the same as when click is
3711 // cancelled (revert the checkbox to its original value).
3712 if (status
== nsEventStatus_eConsumeNoDefault
) {
3713 aVisitor
.mEventStatus
= status
;
3719 bool preventDefault
=
3720 aVisitor
.mEventStatus
== nsEventStatus_eConsumeNoDefault
;
3721 if (IsDisabled() && oldType
!= FormControlType::InputCheckbox
&&
3722 oldType
!= FormControlType::InputRadio
) {
3723 // Behave as if defaultPrevented when the element becomes disabled by event
3724 // listeners. Checkboxes and radio buttons should still process clicks for
3726 // https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
3727 preventDefault
= true;
3730 if (NS_SUCCEEDED(rv
)) {
3731 WidgetKeyboardEvent
* keyEvent
= aVisitor
.mEvent
->AsKeyboardEvent();
3732 if (keyEvent
&& StepsInputValue(*keyEvent
)) {
3733 StepNumberControlForUserEvent(keyEvent
->mKeyCode
== NS_VK_UP
? 1 : -1);
3734 FireChangeEventIfNeeded();
3735 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
3736 } else if (!preventDefault
) {
3737 if (keyEvent
&& ActivatesWithKeyboard(mType
, keyEvent
->mKeyCode
) &&
3738 keyEvent
->IsTrusted()) {
3739 // We maybe dispatch a synthesized click for keyboard activation.
3740 HandleKeyboardActivation(aVisitor
);
3743 switch (aVisitor
.mEvent
->mMessage
) {
3745 // see if we should select the contents of the textbox. This happens
3746 // for text and password fields when the field was focused by the
3747 // keyboard or a navigation, the platform allows it, and it wasn't
3748 // just because we raised a window.
3750 // While it'd usually make sense, we don't do this for JS callers
3751 // because it causes some compat issues, see bug 1712724 for example.
3752 nsFocusManager
* fm
= nsFocusManager::GetFocusManager();
3753 if (fm
&& IsSingleLineTextControl(false) &&
3754 !aVisitor
.mEvent
->AsFocusEvent()->mFromRaise
&&
3755 SelectTextFieldOnFocus()) {
3756 if (Document
* document
= GetComposedDoc()) {
3757 uint32_t lastFocusMethod
=
3758 fm
->GetLastFocusMethod(document
->GetWindow());
3759 const bool shouldSelectAllOnFocus
= [&] {
3760 if (lastFocusMethod
& nsIFocusManager::FLAG_BYMOVEFOCUS
) {
3763 if (lastFocusMethod
& nsIFocusManager::FLAG_BYJS
) {
3766 return bool(lastFocusMethod
& nsIFocusManager::FLAG_BYKEY
);
3768 if (shouldSelectAllOnFocus
) {
3769 RefPtr
<nsPresContext
> presContext
=
3770 GetPresContext(eForComposedDoc
);
3771 SelectAll(presContext
);
3779 // For compatibility with the other browsers, we should active this
3780 // element at least when a checkbox or a radio button.
3781 // TODO: Investigate which elements are activated by space key in the
3783 if (aVisitor
.mPresContext
&& keyEvent
->IsTrusted() && !IsDisabled() &&
3784 keyEvent
->ShouldWorkAsSpaceKey() &&
3785 (mType
== FormControlType::InputCheckbox
||
3786 mType
== FormControlType::InputRadio
)) {
3787 EventStateManager::SetActiveManager(
3788 aVisitor
.mPresContext
->EventStateManager(), this);
3794 if (mType
== FormControlType::InputRadio
&& keyEvent
->IsTrusted() &&
3795 !keyEvent
->IsAlt() && !keyEvent
->IsControl() &&
3796 !keyEvent
->IsMeta()) {
3797 rv
= MaybeHandleRadioButtonNavigation(aVisitor
, keyEvent
->mKeyCode
);
3801 * For some input types, if the user hits enter, the form is
3804 * Bug 99920, bug 109463 and bug 147850:
3805 * (a) if there is a submit control in the form, click the first
3806 * submit control in the form.
3807 * (b) if there is just one text control in the form, submit by
3808 * sending a submit event directly to the form
3809 * (c) if there is more than one text input and no submit buttons, do
3810 * not submit, period.
3813 if (keyEvent
->mKeyCode
== NS_VK_RETURN
&& keyEvent
->IsTrusted() &&
3814 (IsSingleLineTextControl(false, mType
) ||
3815 IsDateTimeInputType(mType
) ||
3816 mType
== FormControlType::InputCheckbox
||
3817 mType
== FormControlType::InputRadio
)) {
3818 if (IsSingleLineTextControl(false, mType
) ||
3819 IsDateTimeInputType(mType
)) {
3820 FireChangeEventIfNeeded();
3823 if (aVisitor
.mPresContext
) {
3824 MaybeSubmitForm(aVisitor
.mPresContext
);
3828 if (mType
== FormControlType::InputRange
&& keyEvent
->IsTrusted() &&
3829 !keyEvent
->IsAlt() && !keyEvent
->IsControl() &&
3830 !keyEvent
->IsMeta() &&
3831 (keyEvent
->mKeyCode
== NS_VK_LEFT
||
3832 keyEvent
->mKeyCode
== NS_VK_RIGHT
||
3833 keyEvent
->mKeyCode
== NS_VK_UP
||
3834 keyEvent
->mKeyCode
== NS_VK_DOWN
||
3835 keyEvent
->mKeyCode
== NS_VK_PAGE_UP
||
3836 keyEvent
->mKeyCode
== NS_VK_PAGE_DOWN
||
3837 keyEvent
->mKeyCode
== NS_VK_HOME
||
3838 keyEvent
->mKeyCode
== NS_VK_END
)) {
3839 Decimal minimum
= GetMinimum();
3840 Decimal maximum
= GetMaximum();
3841 MOZ_ASSERT(minimum
.isFinite() && maximum
.isFinite());
3842 if (minimum
< maximum
) { // else the value is locked to the minimum
3843 Decimal value
= GetValueAsDecimal();
3844 Decimal step
= GetStep();
3845 if (step
== kStepAny
) {
3846 step
= GetDefaultStep();
3848 MOZ_ASSERT(value
.isFinite() && step
.isFinite());
3850 switch (keyEvent
->mKeyCode
) {
3853 (GetComputedDirectionality() == Directionality::Rtl
3859 (GetComputedDirectionality() == Directionality::Rtl
3864 // Even for horizontal range, "up" means "increase"
3865 newValue
= value
+ step
;
3868 // Even for horizontal range, "down" means "decrease"
3869 newValue
= value
- step
;
3878 // For PgUp/PgDn we jump 10% of the total range, unless step
3879 // requires us to jump more.
3881 value
+ std::max(step
, (maximum
- minimum
) / Decimal(10));
3883 case NS_VK_PAGE_DOWN
:
3885 value
- std::max(step
, (maximum
- minimum
) / Decimal(10));
3888 SetValueOfRangeForUserEvent(newValue
);
3889 FireChangeEventIfNeeded();
3890 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
3894 } break; // eKeyPress
3898 case eMouseDoubleClick
: {
3899 // cancel all of these events for buttons
3901 WidgetMouseEvent
* mouseEvent
= aVisitor
.mEvent
->AsMouseEvent();
3902 if (mouseEvent
->mButton
== MouseButton::eMiddle
||
3903 mouseEvent
->mButton
== MouseButton::eSecondary
) {
3904 if (mType
== FormControlType::InputButton
||
3905 mType
== FormControlType::InputReset
||
3906 mType
== FormControlType::InputSubmit
) {
3907 if (aVisitor
.mDOMEvent
) {
3908 aVisitor
.mDOMEvent
->StopPropagation();
3910 rv
= NS_ERROR_FAILURE
;
3914 if (mType
== FormControlType::InputNumber
&&
3915 aVisitor
.mEvent
->IsTrusted()) {
3916 if (mouseEvent
->mButton
== MouseButton::ePrimary
&&
3917 !IgnoreInputEventWithModifier(*mouseEvent
, false)) {
3918 nsNumberControlFrame
* numberControlFrame
=
3919 do_QueryFrame(GetPrimaryFrame());
3920 if (numberControlFrame
) {
3921 if (aVisitor
.mEvent
->mMessage
== eMouseDown
&& IsMutable()) {
3922 switch (numberControlFrame
->GetSpinButtonForPointerEvent(
3923 aVisitor
.mEvent
->AsMouseEvent())) {
3924 case nsNumberControlFrame::eSpinButtonUp
:
3925 StepNumberControlForUserEvent(1);
3926 mNumberControlSpinnerSpinsUp
= true;
3927 StartNumberControlSpinnerSpin();
3928 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
3930 case nsNumberControlFrame::eSpinButtonDown
:
3931 StepNumberControlForUserEvent(-1);
3932 mNumberControlSpinnerSpinsUp
= false;
3933 StartNumberControlSpinnerSpin();
3934 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
3940 if (aVisitor
.mEventStatus
!= nsEventStatus_eConsumeNoDefault
) {
3941 // We didn't handle this to step up/down. Whatever this was, be
3942 // aggressive about stopping the spin. (And don't set
3943 // nsEventStatus_eConsumeNoDefault after doing so, since that
3944 // might prevent, say, the context menu from opening.)
3945 StopNumberControlSpinnerSpin();
3950 #if !defined(ANDROID) && !defined(XP_MACOSX)
3952 // Handle wheel events as increasing / decreasing the input element's
3953 // value when it's focused and it's type is number or range.
3954 WidgetWheelEvent
* wheelEvent
= aVisitor
.mEvent
->AsWheelEvent();
3955 if (!aVisitor
.mEvent
->DefaultPrevented() &&
3956 aVisitor
.mEvent
->IsTrusted() && IsMutable() && wheelEvent
&&
3957 wheelEvent
->mDeltaY
!= 0 &&
3958 wheelEvent
->mDeltaMode
!= WheelEvent_Binding::DOM_DELTA_PIXEL
) {
3959 if (mType
== FormControlType::InputNumber
) {
3960 if (nsContentUtils::IsFocusedContent(this)) {
3961 StepNumberControlForUserEvent(wheelEvent
->mDeltaY
> 0 ? -1 : 1);
3962 FireChangeEventIfNeeded();
3963 aVisitor
.mEvent
->PreventDefault();
3965 } else if (mType
== FormControlType::InputRange
&&
3966 nsContentUtils::IsFocusedContent(this) &&
3967 GetMinimum() < GetMaximum()) {
3968 Decimal value
= GetValueAsDecimal();
3969 Decimal step
= GetStep();
3970 if (step
== kStepAny
) {
3971 step
= GetDefaultStep();
3973 MOZ_ASSERT(value
.isFinite() && step
.isFinite());
3974 SetValueOfRangeForUserEvent(
3975 wheelEvent
->mDeltaY
< 0 ? value
+ step
: value
- step
);
3976 FireChangeEventIfNeeded();
3977 aVisitor
.mEvent
->PreventDefault();
3984 if (!aVisitor
.mEvent
->DefaultPrevented() &&
3985 aVisitor
.mEvent
->IsTrusted() &&
3986 aVisitor
.mEvent
->AsMouseEvent()->mButton
==
3987 MouseButton::ePrimary
) {
3988 // TODO(emilio): Handling this should ideally not move focus.
3989 if (mType
== FormControlType::InputSearch
) {
3990 if (nsSearchControlFrame
* searchControlFrame
=
3991 do_QueryFrame(GetPrimaryFrame())) {
3992 Element
* clearButton
= searchControlFrame
->GetAnonClearButton();
3994 aVisitor
.mEvent
->mOriginalTarget
== clearButton
) {
3995 SetUserInput(EmptyString(),
3996 *nsContentUtils::GetSystemPrincipal());
3997 // TODO(emilio): This should focus the input, but calling
3998 // SetFocus(this, FLAG_NOSCROLL) for some reason gets us into
3999 // an inconsistent state where we're focused but don't match
4000 // :focus-visible / :focus.
4003 } else if (mType
== FormControlType::InputPassword
) {
4004 if (nsTextControlFrame
* textControlFrame
=
4005 do_QueryFrame(GetPrimaryFrame())) {
4006 auto* reveal
= textControlFrame
->GetRevealButton();
4007 if (reveal
&& aVisitor
.mEvent
->mOriginalTarget
== reveal
) {
4008 SetRevealPassword(!RevealPassword());
4009 // TODO(emilio): This should focus the input, but calling
4010 // SetFocus(this, FLAG_NOSCROLL) for some reason gets us into
4011 // an inconsistent state where we're focused but don't match
4012 // :focus-visible / :focus.
4023 // Bug 1459231: Temporarily needed till links respect activation target,
4024 // then also remove NS_OUTER_ACTIVATE_EVENT. The appropriate
4025 // behavior/model for links is still under discussion (see
4026 // https://github.com/whatwg/html/issues/1576). For now, we aim for
4027 // consistency with other browsers.
4028 if (aVisitor
.mItemFlags
& NS_OUTER_ACTIVATE_EVENT
) {
4030 case FormControlType::InputReset
:
4031 case FormControlType::InputSubmit
:
4032 case FormControlType::InputImage
:
4034 aVisitor
.mEvent
->mFlags
.mMultipleActionsPrevented
= true;
4037 case FormControlType::InputCheckbox
:
4038 case FormControlType::InputRadio
:
4039 aVisitor
.mEvent
->mFlags
.mMultipleActionsPrevented
= true;
4048 if (NS_SUCCEEDED(rv
) && mType
== FormControlType::InputRange
) {
4049 PostHandleEventForRangeThumb(aVisitor
);
4052 if (!preventDefault
) {
4053 MOZ_TRY(MaybeInitPickers(aVisitor
));
4058 void EndSubmitClick(EventChainPostVisitor
& aVisitor
) {
4059 auto oldType
= FormControlType(NS_CONTROL_TYPE(aVisitor
.mItemFlags
));
4060 if ((aVisitor
.mItemFlags
& NS_IN_SUBMIT_CLICK
) &&
4061 (oldType
== FormControlType::InputSubmit
||
4062 oldType
== FormControlType::InputImage
)) {
4063 nsCOMPtr
<nsIContent
> content(do_QueryInterface(aVisitor
.mItemData
));
4064 RefPtr
<HTMLFormElement
> form
= HTMLFormElement::FromNodeOrNull(content
);
4065 // Tell the form that we are about to exit a click handler,
4066 // so the form knows not to defer subsequent submissions.
4067 // The pending ones that were created during the handler
4068 // will be flushed or forgotten.
4069 form
->OnSubmitClickEnd();
4070 // tell the form to flush a possible pending submission.
4071 // the reason is that the script returned false (the event was
4072 // not ignored) so if there is a stored submission, it needs to
4073 // be submitted immediately.
4074 form
->FlushPendingSubmission();
4078 void HTMLInputElement::ActivationBehavior(EventChainPostVisitor
& aVisitor
) {
4079 auto oldType
= FormControlType(NS_CONTROL_TYPE(aVisitor
.mItemFlags
));
4081 if (IsDisabled() && oldType
!= FormControlType::InputCheckbox
&&
4082 oldType
!= FormControlType::InputRadio
) {
4083 // Behave as if defaultPrevented when the element becomes disabled by event
4084 // listeners. Checkboxes and radio buttons should still process clicks for
4086 // https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
4087 EndSubmitClick(aVisitor
);
4091 if (mCheckedIsToggled
) {
4092 SetUserInteracted(true);
4094 // Fire input event and then change event.
4095 DebugOnly
<nsresult
> rvIgnored
= nsContentUtils::DispatchInputEvent(this);
4096 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
4097 "Failed to dispatch input event");
4099 // FIXME: Why is this different than every other change event?
4100 nsContentUtils::DispatchTrustedEvent
<WidgetEvent
>(
4101 OwnerDoc(), static_cast<Element
*>(this), eFormChange
, CanBubble::eYes
,
4103 #ifdef ACCESSIBILITY
4104 // Fire an event to notify accessibility
4105 if (mType
== FormControlType::InputCheckbox
) {
4106 if (nsContentUtils::MayHaveFormCheckboxStateChangeListeners()) {
4107 FireEventForAccessibility(this, eFormCheckboxStateChange
);
4109 } else if (nsContentUtils::MayHaveFormRadioStateChangeListeners()) {
4110 FireEventForAccessibility(this, eFormRadioStateChange
);
4111 // Fire event for the previous selected radio.
4112 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aVisitor
.mItemData
);
4113 if (auto* previous
= HTMLInputElement::FromNodeOrNull(content
)) {
4114 FireEventForAccessibility(previous
, eFormRadioStateChange
);
4121 case FormControlType::InputReset
:
4122 case FormControlType::InputSubmit
:
4123 case FormControlType::InputImage
:
4125 // Hold a strong ref while dispatching
4126 RefPtr
<HTMLFormElement
> form(mForm
);
4127 if (mType
== FormControlType::InputReset
) {
4128 form
->MaybeReset(this);
4130 form
->MaybeSubmit(this);
4132 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
4139 if (IsButtonControl()) {
4140 if (!GetInvokeTargetElement()) {
4141 HandlePopoverTargetAction();
4143 HandleInvokeTargetAction();
4147 EndSubmitClick(aVisitor
);
4150 void HTMLInputElement::LegacyCanceledActivationBehavior(
4151 EventChainPostVisitor
& aVisitor
) {
4152 bool originalCheckedValue
=
4153 !!(aVisitor
.mItemFlags
& NS_ORIGINAL_CHECKED_VALUE
);
4154 auto oldType
= FormControlType(NS_CONTROL_TYPE(aVisitor
.mItemFlags
));
4156 if (mCheckedIsToggled
) {
4157 // if it was canceled and a radio button, then set the old
4158 // selected btn to TRUE. if it is a checkbox then set it to its
4159 // original value (legacy-canceled-activation)
4160 if (oldType
== FormControlType::InputRadio
) {
4161 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aVisitor
.mItemData
);
4162 HTMLInputElement
* selectedRadioButton
=
4163 HTMLInputElement::FromNodeOrNull(content
);
4164 if (selectedRadioButton
) {
4165 selectedRadioButton
->SetChecked(true);
4167 // If there was no checked radio button or this one is no longer a
4168 // radio button we must reset it back to false to cancel the action.
4169 // See how the web of hack grows?
4170 if (!selectedRadioButton
|| mType
!= FormControlType::InputRadio
) {
4171 DoSetChecked(false, true, true);
4173 } else if (oldType
== FormControlType::InputCheckbox
) {
4174 bool originalIndeterminateValue
=
4175 !!(aVisitor
.mItemFlags
& NS_ORIGINAL_INDETERMINATE_VALUE
);
4176 SetIndeterminateInternal(originalIndeterminateValue
, false);
4177 DoSetChecked(originalCheckedValue
, true, true);
4181 // Relevant for bug 242494: submit button with "submit(); return false;"
4182 EndSubmitClick(aVisitor
);
4185 enum class RadioButtonMove
{ Back
, Forward
, None
};
4186 nsresult
HTMLInputElement::MaybeHandleRadioButtonNavigation(
4187 EventChainPostVisitor
& aVisitor
, uint32_t aKeyCode
) {
4191 return RadioButtonMove::Back
;
4193 return RadioButtonMove::Forward
;
4196 const bool isRtl
= GetComputedDirectionality() == Directionality::Rtl
;
4197 return isRtl
== (aKeyCode
== NS_VK_LEFT
) ? RadioButtonMove::Forward
4198 : RadioButtonMove::Back
;
4201 return RadioButtonMove::None
;
4203 if (move
== RadioButtonMove::None
) {
4206 // Arrow key pressed, focus+select prev/next radio button
4207 RefPtr
<HTMLInputElement
> selectedRadioButton
;
4208 if (auto* container
= GetCurrentRadioGroupContainer()) {
4210 GetAttr(nsGkAtoms::name
, name
);
4211 container
->GetNextRadioButton(name
, move
== RadioButtonMove::Back
, this,
4212 getter_AddRefs(selectedRadioButton
));
4214 if (!selectedRadioButton
) {
4217 FocusOptions options
;
4219 selectedRadioButton
->Focus(options
, CallerType::System
, error
);
4220 if (error
.Failed()) {
4221 return error
.StealNSResult();
4223 nsresult rv
= DispatchSimulatedClick(
4224 selectedRadioButton
, aVisitor
.mEvent
->IsTrusted(), aVisitor
.mPresContext
);
4225 if (NS_SUCCEEDED(rv
)) {
4226 aVisitor
.mEventStatus
= nsEventStatus_eConsumeNoDefault
;
4231 void HTMLInputElement::PostHandleEventForRangeThumb(
4232 EventChainPostVisitor
& aVisitor
) {
4233 MOZ_ASSERT(mType
== FormControlType::InputRange
);
4235 if (nsEventStatus_eConsumeNoDefault
== aVisitor
.mEventStatus
||
4236 !(aVisitor
.mEvent
->mClass
== eMouseEventClass
||
4237 aVisitor
.mEvent
->mClass
== eTouchEventClass
||
4238 aVisitor
.mEvent
->mClass
== eKeyboardEventClass
)) {
4242 nsRangeFrame
* rangeFrame
= do_QueryFrame(GetPrimaryFrame());
4243 if (!rangeFrame
&& mIsDraggingRange
) {
4244 CancelRangeThumbDrag();
4248 switch (aVisitor
.mEvent
->mMessage
) {
4251 if (mIsDraggingRange
) {
4254 if (PresShell::GetCapturingContent()) {
4255 break; // don't start drag if someone else is already capturing
4257 WidgetInputEvent
* inputEvent
= aVisitor
.mEvent
->AsInputEvent();
4258 if (IgnoreInputEventWithModifier(*inputEvent
, true)) {
4261 if (aVisitor
.mEvent
->mMessage
== eMouseDown
) {
4262 if (aVisitor
.mEvent
->AsMouseEvent()->mButtons
==
4263 MouseButtonsFlag::ePrimaryFlag
) {
4264 StartRangeThumbDrag(inputEvent
);
4265 } else if (mIsDraggingRange
) {
4266 CancelRangeThumbDrag();
4269 if (aVisitor
.mEvent
->AsTouchEvent()->mTouches
.Length() == 1) {
4270 StartRangeThumbDrag(inputEvent
);
4271 } else if (mIsDraggingRange
) {
4272 CancelRangeThumbDrag();
4275 aVisitor
.mEvent
->mFlags
.mMultipleActionsPrevented
= true;
4280 if (!mIsDraggingRange
) {
4283 if (PresShell::GetCapturingContent() != this) {
4284 // Someone else grabbed capture.
4285 CancelRangeThumbDrag();
4288 SetValueOfRangeForUserEvent(
4289 rangeFrame
->GetValueAtEventPoint(aVisitor
.mEvent
->AsInputEvent()),
4290 SnapToTickMarks::Yes
);
4291 aVisitor
.mEvent
->mFlags
.mMultipleActionsPrevented
= true;
4296 if (!mIsDraggingRange
) {
4299 // We don't check to see whether we are the capturing content here and
4300 // call CancelRangeThumbDrag() if that is the case. We just finish off
4301 // the drag and set our final value (unless someone has called
4302 // preventDefault() and prevents us getting here).
4303 FinishRangeThumbDrag(aVisitor
.mEvent
->AsInputEvent());
4304 aVisitor
.mEvent
->mFlags
.mMultipleActionsPrevented
= true;
4308 if (mIsDraggingRange
&&
4309 aVisitor
.mEvent
->AsKeyboardEvent()->mKeyCode
== NS_VK_ESCAPE
) {
4310 CancelRangeThumbDrag();
4315 if (mIsDraggingRange
) {
4316 CancelRangeThumbDrag();
4325 void HTMLInputElement::MaybeLoadImage() {
4326 // Our base URI may have changed; claim that our URI changed, and the
4327 // nsImageLoadingContent will decide whether a new image load is warranted.
4329 if (mType
== FormControlType::InputImage
&& GetAttr(nsGkAtoms::src
, uri
) &&
4330 (NS_FAILED(LoadImage(uri
, false, true, eImageLoadType_Normal
,
4331 mSrcTriggeringPrincipal
)) ||
4332 !LoadingEnabled())) {
4333 CancelImageRequests(true);
4337 nsresult
HTMLInputElement::BindToTree(BindContext
& aContext
, nsINode
& aParent
) {
4338 // If we are currently bound to a disconnected subtree root, remove
4339 // ourselves from it first.
4340 if (!mForm
&& mType
== FormControlType::InputRadio
) {
4341 RemoveFromRadioGroup();
4345 nsGenericHTMLFormControlElementWithState::BindToTree(aContext
, aParent
);
4346 NS_ENSURE_SUCCESS(rv
, rv
);
4348 nsImageLoadingContent::BindToTree(aContext
, aParent
);
4350 if (mType
== FormControlType::InputImage
) {
4351 // Our base URI may have changed; claim that our URI changed, and the
4352 // nsImageLoadingContent will decide whether a new image load is warranted.
4353 if (HasAttr(nsGkAtoms::src
)) {
4354 // Mark channel as urgent-start before load image if the image load is
4355 // initaiated by a user interaction.
4356 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
4358 nsContentUtils::AddScriptRunner(
4359 NewRunnableMethod("dom::HTMLInputElement::MaybeLoadImage", this,
4360 &HTMLInputElement::MaybeLoadImage
));
4364 // Add radio to document if we don't have a form already (if we do it's
4365 // already been added into that group)
4366 if (!mForm
&& mType
== FormControlType::InputRadio
) {
4370 // Set direction based on value if dir=auto
4372 SetAutoDirectionality(false);
4375 // An element can't suffer from value missing if it is not in a document.
4376 // We have to check if we suffer from that as we are now in a document.
4377 UpdateValueMissingValidityState();
4379 // If there is a disabled fieldset in the parent chain, the element is now
4380 // barred from constraint validation and can't suffer from value missing
4381 // (call done before).
4382 UpdateBarredFromConstraintValidation();
4384 // And now make sure our state is up to date
4385 UpdateValidityElementStates(true);
4387 if (CreatesDateTimeWidget() && IsInComposedDoc()) {
4388 // Construct Shadow Root so web content can be hidden in the DOM.
4389 AttachAndSetUAShadowRoot(NotifyUAWidgetSetup::Yes
, DelegatesFocus::Yes
);
4392 MaybeDispatchLoginManagerEvents(mForm
);
4397 void HTMLInputElement::MaybeDispatchLoginManagerEvents(HTMLFormElement
* aForm
) {
4398 // Don't disptach the event if the <input> is disconnected
4399 // or belongs to a disconnected form
4400 if (!IsInComposedDoc()) {
4405 Element
* target
= nullptr;
4407 if (mType
== FormControlType::InputPassword
) {
4408 // Don't fire another event if we have a pending event.
4409 if (aForm
&& aForm
->mHasPendingPasswordEvent
) {
4413 // TODO(Bug 1864404): Use one event for formless and form inputs.
4414 eventType
= aForm
? u
"DOMFormHasPassword"_ns
: u
"DOMInputPasswordAdded"_ns
;
4416 target
= aForm
? static_cast<Element
*>(aForm
) : this;
4419 aForm
->mHasPendingPasswordEvent
= true;
4422 } else if (mType
== FormControlType::InputEmail
||
4423 mType
== FormControlType::InputText
) {
4424 // Don't fire a username event if:
4425 // - <input> is not part of a form
4426 // - we have a pending event
4427 // - username only forms are not supported
4428 if (!aForm
|| aForm
->mHasPendingPossibleUsernameEvent
||
4429 !StaticPrefs::signon_usernameOnlyForm_enabled()) {
4433 eventType
= u
"DOMFormHasPossibleUsername"_ns
;
4436 aForm
->mHasPendingPossibleUsernameEvent
= true;
4442 RefPtr
<AsyncEventDispatcher
> dispatcher
= new AsyncEventDispatcher(
4443 target
, eventType
, CanBubble::eYes
, ChromeOnlyDispatch::eYes
);
4444 dispatcher
->PostDOMEvent();
4447 void HTMLInputElement::UnbindFromTree(UnbindContext
& aContext
) {
4448 if (mType
== FormControlType::InputPassword
) {
4449 MaybeFireInputPasswordRemoved();
4452 // If we have a form and are unbound from it,
4453 // nsGenericHTMLFormControlElementWithState::UnbindFromTree() will unset the
4454 // form and that takes care of form's WillRemove so we just have to take care
4455 // of the case where we're removing from the document and we don't
4457 if (!mForm
&& mType
== FormControlType::InputRadio
) {
4458 RemoveFromRadioGroup();
4461 if (CreatesDateTimeWidget() && IsInComposedDoc()) {
4462 NotifyUAWidgetTeardown();
4465 nsImageLoadingContent::UnbindFromTree();
4466 nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext
);
4468 // If we are contained within a disconnected subtree, attempt to add
4469 // ourselves to the subtree root's radio group.
4470 if (!mForm
&& mType
== FormControlType::InputRadio
) {
4474 // GetCurrentDoc is returning nullptr so we can update the value
4475 // missing validity state to reflect we are no longer into a doc.
4476 UpdateValueMissingValidityState();
4477 // We might be no longer disabled because of parent chain changed.
4478 UpdateBarredFromConstraintValidation();
4479 // And now make sure our state is up to date
4480 UpdateValidityElementStates(false);
4484 * @param aType InputElementTypes
4485 * @return true, iff SetRangeText applies to aType as specified at
4486 * https://html.spec.whatwg.org/#concept-input-apply.
4488 static bool SetRangeTextApplies(FormControlType aType
) {
4489 return aType
== FormControlType::InputText
||
4490 aType
== FormControlType::InputSearch
||
4491 aType
== FormControlType::InputUrl
||
4492 aType
== FormControlType::InputTel
||
4493 aType
== FormControlType::InputPassword
;
4496 void HTMLInputElement::HandleTypeChange(FormControlType aNewType
,
4498 FormControlType oldType
= mType
;
4499 MOZ_ASSERT(oldType
!= aNewType
);
4501 mHasBeenTypePassword
=
4502 mHasBeenTypePassword
|| aNewType
== FormControlType::InputPassword
;
4504 if (nsFocusManager
* fm
= nsFocusManager::GetFocusManager()) {
4505 // Input element can represent very different kinds of UIs, and we may
4506 // need to flush styling even when focusing the already focused input
4508 fm
->NeedsFlushBeforeEventHandling(this);
4511 if (oldType
== FormControlType::InputPassword
&&
4512 State().HasState(ElementState::REVEALED
)) {
4513 // Modify the state directly to avoid dispatching events.
4514 RemoveStates(ElementState::REVEALED
, aNotify
);
4517 if (aNewType
== FormControlType::InputFile
||
4518 oldType
== FormControlType::InputFile
) {
4519 if (aNewType
== FormControlType::InputFile
) {
4520 mFileData
.reset(new FileData());
4522 mFileData
->Unlink();
4523 mFileData
= nullptr;
4527 if (oldType
== FormControlType::InputRange
&& mIsDraggingRange
) {
4528 CancelRangeThumbDrag(false);
4531 const ValueModeType oldValueMode
= GetValueMode();
4532 nsAutoString oldValue
;
4533 if (oldValueMode
== VALUE_MODE_VALUE
) {
4534 // Doesn't matter what caller type we pass here, since we know we're not a
4535 // file input anyway.
4536 GetValue(oldValue
, CallerType::NonSystem
);
4539 TextControlState::SelectionProperties sp
;
4541 if (IsSingleLineTextControl(false) && mInputData
.mState
) {
4542 mInputData
.mState
->SyncUpSelectionPropertiesBeforeDestruction();
4543 sp
= mInputData
.mState
->GetSelectionProperties();
4546 // We already have a copy of the value, lets free it and changes the type.
4549 void* memory
= mInputTypeMem
;
4550 mInputType
= InputType::Create(this, mType
, memory
);
4552 if (IsSingleLineTextControl()) {
4553 mInputData
.mState
= TextControlState::Construct(this);
4554 if (!sp
.IsDefault()) {
4555 mInputData
.mState
->SetSelectionProperties(sp
);
4559 // Whether placeholder applies might have changed.
4560 UpdatePlaceholderShownState();
4561 // Whether readonly applies might have changed.
4562 UpdateReadOnlyState(aNotify
);
4563 UpdateCheckedState(aNotify
);
4564 UpdateIndeterminateState(aNotify
);
4565 const bool isDefault
= IsRadioOrCheckbox()
4567 : (mForm
&& mForm
->IsDefaultSubmitElement(this));
4568 SetStates(ElementState::DEFAULT
, isDefault
, aNotify
);
4570 // https://html.spec.whatwg.org/#input-type-change
4571 switch (GetValueMode()) {
4572 case VALUE_MODE_DEFAULT
:
4573 case VALUE_MODE_DEFAULT_ON
:
4574 // 1. If the previous state of the element's type attribute put the value
4575 // IDL attribute in the value mode, and the element's value is not the
4576 // empty string, and the new state of the element's type attribute puts
4577 // the value IDL attribute in either the default mode or the default/on
4578 // mode, then set the element's value content attribute to the
4580 if (oldValueMode
== VALUE_MODE_VALUE
&& !oldValue
.IsEmpty()) {
4581 SetAttr(kNameSpaceID_None
, nsGkAtoms::value
, oldValue
, true);
4584 case VALUE_MODE_VALUE
: {
4585 ValueSetterOptions options
{ValueSetterOption::ByInternalAPI
};
4586 if (!SetRangeTextApplies(oldType
) && SetRangeTextApplies(mType
)) {
4588 ValueSetterOption::MoveCursorToBeginSetSelectionDirectionForward
;
4590 if (oldValueMode
!= VALUE_MODE_VALUE
) {
4591 // 2. Otherwise, if the previous state of the element's type attribute
4592 // put the value IDL attribute in any mode other than the value
4593 // mode, and the new state of the element's type attribute puts the
4594 // value IDL attribute in the value mode, then set the value of the
4595 // element to the value of the value content attribute, if there is
4596 // one, or the empty string otherwise, and then set the control's
4597 // dirty value flag to false.
4599 GetAttr(nsGkAtoms::value
, value
);
4600 SetValueInternal(value
, options
);
4601 SetValueChanged(false);
4602 } else if (mValueChanged
) {
4603 // We're both in the "value" mode state, we need to make no change per
4604 // spec, but due to how we store the value internally we need to call
4605 // SetValueInternal, if our value had changed at all.
4606 // TODO: What should we do if SetValueInternal fails? (The allocation
4607 // may potentially be big, but most likely we've failed to allocate
4608 // before the type change.)
4609 SetValueInternal(oldValue
, options
);
4611 // The value dirty flag is not set, so our value is based on our default
4612 // value. But our default value might be dependent on the type. Make
4613 // sure to set it so that state is consistent.
4614 SetDefaultValueAsValue();
4618 case VALUE_MODE_FILENAME
:
4620 // 3. Otherwise, if the previous state of the element's type attribute
4621 // put the value IDL attribute in any mode other than the filename
4622 // mode, and the new state of the element's type attribute puts the
4623 // value IDL attribute in the filename mode, then set the value of the
4624 // element to the empty string.
4626 // Setting the attribute to the empty string is basically calling
4627 // ClearFiles, but there can't be any files.
4631 // Updating mFocusedValue in consequence:
4632 // If the new type fires a change event on blur, but the previous type
4633 // doesn't, we should set mFocusedValue to the current value.
4634 // Otherwise, if the new type doesn't fire a change event on blur, but the
4635 // previous type does, we should clear out mFocusedValue.
4636 if (MayFireChangeOnBlur(mType
) && !MayFireChangeOnBlur(oldType
)) {
4637 GetValue(mFocusedValue
, CallerType::System
);
4638 } else if (!IsSingleLineTextControl(false, mType
) &&
4639 IsSingleLineTextControl(false, oldType
)) {
4640 mFocusedValue
.Truncate();
4643 // Update or clear our required states since we may have changed from a
4644 // required input type to a non-required input type or viceversa.
4645 if (DoesRequiredApply()) {
4646 const bool isRequired
= HasAttr(nsGkAtoms::required
);
4647 UpdateRequiredState(isRequired
, aNotify
);
4649 RemoveStates(ElementState::REQUIRED_STATES
, aNotify
);
4652 UpdateHasRange(aNotify
);
4654 // Update validity states, but not element state. We'll update
4655 // element state later, as part of this attribute change.
4656 UpdateAllValidityStatesButNotElementState();
4658 UpdateApzAwareFlag();
4660 UpdateBarredFromConstraintValidation();
4662 // Changing type may affect auto directionality, or non-auto directionality
4663 // because of the special-case for <input type=tel>, as specified in
4664 // https://html.spec.whatwg.org/multipage/dom.html#the-directionality
4666 const bool autoDirAssociated
= IsAutoDirectionalityAssociated(mType
);
4667 if (IsAutoDirectionalityAssociated(oldType
) != autoDirAssociated
) {
4668 SetAutoDirectionality(aNotify
);
4670 } else if (oldType
== FormControlType::InputTel
||
4671 mType
== FormControlType::InputTel
) {
4672 RecomputeDirectionality(this, aNotify
);
4675 if (oldType
== FormControlType::InputImage
||
4676 mType
== FormControlType::InputImage
) {
4677 if (oldType
== FormControlType::InputImage
) {
4678 // We're no longer an image input. Cancel our image requests, if we have
4680 CancelImageRequests(aNotify
);
4681 RemoveStates(ElementState::BROKEN
, aNotify
);
4683 // We just got switched to be an image input; we should see whether we
4684 // have an image to load;
4685 bool hasSrc
= false;
4688 if ((hasSrc
= GetAttr(nsGkAtoms::src
, src
))) {
4689 // Mark channel as urgent-start before load image if the image load is
4690 // initiated by a user interaction.
4691 mUseUrgentStartForChannel
= UserActivation::IsHandlingUserInput();
4693 LoadImage(src
, false, aNotify
, eImageLoadType_Normal
,
4694 mSrcTriggeringPrincipal
);
4697 hasSrc
= HasAttr(nsGkAtoms::src
);
4700 AddStates(ElementState::BROKEN
, aNotify
);
4703 // We should update our mapped attribute mapping function.
4704 if (mAttrs
.HasAttrs() && !mAttrs
.IsPendingMappedAttributeEvaluation()) {
4705 mAttrs
.InfallibleMarkAsPendingPresAttributeEvaluation();
4706 if (auto* doc
= GetComposedDoc()) {
4707 doc
->ScheduleForPresAttrEvaluation(this);
4712 MaybeDispatchLoginManagerEvents(mForm
);
4714 if (IsInComposedDoc()) {
4715 if (CreatesDateTimeWidget(oldType
)) {
4716 if (!CreatesDateTimeWidget()) {
4717 // Switch away from date/time type.
4718 NotifyUAWidgetTeardown();
4720 // Switch between date and time.
4721 NotifyUAWidgetSetupOrChange();
4723 } else if (CreatesDateTimeWidget()) {
4724 // Switch to date/time type.
4725 AttachAndSetUAShadowRoot(NotifyUAWidgetSetup::Yes
, DelegatesFocus::Yes
);
4727 // If we're becoming a text control and have focus, make sure to show focus
4729 if (State().HasState(ElementState::FOCUS
) && IsSingleLineTextControl() &&
4730 !IsSingleLineTextControl(/* aExcludePassword = */ false, oldType
)) {
4731 AddStates(ElementState::FOCUSRING
);
4736 void HTMLInputElement::MaybeSnapToTickMark(Decimal
& aValue
) {
4737 nsRangeFrame
* rangeFrame
= do_QueryFrame(GetPrimaryFrame());
4741 auto tickMark
= rangeFrame
->NearestTickMark(aValue
);
4742 if (tickMark
.isNaN()) {
4745 auto rangeFrameSize
= CSSPixel::FromAppUnits(rangeFrame
->GetSize());
4746 CSSCoord rangeTrackLength
;
4747 if (rangeFrame
->IsHorizontal()) {
4748 rangeTrackLength
= rangeFrameSize
.width
;
4750 rangeTrackLength
= rangeFrameSize
.height
;
4752 auto stepBase
= GetStepBase();
4753 auto distanceToTickMark
=
4754 rangeTrackLength
* float(rangeFrame
->GetDoubleAsFractionOfRange(
4755 stepBase
+ (tickMark
- aValue
).abs()));
4756 const CSSCoord
magnetEffectRange(
4757 StaticPrefs::dom_range_element_magnet_effect_threshold());
4758 if (distanceToTickMark
<= magnetEffectRange
) {
4763 void HTMLInputElement::SanitizeValue(nsAString
& aValue
,
4764 SanitizationKind aKind
) const {
4765 NS_ASSERTION(mDoneCreating
, "The element creation should be finished!");
4768 case FormControlType::InputText
:
4769 case FormControlType::InputSearch
:
4770 case FormControlType::InputTel
:
4771 case FormControlType::InputPassword
: {
4774 case FormControlType::InputEmail
: {
4776 aValue
= nsContentUtils::TrimWhitespace
<nsContentUtils::IsHTMLWhitespace
>(
4779 if (Multiple() && !aValue
.IsEmpty()) {
4780 nsAutoString
oldValue(aValue
);
4781 HTMLSplitOnSpacesTokenizer
tokenizer(oldValue
, ',');
4783 aValue
.Append(tokenizer
.nextToken());
4784 while (tokenizer
.hasMoreTokens() ||
4785 tokenizer
.separatorAfterCurrentToken()) {
4787 aValue
.Append(tokenizer
.nextToken());
4791 case FormControlType::InputUrl
: {
4794 aValue
= nsContentUtils::TrimWhitespace
<nsContentUtils::IsHTMLWhitespace
>(
4797 case FormControlType::InputNumber
: {
4798 if (aKind
== SanitizationKind::ForValueSetter
&& !aValue
.IsEmpty() &&
4799 (aValue
.First() == '+' || aValue
.Last() == '.')) {
4800 // A value with a leading plus or trailing dot should fail to parse.
4801 // However, the localized parser accepts this, and when we convert it
4802 // back to a Decimal, it disappears. So, we need to check first.
4804 // FIXME(emilio): Should we just use the unlocalized parser
4805 // (StringToDecimal) for the value setter? Other browsers don't seem to
4806 // allow setting localized strings there, and that way we don't need
4807 // this special-case.
4812 InputType::StringToNumberResult result
=
4813 mInputType
->ConvertStringToNumber(aValue
);
4814 if (!result
.mResult
.isFinite()) {
4819 case SanitizationKind::ForValueGetter
: {
4820 // If the default non-localized algorithm parses the value, then we're
4821 // done, don't un-localize it, to avoid precision loss, and to
4822 // preserve scientific notation as well for example.
4823 if (!result
.mLocalized
) {
4826 // For the <input type=number> value getter, we return the unlocalized
4827 // value if it doesn't parse as StringToDecimal, for compat with other
4830 DebugOnly
<bool> ok
= result
.mResult
.toString(buf
, ArrayLength(buf
));
4831 aValue
.AssignASCII(buf
);
4832 MOZ_ASSERT(ok
, "buf not big enough");
4835 case SanitizationKind::ForDisplay
:
4836 case SanitizationKind::ForValueSetter
: {
4837 // We localize as needed, but if both the localized and unlocalized
4838 // version parse with the generic parser, we just use the unlocalized
4839 // one, to preserve the input as much as possible.
4841 // FIXME(emilio, bug 1622808): Localization should ideally be more
4842 // input-preserving.
4843 nsString localizedValue
;
4844 mInputType
->ConvertNumberToString(result
.mResult
, localizedValue
);
4845 if (!StringToDecimal(localizedValue
).isFinite()) {
4846 aValue
= std::move(localizedValue
);
4853 case FormControlType::InputRange
: {
4854 Decimal minimum
= GetMinimum();
4855 Decimal maximum
= GetMaximum();
4856 MOZ_ASSERT(minimum
.isFinite() && maximum
.isFinite(),
4857 "type=range should have a default maximum/minimum");
4859 // We use this to avoid modifying the string unnecessarily, since that
4860 // may introduce rounding. This is set to true only if the value we
4861 // parse out from aValue needs to be sanitized.
4862 bool needSanitization
= false;
4864 Decimal value
= mInputType
->ConvertStringToNumber(aValue
).mResult
;
4865 if (!value
.isFinite()) {
4866 needSanitization
= true;
4867 // Set value to midway between minimum and maximum.
4868 value
= maximum
<= minimum
? minimum
4869 : minimum
+ (maximum
- minimum
) / Decimal(2);
4870 } else if (value
< minimum
|| maximum
< minimum
) {
4871 needSanitization
= true;
4873 } else if (value
> maximum
) {
4874 needSanitization
= true;
4878 Decimal step
= GetStep();
4879 if (step
!= kStepAny
) {
4880 Decimal stepBase
= GetStepBase();
4881 // There could be rounding issues below when dealing with fractional
4882 // numbers, but let's ignore that until ECMAScript supplies us with a
4883 // decimal number type.
4884 Decimal deltaToStep
= NS_floorModulo(value
- stepBase
, step
);
4885 if (deltaToStep
!= Decimal(0)) {
4886 // "suffering from a step mismatch"
4887 // Round the element's value to the nearest number for which the
4888 // element would not suffer from a step mismatch, and which is
4889 // greater than or equal to the minimum, and, if the maximum is not
4890 // less than the minimum, which is less than or equal to the
4891 // maximum, if there is a number that matches these constraints:
4892 MOZ_ASSERT(deltaToStep
> Decimal(0),
4893 "stepBelow/stepAbove will be wrong");
4894 Decimal stepBelow
= value
- deltaToStep
;
4895 Decimal stepAbove
= value
- deltaToStep
+ step
;
4896 Decimal halfStep
= step
/ Decimal(2);
4897 bool stepAboveIsClosest
= (stepAbove
- value
) <= halfStep
;
4898 bool stepAboveInRange
= stepAbove
>= minimum
&& stepAbove
<= maximum
;
4899 bool stepBelowInRange
= stepBelow
>= minimum
&& stepBelow
<= maximum
;
4901 if ((stepAboveIsClosest
|| !stepBelowInRange
) && stepAboveInRange
) {
4902 needSanitization
= true;
4904 } else if ((!stepAboveIsClosest
|| !stepAboveInRange
) &&
4906 needSanitization
= true;
4912 if (needSanitization
) {
4914 DebugOnly
<bool> ok
= value
.toString(buf
, ArrayLength(buf
));
4915 aValue
.AssignASCII(buf
);
4916 MOZ_ASSERT(ok
, "buf not big enough");
4919 case FormControlType::InputDate
: {
4920 if (!aValue
.IsEmpty() && !IsValidDate(aValue
)) {
4924 case FormControlType::InputTime
: {
4925 if (!aValue
.IsEmpty() && !IsValidTime(aValue
)) {
4929 case FormControlType::InputMonth
: {
4930 if (!aValue
.IsEmpty() && !IsValidMonth(aValue
)) {
4934 case FormControlType::InputWeek
: {
4935 if (!aValue
.IsEmpty() && !IsValidWeek(aValue
)) {
4939 case FormControlType::InputDatetimeLocal
: {
4940 if (!aValue
.IsEmpty() && !IsValidDateTimeLocal(aValue
)) {
4943 NormalizeDateTimeLocal(aValue
);
4946 case FormControlType::InputColor
: {
4947 if (IsValidSimpleColor(aValue
)) {
4948 ToLowerCase(aValue
);
4950 // Set default (black) color, if aValue wasn't parsed correctly.
4951 aValue
.AssignLiteral("#000000");
4959 Maybe
<nscolor
> HTMLInputElement::ParseSimpleColor(const nsAString
& aColor
) {
4960 // Input color string should be 7 length (i.e. a string representing a valid
4962 if (aColor
.Length() != 7 || aColor
.First() != '#') {
4966 const nsAString
& withoutHash
= StringTail(aColor
, 6);
4968 if (!NS_HexToRGBA(withoutHash
, nsHexColorType::NoAlpha
, &color
)) {
4975 bool HTMLInputElement::IsValidSimpleColor(const nsAString
& aValue
) const {
4976 if (aValue
.Length() != 7 || aValue
.First() != '#') {
4980 for (int i
= 1; i
< 7; ++i
) {
4981 if (!IsAsciiDigit(aValue
[i
]) && !(aValue
[i
] >= 'a' && aValue
[i
] <= 'f') &&
4982 !(aValue
[i
] >= 'A' && aValue
[i
] <= 'F')) {
4989 bool HTMLInputElement::IsLeapYear(uint32_t aYear
) const {
4990 if ((aYear
% 4 == 0 && aYear
% 100 != 0) || (aYear
% 400 == 0)) {
4996 uint32_t HTMLInputElement::DayOfWeek(uint32_t aYear
, uint32_t aMonth
,
4997 uint32_t aDay
, bool isoWeek
) const {
4998 MOZ_ASSERT(1 <= aMonth
&& aMonth
<= 12, "month is in 1..12");
4999 MOZ_ASSERT(1 <= aDay
&& aDay
<= 31, "day is in 1..31");
5001 // Tomohiko Sakamoto algorithm.
5002 int monthTable
[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
5003 aYear
-= aMonth
< 3;
5005 uint32_t day
= (aYear
+ aYear
/ 4 - aYear
/ 100 + aYear
/ 400 +
5006 monthTable
[aMonth
- 1] + aDay
) %
5010 return ((day
+ 6) % 7) + 1;
5016 uint32_t HTMLInputElement::MaximumWeekInYear(uint32_t aYear
) const {
5017 int day
= DayOfWeek(aYear
, 1, 1, true); // January 1.
5018 // A year starting on Thursday or a leap year starting on Wednesday has 53
5019 // weeks. All other years have 52 weeks.
5020 return day
== 4 || (day
== 3 && IsLeapYear(aYear
)) ? kMaximumWeekInYear
5021 : kMaximumWeekInYear
- 1;
5024 bool HTMLInputElement::IsValidWeek(const nsAString
& aValue
) const {
5025 uint32_t year
, week
;
5026 return ParseWeek(aValue
, &year
, &week
);
5029 bool HTMLInputElement::IsValidMonth(const nsAString
& aValue
) const {
5030 uint32_t year
, month
;
5031 return ParseMonth(aValue
, &year
, &month
);
5034 bool HTMLInputElement::IsValidDate(const nsAString
& aValue
) const {
5035 uint32_t year
, month
, day
;
5036 return ParseDate(aValue
, &year
, &month
, &day
);
5039 bool HTMLInputElement::IsValidDateTimeLocal(const nsAString
& aValue
) const {
5040 uint32_t year
, month
, day
, time
;
5041 return ParseDateTimeLocal(aValue
, &year
, &month
, &day
, &time
);
5044 bool HTMLInputElement::ParseYear(const nsAString
& aValue
,
5045 uint32_t* aYear
) const {
5046 if (aValue
.Length() < 4) {
5050 return DigitSubStringToNumber(aValue
, 0, aValue
.Length(), aYear
) &&
5054 bool HTMLInputElement::ParseMonth(const nsAString
& aValue
, uint32_t* aYear
,
5055 uint32_t* aMonth
) const {
5056 // Parse the year, month values out a string formatted as 'yyyy-mm'.
5057 if (aValue
.Length() < 7) {
5061 uint32_t endOfYearOffset
= aValue
.Length() - 3;
5062 if (aValue
[endOfYearOffset
] != '-') {
5066 const nsAString
& yearStr
= Substring(aValue
, 0, endOfYearOffset
);
5067 if (!ParseYear(yearStr
, aYear
)) {
5071 return DigitSubStringToNumber(aValue
, endOfYearOffset
+ 1, 2, aMonth
) &&
5072 *aMonth
> 0 && *aMonth
<= 12;
5075 bool HTMLInputElement::ParseWeek(const nsAString
& aValue
, uint32_t* aYear
,
5076 uint32_t* aWeek
) const {
5077 // Parse the year, month values out a string formatted as 'yyyy-Www'.
5078 if (aValue
.Length() < 8) {
5082 uint32_t endOfYearOffset
= aValue
.Length() - 4;
5083 if (aValue
[endOfYearOffset
] != '-') {
5087 if (aValue
[endOfYearOffset
+ 1] != 'W') {
5091 const nsAString
& yearStr
= Substring(aValue
, 0, endOfYearOffset
);
5092 if (!ParseYear(yearStr
, aYear
)) {
5096 return DigitSubStringToNumber(aValue
, endOfYearOffset
+ 2, 2, aWeek
) &&
5097 *aWeek
> 0 && *aWeek
<= MaximumWeekInYear(*aYear
);
5100 bool HTMLInputElement::ParseDate(const nsAString
& aValue
, uint32_t* aYear
,
5101 uint32_t* aMonth
, uint32_t* aDay
) const {
5103 * Parse the year, month, day values out a date string formatted as
5104 * yyyy-mm-dd. -The year must be 4 or more digits long, and year > 0 -The
5105 * month must be exactly 2 digits long, and 01 <= month <= 12 -The day must be
5106 * exactly 2 digit long, and 01 <= day <= maxday Where maxday is the number of
5107 * days in the month 'month' and year 'year'
5109 if (aValue
.Length() < 10) {
5113 uint32_t endOfMonthOffset
= aValue
.Length() - 3;
5114 if (aValue
[endOfMonthOffset
] != '-') {
5118 const nsAString
& yearMonthStr
= Substring(aValue
, 0, endOfMonthOffset
);
5119 if (!ParseMonth(yearMonthStr
, aYear
, aMonth
)) {
5123 return DigitSubStringToNumber(aValue
, endOfMonthOffset
+ 1, 2, aDay
) &&
5124 *aDay
> 0 && *aDay
<= NumberOfDaysInMonth(*aMonth
, *aYear
);
5127 bool HTMLInputElement::ParseDateTimeLocal(const nsAString
& aValue
,
5128 uint32_t* aYear
, uint32_t* aMonth
,
5130 uint32_t* aTime
) const {
5131 // Parse the year, month, day and time values out a string formatted as
5132 // 'yyyy-mm-ddThh:mm[:ss.s] or 'yyyy-mm-dd hh:mm[:ss.s]', where fractions of
5133 // seconds can be 1 to 3 digits.
5134 // The minimum length allowed is 16, which is of the form 'yyyy-mm-ddThh:mm'
5135 // or 'yyyy-mm-dd hh:mm'.
5136 if (aValue
.Length() < 16) {
5140 int32_t sepIndex
= aValue
.FindChar('T');
5141 if (sepIndex
== -1) {
5142 sepIndex
= aValue
.FindChar(' ');
5144 if (sepIndex
== -1) {
5149 const nsAString
& dateStr
= Substring(aValue
, 0, sepIndex
);
5150 if (!ParseDate(dateStr
, aYear
, aMonth
, aDay
)) {
5154 const nsAString
& timeStr
=
5155 Substring(aValue
, sepIndex
+ 1, aValue
.Length() - sepIndex
+ 1);
5156 if (!ParseTime(timeStr
, aTime
)) {
5163 void HTMLInputElement::NormalizeDateTimeLocal(nsAString
& aValue
) const {
5164 if (aValue
.IsEmpty()) {
5168 // Use 'T' as the separator between date string and time string.
5169 int32_t sepIndex
= aValue
.FindChar(' ');
5170 if (sepIndex
!= -1) {
5171 aValue
.ReplaceLiteral(sepIndex
, 1, u
"T");
5173 sepIndex
= aValue
.FindChar('T');
5176 // Time expressed as the shortest possible string, which is hh:mm.
5177 if ((aValue
.Length() - sepIndex
) == 6) {
5181 // Fractions of seconds part is optional, ommit it if it's 0.
5182 if ((aValue
.Length() - sepIndex
) > 9) {
5183 const uint32_t millisecSepIndex
= sepIndex
+ 9;
5184 uint32_t milliseconds
;
5185 if (!DigitSubStringToNumber(aValue
, millisecSepIndex
+ 1,
5186 aValue
.Length() - (millisecSepIndex
+ 1),
5191 if (milliseconds
!= 0) {
5195 aValue
.Cut(millisecSepIndex
, aValue
.Length() - millisecSepIndex
);
5198 // Seconds part is optional, ommit it if it's 0.
5199 const uint32_t secondSepIndex
= sepIndex
+ 6;
5201 if (!DigitSubStringToNumber(aValue
, secondSepIndex
+ 1,
5202 aValue
.Length() - (secondSepIndex
+ 1),
5211 aValue
.Cut(secondSepIndex
, aValue
.Length() - secondSepIndex
);
5214 double HTMLInputElement::DaysSinceEpochFromWeek(uint32_t aYear
,
5215 uint32_t aWeek
) const {
5216 double days
= JS::DayFromYear(aYear
) + (aWeek
- 1) * 7;
5217 uint32_t dayOneIsoWeekday
= DayOfWeek(aYear
, 1, 1, true);
5219 // If day one of that year is on/before Thursday, we should subtract the
5220 // days that belong to last year in our first week, otherwise, our first
5221 // days belong to last year's last week, and we should add those days
5223 if (dayOneIsoWeekday
<= 4) {
5224 days
-= (dayOneIsoWeekday
- 1);
5226 days
+= (7 - dayOneIsoWeekday
+ 1);
5232 uint32_t HTMLInputElement::NumberOfDaysInMonth(uint32_t aMonth
,
5233 uint32_t aYear
) const {
5235 * Returns the number of days in a month.
5236 * Months that are |longMonths| always have 31 days.
5237 * Months that are not |longMonths| have 30 days except February (month 2).
5238 * February has 29 days during leap years which are years that are divisible
5239 * by 400. or divisible by 100 and 4. February has 28 days otherwise.
5242 static const bool longMonths
[] = {true, false, true, false, true, false,
5243 true, true, false, true, false, true};
5244 MOZ_ASSERT(aMonth
<= 12 && aMonth
> 0);
5246 if (longMonths
[aMonth
- 1]) {
5254 return IsLeapYear(aYear
) ? 29 : 28;
5258 bool HTMLInputElement::DigitSubStringToNumber(const nsAString
& aStr
,
5259 uint32_t aStart
, uint32_t aLen
,
5260 uint32_t* aRetVal
) {
5261 MOZ_ASSERT(aStr
.Length() > (aStart
+ aLen
- 1));
5263 for (uint32_t offset
= 0; offset
< aLen
; ++offset
) {
5264 if (!IsAsciiDigit(aStr
[aStart
+ offset
])) {
5270 *aRetVal
= static_cast<uint32_t>(
5271 PromiseFlatString(Substring(aStr
, aStart
, aLen
)).ToInteger(&ec
));
5273 return NS_SUCCEEDED(ec
);
5276 bool HTMLInputElement::IsValidTime(const nsAString
& aValue
) const {
5277 return ParseTime(aValue
, nullptr);
5281 bool HTMLInputElement::ParseTime(const nsAString
& aValue
, uint32_t* aResult
) {
5282 /* The string must have the following parts:
5283 * - HOURS: two digits, value being in [0, 23];
5285 * - MINUTES: two digits, value being in [0, 59];
5288 * - SECONDS: two digits, value being in [0, 59];
5291 * - FRACTIONAL SECONDS: one to three digits, no value range.
5294 // The following format is the shorter one allowed: "HH:MM".
5295 if (aValue
.Length() < 5) {
5300 if (!DigitSubStringToNumber(aValue
, 0, 2, &hours
) || hours
> 23) {
5304 // Hours/minutes separator.
5305 if (aValue
[2] != ':') {
5310 if (!DigitSubStringToNumber(aValue
, 3, 2, &minutes
) || minutes
> 59) {
5314 if (aValue
.Length() == 5) {
5316 *aResult
= ((hours
* 60) + minutes
) * 60000;
5321 // The following format is the next shorter one: "HH:MM:SS".
5322 if (aValue
.Length() < 8 || aValue
[5] != ':') {
5327 if (!DigitSubStringToNumber(aValue
, 6, 2, &seconds
) || seconds
> 59) {
5331 if (aValue
.Length() == 8) {
5333 *aResult
= (((hours
* 60) + minutes
) * 60 + seconds
) * 1000;
5338 // The string must follow this format now: "HH:MM:SS.{s,ss,sss}".
5339 // There can be 1 to 3 digits for the fractions of seconds.
5340 if (aValue
.Length() == 9 || aValue
.Length() > 12 || aValue
[8] != '.') {
5344 uint32_t fractionsSeconds
;
5345 if (!DigitSubStringToNumber(aValue
, 9, aValue
.Length() - 9,
5346 &fractionsSeconds
)) {
5351 *aResult
= (((hours
* 60) + minutes
) * 60 + seconds
) * 1000 +
5352 // NOTE: there is 10.0 instead of 10 and static_cast<int> because
5353 // some old [and stupid] compilers can't just do the right thing.
5355 pow(10.0, static_cast<int>(3 - (aValue
.Length() - 9)));
5362 bool HTMLInputElement::IsDateTimeTypeSupported(
5363 FormControlType aDateTimeInputType
) {
5364 switch (aDateTimeInputType
) {
5365 case FormControlType::InputDate
:
5366 case FormControlType::InputTime
:
5367 case FormControlType::InputDatetimeLocal
:
5369 case FormControlType::InputMonth
:
5370 case FormControlType::InputWeek
:
5371 return StaticPrefs::dom_forms_datetime_others();
5377 void HTMLInputElement::GetLastInteractiveValue(nsAString
& aValue
) {
5378 if (mLastValueChangeWasInteractive
) {
5379 return GetValue(aValue
, CallerType::System
);
5381 if (TextControlState
* state
= GetEditorState()) {
5382 return aValue
.Assign(
5383 state
->LastInteractiveValueIfLastChangeWasNonInteractive());
5388 bool HTMLInputElement::ParseAttribute(int32_t aNamespaceID
, nsAtom
* aAttribute
,
5389 const nsAString
& aValue
,
5390 nsIPrincipal
* aMaybeScriptedPrincipal
,
5391 nsAttrValue
& aResult
) {
5392 // We can't make these static_asserts because kInputDefaultType and
5393 // kInputTypeTable aren't constexpr.
5395 FormControlType(kInputDefaultType
->value
) == FormControlType::InputText
,
5396 "Someone forgot to update kInputDefaultType when adding a new "
5398 MOZ_ASSERT(kInputTypeTable
[ArrayLength(kInputTypeTable
) - 1].tag
== nullptr,
5399 "Last entry in the table must be the nullptr guard");
5400 MOZ_ASSERT(FormControlType(
5401 kInputTypeTable
[ArrayLength(kInputTypeTable
) - 2].value
) ==
5402 FormControlType::InputText
,
5403 "Next to last entry in the table must be the \"text\" entry");
5405 if (aNamespaceID
== kNameSpaceID_None
) {
5406 if (aAttribute
== nsGkAtoms::type
) {
5407 aResult
.ParseEnumValue(aValue
, kInputTypeTable
, false, kInputDefaultType
);
5408 auto newType
= FormControlType(aResult
.GetEnumValue());
5409 if (IsDateTimeInputType(newType
) && !IsDateTimeTypeSupported(newType
)) {
5410 // There's no public way to set an nsAttrValue to an enum value, but we
5411 // can just re-parse with a table that doesn't have any types other than
5413 aResult
.ParseEnumValue(aValue
, kInputDefaultType
, false,
5419 if (aAttribute
== nsGkAtoms::width
) {
5420 return aResult
.ParseHTMLDimension(aValue
);
5422 if (aAttribute
== nsGkAtoms::height
) {
5423 return aResult
.ParseHTMLDimension(aValue
);
5425 if (aAttribute
== nsGkAtoms::maxlength
) {
5426 return aResult
.ParseNonNegativeIntValue(aValue
);
5428 if (aAttribute
== nsGkAtoms::minlength
) {
5429 return aResult
.ParseNonNegativeIntValue(aValue
);
5431 if (aAttribute
== nsGkAtoms::size
) {
5432 return aResult
.ParsePositiveIntValue(aValue
);
5434 if (aAttribute
== nsGkAtoms::align
) {
5435 return ParseAlignValue(aValue
, aResult
);
5437 if (aAttribute
== nsGkAtoms::formmethod
) {
5438 return aResult
.ParseEnumValue(aValue
, kFormMethodTable
, false);
5440 if (aAttribute
== nsGkAtoms::formenctype
) {
5441 return aResult
.ParseEnumValue(aValue
, kFormEnctypeTable
, false);
5443 if (aAttribute
== nsGkAtoms::autocomplete
) {
5444 aResult
.ParseAtomArray(aValue
);
5447 if (aAttribute
== nsGkAtoms::capture
) {
5448 return aResult
.ParseEnumValue(aValue
, kCaptureTable
, false,
5451 if (ParseImageAttribute(aAttribute
, aValue
, aResult
)) {
5452 // We have to call |ParseImageAttribute| unconditionally since we
5453 // don't know if we're going to have a type="image" attribute yet,
5454 // (or could have it set dynamically in the future). See bug
5460 return TextControlElement::ParseAttribute(aNamespaceID
, aAttribute
, aValue
,
5461 aMaybeScriptedPrincipal
, aResult
);
5464 void HTMLInputElement::ImageInputMapAttributesIntoRule(
5465 MappedDeclarationsBuilder
& aBuilder
) {
5466 nsGenericHTMLFormControlElementWithState::MapImageBorderAttributeInto(
5468 nsGenericHTMLFormControlElementWithState::MapImageMarginAttributeInto(
5470 nsGenericHTMLFormControlElementWithState::MapImageSizeAttributesInto(
5471 aBuilder
, MapAspectRatio::Yes
);
5472 // Images treat align as "float"
5473 nsGenericHTMLFormControlElementWithState::MapImageAlignAttributeInto(
5475 nsGenericHTMLFormControlElementWithState::MapCommonAttributesInto(aBuilder
);
5478 nsChangeHint
HTMLInputElement::GetAttributeChangeHint(const nsAtom
* aAttribute
,
5479 int32_t aModType
) const {
5480 nsChangeHint retval
=
5481 nsGenericHTMLFormControlElementWithState::GetAttributeChangeHint(
5482 aAttribute
, aModType
);
5484 const bool isAdditionOrRemoval
=
5485 aModType
== MutationEvent_Binding::ADDITION
||
5486 aModType
== MutationEvent_Binding::REMOVAL
;
5488 const bool reconstruct
= [&] {
5489 if (aAttribute
== nsGkAtoms::type
) {
5493 if (PlaceholderApplies() && aAttribute
== nsGkAtoms::placeholder
&&
5494 isAdditionOrRemoval
) {
5495 // We need to re-create our placeholder text.
5499 if (mType
== FormControlType::InputFile
&&
5500 aAttribute
== nsGkAtoms::webkitdirectory
) {
5501 // The presence or absence of the 'directory' attribute determines what
5502 // value we show in the file label when empty, via GetDisplayFileName.
5506 if (mType
== FormControlType::InputImage
&& isAdditionOrRemoval
&&
5507 (aAttribute
== nsGkAtoms::alt
|| aAttribute
== nsGkAtoms::value
)) {
5508 // We might need to rebuild our alt text. Just go ahead and
5509 // reconstruct our frame. This should be quite rare..
5516 retval
|= nsChangeHint_ReconstructFrame
;
5517 } else if (aAttribute
== nsGkAtoms::value
) {
5518 retval
|= NS_STYLE_HINT_REFLOW
;
5519 } else if (aAttribute
== nsGkAtoms::size
&& IsSingleLineTextControl(false)) {
5520 retval
|= NS_STYLE_HINT_REFLOW
;
5526 NS_IMETHODIMP_(bool)
5527 HTMLInputElement::IsAttributeMapped(const nsAtom
* aAttribute
) const {
5528 static const MappedAttributeEntry attributes
[] = {
5533 static const MappedAttributeEntry
* const map
[] = {
5535 sCommonAttributeMap
,
5536 sImageMarginSizeAttributeMap
,
5537 sImageBorderAttributeMap
,
5540 return FindAttributeDependence(aAttribute
, map
);
5543 nsMapRuleToAttributesFunc
HTMLInputElement::GetAttributeMappingFunction()
5545 // GetAttributeChangeHint guarantees that changes to mType will trigger a
5546 // reframe, and we update the mapping function in our mapped attrs when our
5547 // type changes, so it's safe to condition our attribute mapping function on
5549 if (mType
== FormControlType::InputImage
) {
5550 return &ImageInputMapAttributesIntoRule
;
5553 return &MapCommonAttributesInto
;
5556 // Directory picking methods:
5558 already_AddRefed
<Promise
> HTMLInputElement::GetFilesAndDirectories(
5560 if (mType
!= FormControlType::InputFile
) {
5561 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5565 nsCOMPtr
<nsIGlobalObject
> global
= OwnerDoc()->GetScopeObject();
5571 RefPtr
<Promise
> p
= Promise::Create(global
, aRv
);
5576 const nsTArray
<OwningFileOrDirectory
>& filesAndDirs
=
5577 GetFilesOrDirectoriesInternal();
5579 Sequence
<OwningFileOrDirectory
> filesAndDirsSeq
;
5581 if (!filesAndDirsSeq
.SetLength(filesAndDirs
.Length(), fallible
)) {
5582 p
->MaybeReject(NS_ERROR_OUT_OF_MEMORY
);
5586 for (uint32_t i
= 0; i
< filesAndDirs
.Length(); ++i
) {
5587 if (filesAndDirs
[i
].IsDirectory()) {
5588 RefPtr
<Directory
> directory
= filesAndDirs
[i
].GetAsDirectory();
5590 // In future we could refactor SetFilePickerFiltersFromAccept to return a
5591 // semicolon separated list of file extensions and include that in the
5592 // filter string passed here.
5593 directory
->SetContentFilters(u
"filter-out-sensitive"_ns
);
5594 filesAndDirsSeq
[i
].SetAsDirectory() = directory
;
5596 MOZ_ASSERT(filesAndDirs
[i
].IsFile());
5598 // This file was directly selected by the user, so don't filter it.
5599 filesAndDirsSeq
[i
].SetAsFile() = filesAndDirs
[i
].GetAsFile();
5603 p
->MaybeResolve(filesAndDirsSeq
);
5607 // Controllers Methods
5609 nsIControllers
* HTMLInputElement::GetControllers(ErrorResult
& aRv
) {
5610 // XXX: what about type "file"?
5611 if (IsSingleLineTextControl(false)) {
5612 if (!mControllers
) {
5613 mControllers
= new nsXULControllers();
5614 if (!mControllers
) {
5615 aRv
.Throw(NS_ERROR_FAILURE
);
5619 RefPtr
<nsBaseCommandController
> commandController
=
5620 nsBaseCommandController::CreateEditorController();
5621 if (!commandController
) {
5622 aRv
.Throw(NS_ERROR_FAILURE
);
5626 mControllers
->AppendController(commandController
);
5628 commandController
= nsBaseCommandController::CreateEditingController();
5629 if (!commandController
) {
5630 aRv
.Throw(NS_ERROR_FAILURE
);
5634 mControllers
->AppendController(commandController
);
5638 return mControllers
;
5641 nsresult
HTMLInputElement::GetControllers(nsIControllers
** aResult
) {
5642 NS_ENSURE_ARG_POINTER(aResult
);
5645 RefPtr
<nsIControllers
> controller
= GetControllers(rv
);
5646 controller
.forget(aResult
);
5647 return rv
.StealNSResult();
5650 int32_t HTMLInputElement::InputTextLength(CallerType aCallerType
) {
5652 GetValue(val
, aCallerType
);
5653 return val
.Length();
5656 void HTMLInputElement::SetSelectionRange(uint32_t aSelectionStart
,
5657 uint32_t aSelectionEnd
,
5658 const Optional
<nsAString
>& aDirection
,
5660 if (!SupportsTextSelection()) {
5661 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5665 TextControlState
* state
= GetEditorState();
5666 MOZ_ASSERT(state
, "SupportsTextSelection() returned true!");
5667 state
->SetSelectionRange(aSelectionStart
, aSelectionEnd
, aDirection
, aRv
);
5670 void HTMLInputElement::SetRangeText(const nsAString
& aReplacement
,
5672 if (!SupportsTextSelection()) {
5673 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5677 TextControlState
* state
= GetEditorState();
5678 MOZ_ASSERT(state
, "SupportsTextSelection() returned true!");
5679 state
->SetRangeText(aReplacement
, aRv
);
5682 void HTMLInputElement::SetRangeText(const nsAString
& aReplacement
,
5683 uint32_t aStart
, uint32_t aEnd
,
5684 SelectionMode aSelectMode
,
5686 if (!SupportsTextSelection()) {
5687 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5691 TextControlState
* state
= GetEditorState();
5692 MOZ_ASSERT(state
, "SupportsTextSelection() returned true!");
5693 state
->SetRangeText(aReplacement
, aStart
, aEnd
, aSelectMode
, aRv
);
5696 void HTMLInputElement::GetValueFromSetRangeText(nsAString
& aValue
) {
5697 GetNonFileValueInternal(aValue
);
5700 nsresult
HTMLInputElement::SetValueFromSetRangeText(const nsAString
& aValue
) {
5701 return SetValueInternal(aValue
, {ValueSetterOption::ByContentAPI
,
5702 ValueSetterOption::BySetRangeTextAPI
,
5703 ValueSetterOption::SetValueChanged
});
5706 Nullable
<uint32_t> HTMLInputElement::GetSelectionStart(ErrorResult
& aRv
) {
5707 if (!SupportsTextSelection()) {
5708 return Nullable
<uint32_t>();
5711 uint32_t selStart
= GetSelectionStartIgnoringType(aRv
);
5713 return Nullable
<uint32_t>();
5716 return Nullable
<uint32_t>(selStart
);
5719 uint32_t HTMLInputElement::GetSelectionStartIgnoringType(ErrorResult
& aRv
) {
5720 uint32_t selEnd
, selStart
;
5721 GetSelectionRange(&selStart
, &selEnd
, aRv
);
5725 void HTMLInputElement::SetSelectionStart(
5726 const Nullable
<uint32_t>& aSelectionStart
, ErrorResult
& aRv
) {
5727 if (!SupportsTextSelection()) {
5728 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5732 TextControlState
* state
= GetEditorState();
5733 MOZ_ASSERT(state
, "SupportsTextSelection() returned true!");
5734 state
->SetSelectionStart(aSelectionStart
, aRv
);
5737 Nullable
<uint32_t> HTMLInputElement::GetSelectionEnd(ErrorResult
& aRv
) {
5738 if (!SupportsTextSelection()) {
5739 return Nullable
<uint32_t>();
5742 uint32_t selEnd
= GetSelectionEndIgnoringType(aRv
);
5744 return Nullable
<uint32_t>();
5747 return Nullable
<uint32_t>(selEnd
);
5750 uint32_t HTMLInputElement::GetSelectionEndIgnoringType(ErrorResult
& aRv
) {
5751 uint32_t selEnd
, selStart
;
5752 GetSelectionRange(&selStart
, &selEnd
, aRv
);
5756 void HTMLInputElement::SetSelectionEnd(const Nullable
<uint32_t>& aSelectionEnd
,
5758 if (!SupportsTextSelection()) {
5759 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5763 TextControlState
* state
= GetEditorState();
5764 MOZ_ASSERT(state
, "SupportsTextSelection() returned true!");
5765 state
->SetSelectionEnd(aSelectionEnd
, aRv
);
5768 void HTMLInputElement::GetSelectionRange(uint32_t* aSelectionStart
,
5769 uint32_t* aSelectionEnd
,
5771 TextControlState
* state
= GetEditorState();
5773 // Not a text control.
5774 aRv
.Throw(NS_ERROR_UNEXPECTED
);
5778 state
->GetSelectionRange(aSelectionStart
, aSelectionEnd
, aRv
);
5781 void HTMLInputElement::GetSelectionDirection(nsAString
& aDirection
,
5783 if (!SupportsTextSelection()) {
5784 aDirection
.SetIsVoid(true);
5788 TextControlState
* state
= GetEditorState();
5789 MOZ_ASSERT(state
, "SupportsTextSelection came back true!");
5790 state
->GetSelectionDirectionString(aDirection
, aRv
);
5793 void HTMLInputElement::SetSelectionDirection(const nsAString
& aDirection
,
5795 if (!SupportsTextSelection()) {
5796 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
5800 TextControlState
* state
= GetEditorState();
5801 MOZ_ASSERT(state
, "SupportsTextSelection came back true!");
5802 state
->SetSelectionDirection(aDirection
, aRv
);
5805 // https://html.spec.whatwg.org/multipage/input.html#dom-input-showpicker
5806 void HTMLInputElement::ShowPicker(ErrorResult
& aRv
) {
5807 // Step 1. If this is not mutable, then throw an "InvalidStateError"
5810 return aRv
.ThrowInvalidStateError(
5811 "This input is either disabled or readonly.");
5814 // Step 2. If this's relevant settings object's origin is not same origin with
5815 // this's relevant settings object's top-level origin, and this's type
5816 // attribute is not in the File Upload state or Color state, then throw a
5817 // "SecurityError" DOMException.
5818 if (mType
!= FormControlType::InputFile
&&
5819 mType
!= FormControlType::InputColor
) {
5820 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
5821 WindowGlobalChild
* windowGlobalChild
=
5822 window
? window
->GetWindowGlobalChild() : nullptr;
5823 if (!windowGlobalChild
|| !windowGlobalChild
->SameOriginWithTop()) {
5824 return aRv
.ThrowSecurityError(
5825 "Call was blocked because the current origin isn't same-origin with "
5830 // Step 3. If this's relevant global object does not have transient
5831 // activation, then throw a "NotAllowedError" DOMException.
5832 if (!OwnerDoc()->HasValidTransientUserGestureActivation()) {
5833 return aRv
.ThrowNotAllowedError(
5834 "Call was blocked due to lack of user activation.");
5837 // Step 4. Show the picker, if applicable, for this.
5839 // https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
5840 // To show the picker, if applicable for an input element element:
5842 // Step 1. Assert: element's relevant global object has transient activation.
5843 // Step 2. If element is not mutable, then return.
5846 // Step 3. If element's type attribute is in the File Upload state, then run
5847 // these steps in parallel:
5848 if (mType
== FormControlType::InputFile
) {
5849 FilePickerType type
= FILE_PICKER_FILE
;
5850 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
5851 HasAttr(nsGkAtoms::webkitdirectory
)) {
5852 type
= FILE_PICKER_DIRECTORY
;
5854 InitFilePicker(type
);
5858 // Step 4. Otherwise, the user agent should show any relevant user interface
5859 // for selecting a value for element, in the way it normally would when the
5860 // user interacts with the control
5861 if (mType
== FormControlType::InputColor
) {
5866 if (!IsInComposedDoc()) {
5870 if (IsDateTimeTypeSupported(mType
)) {
5871 if (CreatesDateTimeWidget()) {
5872 if (RefPtr
<Element
> dateTimeBoxElement
= GetDateTimeBoxElement()) {
5873 // Event is dispatched to closed-shadow tree and doesn't bubble.
5874 RefPtr
<Document
> doc
= dateTimeBoxElement
->OwnerDoc();
5875 nsContentUtils::DispatchTrustedEvent(doc
, dateTimeBoxElement
,
5876 u
"MozDateTimeShowPickerForJS"_ns
,
5877 CanBubble::eNo
, Cancelable::eNo
);
5880 DateTimeValue value
;
5881 GetDateTimeInputBoxValue(value
);
5882 OpenDateTimePicker(value
);
5887 #ifdef ACCESSIBILITY
5888 /*static*/ nsresult
FireEventForAccessibility(HTMLInputElement
* aTarget
,
5889 EventMessage aEventMessage
) {
5890 Element
* element
= static_cast<Element
*>(aTarget
);
5891 return nsContentUtils::DispatchTrustedEvent
<WidgetEvent
>(
5892 element
->OwnerDoc(), element
, aEventMessage
, CanBubble::eYes
,
5897 void HTMLInputElement::UpdateApzAwareFlag() {
5898 #if !defined(ANDROID) && !defined(XP_MACOSX)
5899 if (mType
== FormControlType::InputNumber
||
5900 mType
== FormControlType::InputRange
) {
5906 nsresult
HTMLInputElement::SetDefaultValueAsValue() {
5907 NS_ASSERTION(GetValueMode() == VALUE_MODE_VALUE
,
5908 "GetValueMode() should return VALUE_MODE_VALUE!");
5910 // The element has a content attribute value different from it's value when
5911 // it's in the value mode value.
5912 nsAutoString resetVal
;
5913 GetDefaultValue(resetVal
);
5915 // SetValueInternal is going to sanitize the value.
5916 // TODO(mbrodesser): sanitizing will only happen if `mDoneCreating` is true.
5917 return SetValueInternal(resetVal
, ValueSetterOption::ByInternalAPI
);
5920 // https://html.spec.whatwg.org/#auto-directionality
5921 void HTMLInputElement::SetAutoDirectionality(bool aNotify
,
5922 const nsAString
* aKnownValue
) {
5923 if (!IsAutoDirectionalityAssociated()) {
5924 return SetDirectionality(Directionality::Ltr
, aNotify
);
5928 // It's unclear if per spec we should use the sanitized or unsanitized
5929 // value to set the directionality, but aKnownValue is unsanitized, so be
5930 // consistent. Using what the user is seeing to determine directionality
5931 // instead of the sanitized (empty if invalid) value probably makes more
5933 GetValueInternal(value
, CallerType::System
);
5934 aKnownValue
= &value
;
5936 SetDirectionalityFromValue(this, *aKnownValue
, aNotify
);
5940 HTMLInputElement::Reset() {
5941 // We should be able to reset all dirty flags regardless of the type.
5942 SetCheckedChanged(false);
5943 SetValueChanged(false);
5944 SetLastValueChangeWasInteractive(false);
5945 SetUserInteracted(false);
5947 switch (GetValueMode()) {
5948 case VALUE_MODE_VALUE
: {
5949 nsresult result
= SetDefaultValueAsValue();
5950 if (CreatesDateTimeWidget()) {
5951 // mFocusedValue has to be set here, so that `FireChangeEventIfNeeded`
5952 // can fire a change event if necessary.
5953 GetValue(mFocusedValue
, CallerType::System
);
5957 case VALUE_MODE_DEFAULT_ON
:
5958 DoSetChecked(DefaultChecked(), true, false);
5960 case VALUE_MODE_FILENAME
:
5963 case VALUE_MODE_DEFAULT
:
5970 HTMLInputElement::SubmitNamesValues(FormData
* aFormData
) {
5971 // For type=reset, and type=button, we just never submit, period.
5972 // For type=image and type=button, we only submit if we were the button
5974 // For type=radio and type=checkbox, we only submit if checked=true
5975 if (mType
== FormControlType::InputReset
||
5976 mType
== FormControlType::InputButton
||
5977 ((mType
== FormControlType::InputSubmit
||
5978 mType
== FormControlType::InputImage
) &&
5979 aFormData
->GetSubmitterElement() != this) ||
5980 ((mType
== FormControlType::InputRadio
||
5981 mType
== FormControlType::InputCheckbox
) &&
5988 GetAttr(nsGkAtoms::name
, name
);
5990 // Submit .x, .y for input type=image
5991 if (mType
== FormControlType::InputImage
) {
5992 // Get a property set by the frame to find out where it was clicked.
5993 const auto* lastClickedPoint
=
5994 static_cast<CSSIntPoint
*>(GetProperty(nsGkAtoms::imageClickedPoint
));
5996 if (lastClickedPoint
) {
5997 // Convert the values to strings for submission
5998 x
= lastClickedPoint
->x
;
5999 y
= lastClickedPoint
->y
;
6004 nsAutoString xVal
, yVal
;
6008 if (!name
.IsEmpty()) {
6009 aFormData
->AddNameValuePair(name
+ u
".x"_ns
, xVal
);
6010 aFormData
->AddNameValuePair(name
+ u
".y"_ns
, yVal
);
6012 // If the Image Element has no name, simply return x and y
6013 // to Nav and IE compatibility.
6014 aFormData
->AddNameValuePair(u
"x"_ns
, xVal
);
6015 aFormData
->AddNameValuePair(u
"y"_ns
, yVal
);
6021 // If name not there, don't submit
6022 if (name
.IsEmpty()) {
6027 // Submit file if its input type=file and this encoding method accepts files
6029 if (mType
== FormControlType::InputFile
) {
6032 const nsTArray
<OwningFileOrDirectory
>& files
=
6033 GetFilesOrDirectoriesInternal();
6035 if (files
.IsEmpty()) {
6036 NS_ENSURE_STATE(GetOwnerGlobal());
6038 RefPtr
<Blob
> blob
= Blob::CreateStringBlob(
6039 GetOwnerGlobal(), ""_ns
, u
"application/octet-stream"_ns
);
6040 RefPtr
<File
> file
= blob
->ToFile(u
""_ns
, rv
);
6043 aFormData
->AddNameBlobPair(name
, file
);
6046 return rv
.StealNSResult();
6049 for (uint32_t i
= 0; i
< files
.Length(); ++i
) {
6050 if (files
[i
].IsFile()) {
6051 aFormData
->AddNameBlobPair(name
, files
[i
].GetAsFile());
6053 MOZ_ASSERT(files
[i
].IsDirectory());
6054 aFormData
->AddNameDirectoryPair(name
, files
[i
].GetAsDirectory());
6061 if (mType
== FormControlType::InputHidden
&&
6062 name
.LowerCaseEqualsLiteral("_charset_")) {
6064 aFormData
->GetCharset(charset
);
6065 return aFormData
->AddNameValuePair(name
, NS_ConvertASCIItoUTF16(charset
));
6069 // Submit name=value
6074 GetValue(value
, CallerType::System
);
6076 if (mType
== FormControlType::InputSubmit
&& value
.IsEmpty() &&
6077 !HasAttr(nsGkAtoms::value
)) {
6078 // Get our default value, which is the same as our default label
6079 nsAutoString defaultValue
;
6080 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
6081 "Submit", OwnerDoc(), defaultValue
);
6082 value
= defaultValue
;
6085 const nsresult rv
= aFormData
->AddNameValuePair(name
, value
);
6086 if (NS_FAILED(rv
)) {
6090 // Submit dirname=dir
6091 if (IsAutoDirectionalityAssociated()) {
6092 return SubmitDirnameDir(aFormData
);
6098 static nsTArray
<FileContentData
> SaveFileContentData(
6099 const nsTArray
<OwningFileOrDirectory
>& aArray
) {
6100 nsTArray
<FileContentData
> res(aArray
.Length());
6101 for (const auto& it
: aArray
) {
6103 RefPtr
<BlobImpl
> impl
= it
.GetAsFile()->Impl();
6104 res
.AppendElement(std::move(impl
));
6106 MOZ_ASSERT(it
.IsDirectory());
6108 nsresult rv
= it
.GetAsDirectory()->GetFullRealPath(fullPath
);
6109 if (NS_WARN_IF(NS_FAILED(rv
))) {
6112 res
.AppendElement(std::move(fullPath
));
6118 void HTMLInputElement::SaveState() {
6119 PresState
* state
= nullptr;
6120 switch (GetValueMode()) {
6121 case VALUE_MODE_DEFAULT_ON
:
6122 if (mCheckedChanged
) {
6123 state
= GetPrimaryPresState();
6128 state
->contentData() = CheckedContentData(mChecked
);
6131 case VALUE_MODE_FILENAME
:
6132 if (!mFileData
->mFilesOrDirectories
.IsEmpty()) {
6133 state
= GetPrimaryPresState();
6138 state
->contentData() =
6139 SaveFileContentData(mFileData
->mFilesOrDirectories
);
6142 case VALUE_MODE_VALUE
:
6143 case VALUE_MODE_DEFAULT
:
6144 // VALUE_MODE_DEFAULT shouldn't have their value saved except 'hidden',
6145 // mType should have never been FormControlType::InputPassword and value
6146 // should have changed.
6147 if ((GetValueMode() == VALUE_MODE_DEFAULT
&&
6148 mType
!= FormControlType::InputHidden
) ||
6149 mHasBeenTypePassword
|| !mValueChanged
) {
6153 state
= GetPrimaryPresState();
6159 GetValue(value
, CallerType::System
);
6161 if (!IsSingleLineTextControl(false) &&
6162 NS_FAILED(nsLinebreakConverter::ConvertStringLineBreaks(
6163 value
, nsLinebreakConverter::eLinebreakPlatform
,
6164 nsLinebreakConverter::eLinebreakContent
))) {
6165 NS_ERROR("Converting linebreaks failed!");
6169 state
->contentData() =
6170 TextContentData(value
, mLastValueChangeWasInteractive
);
6174 if (mDisabledChanged
) {
6176 state
= GetPrimaryPresState();
6179 // We do not want to save the real disabled state but the disabled
6181 state
->disabled() = HasAttr(nsGkAtoms::disabled
);
6182 state
->disabledSet() = true;
6187 void HTMLInputElement::DoneCreatingElement() {
6188 mDoneCreating
= true;
6191 // Restore state as needed. Note that disabled state applies to all control
6194 bool restoredCheckedState
= false;
6195 if (!mInhibitRestoration
) {
6197 restoredCheckedState
= RestoreFormControlState();
6201 // If restore does not occur, we initialize .checked using the CHECKED
6204 if (!restoredCheckedState
&& mShouldInitChecked
) {
6205 DoSetChecked(DefaultChecked(), false, false);
6208 // Sanitize the value and potentially set mFocusedValue.
6209 if (GetValueMode() == VALUE_MODE_VALUE
) {
6211 GetValue(value
, CallerType::System
);
6212 // TODO: What should we do if SetValueInternal fails? (The allocation
6213 // may potentially be big, but most likely we've failed to allocate
6214 // before the type change.)
6215 SetValueInternal(value
, ValueSetterOption::ByInternalAPI
);
6217 if (CreatesDateTimeWidget()) {
6218 // mFocusedValue has to be set here, so that `FireChangeEventIfNeeded` can
6219 // fire a change event if necessary.
6220 mFocusedValue
= value
;
6224 mShouldInitChecked
= false;
6227 void HTMLInputElement::DestroyContent() {
6228 nsImageLoadingContent::Destroy();
6229 TextControlElement::DestroyContent();
6232 void HTMLInputElement::UpdateValidityElementStates(bool aNotify
) {
6233 AutoStateChangeNotifier
notifier(*this, aNotify
);
6234 RemoveStatesSilently(ElementState::VALIDITY_STATES
);
6235 if (!IsCandidateForConstraintValidation()) {
6240 state
|= ElementState::VALID
;
6241 if (mUserInteracted
) {
6242 state
|= ElementState::USER_VALID
;
6245 state
|= ElementState::INVALID
;
6246 if (mUserInteracted
) {
6247 state
|= ElementState::USER_INVALID
;
6250 AddStatesSilently(state
);
6253 static nsTArray
<OwningFileOrDirectory
> RestoreFileContentData(
6254 nsPIDOMWindowInner
* aWindow
, const nsTArray
<FileContentData
>& aData
) {
6255 nsTArray
<OwningFileOrDirectory
> res(aData
.Length());
6256 for (const auto& it
: aData
) {
6257 if (it
.type() == FileContentData::TBlobImpl
) {
6258 if (!it
.get_BlobImpl()) {
6259 // Serialization failed, skip this file.
6263 RefPtr
<File
> file
= File::Create(aWindow
->AsGlobal(), it
.get_BlobImpl());
6264 if (NS_WARN_IF(!file
)) {
6268 OwningFileOrDirectory
* element
= res
.AppendElement();
6269 element
->SetAsFile() = file
;
6271 MOZ_ASSERT(it
.type() == FileContentData::TnsString
);
6272 nsCOMPtr
<nsIFile
> file
;
6274 NS_NewLocalFile(it
.get_nsString(), true, getter_AddRefs(file
));
6275 if (NS_WARN_IF(NS_FAILED(rv
))) {
6279 RefPtr
<Directory
> directory
=
6280 Directory::Create(aWindow
->AsGlobal(), file
);
6281 MOZ_ASSERT(directory
);
6283 OwningFileOrDirectory
* element
= res
.AppendElement();
6284 element
->SetAsDirectory() = directory
;
6290 bool HTMLInputElement::RestoreState(PresState
* aState
) {
6291 bool restoredCheckedState
= false;
6293 const PresContentData
& inputState
= aState
->contentData();
6295 switch (GetValueMode()) {
6296 case VALUE_MODE_DEFAULT_ON
:
6297 if (inputState
.type() == PresContentData::TCheckedContentData
) {
6298 restoredCheckedState
= true;
6299 bool checked
= inputState
.get_CheckedContentData().checked();
6300 DoSetChecked(checked
, true, true);
6303 case VALUE_MODE_FILENAME
:
6304 if (inputState
.type() == PresContentData::TArrayOfFileContentData
) {
6305 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
6307 nsTArray
<OwningFileOrDirectory
> array
=
6308 RestoreFileContentData(window
, inputState
);
6309 SetFilesOrDirectories(array
, true);
6313 case VALUE_MODE_VALUE
:
6314 case VALUE_MODE_DEFAULT
:
6315 if (GetValueMode() == VALUE_MODE_DEFAULT
&&
6316 mType
!= FormControlType::InputHidden
) {
6320 if (inputState
.type() == PresContentData::TTextContentData
) {
6321 // TODO: What should we do if SetValueInternal fails? (The allocation
6322 // may potentially be big, but most likely we've failed to allocate
6323 // before the type change.)
6324 SetValueInternal(inputState
.get_TextContentData().value(),
6325 ValueSetterOption::SetValueChanged
);
6326 if (inputState
.get_TextContentData().lastValueChangeWasInteractive()) {
6327 SetLastValueChangeWasInteractive(true);
6333 if (aState
->disabledSet() && !aState
->disabled()) {
6334 SetDisabled(false, IgnoreErrors());
6337 return restoredCheckedState
;
6344 void HTMLInputElement::AddToRadioGroup() {
6345 MOZ_ASSERT(!mRadioGroupContainer
,
6346 "Radio button must be removed from previous radio group container "
6347 "before being added to another!");
6349 // If the element has no radio group container we can stop here.
6350 auto* container
= FindTreeRadioGroupContainer();
6356 GetAttr(nsGkAtoms::name
, name
);
6357 // If we are part of a radio group, the element must have a name.
6358 MOZ_ASSERT(!name
.IsEmpty());
6361 // Add the radio to the radio group container.
6363 container
->AddToRadioGroup(name
, this, mForm
);
6364 mRadioGroupContainer
= container
;
6367 // If the input element is checked, and we add it to the group, it will
6368 // deselect whatever is currently selected in that group
6372 // If it is checked, call "RadioSetChecked" to perform the selection/
6373 // deselection ritual. This has the side effect of repainting the
6374 // radio button, but as adding a checked radio button into the group
6375 // should not be that common an occurrence, I think we can live with
6377 // Make sure not to notify if we're still being created.
6379 RadioSetChecked(mDoneCreating
);
6381 bool indeterminate
= !container
->GetCurrentRadioButton(name
);
6382 SetStates(ElementState::INDETERMINATE
, indeterminate
, mDoneCreating
);
6386 // For integrity purposes, we have to ensure that "checkedChanged" is
6387 // the same for this new element as for all the others in the group
6389 bool checkedChanged
= mCheckedChanged
;
6391 nsCOMPtr
<nsIRadioVisitor
> visitor
=
6392 new nsRadioGetCheckedChangedVisitor(&checkedChanged
, this);
6393 VisitGroup(visitor
);
6395 SetCheckedChangedInternal(checkedChanged
);
6397 // We initialize the validity of the element to the validity of the group
6398 // because we assume UpdateValueMissingState() will be called after.
6399 SetValidityState(VALIDITY_STATE_VALUE_MISSING
,
6400 container
->GetValueMissingState(name
));
6403 void HTMLInputElement::RemoveFromRadioGroup() {
6404 auto* container
= GetCurrentRadioGroupContainer();
6410 GetAttr(nsGkAtoms::name
, name
);
6412 // If this button was checked, we need to notify the group that there is no
6413 // longer a selected radio button
6415 container
->SetCurrentRadioButton(name
, nullptr);
6416 nsCOMPtr
<nsIRadioVisitor
> visitor
= new nsRadioUpdateStateVisitor(this);
6417 VisitGroup(visitor
);
6419 AddStates(ElementState::INDETERMINATE
);
6422 // Remove this radio from its group in the container.
6423 // We need to call UpdateValueMissingValidityStateForRadio before to make sure
6424 // the group validity is updated (with this element being ignored).
6425 UpdateValueMissingValidityStateForRadio(true);
6426 container
->RemoveFromRadioGroup(name
, this);
6427 mRadioGroupContainer
= nullptr;
6430 bool HTMLInputElement::IsHTMLFocusable(bool aWithMouse
, bool* aIsFocusable
,
6431 int32_t* aTabIndex
) {
6432 if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
6433 aWithMouse
, aIsFocusable
, aTabIndex
)) {
6438 *aIsFocusable
= false;
6442 if (IsSingleLineTextControl(false) || mType
== FormControlType::InputRange
) {
6443 *aIsFocusable
= true;
6447 const bool defaultFocusable
= IsFormControlDefaultFocusable(aWithMouse
);
6448 if (CreatesDateTimeWidget()) {
6450 // We only want our native anonymous child to be tabable to, not ourself.
6453 *aIsFocusable
= true;
6457 if (mType
== FormControlType::InputHidden
) {
6461 *aIsFocusable
= false;
6466 // The other controls are all focusable
6467 *aIsFocusable
= defaultFocusable
;
6471 if (mType
!= FormControlType::InputRadio
) {
6472 *aIsFocusable
= defaultFocusable
;
6477 // Selected radio buttons are tabbable
6478 *aIsFocusable
= defaultFocusable
;
6482 // Current radio button is not selected.
6483 // Make it tabbable if nothing in group is selected and it is the first radio
6485 auto* container
= GetCurrentRadioGroupContainer();
6487 *aIsFocusable
= defaultFocusable
;
6492 GetAttr(nsGkAtoms::name
, name
);
6494 if (container
->GetCurrentRadioButton(name
) ||
6495 container
->GetFirstRadioButton(name
) != this) {
6498 *aIsFocusable
= defaultFocusable
;
6502 nsresult
HTMLInputElement::VisitGroup(nsIRadioVisitor
* aVisitor
) {
6503 if (auto* container
= GetCurrentRadioGroupContainer()) {
6505 GetAttr(nsGkAtoms::name
, name
);
6506 return container
->WalkRadioGroup(name
, aVisitor
);
6509 aVisitor
->Visit(this);
6513 HTMLInputElement::ValueModeType
HTMLInputElement::GetValueMode() const {
6515 case FormControlType::InputHidden
:
6516 case FormControlType::InputSubmit
:
6517 case FormControlType::InputButton
:
6518 case FormControlType::InputReset
:
6519 case FormControlType::InputImage
:
6520 return VALUE_MODE_DEFAULT
;
6521 case FormControlType::InputCheckbox
:
6522 case FormControlType::InputRadio
:
6523 return VALUE_MODE_DEFAULT_ON
;
6524 case FormControlType::InputFile
:
6525 return VALUE_MODE_FILENAME
;
6527 case FormControlType::InputText
:
6528 case FormControlType::InputPassword
:
6529 case FormControlType::InputSearch
:
6530 case FormControlType::InputTel
:
6531 case FormControlType::InputEmail
:
6532 case FormControlType::InputUrl
:
6533 case FormControlType::InputNumber
:
6534 case FormControlType::InputRange
:
6535 case FormControlType::InputDate
:
6536 case FormControlType::InputTime
:
6537 case FormControlType::InputColor
:
6538 case FormControlType::InputMonth
:
6539 case FormControlType::InputWeek
:
6540 case FormControlType::InputDatetimeLocal
:
6541 return VALUE_MODE_VALUE
;
6543 MOZ_ASSERT_UNREACHABLE("Unexpected input type in GetValueMode()");
6544 return VALUE_MODE_VALUE
;
6547 return VALUE_MODE_VALUE
;
6552 bool HTMLInputElement::IsMutable() const {
6553 return !IsDisabled() &&
6554 !(DoesReadOnlyApply() && State().HasState(ElementState::READONLY
));
6557 bool HTMLInputElement::DoesRequiredApply() const {
6559 case FormControlType::InputHidden
:
6560 case FormControlType::InputButton
:
6561 case FormControlType::InputImage
:
6562 case FormControlType::InputReset
:
6563 case FormControlType::InputSubmit
:
6564 case FormControlType::InputRange
:
6565 case FormControlType::InputColor
:
6568 case FormControlType::InputRadio
:
6569 case FormControlType::InputCheckbox
:
6570 case FormControlType::InputFile
:
6571 case FormControlType::InputText
:
6572 case FormControlType::InputPassword
:
6573 case FormControlType::InputSearch
:
6574 case FormControlType::InputTel
:
6575 case FormControlType::InputEmail
:
6576 case FormControlType::InputUrl
:
6577 case FormControlType::InputNumber
:
6578 case FormControlType::InputDate
:
6579 case FormControlType::InputTime
:
6580 case FormControlType::InputMonth
:
6581 case FormControlType::InputWeek
:
6582 case FormControlType::InputDatetimeLocal
:
6585 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
6594 bool HTMLInputElement::PlaceholderApplies() const {
6595 if (IsDateTimeInputType(mType
)) {
6598 return IsSingleLineTextControl(false);
6601 bool HTMLInputElement::DoesMinMaxApply() const {
6603 case FormControlType::InputNumber
:
6604 case FormControlType::InputDate
:
6605 case FormControlType::InputTime
:
6606 case FormControlType::InputRange
:
6607 case FormControlType::InputMonth
:
6608 case FormControlType::InputWeek
:
6609 case FormControlType::InputDatetimeLocal
:
6612 case FormControlType::InputReset
:
6613 case FormControlType::InputSubmit
:
6614 case FormControlType::InputImage
:
6615 case FormControlType::InputButton
:
6616 case FormControlType::InputHidden
:
6617 case FormControlType::InputRadio
:
6618 case FormControlType::InputCheckbox
:
6619 case FormControlType::InputFile
:
6620 case FormControlType::InputText
:
6621 case FormControlType::InputPassword
:
6622 case FormControlType::InputSearch
:
6623 case FormControlType::InputTel
:
6624 case FormControlType::InputEmail
:
6625 case FormControlType::InputUrl
:
6626 case FormControlType::InputColor
:
6629 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
6638 bool HTMLInputElement::DoesAutocompleteApply() const {
6640 case FormControlType::InputHidden
:
6641 case FormControlType::InputText
:
6642 case FormControlType::InputSearch
:
6643 case FormControlType::InputUrl
:
6644 case FormControlType::InputTel
:
6645 case FormControlType::InputEmail
:
6646 case FormControlType::InputPassword
:
6647 case FormControlType::InputDate
:
6648 case FormControlType::InputTime
:
6649 case FormControlType::InputNumber
:
6650 case FormControlType::InputRange
:
6651 case FormControlType::InputColor
:
6652 case FormControlType::InputMonth
:
6653 case FormControlType::InputWeek
:
6654 case FormControlType::InputDatetimeLocal
:
6657 case FormControlType::InputReset
:
6658 case FormControlType::InputSubmit
:
6659 case FormControlType::InputImage
:
6660 case FormControlType::InputButton
:
6661 case FormControlType::InputRadio
:
6662 case FormControlType::InputCheckbox
:
6663 case FormControlType::InputFile
:
6666 MOZ_ASSERT_UNREACHABLE(
6667 "Unexpected input type in DoesAutocompleteApply()");
6676 Decimal
HTMLInputElement::GetStep() const {
6677 MOZ_ASSERT(DoesStepApply(), "GetStep() can only be called if @step applies");
6679 if (!HasAttr(nsGkAtoms::step
)) {
6680 return GetDefaultStep() * GetStepScaleFactor();
6683 nsAutoString stepStr
;
6684 GetAttr(nsGkAtoms::step
, stepStr
);
6686 if (stepStr
.LowerCaseEqualsLiteral("any")) {
6687 // The element can't suffer from step mismatch if there is no step.
6691 Decimal step
= StringToDecimal(stepStr
);
6692 if (!step
.isFinite() || step
<= Decimal(0)) {
6693 step
= GetDefaultStep();
6696 // For input type=date, we round the step value to have a rounded day.
6697 if (mType
== FormControlType::InputDate
||
6698 mType
== FormControlType::InputMonth
||
6699 mType
== FormControlType::InputWeek
) {
6700 step
= std::max(step
.round(), Decimal(1));
6703 return step
* GetStepScaleFactor();
6706 // ConstraintValidation
6708 void HTMLInputElement::SetCustomValidity(const nsAString
& aError
) {
6709 ConstraintValidation::SetCustomValidity(aError
);
6710 UpdateValidityElementStates(true);
6713 bool HTMLInputElement::IsTooLong() {
6714 if (!mValueChanged
|| !mLastValueChangeWasInteractive
) {
6718 return mInputType
->IsTooLong();
6721 bool HTMLInputElement::IsTooShort() {
6722 if (!mValueChanged
|| !mLastValueChangeWasInteractive
) {
6726 return mInputType
->IsTooShort();
6729 bool HTMLInputElement::IsValueMissing() const {
6730 // Should use UpdateValueMissingValidityStateForRadio() for type radio.
6731 MOZ_ASSERT(mType
!= FormControlType::InputRadio
);
6733 return mInputType
->IsValueMissing();
6736 bool HTMLInputElement::HasTypeMismatch() const {
6737 return mInputType
->HasTypeMismatch();
6740 Maybe
<bool> HTMLInputElement::HasPatternMismatch() const {
6741 return mInputType
->HasPatternMismatch();
6744 bool HTMLInputElement::IsRangeOverflow() const {
6745 return mInputType
->IsRangeOverflow();
6748 bool HTMLInputElement::IsRangeUnderflow() const {
6749 return mInputType
->IsRangeUnderflow();
6752 bool HTMLInputElement::ValueIsStepMismatch(const Decimal
& aValue
) const {
6753 if (aValue
.isNaN()) {
6754 // The element can't suffer from step mismatch if its value isn't a
6759 Decimal step
= GetStep();
6760 if (step
== kStepAny
) {
6764 // Value has to be an integral multiple of step.
6765 return NS_floorModulo(aValue
- GetStepBase(), step
) != Decimal(0);
6768 bool HTMLInputElement::HasStepMismatch() const {
6769 return mInputType
->HasStepMismatch();
6772 bool HTMLInputElement::HasBadInput() const { return mInputType
->HasBadInput(); }
6774 void HTMLInputElement::UpdateTooLongValidityState() {
6775 SetValidityState(VALIDITY_STATE_TOO_LONG
, IsTooLong());
6778 void HTMLInputElement::UpdateTooShortValidityState() {
6779 SetValidityState(VALIDITY_STATE_TOO_SHORT
, IsTooShort());
6782 void HTMLInputElement::UpdateValueMissingValidityStateForRadio(
6784 MOZ_ASSERT(mType
== FormControlType::InputRadio
,
6785 "This should be called only for radio input types");
6787 HTMLInputElement
* selection
= GetSelectedRadioButton();
6789 // If there is no selection, that might mean the radio is not in a group.
6790 // In that case, we can look for the checked state of the radio.
6791 bool selected
= selection
|| (!aIgnoreSelf
&& mChecked
);
6792 bool required
= !aIgnoreSelf
&& IsRequired();
6794 auto* container
= GetCurrentRadioGroupContainer();
6796 SetValidityState(VALIDITY_STATE_VALUE_MISSING
, false);
6801 GetAttr(nsGkAtoms::name
, name
);
6803 // If the current radio is required and not ignored, we can assume the entire
6804 // group is required.
6806 required
= (aIgnoreSelf
&& IsRequired())
6807 ? container
->GetRequiredRadioCount(name
) - 1
6808 : container
->GetRequiredRadioCount(name
);
6811 bool valueMissing
= required
&& !selected
;
6812 if (container
->GetValueMissingState(name
) != valueMissing
) {
6813 container
->SetValueMissingState(name
, valueMissing
);
6815 SetValidityState(VALIDITY_STATE_VALUE_MISSING
, valueMissing
);
6817 // nsRadioSetValueMissingState will call ElementStateChanged while visiting.
6818 nsAutoScriptBlocker scriptBlocker
;
6819 nsCOMPtr
<nsIRadioVisitor
> visitor
=
6820 new nsRadioSetValueMissingState(this, valueMissing
);
6821 VisitGroup(visitor
);
6825 void HTMLInputElement::UpdateValueMissingValidityState() {
6826 if (mType
== FormControlType::InputRadio
) {
6827 UpdateValueMissingValidityStateForRadio(false);
6831 SetValidityState(VALIDITY_STATE_VALUE_MISSING
, IsValueMissing());
6834 void HTMLInputElement::UpdateTypeMismatchValidityState() {
6835 SetValidityState(VALIDITY_STATE_TYPE_MISMATCH
, HasTypeMismatch());
6838 void HTMLInputElement::UpdatePatternMismatchValidityState() {
6839 Maybe
<bool> hasMismatch
= HasPatternMismatch();
6840 // Don't update if the JS engine failed to evaluate it.
6841 if (hasMismatch
.isSome()) {
6842 SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH
, hasMismatch
.value());
6846 void HTMLInputElement::UpdateRangeOverflowValidityState() {
6847 SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW
, IsRangeOverflow());
6848 UpdateInRange(true);
6851 void HTMLInputElement::UpdateRangeUnderflowValidityState() {
6852 SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
, IsRangeUnderflow());
6853 UpdateInRange(true);
6856 void HTMLInputElement::UpdateStepMismatchValidityState() {
6857 SetValidityState(VALIDITY_STATE_STEP_MISMATCH
, HasStepMismatch());
6860 void HTMLInputElement::UpdateBadInputValidityState() {
6861 SetValidityState(VALIDITY_STATE_BAD_INPUT
, HasBadInput());
6864 void HTMLInputElement::UpdateAllValidityStates(bool aNotify
) {
6865 bool validBefore
= IsValid();
6866 UpdateAllValidityStatesButNotElementState();
6867 if (validBefore
!= IsValid()) {
6868 UpdateValidityElementStates(aNotify
);
6872 void HTMLInputElement::UpdateAllValidityStatesButNotElementState() {
6873 UpdateTooLongValidityState();
6874 UpdateTooShortValidityState();
6875 UpdateValueMissingValidityState();
6876 UpdateTypeMismatchValidityState();
6877 UpdatePatternMismatchValidityState();
6878 UpdateRangeOverflowValidityState();
6879 UpdateRangeUnderflowValidityState();
6880 UpdateStepMismatchValidityState();
6881 UpdateBadInputValidityState();
6884 void HTMLInputElement::UpdateBarredFromConstraintValidation() {
6885 // NOTE: readonly attribute causes an element to be barred from constraint
6886 // validation even if it doesn't apply to that input type. That's rather
6887 // weird, but pre-existing behavior.
6888 bool wasCandidate
= IsCandidateForConstraintValidation();
6889 SetBarredFromConstraintValidation(
6890 mType
== FormControlType::InputHidden
||
6891 mType
== FormControlType::InputButton
||
6892 mType
== FormControlType::InputReset
|| IsDisabled() ||
6893 HasAttr(nsGkAtoms::readonly
) ||
6894 HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR
));
6895 if (IsCandidateForConstraintValidation() != wasCandidate
) {
6896 UpdateInRange(true);
6900 nsresult
HTMLInputElement::GetValidationMessage(nsAString
& aValidationMessage
,
6901 ValidityStateType aType
) {
6902 return mInputType
->GetValidationMessage(aValidationMessage
, aType
);
6905 bool HTMLInputElement::IsSingleLineTextControl() const {
6906 return IsSingleLineTextControl(false);
6909 bool HTMLInputElement::IsTextArea() const { return false; }
6911 bool HTMLInputElement::IsPasswordTextControl() const {
6912 return mType
== FormControlType::InputPassword
;
6915 int32_t HTMLInputElement::GetCols() {
6916 // Else we know (assume) it is an input with size attr
6917 const nsAttrValue
* attr
= GetParsedAttr(nsGkAtoms::size
);
6918 if (attr
&& attr
->Type() == nsAttrValue::eInteger
) {
6919 int32_t cols
= attr
->GetIntegerValue();
6925 return DEFAULT_COLS
;
6928 int32_t HTMLInputElement::GetWrapCols() {
6929 return 0; // only textarea's can have wrap cols
6932 int32_t HTMLInputElement::GetRows() { return DEFAULT_ROWS
; }
6934 void HTMLInputElement::GetDefaultValueFromContent(nsAString
& aValue
,
6936 if (!GetEditorState()) {
6939 GetDefaultValue(aValue
);
6940 // This is called by the frame to show the value.
6941 // We have to sanitize it when needed.
6942 // FIXME: Do we want to sanitize even when aForDisplay is false?
6943 if (mDoneCreating
) {
6944 SanitizeValue(aValue
, aForDisplay
? SanitizationKind::ForDisplay
6945 : SanitizationKind::ForValueGetter
);
6949 bool HTMLInputElement::ValueChanged() const { return mValueChanged
; }
6951 void HTMLInputElement::GetTextEditorValue(nsAString
& aValue
) const {
6952 if (TextControlState
* state
= GetEditorState()) {
6953 state
->GetValue(aValue
, /* aIgnoreWrap = */ true, /* aForDisplay = */ true);
6957 void HTMLInputElement::InitializeKeyboardEventListeners() {
6958 TextControlState
* state
= GetEditorState();
6960 state
->InitializeKeyboardEventListeners();
6964 void HTMLInputElement::UpdatePlaceholderShownState() {
6965 SetStates(ElementState::PLACEHOLDER_SHOWN
,
6966 IsValueEmpty() && PlaceholderApplies() &&
6967 HasAttr(nsGkAtoms::placeholder
));
6970 void HTMLInputElement::OnValueChanged(ValueChangeKind aKind
,
6971 bool aNewValueEmpty
,
6972 const nsAString
* aKnownNewValue
) {
6973 MOZ_ASSERT_IF(aKnownNewValue
, aKnownNewValue
->IsEmpty() == aNewValueEmpty
);
6974 if (aKind
!= ValueChangeKind::Internal
) {
6975 mLastValueChangeWasInteractive
= aKind
== ValueChangeKind::UserInteraction
;
6978 if (aNewValueEmpty
!= IsValueEmpty()) {
6979 SetStates(ElementState::VALUE_EMPTY
, aNewValueEmpty
);
6980 UpdatePlaceholderShownState();
6983 UpdateAllValidityStates(true);
6986 SetAutoDirectionality(true, aKnownNewValue
);
6990 bool HTMLInputElement::HasCachedSelection() {
6991 TextControlState
* state
= GetEditorState();
6995 return state
->IsSelectionCached() && state
->HasNeverInitializedBefore() &&
6996 state
->GetSelectionProperties().GetStart() !=
6997 state
->GetSelectionProperties().GetEnd();
7000 void HTMLInputElement::SetRevealPassword(bool aValue
) {
7001 if (NS_WARN_IF(mType
!= FormControlType::InputPassword
)) {
7004 if (aValue
== State().HasState(ElementState::REVEALED
)) {
7007 RefPtr doc
= OwnerDoc();
7008 // We allow chrome code to prevent this. This is important for about:logins,
7009 // which may need to run some OS-dependent authentication code before
7010 // revealing the saved passwords.
7011 bool defaultAction
= true;
7012 nsContentUtils::DispatchEventOnlyToChrome(
7013 doc
, this, u
"MozWillToggleReveal"_ns
, CanBubble::eYes
, Cancelable::eYes
,
7015 if (NS_WARN_IF(!defaultAction
)) {
7018 SetStates(ElementState::REVEALED
, aValue
);
7021 bool HTMLInputElement::RevealPassword() const {
7022 if (NS_WARN_IF(mType
!= FormControlType::InputPassword
)) {
7025 return State().HasState(ElementState::REVEALED
);
7028 void HTMLInputElement::FieldSetDisabledChanged(bool aNotify
) {
7029 // This *has* to be called *before* UpdateBarredFromConstraintValidation and
7030 // UpdateValueMissingValidityState because these two functions depend on our
7032 nsGenericHTMLFormControlElementWithState::FieldSetDisabledChanged(aNotify
);
7034 UpdateValueMissingValidityState();
7035 UpdateBarredFromConstraintValidation();
7036 UpdateValidityElementStates(aNotify
);
7039 void HTMLInputElement::SetFilePickerFiltersFromAccept(
7040 nsIFilePicker
* filePicker
) {
7041 // We always add |filterAll|
7042 filePicker
->AppendFilters(nsIFilePicker::filterAll
);
7044 NS_ASSERTION(HasAttr(nsGkAtoms::accept
),
7045 "You should not call SetFilePickerFiltersFromAccept if the"
7046 " element has no accept attribute!");
7048 // Services to retrieve image/*, audio/*, video/* filters
7049 nsCOMPtr
<nsIStringBundleService
> stringService
=
7050 components::StringBundle::Service();
7051 if (!stringService
) {
7054 nsCOMPtr
<nsIStringBundle
> filterBundle
;
7055 if (NS_FAILED(stringService
->CreateBundle(
7056 "chrome://global/content/filepicker.properties",
7057 getter_AddRefs(filterBundle
)))) {
7061 // Service to retrieve mime type information for mime types filters
7062 nsCOMPtr
<nsIMIMEService
> mimeService
= do_GetService("@mozilla.org/mime;1");
7067 nsAutoString accept
;
7068 GetAttr(nsGkAtoms::accept
, accept
);
7070 HTMLSplitOnSpacesTokenizer
tokenizer(accept
, ',');
7072 nsTArray
<nsFilePickerFilter
> filters
;
7073 nsString allExtensionsList
;
7075 // Retrieve all filters
7076 while (tokenizer
.hasMoreTokens()) {
7077 const nsDependentSubstring
& token
= tokenizer
.nextToken();
7079 if (token
.IsEmpty()) {
7083 int32_t filterMask
= 0;
7084 nsString filterName
;
7085 nsString extensionListStr
;
7087 // First, check for image/audio/video filters...
7088 if (token
.EqualsLiteral("image/*")) {
7089 filterMask
= nsIFilePicker::filterImages
;
7090 filterBundle
->GetStringFromName("imageFilter", extensionListStr
);
7091 } else if (token
.EqualsLiteral("audio/*")) {
7092 filterMask
= nsIFilePicker::filterAudio
;
7093 filterBundle
->GetStringFromName("audioFilter", extensionListStr
);
7094 } else if (token
.EqualsLiteral("video/*")) {
7095 filterMask
= nsIFilePicker::filterVideo
;
7096 filterBundle
->GetStringFromName("videoFilter", extensionListStr
);
7097 } else if (token
.First() == '.') {
7098 if (token
.Contains(';') || token
.Contains('*')) {
7099 // Ignore this filter as it contains reserved characters
7102 extensionListStr
= u
"*"_ns
+ token
;
7103 filterName
= extensionListStr
;
7105 //... if no image/audio/video filter is found, check mime types filters
7106 nsCOMPtr
<nsIMIMEInfo
> mimeInfo
;
7108 mimeService
->GetFromTypeAndExtension(NS_ConvertUTF16toUTF8(token
),
7109 ""_ns
, // No extension
7110 getter_AddRefs(mimeInfo
))) ||
7115 // Get a name for the filter: first try the description, then the mime
7116 // type name if there is no description
7117 mimeInfo
->GetDescription(filterName
);
7118 if (filterName
.IsEmpty()) {
7119 nsCString mimeTypeName
;
7120 mimeInfo
->GetType(mimeTypeName
);
7121 CopyUTF8toUTF16(mimeTypeName
, filterName
);
7124 // Get extension list
7125 nsCOMPtr
<nsIUTF8StringEnumerator
> extensions
;
7126 mimeInfo
->GetFileExtensions(getter_AddRefs(extensions
));
7129 while (NS_SUCCEEDED(extensions
->HasMore(&hasMore
)) && hasMore
) {
7130 nsCString extension
;
7131 if (NS_FAILED(extensions
->GetNext(extension
))) {
7134 if (!extensionListStr
.IsEmpty()) {
7135 extensionListStr
.AppendLiteral("; ");
7137 extensionListStr
+= u
"*."_ns
+ NS_ConvertUTF8toUTF16(extension
);
7141 if (!filterMask
&& (extensionListStr
.IsEmpty() || filterName
.IsEmpty())) {
7142 // No valid filter found
7146 // At this point we're sure the token represents a valid filter, so pass
7147 // it directly as a raw filter.
7148 filePicker
->AppendRawFilter(token
);
7150 // If we arrived here, that means we have a valid filter: let's create it
7151 // and add it to our list, if no similar filter is already present
7152 nsFilePickerFilter filter
;
7154 filter
= nsFilePickerFilter(filterMask
);
7156 filter
= nsFilePickerFilter(filterName
, extensionListStr
);
7159 if (!filters
.Contains(filter
)) {
7160 if (!allExtensionsList
.IsEmpty()) {
7161 allExtensionsList
.AppendLiteral("; ");
7163 allExtensionsList
+= extensionListStr
;
7164 filters
.AppendElement(filter
);
7168 // Remove similar filters
7169 // Iterate over a copy, as we might modify the original filters list
7170 const nsTArray
<nsFilePickerFilter
> filtersCopy
= filters
.Clone();
7171 for (uint32_t i
= 0; i
< filtersCopy
.Length(); ++i
) {
7172 const nsFilePickerFilter
& filterToCheck
= filtersCopy
[i
];
7173 if (filterToCheck
.mFilterMask
) {
7176 for (uint32_t j
= 0; j
< filtersCopy
.Length(); ++j
) {
7180 // Check if this filter's extension list is a substring of the other one.
7181 // e.g. if filters are "*.jpeg" and "*.jpeg; *.jpg" the first one should
7183 // Add an extra "; " to be sure the check will work and avoid cases like
7184 // "*.xls" being a subtring of "*.xslx" while those are two differents
7185 // filters and none should be removed.
7186 if (FindInReadable(filterToCheck
.mFilter
+ u
";"_ns
,
7187 filtersCopy
[j
].mFilter
+ u
";"_ns
)) {
7188 // We already have a similar, less restrictive filter (i.e.
7189 // filterToCheck extensionList is just a subset of another filter
7190 // extension list): remove this one
7191 filters
.RemoveElement(filterToCheck
);
7196 // Add "All Supported Types" filter
7197 if (filters
.Length() > 1) {
7199 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES
,
7200 "AllSupportedTypes", title
);
7201 filePicker
->AppendFilter(title
, allExtensionsList
);
7205 for (uint32_t i
= 0; i
< filters
.Length(); ++i
) {
7206 const nsFilePickerFilter
& filter
= filters
[i
];
7207 if (filter
.mFilterMask
) {
7208 filePicker
->AppendFilters(filter
.mFilterMask
);
7210 filePicker
->AppendFilter(filter
.mTitle
, filter
.mFilter
);
7214 if (filters
.Length() >= 1) {
7215 // |filterAll| will always use index=0 so we need to set index=1 as the
7216 // current filter. This will be "All Supported Types" for multiple filters.
7217 filePicker
->SetFilterIndex(1);
7221 Decimal
HTMLInputElement::GetStepScaleFactor() const {
7222 MOZ_ASSERT(DoesStepApply());
7225 case FormControlType::InputDate
:
7226 return kStepScaleFactorDate
;
7227 case FormControlType::InputNumber
:
7228 case FormControlType::InputRange
:
7229 return kStepScaleFactorNumberRange
;
7230 case FormControlType::InputTime
:
7231 case FormControlType::InputDatetimeLocal
:
7232 return kStepScaleFactorTime
;
7233 case FormControlType::InputMonth
:
7234 return kStepScaleFactorMonth
;
7235 case FormControlType::InputWeek
:
7236 return kStepScaleFactorWeek
;
7238 MOZ_ASSERT(false, "Unrecognized input type");
7239 return Decimal::nan();
7243 Decimal
HTMLInputElement::GetDefaultStep() const {
7244 MOZ_ASSERT(DoesStepApply());
7247 case FormControlType::InputDate
:
7248 case FormControlType::InputMonth
:
7249 case FormControlType::InputWeek
:
7250 case FormControlType::InputNumber
:
7251 case FormControlType::InputRange
:
7252 return kDefaultStep
;
7253 case FormControlType::InputTime
:
7254 case FormControlType::InputDatetimeLocal
:
7255 return kDefaultStepTime
;
7257 MOZ_ASSERT(false, "Unrecognized input type");
7258 return Decimal::nan();
7262 void HTMLInputElement::SetUserInteracted(bool aInteracted
) {
7263 if (mUserInteracted
== aInteracted
) {
7266 mUserInteracted
= aInteracted
;
7267 UpdateValidityElementStates(true);
7270 void HTMLInputElement::UpdateInRange(bool aNotify
) {
7271 AutoStateChangeNotifier
notifier(*this, aNotify
);
7272 RemoveStatesSilently(ElementState::INRANGE
| ElementState::OUTOFRANGE
);
7273 if (!mHasRange
|| !IsCandidateForConstraintValidation()) {
7276 bool outOfRange
= GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW
) ||
7277 GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW
);
7278 AddStatesSilently(outOfRange
? ElementState::OUTOFRANGE
7279 : ElementState::INRANGE
);
7282 void HTMLInputElement::UpdateHasRange(bool aNotify
) {
7283 // There is a range if min/max applies for the type and if the element
7284 // currently have a valid min or max.
7285 const bool newHasRange
= [&] {
7286 if (!DoesMinMaxApply()) {
7289 return !GetMinimum().isNaN() || !GetMaximum().isNaN();
7292 if (newHasRange
== mHasRange
) {
7296 mHasRange
= newHasRange
;
7297 UpdateInRange(aNotify
);
7300 void HTMLInputElement::PickerClosed() { mPickerRunning
= false; }
7302 JSObject
* HTMLInputElement::WrapNode(JSContext
* aCx
,
7303 JS::Handle
<JSObject
*> aGivenProto
) {
7304 return HTMLInputElement_Binding::Wrap(aCx
, this, aGivenProto
);
7307 GetFilesHelper
* HTMLInputElement::GetOrCreateGetFilesHelper(bool aRecursiveFlag
,
7309 MOZ_ASSERT(mFileData
);
7311 if (aRecursiveFlag
) {
7312 if (!mFileData
->mGetFilesRecursiveHelper
) {
7313 mFileData
->mGetFilesRecursiveHelper
= GetFilesHelper::Create(
7314 GetFilesOrDirectoriesInternal(), aRecursiveFlag
, aRv
);
7315 if (NS_WARN_IF(aRv
.Failed())) {
7320 return mFileData
->mGetFilesRecursiveHelper
;
7323 if (!mFileData
->mGetFilesNonRecursiveHelper
) {
7324 mFileData
->mGetFilesNonRecursiveHelper
= GetFilesHelper::Create(
7325 GetFilesOrDirectoriesInternal(), aRecursiveFlag
, aRv
);
7326 if (NS_WARN_IF(aRv
.Failed())) {
7331 return mFileData
->mGetFilesNonRecursiveHelper
;
7334 void HTMLInputElement::UpdateEntries(
7335 const nsTArray
<OwningFileOrDirectory
>& aFilesOrDirectories
) {
7336 MOZ_ASSERT(mFileData
&& mFileData
->mEntries
.IsEmpty());
7338 nsCOMPtr
<nsIGlobalObject
> global
= OwnerDoc()->GetScopeObject();
7341 RefPtr
<FileSystem
> fs
= FileSystem::Create(global
);
7342 if (NS_WARN_IF(!fs
)) {
7346 Sequence
<RefPtr
<FileSystemEntry
>> entries
;
7347 for (uint32_t i
= 0; i
< aFilesOrDirectories
.Length(); ++i
) {
7348 RefPtr
<FileSystemEntry
> entry
=
7349 FileSystemEntry::Create(global
, aFilesOrDirectories
[i
], fs
);
7352 if (!entries
.AppendElement(entry
, fallible
)) {
7357 // The root fileSystem is a DirectoryEntry object that contains only the
7358 // dropped fileEntry and directoryEntry objects.
7359 fs
->CreateRoot(entries
);
7361 mFileData
->mEntries
= std::move(entries
);
7364 void HTMLInputElement::GetWebkitEntries(
7365 nsTArray
<RefPtr
<FileSystemEntry
>>& aSequence
) {
7366 if (NS_WARN_IF(mType
!= FormControlType::InputFile
)) {
7370 Telemetry::Accumulate(Telemetry::BLINK_FILESYSTEM_USED
, true);
7371 aSequence
.AppendElements(mFileData
->mEntries
);
7374 already_AddRefed
<nsINodeList
> HTMLInputElement::GetLabels() {
7375 if (!IsLabelable()) {
7379 return nsGenericHTMLElement::Labels();
7382 void HTMLInputElement::MaybeFireInputPasswordRemoved() {
7383 // We want this event to be fired only when the password field is removed
7384 // from the DOM tree, not when it is released (ex, tab is closed). So don't
7385 // fire an event when the password input field doesn't have a docshell.
7386 Document
* doc
= GetComposedDoc();
7387 nsIDocShell
* container
= doc
? doc
->GetDocShell() : nullptr;
7392 // Right now, only the password manager listens to the event and only listen
7393 // to it under certain circumstances. So don't fire this event unless
7395 if (!doc
->ShouldNotifyFormOrPasswordRemoved()) {
7399 AsyncEventDispatcher::RunDOMEventWhenSafe(
7400 *this, u
"DOMInputPasswordRemoved"_ns
, CanBubble::eNo
,
7401 ChromeOnlyDispatch::eYes
);
7404 } // namespace mozilla::dom
7406 #undef NS_ORIGINAL_CHECKED_VALUE