no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / dom / html / HTMLInputElement.cpp
blobd40c2a230c8c86f585935061d05e20b405c906fe
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/Maybe.h"
31 #include "mozilla/MouseEvents.h"
32 #include "mozilla/PresShell.h"
33 #include "mozilla/StaticPrefs_dom.h"
34 #include "mozilla/StaticPrefs_signon.h"
35 #include "mozilla/TextUtils.h"
36 #include "mozilla/Try.h"
37 #include "nsAttrValueInlines.h"
38 #include "nsCRTGlue.h"
39 #include "nsIFilePicker.h"
40 #include "nsNetUtil.h"
41 #include "nsQueryObject.h"
43 #include "HTMLDataListElement.h"
44 #include "HTMLFormSubmissionConstants.h"
45 #include "mozilla/Telemetry.h"
46 #include "nsBaseCommandController.h"
47 #include "nsIStringBundle.h"
48 #include "nsFocusManager.h"
49 #include "nsColorControlFrame.h"
50 #include "nsFileControlFrame.h"
51 #include "nsNumberControlFrame.h"
52 #include "nsSearchControlFrame.h"
53 #include "nsPIDOMWindow.h"
54 #include "nsRepeatService.h"
55 #include "mozilla/dom/ProgressEvent.h"
56 #include "nsGkAtoms.h"
57 #include "nsStyleConsts.h"
58 #include "nsPresContext.h"
59 #include "nsIFormControl.h"
60 #include "mozilla/dom/Document.h"
61 #include "mozilla/dom/HTMLDataListElement.h"
62 #include "mozilla/dom/HTMLOptionElement.h"
63 #include "nsIFrame.h"
64 #include "nsRangeFrame.h"
65 #include "nsError.h"
66 #include "nsIEditor.h"
67 #include "nsIPromptCollection.h"
69 #include "mozilla/PresState.h"
70 #include "nsLinebreakConverter.h" //to strip out carriage returns
71 #include "nsReadableUtils.h"
72 #include "nsUnicharUtils.h"
73 #include "nsLayoutUtils.h"
74 #include "nsVariant.h"
76 #include "mozilla/ContentEvents.h"
77 #include "mozilla/EventDispatcher.h"
78 #include "mozilla/MappedDeclarationsBuilder.h"
79 #include "mozilla/InternalMutationEvent.h"
80 #include "mozilla/TextControlState.h"
81 #include "mozilla/TextEditor.h"
82 #include "mozilla/TextEvents.h"
83 #include "mozilla/TouchEvents.h"
85 #include <algorithm>
87 // input type=radio
88 #include "mozilla/dom/RadioGroupContainer.h"
89 #include "nsIRadioVisitor.h"
90 #include "nsRadioVisitor.h"
92 // input type=file
93 #include "mozilla/dom/FileSystemEntry.h"
94 #include "mozilla/dom/FileSystem.h"
95 #include "mozilla/dom/File.h"
96 #include "mozilla/dom/FileList.h"
97 #include "nsIFile.h"
98 #include "nsDirectoryServiceDefs.h"
99 #include "nsIContentPrefService2.h"
100 #include "nsIMIMEService.h"
101 #include "nsIObserverService.h"
103 // input type=image
104 #include "nsImageLoadingContent.h"
105 #include "imgRequestProxy.h"
107 #include "mozAutoDocUpdate.h"
108 #include "nsContentCreatorFunctions.h"
109 #include "nsContentUtils.h"
110 #include "mozilla/dom/DirectionalityUtils.h"
112 #include "mozilla/LookAndFeel.h"
113 #include "mozilla/Preferences.h"
114 #include "mozilla/MathAlgorithms.h"
116 #include <limits>
118 #include "nsIColorPicker.h"
119 #include "nsIStringEnumerator.h"
120 #include "HTMLSplitOnSpacesTokenizer.h"
121 #include "nsIMIMEInfo.h"
122 #include "nsFrameSelection.h"
123 #include "nsXULControllers.h"
125 // input type=date
126 #include "js/Date.h"
128 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Input)
130 // XXX align=left, hspace, vspace, border? other nav4 attrs
132 namespace mozilla::dom {
134 // First bits are needed for the control type.
135 #define NS_OUTER_ACTIVATE_EVENT (1 << 9)
136 #define NS_ORIGINAL_CHECKED_VALUE (1 << 10)
137 // (1 << 11 is unused)
138 #define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
139 #define NS_PRE_HANDLE_BLUR_EVENT (1 << 13)
140 #define NS_IN_SUBMIT_CLICK (1 << 15)
141 #define NS_CONTROL_TYPE(bits) \
142 ((bits) & ~(NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | \
143 NS_ORIGINAL_INDETERMINATE_VALUE | NS_PRE_HANDLE_BLUR_EVENT | \
144 NS_IN_SUBMIT_CLICK))
146 // whether textfields should be selected once focused:
147 // -1: no, 1: yes, 0: uninitialized
148 static int32_t gSelectTextFieldOnFocus;
149 UploadLastDir* HTMLInputElement::gUploadLastDir;
151 static const nsAttrValue::EnumTable kInputTypeTable[] = {
152 {"button", FormControlType::InputButton},
153 {"checkbox", FormControlType::InputCheckbox},
154 {"color", FormControlType::InputColor},
155 {"date", FormControlType::InputDate},
156 {"datetime-local", FormControlType::InputDatetimeLocal},
157 {"email", FormControlType::InputEmail},
158 {"file", FormControlType::InputFile},
159 {"hidden", FormControlType::InputHidden},
160 {"reset", FormControlType::InputReset},
161 {"image", FormControlType::InputImage},
162 {"month", FormControlType::InputMonth},
163 {"number", FormControlType::InputNumber},
164 {"password", FormControlType::InputPassword},
165 {"radio", FormControlType::InputRadio},
166 {"range", FormControlType::InputRange},
167 {"search", FormControlType::InputSearch},
168 {"submit", FormControlType::InputSubmit},
169 {"tel", FormControlType::InputTel},
170 {"time", FormControlType::InputTime},
171 {"url", FormControlType::InputUrl},
172 {"week", FormControlType::InputWeek},
173 // "text" must be last for ParseAttribute to work right. If you add things
174 // before it, please update kInputDefaultType.
175 {"text", FormControlType::InputText},
176 {nullptr, 0}};
178 // Default type is 'text'.
179 static const nsAttrValue::EnumTable* kInputDefaultType =
180 &kInputTypeTable[ArrayLength(kInputTypeTable) - 2];
182 static const nsAttrValue::EnumTable kCaptureTable[] = {
183 {"user", nsIFilePicker::captureUser},
184 {"environment", nsIFilePicker::captureEnv},
185 {"", nsIFilePicker::captureDefault},
186 {nullptr, nsIFilePicker::captureNone}};
188 static const nsAttrValue::EnumTable* kCaptureDefault = &kCaptureTable[2];
190 using namespace blink;
192 constexpr Decimal HTMLInputElement::kStepScaleFactorDate(86400000_d);
193 constexpr Decimal HTMLInputElement::kStepScaleFactorNumberRange(1_d);
194 constexpr Decimal HTMLInputElement::kStepScaleFactorTime(1000_d);
195 constexpr Decimal HTMLInputElement::kStepScaleFactorMonth(1_d);
196 constexpr Decimal HTMLInputElement::kStepScaleFactorWeek(7 * 86400000_d);
197 constexpr Decimal HTMLInputElement::kDefaultStepBase(0_d);
198 constexpr Decimal HTMLInputElement::kDefaultStepBaseWeek(-259200000_d);
199 constexpr Decimal HTMLInputElement::kDefaultStep(1_d);
200 constexpr Decimal HTMLInputElement::kDefaultStepTime(60_d);
201 constexpr Decimal HTMLInputElement::kStepAny(0_d);
203 const double HTMLInputElement::kMinimumYear = 1;
204 const double HTMLInputElement::kMaximumYear = 275760;
205 const double HTMLInputElement::kMaximumWeekInMaximumYear = 37;
206 const double HTMLInputElement::kMaximumDayInMaximumYear = 13;
207 const double HTMLInputElement::kMaximumMonthInMaximumYear = 9;
208 const double HTMLInputElement::kMaximumWeekInYear = 53;
209 const double HTMLInputElement::kMsPerDay = 24 * 60 * 60 * 1000;
211 // An helper class for the dispatching of the 'change' event.
212 // This class is used when the FilePicker finished its task (or when files and
213 // directories are set by some chrome/test only method).
214 // The task of this class is to postpone the dispatching of 'change' and 'input'
215 // events at the end of the exploration of the directories.
216 class DispatchChangeEventCallback final : public GetFilesCallback {
217 public:
218 explicit DispatchChangeEventCallback(HTMLInputElement* aInputElement)
219 : mInputElement(aInputElement) {
220 MOZ_ASSERT(aInputElement);
223 virtual void Callback(
224 nsresult aStatus,
225 const FallibleTArray<RefPtr<BlobImpl>>& aBlobImpls) override {
226 if (!mInputElement->GetOwnerGlobal()) {
227 return;
230 nsTArray<OwningFileOrDirectory> array;
231 for (uint32_t i = 0; i < aBlobImpls.Length(); ++i) {
232 OwningFileOrDirectory* element = array.AppendElement();
233 RefPtr<File> file =
234 File::Create(mInputElement->GetOwnerGlobal(), aBlobImpls[i]);
235 if (NS_WARN_IF(!file)) {
236 return;
239 element->SetAsFile() = file;
242 mInputElement->SetFilesOrDirectories(array, true);
243 Unused << NS_WARN_IF(NS_FAILED(DispatchEvents()));
246 MOZ_CAN_RUN_SCRIPT_BOUNDARY
247 nsresult DispatchEvents() {
248 RefPtr<HTMLInputElement> inputElement(mInputElement);
249 nsresult rv = nsContentUtils::DispatchInputEvent(inputElement);
250 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch input event");
251 mInputElement->SetUserInteracted(true);
252 rv = nsContentUtils::DispatchTrustedEvent(mInputElement->OwnerDoc(),
253 mInputElement, u"change"_ns,
254 CanBubble::eYes, Cancelable::eNo);
256 return rv;
259 private:
260 RefPtr<HTMLInputElement> mInputElement;
263 struct HTMLInputElement::FileData {
265 * The value of the input if it is a file input. This is the list of files or
266 * directories DOM objects used when uploading a file. It is vital that this
267 * is kept separate from mValue so that it won't be possible to 'leak' the
268 * value from a text-input to a file-input. Additionally, the logic for this
269 * value is kept as simple as possible to avoid accidental errors where the
270 * wrong filename is used. Therefor the list of filenames is always owned by
271 * this member, never by the frame. Whenever the frame wants to change the
272 * filename it has to call SetFilesOrDirectories to update this member.
274 nsTArray<OwningFileOrDirectory> mFilesOrDirectories;
276 RefPtr<GetFilesHelper> mGetFilesRecursiveHelper;
277 RefPtr<GetFilesHelper> mGetFilesNonRecursiveHelper;
280 * Hack for bug 1086684: Stash the .value when we're a file picker.
282 nsString mFirstFilePath;
284 RefPtr<FileList> mFileList;
285 Sequence<RefPtr<FileSystemEntry>> mEntries;
287 nsString mStaticDocFileList;
289 void ClearGetFilesHelpers() {
290 if (mGetFilesRecursiveHelper) {
291 mGetFilesRecursiveHelper->Unlink();
292 mGetFilesRecursiveHelper = nullptr;
295 if (mGetFilesNonRecursiveHelper) {
296 mGetFilesNonRecursiveHelper->Unlink();
297 mGetFilesNonRecursiveHelper = nullptr;
301 // Cycle Collection support.
302 void Traverse(nsCycleCollectionTraversalCallback& cb) {
303 FileData* tmp = this;
304 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFilesOrDirectories)
305 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFileList)
306 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEntries)
307 if (mGetFilesRecursiveHelper) {
308 mGetFilesRecursiveHelper->Traverse(cb);
311 if (mGetFilesNonRecursiveHelper) {
312 mGetFilesNonRecursiveHelper->Traverse(cb);
316 void Unlink() {
317 FileData* tmp = this;
318 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFilesOrDirectories)
319 NS_IMPL_CYCLE_COLLECTION_UNLINK(mFileList)
320 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEntries)
321 ClearGetFilesHelpers();
325 HTMLInputElement::nsFilePickerShownCallback::nsFilePickerShownCallback(
326 HTMLInputElement* aInput, nsIFilePicker* aFilePicker)
327 : mFilePicker(aFilePicker), mInput(aInput) {}
329 NS_IMPL_ISUPPORTS(UploadLastDir::ContentPrefCallback, nsIContentPrefCallback2)
331 NS_IMETHODIMP
332 UploadLastDir::ContentPrefCallback::HandleCompletion(uint16_t aReason) {
333 nsCOMPtr<nsIFile> localFile;
334 nsAutoString prefStr;
336 if (aReason == nsIContentPrefCallback2::COMPLETE_ERROR || !mResult) {
337 Preferences::GetString("dom.input.fallbackUploadDir", prefStr);
340 if (prefStr.IsEmpty() && mResult) {
341 nsCOMPtr<nsIVariant> pref;
342 mResult->GetValue(getter_AddRefs(pref));
343 pref->GetAsAString(prefStr);
346 if (!prefStr.IsEmpty()) {
347 localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
348 if (localFile && NS_WARN_IF(NS_FAILED(localFile->InitWithPath(prefStr)))) {
349 localFile = nullptr;
353 if (localFile) {
354 mFilePicker->SetDisplayDirectory(localFile);
355 } else {
356 // If no custom directory was set through the pref, default to
357 // "desktop" directory for each platform.
358 mFilePicker->SetDisplaySpecialDirectory(
359 NS_LITERAL_STRING_FROM_CSTRING(NS_OS_DESKTOP_DIR));
362 mFilePicker->Open(mFpCallback);
363 return NS_OK;
366 NS_IMETHODIMP
367 UploadLastDir::ContentPrefCallback::HandleResult(nsIContentPref* pref) {
368 mResult = pref;
369 return NS_OK;
372 NS_IMETHODIMP
373 UploadLastDir::ContentPrefCallback::HandleError(nsresult error) {
374 // HandleCompletion is always called (even with HandleError was called),
375 // so we don't need to do anything special here.
376 return NS_OK;
379 namespace {
382 * This may return nullptr if the DOM File's implementation of
383 * File::mozFullPathInternal does not successfully return a non-empty
384 * string that is a valid path. This can happen on Firefox OS, for example,
385 * where the file picker can create Blobs.
387 static already_AddRefed<nsIFile> LastUsedDirectory(
388 const OwningFileOrDirectory& aData) {
389 if (aData.IsFile()) {
390 nsAutoString path;
391 ErrorResult error;
392 aData.GetAsFile()->GetMozFullPathInternal(path, error);
393 if (error.Failed() || path.IsEmpty()) {
394 error.SuppressException();
395 return nullptr;
398 nsCOMPtr<nsIFile> localFile;
399 nsresult rv = NS_NewLocalFile(path, true, getter_AddRefs(localFile));
400 if (NS_WARN_IF(NS_FAILED(rv))) {
401 return nullptr;
404 nsCOMPtr<nsIFile> parentFile;
405 rv = localFile->GetParent(getter_AddRefs(parentFile));
406 if (NS_WARN_IF(NS_FAILED(rv))) {
407 return nullptr;
410 return parentFile.forget();
413 MOZ_ASSERT(aData.IsDirectory());
415 nsCOMPtr<nsIFile> localFile = aData.GetAsDirectory()->GetInternalNsIFile();
416 MOZ_ASSERT(localFile);
418 return localFile.forget();
421 void GetDOMFileOrDirectoryName(const OwningFileOrDirectory& aData,
422 nsAString& aName) {
423 if (aData.IsFile()) {
424 aData.GetAsFile()->GetName(aName);
425 } else {
426 MOZ_ASSERT(aData.IsDirectory());
427 ErrorResult rv;
428 aData.GetAsDirectory()->GetName(aName, rv);
429 if (NS_WARN_IF(rv.Failed())) {
430 rv.SuppressException();
435 void GetDOMFileOrDirectoryPath(const OwningFileOrDirectory& aData,
436 nsAString& aPath, ErrorResult& aRv) {
437 if (aData.IsFile()) {
438 aData.GetAsFile()->GetMozFullPathInternal(aPath, aRv);
439 } else {
440 MOZ_ASSERT(aData.IsDirectory());
441 aData.GetAsDirectory()->GetFullRealPath(aPath);
445 } // namespace
447 NS_IMETHODIMP
448 HTMLInputElement::nsFilePickerShownCallback::Done(
449 nsIFilePicker::ResultCode aResult) {
450 mInput->PickerClosed();
452 if (aResult == nsIFilePicker::returnCancel) {
453 RefPtr<HTMLInputElement> inputElement(mInput);
454 return nsContentUtils::DispatchTrustedEvent(
455 inputElement->OwnerDoc(), inputElement, u"cancel"_ns, CanBubble::eYes,
456 Cancelable::eNo);
459 mInput->OwnerDoc()->NotifyUserGestureActivation();
461 nsIFilePicker::Mode mode;
462 mFilePicker->GetMode(&mode);
464 // Collect new selected filenames
465 nsTArray<OwningFileOrDirectory> newFilesOrDirectories;
466 if (mode == nsIFilePicker::modeOpenMultiple) {
467 nsCOMPtr<nsISimpleEnumerator> iter;
468 nsresult rv =
469 mFilePicker->GetDomFileOrDirectoryEnumerator(getter_AddRefs(iter));
470 NS_ENSURE_SUCCESS(rv, rv);
472 if (!iter) {
473 return NS_OK;
476 nsCOMPtr<nsISupports> tmp;
477 bool hasMore = true;
479 while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
480 iter->GetNext(getter_AddRefs(tmp));
481 RefPtr<Blob> domBlob = do_QueryObject(tmp);
482 MOZ_ASSERT(domBlob,
483 "Null file object from FilePicker's file enumerator?");
484 if (!domBlob) {
485 continue;
488 OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
489 element->SetAsFile() = domBlob->ToFile();
491 } else {
492 MOZ_ASSERT(mode == nsIFilePicker::modeOpen ||
493 mode == nsIFilePicker::modeGetFolder);
494 nsCOMPtr<nsISupports> tmp;
495 nsresult rv = mFilePicker->GetDomFileOrDirectory(getter_AddRefs(tmp));
496 NS_ENSURE_SUCCESS(rv, rv);
498 if (!tmp) {
499 return NS_OK;
502 // Show a prompt to get user confirmation before allowing folder access.
503 // This is to prevent sites from tricking the user into uploading files.
504 // See Bug 1338637.
505 if (mode == nsIFilePicker::modeGetFolder) {
506 nsCOMPtr<nsIPromptCollection> prompter =
507 do_GetService("@mozilla.org/embedcomp/prompt-collection;1");
508 if (!prompter) {
509 return NS_ERROR_NOT_AVAILABLE;
512 bool confirmed = false;
513 BrowsingContext* bc = mInput->OwnerDoc()->GetBrowsingContext();
515 // Get directory name
516 RefPtr<Directory> directory = static_cast<Directory*>(tmp.get());
517 nsAutoString directoryName;
518 ErrorResult error;
519 directory->GetName(directoryName, error);
520 if (NS_WARN_IF(error.Failed())) {
521 return error.StealNSResult();
524 rv = prompter->ConfirmFolderUpload(bc, directoryName, &confirmed);
525 NS_ENSURE_SUCCESS(rv, rv);
526 if (!confirmed) {
527 // User aborted upload
528 return NS_OK;
532 RefPtr<Blob> blob = do_QueryObject(tmp);
533 if (blob) {
534 RefPtr<File> file = blob->ToFile();
535 MOZ_ASSERT(file);
537 OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
538 element->SetAsFile() = file;
539 } else if (tmp) {
540 RefPtr<Directory> directory = static_cast<Directory*>(tmp.get());
541 OwningFileOrDirectory* element = newFilesOrDirectories.AppendElement();
542 element->SetAsDirectory() = directory;
546 if (newFilesOrDirectories.IsEmpty()) {
547 return NS_OK;
550 // Store the last used directory using the content pref service:
551 nsCOMPtr<nsIFile> lastUsedDir = LastUsedDirectory(newFilesOrDirectories[0]);
553 if (lastUsedDir) {
554 HTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(mInput->OwnerDoc(),
555 lastUsedDir);
558 // The text control frame (if there is one) isn't going to send a change
559 // event because it will think this is done by a script.
560 // So, we can safely send one by ourself.
561 mInput->SetFilesOrDirectories(newFilesOrDirectories, true);
563 // mInput(HTMLInputElement) has no scriptGlobalObject, don't create
564 // DispatchChangeEventCallback
565 if (!mInput->GetOwnerGlobal()) {
566 return NS_OK;
568 RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
569 new DispatchChangeEventCallback(mInput);
571 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
572 mInput->HasAttr(nsGkAtoms::webkitdirectory)) {
573 ErrorResult error;
574 GetFilesHelper* helper = mInput->GetOrCreateGetFilesHelper(true, error);
575 if (NS_WARN_IF(error.Failed())) {
576 return error.StealNSResult();
579 helper->AddCallback(dispatchChangeEventCallback);
580 return NS_OK;
583 return dispatchChangeEventCallback->DispatchEvents();
586 NS_IMPL_ISUPPORTS(HTMLInputElement::nsFilePickerShownCallback,
587 nsIFilePickerShownCallback)
589 class nsColorPickerShownCallback final : public nsIColorPickerShownCallback {
590 ~nsColorPickerShownCallback() = default;
592 public:
593 nsColorPickerShownCallback(HTMLInputElement* aInput,
594 nsIColorPicker* aColorPicker)
595 : mInput(aInput), mColorPicker(aColorPicker), mValueChanged(false) {}
597 NS_DECL_ISUPPORTS
599 MOZ_CAN_RUN_SCRIPT_BOUNDARY
600 NS_IMETHOD Update(const nsAString& aColor) override;
601 MOZ_CAN_RUN_SCRIPT_BOUNDARY
602 NS_IMETHOD Done(const nsAString& aColor) override;
604 private:
606 * Updates the internals of the object using aColor as the new value.
607 * If aTrustedUpdate is true, it will consider that aColor is a new value.
608 * Otherwise, it will check that aColor is different from the current value.
610 MOZ_CAN_RUN_SCRIPT
611 nsresult UpdateInternal(const nsAString& aColor, bool aTrustedUpdate);
613 RefPtr<HTMLInputElement> mInput;
614 nsCOMPtr<nsIColorPicker> mColorPicker;
615 bool mValueChanged;
618 nsresult nsColorPickerShownCallback::UpdateInternal(const nsAString& aColor,
619 bool aTrustedUpdate) {
620 bool valueChanged = false;
621 nsAutoString oldValue;
622 if (aTrustedUpdate) {
623 mInput->OwnerDoc()->NotifyUserGestureActivation();
624 valueChanged = true;
625 } else {
626 mInput->GetValue(oldValue, CallerType::System);
629 mInput->SetValue(aColor, CallerType::System, IgnoreErrors());
631 if (!aTrustedUpdate) {
632 nsAutoString newValue;
633 mInput->GetValue(newValue, CallerType::System);
634 if (!oldValue.Equals(newValue)) {
635 valueChanged = true;
639 if (!valueChanged) {
640 return NS_OK;
643 mValueChanged = true;
644 RefPtr<HTMLInputElement> input(mInput);
645 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(input);
646 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
647 "Failed to dispatch input event");
648 return NS_OK;
651 NS_IMETHODIMP
652 nsColorPickerShownCallback::Update(const nsAString& aColor) {
653 return UpdateInternal(aColor, true);
656 NS_IMETHODIMP
657 nsColorPickerShownCallback::Done(const nsAString& aColor) {
659 * When Done() is called, we might be at the end of a serie of Update() calls
660 * in which case mValueChanged is set to true and a change event will have to
661 * be fired but we might also be in a one shot Done() call situation in which
662 * case we should fire a change event iif the value actually changed.
663 * UpdateInternal(bool) is taking care of that logic for us.
665 nsresult rv = NS_OK;
667 mInput->PickerClosed();
669 if (!aColor.IsEmpty()) {
670 UpdateInternal(aColor, false);
673 if (mValueChanged) {
674 mInput->SetUserInteracted(true);
675 rv = nsContentUtils::DispatchTrustedEvent(
676 mInput->OwnerDoc(), static_cast<Element*>(mInput.get()), u"change"_ns,
677 CanBubble::eYes, Cancelable::eNo);
680 return rv;
683 NS_IMPL_ISUPPORTS(nsColorPickerShownCallback, nsIColorPickerShownCallback)
685 static bool IsPickerBlocked(Document* aDoc) {
686 if (aDoc->ConsumeTransientUserGestureActivation()) {
687 return false;
690 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, aDoc,
691 nsContentUtils::eDOM_PROPERTIES,
692 "InputPickerBlockedNoUserActivation");
693 return true;
696 nsTArray<nsString> HTMLInputElement::GetColorsFromList() {
697 RefPtr<HTMLDataListElement> dataList = GetList();
698 if (!dataList) {
699 return {};
702 nsTArray<nsString> colors;
704 RefPtr<nsContentList> options = dataList->Options();
705 uint32_t length = options->Length(true);
706 for (uint32_t i = 0; i < length; ++i) {
707 auto* option = HTMLOptionElement::FromNodeOrNull(options->Item(i, false));
708 if (!option) {
709 continue;
712 nsString value;
713 option->GetValue(value);
714 if (IsValidSimpleColor(value)) {
715 ToLowerCase(value);
716 colors.AppendElement(value);
720 return colors;
723 nsresult HTMLInputElement::InitColorPicker() {
724 MOZ_ASSERT(IsMutable());
726 if (mPickerRunning) {
727 NS_WARNING("Just one nsIColorPicker is allowed");
728 return NS_ERROR_FAILURE;
731 nsCOMPtr<Document> doc = OwnerDoc();
733 nsCOMPtr<nsPIDOMWindowOuter> win = doc->GetWindow();
734 if (!win) {
735 return NS_ERROR_FAILURE;
738 if (IsPickerBlocked(doc)) {
739 return NS_OK;
742 // Get Loc title
743 nsAutoString title;
744 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
745 "ColorPicker", title);
747 nsCOMPtr<nsIColorPicker> colorPicker =
748 do_CreateInstance("@mozilla.org/colorpicker;1");
749 if (!colorPicker) {
750 return NS_ERROR_FAILURE;
753 nsAutoString initialValue;
754 GetNonFileValueInternal(initialValue);
755 nsTArray<nsString> colors = GetColorsFromList();
756 nsresult rv = colorPicker->Init(win, title, initialValue, colors);
757 NS_ENSURE_SUCCESS(rv, rv);
759 nsCOMPtr<nsIColorPickerShownCallback> callback =
760 new nsColorPickerShownCallback(this, colorPicker);
762 rv = colorPicker->Open(callback);
763 if (NS_SUCCEEDED(rv)) {
764 mPickerRunning = true;
767 return rv;
770 nsresult HTMLInputElement::InitFilePicker(FilePickerType aType) {
771 MOZ_ASSERT(IsMutable());
773 if (mPickerRunning) {
774 NS_WARNING("Just one nsIFilePicker is allowed");
775 return NS_ERROR_FAILURE;
778 // Get parent nsPIDOMWindow object.
779 nsCOMPtr<Document> doc = OwnerDoc();
781 RefPtr<BrowsingContext> bc = doc->GetBrowsingContext();
782 if (!bc) {
783 return NS_ERROR_FAILURE;
786 if (IsPickerBlocked(doc)) {
787 return NS_OK;
790 // Get Loc title
791 nsAutoString title;
792 nsAutoString okButtonLabel;
793 if (aType == FILE_PICKER_DIRECTORY) {
794 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
795 "DirectoryUpload", doc, title);
797 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
798 "DirectoryPickerOkButtonLabel", doc,
799 okButtonLabel);
800 } else {
801 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
802 "FileUpload", doc, title);
805 nsCOMPtr<nsIFilePicker> filePicker =
806 do_CreateInstance("@mozilla.org/filepicker;1");
807 if (!filePicker) return NS_ERROR_FAILURE;
809 nsIFilePicker::Mode mode;
811 if (aType == FILE_PICKER_DIRECTORY) {
812 mode = nsIFilePicker::modeGetFolder;
813 } else if (HasAttr(nsGkAtoms::multiple)) {
814 mode = nsIFilePicker::modeOpenMultiple;
815 } else {
816 mode = nsIFilePicker::modeOpen;
819 nsresult rv = filePicker->Init(bc, title, mode);
820 NS_ENSURE_SUCCESS(rv, rv);
822 if (!okButtonLabel.IsEmpty()) {
823 filePicker->SetOkButtonLabel(okButtonLabel);
826 // Native directory pickers ignore file type filters, so we don't spend
827 // cycles adding them for FILE_PICKER_DIRECTORY.
828 if (HasAttr(nsGkAtoms::accept) && aType != FILE_PICKER_DIRECTORY) {
829 SetFilePickerFiltersFromAccept(filePicker);
831 if (StaticPrefs::dom_capture_enabled()) {
832 if (const nsAttrValue* captureVal = GetParsedAttr(nsGkAtoms::capture)) {
833 filePicker->SetCapture(static_cast<nsIFilePicker::CaptureTarget>(
834 captureVal->GetEnumValue()));
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 mUserInteracted(false),
998 mLastValueChangeWasInteractive(false),
999 mCheckedChanged(false),
1000 mChecked(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 mHasRange(false),
1009 mIsDraggingRange(false),
1010 mNumberControlSpinnerIsSpinning(false),
1011 mNumberControlSpinnerSpinsUp(false),
1012 mPickerRunning(false),
1013 mIsPreviewEnabled(false),
1014 mHasBeenTypePassword(false),
1015 mHasPatternAttribute(false),
1016 mRadioGroupContainer(nullptr) {
1017 // If size is above 512, mozjemalloc allocates 1kB, see
1018 // memory/build/mozjemalloc.cpp
1019 static_assert(sizeof(HTMLInputElement) <= 512,
1020 "Keep the size of HTMLInputElement under 512 to avoid "
1021 "performance regression!");
1023 // We are in a type=text but we create TextControlState lazily.
1024 mInputData.mState = nullptr;
1026 void* memory = mInputTypeMem;
1027 mInputType = InputType::Create(this, mType, memory);
1029 if (!gUploadLastDir) HTMLInputElement::InitUploadLastDir();
1031 // Set up our default state. By default we're enabled (since we're a control
1032 // type that can be disabled but not actually disabled right now), optional,
1033 // read-write, and valid. Also by default we don't have to show validity UI
1034 // and so forth.
1035 AddStatesSilently(ElementState::ENABLED | ElementState::OPTIONAL_ |
1036 ElementState::VALID | ElementState::VALUE_EMPTY |
1037 ElementState::READWRITE);
1038 RemoveStatesSilently(ElementState::READONLY);
1039 UpdateApzAwareFlag();
1042 HTMLInputElement::~HTMLInputElement() {
1043 if (mNumberControlSpinnerIsSpinning) {
1044 StopNumberControlSpinnerSpin(eDisallowDispatchingEvents);
1046 nsImageLoadingContent::Destroy();
1047 FreeData();
1050 void HTMLInputElement::FreeData() {
1051 if (!IsSingleLineTextControl(false)) {
1052 free(mInputData.mValue);
1053 mInputData.mValue = nullptr;
1054 } else if (mInputData.mState) {
1055 // XXX Passing nullptr to UnbindFromFrame doesn't do anything!
1056 UnbindFromFrame(nullptr);
1057 mInputData.mState->Destroy();
1058 mInputData.mState = nullptr;
1061 if (mInputType) {
1062 mInputType->DropReference();
1063 mInputType = nullptr;
1067 void HTMLInputElement::EnsureEditorState() {
1068 MOZ_ASSERT(IsSingleLineTextControl(false));
1069 if (!mInputData.mState) {
1070 mInputData.mState = TextControlState::Construct(this);
1074 TextControlState* HTMLInputElement::GetEditorState() const {
1075 if (!IsSingleLineTextControl(false)) {
1076 return nullptr;
1079 // We've postponed allocating TextControlState, doing that in a const
1080 // method is fine.
1081 const_cast<HTMLInputElement*>(this)->EnsureEditorState();
1083 MOZ_ASSERT(mInputData.mState,
1084 "Single line text controls need to have a state"
1085 " associated with them");
1087 return mInputData.mState;
1090 // nsISupports
1092 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLInputElement)
1094 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLInputElement,
1095 TextControlElement)
1096 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mValidity)
1097 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
1098 if (tmp->IsSingleLineTextControl(false) && tmp->mInputData.mState) {
1099 tmp->mInputData.mState->Traverse(cb);
1102 if (tmp->mFileData) {
1103 tmp->mFileData->Traverse(cb);
1105 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1107 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLInputElement,
1108 TextControlElement)
1109 NS_IMPL_CYCLE_COLLECTION_UNLINK(mValidity)
1110 NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
1111 if (tmp->IsSingleLineTextControl(false) && tmp->mInputData.mState) {
1112 tmp->mInputData.mState->Unlink();
1115 if (tmp->mFileData) {
1116 tmp->mFileData->Unlink();
1118 // XXX should unlink more?
1119 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1121 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(HTMLInputElement,
1122 TextControlElement,
1123 imgINotificationObserver,
1124 nsIImageLoadingContent,
1125 nsIConstraintValidation)
1127 // nsINode
1129 nsresult HTMLInputElement::Clone(dom::NodeInfo* aNodeInfo,
1130 nsINode** aResult) const {
1131 *aResult = nullptr;
1133 RefPtr<HTMLInputElement> it = new (aNodeInfo->NodeInfoManager())
1134 HTMLInputElement(do_AddRef(aNodeInfo), NOT_FROM_PARSER, FromClone::Yes);
1136 nsresult rv = const_cast<HTMLInputElement*>(this)->CopyInnerTo(it);
1137 NS_ENSURE_SUCCESS(rv, rv);
1139 switch (GetValueMode()) {
1140 case VALUE_MODE_VALUE:
1141 if (mValueChanged) {
1142 // We don't have our default value anymore. Set our value on
1143 // the clone.
1144 nsAutoString value;
1145 GetNonFileValueInternal(value);
1146 // SetValueInternal handles setting the VALUE_CHANGED bit for us
1147 if (NS_WARN_IF(
1148 NS_FAILED(rv = it->SetValueInternal(
1149 value, {ValueSetterOption::SetValueChanged})))) {
1150 return rv;
1153 break;
1154 case VALUE_MODE_FILENAME:
1155 if (it->OwnerDoc()->IsStaticDocument()) {
1156 // We're going to be used in print preview. Since the doc is static
1157 // we can just grab the pretty string and use it as wallpaper
1158 GetDisplayFileName(it->mFileData->mStaticDocFileList);
1159 } else {
1160 it->mFileData->ClearGetFilesHelpers();
1161 it->mFileData->mFilesOrDirectories.Clear();
1162 it->mFileData->mFilesOrDirectories.AppendElements(
1163 mFileData->mFilesOrDirectories);
1165 break;
1166 case VALUE_MODE_DEFAULT_ON:
1167 case VALUE_MODE_DEFAULT:
1168 break;
1171 if (mCheckedChanged) {
1172 // We no longer have our original checked state. Set our
1173 // checked state on the clone.
1174 it->DoSetChecked(mChecked, false, true);
1175 // Then tell DoneCreatingElement() not to overwrite:
1176 it->mShouldInitChecked = false;
1179 it->mIndeterminate = mIndeterminate;
1181 it->DoneCreatingElement();
1183 it->SetLastValueChangeWasInteractive(mLastValueChangeWasInteractive);
1184 it.forget(aResult);
1185 return NS_OK;
1188 void HTMLInputElement::BeforeSetAttr(int32_t aNameSpaceID, nsAtom* aName,
1189 const nsAttrValue* aValue, bool aNotify) {
1190 if (aNameSpaceID == kNameSpaceID_None) {
1191 if (aNotify && aName == nsGkAtoms::disabled) {
1192 mDisabledChanged = true;
1195 // When name or type changes, radio should be removed from radio group.
1196 // If we are not done creating the radio, we also should not do it.
1197 if (mType == FormControlType::InputRadio) {
1198 if ((aName == nsGkAtoms::name || (aName == nsGkAtoms::type && !mForm)) &&
1199 (mForm || mDoneCreating)) {
1200 RemoveFromRadioGroup();
1201 } else if (aName == nsGkAtoms::required) {
1202 auto* container = GetCurrentRadioGroupContainer();
1204 if (container && ((aValue && !HasAttr(aNameSpaceID, aName)) ||
1205 (!aValue && HasAttr(aNameSpaceID, aName)))) {
1206 nsAutoString name;
1207 GetAttr(nsGkAtoms::name, name);
1208 container->RadioRequiredWillChange(name, !!aValue);
1213 if (aName == nsGkAtoms::webkitdirectory) {
1214 Telemetry::Accumulate(Telemetry::WEBKIT_DIRECTORY_USED, true);
1218 return nsGenericHTMLFormControlElementWithState::BeforeSetAttr(
1219 aNameSpaceID, aName, aValue, aNotify);
1222 void HTMLInputElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
1223 const nsAttrValue* aValue,
1224 const nsAttrValue* aOldValue,
1225 nsIPrincipal* aSubjectPrincipal,
1226 bool aNotify) {
1227 if (aNameSpaceID == kNameSpaceID_None) {
1228 bool needValidityUpdate = false;
1229 if (aName == nsGkAtoms::src) {
1230 mSrcTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal(
1231 this, aValue ? aValue->GetStringValue() : EmptyString(),
1232 aSubjectPrincipal);
1233 if (aNotify && mType == FormControlType::InputImage) {
1234 if (aValue) {
1235 // Mark channel as urgent-start before load image if the image load is
1236 // initiated by a user interaction.
1237 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
1239 LoadImage(aValue->GetStringValue(), true, aNotify,
1240 eImageLoadType_Normal, mSrcTriggeringPrincipal);
1241 } else {
1242 // Null value means the attr got unset; drop the image
1243 CancelImageRequests(aNotify);
1248 if (aName == nsGkAtoms::value) {
1249 // If the element has a value in value mode, the value content attribute
1250 // is the default value. So if the elements value didn't change from the
1251 // default, we have to re-set it.
1252 if (!mValueChanged && GetValueMode() == VALUE_MODE_VALUE) {
1253 SetDefaultValueAsValue();
1254 } else if (GetValueMode() == VALUE_MODE_DEFAULT) {
1255 ResetDirFormAssociatedElement(this, aNotify, HasDirAuto());
1257 // GetStepBase() depends on the `value` attribute if `min` is not present,
1258 // even if the value doesn't change.
1259 UpdateStepMismatchValidityState();
1260 needValidityUpdate = true;
1263 // Checked must be set no matter what type of control it is, since
1264 // mChecked must reflect the new value
1265 if (aName == nsGkAtoms::checked) {
1266 if (IsRadioOrCheckbox()) {
1267 SetStates(ElementState::DEFAULT, !!aValue, aNotify);
1269 if (!mCheckedChanged) {
1270 // Delay setting checked if we are creating this element (wait
1271 // until everything is set)
1272 if (!mDoneCreating) {
1273 mShouldInitChecked = true;
1274 } else {
1275 DoSetChecked(!!aValue, aNotify, false);
1278 needValidityUpdate = true;
1281 if (aName == nsGkAtoms::type) {
1282 FormControlType newType;
1283 if (!aValue) {
1284 // We're now a text input.
1285 newType = FormControlType(kInputDefaultType->value);
1286 } else {
1287 newType = FormControlType(aValue->GetEnumValue());
1289 if (newType != mType) {
1290 HandleTypeChange(newType, aNotify);
1291 needValidityUpdate = true;
1295 // When name or type changes, radio should be added to radio group.
1296 // If we are not done creating the radio, we also should not do it.
1297 if ((aName == nsGkAtoms::name || (aName == nsGkAtoms::type && !mForm)) &&
1298 mType == FormControlType::InputRadio && (mForm || mDoneCreating)) {
1299 AddToRadioGroup();
1300 UpdateValueMissingValidityStateForRadio(false);
1301 needValidityUpdate = true;
1304 if (aName == nsGkAtoms::required || aName == nsGkAtoms::disabled ||
1305 aName == nsGkAtoms::readonly) {
1306 if (aName == nsGkAtoms::disabled) {
1307 // This *has* to be called *before* validity state check because
1308 // UpdateBarredFromConstraintValidation and
1309 // UpdateValueMissingValidityState depend on our disabled state.
1310 UpdateDisabledState(aNotify);
1313 if (aName == nsGkAtoms::required && DoesRequiredApply()) {
1314 // This *has* to be called *before* UpdateValueMissingValidityState
1315 // because UpdateValueMissingValidityState depends on our required
1316 // state.
1317 UpdateRequiredState(!!aValue, aNotify);
1320 if (aName == nsGkAtoms::readonly && !!aValue != !!aOldValue) {
1321 UpdateReadOnlyState(aNotify);
1324 UpdateValueMissingValidityState();
1326 // This *has* to be called *after* validity has changed.
1327 if (aName == nsGkAtoms::readonly || aName == nsGkAtoms::disabled) {
1328 UpdateBarredFromConstraintValidation();
1330 needValidityUpdate = true;
1331 } else if (aName == nsGkAtoms::maxlength) {
1332 UpdateTooLongValidityState();
1333 needValidityUpdate = true;
1334 } else if (aName == nsGkAtoms::minlength) {
1335 UpdateTooShortValidityState();
1336 needValidityUpdate = true;
1337 } else if (aName == nsGkAtoms::pattern) {
1338 // Although pattern attribute only applies to single line text controls,
1339 // we set this flag for all input types to save having to check the type
1340 // here.
1341 mHasPatternAttribute = !!aValue;
1343 if (mDoneCreating) {
1344 UpdatePatternMismatchValidityState();
1346 needValidityUpdate = true;
1347 } else if (aName == nsGkAtoms::multiple) {
1348 UpdateTypeMismatchValidityState();
1349 needValidityUpdate = true;
1350 } else if (aName == nsGkAtoms::max) {
1351 UpdateHasRange(aNotify);
1352 mInputType->MinMaxStepAttrChanged();
1353 // Validity state must be updated *after* the UpdateValueDueToAttrChange
1354 // call above or else the following assert will not be valid.
1355 // We don't assert the state of underflow during creation since
1356 // DoneCreatingElement sanitizes.
1357 UpdateRangeOverflowValidityState();
1358 needValidityUpdate = true;
1359 MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
1360 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1361 "HTML5 spec does not allow underflow for type=range");
1362 } else if (aName == nsGkAtoms::min) {
1363 UpdateHasRange(aNotify);
1364 mInputType->MinMaxStepAttrChanged();
1365 // See corresponding @max comment
1366 UpdateRangeUnderflowValidityState();
1367 UpdateStepMismatchValidityState();
1368 needValidityUpdate = true;
1369 MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
1370 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1371 "HTML5 spec does not allow underflow for type=range");
1372 } else if (aName == nsGkAtoms::step) {
1373 mInputType->MinMaxStepAttrChanged();
1374 // See corresponding @max comment
1375 UpdateStepMismatchValidityState();
1376 needValidityUpdate = true;
1377 MOZ_ASSERT(!mDoneCreating || mType != FormControlType::InputRange ||
1378 !GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW),
1379 "HTML5 spec does not allow underflow for type=range");
1380 } else if (aName == nsGkAtoms::dir && aValue &&
1381 aValue->Equals(nsGkAtoms::_auto, eIgnoreCase)) {
1382 ResetDirFormAssociatedElement(this, aNotify, true);
1383 } else if (aName == nsGkAtoms::lang) {
1384 // FIXME(emilio, bug 1651070): This doesn't account for lang changes on
1385 // ancestors.
1386 if (mType == FormControlType::InputNumber) {
1387 // The validity of our value may have changed based on the locale.
1388 UpdateValidityState();
1389 needValidityUpdate = true;
1391 } else if (aName == nsGkAtoms::autocomplete) {
1392 // Clear the cached @autocomplete attribute and autocompleteInfo state.
1393 mAutocompleteAttrState = nsContentUtils::eAutocompleteAttrState_Unknown;
1394 mAutocompleteInfoState = nsContentUtils::eAutocompleteAttrState_Unknown;
1395 } else if (aName == nsGkAtoms::placeholder) {
1396 // Full addition / removals of the attribute reconstruct right now.
1397 if (nsTextControlFrame* f = do_QueryFrame(GetPrimaryFrame())) {
1398 f->PlaceholderChanged(aOldValue, aValue);
1400 UpdatePlaceholderShownState();
1401 needValidityUpdate = true;
1404 if (CreatesDateTimeWidget()) {
1405 if (aName == nsGkAtoms::value || aName == nsGkAtoms::readonly ||
1406 aName == nsGkAtoms::tabindex || aName == nsGkAtoms::required ||
1407 aName == nsGkAtoms::disabled) {
1408 // If original target is this and not the inner text control, we should
1409 // pass the focus to the inner text control.
1410 if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
1411 AsyncEventDispatcher::RunDOMEventWhenSafe(
1412 *dateTimeBoxElement,
1413 aName == nsGkAtoms::value ? u"MozDateTimeValueChanged"_ns
1414 : u"MozDateTimeAttributeChanged"_ns,
1415 CanBubble::eNo, ChromeOnlyDispatch::eNo);
1419 if (needValidityUpdate) {
1420 UpdateValidityElementStates(aNotify);
1424 return nsGenericHTMLFormControlElementWithState::AfterSetAttr(
1425 aNameSpaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
1428 void HTMLInputElement::BeforeSetForm(HTMLFormElement* aForm, bool aBindToTree) {
1429 // No need to remove from radio group if we are just binding to tree.
1430 if (mType == FormControlType::InputRadio && !aBindToTree) {
1431 RemoveFromRadioGroup();
1434 // Dispatch event when <input> @form is set
1435 if (!aBindToTree) {
1436 MaybeDispatchLoginManagerEvents(aForm);
1440 void HTMLInputElement::AfterClearForm(bool aUnbindOrDelete) {
1441 MOZ_ASSERT(!mForm);
1443 // Do not add back to radio group if we are releasing or unbinding from tree.
1444 if (mType == FormControlType::InputRadio && !aUnbindOrDelete &&
1445 !GetCurrentRadioGroupContainer()) {
1446 AddToRadioGroup();
1447 UpdateValueMissingValidityStateForRadio(false);
1451 void HTMLInputElement::ResultForDialogSubmit(nsAString& aResult) {
1452 if (mType == FormControlType::InputImage) {
1453 // Get a property set by the frame to find out where it was clicked.
1454 const auto* lastClickedPoint =
1455 static_cast<CSSIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
1456 int32_t x, y;
1457 if (lastClickedPoint) {
1458 x = lastClickedPoint->x;
1459 y = lastClickedPoint->y;
1460 } else {
1461 x = y = 0;
1463 aResult.AppendInt(x);
1464 aResult.AppendLiteral(",");
1465 aResult.AppendInt(y);
1466 } else {
1467 GetAttr(nsGkAtoms::value, aResult);
1471 void HTMLInputElement::GetAutocomplete(nsAString& aValue) {
1472 if (!DoesAutocompleteApply()) {
1473 return;
1476 aValue.Truncate();
1477 const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
1479 mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute(
1480 attributeVal, aValue, mAutocompleteAttrState);
1483 void HTMLInputElement::GetAutocompleteInfo(Nullable<AutocompleteInfo>& aInfo) {
1484 if (!DoesAutocompleteApply()) {
1485 aInfo.SetNull();
1486 return;
1489 const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
1490 mAutocompleteInfoState = nsContentUtils::SerializeAutocompleteAttribute(
1491 attributeVal, aInfo.SetValue(), mAutocompleteInfoState, true);
1494 void HTMLInputElement::GetCapture(nsAString& aValue) {
1495 GetEnumAttr(nsGkAtoms::capture, kCaptureDefault->tag, aValue);
1498 void HTMLInputElement::GetFormEnctype(nsAString& aValue) {
1499 GetEnumAttr(nsGkAtoms::formenctype, "", kFormDefaultEnctype->tag, aValue);
1502 void HTMLInputElement::GetFormMethod(nsAString& aValue) {
1503 GetEnumAttr(nsGkAtoms::formmethod, "", kFormDefaultMethod->tag, aValue);
1506 void HTMLInputElement::GetType(nsAString& aValue) const {
1507 GetEnumAttr(nsGkAtoms::type, kInputDefaultType->tag, aValue);
1510 int32_t HTMLInputElement::TabIndexDefault() { return 0; }
1512 uint32_t HTMLInputElement::Height() {
1513 if (mType != FormControlType::InputImage) {
1514 return 0;
1516 return GetWidthHeightForImage().height;
1519 void HTMLInputElement::SetIndeterminateInternal(bool aValue,
1520 bool aShouldInvalidate) {
1521 mIndeterminate = aValue;
1522 if (mType != FormControlType::InputCheckbox) {
1523 return;
1526 SetStates(ElementState::INDETERMINATE, aValue);
1528 if (aShouldInvalidate) {
1529 // Repaint the frame
1530 if (nsIFrame* frame = GetPrimaryFrame()) {
1531 frame->InvalidateFrameSubtree();
1536 void HTMLInputElement::SetIndeterminate(bool aValue) {
1537 SetIndeterminateInternal(aValue, true);
1540 uint32_t HTMLInputElement::Width() {
1541 if (mType != FormControlType::InputImage) {
1542 return 0;
1544 return GetWidthHeightForImage().width;
1547 bool HTMLInputElement::SanitizesOnValueGetter() const {
1548 // Don't return non-sanitized value for datetime types, email, or number.
1549 return mType == FormControlType::InputEmail ||
1550 mType == FormControlType::InputNumber || IsDateTimeInputType(mType);
1553 void HTMLInputElement::GetValue(nsAString& aValue, CallerType aCallerType) {
1554 GetValueInternal(aValue, aCallerType);
1556 // In the case where we need to sanitize an input value without affecting
1557 // the displayed user's input, we instead sanitize only on .value accesses.
1558 // For the more general case of input elements displaying text that isn't
1559 // their current value, see bug 805049.
1560 if (SanitizesOnValueGetter()) {
1561 SanitizeValue(aValue, SanitizationKind::ForValueGetter);
1565 void HTMLInputElement::GetValueInternal(nsAString& aValue,
1566 CallerType aCallerType) const {
1567 if (mType != FormControlType::InputFile) {
1568 GetNonFileValueInternal(aValue);
1569 return;
1572 if (aCallerType == CallerType::System) {
1573 aValue.Assign(mFileData->mFirstFilePath);
1574 return;
1577 if (mFileData->mFilesOrDirectories.IsEmpty()) {
1578 aValue.Truncate();
1579 return;
1582 nsAutoString file;
1583 GetDOMFileOrDirectoryName(mFileData->mFilesOrDirectories[0], file);
1584 if (file.IsEmpty()) {
1585 aValue.Truncate();
1586 return;
1589 aValue.AssignLiteral("C:\\fakepath\\");
1590 aValue.Append(file);
1593 void HTMLInputElement::GetNonFileValueInternal(nsAString& aValue) const {
1594 switch (GetValueMode()) {
1595 case VALUE_MODE_VALUE:
1596 if (IsSingleLineTextControl(false)) {
1597 if (mInputData.mState) {
1598 mInputData.mState->GetValue(aValue, true, /* aForDisplay = */ false);
1599 } else {
1600 // Value hasn't been set yet.
1601 aValue.Truncate();
1603 } else if (!aValue.Assign(mInputData.mValue, fallible)) {
1604 aValue.Truncate();
1606 return;
1608 case VALUE_MODE_FILENAME:
1609 MOZ_ASSERT_UNREACHABLE("Someone screwed up here");
1610 // We'll just return empty string if someone does screw up.
1611 aValue.Truncate();
1612 return;
1614 case VALUE_MODE_DEFAULT:
1615 // Treat defaultValue as value.
1616 GetAttr(nsGkAtoms::value, aValue);
1617 return;
1619 case VALUE_MODE_DEFAULT_ON:
1620 // Treat default value as value and returns "on" if no value.
1621 if (!GetAttr(nsGkAtoms::value, aValue)) {
1622 aValue.AssignLiteral("on");
1624 return;
1628 void HTMLInputElement::ClearFiles(bool aSetValueChanged) {
1629 nsTArray<OwningFileOrDirectory> data;
1630 SetFilesOrDirectories(data, aSetValueChanged);
1633 int32_t HTMLInputElement::MonthsSinceJan1970(uint32_t aYear,
1634 uint32_t aMonth) const {
1635 return (aYear - 1970) * 12 + aMonth - 1;
1638 /* static */
1639 Decimal HTMLInputElement::StringToDecimal(const nsAString& aValue) {
1640 if (!IsAscii(aValue)) {
1641 return Decimal::nan();
1643 NS_LossyConvertUTF16toASCII asciiString(aValue);
1644 std::string stdString(asciiString.get(), asciiString.Length());
1645 auto decimal = Decimal::fromString(stdString);
1646 if (!decimal.isFinite()) {
1647 return Decimal::nan();
1649 // Numbers are considered finite IEEE 754 Double-precision floating point
1650 // values, but decimal supports a bigger range.
1651 static const Decimal maxDouble =
1652 Decimal::fromDouble(std::numeric_limits<double>::max());
1653 if (decimal < -maxDouble || decimal > maxDouble) {
1654 return Decimal::nan();
1656 return decimal;
1659 Decimal HTMLInputElement::GetValueAsDecimal() const {
1660 nsAutoString stringValue;
1661 GetNonFileValueInternal(stringValue);
1662 Decimal result = mInputType->ConvertStringToNumber(stringValue).mResult;
1663 return result.isFinite() ? result : Decimal::nan();
1666 void HTMLInputElement::SetValue(const nsAString& aValue, CallerType aCallerType,
1667 ErrorResult& aRv) {
1668 // check security. Note that setting the value to the empty string is always
1669 // OK and gives pages a way to clear a file input if necessary.
1670 if (mType == FormControlType::InputFile) {
1671 if (!aValue.IsEmpty()) {
1672 if (aCallerType != CallerType::System) {
1673 // setting the value of a "FILE" input widget requires
1674 // chrome privilege
1675 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1676 return;
1678 Sequence<nsString> list;
1679 if (!list.AppendElement(aValue, fallible)) {
1680 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
1681 return;
1684 MozSetFileNameArray(list, aRv);
1685 return;
1687 ClearFiles(true);
1688 } else {
1689 if (MayFireChangeOnBlur()) {
1690 // If the value has been set by a script, we basically want to keep the
1691 // current change event state. If the element is ready to fire a change
1692 // event, we should keep it that way. Otherwise, we should make sure the
1693 // element will not fire any event because of the script interaction.
1695 // NOTE: this is currently quite expensive work (too much string
1696 // manipulation). We should probably optimize that.
1697 nsAutoString currentValue;
1698 GetNonFileValueInternal(currentValue);
1700 nsresult rv = SetValueInternal(
1701 aValue, &currentValue,
1702 {ValueSetterOption::ByContentAPI, ValueSetterOption::SetValueChanged,
1703 ValueSetterOption::MoveCursorToEndIfValueChanged});
1704 if (NS_FAILED(rv)) {
1705 aRv.Throw(rv);
1706 return;
1709 if (mFocusedValue.Equals(currentValue)) {
1710 GetValue(mFocusedValue, aCallerType);
1712 } else {
1713 nsresult rv = SetValueInternal(
1714 aValue,
1715 {ValueSetterOption::ByContentAPI, ValueSetterOption::SetValueChanged,
1716 ValueSetterOption::MoveCursorToEndIfValueChanged});
1717 if (NS_FAILED(rv)) {
1718 aRv.Throw(rv);
1719 return;
1725 HTMLDataListElement* HTMLInputElement::GetList() const {
1726 nsAutoString dataListId;
1727 GetAttr(nsGkAtoms::list_, dataListId);
1728 if (dataListId.IsEmpty()) {
1729 return nullptr;
1732 DocumentOrShadowRoot* docOrShadow = GetUncomposedDocOrConnectedShadowRoot();
1733 if (!docOrShadow) {
1734 return nullptr;
1737 return HTMLDataListElement::FromNodeOrNull(
1738 docOrShadow->GetElementById(dataListId));
1741 void HTMLInputElement::SetValue(Decimal aValue, CallerType aCallerType) {
1742 MOZ_ASSERT(!aValue.isInfinity(), "aValue must not be Infinity!");
1744 if (aValue.isNaN()) {
1745 SetValue(u""_ns, aCallerType, IgnoreErrors());
1746 return;
1749 nsAutoString value;
1750 mInputType->ConvertNumberToString(aValue, value);
1751 SetValue(value, aCallerType, IgnoreErrors());
1754 void HTMLInputElement::GetValueAsDate(JSContext* aCx,
1755 JS::MutableHandle<JSObject*> aObject,
1756 ErrorResult& aRv) {
1757 aObject.set(nullptr);
1758 if (!IsDateTimeInputType(mType)) {
1759 return;
1762 Maybe<JS::ClippedTime> time;
1764 switch (mType) {
1765 case FormControlType::InputDate: {
1766 uint32_t year, month, day;
1767 nsAutoString value;
1768 GetNonFileValueInternal(value);
1769 if (!ParseDate(value, &year, &month, &day)) {
1770 return;
1773 time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, day)));
1774 break;
1776 case FormControlType::InputTime: {
1777 uint32_t millisecond;
1778 nsAutoString value;
1779 GetNonFileValueInternal(value);
1780 if (!ParseTime(value, &millisecond)) {
1781 return;
1784 time.emplace(JS::TimeClip(millisecond));
1785 MOZ_ASSERT(time->toDouble() == millisecond,
1786 "HTML times are restricted to the day after the epoch and "
1787 "never clip");
1788 break;
1790 case FormControlType::InputMonth: {
1791 uint32_t year, month;
1792 nsAutoString value;
1793 GetNonFileValueInternal(value);
1794 if (!ParseMonth(value, &year, &month)) {
1795 return;
1798 time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, 1)));
1799 break;
1801 case FormControlType::InputWeek: {
1802 uint32_t year, week;
1803 nsAutoString value;
1804 GetNonFileValueInternal(value);
1805 if (!ParseWeek(value, &year, &week)) {
1806 return;
1809 double days = DaysSinceEpochFromWeek(year, week);
1810 time.emplace(JS::TimeClip(days * kMsPerDay));
1812 break;
1814 case FormControlType::InputDatetimeLocal: {
1815 uint32_t year, month, day, timeInMs;
1816 nsAutoString value;
1817 GetNonFileValueInternal(value);
1818 if (!ParseDateTimeLocal(value, &year, &month, &day, &timeInMs)) {
1819 return;
1822 time.emplace(JS::TimeClip(JS::MakeDate(year, month - 1, day, timeInMs)));
1823 break;
1825 default:
1826 break;
1829 if (time) {
1830 aObject.set(JS::NewDateObject(aCx, *time));
1831 if (!aObject) {
1832 aRv.NoteJSContextException(aCx);
1834 return;
1837 MOZ_ASSERT(false, "Unrecognized input type");
1838 aRv.Throw(NS_ERROR_UNEXPECTED);
1841 void HTMLInputElement::SetValueAsDate(JSContext* aCx,
1842 JS::Handle<JSObject*> aObj,
1843 ErrorResult& aRv) {
1844 if (!IsDateTimeInputType(mType)) {
1845 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1846 return;
1849 if (aObj) {
1850 bool isDate;
1851 if (!JS::ObjectIsDate(aCx, aObj, &isDate)) {
1852 aRv.NoteJSContextException(aCx);
1853 return;
1855 if (!isDate) {
1856 aRv.ThrowTypeError("Value being assigned is not a date.");
1857 return;
1861 double milliseconds;
1862 if (aObj) {
1863 if (!js::DateGetMsecSinceEpoch(aCx, aObj, &milliseconds)) {
1864 aRv.NoteJSContextException(aCx);
1865 return;
1867 } else {
1868 milliseconds = UnspecifiedNaN<double>();
1871 // At this point we know we're not a file input, so we can just pass "not
1872 // system" as the caller type, since the caller type only matters in the file
1873 // input case.
1874 if (std::isnan(milliseconds)) {
1875 SetValue(u""_ns, CallerType::NonSystem, aRv);
1876 return;
1879 if (mType != FormControlType::InputMonth) {
1880 SetValue(Decimal::fromDouble(milliseconds), CallerType::NonSystem);
1881 return;
1884 // type=month expects the value to be number of months.
1885 double year = JS::YearFromTime(milliseconds);
1886 double month = JS::MonthFromTime(milliseconds);
1888 if (std::isnan(year) || std::isnan(month)) {
1889 SetValue(u""_ns, CallerType::NonSystem, aRv);
1890 return;
1893 int32_t months = MonthsSinceJan1970(year, month + 1);
1894 SetValue(Decimal(int32_t(months)), CallerType::NonSystem);
1897 void HTMLInputElement::SetValueAsNumber(double aValueAsNumber,
1898 ErrorResult& aRv) {
1899 // TODO: return TypeError when HTMLInputElement is converted to WebIDL, see
1900 // bug 825197.
1901 if (std::isinf(aValueAsNumber)) {
1902 aRv.Throw(NS_ERROR_INVALID_ARG);
1903 return;
1906 if (!DoesValueAsNumberApply()) {
1907 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
1908 return;
1911 // At this point we know we're not a file input, so we can just pass "not
1912 // system" as the caller type, since the caller type only matters in the file
1913 // input case.
1914 SetValue(Decimal::fromDouble(aValueAsNumber), CallerType::NonSystem);
1917 Decimal HTMLInputElement::GetMinimum() const {
1918 MOZ_ASSERT(
1919 DoesValueAsNumberApply(),
1920 "GetMinimum() should only be used for types that allow .valueAsNumber");
1922 // Only type=range has a default minimum
1923 Decimal defaultMinimum =
1924 mType == FormControlType::InputRange ? Decimal(0) : Decimal::nan();
1926 if (!HasAttr(nsGkAtoms::min)) {
1927 return defaultMinimum;
1930 nsAutoString minStr;
1931 GetAttr(nsGkAtoms::min, minStr);
1933 Decimal min = mInputType->ConvertStringToNumber(minStr).mResult;
1934 return min.isFinite() ? min : defaultMinimum;
1937 Decimal HTMLInputElement::GetMaximum() const {
1938 MOZ_ASSERT(
1939 DoesValueAsNumberApply(),
1940 "GetMaximum() should only be used for types that allow .valueAsNumber");
1942 // Only type=range has a default maximum
1943 Decimal defaultMaximum =
1944 mType == FormControlType::InputRange ? Decimal(100) : Decimal::nan();
1946 if (!HasAttr(nsGkAtoms::max)) {
1947 return defaultMaximum;
1950 nsAutoString maxStr;
1951 GetAttr(nsGkAtoms::max, maxStr);
1953 Decimal max = mInputType->ConvertStringToNumber(maxStr).mResult;
1954 return max.isFinite() ? max : defaultMaximum;
1957 Decimal HTMLInputElement::GetStepBase() const {
1958 MOZ_ASSERT(IsDateTimeInputType(mType) ||
1959 mType == FormControlType::InputNumber ||
1960 mType == FormControlType::InputRange,
1961 "Check that kDefaultStepBase is correct for this new type");
1962 // Do NOT use GetMinimum here - the spec says to use "the min content
1963 // attribute", not "the minimum".
1964 nsAutoString minStr;
1965 if (GetAttr(nsGkAtoms::min, minStr)) {
1966 Decimal min = mInputType->ConvertStringToNumber(minStr).mResult;
1967 if (min.isFinite()) {
1968 return min;
1972 // If @min is not a double, we should use @value.
1973 nsAutoString valueStr;
1974 if (GetAttr(nsGkAtoms::value, valueStr)) {
1975 Decimal value = mInputType->ConvertStringToNumber(valueStr).mResult;
1976 if (value.isFinite()) {
1977 return value;
1981 if (mType == FormControlType::InputWeek) {
1982 return kDefaultStepBaseWeek;
1985 return kDefaultStepBase;
1988 Decimal HTMLInputElement::GetValueIfStepped(int32_t aStep,
1989 StepCallerType aCallerType,
1990 ErrorResult& aRv) {
1991 constexpr auto kNaN = Decimal::nan();
1992 if (!DoStepDownStepUpApply()) {
1993 aRv.ThrowInvalidStateError("Step doesn't apply to this input type");
1994 return kNaN;
1997 Decimal stepBase = GetStepBase();
1998 Decimal step = GetStep();
1999 if (step == kStepAny) {
2000 if (aCallerType != StepCallerType::ForUserEvent) {
2001 aRv.ThrowInvalidStateError("Can't step an input with step=\"any\"");
2002 return kNaN;
2004 // Allow the spin buttons and up/down arrow keys to do something sensible:
2005 step = GetDefaultStep();
2008 Decimal minimum = GetMinimum();
2009 Decimal maximum = GetMaximum();
2011 if (!maximum.isNaN()) {
2012 // "max - (max - stepBase) % step" is the nearest valid value to max.
2013 maximum = maximum - NS_floorModulo(maximum - stepBase, step);
2014 if (!minimum.isNaN()) {
2015 if (minimum > maximum) {
2016 // Either the minimum was greater than the maximum prior to our
2017 // adjustment to align maximum on a step, or else (if we adjusted
2018 // maximum) there is no valid step between minimum and the unadjusted
2019 // maximum.
2020 return kNaN;
2025 Decimal value = GetValueAsDecimal();
2026 bool valueWasNaN = false;
2027 if (value.isNaN()) {
2028 value = Decimal(0);
2029 valueWasNaN = true;
2031 Decimal valueBeforeStepping = value;
2033 Decimal deltaFromStep = NS_floorModulo(value - stepBase, step);
2035 if (deltaFromStep != Decimal(0)) {
2036 if (aStep > 0) {
2037 value += step - deltaFromStep; // partial step
2038 value += step * Decimal(aStep - 1); // then remaining steps
2039 } else if (aStep < 0) {
2040 value -= deltaFromStep; // partial step
2041 value += step * Decimal(aStep + 1); // then remaining steps
2043 } else {
2044 value += step * Decimal(aStep);
2047 if (value < minimum) {
2048 value = minimum;
2049 deltaFromStep = NS_floorModulo(value - stepBase, step);
2050 if (deltaFromStep != Decimal(0)) {
2051 value += step - deltaFromStep;
2054 if (value > maximum) {
2055 value = maximum;
2056 deltaFromStep = NS_floorModulo(value - stepBase, step);
2057 if (deltaFromStep != Decimal(0)) {
2058 value -= deltaFromStep;
2062 if (!valueWasNaN && // value="", resulting in us using "0"
2063 ((aStep > 0 && value < valueBeforeStepping) ||
2064 (aStep < 0 && value > valueBeforeStepping))) {
2065 // We don't want step-up to effectively step down, or step-down to
2066 // effectively step up, so return;
2067 return kNaN;
2070 return value;
2073 void HTMLInputElement::ApplyStep(int32_t aStep, ErrorResult& aRv) {
2074 Decimal nextStep = GetValueIfStepped(aStep, StepCallerType::ForScript, aRv);
2075 if (aRv.Failed() || !nextStep.isFinite()) {
2076 return;
2078 // We know we're not a file input, so the caller type does not matter; just
2079 // pass "not system" to be safe.
2080 SetValue(nextStep, CallerType::NonSystem);
2083 bool HTMLInputElement::IsDateTimeInputType(FormControlType aType) {
2084 switch (aType) {
2085 case FormControlType::InputDate:
2086 case FormControlType::InputTime:
2087 case FormControlType::InputMonth:
2088 case FormControlType::InputWeek:
2089 case FormControlType::InputDatetimeLocal:
2090 return true;
2091 default:
2092 return false;
2096 void HTMLInputElement::MozGetFileNameArray(nsTArray<nsString>& aArray,
2097 ErrorResult& aRv) {
2098 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2099 return;
2102 const nsTArray<OwningFileOrDirectory>& filesOrDirs =
2103 GetFilesOrDirectoriesInternal();
2104 for (uint32_t i = 0; i < filesOrDirs.Length(); i++) {
2105 nsAutoString str;
2106 GetDOMFileOrDirectoryPath(filesOrDirs[i], str, aRv);
2107 if (NS_WARN_IF(aRv.Failed())) {
2108 return;
2111 aArray.AppendElement(str);
2115 void HTMLInputElement::MozSetFileArray(
2116 const Sequence<OwningNonNull<File>>& aFiles) {
2117 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2118 return;
2121 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
2122 MOZ_ASSERT(global);
2123 if (!global) {
2124 return;
2127 nsTArray<OwningFileOrDirectory> files;
2128 for (uint32_t i = 0; i < aFiles.Length(); ++i) {
2129 RefPtr<File> file = File::Create(global, aFiles[i].get()->Impl());
2130 if (NS_WARN_IF(!file)) {
2131 return;
2134 OwningFileOrDirectory* element = files.AppendElement();
2135 element->SetAsFile() = file;
2138 SetFilesOrDirectories(files, true);
2141 void HTMLInputElement::MozSetFileNameArray(const Sequence<nsString>& aFileNames,
2142 ErrorResult& aRv) {
2143 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2144 return;
2147 if (XRE_IsContentProcess()) {
2148 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
2149 return;
2152 nsTArray<OwningFileOrDirectory> files;
2153 for (uint32_t i = 0; i < aFileNames.Length(); ++i) {
2154 nsCOMPtr<nsIFile> file;
2156 if (StringBeginsWith(aFileNames[i], u"file:"_ns,
2157 nsASCIICaseInsensitiveStringComparator)) {
2158 // Converts the URL string into the corresponding nsIFile if possible
2159 // A local file will be created if the URL string begins with file://
2160 NS_GetFileFromURLSpec(NS_ConvertUTF16toUTF8(aFileNames[i]),
2161 getter_AddRefs(file));
2164 if (!file) {
2165 // this is no "file://", try as local file
2166 NS_NewLocalFile(aFileNames[i], false, getter_AddRefs(file));
2169 if (!file) {
2170 continue; // Not much we can do if the file doesn't exist
2173 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
2174 if (!global) {
2175 aRv.Throw(NS_ERROR_FAILURE);
2176 return;
2179 RefPtr<File> domFile = File::CreateFromFile(global, file);
2180 if (NS_WARN_IF(!domFile)) {
2181 aRv.Throw(NS_ERROR_FAILURE);
2182 return;
2185 OwningFileOrDirectory* element = files.AppendElement();
2186 element->SetAsFile() = domFile;
2189 SetFilesOrDirectories(files, true);
2192 void HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath,
2193 ErrorResult& aRv) {
2194 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2195 return;
2198 nsCOMPtr<nsIFile> file;
2199 aRv = NS_NewLocalFile(aDirectoryPath, true, getter_AddRefs(file));
2200 if (NS_WARN_IF(aRv.Failed())) {
2201 return;
2204 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
2205 if (NS_WARN_IF(!window)) {
2206 aRv.Throw(NS_ERROR_FAILURE);
2207 return;
2210 RefPtr<Directory> directory = Directory::Create(window->AsGlobal(), file);
2211 MOZ_ASSERT(directory);
2213 nsTArray<OwningFileOrDirectory> array;
2214 OwningFileOrDirectory* element = array.AppendElement();
2215 element->SetAsDirectory() = directory;
2217 SetFilesOrDirectories(array, true);
2220 void HTMLInputElement::GetDateTimeInputBoxValue(DateTimeValue& aValue) {
2221 if (NS_WARN_IF(!IsDateTimeInputType(mType)) || !mDateTimeInputBoxValue) {
2222 return;
2225 aValue = *mDateTimeInputBoxValue;
2228 Element* HTMLInputElement::GetDateTimeBoxElement() {
2229 if (!GetShadowRoot()) {
2230 return nullptr;
2233 // The datetimebox <div> is the only child of the UA Widget Shadow Root
2234 // if it is present.
2235 MOZ_ASSERT(GetShadowRoot()->IsUAWidget());
2236 MOZ_ASSERT(1 >= GetShadowRoot()->GetChildCount());
2237 if (nsIContent* inputAreaContent = GetShadowRoot()->GetFirstChild()) {
2238 return inputAreaContent->AsElement();
2241 return nullptr;
2244 void HTMLInputElement::OpenDateTimePicker(const DateTimeValue& aInitialValue) {
2245 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2246 return;
2249 mDateTimeInputBoxValue = MakeUnique<DateTimeValue>(aInitialValue);
2250 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
2251 u"MozOpenDateTimePicker"_ns,
2252 CanBubble::eYes, Cancelable::eYes);
2255 void HTMLInputElement::UpdateDateTimePicker(const DateTimeValue& aValue) {
2256 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2257 return;
2260 mDateTimeInputBoxValue = MakeUnique<DateTimeValue>(aValue);
2261 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
2262 u"MozUpdateDateTimePicker"_ns,
2263 CanBubble::eYes, Cancelable::eYes);
2266 void HTMLInputElement::CloseDateTimePicker() {
2267 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2268 return;
2271 nsContentUtils::DispatchChromeEvent(OwnerDoc(), static_cast<Element*>(this),
2272 u"MozCloseDateTimePicker"_ns,
2273 CanBubble::eYes, Cancelable::eYes);
2276 void HTMLInputElement::SetFocusState(bool aIsFocused) {
2277 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2278 return;
2280 SetStates(ElementState::FOCUS | ElementState::FOCUSRING, aIsFocused);
2283 void HTMLInputElement::UpdateValidityState() {
2284 if (NS_WARN_IF(!IsDateTimeInputType(mType))) {
2285 return;
2288 // For now, datetime input box call this function only when the value may
2289 // become valid/invalid. For other validity states, they will be updated when
2290 // .value is actually changed.
2291 UpdateBadInputValidityState();
2292 UpdateValidityElementStates(true);
2295 bool HTMLInputElement::MozIsTextField(bool aExcludePassword) {
2296 // TODO: temporary until bug 888320 is fixed.
2298 // FIXME: Historically we never returned true for `number`, we should consider
2299 // changing that now that it is similar to other inputs.
2300 if (IsDateTimeInputType(mType) || mType == FormControlType::InputNumber) {
2301 return false;
2304 return IsSingleLineTextControl(aExcludePassword);
2307 void HTMLInputElement::SetUserInput(const nsAString& aValue,
2308 nsIPrincipal& aSubjectPrincipal) {
2309 AutoHandlingUserInputStatePusher inputStatePusher(true);
2311 if (mType == FormControlType::InputFile &&
2312 !aSubjectPrincipal.IsSystemPrincipal()) {
2313 return;
2316 if (mType == FormControlType::InputFile) {
2317 Sequence<nsString> list;
2318 if (!list.AppendElement(aValue, fallible)) {
2319 return;
2322 MozSetFileNameArray(list, IgnoreErrors());
2323 return;
2326 bool isInputEventDispatchedByTextControlState =
2327 GetValueMode() == VALUE_MODE_VALUE && IsSingleLineTextControl(false);
2329 nsresult rv = SetValueInternal(
2330 aValue,
2331 {ValueSetterOption::BySetUserInputAPI, ValueSetterOption::SetValueChanged,
2332 ValueSetterOption::MoveCursorToEndIfValueChanged});
2333 NS_ENSURE_SUCCESS_VOID(rv);
2335 if (!isInputEventDispatchedByTextControlState) {
2336 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
2337 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2338 "Failed to dispatch input event");
2341 // If this element is not currently focused, it won't receive a change event
2342 // for this update through the normal channels. So fire a change event
2343 // immediately, instead.
2344 if (CreatesDateTimeWidget() || !ShouldBlur(this)) {
2345 FireChangeEventIfNeeded();
2349 nsIEditor* HTMLInputElement::GetEditorForBindings() {
2350 if (!GetPrimaryFrame()) {
2351 // Ensure we construct frames (and thus an editor) if needed.
2352 GetPrimaryFrame(FlushType::Frames);
2354 return GetTextEditorFromState();
2357 bool HTMLInputElement::HasEditor() const {
2358 return !!GetTextEditorWithoutCreation();
2361 TextEditor* HTMLInputElement::GetTextEditorFromState() {
2362 TextControlState* state = GetEditorState();
2363 if (state) {
2364 return state->GetTextEditor();
2366 return nullptr;
2369 TextEditor* HTMLInputElement::GetTextEditor() {
2370 return GetTextEditorFromState();
2373 TextEditor* HTMLInputElement::GetTextEditorWithoutCreation() const {
2374 TextControlState* state = GetEditorState();
2375 if (!state) {
2376 return nullptr;
2378 return state->GetTextEditorWithoutCreation();
2381 nsISelectionController* HTMLInputElement::GetSelectionController() {
2382 TextControlState* state = GetEditorState();
2383 if (state) {
2384 return state->GetSelectionController();
2386 return nullptr;
2389 nsFrameSelection* HTMLInputElement::GetConstFrameSelection() {
2390 TextControlState* state = GetEditorState();
2391 if (state) {
2392 return state->GetConstFrameSelection();
2394 return nullptr;
2397 nsresult HTMLInputElement::BindToFrame(nsTextControlFrame* aFrame) {
2398 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
2399 TextControlState* state = GetEditorState();
2400 if (state) {
2401 return state->BindToFrame(aFrame);
2403 return NS_ERROR_FAILURE;
2406 void HTMLInputElement::UnbindFromFrame(nsTextControlFrame* aFrame) {
2407 TextControlState* state = GetEditorState();
2408 if (state && aFrame) {
2409 state->UnbindFromFrame(aFrame);
2413 nsresult HTMLInputElement::CreateEditor() {
2414 TextControlState* state = GetEditorState();
2415 if (state) {
2416 return state->PrepareEditor();
2418 return NS_ERROR_FAILURE;
2421 void HTMLInputElement::SetPreviewValue(const nsAString& aValue) {
2422 TextControlState* state = GetEditorState();
2423 if (state) {
2424 state->SetPreviewText(aValue, true);
2428 void HTMLInputElement::GetPreviewValue(nsAString& aValue) {
2429 TextControlState* state = GetEditorState();
2430 if (state) {
2431 state->GetPreviewText(aValue);
2435 void HTMLInputElement::EnablePreview() {
2436 if (mIsPreviewEnabled) {
2437 return;
2440 mIsPreviewEnabled = true;
2441 // Reconstruct the frame to append an anonymous preview node
2442 nsLayoutUtils::PostRestyleEvent(this, RestyleHint{0},
2443 nsChangeHint_ReconstructFrame);
2446 bool HTMLInputElement::IsPreviewEnabled() { return mIsPreviewEnabled; }
2448 void HTMLInputElement::GetDisplayFileName(nsAString& aValue) const {
2449 MOZ_ASSERT(mFileData);
2451 if (OwnerDoc()->IsStaticDocument()) {
2452 aValue = mFileData->mStaticDocFileList;
2453 return;
2456 if (mFileData->mFilesOrDirectories.Length() == 1) {
2457 GetDOMFileOrDirectoryName(mFileData->mFilesOrDirectories[0], aValue);
2458 return;
2461 nsAutoString value;
2463 if (mFileData->mFilesOrDirectories.IsEmpty()) {
2464 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
2465 HasAttr(nsGkAtoms::webkitdirectory)) {
2466 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
2467 "NoDirSelected", OwnerDoc(),
2468 value);
2469 } else if (HasAttr(nsGkAtoms::multiple)) {
2470 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
2471 "NoFilesSelected", OwnerDoc(),
2472 value);
2473 } else {
2474 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
2475 "NoFileSelected", OwnerDoc(),
2476 value);
2478 } else {
2479 nsString count;
2480 count.AppendInt(int(mFileData->mFilesOrDirectories.Length()));
2482 nsContentUtils::FormatMaybeLocalizedString(
2483 value, nsContentUtils::eFORMS_PROPERTIES, "XFilesSelected", OwnerDoc(),
2484 count);
2487 aValue = value;
2490 const nsTArray<OwningFileOrDirectory>&
2491 HTMLInputElement::GetFilesOrDirectoriesInternal() const {
2492 return mFileData->mFilesOrDirectories;
2495 void HTMLInputElement::SetFilesOrDirectories(
2496 const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories,
2497 bool aSetValueChanged) {
2498 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2499 return;
2502 MOZ_ASSERT(mFileData);
2504 mFileData->ClearGetFilesHelpers();
2506 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2507 HTMLInputElement_Binding::ClearCachedWebkitEntriesValue(this);
2508 mFileData->mEntries.Clear();
2511 mFileData->mFilesOrDirectories.Clear();
2512 mFileData->mFilesOrDirectories.AppendElements(aFilesOrDirectories);
2514 AfterSetFilesOrDirectories(aSetValueChanged);
2517 void HTMLInputElement::SetFiles(FileList* aFiles, bool aSetValueChanged) {
2518 MOZ_ASSERT(mFileData);
2520 mFileData->mFilesOrDirectories.Clear();
2521 mFileData->ClearGetFilesHelpers();
2523 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2524 HTMLInputElement_Binding::ClearCachedWebkitEntriesValue(this);
2525 mFileData->mEntries.Clear();
2528 if (aFiles) {
2529 uint32_t listLength = aFiles->Length();
2530 for (uint32_t i = 0; i < listLength; i++) {
2531 OwningFileOrDirectory* element =
2532 mFileData->mFilesOrDirectories.AppendElement();
2533 element->SetAsFile() = aFiles->Item(i);
2537 AfterSetFilesOrDirectories(aSetValueChanged);
2540 // This method is used for testing only.
2541 void HTMLInputElement::MozSetDndFilesAndDirectories(
2542 const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories) {
2543 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
2544 return;
2547 SetFilesOrDirectories(aFilesOrDirectories, true);
2549 if (StaticPrefs::dom_webkitBlink_filesystem_enabled()) {
2550 UpdateEntries(aFilesOrDirectories);
2553 RefPtr<DispatchChangeEventCallback> dispatchChangeEventCallback =
2554 new DispatchChangeEventCallback(this);
2556 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
2557 HasAttr(nsGkAtoms::webkitdirectory)) {
2558 ErrorResult rv;
2559 GetFilesHelper* helper =
2560 GetOrCreateGetFilesHelper(true /* recursionFlag */, rv);
2561 if (NS_WARN_IF(rv.Failed())) {
2562 rv.SuppressException();
2563 return;
2566 helper->AddCallback(dispatchChangeEventCallback);
2567 } else {
2568 dispatchChangeEventCallback->DispatchEvents();
2572 void HTMLInputElement::AfterSetFilesOrDirectories(bool aSetValueChanged) {
2573 // No need to flush here, if there's no frame at this point we
2574 // don't need to force creation of one just to tell it about this
2575 // new value. We just want the display to update as needed.
2576 if (nsFileControlFrame* f = do_QueryFrame(GetPrimaryFrame())) {
2577 f->SelectedFilesUpdated();
2580 // Grab the full path here for any chrome callers who access our .value via a
2581 // CPOW. This path won't be called from a CPOW meaning the potential sync IPC
2582 // call under GetMozFullPath won't be rejected for not being urgent.
2583 if (mFileData->mFilesOrDirectories.IsEmpty()) {
2584 mFileData->mFirstFilePath.Truncate();
2585 } else {
2586 ErrorResult rv;
2587 GetDOMFileOrDirectoryPath(mFileData->mFilesOrDirectories[0],
2588 mFileData->mFirstFilePath, rv);
2589 if (NS_WARN_IF(rv.Failed())) {
2590 rv.SuppressException();
2594 // Null out |mFileData->mFileList| to return a new file list when asked for.
2595 // Don't clear it since the file list might come from the user via SetFiles.
2596 if (mFileData->mFileList) {
2597 mFileData->mFileList = nullptr;
2600 if (aSetValueChanged) {
2601 SetValueChanged(true);
2604 UpdateAllValidityStates(true);
2607 void HTMLInputElement::FireChangeEventIfNeeded() {
2608 if (!MayFireChangeOnBlur()) {
2609 return;
2612 // We're not exposing the GetValue return value anywhere here, so it's safe to
2613 // claim to be a system caller.
2614 nsAutoString value;
2615 GetValue(value, CallerType::System);
2617 // NOTE(emilio): Per spec we should not set this if we don't fire the change
2618 // event, but that seems like a bug. Using mValueChanged seems reasonable to
2619 // keep the expected behavior while
2620 // https://github.com/whatwg/html/issues/10013 is resolved.
2621 if (mValueChanged) {
2622 SetUserInteracted(true);
2624 if (mFocusedValue.Equals(value)) {
2625 return;
2627 // Dispatch the change event.
2628 mFocusedValue = value;
2629 nsContentUtils::DispatchTrustedEvent(
2630 OwnerDoc(), static_cast<nsIContent*>(this), u"change"_ns, CanBubble::eYes,
2631 Cancelable::eNo);
2634 FileList* HTMLInputElement::GetFiles() {
2635 if (mType != FormControlType::InputFile) {
2636 return nullptr;
2639 if (!mFileData->mFileList) {
2640 mFileData->mFileList = new FileList(static_cast<nsIContent*>(this));
2641 for (const OwningFileOrDirectory& item : GetFilesOrDirectoriesInternal()) {
2642 if (item.IsFile()) {
2643 mFileData->mFileList->Append(item.GetAsFile());
2648 return mFileData->mFileList;
2651 void HTMLInputElement::SetFiles(FileList* aFiles) {
2652 if (mType != FormControlType::InputFile || !aFiles) {
2653 return;
2656 // Update |mFileData->mFilesOrDirectories|
2657 SetFiles(aFiles, true);
2659 MOZ_ASSERT(!mFileData->mFileList, "Should've cleared the existing file list");
2661 // Update |mFileData->mFileList| without copy
2662 mFileData->mFileList = aFiles;
2665 /* static */
2666 void HTMLInputElement::HandleNumberControlSpin(void* aData) {
2667 RefPtr<HTMLInputElement> input = static_cast<HTMLInputElement*>(aData);
2669 NS_ASSERTION(input->mNumberControlSpinnerIsSpinning,
2670 "Should have called nsRepeatService::Stop()");
2672 nsNumberControlFrame* numberControlFrame =
2673 do_QueryFrame(input->GetPrimaryFrame());
2674 if (input->mType != FormControlType::InputNumber || !numberControlFrame) {
2675 // Type has changed (and possibly our frame type hasn't been updated yet)
2676 // or else we've lost our frame. Either way, stop the timer and don't do
2677 // anything else.
2678 input->StopNumberControlSpinnerSpin();
2679 } else {
2680 input->StepNumberControlForUserEvent(
2681 input->mNumberControlSpinnerSpinsUp ? 1 : -1);
2685 nsresult HTMLInputElement::SetValueInternal(
2686 const nsAString& aValue, const nsAString* aOldValue,
2687 const ValueSetterOptions& aOptions) {
2688 MOZ_ASSERT(GetValueMode() != VALUE_MODE_FILENAME,
2689 "Don't call SetValueInternal for file inputs");
2691 // We want to remember if the SetValueInternal() call is being made for a XUL
2692 // element. We do that by looking at the parent node here, and if that node
2693 // is a XUL node, we consider our control a XUL control. XUL controls preserve
2694 // edit history across value setters.
2696 // TODO(emilio): Rather than doing this maybe add an attribute instead and
2697 // read it only on chrome docs or something? That'd allow front-end code to
2698 // move away from xul without weird side-effects.
2699 const bool forcePreserveUndoHistory = mParent && mParent->IsXULElement();
2701 switch (GetValueMode()) {
2702 case VALUE_MODE_VALUE: {
2703 // At the moment, only single line text control have to sanitize their
2704 // value Because we have to create a new string for that, we should
2705 // prevent doing it if it's useless.
2706 nsAutoString value(aValue);
2708 if (mDoneCreating &&
2709 !(mType == FormControlType::InputNumber &&
2710 aOptions.contains(ValueSetterOption::BySetUserInputAPI))) {
2711 // When the value of a number input is set by a script, we need to make
2712 // sure the value is a valid floating-point number.
2713 // https://html.spec.whatwg.org/#valid-floating-point-number
2714 // When it's set by a user, however, we need to be more permissive, so
2715 // we don't sanitize its value here. See bug 1839572.
2716 SanitizeValue(value, SanitizationKind::ForValueSetter);
2718 // else DoneCreatingElement calls us again once mDoneCreating is true
2720 const bool setValueChanged =
2721 aOptions.contains(ValueSetterOption::SetValueChanged);
2722 if (setValueChanged) {
2723 SetValueChanged(true);
2726 if (IsSingleLineTextControl(false)) {
2727 // Note that if aOptions includes
2728 // ValueSetterOption::BySetUserInputAPI, "input" event is automatically
2729 // dispatched by TextControlState::SetValue(). If you'd change condition
2730 // of calling this method, you need to maintain SetUserInput() too. FYI:
2731 // After calling SetValue(), the input type might have been
2732 // modified so that mInputData may not store TextControlState.
2733 EnsureEditorState();
2734 if (!mInputData.mState->SetValue(
2735 value, aOldValue,
2736 forcePreserveUndoHistory
2737 ? aOptions + ValueSetterOption::PreserveUndoHistory
2738 : aOptions)) {
2739 return NS_ERROR_OUT_OF_MEMORY;
2741 // If the caller won't dispatch "input" event via
2742 // nsContentUtils::DispatchInputEvent(), we need to modify
2743 // validationMessage value here.
2745 // FIXME(emilio): ValueSetterOption::ByInternalAPI is not supposed to
2746 // change state, but maybe we could run this too?
2747 if (aOptions.contains(ValueSetterOption::ByContentAPI)) {
2748 MaybeUpdateAllValidityStates(!mDoneCreating);
2750 } else {
2751 free(mInputData.mValue);
2752 mInputData.mValue = ToNewUnicode(value);
2753 if (setValueChanged) {
2754 SetValueChanged(true);
2756 if (mType == FormControlType::InputRange) {
2757 nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame());
2758 if (frame) {
2759 frame->UpdateForValueChange();
2761 } else if (CreatesDateTimeWidget() &&
2762 !aOptions.contains(ValueSetterOption::BySetUserInputAPI)) {
2763 if (Element* dateTimeBoxElement = GetDateTimeBoxElement()) {
2764 AsyncEventDispatcher::RunDOMEventWhenSafe(
2765 *dateTimeBoxElement, u"MozDateTimeValueChanged"_ns,
2766 CanBubble::eNo, ChromeOnlyDispatch::eNo);
2769 if (mDoneCreating) {
2770 OnValueChanged(ValueChangeKind::Internal, value.IsEmpty(), &value);
2772 // else DoneCreatingElement calls us again once mDoneCreating is true
2775 if (mType == FormControlType::InputColor) {
2776 // Update color frame, to reflect color changes
2777 nsColorControlFrame* colorControlFrame =
2778 do_QueryFrame(GetPrimaryFrame());
2779 if (colorControlFrame) {
2780 colorControlFrame->UpdateColor();
2783 return NS_OK;
2786 case VALUE_MODE_DEFAULT:
2787 case VALUE_MODE_DEFAULT_ON:
2788 // If the value of a hidden input was changed, we mark it changed so that
2789 // we will know we need to save / restore the value. Yes, we are
2790 // overloading the meaning of ValueChanged just a teensy bit to save a
2791 // measly byte of storage space in HTMLInputElement. Yes, you are free to
2792 // make a new flag, NEED_TO_SAVE_VALUE, at such time as mBitField becomes
2793 // a 16-bit value.
2794 if (mType == FormControlType::InputHidden) {
2795 SetValueChanged(true);
2798 // Make sure to keep track of the last value change not being interactive,
2799 // just in case this used to be another kind of editable input before.
2800 // Note that a checked change _could_ really be interactive, but we don't
2801 // keep track of that elsewhere so seems fine to just do this.
2802 SetLastValueChangeWasInteractive(false);
2804 // Treat value == defaultValue for other input elements.
2805 return nsGenericHTMLFormControlElementWithState::SetAttr(
2806 kNameSpaceID_None, nsGkAtoms::value, aValue, true);
2808 case VALUE_MODE_FILENAME:
2809 return NS_ERROR_UNEXPECTED;
2812 // This return statement is required for some compilers.
2813 return NS_OK;
2816 void HTMLInputElement::SetValueChanged(bool aValueChanged) {
2817 if (mValueChanged == aValueChanged) {
2818 return;
2820 mValueChanged = aValueChanged;
2821 UpdateTooLongValidityState();
2822 UpdateTooShortValidityState();
2823 UpdateValidityElementStates(true);
2826 void HTMLInputElement::SetLastValueChangeWasInteractive(bool aWasInteractive) {
2827 if (aWasInteractive == mLastValueChangeWasInteractive) {
2828 return;
2830 mLastValueChangeWasInteractive = aWasInteractive;
2831 const bool wasValid = IsValid();
2832 UpdateTooLongValidityState();
2833 UpdateTooShortValidityState();
2834 if (wasValid != IsValid()) {
2835 UpdateValidityElementStates(true);
2839 void HTMLInputElement::SetCheckedChanged(bool aCheckedChanged) {
2840 DoSetCheckedChanged(aCheckedChanged, true);
2843 void HTMLInputElement::DoSetCheckedChanged(bool aCheckedChanged, bool aNotify) {
2844 if (mType == FormControlType::InputRadio) {
2845 if (mCheckedChanged != aCheckedChanged) {
2846 nsCOMPtr<nsIRadioVisitor> visitor =
2847 new nsRadioSetCheckedChangedVisitor(aCheckedChanged);
2848 VisitGroup(visitor);
2850 } else {
2851 SetCheckedChangedInternal(aCheckedChanged);
2855 void HTMLInputElement::SetCheckedChangedInternal(bool aCheckedChanged) {
2856 if (mCheckedChanged == aCheckedChanged) {
2857 return;
2859 mCheckedChanged = aCheckedChanged;
2860 UpdateValidityElementStates(true);
2863 void HTMLInputElement::SetChecked(bool aChecked) {
2864 DoSetChecked(aChecked, true, true);
2867 void HTMLInputElement::DoSetChecked(bool aChecked, bool aNotify,
2868 bool aSetValueChanged) {
2869 // If the user or JS attempts to set checked, whether it actually changes the
2870 // value or not, we say the value was changed so that defaultValue don't
2871 // affect it no more.
2872 if (aSetValueChanged) {
2873 DoSetCheckedChanged(true, aNotify);
2876 // Don't do anything if we're not changing whether it's checked (it would
2877 // screw up state actually, especially when you are setting radio button to
2878 // false)
2879 if (mChecked == aChecked) {
2880 return;
2883 // Set checked
2884 if (mType != FormControlType::InputRadio) {
2885 SetCheckedInternal(aChecked, aNotify);
2886 return;
2889 // For radio button, we need to do some extra fun stuff
2890 if (aChecked) {
2891 RadioSetChecked(aNotify);
2892 return;
2895 if (auto* container = GetCurrentRadioGroupContainer()) {
2896 nsAutoString name;
2897 GetAttr(nsGkAtoms::name, name);
2898 container->SetCurrentRadioButton(name, nullptr);
2900 // SetCheckedInternal is going to ask all radios to update their
2901 // validity state. We have to be sure the radio group container knows
2902 // the currently selected radio.
2903 SetCheckedInternal(false, aNotify);
2906 void HTMLInputElement::RadioSetChecked(bool aNotify) {
2907 // Find the selected radio button so we can deselect it
2908 HTMLInputElement* currentlySelected = GetSelectedRadioButton();
2910 // Deselect the currently selected radio button
2911 if (currentlySelected) {
2912 // Pass true for the aNotify parameter since the currently selected
2913 // button is already in the document.
2914 currentlySelected->SetCheckedInternal(false, true);
2917 // Let the group know that we are now the One True Radio Button
2918 if (auto* container = GetCurrentRadioGroupContainer()) {
2919 nsAutoString name;
2920 GetAttr(nsGkAtoms::name, name);
2921 container->SetCurrentRadioButton(name, this);
2924 // SetCheckedInternal is going to ask all radios to update their
2925 // validity state.
2926 SetCheckedInternal(true, aNotify);
2929 RadioGroupContainer* HTMLInputElement::GetCurrentRadioGroupContainer() const {
2930 NS_ASSERTION(
2931 mType == FormControlType::InputRadio,
2932 "GetRadioGroupContainer should only be called when type='radio'");
2933 return mRadioGroupContainer;
2936 RadioGroupContainer* HTMLInputElement::FindTreeRadioGroupContainer() const {
2937 nsAutoString name;
2938 GetAttr(nsGkAtoms::name, name);
2940 if (name.IsEmpty()) {
2941 return nullptr;
2943 if (mForm) {
2944 return &mForm->OwnedRadioGroupContainer();
2946 if (IsInNativeAnonymousSubtree()) {
2947 return nullptr;
2949 if (Document* doc = GetUncomposedDoc()) {
2950 return &doc->OwnedRadioGroupContainer();
2952 return &static_cast<FragmentOrElement*>(SubtreeRoot())
2953 ->OwnedRadioGroupContainer();
2956 void HTMLInputElement::DisconnectRadioGroupContainer() {
2957 mRadioGroupContainer = nullptr;
2960 HTMLInputElement* HTMLInputElement::GetSelectedRadioButton() const {
2961 auto* container = GetCurrentRadioGroupContainer();
2962 if (!container) {
2963 return nullptr;
2966 nsAutoString name;
2967 GetAttr(nsGkAtoms::name, name);
2969 return container->GetCurrentRadioButton(name);
2972 void HTMLInputElement::MaybeSubmitForm(nsPresContext* aPresContext) {
2973 if (!mForm) {
2974 // Nothing to do here.
2975 return;
2978 RefPtr<PresShell> presShell = aPresContext->GetPresShell();
2979 if (!presShell) {
2980 return;
2983 // Get the default submit element
2984 if (RefPtr<nsGenericHTMLFormElement> submitContent =
2985 mForm->GetDefaultSubmitElement()) {
2986 WidgetPointerEvent event(true, ePointerClick, nullptr);
2987 event.mInputSource = MouseEvent_Binding::MOZ_SOURCE_KEYBOARD;
2988 // pointerId definition in Pointer Events:
2989 // > The pointerId value of -1 MUST be reserved and used to indicate events
2990 // > that were generated by something other than a pointing device.
2991 event.pointerId = -1;
2992 nsEventStatus status = nsEventStatus_eIgnore;
2993 presShell->HandleDOMEventWithTarget(submitContent, &event, &status);
2994 } else if (!mForm->ImplicitSubmissionIsDisabled()) {
2995 // If there's only one text control, just submit the form
2996 // Hold strong ref across the event
2997 RefPtr<dom::HTMLFormElement> form(mForm);
2998 form->MaybeSubmit(nullptr);
3002 void HTMLInputElement::UpdateCheckedState(bool aNotify) {
3003 SetStates(ElementState::CHECKED, IsRadioOrCheckbox() && mChecked, aNotify);
3006 void HTMLInputElement::UpdateIndeterminateState(bool aNotify) {
3007 bool indeterminate = [&] {
3008 if (mType == FormControlType::InputCheckbox) {
3009 return mIndeterminate;
3011 if (mType == FormControlType::InputRadio) {
3012 return !mChecked && !GetSelectedRadioButton();
3014 return false;
3015 }();
3016 SetStates(ElementState::INDETERMINATE, indeterminate, aNotify);
3019 void HTMLInputElement::SetCheckedInternal(bool aChecked, bool aNotify) {
3020 // Set the value
3021 mChecked = aChecked;
3023 if (IsRadioOrCheckbox()) {
3024 SetStates(ElementState::CHECKED, aChecked, aNotify);
3027 // No need to update element state, since we're about to call
3028 // UpdateState anyway.
3029 UpdateAllValidityStatesButNotElementState();
3030 UpdateIndeterminateState(aNotify);
3031 UpdateValidityElementStates(aNotify);
3033 // Notify all radios in the group that value has changed, this is to let
3034 // radios to have the chance to update its states, e.g., :indeterminate.
3035 if (mType == FormControlType::InputRadio) {
3036 nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
3037 VisitGroup(visitor);
3041 #if !defined(ANDROID) && !defined(XP_MACOSX)
3042 bool HTMLInputElement::IsNodeApzAwareInternal() const {
3043 // Tell APZC we may handle mouse wheel event and do preventDefault when input
3044 // type is number.
3045 return mType == FormControlType::InputNumber ||
3046 mType == FormControlType::InputRange ||
3047 nsINode::IsNodeApzAwareInternal();
3049 #endif
3051 bool HTMLInputElement::IsInteractiveHTMLContent() const {
3052 return mType != FormControlType::InputHidden ||
3053 nsGenericHTMLFormControlElementWithState::IsInteractiveHTMLContent();
3056 void HTMLInputElement::AsyncEventRunning(AsyncEventDispatcher* aEvent) {
3057 nsImageLoadingContent::AsyncEventRunning(aEvent);
3060 void HTMLInputElement::Select() {
3061 if (!IsSingleLineTextControl(false)) {
3062 return;
3065 TextControlState* state = GetEditorState();
3066 MOZ_ASSERT(state, "Single line text controls are expected to have a state");
3068 if (FocusState() != FocusTristate::eUnfocusable) {
3069 RefPtr<nsFrameSelection> fs = state->GetConstFrameSelection();
3070 if (fs && fs->MouseDownRecorded()) {
3071 // This means that we're being called while the frame selection has a
3072 // mouse down event recorded to adjust the caret during the mouse up
3073 // event. We are probably called from the focus event handler. We should
3074 // override the delayed caret data in this case to ensure that this
3075 // select() call takes effect.
3076 fs->SetDelayedCaretData(nullptr);
3079 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
3080 fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL);
3082 // A focus event handler may change the type attribute, which will destroy
3083 // the previous state object.
3084 state = GetEditorState();
3085 if (!state) {
3086 return;
3091 // Directly call TextControlState::SetSelectionRange because
3092 // HTMLInputElement::SetSelectionRange only applies to fewer types
3093 state->SetSelectionRange(0, UINT32_MAX, Optional<nsAString>(), IgnoreErrors(),
3094 TextControlState::ScrollAfterSelection::No);
3097 void HTMLInputElement::SelectAll() {
3098 // FIXME(emilio): Should we try to call Select(), which will avoid flushing?
3099 if (nsTextControlFrame* tf =
3100 do_QueryFrame(GetPrimaryFrame(FlushType::Frames))) {
3101 tf->SelectAll();
3105 bool HTMLInputElement::NeedToInitializeEditorForEvent(
3106 EventChainPreVisitor& aVisitor) const {
3107 // We only need to initialize the editor for single line input controls
3108 // because they are lazily initialized. We don't need to initialize the
3109 // control for certain types of events, because we know that those events are
3110 // safe to be handled without the editor being initialized. These events
3111 // include: mousein/move/out, overflow/underflow, DOM mutation, and void
3112 // events. Void events are dispatched frequently by async keyboard scrolling
3113 // to focused elements, so it's important to handle them to prevent excessive
3114 // DOM mutations.
3115 if (!IsSingleLineTextControl(false) ||
3116 aVisitor.mEvent->mClass == eMutationEventClass) {
3117 return false;
3120 switch (aVisitor.mEvent->mMessage) {
3121 case eVoidEvent:
3122 case eMouseMove:
3123 case eMouseEnterIntoWidget:
3124 case eMouseExitFromWidget:
3125 case eMouseOver:
3126 case eMouseOut:
3127 case eScrollPortUnderflow:
3128 case eScrollPortOverflow:
3129 return false;
3130 default:
3131 return true;
3135 bool HTMLInputElement::IsDisabledForEvents(WidgetEvent* aEvent) {
3136 return IsElementDisabledForEvents(aEvent, GetPrimaryFrame());
3139 bool HTMLInputElement::CheckActivationBehaviorPreconditions(
3140 EventChainVisitor& aVisitor) const {
3141 switch (mType) {
3142 case FormControlType::InputColor:
3143 case FormControlType::InputCheckbox:
3144 case FormControlType::InputRadio:
3145 case FormControlType::InputFile:
3146 case FormControlType::InputSubmit:
3147 case FormControlType::InputImage:
3148 case FormControlType::InputReset:
3149 case FormControlType::InputButton: {
3150 // Track whether we're in the outermost Dispatch invocation that will
3151 // cause activation of the input. That is, if we're a click event, or a
3152 // DOMActivate that was dispatched directly, this will be set, but if
3153 // we're a DOMActivate dispatched from click handling, it will not be set.
3154 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3155 bool outerActivateEvent =
3156 (mouseEvent && mouseEvent->IsLeftClickEvent()) ||
3157 (aVisitor.mEvent->mMessage == eLegacyDOMActivate &&
3158 !mInInternalActivate);
3159 if (outerActivateEvent) {
3160 aVisitor.mItemFlags |= NS_OUTER_ACTIVATE_EVENT;
3162 return outerActivateEvent;
3164 default:
3165 return false;
3169 void HTMLInputElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
3170 // Do not process any DOM events if the element is disabled
3171 aVisitor.mCanHandle = false;
3172 if (IsDisabledForEvents(aVisitor.mEvent)) {
3173 return;
3176 // Initialize the editor if needed.
3177 if (NeedToInitializeEditorForEvent(aVisitor)) {
3178 if (nsTextControlFrame* tcf = do_QueryFrame(GetPrimaryFrame())) {
3179 tcf->EnsureEditorInitialized();
3183 if (CheckActivationBehaviorPreconditions(aVisitor)) {
3184 aVisitor.mWantsActivationBehavior = true;
3187 // We must cache type because mType may change during JS event (bug 2369)
3188 aVisitor.mItemFlags |= uint8_t(mType);
3190 if (aVisitor.mEvent->mMessage == eFocus && aVisitor.mEvent->IsTrusted() &&
3191 MayFireChangeOnBlur() &&
3192 // StartRangeThumbDrag already set mFocusedValue on 'mousedown' before
3193 // we get the 'focus' event.
3194 !mIsDraggingRange) {
3195 GetValue(mFocusedValue, CallerType::System);
3198 // Fire onchange (if necessary), before we do the blur, bug 357684.
3199 if (aVisitor.mEvent->mMessage == eBlur) {
3200 // We set NS_PRE_HANDLE_BLUR_EVENT here and handle it in PreHandleEvent to
3201 // prevent breaking event target chain creation.
3202 aVisitor.mWantsPreHandleEvent = true;
3203 aVisitor.mItemFlags |= NS_PRE_HANDLE_BLUR_EVENT;
3206 if (mType == FormControlType::InputRange &&
3207 (aVisitor.mEvent->mMessage == eFocus ||
3208 aVisitor.mEvent->mMessage == eBlur)) {
3209 // We handle focus here.
3210 // FIXME(emilio): Why is this needed? If it is it should be moved to
3211 // nsRangeFrame::ElementStateChanged.
3212 if (nsIFrame* frame = GetPrimaryFrame()) {
3213 frame->InvalidateFrameSubtree();
3217 if (mType == FormControlType::InputNumber && aVisitor.mEvent->IsTrusted()) {
3218 if (mNumberControlSpinnerIsSpinning) {
3219 // If the timer is running the user has depressed the mouse on one of the
3220 // spin buttons. If the mouse exits the button we either want to reverse
3221 // the direction of spin if it has moved over the other button, or else
3222 // we want to end the spin. We do this here (rather than in
3223 // PostHandleEvent) because we don't want to let content preventDefault()
3224 // the end of the spin.
3225 if (aVisitor.mEvent->mMessage == eMouseMove) {
3226 // Be aggressive about stopping the spin:
3227 bool stopSpin = true;
3228 nsNumberControlFrame* numberControlFrame =
3229 do_QueryFrame(GetPrimaryFrame());
3230 if (numberControlFrame) {
3231 bool oldNumberControlSpinTimerSpinsUpValue =
3232 mNumberControlSpinnerSpinsUp;
3233 switch (numberControlFrame->GetSpinButtonForPointerEvent(
3234 aVisitor.mEvent->AsMouseEvent())) {
3235 case nsNumberControlFrame::eSpinButtonUp:
3236 mNumberControlSpinnerSpinsUp = true;
3237 stopSpin = false;
3238 break;
3239 case nsNumberControlFrame::eSpinButtonDown:
3240 mNumberControlSpinnerSpinsUp = false;
3241 stopSpin = false;
3242 break;
3244 if (mNumberControlSpinnerSpinsUp !=
3245 oldNumberControlSpinTimerSpinsUpValue) {
3246 nsNumberControlFrame* numberControlFrame =
3247 do_QueryFrame(GetPrimaryFrame());
3248 if (numberControlFrame) {
3249 numberControlFrame->SpinnerStateChanged();
3253 if (stopSpin) {
3254 StopNumberControlSpinnerSpin();
3256 } else if (aVisitor.mEvent->mMessage == eMouseUp) {
3257 StopNumberControlSpinnerSpin();
3262 nsGenericHTMLFormControlElementWithState::GetEventTargetParent(aVisitor);
3264 // Stop the event if the related target's first non-native ancestor is the
3265 // same as the original target's first non-native ancestor (we are moving
3266 // inside of the same element).
3268 // FIXME(emilio): Is this still needed now that we use Shadow DOM for this?
3269 if (CreatesDateTimeWidget() && aVisitor.mEvent->IsTrusted() &&
3270 (aVisitor.mEvent->mMessage == eFocus ||
3271 aVisitor.mEvent->mMessage == eFocusIn ||
3272 aVisitor.mEvent->mMessage == eFocusOut ||
3273 aVisitor.mEvent->mMessage == eBlur)) {
3274 nsIContent* originalTarget = nsIContent::FromEventTargetOrNull(
3275 aVisitor.mEvent->AsFocusEvent()->mOriginalTarget);
3276 nsIContent* relatedTarget = nsIContent::FromEventTargetOrNull(
3277 aVisitor.mEvent->AsFocusEvent()->mRelatedTarget);
3279 if (originalTarget && relatedTarget &&
3280 originalTarget->FindFirstNonChromeOnlyAccessContent() ==
3281 relatedTarget->FindFirstNonChromeOnlyAccessContent()) {
3282 aVisitor.mCanHandle = false;
3287 void HTMLInputElement::LegacyPreActivationBehavior(
3288 EventChainVisitor& aVisitor) {
3290 // Web pages expect the value of a radio button or checkbox to be set
3291 // *before* onclick and DOMActivate fire, and they expect that if they set
3292 // the value explicitly during onclick or DOMActivate it will not be toggled
3293 // or any such nonsense.
3294 // In order to support that (bug 57137 and 58460 are examples) we toggle
3295 // the checked attribute *first*, and then fire onclick. If the user
3296 // returns false, we reset the control to the old checked value. Otherwise,
3297 // we dispatch DOMActivate. If DOMActivate is cancelled, we also reset
3298 // the control to the old checked value. We need to keep track of whether
3299 // we've already toggled the state from onclick since the user could
3300 // explicitly dispatch DOMActivate on the element.
3302 // These are compatibility hacks and are defined as legacy-pre-activation
3303 // and legacy-canceled-activation behavior in HTML.
3306 // Assert mType didn't change after GetEventTargetParent
3307 MOZ_ASSERT(NS_CONTROL_TYPE(aVisitor.mItemFlags) == uint8_t(mType));
3309 bool originalCheckedValue = false;
3310 mCheckedIsToggled = false;
3312 if (mType == FormControlType::InputCheckbox) {
3313 if (mIndeterminate) {
3314 // indeterminate is always set to FALSE when the checkbox is toggled
3315 SetIndeterminateInternal(false, false);
3316 aVisitor.mItemFlags |= NS_ORIGINAL_INDETERMINATE_VALUE;
3319 originalCheckedValue = Checked();
3320 DoSetChecked(!originalCheckedValue, true, true);
3321 mCheckedIsToggled = true;
3323 if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
3324 aVisitor.mEventStatus = nsEventStatus_eConsumeDoDefault;
3326 } else if (mType == FormControlType::InputRadio) {
3327 HTMLInputElement* selectedRadioButton = GetSelectedRadioButton();
3328 aVisitor.mItemData = static_cast<Element*>(selectedRadioButton);
3330 originalCheckedValue = Checked();
3331 if (!originalCheckedValue) {
3332 DoSetChecked(true, true, true);
3333 mCheckedIsToggled = true;
3336 if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
3337 aVisitor.mEventStatus = nsEventStatus_eConsumeDoDefault;
3341 if (originalCheckedValue) {
3342 aVisitor.mItemFlags |= NS_ORIGINAL_CHECKED_VALUE;
3345 // out-of-spec legacy pre-activation behavior needed because of bug 1803805
3346 if ((mType == FormControlType::InputSubmit ||
3347 mType == FormControlType::InputImage) &&
3348 mForm) {
3349 aVisitor.mItemFlags |= NS_IN_SUBMIT_CLICK;
3350 aVisitor.mItemData = static_cast<Element*>(mForm);
3351 // tell the form that we are about to enter a click handler.
3352 // that means that if there are scripted submissions, the
3353 // latest one will be deferred until after the exit point of the
3354 // handler.
3355 mForm->OnSubmitClickBegin(this);
3359 nsresult HTMLInputElement::PreHandleEvent(EventChainVisitor& aVisitor) {
3360 if (aVisitor.mItemFlags & NS_PRE_HANDLE_BLUR_EVENT) {
3361 MOZ_ASSERT(aVisitor.mEvent->mMessage == eBlur);
3362 FireChangeEventIfNeeded();
3364 return nsGenericHTMLFormControlElementWithState::PreHandleEvent(aVisitor);
3367 void HTMLInputElement::StartRangeThumbDrag(WidgetGUIEvent* aEvent) {
3368 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
3369 if (!rangeFrame) {
3370 return;
3373 mIsDraggingRange = true;
3374 mRangeThumbDragStartValue = GetValueAsDecimal();
3375 // Don't use CaptureFlags::RetargetToElement, as that breaks pseudo-class
3376 // styling of the thumb.
3377 PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState);
3379 // Before we change the value, record the current value so that we'll
3380 // correctly send a 'change' event if appropriate. We need to do this here
3381 // because the 'focus' event is handled after the 'mousedown' event that
3382 // we're being called for (i.e. too late to update mFocusedValue, since we'll
3383 // have changed it by then).
3384 GetValue(mFocusedValue, CallerType::System);
3386 SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent),
3387 SnapToTickMarks::Yes);
3390 void HTMLInputElement::FinishRangeThumbDrag(WidgetGUIEvent* aEvent) {
3391 MOZ_ASSERT(mIsDraggingRange);
3393 if (PresShell::GetCapturingContent() == this) {
3394 PresShell::ReleaseCapturingContent();
3396 if (aEvent) {
3397 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
3398 SetValueOfRangeForUserEvent(rangeFrame->GetValueAtEventPoint(aEvent),
3399 SnapToTickMarks::Yes);
3401 mIsDraggingRange = false;
3402 FireChangeEventIfNeeded();
3405 void HTMLInputElement::CancelRangeThumbDrag(bool aIsForUserEvent) {
3406 MOZ_ASSERT(mIsDraggingRange);
3408 mIsDraggingRange = false;
3409 if (PresShell::GetCapturingContent() == this) {
3410 PresShell::ReleaseCapturingContent();
3412 if (aIsForUserEvent) {
3413 SetValueOfRangeForUserEvent(mRangeThumbDragStartValue,
3414 SnapToTickMarks::Yes);
3415 } else {
3416 // Don't dispatch an 'input' event - at least not using
3417 // DispatchTrustedEvent.
3418 // TODO: decide what we should do here - bug 851782.
3419 nsAutoString val;
3420 mInputType->ConvertNumberToString(mRangeThumbDragStartValue, val);
3421 // TODO: What should we do if SetValueInternal fails? (The allocation
3422 // is small, so we should be fine here.)
3423 SetValueInternal(val, {ValueSetterOption::BySetUserInputAPI,
3424 ValueSetterOption::SetValueChanged});
3425 if (nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame())) {
3426 frame->UpdateForValueChange();
3428 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
3429 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3430 "Failed to dispatch input event");
3434 void HTMLInputElement::SetValueOfRangeForUserEvent(
3435 Decimal aValue, SnapToTickMarks aSnapToTickMarks) {
3436 MOZ_ASSERT(aValue.isFinite());
3437 if (aSnapToTickMarks == SnapToTickMarks::Yes) {
3438 MaybeSnapToTickMark(aValue);
3441 Decimal oldValue = GetValueAsDecimal();
3443 nsAutoString val;
3444 mInputType->ConvertNumberToString(aValue, val);
3445 // TODO: What should we do if SetValueInternal fails? (The allocation
3446 // is small, so we should be fine here.)
3447 SetValueInternal(val, {ValueSetterOption::BySetUserInputAPI,
3448 ValueSetterOption::SetValueChanged});
3449 if (nsRangeFrame* frame = do_QueryFrame(GetPrimaryFrame())) {
3450 frame->UpdateForValueChange();
3453 if (GetValueAsDecimal() != oldValue) {
3454 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
3455 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
3456 "Failed to dispatch input event");
3460 void HTMLInputElement::StartNumberControlSpinnerSpin() {
3461 MOZ_ASSERT(!mNumberControlSpinnerIsSpinning);
3463 mNumberControlSpinnerIsSpinning = true;
3465 nsRepeatService::GetInstance()->Start(
3466 HandleNumberControlSpin, this, OwnerDoc(), "HandleNumberControlSpin"_ns);
3468 // Capture the mouse so that we can tell if the pointer moves from one
3469 // spin button to the other, or to some other element:
3470 PresShell::SetCapturingContent(this, CaptureFlags::IgnoreAllowedState);
3472 nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
3473 if (numberControlFrame) {
3474 numberControlFrame->SpinnerStateChanged();
3478 void HTMLInputElement::StopNumberControlSpinnerSpin(SpinnerStopState aState) {
3479 if (mNumberControlSpinnerIsSpinning) {
3480 if (PresShell::GetCapturingContent() == this) {
3481 PresShell::ReleaseCapturingContent();
3484 nsRepeatService::GetInstance()->Stop(HandleNumberControlSpin, this);
3486 mNumberControlSpinnerIsSpinning = false;
3488 if (aState == eAllowDispatchingEvents) {
3489 FireChangeEventIfNeeded();
3492 nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame());
3493 if (numberControlFrame) {
3494 MOZ_ASSERT(aState == eAllowDispatchingEvents,
3495 "Shouldn't have primary frame for the element when we're not "
3496 "allowed to dispatch events to it anymore.");
3497 numberControlFrame->SpinnerStateChanged();
3502 void HTMLInputElement::StepNumberControlForUserEvent(int32_t aDirection) {
3503 // We can't use GetValidityState here because the validity state is not set
3504 // if the user hasn't previously taken an action to set or change the value,
3505 // according to the specs.
3506 if (HasBadInput()) {
3507 // If the user has typed a value into the control and inadvertently made a
3508 // mistake (e.g. put a thousand separator at the wrong point) we do not
3509 // want to wipe out what they typed if they try to increment/decrement the
3510 // value. Better is to highlight the value as being invalid so that they
3511 // can correct what they typed.
3512 // We only do this if there actually is a value typed in by/displayed to
3513 // the user. (IsValid() can return false if the 'required' attribute is
3514 // set and the value is the empty string.)
3515 if (!IsValueEmpty()) {
3516 // We pass 'true' for SetUserInteracted because we need the UI to update
3517 // _now_ or the user will wonder why the step behavior isn't functioning.
3518 SetUserInteracted(true);
3519 return;
3523 Decimal newValue = GetValueIfStepped(aDirection, StepCallerType::ForUserEvent,
3524 IgnoreErrors());
3525 if (!newValue.isFinite()) {
3526 return; // value should not or will not change
3529 nsAutoString newVal;
3530 mInputType->ConvertNumberToString(newValue, newVal);
3531 // TODO: What should we do if SetValueInternal fails? (The allocation
3532 // is small, so we should be fine here.)
3533 SetValueInternal(newVal, {ValueSetterOption::BySetUserInputAPI,
3534 ValueSetterOption::SetValueChanged});
3537 static bool SelectTextFieldOnFocus() {
3538 if (!gSelectTextFieldOnFocus) {
3539 int32_t selectTextfieldsOnKeyFocus = -1;
3540 nsresult rv =
3541 LookAndFeel::GetInt(LookAndFeel::IntID::SelectTextfieldsOnKeyFocus,
3542 &selectTextfieldsOnKeyFocus);
3543 if (NS_FAILED(rv)) {
3544 gSelectTextFieldOnFocus = -1;
3545 } else {
3546 gSelectTextFieldOnFocus = selectTextfieldsOnKeyFocus != 0 ? 1 : -1;
3550 return gSelectTextFieldOnFocus == 1;
3553 bool HTMLInputElement::ShouldPreventDOMActivateDispatch(
3554 EventTarget* aOriginalTarget) {
3556 * For the moment, there is only one situation where we actually want to
3557 * prevent firing a DOMActivate event:
3558 * - we are a <input type='file'> that just got a click event,
3559 * - the event was targeted to our button which should have sent a
3560 * DOMActivate event.
3563 if (mType != FormControlType::InputFile) {
3564 return false;
3567 Element* target = Element::FromEventTargetOrNull(aOriginalTarget);
3568 if (!target) {
3569 return false;
3572 return target->GetParent() == this &&
3573 target->IsRootOfNativeAnonymousSubtree() &&
3574 target->IsHTMLElement(nsGkAtoms::button);
3577 nsresult HTMLInputElement::MaybeInitPickers(EventChainPostVisitor& aVisitor) {
3578 // Open a file picker when we receive a click on a <input type='file'>, or
3579 // open a color picker when we receive a click on a <input type='color'>.
3580 // A click is handled if it's the left mouse button.
3581 // We do not prevent non-trusted click because authors can already use
3582 // .click(). However, the pickers will check and consume user activation.
3583 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3584 if (!(mouseEvent && mouseEvent->IsLeftClickEvent())) {
3585 return NS_OK;
3587 if (mType == FormControlType::InputFile) {
3588 // If the user clicked on the "Choose folder..." button we open the
3589 // directory picker, else we open the file picker.
3590 FilePickerType type = FILE_PICKER_FILE;
3591 nsIContent* target =
3592 nsIContent::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget);
3593 if (target && target->FindFirstNonChromeOnlyAccessContent() == this &&
3594 StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
3595 HasAttr(nsGkAtoms::webkitdirectory)) {
3596 type = FILE_PICKER_DIRECTORY;
3598 return InitFilePicker(type);
3600 if (mType == FormControlType::InputColor) {
3601 return InitColorPicker();
3604 return NS_OK;
3608 * Return true if the input event should be ignored because of its modifiers.
3609 * Control is treated specially, since sometimes we ignore it, and sometimes
3610 * we don't (for webcompat reasons).
3612 static bool IgnoreInputEventWithModifier(const WidgetInputEvent& aEvent,
3613 bool ignoreControl) {
3614 return (ignoreControl && aEvent.IsControl()) ||
3615 aEvent.IsAltGraph()
3616 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
3617 // Meta key is the Windows Logo key on Windows and Linux which may
3618 // assign some special meaning for the events while it's pressed.
3619 // On the other hand, it's a normal modifier in macOS and Android.
3620 // Therefore, We should ignore it only in Win/Linux.
3621 || aEvent.IsMeta()
3622 #endif
3623 || aEvent.IsFn();
3626 bool HTMLInputElement::StepsInputValue(
3627 const WidgetKeyboardEvent& aEvent) const {
3628 if (mType != FormControlType::InputNumber) {
3629 return false;
3631 if (aEvent.mMessage != eKeyPress) {
3632 return false;
3634 if (!aEvent.IsTrusted()) {
3635 return false;
3637 if (aEvent.mKeyCode != NS_VK_UP && aEvent.mKeyCode != NS_VK_DOWN) {
3638 return false;
3640 if (IgnoreInputEventWithModifier(aEvent, false)) {
3641 return false;
3643 if (aEvent.DefaultPrevented()) {
3644 return false;
3646 if (!IsMutable()) {
3647 return false;
3649 return true;
3652 static bool ActivatesWithKeyboard(FormControlType aType, uint32_t aKeyCode) {
3653 switch (aType) {
3654 case FormControlType::InputCheckbox:
3655 case FormControlType::InputRadio:
3656 // Checkbox and Radio try to submit on Enter press
3657 return aKeyCode != NS_VK_RETURN;
3658 case FormControlType::InputButton:
3659 case FormControlType::InputReset:
3660 case FormControlType::InputSubmit:
3661 case FormControlType::InputFile:
3662 case FormControlType::InputImage: // Bug 34418
3663 case FormControlType::InputColor:
3664 return true;
3665 default:
3666 return false;
3670 nsresult HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
3671 if (aVisitor.mEvent->mMessage == eBlur) {
3672 if (mIsDraggingRange) {
3673 FinishRangeThumbDrag();
3674 } else if (mNumberControlSpinnerIsSpinning) {
3675 StopNumberControlSpinnerSpin();
3679 nsresult rv = NS_OK;
3680 auto oldType = FormControlType(NS_CONTROL_TYPE(aVisitor.mItemFlags));
3682 // Ideally we would make the default action for click and space just dispatch
3683 // DOMActivate, and the default action for DOMActivate flip the checkbox/
3684 // radio state and fire onchange. However, for backwards compatibility, we
3685 // need to flip the state before firing click, and we need to fire click
3686 // when space is pressed. So, we just nest the firing of DOMActivate inside
3687 // the click event handling, and allow cancellation of DOMActivate to cancel
3688 // the click.
3689 if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault &&
3690 !IsSingleLineTextControl(true) && mType != FormControlType::InputNumber) {
3691 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3692 if (mouseEvent && mouseEvent->IsLeftClickEvent() &&
3693 OwnerDoc()->MayHaveDOMActivateListeners() &&
3694 !ShouldPreventDOMActivateDispatch(aVisitor.mEvent->mOriginalTarget)) {
3695 // DOMActive event should be trusted since the activation is actually
3696 // occurred even if the cause is an untrusted click event.
3697 InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
3698 actEvent.mDetail = 1;
3700 if (RefPtr<PresShell> presShell =
3701 aVisitor.mPresContext ? aVisitor.mPresContext->GetPresShell()
3702 : nullptr) {
3703 nsEventStatus status = nsEventStatus_eIgnore;
3704 mInInternalActivate = true;
3705 rv = presShell->HandleDOMEventWithTarget(this, &actEvent, &status);
3706 mInInternalActivate = false;
3708 // If activate is cancelled, we must do the same as when click is
3709 // cancelled (revert the checkbox to its original value).
3710 if (status == nsEventStatus_eConsumeNoDefault) {
3711 aVisitor.mEventStatus = status;
3717 bool preventDefault =
3718 aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault;
3719 if (IsDisabled() && oldType != FormControlType::InputCheckbox &&
3720 oldType != FormControlType::InputRadio) {
3721 // Behave as if defaultPrevented when the element becomes disabled by event
3722 // listeners. Checkboxes and radio buttons should still process clicks for
3723 // web compat. See:
3724 // https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
3725 preventDefault = true;
3728 if (NS_SUCCEEDED(rv)) {
3729 WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
3730 if (keyEvent && StepsInputValue(*keyEvent)) {
3731 StepNumberControlForUserEvent(keyEvent->mKeyCode == NS_VK_UP ? 1 : -1);
3732 FireChangeEventIfNeeded();
3733 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3734 } else if (!preventDefault) {
3735 if (keyEvent && ActivatesWithKeyboard(mType, keyEvent->mKeyCode) &&
3736 keyEvent->IsTrusted()) {
3737 // We maybe dispatch a synthesized click for keyboard activation.
3738 HandleKeyboardActivation(aVisitor);
3741 switch (aVisitor.mEvent->mMessage) {
3742 case eFocus: {
3743 // see if we should select the contents of the textbox. This happens
3744 // for text and password fields when the field was focused by the
3745 // keyboard or a navigation, the platform allows it, and it wasn't
3746 // just because we raised a window.
3748 // While it'd usually make sense, we don't do this for JS callers
3749 // because it causes some compat issues, see bug 1712724 for example.
3750 nsFocusManager* fm = nsFocusManager::GetFocusManager();
3751 if (fm && IsSingleLineTextControl(false) &&
3752 !aVisitor.mEvent->AsFocusEvent()->mFromRaise &&
3753 SelectTextFieldOnFocus()) {
3754 if (Document* document = GetComposedDoc()) {
3755 uint32_t lastFocusMethod =
3756 fm->GetLastFocusMethod(document->GetWindow());
3757 const bool shouldSelectAllOnFocus = [&] {
3758 if (lastFocusMethod & nsIFocusManager::FLAG_BYMOVEFOCUS) {
3759 return true;
3761 if (lastFocusMethod & nsIFocusManager::FLAG_BYJS) {
3762 return false;
3764 return bool(lastFocusMethod & nsIFocusManager::FLAG_BYKEY);
3765 }();
3766 if (shouldSelectAllOnFocus) {
3767 SelectAll();
3771 break;
3774 case eKeyDown: {
3775 // For compatibility with the other browsers, we should active this
3776 // element at least when a checkbox or a radio button.
3777 // TODO: Investigate which elements are activated by space key in the
3778 // other browsers.
3779 if (aVisitor.mPresContext && keyEvent->IsTrusted() && !IsDisabled() &&
3780 keyEvent->ShouldWorkAsSpaceKey() &&
3781 (mType == FormControlType::InputCheckbox ||
3782 mType == FormControlType::InputRadio)) {
3783 EventStateManager::SetActiveManager(
3784 aVisitor.mPresContext->EventStateManager(), this);
3786 break;
3789 case eKeyPress: {
3790 if (mType == FormControlType::InputRadio && keyEvent->IsTrusted() &&
3791 !keyEvent->IsAlt() && !keyEvent->IsControl() &&
3792 !keyEvent->IsMeta()) {
3793 // Radio button navigation needs to check visibility, so flush
3794 // to ensure visibility is up to date.
3795 if (Document* doc = GetComposedDoc()) {
3796 doc->FlushPendingNotifications(
3797 FlushType::EnsurePresShellInitAndFrames);
3799 rv = MaybeHandleRadioButtonNavigation(aVisitor, keyEvent->mKeyCode);
3803 * For some input types, if the user hits enter, the form is
3804 * submitted.
3806 * Bug 99920, bug 109463 and bug 147850:
3807 * (a) if there is a submit control in the form, click the first
3808 * submit control in the form.
3809 * (b) if there is just one text control in the form, submit by
3810 * sending a submit event directly to the form
3811 * (c) if there is more than one text input and no submit buttons, do
3812 * not submit, period.
3815 if (keyEvent->mKeyCode == NS_VK_RETURN && keyEvent->IsTrusted() &&
3816 (IsSingleLineTextControl(false, mType) ||
3817 IsDateTimeInputType(mType) ||
3818 mType == FormControlType::InputCheckbox ||
3819 mType == FormControlType::InputRadio)) {
3820 if (IsSingleLineTextControl(false, mType) ||
3821 IsDateTimeInputType(mType)) {
3822 FireChangeEventIfNeeded();
3825 if (aVisitor.mPresContext) {
3826 MaybeSubmitForm(aVisitor.mPresContext);
3830 if (mType == FormControlType::InputRange && keyEvent->IsTrusted() &&
3831 !keyEvent->IsAlt() && !keyEvent->IsControl() &&
3832 !keyEvent->IsMeta() &&
3833 (keyEvent->mKeyCode == NS_VK_LEFT ||
3834 keyEvent->mKeyCode == NS_VK_RIGHT ||
3835 keyEvent->mKeyCode == NS_VK_UP ||
3836 keyEvent->mKeyCode == NS_VK_DOWN ||
3837 keyEvent->mKeyCode == NS_VK_PAGE_UP ||
3838 keyEvent->mKeyCode == NS_VK_PAGE_DOWN ||
3839 keyEvent->mKeyCode == NS_VK_HOME ||
3840 keyEvent->mKeyCode == NS_VK_END)) {
3841 Decimal minimum = GetMinimum();
3842 Decimal maximum = GetMaximum();
3843 MOZ_ASSERT(minimum.isFinite() && maximum.isFinite());
3844 if (minimum < maximum) { // else the value is locked to the minimum
3845 Decimal value = GetValueAsDecimal();
3846 Decimal step = GetStep();
3847 if (step == kStepAny) {
3848 step = GetDefaultStep();
3850 MOZ_ASSERT(value.isFinite() && step.isFinite());
3851 Decimal newValue;
3852 switch (keyEvent->mKeyCode) {
3853 case NS_VK_LEFT:
3854 newValue = value +
3855 (GetComputedDirectionality() == Directionality::Rtl
3856 ? step
3857 : -step);
3858 break;
3859 case NS_VK_RIGHT:
3860 newValue = value +
3861 (GetComputedDirectionality() == Directionality::Rtl
3862 ? -step
3863 : step);
3864 break;
3865 case NS_VK_UP:
3866 // Even for horizontal range, "up" means "increase"
3867 newValue = value + step;
3868 break;
3869 case NS_VK_DOWN:
3870 // Even for horizontal range, "down" means "decrease"
3871 newValue = value - step;
3872 break;
3873 case NS_VK_HOME:
3874 newValue = minimum;
3875 break;
3876 case NS_VK_END:
3877 newValue = maximum;
3878 break;
3879 case NS_VK_PAGE_UP:
3880 // For PgUp/PgDn we jump 10% of the total range, unless step
3881 // requires us to jump more.
3882 newValue =
3883 value + std::max(step, (maximum - minimum) / Decimal(10));
3884 break;
3885 case NS_VK_PAGE_DOWN:
3886 newValue =
3887 value - std::max(step, (maximum - minimum) / Decimal(10));
3888 break;
3890 SetValueOfRangeForUserEvent(newValue);
3891 FireChangeEventIfNeeded();
3892 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3896 } break; // eKeyPress
3898 case eMouseDown:
3899 case eMouseUp:
3900 case eMouseDoubleClick: {
3901 // cancel all of these events for buttons
3902 // XXXsmaug Why?
3903 WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
3904 if (mouseEvent->mButton == MouseButton::eMiddle ||
3905 mouseEvent->mButton == MouseButton::eSecondary) {
3906 if (mType == FormControlType::InputButton ||
3907 mType == FormControlType::InputReset ||
3908 mType == FormControlType::InputSubmit) {
3909 if (aVisitor.mDOMEvent) {
3910 aVisitor.mDOMEvent->StopPropagation();
3911 } else {
3912 rv = NS_ERROR_FAILURE;
3916 if (mType == FormControlType::InputNumber &&
3917 aVisitor.mEvent->IsTrusted()) {
3918 if (mouseEvent->mButton == MouseButton::ePrimary &&
3919 !IgnoreInputEventWithModifier(*mouseEvent, false)) {
3920 nsNumberControlFrame* numberControlFrame =
3921 do_QueryFrame(GetPrimaryFrame());
3922 if (numberControlFrame) {
3923 if (aVisitor.mEvent->mMessage == eMouseDown && IsMutable()) {
3924 switch (numberControlFrame->GetSpinButtonForPointerEvent(
3925 aVisitor.mEvent->AsMouseEvent())) {
3926 case nsNumberControlFrame::eSpinButtonUp:
3927 StepNumberControlForUserEvent(1);
3928 mNumberControlSpinnerSpinsUp = true;
3929 StartNumberControlSpinnerSpin();
3930 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3931 break;
3932 case nsNumberControlFrame::eSpinButtonDown:
3933 StepNumberControlForUserEvent(-1);
3934 mNumberControlSpinnerSpinsUp = false;
3935 StartNumberControlSpinnerSpin();
3936 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
3937 break;
3942 if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
3943 // We didn't handle this to step up/down. Whatever this was, be
3944 // aggressive about stopping the spin. (And don't set
3945 // nsEventStatus_eConsumeNoDefault after doing so, since that
3946 // might prevent, say, the context menu from opening.)
3947 StopNumberControlSpinnerSpin();
3950 break;
3952 case eWheel: {
3953 if (StaticPrefs::
3954 dom_input_number_and_range_modified_by_mousewheel()) {
3955 // Handle wheel events as increasing / decreasing the input
3956 // element's value when it's focused and it's type is number or
3957 // range.
3958 WidgetWheelEvent* wheelEvent = aVisitor.mEvent->AsWheelEvent();
3959 if (!aVisitor.mEvent->DefaultPrevented() &&
3960 aVisitor.mEvent->IsTrusted() && IsMutable() && wheelEvent &&
3961 wheelEvent->mDeltaY != 0 &&
3962 wheelEvent->mDeltaMode != WheelEvent_Binding::DOM_DELTA_PIXEL) {
3963 if (mType == FormControlType::InputNumber) {
3964 if (nsFocusManager::GetFocusedElementStatic() == this) {
3965 StepNumberControlForUserEvent(wheelEvent->mDeltaY > 0 ? -1
3966 : 1);
3967 FireChangeEventIfNeeded();
3968 aVisitor.mEvent->PreventDefault();
3970 } else if (mType == FormControlType::InputRange &&
3971 nsFocusManager::GetFocusedElementStatic() == this &&
3972 GetMinimum() < GetMaximum()) {
3973 Decimal value = GetValueAsDecimal();
3974 Decimal step = GetStep();
3975 if (step == kStepAny) {
3976 step = GetDefaultStep();
3978 MOZ_ASSERT(value.isFinite() && step.isFinite());
3979 SetValueOfRangeForUserEvent(
3980 wheelEvent->mDeltaY < 0 ? value + step : value - step);
3981 FireChangeEventIfNeeded();
3982 aVisitor.mEvent->PreventDefault();
3986 break;
3988 case ePointerClick: {
3989 if (!aVisitor.mEvent->DefaultPrevented() &&
3990 aVisitor.mEvent->IsTrusted() &&
3991 aVisitor.mEvent->AsMouseEvent()->mButton ==
3992 MouseButton::ePrimary) {
3993 // TODO(emilio): Handling this should ideally not move focus.
3994 if (mType == FormControlType::InputSearch) {
3995 if (nsSearchControlFrame* searchControlFrame =
3996 do_QueryFrame(GetPrimaryFrame())) {
3997 Element* clearButton = searchControlFrame->GetButton();
3998 if (clearButton &&
3999 aVisitor.mEvent->mOriginalTarget == clearButton) {
4000 SetUserInput(EmptyString(),
4001 *nsContentUtils::GetSystemPrincipal());
4002 // TODO(emilio): This should focus the input, but calling
4003 // SetFocus(this, FLAG_NOSCROLL) for some reason gets us into
4004 // an inconsistent state where we're focused but don't match
4005 // :focus-visible / :focus.
4008 } else if (mType == FormControlType::InputPassword) {
4009 if (nsTextControlFrame* textControlFrame =
4010 do_QueryFrame(GetPrimaryFrame())) {
4011 auto* reveal = textControlFrame->GetButton();
4012 if (reveal && aVisitor.mEvent->mOriginalTarget == reveal) {
4013 SetRevealPassword(!RevealPassword());
4014 // TODO(emilio): This should focus the input, but calling
4015 // SetFocus(this, FLAG_NOSCROLL) for some reason gets us into
4016 // an inconsistent state where we're focused but don't match
4017 // :focus-visible / :focus.
4022 break;
4024 default:
4025 break;
4028 // Bug 1459231: Temporarily needed till links respect activation target,
4029 // then also remove NS_OUTER_ACTIVATE_EVENT. The appropriate
4030 // behavior/model for links is still under discussion (see
4031 // https://github.com/whatwg/html/issues/1576). For now, we aim for
4032 // consistency with other browsers.
4033 if (aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT) {
4034 switch (mType) {
4035 case FormControlType::InputReset:
4036 case FormControlType::InputSubmit:
4037 case FormControlType::InputImage:
4038 if (mForm) {
4039 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4041 break;
4042 case FormControlType::InputCheckbox:
4043 case FormControlType::InputRadio:
4044 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4045 break;
4046 default:
4047 break;
4051 } // if
4053 if (NS_SUCCEEDED(rv) && mType == FormControlType::InputRange) {
4054 PostHandleEventForRangeThumb(aVisitor);
4057 if (!preventDefault) {
4058 MOZ_TRY(MaybeInitPickers(aVisitor));
4060 return NS_OK;
4063 void EndSubmitClick(EventChainPostVisitor& aVisitor) {
4064 auto oldType = FormControlType(NS_CONTROL_TYPE(aVisitor.mItemFlags));
4065 if ((aVisitor.mItemFlags & NS_IN_SUBMIT_CLICK) &&
4066 (oldType == FormControlType::InputSubmit ||
4067 oldType == FormControlType::InputImage)) {
4068 nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mItemData));
4069 RefPtr<HTMLFormElement> form = HTMLFormElement::FromNodeOrNull(content);
4070 // Tell the form that we are about to exit a click handler,
4071 // so the form knows not to defer subsequent submissions.
4072 // The pending ones that were created during the handler
4073 // will be flushed or forgotten.
4074 form->OnSubmitClickEnd();
4075 // tell the form to flush a possible pending submission.
4076 // the reason is that the script returned false (the event was
4077 // not ignored) so if there is a stored submission, it needs to
4078 // be submitted immediately.
4079 form->FlushPendingSubmission();
4083 void HTMLInputElement::ActivationBehavior(EventChainPostVisitor& aVisitor) {
4084 auto oldType = FormControlType(NS_CONTROL_TYPE(aVisitor.mItemFlags));
4086 if (IsDisabled() && oldType != FormControlType::InputCheckbox &&
4087 oldType != FormControlType::InputRadio) {
4088 // Behave as if defaultPrevented when the element becomes disabled by event
4089 // listeners. Checkboxes and radio buttons should still process clicks for
4090 // web compat. See:
4091 // https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
4092 EndSubmitClick(aVisitor);
4093 return;
4096 if (mCheckedIsToggled) {
4097 SetUserInteracted(true);
4099 // Fire input event and then change event.
4100 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(this);
4101 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
4102 "Failed to dispatch input event");
4104 // FIXME: Why is this different than every other change event?
4105 nsContentUtils::DispatchTrustedEvent<WidgetEvent>(
4106 OwnerDoc(), static_cast<Element*>(this), eFormChange, CanBubble::eYes,
4107 Cancelable::eNo);
4108 #ifdef ACCESSIBILITY
4109 // Fire an event to notify accessibility
4110 if (mType == FormControlType::InputCheckbox) {
4111 if (nsContentUtils::MayHaveFormCheckboxStateChangeListeners()) {
4112 FireEventForAccessibility(this, eFormCheckboxStateChange);
4114 } else if (nsContentUtils::MayHaveFormRadioStateChangeListeners()) {
4115 FireEventForAccessibility(this, eFormRadioStateChange);
4116 // Fire event for the previous selected radio.
4117 nsCOMPtr<nsIContent> content = do_QueryInterface(aVisitor.mItemData);
4118 if (auto* previous = HTMLInputElement::FromNodeOrNull(content)) {
4119 FireEventForAccessibility(previous, eFormRadioStateChange);
4122 #endif
4125 switch (mType) {
4126 case FormControlType::InputReset:
4127 case FormControlType::InputSubmit:
4128 case FormControlType::InputImage:
4129 if (mForm) {
4130 // Hold a strong ref while dispatching
4131 RefPtr<HTMLFormElement> form(mForm);
4132 if (mType == FormControlType::InputReset) {
4133 form->MaybeReset(this);
4134 } else {
4135 form->MaybeSubmit(this);
4137 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4139 break;
4141 default:
4142 break;
4143 } // switch
4144 if (IsButtonControl()) {
4145 if (!GetInvokeTargetElement()) {
4146 HandlePopoverTargetAction();
4147 } else {
4148 HandleInvokeTargetAction();
4152 EndSubmitClick(aVisitor);
4155 void HTMLInputElement::LegacyCanceledActivationBehavior(
4156 EventChainPostVisitor& aVisitor) {
4157 bool originalCheckedValue =
4158 !!(aVisitor.mItemFlags & NS_ORIGINAL_CHECKED_VALUE);
4159 auto oldType = FormControlType(NS_CONTROL_TYPE(aVisitor.mItemFlags));
4161 if (mCheckedIsToggled) {
4162 // if it was canceled and a radio button, then set the old
4163 // selected btn to TRUE. if it is a checkbox then set it to its
4164 // original value (legacy-canceled-activation)
4165 if (oldType == FormControlType::InputRadio) {
4166 nsCOMPtr<nsIContent> content = do_QueryInterface(aVisitor.mItemData);
4167 HTMLInputElement* selectedRadioButton =
4168 HTMLInputElement::FromNodeOrNull(content);
4169 if (selectedRadioButton) {
4170 selectedRadioButton->SetChecked(true);
4172 // If there was no checked radio button or this one is no longer a
4173 // radio button we must reset it back to false to cancel the action.
4174 // See how the web of hack grows?
4175 if (!selectedRadioButton || mType != FormControlType::InputRadio) {
4176 DoSetChecked(false, true, true);
4178 } else if (oldType == FormControlType::InputCheckbox) {
4179 bool originalIndeterminateValue =
4180 !!(aVisitor.mItemFlags & NS_ORIGINAL_INDETERMINATE_VALUE);
4181 SetIndeterminateInternal(originalIndeterminateValue, false);
4182 DoSetChecked(originalCheckedValue, true, true);
4186 // Relevant for bug 242494: submit button with "submit(); return false;"
4187 EndSubmitClick(aVisitor);
4190 enum class RadioButtonMove { Back, Forward, None };
4191 nsresult HTMLInputElement::MaybeHandleRadioButtonNavigation(
4192 EventChainPostVisitor& aVisitor, uint32_t aKeyCode) {
4193 auto move = [&] {
4194 switch (aKeyCode) {
4195 case NS_VK_UP:
4196 return RadioButtonMove::Back;
4197 case NS_VK_DOWN:
4198 return RadioButtonMove::Forward;
4199 case NS_VK_LEFT:
4200 case NS_VK_RIGHT: {
4201 const bool isRtl = GetComputedDirectionality() == Directionality::Rtl;
4202 return isRtl == (aKeyCode == NS_VK_LEFT) ? RadioButtonMove::Forward
4203 : RadioButtonMove::Back;
4206 return RadioButtonMove::None;
4207 }();
4208 if (move == RadioButtonMove::None) {
4209 return NS_OK;
4211 // Arrow key pressed, focus+select prev/next radio button
4212 RefPtr<HTMLInputElement> selectedRadioButton;
4213 if (auto* container = GetCurrentRadioGroupContainer()) {
4214 nsAutoString name;
4215 GetAttr(nsGkAtoms::name, name);
4216 container->GetNextRadioButton(name, move == RadioButtonMove::Back, this,
4217 getter_AddRefs(selectedRadioButton));
4219 if (!selectedRadioButton) {
4220 return NS_OK;
4222 FocusOptions options;
4223 ErrorResult error;
4224 selectedRadioButton->Focus(options, CallerType::System, error);
4225 if (error.Failed()) {
4226 return error.StealNSResult();
4228 nsresult rv = DispatchSimulatedClick(
4229 selectedRadioButton, aVisitor.mEvent->IsTrusted(), aVisitor.mPresContext);
4230 if (NS_SUCCEEDED(rv)) {
4231 aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
4233 return rv;
4236 void HTMLInputElement::PostHandleEventForRangeThumb(
4237 EventChainPostVisitor& aVisitor) {
4238 MOZ_ASSERT(mType == FormControlType::InputRange);
4240 if (nsEventStatus_eConsumeNoDefault == aVisitor.mEventStatus ||
4241 !(aVisitor.mEvent->mClass == eMouseEventClass ||
4242 aVisitor.mEvent->mClass == eTouchEventClass ||
4243 aVisitor.mEvent->mClass == eKeyboardEventClass)) {
4244 return;
4247 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
4248 if (!rangeFrame && mIsDraggingRange) {
4249 CancelRangeThumbDrag();
4250 return;
4253 switch (aVisitor.mEvent->mMessage) {
4254 case eMouseDown:
4255 case eTouchStart: {
4256 if (mIsDraggingRange) {
4257 break;
4259 if (PresShell::GetCapturingContent()) {
4260 break; // don't start drag if someone else is already capturing
4262 WidgetInputEvent* inputEvent = aVisitor.mEvent->AsInputEvent();
4263 if (IgnoreInputEventWithModifier(*inputEvent, true)) {
4264 break; // ignore
4266 if (aVisitor.mEvent->mMessage == eMouseDown) {
4267 if (aVisitor.mEvent->AsMouseEvent()->mButtons ==
4268 MouseButtonsFlag::ePrimaryFlag) {
4269 StartRangeThumbDrag(inputEvent);
4270 } else if (mIsDraggingRange) {
4271 CancelRangeThumbDrag();
4273 } else {
4274 if (aVisitor.mEvent->AsTouchEvent()->mTouches.Length() == 1) {
4275 StartRangeThumbDrag(inputEvent);
4276 } else if (mIsDraggingRange) {
4277 CancelRangeThumbDrag();
4280 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4281 } break;
4283 case eMouseMove:
4284 case eTouchMove:
4285 if (!mIsDraggingRange) {
4286 break;
4288 if (PresShell::GetCapturingContent() != this) {
4289 // Someone else grabbed capture.
4290 CancelRangeThumbDrag();
4291 break;
4293 SetValueOfRangeForUserEvent(
4294 rangeFrame->GetValueAtEventPoint(aVisitor.mEvent->AsInputEvent()),
4295 SnapToTickMarks::Yes);
4296 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4297 break;
4299 case eMouseUp:
4300 case eTouchEnd:
4301 if (!mIsDraggingRange) {
4302 break;
4304 // We don't check to see whether we are the capturing content here and
4305 // call CancelRangeThumbDrag() if that is the case. We just finish off
4306 // the drag and set our final value (unless someone has called
4307 // preventDefault() and prevents us getting here).
4308 FinishRangeThumbDrag(aVisitor.mEvent->AsInputEvent());
4309 aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
4310 break;
4312 case eKeyPress:
4313 if (mIsDraggingRange &&
4314 aVisitor.mEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) {
4315 CancelRangeThumbDrag();
4317 break;
4319 case eTouchCancel:
4320 if (mIsDraggingRange) {
4321 CancelRangeThumbDrag();
4323 break;
4325 default:
4326 break;
4330 void HTMLInputElement::MaybeLoadImage() {
4331 // Our base URI may have changed; claim that our URI changed, and the
4332 // nsImageLoadingContent will decide whether a new image load is warranted.
4333 nsAutoString uri;
4334 if (mType == FormControlType::InputImage && GetAttr(nsGkAtoms::src, uri) &&
4335 (NS_FAILED(LoadImage(uri, false, true, eImageLoadType_Normal,
4336 mSrcTriggeringPrincipal)) ||
4337 !LoadingEnabled())) {
4338 CancelImageRequests(true);
4342 nsresult HTMLInputElement::BindToTree(BindContext& aContext, nsINode& aParent) {
4343 // If we are currently bound to a disconnected subtree root, remove
4344 // ourselves from it first.
4345 if (!mForm && mType == FormControlType::InputRadio) {
4346 RemoveFromRadioGroup();
4349 nsresult rv =
4350 nsGenericHTMLFormControlElementWithState::BindToTree(aContext, aParent);
4351 NS_ENSURE_SUCCESS(rv, rv);
4353 nsImageLoadingContent::BindToTree(aContext, aParent);
4355 if (mType == FormControlType::InputImage) {
4356 // Our base URI may have changed; claim that our URI changed, and the
4357 // nsImageLoadingContent will decide whether a new image load is warranted.
4358 if (HasAttr(nsGkAtoms::src)) {
4359 // Mark channel as urgent-start before load image if the image load is
4360 // initaiated by a user interaction.
4361 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
4363 nsContentUtils::AddScriptRunner(
4364 NewRunnableMethod("dom::HTMLInputElement::MaybeLoadImage", this,
4365 &HTMLInputElement::MaybeLoadImage));
4369 // Add radio to document if we don't have a form already (if we do it's
4370 // already been added into that group)
4371 if (!mForm && mType == FormControlType::InputRadio) {
4372 AddToRadioGroup();
4375 // Set direction based on value if dir=auto
4376 ResetDirFormAssociatedElement(this, false, HasDirAuto());
4378 // An element can't suffer from value missing if it is not in a document.
4379 // We have to check if we suffer from that as we are now in a document.
4380 UpdateValueMissingValidityState();
4382 // If there is a disabled fieldset in the parent chain, the element is now
4383 // barred from constraint validation and can't suffer from value missing
4384 // (call done before).
4385 UpdateBarredFromConstraintValidation();
4387 // And now make sure our state is up to date
4388 UpdateValidityElementStates(true);
4390 if (CreatesDateTimeWidget() && IsInComposedDoc()) {
4391 // Construct Shadow Root so web content can be hidden in the DOM.
4392 AttachAndSetUAShadowRoot(NotifyUAWidgetSetup::Yes, DelegatesFocus::Yes);
4395 MaybeDispatchLoginManagerEvents(mForm);
4397 return rv;
4400 void HTMLInputElement::MaybeDispatchLoginManagerEvents(HTMLFormElement* aForm) {
4401 // Don't disptach the event if the <input> is disconnected
4402 // or belongs to a disconnected form
4403 if (!IsInComposedDoc()) {
4404 return;
4407 nsString eventType;
4408 EventTarget* target = nullptr;
4410 if (mType == FormControlType::InputPassword) {
4411 // Don't fire another event if we have a pending event.
4412 if (aForm && aForm->mHasPendingPasswordEvent) {
4413 return;
4416 // TODO(Bug 1864404): Use one event for formless and form inputs.
4417 eventType = aForm ? u"DOMFormHasPassword"_ns : u"DOMInputPasswordAdded"_ns;
4419 if (aForm) {
4420 target = aForm;
4421 aForm->mHasPendingPasswordEvent = true;
4422 } else {
4423 target = this;
4426 } else if (mType == FormControlType::InputEmail ||
4427 mType == FormControlType::InputText) {
4428 // Don't fire a username event if:
4429 // - we have a pending event
4430 // - username only forms are not supported
4431 // fire event if we have a username field without a form with the
4432 // autcomplete value of username
4434 if (!StaticPrefs::signon_usernameOnlyForm_enabled()) {
4435 return;
4438 if (aForm) {
4439 if (aForm->mHasPendingPossibleUsernameEvent) {
4440 return;
4442 aForm->mHasPendingPossibleUsernameEvent = true;
4443 target = aForm;
4444 } else {
4445 nsAutoString autocompleteValue;
4446 GetAutocomplete(autocompleteValue);
4447 if (!autocompleteValue.EqualsASCII("username")) {
4448 return;
4450 target = GetComposedDoc();
4452 eventType = u"DOMPossibleUsernameInputAdded"_ns;
4453 } else {
4454 return;
4457 RefPtr<AsyncEventDispatcher> dispatcher = new AsyncEventDispatcher(
4458 target, eventType, CanBubble::eYes, ChromeOnlyDispatch::eYes);
4459 dispatcher->PostDOMEvent();
4462 void HTMLInputElement::UnbindFromTree(UnbindContext& aContext) {
4463 if (mType == FormControlType::InputPassword) {
4464 MaybeFireInputPasswordRemoved();
4467 // If we have a form and are unbound from it,
4468 // nsGenericHTMLFormControlElementWithState::UnbindFromTree() will unset the
4469 // form and that takes care of form's WillRemove so we just have to take care
4470 // of the case where we're removing from the document and we don't
4471 // have a form
4472 if (!mForm && mType == FormControlType::InputRadio) {
4473 RemoveFromRadioGroup();
4476 if (CreatesDateTimeWidget() && IsInComposedDoc()) {
4477 NotifyUAWidgetTeardown();
4480 nsImageLoadingContent::UnbindFromTree();
4481 nsGenericHTMLFormControlElementWithState::UnbindFromTree(aContext);
4483 // If we are contained within a disconnected subtree, attempt to add
4484 // ourselves to the subtree root's radio group.
4485 if (!mForm && mType == FormControlType::InputRadio) {
4486 AddToRadioGroup();
4489 // GetCurrentDoc is returning nullptr so we can update the value
4490 // missing validity state to reflect we are no longer into a doc.
4491 UpdateValueMissingValidityState();
4492 // We might be no longer disabled because of parent chain changed.
4493 UpdateBarredFromConstraintValidation();
4494 // And now make sure our state is up to date
4495 UpdateValidityElementStates(false);
4499 * @param aType InputElementTypes
4500 * @return true, iff SetRangeText applies to aType as specified at
4501 * https://html.spec.whatwg.org/#concept-input-apply.
4503 static bool SetRangeTextApplies(FormControlType aType) {
4504 return aType == FormControlType::InputText ||
4505 aType == FormControlType::InputSearch ||
4506 aType == FormControlType::InputUrl ||
4507 aType == FormControlType::InputTel ||
4508 aType == FormControlType::InputPassword;
4511 void HTMLInputElement::HandleTypeChange(FormControlType aNewType,
4512 bool aNotify) {
4513 FormControlType oldType = mType;
4514 MOZ_ASSERT(oldType != aNewType);
4516 mHasBeenTypePassword =
4517 mHasBeenTypePassword || aNewType == FormControlType::InputPassword;
4519 if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
4520 // Input element can represent very different kinds of UIs, and we may
4521 // need to flush styling even when focusing the already focused input
4522 // element.
4523 fm->NeedsFlushBeforeEventHandling(this);
4526 if (oldType == FormControlType::InputPassword &&
4527 State().HasState(ElementState::REVEALED)) {
4528 // Modify the state directly to avoid dispatching events.
4529 RemoveStates(ElementState::REVEALED, aNotify);
4532 if (aNewType == FormControlType::InputFile ||
4533 oldType == FormControlType::InputFile) {
4534 if (aNewType == FormControlType::InputFile) {
4535 mFileData.reset(new FileData());
4536 } else {
4537 mFileData->Unlink();
4538 mFileData = nullptr;
4542 if (oldType == FormControlType::InputRange && mIsDraggingRange) {
4543 CancelRangeThumbDrag(false);
4546 const ValueModeType oldValueMode = GetValueMode();
4547 nsAutoString oldValue;
4548 if (oldValueMode == VALUE_MODE_VALUE) {
4549 // Doesn't matter what caller type we pass here, since we know we're not a
4550 // file input anyway.
4551 GetValue(oldValue, CallerType::NonSystem);
4554 TextControlState::SelectionProperties sp;
4556 if (IsSingleLineTextControl(false) && mInputData.mState) {
4557 mInputData.mState->SyncUpSelectionPropertiesBeforeDestruction();
4558 sp = mInputData.mState->GetSelectionProperties();
4561 // We already have a copy of the value, lets free it and changes the type.
4562 FreeData();
4563 mType = aNewType;
4564 void* memory = mInputTypeMem;
4565 mInputType = InputType::Create(this, mType, memory);
4567 if (IsSingleLineTextControl()) {
4568 mInputData.mState = TextControlState::Construct(this);
4569 if (!sp.IsDefault()) {
4570 mInputData.mState->SetSelectionProperties(sp);
4574 // Whether placeholder applies might have changed.
4575 UpdatePlaceholderShownState();
4576 // Whether readonly applies might have changed.
4577 UpdateReadOnlyState(aNotify);
4578 UpdateCheckedState(aNotify);
4579 UpdateIndeterminateState(aNotify);
4580 const bool isDefault = IsRadioOrCheckbox()
4581 ? DefaultChecked()
4582 : (mForm && mForm->IsDefaultSubmitElement(this));
4583 SetStates(ElementState::DEFAULT, isDefault, aNotify);
4585 // https://html.spec.whatwg.org/#input-type-change
4586 switch (GetValueMode()) {
4587 case VALUE_MODE_DEFAULT:
4588 case VALUE_MODE_DEFAULT_ON:
4589 // 1. If the previous state of the element's type attribute put the value
4590 // IDL attribute in the value mode, and the element's value is not the
4591 // empty string, and the new state of the element's type attribute puts
4592 // the value IDL attribute in either the default mode or the default/on
4593 // mode, then set the element's value content attribute to the
4594 // element's value.
4595 if (oldValueMode == VALUE_MODE_VALUE && !oldValue.IsEmpty()) {
4596 SetAttr(kNameSpaceID_None, nsGkAtoms::value, oldValue, true);
4598 break;
4599 case VALUE_MODE_VALUE: {
4600 ValueSetterOptions options{ValueSetterOption::ByInternalAPI};
4601 if (!SetRangeTextApplies(oldType) && SetRangeTextApplies(mType)) {
4602 options +=
4603 ValueSetterOption::MoveCursorToBeginSetSelectionDirectionForward;
4605 if (oldValueMode != VALUE_MODE_VALUE) {
4606 // 2. Otherwise, if the previous state of the element's type attribute
4607 // put the value IDL attribute in any mode other than the value
4608 // mode, and the new state of the element's type attribute puts the
4609 // value IDL attribute in the value mode, then set the value of the
4610 // element to the value of the value content attribute, if there is
4611 // one, or the empty string otherwise, and then set the control's
4612 // dirty value flag to false.
4613 nsAutoString value;
4614 GetAttr(nsGkAtoms::value, value);
4615 SetValueInternal(value, options);
4616 SetValueChanged(false);
4617 } else if (mValueChanged) {
4618 // We're both in the "value" mode state, we need to make no change per
4619 // spec, but due to how we store the value internally we need to call
4620 // SetValueInternal, if our value had changed at all.
4621 // TODO: What should we do if SetValueInternal fails? (The allocation
4622 // may potentially be big, but most likely we've failed to allocate
4623 // before the type change.)
4624 SetValueInternal(oldValue, options);
4625 } else {
4626 // The value dirty flag is not set, so our value is based on our default
4627 // value. But our default value might be dependent on the type. Make
4628 // sure to set it so that state is consistent.
4629 SetDefaultValueAsValue();
4631 break;
4633 case VALUE_MODE_FILENAME:
4634 default:
4635 // 3. Otherwise, if the previous state of the element's type attribute
4636 // put the value IDL attribute in any mode other than the filename
4637 // mode, and the new state of the element's type attribute puts the
4638 // value IDL attribute in the filename mode, then set the value of the
4639 // element to the empty string.
4641 // Setting the attribute to the empty string is basically calling
4642 // ClearFiles, but there can't be any files.
4643 break;
4646 // Updating mFocusedValue in consequence:
4647 // If the new type fires a change event on blur, but the previous type
4648 // doesn't, we should set mFocusedValue to the current value.
4649 // Otherwise, if the new type doesn't fire a change event on blur, but the
4650 // previous type does, we should clear out mFocusedValue.
4651 if (MayFireChangeOnBlur(mType) && !MayFireChangeOnBlur(oldType)) {
4652 GetValue(mFocusedValue, CallerType::System);
4653 } else if (!IsSingleLineTextControl(false, mType) &&
4654 IsSingleLineTextControl(false, oldType)) {
4655 mFocusedValue.Truncate();
4658 // Update or clear our required states since we may have changed from a
4659 // required input type to a non-required input type or viceversa.
4660 if (DoesRequiredApply()) {
4661 const bool isRequired = HasAttr(nsGkAtoms::required);
4662 UpdateRequiredState(isRequired, aNotify);
4663 } else {
4664 RemoveStates(ElementState::REQUIRED_STATES, aNotify);
4667 UpdateHasRange(aNotify);
4669 // Update validity states, but not element state. We'll update
4670 // element state later, as part of this attribute change.
4671 UpdateAllValidityStatesButNotElementState();
4673 UpdateApzAwareFlag();
4675 UpdateBarredFromConstraintValidation();
4677 // Changing type might change auto directionality of this or the assigned slot
4678 const bool autoDirAssociated = IsAutoDirectionalityAssociated(mType);
4679 if (IsAutoDirectionalityAssociated(oldType) != autoDirAssociated) {
4680 ResetDirFormAssociatedElement(this, aNotify, true);
4682 // Special case for <input type=tel> as specified in
4683 // https://html.spec.whatwg.org/multipage/dom.html#the-directionality
4684 if (!HasDirAuto() && (oldType == FormControlType::InputTel ||
4685 mType == FormControlType::InputTel)) {
4686 RecomputeDirectionality(this, aNotify);
4689 if (oldType == FormControlType::InputImage ||
4690 mType == FormControlType::InputImage) {
4691 if (oldType == FormControlType::InputImage) {
4692 // We're no longer an image input. Cancel our image requests, if we have
4693 // any.
4694 CancelImageRequests(aNotify);
4695 RemoveStates(ElementState::BROKEN, aNotify);
4696 } else {
4697 // We just got switched to be an image input; we should see whether we
4698 // have an image to load;
4699 bool hasSrc = false;
4700 if (aNotify) {
4701 nsAutoString src;
4702 if ((hasSrc = GetAttr(nsGkAtoms::src, src))) {
4703 // Mark channel as urgent-start before load image if the image load is
4704 // initiated by a user interaction.
4705 mUseUrgentStartForChannel = UserActivation::IsHandlingUserInput();
4707 LoadImage(src, false, aNotify, eImageLoadType_Normal,
4708 mSrcTriggeringPrincipal);
4710 } else {
4711 hasSrc = HasAttr(nsGkAtoms::src);
4713 if (!hasSrc) {
4714 AddStates(ElementState::BROKEN, aNotify);
4717 // We should update our mapped attribute mapping function.
4718 if (mAttrs.HasAttrs() && !mAttrs.IsPendingMappedAttributeEvaluation()) {
4719 mAttrs.InfallibleMarkAsPendingPresAttributeEvaluation();
4720 if (auto* doc = GetComposedDoc()) {
4721 doc->ScheduleForPresAttrEvaluation(this);
4726 MaybeDispatchLoginManagerEvents(mForm);
4728 if (IsInComposedDoc()) {
4729 if (CreatesDateTimeWidget(oldType)) {
4730 if (!CreatesDateTimeWidget()) {
4731 // Switch away from date/time type.
4732 NotifyUAWidgetTeardown();
4733 } else {
4734 // Switch between date and time.
4735 NotifyUAWidgetSetupOrChange();
4737 } else if (CreatesDateTimeWidget()) {
4738 // Switch to date/time type.
4739 AttachAndSetUAShadowRoot(NotifyUAWidgetSetup::Yes, DelegatesFocus::Yes);
4741 // If we're becoming a text control and have focus, make sure to show focus
4742 // rings.
4743 if (State().HasState(ElementState::FOCUS) && IsSingleLineTextControl() &&
4744 !IsSingleLineTextControl(/* aExcludePassword = */ false, oldType)) {
4745 AddStates(ElementState::FOCUSRING);
4750 void HTMLInputElement::MaybeSnapToTickMark(Decimal& aValue) {
4751 nsRangeFrame* rangeFrame = do_QueryFrame(GetPrimaryFrame());
4752 if (!rangeFrame) {
4753 return;
4755 auto tickMark = rangeFrame->NearestTickMark(aValue);
4756 if (tickMark.isNaN()) {
4757 return;
4759 auto rangeFrameSize = CSSPixel::FromAppUnits(rangeFrame->GetSize());
4760 CSSCoord rangeTrackLength;
4761 if (rangeFrame->IsHorizontal()) {
4762 rangeTrackLength = rangeFrameSize.width;
4763 } else {
4764 rangeTrackLength = rangeFrameSize.height;
4766 auto stepBase = GetStepBase();
4767 auto distanceToTickMark =
4768 rangeTrackLength * float(rangeFrame->GetDoubleAsFractionOfRange(
4769 stepBase + (tickMark - aValue).abs()));
4770 const CSSCoord magnetEffectRange(
4771 StaticPrefs::dom_range_element_magnet_effect_threshold());
4772 if (distanceToTickMark <= magnetEffectRange) {
4773 aValue = tickMark;
4777 void HTMLInputElement::SanitizeValue(nsAString& aValue,
4778 SanitizationKind aKind) const {
4779 NS_ASSERTION(mDoneCreating, "The element creation should be finished!");
4781 switch (mType) {
4782 case FormControlType::InputText:
4783 case FormControlType::InputSearch:
4784 case FormControlType::InputTel:
4785 case FormControlType::InputPassword: {
4786 aValue.StripCRLF();
4787 } break;
4788 case FormControlType::InputEmail: {
4789 aValue.StripCRLF();
4790 aValue = nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
4791 aValue);
4793 if (Multiple() && !aValue.IsEmpty()) {
4794 nsAutoString oldValue(aValue);
4795 HTMLSplitOnSpacesTokenizer tokenizer(oldValue, ',');
4796 aValue.Truncate(0);
4797 aValue.Append(tokenizer.nextToken());
4798 while (tokenizer.hasMoreTokens() ||
4799 tokenizer.separatorAfterCurrentToken()) {
4800 aValue.Append(',');
4801 aValue.Append(tokenizer.nextToken());
4804 } break;
4805 case FormControlType::InputUrl: {
4806 aValue.StripCRLF();
4808 aValue = nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
4809 aValue);
4810 } break;
4811 case FormControlType::InputNumber: {
4812 if (aKind == SanitizationKind::ForValueSetter && !aValue.IsEmpty() &&
4813 (aValue.First() == '+' || aValue.Last() == '.')) {
4814 // A value with a leading plus or trailing dot should fail to parse.
4815 // However, the localized parser accepts this, and when we convert it
4816 // back to a Decimal, it disappears. So, we need to check first.
4818 // FIXME(emilio): Should we just use the unlocalized parser
4819 // (StringToDecimal) for the value setter? Other browsers don't seem to
4820 // allow setting localized strings there, and that way we don't need
4821 // this special-case.
4822 aValue.Truncate();
4823 return;
4826 InputType::StringToNumberResult result =
4827 mInputType->ConvertStringToNumber(aValue);
4828 if (!result.mResult.isFinite()) {
4829 aValue.Truncate();
4830 return;
4832 switch (aKind) {
4833 case SanitizationKind::ForValueGetter: {
4834 // If the default non-localized algorithm parses the value, then we're
4835 // done, don't un-localize it, to avoid precision loss, and to
4836 // preserve scientific notation as well for example.
4837 if (!result.mLocalized) {
4838 return;
4840 // For the <input type=number> value getter, we return the unlocalized
4841 // value if it doesn't parse as StringToDecimal, for compat with other
4842 // browsers.
4843 char buf[32];
4844 DebugOnly<bool> ok = result.mResult.toString(buf, ArrayLength(buf));
4845 aValue.AssignASCII(buf);
4846 MOZ_ASSERT(ok, "buf not big enough");
4847 break;
4849 case SanitizationKind::ForDisplay:
4850 case SanitizationKind::ForValueSetter: {
4851 // We localize as needed, but if both the localized and unlocalized
4852 // version parse with the generic parser, we just use the unlocalized
4853 // one, to preserve the input as much as possible.
4855 // FIXME(emilio, bug 1622808): Localization should ideally be more
4856 // input-preserving.
4857 nsString localizedValue;
4858 mInputType->ConvertNumberToString(result.mResult, localizedValue);
4859 if (!StringToDecimal(localizedValue).isFinite()) {
4860 aValue = std::move(localizedValue);
4862 break;
4865 break;
4867 case FormControlType::InputRange: {
4868 Decimal minimum = GetMinimum();
4869 Decimal maximum = GetMaximum();
4870 MOZ_ASSERT(minimum.isFinite() && maximum.isFinite(),
4871 "type=range should have a default maximum/minimum");
4873 // We use this to avoid modifying the string unnecessarily, since that
4874 // may introduce rounding. This is set to true only if the value we
4875 // parse out from aValue needs to be sanitized.
4876 bool needSanitization = false;
4878 Decimal value = mInputType->ConvertStringToNumber(aValue).mResult;
4879 if (!value.isFinite()) {
4880 needSanitization = true;
4881 // Set value to midway between minimum and maximum.
4882 value = maximum <= minimum ? minimum
4883 : minimum + (maximum - minimum) / Decimal(2);
4884 } else if (value < minimum || maximum < minimum) {
4885 needSanitization = true;
4886 value = minimum;
4887 } else if (value > maximum) {
4888 needSanitization = true;
4889 value = maximum;
4892 Decimal step = GetStep();
4893 if (step != kStepAny) {
4894 Decimal stepBase = GetStepBase();
4895 // There could be rounding issues below when dealing with fractional
4896 // numbers, but let's ignore that until ECMAScript supplies us with a
4897 // decimal number type.
4898 Decimal deltaToStep = NS_floorModulo(value - stepBase, step);
4899 if (deltaToStep != Decimal(0)) {
4900 // "suffering from a step mismatch"
4901 // Round the element's value to the nearest number for which the
4902 // element would not suffer from a step mismatch, and which is
4903 // greater than or equal to the minimum, and, if the maximum is not
4904 // less than the minimum, which is less than or equal to the
4905 // maximum, if there is a number that matches these constraints:
4906 MOZ_ASSERT(deltaToStep > Decimal(0),
4907 "stepBelow/stepAbove will be wrong");
4908 Decimal stepBelow = value - deltaToStep;
4909 Decimal stepAbove = value - deltaToStep + step;
4910 Decimal halfStep = step / Decimal(2);
4911 bool stepAboveIsClosest = (stepAbove - value) <= halfStep;
4912 bool stepAboveInRange = stepAbove >= minimum && stepAbove <= maximum;
4913 bool stepBelowInRange = stepBelow >= minimum && stepBelow <= maximum;
4915 if ((stepAboveIsClosest || !stepBelowInRange) && stepAboveInRange) {
4916 needSanitization = true;
4917 value = stepAbove;
4918 } else if ((!stepAboveIsClosest || !stepAboveInRange) &&
4919 stepBelowInRange) {
4920 needSanitization = true;
4921 value = stepBelow;
4926 if (needSanitization) {
4927 char buf[32];
4928 DebugOnly<bool> ok = value.toString(buf, ArrayLength(buf));
4929 aValue.AssignASCII(buf);
4930 MOZ_ASSERT(ok, "buf not big enough");
4932 } break;
4933 case FormControlType::InputDate: {
4934 if (!aValue.IsEmpty() && !IsValidDate(aValue)) {
4935 aValue.Truncate();
4937 } break;
4938 case FormControlType::InputTime: {
4939 if (!aValue.IsEmpty() && !IsValidTime(aValue)) {
4940 aValue.Truncate();
4942 } break;
4943 case FormControlType::InputMonth: {
4944 if (!aValue.IsEmpty() && !IsValidMonth(aValue)) {
4945 aValue.Truncate();
4947 } break;
4948 case FormControlType::InputWeek: {
4949 if (!aValue.IsEmpty() && !IsValidWeek(aValue)) {
4950 aValue.Truncate();
4952 } break;
4953 case FormControlType::InputDatetimeLocal: {
4954 if (!aValue.IsEmpty() && !IsValidDateTimeLocal(aValue)) {
4955 aValue.Truncate();
4956 } else {
4957 NormalizeDateTimeLocal(aValue);
4959 } break;
4960 case FormControlType::InputColor: {
4961 if (IsValidSimpleColor(aValue)) {
4962 ToLowerCase(aValue);
4963 } else {
4964 // Set default (black) color, if aValue wasn't parsed correctly.
4965 aValue.AssignLiteral("#000000");
4967 } break;
4968 default:
4969 break;
4973 Maybe<nscolor> HTMLInputElement::ParseSimpleColor(const nsAString& aColor) {
4974 // Input color string should be 7 length (i.e. a string representing a valid
4975 // simple color)
4976 if (aColor.Length() != 7 || aColor.First() != '#') {
4977 return {};
4980 const nsAString& withoutHash = StringTail(aColor, 6);
4981 nscolor color;
4982 if (!NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) {
4983 return {};
4986 return Some(color);
4989 bool HTMLInputElement::IsValidSimpleColor(const nsAString& aValue) const {
4990 if (aValue.Length() != 7 || aValue.First() != '#') {
4991 return false;
4994 for (int i = 1; i < 7; ++i) {
4995 if (!IsAsciiDigit(aValue[i]) && !(aValue[i] >= 'a' && aValue[i] <= 'f') &&
4996 !(aValue[i] >= 'A' && aValue[i] <= 'F')) {
4997 return false;
5000 return true;
5003 bool HTMLInputElement::IsLeapYear(uint32_t aYear) const {
5004 if ((aYear % 4 == 0 && aYear % 100 != 0) || (aYear % 400 == 0)) {
5005 return true;
5007 return false;
5010 uint32_t HTMLInputElement::DayOfWeek(uint32_t aYear, uint32_t aMonth,
5011 uint32_t aDay, bool isoWeek) const {
5012 MOZ_ASSERT(1 <= aMonth && aMonth <= 12, "month is in 1..12");
5013 MOZ_ASSERT(1 <= aDay && aDay <= 31, "day is in 1..31");
5015 // Tomohiko Sakamoto algorithm.
5016 int monthTable[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
5017 aYear -= aMonth < 3;
5019 uint32_t day = (aYear + aYear / 4 - aYear / 100 + aYear / 400 +
5020 monthTable[aMonth - 1] + aDay) %
5023 if (isoWeek) {
5024 return ((day + 6) % 7) + 1;
5027 return day;
5030 uint32_t HTMLInputElement::MaximumWeekInYear(uint32_t aYear) const {
5031 int day = DayOfWeek(aYear, 1, 1, true); // January 1.
5032 // A year starting on Thursday or a leap year starting on Wednesday has 53
5033 // weeks. All other years have 52 weeks.
5034 return day == 4 || (day == 3 && IsLeapYear(aYear)) ? kMaximumWeekInYear
5035 : kMaximumWeekInYear - 1;
5038 bool HTMLInputElement::IsValidWeek(const nsAString& aValue) const {
5039 uint32_t year, week;
5040 return ParseWeek(aValue, &year, &week);
5043 bool HTMLInputElement::IsValidMonth(const nsAString& aValue) const {
5044 uint32_t year, month;
5045 return ParseMonth(aValue, &year, &month);
5048 bool HTMLInputElement::IsValidDate(const nsAString& aValue) const {
5049 uint32_t year, month, day;
5050 return ParseDate(aValue, &year, &month, &day);
5053 bool HTMLInputElement::IsValidDateTimeLocal(const nsAString& aValue) const {
5054 uint32_t year, month, day, time;
5055 return ParseDateTimeLocal(aValue, &year, &month, &day, &time);
5058 bool HTMLInputElement::ParseYear(const nsAString& aValue,
5059 uint32_t* aYear) const {
5060 if (aValue.Length() < 4) {
5061 return false;
5064 return DigitSubStringToNumber(aValue, 0, aValue.Length(), aYear) &&
5065 *aYear > 0;
5068 bool HTMLInputElement::ParseMonth(const nsAString& aValue, uint32_t* aYear,
5069 uint32_t* aMonth) const {
5070 // Parse the year, month values out a string formatted as 'yyyy-mm'.
5071 if (aValue.Length() < 7) {
5072 return false;
5075 uint32_t endOfYearOffset = aValue.Length() - 3;
5076 if (aValue[endOfYearOffset] != '-') {
5077 return false;
5080 const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
5081 if (!ParseYear(yearStr, aYear)) {
5082 return false;
5085 return DigitSubStringToNumber(aValue, endOfYearOffset + 1, 2, aMonth) &&
5086 *aMonth > 0 && *aMonth <= 12;
5089 bool HTMLInputElement::ParseWeek(const nsAString& aValue, uint32_t* aYear,
5090 uint32_t* aWeek) const {
5091 // Parse the year, month values out a string formatted as 'yyyy-Www'.
5092 if (aValue.Length() < 8) {
5093 return false;
5096 uint32_t endOfYearOffset = aValue.Length() - 4;
5097 if (aValue[endOfYearOffset] != '-') {
5098 return false;
5101 if (aValue[endOfYearOffset + 1] != 'W') {
5102 return false;
5105 const nsAString& yearStr = Substring(aValue, 0, endOfYearOffset);
5106 if (!ParseYear(yearStr, aYear)) {
5107 return false;
5110 return DigitSubStringToNumber(aValue, endOfYearOffset + 2, 2, aWeek) &&
5111 *aWeek > 0 && *aWeek <= MaximumWeekInYear(*aYear);
5114 bool HTMLInputElement::ParseDate(const nsAString& aValue, uint32_t* aYear,
5115 uint32_t* aMonth, uint32_t* aDay) const {
5117 * Parse the year, month, day values out a date string formatted as
5118 * yyyy-mm-dd. -The year must be 4 or more digits long, and year > 0 -The
5119 * month must be exactly 2 digits long, and 01 <= month <= 12 -The day must be
5120 * exactly 2 digit long, and 01 <= day <= maxday Where maxday is the number of
5121 * days in the month 'month' and year 'year'
5123 if (aValue.Length() < 10) {
5124 return false;
5127 uint32_t endOfMonthOffset = aValue.Length() - 3;
5128 if (aValue[endOfMonthOffset] != '-') {
5129 return false;
5132 const nsAString& yearMonthStr = Substring(aValue, 0, endOfMonthOffset);
5133 if (!ParseMonth(yearMonthStr, aYear, aMonth)) {
5134 return false;
5137 return DigitSubStringToNumber(aValue, endOfMonthOffset + 1, 2, aDay) &&
5138 *aDay > 0 && *aDay <= NumberOfDaysInMonth(*aMonth, *aYear);
5141 bool HTMLInputElement::ParseDateTimeLocal(const nsAString& aValue,
5142 uint32_t* aYear, uint32_t* aMonth,
5143 uint32_t* aDay,
5144 uint32_t* aTime) const {
5145 // Parse the year, month, day and time values out a string formatted as
5146 // 'yyyy-mm-ddThh:mm[:ss.s] or 'yyyy-mm-dd hh:mm[:ss.s]', where fractions of
5147 // seconds can be 1 to 3 digits.
5148 // The minimum length allowed is 16, which is of the form 'yyyy-mm-ddThh:mm'
5149 // or 'yyyy-mm-dd hh:mm'.
5150 if (aValue.Length() < 16) {
5151 return false;
5154 int32_t sepIndex = aValue.FindChar('T');
5155 if (sepIndex == -1) {
5156 sepIndex = aValue.FindChar(' ');
5158 if (sepIndex == -1) {
5159 return false;
5163 const nsAString& dateStr = Substring(aValue, 0, sepIndex);
5164 if (!ParseDate(dateStr, aYear, aMonth, aDay)) {
5165 return false;
5168 const nsAString& timeStr =
5169 Substring(aValue, sepIndex + 1, aValue.Length() - sepIndex + 1);
5170 if (!ParseTime(timeStr, aTime)) {
5171 return false;
5174 return true;
5177 void HTMLInputElement::NormalizeDateTimeLocal(nsAString& aValue) const {
5178 if (aValue.IsEmpty()) {
5179 return;
5182 // Use 'T' as the separator between date string and time string.
5183 int32_t sepIndex = aValue.FindChar(' ');
5184 if (sepIndex != -1) {
5185 aValue.ReplaceLiteral(sepIndex, 1, u"T");
5186 } else {
5187 sepIndex = aValue.FindChar('T');
5190 // Time expressed as the shortest possible string, which is hh:mm.
5191 if ((aValue.Length() - sepIndex) == 6) {
5192 return;
5195 // Fractions of seconds part is optional, ommit it if it's 0.
5196 if ((aValue.Length() - sepIndex) > 9) {
5197 const uint32_t millisecSepIndex = sepIndex + 9;
5198 uint32_t milliseconds;
5199 if (!DigitSubStringToNumber(aValue, millisecSepIndex + 1,
5200 aValue.Length() - (millisecSepIndex + 1),
5201 &milliseconds)) {
5202 return;
5205 if (milliseconds != 0) {
5206 return;
5209 aValue.Cut(millisecSepIndex, aValue.Length() - millisecSepIndex);
5212 // Seconds part is optional, ommit it if it's 0.
5213 const uint32_t secondSepIndex = sepIndex + 6;
5214 uint32_t seconds;
5215 if (!DigitSubStringToNumber(aValue, secondSepIndex + 1,
5216 aValue.Length() - (secondSepIndex + 1),
5217 &seconds)) {
5218 return;
5221 if (seconds != 0) {
5222 return;
5225 aValue.Cut(secondSepIndex, aValue.Length() - secondSepIndex);
5228 double HTMLInputElement::DaysSinceEpochFromWeek(uint32_t aYear,
5229 uint32_t aWeek) const {
5230 double days = JS::DayFromYear(aYear) + (aWeek - 1) * 7;
5231 uint32_t dayOneIsoWeekday = DayOfWeek(aYear, 1, 1, true);
5233 // If day one of that year is on/before Thursday, we should subtract the
5234 // days that belong to last year in our first week, otherwise, our first
5235 // days belong to last year's last week, and we should add those days
5236 // back.
5237 if (dayOneIsoWeekday <= 4) {
5238 days -= (dayOneIsoWeekday - 1);
5239 } else {
5240 days += (7 - dayOneIsoWeekday + 1);
5243 return days;
5246 uint32_t HTMLInputElement::NumberOfDaysInMonth(uint32_t aMonth,
5247 uint32_t aYear) const {
5249 * Returns the number of days in a month.
5250 * Months that are |longMonths| always have 31 days.
5251 * Months that are not |longMonths| have 30 days except February (month 2).
5252 * February has 29 days during leap years which are years that are divisible
5253 * by 400. or divisible by 100 and 4. February has 28 days otherwise.
5256 static const bool longMonths[] = {true, false, true, false, true, false,
5257 true, true, false, true, false, true};
5258 MOZ_ASSERT(aMonth <= 12 && aMonth > 0);
5260 if (longMonths[aMonth - 1]) {
5261 return 31;
5264 if (aMonth != 2) {
5265 return 30;
5268 return IsLeapYear(aYear) ? 29 : 28;
5271 /* static */
5272 bool HTMLInputElement::DigitSubStringToNumber(const nsAString& aStr,
5273 uint32_t aStart, uint32_t aLen,
5274 uint32_t* aRetVal) {
5275 MOZ_ASSERT(aStr.Length() > (aStart + aLen - 1));
5277 for (uint32_t offset = 0; offset < aLen; ++offset) {
5278 if (!IsAsciiDigit(aStr[aStart + offset])) {
5279 return false;
5283 nsresult ec;
5284 *aRetVal = static_cast<uint32_t>(
5285 PromiseFlatString(Substring(aStr, aStart, aLen)).ToInteger(&ec));
5287 return NS_SUCCEEDED(ec);
5290 bool HTMLInputElement::IsValidTime(const nsAString& aValue) const {
5291 return ParseTime(aValue, nullptr);
5294 /* static */
5295 bool HTMLInputElement::ParseTime(const nsAString& aValue, uint32_t* aResult) {
5296 /* The string must have the following parts:
5297 * - HOURS: two digits, value being in [0, 23];
5298 * - Colon (:);
5299 * - MINUTES: two digits, value being in [0, 59];
5300 * - Optional:
5301 * - Colon (:);
5302 * - SECONDS: two digits, value being in [0, 59];
5303 * - Optional:
5304 * - DOT (.);
5305 * - FRACTIONAL SECONDS: one to three digits, no value range.
5308 // The following format is the shorter one allowed: "HH:MM".
5309 if (aValue.Length() < 5) {
5310 return false;
5313 uint32_t hours;
5314 if (!DigitSubStringToNumber(aValue, 0, 2, &hours) || hours > 23) {
5315 return false;
5318 // Hours/minutes separator.
5319 if (aValue[2] != ':') {
5320 return false;
5323 uint32_t minutes;
5324 if (!DigitSubStringToNumber(aValue, 3, 2, &minutes) || minutes > 59) {
5325 return false;
5328 if (aValue.Length() == 5) {
5329 if (aResult) {
5330 *aResult = ((hours * 60) + minutes) * 60000;
5332 return true;
5335 // The following format is the next shorter one: "HH:MM:SS".
5336 if (aValue.Length() < 8 || aValue[5] != ':') {
5337 return false;
5340 uint32_t seconds;
5341 if (!DigitSubStringToNumber(aValue, 6, 2, &seconds) || seconds > 59) {
5342 return false;
5345 if (aValue.Length() == 8) {
5346 if (aResult) {
5347 *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000;
5349 return true;
5352 // The string must follow this format now: "HH:MM:SS.{s,ss,sss}".
5353 // There can be 1 to 3 digits for the fractions of seconds.
5354 if (aValue.Length() == 9 || aValue.Length() > 12 || aValue[8] != '.') {
5355 return false;
5358 uint32_t fractionsSeconds;
5359 if (!DigitSubStringToNumber(aValue, 9, aValue.Length() - 9,
5360 &fractionsSeconds)) {
5361 return false;
5364 if (aResult) {
5365 *aResult = (((hours * 60) + minutes) * 60 + seconds) * 1000 +
5366 // NOTE: there is 10.0 instead of 10 and static_cast<int> because
5367 // some old [and stupid] compilers can't just do the right thing.
5368 fractionsSeconds *
5369 pow(10.0, static_cast<int>(3 - (aValue.Length() - 9)));
5372 return true;
5375 /* static */
5376 bool HTMLInputElement::IsDateTimeTypeSupported(
5377 FormControlType aDateTimeInputType) {
5378 switch (aDateTimeInputType) {
5379 case FormControlType::InputDate:
5380 case FormControlType::InputTime:
5381 case FormControlType::InputDatetimeLocal:
5382 return true;
5383 case FormControlType::InputMonth:
5384 case FormControlType::InputWeek:
5385 return StaticPrefs::dom_forms_datetime_others();
5386 default:
5387 return false;
5391 void HTMLInputElement::GetLastInteractiveValue(nsAString& aValue) {
5392 if (mLastValueChangeWasInteractive) {
5393 return GetValue(aValue, CallerType::System);
5395 if (TextControlState* state = GetEditorState()) {
5396 return aValue.Assign(
5397 state->LastInteractiveValueIfLastChangeWasNonInteractive());
5399 aValue.Truncate();
5402 bool HTMLInputElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
5403 const nsAString& aValue,
5404 nsIPrincipal* aMaybeScriptedPrincipal,
5405 nsAttrValue& aResult) {
5406 // We can't make these static_asserts because kInputDefaultType and
5407 // kInputTypeTable aren't constexpr.
5408 MOZ_ASSERT(
5409 FormControlType(kInputDefaultType->value) == FormControlType::InputText,
5410 "Someone forgot to update kInputDefaultType when adding a new "
5411 "input type.");
5412 MOZ_ASSERT(kInputTypeTable[ArrayLength(kInputTypeTable) - 1].tag == nullptr,
5413 "Last entry in the table must be the nullptr guard");
5414 MOZ_ASSERT(FormControlType(
5415 kInputTypeTable[ArrayLength(kInputTypeTable) - 2].value) ==
5416 FormControlType::InputText,
5417 "Next to last entry in the table must be the \"text\" entry");
5419 if (aNamespaceID == kNameSpaceID_None) {
5420 if (aAttribute == nsGkAtoms::type) {
5421 aResult.ParseEnumValue(aValue, kInputTypeTable, false, kInputDefaultType);
5422 auto newType = FormControlType(aResult.GetEnumValue());
5423 if (IsDateTimeInputType(newType) && !IsDateTimeTypeSupported(newType)) {
5424 // There's no public way to set an nsAttrValue to an enum value, but we
5425 // can just re-parse with a table that doesn't have any types other than
5426 // "text" in it.
5427 aResult.ParseEnumValue(aValue, kInputDefaultType, false,
5428 kInputDefaultType);
5431 return true;
5433 if (aAttribute == nsGkAtoms::width) {
5434 return aResult.ParseHTMLDimension(aValue);
5436 if (aAttribute == nsGkAtoms::height) {
5437 return aResult.ParseHTMLDimension(aValue);
5439 if (aAttribute == nsGkAtoms::maxlength) {
5440 return aResult.ParseNonNegativeIntValue(aValue);
5442 if (aAttribute == nsGkAtoms::minlength) {
5443 return aResult.ParseNonNegativeIntValue(aValue);
5445 if (aAttribute == nsGkAtoms::size) {
5446 return aResult.ParsePositiveIntValue(aValue);
5448 if (aAttribute == nsGkAtoms::align) {
5449 return ParseAlignValue(aValue, aResult);
5451 if (aAttribute == nsGkAtoms::formmethod) {
5452 return aResult.ParseEnumValue(aValue, kFormMethodTable, false);
5454 if (aAttribute == nsGkAtoms::formenctype) {
5455 return aResult.ParseEnumValue(aValue, kFormEnctypeTable, false);
5457 if (aAttribute == nsGkAtoms::autocomplete) {
5458 aResult.ParseAtomArray(aValue);
5459 return true;
5461 if (aAttribute == nsGkAtoms::capture) {
5462 return aResult.ParseEnumValue(aValue, kCaptureTable, false,
5463 kCaptureDefault);
5465 if (ParseImageAttribute(aAttribute, aValue, aResult)) {
5466 // We have to call |ParseImageAttribute| unconditionally since we
5467 // don't know if we're going to have a type="image" attribute yet,
5468 // (or could have it set dynamically in the future). See bug
5469 // 214077.
5470 return true;
5474 return TextControlElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
5475 aMaybeScriptedPrincipal, aResult);
5478 void HTMLInputElement::ImageInputMapAttributesIntoRule(
5479 MappedDeclarationsBuilder& aBuilder) {
5480 nsGenericHTMLFormControlElementWithState::MapImageBorderAttributeInto(
5481 aBuilder);
5482 nsGenericHTMLFormControlElementWithState::MapImageMarginAttributeInto(
5483 aBuilder);
5484 nsGenericHTMLFormControlElementWithState::MapImageSizeAttributesInto(
5485 aBuilder, MapAspectRatio::Yes);
5486 // Images treat align as "float"
5487 nsGenericHTMLFormControlElementWithState::MapImageAlignAttributeInto(
5488 aBuilder);
5489 nsGenericHTMLFormControlElementWithState::MapCommonAttributesInto(aBuilder);
5492 nsChangeHint HTMLInputElement::GetAttributeChangeHint(const nsAtom* aAttribute,
5493 int32_t aModType) const {
5494 nsChangeHint retval =
5495 nsGenericHTMLFormControlElementWithState::GetAttributeChangeHint(
5496 aAttribute, aModType);
5498 const bool isAdditionOrRemoval =
5499 aModType == MutationEvent_Binding::ADDITION ||
5500 aModType == MutationEvent_Binding::REMOVAL;
5502 const bool reconstruct = [&] {
5503 if (aAttribute == nsGkAtoms::type) {
5504 return true;
5507 if (PlaceholderApplies() && aAttribute == nsGkAtoms::placeholder &&
5508 isAdditionOrRemoval) {
5509 // We need to re-create our placeholder text.
5510 return true;
5513 if (mType == FormControlType::InputFile &&
5514 aAttribute == nsGkAtoms::webkitdirectory) {
5515 // The presence or absence of the 'directory' attribute determines what
5516 // value we show in the file label when empty, via GetDisplayFileName.
5517 return true;
5520 if (mType == FormControlType::InputImage && isAdditionOrRemoval &&
5521 (aAttribute == nsGkAtoms::alt || aAttribute == nsGkAtoms::value)) {
5522 // We might need to rebuild our alt text. Just go ahead and
5523 // reconstruct our frame. This should be quite rare..
5524 return true;
5526 return false;
5527 }();
5529 if (reconstruct) {
5530 retval |= nsChangeHint_ReconstructFrame;
5531 } else if (aAttribute == nsGkAtoms::value) {
5532 retval |= NS_STYLE_HINT_REFLOW;
5533 } else if (aAttribute == nsGkAtoms::size && IsSingleLineTextControl(false)) {
5534 retval |= NS_STYLE_HINT_REFLOW;
5537 return retval;
5540 NS_IMETHODIMP_(bool)
5541 HTMLInputElement::IsAttributeMapped(const nsAtom* aAttribute) const {
5542 static const MappedAttributeEntry attributes[] = {
5543 {nsGkAtoms::align},
5544 {nullptr},
5547 static const MappedAttributeEntry* const map[] = {
5548 attributes,
5549 sCommonAttributeMap,
5550 sImageMarginSizeAttributeMap,
5551 sImageBorderAttributeMap,
5554 return FindAttributeDependence(aAttribute, map);
5557 nsMapRuleToAttributesFunc HTMLInputElement::GetAttributeMappingFunction()
5558 const {
5559 // GetAttributeChangeHint guarantees that changes to mType will trigger a
5560 // reframe, and we update the mapping function in our mapped attrs when our
5561 // type changes, so it's safe to condition our attribute mapping function on
5562 // mType.
5563 if (mType == FormControlType::InputImage) {
5564 return &ImageInputMapAttributesIntoRule;
5567 return &MapCommonAttributesInto;
5570 // Directory picking methods:
5572 already_AddRefed<Promise> HTMLInputElement::GetFilesAndDirectories(
5573 ErrorResult& aRv) {
5574 if (mType != FormControlType::InputFile) {
5575 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5576 return nullptr;
5579 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
5580 MOZ_ASSERT(global);
5581 if (!global) {
5582 return nullptr;
5585 RefPtr<Promise> p = Promise::Create(global, aRv);
5586 if (aRv.Failed()) {
5587 return nullptr;
5590 const nsTArray<OwningFileOrDirectory>& filesAndDirs =
5591 GetFilesOrDirectoriesInternal();
5593 Sequence<OwningFileOrDirectory> filesAndDirsSeq;
5595 if (!filesAndDirsSeq.SetLength(filesAndDirs.Length(), fallible)) {
5596 p->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
5597 return p.forget();
5600 for (uint32_t i = 0; i < filesAndDirs.Length(); ++i) {
5601 if (filesAndDirs[i].IsDirectory()) {
5602 RefPtr<Directory> directory = filesAndDirs[i].GetAsDirectory();
5604 // In future we could refactor SetFilePickerFiltersFromAccept to return a
5605 // semicolon separated list of file extensions and include that in the
5606 // filter string passed here.
5607 directory->SetContentFilters(u"filter-out-sensitive"_ns);
5608 filesAndDirsSeq[i].SetAsDirectory() = directory;
5609 } else {
5610 MOZ_ASSERT(filesAndDirs[i].IsFile());
5612 // This file was directly selected by the user, so don't filter it.
5613 filesAndDirsSeq[i].SetAsFile() = filesAndDirs[i].GetAsFile();
5617 p->MaybeResolve(filesAndDirsSeq);
5618 return p.forget();
5621 // Controllers Methods
5623 nsIControllers* HTMLInputElement::GetControllers(ErrorResult& aRv) {
5624 // XXX: what about type "file"?
5625 if (IsSingleLineTextControl(false)) {
5626 if (!mControllers) {
5627 mControllers = new nsXULControllers();
5628 if (!mControllers) {
5629 aRv.Throw(NS_ERROR_FAILURE);
5630 return nullptr;
5633 RefPtr<nsBaseCommandController> commandController =
5634 nsBaseCommandController::CreateEditorController();
5635 if (!commandController) {
5636 aRv.Throw(NS_ERROR_FAILURE);
5637 return nullptr;
5640 mControllers->AppendController(commandController);
5642 commandController = nsBaseCommandController::CreateEditingController();
5643 if (!commandController) {
5644 aRv.Throw(NS_ERROR_FAILURE);
5645 return nullptr;
5648 mControllers->AppendController(commandController);
5652 return mControllers;
5655 nsresult HTMLInputElement::GetControllers(nsIControllers** aResult) {
5656 NS_ENSURE_ARG_POINTER(aResult);
5658 ErrorResult rv;
5659 RefPtr<nsIControllers> controller = GetControllers(rv);
5660 controller.forget(aResult);
5661 return rv.StealNSResult();
5664 int32_t HTMLInputElement::InputTextLength(CallerType aCallerType) {
5665 nsAutoString val;
5666 GetValue(val, aCallerType);
5667 return val.Length();
5670 void HTMLInputElement::SetSelectionRange(uint32_t aSelectionStart,
5671 uint32_t aSelectionEnd,
5672 const Optional<nsAString>& aDirection,
5673 ErrorResult& aRv) {
5674 if (!SupportsTextSelection()) {
5675 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5676 return;
5679 TextControlState* state = GetEditorState();
5680 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5681 state->SetSelectionRange(aSelectionStart, aSelectionEnd, aDirection, aRv);
5684 void HTMLInputElement::SetRangeText(const nsAString& aReplacement,
5685 ErrorResult& aRv) {
5686 if (!SupportsTextSelection()) {
5687 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5688 return;
5691 TextControlState* state = GetEditorState();
5692 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5693 state->SetRangeText(aReplacement, aRv);
5696 void HTMLInputElement::SetRangeText(const nsAString& aReplacement,
5697 uint32_t aStart, uint32_t aEnd,
5698 SelectionMode aSelectMode,
5699 ErrorResult& aRv) {
5700 if (!SupportsTextSelection()) {
5701 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5702 return;
5705 TextControlState* state = GetEditorState();
5706 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5707 state->SetRangeText(aReplacement, aStart, aEnd, aSelectMode, aRv);
5710 void HTMLInputElement::GetValueFromSetRangeText(nsAString& aValue) {
5711 GetNonFileValueInternal(aValue);
5714 nsresult HTMLInputElement::SetValueFromSetRangeText(const nsAString& aValue) {
5715 return SetValueInternal(aValue, {ValueSetterOption::ByContentAPI,
5716 ValueSetterOption::BySetRangeTextAPI,
5717 ValueSetterOption::SetValueChanged});
5720 Nullable<uint32_t> HTMLInputElement::GetSelectionStart(ErrorResult& aRv) {
5721 if (!SupportsTextSelection()) {
5722 return Nullable<uint32_t>();
5725 uint32_t selStart = GetSelectionStartIgnoringType(aRv);
5726 if (aRv.Failed()) {
5727 return Nullable<uint32_t>();
5730 return Nullable<uint32_t>(selStart);
5733 uint32_t HTMLInputElement::GetSelectionStartIgnoringType(ErrorResult& aRv) {
5734 uint32_t selEnd, selStart;
5735 GetSelectionRange(&selStart, &selEnd, aRv);
5736 return selStart;
5739 void HTMLInputElement::SetSelectionStart(
5740 const Nullable<uint32_t>& aSelectionStart, ErrorResult& aRv) {
5741 if (!SupportsTextSelection()) {
5742 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5743 return;
5746 TextControlState* state = GetEditorState();
5747 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5748 state->SetSelectionStart(aSelectionStart, aRv);
5751 Nullable<uint32_t> HTMLInputElement::GetSelectionEnd(ErrorResult& aRv) {
5752 if (!SupportsTextSelection()) {
5753 return Nullable<uint32_t>();
5756 uint32_t selEnd = GetSelectionEndIgnoringType(aRv);
5757 if (aRv.Failed()) {
5758 return Nullable<uint32_t>();
5761 return Nullable<uint32_t>(selEnd);
5764 uint32_t HTMLInputElement::GetSelectionEndIgnoringType(ErrorResult& aRv) {
5765 uint32_t selEnd, selStart;
5766 GetSelectionRange(&selStart, &selEnd, aRv);
5767 return selEnd;
5770 void HTMLInputElement::SetSelectionEnd(const Nullable<uint32_t>& aSelectionEnd,
5771 ErrorResult& aRv) {
5772 if (!SupportsTextSelection()) {
5773 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5774 return;
5777 TextControlState* state = GetEditorState();
5778 MOZ_ASSERT(state, "SupportsTextSelection() returned true!");
5779 state->SetSelectionEnd(aSelectionEnd, aRv);
5782 void HTMLInputElement::GetSelectionRange(uint32_t* aSelectionStart,
5783 uint32_t* aSelectionEnd,
5784 ErrorResult& aRv) {
5785 TextControlState* state = GetEditorState();
5786 if (!state) {
5787 // Not a text control.
5788 aRv.Throw(NS_ERROR_UNEXPECTED);
5789 return;
5792 state->GetSelectionRange(aSelectionStart, aSelectionEnd, aRv);
5795 void HTMLInputElement::GetSelectionDirection(nsAString& aDirection,
5796 ErrorResult& aRv) {
5797 if (!SupportsTextSelection()) {
5798 aDirection.SetIsVoid(true);
5799 return;
5802 TextControlState* state = GetEditorState();
5803 MOZ_ASSERT(state, "SupportsTextSelection came back true!");
5804 state->GetSelectionDirectionString(aDirection, aRv);
5807 void HTMLInputElement::SetSelectionDirection(const nsAString& aDirection,
5808 ErrorResult& aRv) {
5809 if (!SupportsTextSelection()) {
5810 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
5811 return;
5814 TextControlState* state = GetEditorState();
5815 MOZ_ASSERT(state, "SupportsTextSelection came back true!");
5816 state->SetSelectionDirection(aDirection, aRv);
5819 // https://html.spec.whatwg.org/multipage/input.html#dom-input-showpicker
5820 void HTMLInputElement::ShowPicker(ErrorResult& aRv) {
5821 // Step 1. If this is not mutable, then throw an "InvalidStateError"
5822 // DOMException.
5823 if (!IsMutable()) {
5824 return aRv.ThrowInvalidStateError(
5825 "This input is either disabled or readonly.");
5828 // Step 2. If this's relevant settings object's origin is not same origin with
5829 // this's relevant settings object's top-level origin, and this's type
5830 // attribute is not in the File Upload state or Color state, then throw a
5831 // "SecurityError" DOMException.
5832 if (mType != FormControlType::InputFile &&
5833 mType != FormControlType::InputColor) {
5834 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
5835 WindowGlobalChild* windowGlobalChild =
5836 window ? window->GetWindowGlobalChild() : nullptr;
5837 if (!windowGlobalChild || !windowGlobalChild->SameOriginWithTop()) {
5838 return aRv.ThrowSecurityError(
5839 "Call was blocked because the current origin isn't same-origin with "
5840 "top.");
5844 // Step 3. If this's relevant global object does not have transient
5845 // activation, then throw a "NotAllowedError" DOMException.
5846 if (!OwnerDoc()->HasValidTransientUserGestureActivation()) {
5847 return aRv.ThrowNotAllowedError(
5848 "Call was blocked due to lack of user activation.");
5851 // Step 4. Show the picker, if applicable, for this.
5853 // https://html.spec.whatwg.org/multipage/input.html#show-the-picker,-if-applicable
5854 // To show the picker, if applicable for an input element element:
5856 // Step 1. Assert: element's relevant global object has transient activation.
5857 // Step 2. If element is not mutable, then return.
5858 // (See above.)
5860 // Step 3. Consume user activation given element's relevant global object.
5861 // InitFilePicker() and InitColorPicker() consume it themselves,
5862 // so only consume in this function if not those.
5864 // Step 4. If element's type attribute is in the File Upload state, then run
5865 // these steps in parallel:
5866 if (mType == FormControlType::InputFile) {
5867 FilePickerType type = FILE_PICKER_FILE;
5868 if (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
5869 HasAttr(nsGkAtoms::webkitdirectory)) {
5870 type = FILE_PICKER_DIRECTORY;
5872 InitFilePicker(type);
5873 return;
5876 // Step 5. Otherwise, the user agent should show any relevant user interface
5877 // for selecting a value for element, in the way it normally would when the
5878 // user interacts with the control
5879 if (mType == FormControlType::InputColor) {
5880 InitColorPicker();
5881 return;
5884 // See Step 3.
5885 OwnerDoc()->ConsumeTransientUserGestureActivation();
5887 if (!IsInComposedDoc()) {
5888 return;
5891 if (IsDateTimeTypeSupported(mType)) {
5892 if (CreatesDateTimeWidget()) {
5893 if (RefPtr<Element> dateTimeBoxElement = GetDateTimeBoxElement()) {
5894 // Event is dispatched to closed-shadow tree and doesn't bubble.
5895 RefPtr<Document> doc = dateTimeBoxElement->OwnerDoc();
5896 nsContentUtils::DispatchTrustedEvent(doc, dateTimeBoxElement,
5897 u"MozDateTimeShowPickerForJS"_ns,
5898 CanBubble::eNo, Cancelable::eNo);
5900 } else {
5901 DateTimeValue value;
5902 GetDateTimeInputBoxValue(value);
5903 OpenDateTimePicker(value);
5908 #ifdef ACCESSIBILITY
5909 /*static*/ nsresult FireEventForAccessibility(HTMLInputElement* aTarget,
5910 EventMessage aEventMessage) {
5911 Element* element = static_cast<Element*>(aTarget);
5912 return nsContentUtils::DispatchTrustedEvent<WidgetEvent>(
5913 element->OwnerDoc(), element, aEventMessage, CanBubble::eYes,
5914 Cancelable::eYes);
5916 #endif
5918 void HTMLInputElement::UpdateApzAwareFlag() {
5919 #if !defined(ANDROID) && !defined(XP_MACOSX)
5920 if (mType == FormControlType::InputNumber ||
5921 mType == FormControlType::InputRange) {
5922 SetMayBeApzAware();
5924 #endif
5927 nsresult HTMLInputElement::SetDefaultValueAsValue() {
5928 NS_ASSERTION(GetValueMode() == VALUE_MODE_VALUE,
5929 "GetValueMode() should return VALUE_MODE_VALUE!");
5931 // The element has a content attribute value different from it's value when
5932 // it's in the value mode value.
5933 nsAutoString resetVal;
5934 GetDefaultValue(resetVal);
5936 // SetValueInternal is going to sanitize the value.
5937 // TODO(mbrodesser): sanitizing will only happen if `mDoneCreating` is true.
5938 return SetValueInternal(resetVal, ValueSetterOption::ByInternalAPI);
5941 NS_IMETHODIMP
5942 HTMLInputElement::Reset() {
5943 // We should be able to reset all dirty flags regardless of the type.
5944 SetCheckedChanged(false);
5945 SetValueChanged(false);
5946 SetLastValueChangeWasInteractive(false);
5947 SetUserInteracted(false);
5949 switch (GetValueMode()) {
5950 case VALUE_MODE_VALUE: {
5951 nsresult result = SetDefaultValueAsValue();
5952 if (CreatesDateTimeWidget()) {
5953 // mFocusedValue has to be set here, so that `FireChangeEventIfNeeded`
5954 // can fire a change event if necessary.
5955 GetValue(mFocusedValue, CallerType::System);
5957 return result;
5959 case VALUE_MODE_DEFAULT_ON:
5960 DoSetChecked(DefaultChecked(), true, false);
5961 return NS_OK;
5962 case VALUE_MODE_FILENAME:
5963 ClearFiles(false);
5964 return NS_OK;
5965 case VALUE_MODE_DEFAULT:
5966 default:
5967 return NS_OK;
5971 NS_IMETHODIMP
5972 HTMLInputElement::SubmitNamesValues(FormData* aFormData) {
5973 // For type=reset, and type=button, we just never submit, period.
5974 // For type=image and type=button, we only submit if we were the button
5975 // pressed
5976 // For type=radio and type=checkbox, we only submit if checked=true
5977 if (mType == FormControlType::InputReset ||
5978 mType == FormControlType::InputButton ||
5979 ((mType == FormControlType::InputSubmit ||
5980 mType == FormControlType::InputImage) &&
5981 aFormData->GetSubmitterElement() != this) ||
5982 ((mType == FormControlType::InputRadio ||
5983 mType == FormControlType::InputCheckbox) &&
5984 !mChecked)) {
5985 return NS_OK;
5988 // Get the name
5989 nsAutoString name;
5990 GetAttr(nsGkAtoms::name, name);
5992 // Submit .x, .y for input type=image
5993 if (mType == FormControlType::InputImage) {
5994 // Get a property set by the frame to find out where it was clicked.
5995 const auto* lastClickedPoint =
5996 static_cast<CSSIntPoint*>(GetProperty(nsGkAtoms::imageClickedPoint));
5997 int32_t x, y;
5998 if (lastClickedPoint) {
5999 // Convert the values to strings for submission
6000 x = lastClickedPoint->x;
6001 y = lastClickedPoint->y;
6002 } else {
6003 x = y = 0;
6006 nsAutoString xVal, yVal;
6007 xVal.AppendInt(x);
6008 yVal.AppendInt(y);
6010 if (!name.IsEmpty()) {
6011 aFormData->AddNameValuePair(name + u".x"_ns, xVal);
6012 aFormData->AddNameValuePair(name + u".y"_ns, yVal);
6013 } else {
6014 // If the Image Element has no name, simply return x and y
6015 // to Nav and IE compatibility.
6016 aFormData->AddNameValuePair(u"x"_ns, xVal);
6017 aFormData->AddNameValuePair(u"y"_ns, yVal);
6020 return NS_OK;
6023 // If name not there, don't submit
6024 if (name.IsEmpty()) {
6025 return NS_OK;
6029 // Submit file if its input type=file and this encoding method accepts files
6031 if (mType == FormControlType::InputFile) {
6032 // Submit files
6034 const nsTArray<OwningFileOrDirectory>& files =
6035 GetFilesOrDirectoriesInternal();
6037 if (files.IsEmpty()) {
6038 NS_ENSURE_STATE(GetOwnerGlobal());
6039 ErrorResult rv;
6040 RefPtr<Blob> blob = Blob::CreateStringBlob(
6041 GetOwnerGlobal(), ""_ns, u"application/octet-stream"_ns);
6042 RefPtr<File> file = blob->ToFile(u""_ns, rv);
6044 if (!rv.Failed()) {
6045 aFormData->AddNameBlobPair(name, file);
6048 return rv.StealNSResult();
6051 for (uint32_t i = 0; i < files.Length(); ++i) {
6052 if (files[i].IsFile()) {
6053 aFormData->AddNameBlobPair(name, files[i].GetAsFile());
6054 } else {
6055 MOZ_ASSERT(files[i].IsDirectory());
6056 aFormData->AddNameDirectoryPair(name, files[i].GetAsDirectory());
6060 return NS_OK;
6063 if (mType == FormControlType::InputHidden &&
6064 name.LowerCaseEqualsLiteral("_charset_")) {
6065 nsCString charset;
6066 aFormData->GetCharset(charset);
6067 return aFormData->AddNameValuePair(name, NS_ConvertASCIItoUTF16(charset));
6071 // Submit name=value
6074 // Get the value
6075 nsAutoString value;
6076 GetValue(value, CallerType::System);
6078 if (mType == FormControlType::InputSubmit && value.IsEmpty() &&
6079 !HasAttr(nsGkAtoms::value)) {
6080 // Get our default value, which is the same as our default label
6081 nsAutoString defaultValue;
6082 nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
6083 "Submit", OwnerDoc(), defaultValue);
6084 value = defaultValue;
6087 const nsresult rv = aFormData->AddNameValuePair(name, value);
6088 if (NS_FAILED(rv)) {
6089 return rv;
6092 // Submit dirname=dir
6093 if (IsAutoDirectionalityAssociated()) {
6094 return SubmitDirnameDir(aFormData);
6097 return NS_OK;
6100 static nsTArray<FileContentData> SaveFileContentData(
6101 const nsTArray<OwningFileOrDirectory>& aArray) {
6102 nsTArray<FileContentData> res(aArray.Length());
6103 for (const auto& it : aArray) {
6104 if (it.IsFile()) {
6105 RefPtr<BlobImpl> impl = it.GetAsFile()->Impl();
6106 res.AppendElement(std::move(impl));
6107 } else {
6108 MOZ_ASSERT(it.IsDirectory());
6109 nsString fullPath;
6110 nsresult rv = it.GetAsDirectory()->GetFullRealPath(fullPath);
6111 if (NS_WARN_IF(NS_FAILED(rv))) {
6112 continue;
6114 res.AppendElement(std::move(fullPath));
6117 return res;
6120 void HTMLInputElement::SaveState() {
6121 PresState* state = nullptr;
6122 switch (GetValueMode()) {
6123 case VALUE_MODE_DEFAULT_ON:
6124 if (mCheckedChanged) {
6125 state = GetPrimaryPresState();
6126 if (!state) {
6127 return;
6130 state->contentData() = CheckedContentData(mChecked);
6132 break;
6133 case VALUE_MODE_FILENAME:
6134 if (!mFileData->mFilesOrDirectories.IsEmpty()) {
6135 state = GetPrimaryPresState();
6136 if (!state) {
6137 return;
6140 state->contentData() =
6141 SaveFileContentData(mFileData->mFilesOrDirectories);
6143 break;
6144 case VALUE_MODE_VALUE:
6145 case VALUE_MODE_DEFAULT:
6146 // VALUE_MODE_DEFAULT shouldn't have their value saved except 'hidden',
6147 // mType should have never been FormControlType::InputPassword and value
6148 // should have changed.
6149 if ((GetValueMode() == VALUE_MODE_DEFAULT &&
6150 mType != FormControlType::InputHidden) ||
6151 mHasBeenTypePassword || !mValueChanged) {
6152 break;
6155 state = GetPrimaryPresState();
6156 if (!state) {
6157 return;
6160 nsAutoString value;
6161 GetValue(value, CallerType::System);
6163 if (!IsSingleLineTextControl(false) &&
6164 NS_FAILED(nsLinebreakConverter::ConvertStringLineBreaks(
6165 value, nsLinebreakConverter::eLinebreakPlatform,
6166 nsLinebreakConverter::eLinebreakContent))) {
6167 NS_ERROR("Converting linebreaks failed!");
6168 return;
6171 state->contentData() =
6172 TextContentData(value, mLastValueChangeWasInteractive);
6173 break;
6176 if (mDisabledChanged) {
6177 if (!state) {
6178 state = GetPrimaryPresState();
6180 if (state) {
6181 // We do not want to save the real disabled state but the disabled
6182 // attribute.
6183 state->disabled() = HasAttr(nsGkAtoms::disabled);
6184 state->disabledSet() = true;
6189 void HTMLInputElement::DoneCreatingElement() {
6190 mDoneCreating = true;
6193 // Restore state as needed. Note that disabled state applies to all control
6194 // types.
6196 bool restoredCheckedState = false;
6197 if (!mInhibitRestoration) {
6198 GenerateStateKey();
6199 restoredCheckedState = RestoreFormControlState();
6203 // If restore does not occur, we initialize .checked using the CHECKED
6204 // property.
6206 if (!restoredCheckedState && mShouldInitChecked) {
6207 DoSetChecked(DefaultChecked(), false, false);
6210 // Sanitize the value and potentially set mFocusedValue.
6211 if (GetValueMode() == VALUE_MODE_VALUE) {
6212 nsAutoString value;
6213 GetValue(value, CallerType::System);
6214 // TODO: What should we do if SetValueInternal fails? (The allocation
6215 // may potentially be big, but most likely we've failed to allocate
6216 // before the type change.)
6217 SetValueInternal(value, ValueSetterOption::ByInternalAPI);
6219 if (CreatesDateTimeWidget()) {
6220 // mFocusedValue has to be set here, so that `FireChangeEventIfNeeded` can
6221 // fire a change event if necessary.
6222 mFocusedValue = value;
6226 mShouldInitChecked = false;
6229 void HTMLInputElement::DestroyContent() {
6230 nsImageLoadingContent::Destroy();
6231 TextControlElement::DestroyContent();
6234 void HTMLInputElement::UpdateValidityElementStates(bool aNotify) {
6235 AutoStateChangeNotifier notifier(*this, aNotify);
6236 RemoveStatesSilently(ElementState::VALIDITY_STATES);
6237 if (!IsCandidateForConstraintValidation()) {
6238 return;
6240 ElementState state;
6241 if (IsValid()) {
6242 state |= ElementState::VALID;
6243 if (mUserInteracted) {
6244 state |= ElementState::USER_VALID;
6246 } else {
6247 state |= ElementState::INVALID;
6248 if (mUserInteracted) {
6249 state |= ElementState::USER_INVALID;
6252 AddStatesSilently(state);
6255 static nsTArray<OwningFileOrDirectory> RestoreFileContentData(
6256 nsPIDOMWindowInner* aWindow, const nsTArray<FileContentData>& aData) {
6257 nsTArray<OwningFileOrDirectory> res(aData.Length());
6258 for (const auto& it : aData) {
6259 if (it.type() == FileContentData::TBlobImpl) {
6260 if (!it.get_BlobImpl()) {
6261 // Serialization failed, skip this file.
6262 continue;
6265 RefPtr<File> file = File::Create(aWindow->AsGlobal(), it.get_BlobImpl());
6266 if (NS_WARN_IF(!file)) {
6267 continue;
6270 OwningFileOrDirectory* element = res.AppendElement();
6271 element->SetAsFile() = file;
6272 } else {
6273 MOZ_ASSERT(it.type() == FileContentData::TnsString);
6274 nsCOMPtr<nsIFile> file;
6275 nsresult rv =
6276 NS_NewLocalFile(it.get_nsString(), true, getter_AddRefs(file));
6277 if (NS_WARN_IF(NS_FAILED(rv))) {
6278 continue;
6281 RefPtr<Directory> directory =
6282 Directory::Create(aWindow->AsGlobal(), file);
6283 MOZ_ASSERT(directory);
6285 OwningFileOrDirectory* element = res.AppendElement();
6286 element->SetAsDirectory() = directory;
6289 return res;
6292 bool HTMLInputElement::RestoreState(PresState* aState) {
6293 bool restoredCheckedState = false;
6295 const PresContentData& inputState = aState->contentData();
6297 switch (GetValueMode()) {
6298 case VALUE_MODE_DEFAULT_ON:
6299 if (inputState.type() == PresContentData::TCheckedContentData) {
6300 restoredCheckedState = true;
6301 bool checked = inputState.get_CheckedContentData().checked();
6302 DoSetChecked(checked, true, true);
6304 break;
6305 case VALUE_MODE_FILENAME:
6306 if (inputState.type() == PresContentData::TArrayOfFileContentData) {
6307 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
6308 if (window) {
6309 nsTArray<OwningFileOrDirectory> array =
6310 RestoreFileContentData(window, inputState);
6311 SetFilesOrDirectories(array, true);
6314 break;
6315 case VALUE_MODE_VALUE:
6316 case VALUE_MODE_DEFAULT:
6317 if (GetValueMode() == VALUE_MODE_DEFAULT &&
6318 mType != FormControlType::InputHidden) {
6319 break;
6322 if (inputState.type() == PresContentData::TTextContentData) {
6323 // TODO: What should we do if SetValueInternal fails? (The allocation
6324 // may potentially be big, but most likely we've failed to allocate
6325 // before the type change.)
6326 SetValueInternal(inputState.get_TextContentData().value(),
6327 ValueSetterOption::SetValueChanged);
6328 if (inputState.get_TextContentData().lastValueChangeWasInteractive()) {
6329 SetLastValueChangeWasInteractive(true);
6332 break;
6335 if (aState->disabledSet() && !aState->disabled()) {
6336 SetDisabled(false, IgnoreErrors());
6339 return restoredCheckedState;
6343 * Radio group stuff
6346 void HTMLInputElement::AddToRadioGroup() {
6347 MOZ_ASSERT(!mRadioGroupContainer,
6348 "Radio button must be removed from previous radio group container "
6349 "before being added to another!");
6351 // If the element has no radio group container we can stop here.
6352 auto* container = FindTreeRadioGroupContainer();
6353 if (!container) {
6354 return;
6357 nsAutoString name;
6358 GetAttr(nsGkAtoms::name, name);
6359 // If we are part of a radio group, the element must have a name.
6360 MOZ_ASSERT(!name.IsEmpty());
6363 // Add the radio to the radio group container.
6365 container->AddToRadioGroup(name, this, mForm);
6366 mRadioGroupContainer = container;
6369 // If the input element is checked, and we add it to the group, it will
6370 // deselect whatever is currently selected in that group
6372 if (mChecked) {
6374 // If it is checked, call "RadioSetChecked" to perform the selection/
6375 // deselection ritual. This has the side effect of repainting the
6376 // radio button, but as adding a checked radio button into the group
6377 // should not be that common an occurrence, I think we can live with
6378 // that.
6379 // Make sure not to notify if we're still being created.
6381 RadioSetChecked(mDoneCreating);
6382 } else {
6383 bool indeterminate = !container->GetCurrentRadioButton(name);
6384 SetStates(ElementState::INDETERMINATE, indeterminate, mDoneCreating);
6388 // For integrity purposes, we have to ensure that "checkedChanged" is
6389 // the same for this new element as for all the others in the group
6391 bool checkedChanged = mCheckedChanged;
6393 nsCOMPtr<nsIRadioVisitor> visitor =
6394 new nsRadioGetCheckedChangedVisitor(&checkedChanged, this);
6395 VisitGroup(visitor);
6397 SetCheckedChangedInternal(checkedChanged);
6399 // We initialize the validity of the element to the validity of the group
6400 // because we assume UpdateValueMissingState() will be called after.
6401 SetValidityState(VALIDITY_STATE_VALUE_MISSING,
6402 container->GetValueMissingState(name));
6405 void HTMLInputElement::RemoveFromRadioGroup() {
6406 auto* container = GetCurrentRadioGroupContainer();
6407 if (!container) {
6408 return;
6411 nsAutoString name;
6412 GetAttr(nsGkAtoms::name, name);
6414 // If this button was checked, we need to notify the group that there is no
6415 // longer a selected radio button
6416 if (mChecked) {
6417 container->SetCurrentRadioButton(name, nullptr);
6418 nsCOMPtr<nsIRadioVisitor> visitor = new nsRadioUpdateStateVisitor(this);
6419 VisitGroup(visitor);
6420 } else {
6421 AddStates(ElementState::INDETERMINATE);
6424 // Remove this radio from its group in the container.
6425 // We need to call UpdateValueMissingValidityStateForRadio before to make sure
6426 // the group validity is updated (with this element being ignored).
6427 UpdateValueMissingValidityStateForRadio(true);
6428 container->RemoveFromRadioGroup(name, this);
6429 mRadioGroupContainer = nullptr;
6432 bool HTMLInputElement::IsHTMLFocusable(IsFocusableFlags aFlags,
6433 bool* aIsFocusable, int32_t* aTabIndex) {
6434 if (nsGenericHTMLFormControlElementWithState::IsHTMLFocusable(
6435 aFlags, aIsFocusable, aTabIndex)) {
6436 return true;
6439 if (IsDisabled()) {
6440 *aIsFocusable = false;
6441 return true;
6444 if (IsSingleLineTextControl(false) || mType == FormControlType::InputRange) {
6445 *aIsFocusable = true;
6446 return false;
6449 const bool defaultFocusable = IsFormControlDefaultFocusable(aFlags);
6450 if (CreatesDateTimeWidget()) {
6451 if (aTabIndex) {
6452 // We only want our native anonymous child to be tabable to, not ourself.
6453 *aTabIndex = -1;
6455 *aIsFocusable = true;
6456 return true;
6459 if (mType == FormControlType::InputHidden) {
6460 if (aTabIndex) {
6461 *aTabIndex = -1;
6463 *aIsFocusable = false;
6464 return false;
6467 if (!aTabIndex) {
6468 // The other controls are all focusable
6469 *aIsFocusable = defaultFocusable;
6470 return false;
6473 if (mType != FormControlType::InputRadio) {
6474 *aIsFocusable = defaultFocusable;
6475 return false;
6478 if (mChecked) {
6479 // Selected radio buttons are tabbable
6480 *aIsFocusable = defaultFocusable;
6481 return false;
6484 // Current radio button is not selected.
6485 // Make it tabbable if nothing in group is selected and it is the first radio
6486 // button.
6487 auto* container = GetCurrentRadioGroupContainer();
6488 if (!container) {
6489 *aIsFocusable = defaultFocusable;
6490 return false;
6493 nsAutoString name;
6494 GetAttr(nsGkAtoms::name, name);
6496 // If there is a selected radio button but it is disabled or hidden, it
6497 // shouldn't be considered as selected for this check. Otherwise, the entire
6498 // group will be unreachable with the tab key.
6499 HTMLInputElement* selectedRadio = container->GetCurrentRadioButton(name);
6500 if ((selectedRadio && !selectedRadio->Disabled() &&
6501 selectedRadio->GetPrimaryFrame()) ||
6502 container->GetFirstRadioButton(name) != this) {
6503 *aTabIndex = -1;
6505 *aIsFocusable = defaultFocusable;
6506 return false;
6509 nsresult HTMLInputElement::VisitGroup(nsIRadioVisitor* aVisitor) {
6510 if (auto* container = GetCurrentRadioGroupContainer()) {
6511 nsAutoString name;
6512 GetAttr(nsGkAtoms::name, name);
6513 return container->WalkRadioGroup(name, aVisitor);
6516 aVisitor->Visit(this);
6517 return NS_OK;
6520 HTMLInputElement::ValueModeType HTMLInputElement::GetValueMode() const {
6521 switch (mType) {
6522 case FormControlType::InputHidden:
6523 case FormControlType::InputSubmit:
6524 case FormControlType::InputButton:
6525 case FormControlType::InputReset:
6526 case FormControlType::InputImage:
6527 return VALUE_MODE_DEFAULT;
6528 case FormControlType::InputCheckbox:
6529 case FormControlType::InputRadio:
6530 return VALUE_MODE_DEFAULT_ON;
6531 case FormControlType::InputFile:
6532 return VALUE_MODE_FILENAME;
6533 #ifdef DEBUG
6534 case FormControlType::InputText:
6535 case FormControlType::InputPassword:
6536 case FormControlType::InputSearch:
6537 case FormControlType::InputTel:
6538 case FormControlType::InputEmail:
6539 case FormControlType::InputUrl:
6540 case FormControlType::InputNumber:
6541 case FormControlType::InputRange:
6542 case FormControlType::InputDate:
6543 case FormControlType::InputTime:
6544 case FormControlType::InputColor:
6545 case FormControlType::InputMonth:
6546 case FormControlType::InputWeek:
6547 case FormControlType::InputDatetimeLocal:
6548 return VALUE_MODE_VALUE;
6549 default:
6550 MOZ_ASSERT_UNREACHABLE("Unexpected input type in GetValueMode()");
6551 return VALUE_MODE_VALUE;
6552 #else // DEBUG
6553 default:
6554 return VALUE_MODE_VALUE;
6555 #endif // DEBUG
6559 bool HTMLInputElement::IsMutable() const {
6560 return !IsDisabled() &&
6561 !(DoesReadWriteApply() && State().HasState(ElementState::READONLY));
6564 bool HTMLInputElement::DoesRequiredApply() const {
6565 switch (mType) {
6566 case FormControlType::InputHidden:
6567 case FormControlType::InputButton:
6568 case FormControlType::InputImage:
6569 case FormControlType::InputReset:
6570 case FormControlType::InputSubmit:
6571 case FormControlType::InputRange:
6572 case FormControlType::InputColor:
6573 return false;
6574 #ifdef DEBUG
6575 case FormControlType::InputRadio:
6576 case FormControlType::InputCheckbox:
6577 case FormControlType::InputFile:
6578 case FormControlType::InputText:
6579 case FormControlType::InputPassword:
6580 case FormControlType::InputSearch:
6581 case FormControlType::InputTel:
6582 case FormControlType::InputEmail:
6583 case FormControlType::InputUrl:
6584 case FormControlType::InputNumber:
6585 case FormControlType::InputDate:
6586 case FormControlType::InputTime:
6587 case FormControlType::InputMonth:
6588 case FormControlType::InputWeek:
6589 case FormControlType::InputDatetimeLocal:
6590 return true;
6591 default:
6592 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
6593 return true;
6594 #else // DEBUG
6595 default:
6596 return true;
6597 #endif // DEBUG
6601 bool HTMLInputElement::PlaceholderApplies() const {
6602 if (IsDateTimeInputType(mType)) {
6603 return false;
6605 return IsSingleLineTextControl(false);
6608 bool HTMLInputElement::DoesMinMaxApply() const {
6609 switch (mType) {
6610 case FormControlType::InputNumber:
6611 case FormControlType::InputDate:
6612 case FormControlType::InputTime:
6613 case FormControlType::InputRange:
6614 case FormControlType::InputMonth:
6615 case FormControlType::InputWeek:
6616 case FormControlType::InputDatetimeLocal:
6617 return true;
6618 #ifdef DEBUG
6619 case FormControlType::InputReset:
6620 case FormControlType::InputSubmit:
6621 case FormControlType::InputImage:
6622 case FormControlType::InputButton:
6623 case FormControlType::InputHidden:
6624 case FormControlType::InputRadio:
6625 case FormControlType::InputCheckbox:
6626 case FormControlType::InputFile:
6627 case FormControlType::InputText:
6628 case FormControlType::InputPassword:
6629 case FormControlType::InputSearch:
6630 case FormControlType::InputTel:
6631 case FormControlType::InputEmail:
6632 case FormControlType::InputUrl:
6633 case FormControlType::InputColor:
6634 return false;
6635 default:
6636 MOZ_ASSERT_UNREACHABLE("Unexpected input type in DoesRequiredApply()");
6637 return false;
6638 #else // DEBUG
6639 default:
6640 return false;
6641 #endif // DEBUG
6645 bool HTMLInputElement::DoesAutocompleteApply() const {
6646 switch (mType) {
6647 case FormControlType::InputHidden:
6648 case FormControlType::InputText:
6649 case FormControlType::InputSearch:
6650 case FormControlType::InputUrl:
6651 case FormControlType::InputTel:
6652 case FormControlType::InputEmail:
6653 case FormControlType::InputPassword:
6654 case FormControlType::InputDate:
6655 case FormControlType::InputTime:
6656 case FormControlType::InputNumber:
6657 case FormControlType::InputRange:
6658 case FormControlType::InputColor:
6659 case FormControlType::InputMonth:
6660 case FormControlType::InputWeek:
6661 case FormControlType::InputDatetimeLocal:
6662 return true;
6663 #ifdef DEBUG
6664 case FormControlType::InputReset:
6665 case FormControlType::InputSubmit:
6666 case FormControlType::InputImage:
6667 case FormControlType::InputButton:
6668 case FormControlType::InputRadio:
6669 case FormControlType::InputCheckbox:
6670 case FormControlType::InputFile:
6671 return false;
6672 default:
6673 MOZ_ASSERT_UNREACHABLE(
6674 "Unexpected input type in DoesAutocompleteApply()");
6675 return false;
6676 #else // DEBUG
6677 default:
6678 return false;
6679 #endif // DEBUG
6683 Decimal HTMLInputElement::GetStep() const {
6684 MOZ_ASSERT(DoesStepApply(), "GetStep() can only be called if @step applies");
6686 if (!HasAttr(nsGkAtoms::step)) {
6687 return GetDefaultStep() * GetStepScaleFactor();
6690 nsAutoString stepStr;
6691 GetAttr(nsGkAtoms::step, stepStr);
6693 if (stepStr.LowerCaseEqualsLiteral("any")) {
6694 // The element can't suffer from step mismatch if there is no step.
6695 return kStepAny;
6698 Decimal step = StringToDecimal(stepStr);
6699 if (!step.isFinite() || step <= Decimal(0)) {
6700 step = GetDefaultStep();
6703 // For input type=date, we round the step value to have a rounded day.
6704 if (mType == FormControlType::InputDate ||
6705 mType == FormControlType::InputMonth ||
6706 mType == FormControlType::InputWeek) {
6707 step = std::max(step.round(), Decimal(1));
6710 return step * GetStepScaleFactor();
6713 // ConstraintValidation
6715 void HTMLInputElement::SetCustomValidity(const nsAString& aError) {
6716 ConstraintValidation::SetCustomValidity(aError);
6717 UpdateValidityElementStates(true);
6720 bool HTMLInputElement::IsTooLong() {
6721 if (!mValueChanged || !mLastValueChangeWasInteractive) {
6722 return false;
6725 return mInputType->IsTooLong();
6728 bool HTMLInputElement::IsTooShort() {
6729 if (!mValueChanged || !mLastValueChangeWasInteractive) {
6730 return false;
6733 return mInputType->IsTooShort();
6736 bool HTMLInputElement::IsValueMissing() const {
6737 // Should use UpdateValueMissingValidityStateForRadio() for type radio.
6738 MOZ_ASSERT(mType != FormControlType::InputRadio);
6740 return mInputType->IsValueMissing();
6743 bool HTMLInputElement::HasTypeMismatch() const {
6744 return mInputType->HasTypeMismatch();
6747 Maybe<bool> HTMLInputElement::HasPatternMismatch() const {
6748 return mInputType->HasPatternMismatch();
6751 bool HTMLInputElement::IsRangeOverflow() const {
6752 return mInputType->IsRangeOverflow();
6755 bool HTMLInputElement::IsRangeUnderflow() const {
6756 return mInputType->IsRangeUnderflow();
6759 bool HTMLInputElement::ValueIsStepMismatch(const Decimal& aValue) const {
6760 if (aValue.isNaN()) {
6761 // The element can't suffer from step mismatch if its value isn't a
6762 // number.
6763 return false;
6766 Decimal step = GetStep();
6767 if (step == kStepAny) {
6768 return false;
6771 // Value has to be an integral multiple of step.
6772 return NS_floorModulo(aValue - GetStepBase(), step) != Decimal(0);
6775 bool HTMLInputElement::HasStepMismatch() const {
6776 return mInputType->HasStepMismatch();
6779 bool HTMLInputElement::HasBadInput() const { return mInputType->HasBadInput(); }
6781 void HTMLInputElement::UpdateTooLongValidityState() {
6782 SetValidityState(VALIDITY_STATE_TOO_LONG, IsTooLong());
6785 void HTMLInputElement::UpdateTooShortValidityState() {
6786 SetValidityState(VALIDITY_STATE_TOO_SHORT, IsTooShort());
6789 void HTMLInputElement::UpdateValueMissingValidityStateForRadio(
6790 bool aIgnoreSelf) {
6791 MOZ_ASSERT(mType == FormControlType::InputRadio,
6792 "This should be called only for radio input types");
6794 HTMLInputElement* selection = GetSelectedRadioButton();
6796 // If there is no selection, that might mean the radio is not in a group.
6797 // In that case, we can look for the checked state of the radio.
6798 bool selected = selection || (!aIgnoreSelf && mChecked);
6799 bool required = !aIgnoreSelf && IsRequired();
6801 auto* container = GetCurrentRadioGroupContainer();
6802 if (!container) {
6803 SetValidityState(VALIDITY_STATE_VALUE_MISSING, false);
6804 return;
6807 nsAutoString name;
6808 GetAttr(nsGkAtoms::name, name);
6810 // If the current radio is required and not ignored, we can assume the entire
6811 // group is required.
6812 if (!required) {
6813 required = (aIgnoreSelf && IsRequired())
6814 ? container->GetRequiredRadioCount(name) - 1
6815 : container->GetRequiredRadioCount(name);
6818 bool valueMissing = required && !selected;
6819 if (container->GetValueMissingState(name) != valueMissing) {
6820 container->SetValueMissingState(name, valueMissing);
6822 SetValidityState(VALIDITY_STATE_VALUE_MISSING, valueMissing);
6824 // nsRadioSetValueMissingState will call ElementStateChanged while visiting.
6825 nsAutoScriptBlocker scriptBlocker;
6826 nsCOMPtr<nsIRadioVisitor> visitor =
6827 new nsRadioSetValueMissingState(this, valueMissing);
6828 VisitGroup(visitor);
6832 void HTMLInputElement::UpdateValueMissingValidityState() {
6833 if (mType == FormControlType::InputRadio) {
6834 UpdateValueMissingValidityStateForRadio(false);
6835 return;
6838 SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
6841 void HTMLInputElement::UpdateTypeMismatchValidityState() {
6842 SetValidityState(VALIDITY_STATE_TYPE_MISMATCH, HasTypeMismatch());
6845 void HTMLInputElement::UpdatePatternMismatchValidityState() {
6846 Maybe<bool> hasMismatch = HasPatternMismatch();
6847 // Don't update if the JS engine failed to evaluate it.
6848 if (hasMismatch.isSome()) {
6849 SetValidityState(VALIDITY_STATE_PATTERN_MISMATCH, hasMismatch.value());
6853 void HTMLInputElement::UpdateRangeOverflowValidityState() {
6854 SetValidityState(VALIDITY_STATE_RANGE_OVERFLOW, IsRangeOverflow());
6855 UpdateInRange(true);
6858 void HTMLInputElement::UpdateRangeUnderflowValidityState() {
6859 SetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW, IsRangeUnderflow());
6860 UpdateInRange(true);
6863 void HTMLInputElement::UpdateStepMismatchValidityState() {
6864 SetValidityState(VALIDITY_STATE_STEP_MISMATCH, HasStepMismatch());
6867 void HTMLInputElement::UpdateBadInputValidityState() {
6868 SetValidityState(VALIDITY_STATE_BAD_INPUT, HasBadInput());
6871 void HTMLInputElement::UpdateAllValidityStates(bool aNotify) {
6872 bool validBefore = IsValid();
6873 UpdateAllValidityStatesButNotElementState();
6874 if (validBefore != IsValid()) {
6875 UpdateValidityElementStates(aNotify);
6879 void HTMLInputElement::UpdateAllValidityStatesButNotElementState() {
6880 UpdateTooLongValidityState();
6881 UpdateTooShortValidityState();
6882 UpdateValueMissingValidityState();
6883 UpdateTypeMismatchValidityState();
6884 UpdatePatternMismatchValidityState();
6885 UpdateRangeOverflowValidityState();
6886 UpdateRangeUnderflowValidityState();
6887 UpdateStepMismatchValidityState();
6888 UpdateBadInputValidityState();
6891 void HTMLInputElement::UpdateBarredFromConstraintValidation() {
6892 // NOTE: readonly attribute causes an element to be barred from constraint
6893 // validation even if it doesn't apply to that input type. That's rather
6894 // weird, but pre-existing behavior.
6895 bool wasCandidate = IsCandidateForConstraintValidation();
6896 SetBarredFromConstraintValidation(
6897 mType == FormControlType::InputHidden ||
6898 mType == FormControlType::InputButton ||
6899 mType == FormControlType::InputReset || IsDisabled() ||
6900 HasAttr(nsGkAtoms::readonly) ||
6901 HasFlag(ELEMENT_IS_DATALIST_OR_HAS_DATALIST_ANCESTOR));
6902 if (IsCandidateForConstraintValidation() != wasCandidate) {
6903 UpdateInRange(true);
6907 nsresult HTMLInputElement::GetValidationMessage(nsAString& aValidationMessage,
6908 ValidityStateType aType) {
6909 return mInputType->GetValidationMessage(aValidationMessage, aType);
6912 bool HTMLInputElement::IsSingleLineTextControl() const {
6913 return IsSingleLineTextControl(false);
6916 bool HTMLInputElement::IsTextArea() const { return false; }
6918 bool HTMLInputElement::IsPasswordTextControl() const {
6919 return mType == FormControlType::InputPassword;
6922 Maybe<int32_t> HTMLInputElement::GetNumberInputCols() const {
6923 // This logic is ported from WebKit, see
6924 // https://github.com/whatwg/html/issues/10390
6925 struct RenderSize {
6926 uint32_t mBeforeDecimal = 0;
6927 uint32_t mAfterDecimal = 0;
6929 RenderSize Max(const RenderSize& aOther) const {
6930 return {std::max(mBeforeDecimal, aOther.mBeforeDecimal),
6931 std::max(mAfterDecimal, aOther.mAfterDecimal)};
6934 static RenderSize From(const Decimal& aValue) {
6935 MOZ_ASSERT(aValue.isFinite());
6936 nsAutoCString tmp;
6937 tmp.AppendInt(aValue.value().coefficient());
6938 const uint32_t sizeOfDigits = tmp.Length();
6939 const uint32_t sizeOfSign = aValue.isNegative() ? 1 : 0;
6940 const int32_t exponent = aValue.exponent();
6941 if (exponent >= 0) {
6942 return {sizeOfSign + sizeOfDigits, 0};
6945 const int32_t sizeBeforeDecimalPoint = exponent + int32_t(sizeOfDigits);
6946 if (sizeBeforeDecimalPoint > 0) {
6947 // In case of "123.456"
6948 return {sizeOfSign + sizeBeforeDecimalPoint,
6949 sizeOfDigits - sizeBeforeDecimalPoint};
6952 // In case of "0.00012345"
6953 const uint32_t sizeOfZero = 1;
6954 const uint32_t numberOfZeroAfterDecimalPoint = -sizeBeforeDecimalPoint;
6955 return {sizeOfSign + sizeOfZero,
6956 numberOfZeroAfterDecimalPoint + sizeOfDigits};
6960 if (mType != FormControlType::InputNumber) {
6961 return {};
6963 Decimal min = GetMinimum();
6964 if (!min.isFinite()) {
6965 return {};
6967 Decimal max = GetMaximum();
6968 if (!max.isFinite()) {
6969 return {};
6971 Decimal step = GetStep();
6972 if (step == kStepAny) {
6973 return {};
6975 MOZ_ASSERT(step.isFinite());
6976 RenderSize size = RenderSize::From(min).Max(
6977 RenderSize::From(max).Max(RenderSize::From(step)));
6978 return Some(size.mBeforeDecimal + size.mAfterDecimal +
6979 (size.mAfterDecimal ? 1 : 0));
6982 Maybe<int32_t> HTMLInputElement::GetCols() {
6983 if (const nsAttrValue* attr = GetParsedAttr(nsGkAtoms::size);
6984 attr && attr->Type() == nsAttrValue::eInteger) {
6985 int32_t cols = attr->GetIntegerValue();
6986 if (cols > 0) {
6987 return Some(cols);
6991 if (Maybe<int32_t> cols = GetNumberInputCols(); cols && *cols > 0) {
6992 return cols;
6995 return {};
6998 int32_t HTMLInputElement::GetWrapCols() {
6999 return 0; // only textarea's can have wrap cols
7002 int32_t HTMLInputElement::GetRows() { return DEFAULT_ROWS; }
7004 void HTMLInputElement::GetDefaultValueFromContent(nsAString& aValue,
7005 bool aForDisplay) {
7006 if (!GetEditorState()) {
7007 return;
7009 GetDefaultValue(aValue);
7010 // This is called by the frame to show the value.
7011 // We have to sanitize it when needed.
7012 // FIXME: Do we want to sanitize even when aForDisplay is false?
7013 if (mDoneCreating) {
7014 SanitizeValue(aValue, aForDisplay ? SanitizationKind::ForDisplay
7015 : SanitizationKind::ForValueGetter);
7019 bool HTMLInputElement::ValueChanged() const { return mValueChanged; }
7021 void HTMLInputElement::GetTextEditorValue(nsAString& aValue) const {
7022 if (TextControlState* state = GetEditorState()) {
7023 state->GetValue(aValue, /* aIgnoreWrap = */ true, /* aForDisplay = */ true);
7027 void HTMLInputElement::InitializeKeyboardEventListeners() {
7028 TextControlState* state = GetEditorState();
7029 if (state) {
7030 state->InitializeKeyboardEventListeners();
7034 void HTMLInputElement::UpdatePlaceholderShownState() {
7035 SetStates(ElementState::PLACEHOLDER_SHOWN,
7036 IsValueEmpty() && PlaceholderApplies() &&
7037 HasAttr(nsGkAtoms::placeholder));
7040 void HTMLInputElement::OnValueChanged(ValueChangeKind aKind,
7041 bool aNewValueEmpty,
7042 const nsAString* aKnownNewValue) {
7043 MOZ_ASSERT_IF(aKnownNewValue, aKnownNewValue->IsEmpty() == aNewValueEmpty);
7044 if (aKind != ValueChangeKind::Internal) {
7045 mLastValueChangeWasInteractive = aKind == ValueChangeKind::UserInteraction;
7047 if (mLastValueChangeWasInteractive &&
7048 State().HasState(ElementState::AUTOFILL)) {
7049 RemoveStates(ElementState::AUTOFILL | ElementState::AUTOFILL_PREVIEW);
7053 if (aNewValueEmpty != IsValueEmpty()) {
7054 SetStates(ElementState::VALUE_EMPTY, aNewValueEmpty);
7055 UpdatePlaceholderShownState();
7058 UpdateAllValidityStates(true);
7060 ResetDirFormAssociatedElement(this, true, HasDirAuto(), aKnownNewValue);
7063 bool HTMLInputElement::HasCachedSelection() {
7064 TextControlState* state = GetEditorState();
7065 if (!state) {
7066 return false;
7068 return state->IsSelectionCached() && state->HasNeverInitializedBefore() &&
7069 state->GetSelectionProperties().GetStart() !=
7070 state->GetSelectionProperties().GetEnd();
7073 void HTMLInputElement::SetRevealPassword(bool aValue) {
7074 if (NS_WARN_IF(mType != FormControlType::InputPassword)) {
7075 return;
7077 if (aValue == State().HasState(ElementState::REVEALED)) {
7078 return;
7080 RefPtr doc = OwnerDoc();
7081 // We allow chrome code to prevent this. This is important for about:logins,
7082 // which may need to run some OS-dependent authentication code before
7083 // revealing the saved passwords.
7084 bool defaultAction = true;
7085 nsContentUtils::DispatchEventOnlyToChrome(
7086 doc, this, u"MozWillToggleReveal"_ns, CanBubble::eYes, Cancelable::eYes,
7087 &defaultAction);
7088 if (NS_WARN_IF(!defaultAction)) {
7089 return;
7091 SetStates(ElementState::REVEALED, aValue);
7094 bool HTMLInputElement::RevealPassword() const {
7095 if (NS_WARN_IF(mType != FormControlType::InputPassword)) {
7096 return false;
7098 return State().HasState(ElementState::REVEALED);
7101 void HTMLInputElement::FieldSetDisabledChanged(bool aNotify) {
7102 // This *has* to be called *before* UpdateBarredFromConstraintValidation and
7103 // UpdateValueMissingValidityState because these two functions depend on our
7104 // disabled state.
7105 nsGenericHTMLFormControlElementWithState::FieldSetDisabledChanged(aNotify);
7107 UpdateValueMissingValidityState();
7108 UpdateBarredFromConstraintValidation();
7109 UpdateValidityElementStates(aNotify);
7112 void HTMLInputElement::SetFilePickerFiltersFromAccept(
7113 nsIFilePicker* filePicker) {
7114 // We always add |filterAll|
7115 filePicker->AppendFilters(nsIFilePicker::filterAll);
7117 NS_ASSERTION(HasAttr(nsGkAtoms::accept),
7118 "You should not call SetFilePickerFiltersFromAccept if the"
7119 " element has no accept attribute!");
7121 // Services to retrieve image/*, audio/*, video/* filters
7122 nsCOMPtr<nsIStringBundleService> stringService =
7123 components::StringBundle::Service();
7124 if (!stringService) {
7125 return;
7127 nsCOMPtr<nsIStringBundle> filterBundle;
7128 if (NS_FAILED(stringService->CreateBundle(
7129 "chrome://global/content/filepicker.properties",
7130 getter_AddRefs(filterBundle)))) {
7131 return;
7134 // Service to retrieve mime type information for mime types filters
7135 nsCOMPtr<nsIMIMEService> mimeService = do_GetService("@mozilla.org/mime;1");
7136 if (!mimeService) {
7137 return;
7140 nsAutoString accept;
7141 GetAttr(nsGkAtoms::accept, accept);
7143 HTMLSplitOnSpacesTokenizer tokenizer(accept, ',');
7145 nsTArray<nsFilePickerFilter> filters;
7146 nsString allExtensionsList;
7148 // Retrieve all filters
7149 while (tokenizer.hasMoreTokens()) {
7150 const nsDependentSubstring& token = tokenizer.nextToken();
7152 if (token.IsEmpty()) {
7153 continue;
7156 int32_t filterMask = 0;
7157 nsString filterName;
7158 nsString extensionListStr;
7160 // First, check for image/audio/video filters...
7161 if (token.EqualsLiteral("image/*")) {
7162 filterMask = nsIFilePicker::filterImages;
7163 filterBundle->GetStringFromName("imageFilter", extensionListStr);
7164 } else if (token.EqualsLiteral("audio/*")) {
7165 filterMask = nsIFilePicker::filterAudio;
7166 filterBundle->GetStringFromName("audioFilter", extensionListStr);
7167 } else if (token.EqualsLiteral("video/*")) {
7168 filterMask = nsIFilePicker::filterVideo;
7169 filterBundle->GetStringFromName("videoFilter", extensionListStr);
7170 } else if (token.First() == '.') {
7171 if (token.Contains(';') || token.Contains('*')) {
7172 // Ignore this filter as it contains reserved characters
7173 continue;
7175 extensionListStr = u"*"_ns + token;
7176 filterName = extensionListStr;
7177 } else {
7178 //... if no image/audio/video filter is found, check mime types filters
7179 nsCOMPtr<nsIMIMEInfo> mimeInfo;
7180 if (NS_FAILED(
7181 mimeService->GetFromTypeAndExtension(NS_ConvertUTF16toUTF8(token),
7182 ""_ns, // No extension
7183 getter_AddRefs(mimeInfo))) ||
7184 !mimeInfo) {
7185 continue;
7188 // Get a name for the filter: first try the description, then the mime
7189 // type name if there is no description
7190 mimeInfo->GetDescription(filterName);
7191 if (filterName.IsEmpty()) {
7192 nsCString mimeTypeName;
7193 mimeInfo->GetType(mimeTypeName);
7194 CopyUTF8toUTF16(mimeTypeName, filterName);
7197 // Get extension list
7198 nsCOMPtr<nsIUTF8StringEnumerator> extensions;
7199 mimeInfo->GetFileExtensions(getter_AddRefs(extensions));
7201 bool hasMore;
7202 while (NS_SUCCEEDED(extensions->HasMore(&hasMore)) && hasMore) {
7203 nsCString extension;
7204 if (NS_FAILED(extensions->GetNext(extension))) {
7205 continue;
7207 if (!extensionListStr.IsEmpty()) {
7208 extensionListStr.AppendLiteral("; ");
7210 extensionListStr += u"*."_ns + NS_ConvertUTF8toUTF16(extension);
7214 if (!filterMask && (extensionListStr.IsEmpty() || filterName.IsEmpty())) {
7215 // No valid filter found
7216 continue;
7219 // At this point we're sure the token represents a valid filter, so pass
7220 // it directly as a raw filter.
7221 filePicker->AppendRawFilter(token);
7223 // If we arrived here, that means we have a valid filter: let's create it
7224 // and add it to our list, if no similar filter is already present
7225 nsFilePickerFilter filter;
7226 if (filterMask) {
7227 filter = nsFilePickerFilter(filterMask);
7228 } else {
7229 filter = nsFilePickerFilter(filterName, extensionListStr);
7232 if (!filters.Contains(filter)) {
7233 if (!allExtensionsList.IsEmpty()) {
7234 allExtensionsList.AppendLiteral("; ");
7236 allExtensionsList += extensionListStr;
7237 filters.AppendElement(filter);
7241 // Remove similar filters
7242 // Iterate over a copy, as we might modify the original filters list
7243 const nsTArray<nsFilePickerFilter> filtersCopy = filters.Clone();
7244 for (uint32_t i = 0; i < filtersCopy.Length(); ++i) {
7245 const nsFilePickerFilter& filterToCheck = filtersCopy[i];
7246 if (filterToCheck.mFilterMask) {
7247 continue;
7249 for (uint32_t j = 0; j < filtersCopy.Length(); ++j) {
7250 if (i == j) {
7251 continue;
7253 // Check if this filter's extension list is a substring of the other one.
7254 // e.g. if filters are "*.jpeg" and "*.jpeg; *.jpg" the first one should
7255 // be removed.
7256 // Add an extra "; " to be sure the check will work and avoid cases like
7257 // "*.xls" being a subtring of "*.xslx" while those are two differents
7258 // filters and none should be removed.
7259 if (FindInReadable(filterToCheck.mFilter + u";"_ns,
7260 filtersCopy[j].mFilter + u";"_ns)) {
7261 // We already have a similar, less restrictive filter (i.e.
7262 // filterToCheck extensionList is just a subset of another filter
7263 // extension list): remove this one
7264 filters.RemoveElement(filterToCheck);
7269 // Add "All Supported Types" filter
7270 if (filters.Length() > 1) {
7271 nsAutoString title;
7272 nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
7273 "AllSupportedTypes", title);
7274 filePicker->AppendFilter(title, allExtensionsList);
7277 // Add each filter
7278 for (uint32_t i = 0; i < filters.Length(); ++i) {
7279 const nsFilePickerFilter& filter = filters[i];
7280 if (filter.mFilterMask) {
7281 filePicker->AppendFilters(filter.mFilterMask);
7282 } else {
7283 filePicker->AppendFilter(filter.mTitle, filter.mFilter);
7287 if (filters.Length() >= 1) {
7288 // |filterAll| will always use index=0 so we need to set index=1 as the
7289 // current filter. This will be "All Supported Types" for multiple filters.
7290 filePicker->SetFilterIndex(1);
7294 Decimal HTMLInputElement::GetStepScaleFactor() const {
7295 MOZ_ASSERT(DoesStepApply());
7297 switch (mType) {
7298 case FormControlType::InputDate:
7299 return kStepScaleFactorDate;
7300 case FormControlType::InputNumber:
7301 case FormControlType::InputRange:
7302 return kStepScaleFactorNumberRange;
7303 case FormControlType::InputTime:
7304 case FormControlType::InputDatetimeLocal:
7305 return kStepScaleFactorTime;
7306 case FormControlType::InputMonth:
7307 return kStepScaleFactorMonth;
7308 case FormControlType::InputWeek:
7309 return kStepScaleFactorWeek;
7310 default:
7311 MOZ_ASSERT(false, "Unrecognized input type");
7312 return Decimal::nan();
7316 Decimal HTMLInputElement::GetDefaultStep() const {
7317 MOZ_ASSERT(DoesStepApply());
7319 switch (mType) {
7320 case FormControlType::InputDate:
7321 case FormControlType::InputMonth:
7322 case FormControlType::InputWeek:
7323 case FormControlType::InputNumber:
7324 case FormControlType::InputRange:
7325 return kDefaultStep;
7326 case FormControlType::InputTime:
7327 case FormControlType::InputDatetimeLocal:
7328 return kDefaultStepTime;
7329 default:
7330 MOZ_ASSERT(false, "Unrecognized input type");
7331 return Decimal::nan();
7335 void HTMLInputElement::SetUserInteracted(bool aInteracted) {
7336 if (mUserInteracted == aInteracted) {
7337 return;
7339 mUserInteracted = aInteracted;
7340 UpdateValidityElementStates(true);
7343 void HTMLInputElement::UpdateInRange(bool aNotify) {
7344 AutoStateChangeNotifier notifier(*this, aNotify);
7345 RemoveStatesSilently(ElementState::INRANGE | ElementState::OUTOFRANGE);
7346 if (!mHasRange || !IsCandidateForConstraintValidation()) {
7347 return;
7349 bool outOfRange = GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW) ||
7350 GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW);
7351 AddStatesSilently(outOfRange ? ElementState::OUTOFRANGE
7352 : ElementState::INRANGE);
7355 void HTMLInputElement::UpdateHasRange(bool aNotify) {
7356 // There is a range if min/max applies for the type and if the element
7357 // currently have a valid min or max.
7358 const bool newHasRange = [&] {
7359 if (!DoesMinMaxApply()) {
7360 return false;
7362 return !GetMinimum().isNaN() || !GetMaximum().isNaN();
7363 }();
7365 if (newHasRange == mHasRange) {
7366 return;
7369 mHasRange = newHasRange;
7370 UpdateInRange(aNotify);
7373 void HTMLInputElement::PickerClosed() { mPickerRunning = false; }
7375 JSObject* HTMLInputElement::WrapNode(JSContext* aCx,
7376 JS::Handle<JSObject*> aGivenProto) {
7377 return HTMLInputElement_Binding::Wrap(aCx, this, aGivenProto);
7380 GetFilesHelper* HTMLInputElement::GetOrCreateGetFilesHelper(bool aRecursiveFlag,
7381 ErrorResult& aRv) {
7382 MOZ_ASSERT(mFileData);
7384 if (aRecursiveFlag) {
7385 if (!mFileData->mGetFilesRecursiveHelper) {
7386 mFileData->mGetFilesRecursiveHelper = GetFilesHelper::Create(
7387 GetFilesOrDirectoriesInternal(), aRecursiveFlag, aRv);
7388 if (NS_WARN_IF(aRv.Failed())) {
7389 return nullptr;
7393 return mFileData->mGetFilesRecursiveHelper;
7396 if (!mFileData->mGetFilesNonRecursiveHelper) {
7397 mFileData->mGetFilesNonRecursiveHelper = GetFilesHelper::Create(
7398 GetFilesOrDirectoriesInternal(), aRecursiveFlag, aRv);
7399 if (NS_WARN_IF(aRv.Failed())) {
7400 return nullptr;
7404 return mFileData->mGetFilesNonRecursiveHelper;
7407 void HTMLInputElement::UpdateEntries(
7408 const nsTArray<OwningFileOrDirectory>& aFilesOrDirectories) {
7409 MOZ_ASSERT(mFileData && mFileData->mEntries.IsEmpty());
7411 nsCOMPtr<nsIGlobalObject> global = OwnerDoc()->GetScopeObject();
7412 MOZ_ASSERT(global);
7414 RefPtr<FileSystem> fs = FileSystem::Create(global);
7415 if (NS_WARN_IF(!fs)) {
7416 return;
7419 Sequence<RefPtr<FileSystemEntry>> entries;
7420 for (uint32_t i = 0; i < aFilesOrDirectories.Length(); ++i) {
7421 RefPtr<FileSystemEntry> entry =
7422 FileSystemEntry::Create(global, aFilesOrDirectories[i], fs);
7423 MOZ_ASSERT(entry);
7425 if (!entries.AppendElement(entry, fallible)) {
7426 return;
7430 // The root fileSystem is a DirectoryEntry object that contains only the
7431 // dropped fileEntry and directoryEntry objects.
7432 fs->CreateRoot(entries);
7434 mFileData->mEntries = std::move(entries);
7437 void HTMLInputElement::GetWebkitEntries(
7438 nsTArray<RefPtr<FileSystemEntry>>& aSequence) {
7439 if (NS_WARN_IF(mType != FormControlType::InputFile)) {
7440 return;
7443 Telemetry::Accumulate(Telemetry::BLINK_FILESYSTEM_USED, true);
7444 aSequence.AppendElements(mFileData->mEntries);
7447 already_AddRefed<nsINodeList> HTMLInputElement::GetLabels() {
7448 if (!IsLabelable()) {
7449 return nullptr;
7452 return nsGenericHTMLElement::Labels();
7455 void HTMLInputElement::MaybeFireInputPasswordRemoved() {
7456 // We want this event to be fired only when the password field is removed
7457 // from the DOM tree, not when it is released (ex, tab is closed). So don't
7458 // fire an event when the password input field doesn't have a docshell.
7459 Document* doc = GetComposedDoc();
7460 nsIDocShell* container = doc ? doc->GetDocShell() : nullptr;
7461 if (!container) {
7462 return;
7465 // Right now, only the password manager listens to the event and only listen
7466 // to it under certain circumstances. So don't fire this event unless
7467 // necessary.
7468 if (!doc->ShouldNotifyFormOrPasswordRemoved()) {
7469 return;
7472 AsyncEventDispatcher::RunDOMEventWhenSafe(
7473 *this, u"DOMInputPasswordRemoved"_ns, CanBubble::eNo,
7474 ChromeOnlyDispatch::eYes);
7477 } // namespace mozilla::dom
7479 #undef NS_ORIGINAL_CHECKED_VALUE