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