Backed out changeset 62f7af8fe549 (bug 1843981) for causing valgrind bustage. CLOSED...
[gecko.git] / dom / html / HTMLInputElement.cpp
blob266180e478e72195daed4b65422bb166030de870
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/TextUtils.h"
33 #include "mozilla/Try.h"
34 #include "nsAttrValueInlines.h"
35 #include "nsCRTGlue.h"
36 #include "nsIFilePicker.h"
37 #include "nsNetUtil.h"
38 #include "nsQueryObject.h"
40 #include "HTMLDataListElement.h"
41 #include "HTMLFormSubmissionConstants.h"
42 #include "mozilla/Telemetry.h"
43 #include "nsBaseCommandController.h"
44 #include "nsIStringBundle.h"
45 #include "nsFocusManager.h"
46 #include "nsColorControlFrame.h"
47 #include "nsNumberControlFrame.h"
48 #include "nsSearchControlFrame.h"
49 #include "nsPIDOMWindow.h"
50 #include "nsRepeatService.h"
51 #include "nsContentCID.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"
62 #include "nsIFrame.h"
63 #include "nsRangeFrame.h"
64 #include "nsError.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"
84 #include <algorithm>
86 // input type=radio
87 #include "mozilla/dom/RadioGroupContainer.h"
88 #include "nsIRadioVisitor.h"
89 #include "nsRadioVisitor.h"
91 // input type=file
92 #include "mozilla/dom/FileSystemEntry.h"
93 #include "mozilla/dom/FileSystem.h"
94 #include "mozilla/dom/File.h"
95 #include "mozilla/dom/FileList.h"
96 #include "nsIFile.h"
97 #include "nsDirectoryServiceDefs.h"
98 #include "nsIContentPrefService2.h"
99 #include "nsIMIMEService.h"
100 #include "nsIObserverService.h"
102 // input type=image
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"
115 #include <limits>
117 #include "nsIColorPicker.h"
118 #include "nsIStringEnumerator.h"
119 #include "HTMLSplitOnSpacesTokenizer.h"
120 #include "nsIMIMEInfo.h"
121 #include "nsFrameSelection.h"
122 #include "nsXULControllers.h"
124 // input type=date
125 #include "js/Date.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 | \
143 NS_IN_SUBMIT_CLICK))
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},
175 {nullptr, 0}};
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 {
216 public:
217 explicit DispatchChangeEventCallback(HTMLInputElement* aInputElement)
218 : mInputElement(aInputElement) {
219 MOZ_ASSERT(aInputElement);
222 virtual void Callback(
223 nsresult aStatus,
224 const FallibleTArray<RefPtr<BlobImpl>>& aBlobImpls) override {
225 if (!mInputElement->GetOwnerGlobal()) {
226 return;
229 nsTArray<OwningFileOrDirectory> array;
230 for (uint32_t i = 0; i < aBlobImpls.Length(); ++i) {
231 OwningFileOrDirectory* element = array.AppendElement();
232 RefPtr<File> file =
233 File::Create(mInputElement->GetOwnerGlobal(), aBlobImpls[i]);
234 if (NS_WARN_IF(!file)) {
235 return;
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");
251 rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
252 mInputElement, u"change"_ns,
253 CanBubble::eYes, Cancelable::eNo);
255 return rv;
258 private:
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);
315 void Unlink() {
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)
330 NS_IMETHODIMP
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)))) {
348 localFile = nullptr;
352 if (localFile) {
353 mFilePicker->SetDisplayDirectory(localFile);
354 } else {
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);
362 return NS_OK;
365 NS_IMETHODIMP
366 UploadLastDir::ContentPrefCallback::HandleResult(nsIContentPref* pref) {
367 mResult = pref;
368 return NS_OK;
371 NS_IMETHODIMP
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.
375 return NS_OK;
378 namespace {
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()) {
389 nsAutoString path;
390 ErrorResult error;
391 aData.GetAsFile()->GetMozFullPathInternal(path, error);
392 if (error.Failed() || path.IsEmpty()) {
393 error.SuppressException();
394 return nullptr;
397 nsCOMPtr<nsIFile> localFile;
398 nsresult rv = NS_NewLocalFile(path, true, getter_AddRefs(localFile));
399 if (NS_WARN_IF(NS_FAILED(rv))) {
400 return nullptr;
403 nsCOMPtr<nsIFile> parentFile;
404 rv = localFile->GetParent(getter_AddRefs(parentFile));
405 if (NS_WARN_IF(NS_FAILED(rv))) {
406 return nullptr;
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,
421 nsAString& aName) {
422 if (aData.IsFile()) {
423 aData.GetAsFile()->GetName(aName);
424 } else {
425 MOZ_ASSERT(aData.IsDirectory());
426 ErrorResult rv;
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);
438 } else {
439 MOZ_ASSERT(aData.IsDirectory());
440 aData.GetAsDirectory()->GetFullRealPath(aPath);
444 } // namespace
446 NS_IMETHODIMP
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,
455 Cancelable::eNo);
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;
467 nsresult rv =
468 mFilePicker->GetDomFileOrDirectoryEnumerator(getter_AddRefs(iter));
469 NS_ENSURE_SUCCESS(rv, rv);
471 if (!iter) {
472 return NS_OK;
475 nsCOMPtr<nsISupports> tmp;
476 bool hasMore = true;
478 while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
479 iter->GetNext(getter_AddRefs(tmp));
480 RefPtr<Blob> domBlob = do_QueryObject(tmp);
481 MOZ_ASSERT(domBlob,
482 "Null file object from FilePicker's file enumerator?");
483 if (!domBlob) {
484 continue;
487 OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
488 element->SetAsFile() = domBlob->ToFile();
490 } else {
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.
499 // See Bug 1338637.
500 if (mode == nsIFilePicker::modeGetFolder) {
501 nsCOMPtr<nsIPromptCollection> prompter =
502 do_GetService("@mozilla.org/embedcomp/prompt-collection;1");
503 if (!prompter) {
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;
513 ErrorResult error;
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);
521 if (!confirmed) {
522 // User aborted upload
523 return NS_OK;
527 RefPtr<Blob> blob = do_QueryObject(tmp);
528 if (blob) {
529 RefPtr<File> file = blob->ToFile();
530 MOZ_ASSERT(file);
532 OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
533 element->SetAsFile() = file;
534 } else if (tmp) {
535 RefPtr<Directory> directory = static_cast<Directory*>(tmp.get());
536 OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
537 element->SetAsDirectory() = directory;
541 if (newFilesOrDirectories.IsEmpty()) {
542 return NS_OK;
545 // Store the last used directory using the content pref service:
546 nsCOMPtr<nsIFile> lastUsedDir = LastUsedDirectory(newFilesOrDirectories[0]);
548 if (lastUsedDir) {
549 HTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(mInput->OwnerDoc(),
550 lastUsedDir);
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()) {
561 return NS_OK;
563 RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
564 new DispatchChangeEventCallback(mInput);
566 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
567 mInput->HasAttr(nsGkAtoms::webkitdirectory)) {
568 ErrorResult error;
569 GetFilesHelper* helper = mInput->GetOrCreateGetFilesHelper(true, error);
570 if (NS_WARN_IF(error.Failed())) {
571 return error.StealNSResult();
574 helper->AddCallback(dispatchChangeEventCallback);
575 return NS_OK;
578 return dispatchChangeEventCallback->DispatchEvents();
581 NS_IMPL_ISUPPORTS(HTMLInputElement::nsFilePickerShownCallback,
582 nsIFilePickerShownCallback)
584 class nsColorPickerShownCallback final : public nsIColorPickerShownCallback {
585 ~nsColorPickerShownCallback() = default;
587 public:
588 nsColorPickerShownCallback(HTMLInputElement* aInput,
589 nsIColorPicker* aColorPicker)
590 : mInput(aInput), mColorPicker(aColorPicker), mValueChanged(false) {}
592 NS_DECL_ISUPPORTS
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;
599 private:
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.
605 MOZ_CAN_RUN_SCRIPT
606 nsresult UpdateInternal(const nsAString& aColor, bool aTrustedUpdate);
608 RefPtr<HTMLInputElement> mInput;
609 nsCOMPtr<nsIColorPicker> mColorPicker;
610 bool mValueChanged;
613 nsresult nsColorPickerShownCallback::UpdateInternal(const nsAString& aColor,
614 bool aTrustedUpdate) {
615 bool valueChanged = false;
616 nsAutoString oldValue;
617 if (aTrustedUpdate) {
618 mInput->OwnerDoc()->NotifyUserGestureActivation();
619 valueChanged = true;
620 } else {
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)) {
630 valueChanged = true;
634 if (!valueChanged) {
635 return NS_OK;
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");
643 return NS_OK;
646 NS_IMETHODIMP
647 nsColorPickerShownCallback::Update(const nsAString& aColor) {
648 return UpdateInternal(aColor, true);
651 NS_IMETHODIMP
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.
660 nsresult rv = NS_OK;
662 mInput->PickerClosed();
664 if (!aColor.IsEmpty()) {
665 UpdateInternal(aColor, false);
668 if (mValueChanged) {
669 rv = nsContentUtils::DispatchTrustedEvent(
670 mInput->OwnerDoc(), static_cast<Element*>(mInput.get()), u"change"_ns,
671 CanBubble::eYes, Cancelable::eNo);
674 return rv;
677 NS_IMPL_ISUPPORTS(nsColorPickerShownCallback, nsIColorPickerShownCallback)
679 static bool IsPopupBlocked(Document* aDoc) {
680 if (aDoc->ConsumeTransientUserGestureActivation()) {
681 return false;
684 WindowContext* wc = aDoc->GetWindowContext();
685 if (wc && wc->CanShowPopup()) {
686 return false;
689 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, aDoc,
690 nsContentUtils::eDOM_PROPERTIES,
691 "InputPickerBlockedNoUserActivation");
692 return true;
695 nsTArray<nsString> HTMLInputElement::GetColorsFromList() {
696 RefPtr<HTMLDataListElement> dataList = GetList();
697 if (!dataList) {
698 return {};
701 nsTArray<nsString> colors;
703 RefPtr<nsContentList> options = dataList->Options();
704 uint32_t length = options->Length(true);
705 for (uint32_t i = 0; i < length; ++i) {
706 auto* option = HTMLOptionElement::FromNodeOrNull(options->Item(i, false));
707 if (!option) {
708 continue;
711 nsString value;
712 option->GetValue(value);
713 if (IsValidSimpleColor(value)) {
714 ToLowerCase(value);
715 colors.AppendElement(value);
719 return colors;
722 nsresult HTMLInputElement::InitColorPicker() {
723 MOZ_ASSERT(IsMutable());
725 if (mPickerRunning) {
726 NS_WARNING("Just one nsIColorPicker is allowed");
727 return NS_ERROR_FAILURE;
730 nsCOMPtr<Document> doc = OwnerDoc();
732 nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
733 if (!win) {
734 return NS_ERROR_FAILURE;
737 if (IsPopupBlocked(doc)) {
738 return NS_OK;
741 // Get Loc title
742 nsAutoString title;
743 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
744 "ColorPicker", title);
746 nsCOMPtr<nsIColorPicker> colorPicker =
747 do_CreateInstance("@mozilla.org/colorpicker;1");
748 if (!colorPicker) {
749 return NS_ERROR_FAILURE;
752 nsAutoString initialValue;
753 GetNonFileValueInternal(initialValue);
754 nsTArray<nsString> colors = GetColorsFromList();
755 nsresult rv = colorPicker->Init(win, title, initialValue, colors);
756 NS_ENSURE_SUCCESS(rv, rv);
758 nsCOMPtr<nsIColorPickerShownCallback> callback =
759 new nsColorPickerShownCallback(this, colorPicker);
761 rv = colorPicker->Open(callback);
762 if (NS_SUCCEEDED(rv)) {
763 mPickerRunning = true;
766 return rv;
769 nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) {
770 MOZ_ASSERT(IsMutable());
772 if (mPickerRunning) {
773 NS_WARNING("Just one nsIFilePicker is allowed");
774 return NS_ERROR_FAILURE;
777 // Get parent nsPIDOMWindow object.
778 nsCOMPtr<Document> doc = OwnerDoc();
780 nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
781 if (!win) {
782 return NS_ERROR_FAILURE;
785 if (IsPopupBlocked(doc)) {
786 return NS_OK;
789 // Get Loc title
790 nsAutoString title;
791 nsAutoString okButtonLabel;
792 if (aType == FILE_PICKER_DIRECTORY) {
793 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
794 "DirectoryUpload", OwnerDoc(),
795 title);
797 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
798 "DirectoryPickerOkButtonLabel",
799 OwnerDoc(), okButtonLabel);
800 } else {
801 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
802 "FileUpload", OwnerDoc(), 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;
815 } else {
816 mode = nsIFilePicker::modeOpen;
819 nsresult rv = filePicker->Init(win, 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()));
837 } else {
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) {
851 nsAutoString path;
853 nsCOMPtr<nsIFile> parentFile = LastUsedDirectory(oldFiles[0]);
854 if (parentFile) {
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;
875 return rv;
878 HTMLInputElement::gUploadLastDir->FetchDirectoryAndDisplayPicker(
879 doc, filePicker, callback);
880 mPickerRunning = true;
881 return NS_OK;
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
916 // Desktop folder
917 nsCOMPtr<nsIContentPrefService2> contentPrefService =
918 do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
919 if (!contentPrefService) {
920 prefCallback->HandleCompletion(nsIContentPrefCallback2::COMPLETE_ERROR);
921 return NS_OK;
924 nsAutoCString cstrSpec;
925 docURI->GetSpec(cstrSpec);
926 NS_ConvertUTF8toUTF16 spec(cstrSpec);
928 contentPrefService->GetByDomainAndName(spec, CPS_PREF_NAME, loadContext,
929 prefCallback);
930 return NS_OK;
933 nsresult UploadLastDir::StoreLastUsedDirectory(Document* aDoc, nsIFile* aDir) {
934 MOZ_ASSERT(aDoc, "aDoc is null");
935 if (!aDir) {
936 return NS_OK;
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
955 return NS_OK;
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,
964 nullptr);
967 NS_IMETHODIMP
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);
976 return NS_OK;
979 #ifdef ACCESSIBILITY
980 // Helper method
981 static nsresult FireEventForAccessibility(HTMLInputElement* aTarget,
982 EventMessage aEventMessage);
983 #endif
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 mLastValueChangeWasInteractive(false),
998 mCheckedChanged(false),
999 mChecked(false),
1000 mHandlingSelectEvent(false),
1001 mShouldInitChecked(false),
1002 mDoneCreating(aFromParser == NOT_FROM_PARSER &&
1003 aFromClone == FromClone::No),
1004 mInInternalActivate(false),
1005 mCheckedIsToggled(false),
1006 mIndeterminate(false),
1007 mInhibitRestoration(aFromParser & FROM_PARSER_FRAGMENT),
1008 mCanShowValidUI(true),
1009 mCanShowInvalidUI(true),
1010 mHasRange(false),
1011 mIsDraggingRange(false),
1012 mNumberControlSpinnerIsSpinning(false),
1013 mNumberControlSpinnerSpinsUp(false),
1014 mPickerRunning(false),
1015 mIsPreviewEnabled(false),
1016 mHasBeenTypePassword(false),
1017 mHasPatternAttribute(false),
1018 mRadioGroupContainer(nullptr) {
1019 // If size is above 512, mozjemalloc allocates 1kB, see
1020 // memory/build/mozjemalloc.cpp
1021 static_assert(sizeof(HTMLInputElement) <= 512,
1022 "Keep the size of HTMLInputElement under 512 to avoid "
1023 "performance regression!");
1025 // We are in a type=text so we now we currenty need a TextControlState.
1026 mInputData.mState = TextControlState::Construct(this);
1028 void* memory = mInputTypeMem;
1029 mInputType = InputType::Create(this, mType, memory);
1031 if (!gUploadLastDir) HTMLInputElement::InitUploadLastDir();
1033 // Set up our default state. By default we're enabled (since we're a control
1034 // type that can be disabled but not actually disabled right now), optional,
1035 // read-write, and valid. Also by default we don't have to show validity UI
1036 // and so forth.
1037 AddStatesSilently(ElementState::ENABLED | ElementState::OPTIONAL_ |
1038 ElementState::VALID | ElementState::VALUE_EMPTY |
1039 ElementState::READWRITE);
1040 RemoveStatesSilently(ElementState::READONLY);
1041 UpdateApzAwareFlag();
1044 HTMLInputElement::~HTMLInputElement() {
1045 if (mNumberControlSpinnerIsSpinning) {
1046 StopNumberControlSpinnerSpin(eDisallowDispatchingEvents);
1048 nsImageLoadingContent::Destroy();
1049 FreeData();
1052 void HTMLInputElement::FreeData() {
1053 if (!IsSingleLineTextControl(false)) {
1054 free(mInputData.mValue);
1055 mInputData.mValue = nullptr;
1056 } else {
1057 UnbindFromFrame(nullptr);
1058 mInputData.mState->Destroy();
1059 mInputData.mState = nullptr;
1062 if (mInputType) {
1063 mInputType->DropReference();
1064 mInputType = nullptr;
1068 TextControlState* HTMLInputElement::GetEditorState() const {
1069 if (!IsSingleLineTextControl(false)) {
1070 return nullptr;
1073 MOZ_ASSERT(mInputData.mState,
1074 "Single line text controls need to have a state"
1075 " associated with them");
1077 return mInputData.mState;
1080 // nsISupports
1082 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLInputElement)
1084 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement,
1085 TextControlElement)
1086 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
1087 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
1088 if (tmp->IsSingleLineTextControl(false)) {
1089 tmp->mInputData.mState->Traverse(cb);
1092 if (tmp->mFileData) {
1093 tmp->mFileData->Traverse(cb);
1095 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1097 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement,
1098 TextControlElement)
1099 NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
1100 NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
1101 if (tmp->IsSingleLineTextControl(false)) {
1102 tmp->mInputData.mState->Unlink();
1105 if (tmp->mFileData) {
1106 tmp->mFileData->Unlink();
1108 // XXX should unlink more?
1109 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1111 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLInputElement,
1112 TextControlElement,
1113 imgINotificationObserver,
1114 nsIImageLoadingContent,
1115 nsIConstraintValidation)
1117 // nsINode
1119 nsresult HTMLInputElement::Clone(dom::NodeInfo* aNodeInfo,
1120 nsINode** aResult) const {
1121 *aResult = nullptr;
1123 RefPtr<HTMLInputElement> it = new (aNodeInfo->NodeInfoManager())
1124 HTMLInputElement(do_AddRef(aNodeInfo), NOT_FROM_PARSER, FromClone::Yes);
1126 nsresult rv = const_cast<HTMLInputElement*>(this)->CopyInnerTo(it);
1127 NS_ENSURE_SUCCESS(rv, rv);
1129 switch (GetValueMode()) {
1130 case VALUE_MODE_VALUE:
1131 if (mValueChanged) {
1132 // We don't have our default value anymore. Set our value on
1133 // the clone.
1134 nsAutoString value;
1135 GetNonFileValueInternal(value);
1136 // SetValueInternal handles setting the VALUE_CHANGED bit for us
1137 if (NS_WARN_IF(
1138 NS_FAILED(rv = it->SetValueInternal(
1139 value, {ValueSetterOption::SetValueChanged})))) {
1140 return rv;
1143 break;
1144 case VALUE_MODE_FILENAME:
1145 if (it->OwnerDoc()->IsStaticDocument()) {
1146 // We're going to be used in print preview. Since the doc is static
1147 // we can just grab the pretty string and use it as wallpaper
1148 GetDisplayFileName(it->mFileData->mStaticDocFileList);
1149 } else {
1150 it->mFileData->ClearGetFilesHelpers();
1151 it->mFileData->mFilesOrDirectories.Clear();
1152 it->mFileData->mFilesOrDirectories.AppendElements(
1153 mFileData->mFilesOrDirectories);
1155 break;
1156 case VALUE_MODE_DEFAULT_ON:
1157 case VALUE_MODE_DEFAULT:
1158 break;
1161 if (mCheckedChanged) {
1162 // We no longer have our original checked state. Set our
1163 // checked state on the clone.
1164 it->DoSetChecked(mChecked, false, true);
1165 // Then tell DoneCreatingElement() not to overwrite:
1166 it->mShouldInitChecked = false;
1169 it->mIndeterminate = mIndeterminate;
1171 it->DoneCreatingElement();
1173 it->SetLastValueChangeWasInteractive(mLastValueChangeWasInteractive);
1174 it.forget(aResult);
1175 return NS_OK;
1178 void HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
1179 const nsAttrValue* aValue, bool aNotify) {
1180 if (aNameSpaceID == kNameSpaceID_None) {
1181 if (aNotify && aName == nsGkAtoms::disabled) {
1182 mDisabledChanged = true;
1185 // When name or type changes, radio should be removed from radio group.
1186 // If we are not done creating the radio, we also should not do it.
1187 if (mType == FormControlType::InputRadio) {
1188 if ((aName == nsGkAtoms::name || (aName == nsGkAtoms::type && !mForm)) &&
1189 (mForm || mDoneCreating)) {
1190 RemoveFromRadioGroup();
1191 } else if (aName == nsGkAtoms::required) {
1192 auto* container = GetCurrentRadioGroupContainer();
1194 if (container && ((aValue && !HasAttr(aNameSpaceID, aName)) ||
1195 (!aValue && HasAttr(aNameSpaceID, aName)))) {
1196 nsAutoString name;
1197 GetAttr(nsGkAtoms::name, name);
1198 container->RadioRequiredWillChange(name, !!aValue);
1203 if (aName == nsGkAtoms::webkitdirectory) {
1204 Telemetry::Accumulate(Telemetry::WEBKIT_DIRECTORY_USED, true);
1208 return nsGenericHTMLFormControlElementWithState::BeforeSetAttr(
1209 aNameSpaceID, aName, aValue, aNotify);
1212 void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
1213 const nsAttrValue* aValue,
1214 const nsAttrValue* aOldValue,
1215 nsIPrincipal* aSubjectPrincipal,
1216 bool aNotify) {
1217 if (aNameSpaceID == kNameSpaceID_None) {
1218 bool needValidityUpdate = false;
1219 if (aName == nsGkAtoms::src) {
1220 mSrcTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal(
1221 this, aValue ? aValue->GetStringValue() : EmptyString(),
1222 aSubjectPrincipal);
1223 if (aNotify && mType == FormControlType::InputImage) {
1224 if (aValue) {
1225 // Mark channel as urgent-start before load image if the image load is
1226 // initiated by a user interaction.
1227 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
1229 LoadImage(aValue->GetStringValue(), true, aNotify,
1230 eImageLoadType_Normal, mSrcTriggeringPrincipal);
1231 } else {
1232 // Null value means the attr got unset; drop the image
1233 CancelImageRequests(aNotify);
1238 // If @value is changed and BF_VALUE_CHANGED is false, @value is the value
1239 // of the element so, if the value of the element is different than @value,
1240 // we have to re-set it. This is only the case when GetValueMode() returns
1241 // VALUE_MODE_VALUE.
1242 if (aName == nsGkAtoms::value) {
1243 if (!mValueChanged && GetValueMode() == VALUE_MODE_VALUE) {
1244 SetDefaultValueAsValue();
1246 // GetStepBase() depends on the `value` attribute if `min` is not present,
1247 // even if the value doesn't change.
1248 UpdateStepMismatchValidityState();
1249 needValidityUpdate = true;
1252 // Checked must be set no matter what type of control it is, since
1253 // mChecked must reflect the new value
1254 if (aName == nsGkAtoms::checked) {
1255 if (IsRadioOrCheckbox()) {
1256 SetStates(ElementState::DEFAULT, !!aValue, aNotify);
1258 if (!mCheckedChanged) {
1259 // Delay setting checked if we are creating this element (wait
1260 // until everything is set)
1261 if (!mDoneCreating) {
1262 mShouldInitChecked = true;
1263 } else {
1264 DoSetChecked(!!aValue, aNotify, false);
1267 needValidityUpdate = true;
1270 if (aName == nsGkAtoms::type) {
1271 FormControlType newType;
1272 if (!aValue) {
1273 // We're now a text input.
1274 newType = FormControlType(kInputDefaultType->value);
1275 } else {
1276 newType = FormControlType(aValue->GetEnumValue());
1278 if (newType != mType) {
1279 HandleTypeChange(newType, aNotify);
1280 needValidityUpdate = true;
1284 // When name or type changes, radio should be added to radio group.
1285 // If we are not done creating the radio, we also should not do it.
1286 if ((aName == nsGkAtoms::name || (aName == nsGkAtoms::type && !mForm)) &&
1287 mType == FormControlType::InputRadio && (mForm || mDoneCreating)) {
1288 AddToRadioGroup();
1289 UpdateValueMissingValidityStateForRadio(false);
1290 needValidityUpdate = true;
1293 if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
1294 aName == nsGkAtoms::readonly) {
1295 if (aName == nsGkAtoms::disabled) {
1296 // This *has* to be called *before* validity state check because
1297 // UpdateBarredFromConstraintValidation and
1298 // UpdateValueMissingValidityState depend on our disabled state.
1299 UpdateDisabledState(aNotify);
1302 if (aName == nsGkAtoms::required && DoesRequiredApply()) {
1303 // This *has* to be called *before* UpdateValueMissingValidityState
1304 // because UpdateValueMissingValidityState depends on our required
1305 // state.
1306 UpdateRequiredState(!!aValue, aNotify);
1309 if (aName == nsGkAtoms::readonly && !!aValue != !!aOldValue) {
1310 UpdateReadOnlyState(aNotify);
1313 UpdateValueMissingValidityState();
1315 // This *has* to be called *after* validity has changed.
1316 if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
1317 UpdateBarredFromConstraintValidation();
1319 needValidityUpdate = true;
1320 } else if (aName == nsGkAtoms::maxlength) {
1321 UpdateTooLongValidityState();
1322 needValidityUpdate = true;
1323 } else if (aName == nsGkAtoms::minlength) {
1324 UpdateTooShortValidityState();
1325 needValidityUpdate = true;
1326 } else if (aName == nsGkAtoms::pattern) {
1327 // Although pattern attribute only applies to single line text controls,
1328 // we set this flag for all input types to save having to check the type
1329 // here.
1330 mHasPatternAttribute = !!aValue;
1332 if (mDoneCreating) {
1333 UpdatePatternMismatchValidityState();
1335 needValidityUpdate = true;
1336 } else if (aName == nsGkAtoms::multiple) {
1337 UpdateTypeMismatchValidityState();
1338 needValidityUpdate = true;
1339 } else if (aName == nsGkAtoms::max) {
1340 UpdateHasRange(aNotify);
1341 mInputType->MinMaxStepAttrChanged();
1342 // Validity state must be updated *after* the UpdateValueDueToAttrChange
1343 // call above or else the following assert will not be valid.
1344 // We don't assert the state of underflow during creation since
1345 // DoneCreatingElement sanitizes.
1346 UpdateRangeOverflowValidityState();
1347 needValidityUpdate = true;
1348 MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
1349 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1350 "HTML5 spec does not allow underflow for type=range");
1351 } else if (aName == nsGkAtoms::min) {
1352 UpdateHasRange(aNotify);
1353 mInputType->MinMaxStepAttrChanged();
1354 // See corresponding @max comment
1355 UpdateRangeUnderflowValidityState();
1356 UpdateStepMismatchValidityState();
1357 needValidityUpdate = true;
1358 MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
1359 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1360 "HTML5 spec does not allow underflow for type=range");
1361 } else if (aName == nsGkAtoms::step) {
1362 mInputType->MinMaxStepAttrChanged();
1363 // See corresponding @max comment
1364 UpdateStepMismatchValidityState();
1365 needValidityUpdate = true;
1366 MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
1367 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1368 "HTML5 spec does not allow underflow for type=range");
1369 } else if (aName == nsGkAtoms::dir && aValue &&
1370 aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
1371 SetDirectionFromValue(aNotify);
1372 } else if (aName == nsGkAtoms::lang) {
1373 // FIXME(emilio, bug 1651070): This doesn't account for lang changes on
1374 // ancestors.
1375 if (mType == FormControlType::InputNumber) {
1376 // The validity of our value may have changed based on the locale.
1377 UpdateValidityState();
1378 needValidityUpdate = true;
1380 } else if (aName == nsGkAtoms::autocomplete) {
1381 // Clear the cached @autocomplete attribute and autocompleteInfo state.
1382 mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
1383 mAutocompleteInfoState = nsContentUtils::eAutocompleteAttrState_Unknown;
1384 } else if (aName == nsGkAtoms::placeholder) {
1385 // Full addition / removals of the attribute reconstruct right now.
1386 if (nsTextControlFrame* f = do_QueryFrame(GetPrimaryFrame())) {
1387 f->PlaceholderChanged(aOldValue, aValue);
1389 UpdatePlaceholderShownState();
1390 needValidityUpdate = true;
1393 if (CreatesDateTimeWidget()) {
1394 if (aName == nsGkAtoms::value || aName == nsGkAtoms::readonly ||
1395 aName == nsGkAtoms::tabindex || aName == nsGkAtoms::required ||
1396 aName == nsGkAtoms::disabled) {
1397 // If original target is this and not the inner text control, we should
1398 // pass the focus to the inner text control.
1399 if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
1400 AsyncEventDispatcher::RunDOMEventWhenSafe(
1401 *dateTimeBoxElement,
1402 aName == nsGkAtoms::value ? u"MozDateTimeValueChanged"_ns
1403 : u"MozDateTimeAttributeChanged"_ns,
1404 CanBubble::eNo, ChromeOnlyDispatch::eNo);
1408 if (needValidityUpdate) {
1409 UpdateValidityElementStates(aNotify);
1413 return nsGenericHTMLFormControlElementWithState::AfterSetAttr(
1414 aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
1417 void HTMLInputElement::BeforeSetForm(bool aBindToTree) {
1418 // No need to remove from radio group if we are just binding to tree.
1419 if (mType == FormControlType::InputRadio && !aBindToTree) {
1420 RemoveFromRadioGroup();
1424 void HTMLInputElement::AfterClearForm(bool aUnbindOrDelete) {
1425 MOZ_ASSERT(!mForm);
1427 // Do not add back to radio group if we are releasing or unbinding from tree.
1428 if (mType == FormControlType::InputRadio && !aUnbindOrDelete &&
1429 !GetCurrentRadioGroupContainer()) {
1430 AddToRadioGroup();
1431 UpdateValueMissingValidityStateForRadio(false);
1435 void HTMLInputElement::ResultForDialogSubmit(nsAString& aResult) {
1436 if (mType == FormControlType::InputImage) {
1437 // Get a property set by the frame to find out where it was clicked.
1438 const auto* lastClickedPoint =
1439 static_cast<CSSIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
1440 int32_t x, y;
1441 if (lastClickedPoint) {
1442 x = lastClickedPoint->x;
1443 y = lastClickedPoint->y;
1444 } else {
1445 x = y = 0;
1447 aResult.AppendInt(x);
1448 aResult.AppendLiteral(",");
1449 aResult.AppendInt(y);
1450 } else {
1451 GetAttr(nsGkAtoms::value, aResult);
1455 void HTMLInputElement::GetAutocomplete(nsAString& aValue) {
1456 if (!DoesAutocompleteApply()) {
1457 return;
1460 aValue.Truncate();
1461 const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
1463 mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute(
1464 attributeVal, aValue, mAutocompleteAttrState);
1467 void HTMLInputElement::GetAutocompleteInfo(Nullable<AutocompleteInfo>& aInfo) {
1468 if (!DoesAutocompleteApply()) {
1469 aInfo.SetNull();
1470 return;
1473 const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
1474 mAutocompleteInfoState = nsContentUtils::SerializeAutocompleteAttribute(
1475 attributeVal, aInfo.SetValue(), mAutocompleteInfoState, true);
1478 void HTMLInputElement::GetCapture(nsAString& aValue) {
1479 GetEnumAttr(nsGkAtoms::capture, kCaptureDefault->tag, aValue);
1482 void HTMLInputElement::GetFormEnctype(nsAString& aValue) {
1483 GetEnumAttr(nsGkAtoms::formenctype, "", kFormDefaultEnctype->tag, aValue);
1486 void HTMLInputElement::GetFormMethod(nsAString& aValue) {
1487 GetEnumAttr(nsGkAtoms::formmethod, "", kFormDefaultMethod->tag, aValue);
1490 void HTMLInputElement::GetType(nsAString& aValue) const {
1491 GetEnumAttr(nsGkAtoms::type, kInputDefaultType->tag, aValue);
1494 int32_t HTMLInputElement::TabIndexDefault() { return 0; }
1496 uint32_t HTMLInputElement::Height() {
1497 if (mType != FormControlType::InputImage) {
1498 return 0;
1500 return GetWidthHeightForImage().height;
1503 void HTMLInputElement::SetIndeterminateInternal(bool aValue,
1504 bool aShouldInvalidate) {
1505 mIndeterminate = aValue;
1506 if (mType != FormControlType::InputCheckbox) {
1507 return;
1510 SetStates(ElementState::INDETERMINATE, aValue);
1512 if (aShouldInvalidate) {
1513 // Repaint the frame
1514 if (nsIFrame* frame = GetPrimaryFrame()) {
1515 frame->InvalidateFrameSubtree();
1520 void HTMLInputElement::SetIndeterminate(bool aValue) {
1521 SetIndeterminateInternal(aValue, true);
1524 uint32_t HTMLInputElement::Width() {
1525 if (mType != FormControlType::InputImage) {
1526 return 0;
1528 return GetWidthHeightForImage().width;
1531 bool HTMLInputElement::SanitizesOnValueGetter() const {
1532 // Don't return non-sanitized value for datetime types, email, or number.
1533 return mType == FormControlType::InputEmail ||
1534 mType == FormControlType::InputNumber || IsDateTimeInputType(mType);
1537 void HTMLInputElement::GetValue(nsAString& aValue, CallerType aCallerType) {
1538 GetValueInternal(aValue, aCallerType);
1540 // In the case where we need to sanitize an input value without affecting
1541 // the displayed user's input, we instead sanitize only on .value accesses.
1542 // For the more general case of input elements displaying text that isn't
1543 // their current value, see bug 805049.
1544 if (SanitizesOnValueGetter()) {
1545 SanitizeValue(aValue, SanitizationKind::ForValueGetter);
1549 void HTMLInputElement::GetValueInternal(nsAString& aValue,
1550 CallerType aCallerType) const {
1551 if (mType != FormControlType::InputFile) {
1552 GetNonFileValueInternal(aValue);
1553 return;
1556 if (aCallerType == CallerType::System) {
1557 aValue.Assign(mFileData->mFirstFilePath);
1558 return;
1561 if (mFileData->mFilesOrDirectories.IsEmpty()) {
1562 aValue.Truncate();
1563 return;
1566 nsAutoString file;
1567 GetDOMFileOrDirectoryName(mFileData->mFilesOrDirectories[0], file);
1568 if (file.IsEmpty()) {
1569 aValue.Truncate();
1570 return;
1573 aValue.AssignLiteral("C:\\fakepath\\");
1574 aValue.Append(file);
1577 void HTMLInputElement::GetNonFileValueInternal(nsAString& aValue) const {
1578 switch (GetValueMode()) {
1579 case VALUE_MODE_VALUE:
1580 if (IsSingleLineTextControl(false)) {
1581 mInputData.mState->GetValue(aValue, true, /* aForDisplay = */ false);
1582 } else if (!aValue.Assign(mInputData.mValue, fallible)) {
1583 aValue.Truncate();
1585 return;
1587 case VALUE_MODE_FILENAME:
1588 MOZ_ASSERT_UNREACHABLE("Someone screwed up here");
1589 // We'll just return empty string if someone does screw up.
1590 aValue.Truncate();
1591 return;
1593 case VALUE_MODE_DEFAULT:
1594 // Treat defaultValue as value.
1595 GetAttr(nsGkAtoms::value, aValue);
1596 return;
1598 case VALUE_MODE_DEFAULT_ON:
1599 // Treat default value as value and returns "on" if no value.
1600 if (!GetAttr(nsGkAtoms::value, aValue)) {
1601 aValue.AssignLiteral("on");
1603 return;
1607 void HTMLInputElement::ClearFiles(bool aSetValueChanged) {
1608 nsTArray<OwningFileOrDirectory> data;
1609 SetFilesOrDirectories(data, aSetValueChanged);
1612 int32_t HTMLInputElement::MonthsSinceJan1970(uint32_t aYear,
1613 uint32_t aMonth) const {
1614 return (aYear - 1970) * 12 + aMonth - 1;
1617 /* static */
1618 Decimal HTMLInputElement::StringToDecimal(const nsAString& aValue) {
1619 if (!IsAscii(aValue)) {
1620 return Decimal::nan();
1622 NS_LossyConvertUTF16toASCII asciiString(aValue);
1623 std::string stdString(asciiString.get(), asciiString.Length());
1624 auto decimal = Decimal::fromString(stdString);
1625 if (!decimal.isFinite()) {
1626 return Decimal::nan();
1628 // Numbers are considered finite IEEE 754 Double-precision floating point
1629 // values, but decimal supports a bigger range.
1630 static const Decimal maxDouble =
1631 Decimal::fromDouble(std::numeric_limits<double>::max());
1632 if (decimal < -maxDouble || decimal > maxDouble) {
1633 return Decimal::nan();
1635 return decimal;
1638 Decimal HTMLInputElement::GetValueAsDecimal() const {
1639 nsAutoString stringValue;
1640 GetNonFileValueInternal(stringValue);
1641 Decimal result = mInputType->ConvertStringToNumber(stringValue).mResult;
1642 return result.isFinite() ? result : Decimal::nan();
1645 void HTMLInputElement::SetValue(const nsAString& aValue, CallerType aCallerType,
1646 ErrorResult& aRv) {
1647 // check security. Note that setting the value to the empty string is always
1648 // OK and gives pages a way to clear a file input if necessary.
1649 if (mType == FormControlType::InputFile) {
1650 if (!aValue.IsEmpty()) {
1651 if (aCallerType != CallerType::System) {
1652 // setting the value of a "FILE" input widget requires
1653 // chrome privilege
1654 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1655 return;
1657 Sequence<nsString> list;
1658 if (!list.AppendElement(aValue, fallible)) {
1659 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1660 return;
1663 MozSetFileNameArray(list, aRv);
1664 return;
1666 ClearFiles(true);
1667 } else {
1668 if (MayFireChangeOnBlur()) {
1669 // If the value has been set by a script, we basically want to keep the
1670 // current change event state. If the element is ready to fire a change
1671 // event, we should keep it that way. Otherwise, we should make sure the
1672 // element will not fire any event because of the script interaction.
1674 // NOTE: this is currently quite expensive work (too much string
1675 // manipulation). We should probably optimize that.
1676 nsAutoString currentValue;
1677 GetNonFileValueInternal(currentValue);
1679 nsresult rv = SetValueInternal(
1680 aValue, &currentValue,
1681 {ValueSetterOption::ByContentAPI, ValueSetterOption::SetValueChanged,
1682 ValueSetterOption::MoveCursorToEndIfValueChanged});
1683 if (NS_FAILED(rv)) {
1684 aRv.Throw(rv);
1685 return;
1688 if (mFocusedValue.Equals(currentValue)) {
1689 GetValue(mFocusedValue, aCallerType);
1691 } else {
1692 nsresult rv = SetValueInternal(
1693 aValue,
1694 {ValueSetterOption::ByContentAPI, ValueSetterOption::SetValueChanged,
1695 ValueSetterOption::MoveCursorToEndIfValueChanged});
1696 if (NS_FAILED(rv)) {
1697 aRv.Throw(rv);
1698 return;
1704 HTMLDataListElement* HTMLInputElement::GetList() const {
1705 nsAutoString dataListId;
1706 GetAttr(nsGkAtoms::list_, dataListId);
1707 if (dataListId.IsEmpty()) {
1708 return nullptr;
1711 DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
1712 if (!docOrShadow) {
1713 return nullptr;
1716 return HTMLDataListElement::FromNodeOrNull(
1717 docOrShadow->GetElementById(dataListId));
1720 void HTMLInputElement::SetValue(Decimal aValue, CallerType aCallerType) {
1721 MOZ_ASSERT(!aValue.isInfinity(), "aValue must not be Infinity!");
1723 if (aValue.isNaN()) {
1724 SetValue(u""_ns, aCallerType, IgnoreErrors());
1725 return;
1728 nsAutoString value;
1729 mInputType->ConvertNumberToString(aValue, value);
1730 SetValue(value, aCallerType, IgnoreErrors());
1733 void HTMLInputElement::GetValueAsDate(JSContext* aCx,
1734 JS::MutableHandle<JSObject*> aObject,
1735 ErrorResult& aRv) {
1736 aObject.set(nullptr);
1737 if (!IsDateTimeInputType(mType)) {
1738 return;
1741 Maybe<JS::ClippedTime> time;
1743 switch (mType) {
1744 case FormControlType::InputDate: {
1745 uint32_t year, month, day;
1746 nsAutoString value;
1747 GetNonFileValueInternal(value);
1748 if (!ParseDate(value, &year, &month, &day)) {
1749 return;
1752 time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, day)));
1753 break;
1755 case FormControlType::InputTime: {
1756 uint32_t millisecond;
1757 nsAutoString value;
1758 GetNonFileValueInternal(value);
1759 if (!ParseTime(value, &millisecond)) {
1760 return;
1763 time.emplace(JS::TimeClip(millisecond));
1764 MOZ_ASSERT(time->toDouble() == millisecond,
1765 "HTML times are restricted to the day after the epoch and "
1766 "never clip");
1767 break;
1769 case FormControlType::InputMonth: {
1770 uint32_t year, month;
1771 nsAutoString value;
1772 GetNonFileValueInternal(value);
1773 if (!ParseMonth(value, &year, &month)) {
1774 return;
1777 time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, 1)));
1778 break;
1780 case FormControlType::InputWeek: {
1781 uint32_t year, week;
1782 nsAutoString value;
1783 GetNonFileValueInternal(value);
1784 if (!ParseWeek(value, &year, &week)) {
1785 return;
1788 double days = DaysSinceEpochFromWeek(year, week);
1789 time.emplace(JS::TimeClip(days * kMsPerDay));
1791 break;
1793 case FormControlType::InputDatetimeLocal: {
1794 uint32_t year, month, day, timeInMs;
1795 nsAutoString value;
1796 GetNonFileValueInternal(value);
1797 if (!ParseDateTimeLocal(value, &year, &month, &day, &timeInMs)) {
1798 return;
1801 time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, day, timeInMs)));
1802 break;
1804 default:
1805 break;
1808 if (time) {
1809 aObject.set(JS::NewDateObject(aCx, *time));
1810 if (!aObject) {
1811 aRv.NoteJSContextException(aCx);
1813 return;
1816 MOZ_ASSERT(false, "Unrecognized input type");
1817 aRv.Throw(NS_ERROR_UNEXPECTED);
1820 void HTMLInputElement::SetValueAsDate(JSContext* aCx,
1821 JS::Handle<JSObject*> aObj,
1822 ErrorResult& aRv) {
1823 if (!IsDateTimeInputType(mType)) {
1824 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1825 return;
1828 if (aObj) {
1829 bool isDate;
1830 if (!JS::ObjectIsDate(aCx, aObj, &isDate)) {
1831 aRv.NoteJSContextException(aCx);
1832 return;
1834 if (!isDate) {
1835 aRv.ThrowTypeError("Value being assigned is not a date.");
1836 return;
1840 double milliseconds;
1841 if (aObj) {
1842 if (!js::DateGetMsecSinceEpoch(aCx, aObj, &milliseconds)) {
1843 aRv.NoteJSContextException(aCx);
1844 return;
1846 } else {
1847 milliseconds = UnspecifiedNaN<double>();
1850 // At this point we know we're not a file input, so we can just pass "not
1851 // system" as the caller type, since the caller type only matters in the file
1852 // input case.
1853 if (std::isnan(milliseconds)) {
1854 SetValue(u""_ns, CallerType::NonSystem, aRv);
1855 return;
1858 if (mType != FormControlType::InputMonth) {
1859 SetValue(Decimal::fromDouble(milliseconds), CallerType::NonSystem);
1860 return;
1863 // type=month expects the value to be number of months.
1864 double year = JS::YearFromTime(milliseconds);
1865 double month = JS::MonthFromTime(milliseconds);
1867 if (std::isnan(year) || std::isnan(month)) {
1868 SetValue(u""_ns, CallerType::NonSystem, aRv);
1869 return;
1872 int32_t months = MonthsSinceJan1970(year, month + 1);
1873 SetValue(Decimal(int32_t(months)), CallerType::NonSystem);
1876 void HTMLInputElement::SetValueAsNumber(double aValueAsNumber,
1877 ErrorResult& aRv) {
1878 // TODO: return TypeError when HTMLInputElement is converted to WebIDL, see
1879 // bug 825197.
1880 if (std::isinf(aValueAsNumber)) {
1881 aRv.Throw(NS_ERROR_INVALID_ARG);
1882 return;
1885 if (!DoesValueAsNumberApply()) {
1886 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1887 return;
1890 // At this point we know we're not a file input, so we can just pass "not
1891 // system" as the caller type, since the caller type only matters in the file
1892 // input case.
1893 SetValue(Decimal::fromDouble(aValueAsNumber), CallerType::NonSystem);
1896 Decimal HTMLInputElement::GetMinimum() const {
1897 MOZ_ASSERT(
1898 DoesValueAsNumberApply(),
1899 "GetMinimum() should only be used for types that allow .valueAsNumber");
1901 // Only type=range has a default minimum
1902 Decimal defaultMinimum =
1903 mType == FormControlType::InputRange ? Decimal(0) : Decimal::nan();
1905 if (!HasAttr(nsGkAtoms::min)) {
1906 return defaultMinimum;
1909 nsAutoString minStr;
1910 GetAttr(nsGkAtoms::min, minStr);
1912 Decimal min = mInputType->ConvertStringToNumber(minStr).mResult;
1913 return min.isFinite() ? min : defaultMinimum;
1916 Decimal HTMLInputElement::GetMaximum() const {
1917 MOZ_ASSERT(
1918 DoesValueAsNumberApply(),
1919 "GetMaximum() should only be used for types that allow .valueAsNumber");
1921 // Only type=range has a default maximum
1922 Decimal defaultMaximum =
1923 mType == FormControlType::InputRange ? Decimal(100) : Decimal::nan();
1925 if (!HasAttr(nsGkAtoms::max)) {
1926 return defaultMaximum;
1929 nsAutoString maxStr;
1930 GetAttr(nsGkAtoms::max, maxStr);
1932 Decimal max = mInputType->ConvertStringToNumber(maxStr).mResult;
1933 return max.isFinite() ? max : defaultMaximum;
1936 Decimal HTMLInputElement::GetStepBase() const {
1937 MOZ_ASSERT(IsDateTimeInputType(mType) ||
1938 mType == FormControlType::InputNumber ||
1939 mType == FormControlType::InputRange,
1940 "Check that kDefaultStepBase is correct for this new type");
1941 // Do NOT use GetMinimum here - the spec says to use "the min content
1942 // attribute", not "the minimum".
1943 nsAutoString minStr;
1944 if (GetAttr(nsGkAtoms::min, minStr)) {
1945 Decimal min = mInputType->ConvertStringToNumber(minStr).mResult;
1946 if (min.isFinite()) {
1947 return min;
1951 // If @min is not a double, we should use @value.
1952 nsAutoString valueStr;
1953 if (GetAttr(nsGkAtoms::value, valueStr)) {
1954 Decimal value = mInputType->ConvertStringToNumber(valueStr).mResult;
1955 if (value.isFinite()) {
1956 return value;
1960 if (mType == FormControlType::InputWeek) {
1961 return kDefaultStepBaseWeek;
1964 return kDefaultStepBase;
1967 nsresult HTMLInputElement::GetValueIfStepped(int32_t aStep,
1968 StepCallerType aCallerType,
1969 Decimal* aNextStep) {
1970 if (!DoStepDownStepUpApply()) {
1971 return NS_ERROR_DOM_INVALID_STATE_ERR;
1974 Decimal stepBase = GetStepBase();
1975 Decimal step = GetStep();
1976 if (step == kStepAny) {
1977 if (aCallerType != CALLED_FOR_USER_EVENT) {
1978 return NS_ERROR_DOM_INVALID_STATE_ERR;
1980 // Allow the spin buttons and up/down arrow keys to do something sensible:
1981 step = GetDefaultStep();
1984 Decimal minimum = GetMinimum();
1985 Decimal maximum = GetMaximum();
1987 if (!maximum.isNaN()) {
1988 // "max - (max - stepBase) % step" is the nearest valid value to max.
1989 maximum = maximum - NS_floorModulo(maximum - stepBase, step);
1990 if (!minimum.isNaN()) {
1991 if (minimum > maximum) {
1992 // Either the minimum was greater than the maximum prior to our
1993 // adjustment to align maximum on a step, or else (if we adjusted
1994 // maximum) there is no valid step between minimum and the unadjusted
1995 // maximum.
1996 return NS_OK;
2001 Decimal value = GetValueAsDecimal();
2002 bool valueWasNaN = false;
2003 if (value.isNaN()) {
2004 value = Decimal(0);
2005 valueWasNaN = true;
2007 Decimal valueBeforeStepping = value;
2009 Decimal deltaFromStep = NS_floorModulo(value - stepBase, step);
2011 if (deltaFromStep != Decimal(0)) {
2012 if (aStep > 0) {
2013 value += step - deltaFromStep; // partial step
2014 value += step * Decimal(aStep - 1); // then remaining steps
2015 } else if (aStep < 0) {
2016 value -= deltaFromStep; // partial step
2017 value += step * Decimal(aStep + 1); // then remaining steps
2019 } else {
2020 value += step * Decimal(aStep);
2023 if (value < minimum) {
2024 value = minimum;
2025 deltaFromStep = NS_floorModulo(value - stepBase, step);
2026 if (deltaFromStep != Decimal(0)) {
2027 value += step - deltaFromStep;
2030 if (value > maximum) {
2031 value = maximum;
2032 deltaFromStep = NS_floorModulo(value - stepBase, step);
2033 if (deltaFromStep != Decimal(0)) {
2034 value -= deltaFromStep;
2038 if (!valueWasNaN && // value="", resulting in us using "0"
2039 ((aStep > 0 && value < valueBeforeStepping) ||
2040 (aStep < 0 && value > valueBeforeStepping))) {
2041 // We don't want step-up to effectively step down, or step-down to
2042 // effectively step up, so return;
2043 return NS_OK;
2046 *aNextStep = value;
2047 return NS_OK;
2050 nsresult HTMLInputElement::ApplyStep(int32_t aStep) {
2051 Decimal nextStep = Decimal::nan(); // unchanged if value will not change
2053 nsresult rv = GetValueIfStepped(aStep, CALLED_FOR_SCRIPT, &nextStep);
2055 if (NS_SUCCEEDED(rv) && nextStep.isFinite()) {
2056 // We know we're not a file input, so the caller type does not matter; just
2057 // pass "not system" to be safe.
2058 SetValue(nextStep, CallerType::NonSystem);
2061 return rv;
2064 bool HTMLInputElement::IsDateTimeInputType(FormControlType aType) {
2065 switch (aType) {
2066 case FormControlType::InputDate:
2067 case FormControlType::InputTime:
2068 case FormControlType::InputMonth:
2069 case FormControlType::InputWeek:
2070 case FormControlType::InputDatetimeLocal:
2071 return true;
2072 default:
2073 return false;
2077 void HTMLInputElement::MozGetFileNameArray(nsTArray<nsString>& aArray,
2078 ErrorResult& aRv) {
2079 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2080 return;
2083 const nsTArray<OwningFileOrDirectory>& filesOrDirs =
2084 GetFilesOrDirectoriesInternal();
2085 for (uint32_t i = 0; i < filesOrDirs.Length(); i++) {
2086 nsAutoString str;
2087 GetDOMFileOrDirectoryPath(filesOrDirs[i], str, aRv);
2088 if (NS_WARN_IF(aRv.Failed())) {
2089 return;
2092 aArray.AppendElement(str);
2096 void HTMLInputElement::MozSetFileArray(
2097 const Sequence<OwningNonNull<File>>& aFiles) {
2098 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2099 return;
2102 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
2103 MOZ_ASSERT(global);
2104 if (!global) {
2105 return;
2108 nsTArray<OwningFileOrDirectory> files;
2109 for (uint32_t i = 0; i < aFiles.Length(); ++i) {
2110 RefPtr<File> file = File::Create(global, aFiles[i].get()->Impl());
2111 if (NS_WARN_IF(!file)) {
2112 return;
2115 OwningFileOrDirectory* element = files.AppendElement();
2116 element->SetAsFile() = file;
2119 SetFilesOrDirectories(files, true);
2122 void HTMLInputElement::MozSetFileNameArray(const Sequence<nsString>& aFileNames,
2123 ErrorResult& aRv) {
2124 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2125 return;
2128 if (XRE_IsContentProcess()) {
2129 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
2130 return;
2133 nsTArray<OwningFileOrDirectory> files;
2134 for (uint32_t i = 0; i < aFileNames.Length(); ++i) {
2135 nsCOMPtr<nsIFile> file;
2137 if (StringBeginsWith(aFileNames[i], u"file:"_ns,
2138 nsASCIICaseInsensitiveStringComparator)) {
2139 // Converts the URL string into the corresponding nsIFile if possible
2140 // A local file will be created if the URL string begins with file://
2141 NS_GetFileFromURLSpec(NS_ConvertUTF16toUTF8(aFileNames[i]),
2142 getter_AddRefs(file));
2145 if (!file) {
2146 // this is no "file://", try as local file
2147 NS_NewLocalFile(aFileNames[i], false, getter_AddRefs(file));
2150 if (!file) {
2151 continue; // Not much we can do if the file doesn't exist
2154 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
2155 if (!global) {
2156 aRv.Throw(NS_ERROR_FAILURE);
2157 return;
2160 RefPtr<File> domFile = File::CreateFromFile(global, file);
2161 if (NS_WARN_IF(!domFile)) {
2162 aRv.Throw(NS_ERROR_FAILURE);
2163 return;
2166 OwningFileOrDirectory* element = files.AppendElement();
2167 element->SetAsFile() = domFile;
2170 SetFilesOrDirectories(files, true);
2173 void HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath,
2174 ErrorResult& aRv) {
2175 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2176 return;
2179 nsCOMPtr<nsIFile> file;
2180 aRv = NS_NewLocalFile(aDirectoryPath, true, getter_AddRefs(file));
2181 if (NS_WARN_IF(aRv.Failed())) {
2182 return;
2185 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
2186 if (NS_WARN_IF(!window)) {
2187 aRv.Throw(NS_ERROR_FAILURE);
2188 return;
2191 RefPtr<Directory> directory = Directory::Create(window->AsGlobal(), file);
2192 MOZ_ASSERT(directory);
2194 nsTArray<OwningFileOrDirectory> array;
2195 OwningFileOrDirectory* element = array.AppendElement();
2196 element->SetAsDirectory() = directory;
2198 SetFilesOrDirectories(array, true);
2201 void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue& aValue) {
2202 if (NS_WARN_IF(!IsDateTimeInputType(mType)) || !mDateTimeInputBoxValue) {
2203 return;
2206 aValue = *mDateTimeInputBoxValue;
2209 Element* HTMLInputElement::GetDateTimeBoxElement() {
2210 if (!GetShadowRoot()) {
2211 return nullptr;
2214 // The datetimebox <div> is the only child of the UA Widget Shadow Root
2215 // if it is present.
2216 MOZ_ASSERT(GetShadowRoot()->IsUAWidget());
2217 MOZ_ASSERT(1 >= GetShadowRoot()->GetChildCount());
2218 if (nsIContent* inputAreaContent = GetShadowRoot()->GetFirstChild()) {
2219 return inputAreaContent->AsElement();
2222 return nullptr;
2225 void HTMLInputElement::OpenDateTimePicker(const DateTimeValue& aInitialValue) {
2226 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2227 return;
2230 mDateTimeInputBoxValue = MakeUnique<DateTimeValue>(aInitialValue);
2231 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
2232 u"MozOpenDateTimePicker"_ns,
2233 CanBubble::eYes, Cancelable::eYes);
2236 void HTMLInputElement::UpdateDateTimePicker(const DateTimeValue& aValue) {
2237 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2238 return;
2241 mDateTimeInputBoxValue = MakeUnique<DateTimeValue>(aValue);
2242 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
2243 u"MozUpdateDateTimePicker"_ns,
2244 CanBubble::eYes, Cancelable::eYes);
2247 void HTMLInputElement::CloseDateTimePicker() {
2248 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2249 return;
2252 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
2253 u"MozCloseDateTimePicker"_ns,
2254 CanBubble::eYes, Cancelable::eYes);
2257 void HTMLInputElement::SetFocusState(bool aIsFocused) {
2258 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2259 return;
2261 SetStates(ElementState::FOCUS | ElementState::FOCUSRING, aIsFocused);
2264 void HTMLInputElement::UpdateValidityState() {
2265 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2266 return;
2269 // For now, datetime input box call this function only when the value may
2270 // become valid/invalid. For other validity states, they will be updated when
2271 // .value is actually changed.
2272 UpdateBadInputValidityState();
2273 UpdateValidityElementStates(true);
2276 bool HTMLInputElement::MozIsTextField(bool aExcludePassword) {
2277 // TODO: temporary until bug 888320 is fixed.
2279 // FIXME: Historically we never returned true for `number`, we should consider
2280 // changing that now that it is similar to other inputs.
2281 if (IsDateTimeInputType(mType) || mType == FormControlType::InputNumber) {
2282 return false;
2285 return IsSingleLineTextControl(aExcludePassword);
2288 void HTMLInputElement::SetUserInput(const nsAString& aValue,
2289 nsIPrincipal& aSubjectPrincipal) {
2290 AutoHandlingUserInputStatePusher inputStatePusher(true);
2292 if (mType == FormControlType::InputFile &&
2293 !aSubjectPrincipal.IsSystemPrincipal()) {
2294 return;
2297 if (mType == FormControlType::InputFile) {
2298 Sequence<nsString> list;
2299 if (!list.AppendElement(aValue, fallible)) {
2300 return;
2303 MozSetFileNameArray(list, IgnoreErrors());
2304 return;
2307 bool isInputEventDispatchedByTextControlState =
2308 GetValueMode() == VALUE_MODE_VALUE && IsSingleLineTextControl(false);
2310 nsresult rv = SetValueInternal(
2311 aValue,
2312 {ValueSetterOption::BySetUserInputAPI, ValueSetterOption::SetValueChanged,
2313 ValueSetterOption::MoveCursorToEndIfValueChanged});
2314 NS_ENSURE_SUCCESS_VOID(rv);
2316 if (!isInputEventDispatchedByTextControlState) {
2317 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
2318 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2319 "Failed to dispatch input event");
2322 // If this element is not currently focused, it won't receive a change event
2323 // for this update through the normal channels. So fire a change event
2324 // immediately, instead.
2325 if (CreatesDateTimeWidget() || !ShouldBlur(this)) {
2326 FireChangeEventIfNeeded();
2330 nsIEditor* HTMLInputElement::GetEditorForBindings() {
2331 if (!GetPrimaryFrame()) {
2332 // Ensure we construct frames (and thus an editor) if needed.
2333 GetPrimaryFrame(FlushType::Frames);
2335 return GetTextEditorFromState();
2338 bool HTMLInputElement::HasEditor() { return !!GetTextEditorWithoutCreation(); }
2340 TextEditor* HTMLInputElement::GetTextEditorFromState() {
2341 TextControlState* state = GetEditorState();
2342 if (state) {
2343 return state->GetTextEditor();
2345 return nullptr;
2348 TextEditor* HTMLInputElement::GetTextEditor() {
2349 return GetTextEditorFromState();
2352 TextEditor* HTMLInputElement::GetTextEditorWithoutCreation() {
2353 TextControlState* state = GetEditorState();
2354 if (!state) {
2355 return nullptr;
2357 return state->GetTextEditorWithoutCreation();
2360 nsISelectionController* HTMLInputElement::GetSelectionController() {
2361 TextControlState* state = GetEditorState();
2362 if (state) {
2363 return state->GetSelectionController();
2365 return nullptr;
2368 nsFrameSelection* HTMLInputElement::GetConstFrameSelection() {
2369 TextControlState* state = GetEditorState();
2370 if (state) {
2371 return state->GetConstFrameSelection();
2373 return nullptr;
2376 nsresult HTMLInputElement::BindToFrame(nsTextControlFrame* aFrame) {
2377 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
2378 TextControlState* state = GetEditorState();
2379 if (state) {
2380 return state->BindToFrame(aFrame);
2382 return NS_ERROR_FAILURE;
2385 void HTMLInputElement::UnbindFromFrame(nsTextControlFrame* aFrame) {
2386 TextControlState* state = GetEditorState();
2387 if (state && aFrame) {
2388 state->UnbindFromFrame(aFrame);
2392 nsresult HTMLInputElement::CreateEditor() {
2393 TextControlState* state = GetEditorState();
2394 if (state) {
2395 return state->PrepareEditor();
2397 return NS_ERROR_FAILURE;
2400 void HTMLInputElement::SetPreviewValue(const nsAString& aValue) {
2401 TextControlState* state = GetEditorState();
2402 if (state) {
2403 state->SetPreviewText(aValue, true);
2407 void HTMLInputElement::GetPreviewValue(nsAString& aValue) {
2408 TextControlState* state = GetEditorState();
2409 if (state) {
2410 state->GetPreviewText(aValue);
2414 void HTMLInputElement::EnablePreview() {
2415 if (mIsPreviewEnabled) {
2416 return;
2419 mIsPreviewEnabled = true;
2420 // Reconstruct the frame to append an anonymous preview node
2421 nsLayoutUtils::PostRestyleEvent(this, RestyleHint{0},
2422 nsChangeHint_ReconstructFrame);
2425 bool HTMLInputElement::IsPreviewEnabled() { return mIsPreviewEnabled; }
2427 void HTMLInputElement::GetDisplayFileName(nsAString& aValue) const {
2428 MOZ_ASSERT(mFileData);
2430 if (OwnerDoc()->IsStaticDocument()) {
2431 aValue = mFileData->mStaticDocFileList;
2432 return;
2435 if (mFileData->mFilesOrDirectories.Length() == 1) {
2436 GetDOMFileOrDirectoryName(mFileData->mFilesOrDirectories[0], aValue);
2437 return;
2440 nsAutoString value;
2442 if (mFileData->mFilesOrDirectories.IsEmpty()) {
2443 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
2444 HasAttr(nsGkAtoms::webkitdirectory)) {
2445 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
2446 "NoDirSelected", OwnerDoc(),
2447 value);
2448 } else if (HasAttr(nsGkAtoms::multiple)) {
2449 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
2450 "NoFilesSelected", OwnerDoc(),
2451 value);
2452 } else {
2453 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
2454 "NoFileSelected", OwnerDoc(),
2455 value);
2457 } else {
2458 nsString count;
2459 count.AppendInt(int(mFileData->mFilesOrDirectories.Length()));
2461 nsContentUtils::FormatMaybeLocalizedString(
2462 value, nsContentUtils::eFORMS_PROPERTIES, "XFilesSelected", OwnerDoc(),
2463 count);
2466 aValue = value;
2469 const nsTArray<OwningFileOrDirectory>&
2470 HTMLInputElement::GetFilesOrDirectoriesInternal() const {
2471 return mFileData->mFilesOrDirectories;
2474 void HTMLInputElement::SetFilesOrDirectories(
2475 const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories,
2476 bool aSetValueChanged) {
2477 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2478 return;
2481 MOZ_ASSERT(mFileData);
2483 mFileData->ClearGetFilesHelpers();
2485 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2486 HTMLInputElement_Binding::ClearCachedWebkitEntriesValue(this);
2487 mFileData->mEntries.Clear();
2490 mFileData->mFilesOrDirectories.Clear();
2491 mFileData->mFilesOrDirectories.AppendElements(aFilesOrDirectories);
2493 AfterSetFilesOrDirectories(aSetValueChanged);
2496 void HTMLInputElement::SetFiles(FileList* aFiles, bool aSetValueChanged) {
2497 MOZ_ASSERT(mFileData);
2499 mFileData->mFilesOrDirectories.Clear();
2500 mFileData->ClearGetFilesHelpers();
2502 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2503 HTMLInputElement_Binding::ClearCachedWebkitEntriesValue(this);
2504 mFileData->mEntries.Clear();
2507 if (aFiles) {
2508 uint32_t listLength = aFiles->Length();
2509 for (uint32_t i = 0; i < listLength; i++) {
2510 OwningFileOrDirectory* element =
2511 mFileData->mFilesOrDirectories.AppendElement();
2512 element->SetAsFile() = aFiles->Item(i);
2516 AfterSetFilesOrDirectories(aSetValueChanged);
2519 // This method is used for testing only.
2520 void HTMLInputElement::MozSetDndFilesAndDirectories(
2521 const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories) {
2522 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2523 return;
2526 SetFilesOrDirectories(aFilesOrDirectories, true);
2528 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2529 UpdateEntries(aFilesOrDirectories);
2532 RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
2533 new DispatchChangeEventCallback(this);
2535 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
2536 HasAttr(nsGkAtoms::webkitdirectory)) {
2537 ErrorResult rv;
2538 GetFilesHelper* helper =
2539 GetOrCreateGetFilesHelper(true /* recursionFlag */, rv);
2540 if (NS_WARN_IF(rv.Failed())) {
2541 rv.SuppressException();
2542 return;
2545 helper->AddCallback(dispatchChangeEventCallback);
2546 } else {
2547 dispatchChangeEventCallback->DispatchEvents();
2551 void HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged) {
2552 // No need to flush here, if there's no frame at this point we
2553 // don't need to force creation of one just to tell it about this
2554 // new value. We just want the display to update as needed.
2555 nsIFormControlFrame* formControlFrame = GetFormControlFrame(false);
2556 if (formControlFrame) {
2557 nsAutoString readableValue;
2558 GetDisplayFileName(readableValue);
2559 formControlFrame->SetFormProperty(nsGkAtoms::value, readableValue);
2562 // Grab the full path here for any chrome callers who access our .value via a
2563 // CPOW. This path won't be called from a CPOW meaning the potential sync IPC
2564 // call under GetMozFullPath won't be rejected for not being urgent.
2565 if (mFileData->mFilesOrDirectories.IsEmpty()) {
2566 mFileData->mFirstFilePath.Truncate();
2567 } else {
2568 ErrorResult rv;
2569 GetDOMFileOrDirectoryPath(mFileData->mFilesOrDirectories[0],
2570 mFileData->mFirstFilePath, rv);
2571 if (NS_WARN_IF(rv.Failed())) {
2572 rv.SuppressException();
2576 // Null out |mFileData->mFileList| to return a new file list when asked for.
2577 // Don't clear it since the file list might come from the user via SetFiles.
2578 if (mFileData->mFileList) {
2579 mFileData->mFileList = nullptr;
2582 if (aSetValueChanged) {
2583 SetValueChanged(true);
2586 UpdateAllValidityStates(true);
2589 void HTMLInputElement::FireChangeEventIfNeeded() {
2590 // We're not exposing the GetValue return value anywhere here, so it's safe to
2591 // claim to be a system caller.
2592 nsAutoString value;
2593 GetValue(value, CallerType::System);
2595 if (!MayFireChangeOnBlur() || mFocusedValue.Equals(value)) {
2596 return;
2599 // Dispatch the change event.
2600 mFocusedValue = value;
2601 nsContentUtils::DispatchTrustedEvent(
2602 OwnerDoc(), static_cast<nsIContent*>(this), u"change"_ns, CanBubble::eYes,
2603 Cancelable::eNo);
2606 FileList* HTMLInputElement::GetFiles() {
2607 if (mType != FormControlType::InputFile) {
2608 return nullptr;
2611 if (!mFileData->mFileList) {
2612 mFileData->mFileList = new FileList(static_cast<nsIContent*>(this));
2613 for (const OwningFileOrDirectory& item : GetFilesOrDirectoriesInternal()) {
2614 if (item.IsFile()) {
2615 mFileData->mFileList->Append(item.GetAsFile());
2620 return mFileData->mFileList;
2623 void HTMLInputElement::SetFiles(FileList* aFiles) {
2624 if (mType != FormControlType::InputFile || !aFiles) {
2625 return;
2628 // Update |mFileData->mFilesOrDirectories|
2629 SetFiles(aFiles, true);
2631 MOZ_ASSERT(!mFileData->mFileList, "Should've cleared the existing file list");
2633 // Update |mFileData->mFileList| without copy
2634 mFileData->mFileList = aFiles;
2637 /* static */
2638 void HTMLInputElement::HandleNumberControlSpin(void* aData) {
2639 RefPtr<HTMLInputElement> input = static_cast<HTMLInputElement*>(aData);
2641 NS_ASSERTION(input->mNumberControlSpinnerIsSpinning,
2642 "Should have called nsRepeatService::Stop()");
2644 nsNumberControlFrame* numberControlFrame =
2645 do_QueryFrame(input->GetPrimaryFrame());
2646 if (input->mType != FormControlType::InputNumber || !numberControlFrame) {
2647 // Type has changed (and possibly our frame type hasn't been updated yet)
2648 // or else we've lost our frame. Either way, stop the timer and don't do
2649 // anything else.
2650 input->StopNumberControlSpinnerSpin();
2651 } else {
2652 input->StepNumberControlForUserEvent(
2653 input->mNumberControlSpinnerSpinsUp ? 1 : -1);
2657 nsresult HTMLInputElement::SetValueInternal(
2658 const nsAString& aValue, const nsAString* aOldValue,
2659 const ValueSetterOptions& aOptions) {
2660 MOZ_ASSERT(GetValueMode() != VALUE_MODE_FILENAME,
2661 "Don't call SetValueInternal for file inputs");
2663 // We want to remember if the SetValueInternal() call is being made for a XUL
2664 // element. We do that by looking at the parent node here, and if that node
2665 // is a XUL node, we consider our control a XUL control. XUL controls preserve
2666 // edit history across value setters.
2668 // TODO(emilio): Rather than doing this maybe add an attribute instead and
2669 // read it only on chrome docs or something? That'd allow front-end code to
2670 // move away from xul without weird side-effects.
2671 const bool forcePreserveUndoHistory = mParent && mParent->IsXULElement();
2673 switch (GetValueMode()) {
2674 case VALUE_MODE_VALUE: {
2675 // At the moment, only single line text control have to sanitize their
2676 // value Because we have to create a new string for that, we should
2677 // prevent doing it if it's useless.
2678 nsAutoString value(aValue);
2680 if (mDoneCreating &&
2681 !(mType == FormControlType::InputNumber &&
2682 aOptions.contains(ValueSetterOption::BySetUserInputAPI))) {
2683 // When the value of a number input is set by a script, we need to make
2684 // sure the value is a valid floating-point number.
2685 // https://html.spec.whatwg.org/#valid-floating-point-number
2686 // When it's set by a user, however, we need to be more permissive, so
2687 // we don't sanitize its value here. See bug 1839572.
2688 SanitizeValue(value);
2690 // else DoneCreatingElement calls us again once mDoneCreating is true
2692 const bool setValueChanged =
2693 aOptions.contains(ValueSetterOption::SetValueChanged);
2694 if (setValueChanged) {
2695 SetValueChanged(true);
2698 if (IsSingleLineTextControl(false)) {
2699 // Note that if aOptions includes
2700 // ValueSetterOption::BySetUserInputAPI, "input" event is automatically
2701 // dispatched by TextControlState::SetValue(). If you'd change condition
2702 // of calling this method, you need to maintain SetUserInput() too. FYI:
2703 // After calling SetValue(), the input type might have been
2704 // modified so that mInputData may not store TextControlState.
2705 if (!mInputData.mState->SetValue(
2706 value, aOldValue,
2707 forcePreserveUndoHistory
2708 ? aOptions + ValueSetterOption::PreserveUndoHistory
2709 : aOptions)) {
2710 return NS_ERROR_OUT_OF_MEMORY;
2712 // If the caller won't dispatch "input" event via
2713 // nsContentUtils::DispatchInputEvent(), we need to modify
2714 // validationMessage value here.
2716 // FIXME(emilio): ValueSetterOption::ByInternalAPI is not supposed to
2717 // change state, but maybe we could run this too?
2718 if (aOptions.contains(ValueSetterOption::ByContentAPI)) {
2719 MaybeUpdateAllValidityStates(!mDoneCreating);
2721 } else {
2722 free(mInputData.mValue);
2723 mInputData.mValue = ToNewUnicode(value);
2724 if (setValueChanged) {
2725 SetValueChanged(true);
2727 if (mType == FormControlType::InputRange) {
2728 nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
2729 if (frame) {
2730 frame->UpdateForValueChange();
2732 } else if (CreatesDateTimeWidget() &&
2733 !aOptions.contains(ValueSetterOption::BySetUserInputAPI)) {
2734 if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
2735 AsyncEventDispatcher::RunDOMEventWhenSafe(
2736 *dateTimeBoxElement, u"MozDateTimeValueChanged"_ns,
2737 CanBubble::eNo, ChromeOnlyDispatch::eNo);
2740 if (mDoneCreating) {
2741 OnValueChanged(ValueChangeKind::Internal, value.IsEmpty(), &value);
2743 // else DoneCreatingElement calls us again once mDoneCreating is true
2746 if (mType == FormControlType::InputColor) {
2747 // Update color frame, to reflect color changes
2748 nsColorControlFrame* colorControlFrame =
2749 do_QueryFrame(GetPrimaryFrame());
2750 if (colorControlFrame) {
2751 colorControlFrame->UpdateColor();
2754 return NS_OK;
2757 case VALUE_MODE_DEFAULT:
2758 case VALUE_MODE_DEFAULT_ON:
2759 // If the value of a hidden input was changed, we mark it changed so that
2760 // we will know we need to save / restore the value. Yes, we are
2761 // overloading the meaning of ValueChanged just a teensy bit to save a
2762 // measly byte of storage space in HTMLInputElement. Yes, you are free to
2763 // make a new flag, NEED_TO_SAVE_VALUE, at such time as mBitField becomes
2764 // a 16-bit value.
2765 if (mType == FormControlType::InputHidden) {
2766 SetValueChanged(true);
2769 // Make sure to keep track of the last value change not being interactive,
2770 // just in case this used to be another kind of editable input before.
2771 // Note that a checked change _could_ really be interactive, but we don't
2772 // keep track of that elsewhere so seems fine to just do this.
2773 SetLastValueChangeWasInteractive(false);
2775 // Treat value == defaultValue for other input elements.
2776 return nsGenericHTMLFormControlElementWithState::SetAttr(
2777 kNameSpaceID_None, nsGkAtoms::value, aValue, true);
2779 case VALUE_MODE_FILENAME:
2780 return NS_ERROR_UNEXPECTED;
2783 // This return statement is required for some compilers.
2784 return NS_OK;
2787 void HTMLInputElement::SetValueChanged(bool aValueChanged) {
2788 if (mValueChanged == aValueChanged) {
2789 return;
2791 mValueChanged = aValueChanged;
2792 UpdateTooLongValidityState();
2793 UpdateTooShortValidityState();
2794 UpdateValidityElementStates(true);
2797 void HTMLInputElement::SetLastValueChangeWasInteractive(bool aWasInteractive) {
2798 if (aWasInteractive == mLastValueChangeWasInteractive) {
2799 return;
2801 mLastValueChangeWasInteractive = aWasInteractive;
2802 const bool wasValid = IsValid();
2803 UpdateTooLongValidityState();
2804 UpdateTooShortValidityState();
2805 if (wasValid != IsValid()) {
2806 UpdateValidityElementStates(true);
2810 void HTMLInputElement::SetCheckedChanged(bool aCheckedChanged) {
2811 DoSetCheckedChanged(aCheckedChanged, true);
2814 void HTMLInputElement::DoSetCheckedChanged(bool aCheckedChanged, bool aNotify) {
2815 if (mType == FormControlType::InputRadio) {
2816 if (mCheckedChanged != aCheckedChanged) {
2817 nsCOMPtr<nsIRadioVisitor> visitor =
2818 new nsRadioSetCheckedChangedVisitor(aCheckedChanged);
2819 VisitGroup(visitor);
2821 } else {
2822 SetCheckedChangedInternal(aCheckedChanged);
2826 void HTMLInputElement::SetCheckedChangedInternal(bool aCheckedChanged) {
2827 if (mCheckedChanged == aCheckedChanged) {
2828 return;
2830 mCheckedChanged = aCheckedChanged;
2831 UpdateValidityElementStates(true);
2834 void HTMLInputElement::SetChecked(bool aChecked) {
2835 DoSetChecked(aChecked, true, true);
2838 void HTMLInputElement::DoSetChecked(bool aChecked, bool aNotify,
2839 bool aSetValueChanged) {
2840 // If the user or JS attempts to set checked, whether it actually changes the
2841 // value or not, we say the value was changed so that defaultValue don't
2842 // affect it no more.
2843 if (aSetValueChanged) {
2844 DoSetCheckedChanged(true, aNotify);
2847 // Don't do anything if we're not changing whether it's checked (it would
2848 // screw up state actually, especially when you are setting radio button to
2849 // false)
2850 if (mChecked == aChecked) {
2851 return;
2854 // Set checked
2855 if (mType != FormControlType::InputRadio) {
2856 SetCheckedInternal(aChecked, aNotify);
2857 return;
2860 // For radio button, we need to do some extra fun stuff
2861 if (aChecked) {
2862 RadioSetChecked(aNotify);
2863 return;
2866 if (auto* container = GetCurrentRadioGroupContainer()) {
2867 nsAutoString name;
2868 GetAttr(nsGkAtoms::name, name);
2869 container->SetCurrentRadioButton(name, nullptr);
2871 // SetCheckedInternal is going to ask all radios to update their
2872 // validity state. We have to be sure the radio group container knows
2873 // the currently selected radio.
2874 SetCheckedInternal(false, aNotify);
2877 void HTMLInputElement::RadioSetChecked(bool aNotify) {
2878 // Find the selected radio button so we can deselect it
2879 HTMLInputElement* currentlySelected = GetSelectedRadioButton();
2881 // Deselect the currently selected radio button
2882 if (currentlySelected) {
2883 // Pass true for the aNotify parameter since the currently selected
2884 // button is already in the document.
2885 currentlySelected->SetCheckedInternal(false, true);
2888 // Let the group know that we are now the One True Radio Button
2889 if (auto* container = GetCurrentRadioGroupContainer()) {
2890 nsAutoString name;
2891 GetAttr(nsGkAtoms::name, name);
2892 container->SetCurrentRadioButton(name, this);
2895 // SetCheckedInternal is going to ask all radios to update their
2896 // validity state.
2897 SetCheckedInternal(true, aNotify);
2900 RadioGroupContainer* HTMLInputElement::GetCurrentRadioGroupContainer() const {
2901 NS_ASSERTION(
2902 mType == FormControlType::InputRadio,
2903 "GetRadioGroupContainer should only be called when type='radio'");
2904 return mRadioGroupContainer;
2907 RadioGroupContainer* HTMLInputElement::FindTreeRadioGroupContainer() const {
2908 nsAutoString name;
2909 GetAttr(nsGkAtoms::name, name);
2911 if (name.IsEmpty()) {
2912 return nullptr;
2914 if (mForm) {
2915 return &mForm->OwnedRadioGroupContainer();
2917 if (IsInNativeAnonymousSubtree()) {
2918 return nullptr;
2920 if (Document* doc = GetUncomposedDoc()) {
2921 return &doc->OwnedRadioGroupContainer();
2923 return &static_cast<FragmentOrElement*>(SubtreeRoot())
2924 ->OwnedRadioGroupContainer();
2927 void HTMLInputElement::DisconnectRadioGroupContainer() {
2928 mRadioGroupContainer = nullptr;
2931 HTMLInputElement* HTMLInputElement::GetSelectedRadioButton() const {
2932 auto* container = GetCurrentRadioGroupContainer();
2933 if (!container) {
2934 return nullptr;
2937 nsAutoString name;
2938 GetAttr(nsGkAtoms::name, name);
2940 HTMLInputElement* selected = container->GetCurrentRadioButton(name);
2941 return selected;
2944 void HTMLInputElement::MaybeSubmitForm(nsPresContext* aPresContext) {
2945 if (!mForm) {
2946 // Nothing to do here.
2947 return;
2950 RefPtr<PresShell> presShell = aPresContext->GetPresShell();
2951 if (!presShell) {
2952 return;
2955 // Get the default submit element
2956 if (RefPtr<nsGenericHTMLFormElement> submitContent =
2957 mForm->GetDefaultSubmitElement()) {
2958 WidgetMouseEvent event(true, eMouseClick, nullptr, WidgetMouseEvent::eReal);
2959 nsEventStatus status = nsEventStatus_eIgnore;
2960 presShell->HandleDOMEventWithTarget(submitContent, &event, &status);
2961 } else if (!mForm->ImplicitSubmissionIsDisabled()) {
2962 // If there's only one text control, just submit the form
2963 // Hold strong ref across the event
2964 RefPtr<dom::HTMLFormElement> form(mForm);
2965 form->MaybeSubmit(nullptr);
2969 void HTMLInputElement::UpdateCheckedState(bool aNotify) {
2970 SetStates(ElementState::CHECKED, IsRadioOrCheckbox() && mChecked, aNotify);
2973 void HTMLInputElement::UpdateIndeterminateState(bool aNotify) {
2974 bool indeterminate = [&] {
2975 if (mType == FormControlType::InputCheckbox) {
2976 return mIndeterminate;
2978 if (mType == FormControlType::InputRadio) {
2979 return !mChecked && !GetSelectedRadioButton();
2981 return false;
2982 }();
2983 SetStates(ElementState::INDETERMINATE, indeterminate, aNotify);
2986 void HTMLInputElement::SetCheckedInternal(bool aChecked, bool aNotify) {
2987 // Set the value
2988 mChecked = aChecked;
2990 if (IsRadioOrCheckbox()) {
2991 SetStates(ElementState::CHECKED, aChecked, aNotify);
2994 // No need to update element state, since we're about to call
2995 // UpdateState anyway.
2996 UpdateAllValidityStatesButNotElementState();
2997 UpdateIndeterminateState(aNotify);
2998 UpdateValidityElementStates(aNotify);
3000 // Notify all radios in the group that value has changed, this is to let
3001 // radios to have the chance to update its states, e.g., :indeterminate.
3002 if (mType == FormControlType::InputRadio) {
3003 nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
3004 VisitGroup(visitor);
3008 #if !defined(ANDROID) && !defined(XP_MACOSX)
3009 bool HTMLInputElement::IsNodeApzAwareInternal() const {
3010 // Tell APZC we may handle mouse wheel event and do preventDefault when input
3011 // type is number.
3012 return mType == FormControlType::InputNumber ||
3013 mType == FormControlType::InputRange ||
3014 nsINode::IsNodeApzAwareInternal();
3016 #endif
3018 bool HTMLInputElement::IsInteractiveHTMLContent() const {
3019 return mType != FormControlType::InputHidden ||
3020 nsGenericHTMLFormControlElementWithState::IsInteractiveHTMLContent();
3023 void HTMLInputElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) {
3024 nsImageLoadingContent::AsyncEventRunning(aEvent);
3027 void HTMLInputElement::Select() {
3028 if (!IsSingleLineTextControl(false)) {
3029 return;
3032 TextControlState* state = GetEditorState();
3033 MOZ_ASSERT(state, "Single line text controls are expected to have a state");
3035 if (FocusState() != FocusTristate::eUnfocusable) {
3036 RefPtr<nsFrameSelection> fs = state->GetConstFrameSelection();
3037 if (fs && fs->MouseDownRecorded()) {
3038 // This means that we're being called while the frame selection has a
3039 // mouse down event recorded to adjust the caret during the mouse up
3040 // event. We are probably called from the focus event handler. We should
3041 // override the delayed caret data in this case to ensure that this
3042 // select() call takes effect.
3043 fs->SetDelayedCaretData(nullptr);
3046 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
3047 fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL);
3049 // A focus event handler may change the type attribute, which will destroy
3050 // the previous state object.
3051 state = GetEditorState();
3052 if (!state) {
3053 return;
3058 // Directly call TextControlState::SetSelectionRange because
3059 // HTMLInputElement::SetSelectionRange only applies to fewer types
3060 state->SetSelectionRange(0, UINT32_MAX, Optional<nsAString>(), IgnoreErrors(),
3061 TextControlState::ScrollAfterSelection::No);
3064 void HTMLInputElement::SelectAll(nsPresContext* aPresContext) {
3065 nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
3067 if (formControlFrame) {
3068 formControlFrame->SetFormProperty(nsGkAtoms::select, u""_ns);
3072 bool HTMLInputElement::NeedToInitializeEditorForEvent(
3073 EventChainPreVisitor& aVisitor) const {
3074 // We only need to initialize the editor for single line input controls
3075 // because they are lazily initialized. We don't need to initialize the
3076 // control for certain types of events, because we know that those events are
3077 // safe to be handled without the editor being initialized. These events
3078 // include: mousein/move/out, overflow/underflow, DOM mutation, and void
3079 // events. Void events are dispatched frequently by async keyboard scrolling
3080 // to focused elements, so it's important to handle them to prevent excessive
3081 // DOM mutations.
3082 if (!IsSingleLineTextControl(false) ||
3083 aVisitor.mEvent->mClass == eMutationEventClass) {
3084 return false;
3087 switch (aVisitor.mEvent->mMessage) {
3088 case eVoidEvent:
3089 case eMouseMove:
3090 case eMouseEnterIntoWidget:
3091 case eMouseExitFromWidget:
3092 case eMouseOver:
3093 case eMouseOut:
3094 case eScrollPortUnderflow:
3095 case eScrollPortOverflow:
3096 return false;
3097 default:
3098 return true;
3102 bool HTMLInputElement::IsDisabledForEvents(WidgetEvent* aEvent) {
3103 return IsElementDisabledForEvents(aEvent, GetPrimaryFrame());
3106 bool HTMLInputElement::CheckActivationBehaviorPreconditions(
3107 EventChainVisitor& aVisitor) const {
3108 switch (mType) {
3109 case FormControlType::InputColor:
3110 case FormControlType::InputCheckbox:
3111 case FormControlType::InputRadio:
3112 case FormControlType::InputFile:
3113 case FormControlType::InputSubmit:
3114 case FormControlType::InputImage:
3115 case FormControlType::InputReset:
3116 case FormControlType::InputButton: {
3117 // Track whether we're in the outermost Dispatch invocation that will
3118 // cause activation of the input. That is, if we're a click event, or a
3119 // DOMActivate that was dispatched directly, this will be set, but if
3120 // we're a DOMActivate dispatched from click handling, it will not be set.
3121 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3122 bool outerActivateEvent =
3123 ((mouseEvent && mouseEvent->IsLeftClickEvent()) ||
3124 (aVisitor.mEvent->mMessage == eLegacyDOMActivate &&
3125 !mInInternalActivate));
3126 if (outerActivateEvent) {
3127 aVisitor.mItemFlags |= NS_OUTER_ACTIVATE_EVENT;
3129 return outerActivateEvent &&
3130 !aVisitor.mEvent->mFlags.mMultiplePreActionsPrevented;
3132 default:
3133 return false;
3137 void HTMLInputElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
3138 // Do not process any DOM events if the element is disabled
3139 aVisitor.mCanHandle = false;
3140 if (IsDisabledForEvents(aVisitor.mEvent)) {
3141 return;
3144 // Initialize the editor if needed.
3145 if (NeedToInitializeEditorForEvent(aVisitor)) {
3146 nsITextControlFrame* textControlFrame = do_QueryFrame(GetPrimaryFrame());
3147 if (textControlFrame) textControlFrame->EnsureEditorInitialized();
3150 if (CheckActivationBehaviorPreconditions(aVisitor)) {
3151 aVisitor.mWantsActivationBehavior = true;
3153 if ((mType == FormControlType::InputSubmit ||
3154 mType == FormControlType::InputImage) &&
3155 mForm) {
3156 // Make sure other submit elements don't try to trigger submission.
3157 aVisitor.mItemFlags |= NS_IN_SUBMIT_CLICK;
3158 aVisitor.mItemData = static_cast<Element*>(mForm);
3159 // tell the form that we are about to enter a click handler.
3160 // that means that if there are scripted submissions, the
3161 // latest one will be deferred until after the exit point of the
3162 // handler.
3163 mForm->OnSubmitClickBegin(this);
3167 // We must cache type because mType may change during JS event (bug 2369)
3168 aVisitor.mItemFlags |= uint8_t(mType);
3170 if (aVisitor.mEvent->mMessage == eFocus && aVisitor.mEvent->IsTrusted() &&
3171 MayFireChangeOnBlur() &&
3172 // StartRangeThumbDrag already set mFocusedValue on 'mousedown' before
3173 // we get the 'focus' event.
3174 !mIsDraggingRange) {
3175 GetValue(mFocusedValue, CallerType::System);
3178 // Fire onchange (if necessary), before we do the blur, bug 357684.
3179 if (aVisitor.mEvent->mMessage == eBlur) {
3180 // We set NS_PRE_HANDLE_BLUR_EVENT here and handle it in PreHandleEvent to
3181 // prevent breaking event target chain creation.
3182 aVisitor.mWantsPreHandleEvent = true;
3183 aVisitor.mItemFlags |= NS_PRE_HANDLE_BLUR_EVENT;
3186 if (mType == FormControlType::InputRange &&
3187 (aVisitor.mEvent->mMessage == eFocus ||
3188 aVisitor.mEvent->mMessage == eBlur)) {
3189 // Just as nsGenericHTMLFormControlElementWithState::GetEventTargetParent
3190 // calls nsIFormControlFrame::SetFocus, we handle focus here.
3191 nsIFrame* frame = GetPrimaryFrame();
3192 if (frame) {
3193 frame->InvalidateFrameSubtree();
3197 if (mType == FormControlType::InputNumber && aVisitor.mEvent->IsTrusted()) {
3198 if (mNumberControlSpinnerIsSpinning) {
3199 // If the timer is running the user has depressed the mouse on one of the
3200 // spin buttons. If the mouse exits the button we either want to reverse
3201 // the direction of spin if it has moved over the other button, or else
3202 // we want to end the spin. We do this here (rather than in
3203 // PostHandleEvent) because we don't want to let content preventDefault()
3204 // the end of the spin.
3205 if (aVisitor.mEvent->mMessage == eMouseMove) {
3206 // Be aggressive about stopping the spin:
3207 bool stopSpin = true;
3208 nsNumberControlFrame* numberControlFrame =
3209 do_QueryFrame(GetPrimaryFrame());
3210 if (numberControlFrame) {
3211 bool oldNumberControlSpinTimerSpinsUpValue =
3212 mNumberControlSpinnerSpinsUp;
3213 switch (numberControlFrame->GetSpinButtonForPointerEvent(
3214 aVisitor.mEvent->AsMouseEvent())) {
3215 case nsNumberControlFrame::eSpinButtonUp:
3216 mNumberControlSpinnerSpinsUp = true;
3217 stopSpin = false;
3218 break;
3219 case nsNumberControlFrame::eSpinButtonDown:
3220 mNumberControlSpinnerSpinsUp = false;
3221 stopSpin = false;
3222 break;
3224 if (mNumberControlSpinnerSpinsUp !=
3225 oldNumberControlSpinTimerSpinsUpValue) {
3226 nsNumberControlFrame* numberControlFrame =
3227 do_QueryFrame(GetPrimaryFrame());
3228 if (numberControlFrame) {
3229 numberControlFrame->SpinnerStateChanged();
3233 if (stopSpin) {
3234 StopNumberControlSpinnerSpin();
3236 } else if (aVisitor.mEvent->mMessage == eMouseUp) {
3237 StopNumberControlSpinnerSpin();
3242 nsGenericHTMLFormControlElementWithState::GetEventTargetParent(aVisitor);
3244 // Stop the event if the related target's first non-native ancestor is the
3245 // same as the original target's first non-native ancestor (we are moving
3246 // inside of the same element).
3248 // FIXME(emilio): Is this still needed now that we use Shadow DOM for this?
3249 if (CreatesDateTimeWidget() && aVisitor.mEvent->IsTrusted() &&
3250 (aVisitor.mEvent->mMessage == eFocus ||
3251 aVisitor.mEvent->mMessage == eFocusIn ||
3252 aVisitor.mEvent->mMessage == eFocusOut ||
3253 aVisitor.mEvent->mMessage == eBlur)) {
3254 nsIContent* originalTarget = nsIContent::FromEventTargetOrNull(
3255 aVisitor.mEvent->AsFocusEvent()->mOriginalTarget);
3256 nsIContent* relatedTarget = nsIContent::FromEventTargetOrNull(
3257 aVisitor.mEvent->AsFocusEvent()->mRelatedTarget);
3259 if (originalTarget && relatedTarget &&
3260 originalTarget->FindFirstNonChromeOnlyAccessContent() ==
3261 relatedTarget->FindFirstNonChromeOnlyAccessContent()) {
3262 aVisitor.mCanHandle = false;
3267 void HTMLInputElement::LegacyPreActivationBehavior(
3268 EventChainVisitor& aVisitor) {
3270 // Web pages expect the value of a radio button or checkbox to be set
3271 // *before* onclick and DOMActivate fire, and they expect that if they set
3272 // the value explicitly during onclick or DOMActivate it will not be toggled
3273 // or any such nonsense.
3274 // In order to support that (bug 57137 and 58460 are examples) we toggle
3275 // the checked attribute *first*, and then fire onclick. If the user
3276 // returns false, we reset the control to the old checked value. Otherwise,
3277 // we dispatch DOMActivate. If DOMActivate is cancelled, we also reset
3278 // the control to the old checked value. We need to keep track of whether
3279 // we've already toggled the state from onclick since the user could
3280 // explicitly dispatch DOMActivate on the element.
3282 // These are compatibility hacks and are defined as legacy-pre-activation
3283 // and legacy-canceled-activation behavior in HTML.
3286 bool originalCheckedValue = false;
3287 mCheckedIsToggled = false;
3289 if (mType == FormControlType::InputCheckbox) {
3290 if (mIndeterminate) {
3291 // indeterminate is always set to FALSE when the checkbox is toggled
3292 SetIndeterminateInternal(false, false);
3293 aVisitor.mItemFlags |= NS_ORIGINAL_INDETERMINATE_VALUE;
3296 originalCheckedValue = Checked();
3297 DoSetChecked(!originalCheckedValue, true, true);
3298 mCheckedIsToggled = true;
3300 if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
3301 aVisitor.mEventStatus = nsEventStatus_eConsumeDoDefault;
3303 } else if (mType == FormControlType::InputRadio) {
3304 HTMLInputElement* selectedRadioButton = GetSelectedRadioButton();
3305 aVisitor.mItemData = static_cast<Element*>(selectedRadioButton);
3307 originalCheckedValue = Checked();
3308 if (!originalCheckedValue) {
3309 DoSetChecked(true, true, true);
3310 mCheckedIsToggled = true;
3313 if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
3314 aVisitor.mEventStatus = nsEventStatus_eConsumeDoDefault;
3318 if (originalCheckedValue) {
3319 aVisitor.mItemFlags |= NS_ORIGINAL_CHECKED_VALUE;
3323 nsresult HTMLInputElement::PreHandleEvent(EventChainVisitor& aVisitor) {
3324 if (aVisitor.mItemFlags & NS_PRE_HANDLE_BLUR_EVENT) {
3325 MOZ_ASSERT(aVisitor.mEvent->mMessage == eBlur);
3326 FireChangeEventIfNeeded();
3328 return nsGenericHTMLFormControlElementWithState::PreHandleEvent(aVisitor);
3331 void HTMLInputElement::StartRangeThumbDrag(WidgetGUIEvent* aEvent) {
3332 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
3333 if (!rangeFrame) {
3334 return;
3337 mIsDraggingRange = true;
3338 mRangeThumbDragStartValue = GetValueAsDecimal();
3339 // Don't use CaptureFlags::RetargetToElement, as that breaks pseudo-class
3340 // styling of the thumb.
3341 PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState);
3343 // Before we change the value, record the current value so that we'll
3344 // correctly send a 'change' event if appropriate. We need to do this here
3345 // because the 'focus' event is handled after the 'mousedown' event that
3346 // we're being called for (i.e. too late to update mFocusedValue, since we'll
3347 // have changed it by then).
3348 GetValue(mFocusedValue, CallerType::System);
3350 SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent),
3351 SnapToTickMarks::Yes);
3354 void HTMLInputElement::FinishRangeThumbDrag(WidgetGUIEvent* aEvent) {
3355 MOZ_ASSERT(mIsDraggingRange);
3357 if (PresShell::GetCapturingContent() == this) {
3358 PresShell::ReleaseCapturingContent();
3360 if (aEvent) {
3361 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
3362 SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent),
3363 SnapToTickMarks::Yes);
3365 mIsDraggingRange = false;
3366 FireChangeEventIfNeeded();
3369 void HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent) {
3370 MOZ_ASSERT(mIsDraggingRange);
3372 mIsDraggingRange = false;
3373 if (PresShell::GetCapturingContent() == this) {
3374 PresShell::ReleaseCapturingContent();
3376 if (aIsForUserEvent) {
3377 SetValueOfRangeForUserEvent(mRangeThumbDragStartValue,
3378 SnapToTickMarks::Yes);
3379 } else {
3380 // Don't dispatch an 'input' event - at least not using
3381 // DispatchTrustedEvent.
3382 // TODO: decide what we should do here - bug 851782.
3383 nsAutoString val;
3384 mInputType->ConvertNumberToString(mRangeThumbDragStartValue, val);
3385 // TODO: What should we do if SetValueInternal fails? (The allocation
3386 // is small, so we should be fine here.)
3387 SetValueInternal(val, {ValueSetterOption::BySetUserInputAPI,
3388 ValueSetterOption::SetValueChanged});
3389 if (nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame())) {
3390 frame->UpdateForValueChange();
3392 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
3393 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3394 "Failed to dispatch input event");
3398 void HTMLInputElement::SetValueOfRangeForUserEvent(
3399 Decimal aValue, SnapToTickMarks aSnapToTickMarks) {
3400 MOZ_ASSERT(aValue.isFinite());
3401 if (aSnapToTickMarks == SnapToTickMarks::Yes) {
3402 MaybeSnapToTickMark(aValue);
3405 Decimal oldValue = GetValueAsDecimal();
3407 nsAutoString val;
3408 mInputType->ConvertNumberToString(aValue, val);
3409 // TODO: What should we do if SetValueInternal fails? (The allocation
3410 // is small, so we should be fine here.)
3411 SetValueInternal(val, {ValueSetterOption::BySetUserInputAPI,
3412 ValueSetterOption::SetValueChanged});
3413 if (nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame())) {
3414 frame->UpdateForValueChange();
3417 if (GetValueAsDecimal() != oldValue) {
3418 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
3419 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3420 "Failed to dispatch input event");
3424 void HTMLInputElement::StartNumberControlSpinnerSpin() {
3425 MOZ_ASSERT(!mNumberControlSpinnerIsSpinning);
3427 mNumberControlSpinnerIsSpinning = true;
3429 nsRepeatService::GetInstance()->Start(
3430 HandleNumberControlSpin, this, OwnerDoc(), "HandleNumberControlSpin"_ns);
3432 // Capture the mouse so that we can tell if the pointer moves from one
3433 // spin button to the other, or to some other element:
3434 PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState);
3436 nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
3437 if (numberControlFrame) {
3438 numberControlFrame->SpinnerStateChanged();
3442 void HTMLInputElement::StopNumberControlSpinnerSpin(SpinnerStopState aState) {
3443 if (mNumberControlSpinnerIsSpinning) {
3444 if (PresShell::GetCapturingContent() == this) {
3445 PresShell::ReleaseCapturingContent();
3448 nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin, this);
3450 mNumberControlSpinnerIsSpinning = false;
3452 if (aState == eAllowDispatchingEvents) {
3453 FireChangeEventIfNeeded();
3456 nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
3457 if (numberControlFrame) {
3458 MOZ_ASSERT(aState == eAllowDispatchingEvents,
3459 "Shouldn't have primary frame for the element when we're not "
3460 "allowed to dispatch events to it anymore.");
3461 numberControlFrame->SpinnerStateChanged();
3466 void HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection) {
3467 // We can't use GetValidityState here because the validity state is not set
3468 // if the user hasn't previously taken an action to set or change the value,
3469 // according to the specs.
3470 if (HasBadInput()) {
3471 // If the user has typed a value into the control and inadvertently made a
3472 // mistake (e.g. put a thousand separator at the wrong point) we do not
3473 // want to wipe out what they typed if they try to increment/decrement the
3474 // value. Better is to highlight the value as being invalid so that they
3475 // can correct what they typed.
3476 // We only do this if there actually is a value typed in by/displayed to
3477 // the user. (IsValid() can return false if the 'required' attribute is
3478 // set and the value is the empty string.)
3479 if (!IsValueEmpty()) {
3480 // We pass 'true' for UpdateValidityUIBits' aIsFocused argument
3481 // regardless because we need the UI to update _now_ or the user will
3482 // wonder why the step behavior isn't functioning.
3483 UpdateValidityUIBits(true);
3484 UpdateValidityElementStates(true);
3485 return;
3489 Decimal newValue = Decimal::nan(); // unchanged if value will not change
3491 nsresult rv = GetValueIfStepped(aDirection, CALLED_FOR_USER_EVENT, &newValue);
3493 if (NS_FAILED(rv) || !newValue.isFinite()) {
3494 return; // value should not or will not change
3497 nsAutoString newVal;
3498 mInputType->ConvertNumberToString(newValue, newVal);
3499 // TODO: What should we do if SetValueInternal fails? (The allocation
3500 // is small, so we should be fine here.)
3501 SetValueInternal(newVal, {ValueSetterOption::BySetUserInputAPI,
3502 ValueSetterOption::SetValueChanged});
3505 static bool SelectTextFieldOnFocus() {
3506 if (!gSelectTextFieldOnFocus) {
3507 int32_t selectTextfieldsOnKeyFocus = -1;
3508 nsresult rv =
3509 LookAndFeel::GetInt(LookAndFeel::IntID::SelectTextfieldsOnKeyFocus,
3510 &selectTextfieldsOnKeyFocus);
3511 if (NS_FAILED(rv)) {
3512 gSelectTextFieldOnFocus = -1;
3513 } else {
3514 gSelectTextFieldOnFocus = selectTextfieldsOnKeyFocus != 0 ? 1 : -1;
3518 return gSelectTextFieldOnFocus == 1;
3521 bool HTMLInputElement::ShouldPreventDOMActivateDispatch(
3522 EventTarget* aOriginalTarget) {
3524 * For the moment, there is only one situation where we actually want to
3525 * prevent firing a DOMActivate event:
3526 * - we are a <input type='file'> that just got a click event,
3527 * - the event was targeted to our button which should have sent a
3528 * DOMActivate event.
3531 if (mType != FormControlType::InputFile) {
3532 return false;
3535 Element* target = Element::FromEventTargetOrNull(aOriginalTarget);
3536 if (!target) {
3537 return false;
3540 return target->GetParent() == this &&
3541 target->IsRootOfNativeAnonymousSubtree() &&
3542 target->IsHTMLElement(nsGkAtoms::button);
3545 nsresult HTMLInputElement::MaybeInitPickers(EventChainPostVisitor& aVisitor) {
3546 // Open a file picker when we receive a click on a <input type='file'>, or
3547 // open a color picker when we receive a click on a <input type='color'>.
3548 // A click is handled if it's the left mouse button.
3549 // We do not prevent non-trusted click because authors can already use
3550 // .click(). However, the pickers will follow the rules of popup-blocking.
3551 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3552 if (!(mouseEvent && mouseEvent->IsLeftClickEvent())) {
3553 return NS_OK;
3555 if (mType == FormControlType::InputFile) {
3556 // If the user clicked on the "Choose folder..." button we open the
3557 // directory picker, else we open the file picker.
3558 FilePickerType type = FILE_PICKER_FILE;
3559 nsIContent* target =
3560 nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget);
3561 if (target && target->FindFirstNonChromeOnlyAccessContent() == this &&
3562 StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
3563 HasAttr(nsGkAtoms::webkitdirectory)) {
3564 type = FILE_PICKER_DIRECTORY;
3566 return InitFilePicker(type);
3568 if (mType == FormControlType::InputColor) {
3569 return InitColorPicker();
3572 return NS_OK;
3576 * Return true if the input event should be ignored because of its modifiers.
3577 * Control is treated specially, since sometimes we ignore it, and sometimes
3578 * we don't (for webcompat reasons).
3580 static bool IgnoreInputEventWithModifier(const WidgetInputEvent& aEvent,
3581 bool ignoreControl) {
3582 return (ignoreControl && aEvent.IsControl()) ||
3583 aEvent.IsAltGraph()
3584 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
3585 // Meta key is the Windows Logo key on Windows and Linux which may
3586 // assign some special meaning for the events while it's pressed.
3587 // On the other hand, it's a normal modifier in macOS and Android.
3588 // Therefore, We should ignore it only in Win/Linux.
3589 || aEvent.IsMeta()
3590 #endif
3591 || aEvent.IsFn();
3594 bool HTMLInputElement::StepsInputValue(
3595 const WidgetKeyboardEvent& aEvent) const {
3596 if (mType != FormControlType::InputNumber) {
3597 return false;
3599 if (aEvent.mMessage != eKeyPress) {
3600 return false;
3602 if (!aEvent.IsTrusted()) {
3603 return false;
3605 if (aEvent.mKeyCode != NS_VK_UP && aEvent.mKeyCode != NS_VK_DOWN) {
3606 return false;
3608 if (IgnoreInputEventWithModifier(aEvent, false)) {
3609 return false;
3611 if (aEvent.DefaultPrevented()) {
3612 return false;
3614 if (!IsMutable()) {
3615 return false;
3617 return true;
3620 static bool ActivatesWithKeyboard(FormControlType aType, uint32_t aKeyCode) {
3621 switch (aType) {
3622 case FormControlType::InputCheckbox:
3623 case FormControlType::InputRadio:
3624 // Checkbox and Radio try to submit on Enter press
3625 return aKeyCode != NS_VK_RETURN;
3626 case FormControlType::InputButton:
3627 case FormControlType::InputReset:
3628 case FormControlType::InputSubmit:
3629 case FormControlType::InputFile:
3630 case FormControlType::InputImage: // Bug 34418
3631 case FormControlType::InputColor:
3632 return true;
3633 default:
3634 return false;
3638 nsresult HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
3639 if (aVisitor.mEvent->mMessage == eFocus ||
3640 aVisitor.mEvent->mMessage == eBlur) {
3641 if (aVisitor.mEvent->mMessage == eBlur) {
3642 if (mIsDraggingRange) {
3643 FinishRangeThumbDrag();
3644 } else if (mNumberControlSpinnerIsSpinning) {
3645 StopNumberControlSpinnerSpin();
3649 UpdateValidityUIBits(aVisitor.mEvent->mMessage == eFocus);
3650 UpdateValidityElementStates(true);
3653 nsresult rv = NS_OK;
3654 bool outerActivateEvent = !!(aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT);
3655 auto oldType = FormControlType(NS_CONTROL_TYPE(aVisitor.mItemFlags));
3657 // Ideally we would make the default action for click and space just dispatch
3658 // DOMActivate, and the default action for DOMActivate flip the checkbox/
3659 // radio state and fire onchange. However, for backwards compatibility, we
3660 // need to flip the state before firing click, and we need to fire click
3661 // when space is pressed. So, we just nest the firing of DOMActivate inside
3662 // the click event handling, and allow cancellation of DOMActivate to cancel
3663 // the click.
3664 if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault &&
3665 !IsSingleLineTextControl(true) && mType != FormControlType::InputNumber) {
3666 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3667 if (mouseEvent && mouseEvent->IsLeftClickEvent() &&
3668 OwnerDoc()->MayHaveDOMActivateListeners() &&
3669 !ShouldPreventDOMActivateDispatch(aVisitor.mEvent->mOriginalTarget)) {
3670 // DOMActive event should be trusted since the activation is actually
3671 // occurred even if the cause is an untrusted click event.
3672 InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
3673 actEvent.mDetail = 1;
3675 if (RefPtr<PresShell> presShell =
3676 aVisitor.mPresContext ? aVisitor.mPresContext->GetPresShell()
3677 : nullptr) {
3678 nsEventStatus status = nsEventStatus_eIgnore;
3679 mInInternalActivate = true;
3680 rv = presShell->HandleDOMEventWithTarget(this, &actEvent, &status);
3681 mInInternalActivate = false;
3683 // If activate is cancelled, we must do the same as when click is
3684 // cancelled (revert the checkbox to its original value).
3685 if (status == nsEventStatus_eConsumeNoDefault) {
3686 aVisitor.mEventStatus = status;
3692 bool preventDefault =
3693 aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault;
3694 if (IsDisabled() && oldType != FormControlType::InputCheckbox &&
3695 oldType != FormControlType::InputRadio) {
3696 // Behave as if defaultPrevented when the element becomes disabled by event
3697 // listeners. Checkboxes and radio buttons should still process clicks for
3698 // web compat. See:
3699 // https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
3700 preventDefault = true;
3703 if (NS_SUCCEEDED(rv)) {
3704 WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
3705 if (keyEvent && StepsInputValue(*keyEvent)) {
3706 StepNumberControlForUserEvent(keyEvent->mKeyCode == NS_VK_UP ? 1 : -1);
3707 FireChangeEventIfNeeded();
3708 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3709 } else if (!preventDefault) {
3710 if (keyEvent && ActivatesWithKeyboard(mType, keyEvent->mKeyCode) &&
3711 keyEvent->IsTrusted()) {
3712 // We maybe dispatch a synthesized click for keyboard activation.
3713 HandleKeyboardActivation(aVisitor);
3716 switch (aVisitor.mEvent->mMessage) {
3717 case eFocus: {
3718 // see if we should select the contents of the textbox. This happens
3719 // for text and password fields when the field was focused by the
3720 // keyboard or a navigation, the platform allows it, and it wasn't
3721 // just because we raised a window.
3723 // While it'd usually make sense, we don't do this for JS callers
3724 // because it causes some compat issues, see bug 1712724 for example.
3725 nsFocusManager* fm = nsFocusManager::GetFocusManager();
3726 if (fm && IsSingleLineTextControl(false) &&
3727 !aVisitor.mEvent->AsFocusEvent()->mFromRaise &&
3728 SelectTextFieldOnFocus()) {
3729 if (Document* document = GetComposedDoc()) {
3730 uint32_t lastFocusMethod =
3731 fm->GetLastFocusMethod(document->GetWindow());
3732 const bool shouldSelectAllOnFocus = [&] {
3733 if (lastFocusMethod & nsIFocusManager::FLAG_BYMOVEFOCUS) {
3734 return true;
3736 if (lastFocusMethod & nsIFocusManager::FLAG_BYJS) {
3737 return false;
3739 return bool(lastFocusMethod & nsIFocusManager::FLAG_BYKEY);
3740 }();
3741 if (shouldSelectAllOnFocus) {
3742 RefPtr<nsPresContext> presContext =
3743 GetPresContext(eForComposedDoc);
3744 SelectAll(presContext);
3748 break;
3751 case eKeyDown: {
3752 // For compatibility with the other browsers, we should active this
3753 // element at least when a checkbox or a radio button.
3754 // TODO: Investigate which elements are activated by space key in the
3755 // other browsers.
3756 if (aVisitor.mPresContext && keyEvent->IsTrusted() && !IsDisabled() &&
3757 keyEvent->ShouldWorkAsSpaceKey() &&
3758 (mType == FormControlType::InputCheckbox ||
3759 mType == FormControlType::InputRadio)) {
3760 EventStateManager::SetActiveManager(
3761 aVisitor.mPresContext->EventStateManager(), this);
3763 break;
3766 case eKeyPress: {
3767 if (mType == FormControlType::InputRadio && keyEvent->IsTrusted() &&
3768 !keyEvent->IsAlt() && !keyEvent->IsControl() &&
3769 !keyEvent->IsMeta()) {
3770 rv = MaybeHandleRadioButtonNavigation(aVisitor, keyEvent->mKeyCode);
3774 * For some input types, if the user hits enter, the form is
3775 * submitted.
3777 * Bug 99920, bug 109463 and bug 147850:
3778 * (a) if there is a submit control in the form, click the first
3779 * submit control in the form.
3780 * (b) if there is just one text control in the form, submit by
3781 * sending a submit event directly to the form
3782 * (c) if there is more than one text input and no submit buttons, do
3783 * not submit, period.
3786 if (keyEvent->mKeyCode == NS_VK_RETURN && keyEvent->IsTrusted() &&
3787 (IsSingleLineTextControl(false, mType) ||
3788 IsDateTimeInputType(mType) ||
3789 mType == FormControlType::InputCheckbox ||
3790 mType == FormControlType::InputRadio)) {
3791 if (IsSingleLineTextControl(false, mType) ||
3792 IsDateTimeInputType(mType)) {
3793 FireChangeEventIfNeeded();
3796 if (aVisitor.mPresContext) {
3797 MaybeSubmitForm(aVisitor.mPresContext);
3801 if (mType == FormControlType::InputRange && keyEvent->IsTrusted() &&
3802 !keyEvent->IsAlt() && !keyEvent->IsControl() &&
3803 !keyEvent->IsMeta() &&
3804 (keyEvent->mKeyCode == NS_VK_LEFT ||
3805 keyEvent->mKeyCode == NS_VK_RIGHT ||
3806 keyEvent->mKeyCode == NS_VK_UP ||
3807 keyEvent->mKeyCode == NS_VK_DOWN ||
3808 keyEvent->mKeyCode == NS_VK_PAGE_UP ||
3809 keyEvent->mKeyCode == NS_VK_PAGE_DOWN ||
3810 keyEvent->mKeyCode == NS_VK_HOME ||
3811 keyEvent->mKeyCode == NS_VK_END)) {
3812 Decimal minimum = GetMinimum();
3813 Decimal maximum = GetMaximum();
3814 MOZ_ASSERT(minimum.isFinite() && maximum.isFinite());
3815 if (minimum < maximum) { // else the value is locked to the minimum
3816 Decimal value = GetValueAsDecimal();
3817 Decimal step = GetStep();
3818 if (step == kStepAny) {
3819 step = GetDefaultStep();
3821 MOZ_ASSERT(value.isFinite() && step.isFinite());
3822 Decimal newValue;
3823 switch (keyEvent->mKeyCode) {
3824 case NS_VK_LEFT:
3825 newValue =
3826 value +
3827 (GetComputedDirectionality() == eDir_RTL ? step : -step);
3828 break;
3829 case NS_VK_RIGHT:
3830 newValue =
3831 value +
3832 (GetComputedDirectionality() == eDir_RTL ? -step : step);
3833 break;
3834 case NS_VK_UP:
3835 // Even for horizontal range, "up" means "increase"
3836 newValue = value + step;
3837 break;
3838 case NS_VK_DOWN:
3839 // Even for horizontal range, "down" means "decrease"
3840 newValue = value - step;
3841 break;
3842 case NS_VK_HOME:
3843 newValue = minimum;
3844 break;
3845 case NS_VK_END:
3846 newValue = maximum;
3847 break;
3848 case NS_VK_PAGE_UP:
3849 // For PgUp/PgDn we jump 10% of the total range, unless step
3850 // requires us to jump more.
3851 newValue =
3852 value + std::max(step, (maximum - minimum) / Decimal(10));
3853 break;
3854 case NS_VK_PAGE_DOWN:
3855 newValue =
3856 value - std::max(step, (maximum - minimum) / Decimal(10));
3857 break;
3859 SetValueOfRangeForUserEvent(newValue);
3860 FireChangeEventIfNeeded();
3861 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3865 } break; // eKeyPress
3867 case eMouseDown:
3868 case eMouseUp:
3869 case eMouseDoubleClick: {
3870 // cancel all of these events for buttons
3871 // XXXsmaug Why?
3872 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3873 if (mouseEvent->mButton == MouseButton::eMiddle ||
3874 mouseEvent->mButton == MouseButton::eSecondary) {
3875 if (mType == FormControlType::InputButton ||
3876 mType == FormControlType::InputReset ||
3877 mType == FormControlType::InputSubmit) {
3878 if (aVisitor.mDOMEvent) {
3879 aVisitor.mDOMEvent->StopPropagation();
3880 } else {
3881 rv = NS_ERROR_FAILURE;
3885 if (mType == FormControlType::InputNumber &&
3886 aVisitor.mEvent->IsTrusted()) {
3887 if (mouseEvent->mButton == MouseButton::ePrimary &&
3888 !IgnoreInputEventWithModifier(*mouseEvent, false)) {
3889 nsNumberControlFrame* numberControlFrame =
3890 do_QueryFrame(GetPrimaryFrame());
3891 if (numberControlFrame) {
3892 if (aVisitor.mEvent->mMessage == eMouseDown && IsMutable()) {
3893 switch (numberControlFrame->GetSpinButtonForPointerEvent(
3894 aVisitor.mEvent->AsMouseEvent())) {
3895 case nsNumberControlFrame::eSpinButtonUp:
3896 StepNumberControlForUserEvent(1);
3897 mNumberControlSpinnerSpinsUp = true;
3898 StartNumberControlSpinnerSpin();
3899 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3900 break;
3901 case nsNumberControlFrame::eSpinButtonDown:
3902 StepNumberControlForUserEvent(-1);
3903 mNumberControlSpinnerSpinsUp = false;
3904 StartNumberControlSpinnerSpin();
3905 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3906 break;
3911 if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
3912 // We didn't handle this to step up/down. Whatever this was, be
3913 // aggressive about stopping the spin. (And don't set
3914 // nsEventStatus_eConsumeNoDefault after doing so, since that
3915 // might prevent, say, the context menu from opening.)
3916 StopNumberControlSpinnerSpin();
3919 break;
3921 #if !defined(ANDROID) && !defined(XP_MACOSX)
3922 case eWheel: {
3923 // Handle wheel events as increasing / decreasing the input element's
3924 // value when it's focused and it's type is number or range.
3925 WidgetWheelEvent* wheelEvent = aVisitor.mEvent->AsWheelEvent();
3926 if (!aVisitor.mEvent->DefaultPrevented() &&
3927 aVisitor.mEvent->IsTrusted() && IsMutable() && wheelEvent &&
3928 wheelEvent->mDeltaY != 0 &&
3929 wheelEvent->mDeltaMode != WheelEvent_Binding::DOM_DELTA_PIXEL) {
3930 if (mType == FormControlType::InputNumber) {
3931 if (nsContentUtils::IsFocusedContent(this)) {
3932 StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1 : 1);
3933 FireChangeEventIfNeeded();
3934 aVisitor.mEvent->PreventDefault();
3936 } else if (mType == FormControlType::InputRange &&
3937 nsContentUtils::IsFocusedContent(this) &&
3938 GetMinimum() < GetMaximum()) {
3939 Decimal value = GetValueAsDecimal();
3940 Decimal step = GetStep();
3941 if (step == kStepAny) {
3942 step = GetDefaultStep();
3944 MOZ_ASSERT(value.isFinite() && step.isFinite());
3945 SetValueOfRangeForUserEvent(
3946 wheelEvent->mDeltaY < 0 ? value + step : value - step);
3947 FireChangeEventIfNeeded();
3948 aVisitor.mEvent->PreventDefault();
3951 break;
3953 #endif
3954 case eMouseClick: {
3955 if (!aVisitor.mEvent->DefaultPrevented() &&
3956 aVisitor.mEvent->IsTrusted() &&
3957 aVisitor.mEvent->AsMouseEvent()->mButton ==
3958 MouseButton::ePrimary) {
3959 // TODO(emilio): Handling this should ideally not move focus.
3960 if (mType == FormControlType::InputSearch) {
3961 if (nsSearchControlFrame* searchControlFrame =
3962 do_QueryFrame(GetPrimaryFrame())) {
3963 Element* clearButton = searchControlFrame->GetAnonClearButton();
3964 if (clearButton &&
3965 aVisitor.mEvent->mOriginalTarget == clearButton) {
3966 SetUserInput(EmptyString(),
3967 *nsContentUtils::GetSystemPrincipal());
3968 // TODO(emilio): This should focus the input, but calling
3969 // SetFocus(this, FLAG_NOSCROLL) for some reason gets us into
3970 // an inconsistent state where we're focused but don't match
3971 // :focus-visible / :focus.
3974 } else if (mType == FormControlType::InputPassword) {
3975 if (nsTextControlFrame* textControlFrame =
3976 do_QueryFrame(GetPrimaryFrame())) {
3977 auto* reveal = textControlFrame->GetRevealButton();
3978 if (reveal && aVisitor.mEvent->mOriginalTarget == reveal) {
3979 SetRevealPassword(!RevealPassword());
3980 // TODO(emilio): This should focus the input, but calling
3981 // SetFocus(this, FLAG_NOSCROLL) for some reason gets us into
3982 // an inconsistent state where we're focused but don't match
3983 // :focus-visible / :focus.
3988 break;
3990 default:
3991 break;
3994 // Bug 1459231: should be in ActivationBehavior(). blocked by 1803805
3995 if (outerActivateEvent) {
3996 switch (mType) {
3997 case FormControlType::InputReset:
3998 case FormControlType::InputSubmit:
3999 case FormControlType::InputImage:
4000 if (mForm) {
4001 // Hold a strong ref while dispatching
4002 RefPtr<HTMLFormElement> form(mForm);
4003 if (mType == FormControlType::InputReset) {
4004 form->MaybeReset(this);
4005 } else {
4006 form->MaybeSubmit(this);
4008 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4010 break;
4012 default:
4013 break;
4014 } // switch
4015 if (IsButtonControl()) {
4016 HandlePopoverTargetAction();
4018 } // click or outer activate event
4020 } // if
4021 if ((aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK) &&
4022 (oldType == FormControlType::InputSubmit ||
4023 oldType == FormControlType::InputImage)) {
4024 nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mItemData));
4025 RefPtr<HTMLFormElement> form = HTMLFormElement::FromNodeOrNull(content);
4026 MOZ_ASSERT(form);
4027 // Tell the form that we are about to exit a click handler,
4028 // so the form knows not to defer subsequent submissions.
4029 // The pending ones that were created during the handler
4030 // will be flushed or forgotten.
4031 form->OnSubmitClickEnd();
4032 // tell the form to flush a possible pending submission.
4033 // the reason is that the script returned false (the event was
4034 // not ignored) so if there is a stored submission, it needs to
4035 // be submitted immediately.
4036 form->FlushPendingSubmission();
4039 if (NS_SUCCEEDED(rv) && mType == FormControlType::InputRange) {
4040 PostHandleEventForRangeThumb(aVisitor);
4043 if (!preventDefault) {
4044 MOZ_TRY(MaybeInitPickers(aVisitor));
4046 return NS_OK;
4049 void HTMLInputElement::ActivationBehavior(EventChainPostVisitor& aVisitor) {
4050 auto oldType = FormControlType(NS_CONTROL_TYPE(aVisitor.mItemFlags));
4052 if (IsDisabled() && oldType != FormControlType::InputCheckbox &&
4053 oldType != FormControlType::InputRadio) {
4054 // Behave as if defaultPrevented when the element becomes disabled by event
4055 // listeners. Checkboxes and radio buttons should still process clicks for
4056 // web compat. See:
4057 // https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
4058 return;
4061 if (mCheckedIsToggled) {
4062 // Fire input event and then change event.
4063 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
4064 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
4065 "Failed to dispatch input event");
4067 nsContentUtils::DispatchTrustedEvent<WidgetEvent>(
4068 OwnerDoc(), static_cast<Element*>(this), eFormChange, CanBubble::eYes,
4069 Cancelable::eNo);
4070 #ifdef ACCESSIBILITY
4071 // Fire an event to notify accessibility
4072 if (mType == FormControlType::InputCheckbox) {
4073 if (nsContentUtils::MayHaveFormCheckboxStateChangeListeners()) {
4074 FireEventForAccessibility(this, eFormCheckboxStateChange);
4076 } else if (nsContentUtils::MayHaveFormRadioStateChangeListeners()) {
4077 FireEventForAccessibility(this, eFormRadioStateChange);
4078 // Fire event for the previous selected radio.
4079 nsCOMPtr<nsIContent> content = do_QueryInterface(aVisitor.mItemData);
4080 if (HTMLInputElement* previous =
4081 HTMLInputElement::FromNodeOrNull(content)) {
4082 FireEventForAccessibility(previous, eFormRadioStateChange);
4085 #endif
4089 void HTMLInputElement::LegacyCanceledActivationBehavior(
4090 EventChainPostVisitor& aVisitor) {
4091 bool originalCheckedValue =
4092 !!(aVisitor.mItemFlags & NS_ORIGINAL_CHECKED_VALUE);
4093 auto oldType = FormControlType(NS_CONTROL_TYPE(aVisitor.mItemFlags));
4095 if (mCheckedIsToggled) {
4096 // if it was canceled and a radio button, then set the old
4097 // selected btn to TRUE. if it is a checkbox then set it to its
4098 // original value (legacy-canceled-activation)
4099 if (oldType == FormControlType::InputRadio) {
4100 nsCOMPtr<nsIContent> content = do_QueryInterface(aVisitor.mItemData);
4101 HTMLInputElement* selectedRadioButton =
4102 HTMLInputElement::FromNodeOrNull(content);
4103 if (selectedRadioButton) {
4104 selectedRadioButton->SetChecked(true);
4106 // If there was no checked radio button or this one is no longer a
4107 // radio button we must reset it back to false to cancel the action.
4108 // See how the web of hack grows?
4109 if (!selectedRadioButton || mType != FormControlType::InputRadio) {
4110 DoSetChecked(false, true, true);
4112 } else if (oldType == FormControlType::InputCheckbox) {
4113 bool originalIndeterminateValue =
4114 !!(aVisitor.mItemFlags & NS_ORIGINAL_INDETERMINATE_VALUE);
4115 SetIndeterminateInternal(originalIndeterminateValue, false);
4116 DoSetChecked(originalCheckedValue, true, true);
4121 enum class RadioButtonMove { Back, Forward, None };
4122 nsresult HTMLInputElement::MaybeHandleRadioButtonNavigation(
4123 EventChainPostVisitor& aVisitor, uint32_t aKeyCode) {
4124 auto move = [&] {
4125 switch (aKeyCode) {
4126 case NS_VK_UP:
4127 return RadioButtonMove::Back;
4128 case NS_VK_DOWN:
4129 return RadioButtonMove::Forward;
4130 case NS_VK_LEFT:
4131 case NS_VK_RIGHT: {
4132 const bool isRtl = GetComputedDirectionality() == eDir_RTL;
4133 return isRtl == (aKeyCode == NS_VK_LEFT) ? RadioButtonMove::Forward
4134 : RadioButtonMove::Back;
4137 return RadioButtonMove::None;
4138 }();
4139 if (move == RadioButtonMove::None) {
4140 return NS_OK;
4142 // Arrow key pressed, focus+select prev/next radio button
4143 RefPtr<HTMLInputElement> selectedRadioButton;
4144 if (auto* container = GetCurrentRadioGroupContainer()) {
4145 nsAutoString name;
4146 GetAttr(nsGkAtoms::name, name);
4147 container->GetNextRadioButton(name, move == RadioButtonMove::Back, this,
4148 getter_AddRefs(selectedRadioButton));
4150 if (!selectedRadioButton) {
4151 return NS_OK;
4153 FocusOptions options;
4154 ErrorResult error;
4155 selectedRadioButton->Focus(options, CallerType::System, error);
4156 if (error.Failed()) {
4157 return error.StealNSResult();
4159 nsresult rv = DispatchSimulatedClick(
4160 selectedRadioButton, aVisitor.mEvent->IsTrusted(), aVisitor.mPresContext);
4161 if (NS_SUCCEEDED(rv)) {
4162 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4164 return rv;
4167 void HTMLInputElement::PostHandleEventForRangeThumb(
4168 EventChainPostVisitor& aVisitor) {
4169 MOZ_ASSERT(mType == FormControlType::InputRange);
4171 if (nsEventStatus_eConsumeNoDefault == aVisitor.mEventStatus ||
4172 !(aVisitor.mEvent->mClass == eMouseEventClass ||
4173 aVisitor.mEvent->mClass == eTouchEventClass ||
4174 aVisitor.mEvent->mClass == eKeyboardEventClass)) {
4175 return;
4178 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
4179 if (!rangeFrame && mIsDraggingRange) {
4180 CancelRangeThumbDrag();
4181 return;
4184 switch (aVisitor.mEvent->mMessage) {
4185 case eMouseDown:
4186 case eTouchStart: {
4187 if (mIsDraggingRange) {
4188 break;
4190 if (PresShell::GetCapturingContent()) {
4191 break; // don't start drag if someone else is already capturing
4193 WidgetInputEvent* inputEvent = aVisitor.mEvent->AsInputEvent();
4194 if (IgnoreInputEventWithModifier(*inputEvent, true)) {
4195 break; // ignore
4197 if (aVisitor.mEvent->mMessage == eMouseDown) {
4198 if (aVisitor.mEvent->AsMouseEvent()->mButtons ==
4199 MouseButtonsFlag::ePrimaryFlag) {
4200 StartRangeThumbDrag(inputEvent);
4201 } else if (mIsDraggingRange) {
4202 CancelRangeThumbDrag();
4204 } else {
4205 if (aVisitor.mEvent->AsTouchEvent()->mTouches.Length() == 1) {
4206 StartRangeThumbDrag(inputEvent);
4207 } else if (mIsDraggingRange) {
4208 CancelRangeThumbDrag();
4211 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4212 } break;
4214 case eMouseMove:
4215 case eTouchMove:
4216 if (!mIsDraggingRange) {
4217 break;
4219 if (PresShell::GetCapturingContent() != this) {
4220 // Someone else grabbed capture.
4221 CancelRangeThumbDrag();
4222 break;
4224 SetValueOfRangeForUserEvent(
4225 rangeFrame->GetValueAtEventPoint(aVisitor.mEvent->AsInputEvent()),
4226 SnapToTickMarks::Yes);
4227 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4228 break;
4230 case eMouseUp:
4231 case eTouchEnd:
4232 if (!mIsDraggingRange) {
4233 break;
4235 // We don't check to see whether we are the capturing content here and
4236 // call CancelRangeThumbDrag() if that is the case. We just finish off
4237 // the drag and set our final value (unless someone has called
4238 // preventDefault() and prevents us getting here).
4239 FinishRangeThumbDrag(aVisitor.mEvent->AsInputEvent());
4240 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4241 break;
4243 case eKeyPress:
4244 if (mIsDraggingRange &&
4245 aVisitor.mEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) {
4246 CancelRangeThumbDrag();
4248 break;
4250 case eTouchCancel:
4251 if (mIsDraggingRange) {
4252 CancelRangeThumbDrag();
4254 break;
4256 default:
4257 break;
4261 void HTMLInputElement::MaybeLoadImage() {
4262 // Our base URI may have changed; claim that our URI changed, and the
4263 // nsImageLoadingContent will decide whether a new image load is warranted.
4264 nsAutoString uri;
4265 if (mType == FormControlType::InputImage && GetAttr(nsGkAtoms::src, uri) &&
4266 (NS_FAILED(LoadImage(uri, false, true, eImageLoadType_Normal,
4267 mSrcTriggeringPrincipal)) ||
4268 !LoadingEnabled())) {
4269 CancelImageRequests(true);
4273 nsresult HTMLInputElement::BindToTree(BindContext& aContext, nsINode& aParent) {
4274 // If we are currently bound to a disconnected subtree root, remove
4275 // ourselves from it first.
4276 if (!mForm && mType == FormControlType::InputRadio) {
4277 RemoveFromRadioGroup();
4280 nsresult rv =
4281 nsGenericHTMLFormControlElementWithState::BindToTree(aContext, aParent);
4282 NS_ENSURE_SUCCESS(rv, rv);
4284 nsImageLoadingContent::BindToTree(aContext, aParent);
4286 if (mType == FormControlType::InputImage) {
4287 // Our base URI may have changed; claim that our URI changed, and the
4288 // nsImageLoadingContent will decide whether a new image load is warranted.
4289 if (HasAttr(nsGkAtoms::src)) {
4290 // Mark channel as urgent-start before load image if the image load is
4291 // initaiated by a user interaction.
4292 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
4294 nsContentUtils::AddScriptRunner(
4295 NewRunnableMethod("dom::HTMLInputElement::MaybeLoadImage", this,
4296 &HTMLInputElement::MaybeLoadImage));
4300 // Add radio to document if we don't have a form already (if we do it's
4301 // already been added into that group)
4302 if (!mForm && mType == FormControlType::InputRadio) {
4303 AddToRadioGroup();
4306 // Set direction based on value if dir=auto
4307 if (HasDirAuto()) {
4308 SetDirectionFromValue(false);
4311 // An element can't suffer from value missing if it is not in a document.
4312 // We have to check if we suffer from that as we are now in a document.
4313 UpdateValueMissingValidityState();
4315 // If there is a disabled fieldset in the parent chain, the element is now
4316 // barred from constraint validation and can't suffer from value missing
4317 // (call done before).
4318 UpdateBarredFromConstraintValidation();
4320 // And now make sure our state is up to date
4321 UpdateValidityElementStates(true);
4323 if (CreatesDateTimeWidget() && IsInComposedDoc()) {
4324 // Construct Shadow Root so web content can be hidden in the DOM.
4325 AttachAndSetUAShadowRoot(NotifyUAWidgetSetup::Yes, DelegatesFocus::Yes);
4328 if (mType == FormControlType::InputPassword) {
4329 if (IsInComposedDoc()) {
4330 AsyncEventDispatcher* dispatcher =
4331 new AsyncEventDispatcher(this, u"DOMInputPasswordAdded"_ns,
4332 CanBubble::eYes, ChromeOnlyDispatch::eYes);
4333 dispatcher->PostDOMEvent();
4336 #ifdef EARLY_BETA_OR_EARLIER
4337 Telemetry::Accumulate(Telemetry::PWMGR_PASSWORD_INPUT_IN_FORM, !!mForm);
4338 #endif
4341 return rv;
4344 void HTMLInputElement::UnbindFromTree(bool aNullParent) {
4345 if (mType == FormControlType::InputPassword) {
4346 MaybeFireInputPasswordRemoved();
4349 // If we have a form and are unbound from it,
4350 // nsGenericHTMLFormControlElementWithState::UnbindFromTree() will unset the
4351 // form and that takes care of form's WillRemove so we just have to take care
4352 // of the case where we're removing from the document and we don't
4353 // have a form
4354 if (!mForm && mType == FormControlType::InputRadio) {
4355 RemoveFromRadioGroup();
4358 if (CreatesDateTimeWidget() && IsInComposedDoc()) {
4359 NotifyUAWidgetTeardown();
4362 nsImageLoadingContent::UnbindFromTree(aNullParent);
4363 nsGenericHTMLFormControlElementWithState::UnbindFromTree(aNullParent);
4365 // If we are contained within a disconnected subtree, attempt to add
4366 // ourselves to the subtree root's radio group.
4367 if (!mForm && mType == FormControlType::InputRadio) {
4368 AddToRadioGroup();
4371 // GetCurrentDoc is returning nullptr so we can update the value
4372 // missing validity state to reflect we are no longer into a doc.
4373 UpdateValueMissingValidityState();
4374 // We might be no longer disabled because of parent chain changed.
4375 UpdateBarredFromConstraintValidation();
4376 // And now make sure our state is up to date
4377 UpdateValidityElementStates(false);
4381 * @param aType InputElementTypes
4382 * @return true, iff SetRangeText applies to aType as specified at
4383 * https://html.spec.whatwg.org/#concept-input-apply.
4385 static bool SetRangeTextApplies(FormControlType aType) {
4386 return aType == FormControlType::InputText ||
4387 aType == FormControlType::InputSearch ||
4388 aType == FormControlType::InputUrl ||
4389 aType == FormControlType::InputTel ||
4390 aType == FormControlType::InputPassword;
4393 void HTMLInputElement::HandleTypeChange(FormControlType aNewType,
4394 bool aNotify) {
4395 FormControlType oldType = mType;
4396 MOZ_ASSERT(oldType != aNewType);
4398 mHasBeenTypePassword =
4399 mHasBeenTypePassword || aNewType == FormControlType::InputPassword;
4401 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
4402 // Input element can represent very different kinds of UIs, and we may
4403 // need to flush styling even when focusing the already focused input
4404 // element.
4405 fm->NeedsFlushBeforeEventHandling(this);
4408 if (oldType == FormControlType::InputPassword &&
4409 State().HasState(ElementState::REVEALED)) {
4410 // Modify the state directly to avoid dispatching events.
4411 RemoveStates(ElementState::REVEALED, aNotify);
4414 if (aNewType == FormControlType::InputFile ||
4415 oldType == FormControlType::InputFile) {
4416 if (aNewType == FormControlType::InputFile) {
4417 mFileData.reset(new FileData());
4418 } else {
4419 mFileData->Unlink();
4420 mFileData = nullptr;
4424 if (oldType == FormControlType::InputRange && mIsDraggingRange) {
4425 CancelRangeThumbDrag(false);
4428 const ValueModeType oldValueMode = GetValueMode();
4429 nsAutoString oldValue;
4430 if (oldValueMode == VALUE_MODE_VALUE) {
4431 // Doesn't matter what caller type we pass here, since we know we're not a
4432 // file input anyway.
4433 GetValue(oldValue, CallerType::NonSystem);
4436 TextControlState::SelectionProperties sp;
4438 if (GetEditorState()) {
4439 mInputData.mState->SyncUpSelectionPropertiesBeforeDestruction();
4440 sp = mInputData.mState->GetSelectionProperties();
4443 // We already have a copy of the value, lets free it and changes the type.
4444 FreeData();
4445 mType = aNewType;
4446 void* memory = mInputTypeMem;
4447 mInputType = InputType::Create(this, mType, memory);
4449 if (IsSingleLineTextControl()) {
4450 mInputData.mState = TextControlState::Construct(this);
4451 if (!sp.IsDefault()) {
4452 mInputData.mState->SetSelectionProperties(sp);
4456 // Whether placeholder applies might have changed.
4457 UpdatePlaceholderShownState();
4458 // Whether readonly applies might have changed.
4459 UpdateReadOnlyState(aNotify);
4460 UpdateCheckedState(aNotify);
4461 UpdateIndeterminateState(aNotify);
4462 const bool isDefault = IsRadioOrCheckbox()
4463 ? DefaultChecked()
4464 : (mForm && mForm->IsDefaultSubmitElement(this));
4465 SetStates(ElementState::DEFAULT, isDefault, aNotify);
4467 // https://html.spec.whatwg.org/#input-type-change
4468 switch (GetValueMode()) {
4469 case VALUE_MODE_DEFAULT:
4470 case VALUE_MODE_DEFAULT_ON:
4471 // 1. If the previous state of the element's type attribute put the value
4472 // IDL attribute in the value mode, and the element's value is not the
4473 // empty string, and the new state of the element's type attribute puts
4474 // the value IDL attribute in either the default mode or the default/on
4475 // mode, then set the element's value content attribute to the
4476 // element's value.
4477 if (oldValueMode == VALUE_MODE_VALUE && !oldValue.IsEmpty()) {
4478 SetAttr(kNameSpaceID_None, nsGkAtoms::value, oldValue, true);
4480 break;
4481 case VALUE_MODE_VALUE: {
4482 ValueSetterOptions options{ValueSetterOption::ByInternalAPI};
4483 if (!SetRangeTextApplies(oldType) && SetRangeTextApplies(mType)) {
4484 options +=
4485 ValueSetterOption::MoveCursorToBeginSetSelectionDirectionForward;
4487 if (oldValueMode != VALUE_MODE_VALUE) {
4488 // 2. Otherwise, if the previous state of the element's type attribute
4489 // put the value IDL attribute in any mode other than the value
4490 // mode, and the new state of the element's type attribute puts the
4491 // value IDL attribute in the value mode, then set the value of the
4492 // element to the value of the value content attribute, if there is
4493 // one, or the empty string otherwise, and then set the control's
4494 // dirty value flag to false.
4495 nsAutoString value;
4496 GetAttr(nsGkAtoms::value, value);
4497 SetValueInternal(value, options);
4498 SetValueChanged(false);
4499 } else if (mValueChanged) {
4500 // We're both in the "value" mode state, we need to make no change per
4501 // spec, but due to how we store the value internally we need to call
4502 // SetValueInternal, if our value had changed at all.
4503 // TODO: What should we do if SetValueInternal fails? (The allocation
4504 // may potentially be big, but most likely we've failed to allocate
4505 // before the type change.)
4506 SetValueInternal(oldValue, options);
4507 } else {
4508 // The value dirty flag is not set, so our value is based on our default
4509 // value. But our default value might be dependent on the type. Make
4510 // sure to set it so that state is consistent.
4511 SetDefaultValueAsValue();
4513 break;
4515 case VALUE_MODE_FILENAME:
4516 default:
4517 // 3. Otherwise, if the previous state of the element's type attribute
4518 // put the value IDL attribute in any mode other than the filename
4519 // mode, and the new state of the element's type attribute puts the
4520 // value IDL attribute in the filename mode, then set the value of the
4521 // element to the empty string.
4523 // Setting the attribute to the empty string is basically calling
4524 // ClearFiles, but there can't be any files.
4525 break;
4528 // Updating mFocusedValue in consequence:
4529 // If the new type fires a change event on blur, but the previous type
4530 // doesn't, we should set mFocusedValue to the current value.
4531 // Otherwise, if the new type doesn't fire a change event on blur, but the
4532 // previous type does, we should clear out mFocusedValue.
4533 if (MayFireChangeOnBlur(mType) && !MayFireChangeOnBlur(oldType)) {
4534 GetValue(mFocusedValue, CallerType::System);
4535 } else if (!IsSingleLineTextControl(false, mType) &&
4536 IsSingleLineTextControl(false, oldType)) {
4537 mFocusedValue.Truncate();
4540 // Update or clear our required states since we may have changed from a
4541 // required input type to a non-required input type or viceversa.
4542 if (DoesRequiredApply()) {
4543 const bool isRequired = HasAttr(nsGkAtoms::required);
4544 UpdateRequiredState(isRequired, aNotify);
4545 } else {
4546 RemoveStates(ElementState::REQUIRED_STATES, aNotify);
4549 UpdateHasRange(aNotify);
4551 // Update validity states, but not element state. We'll update
4552 // element state later, as part of this attribute change.
4553 UpdateAllValidityStatesButNotElementState();
4555 UpdateApzAwareFlag();
4557 UpdateBarredFromConstraintValidation();
4559 // Changing type may affect directionality because of the special-case for
4560 // <input type=tel>, as specified in
4561 // https://html.spec.whatwg.org/multipage/dom.html#the-directionality
4562 if (!HasDirAuto() && (oldType == FormControlType::InputTel ||
4563 mType == FormControlType::InputTel)) {
4564 RecomputeDirectionality(this, aNotify);
4567 if (oldType == FormControlType::InputImage ||
4568 mType == FormControlType::InputImage) {
4569 if (oldType == FormControlType::InputImage) {
4570 // We're no longer an image input. Cancel our image requests, if we have
4571 // any.
4572 CancelImageRequests(aNotify);
4573 RemoveStates(ElementState::BROKEN, aNotify);
4574 } else {
4575 // We just got switched to be an image input; we should see whether we
4576 // have an image to load;
4577 bool hasSrc = false;
4578 if (aNotify) {
4579 nsAutoString src;
4580 if ((hasSrc = GetAttr(nsGkAtoms::src, src))) {
4581 // Mark channel as urgent-start before load image if the image load is
4582 // initiated by a user interaction.
4583 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
4585 LoadImage(src, false, aNotify, eImageLoadType_Normal,
4586 mSrcTriggeringPrincipal);
4588 } else {
4589 hasSrc = HasAttr(nsGkAtoms::src);
4591 if (!hasSrc) {
4592 AddStates(ElementState::BROKEN, aNotify);
4595 // We should update our mapped attribute mapping function.
4596 if (mAttrs.HasAttrs() && !mAttrs.IsPendingMappedAttributeEvaluation()) {
4597 mAttrs.InfallibleMarkAsPendingPresAttributeEvaluation();
4598 if (auto* doc = GetComposedDoc()) {
4599 doc->ScheduleForPresAttrEvaluation(this);
4604 if (mType == FormControlType::InputPassword && IsInComposedDoc()) {
4605 AsyncEventDispatcher* dispatcher =
4606 new AsyncEventDispatcher(this, u"DOMInputPasswordAdded"_ns,
4607 CanBubble::eYes, ChromeOnlyDispatch::eYes);
4608 dispatcher->PostDOMEvent();
4611 if (IsInComposedDoc()) {
4612 if (CreatesDateTimeWidget(oldType)) {
4613 if (!CreatesDateTimeWidget()) {
4614 // Switch away from date/time type.
4615 NotifyUAWidgetTeardown();
4616 } else {
4617 // Switch between date and time.
4618 NotifyUAWidgetSetupOrChange();
4620 } else if (CreatesDateTimeWidget()) {
4621 // Switch to date/time type.
4622 AttachAndSetUAShadowRoot(NotifyUAWidgetSetup::Yes, DelegatesFocus::Yes);
4624 // If we're becoming a text control and have focus, make sure to show focus
4625 // rings.
4626 if (State().HasState(ElementState::FOCUS) && IsSingleLineTextControl() &&
4627 !IsSingleLineTextControl(/* aExcludePassword = */ false, oldType)) {
4628 AddStates(ElementState::FOCUSRING);
4633 void HTMLInputElement::MaybeSnapToTickMark(Decimal& aValue) {
4634 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
4635 if (!rangeFrame) {
4636 return;
4638 auto tickMark = rangeFrame->NearestTickMark(aValue);
4639 if (tickMark.isNaN()) {
4640 return;
4642 auto rangeFrameSize = CSSPixel::FromAppUnits(rangeFrame->GetSize());
4643 CSSCoord rangeTrackLength;
4644 if (rangeFrame->IsHorizontal()) {
4645 rangeTrackLength = rangeFrameSize.width;
4646 } else {
4647 rangeTrackLength = rangeFrameSize.height;
4649 auto stepBase = GetStepBase();
4650 auto distanceToTickMark =
4651 rangeTrackLength * float(rangeFrame->GetDoubleAsFractionOfRange(
4652 stepBase + (tickMark - aValue).abs()));
4653 const CSSCoord magnetEffectRange(
4654 StaticPrefs::dom_range_element_magnet_effect_threshold());
4655 if (distanceToTickMark <= magnetEffectRange) {
4656 aValue = tickMark;
4660 void HTMLInputElement::SanitizeValue(nsAString& aValue,
4661 SanitizationKind aKind) {
4662 NS_ASSERTION(mDoneCreating, "The element creation should be finished!");
4664 switch (mType) {
4665 case FormControlType::InputText:
4666 case FormControlType::InputSearch:
4667 case FormControlType::InputTel:
4668 case FormControlType::InputPassword: {
4669 aValue.StripCRLF();
4670 } break;
4671 case FormControlType::InputEmail: {
4672 aValue.StripCRLF();
4673 aValue = nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
4674 aValue);
4676 if (Multiple() && !aValue.IsEmpty()) {
4677 nsAutoString oldValue(aValue);
4678 HTMLSplitOnSpacesTokenizer tokenizer(oldValue, ',');
4679 aValue.Truncate(0);
4680 aValue.Append(tokenizer.nextToken());
4681 while (tokenizer.hasMoreTokens() ||
4682 tokenizer.separatorAfterCurrentToken()) {
4683 aValue.Append(',');
4684 aValue.Append(tokenizer.nextToken());
4687 } break;
4688 case FormControlType::InputUrl: {
4689 aValue.StripCRLF();
4691 aValue = nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
4692 aValue);
4693 } break;
4694 case FormControlType::InputNumber: {
4695 if (aKind == SanitizationKind::Other && !aValue.IsEmpty() &&
4696 (aValue.First() == '+' || aValue.Last() == '.')) {
4697 // A value with a leading plus or trailing dot should fail to parse.
4698 // However, the localized parser accepts this, and when we convert it
4699 // back to a Decimal, it disappears. So, we need to check first.
4700 aValue.Truncate();
4701 return;
4704 InputType::StringToNumberResult result =
4705 mInputType->ConvertStringToNumber(aValue);
4706 if (!result.mResult.isFinite()) {
4707 aValue.Truncate();
4708 return;
4711 if (aKind == SanitizationKind::ForValueGetter) {
4712 // If the default non-localized algorithm parses the value, then we're
4713 // done, don't un-localize it, to avoid precision loss, and to preserve
4714 // scientific notation as well for example.
4715 if (!result.mLocalized) {
4716 return;
4718 // For the <input type=number> value getter, we return the unlocalized
4719 // value if it doesn't parse as StringToDecimal, for compat with other
4720 // browsers.
4721 char buf[32];
4722 DebugOnly<bool> ok = result.mResult.toString(buf, ArrayLength(buf));
4723 aValue.AssignASCII(buf);
4724 MOZ_ASSERT(ok, "buf not big enough");
4725 } else if (aKind == SanitizationKind::ForDisplay) {
4726 // If this SanitizeValue call is for display, we localize as needed, but
4727 // if both the localized and unlocalized version parse with the generic
4728 // parser, we just use the unlocalized one, to preserve the input as
4729 // much as possible.
4731 // FIXME(emilio, bug 1622808): Localization should ideally be more
4732 // input-preserving.
4733 nsString localizedValue;
4734 mInputType->ConvertNumberToString(result.mResult, localizedValue);
4735 if (StringToDecimal(localizedValue).isFinite()) {
4736 return;
4738 aValue = std::move(localizedValue);
4739 } else {
4740 // Leave aValue untouched.
4742 } break;
4743 case FormControlType::InputRange: {
4744 Decimal minimum = GetMinimum();
4745 Decimal maximum = GetMaximum();
4746 MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(),
4747 "type=range should have a default maximum/minimum");
4749 // We use this to avoid modifying the string unnecessarily, since that
4750 // may introduce rounding. This is set to true only if the value we
4751 // parse out from aValue needs to be sanitized.
4752 bool needSanitization = false;
4754 Decimal value = mInputType->ConvertStringToNumber(aValue).mResult;
4755 if (!value.isFinite()) {
4756 needSanitization = true;
4757 // Set value to midway between minimum and maximum.
4758 value = maximum <= minimum ? minimum
4759 : minimum + (maximum - minimum) / Decimal(2);
4760 } else if (value < minimum || maximum < minimum) {
4761 needSanitization = true;
4762 value = minimum;
4763 } else if (value > maximum) {
4764 needSanitization = true;
4765 value = maximum;
4768 Decimal step = GetStep();
4769 if (step != kStepAny) {
4770 Decimal stepBase = GetStepBase();
4771 // There could be rounding issues below when dealing with fractional
4772 // numbers, but let's ignore that until ECMAScript supplies us with a
4773 // decimal number type.
4774 Decimal deltaToStep = NS_floorModulo(value - stepBase, step);
4775 if (deltaToStep != Decimal(0)) {
4776 // "suffering from a step mismatch"
4777 // Round the element's value to the nearest number for which the
4778 // element would not suffer from a step mismatch, and which is
4779 // greater than or equal to the minimum, and, if the maximum is not
4780 // less than the minimum, which is less than or equal to the
4781 // maximum, if there is a number that matches these constraints:
4782 MOZ_ASSERT(deltaToStep > Decimal(0),
4783 "stepBelow/stepAbove will be wrong");
4784 Decimal stepBelow = value - deltaToStep;
4785 Decimal stepAbove = value - deltaToStep + step;
4786 Decimal halfStep = step / Decimal(2);
4787 bool stepAboveIsClosest = (stepAbove - value) <= halfStep;
4788 bool stepAboveInRange = stepAbove >= minimum && stepAbove <= maximum;
4789 bool stepBelowInRange = stepBelow >= minimum && stepBelow <= maximum;
4791 if ((stepAboveIsClosest || !stepBelowInRange) && stepAboveInRange) {
4792 needSanitization = true;
4793 value = stepAbove;
4794 } else if ((!stepAboveIsClosest || !stepAboveInRange) &&
4795 stepBelowInRange) {
4796 needSanitization = true;
4797 value = stepBelow;
4802 if (needSanitization) {
4803 char buf[32];
4804 DebugOnly<bool> ok = value.toString(buf, ArrayLength(buf));
4805 aValue.AssignASCII(buf);
4806 MOZ_ASSERT(ok, "buf not big enough");
4808 } break;
4809 case FormControlType::InputDate: {
4810 if (!aValue.IsEmpty() && !IsValidDate(aValue)) {
4811 aValue.Truncate();
4813 } break;
4814 case FormControlType::InputTime: {
4815 if (!aValue.IsEmpty() && !IsValidTime(aValue)) {
4816 aValue.Truncate();
4818 } break;
4819 case FormControlType::InputMonth: {
4820 if (!aValue.IsEmpty() && !IsValidMonth(aValue)) {
4821 aValue.Truncate();
4823 } break;
4824 case FormControlType::InputWeek: {
4825 if (!aValue.IsEmpty() && !IsValidWeek(aValue)) {
4826 aValue.Truncate();
4828 } break;
4829 case FormControlType::InputDatetimeLocal: {
4830 if (!aValue.IsEmpty() && !IsValidDateTimeLocal(aValue)) {
4831 aValue.Truncate();
4832 } else {
4833 NormalizeDateTimeLocal(aValue);
4835 } break;
4836 case FormControlType::InputColor: {
4837 if (IsValidSimpleColor(aValue)) {
4838 ToLowerCase(aValue);
4839 } else {
4840 // Set default (black) color, if aValue wasn't parsed correctly.
4841 aValue.AssignLiteral("#000000");
4843 } break;
4844 default:
4845 break;
4849 Maybe<nscolor> HTMLInputElement::ParseSimpleColor(const nsAString& aColor) {
4850 // Input color string should be 7 length (i.e. a string representing a valid
4851 // simple color)
4852 if (aColor.Length() != 7 || aColor.First() != '#') {
4853 return {};
4856 const nsAString& withoutHash = StringTail(aColor, 6);
4857 nscolor color;
4858 if (!NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) {
4859 return {};
4862 return Some(color);
4865 bool HTMLInputElement::IsValidSimpleColor(const nsAString& aValue) const {
4866 if (aValue.Length() != 7 || aValue.First() != '#') {
4867 return false;
4870 for (int i = 1; i < 7; ++i) {
4871 if (!IsAsciiDigit(aValue[i]) && !(aValue[i] >= 'a' && aValue[i] <= 'f') &&
4872 !(aValue[i] >= 'A' && aValue[i] <= 'F')) {
4873 return false;
4876 return true;
4879 bool HTMLInputElement::IsLeapYear(uint32_t aYear) const {
4880 if ((aYear % 4 == 0 && aYear % 100 != 0) || (aYear % 400 == 0)) {
4881 return true;
4883 return false;
4886 uint32_t HTMLInputElement::DayOfWeek(uint32_t aYear, uint32_t aMonth,
4887 uint32_t aDay, bool isoWeek) const {
4888 MOZ_ASSERT(1 <= aMonth && aMonth <= 12, "month is in 1..12");
4889 MOZ_ASSERT(1 <= aDay && aDay <= 31, "day is in 1..31");
4891 // Tomohiko Sakamoto algorithm.
4892 int monthTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
4893 aYear -= aMonth < 3;
4895 uint32_t day = (aYear + aYear / 4 - aYear / 100 + aYear / 400 +
4896 monthTable[aMonth - 1] + aDay) %
4899 if (isoWeek) {
4900 return ((day + 6) % 7) + 1;
4903 return day;
4906 uint32_t HTMLInputElement::MaximumWeekInYear(uint32_t aYear) const {
4907 int day = DayOfWeek(aYear, 1, 1, true); // January 1.
4908 // A year starting on Thursday or a leap year starting on Wednesday has 53
4909 // weeks. All other years have 52 weeks.
4910 return day == 4 || (day == 3 && IsLeapYear(aYear)) ? kMaximumWeekInYear
4911 : kMaximumWeekInYear - 1;
4914 bool HTMLInputElement::IsValidWeek(const nsAString& aValue) const {
4915 uint32_t year, week;
4916 return ParseWeek(aValue, &year, &week);
4919 bool HTMLInputElement::IsValidMonth(const nsAString& aValue) const {
4920 uint32_t year, month;
4921 return ParseMonth(aValue, &year, &month);
4924 bool HTMLInputElement::IsValidDate(const nsAString& aValue) const {
4925 uint32_t year, month, day;
4926 return ParseDate(aValue, &year, &month, &day);
4929 bool HTMLInputElement::IsValidDateTimeLocal(const nsAString& aValue) const {
4930 uint32_t year, month, day, time;
4931 return ParseDateTimeLocal(aValue, &year, &month, &day, &time);
4934 bool HTMLInputElement::ParseYear(const nsAString& aValue,
4935 uint32_t* aYear) const {
4936 if (aValue.Length() < 4) {
4937 return false;
4940 return DigitSubStringToNumber(aValue, 0, aValue.Length(), aYear) &&
4941 *aYear > 0;
4944 bool HTMLInputElement::ParseMonth(const nsAString& aValue, uint32_t* aYear,
4945 uint32_t* aMonth) const {
4946 // Parse the year, month values out a string formatted as 'yyyy-mm'.
4947 if (aValue.Length() < 7) {
4948 return false;
4951 uint32_t endOfYearOffset = aValue.Length() - 3;
4952 if (aValue[endOfYearOffset] != '-') {
4953 return false;
4956 const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
4957 if (!ParseYear(yearStr, aYear)) {
4958 return false;
4961 return DigitSubStringToNumber(aValue, endOfYearOffset + 1, 2, aMonth) &&
4962 *aMonth > 0 && *aMonth <= 12;
4965 bool HTMLInputElement::ParseWeek(const nsAString& aValue, uint32_t* aYear,
4966 uint32_t* aWeek) const {
4967 // Parse the year, month values out a string formatted as 'yyyy-Www'.
4968 if (aValue.Length() < 8) {
4969 return false;
4972 uint32_t endOfYearOffset = aValue.Length() - 4;
4973 if (aValue[endOfYearOffset] != '-') {
4974 return false;
4977 if (aValue[endOfYearOffset + 1] != 'W') {
4978 return false;
4981 const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
4982 if (!ParseYear(yearStr, aYear)) {
4983 return false;
4986 return DigitSubStringToNumber(aValue, endOfYearOffset + 2, 2, aWeek) &&
4987 *aWeek > 0 && *aWeek <= MaximumWeekInYear(*aYear);
4990 bool HTMLInputElement::ParseDate(const nsAString& aValue, uint32_t* aYear,
4991 uint32_t* aMonth, uint32_t* aDay) const {
4993 * Parse the year, month, day values out a date string formatted as
4994 * yyyy-mm-dd. -The year must be 4 or more digits long, and year > 0 -The
4995 * month must be exactly 2 digits long, and 01 <= month <= 12 -The day must be
4996 * exactly 2 digit long, and 01 <= day <= maxday Where maxday is the number of
4997 * days in the month 'month' and year 'year'
4999 if (aValue.Length() < 10) {
5000 return false;
5003 uint32_t endOfMonthOffset = aValue.Length() - 3;
5004 if (aValue[endOfMonthOffset] != '-') {
5005 return false;
5008 const nsAString& yearMonthStr = Substring(aValue, 0, endOfMonthOffset);
5009 if (!ParseMonth(yearMonthStr, aYear, aMonth)) {
5010 return false;
5013 return DigitSubStringToNumber(aValue, endOfMonthOffset + 1, 2, aDay) &&
5014 *aDay > 0 && *aDay <= NumberOfDaysInMonth(*aMonth, *aYear);
5017 bool HTMLInputElement::ParseDateTimeLocal(const nsAString& aValue,
5018 uint32_t* aYear, uint32_t* aMonth,
5019 uint32_t* aDay,
5020 uint32_t* aTime) const {
5021 // Parse the year, month, day and time values out a string formatted as
5022 // 'yyyy-mm-ddThh:mm[:ss.s] or 'yyyy-mm-dd hh:mm[:ss.s]', where fractions of
5023 // seconds can be 1 to 3 digits.
5024 // The minimum length allowed is 16, which is of the form 'yyyy-mm-ddThh:mm'
5025 // or 'yyyy-mm-dd hh:mm'.
5026 if (aValue.Length() < 16) {
5027 return false;
5030 int32_t sepIndex = aValue.FindChar('T');
5031 if (sepIndex == -1) {
5032 sepIndex = aValue.FindChar(' ');
5034 if (sepIndex == -1) {
5035 return false;
5039 const nsAString& dateStr = Substring(aValue, 0, sepIndex);
5040 if (!ParseDate(dateStr, aYear, aMonth, aDay)) {
5041 return false;
5044 const nsAString& timeStr =
5045 Substring(aValue, sepIndex + 1, aValue.Length() - sepIndex + 1);
5046 if (!ParseTime(timeStr, aTime)) {
5047 return false;
5050 return true;
5053 void HTMLInputElement::NormalizeDateTimeLocal(nsAString& aValue) const {
5054 if (aValue.IsEmpty()) {
5055 return;
5058 // Use 'T' as the separator between date string and time string.
5059 int32_t sepIndex = aValue.FindChar(' ');
5060 if (sepIndex != -1) {
5061 aValue.ReplaceLiteral(sepIndex, 1, u"T");
5062 } else {
5063 sepIndex = aValue.FindChar('T');
5066 // Time expressed as the shortest possible string, which is hh:mm.
5067 if ((aValue.Length() - sepIndex) == 6) {
5068 return;
5071 // Fractions of seconds part is optional, ommit it if it's 0.
5072 if ((aValue.Length() - sepIndex) > 9) {
5073 const uint32_t millisecSepIndex = sepIndex + 9;
5074 uint32_t milliseconds;
5075 if (!DigitSubStringToNumber(aValue, millisecSepIndex + 1,
5076 aValue.Length() - (millisecSepIndex + 1),
5077 &milliseconds)) {
5078 return;
5081 if (milliseconds != 0) {
5082 return;
5085 aValue.Cut(millisecSepIndex, aValue.Length() - millisecSepIndex);
5088 // Seconds part is optional, ommit it if it's 0.
5089 const uint32_t secondSepIndex = sepIndex + 6;
5090 uint32_t seconds;
5091 if (!DigitSubStringToNumber(aValue, secondSepIndex + 1,
5092 aValue.Length() - (secondSepIndex + 1),
5093 &seconds)) {
5094 return;
5097 if (seconds != 0) {
5098 return;
5101 aValue.Cut(secondSepIndex, aValue.Length() - secondSepIndex);
5104 double HTMLInputElement::DaysSinceEpochFromWeek(uint32_t aYear,
5105 uint32_t aWeek) const {
5106 double days = JS::DayFromYear(aYear) + (aWeek - 1) * 7;
5107 uint32_t dayOneIsoWeekday = DayOfWeek(aYear, 1, 1, true);
5109 // If day one of that year is on/before Thursday, we should subtract the
5110 // days that belong to last year in our first week, otherwise, our first
5111 // days belong to last year's last week, and we should add those days
5112 // back.
5113 if (dayOneIsoWeekday <= 4) {
5114 days -= (dayOneIsoWeekday - 1);
5115 } else {
5116 days += (7 - dayOneIsoWeekday + 1);
5119 return days;
5122 uint32_t HTMLInputElement::NumberOfDaysInMonth(uint32_t aMonth,
5123 uint32_t aYear) const {
5125 * Returns the number of days in a month.
5126 * Months that are |longMonths| always have 31 days.
5127 * Months that are not |longMonths| have 30 days except February (month 2).
5128 * February has 29 days during leap years which are years that are divisible
5129 * by 400. or divisible by 100 and 4. February has 28 days otherwise.
5132 static const bool longMonths[] = {true, false, true, false, true, false,
5133 true, true, false, true, false, true};
5134 MOZ_ASSERT(aMonth <= 12 && aMonth > 0);
5136 if (longMonths[aMonth - 1]) {
5137 return 31;
5140 if (aMonth != 2) {
5141 return 30;
5144 return IsLeapYear(aYear) ? 29 : 28;
5147 /* static */
5148 bool HTMLInputElement::DigitSubStringToNumber(const nsAString& aStr,
5149 uint32_t aStart, uint32_t aLen,
5150 uint32_t* aRetVal) {
5151 MOZ_ASSERT(aStr.Length() > (aStart + aLen - 1));
5153 for (uint32_t offset = 0; offset < aLen; ++offset) {
5154 if (!IsAsciiDigit(aStr[aStart + offset])) {
5155 return false;
5159 nsresult ec;
5160 *aRetVal = static_cast<uint32_t>(
5161 PromiseFlatString(Substring(aStr, aStart, aLen)).ToInteger(&ec));
5163 return NS_SUCCEEDED(ec);
5166 bool HTMLInputElement::IsValidTime(const nsAString& aValue) const {
5167 return ParseTime(aValue, nullptr);
5170 /* static */
5171 bool HTMLInputElement::ParseTime(const nsAString& aValue, uint32_t* aResult) {
5172 /* The string must have the following parts:
5173 * - HOURS: two digits, value being in [0, 23];
5174 * - Colon (:);
5175 * - MINUTES: two digits, value being in [0, 59];
5176 * - Optional:
5177 * - Colon (:);
5178 * - SECONDS: two digits, value being in [0, 59];
5179 * - Optional:
5180 * - DOT (.);
5181 * - FRACTIONAL SECONDS: one to three digits, no value range.
5184 // The following format is the shorter one allowed: "HH:MM".
5185 if (aValue.Length() < 5) {
5186 return false;
5189 uint32_t hours;
5190 if (!DigitSubStringToNumber(aValue, 0, 2, &hours) || hours > 23) {
5191 return false;
5194 // Hours/minutes separator.
5195 if (aValue[2] != ':') {
5196 return false;
5199 uint32_t minutes;
5200 if (!DigitSubStringToNumber(aValue, 3, 2, &minutes) || minutes > 59) {
5201 return false;
5204 if (aValue.Length() == 5) {
5205 if (aResult) {
5206 *aResult = ((hours * 60) + minutes) * 60000;
5208 return true;
5211 // The following format is the next shorter one: "HH:MM:SS".
5212 if (aValue.Length() < 8 || aValue[5] != ':') {
5213 return false;
5216 uint32_t seconds;
5217 if (!DigitSubStringToNumber(aValue, 6, 2, &seconds) || seconds > 59) {
5218 return false;
5221 if (aValue.Length() == 8) {
5222 if (aResult) {
5223 *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000;
5225 return true;
5228 // The string must follow this format now: "HH:MM:SS.{s,ss,sss}".
5229 // There can be 1 to 3 digits for the fractions of seconds.
5230 if (aValue.Length() == 9 || aValue.Length() > 12 || aValue[8] != '.') {
5231 return false;
5234 uint32_t fractionsSeconds;
5235 if (!DigitSubStringToNumber(aValue, 9, aValue.Length() - 9,
5236 &fractionsSeconds)) {
5237 return false;
5240 if (aResult) {
5241 *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000 +
5242 // NOTE: there is 10.0 instead of 10 and static_cast<int> because
5243 // some old [and stupid] compilers can't just do the right thing.
5244 fractionsSeconds *
5245 pow(10.0, static_cast<int>(3 - (aValue.Length() - 9)));
5248 return true;
5251 /* static */
5252 bool HTMLInputElement::IsDateTimeTypeSupported(
5253 FormControlType aDateTimeInputType) {
5254 switch (aDateTimeInputType) {
5255 case FormControlType::InputDate:
5256 case FormControlType::InputTime:
5257 case FormControlType::InputDatetimeLocal:
5258 return true;
5259 case FormControlType::InputMonth:
5260 case FormControlType::InputWeek:
5261 return StaticPrefs::dom_forms_datetime_others();
5262 default:
5263 return false;
5267 void HTMLInputElement::GetLastInteractiveValue(nsAString& aValue) {
5268 if (mLastValueChangeWasInteractive) {
5269 return GetValue(aValue, CallerType::System);
5271 if (TextControlState* state = GetEditorState()) {
5272 return aValue.Assign(
5273 state->LastInteractiveValueIfLastChangeWasNonInteractive());
5275 aValue.Truncate();
5278 bool HTMLInputElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
5279 const nsAString& aValue,
5280 nsIPrincipal* aMaybeScriptedPrincipal,
5281 nsAttrValue& aResult) {
5282 // We can't make these static_asserts because kInputDefaultType and
5283 // kInputTypeTable aren't constexpr.
5284 MOZ_ASSERT(
5285 FormControlType(kInputDefaultType->value) == FormControlType::InputText,
5286 "Someone forgot to update kInputDefaultType when adding a new "
5287 "input type.");
5288 MOZ_ASSERT(kInputTypeTable[ArrayLength(kInputTypeTable) - 1].tag == nullptr,
5289 "Last entry in the table must be the nullptr guard");
5290 MOZ_ASSERT(FormControlType(
5291 kInputTypeTable[ArrayLength(kInputTypeTable) - 2].value) ==
5292 FormControlType::InputText,
5293 "Next to last entry in the table must be the \"text\" entry");
5295 if (aNamespaceID == kNameSpaceID_None) {
5296 if (aAttribute == nsGkAtoms::type) {
5297 aResult.ParseEnumValue(aValue, kInputTypeTable, false, kInputDefaultType);
5298 auto newType = FormControlType(aResult.GetEnumValue());
5299 if (IsDateTimeInputType(newType) && !IsDateTimeTypeSupported(newType)) {
5300 // There's no public way to set an nsAttrValue to an enum value, but we
5301 // can just re-parse with a table that doesn't have any types other than
5302 // "text" in it.
5303 aResult.ParseEnumValue(aValue, kInputDefaultType, false,
5304 kInputDefaultType);
5307 return true;
5309 if (aAttribute == nsGkAtoms::width) {
5310 return aResult.ParseHTMLDimension(aValue);
5312 if (aAttribute == nsGkAtoms::height) {
5313 return aResult.ParseHTMLDimension(aValue);
5315 if (aAttribute == nsGkAtoms::maxlength) {
5316 return aResult.ParseNonNegativeIntValue(aValue);
5318 if (aAttribute == nsGkAtoms::minlength) {
5319 return aResult.ParseNonNegativeIntValue(aValue);
5321 if (aAttribute == nsGkAtoms::size) {
5322 return aResult.ParsePositiveIntValue(aValue);
5324 if (aAttribute == nsGkAtoms::align) {
5325 return ParseAlignValue(aValue, aResult);
5327 if (aAttribute == nsGkAtoms::formmethod) {
5328 return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
5330 if (aAttribute == nsGkAtoms::formenctype) {
5331 return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
5333 if (aAttribute == nsGkAtoms::autocomplete) {
5334 aResult.ParseAtomArray(aValue);
5335 return true;
5337 if (aAttribute == nsGkAtoms::capture) {
5338 return aResult.ParseEnumValue(aValue, kCaptureTable, false,
5339 kCaptureDefault);
5341 if (ParseImageAttribute(aAttribute, aValue, aResult)) {
5342 // We have to call |ParseImageAttribute| unconditionally since we
5343 // don't know if we're going to have a type="image" attribute yet,
5344 // (or could have it set dynamically in the future). See bug
5345 // 214077.
5346 return true;
5350 return TextControlElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
5351 aMaybeScriptedPrincipal, aResult);
5354 void HTMLInputElement::ImageInputMapAttributesIntoRule(
5355 MappedDeclarationsBuilder& aBuilder) {
5356 nsGenericHTMLFormControlElementWithState::MapImageBorderAttributeInto(
5357 aBuilder);
5358 nsGenericHTMLFormControlElementWithState::MapImageMarginAttributeInto(
5359 aBuilder);
5360 nsGenericHTMLFormControlElementWithState::MapImageSizeAttributesInto(
5361 aBuilder, MapAspectRatio::Yes);
5362 // Images treat align as "float"
5363 nsGenericHTMLFormControlElementWithState::MapImageAlignAttributeInto(
5364 aBuilder);
5365 nsGenericHTMLFormControlElementWithState::MapCommonAttributesInto(aBuilder);
5368 nsChangeHint HTMLInputElement::GetAttributeChangeHint(const nsAtom* aAttribute,
5369 int32_t aModType) const {
5370 nsChangeHint retval =
5371 nsGenericHTMLFormControlElementWithState::GetAttributeChangeHint(
5372 aAttribute, aModType);
5374 const bool isAdditionOrRemoval =
5375 aModType == MutationEvent_Binding::ADDITION ||
5376 aModType == MutationEvent_Binding::REMOVAL;
5378 const bool reconstruct = [&] {
5379 if (aAttribute == nsGkAtoms::type) {
5380 return true;
5383 if (PlaceholderApplies() && aAttribute == nsGkAtoms::placeholder &&
5384 isAdditionOrRemoval) {
5385 // We need to re-create our placeholder text.
5386 return true;
5389 if (mType == FormControlType::InputFile &&
5390 aAttribute == nsGkAtoms::webkitdirectory) {
5391 // The presence or absence of the 'directory' attribute determines what
5392 // value we show in the file label when empty, via GetDisplayFileName.
5393 return true;
5396 if (mType == FormControlType::InputImage && isAdditionOrRemoval &&
5397 (aAttribute == nsGkAtoms::alt || aAttribute == nsGkAtoms::value)) {
5398 // We might need to rebuild our alt text. Just go ahead and
5399 // reconstruct our frame. This should be quite rare..
5400 return true;
5402 return false;
5403 }();
5405 if (reconstruct) {
5406 retval |= nsChangeHint_ReconstructFrame;
5407 } else if (aAttribute == nsGkAtoms::value) {
5408 retval |= NS_STYLE_HINT_REFLOW;
5409 } else if (aAttribute == nsGkAtoms::size && IsSingleLineTextControl(false)) {
5410 retval |= NS_STYLE_HINT_REFLOW;
5413 return retval;
5416 NS_IMETHODIMP_(bool)
5417 HTMLInputElement::IsAttributeMapped(const nsAtom* aAttribute) const {
5418 static const MappedAttributeEntry attributes[] = {
5419 {nsGkAtoms::align},
5420 {nullptr},
5423 static const MappedAttributeEntry* const map[] = {
5424 attributes,
5425 sCommonAttributeMap,
5426 sImageMarginSizeAttributeMap,
5427 sImageBorderAttributeMap,
5430 return FindAttributeDependence(aAttribute, map);
5433 nsMapRuleToAttributesFunc HTMLInputElement::GetAttributeMappingFunction()
5434 const {
5435 // GetAttributeChangeHint guarantees that changes to mType will trigger a
5436 // reframe, and we update the mapping function in our mapped attrs when our
5437 // type changes, so it's safe to condition our attribute mapping function on
5438 // mType.
5439 if (mType == FormControlType::InputImage) {
5440 return &ImageInputMapAttributesIntoRule;
5443 return &MapCommonAttributesInto;
5446 // Directory picking methods:
5448 already_AddRefed<Promise> HTMLInputElement::GetFilesAndDirectories(
5449 ErrorResult& aRv) {
5450 if (mType != FormControlType::InputFile) {
5451 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5452 return nullptr;
5455 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
5456 MOZ_ASSERT(global);
5457 if (!global) {
5458 return nullptr;
5461 RefPtr<Promise> p = Promise::Create(global, aRv);
5462 if (aRv.Failed()) {
5463 return nullptr;
5466 const nsTArray<OwningFileOrDirectory>& filesAndDirs =
5467 GetFilesOrDirectoriesInternal();
5469 Sequence<OwningFileOrDirectory> filesAndDirsSeq;
5471 if (!filesAndDirsSeq.SetLength(filesAndDirs.Length(), fallible)) {
5472 p->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
5473 return p.forget();
5476 for (uint32_t i = 0; i < filesAndDirs.Length(); ++i) {
5477 if (filesAndDirs[i].IsDirectory()) {
5478 RefPtr<Directory> directory = filesAndDirs[i].GetAsDirectory();
5480 // In future we could refactor SetFilePickerFiltersFromAccept to return a
5481 // semicolon separated list of file extensions and include that in the
5482 // filter string passed here.
5483 directory->SetContentFilters(u"filter-out-sensitive"_ns);
5484 filesAndDirsSeq[i].SetAsDirectory() = directory;
5485 } else {
5486 MOZ_ASSERT(filesAndDirs[i].IsFile());
5488 // This file was directly selected by the user, so don't filter it.
5489 filesAndDirsSeq[i].SetAsFile() = filesAndDirs[i].GetAsFile();
5493 p->MaybeResolve(filesAndDirsSeq);
5494 return p.forget();
5497 // Controllers Methods
5499 nsIControllers* HTMLInputElement::GetControllers(ErrorResult& aRv) {
5500 // XXX: what about type "file"?
5501 if (IsSingleLineTextControl(false)) {
5502 if (!mControllers) {
5503 mControllers = new nsXULControllers();
5504 if (!mControllers) {
5505 aRv.Throw(NS_ERROR_FAILURE);
5506 return nullptr;
5509 RefPtr<nsBaseCommandController> commandController =
5510 nsBaseCommandController::CreateEditorController();
5511 if (!commandController) {
5512 aRv.Throw(NS_ERROR_FAILURE);
5513 return nullptr;
5516 mControllers->AppendController(commandController);
5518 commandController = nsBaseCommandController::CreateEditingController();
5519 if (!commandController) {
5520 aRv.Throw(NS_ERROR_FAILURE);
5521 return nullptr;
5524 mControllers->AppendController(commandController);
5528 return mControllers;
5531 nsresult HTMLInputElement::GetControllers(nsIControllers** aResult) {
5532 NS_ENSURE_ARG_POINTER(aResult);
5534 ErrorResult rv;
5535 RefPtr<nsIControllers> controller = GetControllers(rv);
5536 controller.forget(aResult);
5537 return rv.StealNSResult();
5540 int32_t HTMLInputElement::InputTextLength(CallerType aCallerType) {
5541 nsAutoString val;
5542 GetValue(val, aCallerType);
5543 return val.Length();
5546 void HTMLInputElement::SetSelectionRange(uint32_t aSelectionStart,
5547 uint32_t aSelectionEnd,
5548 const Optional<nsAString>& aDirection,
5549 ErrorResult& aRv) {
5550 if (!SupportsTextSelection()) {
5551 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5552 return;
5555 TextControlState* state = GetEditorState();
5556 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5557 state->SetSelectionRange(aSelectionStart, aSelectionEnd, aDirection, aRv);
5560 void HTMLInputElement::SetRangeText(const nsAString& aReplacement,
5561 ErrorResult& aRv) {
5562 if (!SupportsTextSelection()) {
5563 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5564 return;
5567 TextControlState* state = GetEditorState();
5568 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5569 state->SetRangeText(aReplacement, aRv);
5572 void HTMLInputElement::SetRangeText(const nsAString& aReplacement,
5573 uint32_t aStart, uint32_t aEnd,
5574 SelectionMode aSelectMode,
5575 ErrorResult& aRv) {
5576 if (!SupportsTextSelection()) {
5577 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5578 return;
5581 TextControlState* state = GetEditorState();
5582 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5583 state->SetRangeText(aReplacement, aStart, aEnd, aSelectMode, aRv);
5586 void HTMLInputElement::GetValueFromSetRangeText(nsAString& aValue) {
5587 GetNonFileValueInternal(aValue);
5590 nsresult HTMLInputElement::SetValueFromSetRangeText(const nsAString& aValue) {
5591 return SetValueInternal(aValue, {ValueSetterOption::ByContentAPI,
5592 ValueSetterOption::BySetRangeTextAPI,
5593 ValueSetterOption::SetValueChanged});
5596 Nullable<uint32_t> HTMLInputElement::GetSelectionStart(ErrorResult& aRv) {
5597 if (!SupportsTextSelection()) {
5598 return Nullable<uint32_t>();
5601 uint32_t selStart = GetSelectionStartIgnoringType(aRv);
5602 if (aRv.Failed()) {
5603 return Nullable<uint32_t>();
5606 return Nullable<uint32_t>(selStart);
5609 uint32_t HTMLInputElement::GetSelectionStartIgnoringType(ErrorResult& aRv) {
5610 uint32_t selEnd, selStart;
5611 GetSelectionRange(&selStart, &selEnd, aRv);
5612 return selStart;
5615 void HTMLInputElement::SetSelectionStart(
5616 const Nullable<uint32_t>& aSelectionStart, ErrorResult& aRv) {
5617 if (!SupportsTextSelection()) {
5618 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5619 return;
5622 TextControlState* state = GetEditorState();
5623 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5624 state->SetSelectionStart(aSelectionStart, aRv);
5627 Nullable<uint32_t> HTMLInputElement::GetSelectionEnd(ErrorResult& aRv) {
5628 if (!SupportsTextSelection()) {
5629 return Nullable<uint32_t>();
5632 uint32_t selEnd = GetSelectionEndIgnoringType(aRv);
5633 if (aRv.Failed()) {
5634 return Nullable<uint32_t>();
5637 return Nullable<uint32_t>(selEnd);
5640 uint32_t HTMLInputElement::GetSelectionEndIgnoringType(ErrorResult& aRv) {
5641 uint32_t selEnd, selStart;
5642 GetSelectionRange(&selStart, &selEnd, aRv);
5643 return selEnd;
5646 void HTMLInputElement::SetSelectionEnd(const Nullable<uint32_t>& aSelectionEnd,
5647 ErrorResult& aRv) {
5648 if (!SupportsTextSelection()) {
5649 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5650 return;
5653 TextControlState* state = GetEditorState();
5654 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5655 state->SetSelectionEnd(aSelectionEnd, aRv);
5658 void HTMLInputElement::GetSelectionRange(uint32_t* aSelectionStart,
5659 uint32_t* aSelectionEnd,
5660 ErrorResult& aRv) {
5661 TextControlState* state = GetEditorState();
5662 if (!state) {
5663 // Not a text control.
5664 aRv.Throw(NS_ERROR_UNEXPECTED);
5665 return;
5668 state->GetSelectionRange(aSelectionStart, aSelectionEnd, aRv);
5671 void HTMLInputElement::GetSelectionDirection(nsAString& aDirection,
5672 ErrorResult& aRv) {
5673 if (!SupportsTextSelection()) {
5674 aDirection.SetIsVoid(true);
5675 return;
5678 TextControlState* state = GetEditorState();
5679 MOZ_ASSERT(state, "SupportsTextSelection came back true!");
5680 state->GetSelectionDirectionString(aDirection, aRv);
5683 void HTMLInputElement::SetSelectionDirection(const nsAString& aDirection,
5684 ErrorResult& aRv) {
5685 if (!SupportsTextSelection()) {
5686 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5687 return;
5690 TextControlState* state = GetEditorState();
5691 MOZ_ASSERT(state, "SupportsTextSelection came back true!");
5692 state->SetSelectionDirection(aDirection, aRv);
5695 // https://html.spec.whatwg.org/multipage/input.html#dom-input-showpicker
5696 void HTMLInputElement::ShowPicker(ErrorResult& aRv) {
5697 // Step 1. If this is not mutable, then throw an "InvalidStateError"
5698 // DOMException.
5699 if (!IsMutable()) {
5700 return aRv.ThrowInvalidStateError(
5701 "This input is either disabled or readonly.");
5704 // Step 2. If this's relevant settings object's origin is not same origin with
5705 // this's relevant settings object's top-level origin, and this's type
5706 // attribute is not in the File Upload state or Color state, then throw a
5707 // "SecurityError" DOMException.
5708 if (mType != FormControlType::InputFile &&
5709 mType != FormControlType::InputColor) {
5710 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
5711 WindowGlobalChild* windowGlobalChild =
5712 window ? window->GetWindowGlobalChild() : nullptr;
5713 if (!windowGlobalChild || !windowGlobalChild->SameOriginWithTop()) {
5714 return aRv.ThrowSecurityError(
5715 "Call was blocked because the current origin isn't same-origin with "
5716 "top.");
5720 // Step 3. If this's relevant global object does not have transient
5721 // activation, then throw a "NotAllowedError" DOMException.
5722 if (!OwnerDoc()->HasValidTransientUserGestureActivation()) {
5723 return aRv.ThrowNotAllowedError(
5724 "Call was blocked due to lack of user activation.");
5727 // Step 4. Show the picker, if applicable, for this.
5729 // https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
5730 // To show the picker, if applicable for an input element element:
5732 // Step 1. Assert: element's relevant global object has transient activation.
5733 // Step 2. If element is not mutable, then return.
5734 // (See above.)
5736 // Step 3. If element's type attribute is in the File Upload state, then run
5737 // these steps in parallel:
5738 if (mType == FormControlType::InputFile) {
5739 FilePickerType type = FILE_PICKER_FILE;
5740 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
5741 HasAttr(nsGkAtoms::webkitdirectory)) {
5742 type = FILE_PICKER_DIRECTORY;
5744 InitFilePicker(type);
5745 return;
5748 // Step 4. Otherwise, the user agent should show any relevant user interface
5749 // for selecting a value for element, in the way it normally would when the
5750 // user interacts with the control
5751 if (mType == FormControlType::InputColor) {
5752 InitColorPicker();
5753 return;
5756 if (CreatesDateTimeWidget() && IsInComposedDoc()) {
5757 if (RefPtr<Element> dateTimeBoxElement = GetDateTimeBoxElement()) {
5758 // Event is dispatched to closed-shadow tree and doesn't bubble.
5759 RefPtr<Document> doc = dateTimeBoxElement->OwnerDoc();
5760 nsContentUtils::DispatchTrustedEvent(doc, dateTimeBoxElement,
5761 u"MozDateTimeShowPickerForJS"_ns,
5762 CanBubble::eNo, Cancelable::eNo);
5767 #ifdef ACCESSIBILITY
5768 /*static*/ nsresult FireEventForAccessibility(HTMLInputElement* aTarget,
5769 EventMessage aEventMessage) {
5770 Element* element = static_cast<Element*>(aTarget);
5771 return nsContentUtils::DispatchTrustedEvent<WidgetEvent>(
5772 element->OwnerDoc(), element, aEventMessage, CanBubble::eYes,
5773 Cancelable::eYes);
5775 #endif
5777 void HTMLInputElement::UpdateApzAwareFlag() {
5778 #if !defined(ANDROID) && !defined(XP_MACOSX)
5779 if (mType == FormControlType::InputNumber ||
5780 mType == FormControlType::InputRange) {
5781 SetMayBeApzAware();
5783 #endif
5786 nsresult HTMLInputElement::SetDefaultValueAsValue() {
5787 NS_ASSERTION(GetValueMode() == VALUE_MODE_VALUE,
5788 "GetValueMode() should return VALUE_MODE_VALUE!");
5790 // The element has a content attribute value different from it's value when
5791 // it's in the value mode value.
5792 nsAutoString resetVal;
5793 GetDefaultValue(resetVal);
5795 // SetValueInternal is going to sanitize the value.
5796 // TODO(mbrodesser): sanitizing will only happen if `mDoneCreating` is true.
5797 return SetValueInternal(resetVal, ValueSetterOption::ByInternalAPI);
5800 void HTMLInputElement::SetDirectionFromValue(bool aNotify,
5801 const nsAString* aKnownValue) {
5802 // FIXME(emilio): https://html.spec.whatwg.org/#the-directionality says this
5803 // applies to Text, Search, Telephone, URL, or Email state, but the check
5804 // below doesn't filter out week/month/number.
5805 if (!IsSingleLineTextControl(true)) {
5806 return;
5808 nsAutoString value;
5809 if (!aKnownValue) {
5810 // It's unclear if per spec we should use the sanitized or unsanitized
5811 // value to set the directionality, but aKnownValue is unsanitized, so be
5812 // consistent. Using what the user is seeing to determine directionality
5813 // instead of the sanitized (empty if invalid) value probably makes more
5814 // sense.
5815 GetValueInternal(value, CallerType::System);
5816 aKnownValue = &value;
5818 SetDirectionalityFromValue(this, *aKnownValue, aNotify);
5821 NS_IMETHODIMP
5822 HTMLInputElement::Reset() {
5823 // We should be able to reset all dirty flags regardless of the type.
5824 SetCheckedChanged(false);
5825 SetValueChanged(false);
5826 SetLastValueChangeWasInteractive(false);
5828 switch (GetValueMode()) {
5829 case VALUE_MODE_VALUE: {
5830 nsresult result = SetDefaultValueAsValue();
5831 if (CreatesDateTimeWidget()) {
5832 // mFocusedValue has to be set here, so that `FireChangeEventIfNeeded`
5833 // can fire a change event if necessary.
5834 GetValue(mFocusedValue, CallerType::System);
5836 return result;
5838 case VALUE_MODE_DEFAULT_ON:
5839 DoSetChecked(DefaultChecked(), true, false);
5840 return NS_OK;
5841 case VALUE_MODE_FILENAME:
5842 ClearFiles(false);
5843 return NS_OK;
5844 case VALUE_MODE_DEFAULT:
5845 default:
5846 return NS_OK;
5850 NS_IMETHODIMP
5851 HTMLInputElement::SubmitNamesValues(FormData* aFormData) {
5852 // For type=reset, and type=button, we just never submit, period.
5853 // For type=image and type=button, we only submit if we were the button
5854 // pressed
5855 // For type=radio and type=checkbox, we only submit if checked=true
5856 if (mType == FormControlType::InputReset ||
5857 mType == FormControlType::InputButton ||
5858 ((mType == FormControlType::InputSubmit ||
5859 mType == FormControlType::InputImage) &&
5860 aFormData->GetSubmitterElement() != this) ||
5861 ((mType == FormControlType::InputRadio ||
5862 mType == FormControlType::InputCheckbox) &&
5863 !mChecked)) {
5864 return NS_OK;
5867 // Get the name
5868 nsAutoString name;
5869 GetAttr(nsGkAtoms::name, name);
5871 // Submit .x, .y for input type=image
5872 if (mType == FormControlType::InputImage) {
5873 // Get a property set by the frame to find out where it was clicked.
5874 const auto* lastClickedPoint =
5875 static_cast<CSSIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
5876 int32_t x, y;
5877 if (lastClickedPoint) {
5878 // Convert the values to strings for submission
5879 x = lastClickedPoint->x;
5880 y = lastClickedPoint->y;
5881 } else {
5882 x = y = 0;
5885 nsAutoString xVal, yVal;
5886 xVal.AppendInt(x);
5887 yVal.AppendInt(y);
5889 if (!name.IsEmpty()) {
5890 aFormData->AddNameValuePair(name + u".x"_ns, xVal);
5891 aFormData->AddNameValuePair(name + u".y"_ns, yVal);
5892 } else {
5893 // If the Image Element has no name, simply return x and y
5894 // to Nav and IE compatibility.
5895 aFormData->AddNameValuePair(u"x"_ns, xVal);
5896 aFormData->AddNameValuePair(u"y"_ns, yVal);
5899 return NS_OK;
5902 // If name not there, don't submit
5903 if (name.IsEmpty()) {
5904 return NS_OK;
5908 // Submit file if its input type=file and this encoding method accepts files
5910 if (mType == FormControlType::InputFile) {
5911 // Submit files
5913 const nsTArray<OwningFileOrDirectory>& files =
5914 GetFilesOrDirectoriesInternal();
5916 if (files.IsEmpty()) {
5917 NS_ENSURE_STATE(GetOwnerGlobal());
5918 ErrorResult rv;
5919 RefPtr<Blob> blob = Blob::CreateStringBlob(
5920 GetOwnerGlobal(), ""_ns, u"application/octet-stream"_ns);
5921 RefPtr<File> file = blob->ToFile(u""_ns, rv);
5923 if (!rv.Failed()) {
5924 aFormData->AddNameBlobPair(name, file);
5927 return rv.StealNSResult();
5930 for (uint32_t i = 0; i < files.Length(); ++i) {
5931 if (files[i].IsFile()) {
5932 aFormData->AddNameBlobPair(name, files[i].GetAsFile());
5933 } else {
5934 MOZ_ASSERT(files[i].IsDirectory());
5935 aFormData->AddNameDirectoryPair(name, files[i].GetAsDirectory());
5939 return NS_OK;
5942 if (mType == FormControlType::InputHidden &&
5943 name.LowerCaseEqualsLiteral("_charset_")) {
5944 nsCString charset;
5945 aFormData->GetCharset(charset);
5946 return aFormData->AddNameValuePair(name, NS_ConvertASCIItoUTF16(charset));
5950 // Submit name=value
5953 // Get the value
5954 nsAutoString value;
5955 GetValue(value, CallerType::System);
5957 if (mType == FormControlType::InputSubmit && value.IsEmpty() &&
5958 !HasAttr(nsGkAtoms::value)) {
5959 // Get our default value, which is the same as our default label
5960 nsAutoString defaultValue;
5961 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
5962 "Submit", OwnerDoc(), defaultValue);
5963 value = defaultValue;
5966 const nsresult rv = aFormData->AddNameValuePair(name, value);
5967 if (NS_FAILED(rv)) {
5968 return rv;
5971 // Submit dirname=dir
5972 if (DoesDirnameApply()) {
5973 return SubmitDirnameDir(aFormData);
5976 return NS_OK;
5979 static nsTArray<FileContentData> SaveFileContentData(
5980 const nsTArray<OwningFileOrDirectory>& aArray) {
5981 nsTArray<FileContentData> res(aArray.Length());
5982 for (const auto& it : aArray) {
5983 if (it.IsFile()) {
5984 RefPtr<BlobImpl> impl = it.GetAsFile()->Impl();
5985 res.AppendElement(std::move(impl));
5986 } else {
5987 MOZ_ASSERT(it.IsDirectory());
5988 nsString fullPath;
5989 nsresult rv = it.GetAsDirectory()->GetFullRealPath(fullPath);
5990 if (NS_WARN_IF(NS_FAILED(rv))) {
5991 continue;
5993 res.AppendElement(std::move(fullPath));
5996 return res;
5999 void HTMLInputElement::SaveState() {
6000 PresState* state = nullptr;
6001 switch (GetValueMode()) {
6002 case VALUE_MODE_DEFAULT_ON:
6003 if (mCheckedChanged) {
6004 state = GetPrimaryPresState();
6005 if (!state) {
6006 return;
6009 state->contentData() = CheckedContentData(mChecked);
6011 break;
6012 case VALUE_MODE_FILENAME:
6013 if (!mFileData->mFilesOrDirectories.IsEmpty()) {
6014 state = GetPrimaryPresState();
6015 if (!state) {
6016 return;
6019 state->contentData() =
6020 SaveFileContentData(mFileData->mFilesOrDirectories);
6022 break;
6023 case VALUE_MODE_VALUE:
6024 case VALUE_MODE_DEFAULT:
6025 // VALUE_MODE_DEFAULT shouldn't have their value saved except 'hidden',
6026 // mType should have never been FormControlType::InputPassword and value
6027 // should have changed.
6028 if ((GetValueMode() == VALUE_MODE_DEFAULT &&
6029 mType != FormControlType::InputHidden) ||
6030 mHasBeenTypePassword || !mValueChanged) {
6031 break;
6034 state = GetPrimaryPresState();
6035 if (!state) {
6036 return;
6039 nsAutoString value;
6040 GetValue(value, CallerType::System);
6042 if (!IsSingleLineTextControl(false) &&
6043 NS_FAILED(nsLinebreakConverter::ConvertStringLineBreaks(
6044 value, nsLinebreakConverter::eLinebreakPlatform,
6045 nsLinebreakConverter::eLinebreakContent))) {
6046 NS_ERROR("Converting linebreaks failed!");
6047 return;
6050 state->contentData() =
6051 TextContentData(value, mLastValueChangeWasInteractive);
6052 break;
6055 if (mDisabledChanged) {
6056 if (!state) {
6057 state = GetPrimaryPresState();
6059 if (state) {
6060 // We do not want to save the real disabled state but the disabled
6061 // attribute.
6062 state->disabled() = HasAttr(nsGkAtoms::disabled);
6063 state->disabledSet() = true;
6068 void HTMLInputElement::DoneCreatingElement() {
6069 mDoneCreating = true;
6072 // Restore state as needed. Note that disabled state applies to all control
6073 // types.
6075 bool restoredCheckedState = false;
6076 if (!mInhibitRestoration) {
6077 GenerateStateKey();
6078 restoredCheckedState = RestoreFormControlState();
6082 // If restore does not occur, we initialize .checked using the CHECKED
6083 // property.
6085 if (!restoredCheckedState && mShouldInitChecked) {
6086 DoSetChecked(DefaultChecked(), false, false);
6089 // Sanitize the value and potentially set mFocusedValue.
6090 if (GetValueMode() == VALUE_MODE_VALUE) {
6091 nsAutoString value;
6092 GetValue(value, CallerType::System);
6093 // TODO: What should we do if SetValueInternal fails? (The allocation
6094 // may potentially be big, but most likely we've failed to allocate
6095 // before the type change.)
6096 SetValueInternal(value, ValueSetterOption::ByInternalAPI);
6098 if (CreatesDateTimeWidget()) {
6099 // mFocusedValue has to be set here, so that `FireChangeEventIfNeeded` can
6100 // fire a change event if necessary.
6101 mFocusedValue = value;
6105 mShouldInitChecked = false;
6108 void HTMLInputElement::DestroyContent() {
6109 nsImageLoadingContent::Destroy();
6110 TextControlElement::DestroyContent();
6113 void HTMLInputElement::UpdateValidityElementStates(bool aNotify) {
6114 AutoStateChangeNotifier notifier(*this, aNotify);
6115 RemoveStatesSilently(ElementState::VALIDITY_STATES);
6116 if (!IsCandidateForConstraintValidation()) {
6117 return;
6119 ElementState state;
6120 if (IsValid()) {
6121 state |= ElementState::VALID;
6122 } else {
6123 state |= ElementState::INVALID;
6124 if (GetValidityState(VALIDITY_STATE_CUSTOM_ERROR) ||
6125 (mCanShowInvalidUI && ShouldShowValidityUI())) {
6126 state |= ElementState::USER_INVALID;
6129 // :-moz-ui-valid applies if all of the following conditions are true:
6130 // 1. The element is not focused, or had either :-moz-ui-valid or
6131 // :-moz-ui-invalid applying before it was focused ;
6132 // 2. The element is either valid or isn't allowed to have
6133 // :-moz-ui-invalid applying ;
6134 // 3. The element has already been modified or the user tried to submit the
6135 // form owner while invalid.
6136 if (mCanShowValidUI && ShouldShowValidityUI() &&
6137 (IsValid() ||
6138 (!state.HasState(ElementState::USER_INVALID) && !mCanShowInvalidUI))) {
6139 state |= ElementState::USER_VALID;
6141 AddStatesSilently(state);
6144 static nsTArray<OwningFileOrDirectory> RestoreFileContentData(
6145 nsPIDOMWindowInner* aWindow, const nsTArray<FileContentData>& aData) {
6146 nsTArray<OwningFileOrDirectory> res(aData.Length());
6147 for (const auto& it : aData) {
6148 if (it.type() == FileContentData::TBlobImpl) {
6149 if (!it.get_BlobImpl()) {
6150 // Serialization failed, skip this file.
6151 continue;
6154 RefPtr<File> file = File::Create(aWindow->AsGlobal(), it.get_BlobImpl());
6155 if (NS_WARN_IF(!file)) {
6156 continue;
6159 OwningFileOrDirectory* element = res.AppendElement();
6160 element->SetAsFile() = file;
6161 } else {
6162 MOZ_ASSERT(it.type() == FileContentData::TnsString);
6163 nsCOMPtr<nsIFile> file;
6164 nsresult rv =
6165 NS_NewLocalFile(it.get_nsString(), true, getter_AddRefs(file));
6166 if (NS_WARN_IF(NS_FAILED(rv))) {
6167 continue;
6170 RefPtr<Directory> directory =
6171 Directory::Create(aWindow->AsGlobal(), file);
6172 MOZ_ASSERT(directory);
6174 OwningFileOrDirectory* element = res.AppendElement();
6175 element->SetAsDirectory() = directory;
6178 return res;
6181 bool HTMLInputElement::RestoreState(PresState* aState) {
6182 bool restoredCheckedState = false;
6184 const PresContentData& inputState = aState->contentData();
6186 switch (GetValueMode()) {
6187 case VALUE_MODE_DEFAULT_ON:
6188 if (inputState.type() == PresContentData::TCheckedContentData) {
6189 restoredCheckedState = true;
6190 bool checked = inputState.get_CheckedContentData().checked();
6191 DoSetChecked(checked, true, true);
6193 break;
6194 case VALUE_MODE_FILENAME:
6195 if (inputState.type() == PresContentData::TArrayOfFileContentData) {
6196 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
6197 if (window) {
6198 nsTArray<OwningFileOrDirectory> array =
6199 RestoreFileContentData(window, inputState);
6200 SetFilesOrDirectories(array, true);
6203 break;
6204 case VALUE_MODE_VALUE:
6205 case VALUE_MODE_DEFAULT:
6206 if (GetValueMode() == VALUE_MODE_DEFAULT &&
6207 mType != FormControlType::InputHidden) {
6208 break;
6211 if (inputState.type() == PresContentData::TTextContentData) {
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(inputState.get_TextContentData().value(),
6216 ValueSetterOption::SetValueChanged);
6217 if (inputState.get_TextContentData().lastValueChangeWasInteractive()) {
6218 SetLastValueChangeWasInteractive(true);
6221 break;
6224 if (aState->disabledSet() && !aState->disabled()) {
6225 SetDisabled(false, IgnoreErrors());
6228 return restoredCheckedState;
6232 * Radio group stuff
6235 void HTMLInputElement::AddToRadioGroup() {
6236 MOZ_ASSERT(!mRadioGroupContainer,
6237 "Radio button must be removed from previous radio group container "
6238 "before being added to another!");
6240 // If the element has no radio group container we can stop here.
6241 auto* container = FindTreeRadioGroupContainer();
6242 if (!container) {
6243 return;
6246 nsAutoString name;
6247 GetAttr(nsGkAtoms::name, name);
6248 // If we are part of a radio group, the element must have a name.
6249 MOZ_ASSERT(!name.IsEmpty());
6252 // Add the radio to the radio group container.
6254 container->AddToRadioGroup(name, this, mForm);
6255 mRadioGroupContainer = container;
6258 // If the input element is checked, and we add it to the group, it will
6259 // deselect whatever is currently selected in that group
6261 if (mChecked) {
6263 // If it is checked, call "RadioSetChecked" to perform the selection/
6264 // deselection ritual. This has the side effect of repainting the
6265 // radio button, but as adding a checked radio button into the group
6266 // should not be that common an occurrence, I think we can live with
6267 // that.
6268 // Make sure not to notify if we're still being created.
6270 RadioSetChecked(mDoneCreating);
6274 // For integrity purposes, we have to ensure that "checkedChanged" is
6275 // the same for this new element as for all the others in the group
6277 bool checkedChanged = mCheckedChanged;
6279 nsCOMPtr<nsIRadioVisitor> visitor =
6280 new nsRadioGetCheckedChangedVisitor(&checkedChanged, this);
6281 VisitGroup(visitor);
6283 SetCheckedChangedInternal(checkedChanged);
6285 // We initialize the validity of the element to the validity of the group
6286 // because we assume UpdateValueMissingState() will be called after.
6287 SetValidityState(VALIDITY_STATE_VALUE_MISSING,
6288 container->GetValueMissingState(name));
6291 void HTMLInputElement::RemoveFromRadioGroup() {
6292 auto* container = GetCurrentRadioGroupContainer();
6293 if (!container) {
6294 return;
6297 nsAutoString name;
6298 GetAttr(nsGkAtoms::name, name);
6300 // If this button was checked, we need to notify the group that there is no
6301 // longer a selected radio button
6302 if (mChecked) {
6303 container->SetCurrentRadioButton(name, nullptr);
6304 nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
6305 VisitGroup(visitor);
6308 // Remove this radio from its group in the container.
6309 // We need to call UpdateValueMissingValidityStateForRadio before to make sure
6310 // the group validity is updated (with this element being ignored).
6311 UpdateValueMissingValidityStateForRadio(true);
6312 container->RemoveFromRadioGroup(name, this);
6313 mRadioGroupContainer = nullptr;
6316 bool HTMLInputElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
6317 int32_t* aTabIndex) {
6318 if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
6319 aWithMouse, aIsFocusable, aTabIndex)) {
6320 return true;
6323 if (IsDisabled()) {
6324 *aIsFocusable = false;
6325 return true;
6328 if (IsSingleLineTextControl(false) || mType == FormControlType::InputRange) {
6329 *aIsFocusable = true;
6330 return false;
6333 const bool defaultFocusable = IsFormControlDefaultFocusable(aWithMouse);
6334 if (CreatesDateTimeWidget()) {
6335 if (aTabIndex) {
6336 // We only want our native anonymous child to be tabable to, not ourself.
6337 *aTabIndex = -1;
6339 *aIsFocusable = true;
6340 return true;
6343 if (mType == FormControlType::InputHidden) {
6344 if (aTabIndex) {
6345 *aTabIndex = -1;
6347 *aIsFocusable = false;
6348 return false;
6351 if (!aTabIndex) {
6352 // The other controls are all focusable
6353 *aIsFocusable = defaultFocusable;
6354 return false;
6357 if (mType != FormControlType::InputRadio) {
6358 *aIsFocusable = defaultFocusable;
6359 return false;
6362 if (mChecked) {
6363 // Selected radio buttons are tabbable
6364 *aIsFocusable = defaultFocusable;
6365 return false;
6368 // Current radio button is not selected.
6369 // But make it tabbable if nothing in group is selected.
6370 auto* container = GetCurrentRadioGroupContainer();
6371 if (!container) {
6372 *aIsFocusable = defaultFocusable;
6373 return false;
6376 nsAutoString name;
6377 GetAttr(nsGkAtoms::name, name);
6379 if (container->GetCurrentRadioButton(name)) {
6380 *aTabIndex = -1;
6382 *aIsFocusable = defaultFocusable;
6383 return false;
6386 nsresult HTMLInputElement::VisitGroup(nsIRadioVisitor* aVisitor) {
6387 if (auto* container = GetCurrentRadioGroupContainer()) {
6388 nsAutoString name;
6389 GetAttr(nsGkAtoms::name, name);
6390 return container->WalkRadioGroup(name, aVisitor);
6393 aVisitor->Visit(this);
6394 return NS_OK;
6397 HTMLInputElement::ValueModeType HTMLInputElement::GetValueMode() const {
6398 switch (mType) {
6399 case FormControlType::InputHidden:
6400 case FormControlType::InputSubmit:
6401 case FormControlType::InputButton:
6402 case FormControlType::InputReset:
6403 case FormControlType::InputImage:
6404 return VALUE_MODE_DEFAULT;
6405 case FormControlType::InputCheckbox:
6406 case FormControlType::InputRadio:
6407 return VALUE_MODE_DEFAULT_ON;
6408 case FormControlType::InputFile:
6409 return VALUE_MODE_FILENAME;
6410 #ifdef DEBUG
6411 case FormControlType::InputText:
6412 case FormControlType::InputPassword:
6413 case FormControlType::InputSearch:
6414 case FormControlType::InputTel:
6415 case FormControlType::InputEmail:
6416 case FormControlType::InputUrl:
6417 case FormControlType::InputNumber:
6418 case FormControlType::InputRange:
6419 case FormControlType::InputDate:
6420 case FormControlType::InputTime:
6421 case FormControlType::InputColor:
6422 case FormControlType::InputMonth:
6423 case FormControlType::InputWeek:
6424 case FormControlType::InputDatetimeLocal:
6425 return VALUE_MODE_VALUE;
6426 default:
6427 MOZ_ASSERT_UNREACHABLE("Unexpected input type in GetValueMode()");
6428 return VALUE_MODE_VALUE;
6429 #else // DEBUG
6430 default:
6431 return VALUE_MODE_VALUE;
6432 #endif // DEBUG
6436 bool HTMLInputElement::IsMutable() const {
6437 return !IsDisabled() &&
6438 !(DoesReadOnlyApply() && State().HasState(ElementState::READONLY));
6441 bool HTMLInputElement::DoesRequiredApply() const {
6442 switch (mType) {
6443 case FormControlType::InputHidden:
6444 case FormControlType::InputButton:
6445 case FormControlType::InputImage:
6446 case FormControlType::InputReset:
6447 case FormControlType::InputSubmit:
6448 case FormControlType::InputRange:
6449 case FormControlType::InputColor:
6450 return false;
6451 #ifdef DEBUG
6452 case FormControlType::InputRadio:
6453 case FormControlType::InputCheckbox:
6454 case FormControlType::InputFile:
6455 case FormControlType::InputText:
6456 case FormControlType::InputPassword:
6457 case FormControlType::InputSearch:
6458 case FormControlType::InputTel:
6459 case FormControlType::InputEmail:
6460 case FormControlType::InputUrl:
6461 case FormControlType::InputNumber:
6462 case FormControlType::InputDate:
6463 case FormControlType::InputTime:
6464 case FormControlType::InputMonth:
6465 case FormControlType::InputWeek:
6466 case FormControlType::InputDatetimeLocal:
6467 return true;
6468 default:
6469 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
6470 return true;
6471 #else // DEBUG
6472 default:
6473 return true;
6474 #endif // DEBUG
6478 bool HTMLInputElement::PlaceholderApplies() const {
6479 if (IsDateTimeInputType(mType)) {
6480 return false;
6482 return IsSingleLineTextControl(false);
6485 bool HTMLInputElement::DoesMinMaxApply() const {
6486 switch (mType) {
6487 case FormControlType::InputNumber:
6488 case FormControlType::InputDate:
6489 case FormControlType::InputTime:
6490 case FormControlType::InputRange:
6491 case FormControlType::InputMonth:
6492 case FormControlType::InputWeek:
6493 case FormControlType::InputDatetimeLocal:
6494 return true;
6495 #ifdef DEBUG
6496 case FormControlType::InputReset:
6497 case FormControlType::InputSubmit:
6498 case FormControlType::InputImage:
6499 case FormControlType::InputButton:
6500 case FormControlType::InputHidden:
6501 case FormControlType::InputRadio:
6502 case FormControlType::InputCheckbox:
6503 case FormControlType::InputFile:
6504 case FormControlType::InputText:
6505 case FormControlType::InputPassword:
6506 case FormControlType::InputSearch:
6507 case FormControlType::InputTel:
6508 case FormControlType::InputEmail:
6509 case FormControlType::InputUrl:
6510 case FormControlType::InputColor:
6511 return false;
6512 default:
6513 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
6514 return false;
6515 #else // DEBUG
6516 default:
6517 return false;
6518 #endif // DEBUG
6522 bool HTMLInputElement::DoesAutocompleteApply() const {
6523 switch (mType) {
6524 case FormControlType::InputHidden:
6525 case FormControlType::InputText:
6526 case FormControlType::InputSearch:
6527 case FormControlType::InputUrl:
6528 case FormControlType::InputTel:
6529 case FormControlType::InputEmail:
6530 case FormControlType::InputPassword:
6531 case FormControlType::InputDate:
6532 case FormControlType::InputTime:
6533 case FormControlType::InputNumber:
6534 case FormControlType::InputRange:
6535 case FormControlType::InputColor:
6536 case FormControlType::InputMonth:
6537 case FormControlType::InputWeek:
6538 case FormControlType::InputDatetimeLocal:
6539 return true;
6540 #ifdef DEBUG
6541 case FormControlType::InputReset:
6542 case FormControlType::InputSubmit:
6543 case FormControlType::InputImage:
6544 case FormControlType::InputButton:
6545 case FormControlType::InputRadio:
6546 case FormControlType::InputCheckbox:
6547 case FormControlType::InputFile:
6548 return false;
6549 default:
6550 MOZ_ASSERT_UNREACHABLE(
6551 "Unexpected input type in DoesAutocompleteApply()");
6552 return false;
6553 #else // DEBUG
6554 default:
6555 return false;
6556 #endif // DEBUG
6560 Decimal HTMLInputElement::GetStep() const {
6561 MOZ_ASSERT(DoesStepApply(), "GetStep() can only be called if @step applies");
6563 if (!HasAttr(nsGkAtoms::step)) {
6564 return GetDefaultStep() * GetStepScaleFactor();
6567 nsAutoString stepStr;
6568 GetAttr(nsGkAtoms::step, stepStr);
6570 if (stepStr.LowerCaseEqualsLiteral("any")) {
6571 // The element can't suffer from step mismatch if there is no step.
6572 return kStepAny;
6575 Decimal step = StringToDecimal(stepStr);
6576 if (!step.isFinite() || step <= Decimal(0)) {
6577 step = GetDefaultStep();
6580 // For input type=date, we round the step value to have a rounded day.
6581 if (mType == FormControlType::InputDate ||
6582 mType == FormControlType::InputMonth ||
6583 mType == FormControlType::InputWeek) {
6584 step = std::max(step.round(), Decimal(1));
6587 return step * GetStepScaleFactor();
6590 // ConstraintValidation
6592 void HTMLInputElement::SetCustomValidity(const nsAString& aError) {
6593 ConstraintValidation::SetCustomValidity(aError);
6594 UpdateValidityElementStates(true);
6597 bool HTMLInputElement::IsTooLong() {
6598 if (!mValueChanged || !mLastValueChangeWasInteractive) {
6599 return false;
6602 return mInputType->IsTooLong();
6605 bool HTMLInputElement::IsTooShort() {
6606 if (!mValueChanged || !mLastValueChangeWasInteractive) {
6607 return false;
6610 return mInputType->IsTooShort();
6613 bool HTMLInputElement::IsValueMissing() const {
6614 // Should use UpdateValueMissingValidityStateForRadio() for type radio.
6615 MOZ_ASSERT(mType != FormControlType::InputRadio);
6617 return mInputType->IsValueMissing();
6620 bool HTMLInputElement::HasTypeMismatch() const {
6621 return mInputType->HasTypeMismatch();
6624 Maybe<bool> HTMLInputElement::HasPatternMismatch() const {
6625 return mInputType->HasPatternMismatch();
6628 bool HTMLInputElement::IsRangeOverflow() const {
6629 return mInputType->IsRangeOverflow();
6632 bool HTMLInputElement::IsRangeUnderflow() const {
6633 return mInputType->IsRangeUnderflow();
6636 bool HTMLInputElement::ValueIsStepMismatch(const Decimal& aValue) const {
6637 if (aValue.isNaN()) {
6638 // The element can't suffer from step mismatch if its value isn't a
6639 // number.
6640 return false;
6643 Decimal step = GetStep();
6644 if (step == kStepAny) {
6645 return false;
6648 // Value has to be an integral multiple of step.
6649 return NS_floorModulo(aValue - GetStepBase(), step) != Decimal(0);
6652 bool HTMLInputElement::HasStepMismatch() const {
6653 return mInputType->HasStepMismatch();
6656 bool HTMLInputElement::HasBadInput() const { return mInputType->HasBadInput(); }
6658 void HTMLInputElement::UpdateTooLongValidityState() {
6659 SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong());
6662 void HTMLInputElement::UpdateTooShortValidityState() {
6663 SetValidityState(VALIDITY_STATE_TOO_SHORT, IsTooShort());
6666 void HTMLInputElement::UpdateValueMissingValidityStateForRadio(
6667 bool aIgnoreSelf) {
6668 MOZ_ASSERT(mType == FormControlType::InputRadio,
6669 "This should be called only for radio input types");
6671 HTMLInputElement* selection = GetSelectedRadioButton();
6673 // If there is no selection, that might mean the radio is not in a group.
6674 // In that case, we can look for the checked state of the radio.
6675 bool selected = selection || (!aIgnoreSelf && mChecked);
6676 bool required = !aIgnoreSelf && IsRequired();
6678 auto* container = GetCurrentRadioGroupContainer();
6679 if (!container) {
6680 SetValidityState(VALIDITY_STATE_VALUE_MISSING, false);
6681 return;
6684 nsAutoString name;
6685 GetAttr(nsGkAtoms::name, name);
6687 // If the current radio is required and not ignored, we can assume the entire
6688 // group is required.
6689 if (!required) {
6690 required = (aIgnoreSelf && IsRequired())
6691 ? container->GetRequiredRadioCount(name) - 1
6692 : container->GetRequiredRadioCount(name);
6695 bool valueMissing = required && !selected;
6696 if (container->GetValueMissingState(name) != valueMissing) {
6697 container->SetValueMissingState(name, valueMissing);
6699 SetValidityState(VALIDITY_STATE_VALUE_MISSING, valueMissing);
6701 // nsRadioSetValueMissingState will call ElementStateChanged while visiting.
6702 nsAutoScriptBlocker scriptBlocker;
6703 nsCOMPtr<nsIRadioVisitor> visitor =
6704 new nsRadioSetValueMissingState(this, valueMissing);
6705 VisitGroup(visitor);
6709 void HTMLInputElement::UpdateValueMissingValidityState() {
6710 if (mType == FormControlType::InputRadio) {
6711 UpdateValueMissingValidityStateForRadio(false);
6712 return;
6715 SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
6718 void HTMLInputElement::UpdateTypeMismatchValidityState() {
6719 SetValidityState(VALIDITY_STATE_TYPE_MISMATCH, HasTypeMismatch());
6722 void HTMLInputElement::UpdatePatternMismatchValidityState() {
6723 Maybe<bool> hasMismatch = HasPatternMismatch();
6724 // Don't update if the JS engine failed to evaluate it.
6725 if (hasMismatch.isSome()) {
6726 SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH, hasMismatch.value());
6730 void HTMLInputElement::UpdateRangeOverflowValidityState() {
6731 SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW, IsRangeOverflow());
6732 UpdateInRange(true);
6735 void HTMLInputElement::UpdateRangeUnderflowValidityState() {
6736 SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW, IsRangeUnderflow());
6737 UpdateInRange(true);
6740 void HTMLInputElement::UpdateStepMismatchValidityState() {
6741 SetValidityState(VALIDITY_STATE_STEP_MISMATCH, HasStepMismatch());
6744 void HTMLInputElement::UpdateBadInputValidityState() {
6745 SetValidityState(VALIDITY_STATE_BAD_INPUT, HasBadInput());
6748 void HTMLInputElement::UpdateAllValidityStates(bool aNotify) {
6749 bool validBefore = IsValid();
6750 UpdateAllValidityStatesButNotElementState();
6751 if (validBefore != IsValid()) {
6752 UpdateValidityElementStates(aNotify);
6756 void HTMLInputElement::UpdateAllValidityStatesButNotElementState() {
6757 UpdateTooLongValidityState();
6758 UpdateTooShortValidityState();
6759 UpdateValueMissingValidityState();
6760 UpdateTypeMismatchValidityState();
6761 UpdatePatternMismatchValidityState();
6762 UpdateRangeOverflowValidityState();
6763 UpdateRangeUnderflowValidityState();
6764 UpdateStepMismatchValidityState();
6765 UpdateBadInputValidityState();
6768 void HTMLInputElement::UpdateBarredFromConstraintValidation() {
6769 // NOTE: readonly attribute causes an element to be barred from constraint
6770 // validation even if it doesn't apply to that input type. That's rather
6771 // weird, but pre-existing behavior.
6772 bool wasCandidate = IsCandidateForConstraintValidation();
6773 SetBarredFromConstraintValidation(
6774 mType == FormControlType::InputHidden ||
6775 mType == FormControlType::InputButton ||
6776 mType == FormControlType::InputReset || IsDisabled() ||
6777 HasAttr(nsGkAtoms::readonly) ||
6778 HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR));
6779 if (IsCandidateForConstraintValidation() != wasCandidate) {
6780 UpdateInRange(true);
6784 nsresult HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
6785 ValidityStateType aType) {
6786 return mInputType->GetValidationMessage(aValidationMessage, aType);
6789 bool HTMLInputElement::IsSingleLineTextControl() const {
6790 return IsSingleLineTextControl(false);
6793 bool HTMLInputElement::IsTextArea() const { return false; }
6795 bool HTMLInputElement::IsPasswordTextControl() const {
6796 return mType == FormControlType::InputPassword;
6799 int32_t HTMLInputElement::GetCols() {
6800 // Else we know (assume) it is an input with size attr
6801 const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::size);
6802 if (attr && attr->Type() == nsAttrValue::eInteger) {
6803 int32_t cols = attr->GetIntegerValue();
6804 if (cols > 0) {
6805 return cols;
6809 return DEFAULT_COLS;
6812 int32_t HTMLInputElement::GetWrapCols() {
6813 return 0; // only textarea's can have wrap cols
6816 int32_t HTMLInputElement::GetRows() { return DEFAULT_ROWS; }
6818 void HTMLInputElement::GetDefaultValueFromContent(nsAString& aValue,
6819 bool aForDisplay) {
6820 if (!GetEditorState()) {
6821 return;
6823 GetDefaultValue(aValue);
6824 // This is called by the frame to show the value.
6825 // We have to sanitize it when needed.
6826 // FIXME: Do we want to sanitize even when aForDisplay is false?
6827 if (mDoneCreating) {
6828 SanitizeValue(aValue, aForDisplay ? SanitizationKind::ForDisplay
6829 : SanitizationKind::Other);
6833 bool HTMLInputElement::ValueChanged() const { return mValueChanged; }
6835 void HTMLInputElement::GetTextEditorValue(nsAString& aValue) const {
6836 if (TextControlState* state = GetEditorState()) {
6837 state->GetValue(aValue, /* aIgnoreWrap = */ true, /* aForDisplay = */ true);
6841 void HTMLInputElement::InitializeKeyboardEventListeners() {
6842 TextControlState* state = GetEditorState();
6843 if (state) {
6844 state->InitializeKeyboardEventListeners();
6848 void HTMLInputElement::UpdatePlaceholderShownState() {
6849 SetStates(ElementState::PLACEHOLDER_SHOWN,
6850 IsValueEmpty() && PlaceholderApplies() &&
6851 HasAttr(nsGkAtoms::placeholder));
6854 void HTMLInputElement::OnValueChanged(ValueChangeKind aKind,
6855 bool aNewValueEmpty,
6856 const nsAString* aKnownNewValue) {
6857 MOZ_ASSERT_IF(aKnownNewValue, aKnownNewValue->IsEmpty() == aNewValueEmpty);
6858 if (aKind != ValueChangeKind::Internal) {
6859 mLastValueChangeWasInteractive = aKind == ValueChangeKind::UserInteraction;
6862 if (aNewValueEmpty != IsValueEmpty()) {
6863 SetStates(ElementState::VALUE_EMPTY, aNewValueEmpty);
6864 UpdatePlaceholderShownState();
6867 UpdateAllValidityStates(true);
6869 if (HasDirAuto()) {
6870 SetDirectionFromValue(true, aKnownNewValue);
6874 bool HTMLInputElement::HasCachedSelection() {
6875 TextControlState* state = GetEditorState();
6876 if (!state) {
6877 return false;
6879 return state->IsSelectionCached() && state->HasNeverInitializedBefore() &&
6880 state->GetSelectionProperties().GetStart() !=
6881 state->GetSelectionProperties().GetEnd();
6884 void HTMLInputElement::SetRevealPassword(bool aValue) {
6885 if (NS_WARN_IF(mType != FormControlType::InputPassword)) {
6886 return;
6888 if (aValue == State().HasState(ElementState::REVEALED)) {
6889 return;
6891 RefPtr doc = OwnerDoc();
6892 // We allow chrome code to prevent this. This is important for about:logins,
6893 // which may need to run some OS-dependent authentication code before
6894 // revealing the saved passwords.
6895 bool defaultAction = true;
6896 nsContentUtils::DispatchEventOnlyToChrome(
6897 doc, this, u"MozWillToggleReveal"_ns, CanBubble::eYes, Cancelable::eYes,
6898 &defaultAction);
6899 if (NS_WARN_IF(!defaultAction)) {
6900 return;
6902 SetStates(ElementState::REVEALED, aValue);
6905 bool HTMLInputElement::RevealPassword() const {
6906 if (NS_WARN_IF(mType != FormControlType::InputPassword)) {
6907 return false;
6909 return State().HasState(ElementState::REVEALED);
6912 void HTMLInputElement::FieldSetDisabledChanged(bool aNotify) {
6913 // This *has* to be called *before* UpdateBarredFromConstraintValidation and
6914 // UpdateValueMissingValidityState because these two functions depend on our
6915 // disabled state.
6916 nsGenericHTMLFormControlElementWithState::FieldSetDisabledChanged(aNotify);
6918 UpdateValueMissingValidityState();
6919 UpdateBarredFromConstraintValidation();
6920 UpdateValidityElementStates(aNotify);
6923 void HTMLInputElement::SetFilePickerFiltersFromAccept(
6924 nsIFilePicker* filePicker) {
6925 // We always add |filterAll|
6926 filePicker->AppendFilters(nsIFilePicker::filterAll);
6928 NS_ASSERTION(HasAttr(nsGkAtoms::accept),
6929 "You should not call SetFilePickerFiltersFromAccept if the"
6930 " element has no accept attribute!");
6932 // Services to retrieve image/*, audio/*, video/* filters
6933 nsCOMPtr<nsIStringBundleService> stringService =
6934 components::StringBundle::Service();
6935 if (!stringService) {
6936 return;
6938 nsCOMPtr<nsIStringBundle> filterBundle;
6939 if (NS_FAILED(stringService->CreateBundle(
6940 "chrome://global/content/filepicker.properties",
6941 getter_AddRefs(filterBundle)))) {
6942 return;
6945 // Service to retrieve mime type information for mime types filters
6946 nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
6947 if (!mimeService) {
6948 return;
6951 nsAutoString accept;
6952 GetAttr(nsGkAtoms::accept, accept);
6954 HTMLSplitOnSpacesTokenizer tokenizer(accept, ',');
6956 nsTArray<nsFilePickerFilter> filters;
6957 nsString allExtensionsList;
6959 // Retrieve all filters
6960 while (tokenizer.hasMoreTokens()) {
6961 const nsDependentSubstring& token = tokenizer.nextToken();
6963 if (token.IsEmpty()) {
6964 continue;
6967 int32_t filterMask = 0;
6968 nsString filterName;
6969 nsString extensionListStr;
6971 // First, check for image/audio/video filters...
6972 if (token.EqualsLiteral("image/*")) {
6973 filterMask = nsIFilePicker::filterImages;
6974 filterBundle->GetStringFromName("imageFilter", extensionListStr);
6975 } else if (token.EqualsLiteral("audio/*")) {
6976 filterMask = nsIFilePicker::filterAudio;
6977 filterBundle->GetStringFromName("audioFilter", extensionListStr);
6978 } else if (token.EqualsLiteral("video/*")) {
6979 filterMask = nsIFilePicker::filterVideo;
6980 filterBundle->GetStringFromName("videoFilter", extensionListStr);
6981 } else if (token.First() == '.') {
6982 if (token.Contains(';') || token.Contains('*')) {
6983 // Ignore this filter as it contains reserved characters
6984 continue;
6986 extensionListStr = u"*"_ns + token;
6987 filterName = extensionListStr;
6988 } else {
6989 //... if no image/audio/video filter is found, check mime types filters
6990 nsCOMPtr<nsIMIMEInfo> mimeInfo;
6991 if (NS_FAILED(
6992 mimeService->GetFromTypeAndExtension(NS_ConvertUTF16toUTF8(token),
6993 ""_ns, // No extension
6994 getter_AddRefs(mimeInfo))) ||
6995 !mimeInfo) {
6996 continue;
6999 // Get a name for the filter: first try the description, then the mime
7000 // type name if there is no description
7001 mimeInfo->GetDescription(filterName);
7002 if (filterName.IsEmpty()) {
7003 nsCString mimeTypeName;
7004 mimeInfo->GetType(mimeTypeName);
7005 CopyUTF8toUTF16(mimeTypeName, filterName);
7008 // Get extension list
7009 nsCOMPtr<nsIUTF8StringEnumerator> extensions;
7010 mimeInfo->GetFileExtensions(getter_AddRefs(extensions));
7012 bool hasMore;
7013 while (NS_SUCCEEDED(extensions->HasMore(&hasMore)) && hasMore) {
7014 nsCString extension;
7015 if (NS_FAILED(extensions->GetNext(extension))) {
7016 continue;
7018 if (!extensionListStr.IsEmpty()) {
7019 extensionListStr.AppendLiteral("; ");
7021 extensionListStr += u"*."_ns + NS_ConvertUTF8toUTF16(extension);
7025 if (!filterMask && (extensionListStr.IsEmpty() || filterName.IsEmpty())) {
7026 // No valid filter found
7027 continue;
7030 // At this point we're sure the token represents a valid filter, so pass
7031 // it directly as a raw filter.
7032 filePicker->AppendRawFilter(token);
7034 // If we arrived here, that means we have a valid filter: let's create it
7035 // and add it to our list, if no similar filter is already present
7036 nsFilePickerFilter filter;
7037 if (filterMask) {
7038 filter = nsFilePickerFilter(filterMask);
7039 } else {
7040 filter = nsFilePickerFilter(filterName, extensionListStr);
7043 if (!filters.Contains(filter)) {
7044 if (!allExtensionsList.IsEmpty()) {
7045 allExtensionsList.AppendLiteral("; ");
7047 allExtensionsList += extensionListStr;
7048 filters.AppendElement(filter);
7052 // Remove similar filters
7053 // Iterate over a copy, as we might modify the original filters list
7054 const nsTArray<nsFilePickerFilter> filtersCopy = filters.Clone();
7055 for (uint32_t i = 0; i < filtersCopy.Length(); ++i) {
7056 const nsFilePickerFilter& filterToCheck = filtersCopy[i];
7057 if (filterToCheck.mFilterMask) {
7058 continue;
7060 for (uint32_t j = 0; j < filtersCopy.Length(); ++j) {
7061 if (i == j) {
7062 continue;
7064 // Check if this filter's extension list is a substring of the other one.
7065 // e.g. if filters are "*.jpeg" and "*.jpeg; *.jpg" the first one should
7066 // be removed.
7067 // Add an extra "; " to be sure the check will work and avoid cases like
7068 // "*.xls" being a subtring of "*.xslx" while those are two differents
7069 // filters and none should be removed.
7070 if (FindInReadable(filterToCheck.mFilter + u";"_ns,
7071 filtersCopy[j].mFilter + u";"_ns)) {
7072 // We already have a similar, less restrictive filter (i.e.
7073 // filterToCheck extensionList is just a subset of another filter
7074 // extension list): remove this one
7075 filters.RemoveElement(filterToCheck);
7080 // Add "All Supported Types" filter
7081 if (filters.Length() > 1) {
7082 nsAutoString title;
7083 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
7084 "AllSupportedTypes", title);
7085 filePicker->AppendFilter(title, allExtensionsList);
7088 // Add each filter
7089 for (uint32_t i = 0; i < filters.Length(); ++i) {
7090 const nsFilePickerFilter& filter = filters[i];
7091 if (filter.mFilterMask) {
7092 filePicker->AppendFilters(filter.mFilterMask);
7093 } else {
7094 filePicker->AppendFilter(filter.mTitle, filter.mFilter);
7098 if (filters.Length() >= 1) {
7099 // |filterAll| will always use index=0 so we need to set index=1 as the
7100 // current filter. This will be "All Supported Types" for multiple filters.
7101 filePicker->SetFilterIndex(1);
7105 Decimal HTMLInputElement::GetStepScaleFactor() const {
7106 MOZ_ASSERT(DoesStepApply());
7108 switch (mType) {
7109 case FormControlType::InputDate:
7110 return kStepScaleFactorDate;
7111 case FormControlType::InputNumber:
7112 case FormControlType::InputRange:
7113 return kStepScaleFactorNumberRange;
7114 case FormControlType::InputTime:
7115 case FormControlType::InputDatetimeLocal:
7116 return kStepScaleFactorTime;
7117 case FormControlType::InputMonth:
7118 return kStepScaleFactorMonth;
7119 case FormControlType::InputWeek:
7120 return kStepScaleFactorWeek;
7121 default:
7122 MOZ_ASSERT(false, "Unrecognized input type");
7123 return Decimal::nan();
7127 Decimal HTMLInputElement::GetDefaultStep() const {
7128 MOZ_ASSERT(DoesStepApply());
7130 switch (mType) {
7131 case FormControlType::InputDate:
7132 case FormControlType::InputMonth:
7133 case FormControlType::InputWeek:
7134 case FormControlType::InputNumber:
7135 case FormControlType::InputRange:
7136 return kDefaultStep;
7137 case FormControlType::InputTime:
7138 case FormControlType::InputDatetimeLocal:
7139 return kDefaultStepTime;
7140 default:
7141 MOZ_ASSERT(false, "Unrecognized input type");
7142 return Decimal::nan();
7146 void HTMLInputElement::UpdateValidityUIBits(bool aIsFocused) {
7147 if (aIsFocused) {
7148 // If the invalid UI is shown, we should show it while focusing (and
7149 // update). Otherwise, we should not.
7150 mCanShowInvalidUI = !IsValid() && ShouldShowValidityUI();
7152 // If neither invalid UI nor valid UI is shown, we shouldn't show the valid
7153 // UI while typing.
7154 mCanShowValidUI = ShouldShowValidityUI();
7155 } else {
7156 mCanShowInvalidUI = true;
7157 mCanShowValidUI = true;
7161 void HTMLInputElement::UpdateInRange(bool aNotify) {
7162 AutoStateChangeNotifier notifier(*this, aNotify);
7163 RemoveStatesSilently(ElementState::INRANGE | ElementState::OUTOFRANGE);
7164 if (!mHasRange || !IsCandidateForConstraintValidation()) {
7165 return;
7167 bool outOfRange = GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW) ||
7168 GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW);
7169 AddStatesSilently(outOfRange ? ElementState::OUTOFRANGE
7170 : ElementState::INRANGE);
7173 void HTMLInputElement::UpdateHasRange(bool aNotify) {
7174 // There is a range if min/max applies for the type and if the element
7175 // currently have a valid min or max.
7176 const bool newHasRange = [&] {
7177 if (!DoesMinMaxApply()) {
7178 return false;
7180 return !GetMinimum().isNaN() || !GetMaximum().isNaN();
7181 }();
7183 if (newHasRange == mHasRange) {
7184 return;
7187 mHasRange = newHasRange;
7188 UpdateInRange(aNotify);
7191 void HTMLInputElement::PickerClosed() { mPickerRunning = false; }
7193 JSObject* HTMLInputElement::WrapNode(JSContext* aCx,
7194 JS::Handle<JSObject*> aGivenProto) {
7195 return HTMLInputElement_Binding::Wrap(aCx, this, aGivenProto);
7198 GetFilesHelper* HTMLInputElement::GetOrCreateGetFilesHelper(bool aRecursiveFlag,
7199 ErrorResult& aRv) {
7200 MOZ_ASSERT(mFileData);
7202 if (aRecursiveFlag) {
7203 if (!mFileData->mGetFilesRecursiveHelper) {
7204 mFileData->mGetFilesRecursiveHelper = GetFilesHelper::Create(
7205 GetFilesOrDirectoriesInternal(), aRecursiveFlag, aRv);
7206 if (NS_WARN_IF(aRv.Failed())) {
7207 return nullptr;
7211 return mFileData->mGetFilesRecursiveHelper;
7214 if (!mFileData->mGetFilesNonRecursiveHelper) {
7215 mFileData->mGetFilesNonRecursiveHelper = GetFilesHelper::Create(
7216 GetFilesOrDirectoriesInternal(), aRecursiveFlag, aRv);
7217 if (NS_WARN_IF(aRv.Failed())) {
7218 return nullptr;
7222 return mFileData->mGetFilesNonRecursiveHelper;
7225 void HTMLInputElement::UpdateEntries(
7226 const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories) {
7227 MOZ_ASSERT(mFileData && mFileData->mEntries.IsEmpty());
7229 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
7230 MOZ_ASSERT(global);
7232 RefPtr<FileSystem> fs = FileSystem::Create(global);
7233 if (NS_WARN_IF(!fs)) {
7234 return;
7237 Sequence<RefPtr<FileSystemEntry>> entries;
7238 for (uint32_t i = 0; i < aFilesOrDirectories.Length(); ++i) {
7239 RefPtr<FileSystemEntry> entry =
7240 FileSystemEntry::Create(global, aFilesOrDirectories[i], fs);
7241 MOZ_ASSERT(entry);
7243 if (!entries.AppendElement(entry, fallible)) {
7244 return;
7248 // The root fileSystem is a DirectoryEntry object that contains only the
7249 // dropped fileEntry and directoryEntry objects.
7250 fs->CreateRoot(entries);
7252 mFileData->mEntries = std::move(entries);
7255 void HTMLInputElement::GetWebkitEntries(
7256 nsTArray<RefPtr<FileSystemEntry>>& aSequence) {
7257 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
7258 return;
7261 Telemetry::Accumulate(Telemetry::BLINK_FILESYSTEM_USED, true);
7262 aSequence.AppendElements(mFileData->mEntries);
7265 already_AddRefed<nsINodeList> HTMLInputElement::GetLabels() {
7266 if (!IsLabelable()) {
7267 return nullptr;
7270 return nsGenericHTMLElement::Labels();
7273 void HTMLInputElement::MaybeFireInputPasswordRemoved() {
7274 // We want this event to be fired only when the password field is removed
7275 // from the DOM tree, not when it is released (ex, tab is closed). So don't
7276 // fire an event when the password input field doesn't have a docshell.
7277 Document* doc = GetComposedDoc();
7278 nsIDocShell* container = doc ? doc->GetDocShell() : nullptr;
7279 if (!container) {
7280 return;
7283 // Right now, only the password manager listens to the event and only listen
7284 // to it under certain circumstances. So don't fire this event unless
7285 // necessary.
7286 if (!doc->ShouldNotifyFormOrPasswordRemoved()) {
7287 return;
7290 AsyncEventDispatcher::RunDOMEventWhenSafe(
7291 *this, u"DOMInputPasswordRemoved"_ns, CanBubble::eNo,
7292 ChromeOnlyDispatch::eYes);
7295 } // namespace mozilla::dom
7297 #undef NS_ORIGINAL_CHECKED_VALUE